Class: Moxml::Adapter::Oga

Inherits:
Base
  • Object
show all
Defined in:
lib/moxml/adapter/oga.rb

Class Method Summary collapse

Methods inherited from Base

create_cdata, create_comment, create_declaration, create_doctype, create_element, create_namespace, create_processing_instruction, create_text, duplicate_node, patch_node, prepare_for_new_document, sax_supported?, set_attribute_name, set_attribute_value

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

.add_child(element, child_or_text) ⇒ Object



245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
# File 'lib/moxml/adapter/oga.rb', line 245

def add_child(element, child_or_text)
  child =
    if child_or_text.is_a?(String)
      create_native_text(child_or_text)
    else
      child_or_text
    end

  # Special handling for declarations on Oga documents
  if element.is_a?(::Oga::XML::Document) &&
      child.is_a?(::Oga::XML::XmlDeclaration)
    # Set as document's xml_declaration
    element.instance_variable_set(:@xml_declaration, child)
  end

  element.children << child
end

.add_next_sibling(node, sibling) ⇒ Object



274
275
276
277
278
279
280
281
282
283
# File 'lib/moxml/adapter/oga.rb', line 274

def add_next_sibling(node, sibling)
  if node.parent == sibling.parent
    # Oga doesn't manipulate children of the same parent
    dup_sibling = node.node_set.delete(sibling)
    index = node.node_set.index(node) + 1
    node.node_set.insert(index, dup_sibling)
  else
    node.after(sibling)
  end
end

.add_previous_sibling(node, sibling) ⇒ Object



263
264
265
266
267
268
269
270
271
272
# File 'lib/moxml/adapter/oga.rb', line 263

def add_previous_sibling(node, sibling)
  if node.parent == sibling.parent
    # Oga doesn't manipulate children of the same parent
    dup_sibling = node.node_set.delete(sibling)
    index = node.node_set.index(node)
    node.node_set.insert(index, dup_sibling)
  else
    node.before(sibling)
  end
end

.at_xpath(node, expression, namespaces = nil) ⇒ Object



380
381
382
383
384
385
386
387
388
389
# File 'lib/moxml/adapter/oga.rb', line 380

def at_xpath(node, expression, namespaces = nil)
  node.at_xpath(expression, namespaces: namespaces)
rescue ::Oga::XPath::Error => e
  raise Moxml::XPathError.new(
    e.message,
    expression: expression,
    adapter: "Oga",
    node: node,
  )
end

.attribute_element(attr) ⇒ Object



204
205
206
# File 'lib/moxml/adapter/oga.rb', line 204

def attribute_element(attr)
  attr.element
end

.attributes(element) ⇒ Object



208
209
210
211
212
213
214
215
# File 'lib/moxml/adapter/oga.rb', line 208

def attributes(element)
  return [] unless element.respond_to?(:attributes)

  # remove attributes-namespaces
  element.attributes.reject do |attr|
    attr.name == ::Oga::XML::Element::XMLNS_PREFIX || attr.namespace_name == ::Oga::XML::Element::XMLNS_PREFIX
  end
end

.cdata_content(node) ⇒ Object



327
328
329
# File 'lib/moxml/adapter/oga.rb', line 327

def cdata_content(node)
  node.text
end

.children(node) ⇒ Object



164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
# File 'lib/moxml/adapter/oga.rb', line 164

def children(node)
  all_children = []

  if node.is_a?(::Oga::XML::Document)
    all_children += [node.xml_declaration,
                     node.doctype].compact
  end

  return all_children unless node.respond_to?(:children)

  all_children + node.children.reject do |child|
    child.is_a?(::Oga::XML::Text) &&
      child.text.strip.empty? &&
      !(child.previous.nil? && child.next.nil?)
  end
end

.comment_content(node) ⇒ Object



335
336
337
# File 'lib/moxml/adapter/oga.rb', line 335

def comment_content(node)
  node.text
end

.create_document(_native_doc = nil) ⇒ Object



55
56
57
# File 'lib/moxml/adapter/oga.rb', line 55

def create_document(_native_doc = nil)
  ::Oga::XML::Document.new
end

.create_native_cdata(content) ⇒ Object



67
68
69
# File 'lib/moxml/adapter/oga.rb', line 67

def create_native_cdata(content)
  ::Oga::XML::Cdata.new(text: content)
end

.create_native_comment(content) ⇒ Object



71
72
73
# File 'lib/moxml/adapter/oga.rb', line 71

def create_native_comment(content)
  ::Oga::XML::Comment.new(text: content)
end

.create_native_declaration(version, encoding, standalone) ⇒ Object



85
86
87
88
89
90
91
92
# File 'lib/moxml/adapter/oga.rb', line 85

def create_native_declaration(version, encoding, standalone)
  attrs = {
    version: version,
    encoding: encoding,
    standalone: standalone,
  }.compact
  ::Moxml::Adapter::CustomizedOga::XmlDeclaration.new(attrs)
end

.create_native_doctype(name, external_id, system_id) ⇒ Object



75
76
77
78
79
# File 'lib/moxml/adapter/oga.rb', line 75

def create_native_doctype(name, external_id, system_id)
  ::Oga::XML::Doctype.new(
    name: name, public_id: external_id, system_id: system_id, type: "PUBLIC",
  )
end

.create_native_element(name) ⇒ Object



59
60
61
# File 'lib/moxml/adapter/oga.rb', line 59

def create_native_element(name)
  ::Oga::XML::Element.new(name: name)
end

.create_native_namespace(element, prefix, uri) ⇒ Object



110
111
112
113
114
115
116
117
118
119
# File 'lib/moxml/adapter/oga.rb', line 110

def create_native_namespace(element, prefix, uri)
  ns = element.available_namespaces[prefix]
  return ns unless ns.nil?

  # Oga creates an attribute and registers a namespace
  set_attribute(element,
                [::Oga::XML::Element::XMLNS_PREFIX, prefix].compact.join(":"), uri)
  element.register_namespace(prefix, uri)
  ::Oga::XML::Namespace.new(name: prefix, uri: uri)
end

.create_native_processing_instruction(target, content) ⇒ Object



81
82
83
# File 'lib/moxml/adapter/oga.rb', line 81

def create_native_processing_instruction(target, content)
  ::Oga::XML::ProcessingInstruction.new(name: target, text: content)
end

.create_native_text(content) ⇒ Object



63
64
65
# File 'lib/moxml/adapter/oga.rb', line 63

def create_native_text(content)
  ::Oga::XML::Text.new(text: content)
end

.declaration_attribute(declaration, attr_name) ⇒ Object



94
95
96
97
98
99
100
# File 'lib/moxml/adapter/oga.rb', line 94

def declaration_attribute(declaration, attr_name)
  unless ::Moxml::Declaration::ALLOWED_ATTRIBUTES.include?(attr_name.to_s)
    return
  end

  declaration.public_send(attr_name)
end

.document(node) ⇒ Object



193
194
195
196
197
198
# File 'lib/moxml/adapter/oga.rb', line 193

def document(node)
  current = node
  current = current.parent while parent(current)

  current
end

.get_attribute(element, name) ⇒ Object



232
233
234
# File 'lib/moxml/adapter/oga.rb', line 232

def get_attribute(element, name)
  element.attribute(name.to_s)
end

.get_attribute_value(element, name) ⇒ Object



236
237
238
# File 'lib/moxml/adapter/oga.rb', line 236

def get_attribute_value(element, name)
  element[name.to_s]
end

.inner_text(node) ⇒ Object



309
310
311
312
313
314
315
316
# File 'lib/moxml/adapter/oga.rb', line 309

def inner_text(node)
  if node.respond_to?(:inner_text)
    node.inner_text
  else
    # Oga::XML::Text node for example
    node.text
  end
end

.namespace(element) ⇒ Object



125
126
127
128
129
130
131
132
133
134
135
# File 'lib/moxml/adapter/oga.rb', line 125

def namespace(element)
  if element.respond_to?(:namespace)
    element.namespace
  elsif element.respond_to?(:namespaces)
    element.namespaces.values.last
  end
rescue NoMethodError
  # Oga attributes fail with NoMethodError:
  # undefined method `available_namespaces' for nil:NilClass
  nil
end

.namespace_definitions(node) ⇒ Object



362
363
364
365
366
# File 'lib/moxml/adapter/oga.rb', line 362

def namespace_definitions(node)
  return [] unless node.respond_to?(:namespaces)

  node.namespaces.values
end

.namespace_prefix(namespace) ⇒ Object



351
352
353
354
355
356
# File 'lib/moxml/adapter/oga.rb', line 351

def namespace_prefix(namespace)
  # nil for the default namespace
  return if namespace.name == ::Oga::XML::Element::XMLNS_PREFIX

  namespace.name
end

.namespace_uri(namespace) ⇒ Object



358
359
360
# File 'lib/moxml/adapter/oga.rb', line 358

def namespace_uri(namespace)
  namespace.uri
end

.next_sibling(node) ⇒ Object



185
186
187
# File 'lib/moxml/adapter/oga.rb', line 185

def next_sibling(node)
  node.next
end

.node_name(node) ⇒ Object



156
157
158
# File 'lib/moxml/adapter/oga.rb', line 156

def node_name(node)
  node.name
end

.node_type(node) ⇒ Object



141
142
143
144
145
146
147
148
149
150
151
152
153
154
# File 'lib/moxml/adapter/oga.rb', line 141

def node_type(node)
  case node
  when ::Oga::XML::Element then :element
  when ::Oga::XML::Text then :text
  when ::Oga::XML::Cdata then :cdata
  when ::Oga::XML::Comment then :comment
  when ::Oga::XML::Attribute then :attribute
  when ::Oga::XML::Namespace then :namespace
  when ::Oga::XML::ProcessingInstruction then :processing_instruction
  when ::Oga::XML::Document then :document
  when ::Oga::XML::Doctype then :doctype
  else :unknown
  end
end

.parent(node) ⇒ Object



181
182
183
# File 'lib/moxml/adapter/oga.rb', line 181

def parent(node)
  node.parent if node.respond_to?(:parent)
end

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



20
21
22
23
24
25
26
27
28
29
30
31
# File 'lib/moxml/adapter/oga.rb', line 20

def parse(xml, options = {})
  native_doc = begin
    ::Oga.parse_xml(xml, strict: options[:strict])
  rescue LL::ParserError => e
    raise Moxml::ParseError.new(
      e.message,
      source: xml.is_a?(String) ? xml[0..100] : nil,
    )
  end

  DocumentBuilder.new(Context.new(:oga)).build(native_doc)
end

.previous_sibling(node) ⇒ Object



189
190
191
# File 'lib/moxml/adapter/oga.rb', line 189

def previous_sibling(node)
  node.previous
end

.processing_instruction_content(node) ⇒ Object



343
344
345
# File 'lib/moxml/adapter/oga.rb', line 343

def processing_instruction_content(node)
  node.text
end

.processing_instruction_target(node) ⇒ Object



137
138
139
# File 'lib/moxml/adapter/oga.rb', line 137

def processing_instruction_target(node)
  node.name
end

.remove(node) ⇒ Object



285
286
287
288
289
290
291
292
293
294
# File 'lib/moxml/adapter/oga.rb', line 285

def remove(node)
  # Special handling for declarations on Oga documents
  if node.is_a?(::Oga::XML::XmlDeclaration) &&
      node.parent.is_a?(::Oga::XML::Document)
    # Clear document's xml_declaration when removing declaration
    node.parent.instance_variable_set(:@xml_declaration, nil)
  end

  node.remove
end

.remove_attribute(element, name) ⇒ Object



240
241
242
243
# File 'lib/moxml/adapter/oga.rb', line 240

def remove_attribute(element, name)
  attr = element.attribute(name.to_s)
  element.attributes.delete(attr) if attr
end

.replace(node, new_node) ⇒ Object



296
297
298
# File 'lib/moxml/adapter/oga.rb', line 296

def replace(node, new_node)
  node.replace(new_node)
end

.replace_children(node, new_children) ⇒ Object



300
301
302
303
# File 'lib/moxml/adapter/oga.rb', line 300

def replace_children(node, new_children)
  node.children = []
  new_children.each { |child| add_child(node, child) }
end

.root(document) ⇒ Object



200
201
202
# File 'lib/moxml/adapter/oga.rb', line 200

def root(document)
  document.children.find { |node| node.is_a?(::Oga::XML::Element) }
end

.sax_parse(xml, handler) ⇒ void

This method returns an undefined value.

SAX parsing implementation for Oga

Parameters:



38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# File 'lib/moxml/adapter/oga.rb', line 38

def sax_parse(xml, handler)
  bridge = OgaSAXBridge.new(handler)

  xml_string = xml.respond_to?(:read) ? xml.read : xml.to_s

  # Manually call start_document (Oga doesn't)
  handler.on_start_document

  ::Oga.sax_parse_xml(bridge, xml_string)

  # Manually call end_document (Oga doesn't)
  handler.on_end_document
rescue StandardError => e
  error = Moxml::ParseError.new(e.message)
  handler.on_error(error)
end

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



391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
# File 'lib/moxml/adapter/oga.rb', line 391

def serialize(node, options = {})
  # Oga's XmlGenerator doesn't support options directly
  # We need to handle declaration options ourselves for Document nodes
  if node.is_a?(::Oga::XML::Document)
    # Check if we should include declaration
    # Priority: explicit option > existence of xml_declaration node
    should_include_decl = if options.key?(:no_declaration)
                            !options[:no_declaration]
                          elsif options.key?(:declaration)
                            options[:declaration]
                          else
                            # Default: include if document has xml_declaration node
                            node.xml_declaration ? true : false
                          end

    if should_include_decl && !node.xml_declaration
      # Need to add declaration - create default one
      output = +""
      output << '<?xml version="1.0" encoding="UTF-8"?>'
      output << "\n"

      # Serialize doctype if present
      output << node.doctype.to_xml << "\n" if node.doctype

      # Serialize children
      node.children.each do |child|
        output << ::Moxml::Adapter::CustomizedOga::XmlGenerator.new(child).to_xml
      end

      return output
    elsif !should_include_decl
      # Skip xml_declaration
      output = +""

      # Serialize doctype if present
      output << node.doctype.to_xml << "\n" if node.doctype

      # Serialize root and other children
      node.children.each do |child|
        next if child.is_a?(::Oga::XML::XmlDeclaration)

        output << ::Moxml::Adapter::CustomizedOga::XmlGenerator.new(child).to_xml
      end

      return output
    end
  end

  # Default: use XmlGenerator
  ::Moxml::Adapter::CustomizedOga::XmlGenerator.new(node).to_xml
end

.set_attribute(element, name, value) ⇒ Object



217
218
219
220
221
222
223
224
225
226
227
228
229
230
# File 'lib/moxml/adapter/oga.rb', line 217

def set_attribute(element, name, value)
  namespace_name = nil
  if name.to_s.include?(":")
    namespace_name, name = name.to_s.split(":",
                                           2)
  end

  attr = ::Oga::XML::Attribute.new(
    name: name.to_s,
    namespace_name: namespace_name,
    value: value.to_s,
  )
  element.add_attribute(attr)
end

.set_cdata_content(node, content) ⇒ Object



331
332
333
# File 'lib/moxml/adapter/oga.rb', line 331

def set_cdata_content(node, content)
  node.text = content
end

.set_comment_content(node, content) ⇒ Object



339
340
341
# File 'lib/moxml/adapter/oga.rb', line 339

def set_comment_content(node, content)
  node.text = content
end

.set_declaration_attribute(declaration, attr_name, value) ⇒ Object



102
103
104
105
106
107
108
# File 'lib/moxml/adapter/oga.rb', line 102

def set_declaration_attribute(declaration, attr_name, value)
  unless ::Moxml::Declaration::ALLOWED_ATTRIBUTES.include?(attr_name.to_s)
    return
  end

  declaration.public_send("#{attr_name}=", value)
end

.set_namespace(element, ns_or_string) ⇒ Object



121
122
123
# File 'lib/moxml/adapter/oga.rb', line 121

def set_namespace(element, ns_or_string)
  element.namespace_name = ns_or_string.to_s
end

.set_node_name(node, name) ⇒ Object



160
161
162
# File 'lib/moxml/adapter/oga.rb', line 160

def set_node_name(node, name)
  node.name = name
end

.set_processing_instruction_content(node, content) ⇒ Object



347
348
349
# File 'lib/moxml/adapter/oga.rb', line 347

def set_processing_instruction_content(node, content)
  node.text = content
end

.set_root(doc, element) ⇒ Object



12
13
14
15
16
17
18
# File 'lib/moxml/adapter/oga.rb', line 12

def set_root(doc, element)
  # Clear existing root element if any - Oga's NodeSet needs special handling
  # We need to manually remove elements since NodeSet doesn't support clear or delete_if
  elements_to_remove = doc.children.select { |child| child.is_a?(::Oga::XML::Element) }
  elements_to_remove.each { |elem| doc.children.delete(elem) }
  doc.children << element
end

.set_text_content(node, content) ⇒ Object



318
319
320
321
322
323
324
325
# File 'lib/moxml/adapter/oga.rb', line 318

def set_text_content(node, content)
  if node.respond_to?(:inner_text=)
    node.inner_text = content
  else
    # Oga::XML::Text node for example
    node.text = content
  end
end

.text_content(node) ⇒ Object



305
306
307
# File 'lib/moxml/adapter/oga.rb', line 305

def text_content(node)
  node.text
end

.xpath(node, expression, namespaces = nil) ⇒ Object



368
369
370
371
372
373
374
375
376
377
378
# File 'lib/moxml/adapter/oga.rb', line 368

def xpath(node, expression, namespaces = nil)
  node.xpath(expression, {},
             namespaces: namespaces&.transform_keys(&:to_s)).to_a
rescue ::LL::ParserError => e
  raise Moxml::XPathError.new(
    e.message,
    expression: expression,
    adapter: "Oga",
    node: node,
  )
end