소스 검색

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

main
Rocketsoup 1 년 전
부모
커밋
4065375ab5
3개의 변경된 파일3318개의 추가작업 그리고 105개의 파일을 삭제
  1. 275
    97
      js/markdown.js
  2. 2743
    0
      js/spreadsheet.js
  3. 300
    8
      testjs.html

+ 275
- 97
js/markdown.js 파일 보기

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

+ 2743
- 0
js/spreadsheet.js
파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
파일 보기


+ 300
- 8
testjs.html 파일 보기

74
 			}
74
 			}
75
 		</style>
75
 		</style>
76
 		<script src="js/markdown.js"></script>
76
 		<script src="js/markdown.js"></script>
77
+		<script src="js/spreadsheet.js"></script>
77
 		<!-- Testing infrastructure -->
78
 		<!-- Testing infrastructure -->
78
 		<script>
79
 		<script>
79
 			/**
80
 			/**
122
 				assertFalse(test, failMessage=null) {
123
 				assertFalse(test, failMessage=null) {
123
 					if (test) this.fail(failMessage || `expected false, got ${test}`);
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
 					if (aVal.length > 20 || bVal.length > 20) {
130
 					if (aVal.length > 20 || bVal.length > 20) {
130
 						this.fail(failMessage || `equality failed:\n${aVal}\n!=\n${bVal}`);
131
 						this.fail(failMessage || `equality failed:\n${aVal}\n!=\n${bVal}`);
131
 					} else {
132
 					} else {
423
 		<script>
424
 		<script>
424
 			function onLoad() {
425
 			function onLoad() {
425
 				let testClasses = [
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
 				TestClassRunner.runAll(testClasses);
434
 				TestClassRunner.runAll(testClasses);
432
 			}
435
 			}
1110
 					this.assertEqual(actual, expected);
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
 		</script>
1405
 		</script>
1114
 	</head>
1406
 	</head>
1115
 	<body>
1407
 	<body>

Loading…
취소
저장