[ Index ] |
PHP Cross Reference of phpBB-3.3.14-deutsch |
[Summary view] [Print] [Text view]
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 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Mon Nov 25 19:05:08 2024 | Cross-referenced by PHPXref 0.7.1 |