XML XSLT Identity Transform
An identity template copies nodes and attributes recursively. Add a more specific template to change one selected field while retaining the rest of the structure.
How it works
This is a transformation into a new result, not an in-place update of the source file. copy preserves the current node’s kind and name; apply-templates processes attributes and children. The special quantity rule overrides the general identity rule for the unavailable packet. Preservation is structural rather than byte-for-byte: a serializer may change prefix spelling, quote style or declaration details. Signed inputs need their own canonicalization and signature verification workflow.
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 identity-transform.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 identity-transform.py source. Keep each lab in its own folder. Sources are served as text, not as public executable server scripts. Review the starting setup.
Example
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)
style_source = "<xsl:stylesheet version=\"1.0\" xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\"><xsl:output method=\"text\"/><xsl:template match=\"/\"><xsl:apply-templates select=\"exchange/packet[@id='p2']/quantity\"/></xsl:template><xsl:template match=\"@*|node()\"><xsl:copy><xsl:apply-templates select=\"@*|node()\"/></xsl:copy></xsl:template><xsl:template match=\"packet[@available='false']/quantity\"><xsl:text>Awaiting restock</xsl:text></xsl:template></xsl:stylesheet>"
transform = E.XSLT(parse(style_source), access_control=E.XSLTAccessControl.DENY_ALL)
print(str(transform(root)), end="")
Expected result
Awaiting restock
The root rule deliberately selects the changed field so this text-output example is easy to check. The supplemental boundary lab applies the identity pattern to a complete tree and verifies unchanged packet ids and the replaced quantity.
Check the exact contract in the XML specification, XPath 1.0, the XSD 1.0 primer, lxml validation documentation and Saxon’s processor documentation.