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, 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_namespace, create_native_processing_instruction, create_native_text, declaration_attribute, document, duplicate_node, 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

Methods inherited from Base

create_cdata, create_comment, create_declaration, create_doctype, create_document, create_element, create_namespace, create_processing_instruction, create_text, duplicate_node, 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_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:



110
111
112
113
# File 'lib/moxml/adapter/headed_ox.rb', line 110

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



128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
# File 'lib/moxml/adapter/headed_ox.rb', line 128

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

Override parse to use HeadedOx context instead of Ox context



29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/moxml/adapter/headed_ox.rb', line 29

def parse(xml, _options = {})
  native_doc = begin
    result = ::Ox.parse(xml)

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

  # Use :headed_ox context instead of :ox
  DocumentBuilder.new(Context.new(:headed_ox)).build(native_doc)
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:



60
61
62
63
64
65
66
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
# File 'lib/moxml/adapter/headed_ox.rb', line 60

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 = 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



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

def xpath_supported?
  true
end