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 Jagielski"""Statically extract the (namespace, local name) pairs that the ODF export 23*0e16a31dSJim Jagielskicode can emit. Usage: python3 -I cov_source.py <repo-main-dir> <out-dir> 24*0e16a31dSJim Jagielski 25*0e16a31dSJim JagielskiWrites emit.tsv, emit-unresolved.tsv and emit-skipped.tsv into <out-dir>. 26*0e16a31dSJim Jagielski""" 27*0e16a31dSJim Jagielski 28*0e16a31dSJim Jagielskiimport collections 29*0e16a31dSJim Jagielskiimport os 30*0e16a31dSJim Jagielskiimport re 31*0e16a31dSJim Jagielskiimport sys 32*0e16a31dSJim Jagielski 33*0e16a31dSJim JagielskiSRC_EXT = (".cxx", ".hxx", ".h") 34*0e16a31dSJim JagielskiSKIP_DIRS = {"test", "qa", "workben", "unotest", "transform", "unoxml", "solver"} 35*0e16a31dSJim JagielskiSKIP_PATHS = ("filter/source/svg/",) 36*0e16a31dSJim JagielskiKEEP_NAMES = {"impastp4.cxx"} 37*0e16a31dSJim JagielskiIMPORT_NAME = re.compile(r"import|imp", re.I) 38*0e16a31dSJim Jagielski 39*0e16a31dSJim JagielskiNS_RE = re.compile(r"XML_NAMESPACE_([A-Z][A-Z0-9_]*)") 40*0e16a31dSJim JagielskiTOK_RE = re.compile(r"\bXML_[A-Z_][A-Z0-9_]*\b") 41*0e16a31dSJim JagielskiSTR_RE = re.compile(r'"((?:[^"\\\n]|\\.)*)"') 42*0e16a31dSJim JagielskiNO_EXPORT = re.compile(r"\bMID_FLAG_NO_PROPERTY(_EXPORT)?\b|\bMID_SW_FLAG_NO_ITEM(_EXPORT)?\b") 43*0e16a31dSJim JagielskiELEMENT_ROW = re.compile(r"\bMID_(SW_)?FLAG_ELEMENT_ITEM(_EXPORT)?\b") 44*0e16a31dSJim JagielskiPROP_ROW = re.compile(r"\bMID_\w+|\bXML_(?:\w+_)?TYPE_|\bsizeof\s*\(") 45*0e16a31dSJim JagielskiEVENT_ROW = re.compile(r'^\{\s*"[^"]*"\s*,\s*XML_NAMESPACE_\w+\s*,\s*"') 46*0e16a31dSJim JagielskiEMIT_ATTR = ("AddAttribute", "AddAttributeASCII") 47*0e16a31dSJim JagielskiEMIT_FUNCS = set(EMIT_ATTR) | {"AddAttributeIdLegacy", "SvXMLElementExport", "GetQNameByKey"} 48*0e16a31dSJim JagielskiDECL_ARG = re.compile(r"^(?:const\s+|enum\s+|sal_uInt16\b|SvXMLExport\b|SvXMLAttributeList\b)") 49*0e16a31dSJim Jagielski 50*0e16a31dSJim Jagielski 51*0e16a31dSJim Jagielskidef read(path): 52*0e16a31dSJim Jagielski with open(path, errors="replace") as f: 53*0e16a31dSJim Jagielski return f.read() 54*0e16a31dSJim Jagielski 55*0e16a31dSJim Jagielski 56*0e16a31dSJim Jagielskidef mask(text): 57*0e16a31dSJim Jagielski """Blank comments and string contents (same length, newlines kept).""" 58*0e16a31dSJim Jagielski out = list(text) 59*0e16a31dSJim Jagielski i, n = 0, len(text) 60*0e16a31dSJim Jagielski while i < n: 61*0e16a31dSJim Jagielski c = text[i] 62*0e16a31dSJim Jagielski if c == "/" and text[i:i + 2] == "//": 63*0e16a31dSJim Jagielski j = text.find("\n", i) 64*0e16a31dSJim Jagielski j = n if j < 0 else j 65*0e16a31dSJim Jagielski for k in range(i, j): 66*0e16a31dSJim Jagielski out[k] = " " 67*0e16a31dSJim Jagielski i = j 68*0e16a31dSJim Jagielski elif c == "/" and text[i:i + 2] == "/*": 69*0e16a31dSJim Jagielski j = text.find("*/", i + 2) 70*0e16a31dSJim Jagielski j = n if j < 0 else j + 2 71*0e16a31dSJim Jagielski for k in range(i, j): 72*0e16a31dSJim Jagielski if text[k] != "\n": 73*0e16a31dSJim Jagielski out[k] = " " 74*0e16a31dSJim Jagielski i = j 75*0e16a31dSJim Jagielski elif c in "\"'": 76*0e16a31dSJim Jagielski j = i + 1 77*0e16a31dSJim Jagielski while j < n and text[j] != c and text[j] != "\n": 78*0e16a31dSJim Jagielski j += 2 if text[j] == "\\" else 1 79*0e16a31dSJim Jagielski for k in range(i + 1, min(j, n)): 80*0e16a31dSJim Jagielski out[k] = " " 81*0e16a31dSJim Jagielski i = j + 1 82*0e16a31dSJim Jagielski else: 83*0e16a31dSJim Jagielski i += 1 84*0e16a31dSJim Jagielski return "".join(out) 85*0e16a31dSJim Jagielski 86*0e16a31dSJim Jagielski 87*0e16a31dSJim Jagielskidef directive_spans(text): 88*0e16a31dSJim Jagielski """Character ranges of preprocessor lines, with backslash continuations.""" 89*0e16a31dSJim Jagielski spans = [] 90*0e16a31dSJim Jagielski for m in re.finditer(r"^[ \t]*#(?:[^\n]*\\\n)*[^\n]*", text, re.M): 91*0e16a31dSJim Jagielski spans.append((m.start(), m.end())) 92*0e16a31dSJim Jagielski return spans 93*0e16a31dSJim Jagielski 94*0e16a31dSJim Jagielski 95*0e16a31dSJim Jagielskidef blank(masked, spans): 96*0e16a31dSJim Jagielski out = list(masked) 97*0e16a31dSJim Jagielski for a, b in spans: 98*0e16a31dSJim Jagielski for k in range(a, b): 99*0e16a31dSJim Jagielski if out[k] != "\n": 100*0e16a31dSJim Jagielski out[k] = " " 101*0e16a31dSJim Jagielski return "".join(out) 102*0e16a31dSJim Jagielski 103*0e16a31dSJim Jagielski 104*0e16a31dSJim Jagielskidef match_close(m, i): 105*0e16a31dSJim Jagielski """Index of the bracket closing the one at m[i].""" 106*0e16a31dSJim Jagielski depth = 0 107*0e16a31dSJim Jagielski for k in range(i, len(m)): 108*0e16a31dSJim Jagielski c = m[k] 109*0e16a31dSJim Jagielski if c in "({[": 110*0e16a31dSJim Jagielski depth += 1 111*0e16a31dSJim Jagielski elif c in ")}]": 112*0e16a31dSJim Jagielski depth -= 1 113*0e16a31dSJim Jagielski if depth == 0: 114*0e16a31dSJim Jagielski return k 115*0e16a31dSJim Jagielski return len(m) - 1 116*0e16a31dSJim Jagielski 117*0e16a31dSJim Jagielski 118*0e16a31dSJim Jagielskidef split_args(m, open_i, close_i): 119*0e16a31dSJim Jagielski """(start, end) of each top-level argument between two brackets.""" 120*0e16a31dSJim Jagielski args, depth, start = [], 0, open_i + 1 121*0e16a31dSJim Jagielski for k in range(open_i + 1, close_i): 122*0e16a31dSJim Jagielski c = m[k] 123*0e16a31dSJim Jagielski if c in "({[": 124*0e16a31dSJim Jagielski depth += 1 125*0e16a31dSJim Jagielski elif c in ")}]": 126*0e16a31dSJim Jagielski depth -= 1 127*0e16a31dSJim Jagielski elif c == "," and depth == 0: 128*0e16a31dSJim Jagielski args.append((start, k)) 129*0e16a31dSJim Jagielski start = k + 1 130*0e16a31dSJim Jagielski args.append((start, close_i)) 131*0e16a31dSJim Jagielski return args 132*0e16a31dSJim Jagielski 133*0e16a31dSJim Jagielski 134*0e16a31dSJim Jagielskidef enclosing_call(m, pos): 135*0e16a31dSJim Jagielski """(function name, argument index, argument count) of the innermost call around pos.""" 136*0e16a31dSJim Jagielski depth = 0 137*0e16a31dSJim Jagielski for k in range(pos - 1, -1, -1): 138*0e16a31dSJim Jagielski c = m[k] 139*0e16a31dSJim Jagielski if c in ")}]": 140*0e16a31dSJim Jagielski depth += 1 141*0e16a31dSJim Jagielski elif c in "({[": 142*0e16a31dSJim Jagielski if depth == 0: 143*0e16a31dSJim Jagielski if c != "(": 144*0e16a31dSJim Jagielski return None 145*0e16a31dSJim Jagielski name = re.search(r"(\w+)\s*$", m[max(0, k - 60):k]) 146*0e16a31dSJim Jagielski a = split_args(m, k, match_close(m, k)) 147*0e16a31dSJim Jagielski idx = next((n for n, (x, y) in enumerate(a) if x <= pos < y), None) 148*0e16a31dSJim Jagielski return (name.group(1) if name else None), idx, len(a) 149*0e16a31dSJim Jagielski depth -= 1 150*0e16a31dSJim Jagielski return None 151*0e16a31dSJim Jagielski 152*0e16a31dSJim Jagielski 153*0e16a31dSJim Jagielskidef parse_tokens(path): 154*0e16a31dSJim Jagielski toks = {} 155*0e16a31dSJim Jagielski pat = re.compile(r'TOKEN\(\s*"((?:[^"\\]|\\.)*)"\s*,\s*(XML_\w+)\s*(?:/\*.*?\*/)?\s*\)') 156*0e16a31dSJim Jagielski for line in read(path).splitlines(): 157*0e16a31dSJim Jagielski if line.lstrip().startswith("//"): 158*0e16a31dSJim Jagielski continue 159*0e16a31dSJim Jagielski m = pat.search(line) 160*0e16a31dSJim Jagielski if m: 161*0e16a31dSJim Jagielski toks[m.group(2)] = m.group(1).replace('\\"', '"') 162*0e16a31dSJim Jagielski return toks 163*0e16a31dSJim Jagielski 164*0e16a31dSJim Jagielski 165*0e16a31dSJim JagielskiADD_RE = re.compile(r"(?:\b_GetNamespaceMap\s*\(\s*\)\s*\.|\bmpNamespaceMap\s*->)\s*Add(?:AtIndex)?\s*\(") 166*0e16a31dSJim Jagielski 167*0e16a31dSJim Jagielski 168*0e16a31dSJim Jagielskidef ns_adds(masked, clean, toks): 169*0e16a31dSJim Jagielski """(key, URI) pairs from export-side namespace map Add/AddAtIndex calls.""" 170*0e16a31dSJim Jagielski defs = dict(re.findall(r'^[ \t]*#[ \t]*define[ \t]+(\w+)[ \t]+"([^"]*)"', clean, re.M)) 171*0e16a31dSJim Jagielski out = [] 172*0e16a31dSJim Jagielski for m in ADD_RE.finditer(masked): 173*0e16a31dSJim Jagielski o = m.end() - 1 174*0e16a31dSJim Jagielski a = split_args(masked, o, match_close(masked, o)) 175*0e16a31dSJim Jagielski if len(a) not in (3, 4): 176*0e16a31dSJim Jagielski continue 177*0e16a31dSJim Jagielski key = NS_RE.fullmatch(masked[a[-1][0]:a[-1][1]].strip()) 178*0e16a31dSJim Jagielski arg = clean[a[-2][0]:a[-2][1]] 179*0e16a31dSJim Jagielski tok = re.search(r"\b(XML_N_\w+)", arg) 180*0e16a31dSJim Jagielski lit = next((defs[w] for w in re.findall(r"\w+", arg) if w in defs), None) 181*0e16a31dSJim Jagielski uri = toks.get(tok.group(1)) if tok else lit 182*0e16a31dSJim Jagielski if key and uri is not None: 183*0e16a31dSJim Jagielski out.append((key.group(1), uri)) 184*0e16a31dSJim Jagielski return out 185*0e16a31dSJim Jagielski 186*0e16a31dSJim Jagielski 187*0e16a31dSJim Jagielskiclass Macro: 188*0e16a31dSJim Jagielski def __init__(self, ns_i, ns_fixed, tok_i, tok_prefix): 189*0e16a31dSJim Jagielski self.ns_i, self.ns_fixed, self.tok_i, self.tok_prefix = ns_i, ns_fixed, tok_i, tok_prefix 190*0e16a31dSJim Jagielski 191*0e16a31dSJim Jagielski 192*0e16a31dSJim Jagielskidef parse_macros(text): 193*0e16a31dSJim Jagielski """Function-like macros that carry a namespace key and a token parameter.""" 194*0e16a31dSJim Jagielski macros = {} 195*0e16a31dSJim Jagielski for m in re.finditer(r"^[ \t]*#[ \t]*define[ \t]+(\w+)\(([^)]*)\)((?:[^\n]|\\\n)*)", text, re.M): 196*0e16a31dSJim Jagielski name, params, body = m.group(1), [p.strip() for p in m.group(2).split(",")], m.group(3) 197*0e16a31dSJim Jagielski ns = re.search(r"XML_NAMESPACE_##(\w+)", body) 198*0e16a31dSJim Jagielski fixed = re.search(r"XML_NAMESPACE_([A-Z][A-Z0-9_]*)\s*,\s*(\w+)", body) 199*0e16a31dSJim Jagielski if ns and ns.group(1) in params: 200*0e16a31dSJim Jagielski ns_i = params.index(ns.group(1)) 201*0e16a31dSJim Jagielski tok = re.search(r"(?:xmloff::token::)?XML_##(\w+)", body) 202*0e16a31dSJim Jagielski if tok and tok.group(1) in params: 203*0e16a31dSJim Jagielski macros[name] = Macro(ns_i, None, params.index(tok.group(1)), "XML_") 204*0e16a31dSJim Jagielski continue 205*0e16a31dSJim Jagielski tok = re.search(r"xmloff::token::(\w+)", body) 206*0e16a31dSJim Jagielski if tok and tok.group(1) in params: 207*0e16a31dSJim Jagielski macros[name] = Macro(ns_i, None, params.index(tok.group(1)), "") 208*0e16a31dSJim Jagielski elif fixed and fixed.group(2) in params: 209*0e16a31dSJim Jagielski macros[name] = Macro(None, fixed.group(1), params.index(fixed.group(2)), "") 210*0e16a31dSJim Jagielski return macros 211*0e16a31dSJim Jagielski 212*0e16a31dSJim Jagielski 213*0e16a31dSJim Jagielskidef included(text): 214*0e16a31dSJim Jagielski return re.findall(r'^[ \t]*#[ \t]*include[ \t]*["<]([^">]+)[">]', text, re.M) 215*0e16a31dSJim Jagielski 216*0e16a31dSJim Jagielski 217*0e16a31dSJim JagielskiKEYWORDS = {"if", "for", "while", "switch", "catch", "return", "sizeof", "else"} 218*0e16a31dSJim JagielskiDEF_TAIL = re.compile(r"\s*(?:const\s*)?(?:throw\s*\([^)]*\)\s*)?(?::[^{;]*)?\{") 219*0e16a31dSJim Jagielski 220*0e16a31dSJim Jagielski 221*0e16a31dSJim Jagielskidef func_defs(m): 222*0e16a31dSJim Jagielski """(name, parameter identifiers, body start, body end) of function definitions.""" 223*0e16a31dSJim Jagielski out = [] 224*0e16a31dSJim Jagielski for mm in re.finditer(r"\b([A-Za-z_]\w*)\s*\(", m): 225*0e16a31dSJim Jagielski if mm.group(1) in KEYWORDS: 226*0e16a31dSJim Jagielski continue 227*0e16a31dSJim Jagielski o = mm.end() - 1 228*0e16a31dSJim Jagielski c = match_close(m, o) 229*0e16a31dSJim Jagielski tail = DEF_TAIL.match(m, c + 1) 230*0e16a31dSJim Jagielski if not tail: 231*0e16a31dSJim Jagielski continue 232*0e16a31dSJim Jagielski params = [] 233*0e16a31dSJim Jagielski for a, b in split_args(m, o, c): 234*0e16a31dSJim Jagielski w = re.findall(r"\w+", re.sub(r"=.*", "", m[a:b], flags=re.S)) 235*0e16a31dSJim Jagielski params.append(w[-1] if len(w) > 1 else None) 236*0e16a31dSJim Jagielski b0 = tail.end() - 1 237*0e16a31dSJim Jagielski out.append((mm.group(1), params, b0, match_close(m, b0))) 238*0e16a31dSJim Jagielski return out 239*0e16a31dSJim Jagielski 240*0e16a31dSJim Jagielski 241*0e16a31dSJim Jagielskiclass Wrapper: 242*0e16a31dSJim Jagielski def __init__(self, tok_i, nparams, keys, ns_param, role): 243*0e16a31dSJim Jagielski self.tok_i, self.nparams, self.keys, self.ns_param, self.role = tok_i, nparams, keys, ns_param, role 244*0e16a31dSJim Jagielski 245*0e16a31dSJim Jagielski 246*0e16a31dSJim Jagielskiclass Scanner: 247*0e16a31dSJim Jagielski def __init__(self, main): 248*0e16a31dSJim Jagielski self.main = main 249*0e16a31dSJim Jagielski self.toks = parse_tokens(os.path.join(main, "xmloff/source/core/xmltoken.cxx")) 250*0e16a31dSJim Jagielski self.pairs = collections.defaultdict(lambda: {"roles": set(), "mods": set(), "ex": []}) 251*0e16a31dSJim Jagielski self.unres, self.skipped, self.files = [], [], [] 252*0e16a31dSJim Jagielski self.header_macros = {} 253*0e16a31dSJim Jagielski self.nskey_uri_global = {} 254*0e16a31dSJim Jagielski self.ns_local = {} 255*0e16a31dSJim Jagielski self.wrappers = collections.defaultdict(list) 256*0e16a31dSJim Jagielski self.cache = {} 257*0e16a31dSJim Jagielski self.funcs = {} 258*0e16a31dSJim Jagielski 259*0e16a31dSJim Jagielski def walk(self): 260*0e16a31dSJim Jagielski out = [] 261*0e16a31dSJim Jagielski for d, ds, fs in os.walk(self.main): 262*0e16a31dSJim Jagielski rel = os.path.relpath(d, self.main).replace(os.sep, "/") + "/" 263*0e16a31dSJim Jagielski ds[:] = [x for x in ds if x not in SKIP_DIRS] 264*0e16a31dSJim Jagielski if any(p in rel for p in SKIP_PATHS): 265*0e16a31dSJim Jagielski continue 266*0e16a31dSJim Jagielski for f in fs: 267*0e16a31dSJim Jagielski if f.endswith(SRC_EXT): 268*0e16a31dSJim Jagielski out.append(os.path.join(d, f)) 269*0e16a31dSJim Jagielski return sorted(out) 270*0e16a31dSJim Jagielski 271*0e16a31dSJim Jagielski @staticmethod 272*0e16a31dSJim Jagielski def eligible(path): 273*0e16a31dSJim Jagielski base = os.path.basename(path) 274*0e16a31dSJim Jagielski return base.lower() in KEEP_NAMES or not IMPORT_NAME.search(base) 275*0e16a31dSJim Jagielski 276*0e16a31dSJim Jagielski def uri(self, key, path): 277*0e16a31dSJim Jagielski key = re.sub(r"_IDX$", "", key) 278*0e16a31dSJim Jagielski uri = self.ns_local.get(path, {}).get(key) or self.nskey_uri_global.get(key) 279*0e16a31dSJim Jagielski if uri: 280*0e16a31dSJim Jagielski return uri 281*0e16a31dSJim Jagielski if key == "NONE": 282*0e16a31dSJim Jagielski return "" 283*0e16a31dSJim Jagielski if key == "XML": 284*0e16a31dSJim Jagielski return self.toks["XML_N_XML"] 285*0e16a31dSJim Jagielski return None 286*0e16a31dSJim Jagielski 287*0e16a31dSJim Jagielski def prepare(self): 288*0e16a31dSJim Jagielski texts = {} 289*0e16a31dSJim Jagielski for p in self.walk(): 290*0e16a31dSJim Jagielski t = read(p) 291*0e16a31dSJim Jagielski if "XML_NAMESPACE_" not in t: 292*0e16a31dSJim Jagielski continue 293*0e16a31dSJim Jagielski texts[p] = t 294*0e16a31dSJim Jagielski for p, t in texts.items(): 295*0e16a31dSJim Jagielski if p.endswith((".hxx", ".h")): 296*0e16a31dSJim Jagielski mc = parse_macros(t) 297*0e16a31dSJim Jagielski if mc: 298*0e16a31dSJim Jagielski self.header_macros[os.path.basename(p)] = mc 299*0e16a31dSJim Jagielski first = {} 300*0e16a31dSJim Jagielski for p, t in sorted(texts.items(), key=lambda kv: kv[0]): 301*0e16a31dSJim Jagielski if not self.eligible(p): 302*0e16a31dSJim Jagielski continue 303*0e16a31dSJim Jagielski self.files.append(p) 304*0e16a31dSJim Jagielski m = mask(t) 305*0e16a31dSJim Jagielski for key, tok in ns_adds(m, t, self.toks): 306*0e16a31dSJim Jagielski self.ns_local.setdefault(p, {})[key] = tok 307*0e16a31dSJim Jagielski if p.endswith("xmloff/source/core/xmlexp.cxx"): 308*0e16a31dSJim Jagielski self.nskey_uri_global[key] = tok 309*0e16a31dSJim Jagielski else: 310*0e16a31dSJim Jagielski first.setdefault(key, tok) 311*0e16a31dSJim Jagielski for key, tok in first.items(): 312*0e16a31dSJim Jagielski self.nskey_uri_global.setdefault(key, tok) 313*0e16a31dSJim Jagielski self.texts = texts 314*0e16a31dSJim Jagielski 315*0e16a31dSJim Jagielski def names(self, arg_masked, arg_clean): 316*0e16a31dSJim Jagielski """Names in a name argument, and whether anything was left unresolved.""" 317*0e16a31dSJim Jagielski found, rest = [], arg_masked 318*0e16a31dSJim Jagielski for m in STR_RE.finditer(arg_clean): 319*0e16a31dSJim Jagielski if m.group(1): 320*0e16a31dSJim Jagielski found.append(m.group(1)) 321*0e16a31dSJim Jagielski for m in TOK_RE.finditer(arg_masked): 322*0e16a31dSJim Jagielski if m.group(0) in self.toks and not m.group(0).startswith(("XML_NP_", "XML_N_")): 323*0e16a31dSJim Jagielski found.append(self.toks[m.group(0)]) 324*0e16a31dSJim Jagielski if not found and re.search(r"\bXML_NP?_[A-Z]", arg_masked): 325*0e16a31dSJim Jagielski return [], False 326*0e16a31dSJim Jagielski rest = TOK_RE.sub(" ", STR_RE.sub(" ", arg_clean)) 327*0e16a31dSJim Jagielski rest = re.sub(r"(?:::)?(?:xmloff::token::|token::|GetXMLToken|RTL_CONSTASCII_USTRINGPARAM|" 328*0e16a31dSJim Jagielski r"OUString|createFromAscii|rtl::|sal_Char|static_cast<[^>]*>)", " ", rest) 329*0e16a31dSJim Jagielski rest = re.sub(r"[\s()?:]|\bsal_True\b|\bsal_False\b", "", rest) 330*0e16a31dSJim Jagielski return found, bool(rest) or not found 331*0e16a31dSJim Jagielski 332*0e16a31dSJim Jagielski def record(self, path, line, key, names, role, row): 333*0e16a31dSJim Jagielski uri = self.uri(key, path) 334*0e16a31dSJim Jagielski if uri is None: 335*0e16a31dSJim Jagielski self.unres.append((path, line, "unmapped namespace key " + key, row)) 336*0e16a31dSJim Jagielski return 337*0e16a31dSJim Jagielski mod = os.path.relpath(path, self.main).split(os.sep)[0] 338*0e16a31dSJim Jagielski for n in names: 339*0e16a31dSJim Jagielski e = self.pairs[(uri, n)] 340*0e16a31dSJim Jagielski e["roles"].add(role) 341*0e16a31dSJim Jagielski e["mods"].add(mod) 342*0e16a31dSJim Jagielski e["ex"].append((os.path.relpath(path, self.main), line, role)) 343*0e16a31dSJim Jagielski 344*0e16a31dSJim Jagielski def scan_file(self, path): 345*0e16a31dSJim Jagielski text = self.texts[path] 346*0e16a31dSJim Jagielski spans = directive_spans(text) 347*0e16a31dSJim Jagielski m = blank(mask(text), spans) 348*0e16a31dSJim Jagielski clean = blank(text, spans) 349*0e16a31dSJim Jagielski macros = {} 350*0e16a31dSJim Jagielski for inc in included(text): 351*0e16a31dSJim Jagielski macros.update(self.header_macros.get(os.path.basename(inc), {})) 352*0e16a31dSJim Jagielski macros.update(parse_macros(text)) 353*0e16a31dSJim Jagielski self.cache[path] = (m, clean, text) 354*0e16a31dSJim Jagielski line_of = lambda i: text.count("\n", 0, i) + 1 355*0e16a31dSJim Jagielski snip = lambda a, b: " ".join(clean[a:b].split())[:160] 356*0e16a31dSJim Jagielski handled = set() 357*0e16a31dSJim Jagielski opener = self.openers(m) 358*0e16a31dSJim Jagielski 359*0e16a31dSJim Jagielski funcs = self.funcs[path] = func_defs(m) 360*0e16a31dSJim Jagielski tokmaps = [(t.end(), match_close(m, t.end() - 1)) 361*0e16a31dSJim Jagielski for t in re.finditer(r"SvXMLTokenMapEntry\s+\w+\s*\[[^\]]*\]\s*=\s*\{", m)] 362*0e16a31dSJim Jagielski in_tokmap = lambda i: any(a <= i < b for a, b in tokmaps) 363*0e16a31dSJim Jagielski 364*0e16a31dSJim Jagielski def wrap(ns_a, ns_b, nm_a, nm_b, role, i): 365*0e16a31dSJim Jagielski """Register the enclosing function as a wrapper when it forwards its parameters.""" 366*0e16a31dSJim Jagielski inner = [f for f in funcs if f[2] < i < f[3]] 367*0e16a31dSJim Jagielski if not inner: 368*0e16a31dSJim Jagielski return False 369*0e16a31dSJim Jagielski name, params, b0, b1 = min(inner, key=lambda f: f[3] - f[2]) 370*0e16a31dSJim Jagielski nm, ns = m[nm_a:nm_b].strip(), m[ns_a:ns_b].strip() 371*0e16a31dSJim Jagielski if nm not in params or name in EMIT_FUNCS or role == "either": 372*0e16a31dSJim Jagielski return False 373*0e16a31dSJim Jagielski if NS_RE.fullmatch(ns): 374*0e16a31dSJim Jagielski keys, ns_param = [NS_RE.fullmatch(ns).group(1)], None 375*0e16a31dSJim Jagielski elif ns in params: 376*0e16a31dSJim Jagielski keys, ns_param = None, ns 377*0e16a31dSJim Jagielski else: 378*0e16a31dSJim Jagielski return False 379*0e16a31dSJim Jagielski module = os.path.relpath(path, self.main).split(os.sep)[0] 380*0e16a31dSJim Jagielski self.wrappers[(module, name)].append( 381*0e16a31dSJim Jagielski Wrapper(params.index(nm), len(params), keys, params.index(ns) if ns_param else None, role)) 382*0e16a31dSJim Jagielski return True 383*0e16a31dSJim Jagielski 384*0e16a31dSJim Jagielski def pair_from(ns_a, ns_b, nm_a, nm_b, role, ctx_a, ctx_b, i, fixed=None, wrapok=True): 385*0e16a31dSJim Jagielski if in_tokmap(i): 386*0e16a31dSJim Jagielski return 387*0e16a31dSJim Jagielski keys = NS_RE.findall(m[ns_a:ns_b]) 388*0e16a31dSJim Jagielski names, bad = (fixed, False) if fixed else self.names(m[nm_a:nm_b], clean[nm_a:nm_b]) 389*0e16a31dSJim Jagielski row = snip(ctx_a, ctx_b) 390*0e16a31dSJim Jagielski for k in NS_RE.finditer(m[ns_a:ns_b]): 391*0e16a31dSJim Jagielski handled.add(ns_a + k.start()) 392*0e16a31dSJim Jagielski if (not keys or bad) and wrapok and not fixed and wrap(ns_a, ns_b, nm_a, nm_b, role, i): 393*0e16a31dSJim Jagielski return 394*0e16a31dSJim Jagielski if not keys: 395*0e16a31dSJim Jagielski self.unres.append((path, line_of(i), "namespace not a literal key", row)) 396*0e16a31dSJim Jagielski return 397*0e16a31dSJim Jagielski if bad: 398*0e16a31dSJim Jagielski self.unres.append((path, line_of(i), "name not a literal token or string", row)) 399*0e16a31dSJim Jagielski if not names: 400*0e16a31dSJim Jagielski return 401*0e16a31dSJim Jagielski if NO_EXPORT.search(clean[ctx_a:ctx_b]): 402*0e16a31dSJim Jagielski self.skipped.append((path, line_of(i), "not exported (flags)", row)) 403*0e16a31dSJim Jagielski return 404*0e16a31dSJim Jagielski for k in keys: 405*0e16a31dSJim Jagielski self.record(path, line_of(i), k, names, role, row) 406*0e16a31dSJim Jagielski 407*0e16a31dSJim Jagielski for name, params, b0, b1 in funcs: 408*0e16a31dSJim Jagielski ns_fn = [f for f in funcs if f[0] == re.sub(r"Name$", "Namespace", name) and name.endswith("AttributeName")] 409*0e16a31dSJim Jagielski if not ns_fn: 410*0e16a31dSJim Jagielski continue 411*0e16a31dSJim Jagielski ns_body = clean[ns_fn[0][2]:ns_fn[0][3]] 412*0e16a31dSJim Jagielski default = re.findall(r"return\s+XML_NAMESPACE_([A-Z][A-Z0-9_]*)\s*;", ns_body)[-1:] 413*0e16a31dSJim Jagielski special = {(y if x in ns_fn[0][1] else x): k for x, y, k in re.findall( 414*0e16a31dSJim Jagielski r"if\s*\(\s*(\w+)\s*==\s*(\w+)\s*\)\s*return\s+XML_NAMESPACE_(\w+)", ns_body)} 415*0e16a31dSJim Jagielski for cm in re.finditer(r'case\s+(\w+)\s*:\s*return\s+"([^"]+)"', clean[b0:b1]): 416*0e16a31dSJim Jagielski key = special.get(cm.group(1)) or default and default[0] 417*0e16a31dSJim Jagielski if key: 418*0e16a31dSJim Jagielski self.record(path, line_of(b0 + cm.start()), key, [cm.group(2)], "attribute", cm.group(0)) 419*0e16a31dSJim Jagielski 420*0e16a31dSJim Jagielski for mm in re.finditer(r"\b(%s)\s*\(" % "|".join(EMIT_ATTR), m): 421*0e16a31dSJim Jagielski o = mm.end() - 1 422*0e16a31dSJim Jagielski c = match_close(m, o) 423*0e16a31dSJim Jagielski a = split_args(m, o, c) 424*0e16a31dSJim Jagielski if len(a) < 2 or (len(a) == 2 and "XML_NAMESPACE_" not in m[a[0][0]:a[0][1]]): 425*0e16a31dSJim Jagielski continue 426*0e16a31dSJim Jagielski if DECL_ARG.match(m[a[0][0]:a[0][1]].strip()): 427*0e16a31dSJim Jagielski continue 428*0e16a31dSJim Jagielski pair_from(a[0][0], a[0][1], a[1][0], a[1][1], "attribute", mm.start(), c + 1, mm.start()) 429*0e16a31dSJim Jagielski 430*0e16a31dSJim Jagielski for mm in re.finditer(r"\bSvXMLElementExport\b\s*(?:\w+\s*)?\(", m): 431*0e16a31dSJim Jagielski o = mm.end() - 1 432*0e16a31dSJim Jagielski c = match_close(m, o) 433*0e16a31dSJim Jagielski a = split_args(m, o, c) 434*0e16a31dSJim Jagielski k = 2 if len(a) >= 6 else 1 435*0e16a31dSJim Jagielski if len(a) < 3 or len(a) <= k + 1 or DECL_ARG.match(m[a[k][0]:a[k][1]].strip()): 436*0e16a31dSJim Jagielski continue 437*0e16a31dSJim Jagielski pair_from(a[k][0], a[k][1], a[k + 1][0], a[k + 1][1], "element", mm.start(), c + 1, mm.start()) 438*0e16a31dSJim Jagielski 439*0e16a31dSJim Jagielski for mm in re.finditer(r"\bAddAttributeIdLegacy\s*\(", m): 440*0e16a31dSJim Jagielski o = mm.end() - 1 441*0e16a31dSJim Jagielski c = match_close(m, o) 442*0e16a31dSJim Jagielski a = split_args(m, o, c) 443*0e16a31dSJim Jagielski if len(a) == 2 and not DECL_ARG.match(m[a[0][0]:a[0][1]].strip()): 444*0e16a31dSJim Jagielski pair_from(a[0][0], a[0][1], 0, 0, "attribute", mm.start(), c + 1, mm.start(), ["id"]) 445*0e16a31dSJim Jagielski 446*0e16a31dSJim Jagielski for mm in re.finditer(r"\bGetQNameByKey\s*\(", m): 447*0e16a31dSJim Jagielski o = mm.end() - 1 448*0e16a31dSJim Jagielski c = match_close(m, o) 449*0e16a31dSJim Jagielski a = split_args(m, o, c) 450*0e16a31dSJim Jagielski up = enclosing_call(m, mm.start()) 451*0e16a31dSJim Jagielski if up and up[0] in EMIT_ATTR and up[1] >= (1 if up[2] == 2 else 2): 452*0e16a31dSJim Jagielski self.skipped.append((path, line_of(mm.start()), "QName used as an attribute value", snip(mm.start(), c + 1))) 453*0e16a31dSJim Jagielski for k in NS_RE.finditer(m[a[0][0]:a[0][1]]): 454*0e16a31dSJim Jagielski handled.add(a[0][0] + k.start()) 455*0e16a31dSJim Jagielski elif len(a) == 2 and STR_RE.search(clean[a[1][0]:a[1][1]]) and not TOK_RE.search(m[a[1][0]:a[1][1]]): 456*0e16a31dSJim Jagielski self.skipped.append((path, line_of(mm.start()), "QName with literal local name (a value)", snip(mm.start(), c + 1))) 457*0e16a31dSJim Jagielski elif len(a) == 2: 458*0e16a31dSJim Jagielski pair_from(a[0][0], a[0][1], a[1][0], a[1][1], "either", mm.start(), c + 1, mm.start(), wrapok=False) 459*0e16a31dSJim Jagielski 460*0e16a31dSJim Jagielski for name, mac in macros.items(): 461*0e16a31dSJim Jagielski for mm in re.finditer(r"\b%s\s*\(" % re.escape(name), m): 462*0e16a31dSJim Jagielski o = mm.end() - 1 463*0e16a31dSJim Jagielski c = match_close(m, o) 464*0e16a31dSJim Jagielski a = split_args(m, o, c) 465*0e16a31dSJim Jagielski if len(a) <= max(mac.ns_i or 0, mac.tok_i): 466*0e16a31dSJim Jagielski continue 467*0e16a31dSJim Jagielski if in_tokmap(mm.start()): 468*0e16a31dSJim Jagielski continue 469*0e16a31dSJim Jagielski row = snip(mm.start(), c + 1) 470*0e16a31dSJim Jagielski ns = mac.ns_fixed or m[a[mac.ns_i][0]:a[mac.ns_i][1]].strip() 471*0e16a31dSJim Jagielski tk = mac.tok_prefix + clean[a[mac.tok_i][0]:a[mac.tok_i][1]].strip() 472*0e16a31dSJim Jagielski names, bad = self.names(tk, tk) 473*0e16a31dSJim Jagielski if not re.fullmatch(r"[A-Z][A-Z0-9_]*", ns) or bad: 474*0e16a31dSJim Jagielski self.unres.append((path, line_of(mm.start()), "macro argument not a known key/token", row)) 475*0e16a31dSJim Jagielski continue 476*0e16a31dSJim Jagielski body = clean[mm.start():c] 477*0e16a31dSJim Jagielski if NO_EXPORT.search(body): 478*0e16a31dSJim Jagielski self.skipped.append((path, line_of(mm.start()), "not exported (flags)", row)) 479*0e16a31dSJim Jagielski continue 480*0e16a31dSJim Jagielski role = "element" if ELEMENT_ROW.search(body) else "attribute" if PROP_ROW.search(body) else "either" 481*0e16a31dSJim Jagielski self.record(path, line_of(mm.start()), ns, names, role, row) 482*0e16a31dSJim Jagielski 483*0e16a31dSJim Jagielski for mm in NS_RE.finditer(m): 484*0e16a31dSJim Jagielski if mm.start() in handled: 485*0e16a31dSJim Jagielski continue 486*0e16a31dSJim Jagielski j = mm.end() 487*0e16a31dSJim Jagielski while j < len(m) and m[j] in " \t\r\n": 488*0e16a31dSJim Jagielski j += 1 489*0e16a31dSJim Jagielski if j >= len(m) or m[j] != ",": 490*0e16a31dSJim Jagielski continue 491*0e16a31dSJim Jagielski br = opener.get(mm.start()) 492*0e16a31dSJim Jagielski if br is None: 493*0e16a31dSJim Jagielski continue 494*0e16a31dSJim Jagielski bi, bc = br 495*0e16a31dSJim Jagielski before = re.search(r"(\w+)\s*(\w*)\s*$", m[max(0, bi - 80):bi]) 496*0e16a31dSJim Jagielski if m[bi] == "(" and before and (before.group(1) in EMIT_FUNCS or before.group(1) == "SvXMLElementExport" 497*0e16a31dSJim Jagielski or before.group(2) and before.group(1) == "SvXMLElementExport"): 498*0e16a31dSJim Jagielski continue 499*0e16a31dSJim Jagielski a = split_args(m, bi, bc) 500*0e16a31dSJim Jagielski idx = next(n for n, (s, e) in enumerate(a) if s <= mm.start() < e) 501*0e16a31dSJim Jagielski if idx + 1 >= len(a) or m[a[idx][0]:a[idx][1]].strip() != mm.group(0): 502*0e16a31dSJim Jagielski continue 503*0e16a31dSJim Jagielski row_clean = clean[bi:bc + 1] 504*0e16a31dSJim Jagielski if m[bi] == "{" and re.search(r"\bXML_TOK_\w+", row_clean): 505*0e16a31dSJim Jagielski continue 506*0e16a31dSJim Jagielski if EVENT_ROW.match(row_clean): 507*0e16a31dSJim Jagielski self.skipped.append((path, line_of(mm.start()), "event-name translation (a value, not a name)", snip(bi, bc + 1))) 508*0e16a31dSJim Jagielski continue 509*0e16a31dSJim Jagielski if m[bi] == "{" or PROP_ROW.search(row_clean): 510*0e16a31dSJim Jagielski role = "element" if ELEMENT_ROW.search(row_clean) else "attribute" 511*0e16a31dSJim Jagielski if not PROP_ROW.search(row_clean): 512*0e16a31dSJim Jagielski role = "either" 513*0e16a31dSJim Jagielski else: 514*0e16a31dSJim Jagielski role = "either" 515*0e16a31dSJim Jagielski pair_from(a[idx][0], a[idx][1], a[idx + 1][0], a[idx + 1][1], role, bi, bc + 1, mm.start(), wrapok=False) 516*0e16a31dSJim Jagielski 517*0e16a31dSJim Jagielski def ns_default(self, module, fname, ns_param_i): 518*0e16a31dSJim Jagielski """Default namespace key(s) of the ns parameter of fname, from its declarations.""" 519*0e16a31dSJim Jagielski out = set() 520*0e16a31dSJim Jagielski for p, (m, clean, text) in self.cache.items(): 521*0e16a31dSJim Jagielski if os.path.relpath(p, self.main).split(os.sep)[0] != module: 522*0e16a31dSJim Jagielski continue 523*0e16a31dSJim Jagielski for mm in re.finditer(r"\b%s\s*\(" % re.escape(fname), m): 524*0e16a31dSJim Jagielski o = mm.end() - 1 525*0e16a31dSJim Jagielski a = split_args(m, o, match_close(m, o)) 526*0e16a31dSJim Jagielski if ns_param_i < len(a): 527*0e16a31dSJim Jagielski d = re.search(r"=\s*XML_NAMESPACE_([A-Z][A-Z0-9_]*)", m[a[ns_param_i][0]:a[ns_param_i][1]]) 528*0e16a31dSJim Jagielski if d: 529*0e16a31dSJim Jagielski out.add(d.group(1)) 530*0e16a31dSJim Jagielski return out 531*0e16a31dSJim Jagielski 532*0e16a31dSJim Jagielski def scan_wrapper_calls(self, path, discover): 533*0e16a31dSJim Jagielski """Resolve calls of wrapper functions; with discover, only register forwarders.""" 534*0e16a31dSJim Jagielski m, clean, text = self.cache[path] 535*0e16a31dSJim Jagielski module = os.path.relpath(path, self.main).split(os.sep)[0] 536*0e16a31dSJim Jagielski line_of = lambda i: text.count("\n", 0, i) + 1 537*0e16a31dSJim Jagielski found = 0 538*0e16a31dSJim Jagielski for (mod, fname), ws in list(self.wrappers.items()): 539*0e16a31dSJim Jagielski if mod != module: 540*0e16a31dSJim Jagielski continue 541*0e16a31dSJim Jagielski for mm in re.finditer(r"\b%s\s*\(" % re.escape(fname), m): 542*0e16a31dSJim Jagielski o = mm.end() - 1 543*0e16a31dSJim Jagielski c = match_close(m, o) 544*0e16a31dSJim Jagielski a = split_args(m, o, c) 545*0e16a31dSJim Jagielski a0 = m[a[0][0]:a[0][1]].strip() 546*0e16a31dSJim Jagielski if DEF_TAIL.match(m, c + 1) or re.fullmatch(r"[\w:]+(?:\s+[\w:*&]+)+\s*(=.*)?", a0, re.S): 547*0e16a31dSJim Jagielski continue 548*0e16a31dSJim Jagielski row = " ".join(clean[mm.start():c + 1].split())[:160] 549*0e16a31dSJim Jagielski res, fwd = set(), [] 550*0e16a31dSJim Jagielski for w in ws: 551*0e16a31dSJim Jagielski if not (w.tok_i < len(a) <= w.nparams): 552*0e16a31dSJim Jagielski continue 553*0e16a31dSJim Jagielski ta, tb = a[w.tok_i] 554*0e16a31dSJim Jagielski names, bad = self.names(m[ta:tb], clean[ta:tb]) 555*0e16a31dSJim Jagielski if w.keys: 556*0e16a31dSJim Jagielski keys = tuple(w.keys) 557*0e16a31dSJim Jagielski elif w.ns_param < len(a): 558*0e16a31dSJim Jagielski k = NS_RE.fullmatch(m[a[w.ns_param][0]:a[w.ns_param][1]].strip()) 559*0e16a31dSJim Jagielski keys = (k.group(1),) if k else None 560*0e16a31dSJim Jagielski else: 561*0e16a31dSJim Jagielski d = self.ns_default(module, fname, w.ns_param) 562*0e16a31dSJim Jagielski keys = tuple(d) if len(d) == 1 else None 563*0e16a31dSJim Jagielski if bad and keys: 564*0e16a31dSJim Jagielski fwd.append((m[ta:tb].strip(), keys, w.role)) 565*0e16a31dSJim Jagielski res.add(None if bad or not keys else (tuple(names), keys, w.role)) 566*0e16a31dSJim Jagielski if discover: 567*0e16a31dSJim Jagielski inner = [f for f in self.funcs[path] if f[2] < mm.start() < f[3]] 568*0e16a31dSJim Jagielski if inner and len(fwd) == 1 and len(res) == 1: 569*0e16a31dSJim Jagielski name, params, b0, b1 = min(inner, key=lambda f: f[3] - f[2]) 570*0e16a31dSJim Jagielski if fwd[0][0] in params and name not in EMIT_FUNCS: 571*0e16a31dSJim Jagielski nw = Wrapper(params.index(fwd[0][0]), len(params), list(fwd[0][1]), None, fwd[0][2]) 572*0e16a31dSJim Jagielski old = self.wrappers[(module, name)] 573*0e16a31dSJim Jagielski if not any(x.tok_i == nw.tok_i and x.nparams == nw.nparams and x.keys == nw.keys for x in old): 574*0e16a31dSJim Jagielski old.append(nw) 575*0e16a31dSJim Jagielski found += 1 576*0e16a31dSJim Jagielski continue 577*0e16a31dSJim Jagielski if len(res) != 1 or None in res: 578*0e16a31dSJim Jagielski if res and not (len(fwd) == 1 and any(f[2] < mm.start() < f[3] and fwd[0][0] in f[1] for f in self.funcs[path])): 579*0e16a31dSJim Jagielski self.unres.append((path, line_of(mm.start()), "wrapper %s() call not resolvable" % fname, row)) 580*0e16a31dSJim Jagielski continue 581*0e16a31dSJim Jagielski names, keys, role = res.pop() 582*0e16a31dSJim Jagielski for k in keys: 583*0e16a31dSJim Jagielski self.record(path, line_of(mm.start()), k, list(names), role, row) 584*0e16a31dSJim Jagielski return found 585*0e16a31dSJim Jagielski 586*0e16a31dSJim Jagielski @staticmethod 587*0e16a31dSJim Jagielski def openers(m): 588*0e16a31dSJim Jagielski """Map every XML_NAMESPACE_ match start to its innermost enclosing bracket.""" 589*0e16a31dSJim Jagielski starts = {x.start() for x in NS_RE.finditer(m)} 590*0e16a31dSJim Jagielski out, stack = {}, [] 591*0e16a31dSJim Jagielski pairs = {} 592*0e16a31dSJim Jagielski for i, c in enumerate(m): 593*0e16a31dSJim Jagielski if c in "({": 594*0e16a31dSJim Jagielski stack.append(i) 595*0e16a31dSJim Jagielski elif c in ")}": 596*0e16a31dSJim Jagielski if stack: 597*0e16a31dSJim Jagielski pairs[stack.pop()] = i 598*0e16a31dSJim Jagielski elif i in starts: 599*0e16a31dSJim Jagielski out[i] = stack[-1] if stack else None 600*0e16a31dSJim Jagielski res = {} 601*0e16a31dSJim Jagielski for s, o in out.items(): 602*0e16a31dSJim Jagielski if o is not None and o in pairs: 603*0e16a31dSJim Jagielski res[s] = (o, pairs[o]) 604*0e16a31dSJim Jagielski return res 605*0e16a31dSJim Jagielski 606*0e16a31dSJim Jagielski def write(self, outdir): 607*0e16a31dSJim Jagielski os.makedirs(outdir, exist_ok=True) 608*0e16a31dSJim Jagielski rows = [] 609*0e16a31dSJim Jagielski for (uri, name), e in self.pairs.items(): 610*0e16a31dSJim Jagielski known = e["roles"] - {"either"} 611*0e16a31dSJim Jagielski role = known.pop() if len(known) == 1 else "either" 612*0e16a31dSJim Jagielski ex = sorted(e["ex"], key=lambda x: (x[2] == "either", x[0], x[1]))[0] 613*0e16a31dSJim Jagielski rows.append((uri, name, role, ",".join(sorted(e["mods"])), "%s:%d" % ex[:2])) 614*0e16a31dSJim Jagielski rows.sort() 615*0e16a31dSJim Jagielski with open(os.path.join(outdir, "emit.tsv"), "w") as f: 616*0e16a31dSJim Jagielski f.write("# ODF export names found statically in %d export-side C++ files under main/.\n" % len(self.files)) 617*0e16a31dSJim Jagielski f.write("# Rule: .cxx/.hxx/.h under main/ whose name has no 'imp'/'import' (impastp4.cxx kept), outside test/qa/workben/unotest/unoxml/solver," 618*0e16a31dSJim Jagielski " xmloff/source/transform and filter/source/svg; shared tables (property maps, handlers) included;" 619*0e16a31dSJim Jagielski " import token-map rows (XML_TOK_) and rows flagged no-export dropped.\n") 620*0e16a31dSJim Jagielski f.write("# role: element/attribute from the emitting call or property-map flags; either = unknown or seen as both." 621*0e16a31dSJim Jagielski " Empty ns_uri = XML_NAMESPACE_NONE.\n") 622*0e16a31dSJim Jagielski f.write("# ns_uri\tlocal\trole\tmodules\texample_file:line\n") 623*0e16a31dSJim Jagielski for r in rows: 624*0e16a31dSJim Jagielski f.write("\t".join(r) + "\n") 625*0e16a31dSJim Jagielski for name, lst in (("emit-unresolved.tsv", self.unres), ("emit-skipped.tsv", self.skipped)): 626*0e16a31dSJim Jagielski with open(os.path.join(outdir, name), "w") as f: 627*0e16a31dSJim Jagielski f.write("# file:line\treason\tsnippet\n") 628*0e16a31dSJim Jagielski for p, ln, why, row in sorted(set(lst)): 629*0e16a31dSJim Jagielski f.write("%s:%d\t%s\t%s\n" % (os.path.relpath(p, self.main), ln, why, row)) 630*0e16a31dSJim Jagielski return rows 631*0e16a31dSJim Jagielski 632*0e16a31dSJim Jagielski 633*0e16a31dSJim Jagielskidef main(argv): 634*0e16a31dSJim Jagielski if len(argv) != 3: 635*0e16a31dSJim Jagielski sys.exit(__doc__) 636*0e16a31dSJim Jagielski sc = Scanner(argv[1]) 637*0e16a31dSJim Jagielski sc.prepare() 638*0e16a31dSJim Jagielski for p in sc.files: 639*0e16a31dSJim Jagielski sc.scan_file(p) 640*0e16a31dSJim Jagielski for _ in range(5): 641*0e16a31dSJim Jagielski if not sum(sc.scan_wrapper_calls(p, True) for p in sc.files): 642*0e16a31dSJim Jagielski break 643*0e16a31dSJim Jagielski for p in sc.files: 644*0e16a31dSJim Jagielski sc.scan_wrapper_calls(p, False) 645*0e16a31dSJim Jagielski rows = sc.write(argv[2]) 646*0e16a31dSJim Jagielski roles = collections.Counter(r[2] for r in rows) 647*0e16a31dSJim Jagielski print("tokens in xmltoken.cxx: %d; files scanned: %d" % (len(sc.toks), len(sc.files))) 648*0e16a31dSJim Jagielski print("distinct pairs: %d %s" % (len(rows), dict(roles))) 649*0e16a31dSJim Jagielski print("unresolved sites: %d; skipped rows: %d" % (len(set(sc.unres)), len(set(sc.skipped)))) 650*0e16a31dSJim Jagielski 651*0e16a31dSJim Jagielski 652*0e16a31dSJim Jagielskiif __name__ == "__main__": 653*0e16a31dSJim Jagielski main(sys.argv) 654