|
|
@@ -69,7 +69,7 @@ class MDUtils {
|
|
69
|
69
|
* @param string[] $lines
|
|
70
|
70
|
* @return string[]
|
|
71
|
71
|
*/
|
|
72
|
|
- public static function withoutTrailingBlankLines(array &$lines): array {
|
|
|
72
|
+ public static function withoutTrailingBlankLines(array $lines): array {
|
|
73
|
73
|
$stripped = $lines;
|
|
74
|
74
|
while (sizeof($stripped) > 0 && mb_strlen(trim($stripped[sizeof($stripped) - 1])) == 0) {
|
|
75
|
75
|
array_pop($stripped);
|
|
|
@@ -83,7 +83,7 @@ class MDUtils {
|
|
83
|
83
|
*
|
|
84
|
84
|
* @param string[] $lines
|
|
85
|
85
|
*/
|
|
86
|
|
- public static function containsBlankLine(array &$lines): bool {
|
|
|
86
|
+ public static function containsBlankLine(array $lines): bool {
|
|
87
|
87
|
foreach ($lines as $line) {
|
|
88
|
88
|
if (mb_strlen(trim($line)) == 0) return true;
|
|
89
|
89
|
}
|
|
|
@@ -462,6 +462,12 @@ class MDState {
|
|
462
|
462
|
*/
|
|
463
|
463
|
public int $p = 0;
|
|
464
|
464
|
|
|
|
465
|
+ /**
|
|
|
466
|
+ * General storage for anything readers need to track during the parsing
|
|
|
467
|
+ * process.
|
|
|
468
|
+ */
|
|
|
469
|
+ public array $userInfo = [];
|
|
|
470
|
+
|
|
465
|
471
|
private ?MDState $parent = null;
|
|
466
|
472
|
|
|
467
|
473
|
/**
|
|
|
@@ -495,8 +501,6 @@ class MDState {
|
|
495
|
501
|
*/
|
|
496
|
502
|
public MDHTMLFilter $tagFilter;
|
|
497
|
503
|
|
|
498
|
|
- private static string $textWhitespaceRegex = '^(\\s*)(?:(\\S|\\S.*\\S)(\\s*?))?$'; // 1=leading WS, 2=text, 3=trailing WS
|
|
499
|
|
-
|
|
500
|
504
|
/**
|
|
501
|
505
|
* @param string[] $lines - lines of markdown text
|
|
502
|
506
|
*/
|
|
|
@@ -552,7 +556,7 @@ class MDState {
|
|
552
|
556
|
$lines = MDUtils::withoutTrailingBlankLines(array_slice($this->lines, $this->p));
|
|
553
|
557
|
if (sizeof($lines) == 0) return null;
|
|
554
|
558
|
$this->p = sizeof($this->lines);
|
|
555
|
|
- return $this->inlineMarkdownToNode(implode("\n", $lines));
|
|
|
559
|
+ return new MDBlockNode($this->inlineMarkdownToNode(implode("\n", $lines)));
|
|
556
|
560
|
}
|
|
557
|
561
|
|
|
558
|
562
|
/**
|
|
|
@@ -571,8 +575,8 @@ class MDState {
|
|
571
|
575
|
if ($this->p == $startP) {
|
|
572
|
576
|
$readerClassName = get_class($reader);
|
|
573
|
577
|
$blockClassName = get_class($block);
|
|
574
|
|
- throw new Error("{$readerClassName} returned an " +
|
|
575
|
|
- "{$blockClassName} without incrementing MDState.p. " +
|
|
|
578
|
+ throw new Error("{$readerClassName} returned an " .
|
|
|
579
|
+ "{$blockClassName} without incrementing MDState.p. " .
|
|
576
|
580
|
"This could lead to an infinite loop.");
|
|
577
|
581
|
}
|
|
578
|
582
|
return $block;
|
|
|
@@ -596,27 +600,24 @@ class MDState {
|
|
596
|
600
|
/**
|
|
597
|
601
|
* Flushes accumulated content in `text` to `tokens`.
|
|
598
|
602
|
*/
|
|
599
|
|
- function endText() {
|
|
|
603
|
+ $endText = function() use (&$tokens, &$text) {
|
|
600
|
604
|
if (mb_strlen($text) == 0) return;
|
|
601
|
|
- $textGroups = null;
|
|
602
|
|
- if (mb_eregi(MDState::$textWhitespaceRegex, $text, $textGroups)) {
|
|
603
|
|
- if (mb_strlen($textGroups[1]) > 0) {
|
|
604
|
|
- array_push($tokens, new MDToken($textGroups[1], MDTokenType::Whitespace, $textGroups[1]));
|
|
605
|
|
- }
|
|
606
|
|
- if ($textGroups[2] && mb_strlen($textGroups[2]) > 0) {
|
|
607
|
|
- $tokens.push(new MDToken($textGroups[2], MDTokenType::Text, $textGroups[2]));
|
|
608
|
|
- }
|
|
609
|
|
- if ($textGroups[3] && mb_strlen($textGroups[3]) > 0) {
|
|
610
|
|
- $tokens.push(new MDToken($textGroups[3], MDTokenType::Whitespace, $textGroups[3]));
|
|
611
|
|
- }
|
|
|
605
|
+ $textGroups = [];
|
|
|
606
|
+ if (mb_eregi('^(\s+)(.*?)$', $text, $textGroups)) {
|
|
|
607
|
+ array_push($tokens, new MDToken($textGroups[1], MDTokenType::Whitespace, $textGroups[1]));
|
|
|
608
|
+ $text = $textGroups[2];
|
|
|
609
|
+ }
|
|
|
610
|
+ if (mb_eregi('^(.*?)(\s+)$', $text, $textGroups)) {
|
|
|
611
|
+ array_push($tokens, new MDToken($textGroups[1], MDTokenType::Text, $textGroups[1]));
|
|
|
612
|
+ array_push($tokens, new MDToken($textGroups[2], MDTokenType::Whitespace, $textGroups[2]));
|
|
612
|
613
|
} else {
|
|
613
|
614
|
array_push($tokens, new MDToken($text, MDTokenType::Text, $text));
|
|
614
|
615
|
}
|
|
615
|
616
|
$text = '';
|
|
616
|
|
- }
|
|
|
617
|
+ };
|
|
617
|
618
|
|
|
618
|
|
- for ($p = 0; $p < mb_strlen(line); $p++) {
|
|
619
|
|
- $ch = mb_substr($line, p, 1);
|
|
|
619
|
+ for ($p = 0; $p < mb_strlen($line); $p++) {
|
|
|
620
|
+ $ch = mb_substr($line, $p, 1);
|
|
620
|
621
|
$remainder = mb_substr($line, $p);
|
|
621
|
622
|
if ($expectLiteral) {
|
|
622
|
623
|
$text .= $ch;
|
|
|
@@ -631,7 +632,7 @@ class MDState {
|
|
631
|
632
|
foreach ($this->root()->readersByTokenPriority as $reader) {
|
|
632
|
633
|
$token = $reader->readToken($this, $remainder);
|
|
633
|
634
|
if ($token === null) continue;
|
|
634
|
|
- endText();
|
|
|
635
|
+ $endText();
|
|
635
|
636
|
array_push($tokens, $token);
|
|
636
|
637
|
if ($token->original == null || mb_strlen($token->original) == 0) {
|
|
637
|
638
|
$readerClassName = get_class($reader);
|
|
|
@@ -642,10 +643,10 @@ class MDState {
|
|
642
|
643
|
break;
|
|
643
|
644
|
}
|
|
644
|
645
|
if (!$found) {
|
|
645
|
|
- $text += $ch;
|
|
|
646
|
+ $text .= $ch;
|
|
646
|
647
|
}
|
|
647
|
648
|
}
|
|
648
|
|
- endText();
|
|
|
649
|
+ $endText();
|
|
649
|
650
|
return $tokens;
|
|
650
|
651
|
}
|
|
651
|
652
|
|
|
|
@@ -686,7 +687,7 @@ class MDState {
|
|
686
|
687
|
$anyChanges = false;
|
|
687
|
688
|
do {
|
|
688
|
689
|
$anyChanges = false;
|
|
689
|
|
- foreach ($this->root->readersBySubstitutePriority as $readerTuple) {
|
|
|
690
|
+ foreach ($this->root()->readersBySubstitutePriority as $readerTuple) {
|
|
690
|
691
|
/** @var int */
|
|
691
|
692
|
$pass = $readerTuple[0];
|
|
692
|
693
|
/** @var MDReader */
|
|
|
@@ -744,7 +745,7 @@ class MDState {
|
|
744
|
745
|
* Defines a URL by reference symbol.
|
|
745
|
746
|
*/
|
|
746
|
747
|
public function defineURL(string $reference, string $url, ?string $title=null) {
|
|
747
|
|
- $this->root->referenceToURL[mb_strtolower($reference)] = $url;
|
|
|
748
|
+ $this->root()->referenceToURL[mb_strtolower($reference)] = $url;
|
|
748
|
749
|
if ($title !== null) $this->root()->referenceToTitle[mb_strtolower($reference)] = $title;
|
|
749
|
750
|
}
|
|
750
|
751
|
|
|
|
@@ -1012,10 +1013,10 @@ class MDHTMLFilter {
|
|
1012
|
1013
|
case '*':
|
|
1013
|
1014
|
return true;
|
|
1014
|
1015
|
case '{classlist}':
|
|
1015
|
|
- if (mb_eregi(self::classListRegex, $value)) return true;
|
|
|
1016
|
+ if (mb_eregi(self::$classListRegex, $value)) return true;
|
|
1016
|
1017
|
break;
|
|
1017
|
1018
|
case '{int}':
|
|
1018
|
|
- if (mb_eregi(self::integerRegex, $value)) return true;
|
|
|
1019
|
+ if (mb_eregi(self::$integerRegex, $value)) return true;
|
|
1019
|
1020
|
break;
|
|
1020
|
1021
|
case '{none}':
|
|
1021
|
1022
|
if ($value === true) return true;
|
|
|
@@ -1024,7 +1025,7 @@ class MDHTMLFilter {
|
|
1024
|
1025
|
if ($this->isValidStyleDeclaration($value)) return true;
|
|
1025
|
1026
|
break;
|
|
1026
|
1027
|
case '{url}':
|
|
1027
|
|
- if (mb_eregi(self::permissiveURLRegex, $value)) return true;
|
|
|
1028
|
+ if (mb_eregi(self::$permissiveURLRegex, $value)) return true;
|
|
1028
|
1029
|
break;
|
|
1029
|
1030
|
default:
|
|
1030
|
1031
|
if ($value === $option) return true;
|
|
|
@@ -1126,7 +1127,7 @@ class MDHTMLTag {
|
|
1126
|
1127
|
if ($value === true) {
|
|
1127
|
1128
|
$html .= " {$safeName}";
|
|
1128
|
1129
|
} else {
|
|
1129
|
|
- $escapedValue = MDUtils::escapeHTML("{$value}");
|
|
|
1130
|
+ $escapedValue = htmlentities("{$value}");
|
|
1130
|
1131
|
$html .= " {$safeName}=\"{$escapedValue}\"";
|
|
1131
|
1132
|
}
|
|
1132
|
1133
|
}
|
|
|
@@ -1605,7 +1606,7 @@ class MDReader {
|
|
1605
|
1606
|
$valuesById = [];
|
|
1606
|
1607
|
|
|
1607
|
1608
|
// Build the graph and compute in-degrees
|
|
1608
|
|
- foreach ($arr as $elem) {
|
|
|
1609
|
+ foreach ($arr as $index => $elem) {
|
|
1609
|
1610
|
$id = $idFn($elem);
|
|
1610
|
1611
|
$graph[$id] = [];
|
|
1611
|
1612
|
$inDegrees[$id] = 0;
|
|
|
@@ -1621,7 +1622,7 @@ class MDReader {
|
|
1621
|
1622
|
$idB = $idFn($elemB);
|
|
1622
|
1623
|
$comparisonResult = $compareFn($elemA, $elemB);
|
|
1623
|
1624
|
if ($comparisonResult < 0) {
|
|
1624
|
|
- array_push($graph[$idA], push($idB));
|
|
|
1625
|
+ array_push($graph[$idA], $idB);
|
|
1625
|
1626
|
$inDegrees[$idB]++;
|
|
1626
|
1627
|
} elseif ($comparisonResult > 0) {
|
|
1627
|
1628
|
array_push($graph[$idB], $idA);
|
|
|
@@ -1632,8 +1633,8 @@ class MDReader {
|
|
1632
|
1633
|
|
|
1633
|
1634
|
// Initialize the queue with zero-inDegree nodes
|
|
1634
|
1635
|
$queue = [];
|
|
1635
|
|
- foreach ($inDegrees as $elemId) {
|
|
1636
|
|
- if ($inDegrees[$elemId] === 0) {
|
|
|
1636
|
+ foreach ($inDegrees as $elemId => $degree) {
|
|
|
1637
|
+ if ($degree === 0) {
|
|
1637
|
1638
|
array_push($queue, $elemId);
|
|
1638
|
1639
|
}
|
|
1639
|
1640
|
}
|
|
|
@@ -1666,7 +1667,7 @@ class MDReader {
|
|
1666
|
1667
|
* @param MDReader[] $readers
|
|
1667
|
1668
|
* @return MDReader[] sorted readers
|
|
1668
|
1669
|
*/
|
|
1669
|
|
- public static function sortReaderForBlocks(array &$readers) {
|
|
|
1670
|
+ public static function sortReaderForBlocks(array &$readers): array {
|
|
1670
|
1671
|
$sorted = $readers;
|
|
1671
|
1672
|
return self::kahnTopologicalSort($sorted, function(MDReader $a, MDReader $b): int {
|
|
1672
|
1673
|
return $a->compareBlockOrdering($b);
|
|
|
@@ -1679,7 +1680,7 @@ class MDReader {
|
|
1679
|
1680
|
* @param MDReader[] $readers
|
|
1680
|
1681
|
* @return MDReader[] sorted readers
|
|
1681
|
1682
|
*/
|
|
1682
|
|
- public static function sortReadersForTokenizing(array $readers): array {
|
|
|
1683
|
+ public static function sortReadersForTokenizing(array &$readers): array {
|
|
1683
|
1684
|
$sorted = $readers;
|
|
1684
|
1685
|
return self::kahnTopologicalSort($sorted, function(MDReader $a, MDReader $b): int {
|
|
1685
|
1686
|
return $a->compareTokenizeOrdering($b);
|
|
|
@@ -1698,20 +1699,20 @@ class MDReader {
|
|
1698
|
1699
|
* @return MDReader[] sorted array of tuples with the pass number and
|
|
1699
|
1700
|
* reader instance in each
|
|
1700
|
1701
|
*/
|
|
1701
|
|
- public static function sortReadersForSubstitution(array $readers): array {
|
|
|
1702
|
+ public static function sortReadersForSubstitution(array &$readers): array {
|
|
1702
|
1703
|
$tuples = [];
|
|
1703
|
1704
|
$maxPass = 1;
|
|
1704
|
1705
|
foreach ($readers as $reader) {
|
|
1705
|
1706
|
$passCount = $reader->substitutionPassCount();
|
|
1706
|
1707
|
for ($pass = 1; $pass <= $passCount; $pass++) {
|
|
1707
|
|
- array_push($tuples, [ $pass, $reader ]);
|
|
|
1708
|
+ array_push($tuples, [[ $pass, $reader ]]);
|
|
1708
|
1709
|
}
|
|
1709
|
1710
|
$maxPass = max($maxPass, $pass);
|
|
1710
|
1711
|
}
|
|
1711
|
1712
|
$result = [];
|
|
1712
|
1713
|
for ($pass = 1; $pass <= $maxPass; $pass++) {
|
|
1713
|
|
- $readersThisPass = array_filter(tuples, fn($tup) => $tup[0] == $pass);
|
|
1714
|
|
- $passResult = self::kahnTopologicalSort($readersThisPass, function(MDReader $a, MDReader $b): int {
|
|
|
1714
|
+ $readersThisPass = array_filter($tuples, fn($tup) => $tup[0] == $pass);
|
|
|
1715
|
+ $passResult = self::kahnTopologicalSort($readersThisPass, function(array $a, array $b) use ($pass): int {
|
|
1715
|
1716
|
$aReader = $a[1];
|
|
1716
|
1717
|
$bReader = $b[1];
|
|
1717
|
1718
|
return $aReader->compareSubstituteOrdering($bReader, $pass);
|
|
|
@@ -1733,7 +1734,7 @@ class MDUnderlinedHeadingReader extends MDReader {
|
|
1733
|
1734
|
if (!$state->hasLines(2)) return null;
|
|
1734
|
1735
|
$modifier;
|
|
1735
|
1736
|
$contentLine = trim($state->lines[$p++]);
|
|
1736
|
|
- [$contentLine, $modifier] = MDTagModifier.fromLine(contentLine, state);
|
|
|
1737
|
+ [$contentLine, $modifier] = MDTagModifier::fromLine($contentLine, $state);
|
|
1737
|
1738
|
$underLine = trim($state->lines[$p++]);
|
|
1738
|
1739
|
if ($contentLine == '') return null;
|
|
1739
|
1740
|
if (mb_eregi('^=+$', $underLine)) {
|
|
|
@@ -1766,7 +1767,7 @@ class MDHashHeadingReader extends MDReader {
|
|
1766
|
1767
|
$line = $state->lines[$p++];
|
|
1767
|
1768
|
$modifier;
|
|
1768
|
1769
|
[$line, $modifier] = MDTagModifier::fromLine($line, $state);
|
|
1769
|
|
- if (!mb_eregi(self::hashHeadingRegex, $line, $groups)) return null;
|
|
|
1770
|
+ if (!mb_eregi(self::$hashHeadingRegex, $line, $groups)) return null;
|
|
1770
|
1771
|
$state->p = $p;
|
|
1771
|
1772
|
$level = mb_strlen($groups[1]);
|
|
1772
|
1773
|
$content = $groups[2];
|
|
|
@@ -1790,7 +1791,7 @@ class MDSubtextReader extends MDReader {
|
|
1790
|
1791
|
$line = $state->lines[$p++];
|
|
1791
|
1792
|
$modifier;
|
|
1792
|
1793
|
[$line, $modifier] = MDTagModifier::fromLine($line, $state);
|
|
1793
|
|
- if (!mb_eregi(self::subtextRegex, $line, $groups)) return null;
|
|
|
1794
|
+ if (!mb_eregi(self::$subtextRegex, $line, $groups)) return null;
|
|
1794
|
1795
|
$state->p = $p;
|
|
1795
|
1796
|
$content = $groups[1];
|
|
1796
|
1797
|
$block = new MDSubtextNode($state->inlineMarkdownToNodes($content));
|
|
|
@@ -1882,7 +1883,7 @@ class _MDListReader extends MDReader {
|
|
1882
|
1883
|
// Multiline content with no blank lines. Search for new block
|
|
1883
|
1884
|
// boundaries without the benefit of a blank line to demarcate it.
|
|
1884
|
1885
|
for ($p = 1; $p < sizeof($itemLines); $p++) {
|
|
1885
|
|
- $line = $itemLines[p];
|
|
|
1886
|
+ $line = $itemLines[$p];
|
|
1886
|
1887
|
if (mb_eregi('^(?:\\*|\\-|\\+|\\d+\\.)\\s+', $line)) {
|
|
1887
|
1888
|
// Nested list found
|
|
1888
|
1889
|
$firstBlock = $state->inlineMarkdownToNode(implode("\n", array_slice($itemLines, 0, $p)));
|
|
|
@@ -1901,7 +1902,8 @@ class _MDListReader extends MDReader {
|
|
1901
|
1902
|
}
|
|
1902
|
1903
|
|
|
1903
|
1904
|
public function readBlock(MDState $state): ?MDBlockNode {
|
|
1904
|
|
- throw new Error(`Abstract readBlock must be overridden in ${this.constructor.name}`);
|
|
|
1905
|
+ $className = get_class($this);
|
|
|
1906
|
+ throw new Error("Abstract readBlock must be overridden in {$className}");
|
|
1905
|
1907
|
}
|
|
1906
|
1908
|
}
|
|
1907
|
1909
|
|
|
|
@@ -1955,7 +1957,7 @@ class MDOrderedListReader extends _MDListReader {
|
|
1955
|
1957
|
$item = $this->readOrderedListItem($state);
|
|
1956
|
1958
|
if ($item) array_push($items, $item);
|
|
1957
|
1959
|
} while ($item);
|
|
1958
|
|
- if (sizeof($items)) return null;
|
|
|
1960
|
+ if (sizeof($items) == 0) return null;
|
|
1959
|
1961
|
return new MDOrderedListNode($items, $items[0]->ordinal);
|
|
1960
|
1962
|
}
|
|
1961
|
1963
|
}
|
|
|
@@ -1973,7 +1975,7 @@ class MDFencedCodeBlockReader extends MDReader {
|
|
1973
|
1975
|
if (!$state->hasLines(2)) return null;
|
|
1974
|
1976
|
$p = $state->p;
|
|
1975
|
1977
|
$openFenceLine = $state->lines[$p++];
|
|
1976
|
|
- [$openFenceLine, $modifier] = MDTagModifier->fromLine($openFenceLine, $state);
|
|
|
1978
|
+ [$openFenceLine, $modifier] = MDTagModifier::fromLine($openFenceLine, $state);
|
|
1977
|
1979
|
if (!mb_eregi('```\s*([a-z0-9]*)\s*$', $openFenceLine, $groups)) return null;
|
|
1978
|
1980
|
$language = mb_strlen($groups[1]) > 0 ? $groups[1] : null;
|
|
1979
|
1981
|
$codeLines = [];
|
|
|
@@ -2023,7 +2025,7 @@ class MDHorizontalRuleReader extends MDReader {
|
|
2023
|
2025
|
$p = $state->p;
|
|
2024
|
2026
|
$line = $state->lines[$p++];
|
|
2025
|
2027
|
[$line, $modifier] = MDTagModifier::fromLine($line, $state);
|
|
2026
|
|
- if (mb_eregi(self::horizontalRuleRegex, $line)) {
|
|
|
2028
|
+ if (mb_eregi(self::$horizontalRuleRegex, $line)) {
|
|
2027
|
2029
|
$state->p = $p;
|
|
2028
|
2030
|
$block = new MDHorizontalRuleNode();
|
|
2029
|
2031
|
if ($modifier) $modifier->applyTo($block);
|
|
|
@@ -2169,34 +2171,34 @@ class MDFootnoteReader extends MDReader {
|
|
2169
|
2171
|
* @param MDNode[] $footnote
|
|
2170
|
2172
|
*/
|
|
2171
|
2173
|
private function defineFootnote(MDState $state, string $symbol, array $footnote) {
|
|
2172
|
|
- $footnotes = $state->root()['footnotes'] ?? [];
|
|
|
2174
|
+ $footnotes = $state->root()->userInfo['footnotes'] ?? [];
|
|
2173
|
2175
|
$footnotes[$symbol] = $footnote;
|
|
2174
|
|
- $state->root()['footnotes'] = $footnotes;
|
|
|
2176
|
+ $state->root()->userInfo['footnotes'] = $footnotes;
|
|
2175
|
2177
|
}
|
|
2176
|
2178
|
|
|
2177
|
2179
|
private function registerUniqueInstance(MDState $state, string $symbol, int $unique) {
|
|
2178
|
|
- $footnoteInstances = $state->root()['footnoteInstances'];
|
|
|
2180
|
+ $footnoteInstances = $state->root()->userInfo['footnoteInstances'];
|
|
2179
|
2181
|
$instances = $footnoteInstances[$symbol] ?? [];
|
|
2180
|
2182
|
array_push($instances, $unique);
|
|
2181
|
2183
|
$footnoteInstances[$symbol] = $instances;
|
|
2182
|
2184
|
}
|
|
2183
|
2185
|
|
|
2184
|
2186
|
private function idForFootnoteSymbol(MDState $state, string $symbol): int {
|
|
2185
|
|
- $footnoteIds = $state->root()['footnoteIds'];
|
|
|
2187
|
+ $footnoteIds = $state->root()->userInfo['footnoteIds'];
|
|
2186
|
2188
|
$existing = $footnoteIds[$symbol];
|
|
2187
|
2189
|
if ($existing) return $existing;
|
|
2188
|
|
- $nextFootnoteId = $state->root()['nextFootnoteId'];
|
|
|
2190
|
+ $nextFootnoteId = $state->root()->userInfo['nextFootnoteId'];
|
|
2189
|
2191
|
$id = $nextFootnoteId++;
|
|
2190
|
2192
|
$footnoteIds[$symbol] = $id;
|
|
2191
|
|
- $state->root()['nextFootnoteId'] = $nextFootnoteId;
|
|
|
2193
|
+ $state->root()->userInfo['nextFootnoteId'] = $nextFootnoteId;
|
|
2192
|
2194
|
return $id;
|
|
2193
|
2195
|
}
|
|
2194
|
2196
|
|
|
2195
|
2197
|
public function preProcess(MDState $state) {
|
|
2196
|
|
- $state->root()['footnoteInstances'] = [];
|
|
2197
|
|
- $state->root()['footnotes'] = [];
|
|
2198
|
|
- $state->root()['footnoteIds'] = [];
|
|
2199
|
|
- $state->root()['nextFootnoteId'] = 1;
|
|
|
2198
|
+ $state->root()->userInfo['footnoteInstances'] = [];
|
|
|
2199
|
+ $state->root()->userInfo['footnotes'] = [];
|
|
|
2200
|
+ $state->root()->userInfo['footnoteIds'] = [];
|
|
|
2201
|
+ $state->root()->userInfo['nextFootnoteId'] = 1;
|
|
2200
|
2202
|
}
|
|
2201
|
2203
|
|
|
2202
|
2204
|
public function readBlock(MDState $state): ?MDBlockNode {
|
|
|
@@ -2207,7 +2209,7 @@ class MDFootnoteReader extends MDReader {
|
|
2207
|
2209
|
while ($state->hasLines(1, $p)) {
|
|
2208
|
2210
|
$line = $state->lines[$p++];
|
|
2209
|
2211
|
if (mb_eregi('^\\s+', $line)) {
|
|
2210
|
|
- $def += "\n" . $line;
|
|
|
2212
|
+ $def .= "\n" . $line;
|
|
2211
|
2213
|
} else {
|
|
2212
|
2214
|
$p--;
|
|
2213
|
2215
|
break;
|
|
|
@@ -2224,14 +2226,14 @@ class MDFootnoteReader extends MDReader {
|
|
2224
|
2226
|
if (mb_eregi(self::$footnoteWithTitleRegex, $line, $groups)) {
|
|
2225
|
2227
|
return new MDToken($groups[0], MDTokenType::Footnote, $groups[1], $groups[2]);
|
|
2226
|
2228
|
}
|
|
2227
|
|
- if (mb_eregi(MDFootnoteReader::footnoteRegex, $line, $groups)) {
|
|
|
2229
|
+ if (mb_eregi(self::$footnoteRegex, $line, $groups)) {
|
|
2228
|
2230
|
return new MDToken($groups[0], MDTokenType::Footnote, $groups[1]);
|
|
2229
|
2231
|
}
|
|
2230
|
2232
|
return null;
|
|
2231
|
2233
|
}
|
|
2232
|
2234
|
|
|
2233
|
2235
|
public function substituteTokens(MDState $state, int $pass, array &$tokens): bool {
|
|
2234
|
|
- if ($match = MDToken::findFirstTokens($tokens, [ MDTokenType::Footnote ])) {
|
|
|
2236
|
+ if ($match = self::findFirstTokens($tokens, [ MDTokenType::Footnote ])) {
|
|
2235
|
2237
|
$symbol = $match->tokens[0]->content;
|
|
2236
|
2238
|
array_splice($tokens, $match->index, 1, new MDFootnoteNode($symbol));
|
|
2237
|
2239
|
return true;
|
|
|
@@ -2254,7 +2256,7 @@ class MDFootnoteReader extends MDReader {
|
|
2254
|
2256
|
$this->$registerUniqueInstance($state, $node->symbol, $node->occurrenceId);
|
|
2255
|
2257
|
});
|
|
2256
|
2258
|
}
|
|
2257
|
|
- if (sizeof($state->footnotes) == 0) return;
|
|
|
2259
|
+ if (sizeof($state->userInfo['footnotes']) == 0) return;
|
|
2258
|
2260
|
array_push($blocks, new MDFootnoteListNode());
|
|
2259
|
2261
|
}
|
|
2260
|
2262
|
|
|
|
@@ -2294,8 +2296,8 @@ class MDAbbreviationReader extends MDReader {
|
|
2294
|
2296
|
}
|
|
2295
|
2297
|
|
|
2296
|
2298
|
public function preProcess(MDState $state) {
|
|
2297
|
|
- $state->root()['abbreviations'] = [];
|
|
2298
|
|
- $state->root()['abbreviationRegexes'] = [];
|
|
|
2299
|
+ $state->root()->userInfo['abbreviations'] = [];
|
|
|
2300
|
+ $state->root()->userInfo['abbreviationRegexes'] = [];
|
|
2299
|
2301
|
}
|
|
2300
|
2302
|
|
|
2301
|
2303
|
public function readBlock(MDState $state): ?MDBlockNode {
|
|
|
@@ -2314,14 +2316,14 @@ class MDAbbreviationReader extends MDReader {
|
|
2314
|
2316
|
* @param MDNode[] $blocks
|
|
2315
|
2317
|
*/
|
|
2316
|
2318
|
public function postProcess(MDState $state, array &$blocks) {
|
|
2317
|
|
- $abbreviations = $state->root()['abbreviations'];
|
|
2318
|
|
- $regexes = $state->root()['abbreviationRegexes'];
|
|
2319
|
|
- MDNode::replaceNodes($state, $blocks, function($original) {
|
|
|
2319
|
+ $abbreviations = $state->root()->userInfo['abbreviations'];
|
|
|
2320
|
+ $regexes = $state->root()->userInfo['abbreviationRegexes'];
|
|
|
2321
|
+ MDNode::replaceNodes($state, $blocks, function($original) use ($abbreviations, $regexes) {
|
|
2320
|
2322
|
if (!($original instanceof MDTextNode)) return null;
|
|
2321
|
2323
|
$changed = false;
|
|
2322
|
2324
|
$elems = [ $original->text ]; // mix of strings and MDNodes
|
|
2323
|
2325
|
for ($i = 0; $i < sizeof($elems); $i++) {
|
|
2324
|
|
- $text = $elems[i];
|
|
|
2326
|
+ $text = $elems[$i];
|
|
2325
|
2327
|
if (!is_string($text)) continue;
|
|
2326
|
2328
|
foreach ($abbreviations as $abbreviation) {
|
|
2327
|
2329
|
$index = strpos($text, $abbreviation);
|
|
|
@@ -2628,7 +2630,7 @@ class MDSuperscriptReader extends MDSimplePairInlineReader {
|
|
2628
|
2630
|
class MDLinkReader extends MDReader {
|
|
2629
|
2631
|
public function readToken(MDState $state, string $line): ?MDToken {
|
|
2630
|
2632
|
$simpleEmailRegex = "^<(" . MDUtils::$baseEmailRegex . ")>";
|
|
2631
|
|
- $simpleURLRegex = "^<(" . MDUtils::$baseURLRegex + ")>";
|
|
|
2633
|
+ $simpleURLRegex = "^<(" . MDUtils::$baseURLRegex . ")>";
|
|
2632
|
2634
|
if ($groups = MDToken::tokenizeLabel($line)) {
|
|
2633
|
2635
|
return new MDToken($groups[0], MDTokenType::Label, $groups[1]);
|
|
2634
|
2636
|
}
|
|
|
@@ -2901,7 +2903,7 @@ class MDNode {
|
|
2901
|
2903
|
}
|
|
2902
|
2904
|
}
|
|
2903
|
2905
|
$this->children = $children;
|
|
2904
|
|
- } else if ($children instanceof MDNode) {
|
|
|
2906
|
+ } elseif ($children instanceof MDNode) {
|
|
2905
|
2907
|
$this->children = [ $children ];
|
|
2906
|
2908
|
} else {
|
|
2907
|
2909
|
$thisClassName = get_class($this);
|
|
|
@@ -2971,7 +2973,7 @@ class MDNode {
|
|
2971
|
2973
|
$html .= " class=\"{$classList}\"";
|
|
2972
|
2974
|
}
|
|
2973
|
2975
|
if ($this->cssId !== null && mb_strlen($this->cssId) > 0) {
|
|
2974
|
|
- $html += " id=\"{$this->cssId}\"";
|
|
|
2976
|
+ $html .= " id=\"{$this->cssId}\"";
|
|
2975
|
2977
|
}
|
|
2976
|
2978
|
$styles = [];
|
|
2977
|
2979
|
foreach ($this->cssStyles as $key => $value) {
|
|
|
@@ -3041,7 +3043,7 @@ class MDNode {
|
|
3041
|
3043
|
* @return string HTML string
|
|
3042
|
3044
|
*/
|
|
3043
|
3045
|
public static function arrayToHTML(array $nodes, MDState $state): string {
|
|
3044
|
|
- return implode('', array_map(function($node) {
|
|
|
3046
|
+ return implode('', array_map(function($node) use ($state) {
|
|
3045
|
3047
|
return $node->toHTML($state) . ($node instanceof MDBlockNode ? "\n" : '');
|
|
3046
|
3048
|
}, $nodes));
|
|
3047
|
3049
|
}
|
|
|
@@ -3218,8 +3220,8 @@ class MDCodeBlockNode extends MDBlockNode {
|
|
3218
|
3220
|
|
|
3219
|
3221
|
public function toHTML(MDState $state): string {
|
|
3220
|
3222
|
$languageModifier = ($this->language !== null) ? " class=\"language-{$this->language}\"" : '';
|
|
3221
|
|
- return "<pre" . $this->htmlAttributes() . "><code{$languageModifier}>" +
|
|
3222
|
|
- MDUtils.escapeHTML($this->text) . "</code></pre>\n";
|
|
|
3223
|
+ return "<pre" . $this->htmlAttributes() . "><code{$languageModifier}>" .
|
|
|
3224
|
+ htmlentities($this->text) . "</code></pre>\n";
|
|
3223
|
3225
|
}
|
|
3224
|
3226
|
}
|
|
3225
|
3227
|
|
|
|
@@ -3282,7 +3284,7 @@ class MDTableNode extends MDBlockNode {
|
|
3282
|
3284
|
$html .= $this->headerRow->toHTML($state) . "\n";
|
|
3283
|
3285
|
$html .= "</thead>\n";
|
|
3284
|
3286
|
$html .= "<tbody>\n";
|
|
3285
|
|
- $html .= MDNode::toHTML($this->bodyRows, $state) + "\n";
|
|
|
3287
|
+ $html .= MDNode::toHTML($this->bodyRows, $state) . "\n";
|
|
3286
|
3288
|
$html .= "</tbody>\n";
|
|
3287
|
3289
|
$html .= "</table>\n";
|
|
3288
|
3290
|
return $html;
|
|
|
@@ -3353,13 +3355,13 @@ class MDDefinitionListDefinitionNode extends MDBlockNode {
|
|
3353
|
3355
|
*/
|
|
3354
|
3356
|
class MDFootnoteListNode extends MDBlockNode {
|
|
3355
|
3357
|
private function footnoteId(MDState $state, string $symbol): int {
|
|
3356
|
|
- $lookup = $state->root()['footnoteIds'];
|
|
|
3358
|
+ $lookup = $state->root()->userInfo['footnoteIds'];
|
|
3357
|
3359
|
if (!$lookup) return null;
|
|
3358
|
3360
|
return $lookup[$symbol] ?? null;
|
|
3359
|
3361
|
}
|
|
3360
|
3362
|
|
|
3361
|
3363
|
public function toHTML(MDState $state): string {
|
|
3362
|
|
- $footnotes = $state->footnotes;
|
|
|
3364
|
+ $footnotes = $state->userInfo['footnotes'];
|
|
3363
|
3365
|
$symbolOrder = array_keys($footnotes);
|
|
3364
|
3366
|
if (sizeof($footnotes) == 0) return '';
|
|
3365
|
3367
|
$footnoteUniques = $state->root()->footnoteInstances;
|
|
|
@@ -3375,7 +3377,7 @@ class MDFootnoteListNode extends MDBlockNode {
|
|
3375
|
3377
|
$uniques = $footnoteUniques[$symbol];
|
|
3376
|
3378
|
if ($uniques) {
|
|
3377
|
3379
|
foreach ($uniques as $unique) {
|
|
3378
|
|
- $html .= " <a href=\"#{$state->root->elementIdPrefix}footnoteref_{$unique}\" class=\"footnote-backref\">↩︎</a>";
|
|
|
3380
|
+ $html .= " <a href=\"#{$state->root()->elementIdPrefix}footnoteref_{$unique}\" class=\"footnote-backref\">↩︎</a>";
|
|
3379
|
3381
|
}
|
|
3380
|
3382
|
}
|
|
3381
|
3383
|
$html .= "</li>\n";
|
|
|
@@ -3386,7 +3388,7 @@ class MDFootnoteListNode extends MDBlockNode {
|
|
3386
|
3388
|
}
|
|
3387
|
3389
|
|
|
3388
|
3390
|
public function toPlaintext(MDState $state): string {
|
|
3389
|
|
- $footnotes = $state->footnotes;
|
|
|
3391
|
+ $footnotes = $state->userInfo['footnotes'];
|
|
3390
|
3392
|
$symbolOrder = array_keys($footnotes);
|
|
3391
|
3393
|
if (sizeof($footnotes) == 0) return '';
|
|
3392
|
3394
|
$text = '';
|
|
|
@@ -3412,6 +3414,7 @@ class MDTextNode extends MDInlineNode {
|
|
3412
|
3414
|
|
|
3413
|
3415
|
public function __construct(string $text) {
|
|
3414
|
3416
|
parent::__construct([]);
|
|
|
3417
|
+ if (!is_string($text) || mb_strlen($text) == 0) throw new Error("Meh!");
|
|
3415
|
3418
|
$this->text = $text;
|
|
3416
|
3419
|
}
|
|
3417
|
3420
|
|
|
|
@@ -3806,11 +3809,11 @@ class Markdown {
|
|
3806
|
3809
|
*
|
|
3807
|
3810
|
* @param MDReader[] $readers
|
|
3808
|
3811
|
*/
|
|
3809
|
|
- public function __construct(?array $readers) {
|
|
|
3812
|
+ public function __construct(?array $readers=null) {
|
|
3810
|
3813
|
$this->readers = $readers ?? self::allReaders();
|
|
3811
|
|
- $this->readersByBlockPriority = MDReader::sortReaderForBlocks($readers);
|
|
3812
|
|
- $this->readersByTokenPriority = MDReader::sortReadersForTokenizing($readers);
|
|
3813
|
|
- $this->readersBySubstitutePriority = MDReader::sortReadersForSubstitution($readers);
|
|
|
3814
|
+ $this->readersByBlockPriority = MDReader::sortReaderForBlocks($this->readers);
|
|
|
3815
|
+ $this->readersByTokenPriority = MDReader::sortReadersForTokenizing($this->readers);
|
|
|
3816
|
+ $this->readersBySubstitutePriority = MDReader::sortReadersForSubstitution($this->readers);
|
|
3814
|
3817
|
$this->tagFilter = new MDHTMLFilter();
|
|
3815
|
3818
|
}
|
|
3816
|
3819
|
|
|
|
@@ -3844,7 +3847,7 @@ class Markdown {
|
|
3844
|
3847
|
$state->readersBySubstitutePriority = $this->readersBySubstitutePriority;
|
|
3845
|
3848
|
$state->tagFilter = $this->tagFilter;
|
|
3846
|
3849
|
$state->elementIdPrefix = $elementIdPrefix;
|
|
3847
|
|
- foreach ($this.readers as $reader) {
|
|
|
3850
|
+ foreach ($this->readers as $reader) {
|
|
3848
|
3851
|
$reader->preProcess($state);
|
|
3849
|
3852
|
}
|
|
3850
|
3853
|
$nodes = $state->readBlocks();
|