moxml 0.1.7 → 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 (215) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/dependent-repos.json +5 -0
  3. data/.github/workflows/dependent-tests.yml +20 -0
  4. data/.github/workflows/docs.yml +59 -0
  5. data/.github/workflows/rake.yml +10 -10
  6. data/.github/workflows/release.yml +5 -3
  7. data/.gitignore +37 -0
  8. data/.rubocop.yml +15 -7
  9. data/.rubocop_todo.yml +224 -43
  10. data/Gemfile +14 -9
  11. data/LICENSE.md +6 -2
  12. data/README.adoc +535 -373
  13. data/Rakefile +53 -0
  14. data/benchmarks/.gitignore +6 -0
  15. data/benchmarks/generate_report.rb +550 -0
  16. data/docs/Gemfile +13 -0
  17. data/docs/_config.yml +138 -0
  18. data/docs/_guides/advanced-features.adoc +87 -0
  19. data/docs/_guides/development-testing.adoc +165 -0
  20. data/docs/_guides/index.adoc +51 -0
  21. data/docs/_guides/modifying-xml.adoc +292 -0
  22. data/docs/_guides/parsing-xml.adoc +230 -0
  23. data/docs/_guides/sax-parsing.adoc +603 -0
  24. data/docs/_guides/working-with-documents.adoc +118 -0
  25. data/docs/_guides/xml-declaration.adoc +450 -0
  26. data/docs/_pages/adapter-compatibility.adoc +369 -0
  27. data/docs/_pages/adapters/headed-ox.adoc +237 -0
  28. data/docs/_pages/adapters/index.adoc +97 -0
  29. data/docs/_pages/adapters/libxml.adoc +285 -0
  30. data/docs/_pages/adapters/nokogiri.adoc +251 -0
  31. data/docs/_pages/adapters/oga.adoc +291 -0
  32. data/docs/_pages/adapters/ox.adoc +56 -0
  33. data/docs/_pages/adapters/rexml.adoc +292 -0
  34. data/docs/_pages/best-practices.adoc +429 -0
  35. data/docs/_pages/compatibility.adoc +467 -0
  36. data/docs/_pages/configuration.adoc +250 -0
  37. data/docs/_pages/error-handling.adoc +349 -0
  38. data/docs/_pages/headed-ox-limitations.adoc +574 -0
  39. data/docs/_pages/headed-ox.adoc +1025 -0
  40. data/docs/_pages/index.adoc +35 -0
  41. data/docs/_pages/installation.adoc +140 -0
  42. data/docs/_pages/node-api-reference.adoc +49 -0
  43. data/docs/_pages/performance.adoc +35 -0
  44. data/docs/_pages/quick-start.adoc +243 -0
  45. data/docs/_pages/thread-safety.adoc +28 -0
  46. data/docs/_references/document-api.adoc +407 -0
  47. data/docs/_references/index.adoc +48 -0
  48. data/docs/_tutorials/basic-usage.adoc +267 -0
  49. data/docs/_tutorials/builder-pattern.adoc +342 -0
  50. data/docs/_tutorials/index.adoc +33 -0
  51. data/docs/_tutorials/namespace-handling.adoc +324 -0
  52. data/docs/_tutorials/xpath-queries.adoc +358 -0
  53. data/docs/index.adoc +122 -0
  54. data/examples/README.md +124 -0
  55. data/examples/api_client/README.md +424 -0
  56. data/examples/api_client/api_client.rb +394 -0
  57. data/examples/api_client/example_response.xml +48 -0
  58. data/examples/headed_ox_example/README.md +90 -0
  59. data/examples/headed_ox_example/headed_ox_demo.rb +71 -0
  60. data/examples/rss_parser/README.md +194 -0
  61. data/examples/rss_parser/example_feed.xml +93 -0
  62. data/examples/rss_parser/rss_parser.rb +189 -0
  63. data/examples/sax_parsing/README.md +50 -0
  64. data/examples/sax_parsing/data_extractor.rb +75 -0
  65. data/examples/sax_parsing/example.xml +21 -0
  66. data/examples/sax_parsing/large_file.rb +78 -0
  67. data/examples/sax_parsing/simple_parser.rb +55 -0
  68. data/examples/web_scraper/README.md +352 -0
  69. data/examples/web_scraper/example_page.html +201 -0
  70. data/examples/web_scraper/web_scraper.rb +312 -0
  71. data/lib/moxml/adapter/base.rb +107 -28
  72. data/lib/moxml/adapter/customized_libxml/cdata.rb +28 -0
  73. data/lib/moxml/adapter/customized_libxml/comment.rb +24 -0
  74. data/lib/moxml/adapter/customized_libxml/declaration.rb +85 -0
  75. data/lib/moxml/adapter/customized_libxml/element.rb +39 -0
  76. data/lib/moxml/adapter/customized_libxml/node.rb +44 -0
  77. data/lib/moxml/adapter/customized_libxml/processing_instruction.rb +31 -0
  78. data/lib/moxml/adapter/customized_libxml/text.rb +27 -0
  79. data/lib/moxml/adapter/customized_oga/xml_generator.rb +1 -1
  80. data/lib/moxml/adapter/customized_ox/attribute.rb +28 -1
  81. data/lib/moxml/adapter/customized_rexml/formatter.rb +13 -8
  82. data/lib/moxml/adapter/headed_ox.rb +161 -0
  83. data/lib/moxml/adapter/libxml.rb +1564 -0
  84. data/lib/moxml/adapter/nokogiri.rb +156 -9
  85. data/lib/moxml/adapter/oga.rb +190 -15
  86. data/lib/moxml/adapter/ox.rb +322 -28
  87. data/lib/moxml/adapter/rexml.rb +157 -28
  88. data/lib/moxml/adapter.rb +21 -4
  89. data/lib/moxml/attribute.rb +6 -0
  90. data/lib/moxml/builder.rb +40 -4
  91. data/lib/moxml/config.rb +8 -3
  92. data/lib/moxml/context.rb +57 -2
  93. data/lib/moxml/declaration.rb +9 -0
  94. data/lib/moxml/doctype.rb +13 -1
  95. data/lib/moxml/document.rb +53 -6
  96. data/lib/moxml/document_builder.rb +34 -5
  97. data/lib/moxml/element.rb +71 -2
  98. data/lib/moxml/error.rb +175 -6
  99. data/lib/moxml/node.rb +155 -4
  100. data/lib/moxml/node_set.rb +34 -0
  101. data/lib/moxml/sax/block_handler.rb +194 -0
  102. data/lib/moxml/sax/element_handler.rb +124 -0
  103. data/lib/moxml/sax/handler.rb +113 -0
  104. data/lib/moxml/sax.rb +31 -0
  105. data/lib/moxml/version.rb +1 -1
  106. data/lib/moxml/xml_utils/encoder.rb +4 -4
  107. data/lib/moxml/xml_utils.rb +7 -4
  108. data/lib/moxml/xpath/ast/node.rb +159 -0
  109. data/lib/moxml/xpath/cache.rb +91 -0
  110. data/lib/moxml/xpath/compiler.rb +1770 -0
  111. data/lib/moxml/xpath/context.rb +26 -0
  112. data/lib/moxml/xpath/conversion.rb +124 -0
  113. data/lib/moxml/xpath/engine.rb +52 -0
  114. data/lib/moxml/xpath/errors.rb +101 -0
  115. data/lib/moxml/xpath/lexer.rb +304 -0
  116. data/lib/moxml/xpath/parser.rb +485 -0
  117. data/lib/moxml/xpath/ruby/generator.rb +269 -0
  118. data/lib/moxml/xpath/ruby/node.rb +193 -0
  119. data/lib/moxml/xpath.rb +37 -0
  120. data/lib/moxml.rb +5 -2
  121. data/moxml.gemspec +3 -1
  122. data/old-specs/moxml/adapter/customized_libxml/.gitkeep +6 -0
  123. data/spec/consistency/README.md +77 -0
  124. data/spec/{moxml/examples/adapter_spec.rb → consistency/adapter_parity_spec.rb} +4 -4
  125. data/spec/examples/README.md +75 -0
  126. data/spec/{support/shared_examples/examples/attribute.rb → examples/attribute_examples_spec.rb} +1 -1
  127. data/spec/{support/shared_examples/examples/basic_usage.rb → examples/basic_usage_spec.rb} +2 -2
  128. data/spec/{support/shared_examples/examples/namespace.rb → examples/namespace_examples_spec.rb} +3 -3
  129. data/spec/{support/shared_examples/examples/readme_examples.rb → examples/readme_examples_spec.rb} +6 -4
  130. data/spec/{support/shared_examples/examples/xpath.rb → examples/xpath_examples_spec.rb} +10 -6
  131. data/spec/integration/README.md +71 -0
  132. data/spec/{moxml/all_with_adapters_spec.rb → integration/all_adapters_spec.rb} +3 -2
  133. data/spec/integration/headed_ox_integration_spec.rb +326 -0
  134. data/spec/{support → integration}/shared_examples/edge_cases.rb +37 -10
  135. data/spec/integration/shared_examples/high_level/.gitkeep +0 -0
  136. data/spec/{support/shared_examples/context.rb → integration/shared_examples/high_level/context_behavior.rb} +2 -1
  137. data/spec/{support/shared_examples/integration.rb → integration/shared_examples/integration_workflows.rb} +23 -6
  138. data/spec/integration/shared_examples/node_wrappers/.gitkeep +0 -0
  139. data/spec/{support/shared_examples/cdata.rb → integration/shared_examples/node_wrappers/cdata_behavior.rb} +6 -1
  140. data/spec/{support/shared_examples/comment.rb → integration/shared_examples/node_wrappers/comment_behavior.rb} +2 -1
  141. data/spec/{support/shared_examples/declaration.rb → integration/shared_examples/node_wrappers/declaration_behavior.rb} +5 -5
  142. data/spec/{support/shared_examples/doctype.rb → integration/shared_examples/node_wrappers/doctype_behavior.rb} +2 -2
  143. data/spec/{support/shared_examples/document.rb → integration/shared_examples/node_wrappers/document_behavior.rb} +1 -1
  144. data/spec/{support/shared_examples/node.rb → integration/shared_examples/node_wrappers/node_behavior.rb} +9 -2
  145. data/spec/{support/shared_examples/node_set.rb → integration/shared_examples/node_wrappers/node_set_behavior.rb} +1 -18
  146. data/spec/{support/shared_examples/processing_instruction.rb → integration/shared_examples/node_wrappers/processing_instruction_behavior.rb} +6 -2
  147. data/spec/moxml/README.md +41 -0
  148. data/spec/moxml/adapter/.gitkeep +0 -0
  149. data/spec/moxml/adapter/README.md +61 -0
  150. data/spec/moxml/adapter/base_spec.rb +27 -0
  151. data/spec/moxml/adapter/headed_ox_spec.rb +311 -0
  152. data/spec/moxml/adapter/libxml_spec.rb +14 -0
  153. data/spec/moxml/adapter/ox_spec.rb +9 -8
  154. data/spec/moxml/adapter/shared_examples/.gitkeep +0 -0
  155. data/spec/{support/shared_examples/xml_adapter.rb → moxml/adapter/shared_examples/adapter_contract.rb} +39 -12
  156. data/spec/moxml/adapter_spec.rb +16 -0
  157. data/spec/moxml/attribute_spec.rb +30 -0
  158. data/spec/moxml/builder_spec.rb +33 -0
  159. data/spec/moxml/cdata_spec.rb +31 -0
  160. data/spec/moxml/comment_spec.rb +31 -0
  161. data/spec/moxml/config_spec.rb +3 -3
  162. data/spec/moxml/context_spec.rb +28 -0
  163. data/spec/moxml/declaration_preservation_spec.rb +217 -0
  164. data/spec/moxml/declaration_spec.rb +36 -0
  165. data/spec/moxml/doctype_spec.rb +33 -0
  166. data/spec/moxml/document_builder_spec.rb +30 -0
  167. data/spec/moxml/document_spec.rb +105 -0
  168. data/spec/moxml/element_spec.rb +143 -0
  169. data/spec/moxml/error_spec.rb +266 -22
  170. data/spec/{moxml_spec.rb → moxml/moxml_spec.rb} +9 -9
  171. data/spec/moxml/namespace_spec.rb +32 -0
  172. data/spec/moxml/node_set_spec.rb +39 -0
  173. data/spec/moxml/node_spec.rb +37 -0
  174. data/spec/moxml/processing_instruction_spec.rb +34 -0
  175. data/spec/moxml/sax_spec.rb +1067 -0
  176. data/spec/moxml/text_spec.rb +31 -0
  177. data/spec/moxml/version_spec.rb +14 -0
  178. data/spec/moxml/xml_utils/.gitkeep +0 -0
  179. data/spec/moxml/xml_utils/encoder_spec.rb +27 -0
  180. data/spec/moxml/xml_utils_spec.rb +49 -0
  181. data/spec/moxml/xpath/ast/node_spec.rb +83 -0
  182. data/spec/moxml/xpath/axes_spec.rb +296 -0
  183. data/spec/moxml/xpath/cache_spec.rb +358 -0
  184. data/spec/moxml/xpath/compiler_spec.rb +406 -0
  185. data/spec/moxml/xpath/context_spec.rb +210 -0
  186. data/spec/moxml/xpath/conversion_spec.rb +365 -0
  187. data/spec/moxml/xpath/fixtures/sample.xml +25 -0
  188. data/spec/moxml/xpath/functions/boolean_functions_spec.rb +114 -0
  189. data/spec/moxml/xpath/functions/node_functions_spec.rb +145 -0
  190. data/spec/moxml/xpath/functions/numeric_functions_spec.rb +164 -0
  191. data/spec/moxml/xpath/functions/position_functions_spec.rb +93 -0
  192. data/spec/moxml/xpath/functions/special_functions_spec.rb +89 -0
  193. data/spec/moxml/xpath/functions/string_functions_spec.rb +381 -0
  194. data/spec/moxml/xpath/lexer_spec.rb +488 -0
  195. data/spec/moxml/xpath/parser_integration_spec.rb +210 -0
  196. data/spec/moxml/xpath/parser_spec.rb +364 -0
  197. data/spec/moxml/xpath/ruby/generator_spec.rb +421 -0
  198. data/spec/moxml/xpath/ruby/node_spec.rb +291 -0
  199. data/spec/moxml/xpath_capabilities_spec.rb +199 -0
  200. data/spec/moxml/xpath_spec.rb +77 -0
  201. data/spec/performance/README.md +83 -0
  202. data/spec/performance/benchmark_spec.rb +64 -0
  203. data/spec/{support/shared_examples/examples/memory.rb → performance/memory_usage_spec.rb} +4 -1
  204. data/spec/{support/shared_examples/examples/thread_safety.rb → performance/thread_safety_spec.rb} +3 -1
  205. data/spec/performance/xpath_benchmark_spec.rb +259 -0
  206. data/spec/spec_helper.rb +58 -1
  207. data/spec/support/xml_matchers.rb +1 -1
  208. metadata +178 -34
  209. data/spec/support/shared_examples/examples/benchmark_spec.rb +0 -51
  210. /data/spec/{support/shared_examples/builder.rb → integration/shared_examples/high_level/builder_behavior.rb} +0 -0
  211. /data/spec/{support/shared_examples/document_builder.rb → integration/shared_examples/high_level/document_builder_behavior.rb} +0 -0
  212. /data/spec/{support/shared_examples/attribute.rb → integration/shared_examples/node_wrappers/attribute_behavior.rb} +0 -0
  213. /data/spec/{support/shared_examples/element.rb → integration/shared_examples/node_wrappers/element_behavior.rb} +0 -0
  214. /data/spec/{support/shared_examples/namespace.rb → integration/shared_examples/node_wrappers/namespace_behavior.rb} +0 -0
  215. /data/spec/{support/shared_examples/text.rb → integration/shared_examples/node_wrappers/text_behavior.rb} +0 -0
@@ -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