philiprehberger-xml_builder
Lightweight XML builder DSL without Nokogiri dependency
Requirements
- Ruby >= 3.1
Installation
Add to your Gemfile:
gem "philiprehberger-xml_builder"
Or install directly:
gem install philiprehberger-xml_builder
Usage
Basic Elements
require "philiprehberger/xml_builder"
xml = Philiprehberger::XmlBuilder.build do |doc|
doc.tag(:root) do
doc.tag(:item, id: "1") { doc.text("Hello") }
end
end
puts xml
# <?xml version="1.0" encoding="UTF-8"?><root><item id="1">Hello</item></root>
Method Missing DSL
Use method names directly as tag names for a cleaner syntax:
xml = Philiprehberger::XmlBuilder.build do |doc|
doc.person(name: "John") do
doc.age("30")
doc.email("john@example.com")
end
end
# <person name="John"><age>30</age><email>john@example.com</email></person>
CDATA and Comments
xml = Philiprehberger::XmlBuilder.build do |doc|
doc.tag(:root) do
doc.comment("Generated XML")
doc.tag(:script) { doc.cdata('var x = 1 < 2;') }
end
end
Processing Instructions
Pass keyword attributes to emit a structured PI with XML-escaped values:
xml = Philiprehberger::XmlBuilder.build do |doc|
doc.pi("xml-stylesheet", href: "style.xsl", type: "text/xsl")
doc.tag(:root) { doc.text("content") }
end
puts xml
# <?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet href="style.xsl" type="text/xsl"?><root>content</root>
A legacy positional content string is also supported:
xml = Philiprehberger::XmlBuilder.build do |doc|
doc.processing_instruction("xml-stylesheet", 'type="text/xsl" href="style.xsl"')
doc.tag(:root) { doc.text("content") }
end
Pretty Printing
doc = Philiprehberger::XmlBuilder::Document.new
doc.tag(:root) do
doc.tag(:child) { doc.text("value") }
end
puts doc.to_xml(indent: 2)
# <?xml version="1.0" encoding="UTF-8"?>
# <root>
# <child>value</child>
# </root>
Raw XML
xml = Philiprehberger::XmlBuilder.build do |doc|
doc.tag(:root) { doc.raw("<pre>formatted</pre>") }
end
Without XML Declaration
Omit the <?xml ... ?> declaration when building fragments:
xml = Philiprehberger::XmlBuilder.build(declaration: false) do |doc|
doc.tag(:item, id: "1") { doc.text("fragment") }
end
puts xml
# <item id="1">fragment</item>
XML Namespaces
Register namespace prefixes and create namespace-aware elements:
xml = Philiprehberger::XmlBuilder.build do |doc|
doc.namespace(:soap, "http://schemas.xmlsoap.org/soap/envelope/")
doc.namespace(:wsse, "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd")
doc.namespace_tag(:soap, :Envelope) do
doc.namespace_tag(:soap, :Header) do
doc.namespace_tag(:wsse, :Security)
end
doc.namespace_tag(:soap, :Body)
end
end
You can also use string tag names directly:
xml = Philiprehberger::XmlBuilder.build do |doc|
doc.tag("soap:Envelope", "xmlns:soap" => "http://schemas.xmlsoap.org/soap/envelope/") do
doc.tag("soap:Body")
end
end
SOAP Envelope Builder
Build SOAP 1.1 or 1.2 envelopes with a convenience DSL:
xml = Philiprehberger::XmlBuilder.build do |doc|
doc.soap_envelope(version: "1.1") do |header, body|
header << ->(d) { d.tag("auth") { d.text("token123") } }
body << ->(d) { d.tag("GetPrice") { d.text("Widget") } }
end
end
Or use the top-level shortcut:
xml = Philiprehberger::XmlBuilder.build_soap(soap_version: "1.2") do |header, body|
body << ->(d) { d.tag("GetStockPrice") { d.tag("Symbol") { d.text("AAPL") } } }
end
XML Fragment Composition
Combine separately built document fragments:
# Build fragments independently
header = Philiprehberger::XmlBuilder::Document.new
header.tag(:title) { header.text("My Document") }
body = Philiprehberger::XmlBuilder::Document.new
body.tag(:paragraph) { body.text("Hello world") }
# Compose into a single document
xml = Philiprehberger::XmlBuilder.build do |doc|
doc.tag(:document) do
doc.tag(:header) { doc.append(header) }
doc.tag(:body) { doc.append(body) }
end
end
Insert raw XML fragment strings:
xml = Philiprehberger::XmlBuilder.build do |doc|
doc.tag(:root) do
doc.insert_fragment('<existing>data</existing>')
end
end
API
Philiprehberger::XmlBuilder
| Method | Description |
|---|---|
| `.build(encoding: "UTF-8", version: "1.0", declaration: true) { \ | doc\ |
| `.build_soap(soap_version: "1.1", encoding: "UTF-8", version: "1.0", declaration: true) { \ | header, body\ |
Document
| Method | Description |
|---|---|
#tag(name, attributes = {}) { ... } |
Add an element with optional attributes and children |
#text(content) |
Add escaped text content |
#cdata(content) |
Add a CDATA section |
#comment(text) |
Add an XML comment |
#processing_instruction(target, content) |
Add a processing instruction |
#processing_instruction(target, **attrs) |
Append an XML processing instruction (alias: #pi) |
#raw(string) |
Add raw unescaped XML |
#namespace(prefix, uri) |
Register an XML namespace prefix and URI |
#namespace_tag(prefix, name, attributes = {}) { ... } |
Add a namespace-prefixed element with auto xmlns |
| `#soap_envelope(version: "1.1") { \ | header, body\ |
#append(other_document) |
Append children from another Document |
#insert_fragment(xml_string) |
Insert a raw XML fragment string |
#to_s |
Render compact XML string |
#to_xml(indent: nil) |
Render XML with optional indentation |
Escaper
| Method | Description |
|---|---|
.escape(text) |
Escape XML entities (&, <, >, ", ') |
Development
bundle install
bundle exec rspec
bundle exec rubocop
Support
If you find this project useful: