bhylu.com

Advertisements

XML Study Plan

Work towards one complete exchange rather than memorising every XML-related acronym. Keep a repeatable fixture and a checklist of accepted and rejected cases.

Your brief

Begin with a small vocabulary and a document you can explain by hand. Add a restrictive parser and explicit field checks, then test queries against missing and repeated records. Add an XSD when a shared structural contract becomes useful. Finish with a report and a checked local HTTP client. DTD, SOAP, RDF and WSDL are important when an existing integration requires them, but a new seed exchange does not need all of them simply to move three records.

Run the code

Use Python 3 with lxml 6.1.1 in a separate virtual environment. The complete listing embeds its own XML and any schema or stylesheet; run python study-plan.py. It does not need Apache or MySQL. The shared restrictive parse function rejects declarations for untrusted input. The trusted DTD/entity and streaming lessons explicitly explain their separate fixed-input boundaries.

Download the complete study-plan.py source. Keep each lab in its own folder. Sources are served as text, not as public executable server scripts. Review the starting setup.

Worked solution

Show the worked solution
study-plan.py

from lxml import etree as E

class DenyExternal(E.Resolver):
    def resolve(self, url, public_id, context):
        raise ValueError("External resources are disabled")

def parse(text):
    if "<!DOCTYPE" in text.upper() or "<!ENTITY" in text.upper():
        raise ValueError("Declarations are disabled for untrusted XML")
    parser = E.XMLParser(resolve_entities=False, load_dtd=False,
                         no_network=True, huge_tree=False)
    parser.resolvers.add(DenyExternal())
    return E.fromstring(text.encode("utf-8"), parser)

source = "<exchange region=\"west\">\n  <packet id=\"p1\" available=\"true\"><name>Marigold</name><quantity>12</quantity><category>flower</category></packet>\n  <packet id=\"p2\" available=\"false\"><name>Fenugreek</name><quantity>0</quantity><category>herb</category></packet>\n  <packet id=\"p3\" available=\"true\"><name>Basil</name><quantity>7</quantity><category>herb</category></packet>\n</exchange>"
root = parse(source)
checks = {
    "root": root.tag == "exchange",
    "three records": len(root.findall("packet")) == 3,
    "total stock": root.xpath("sum(packet/quantity)") == 19,
}
for name, passed in checks.items():
    print(name + ": " + ("pass" if passed else "fail"))

Expected result

root: pass
three records: pass
total stock: pass

The checklist captures three useful invariants of the fixture. Expand it with malformed input, wrong namespaces, invalid schema values and hostile declarations before calling an importer ready. Keep expected rejections as part of the normal review.

Extend and test

Create your own two-packet fixture, write its expected total before running it and add a rejection case for every field your importer requires.

Check the exact contract in the XML specification, XPath 1.0, the XSD 1.0 primer, lxml validation documentation and Saxon’s processor documentation.

Advertisements