Переглянути джерело

Able to parse simple markdown with PHP

main
Rocketsoup 1 рік тому
джерело
коміт
06e046bef3
1 змінених файлів з 88 додано та 85 видалено
  1. 88
    85
      php/markdown.php

+ 88
- 85
php/markdown.php Переглянути файл

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

Завантаження…
Відмінити
Зберегти