xref: /trunk/test/odf-validate/fixtures/page-fill-1_3/make-foreign.py (revision 0e16a31d78a652c56c5c0fc79c752f48b9048df9)
1# usage: python3 -I make-foreign.py <other-suite-soffice> <scratch-dir> [edges-only]
2# Writes foreign-v<N>.odt with the other suite saving at ODF version setting N
3# (/org.openoffice.Office.Common/Save/ODF DefaultVersion; 10/11 write 1.3, 12/3 write 1.4, 8 writes 1.2 extended),
4# using gen.bas as macro Standard.Module1.Gen in a throw-away profile, then derives the edge-*.odt variants
5# by editing the XML text of foreign-v10.odt and foreign-v11.odt (edges-only: only the variants).
6import os, re, subprocess, sys, time, zipfile
7from xml.sax.saxutils import escape
8
9soffice, scratch = sys.argv[1], sys.argv[2]
10here = os.path.dirname(os.path.abspath(__file__))
11KINDS = "solid,solid_transp,gradient,gradient_transp,gradient_tgrad,hatch,hatch_nobg,bitmap_tile,bitmap_stretch,bitmap_once,none,shared"
12MODES = (10, 11, 12, 3, 8)
13XCU = """<?xml version="1.0" encoding="UTF-8"?>
14<oor:items xmlns:oor="http://openoffice.org/2001/registry" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
15<item oor:path="/org.openoffice.Office.Common/Save/ODF"><prop oor:name="DefaultVersion" oor:op="fuse"><value>%d</value></prop></item>
16<item oor:path="/org.openoffice.Office.Common/Security/Scripting"><prop oor:name="MacroSecurityLevel" oor:op="fuse"><value>0</value></prop></item>
17</oor:items>
18"""
19MODULE = ('<?xml version="1.0" encoding="UTF-8"?>\n<!DOCTYPE script:module PUBLIC "-//OpenOffice.org//DTD OfficeDocument 1.0//EN" "module.dtd">\n'
20          '<script:module xmlns:script="http://openoffice.org/2000/script" script:name="Module1" script:language="StarBasic">%s</script:module>\n')
21OPTS = ["--headless", "--nofirststartwizard", "--norestore", "--nologo"]
22
23def wait_gone(prof):
24    while subprocess.run(["pgrep", "-f", prof], capture_output=True).returncode == 0:
25        time.sleep(1)
26
27def generate():
28    for mode in MODES:
29        prof = os.path.join(scratch, "profile_page_%d" % mode)
30        out = os.path.join(here, "foreign-v%d.odt" % mode)
31        env = dict(os.environ, PFF_KINDS=KINDS, PFF_OUT=out, PFF_PNG="file://" + os.path.join(here, "tile.png"))
32        ui = "-env:UserInstallation=file://" + prof
33        os.makedirs(os.path.join(prof, "user"))
34        open(os.path.join(prof, "user", "registrymodifications.xcu"), "w").write(XCU % mode)
35        subprocess.run([soffice, *OPTS, ui, "macro:///Standard.Module1.Main"], env=env, timeout=300)  # creates the profile and its Standard library
36        wait_gone(prof)
37        open(os.path.join(prof, "user", "basic", "Standard", "Module1.xba"), "w").write(MODULE % escape(open(os.path.join(here, "gen.bas")).read()))
38        subprocess.run([soffice, *OPTS, ui, "macro:///Standard.Module1.Gen"], env=env, timeout=300)
39        wait_gone(prof)
40        assert os.path.getsize(out) > 0, "no output for mode %d" % mode
41        scrub(out)
42
43def scrub(path):
44    """replaces the generator string in meta.xml and the colour theme name in styles.xml by neutral ones"""
45    zi = zipfile.ZipFile(path); items = [(it, zi.read(it.filename)) for it in zi.infolist()]; zi.close()
46    zo = zipfile.ZipFile(path, "w")
47    for it, data in items:
48        if it.filename == "meta.xml":
49            data = re.sub(rb"<meta:generator>[^<]*</meta:generator>", b"<meta:generator>other suite</meta:generator>", data)
50        if it.filename == "styles.xml":
51            data = re.sub(rb'(<loext:theme-colors loext:name=")[^"]*"', rb'\1Other"', data)
52        zo.writestr(it, data, compress_type=zipfile.ZIP_STORED if it.filename == "mimetype" else zipfile.ZIP_DEFLATED)
53    zo.close()
54
55def rewrite(src, dst, fn):
56    zi = zipfile.ZipFile(os.path.join(here, src)); zo = zipfile.ZipFile(os.path.join(here, dst), "w")
57    for it in zi.infolist():
58        data = zi.read(it.filename)
59        if it.filename == "styles.xml":
60            data = fn(data.decode("utf-8")).encode("utf-8")
61        zo.writestr(it, data, compress_type=zipfile.ZIP_STORED if it.filename == "mimetype" else zipfile.ZIP_DEFLATED)
62    zo.close()
63
64def sub1(t, old, new):
65    assert t.count(old) == 1, (old, t.count(old))
66    return t.replace(old, new)
67
68def dp(t, name):
69    m = re.search(r'<style:style style:name="%s" style:family="drawing-page">.*?</style:style>' % name, t)
70    assert m, name
71    return m.group(0)
72
73def edges():
74    # unresolved names: gradient (P_gradient, P_shared, P_shared2), hatch (P_hatch), transparency gradient only (P_gradient_tgrad)
75    def unknown(t):
76        t = sub1(t, dp(t, "Mdp3"), dp(t, "Mdp3").replace('"Gradient_20_1"', '"No_20_Such"'))
77        t = sub1(t, dp(t, "Mdp6"), dp(t, "Mdp6").replace('"Red_20_90_20_Degrees_20_Crossed_20_1"', '"No_20_Such"'))
78        return sub1(t, dp(t, "Mdp5"), dp(t, "Mdp5").replace('draw:opacity-name="TG1"', 'draw:opacity-name="No_20_Such"'))
79    rewrite("foreign-v10.odt", "edge-unknown-name.odt", unknown)
80    # empty properties element (P_gradient_transp), style without properties element (P_solid_transp)
81    def empty(t):
82        t = sub1(t, dp(t, "Mdp4"), '<style:style style:name="Mdp4" style:family="drawing-page"><style:drawing-page-properties/></style:style>')
83        return sub1(t, dp(t, "Mdp2"), '<style:style style:name="Mdp2" style:family="drawing-page"/>')
84    rewrite("foreign-v10.odt", "edge-empty.odt", empty)
85    # master page naming a drawing page style that is not defined (P_hatch)
86    rewrite("foreign-v10.odt", "edge-undefined.odt", lambda t: sub1(t, 'style:page-layout-name="Mpm7" draw:style-name="Mdp6"', 'style:page-layout-name="Mpm7" draw:style-name="No_20_Such"'))
87    # draw:fill="none" next to the page layout's fo:background-color (Standard, P_solid)
88    rewrite("foreign-v10.odt", "edge-none.odt", lambda t: sub1(t, dp(t, "Mdp1"), dp(t, "Mdp1").replace('draw:fill="solid"', 'draw:fill="none"')))
89    # drawing page style among the common styles, before the hatch it names (P_hatch)
90    def common(t):
91        d = dp(t, "Mdp6")
92        return sub1(sub1(t, d, ""), "<office:styles>", "<office:styles>" + d)
93    rewrite("foreign-v10.odt", "edge-common.odt", common)
94    # both forms disagree: page layouts keep the gradient, the drawing page says solid red (P_gradient, P_shared, P_shared2);
95    # unusable drawing page next to a usable page layout fill (P_hatch_nobg, First Page)
96    def both(t):
97        t = sub1(t, dp(t, "Mdp3"), '<style:style style:name="Mdp3" style:family="drawing-page"><style:drawing-page-properties draw:fill="solid" draw:fill-color="#ff0000"/></style:style>')
98        return sub1(t, dp(t, "Mdp7"), dp(t, "Mdp7").replace('"Red_20_90_20_Degrees_20_Crossed_20_1"', '"No_20_Such"'))
99    rewrite("foreign-v11.odt", "edge-both.odt", both)
100    # page layout shared by a master page with a drawing page fill and one without (P_shared2; P_bitmap_stretch moved to the
101    # tile layout): the second keeps the page layout's own fallback
102    def shared(t):
103        t = sub1(t, 'style:display-name="P_shared2" style:page-layout-name="Mpm13" draw:style-name="Mdp3"', 'style:display-name="P_shared2" style:page-layout-name="Mpm13"')
104        return sub1(t, 'style:display-name="P_bitmap_stretch" style:page-layout-name="Mpm10" draw:style-name="Mdp9"', 'style:display-name="P_bitmap_stretch" style:page-layout-name="Mpm9"')
105    rewrite("foreign-v10.odt", "edge-shared-layout.odt", shared)
106
107if __name__ == "__main__":
108    if sys.argv[3:4] != ["edges-only"]:
109        generate()
110    edges()
111