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.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +22 -39
  3. data/docs/_config.yml +3 -3
  4. data/docs/_guides/index.adoc +6 -0
  5. data/docs/_guides/modifying-xml.adoc +0 -1
  6. data/docs/_guides/parsing-xml.adoc +0 -1
  7. data/docs/_guides/xml-declaration.adoc +450 -0
  8. data/docs/_pages/adapter-compatibility.adoc +1 -1
  9. data/docs/_pages/adapters/headed-ox.adoc +9 -9
  10. data/docs/_pages/adapters/index.adoc +0 -1
  11. data/docs/_pages/adapters/libxml.adoc +1 -2
  12. data/docs/_pages/adapters/nokogiri.adoc +1 -2
  13. data/docs/_pages/adapters/oga.adoc +1 -2
  14. data/docs/_pages/adapters/ox.adoc +2 -1
  15. data/docs/_pages/adapters/rexml.adoc +1 -2
  16. data/docs/_pages/best-practices.adoc +0 -1
  17. data/docs/_pages/compatibility.adoc +0 -1
  18. data/docs/_pages/configuration.adoc +0 -1
  19. data/docs/_pages/error-handling.adoc +0 -1
  20. data/docs/_pages/headed-ox-limitations.adoc +16 -0
  21. data/docs/_pages/installation.adoc +0 -1
  22. data/docs/_pages/node-api-reference.adoc +0 -1
  23. data/docs/_pages/performance.adoc +0 -1
  24. data/docs/_pages/quick-start.adoc +0 -1
  25. data/docs/_pages/thread-safety.adoc +0 -1
  26. data/docs/_references/document-api.adoc +0 -1
  27. data/docs/_tutorials/basic-usage.adoc +0 -1
  28. data/docs/_tutorials/builder-pattern.adoc +0 -1
  29. data/docs/_tutorials/namespace-handling.adoc +0 -1
  30. data/docs/_tutorials/xpath-queries.adoc +0 -1
  31. data/lib/moxml/adapter/customized_rexml/formatter.rb +2 -2
  32. data/lib/moxml/adapter/libxml.rb +19 -3
  33. data/lib/moxml/adapter/nokogiri.rb +37 -2
  34. data/lib/moxml/adapter/oga.rb +67 -3
  35. data/lib/moxml/adapter/ox.rb +45 -7
  36. data/lib/moxml/adapter/rexml.rb +32 -10
  37. data/lib/moxml/context.rb +18 -1
  38. data/lib/moxml/declaration.rb +9 -0
  39. data/lib/moxml/document.rb +14 -0
  40. data/lib/moxml/document_builder.rb +7 -0
  41. data/lib/moxml/node.rb +61 -1
  42. data/lib/moxml/version.rb +1 -1
  43. data/lib/moxml/xpath/compiler.rb +2 -0
  44. data/spec/integration/shared_examples/node_wrappers/declaration_behavior.rb +0 -3
  45. data/spec/moxml/declaration_preservation_spec.rb +217 -0
  46. data/spec/performance/memory_usage_spec.rb +3 -2
  47. metadata +3 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2f1290755cf60610b63b7c82a4d043e662627a0c1b7a5692a70e987ddae69fa6
4
- data.tar.gz: 05c9430233444a7915cb36789af8ebad99bc34391324c010a8e9c07f1cd36d33
3
+ metadata.gz: 4b2faed71c75f32fcfe528621b1d3ed01c603b04f81e75d5b45b6c525d9d1836
4
+ data.tar.gz: 68ead788c2e5098b2f1f88653443fe4d8ff1e76b17dab09c08badb64c7ab70a3
5
5
  SHA512:
6
- metadata.gz: ca2bf23c9b78a09dc984a974b86eadabddca8e4f90dbcdda263ff2295a61a4a6c1252fdeb5ad7ace732533887abef1d8636f5e1c6556f720485ed61d8e93da32
7
- data.tar.gz: 508e707c528f5a729dea7de4a5940a6df8f70a3bd537295ef510a1280be322833b06bdc3b4242f869dae11c7cda09b07b84a9e13ead7f5810fb90f6f565e872c
6
+ metadata.gz: 8aa59a486d44d101e1c078fc6fde2eea1cad9d4d7690d937dc7faef233a4143206432ffab53da3a2ef01af9245d17f706ddb01bc55b2c3555b358a6a3df10143
7
+ data.tar.gz: b5256089daef1f94012046bd7ff0fcbff1827c615b46d416231933de3193f3a262063a6ad761a87c629cdfe15fc51644fc1887d43eaeff5f8710bd16a791ea3c
data/.rubocop_todo.yml CHANGED
@@ -1,23 +1,24 @@
1
1
  # This configuration was generated by
2
2
  # `rubocop --auto-gen-config`
3
- # on 2025-11-24 02:45:12 UTC using RuboCop version 1.80.0.
3
+ # on 2025-11-24 13:45:20 UTC using RuboCop version 1.80.0.
4
4
  # The point is for the user to remove these configuration records
5
5
  # one by one as the offenses are removed from the code base.
6
6
  # Note that changes in the inspected code, or installation of new
7
7
  # versions of RuboCop, may require this file to be generated again.
8
8
 
9
- # Offense count: 179
9
+ # Offense count: 192
10
10
  # This cop supports safe autocorrection (--autocorrect).
11
11
  # Configuration parameters: Max, AllowHeredoc, AllowURI, AllowQualifiedName, URISchemes, IgnoreCopDirectives, AllowedPatterns, SplitStrings.
12
12
  # URISchemes: http, https
13
13
  Layout/LineLength:
14
14
  Enabled: false
15
15
 
16
- # Offense count: 6
16
+ # Offense count: 7
17
17
  # Configuration parameters: AllowedMethods.
18
18
  # AllowedMethods: enums
19
19
  Lint/ConstantDefinitionInBlock:
20
20
  Exclude:
21
+ - 'spec/moxml/declaration_preservation_spec.rb'
21
22
  - 'spec/moxml/sax_spec.rb'
22
23
 
23
24
  # Offense count: 6
@@ -59,7 +60,7 @@ Lint/NoReturnInBeginEndBlocks:
59
60
  Exclude:
60
61
  - 'examples/api_client/api_client.rb'
61
62
 
62
- # Offense count: 86
63
+ # Offense count: 93
63
64
  # Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes, Max.
64
65
  Metrics/AbcSize:
65
66
  Enabled: false
@@ -75,12 +76,12 @@ Metrics/BlockLength:
75
76
  Metrics/BlockNesting:
76
77
  Max: 4
77
78
 
78
- # Offense count: 57
79
+ # Offense count: 63
79
80
  # Configuration parameters: AllowedMethods, AllowedPatterns, Max.
80
81
  Metrics/CyclomaticComplexity:
81
82
  Enabled: false
82
83
 
83
- # Offense count: 157
84
+ # Offense count: 164
84
85
  # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
85
86
  Metrics/MethodLength:
86
87
  Max: 110
@@ -90,23 +91,10 @@ Metrics/MethodLength:
90
91
  Metrics/ParameterLists:
91
92
  Max: 7
92
93
 
93
- # Offense count: 36
94
+ # Offense count: 42
94
95
  # Configuration parameters: AllowedMethods, AllowedPatterns, Max.
95
96
  Metrics/PerceivedComplexity:
96
- Exclude:
97
- - 'benchmarks/generate_report.rb'
98
- - 'examples/web_scraper/web_scraper.rb'
99
- - 'lib/moxml/adapter/customized_oga/xml_generator.rb'
100
- - 'lib/moxml/adapter/customized_rexml/formatter.rb'
101
- - 'lib/moxml/adapter/libxml.rb'
102
- - 'lib/moxml/adapter/ox.rb'
103
- - 'lib/moxml/adapter/rexml.rb'
104
- - 'lib/moxml/document.rb'
105
- - 'lib/moxml/xpath/compiler.rb'
106
- - 'lib/moxml/xpath/conversion.rb'
107
- - 'lib/moxml/xpath/lexer.rb'
108
- - 'lib/moxml/xpath/parser.rb'
109
- - 'lib/moxml/xpath/ruby/generator.rb'
97
+ Enabled: false
110
98
 
111
99
  # Offense count: 15
112
100
  # Configuration parameters: MinNameLength, AllowNamesEndingInNumbers, AllowedNames, ForbiddenNames.
@@ -130,14 +118,7 @@ Naming/PredicateMethod:
130
118
  Exclude:
131
119
  - 'lib/moxml/xpath/ruby/node.rb'
132
120
 
133
- # Offense count: 1
134
- # This cop supports unsafe autocorrection (--autocorrect-all).
135
- # Configuration parameters: OnlySumOrWithInitialValue.
136
- Performance/Sum:
137
- Exclude:
138
- - 'lib/moxml/xpath/compiler.rb'
139
-
140
- # Offense count: 42
121
+ # Offense count: 46
141
122
  # Configuration parameters: Prefixes, AllowedPatterns.
142
123
  # Prefixes: when, with, without
143
124
  RSpec/ContextWording:
@@ -145,11 +126,12 @@ RSpec/ContextWording:
145
126
  - 'spec/integration/headed_ox_integration_spec.rb'
146
127
  - 'spec/moxml/adapter/headed_ox_spec.rb'
147
128
  - 'spec/moxml/adapter/shared_examples/adapter_contract.rb'
129
+ - 'spec/moxml/declaration_preservation_spec.rb'
148
130
  - 'spec/moxml/xpath/lexer_spec.rb'
149
131
  - 'spec/moxml/xpath/parser_spec.rb'
150
132
  - 'spec/performance/benchmark_spec.rb'
151
133
 
152
- # Offense count: 14
134
+ # Offense count: 15
153
135
  # Configuration parameters: IgnoredMetadata.
154
136
  RSpec/DescribeClass:
155
137
  Exclude:
@@ -161,6 +143,7 @@ RSpec/DescribeClass:
161
143
  - 'spec/consistency/adapter_parity_spec.rb'
162
144
  - 'spec/integration/all_adapters_spec.rb'
163
145
  - 'spec/integration/headed_ox_integration_spec.rb'
146
+ - 'spec/moxml/declaration_preservation_spec.rb'
164
147
  - 'spec/moxml/error_spec.rb'
165
148
  - 'spec/moxml/xpath/axes_spec.rb'
166
149
  - 'spec/moxml/xpath/functions/boolean_functions_spec.rb'
@@ -173,7 +156,7 @@ RSpec/DescribeClass:
173
156
  - 'spec/moxml/xpath_capabilities_spec.rb'
174
157
  - 'spec/performance/xpath_benchmark_spec.rb'
175
158
 
176
- # Offense count: 215
159
+ # Offense count: 217
177
160
  # Configuration parameters: CountAsOne.
178
161
  RSpec/ExampleLength:
179
162
  Max: 85
@@ -197,9 +180,10 @@ RSpec/InstanceVariable:
197
180
  Exclude:
198
181
  - 'spec/moxml/sax_spec.rb'
199
182
 
200
- # Offense count: 6
183
+ # Offense count: 7
201
184
  RSpec/LeakyConstantDeclaration:
202
185
  Exclude:
186
+ - 'spec/moxml/declaration_preservation_spec.rb'
203
187
  - 'spec/moxml/sax_spec.rb'
204
188
 
205
189
  # Offense count: 2
@@ -208,7 +192,7 @@ RSpec/LeakyConstantDeclaration:
208
192
  RSpec/MessageSpies:
209
193
  EnforcedStyle: receive
210
194
 
211
- # Offense count: 308
195
+ # Offense count: 317
212
196
  RSpec/MultipleExpectations:
213
197
  Max: 10
214
198
 
@@ -217,6 +201,11 @@ RSpec/MultipleExpectations:
217
201
  RSpec/MultipleMemoizedHelpers:
218
202
  Max: 7
219
203
 
204
+ # Offense count: 10
205
+ # Configuration parameters: AllowedGroups.
206
+ RSpec/NestedGroups:
207
+ Max: 4
208
+
220
209
  # Offense count: 3
221
210
  # Configuration parameters: AllowedPatterns.
222
211
  # AllowedPatterns: ^expect_, ^assert_
@@ -262,12 +251,6 @@ Security/Eval:
262
251
  Exclude:
263
252
  - 'spec/moxml/xpath/ruby/generator_spec.rb'
264
253
 
265
- # Offense count: 1
266
- # This cop supports unsafe autocorrection (--autocorrect-all).
267
- Style/CombinableLoops:
268
- Exclude:
269
- - 'lib/moxml/adapter/customized_rexml/formatter.rb'
270
-
271
254
  # Offense count: 1
272
255
  Style/DocumentDynamicEvalDefinition:
273
256
  Exclude:
data/docs/_config.yml CHANGED
@@ -1,9 +1,9 @@
1
1
  # Site settings
2
- title: Moxml
2
+ title: "Moxml: Modern XML for Ruby"
3
3
  description: >-
4
4
  Modern XML processing for Ruby with unified adapter interface
5
5
  baseurl: "/moxml"
6
- url: "https://lutaml.github.io"
6
+ url: "https://www.lutaml.org"
7
7
 
8
8
  # Theme
9
9
  theme: just-the-docs
@@ -43,7 +43,7 @@ back_to_top_text: "Back to top"
43
43
  # Footer content
44
44
  footer_content: >-
45
45
  Copyright © 2025 Ribose. Distributed by a
46
- <a href="https://github.com/lutaml/moxml/blob/main/LICENSE.md">BSD-2-Clause license</a>.
46
+ <a href="https://github.com/lutaml/moxml/blob/main/LICENSE.md">BSD-3-Clause license</a>.
47
47
 
48
48
  # Footer last edit timestamp
49
49
  last_edit_timestamp: true
@@ -12,6 +12,12 @@ Task-oriented guides for common XML processing operations with Moxml.
12
12
  link:parsing-xml[Parsing XML]::
13
13
  Parse XML from strings, files, and IO streams with different adapters.
14
14
 
15
+ link:sax-parsing[SAX parsing]::
16
+ Memory-efficient event-driven parsing for large XML files and streaming data.
17
+
18
+ link:xml-declaration[XML declaration preservation]::
19
+ Understand how Moxml preserves XML declarations for round-trip fidelity and standards compliance.
20
+
15
21
  link:creating-documents[Creating documents]::
16
22
  Build XML documents from scratch using the builder pattern or direct
17
23
  manipulation.
@@ -1,6 +1,5 @@
1
1
  ---
2
2
  title: Modifying XML
3
- parent: Overview
4
3
  nav_order: 3
5
4
  ---
6
5
 
@@ -1,6 +1,5 @@
1
1
  ---
2
2
  title: Parsing XML
3
- parent: Overview
4
3
  nav_order: 2
5
4
  ---
6
5
 
@@ -0,0 +1,450 @@
1
+ = XML Declaration Preservation
2
+ :toc:
3
+ :toclevels: 3
4
+
5
+ == Overview
6
+
7
+ Moxml automatically preserves the presence or absence of XML declarations (`<?xml version="1.0"?>`) when parsing and serializing documents. This ensures round-trip fidelity and compliance with standards that require specific declaration handling.
8
+
9
+ === Why This Matters
10
+
11
+ Some XML use cases require specific declaration handling:
12
+
13
+ * **SVG Files**: Often have no XML declaration
14
+ * **XML Fragments**: Should not have declarations
15
+ * **Standards Compliance**: Some specs prohibit declarations in certain contexts
16
+ * **Round-Trip Fidelity**: Parse → Modify → Serialize should preserve format
17
+
18
+ === Key Features
19
+
20
+ * **Automatic Detection**: Moxml detects whether input had a declaration
21
+ * **Automatic Preservation**: Output matches input format by default
22
+ * **Explicit Override**: Force add or remove declarations when needed
23
+ * **All Adapters**: Works across all 6 XML adapters
24
+
25
+ == Basic Usage
26
+
27
+ === Automatic Preservation
28
+
29
+ Moxml automatically preserves whether input had an XML declaration:
30
+
31
+ [source,ruby]
32
+ ----
33
+ require 'moxml'
34
+
35
+ # Document without declaration
36
+ svg = '<svg xmlns="http://www.w3.org/2000/svg"><rect/></svg>'
37
+ doc = Moxml.new.parse(svg)
38
+ doc.to_xml
39
+ # => "<svg xmlns=\"http://www.w3.org/2000/svg\"><rect/></svg>"
40
+ # ✓ No <?xml...?> added
41
+
42
+ # Document with declaration
43
+ xml = '<?xml version="1.0" encoding="UTF-8"?><root><child/></root>'
44
+ doc = Moxml.new.parse(xml)
45
+ doc.to_xml
46
+ # => "<?xml version=\"1.0\" encoding=\"UTF-8\"?><root><child/></root>"
47
+ # ✓ Declaration preserved
48
+ ----
49
+
50
+ === Checking Declaration Presence
51
+
52
+ Use the `has_xml_declaration` attribute to check if a document has a declaration:
53
+
54
+ [source,ruby]
55
+ ----
56
+ # Document without declaration
57
+ doc = Moxml.new.parse('<root/>')
58
+ doc.has_xml_declaration # => false
59
+
60
+ # Document with declaration
61
+ doc = Moxml.new.parse('<?xml version="1.0"?><root/>')
62
+ doc.has_xml_declaration # => true
63
+ ----
64
+
65
+ == Explicit Control
66
+
67
+ === Forcing Declaration Addition
68
+
69
+ Add a declaration to documents that don't have one:
70
+
71
+ [source,ruby]
72
+ ----
73
+ svg = '<svg><rect/></svg>'
74
+ doc = Moxml.new.parse(svg)
75
+
76
+ # Force add declaration
77
+ output = doc.to_xml(declaration: true)
78
+ # => "<?xml version=\"1.0\" encoding=\"UTF-8\"?><svg><rect/></svg>"
79
+ ----
80
+
81
+ === Removing Declarations
82
+
83
+ Remove declaration from documents that have one:
84
+
85
+ [source,ruby]
86
+ ----
87
+ xml = '<?xml version="1.0"?><root><item/></root>'
88
+ doc = Moxml.new.parse(xml)
89
+
90
+ # Force remove declaration
91
+ output = doc.to_xml(declaration: false)
92
+ # => "<root><item/></root>"
93
+ ----
94
+
95
+ == Use Cases
96
+
97
+ === SVG File Processing
98
+
99
+ SVG files often don't have XML declarations. Moxml preserves this:
100
+
101
+ [source,ruby]
102
+ ----
103
+ # Original SVG without declaration
104
+ svg_content = File.read('image.svg')
105
+ doc = Moxml.new.parse(svg_content)
106
+
107
+ # Modify SVG (add viewBox)
108
+ doc.root['viewBox'] = '0 0 100 100'
109
+
110
+ # Save - no declaration added
111
+ File.write('image.svg', doc.to_xml)
112
+ ----
113
+
114
+ === XML Fragment Generation
115
+
116
+ Create XML fragments without declarations:
117
+
118
+ [source,ruby]
119
+ ----
120
+ context = Moxml.new
121
+ doc = context.create_document
122
+
123
+ # Build fragment
124
+ root = doc.create_element('fragment')
125
+ root << doc.create_element('item')
126
+ doc.root = root
127
+
128
+ # Serialize without declaration (default for built documents)
129
+ doc.to_xml # => "<fragment><item/></fragment>"
130
+ ----
131
+
132
+ === Standards-Compliant XML
133
+
134
+ Some XML standards require or prohibit declarations:
135
+
136
+ [source,ruby]
137
+ ----
138
+ # Standard prohibits declarations
139
+ doc = Moxml.new.parse(compliant_xml_without_decl)
140
+ output = doc.to_xml # Declaration correctly absent
141
+
142
+ # Standard requires declarations
143
+ doc = Moxml.new.parse(standard_xml_with_decl)
144
+ output = doc.to_xml # Declaration correctly present
145
+ ----
146
+
147
+ === Round-Trip Processing
148
+
149
+ Preserve original format through multiple parse/serialize cycles:
150
+
151
+ [source,ruby]
152
+ ----
153
+ original = '<data><item id="1"/></data>'
154
+
155
+ # First round-trip
156
+ doc1 = Moxml.new.parse(original)
157
+ intermediate = doc1.to_xml
158
+
159
+ # Second round-trip
160
+ doc2 = Moxml.new.parse(intermediate)
161
+ final = doc2.to_xml
162
+
163
+ # All three are identical
164
+ original == intermediate && intermediate == final # => true
165
+ ----
166
+
167
+ == Adapter Behavior
168
+
169
+ All 6 adapters support declaration preservation:
170
+
171
+ [cols="1,3"]
172
+ |===
173
+ |Adapter |Implementation
174
+
175
+ |Nokogiri
176
+ |Uses `SaveOptions::NO_DECLARATION` flag
177
+
178
+ |Oga
179
+ |Custom serialization logic
180
+
181
+ |REXML
182
+ |Conditional declaration output
183
+
184
+ |Ox
185
+ |Declaration control in serialize
186
+
187
+ |LibXML
188
+ |Custom serializer respects flag
189
+
190
+ |HeadedOx
191
+ |Inherits Ox implementation
192
+ |===
193
+
194
+ == Advanced Usage
195
+
196
+ === Programmatically Built Documents
197
+
198
+ Documents built from scratch default to no declaration:
199
+
200
+ [source,ruby]
201
+ ----
202
+ doc = Moxml.new.create_document
203
+ root = doc.create_element('config')
204
+ doc.root = root
205
+
206
+ doc.has_xml_declaration # => false
207
+ doc.to_xml # => "<config/>"
208
+
209
+ # Explicitly add declaration if needed
210
+ doc.to_xml(declaration: true)
211
+ # => "<?xml version=\"1.0\" encoding=\"UTF-8\"?><config/>"
212
+ ----
213
+
214
+ === Element Serialization
215
+
216
+ Only document nodes can have declarations. Element serialization never includes declarations:
217
+
218
+ [source,ruby]
219
+ ----
220
+ doc = Moxml.new.parse('<?xml version="1.0"?><root><child/></root>')
221
+ element = doc.root
222
+
223
+ # Element serialization - no declaration
224
+ element.to_xml # => "<root><child/></root>"
225
+
226
+ # Even with explicit request (ignored for elements)
227
+ element.to_xml(declaration: true) # => "<root><child/></root>"
228
+ ----
229
+
230
+ === Custom Declaration Attributes
231
+
232
+ When forcing declaration addition, use standard attributes:
233
+
234
+ [source,ruby]
235
+ ----
236
+ doc = Moxml.new.parse('<root/>')
237
+
238
+ # Default declaration
239
+ doc.to_xml(declaration: true)
240
+ # => "<?xml version=\"1.0\" encoding=\"UTF-8\"?><root/>"
241
+
242
+ # Custom encoding via adapter
243
+ doc.to_xml(declaration: true, encoding: "ISO-8859-1")
244
+ # => "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?><root/>"
245
+ ----
246
+
247
+ == Best Practices
248
+
249
+ === Let Moxml Handle It
250
+
251
+ In most cases, rely on automatic preservation:
252
+
253
+ [source,ruby]
254
+ ----
255
+ # Good - automatic preservation
256
+ doc = Moxml.new.parse(xml_content)
257
+ modified = process_document(doc)
258
+ output = doc.to_xml # Declaration preserved automatically
259
+
260
+ # Only use explicit control when required by specific needs
261
+ output = doc.to_xml(declaration: false) # Explicit requirement
262
+ ----
263
+
264
+ === Check Declaration Before Processing
265
+
266
+ Know what you're working with:
267
+
268
+ [source,ruby]
269
+ ----
270
+ doc = Moxml.new.parse(xml_source)
271
+
272
+ if doc.has_xml_declaration
273
+ # Handle documents with declarations
274
+ process_with_declaration(doc)
275
+ else
276
+ # Handle fragments without declarations
277
+ process_fragment(doc)
278
+ end
279
+ ----
280
+
281
+ === Document Your Requirements
282
+
283
+ Make declaration requirements explicit in code:
284
+
285
+ [source,ruby]
286
+ ----
287
+ def save_svg(doc)
288
+ # SVG files should not have XML declarations
289
+ raise "SVG has declaration" if doc.has_xml_declaration
290
+ File.write('output.svg', doc.to_xml)
291
+ end
292
+
293
+ def save_xml_config(doc)
294
+ # Config files require declarations
295
+ File.write('config.xml', doc.to_xml(declaration: true))
296
+ end
297
+ ----
298
+
299
+ == Migration from Previous Versions
300
+
301
+ === Behavior Change
302
+
303
+ In Moxml versions before 0.2.1, serialization *always* added an XML declaration. Starting with 0.2.1, behavior changed to preserve input format:
304
+
305
+ [cols="1,2,2"]
306
+ |===
307
+ |Scenario |Before v0.2.1 |v0.2.1+
308
+
309
+ |Parse without declaration
310
+ |Added declaration ❌
311
+ |No declaration ✓
312
+
313
+ |Parse with declaration
314
+ |Preserved declaration ✓
315
+ |Preserved declaration ✓
316
+
317
+ |Built document
318
+ |Added declaration
319
+ |No declaration (can override)
320
+ |===
321
+
322
+ === Update Your Code
323
+
324
+ If you relied on automatic declaration addition:
325
+
326
+ [source,ruby]
327
+ ----
328
+ # Before (relied on automatic declaration)
329
+ doc = Moxml.new.parse('<root/>')
330
+ output = doc.to_xml # Had declaration
331
+
332
+ # After (explicitly request if needed)
333
+ doc = Moxml.new.parse('<root/>')
334
+ output = doc.to_xml(declaration: true) # Force add
335
+ ----
336
+
337
+ === Minimal Impact
338
+
339
+ Most code will see **no change** because:
340
+
341
+ * Documents with declarations still preserve them
342
+ * Only fragments without declarations behave differently
343
+ * New behavior is arguably more correct
344
+
345
+ == Troubleshooting
346
+
347
+ === Declaration Not Preserved
348
+
349
+ If declaration isn't being preserved, check:
350
+
351
+ 1. **Input Format**: Verify input actually has `<?xml...?>`
352
+ +
353
+ [source,ruby]
354
+ ----
355
+ xml_content = File.read('file.xml')
356
+ puts "Has declaration: #{xml_content.strip.start_with?('<?xml')}"
357
+ ----
358
+
359
+ 2. **Explicit Override**: Check if code explicitly removes it
360
+ +
361
+ [source,ruby]
362
+ ----
363
+ # This will remove declaration regardless of input
364
+ doc.to_xml(declaration: false)
365
+ ----
366
+
367
+ 3. **Element vs Document**: Only documents can have declarations
368
+ +
369
+ [source,ruby]
370
+ ----
371
+ element = doc.root
372
+ element.to_xml # Never has declaration (correct)
373
+ ----
374
+
375
+ === Unwanted Declaration
376
+
377
+ If declaration is added when you don't want it:
378
+
379
+ [source,ruby]
380
+ ----
381
+ # Solution 1: Parse input without declaration
382
+ svg = '<svg><rect/></svg>' # No <?xml...?>
383
+ doc = Moxml.new.parse(svg)
384
+ doc.to_xml # No declaration
385
+
386
+ # Solution 2: Explicitly remove
387
+ doc.to_xml(declaration: false)
388
+
389
+ # Solution 3: Check and fix source
390
+ if doc.has_xml_declaration
391
+ # Input has declaration - remove from source or override
392
+ output = doc.to_xml(declaration: false)
393
+ end
394
+ ----
395
+
396
+ === Whitespace Before Declaration
397
+
398
+ XML declarations must be at the document start. Whitespace before the declaration makes it invalid:
399
+
400
+ [source,ruby]
401
+ ----
402
+ # Invalid - whitespace before declaration
403
+ invalid = ' <?xml version="1.0"?><root/>'
404
+ doc = Moxml.new.parse(invalid) # May raise error depending on adapter
405
+
406
+ # Valid - declaration at start
407
+ valid = '<?xml version="1.0"?><root/>'
408
+ doc = Moxml.new.parse(valid) # Works correctly
409
+ ----
410
+
411
+ == API Reference
412
+
413
+ === Document Attributes
414
+
415
+ ==== has_xml_declaration
416
+
417
+ [source,ruby]
418
+ ----
419
+ doc.has_xml_declaration # => Boolean
420
+ ----
421
+
422
+ Returns `true` if the document was parsed from XML that contained an XML declaration, `false` otherwise.
423
+
424
+ * Read/write attribute (can be manually set)
425
+ * Defaults to `false` for programmatically built documents
426
+ * Automatically set during parsing
427
+
428
+ === Serialization Options
429
+
430
+ ==== declaration
431
+
432
+ [source,ruby]
433
+ ----
434
+ doc.to_xml(declaration: true) # Force include declaration
435
+ doc.to_xml(declaration: false) # Force exclude declaration
436
+ doc.to_xml # Use automatic preservation
437
+ ----
438
+
439
+ Controls whether XML declaration is included in serialized output:
440
+
441
+ * `true`: Always include declaration
442
+ * `false`: Never include declaration
443
+ * Not specified: Use `has_xml_declaration` value (automatic preservation)
444
+
445
+ == See Also
446
+
447
+ * link:parsing-xml.html[Parsing XML] - How to parse XML documents
448
+ * link:modifying-xml.html[Modifying XML] - Working with parsed documents
449
+ * link:../adapters/index.html[Adapters] - Adapter-specific behavior
450
+ * link:../best-practices.html[Best Practices] - General XML processing guidelines
@@ -1,4 +1,4 @@
1
- = Moxml adapter compatibility matrix
1
+ = Adapter compatibility matrix
2
2
  :toc:
3
3
  :toc-placement!:
4
4