|
|
@@ -90,6 +90,11 @@ class MDUtils {
|
|
90
|
90
|
return false;
|
|
91
|
91
|
}
|
|
92
|
92
|
|
|
|
93
|
+ public static function typename($value): string {
|
|
|
94
|
+ $tn = gettype($value);
|
|
|
95
|
+ return ($tn === 'object') ? get_class($value) : $tn;
|
|
|
96
|
+ }
|
|
|
97
|
+
|
|
93
|
98
|
public static function equalAssocArrays(array &$a, array &$b) {
|
|
94
|
99
|
return empty(array_diff_assoc($a, $b));
|
|
95
|
100
|
}
|
|
|
@@ -217,8 +222,8 @@ class MDToken {
|
|
217
|
222
|
}
|
|
218
|
223
|
|
|
219
|
224
|
public function __toString(): string {
|
|
220
|
|
- $classname = get_class($this);
|
|
221
|
|
- return "({$classname} type={$this->type} content={$this->content})";
|
|
|
225
|
+ $classname = MDUtils::typename($this);
|
|
|
226
|
+ return "<{$classname} type={$this->type->name} content=\"{$this->content}\">";
|
|
222
|
227
|
}
|
|
223
|
228
|
|
|
224
|
229
|
/**
|
|
|
@@ -321,12 +326,15 @@ class MDToken {
|
|
321
|
326
|
* @return ?MDTokenMatch match object, or `null` if not found
|
|
322
|
327
|
*/
|
|
323
|
328
|
public static function findFirstTokens(array $tokensToSearch, array $pattern, int $startIndex=0): ?MDTokenMatch {
|
|
|
329
|
+ if (sizeof($pattern) == 0) {
|
|
|
330
|
+ throw new Error("pattern empty");
|
|
|
331
|
+ }
|
|
324
|
332
|
$matched = [];
|
|
325
|
333
|
for ($t = $startIndex; $t < sizeof($tokensToSearch); $t++) {
|
|
326
|
334
|
$matchedAll = true;
|
|
327
|
335
|
$matched = [];
|
|
328
|
336
|
$patternOffset = 0;
|
|
329
|
|
- for ($p = 0; $p < mb_strlen($pattern); $p++) {
|
|
|
337
|
+ for ($p = 0; $p < sizeof($pattern); $p++) {
|
|
330
|
338
|
$t0 = $t + $p + $patternOffset;
|
|
331
|
339
|
if ($t0 >= sizeof($tokensToSearch)) return null;
|
|
332
|
340
|
$token = $tokensToSearch[$t0];
|
|
|
@@ -383,11 +391,11 @@ class MDToken {
|
|
383
|
391
|
array $startPattern, array $endPattern, ?callable $contentValidator=null,
|
|
384
|
392
|
int $startIndex=0): ?MDPairedTokenMatch {
|
|
385
|
393
|
for ($s = $startIndex; $s < sizeof($tokensToSearch); $s++) {
|
|
386
|
|
- $startMatch = findFirstTokens($tokensToSearch, $startPattern, $s);
|
|
|
394
|
+ $startMatch = self::findFirstTokens($tokensToSearch, $startPattern, $s);
|
|
387
|
395
|
if ($startMatch === null) return null;
|
|
388
|
396
|
$endStart = $startMatch->index + sizeof($startMatch->tokens);
|
|
389
|
397
|
while ($endStart < sizeof($tokensToSearch)) {
|
|
390
|
|
- $endMatch = findFirstTokens($tokensToSearch, $endPattern, $endStart);
|
|
|
398
|
+ $endMatch = self::findFirstTokens($tokensToSearch, $endPattern, $endStart);
|
|
391
|
399
|
if ($endMatch === null) break;
|
|
392
|
400
|
$contentStart = $startMatch->index + sizeof($startMatch->tokens);
|
|
393
|
401
|
$contentLength = $endMatch->index - $contentStart;
|
|
|
@@ -506,6 +514,7 @@ class MDState {
|
|
506
|
514
|
*/
|
|
507
|
515
|
public function __construct(array $lines) {
|
|
508
|
516
|
$this->lines = $lines;
|
|
|
517
|
+ $this->startTime = microtime(true);
|
|
509
|
518
|
}
|
|
510
|
519
|
|
|
511
|
520
|
/**
|
|
|
@@ -573,8 +582,8 @@ class MDState {
|
|
573
|
582
|
$block = $reader->readBlock($this);
|
|
574
|
583
|
if ($block) {
|
|
575
|
584
|
if ($this->p == $startP) {
|
|
576
|
|
- $readerClassName = get_class($reader);
|
|
577
|
|
- $blockClassName = get_class($block);
|
|
|
585
|
+ $readerClassName = MDUtils::typename($reader);
|
|
|
586
|
+ $blockClassName = MDUtils::typename($block);
|
|
578
|
587
|
throw new Error("{$readerClassName} returned an " .
|
|
579
|
588
|
"{$blockClassName} without incrementing MDState.p. " .
|
|
580
|
589
|
"This could lead to an infinite loop.");
|
|
|
@@ -598,16 +607,16 @@ class MDState {
|
|
598
|
607
|
$expectLiteral = false;
|
|
599
|
608
|
|
|
600
|
609
|
/**
|
|
601
|
|
- * Flushes accumulated content in `text` to `tokens`.
|
|
|
610
|
+ * Flushes accumulated content in `$text` to `$tokens`.
|
|
602
|
611
|
*/
|
|
603
|
612
|
$endText = function() use (&$tokens, &$text) {
|
|
604
|
613
|
if (mb_strlen($text) == 0) return;
|
|
605
|
614
|
$textGroups = [];
|
|
606
|
|
- if (mb_eregi('^(\s+)(.*?)$', $text, $textGroups)) {
|
|
|
615
|
+ if (mb_eregi('^(\\s+)(.*?)$', $text, $textGroups)) {
|
|
607
|
616
|
array_push($tokens, new MDToken($textGroups[1], MDTokenType::Whitespace, $textGroups[1]));
|
|
608
|
|
- $text = $textGroups[2];
|
|
|
617
|
+ $text = is_string($textGroups[2]) ? $textGroups[2] : '';
|
|
609
|
618
|
}
|
|
610
|
|
- if (mb_eregi('^(.*?)(\s+)$', $text, $textGroups)) {
|
|
|
619
|
+ if (mb_eregi('^(.*?)(\\s+)$', $text, $textGroups)) {
|
|
611
|
620
|
array_push($tokens, new MDToken($textGroups[1], MDTokenType::Text, $textGroups[1]));
|
|
612
|
621
|
array_push($tokens, new MDToken($textGroups[2], MDTokenType::Whitespace, $textGroups[2]));
|
|
613
|
622
|
} else {
|
|
|
@@ -635,7 +644,7 @@ class MDState {
|
|
635
|
644
|
$endText();
|
|
636
|
645
|
array_push($tokens, $token);
|
|
637
|
646
|
if ($token->original == null || mb_strlen($token->original) == 0) {
|
|
638
|
|
- $readerClassName = get_class($reader);
|
|
|
647
|
+ $readerClassName = MDUtils::typename($reader);
|
|
639
|
648
|
throw new Error(`{$readerClassName} returned a token with an empty .original. This would cause an infinite loop.`);
|
|
640
|
649
|
}
|
|
641
|
650
|
$p += mb_strlen($token->original) - 1;
|
|
|
@@ -703,7 +712,7 @@ class MDState {
|
|
703
|
712
|
// CSS modifiers.
|
|
704
|
713
|
$lastNode = null;
|
|
705
|
714
|
$me = $this;
|
|
706
|
|
- $nodes = array_map(function($node) use ($lastNode, $me) {
|
|
|
715
|
+ $nodes = array_map(function($node) use (&$lastNode, $me, $nodes) {
|
|
707
|
716
|
if ($node instanceof MDToken) {
|
|
708
|
717
|
/** @var MDToken */
|
|
709
|
718
|
$token = $node;
|
|
|
@@ -719,7 +728,7 @@ class MDState {
|
|
719
|
728
|
$lastNode = ($node instanceof MDTextNode) ? null : $node;
|
|
720
|
729
|
return $node;
|
|
721
|
730
|
} else {
|
|
722
|
|
- $nodeClassName = get_class($node);
|
|
|
731
|
+ $nodeClassName = MDUtils::typename($node);
|
|
723
|
732
|
throw new Error("Unexpected node type {$nodeClassName}");
|
|
724
|
733
|
}
|
|
725
|
734
|
}, $nodes);
|
|
|
@@ -727,6 +736,19 @@ class MDState {
|
|
727
|
736
|
return $nodes;
|
|
728
|
737
|
}
|
|
729
|
738
|
|
|
|
739
|
+ public $startTime;
|
|
|
740
|
+
|
|
|
741
|
+ /**
|
|
|
742
|
+ * Checks if parsing has taken an excessive length of time. Because I'm not
|
|
|
743
|
+ * fully confident in my loops yet. :)
|
|
|
744
|
+ */
|
|
|
745
|
+ public function checkExecutionTime(float $maxSeconds=1.0) {
|
|
|
746
|
+ $elapsed = microtime(true) - $this->root()->startTime;
|
|
|
747
|
+ if ($elapsed > $maxSeconds) {
|
|
|
748
|
+ throw new Error("Markdown parsing taking too long. Infinite loop?");
|
|
|
749
|
+ }
|
|
|
750
|
+ }
|
|
|
751
|
+
|
|
730
|
752
|
/**
|
|
731
|
753
|
* Mapping of reference symbols to URLs. Used by `MDReferencedLinkReader`
|
|
732
|
754
|
* and `MDReferencedImageReader`.
|
|
|
@@ -1671,7 +1693,7 @@ class MDReader {
|
|
1671
|
1693
|
$sorted = $readers;
|
|
1672
|
1694
|
return self::kahnTopologicalSort($sorted, function(MDReader $a, MDReader $b): int {
|
|
1673
|
1695
|
return $a->compareBlockOrdering($b);
|
|
1674
|
|
- }, fn($elem) => get_class($elem));
|
|
|
1696
|
+ }, fn($elem) => MDUtils::typename($elem));
|
|
1675
|
1697
|
}
|
|
1676
|
1698
|
|
|
1677
|
1699
|
/**
|
|
|
@@ -1684,7 +1706,7 @@ class MDReader {
|
|
1684
|
1706
|
$sorted = $readers;
|
|
1685
|
1707
|
return self::kahnTopologicalSort($sorted, function(MDReader $a, MDReader $b): int {
|
|
1686
|
1708
|
return $a->compareTokenizeOrdering($b);
|
|
1687
|
|
- }, fn($elem) => get_class($elem));
|
|
|
1709
|
+ }, fn($elem) => MDUtils::typename($elem));
|
|
1688
|
1710
|
}
|
|
1689
|
1711
|
|
|
1690
|
1712
|
/**
|
|
|
@@ -1704,19 +1726,19 @@ class MDReader {
|
|
1704
|
1726
|
$maxPass = 1;
|
|
1705
|
1727
|
foreach ($readers as $reader) {
|
|
1706
|
1728
|
$passCount = $reader->substitutionPassCount();
|
|
|
1729
|
+ $maxPass = max($paxPass, $passCount);
|
|
1707
|
1730
|
for ($pass = 1; $pass <= $passCount; $pass++) {
|
|
1708
|
|
- array_push($tuples, [[ $pass, $reader ]]);
|
|
|
1731
|
+ array_push($tuples, [ $pass, $reader ]);
|
|
1709
|
1732
|
}
|
|
1710
|
|
- $maxPass = max($maxPass, $pass);
|
|
1711
|
1733
|
}
|
|
1712
|
1734
|
$result = [];
|
|
1713
|
1735
|
for ($pass = 1; $pass <= $maxPass; $pass++) {
|
|
1714
|
|
- $readersThisPass = array_filter($tuples, fn($tup) => $tup[0] == $pass);
|
|
|
1736
|
+ $readersThisPass = array_values(array_filter($tuples, fn($tup) => $tup[0] === $pass));
|
|
1715
|
1737
|
$passResult = self::kahnTopologicalSort($readersThisPass, function(array $a, array $b) use ($pass): int {
|
|
1716
|
1738
|
$aReader = $a[1];
|
|
1717
|
1739
|
$bReader = $b[1];
|
|
1718
|
1740
|
return $aReader->compareSubstituteOrdering($bReader, $pass);
|
|
1719
|
|
- }, fn($elem) => get_class($elem[1]));
|
|
|
1741
|
+ }, fn($elem) => MDUtils::typename($elem[1]));
|
|
1720
|
1742
|
$result = array_merge($result, $passResult);
|
|
1721
|
1743
|
}
|
|
1722
|
1744
|
return $result;
|
|
|
@@ -1870,14 +1892,14 @@ class _MDListReader extends MDReader {
|
|
1870
|
1892
|
$state->p += max(sizeof($itemLines), 1);
|
|
1871
|
1893
|
|
|
1872
|
1894
|
if (sizeof($itemLines) == 1) {
|
|
1873
|
|
- return $state->inlineMarkdownToNode($itemLines[0]);
|
|
|
1895
|
+ return new MDBlockNode($state->inlineMarkdownToNodes($itemLines[0]));
|
|
1874
|
1896
|
}
|
|
1875
|
1897
|
|
|
1876
|
1898
|
$hasBlankLines = sizeof(array_filter($itemLines, fn($line) => trim($line) == '')) > 0;
|
|
1877
|
1899
|
if ($hasBlankLines) {
|
|
1878
|
1900
|
$substate = $state->copy($itemLines);
|
|
1879
|
1901
|
$blocks = $substate->readBlocks();
|
|
1880
|
|
- return (sizeof($blocks) == 1) ? $blocks[0] : new MDNode($blocks);
|
|
|
1902
|
+ return (sizeof($blocks) == 1) ? $blocks[0] : new MBlockDNode($blocks);
|
|
1881
|
1903
|
}
|
|
1882
|
1904
|
|
|
1883
|
1905
|
// Multiline content with no blank lines. Search for new block
|
|
|
@@ -1886,10 +1908,10 @@ class _MDListReader extends MDReader {
|
|
1886
|
1908
|
$line = $itemLines[$p];
|
|
1887
|
1909
|
if (mb_eregi('^(?:\\*|\\-|\\+|\\d+\\.)\\s+', $line)) {
|
|
1888
|
1910
|
// Nested list found
|
|
1889
|
|
- $firstBlock = $state->inlineMarkdownToNode(implode("\n", array_slice($itemLines, 0, $p)));
|
|
|
1911
|
+ $firstBlock = new MDBlockNode($state->inlineMarkdownToNodes(implode("\n", array_slice($itemLines, 0, $p))));
|
|
1890
|
1912
|
$substate = $state->copy(array_slice($itemLines, $p));
|
|
1891
|
1913
|
$blocks = $substate->readBlocks();
|
|
1892
|
|
- return array_merge([ $firstBlock, $blocks ]);
|
|
|
1914
|
+ return new MDBlockNode(array_merge([ $firstBlock ], $blocks));
|
|
1893
|
1915
|
}
|
|
1894
|
1916
|
}
|
|
1895
|
1917
|
|
|
|
@@ -1897,12 +1919,12 @@ class _MDListReader extends MDReader {
|
|
1897
|
1919
|
{
|
|
1898
|
1920
|
$substate = $state->copy($itemLines);
|
|
1899
|
1921
|
$blocks = $substate->readBlocks();
|
|
1900
|
|
- return (sizeof($blocks) == 1) ? $blocks[0] : new MDNode($blocks);
|
|
|
1922
|
+ return (sizeof($blocks) == 1) ? $blocks[0] : new MDBlockNode($blocks);
|
|
1901
|
1923
|
}
|
|
1902
|
1924
|
}
|
|
1903
|
1925
|
|
|
1904
|
1926
|
public function readBlock(MDState $state): ?MDBlockNode {
|
|
1905
|
|
- $className = get_class($this);
|
|
|
1927
|
+ $className = MDUtils::typename($this);
|
|
1906
|
1928
|
throw new Error("Abstract readBlock must be overridden in {$className}");
|
|
1907
|
1929
|
}
|
|
1908
|
1930
|
}
|
|
|
@@ -1914,6 +1936,7 @@ class MDUnorderedListReader extends _MDListReader {
|
|
1914
|
1936
|
private static string $unorderedListRegex = '^([\\*\\+\\-]\\s+)(.*)$'; // 1=bullet, 2=content
|
|
1915
|
1937
|
|
|
1916
|
1938
|
private function readUnorderedListItem(MDState $state): ?MDListItemNode {
|
|
|
1939
|
+ if (!$state->hasLines(1)) return null;
|
|
1917
|
1940
|
$p = $state->p;
|
|
1918
|
1941
|
$line = $state->lines[$p];
|
|
1919
|
1942
|
if (!mb_eregi(self::$unorderedListRegex, $line, $groups)) return null;
|
|
|
@@ -1942,6 +1965,7 @@ class MDOrderedListReader extends _MDListReader {
|
|
1942
|
1965
|
private static string $orderedListRegex = '^(\\d+)(\\.\\s+)(.*)$'; // 1=number, 2=dot, 3=content
|
|
1943
|
1966
|
|
|
1944
|
1967
|
private function readOrderedListItem(MDState $state): ?MDListItemNode {
|
|
|
1968
|
+ if (!$state->hasLines(1)) return null;
|
|
1945
|
1969
|
$p = $state->p;
|
|
1946
|
1970
|
$line = $state->lines[$p];
|
|
1947
|
1971
|
if (!mb_eregi(self::$orderedListRegex, $line, $groups)) return null;
|
|
|
@@ -1976,7 +2000,7 @@ class MDFencedCodeBlockReader extends MDReader {
|
|
1976
|
2000
|
$p = $state->p;
|
|
1977
|
2001
|
$openFenceLine = $state->lines[$p++];
|
|
1978
|
2002
|
[$openFenceLine, $modifier] = MDTagModifier::fromLine($openFenceLine, $state);
|
|
1979
|
|
- if (!mb_eregi('```\s*([a-z0-9]*)\s*$', $openFenceLine, $groups)) return null;
|
|
|
2003
|
+ if (!mb_eregi('```\\s*([a-z0-9]*)\\s*$', $openFenceLine, $groups)) return null;
|
|
1980
|
2004
|
$language = mb_strlen($groups[1]) > 0 ? $groups[1] : null;
|
|
1981
|
2005
|
$codeLines = [];
|
|
1982
|
2006
|
while ($state->hasLines(1, $p)) {
|
|
|
@@ -2056,7 +2080,7 @@ class MDTableReader extends MDReader {
|
|
2056
|
2080
|
if (str_starts_with($line, '|')) $line = mb_substr($line, 1);
|
|
2057
|
2081
|
if (str_ends_with($line, '|')) $line = mb_substr($line, 0, mb_strlen($line) - 1);
|
|
2058
|
2082
|
$cellTokens = explode('|', $line);
|
|
2059
|
|
- $cells = array_map(function($token) use ($isHeader) {
|
|
|
2083
|
+ $cells = array_map(function($token) use ($isHeader, $state) {
|
|
2060
|
2084
|
$content = $state->inlineMarkdownToNode(trim($token));
|
|
2061
|
2085
|
return $isHeader ? new MDTableHeaderCellNode($content) : new MDTableCellNode($content);
|
|
2062
|
2086
|
}, $cellTokens);
|
|
|
@@ -2144,8 +2168,8 @@ class MDDefinitionListReader extends MDReader {
|
|
2144
|
2168
|
}
|
|
2145
|
2169
|
}
|
|
2146
|
2170
|
if ($termCount == 0 || $definitionCount == 0) return null;
|
|
2147
|
|
- $blocks = array_map(function($line) {
|
|
2148
|
|
- if (mb_eregi('^:\\s+(.*?)$', $line)) {
|
|
|
2171
|
+ $blocks = array_map(function($line) use ($state) {
|
|
|
2172
|
+ if (mb_eregi('^:\\s+(.*?)$', $line, $groups)) {
|
|
2149
|
2173
|
return new MDDefinitionListDefinitionNode($state->inlineMarkdownToNodes($groups[1]));
|
|
2150
|
2174
|
} else {
|
|
2151
|
2175
|
return new MDDefinitionListTermNode($state->inlineMarkdownToNodes($line));
|
|
|
@@ -2181,16 +2205,18 @@ class MDFootnoteReader extends MDReader {
|
|
2181
|
2205
|
$instances = $footnoteInstances[$symbol] ?? [];
|
|
2182
|
2206
|
array_push($instances, $unique);
|
|
2183
|
2207
|
$footnoteInstances[$symbol] = $instances;
|
|
|
2208
|
+ $state->root()->userInfo['footnoteInstances'] = $footnoteInstances;
|
|
2184
|
2209
|
}
|
|
2185
|
2210
|
|
|
2186
|
2211
|
private function idForFootnoteSymbol(MDState $state, string $symbol): int {
|
|
2187
|
|
- $footnoteIds = $state->root()->userInfo['footnoteIds'];
|
|
2188
|
|
- $existing = $footnoteIds[$symbol];
|
|
2189
|
|
- if ($existing) return $existing;
|
|
2190
|
|
- $nextFootnoteId = $state->root()->userInfo['nextFootnoteId'];
|
|
|
2212
|
+ $footnoteIds = $state->root()->userInfo['footnoteIds'] ?? [];
|
|
|
2213
|
+ $existing = $footnoteIds[$symbol] ?? null;
|
|
|
2214
|
+ if ($existing !== null) return $existing;
|
|
|
2215
|
+ $nextFootnoteId = $state->root()->userInfo['nextFootnoteId'] ?? 1;
|
|
2191
|
2216
|
$id = $nextFootnoteId++;
|
|
2192
|
2217
|
$footnoteIds[$symbol] = $id;
|
|
2193
|
2218
|
$state->root()->userInfo['nextFootnoteId'] = $nextFootnoteId;
|
|
|
2219
|
+ $state->root()->userInfo['footnoteIds'] = $footnoteIds;
|
|
2194
|
2220
|
return $id;
|
|
2195
|
2221
|
}
|
|
2196
|
2222
|
|
|
|
@@ -2218,7 +2244,7 @@ class MDFootnoteReader extends MDReader {
|
|
2218
|
2244
|
$content = $state->inlineMarkdownToNodes($def);
|
|
2219
|
2245
|
$this->defineFootnote($state, $symbol, $content);
|
|
2220
|
2246
|
$state->p = $p;
|
|
2221
|
|
- return new MDNode(); // empty
|
|
|
2247
|
+ return new MDBlockNode(); // empty
|
|
2222
|
2248
|
}
|
|
2223
|
2249
|
|
|
2224
|
2250
|
public function readToken(MDState $state, string $line): ?MDToken {
|
|
|
@@ -2233,9 +2259,9 @@ class MDFootnoteReader extends MDReader {
|
|
2233
|
2259
|
}
|
|
2234
|
2260
|
|
|
2235
|
2261
|
public function substituteTokens(MDState $state, int $pass, array &$tokens): bool {
|
|
2236
|
|
- if ($match = self::findFirstTokens($tokens, [ MDTokenType::Footnote ])) {
|
|
|
2262
|
+ if ($match = MDToken::findFirstTokens($tokens, [ MDTokenType::Footnote ])) {
|
|
2237
|
2263
|
$symbol = $match->tokens[0]->content;
|
|
2238
|
|
- array_splice($tokens, $match->index, 1, new MDFootnoteNode($symbol));
|
|
|
2264
|
+ array_splice($tokens, $match->index, 1, [new MDFootnoteNode($symbol)]);
|
|
2239
|
2265
|
return true;
|
|
2240
|
2266
|
}
|
|
2241
|
2267
|
return false;
|
|
|
@@ -2248,12 +2274,12 @@ class MDFootnoteReader extends MDReader {
|
|
2248
|
2274
|
public function postProcess(MDState $state, array &$blocks) {
|
|
2249
|
2275
|
$nextOccurrenceId = 1;
|
|
2250
|
2276
|
foreach ($blocks as $block) {
|
|
2251
|
|
- $block->visitChildren(function($node) use (&$nextOccurrenceId) {
|
|
|
2277
|
+ $block->visitChildren(function($node) use (&$nextOccurrenceId, $state) {
|
|
2252
|
2278
|
if (!($node instanceof MDFootnoteNode)) return;
|
|
2253
|
2279
|
$node->footnoteId = $this->idForFootnoteSymbol($state, $node->symbol);
|
|
2254
|
2280
|
$node->occurrenceId = $nextOccurrenceId++;
|
|
2255
|
2281
|
$node->displaySymbol = strval($node->footnoteId);
|
|
2256
|
|
- $this->$registerUniqueInstance($state, $node->symbol, $node->occurrenceId);
|
|
|
2282
|
+ $this->registerUniqueInstance($state, $node->symbol, $node->occurrenceId);
|
|
2257
|
2283
|
});
|
|
2258
|
2284
|
}
|
|
2259
|
2285
|
if (sizeof($state->userInfo['footnotes']) == 0) return;
|
|
|
@@ -2290,14 +2316,13 @@ class MDFootnoteReader extends MDReader {
|
|
2290
|
2316
|
*/
|
|
2291
|
2317
|
class MDAbbreviationReader extends MDReader {
|
|
2292
|
2318
|
private function defineAbbreviation(MDState $state, string $abbreviation, string $definition) {
|
|
2293
|
|
- $state->root()->abbreviations[$abbreviation] = $definition;
|
|
2294
|
|
- $regex = "\\b(" . preg_quote($abbreviation) . ")\\b";
|
|
2295
|
|
- $state->root()->abbreviationRegexes[$abbreviation] = $regex;
|
|
|
2319
|
+ $abbrevs = $state->root()->userInfo['abbreviations'];
|
|
|
2320
|
+ $abbrevs[$abbreviation] = $definition;
|
|
|
2321
|
+ $state->root()->userInfo['abbreviations'] = $abbrevs;
|
|
2296
|
2322
|
}
|
|
2297
|
2323
|
|
|
2298
|
2324
|
public function preProcess(MDState $state) {
|
|
2299
|
2325
|
$state->root()->userInfo['abbreviations'] = [];
|
|
2300
|
|
- $state->root()->userInfo['abbreviationRegexes'] = [];
|
|
2301
|
2326
|
}
|
|
2302
|
2327
|
|
|
2303
|
2328
|
public function readBlock(MDState $state): ?MDBlockNode {
|
|
|
@@ -2308,7 +2333,7 @@ class MDAbbreviationReader extends MDReader {
|
|
2308
|
2333
|
$def = $groups[2];
|
|
2309
|
2334
|
$this->defineAbbreviation($state, $abbrev, $def);
|
|
2310
|
2335
|
$state->p = $p;
|
|
2311
|
|
- return new MDNode(); // empty
|
|
|
2336
|
+ return new MDBlockNode(); // empty
|
|
2312
|
2337
|
}
|
|
2313
|
2338
|
|
|
2314
|
2339
|
/**
|
|
|
@@ -2317,28 +2342,26 @@ class MDAbbreviationReader extends MDReader {
|
|
2317
|
2342
|
*/
|
|
2318
|
2343
|
public function postProcess(MDState $state, array &$blocks) {
|
|
2319
|
2344
|
$abbreviations = $state->root()->userInfo['abbreviations'];
|
|
2320
|
|
- $regexes = $state->root()->userInfo['abbreviationRegexes'];
|
|
2321
|
|
- MDNode::replaceNodes($state, $blocks, function($original) use ($abbreviations, $regexes) {
|
|
|
2345
|
+ MDNode::replaceNodes($state, $blocks, function($original) use ($abbreviations) {
|
|
2322
|
2346
|
if (!($original instanceof MDTextNode)) return null;
|
|
2323
|
2347
|
$changed = false;
|
|
2324
|
2348
|
$elems = [ $original->text ]; // mix of strings and MDNodes
|
|
2325
|
2349
|
for ($i = 0; $i < sizeof($elems); $i++) {
|
|
2326
|
2350
|
$text = $elems[$i];
|
|
2327
|
2351
|
if (!is_string($text)) continue;
|
|
2328
|
|
- foreach ($abbreviations as $abbreviation) {
|
|
|
2352
|
+ foreach ($abbreviations as $abbreviation => $definition) {
|
|
2329
|
2353
|
$index = strpos($text, $abbreviation);
|
|
2330
|
|
- if ($index === false) break;
|
|
|
2354
|
+ if ($index === false) continue;
|
|
2331
|
2355
|
$prefix = substr($text, 0, $index);
|
|
2332
|
2356
|
$suffix = substr($text, $index + strlen($abbreviation));
|
|
2333
|
|
- $definition = $abbreviations[$abbreviation];
|
|
2334
|
|
- array_splice($elems, $i, 1, [ $prefix, new MDAbbreviationNode($abbreviation, $definition), $suffix ]);
|
|
|
2357
|
+ array_splice($elems, $i, 1, [$prefix, new MDAbbreviationNode($abbreviation, $definition), $suffix]);
|
|
2335
|
2358
|
$i = -1; // start over
|
|
2336
|
2359
|
$changed = true;
|
|
2337
|
2360
|
break;
|
|
2338
|
2361
|
}
|
|
2339
|
2362
|
}
|
|
2340
|
2363
|
if (!$changed) return null;
|
|
2341
|
|
- $nodes = array_map(fn($elem) => is_string($elem) ? new MDTextNode($elem) : $elem);
|
|
|
2364
|
+ $nodes = array_map(fn($elem) => is_string($elem) ? new MDTextNode($elem) : $elem, $elems);
|
|
2342
|
2365
|
return new MDNode($nodes);
|
|
2343
|
2366
|
});
|
|
2344
|
2367
|
}
|
|
|
@@ -2417,17 +2440,16 @@ class MDSimplePairInlineReader extends MDReader {
|
|
2417
|
2440
|
// #4: singles with paired inner tokens
|
|
2418
|
2441
|
if ($count == 1 && $pass != 2 && $pass != 4) return false;
|
|
2419
|
2442
|
if ($count > 1 && $pass != 1 && $pass != 3) return false;
|
|
2420
|
|
- $delimiters = [];
|
|
2421
|
|
- array_fill(0, $count, $delimiter);
|
|
|
2443
|
+ $delimiters = array_fill(0, $count, $delimiter);
|
|
2422
|
2444
|
$isFirstOfMultiplePasses = $this->substitutionPassCount() > 1 && $pass == 1;
|
|
2423
|
|
- $match = MDToken::findPairedTokens($tokens, $delimiters, $delimiters, function($content) {
|
|
|
2445
|
+ $match = MDToken::findPairedTokens($tokens, $delimiters, $delimiters, function($content) use ($nodeClass, $isFirstOfMultiplePasses, $delimiter) {
|
|
2424
|
2446
|
$firstType = $content[0] instanceof MDToken ? $content[0]->type : null;
|
|
2425
|
2447
|
$lastType = $content[sizeof($content) - 1] instanceof MDToken ? $content[sizeof($content) - 1]->type : null;
|
|
2426
|
2448
|
if ($firstType == MDTokenType::Whitespace) return false;
|
|
2427
|
2449
|
if ($lastType == MDTokenType::Whitespace) return false;
|
|
2428
|
2450
|
foreach ($content as $token) {
|
|
2429
|
2451
|
// Don't allow nesting
|
|
2430
|
|
- if (get_class($token) == $nodeClass) return false;
|
|
|
2452
|
+ if (MDUtils::typename($token) == $nodeClass) return false;
|
|
2431
|
2453
|
}
|
|
2432
|
2454
|
if ($isFirstOfMultiplePasses) {
|
|
2433
|
2455
|
$innerCount = 0;
|
|
|
@@ -2439,14 +2461,19 @@ class MDSimplePairInlineReader extends MDReader {
|
|
2439
|
2461
|
return true;
|
|
2440
|
2462
|
});
|
|
2441
|
2463
|
if ($match === null) return false;
|
|
2442
|
|
- $content = ($plaintext)
|
|
2443
|
|
- ? implode('', array_map(fn($token) => $token->original, $match->contentTokens))
|
|
2444
|
|
- : $state->tokensToNodes($match->contentTokens);
|
|
|
2464
|
+ $state->checkExecutionTime();
|
|
|
2465
|
+ if ($plaintext) {
|
|
|
2466
|
+ $content = implode('', array_map(fn($token) => $token instanceof MDToken ? $token->original : $token->toPlaintext($state), $match->contentTokens));
|
|
|
2467
|
+ } else {
|
|
|
2468
|
+ $content = $state->tokensToNodes($match->contentTokens);
|
|
|
2469
|
+ }
|
|
2445
|
2470
|
$ref = new ReflectionClass($nodeClass);
|
|
2446
|
2471
|
$node = $ref->newInstanceArgs([ $content ]);
|
|
2447
|
|
- array_splice($tokens, $match->startIndex, $match->totalLength, [ $node ]);
|
|
|
2472
|
+ array_splice($tokens, $match->startIndex, $match->totalLength, [$node]);
|
|
2448
|
2473
|
return true;
|
|
2449
|
2474
|
}
|
|
|
2475
|
+
|
|
|
2476
|
+ private static $firstTime = null;
|
|
2450
|
2477
|
}
|
|
2451
|
2478
|
|
|
2452
|
2479
|
/**
|
|
|
@@ -2582,6 +2609,7 @@ class MDCodeSpanReader extends MDSimplePairInlineReader {
|
|
2582
|
2609
|
public function substituteTokens(MDState $state, int $pass, array &$tokens): bool {
|
|
2583
|
2610
|
if ($this->attemptPair($state, $pass, $tokens, 'MDCodeNode', MDTokenType::Backtick, 2, true)) return true;
|
|
2584
|
2611
|
if ($this->attemptPair($state, $pass, $tokens, 'MDCodeNode', MDTokenType::Backtick, 1, true)) return true;
|
|
|
2612
|
+ return false;
|
|
2585
|
2613
|
}
|
|
2586
|
2614
|
}
|
|
2587
|
2615
|
|
|
|
@@ -2654,7 +2682,7 @@ class MDLinkReader extends MDReader {
|
|
2654
|
2682
|
$text = $match->tokens[0]->content;
|
|
2655
|
2683
|
$url = $match->tokens[sizeof($match->tokens) - 1]->content;
|
|
2656
|
2684
|
$title = $match->tokens[sizeof($match->tokens) - 1]->extra;
|
|
2657
|
|
- array_splice($tokens, $match->index, sizeof($match->tokens), new MDLinkNode($url, $state->inlineMarkdownToNode($text), $title));
|
|
|
2685
|
+ array_splice($tokens, $match->index, sizeof($match->tokens), [new MDLinkNode($url, $state->inlineMarkdownToNode($text), $title)]);
|
|
2658
|
2686
|
return true;
|
|
2659
|
2687
|
}
|
|
2660
|
2688
|
if ($match = MDToken::findFirstTokens($tokens, [ MDTokenType::Label, MDTokenType::META_OptionalWhitespace, MDTokenType::Email ])) {
|
|
|
@@ -2662,21 +2690,21 @@ class MDLinkReader extends MDReader {
|
|
2662
|
2690
|
$email = $match->tokens[sizeof($match->tokens) - 1]->content;
|
|
2663
|
2691
|
$url = "mailto:{$email}";
|
|
2664
|
2692
|
$title = $match->tokens[sizeof($match->tokens) - 1]->extra;
|
|
2665
|
|
- array_splice($tokens, $match->index, sizeof($match->tokens), new MDLinkNode($url, $state->inlineMarkdownToNodes($text), $title));
|
|
|
2693
|
+ array_splice($tokens, $match->index, sizeof($match->tokens), [new MDLinkNode($url, $state->inlineMarkdownToNodes($text), $title)]);
|
|
2666
|
2694
|
return true;
|
|
2667
|
2695
|
}
|
|
2668
|
2696
|
if ($match = MDToken::findFirstTokens($tokens, [ MDTokenType::SimpleEmail ])) {
|
|
2669
|
2697
|
$token = $match->tokens[0];
|
|
2670
|
2698
|
$link = "mailto:{$token->content}";
|
|
2671
|
2699
|
$node = new MDLinkNode($link, new MDObfuscatedTextNode($token->content));
|
|
2672
|
|
- array_splice($tokens, $match->index, 1, $node);
|
|
|
2700
|
+ array_splice($tokens, $match->index, 1, [$node]);
|
|
2673
|
2701
|
return true;
|
|
2674
|
2702
|
}
|
|
2675
|
2703
|
if ($match = MDToken::findFirstTokens($tokens, [ MDTokenType::SimpleLink ])) {
|
|
2676
|
2704
|
$token = $match->tokens[0];
|
|
2677
|
2705
|
$link = $token->content;
|
|
2678
|
2706
|
$node = new MDLinkNode($link, new MDTextNode($link));
|
|
2679
|
|
- array_splice($tokens, $match->index, 1, $node);
|
|
|
2707
|
+ array_splice($tokens, $match->index, 1, [$node]);
|
|
2680
|
2708
|
return true;
|
|
2681
|
2709
|
}
|
|
2682
|
2710
|
return false;
|
|
|
@@ -2701,20 +2729,21 @@ class MDReferencedLinkReader extends MDLinkReader {
|
|
2701
|
2729
|
if (mb_eregi('^\\s*\\[(.+?)]:\\s*(\\S+)\\s*$', $line, $groups)) {
|
|
2702
|
2730
|
$symbol = $groups[1];
|
|
2703
|
2731
|
$url = $groups[2];
|
|
|
2732
|
+ $title = null;
|
|
2704
|
2733
|
} else {
|
|
2705
|
2734
|
return null;
|
|
2706
|
2735
|
}
|
|
2707
|
2736
|
}
|
|
2708
|
2737
|
$state->defineURL($symbol, $url, $title);
|
|
2709
|
2738
|
$state->p = $p;
|
|
2710
|
|
- return new MDNode([]); // empty
|
|
|
2739
|
+ return new MDBlockNode([]); // empty
|
|
2711
|
2740
|
}
|
|
2712
|
2741
|
|
|
2713
|
2742
|
public function substituteTokens(MDState $state, int $pass, array &$tokens): bool {
|
|
2714
|
2743
|
if ($match = MDToken::findFirstTokens($tokens, [ MDTokenType::Label, MDTokenType::META_OptionalWhitespace, MDTokenType::Label ])) {
|
|
2715
|
2744
|
$text = $match->tokens[0]->content;
|
|
2716
|
2745
|
$ref = $match->tokens[sizeof($match->tokens) - 1]->content;
|
|
2717
|
|
- array_splice($tokens, $match->index, sizeof($match->tokens), new MDReferencedLinkNode($ref, $state->inlineMarkdownToNodes($text)));
|
|
|
2746
|
+ array_splice($tokens, $match->index, sizeof($match->tokens), [new MDReferencedLinkNode($ref, $state->inlineMarkdownToNodes($text))]);
|
|
2718
|
2747
|
return true;
|
|
2719
|
2748
|
}
|
|
2720
|
2749
|
return false;
|
|
|
@@ -2742,7 +2771,7 @@ class MDImageReader extends MDLinkReader {
|
|
2742
|
2771
|
if ($title !== null) {
|
|
2743
|
2772
|
$node->attributes['title'] = $title;
|
|
2744
|
2773
|
}
|
|
2745
|
|
- array_splice($tokens, $match->index, sizeof($match->tokens), $node);
|
|
|
2774
|
+ array_splice($tokens, $match->index, sizeof($match->tokens), [$node]);
|
|
2746
|
2775
|
return true;
|
|
2747
|
2776
|
}
|
|
2748
|
2777
|
return false;
|
|
|
@@ -2773,7 +2802,7 @@ class MDReferencedImageReader extends MDReferencedLinkReader {
|
|
2773
|
2802
|
if ($match = MDToken::findFirstTokens($tokens, [ MDTokenType::Bang, MDTokenType::Label, MDTokenType::META_OptionalWhitespace, MDTokenType::Label ])) {
|
|
2774
|
2803
|
$alt = $match->tokens[1]->content;
|
|
2775
|
2804
|
$ref = $match->tokens[sizeof($match->tokens) - 1]->content;
|
|
2776
|
|
- array_splice($tokens, $match->index, sizeof($match->tokens), new MDReferencedImageNode($ref, $alt));
|
|
|
2805
|
+ array_splice($tokens, $match->index, sizeof($match->tokens), [new MDReferencedImageNode($ref, $alt)]);
|
|
2777
|
2806
|
return true;
|
|
2778
|
2807
|
}
|
|
2779
|
2808
|
return false;
|
|
|
@@ -2827,7 +2856,7 @@ class MDHTMLTagReader extends MDReader {
|
|
2827
|
2856
|
public function substituteTokens(MDState $state, int $pass, array &$tokens): bool {
|
|
2828
|
2857
|
if ($match = MDToken::findFirstTokens($tokens, [ MDTokenType::HTMLTag ])) {
|
|
2829
|
2858
|
$tag = $match->tokens[0]->tag;
|
|
2830
|
|
- array_splice($tokens, $match->index, sizeof($match->tokens), new MDHTMLTagNode($tag));
|
|
|
2859
|
+ array_splice($tokens, $match->index, 1, [new MDHTMLTagNode($tag)]);
|
|
2831
|
2860
|
return true;
|
|
2832
|
2861
|
}
|
|
2833
|
2862
|
return false;
|
|
|
@@ -2897,8 +2926,8 @@ class MDNode {
|
|
2897
|
2926
|
if (is_array($children)) {
|
|
2898
|
2927
|
foreach ($children as $elem) {
|
|
2899
|
2928
|
if (!($elem instanceof MDNode)) {
|
|
2900
|
|
- $thisClassName = get_class($this);
|
|
2901
|
|
- $elemClassName = get_class($elem);
|
|
|
2929
|
+ $thisClassName = MDUtils::typename($this);
|
|
|
2930
|
+ $elemClassName = MDUtils::typename($elem);
|
|
2902
|
2931
|
throw new Error("{$thisClassName} expects children of type MDNode[] or MDNode, got array with {$elemClassName} element");
|
|
2903
|
2932
|
}
|
|
2904
|
2933
|
}
|
|
|
@@ -2906,12 +2935,21 @@ class MDNode {
|
|
2906
|
2935
|
} elseif ($children instanceof MDNode) {
|
|
2907
|
2936
|
$this->children = [ $children ];
|
|
2908
|
2937
|
} else {
|
|
2909
|
|
- $thisClassName = get_class($this);
|
|
2910
|
|
- $elemClassName = gettype($children) == 'object' ? get_class($children) : gettype($children);
|
|
|
2938
|
+ $thisClassName = MDUtils::typename($this);
|
|
|
2939
|
+ $elemClassName = MDUtils::typename($children);
|
|
2911
|
2940
|
throw new Error("{$thisClassName} expects children of type MDNode[] or MDNode, got {$elemClassName}");
|
|
2912
|
2941
|
}
|
|
2913
|
2942
|
}
|
|
2914
|
2943
|
|
|
|
2944
|
+ public function __toString(): string {
|
|
|
2945
|
+ $s = "<" . get_class($this);
|
|
|
2946
|
+ foreach ($this->children as $child) {
|
|
|
2947
|
+ $s .= " {$child}";
|
|
|
2948
|
+ }
|
|
|
2949
|
+ $s .= ">";
|
|
|
2950
|
+ return $s;
|
|
|
2951
|
+ }
|
|
|
2952
|
+
|
|
2915
|
2953
|
/**
|
|
2916
|
2954
|
* Adds a CSS class. If already present it will not be duplicated.
|
|
2917
|
2955
|
*/
|
|
|
@@ -2969,25 +3007,25 @@ class MDNode {
|
|
2969
|
3007
|
protected function htmlAttributes(): string {
|
|
2970
|
3008
|
$html = '';
|
|
2971
|
3009
|
if (sizeof($this->cssClasses) > 0) {
|
|
2972
|
|
- $classlist = implode(' ', $this->cssClasses);
|
|
2973
|
|
- $html .= " class=\"{$classList}\"";
|
|
|
3010
|
+ $classlist = htmlentities(implode(' ', $this->cssClasses));
|
|
|
3011
|
+ $html .= " class=\"{$classlist}\"";
|
|
2974
|
3012
|
}
|
|
2975
|
3013
|
if ($this->cssId !== null && mb_strlen($this->cssId) > 0) {
|
|
2976
|
|
- $html .= " id=\"{$this->cssId}\"";
|
|
|
3014
|
+ $html .= " id=\"" . htmlentities($this->cssId) . "\"";
|
|
2977
|
3015
|
}
|
|
2978
|
3016
|
$styles = [];
|
|
2979
|
3017
|
foreach ($this->cssStyles as $key => $value) {
|
|
2980
|
3018
|
array_push($styles, "{$key}: {$value};");
|
|
2981
|
3019
|
}
|
|
2982
|
3020
|
if (sizeof($styles) > 0) {
|
|
2983
|
|
- $escaped = htmlspecialchars(implode(' ', $styles));
|
|
|
3021
|
+ $escaped = htmlentities(implode(' ', $styles));
|
|
2984
|
3022
|
$html .= " style=\"{$escaped}\"";
|
|
2985
|
3023
|
}
|
|
2986
|
3024
|
foreach ($this->attributes as $key => $value) {
|
|
2987
|
3025
|
if ($key === 'class' || $key === 'id' || $key === 'style') continue;
|
|
2988
|
3026
|
$cleanKey = MDUtils::scrubAttributeName($key);
|
|
2989
|
3027
|
if (mb_strlen($cleanKey) == 0) continue;
|
|
2990
|
|
- $cleanValue = htmlspecialchars($value);
|
|
|
3028
|
+ $cleanValue = htmlentities($value);
|
|
2991
|
3029
|
$html .= " {$cleanKey}=\"{$cleanValue}\"";
|
|
2992
|
3030
|
}
|
|
2993
|
3031
|
return $html;
|
|
|
@@ -3106,7 +3144,7 @@ class MDHeadingNode extends MDBlockNode {
|
|
3106
|
3144
|
public function __construct(int $level, array $children) {
|
|
3107
|
3145
|
parent::__construct($children);
|
|
3108
|
3146
|
if (!is_int($level) || ($level < 1 || $level > 6)) {
|
|
3109
|
|
- $thisClassName = get_class($this);
|
|
|
3147
|
+ $thisClassName = MDUtils::typename($this);
|
|
3110
|
3148
|
throw new Error("{$thisClassName} requires heading level 1 to 6");
|
|
3111
|
3149
|
}
|
|
3112
|
3150
|
$this->level = $level;
|
|
|
@@ -3162,7 +3200,7 @@ class MDUnorderedListNode extends MDBlockNode {
|
|
3162
|
3200
|
class MDOrderedListNode extends MDBlockNode {
|
|
3163
|
3201
|
/** @var MDListItemNode[] $children */
|
|
3164
|
3202
|
|
|
3165
|
|
- public int $startOrdinal;
|
|
|
3203
|
+ public ?int $startOrdinal;
|
|
3166
|
3204
|
|
|
3167
|
3205
|
/**
|
|
3168
|
3206
|
* @param MDListItemNode[] $children
|
|
|
@@ -3185,13 +3223,13 @@ class MDOrderedListNode extends MDBlockNode {
|
|
3185
|
3223
|
* An item in a bulleted or numbered list.
|
|
3186
|
3224
|
*/
|
|
3187
|
3225
|
class MDListItemNode extends MDBlockNode {
|
|
3188
|
|
- public int $ordinal;
|
|
|
3226
|
+ public ?int $ordinal;
|
|
3189
|
3227
|
|
|
3190
|
3228
|
/**
|
|
3191
|
3229
|
* @param MDNode|MDNode[] $children
|
|
3192
|
3230
|
* @param ?int $ordinal
|
|
3193
|
3231
|
*/
|
|
3194
|
|
- public function __construct(array $children, ?int $ordinal=null) {
|
|
|
3232
|
+ public function __construct(array|MDNode $children, ?int $ordinal=null) {
|
|
3195
|
3233
|
parent::__construct($children);
|
|
3196
|
3234
|
$this->ordinal = $ordinal;
|
|
3197
|
3235
|
}
|
|
|
@@ -3213,7 +3251,7 @@ class MDCodeBlockNode extends MDBlockNode {
|
|
3213
|
3251
|
public ?string $language;
|
|
3214
|
3252
|
|
|
3215
|
3253
|
public function __construct(string $text, ?string $language=null) {
|
|
3216
|
|
- super([]);
|
|
|
3254
|
+ parent::__construct([]);
|
|
3217
|
3255
|
$this->text = $text;
|
|
3218
|
3256
|
$this->language = $language;
|
|
3219
|
3257
|
}
|
|
|
@@ -3251,7 +3289,7 @@ class MDTableNode extends MDBlockNode {
|
|
3251
|
3289
|
* @param MDTableRowNode $headerRow
|
|
3252
|
3290
|
* @param MDTableRowNode[] $bodyRows
|
|
3253
|
3291
|
*/
|
|
3254
|
|
- public function __construct(MDTableRow $headerRow, array $bodyRows) {
|
|
|
3292
|
+ public function __construct(MDTableRowNode $headerRow, array $bodyRows) {
|
|
3255
|
3293
|
parent::__construct(array_merge([ $headerRow ], $bodyRows));
|
|
3256
|
3294
|
}
|
|
3257
|
3295
|
|
|
|
@@ -3280,11 +3318,11 @@ class MDTableNode extends MDBlockNode {
|
|
3280
|
3318
|
$this->applyAlignments();
|
|
3281
|
3319
|
$html = '';
|
|
3282
|
3320
|
$html .= "<table" . $this->htmlAttributes() . ">\n";
|
|
3283
|
|
- $html .= '<thead>\n';
|
|
3284
|
|
- $html .= $this->headerRow->toHTML($state) . "\n";
|
|
|
3321
|
+ $html .= "<thead>\n";
|
|
|
3322
|
+ $html .= $this->headerRow()->toHTML($state) . "\n";
|
|
3285
|
3323
|
$html .= "</thead>\n";
|
|
3286
|
3324
|
$html .= "<tbody>\n";
|
|
3287
|
|
- $html .= MDNode::toHTML($this->bodyRows, $state) . "\n";
|
|
|
3325
|
+ $html .= MDNode::arrayToHTML($this->bodyRows(), $state) . "\n";
|
|
3288
|
3326
|
$html .= "</tbody>\n";
|
|
3289
|
3327
|
$html .= "</table>\n";
|
|
3290
|
3328
|
return $html;
|
|
|
@@ -3314,7 +3352,7 @@ class MDTableCellNode extends MDBlockNode {
|
|
3314
|
3352
|
/**
|
|
3315
|
3353
|
* Node for a header cell in a header table row.
|
|
3316
|
3354
|
*/
|
|
3317
|
|
-class MDTableHeaderCellNode extends MDBlockNode {
|
|
|
3355
|
+class MDTableHeaderCellNode extends MDTableCellNode {
|
|
3318
|
3356
|
public function toHTML(MDState $state): string {
|
|
3319
|
3357
|
return $this->simplePairedTagHTML($state, 'th');
|
|
3320
|
3358
|
}
|
|
|
@@ -3354,27 +3392,28 @@ class MDDefinitionListDefinitionNode extends MDBlockNode {
|
|
3354
|
3392
|
* content.
|
|
3355
|
3393
|
*/
|
|
3356
|
3394
|
class MDFootnoteListNode extends MDBlockNode {
|
|
3357
|
|
- private function footnoteId(MDState $state, string $symbol): int {
|
|
|
3395
|
+ private function footnoteId(MDState $state, string $symbol): ?int {
|
|
3358
|
3396
|
$lookup = $state->root()->userInfo['footnoteIds'];
|
|
3359
|
3397
|
if (!$lookup) return null;
|
|
3360
|
3398
|
return $lookup[$symbol] ?? null;
|
|
3361
|
3399
|
}
|
|
3362
|
3400
|
|
|
3363
|
3401
|
public function toHTML(MDState $state): string {
|
|
3364
|
|
- $footnotes = $state->userInfo['footnotes'];
|
|
|
3402
|
+ $footnotes = $state->root()->userInfo['footnotes'];
|
|
3365
|
3403
|
$symbolOrder = array_keys($footnotes);
|
|
3366
|
3404
|
if (sizeof($footnotes) == 0) return '';
|
|
3367
|
|
- $footnoteUniques = $state->root()->footnoteInstances;
|
|
|
3405
|
+ $footnoteUniques = $state->root()->userInfo['footnoteInstances'];
|
|
3368
|
3406
|
$html = '';
|
|
3369
|
3407
|
$html .= '<div class="footnotes">';
|
|
3370
|
3408
|
$html .= '<ol>';
|
|
3371
|
|
- foreach ($symbolOrder as $symbol) {
|
|
|
3409
|
+ foreach ($symbolOrder as $symbolRaw) {
|
|
|
3410
|
+ $symbol = "{$symbolRaw}";
|
|
3372
|
3411
|
$content = $footnotes[$symbol];
|
|
3373
|
3412
|
if (!$content) continue;
|
|
3374
|
3413
|
$footnoteId = $this->footnoteId($state, $symbol);
|
|
3375
|
|
- $contentHTML = MDNode::toHTML($content, $state);
|
|
|
3414
|
+ $contentHTML = MDNode::arrayToHTML($content, $state);
|
|
3376
|
3415
|
$html .= "<li value=\"{$footnoteId}\" id=\"{$state->root()->elementIdPrefix}footnote_{$footnoteId}\">{$contentHTML}";
|
|
3377
|
|
- $uniques = $footnoteUniques[$symbol];
|
|
|
3416
|
+ $uniques = $footnoteUniques[$symbol] ?? null;
|
|
3378
|
3417
|
if ($uniques) {
|
|
3379
|
3418
|
foreach ($uniques as $unique) {
|
|
3380
|
3419
|
$html .= " <a href=\"#{$state->root()->elementIdPrefix}footnoteref_{$unique}\" class=\"footnote-backref\">↩︎</a>";
|
|
|
@@ -3384,7 +3423,7 @@ class MDFootnoteListNode extends MDBlockNode {
|
|
3384
|
3423
|
}
|
|
3385
|
3424
|
$html .= '</ol>';
|
|
3386
|
3425
|
$html .= '</div>';
|
|
3387
|
|
- return html;
|
|
|
3426
|
+ return $html;
|
|
3388
|
3427
|
}
|
|
3389
|
3428
|
|
|
3390
|
3429
|
public function toPlaintext(MDState $state): string {
|
|
|
@@ -3392,7 +3431,8 @@ class MDFootnoteListNode extends MDBlockNode {
|
|
3392
|
3431
|
$symbolOrder = array_keys($footnotes);
|
|
3393
|
3432
|
if (sizeof($footnotes) == 0) return '';
|
|
3394
|
3433
|
$text = '';
|
|
3395
|
|
- foreach ($symbolOrder as $symbol) {
|
|
|
3434
|
+ foreach ($symbolOrder as $symbolRaw) {
|
|
|
3435
|
+ $symbol = "{$symbolRaw}";
|
|
3396
|
3436
|
$content = $footnotes[$symbol];
|
|
3397
|
3437
|
if (!$content) continue;
|
|
3398
|
3438
|
$text .= "{$symbol}. " . $this->childPlaintext(state) . "\n";
|
|
|
@@ -3414,7 +3454,6 @@ class MDTextNode extends MDInlineNode {
|
|
3414
|
3454
|
|
|
3415
|
3455
|
public function __construct(string $text) {
|
|
3416
|
3456
|
parent::__construct([]);
|
|
3417
|
|
- if (!is_string($text) || mb_strlen($text) == 0) throw new Error("Meh!");
|
|
3418
|
3457
|
$this->text = $text;
|
|
3419
|
3458
|
}
|
|
3420
|
3459
|
|
|
|
@@ -3513,7 +3552,7 @@ class MDCodeNode extends MDInlineNode {
|
|
3513
|
3552
|
}
|
|
3514
|
3553
|
|
|
3515
|
3554
|
public function toHTML(MDState $state): string {
|
|
3516
|
|
- return "<code" . $this->htmlAttributes() . ">" . MDUtils::escapeHTML($this->text) . "</code>";
|
|
|
3555
|
+ return "<code" . $this->htmlAttributes() . ">" . htmlentities($this->text) . "</code>";
|
|
3517
|
3556
|
}
|
|
3518
|
3557
|
}
|
|
3519
|
3558
|
|
|
|
@@ -3551,7 +3590,7 @@ class MDFootnoteNode extends MDInlineNode {
|
|
3551
|
3590
|
}
|
|
3552
|
3591
|
|
|
3553
|
3592
|
public function toHTML(MDState $state): string {
|
|
3554
|
|
- if ($this->differentiator !== null) {
|
|
|
3593
|
+ if ($this->footnoteId !== null) {
|
|
3555
|
3594
|
return "<sup class=\"footnote\" id=\"{$state->root()->elementIdPrefix}footnoteref_{$this->occurrenceId}\"" . $this->htmlAttributes() . ">" .
|
|
3556
|
3595
|
"<a href=\"#{$state->root()->elementIdPrefix}footnote_{$this->footnoteId}\">" . htmlentities($this->displaySymbol ?? $this->symbol) . "</a></sup>";
|
|
3557
|
3596
|
}
|
|
|
@@ -3603,7 +3642,7 @@ class MDReferencedLinkNode extends MDLinkNode {
|
|
3603
|
3642
|
$title = $state->urlTitleForReference($this->reference);
|
|
3604
|
3643
|
if ($title) $this->attributes['title'] = $title;
|
|
3605
|
3644
|
}
|
|
3606
|
|
- return $super->toHTML($state);
|
|
|
3645
|
+ return parent::toHTML($state);
|
|
3607
|
3646
|
}
|
|
3608
|
3647
|
}
|
|
3609
|
3648
|
|
|
|
@@ -3616,7 +3655,7 @@ class MDImageNode extends MDInlineNode {
|
|
3616
|
3655
|
public ?string $alt;
|
|
3617
|
3656
|
|
|
3618
|
3657
|
public function __construct(string $src, ?string $alt) {
|
|
3619
|
|
- super([]);
|
|
|
3658
|
+ parent::__construct([]);
|
|
3620
|
3659
|
$this->src = $src;
|
|
3621
|
3660
|
$this->alt = $alt;
|
|
3622
|
3661
|
}
|
|
|
@@ -3647,7 +3686,7 @@ class MDReferencedImageNode extends MDImageNode {
|
|
3647
|
3686
|
$title = $state->urlTitleForReference($this->reference);
|
|
3648
|
3687
|
if ($title !== null) $this->attributes['title'] = $title;
|
|
3649
|
3688
|
}
|
|
3650
|
|
- return super.toHTML(state);
|
|
|
3689
|
+ return parent::toHTML($state);
|
|
3651
|
3690
|
}
|
|
3652
|
3691
|
}
|
|
3653
|
3692
|
|
|
|
@@ -3663,7 +3702,7 @@ class MDAbbreviationNode extends MDInlineNode {
|
|
3663
|
3702
|
* @param {string} definition
|
|
3664
|
3703
|
*/
|
|
3665
|
3704
|
public function __construct(string $abbreviation, string $definition) {
|
|
3666
|
|
- super([]);
|
|
|
3705
|
+ parent::__construct([]);
|
|
3667
|
3706
|
$this->abbreviation = $abbreviation;
|
|
3668
|
3707
|
$this->attributes['title'] = $definition;
|
|
3669
|
3708
|
}
|
|
|
@@ -3865,6 +3904,7 @@ class Markdown {
|
|
3865
|
3904
|
* @param string $elementIdPrefix
|
|
3866
|
3905
|
*/
|
|
3867
|
3906
|
private function investigateException(array $lines, string $elementIdPrefix) {
|
|
|
3907
|
+ print("Investigating error...\n");
|
|
3868
|
3908
|
$startIndex = 0;
|
|
3869
|
3909
|
$endIndex = sizeof($lines);
|
|
3870
|
3910
|
// Keep stripping away first line until an exception stops being thrown
|
|
|
@@ -3886,7 +3926,7 @@ class Markdown {
|
|
3886
|
3926
|
}
|
|
3887
|
3927
|
}
|
|
3888
|
3928
|
$problematicMarkdown = implode("\n", array_slice($lines, $startIndex, $endIndex));
|
|
3889
|
|
- print("This portion of markdown caused an unexpected exception: {$problematicMarkdown}");
|
|
|
3929
|
+ print("This portion of markdown caused an unexpected exception:\n{$problematicMarkdown}\n");
|
|
3890
|
3930
|
}
|
|
3891
|
3931
|
}
|
|
3892
|
3932
|
?>
|