Class: Oga::XML::Generator

Inherits:
Object
  • Object
show all
Defined in:
lib/oga/xml/generator.rb

Overview

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>"

Instance Method Summary collapse

Constructor Details

#initialize(root) ⇒ Generator

Returns a new instance of Generator

Parameters:



17
18
19
20
21
22
23
24
25
# 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

Instance Method Details

#after_element(element, output) ⇒ Object

Parameters:



149
150
151
# 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) ⇒ Boolean

Returns:

  • (Boolean)


220
221
222
# 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) ⇒ Object

Parameters:



155
156
157
158
159
160
# 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) ⇒ Object

Parameters:



111
112
113
# File 'lib/oga/xml/generator.rb', line 111

def on_cdata(node, output)
  output << "<![CDATA[#{node.text}]]>"
end

#on_comment(node, output) ⇒ Object

Parameters:



117
118
119
# File 'lib/oga/xml/generator.rb', line 117

def on_comment(node, output)
  output << "<!--#{node.text}-->"
end

#on_doctype(node, output) ⇒ Object

Parameters:



164
165
166
167
168
169
170
171
172
# 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) ⇒ Object

Parameters:



176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
# 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) ⇒ Object

Parameters:



129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
# 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) ⇒ Object

Parameters:



123
124
125
# File 'lib/oga/xml/generator.rb', line 123

def on_processing_instruction(node, output)
  output << "<?#{node.name}#{node.text}?>"
end

#on_text(node, output) ⇒ Object

Parameters:



101
102
103
104
105
106
107
# 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) ⇒ Object

Parameters:



198
199
200
201
202
203
204
205
206
207
208
# 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) ⇒ TrueClass|FalseClass

Parameters:

Returns:

  • (TrueClass|FalseClass)


212
213
214
215
216
217
218
# 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_xmlString

Returns the XML for the current root node.

Returns:

  • (String)


30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# 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