Class: Moxml::Adapter::HeadedOx

Inherits:
Ox
  • Object
show all
Defined in:
lib/moxml/adapter/headed_ox.rb

Overview

HeadedOx adapter - combines Ox’s fast parsing with Moxml’s XPath engine.

This adapter uses:

  • Ox for XML parsing (fast C-based parser)

  • Moxml::XPath engine for comprehensive XPath 1.0 support

Unlike the standard Ox adapter which has limited XPath support through Ox’s locate() method, HeadedOx provides full XPath 1.0 functionality including all axes, predicates, and 27 standard functions.

Examples:

context = Moxml.new(:headed_ox)
doc = context.parse(xml_string)
results = doc.xpath('//book[@price < 10]/title')

Class Method Summary collapse

Methods inherited from Ox

add_child, add_next_sibling, add_previous_sibling, ancestors, assign_parents, attribute_element, attributes, cdata_content, children, comment_content, create_document, create_native_cdata, create_native_comment, create_native_declaration, create_native_doctype, create_native_element, create_native_entity_reference, create_native_namespace, create_native_processing_instruction, create_native_text, declaration_attribute, doctype_external_id, doctype_name, doctype_system_id, document, duplicate_node, entity_reference_name, get_attribute, get_attribute_value, inner_text, namespace, namespace_definitions, namespace_prefix, namespace_uri, next_sibling, node_name, node_type, parent, patch_node, previous_sibling, processing_instruction_content, processing_instruction_target, remove, remove_attribute, replace, replace_children, root, sax_parse, serialize, set_attribute, set_attribute_name, set_attribute_value, set_cdata_content, set_comment_content, set_declaration_attribute, set_namespace, set_node_name, set_processing_instruction_content, set_root, set_text_content, text_content, unpatch_node, validate_single_root

Methods inherited from Base

create_cdata, create_comment, create_declaration, create_doctype, create_document, create_element, create_entity_reference, create_namespace, create_processing_instruction, create_text, duplicate_node, entity_reference_name, patch_node, prepare_for_new_document, sax_parse, sax_supported?, set_attribute_name, set_attribute_value, set_root

Methods included from XmlUtils

#encode_entities, #normalize_xml_value, #validate_comment_content, #validate_declaration_encoding, #validate_declaration_standalone, #validate_declaration_version, #validate_element_name, #validate_entity_reference_name, #validate_pi_target, #validate_prefix, #validate_uri

Class Method Details

.at_xpath(node, expression, namespaces = {}) ⇒ Moxml::Node, ...

Execute XPath query and return first result

Parameters:

  • node (Moxml::Node)

    Starting node

  • expression (String)

    XPath expression

  • namespaces (Hash) (defaults to: {})

    Namespace prefix mappings

Returns:



117
118
119
120
# File 'lib/moxml/adapter/headed_ox.rb', line 117

def at_xpath(node, expression, namespaces = {})
  result = xpath(node, expression, namespaces)
  result.is_a?(NodeSet) ? result.first : result
end

.capabilitiesHash

Report adapter capabilities

HeadedOx extends Ox’s capabilities with full XPath support through Moxml’s XPath engine

Returns:

  • (Hash)

    Capability flags



135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
# File 'lib/moxml/adapter/headed_ox.rb', line 135

def capabilities
  {
    # Core adapter capabilities
    parse: true,

    # Parsing capabilities (inherited from Ox)
    sax_parsing: true,
    namespace_aware: true,
    namespace_support: :partial,
    dtd_support: true,
    parsing_speed: :fast,

    # XPath capabilities (provided by Moxml's XPath engine)
    xpath_support: :full,
    xpath_full: true,
    xpath_axes: :partial, # 6 of 13 axes: child, descendant, descendant-or-self, self, attribute, parent
    xpath_functions: :complete, # All 27 XPath 1.0 functions
    xpath_predicates: true,
    xpath_namespaces: true,
    xpath_variables: true,

    # Serialization capabilities (inherited from Ox)
    namespace_serialization: true,
    pretty_print: true,

    # Known limitations
    schema_validation: false,
    xslt_support: false,
  }
end

.parse(xml, options = {}, _context = nil) ⇒ Object

Override parse to use lazy wrapping like the Ox adapter. Previously used DocumentBuilder (eager tree construction causing ~176K allocations per 100-element parse). Lazy parse defers wrapper creation until nodes are accessed, matching Ox adapter behavior.



32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/moxml/adapter/headed_ox.rb', line 32

def parse(xml, options = {}, _context = nil)
  native_doc = begin
    result = ::Ox.parse(xml)

    # result can be either Document or Element
    if result.is_a?(::Ox::Document)
      assign_parents(result)
      validate_single_root(result) if options[:strict]
      result
    else
      doc = ::Ox::Document.new
      doc << result
      assign_parents(doc)
      doc
    end
  rescue ::Ox::ParseError => e
    raise Moxml::ParseError.new(
      e.message,
      source: xml.is_a?(String) ? xml[0..100] : nil,
    )
  end

  # Use provided context if available, otherwise create new one
  ctx = _context || Context.new(:headed_ox)
  Document.new(native_doc, ctx)
end

.xpath(node, expression, namespaces = {}) ⇒ Moxml::NodeSet, Object

Execute XPath query using Moxml’s XPath engine

This overrides the Ox adapter’s xpath method which uses locate().

Parameters:

  • node (Moxml::Node)

    Starting node (wrapped Moxml node)

  • expression (String)

    XPath expression

  • namespaces (Hash) (defaults to: {})

    Namespace prefix mappings

Returns:



67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/moxml/adapter/headed_ox.rb', line 67

def xpath(node, expression, namespaces = {})
  # If we receive a native node, wrap it first
  # Document#xpath passes @native, but our compiled XPath needs Moxml nodes
  unless node.is_a?(Moxml::Node)
    # Determine the context from the node if possible
    # For now, create a basic context for wrapped nodes
    ctx = Context.new(:headed_ox)

    # Wrap the native node - don't rebuild the whole document
    node = Moxml::Node.wrap(node, ctx)
  end

  # Parse XPath expression to AST
  ast = XPath::Parser.parse(expression)

  # Compile AST to executable Proc using class method
  proc = XPath::Compiler.compile_with_cache(ast, namespaces: namespaces)

  # Execute on the node (now guaranteed to be wrapped Moxml node)
  result = proc.call(node)

  # Wrap Array results in NodeSet, return other types directly
  case result
  when Array
    # Deduplicate by native object identity to handle descendant-or-self
    # which may yield the same native node multiple times
    nodeset = NodeSet.new(result, node.context)
    nodeset.uniq_by_native
  when NodeSet
    # Deduplicate NodeSet results as well
    result.uniq_by_native
  else
    # Scalar values (string, number, boolean) - return as-is
    result
  end
rescue StandardError => e
  raise Moxml::XPathError.new(
    "XPath execution failed: #{e.message}",
    expression: expression,
    adapter: "HeadedOx",
    node: node,
  )
end

.xpath_supported?Boolean

Check if XPath is supported

Returns:

  • (Boolean)

    Always true for HeadedOx



125
126
127
# File 'lib/moxml/adapter/headed_ox.rb', line 125

def xpath_supported?
  true
end