Verzeichnisstruktur phpBB-3.2.0
- Veröffentlicht
- 06.01.2017
So funktioniert es
|
Auf das letzte Element klicken. Dies geht jeweils ein Schritt zurück |
Auf das Icon klicken, dies öffnet das Verzeichnis. Nochmal klicken schließt das Verzeichnis. |
|
(Beispiel Datei-Icons)
|
Auf das Icon klicken um den Quellcode anzuzeigen |
Parser.js
0001 var hasEscapedChars, hasRefs, refs, startTagLen, startTagPos, endTagPos, endTagLen;
0002
0003 // Unlike the PHP parser, init() must not take an argument
0004 init();
0005
0006 // Match block-level markup as well as forced line breaks
0007 matchBlockLevelMarkup();
0008
0009 // Capture link references after block markup as been overwritten
0010 matchLinkReferences();
0011
0012 // Inline code must be done first to avoid false positives in other inline markup
0013 matchInlineCode();
0014
0015 // Do the rest of inline markup. Images must be matched before links
0016 matchImages();
0017 matchLinks();
0018 matchStrikethrough();
0019 matchSuperscript();
0020 matchEmphasis();
0021 matchForcedLineBreaks();
0022
0023 /**
0024 * Add an image tag for given text span
0025 *
0026 * @param {!number} startTagPos Start tag position
0027 * @param {!number} endTagPos End tag position
0028 * @param {!number} endTagLen End tag length
0029 * @param {!string} linkInfo URL optionally followed by space and a title
0030 * @param {!string} alt Value for the alt attribute
0031 */
0032 function addImageTag(startTagPos, endTagPos, endTagLen, linkInfo, alt)
0033 {
0034 var tag = addTagPair('IMG', startTagPos, 2, endTagPos, endTagLen);
0035 setLinkAttributes(tag, linkInfo, 'src');
0036 tag.setAttribute('alt', decode(alt));
0037
0038 // Overwrite the markup
0039 overwrite(startTagPos, endTagPos + endTagLen - startTagPos);
0040 }
0041
0042 /**
0043 * Add the tag pair for an inline code span
0044 *
0045 * @param {!Object} left Left marker
0046 * @param {!Object} right Right marker
0047 */
0048 function addInlineCodeTags(left, right)
0049 {
0050 var startTagPos = left.pos,
0051 startTagLen = left.len + left.trimAfter,
0052 endTagPos = right.pos - right.trimBefore,
0053 endTagLen = right.len + right.trimBefore;
0054 addTagPair('C', startTagPos, startTagLen, endTagPos, endTagLen);
0055 overwrite(startTagPos, endTagPos + endTagLen - startTagPos);
0056 }
0057
0058 /**
0059 * Add an image tag for given text span
0060 *
0061 * @param {!number} startTagPos Start tag position
0062 * @param {!number} endTagPos End tag position
0063 * @param {!number} endTagLen End tag length
0064 * @param {!string} linkInfo URL optionally followed by space and a title
0065 */
0066 function addLinkTag(startTagPos, endTagPos, endTagLen, linkInfo)
0067 {
0068 // Give the link a slightly worse priority if this is a implicit reference and a slightly
0069 // better priority if it's an explicit reference or an inline link or to give it precedence
0070 // over possible BBCodes such as [b](https://en.wikipedia.org/wiki/B)
0071 var priority = (endTagLen === 1) ? 1 : -1;
0072
0073 var tag = addTagPair('URL', startTagPos, 1, endTagPos, endTagLen, priority);
0074 setLinkAttributes(tag, linkInfo, 'url');
0075
0076 // Overwrite the markup without touching the link's text
0077 overwrite(startTagPos, 1);
0078 overwrite(endTagPos, endTagLen);
0079 }
0080
0081 /**
0082 * Close a list at given offset
0083 *
0084 * @param {!Array} list
0085 * @param {!number} textBoundary
0086 */
0087 function closeList(list, textBoundary)
0088 {
0089 addEndTag('LIST', textBoundary, 0).pairWith(list.listTag);
0090 addEndTag('LI', textBoundary, 0).pairWith(list.itemTag);
0091
0092 if (list.tight)
0093 {
0094 list.itemTags.forEach(function(itemTag)
0095 {
0096 itemTag.removeFlags(RULE_CREATE_PARAGRAPHS);
0097 });
0098 }
0099 }
0100
0101 /**
0102 * Compute the amount of text to ignore at the start of a quote line
0103 *
0104 * @param {!string} str Original quote markup
0105 * @param {!number} maxQuoteDepth Maximum quote depth
0106 * @return {!number} Number of characters to ignore
0107 */
0108 function computeQuoteIgnoreLen(str, maxQuoteDepth)
0109 {
0110 var remaining = str;
0111 while (--maxQuoteDepth >= 0)
0112 {
0113 remaining = remaining.replace(/^ *> ?/, '');
0114 }
0115
0116 return str.length - remaining.length;
0117 }
0118
0119 /**
0120 * Decode a chunk of encoded text to be used as an attribute value
0121 *
0122 * Decodes escaped literals and removes slashes and 0x1A characters
0123 *
0124 * @param {!string} str Encoded text
0125 * @return {!string} Decoded text
0126 */
0127 function decode(str)
0128 {
0129 if (HINT.LITEDOWN_DECODE_HTML_ENTITIES && config.decodeHtmlEntities && str.indexOf('&') > -1)
0130 {
0131 str = html_entity_decode(str);
0132 }
0133 str = str.replace(/\x1A/g, '');
0134
0135 if (hasEscapedChars)
0136 {
0137 str = str.replace(
0138 /\x1B./g,
0139 function (seq)
0140 {
0141 return {
0142 "\x1B0": '!', "\x1B1": '"', "\x1B2": "'", "\x1B3": '(',
0143 "\x1B4": ')', "\x1B5": '*', "\x1B6": '[', "\x1B7": '\\',
0144 "\x1B8": ']', "\x1B9": '^', "\x1BA": '_', "\x1BB": '`',
0145 "\x1BC": '~'
0146 }[seq];
0147 }
0148 );
0149 }
0150
0151 return str;
0152 }
0153
0154 /**
0155 * Encode escaped literals that have a special meaning
0156 *
0157 * @param {!string} str Original text
0158 * @return {!string} Encoded text
0159 */
0160 function encode(str)
0161 {
0162 return str.replace(
0163 /\\[!"'()*[\\\]^_`~]/g,
0164 function (str)
0165 {
0166 return {
0167 '\\!': "\x1B0", '\\"': "\x1B1", "\\'": "\x1B2", '\\(' : "\x1B3",
0168 '\\)': "\x1B4", '\\*': "\x1B5", '\\[': "\x1B6", '\\\\': "\x1B7",
0169 '\\]': "\x1B8", '\\^': "\x1B9", '\\_': "\x1BA", '\\`' : "\x1BB",
0170 '\\~': "\x1BC"
0171 }[str];
0172 }
0173 );
0174 }
0175
0176 /**
0177 * Return the length of the markup at the end of an ATX header
0178 *
0179 * @param {!number} startPos Start of the header's text
0180 * @param {!number} endPos End of the header's text
0181 * @return {!number}
0182 */
0183 function getAtxHeaderEndTagLen(startPos, endPos)
0184 {
0185 var content = text.substr(startPos, endPos - startPos),
0186 m = /[ \t]*#*[ \t]*$/.exec(content);
0187
0188 return m[0].length;
0189 }
0190
0191 /**
0192 * Get emphasis markup split by block
0193 *
0194 * @param {!RegExp} regexp Regexp used to match emphasis
0195 * @param {!number} pos Position in the text of the first emphasis character
0196 * @return {!Array} Each array contains a list of [matchPos, matchLen] pairs
0197 */
0198 function getEmphasisByBlock(regexp, pos)
0199 {
0200 var block = [],
0201 blocks = [],
0202 breakPos = breakPos = text.indexOf("\x17", pos),
0203 m;
0204
0205 regexp.lastIndex = pos;
0206 while (m = regexp.exec(text))
0207 {
0208 var matchPos = m['index'],
0209 matchLen = m[0].length;
0210
0211 // Test whether we've just passed the limits of a block
0212 if (matchPos > breakPos)
0213 {
0214 blocks.push(block);
0215 block = [];
0216 breakPos = text.indexOf("\x17", matchPos);
0217 }
0218
0219 // Test whether we should ignore this markup
0220 if (!ignoreEmphasis(matchPos, matchLen))
0221 {
0222 block.push([matchPos, matchLen]);
0223 }
0224 }
0225 blocks.push(block);
0226
0227 return blocks;
0228 }
0229
0230 /**
0231 * Capture and return inline code markers
0232 *
0233 * @return {!Array<!Object>}
0234 */
0235 function getInlineCodeMarkers()
0236 {
0237 var pos = text.indexOf('`');
0238 if (pos < 0)
0239 {
0240 return [];
0241 }
0242
0243 var regexp = /(`+)(\s*)[^\x17`]*/g,
0244 trimNext = 0,
0245 markers = [],
0246 _text = text.replace(/\x1BB/g, '\\`'),
0247 m;
0248 regexp.lastIndex = pos;
0249 while (m = regexp.exec(_text))
0250 {
0251 markers.push({
0252 pos : m['index'],
0253 len : m[1].length,
0254 trimBefore : trimNext,
0255 trimAfter : m[2].length,
0256 next : m['index'] + m[0].length
0257 });
0258 trimNext = m[0].length - m[0].replace(/\s+$/, '').length;
0259 }
0260
0261 return markers;
0262 }
0263
0264 /**
0265 * Capture and return labels used in current text
0266 *
0267 * @return {!Object} Labels' text position as keys, lowercased text content as values
0268 */
0269 function getLabels()
0270 {
0271 var labels = {}, m, regexp = /\[((?:[^\x17[\]]|\[[^\x17[\]]*\])*)\]/g;
0272 while (m = regexp.exec(text))
0273 {
0274 labels[m['index']] = m[1].toLowerCase();
0275 }
0276
0277 return labels;
0278 }
0279
0280 /**
0281 * Capture lines that contain a Setext-tyle header
0282 *
0283 * @return {!Object}
0284 */
0285 function getSetextLines()
0286 {
0287 var setextLines = {};
0288
0289 // Capture the underlines used for Setext-style headers
0290 if (text.indexOf('-') === -1 && text.indexOf('=') === -1)
0291 {
0292 return setextLines;
0293 }
0294
0295 // Capture the any series of - or = alone on a line, optionally preceded with the
0296 // angle brackets notation used in blockquotes
0297 var m, regexp = /^(?=[-=>])(?:> ?)*(?=[-=])(?:-+|=+) *$/gm;
0298
0299 while (m = regexp.exec(text))
0300 {
0301 var match = m[0],
0302 matchPos = m['index'];
0303
0304 // Compute the position of the end tag. We start on the LF character before the
0305 // match and keep rewinding until we find a non-space character
0306 var endTagPos = matchPos - 1;
0307 while (endTagPos > 0 && text[endTagPos - 1] === ' ')
0308 {
0309 --endTagPos;
0310 }
0311
0312 // Store at the offset of the LF character
0313 setextLines[matchPos - 1] = {
0314 endTagLen : matchPos + match.length - endTagPos,
0315 endTagPos : endTagPos,
0316 quoteDepth : match.length - match.replace(/>/g, '').length,
0317 tagName : (match.charAt(0) === '=') ? 'H1' : 'H2'
0318 };
0319 }
0320
0321 return setextLines;
0322 }
0323
0324 /**
0325 * Test whether emphasis should be ignored at the given position in the text
0326 *
0327 * @param {!number} matchPos Position of the emphasis in the text
0328 * @param {!number} matchLen Length of the emphasis
0329 * @return {!boolean}
0330 */
0331 function ignoreEmphasis(matchPos, matchLen)
0332 {
0333 // Ignore single underscores between alphanumeric characters
0334 return (text[matchPos] === '_' && matchLen === 1 && isSurroundedByAlnum(matchPos, matchLen));
0335 }
0336
0337 /**
0338 * Initialize this parser
0339 */
0340 function init()
0341 {
0342 if (text.indexOf('\\') < 0)
0343 {
0344 hasEscapedChars = false;
0345 }
0346 else
0347 {
0348 hasEscapedChars = true;
0349
0350 // Encode escaped literals that have a special meaning otherwise, so that we don't have
0351 // to take them into account in regexps
0352 text = encode(text);
0353 }
0354
0355 // We append a couple of lines and a non-whitespace character at the end of the text in
0356 // order to trigger the closure of all open blocks such as quotes and lists
0357 text += "\n\n\x17";
0358 }
0359
0360 /**
0361 * Test whether given character is alphanumeric
0362 *
0363 * @param {!string} chr
0364 * @return {!boolean}
0365 */
0366 function isAlnum(chr)
0367 {
0368 return (' abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'.indexOf(chr) > 0);
0369 }
0370
0371 /**
0372 * Test whether a length of text is surrounded by alphanumeric characters
0373 *
0374 * @param {!number} matchPos Start of the text
0375 * @param {!number} matchLen Length of the text
0376 * @return {!boolean}
0377 */
0378 function isSurroundedByAlnum(matchPos, matchLen)
0379 {
0380 return (matchPos > 0 && isAlnum(text[matchPos - 1]) && isAlnum(text[matchPos + matchLen]));
0381 }
0382
0383 /**
0384 * Mark the boundary of a block in the original text
0385 *
0386 * @param {!number} pos
0387 */
0388 function markBoundary(pos)
0389 {
0390 text = text.substr(0, pos) + "\x17" + text.substr(pos + 1);
0391 }
0392
0393 /**
0394 * Match block-level markup, as well as forced line breaks and headers
0395 */
0396 function matchBlockLevelMarkup()
0397 {
0398 var codeFence,
0399 codeIndent = 4,
0400 codeTag,
0401 lineIsEmpty = true,
0402 lists = [],
0403 listsCnt = 0,
0404 newContext = false,
0405 quotes = [],
0406 quotesCnt = 0,
0407 setextLines = getSetextLines(),
0408 textBoundary = 0,
0409 breakParagraph,
0410 continuation,
0411 endTag,
0412 ignoreLen,
0413 indentStr,
0414 indentLen,
0415 lfPos,
0416 listIndex,
0417 maxIndent,
0418 minIndent,
0419 quoteDepth,
0420 tagPos,
0421 tagLen;
0422
0423 // Capture all the lines at once so that we can overwrite newlines safely, without preventing
0424 // further matches
0425 var matches = [],
0426 m,
0427 regexp = /^(?:(?=[-*+\d \t>`~#_])((?: {0,3}> ?)+)?([ \t]+)?(\* *\* *\*[* ]*$|- *- *-[- ]*$|_ *_ *_[_ ]*$)?((?:[-*+]|\d+\.)[ \t]+(?=\S))?[ \t]*(#{1,6}[ \t]+|```+[^`\n]*$|~~~+[^~\n]*$)?)?/gm;
0428 while (m = regexp.exec(text))
0429 {
0430 matches.push(m);
0431
0432 // Move regexp.lastIndex if the current match is empty
0433 if (m['index'] === regexp['lastIndex'])
0434 {
0435 ++regexp['lastIndex'];
0436 }
0437 }
0438
0439 matches.forEach(function(m)
0440 {
0441 var matchPos = m['index'],
0442 matchLen = m[0].length;
0443
0444 ignoreLen = 0;
0445 quoteDepth = 0;
0446
0447 // If the last line was empty then this is not a continuation, and vice-versa
0448 continuation = !lineIsEmpty;
0449
0450 // Capture the position of the end of the line and determine whether the line is empty
0451 lfPos = text.indexOf("\n", matchPos);
0452 lineIsEmpty = (lfPos === matchPos + matchLen && !m[3] && !m[4] && !m[5]);
0453
0454 // If the match is empty we need to move the cursor manually
0455 if (!matchLen)
0456 {
0457 ++regexp.lastIndex;
0458 }
0459
0460 // If the line is empty and it's the first empty line then we break current paragraph.
0461 breakParagraph = (lineIsEmpty && continuation);
0462
0463 // Count quote marks
0464 if (m[1])
0465 {
0466 quoteDepth = m[1].length - m[1].replace(/>/g, '').length;
0467 ignoreLen = m[1].length;
0468 if (codeTag && codeTag.hasAttribute('quoteDepth'))
0469 {
0470 quoteDepth = Math.min(quoteDepth, codeTag.getAttribute('quoteDepth'));
0471 ignoreLen = computeQuoteIgnoreLen(m[1], quoteDepth);
0472 }
0473
0474 // Overwrite quote markup
0475 overwrite(matchPos, ignoreLen);
0476 }
0477
0478 // Close supernumerary quotes
0479 if (quoteDepth < quotesCnt && !continuation)
0480 {
0481 newContext = true;
0482
0483 do
0484 {
0485 addEndTag('QUOTE', textBoundary, 0).pairWith(quotes.pop());
0486 }
0487 while (quoteDepth < --quotesCnt);
0488 }
0489
0490 // Open new quotes
0491 if (quoteDepth > quotesCnt && !lineIsEmpty)
0492 {
0493 newContext = true;
0494
0495 do
0496 {
0497 var tag = addStartTag('QUOTE', matchPos, 0, quotesCnt - 999);
0498 quotes.push(tag);
0499 }
0500 while (quoteDepth > ++quotesCnt);
0501 }
0502
0503 // Compute the width of the indentation
0504 var indentWidth = 0,
0505 indentPos = 0;
0506 if (m[2] && !codeFence)
0507 {
0508 indentStr = m[2];
0509 indentLen = indentStr.length;
0510
0511 do
0512 {
0513 if (indentStr.charAt(indentPos) === ' ')
0514 {
0515 ++indentWidth;
0516 }
0517 else
0518 {
0519 indentWidth = (indentWidth + 4) & ~3;
0520 }
0521 }
0522 while (++indentPos < indentLen && indentWidth < codeIndent);
0523 }
0524
0525 // Test whether we're out of a code block
0526 if (codeTag && !codeFence && indentWidth < codeIndent && !lineIsEmpty)
0527 {
0528 newContext = true;
0529 }
0530
0531 if (newContext)
0532 {
0533 newContext = false;
0534
0535 // Close the code block if applicable
0536 if (codeTag)
0537 {
0538 // Overwrite the whole block
0539 overwrite(codeTag.getPos(), textBoundary - codeTag.getPos());
0540
0541 endTag = addEndTag('CODE', textBoundary, 0, -1);
0542 endTag.pairWith(codeTag);
0543 codeTag = null;
0544 codeFence = null;
0545 }
0546
0547 // Close all the lists
0548 lists.forEach(function(list)
0549 {
0550 closeList(list, textBoundary);
0551 });
0552 lists = [];
0553 listsCnt = 0;
0554
0555 // Mark the block boundary
0556 if (matchPos)
0557 {
0558 markBoundary(matchPos - 1);
0559 }
0560 }
0561
0562 if (indentWidth >= codeIndent)
0563 {
0564 if (codeTag || !continuation)
0565 {
0566 // Adjust the amount of text being ignored
0567 ignoreLen = (m[1] || '').length + indentPos;
0568
0569 if (!codeTag)
0570 {
0571 // Create code block
0572 codeTag = addStartTag('CODE', matchPos + ignoreLen, 0, -999);
0573 }
0574
0575 // Clear the captures to prevent any further processing
0576 m = {};
0577 }
0578 }
0579 else
0580 {
0581 var hasListItem = !!m[4];
0582
0583 if (!indentWidth && !continuation && !hasListItem)
0584 {
0585 // Start of a new context
0586 listIndex = -1;
0587 }
0588 else if (continuation && !hasListItem)
0589 {
0590 // Continuation of current list item or paragraph
0591 listIndex = listsCnt - 1;
0592 }
0593 else if (!listsCnt)
0594 {
0595 // We're not inside of a list already, we can start one if there's a list item
0596 // and it's either not in continuation of a paragraph or immediately after a
0597 // block
0598 if (hasListItem && (!continuation || text.charAt(matchPos - 1) === "\x17"))
0599 {
0600 // Start of a new list
0601 listIndex = 0;
0602 }
0603 else
0604 {
0605 // We're in a normal paragraph
0606 listIndex = -1;
0607 }
0608 }
0609 else
0610 {
0611 // We're inside of a list but we need to compute the depth
0612 listIndex = 0;
0613 while (listIndex < listsCnt && indentWidth > lists[listIndex].maxIndent)
0614 {
0615 ++listIndex;
0616 }
0617 }
0618
0619 // Close deeper lists
0620 while (listIndex < listsCnt - 1)
0621 {
0622 closeList(lists.pop(), textBoundary);
0623 --listsCnt;
0624 }
0625
0626 // If there's no list item at current index, we'll need to either create one or
0627 // drop down to previous index, in which case we have to adjust maxIndent
0628 if (listIndex === listsCnt && !hasListItem)
0629 {
0630 --listIndex;
0631 }
0632
0633 if (hasListItem && listIndex >= 0)
0634 {
0635 breakParagraph = true;
0636
0637 // Compute the position and amount of text consumed by the item tag
0638 tagPos = matchPos + ignoreLen + indentPos
0639 tagLen = m[4].length;
0640
0641 // Create a LI tag that consumes its markup
0642 var itemTag = addStartTag('LI', tagPos, tagLen);
0643
0644 // Overwrite the markup
0645 overwrite(tagPos, tagLen);
0646
0647 // If the list index is within current lists count it means this is not a new
0648 // list and we have to close the last item. Otherwise, it's a new list that we
0649 // have to create
0650 if (listIndex < listsCnt)
0651 {
0652 addEndTag('LI', textBoundary, 0).pairWith(lists[listIndex].itemTag);
0653
0654 // Record the item in the list
0655 lists[listIndex].itemTag = itemTag;
0656 lists[listIndex].itemTags.push(itemTag);
0657 }
0658 else
0659 {
0660 ++listsCnt;
0661
0662 if (listIndex)
0663 {
0664 minIndent = lists[listIndex - 1].maxIndent + 1;
0665 maxIndent = Math.max(minIndent, listIndex * 4);
0666 }
0667 else
0668 {
0669 minIndent = 0;
0670 maxIndent = indentWidth;
0671 }
0672
0673 // Create a 0-width LIST tag right before the item tag LI
0674 var listTag = addStartTag('LIST', tagPos, 0);
0675
0676 // Test whether the list item ends with a dot, as in "1."
0677 if (m[4].indexOf('.') > -1)
0678 {
0679 listTag.setAttribute('type', 'decimal');
0680
0681 var start = +m[4];
0682 if (start !== 1)
0683 {
0684 listTag.setAttribute('start', start);
0685 }
0686 }
0687
0688 // Record the new list depth
0689 lists.push({
0690 listTag : listTag,
0691 itemTag : itemTag,
0692 itemTags : [itemTag],
0693 minIndent : minIndent,
0694 maxIndent : maxIndent,
0695 tight : true
0696 });
0697 }
0698 }
0699
0700 // If we're in a list, on a non-empty line preceded with a blank line...
0701 if (listsCnt && !continuation && !lineIsEmpty)
0702 {
0703 // ...and this is not the first item of the list...
0704 if (lists[0].itemTags.length > 1 || !hasListItem)
0705 {
0706 // ...every list that is currently open becomes loose
0707 lists.forEach(function(list)
0708 {
0709 list.tight = false;
0710 });
0711 }
0712 }
0713
0714 codeIndent = (listsCnt + 1) * 4;
0715 }
0716
0717 if (m[5])
0718 {
0719 // Headers
0720 if (m[5].charAt(0) === '#')
0721 {
0722 startTagLen = m[5].length;
0723 startTagPos = matchPos + matchLen - startTagLen;
0724 endTagLen = getAtxHeaderEndTagLen(matchPos + matchLen, lfPos);
0725 endTagPos = lfPos - endTagLen;
0726
0727 addTagPair('H' + /#{1,6}/.exec(m[5])[0].length, startTagPos, startTagLen, endTagPos, endTagLen);
0728
0729 // Mark the start and the end of the header as boundaries
0730 markBoundary(startTagPos);
0731 markBoundary(lfPos);
0732
0733 if (continuation)
0734 {
0735 breakParagraph = true;
0736 }
0737 }
0738 // Code fence
0739 else if (m[5].charAt(0) === '`' || m[5].charAt(0) === '~')
0740 {
0741 tagPos = matchPos + ignoreLen;
0742 tagLen = lfPos - tagPos;
0743
0744 if (codeTag && m[5] === codeFence)
0745 {
0746 endTag = addEndTag('CODE', tagPos, tagLen, -1);
0747 endTag.pairWith(codeTag);
0748
0749 addIgnoreTag(textBoundary, tagPos - textBoundary);
0750
0751 // Overwrite the whole block
0752 overwrite(codeTag.getPos(), tagPos + tagLen - codeTag.getPos());
0753 codeTag = null;
0754 codeFence = null;
0755 }
0756 else if (!codeTag)
0757 {
0758 // Create code block
0759 codeTag = addStartTag('CODE', tagPos, tagLen);
0760 codeFence = m[5].replace(/[^`~]+/, '');
0761 codeTag.setAttribute('quoteDepth', quoteDepth);
0762
0763 // Ignore the next character, which should be a newline
0764 addIgnoreTag(tagPos + tagLen, 1);
0765
0766 // Add the language if present, e.g. ```php
0767 var lang = m[5].replace(/^[`~\s]*/, '').replace(/\s+$/, '');
0768 if (lang !== '')
0769 {
0770 codeTag.setAttribute('lang', lang);
0771 }
0772 }
0773 }
0774 }
0775 else if (m[3] && !listsCnt && text.charAt(matchPos + matchLen) !== "\x17")
0776 {
0777 // Horizontal rule
0778 addSelfClosingTag('HR', matchPos + ignoreLen, matchLen - ignoreLen);
0779 breakParagraph = true;
0780
0781 // Mark the end of the line as a boundary
0782 markBoundary(lfPos);
0783 }
0784 else if (setextLines[lfPos] && setextLines[lfPos].quoteDepth === quoteDepth && !lineIsEmpty && !listsCnt && !codeTag)
0785 {
0786 // Setext-style header
0787 addTagPair(
0788 setextLines[lfPos].tagName,
0789 matchPos + ignoreLen,
0790 0,
0791 setextLines[lfPos].endTagPos,
0792 setextLines[lfPos].endTagLen
0793 );
0794
0795 // Mark the end of the Setext line
0796 markBoundary(setextLines[lfPos].endTagPos + setextLines[lfPos].endTagLen);
0797 }
0798
0799 if (breakParagraph)
0800 {
0801 addParagraphBreak(textBoundary);
0802 markBoundary(textBoundary);
0803 }
0804
0805 if (!lineIsEmpty)
0806 {
0807 textBoundary = lfPos;
0808 }
0809
0810 if (ignoreLen)
0811 {
0812 addIgnoreTag(matchPos, ignoreLen, 1000);
0813 }
0814 });
0815 }
0816
0817 /**
0818 * Match all forms of emphasis (emphasis and strong, using underscores or asterisks)
0819 */
0820 function matchEmphasis()
0821 {
0822 matchEmphasisByCharacter('*', /\*+/g);
0823 matchEmphasisByCharacter('_', /_+/g);
0824 }
0825
0826 /**
0827 * Match emphasis and strong applied using given character
0828 *
0829 * @param {!string} character Markup character, either * or _
0830 * @param {!RegExp} regexp Regexp used to match the series of emphasis character
0831 */
0832 function matchEmphasisByCharacter(character, regexp)
0833 {
0834 var pos = text.indexOf(character);
0835 if (pos === -1)
0836 {
0837 return;
0838 }
0839
0840 getEmphasisByBlock(regexp, pos).forEach(processEmphasisBlock);
0841 }
0842
0843 /**
0844 * Match forced line break
0845 */
0846 function matchForcedLineBreaks()
0847 {
0848 var pos = text.indexOf(" \n");
0849 while (pos !== -1)
0850 {
0851 addBrTag(pos + 2);
0852 pos = text.indexOf(" \n", pos + 3);
0853 }
0854 }
0855
0856 /**
0857 * Match images markup
0858 */
0859 function matchImages()
0860 {
0861 var pos = text.indexOf(' > 0)
0867 {
0868 matchInlineImages();
0869 }
0870 if (hasRefs)
0871 {
0872 matchReferenceImages();
0873 }
0874 }
0875
0876 /**
0877 * Match inline images markup
0878 */
0879 function matchInlineImages()
0880 {
0881 var m, regexp = /!\[(?:[^\x17[\]]|\[[^\x17[\]]*\])*\]\(( *(?:[^\x17\s()]|\([^\x17\s()]*\))*(?=[ )]) *(?:"[^\x17]*?"|'[^\x17]*?'|\([^\x17)]*\))? *)\)/g;
0882 while (m = regexp.exec(text))
0883 {
0884 var linkInfo = m[1],
0885 startTagPos = m['index'],
0886 endTagLen = 3 + linkInfo.length,
0887 endTagPos = startTagPos + m[0].length - endTagLen,
0888 alt = m[0].substr(2, m[0].length - endTagLen - 2);
0889
0890 addImageTag(startTagPos, endTagPos, endTagLen, linkInfo, alt);
0891 }
0892 }
0893
0894 /**
0895 * Match reference images markup
0896 */
0897 function matchReferenceImages()
0898 {
0899 var m, regexp = /!\[((?:[^\x17[\]]|\[[^\x17[\]]*\])*)\](?: ?\[([^\x17[\]]+)\])?/g;
0900 while (m = regexp.exec(text))
0901 {
0902 var startTagPos = +m['index'],
0903 endTagPos = startTagPos + 2 + m[1].length,
0904 endTagLen = 1,
0905 alt = m[1],
0906 id = alt;
0907
0908 if (m[2] > '' && refs[m[2]])
0909 {
0910 endTagLen = m[0].length - alt.length - 2;
0911 id = m[2];
0912 }
0913 else if (!refs[id])
0914 {
0915 continue;
0916 }
0917
0918 addImageTag(startTagPos, endTagPos, endTagLen, refs[id], alt);
0919 }
0920 }
0921
0922 /**
0923 * Match inline code spans
0924 */
0925 function matchInlineCode()
0926 {
0927 var markers = getInlineCodeMarkers(),
0928 i = -1,
0929 cnt = markers.length;
0930 while (++i < (cnt - 1))
0931 {
0932 var pos = markers[i].next,
0933 j = i;
0934 if (text.charAt(markers[i].pos) !== '`')
0935 {
0936 // Adjust the left marker if its first backtick was escaped
0937 ++markers[i].pos;
0938 --markers[i].len;
0939 }
0940 while (++j < cnt && markers[j].pos === pos)
0941 {
0942 if (markers[j].len === markers[i].len)
0943 {
0944 addInlineCodeTags(markers[i], markers[j]);
0945 i = j;
0946 break;
0947 }
0948 pos = markers[j].next;
0949 }
0950 }
0951 }
0952
0953 /**
0954 * Match inline links markup
0955 */
0956 function matchInlineLinks()
0957 {
0958 var m, regexp = /\[(?:[^\x17[\]]|\[[^\x17[\]]*\])*\]\(( *(?:[^\x17\s()]|\([^\x17\s()]*\))*(?=[ )]) *(?:"[^\x17]*?"|'[^\x17]*?'|\([^\x17)]*\))? *)\)/g;
0959 while (m = regexp.exec(text))
0960 {
0961 var linkInfo = m[1],
0962 startTagPos = m['index'],
0963 endTagLen = 3 + linkInfo.length,
0964 endTagPos = startTagPos + m[0].length - endTagLen;
0965
0966 addLinkTag(startTagPos, endTagPos, endTagLen, linkInfo);
0967 }
0968 }
0969
0970 /**
0971 * Capture link reference definitions in current text
0972 */
0973 function matchLinkReferences()
0974 {
0975 hasRefs = false;
0976 refs = {};
0977 if (text.indexOf(']:') === -1)
0978 {
0979 return;
0980 }
0981
0982 var m, regexp = /^\x1A* {0,3}\[([^\x17\]]+)\]: *([^\s\x17]+ *(?:"[^\x17]*?"|'[^\x17]*?'|\([^\x17)]*\))?)[^\x17\n]*\n?/gm;
0983 while (m = regexp.exec(text))
0984 {
0985 addIgnoreTag(m['index'], m[0].length, -2);
0986
0987 // Ignore the reference if it already exists
0988 var id = m[1].toLowerCase();
0989 if (refs[id])
0990 {
0991 continue;
0992 }
0993
0994 hasRefs = true;
0995 refs[id] = m[2];
0996 }
0997 }
0998
0999 /**
1000 * Match inline and reference links
1001 */
1002 function matchLinks()
1003 {
1004 if (text.indexOf('](') !== -1)
1005 {
1006 matchInlineLinks();
1007 }
1008 if (hasRefs)
1009 {
1010 matchReferenceLinks();
1011 }
1012 }
1013
1014 /**
1015 * Match reference links markup
1016 */
1017 function matchReferenceLinks()
1018 {
1019 var labels = getLabels(), startTagPos;
1020 for (startTagPos in labels)
1021 {
1022 var id = labels[startTagPos],
1023 labelPos = +startTagPos + 2 + id.length,
1024 endTagPos = labelPos - 1,
1025 endTagLen = 1;
1026
1027 if (text.charAt(labelPos) === ' ')
1028 {
1029 ++labelPos;
1030 }
1031 if (labels[labelPos] > '' && refs[labels[labelPos]])
1032 {
1033 id = labels[labelPos];
1034 endTagLen = labelPos + 2 + id.length - endTagPos;
1035 }
1036 if (refs[id])
1037 {
1038 addLinkTag(+startTagPos, endTagPos, endTagLen, refs[id]);
1039 }
1040 }
1041 }
1042
1043 /**
1044 * Match strikethrough
1045 */
1046 function matchStrikethrough()
1047 {
1048 if (text.indexOf('~~') === -1)
1049 {
1050 return;
1051 }
1052
1053 var m, regexp = /~~[^\x17]+?~~/g;
1054 while (m = regexp.exec(text))
1055 {
1056 var match = m[0],
1057 matchPos = m['index'],
1058 matchLen = match.length;
1059
1060 addTagPair('DEL', matchPos, 2, matchPos + matchLen - 2, 2);
1061 }
1062 }
1063
1064 /**
1065 * Match superscript
1066 */
1067 function matchSuperscript()
1068 {
1069 if (text.indexOf('^') === -1)
1070 {
1071 return;
1072 }
1073
1074 var m, regexp = /\^[^\x17\s]+/g;
1075 while (m = regexp.exec(text))
1076 {
1077 var match = m[0],
1078 matchPos = m['index'],
1079 matchLen = match.length,
1080 startTagPos = matchPos,
1081 endTagPos = matchPos + matchLen;
1082
1083 var parts = match.split('^');
1084 parts.shift();
1085
1086 parts.forEach(function(part)
1087 {
1088 addTagPair('SUP', startTagPos, 1, endTagPos, 0);
1089 startTagPos += 1 + part.length;
1090 });
1091 }
1092 }
1093
1094 /**
1095 * Overwrite part of the text with substitution characters ^Z (0x1A)
1096 *
1097 * @param {!number} pos Start of the range
1098 * @param {!number} len Length of text to overwrite
1099 */
1100 function overwrite(pos, len)
1101 {
1102 text = text.substr(0, pos) + new Array(1 + len).join("\x1A") + text.substr(pos + len);
1103 }
1104
1105 /**
1106 * Process a list of emphasis markup strings
1107 *
1108 * @param {!Array<!Array<!number>>} block List of [matchPos, matchLen] pairs
1109 */
1110 function processEmphasisBlock(block)
1111 {
1112 var buffered = 0,
1113 emPos = -1,
1114 strongPos = -1,
1115 pair,
1116 remaining;
1117
1118 block.forEach(function(pair)
1119 {
1120 var matchPos = pair[0],
1121 matchLen = pair[1],
1122 closeLen = Math.min(3, matchLen),
1123 closeEm = closeLen & buffered & 1,
1124 closeStrong = closeLen & buffered & 2,
1125 emEndPos = matchPos,
1126 strongEndPos = matchPos;
1127
1128 if (buffered > 2 && emPos === strongPos)
1129 {
1130 if (closeEm)
1131 {
1132 emPos += 2;
1133 }
1134 else
1135 {
1136 ++strongPos;
1137 }
1138 }
1139
1140 if (closeEm && closeStrong)
1141 {
1142 if (emPos < strongPos)
1143 {
1144 emEndPos += 2;
1145 }
1146 else
1147 {
1148 ++strongEndPos;
1149 }
1150 }
1151
1152 remaining = matchLen;
1153 if (closeEm)
1154 {
1155 --buffered;
1156 --remaining;
1157 addTagPair('EM', emPos, 1, emEndPos, 1);
1158 }
1159 if (closeStrong)
1160 {
1161 buffered -= 2;
1162 remaining -= 2;
1163 addTagPair('STRONG', strongPos, 2, strongEndPos, 2);
1164 }
1165
1166 remaining = Math.min(3, remaining);
1167 if (remaining & 1)
1168 {
1169 emPos = matchPos + matchLen - remaining;
1170 }
1171 if (remaining & 2)
1172 {
1173 strongPos = matchPos + matchLen - remaining;
1174 }
1175 buffered += remaining;
1176 });
1177 }
1178
1179 /**
1180 * Set a URL or IMG tag's attributes
1181 *
1182 * @param {!Tag} tag URL or IMG tag
1183 * @param {!string} linkInfo Link's info: an URL optionally followed by spaces and a title
1184 * @param {!string} attrName Name of the URL attribute
1185 */
1186 function setLinkAttributes(tag, linkInfo, attrName)
1187 {
1188 var url = linkInfo.replace(/^\s*/, '').replace(/\s*$/, ''),
1189 title = '',
1190 pos = url.indexOf(' ')
1191 if (pos !== -1)
1192 {
1193 title = url.substr(pos).replace(/^\s*\S/, '').replace(/\S\s*$/, '');
1194 url = url.substr(0, pos);
1195 }
1196
1197 tag.setAttribute(attrName, decode(url));
1198 if (title > '')
1199 {
1200 tag.setAttribute('title', decode(title));
1201 }
1202 }