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, attachments, 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, has_declaration?, 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

actual_native, 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, has_declaration?, 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 = {}) ⇒ Object?

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:

  • (Object, nil)

    First native node or scalar value



134
135
136
137
# File 'lib/moxml/adapter/headed_ox.rb', line 134

def at_xpath(node, expression, namespaces = {})
  result = xpath(node, expression, namespaces)
  result.is_a?(Array) ? 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



152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
# File 'lib/moxml/adapter/headed_ox.rb', line 152

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 = {}) ⇒ Array, Object

Execute XPath query using Moxml’s XPath engine

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

Parameters:

  • node

    Starting node (native or wrapped)

  • expression (String)

    XPath expression

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

    Namespace prefix mappings

Returns:

  • (Array, Object)

    Native node array or scalar value



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
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
# 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)

  # Return native arrays for Node#xpath to wrap, scalars directly.
  # The adapter contract: xpath() returns Array<native> | scalar.
  case result
  when Array
    # XPath engine returns wrapped Moxml::Node objects.
    # Extract native nodes and deduplicate by object identity.
    native_nodes = result.map { |n| n.is_a?(Moxml::Node) ? n.native : n }
    seen = {}
    native_nodes.select do |native|
      id = native.object_id
      if seen[id]
        false
      else
        seen[id] = true
      end
    end
  when NodeSet
    # NodeSet from intermediate evaluation - extract natives and deduplicate
    seen = {}
    result.to_a.map(&:native).select do |native|
      id = native.object_id
      if seen[id]
        false
      else
        seen[id] = true
      end
    end
  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



142
143
144
# File 'lib/moxml/adapter/headed_ox.rb', line 142

def xpath_supported?
  true
end