123456789_123456789_123456789_123456789_123456789_

Class: Nokogiri::XML::Builder

Relationships & Source Files
Namespace Children
Classes:
Extension / Inclusion / Inheritance Descendants
Subclasses:
Super Chains via Extension / Inclusion / Inheritance
Instance Chain:
Inherits: Object
Defined in: lib/nokogiri/xml/builder.rb

Overview

::Nokogiri builder can be used for building ::Nokogiri::XML and ::Nokogiri::HTML documents.

Synopsis:

builder = Nokogiri::XML::Builder.new do |xml|
  xml.root {
    xml.products {
      xml.widget {
        xml.id_ "10"
        xml.name "Awesome widget"
      }
    }
  }
end
puts builder.to_xml

Will output:

<?xml version="1.0"?>
<root>
  <products>
    <widget>
      <id>10</id>
      <name>Awesome widget</name>
    </widget>
  </products>
</root>

Builder scope

The builder allows two forms. When the builder is supplied with a block that has a parameter, the outside scope is maintained. This means you can access variables that are outside your builder. If you don’t need outside scope, you can use the builder without the “xml” prefix like this:

builder = Nokogiri::XML::Builder.new do
  root {
    products {
      widget {
        id_ "10"
        name "Awesome widget"
      }
    }
  }
end

Special Tags

The builder works by taking advantage of method_missing. Unfortunately some methods are defined in ruby that are difficult or dangerous to remove. You may want to create tags with the name “type”, “class”, and “id” for example. In that case, you can use an underscore to disambiguate your tag name from the method call.

Here is an example of using the underscore to disambiguate tag names from ruby methods:

@objects = [Object.new, Object.new, Object.new]

builder = Nokogiri::XML::Builder.new do |xml|
  xml.root {
    xml.objects {
      @objects.each do |o|
        xml.object {
          xml.type_   o.type
          xml.class_  o.class.name
          xml.id_     o.id
        }
      end
    }
  }
end
puts builder.to_xml

The underscore may be used with any tag name, and the last underscore will just be removed. This code will output the following ::Nokogiri::XML:

<?xml version="1.0"?>
<root>
  <objects>
    <object>
      <type>Object</type>
      <class>Object</class>
      <id>48390</id>
    </object>
    <object>
      <type>Object</type>
      <class>Object</class>
      <id>48380</id>
    </object>
    <object>
      <type>Object</type>
      <class>Object</class>
      <id>48370</id>
    </object>
  </objects>
</root>

Tag Attributes

Tag attributes may be supplied as method arguments. Here is our previous example, but using attributes rather than tags:

@objects = [Object.new, Object.new, Object.new]

builder = Nokogiri::XML::Builder.new do |xml|
  xml.root {
    xml.objects {
      @objects.each do |o|
        xml.object(:type => o.type, :class => o.class, :id => o.id)
      end
    }
  }
end
puts builder.to_xml

Tag Attribute Short Cuts

A couple attribute short cuts are available when building tags. The short cuts are available by special method calls when building a tag.

This example builds an “object” tag with the class attribute “classy” and the id of “thing”:

builder = Nokogiri::XML::Builder.new do |xml|
  xml.root {
    xml.objects {
      xml.object.classy.thing!
    }
  }
end
puts builder.to_xml

Which will output:

<?xml version="1.0"?>
<root>
  <objects>
    <object class="classy" id="thing"/>
  </objects>
</root>

All other options are still supported with this syntax, including blocks and extra tag attributes.

Namespaces

Namespaces are added similarly to attributes. Builder assumes that when an attribute starts with “xmlns”, it is meant to be a namespace:

builder = Nokogiri::XML::Builder.new { |xml|
  xml.root('xmlns' => 'default', 'xmlns:foo' => 'bar') do
    xml.tenderlove
  end
}
puts builder.to_xml

Will output ::Nokogiri::XML like this:

<?xml version="1.0"?>
<root xmlns:foo="bar" xmlns="default">
  <tenderlove/>
</root>

Referencing declared namespaces

Tags that reference non-default namespaces (i.e. a tag “foo:bar”) can be built by using the #[] method.

For example:

builder = Nokogiri::XML::Builder.new do |xml|
  xml.root('xmlns:foo' => 'bar') {
    xml.objects {
      xml['foo'].object.classy.thing!
    }
  }
end
puts builder.to_xml

Will output this ::Nokogiri::XML:

<?xml version="1.0"?>
<root xmlns:foo="bar">
  <objects>
    <foo:object class="classy" id="thing"/>
  </objects>
</root>

Note the “foo:object” tag.

Namespace inheritance

In the Builder context, children will inherit their parent’s namespace. This is the same behavior as if the underlying Document set namespace_inheritance to true:

result = Nokogiri::XML::Builder.new do |xml|
  xml["soapenv"].Envelope("xmlns:soapenv" => "http://schemas.xmlsoap.org/soap/envelope/") do
    xml.Header
  end
end
result.doc.to_xml
# => <?xml version="1.0" encoding="utf-8"?>
#    <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
#      <soapenv:Header/>
#    </soapenv:Envelope>

Users may turn this behavior off by passing a keyword argument namespace_inheritance:false to the initializer:

result = Nokogiri::XML::Builder.new(namespace_inheritance: false) do |xml|
  xml["soapenv"].Envelope("xmlns:soapenv" => "http://schemas.xmlsoap.org/soap/envelope/") do
    xml.Header
    xml["soapenv"].Body # users may explicitly opt into the namespace
  end
end
result.doc.to_xml
# => <?xml version="1.0" encoding="utf-8"?>
#    <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
#      <Header/>
#      <soapenv:Body/>
#    </soapenv:Envelope>

For more information on namespace inheritance, please see Document#namespace_inheritance

Document Types

To create a document type (DTD), use the #doc method to get the current context document. Then call Node#create_internal_subset to create the DTD node.

For example, this Ruby:

builder = Nokogiri::XML::Builder.new do |xml|
  xml.doc.create_internal_subset(
    'html',
    "-//W3C//DTD HTML 4.01 Transitional//EN",
    "http://www.w3.org/TR/html4/loose.dtd"
  )
  xml.root do
    xml.foo
  end
end

puts builder.to_xml

Will output this xml:

<?xml version="1.0"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<root>
  <foo/>
</root>

Constant Summary

::Nokogiri::ClassResolver - Included

VALID_NAMESPACES

Class Method Summary

Instance Attribute Summary

  • #context rw

    A context object for use when the block has no arguments.

  • #doc rw

    The current Document object being built.

  • #parent rw

    The parent of the current node being built.

  • #arity rw Internal use only

Instance Method Summary

::Nokogiri::ClassResolver - Included

#related_class

Find a class constant within the.

Constructor Details

.new(options = {}, root = nil, &block) ⇒ Builder

Create a new Builder object. options are sent to the top level Document that is being built.

Building a document with a particular encoding for example:

Nokogiri::XML::Builder.new(:encoding => 'UTF-8') do |xml|
  #...
end
[ GitHub ]

  
# File 'lib/nokogiri/xml/builder.rb', line 307

def initialize(options = {}, root = nil, &block)
  if root
    @doc = root.document
    @parent = root
  else
    @parent = @doc = related_class("Document").new
  end

  @context = nil
  @arity = nil
  @ns = nil

  options = DEFAULT_DOCUMENT_OPTIONS.merge(options)
  options.each do |k, v|
    @doc.send(:"#{k}=", v)
  end

  return unless block

  @arity = block.arity
  if @arity <= 0
    @context = eval("self", block.binding)
    instance_eval(&block)
  else
    yield self
  end

  @parent = @doc
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(method, *args, &block)

This method is for internal use only.
[ GitHub ]

  
# File 'lib/nokogiri/xml/builder.rb', line 394

def method_missing(method, *args, &block) # :nodoc:
  if @context&.respond_to?(method)
    @context.send(method, *args, &block)
  else
    node = @doc.create_element(method.to_s.sub(/[_!]$/, ""), *args) do |n|
      # Set up the namespace
      if @ns.is_a?(Nokogiri::XML::Namespace)
        n.namespace = @ns
        @ns = nil
      end
    end

    if @ns.is_a?(Hash)
      node.namespace = node.namespace_definitions.find { |x| x.prefix == @ns[:pending] }
      if node.namespace.nil?
        raise ArgumentError, "Namespace #{@ns[:pending]} has not been defined"
      end

      @ns = nil
    end

    insert(node, &block)
  end
end

Class Method Details

.with(root, &block)

Create a builder with an existing root object. This is for use when you have an existing document that you would like to augment with builder methods. The builder context created will start with the given :: node.

For example:

doc = Nokogiri::XML(File.read('somedoc.xml'))
Nokogiri::XML::Builder.with(doc.at_css('some_tag')) do |xml|
  # ... Use normal builder methods here ...
  xml.awesome # add the "awesome" tag below "some_tag"
end
[ GitHub ]

  
# File 'lib/nokogiri/xml/builder.rb', line 294

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

Instance Attribute Details

#arity (rw)

This method is for internal use only.
[ GitHub ]

  
# File 'lib/nokogiri/xml/builder.rb', line 278

attr_accessor :arity # :nodoc:

#context (rw)

A context object for use when the block has no arguments

[ GitHub ]

  
# File 'lib/nokogiri/xml/builder.rb', line 276

attr_accessor :context

#doc (rw)

The current Document object being built

[ GitHub ]

  
# File 'lib/nokogiri/xml/builder.rb', line 270

attr_accessor :doc

#parent (rw)

The parent of the current node being built

[ GitHub ]

  
# File 'lib/nokogiri/xml/builder.rb', line 273

attr_accessor :parent

Instance Method Details

#<<(string)

Append the given raw ::Nokogiri::XML string to the document

[ GitHub ]

  
# File 'lib/nokogiri/xml/builder.rb', line 390

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

#[](ns)

Build a tag that is associated with namespace ns. Raises an ArgumentError if ns has not been defined higher in the tree.

[ GitHub ]

  
# File 'lib/nokogiri/xml/builder.rb', line 358

def [](ns)
  if @parent != @doc
    @ns = @parent.namespace_definitions.find { |x| x.prefix == ns.to_s }
  end
  return self if @ns

  @parent.ancestors.each do |a|
    next if a == doc

    @ns = a.namespace_definitions.find { |x| x.prefix == ns.to_s }
    return self if @ns
  end

  @ns = { pending: ns.to_s }
  self
end

#cdata(string)

Create a CDATA Node with content of string

[ GitHub ]

  
# File 'lib/nokogiri/xml/builder.rb', line 345

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

#comment(string)

Create a Comment Node with content of string

[ GitHub ]

  
# File 'lib/nokogiri/xml/builder.rb', line 351

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

#insert(node, &block) (private)

Insert node as a child of the current Node

[ GitHub ]

  
# File 'lib/nokogiri/xml/builder.rb', line 423

def insert(node, &block)
  node = @parent.add_child(node)
  if block
    begin
      old_parent = @parent
      @parent = node
      @arity ||= block.arity
      if @arity <= 0
        instance_eval(&block)
      else
        yield(self)
      end
    ensure
      @parent = old_parent
    end
  end
  NodeBuilder.new(node, self)
end

#text(string)

Create a Text Node with content of string

[ GitHub ]

  
# File 'lib/nokogiri/xml/builder.rb', line 339

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

#to_xml(*args)

Convert this Builder object to ::Nokogiri::XML

[ GitHub ]

  
# File 'lib/nokogiri/xml/builder.rb', line 377

def to_xml(*args)
  if Nokogiri.jruby?
    options = args.first.is_a?(Hash) ? args.shift : {}
    unless options[:save_with]
      options[:save_with] = Node::SaveOptions::AS_BUILDER
    end
    args.insert(0, options)
  end
  @doc.to_xml(*args)
end