class Oga::XML::Generator

Class for generating XML as a String based on an existing document.

Basic usage:

element = Oga::XML::Element.new(name: 'root')
element.inner_text = 'hello'

gen = Oga::XML::Generator.new(element)

gen.to_xml # => "<root>hello</root>"

@private

Public Class Methods

new(root) click to toggle source

@param [Oga::XML::Document|Oga::XML::Node] root The node to serialise.

# File lib/oga/xml/generator.rb, line 17
def initialize(root)
  @start = root

  if @start.respond_to?(:html?)
    @html_mode = @start.html?
  else
    @html_mode = false
  end
end

Public Instance Methods

after_element(element, output) click to toggle source

@param [Oga::XML::Element] element @param [String] output

# File lib/oga/xml/generator.rb, line 149
def after_element(element, output)
  output << "</#{element.expanded_name}>" unless self_closing?(element)
end
html_void_element?(element) click to toggle source
# File lib/oga/xml/generator.rb, line 220
def html_void_element?(element)
  @html_mode && HTML_VOID_ELEMENTS.allow?(element.name)
end
on_attribute(attr, output) click to toggle source

@param [Oga::XML::Attribute] attr @param [String] output

# File lib/oga/xml/generator.rb, line 155
def on_attribute(attr, output)
  name = attr.expanded_name
  enc_value = attr.value ? Entities.encode_attribute(attr.value) : nil

  output << %Q(#{name}="#{enc_value}")
end
on_cdata(node, output) click to toggle source

@param [Oga::XML::Cdata] node @param [String] output

# File lib/oga/xml/generator.rb, line 111
def on_cdata(node, output)
  output << "<![CDATA[#{node.text}]]>"
end
on_comment(node, output) click to toggle source

@param [Oga::XML::Comment] node @param [String] output

# File lib/oga/xml/generator.rb, line 117
def on_comment(node, output)
  output << "<!--#{node.text}-->"
end
on_doctype(node, output) click to toggle source

@param [Oga::XML::Doctype] node @param [String] output

# File lib/oga/xml/generator.rb, line 164
def on_doctype(node, output)
  output << "<!DOCTYPE #{node.name}"

  output << " #{node.type}" if node.type
  output << %Q{ "#{node.public_id}"} if node.public_id
  output << %Q{ "#{node.system_id}"} if node.system_id
  output << " [#{node.inline_rules}]" if node.inline_rules
  output << '>'
end
on_document(doc, output) click to toggle source

@param [Oga::XML::Document] doc @param [String] output

# File lib/oga/xml/generator.rb, line 176
def on_document(doc, output)
  if doc.xml_declaration
    on_xml_declaration(doc.xml_declaration, output)
    output << "\n"
  end

  if doc.doctype
    on_doctype(doc.doctype, output)
    output << "\n"
  end

  first_child = doc.children[0]

  # Prevent excessive newlines in case the next node is a newline text
  # node.
  if first_child.is_a?(Text) && first_child.text.start_with?("\r\n", "\n")
    output.chomp!
  end
end
on_element(element, output) click to toggle source

@param [Oga::XML::Element] element @param [String] output The content of the element.

# File lib/oga/xml/generator.rb, line 129
def on_element(element, output)
  name = element.expanded_name
  attrs = ''

  element.attributes.each do |attr|
    attrs << ' '
    on_attribute(attr, attrs)
  end

  if self_closing?(element)
    closing_tag = html_void_element?(element) ? '>' : ' />'

    output << "<#{name}#{attrs}#{closing_tag}"
  else
    output << "<#{name}#{attrs}>"
  end
end
on_processing_instruction(node, output) click to toggle source

@param [Oga::XML::ProcessingInstruction] node @param [String] output

# File lib/oga/xml/generator.rb, line 123
def on_processing_instruction(node, output)
  output << "<?#{node.name}#{node.text}?>"
end
on_text(node, output) click to toggle source

@param [Oga::XML::Text] node @param [String] output

# File lib/oga/xml/generator.rb, line 101
def on_text(node, output)
  if @html_mode && (parent = node.parent) && parent.literal_html_name?
    output << node.text
  else
    output << Entities.encode(node.text)
  end
end
on_xml_declaration(node, output) click to toggle source

@param [Oga::XML::XmlDeclaration] node @param [String] output

# File lib/oga/xml/generator.rb, line 198
def on_xml_declaration(node, output)
  output << '<?xml'

  [:version, :encoding, :standalone].each do |getter|
    value = node.send(getter)

    output << %Q{ #{getter}="#{value}"} if value
  end

  output << ' ?>'
end
self_closing?(element) click to toggle source

@param [Oga::XML::Element] element @return [TrueClass|FalseClass]

# File lib/oga/xml/generator.rb, line 212
def self_closing?(element)
  if @html_mode && !HTML_VOID_ELEMENTS.allow?(element.name)
    false
  else
    element.children.empty?
  end
end
to_xml() click to toggle source

Returns the XML for the current root node.

@return [String]

# File lib/oga/xml/generator.rb, line 30
def to_xml
  current = @start
  output = ''

  while current
    children = false

    # Determine what callback to use for the current node. The order of
    # this statement is based on how likely it is for an arm to match.
    case current
    when Oga::XML::Element
      callback = :on_element
      children = true
    when Oga::XML::Text
      callback = :on_text
    when Oga::XML::Cdata
      callback = :on_cdata
    when Oga::XML::Comment
      callback = :on_comment
    when Oga::XML::Attribute
      callback = :on_attribute
    when Oga::XML::XmlDeclaration
      # This must come before ProcessingInstruction since XmlDeclaration
      # extends ProcessingInstruction.
      callback = :on_xml_declaration
    when Oga::XML::ProcessingInstruction
      callback = :on_processing_instruction
    when Oga::XML::Doctype
      callback = :on_doctype
    when Oga::XML::Document
      callback = :on_document
      children = true
    else
      raise TypeError, "Can't serialize #{current.class} to XML"
    end

    send(callback, current, output)

    if child_node = children && current.children[0]
      current = child_node
    elsif current == @start
      # When we have reached the root node we should not process
      # any of its siblings. If we did we'd include XML in the
      # output from elements no part of the root node.
      after_element(current, output) if current.is_a?(Element)

      break
    else
      # Make sure to always close the current element before
      # moving to any siblings.
      after_element(current, output) if current.is_a?(Element)

      until next_node = current.is_a?(Node) && current.next
        if current.is_a?(Node) && current != @start
          current = current.parent
        end

        after_element(current, output) if current.is_a?(Element)

        break if current == @start
      end

      current = next_node
    end
  end

  output
end