moxml 0.1.6 → 0.1.8

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 +12 -4
  6. data/.github/workflows/release.yml +5 -3
  7. data/.gitignore +37 -0
  8. data/.rubocop.yml +15 -7
  9. data/.rubocop_todo.yml +238 -40
  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 +45 -0
  21. data/docs/_guides/modifying-xml.adoc +293 -0
  22. data/docs/_guides/parsing-xml.adoc +231 -0
  23. data/docs/_guides/sax-parsing.adoc +603 -0
  24. data/docs/_guides/working-with-documents.adoc +118 -0
  25. data/docs/_pages/adapter-compatibility.adoc +369 -0
  26. data/docs/_pages/adapters/headed-ox.adoc +237 -0
  27. data/docs/_pages/adapters/index.adoc +98 -0
  28. data/docs/_pages/adapters/libxml.adoc +286 -0
  29. data/docs/_pages/adapters/nokogiri.adoc +252 -0
  30. data/docs/_pages/adapters/oga.adoc +292 -0
  31. data/docs/_pages/adapters/ox.adoc +55 -0
  32. data/docs/_pages/adapters/rexml.adoc +293 -0
  33. data/docs/_pages/best-practices.adoc +430 -0
  34. data/docs/_pages/compatibility.adoc +468 -0
  35. data/docs/_pages/configuration.adoc +251 -0
  36. data/docs/_pages/error-handling.adoc +350 -0
  37. data/docs/_pages/headed-ox-limitations.adoc +558 -0
  38. data/docs/_pages/headed-ox.adoc +1025 -0
  39. data/docs/_pages/index.adoc +35 -0
  40. data/docs/_pages/installation.adoc +141 -0
  41. data/docs/_pages/node-api-reference.adoc +50 -0
  42. data/docs/_pages/performance.adoc +36 -0
  43. data/docs/_pages/quick-start.adoc +244 -0
  44. data/docs/_pages/thread-safety.adoc +29 -0
  45. data/docs/_references/document-api.adoc +408 -0
  46. data/docs/_references/index.adoc +48 -0
  47. data/docs/_tutorials/basic-usage.adoc +268 -0
  48. data/docs/_tutorials/builder-pattern.adoc +343 -0
  49. data/docs/_tutorials/index.adoc +33 -0
  50. data/docs/_tutorials/namespace-handling.adoc +325 -0
  51. data/docs/_tutorials/xpath-queries.adoc +359 -0
  52. data/docs/index.adoc +122 -0
  53. data/examples/README.md +124 -0
  54. data/examples/api_client/README.md +424 -0
  55. data/examples/api_client/api_client.rb +394 -0
  56. data/examples/api_client/example_response.xml +48 -0
  57. data/examples/headed_ox_example/README.md +90 -0
  58. data/examples/headed_ox_example/headed_ox_demo.rb +71 -0
  59. data/examples/rss_parser/README.md +194 -0
  60. data/examples/rss_parser/example_feed.xml +93 -0
  61. data/examples/rss_parser/rss_parser.rb +189 -0
  62. data/examples/sax_parsing/README.md +50 -0
  63. data/examples/sax_parsing/data_extractor.rb +75 -0
  64. data/examples/sax_parsing/example.xml +21 -0
  65. data/examples/sax_parsing/large_file.rb +78 -0
  66. data/examples/sax_parsing/simple_parser.rb +55 -0
  67. data/examples/web_scraper/README.md +352 -0
  68. data/examples/web_scraper/example_page.html +201 -0
  69. data/examples/web_scraper/web_scraper.rb +312 -0
  70. data/lib/moxml/adapter/base.rb +107 -28
  71. data/lib/moxml/adapter/customized_libxml/cdata.rb +28 -0
  72. data/lib/moxml/adapter/customized_libxml/comment.rb +24 -0
  73. data/lib/moxml/adapter/customized_libxml/declaration.rb +85 -0
  74. data/lib/moxml/adapter/customized_libxml/element.rb +39 -0
  75. data/lib/moxml/adapter/customized_libxml/node.rb +44 -0
  76. data/lib/moxml/adapter/customized_libxml/processing_instruction.rb +31 -0
  77. data/lib/moxml/adapter/customized_libxml/text.rb +27 -0
  78. data/lib/moxml/adapter/customized_oga/xml_generator.rb +1 -1
  79. data/lib/moxml/adapter/customized_ox/attribute.rb +28 -3
  80. data/lib/moxml/adapter/customized_ox/namespace.rb +0 -2
  81. data/lib/moxml/adapter/customized_ox/text.rb +0 -2
  82. data/lib/moxml/adapter/customized_rexml/formatter.rb +11 -6
  83. data/lib/moxml/adapter/headed_ox.rb +161 -0
  84. data/lib/moxml/adapter/libxml.rb +1548 -0
  85. data/lib/moxml/adapter/nokogiri.rb +121 -9
  86. data/lib/moxml/adapter/oga.rb +123 -12
  87. data/lib/moxml/adapter/ox.rb +283 -27
  88. data/lib/moxml/adapter/rexml.rb +127 -20
  89. data/lib/moxml/adapter.rb +21 -4
  90. data/lib/moxml/attribute.rb +6 -0
  91. data/lib/moxml/builder.rb +40 -4
  92. data/lib/moxml/config.rb +8 -3
  93. data/lib/moxml/context.rb +39 -1
  94. data/lib/moxml/doctype.rb +13 -1
  95. data/lib/moxml/document.rb +39 -6
  96. data/lib/moxml/document_builder.rb +27 -5
  97. data/lib/moxml/element.rb +71 -2
  98. data/lib/moxml/error.rb +175 -6
  99. data/lib/moxml/node.rb +94 -3
  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 +1768 -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 -2
  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_spec.rb +36 -0
  164. data/spec/moxml/doctype_spec.rb +33 -0
  165. data/spec/moxml/document_builder_spec.rb +30 -0
  166. data/spec/moxml/document_spec.rb +105 -0
  167. data/spec/moxml/element_spec.rb +143 -0
  168. data/spec/moxml/error_spec.rb +266 -22
  169. data/spec/{moxml_spec.rb → moxml/moxml_spec.rb} +9 -9
  170. data/spec/moxml/namespace_spec.rb +32 -0
  171. data/spec/moxml/node_set_spec.rb +39 -0
  172. data/spec/moxml/node_spec.rb +37 -0
  173. data/spec/moxml/processing_instruction_spec.rb +34 -0
  174. data/spec/moxml/sax_spec.rb +1067 -0
  175. data/spec/moxml/text_spec.rb +31 -0
  176. data/spec/moxml/version_spec.rb +14 -0
  177. data/spec/moxml/xml_utils/.gitkeep +0 -0
  178. data/spec/moxml/xml_utils/encoder_spec.rb +27 -0
  179. data/spec/moxml/xml_utils_spec.rb +49 -0
  180. data/spec/moxml/xpath/ast/node_spec.rb +83 -0
  181. data/spec/moxml/xpath/axes_spec.rb +296 -0
  182. data/spec/moxml/xpath/cache_spec.rb +358 -0
  183. data/spec/moxml/xpath/compiler_spec.rb +406 -0
  184. data/spec/moxml/xpath/context_spec.rb +210 -0
  185. data/spec/moxml/xpath/conversion_spec.rb +365 -0
  186. data/spec/moxml/xpath/fixtures/sample.xml +25 -0
  187. data/spec/moxml/xpath/functions/boolean_functions_spec.rb +114 -0
  188. data/spec/moxml/xpath/functions/node_functions_spec.rb +145 -0
  189. data/spec/moxml/xpath/functions/numeric_functions_spec.rb +164 -0
  190. data/spec/moxml/xpath/functions/position_functions_spec.rb +93 -0
  191. data/spec/moxml/xpath/functions/special_functions_spec.rb +89 -0
  192. data/spec/moxml/xpath/functions/string_functions_spec.rb +381 -0
  193. data/spec/moxml/xpath/lexer_spec.rb +488 -0
  194. data/spec/moxml/xpath/parser_integration_spec.rb +210 -0
  195. data/spec/moxml/xpath/parser_spec.rb +364 -0
  196. data/spec/moxml/xpath/ruby/generator_spec.rb +421 -0
  197. data/spec/moxml/xpath/ruby/node_spec.rb +291 -0
  198. data/spec/moxml/xpath_capabilities_spec.rb +199 -0
  199. data/spec/moxml/xpath_spec.rb +77 -0
  200. data/spec/performance/README.md +83 -0
  201. data/spec/performance/benchmark_spec.rb +64 -0
  202. data/spec/{support/shared_examples/examples/memory.rb → performance/memory_usage_spec.rb} +3 -1
  203. data/spec/{support/shared_examples/examples/thread_safety.rb → performance/thread_safety_spec.rb} +3 -1
  204. data/spec/performance/xpath_benchmark_spec.rb +259 -0
  205. data/spec/spec_helper.rb +58 -1
  206. data/spec/support/xml_matchers.rb +1 -1
  207. metadata +176 -35
  208. data/lib/ox/node.rb +0 -9
  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,430 @@
1
+ ---
2
+ title: Best practices
3
+ parent: Overview
4
+ nav_order: 9
5
+ ---
6
+
7
+ == Best practices
8
+
9
+ === Purpose
10
+
11
+ Recommended patterns and practices for using Moxml effectively in production
12
+ applications.
13
+
14
+ === Document creation
15
+
16
+ ==== Use builder pattern for new documents
17
+
18
+ [source,ruby]
19
+ ----
20
+ # Preferred - clean and maintainable
21
+ doc = Moxml::Builder.build(Moxml.new) do |xml|
22
+ xml.declaration version: "1.0", encoding: "UTF-8"
23
+ xml.library do
24
+ xml.book "Content"
25
+ end
26
+ end
27
+
28
+ # Avoid - verbose and error-prone
29
+ doc = Moxml.new.create_document
30
+ doc.add_child(doc.create_declaration)
31
+ root = doc.create_element('library')
32
+ # ... many more lines
33
+ ----
34
+
35
+ ==== Use direct manipulation for modifications
36
+
37
+ [source,ruby]
38
+ ----
39
+ # Preferred for modifying existing documents
40
+ doc = Moxml.new.parse(xml)
41
+ book = doc.at_xpath('//book[@id="1"]')
42
+ book['edition'] = '2nd'
43
+ book.at_xpath('.//price').text = '24.99'
44
+ ----
45
+
46
+ === XPath queries
47
+
48
+ ==== Use specific paths
49
+
50
+ [source,ruby]
51
+ ----
52
+ # More efficient - specific path
53
+ doc.xpath('/library/section/book')
54
+
55
+ # Less efficient - requires full document scan
56
+ doc.xpath('//book')
57
+
58
+ # Most efficient - from known parent
59
+ section.xpath('./book')
60
+ ----
61
+
62
+ ==== Cache query results
63
+
64
+ [source,ruby]
65
+ ----
66
+ # Inefficient - queries multiple times
67
+ doc.xpath('//book').each do |book|
68
+ if doc.xpath('//book').length > 10
69
+ # ...
70
+ end
71
+ end
72
+
73
+ # Better - cache the result
74
+ books = doc.xpath('//book')
75
+ books.each do |book|
76
+ if books.length > 10
77
+ # ...
78
+ end
79
+ end
80
+ ----
81
+
82
+ ==== Use at_xpath for single results
83
+
84
+ [source,ruby]
85
+ ----
86
+ # Preferred - when expecting single result
87
+ book = doc.at_xpath('//book[@id="1"]')
88
+
89
+ # Avoid - unnecessary array creation
90
+ book = doc.xpath('//book[@id="1"]').first
91
+ ----
92
+
93
+ === Adapter selection
94
+
95
+ ==== Choose adapter explicitly in production
96
+
97
+ [source,ruby]
98
+ ----
99
+ # Good - explicit and predictable
100
+ class XmlProcessor
101
+ def initialize
102
+ @context = Moxml.new
103
+ @context.config.adapter = :nokogiri
104
+ end
105
+ end
106
+
107
+ # Avoid - relies on gem load order
108
+ class XmlProcessor
109
+ def initialize
110
+ @context = Moxml.new # Uses whatever is available
111
+ end
112
+ end
113
+ ----
114
+
115
+ ==== Match adapter to use case
116
+
117
+ [source,ruby]
118
+ ----
119
+ # High-throughput simple docs
120
+ fast_context = Moxml.new
121
+ fast_context.config.adapter = :ox
122
+
123
+ # Complex XPath queries
124
+ full_context = Moxml.new
125
+ full_context.config.adapter = :nokogiri
126
+
127
+ # Pure Ruby requirement
128
+ pure_context = Moxml.new
129
+ pure_context.config.adapter = :oga
130
+ ----
131
+
132
+ === Error handling
133
+
134
+ ==== Always use strict parsing in production
135
+
136
+ [source,ruby]
137
+ ----
138
+ # Production
139
+ def parse_production(xml)
140
+ Moxml.new.parse(xml, strict: true)
141
+ rescue Moxml::ParseError => e
142
+ logger.error("Invalid XML received: #{e.message}")
143
+ raise
144
+ end
145
+
146
+ # Development/testing only
147
+ def parse_development(xml)
148
+ Moxml.new.parse(xml, strict: false)
149
+ end
150
+ ----
151
+
152
+ ==== Catch specific errors
153
+
154
+ [source,ruby]
155
+ ----
156
+ # Good - targeted error handling
157
+ begin
158
+ doc = Moxml.new.parse(xml)
159
+ process(doc)
160
+ rescue Moxml::ParseError => e
161
+ handle_parse_error(e)
162
+ rescue Moxml::XPathError => e
163
+ handle_xpath_error(e)
164
+ end
165
+
166
+ # Avoid - too broad
167
+ begin
168
+ doc = Moxml.new.parse(xml)
169
+ process(doc)
170
+ rescue StandardError => e
171
+ # Catches too much
172
+ end
173
+ ----
174
+
175
+ === Namespace handling
176
+
177
+ ==== Define namespace mappings clearly
178
+
179
+ [source,ruby]
180
+ ----
181
+ # Good - clear and reusable
182
+ NAMESPACES = {
183
+ 'dc' => 'http://purl.org/dc/elements/1.1/',
184
+ 'xhtml' => 'http://www.w3.org/1999/xhtml'
185
+ }.freeze
186
+
187
+ titles = doc.xpath('//dc:title', NAMESPACES)
188
+
189
+ # Avoid - inline everywhere
190
+ doc.xpath('//dc:title', { 'dc' => 'http://purl.org/dc/elements/1.1/' })
191
+ doc.xpath('//dc:creator', { 'dc' => 'http://purl.org/dc/elements/1.1/' })
192
+ ----
193
+
194
+ ==== Check adapter namespace support
195
+
196
+ [source,ruby]
197
+ ----
198
+ def query_with_namespace(doc, expression, namespaces)
199
+ adapter = doc.context.config.adapter.name
200
+
201
+ if adapter.include?('Rexml') || adapter.include?('Ox')
202
+ # Fallback for limited namespace support
203
+ query_without_namespace(doc, expression)
204
+ else
205
+ doc.xpath(expression, namespaces)
206
+ end
207
+ end
208
+ ----
209
+
210
+ === Memory management
211
+
212
+ ==== Release document references
213
+
214
+ [source,ruby]
215
+ ----
216
+ # Process large documents
217
+ def process_large_xml(xml)
218
+ doc = Moxml.new.parse(xml)
219
+ result = extract_data(doc)
220
+ doc = nil # Allow GC
221
+ result
222
+ end
223
+
224
+ # Batch processing
225
+ xml_files.each do |file|
226
+ doc = Moxml.new.parse(File.read(file))
227
+ process(doc)
228
+ doc = nil # Release before next iteration
229
+ GC.start if large_file?(file)
230
+ end
231
+ ----
232
+
233
+ ==== Use streaming for very large files
234
+
235
+ [source,ruby]
236
+ ----
237
+ # For extremely large documents
238
+ def process_huge_xml(filename)
239
+ # Process in chunks if possible
240
+ File.open(filename) do |file|
241
+ # Read and process incrementally
242
+ end
243
+ end
244
+ ----
245
+
246
+ === Thread safety
247
+
248
+ ==== Use separate contexts per thread
249
+
250
+ [source,ruby]
251
+ ----
252
+ # Good - thread-safe
253
+ class XmlProcessor
254
+ def process(xml)
255
+ context = Moxml.new # New context per call
256
+ doc = context.parse(xml)
257
+ # Process...
258
+ end
259
+ end
260
+
261
+ # Avoid - shared state
262
+ class XmlProcessor
263
+ def initialize
264
+ @context = Moxml.new
265
+ end
266
+
267
+ def process(xml)
268
+ # Multiple threads share @context
269
+ @context.parse(xml) # Not thread-safe
270
+ end
271
+ end
272
+ ----
273
+
274
+ ==== Protect shared resources
275
+
276
+ [source,ruby]
277
+ ----
278
+ class ThreadSafeProcessor
279
+ def initialize
280
+ @mutex = Mutex.new
281
+ @context = Moxml.new
282
+ end
283
+
284
+ def process(xml)
285
+ @mutex.synchronize do
286
+ doc = @context.parse(xml)
287
+ # Modify document safely
288
+ doc.to_xml
289
+ end
290
+ end
291
+ end
292
+ ----
293
+
294
+ === Performance optimization
295
+
296
+ ==== Reuse context instances
297
+
298
+ [source,ruby]
299
+ ----
300
+ # Good - reuse context
301
+ class DocumentProcessor
302
+ def initialize
303
+ @context = Moxml.new
304
+ @context.config.adapter = :nokogiri
305
+ end
306
+
307
+ def process_many(xml_documents)
308
+ xml_documents.map do |xml|
309
+ @context.parse(xml) # Reuses same context
310
+ end
311
+ end
312
+ end
313
+ ----
314
+
315
+ ==== Choose appropriate adapter
316
+
317
+ [source,ruby]
318
+ ----
319
+ # Match adapter to workload
320
+ def select_adapter(xml)
321
+ if complex_xpath_needed?(xml)
322
+ :nokogiri # Full XPath support
323
+ elsif simple_parsing?(xml)
324
+ :ox # Maximum speed
325
+ else
326
+ :nokogiri # Safe default
327
+ end
328
+ end
329
+
330
+ context = Moxml.new
331
+ context.config.adapter = select_adapter(xml)
332
+ ----
333
+
334
+ === Code organization
335
+
336
+ ==== Extract XML operations into methods
337
+
338
+ [source,ruby]
339
+ ----
340
+ class BookProcessor
341
+ def initialize
342
+ @context = Moxml.new
343
+ @context.config.adapter = :nokogiri
344
+ end
345
+
346
+ def parse_catalog(xml)
347
+ @context.parse(xml)
348
+ end
349
+
350
+ def find_book(doc, id)
351
+ doc.at_xpath("//book[@id='#{id}']")
352
+ end
353
+
354
+ def update_price(book, new_price)
355
+ price_elem = book.at_xpath('.//price')
356
+ price_elem.text = new_price.to_s
357
+ end
358
+ end
359
+ ----
360
+
361
+ ==== Use value objects for XML data
362
+
363
+ [source,ruby]
364
+ ----
365
+ class Book
366
+ attr_accessor :id, :title, :author, :price
367
+
368
+ def self.from_xml(element)
369
+ new.tap do |book|
370
+ book.id = element['id']
371
+ book.title = element.at_xpath('.//title')&.text
372
+ book.author = element.at_xpath('.//author')&.text
373
+ book.price = element.at_xpath('.//price')&.text&.to_f
374
+ end
375
+ end
376
+
377
+ def to_xml(doc)
378
+ doc.create_element('book').tap do |elem|
379
+ elem['id'] = id
380
+ elem.add_child(doc.create_element('title').tap { |e| e.text = title })
381
+ elem.add_child(doc.create_element('author').tap { |e| e.text = author })
382
+ elem.add_child(doc.create_element('price').tap { |e| e.text = price.to_s })
383
+ end
384
+ end
385
+ end
386
+ ----
387
+
388
+ === Testing
389
+
390
+ ==== Test with multiple adapters
391
+
392
+ [source,ruby]
393
+ ----
394
+ RSpec.describe "XML Processing" do
395
+ [:nokogiri, :libxml, :oga].each do |adapter_name|
396
+ context "with #{adapter_name}" do
397
+ let(:context) do
398
+ Moxml.new.tap { |c| c.config.adapter = adapter_name }
399
+ end
400
+
401
+ it "processes correctly" do
402
+ doc = context.parse(xml)
403
+ # Test operations
404
+ end
405
+ end
406
+ end
407
+ end
408
+ ----
409
+
410
+ ==== Use fixtures for test XML
411
+
412
+ [source,ruby]
413
+ ----
414
+ # spec/fixtures/sample.xml
415
+ XML_FIXTURE = File.read('spec/fixtures/sample.xml')
416
+
417
+ RSpec.describe "Processing" do
418
+ let(:doc) { Moxml.new.parse(XML_FIXTURE) }
419
+
420
+ it "extracts data" do
421
+ # Test with consistent fixture
422
+ end
423
+ end
424
+ ----
425
+
426
+ === See also
427
+
428
+ * link:configuration[Configuration] - Setup and options
429
+ * link:error-handling[Error handling] - Error management
430
+ * link:../guides/performance-optimization[Performance optimization]