[ Index ]

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


Generated: Mon Nov 25 19:05:08 2024 Cross-referenced by PHPXref 0.7.1