philiprehberger-xml_builder

Tests Gem Version Last updated

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:

Star the repo

🐛 Report issues

💡 Suggest features

❤️ Sponsor development

🌐 All Open Source Projects

💻 GitHub Profile

🔗 LinkedIn Profile

License

MIT