XML Stock Report Project
Create a new report containing only available packets with positive quantities. Sort by identifier and keep the source tree unchanged.
Your brief
Separate selection, validation and output construction. An available flag alone is insufficient if a record has zero stock; the report rule needs both conditions. Building a new tree makes it clear which fields are exported and avoids accidentally leaking future source fields. A deterministic order simplifies review, but byte-level equality still depends on the serialization rules used by the caller.
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 report-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 report-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
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)
report = E.Element("available-stock")
packets = sorted(root.findall("packet"), key=lambda item: item.get("id", ""))
for packet in packets:
quantity = int(packet.findtext("quantity"))
if packet.get("available") == "true" and quantity > 0:
row = E.SubElement(report, "packet", id=packet.get("id"))
E.SubElement(row, "name").text = packet.findtext("name")
print(E.tostring(report, encoding="unicode"))
print("Source packets:", len(root.findall("packet")))
Expected result
<available-stock><packet id="p1"><name>Marigold</name></packet><packet id="p3"><name>Basil</name></packet></available-stock> Source packets: 3
The new report has two packets and the original still has three. Text assignment preserves escaping for future names containing punctuation. This worked solution assumes the importer has already checked required fields and numeric quantities.
Extend and test
Add an unavailable packet with positive stock and an available packet with zero stock. Neither should enter the report. Add an ampersand to a name and check a serialize/parse round trip.
Check the exact contract in the XML specification, XPath 1.0, the XSD 1.0 primer, lxml validation documentation and Saxon’s processor documentation.