philiprehberger-xml_builder 0.3.0 → 0.4.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 +5 -0
- data/README.md +15 -0
- data/lib/philiprehberger/xml_builder/document.rb +30 -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: 720c31767f0706544320e00c6fb4ed6e3dd946f601c68c947c6eaf409c324dd2
|
|
4
|
+
data.tar.gz: a8dafaef9908d5f485cdf57fc65654a8b11610e93575a27f78b668f6b9e342e4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 466443cf83706000e5ae81f7a8fd5cf673cb7aad9306a4de70d189182af0459e75a415b047063af46feaffe227342f3c1b51ac8288e312cca5ba2983efd38233
|
|
7
|
+
data.tar.gz: 5c904af4d1da2f0899771115ac35632ebd0c871c65236e24a98001522821a23e93d003723c4b4b98470da8579d8267a52443ba91243e16e46571a76fd6164515
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.4.0] - 2026-04-16
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- Processing instruction (PI) node support via `Document#processing_instruction` / `#pi`
|
|
14
|
+
|
|
10
15
|
## [0.3.0] - 2026-04-10
|
|
11
16
|
|
|
12
17
|
### 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"')
|
|
@@ -209,6 +223,7 @@ end
|
|
|
209
223
|
| `#cdata(content)` | Add a CDATA section |
|
|
210
224
|
| `#comment(text)` | Add an XML comment |
|
|
211
225
|
| `#processing_instruction(target, content)` | Add a processing instruction |
|
|
226
|
+
| `#processing_instruction(target, **attrs)` | Append an XML processing instruction (alias: `#pi`) |
|
|
212
227
|
| `#raw(string)` | Add raw unescaped XML |
|
|
213
228
|
| `#namespace(prefix, uri)` | Register an XML namespace prefix and URI |
|
|
214
229
|
| `#namespace_tag(prefix, name, attributes = {}) { ... }` | Add a namespace-prefixed element with auto xmlns |
|
|
@@ -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
|
|
@@ -236,10 +254,18 @@ module Philiprehberger
|
|
|
236
254
|
@node_stack.last&.children || @children
|
|
237
255
|
end
|
|
238
256
|
|
|
257
|
+
def validate_pi_target!(target)
|
|
258
|
+
raise ArgumentError, 'PI target must be a non-empty String' unless target.is_a?(String) && !target.empty?
|
|
259
|
+
raise ArgumentError, 'PI target "xml" is reserved by the XML specification' if target.casecmp('xml').zero?
|
|
260
|
+
raise ArgumentError, "Invalid PI target: #{target.inspect}" unless target.match?(PI_TARGET_PATTERN)
|
|
261
|
+
end
|
|
262
|
+
|
|
239
263
|
def render_child(child, indent:, level:)
|
|
240
264
|
case child
|
|
241
265
|
when Node
|
|
242
266
|
child.render(indent: indent, level: level)
|
|
267
|
+
when ProcessingInstruction
|
|
268
|
+
child.to_xml(indent: indent, level: level, pretty: !indent.nil?)
|
|
243
269
|
when String
|
|
244
270
|
if indent
|
|
245
271
|
"#{' ' * (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.4.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-04-
|
|
11
|
+
date: 2026-04-16 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:
|