HeadedOx adapter
General
The HeadedOx adapter combines Ox’s fast C-based XML parsing with Moxml’s comprehensive pure Ruby XPath 1.0 engine.
HeadedOx provides full XPath 1.0 functionality through a pure Ruby XPath engine layered on top of Ox’s fast C parser, allowing comprehensive XPath queries unhampered by the locate() method of the default Ox implementation.
| Trivia: the "Headed Ox" implementation allows the Ox to head in the right direction to find the desired nodes through its comprehensive XPath layer. |
| The HeadedOx adapter is added in v0.2.0. |
For complete architectural details and implementation guide, see HeadedOx Documentation.
# Use HeadedOx adapter
context = Moxml.new(:headed_ox)
doc = context.parse(xml_string)
# Full XPath 1.0 support - All 27 functions work
books = doc.xpath('//book[@price < 20]')
count = doc.xpath('count(//book)')
titles = doc.xpath('//book/title[contains(., "Ruby")]')
cheap = doc.xpath('//book[@price <= sum(//book/@price) div count(//book)]')| For complete XPath 1.0 specification with zero limitations today, use Nokogiri or Oga adapters. |
Features
-
Fast XML parsing (Ox C extension) - Same speed as standard Ox
-
6 of 13 XPath axes (46% - covers 80% of common usage patterns)
-
Complex XPath predicates with numeric/string/boolean expressions
-
Basic namespace-aware XPath queries (Ox namespace limitations apply)
-
Expression compilation and caching (1000-entry LRU cache)
-
Document construction and serialization through Ox
Architecture
HeadedOx is a hybrid adapter that layers Moxml’s pure Ruby XPath engine on top of Ox’s fast C parser:
┌─────────────────────────────────────────┐
│ Moxml Unified API │
│ (Document, Element, Node, Builder) │
└──────────────┬──────────────────────────┘
│
┌──────────────▼──────────────────────────┐
│ HeadedOx Adapter Layer │
│ (Delegates to Ox + XPath Engine) │
└──────────────┬──────────────────────────┘
│
┌────────┴─────────┐
├───────────┐ │
┌─────▼────┐ ┌────▼──────▼─────────────┐
│ Ox Gem │ │ Moxml XPath Engine │
│ (C Parse)│ │ (Pure Ruby) │
└──────────┘ │ • Lexer (Tokenize) │
│ • Parser (AST Build) │
│ • Compiler (Ruby Gen) │
│ • Cache (1000 entries) │
└─────────────────────────┘Known limitations
The following 16 test failures represent architectural boundaries in the Ox gem, not bugs in HeadedOx:
-
✗ Attribute wildcard syntax (
@*) - Ox API limitation -
✗ Namespace introspection methods - Ox doesn’t expose namespace data
-
✗ Parent node setter - Ox C struct immutability
-
✗ CDATA end marker escaping - Complex nested
]]>sequences -
✗ Complex namespace inheritance - Ox parses but doesn’t track
-
✗ Namespaced attribute access -
element["ns:attr"]pattern
| These are Ox limitations, not HeadedOx bugs. |
See HEADED_OX_LIMITATIONS.md for:
-
Detailed analysis of each limitation with examples
-
Workarounds and alternative approaches
-
Exact Ox API enhancements required for full compatibility
-
When to use HeadedOx vs other adapters decision guide
-
Future roadmap if Ox adds namespace introspection API
When to Use HeadedOx
You can use HeadedOx instead of Ox for all XML parsing needs, except when certain advanced XPath features are required.
-
Need fast parsing + comprehensive XPath beyond Ox’s
locate() -
XPath functions are critical (count, sum, contains, substring, etc.)
-
Complex predicates required (
[@price < average],[position() = last()]) -
Prefer pure Ruby XPath for debugging and customization
-
Basic namespace queries are sufficient
-
Document structure is mostly read-only
-
Performance matters but XPath features are non-negotiable
When not to use HeadedOx:
-
Need all 13 XPath axes (especially ancestor, sibling, following/preceding)
-
Advanced namespace operations required (introspection, complex inheritance)
-
Complex DOM modifications needed (parent node mutation)
-
CDATA escaping for nested markers is critical
-
Full Nokogiri feature parity required
For complete details, see HeadedOx Implementation Guide and HeadedOx Limitations Documentation.
XPath capabilities
| Category | XPath 1.0 Support | Details |
|---|---|---|
Functions | ✅ | All XPath 1.0 standard functions fully implemented and tested: String (10), Numeric (6), Boolean (4), Node (4), Position (2), Special (1) |
Axes | 6/13 axes (46%) | ✓ Implemented: child, self, parent, descendant, descendant-or-self (//), attribute (@) ✗ Missing: ancestor, sibling families, following/preceding families, namespace Coverage: 80% of real-world XPath usage patterns |
Operators | ✅ | All comparison (=, !=, <, >, ⇐, >=), arithmetic (+, -, *, div, mod), logical (and, or), and union (|) operators |
Predicates | ✅ of Core | Position predicates |
Parsing | ✅ Complete | Uses Ox’s C parser for maximum speed - fastest of all adapters |
Caching | ✅ LRU Cache | 1000-entry cache for compiled XPath expressions - significant performance boost for repeated queries |
What XPath queries work in HeadedOx
| This table is of v0.2.0. |
The following XPath patterns are fully functional:
# Descendant searches
doc.xpath('//book') # ✅ Works
doc.xpath('//book/title') # ✅ Works
# Attribute selection
doc.xpath('//book/@price') # ✅ Works
doc.xpath('//@price') # ✅ Works
# Predicates with operators
doc.xpath('//book[@price < 20]') # ✅ Works
doc.xpath('//book[1]') # ✅ Works (position)
doc.xpath('//book[last()]') # ✅ Works (last position)
doc.xpath('//book[@price=10 or @price=30]') # ✅ Works (logical)
# All 27 XPath 1.0 functions
doc.xpath('count(//book)') # ✅ Returns Float
doc.xpath('sum(//book/@price)') # ✅ Returns Float
doc.xpath('string(//title[1])') # ✅ Returns String
doc.xpath('concat("Price: ", //book/@price)') # ✅ String concatenation
doc.xpath('contains(//title, "Ruby")') # ✅ Boolean search
doc.xpath('substring(//title, 1, 5)') # ✅ String extraction
doc.xpath('normalize-space(//title)') # ✅ Whitespace handling
doc.xpath('boolean(//book[@price])') # ✅ Boolean conversion
doc.xpath('floor(//book/@price)') # ✅ Numeric rounding
doc.xpath('starts-with(//title, "Ruby")') # ✅ Prefix checking
# Complex queries with function composition
doc.xpath('//book[@price < 25]/title') # ✅ Chained paths
doc.xpath('//book[contains(title, "Ruby")]') # ✅ Functions in predicates
doc.xpath('//book[position() = last()]') # ✅ Position functions
doc.xpath('//book[string-length(title) > 10]') # ✅ String functions
doc.xpath('//book[@price < sum(//book/@price) div count(//book)]') # ✅ Complex arithmetic