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