xref: /trunk/test/odf-validate/fixtures/p8/numfmt_save_check.py (revision 0e16a31d78a652c56c5c0fc79c752f48b9048df9)
1#**************************************************************
2#
3#  Licensed to the Apache Software Foundation (ASF) under one
4#  or more contributor license agreements.  See the NOTICE file
5#  distributed with this work for additional information
6#  regarding copyright ownership.  The ASF licenses this file
7#  to you under the Apache License, Version 2.0 (the
8#  "License"); you may not use this file except in compliance
9#  with the License.  You may obtain a copy of the License at
10#
11#    http://www.apache.org/licenses/LICENSE-2.0
12#
13#  Unless required by applicable law or agreed to in writing,
14#  software distributed under the License is distributed on an
15#  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16#  KIND, either express or implied.  See the License for the
17#  specific language governing permissions and limitations
18#  under the License.
19#
20#**************************************************************
21
22# usage: runpy.sh fixtures/p8/numfmt_save_check.py <port> <outdir> <mode name> <DefaultVersion> <office:version>
23# The office must run with Save/ODF DefaultVersion set to <DefaultVersion> in its profile (4 = 1.2, 3 = 1.2 Extended, 5 = 1.3,
24# 6 = 1.3 Extended); the setting is read when the office starts. Creates number formats with optional decimals, saves the sheet
25# and reads number:decimal-places, number:min-decimal-places and number:decimal-replacement back from the number style of
26# each cell. In the 1.3 modes the minimum is written when some decimals are optional; the 1.2 modes must not write it. The saved
27# file must carry <office:version> (control that the mode was really in force). Exit code 1 when any row differs.
28import os, re, sys, zipfile
29import uno
30from com.sun.star.lang import Locale
31from unohelp import connect, desktop, pv
32
33port, out = int(sys.argv[1]), os.path.abspath(sys.argv[2])
34name, want_version = sys.argv[3], sys.argv[5]
35os.makedirs(out, exist_ok=True)
36
37# format code, expected (decimal-places, min-decimal-places, decimal-replacement) in the 1.3 modes; the 1.2 modes never get a min
38CASES = [
39    ("0.0##",        (3, 1, None)),
40    ("0.0#",         (2, 1, None)),
41    ("0.00#",        (3, 2, None)),
42    ("0.000",        (3, None, None)),
43    ("0.###",        (3, None, "")),
44    ("0.00",         (2, None, None)),
45    ("0",            (0, None, None)),
46    ("#,##0.0#",     (2, 1, None)),
47    ("0.0#%",        (2, 1, None)),
48    ("0.0##E+00",    (3, 1, None)),
49    ("0.###E+00",    (3, 0, None)),
50    ("0.000E+00",    (3, None, None)),
51    ('0.0#" kg"',    (2, 1, None)),
52    ("[RED]0.0##",   (3, 1, None)),
53]
54
55ctx = connect(port)
56desk = desktop(ctx)
57
58
59def attrs(tag):
60    return dict(re.findall(r'number:([a-z-]+)="([^"]*)"', tag))
61
62
63def styles_of(path):
64    z = zipfile.ZipFile(path)
65    parts = {n: z.read(n).decode("utf-8") for n in ("content.xml", "styles.xml")}
66    version = re.search(r'office:version="([^"]+)"', parts["content.xml"]).group(1)
67    allx = parts["content.xml"] + parts["styles.xml"]
68    datastyle = dict(re.findall(r'<style:style style:name="([^"]+)"[^>]*style:data-style-name="([^"]+)"', allx))
69    numstyle = {}
70    for m in re.finditer(r'<number:(?:number|percentage|currency)-style style:name="([^"]+)"[^>]*>(.*?)</number:(?:number|percentage|currency)-style>', allx, re.S):
71        e = re.search(r'<number:(?:number|scientific-number)\b([^>]*)/?>', m.group(2))
72        numstyle[m.group(1)] = attrs(e.group(1)) if e else {}
73    # a cell whose style is the default of its column carries no table:style-name
74    coldef = re.search(r'<table:table-column[^>]*table:default-cell-style-name="([^"]+)"', parts["content.xml"])
75    cells = []
76    for row in re.findall(r"<table:table-row[^>]*>(.*?)</table:table-row>", parts["content.xml"], re.S):
77        m = re.match(r"<table:table-cell([^>]*)>", row)
78        sn = re.search(r'table:style-name="([^"]+)"', m.group(1)) if m else None
79        cells.append(sn.group(1) if sn else (coldef.group(1) if coldef else ""))
80    return version, [numstyle.get(datastyle.get(c, ""), {}) for c in cells]
81
82
83doc = desk.loadComponentFromURL("private:factory/scalc", "_blank", 0, (pv("Hidden", True),))
84sheet = doc.Sheets.getByIndex(0)
85formats = doc.NumberFormats
86loc = Locale("en", "US", "")
87for i, (code, _) in enumerate(CASES):
88    key = formats.queryKey(code, loc, False)
89    if key == -1:
90        key = formats.addNew(code, loc)
91    cell = sheet.getCellByPosition(0, i)
92    cell.Value = 1.5
93    cell.NumberFormat = key
94
95fails = 0
96for i, (code, _) in enumerate(CASES):
97    fmt = formats.getByKey(sheet.getCellByPosition(0, i).NumberFormat).FormatString
98    if fmt != code:
99        print("FAIL setup %r: the cell has format %r" % (code, fmt))
100        fails += 1
101path = os.path.join(out, "save-%s.ods" % name)
102doc.storeToURL(uno.systemPathToFileUrl(path), (pv("FilterName", "calc8"),))
103version, cells = styles_of(path)
104ok_v = version == want_version
105print("%-4s %-7s office:version=%s (want %s)" % ("ok" if ok_v else "FAIL", name, version, want_version))
106fails += not ok_v
107for i, (code, (places, minimum, repl)) in enumerate(CASES):
108    a = cells[i] if i < len(cells) else {}
109    want_min = minimum if want_version == "1.3" else None
110    got = (int(a["decimal-places"]) if "decimal-places" in a else None,
111           int(a["min-decimal-places"]) if "min-decimal-places" in a else None,
112           a.get("decimal-replacement"))
113    want = (places, want_min, repl)
114    ok = got == want
115    fails += not ok
116    print("%-4s %-7s %-14s got %r want %r" % ("ok" if ok else "FAIL", name, code, got, want))
117doc.close(True)
118print("%d failing rows" % fails)
119sys.exit(1 if fails else 0)
120