moxml 0.1.4 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +0 -1
  3. data/.rubocop_todo.yml +2 -2
  4. data/Gemfile +2 -0
  5. data/README.adoc +71 -21
  6. data/lib/moxml/adapter/base.rb +8 -2
  7. data/lib/moxml/adapter/customized_ox/attribute.rb +29 -0
  8. data/lib/moxml/adapter/customized_ox/namespace.rb +34 -0
  9. data/lib/moxml/adapter/customized_ox/text.rb +12 -0
  10. data/lib/moxml/adapter/customized_rexml/formatter.rb +3 -3
  11. data/lib/moxml/adapter/nokogiri.rb +3 -1
  12. data/lib/moxml/adapter/oga.rb +4 -2
  13. data/lib/moxml/adapter/ox.rb +238 -92
  14. data/lib/moxml/adapter/rexml.rb +10 -6
  15. data/lib/moxml/adapter.rb +1 -1
  16. data/lib/moxml/cdata.rb +0 -4
  17. data/lib/moxml/comment.rb +0 -4
  18. data/lib/moxml/context.rb +2 -2
  19. data/lib/moxml/doctype.rb +1 -5
  20. data/lib/moxml/document.rb +1 -1
  21. data/lib/moxml/document_builder.rb +1 -1
  22. data/lib/moxml/element.rb +2 -1
  23. data/lib/moxml/namespace.rb +4 -0
  24. data/lib/moxml/node.rb +17 -2
  25. data/lib/moxml/node_set.rb +8 -1
  26. data/lib/moxml/processing_instruction.rb +0 -4
  27. data/lib/moxml/text.rb +0 -4
  28. data/lib/moxml/version.rb +1 -1
  29. data/lib/ox/node.rb +9 -0
  30. data/spec/fixtures/small.xml +1 -0
  31. data/spec/moxml/all_with_adapters_spec.rb +2 -1
  32. data/spec/support/shared_examples/builder.rb +19 -2
  33. data/spec/support/shared_examples/cdata.rb +7 -5
  34. data/spec/support/shared_examples/declaration.rb +16 -4
  35. data/spec/support/shared_examples/doctype.rb +2 -1
  36. data/spec/support/shared_examples/document.rb +10 -0
  37. data/spec/support/shared_examples/edge_cases.rb +6 -0
  38. data/spec/support/shared_examples/element.rb +4 -0
  39. data/spec/support/shared_examples/examples/benchmark_spec.rb +51 -0
  40. data/spec/support/shared_examples/examples/memory.rb +30 -17
  41. data/spec/support/shared_examples/examples/readme_examples.rb +5 -0
  42. data/spec/support/shared_examples/examples/thread_safety.rb +2 -0
  43. data/spec/support/shared_examples/examples/xpath.rb +34 -3
  44. data/spec/support/shared_examples/integration.rb +4 -0
  45. data/spec/support/shared_examples/namespace.rb +16 -0
  46. data/spec/support/shared_examples/node.rb +4 -0
  47. data/spec/support/shared_examples/node_set.rb +20 -0
  48. data/spec/support/shared_examples/processing_instruction.rb +1 -1
  49. data/spec/support/shared_examples/text.rb +2 -1
  50. metadata +8 -2
@@ -1,7 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "base"
4
+ # before ox so that all Ox classes inherit the monkey-patched Node
5
+ require_relative "../../ox/node"
4
6
  require "ox"
7
+ require_relative "customized_ox/text"
8
+ require_relative "customized_ox/attribute"
9
+ require_relative "customized_ox/namespace"
5
10
 
6
11
  module Moxml
7
12
  module Adapter
@@ -30,8 +35,9 @@ module Moxml
30
35
  DocumentBuilder.new(Context.new(:ox)).build(native_doc)
31
36
  end
32
37
 
33
- def create_document
34
- ::Ox::Document.new
38
+ def create_document(native_doc = nil)
39
+ attrs = native_doc&.attributes || {}
40
+ ::Ox::Document.new(**attrs)
35
41
  end
36
42
 
37
43
  def create_native_element(name)
@@ -52,63 +58,94 @@ module Moxml
52
58
  ::Ox::Comment.new(content)
53
59
  end
54
60
 
61
+ def create_native_doctype(name, external_id, system_id)
62
+ ::Ox::DocType.new(
63
+ "#{name} PUBLIC \"#{external_id}\" \"#{system_id}\""
64
+ )
65
+ end
66
+
55
67
  def create_native_processing_instruction(target, content)
56
- inst = ::Ox::Instruction.new(target)
57
- inst.value = content
68
+ inst = ::Ox::Instruct.new(target)
69
+ set_processing_instruction_content(inst, content)
58
70
  inst
59
71
  end
60
72
 
61
- # TODO: compare to create_native_declaration
62
- def create_native_declaration2(version, encoding, standalone)
73
+ def create_native_declaration(version, encoding, standalone)
63
74
  inst = ::Ox::Instruct.new("xml")
64
- inst.value = build_declaration_attrs(version, encoding, standalone)
75
+ set_attribute(inst, "version", version)
76
+ set_attribute(inst, "encoding", encoding)
77
+ set_attribute(inst, "standalone", standalone)
65
78
  inst
66
79
  end
67
80
 
68
- def create_native_declaration(version, encoding, standalone)
69
- doc = ::Ox::Document.new
70
- doc.version = version
71
- doc.encoding = encoding
72
- doc.standalone = standalone
73
- doc
81
+ def declaration_attribute(declaration, attr_name)
82
+ get_attribute_value(declaration, attr_name)
83
+ end
84
+
85
+ def set_declaration_attribute(declaration, attr_name, value)
86
+ set_attribute(declaration, attr_name, value)
74
87
  end
75
88
 
76
89
  def create_native_namespace(element, prefix, uri)
77
- element.attributes ||= {}
78
- attr_name = prefix ? "xmlns:#{prefix}" : "xmlns"
79
- element.attributes[attr_name] = uri
80
- [prefix, uri]
90
+ ns = ::Moxml::Adapter::CustomizedOx::Namespace.new(prefix, uri, element)
91
+ set_attribute(element, ns.expanded_prefix, uri)
92
+ ns
81
93
  end
82
94
 
83
95
  def set_namespace(element, ns)
84
- prefix, uri = ns
85
- element.attributes ||= {}
86
- attr_name = prefix ? "xmlns:#{prefix}" : "xmlns"
87
- element.attributes[attr_name] = uri
96
+ return unless element.respond_to?(:name)
97
+
98
+ prefix = ns.prefix
99
+ # attributes don't have attributes but can have a namespace prefix
100
+ set_attribute(element, ns.expanded_prefix, ns.uri) if element.respond_to?(:attributes)
101
+ element.name = [prefix, element.name.delete_prefix("xmlns:")].compact.join(":")
102
+ namespace(element)
88
103
  end
89
104
 
90
105
  def namespace(element)
91
- return nil unless element.attributes
106
+ prefix =
107
+ if element.respond_to?(:prefix)
108
+ # attribute
109
+ element.prefix
110
+ elsif element.name.include?(":")
111
+ element.name.split(":").first
112
+ end
113
+ attr_name = ["xmlns", prefix].compact.join(":")
92
114
 
93
- xmlns_attr = element.attributes.find { |k, _| k.start_with?("xmlns:") || k == "xmlns" }
94
- return nil unless xmlns_attr
115
+ ([element] + ancestors(element)).each do |node|
116
+ next unless node.respond_to?(:attributes) && node.attributes
95
117
 
96
- prefix = xmlns_attr[0] == "xmlns" ? nil : xmlns_attr[0].sub("xmlns:", "")
97
- [prefix, xmlns_attr[1]]
118
+ if node[attr_name]
119
+ return ::Moxml::Adapter::CustomizedOx::Namespace.new(
120
+ prefix, node[attr_name], element
121
+ )
122
+ end
123
+ end
124
+
125
+ nil
126
+ end
127
+
128
+ def ancestors(node)
129
+ return [] unless (parent = parent(node))
130
+
131
+ [parent] + ancestors(parent)
98
132
  end
99
133
 
100
134
  def processing_instruction_target(node)
101
- node.name
135
+ node.target
102
136
  end
103
137
 
104
138
  def node_type(node)
105
139
  case node
106
140
  when ::Ox::Document then :document
107
- when String then :text
141
+ when ::Moxml::Adapter::CustomizedOx::Text, String then :text
108
142
  when ::Ox::CData then :cdata
109
143
  when ::Ox::Comment then :comment
110
144
  when ::Ox::Instruct then :processing_instruction
111
145
  when ::Ox::Element then :element
146
+ when ::Ox::DocType then :doctype
147
+ when ::Moxml::Adapter::CustomizedOx::Namespace then :banespace
148
+ when ::Moxml::Adapter::CustomizedOx::Attribute then :attribute
112
149
  else :unknown
113
150
  end
114
151
  end
@@ -120,8 +157,40 @@ module Moxml
120
157
  end
121
158
 
122
159
  def set_node_name(node, name)
123
- node.value = name if node.respond_to?(:value=)
124
- node.name = name if node.respond_to?(:name=)
160
+ if node.respond_to?(:name=)
161
+ node.name = name
162
+ elsif node.respond_to?(:value=)
163
+ node.value = name
164
+ end
165
+ end
166
+
167
+ def duplicate_node(node)
168
+ Marshal.load(Marshal.dump(node))
169
+ end
170
+
171
+ def patch_node(node, parent = nil)
172
+ new_node =
173
+ case node
174
+ # it can be either attribute or namespace
175
+ when Array then ::Moxml::Adapter::CustomizedOx::Attribute.new(node.first, node.last)
176
+ when Hash then ::Moxml::Adapter::CustomizedOx::Attribute.new(node.keys.first, node.values.first)
177
+ when String then ::Moxml::Adapter::CustomizedOx::Text.new(node)
178
+ else node
179
+ end
180
+
181
+ new_node.parent = parent if new_node.respond_to?(:parent)
182
+
183
+ new_node
184
+ end
185
+
186
+ def unpatch_node(node)
187
+ case node
188
+ # it can be either attribute or namespace
189
+ when ::Moxml::Adapter::CustomizedOx::Attribute then [node.name, node.value]
190
+ # when ::Moxml::Adapter::CustomizedOx::Attribute then { node.name => node.value }
191
+ when ::Moxml::Adapter::CustomizedOx::Text then node.value
192
+ else node
193
+ end
125
194
  end
126
195
 
127
196
  def children(node)
@@ -135,19 +204,19 @@ module Moxml
135
204
  end
136
205
 
137
206
  def next_sibling(node)
138
- return unless (parent = parent(node))
207
+ return unless (parent = node.parent)
139
208
 
140
209
  siblings = parent.nodes
141
- idx = siblings.index(node)
142
- idx ? siblings[idx + 1] : nil
210
+ idx = siblings.index(unpatch_node(node))
211
+ idx ? patch_node(siblings[idx + 1], parent) : nil
143
212
  end
144
213
 
145
214
  def previous_sibling(node)
146
215
  return unless (parent = parent(node))
147
216
 
148
217
  siblings = parent.nodes
149
- idx = siblings.index(node)
150
- idx&.positive? ? siblings[idx - 1] : nil
218
+ idx = siblings.index(unpatch_node(node))
219
+ idx&.positive? ? patch_node(siblings[idx - 1], parent) : nil
151
220
  end
152
221
 
153
222
  def document(node)
@@ -161,74 +230,153 @@ module Moxml
161
230
  end
162
231
 
163
232
  def attributes(element)
164
- return {} unless element.respond_to?(:attributes) && element.attributes
233
+ return [] unless element.respond_to?(:attributes) && element.attributes
165
234
 
166
- element.attributes.reject { |k, _| k.start_with?("xmlns") }
235
+ element.attributes.map do |name, value|
236
+ next if name.start_with?("xmlns")
237
+
238
+ ::Moxml::Adapter::CustomizedOx::Attribute.new(
239
+ name, value, element
240
+ )
241
+ end.compact
242
+ end
243
+
244
+ def attribute_element(attribute)
245
+ attribute.parent
167
246
  end
168
247
 
169
248
  def set_attribute(element, name, value)
170
249
  element.attributes ||= {}
171
- element.attributes[name.to_s] = value.to_s
250
+ if value.nil?
251
+ # Ox converts all values to strings
252
+ remove_attribute(element, name)
253
+ else
254
+ element.attributes[name.to_s] = value
255
+ end
256
+
257
+ ::Moxml::Adapter::CustomizedOx::Attribute.new(
258
+ name.to_s, value&.to_s, element
259
+ )
260
+ end
261
+
262
+ def set_attribute_name(attribute, name)
263
+ old_name = attribute.name
264
+ attribute.name = name.to_s
265
+ # Ox doesn't change the keys of the attributes hash
266
+ element = attribute.parent
267
+ element.attributes.delete(old_name)
268
+ element.attributes[name] = attribute.value
269
+ end
270
+
271
+ def set_attribute_value(attribute, new_value)
272
+ if new_value.nil?
273
+ # Ox converts all values to strings
274
+ remove_attribute(attribute.parent, attribute.name)
275
+ else
276
+ attribute.value = new_value
277
+ attribute.parent.attributes[attribute.name] = new_value
278
+ end
172
279
  end
173
280
 
174
281
  def get_attribute(element, name)
175
- return nil unless element.respond_to?(:attributes) && element.attributes
282
+ return unless element.respond_to?(:attributes) && element.attributes
283
+ return unless element.attributes.key?(name.to_s) || element.attributes.key?(name.to_s.to_sym)
284
+
285
+ ::Moxml::Adapter::CustomizedOx::Attribute.new(
286
+ name.to_s, element.attributes[name], element
287
+ )
288
+ end
176
289
 
177
- element.attributes[name.to_s]
290
+ def get_attribute_value(element, name)
291
+ element[name]
178
292
  end
179
293
 
180
294
  def remove_attribute(element, name)
181
295
  return unless element.respond_to?(:attributes) && element.attributes
182
296
 
183
297
  element.attributes.delete(name.to_s)
298
+ element.attributes.delete(name.to_s.to_sym)
184
299
  end
185
300
 
186
301
  def add_child(element, child)
302
+ child.parent = element if child.respond_to?(:parent)
187
303
  element.nodes ||= []
188
- puts "Add child #{child} for #{element.name}: #{element.nodes.count}"
189
304
  element.nodes << child
190
305
  end
191
306
 
192
307
  def add_previous_sibling(node, sibling)
193
- return unless parent(node)
308
+ return unless (parent = parent(node))
194
309
 
195
- idx = node.parent.nodes.index(node)
196
- node.parent.nodes.insert(idx, sibling) if idx
310
+ if sibling.respond_to?(:parent)
311
+ sibling.parent&.nodes&.delete(sibling)
312
+ sibling.parent = parent
313
+ end
314
+ idx = parent.nodes.index(node)
315
+ parent.nodes.insert(idx, sibling) if idx
197
316
  end
198
317
 
199
318
  def add_next_sibling(node, sibling)
200
- return unless parent(node)
319
+ return unless (parent = parent(node))
201
320
 
202
- idx = node.parent.nodes.index(node)
203
- node.parent.nodes.insert(idx + 1, sibling) if idx
321
+ if sibling.respond_to?(:parent)
322
+ sibling.parent&.nodes&.delete(sibling)
323
+ sibling.parent = parent
324
+ end
325
+ idx = parent.nodes.index(node)
326
+ parent.nodes.insert(idx + 1, sibling) if idx
204
327
  end
205
328
 
206
329
  def remove(node)
330
+ return node.clear if node.is_a?(String)
331
+
207
332
  return unless parent(node)
208
333
 
209
- node.parent.nodes.delete(node)
334
+ parent(node).nodes.delete(node)
210
335
  end
211
336
 
212
337
  def replace(node, new_node)
213
- return unless parent(node)
338
+ return node.replace(new_node) if node.is_a?(String) && new_node.is_a?(String)
339
+ # There are other cases:
340
+ # when node is a String and new_node isn't
341
+ # when node isn't a String, and new_node is a String
342
+
343
+ return unless (parent = parent(node))
214
344
 
215
- idx = node.parent.nodes.index(node)
216
- node.parent.nodes[idx] = new_node if idx
345
+ new_node.parent = parent if new_node.respond_to?(:parent)
346
+ idx = parent.nodes.index(node)
347
+ parent.nodes[idx] = new_node if idx
217
348
  end
218
349
 
219
350
  def replace_children(node, new_children)
220
351
  node.remove_children_by_path("*")
221
- new_children.each { |child| node << child }
352
+ new_children.each do |child|
353
+ child.parent = node if child.respond_to?(:parent)
354
+ node << child
355
+ end
222
356
  node
223
357
  end
224
358
 
225
359
  def text_content(node)
226
- node.is_a?(String) ? node : node.value.to_s
360
+ case node
361
+ when String then node.to_s
362
+ when ::Moxml::Adapter::CustomizedOx::Text then node.value
363
+ else
364
+ node.nodes.map do |n|
365
+ text_content(n)
366
+ end.join
367
+ end
368
+ end
369
+
370
+ def inner_text(node)
371
+ return "" unless node.respond_to?(:nodes)
372
+
373
+ node.nodes.select { _1.is_a?(String) }.join
227
374
  end
228
375
 
229
376
  def set_text_content(node, content)
230
- if node.is_a?(String)
231
- node.replace(content.to_s)
377
+ case node
378
+ when String then node.replace(content.to_s)
379
+ when ::Ox::Element then node.replace_text(content.to_s)
232
380
  else
233
381
  node.value = content.to_s
234
382
  end
@@ -251,48 +399,60 @@ module Moxml
251
399
  end
252
400
 
253
401
  def processing_instruction_content(node)
254
- node.value.to_s
402
+ node.content.to_s
255
403
  end
256
404
 
257
405
  def set_processing_instruction_content(node, content)
258
- node.value = content.to_s
406
+ node.content = content.to_s
407
+ end
408
+
409
+ def namespace_prefix(namespace)
410
+ namespace.prefix
411
+ end
412
+
413
+ def namespace_uri(namespace)
414
+ namespace.uri
259
415
  end
260
416
 
261
417
  def namespace_definitions(node)
262
- return [] unless node.respond_to?(:attributes) && node.attributes
418
+ ([node] + ancestors(node)).reverse.each_with_object({}) do |n, namespaces|
419
+ next unless n.respond_to?(:attributes) && n.attributes
263
420
 
264
- node.attributes.each_with_object([]) do |(name, value), namespaces|
265
- next unless name.start_with?("xmlns")
421
+ n.attributes.each do |name, value|
422
+ next unless name.to_s.start_with?("xmlns")
266
423
 
267
- prefix = name == "xmlns" ? nil : name.sub("xmlns:", "")
268
- namespaces << [prefix, value]
269
- end
424
+ namespaces[name] = ::Moxml::Adapter::CustomizedOx::Namespace.new(
425
+ name, value, n
426
+ )
427
+ end
428
+ end.values
270
429
  end
271
430
 
272
- def xpath(node, expression, namespaces = {})
273
- # Ox doesn't support XPath, implement basic path matching
274
- results = []
275
- traverse(node) do |n|
276
- results << n if matches_xpath?(n, expression, namespaces)
277
- end
278
- results
431
+ def xpath(node, expression, _namespaces = {})
432
+ # locate has a different syntax
433
+ node.locate(expression)
279
434
  end
280
435
 
281
436
  def at_xpath(node, expression, namespaces = {})
282
- traverse(node) do |n|
283
- return n if matches_xpath?(n, expression, namespaces)
284
- end
285
- nil
437
+ xpath(node, expression, namespaces)&.first
286
438
  end
287
439
 
288
440
  def serialize(node, options = {})
441
+ output = ""
442
+ if node.is_a?(::Ox::Document)
443
+ # add declaration
444
+ decl = create_native_declaration(node[:version], node[:encoding], node[:standalone])
445
+ output = ::Ox.dump(::Ox::Document.new << decl).strip
446
+ end
447
+
289
448
  ox_options = {
290
- indent: options[:indent] || -1,
291
- with_xml: true,
449
+ indent: -1, # options[:indent] || -1, # indent is a beast
450
+ # with_xml: true,
292
451
  with_instructions: true,
293
- encoding: options[:encoding]
452
+ encoding: options[:encoding],
453
+ no_empty: options[:expand_empty]
294
454
  }
295
- ::Ox.dump(node, ox_options)
455
+ output + ::Ox.dump(node, ox_options)
296
456
  end
297
457
 
298
458
  private
@@ -305,20 +465,6 @@ module Moxml
305
465
 
306
466
  node.nodes&.each { |child| traverse(child, &block) }
307
467
  end
308
-
309
- def matches_xpath?(node, expression, _namespaces = {})
310
- case expression
311
- when %r{//(\w+)}
312
- node.is_a?(::Ox::Element) && node.value == ::Regexp.last_match(1)
313
- when %r{//(\w+)\[@(\w+)='([^']+)'\]}
314
- node.is_a?(::Ox::Element) &&
315
- node.value == ::Regexp.last_match(1) &&
316
- node.attributes &&
317
- node.attributes[::Regexp.last_match(2)] == ::Regexp.last_match(3)
318
- else
319
- false
320
- end
321
- end
322
468
  end
323
469
  end
324
470
  end
@@ -21,7 +21,7 @@ module Moxml
21
21
  DocumentBuilder.new(Context.new(:rexml)).build(native_doc)
22
22
  end
23
23
 
24
- def create_document
24
+ def create_document(_native_doc = nil)
25
25
  ::REXML::Document.new
26
26
  end
27
27
 
@@ -75,6 +75,8 @@ module Moxml
75
75
  when ::REXML::CData then :cdata
76
76
  when ::REXML::Text then :text
77
77
  when ::REXML::Comment then :comment
78
+ when ::REXML::Attribute then :attribute # but in fact it may be a namespace as well
79
+ when ::REXML::Namespace then :namespace # we don't use this one
78
80
  when ::REXML::Instruction then :processing_instruction
79
81
  when ::REXML::DocType then :doctype
80
82
  when ::REXML::XMLDecl then :declaration
@@ -325,7 +327,7 @@ module Moxml
325
327
  def inner_text(node)
326
328
  # Get direct text children only, filter duplicates
327
329
  text_children = node.children
328
- .select { |c| c.is_a?(::REXML::Text) }
330
+ .select { _1.is_a?(::REXML::Text) }
329
331
  .uniq(&:object_id)
330
332
  text_children.map(&:value).join
331
333
  end
@@ -380,6 +382,8 @@ module Moxml
380
382
  end
381
383
  end
382
384
 
385
+ # not used at the moment
386
+ # but may be useful when the xpath is upgraded to work with namespaces
383
387
  def prepare_xpath_namespaces(node)
384
388
  ns = {}
385
389
 
@@ -430,10 +434,10 @@ module Moxml
430
434
 
431
435
  # Write processing instructions
432
436
  node.children.each do |child|
433
- if child.is_a?(::REXML::Instruction)
434
- child.write(output)
435
- # output << "\n"
436
- end
437
+ next unless [::REXML::Instruction, ::REXML::CData, ::REXML::Comment, ::REXML::Text].include?(child.class)
438
+
439
+ write_with_formatter(child, output, options[:indent] || 2)
440
+ # output << "\n"
437
441
  end
438
442
 
439
443
  write_with_formatter(node.root, output, options[:indent] || 2) if node.root
data/lib/moxml/adapter.rb CHANGED
@@ -4,7 +4,7 @@ require_relative "adapter/base"
4
4
 
5
5
  module Moxml
6
6
  module Adapter
7
- AVALIABLE_ADAPTERS = %i[nokogiri oga rexml].freeze # ox to be added later
7
+ AVALIABLE_ADAPTERS = %i[nokogiri oga rexml ox].freeze
8
8
 
9
9
  class << self
10
10
  def load(name)
data/lib/moxml/cdata.rb CHANGED
@@ -9,9 +9,5 @@ module Moxml
9
9
  def content=(text)
10
10
  adapter.set_cdata_content(@native, normalize_xml_value(text))
11
11
  end
12
-
13
- def cdata?
14
- true
15
- end
16
12
  end
17
13
  end
data/lib/moxml/comment.rb CHANGED
@@ -11,9 +11,5 @@ module Moxml
11
11
  adapter.validate_comment_content(text)
12
12
  adapter.set_comment_content(@native, text)
13
13
  end
14
-
15
- def comment?
16
- true
17
- end
18
14
  end
19
15
  end
data/lib/moxml/context.rb CHANGED
@@ -8,8 +8,8 @@ module Moxml
8
8
  @config = Config.new(adapter)
9
9
  end
10
10
 
11
- def create_document
12
- Document.new(config.adapter.create_document, self)
11
+ def create_document(native_doc = nil)
12
+ Document.new(config.adapter.create_document(native_doc), self)
13
13
  end
14
14
 
15
15
  def parse(xml, options = {})
data/lib/moxml/doctype.rb CHANGED
@@ -1,9 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Moxml
4
- class Doctype < Node
5
- def doctype?
6
- true
7
- end
8
- end
4
+ class Doctype < Node; end
9
5
  end
@@ -63,7 +63,7 @@ module Moxml
63
63
  if children.empty?
64
64
  adapter.add_child(@native, node.native)
65
65
  else
66
- adapter.add_previous_sibling(children.first.native, node.native)
66
+ adapter.add_previous_sibling(adapter.children(@native).first, node.native)
67
67
  end
68
68
  elsif root && !node.is_a?(ProcessingInstruction) && !node.is_a?(Comment)
69
69
  raise Error, "Document already has a root element"
@@ -10,7 +10,7 @@ module Moxml
10
10
  end
11
11
 
12
12
  def build(native_doc)
13
- @current_doc = context.create_document
13
+ @current_doc = context.create_document(native_doc)
14
14
  visit_node(native_doc)
15
15
  @current_doc
16
16
  end
data/lib/moxml/element.rb CHANGED
@@ -52,7 +52,8 @@ module Moxml
52
52
  ns && Namespace.new(ns, context)
53
53
  end
54
54
 
55
- # it does NOT change the list of namespace definitions
55
+ # add the prefix to the element name
56
+ # and add the namespace to the list of namespace definitions
56
57
  def namespace=(ns_or_hash)
57
58
  if ns_or_hash.is_a?(Hash)
58
59
  adapter.set_namespace(
@@ -22,6 +22,10 @@ module Moxml
22
22
  end
23
23
  end
24
24
 
25
+ def default?
26
+ prefix.nil? || prefix.to_s == "xmlns"
27
+ end
28
+
25
29
  def namespace?
26
30
  true
27
31
  end
data/lib/moxml/node.rb CHANGED
@@ -7,11 +7,17 @@ module Moxml
7
7
  class Node
8
8
  include XmlUtils
9
9
 
10
+ TYPES = %i[
11
+ element text cdata comment processing_instruction document
12
+ declaration doctype namespace attribute unknown
13
+ ].freeze
14
+
10
15
  attr_reader :native, :context
11
16
 
12
17
  def initialize(native, context)
13
- @native = native
14
18
  @context = context
19
+ # @native = adapter.patch_node(native)
20
+ @native = native
15
21
  end
16
22
 
17
23
  def document
@@ -23,7 +29,10 @@ module Moxml
23
29
  end
24
30
 
25
31
  def children
26
- NodeSet.new(adapter.children(@native), context)
32
+ NodeSet.new(
33
+ adapter.children(@native).map { adapter.patch_node(_1, @native) },
34
+ context
35
+ )
27
36
  end
28
37
 
29
38
  def next_sibling
@@ -79,6 +88,12 @@ module Moxml
79
88
  self.class == other.class && @native == other.native
80
89
  end
81
90
 
91
+ TYPES.each do |node_type|
92
+ define_method "#{node_type}?" do
93
+ adapter.node_type(native) == node_type
94
+ end
95
+ end
96
+
82
97
  def self.wrap(node, context)
83
98
  return nil if node.nil?
84
99