philiprehberger-xml_builder 0.3.0 → 0.5.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 +10 -0
- data/README.md +26 -0
- data/lib/philiprehberger/xml_builder/document.rb +38 -4
- data/lib/philiprehberger/xml_builder/processing_instruction.rb +49 -0
- data/lib/philiprehberger/xml_builder/version.rb +1 -1
- data/lib/philiprehberger/xml_builder.rb +1 -0
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c3469bf0da4d625250b3e99c2659f1c5299d344a7b6b7f60f24af0cc9fd07f71
|
|
4
|
+
data.tar.gz: c806ec833b1d176a5907fb977c39c54b3e35f1103314cd2abfd335b260faff39
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9d8b31224c876a05e5d71619d80ad32a6cff4b7df989b21344d6b7063313e5a7454f1fb58ad0a506f3b49205258d5c9a4937231368e37f82572c8dbaf47230d2
|
|
7
|
+
data.tar.gz: fe9aba2f34358d9f54fce39e6894fa3959aa1c8dc29588fcb568112c24eaf432e84909cec5c78716f149b8803c8cdff080ab23241060af8a0a67b20c4e3e4c19
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.5.0] - 2026-05-07
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- `Document#pretty(indent: 2)` — convenience renderer for pretty-printed XML, equivalent to `to_xml(indent: 2)` but with sane defaults.
|
|
14
|
+
|
|
15
|
+
## [0.4.0] - 2026-04-16
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
- Processing instruction (PI) node support via `Document#processing_instruction` / `#pi`
|
|
19
|
+
|
|
10
20
|
## [0.3.0] - 2026-04-10
|
|
11
21
|
|
|
12
22
|
### Added
|
data/README.md
CHANGED
|
@@ -68,6 +68,20 @@ end
|
|
|
68
68
|
|
|
69
69
|
### Processing Instructions
|
|
70
70
|
|
|
71
|
+
Pass keyword attributes to emit a structured PI with XML-escaped values:
|
|
72
|
+
|
|
73
|
+
```ruby
|
|
74
|
+
xml = Philiprehberger::XmlBuilder.build do |doc|
|
|
75
|
+
doc.pi("xml-stylesheet", href: "style.xsl", type: "text/xsl")
|
|
76
|
+
doc.tag(:root) { doc.text("content") }
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
puts xml
|
|
80
|
+
# <?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet href="style.xsl" type="text/xsl"?><root>content</root>
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
A legacy positional content string is also supported:
|
|
84
|
+
|
|
71
85
|
```ruby
|
|
72
86
|
xml = Philiprehberger::XmlBuilder.build do |doc|
|
|
73
87
|
doc.processing_instruction("xml-stylesheet", 'type="text/xsl" href="style.xsl"')
|
|
@@ -90,6 +104,16 @@ puts doc.to_xml(indent: 2)
|
|
|
90
104
|
# </root>
|
|
91
105
|
```
|
|
92
106
|
|
|
107
|
+
Or use `#pretty` for the same output with sane defaults:
|
|
108
|
+
|
|
109
|
+
```ruby
|
|
110
|
+
puts doc.pretty
|
|
111
|
+
# Equivalent to doc.to_xml(indent: 2)
|
|
112
|
+
|
|
113
|
+
puts doc.pretty(indent: 4)
|
|
114
|
+
# Equivalent to doc.to_xml(indent: 4)
|
|
115
|
+
```
|
|
116
|
+
|
|
93
117
|
### Raw XML
|
|
94
118
|
|
|
95
119
|
```ruby
|
|
@@ -209,6 +233,7 @@ end
|
|
|
209
233
|
| `#cdata(content)` | Add a CDATA section |
|
|
210
234
|
| `#comment(text)` | Add an XML comment |
|
|
211
235
|
| `#processing_instruction(target, content)` | Add a processing instruction |
|
|
236
|
+
| `#processing_instruction(target, **attrs)` | Append an XML processing instruction (alias: `#pi`) |
|
|
212
237
|
| `#raw(string)` | Add raw unescaped XML |
|
|
213
238
|
| `#namespace(prefix, uri)` | Register an XML namespace prefix and URI |
|
|
214
239
|
| `#namespace_tag(prefix, name, attributes = {}) { ... }` | Add a namespace-prefixed element with auto xmlns |
|
|
@@ -217,6 +242,7 @@ end
|
|
|
217
242
|
| `#insert_fragment(xml_string)` | Insert a raw XML fragment string |
|
|
218
243
|
| `#to_s` | Render compact XML string |
|
|
219
244
|
| `#to_xml(indent: nil)` | Render XML with optional indentation |
|
|
245
|
+
| `#pretty(indent: 2)` | Renders with default 2-space indentation |
|
|
220
246
|
|
|
221
247
|
### `Escaper`
|
|
222
248
|
|
|
@@ -66,15 +66,33 @@ module Philiprehberger
|
|
|
66
66
|
current_parent.push("<!-- #{text} -->")
|
|
67
67
|
end
|
|
68
68
|
|
|
69
|
+
# Valid PI target pattern per the XML spec (simplified).
|
|
70
|
+
PI_TARGET_PATTERN = /\A[A-Za-z_][\w.-]*\z/
|
|
71
|
+
|
|
69
72
|
# Add a processing instruction.
|
|
70
73
|
#
|
|
71
|
-
#
|
|
72
|
-
#
|
|
74
|
+
# Accepts either a legacy positional content string or keyword attributes.
|
|
75
|
+
# When attrs are given, renders as <?target key="value" key2="value2"?>.
|
|
76
|
+
# When a content string is given, renders as <?target content?>.
|
|
77
|
+
#
|
|
78
|
+
# @param target [String] the PI target (must match PI_TARGET_PATTERN; "xml" is forbidden)
|
|
79
|
+
# @param content [String, nil] optional raw PI content string (legacy)
|
|
80
|
+
# @param attrs [Hash] attribute key/value pairs (XML-escaped)
|
|
73
81
|
# @return [void]
|
|
74
|
-
|
|
75
|
-
|
|
82
|
+
# @raise [ArgumentError] if target is empty, invalid, or equal to "xml" (case-insensitive)
|
|
83
|
+
def processing_instruction(target, content = nil, **attrs)
|
|
84
|
+
validate_pi_target!(target)
|
|
85
|
+
|
|
86
|
+
if content.is_a?(String)
|
|
87
|
+
current_parent.push("<?#{target} #{content}?>")
|
|
88
|
+
else
|
|
89
|
+
current_parent.push(ProcessingInstruction.new(target, attrs))
|
|
90
|
+
end
|
|
76
91
|
end
|
|
77
92
|
|
|
93
|
+
# Alias for #processing_instruction.
|
|
94
|
+
alias pi processing_instruction
|
|
95
|
+
|
|
78
96
|
# Add raw XML content without escaping.
|
|
79
97
|
#
|
|
80
98
|
# @param string [String] raw XML string
|
|
@@ -90,6 +108,14 @@ module Philiprehberger
|
|
|
90
108
|
to_xml
|
|
91
109
|
end
|
|
92
110
|
|
|
111
|
+
# Render the document as a pretty-printed XML string with default 2-space indentation.
|
|
112
|
+
#
|
|
113
|
+
# @param indent [Integer] number of spaces per indentation level (default 2)
|
|
114
|
+
# @return [String] the rendered XML document
|
|
115
|
+
def pretty(indent: 2)
|
|
116
|
+
to_xml(indent: indent)
|
|
117
|
+
end
|
|
118
|
+
|
|
93
119
|
# Render the document as an XML string with optional indentation.
|
|
94
120
|
#
|
|
95
121
|
# @param indent [Integer, nil] number of spaces per indentation level, or nil for compact output
|
|
@@ -236,10 +262,18 @@ module Philiprehberger
|
|
|
236
262
|
@node_stack.last&.children || @children
|
|
237
263
|
end
|
|
238
264
|
|
|
265
|
+
def validate_pi_target!(target)
|
|
266
|
+
raise ArgumentError, 'PI target must be a non-empty String' unless target.is_a?(String) && !target.empty?
|
|
267
|
+
raise ArgumentError, 'PI target "xml" is reserved by the XML specification' if target.casecmp('xml').zero?
|
|
268
|
+
raise ArgumentError, "Invalid PI target: #{target.inspect}" unless target.match?(PI_TARGET_PATTERN)
|
|
269
|
+
end
|
|
270
|
+
|
|
239
271
|
def render_child(child, indent:, level:)
|
|
240
272
|
case child
|
|
241
273
|
when Node
|
|
242
274
|
child.render(indent: indent, level: level)
|
|
275
|
+
when ProcessingInstruction
|
|
276
|
+
child.to_xml(indent: indent, level: level, pretty: !indent.nil?)
|
|
243
277
|
when String
|
|
244
278
|
if indent
|
|
245
279
|
"#{' ' * (indent * level)}#{child}\n"
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Philiprehberger
|
|
4
|
+
module XmlBuilder
|
|
5
|
+
# Represents an XML processing instruction (PI).
|
|
6
|
+
#
|
|
7
|
+
# Renders as <?target key="value" key2="value2"?>, with attribute
|
|
8
|
+
# values escaped via Escaper.
|
|
9
|
+
class ProcessingInstruction
|
|
10
|
+
attr_reader :target, :attributes
|
|
11
|
+
|
|
12
|
+
# @param target [String] the PI target name
|
|
13
|
+
# @param attributes [Hash] attribute key/value pairs
|
|
14
|
+
def initialize(target, attributes = {})
|
|
15
|
+
@target = target.to_s
|
|
16
|
+
@attributes = attributes
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Render this processing instruction as an XML string.
|
|
20
|
+
#
|
|
21
|
+
# @param indent [Integer, nil] number of spaces per indentation level, or nil for compact output
|
|
22
|
+
# @param level [Integer] current nesting depth (used internally)
|
|
23
|
+
# @param pretty [Boolean] whether to apply pretty-print formatting
|
|
24
|
+
# @return [String] the rendered processing instruction
|
|
25
|
+
def to_xml(indent: nil, level: 0, pretty: false)
|
|
26
|
+
prefix = indent && pretty ? ' ' * (indent * level) : ''
|
|
27
|
+
newline = indent && pretty ? "\n" : ''
|
|
28
|
+
"#{prefix}<?#{@target}#{render_attributes}?>#{newline}"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Alias for to_xml to match Node#render semantics.
|
|
32
|
+
def render(indent: nil, level: 0)
|
|
33
|
+
to_xml(indent: indent, level: level, pretty: !indent.nil?)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def render_attributes
|
|
39
|
+
return '' if @attributes.empty?
|
|
40
|
+
|
|
41
|
+
pairs = @attributes.map do |key, value|
|
|
42
|
+
attr_name = key.to_s.gsub('__', ':')
|
|
43
|
+
" #{attr_name}=\"#{Escaper.escape(value)}\""
|
|
44
|
+
end
|
|
45
|
+
pairs.join
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
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.5.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-
|
|
11
|
+
date: 2026-05-07 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.
|
|
@@ -25,6 +25,7 @@ files:
|
|
|
25
25
|
- lib/philiprehberger/xml_builder/document.rb
|
|
26
26
|
- lib/philiprehberger/xml_builder/escaper.rb
|
|
27
27
|
- lib/philiprehberger/xml_builder/node.rb
|
|
28
|
+
- lib/philiprehberger/xml_builder/processing_instruction.rb
|
|
28
29
|
- lib/philiprehberger/xml_builder/version.rb
|
|
29
30
|
homepage: https://philiprehberger.com/open-source-packages/ruby/philiprehberger-xml_builder
|
|
30
31
|
licenses:
|