[ Index ]

PHP Cross Reference of phpBB-3.2.11-deutsch

title

Body

[close]

/vendor/s9e/text-formatter/src/Plugins/Litedown/Parser/Passes/ -> Blocks.js (source)

   1  var setextLines = {};
   2  
   3  function parse()
   4  {
   5      matchSetextLines();
   6  
   7      var codeFence,
   8          codeIndent   = 4,
   9          codeTag,
  10          lineIsEmpty  = true,
  11          lists        = [],
  12          listsCnt     = 0,
  13          newContext   = false,
  14          quotes       = [],
  15          quotesCnt    = 0,
  16          textBoundary = 0,
  17          breakParagraph,
  18          continuation,
  19          endTag,
  20          ignoreLen,
  21          indentStr,
  22          indentLen,
  23          lfPos,
  24          listIndex,
  25          maxIndent,
  26          minIndent,
  27          quoteDepth,
  28          tagPos,
  29          tagLen;
  30  
  31      // Capture all the lines at once so that we can overwrite newlines safely, without preventing
  32      // further matches
  33      var matches = [],
  34          m,
  35          regexp = /^(?:(?=[-*+\d \t>`~#_])((?: {0,3}> ?)+)?([ \t]+)?(\* *\* *\*[* ]*$|- *- *-[- ]*$|_ *_ *_[_ ]*$)?((?:[-*+]|\d+\.)[ \t]+(?=\S))?[ \t]*(#{1,6}[ \t]+|```+[^`\n]*$|~~~+[^~\n]*$)?)?/gm;
  36      while (m = regexp.exec(text))
  37      {
  38          matches.push(m);
  39  
  40          // Move regexp.lastIndex if the current match is empty
  41          if (m['index'] === regexp['lastIndex'])
  42          {
  43              ++regexp['lastIndex'];
  44          }
  45      }
  46  
  47      matches.forEach(function(m)
  48      {
  49          var matchPos = +m['index'],
  50              matchLen = m[0].length,
  51              startPos,
  52              startLen,
  53              endPos,
  54              endLen;
  55  
  56          ignoreLen  = 0;
  57          quoteDepth = 0;
  58  
  59          // If the last line was empty then this is not a continuation, and vice-versa
  60          continuation = !lineIsEmpty;
  61  
  62          // Capture the position of the end of the line and determine whether the line is empty
  63          lfPos       = text.indexOf("\n", matchPos);
  64          lineIsEmpty = (lfPos === matchPos + matchLen && !m[3] && !m[4] && !m[5]);
  65  
  66          // If the match is empty we need to move the cursor manually
  67          if (!matchLen)
  68          {
  69              ++regexp.lastIndex;
  70          }
  71  
  72          // If the line is empty and it's the first empty line then we break current paragraph.
  73          breakParagraph = (lineIsEmpty && continuation);
  74  
  75          // Count quote marks
  76          if (m[1])
  77          {
  78              quoteDepth = m[1].length - m[1].replace(/>/g, '').length;
  79              ignoreLen  = m[1].length;
  80              if (codeTag && codeTag.hasAttribute('quoteDepth'))
  81              {
  82                  quoteDepth = Math.min(quoteDepth, codeTag.getAttribute('quoteDepth'));
  83                  ignoreLen  = computeQuoteIgnoreLen(m[1], quoteDepth);
  84              }
  85  
  86              // Overwrite quote markup
  87              overwrite(matchPos, ignoreLen);
  88          }
  89  
  90          // Close supernumerary quotes
  91          if (quoteDepth < quotesCnt && !continuation)
  92          {
  93              newContext = true;
  94  
  95              do
  96              {
  97                  addEndTag('QUOTE', textBoundary, 0).pairWith(quotes.pop());
  98              }
  99              while (quoteDepth < --quotesCnt);
 100          }
 101  
 102          // Open new quotes
 103          if (quoteDepth > quotesCnt && !lineIsEmpty)
 104          {
 105              newContext = true;
 106  
 107              do
 108              {
 109                  var tag = addStartTag('QUOTE', matchPos, 0, quotesCnt - 999);
 110                  quotes.push(tag);
 111              }
 112              while (quoteDepth > ++quotesCnt);
 113          }
 114  
 115          // Compute the width of the indentation
 116          var indentWidth = 0,
 117              indentPos   = 0;
 118          if (m[2] && !codeFence)
 119          {
 120              indentStr = m[2];
 121              indentLen = indentStr.length;
 122  
 123              do
 124              {
 125                  if (indentStr[indentPos] === ' ')
 126                  {
 127                      ++indentWidth;
 128                  }
 129                  else
 130                  {
 131                      indentWidth = (indentWidth + 4) & ~3;
 132                  }
 133              }
 134              while (++indentPos < indentLen && indentWidth < codeIndent);
 135          }
 136  
 137          // Test whether we're out of a code block
 138          if (codeTag && !codeFence && indentWidth < codeIndent && !lineIsEmpty)
 139          {
 140              newContext = true;
 141          }
 142  
 143          if (newContext)
 144          {
 145              newContext = false;
 146  
 147              // Close the code block if applicable
 148              if (codeTag)
 149              {
 150                  if (textBoundary > codeTag.getPos())
 151                  {
 152                      // Overwrite the whole block
 153                      overwrite(codeTag.getPos(), textBoundary - codeTag.getPos());
 154  
 155                      endTag = addEndTag('CODE', textBoundary, 0, -1);
 156                      endTag.pairWith(codeTag);
 157                  }
 158                  else
 159                  {
 160                      // The code block is empty
 161                      codeTag.invalidate();
 162                  }
 163                  codeTag = null;
 164                  codeFence = null;
 165              }
 166  
 167              // Close all the lists
 168              lists.forEach(function(list)
 169              {
 170                  closeList(list, textBoundary);
 171              });
 172              lists    = [];
 173              listsCnt = 0;
 174  
 175              // Mark the block boundary
 176              if (matchPos)
 177              {
 178                  markBoundary(matchPos - 1);
 179              }
 180          }
 181  
 182          if (indentWidth >= codeIndent)
 183          {
 184              if (codeTag || !continuation)
 185              {
 186                  // Adjust the amount of text being ignored
 187                  ignoreLen = (m[1] || '').length + indentPos;
 188  
 189                  if (!codeTag)
 190                  {
 191                      // Create code block
 192                      codeTag = addStartTag('CODE', matchPos + ignoreLen, 0, -999);
 193                  }
 194  
 195                  // Clear the captures to prevent any further processing
 196                  m = {};
 197              }
 198          }
 199          else
 200          {
 201              var hasListItem = !!m[4];
 202  
 203              if (!indentWidth && !continuation && !hasListItem)
 204              {
 205                  // Start of a new context
 206                  listIndex = -1;
 207              }
 208              else if (continuation && !hasListItem)
 209              {
 210                  // Continuation of current list item or paragraph
 211                  listIndex = listsCnt - 1;
 212              }
 213              else if (!listsCnt)
 214              {
 215                  // We're not inside of a list already, we can start one if there's a list item
 216                  listIndex = (hasListItem) ? 0 : -1
 217              }
 218              else
 219              {
 220                  // We're inside of a list but we need to compute the depth
 221                  listIndex = 0;
 222                  while (listIndex < listsCnt && indentWidth > lists[listIndex].maxIndent)
 223                  {
 224                      ++listIndex;
 225                  }
 226              }
 227  
 228              // Close deeper lists
 229              while (listIndex < listsCnt - 1)
 230              {
 231                  closeList(lists.pop(), textBoundary);
 232                  --listsCnt;
 233              }
 234  
 235              // If there's no list item at current index, we'll need to either create one or
 236              // drop down to previous index, in which case we have to adjust maxIndent
 237              if (listIndex === listsCnt && !hasListItem)
 238              {
 239                  --listIndex;
 240              }
 241  
 242              if (hasListItem && listIndex >= 0)
 243              {
 244                  breakParagraph = true;
 245  
 246                  // Compute the position and amount of text consumed by the item tag
 247                  tagPos = matchPos + ignoreLen + indentPos
 248                  tagLen = m[4].length;
 249  
 250                  // Create a LI tag that consumes its markup
 251                  var itemTag = addStartTag('LI', tagPos, tagLen);
 252  
 253                  // Overwrite the markup
 254                  overwrite(tagPos, tagLen);
 255  
 256                  // If the list index is within current lists count it means this is not a new
 257                  // list and we have to close the last item. Otherwise, it's a new list that we
 258                  // have to create
 259                  if (listIndex < listsCnt)
 260                  {
 261                      addEndTag('LI', textBoundary, 0).pairWith(lists[listIndex].itemTag);
 262  
 263                      // Record the item in the list
 264                      lists[listIndex].itemTag = itemTag;
 265                      lists[listIndex].itemTags.push(itemTag);
 266                  }
 267                  else
 268                  {
 269                      ++listsCnt;
 270  
 271                      if (listIndex)
 272                      {
 273                          minIndent = lists[listIndex - 1].maxIndent + 1;
 274                          maxIndent = Math.max(minIndent, listIndex * 4);
 275                      }
 276                      else
 277                      {
 278                          minIndent = 0;
 279                          maxIndent = indentWidth;
 280                      }
 281  
 282                      // Create a 0-width LIST tag right before the item tag LI
 283                      var listTag = addStartTag('LIST', tagPos, 0);
 284  
 285                      // Test whether the list item ends with a dot, as in "1."
 286                      if (m[4].indexOf('.') > -1)
 287                      {
 288                          listTag.setAttribute('type', 'decimal');
 289  
 290                          var start = +m[4];
 291                          if (start !== 1)
 292                          {
 293                              listTag.setAttribute('start', start);
 294                          }
 295                      }
 296  
 297                      // Record the new list depth
 298                      lists.push({
 299                          listTag   : listTag,
 300                          itemTag   : itemTag,
 301                          itemTags  : [itemTag],
 302                          minIndent : minIndent,
 303                          maxIndent : maxIndent,
 304                          tight     : true
 305                      });
 306                  }
 307              }
 308  
 309              // If we're in a list, on a non-empty line preceded with a blank line...
 310              if (listsCnt && !continuation && !lineIsEmpty)
 311              {
 312                  // ...and this is not the first item of the list...
 313                  if (lists[0].itemTags.length > 1 || !hasListItem)
 314                  {
 315                      // ...every list that is currently open becomes loose
 316                      lists.forEach(function(list)
 317                      {
 318                          list.tight = false;
 319                      });
 320                  }
 321              }
 322  
 323              codeIndent = (listsCnt + 1) * 4;
 324          }
 325  
 326          if (m[5])
 327          {
 328              // Headers
 329              if (m[5][0] === '#')
 330              {
 331                  startLen = m[5].length;
 332                  startPos = matchPos + matchLen - startLen;
 333                  endLen   = getAtxHeaderEndTagLen(matchPos + matchLen, lfPos);
 334                  endPos   = lfPos - endLen;
 335  
 336                  addTagPair('H' + /#{1,6}/.exec(m[5])[0].length, startPos, startLen, endPos, endLen);
 337  
 338                  // Mark the start and the end of the header as boundaries
 339                  markBoundary(startPos);
 340                  markBoundary(lfPos);
 341  
 342                  if (continuation)
 343                  {
 344                      breakParagraph = true;
 345                  }
 346              }
 347              // Code fence
 348              else if (m[5][0] === '`' || m[5][0] === '~')
 349              {
 350                  tagPos = matchPos + ignoreLen;
 351                  tagLen = lfPos - tagPos;
 352  
 353                  if (codeTag && m[5] === codeFence)
 354                  {
 355                      endTag = addEndTag('CODE', tagPos, tagLen, -1);
 356                      endTag.pairWith(codeTag);
 357  
 358                      addIgnoreTag(textBoundary, tagPos - textBoundary);
 359  
 360                      // Overwrite the whole block
 361                      overwrite(codeTag.getPos(), tagPos + tagLen - codeTag.getPos());
 362                      codeTag = null;
 363                      codeFence = null;
 364                  }
 365                  else if (!codeTag)
 366                  {
 367                      // Create code block
 368                      codeTag   = addStartTag('CODE', tagPos, tagLen);
 369                      codeFence = m[5].replace(/[^`~]+/, '');
 370                      codeTag.setAttribute('quoteDepth', quoteDepth);
 371  
 372                      // Ignore the next character, which should be a newline
 373                      addIgnoreTag(tagPos + tagLen, 1);
 374  
 375                      // Add the language if present, e.g. ```php
 376                      var lang = m[5].replace(/^[`~\s]*/, '').replace(/\s+$/, '');
 377                      if (lang !== '')
 378                      {
 379                          codeTag.setAttribute('lang', lang);
 380                      }
 381                  }
 382              }
 383          }
 384          else if (m[3] && !listsCnt && text[matchPos + matchLen] !== "\x17")
 385          {
 386              // Horizontal rule
 387              addSelfClosingTag('HR', matchPos + ignoreLen, matchLen - ignoreLen);
 388              breakParagraph = true;
 389  
 390              // Mark the end of the line as a boundary
 391              markBoundary(lfPos);
 392          }
 393          else if (setextLines[lfPos] && setextLines[lfPos].quoteDepth === quoteDepth && !lineIsEmpty && !listsCnt && !codeTag)
 394          {
 395              // Setext-style header
 396              addTagPair(
 397                  setextLines[lfPos].tagName,
 398                  matchPos + ignoreLen,
 399                  0,
 400                  setextLines[lfPos].endPos,
 401                  setextLines[lfPos].endLen
 402              );
 403  
 404              // Mark the end of the Setext line
 405              markBoundary(setextLines[lfPos].endPos + setextLines[lfPos].endLen);
 406          }
 407  
 408          if (breakParagraph)
 409          {
 410              addParagraphBreak(textBoundary);
 411              markBoundary(textBoundary);
 412          }
 413  
 414          if (!lineIsEmpty)
 415          {
 416              textBoundary = lfPos;
 417          }
 418  
 419          if (ignoreLen)
 420          {
 421              addIgnoreTag(matchPos, ignoreLen, 1000);
 422          }
 423      });
 424  }
 425  
 426  /**
 427  * Close a list at given offset
 428  *
 429  * @param  {!Array}  list
 430  * @param  {number} textBoundary
 431  */
 432  function closeList(list, textBoundary)
 433  {
 434      addEndTag('LIST', textBoundary, 0).pairWith(list.listTag);
 435      addEndTag('LI',   textBoundary, 0).pairWith(list.itemTag);
 436  
 437      if (list.tight)
 438      {
 439          list.itemTags.forEach(function(itemTag)
 440          {
 441              itemTag.removeFlags(RULE_CREATE_PARAGRAPHS);
 442          });
 443      }
 444  }
 445  
 446  /**
 447  * Compute the amount of text to ignore at the start of a quote line
 448  *
 449  * @param  {string} str           Original quote markup
 450  * @param  {number} maxQuoteDepth Maximum quote depth
 451  * @return {number}               Number of characters to ignore
 452  */
 453  function computeQuoteIgnoreLen(str, maxQuoteDepth)
 454  {
 455      var remaining = str;
 456      while (--maxQuoteDepth >= 0)
 457      {
 458          remaining = remaining.replace(/^ *> ?/, '');
 459      }
 460  
 461      return str.length - remaining.length;
 462  }
 463  
 464  /**
 465  * Return the length of the markup at the end of an ATX header
 466  *
 467  * @param  {number} startPos Start of the header's text
 468  * @param  {number} endPos   End of the header's text
 469  * @return {number}
 470  */
 471  function getAtxHeaderEndTagLen(startPos, endPos)
 472  {
 473      var content = text.substr(startPos, endPos - startPos),
 474          m = /[ \t]*#*[ \t]*$/.exec(content);
 475  
 476      return m[0].length;
 477  }
 478  
 479  /**
 480  * Capture and store lines that contain a Setext-tyle header
 481  */
 482  function matchSetextLines()
 483  {
 484      // Capture the underlines used for Setext-style headers
 485      if (text.indexOf('-') === -1 && text.indexOf('=') === -1)
 486      {
 487          return;
 488      }
 489  
 490      // Capture the any series of - or = alone on a line, optionally preceded with the
 491      // angle brackets notation used in blockquotes
 492      var m, regexp = /^(?=[-=>])(?:> ?)*(?=[-=])(?:-+|=+) *$/gm;
 493  
 494      while (m = regexp.exec(text))
 495      {
 496          var match    = m[0],
 497              matchPos = +m['index'];
 498  
 499          // Compute the position of the end tag. We start on the LF character before the
 500          // match and keep rewinding until we find a non-space character
 501          var endPos = matchPos - 1;
 502          while (endPos > 0 && text[endPos - 1] === ' ')
 503          {
 504              --endPos;
 505          }
 506  
 507          // Store at the offset of the LF character
 508          setextLines[matchPos - 1] = {
 509              endLen     : matchPos + match.length - endPos,
 510              endPos     : endPos,
 511              quoteDepth : match.length - match.replace(/>/g, '').length,
 512              tagName    : (match[0] === '=') ? 'H1' : 'H2'
 513          };
 514      }
 515  }


Generated: Wed Nov 11 20:33:01 2020 Cross-referenced by PHPXref 0.7.1