philiprehberger-xml_builder 0.1.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 +7 -0
- data/CHANGELOG.md +24 -0
- data/LICENSE +21 -0
- data/README.md +137 -0
- data/lib/philiprehberger/xml_builder/document.rb +155 -0
- data/lib/philiprehberger/xml_builder/escaper.rb +26 -0
- data/lib/philiprehberger/xml_builder/node.rb +74 -0
- data/lib/philiprehberger/xml_builder/version.rb +7 -0
- data/lib/philiprehberger/xml_builder.rb +24 -0
- metadata +57 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 922733a983681c827ed0fc8e94454e002abf37679785f1daba0153b9fff1dc01
|
|
4
|
+
data.tar.gz: 6826f30ce35d4c783862d55aee71819b643a0c41e1636d30d774afaba8928e37
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 2a1c52baeb6dd26956f6d6b0662742cc61872bc1a27cd45e3512bc99bd0db1486e30953cecf7661540dd66097b68e9cd1083b75d33d5aa90f0bbd34ea658ba19
|
|
7
|
+
data.tar.gz: 4eb32107f4e5007bb7f6afea092cfbd25f927fc73bd4251d81384ae97d46d7931e858295ca0f7970d8c28111d84f55bc697448069ec03dba7ffe32ef1711e627
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this gem will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.1.0] - 2026-03-26
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- Initial release
|
|
14
|
+
- Block-based DSL for building XML documents
|
|
15
|
+
- XML declaration with configurable version and encoding
|
|
16
|
+
- Element creation with attributes via `tag` method
|
|
17
|
+
- Escaped text content via `text` method
|
|
18
|
+
- CDATA sections via `cdata` method
|
|
19
|
+
- XML comments via `comment` method
|
|
20
|
+
- Processing instructions via `processing_instruction` method
|
|
21
|
+
- Raw XML insertion via `raw` method
|
|
22
|
+
- method_missing DSL for natural element creation
|
|
23
|
+
- Pretty printing with configurable indentation via `to_xml(indent:)`
|
|
24
|
+
- XML entity escaping for all five standard entities (&, <, >, ", ')
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 philiprehberger
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# philiprehberger-xml_builder
|
|
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)
|
|
7
|
+
|
|
8
|
+
Lightweight XML builder DSL without Nokogiri dependency
|
|
9
|
+
|
|
10
|
+
## Requirements
|
|
11
|
+
|
|
12
|
+
- Ruby >= 3.1
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
Add to your Gemfile:
|
|
17
|
+
|
|
18
|
+
```ruby
|
|
19
|
+
gem "philiprehberger-xml_builder"
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Or install directly:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
gem install philiprehberger-xml_builder
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Usage
|
|
29
|
+
|
|
30
|
+
```ruby
|
|
31
|
+
require "philiprehberger/xml_builder"
|
|
32
|
+
|
|
33
|
+
xml = Philiprehberger::XmlBuilder.build do |doc|
|
|
34
|
+
doc.tag(:root) do
|
|
35
|
+
doc.tag(:item, id: "1") { doc.text("Hello") }
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
puts xml
|
|
40
|
+
# <?xml version="1.0" encoding="UTF-8"?><root><item id="1">Hello</item></root>
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Method Missing DSL
|
|
44
|
+
|
|
45
|
+
Use method names directly as tag names for a cleaner syntax:
|
|
46
|
+
|
|
47
|
+
```ruby
|
|
48
|
+
xml = Philiprehberger::XmlBuilder.build do |doc|
|
|
49
|
+
doc.person(name: "John") do
|
|
50
|
+
doc.age("30")
|
|
51
|
+
doc.email("john@example.com")
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
# <person name="John"><age>30</age><email>john@example.com</email></person>
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### CDATA and Comments
|
|
58
|
+
|
|
59
|
+
```ruby
|
|
60
|
+
xml = Philiprehberger::XmlBuilder.build do |doc|
|
|
61
|
+
doc.tag(:root) do
|
|
62
|
+
doc.comment("Generated XML")
|
|
63
|
+
doc.tag(:script) { doc.cdata('var x = 1 < 2;') }
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Processing Instructions
|
|
69
|
+
|
|
70
|
+
```ruby
|
|
71
|
+
xml = Philiprehberger::XmlBuilder.build do |doc|
|
|
72
|
+
doc.processing_instruction("xml-stylesheet", 'type="text/xsl" href="style.xsl"')
|
|
73
|
+
doc.tag(:root) { doc.text("content") }
|
|
74
|
+
end
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Pretty Printing
|
|
78
|
+
|
|
79
|
+
```ruby
|
|
80
|
+
doc = Philiprehberger::XmlBuilder::Document.new
|
|
81
|
+
doc.tag(:root) do
|
|
82
|
+
doc.tag(:child) { doc.text("value") }
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
puts doc.to_xml(indent: 2)
|
|
86
|
+
# <?xml version="1.0" encoding="UTF-8"?>
|
|
87
|
+
# <root>
|
|
88
|
+
# <child>value</child>
|
|
89
|
+
# </root>
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Raw XML
|
|
93
|
+
|
|
94
|
+
```ruby
|
|
95
|
+
xml = Philiprehberger::XmlBuilder.build do |doc|
|
|
96
|
+
doc.tag(:root) { doc.raw("<pre>formatted</pre>") }
|
|
97
|
+
end
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## API
|
|
101
|
+
|
|
102
|
+
### `Philiprehberger::XmlBuilder`
|
|
103
|
+
|
|
104
|
+
| Method | Description |
|
|
105
|
+
|--------|-------------|
|
|
106
|
+
| `.build(encoding: "UTF-8", version: "1.0") { \|doc\| ... }` | Build an XML document and return the string |
|
|
107
|
+
|
|
108
|
+
### `Document`
|
|
109
|
+
|
|
110
|
+
| Method | Description |
|
|
111
|
+
|--------|-------------|
|
|
112
|
+
| `#tag(name, attributes = {}) { ... }` | Add an element with optional attributes and children |
|
|
113
|
+
| `#text(content)` | Add escaped text content |
|
|
114
|
+
| `#cdata(content)` | Add a CDATA section |
|
|
115
|
+
| `#comment(text)` | Add an XML comment |
|
|
116
|
+
| `#processing_instruction(target, content)` | Add a processing instruction |
|
|
117
|
+
| `#raw(string)` | Add raw unescaped XML |
|
|
118
|
+
| `#to_s` | Render compact XML string |
|
|
119
|
+
| `#to_xml(indent: nil)` | Render XML with optional indentation |
|
|
120
|
+
|
|
121
|
+
### `Escaper`
|
|
122
|
+
|
|
123
|
+
| Method | Description |
|
|
124
|
+
|--------|-------------|
|
|
125
|
+
| `.escape(text)` | Escape XML entities (&, <, >, ", ') |
|
|
126
|
+
|
|
127
|
+
## Development
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
bundle install
|
|
131
|
+
bundle exec rspec
|
|
132
|
+
bundle exec rubocop
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## License
|
|
136
|
+
|
|
137
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Philiprehberger
|
|
4
|
+
module XmlBuilder
|
|
5
|
+
# Accumulates XML nodes and renders the final document.
|
|
6
|
+
#
|
|
7
|
+
# Used as the context object inside XmlBuilder.build blocks.
|
|
8
|
+
class Document
|
|
9
|
+
attr_reader :version, :encoding
|
|
10
|
+
|
|
11
|
+
# @param version [String] XML version for the declaration
|
|
12
|
+
# @param encoding [String] XML encoding for the declaration
|
|
13
|
+
def initialize(version: '1.0', encoding: 'UTF-8')
|
|
14
|
+
@version = version
|
|
15
|
+
@encoding = encoding
|
|
16
|
+
@children = []
|
|
17
|
+
@node_stack = []
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Add an XML element with optional attributes and nested children.
|
|
21
|
+
#
|
|
22
|
+
# @param name [String, Symbol] the element tag name
|
|
23
|
+
# @param attributes [Hash] element attributes
|
|
24
|
+
# @yield optional block for adding child elements
|
|
25
|
+
# @return [Node] the created node
|
|
26
|
+
def tag(name, attributes = {}, &block)
|
|
27
|
+
node = Node.new(name, attributes)
|
|
28
|
+
|
|
29
|
+
if block
|
|
30
|
+
@node_stack.push(node)
|
|
31
|
+
block.call
|
|
32
|
+
@node_stack.pop
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
current_parent.push(node)
|
|
36
|
+
node
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Add escaped text content to the current element.
|
|
40
|
+
#
|
|
41
|
+
# @param content [String] the text content to escape and add
|
|
42
|
+
# @return [void]
|
|
43
|
+
def text(content)
|
|
44
|
+
current_parent.push(Escaper.escape(content.to_s))
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Add a CDATA section.
|
|
48
|
+
#
|
|
49
|
+
# @param content [String] the CDATA content (must not contain "]]>")
|
|
50
|
+
# @return [void]
|
|
51
|
+
def cdata(content)
|
|
52
|
+
current_parent.push("<![CDATA[#{content}]]>")
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Add an XML comment.
|
|
56
|
+
#
|
|
57
|
+
# @param text [String] the comment text
|
|
58
|
+
# @return [void]
|
|
59
|
+
def comment(text)
|
|
60
|
+
current_parent.push("<!-- #{text} -->")
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Add a processing instruction.
|
|
64
|
+
#
|
|
65
|
+
# @param target [String] the PI target
|
|
66
|
+
# @param content [String] the PI content
|
|
67
|
+
# @return [void]
|
|
68
|
+
def processing_instruction(target, content)
|
|
69
|
+
current_parent.push("<?#{target} #{content}?>")
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Add raw XML content without escaping.
|
|
73
|
+
#
|
|
74
|
+
# @param string [String] raw XML string
|
|
75
|
+
# @return [void]
|
|
76
|
+
def raw(string)
|
|
77
|
+
current_parent.push(string.to_s)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Render the document as a compact XML string (no indentation).
|
|
81
|
+
#
|
|
82
|
+
# @return [String] the rendered XML document
|
|
83
|
+
def to_s
|
|
84
|
+
to_xml
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Render the document as an XML string with optional indentation.
|
|
88
|
+
#
|
|
89
|
+
# @param indent [Integer, nil] number of spaces per indentation level, or nil for compact output
|
|
90
|
+
# @return [String] the rendered XML document
|
|
91
|
+
def to_xml(indent: nil)
|
|
92
|
+
parts = ["<?xml version=\"#{@version}\" encoding=\"#{@encoding}\"?>"]
|
|
93
|
+
parts << (indent ? "\n" : '')
|
|
94
|
+
|
|
95
|
+
@children.each do |child|
|
|
96
|
+
parts << render_child(child, indent: indent, level: 0)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
parts.join
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Support method_missing for DSL-style tag creation.
|
|
103
|
+
#
|
|
104
|
+
# @example
|
|
105
|
+
# xml.person(name: "John") { xml.age("30") }
|
|
106
|
+
# # => <person name="John"><age>30</age></person>
|
|
107
|
+
def method_missing(method_name, *args, &block)
|
|
108
|
+
first_arg = args.first
|
|
109
|
+
attributes = {}
|
|
110
|
+
text_content = nil
|
|
111
|
+
|
|
112
|
+
if first_arg.is_a?(Hash)
|
|
113
|
+
attributes = first_arg
|
|
114
|
+
elsif first_arg
|
|
115
|
+
text_content = first_arg.to_s
|
|
116
|
+
attributes = args[1] if args[1].is_a?(Hash)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
if text_content
|
|
120
|
+
tag(method_name, attributes) { text(text_content) }
|
|
121
|
+
elsif block
|
|
122
|
+
tag(method_name, attributes, &block)
|
|
123
|
+
else
|
|
124
|
+
tag(method_name, attributes)
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# @return [Boolean]
|
|
129
|
+
def respond_to_missing?(_method_name, _include_private = false)
|
|
130
|
+
true
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
private
|
|
134
|
+
|
|
135
|
+
def current_parent
|
|
136
|
+
@node_stack.last&.children || @children
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def render_child(child, indent:, level:)
|
|
140
|
+
case child
|
|
141
|
+
when Node
|
|
142
|
+
child.render(indent: indent, level: level)
|
|
143
|
+
when String
|
|
144
|
+
if indent
|
|
145
|
+
"#{' ' * (indent * level)}#{child}\n"
|
|
146
|
+
else
|
|
147
|
+
child
|
|
148
|
+
end
|
|
149
|
+
else
|
|
150
|
+
child.to_s
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Philiprehberger
|
|
4
|
+
module XmlBuilder
|
|
5
|
+
# XML entity escaping for text content and attribute values.
|
|
6
|
+
module Escaper
|
|
7
|
+
ENTITIES = {
|
|
8
|
+
'&' => '&',
|
|
9
|
+
'<' => '<',
|
|
10
|
+
'>' => '>',
|
|
11
|
+
'"' => '"',
|
|
12
|
+
"'" => '''
|
|
13
|
+
}.freeze
|
|
14
|
+
|
|
15
|
+
ENTITY_PATTERN = Regexp.union(ENTITIES.keys).freeze
|
|
16
|
+
|
|
17
|
+
# Escape special XML characters in a string.
|
|
18
|
+
#
|
|
19
|
+
# @param text [String] the text to escape
|
|
20
|
+
# @return [String] the escaped text
|
|
21
|
+
def self.escape(text)
|
|
22
|
+
text.to_s.gsub(ENTITY_PATTERN, ENTITIES)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Philiprehberger
|
|
4
|
+
module XmlBuilder
|
|
5
|
+
# Represents a single XML element with optional attributes and children.
|
|
6
|
+
class Node
|
|
7
|
+
attr_reader :name, :attributes, :children
|
|
8
|
+
|
|
9
|
+
# @param name [String, Symbol] the element tag name
|
|
10
|
+
# @param attributes [Hash] element attributes
|
|
11
|
+
def initialize(name, attributes = {})
|
|
12
|
+
@name = name.to_s
|
|
13
|
+
@attributes = attributes
|
|
14
|
+
@children = []
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Render this node and its children as an XML string.
|
|
18
|
+
#
|
|
19
|
+
# @param indent [Integer, nil] number of spaces per indentation level, or nil for compact output
|
|
20
|
+
# @param level [Integer] current nesting depth (used internally)
|
|
21
|
+
# @return [String] the rendered XML
|
|
22
|
+
def render(indent: nil, level: 0)
|
|
23
|
+
prefix = indent ? ' ' * (indent * level) : ''
|
|
24
|
+
newline = indent ? "\n" : ''
|
|
25
|
+
|
|
26
|
+
attrs = render_attributes
|
|
27
|
+
tag_open = "#{prefix}<#{@name}#{attrs}"
|
|
28
|
+
|
|
29
|
+
if @children.empty?
|
|
30
|
+
"#{tag_open} />#{newline}"
|
|
31
|
+
else
|
|
32
|
+
parts = ["#{tag_open}>"]
|
|
33
|
+
inline = !indent || @children.all?(String)
|
|
34
|
+
|
|
35
|
+
if inline
|
|
36
|
+
@children.each { |child| parts << render_child(child, indent: nil, level: 0) }
|
|
37
|
+
parts << "</#{@name}>#{newline}"
|
|
38
|
+
else
|
|
39
|
+
parts[0] << newline
|
|
40
|
+
@children.each { |child| parts << render_child(child, indent: indent, level: level + 1) }
|
|
41
|
+
parts << "#{prefix}</#{@name}>#{newline}"
|
|
42
|
+
end
|
|
43
|
+
parts.join
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
def render_attributes
|
|
50
|
+
return '' if @attributes.empty?
|
|
51
|
+
|
|
52
|
+
pairs = @attributes.map do |key, value|
|
|
53
|
+
" #{key}=\"#{Escaper.escape(value)}\""
|
|
54
|
+
end
|
|
55
|
+
pairs.join
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def render_child(child, indent:, level:)
|
|
59
|
+
case child
|
|
60
|
+
when Node
|
|
61
|
+
child.render(indent: indent, level: level)
|
|
62
|
+
when String
|
|
63
|
+
if indent && level.positive?
|
|
64
|
+
"#{' ' * (indent * level)}#{child}\n"
|
|
65
|
+
else
|
|
66
|
+
child
|
|
67
|
+
end
|
|
68
|
+
else
|
|
69
|
+
child.to_s
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'xml_builder/version'
|
|
4
|
+
require_relative 'xml_builder/escaper'
|
|
5
|
+
require_relative 'xml_builder/node'
|
|
6
|
+
require_relative 'xml_builder/document'
|
|
7
|
+
|
|
8
|
+
module Philiprehberger
|
|
9
|
+
module XmlBuilder
|
|
10
|
+
class Error < StandardError; end
|
|
11
|
+
|
|
12
|
+
# Build an XML document using a block-based DSL.
|
|
13
|
+
#
|
|
14
|
+
# @param encoding [String] XML encoding declaration (default: "UTF-8")
|
|
15
|
+
# @param version [String] XML version declaration (default: "1.0")
|
|
16
|
+
# @yield [Document] the document builder
|
|
17
|
+
# @return [String] the rendered XML string
|
|
18
|
+
def self.build(encoding: 'UTF-8', version: '1.0', &block)
|
|
19
|
+
doc = Document.new(version: version, encoding: encoding)
|
|
20
|
+
block.call(doc)
|
|
21
|
+
doc.to_s
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: philiprehberger-xml_builder
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Philip Rehberger
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-03-27 00:00:00.000000000 Z
|
|
12
|
+
dependencies: []
|
|
13
|
+
description: Programmatic XML construction with a clean DSL, auto-escaping, CDATA,
|
|
14
|
+
comments, processing instructions, and pretty printing. Zero dependencies.
|
|
15
|
+
email:
|
|
16
|
+
- me@philiprehberger.com
|
|
17
|
+
executables: []
|
|
18
|
+
extensions: []
|
|
19
|
+
extra_rdoc_files: []
|
|
20
|
+
files:
|
|
21
|
+
- CHANGELOG.md
|
|
22
|
+
- LICENSE
|
|
23
|
+
- README.md
|
|
24
|
+
- lib/philiprehberger/xml_builder.rb
|
|
25
|
+
- lib/philiprehberger/xml_builder/document.rb
|
|
26
|
+
- lib/philiprehberger/xml_builder/escaper.rb
|
|
27
|
+
- lib/philiprehberger/xml_builder/node.rb
|
|
28
|
+
- lib/philiprehberger/xml_builder/version.rb
|
|
29
|
+
homepage: https://github.com/philiprehberger/rb-xml-builder
|
|
30
|
+
licenses:
|
|
31
|
+
- MIT
|
|
32
|
+
metadata:
|
|
33
|
+
homepage_uri: https://github.com/philiprehberger/rb-xml-builder
|
|
34
|
+
source_code_uri: https://github.com/philiprehberger/rb-xml-builder
|
|
35
|
+
changelog_uri: https://github.com/philiprehberger/rb-xml-builder/blob/main/CHANGELOG.md
|
|
36
|
+
bug_tracker_uri: https://github.com/philiprehberger/rb-xml-builder/issues
|
|
37
|
+
rubygems_mfa_required: 'true'
|
|
38
|
+
post_install_message:
|
|
39
|
+
rdoc_options: []
|
|
40
|
+
require_paths:
|
|
41
|
+
- lib
|
|
42
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - ">="
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: 3.1.0
|
|
47
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
48
|
+
requirements:
|
|
49
|
+
- - ">="
|
|
50
|
+
- !ruby/object:Gem::Version
|
|
51
|
+
version: '0'
|
|
52
|
+
requirements: []
|
|
53
|
+
rubygems_version: 3.5.22
|
|
54
|
+
signing_key:
|
|
55
|
+
specification_version: 4
|
|
56
|
+
summary: Lightweight XML builder DSL without Nokogiri dependency
|
|
57
|
+
test_files: []
|