Module: Canon::XmlParsing

Defined in:
lib/canon/xml_parsing.rb

Overview

Backend-agnostic XML parsing, serialization, and type dispatch.

Responsibilities (MECE):

  • Engine actions: parse selects the XML engine chosen by Canon::XmlBackend (raw Nokogiri or moxml with its resolved adapter — leptris when installed, nokogiri otherwise).
  • Node type dispatch: type queries and traversal answer for ANY recognized node (Nokogiri or moxml) regardless of the active engine — callers may hand us nodes from either library (user input, format detection). Dispatch is by node type, never by backend.

defined?(Nokogiri) guards keep Nokogiri constants unresolved under Opal (no NameError at runtime).

OCP: adding a new engine means extending Canon::XmlBackend detection plus the node-type union here; all comparator/formatter code stays untouched because it goes through this module.

Class Method Summary collapse

Class Method Details

.attribute_value(node, attr_name) ⇒ Object



163
164
165
166
167
168
169
# File 'lib/canon/xml_parsing.rb', line 163

def attribute_value(node, attr_name)
  if defined?(Nokogiri) && node.is_a?(Nokogiri::XML::Element)
    node[attr_name.to_s]
  elsif node.is_a?(Moxml::Element)
    node[attr_name.to_s]
  end
end

.attributes(node) ⇒ Object



153
154
155
156
157
158
159
160
161
# File 'lib/canon/xml_parsing.rb', line 153

def attributes(node)
  if defined?(Nokogiri) && node.is_a?(Nokogiri::XML::Element)
    node.attributes.values
  elsif node.is_a?(Moxml::Element)
    node.attributes
  else
    []
  end
end

.cdata?(node) ⇒ Boolean

Returns:

  • (Boolean)


98
99
100
101
102
# File 'lib/canon/xml_parsing.rb', line 98

def cdata?(node)
  return true if defined?(Nokogiri) && node.is_a?(Nokogiri::XML::CDATA)

  node.is_a?(Moxml::Cdata)
end

.children(node) ⇒ Object

--- Node traversal ---



120
121
122
123
124
125
126
127
128
# File 'lib/canon/xml_parsing.rb', line 120

def children(node)
  if defined?(Nokogiri) && node.is_a?(Nokogiri::XML::Node)
    node.children.to_a
  elsif node.is_a?(Moxml::Node)
    node.children.to_a
  else
    []
  end
end

.comment?(node) ⇒ Boolean

Returns:

  • (Boolean)


92
93
94
95
96
# File 'lib/canon/xml_parsing.rb', line 92

def comment?(node)
  return true if defined?(Nokogiri) && node.is_a?(Nokogiri::XML::Comment)

  node.is_a?(Moxml::Comment)
end

.document?(obj) ⇒ Boolean

--- Type checks (any recognized engine node) ---

Returns:

  • (Boolean)


68
69
70
71
72
# File 'lib/canon/xml_parsing.rb', line 68

def document?(obj)
  return true if defined?(Nokogiri) && obj.is_a?(Nokogiri::XML::Document)

  obj.is_a?(Moxml::Document)
end

.document_fragment?(obj) ⇒ Boolean

Returns:

  • (Boolean)


110
111
112
# File 'lib/canon/xml_parsing.rb', line 110

def document_fragment?(obj)
  defined?(Nokogiri) && obj.is_a?(Nokogiri::XML::DocumentFragment)
end

.dtd?(node) ⇒ Boolean

Returns:

  • (Boolean)


114
115
116
# File 'lib/canon/xml_parsing.rb', line 114

def dtd?(node)
  defined?(Nokogiri) && node.is_a?(Nokogiri::XML::DTD)
end

.element?(node) ⇒ Boolean

Returns:

  • (Boolean)


80
81
82
83
84
# File 'lib/canon/xml_parsing.rb', line 80

def element?(node)
  return true if defined?(Nokogiri) && node.is_a?(Nokogiri::XML::Element)

  node.is_a?(Moxml::Element)
end

.moxml_adapter_nameObject

The adapter moxml resolved on this runtime. This is the single source of truth for engine capability: moxml owns the preference order, canon never probes gems itself.



33
34
35
# File 'lib/canon/xml_parsing.rb', line 33

def moxml_adapter_name
  moxml_context.config.adapter_name
end

.moxml_contextObject



23
24
25
26
27
28
# File 'lib/canon/xml_parsing.rb', line 23

def moxml_context
  # Opal needs the explicit rexml adapter: stock oga requires its
  # C extension there. CRuby defers to moxml's preferred adapter
  # (leptris when installed and capable, nokogiri otherwise).
  @moxml_context ||= Moxml.new(RUBY_ENGINE == "opal" ? :rexml : nil)
end

.name(node) ⇒ Object



130
131
132
133
134
135
136
# File 'lib/canon/xml_parsing.rb', line 130

def name(node)
  if defined?(Nokogiri) && node.is_a?(Nokogiri::XML::Node)
    node.name
  elsif node.is_a?(Moxml::Node)
    node.name
  end
end

.namespace_definitions(node) ⇒ Object



171
172
173
174
175
176
177
178
179
# File 'lib/canon/xml_parsing.rb', line 171

def namespace_definitions(node)
  if defined?(Nokogiri) && node.is_a?(Nokogiri::XML::Element)
    node.namespace_definitions
  elsif node.is_a?(Moxml::Element)
    node.namespace_definitions
  else
    []
  end
end

.namespace_uri(node) ⇒ Object



189
190
191
192
193
194
195
# File 'lib/canon/xml_parsing.rb', line 189

def namespace_uri(node)
  if defined?(Nokogiri) && node.is_a?(Nokogiri::XML::Element)
    node.namespace&.href
  elsif node.is_a?(Moxml::Element)
    node.namespace_uri
  end
end

.node_type(node) ⇒ Object

Returns a symbol for all engines (:element, :text, :comment, etc.) or nil for unrecognised nodes.



199
200
201
202
203
204
205
# File 'lib/canon/xml_parsing.rb', line 199

def node_type(node)
  if defined?(Nokogiri) && node.is_a?(Nokogiri::XML::Node)
    nokogiri_node_type(node)
  elsif node.is_a?(Moxml::Node)
    moxml_node_type(node)
  end
end

.parent(node) ⇒ Object



181
182
183
184
185
186
187
# File 'lib/canon/xml_parsing.rb', line 181

def parent(node)
  return nil unless xml_node?(node)
  # Document nodes have no parent
  return nil if document?(node)

  node.parent
end

.parse(xml_string, options = {}) ⇒ Object

--- Parsing ---



39
40
41
42
43
44
45
# File 'lib/canon/xml_parsing.rb', line 39

def parse(xml_string, options = {})
  if XmlBackend.nokogiri?
    nokogiri_parse(xml_string, options)
  else
    moxml_parse(xml_string, options)
  end
end

.parse_fragment(xml_string) ⇒ Object



47
48
49
50
51
52
53
54
# File 'lib/canon/xml_parsing.rb', line 47

def parse_fragment(xml_string)
  if XmlBackend.nokogiri?
    Nokogiri::XML.fragment(xml_string).children.to_a
  else
    doc = moxml_context.parse("<__frag__>#{xml_string}</__frag__>", readonly: true)
    doc.root.children.to_a
  end
end

.processing_instruction?(node) ⇒ Boolean

Returns:

  • (Boolean)


104
105
106
107
108
# File 'lib/canon/xml_parsing.rb', line 104

def processing_instruction?(node)
  return true if defined?(Nokogiri) && node.is_a?(Nokogiri::XML::ProcessingInstruction)

  node.is_a?(Moxml::ProcessingInstruction)
end

.serialize(node) ⇒ Object

--- Serialization ---



58
59
60
61
62
63
64
# File 'lib/canon/xml_parsing.rb', line 58

def serialize(node)
  if defined?(Nokogiri) && node.is_a?(Nokogiri::XML::Document)
    node.to_xml(encoding: "UTF-8")
  else
    node.to_xml
  end
end

.text_content(node) ⇒ Object



138
139
140
141
142
143
144
145
146
147
148
149
150
151
# File 'lib/canon/xml_parsing.rb', line 138

def text_content(node)
  if defined?(Nokogiri) && node.is_a?(Nokogiri::XML::Node)
    node.content
  else
    case node
    when Moxml::Text, Moxml::Cdata, Moxml::Comment
      node.content.to_s
    when Moxml::Node
      node.text.to_s
    else
      node.to_s
    end
  end
end

.text_node?(node) ⇒ Boolean

Returns:

  • (Boolean)


86
87
88
89
90
# File 'lib/canon/xml_parsing.rb', line 86

def text_node?(node)
  return true if defined?(Nokogiri) && node.is_a?(Nokogiri::XML::Text)

  node.is_a?(Moxml::Text)
end

.xml_node?(obj) ⇒ Boolean

Returns:

  • (Boolean)


74
75
76
77
78
# File 'lib/canon/xml_parsing.rb', line 74

def xml_node?(obj)
  return true if defined?(Nokogiri) && obj.is_a?(Nokogiri::XML::Node)

  obj.is_a?(Moxml::Node)
end