krikri-xml
krikri-xml
A clean-room XML 1.0 (Fifth Edition) parser in pure Crystal. No C dependencies - the stdlib xml module links libxml2; this shard does not link any native library at all.
Provenance
Implemented directly from the W3C specification REC-xml-20081126 (Extensible Markup Language (XML) 1.0, Fifth Edition) plus Namespaces in XML 1.0. No code from other implementations was consulted or ported; the specification is the only reference.
Status
- Strict, non-recovering well-formedness parsing (like lxml: any well-formedness violation raises).
- DOM tree (
Document,Element,Text,CData,Comment,ProcessingInstruction,DocumentType) with a mutation API (append_child,add_next_sibling,add_prev_sibling,unlink,text=,set_attribute,delete_attribute) mirroring libxml2 move semantics. - Full internal DTD subset processing: ENTITY/ENTITY % declarations, ATTLIST defaults (with per-type attribute-value normalization), and the 4.4/4.5 reference treatment (Included / Included in literal / Bypassed / Forbidden) exactly as specified.
- Namespaces in XML 1.0 (default namespace, prefixed bindings, undeclaring, conflict detection).
to_xmlserialization, with aprettymode mirroring libxml2'sXML::SaveOptions::FORMAT.- XPath 1.0 evaluation (
KXML::XPath), implemented from REC-xpath-19991116 and covered byspec/xpath_spec.cr. Variable references ($name) resolve through an optionalvarsbinding map passed toevaluate/evaluate_nodes; the namespace axis works via synthesized namespace nodes;id()resolves ID attributes declared in the internal DTD subset. Unknown variables and syntax errors raiseKXML::XPath::Error. Name tests follow XPath 1.0 section 2.3: an unprefixed node test matches only no-namespace nodes (the defaultxmlnsdeclaration is not used); pass an explicitns_mapto resolve expression prefixes.
Not implemented (yet):
- External entity / external subset fetching (references to external entities are recognized and rejected - deliberate, since krikri's use is offline).
- Serializer fidelity to libxml2/lxml byte output (the current serializer is spec-shaped, not libxml2-shaped).
Usage
require "krikri-xml"
doc = KXML.parse(%(<root><child a="1">text</child></root>))
doc.root.not_nil!.elements[0]["a"] # => "1"
begin
KXML.parse("<root><a></root>")
rescue e : KXML::Error
e.line # line number of the violation
e.column # column of the violation
end
Deliberate parser policies
The spec mandates behaviors; some operational limits are parser policy and configurable constants on KXML::Parser:
MAX_ELEMENT_DEPTH(2_048, matching libxml2'sXML_MAX_DEPTH) - guards against stack exhaustion on deeply nested documents.MAX_ENTITY_DEPTH(64) andMAX_EXPANDED_BYTES(10M) - guards against billion-laughs attacks. The spec permits a processor to impose limits.- XML version
1.1documents are rejected (this is a 1.0 processor). - Character references and names follow the Fifth Edition productions exactly (NameChar per erratum E09).
Conformance
The W3C XML Conformance Test Suite (xmlts20130923.zip, vendored under testdata/) runs as a data-driven spec: spec/conformance_spec.cr reads the sub-catalog files from the zip, parses each catalog with this parser itself, and runs every test file whose expectations this parser can honestly check.
Results across the 15 sub-catalogs (James Clark xmltest, OASIS/NIST, Sun, IBM, Edinburgh errata-2e/3e/4e, Richard Tobin's Namespaces 1.0 + errata):
- 1,668 passed of 1,981 executed (well-formed accepted, not-wf rejected).
- 313 documented divergences - every one enumerated with its reason in
spec/conformance_spec.cr(KNOWN_DIVERGENCES). They fall into three classes:- 300 IBM PITarget tests expecting 4th-edition Letter/NameChar classes; this parser implements the 5th-edition
NameStartCharproduction, under which those characters are legal (the suite's own errata-4e tests agree). - Colon-containing names the suite's Name-production tests accept but a Namespaces-aware processor must reject (the suite is self-contradictory here; this parser follows the NS-aware reading, matching rmt-ns10-042/043/044).
- Encoding-declaration compatibility checks, which require real transcoding this parser does not perform.
- 300 IBM PITarget tests expecting 4th-edition Letter/NameChar classes; this parser implements the 5th-edition
- 319 skipped - tests that require external entities (
ENTITIESparameter/both/general) or non-UTF-8 encodings, which the parser deliberately does not support.
Development
crystal spec
The specs are organized one file per feature area (spec/parser_spec.cr, entities_spec.cr, doctype_spec.cr, namespaces_spec.cr, normalization_spec.cr, mutation_spec.cr, xpath_spec.cr), plus the W3C conformance suite (spec/conformance_spec.cr), which reads testdata/xmlts20130923.zip and runs every case whose expectations this parser can honestly check - roughly 2,000 additional cases on top of the unit specs.
krikri-xml
- 0
- 0
- 0
- 1
- 2
- 7 minutes ago
- September 24, 2026
MIT License
Fri, 25 Sep 2026 01:22:24 GMT