bhylu.com

Advertisements

XML Import Project

Build an importer that checks required ids, duplicate ids and non-negative quantities before calculating a total. Treat a failed record as a rejected exchange.

Your brief

Attempt the rules before opening the worked solution. The fixed fixture is small, so a set of ids and one pass are enough. Parsing alone does not enforce unique business identifiers or integer quantities. Make validation finish before applying any persistent change; otherwise a late error can leave a partially imported exchange. This project stays in memory and deliberately performs no database writes.

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 import-project.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 import-project.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
import-project.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)
seen = set()
total = 0
for packet in root.findall("packet"):
    identifier = packet.get("id")
    if not identifier or identifier in seen:
        raise ValueError("Missing or repeated id")
    seen.add(identifier)
    text = packet.findtext("quantity", "")
    if not text.isascii() or not text.isdigit():
        raise ValueError("Quantity must be non-negative digits")
    total += int(text)
print("Accepted", len(seen), "packets; total", total)

Expected result

Accepted 3 packets; total 19

The set catches repeated identity before any total is accepted. The digit rule is intentionally narrow and rejects signs, spaces and decimal fractions. A production contract should state whether those alternative numeric spellings are allowed.

Extend and test

Change the second id to p1, remove a quantity and set one quantity to -1 in separate runs. Each change should reject the exchange rather than produce a partial total.

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