From ae44c66a58a8baea2da70d697743f31029dc2662 Mon Sep 17 00:00:00 2001 From: "Adrien Minne (adrm)" Date: Thu, 12 Dec 2024 14:58:17 +0000 Subject: [PATCH] [FIX] cell: keep non-breaking spaces MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The non-breaking spaces were removed when updating a cell. This caused issues is odoo pivots, since the formula contained a group name with the non-breaking space removed, but the data was still using the non-breaking space. closes odoo/o-spreadsheet#5365 Task: 4403607 X-original-commit: 5719d7dcc152f7fe73f282239ef9237baf4c1df4 Signed-off-by: Adrien Minne (adrm) Signed-off-by: RĂ©mi Rahir (rar) --- src/formulas/tokenizer.ts | 15 ++++++++------- src/helpers/misc.ts | 12 ++++++------ src/plugins/core/cell.ts | 4 ++-- tests/cells/cell_plugin.test.ts | 7 ++++++- tests/evaluation/tokenizer.test.ts | 2 +- 5 files changed, 23 insertions(+), 17 deletions(-) diff --git a/src/formulas/tokenizer.ts b/src/formulas/tokenizer.ts index b8bf9cd31d..f1f3614973 100644 --- a/src/formulas/tokenizer.ts +++ b/src/formulas/tokenizer.ts @@ -3,7 +3,8 @@ import { TokenizingChars, getFormulaNumberRegex, rangeReference, - replaceSpecialSpaces, + replaceNewLines, + whiteSpaceRegexp, } from "../helpers/index"; import { DEFAULT_LOCALE, Locale } from "../types"; import { CellErrorType } from "../types/errors"; @@ -48,7 +49,7 @@ export interface Token { } export function tokenize(str: string, locale = DEFAULT_LOCALE): Token[] { - str = replaceSpecialSpaces(str); + str = replaceNewLines(str); const chars = new TokenizingChars(str); const result: Token[] = []; @@ -214,13 +215,13 @@ function tokenizeSpace(chars: TokenizingChars): Token | null { return { type: "SPACE", value: NEWLINE.repeat(length) }; } - while (chars.current === " ") { - length++; - chars.shift(); + let spaces = ""; + while (chars.current && chars.current.match(whiteSpaceRegexp)) { + spaces += chars.shift(); } - if (length) { - return { type: "SPACE", value: " ".repeat(length) }; + if (spaces) { + return { type: "SPACE", value: spaces }; } return null; } diff --git a/src/helpers/misc.ts b/src/helpers/misc.ts index 396c04d75b..61f6535e69 100644 --- a/src/helpers/misc.ts +++ b/src/helpers/misc.ts @@ -444,6 +444,7 @@ export function removeFalsyAttributes(obj: * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions/Character_Classes */ const whiteSpaceSpecialCharacters = [ + " ", "\t", "\f", "\v", @@ -458,16 +459,15 @@ const whiteSpaceSpecialCharacters = [ String.fromCharCode(parseInt("3000", 16)), String.fromCharCode(parseInt("feff", 16)), ]; -const whiteSpaceRegexp = new RegExp(whiteSpaceSpecialCharacters.join("|") + "|(\r\n|\r|\n)", "g"); +export const whiteSpaceRegexp = new RegExp(whiteSpaceSpecialCharacters.join("|"), "g"); +const newLineRegexp = /(\r\n|\r)/g; /** - * Replace all the special spaces in a string (non-breaking, tabs, ...) by normal spaces, and all the - * different newlines types by \n. + * Replace all different newlines characters by \n */ -export function replaceSpecialSpaces(text: string | undefined): string { +export function replaceNewLines(text: string | undefined): string { if (!text) return ""; - if (!whiteSpaceRegexp.test(text)) return text; - return text.replace(whiteSpaceRegexp, (match, newLine) => (newLine ? NEWLINE : " ")); + return text.replace(newLineRegexp, NEWLINE); } /** diff --git a/src/plugins/core/cell.ts b/src/plugins/core/cell.ts index 11ce377b90..d2f727433c 100644 --- a/src/plugins/core/cell.ts +++ b/src/plugins/core/cell.ts @@ -15,7 +15,7 @@ import { detectNumberFormat, isInside, range, - replaceSpecialSpaces, + replaceNewLines, toCartesian, toXC, } from "../../helpers/index"; @@ -564,7 +564,7 @@ export class CellPlugin extends CorePlugin implements CoreState { const hasContent = "content" in after || "formula" in after; // Compute the new cell properties - const afterContent = hasContent ? replaceSpecialSpaces(after?.content) : before?.content || ""; + const afterContent = hasContent ? replaceNewLines(after?.content) : before?.content || ""; let style: Style | undefined; if (after.style !== undefined) { style = after.style || undefined; diff --git a/tests/cells/cell_plugin.test.ts b/tests/cells/cell_plugin.test.ts index 9e83fc1633..64311beb59 100644 --- a/tests/cells/cell_plugin.test.ts +++ b/tests/cells/cell_plugin.test.ts @@ -243,6 +243,12 @@ describe("getCellText", () => { setCellContent(model, "A1", '="hello \\"world\\""'); expect(getEvaluatedCell(model, "A1")?.formattedValue).toBe('hello "world"'); }); + + test("Non breaking spaces are kept on cell insertion", () => { + const model = new Model(); + setCellContent(model, "A1", "hello\u00A0world"); + expect(getCellText(model, "A1")).toBe("hello\u00A0world"); + }); }); describe("link cell", () => { @@ -549,7 +555,6 @@ test.each([ ["Line\nLine2", "Line\nLine2"], ["Line\rLine2", "Line\nLine2"], ["Line\r\nLine2", "Line\nLine2"], - ["Word\xa0Word2", "Word Word2"], // \xa0 => non-breaking space ])( "Content string given to update cell are properly sanitized %s", (originalString: string, sanitizedString: string) => { diff --git a/tests/evaluation/tokenizer.test.ts b/tests/evaluation/tokenizer.test.ts index f540518192..71001e870a 100644 --- a/tests/evaluation/tokenizer.test.ts +++ b/tests/evaluation/tokenizer.test.ts @@ -60,7 +60,7 @@ describe("tokenizer", () => { test("spaces", () => { expect(tokenize(" ")).toEqual([{ type: "SPACE", value: " ".repeat(2) }]); - expect(tokenize(" \t\v\f")).toEqual([{ type: "SPACE", value: " ".repeat(4) }]); + expect(tokenize(" \t\v\f")).toEqual([{ type: "SPACE", value: " \t\v\f" }]); expect(tokenize("= 50 %")).toEqual([ { type: "OPERATOR", value: "=" }, { type: "SPACE", value: " " },