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,424 @@
1
+ # API Client Example
2
+
3
+ This example demonstrates how to use Moxml for XML API interactions, including building SOAP requests, parsing responses, and handling namespaces.
4
+
5
+ ## What This Example Demonstrates
6
+
7
+ - **XML Request Building**: Creating SOAP requests with Moxml::Builder
8
+ - **Response Parsing**: Extracting data from XML API responses
9
+ - **Namespace Handling**: Working with multiple XML namespaces (SOAP, custom)
10
+ - **Authentication**: Including authentication headers
11
+ - **Error Handling**: Robust error handling for API interactions
12
+ - **Data Structuring**: Converting XML responses to Ruby objects
13
+
14
+ ## Files
15
+
16
+ - `api_client.rb` - Main API client implementation
17
+ - `example_response.xml` - Sample SOAP API response
18
+ - `README.md` - This file
19
+
20
+ ## Running the Example
21
+
22
+ ### Using the Example Response
23
+
24
+ ```bash
25
+ ruby examples/api_client/api_client.rb
26
+ ```
27
+
28
+ ### Using Your Own Response
29
+
30
+ ```bash
31
+ ruby examples/api_client/api_client.rb path/to/your/response.xml
32
+ ```
33
+
34
+ ## Expected Output
35
+
36
+ ```
37
+ SOAP API Client Example
38
+ ================================================================================
39
+
40
+ Building SOAP Requests
41
+ ================================================================================
42
+
43
+ 1. GetUser Request:
44
+ --------------------------------------------------------------------------------
45
+ <?xml version="1.0" encoding="UTF-8"?>
46
+ <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" ...>
47
+ <soap:Header>
48
+ <AuthHeader xmlns="http://api.example.com/auth">
49
+ <SessionId>session-abc-123</SessionId>
50
+ <Timestamp>2024-10-30T10:00:00Z</Timestamp>
51
+ <RequestId>req-a1b2c3</RequestId>
52
+ </AuthHeader>
53
+ </soap:Header>
54
+ <soap:Body>
55
+ <GetUserRequest xmlns="http://api.example.com/users">
56
+ <UserId>1001</UserId>
57
+ </GetUserRequest>
58
+ </soap:Body>
59
+ </soap:Envelope>
60
+
61
+ [Additional request examples...]
62
+
63
+ Parsing SOAP Response
64
+ ================================================================================
65
+
66
+ Response Information:
67
+ --------------------------------------------------------------------------------
68
+ Status: 200 - Success
69
+ Session ID: a1b2c3d4-e5f6-g7h8-i9j0-k1l2m3n4o5p6
70
+ Request ID: req-12345
71
+ Metadata: {:response_time=>"45", :server_version=>"2.1.0", :cache_hit=>"false"}
72
+
73
+ User Data:
74
+ --------------------------------------------------------------------------------
75
+ User ID: 1001
76
+ Username: johndoe
77
+ Email: john.doe@example.com
78
+ Full Name: John Doe
79
+ Role: Administrator
80
+ Status: Active
81
+ Created: 2024-01-15T08:30:00Z
82
+ Last Login: 2024-10-29T14:22:00Z
83
+ Permissions: users.read, users.write, admin.access
84
+ Profile:
85
+ Department: Engineering
86
+ Title: Senior Developer
87
+ Location: San Francisco, CA
88
+ PhoneNumber: +1-555-0123
89
+
90
+ Result: SUCCESS ✓
91
+ ```
92
+
93
+ ## Key Concepts
94
+
95
+ ### Building SOAP Requests
96
+
97
+ Use Moxml::Builder to create well-formed SOAP envelopes:
98
+
99
+ ```ruby
100
+ doc = Moxml::Builder.new(@moxml).build do
101
+ declaration version: "1.0", encoding: "UTF-8"
102
+
103
+ element 'soap:Envelope',
104
+ 'xmlns:soap' => 'http://schemas.xmlsoap.org/soap/envelope/' do
105
+
106
+ element 'soap:Header' do
107
+ # Authentication headers
108
+ end
109
+
110
+ element 'soap:Body' do
111
+ # Request payload
112
+ end
113
+ end
114
+ end
115
+ ```
116
+
117
+ ### Namespace Declaration
118
+
119
+ Define namespaces as constants for reusability:
120
+
121
+ ```ruby
122
+ NAMESPACES = {
123
+ 'soap' => 'http://schemas.xmlsoap.org/soap/envelope/',
124
+ 'auth' => 'http://api.example.com/auth',
125
+ 'users' => 'http://api.example.com/users'
126
+ }.freeze
127
+
128
+ # Use in XPath queries
129
+ doc.at_xpath('//users:User', NAMESPACES)
130
+ ```
131
+
132
+ ### Parsing Responses
133
+
134
+ Extract data using namespace-aware XPath:
135
+
136
+ ```ruby
137
+ # Extract with namespace
138
+ user_id = extract_text(doc, '//users:User/users:Id', NAMESPACES)
139
+
140
+ # Extract arrays
141
+ permissions = doc.xpath('//users:Permission', NAMESPACES).map(&:text)
142
+
143
+ # Extract nested structures
144
+ profile = doc.at_xpath('//users:Profile', NAMESPACES)
145
+ department = extract_text(profile, './users:Department', NAMESPACES)
146
+ ```
147
+
148
+ ### Error Handling
149
+
150
+ Handle parsing and query errors:
151
+
152
+ ```ruby
153
+ begin
154
+ doc = @moxml.parse(xml_string)
155
+ response = parse_response(doc)
156
+ rescue Moxml::ParseError => e
157
+ puts "Parse error: #{e.message}"
158
+ rescue Moxml::XPathError => e
159
+ puts "Query error: #{e.message}"
160
+ end
161
+ ```
162
+
163
+ ### Authentication Headers
164
+
165
+ Include authentication in SOAP headers:
166
+
167
+ ```ruby
168
+ element 'soap:Header' do
169
+ element 'AuthHeader', 'xmlns' => NAMESPACES['auth'] do
170
+ element 'SessionId' do
171
+ text session_id
172
+ end
173
+ element 'Timestamp' do
174
+ text Time.now.utc.iso8601
175
+ end
176
+ end
177
+ end
178
+ ```
179
+
180
+ ## Code Structure
181
+
182
+ ### User Class
183
+
184
+ Represents API user data:
185
+ - Basic info (id, username, email, etc.)
186
+ - Permissions array
187
+ - Profile hash
188
+
189
+ ### APIResponse Class
190
+
191
+ Encapsulates API response:
192
+ - Status code and message
193
+ - Response data
194
+ - Metadata (response time, version, etc.)
195
+ - Session and request IDs
196
+
197
+ ### SOAPClient Class
198
+
199
+ Main API client with methods:
200
+ - `build_get_user_request` - Build GetUser SOAP request
201
+ - `build_create_user_request` - Build CreateUser SOAP request
202
+ - `parse_response` - Parse SOAP response
203
+ - `parse_user` - Extract user data
204
+ - `extract_metadata` - Extract response metadata
205
+
206
+ ## SOAP Structure Reference
207
+
208
+ ### Request Structure
209
+
210
+ ```xml
211
+ <soap:Envelope>
212
+ <soap:Header>
213
+ <AuthHeader>
214
+ <!-- Authentication data -->
215
+ </AuthHeader>
216
+ </soap:Header>
217
+ <soap:Body>
218
+ <OperationRequest>
219
+ <!-- Request parameters -->
220
+ </OperationRequest>
221
+ </soap:Body>
222
+ </soap:Envelope>
223
+ ```
224
+
225
+ ### Response Structure
226
+
227
+ ```xml
228
+ <soap:Envelope>
229
+ <soap:Header>
230
+ <AuthHeader>
231
+ <!-- Session info -->
232
+ </AuthHeader>
233
+ </soap:Header>
234
+ <soap:Body>
235
+ <OperationResponse>
236
+ <Status>
237
+ <!-- Status code/message -->
238
+ </Status>
239
+ <Result>
240
+ <!-- Response data -->
241
+ </Result>
242
+ <Metadata>
243
+ <!-- Additional info -->
244
+ </Metadata>
245
+ </OperationResponse>
246
+ </soap:Body>
247
+ </soap:Envelope>
248
+ ```
249
+
250
+ ## Customization
251
+
252
+ ### Adding New Operations
253
+
254
+ Create new request builders:
255
+
256
+ ```ruby
257
+ def build_delete_user_request(user_id)
258
+ Moxml::Builder.new(@moxml).build do
259
+ declaration version: "1.0", encoding: "UTF-8"
260
+
261
+ element 'soap:Envelope', 'xmlns:soap' => NAMESPACES['soap'] do
262
+ element 'soap:Header' do
263
+ # Auth header
264
+ end
265
+
266
+ element 'soap:Body' do
267
+ element 'DeleteUserRequest', 'xmlns' => NAMESPACES['users'] do
268
+ element 'UserId' do
269
+ text user_id.to_s
270
+ end
271
+ end
272
+ end
273
+ end
274
+ end
275
+ end
276
+ ```
277
+
278
+ ### Custom Response Parsers
279
+
280
+ Parse different response types:
281
+
282
+ ```ruby
283
+ def parse_list_response(doc)
284
+ users = doc.xpath('//users:User', NAMESPACES)
285
+ users.map { |user_elem| parse_user(user_elem) }
286
+ end
287
+ ```
288
+
289
+ ### Error Responses
290
+
291
+ Handle SOAP faults:
292
+
293
+ ```ruby
294
+ fault = doc.at_xpath('//soap:Fault', NAMESPACES)
295
+ if fault
296
+ fault_code = extract_text(fault, './faultcode')
297
+ fault_string = extract_text(fault, './faultstring')
298
+ raise "SOAP Fault: #{fault_code} - #{fault_string}"
299
+ end
300
+ ```
301
+
302
+ ## Namespace Best Practices
303
+
304
+ 1. **Define once**: Keep namespace URIs in constants
305
+ 2. **Use prefixes**: Consistent prefixes make code readable
306
+ 3. **Pass to XPath**: Always include namespaces in queries
307
+ 4. **Document**: Comment namespace purposes
308
+
309
+ ```ruby
310
+ # Good: Clear namespace management
311
+ NAMESPACES = {
312
+ 'soap' => 'http://schemas.xmlsoap.org/soap/envelope/', # SOAP envelope
313
+ 'auth' => 'http://api.example.com/auth', # Authentication
314
+ 'users' => 'http://api.example.com/users' # User operations
315
+ }.freeze
316
+
317
+ # Use consistently
318
+ doc.xpath('//users:User', NAMESPACES)
319
+ ```
320
+
321
+ ## Common Patterns
322
+
323
+ ### Conditional Elements
324
+
325
+ Include elements conditionally:
326
+
327
+ ```ruby
328
+ element 'soap:Body' do
329
+ element 'Request' do
330
+ element 'UserId' do
331
+ text user_id
332
+ end
333
+
334
+ # Optional filter
335
+ if filter
336
+ element 'Filter' do
337
+ text filter
338
+ end
339
+ end
340
+ end
341
+ end
342
+ ```
343
+
344
+ ### Array Elements
345
+
346
+ Create multiple elements:
347
+
348
+ ```ruby
349
+ permissions.each do |perm|
350
+ element 'Permission' do
351
+ text perm
352
+ end
353
+ end
354
+ ```
355
+
356
+ ### Nested Structures
357
+
358
+ Build complex hierarchies:
359
+
360
+ ```ruby
361
+ element 'User' do
362
+ element 'BasicInfo' do
363
+ element 'Username' do
364
+ text username
365
+ end
366
+ end
367
+
368
+ element 'Profile' do
369
+ element 'Department' do
370
+ text department
371
+ end
372
+ end
373
+ end
374
+ ```
375
+
376
+ ## Learning Points
377
+
378
+ 1. **Builder pattern**: Clean, readable XML construction
379
+ 2. **Namespaces**: Critical for SOAP and enterprise XML
380
+ 3. **XPath + namespaces**: Powerful data extraction
381
+ 4. **Structure data**: Convert XML to Ruby objects
382
+ 5. **Error handling**: Robust API client behavior
383
+ 6. **Reusability**: Extract common patterns to methods
384
+
385
+ ## Testing
386
+
387
+ Test API clients thoroughly:
388
+
389
+ ```ruby
390
+ # Test request building
391
+ request = client.build_get_user_request(123)
392
+ assert request.to_xml.include?('<UserId>123</UserId>')
393
+
394
+ # Test response parsing
395
+ response = client.parse_response(sample_xml)
396
+ assert_equal 200, response.status_code
397
+ assert response.success?
398
+ ```
399
+
400
+ ## Real-World Usage
401
+
402
+ To use with actual APIs:
403
+
404
+ 1. Replace `example_response.xml` with real responses
405
+ 2. Add HTTP client (e.g., Net::HTTP, HTTParty)
406
+ 3. Handle network errors
407
+ 4. Implement retry logic
408
+ 5. Add request/response logging
409
+ 6. Implement authentication refresh
410
+
411
+ ## Next Steps
412
+
413
+ - Add more API operations (update, delete, list)
414
+ - Implement HTTP transport layer
415
+ - Add request/response logging
416
+ - Implement authentication token refresh
417
+ - Handle pagination in list responses
418
+ - Add response caching
419
+ - Implement request rate limiting
420
+
421
+ ## Related Examples
422
+
423
+ - [RSS Parser](../rss_parser/) - XPath and namespace handling
424
+ - [Web Scraper](../web_scraper/) - DOM navigation techniques