krz/org-live

An org-mode editor with live preview.

clone: git clone https://gitbay.org/krz/org-live.git

main: static/org.js · raw

   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        "&": ["&#38;", null],
1706        "<": ["&#60;", null],
1707        ">": ["&#62;", null],
1708        '"': ["&#34;", null],
1709        "'": ["&#39;", null],
1710        "->": ["&#10132;", 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  })();