Class: Makiri::XML::Builder

Inherits:
Object
  • Object
show all
Defined in:
lib/makiri/xml/builder.rb

Overview

A Nokogiri-compatible DSL for building an XML document (or subtree) from scratch. It is a thin, pure-Ruby layer over the public construction surface (XML::Document.new, Document#create_element / #create_text_node / #create_cdata / #create_comment, and Node#add_child); no C code is involved.

An element is created by calling a method named after the tag. Trailing arguments follow the Nokogiri convention: a Hash sets attributes (including xmlns / xmlns:prefix namespace declarations), any other argument becomes the element’s text content, and a block builds nested children.

Tag names that collide with a Ruby/Kernel method (or with one of this builder’s own helpers below - text, cdata, comment, doc, parent, to_xml, to_s, descend) must be written with a trailing underscore, which is stripped: xml.id_(“9”) produces <id>9</id>. This matches Nokogiri.

A namespace prefix is selected for the next element with []: xml.title produces <dc:title> (the prefix must be in scope, i.e. declared via an “xmlns:dc” attribute on an ancestor or on the element itself, exactly as Makiri resolves prefixes at insertion time).

Examples:

Block-with-argument form (recommended)

builder = Makiri::XML::Builder.new do |xml|
  xml.feed("xmlns" => "urn:a") do
    xml.entry do
      xml.title("Hello")
    end
  end
end
builder.to_xml

instance_eval form (no block argument)

builder = Makiri::XML::Builder.new do
  root { child("text") }
end

Defined Under Namespace

Classes: NodeBuilder

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options = {}, root = nil) {|self| ... } ⇒ Builder

Returns a new instance of Builder.

Parameters:

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

    accepted for Nokogiri compatibility and ignored - a Makiri document has no configurable options (it is always UTF-8).

  • root (Makiri::XML::Node, nil) (defaults to: nil)

    when given, build into this node: top-level calls append to it and its document is used. (This is what with passes; mirrors Nokogiri::XML::Builder.new(options, root).)

Yields:

  • (self)

    when the block takes an argument; otherwise the block is instance_eval‘d against the builder.



55
56
57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/makiri/xml/builder.rb', line 55

def initialize(options = {}, root = nil, &block)
  if root
    @doc = root.document
    @parent = root
  else
    @parent = @doc = Makiri::XML::Document.new
  end
  @ns_prefix = nil
  @arity = nil
  return unless block

  run(&block)
  @parent = @doc # like Nokogiri: after a build block, settle back at the document
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

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

Any other method name is a tag: create the element and insert it.



122
123
124
125
126
127
128
129
130
131
132
# File 'lib/makiri/xml/builder.rb', line 122

def method_missing(name, *args, &block)
  tag = name.to_s.sub(/[_!]\z/, "")
  prefix = @ns_prefix
  if prefix
    tag = "#{prefix}:#{tag}"
    @ns_prefix = nil
  end
  node = create_element(tag, args)
  check_prefix_defined!(node, prefix) if prefix
  insert(node, &block)
end

Instance Attribute Details

#docObject (readonly)

The document being built (a Document).



42
43
44
# File 'lib/makiri/xml/builder.rb', line 42

def doc
  @doc
end

#parentObject (readonly)

The node new children are currently appended to. While a nested block is running this is that block’s element; otherwise it is #doc.



46
47
48
# File 'lib/makiri/xml/builder.rb', line 46

def parent
  @parent
end

Class Method Details

.with(node, &block) ⇒ Builder

Build into an existing node: top-level calls append to node, using node‘s document. Mirrors Nokogiri::XML::Builder.with.

Parameters:

Returns:



75
76
77
# File 'lib/makiri/xml/builder.rb', line 75

def self.with(node, &block)
  new({}, node, &block)
end

Instance Method Details

#<<(string) ⇒ self

Parse string as an XML fragment (against the document’s in-scope namespaces) and append its children to the current parent. The Builder analogue of Nokogiri::XML::Builder#<<.

Returns:

  • (self)


101
102
103
104
# File 'lib/makiri/xml/builder.rb', line 101

def <<(string)
  @doc.fragment(string).children.to_a.each { |child| insert(child) }
  self
end

#[](ns_prefix) ⇒ self

Select the namespace prefix for the next element (consumed by the next tag method). Returns self so it reads as xml.title.

Returns:

  • (self)


109
110
111
112
# File 'lib/makiri/xml/builder.rb', line 109

def [](ns_prefix)
  @ns_prefix = ns_prefix.to_s
  self
end

#cdata(string) ⇒ NodeBuilder

Append a CDATA section to the current parent.

Returns:



87
88
89
# File 'lib/makiri/xml/builder.rb', line 87

def cdata(string)
  insert(@doc.create_cdata(string.to_s))
end

#comment(string) ⇒ NodeBuilder

Append a comment node to the current parent.

Returns:



93
94
95
# File 'lib/makiri/xml/builder.rb', line 93

def comment(string)
  insert(@doc.create_comment(string.to_s))
end

#descend(node, &block) ⇒ Object

Run block with node as the current parent, restoring the previous parent afterward (even if the block raises) and returning the block’s value. The single place @parent is pushed/popped - shared by #insert and by NodeBuilder‘s nested-block chain, so neither manipulates the parent state directly. Public so NodeBuilder (a separate class) can reuse it without reaching into a private method.



146
147
148
149
150
151
152
153
154
# File 'lib/makiri/xml/builder.rb', line 146

def descend(node, &block)
  previous = @parent
  @parent = node
  begin
    run(&block)
  ensure
    @parent = previous
  end
end

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

Tag methods are open-ended, so report respond_to? truthfully for them (anything that is not already a real method is a candidate tag).

Returns:

  • (Boolean)


136
137
138
# File 'lib/makiri/xml/builder.rb', line 136

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

#text(string) ⇒ NodeBuilder

Append a text node to the current parent.

Returns:



81
82
83
# File 'lib/makiri/xml/builder.rb', line 81

def text(string)
  insert(@doc.create_text_node(string.to_s))
end

#to_xmlObject Also known as: to_s

Serialize the built document. Forwards to NodeMethods#to_xml (so pretty: works).



116
117
118
# File 'lib/makiri/xml/builder.rb', line 116

def to_xml(...)
  @doc.to_xml(...)
end