Class: Philiprehberger::XmlBuilder::Document

Inherits:
Object
  • Object
show all
Defined in:
lib/philiprehberger/xml_builder/document.rb

Overview

Accumulates XML nodes and renders the final document.

Used as the context object inside XmlBuilder.build blocks.

Constant Summary collapse

PI_TARGET_PATTERN =

Valid PI target pattern per the XML spec (simplified).

/\A[A-Za-z_][\w.-]*\z/

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(version: '1.0', encoding: 'UTF-8', declaration: true) ⇒ Document

Returns a new instance of Document.

Parameters:

  • version (String) (defaults to: '1.0')

    XML version for the declaration

  • encoding (String) (defaults to: 'UTF-8')

    XML encoding for the declaration



13
14
15
16
17
18
19
20
# File 'lib/philiprehberger/xml_builder/document.rb', line 13

def initialize(version: '1.0', encoding: 'UTF-8', declaration: true)
  @version = version
  @encoding = encoding
  @declaration = declaration
  @children = []
  @node_stack = []
  @namespaces = {}
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(method_name, *args, &block) ⇒ Object

Support method_missing for DSL-style tag creation.

Examples:

xml.person(name: "John") { xml.age("30") }
# => <person name="John"><age>30</age></person>


233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
# File 'lib/philiprehberger/xml_builder/document.rb', line 233

def method_missing(method_name, *args, &block)
  first_arg = args.first
  attributes = {}
  text_content = nil

  if first_arg.is_a?(Hash)
    attributes = first_arg
  elsif first_arg
    text_content = first_arg.to_s
    attributes = args[1] if args[1].is_a?(Hash)
  end

  if text_content
    tag(method_name, attributes) { text(text_content) }
  elsif block
    tag(method_name, attributes, &block)
  else
    tag(method_name, attributes)
  end
end

Instance Attribute Details

#childrenObject (readonly)

Returns the value of attribute children.



9
10
11
# File 'lib/philiprehberger/xml_builder/document.rb', line 9

def children
  @children
end

#encodingObject (readonly)

Returns the value of attribute encoding.



9
10
11
# File 'lib/philiprehberger/xml_builder/document.rb', line 9

def encoding
  @encoding
end

#versionObject (readonly)

Returns the value of attribute version.



9
10
11
# File 'lib/philiprehberger/xml_builder/document.rb', line 9

def version
  @version
end

Instance Method Details

#append(other) ⇒ void

This method returns an undefined value.

Append children from another Document into this document.

Copies all top-level children from the source document into the current insertion point (either the document root or the current parent element).

Parameters:

  • other (Document)

    the source document whose children to import

Raises:



209
210
211
212
213
214
215
# File 'lib/philiprehberger/xml_builder/document.rb', line 209

def append(other)
  raise Error, 'append expects a Document' unless other.is_a?(Document)

  other.children.each do |child|
    current_parent.push(child)
  end
end

#cdata(content) ⇒ void

This method returns an undefined value.

Add a CDATA section.

Parameters:

  • content (String)

    the CDATA content (must not contain “]]>”)

Raises:



53
54
55
56
57
# File 'lib/philiprehberger/xml_builder/document.rb', line 53

def cdata(content)
  raise Error, 'CDATA content must not contain "]]>"' if content.to_s.include?(']]>')

  current_parent.push("<![CDATA[#{content}]]>")
end

#comment(text) ⇒ void

This method returns an undefined value.

Add an XML comment.

Parameters:

  • text (String)

    the comment text

Raises:



63
64
65
66
67
# File 'lib/philiprehberger/xml_builder/document.rb', line 63

def comment(text)
  raise Error, 'Comment text must not contain "--"' if text.to_s.include?('--')

  current_parent.push("<!-- #{text} -->")
end

#insert_fragment(xml_string) ⇒ void

This method returns an undefined value.

Insert a raw XML fragment string into the current position.

This is an alias for #raw, provided for semantic clarity when composing fragments.

Parameters:

  • xml_string (String)

    the XML fragment to insert



224
225
226
# File 'lib/philiprehberger/xml_builder/document.rb', line 224

def insert_fragment(xml_string)
  raw(xml_string)
end

#namespace(prefix, uri) ⇒ void

This method returns an undefined value.

Register an XML namespace prefix and URI.

Registered namespaces are automatically added as xmlns attributes when using namespace_tag.

Parameters:

  • prefix (String, Symbol)

    the namespace prefix

  • uri (String)

    the namespace URI



146
147
148
# File 'lib/philiprehberger/xml_builder/document.rb', line 146

def namespace(prefix, uri)
  @namespaces[prefix.to_s] = uri
end

#namespace_tag(prefix, name, attributes = {}) { ... } ⇒ Node

Add a namespace-prefixed element.

Automatically includes the xmlns declaration for the prefix if it was registered via #namespace and this is the first use in the current scope.

Parameters:

  • prefix (String, Symbol)

    the namespace prefix

  • name (String, Symbol)

    the local element name

  • attributes (Hash) (defaults to: {})

    additional element attributes

Yields:

  • optional block for adding child elements

Returns:

  • (Node)

    the created node



160
161
162
163
164
165
166
167
168
169
# File 'lib/philiprehberger/xml_builder/document.rb', line 160

def namespace_tag(prefix, name, attributes = {}, &)
  prefixed_name = "#{prefix}:#{name}"
  uri = @namespaces[prefix.to_s]
  attrs = if uri
            { "xmlns:#{prefix}" => uri }.merge(attributes)
          else
            attributes
          end
  tag(prefixed_name, attrs, &)
end

#pretty(indent: 2) ⇒ String

Render the document as a pretty-printed XML string with default 2-space indentation.

Parameters:

  • indent (Integer) (defaults to: 2)

    number of spaces per indentation level (default 2)

Returns:

  • (String)

    the rendered XML document



115
116
117
# File 'lib/philiprehberger/xml_builder/document.rb', line 115

def pretty(indent: 2)
  to_xml(indent: indent)
end

#processing_instruction(target, content = nil, **attrs) ⇒ void Also known as: pi

This method returns an undefined value.

Add a processing instruction.

Accepts either a legacy positional content string or keyword attributes. When attrs are given, renders as <?target key=“value” key2=“value2”?>. When a content string is given, renders as <?target content?>.

Parameters:

  • target (String)

    the PI target (must match PI_TARGET_PATTERN; “xml” is forbidden)

  • content (String, nil) (defaults to: nil)

    optional raw PI content string (legacy)

  • attrs (Hash)

    attribute key/value pairs (XML-escaped)

Raises:

  • (ArgumentError)

    if target is empty, invalid, or equal to “xml” (case-insensitive)



83
84
85
86
87
88
89
90
91
# File 'lib/philiprehberger/xml_builder/document.rb', line 83

def processing_instruction(target, content = nil, **attrs)
  validate_pi_target!(target)

  if content.is_a?(String)
    current_parent.push("<?#{target} #{content}?>")
  else
    current_parent.push(ProcessingInstruction.new(target, attrs))
  end
end

#raw(string) ⇒ void

This method returns an undefined value.

Add raw XML content without escaping.

Parameters:

  • string (String)

    raw XML string



100
101
102
# File 'lib/philiprehberger/xml_builder/document.rb', line 100

def raw(string)
  current_parent.push(string.to_s)
end

#respond_to_missing?(_method_name, _include_private = false) ⇒ Boolean

Returns:

  • (Boolean)


255
256
257
# File 'lib/philiprehberger/xml_builder/document.rb', line 255

def respond_to_missing?(_method_name, _include_private = false)
  true
end

#soap_envelope(version: '1.1') {|header, body| ... } ⇒ void

This method returns an undefined value.

Build a SOAP envelope using a block-based DSL.

Supports SOAP 1.1 (default) and 1.2. Automatically sets the correct namespace URI and creates the Envelope, Header, and Body elements.

Parameters:

  • version (String) (defaults to: '1.1')

    SOAP version: “1.1” or “1.2”

Yields:

  • (header, body)

    yields two procs for adding header and body content



179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
# File 'lib/philiprehberger/xml_builder/document.rb', line 179

def soap_envelope(version: '1.1')
  uri = case version
        when '1.1' then 'http://schemas.xmlsoap.org/soap/envelope/'
        when '1.2' then 'http://www.w3.org/2003/05/soap-envelope'
        else
          raise Error, "Unsupported SOAP version: #{version}. Use '1.1' or '1.2'."
        end

  header_children = []
  body_children = []

  yield(header_children, body_children) if block_given?

  tag('soap:Envelope', 'xmlns:soap' => uri) do
    tag('soap:Header') do
      header_children.each { |child_block| child_block.call(self) }
    end
    tag('soap:Body') do
      body_children.each { |child_block| child_block.call(self) }
    end
  end
end

#tag(name, attributes = {}) { ... } ⇒ Node

Add an XML element with optional attributes and nested children.

Parameters:

  • name (String, Symbol)

    the element tag name

  • attributes (Hash) (defaults to: {})

    element attributes

Yields:

  • optional block for adding child elements

Returns:

  • (Node)

    the created node



28
29
30
31
32
33
34
35
36
37
38
39
# File 'lib/philiprehberger/xml_builder/document.rb', line 28

def tag(name, attributes = {}, &block)
  node = Node.new(name, attributes)

  if block
    @node_stack.push(node)
    block.call
    @node_stack.pop
  end

  current_parent.push(node)
  node
end

#text(content) ⇒ void

This method returns an undefined value.

Add escaped text content to the current element.

Parameters:

  • content (String)

    the text content to escape and add



45
46
47
# File 'lib/philiprehberger/xml_builder/document.rb', line 45

def text(content)
  current_parent.push(Escaper.escape(content.to_s))
end

#to_sString

Render the document as a compact XML string (no indentation).

Returns:

  • (String)

    the rendered XML document



107
108
109
# File 'lib/philiprehberger/xml_builder/document.rb', line 107

def to_s
  to_xml
end

#to_xml(indent: nil) ⇒ String

Render the document as an XML string with optional indentation.

Parameters:

  • indent (Integer, nil) (defaults to: nil)

    number of spaces per indentation level, or nil for compact output

Returns:

  • (String)

    the rendered XML document



123
124
125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/philiprehberger/xml_builder/document.rb', line 123

def to_xml(indent: nil)
  parts = []

  if @declaration
    parts << "<?xml version=\"#{@version}\" encoding=\"#{@encoding}\"?>"
    parts << (indent ? "\n" : '')
  end

  @children.each do |child|
    parts << render_child(child, indent: indent, level: 0)
  end

  parts.join
end