bhylu.com

Advertisements

XML XPath Data Types

XPath 1.0 has node-sets, strings, numbers and Booleans. The result type affects how a query should be read and checked.

How it works

count and sum return numbers, even when a count is mathematically an integer. string converts a node-set using its first node in document order; it does not concatenate every match. A Boolean conversion of a non-empty node-set reports existence, not whether the selected text literally says true. Explicit result types make application code easier to reason about and prevent accidental comparisons between unrelated representations.

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-values.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-values.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

xpath-values.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)
print(root.xpath("sum(packet/quantity)"))
print(root.xpath("string(packet/name)"))
print(root.xpath("boolean(packet[@id='missing'])"))

Expected result

19.0
Marigold
False

The total is nineteen, string reads only Marigold and the missing identifier produces false. To retrieve every name, select text nodes and iterate rather than converting the whole node-set to one string.

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