Class: Moxml::Adapter::Oga

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

Constant Summary

Constants inherited from Base

Base::ENTITY_MARKER, Base::ENTITY_MARKER_RE, Base::ENTITY_NAME_PATTERN, Base::ENTITY_NAME_RE, Base::SERIALIZED_ENTITY_MARKER_RE, Base::STANDARD_ENTITIES

Class Method Summary collapse

Methods inherited from Base

actual_native, create_cdata, create_comment, create_declaration, create_doctype, create_element, create_entity_reference, create_namespace, create_processing_instruction, create_text, duplicate_node, in_scope_namespaces, patch_node, prepare_for_new_document, preprocess_entities, restore_entities, 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_entity_reference_name, #validate_pi_target, #validate_prefix, #validate_uri

Class Method Details

.add_child(element, child_or_text) ⇒ Object



283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
# File 'lib/moxml/adapter/oga.rb', line 283

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)
    # Track declaration state in attachment map
    attachments.set(element, :xml_declaration, child)
  end

  element.children << child
end

.add_next_sibling(node, sibling) ⇒ Object



312
313
314
315
316
317
318
319
320
321
# File 'lib/moxml/adapter/oga.rb', line 312

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



301
302
303
304
305
306
307
308
309
310
# File 'lib/moxml/adapter/oga.rb', line 301

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

.adjacent_to_entity_reference?(node) ⇒ Boolean

Returns:

  • (Boolean)


210
211
212
# File 'lib/moxml/adapter/oga.rb', line 210

def adjacent_to_entity_reference?(node)
  entity_ref?(node.previous) || entity_ref?(node.next)
end

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



440
441
442
443
444
445
446
447
448
449
# File 'lib/moxml/adapter/oga.rb', line 440

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

.attachmentsObject



11
12
13
# File 'lib/moxml/adapter/oga.rb', line 11

def attachments
  @attachments ||= Moxml::NativeAttachment.new
end

.attribute_element(attr) ⇒ Object



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

def attribute_element(attr)
  attr.element
end

.attributes(element) ⇒ Object



246
247
248
249
250
251
252
253
# File 'lib/moxml/adapter/oga.rb', line 246

def attributes(element)
  return [] unless element.is_a?(::Oga::XML::Element)

  # 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



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

def cdata_content(node)
  node.text
end

.children(node) ⇒ Object



186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
# File 'lib/moxml/adapter/oga.rb', line 186

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.is_a?(::Oga::XML::Node) || node.is_a?(::Oga::XML::Document)

  child_nodes = node.children.to_a
  # Filter out whitespace-only text nodes at document level only.
  # Document-level whitespace (between <?xml?> and <root>) is
  # formatting, not content, and differs across adapters.
  # Whitespace inside elements (e.g. "FigureA.1" spacing) is
  # meaningful and must be preserved.
  if node.is_a?(::Oga::XML::Document)
    child_nodes = child_nodes.reject do |child|
      child.is_a?(::Oga::XML::Text) && child.text.strip.empty?
    end
  end
  all_children + child_nodes
end

.comment_content(node) ⇒ Object



372
373
374
# File 'lib/moxml/adapter/oga.rb', line 372

def comment_content(node)
  node.text
end

.create_document(_native_doc = nil) ⇒ Object



61
62
63
# File 'lib/moxml/adapter/oga.rb', line 61

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

.create_native_cdata(content, _owner_doc = nil) ⇒ Object



84
85
86
# File 'lib/moxml/adapter/oga.rb', line 84

def create_native_cdata(content, _owner_doc = nil)
  ::Oga::XML::Cdata.new(text: content)
end

.create_native_comment(content, _owner_doc = nil) ⇒ Object



88
89
90
# File 'lib/moxml/adapter/oga.rb', line 88

def create_native_comment(content, _owner_doc = nil)
  ::Oga::XML::Comment.new(text: content)
end

.create_native_declaration(version, encoding, standalone) ⇒ Object



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

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



92
93
94
95
96
97
# File 'lib/moxml/adapter/oga.rb', line 92

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

.create_native_element(name, _owner_doc = nil) ⇒ Object



65
66
67
# File 'lib/moxml/adapter/oga.rb', line 65

def create_native_element(name, _owner_doc = nil)
  ::Oga::XML::Element.new(name: name)
end

.create_native_entity_reference(name) ⇒ Object



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

def create_native_entity_reference(name)
  text = ::Oga::XML::Text.new
  text.text = "#{self::ENTITY_MARKER}#{name};"
  attachments.set(text, :entity_name, name)
  text
end

.create_native_namespace(element, prefix, uri) ⇒ Object



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

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



99
100
101
# File 'lib/moxml/adapter/oga.rb', line 99

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

.create_native_text(content, _owner_doc = nil) ⇒ Object



69
70
71
# File 'lib/moxml/adapter/oga.rb', line 69

def create_native_text(content, _owner_doc = nil)
  ::Oga::XML::Text.new(text: preprocess_entities(content))
end

.declaration_attribute(declaration, attr_name) ⇒ Object



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

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

  declaration.public_send(attr_name)
end

.doctype_external_id(native) ⇒ Object



412
413
414
415
416
417
418
# File 'lib/moxml/adapter/oga.rb', line 412

def doctype_external_id(native)
  if native.type == "SYSTEM"
    nil
  else
    native.public_id
  end
end

.doctype_name(native) ⇒ Object

Doctype accessor methods Note: Oga stores SYSTEM identifier in public_id for SYSTEM doctypes. See: Oga::XML::Doctype puts SYSTEM dtd in public_id, system_id is nil.



408
409
410
# File 'lib/moxml/adapter/oga.rb', line 408

def doctype_name(native)
  native.name
end

.doctype_system_id(native) ⇒ Object



420
421
422
423
424
425
426
# File 'lib/moxml/adapter/oga.rb', line 420

def doctype_system_id(native)
  if native.type == "SYSTEM"
    native.public_id
  else
    native.system_id
  end
end

.document(node) ⇒ Object



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

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

  current
end

.entity_ref?(node) ⇒ Boolean

Returns:

  • (Boolean)


214
215
216
217
# File 'lib/moxml/adapter/oga.rb', line 214

def entity_ref?(node)
  node.is_a?(::Oga::XML::Text) &&
    attachments.get(node, :entity_name)
end

.entity_reference_name(node) ⇒ Object



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

def entity_reference_name(node)
  attachments.get(node, :entity_name)
end

.get_attribute(element, name) ⇒ Object



270
271
272
# File 'lib/moxml/adapter/oga.rb', line 270

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

.get_attribute_value(element, name) ⇒ Object



274
275
276
# File 'lib/moxml/adapter/oga.rb', line 274

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

.has_declaration?(native_doc, _wrapper) ⇒ Boolean

Returns:

  • (Boolean)


455
456
457
458
459
460
461
462
463
# File 'lib/moxml/adapter/oga.rb', line 455

def has_declaration?(native_doc, _wrapper)
  decl = attachments.get(native_doc, :xml_declaration)
  if decl.nil? && !attachments.key?(native_doc, :xml_declaration)
    # No attachment entry - check native doc (for parsed documents)
    native_doc.respond_to?(:xml_declaration) && !native_doc.xml_declaration.nil?
  else
    !decl.nil?
  end
end

.inner_text(node) ⇒ Object



347
348
349
350
351
352
353
# File 'lib/moxml/adapter/oga.rb', line 347

def inner_text(node)
  if node.is_a?(::Oga::XML::Element)
    node.inner_text
  else
    node.text
  end
end

.namespace(element) ⇒ Object



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

def namespace(element)
  case element
  when ::Oga::XML::Element, ::Oga::XML::Attribute
    element.namespace
  end
rescue NoMethodError
  # Oga attributes fail with NoMethodError:
  # undefined method `available_namespaces' for nil:NilClass
  nil
end

.namespace_definitions(node) ⇒ Object



399
400
401
402
403
# File 'lib/moxml/adapter/oga.rb', line 399

def namespace_definitions(node)
  return [] unless node.is_a?(::Oga::XML::Element)

  node.namespaces.values
end

.namespace_prefix(namespace) ⇒ Object



388
389
390
391
392
393
# File 'lib/moxml/adapter/oga.rb', line 388

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



395
396
397
# File 'lib/moxml/adapter/oga.rb', line 395

def namespace_uri(namespace)
  namespace.uri
end

.next_sibling(node) ⇒ Object



223
224
225
# File 'lib/moxml/adapter/oga.rb', line 223

def next_sibling(node)
  node.next
end

.node_name(node) ⇒ Object



178
179
180
# File 'lib/moxml/adapter/oga.rb', line 178

def node_name(node)
  node.name
end

.node_type(node) ⇒ Object



158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
# File 'lib/moxml/adapter/oga.rb', line 158

def node_type(node)
  case node
  when ::Oga::XML::Element then :element
  when ::Oga::XML::Text
    if attachments.key?(node, :entity_name)
      :entity_reference
    else
      :text
    end
  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



219
220
221
# File 'lib/moxml/adapter/oga.rb', line 219

def parent(node)
  node.parent if node.is_a?(::Oga::XML::Node)
end

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



23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# File 'lib/moxml/adapter/oga.rb', line 23

def parse(xml, options = {}, _context = nil)
  processed_xml = preprocess_entities(xml)

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

  ctx = _context || Context.new(:oga)
  DocumentBuilder.new(ctx).build(native_doc)
end

.previous_sibling(node) ⇒ Object



227
228
229
# File 'lib/moxml/adapter/oga.rb', line 227

def previous_sibling(node)
  node.previous
end

.processing_instruction_content(node) ⇒ Object



380
381
382
# File 'lib/moxml/adapter/oga.rb', line 380

def processing_instruction_content(node)
  node.text
end

.processing_instruction_target(node) ⇒ Object



154
155
156
# File 'lib/moxml/adapter/oga.rb', line 154

def processing_instruction_target(node)
  node.name
end

.remove(node) ⇒ Object



323
324
325
326
327
328
329
330
331
332
# File 'lib/moxml/adapter/oga.rb', line 323

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 declaration state in attachment map
    attachments.set(node.parent, :xml_declaration, nil)
  end

  node.remove
end

.remove_attribute(element, name) ⇒ Object



278
279
280
281
# File 'lib/moxml/adapter/oga.rb', line 278

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

.replace(node, new_node) ⇒ Object



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

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

.replace_children(node, new_children) ⇒ Object



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

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

.root(document) ⇒ Object



238
239
240
# File 'lib/moxml/adapter/oga.rb', line 238

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:



44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/moxml/adapter/oga.rb', line 44

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

  xml_string = xml.is_a?(IO) || xml.is_a?(StringIO) ? 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



451
452
453
# File 'lib/moxml/adapter/oga.rb', line 451

def serialize(node, options = {})
  serialize_without_entity_processing(node, options)
end

.set_attribute(element, name, value) ⇒ Object



255
256
257
258
259
260
261
262
263
264
265
266
267
268
# File 'lib/moxml/adapter/oga.rb', line 255

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: preprocess_entities(value.to_s),
  )
  element.add_attribute(attr)
end

.set_cdata_content(node, content) ⇒ Object



368
369
370
# File 'lib/moxml/adapter/oga.rb', line 368

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

.set_comment_content(node, content) ⇒ Object



376
377
378
# File 'lib/moxml/adapter/oga.rb', line 376

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

.set_declaration_attribute(declaration, attr_name, value) ⇒ Object



120
121
122
123
124
125
126
# File 'lib/moxml/adapter/oga.rb', line 120

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



139
140
141
# File 'lib/moxml/adapter/oga.rb', line 139

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

.set_node_name(node, name) ⇒ Object



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

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

.set_processing_instruction_content(node, content) ⇒ Object



384
385
386
# File 'lib/moxml/adapter/oga.rb', line 384

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

.set_root(doc, element) ⇒ Object



15
16
17
18
19
20
21
# File 'lib/moxml/adapter/oga.rb', line 15

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.grep(::Oga::XML::Element)
  elements_to_remove.each { |elem| doc.children.delete(elem) }
  doc.children << element
end

.set_text_content(node, content) ⇒ Object



355
356
357
358
359
360
361
362
# File 'lib/moxml/adapter/oga.rb', line 355

def set_text_content(node, content)
  processed = preprocess_entities(content)
  if node.is_a?(::Oga::XML::Element)
    node.inner_text = processed
  else
    node.text = processed
  end
end

.text_content(node) ⇒ Object



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

def text_content(node)
  node.text
end

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



428
429
430
431
432
433
434
435
436
437
438
# File 'lib/moxml/adapter/oga.rb', line 428

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