Module: Canon::XmlParsing

Defined in:
lib/canon/xml_parsing.rb

Overview

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

Provides a unified API that delegates to the active backend (Nokogiri or moxml/Oga). Uses backend-branching (‘if XmlBackend.nokogiri?`) rather than `case/when` with constant references — this ensures Nokogiri constants are never resolved under Opal, preventing NameError at runtime.

OCP: adding a new backend only requires updating this module. DRY: all backend dispatch centralized here, not scattered across comparator/formatter files.

Class Method Summary collapse

Class Method Details

.attribute_value(node, attr_name) ⇒ Object



169
170
171
172
173
174
175
# File 'lib/canon/xml_parsing.rb', line 169

def attribute_value(node, attr_name)
  if XmlBackend.nokogiri?
    node.is_a?(Nokogiri::XML::Element) ? node[attr_name.to_s] : nil
  else
    node.is_a?(Moxml::Element) ? node[attr_name.to_s] : nil
  end
end

.attributes(node) ⇒ Object



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

def attributes(node)
  if XmlBackend.nokogiri?
    node.is_a?(Nokogiri::XML::Element) ? node.attributes.values : []
  else
    node.is_a?(Moxml::Element) ? node.attributes : []
  end
end

.canonicalize(node, options = {}) ⇒ Object



211
212
213
214
215
216
217
# File 'lib/canon/xml_parsing.rb', line 211

def canonicalize(node, options = {})
  if XmlBackend.nokogiri?
    node.canonicalize(options)
  else
    moxml_canonicalize(node, options)
  end
end

.cdata?(node) ⇒ Boolean

Returns:

  • (Boolean)


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

def cdata?(node)
  if XmlBackend.nokogiri?
    node.is_a?(Nokogiri::XML::CDATA) || node.is_a?(Moxml::Cdata)
  else
    node.is_a?(Moxml::Cdata)
  end
end

.children(node) ⇒ Object

— Node traversal —



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

def children(node)
  if XmlBackend.nokogiri?
    node.is_a?(Nokogiri::XML::Node) ? node.children.to_a : []
  else
    node.is_a?(Moxml::Node) ? node.children.to_a : []
  end
end

.comment?(node) ⇒ Boolean

Returns:

  • (Boolean)


88
89
90
91
92
93
94
# File 'lib/canon/xml_parsing.rb', line 88

def comment?(node)
  if XmlBackend.nokogiri?
    node.is_a?(Nokogiri::XML::Comment) || node.is_a?(Moxml::Comment)
  else
    node.is_a?(Moxml::Comment)
  end
end

.document?(obj) ⇒ Boolean

— Type checks (backend-safe) —

Both Nokogiri and Moxml are loaded as dependencies. XmlBackend determines which is used for parsing, but nodes from either library may flow through comparison code (e.g. tests, format detection). Under Nokogiri backend, both types are checked.

Returns:

  • (Boolean)


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

def document?(obj)
  if XmlBackend.nokogiri?
    obj.is_a?(Nokogiri::XML::Document) || obj.is_a?(Moxml::Document)
  else
    obj.is_a?(Moxml::Document)
  end
end

.document_fragment?(obj) ⇒ Boolean

Returns:

  • (Boolean)


112
113
114
115
116
117
118
# File 'lib/canon/xml_parsing.rb', line 112

def document_fragment?(obj)
  if XmlBackend.nokogiri?
    obj.is_a?(Nokogiri::XML::DocumentFragment)
  else
    false
  end
end

.dtd?(node) ⇒ Boolean

Returns:

  • (Boolean)


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

def dtd?(node)
  if XmlBackend.nokogiri?
    node.is_a?(Nokogiri::XML::DTD)
  else
    false
  end
end

.element?(node) ⇒ Boolean

Returns:

  • (Boolean)


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

def element?(node)
  if XmlBackend.nokogiri?
    node.is_a?(Nokogiri::XML::Element) || node.is_a?(Moxml::Element)
  else
    node.is_a?(Moxml::Element)
  end
end

.moxml_contextObject



16
17
18
# File 'lib/canon/xml_parsing.rb', line 16

def moxml_context
  @moxml_context ||= Moxml.new(RUBY_ENGINE == "opal" ? :rexml : :oga)
end

.name(node) ⇒ Object



138
139
140
141
142
143
144
# File 'lib/canon/xml_parsing.rb', line 138

def name(node)
  if XmlBackend.nokogiri?
    node.is_a?(Nokogiri::XML::Node) ? node.name : nil
  else
    node.is_a?(Moxml::Node) ? node.name : nil
  end
end

.namespace_definitions(node) ⇒ Object



177
178
179
180
181
182
183
# File 'lib/canon/xml_parsing.rb', line 177

def namespace_definitions(node)
  if XmlBackend.nokogiri?
    node.is_a?(Nokogiri::XML::Element) ? node.namespace_definitions : []
  else
    node.is_a?(Moxml::Element) ? node.namespace_definitions : []
  end
end

.namespace_uri(node) ⇒ Object



193
194
195
196
197
198
199
# File 'lib/canon/xml_parsing.rb', line 193

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

.node_type(node) ⇒ Object

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



203
204
205
206
207
208
209
# File 'lib/canon/xml_parsing.rb', line 203

def node_type(node)
  if XmlBackend.nokogiri?
    nokogiri_node_type(node)
  else
    moxml_node_type(node)
  end
end

.parent(node) ⇒ Object



185
186
187
188
189
190
191
# File 'lib/canon/xml_parsing.rb', line 185

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 —



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

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



30
31
32
33
34
35
36
37
# File 'lib/canon/xml_parsing.rb', line 30

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__>")
    doc.root.children.to_a
  end
end

.processing_instruction?(node) ⇒ Boolean

Returns:

  • (Boolean)


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

def processing_instruction?(node)
  if XmlBackend.nokogiri?
    node.is_a?(Nokogiri::XML::ProcessingInstruction) || node.is_a?(Moxml::ProcessingInstruction)
  else
    node.is_a?(Moxml::ProcessingInstruction)
  end
end

.serialize(node) ⇒ Object

— Serialization —



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

def serialize(node)
  if XmlBackend.nokogiri?
    nokogiri_serialize(node)
  else
    moxml_serialize(node)
  end
end

.text_content(node) ⇒ Object



146
147
148
149
150
151
152
153
154
155
156
157
158
159
# File 'lib/canon/xml_parsing.rb', line 146

def text_content(node)
  if XmlBackend.nokogiri?
    node.is_a?(Nokogiri::XML::Node) ? node.content : node.to_s
  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)


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

def text_node?(node)
  if XmlBackend.nokogiri?
    node.is_a?(Nokogiri::XML::Text) || node.is_a?(Moxml::Text)
  else
    node.is_a?(Moxml::Text)
  end
end

.xml_node?(obj) ⇒ Boolean

Returns:

  • (Boolean)


64
65
66
67
68
69
70
# File 'lib/canon/xml_parsing.rb', line 64

def xml_node?(obj)
  if XmlBackend.nokogiri?
    obj.is_a?(Nokogiri::XML::Node) || obj.is_a?(Moxml::Node)
  else
    obj.is_a?(Moxml::Node)
  end
end