Tests + docs for the TreeBuilder class.

This commit is contained in:
Yorick Peterse 2014-03-28 17:11:54 +01:00
parent 6d866523b8
commit f99c13b516
2 changed files with 246 additions and 17 deletions

View File

@ -1,14 +1,21 @@
module Oga
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
# @return [Oga::AST::Node]
# Basic usage:
#
# builder = Oga::XML::TreeBuilder.new
# ast = s(:element, ...)
#
# builder.process(ast) # => #<Oga::XML::Document ...>
#
class TreeBuilder < ::AST::Processor
attr_reader :ast
##
# @param [Oga::AST::Node] node
# @return [Oga::XML::Document]
#
def on_document(node)
document = Document.new
document.children = process_all(node)
@ -20,10 +27,23 @@ module Oga
return document
end
##
# @param [Oga::AST::Node] node
# @return [Oga::XML::Comment]
#
def on_comment(node)
return Comment.new(:text => node.children[0])
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)
ns, name, attr, *children = *node
@ -32,7 +52,7 @@ module Oga
end
if children
children = process_all(children)
children = process_all(children.compact)
end
element = Element.new(
@ -42,38 +62,73 @@ module Oga
:children => children
)
element.children.each_with_index do |child, index|
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
process_children(element)
return element
end
##
# @param [Oga::AST::Node] node
# @return [Oga::XML::Text]
#
def on_text(node)
return Text.new(:text => node.children[0])
end
##
# @param [Oga::AST::Node] node
# @return [Oga::XML::Cdata]
#
def on_cdata(node)
return Cdata.new(:text => node.children[0])
end
##
# Converts a `(attributes)` node into a Hash.
#
# @param [Oga::AST::Node] node
# @return [Hash]
#
def on_attributes(node)
pairs = process_all(node)
return Hash[pairs]
end
##
# @param [Oga::AST::Node] node
# @return [Array]
#
def on_attribute(node)
return *node
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 # XML
end # Oga

View File

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