Bläddra i källkod

Initial port of spreadsheet code. Exposing members of MDBlocks and MDSpans. Adding toPlaintext method to blocks and spans.

main
Rocketsoup 1 år sedan
förälder
incheckning
4065375ab5
3 ändrade filer med 3318 tillägg och 105 borttagningar
  1. 275
    97
      js/markdown.js
  2. 2743
    0
      js/spreadsheet.js
  3. 300
    8
      testjs.html

+ 275
- 97
js/markdown.js Visa fil

@@ -442,13 +442,19 @@ class MDUtils {
442 442
 	 * @param {any} b
443 443
 	 * @returns {boolean}
444 444
 	 */
445
-	static equal(a, b) {
445
+	static equal(a, b, floatDifferencePercent=0.0) {
446 446
 		if (a instanceof Array && b instanceof Array) {
447 447
 			return this.#equalArrays(a, b);
448 448
 		}
449 449
 		if (a instanceof Object && b instanceof Object) {
450 450
 			return this.#equalObjects(a, b);
451 451
 		}
452
+		if (typeof a == 'number' && typeof b == 'number') {
453
+			if (a === b) return true;
454
+			const delta = b - a;
455
+			const ratio = delta / a;
456
+			return Math.abs(ratio) <= floatDifferencePercent;
457
+		}
452 458
 		return a == b;
453 459
 	}
454 460
 }
@@ -1643,7 +1649,7 @@ class MDHTMLTagInlineReader extends MDInlineReader {
1643 1649
 		/** @type {MDToken} */
1644 1650
 		const token = result.tokens[0];
1645 1651
 		const tag = token.tag;
1646
-		const span = new MDHTMLSpan(tag.fullTag);
1652
+		const span = new MDHTMLSpan(tag);
1647 1653
 		tokens.splice(result.index, 1, span);
1648 1654
 		return true;
1649 1655
 	}
@@ -1681,9 +1687,18 @@ class MDBlock {
1681 1687
 
1682 1688
 	/**
1683 1689
 	 * @param {MDState} state
1690
+	 * @returns {string}
1684 1691
 	 */
1685 1692
 	toHTML(state) {
1686
-		throw new Error(`Abstract toHTML must be overridden in ${self.constructor.name}`);
1693
+		throw new Error(`Abstract ${this.constructor.name}.toHTML must be implemented`);
1694
+	}
1695
+
1696
+	/**
1697
+	 * @param {MDState} state
1698
+	 * @returns {string}
1699
+	 */
1700
+	toPlaintext(state) {
1701
+		throw new Error(`Abstract ${this.constructor.name}.toPlaintext must be implemented`);
1687 1702
 	}
1688 1703
 
1689 1704
 	htmlAttributes() {
@@ -1711,6 +1726,15 @@ class MDBlock {
1711 1726
 	}
1712 1727
 
1713 1728
 	/**
1729
+	 * @param {MDBlock[]} blocks
1730
+	 * @param {MDState} state
1731
+	 * @returns {string}
1732
+	 */
1733
+	static toPlaintext(blocks, state) {
1734
+		return blocks.map((block) => block.toPlaintext(state)).join("\n");
1735
+	}
1736
+
1737
+	/**
1714 1738
 	 * Visits all block and inline children of this block, calling the given
1715 1739
 	 * function with each. Should be implemented for any block with child nodes.
1716 1740
 	 *
@@ -1721,7 +1745,7 @@ class MDBlock {
1721 1745
 
1722 1746
 class MDMultiBlock extends MDBlock {
1723 1747
 	/** @type {MDBlock[]} */
1724
-	#blocks;
1748
+	blocks;
1725 1749
 
1726 1750
 	/**
1727 1751
 	 * @param {MDBlock[]} blocks
@@ -1729,18 +1753,22 @@ class MDMultiBlock extends MDBlock {
1729 1753
 	constructor(blocks) {
1730 1754
 		super();
1731 1755
 		if (blocks instanceof Array) {
1732
-			this.#blocks = blocks;
1756
+			this.blocks = blocks;
1733 1757
 		} else {
1734 1758
 			throw new Error(`${MDUtils.typename(this)} expects MDBlock[], got ${MDUtils.typename(blocks)}`);
1735 1759
 		}
1736 1760
 	}
1737 1761
 
1738 1762
 	toHTML(state) {
1739
-		return MDBlock.toHTML(this.#blocks, state);
1763
+		return MDBlock.toHTML(this.blocks, state);
1764
+	}
1765
+
1766
+	toPlaintext(state) {
1767
+		return MDBlock.toPlaintext(this.blocks, state);
1740 1768
 	}
1741 1769
 
1742 1770
 	visitChildren(fn) {
1743
-		for (const block of this.#blocks) {
1771
+		for (const block of this.blocks) {
1744 1772
 			fn(block);
1745 1773
 			block.visitChildren(fn);
1746 1774
 		}
@@ -1749,7 +1777,7 @@ class MDMultiBlock extends MDBlock {
1749 1777
 
1750 1778
 class MDParagraphBlock extends MDBlock {
1751 1779
 	/** @type {MDBlock[]} */
1752
-	#content;
1780
+	content;
1753 1781
 
1754 1782
 	/**
1755 1783
 	 * @param {MDBlock|MDBlock[]} content
@@ -1757,21 +1785,25 @@ class MDParagraphBlock extends MDBlock {
1757 1785
 	constructor(content) {
1758 1786
 		super();
1759 1787
 		if (content instanceof Array) {
1760
-			this.#content = content;
1788
+			this.content = content;
1761 1789
 		} else if (content instanceof MDBlock) {
1762
-			this.#content = [ content ];
1790
+			this.content = [ content ];
1763 1791
 		} else {
1764 1792
 			throw new Error(`${MDUtils.typename(this)} expects MDBlock[] or MDBlock, got ${MDUtils.typename(content)}`);
1765 1793
 		}
1766 1794
 	}
1767 1795
 
1768 1796
 	toHTML(state) {
1769
-		const contentHTML = MDBlock.toHTML(this.#content, state);
1797
+		const contentHTML = MDBlock.toHTML(this.content, state);
1770 1798
 		return `<p${this.htmlAttributes()}>${contentHTML}</p>\n`;
1771 1799
 	}
1772 1800
 
1801
+	toPlaintext(state) {
1802
+		return MDBlock.toPlaintext(this.content, state);
1803
+	}
1804
+
1773 1805
 	visitChildren(fn) {
1774
-		for (const child of this.#content) {
1806
+		for (const child of this.content) {
1775 1807
 			fn(child);
1776 1808
 			child.visitChildren(fn);
1777 1809
 		}
@@ -1780,9 +1812,9 @@ class MDParagraphBlock extends MDBlock {
1780 1812
 
1781 1813
 class MDHeaderBlock extends MDBlock {
1782 1814
 	/** @type {number} */
1783
-	#level;
1815
+	level;
1784 1816
 	/** @type {MDBlock[]} */
1785
-	#content;
1817
+	content;
1786 1818
 
1787 1819
 	/**
1788 1820
 	 * @param {number} level
@@ -1790,17 +1822,21 @@ class MDHeaderBlock extends MDBlock {
1790 1822
 	 */
1791 1823
 	constructor(level, content) {
1792 1824
 		super();
1793
-		this.#level = level;
1794
-		this.#content = (content instanceof Array) ? content : [ content ];
1825
+		this.level = level;
1826
+		this.content = (content instanceof Array) ? content : [ content ];
1795 1827
 	}
1796 1828
 
1797 1829
 	toHTML(state) {
1798
-		let contentHTML = MDBlock.toHTML(this.#content, state);
1799
-		return `<h${this.#level}${this.htmlAttributes()}>${contentHTML}</h${this.#level}>\n`;
1830
+		let contentHTML = MDBlock.toHTML(this.content, state);
1831
+		return `<h${this.level}${this.htmlAttributes()}>${contentHTML}</h${this.level}>\n`;
1832
+	}
1833
+
1834
+	toPlaintext(state) {
1835
+		return MDBlock.toPlaintext(this.content, state);
1800 1836
 	}
1801 1837
 
1802 1838
 	visitChildren(fn) {
1803
-		for (const child of this.#content) {
1839
+		for (const child of this.content) {
1804 1840
 			fn(child);
1805 1841
 			child.visitChildren(fn);
1806 1842
 		}
@@ -1824,6 +1860,10 @@ class MDBlockquoteBlock extends MDBlock {
1824 1860
 		return `<blockquote${this.htmlAttributes()}>\n${contentHTML}\n</blockquote>`;
1825 1861
 	}
1826 1862
 
1863
+	toPlaintext(state) {
1864
+		return MDBlock.toPlaintext(this.content, state);
1865
+	}
1866
+
1827 1867
 	visitChildren(fn) {
1828 1868
 		for (const block of this.content) {
1829 1869
 			fn(block);
@@ -1849,6 +1889,10 @@ class MDUnorderedListBlock extends MDBlock {
1849 1889
 		return `<ul${this.htmlAttributes()}>\n${contentHTML}\n</ul>`;
1850 1890
 	}
1851 1891
 
1892
+	toPlaintext(state) {
1893
+		return MDBlock.toPlaintext(this.items, state);
1894
+	}
1895
+
1852 1896
 	visitChildren(fn) {
1853 1897
 		for (const item of this.items) {
1854 1898
 			fn(item);
@@ -1886,6 +1930,10 @@ class MDOrderedListBlock extends MDBlock {
1886 1930
 		return `<ol${this.htmlAttributes()}>\n${contentHTML}\n</ol>`;
1887 1931
 	}
1888 1932
 
1933
+	toPlaintext(state) {
1934
+		return MDBlock.toPlaintext(this.items, state);
1935
+	}
1936
+
1889 1937
 	visitChildren(fn) {
1890 1938
 		for (const item of this.items) {
1891 1939
 			fn(item);
@@ -1895,45 +1943,60 @@ class MDOrderedListBlock extends MDBlock {
1895 1943
 }
1896 1944
 
1897 1945
 class MDListItemBlock extends MDBlock {
1898
-	/** @type {MDBlock} */
1946
+	/** @type {MDBlock[]} */
1899 1947
 	content;
1900 1948
 	/** @type {number|null} */
1901 1949
 	ordinal;
1902 1950
 
1903 1951
 	/**
1904
-	 * @param {MDBlock} content
1952
+	 * @param {MDBlock|MDBlock[]} content
1953
+	 * @param {number|null} ordinal
1905 1954
 	 */
1906 1955
 	constructor(content, ordinal=null) {
1907 1956
 		super();
1908
-		this.content = content;
1957
+		if (content instanceof Array) {
1958
+			this.content = content;
1959
+		} else {
1960
+			this.content = [ content ];
1961
+		}
1909 1962
 		this.ordinal = ordinal;
1910 1963
 	}
1911 1964
 
1912 1965
 	toHTML(state) {
1913
-		let contentHTML = this.content.toHTML(state);
1966
+		let contentHTML = MDBlock.toHTML(this.content, state);
1914 1967
 		return `<li${this.htmlAttributes()}>${contentHTML}</li>`;
1915 1968
 	}
1916 1969
 
1970
+	toPlaintext(state) {
1971
+		return MDBlock.toPlaintext(this.content, state);
1972
+	}
1973
+
1917 1974
 	visitChildren(fn) {
1918
-		fn(this.content);
1919
-		this.content.visitChildren(fn);
1975
+		for (const child of this.content) {
1976
+			fn(child);
1977
+			child.visitChildren(fn);
1978
+		}
1920 1979
 	}
1921 1980
 }
1922 1981
 
1923 1982
 class MDCodeBlock extends MDBlock {
1924 1983
 	/** @type {string} */
1925
-	#code;
1984
+	code;
1926 1985
 
1927 1986
 	/**
1928 1987
 	 * @param {string} code
1929 1988
 	 */
1930 1989
 	constructor(code) {
1931 1990
 		super();
1932
-		this.#code = code;
1991
+		this.code = code;
1933 1992
 	}
1934 1993
 
1935 1994
 	toHTML(state) {
1936
-		return `<pre${this.htmlAttributes()}><code>${MDUtils.escapeHTML(this.#code)}</code></pre>`;
1995
+		return `<pre${this.htmlAttributes()}><code>${MDUtils.escapeHTML(this.code)}</code></pre>`;
1996
+	}
1997
+
1998
+	toPlaintext(state) {
1999
+		return this.code;
1937 2000
 	}
1938 2001
 }
1939 2002
 
@@ -1941,6 +2004,10 @@ class MDHorizontalRuleBlock extends MDBlock {
1941 2004
 	toHTML(state) {
1942 2005
 		return `<hr${this.htmlAttributes()}>\n`;
1943 2006
 	}
2007
+
2008
+	toPlaintext(state) {
2009
+		return '';
2010
+	}
1944 2011
 }
1945 2012
 
1946 2013
 class MDTableCellBlock extends MDBlock {
@@ -1949,7 +2016,7 @@ class MDTableCellBlock extends MDBlock {
1949 2016
 	static AlignRight = 'right';
1950 2017
 
1951 2018
 	/** @type {MDBlock} */
1952
-	#content;
2019
+	content;
1953 2020
 	/** @type {string|null} */
1954 2021
 	align = null;
1955 2022
 
@@ -1958,7 +2025,7 @@ class MDTableCellBlock extends MDBlock {
1958 2025
 	 */
1959 2026
 	constructor(content) {
1960 2027
 		super();
1961
-		this.#content = content;
2028
+		this.content = content;
1962 2029
 	}
1963 2030
 
1964 2031
 	#alignAttribute() {
@@ -1977,13 +2044,17 @@ class MDTableCellBlock extends MDBlock {
1977 2044
 	}
1978 2045
 
1979 2046
 	toHTML(state) {
1980
-		let contentHTML = this.#content.toHTML(state);
2047
+		let contentHTML = this.content.toHTML(state);
1981 2048
 		return `<td${this.htmlAttributes()}>${contentHTML}</td>`;
1982 2049
 	}
1983 2050
 
2051
+	toPlaintext(state) {
2052
+		return this.content.toPlaintext(state);
2053
+	}
2054
+
1984 2055
 	visitChildren(fn) {
1985
-		fn(this.#content);
1986
-		this.#content.visitChildren(fn);
2056
+		fn(this.content);
2057
+		this.content.visitChildren(fn);
1987 2058
 	}
1988 2059
 }
1989 2060
 
@@ -1997,34 +2068,38 @@ class MDTableHeaderCellBlock extends MDTableCellBlock {
1997 2068
 
1998 2069
 class MDTableRowBlock extends MDBlock {
1999 2070
 	/** @type {MDTableCellBlock[]|MDTableHeaderCellBlock[]} */
2000
-	#cells;
2071
+	cells;
2001 2072
 
2002 2073
 	/**
2003 2074
 	 * @param {MDTableCellBlock[]|MDTableHeaderCellBlock[]} cells
2004 2075
 	 */
2005 2076
 	constructor(cells) {
2006 2077
 		super();
2007
-		this.#cells = cells;
2078
+		this.cells = cells;
2008 2079
 	}
2009 2080
 
2010 2081
 	/**
2011 2082
 	 * @param {string[]} alignments
2012 2083
 	 */
2013 2084
 	applyAlignments(alignments) {
2014
-		for (var i = 0; i < this.#cells.length; i++) {
2015
-			let cell = this.#cells[i];
2085
+		for (var i = 0; i < this.cells.length; i++) {
2086
+			let cell = this.cells[i];
2016 2087
 			let align = i < alignments.length ? alignments[i] : null;
2017 2088
 			cell.align = align;
2018 2089
 		}
2019 2090
 	}
2020 2091
 
2021 2092
 	toHTML(state) {
2022
-		let cellsHTML = MDBlock.toHTML(this.#cells, state);
2093
+		let cellsHTML = MDBlock.toHTML(this.cells, state);
2023 2094
 		return `<tr${this.htmlAttributes()}>\n${cellsHTML}\n</tr>`;
2024 2095
 	}
2025 2096
 
2097
+	toPlaintext(state) {
2098
+		return this.cells.map((cell) => cell.toPlaintext(state)).join(' ');
2099
+	}
2100
+
2026 2101
 	visitChildren(fn) {
2027
-		for (const cell of this.#cells) {
2102
+		for (const cell of this.cells) {
2028 2103
 			fn(cell);
2029 2104
 			cell.visitChildren(fn);
2030 2105
 		}
@@ -2033,9 +2108,9 @@ class MDTableRowBlock extends MDBlock {
2033 2108
 
2034 2109
 class MDTableBlock extends MDBlock {
2035 2110
 	/** @type {MDTableRowBlock} */
2036
-	#headerRow;
2111
+	headerRow;
2037 2112
 	/** @type {MDTableRowBlock[]} */
2038
-	#bodyRows;
2113
+	bodyRows;
2039 2114
 
2040 2115
 	/**
2041 2116
 	 * @param {MDTableRowBlock} headerRow
@@ -2043,20 +2118,24 @@ class MDTableBlock extends MDBlock {
2043 2118
 	 */
2044 2119
 	constructor(headerRow, bodyRows) {
2045 2120
 		super();
2046
-		this.#headerRow = headerRow;
2047
-		this.#bodyRows = bodyRows;
2121
+		this.headerRow = headerRow;
2122
+		this.bodyRows = bodyRows;
2048 2123
 	}
2049 2124
 
2050 2125
 	toHTML(state) {
2051
-		let headerRowHTML = this.#headerRow.toHTML(state);
2052
-		let bodyRowsHTML = MDBlock.toHTML(this.#bodyRows, state);
2126
+		let headerRowHTML = this.headerRow.toHTML(state);
2127
+		let bodyRowsHTML = MDBlock.toHTML(this.bodyRows, state);
2053 2128
 		return `<table${this.htmlAttributes()}>\n<thead>\n${headerRowHTML}\n</thead>\n<tbody>\n${bodyRowsHTML}\n</tbody>\n</table>`;
2054 2129
 	}
2055 2130
 
2131
+	toPlaintext(state) {
2132
+		return this.headerRow.toPlaintext(state) + "\n" + this.bodyRows.map((row) => row.toPlaintext(state)).join("\n");
2133
+	}
2134
+
2056 2135
 	visitChildren(fn) {
2057
-		fn(this.#headerRow);
2058
-		this.#headerRow.visitChildren(fn);
2059
-		for (const row of this.#bodyRows) {
2136
+		fn(this.headerRow);
2137
+		this.headerRow.visitChildren(fn);
2138
+		for (const row of this.bodyRows) {
2060 2139
 			fn(row);
2061 2140
 			row.visitChildren(fn);
2062 2141
 		}
@@ -2065,23 +2144,27 @@ class MDTableBlock extends MDBlock {
2065 2144
 
2066 2145
 class MDDefinitionListBlock extends MDBlock {
2067 2146
 	/** @type {MDBlock[]} */
2068
-	#content;
2147
+	content;
2069 2148
 
2070 2149
 	/**
2071 2150
 	 * @param {MDBlock[]} content
2072 2151
 	 */
2073 2152
 	constructor(content) {
2074 2153
 		super();
2075
-		this.#content = content;
2154
+		this.content = content;
2076 2155
 	}
2077 2156
 
2078 2157
 	toHTML(state) {
2079
-		let contentHTML = MDBlock.toHTML(this.#content, state);
2158
+		let contentHTML = MDBlock.toHTML(this.content, state);
2080 2159
 		return `<dl${this.htmlAttributes()}>\n${contentHTML}\n</dl>`;
2081 2160
 	}
2082 2161
 
2162
+	toPlaintext(state) {
2163
+		return MDBlock.toPlaintext(this.content, state);
2164
+	}
2165
+
2083 2166
 	visitChildren(fn) {
2084
-		for (const block of this.#content) {
2167
+		for (const block of this.content) {
2085 2168
 			fn(block);
2086 2169
 			block.visitChildren(fn);
2087 2170
 		}
@@ -2090,7 +2173,7 @@ class MDDefinitionListBlock extends MDBlock {
2090 2173
 
2091 2174
 class MDDefinitionTermBlock extends MDBlock {
2092 2175
 	/** @type {MDBlock[]} */
2093
-	#content;
2176
+	content;
2094 2177
 
2095 2178
 	/**
2096 2179
 	 * @param {MDBlock|MDBlock[]} content
@@ -2098,21 +2181,25 @@ class MDDefinitionTermBlock extends MDBlock {
2098 2181
 	constructor(content) {
2099 2182
 		super();
2100 2183
 		if (content instanceof Array) {
2101
-			this.#content = content;
2184
+			this.content = content;
2102 2185
 		} else if (content instanceof MDBlock) {
2103
-			this.#content = [ content ];
2186
+			this.content = [ content ];
2104 2187
 		} else {
2105 2188
 			throw new Error(`${this.constructor.name} expects MDBlock or MDBlock[], got ${typeof content}`);
2106 2189
 		}
2107 2190
 	}
2108 2191
 
2109 2192
 	toHTML(state) {
2110
-		let contentHTML = MDBlock.toHTML(this.#content, state);
2193
+		let contentHTML = MDBlock.toHTML(this.content, state);
2111 2194
 		return `<dt${this.htmlAttributes()}>${contentHTML}</dt>`;
2112 2195
 	}
2113 2196
 
2197
+	toPlaintext(state) {
2198
+		return MDBlock.toPlaintext(this.content, state);
2199
+	}
2200
+
2114 2201
 	visitChildren(fn) {
2115
-		for (const child of this.#content) {
2202
+		for (const child of this.content) {
2116 2203
 			fn(child);
2117 2204
 			child.visitChildren(fn);
2118 2205
 		}
@@ -2121,7 +2208,7 @@ class MDDefinitionTermBlock extends MDBlock {
2121 2208
 
2122 2209
 class MDDefinitionDefinitionBlock extends MDBlock {
2123 2210
 	/** @type {MDBlock[]} */
2124
-	#content;
2211
+	content;
2125 2212
 
2126 2213
 	/**
2127 2214
 	 * @param {MDBlock|MDBlock[]} content
@@ -2129,21 +2216,25 @@ class MDDefinitionDefinitionBlock extends MDBlock {
2129 2216
 	constructor(content) {
2130 2217
 		super();
2131 2218
 		if (content instanceof Array) {
2132
-			this.#content = content;
2219
+			this.content = content;
2133 2220
 		} else if (content instanceof MDBlock) {
2134
-			this.#content = [ content ];
2221
+			this.content = [ content ];
2135 2222
 		} else {
2136 2223
 			throw new Error(`${this.constructor.name} expects MDBlock or MDBlock[], got ${typeof content}`);
2137 2224
 		}
2138 2225
 	}
2139 2226
 
2140 2227
 	toHTML(state) {
2141
-		let contentHTML = MDBlock.toHTML(this.#content, state);
2228
+		let contentHTML = MDBlock.toHTML(this.content, state);
2142 2229
 		return `<dd${this.htmlAttributes()}>${contentHTML}</dd>`;
2143 2230
 	}
2144 2231
 
2232
+	toPlaintext(state) {
2233
+		return MDBlock.toPlaintext(this.content, state);
2234
+	}
2235
+
2145 2236
 	visitChildren(fn) {
2146
-		for (const child of this.#content) {
2237
+		for (const child of this.content) {
2147 2238
 			fn(child);
2148 2239
 			child.visitChildren(fn);
2149 2240
 		}
@@ -2180,19 +2271,32 @@ class MDFootnoteListingBlock extends MDBlock {
2180 2271
 		html += '</div>';
2181 2272
 		return html;
2182 2273
 	}
2274
+
2275
+	toPlaintext(state) {
2276
+		const footnotes = state.footnotes;
2277
+		var symbolOrder = Object.keys(footnotes);
2278
+		if (Object.keys(footnotes).length == 0) return '';
2279
+		var text = '';
2280
+		for (const symbol of symbolOrder) {
2281
+			let content = footnotes[symbol];
2282
+			if (!content) continue;
2283
+			text += `${symbol}. ${content.toPlaintext(state)}\n`;
2284
+		}
2285
+		return text.trim();
2286
+	}
2183 2287
 }
2184 2288
 
2185 2289
 class MDInlineBlock extends MDBlock {
2186 2290
 	/** @type {MDSpan[]} */
2187
-	#content;
2291
+	content;
2188 2292
 
2189 2293
 	/**
2190 2294
 	 * @param {MDSpan|MDSpan[]} content
2191 2295
 	 */
2192 2296
 	constructor(content) {
2193 2297
 		super();
2194
-		this.#content = (content instanceof Array) ? content : [ content ];
2195
-		for (const span of this.#content) {
2298
+		this.content = (content instanceof Array) ? content : [ content ];
2299
+		for (const span of this.content) {
2196 2300
 			if (!(span instanceof MDSpan)) {
2197 2301
 				throw new Error(`${this.constructor.name} expects MDSpan or MDSpan[], got ${MDUtils.typename(span)}`);
2198 2302
 			}
@@ -2200,11 +2304,15 @@ class MDInlineBlock extends MDBlock {
2200 2304
 	}
2201 2305
 
2202 2306
 	toHTML(state) {
2203
-		return MDSpan.toHTML(this.#content, state);
2307
+		return MDSpan.toHTML(this.content, state);
2308
+	}
2309
+
2310
+	toPlaintext(state) {
2311
+		return MDSpan.toPlaintext(this.content, state);
2204 2312
 	}
2205 2313
 
2206 2314
 	visitChildren(fn) {
2207
-		for (const span of this.#content) {
2315
+		for (const span of this.content) {
2208 2316
 			fn(span);
2209 2317
 			span.visitChildren(fn);
2210 2318
 		}
@@ -2227,7 +2335,11 @@ class MDSpan {
2227 2335
 	 * @returns {string} HTML
2228 2336
 	 */
2229 2337
 	toHTML(state) {
2230
-		throw new Error(`Abstract toHTML must be overridden in ${self.constructor.name}`);
2338
+		throw new Error(`Abstract ${this.constructor.name}.toHTML must be implemented`);
2339
+	}
2340
+
2341
+	toPlaintext(state) {
2342
+		throw new Error(`Abstract ${this.constructor.name}.toPlaintext must be implemented`);
2231 2343
 	}
2232 2344
 
2233 2345
 	htmlAttributes() {
@@ -2248,9 +2360,19 @@ class MDSpan {
2248 2360
 	/**
2249 2361
 	 * @param {MDSpan[]} spans
2250 2362
 	 * @param {MDState} state
2363
+	 * @returns {string}
2251 2364
 	 */
2252 2365
 	static toHTML(spans, state) {
2253
-		return spans.map((span) => span.toHTML(state)).join("");
2366
+		return spans.map((span) => span.toHTML(state)).join('');
2367
+	}
2368
+
2369
+	/**
2370
+	 * @param {MDSpan[]} spans
2371
+	 * @param {MDState} state
2372
+	 * @returns {string}
2373
+	 */
2374
+	static toPlaintext(spans, state) {
2375
+		return spans.map((span) => span.toPlaintext(state)).join('');
2254 2376
 	}
2255 2377
 
2256 2378
 	/**
@@ -2278,6 +2400,10 @@ class MDMultiSpan extends MDSpan {
2278 2400
 		return MDSpan.toHTML(this.content, state);
2279 2401
 	}
2280 2402
 
2403
+	toPlaintext(state) {
2404
+		return MDSpan.toPlaintext(this.content, state);
2405
+	}
2406
+
2281 2407
 	visitChildren(fn) {
2282 2408
 		for (const span of this.content) {
2283 2409
 			fn(span);
@@ -2310,22 +2436,30 @@ class MDTextSpan extends MDSpan {
2310 2436
 		}
2311 2437
 		return html;
2312 2438
 	}
2439
+
2440
+	toPlaintext(state) {
2441
+		return this.text;
2442
+	}
2313 2443
 }
2314 2444
 
2315 2445
 class MDHTMLSpan extends MDSpan {
2316
-	/** @param {string} html */
2317
-	html;
2446
+	/** @param {string} */
2447
+	tag;
2318 2448
 
2319 2449
 	/**
2320
-	 * @param {string} html
2450
+	 * @param {MDHTMLTag} tag
2321 2451
 	 */
2322
-	constructor(html) {
2452
+	constructor(tag) {
2323 2453
 		super();
2324
-		this.html = html;
2454
+		this.tag = tag;
2325 2455
 	}
2326 2456
 
2327 2457
 	toHTML(state) {
2328
-		return this.html;
2458
+		return this.tag.fullTag;
2459
+	}
2460
+
2461
+	toPlaintext(state) {
2462
+		return '';
2329 2463
 	}
2330 2464
 }
2331 2465
 
@@ -2344,6 +2478,10 @@ class MDObfuscatedTextSpan extends MDSpan {
2344 2478
 	toHTML(state) {
2345 2479
 		return MDUtils.escapeObfuscated(this.text);
2346 2480
 	}
2481
+
2482
+	toPlaintext(state) {
2483
+		return this.text;
2484
+	}
2347 2485
 }
2348 2486
 
2349 2487
 class MDLinkSpan extends MDSpan {
@@ -2387,6 +2525,10 @@ class MDLinkSpan extends MDSpan {
2387 2525
 		return html;
2388 2526
 	}
2389 2527
 
2528
+	toPlaintext(state) {
2529
+		return this.content.toPlaintext(state);
2530
+	}
2531
+
2390 2532
 	visitChildren(fn) {
2391 2533
 		fn(this.content);
2392 2534
 		this.content.visitChildren(fn);
@@ -2398,7 +2540,7 @@ class MDReferencedLinkSpan extends MDLinkSpan {
2398 2540
 	ref;
2399 2541
 
2400 2542
 	constructor(ref, content) {
2401
-		super(null, content);
2543
+		super(null, content, null);
2402 2544
 		this.ref = ref;
2403 2545
 	}
2404 2546
 
@@ -2423,23 +2565,27 @@ class MDReferencedLinkSpan extends MDLinkSpan {
2423 2565
 
2424 2566
 class MDEmphasisSpan extends MDSpan {
2425 2567
 	/** @type {MDSpan[]} */
2426
-	#content;
2568
+	content;
2427 2569
 
2428 2570
 	/**
2429 2571
 	 * @param {MDSpan|MDSpan[]} content
2430 2572
 	 */
2431 2573
 	constructor(content) {
2432 2574
 		super();
2433
-		this.#content = (content instanceof MDSpan) ? [ content ] : content;
2575
+		this.content = (content instanceof MDSpan) ? [ content ] : content;
2434 2576
 	}
2435 2577
 
2436 2578
 	toHTML(state) {
2437
-		let contentHTML = MDSpan.toHTML(this.#content, state);
2579
+		let contentHTML = MDSpan.toHTML(this.content, state);
2438 2580
 		return `<em${this.htmlAttributes()}>${contentHTML}</em>`;
2439 2581
 	}
2440 2582
 
2583
+	toPlaintext(state) {
2584
+		return MDSpan.toPlaintext(this.content, state);
2585
+	}
2586
+
2441 2587
 	visitChildren(fn) {
2442
-		for (const span of this.#content) {
2588
+		for (const span of this.content) {
2443 2589
 			fn(span);
2444 2590
 			span.visitChildren(fn);
2445 2591
 		}
@@ -2448,23 +2594,27 @@ class MDEmphasisSpan extends MDSpan {
2448 2594
 
2449 2595
 class MDStrongSpan extends MDSpan {
2450 2596
 	/** @type {MDSpan[]} content */
2451
-	#content;
2597
+	content;
2452 2598
 
2453 2599
 	/**
2454 2600
 	 * @param {MDSpan|MDSpan[]} content
2455 2601
 	 */
2456 2602
 	constructor(content) {
2457 2603
 		super();
2458
-		this.#content = (content instanceof MDSpan) ? [content] : content;
2604
+		this.content = (content instanceof MDSpan) ? [content] : content;
2459 2605
 	}
2460 2606
 
2461 2607
 	toHTML(state) {
2462
-		let contentHTML = MDSpan.toHTML(this.#content, state);
2608
+		let contentHTML = MDSpan.toHTML(this.content, state);
2463 2609
 		return `<strong${this.htmlAttributes()}>${contentHTML}</strong>`;
2464 2610
 	}
2465 2611
 
2612
+	toPlaintext(state) {
2613
+		return MDSpan.toPlaintext(this.content, state);
2614
+	}
2615
+
2466 2616
 	visitChildren(fn) {
2467
-		for (const span of this.#content) {
2617
+		for (const span of this.content) {
2468 2618
 			fn(span);
2469 2619
 			span.visitChildren(fn);
2470 2620
 		}
@@ -2473,23 +2623,27 @@ class MDStrongSpan extends MDSpan {
2473 2623
 
2474 2624
 class MDStrikethroughSpan extends MDSpan {
2475 2625
 	/** @type {MDSpan[]} content */
2476
-	#content;
2626
+	content;
2477 2627
 
2478 2628
 	/**
2479 2629
 	 * @param {MDSpan|MDSpan[]} content
2480 2630
 	 */
2481 2631
 	constructor(content) {
2482 2632
 		super();
2483
-		this.#content = (content instanceof MDSpan) ? [content] : content;
2633
+		this.content = (content instanceof MDSpan) ? [content] : content;
2484 2634
 	}
2485 2635
 
2486 2636
 	toHTML(state) {
2487
-		let contentHTML = MDSpan.toHTML(this.#content, state);
2637
+		let contentHTML = MDSpan.toHTML(this.content, state);
2488 2638
 		return `<strike${this.htmlAttributes()}>${contentHTML}</strike>`;
2489 2639
 	}
2490 2640
 
2641
+	toPlaintext(state) {
2642
+		return MDSpan.toPlaintext(this.content, state);
2643
+	}
2644
+
2491 2645
 	visitChildren(fn) {
2492
-		for (const span of this.#content) {
2646
+		for (const span of this.content) {
2493 2647
 			fn(span);
2494 2648
 			span.visitChildren(fn);
2495 2649
 		}
@@ -2498,7 +2652,7 @@ class MDStrikethroughSpan extends MDSpan {
2498 2652
 
2499 2653
 class MDCodeSpan extends MDSpan {
2500 2654
 	/** @type {String} content */
2501
-	#content;
2655
+	content;
2502 2656
 
2503 2657
 	/**
2504 2658
 	 * @param {String} content
@@ -2506,14 +2660,18 @@ class MDCodeSpan extends MDSpan {
2506 2660
 	constructor(content) {
2507 2661
 		super();
2508 2662
 		if (typeof content == 'string') {
2509
-			this.#content = content;
2663
+			this.content = content;
2510 2664
 		} else {
2511 2665
 			throw new Error(`${this.constructor.name} content must be String, got ${typeof content}`);
2512 2666
 		}
2513 2667
 	}
2514 2668
 
2515 2669
 	toHTML(state) {
2516
-		return `<code${this.htmlAttributes()}>${MDUtils.escapeHTML(this.#content)}</code>`;
2670
+		return `<code${this.htmlAttributes()}>${MDUtils.escapeHTML(this.content)}</code>`;
2671
+	}
2672
+
2673
+	toPlaintext(state) {
2674
+		return this.content;
2517 2675
 	}
2518 2676
 }
2519 2677
 
@@ -2549,6 +2707,10 @@ class MDImageSpan extends MDSpan {
2549 2707
 		html += '>';
2550 2708
 		return html;
2551 2709
 	}
2710
+
2711
+	toPlaintext(state) {
2712
+		return this.alt || '';
2713
+	}
2552 2714
 }
2553 2715
 
2554 2716
 class MDReferencedImageSpan extends MDImageSpan {
@@ -2600,6 +2762,10 @@ class MDFootnoteReferenceSpan extends MDSpan {
2600 2762
 		}
2601 2763
 		return `<!--FNREF:{${this.symbol}}-->`;
2602 2764
 	}
2765
+
2766
+	toPlaintext(state) {
2767
+		return this.symbol;
2768
+	}
2603 2769
 }
2604 2770
 
2605 2771
 
@@ -2908,28 +3074,40 @@ class MDState {
2908 3074
 	/** @type {string[]} */
2909 3075
 	#lines = [];
2910 3076
 
2911
-	/** @type {object} */
3077
+	/**
3078
+	 * Abbreviation string (case sensitive) -> definition string
3079
+	 * @type {object}
3080
+	 */
2912 3081
 	#abbreviations = {};
2913 3082
 
2914
-	/** @type {object} */
3083
+	/**
3084
+	 * Abbreviation string (case sensitive) -> RegExp
3085
+	 * @type {object}
3086
+	 */
2915 3087
 	#abbreviationRegexes = {};
2916 3088
 
2917 3089
 	/**
2918
-	 * symbol:string -> content:MDBlock
3090
+	 * Footnote symbol string -> content MDBlock
2919 3091
 	 * @type {object}
2920 3092
 	 */
2921 3093
 	#footnotes = {};
2922 3094
 
2923 3095
 	/**
2924
-	 * symbol:string -> number[]
3096
+	 * Footnote symbol string -> unique number[]
2925 3097
 	 * @type {object}
2926 3098
 	 */
2927 3099
 	#footnoteInstances = {};
2928 3100
 
2929
-	/** @type {object} */
3101
+	/**
3102
+	 * Reference symbol -> URL string
3103
+	 * @type {object}
3104
+	 */
2930 3105
 	#urlDefinitions = {};
2931 3106
 
2932
-	/** @type {object} */
3107
+	/**
3108
+	 * Reference symbol -> title string
3109
+	 * @type {object}
3110
+	 */
2933 3111
 	#urlTitles = {};
2934 3112
 
2935 3113
 	/** @type {number} */

+ 2743
- 0
js/spreadsheet.js
Filskillnaden har hållits tillbaka eftersom den är för stor
Visa fil


+ 300
- 8
testjs.html Visa fil

@@ -74,6 +74,7 @@
74 74
 			}
75 75
 		</style>
76 76
 		<script src="js/markdown.js"></script>
77
+		<script src="js/spreadsheet.js"></script>
77 78
 		<!-- Testing infrastructure -->
78 79
 		<script>
79 80
 			/**
@@ -122,10 +123,10 @@
122 123
 				assertFalse(test, failMessage=null) {
123 124
 					if (test) this.fail(failMessage || `expected false, got ${test}`);
124 125
 				}
125
-				assertEqual(a, b, failMessage=null) {
126
-					if (MDUtils.equal(a, b)) return;
127
-					const aVal = `${a}`;
128
-					const bVal = `${b}`;
126
+				assertEqual(a, b, floatDifferenceRatio=0.0, failMessage=null) {
127
+					if (MDUtils.equal(a, b, floatDifferenceRatio)) return;
128
+					const aVal = (typeof a == 'string') ? `"${a}"` : `${a}`;
129
+					const bVal = (typeof b == 'string') ? `"${b}"` : `${b}`;
129 130
 					if (aVal.length > 20 || bVal.length > 20) {
130 131
 						this.fail(failMessage || `equality failed:\n${aVal}\n!=\n${bVal}`);
131 132
 					} else {
@@ -423,10 +424,12 @@
423 424
 		<script>
424 425
 			function onLoad() {
425 426
 				let testClasses = [
426
-					TokenTests,
427
-					UtilsTests,
428
-					InlineTests,
429
-					BlockTests,
427
+					// FIXME: Reenable these! Disabled temporarily to test spreadsheets faster.
428
+					// TokenTests,
429
+					// UtilsTests,
430
+					// InlineTests,
431
+					// BlockTests,
432
+					CellValueTests,
430 433
 				];
431 434
 				TestClassRunner.runAll(testClasses);
432 435
 			}
@@ -1110,6 +1113,295 @@
1110 1113
 					this.assertEqual(actual, expected);
1111 1114
 				}
1112 1115
 			}
1116
+
1117
+			class CellValueTests extends BaseTest {
1118
+				test_fromCellString_blank() {
1119
+					var value;
1120
+					value = CellValue.fromCellString('');
1121
+					this.assertEqual(value.type, CellValue.TYPE_BLANK);
1122
+					this.assertEqual(value.formattedValue, '');
1123
+					this.assertEqual(value.value, null);
1124
+					value = CellValue.fromCellString(' ');
1125
+					this.assertEqual(value.type, CellValue.TYPE_BLANK);
1126
+					this.assertEqual(value.formattedValue, '');
1127
+					this.assertEqual(value.value, null);
1128
+				}
1129
+
1130
+				test_fromCellString_number() {
1131
+					var value;
1132
+					value = CellValue.fromCellString('123');
1133
+					this.assertEqual(value.type, CellValue.TYPE_NUMBER);
1134
+					this.assertEqual(value.formattedValue, '123');
1135
+					this.assertEqual(value.value, 123);
1136
+					this.assertEqual(value.decimals, 0);
1137
+					value = CellValue.fromCellString('-0');
1138
+					this.assertEqual(value.type, CellValue.TYPE_NUMBER);
1139
+					this.assertEqual(value.formattedValue, '-0');
1140
+					this.assertEqual(value.value, 0);
1141
+					this.assertEqual(value.decimals, 0);
1142
+					value = CellValue.fromCellString('1,234');
1143
+					this.assertEqual(value.type, CellValue.TYPE_NUMBER);
1144
+					this.assertEqual(value.formattedValue, '1,234');
1145
+					this.assertEqual(value.value, 1234);
1146
+					this.assertEqual(value.decimals, 0);
1147
+					value = CellValue.fromCellString('-1,234,567.89');
1148
+					this.assertEqual(value.type, CellValue.TYPE_NUMBER);
1149
+					this.assertEqual(value.formattedValue, '-1,234,567.89');
1150
+					this.assertEqual(value.value, -1234567.89);
1151
+					this.assertEqual(value.decimals, 2);
1152
+				}
1153
+
1154
+				test_fromCellString_percent() {
1155
+					var value;
1156
+					value = CellValue.fromCellString('123%');
1157
+					this.assertEqual(value.type, CellValue.TYPE_PERCENT);
1158
+					this.assertEqual(value.formattedValue, '123%');
1159
+					this.assertEqual(value.value, 1.23, 0.0001);
1160
+					this.assertEqual(value.decimals, 0);
1161
+					value = CellValue.fromCellString('-12.3%');
1162
+					this.assertEqual(value.type, CellValue.TYPE_PERCENT);
1163
+					this.assertEqual(value.formattedValue, '-12.3%');
1164
+					this.assertEqual(value.value, -0.123, 0.0001);
1165
+					this.assertEqual(value.decimals, 1);
1166
+				}
1167
+
1168
+				test_fromCellString_currency() {
1169
+					var value;
1170
+					value = CellValue.fromCellString('$123');
1171
+					this.assertEqual(value.type, CellValue.TYPE_CURRENCY);
1172
+					this.assertEqual(value.formattedValue, '$123');
1173
+					this.assertEqual(value.value, 123);
1174
+					this.assertEqual(value.decimals, 0);
1175
+					value = CellValue.fromCellString('-$12.34');
1176
+					this.assertEqual(value.type, CellValue.TYPE_CURRENCY);
1177
+					this.assertEqual(value.formattedValue, '-$12.34');
1178
+					this.assertEqual(value.value, -12.34, 0.0001);
1179
+					this.assertEqual(value.decimals, 2);
1180
+				}
1181
+
1182
+				test_fromCellString_boolean() {
1183
+					var value;
1184
+					value = CellValue.fromCellString('true');
1185
+					this.assertEqual(value.type, CellValue.TYPE_BOOLEAN);
1186
+					this.assertEqual(value.formattedValue, 'TRUE');
1187
+					this.assertEqual(value.value, true);
1188
+					value = CellValue.fromCellString('false');
1189
+					this.assertEqual(value.type, CellValue.TYPE_BOOLEAN);
1190
+					this.assertEqual(value.formattedValue, 'FALSE');
1191
+					this.assertEqual(value.value, false);
1192
+				}
1193
+
1194
+				test_fromCellString_string() {
1195
+					var value;
1196
+					value = CellValue.fromCellString('some text');
1197
+					this.assertEqual(value.type, CellValue.TYPE_STRING);
1198
+					this.assertEqual(value.formattedValue, 'some text');
1199
+					this.assertEqual(value.value, 'some text');
1200
+					value = CellValue.fromCellString("'123");
1201
+					this.assertEqual(value.type, CellValue.TYPE_STRING);
1202
+					this.assertEqual(value.formattedValue, '123');
1203
+					this.assertEqual(value.value, '123');
1204
+				}
1205
+
1206
+				test_fromCellString_formula() {
1207
+					var value;
1208
+					value = CellValue.fromCellString('=A*B');
1209
+					this.assertEqual(value.type, CellValue.TYPE_FORMULA);
1210
+					this.assertEqual(value.formattedValue, '=A*B');
1211
+					this.assertEqual(value.value, '=A*B');
1212
+					value = CellValue.fromCellString('=MAX(A, 3)');
1213
+					this.assertEqual(value.type, CellValue.TYPE_FORMULA);
1214
+					this.assertEqual(value.formattedValue, '=MAX(A, 3)');
1215
+					this.assertEqual(value.value, '=MAX(A, 3)');
1216
+				}
1217
+
1218
+				test_fromValue_null() {
1219
+					var value;
1220
+					value = CellValue.fromValue(null);
1221
+					this.assertEqual(value.type, CellValue.TYPE_BLANK);
1222
+				}
1223
+
1224
+				test_fromValue_number() {
1225
+					var value;
1226
+					value = CellValue.fromValue(123);
1227
+					this.assertEqual(value.type, CellValue.TYPE_NUMBER);
1228
+					this.assertEqual(value.formattedValue, '123');
1229
+					value = CellValue.fromValue(3.141592);
1230
+					this.assertEqual(value.type, CellValue.TYPE_NUMBER);
1231
+					this.assertEqual(value.formattedValue, '3.141592');
1232
+					value = CellValue.fromValue(123456789);
1233
+					this.assertEqual(value.type, CellValue.TYPE_NUMBER);
1234
+					this.assertEqual(value.formattedValue, '123,456,789');
1235
+				}
1236
+
1237
+				test_fromValue_boolean() {
1238
+					var value;
1239
+					value = CellValue.fromValue(true);
1240
+					this.assertEqual(value.type, CellValue.TYPE_BOOLEAN);
1241
+					this.assertEqual(value.formattedValue, 'TRUE');
1242
+					value = CellValue.fromValue(false);
1243
+					this.assertEqual(value.type, CellValue.TYPE_BOOLEAN);
1244
+					this.assertEqual(value.formattedValue, 'FALSE');
1245
+				}
1246
+
1247
+				test_fromValue_string() {
1248
+					var value;
1249
+					value = CellValue.fromValue('foo');
1250
+					this.assertEqual(value.type, CellValue.TYPE_STRING);
1251
+					this.assertEqual(value.formattedValue, 'foo');
1252
+					value = CellValue.fromValue('123');
1253
+					this.assertEqual(value.type, CellValue.TYPE_STRING);
1254
+					this.assertEqual(value.formattedValue, '123');
1255
+				}
1256
+
1257
+				test_fromValue_formula() {
1258
+					var value;
1259
+					value = CellValue.fromValue('=A*B');
1260
+					this.assertEqual(value.type, CellValue.TYPE_FORMULA);
1261
+					this.assertEqual(value.formattedValue, '=A*B');
1262
+				}
1263
+
1264
+				test_operation_add() {
1265
+					var a, b, result, expected;
1266
+					a = CellValue.fromValue(3);
1267
+					b = CellValue.fromValue(4);
1268
+					result = a.add(b)
1269
+					expected = new CellValue('7', 7, CellValue.TYPE_NUMBER, 0);
1270
+					this.assertEqual(result, expected);
1271
+
1272
+					a = CellValue.fromCellString('100%');
1273
+					b = CellValue.fromCellString('50%');
1274
+					result = a.add(b);
1275
+					expected = new CellValue('150%', 1.5, CellValue.TYPE_PERCENT, 0);
1276
+					this.assertEqual(result, expected);
1277
+
1278
+					a = CellValue.fromCellString('$123');
1279
+					b = CellValue.fromCellString('$321');
1280
+					result = a.add(b);
1281
+					expected = new CellValue('$444.00', 444, CellValue.TYPE_CURRENCY, 2);
1282
+					this.assertEqual(result, expected);
1283
+				}
1284
+
1285
+				test_operation_subtract() {
1286
+					var a, b, result, expected;
1287
+					a = CellValue.fromValue(9);
1288
+					b = CellValue.fromValue(4);
1289
+					result = a.subtract(b)
1290
+					expected = new CellValue('5', 5, CellValue.TYPE_NUMBER, 0);
1291
+					this.assertEqual(result, expected);
1292
+
1293
+					a = CellValue.fromCellString('100%');
1294
+					b = CellValue.fromCellString('50%');
1295
+					result = a.subtract(b);
1296
+					expected = new CellValue('50%', 0.5, CellValue.TYPE_PERCENT, 0);
1297
+					this.assertEqual(result, expected);
1298
+
1299
+					a = CellValue.fromCellString('$321');
1300
+					b = CellValue.fromCellString('$123');
1301
+					result = a.subtract(b);
1302
+					expected = new CellValue('$198.00', 198, CellValue.TYPE_CURRENCY, 2);
1303
+					this.assertEqual(result, expected);
1304
+				}
1305
+
1306
+				test_operation_multiply() {
1307
+					var a, b, result, expected;
1308
+					a = CellValue.fromValue(3);
1309
+					b = CellValue.fromValue(4);
1310
+					result = a.multiply(b)
1311
+					expected = new CellValue('12', 12, CellValue.TYPE_NUMBER, 0);
1312
+					this.assertEqual(result, expected);
1313
+
1314
+					a = CellValue.fromCellString('150%');
1315
+					b = CellValue.fromCellString('50%');
1316
+					result = a.multiply(b);
1317
+					expected = new CellValue('75%', 0.75, CellValue.TYPE_PERCENT, 0);
1318
+					this.assertEqual(result, expected);
1319
+
1320
+					a = CellValue.fromCellString('$321');
1321
+					b = CellValue.fromCellString('50%');
1322
+					result = a.multiply(b);
1323
+					expected = new CellValue('$160.50', 160.50, CellValue.TYPE_CURRENCY, 2);
1324
+					this.assertEqual(result, expected);
1325
+				}
1326
+
1327
+				test_operation_divide() {
1328
+					var a, b, result, expected;
1329
+					a = CellValue.fromValue(12);
1330
+					b = CellValue.fromValue(4);
1331
+					result = a.divide(b)
1332
+					expected = new CellValue('3', 3, CellValue.TYPE_NUMBER, 0);
1333
+					this.assertEqual(result, expected);
1334
+
1335
+					a = CellValue.fromCellString('150%');
1336
+					b = CellValue.fromCellString('50%');
1337
+					result = a.divide(b);
1338
+					expected = new CellValue('300%', 3.0, CellValue.TYPE_PERCENT, 0);
1339
+					this.assertEqual(result, expected);
1340
+
1341
+					a = CellValue.fromCellString('$321');
1342
+					b = CellValue.fromCellString('200%');
1343
+					result = a.divide(b);
1344
+					expected = new CellValue('$160.50', 160.50, CellValue.TYPE_CURRENCY, 2);
1345
+					this.assertEqual(result, expected);
1346
+				}
1347
+
1348
+				test_operation_modulo() {
1349
+					var a, b, result, expected;
1350
+					a = CellValue.fromValue(7);
1351
+					b = CellValue.fromValue(4);
1352
+					result = a.modulo(b)
1353
+					expected = new CellValue('3', 3, CellValue.TYPE_NUMBER, 0);
1354
+					this.assertEqual(result, expected);
1355
+
1356
+					a = CellValue.fromCellString('175%');
1357
+					b = CellValue.fromCellString('50%');
1358
+					result = a.modulo(b);
1359
+					expected = new CellValue('25%', 0.25, CellValue.TYPE_PERCENT, 0);
1360
+					this.assertEqual(result, expected);
1361
+
1362
+					a = CellValue.fromCellString('$327');
1363
+					b = CellValue.fromCellString('$20');
1364
+					result = a.modulo(b);
1365
+					expected = new CellValue('$7.00', 7.00, CellValue.TYPE_CURRENCY, 2);
1366
+					this.assertEqual(result, expected);
1367
+				}
1368
+
1369
+				test_operation_comparators() {
1370
+					const a = CellValue.fromValue(3);
1371
+					const b = CellValue.fromValue(4);
1372
+					const t = CellValue.fromValue(true);
1373
+					const f = CellValue.fromValue(false);
1374
+
1375
+					this.assertEqual(a.lt(b), t);
1376
+					this.assertEqual(a.lte(b), t);
1377
+					this.assertEqual(a.gt(b), f);
1378
+					this.assertEqual(a.gte(b), f);
1379
+					this.assertEqual(a.eq(b), f);
1380
+					this.assertEqual(a.neq(b), t);
1381
+
1382
+					this.assertEqual(b.lt(a), f);
1383
+					this.assertEqual(b.lte(a), f);
1384
+					this.assertEqual(b.gt(a), t);
1385
+					this.assertEqual(b.gte(a), t);
1386
+					this.assertEqual(b.eq(a), f);
1387
+					this.assertEqual(b.neq(a), t);
1388
+
1389
+					this.assertEqual(a.lt(a), f);
1390
+					this.assertEqual(a.lte(a), t);
1391
+					this.assertEqual(a.gt(a), f);
1392
+					this.assertEqual(a.gte(a), t);
1393
+					this.assertEqual(a.eq(a), t);
1394
+					this.assertEqual(a.neq(a), f);
1395
+				}
1396
+
1397
+				test_operation_concatenate() {
1398
+					const a = CellValue.fromValue('abc');
1399
+					const b = CellValue.fromValue('xyz');
1400
+					const result = a.concatenate(b);
1401
+					const expected = CellValue.fromValue('abcxyz');
1402
+					this.assertEqual(result, expected);
1403
+				}
1404
+			}
1113 1405
 		</script>
1114 1406
 	</head>
1115 1407
 	<body>

Laddar…
Avbryt
Spara