1// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5/**
6 * @constructor
7 * @param {string} wikiMarkupText
8 */
9WebInspector.WikiParser = function(wikiMarkupText)
10{
11    var text = wikiMarkupText;
12    this._tokenizer = new WebInspector.WikiParser.Tokenizer(text);
13    this._document = this._parse();
14}
15
16/**
17 * @constructor
18 */
19WebInspector.WikiParser.Section = function()
20{
21    /** @type {string} */
22    this.title;
23
24    /** @type {?WebInspector.WikiParser.Values} */
25    this.values;
26
27    /** @type {?WebInspector.WikiParser.ArticleElement} */
28    this.singleValue;
29}
30
31/**
32 * @constructor
33 */
34WebInspector.WikiParser.Field = function()
35{
36    /** @type {string} */
37    this.name;
38
39    /** @type {?WebInspector.WikiParser.FieldValue} */
40    this.value;
41}
42
43/** @typedef {(?WebInspector.WikiParser.ArticleElement|!Array.<!WebInspector.WikiParser.Section>)} */
44WebInspector.WikiParser.FieldValue;
45
46/** @typedef {?Object.<string, !WebInspector.WikiParser.FieldValue>} */
47WebInspector.WikiParser.Values;
48
49/** @typedef {(?WebInspector.WikiParser.Value|?WebInspector.WikiParser.ArticleElement)} */
50WebInspector.WikiParser.Value;
51
52/**
53 * @package
54 * @enum {string}
55 */
56WebInspector.WikiParser.TokenType = {
57    Text: "Text",
58    OpeningTable: "OpeningTable",
59    ClosingTable: "ClosingTable",
60    RowSeparator: "RowSeparator",
61    CellSeparator: "CellSeparator",
62    NameSeparator: "NameSeparator",
63    OpeningCurlyBrackets: "OpeningCurlyBrackets",
64    ClosingCurlyBrackets: "ClosingCurlyBrackets",
65    Exclamation: "Exclamation",
66    OpeningSquareBrackets: "OpeningSquareBrackets",
67    ClosingBrackets: "ClosingBrackets",
68    EqualSign: "EqualSign",
69    EqualSignInCurlyBrackets: "EqualSignInCurlyBrackets",
70    VerticalLine: "VerticalLine",
71    DoubleQuotes: "DoubleQuotes",
72    TripleQuotes: "TripleQuotes",
73    OpeningCodeTag: "OpeningCodeTag",
74    ClosingCodeTag: "ClosingCodeTag",
75    Bullet: "Bullet",
76    LineEnd: "LineEnd",
77    CodeBlock: "CodeBlock",
78    Space: "Space"
79}
80
81/**
82 * @constructor
83 * @param {string} result
84 * @param {!WebInspector.WikiParser.TokenType} type
85 */
86WebInspector.WikiParser.Token = function(result, type)
87{
88    this._value = result;
89    this._type = type;
90}
91
92WebInspector.WikiParser.Token.prototype = {
93    /**
94     * @return {string}
95     */
96    value: function()
97    {
98        return this._value;
99    },
100
101    /**
102     * @return {!WebInspector.WikiParser.TokenType}
103     */
104    type: function()
105    {
106        return this._type;
107    }
108}
109
110/**
111 * @constructor
112 * @param {string} str
113 */
114WebInspector.WikiParser.Tokenizer = function(str)
115{
116    this._text = str;
117    this._oldText = str;
118    this._token = this._internalNextToken();
119    this._mode = WebInspector.WikiParser.Tokenizer.Mode.Normal;
120}
121
122/**
123 * @package
124 * @enum {string}
125 */
126WebInspector.WikiParser.Tokenizer.Mode = {
127    Normal: "Normal",
128    Link: "Link"
129}
130
131WebInspector.WikiParser.Tokenizer.prototype = {
132    /**
133     * @param {!WebInspector.WikiParser.Tokenizer.Mode} mode
134     */
135    _setMode: function(mode)
136    {
137        this._mode = mode;
138        this._text = this._oldText;
139        this._token = this._internalNextToken();
140    },
141
142    /**
143     * @return {boolean}
144     */
145    _isNormalMode: function()
146    {
147        return this._mode === WebInspector.WikiParser.Tokenizer.Mode.Normal;
148    },
149
150    /**
151     * @return {!WebInspector.WikiParser.Token}
152     */
153    peekToken: function()
154    {
155        return this._token;
156    },
157
158    /**
159     * @return {!WebInspector.WikiParser.Token}
160     */
161    nextToken: function()
162    {
163        var token = this._token;
164        this._oldText = this._text;
165        this._token = this._internalNextToken();
166        return token;
167    },
168
169    /**
170     * @return {!WebInspector.WikiParser.Token}
171     */
172    _internalNextToken: function()
173    {
174        if (WebInspector.WikiParser.newLineWithSpace.test(this._text)) {
175            var result = WebInspector.WikiParser.newLineWithSpace.exec(this._text);
176            var begin = result.index;
177            var end = this._text.length;
178            var lineEnd = WebInspector.WikiParser.newLineWithoutSpace.exec(this._text);
179            if (lineEnd)
180                end = lineEnd.index;
181            var token = this._text.substring(begin, end).replace(/\n /g, "\n").replace(/{{=}}/g, "=");
182            this._text = this._text.substring(end + 1);
183            return new WebInspector.WikiParser.Token(token, WebInspector.WikiParser.TokenType.CodeBlock);
184        }
185
186        for (var i = 0; i < WebInspector.WikiParser._tokenDescriptors.length; ++i) {
187            if (this._isNormalMode() && WebInspector.WikiParser._tokenDescriptors[i].type === WebInspector.WikiParser.TokenType.Space)
188                continue;
189            var result = WebInspector.WikiParser._tokenDescriptors[i].regex.exec(this._text);
190            if (result) {
191                this._text = this._text.substring(result.index + result[0].length);
192                return new WebInspector.WikiParser.Token(result[0], WebInspector.WikiParser._tokenDescriptors[i].type);
193            }
194        }
195
196        for (var lastIndex = 0; lastIndex < this._text.length; ++lastIndex) {
197            var testString = this._text.substring(lastIndex);
198            for (var i = 0; i < WebInspector.WikiParser._tokenDescriptors.length; ++i) {
199                if (this._isNormalMode() && WebInspector.WikiParser._tokenDescriptors[i].type === WebInspector.WikiParser.TokenType.Space)
200                    continue;
201                if (WebInspector.WikiParser._tokenDescriptors[i].regex.test(testString)) {
202                    var token = this._text.substring(0, lastIndex);
203                    this._text = this._text.substring(lastIndex);
204                    return new WebInspector.WikiParser.Token(token, WebInspector.WikiParser.TokenType.Text);
205                }
206            }
207        }
208
209        var token = this._text;
210        this._text = "";
211        return new WebInspector.WikiParser.Token(token, WebInspector.WikiParser.TokenType.Text);
212    },
213
214    /**
215     * @return {!WebInspector.WikiParser.Tokenizer}
216     */
217    clone: function()
218    {
219        var tokenizer = new WebInspector.WikiParser.Tokenizer(this._text);
220        tokenizer._token = this._token;
221        tokenizer._text = this._text;
222        tokenizer._oldText = this._oldText;
223        tokenizer._mode = this._mode;
224        return tokenizer;
225    },
226
227    /**
228     * @return {boolean}
229     */
230    hasMoreTokens: function()
231    {
232        return !!this._text.length;
233    }
234}
235
236WebInspector.WikiParser.openingTable = /^\n{{{!}}/;
237WebInspector.WikiParser.closingTable = /^\n{{!}}}/;
238WebInspector.WikiParser.cellSeparator = /^\n{{!}}/;
239WebInspector.WikiParser.rowSeparator = /^\n{{!}}-/;
240WebInspector.WikiParser.nameSeparator = /^\n!/;
241WebInspector.WikiParser.exclamation = /^{{!}}/;
242WebInspector.WikiParser.openingCurlyBrackets = /^{{/;
243WebInspector.WikiParser.equalSign = /^=/;
244WebInspector.WikiParser.equalSignInCurlyBrackets = /^{{=}}/;
245WebInspector.WikiParser.closingCurlyBrackets = /^\s*}}/;
246WebInspector.WikiParser.oneOpeningSquareBracket = /^\n*\[/;
247WebInspector.WikiParser.twoOpeningSquareBrackets = /^\n*\[\[/;
248WebInspector.WikiParser.oneClosingBracket = /^\n*\]/;
249WebInspector.WikiParser.twoClosingBrackets = /^\n*\]\]/;
250WebInspector.WikiParser.tripleQuotes = /^\n*'''/;
251WebInspector.WikiParser.doubleQuotes = /^\n*''/;
252WebInspector.WikiParser.openingCodeTag = /^<code\s*>/;
253WebInspector.WikiParser.closingCodeTag = /^<\/code\s*>/;
254WebInspector.WikiParser.closingBullet = /^\*/;
255WebInspector.WikiParser.lineEnd = /^\n/;
256WebInspector.WikiParser.verticalLine = /^\n*\|/;
257WebInspector.WikiParser.newLineWithSpace = /^\n [^ ]/;
258WebInspector.WikiParser.newLineWithoutSpace = /\n[^ ]/;
259WebInspector.WikiParser.space = /^ /;
260
261/**
262 * @constructor
263 * @param {!RegExp} regex
264 * @param {!WebInspector.WikiParser.TokenType} type
265 */
266WebInspector.WikiParser.TokenDescriptor = function(regex, type)
267{
268    this.regex = regex;
269    this.type = type;
270}
271
272WebInspector.WikiParser._tokenDescriptors = [
273    new WebInspector.WikiParser.TokenDescriptor(WebInspector.WikiParser.closingTable, WebInspector.WikiParser.TokenType.ClosingTable),
274    new WebInspector.WikiParser.TokenDescriptor(WebInspector.WikiParser.openingTable, WebInspector.WikiParser.TokenType.OpeningTable),
275    new WebInspector.WikiParser.TokenDescriptor(WebInspector.WikiParser.rowSeparator, WebInspector.WikiParser.TokenType.RowSeparator),
276    new WebInspector.WikiParser.TokenDescriptor(WebInspector.WikiParser.cellSeparator, WebInspector.WikiParser.TokenType.CellSeparator),
277    new WebInspector.WikiParser.TokenDescriptor(WebInspector.WikiParser.nameSeparator, WebInspector.WikiParser.TokenType.NameSeparator),
278    new WebInspector.WikiParser.TokenDescriptor(WebInspector.WikiParser.exclamation, WebInspector.WikiParser.TokenType.Exclamation),
279    new WebInspector.WikiParser.TokenDescriptor(WebInspector.WikiParser.equalSignInCurlyBrackets, WebInspector.WikiParser.TokenType.EqualSignInCurlyBrackets),
280    new WebInspector.WikiParser.TokenDescriptor(WebInspector.WikiParser.equalSign, WebInspector.WikiParser.TokenType.EqualSign),
281    new WebInspector.WikiParser.TokenDescriptor(WebInspector.WikiParser.openingTable, WebInspector.WikiParser.TokenType.OpeningTable),
282    new WebInspector.WikiParser.TokenDescriptor(WebInspector.WikiParser.openingCurlyBrackets, WebInspector.WikiParser.TokenType.OpeningCurlyBrackets),
283    new WebInspector.WikiParser.TokenDescriptor(WebInspector.WikiParser.verticalLine, WebInspector.WikiParser.TokenType.VerticalLine),
284    new WebInspector.WikiParser.TokenDescriptor(WebInspector.WikiParser.closingCurlyBrackets, WebInspector.WikiParser.TokenType.ClosingCurlyBrackets),
285    new WebInspector.WikiParser.TokenDescriptor(WebInspector.WikiParser.twoOpeningSquareBrackets, WebInspector.WikiParser.TokenType.OpeningSquareBrackets),
286    new WebInspector.WikiParser.TokenDescriptor(WebInspector.WikiParser.twoClosingBrackets, WebInspector.WikiParser.TokenType.ClosingBrackets),
287    new WebInspector.WikiParser.TokenDescriptor(WebInspector.WikiParser.oneOpeningSquareBracket, WebInspector.WikiParser.TokenType.OpeningSquareBrackets),
288    new WebInspector.WikiParser.TokenDescriptor(WebInspector.WikiParser.oneClosingBracket, WebInspector.WikiParser.TokenType.ClosingBrackets),
289    new WebInspector.WikiParser.TokenDescriptor(WebInspector.WikiParser.newLineWithSpace, WebInspector.WikiParser.TokenType.CodeBlock),
290    new WebInspector.WikiParser.TokenDescriptor(WebInspector.WikiParser.tripleQuotes, WebInspector.WikiParser.TokenType.TripleQuotes),
291    new WebInspector.WikiParser.TokenDescriptor(WebInspector.WikiParser.doubleQuotes, WebInspector.WikiParser.TokenType.DoubleQuotes),
292    new WebInspector.WikiParser.TokenDescriptor(WebInspector.WikiParser.openingCodeTag, WebInspector.WikiParser.TokenType.OpeningCodeTag),
293    new WebInspector.WikiParser.TokenDescriptor(WebInspector.WikiParser.closingCodeTag, WebInspector.WikiParser.TokenType.ClosingCodeTag),
294    new WebInspector.WikiParser.TokenDescriptor(WebInspector.WikiParser.closingBullet, WebInspector.WikiParser.TokenType.Bullet),
295    new WebInspector.WikiParser.TokenDescriptor(WebInspector.WikiParser.lineEnd, WebInspector.WikiParser.TokenType.LineEnd),
296    new WebInspector.WikiParser.TokenDescriptor(WebInspector.WikiParser.space, WebInspector.WikiParser.TokenType.Space)
297]
298
299WebInspector.WikiParser.prototype = {
300    /**
301     * @return {!Object}
302     */
303    document: function()
304    {
305        return this._document;
306    },
307
308    /**
309     * @return {?WebInspector.WikiParser.TokenType}
310     */
311    _secondTokenType: function()
312    {
313        var tokenizer = this._tokenizer.clone();
314        if (!tokenizer.hasMoreTokens())
315            return null;
316        tokenizer.nextToken();
317        if (!tokenizer.hasMoreTokens())
318            return null;
319        return tokenizer.nextToken().type();
320    },
321
322    /**
323     * @return {!Object.<string, ?WebInspector.WikiParser.Value>}
324     */
325    _parse: function()
326    {
327        var obj = {};
328        while (this._tokenizer.hasMoreTokens()) {
329            var section = this._parseSection();
330            if (section.title)
331                obj[section.title] = section.singleValue || section.values;
332        }
333        return obj;
334    },
335
336    /**
337     * @return {!WebInspector.WikiParser.Section}
338     */
339    _parseSection: function()
340    {
341        var section = new WebInspector.WikiParser.Section();
342        if (!this._tokenizer.hasMoreTokens() || this._tokenizer.nextToken().type() !== WebInspector.WikiParser.TokenType.OpeningCurlyBrackets)
343            return section;
344
345        var title = this._deleteTrailingSpaces(this._parseSectionTitle());
346        if (!title.length)
347            return section;
348        section.title = title;
349        if (this._tokenizer.peekToken().type() === WebInspector.WikiParser.TokenType.ClosingCurlyBrackets) {
350            this._tokenizer.nextToken();
351            return section;
352        }
353        var secondTokenType = this._secondTokenType();
354        if (!secondTokenType || secondTokenType !== WebInspector.WikiParser.TokenType.EqualSign) {
355            section.singleValue = this._parseMarkupText();
356        } else {
357            section.values = {};
358            while (this._tokenizer.hasMoreTokens()) {
359                var field = this._parseField();
360                section.values[field.name] = field.value;
361                if (this._tokenizer.peekToken().type() === WebInspector.WikiParser.TokenType.ClosingCurlyBrackets) {
362                    this._tokenizer.nextToken();
363                    return section;
364                }
365            }
366        }
367        var token = this._tokenizer.nextToken();
368        if (token.type() !== WebInspector.WikiParser.TokenType.ClosingCurlyBrackets)
369            throw new Error("Two closing curly brackets expected; found " + token.value());
370
371        return section;
372    },
373
374    /**
375     * @return {!WebInspector.WikiParser.Field}
376     */
377    _parseField: function()
378    {
379        var field = new WebInspector.WikiParser.Field();
380        field.name = this._parseFieldName();
381        var token = this._tokenizer.peekToken();
382        switch (token.type()) {
383        case WebInspector.WikiParser.TokenType.OpeningCurlyBrackets:
384            field.value = this._parseArray();
385            break;
386        case WebInspector.WikiParser.TokenType.LineEnd:
387            this._tokenizer.nextToken();
388            break;
389        case WebInspector.WikiParser.TokenType.ClosingCurlyBrackets:
390            return field;
391        default:
392            if (field.name.toUpperCase() === "CODE")
393                field.value = this._parseExampleCode();
394            else
395                field.value = this._parseMarkupText();
396        }
397        return field;
398    },
399
400    /**
401     * @return {!Array.<!WebInspector.WikiParser.Section>}
402     */
403    _parseArray: function()
404    {
405        var array = [];
406        while (this._tokenizer.peekToken().type() === WebInspector.WikiParser.TokenType.OpeningCurlyBrackets)
407            array.push(this._parseSection());
408        if (this._tokenizer.peekToken().type() === WebInspector.WikiParser.TokenType.VerticalLine)
409            this._tokenizer.nextToken();
410        return array;
411    },
412
413    /**
414     * @return {string}
415     */
416    _parseSectionTitle: function()
417    {
418        var title = "";
419        while (this._tokenizer.hasMoreTokens()) {
420            var token = this._tokenizer.peekToken();
421            switch (token.type()) {
422            case WebInspector.WikiParser.TokenType.ClosingCurlyBrackets:
423                return title;
424            case WebInspector.WikiParser.TokenType.VerticalLine:
425                this._tokenizer.nextToken();
426                return title;
427            case WebInspector.WikiParser.TokenType.Text:
428                title += this._tokenizer.nextToken().value();
429                break;
430            default:
431                throw new Error("Title could not be parsed. Unexpected token " + token.value());
432            }
433        }
434        return title;
435    },
436
437    /**
438     * @return {string}
439     */
440    _parseFieldName: function()
441    {
442        var name = "";
443        while (this._tokenizer.hasMoreTokens()) {
444            var token = this._tokenizer.peekToken();
445            switch (token.type()) {
446            case WebInspector.WikiParser.TokenType.ClosingCurlyBrackets:
447                return name;
448            case WebInspector.WikiParser.TokenType.EqualSign:
449                this._tokenizer.nextToken();
450                return name;
451            case WebInspector.WikiParser.TokenType.VerticalLine:
452            case WebInspector.WikiParser.TokenType.Text:
453                name += this._tokenizer.nextToken().value();
454                break;
455            default:
456                throw new Error("Name could not be parsed. Unexpected token " + token.value());
457            }
458        }
459        return name;
460    },
461
462    /**
463     * @return {!WebInspector.WikiParser.Block}
464     */
465    _parseExampleCode: function()
466    {
467        var code = "";
468
469        /**
470         * @return {!WebInspector.WikiParser.Block}
471         */
472        function wrapIntoArticleElement()
473        {
474            var plainText = new WebInspector.WikiParser.PlainText(code);
475            var block = new WebInspector.WikiParser.Block([plainText])
476            var articleElement = new WebInspector.WikiParser.Block([block]);
477            return articleElement;
478        }
479
480        while (this._tokenizer.hasMoreTokens()) {
481            var token = this._tokenizer.peekToken();
482            switch (token.type()) {
483            case WebInspector.WikiParser.TokenType.ClosingCurlyBrackets:
484                return wrapIntoArticleElement();
485            case WebInspector.WikiParser.TokenType.VerticalLine:
486                this._tokenizer.nextToken();
487                return wrapIntoArticleElement();
488            case WebInspector.WikiParser.TokenType.Exclamation:
489                this._tokenizer.nextToken();
490                code += "|";
491                break;
492            case WebInspector.WikiParser.TokenType.EqualSignInCurlyBrackets:
493                this._tokenizer.nextToken();
494                code += "=";
495                break;
496            default:
497                this._tokenizer.nextToken();
498                code += token.value();
499            }
500        }
501        return wrapIntoArticleElement();
502    },
503
504    /**
505     * @return {?WebInspector.WikiParser.Block}
506     */
507    _parseMarkupText: function()
508    {
509        var children = [];
510        var blockChildren = [];
511        var text = "";
512
513        /**
514         * @this {WebInspector.WikiParser}
515         */
516        function processSimpleText()
517        {
518            var currentText = this._deleteTrailingSpaces(text);
519            if (!currentText.length)
520                return;
521            var simpleText = new WebInspector.WikiParser.PlainText(currentText);
522            blockChildren.push(simpleText);
523            text = "";
524        }
525
526        function processBlock()
527        {
528            if (blockChildren.length) {
529                children.push(new WebInspector.WikiParser.Block(blockChildren));
530                blockChildren = [];
531            }
532        }
533
534        while (this._tokenizer.hasMoreTokens()) {
535            var token = this._tokenizer.peekToken();
536            switch (token.type()) {
537            case WebInspector.WikiParser.TokenType.RowSeparator:
538            case WebInspector.WikiParser.TokenType.NameSeparator:
539            case WebInspector.WikiParser.TokenType.CellSeparator:
540            case WebInspector.WikiParser.TokenType.ClosingTable:
541            case WebInspector.WikiParser.TokenType.VerticalLine:
542            case WebInspector.WikiParser.TokenType.ClosingCurlyBrackets:
543                if (token.type() === WebInspector.WikiParser.TokenType.VerticalLine)
544                    this._tokenizer.nextToken();
545                processSimpleText.call(this);
546                processBlock();
547                return new WebInspector.WikiParser.Block(children);
548            case WebInspector.WikiParser.TokenType.TripleQuotes:
549                this._tokenizer.nextToken();
550                processSimpleText.call(this);
551                blockChildren.push(this._parseHighlight());
552                break;
553            case WebInspector.WikiParser.TokenType.DoubleQuotes:
554                this._tokenizer.nextToken();
555                processSimpleText.call(this);
556                blockChildren.push(this._parseItalics());
557                break;
558            case WebInspector.WikiParser.TokenType.OpeningSquareBrackets:
559                processSimpleText.call(this);
560                blockChildren.push(this._parseLink());
561                break;
562            case WebInspector.WikiParser.TokenType.OpeningCodeTag:
563                this._tokenizer.nextToken();
564                processSimpleText.call(this);
565                blockChildren.push(this._parseCode());
566                break;
567            case WebInspector.WikiParser.TokenType.Bullet:
568                this._tokenizer.nextToken();
569                processSimpleText.call(this);
570                processBlock();
571                children.push(this._parseBullet());
572                break;
573            case WebInspector.WikiParser.TokenType.CodeBlock:
574                this._tokenizer.nextToken();
575                processSimpleText.call(this);
576                processBlock();
577                var code = new WebInspector.WikiParser.CodeBlock(this._trimLeadingNewLines(token.value()));
578                children.push(code);
579                break;
580            case WebInspector.WikiParser.TokenType.LineEnd:
581                this._tokenizer.nextToken();
582                processSimpleText.call(this);
583                processBlock();
584                break;
585            case WebInspector.WikiParser.TokenType.EqualSignInCurlyBrackets:
586                this._tokenizer.nextToken();
587                text += "=";
588                break;
589            case WebInspector.WikiParser.TokenType.Exclamation:
590                this._tokenizer.nextToken();
591                text += "|";
592                break;
593            case WebInspector.WikiParser.TokenType.OpeningTable:
594                this._tokenizer.nextToken();
595                processSimpleText.call(this);
596                processBlock();
597                children.push(this._parseTable());
598                break;
599            case WebInspector.WikiParser.TokenType.ClosingBrackets:
600            case WebInspector.WikiParser.TokenType.Text:
601            case WebInspector.WikiParser.TokenType.EqualSign:
602                this._tokenizer.nextToken();
603                text += token.value();
604                break;
605            default:
606                this._tokenizer.nextToken();
607                return null;
608            }
609        }
610
611        processSimpleText.call(this);
612        processBlock();
613
614        return new WebInspector.WikiParser.Block(children);
615    },
616
617    /**
618     * @return {!WebInspector.WikiParser.ArticleElement}
619     */
620    _parseLink: function()
621    {
622        var tokenizer = this._tokenizer.clone();
623        this._tokenizer.nextToken();
624        this._tokenizer._setMode(WebInspector.WikiParser.Tokenizer.Mode.Link);
625        var url = "";
626        var children = [];
627
628        /**
629         * @return {!WebInspector.WikiParser.ArticleElement}
630         * @this {WebInspector.WikiParser}
631         */
632        function finalizeLink()
633        {
634            this._tokenizer._setMode(WebInspector.WikiParser.Tokenizer.Mode.Normal);
635            return new WebInspector.WikiParser.Link(url, children);
636        }
637
638        /**
639         * @return {!WebInspector.WikiParser.ArticleElement}
640         * @this {WebInspector.WikiParser}
641         */
642        function recoverAsText()
643        {
644            this._tokenizer = tokenizer;
645            return this._parseTextUntilBrackets();
646        }
647
648        while (this._tokenizer.hasMoreTokens()) {
649            var token = this._tokenizer.nextToken();
650            switch (token.type()) {
651            case WebInspector.WikiParser.TokenType.ClosingBrackets:
652                if (this._isLink(url))
653                    return finalizeLink.call(this);
654                return recoverAsText.call(this);
655            case WebInspector.WikiParser.TokenType.VerticalLine:
656            case WebInspector.WikiParser.TokenType.Space:
657            case WebInspector.WikiParser.TokenType.Exclamation:
658                if (this._isLink(url)) {
659                    children.push(this._parseLinkName());
660                    return finalizeLink.call(this);
661                }
662                return recoverAsText.call(this);
663            default:
664                url += token.value();
665            }
666        }
667
668        return finalizeLink.call(this);
669    },
670
671    /**
672     * @return {!WebInspector.WikiParser.Inline}
673     */
674    _parseLinkName: function()
675    {
676        var children = [];
677        var text = "";
678
679        /**
680         * @this {WebInspector.WikiParser}
681         */
682        function processSimpleText()
683        {
684            text = this._deleteTrailingSpaces(text);
685            if (!text.length)
686                return;
687            var simpleText = new WebInspector.WikiParser.PlainText(text);
688            children.push(simpleText);
689            text = "";
690        }
691
692        while (this._tokenizer.hasMoreTokens()) {
693            var token = this._tokenizer.nextToken();
694            switch (token.type()) {
695            case WebInspector.WikiParser.TokenType.ClosingBrackets:
696                processSimpleText.call(this);
697                return new WebInspector.WikiParser.Inline(WebInspector.WikiParser.ArticleElement.Type.Inline, children);
698            case WebInspector.WikiParser.TokenType.OpeningCodeTag:
699                processSimpleText.call(this);
700                children.push(this._parseCode());
701                break;
702            default:
703                text += token.value();
704                break;
705            }
706        }
707
708        return new WebInspector.WikiParser.Inline(WebInspector.WikiParser.ArticleElement.Type.Inline, children);
709    },
710
711    /**
712     * @return {!WebInspector.WikiParser.Inline}
713     */
714    _parseCode: function()
715    {
716        var children = [];
717        var text = "";
718
719        /**
720         * @this {WebInspector.WikiParser}
721         */
722        function processSimpleText()
723        {
724            text = this._deleteTrailingSpaces(text);
725            if (!text.length)
726                return;
727            var simpleText = new WebInspector.WikiParser.PlainText(text);
728            children.push(simpleText);
729            text = "";
730        }
731
732        while (this._tokenizer.hasMoreTokens()) {
733            var token = this._tokenizer.peekToken();
734            switch (token.type()) {
735            case WebInspector.WikiParser.TokenType.ClosingCodeTag:
736                this._tokenizer.nextToken();
737                processSimpleText.call(this);
738                var code = new WebInspector.WikiParser.Inline(WebInspector.WikiParser.ArticleElement.Type.Code, children);
739                return code;
740            case WebInspector.WikiParser.TokenType.OpeningSquareBrackets:
741                processSimpleText.call(this);
742                children.push(this._parseLink());
743                break;
744            default:
745                this._tokenizer.nextToken();
746                text += token.value();
747            }
748        }
749
750        text = this._deleteTrailingSpaces(text);
751        if (text.length)
752            children.push(new WebInspector.WikiParser.PlainText(text));
753
754        return new WebInspector.WikiParser.Inline(WebInspector.WikiParser.ArticleElement.Type.Code, children);
755    },
756
757    /**
758     * @return {!WebInspector.WikiParser.Block}
759     */
760    _parseBullet: function()
761    {
762        var children = [];
763        while (this._tokenizer.hasMoreTokens()) {
764            var token = this._tokenizer.peekToken()
765            switch (token.type()) {
766            case WebInspector.WikiParser.TokenType.OpeningSquareBrackets:
767                children.push(this._parseLink());
768                break;
769            case WebInspector.WikiParser.TokenType.OpeningCodeTag:
770                this._tokenizer.nextToken();
771                children.push(this._parseCode());
772                break;
773            case WebInspector.WikiParser.TokenType.LineEnd:
774                this._tokenizer.nextToken();
775                return new WebInspector.WikiParser.Block(children, true);
776            default:
777                this._tokenizer.nextToken();
778                var text = this._deleteTrailingSpaces(token.value());
779                if (text.length) {
780                    var simpleText = new WebInspector.WikiParser.PlainText(text);
781                    children.push(simpleText);
782                    text = "";
783                }
784            }
785        }
786
787        return new WebInspector.WikiParser.Block(children, true);
788    },
789
790    /**
791     * @return {!WebInspector.WikiParser.PlainText}
792     */
793    _parseHighlight: function()
794    {
795        var text = "";
796        while (this._tokenizer.hasMoreTokens()) {
797            var token = this._tokenizer.nextToken();
798            if (token.type() === WebInspector.WikiParser.TokenType.TripleQuotes) {
799                text = this._deleteTrailingSpaces(text);
800                return new WebInspector.WikiParser.PlainText(text, true);
801            } else {
802                text += token.value();
803            }
804        }
805        return new WebInspector.WikiParser.PlainText(text, true);
806    },
807
808    /**
809     * @return {!WebInspector.WikiParser.PlainText}
810     */
811    _parseItalics: function()
812    {
813        var text = "";
814        while (this._tokenizer.hasMoreTokens) {
815            var token = this._tokenizer.nextToken();
816            if (token.type() === WebInspector.WikiParser.TokenType.DoubleQuotes) {
817                text = this._deleteTrailingSpaces(text);
818                return new WebInspector.WikiParser.PlainText(text, false, true);
819            } else {
820                text += token.value();
821            }
822        }
823        return new WebInspector.WikiParser.PlainText(text, false, true);
824    },
825
826    /**
827     * @return {!WebInspector.WikiParser.PlainText}
828     */
829    _parseTextUntilBrackets: function()
830    {
831        var text = this._tokenizer.nextToken().value();
832        while (this._tokenizer.hasMoreTokens()) {
833            var token = this._tokenizer.peekToken();
834            switch (token.type()) {
835            case WebInspector.WikiParser.TokenType.VerticalLine:
836                this._tokenizer.nextToken();
837                return new WebInspector.WikiParser.PlainText(text);
838            case WebInspector.WikiParser.TokenType.ClosingCurlyBrackets:
839            case WebInspector.WikiParser.TokenType.OpeningSquareBrackets:
840                return new WebInspector.WikiParser.PlainText(text);
841            default:
842                this._tokenizer.nextToken();
843                text += token.value();
844            }
845        }
846
847        return new WebInspector.WikiParser.PlainText(text);
848    },
849
850    /**
851     * @return {!WebInspector.WikiParser.Table}
852     */
853    _parseTable: function()
854    {
855        var columnNames = [];
856        var rows = [];
857        while (this._tokenizer.hasMoreTokens() && this._tokenizer.peekToken().type() !== WebInspector.WikiParser.TokenType.RowSeparator)
858            this._tokenizer.nextToken();
859        if (!this._tokenizer.hasMoreTokens())
860            throw new Error("Table could not be parsed");
861        this._tokenizer.nextToken();
862
863        while (this._tokenizer.peekToken().type() === WebInspector.WikiParser.TokenType.NameSeparator) {
864            this._tokenizer.nextToken();
865            columnNames.push(this._parseMarkupText());
866        }
867        while (this._tokenizer.peekToken().type() === WebInspector.WikiParser.TokenType.RowSeparator) {
868            this._tokenizer.nextToken();
869            var row = [];
870            while (this._tokenizer.peekToken().type() === WebInspector.WikiParser.TokenType.CellSeparator) {
871                this._tokenizer.nextToken();
872                row.push(this._parseMarkupText());
873            }
874            rows.push(row);
875        }
876
877        var token = this._tokenizer.nextToken();
878        if (token.type() !== WebInspector.WikiParser.TokenType.ClosingTable)
879            throw new Error("Table could not be parsed. {{!}}} expected; found " + token.value());
880
881        for (var i = 0; i < rows.length; ++i) {
882            if (rows[i].length !== columnNames.length)
883                throw new Error(String.sprintf("Table could not be parsed. Row %d has %d cells; expected %d.", i, rows[i].length, columnNames[i].length));
884        }
885        return new WebInspector.WikiParser.Table(columnNames, rows);
886    },
887
888    /**
889     * @param {string} str
890     * @return {string}
891     */
892    _deleteTrailingSpaces: function(str)
893    {
894        return str.replace(/[\n\r]*$/gm, "");
895    },
896
897    /**
898     * @param {string} str
899     * @return {string}
900     */
901    _trimLeadingNewLines: function(str)
902    {
903        return str.replace(/^\n*/, "");
904    },
905
906    /**
907     * @param {string} str
908     * @return {boolean}
909     */
910    _isInternalLink: function(str)
911    {
912        var len = str.length;
913        return /^[a-zA-Z\/-]+$/.test(str);
914    },
915
916    /**
917     * @param {string} str
918     * @return {boolean}
919     */
920    _isLink: function(str)
921    {
922        if (this._isInternalLink(str))
923            return true;
924        var url = new WebInspector.ParsedURL(str);
925        return url.isValid;
926    }
927}
928
929/**
930 * @constructor
931 * @param {!WebInspector.WikiParser.ArticleElement.Type} type
932 */
933WebInspector.WikiParser.ArticleElement = function(type)
934{
935    this._type = type;
936}
937
938WebInspector.WikiParser.ArticleElement.prototype = {
939    /**
940     * @return {!WebInspector.WikiParser.ArticleElement.Type}
941     */
942    type: function()
943    {
944        return this._type;
945    }
946}
947
948/**
949 * @enum {string}
950 */
951WebInspector.WikiParser.ArticleElement.Type = {
952    PlainText: "PlainText",
953    Link: "Link",
954    Code: "Code",
955    Block: "Block",
956    CodeBlock: "CodeBlock",
957    Inline: "Inline",
958    Table: "Table"
959};
960
961/**
962 * @constructor
963 * @extends {WebInspector.WikiParser.ArticleElement}
964 * @param {!Array.<!WebInspector.WikiParser.ArticleElement>} columnNames
965 * @param {!Array.<!Array.<!WebInspector.WikiParser.ArticleElement>>} rows
966 */
967WebInspector.WikiParser.Table = function(columnNames, rows)
968{
969    WebInspector.WikiParser.ArticleElement.call(this, WebInspector.WikiParser.ArticleElement.Type.Table);
970    this._columnNames = columnNames;
971    this._rows = rows;
972}
973
974WebInspector.WikiParser.Table.prototype = {
975    /**
976     * @return {!Array.<!WebInspector.WikiParser.ArticleElement>}
977     */
978    columnNames: function()
979    {
980        return this._columnNames;
981    },
982
983    /**
984     * @return {!Array.<!Array.<!WebInspector.WikiParser.ArticleElement>>}
985     */
986    rows: function()
987    {
988        return this._rows;
989    },
990
991    __proto__: WebInspector.WikiParser.ArticleElement.prototype
992}
993/**
994 * @constructor
995 * @extends {WebInspector.WikiParser.ArticleElement}
996 * @param {string} text
997 * @param {boolean=} highlight
998 * @param {boolean=} italic
999 */
1000WebInspector.WikiParser.PlainText = function(text, highlight, italic)
1001{
1002    WebInspector.WikiParser.ArticleElement.call(this, WebInspector.WikiParser.ArticleElement.Type.PlainText);
1003    this._text = text.unescapeHTML();
1004    this._isHighlighted = highlight || false;
1005    this._isItalic = italic || false;
1006}
1007
1008WebInspector.WikiParser.PlainText.prototype = {
1009    /**
1010     * @return {string}
1011     */
1012    text: function()
1013    {
1014        return this._text;
1015    },
1016
1017    /**
1018     * @return {boolean}
1019     */
1020    isHighlighted: function()
1021    {
1022        return this._isHighlighted;
1023    },
1024
1025    __proto__: WebInspector.WikiParser.ArticleElement.prototype
1026}
1027
1028/**
1029 * @constructor
1030 * @extends {WebInspector.WikiParser.ArticleElement}
1031 * @param {!Array.<!WebInspector.WikiParser.ArticleElement>} children
1032 * @param {boolean=} hasBullet
1033 */
1034WebInspector.WikiParser.Block = function(children, hasBullet)
1035{
1036    WebInspector.WikiParser.ArticleElement.call(this, WebInspector.WikiParser.ArticleElement.Type.Block);
1037    this._children = children;
1038    this._hasBullet = hasBullet || false;
1039}
1040
1041WebInspector.WikiParser.Block.prototype = {
1042    /**
1043     * @return {!Array.<!WebInspector.WikiParser.ArticleElement>}
1044     */
1045    children: function()
1046    {
1047        return this._children;
1048    },
1049
1050    /**
1051     * @return {boolean}
1052     */
1053    hasChildren: function()
1054    {
1055        return !!this._children && !!this._children.length;
1056    },
1057
1058    /**
1059     * @return {boolean}
1060     */
1061    hasBullet: function()
1062    {
1063        return this._hasBullet;
1064    },
1065
1066    __proto__: WebInspector.WikiParser.ArticleElement.prototype
1067}
1068
1069/**
1070 * @constructor
1071 * @extends {WebInspector.WikiParser.ArticleElement}
1072 * @param {string} text
1073 */
1074WebInspector.WikiParser.CodeBlock = function(text)
1075{
1076    WebInspector.WikiParser.ArticleElement.call(this, WebInspector.WikiParser.ArticleElement.Type.CodeBlock);
1077    this._code = text.unescapeHTML();
1078}
1079
1080WebInspector.WikiParser.CodeBlock.prototype = {
1081    /**
1082     * @return {string}
1083     */
1084    code: function()
1085    {
1086        return this._code;
1087    },
1088
1089    __proto__: WebInspector.WikiParser.ArticleElement.prototype
1090}
1091
1092/**
1093 * @constructor
1094 * @extends {WebInspector.WikiParser.ArticleElement}
1095 * @param {!WebInspector.WikiParser.ArticleElement.Type} type
1096 * @param {!Array.<!WebInspector.WikiParser.ArticleElement>} children
1097 */
1098WebInspector.WikiParser.Inline = function(type, children)
1099{
1100    WebInspector.WikiParser.ArticleElement.call(this, type)
1101    this._children = children;
1102}
1103
1104WebInspector.WikiParser.Inline.prototype = {
1105    /**
1106     * @return {!Array.<!WebInspector.WikiParser.ArticleElement>}
1107     */
1108    children: function()
1109    {
1110        return this._children;
1111    },
1112
1113    __proto__: WebInspector.WikiParser.ArticleElement.prototype
1114}
1115
1116/**
1117 * @constructor
1118 * @extends {WebInspector.WikiParser.Inline}
1119 * @param {string} url
1120 * @param {!Array.<!WebInspector.WikiParser.ArticleElement>} children
1121 */
1122WebInspector.WikiParser.Link = function(url, children)
1123{
1124    WebInspector.WikiParser.Inline.call(this, WebInspector.WikiParser.ArticleElement.Type.Link, children);
1125    this._url = url;
1126}
1127
1128WebInspector.WikiParser.Link.prototype = {
1129    /**
1130     * @return {string}
1131     */
1132    url : function()
1133    {
1134        return this._url;
1135    },
1136
1137    __proto__: WebInspector.WikiParser.Inline.prototype
1138}
1139