philiprehberger-xml_builder 0.1.0 → 0.2.0
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/CHANGELOG.md +15 -0
- data/README.md +93 -4
- data/lib/philiprehberger/xml_builder/document.rb +92 -1
- data/lib/philiprehberger/xml_builder/node.rb +2 -1
- data/lib/philiprehberger/xml_builder/version.rb +1 -1
- data/lib/philiprehberger/xml_builder.rb +16 -0
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 02c1eeed30ddee688b0bee7143b27ad18d90c649a1b1d1d98d5d7f72f437e4e8
|
|
4
|
+
data.tar.gz: 3b01559a4ebfdf722c7992a1f6f844ad3ae40ff809e287ca2925f7297274ddcb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e9b4ebc5605369a8f6e0ee94238ea1797d56728ee32f63d3254808240f789c8637cce12b9f386112ebd2115aa4e558b2b3601eb130a436d84e04e28bfd9e11bc
|
|
7
|
+
data.tar.gz: 4ae1e0ecdc135accc90eec5704ec175e5344cf98b65ee851d98937f478f9cbfcb000b62d167b03dfc1b2ac77ccd4898f9c669885122d7873dc5cca51a827e8f3
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.2.0] - 2026-03-28
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- XML namespace support with `namespace` and `namespace_tag` methods for prefix declarations and namespace-aware elements
|
|
15
|
+
- SOAP envelope builder with `soap_envelope` DSL and `build_soap` convenience method supporting SOAP 1.1 and 1.2
|
|
16
|
+
- XML fragment composition with `append` (merge Document objects) and `insert_fragment` (insert raw XML strings)
|
|
17
|
+
- Support for double-underscore to colon conversion in symbol attribute keys (e.g. `xmlns__soap:` becomes `xmlns:soap=`)
|
|
18
|
+
|
|
19
|
+
## [0.1.1] - 2026-03-26
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
|
|
23
|
+
- Add GitHub funding configuration
|
|
24
|
+
|
|
10
25
|
## [0.1.0] - 2026-03-26
|
|
11
26
|
|
|
12
27
|
### Added
|
data/README.md
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
# philiprehberger-xml_builder
|
|
2
2
|
|
|
3
|
-
[](https://github.com/philiprehberger/rb-xml-builder/actions/workflows/ci.yml)
|
|
4
|
-
[](https://rubygems.org/gems/philiprehberger-xml_builder)
|
|
5
|
-
[](LICENSE)
|
|
6
|
-
[](https://github.com/sponsors/philiprehberger)
|
|
3
|
+
[](https://github.com/philiprehberger/rb-xml-builder/actions/workflows/ci.yml) [](https://rubygems.org/gems/philiprehberger-xml_builder) [](https://github.com/philiprehberger/rb-xml-builder/releases) [](https://github.com/philiprehberger/rb-xml-builder/commits/main) [](LICENSE) [](https://github.com/philiprehberger/rb-xml-builder/issues) [](https://github.com/philiprehberger/rb-xml-builder/issues) [](https://github.com/sponsors/philiprehberger)
|
|
7
4
|
|
|
8
5
|
Lightweight XML builder DSL without Nokogiri dependency
|
|
9
6
|
|
|
@@ -27,6 +24,8 @@ gem install philiprehberger-xml_builder
|
|
|
27
24
|
|
|
28
25
|
## Usage
|
|
29
26
|
|
|
27
|
+
### Basic Elements
|
|
28
|
+
|
|
30
29
|
```ruby
|
|
31
30
|
require "philiprehberger/xml_builder"
|
|
32
31
|
|
|
@@ -97,6 +96,86 @@ xml = Philiprehberger::XmlBuilder.build do |doc|
|
|
|
97
96
|
end
|
|
98
97
|
```
|
|
99
98
|
|
|
99
|
+
### XML Namespaces
|
|
100
|
+
|
|
101
|
+
Register namespace prefixes and create namespace-aware elements:
|
|
102
|
+
|
|
103
|
+
```ruby
|
|
104
|
+
xml = Philiprehberger::XmlBuilder.build do |doc|
|
|
105
|
+
doc.namespace(:soap, "http://schemas.xmlsoap.org/soap/envelope/")
|
|
106
|
+
doc.namespace(:wsse, "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd")
|
|
107
|
+
|
|
108
|
+
doc.namespace_tag(:soap, :Envelope) do
|
|
109
|
+
doc.namespace_tag(:soap, :Header) do
|
|
110
|
+
doc.namespace_tag(:wsse, :Security)
|
|
111
|
+
end
|
|
112
|
+
doc.namespace_tag(:soap, :Body)
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
You can also use string tag names directly:
|
|
118
|
+
|
|
119
|
+
```ruby
|
|
120
|
+
xml = Philiprehberger::XmlBuilder.build do |doc|
|
|
121
|
+
doc.tag("soap:Envelope", "xmlns:soap" => "http://schemas.xmlsoap.org/soap/envelope/") do
|
|
122
|
+
doc.tag("soap:Body")
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### SOAP Envelope Builder
|
|
128
|
+
|
|
129
|
+
Build SOAP 1.1 or 1.2 envelopes with a convenience DSL:
|
|
130
|
+
|
|
131
|
+
```ruby
|
|
132
|
+
xml = Philiprehberger::XmlBuilder.build do |doc|
|
|
133
|
+
doc.soap_envelope(version: "1.1") do |header, body|
|
|
134
|
+
header << ->(d) { d.tag("auth") { d.text("token123") } }
|
|
135
|
+
body << ->(d) { d.tag("GetPrice") { d.text("Widget") } }
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Or use the top-level shortcut:
|
|
141
|
+
|
|
142
|
+
```ruby
|
|
143
|
+
xml = Philiprehberger::XmlBuilder.build_soap(soap_version: "1.2") do |header, body|
|
|
144
|
+
body << ->(d) { d.tag("GetStockPrice") { d.tag("Symbol") { d.text("AAPL") } } }
|
|
145
|
+
end
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### XML Fragment Composition
|
|
149
|
+
|
|
150
|
+
Combine separately built document fragments:
|
|
151
|
+
|
|
152
|
+
```ruby
|
|
153
|
+
# Build fragments independently
|
|
154
|
+
header = Philiprehberger::XmlBuilder::Document.new
|
|
155
|
+
header.tag(:title) { header.text("My Document") }
|
|
156
|
+
|
|
157
|
+
body = Philiprehberger::XmlBuilder::Document.new
|
|
158
|
+
body.tag(:paragraph) { body.text("Hello world") }
|
|
159
|
+
|
|
160
|
+
# Compose into a single document
|
|
161
|
+
xml = Philiprehberger::XmlBuilder.build do |doc|
|
|
162
|
+
doc.tag(:document) do
|
|
163
|
+
doc.tag(:header) { doc.append(header) }
|
|
164
|
+
doc.tag(:body) { doc.append(body) }
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Insert raw XML fragment strings:
|
|
170
|
+
|
|
171
|
+
```ruby
|
|
172
|
+
xml = Philiprehberger::XmlBuilder.build do |doc|
|
|
173
|
+
doc.tag(:root) do
|
|
174
|
+
doc.insert_fragment('<existing>data</existing>')
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
```
|
|
178
|
+
|
|
100
179
|
## API
|
|
101
180
|
|
|
102
181
|
### `Philiprehberger::XmlBuilder`
|
|
@@ -104,6 +183,7 @@ end
|
|
|
104
183
|
| Method | Description |
|
|
105
184
|
|--------|-------------|
|
|
106
185
|
| `.build(encoding: "UTF-8", version: "1.0") { \|doc\| ... }` | Build an XML document and return the string |
|
|
186
|
+
| `.build_soap(soap_version: "1.1", encoding: "UTF-8", version: "1.0") { \|header, body\| ... }` | Build a SOAP envelope document |
|
|
107
187
|
|
|
108
188
|
### `Document`
|
|
109
189
|
|
|
@@ -115,6 +195,11 @@ end
|
|
|
115
195
|
| `#comment(text)` | Add an XML comment |
|
|
116
196
|
| `#processing_instruction(target, content)` | Add a processing instruction |
|
|
117
197
|
| `#raw(string)` | Add raw unescaped XML |
|
|
198
|
+
| `#namespace(prefix, uri)` | Register an XML namespace prefix and URI |
|
|
199
|
+
| `#namespace_tag(prefix, name, attributes = {}) { ... }` | Add a namespace-prefixed element with auto xmlns |
|
|
200
|
+
| `#soap_envelope(version: "1.1") { \|header, body\| ... }` | Build a SOAP envelope with Header and Body |
|
|
201
|
+
| `#append(other_document)` | Append children from another Document |
|
|
202
|
+
| `#insert_fragment(xml_string)` | Insert a raw XML fragment string |
|
|
118
203
|
| `#to_s` | Render compact XML string |
|
|
119
204
|
| `#to_xml(indent: nil)` | Render XML with optional indentation |
|
|
120
205
|
|
|
@@ -132,6 +217,10 @@ bundle exec rspec
|
|
|
132
217
|
bundle exec rubocop
|
|
133
218
|
```
|
|
134
219
|
|
|
220
|
+
## Support
|
|
221
|
+
|
|
222
|
+
[](https://linkedin.com/in/philiprehberger) [](https://github.com/philiprehberger?tab=repositories)
|
|
223
|
+
|
|
135
224
|
## License
|
|
136
225
|
|
|
137
226
|
[MIT](LICENSE)
|
|
@@ -6,7 +6,7 @@ module Philiprehberger
|
|
|
6
6
|
#
|
|
7
7
|
# Used as the context object inside XmlBuilder.build blocks.
|
|
8
8
|
class Document
|
|
9
|
-
attr_reader :version, :encoding
|
|
9
|
+
attr_reader :version, :encoding, :children
|
|
10
10
|
|
|
11
11
|
# @param version [String] XML version for the declaration
|
|
12
12
|
# @param encoding [String] XML encoding for the declaration
|
|
@@ -15,6 +15,7 @@ module Philiprehberger
|
|
|
15
15
|
@encoding = encoding
|
|
16
16
|
@children = []
|
|
17
17
|
@node_stack = []
|
|
18
|
+
@namespaces = {}
|
|
18
19
|
end
|
|
19
20
|
|
|
20
21
|
# Add an XML element with optional attributes and nested children.
|
|
@@ -99,6 +100,96 @@ module Philiprehberger
|
|
|
99
100
|
parts.join
|
|
100
101
|
end
|
|
101
102
|
|
|
103
|
+
# Register an XML namespace prefix and URI.
|
|
104
|
+
#
|
|
105
|
+
# Registered namespaces are automatically added as xmlns attributes
|
|
106
|
+
# when using namespace_tag.
|
|
107
|
+
#
|
|
108
|
+
# @param prefix [String, Symbol] the namespace prefix
|
|
109
|
+
# @param uri [String] the namespace URI
|
|
110
|
+
# @return [void]
|
|
111
|
+
def namespace(prefix, uri)
|
|
112
|
+
@namespaces[prefix.to_s] = uri
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Add a namespace-prefixed element.
|
|
116
|
+
#
|
|
117
|
+
# Automatically includes the xmlns declaration for the prefix if it was
|
|
118
|
+
# registered via #namespace and this is the first use in the current scope.
|
|
119
|
+
#
|
|
120
|
+
# @param prefix [String, Symbol] the namespace prefix
|
|
121
|
+
# @param name [String, Symbol] the local element name
|
|
122
|
+
# @param attributes [Hash] additional element attributes
|
|
123
|
+
# @yield optional block for adding child elements
|
|
124
|
+
# @return [Node] the created node
|
|
125
|
+
def namespace_tag(prefix, name, attributes = {}, &)
|
|
126
|
+
prefixed_name = "#{prefix}:#{name}"
|
|
127
|
+
uri = @namespaces[prefix.to_s]
|
|
128
|
+
attrs = if uri
|
|
129
|
+
{ "xmlns:#{prefix}" => uri }.merge(attributes)
|
|
130
|
+
else
|
|
131
|
+
attributes
|
|
132
|
+
end
|
|
133
|
+
tag(prefixed_name, attrs, &)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Build a SOAP envelope using a block-based DSL.
|
|
137
|
+
#
|
|
138
|
+
# Supports SOAP 1.1 (default) and 1.2. Automatically sets the correct
|
|
139
|
+
# namespace URI and creates the Envelope, Header, and Body elements.
|
|
140
|
+
#
|
|
141
|
+
# @param version [String] SOAP version: "1.1" or "1.2"
|
|
142
|
+
# @yield [header, body] yields two procs for adding header and body content
|
|
143
|
+
# @return [void]
|
|
144
|
+
def soap_envelope(version: '1.1')
|
|
145
|
+
uri = case version
|
|
146
|
+
when '1.1' then 'http://schemas.xmlsoap.org/soap/envelope/'
|
|
147
|
+
when '1.2' then 'http://www.w3.org/2003/05/soap-envelope'
|
|
148
|
+
else
|
|
149
|
+
raise Error, "Unsupported SOAP version: #{version}. Use '1.1' or '1.2'."
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
header_children = []
|
|
153
|
+
body_children = []
|
|
154
|
+
|
|
155
|
+
yield(header_children, body_children) if block_given?
|
|
156
|
+
|
|
157
|
+
tag('soap:Envelope', 'xmlns:soap' => uri) do
|
|
158
|
+
tag('soap:Header') do
|
|
159
|
+
header_children.each { |child_block| child_block.call(self) }
|
|
160
|
+
end
|
|
161
|
+
tag('soap:Body') do
|
|
162
|
+
body_children.each { |child_block| child_block.call(self) }
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Append children from another Document into this document.
|
|
168
|
+
#
|
|
169
|
+
# Copies all top-level children from the source document into the current
|
|
170
|
+
# insertion point (either the document root or the current parent element).
|
|
171
|
+
#
|
|
172
|
+
# @param other [Document] the source document whose children to import
|
|
173
|
+
# @return [void]
|
|
174
|
+
def append(other)
|
|
175
|
+
raise Error, 'append expects a Document' unless other.is_a?(Document)
|
|
176
|
+
|
|
177
|
+
other.children.each do |child|
|
|
178
|
+
current_parent.push(child)
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# Insert a raw XML fragment string into the current position.
|
|
183
|
+
#
|
|
184
|
+
# This is an alias for #raw, provided for semantic clarity when composing
|
|
185
|
+
# fragments.
|
|
186
|
+
#
|
|
187
|
+
# @param xml_string [String] the XML fragment to insert
|
|
188
|
+
# @return [void]
|
|
189
|
+
def insert_fragment(xml_string)
|
|
190
|
+
raw(xml_string)
|
|
191
|
+
end
|
|
192
|
+
|
|
102
193
|
# Support method_missing for DSL-style tag creation.
|
|
103
194
|
#
|
|
104
195
|
# @example
|
|
@@ -50,7 +50,8 @@ module Philiprehberger
|
|
|
50
50
|
return '' if @attributes.empty?
|
|
51
51
|
|
|
52
52
|
pairs = @attributes.map do |key, value|
|
|
53
|
-
|
|
53
|
+
attr_name = key.to_s.gsub('__', ':')
|
|
54
|
+
" #{attr_name}=\"#{Escaper.escape(value)}\""
|
|
54
55
|
end
|
|
55
56
|
pairs.join
|
|
56
57
|
end
|
|
@@ -20,5 +20,21 @@ module Philiprehberger
|
|
|
20
20
|
block.call(doc)
|
|
21
21
|
doc.to_s
|
|
22
22
|
end
|
|
23
|
+
|
|
24
|
+
# Build a SOAP envelope document.
|
|
25
|
+
#
|
|
26
|
+
# Convenience wrapper around Document#soap_envelope that creates
|
|
27
|
+
# a full XML document with the proper SOAP structure.
|
|
28
|
+
#
|
|
29
|
+
# @param soap_version [String] SOAP version: "1.1" or "1.2"
|
|
30
|
+
# @param encoding [String] XML encoding declaration (default: "UTF-8")
|
|
31
|
+
# @param version [String] XML version declaration (default: "1.0")
|
|
32
|
+
# @yield [header, body] yields two arrays; push lambdas that accept a doc
|
|
33
|
+
# @return [String] the rendered SOAP XML string
|
|
34
|
+
def self.build_soap(soap_version: '1.1', encoding: 'UTF-8', version: '1.0', &block)
|
|
35
|
+
doc = Document.new(version: version, encoding: encoding)
|
|
36
|
+
doc.soap_envelope(version: soap_version, &block)
|
|
37
|
+
doc.to_s
|
|
38
|
+
end
|
|
23
39
|
end
|
|
24
40
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: philiprehberger-xml_builder
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Philip Rehberger
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-03-
|
|
11
|
+
date: 2026-03-28 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
13
|
description: Programmatic XML construction with a clean DSL, auto-escaping, CDATA,
|
|
14
14
|
comments, processing instructions, and pretty printing. Zero dependencies.
|