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/formula_export_check.py <port> <outdir> <mode name> <office:version> 23# The office runs with the save mode of <mode name> (DefaultVersion in its profile). Builds a sheet with a named expression 24# that is a list of ranges (as the Excel import creates it) and with formulas whose references were broken by deleting a column 25# (one single reference, one range, one range with a function), saves it, and checks the formulas in content.xml: 26# - the range list is joined with the union operator `~`, never with `;`; 27# - broken references are written as [#REF!], never with #REF! inside a reference such as [.#REF!1]; 28# - a function with several parameters keeps `;` between its parameters; 29# then reloads the file: the named expression keeps both ranges and the broken formulas still give #REF! (error 524); a second 30# save of the reloaded file keeps both spellings. 31# Exit code 1 when any check fails. 32import os, re, sys, zipfile 33import uno 34from com.sun.star.table import CellAddress 35from unohelp import connect, desktop, load, pv 36 37port, out, name, want_version = int(sys.argv[1]), os.path.abspath(sys.argv[2]), sys.argv[3], sys.argv[4] 38os.makedirs(out, exist_ok=True) 39desk = desktop(connect(port)) 40doc = desk.loadComponentFromURL("private:factory/scalc", "_blank", 0, (pv("Hidden", True),)) 41sh = doc.Sheets.getByIndex(0) 42cell = lambda a: sh.getCellRangeByName(a) 43for r in range(1, 4): 44 cell("A%d" % r).Value = r 45 cell("B%d" % r).Value = 10 * r 46cell("E1").setFormula("=A1+1") 47cell("E2").setFormula("=SUM(A1:A3)") 48cell("E3").setFormula("=IF(A1>0;SUM(A1:A3);0)") 49cell("E4").setFormula("=SUM(B1:B3;C1)") 50doc.NamedRanges.addNewByName("rangelist", "$Sheet1.$H$1:$I$2;$Sheet1.$K$1:$L$2", CellAddress(0, 0, 0), 0) 51sh.Columns.removeByIndex(0, 1) # column A: the references to it are now broken 52 53path = os.path.join(out, "formulas-%s.ods" % name) 54doc.storeToURL(uno.systemPathToFileUrl(path), (pv("FilterName", "calc8"),)) 55doc.close(True) 56 57z = zipfile.ZipFile(path) 58content = z.read("content.xml").decode("utf-8") 59version = re.search(r'office:version="([^"]+)"', content).group(1) 60formulas = re.findall(r'table:formula="([^"]*)"', content) 61exprs = re.findall(r'table:expression="([^"]*)"', content) 62print("version", version, "| formulas", formulas, "| expressions", exprs) 63 64checks = [ 65 ("office:version of the mode", version == want_version), 66 ("a named expression was written", len(exprs) == 1), 67 ("range list joined with ~", bool(exprs) and "~" in exprs[0] and "];[" not in exprs[0]), 68 ("#REF! only as a whole reference [#REF!]", all(re.sub(r"\[#REF!\]", "", f).find("#REF!") < 0 for f in formulas + exprs)), 69 ("broken references are [#REF!]", sum("[#REF!]" in f for f in formulas) == 3), 70 ("parameters keep ;", any(f.startswith("of:=SUM(") and ";" in f for f in formulas) 71 or any(";" in f for f in formulas if "SUM" in f)), 72] 73 74ctx_doc = load(desk, path) 75sh2 = ctx_doc.Sheets.getByIndex(0) 76names = ctx_doc.NamedRanges 77checks.append(("the named expression survives a reload", names.hasByName("rangelist"))) 78if names.hasByName("rangelist"): 79 content2 = names.getByName("rangelist").Content 80 print("reloaded content:", content2) 81 checks.append(("the reloaded list has both ranges", content2.count("$G$1") == 1 and content2.count("$J$1") == 1)) 82errs = [sh2.getCellRangeByName(a).getError() for a in ("D1", "D2", "D3")] 83print("reloaded errors of the broken formulas (D1..D3):", errs) 84checks.append(("broken formulas still give #REF! (524)", errs == [524, 524, 524])) 85path2 = os.path.join(out, "formulas2-%s.ods" % name) 86ctx_doc.storeToURL(uno.systemPathToFileUrl(path2), (pv("FilterName", "calc8"),)) 87ctx_doc.close(True) 88content3 = zipfile.ZipFile(path2).read("content.xml").decode("utf-8") 89f3 = re.findall(r'table:formula="([^"]*)"', content3) 90e3 = re.findall(r'table:expression="([^"]*)"', content3) 91print("second save:", f3, e3) 92checks.append(("a second save keeps [#REF!] and ~", sum("[#REF!]" in f for f in f3) == 3 and len(e3) == 1 and "~" in e3[0] 93 and all(re.sub(r"\[#REF!\]", "", f).find("#REF!") < 0 for f in f3 + e3))) 94 95fails = 0 96for label, ok in checks: 97 fails += not ok 98 print("%-4s %s" % ("ok" if ok else "FAIL", label)) 99sys.exit(1 if fails else 0) 100