Class: Moxml::Adapter::Ox

Inherits:
Base
  • Object
show all
Defined in:
lib/moxml/adapter/ox.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

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) ⇒ Object



301
302
303
304
305
# File 'lib/moxml/adapter/ox.rb', line 301

def add_child(element, child)
  child.parent = element if child.respond_to?(:parent)
  element.nodes ||= []
  element.nodes << child
end

.add_next_sibling(node, sibling) ⇒ Object



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

def add_next_sibling(node, sibling)
  return unless (parent = parent(node))

  if sibling.respond_to?(:parent)
    sibling.parent&.nodes&.delete(sibling)
    sibling.parent = parent
  end
  idx = parent.nodes.index(node)
  parent.nodes.insert(idx + 1, sibling) if idx
end

.add_previous_sibling(node, sibling) ⇒ Object



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

def add_previous_sibling(node, sibling)
  return unless (parent = parent(node))

  if sibling.respond_to?(:parent)
    sibling.parent&.nodes&.delete(sibling)
    sibling.parent = parent
  end
  idx = parent.nodes.index(node)
  parent.nodes.insert(idx, sibling) if idx
end

.ancestors(node) ⇒ Object



128
129
130
131
132
# File 'lib/moxml/adapter/ox.rb', line 128

def ancestors(node)
  return [] unless (parent = parent(node))

  [parent] + ancestors(parent)
end

.at_xpath(node, expression, namespaces = {}) ⇒ Object



436
437
438
# File 'lib/moxml/adapter/ox.rb', line 436

def at_xpath(node, expression, namespaces = {})
  xpath(node, expression, namespaces)&.first
end

.attribute_element(attribute) ⇒ Object



244
245
246
# File 'lib/moxml/adapter/ox.rb', line 244

def attribute_element(attribute)
  attribute.parent
end

.attributes(element) ⇒ Object



232
233
234
235
236
237
238
239
240
241
242
# File 'lib/moxml/adapter/ox.rb', line 232

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

  element.attributes.map do |name, value|
    next if name.start_with?("xmlns")

    ::Moxml::Adapter::CustomizedOx::Attribute.new(
      name, value, element
    )
  end.compact
end

.cdata_content(node) ⇒ Object



385
386
387
# File 'lib/moxml/adapter/ox.rb', line 385

def cdata_content(node)
  node.value.to_s
end

.children(node) ⇒ Object



196
197
198
199
200
# File 'lib/moxml/adapter/ox.rb', line 196

def children(node)
  return [] unless node.respond_to?(:nodes)

  node.nodes || []
end

.comment_content(node) ⇒ Object



393
394
395
# File 'lib/moxml/adapter/ox.rb', line 393

def comment_content(node)
  node.value.to_s
end

.create_document(native_doc = nil) ⇒ Object



38
39
40
41
# File 'lib/moxml/adapter/ox.rb', line 38

def create_document(native_doc = nil)
  attrs = native_doc&.attributes || {}
  ::Ox::Document.new(**attrs)
end

.create_native_cdata(content) ⇒ Object



53
54
55
# File 'lib/moxml/adapter/ox.rb', line 53

def create_native_cdata(content)
  ::Ox::CData.new(content)
end

.create_native_comment(content) ⇒ Object



57
58
59
# File 'lib/moxml/adapter/ox.rb', line 57

def create_native_comment(content)
  ::Ox::Comment.new(content)
end

.create_native_declaration(version, encoding, standalone) ⇒ Object



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

def create_native_declaration(version, encoding, standalone)
  inst = ::Ox::Instruct.new("xml")
  set_attribute(inst, "version", version)
  set_attribute(inst, "encoding", encoding)
  set_attribute(inst, "standalone", standalone)
  inst
end

.create_native_doctype(name, external_id, system_id) ⇒ Object



61
62
63
64
65
# File 'lib/moxml/adapter/ox.rb', line 61

def create_native_doctype(name, external_id, system_id)
  ::Ox::DocType.new(
    "#{name} PUBLIC \"#{external_id}\" \"#{system_id}\""
  )
end

.create_native_element(name) ⇒ Object



43
44
45
46
47
# File 'lib/moxml/adapter/ox.rb', line 43

def create_native_element(name)
  element = ::Ox::Element.new(name)
  element.instance_variable_set(:@attributes, {})
  element
end

.create_native_namespace(element, prefix, uri) ⇒ Object



89
90
91
92
93
# File 'lib/moxml/adapter/ox.rb', line 89

def create_native_namespace(element, prefix, uri)
  ns = ::Moxml::Adapter::CustomizedOx::Namespace.new(prefix, uri, element)
  set_attribute(element, ns.expanded_prefix, uri)
  ns
end

.create_native_processing_instruction(target, content) ⇒ Object



67
68
69
70
71
# File 'lib/moxml/adapter/ox.rb', line 67

def create_native_processing_instruction(target, content)
  inst = ::Ox::Instruct.new(target)
  set_processing_instruction_content(inst, content)
  inst
end

.create_native_text(content) ⇒ Object



49
50
51
# File 'lib/moxml/adapter/ox.rb', line 49

def create_native_text(content)
  content
end

.declaration_attribute(declaration, attr_name) ⇒ Object



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

def declaration_attribute(declaration, attr_name)
  get_attribute_value(declaration, attr_name)
end

.document(node) ⇒ Object



222
223
224
225
226
# File 'lib/moxml/adapter/ox.rb', line 222

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

.duplicate_node(node) ⇒ Object



167
168
169
# File 'lib/moxml/adapter/ox.rb', line 167

def duplicate_node(node)
  Marshal.load(Marshal.dump(node))
end

.get_attribute(element, name) ⇒ Object



281
282
283
284
285
286
287
288
# File 'lib/moxml/adapter/ox.rb', line 281

def get_attribute(element, name)
  return unless element.respond_to?(:attributes) && element.attributes
  return unless element.attributes.key?(name.to_s) || element.attributes.key?(name.to_s.to_sym)

  ::Moxml::Adapter::CustomizedOx::Attribute.new(
    name.to_s, element.attributes[name], element
  )
end

.get_attribute_value(element, name) ⇒ Object



290
291
292
# File 'lib/moxml/adapter/ox.rb', line 290

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

.inner_text(node) ⇒ Object



370
371
372
373
374
# File 'lib/moxml/adapter/ox.rb', line 370

def inner_text(node)
  return "" unless node.respond_to?(:nodes)

  node.nodes.select { _1.is_a?(String) }.join
end

.namespace(element) ⇒ Object



105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
# File 'lib/moxml/adapter/ox.rb', line 105

def namespace(element)
  prefix =
    if element.respond_to?(:prefix)
      # attribute
      element.prefix
    elsif element.name.include?(":")
      element.name.split(":").first
    end
  attr_name = ["xmlns", prefix].compact.join(":")

  ([element] + ancestors(element)).each do |node|
    next unless node.respond_to?(:attributes) && node.attributes

    if node[attr_name]
      return ::Moxml::Adapter::CustomizedOx::Namespace.new(
        prefix, node[attr_name], element
      )
    end
  end

  nil
end

.namespace_definitions(node) ⇒ Object



417
418
419
420
421
422
423
424
425
426
427
428
429
# File 'lib/moxml/adapter/ox.rb', line 417

def namespace_definitions(node)
  ([node] + ancestors(node)).reverse.each_with_object({}) do |n, namespaces|
    next unless n.respond_to?(:attributes) && n.attributes

    n.attributes.each do |name, value|
      next unless name.to_s.start_with?("xmlns")

      namespaces[name] = ::Moxml::Adapter::CustomizedOx::Namespace.new(
        name, value, n
      )
    end
  end.values
end

.namespace_prefix(namespace) ⇒ Object



409
410
411
# File 'lib/moxml/adapter/ox.rb', line 409

def namespace_prefix(namespace)
  namespace.prefix
end

.namespace_uri(namespace) ⇒ Object



413
414
415
# File 'lib/moxml/adapter/ox.rb', line 413

def namespace_uri(namespace)
  namespace.uri
end

.next_sibling(node) ⇒ Object



206
207
208
209
210
211
212
# File 'lib/moxml/adapter/ox.rb', line 206

def next_sibling(node)
  return unless (parent = node.parent)

  siblings = parent.nodes
  idx = siblings.index(unpatch_node(node))
  idx ? patch_node(siblings[idx + 1], parent) : nil
end

.node_name(node) ⇒ Object



153
154
155
156
157
# File 'lib/moxml/adapter/ox.rb', line 153

def node_name(node)
  node.value
rescue StandardError
  node.name
end

.node_type(node) ⇒ Object



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

def node_type(node)
  case node
  when ::Ox::Document then :document
  when ::Moxml::Adapter::CustomizedOx::Text, String then :text
  when ::Ox::CData then :cdata
  when ::Ox::Comment then :comment
  when ::Ox::Instruct then :processing_instruction
  when ::Ox::Element then :element
  when ::Ox::DocType then :doctype
  when ::Moxml::Adapter::CustomizedOx::Namespace then :banespace
  when ::Moxml::Adapter::CustomizedOx::Attribute then :attribute
  else :unknown
  end
end

.parent(node) ⇒ Object



202
203
204
# File 'lib/moxml/adapter/ox.rb', line 202

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

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



19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# File 'lib/moxml/adapter/ox.rb', line 19

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, e.message
  end

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

.patch_node(node, parent = nil) ⇒ Object



171
172
173
174
175
176
177
178
179
180
181
182
183
184
# File 'lib/moxml/adapter/ox.rb', line 171

def patch_node(node, parent = nil)
  new_node =
    case node
    # it can be either attribute or namespace
    when Array then ::Moxml::Adapter::CustomizedOx::Attribute.new(node.first, node.last)
    when Hash then ::Moxml::Adapter::CustomizedOx::Attribute.new(node.keys.first, node.values.first)
    when String then ::Moxml::Adapter::CustomizedOx::Text.new(node)
    else node
    end

  new_node.parent = parent if new_node.respond_to?(:parent)

  new_node
end

.previous_sibling(node) ⇒ Object



214
215
216
217
218
219
220
# File 'lib/moxml/adapter/ox.rb', line 214

def previous_sibling(node)
  return unless (parent = parent(node))

  siblings = parent.nodes
  idx = siblings.index(unpatch_node(node))
  idx&.positive? ? patch_node(siblings[idx - 1], parent) : nil
end

.processing_instruction_content(node) ⇒ Object



401
402
403
# File 'lib/moxml/adapter/ox.rb', line 401

def processing_instruction_content(node)
  node.content.to_s
end

.processing_instruction_target(node) ⇒ Object



134
135
136
# File 'lib/moxml/adapter/ox.rb', line 134

def processing_instruction_target(node)
  node.target
end

.remove(node) ⇒ Object



329
330
331
332
333
334
335
# File 'lib/moxml/adapter/ox.rb', line 329

def remove(node)
  return node.clear if node.is_a?(String)

  return unless parent(node)

  parent(node).nodes.delete(node)
end

.remove_attribute(element, name) ⇒ Object



294
295
296
297
298
299
# File 'lib/moxml/adapter/ox.rb', line 294

def remove_attribute(element, name)
  return unless element.respond_to?(:attributes) && element.attributes

  element.attributes.delete(name.to_s)
  element.attributes.delete(name.to_s.to_sym)
end

.replace(node, new_node) ⇒ Object



337
338
339
340
341
342
343
344
345
346
347
348
# File 'lib/moxml/adapter/ox.rb', line 337

def replace(node, new_node)
  return node.replace(new_node) if node.is_a?(String) && new_node.is_a?(String)
  # There are other cases:
  # when node is a String and new_node isn't
  # when node isn't a String, and new_node is a String

  return unless (parent = parent(node))

  new_node.parent = parent if new_node.respond_to?(:parent)
  idx = parent.nodes.index(node)
  parent.nodes[idx] = new_node if idx
end

.replace_children(node, new_children) ⇒ Object



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

def replace_children(node, new_children)
  node.remove_children_by_path("*")
  new_children.each do |child|
    child.parent = node if child.respond_to?(:parent)
    node << child
  end
  node
end

.root(document) ⇒ Object



228
229
230
# File 'lib/moxml/adapter/ox.rb', line 228

def root(document)
  document.nodes&.find { |node| node.is_a?(::Ox::Element) }
end

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



440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
# File 'lib/moxml/adapter/ox.rb', line 440

def serialize(node, options = {})
  output = ""
  if node.is_a?(::Ox::Document)
    # add declaration
    decl = create_native_declaration(node[:version], node[:encoding], node[:standalone])
    output = ::Ox.dump(::Ox::Document.new << decl).strip
  end

  ox_options = {
    indent: -1, # options[:indent] || -1, # indent is a beast
    # with_xml: true,
    with_instructions: true,
    encoding: options[:encoding],
    no_empty: options[:expand_empty]
  }
  output + ::Ox.dump(node, ox_options)
end

.set_attribute(element, name, value) ⇒ Object



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

def set_attribute(element, name, value)
  element.attributes ||= {}
  if value.nil?
    # Ox converts all values to strings
    remove_attribute(element, name)
  else
    element.attributes[name.to_s] = value
  end

  ::Moxml::Adapter::CustomizedOx::Attribute.new(
    name.to_s, value&.to_s, element
  )
end

.set_attribute_name(attribute, name) ⇒ Object



262
263
264
265
266
267
268
269
# File 'lib/moxml/adapter/ox.rb', line 262

def set_attribute_name(attribute, name)
  old_name = attribute.name
  attribute.name = name.to_s
  # Ox doesn't change the keys of the attributes hash
  element = attribute.parent
  element.attributes.delete(old_name)
  element.attributes[name] = attribute.value
end

.set_attribute_value(attribute, new_value) ⇒ Object



271
272
273
274
275
276
277
278
279
# File 'lib/moxml/adapter/ox.rb', line 271

def set_attribute_value(attribute, new_value)
  if new_value.nil?
    # Ox converts all values to strings
    remove_attribute(attribute.parent, attribute.name)
  else
    attribute.value = new_value
    attribute.parent.attributes[attribute.name] = new_value
  end
end

.set_cdata_content(node, content) ⇒ Object



389
390
391
# File 'lib/moxml/adapter/ox.rb', line 389

def set_cdata_content(node, content)
  node.value = content.to_s
end

.set_comment_content(node, content) ⇒ Object



397
398
399
# File 'lib/moxml/adapter/ox.rb', line 397

def set_comment_content(node, content)
  node.value = content.to_s
end

.set_declaration_attribute(declaration, attr_name, value) ⇒ Object



85
86
87
# File 'lib/moxml/adapter/ox.rb', line 85

def set_declaration_attribute(declaration, attr_name, value)
  set_attribute(declaration, attr_name, value)
end

.set_namespace(element, ns) ⇒ Object



95
96
97
98
99
100
101
102
103
# File 'lib/moxml/adapter/ox.rb', line 95

def set_namespace(element, ns)
  return unless element.respond_to?(:name)

  prefix = ns.prefix
  # attributes don't have attributes but can have a namespace prefix
  set_attribute(element, ns.expanded_prefix, ns.uri) if element.respond_to?(:attributes)
  element.name = [prefix, element.name.delete_prefix("xmlns:")].compact.join(":")
  namespace(element)
end

.set_node_name(node, name) ⇒ Object



159
160
161
162
163
164
165
# File 'lib/moxml/adapter/ox.rb', line 159

def set_node_name(node, name)
  if node.respond_to?(:name=)
    node.name = name
  elsif node.respond_to?(:value=)
    node.value = name
  end
end

.set_processing_instruction_content(node, content) ⇒ Object



405
406
407
# File 'lib/moxml/adapter/ox.rb', line 405

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

.set_root(doc, element) ⇒ Object



15
16
17
# File 'lib/moxml/adapter/ox.rb', line 15

def set_root(doc, element)
  replace_children(doc, [element])
end

.set_text_content(node, content) ⇒ Object



376
377
378
379
380
381
382
383
# File 'lib/moxml/adapter/ox.rb', line 376

def set_text_content(node, content)
  case node
  when String then node.replace(content.to_s)
  when ::Ox::Element then node.replace_text(content.to_s)
  else
    node.value = content.to_s
  end
end

.text_content(node) ⇒ Object



359
360
361
362
363
364
365
366
367
368
# File 'lib/moxml/adapter/ox.rb', line 359

def text_content(node)
  case node
  when String then node.to_s
  when ::Moxml::Adapter::CustomizedOx::Text then node.value
  else
    node.nodes.map do |n|
      text_content(n)
    end.join
  end
end

.unpatch_node(node) ⇒ Object



186
187
188
189
190
191
192
193
194
# File 'lib/moxml/adapter/ox.rb', line 186

def unpatch_node(node)
  case node
  # it can be either attribute or namespace
  when ::Moxml::Adapter::CustomizedOx::Attribute then [node.name, node.value]
  # when ::Moxml::Adapter::CustomizedOx::Attribute then { node.name => node.value }
  when ::Moxml::Adapter::CustomizedOx::Text then node.value
  else node
  end
end

.xpath(node, expression, _namespaces = {}) ⇒ Object



431
432
433
434
# File 'lib/moxml/adapter/ox.rb', line 431

def xpath(node, expression, _namespaces = {})
  # locate has a different syntax
  node.locate(expression)
end