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,394 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # API Client Example
5
+ # This example demonstrates how to use Moxml for API interactions:
6
+ # - Building XML API requests (SOAP)
7
+ # - Parsing XML API responses
8
+ # - Handling authentication elements
9
+ # - Working with namespaces
10
+ # - Error handling and validation
11
+
12
+ # Load moxml from the local source (use 'require "moxml"' in production)
13
+ require_relative "../../lib/moxml"
14
+ require "securerandom"
15
+ require "time"
16
+
17
+ # User class to represent API user data
18
+ class User
19
+ attr_reader :id, :username, :email, :full_name, :role, :status,
20
+ :created_at, :last_login, :permissions, :profile
21
+
22
+ def initialize(data)
23
+ @id = data[:id]
24
+ @username = data[:username]
25
+ @email = data[:email]
26
+ @full_name = data[:full_name]
27
+ @role = data[:role]
28
+ @status = data[:status]
29
+ @created_at = data[:created_at]
30
+ @last_login = data[:last_login]
31
+ @permissions = data[:permissions] || []
32
+ @profile = data[:profile] || {}
33
+ end
34
+
35
+ def to_s
36
+ output = []
37
+ output << "User ID: #{@id}"
38
+ output << "Username: #{@username}"
39
+ output << "Email: #{@email}"
40
+ output << "Full Name: #{@full_name}"
41
+ output << "Role: #{@role}"
42
+ output << "Status: #{@status}"
43
+ output << "Created: #{@created_at}"
44
+ output << "Last Login: #{@last_login}"
45
+ output << "Permissions: #{@permissions.join(', ')}"
46
+ output << "Profile:"
47
+ @profile.each { |key, value| output << " #{key}: #{value}" }
48
+ output.join("\n")
49
+ end
50
+ end
51
+
52
+ # APIResponse class to encapsulate API response data
53
+ class APIResponse
54
+ attr_reader :status_code, :message, :data, :metadata, :session_id, :request_id
55
+
56
+ def initialize(status_code:, message:, data: nil, metadata: {},
57
+ session_id: nil, request_id: nil)
58
+ @status_code = status_code.to_i
59
+ @message = message
60
+ @data = data
61
+ @metadata = metadata
62
+ @session_id = session_id
63
+ @request_id = request_id
64
+ end
65
+
66
+ def success?
67
+ @status_code >= 200 && @status_code < 300
68
+ end
69
+
70
+ def to_s
71
+ output = []
72
+ output << "Status: #{@status_code} - #{@message}"
73
+ output << "Session ID: #{@session_id}" if @session_id
74
+ output << "Request ID: #{@request_id}" if @request_id
75
+ output << "Metadata: #{@metadata.inspect}" unless @metadata.empty?
76
+ output.join("\n")
77
+ end
78
+ end
79
+
80
+ # SOAPClient class for making SOAP API requests
81
+ class SOAPClient
82
+ # SOAP namespaces used in requests/responses
83
+ NAMESPACES = {
84
+ "soap" => "http://schemas.xmlsoap.org/soap/envelope/",
85
+ "xsi" => "http://www.w3.org/2001/XMLSchema-instance",
86
+ "xsd" => "http://www.w3.org/2001/XMLSchema",
87
+ "auth" => "http://api.example.com/auth",
88
+ "users" => "http://api.example.com/users",
89
+ }.freeze
90
+
91
+ def initialize
92
+ @moxml = Moxml.new
93
+ end
94
+
95
+ # Build a SOAP GetUser request
96
+ def build_get_user_request(user_id, session_id = nil)
97
+ # Generate request ID and timestamp
98
+ request_id = "req-#{SecureRandom.hex(6)}"
99
+ timestamp = Time.now.utc.iso8601
100
+
101
+ # Build the SOAP envelope using Moxml::Builder
102
+ Moxml::Builder.new(@moxml).build do
103
+ # XML declaration
104
+ declaration version: "1.0", encoding: "UTF-8"
105
+
106
+ # SOAP Envelope with namespaces
107
+ element "soap:Envelope",
108
+ "xmlns:soap" => NAMESPACES["soap"],
109
+ "xmlns:xsi" => NAMESPACES["xsi"],
110
+ "xmlns:xsd" => NAMESPACES["xsd"] do
111
+ # SOAP Header with authentication
112
+ element "soap:Header" do
113
+ element "AuthHeader", "xmlns" => NAMESPACES["auth"] do
114
+ element "SessionId" do
115
+ text(session_id || "demo-session-id")
116
+ end
117
+ element "Timestamp" do
118
+ text timestamp
119
+ end
120
+ element "RequestId" do
121
+ text request_id
122
+ end
123
+ end
124
+ end
125
+
126
+ # SOAP Body with request
127
+ element "soap:Body" do
128
+ element "GetUserRequest", "xmlns" => NAMESPACES["users"] do
129
+ element "UserId" do
130
+ text user_id.to_s
131
+ end
132
+ end
133
+ end
134
+ end
135
+ end
136
+ end
137
+
138
+ # Build a SOAP CreateUser request
139
+ def build_create_user_request(username, email, full_name, role)
140
+ request_id = "req-#{SecureRandom.hex(6)}"
141
+ timestamp = Time.now.utc.iso8601
142
+
143
+ Moxml::Builder.new(@moxml).build do
144
+ declaration version: "1.0", encoding: "UTF-8"
145
+
146
+ element "soap:Envelope",
147
+ "xmlns:soap" => NAMESPACES["soap"],
148
+ "xmlns:xsi" => NAMESPACES["xsi"],
149
+ "xmlns:xsd" => NAMESPACES["xsd"] do
150
+ element "soap:Header" do
151
+ element "AuthHeader", "xmlns" => NAMESPACES["auth"] do
152
+ element "SessionId" do
153
+ text "demo-session-id"
154
+ end
155
+ element "Timestamp" do
156
+ text timestamp
157
+ end
158
+ element "RequestId" do
159
+ text request_id
160
+ end
161
+ end
162
+ end
163
+
164
+ element "soap:Body" do
165
+ element "CreateUserRequest", "xmlns" => NAMESPACES["users"] do
166
+ element "Username" do
167
+ text username
168
+ end
169
+ element "Email" do
170
+ text email
171
+ end
172
+ element "FullName" do
173
+ text full_name
174
+ end
175
+ element "Role" do
176
+ text role
177
+ end
178
+ end
179
+ end
180
+ end
181
+ end
182
+ end
183
+
184
+ # Parse a SOAP response
185
+ def parse_response(xml_string)
186
+ # Parse the XML response
187
+ doc = begin
188
+ @moxml.parse(xml_string)
189
+ rescue Moxml::ParseError => e
190
+ puts "Failed to parse API response: #{e.message}"
191
+ raise
192
+ end
193
+
194
+ # Extract authentication header
195
+ session_id = extract_text(doc, "//auth:SessionId", NAMESPACES)
196
+ request_id = extract_text(doc, "//auth:RequestId", NAMESPACES)
197
+
198
+ # Extract status information
199
+ status_code = extract_text(doc, "//users:Status/users:Code", NAMESPACES)
200
+ status_message = extract_text(doc, "//users:Status/users:Message",
201
+ NAMESPACES)
202
+
203
+ # Extract metadata
204
+ metadata = extract_metadata(doc)
205
+
206
+ # Check if response contains user data
207
+ user_element = doc.at_xpath("//users:User", NAMESPACES)
208
+ user_data = user_element ? parse_user(user_element) : nil
209
+
210
+ APIResponse.new(
211
+ status_code: status_code,
212
+ message: status_message,
213
+ data: user_data,
214
+ metadata: metadata,
215
+ session_id: session_id,
216
+ request_id: request_id,
217
+ )
218
+ end
219
+
220
+ private
221
+
222
+ # Parse user data from XML element
223
+ def parse_user(user_element)
224
+ # Extract basic user fields
225
+ id = extract_text(user_element, "./users:Id", NAMESPACES)
226
+ username = extract_text(user_element, "./users:Username", NAMESPACES)
227
+ email = extract_text(user_element, "./users:Email", NAMESPACES)
228
+ full_name = extract_text(user_element, "./users:FullName", NAMESPACES)
229
+ role = extract_text(user_element, "./users:Role", NAMESPACES)
230
+ status = extract_text(user_element, "./users:Status", NAMESPACES)
231
+ created_at = extract_text(user_element, "./users:CreatedAt", NAMESPACES)
232
+ last_login = extract_text(user_element, "./users:LastLogin", NAMESPACES)
233
+
234
+ # Extract permissions array
235
+ permission_nodes = user_element.xpath(
236
+ "./users:Permissions/users:Permission", NAMESPACES
237
+ )
238
+ permissions = permission_nodes.map(&:text)
239
+
240
+ # Extract profile data
241
+ profile_element = user_element.at_xpath("./users:Profile", NAMESPACES)
242
+ profile = if profile_element
243
+ {
244
+ "Department" => extract_text(profile_element, "./users:Department",
245
+ NAMESPACES),
246
+ "Title" => extract_text(profile_element, "./users:Title",
247
+ NAMESPACES),
248
+ "Location" => extract_text(profile_element, "./users:Location",
249
+ NAMESPACES),
250
+ "PhoneNumber" => extract_text(profile_element, "./users:PhoneNumber",
251
+ NAMESPACES),
252
+ }
253
+ else
254
+ {}
255
+ end
256
+
257
+ User.new(
258
+ id: id,
259
+ username: username,
260
+ email: email,
261
+ full_name: full_name,
262
+ role: role,
263
+ status: status,
264
+ created_at: created_at,
265
+ last_login: last_login,
266
+ permissions: permissions,
267
+ profile: profile,
268
+ )
269
+ end
270
+
271
+ # Extract metadata from response
272
+ def extract_metadata(doc)
273
+ metadata_element = doc.at_xpath("//users:Metadata", NAMESPACES)
274
+ return {} unless metadata_element
275
+
276
+ {
277
+ response_time: extract_text(metadata_element, "./users:ResponseTime",
278
+ NAMESPACES),
279
+ server_version: extract_text(metadata_element, "./users:ServerVersion",
280
+ NAMESPACES),
281
+ cache_hit: extract_text(metadata_element, "./users:CacheHit", NAMESPACES),
282
+ }
283
+ end
284
+
285
+ # Helper method to safely extract text from XPath
286
+ def extract_text(node, xpath, namespaces = {})
287
+ element = node.at_xpath(xpath, namespaces)
288
+ element&.text&.strip || ""
289
+ end
290
+ end
291
+
292
+ # Demonstrate request building
293
+ def demonstrate_request_building
294
+ puts "=" * 80
295
+ puts "Building SOAP Requests"
296
+ puts "=" * 80
297
+ puts
298
+
299
+ client = SOAPClient.new
300
+
301
+ # Build GetUser request
302
+ puts "1. GetUser Request:"
303
+ puts "-" * 80
304
+ get_user_doc = client.build_get_user_request(1001, "session-abc-123")
305
+ puts get_user_doc.to_xml(indent: 2)
306
+ puts
307
+
308
+ # Build CreateUser request
309
+ puts "2. CreateUser Request:"
310
+ puts "-" * 80
311
+ create_user_doc = client.build_create_user_request(
312
+ "janedoe",
313
+ "jane.doe@example.com",
314
+ "Jane Doe",
315
+ "Developer",
316
+ )
317
+ puts create_user_doc.to_xml(indent: 2)
318
+ puts
319
+ end
320
+
321
+ # Demonstrate response parsing
322
+ def demonstrate_response_parsing(response_file)
323
+ puts "=" * 80
324
+ puts "Parsing SOAP Response"
325
+ puts "=" * 80
326
+ puts
327
+
328
+ # Read response XML
329
+ xml_content = File.read(response_file)
330
+
331
+ # Parse response
332
+ client = SOAPClient.new
333
+ response = begin
334
+ client.parse_response(xml_content)
335
+ rescue Moxml::ParseError => e
336
+ puts "Error parsing response: #{e.message}"
337
+ return
338
+ rescue Moxml::XPathError => e
339
+ puts "Error querying response: #{e.message}"
340
+ return
341
+ end
342
+
343
+ # Display response information
344
+ puts "Response Information:"
345
+ puts "-" * 80
346
+ puts response
347
+ puts
348
+
349
+ # Display user data if present
350
+ if response.data
351
+ puts "User Data:"
352
+ puts "-" * 80
353
+ puts response.data
354
+ puts
355
+ end
356
+
357
+ # Show success/failure
358
+ puts "Result: #{response.success? ? 'SUCCESS ✓' : 'FAILED ✗'}"
359
+ puts
360
+ end
361
+
362
+ # Main execution
363
+ if __FILE__ == $0
364
+ puts "SOAP API Client Example"
365
+ puts "=" * 80
366
+ puts
367
+
368
+ # Demonstrate building requests
369
+ demonstrate_request_building
370
+
371
+ # Demonstrate parsing responses
372
+ response_file = ARGV[0] || File.join(__dir__, "example_response.xml")
373
+
374
+ unless File.exist?(response_file)
375
+ puts "Error: Response file not found: #{response_file}"
376
+ puts "Usage: ruby api_client.rb [path/to/response.xml]"
377
+ exit 1
378
+ end
379
+
380
+ demonstrate_response_parsing(response_file)
381
+
382
+ # Summary
383
+ puts "=" * 80
384
+ puts "API Client Example Complete"
385
+ puts "=" * 80
386
+ puts
387
+ puts "Key Takeaways:"
388
+ puts " - Use Moxml::Builder for clean XML request construction"
389
+ puts " - Namespace handling is crucial for SOAP/XML APIs"
390
+ puts " - XPath with namespaces extracts response data efficiently"
391
+ puts " - Proper error handling ensures robust API interactions"
392
+ puts " - Structure data into Ruby objects for easy manipulation"
393
+ puts "=" * 80
394
+ end
@@ -0,0 +1,48 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
3
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema">
5
+ <soap:Header>
6
+ <AuthHeader xmlns="http://api.example.com/auth">
7
+ <SessionId>a1b2c3d4-e5f6-g7h8-i9j0-k1l2m3n4o5p6</SessionId>
8
+ <Timestamp>2024-10-30T10:00:00Z</Timestamp>
9
+ <RequestId>req-12345</RequestId>
10
+ </AuthHeader>
11
+ </soap:Header>
12
+ <soap:Body>
13
+ <GetUserResponse xmlns="http://api.example.com/users">
14
+ <Status>
15
+ <Code>200</Code>
16
+ <Message>Success</Message>
17
+ </Status>
18
+ <Result>
19
+ <User>
20
+ <Id>1001</Id>
21
+ <Username>johndoe</Username>
22
+ <Email>john.doe@example.com</Email>
23
+ <FullName>John Doe</FullName>
24
+ <Role>Administrator</Role>
25
+ <Status>Active</Status>
26
+ <CreatedAt>2024-01-15T08:30:00Z</CreatedAt>
27
+ <LastLogin>2024-10-29T14:22:00Z</LastLogin>
28
+ <Permissions>
29
+ <Permission>users.read</Permission>
30
+ <Permission>users.write</Permission>
31
+ <Permission>admin.access</Permission>
32
+ </Permissions>
33
+ <Profile>
34
+ <Department>Engineering</Department>
35
+ <Title>Senior Developer</Title>
36
+ <Location>San Francisco, CA</Location>
37
+ <PhoneNumber>+1-555-0123</PhoneNumber>
38
+ </Profile>
39
+ </User>
40
+ </Result>
41
+ <Metadata>
42
+ <ResponseTime>45</ResponseTime>
43
+ <ServerVersion>2.1.0</ServerVersion>
44
+ <CacheHit>false</CacheHit>
45
+ </Metadata>
46
+ </GetUserResponse>
47
+ </soap:Body>
48
+ </soap:Envelope>
@@ -0,0 +1,90 @@
1
+ # HeadedOx Demo
2
+
3
+ This example demonstrates the HeadedOx adapter, which combines:
4
+ - Ox's fast C-based XML parsing
5
+ - Moxml's comprehensive pure Ruby XPath 1.0 engine
6
+
7
+ ## What is HeadedOx?
8
+
9
+ HeadedOx is a hybrid adapter that provides:
10
+ - **Fast parsing:** Uses Ox's C-based parser for speed
11
+ - **Full XPath 1.0:** All 27 XPath functions and 6 common axes
12
+ - **Production ready:** 99.20% test pass rate (1,992/2,008 tests)
13
+ - **Pure Ruby XPath:** Debuggable implementation with expression caching
14
+ - **Best of both:** Combines Ox speed with comprehensive XPath support
15
+
16
+ ## Running the Demo
17
+
18
+ ### For Development (from gem source):
19
+ ```bash
20
+ # From the moxml root directory
21
+ bundle exec ruby examples/headed_ox_example/headed_ox_demo.rb
22
+ ```
23
+
24
+ Note: The example uses `require_relative` to load Moxml from source, which requires
25
+ `bundle exec` to properly resolve dependencies in development.
26
+
27
+ ### After Installation:
28
+ ```bash
29
+ # When using the installed gem
30
+ ruby headed_ox_demo.rb
31
+ ```
32
+
33
+ ## Features Demonstrated
34
+
35
+ 1. **Descendant queries** - `//book` syntax
36
+ 2. **Attribute selection** - `@price` syntax
37
+ 3. **Predicates** - `[@price < 20]` filtering
38
+ 4. **XPath functions** - count(), sum(), string(), contains()
39
+ 5. **Complex queries** - Combining multiple features
40
+ 6. **Variable binding** - `$var` support
41
+
42
+ ## Output
43
+
44
+ The demo shows HeadedOx executing various XPath queries on a sample library XML,
45
+ demonstrating the full range of XPath 1.0 capabilities.
46
+
47
+ ## Expected Output
48
+
49
+ ```
50
+ ============================================================
51
+ HeadedOx Demo - Comprehensive XPath on Fast Ox Parsing
52
+ ============================================================
53
+
54
+ 1. Find all books:
55
+ Found 3 books
56
+
57
+ 2. Get all prices:
58
+ Prices: 15.99, 25.99, 12.99
59
+
60
+ 3. Find cheap books (< $20):
61
+ - Programming Ruby: $15.99
62
+ - Programming JavaScript: $12.99
63
+
64
+ 4. XPath functions:
65
+ Total books: 3
66
+ Total price: $54.97
67
+ First title: Programming Ruby
68
+
69
+ 5. Books with 'Ruby' in title:
70
+ - Programming Ruby
71
+
72
+ 6. Using variables:
73
+ Books under $20: 2
74
+
75
+ ============================================================
76
+ HeadedOx provides full XPath 1.0 support!
77
+ ============================================================
78
+ ```
79
+
80
+ ## Why Choose HeadedOx?
81
+
82
+ Use HeadedOx when you need:
83
+ - Fast XML parsing (Ox's strength)
84
+ - Comprehensive XPath beyond basic locate()
85
+ - XPath 1.0 functions like count(), sum(), contains()
86
+ - Complex predicates and expressions
87
+ - Debuggable XPath implementation
88
+ - Production-ready stability (99.20% test coverage)
89
+
90
+ See `docs/HEADED_OX_LIMITATIONS.md` for detailed capabilities and known limitations.
@@ -0,0 +1,71 @@
1
+ require_relative "../../lib/moxml"
2
+
3
+ # HeadedOx Adapter Demo
4
+ # Demonstrates Ox's fast parsing + comprehensive XPath
5
+
6
+ # Sample XML
7
+ xml = <<~XML
8
+ <library>
9
+ <book id="1" price="15.99" year="2020">
10
+ <title>Programming Ruby</title>
11
+ <author>Matz</author>
12
+ <isbn>978-1234567890</isbn>
13
+ </book>
14
+ <book id="2" price="25.99" year="2021">
15
+ <title>Programming Python</title>
16
+ <author>Guido</author>
17
+ <isbn>978-0987654321</isbn>
18
+ </book>
19
+ <book id="3" price="12.99" year="2022">
20
+ <title>Programming JavaScript</title>
21
+ <author>Brendan</author>
22
+ <isbn>978-1122334455</isbn>
23
+ </book>
24
+ </library>
25
+ XML
26
+
27
+ # Initialize HeadedOx
28
+ context = Moxml.new(:headed_ox)
29
+ doc = context.parse(xml)
30
+
31
+ puts "=" * 60
32
+ puts "HeadedOx Demo - Comprehensive XPath on Fast Ox Parsing"
33
+ puts "=" * 60
34
+
35
+ # 1. Basic descendant queries
36
+ puts "\n1. Find all books:"
37
+ books = doc.xpath("//book")
38
+ puts "Found #{books.size} books"
39
+
40
+ # 2. Attribute selection
41
+ puts "\n2. Get all prices:"
42
+ prices = doc.xpath("//book/@price")
43
+ puts "Prices: #{prices.map(&:value).join(', ')}"
44
+
45
+ # 3. Predicates
46
+ puts "\n3. Find cheap books (< $20):"
47
+ cheap = doc.xpath("//book[@price < 20]")
48
+ cheap.each do |book|
49
+ puts " - #{book.xpath('title').first.text}: $#{book['price']}"
50
+ end
51
+
52
+ # 4. XPath functions
53
+ puts "\n4. XPath functions:"
54
+ puts " Total books: #{doc.xpath('count(//book)')}"
55
+ puts " Total price: $#{doc.xpath('sum(//book/@price)')}"
56
+ puts " First title: #{doc.xpath('string(//book[1]/title)')}"
57
+
58
+ # 5. Complex queries
59
+ puts "\n5. Books with 'Ruby' in title:"
60
+ ruby_books = doc.xpath('//book[contains(title, "Ruby")]')
61
+ ruby_books.each { |book| puts " - #{book.xpath('title').first.text}" }
62
+
63
+ # 6. Variable binding
64
+ puts "\n6. Using variables:"
65
+ max_price = 20
66
+ affordable = doc.xpath("//book[@price < $max]", { "max" => max_price })
67
+ puts "Books under $#{max_price}: #{affordable.size}"
68
+
69
+ puts "\n#{'=' * 60}"
70
+ puts "HeadedOx provides full XPath 1.0 support!"
71
+ puts "=" * 60