Tests + docs for the TreeBuilder class.
This commit is contained in:
parent
6d866523b8
commit
f99c13b516
|
@ -1,14 +1,21 @@
|
||||||
module Oga
|
module Oga
|
||||||
module XML
|
module XML
|
||||||
##
|
##
|
||||||
# Class for building a DOM tree of XML/HTML nodes.
|
# The TreeBuilder class turns an AST into a DOM tree. This DOM tree can be
|
||||||
|
# traversed by requesting child elements, parent elements, etc.
|
||||||
#
|
#
|
||||||
# @!attribute [r] ast
|
# Basic usage:
|
||||||
# @return [Oga::AST::Node]
|
#
|
||||||
|
# builder = Oga::XML::TreeBuilder.new
|
||||||
|
# ast = s(:element, ...)
|
||||||
|
#
|
||||||
|
# builder.process(ast) # => #<Oga::XML::Document ...>
|
||||||
#
|
#
|
||||||
class TreeBuilder < ::AST::Processor
|
class TreeBuilder < ::AST::Processor
|
||||||
attr_reader :ast
|
##
|
||||||
|
# @param [Oga::AST::Node] node
|
||||||
|
# @return [Oga::XML::Document]
|
||||||
|
#
|
||||||
def on_document(node)
|
def on_document(node)
|
||||||
document = Document.new
|
document = Document.new
|
||||||
document.children = process_all(node)
|
document.children = process_all(node)
|
||||||
|
@ -20,10 +27,23 @@ module Oga
|
||||||
return document
|
return document
|
||||||
end
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# @param [Oga::AST::Node] node
|
||||||
|
# @return [Oga::XML::Comment]
|
||||||
|
#
|
||||||
def on_comment(node)
|
def on_comment(node)
|
||||||
return Comment.new(:text => node.children[0])
|
return Comment.new(:text => node.children[0])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# Processes an `(element)` node and its child elements.
|
||||||
|
#
|
||||||
|
# An element can have a parent, previous and next element as well as a
|
||||||
|
# number of child elements.
|
||||||
|
#
|
||||||
|
# @param [Oga::AST::Node] node
|
||||||
|
# @return [Oga::XML::Element]
|
||||||
|
#
|
||||||
def on_element(node)
|
def on_element(node)
|
||||||
ns, name, attr, *children = *node
|
ns, name, attr, *children = *node
|
||||||
|
|
||||||
|
@ -32,7 +52,7 @@ module Oga
|
||||||
end
|
end
|
||||||
|
|
||||||
if children
|
if children
|
||||||
children = process_all(children)
|
children = process_all(children.compact)
|
||||||
end
|
end
|
||||||
|
|
||||||
element = Element.new(
|
element = Element.new(
|
||||||
|
@ -42,38 +62,73 @@ module Oga
|
||||||
:children => children
|
:children => children
|
||||||
)
|
)
|
||||||
|
|
||||||
element.children.each_with_index do |child, index|
|
process_children(element)
|
||||||
if index > 0
|
|
||||||
child.previous = element.children[index - 1]
|
|
||||||
end
|
|
||||||
|
|
||||||
if index + 1 <= element.children.length
|
|
||||||
child.next = element.children[index + 1]
|
|
||||||
end
|
|
||||||
|
|
||||||
child.parent = element
|
|
||||||
end
|
|
||||||
|
|
||||||
return element
|
return element
|
||||||
end
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# @param [Oga::AST::Node] node
|
||||||
|
# @return [Oga::XML::Text]
|
||||||
|
#
|
||||||
def on_text(node)
|
def on_text(node)
|
||||||
return Text.new(:text => node.children[0])
|
return Text.new(:text => node.children[0])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# @param [Oga::AST::Node] node
|
||||||
|
# @return [Oga::XML::Cdata]
|
||||||
|
#
|
||||||
def on_cdata(node)
|
def on_cdata(node)
|
||||||
return Cdata.new(:text => node.children[0])
|
return Cdata.new(:text => node.children[0])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# Converts a `(attributes)` node into a Hash.
|
||||||
|
#
|
||||||
|
# @param [Oga::AST::Node] node
|
||||||
|
# @return [Hash]
|
||||||
|
#
|
||||||
def on_attributes(node)
|
def on_attributes(node)
|
||||||
pairs = process_all(node)
|
pairs = process_all(node)
|
||||||
|
|
||||||
return Hash[pairs]
|
return Hash[pairs]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# @param [Oga::AST::Node] node
|
||||||
|
# @return [Array]
|
||||||
|
#
|
||||||
def on_attribute(node)
|
def on_attribute(node)
|
||||||
return *node
|
return *node
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
##
|
||||||
|
# Iterates over the child elements of an element and assigns the parent,
|
||||||
|
# previous and next elements. The supplied object is modified in place.
|
||||||
|
#
|
||||||
|
# @param [Oga::XML::Element] element
|
||||||
|
#
|
||||||
|
def process_children(element)
|
||||||
|
amount = element.children.length
|
||||||
|
|
||||||
|
element.children.each_with_index do |child, index|
|
||||||
|
prev_index = index - 1
|
||||||
|
next_index = index + 1
|
||||||
|
|
||||||
|
if index > 0
|
||||||
|
child.previous = element.children[prev_index]
|
||||||
|
end
|
||||||
|
|
||||||
|
if next_index <= amount
|
||||||
|
child.next = element.children[next_index]
|
||||||
|
end
|
||||||
|
|
||||||
|
child.parent = element
|
||||||
|
end
|
||||||
|
end
|
||||||
end # TreeBuilder
|
end # TreeBuilder
|
||||||
end # XML
|
end # XML
|
||||||
end # Oga
|
end # Oga
|
||||||
|
|
|
@ -0,0 +1,174 @@
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
describe Oga::XML::TreeBuilder do
|
||||||
|
before do
|
||||||
|
@builder = described_class.new
|
||||||
|
end
|
||||||
|
|
||||||
|
context '#on_element' do
|
||||||
|
context 'simple elements' do
|
||||||
|
before do
|
||||||
|
node = s(:element, 'foo', 'p', nil, nil)
|
||||||
|
@tag = @builder.process(node)
|
||||||
|
end
|
||||||
|
|
||||||
|
example 'return a Element node' do
|
||||||
|
@tag.is_a?(Oga::XML::Element).should == true
|
||||||
|
end
|
||||||
|
|
||||||
|
example 'include the name of the element' do
|
||||||
|
@tag.name.should == 'p'
|
||||||
|
end
|
||||||
|
|
||||||
|
example 'include the namespace of the element' do
|
||||||
|
@tag.namespace.should == 'foo'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'elements with attributes' do
|
||||||
|
before do
|
||||||
|
node = s(
|
||||||
|
:element,
|
||||||
|
nil,
|
||||||
|
'p',
|
||||||
|
s(:attributes, s(:attribute, 'key', 'value')),
|
||||||
|
nil
|
||||||
|
)
|
||||||
|
|
||||||
|
@tag = @builder.process(node)
|
||||||
|
end
|
||||||
|
|
||||||
|
example 'include the name of the element' do
|
||||||
|
@tag.name.should == 'p'
|
||||||
|
end
|
||||||
|
|
||||||
|
example 'include the attributes' do
|
||||||
|
@tag.attributes.should == {'key' => 'value'}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'elements with parent elements' do
|
||||||
|
before do
|
||||||
|
node = s(:element, nil, 'p', nil, s(:element, nil, 'span', nil, nil))
|
||||||
|
@tag = @builder.process(node)
|
||||||
|
end
|
||||||
|
|
||||||
|
example 'set the parent element' do
|
||||||
|
@tag.children[0].parent.should == @tag
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'elements with next elements' do
|
||||||
|
before do
|
||||||
|
node = s(
|
||||||
|
:element,
|
||||||
|
nil,
|
||||||
|
'p',
|
||||||
|
nil,
|
||||||
|
s(:element, nil, 'a', nil, nil),
|
||||||
|
s(:element, nil, 'span', nil, nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
@tag = @builder.process(node)
|
||||||
|
end
|
||||||
|
|
||||||
|
example 'set the next element' do
|
||||||
|
@tag.children[0].next.should == @tag.children[1]
|
||||||
|
end
|
||||||
|
|
||||||
|
example 'do not set the next element for the last element' do
|
||||||
|
@tag.children[1].next.should == nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'elements with previous elements' do
|
||||||
|
before do
|
||||||
|
node = s(
|
||||||
|
:element,
|
||||||
|
nil,
|
||||||
|
'p',
|
||||||
|
nil,
|
||||||
|
s(:element, nil, 'a', nil, nil),
|
||||||
|
s(:element, nil, 'span', nil, nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
@tag = @builder.process(node)
|
||||||
|
end
|
||||||
|
|
||||||
|
example 'set the previous element' do
|
||||||
|
@tag.children[1].previous.should == @tag.children[0]
|
||||||
|
end
|
||||||
|
|
||||||
|
example 'do not set the previous element for the first element' do
|
||||||
|
@tag.children[0].previous.should == nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'elements with child elements' do
|
||||||
|
before do
|
||||||
|
node = s(:element, nil, 'p', nil, s(:element, nil, 'span', nil, nil))
|
||||||
|
@tag = @builder.process(node)
|
||||||
|
end
|
||||||
|
|
||||||
|
example 'include the name of the element' do
|
||||||
|
@tag.name.should == 'p'
|
||||||
|
end
|
||||||
|
|
||||||
|
example 'include the child element' do
|
||||||
|
@tag.children[0].is_a?(Oga::XML::Element).should == true
|
||||||
|
end
|
||||||
|
|
||||||
|
example 'include the name of the child element' do
|
||||||
|
@tag.children[0].name.should == 'span'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context '#on_text' do
|
||||||
|
before do
|
||||||
|
node = s(:text, 'Hello')
|
||||||
|
@tag = @builder.process(node)
|
||||||
|
end
|
||||||
|
|
||||||
|
example 'return a Text node' do
|
||||||
|
@tag.is_a?(Oga::XML::Text).should == true
|
||||||
|
end
|
||||||
|
|
||||||
|
example 'include the text of the node' do
|
||||||
|
@tag.text.should == 'Hello'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context '#on_cdata' do
|
||||||
|
before do
|
||||||
|
node = s(:cdata, 'Hello')
|
||||||
|
@tag = @builder.process(node)
|
||||||
|
end
|
||||||
|
|
||||||
|
example 'return a Cdata node' do
|
||||||
|
@tag.is_a?(Oga::XML::Cdata).should == true
|
||||||
|
end
|
||||||
|
|
||||||
|
example 'include the text of the node' do
|
||||||
|
@tag.text.should == 'Hello'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context '#on_attributes' do
|
||||||
|
before do
|
||||||
|
@node = s(
|
||||||
|
:attributes,
|
||||||
|
s(:attribute, 'foo', 'bar'),
|
||||||
|
s(:attribute, 'baz', 'wat')
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
example 'return the attributes as a Hash' do
|
||||||
|
@builder.process(@node).should == {'foo' => 'bar', 'baz' => 'wat'}
|
||||||
|
end
|
||||||
|
|
||||||
|
example 'return an empty Hash by default' do
|
||||||
|
@builder.process(s(:attributes)).should == {}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in New Issue