xref: /trunk/test/odf-validate/fixtures/para-fill-foreign/make.py (revision 0e16a31d78a652c56c5c0fc79c752f48b9048df9)
1# Known limitations of the import these documents exercise:
2#  - a fill is ignored when its gradient, hatch or bitmap definition comes after the style that uses it;
3#  - multi-stop gradients, unnamed direct gradients and hatches, and the opacity of a direct bitmap are not imported;
4#  - draw:angle with a unit suffix such as "30deg" is read as 0 (existing behaviour of the gradient import, unrelated to the fill element).
5# usage: python3 -I make.py <other-suite-soffice> <scratch-dir>
6# Writes foreign-v8.odt, foreign-v3.odt, foreign-v11.odt with the other suite saving at ODF version setting 8, 3, 11
7# (/org.openoffice.Office.Common/Save/ODF DefaultVersion), using gen.bas as macro Standard.Module1.Gen in a throw-away profile,
8# then derives the edge-*.odt variants by editing the XML text of foreign-v8.odt.
9import os, re, subprocess, sys, time, zipfile
10from xml.sax.saxutils import escape
11
12soffice, scratch = sys.argv[1], sys.argv[2]
13here = os.path.dirname(os.path.abspath(__file__))
14KINDS = "solid,solid_transp,gradient,gradient_transp,hatch,hatch_nobg,bitmap_tile,bitmap_stretch,bitmap_once,bitmap_transp"
15XCU = """<?xml version="1.0" encoding="UTF-8"?>
16<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">
17<item oor:path="/org.openoffice.Office.Common/Save/ODF"><prop oor:name="DefaultVersion" oor:op="fuse"><value>%d</value></prop></item>
18<item oor:path="/org.openoffice.Office.Common/Security/Scripting"><prop oor:name="MacroSecurityLevel" oor:op="fuse"><value>0</value></prop></item>
19</oor:items>
20"""
21MODULE = ('<?xml version="1.0" encoding="UTF-8"?>\n<!DOCTYPE script:module PUBLIC "-//OpenOffice.org//DTD OfficeDocument 1.0//EN" "module.dtd">\n'
22          '<script:module xmlns:script="http://openoffice.org/2000/script" script:name="Module1" script:language="StarBasic">%s</script:module>\n')
23OPTS = ["--headless", "--nofirststartwizard", "--norestore", "--nologo"]
24
25def wait_gone(prof):
26    while subprocess.run(["pgrep", "-f", prof], capture_output=True).returncode == 0:
27        time.sleep(1)
28
29for mode in (8, 3, 11):
30    prof = os.path.join(scratch, "profile_%d" % mode)
31    env = dict(os.environ, PFF_KINDS=KINDS, PFF_OUT=os.path.join(here, "foreign-v%d.odt" % mode), 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(env["PFF_OUT"]) > 0, "no output for mode %d" % mode
41
42def scrub(name):
43    """replaces the generator string in meta.xml and the colour theme name in styles.xml by neutral ones"""
44    path = os.path.join(here, name)
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
55for mode in (8, 3, 11):
56    scrub("foreign-v%d.odt" % mode)
57
58def rewrite(src, dst, fn):
59    zi = zipfile.ZipFile(os.path.join(here, src)); zo = zipfile.ZipFile(os.path.join(here, dst), "w")
60    for it in zi.infolist():
61        data = zi.read(it.filename)
62        if it.filename in ("content.xml", "styles.xml"):
63            data = fn(it.filename, data.decode("utf-8")).encode("utf-8")
64        zo.writestr(it, data, compress_type=zipfile.ZIP_STORED if it.filename == "mimetype" else zipfile.ZIP_DEFLATED)
65    zo.close()
66
67def sub1(t, old, new):
68    assert t.count(old) == 1, (old, t.count(old))
69    return t.replace(old, new)
70
71def style_xml(t, name):
72    m = re.search(r'<style:style style:name="%s".*?</style:style>' % re.escape(name), t, re.S)
73    assert m, name
74    return m
75
76# named gradient reference that does not exist
77rewrite("foreign-v8.odt", "edge-unknown-name.odt", lambda n, t: t.replace('draw:fill-gradient-name="Gradient_20_1"', 'draw:fill-gradient-name="No_20_Such"') if n == "styles.xml" else t)
78# empty element in a paragraph style that has fo:background-color
79def empty(n, t):
80    if n != "styles.xml": return t
81    m = style_xml(t, "S_5f_gradient")
82    return t.replace(m.group(0), re.sub(r'<loext:graphic-properties[^>]*/>', '<loext:graphic-properties/>', m.group(0)))
83rewrite("foreign-v8.odt", "edge-empty.odt", empty)
84# same element in non-paragraph styles (text style, table cell style) plus a paragraph that uses them
85EXTRA_TEXT = '<style:style style:name="T9" style:family="text"><loext:graphic-properties draw:fill="solid" draw:fill-color="#ff0000"/></style:style>'
86EXTRA_CELL = '<style:style style:name="C9" style:family="table-cell"><loext:graphic-properties draw:fill="solid" draw:fill-color="#ff0000"/></style:style>'
87EXTRA_FRAME = '<style:style style:name="G9" style:family="graphic"><loext:graphic-properties draw:fill="solid" draw:fill-color="#ff0000"/></style:style>'
88def nonpara(n, t):
89    if n == "styles.xml":
90        return t.replace("</office:styles>", EXTRA_FRAME + "</office:styles>", 1)
91    return t.replace("</office:automatic-styles>", EXTRA_TEXT + EXTRA_CELL + "</office:automatic-styles>", 1)
92rewrite("foreign-v8.odt", "edge-nonparagraph.odt", nonpara)
93# duplicate element (second one must lose) and nested element
94def dup(n, t):
95    if n != "styles.xml": return t
96    m = style_xml(t, "S_5f_solid")
97    s = m.group(0).replace('<style:paragraph-properties', '<loext:graphic-properties draw:fill="solid" draw:fill-color="#ff0000"/><style:paragraph-properties', 1)
98    m2 = style_xml(t, "S_5f_hatch")
99    h = m2.group(0).replace('draw:fill-hatch-solid="true"/>', 'draw:fill-hatch-solid="true"><loext:graphic-properties draw:fill="solid" draw:fill-color="#ff0000"/></loext:graphic-properties>', 1)
100    return t.replace(m.group(0), s).replace(m2.group(0), h)
101rewrite("foreign-v8.odt", "edge-duplicate-nested.odt", dup)
102# the native form and the extension element in one style, both orders
103def both(n, t):
104    if n != "styles.xml": return t
105    m = style_xml(t, "S_5f_solid")
106    native = '<style:graphic-properties draw:fill="solid" draw:fill-color="#ff0000"/>'
107    s1 = m.group(0).replace('<loext:graphic-properties', native + '<loext:graphic-properties', 1)
108    m2 = style_xml(t, "S_5f_solid_5f_fill") if 'S_5f_solid_5f_fill' in t else style_xml(t, "S_5f_solid_5f_transp")
109    s2 = m2.group(0).replace('<style:paragraph-properties', native + '<style:paragraph-properties', 1)
110    return t.replace(m.group(0), s1).replace(m2.group(0), s2)
111rewrite("foreign-v8.odt", "edge-both-forms.odt", both)
112# an attribute of the extension element that no fill property maps to
113def unknown_attr(n, t):
114    if n != "styles.xml": return t
115    m = style_xml(t, "S_5f_solid")
116    return t.replace(m.group(0), m.group(0).replace('<loext:graphic-properties ', '<loext:graphic-properties loext:future-attribute="x" ', 1))
117rewrite("foreign-v8.odt", "edge-unknown-attr.odt", unknown_attr)
118print("done")
119