xref: /trunk/test/odf-validate/dump7.py (revision 0e16a31d78a652c56c5c0fc79c752f48b9048df9)
1*0e16a31dSJim Jagielski# usage: runpy.sh dump7.py <port> <outdir> <file>...
2*0e16a31dSJim Jagielski# Loads each ODF package read-only over UNO and writes a JSON dump of a fixed property set (Task 7, Review Focus 2).
3*0e16a31dSJim Jagielskiimport sys, os, json, hashlib, uno
4*0e16a31dSJim Jagielskifrom unohelp import connect, desktop, load
5*0e16a31dSJim Jagielski
6*0e16a31dSJim Jagielskiport, outdir, files = int(sys.argv[1]), sys.argv[2], sys.argv[3:]
7*0e16a31dSJim JagielskiLIM = 400
8*0e16a31dSJim Jagielski
9*0e16a31dSJim Jagielskidef v(x, depth=0):
10*0e16a31dSJim Jagielski    if isinstance(x, uno.Enum): return x.value
11*0e16a31dSJim Jagielski    if x is None or isinstance(x, (bool, int, float, str)): return x
12*0e16a31dSJim Jagielski    if isinstance(x, (tuple, list)): return [v(i, depth + 1) for i in x][:200]
13*0e16a31dSJim Jagielski    if hasattr(x, "typeName"):
14*0e16a31dSJim Jagielski        if depth > 3: return "<deep>"
15*0e16a31dSJim Jagielski        try: return {k: v(getattr(x, k), depth + 1) for k in dir(x) if not k.startswith("_") and k not in ("typeName", "value")}
16*0e16a31dSJim Jagielski        except Exception: return "<struct>"
17*0e16a31dSJim Jagielski    return "<obj>"
18*0e16a31dSJim Jagielskidef g(o, n):
19*0e16a31dSJim Jagielski    try: return v(o.getPropertyValue(n))
20*0e16a31dSJim Jagielski    except Exception: return "n/a"
21*0e16a31dSJim Jagielskidef gs(o, names): return [g(o, n) for n in names]
22*0e16a31dSJim Jagielskidef h(s): return hashlib.sha256(s.encode("utf-8", "replace")).hexdigest()[:16]
23*0e16a31dSJim Jagielskidef tryf(f, d="n/a"):
24*0e16a31dSJim Jagielski    try: return f()
25*0e16a31dSJim Jagielski    except Exception as e: return d if d != "ERR" else repr(e)[:80]
26*0e16a31dSJim Jagielskidef has(doc, s): return tryf(lambda: doc.supportsService(s), False)
27*0e16a31dSJim Jagielski
28*0e16a31dSJim JagielskiSHAPE_PROPS = ["FillStyle", "FillColor", "FillTransparence", "FillGradientName", "FillHatchName", "FillBitmapName", "LineStyle", "LineColor", "LineWidth", "CharHeight", "ParaAdjust"]
29*0e16a31dSJim Jagielskidef shape(s, depth=0):
30*0e16a31dSJim Jagielski    d = {"t": tryf(lambda: s.ShapeType), "name": tryf(lambda: s.Name), "pos": v(tryf(lambda: s.Position)), "size": v(tryf(lambda: s.Size)),
31*0e16a31dSJim Jagielski         "text": tryf(lambda: s.String[:80]), "p": gs(s, SHAPE_PROPS)}
32*0e16a31dSJim Jagielski    t = d["t"] if isinstance(d["t"], str) else ""
33*0e16a31dSJim Jagielski    if "Connector" in t:
34*0e16a31dSJim Jagielski        d["conn"] = {"kind": g(s, "EdgeKind"), "start": g(s, "StartPosition"), "end": g(s, "EndPosition"), "bez": g(s, "PolyPolygonBezier")}
35*0e16a31dSJim Jagielski    if "OLE2" in t:
36*0e16a31dSJim Jagielski        d["ole"] = {"cls": g(s, "CLSID"), "internal": g(s, "IsInternal"), "model": embedded(tryf(lambda: s.Model, None))}
37*0e16a31dSJim Jagielski    if "GraphicObject" in t: d["gr"] = gs(s, ["GraphicURL", "Transparency"])[1:]
38*0e16a31dSJim Jagielski    if tryf(lambda: s.supportsService("com.sun.star.drawing.GroupShape"), False) and depth < 4:
39*0e16a31dSJim Jagielski        d["kids"] = [shape(s.getByIndex(i), depth + 1) for i in range(min(s.Count, 100))]
40*0e16a31dSJim Jagielski    return d
41*0e16a31dSJim Jagielskidef forms_of(page):
42*0e16a31dSJim Jagielski    out = []
43*0e16a31dSJim Jagielski    try: forms = page.Forms
44*0e16a31dSJim Jagielski    except Exception: return out
45*0e16a31dSJim Jagielski    def walk(c):
46*0e16a31dSJim Jagielski        for i in range(min(c.Count, 300)):
47*0e16a31dSJim Jagielski            e = c.getByIndex(i)
48*0e16a31dSJim Jagielski            if tryf(lambda: e.supportsService("com.sun.star.form.FormComponents"), False) or tryf(lambda: e.supportsService("com.sun.star.form.component.Form"), False):
49*0e16a31dSJim Jagielski                out.append(["FORM", tryf(lambda: e.Name)]); walk(e)
50*0e16a31dSJim Jagielski            else:
51*0e16a31dSJim Jagielski                names = tryf(lambda: [p.Name for p in e.getPropertySetInfo().getProperties()], [])
52*0e16a31dSJim Jagielski                out.append([tryf(lambda: e.Name), tryf(lambda: e.ImplementationName), g(e, "ObjIDinMSO") if "ObjIDinMSO" in names else "-",
53*0e16a31dSJim Jagielski                            tryf(lambda: e.getValueBinding() is not None, "-"), tryf(lambda: (e.getListEntrySource() is not None), "-"), len(names)])
54*0e16a31dSJim Jagielski    walk(forms)
55*0e16a31dSJim Jagielski    return out
56*0e16a31dSJim Jagielskidef chart_info(m):
57*0e16a31dSJim Jagielski    if m is None: return None
58*0e16a31dSJim Jagielski    try:
59*0e16a31dSJim Jagielski        r = {"title": tryf(lambda: m.Title.String if m.HasMainTitle else ""), "diagram": tryf(lambda: m.Diagram.ServiceName if hasattr(m.Diagram, "ServiceName") else m.getDiagram().getDiagramType()),
60*0e16a31dSJim Jagielski             "addshapes": tryf(lambda: [m.DrawPage.Count, sorted(m.DrawPage.getByIndex(i).ShapeType.split(".")[-1] for i in range(m.DrawPage.Count))]), "intdata": tryf(lambda: m.hasInternalDataProvider())}
61*0e16a31dSJim Jagielski        return r
62*0e16a31dSJim Jagielski    except Exception: return "n/a"
63*0e16a31dSJim Jagielskidef embedded(m, depth=0):
64*0e16a31dSJim Jagielski    """Dump an embedded model: drawing and spreadsheet documents fully, charts by title/type/additional shapes."""
65*0e16a31dSJim Jagielski    if m is None: return None
66*0e16a31dSJim Jagielski    if depth > 1: return "<deep>"
67*0e16a31dSJim Jagielski    try:
68*0e16a31dSJim Jagielski        if has(m, "com.sun.star.drawing.DrawingDocument") or has(m, "com.sun.star.presentation.PresentationDocument"): return {"draw": draw(m)}
69*0e16a31dSJim Jagielski        if has(m, "com.sun.star.sheet.SpreadsheetDocument"): return {"calc": calc(m)}
70*0e16a31dSJim Jagielski        if has(m, "com.sun.star.chart2.ChartDocument") or has(m, "com.sun.star.chart.ChartDocument"): return chart_info(m)
71*0e16a31dSJim Jagielski        return {"other": tryf(lambda: list(m.SupportedServiceNames)[:3])}
72*0e16a31dSJim Jagielski    except Exception as e: return "ERR " + repr(e)[:80]
73*0e16a31dSJim Jagielskidef docprops(doc):
74*0e16a31dSJim Jagielski    p = doc.DocumentProperties
75*0e16a31dSJim Jagielski    ud = tryf(lambda: [[n, v(p.UserDefinedProperties.getPropertyValue(n))] for n in sorted(i.Name for i in p.UserDefinedProperties.getPropertySetInfo().getProperties())], [])
76*0e16a31dSJim Jagielski    return {"title": p.Title, "subject": p.Subject, "author": p.Author, "keywords": list(p.Keywords), "desc": p.Description, "gen": p.Generator[:20] if p.Generator else "",
77*0e16a31dSJim Jagielski            "print_date": v(p.PrintDate), "printed_by": p.PrintedBy, "creation_date": v(p.CreationDate), "template": p.TemplateName, "edit_cycles": p.EditingCycles,
78*0e16a31dSJim Jagielski            "autoload": [p.AutoloadURL, p.AutoloadSecs], "defaulttarget": p.DefaultTarget, "userdef": ud}
79*0e16a31dSJim Jagielskidef basic(doc):
80*0e16a31dSJim Jagielski    out = {}
81*0e16a31dSJim Jagielski    for k in ("BasicLibraries", "DialogLibraries"):
82*0e16a31dSJim Jagielski        out[k] = tryf(lambda: sorted(getattr(doc, k).ElementNames), "n/a")
83*0e16a31dSJim Jagielski    return out
84*0e16a31dSJim Jagielski
85*0e16a31dSJim Jagielskidef writer(doc):
86*0e16a31dSJim Jagielski    o = {}
87*0e16a31dSJim Jagielski    txt = doc.Text.String
88*0e16a31dSJim Jagielski    o["text_hash"] = h(txt); o["chars"] = len(txt)
89*0e16a31dSJim Jagielski    paras = []; ncell = 0
90*0e16a31dSJim Jagielski    e = doc.Text.createEnumeration(); tbl = 0; port_hash = []
91*0e16a31dSJim Jagielski    while e.hasMoreElements() and len(paras) < LIM:
92*0e16a31dSJim Jagielski        p = e.nextElement()
93*0e16a31dSJim Jagielski        if p.supportsService("com.sun.star.text.Paragraph"):
94*0e16a31dSJim Jagielski            paras.append([len(p.String), g(p, "ParaStyleName"), g(p, "ParaBackColor"), g(p, "ParaBackTransparent"), g(p, "FillStyle"), g(p, "FillColor"), g(p, "FillTransparence"),
95*0e16a31dSJim Jagielski                          g(p, "CharHeight"), g(p, "ParaAdjust"), g(p, "ParaHyphenationMaxLeadingChars"), g(p, "ParaHyphenationMaxTrailingChars"), g(p, "ParaHyphenationMaxHyphens")])
96*0e16a31dSJim Jagielski            try:
97*0e16a31dSJim Jagielski                pe = p.createEnumeration(); sig = []
98*0e16a31dSJim Jagielski                while pe.hasMoreElements():
99*0e16a31dSJim Jagielski                    t = pe.nextElement(); ty = t.TextPortionType; sig.append(ty)
100*0e16a31dSJim Jagielski                    if ty == "TextField": sig.append(tryf(lambda: t.TextField.ServiceName if hasattr(t.TextField, "ServiceName") else ",".join(t.TextField.SupportedServiceNames)))
101*0e16a31dSJim Jagielski                    u = g(t, "HyperLinkURL")
102*0e16a31dSJim Jagielski                    if u: sig.append("URL:" + str(u))
103*0e16a31dSJim Jagielski                port_hash.append(h("|".join(map(str, sig))))
104*0e16a31dSJim Jagielski            except Exception: port_hash.append("n/a")
105*0e16a31dSJim Jagielski        else: tbl += 1
106*0e16a31dSJim Jagielski    o["paras"] = paras; o["nparas"] = len(paras); o["port_hash"] = h("".join(port_hash)); o["top_tables"] = tbl
107*0e16a31dSJim Jagielski    ps = doc.StyleFamilies.getByName("PageStyles")
108*0e16a31dSJim Jagielski    pk = ["Width", "Height", "BackColor", "BackTransparent", "FillStyle", "FillColor", "FillTransparence", "HeaderIsOn", "HeaderBackColor", "HeaderBackTransparent", "HeaderFillStyle",
109*0e16a31dSJim Jagielski          "HeaderFillColor", "HeaderFillTransparence", "FooterIsOn", "FooterBackColor", "FooterBackTransparent", "FooterFillStyle", "FooterFillColor", "FooterFillTransparence",
110*0e16a31dSJim Jagielski          "GridMode", "GridBaseWidth", "GridBaseHeight", "GridLines", "GridSnapToChars", "StandardPageMode", "GridRubyHeight", "GridDisplay", "GridPrint", "IsLandscape", "PageStyleLayout"]
111*0e16a31dSJim Jagielski    o["pagestyles"] = {n: gs(ps.getByName(n), pk) + [bool(g(ps.getByName(n), "BackGraphicURL")), bool(g(ps.getByName(n), "HeaderBackGraphicURL")), bool(g(ps.getByName(n), "FooterBackGraphicURL"))] for n in ps.ElementNames}
112*0e16a31dSJim Jagielski    pa = doc.StyleFamilies.getByName("ParagraphStyles")
113*0e16a31dSJim Jagielski    ak = ["ParaBackColor", "ParaBackTransparent", "FillStyle", "FillColor", "FillTransparence", "CharHeight", "ParaHyphenationMaxLeadingChars", "ParaHyphenationMaxTrailingChars", "ParaKeepTogether", "ParaAdjust"]
114*0e16a31dSJim Jagielski    o["parastyles"] = {n: gs(pa.getByName(n), ak) for n in pa.ElementNames}
115*0e16a31dSJim Jagielski    cs = doc.StyleFamilies.getByName("CharacterStyles")
116*0e16a31dSJim Jagielski    o["charstyles"] = {n: gs(cs.getByName(n), ["CharHeight", "CharWeight", "CharColor"]) for n in cs.ElementNames}
117*0e16a31dSJim Jagielski    fs = doc.StyleFamilies.getByName("FrameStyles")
118*0e16a31dSJim Jagielski    o["framestyles"] = {n: gs(fs.getByName(n), ["BackColor", "FillStyle", "FillTransparence", "FillColor", "Width", "Height"]) for n in fs.ElementNames}
119*0e16a31dSJim Jagielski    tabs = []
120*0e16a31dSJim Jagielski    for i in range(min(doc.TextTables.Count, 100)):
121*0e16a31dSJim Jagielski        t = doc.TextTables.getByIndex(i)
122*0e16a31dSJim Jagielski        cn = list(t.CellNames)
123*0e16a31dSJim Jagielski        tabs.append({"name": t.Name, "rows": t.Rows.Count, "cols": t.Columns.Count, "cells": len(cn), "cellhash": h("|".join(cn) + "".join(tryf(lambda: t.getCellByName(c).String, "") for c in cn[:300])),
124*0e16a31dSJim Jagielski                     "split": [tryf(lambda: t.Rows.getByIndex(r).IsSplitAllowed) for r in range(min(t.Rows.Count, 50))], "bg": g(t, "BackColor"), "kt": g(t, "Split")})
125*0e16a31dSJim Jagielski    o["tables"] = tabs
126*0e16a31dSJim Jagielski    fld = []
127*0e16a31dSJim Jagielski    en = doc.TextFields.createEnumeration()
128*0e16a31dSJim Jagielski    while en.hasMoreElements() and len(fld) < LIM:
129*0e16a31dSJim Jagielski        f = en.nextElement()
130*0e16a31dSJim Jagielski        sn = tryf(lambda: ",".join(f.SupportedServiceNames))
131*0e16a31dSJim Jagielski        r = [sn.replace("com.sun.star.text.", "")[:120]]
132*0e16a31dSJim Jagielski        if "GetReference" in sn: r += gs(f, ["ReferenceFieldPart", "ReferenceFieldSource", "SourceName", "SequenceNumber"])
133*0e16a31dSJim Jagielski        if "URL" in sn: r += gs(f, ["URL", "Representation"])
134*0e16a31dSJim Jagielski        fld.append(r)
135*0e16a31dSJim Jagielski    o["fields"] = fld
136*0e16a31dSJim Jagielski    o["footnotes"] = [doc.Footnotes.Count, [tryf(lambda: doc.Footnotes.getByIndex(i).String[:30], "") for i in range(min(doc.Footnotes.Count, 30))]]
137*0e16a31dSJim Jagielski    o["endnotes"] = [doc.Endnotes.Count, [tryf(lambda: doc.Endnotes.getByIndex(i).String[:30], "") for i in range(min(doc.Endnotes.Count, 30))]]
138*0e16a31dSJim Jagielski    fr = []
139*0e16a31dSJim Jagielski    for coll, name in ((doc.TextFrames, "frame"), (doc.GraphicObjects, "graphic"), (doc.EmbeddedObjects, "embedded")):
140*0e16a31dSJim Jagielski        for n in coll.ElementNames[:100]:
141*0e16a31dSJim Jagielski            f = coll.getByName(n)
142*0e16a31dSJim Jagielski            fr.append([name, n, g(f, "AnchorType"), g(f, "AnchorPageNo"), g(f, "Width"), g(f, "Height"), g(f, "BackColor"), g(f, "FillTransparence"), g(f, "Transparency"), g(f, "FillStyle"), g(f, "HoriOrientPosition"), g(f, "VertOrientPosition")])
143*0e16a31dSJim Jagielski            if name == "embedded": fr[-1].append(embedded(tryf(lambda: f.Model, None)))
144*0e16a31dSJim Jagielski    o["frames"] = fr
145*0e16a31dSJim Jagielski    sec = []
146*0e16a31dSJim Jagielski    for n in doc.TextSections.ElementNames[:100]:
147*0e16a31dSJim Jagielski        s = doc.TextSections.getByName(n); sec.append([n, g(s, "FileLink"), g(s, "LinkRegion"), g(s, "IsProtected"), g(s, "IsVisible")])
148*0e16a31dSJim Jagielski    o["sections"] = sec
149*0e16a31dSJim Jagielski    rl = []
150*0e16a31dSJim Jagielski    try:
151*0e16a31dSJim Jagielski        en = doc.Redlines.createEnumeration()
152*0e16a31dSJim Jagielski        while en.hasMoreElements() and len(rl) < 100:
153*0e16a31dSJim Jagielski            r = en.nextElement(); rl.append([g(r, "RedlineType"), g(r, "RedlineAuthor"), g(r, "RedlineDateTime"), g(r, "RedlineComment"), g(r, "RedlineSuccessorData") != "n/a" and bool(g(r, "RedlineSuccessorData"))])
154*0e16a31dSJim Jagielski    except Exception: rl = "n/a"
155*0e16a31dSJim Jagielski    o["redlines"] = rl
156*0e16a31dSJim Jagielski    o["bookmarks"] = tryf(lambda: sorted(doc.Bookmarks.ElementNames))
157*0e16a31dSJim Jagielski    o["refmarks"] = tryf(lambda: sorted(doc.ReferenceMarks.ElementNames))
158*0e16a31dSJim Jagielski    o["indexes"] = tryf(lambda: doc.DocumentIndexes.Count)
159*0e16a31dSJim Jagielski    o["shapes"] = [shape(doc.DrawPage.getByIndex(i)) for i in range(min(doc.DrawPage.Count, 150))]
160*0e16a31dSJim Jagielski    o["forms"] = forms_of(doc.DrawPage)
161*0e16a31dSJim Jagielski    o["nstyles"] = {k: len(doc.StyleFamilies.getByName(k).ElementNames) for k in doc.StyleFamilies.ElementNames}
162*0e16a31dSJim Jagielski    return o
163*0e16a31dSJim Jagielski
164*0e16a31dSJim Jagielskidef calc(doc):
165*0e16a31dSJim Jagielski    o = {"sheets": []}
166*0e16a31dSJim Jagielski    nfs = doc.NumberFormats; ftype = {}
167*0e16a31dSJim Jagielski    def ft(k):
168*0e16a31dSJim Jagielski        if k not in ftype: ftype[k] = tryf(lambda: nfs.getByKey(k).Type)
169*0e16a31dSJim Jagielski        return ftype[k]
170*0e16a31dSJim Jagielski    for i in range(doc.Sheets.Count):
171*0e16a31dSJim Jagielski        sh = doc.Sheets.getByIndex(i)
172*0e16a31dSJim Jagielski        cur = sh.createCursor(); cur.gotoEndOfUsedArea(False)
173*0e16a31dSJim Jagielski        rng = sh.getCellRangeByPosition(0, 0, cur.RangeAddress.EndColumn, cur.RangeAddress.EndRow)
174*0e16a31dSJim Jagielski        if cur.RangeAddress.EndColumn > 300 or cur.RangeAddress.EndRow > 3000:
175*0e16a31dSJim Jagielski            rng = sh.getCellRangeByPosition(0, 0, min(cur.RangeAddress.EndColumn, 300), min(cur.RangeAddress.EndRow, 3000))
176*0e16a31dSJim Jagielski        da = rng.DataArray; fa = rng.FormulaArray
177*0e16a31dSJim Jagielski        so = {"name": sh.Name, "end": [cur.RangeAddress.EndColumn, cur.RangeAddress.EndRow], "data_hash": h(repr(da)), "formula_hash": h(repr(fa)),
178*0e16a31dSJim Jagielski              "nformulas": sum(1 for r in fa for c in r if isinstance(c, str) and c.startswith("="))}
179*0e16a31dSJim Jagielski        cells = []
180*0e16a31dSJim Jagielski        nrow = min(len(da), 400)
181*0e16a31dSJim Jagielski        for r in range(nrow):
182*0e16a31dSJim Jagielski            for c in range(min(len(da[r]), 60)):
183*0e16a31dSJim Jagielski                if da[r][c] != "" and da[r][c] is not None:
184*0e16a31dSJim Jagielski                    cell = rng.getCellByPosition(c, r)
185*0e16a31dSJim Jagielski                    cells.append([r, c, ft(cell.NumberFormat), cell.getError(), cell.Type.value])
186*0e16a31dSJim Jagielski        so["cells_hash"] = h(repr(cells)); so["ncells"] = len(cells)
187*0e16a31dSJim Jagielski        so["errors"] = sum(1 for x in cells if x[3])
188*0e16a31dSJim Jagielski        so["annotations"] = [sh.Annotations.Count, h("".join(tryf(lambda: sh.Annotations.getByIndex(k).String, "") for k in range(min(sh.Annotations.Count, 100))))]
189*0e16a31dSJim Jagielski        so["charts"] = {n: chart_info(tryf(lambda: sh.Charts.getByName(n).EmbeddedObject, None)) for n in sh.Charts.ElementNames[:30]}
190*0e16a31dSJim Jagielski        urls = []
191*0e16a31dSJim Jagielski        for r in range(nrow):
192*0e16a31dSJim Jagielski            for c in range(min(len(da[r]), 40)):
193*0e16a31dSJim Jagielski                if isinstance(fa[r][c], str) and fa[r][c] and not fa[r][c].startswith("="):
194*0e16a31dSJim Jagielski                    cell = rng.getCellByPosition(c, r)
195*0e16a31dSJim Jagielski                    try:
196*0e16a31dSJim Jagielski                        en = cell.Text.createEnumeration()
197*0e16a31dSJim Jagielski                        while en.hasMoreElements():
198*0e16a31dSJim Jagielski                            pe = en.nextElement().createEnumeration()
199*0e16a31dSJim Jagielski                            while pe.hasMoreElements():
200*0e16a31dSJim Jagielski                                pt = pe.nextElement()
201*0e16a31dSJim Jagielski                                if pt.TextPortionType == "TextField": urls.append([r, c, tryf(lambda: pt.TextField.URL), tryf(lambda: pt.TextField.Representation)])
202*0e16a31dSJim Jagielski                    except Exception: pass
203*0e16a31dSJim Jagielski                    if len(urls) > 100: break
204*0e16a31dSJim Jagielski        so["urls"] = urls
205*0e16a31dSJim Jagielski        so["shapes"] = [shape(sh.DrawPage.getByIndex(k)) for k in range(min(sh.DrawPage.Count, 100))]
206*0e16a31dSJim Jagielski        so["forms"] = forms_of(sh.DrawPage)
207*0e16a31dSJim Jagielski        so["pagestyle"] = sh.PageStyle
208*0e16a31dSJim Jagielski        so["flags"] = gs(sh, ["IsVisible", "IsProtected"])
209*0e16a31dSJim Jagielski        o["sheets"].append(so)
210*0e16a31dSJim Jagielski    o["dde"] = tryf(lambda: [[n, doc.DDELinks.getByName(n).Application, doc.DDELinks.getByName(n).Topic, doc.DDELinks.getByName(n).Item] for n in doc.DDELinks.ElementNames])
211*0e16a31dSJim Jagielski    o["names"] = tryf(lambda: sorted(doc.NamedRanges.ElementNames))
212*0e16a31dSJim Jagielski    ps = doc.StyleFamilies.getByName("PageStyles")
213*0e16a31dSJim Jagielski    o["pagestyles"] = {n: gs(ps.getByName(n), ["BackColor", "BackTransparent", "FillStyle", "HeaderIsOn", "FooterIsOn", "IsLandscape", "Width", "Height", "ScaleToPagesX"]) for n in ps.ElementNames}
214*0e16a31dSJim Jagielski    cs = doc.StyleFamilies.getByName("CellStyles")
215*0e16a31dSJim Jagielski    o["cellstyles"] = {n: gs(cs.getByName(n), ["CellBackColor", "IsCellBackgroundTransparent", "CharHeight", "NumberFormat"]) for n in cs.ElementNames}
216*0e16a31dSJim Jagielski    return o
217*0e16a31dSJim Jagielski
218*0e16a31dSJim Jagielskidef draw(doc):
219*0e16a31dSJim Jagielski    o = {"pages": []}
220*0e16a31dSJim Jagielski    pages = doc.DrawPages
221*0e16a31dSJim Jagielski    for i in range(pages.Count):
222*0e16a31dSJim Jagielski        pg = pages.getByIndex(i)
223*0e16a31dSJim Jagielski        po = {"name": tryf(lambda: pg.Name), "shapes": [shape(pg.getByIndex(k)) for k in range(min(pg.Count, 300))], "forms": forms_of(pg),
224*0e16a31dSJim Jagielski              "layout": g(pg, "Layout"), "bg": tryf(lambda: gs(pg.Background, ["FillStyle", "FillColor", "FillTransparence", "FillGradientName"])), "master": tryf(lambda: pg.MasterPage.Name)}
225*0e16a31dSJim Jagielski        if has(doc, "com.sun.star.presentation.PresentationDocument"):
226*0e16a31dSJim Jagielski            po["notes"] = tryf(lambda: [pg.NotesPage.getByIndex(k).String[:40] for k in range(min(pg.NotesPage.Count, 20)) if hasattr(pg.NotesPage.getByIndex(k), "String")], "n/a")
227*0e16a31dSJim Jagielski        o["pages"].append(po)
228*0e16a31dSJim Jagielski    masters = doc.MasterPages
229*0e16a31dSJim Jagielski    o["masters"] = [[tryf(lambda: masters.getByIndex(i).Name), len(shape_list(masters.getByIndex(i)))] for i in range(masters.Count)]
230*0e16a31dSJim Jagielski    for fam in ("graphics", "Default"):
231*0e16a31dSJim Jagielski        pass
232*0e16a31dSJim Jagielski    try:
233*0e16a31dSJim Jagielski        gf = doc.StyleFamilies.getByName("graphics")
234*0e16a31dSJim Jagielski        o["graphicstyles"] = {n: gs(gf.getByName(n), ["FillStyle", "FillColor", "FillTransparence", "CharHeight", "LineStyle", "LineColor", "TextWritingMode"]) for n in gf.ElementNames}
235*0e16a31dSJim Jagielski    except Exception: pass
236*0e16a31dSJim Jagielski    return o
237*0e16a31dSJim Jagielskidef shape_list(pg): return [shape(pg.getByIndex(k)) for k in range(min(pg.Count, 300))]
238*0e16a31dSJim Jagielski
239*0e16a31dSJim JagielskiDESK = desktop(connect(port))
240*0e16a31dSJim Jagielskios.makedirs(outdir, exist_ok=True)
241*0e16a31dSJim Jagielskifor f in files:
242*0e16a31dSJim Jagielski    dest = os.path.join(outdir, os.path.basename(f) + ".json")
243*0e16a31dSJim Jagielski    if os.path.exists(dest): continue
244*0e16a31dSJim Jagielski    open(os.path.join(outdir, ".current"), "w").write(f)
245*0e16a31dSJim Jagielski    out = {}
246*0e16a31dSJim Jagielski    try:
247*0e16a31dSJim Jagielski        doc = load(DESK, f)
248*0e16a31dSJim Jagielski        out["type"] = ("writer" if has(doc, "com.sun.star.text.TextDocument") else "calc" if has(doc, "com.sun.star.sheet.SpreadsheetDocument")
249*0e16a31dSJim Jagielski                       else "impress" if has(doc, "com.sun.star.presentation.PresentationDocument") else "draw" if has(doc, "com.sun.star.drawing.DrawingDocument") else "other")
250*0e16a31dSJim Jagielski        out["props"] = tryf(lambda: docprops(doc), "ERR")
251*0e16a31dSJim Jagielski        out["basic"] = basic(doc)
252*0e16a31dSJim Jagielski        try:
253*0e16a31dSJim Jagielski            out["body"] = {"writer": writer, "calc": calc, "impress": draw, "draw": draw}.get(out["type"], lambda d: {})(doc)
254*0e16a31dSJim Jagielski        except Exception as e:
255*0e16a31dSJim Jagielski            out["body_error"] = repr(e)[:200]
256*0e16a31dSJim Jagielski        doc.close(True)
257*0e16a31dSJim Jagielski    except Exception as e:
258*0e16a31dSJim Jagielski        out["error"] = repr(e)[:200]
259*0e16a31dSJim Jagielski    json.dump(out, open(dest, "w"), sort_keys=True, indent=0, default=str)
260*0e16a31dSJim Jagielskiprint("done", len(files))
261