1*0e16a31dSJim Jagielski#************************************************************** 2*0e16a31dSJim Jagielski# 3*0e16a31dSJim Jagielski# Licensed to the Apache Software Foundation (ASF) under one 4*0e16a31dSJim Jagielski# or more contributor license agreements. See the NOTICE file 5*0e16a31dSJim Jagielski# distributed with this work for additional information 6*0e16a31dSJim Jagielski# regarding copyright ownership. The ASF licenses this file 7*0e16a31dSJim Jagielski# to you under the Apache License, Version 2.0 (the 8*0e16a31dSJim Jagielski# "License"); you may not use this file except in compliance 9*0e16a31dSJim Jagielski# with the License. You may obtain a copy of the License at 10*0e16a31dSJim Jagielski# 11*0e16a31dSJim Jagielski# http://www.apache.org/licenses/LICENSE-2.0 12*0e16a31dSJim Jagielski# 13*0e16a31dSJim Jagielski# Unless required by applicable law or agreed to in writing, 14*0e16a31dSJim Jagielski# software distributed under the License is distributed on an 15*0e16a31dSJim Jagielski# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16*0e16a31dSJim Jagielski# KIND, either express or implied. See the License for the 17*0e16a31dSJim Jagielski# specific language governing permissions and limitations 18*0e16a31dSJim Jagielski# under the License. 19*0e16a31dSJim Jagielski# 20*0e16a31dSJim Jagielski#************************************************************** 21*0e16a31dSJim Jagielski 22*0e16a31dSJim Jagielskiimport os, sys 23*0e16a31dSJim Jagielskiimport xml.etree.ElementTree as ET 24*0e16a31dSJim Jagielski 25*0e16a31dSJim JagielskiHERE = os.path.dirname(os.path.abspath(__file__)) 26*0e16a31dSJim JagielskiSCHEMAS = [("", "OpenDocument-v1.2-os-schema.rng"), 27*0e16a31dSJim Jagielski ("manifest", "OpenDocument-v1.2-os-manifest-schema.rng")] 28*0e16a31dSJim JagielskiRNG = "{http://relaxng.org/ns/structure/1.0}" 29*0e16a31dSJim JagielskiXML_NS = "http://www.w3.org/XML/1998/namespace" 30*0e16a31dSJim JagielskiWILD = ("*", "*") 31*0e16a31dSJim Jagielski 32*0e16a31dSJim Jagielski 33*0e16a31dSJim Jagielskidef tag(e): 34*0e16a31dSJim Jagielski return e.tag[len(RNG):] 35*0e16a31dSJim Jagielski 36*0e16a31dSJim Jagielski 37*0e16a31dSJim Jagielskiclass Schema: 38*0e16a31dSJim Jagielski def __init__(self, path): 39*0e16a31dSJim Jagielski self.prefixes = {"xml": XML_NS} 40*0e16a31dSJim Jagielski for ev, v in ET.iterparse(path, events=("start-ns", "end")): 41*0e16a31dSJim Jagielski if ev == "start-ns": 42*0e16a31dSJim Jagielski p, uri = v 43*0e16a31dSJim Jagielski if self.prefixes.setdefault(p, uri) != uri: 44*0e16a31dSJim Jagielski sys.exit("prefix %r bound twice in %s" % (p, path)) 45*0e16a31dSJim Jagielski self.root = ET.parse(path).getroot() 46*0e16a31dSJim Jagielski self.defs = {} 47*0e16a31dSJim Jagielski for d in self.root.iter(RNG + "define"): 48*0e16a31dSJim Jagielski if d.get("name") in self.defs: 49*0e16a31dSJim Jagielski sys.exit("duplicate define %s" % d.get("name")) 50*0e16a31dSJim Jagielski self.defs[d.get("name")] = d 51*0e16a31dSJim Jagielski self.cache = {} 52*0e16a31dSJim Jagielski self.stack = set() 53*0e16a31dSJim Jagielski 54*0e16a31dSJim Jagielski def qname(self, text, is_attr): 55*0e16a31dSJim Jagielski text = text.strip() 56*0e16a31dSJim Jagielski if ":" in text: 57*0e16a31dSJim Jagielski p, local = text.split(":", 1) 58*0e16a31dSJim Jagielski return self.prefixes[p], local 59*0e16a31dSJim Jagielski if not is_attr: 60*0e16a31dSJim Jagielski sys.exit("unprefixed element name %r" % text) 61*0e16a31dSJim Jagielski return "", text 62*0e16a31dSJim Jagielski 63*0e16a31dSJim Jagielski def names(self, nc, is_attr): 64*0e16a31dSJim Jagielski t = tag(nc) 65*0e16a31dSJim Jagielski if t == "name": 66*0e16a31dSJim Jagielski if nc.get("ns") is not None: 67*0e16a31dSJim Jagielski sys.exit("name with ns attribute") 68*0e16a31dSJim Jagielski return [self.qname(nc.text, is_attr)] 69*0e16a31dSJim Jagielski if t == "choice": 70*0e16a31dSJim Jagielski parts = [self.names(c, is_attr) for c in nc] 71*0e16a31dSJim Jagielski return None if any(p is None for p in parts) else [n for p in parts for n in p] 72*0e16a31dSJim Jagielski return None 73*0e16a31dSJim Jagielski 74*0e16a31dSJim Jagielski def merge_group(self, parts): 75*0e16a31dSJim Jagielski out = {} 76*0e16a31dSJim Jagielski for part in parts: 77*0e16a31dSJim Jagielski for k, (req, vals) in part.items(): 78*0e16a31dSJim Jagielski if k in out: 79*0e16a31dSJim Jagielski r0, v0 = out[k] 80*0e16a31dSJim Jagielski vals = None if v0 is None or vals is None else v0 | vals 81*0e16a31dSJim Jagielski req = r0 or req 82*0e16a31dSJim Jagielski out[k] = (req, vals) 83*0e16a31dSJim Jagielski return out 84*0e16a31dSJim Jagielski 85*0e16a31dSJim Jagielski def merge_choice(self, parts): 86*0e16a31dSJim Jagielski out = {} 87*0e16a31dSJim Jagielski for part in parts: 88*0e16a31dSJim Jagielski for k, (req, vals) in part.items(): 89*0e16a31dSJim Jagielski if k in out: 90*0e16a31dSJim Jagielski r0, v0 = out[k] 91*0e16a31dSJim Jagielski vals = None if v0 is None or vals is None else v0 | vals 92*0e16a31dSJim Jagielski req = r0 and req 93*0e16a31dSJim Jagielski else: 94*0e16a31dSJim Jagielski req = req and all(k in p for p in parts) 95*0e16a31dSJim Jagielski out[k] = (req, vals) 96*0e16a31dSJim Jagielski return out 97*0e16a31dSJim Jagielski 98*0e16a31dSJim Jagielski def values(self, children): 99*0e16a31dSJim Jagielski parts = [self.value_of(c) for c in children] 100*0e16a31dSJim Jagielski return None if len(parts) != 1 else parts[0] 101*0e16a31dSJim Jagielski 102*0e16a31dSJim Jagielski def value_of(self, n): 103*0e16a31dSJim Jagielski t = tag(n) 104*0e16a31dSJim Jagielski if t == "value": 105*0e16a31dSJim Jagielski if n.get("type", "token") != "token" or n.get("datatypeLibrary"): 106*0e16a31dSJim Jagielski return None 107*0e16a31dSJim Jagielski return frozenset([n.text or ""]) 108*0e16a31dSJim Jagielski if t == "ref": 109*0e16a31dSJim Jagielski return self.values(list(self.defs[n.get("name")])) 110*0e16a31dSJim Jagielski if t == "choice": 111*0e16a31dSJim Jagielski parts = [self.value_of(c) for c in n] 112*0e16a31dSJim Jagielski return None if any(p is None for p in parts) else frozenset().union(*parts) 113*0e16a31dSJim Jagielski return None 114*0e16a31dSJim Jagielski 115*0e16a31dSJim Jagielski def attrs(self, n): 116*0e16a31dSJim Jagielski t = tag(n) 117*0e16a31dSJim Jagielski if t == "attribute": 118*0e16a31dSJim Jagielski kids = list(n) 119*0e16a31dSJim Jagielski if "name" in n.attrib: 120*0e16a31dSJim Jagielski names, body = [self.qname(n.get("name"), True)], kids 121*0e16a31dSJim Jagielski else: 122*0e16a31dSJim Jagielski names, body = self.names(kids[0], True), kids[1:] 123*0e16a31dSJim Jagielski if names is None: 124*0e16a31dSJim Jagielski return {WILD: (False, None)} 125*0e16a31dSJim Jagielski vals = self.values(body) if body else None 126*0e16a31dSJim Jagielski req = len(names) == 1 127*0e16a31dSJim Jagielski return {k: (req, vals) for k in names} 128*0e16a31dSJim Jagielski if t == "ref": 129*0e16a31dSJim Jagielski return self.attrs_define(n.get("name")) 130*0e16a31dSJim Jagielski if t in ("element", "value", "data", "text", "empty", "list", "notAllowed"): 131*0e16a31dSJim Jagielski return {} 132*0e16a31dSJim Jagielski parts = [self.attrs(c) for c in n] 133*0e16a31dSJim Jagielski if t == "choice": 134*0e16a31dSJim Jagielski return self.merge_choice(parts) 135*0e16a31dSJim Jagielski out = self.merge_group(parts) 136*0e16a31dSJim Jagielski if t in ("optional", "zeroOrMore"): 137*0e16a31dSJim Jagielski out = {k: (False, v) for k, (r, v) in out.items()} 138*0e16a31dSJim Jagielski return out 139*0e16a31dSJim Jagielski 140*0e16a31dSJim Jagielski def attrs_define(self, name): 141*0e16a31dSJim Jagielski if name in self.cache: 142*0e16a31dSJim Jagielski return self.cache[name] 143*0e16a31dSJim Jagielski if name in self.stack: 144*0e16a31dSJim Jagielski sys.exit("pattern recursion without element: %s" % name) 145*0e16a31dSJim Jagielski self.stack.add(name) 146*0e16a31dSJim Jagielski res = self.merge_group([self.attrs(c) for c in self.defs[name]]) 147*0e16a31dSJim Jagielski self.stack.discard(name) 148*0e16a31dSJim Jagielski self.cache[name] = res 149*0e16a31dSJim Jagielski return res 150*0e16a31dSJim Jagielski 151*0e16a31dSJim Jagielski def element_defs(self): 152*0e16a31dSJim Jagielski for e in self.root.iter(RNG + "element"): 153*0e16a31dSJim Jagielski kids = list(e) 154*0e16a31dSJim Jagielski if "name" in e.attrib: 155*0e16a31dSJim Jagielski names, body = [self.qname(e.get("name"), False)], kids 156*0e16a31dSJim Jagielski else: 157*0e16a31dSJim Jagielski names, body = self.names(kids[0], False), kids[1:] 158*0e16a31dSJim Jagielski yield names, self.merge_group([self.attrs(c) for c in body]) 159*0e16a31dSJim Jagielski 160*0e16a31dSJim Jagielski 161*0e16a31dSJim Jagielskidef extract(path): 162*0e16a31dSJim Jagielski s = Schema(path) 163*0e16a31dSJim Jagielski elements, per_elem = set(), {} 164*0e16a31dSJim Jagielski stats = {"element_nodes": 0, "wild_elements": 0, "wild_attr_elements": 0} 165*0e16a31dSJim Jagielski for names, attrs in s.element_defs(): 166*0e16a31dSJim Jagielski stats["element_nodes"] += 1 167*0e16a31dSJim Jagielski if names is None: 168*0e16a31dSJim Jagielski stats["wild_elements"] += 1 169*0e16a31dSJim Jagielski continue 170*0e16a31dSJim Jagielski if WILD in attrs: 171*0e16a31dSJim Jagielski stats["wild_attr_elements"] += len(names) 172*0e16a31dSJim Jagielski del attrs[WILD] 173*0e16a31dSJim Jagielski for nm in names: 174*0e16a31dSJim Jagielski elements.add(nm) 175*0e16a31dSJim Jagielski per_elem.setdefault(nm, []).append(attrs) 176*0e16a31dSJim Jagielski pairs = {} 177*0e16a31dSJim Jagielski for nm, defs in per_elem.items(): 178*0e16a31dSJim Jagielski keys = set().union(*defs) 179*0e16a31dSJim Jagielski for k in keys: 180*0e16a31dSJim Jagielski reqs = [d[k][0] if k in d else False for d in defs] 181*0e16a31dSJim Jagielski vals = [d[k][1] if k in d else frozenset() for d in defs] 182*0e16a31dSJim Jagielski vals = None if any(v is None for v in vals) else frozenset().union(*vals) 183*0e16a31dSJim Jagielski pairs[(nm, k)] = (all(reqs), vals) 184*0e16a31dSJim Jagielski return elements, pairs, stats 185*0e16a31dSJim Jagielski 186*0e16a31dSJim Jagielski 187*0e16a31dSJim Jagielskidef write(outdir, elements, pairs): 188*0e16a31dSJim Jagielski os.makedirs(outdir, exist_ok=True) 189*0e16a31dSJim Jagielski with open(os.path.join(outdir, "elements.tsv"), "w") as f: 190*0e16a31dSJim Jagielski for ns, local in sorted(elements): 191*0e16a31dSJim Jagielski f.write("%s\t%s\n" % (ns, local)) 192*0e16a31dSJim Jagielski with open(os.path.join(outdir, "attributes.tsv"), "w") as f, \ 193*0e16a31dSJim Jagielski open(os.path.join(outdir, "values.tsv"), "w") as g: 194*0e16a31dSJim Jagielski for (en, an), (req, vals) in sorted(pairs.items()): 195*0e16a31dSJim Jagielski f.write("%s\t%s\t%s\t%s\t%s\n" % (en + an + ("required" if req else "optional",))) 196*0e16a31dSJim Jagielski for v in sorted(vals or ()): 197*0e16a31dSJim Jagielski g.write("%s\t%s\t%s\t%s\t%s\n" % (en + an + (v,))) 198*0e16a31dSJim Jagielski 199*0e16a31dSJim Jagielski 200*0e16a31dSJim Jagielskidef main(): 201*0e16a31dSJim Jagielski if len(sys.argv) != 2: 202*0e16a31dSJim Jagielski sys.exit("usage: python3 -I cov_schema.py <out-dir>") 203*0e16a31dSJim Jagielski out = sys.argv[1] 204*0e16a31dSJim Jagielski for sub, name in SCHEMAS: 205*0e16a31dSJim Jagielski elements, pairs, stats = extract(os.path.join(HERE, "schema", name)) 206*0e16a31dSJim Jagielski write(os.path.join(out, sub), elements, pairs) 207*0e16a31dSJim Jagielski enum = [p for p, (r, v) in pairs.items() if v] 208*0e16a31dSJim Jagielski sys.stderr.write( 209*0e16a31dSJim Jagielski "%s: elements=%d (element nodes=%d) attributes=%d pairs=%d required=%d " 210*0e16a31dSJim Jagielski "enumerated pairs=%d values=%d\n" 211*0e16a31dSJim Jagielski " skipped wildcards: %d anyName/nsName element nodes, " 212*0e16a31dSJim Jagielski "%d elements with a wildcard attribute\n" 213*0e16a31dSJim Jagielski % (name, len(elements), stats["element_nodes"], 214*0e16a31dSJim Jagielski len({k for (_, k) in pairs}), len(pairs), 215*0e16a31dSJim Jagielski sum(1 for r, v in pairs.values() if r), len(enum), 216*0e16a31dSJim Jagielski sum(len(pairs[p][1]) for p in enum), 217*0e16a31dSJim Jagielski stats["wild_elements"], stats["wild_attr_elements"])) 218*0e16a31dSJim Jagielski 219*0e16a31dSJim Jagielski 220*0e16a31dSJim Jagielskimain() 221