moxml 0.1.8 → 0.1.9
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.
- checksums.yaml +4 -4
- data/.rubocop_todo.yml +22 -39
- data/docs/_config.yml +3 -3
- data/docs/_guides/index.adoc +6 -0
- data/docs/_guides/modifying-xml.adoc +0 -1
- data/docs/_guides/parsing-xml.adoc +0 -1
- data/docs/_guides/xml-declaration.adoc +450 -0
- data/docs/_pages/adapter-compatibility.adoc +1 -1
- data/docs/_pages/adapters/headed-ox.adoc +9 -9
- data/docs/_pages/adapters/index.adoc +0 -1
- data/docs/_pages/adapters/libxml.adoc +1 -2
- data/docs/_pages/adapters/nokogiri.adoc +1 -2
- data/docs/_pages/adapters/oga.adoc +1 -2
- data/docs/_pages/adapters/ox.adoc +2 -1
- data/docs/_pages/adapters/rexml.adoc +1 -2
- data/docs/_pages/best-practices.adoc +0 -1
- data/docs/_pages/compatibility.adoc +0 -1
- data/docs/_pages/configuration.adoc +0 -1
- data/docs/_pages/error-handling.adoc +0 -1
- data/docs/_pages/headed-ox-limitations.adoc +16 -0
- data/docs/_pages/installation.adoc +0 -1
- data/docs/_pages/node-api-reference.adoc +0 -1
- data/docs/_pages/performance.adoc +0 -1
- data/docs/_pages/quick-start.adoc +0 -1
- data/docs/_pages/thread-safety.adoc +0 -1
- data/docs/_references/document-api.adoc +0 -1
- data/docs/_tutorials/basic-usage.adoc +0 -1
- data/docs/_tutorials/builder-pattern.adoc +0 -1
- data/docs/_tutorials/namespace-handling.adoc +0 -1
- data/docs/_tutorials/xpath-queries.adoc +0 -1
- data/lib/moxml/adapter/customized_rexml/formatter.rb +2 -2
- data/lib/moxml/adapter/libxml.rb +19 -3
- data/lib/moxml/adapter/nokogiri.rb +37 -2
- data/lib/moxml/adapter/oga.rb +67 -3
- data/lib/moxml/adapter/ox.rb +45 -7
- data/lib/moxml/adapter/rexml.rb +32 -10
- data/lib/moxml/context.rb +18 -1
- data/lib/moxml/declaration.rb +9 -0
- data/lib/moxml/document.rb +14 -0
- data/lib/moxml/document_builder.rb +7 -0
- data/lib/moxml/node.rb +61 -1
- data/lib/moxml/version.rb +1 -1
- data/lib/moxml/xpath/compiler.rb +2 -0
- data/spec/integration/shared_examples/node_wrappers/declaration_behavior.rb +0 -3
- data/spec/moxml/declaration_preservation_spec.rb +217 -0
- data/spec/performance/memory_usage_spec.rb +3 -2
- metadata +3 -1
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
---
|
|
2
|
-
title: HeadedOx
|
|
2
|
+
title: HeadedOx
|
|
3
3
|
parent: Adapters
|
|
4
4
|
nav_order: 6
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
== HeadedOx adapter
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
=== General
|
|
10
10
|
|
|
11
11
|
The HeadedOx adapter combines Ox's fast C-based XML parsing with Moxml's
|
|
12
12
|
comprehensive pure Ruby XPath 1.0 engine.
|
|
@@ -39,7 +39,7 @@ cheap = doc.xpath('//book[@price <= sum(//book/@price) div count(//book)]')
|
|
|
39
39
|
IMPORTANT: For complete XPath 1.0 specification with zero limitations today, use
|
|
40
40
|
Nokogiri or Oga adapters.
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
=== Features
|
|
43
43
|
|
|
44
44
|
* Fast XML parsing (Ox C extension) - Same speed as standard Ox
|
|
45
45
|
* 6 of 13 XPath axes (46% - covers 80% of common usage patterns)
|
|
@@ -48,7 +48,7 @@ Nokogiri or Oga adapters.
|
|
|
48
48
|
* Expression compilation and caching (1000-entry LRU cache)
|
|
49
49
|
* Document construction and serialization through Ox
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
=== Architecture
|
|
52
52
|
|
|
53
53
|
HeadedOx is a **hybrid adapter** that layers Moxml's pure Ruby XPath engine on
|
|
54
54
|
top of Ox's fast C parser:
|
|
@@ -78,7 +78,7 @@ top of Ox's fast C parser:
|
|
|
78
78
|
└─────────────────────────┘
|
|
79
79
|
----
|
|
80
80
|
|
|
81
|
-
|
|
81
|
+
=== Known limitations
|
|
82
82
|
|
|
83
83
|
The following 16 test failures represent architectural boundaries in the Ox gem,
|
|
84
84
|
not bugs in HeadedOx:
|
|
@@ -100,7 +100,7 @@ See link:docs/HEADED_OX_LIMITATIONS.md[HEADED_OX_LIMITATIONS.md] for:
|
|
|
100
100
|
* When to use HeadedOx vs other adapters decision guide
|
|
101
101
|
* Future roadmap if Ox adds namespace introspection API
|
|
102
102
|
|
|
103
|
-
|
|
103
|
+
=== When to Use HeadedOx
|
|
104
104
|
|
|
105
105
|
You can use HeadedOx instead of Ox for all XML parsing needs, except when
|
|
106
106
|
certain advanced XPath features are required.
|
|
@@ -126,7 +126,7 @@ link:docs/headed-ox.adoc[HeadedOx Implementation Guide] and
|
|
|
126
126
|
link:docs/HEADED_OX_LIMITATIONS.md[HeadedOx Limitations Documentation].
|
|
127
127
|
|
|
128
128
|
|
|
129
|
-
|
|
129
|
+
=== XPath capabilities
|
|
130
130
|
|
|
131
131
|
[cols="1,1,4"]
|
|
132
132
|
|===
|
|
@@ -169,7 +169,7 @@ operator predicates, complex nested expressions
|
|
|
169
169
|
|===
|
|
170
170
|
|
|
171
171
|
|
|
172
|
-
|
|
172
|
+
=== What XPath queries work in HeadedOx
|
|
173
173
|
|
|
174
174
|
NOTE: This table is of v0.2.0.
|
|
175
175
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
title: Ox
|
|
2
|
+
title: Ox
|
|
3
3
|
parent: Adapters
|
|
4
4
|
nav_order: 5
|
|
5
5
|
---
|
|
@@ -11,6 +11,7 @@ nav_order: 5
|
|
|
11
11
|
Ox is the fastest XML parser available for Ruby, providing excellent performance for simple to moderately complex XML documents.
|
|
12
12
|
|
|
13
13
|
**Best for:**
|
|
14
|
+
|
|
14
15
|
* Maximum parsing speed
|
|
15
16
|
* Simple document structures
|
|
16
17
|
* Memory-constrained environments
|
|
@@ -77,6 +77,7 @@ all_attrs = books.flat_map { |book| book.attributes.values }
|
|
|
77
77
|
----
|
|
78
78
|
|
|
79
79
|
**Test failures:**
|
|
80
|
+
|
|
80
81
|
* `spec/moxml/xpath/compiler_spec.rb:189` - Attribute axis wildcards
|
|
81
82
|
* `spec/moxml/xpath/axes_spec.rb:220` - Attribute + predicate combinations
|
|
82
83
|
|
|
@@ -85,6 +86,7 @@ all_attrs = books.flat_map { |book| book.attributes.values }
|
|
|
85
86
|
**Status:** Not implemented in HeadedOx adapter
|
|
86
87
|
|
|
87
88
|
**What's missing:**
|
|
89
|
+
|
|
88
90
|
* `adapter.namespace(node)` - Get primary namespace of element
|
|
89
91
|
* `adapter.namespace_definitions(node)` - Get all namespace definitions
|
|
90
92
|
* `node.namespace` - Access element's namespace
|
|
@@ -114,6 +116,7 @@ end
|
|
|
114
116
|
None. These operations require Ox enhancements.
|
|
115
117
|
|
|
116
118
|
**Test failures:**
|
|
119
|
+
|
|
117
120
|
* `spec/integration/shared_examples/edge_cases.rb:102` - Default namespace changes
|
|
118
121
|
* `spec/integration/shared_examples/edge_cases.rb:120` - Recursive namespace definitions
|
|
119
122
|
* `spec/integration/shared_examples/integration_workflows.rb:98` - Complex namespace scenarios
|
|
@@ -148,6 +151,7 @@ value = attr&.value
|
|
|
148
151
|
----
|
|
149
152
|
|
|
150
153
|
**Test failures:**
|
|
154
|
+
|
|
151
155
|
* `spec/integration/shared_examples/edge_cases.rb:134` - Attributes with same local name
|
|
152
156
|
|
|
153
157
|
=== 4. Parent Node Setter
|
|
@@ -189,6 +193,7 @@ new_parent.add_child(node) # Add to new parent
|
|
|
189
193
|
**Note:** This workaround is used internally where needed, but the getter/setter syntax is not supported.
|
|
190
194
|
|
|
191
195
|
**Test failures:**
|
|
196
|
+
|
|
192
197
|
* `spec/integration/shared_examples/integration_workflows.rb:122` - Complex modifications
|
|
193
198
|
|
|
194
199
|
=== 5. CDATA End Marker Escaping
|
|
@@ -224,6 +229,7 @@ doc.create_cdata(safe_content)
|
|
|
224
229
|
----
|
|
225
230
|
|
|
226
231
|
**Test failures:**
|
|
232
|
+
|
|
227
233
|
* `spec/integration/shared_examples/edge_cases.rb:41` - CDATA nested markers
|
|
228
234
|
* `spec/integration/shared_examples/node_wrappers/cdata_behavior.rb:44` - CDATA escaping
|
|
229
235
|
|
|
@@ -263,6 +269,7 @@ second_title = titles[1].text # Works correctly
|
|
|
263
269
|
----
|
|
264
270
|
|
|
265
271
|
**Test failures:**
|
|
272
|
+
|
|
266
273
|
* `spec/moxml/adapter/headed_ox_spec.rb:77` - String functions in predicates
|
|
267
274
|
* `spec/moxml/adapter/headed_ox_spec.rb:84` - Position functions
|
|
268
275
|
* `spec/moxml/adapter/headed_ox_spec.rb:304` - last() function
|
|
@@ -277,6 +284,7 @@ second_title = titles[1].text # Works correctly
|
|
|
277
284
|
**Why it fails:**
|
|
278
285
|
|
|
279
286
|
When using `//*` to select all elements, HeadedOx returns 6 elements while Nokogiri returns 7+. This is likely due to differences in:
|
|
287
|
+
|
|
280
288
|
* Document node counting
|
|
281
289
|
* Text node inclusion/exclusion
|
|
282
290
|
* Ox's internal DOM structure
|
|
@@ -297,6 +305,7 @@ result = doc.xpath("//*")
|
|
|
297
305
|
Use specific element names instead of wildcards.
|
|
298
306
|
|
|
299
307
|
**Test failures:**
|
|
308
|
+
|
|
300
309
|
* `spec/moxml/xpath/compiler_spec.rb:160` - Descendant-or-self wildcards
|
|
301
310
|
|
|
302
311
|
=== 8. Namespace-Aware XPath with Predicates
|
|
@@ -340,6 +349,7 @@ result = items.select { |item| item['id'] == '123' }
|
|
|
340
349
|
----
|
|
341
350
|
|
|
342
351
|
**Test failures:**
|
|
352
|
+
|
|
343
353
|
* `spec/integration/shared_examples/integration_workflows.rb:69` - XPath queries
|
|
344
354
|
|
|
345
355
|
== Ox Enhancement Requirements
|
|
@@ -477,6 +487,7 @@ Ensure element counting matches other parsers' conventions when using wildcard s
|
|
|
477
487
|
=== If Ox Adds Namespace API (v1.3)
|
|
478
488
|
|
|
479
489
|
With namespace methods (`namespace()`, `namespace_definitions()`):
|
|
490
|
+
|
|
480
491
|
* **Target:** 99.5% pass rate
|
|
481
492
|
* **Adds:** 4 more passing tests
|
|
482
493
|
* **Still limited:** Parent setter, CDATA escaping, attribute wildcards
|
|
@@ -484,6 +495,7 @@ With namespace methods (`namespace()`, `namespace_definitions()`):
|
|
|
484
495
|
=== If Ox Adds Reparenting API (v1.4)
|
|
485
496
|
|
|
486
497
|
With `reparent(new_parent)` method:
|
|
498
|
+
|
|
487
499
|
* **Target:** 99.6% pass rate
|
|
488
500
|
* **Adds:** 1 more passing test
|
|
489
501
|
* **Still limited:** CDATA escaping, attribute wildcards
|
|
@@ -491,6 +503,7 @@ With `reparent(new_parent)` method:
|
|
|
491
503
|
=== If Ox Fixes CDATA Escaping (v1.5)
|
|
492
504
|
|
|
493
505
|
With proper `]]>` handling:
|
|
506
|
+
|
|
494
507
|
* **Target:** 99.7% pass rate
|
|
495
508
|
* **Adds:** 2 more passing tests
|
|
496
509
|
* **Still limited:** Attribute wildcards
|
|
@@ -498,6 +511,7 @@ With proper `]]>` handling:
|
|
|
498
511
|
=== Full Feature Parity (v2.0)
|
|
499
512
|
|
|
500
513
|
Would require:
|
|
514
|
+
|
|
501
515
|
* All Ox enhancements above
|
|
502
516
|
* XPath parser support for `@*` wildcard
|
|
503
517
|
* Investigation and fixes for text content access
|
|
@@ -546,11 +560,13 @@ Total passing: **1,992 / 2,008** (99.20%)
|
|
|
546
560
|
HeadedOx v1.2 successfully delivers on its core promise: **fast XML parsing with comprehensive XPath support**. The 99.20% pass rate demonstrates excellent compatibility with Moxml's test suite, with the 0.80% of failures representing clear architectural boundaries in the Ox gem rather than bugs in HeadedOx.
|
|
547
561
|
|
|
548
562
|
**Use HeadedOx when:**
|
|
563
|
+
|
|
549
564
|
- Speed + XPath coverage matter most
|
|
550
565
|
- Basic namespace queries are sufficient
|
|
551
566
|
- DOM is mostly read-only
|
|
552
567
|
|
|
553
568
|
**Use Nokogiri/Oga when:**
|
|
569
|
+
|
|
554
570
|
- Need full namespace API
|
|
555
571
|
- Heavy DOM modifications required
|
|
556
572
|
- 100% feature parity is critical
|
|
@@ -166,7 +166,7 @@ module Moxml
|
|
|
166
166
|
end
|
|
167
167
|
|
|
168
168
|
# Then write regular attributes
|
|
169
|
-
node.attributes.each do |name, attr|
|
|
169
|
+
node.attributes.each do |name, attr| # rubocop:disable Style/CombinableLoops
|
|
170
170
|
next if name.to_s.start_with?("xmlns:") || name.to_s == "xmlns"
|
|
171
171
|
|
|
172
172
|
output << " "
|
|
@@ -180,7 +180,7 @@ module Moxml
|
|
|
180
180
|
value = attr.respond_to?(:value) ? attr.value : attr
|
|
181
181
|
output << escape_attribute_value(value.to_s)
|
|
182
182
|
output << "\""
|
|
183
|
-
end
|
|
183
|
+
end # rubocop:enable Style/CombinableLoops
|
|
184
184
|
end
|
|
185
185
|
|
|
186
186
|
def escape_attribute_value(value)
|
data/lib/moxml/adapter/libxml.rb
CHANGED
|
@@ -332,7 +332,13 @@ module Moxml
|
|
|
332
332
|
|
|
333
333
|
def document(node)
|
|
334
334
|
native_node = unpatch_node(node)
|
|
335
|
-
native_node
|
|
335
|
+
return nil unless native_node
|
|
336
|
+
|
|
337
|
+
# Handle documents themselves
|
|
338
|
+
return native_node if native_node.is_a?(::LibXML::XML::Document)
|
|
339
|
+
|
|
340
|
+
# For other nodes, return their document
|
|
341
|
+
native_node.doc
|
|
336
342
|
end
|
|
337
343
|
|
|
338
344
|
def root(document)
|
|
@@ -831,7 +837,16 @@ module Moxml
|
|
|
831
837
|
if native_node.is_a?(::LibXML::XML::Document)
|
|
832
838
|
output = +""
|
|
833
839
|
|
|
834
|
-
|
|
840
|
+
# Check if we should include declaration
|
|
841
|
+
# Priority: explicit no_declaration option > default (include)
|
|
842
|
+
should_include_decl = if options.key?(:no_declaration)
|
|
843
|
+
!options[:no_declaration]
|
|
844
|
+
else
|
|
845
|
+
# Default: include declaration
|
|
846
|
+
true
|
|
847
|
+
end
|
|
848
|
+
|
|
849
|
+
if should_include_decl
|
|
835
850
|
# Check if declaration was explicitly managed
|
|
836
851
|
if native_node.instance_variable_defined?(:@moxml_declaration)
|
|
837
852
|
decl = native_node.instance_variable_get(:@moxml_declaration)
|
|
@@ -1301,7 +1316,7 @@ module Moxml
|
|
|
1301
1316
|
# - On child elements, output namespace definitions that override parent namespaces
|
|
1302
1317
|
if elem.respond_to?(:namespaces) && elem.namespaces.respond_to?(:definitions)
|
|
1303
1318
|
# Get parent's namespace definitions to detect overrides
|
|
1304
|
-
parent_ns_defs = if !include_ns && elem.respond_to?(:parent) && elem.parent
|
|
1319
|
+
parent_ns_defs = if !include_ns && elem.respond_to?(:parent) && elem.parent && !elem.parent.is_a?(::LibXML::XML::Document)
|
|
1305
1320
|
parent_namespaces = {}
|
|
1306
1321
|
if elem.parent.respond_to?(:namespaces)
|
|
1307
1322
|
elem.parent.namespaces.each do |ns|
|
|
@@ -1444,6 +1459,7 @@ module Moxml
|
|
|
1444
1459
|
node.each_child do |child|
|
|
1445
1460
|
collect_ns_from_subtree(child, ns_defs) if child.element?
|
|
1446
1461
|
end
|
|
1462
|
+
ns_defs
|
|
1447
1463
|
end
|
|
1448
1464
|
|
|
1449
1465
|
def build_xpath_namespaces(node, user_namespaces)
|
|
@@ -221,6 +221,23 @@ module Moxml
|
|
|
221
221
|
end
|
|
222
222
|
|
|
223
223
|
def add_child(element, child)
|
|
224
|
+
# Special handling for declarations on Nokogiri documents
|
|
225
|
+
if element.is_a?(::Nokogiri::XML::Document) &&
|
|
226
|
+
child.is_a?(::Nokogiri::XML::ProcessingInstruction) &&
|
|
227
|
+
child.name == "xml"
|
|
228
|
+
# Set document's xml_decl property
|
|
229
|
+
version = declaration_attribute(child, "version") || "1.0"
|
|
230
|
+
encoding = declaration_attribute(child, "encoding")
|
|
231
|
+
standalone = declaration_attribute(child, "standalone")
|
|
232
|
+
|
|
233
|
+
# Nokogiri's xml_decl can only be set via instance variable
|
|
234
|
+
element.instance_variable_set(:@xml_decl, {
|
|
235
|
+
version: version,
|
|
236
|
+
encoding: encoding,
|
|
237
|
+
standalone: standalone,
|
|
238
|
+
}.compact)
|
|
239
|
+
end
|
|
240
|
+
|
|
224
241
|
if node_type(child) == :doctype
|
|
225
242
|
# avoid exceptions: cannot reparent Nokogiri::XML::DTD there
|
|
226
243
|
element.create_internal_subset(
|
|
@@ -240,6 +257,14 @@ module Moxml
|
|
|
240
257
|
end
|
|
241
258
|
|
|
242
259
|
def remove(node)
|
|
260
|
+
# Special handling for declarations on Nokogiri documents
|
|
261
|
+
if node.is_a?(::Nokogiri::XML::ProcessingInstruction) &&
|
|
262
|
+
node.name == "xml" &&
|
|
263
|
+
node.parent.is_a?(::Nokogiri::XML::Document)
|
|
264
|
+
# Clear document's xml_decl when removing declaration
|
|
265
|
+
node.parent.instance_variable_set(:@xml_decl, nil)
|
|
266
|
+
end
|
|
267
|
+
|
|
243
268
|
node.remove
|
|
244
269
|
end
|
|
245
270
|
|
|
@@ -328,8 +353,18 @@ module Moxml
|
|
|
328
353
|
if options[:indent].to_i.positive?
|
|
329
354
|
save_options |= ::Nokogiri::XML::Node::SaveOptions::FORMAT
|
|
330
355
|
end
|
|
331
|
-
|
|
332
|
-
|
|
356
|
+
|
|
357
|
+
# Handle declaration option
|
|
358
|
+
# Priority:
|
|
359
|
+
# 1. Explicit no_declaration option
|
|
360
|
+
# 2. Check Nokogiri's internal @xml_decl (when remove is called, this becomes nil)
|
|
361
|
+
if options.key?(:no_declaration)
|
|
362
|
+
save_options |= ::Nokogiri::XML::Node::SaveOptions::NO_DECLARATION if options[:no_declaration]
|
|
363
|
+
elsif node.respond_to?(:instance_variable_get) &&
|
|
364
|
+
node.instance_variable_defined?(:@xml_decl)
|
|
365
|
+
# Nokogiri's internal state - if nil, declaration was removed
|
|
366
|
+
xml_decl = node.instance_variable_get(:@xml_decl)
|
|
367
|
+
save_options |= ::Nokogiri::XML::Node::SaveOptions::NO_DECLARATION if xml_decl.nil?
|
|
333
368
|
end
|
|
334
369
|
|
|
335
370
|
node.to_xml(
|
data/lib/moxml/adapter/oga.rb
CHANGED
|
@@ -10,7 +10,10 @@ module Moxml
|
|
|
10
10
|
class Oga < Base
|
|
11
11
|
class << self
|
|
12
12
|
def set_root(doc, element)
|
|
13
|
-
|
|
13
|
+
# Clear existing root element if any - Oga's NodeSet needs special handling
|
|
14
|
+
# We need to manually remove elements since NodeSet doesn't support clear or delete_if
|
|
15
|
+
elements_to_remove = doc.children.select { |child| child.is_a?(::Oga::XML::Element) }
|
|
16
|
+
elements_to_remove.each { |elem| doc.children.delete(elem) }
|
|
14
17
|
doc.children << element
|
|
15
18
|
end
|
|
16
19
|
|
|
@@ -247,6 +250,13 @@ module Moxml
|
|
|
247
250
|
child_or_text
|
|
248
251
|
end
|
|
249
252
|
|
|
253
|
+
# Special handling for declarations on Oga documents
|
|
254
|
+
if element.is_a?(::Oga::XML::Document) &&
|
|
255
|
+
child.is_a?(::Oga::XML::XmlDeclaration)
|
|
256
|
+
# Set as document's xml_declaration
|
|
257
|
+
element.instance_variable_set(:@xml_declaration, child)
|
|
258
|
+
end
|
|
259
|
+
|
|
250
260
|
element.children << child
|
|
251
261
|
end
|
|
252
262
|
|
|
@@ -273,6 +283,13 @@ module Moxml
|
|
|
273
283
|
end
|
|
274
284
|
|
|
275
285
|
def remove(node)
|
|
286
|
+
# Special handling for declarations on Oga documents
|
|
287
|
+
if node.is_a?(::Oga::XML::XmlDeclaration) &&
|
|
288
|
+
node.parent.is_a?(::Oga::XML::Document)
|
|
289
|
+
# Clear document's xml_declaration when removing declaration
|
|
290
|
+
node.parent.instance_variable_set(:@xml_declaration, nil)
|
|
291
|
+
end
|
|
292
|
+
|
|
276
293
|
node.remove
|
|
277
294
|
end
|
|
278
295
|
|
|
@@ -371,8 +388,55 @@ module Moxml
|
|
|
371
388
|
)
|
|
372
389
|
end
|
|
373
390
|
|
|
374
|
-
def serialize(node,
|
|
375
|
-
#
|
|
391
|
+
def serialize(node, options = {})
|
|
392
|
+
# Oga's XmlGenerator doesn't support options directly
|
|
393
|
+
# We need to handle declaration options ourselves for Document nodes
|
|
394
|
+
if node.is_a?(::Oga::XML::Document)
|
|
395
|
+
# Check if we should include declaration
|
|
396
|
+
# Priority: explicit option > existence of xml_declaration node
|
|
397
|
+
should_include_decl = if options.key?(:no_declaration)
|
|
398
|
+
!options[:no_declaration]
|
|
399
|
+
elsif options.key?(:declaration)
|
|
400
|
+
options[:declaration]
|
|
401
|
+
else
|
|
402
|
+
# Default: include if document has xml_declaration node
|
|
403
|
+
node.xml_declaration ? true : false
|
|
404
|
+
end
|
|
405
|
+
|
|
406
|
+
if should_include_decl && !node.xml_declaration
|
|
407
|
+
# Need to add declaration - create default one
|
|
408
|
+
output = +""
|
|
409
|
+
output << '<?xml version="1.0" encoding="UTF-8"?>'
|
|
410
|
+
output << "\n"
|
|
411
|
+
|
|
412
|
+
# Serialize doctype if present
|
|
413
|
+
output << node.doctype.to_xml << "\n" if node.doctype
|
|
414
|
+
|
|
415
|
+
# Serialize children
|
|
416
|
+
node.children.each do |child|
|
|
417
|
+
output << ::Moxml::Adapter::CustomizedOga::XmlGenerator.new(child).to_xml
|
|
418
|
+
end
|
|
419
|
+
|
|
420
|
+
return output
|
|
421
|
+
elsif !should_include_decl
|
|
422
|
+
# Skip xml_declaration
|
|
423
|
+
output = +""
|
|
424
|
+
|
|
425
|
+
# Serialize doctype if present
|
|
426
|
+
output << node.doctype.to_xml << "\n" if node.doctype
|
|
427
|
+
|
|
428
|
+
# Serialize root and other children
|
|
429
|
+
node.children.each do |child|
|
|
430
|
+
next if child.is_a?(::Oga::XML::XmlDeclaration)
|
|
431
|
+
|
|
432
|
+
output << ::Moxml::Adapter::CustomizedOga::XmlGenerator.new(child).to_xml
|
|
433
|
+
end
|
|
434
|
+
|
|
435
|
+
return output
|
|
436
|
+
end
|
|
437
|
+
end
|
|
438
|
+
|
|
439
|
+
# Default: use XmlGenerator
|
|
376
440
|
::Moxml::Adapter::CustomizedOga::XmlGenerator.new(node).to_xml
|
|
377
441
|
end
|
|
378
442
|
end
|
data/lib/moxml/adapter/ox.rb
CHANGED
|
@@ -348,6 +348,24 @@ module Moxml
|
|
|
348
348
|
end
|
|
349
349
|
|
|
350
350
|
def add_child(element, child)
|
|
351
|
+
# Special handling for declarations on Ox documents
|
|
352
|
+
if element.is_a?(::Ox::Document) && child.is_a?(::Ox::Instruct) && child.target == "xml"
|
|
353
|
+
# Transfer declaration attributes to document
|
|
354
|
+
element.attributes ||= {}
|
|
355
|
+
if child.attributes["version"]
|
|
356
|
+
element.attributes[:version] =
|
|
357
|
+
child.attributes["version"]
|
|
358
|
+
end
|
|
359
|
+
if child.attributes["encoding"]
|
|
360
|
+
element.attributes[:encoding] =
|
|
361
|
+
child.attributes["encoding"]
|
|
362
|
+
end
|
|
363
|
+
if child.attributes["standalone"]
|
|
364
|
+
element.attributes[:standalone] =
|
|
365
|
+
child.attributes["standalone"]
|
|
366
|
+
end
|
|
367
|
+
end
|
|
368
|
+
|
|
351
369
|
child.parent = element if child.respond_to?(:parent)
|
|
352
370
|
element.nodes ||= []
|
|
353
371
|
element.nodes << child
|
|
@@ -380,6 +398,15 @@ module Moxml
|
|
|
380
398
|
|
|
381
399
|
return unless parent(node)
|
|
382
400
|
|
|
401
|
+
# Special handling for declarations on Ox documents
|
|
402
|
+
if parent(node).is_a?(::Ox::Document) && node.is_a?(::Ox::Instruct) && node.target == "xml"
|
|
403
|
+
# Clear declaration attributes from document
|
|
404
|
+
doc = parent(node)
|
|
405
|
+
doc.attributes&.delete(:version)
|
|
406
|
+
doc.attributes&.delete(:encoding)
|
|
407
|
+
doc.attributes&.delete(:standalone)
|
|
408
|
+
end
|
|
409
|
+
|
|
383
410
|
parent(node).nodes.delete(unpatch_node(node))
|
|
384
411
|
end
|
|
385
412
|
|
|
@@ -524,13 +551,24 @@ module Moxml
|
|
|
524
551
|
def serialize(node, options = {})
|
|
525
552
|
output = ""
|
|
526
553
|
if node.is_a?(::Ox::Document)
|
|
527
|
-
#
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
554
|
+
# Check if we should include declaration
|
|
555
|
+
# Priority: explicit option > document attributes
|
|
556
|
+
should_include_decl = if options.key?(:no_declaration)
|
|
557
|
+
!options[:no_declaration]
|
|
558
|
+
else
|
|
559
|
+
# Check if document has declaration attributes
|
|
560
|
+
node[:version] || node[:encoding] || node[:standalone]
|
|
561
|
+
end
|
|
562
|
+
|
|
563
|
+
# Only add declaration if should_include_decl is true
|
|
564
|
+
if should_include_decl
|
|
565
|
+
version = node[:version] || "1.0"
|
|
566
|
+
encoding = options[:encoding] || node[:encoding]
|
|
567
|
+
standalone = node[:standalone]
|
|
568
|
+
|
|
569
|
+
decl = create_native_declaration(version, encoding, standalone)
|
|
570
|
+
output = ::Ox.dump(::Ox::Document.new << decl).strip
|
|
571
|
+
end
|
|
534
572
|
end
|
|
535
573
|
|
|
536
574
|
ox_options = {
|