moxml 0.1.3 → 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 (55) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +0 -1
  3. data/.rubocop_todo.yml +48 -20
  4. data/Gemfile +3 -0
  5. data/LICENSE.md +33 -0
  6. data/README.adoc +95 -23
  7. data/lib/moxml/adapter/base.rb +20 -2
  8. data/lib/moxml/adapter/customized_ox/attribute.rb +29 -0
  9. data/lib/moxml/adapter/customized_ox/namespace.rb +34 -0
  10. data/lib/moxml/adapter/customized_ox/text.rb +12 -0
  11. data/lib/moxml/adapter/customized_rexml/formatter.rb +195 -0
  12. data/lib/moxml/adapter/nokogiri.rb +4 -2
  13. data/lib/moxml/adapter/oga.rb +25 -9
  14. data/lib/moxml/adapter/ox.rb +238 -92
  15. data/lib/moxml/adapter/rexml.rb +462 -0
  16. data/lib/moxml/adapter.rb +1 -1
  17. data/lib/moxml/attribute.rb +2 -2
  18. data/lib/moxml/cdata.rb +0 -4
  19. data/lib/moxml/comment.rb +0 -4
  20. data/lib/moxml/config.rb +1 -1
  21. data/lib/moxml/context.rb +2 -2
  22. data/lib/moxml/doctype.rb +1 -5
  23. data/lib/moxml/document.rb +1 -1
  24. data/lib/moxml/document_builder.rb +14 -18
  25. data/lib/moxml/element.rb +4 -3
  26. data/lib/moxml/namespace.rb +5 -1
  27. data/lib/moxml/node.rb +17 -2
  28. data/lib/moxml/node_set.rb +8 -1
  29. data/lib/moxml/processing_instruction.rb +0 -4
  30. data/lib/moxml/text.rb +0 -4
  31. data/lib/moxml/version.rb +1 -1
  32. data/lib/ox/node.rb +9 -0
  33. data/spec/fixtures/small.xml +1 -0
  34. data/spec/moxml/adapter/rexml_spec.rb +14 -0
  35. data/spec/moxml/all_with_adapters_spec.rb +2 -3
  36. data/spec/support/shared_examples/builder.rb +19 -2
  37. data/spec/support/shared_examples/cdata.rb +7 -5
  38. data/spec/support/shared_examples/declaration.rb +17 -4
  39. data/spec/support/shared_examples/doctype.rb +2 -1
  40. data/spec/support/shared_examples/document.rb +10 -0
  41. data/spec/support/shared_examples/edge_cases.rb +9 -3
  42. data/spec/support/shared_examples/element.rb +5 -1
  43. data/spec/support/shared_examples/examples/benchmark_spec.rb +51 -0
  44. data/spec/support/shared_examples/examples/memory.rb +30 -17
  45. data/spec/support/shared_examples/examples/readme_examples.rb +5 -0
  46. data/spec/support/shared_examples/examples/thread_safety.rb +2 -0
  47. data/spec/support/shared_examples/examples/xpath.rb +34 -3
  48. data/spec/support/shared_examples/integration.rb +6 -2
  49. data/spec/support/shared_examples/namespace.rb +16 -0
  50. data/spec/support/shared_examples/node.rb +4 -0
  51. data/spec/support/shared_examples/node_set.rb +20 -0
  52. data/spec/support/shared_examples/processing_instruction.rb +1 -1
  53. data/spec/support/shared_examples/text.rb +2 -1
  54. data/spec/support/shared_examples/xml_adapter.rb +169 -7
  55. metadata +13 -3
@@ -0,0 +1,462 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+ require "rexml/document"
5
+ require "rexml/xpath"
6
+ require "set"
7
+ require_relative "customized_rexml/formatter"
8
+
9
+ module Moxml
10
+ module Adapter
11
+ class Rexml < Base
12
+ class << self
13
+ def parse(xml, options = {})
14
+ native_doc = begin
15
+ ::REXML::Document.new(xml)
16
+ rescue ::REXML::ParseException => e
17
+ raise Moxml::ParseError.new(e.message, line: e.line) if options[:strict]
18
+
19
+ create_document
20
+ end
21
+ DocumentBuilder.new(Context.new(:rexml)).build(native_doc)
22
+ end
23
+
24
+ def create_document(_native_doc = nil)
25
+ ::REXML::Document.new
26
+ end
27
+
28
+ def create_native_element(name)
29
+ ::REXML::Element.new(name.to_s)
30
+ end
31
+
32
+ def create_native_text(content)
33
+ ::REXML::Text.new(content.to_s, true, nil)
34
+ end
35
+
36
+ def create_native_cdata(content)
37
+ ::REXML::CData.new(content.to_s)
38
+ end
39
+
40
+ def create_native_comment(content)
41
+ ::REXML::Comment.new(content.to_s)
42
+ end
43
+
44
+ def create_native_processing_instruction(target, content)
45
+ # Clone strings to avoid frozen string errors
46
+ ::REXML::Instruction.new(target.to_s.dup, content.to_s.dup)
47
+ end
48
+
49
+ def create_native_declaration(version, encoding, standalone)
50
+ ::REXML::XMLDecl.new(version, encoding&.downcase, standalone)
51
+ end
52
+
53
+ def create_native_doctype(name, external_id, system_id)
54
+ return nil unless name
55
+
56
+ parts = [name]
57
+ if external_id
58
+ parts.concat(["PUBLIC", %("#{external_id}")])
59
+ parts << %("#{system_id}") if system_id
60
+ elsif system_id
61
+ parts.concat(["SYSTEM", %("#{system_id}")])
62
+ end
63
+
64
+ ::REXML::DocType.new(parts.join(" "))
65
+ end
66
+
67
+ def set_root(doc, element)
68
+ doc.add_element(element)
69
+ end
70
+
71
+ def node_type(node)
72
+ case node
73
+ when ::REXML::Document then :document
74
+ when ::REXML::Element then :element
75
+ when ::REXML::CData then :cdata
76
+ when ::REXML::Text then :text
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
80
+ when ::REXML::Instruction then :processing_instruction
81
+ when ::REXML::DocType then :doctype
82
+ when ::REXML::XMLDecl then :declaration
83
+ else :unknown
84
+ end
85
+ end
86
+
87
+ def set_node_name(node, name)
88
+ case node
89
+ when ::REXML::Element
90
+ node.name = name.to_s
91
+ when ::REXML::Instruction
92
+ node.target = name.to_s
93
+ end
94
+ end
95
+
96
+ def node_name(node)
97
+ case node
98
+ when ::REXML::Element, ::REXML::DocType
99
+ node.name
100
+ when ::REXML::XMLDecl
101
+ "xml"
102
+ when ::REXML::Instruction
103
+ node.target
104
+ end
105
+ end
106
+
107
+ def duplicate_node(node)
108
+ # Make a complete duplicate of the node
109
+ # https://stackoverflow.com/questions/23878384/why-the-original-element-got-changed-when-i-modify-the-copy-created-by-dup-meth
110
+ Marshal.load(Marshal.dump(node))
111
+ end
112
+
113
+ def children(node)
114
+ return [] unless node.respond_to?(:children)
115
+
116
+ # Get all children and filter out empty text nodes between elements
117
+ result = node.children.reject do |child|
118
+ child.is_a?(::REXML::Text) &&
119
+ child.to_s.strip.empty? &&
120
+ !(child.next_sibling.nil? && child.previous_sibling.nil?)
121
+ end
122
+
123
+ # Ensure uniqueness by object_id to prevent duplicates
124
+ result.uniq(&:object_id)
125
+ end
126
+
127
+ def parent(node)
128
+ node.parent
129
+ end
130
+
131
+ def next_sibling(node)
132
+ current = node.next_sibling
133
+
134
+ # Skip empty text nodes and duplicates
135
+ seen = Set.new
136
+ while current
137
+ if current.is_a?(::REXML::Text) && current.to_s.strip.empty?
138
+ current = current.next_sibling
139
+ next
140
+ end
141
+
142
+ # Check for duplicates
143
+ if seen.include?(current.object_id)
144
+ current = current.next_sibling
145
+ next
146
+ end
147
+
148
+ seen.add(current.object_id)
149
+ break
150
+ end
151
+
152
+ current
153
+ end
154
+
155
+ def previous_sibling(node)
156
+ current = node.previous_sibling
157
+
158
+ # Skip empty text nodes and duplicates
159
+ seen = Set.new
160
+ while current
161
+ if current.is_a?(::REXML::Text) && current.to_s.strip.empty?
162
+ current = current.previous_sibling
163
+ next
164
+ end
165
+
166
+ # Check for duplicates
167
+ if seen.include?(current.object_id)
168
+ current = current.previous_sibling
169
+ next
170
+ end
171
+
172
+ seen.add(current.object_id)
173
+ break
174
+ end
175
+
176
+ current
177
+ end
178
+
179
+ def document(node)
180
+ node.document
181
+ end
182
+
183
+ def root(document)
184
+ document.root
185
+ end
186
+
187
+ def attributes(element)
188
+ return [] unless element.respond_to?(:attributes)
189
+
190
+ # Only return non-namespace attributes
191
+ element.attributes.values
192
+ .reject { |attr| attr.prefix.to_s.start_with?("xmlns") }
193
+ end
194
+
195
+ def attribute_element(attribute)
196
+ attribute.element
197
+ end
198
+
199
+ def set_attribute(element, name, value)
200
+ element.attributes[name&.to_s] = value
201
+ ::REXML::Attribute.new(name&.to_s, value.to_s, element)
202
+ end
203
+
204
+ def set_attribute_name(attribute, name)
205
+ old_name = attribute.expanded_name
206
+ attribute.name = name
207
+ # Rexml doesn't change the keys of the attributes hash
208
+ element = attribute.element
209
+ element.attributes.delete(old_name)
210
+ element.attributes << attribute
211
+ end
212
+
213
+ def set_attribute_value(attribute, value)
214
+ attribute.normalized = value
215
+ end
216
+
217
+ def get_attribute(element, name)
218
+ element.attributes.get_attribute(name)
219
+ end
220
+
221
+ def get_attribute_value(element, name)
222
+ element.attributes[name]
223
+ end
224
+
225
+ def remove_attribute(element, name)
226
+ element.delete_attribute(name.to_s)
227
+ end
228
+
229
+ def add_child(element, child)
230
+ case child
231
+ when String
232
+ element.add_text(child)
233
+ else
234
+ element.add(child)
235
+ end
236
+ end
237
+
238
+ def add_previous_sibling(node, sibling)
239
+ parent = node.parent
240
+ # caveat: Rexml fails if children belong to the same parent and are already in a correct order
241
+ # example: "<root><a/><b/></root>"
242
+ # add_previous_sibling(node_b, node_a)
243
+ # result: "<root><b/><a/></root>"
244
+ # expected result: "<root><a/><b/></root>"
245
+ parent.insert_before(node, sibling)
246
+ end
247
+
248
+ def add_next_sibling(node, sibling)
249
+ parent = node.parent
250
+ parent.insert_after(node, sibling)
251
+ end
252
+
253
+ def remove(node)
254
+ node.remove
255
+ end
256
+
257
+ def replace(node, new_node)
258
+ node.replace_with(new_node)
259
+ end
260
+
261
+ def replace_children(element, children)
262
+ element.children.each(&:remove)
263
+ children.each { |child| element.add(child) }
264
+ end
265
+
266
+ def declaration_attribute(node, name)
267
+ case name
268
+ when "version"
269
+ node.version
270
+ when "encoding"
271
+ node.encoding
272
+ when "standalone"
273
+ node.standalone
274
+ end
275
+ end
276
+
277
+ def set_declaration_attribute(node, name, value)
278
+ case name
279
+ when "version"
280
+ node.version = value
281
+ when "encoding"
282
+ node.encoding = value
283
+ when "standalone"
284
+ node.standalone = value
285
+ end
286
+ end
287
+
288
+ def comment_content(node)
289
+ node.string
290
+ end
291
+
292
+ def set_comment_content(node, content)
293
+ node.string = content.to_s
294
+ end
295
+
296
+ def cdata_content(node)
297
+ node.value
298
+ end
299
+
300
+ def set_cdata_content(node, content)
301
+ node.value = content.to_s
302
+ end
303
+
304
+ def processing_instruction_target(node)
305
+ node.target
306
+ end
307
+
308
+ def processing_instruction_content(node)
309
+ node.content
310
+ end
311
+
312
+ def set_processing_instruction_content(node, content)
313
+ node.content = content.to_s
314
+ end
315
+
316
+ def text_content(node)
317
+ case node
318
+ when ::REXML::Text, ::REXML::CData
319
+ node.value.to_s
320
+ when ::REXML::Element
321
+ # Get all text nodes, filter out duplicates, and join
322
+ text_nodes = node.texts.uniq(&:object_id)
323
+ text_nodes.map(&:value).join
324
+ end
325
+ end
326
+
327
+ def inner_text(node)
328
+ # Get direct text children only, filter duplicates
329
+ text_children = node.children
330
+ .select { _1.is_a?(::REXML::Text) }
331
+ .uniq(&:object_id)
332
+ text_children.map(&:value).join
333
+ end
334
+
335
+ def set_text_content(node, content)
336
+ case node
337
+ when ::REXML::Text, ::REXML::CData
338
+ node.value = content.to_s
339
+ when ::REXML::Element
340
+ # Remove existing text nodes to prevent duplicates
341
+ node.texts.each(&:remove)
342
+ # Add new text content
343
+ node.add_text(content.to_s)
344
+ end
345
+ end
346
+
347
+ # add a namespace definition, keep the element name unchanged
348
+ def create_native_namespace(element, prefix, uri)
349
+ element.add_namespace(prefix.to_s, uri)
350
+ ::REXML::Attribute.new(prefix.to_s, uri, element)
351
+ end
352
+
353
+ # add a namespace prefix to the element name AND a namespace definition
354
+ def set_namespace(element, ns)
355
+ prefix = ns.name.to_s.empty? ? "xmlns" : ns.name.to_s
356
+ element.add_namespace(prefix, ns.value) if element.respond_to?(:add_namespace)
357
+ element.name = "#{prefix}:#{element.name}"
358
+ owner = element.is_a?(::REXML::Attribute) ? element.element : element
359
+ ::REXML::Attribute.new(prefix, ns.value, owner)
360
+ end
361
+
362
+ def namespace_prefix(node)
363
+ node.name unless node.name == "xmlns"
364
+ end
365
+
366
+ def namespace_uri(node)
367
+ node.value
368
+ end
369
+
370
+ def namespace(node)
371
+ prefix = node.prefix
372
+ uri = node.namespace(prefix)
373
+ return if prefix.to_s.empty? && uri.to_s.empty?
374
+
375
+ owner = node.is_a?(::REXML::Attribute) ? node.element : node
376
+ ::REXML::Attribute.new(prefix, uri, owner)
377
+ end
378
+
379
+ def namespace_definitions(node)
380
+ node.namespaces.map do |prefix, uri|
381
+ ::REXML::Attribute.new(prefix.to_s, uri, node)
382
+ end
383
+ end
384
+
385
+ # not used at the moment
386
+ # but may be useful when the xpath is upgraded to work with namespaces
387
+ def prepare_xpath_namespaces(node)
388
+ ns = {}
389
+
390
+ # Get all namespace definitions in scope
391
+ all_ns = namespace_definitions(node)
392
+
393
+ # Convert to XPath-friendly format
394
+ all_ns.each do |prefix, uri|
395
+ if prefix.to_s.empty?
396
+ ns["xmlns"] = uri
397
+ else
398
+ ns[prefix] = uri
399
+ end
400
+ end
401
+
402
+ ns
403
+ end
404
+
405
+ def xpath(node, expression, _namespaces = {})
406
+ node.get_elements(expression).to_a
407
+ rescue ::REXML::ParseException => e
408
+ raise Moxml::XPathError, e.message
409
+ end
410
+
411
+ def at_xpath(node, expression, namespaces = {})
412
+ results = xpath(node, expression, namespaces)
413
+ results.first
414
+ end
415
+
416
+ def serialize(node, options = {})
417
+ output = String.new
418
+
419
+ if node.is_a?(::REXML::Document)
420
+ # Always include XML declaration
421
+ decl = node.xml_decl || ::REXML::XMLDecl.new("1.0", options[:encoding] || "UTF-8")
422
+ decl.encoding = options[:encoding] if options[:encoding]
423
+ output << "<?xml"
424
+ output << %( version="#{decl.version}") if decl.version
425
+ output << %( encoding="#{decl.encoding}") if decl.encoding
426
+ output << %( standalone="#{decl.standalone}") if decl.standalone
427
+ output << "?>"
428
+ # output << "\n"
429
+
430
+ if node.doctype
431
+ node.doctype.write(output)
432
+ # output << "\n"
433
+ end
434
+
435
+ # Write processing instructions
436
+ node.children.each do |child|
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"
441
+ end
442
+
443
+ write_with_formatter(node.root, output, options[:indent] || 2) if node.root
444
+ else
445
+ write_with_formatter(node, output, options[:indent] || 2)
446
+ end
447
+
448
+ output.strip
449
+ end
450
+
451
+ private
452
+
453
+ def write_with_formatter(node, output, indent = 2)
454
+ formatter = ::Moxml::Adapter::CustomizedRexml::Formatter.new(
455
+ indentation: indent, self_close_empty: false
456
+ )
457
+ formatter.write(node, output)
458
+ end
459
+ end
460
+ end
461
+ end
462
+ end
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].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)
@@ -7,7 +7,7 @@ module Moxml
7
7
  end
8
8
 
9
9
  def name=(new_name)
10
- @native.name = new_name
10
+ adapter.set_attribute_name(@native, new_name)
11
11
  end
12
12
 
13
13
  def value
@@ -15,7 +15,7 @@ module Moxml
15
15
  end
16
16
 
17
17
  def value=(new_value)
18
- @native.value = normalize_xml_value(new_value)
18
+ adapter.set_attribute_value(@native, new_value)
19
19
  end
20
20
 
21
21
  def namespace
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/config.rb CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Moxml
4
4
  class Config
5
- VALID_ADAPTERS = %i[nokogiri oga ox].freeze
5
+ VALID_ADAPTERS = %i[nokogiri oga rexml ox].freeze
6
6
  DEFAULT_ADAPTER = VALID_ADAPTERS.first
7
7
 
8
8
  class << self
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
@@ -18,7 +18,6 @@ module Moxml
18
18
  private
19
19
 
20
20
  def visit_node(node)
21
- node.respond_to?(:name) ? node.name : node
22
21
  method_name = "visit_#{node_type(node)}"
23
22
  return unless respond_to?(method_name, true)
24
23
 
@@ -28,41 +27,38 @@ module Moxml
28
27
  def visit_document(doc)
29
28
  @node_stack.push(@current_doc)
30
29
  visit_children(doc)
31
- @node_stack.pop
30
+ @node_stack.clear
32
31
  end
33
32
 
34
33
  def visit_element(node)
35
- element = Element.new(node, context)
36
- if @node_stack.empty?
37
- # For root element, we need to set it directly
38
- adapter.set_root(@current_doc.native, element.native)
39
- else
40
- @node_stack.last.add_child(element)
41
- end
42
- @node_stack.push(element)
34
+ childless_node = adapter.duplicate_node(node)
35
+ adapter.replace_children(childless_node, [])
36
+ element = Element.new(childless_node, context)
37
+ @node_stack.last.add_child(element)
38
+
39
+ @node_stack.push(element) # add a parent for its children
43
40
  visit_children(node)
44
- @node_stack.pop
45
- element
41
+ @node_stack.pop # remove the parent
46
42
  end
47
43
 
48
44
  def visit_text(node)
49
- @node_stack.last.add_child(Text.new(node, context)) if @node_stack.any?
45
+ @node_stack.last&.add_child(Text.new(node, context))
50
46
  end
51
47
 
52
48
  def visit_cdata(node)
53
- @node_stack.last.add_child(Cdata.new(node, context)) if @node_stack.any?
49
+ @node_stack.last&.add_child(Cdata.new(node, context))
54
50
  end
55
51
 
56
52
  def visit_comment(node)
57
- @node_stack.last.add_child(Comment.new(node, context)) if @node_stack.any?
53
+ @node_stack.last&.add_child(Comment.new(node, context))
58
54
  end
59
55
 
60
56
  def visit_processing_instruction(node)
61
- @node_stack.last.add_child(ProcessingInstruction.new(node, context)) if @node_stack.any?
57
+ @node_stack.last&.add_child(ProcessingInstruction.new(node, context))
62
58
  end
63
59
 
64
60
  def visit_doctype(node)
65
- @node_stack.last.add_child(Doctype.new(node, context)) if @node_stack.any?
61
+ @node_stack.last&.add_child(Doctype.new(node, context))
66
62
  end
67
63
 
68
64
  def visit_children(node)
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(
@@ -87,8 +88,8 @@ module Moxml
87
88
  adapter.inner_xml(@native)
88
89
  end
89
90
 
90
- def inner_xml=(html)
91
- doc = context.parse("<root>#{html}</root>")
91
+ def inner_xml=(xml)
92
+ doc = context.parse("<root>#{xml}</root>")
92
93
  adapter.replace_children(@native, doc.root.children.map(&:native))
93
94
  end
94
95
 
@@ -15,13 +15,17 @@ module Moxml
15
15
  end
16
16
 
17
17
  def to_s
18
- if prefix
18
+ if prefix && prefix != "xmlns"
19
19
  %(xmlns:#{prefix}="#{uri}")
20
20
  else
21
21
  %(xmlns="#{uri}")
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