krz/org-live
An org-mode editor with live preview.
clone: git clone https://gitbay.org/krz/org-live.git
1// Generated by export.rb at Sat Feb 21 07:44:29 UTC 2015
2/*
3 Copyright (c) 2014 Masafumi Oyamada
4
5 Permission is hereby granted, free of charge, to any person obtaining a copy
6 of this software and associated documentation files (the "Software"), to deal
7 in the Software without restriction, including without limitation the rights
8 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 copies of the Software, and to permit persons to whom the Software is
10 furnished to do so, subject to the following conditions:
11
12 The above copyright notice and this permission notice shall be included in
13 all copies or substantial portions of the Software.
14
15 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 THE SOFTWARE.
22*/
23
24var Org = (function () {
25 var exports = {};
26
27 // ------------------------------------------------------------
28 // Syntax
29 // ------------------------------------------------------------
30
31 var Syntax = {
32 rules: {},
33
34 define: function (name, syntax) {
35 this.rules[name] = syntax;
36 var methodName = "is" + name.substring(0, 1).toUpperCase() + name.substring(1);
37 this[methodName] = function (line) {
38 return this.rules[name].exec(line);
39 };
40 }
41 };
42
43 Syntax.define("header", /^(\*+)\s+(.*)$/); // m[1] => level, m[2] => content
44 Syntax.define("preformatted", /^(\s*):(?: (.*)$|$)/); // m[1] => indentation, m[2] => content
45 Syntax.define("unorderedListElement", /^(\s*)(?:-|\+|\s+\*)\s+(.*)$/); // m[1] => indentation, m[2] => content
46 Syntax.define("orderedListElement", /^(\s*)(\d+)(?:\.|\))\s+(.*)$/); // m[1] => indentation, m[2] => number, m[3] => content
47 Syntax.define("tableSeparator", /^(\s*)\|((?:\+|-)*?)\|?$/); // m[1] => indentation, m[2] => content
48 Syntax.define("tableRow", /^(\s*)\|(.*?)\|?$/); // m[1] => indentation, m[2] => content
49 Syntax.define("blank", /^$/);
50 Syntax.define("horizontalRule", /^(\s*)-{5,}$/); //
51 Syntax.define("directive", /^(\s*)#\+(?:(begin|end)_)?(.*)$/i); // m[1] => indentation, m[2] => type, m[3] => content
52 Syntax.define("comment", /^(\s*)#(.*)$/);
53 Syntax.define("line", /^(\s*)(.*)$/);
54
55 // ------------------------------------------------------------
56 // Token
57 // ------------------------------------------------------------
58
59 function Token() {
60 }
61
62 Token.prototype = {
63 isListElement: function () {
64 return this.type === Lexer.tokens.orderedListElement ||
65 this.type === Lexer.tokens.unorderedListElement;
66 },
67
68 isTableElement: function () {
69 return this.type === Lexer.tokens.tableSeparator ||
70 this.type === Lexer.tokens.tableRow;
71 }
72 };
73
74 // ------------------------------------------------------------
75 // Lexer
76 // ------------------------------------------------------------
77
78 function Lexer(stream) {
79 this.stream = stream;
80 this.tokenStack = [];
81 }
82
83 Lexer.prototype = {
84 tokenize: function (line) {
85 var token = new Token();
86 token.fromLineNumber = this.stream.lineNumber;
87
88 if (Syntax.isHeader(line)) {
89 token.type = Lexer.tokens.header;
90 token.indentation = 0;
91 token.content = RegExp.$2;
92 // specific
93 token.level = RegExp.$1.length;
94 } else if (Syntax.isPreformatted(line)) {
95 token.type = Lexer.tokens.preformatted;
96 token.indentation = RegExp.$1.length;
97 token.content = RegExp.$2;
98 } else if (Syntax.isUnorderedListElement(line)) {
99 token.type = Lexer.tokens.unorderedListElement;
100 token.indentation = RegExp.$1.length;
101 token.content = RegExp.$2;
102 } else if (Syntax.isOrderedListElement(line)) {
103 token.type = Lexer.tokens.orderedListElement;
104 token.indentation = RegExp.$1.length;
105 token.content = RegExp.$3;
106 // specific
107 token.number = RegExp.$2;
108 } else if (Syntax.isTableSeparator(line)) {
109 token.type = Lexer.tokens.tableSeparator;
110 token.indentation = RegExp.$1.length;
111 token.content = RegExp.$2;
112 } else if (Syntax.isTableRow(line)) {
113 token.type = Lexer.tokens.tableRow;
114 token.indentation = RegExp.$1.length;
115 token.content = RegExp.$2;
116 } else if (Syntax.isBlank(line)) {
117 token.type = Lexer.tokens.blank;
118 token.indentation = 0;
119 token.content = null;
120 } else if (Syntax.isHorizontalRule(line)) {
121 token.type = Lexer.tokens.horizontalRule;
122 token.indentation = RegExp.$1.length;
123 token.content = null;
124 } else if (Syntax.isDirective(line)) {
125 token.type = Lexer.tokens.directive;
126 token.indentation = RegExp.$1.length;
127 token.content = RegExp.$3;
128 // decide directive type (begin, end or oneshot)
129 var directiveTypeString = RegExp.$2;
130 if (/^begin/i.test(directiveTypeString))
131 token.beginDirective = true;
132 else if (/^end/i.test(directiveTypeString))
133 token.endDirective = true;
134 else
135 token.oneshotDirective = true;
136 } else if (Syntax.isComment(line)) {
137 token.type = Lexer.tokens.comment;
138 token.indentation = RegExp.$1.length;
139 token.content = RegExp.$2;
140 } else if (Syntax.isLine(line)) {
141 token.type = Lexer.tokens.line;
142 token.indentation = RegExp.$1.length;
143 token.content = RegExp.$2;
144 } else {
145 throw new Error("SyntaxError: Unknown line: " + line);
146 }
147
148 return token;
149 },
150
151 pushToken: function (token) {
152 this.tokenStack.push(token);
153 },
154
155 pushDummyTokenByType: function (type) {
156 var token = new Token();
157 token.type = type;
158 this.tokenStack.push(token);
159 },
160
161 peekStackedToken: function () {
162 return this.tokenStack.length > 0 ?
163 this.tokenStack[this.tokenStack.length - 1] : null;
164 },
165
166 getStackedToken: function () {
167 return this.tokenStack.length > 0 ?
168 this.tokenStack.pop() : null;
169 },
170
171 peekNextToken: function () {
172 return this.peekStackedToken() ||
173 this.tokenize(this.stream.peekNextLine());
174 },
175
176 getNextToken: function () {
177 return this.getStackedToken() ||
178 this.tokenize(this.stream.getNextLine());
179 },
180
181 hasNext: function () {
182 return this.stream.hasNext();
183 },
184
185 getLineNumber: function () {
186 return this.stream.lineNumber;
187 }
188 };
189
190 Lexer.tokens = {};
191 [
192 "header",
193 "orderedListElement",
194 "unorderedListElement",
195 "tableRow",
196 "tableSeparator",
197 "preformatted",
198 "line",
199 "horizontalRule",
200 "blank",
201 "directive",
202 "comment"
203 ].forEach(function (tokenName, i) {
204 Lexer.tokens[tokenName] = i;
205 });
206
207 // ------------------------------------------------------------
208 // Exports
209 // ------------------------------------------------------------
210
211 if (typeof exports !== "undefined")
212 exports.Lexer = Lexer;
213
214 function PrototypeNode(type, children) {
215 this.type = type;
216 this.children = [];
217
218 if (children) {
219 for (var i = 0, len = children.length; i < len; ++i) {
220 this.appendChild(children[i]);
221 }
222 }
223 }
224 PrototypeNode.prototype = {
225 previousSibling: null,
226 parent: null,
227 get firstChild() {
228 return this.children.length < 1 ?
229 null : this.children[0];
230 },
231 get lastChild() {
232 return this.children.length < 1 ?
233 null : this.children[this.children.length - 1];
234 },
235 appendChild: function (newChild) {
236 var previousSibling = this.children.length < 1 ?
237 null : this.lastChild;
238 this.children.push(newChild);
239 newChild.previousSibling = previousSibling;
240 newChild.parent = this;
241 },
242 toString: function () {
243 var string = "<" + this.type + ">";
244
245 if (typeof this.value !== "undefined") {
246 string += " " + this.value;
247 } else if (this.children) {
248 string += "\n" + this.children.map(function (child, idx) {
249 return "#" + idx + " " + child.toString();
250 }).join("\n").split("\n").map(function (line) {
251 return " " + line;
252 }).join("\n");
253 }
254
255 return string;
256 }
257 };
258
259 var Node = {
260 types: {},
261
262 define: function (name, postProcess) {
263 this.types[name] = name;
264
265 var methodName = "create" + name.substring(0, 1).toUpperCase() + name.substring(1);
266 var postProcessGiven = typeof postProcess === "function";
267
268 this[methodName] = function (children, options) {
269 var node = new PrototypeNode(name, children);
270
271 if (postProcessGiven)
272 postProcess(node, options || {});
273
274 return node;
275 };
276 }
277 };
278
279 Node.define("text", function (node, options) {
280 node.value = options.value;
281 });
282 Node.define("header", function (node, options) {
283 node.level = options.level;
284 });
285 Node.define("orderedList");
286 Node.define("unorderedList");
287 Node.define("definitionList");
288 Node.define("listElement");
289 Node.define("paragraph");
290 Node.define("preformatted");
291 Node.define("table");
292 Node.define("tableRow");
293 Node.define("tableCell");
294 Node.define("horizontalRule");
295 Node.define("directive");
296
297 // Inline
298 Node.define("inlineContainer");
299
300 Node.define("bold");
301 Node.define("italic");
302 Node.define("underline");
303 Node.define("code");
304 Node.define("verbatim");
305 Node.define("dashed");
306 Node.define("link", function (node, options) {
307 node.src = options.src;
308 });
309
310 if (typeof exports !== "undefined")
311 exports.Node = Node;
312
313 function Stream(sequence) {
314 this.sequences = sequence.split(/\r?\n/);
315 this.totalLines = this.sequences.length;
316 this.lineNumber = 0;
317 }
318
319 Stream.prototype.peekNextLine = function () {
320 return this.hasNext() ? this.sequences[this.lineNumber] : null;
321 };
322
323 Stream.prototype.getNextLine = function () {
324 return this.hasNext() ? this.sequences[this.lineNumber++] : null;
325 };
326
327 Stream.prototype.hasNext = function () {
328 return this.lineNumber < this.totalLines;
329 };
330
331 if (typeof exports !== "undefined") {
332 exports.Stream = Stream;
333 }
334
335 // var Stream = require("./stream.js").Stream;
336 // var Lexer = require("./lexer.js").Lexer;
337 // var Node = require("./node.js").Node;
338
339 function Parser() {
340 this.inlineParser = new InlineParser();
341 }
342
343 Parser.parseStream = function (stream, options) {
344 var parser = new Parser();
345 parser.initStatus(stream, options);
346 parser.parseNodes();
347 return parser.nodes;
348 };
349
350 Parser.prototype = {
351 initStatus: function (stream, options) {
352 if (typeof stream === "string")
353 stream = new Stream(stream);
354 this.lexer = new Lexer(stream);
355 this.nodes = [];
356 this.options = {
357 toc: true,
358 num: true,
359 "^": "{}",
360 multilineCell: false
361 };
362 // Override option values
363 if (options && typeof options === "object") {
364 for (var key in options) {
365 this.options[key] = options[key];
366 }
367 }
368 this.document = {
369 options: this.options,
370 directiveValues: {},
371 convert: function (ConverterClass, exportOptions) {
372 var converter = new ConverterClass(this, exportOptions);
373 return converter.result;
374 }
375 };
376 },
377
378 parse: function (stream, options) {
379 this.initStatus(stream, options);
380 this.parseDocument();
381 this.document.nodes = this.nodes;
382 return this.document;
383 },
384
385 createErrorReport: function (message) {
386 return new Error(message + " at line " + this.lexer.getLineNumber());
387 },
388
389 skipBlank: function () {
390 var blankToken = null;
391 while (this.lexer.peekNextToken().type === Lexer.tokens.blank)
392 blankToken = this.lexer.getNextToken();
393 return blankToken;
394 },
395
396 setNodeOriginFromToken: function (node, token) {
397 node.fromLineNumber = token.fromLineNumber;
398 return node;
399 },
400
401 appendNode: function (newNode) {
402 var previousSibling = this.nodes.length > 0 ? this.nodes[this.nodes.length - 1] : null;
403 this.nodes.push(newNode);
404 newNode.previousSibling = previousSibling;
405 },
406
407 // ------------------------------------------------------------
408 // <Document> ::= <Element>*
409 // ------------------------------------------------------------
410
411 parseDocument: function () {
412 this.parseTitle();
413 this.parseNodes();
414 },
415
416 parseNodes: function () {
417 while (this.lexer.hasNext()) {
418 var element = this.parseElement();
419 if (element) this.appendNode(element);
420 }
421 },
422
423 parseTitle: function () {
424 this.skipBlank();
425
426 if (this.lexer.hasNext() &&
427 this.lexer.peekNextToken().type === Lexer.tokens.line)
428 this.document.title = this.createTextNode(this.lexer.getNextToken().content);
429 else
430 this.document.title = null;
431
432 this.lexer.pushDummyTokenByType(Lexer.tokens.blank);
433 },
434
435 // ------------------------------------------------------------
436 // <Element> ::= (<Header> | <List>
437 // | <Preformatted> | <Paragraph>
438 // | <Table>)*
439 // ------------------------------------------------------------
440
441 parseElement: function () {
442 var element = null;
443
444 switch (this.lexer.peekNextToken().type) {
445 case Lexer.tokens.header:
446 element = this.parseHeader();
447 break;
448 case Lexer.tokens.preformatted:
449 element = this.parsePreformatted();
450 break;
451 case Lexer.tokens.orderedListElement:
452 case Lexer.tokens.unorderedListElement:
453 element = this.parseList();
454 break;
455 case Lexer.tokens.line:
456 element = this.parseText();
457 break;
458 case Lexer.tokens.tableRow:
459 case Lexer.tokens.tableSeparator:
460 element = this.parseTable();
461 break;
462 case Lexer.tokens.blank:
463 this.skipBlank();
464 if (this.lexer.hasNext()) {
465 if (this.lexer.peekNextToken().type === Lexer.tokens.line)
466 element = this.parseParagraph();
467 else
468 element = this.parseElement();
469 }
470 break;
471 case Lexer.tokens.horizontalRule:
472 this.lexer.getNextToken();
473 element = Node.createHorizontalRule();
474 break;
475 case Lexer.tokens.directive:
476 element = this.parseDirective();
477 break;
478 case Lexer.tokens.comment:
479 // Skip
480 this.lexer.getNextToken();
481 break;
482 default:
483 throw this.createErrorReport("Unhandled token: " + this.lexer.peekNextToken().type);
484 }
485
486 return element;
487 },
488
489 parseElementBesidesDirectiveEnd: function () {
490 try {
491 // Temporary, override the definition of `parseElement`
492 this.parseElement = this.parseElementBesidesDirectiveEndBody;
493 return this.parseElement();
494 } finally {
495 this.parseElement = this.originalParseElement;
496 }
497 },
498
499 parseElementBesidesDirectiveEndBody: function () {
500 if (this.lexer.peekNextToken().type === Lexer.tokens.directive &&
501 this.lexer.peekNextToken().endDirective) {
502 return null;
503 }
504
505 return this.originalParseElement();
506 },
507
508 // ------------------------------------------------------------
509 // <Header>
510 //
511 // : preformatted
512 // : block
513 // ------------------------------------------------------------
514
515 parseHeader: function () {
516 var headerToken = this.lexer.getNextToken();
517 var header = Node.createHeader([
518 this.createTextNode(headerToken.content) // TODO: Parse inline markups
519 ], { level: headerToken.level });
520 this.setNodeOriginFromToken(header, headerToken);
521
522 return header;
523 },
524
525 // ------------------------------------------------------------
526 // <Preformatted>
527 //
528 // : preformatted
529 // : block
530 // ------------------------------------------------------------
531
532 parsePreformatted: function () {
533 var preformattedFirstToken = this.lexer.peekNextToken();
534 var preformatted = Node.createPreformatted([]);
535 this.setNodeOriginFromToken(preformatted, preformattedFirstToken);
536
537 var textContents = [];
538
539 while (this.lexer.hasNext()) {
540 var token = this.lexer.peekNextToken();
541 if (token.type !== Lexer.tokens.preformatted ||
542 token.indentation < preformattedFirstToken.indentation)
543 break;
544 this.lexer.getNextToken();
545 textContents.push(token.content);
546 }
547
548 preformatted.appendChild(this.createTextNode(textContents.join("\n"), true /* no emphasis */));
549
550 return preformatted;
551 },
552
553 // ------------------------------------------------------------
554 // <List>
555 //
556 // - foo
557 // 1. bar
558 // 2. baz
559 // ------------------------------------------------------------
560
561 // XXX: not consider codes (e.g., =Foo::Bar=)
562 definitionPattern: /^(.*?) :: *(.*)$/,
563
564 parseList: function () {
565 var rootToken = this.lexer.peekNextToken();
566 var list;
567 var isDefinitionList = false;
568
569 if (this.definitionPattern.test(rootToken.content)) {
570 list = Node.createDefinitionList([]);
571 isDefinitionList = true;
572 } else {
573 list = rootToken.type === Lexer.tokens.unorderedListElement ?
574 Node.createUnorderedList([]) : Node.createOrderedList([]);
575 }
576 this.setNodeOriginFromToken(list, rootToken);
577
578 while (this.lexer.hasNext()) {
579 var nextToken = this.lexer.peekNextToken();
580 if (!nextToken.isListElement() || nextToken.indentation !== rootToken.indentation)
581 break;
582 list.appendChild(this.parseListElement(rootToken.indentation, isDefinitionList));
583 }
584
585 return list;
586 },
587
588 unknownDefinitionTerm: "???",
589
590 parseListElement: function (rootIndentation, isDefinitionList) {
591 var listElementToken = this.lexer.getNextToken();
592 var listElement = Node.createListElement([]);
593 this.setNodeOriginFromToken(listElement, listElementToken);
594
595 listElement.isDefinitionList = isDefinitionList;
596
597 if (isDefinitionList) {
598 var match = this.definitionPattern.exec(listElementToken.content);
599 listElement.term = [
600 this.createTextNode(match && match[1] ? match[1] : this.unknownDefinitionTerm)
601 ];
602 listElement.appendChild(this.createTextNode(match ? match[2] : listElementToken.content));
603 } else {
604 listElement.appendChild(this.createTextNode(listElementToken.content));
605 }
606
607 while (this.lexer.hasNext()) {
608 var blankToken = this.skipBlank();
609 if (!this.lexer.hasNext())
610 break;
611
612 var notBlankNextToken = this.lexer.peekNextToken();
613 if (blankToken && !notBlankNextToken.isListElement())
614 this.lexer.pushToken(blankToken); // Recover blank token only when next line is not listElement.
615 if (notBlankNextToken.indentation <= rootIndentation)
616 break; // end of the list
617
618 var element = this.parseElement(); // recursive
619 if (element)
620 listElement.appendChild(element);
621 }
622
623 return listElement;
624 },
625
626 // ------------------------------------------------------------
627 // <Table> ::= <TableRow>+
628 // ------------------------------------------------------------
629
630 parseTable: function () {
631 var nextToken = this.lexer.peekNextToken();
632 var table = Node.createTable([]);
633 this.setNodeOriginFromToken(table, nextToken);
634 var sawSeparator = false;
635
636 var allowMultilineCell = nextToken.type === Lexer.tokens.tableSeparator && this.options.multilineCell;
637
638 while (this.lexer.hasNext() &&
639 (nextToken = this.lexer.peekNextToken()).isTableElement()) {
640 if (nextToken.type === Lexer.tokens.tableRow) {
641 var tableRow = this.parseTableRow(allowMultilineCell);
642 table.appendChild(tableRow);
643 } else {
644 // Lexer.tokens.tableSeparator
645 sawSeparator = true;
646 this.lexer.getNextToken();
647 }
648 }
649
650 if (sawSeparator && table.children.length) {
651 table.children[0].children.forEach(function (cell) {
652 cell.isHeader = true;
653 });
654 }
655
656 return table;
657 },
658
659 // ------------------------------------------------------------
660 // <TableRow> ::= <TableCell>+
661 // ------------------------------------------------------------
662
663 parseTableRow: function (allowMultilineCell) {
664 var tableRowTokens = [];
665
666 while (this.lexer.peekNextToken().type === Lexer.tokens.tableRow) {
667 tableRowTokens.push(this.lexer.getNextToken());
668 if (!allowMultilineCell) {
669 break;
670 }
671 }
672
673 if (!tableRowTokens.length) {
674 throw this.createErrorReport("Expected table row");
675 }
676
677 var firstTableRowToken = tableRowTokens.shift();
678 var tableCellTexts = firstTableRowToken.content.split("|");
679
680 tableRowTokens.forEach(function (rowToken) {
681 rowToken.content.split("|").forEach(function (cellText, cellIdx) {
682 tableCellTexts[cellIdx] = (tableCellTexts[cellIdx] || "") + "\n" + cellText;
683 });
684 });
685
686 // TODO: Prepare two pathes: (1)
687 var tableCells = tableCellTexts.map(
688 // TODO: consider '|' escape?
689 function (text) {
690 return Node.createTableCell(Parser.parseStream(text));
691 }, this);
692
693 return this.setNodeOriginFromToken(Node.createTableRow(tableCells), firstTableRowToken);
694 },
695
696 // ------------------------------------------------------------
697 // <Directive> ::= "#+.*"
698 // ------------------------------------------------------------
699
700 parseDirective: function () {
701 var directiveToken = this.lexer.getNextToken();
702 var directiveNode = this.createDirectiveNodeFromToken(directiveToken);
703
704 if (directiveToken.endDirective)
705 throw this.createErrorReport("Unmatched 'end' directive for " + directiveNode.directiveName);
706
707 if (directiveToken.oneshotDirective) {
708 this.interpretDirective(directiveNode);
709 return directiveNode;
710 }
711
712 if (!directiveToken.beginDirective)
713 throw this.createErrorReport("Invalid directive " + directiveNode.directiveName);
714
715 // Parse begin ~ end
716 directiveNode.children = [];
717 if (this.isVerbatimDirective(directiveNode))
718 return this.parseDirectiveBlockVerbatim(directiveNode);
719 else
720 return this.parseDirectiveBlock(directiveNode);
721 },
722
723 createDirectiveNodeFromToken: function (directiveToken) {
724 var matched = /^[ ]*([^ ]*)[ ]*(.*)[ ]*$/.exec(directiveToken.content);
725
726 var directiveNode = Node.createDirective(null);
727 this.setNodeOriginFromToken(directiveNode, directiveToken);
728 directiveNode.directiveName = matched[1].toLowerCase();
729 directiveNode.directiveArguments = this.parseDirectiveArguments(matched[2]);
730 directiveNode.directiveOptions = this.parseDirectiveOptions(matched[2]);
731 directiveNode.directiveRawValue = matched[2];
732
733 return directiveNode;
734 },
735
736 isVerbatimDirective: function (directiveNode) {
737 var directiveName = directiveNode.directiveName;
738 return directiveName === "src" || directiveName === "example" || directiveName === "html";
739 },
740
741 parseDirectiveBlock: function (directiveNode, verbatim) {
742 this.lexer.pushDummyTokenByType(Lexer.tokens.blank);
743
744 while (this.lexer.hasNext()) {
745 var nextToken = this.lexer.peekNextToken();
746 if (nextToken.type === Lexer.tokens.directive &&
747 nextToken.endDirective &&
748 this.createDirectiveNodeFromToken(nextToken).directiveName === directiveNode.directiveName) {
749 // Close directive
750 this.lexer.getNextToken();
751 return directiveNode;
752 }
753 var element = this.parseElementBesidesDirectiveEnd();
754 if (element)
755 directiveNode.appendChild(element);
756 }
757
758 throw this.createErrorReport("Unclosed directive " + directiveNode.directiveName);
759 },
760
761 parseDirectiveBlockVerbatim: function (directiveNode) {
762 var textContent = [];
763
764 while (this.lexer.hasNext()) {
765 var nextToken = this.lexer.peekNextToken();
766 if (nextToken.type === Lexer.tokens.directive &&
767 nextToken.endDirective &&
768 this.createDirectiveNodeFromToken(nextToken).directiveName === directiveNode.directiveName) {
769 this.lexer.getNextToken();
770 directiveNode.appendChild(this.createTextNode(textContent.join("\n"), true));
771 return directiveNode;
772 }
773 textContent.push(this.lexer.stream.getNextLine());
774 }
775
776 throw this.createErrorReport("Unclosed directive " + directiveNode.directiveName);
777 },
778
779 parseDirectiveArguments: function (parameters) {
780 return parameters.split(/[ ]+/).filter(function (param) {
781 return param.length && param[0] !== "-";
782 });
783 },
784
785 parseDirectiveOptions: function (parameters) {
786 return parameters.split(/[ ]+/).filter(function (param) {
787 return param.length && param[0] === "-";
788 });
789 },
790
791 interpretDirective: function (directiveNode) {
792 // http://orgmode.org/manual/Export-options.html
793 switch (directiveNode.directiveName) {
794 case "options:":
795 this.interpretOptionDirective(directiveNode);
796 break;
797 case "title:":
798 this.document.title = directiveNode.directiveRawValue;
799 break;
800 case "author:":
801 this.document.author = directiveNode.directiveRawValue;
802 break;
803 case "email:":
804 this.document.email = directiveNode.directiveRawValue;
805 break;
806 default:
807 this.document.directiveValues[directiveNode.directiveName] = directiveNode.directiveRawValue;
808 break;
809 }
810 },
811
812 interpretOptionDirective: function (optionDirectiveNode) {
813 optionDirectiveNode.directiveArguments.forEach(function (pairString) {
814 var pair = pairString.split(":");
815 this.options[pair[0]] = this.convertLispyValue(pair[1]);
816 }, this);
817 },
818
819 convertLispyValue: function (lispyValue) {
820 switch (lispyValue) {
821 case "t":
822 return true;
823 case "nil":
824 return false;
825 default:
826 if (/^[0-9]+$/.test(lispyValue))
827 return parseInt(lispyValue);
828 return lispyValue;
829 }
830 },
831
832 // ------------------------------------------------------------
833 // <Paragraph> ::= <Blank> <Line>*
834 // ------------------------------------------------------------
835
836 parseParagraph: function () {
837 var paragraphFisrtToken = this.lexer.peekNextToken();
838 var paragraph = Node.createParagraph([]);
839 this.setNodeOriginFromToken(paragraph, paragraphFisrtToken);
840
841 var textContents = [];
842
843 while (this.lexer.hasNext()) {
844 var nextToken = this.lexer.peekNextToken();
845 if (nextToken.type !== Lexer.tokens.line
846 || nextToken.indentation < paragraphFisrtToken.indentation)
847 break;
848 this.lexer.getNextToken();
849 textContents.push(nextToken.content);
850 }
851
852 paragraph.appendChild(this.createTextNode(textContents.join("\n")));
853
854 return paragraph;
855 },
856
857 parseText: function (noEmphasis) {
858 var lineToken = this.lexer.getNextToken();
859 return this.createTextNode(lineToken.content, noEmphasis);
860 },
861
862 // ------------------------------------------------------------
863 // <Text> (DOM Like)
864 // ------------------------------------------------------------
865
866 createTextNode: function (text, noEmphasis) {
867 return noEmphasis ? Node.createText(null, { value: text })
868 : this.inlineParser.parseEmphasis(text);
869 }
870 };
871 Parser.prototype.originalParseElement = Parser.prototype.parseElement;
872
873 // ------------------------------------------------------------
874 // Parser for Inline Elements
875 //
876 // @refs org-emphasis-regexp-components
877 // ------------------------------------------------------------
878
879 function InlineParser() {
880 this.preEmphasis = " \t\\('\"";
881 this.postEmphasis = "- \t.,:!?;'\"\\)";
882 this.borderForbidden = " \t\r\n,\"'";
883 this.bodyRegexp = "[\\s\\S]*?";
884 this.markers = "*/_=~+";
885
886 this.emphasisPattern = this.buildEmphasisPattern();
887 this.linkPattern = /\[\[([^\]]*)\](?:\[([^\]]*)\])?\]/g; // \1 => link, \2 => text
888 }
889
890 InlineParser.prototype = {
891 parseEmphasis: function (text) {
892 var emphasisPattern = this.emphasisPattern;
893 emphasisPattern.lastIndex = 0;
894
895 var result = [],
896 match,
897 previousLast = 0,
898 savedLastIndex;
899
900 while ((match = emphasisPattern.exec(text))) {
901 var whole = match[0];
902 var pre = match[1];
903 var marker = match[2];
904 var body = match[3];
905 var post = match[4];
906
907 {
908 // parse links
909 var matchBegin = emphasisPattern.lastIndex - whole.length;
910 var beforeContent = text.substring(previousLast, matchBegin + pre.length);
911 savedLastIndex = emphasisPattern.lastIndex;
912 result.push(this.parseLink(beforeContent));
913 emphasisPattern.lastIndex = savedLastIndex;
914 }
915
916 var bodyNode = [Node.createText(null, { value: body })];
917 var bodyContainer = this.emphasizeElementByMarker(bodyNode, marker);
918 result.push(bodyContainer);
919
920 previousLast = emphasisPattern.lastIndex - post.length;
921 }
922
923 if (emphasisPattern.lastIndex === 0 ||
924 emphasisPattern.lastIndex !== text.length - 1)
925 result.push(this.parseLink(text.substring(previousLast)));
926
927 if (result.length === 1) {
928 // Avoid duplicated inline container wrapping
929 return result[0];
930 } else {
931 return Node.createInlineContainer(result);
932 }
933 },
934
935 depth: 0,
936 parseLink: function (text) {
937 var linkPattern = this.linkPattern;
938 linkPattern.lastIndex = 0;
939
940 var match,
941 result = [],
942 previousLast = 0,
943 savedLastIndex;
944
945 while ((match = linkPattern.exec(text))) {
946 var whole = match[0];
947 var src = match[1];
948 var title = match[2];
949
950 // parse before content
951 var matchBegin = linkPattern.lastIndex - whole.length;
952 var beforeContent = text.substring(previousLast, matchBegin);
953 result.push(Node.createText(null, { value: beforeContent }));
954
955 // parse link
956 var link = Node.createLink([]);
957 link.src = src;
958 if (title) {
959 savedLastIndex = linkPattern.lastIndex;
960 link.appendChild(this.parseEmphasis(title));
961 linkPattern.lastIndex = savedLastIndex;
962 } else {
963 link.appendChild(Node.createText(null, { value: src }));
964 }
965 result.push(link);
966
967 previousLast = linkPattern.lastIndex;
968 }
969
970 if (linkPattern.lastIndex === 0 ||
971 linkPattern.lastIndex !== text.length - 1)
972 result.push(Node.createText(null, { value: text.substring(previousLast) }));
973
974 return Node.createInlineContainer(result);
975 },
976
977 emphasizeElementByMarker: function (element, marker) {
978 switch (marker) {
979 case "*":
980 return Node.createBold(element);
981 case "/":
982 return Node.createItalic(element);
983 case "_":
984 return Node.createUnderline(element);
985 case "=":
986 case "~":
987 return Node.createCode(element);
988 case "+":
989 return Node.createDashed(element);
990 }
991 },
992
993 buildEmphasisPattern: function () {
994 return new RegExp(
995 "([" + this.preEmphasis + "]|^|\r?\n)" + // \1 => pre
996 "([" + this.markers + "])" + // \2 => marker
997 "([^" + this.borderForbidden + "]|" + // \3 => body
998 "[^" + this.borderForbidden + "]" +
999 this.bodyRegexp +
1000 "[^" + this.borderForbidden + "])" +
1001 "\\2" +
1002 "([" + this.postEmphasis +"]|$|\r?\n)", // \4 => post
1003 // flags
1004 "g"
1005 );
1006 }
1007 };
1008
1009 if (typeof exports !== "undefined") {
1010 exports.Parser = Parser;
1011 exports.InlineParser = InlineParser;
1012 }
1013
1014 // var Node = require("../node.js").Node;
1015
1016 function Converter() {
1017 }
1018
1019 Converter.prototype = {
1020 exportOptions: {
1021 headerOffset: 1,
1022 exportFromLineNumber: false,
1023 suppressSubScriptHandling: false,
1024 suppressAutoLink: false,
1025 // HTML
1026 translateSymbolArrow: false,
1027 suppressCheckboxHandling: false,
1028 // { "directive:": function (node, childText, auxData) {} }
1029 customDirectiveHandler: null,
1030 // e.g., "org-js-"
1031 htmlClassPrefix: null,
1032 htmlIdPrefix: null
1033 },
1034
1035 untitled: "Untitled",
1036 result: null,
1037
1038 // TODO: Manage TODO lists
1039
1040 initialize: function (orgDocument, exportOptions) {
1041 this.orgDocument = orgDocument;
1042 this.documentOptions = orgDocument.options || {};
1043 this.exportOptions = exportOptions || {};
1044
1045 this.headers = [];
1046 this.headerOffset =
1047 typeof this.exportOptions.headerOffset === "number" ? this.exportOptions.headerOffset : 1;
1048 this.sectionNumbers = [0];
1049 },
1050
1051 createTocItem: function (headerNode, parentTocs) {
1052 var childTocs = [];
1053 childTocs.parent = parentTocs;
1054 var tocItem = { headerNode: headerNode, childTocs: childTocs };
1055 return tocItem;
1056 },
1057
1058 computeToc: function (exportTocLevel) {
1059 if (typeof exportTocLevel !== "number")
1060 exportTocLevel = Infinity;
1061
1062 var toc = [];
1063 toc.parent = null;
1064
1065 var previousLevel = 1;
1066 var currentTocs = toc; // first
1067
1068 for (var i = 0; i < this.headers.length; ++i) {
1069 var headerNode = this.headers[i];
1070
1071 if (headerNode.level > exportTocLevel)
1072 continue;
1073
1074 var levelDiff = headerNode.level - previousLevel;
1075 if (levelDiff > 0) {
1076 for (var j = 0; j < levelDiff; ++j) {
1077 if (currentTocs.length === 0) {
1078 // Create a dummy tocItem
1079 var dummyHeader = Node.createHeader([], {
1080 level: previousLevel + j
1081 });
1082 dummyHeader.sectionNumberText = "";
1083 currentTocs.push(this.createTocItem(dummyHeader, currentTocs));
1084 }
1085 currentTocs = currentTocs[currentTocs.length - 1].childTocs;
1086 }
1087 } else if (levelDiff < 0) {
1088 levelDiff = -levelDiff;
1089 for (var k = 0; k < levelDiff; ++k) {
1090 currentTocs = currentTocs.parent;
1091 }
1092 }
1093
1094 currentTocs.push(this.createTocItem(headerNode, currentTocs));
1095
1096 previousLevel = headerNode.level;
1097 }
1098
1099 return toc;
1100 },
1101
1102 convertNode: function (node, recordHeader, insideCodeElement) {
1103 if (!insideCodeElement) {
1104 if (node.type === Node.types.directive) {
1105 if (node.directiveName === "example" ||
1106 node.directiveName === "src") {
1107 insideCodeElement = true;
1108 }
1109 } else if (node.type === Node.types.preformatted) {
1110 insideCodeElement = true;
1111 }
1112 }
1113
1114 if (typeof node === "string") {
1115 node = Node.createText(null, { value: node });
1116 }
1117
1118 var childText = node.children ? this.convertNodesInternal(node.children, recordHeader, insideCodeElement) : "";
1119 var text;
1120
1121 var auxData = this.computeAuxDataForNode(node);
1122
1123 switch (node.type) {
1124 case Node.types.header:
1125 // Parse task status
1126 var taskStatus = null;
1127 if (childText.indexOf("TODO ") === 0)
1128 taskStatus = "todo";
1129 else if (childText.indexOf("DONE ") === 0)
1130 taskStatus = "done";
1131
1132 // Compute section number
1133 var sectionNumberText = null;
1134 if (recordHeader) {
1135 var thisHeaderLevel = node.level;
1136 var previousHeaderLevel = this.sectionNumbers.length;
1137 if (thisHeaderLevel > previousHeaderLevel) {
1138 // Fill missing section number
1139 var levelDiff = thisHeaderLevel - previousHeaderLevel;
1140 for (var j = 0; j < levelDiff; ++j) {
1141 this.sectionNumbers[thisHeaderLevel - 1 - j] = 0; // Extend
1142 }
1143 } else if (thisHeaderLevel < previousHeaderLevel) {
1144 this.sectionNumbers.length = thisHeaderLevel; // Collapse
1145 }
1146 this.sectionNumbers[thisHeaderLevel - 1]++;
1147 sectionNumberText = this.sectionNumbers.join(".");
1148 node.sectionNumberText = sectionNumberText; // Can be used in ToC
1149 }
1150
1151 text = this.convertHeader(node, childText, auxData,
1152 taskStatus, sectionNumberText);
1153
1154 if (recordHeader)
1155 this.headers.push(node);
1156 break;
1157 case Node.types.orderedList:
1158 text = this.convertOrderedList(node, childText, auxData);
1159 break;
1160 case Node.types.unorderedList:
1161 text = this.convertUnorderedList(node, childText, auxData);
1162 break;
1163 case Node.types.definitionList:
1164 text = this.convertDefinitionList(node, childText, auxData);
1165 break;
1166 case Node.types.listElement:
1167 if (node.isDefinitionList) {
1168 var termText = this.convertNodesInternal(node.term, recordHeader, insideCodeElement);
1169 text = this.convertDefinitionItem(node, childText, auxData,
1170 termText, childText);
1171 } else {
1172 text = this.convertListItem(node, childText, auxData);
1173 }
1174 break;
1175 case Node.types.paragraph:
1176 text = this.convertParagraph(node, childText, auxData);
1177 break;
1178 case Node.types.preformatted:
1179 text = this.convertPreformatted(node, childText, auxData);
1180 break;
1181 case Node.types.table:
1182 text = this.convertTable(node, childText, auxData);
1183 break;
1184 case Node.types.tableRow:
1185 text = this.convertTableRow(node, childText, auxData);
1186 break;
1187 case Node.types.tableCell:
1188 if (node.isHeader)
1189 text = this.convertTableHeader(node, childText, auxData);
1190 else
1191 text = this.convertTableCell(node, childText, auxData);
1192 break;
1193 case Node.types.horizontalRule:
1194 text = this.convertHorizontalRule(node, childText, auxData);
1195 break;
1196 // ============================================================ //
1197 // Inline
1198 // ============================================================ //
1199 case Node.types.inlineContainer:
1200 text = this.convertInlineContainer(node, childText, auxData);
1201 break;
1202 case Node.types.bold:
1203 text = this.convertBold(node, childText, auxData);
1204 break;
1205 case Node.types.italic:
1206 text = this.convertItalic(node, childText, auxData);
1207 break;
1208 case Node.types.underline:
1209 text = this.convertUnderline(node, childText, auxData);
1210 break;
1211 case Node.types.code:
1212 text = this.convertCode(node, childText, auxData);
1213 break;
1214 case Node.types.dashed:
1215 text = this.convertDashed(node, childText, auxData);
1216 break;
1217 case Node.types.link:
1218 text = this.convertLink(node, childText, auxData);
1219 break;
1220 case Node.types.directive:
1221 switch (node.directiveName) {
1222 case "quote":
1223 text = this.convertQuote(node, childText, auxData);
1224 break;
1225 case "example":
1226 text = this.convertExample(node, childText, auxData);
1227 break;
1228 case "src":
1229 text = this.convertSrc(node, childText, auxData);
1230 break;
1231 case "html":
1232 case "html:":
1233 text = this.convertHTML(node, childText, auxData);
1234 break;
1235 default:
1236 if (this.exportOptions.customDirectiveHandler &&
1237 this.exportOptions.customDirectiveHandler[node.directiveName]) {
1238 text = this.exportOptions.customDirectiveHandler[node.directiveName](
1239 node, childText, auxData
1240 );
1241 } else {
1242 text = childText;
1243 }
1244 }
1245 break;
1246 case Node.types.text:
1247 text = this.convertText(node.value, insideCodeElement);
1248 break;
1249 default:
1250 throw Error("Unknown node type: " + node.type);
1251 }
1252
1253 if (typeof this.postProcess === "function") {
1254 text = this.postProcess(node, text, insideCodeElement);
1255 }
1256
1257 return text;
1258 },
1259
1260 convertText: function (text, insideCodeElement) {
1261 var escapedText = this.escapeSpecialChars(text, insideCodeElement);
1262
1263 if (!this.exportOptions.suppressSubScriptHandling && !insideCodeElement) {
1264 escapedText = this.makeSubscripts(escapedText, insideCodeElement);
1265 }
1266 if (!this.exportOptions.suppressAutoLink) {
1267 escapedText = this.linkURL(escapedText);
1268 }
1269
1270 return escapedText;
1271 },
1272
1273 // By default, ignore html
1274 convertHTML: function (node, childText, auxData) {
1275 return childText;
1276 },
1277
1278 convertNodesInternal: function (nodes, recordHeader, insideCodeElement) {
1279 var nodesTexts = [];
1280 for (var i = 0; i < nodes.length; ++i) {
1281 var node = nodes[i];
1282 var nodeText = this.convertNode(node, recordHeader, insideCodeElement);
1283 nodesTexts.push(nodeText);
1284 }
1285 return this.combineNodesTexts(nodesTexts);
1286 },
1287
1288 convertHeaderBlock: function (headerBlock, recordHeader) {
1289 throw Error("convertHeaderBlock is not implemented");
1290 },
1291
1292 convertHeaderTree: function (headerTree, recordHeader) {
1293 return this.convertHeaderBlock(headerTree, recordHeader);
1294 },
1295
1296 convertNodesToHeaderTree: function (nodes, nextBlockBegin, blockHeader) {
1297 var childBlocks = [];
1298 var childNodes = [];
1299
1300 if (typeof nextBlockBegin === "undefined") {
1301 nextBlockBegin = 0;
1302 }
1303 if (typeof blockHeader === "undefined") {
1304 blockHeader = null;
1305 }
1306
1307 for (var i = nextBlockBegin; i < nodes.length;) {
1308 var node = nodes[i];
1309
1310 var isHeader = node.type === Node.types.header;
1311
1312 if (!isHeader) {
1313 childNodes.push(node);
1314 i = i + 1;
1315 continue;
1316 }
1317
1318 // Header
1319 if (blockHeader && node.level <= blockHeader.level) {
1320 // Finish Block
1321 break;
1322 } else {
1323 // blockHeader.level < node.level
1324 // Begin child block
1325 var childBlock = this.convertNodesToHeaderTree(nodes, i + 1, node);
1326 childBlocks.push(childBlock);
1327 i = childBlock.nextIndex;
1328 }
1329 }
1330
1331 // Finish block
1332 return {
1333 header: blockHeader,
1334 childNodes: childNodes,
1335 nextIndex: i,
1336 childBlocks: childBlocks
1337 };
1338 },
1339
1340 convertNodes: function (nodes, recordHeader, insideCodeElement) {
1341 return this.convertNodesInternal(nodes, recordHeader, insideCodeElement);
1342 },
1343
1344 combineNodesTexts: function (nodesTexts) {
1345 return nodesTexts.join("");
1346 },
1347
1348 getNodeTextContent: function (node) {
1349 if (node.type === Node.types.text)
1350 return this.escapeSpecialChars(node.value);
1351 else
1352 return node.children ? node.children.map(this.getNodeTextContent, this).join("") : "";
1353 },
1354
1355 // @Override
1356 escapeSpecialChars: function (text) {
1357 throw Error("Implement escapeSpecialChars");
1358 },
1359
1360 // http://daringfireball.net/2010/07/improved_regex_for_matching_urls
1361 urlPattern: /\b(?:https?:\/\/|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}\/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'".,<>?«»“”‘’])/ig,
1362
1363 // @Override
1364 linkURL: function (text) {
1365 var self = this;
1366 return text.replace(this.urlPattern, function (matched) {
1367 if (matched.indexOf("://") < 0)
1368 matched = "http://" + matched;
1369 return self.makeLink(matched);
1370 });
1371 },
1372
1373 makeLink: function (url) {
1374 throw Error("Implement makeLink");
1375 },
1376
1377 makeSubscripts: function (text) {
1378 if (this.documentOptions["^"] === "{}")
1379 return text.replace(/\b([^_ \t]*)_{([^}]*)}/g,
1380 this.makeSubscript);
1381 else if (this.documentOptions["^"])
1382 return text.replace(/\b([^_ \t]*)_([^_]*)\b/g,
1383 this.makeSubscript);
1384 else
1385 return text;
1386 },
1387
1388 makeSubscript: function (match, body, subscript) {
1389 throw Error("Implement makeSubscript");
1390 },
1391
1392 stripParametersFromURL: function (url) {
1393 return url.replace(/\?.*$/, "");
1394 },
1395
1396 imageExtensionPattern: new RegExp("(" + [
1397 "bmp", "png", "jpeg", "jpg", "gif", "tiff",
1398 "tif", "xbm", "xpm", "pbm", "pgm", "ppm", "svg"
1399 ].join("|") + ")$", "i")
1400 };
1401
1402 if (typeof exports !== "undefined")
1403 exports.Converter = Converter;
1404
1405 // var Converter = require("./converter.js").Converter;
1406 // var Node = require("../node.js").Node;
1407
1408 function ConverterHTML(orgDocument, exportOptions) {
1409 this.initialize(orgDocument, exportOptions);
1410 this.result = this.convert();
1411 }
1412
1413 ConverterHTML.prototype = {
1414 __proto__: Converter.prototype,
1415
1416 convert: function () {
1417 var title = this.orgDocument.title ? this.convertNode(this.orgDocument.title) : this.untitled;
1418 var titleHTML = this.tag("h" + Math.max(Number(this.headerOffset), 1), title);
1419 var contentHTML = this.convertNodes(this.orgDocument.nodes, true /* record headers */);
1420 var toc = this.computeToc(this.documentOptions["toc"]);
1421 var tocHTML = this.tocToHTML(toc);
1422
1423 return {
1424 title: title,
1425 titleHTML: titleHTML,
1426 contentHTML: contentHTML,
1427 tocHTML: tocHTML,
1428 toc: toc,
1429 toString: function () {
1430 return titleHTML + tocHTML + "\n" + contentHTML;
1431 }
1432 };
1433 },
1434
1435 tocToHTML: function (toc) {
1436 function tocToHTMLFunction(tocList) {
1437 var html = "";
1438 for (var i = 0; i < tocList.length; ++i) {
1439 var tocItem = tocList[i];
1440 var sectionNumberText = tocItem.headerNode.sectionNumberText;
1441 var sectionNumber = this.documentOptions.num ?
1442 this.inlineTag("span", sectionNumberText, {
1443 "class": "section-number"
1444 }) : "";
1445 var header = this.getNodeTextContent(tocItem.headerNode);
1446 var headerLink = this.inlineTag("a", sectionNumber + header, {
1447 href: "#header-" + sectionNumberText.replace(/\./g, "-")
1448 });
1449 var subList = tocItem.childTocs.length ? tocToHTMLFunction.call(this, tocItem.childTocs) : "";
1450 html += this.tag("li", headerLink + subList);
1451 }
1452 return this.tag("ul", html);
1453 }
1454
1455 return tocToHTMLFunction.call(this, toc);
1456 },
1457
1458 computeAuxDataForNode: function (node) {
1459 while (node.parent &&
1460 node.parent.type === Node.types.inlineContainer) {
1461 node = node.parent;
1462 }
1463 var attributesNode = node.previousSibling;
1464 var attributesText = "";
1465 while (attributesNode &&
1466 attributesNode.type === Node.types.directive &&
1467 attributesNode.directiveName === "attr_html:") {
1468 attributesText += attributesNode.directiveRawValue + " ";
1469 attributesNode = attributesNode.previousSibling;
1470 }
1471 return attributesText;
1472 },
1473
1474 // Method to construct org-js generated class
1475 orgClassName: function (className) {
1476 return this.exportOptions.htmlClassPrefix ?
1477 this.exportOptions.htmlClassPrefix + className
1478 : className;
1479 },
1480
1481 // Method to construct org-js generated id
1482 orgId: function (id) {
1483 return this.exportOptions.htmlIdPrefix ?
1484 this.exportOptions.htmlIdPrefix + id
1485 : id;
1486 },
1487
1488 // ----------------------------------------------------
1489 // Node conversion
1490 // ----------------------------------------------------
1491
1492 convertHeader: function (node, childText, auxData,
1493 taskStatus, sectionNumberText) {
1494 var headerAttributes = {};
1495
1496 if (taskStatus) {
1497 childText = this.inlineTag("span", childText.substring(0, 4), {
1498 "class": "task-status " + taskStatus
1499 }) + childText.substring(5);
1500 }
1501
1502 if (sectionNumberText) {
1503 childText = this.inlineTag("span", sectionNumberText, {
1504 "class": "section-number"
1505 }) + childText;
1506 headerAttributes["id"] = "header-" + sectionNumberText.replace(/\./g, "-");
1507 }
1508
1509 if (taskStatus)
1510 headerAttributes["class"] = "task-status " + taskStatus;
1511
1512 return this.tag("h" + (this.headerOffset + node.level),
1513 childText, headerAttributes, auxData);
1514 },
1515
1516 convertOrderedList: function (node, childText, auxData) {
1517 return this.tag("ol", childText, null, auxData);
1518 },
1519
1520 convertUnorderedList: function (node, childText, auxData) {
1521 return this.tag("ul", childText, null, auxData);
1522 },
1523
1524 convertDefinitionList: function (node, childText, auxData) {
1525 return this.tag("dl", childText, null, auxData);
1526 },
1527
1528 convertDefinitionItem: function (node, childText, auxData,
1529 term, definition) {
1530 return this.tag("dt", term) + this.tag("dd", definition);
1531 },
1532
1533 convertListItem: function (node, childText, auxData) {
1534 if (this.exportOptions.suppressCheckboxHandling) {
1535 return this.tag("li", childText, null, auxData);
1536 } else {
1537 var listItemAttributes = {};
1538 var listItemText = childText;
1539 // Embed checkbox
1540 if (/^\s*\[(X| |-)\]([\s\S]*)/.exec(listItemText)) {
1541 listItemText = RegExp.$2 ;
1542 var checkboxIndicator = RegExp.$1;
1543
1544 var checkboxAttributes = { type: "checkbox" };
1545 switch (checkboxIndicator) {
1546 case "X":
1547 checkboxAttributes["checked"] = "true";
1548 listItemAttributes["data-checkbox-status"] = "done";
1549 break;
1550 case "-":
1551 listItemAttributes["data-checkbox-status"] = "intermediate";
1552 break;
1553 default:
1554 listItemAttributes["data-checkbox-status"] = "undone";
1555 break;
1556 }
1557
1558 listItemText = this.inlineTag("input", null, checkboxAttributes) + listItemText;
1559 }
1560
1561 return this.tag("li", listItemText, listItemAttributes, auxData);
1562 }
1563 },
1564
1565 convertParagraph: function (node, childText, auxData) {
1566 return this.tag("p", childText, null, auxData);
1567 },
1568
1569 convertPreformatted: function (node, childText, auxData) {
1570 return this.tag("pre", childText, null, auxData);
1571 },
1572
1573 convertTable: function (node, childText, auxData) {
1574 return this.tag("table", this.tag("tbody", childText), null, auxData);
1575 },
1576
1577 convertTableRow: function (node, childText, auxData) {
1578 return this.tag("tr", childText);
1579 },
1580
1581 convertTableHeader: function (node, childText, auxData) {
1582 return this.tag("th", childText);
1583 },
1584
1585 convertTableCell: function (node, childText, auxData) {
1586 return this.tag("td", childText);
1587 },
1588
1589 convertHorizontalRule: function (node, childText, auxData) {
1590 return this.tag("hr", null, null, auxData);
1591 },
1592
1593 convertInlineContainer: function (node, childText, auxData) {
1594 return childText;
1595 },
1596
1597 convertBold: function (node, childText, auxData) {
1598 return this.inlineTag("b", childText);
1599 },
1600
1601 convertItalic: function (node, childText, auxData) {
1602 return this.inlineTag("i", childText);
1603 },
1604
1605 convertUnderline: function (node, childText, auxData) {
1606 return this.inlineTag("span", childText, {
1607 style: "text-decoration:underline;"
1608 });
1609 },
1610
1611 convertCode: function (node, childText, auxData) {
1612 return this.inlineTag("code", childText);
1613 },
1614
1615 convertDashed: function (node, childText, auxData) {
1616 return this.inlineTag("del", childText);
1617 },
1618
1619 convertLink: function (node, childText, auxData) {
1620 var srcParameterStripped = this.stripParametersFromURL(node.src);
1621 if (this.imageExtensionPattern.exec(srcParameterStripped)) {
1622 var imgText = this.getNodeTextContent(node);
1623 return this.inlineTag("img", null, {
1624 src: node.src,
1625 alt: imgText,
1626 title: imgText
1627 }, auxData);
1628 } else {
1629 return this.inlineTag("a", childText, { href: node.src });
1630 }
1631 },
1632
1633 convertQuote: function (node, childText, auxData) {
1634 return this.tag("blockquote", childText, null, auxData);
1635 },
1636
1637 convertExample: function (node, childText, auxData) {
1638 return this.tag("pre", childText, null, auxData);
1639 },
1640
1641 convertSrc: function (node, childText, auxData) {
1642 var codeLanguage = node.directiveArguments.length
1643 ? node.directiveArguments[0]
1644 : "unknown";
1645 childText = this.tag("code", childText, {
1646 "class": "language-" + codeLanguage
1647 }, auxData);
1648 return this.tag("pre", childText, {
1649 "class": "prettyprint"
1650 });
1651 },
1652
1653 // @override
1654 convertHTML: function (node, childText, auxData) {
1655 if (node.directiveName === "html:") {
1656 return node.directiveRawValue;
1657 } else if (node.directiveName === "html") {
1658 return node.children.map(function (textNode) {
1659 return textNode.value;
1660 }).join("\n");
1661 } else {
1662 return childText;
1663 }
1664 },
1665
1666 // @implement
1667 convertHeaderBlock: function (headerBlock, level, index) {
1668 level = level || 0;
1669 index = index || 0;
1670
1671 var contents = [];
1672
1673 var headerNode = headerBlock.header;
1674 if (headerNode) {
1675 contents.push(this.convertNode(headerNode));
1676 }
1677
1678 var blockContent = this.convertNodes(headerBlock.childNodes);
1679 contents.push(blockContent);
1680
1681 var childBlockContent = headerBlock.childBlocks
1682 .map(function (block, idx) {
1683 return this.convertHeaderBlock(block, level + 1, idx);
1684 }, this)
1685 .join("\n");
1686 contents.push(childBlockContent);
1687
1688 var contentsText = contents.join("\n");
1689
1690 if (headerNode) {
1691 return this.tag("section", "\n" + contents.join("\n"), {
1692 "class": "block block-level-" + level
1693 });
1694 } else {
1695 return contentsText;
1696 }
1697 },
1698
1699 // ----------------------------------------------------
1700 // Supplemental methods
1701 // ----------------------------------------------------
1702
1703 replaceMap: {
1704 // [replacing pattern, predicate]
1705 "&": ["&", null],
1706 "<": ["<", null],
1707 ">": [">", null],
1708 '"': [""", null],
1709 "'": ["'", null],
1710 "->": ["➔", function (text, insideCodeElement) {
1711 return this.exportOptions.translateSymbolArrow && !insideCodeElement;
1712 }]
1713 },
1714
1715 replaceRegexp: null,
1716
1717 // @implement @override
1718 escapeSpecialChars: function (text, insideCodeElement) {
1719 if (!this.replaceRegexp) {
1720 this.replaceRegexp = new RegExp(Object.keys(this.replaceMap).join("|"), "g");
1721 }
1722
1723 var replaceMap = this.replaceMap;
1724 var self = this;
1725 return text.replace(this.replaceRegexp, function (matched) {
1726 if (!replaceMap[matched]) {
1727 throw Error("escapeSpecialChars: Invalid match");
1728 }
1729
1730 var predicate = replaceMap[matched][1];
1731 if (typeof predicate === "function" &&
1732 !predicate.call(self, text, insideCodeElement)) {
1733 // Not fullfill the predicate
1734 return matched;
1735 }
1736
1737 return replaceMap[matched][0];
1738 });
1739 },
1740
1741 // @implement
1742 postProcess: function (node, currentText, insideCodeElement) {
1743 if (this.exportOptions.exportFromLineNumber &&
1744 typeof node.fromLineNumber === "number") {
1745 // Wrap with line number information
1746 currentText = this.inlineTag("div", currentText, {
1747 "data-line-number": node.fromLineNumber
1748 });
1749 }
1750 return currentText;
1751 },
1752
1753 // @implement
1754 makeLink: function (url) {
1755 return "<a href=\"" + url + "\">" + decodeURIComponent(url) + "</a>";
1756 },
1757
1758 // @implement
1759 makeSubscript: function (match, body, subscript) {
1760 return "<span class=\"org-subscript-parent\">" +
1761 body +
1762 "</span><span class=\"org-subscript-child\">" +
1763 subscript +
1764 "</span>";
1765 },
1766
1767 // ----------------------------------------------------
1768 // Specific methods
1769 // ----------------------------------------------------
1770
1771 attributesObjectToString: function (attributesObject) {
1772 var attributesString = "";
1773 for (var attributeName in attributesObject) {
1774 if (attributesObject.hasOwnProperty(attributeName)) {
1775 var attributeValue = attributesObject[attributeName];
1776 // To avoid id/class name conflicts with other frameworks,
1777 // users can add arbitrary prefix to org-js generated
1778 // ids/classes via exportOptions.
1779 if (attributeName === "class") {
1780 attributeValue = this.orgClassName(attributeValue);
1781 } else if (attributeName === "id") {
1782 attributeValue = this.orgId(attributeValue);
1783 }
1784 attributesString += " " + attributeName + "=\"" + attributeValue + "\"";
1785 }
1786 }
1787 return attributesString;
1788 },
1789
1790 inlineTag: function (name, innerText, attributesObject, auxAttributesText) {
1791 attributesObject = attributesObject || {};
1792
1793 var htmlString = "<" + name;
1794 // TODO: check duplicated attributes
1795 if (auxAttributesText)
1796 htmlString += " " + auxAttributesText;
1797 htmlString += this.attributesObjectToString(attributesObject);
1798
1799 if (innerText === null)
1800 return htmlString + "/>";
1801
1802 htmlString += ">" + innerText + "</" + name + ">";
1803
1804 return htmlString;
1805 },
1806
1807 tag: function (name, innerText, attributesObject, auxAttributesText) {
1808 return this.inlineTag(name, innerText, attributesObject, auxAttributesText) + "\n";
1809 }
1810 };
1811
1812 if (typeof exports !== "undefined")
1813 exports.ConverterHTML = ConverterHTML;
1814
1815 return exports;
1816 })();