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>


225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
# File 'lib/philiprehberger/xml_builder/document.rb', line 225

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:



201
202
203
204
205
206
207
# File 'lib/philiprehberger/xml_builder/document.rb', line 201

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



216
217
218
# File 'lib/philiprehberger/xml_builder/document.rb', line 216

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



138
139
140
# File 'lib/philiprehberger/xml_builder/document.rb', line 138

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



152
153
154
155
156
157
158
159
160
161
# File 'lib/philiprehberger/xml_builder/document.rb', line 152

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

#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)


247
248
249
# File 'lib/philiprehberger/xml_builder/document.rb', line 247

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



171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
# File 'lib/philiprehberger/xml_builder/document.rb', line 171

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



115
116
117
118
119
120
121
122
123
124
125
126
127
128
# File 'lib/philiprehberger/xml_builder/document.rb', line 115

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