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.
Auf den Verzeichnisnamen klicken, dies zeigt nur das Verzeichnis mit Inhalt an

      (Beispiel Datei-Icons)

Auf das Icon klicken um den Quellcode anzuzeigen

Parser.js

Zuletzt modifiziert: 09.10.2024, 12:58 - Dateigröße: 26.48 KiB


 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('![');
 0862      if (pos === -1)
 0863      {
 0864          return;
 0865      }
 0866      if (text.indexOf('](', pos) > 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  }