XML XPath Operators and Variables
Use arithmetic and logical operators to express a condition, but bind external values as variables instead of assembling query source from user input.
How it works
A query string is executable query syntax. Concatenating an identifier containing a quote can change its meaning or make it invalid. lxml’s XPath evaluator accepts named variables, keeping the query structure fixed. Numeric comparison converts values according to XPath 1.0 rules, and Boolean operators combine predicates. Validate the field contract separately so conversions do not silently accept malformed business data.
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 xpath-operators.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 xpath-operators.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)
print(root.xpath("packet[number(quantity) >= $minimum and @available='true']/@id", minimum=8))
print(root.xpath("packet[@id=$identifier]/name/text()", identifier="p3"))
Expected result
['p1'] ['Basil']
Only p1 meets the quantity threshold of eight. Binding p3 then reads Basil without editing the query text. Try an identifier containing a quote: it should be treated as a value and return no match, not become a new expression.
Check the exact contract in the XML specification, XPath 1.0, the XSD 1.0 primer, lxml validation documentation and Saxon’s processor documentation.