xref: /trunk/test/odf-validate/dump14.py (revision fbc76c7d28640d3e663ac0dbfe0daecb23c9f5a0)
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 dump14.py <port> <outdir> <file>...
23# Dump for consumer_smoke.py: loads each ODF package read-only and writes EVERY property value (not a fixed list, unlike dump7.py)
24# of the objects the ODF 1.4 additions touch: paragraphs, user-defined styles, text frames, numbering rules, page styles, custom
25# shape geometry, database range filters, chart axes. A construct the office reads then shows up as a difference against the twin
26# without anyone having to guess its property name. Same output layout and .current marker as dump7.py (diff7.py compares it).
27import sys, os, json, hashlib, uno
28from unohelp import connect, desktop, load
29
30port, outdir, files = int(sys.argv[1]), sys.argv[2], sys.argv[3:]
31CAP = 100
32
33
34def conv(x, depth=0):
35    if depth > 8:
36        return "<deep>"
37    if isinstance(x, uno.Enum):
38        return x.value
39    if x is None or isinstance(x, (bool, int, float)):
40        return x
41    if isinstance(x, str):
42        return x[:300]
43    if isinstance(x, bytes):
44        return "bytes:" + hashlib.sha256(x).hexdigest()[:12]
45    if isinstance(x, (tuple, list)):
46        return [conv(i, depth + 1) for i in x][:CAP]
47    if hasattr(x, "typeName"):
48        if x.typeName.endswith(".PropertyValue"):
49            return [x.Name, conv(x.Value, depth + 1)]
50        try:
51            return {k: conv(getattr(x, k), depth + 1) for k in dir(x) if not k.startswith("_") and k not in ("typeName", "value")}
52        except Exception:
53            return "<struct>"
54    if hasattr(x, "getCount") and hasattr(x, "getByIndex"):
55        try:
56            return [conv(x.getByIndex(i), depth + 1) for i in range(min(x.getCount(), 20))]
57        except Exception:
58            return "<index>"
59    return "<obj>"
60
61
62def tryf(f, d="n/a"):
63    try:
64        return f()
65    except Exception:
66        return d
67
68
69NOISE = ("RuntimeUID", "ListId", "NumberingStyleName")   # differ between any two documents
70
71
72def allprops(o):
73    """Every readable property of o; values that are interfaces (conv gives '<obj>') are left out."""
74    out = {}
75    try:
76        names = sorted(p.Name for p in o.getPropertySetInfo().getProperties())
77    except Exception:
78        return out
79    for n in names:
80        if n in NOISE:
81            continue
82        try:
83            val = conv(o.getPropertyValue(n))
84        except Exception:
85            val = "n/a"
86        if val != "<obj>":
87            out[n] = val
88    return out
89
90
91def styles(doc, family):
92    """User-defined styles of a family, plus the default page/paragraph style."""
93    out = {}
94    try:
95        fam = doc.StyleFamilies.getByName(family)
96    except Exception:
97        return out
98    for n in fam.ElementNames:
99        s = fam.getByName(n)
100        if tryf(lambda: s.isUserDefined(), False) or n in ("Standard", "Default", "Default Style"):
101            out[n] = allprops(s)
102    return out
103
104
105def shapes(page, limit=100):
106    out = []
107    for k in range(min(page.Count, limit)):
108        s = page.getByIndex(k)
109        d = {"type": tryf(lambda: s.ShapeType), "text": tryf(lambda: s.String[:80]), "props": allprops(s)}
110        if tryf(lambda: s.supportsService("com.sun.star.drawing.GroupShape"), False):
111            d["kids"] = shapes(s, 30)
112        out.append(d)
113    return out
114
115
116def writer(doc):
117    o = {"paras": [], "frames": {}}
118    e = doc.Text.createEnumeration()
119    while e.hasMoreElements() and len(o["paras"]) < 50:
120        p = e.nextElement()
121        if p.supportsService("com.sun.star.text.Paragraph"):
122            o["paras"].append({"text": p.String[:80], "props": allprops(p)})
123    for n in doc.TextFrames.ElementNames[:30]:
124        o["frames"][n] = allprops(doc.TextFrames.getByName(n))
125    o["shapes"] = shapes(doc.DrawPage)
126    o["parastyles"] = styles(doc, "ParagraphStyles")
127    o["framestyles"] = styles(doc, "FrameStyles")
128    o["pagestyles"] = styles(doc, "PageStyles")
129    o["liststyles"] = styles(doc, "NumberingStyles")
130    o["settings"] = allprops(doc.createInstance("com.sun.star.document.Settings"))
131    o["docprops"] = allprops(doc)
132    return o
133
134
135def calc(doc):
136    o = {"sheets": []}
137    for i in range(doc.Sheets.Count):
138        sh = doc.Sheets.getByIndex(i)
139        so = {"name": sh.Name, "props": allprops(sh), "cells": {}}
140        for r in range(0, 5):
141            for c in range(0, 3):
142                cell = sh.getCellByPosition(c, r)
143                if cell.Type.value != "EMPTY":
144                    so["cells"]["%d,%d" % (c, r)] = {"type": cell.Type.value, "error": cell.getError(), "string": cell.String[:80], "props": allprops(cell)}
145        dbs = {}
146        for n in tryf(lambda: doc.DatabaseRanges.ElementNames, []):
147            dr = doc.DatabaseRanges.getByName(n)
148            fd = tryf(lambda: dr.getFilterDescriptor(), None)
149            dbs[n] = {"props": allprops(dr), "filter": allprops(fd) if fd else None,
150                      "fields": conv(tryf(lambda: fd.getFilterFields(), []))}
151        so["dbranges"] = dbs
152        so["shapes"] = shapes(sh.DrawPage)
153        o["sheets"].append(so)
154    o["cellstyles"] = styles(doc, "CellStyles")
155    o["pagestyles"] = styles(doc, "PageStyles")
156    o["docprops"] = allprops(doc)
157    return o
158
159
160def draw(doc):
161    o = {"pages": []}
162    for i in range(doc.DrawPages.Count):
163        pg = doc.DrawPages.getByIndex(i)
164        o["pages"].append({"props": allprops(pg), "shapes": shapes(pg), "annotations": tryf(lambda: conv(pg.Annotations), "n/a") if hasattr(pg, "Annotations") else "n/a"})
165    o["graphicstyles"] = styles(doc, "graphics")
166    o["pagestyles"] = styles(doc, "PageStyles")
167    return o
168
169
170def chart(doc):
171    o = {}
172    try:
173        cs = doc.getFirstDiagram().getCoordinateSystems()[0]
174        o["coordsys"] = allprops(cs)
175        for dim in range(cs.getDimension()):
176            for idx in range(cs.getMaximumAxisIndexByDimension(dim) + 1):
177                ax = cs.getAxisByDimension(dim, idx)
178                o["axis%d_%d" % (dim, idx)] = {"props": allprops(ax), "scale": conv(ax.getScaleData())}
179    except Exception as e:
180        o["error"] = repr(e)[:100]
181    o["docprops"] = allprops(doc)
182    return o
183
184
185def has(doc, s):
186    return tryf(lambda: doc.supportsService(s), False)
187
188
189DESK = desktop(connect(port))
190os.makedirs(outdir, exist_ok=True)
191for f in files:
192    dest = os.path.join(outdir, os.path.basename(f) + ".json")
193    if os.path.exists(dest):
194        continue
195    open(os.path.join(outdir, ".current"), "w").write(f)
196    out = {}
197    try:
198        doc = load(DESK, f)
199        out["type"] = ("writer" if has(doc, "com.sun.star.text.TextDocument") else "calc" if has(doc, "com.sun.star.sheet.SpreadsheetDocument")
200                       else "impress" if has(doc, "com.sun.star.presentation.PresentationDocument") else "draw" if has(doc, "com.sun.star.drawing.DrawingDocument")
201                       else "chart" if has(doc, "com.sun.star.chart2.ChartDocument") else "other")
202        try:
203            out["body"] = {"writer": writer, "calc": calc, "impress": draw, "draw": draw, "chart": chart}.get(out["type"], lambda d: {})(doc)
204        except Exception as e:
205            out["body_error"] = repr(e)[:200]
206        doc.close(True)
207    except Exception as e:
208        out["error"] = repr(e)[:200]
209    json.dump(out, open(dest, "w"), sort_keys=True, indent=0, default=str)
210print("done", len(files))
211