1#!/usr/bin/env python3 2# usage: runpy.sh fixtures/t16e.py <port> <outdir> 3# Task 16a: runtime checks of the XML processing requirements of ODF 1.2 Part 1 section 2.4 E on hand-built Writer packages. 4# Prints "RESULT <name> <observation>" lines; the expectation of a conforming consumer is in the name or the comment. 5import sys, os, re, zipfile, uno 6from unohelp import connect, desktop, pv 7port, out = int(sys.argv[1]), os.path.abspath(sys.argv[2]) 8os.makedirs(out, exist_ok=True) 9desk = desktop(connect(port)) 10OFFICE = "urn:oasis:names:tc:opendocument:xmlns:office:1.0" 11TEXT = "urn:oasis:names:tc:opendocument:xmlns:text:1.0" 12STYLE = "urn:oasis:names:tc:opendocument:xmlns:style:1.0" 13XL = "http://www.w3.org/1999/xlink" 14MAN = ('<?xml version="1.0" encoding="UTF-8"?><manifest:manifest xmlns:manifest="urn:oasis:names:tc:opendocument:xmlns:manifest:1.0" manifest:version="1.2">' 15 '<manifest:file-entry manifest:full-path="/" manifest:media-type="application/vnd.oasis.opendocument.text"/>' 16 '<manifest:file-entry manifest:full-path="content.xml" manifest:media-type="text/xml"/>' 17 '<manifest:file-entry manifest:full-path="styles.xml" manifest:media-type="text/xml"/></manifest:manifest>') 18STYLES = ('<?xml version="1.0" encoding="UTF-8"?><office:document-styles xmlns:office="%s" xmlns:style="%s" xmlns:text="%s" office:version="1.2">' 19 '<office:styles><style:style style:name="P1" style:family="paragraph"><style:text-properties style:text-position="33%% 100%%"/></style:style></office:styles>' 20 '</office:document-styles>' % (OFFICE, STYLE, TEXT)) 21def pkg(name, content): 22 p = os.path.join(out, name) 23 z = zipfile.ZipFile(p, "w") 24 z.writestr("mimetype", "application/vnd.oasis.opendocument.text", compress_type=zipfile.ZIP_STORED) 25 z.writestr("META-INF/manifest.xml", MAN); z.writestr("styles.xml", STYLES); z.writestr("content.xml", content) 26 z.close(); return p 27def doc(body, root_ns=None, pre="", extra_attrs=""): 28 ns = root_ns if root_ns is not None else 'xmlns:office="%s" xmlns:text="%s" xmlns:style="%s" xmlns:xlink="%s"' % (OFFICE, TEXT, STYLE, XL) 29 return '<?xml version="1.0" encoding="UTF-8"?>%s<office:document-content %s office:version="1.2"><office:body><office:text>%s</office:text></office:body></office:document-content>' % (pre, ns, body) 30def observe(path): 31 try: 32 d = desk.loadComponentFromURL(uno.systemPathToFileUrl(path), "_blank", 0, (pv("Hidden", True),)) 33 except Exception as e: 34 return {"loaded": False, "error": repr(e)[:120]} 35 if d is None: 36 return {"loaded": False, "error": "None"} 37 r = {"loaded": True} 38 try: 39 r["text"] = d.Text.String.replace("\n", "|") 40 en = d.Text.createEnumeration(); refs = []; styles = [] 41 while en.hasMoreElements(): 42 e = en.nextElement() 43 if e.supportsService("com.sun.star.text.Paragraph"): 44 refs.append(e.MetadataReference.Second); styles.append(e.ParaStyleName) 45 r["xmlid"] = refs; r["parastyle"] = styles 46 r["bookmarks"] = d.Bookmarks.Count 47 except Exception as e: 48 r["obs_error"] = repr(e)[:100] 49 d.close(True); return r 50def run(name, content, note=""): 51 r = observe(pkg(name + ".odt", content)); print("RESULT %-34s %s %s" % (name, r, ("# " + note) if note else ""), flush=True) 52# ---- baseline and namespace handling 53run("00-baseline", doc('<text:p xml:id="p1">one</text:p><text:p>two</text:p><text:bookmark text:name="b1" xml:id="bk1"/>')) 54# prefixes renamed throughout (expected: identical to the baseline) 55b = doc('<text:p xml:id="p1">one</text:p><text:p>two</text:p><text:bookmark text:name="b1" xml:id="bk1"/>') 56b = re.sub(r'(</?|\s)text:', r'\1t:', b); b = b.replace('xmlns:text=', 'xmlns:t=') 57b = re.sub(r'(</?|\s)office:', r'\1o:', b); b = b.replace('xmlns:office=', 'xmlns:o=') 58run("01-prefixes-renamed", b, "conforming: same as baseline") 59# default namespace for the text elements 60run("02-default-namespace", doc('<p xml:id="p1">one</p><p>two</p>', root_ns='xmlns:office="%s" xmlns="%s" xmlns:style="%s"' % (OFFICE, TEXT, STYLE)), "conforming: same text") 61# unprefixed attribute under a default namespace: no namespace per Namespaces in XML, so style-name must be ignored 62run("03-unprefixed-attr", doc('<p style-name="P1">one</p>', root_ns='xmlns:office="%s" xmlns="%s" xmlns:style="%s"' % (OFFICE, TEXT, STYLE)), "conforming: parastyle stays Standard") 63# prefix rebound to another namespace inside the element: it is then not a text:p 64run("04-prefix-rebound", doc('<text:p xmlns:text="urn:example:other">hidden</text:p><text:p>shown</text:p>'), "conforming: only 'shown'") 65# undeclared prefix: not namespace-well-formed 66run("05-undeclared-prefix", doc('<text:p>one</text:p>', root_ns='xmlns:office="%s"' % OFFICE), "conforming: load fails") 67# unknown element and attribute in the ODF namespace 68run("06-unknown-content", doc('<text:foo>x</text:foo><text:p text:bogus="1">kept</text:p>'), "tolerated: 'kept'") 69# ---- xml:id 70run("10-xmlid-whitespace", doc('<text:p xml:id=" w1 ">one</text:p>'), "xml:id spec: normalised to w1") 71run("11-xmlid-duplicate", doc('<text:p xml:id="d1">one</text:p><text:p xml:id="d1">two</text:p>'), "xml:id spec: duplicate is an error") 72run("12-xmlid-not-ncname", doc('<text:p xml:id="1bad">one</text:p>'), "xml:id spec: invalid value") 73# ---- DTD and entities 74run("20-internal-entity", doc('<text:p>&e;</text:p>', pre='<!DOCTYPE office:document-content [<!ENTITY e "ENTITYTEXT">]>'), "expanded") 75host = open("/etc/hostname").read().strip() if os.path.exists("/etc/hostname") else "zzz" 76open(os.path.join(out, "secret.txt"), "w").write("SECRETFILE") 77run("21-external-entity", doc('<text:p>a&x;b</text:p>', pre='<!DOCTYPE office:document-content [<!ENTITY x SYSTEM "file://%s/secret.txt">]>' % out), "must NOT contain SECRETFILE") 78run("22-external-dtd", doc('<text:p>one</text:p>', pre='<!DOCTYPE office:document-content SYSTEM "file://%s/secret.txt">' % out), "must load, nothing fetched") 79laugh = '<!DOCTYPE office:document-content [<!ENTITY a "lol"><!ENTITY b "&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;"><!ENTITY c "&b;&b;&b;&b;&b;&b;&b;&b;&b;&b;"><!ENTITY d "&c;&c;&c;&c;&c;&c;&c;&c;&c;&c;"><!ENTITY e "&d;&d;&d;&d;&d;&d;&d;&d;&d;&d;"><!ENTITY f "&e;&e;&e;&e;&e;&e;&e;&e;&e;&e;"><!ENTITY g "&f;&f;&f;&f;&f;&f;&f;&f;&f;&f;"><!ENTITY h "&g;&g;&g;&g;&g;&g;&g;&g;&g;&g;"><!ENTITY i "&h;&h;&h;&h;&h;&h;&h;&h;&h;&h;">]>' 80run("23-entity-expansion-limit", doc('<text:p>&i;</text:p>', pre=laugh), "expansion 10^8: must not hang or exhaust memory") 81print("DONE") 82