Kaynağa Gözat

Spreadsheet formulas can be styled with certain syntaxes

main
Rocketsoup 1 yıl önce
ebeveyn
işleme
03ffef5f19
7 değiştirilmiş dosya ile 293 ekleme ve 55 silme
  1. 71
    1
      js/markdown.js
  2. 1
    1
      js/markdown.min.js
  3. 65
    23
      js/spreadsheet.js
  4. 1
    1
      js/spreadsheet.min.js
  5. 77
    2
      php/markdown.php
  6. 65
    26
      php/spreadsheet.php
  7. 13
    1
      spreadsheet.md

+ 71
- 1
js/markdown.js Dosyayı Görüntüle

2317
  */
2317
  */
2318
 class MDTableReader extends MDReader {
2318
 class MDTableReader extends MDReader {
2319
 	/**
2319
 	/**
2320
+	 * If cell contents begin with `=`, treat entire contents as plaintext.
2321
+	 * Used by spreadsheet add-on to prevent equation operators from being
2322
+	 * interpreted as markdown.
2323
+	 * @type {boolean}
2324
+	 */
2325
+	preferFormulas = false;
2326
+
2327
+	/**
2320
 	 * @param {MDState} state
2328
 	 * @param {MDState} state
2321
 	 * @param {boolean} isHeader
2329
 	 * @param {boolean} isHeader
2322
 	 * @return {MDTableRowNode|null}
2330
 	 * @return {MDTableRowNode|null}
2329
 		if (line.startsWith('|')) line = line.substring(1);
2337
 		if (line.startsWith('|')) line = line.substring(1);
2330
 		if (line.endsWith('|')) line = line.substring(0, line.length - 1);
2338
 		if (line.endsWith('|')) line = line.substring(0, line.length - 1);
2331
 		let cellTokens = line.split('|');
2339
 		let cellTokens = line.split('|');
2340
+		const reader = this;
2332
 		let cells = cellTokens.map(function(token) {
2341
 		let cells = cellTokens.map(function(token) {
2333
-			let content = state.inlineMarkdownToNode(token.trim());
2342
+			const trimmedToken = token.trim();
2343
+			let content;
2344
+			if (reader.preferFormulas && trimmedToken.indexOf('=') >= 0) {
2345
+				content = reader.#preserveFormula(state, trimmedToken);
2346
+				if (content == null) {
2347
+					content = state.inlineMarkdownToNode(trimmedToken);
2348
+				}
2349
+			} else {
2350
+				content = state.inlineMarkdownToNode(token.trim());
2351
+			}
2334
 			return isHeader ? new MDTableHeaderCellNode(content) : new MDTableCellNode(content);
2352
 			return isHeader ? new MDTableHeaderCellNode(content) : new MDTableCellNode(content);
2335
 		});
2353
 		});
2336
 		state.p = p;
2354
 		state.p = p;
2338
 	}
2356
 	}
2339
 
2357
 
2340
 	/**
2358
 	/**
2359
+	 * @param {MDState} state
2360
+	 * @param {string} cellContents
2361
+	 * @returns {MDNode|null}
2362
+	 */
2363
+	#preserveFormula(state, cellContents) {
2364
+		// Up to three prefix punctuation patterns, formula, then three matching
2365
+		// suffixes. Not guaranteed to catch every possible syntax but an awful lot.
2366
+		const groups = /^([^a-z0-9\s]*)([^a-z0-9\s]*)([^a-z0-9\s]*)(=.*)\3\2\1$/i.exec(cellContents);
2367
+		if (groups === null) return null;
2368
+		const prefix = groups[1] + groups[2] + groups[3];
2369
+		const formula = groups[4];
2370
+		if (prefix.length == 0) {
2371
+			return new MDTextNode(formula);
2372
+		}
2373
+		const suffix = groups[3] + groups[2] + groups[1];
2374
+		// Parse substitute markdown with the same prefix and suffix but just
2375
+		// an "x" as content. We'll swap in the unaltered formula into the
2376
+		// parsed nodes.
2377
+		const tempInline = prefix + 'x' + suffix;
2378
+		const tempNodes = state.inlineMarkdownToNodes(tempInline);
2379
+		if (tempNodes.length != 1) return null;
2380
+		var foundText = false;
2381
+		if (tempNodes[0] instanceof MDTextNode && tempNodes[0].text === 'x') {
2382
+			tempNodes[0].text = formula;
2383
+			foundText = true;
2384
+		} else {
2385
+			tempNodes[0].visitChildren(function(node) {
2386
+				if (node instanceof MDTextNode && node.text === 'x') {
2387
+					node.text = formula;
2388
+					foundText = true;
2389
+				}
2390
+			});
2391
+		}
2392
+		if (!foundText) return null;
2393
+		return tempNodes[0];
2394
+	}
2395
+
2396
+	/**
2341
 	 * @param {string} line
2397
 	 * @param {string} line
2342
 	 * @returns {string[]}
2398
 	 * @returns {string[]}
2343
 	 */
2399
 	 */
3604
 		this.#bodyRows = bodyRows;
3660
 		this.#bodyRows = bodyRows;
3605
 	}
3661
 	}
3606
 
3662
 
3663
+	/**
3664
+	 * Returns a given body cell.
3665
+	 *
3666
+	 * @param {number} column
3667
+	 * @param {number} row
3668
+	 * @returns {MDTableCellNode|null} cell or `null` if out of bounds
3669
+	 */
3670
+	bodyCellAt(column, row) {
3671
+		const rowNode = this.bodyRows[row];
3672
+		if (rowNode === undefined) return null;
3673
+		const cellNode = rowNode.children[column];
3674
+		return (cellNode === undefined) ? null : cellNode;
3675
+	}
3676
+
3607
 	#recalculateChildren() {
3677
 	#recalculateChildren() {
3608
 		this.children = [ this.#headerRow, ...this.#bodyRows ];
3678
 		this.children = [ this.#headerRow, ...this.#bodyRows ];
3609
 	}
3679
 	}

+ 1
- 1
js/markdown.min.js
Dosya farkı çok büyük olduğundan ihmal edildi
Dosyayı Görüntüle


+ 65
- 23
js/spreadsheet.js Dosyayı Görüntüle

2953
  * `MDTableReader`. Tables without at least one formula will not be altered.
2953
  * `MDTableReader`. Tables without at least one formula will not be altered.
2954
  */
2954
  */
2955
 class MDSpreadsheetReader extends MDReader {
2955
 class MDSpreadsheetReader extends MDReader {
2956
+	preProcess(state) {
2957
+		for (const reader of state.readersByBlockPriority) {
2958
+			if (reader instanceof MDTableReader) {
2959
+				reader.preferFormulas = true;
2960
+			}
2961
+		}
2962
+	}
2963
+
2956
 	postProcess(state, nodes) {
2964
 	postProcess(state, nodes) {
2957
 		for (const node of nodes) {
2965
 		for (const node of nodes) {
2958
 			if (node instanceof MDTableNode) {
2966
 			if (node instanceof MDTableNode) {
2977
 		const grid = new SpreadsheetGrid(columnCount, rowCount);
2985
 		const grid = new SpreadsheetGrid(columnCount, rowCount);
2978
 		for (var c = 0; c < columnCount; c++) {
2986
 		for (var c = 0; c < columnCount; c++) {
2979
 			for (var r = 0; r < rowCount; r++) {
2987
 			for (var r = 0; r < rowCount; r++) {
2980
-				const cellNode = tableNode.bodyRows[r].children[c];
2981
-				if (cellNode === undefined) continue;
2988
+				const cellNode = tableNode.bodyCellAt(c, r);
2989
+				if (cellNode === null) continue;
2982
 				const cellText = cellNode.toPlaintext(state);
2990
 				const cellText = cellNode.toPlaintext(state);
2983
 				const gridCell = grid.cells[c][r];
2991
 				const gridCell = grid.cells[c][r];
2984
 				gridCell.originalValue = CellValue.fromCellString(cellText);
2992
 				gridCell.originalValue = CellValue.fromCellString(cellText);
2993
 		var isCalculated = false;
3001
 		var isCalculated = false;
2994
 		for (var c = 0; c < columnCount && !isCalculated; c++) {
3002
 		for (var c = 0; c < columnCount && !isCalculated; c++) {
2995
 			for (var r = 0; r < rowCount; r++) {
3003
 			for (var r = 0; r < rowCount; r++) {
2996
-				if (grid.cells[c][r].isCalculated) {
3004
+				if (grid.cellAt(new CellAddress(c, r)).isCalculated) {
2997
 					isCalculated = true;
3005
 					isCalculated = true;
2998
 					break;
3006
 					break;
2999
 				}
3007
 				}
3004
 		// Copy results back to table
3012
 		// Copy results back to table
3005
 		for (var c = 0; c < columnCount; c++) {
3013
 		for (var c = 0; c < columnCount; c++) {
3006
 			for (var r = 0; r < rowCount; r++) {
3014
 			for (var r = 0; r < rowCount; r++) {
3007
-				const cellNode = tableNode.bodyRows[r].children[c];
3008
-				if (cellNode === undefined) continue;
3009
-				const gridCell = grid.cells[c][r];
3010
-				const gridValue = gridCell.outputValue;
3011
-				const cellText = gridValue.formattedValue;
3015
+				const cellNode = tableNode.bodyCellAt(c, r);
3016
+				const gridCell = grid.cellAt(new CellAddress(c, r));
3017
+				if (cellNode === null || gridCell === null) continue;
3018
+				this.#populateCell(cellNode, gridCell, state, c, r);
3019
+			}
3020
+		}
3021
+	}
3022
+
3023
+	/**
3024
+	 * @param {MDTableCellNode} cellNode
3025
+	 * @param {SpreadsheetCell} gridCell
3026
+	 * @param {MDState} state
3027
+	 * @param {number} c - column index
3028
+	 * @param {number} r - row index
3029
+	 */
3030
+	#populateCell(cellNode, gridCell, state, c, r) {
3031
+		const gridValue = gridCell.outputValue;
3032
+		if (gridValue === null) return;
3033
+		const oldCellText = cellNode.toPlaintext(state).trim();
3034
+		const cellText = gridValue.formattedValue;
3035
+		if (cellText != oldCellText) {
3036
+			// Try to insert the text into any nested whole-value formatting nodes
3037
+			// if possible
3038
+			if (!this.#findTextNode(cellNode, oldCellText, cellText)) {
3039
+				// Contents contain mixed formatting. We'll have to just replace
3040
+				// the whole thing.
3012
 				cellNode.children = [ new MDTextNode(cellText) ];
3041
 				cellNode.children = [ new MDTextNode(cellText) ];
3013
-				if (gridCell.isCalculated) {
3014
-					cellNode.cssClasses.push('calculated');
3015
-				}
3016
-				cellNode.cssClasses.push(`spreadsheet-type-${gridValue.type}`);
3017
-				if (gridValue.type == CellValue.TYPE_ERROR) {
3018
-					cellNode.attributes['title'] = gridValue.value;
3019
-				}
3020
-				const gridNumber = gridValue.numericValue();
3021
-				if (gridNumber !== null) {
3022
-					cellNode.attributes['data-numeric-value'] = `${gridNumber}`;
3023
-				}
3024
-				const gridString = gridValue.stringValue(false);
3025
-				if (gridString !== null) {
3026
-					cellNode.attributes['data-string-value'] = gridString;
3027
-				}
3028
 			}
3042
 			}
3029
 		}
3043
 		}
3044
+		if (gridCell.isCalculated) {
3045
+			cellNode.cssClasses.push('calculated');
3046
+		}
3047
+		cellNode.cssClasses.push(`spreadsheet-type-${gridValue.type}`);
3048
+		if (gridValue.type == CellValue.TYPE_ERROR) {
3049
+			cellNode.attributes['title'] = gridValue.value;
3050
+		}
3051
+		const gridNumber = gridValue.numericValue();
3052
+		if (gridNumber !== null) {
3053
+			cellNode.attributes['data-numeric-value'] = `${gridNumber}`;
3054
+		}
3055
+		const gridString = gridValue.stringValue(false);
3056
+		if (gridString !== null) {
3057
+			cellNode.attributes['data-string-value'] = gridString;
3058
+		}
3059
+	}
3060
+
3061
+	#findTextNode(startNode, expectedText, newText) {
3062
+		if (startNode instanceof MDTextNode) {
3063
+			if (startNode.text.trim() === expectedText.trim()) {
3064
+				startNode.text = newText;
3065
+				return true;
3066
+			}
3067
+		}
3068
+		for (const child of startNode.children) {
3069
+			if (this.#findTextNode(child, expectedText, newText)) return true;
3070
+		}
3071
+		return false;
3030
 	}
3072
 	}
3031
 }
3073
 }

+ 1
- 1
js/spreadsheet.min.js
Dosya farkı çok büyük olduğundan ihmal edildi
Dosyayı Görüntüle


+ 77
- 2
php/markdown.php Dosyayı Görüntüle

2079
  * Supports `MDTagModifier` suffix.
2079
  * Supports `MDTagModifier` suffix.
2080
  */
2080
  */
2081
 class MDTableReader extends MDReader {
2081
 class MDTableReader extends MDReader {
2082
+	/**
2083
+	 * If cell contents begin with `=`, treat entire contents as plaintext.
2084
+	 * Used by spreadsheet add-on to prevent equation operators from being
2085
+	 * interpreted as markdown.
2086
+	 * @type {boolean}
2087
+	 */
2088
+	public bool $preferFormulas = false;
2089
+
2082
 	private function readTableRow(MDState $state, bool $isHeader): ?MDTableRowNode {
2090
 	private function readTableRow(MDState $state, bool $isHeader): ?MDTableRowNode {
2083
 		if (!$state->hasLines(1)) return null;
2091
 		if (!$state->hasLines(1)) return null;
2084
 		$p = $state->p;
2092
 		$p = $state->p;
2087
 		if (str_starts_with($line, '|')) $line = mb_substr($line, 1);
2095
 		if (str_starts_with($line, '|')) $line = mb_substr($line, 1);
2088
 		if (str_ends_with($line, '|')) $line = mb_substr($line, 0, mb_strlen($line) - 1);
2096
 		if (str_ends_with($line, '|')) $line = mb_substr($line, 0, mb_strlen($line) - 1);
2089
 		$cellTokens = explode('|', $line);
2097
 		$cellTokens = explode('|', $line);
2090
-		$cells = array_map(function($token) use ($isHeader, $state) {
2091
-			$content = $state->inlineMarkdownToNode(trim($token));
2098
+		$cells = array_map(function($token) use ($state, $isHeader) {
2099
+			$trimmedToken = trim($token);
2100
+			if ($this->preferFormulas && strpos($trimmedToken, '=') !== false) {
2101
+				$content = $this->preserveFormula($state, $trimmedToken);
2102
+				if ($content === null) {
2103
+					$content = $state->inlineMarkdownToNode($trimmedToken);
2104
+				}
2105
+			} else {
2106
+				$content = $state->inlineMarkdownToNode($trimmedToken);
2107
+			}
2092
 			return $isHeader ? new MDTableHeaderCellNode($content) : new MDTableCellNode($content);
2108
 			return $isHeader ? new MDTableHeaderCellNode($content) : new MDTableCellNode($content);
2093
 		}, $cellTokens);
2109
 		}, $cellTokens);
2094
 		$state->p = $p;
2110
 		$state->p = $p;
2096
 	}
2112
 	}
2097
 
2113
 
2098
 	/**
2114
 	/**
2115
+	 * @param MDState $state
2116
+	 * @param string $cellContents
2117
+	 * @return ?MDNode
2118
+	 */
2119
+	private function preserveFormula(MDState $state, string $cellContents): ?MDNode {
2120
+		// Up to three prefix punctuation patterns, formula, then three matching
2121
+		// suffixes. Not guaranteed to catch every possible syntax but an awful lot.
2122
+		// Using preg_match instead for... reasons.
2123
+		$regex = '/^([^a-z0-9\\s]*)([^a-z0-9\\s]*)([^a-z0-9\\s]*)(=.*)\\3\\2\\1$/i';
2124
+		if (!preg_match($regex, $cellContents, $groups)) {
2125
+			return null;
2126
+		}
2127
+		$prefix = $groups[1] . $groups[2] . $groups[3];
2128
+		$formula = $groups[4];
2129
+		if ($prefix === '') {
2130
+			return new MDTextNode($formula);
2131
+		}
2132
+		$suffix = $groups[3] . $groups[2] . $groups[1];
2133
+		// Parse substitute markdown with the same prefix and suffix but just
2134
+		// an "x" as content. We'll swap in the unaltered formula into the
2135
+		// parsed nodes.
2136
+		$tempInline = $prefix . 'x' . $suffix;
2137
+		$tempNodes = $state->inlineMarkdownToNodes($tempInline);
2138
+		if (count($tempNodes) != 1) return null;
2139
+		$foundText = false;
2140
+		if ($tempNodes[0] instanceof MDTextNode && $tempNodes[0]->text === 'x') {
2141
+			$tempNodes[0]->text = $formula;
2142
+			$foundText = true;
2143
+		} else {
2144
+			$tempNodes[0]->visitChildren(function($node) use ($formula, &$foundText) {
2145
+				if ($node instanceof MDTextNode && $node->text === 'x') {
2146
+					$node->text = $formula;
2147
+					$foundText = true;
2148
+				}
2149
+			});
2150
+		}
2151
+		if (!$foundText) return null;
2152
+		return $tempNodes[0];
2153
+	}
2154
+
2155
+	/**
2099
 	 * @param string $line
2156
 	 * @param string $line
2100
 	 * @return string[]
2157
 	 * @return string[]
2101
 	 */
2158
 	 */
3317
 		parent::__construct(array_merge([ $headerRow ], $bodyRows));
3374
 		parent::__construct(array_merge([ $headerRow ], $bodyRows));
3318
 	}
3375
 	}
3319
 
3376
 
3377
+	/**
3378
+	 * Returns a given body cell.
3379
+	 *
3380
+	 * @param {number} column
3381
+	 * @param {number} row
3382
+	 * @returns {MDTableCellNode|null} cell or `null` if out of bounds
3383
+	 */
3384
+	public function bodyCellAt(int $column, int $row): ?MDTableCellNode {
3385
+		$rowNode = $this->bodyRows()[$row] ?? null;
3386
+		if ($rowNode === null) return null;
3387
+		$cellNode = $rowNode->children[$column] ?? null;
3388
+		return ($cellNode === null) ? null : $cellNode;
3389
+	}
3390
+
3320
 	public function applyAlignments() {
3391
 	public function applyAlignments() {
3321
 		foreach ($this->children as $child) {
3392
 		foreach ($this->children as $child) {
3322
 			$this->applyAlignmentsToRow($child);
3393
 			$this->applyAlignmentsToRow($child);
3490
 	public function toPlaintext(MDState $state): string {
3561
 	public function toPlaintext(MDState $state): string {
3491
 		return $this->text;
3562
 		return $this->text;
3492
 	}
3563
 	}
3564
+
3565
+	public function __toString(): string {
3566
+		return "<MDTextNode \"{$this->text}\">";
3567
+	}
3493
 }
3568
 }
3494
 
3569
 
3495
 /**
3570
 /**

+ 65
- 26
php/spreadsheet.php Dosyayı Görüntüle

2810
  * `MDTableReader`. Tables without at least one formula will not be altered.
2810
  * `MDTableReader`. Tables without at least one formula will not be altered.
2811
  */
2811
  */
2812
 class MDSpreadsheetReader extends MDReader {
2812
 class MDSpreadsheetReader extends MDReader {
2813
+	public function preProcess(MDState $state) {
2814
+		foreach ($state->readersByBlockPriority as $reader) {
2815
+			if ($reader instanceof MDTableReader) {
2816
+				$reader->preferFormulas = true;
2817
+			}
2818
+		}
2819
+	}
2820
+
2813
 	public function postProcess(MDState $state, array &$nodes) {
2821
 	public function postProcess(MDState $state, array &$nodes) {
2814
 		foreach ($nodes as $node) {
2822
 		foreach ($nodes as $node) {
2815
 			if ($node instanceof MDTableNode) {
2823
 			if ($node instanceof MDTableNode) {
2830
 		$grid = new SpreadsheetGrid($columnCount, $rowCount);
2838
 		$grid = new SpreadsheetGrid($columnCount, $rowCount);
2831
 		for ($c = 0; $c < $columnCount; $c++) {
2839
 		for ($c = 0; $c < $columnCount; $c++) {
2832
 			for ($r = 0; $r < $rowCount; $r++) {
2840
 			for ($r = 0; $r < $rowCount; $r++) {
2833
-				$row = $tableNode->bodyRows()[$r] ?? null;
2834
-				if ($row === null) continue;
2835
-				$cellNode = $row->children[$c] ?? null;
2841
+				$cellNode = $tableNode->bodyCellAt($c, $r);
2836
 				if ($cellNode === null) continue;
2842
 				if ($cellNode === null) continue;
2837
 				$cellText = $cellNode->toPlaintext($state);
2843
 				$cellText = $cellNode->toPlaintext($state);
2838
 				$gridCell = $grid->cells[$c][$r];
2844
 				$gridCell = $grid->cells[$c][$r];
2848
 		$isCalculated = false;
2854
 		$isCalculated = false;
2849
 		for ($c = 0; $c < $columnCount && !$isCalculated; $c++) {
2855
 		for ($c = 0; $c < $columnCount && !$isCalculated; $c++) {
2850
 			for ($r = 0; $r < $rowCount; $r++) {
2856
 			for ($r = 0; $r < $rowCount; $r++) {
2851
-				if ($grid->cells[$c][$r]->isCalculated) {
2857
+				if ($grid->cellAt(new CellAddress($c, $r))->isCalculated) {
2852
 					$isCalculated = true;
2858
 					$isCalculated = true;
2853
 					break;
2859
 					break;
2854
 				}
2860
 				}
2859
 		// Copy results back to table
2865
 		// Copy results back to table
2860
 		for ($c = 0; $c < $columnCount; $c++) {
2866
 		for ($c = 0; $c < $columnCount; $c++) {
2861
 			for ($r = 0; $r < $rowCount; $r++) {
2867
 			for ($r = 0; $r < $rowCount; $r++) {
2862
-				$row = $tableNode->bodyRows()[$r] ?? null;
2863
-				if ($row === null) continue;
2864
-				$cellNode = $row->children[$c] ?? null;
2865
-				if ($cellNode === null) continue;
2866
-				$gridCell = $grid->cells[$c][$r];
2867
-				$gridValue = $gridCell->outputValue;
2868
-				$cellText = $gridValue->formattedValue;
2868
+				$cellNode = $tableNode->bodyCellAt($c, $r);
2869
+				$gridCell = $grid->cellAt(new CellAddress($c, $r));
2870
+				if ($cellNode === null || $gridCell === null) continue;
2871
+				$this->populateCell($cellNode, $gridCell, $state, $c, $r);
2872
+			}
2873
+		}
2874
+	}
2875
+
2876
+	/**
2877
+	 * @param MDTableCellNode $cellNode
2878
+	 * @param SpreadsheetCell $gridCell
2879
+	 * @param MDState $state
2880
+	 * @param int $c  column index
2881
+	 * @param int $r  row index
2882
+	 */
2883
+	private function populateCell(MDTableCellNode $cellNode,
2884
+			SpreadsheetCell $gridCell, MDState $state, int $c, int $r) {
2885
+		$gridValue = $gridCell->outputValue;
2886
+		if ($gridValue === null) return;
2887
+		$oldCellText = trim($cellNode->toPlaintext($state));
2888
+		$cellText = $gridValue->formattedValue;
2889
+		if ($cellText != $oldCellText) {
2890
+			// Try to insert the text into any nested whole-value formatting nodes
2891
+			// if possible
2892
+			if (!$this->findTextNode($cellNode, $oldCellText, $cellText)) {
2893
+				// Contents contain mixed formatting. We'll have to just replace
2894
+				// the whole thing.
2869
 				$cellNode->children = [ new MDTextNode($cellText) ];
2895
 				$cellNode->children = [ new MDTextNode($cellText) ];
2870
-				if ($gridCell->isCalculated) {
2871
-					$cellNode->addClass('calculated');
2872
-				}
2873
-				$cellNode->addClass("spreadsheet-type-{$gridValue->type}");
2874
-				if ($gridValue->type === CellValue::TYPE_ERROR) {
2875
-					$cellNode->attributes['title'] = $gridValue->value;
2876
-				}
2877
-				$gridNumber = $gridValue->numericValue();
2878
-				if ($gridNumber !== null) {
2879
-					$cellNode->attributes['data-numeric-value'] = "{$gridNumber}";
2880
-				}
2881
-				$gridString = $gridValue->stringValue(false);
2882
-				if ($gridString !== null) {
2883
-					$cellNode->attributes['data-string-value'] = $gridString;
2884
-				}
2885
 			}
2896
 			}
2886
 		}
2897
 		}
2898
+		if ($gridCell->isCalculated) {
2899
+			$cellNode->addClass('calculated');
2900
+		}
2901
+		$cellNode->addClass("spreadsheet-type-{$gridValue->type}");
2902
+		if ($gridValue->type == CellValue::TYPE_ERROR) {
2903
+			$cellNode->attributes['title'] = $gridValue->value;
2904
+		}
2905
+		$gridNumber = $gridValue->numericValue();
2906
+		if ($gridNumber !== null) {
2907
+			$cellNode->attributes['data-numeric-value'] = "{$gridNumber}";
2908
+		}
2909
+		$gridString = $gridValue->stringValue(false);
2910
+		if ($gridString !== null) {
2911
+			$cellNode->attributes['data-string-value'] = $gridString;
2912
+		}
2913
+	}
2914
+
2915
+	private function findTextNode(MDNode $startNode, string $expectedText, string $newText): bool {
2916
+		if ($startNode instanceof MDTextNode) {
2917
+			if (trim($startNode->text) === trim($expectedText)) {
2918
+				$startNode->text = $newText;
2919
+				return true;
2920
+			}
2921
+		}
2922
+		foreach ($startNode->children as $child) {
2923
+			if ($this->findTextNode($child, $expectedText, $newText)) return true;
2924
+		}
2925
+		return false;
2887
 	}
2926
 	}
2888
 }
2927
 }
2889
 ?>
2928
 ?>

+ 13
- 1
spreadsheet.md Dosyayı Görüntüle

1
 # Spreadsheet Expressions
1
 # Spreadsheet Expressions
2
 
2
 
3
-Spreadsheet expressions are an optional feature not enabled in the default parser configurations. To enable it, include `spreadsheet.js` and create a `Markdown` instance with a `MDSpreadsheetReader`.
3
+Spreadsheet expressions are an optional feature not enabled in the default parser configurations. To enable it, include `spreadsheet.js` / `spreadsheet.php` and create a `Markdown` instance with a `MDSpreadsheetReader`.
4
 
4
 
5
 ## Expressions
5
 ## Expressions
6
 
6
 
94
 ## Errors
94
 ## Errors
95
 
95
 
96
 If an expression cannot be evaluated, the cell will show an error symbol, such as `#REF`, `#SYNTAX`, or `#ERROR`. A more detailed message is in the `title` attribute and can be seen in a tooltip by mousing over it.
96
 If an expression cannot be evaluated, the cell will show an error symbol, such as `#REF`, `#SYNTAX`, or `#ERROR`. A more detailed message is in the `title` attribute and can be seen in a tooltip by mousing over it.
97
+
98
+## Styling
99
+
100
+The parser tries to be smart about differentiating operators from markdown syntax,
101
+but if an operator character (such as `*`) is mistaken for formatting, escape
102
+it with `\\*`.
103
+
104
+A limited set of styling can be applied to formula results if it consists of
105
+matching punctuation before and after. For example, `**=SUM(A:A)**` will
106
+render a sum in bold. Up to three such formats can be combined, e.g.
107
+`==~~__=SUM(A:A)__~~==`. Blank cells that are autofilled by a formula in the
108
+column cannot currently be styled in this way.

Loading…
İptal
Kaydet