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.
- checksums.yaml +4 -4
- data/.github/workflows/dependent-repos.json +5 -0
- data/.github/workflows/dependent-tests.yml +20 -0
- data/.github/workflows/docs.yml +59 -0
- data/.github/workflows/rake.yml +10 -10
- data/.github/workflows/release.yml +5 -3
- data/.gitignore +37 -0
- data/.rubocop.yml +15 -7
- data/.rubocop_todo.yml +224 -43
- data/Gemfile +14 -9
- data/LICENSE.md +6 -2
- data/README.adoc +535 -373
- data/Rakefile +53 -0
- data/benchmarks/.gitignore +6 -0
- data/benchmarks/generate_report.rb +550 -0
- data/docs/Gemfile +13 -0
- data/docs/_config.yml +138 -0
- data/docs/_guides/advanced-features.adoc +87 -0
- data/docs/_guides/development-testing.adoc +165 -0
- data/docs/_guides/index.adoc +51 -0
- data/docs/_guides/modifying-xml.adoc +292 -0
- data/docs/_guides/parsing-xml.adoc +230 -0
- data/docs/_guides/sax-parsing.adoc +603 -0
- data/docs/_guides/working-with-documents.adoc +118 -0
- data/docs/_guides/xml-declaration.adoc +450 -0
- data/docs/_pages/adapter-compatibility.adoc +369 -0
- data/docs/_pages/adapters/headed-ox.adoc +237 -0
- data/docs/_pages/adapters/index.adoc +97 -0
- data/docs/_pages/adapters/libxml.adoc +285 -0
- data/docs/_pages/adapters/nokogiri.adoc +251 -0
- data/docs/_pages/adapters/oga.adoc +291 -0
- data/docs/_pages/adapters/ox.adoc +56 -0
- data/docs/_pages/adapters/rexml.adoc +292 -0
- data/docs/_pages/best-practices.adoc +429 -0
- data/docs/_pages/compatibility.adoc +467 -0
- data/docs/_pages/configuration.adoc +250 -0
- data/docs/_pages/error-handling.adoc +349 -0
- data/docs/_pages/headed-ox-limitations.adoc +574 -0
- data/docs/_pages/headed-ox.adoc +1025 -0
- data/docs/_pages/index.adoc +35 -0
- data/docs/_pages/installation.adoc +140 -0
- data/docs/_pages/node-api-reference.adoc +49 -0
- data/docs/_pages/performance.adoc +35 -0
- data/docs/_pages/quick-start.adoc +243 -0
- data/docs/_pages/thread-safety.adoc +28 -0
- data/docs/_references/document-api.adoc +407 -0
- data/docs/_references/index.adoc +48 -0
- data/docs/_tutorials/basic-usage.adoc +267 -0
- data/docs/_tutorials/builder-pattern.adoc +342 -0
- data/docs/_tutorials/index.adoc +33 -0
- data/docs/_tutorials/namespace-handling.adoc +324 -0
- data/docs/_tutorials/xpath-queries.adoc +358 -0
- data/docs/index.adoc +122 -0
- data/examples/README.md +124 -0
- data/examples/api_client/README.md +424 -0
- data/examples/api_client/api_client.rb +394 -0
- data/examples/api_client/example_response.xml +48 -0
- data/examples/headed_ox_example/README.md +90 -0
- data/examples/headed_ox_example/headed_ox_demo.rb +71 -0
- data/examples/rss_parser/README.md +194 -0
- data/examples/rss_parser/example_feed.xml +93 -0
- data/examples/rss_parser/rss_parser.rb +189 -0
- data/examples/sax_parsing/README.md +50 -0
- data/examples/sax_parsing/data_extractor.rb +75 -0
- data/examples/sax_parsing/example.xml +21 -0
- data/examples/sax_parsing/large_file.rb +78 -0
- data/examples/sax_parsing/simple_parser.rb +55 -0
- data/examples/web_scraper/README.md +352 -0
- data/examples/web_scraper/example_page.html +201 -0
- data/examples/web_scraper/web_scraper.rb +312 -0
- data/lib/moxml/adapter/base.rb +107 -28
- data/lib/moxml/adapter/customized_libxml/cdata.rb +28 -0
- data/lib/moxml/adapter/customized_libxml/comment.rb +24 -0
- data/lib/moxml/adapter/customized_libxml/declaration.rb +85 -0
- data/lib/moxml/adapter/customized_libxml/element.rb +39 -0
- data/lib/moxml/adapter/customized_libxml/node.rb +44 -0
- data/lib/moxml/adapter/customized_libxml/processing_instruction.rb +31 -0
- data/lib/moxml/adapter/customized_libxml/text.rb +27 -0
- data/lib/moxml/adapter/customized_oga/xml_generator.rb +1 -1
- data/lib/moxml/adapter/customized_ox/attribute.rb +28 -1
- data/lib/moxml/adapter/customized_rexml/formatter.rb +13 -8
- data/lib/moxml/adapter/headed_ox.rb +161 -0
- data/lib/moxml/adapter/libxml.rb +1564 -0
- data/lib/moxml/adapter/nokogiri.rb +156 -9
- data/lib/moxml/adapter/oga.rb +190 -15
- data/lib/moxml/adapter/ox.rb +322 -28
- data/lib/moxml/adapter/rexml.rb +157 -28
- data/lib/moxml/adapter.rb +21 -4
- data/lib/moxml/attribute.rb +6 -0
- data/lib/moxml/builder.rb +40 -4
- data/lib/moxml/config.rb +8 -3
- data/lib/moxml/context.rb +57 -2
- data/lib/moxml/declaration.rb +9 -0
- data/lib/moxml/doctype.rb +13 -1
- data/lib/moxml/document.rb +53 -6
- data/lib/moxml/document_builder.rb +34 -5
- data/lib/moxml/element.rb +71 -2
- data/lib/moxml/error.rb +175 -6
- data/lib/moxml/node.rb +155 -4
- data/lib/moxml/node_set.rb +34 -0
- data/lib/moxml/sax/block_handler.rb +194 -0
- data/lib/moxml/sax/element_handler.rb +124 -0
- data/lib/moxml/sax/handler.rb +113 -0
- data/lib/moxml/sax.rb +31 -0
- data/lib/moxml/version.rb +1 -1
- data/lib/moxml/xml_utils/encoder.rb +4 -4
- data/lib/moxml/xml_utils.rb +7 -4
- data/lib/moxml/xpath/ast/node.rb +159 -0
- data/lib/moxml/xpath/cache.rb +91 -0
- data/lib/moxml/xpath/compiler.rb +1770 -0
- data/lib/moxml/xpath/context.rb +26 -0
- data/lib/moxml/xpath/conversion.rb +124 -0
- data/lib/moxml/xpath/engine.rb +52 -0
- data/lib/moxml/xpath/errors.rb +101 -0
- data/lib/moxml/xpath/lexer.rb +304 -0
- data/lib/moxml/xpath/parser.rb +485 -0
- data/lib/moxml/xpath/ruby/generator.rb +269 -0
- data/lib/moxml/xpath/ruby/node.rb +193 -0
- data/lib/moxml/xpath.rb +37 -0
- data/lib/moxml.rb +5 -2
- data/moxml.gemspec +3 -1
- data/old-specs/moxml/adapter/customized_libxml/.gitkeep +6 -0
- data/spec/consistency/README.md +77 -0
- data/spec/{moxml/examples/adapter_spec.rb → consistency/adapter_parity_spec.rb} +4 -4
- data/spec/examples/README.md +75 -0
- data/spec/{support/shared_examples/examples/attribute.rb → examples/attribute_examples_spec.rb} +1 -1
- data/spec/{support/shared_examples/examples/basic_usage.rb → examples/basic_usage_spec.rb} +2 -2
- data/spec/{support/shared_examples/examples/namespace.rb → examples/namespace_examples_spec.rb} +3 -3
- data/spec/{support/shared_examples/examples/readme_examples.rb → examples/readme_examples_spec.rb} +6 -4
- data/spec/{support/shared_examples/examples/xpath.rb → examples/xpath_examples_spec.rb} +10 -6
- data/spec/integration/README.md +71 -0
- data/spec/{moxml/all_with_adapters_spec.rb → integration/all_adapters_spec.rb} +3 -2
- data/spec/integration/headed_ox_integration_spec.rb +326 -0
- data/spec/{support → integration}/shared_examples/edge_cases.rb +37 -10
- data/spec/integration/shared_examples/high_level/.gitkeep +0 -0
- data/spec/{support/shared_examples/context.rb → integration/shared_examples/high_level/context_behavior.rb} +2 -1
- data/spec/{support/shared_examples/integration.rb → integration/shared_examples/integration_workflows.rb} +23 -6
- data/spec/integration/shared_examples/node_wrappers/.gitkeep +0 -0
- data/spec/{support/shared_examples/cdata.rb → integration/shared_examples/node_wrappers/cdata_behavior.rb} +6 -1
- data/spec/{support/shared_examples/comment.rb → integration/shared_examples/node_wrappers/comment_behavior.rb} +2 -1
- data/spec/{support/shared_examples/declaration.rb → integration/shared_examples/node_wrappers/declaration_behavior.rb} +5 -5
- data/spec/{support/shared_examples/doctype.rb → integration/shared_examples/node_wrappers/doctype_behavior.rb} +2 -2
- data/spec/{support/shared_examples/document.rb → integration/shared_examples/node_wrappers/document_behavior.rb} +1 -1
- data/spec/{support/shared_examples/node.rb → integration/shared_examples/node_wrappers/node_behavior.rb} +9 -2
- data/spec/{support/shared_examples/node_set.rb → integration/shared_examples/node_wrappers/node_set_behavior.rb} +1 -18
- data/spec/{support/shared_examples/processing_instruction.rb → integration/shared_examples/node_wrappers/processing_instruction_behavior.rb} +6 -2
- data/spec/moxml/README.md +41 -0
- data/spec/moxml/adapter/.gitkeep +0 -0
- data/spec/moxml/adapter/README.md +61 -0
- data/spec/moxml/adapter/base_spec.rb +27 -0
- data/spec/moxml/adapter/headed_ox_spec.rb +311 -0
- data/spec/moxml/adapter/libxml_spec.rb +14 -0
- data/spec/moxml/adapter/ox_spec.rb +9 -8
- data/spec/moxml/adapter/shared_examples/.gitkeep +0 -0
- data/spec/{support/shared_examples/xml_adapter.rb → moxml/adapter/shared_examples/adapter_contract.rb} +39 -12
- data/spec/moxml/adapter_spec.rb +16 -0
- data/spec/moxml/attribute_spec.rb +30 -0
- data/spec/moxml/builder_spec.rb +33 -0
- data/spec/moxml/cdata_spec.rb +31 -0
- data/spec/moxml/comment_spec.rb +31 -0
- data/spec/moxml/config_spec.rb +3 -3
- data/spec/moxml/context_spec.rb +28 -0
- data/spec/moxml/declaration_preservation_spec.rb +217 -0
- data/spec/moxml/declaration_spec.rb +36 -0
- data/spec/moxml/doctype_spec.rb +33 -0
- data/spec/moxml/document_builder_spec.rb +30 -0
- data/spec/moxml/document_spec.rb +105 -0
- data/spec/moxml/element_spec.rb +143 -0
- data/spec/moxml/error_spec.rb +266 -22
- data/spec/{moxml_spec.rb → moxml/moxml_spec.rb} +9 -9
- data/spec/moxml/namespace_spec.rb +32 -0
- data/spec/moxml/node_set_spec.rb +39 -0
- data/spec/moxml/node_spec.rb +37 -0
- data/spec/moxml/processing_instruction_spec.rb +34 -0
- data/spec/moxml/sax_spec.rb +1067 -0
- data/spec/moxml/text_spec.rb +31 -0
- data/spec/moxml/version_spec.rb +14 -0
- data/spec/moxml/xml_utils/.gitkeep +0 -0
- data/spec/moxml/xml_utils/encoder_spec.rb +27 -0
- data/spec/moxml/xml_utils_spec.rb +49 -0
- data/spec/moxml/xpath/ast/node_spec.rb +83 -0
- data/spec/moxml/xpath/axes_spec.rb +296 -0
- data/spec/moxml/xpath/cache_spec.rb +358 -0
- data/spec/moxml/xpath/compiler_spec.rb +406 -0
- data/spec/moxml/xpath/context_spec.rb +210 -0
- data/spec/moxml/xpath/conversion_spec.rb +365 -0
- data/spec/moxml/xpath/fixtures/sample.xml +25 -0
- data/spec/moxml/xpath/functions/boolean_functions_spec.rb +114 -0
- data/spec/moxml/xpath/functions/node_functions_spec.rb +145 -0
- data/spec/moxml/xpath/functions/numeric_functions_spec.rb +164 -0
- data/spec/moxml/xpath/functions/position_functions_spec.rb +93 -0
- data/spec/moxml/xpath/functions/special_functions_spec.rb +89 -0
- data/spec/moxml/xpath/functions/string_functions_spec.rb +381 -0
- data/spec/moxml/xpath/lexer_spec.rb +488 -0
- data/spec/moxml/xpath/parser_integration_spec.rb +210 -0
- data/spec/moxml/xpath/parser_spec.rb +364 -0
- data/spec/moxml/xpath/ruby/generator_spec.rb +421 -0
- data/spec/moxml/xpath/ruby/node_spec.rb +291 -0
- data/spec/moxml/xpath_capabilities_spec.rb +199 -0
- data/spec/moxml/xpath_spec.rb +77 -0
- data/spec/performance/README.md +83 -0
- data/spec/performance/benchmark_spec.rb +64 -0
- data/spec/{support/shared_examples/examples/memory.rb → performance/memory_usage_spec.rb} +4 -1
- data/spec/{support/shared_examples/examples/thread_safety.rb → performance/thread_safety_spec.rb} +3 -1
- data/spec/performance/xpath_benchmark_spec.rb +259 -0
- data/spec/spec_helper.rb +58 -1
- data/spec/support/xml_matchers.rb +1 -1
- metadata +178 -34
- data/spec/support/shared_examples/examples/benchmark_spec.rb +0 -51
- /data/spec/{support/shared_examples/builder.rb → integration/shared_examples/high_level/builder_behavior.rb} +0 -0
- /data/spec/{support/shared_examples/document_builder.rb → integration/shared_examples/high_level/document_builder_behavior.rb} +0 -0
- /data/spec/{support/shared_examples/attribute.rb → integration/shared_examples/node_wrappers/attribute_behavior.rb} +0 -0
- /data/spec/{support/shared_examples/element.rb → integration/shared_examples/node_wrappers/element_behavior.rb} +0 -0
- /data/spec/{support/shared_examples/namespace.rb → integration/shared_examples/node_wrappers/namespace_behavior.rb} +0 -0
- /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
|