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