Skip to content

Commit 620c921

Browse files
[IMP] spreadsheet: implement a light tokenizer
It happens that during migrations we need some kind of tokenized formula in spreadsheet. This helper will tokenize a string in a similar fashion as spreadsheet. closes #68 Task: 3761476 Related: odoo/odoo#155410 Related: odoo/upgrade#5853 Related: odoo/enterprise#57529 Signed-off-by: Christophe Simonis (chs) <chs@odoo.com>
1 parent 2ce4f01 commit 620c921

File tree

4 files changed

+436
-0
lines changed

4 files changed

+436
-0
lines changed

src/spreadsheet/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from .tokenizer import *

src/spreadsheet/tests/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from .test_spreadsheet_tokenizer import SpreadsheetTokenizeTest
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
from odoo.addons.base.maintenance.migrations.spreadsheet.tokenizer import tokenize
2+
from odoo.addons.base.maintenance.migrations.testing import UnitTestCase
3+
4+
5+
class SpreadsheetTokenizeTest(UnitTestCase):
6+
def test_simple_token(self):
7+
self.assertEqual(tokenize("1"), [("NUMBER", "1")])
8+
9+
def test_number_with_decimal_token(self):
10+
self.assertEqual(
11+
tokenize("=1.5"),
12+
[("OPERATOR", "="), ("NUMBER", "1.5")],
13+
)
14+
15+
def test_formula_token(self):
16+
self.assertEqual(
17+
tokenize("=1"),
18+
[("OPERATOR", "="), ("NUMBER", "1")],
19+
)
20+
21+
def test_longer_operators(self):
22+
self.assertEqual(
23+
tokenize("= >= <= < <>"),
24+
[
25+
("OPERATOR", "="),
26+
("SPACE", " "),
27+
("OPERATOR", ">="),
28+
("SPACE", " "),
29+
("OPERATOR", "<="),
30+
("SPACE", " "),
31+
("OPERATOR", "<"),
32+
("SPACE", " "),
33+
("OPERATOR", "<>"),
34+
],
35+
)
36+
37+
def test_concat_operator(self):
38+
self.assertEqual(tokenize("=&"), [("OPERATOR", "="), ("OPERATOR", "&")])
39+
40+
def test_not_equal_operator(self):
41+
self.assertEqual(tokenize("=<>"), [("OPERATOR", "="), ("OPERATOR", "<>")])
42+
43+
def test_can_tokenize_various_number_expressions(self):
44+
self.assertEqual(
45+
tokenize("1%"),
46+
[("NUMBER", "1"), ("OPERATOR", "%")],
47+
)
48+
self.assertEqual(tokenize("1 %"), [("NUMBER", "1"), ("SPACE", " "), ("OPERATOR", "%")])
49+
self.assertEqual(tokenize("1.1"), [("NUMBER", "1.1")])
50+
self.assertEqual(tokenize("1e3"), [("NUMBER", "1e3")])
51+
52+
def test_debug_formula_token(self):
53+
self.assertEqual(
54+
tokenize("=?1"),
55+
[("OPERATOR", "="), ("DEBUGGER", "?"), ("NUMBER", "1")],
56+
)
57+
58+
def test_REF_formula_token(self):
59+
tokens = tokenize("=#REF+1")
60+
self.assertEqual(
61+
tokens,
62+
[("OPERATOR", "="), ("UNKNOWN", "#"), ("SYMBOL", "REF"), ("OPERATOR", "+"), ("NUMBER", "1")],
63+
)
64+
65+
def test_string(self):
66+
self.assertEqual(tokenize('"hello"'), [("STRING", '"hello"')])
67+
self.assertEqual(tokenize("'hello'"), [("SYMBOL", "'hello'")])
68+
self.assertEqual(tokenize("'hello"), [("UNKNOWN", "'hello")])
69+
self.assertEqual(tokenize('"he\\"l\\"lo"'), [("STRING", '"he\\"l\\"lo"')])
70+
self.assertEqual(tokenize("\"hel'l'o\""), [("STRING", "\"hel'l'o\"")])
71+
self.assertEqual(
72+
tokenize('"hello""test"'),
73+
[
74+
("STRING", '"hello"'),
75+
("STRING", '"test"'),
76+
],
77+
)
78+
79+
def test_function_missing_closing_parenthesis(self):
80+
tokens = tokenize("SUM(")
81+
self.assertEqual(tokens, [("SYMBOL", "SUM"), ("LEFT_PAREN", "(")])
82+
83+
def test_function_token_with_point(self):
84+
self.assertEqual(tokenize("CEILING.MATH"), [("SYMBOL", "CEILING.MATH")])
85+
self.assertEqual(tokenize("ceiling.math"), [("SYMBOL", "ceiling.math")])
86+
self.assertEqual(
87+
tokenize("CEILING.MATH()"),
88+
[("SYMBOL", "CEILING.MATH"), ("LEFT_PAREN", "("), ("RIGHT_PAREN", ")")],
89+
)
90+
self.assertEqual(
91+
tokenize("ceiling.math()"),
92+
[("SYMBOL", "ceiling.math"), ("LEFT_PAREN", "("), ("RIGHT_PAREN", ")")],
93+
)
94+
95+
def test_boolean(self):
96+
self.assertEqual(tokenize("true"), [("SYMBOL", "true")])
97+
self.assertEqual(tokenize("false"), [("SYMBOL", "false")])
98+
self.assertEqual(tokenize("TRUE"), [("SYMBOL", "TRUE")])
99+
self.assertEqual(tokenize("FALSE"), [("SYMBOL", "FALSE")])
100+
self.assertEqual(tokenize("TrUe"), [("SYMBOL", "TrUe")])
101+
self.assertEqual(tokenize("FalSe"), [("SYMBOL", "FalSe")])
102+
self.assertEqual(
103+
tokenize("=AND(true,false)"),
104+
[
105+
("OPERATOR", "="),
106+
("SYMBOL", "AND"),
107+
("LEFT_PAREN", "("),
108+
("SYMBOL", "true"),
109+
("ARG_SEPARATOR", ","),
110+
("SYMBOL", "false"),
111+
("RIGHT_PAREN", ")"),
112+
],
113+
)
114+
self.assertEqual(
115+
tokenize("=trueee"),
116+
[("OPERATOR", "="), ("SYMBOL", "trueee")],
117+
)
118+
119+
def test_references(self):
120+
self.assertEqual(
121+
tokenize("=A1"),
122+
[("OPERATOR", "="), ("REFERENCE", "A1")],
123+
)
124+
self.assertEqual(
125+
tokenize("= A1 "),
126+
[
127+
("OPERATOR", "="),
128+
("SPACE", " "),
129+
("REFERENCE", "A1"),
130+
("SPACE", " "),
131+
],
132+
)
133+
self.assertEqual(
134+
tokenize("=A1:A4"),
135+
[
136+
("OPERATOR", "="),
137+
("REFERENCE", "A1"),
138+
("OPERATOR", ":"),
139+
("REFERENCE", "A4"),
140+
],
141+
)
142+
143+
def test_fixed_references(self):
144+
self.assertEqual(tokenize("=$A$1"), [("OPERATOR", "="), ("REFERENCE", "$A$1")])
145+
self.assertEqual(tokenize("=A$1"), [("OPERATOR", "="), ("REFERENCE", "A$1")])
146+
self.assertEqual(tokenize("=$A1"), [("OPERATOR", "="), ("REFERENCE", "$A1")])
147+
self.assertEqual(tokenize("=Sheet1!$A1"), [("OPERATOR", "="), ("REFERENCE", "Sheet1!$A1")])
148+
self.assertEqual(tokenize("=Sheet1!A$1"), [("OPERATOR", "="), ("REFERENCE", "Sheet1!A$1")])
149+
self.assertEqual(tokenize("='Sheet1'!$A1"), [("OPERATOR", "="), ("REFERENCE", "'Sheet1'!$A1")])
150+
self.assertEqual(tokenize("='Sheet1'!A$1"), [("OPERATOR", "="), ("REFERENCE", "'Sheet1'!A$1")])
151+
152+
def test_reference_and_sheets(self):
153+
self.assertEqual(
154+
tokenize("=Sheet1!A1"),
155+
[("OPERATOR", "="), ("REFERENCE", "Sheet1!A1")],
156+
)
157+
self.assertEqual(
158+
tokenize("=Sheet1!A1:A2"),
159+
[("OPERATOR", "="), ("REFERENCE", "Sheet1!A1"), ("OPERATOR", ":"), ("REFERENCE", "A2")],
160+
)
161+
self.assertEqual(
162+
tokenize("='Sheet1'!A1"),
163+
[("OPERATOR", "="), ("REFERENCE", "'Sheet1'!A1")],
164+
)
165+
self.assertEqual(
166+
tokenize("='Aryl Nibor Xela Nalim'!A1"),
167+
[("OPERATOR", "="), ("REFERENCE", "'Aryl Nibor Xela Nalim'!A1")],
168+
)
169+
self.assertEqual(
170+
tokenize("='a '' b'!A1"),
171+
[("OPERATOR", "="), ("REFERENCE", "'a '' b'!A1")],
172+
)
173+
174+
def test_wrong_references(self):
175+
self.assertEqual(
176+
tokenize("='Sheet1!A1"),
177+
[("OPERATOR", "="), ("UNKNOWN", "'Sheet1!A1")],
178+
)
179+
self.assertEqual(
180+
tokenize("=!A1"),
181+
[("OPERATOR", "="), ("SYMBOL", "!A1")],
182+
)
183+
self.assertEqual(
184+
tokenize("=''!A1"),
185+
[("OPERATOR", "="), ("SYMBOL", "''!A1")],
186+
)

0 commit comments

Comments
 (0)