Bläddra i källkod

Readers now prioritized with compare methods instead of numbers

main
Rocketsoup 1 år sedan
förälder
incheckning
1f8c2c8285
3 ändrade filer med 390 tillägg och 184 borttagningar
  1. 363
    178
      js/markdown.js
  2. 1
    1
      js/markdown.min.js
  3. 26
    5
      testjs.html

+ 363
- 178
js/markdown.js Visa fil

581
 	/** @type {MDState|null} */
581
 	/** @type {MDState|null} */
582
 	#parent = null;
582
 	#parent = null;
583
 
583
 
584
+	/** @type {MDConfig} */
585
+	config;
586
+
584
 	/**
587
 	/**
585
-	 * Tuples of `priority:number` and `MDReader` sorted by `blockPriority`.
586
-	 * @type {Array}
588
+	 * Array of `MDReader`s sorted by block reading priority.
589
+	 * @type {MDReader[]}
587
 	 */
590
 	 */
588
 	#readersByBlockPriority = [];
591
 	#readersByBlockPriority = [];
589
 
592
 
590
 	/**
593
 	/**
591
-	 * Tuples of `priority:number` and `MDReader` sorted by `tokenizePriority`.
592
-	 * @type {Array}
594
+	 * Array of `MDReader`s sorted by tokenization priority.
595
+	 * @type {MDReader[]}
593
 	 */
596
 	 */
594
 	#readersByTokenPriority = [];
597
 	#readersByTokenPriority = [];
595
 
598
 
596
 	/**
599
 	/**
597
-	 * Tuples of `priority:number` and `MDReader` sorted by `substitutePriority`.
600
+	 * Tuples of `pass:number` and `MDReader` sorted substitution priority.
598
 	 * @type {Array}
601
 	 * @type {Array}
599
 	 */
602
 	 */
600
 	#readersBySubstitutePriority = [];
603
 	#readersBySubstitutePriority = [];
615
 
618
 
616
 	/**
619
 	/**
617
 	 * @param {string[]} lines - lines of markdown text
620
 	 * @param {string[]} lines - lines of markdown text
618
-	 * @param {Array} readersByBlockPriority - tuple arrays of priority and MDReader
619
-	 * @param {Array} readersByTokenPriority - tuple arrays of priority and MDReader
621
+	 * @param {MDConfig} config
622
+	 * @param {MDReader[]} readersByBlockPriority
623
+	 * @param {MDReader[]} readersByTokenPriority
620
 	 * @param {Array} readersBySubstitutePriority - tuple arrays of priority and MDReader
624
 	 * @param {Array} readersBySubstitutePriority - tuple arrays of priority and MDReader
621
 	 */
625
 	 */
622
 	constructor(lines,
626
 	constructor(lines,
627
+		config=null,
623
 		readersByBlockPriority=null,
628
 		readersByBlockPriority=null,
624
 		readersByTokenPriority=null,
629
 		readersByTokenPriority=null,
625
 		readersBySubstitutePriority=null) {
630
 		readersBySubstitutePriority=null) {
626
 		this.#lines = lines;
631
 		this.#lines = lines;
632
+		this.config = config;
627
 		this.#readersByBlockPriority = readersByBlockPriority
633
 		this.#readersByBlockPriority = readersByBlockPriority
628
 		this.#readersByTokenPriority = readersByTokenPriority
634
 		this.#readersByTokenPriority = readersByTokenPriority
629
 		this.#readersBySubstitutePriority = readersBySubstitutePriority
635
 		this.#readersBySubstitutePriority = readersBySubstitutePriority
639
 	copy(lines) {
645
 	copy(lines) {
640
 		let cp = new MDState(lines);
646
 		let cp = new MDState(lines);
641
 		cp.#parent = this;
647
 		cp.#parent = this;
648
+		cp.config = this.config;
642
 		return cp;
649
 		return cp;
643
 	}
650
 	}
644
 
651
 
698
 			this.p++;
705
 			this.p++;
699
 		}
706
 		}
700
 		if (!this.hasLines(1)) return null;
707
 		if (!this.hasLines(1)) return null;
701
-		for (const tuple of this.root.#readersByBlockPriority) {
702
-			var reader = tuple[1];
708
+		for (const reader of this.root.#readersByBlockPriority) {
703
 			const startP = this.p;
709
 			const startP = this.p;
704
 			const block = reader.readBlock(this);
710
 			const block = reader.readBlock(this);
705
 			if (block) {
711
 			if (block) {
759
 				continue;
765
 				continue;
760
 			}
766
 			}
761
 			var found = false;
767
 			var found = false;
762
-			for (const readerTuple of this.root.#readersByTokenPriority) {
763
-				/** @type {number} */
764
-				const priority = readerTuple[0];
765
-				/** @type {MDReader} */
766
-				const reader = readerTuple[1];
768
+			for (const reader of this.root.#readersByTokenPriority) {
767
 				const token = reader.readToken(this, remainder);
769
 				const token = reader.readToken(this, remainder);
768
 				if (token === null) continue;
770
 				if (token === null) continue;
769
 				if (token === undefined) {
771
 				if (token === undefined) {
825
 			anyChanges = false;
827
 			anyChanges = false;
826
 			for (const readerTuple of this.root.#readersBySubstitutePriority) {
828
 			for (const readerTuple of this.root.#readersBySubstitutePriority) {
827
 				/** @type {number} */
829
 				/** @type {number} */
828
-				const priority = readerTuple[0];
830
+				const pass = readerTuple[0];
829
 				/** @type {MDReader} */
831
 				/** @type {MDReader} */
830
 				const reader = readerTuple[1];
832
 				const reader = readerTuple[1];
831
-				const changed = reader.substituteTokens(this, priority, nodes);
833
+				const changed = reader.substituteTokens(this, pass, nodes);
832
 				if (!changed) continue;
834
 				if (!changed) continue;
833
 				anyChanges = true;
835
 				anyChanges = true;
834
 				break;
836
 				break;
905
  * any combination of these.
907
  * any combination of these.
906
  * 1. **Blocks** - Processing an array of lines to find block-level structures,
908
  * 1. **Blocks** - Processing an array of lines to find block-level structures,
907
  *    such as paragraphs, lists, tables, blockquotes, etc. and converting them
909
  *    such as paragraphs, lists, tables, blockquotes, etc. and converting them
908
- *    into block-level `MDNode`s.
910
+ *    into block-level `MDNode`s. Override `readBlock`.
909
  * 2. **Inline tokens** - Carving up single lines of markdown into tokens for
911
  * 2. **Inline tokens** - Carving up single lines of markdown into tokens for
910
  *    inline formatting, such as strong, emphasis, links, images, etc.
912
  *    inline formatting, such as strong, emphasis, links, images, etc.
913
+ *    Override `readToken`.
911
  * 3. **Inline substitution** - Finding patterns of tokens and substituting them
914
  * 3. **Inline substitution** - Finding patterns of tokens and substituting them
912
- *    with `MDNode`s.
915
+ *    with `MDNode`s. Override `substituteTokens`. (`readToken` and
916
+ *   `substituteTokens` are usually overridden together.)
913
  * 
917
  * 
914
- * Each parsing phase consults each registered reader, checking if its
915
- * recognized syntax is located in a given place in the markdown. The order
916
- * the readers are tested is affected by priority values for each phase. This
917
- * allows syntactic ambiguities to be resolved in a consistent way (e.g.
918
- * `\*\*strong\*\*` and `\*emphasis\*` using similar syntax).
918
+ * Readers may have similar, ambiguous syntax (such as `**strong**` and
919
+ * `*emphasis*`) and need to process in a certain order. This can be done by
920
+ * overriding the `compare` methods to influence which readers to put before
921
+ * others in each phase. Furthermore, substitution can occur in multiple passes
922
+ * if necessary. These two mechanisms can be used to resolve ambiguities.
919
  */
923
  */
920
 class MDReader {
924
 class MDReader {
921
 	/**
925
 	/**
922
-	 * Block reading priority for this reader. Priority is a unitless relative
923
-	 * value used for sorting readers in the block reading process. Core readers
924
-	 * use a range of 0 to 100 but any value is permitted. Generally, more
925
-	 * distinctive, unambiguous block syntax should be prioritized first (low
926
-	 * priority number), while more general block syntax like paragraphs should
927
-	 * be prioritized last (high priority number). If the reader has no block-level
928
-	 * elements, can be set to `null` to skip during that phase.
929
-	 *
930
-	 * @type {number|null}
931
-	 */
932
-	get blockPriority() { return this.#blockPriority; };
933
-	#blockPriority;
934
-
935
-	/**
936
-	 * Inline tokenization priority for this reader. Priority is a unitless
937
-	 * relative value used for sorting readers in the tokenization process.
938
-	 * Core readers use a range of 0 to 100 but any value is permitted.
939
-	 * Generally, more distinctive, unambiguous token formats should be
940
-	 * prioritized first (low priority number), while more general, ambiguous
941
-	 * tokens should be prioritized last (high priority number). If the reader
942
-	 * has no inline-level elements, can be set to `null` to skip that phase.
943
-	 *
944
-	 * @type {number|null}
945
-	 */
946
-	get tokenizePriority() { return this.#tokenizePriority; };
947
-	#tokenizePriority;
948
-
949
-	/**
950
-	 * Inline token substitution priority/priorities for this reader. Priority
951
-	 * is a unitless relative value or values for sorting readers in the
952
-	 * tokenization process. Core readers use a range of 0 to 100 but any value
953
-	 * is permitted. Generally, more distinctive, unambiguous patterns should
954
-	 * be prioritized first (low priority number), while more general, ambiguous
955
-	 * patterns should be prioritized last (high priority number).
956
-	 * 
957
-	 * This property can be an array of numbers, in monotonically increasing
958
-	 * order. In that case, the same reader will occur 2 or more times in the
959
-	 * prioritization list. This can be useful when a reader handles multiple
960
-	 * patterns of differing priority. When `substituteTokens` is called, the
961
-	 * current priority is passed to differentiate which pass is being done.
962
-	 * 
963
-	 * If the reader has no inline-level elements, can be set to `null` to skip
964
-	 * that phase.
965
-	 *
966
-	 * @type {number|number[]|null}
967
-	 */
968
-	get substitutePriority() { return this.#substitutePriority; };
969
-	#substitutePriority;
970
-
971
-	constructor(blockPriority=100, tokenizePriority=100, substitutePriority=100) {
972
-		this.#blockPriority = blockPriority;
973
-		this.#tokenizePriority = tokenizePriority;
974
-		this.#substitutePriority = substitutePriority;
975
-	}
976
-
977
-	/**
978
 	 * Called before processing begins. `state.lines` is populated and the
926
 	 * Called before processing begins. `state.lines` is populated and the
979
 	 * line pointer `state.p` will be at `0`. Default implementation does nothing.
927
 	 * line pointer `state.p` will be at `0`. Default implementation does nothing.
980
 	 *
928
 	 *
1011
 	 * with one or more `MDNode` subclass instances.
959
 	 * with one or more `MDNode` subclass instances.
1012
 	 * 
960
 	 * 
1013
 	 * @param {MDState} state
961
 	 * @param {MDState} state
1014
-	 * @param {number} priority
962
+	 * @param {number} pass - what substitution pass this is, starting with 1
1015
 	 * @param {Array} tokens - mixed array of `MDToken` and `MDInlineNode` elements
963
 	 * @param {Array} tokens - mixed array of `MDToken` and `MDInlineNode` elements
1016
 	 * @returns {boolean} `true` if a substitution was performed, `false` if not
964
 	 * @returns {boolean} `true` if a substitution was performed, `false` if not
1017
 	 */
965
 	 */
1018
-	substituteTokens(state, priority, tokens) { return false; }
966
+	substituteTokens(state, pass, tokens) { return false; }
1019
 
967
 
1020
 	/**
968
 	/**
1021
 	 * Called after all parsing has completed. An array `blocks` is passed of all
969
 	 * Called after all parsing has completed. An array `blocks` is passed of all
1030
 	 * @param {MDBlockNode[]} blocks
978
 	 * @param {MDBlockNode[]} blocks
1031
 	 */
979
 	 */
1032
 	postProcess(state, blocks) {}
980
 	postProcess(state, blocks) {}
981
+
982
+	/**
983
+	 * @param {MDReader} other
984
+	 * @returns {number} -1 if this should be before other, 0 if the same or don't care, 1 if this should be after other
985
+	 */
986
+	compareBlockOrdering(other) {
987
+		return 0;
988
+	}
989
+
990
+	/**
991
+	 * @param {MDReader} other
992
+	 * @returns {number}
993
+	 */
994
+	compareTokenizeOrdering(other) {
995
+		return 0;
996
+	}
997
+
998
+	/**
999
+	 * @param {MDReader} other
1000
+	 * @param {number} pass
1001
+	 * @returns {number}
1002
+	 */
1003
+	compareSubstituteOrdering(other, pass) {
1004
+		return 0;
1005
+	}
1006
+
1007
+	get substitutionPassCount() { return 1; }
1008
+
1009
+	/**
1010
+	 * For sorting readers with ordering preferences. The `compare` methods
1011
+	 * don't have the properties of normal sorting compares so need to sort
1012
+	 * differently.
1013
+	 *
1014
+	 * @param {MDReader[]} arr - array to sort
1015
+	 * @param {function} compareFn - comparison function, taking two array element
1016
+	 *   arguments and returning -1, 0, or 1 for a < b, a == b, and a > b,
1017
+	 *   respectively
1018
+	 * @param {function} idFn - function for returning a unique hashable id for
1019
+	 *   the array element
1020
+	 * @returns {MDReader[]} sorted array
1021
+	 */
1022
+	static #kahnTopologicalSort(arr, compareFn, idFn) {
1023
+		const graph = {};
1024
+		const inDegrees = {};
1025
+		const valuesById = {};
1026
+	
1027
+		// Build the graph and compute in-degrees
1028
+		for (const elem of arr) {
1029
+			const id = idFn(elem);
1030
+			graph[id] = [];
1031
+			inDegrees[id] = 0;
1032
+			valuesById[id] = elem;
1033
+		}
1034
+	
1035
+		for (let i = 0; i < arr.length; i++) {
1036
+			const elemA = arr[i];
1037
+			const idA = idFn(elemA);
1038
+			for (let j = 0; j < arr.length; j++) {
1039
+				if (i === j) continue;
1040
+				const elemB = arr[j];
1041
+				const idB = idFn(elemB);
1042
+				const comparisonResult = compareFn(elemA, elemB);
1043
+				if (comparisonResult < 0) {
1044
+					graph[idA].push(idB);
1045
+					inDegrees[idB]++;
1046
+				} else if (comparisonResult > 0) {
1047
+					graph[idB].push(idA);
1048
+					inDegrees[idA]++;
1049
+				}
1050
+			}
1051
+		}
1052
+	
1053
+		// Initialize the queue with zero-inDegree nodes
1054
+		const queue = [];
1055
+		for (const elemId in inDegrees) {
1056
+			if (inDegrees[elemId] === 0) {
1057
+				queue.push(elemId);
1058
+			}
1059
+		}
1060
+	
1061
+		// Process the queue and build the topological order list
1062
+		const sorted = [];
1063
+		while (queue.length > 0) {
1064
+			const elemId = queue.shift();
1065
+			sorted.push(valuesById[elemId]);
1066
+			delete valuesById[elemId];
1067
+	
1068
+			for (const neighbor of graph[elemId]) {
1069
+				inDegrees[neighbor]--;
1070
+				if (inDegrees[neighbor] === 0) {
1071
+					queue.push(neighbor);
1072
+				}
1073
+			}
1074
+		}
1075
+		// Anything left over can go at the end. No ordering dependencies.
1076
+		for (const elemId in valuesById) {
1077
+			sorted.push(valuesById[elemId]);
1078
+		}
1079
+	
1080
+		return sorted;
1081
+	}
1082
+
1083
+	/**
1084
+	 * @param {MDReader[]} readers
1085
+	 * @returns {MDReader[]}
1086
+	 */
1087
+	static sortReaderForBlocks(readers) {
1088
+		const sorted = readers.slice();
1089
+		return MDReader.#kahnTopologicalSort(sorted, (a, b) => {
1090
+			return a.compareBlockOrdering(b);
1091
+			// if (ab != 0) return ab;
1092
+			// return -b.compareBlockOrdering(a);
1093
+		}, (elem) => elem.constructor.name);
1094
+	}
1095
+
1096
+	/**
1097
+	 * @param {MDReader[]} readers
1098
+	 * @returns {MDReader[]}
1099
+	 */
1100
+	static sortReadersForTokenizing(readers) {
1101
+		const sorted = readers.slice();
1102
+		return MDReader.#kahnTopologicalSort(sorted, (a, b) => {
1103
+			return a.compareTokenizeOrdering(b);
1104
+			// if (ab != 0) return ab;
1105
+			// return -b.compareTokenizeOrdering(a);
1106
+		}, (elem) => elem.constructor.name);
1107
+	}
1108
+
1109
+	/**
1110
+	 * @param {MDReader[]} readers
1111
+	 * @returns {MDReader[]}
1112
+	 */
1113
+	static sortReadersForSubstitution(readers) {
1114
+		var tuples = [];
1115
+		var maxPass = 1;
1116
+		for (const reader of readers) {
1117
+			const passCount = reader.substitutionPassCount;
1118
+			for (var pass = 1; pass <= passCount; pass++) {
1119
+				tuples.push([ pass, reader ]);
1120
+			}
1121
+			maxPass = Math.max(maxPass, pass);
1122
+		}
1123
+		var result = [];
1124
+		for (var pass = 1; pass <= maxPass; pass++) {
1125
+			var readersThisPass = tuples.filter((tup) => tup[0] == pass);
1126
+			const passResult = MDReader.#kahnTopologicalSort(readersThisPass, (a, b) => {
1127
+				const aReader = a[1];
1128
+				const bReader = b[1];
1129
+				return aReader.compareSubstituteOrdering(bReader, pass);
1130
+			}, (elem) => `${elem[1].constructor.name}:${elem[0]}`);
1131
+			result = result.concat(passResult);
1132
+		}
1133
+		return result;
1134
+	}
1033
 }
1135
 }
1034
 
1136
 
1035
 /**
1137
 /**
1118
 		if (modifier) modifier.applyTo(block);
1220
 		if (modifier) modifier.applyTo(block);
1119
 		return block;
1221
 		return block;
1120
 	}
1222
 	}
1223
+
1224
+	compareBlockOrdering(other) {
1225
+		if (other instanceof MDUnorderedListReader) {
1226
+			return -1;
1227
+		}
1228
+		return 0;
1229
+	}
1121
 }
1230
 }
1122
 
1231
 
1123
 /**
1232
 /**
1410
 		}
1519
 		}
1411
 		return null;
1520
 		return null;
1412
 	}
1521
 	}
1522
+
1523
+	compareBlockOrdering(other) {
1524
+		if (other instanceof MDUnorderedListReader) {
1525
+			return -1;
1526
+		}
1527
+		return 0;
1528
+	}
1413
 }
1529
 }
1414
 
1530
 
1415
 /**
1531
 /**
1650
 		return null;
1766
 		return null;
1651
 	}
1767
 	}
1652
 
1768
 
1653
-	substituteTokens(state, priority, tokens) {
1769
+	substituteTokens(state, pass, tokens) {
1654
 		var match;
1770
 		var match;
1655
 		if (match = MDToken.findFirstTokens(tokens, [ MDTokenType.Footnote ])) {
1771
 		if (match = MDToken.findFirstTokens(tokens, [ MDTokenType.Footnote ])) {
1656
 			let symbol = match.tokens[0].content;
1772
 			let symbol = match.tokens[0].content;
1679
 		if (Object.keys(state.footnotes).length == 0) return;
1795
 		if (Object.keys(state.footnotes).length == 0) return;
1680
 		blocks.push(new MDFootnoteListNode());
1796
 		blocks.push(new MDFootnoteListNode());
1681
 	}
1797
 	}
1798
+
1799
+	compareBlockOrdering(other) {
1800
+		if (other instanceof MDLinkReader || other instanceof MDImageReader) {
1801
+			return -1;
1802
+		}
1803
+		return 0;
1804
+	}
1805
+
1806
+	compareTokenizeOrdering(other) {
1807
+		if (other instanceof MDLinkReader || other instanceof MDImageReader) {
1808
+			return -1;
1809
+		}
1810
+		return 0;
1811
+	}
1812
+
1813
+	compareSubstituteOrdering(other, pass) {
1814
+		if (other instanceof MDLinkReader || other instanceof MDImageReader) {
1815
+			return -1;
1816
+		}
1817
+		return 0;
1818
+	}
1682
 }
1819
 }
1683
 
1820
 
1684
 /**
1821
 /**
1757
 
1894
 
1758
 /**
1895
 /**
1759
  * Block reader for simple paragraphs. Paragraphs are separated by a blank (or
1896
  * Block reader for simple paragraphs. Paragraphs are separated by a blank (or
1760
- * whitespace-only) line. This reader should be prioritized last since there
1761
- * is no distinguishing syntax.
1762
- * 
1763
- * Example:
1764
- * 
1765
- * > ```markdown
1766
- * > Lorem ipsum dolor
1767
- * > sit amet. This is all one paragraph.
1768
- * >
1769
- * > Beginning of a new paragraph.
1770
- * > ```
1897
+ * whitespace-only) line. This reader is prioritized after every other reader
1898
+ * since there is no distinguishing syntax.
1771
  */
1899
  */
1772
 class MDParagraphReader extends MDReader {
1900
 class MDParagraphReader extends MDReader {
1773
 	readBlock(state) {
1901
 	readBlock(state) {
1791
 		}
1919
 		}
1792
 		return null;
1920
 		return null;
1793
 	}
1921
 	}
1922
+
1923
+	compareBlockOrdering(other) {
1924
+		return 1; // always dead last
1925
+	}
1794
 }
1926
 }
1795
 
1927
 
1796
 /**
1928
 /**
1798
  * around some content.
1930
  * around some content.
1799
  */
1931
  */
1800
 class MDSimplePairInlineReader extends MDReader {
1932
 class MDSimplePairInlineReader extends MDReader {
1933
+	get substitutionPassCount() { return 4; }
1934
+
1801
 	/**
1935
 	/**
1802
 	 * Attempts a substitution of a matched pair of delimiting token types.
1936
 	 * Attempts a substitution of a matched pair of delimiting token types.
1803
 	 * If successful, the substitution is performed on `tokens` and `true` is
1937
 	 * If successful, the substitution is performed on `tokens` and `true` is
1804
 	 * returned, otherwise `false` is returned and the array is untouched.
1938
 	 * returned, otherwise `false` is returned and the array is untouched.
1805
 	 * 
1939
 	 * 
1806
-	 * If multiple `substitutePriority` values are specified, the first pass
1940
+	 * If `this.substitutionPassCount` is greater than 1, the first pass
1807
 	 * will reject matches with the delimiting character inside the content
1941
 	 * will reject matches with the delimiting character inside the content
1808
-	 * tokens. If a single `substitutePriority` is given or a subsequent pass
1809
-	 * is performed with multiple values any contents will be accepted.
1942
+	 * tokens. If the reader uses a single pass or a subsequent pass is performed
1943
+	 * with multiple pass any contents will be accepted.
1810
 	 * 
1944
 	 * 
1811
 	 * @param {MDState} state
1945
 	 * @param {MDState} state
1812
-	 * @param {number} priority
1946
+	 * @param {number} pass
1813
 	 * @param {MDToken[]} tokens
1947
 	 * @param {MDToken[]} tokens
1814
 	 * @param {class} nodeClass
1948
 	 * @param {class} nodeClass
1815
 	 * @param {MDTokenType} delimiter
1949
 	 * @param {MDTokenType} delimiter
1816
 	 * @param {number} count - how many times the token is repeated to form the delimiter
1950
 	 * @param {number} count - how many times the token is repeated to form the delimiter
1817
 	 * @returns {boolean} `true` if substitution performed, `false` if not
1951
 	 * @returns {boolean} `true` if substitution performed, `false` if not
1818
 	 */
1952
 	 */
1819
-	attemptPair(state, priority, tokens, nodeClass, delimiter, count=1, plaintext=false) {
1953
+	attemptPair(state, pass, tokens, nodeClass, delimiter, count=1, plaintext=false) {
1954
+		// We do four passes. #1: doubles without inner tokens, #2: singles
1955
+		// without inner tokens, #3: doubles with paired inner tokens,
1956
+		// #4: singles with paired inner tokens
1957
+		if (count == 1 && pass != 2 && pass != 4) return;
1958
+		if (count == 2 && pass != 1 && pass != 3) return;
1820
 		let delimiters = Array(count).fill(delimiter);
1959
 		let delimiters = Array(count).fill(delimiter);
1821
-		let firstPassPriority = (this.substitutePriority instanceof Array) ? this.substitutePriority[0] : null;
1960
+		const isFirstOfMultiplePasses = this.substitutionPassCount > 1 && pass == 1;
1822
 		let match = MDToken.findPairedTokens(tokens, delimiters, delimiters, function(content) {
1961
 		let match = MDToken.findPairedTokens(tokens, delimiters, delimiters, function(content) {
1823
 			const firstType = content[0] instanceof MDToken ? content[0].type : null;
1962
 			const firstType = content[0] instanceof MDToken ? content[0].type : null;
1824
 			const lastType = content[content.length - 1] instanceof MDToken ? content[content.length - 1].type : null;
1963
 			const lastType = content[content.length - 1] instanceof MDToken ? content[content.length - 1].type : null;
1825
 			if (firstType == MDTokenType.Whitespace) return false;
1964
 			if (firstType == MDTokenType.Whitespace) return false;
1826
 			if (lastType == MDTokenType.Whitespace) return false;
1965
 			if (lastType == MDTokenType.Whitespace) return false;
1827
-			if (priority == firstPassPriority) {
1966
+			for (const token of content) {
1967
+				// Don't allow nesting
1968
+				if (token.constructor == nodeClass) return false;
1969
+			}
1970
+			if (isFirstOfMultiplePasses) {
1828
 				var innerCount = 0;
1971
 				var innerCount = 0;
1829
 				for (let token of content) {
1972
 				for (let token of content) {
1830
 					if (token instanceof MDToken && token.type == delimiter) innerCount++;
1973
 					if (token instanceof MDToken && token.type == delimiter) innerCount++;
1849
 		return null;
1992
 		return null;
1850
 	}
1993
 	}
1851
 
1994
 
1852
-	substituteTokens(state, priority, tokens) {
1853
-		if (this.attemptPair(state, priority, tokens, MDEmphasisNode, MDTokenType.Asterisk)) return true;
1854
-		if (this.attemptPair(state, priority, tokens, MDEmphasisNode, MDTokenType.Underscore)) return true;
1995
+	substituteTokens(state, pass, tokens) {
1996
+		if (this.attemptPair(state, pass, tokens, MDEmphasisNode, MDTokenType.Asterisk)) return true;
1997
+		if (this.attemptPair(state, pass, tokens, MDEmphasisNode, MDTokenType.Underscore)) return true;
1855
 		return false;
1998
 		return false;
1856
 	}
1999
 	}
2000
+
2001
+	compareSubstituteOrdering(other, pass) {
2002
+		if (other instanceof MDStrongReader) {
2003
+			return 1;
2004
+		}
2005
+		return 0;
2006
+	}
1857
 }
2007
 }
1858
 
2008
 
1859
 class MDStrongReader extends MDSimplePairInlineReader {
2009
 class MDStrongReader extends MDSimplePairInlineReader {
1863
 		return null;
2013
 		return null;
1864
 	}
2014
 	}
1865
 
2015
 
1866
-	substituteTokens(state, priority, tokens) {
1867
-		if (this.attemptPair(state, priority, tokens, MDStrongNode, MDTokenType.Asterisk, 2)) return true;
1868
-		if (this.attemptPair(state, priority, tokens, MDStrongNode, MDTokenType.Underscore, 2)) return true;
2016
+	substituteTokens(state, pass, tokens) {
2017
+		if (this.attemptPair(state, pass, tokens, MDStrongNode, MDTokenType.Asterisk, 2)) return true;
2018
+		if (this.attemptPair(state, pass, tokens, MDStrongNode, MDTokenType.Underscore, 2)) return true;
1869
 		return false;
2019
 		return false;
1870
 	}
2020
 	}
2021
+
2022
+	compareSubstituteOrdering(other, pass) {
2023
+		if (other instanceof MDEmphasisReader) {
2024
+			return -1;
2025
+		}
2026
+		return 0;
2027
+	}
1871
 }
2028
 }
1872
 
2029
 
1873
 class MDStrikethroughReader extends MDSimplePairInlineReader {
2030
 class MDStrikethroughReader extends MDSimplePairInlineReader {
1876
 		return null;
2033
 		return null;
1877
 	}
2034
 	}
1878
 
2035
 
1879
-	substituteTokens(state, priority, tokens) {
1880
-		if (this.attemptPair(state, priority, tokens, MDStrikethroughNode, MDTokenType.Tilde, 2)) return true;
1881
-		if (this.attemptPair(state, priority, tokens, MDStrikethroughNode, MDTokenType.Tilde)) return true;
2036
+	substituteTokens(state, pass, tokens) {
2037
+		if (this.attemptPair(state, pass, tokens, MDStrikethroughNode, MDTokenType.Tilde, 2)) return true;
2038
+		if (state.config.strikethroughSingleTildeEnabled) {
2039
+			if (this.attemptPair(state, pass, tokens, MDStrikethroughNode, MDTokenType.Tilde)) return true;
2040
+		}
1882
 		return false;
2041
 		return false;
1883
 	}
2042
 	}
1884
 }
2043
 }
1889
 		return null;
2048
 		return null;
1890
 	}
2049
 	}
1891
 
2050
 
1892
-	substituteTokens(state, priority, tokens) {
1893
-		return this.attemptPair(state, priority, tokens, MDUnderlineNode, MDTokenType.Underscore, 2);
2051
+	substituteTokens(state, pass, tokens) {
2052
+		return this.attemptPair(state, pass, tokens, MDUnderlineNode, MDTokenType.Underscore, 2);
2053
+	}
2054
+
2055
+	compareSubstituteOrdering(other, pass) {
2056
+		if (other instanceof MDStrongReader) {
2057
+			return -1;
2058
+		}
2059
+		return 0;
1894
 	}
2060
 	}
1895
 }
2061
 }
1896
 
2062
 
1900
 		return null;
2066
 		return null;
1901
 	}
2067
 	}
1902
 
2068
 
1903
-	substituteTokens(state, priority, tokens) {
1904
-		return this.attemptPair(state, priority, tokens, MDHighlightNode, MDTokenType.Equal, 2);
2069
+	substituteTokens(state, pass, tokens) {
2070
+		return this.attemptPair(state, pass, tokens, MDHighlightNode, MDTokenType.Equal, 2);
1905
 	}
2071
 	}
1906
 }
2072
 }
1907
 
2073
 
1909
 	static #simpleEmailRegex = new RegExp("^<(" + MDUtils.baseEmailRegex.source + ")>", "i");  // 1=email
2075
 	static #simpleEmailRegex = new RegExp("^<(" + MDUtils.baseEmailRegex.source + ")>", "i");  // 1=email
1910
 	static #simpleURLRegex = new RegExp("^<(" + MDUtils.baseURLRegex.source + ")>", "i");  // 1=URL
2076
 	static #simpleURLRegex = new RegExp("^<(" + MDUtils.baseURLRegex.source + ")>", "i");  // 1=URL
1911
 
2077
 
1912
-	constructor(tokenizePriority=0.0, substitutePriority=0.0) {
1913
-		super(tokenizePriority, substitutePriority);
1914
-	}
1915
-
1916
 	readToken(state, line) {
2078
 	readToken(state, line) {
1917
 		var groups;
2079
 		var groups;
1918
 		if (groups = MDUtils.tokenizeLabel(line)) {
2080
 		if (groups = MDUtils.tokenizeLabel(line)) {
1933
 		return null;
2095
 		return null;
1934
 	}
2096
 	}
1935
 
2097
 
1936
-	substituteTokens(state, priority, tokens) {
2098
+	substituteTokens(state, pass, tokens) {
1937
 		var match;
2099
 		var match;
1938
 		if (match = MDToken.findFirstTokens(tokens, [ MDTokenType.Label, MDTokenType.META_OptionalWhitespace, MDTokenType.URL ])) {
2100
 		if (match = MDToken.findFirstTokens(tokens, [ MDTokenType.Label, MDTokenType.META_OptionalWhitespace, MDTokenType.URL ])) {
1939
 			let text = match.tokens[0].content;
2101
 			let text = match.tokens[0].content;
2009
 		return new MDNode([]); // empty
2171
 		return new MDNode([]); // empty
2010
 	}
2172
 	}
2011
 
2173
 
2012
-	substituteTokens(state, priority, tokens) {
2174
+	substituteTokens(state, pass, tokens) {
2013
 		var match;
2175
 		var match;
2014
 		if (match = MDToken.findFirstTokens(tokens, [ MDTokenType.Label, MDTokenType.META_OptionalWhitespace, MDTokenType.Label ])) {
2176
 		if (match = MDToken.findFirstTokens(tokens, [ MDTokenType.Label, MDTokenType.META_OptionalWhitespace, MDTokenType.Label ])) {
2015
 			let text = match.tokens[0].content;
2177
 			let text = match.tokens[0].content;
2022
 }
2184
 }
2023
 
2185
 
2024
 class MDImageReader extends MDLinkReader {
2186
 class MDImageReader extends MDLinkReader {
2025
-	substituteTokens(state, priority, tokens) {}
2026
-
2027
 	readToken(state, line) {
2187
 	readToken(state, line) {
2028
 		const s = super.readToken(state, line);
2188
 		const s = super.readToken(state, line);
2029
 		if (s) return s;
2189
 		if (s) return s;
2031
 		return null;
2191
 		return null;
2032
 	}
2192
 	}
2033
 
2193
 
2034
-	substituteTokens(state, priority, tokens) {
2194
+	substituteTokens(state, pass, tokens) {
2035
 		var match;
2195
 		var match;
2036
 		if (match = MDToken.findFirstTokens(tokens, [ MDTokenType.Bang, MDTokenType.Label, MDTokenType.META_OptionalWhitespace, MDTokenType.URL ])) {
2196
 		if (match = MDToken.findFirstTokens(tokens, [ MDTokenType.Bang, MDTokenType.Label, MDTokenType.META_OptionalWhitespace, MDTokenType.URL ])) {
2037
 			let alt = match.tokens[1].content;
2197
 			let alt = match.tokens[1].content;
2052
 		}
2212
 		}
2053
 		return false;
2213
 		return false;
2054
 	}
2214
 	}
2215
+
2216
+	compareSubstituteOrdering(other, pass) {
2217
+		if (other instanceof MDLinkReader) {
2218
+			return -1;
2219
+		}
2220
+		return 0;
2221
+	}
2055
 }
2222
 }
2056
 
2223
 
2057
 class MDReferencedImageReader extends MDReferencedLinkReader {
2224
 class MDReferencedImageReader extends MDReferencedLinkReader {
2058
 	readBlock(state) { return null; }
2225
 	readBlock(state) { return null; }
2226
+
2227
+	compareSubstituteOrdering(other, pass) {
2228
+		if (other instanceof MDLinkReader) {
2229
+			return -1;
2230
+		}
2231
+		return 0;
2232
+	}
2059
 }
2233
 }
2060
 
2234
 
2061
 class MDCodeSpanReader extends MDSimplePairInlineReader {
2235
 class MDCodeSpanReader extends MDSimplePairInlineReader {
2064
 		return null;
2238
 		return null;
2065
 	}
2239
 	}
2066
 
2240
 
2067
-	substituteTokens(state, priority, tokens) {
2068
-		if (this.attemptPair(state, priority, tokens, MDCodeNode, MDTokenType.Backtick, 2, true)) return true;
2069
-		if (this.attemptPair(state, priority, tokens, MDCodeNode, MDTokenType.Backtick, 1, true)) return true;
2241
+	substituteTokens(state, pass, tokens) {
2242
+		if (this.attemptPair(state, pass, tokens, MDCodeNode, MDTokenType.Backtick, 2, true)) return true;
2243
+		if (this.attemptPair(state, pass, tokens, MDCodeNode, MDTokenType.Backtick, 1, true)) return true;
2070
 	}
2244
 	}
2071
 }
2245
 }
2072
 
2246
 
2076
 		return null;
2250
 		return null;
2077
 	}
2251
 	}
2078
 
2252
 
2079
-	substituteTokens(state, priority, tokens) {
2080
-		return this.attemptPair(state, priority, tokens, MDSubscriptNode, MDTokenType.Tilde);
2253
+	preProcess(state) {
2254
+		// Causes a conflict
2255
+		state.config.strikethroughSingleTildeEnabled = false;
2256
+	}
2257
+
2258
+	substituteTokens(state, pass, tokens) {
2259
+		return this.attemptPair(state, pass, tokens, MDSubscriptNode, MDTokenType.Tilde);
2260
+	}
2261
+
2262
+	compareSubstituteOrdering(other, pass) {
2263
+		if (other instanceof MDStrikethroughReader) {
2264
+			return -1;
2265
+		}
2266
+		return 0;
2081
 	}
2267
 	}
2082
 }
2268
 }
2083
 
2269
 
2087
 		return null;
2273
 		return null;
2088
 	}
2274
 	}
2089
 
2275
 
2090
-	substituteTokens(state, priority, tokens) {
2091
-		return this.attemptPair(state, priority, tokens, MDSuperscriptNode, MDTokenType.Caret);
2276
+	substituteTokens(state, pass, tokens) {
2277
+		return this.attemptPair(state, pass, tokens, MDSuperscriptNode, MDTokenType.Caret);
2092
 	}
2278
 	}
2093
 }
2279
 }
2094
 
2280
 
2099
 		return new MDToken(tag.original, MDTokenType.HTMLTag, null, null, tag)
2285
 		return new MDToken(tag.original, MDTokenType.HTMLTag, null, null, tag)
2100
 	}
2286
 	}
2101
 
2287
 
2102
-	substituteTokens(state, priority, tokens) {
2288
+	substituteTokens(state, pass, tokens) {
2103
 		var match;
2289
 		var match;
2104
 		if (match = MDToken.findFirstTokens(tokens, [ MDTokenType.HTMLTag ])) {
2290
 		if (match = MDToken.findFirstTokens(tokens, [ MDTokenType.HTMLTag ])) {
2105
 			const tag = match.tokens[0].tag
2291
 			const tag = match.tokens[0].tag
2117
 		return null;
2303
 		return null;
2118
 	}
2304
 	}
2119
 
2305
 
2120
-	substituteTokens(state, priority, tokens) {
2306
+	substituteTokens(state, pass, tokens) {
2121
 		// Modifiers are applied elsewhere, and if they're not it's fine if they're
2307
 		// Modifiers are applied elsewhere, and if they're not it's fine if they're
2122
 		// rendered as the original syntax.
2308
 		// rendered as the original syntax.
2123
 		return false;
2309
 		return false;
3184
 }
3370
 }
3185
 
3371
 
3186
 class MDConfig {
3372
 class MDConfig {
3187
-
3373
+	strikethroughSingleTildeEnabled = true;
3188
 }
3374
 }
3189
 
3375
 
3190
 class Markdown {
3376
 class Markdown {
3193
 	 * @type {MDReader[]}
3379
 	 * @type {MDReader[]}
3194
 	 */
3380
 	 */
3195
 	static standardReaders = [
3381
 	static standardReaders = [
3196
-		new MDUnderlinedHeaderReader(0),
3197
-		new MDHashHeaderReader(1),
3198
-		new MDBlockQuoteReader(3),
3199
-		new MDHorizontalRuleReader(9),  // prioritize before unordered list
3200
-		new MDUnorderedListReader(10),
3201
-		new MDOrderedListReader(11),
3202
-		new MDFencedCodeBlockReader(20),
3203
-		new MDIndentedCodeBlockReader(21),
3204
-		new MDParagraphReader(100),
3205
-
3206
-		new MDStrongReader(101, 10, [ 0, 2 ]),  // prioritize before emphasis (both use * and _)
3207
-		new MDEmphasisReader(101, 15, [ 5, 55 ]),
3208
-		new MDCodeSpanReader(101, 20, [ 10, 60 ]),
3209
-		new MDImageReader(101, 30, 15),  // prioritize before links
3210
-		new MDLinkReader(101, 35, 20),
3211
-		new MDHTMLTagReader(101, 80, 30),
3382
+		new MDUnderlinedHeaderReader(),
3383
+		new MDHashHeaderReader(),
3384
+		new MDBlockQuoteReader(),
3385
+		new MDHorizontalRuleReader(),
3386
+		new MDUnorderedListReader(),
3387
+		new MDOrderedListReader(),
3388
+		new MDFencedCodeBlockReader(),
3389
+		new MDIndentedCodeBlockReader(),
3390
+		new MDParagraphReader(),
3391
+
3392
+		new MDStrongReader(),
3393
+		new MDEmphasisReader(),
3394
+		new MDCodeSpanReader(),
3395
+		new MDImageReader(),
3396
+		new MDLinkReader(),
3397
+		new MDHTMLTagReader(),
3212
 	];
3398
 	];
3213
 
3399
 
3214
 	/**
3400
 	/**
3217
 	 */
3403
 	 */
3218
 	static allReaders = [
3404
 	static allReaders = [
3219
 		...this.standardReaders,
3405
 		...this.standardReaders,
3220
-		new MDSubtextReader(2),
3221
-		new MDTableReader(40),
3222
-		new MDDefinitionListReader(50),
3223
-		new MDFootnoteReader(60, 5, 40),  // prioritize before links and images
3224
-		new MDAbbreviationReader(70),
3225
-
3226
-		new MDUnderlineReader(101, 5, [ 9, 11 ]),  // prioritize before emphasis and strong (both use _)
3227
-		// new MDSubscriptReader(101, 20, [ 11, 51 ]),  // prioritize before strikethrough (both use ~)
3228
-		new MDStrikethroughReader(101, 21, [ 12, 50 ]),
3229
-		new MDHighlightReader(101, 23, [ 13, 51 ]),
3230
-		new MDSuperscriptReader(101, 24, [ 14, 54 ]),
3231
-		new MDReferencedImageReader(91, 31, 16),
3232
-		new MDReferencedLinkReader(90, 36, 21),
3233
-		new MDModifierReader(101, 90, 45),
3406
+		new MDSubtextReader(),
3407
+		new MDTableReader(),
3408
+		new MDDefinitionListReader(),
3409
+		new MDFootnoteReader(),
3410
+		new MDAbbreviationReader(),
3411
+
3412
+		new MDUnderlineReader(),
3413
+		new MDSubscriptReader(),
3414
+		new MDStrikethroughReader(),
3415
+		new MDHighlightReader(),
3416
+		new MDSuperscriptReader(),
3417
+		new MDReferencedImageReader(),
3418
+		new MDReferencedLinkReader(),
3419
+		new MDModifierReader(),
3234
 	];
3420
 	];
3235
 
3421
 
3236
 	/**
3422
 	/**
3243
 	 */
3429
 	 */
3244
 	static completeParser = new Markdown(this.allReaders);
3430
 	static completeParser = new Markdown(this.allReaders);
3245
 
3431
 
3432
+	/**
3433
+	 * @type {MDConfig}
3434
+	 */
3435
+	config;
3436
+
3246
 	#readers;
3437
 	#readers;
3247
 
3438
 
3439
+	/** @type {MDReader[]} */
3248
 	#readersByBlockPriority;
3440
 	#readersByBlockPriority;
3441
+	/** @type {MDReader[]} */
3249
 	#readersByTokenPriority;
3442
 	#readersByTokenPriority;
3443
+	/** @type {Array} */
3250
 	#readersBySubstitutePriority;
3444
 	#readersBySubstitutePriority;
3251
 
3445
 
3252
 	/**
3446
 	/**
3253
 	 * Creates a Markdown parser with the given syntax readers.
3447
 	 * Creates a Markdown parser with the given syntax readers.
3254
 	 *
3448
 	 *
3255
 	 * @param {MDReader[]} readers
3449
 	 * @param {MDReader[]} readers
3450
+	 * @param {MDConfig} config
3256
 	 */
3451
 	 */
3257
-	constructor(readers=Markdown.allReaders) {
3452
+	constructor(readers=Markdown.allReaders, config=new MDConfig()) {
3258
 		this.#readers = readers;
3453
 		this.#readers = readers;
3259
-		const duplicateAndSort = function(priorityFn) {
3260
-			var result = [];
3261
-			for (const reader of readers) {
3262
-				const p = priorityFn(reader);
3263
-				const priorities = (p instanceof Array) ? p : [ p ];
3264
-				for (const priority of priorities) {
3265
-					result.push([priority, reader]);
3266
-				}
3267
-			}
3268
-			result.sort((a, b) => a[0] - b[0]);
3269
-			return result;
3270
-		}
3271
-		this.#readersByBlockPriority = duplicateAndSort((reader) => reader.blockPriority);
3272
-		this.#readersByTokenPriority = duplicateAndSort((reader) => reader.tokenizePriority);
3273
-		this.#readersBySubstitutePriority = duplicateAndSort((reader) => reader.substitutePriority);
3454
+		this.config = config;
3455
+		this.#readersByBlockPriority = MDReader.sortReaderForBlocks(readers);
3456
+		this.#readersByTokenPriority = MDReader.sortReadersForTokenizing(readers);
3457
+		this.#readersBySubstitutePriority = MDReader.sortReadersForSubstitution(readers);
3274
 	}
3458
 	}
3275
 
3459
 
3276
 	/**
3460
 	/**
3282
 	toHTML(markdown) {
3466
 	toHTML(markdown) {
3283
 		const lines = markdown.split(/(?:\n|\r|\r\n)/);
3467
 		const lines = markdown.split(/(?:\n|\r|\r\n)/);
3284
 		const state = new MDState(lines,
3468
 		const state = new MDState(lines,
3469
+			this.config,
3285
 			this.#readersByBlockPriority,
3470
 			this.#readersByBlockPriority,
3286
 			this.#readersByTokenPriority,
3471
 			this.#readersByTokenPriority,
3287
 			this.#readersBySubstitutePriority);
3472
 			this.#readersBySubstitutePriority);

+ 1
- 1
js/markdown.min.js
Filskillnaden har hållits tillbaka eftersom den är för stor
Visa fil


+ 26
- 5
testjs.html Visa fil

735
 					this.assertEqual(actual, expected);
735
 					this.assertEqual(actual, expected);
736
 				}
736
 				}
737
 
737
 
738
-				test_strikethrough_single() {
739
-					let markdown = 'Lorem ~ipsum~ dolor';
738
+				test_strikethrough_double() {
739
+					let markdown = 'Lorem ~~ipsum~~ dolor';
740
 					let expected = 'Lorem <s>ipsum</s> dolor';
740
 					let expected = 'Lorem <s>ipsum</s> dolor';
741
 					let actual = this.md(markdown);
741
 					let actual = this.md(markdown);
742
 					this.assertEqual(actual, expected);
742
 					this.assertEqual(actual, expected);
743
 				}
743
 				}
744
 
744
 
745
-				test_strikethrough_double() {
746
-					let markdown = 'Lorem ~~ipsum~~ dolor';
747
-					let expected = 'Lorem <s>ipsum</s> dolor';
745
+				test_subscript() {
746
+					let markdown = 'H~2~O';
747
+					let expected = 'H<sub>2</sub>O';
748
+					let actual = this.md(markdown);
749
+					this.assertEqual(actual, expected);
750
+				}
751
+
752
+				test_superscript() {
753
+					let markdown = 'E=mc^2^';
754
+					let expected = 'E=mc<sup>2</sup>';
755
+					let actual = this.md(markdown);
756
+					this.assertEqual(actual, expected);
757
+				}
758
+
759
+				test_highlight() {
760
+					let markdown = 'Lorem ==ipsum== dolor';
761
+					let expected = 'Lorem <mark>ipsum</mark> dolor';
762
+					let actual = this.md(markdown);
763
+					this.assertEqual(actual, expected);
764
+				}
765
+
766
+				test_underline() {
767
+					let markdown = 'Lorem __ipsum__ dolor';
768
+					let expected = 'Lorem <u>ipsum</u> dolor';
748
 					let actual = this.md(markdown);
769
 					let actual = this.md(markdown);
749
 					this.assertEqual(actual, expected);
770
 					this.assertEqual(actual, expected);
750
 				}
771
 				}

Laddar…
Avbryt
Spara