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  }