require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
describe "SAXMachine" do
describe "element" do
describe "when parsing a single element" do
before :each do
@klass = Class.new do
include SAXMachine
element :title
end
end
it "should provide mass assignment through initialize method" do
document = @klass.new(title: 'Title')
document.title.should == 'Title'
end
it "should provide an accessor" do
document = @klass.new
document.title = "Title"
document.title.should == "Title"
end
it "should allow introspection of the elements" do
@klass.column_names.should =~ [:title]
end
it "should not overwrite the getter is there is already one present" do
@klass = Class.new do
def title
"#{@title} ***"
end
include SAXMachine
element :title
end
document = @klass.new
document.title = "Title"
document.title.should == "Title ***"
end
it "should not overwrite the setter if there is already one present" do
@klass = Class.new do
def title=(val)
@title = "#{val} **"
end
include SAXMachine
element :title
end
document = @klass.new
document.title = "Title"
document.title.should == "Title **"
end
describe "the class attribute" do
before(:each) do
@klass = Class.new do
include SAXMachine
element :date, :class => DateTime
end
@document = @klass.new
@document.date = Time.now.iso8601
end
it "should be available" do
@klass.data_class(:date).should == DateTime
end
it "should handle an integer class" do
@klass = Class.new do
include SAXMachine
element :number, :class => Integer
end
document = @klass.parse("5")
document.number.should == 5
end
it "should handle an float class" do
@klass = Class.new do
include SAXMachine
element :number, :class => Float
end
document = @klass.parse("5.5")
document.number.should == 5.5
end
it "should handle an string class" do
@klass = Class.new do
include SAXMachine
element :number, :class => String
end
document = @klass.parse("5.5")
document.number.should == "5.5"
end
it "should handle a time class" do
@klass = Class.new do
include SAXMachine
element :time, :class => Time
end
document = @klass.parse("")
document.time.should == Time.utc(1994, 2, 4, 6, 20, 0, 0)
end
end
describe "the required attribute" do
it "should be available" do
@klass = Class.new do
include SAXMachine
element :date, :required => true
end
@klass.required?(:date).should be_true
end
end
it "should not overwrite the accessor when the element is not present" do
document = @klass.new
document.title = "Title"
document.parse("")
document.title.should == "Title"
end
it "should overwrite the value when the element is present" do
document = @klass.new
document.title = "Old title"
document.parse("
New title")
document.title.should == "New title"
end
it "should save the element text into an accessor" do
document = @klass.parse("My Title")
document.title.should == "My Title"
end
if RUBY_VERSION >= "1.9.0"
it "should keep the document encoding for elements" do
data = "My Title"
data.encode!("utf-8")
document = @klass.parse(data)
document.title.encoding.should == data.encoding
end
end
it "should save cdata into an accessor" do
document = @klass.parse("")
document.title.should == "A Title"
end
it "should save the element text into an accessor when there are multiple elements" do
document = @klass.parse("My Titlebar")
document.title.should == "My Title"
end
it "should save the first element text when there are multiple of the same element" do
document = @klass.parse("My Titlebar")
document.title.should == "My Title"
end
end
describe "when parsing multiple elements" do
before :each do
@klass = Class.new do
include SAXMachine
element :title
element :name
end
end
it "should save the element text for a second tag" do
document = @klass.parse("My TitlePaul")
document.name.should == "Paul"
document.title.should == "My Title"
end
it "should not overwrite the getter is there is already one present" do
@klass = Class.new do
def items
[]
end
include SAXMachine
elements :items
end
document = @klass.new
document.items = [1, 2, 3, 4]
document.items.should == []
end
it "should not overwrite the setter if there is already one present" do
@klass = Class.new do
def items=(val)
@items = [1, *val]
end
include SAXMachine
elements :items
end
document = @klass.new
document.items = [2, 3]
document.items.should == [1, 2, 3]
end
end
describe "when using options for parsing elements" do
describe "using the 'as' option" do
before :each do
@klass = Class.new do
include SAXMachine
element :description, :as => :summary
end
end
it "should provide an accessor using the 'as' name" do
document = @klass.new
document.summary = "a small summary"
document.summary.should == "a small summary"
end
it "should save the element text into the 'as' accessor" do
document = @klass.parse("here is a description")
document.summary.should == "here is a description"
end
end
describe "using the :with option" do
describe "and the :value option" do
before :each do
@klass = Class.new do
include SAXMachine
element :link, :value => :href, :with => {:foo => "bar"}
end
end
it "should save the value of a matching element" do
document = @klass.parse("asdf")
document.link.should == "test"
end
it "should save the value of the first matching element" do
document = @klass.parse("")
document.link.should == "first"
end
describe "and the :as option" do
before :each do
@klass = Class.new do
include SAXMachine
element :link, :value => :href, :as => :url, :with => {:foo => "bar"}
element :link, :value => :href, :as => :second_url, :with => {:asdf => "jkl"}
end
end
it "should save the value of the first matching element" do
document = @klass.parse("")
document.url.should == "first"
document.second_url.should == "second"
end
end
end
describe "with only one element" do
before :each do
@klass = Class.new do
include SAXMachine
element :link, :with => {:foo => "bar"}
end
end
it "should save the text of an element that has matching attributes" do
document = @klass.parse("match")
document.link.should == "match"
end
it "should not save the text of an element that doesn't have matching attributes" do
document = @klass.parse("no match")
document.link.should be_nil
end
it "should save the text of an element that has matching attributes when it is the second of that type" do
document = @klass.parse("no matchmatch")
document.link.should == "match"
end
it "should save the text of an element that has matching attributes plus a few more" do
document = @klass.parse("no matchmatch")
document.link.should == "match"
end
end
describe "with multiple elements of same tag" do
before :each do
@klass = Class.new do
include SAXMachine
element :link, :as => :first, :with => {:foo => "bar"}
element :link, :as => :second, :with => {:asdf => "jkl"}
end
end
it "should match the first element" do
document = @klass.parse("no matchfirst matchno match")
document.first.should == "first match"
end
it "should match the second element" do
document = @klass.parse("no matchfirst matchsecond matchhi")
document.second.should == "second match"
end
end
describe "with only one element as a regular expression" do
before :each do
@klass = Class.new do
include SAXMachine
element :link, :with => {:foo => /ar$/}
end
end
it "should save the text of an element that has matching attributes" do
document = @klass.parse("match")
document.link.should == "match"
end
it "should not save the text of an element that doesn't have matching attributes" do
document = @klass.parse("no match")
document.link.should be_nil
end
it "should save the text of an element that has matching attributes when it is the second of that type" do
document = @klass.parse("no matchmatch")
document.link.should == "match"
end
it "should save the text of an element that has matching attributes plus a few more" do
document = @klass.parse("no matchmatch")
document.link.should == "match"
end
end
end # using the 'with' option
describe "using the 'value' option" do
before :each do
@klass = Class.new do
include SAXMachine
element :link, :value => :foo
end
end
it "should save the attribute value" do
document = @klass.parse("hello")
document.link.should == 'test'
end
it "should save the attribute value when there is no text enclosed by the tag" do
document = @klass.parse("")
document.link.should == 'test'
end
it "should save the attribute value when the tag close is in the open" do
document = @klass.parse("")
document.link.should == 'test'
end
it "should save two different attribute values on a single tag" do
@klass = Class.new do
include SAXMachine
element :link, :value => :foo, :as => :first
element :link, :value => :bar, :as => :second
end
document = @klass.parse("")
document.first.should == "foo value"
document.second.should == "bar value"
end
it "should not fail if one of the attribute hasn't been defined" do
@klass = Class.new do
include SAXMachine
element :link, :value => :foo, :as => :first
element :link, :value => :bar, :as => :second
end
document = @klass.parse("")
document.first.should == "foo value"
document.second.should be_nil
end
end
describe "when desiring both the content and attributes of an element" do
before :each do
@klass = Class.new do
include SAXMachine
element :link
element :link, :value => :foo, :as => :link_foo
element :link, :value => :bar, :as => :link_bar
end
end
it "should parse the element and attribute values" do
document = @klass.parse("hello")
document.link.should == 'hello'
document.link_foo.should == 'test1'
document.link_bar.should == 'test2'
end
end
end
end
describe "elements" do
describe "when parsing multiple elements" do
before :each do
@klass = Class.new do
include SAXMachine
elements :entry, :as => :entries
end
end
it "should provide a collection accessor" do
document = @klass.new
document.entries << :foo
document.entries.should == [:foo]
end
it "should parse a single element" do
document = @klass.parse("hello")
document.entries.should == ["hello"]
end
it "should parse multiple elements" do
document = @klass.parse("helloworld")
document.entries.should == ["hello", "world"]
end
it "should parse multiple elements when taking an attribute value" do
attribute_klass = Class.new do
include SAXMachine
elements :entry, :as => :entries, :value => :foo
end
doc = attribute_klass.parse("")
doc.entries.should == ["asdf", "jkl"]
end
end
describe "when using the with and class options" do
before :each do
class Bar
include SAXMachine
element :title
end
class Foo
include SAXMachine
element :title
end
class Item
include SAXMachine
end
@klass = Class.new do
include SAXMachine
elements :item, :as => :items, :with => {:type => 'Bar'}, :class => Bar
elements :item, :as => :items, :with => {:type => /Foo/}, :class => Foo
end
end
it "should cast into the correct class" do
document = @klass.parse("Bar titleFoo title")
document.items.size.should == 2
document.items.first.should be_a(Bar)
document.items.first.title.should == "Bar title"
document.items.last.should be_a(Foo)
document.items.last.title.should == "Foo title"
end
end
describe "when using the class option" do
before :each do
class Foo
include SAXMachine
element :title
end
@klass = Class.new do
include SAXMachine
elements :entry, :as => :entries, :class => Foo
end
end
it "should parse a single element with children" do
document = @klass.parse("a title")
document.entries.size.should == 1
document.entries.first.title.should == "a title"
end
it "should parse multiple elements with children" do
document = @klass.parse("title 1title 2")
document.entries.size.should == 2
document.entries.first.title.should == "title 1"
document.entries.last.title.should == "title 2"
end
it "should not parse a top level element that is specified only in a child" do
document = @klass.parse("no parsecorrect title")
document.entries.size.should == 1
document.entries.first.title.should == "correct title"
end
it "should parse elements, and make attributes and inner text available" do
class Related
include SAXMachine
element 'related', :as=>:item
element 'related', :as=>:attr, :value=>'attr'
end
class Foo
elements 'related', :as=>'items', :class=>Related
end
doc = Foo.parse(%{somethingsomethingelse})
doc.items.first.should_not be_nil
doc.items.size.should == 2
doc.items.first.item.should == 'something'
doc.items.last.item.should == 'somethingelse'
end
it "should parse out an attribute value from the tag that starts the collection" do
class Foo
element :entry, :value => :href, :as => :url
end
document = @klass.parse("paul")
document.entries.size.should == 1
document.entries.first.title.should == "paul"
document.entries.first.url.should == "http://pauldix.net"
end
end
end
describe "when dealing with element names containing dashes" do
it 'should automatically convert dashes to underscores' do
class Dashes
include SAXMachine
element :dashed_element
end
parsed = Dashes.parse('Text')
parsed.dashed_element.should eq "Text"
end
end
describe "full example" do
before :each do
@xml = File.read('spec/sax-machine/atom.xml')
class AtomEntry
include SAXMachine
element :title
element :name, :as => :author
element "feedburner:origLink", :as => :url
element :link, :as => :alternate, :value => :href, :with => {:type => "text/html", :rel => "alternate"}
element :summary
element :content
element :published
end
class Atom
include SAXMachine
element :title
element :link, :value => :href, :as => :url, :with => {:type => "text/html"}
element :link, :value => :href, :as => :feed_url, :with => {:type => "application/atom+xml"}
elements :entry, :as => :entries, :class => AtomEntry
end
end # before
it "should parse the url" do
f = Atom.parse(@xml)
f.url.should == "http://www.pauldix.net/"
end
it "should parse entry url" do
f = Atom.parse(@xml)
f.entries.first.url.should == "http://www.pauldix.net/2008/09/marshal-data-to.html?param1=1¶m2=2"
f.entries.first.alternate.should == "http://feeds.feedburner.com/~r/PaulDixExplainsNothing/~3/383536354/marshal-data-to.html?param1=1¶m2=2"
end
end
describe "parsing a tree" do
before do
@xml = %[
FirstSecond
]
class CategoryCollection;
end
class Category
include SAXMachine
attr_accessor :id
element :category, :value => :id, :as => :id
element :title
element :categories, :as => :collection, :class => CategoryCollection
ancestor :ancestor
end
class CategoryCollection
include SAXMachine
elements :category, :as => :categories, :class => Category
end
@collection = CategoryCollection.parse(@xml)
end
it "should parse the first category" do
@collection.categories.first.id.should == "1"
@collection.categories.first.title.should == "First"
@collection.categories.first.ancestor.should == @collection
end
it "should parse the nested category" do
@collection.categories.first.collection.categories.first.id.should == "2"
@collection.categories.first.collection.categories.first.title.should == "Second"
end
end
describe "parsing a tree without a collection class" do
before do
@xml = %[
FirstSecond
]
class CategoryTree
include SAXMachine
attr_accessor :id
element :category, :value => :id, :as => :id
element :title
elements :category, :as => :categories, :class => CategoryTree
end
@collection = CategoryTree.parse(@xml)
end
it "should parse the first category" do
@collection.categories.first.id.should == "1"
@collection.categories.first.title.should == "First"
end
it "should parse the nested category" do
@collection.categories.first.categories.first.id.should == "2"
@collection.categories.first.categories.first.title.should == "Second"
end
end
describe "with element deeper inside the xml structure" do
before do
@xml = %[
Hello
]
@klass = Class.new do
include SAXMachine
attr_accessor :id
element :item, :value => "id", :as => :id
element :title
end
@item = @klass.parse(@xml)
end
it "should have an id" do
@item.id.should == "1"
end
it "should have a title" do
@item.title.should == "Hello"
end
end
describe "with config to pull multiple attributes" do
before do
@xml = %[
]
class AuthorElement
include SAXMachine
attribute :name
attribute :role
end
class ItemElement
include SAXMachine
element :author, :class => AuthorElement
end
@item = ItemElement.parse(@xml)
end
it 'should have the child element' do
@item.author.should_not be_nil
end
it 'should have the author name' do
@item.author.name.should == 'John Doe'
end
it 'should have the author role' do
@item.author.role.should == 'writer'
end
end
describe "with multiple elements and multiple attributes" do
before do
@xml = %[
]
class AuthorElement2
include SAXMachine
attribute :name
attribute :role
end
class ItemElement2
include SAXMachine
elements :author, :as => :authors, :class => AuthorElement2
end
@item = ItemElement2.parse(@xml)
end
it 'should have the child elements' do
@item.authors.should_not be_nil
@item.authors.count.should == 2
end
it 'should have the author names' do
@item.authors.first.name.should == 'John Doe'
@item.authors.last.name.should == 'Jane Doe'
end
it 'should have the author roles' do
@item.authors.first.role.should == 'writer'
@item.authors.last.role.should == 'artist'
end
end
describe "with mixed attributes and element values" do
before do
@xml = %[
John Doe
]
class AuthorElement3
include SAXMachine
value :name
attribute :role
end
class ItemElement3
include SAXMachine
element :author, :class => AuthorElement3
end
@item = ItemElement3.parse(@xml)
end
it 'should have the child elements' do
@item.author.should_not be_nil
end
it 'should have the author names' do
@item.author.name.should == 'John Doe'
end
it 'should have the author roles' do
@item.author.role.should == 'writer'
end
end
describe "with multiple mixed attributes and element values" do
before do
@xml = %[
sweetJohn DoeJane Doe
]
class AuthorElement4
include SAXMachine
value :name
attribute :role
end
class ItemElement4
include SAXMachine
element :title
elements :author, :as => :authors, :class => AuthorElement4
def title=(blah)
#raise 'woo'
@title = blah
end
end
@item = ItemElement4.parse(@xml)
end
it 'should have the title' do
@item.title.should == 'sweet'
end
it 'should have the child elements' do
@item.authors.should_not be_nil
@item.authors.count.should == 2
end
it 'should have the author names' do
@item.authors.first.name.should == 'John Doe'
@item.authors.last.name.should == 'Jane Doe'
end
it 'should have the author roles' do
@item.authors.first.role.should == 'writer'
@item.authors.last.role.should == 'artist'
end
end
end