sergio 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source 'http://rubygems.org'
2
+
3
+ gem 'rake'
4
+ gem 'rspec'
5
+ gem 'nokogiri'
data/Gemfile.lock ADDED
@@ -0,0 +1,24 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ diff-lcs (1.1.2)
5
+ nokogiri (1.4.4)
6
+ rake (0.9.0)
7
+ rcov (0.9.9)
8
+ rspec (2.6.0)
9
+ rspec-core (~> 2.6.0)
10
+ rspec-expectations (~> 2.6.0)
11
+ rspec-mocks (~> 2.6.0)
12
+ rspec-core (2.6.3)
13
+ rspec-expectations (2.6.0)
14
+ diff-lcs (~> 1.1.2)
15
+ rspec-mocks (2.6.0)
16
+
17
+ PLATFORMS
18
+ ruby
19
+
20
+ DEPENDENCIES
21
+ nokogiri
22
+ rake
23
+ rcov
24
+ rspec
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # Get your spec rake tasks working in RSpec 2.0
2
+
3
+ require 'rspec/core/rake_task'
4
+
5
+ desc 'Default: run specs.'
6
+ task :default => :spec
7
+
8
+ desc "Run specs"
9
+ RSpec::Core::RakeTask.new do |t|
10
+ t.pattern = "./spec/**/*_spec.rb" # don't need this, it's default.
11
+ # Put spec opts in a file named .rspec in root
12
+ end
data/lib/sergio.rb ADDED
@@ -0,0 +1,32 @@
1
+ require 'nokogiri'
2
+ require File.dirname(__FILE__) + '/sergio/hash_methods'
3
+ require File.dirname(__FILE__) + '/sergio/sergio_sax'
4
+ require File.dirname(__FILE__) + '/sergio/sergio_element'
5
+ require File.dirname(__FILE__) + '/sergio/sergio_config'
6
+ require File.dirname(__FILE__) + '/sergio/sergio_parsed_document'
7
+
8
+ module Sergio
9
+ def self.included(base)
10
+ base.extend(ClassMethods)
11
+ include(Sergio::HashMethods)
12
+ end
13
+
14
+ def sergio_parsed_document
15
+ @sergio_parsed_document ||= Sergio::ParsedDocument.new
16
+ end
17
+
18
+ def parse(doc)
19
+ Nokogiri::XML::SAX::Parser.new(SergioSax.new(self)).parse(doc)
20
+ sergio_parsed_document.parsed_hash
21
+ end
22
+
23
+ module ClassMethods
24
+ def element(name, newname = nil, args = {}, &blk)
25
+ sergio_config.element(name, newname, args, &blk)
26
+ end
27
+
28
+ def sergio_config
29
+ @sergio_config ||= Sergio::Config.new
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,58 @@
1
+ module Sergio
2
+ module HashMethods
3
+ def hash_from_path(path, val)
4
+ path = path.clone
5
+ k = path.shift
6
+ h = {}
7
+ h[k] = if path.empty?
8
+ val
9
+ else
10
+ hash_from_path(path, val)
11
+ end
12
+ h
13
+ end
14
+
15
+ def value_at_path(path, hash)
16
+ k = path.shift
17
+ k = k.is_a?(Array) ? k[0] : k
18
+ v = hash[k]
19
+ if v
20
+ if v.is_a?(Hash) && path.length > 0
21
+ value_at_path(path, v)
22
+ else
23
+ v
24
+ end
25
+ else
26
+ nil
27
+ end
28
+ end
29
+
30
+ def hash_recursive_merge_to_arrays(lval, rval)
31
+ r = {}
32
+ v = lval.merge(rval) do |key, oldval, newval|
33
+ v = if oldval.class == lval.class
34
+ hash_recursive_merge_to_arrays(oldval, newval)
35
+ else
36
+ if oldval.is_a?(Array)
37
+ oldval << newval
38
+ elsif newval.is_a?(Array)
39
+ newval << oldval
40
+ else
41
+ [oldval] << newval
42
+ end
43
+ end
44
+ r[key] = v
45
+ end
46
+ v
47
+ end
48
+
49
+ #FROM https://gist.github.com/6391/62b6aae9206abe7b3fea6d4659e4c246f8cf7632
50
+ def hash_recursive_merge(lval, rval)
51
+ r = {}
52
+ v = lval.merge(rval) do |key, oldval, newval|
53
+ r[key] = oldval.class == Hash && newval.class == Hash ? hash_recursive_merge(oldval, newval) : newval
54
+ end
55
+ v
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,81 @@
1
+ module Sergio
2
+ class Config
3
+ include HashMethods
4
+ attr_accessor :sergio_elements, :new_path, :current_path
5
+ def initialize
6
+ @parsing_elements = {}
7
+ @new_path, @current_path = [], []
8
+ end
9
+
10
+ def element(name, newname = nil, args = {}, &blk)
11
+ if newname.is_a?(Hash)
12
+ args = newname
13
+ newname = nil
14
+ end
15
+
16
+ args = args.inject({}) do |args, k_v|
17
+ args[k_v[0].to_sym] = k_v[1]
18
+ args
19
+ end
20
+
21
+ args[:attribute] ||= '@text'
22
+ args[:having] ||= false
23
+ newname = name unless newname
24
+ name = [name] unless name.is_a?(Array)
25
+ newname = [newname] unless newname.is_a?(Array)
26
+
27
+ name.each do |n|
28
+ @current_path << n
29
+ end
30
+
31
+ newname.each do |n|
32
+ @new_path << n
33
+ end
34
+
35
+ unless block_given?
36
+ blk = lambda do |val|
37
+ val
38
+ end
39
+ end
40
+
41
+ #block with more calls to #element
42
+ if blk.arity < 1
43
+ blk.call
44
+ callback = lambda {|v|{}}
45
+ else
46
+ callback = blk
47
+ end
48
+
49
+ elem = SergioElement.new(new_path, args, callback)
50
+
51
+ @parsing_elements = hash_recursive_merge_to_arrays(@parsing_elements, hash_from_path(current_path, {:sergio_elem => elem}))
52
+ current_path.pop(name.length)
53
+ new_path.pop(newname.length)
54
+ end
55
+
56
+ def get_element_configs(path)
57
+ path = path.clone
58
+ current_elem = path.last
59
+ v = value_at_path(path, @parsing_elements)
60
+ if v
61
+ v = v[:sergio_elem]
62
+ if v
63
+ v = [v] if v.is_a?(SergioElement)
64
+ vs = v.select do |v|
65
+ if v.options[:having]
66
+ match = v.options[:having].any? do |attr,value|
67
+ current_elem_attrs = current_elem[1]
68
+ elem_val = current_elem_attrs.assoc(attr.to_s)
69
+ elem_val[1] == value.to_s if elem_val
70
+ end
71
+ true if match
72
+ else
73
+ true
74
+ end
75
+ end
76
+ vs
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,8 @@
1
+ class SergioElement
2
+ attr_accessor :new_path, :callback, :options
3
+ def initialize(new_path, args, callback)
4
+ @new_path = new_path.clone
5
+ @callback = callback
6
+ @options = args
7
+ end
8
+ end
@@ -0,0 +1,32 @@
1
+ module Sergio
2
+ class ParsedDocument
3
+ include HashMethods
4
+ attr_accessor :parsed_hash
5
+
6
+ def initialize
7
+ @parsed_hash = {}
8
+ end
9
+
10
+ def set_element(path, val, options = {})
11
+ v = value_at_path(path.clone, self.parsed_hash)
12
+
13
+ val = if v
14
+ if v.is_a? Array
15
+ v << val
16
+ else
17
+ if val.is_a?(Hash) && val.empty?
18
+ options[:as_array] ? [v] : v
19
+ else
20
+ [v] << val
21
+ end
22
+ end
23
+ else
24
+ options[:as_array] ? [val] : val
25
+ end
26
+
27
+ h = hash_from_path(path, val)
28
+ @parsed_hash = hash_recursive_merge(self.parsed_hash, h)
29
+ @parsed_hash
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,45 @@
1
+ class SergioSax < Nokogiri::XML::SAX::Document
2
+ def initialize(object)
3
+ @stack = []
4
+ @object = object
5
+ end
6
+
7
+ def start_element(name, attrs = [])
8
+ @stack << [name, attrs]
9
+ end
10
+
11
+ def characters(string)
12
+ name, attrs = @stack.last
13
+ attrs << ['@text', string]
14
+ end
15
+
16
+ def cdata_block(string)
17
+ characters(string)
18
+ end
19
+
20
+ def end_element(name)
21
+ e_context = @stack.clone
22
+ name, attrs = @stack.pop
23
+ if sergio_elements = @object.class.sergio_config.get_element_configs(e_context)
24
+ sergio_elements.each do |sergio_element|
25
+ attr = sergio_element.options[:attribute]
26
+ val = attrs.assoc(attr)
27
+ if val
28
+ val = val[1]
29
+ hash_path = sergio_element.new_path
30
+ callback = sergio_element.callback
31
+
32
+ r = if callback.arity == 1
33
+ callback.call(val)
34
+ elsif callback.arity == 2
35
+ h = Hash[*attrs.flatten]
36
+ h.delete('@text')
37
+ callback.call(val, Hash[*attrs.flatten])
38
+ end
39
+
40
+ @object.sergio_parsed_document.set_element(hash_path, r, sergio_element.options)
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,20 @@
1
+ require 'rspec'
2
+ require File.dirname(__FILE__) + '/../lib/sergio'
3
+
4
+ # Requires supporting files with custom matchers and macros, etc,
5
+ # in ./support/ and its subdirectories.
6
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
7
+
8
+ RSpec.configure do |config|
9
+
10
+ end
11
+
12
+ def new_sergio(&blk)
13
+ c = Class.new do
14
+ include Sergio
15
+ end
16
+ if block_given?
17
+ c.class_exec(&blk)
18
+ end
19
+ c
20
+ end
@@ -0,0 +1,48 @@
1
+ <entry xmlns="http://www.w3.org/2005/Atom" xmlns:georss="http://www.georss.org/georss" xmlns:media="http://search.yahoo.com/mrss/" xmlns:buzz="http://schemas.google.com/buzz/2010" xmlns:crosspost="http://purl.org/syndication/cross-posting" xmlns:gd="http://schemas.google.com/g/2005" xmlns:thr="http://purl.org/syndication/thread/1.0" xmlns:activity="http://activitystrea.ms/spec/1.0/" xmlns:poco="http://portablecontacts.net/ns/1.0" gd:kind="buzz#activity">
2
+ <title>Seated figure</title>
3
+ <published>2011-05-20T15:30:02.000Z</published>
4
+ <updated>2011-05-20T15:35:30.184Z</updated>
5
+ <id>tag:google.com,2010:buzz:z12dgfjgkwabv3egs04ch3kqzqnscp2xvhc</id>
6
+ <link href="https://profiles.google.com/101327999921150687436/posts/SUjANDpeWVo" type="text/html" rel="alternate"/>
7
+ <link href="https://www.googleapis.com/buzz/v1/activities/101327999921150687436/@self/B:z12dgfjgkwabv3egs04ch3kqzqnscp2xvhc?alt=atom" type="application/atom+xml" rel="self"/>
8
+ <link thr:count="0" href="https://www.googleapis.com/buzz/v1/activities/101327999921150687436/@self/B:z12dgfjgkwabv3egs04ch3kqzqnscp2xvhc/@comments?alt=atom" type="application/atom+xml" thr:updated="2011-05-20T15:35:30.184Z" rel="replies"/>
9
+ <author>
10
+ <poco:id>101327999921150687436</poco:id>
11
+ <poco:photoUrl/>
12
+ <name>Roy Patrick Tan</name>
13
+ <uri>https://profiles.google.com/101327999921150687436</uri>
14
+ <link href="" type="image/jpeg" rel="photo"/>
15
+ <activity:object-type>http://activitystrea.ms/schema/1.0/person</activity:object-type>
16
+ </author>
17
+ <content type="html">Seated figure</content>
18
+ <activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
19
+ <activity:object>
20
+ <activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type>
21
+ <content type="html">Seated figure</content>
22
+ <buzz:original-content type="text"/>
23
+ <link href="https://profiles.google.com/101327999921150687436/posts/SUjANDpeWVo" type="text/html" rel="alternate"/>
24
+ <buzz:attachment>
25
+ <activity:object-type>http://activitystrea.ms/schema/1.0/photo</activity:object-type>
26
+ <title>Seated figure</title>
27
+ <link media:width="1744" href="http://farm4.static.flickr.com/3539/5740322886_f9273216ea_o.jpg" type="image/jpeg" rel="enclosure" media:height="2331"/>
28
+ <link href="http://www.flickr.com/photos/8962549@N02/5740322886" type="text/html" rel="alternate"/>
29
+ <link href="http://images0-focus-opensocial.googleusercontent.com/gadgets/proxy?container=focus&amp;gadget=a&amp;resize_h=100&amp;url=http%3A%2F%2Ffarm4.static.flickr.com%2F3539%2F5740322886_ebcef1bbec_m.jpg" type="image/jpeg" rel="preview"/>
30
+ <link href="http://images0-focus-opensocial.googleusercontent.com/gadgets/proxy?container=focus&amp;gadget=a&amp;resize_h=100&amp;url=http%3A%2F%2Ffarm4.static.flickr.com%2F3539%2F5740322886_ebcef1bbec_b.jpg" type="image/jpeg" rel="preview"/>
31
+ </buzz:attachment>
32
+ </activity:object>
33
+ <source>
34
+ <activity:service>
35
+ <title>Flickr</title>
36
+ </activity:service>
37
+ <id>tag:google.com,2010:buzz-feed:public:posted:101327999921150687436</id>
38
+ <updated>2011-05-20T15:35:30.184Z</updated>
39
+ <link href="https://www.googleapis.com/buzz/v1/activities/101327999921150687436/@public?alt=atom" type="application/atom+xml" rel="self"/>
40
+ <link href="http://pubsubhubbub.appspot.com/" rel="hub"/>
41
+ </source>
42
+ <buzz:visibility>
43
+ <buzz:aclentry type="group">
44
+ <poco:name>Public</poco:name>
45
+ </buzz:aclentry>
46
+ </buzz:visibility>
47
+ <link buzz:count="0" href="https://www.googleapis.com/buzz/v1/activities/101327999921150687436/@self/B:z12dgfjgkwabv3egs04ch3kqzqnscp2xvhc/@liked?alt=atom" type="application/poco+xml" rel="http://schemas.google.com/buzz/2010#liked"/>
48
+ </entry>
@@ -0,0 +1,43 @@
1
+ <entry xmlns="http://www.w3.org/2005/Atom" xmlns:activity="http://activitystrea.ms/spec/1.0/" xmlns:service="http://activitystrea.ms/service-provider" xmlns:thr="http://purl.org/syndication/thread/1.0" xmlns:gnip="http://www.gnip.com/schemas/2010">
2
+ <id>100000601711657_225559834120776</id>
3
+ <created>2011-05-19T23:13:57+00:00</created>
4
+ <published>2011-05-19T23:13:57+00:00</published>
5
+ <updated>2011-05-19T23:13:57+00:00</updated>
6
+ <title>Shane Smith posted a bookmark to Facebook</title>
7
+ <category term="BookmarkPosted" label="Bookmark Posted"/>
8
+ <link rel="alternate" type="html" href="http://www.facebook.com/profile.php?id=100000601711657&amp;v=wall&amp;story_fbid=225559834120776"/>
9
+ <generator uri="http://www.facebook.com/apps/application.php?id=2344061033">Events</generator>
10
+ <source>
11
+ <link rel="self" type="application/json" href="https://graph.facebook.com/search?q=party&amp;type=post&amp;limit=75&amp;access_token=&amp;since=Thu+May+19+19%3A13%3A32+-0400+2011"/>
12
+ <title>Facebook - Keyword - Search - party</title>
13
+ <updated>2011-05-19T23-14-05Z</updated>
14
+ <gnip:rule xmlns:gnip="http://www.gnip.com/schemas/2010">party</gnip:rule>
15
+ </source>
16
+ <service:provider>
17
+ <name>Facebook</name>
18
+ <uri>www.facebook.com</uri>
19
+ <icon/>
20
+ </service:provider>
21
+ <activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
22
+ <activity:object>
23
+ <activity:object-type>http://activitystrea.ms/schema/1.0/bookmark</activity:object-type>
24
+ <id>100000601711657_225559834120776</id>
25
+ <title>GRADUATION PARTY!!!</title>
26
+ <content>Come to my Graduation Party on June 18th!!!</content>
27
+ <link rel="alternate" type="html" href="http://www.facebook.com/profile.php?id=100000601711657&amp;v=wall&amp;story_fbid=225559834120776"/>
28
+ <link rel="related" href="http://www.facebook.com/event.php?eid=184487541600108"/>
29
+ <link rel="preview" href="http://profile.ak.fbcdn.net/hprofile-ak-snc4/187804_184487541600108_5482560_s.jpg"/>
30
+ </activity:object>
31
+ <author>
32
+ <name>Shane Smith</name>
33
+ <uri>http://www.facebook.com/profile.php?id=100000601711657</uri>
34
+ </author>
35
+ <activity:actor>
36
+ <activity:object-type>http://activitystrea.ms/schema/1.0/person</activity:object-type>
37
+ <link rel="alternate" type="html" length="0" href="http://www.facebook.com/profile.php?id=100000601711657"/>
38
+ </activity:actor>
39
+ <gnip:matching_rules>
40
+ <gnip:matching_rule rel="source">party</gnip:matching_rule>
41
+ <gnip:matching_rule rel="source">neat</gnip:matching_rule>
42
+ </gnip:matching_rules>
43
+ </entry>
@@ -0,0 +1,52 @@
1
+ <entry xmlns="http://www.w3.org/2005/Atom" xmlns:gnip="http://www.gnip.com/schemas/2010">
2
+ <id>tag:search.twitter.com,2005:71230904948883456</id>
3
+ <published>2011-05-19T15:09:05+00:00</published>
4
+ <updated>2011-05-19T15:09:05+00:00</updated>
5
+ <summary type="html">@mrpotatochipman cool ;)</summary>
6
+ <link rel="alternate" type="text/html" href="http://twitter.com/_P3PP_CE/statuses/71230904948883456"/>
7
+ <source>
8
+ <link rel="self" type="application/json" href="http://stream.twitter.com/1/statuses/filter.json"/>
9
+ <title>Twitter - Stream - Track</title>
10
+ <updated>2011-05-19T15:09:08Z</updated>
11
+ </source>
12
+ <service:provider xmlns:service="http://activitystrea.ms/service-provider">
13
+ <name>Twitter</name>
14
+ <uri>http://www.twitter.com/</uri>
15
+ <icon/>
16
+ </service:provider>
17
+ <title>P3PP posted a note on Twitter</title>
18
+ <category term="StatusPosted" label="Status Posted"/>
19
+ <category term="NotePosted" label="Note Posted"/>
20
+ <activity:verb xmlns:activity="http://activitystrea.ms/spec/1.0/">http://activitystrea.ms/schema/1.0/post</activity:verb>
21
+ <activity:object xmlns:activity="http://activitystrea.ms/spec/1.0/">
22
+ <activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type>
23
+ <id>object:search.twitter.com,2005:71230904948883456</id>
24
+ <content type="html">@mrpotatochipman cool ;)</content>
25
+ <link rel="alternate" type="text/html" href="http://twitter.com/_P3PP_CE/statuses/71230904948883456"/>
26
+ <thr:in-reply-to xmlns:thr="http://purl.org/syndication/thread/1.0" type="text/html" href="http://twitter.com/mrpotatochipman/statuses/71229949155086336"/>
27
+ </activity:object>
28
+ <author>
29
+ <name>P3PP</name>
30
+ <uri>http://www.twitter.com/_P3PP_CE</uri>
31
+ </author>
32
+ <activity:author xmlns:activity="http://activitystrea.ms/spec/1.0/">
33
+ <activity:object-type>http://activitystrea.ms/schema/1.0/person</activity:object-type>
34
+ <gnip:friends xmlns:gnip="http://www.gnip.com/schemas/2010" followersCount="911" followingCount="839"/>
35
+ <link rel="alternate" type="text/html" length="0" href="http://www.twitter.com/_P3PP_CE"/>
36
+ <link rel="avatar" href="http://a0.twimg.com/profile_images/1352659419/l_15_normal.jpg"/>
37
+ <id>http://www.twitter.com/_P3PP_CE</id>
38
+ </activity:author>
39
+ <activity:actor xmlns:activity="http://activitystrea.ms/spec/1.0/">
40
+ <activity:object-type>http://activitystrea.ms/schema/1.0/person</activity:object-type>
41
+ <gnip:friends xmlns:gnip="http://www.gnip.com/schemas/2010" followersCount="911" followingCount="839"/>
42
+ <gnip:stats xmlns:gnip="http://www.gnip.com/schemas/2010" activityCount="22964" upstreamId="id:twitter.com:125163780"/>
43
+ <link rel="alternate" type="text/html" length="0" href="http://www.twitter.com/_P3PP_CE"/>
44
+ <link rel="avatar" href="http://a0.twimg.com/profile_images/1352659419/l_15_normal.jpg"/>
45
+ <id>http://www.twitter.com/_P3PP_CE</id>
46
+ <os:location xmlns:os="http://ns.opensocial.org/2008/opensocial">Back N Da LAND,Oh10</os:location>
47
+ <os:aboutMe xmlns:os="http://ns.opensocial.org/2008/opensocial">Jus A Laid Back Type Of Chick, Who Aint On No B.S Mad Cool. #TeamGemini, #RMF, #BBN, #TFB, #TeamDroid #TeamSingle Reppn Im Owt ;) </os:aboutMe>
48
+ </activity:actor>
49
+ <gnip:matching_rules>
50
+ <gnip:matching_rule rel="inferred" tag="NEAT">cool</gnip:matching_rule>
51
+ </gnip:matching_rules>
52
+ </entry>
@@ -0,0 +1,79 @@
1
+ require 'spec_helper'
2
+
3
+ describe Sergio::HashMethods do
4
+ context 'value_at_path' do
5
+ before do
6
+ @s = Class.new do
7
+ include Sergio::HashMethods
8
+ end.new
9
+ end
10
+
11
+ it 'gets the value from passed in hash at point in heirarchy specified by passed in array' do
12
+ v = @s.value_at_path([['thing'], ['stuff']], {'thing' => {'stuff' => '2'}})
13
+ v.should == '2'
14
+ end
15
+
16
+ it 'gets the value from passed in hash at point in heirarchy specified by passed in array if array is one element long' do
17
+ v = @s.value_at_path([['thing']], {'thing' => {'stuff' => '2'}})
18
+ v.should == {'stuff' => '2'}
19
+ end
20
+
21
+ it 'returns nil if value is not present in hash' do
22
+ v = @s.value_at_path(['thing', 'stuff'], {'thing' => {'guy' => '2'}})
23
+ v.should == nil
24
+ end
25
+ end
26
+
27
+ context 'hash_recursive_merge_to_arrays' do
28
+ before do
29
+ @s = Class.new do
30
+ include Sergio::HashMethods
31
+ end.new
32
+ end
33
+
34
+
35
+ it 'merges subhashes together' do
36
+ h1 = {:thing => {'guy' => 'cool'}}
37
+ h2 = {:thing => {'guys' => 'cool'}}
38
+ @s.hash_recursive_merge_to_arrays(h1, h2).should == {:thing => {'guy' => 'cool', 'guys' => 'cool'}}
39
+ end
40
+
41
+ it 'builds an array out of values on intersecting keys' do
42
+ h1 = {:thing => {'guy' => 'cool'}}
43
+ h2 = {:thing => {'guy' => 'cool'}}
44
+ @s.hash_recursive_merge_to_arrays(h1, h2).should == {:thing => {'guy' => ['cool', 'cool']}}
45
+ end
46
+
47
+ it 'appends to array if in value of left intersecting key' do
48
+ h1 = {:thing => {'guy' => ['cool', 'neat']}}
49
+ h2 = {:thing => {'guy' => 'cool'}}
50
+ @s.hash_recursive_merge_to_arrays(h1, h2).should == {:thing => {'guy' => ['cool', 'neat', 'cool']}}
51
+ end
52
+
53
+ it 'appends to array if in value of right intersecting key' do
54
+ h1 = {:thing => {'guy' => 'cool'}}
55
+ h2 = {:thing => {'guy' => ['cool', 'neat']}}
56
+ @s.hash_recursive_merge_to_arrays(h1, h2).should == {:thing => {'guy' => ['cool', 'neat', 'cool']}}
57
+ end
58
+ end
59
+
60
+ context 'hash_recursive_merge' do
61
+ before do
62
+ @s = Class.new do
63
+ include Sergio::HashMethods
64
+ end.new
65
+ end
66
+
67
+ it 'merges subhashes together' do
68
+ h1 = {:thing => {'guy' => 'cool'}}
69
+ h2 = {:thing => {'guys' => 'cool'}}
70
+ @s.hash_recursive_merge(h1, h2).should == {:thing => {'guy' => 'cool', 'guys' => 'cool'}}
71
+ end
72
+
73
+ it 'replaces intersecting key values with rightmost hash argument value' do
74
+ h1 = {:thing => {'guy' => 'cool'}}
75
+ h2 = {:thing => {'guy' => 'cools'}}
76
+ @s.hash_recursive_merge(h1, h2).should == {:thing => {'guy' => 'cools'}}
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,248 @@
1
+ require 'spec_helper'
2
+
3
+ describe Sergio do
4
+ context 'element' do
5
+ it 'parses an element' do
6
+ s = new_sergio do
7
+ element 'id'
8
+ end
9
+
10
+ @xml = "<id>1</id>"
11
+ @hash = s.new.parse(@xml)
12
+ @hash['id'].should == '1'
13
+ end
14
+
15
+ it 'parses duplicate elements into an array' do
16
+ s = new_sergio do
17
+ element 'parent' do
18
+ element 'id'
19
+ end
20
+ end
21
+
22
+ @xml = "<parent><id>1</id><id>2</id></parent>"
23
+ @hash = s.new.parse(@xml)
24
+ @hash['parent']['id'].should == ['1', '2']
25
+ end
26
+
27
+ it 'parses duplicate elements whose callbacks return a hash into an array' do
28
+ s = new_sergio do
29
+ element 'parent' do
30
+ element 'id' do |v|
31
+ {'v' => v}
32
+ end
33
+ end
34
+ end
35
+
36
+ @xml = "<parent><id>1</id><id>2</id></parent>"
37
+ @hash = s.new.parse(@xml)
38
+ @hash['parent']['id'].should == [{'v' => '1',}, {'v' => '2'}]
39
+ end
40
+
41
+ it 'parses a nested element' do
42
+ s = new_sergio do
43
+ element 'ip' do
44
+ element 'man'
45
+ end
46
+ end
47
+
48
+ @xml = "<ip><man>neat</man></ip>"
49
+ @hash = s.new.parse(@xml)
50
+ @hash['ip']['man'].should == 'neat'
51
+ end
52
+
53
+ it 'renames an element to second argument passed to #element if argument is provided' do
54
+ s = new_sergio do
55
+ element 'man', 'guy'
56
+ end
57
+
58
+ @xml = "<man>neat</man>"
59
+ @hash = s.new.parse(@xml)
60
+ @hash['guy'].should == 'neat'
61
+ end
62
+
63
+ it 'passes value into block if block has an arity of 1. And sets element value to block result' do
64
+ s = new_sergio do
65
+ element 'man' do |v|
66
+ v.reverse
67
+ end
68
+ end
69
+
70
+ @xml = "<man>neat</man>"
71
+ @hash = s.new.parse(@xml)
72
+ @hash['man'].should == 'taen'
73
+ end
74
+
75
+ it 'passes value and attributes into block if block has an arity of 2 and sets element value to block result' do
76
+ s = new_sergio do
77
+ element 'man' do |v, attrs|
78
+ {'u' => attrs['it'], 'v' => v.reverse}
79
+ end
80
+ end
81
+
82
+ @xml = "<man it='u'>neat</man>"
83
+ @hash = s.new.parse(@xml)
84
+ @hash['man'].should == {'u' => 'u', 'v' => 'taen'}
85
+ end
86
+
87
+ it 'renames a parent element to second argument passed to #element if argument is provided' do
88
+ s = new_sergio do
89
+ element 'parent', 'post' do
90
+ element 'id'
91
+ end
92
+ end
93
+
94
+ @xml = "<parent><id>1</id><id>2</id></parent>"
95
+ @hash = s.new.parse(@xml)
96
+ @hash['post'].should == {'id' => ['1', '2']}
97
+ end
98
+
99
+ it 'renames a child element to second argument passed to #element if argument is provided' do
100
+ s = new_sergio do
101
+ element 'parent', 'post' do
102
+ element 'id', 'di'
103
+ end
104
+ end
105
+
106
+ @xml = "<parent><id>1</id><id>2</id></parent>"
107
+ @hash = s.new.parse(@xml)
108
+ @hash['post'].should == {'di' => ['1', '2']}
109
+ end
110
+
111
+ it 'matches against attributes using :having argument' do
112
+ s = new_sergio do
113
+ element 'parent', 'post' do
114
+ element 'ya', 'kewl', :attribute => 'location', :having => {:href => 'cool', :poopy => 'true'}
115
+ end
116
+ end
117
+
118
+ @xml = "<parent><ha href='imahref'>neat</ha><ha href='cool'>rad</ha><ya href='cool' poopy='true' location='my house'>wee</ya></parent>"
119
+ @hash = s.new.parse(@xml)
120
+ @hash['post']['kewl'].should == 'my house'
121
+ end
122
+
123
+ it 'forces aggregation of matching results into an array even for one match if passed :as_array => true' do
124
+ s = new_sergio do
125
+ element 'parent', 'post' do
126
+ element 'ya', 'kewl', :attribute => 'location', :having => {:href => 'cool', :poopy => 'true'}, :as_array => true
127
+ end
128
+ end
129
+
130
+ @xml = "
131
+ <parent>
132
+ <ha href='imahref'>neat</ha>
133
+ <ha href='cool'>rad</ha>
134
+ <ya href='cool' poopy='true' location='my house'>wee</ya>
135
+ </parent>"
136
+ @hash = s.new.parse(@xml)
137
+ @hash['post']['kewl'].should == ['my house']
138
+ end
139
+
140
+ it 'matches against multiple elements within same parent' do
141
+ s = new_sergio do
142
+ element 'parent', 'post' do
143
+ element 'ha', 'link', :having => {:href => 'cool'} do |val|
144
+ val.reverse
145
+ end
146
+ element 'ya', 'kewl', :attribute => 'location', :having => {:href => 'cool', :poopy => 'true'}
147
+ end
148
+ end
149
+
150
+ @xml = "<parent>
151
+ <ha href='imahref'>neat</ha>
152
+ <ha href='cool'>rad</ha><ya href='cool' poopy='true' location='my house'>wee</ya>
153
+ </parent>"
154
+
155
+ @hash = s.new.parse(@xml)
156
+ @hash['post']['link'].should == 'dar'
157
+ @hash['post']['kewl'].should == 'my house'
158
+ end
159
+
160
+ it 'matches against multiple elements of the same type within same parent' do
161
+ s = new_sergio do
162
+ element 'p' do
163
+ element 'a', :attribute => 'href' do |v|
164
+ v.upcase
165
+ end
166
+ element 'a', :attribute => 'cool' do |v|
167
+ v.upcase
168
+ end
169
+ end
170
+ end
171
+
172
+ @xml = "<p><a href='cool'>hi</a><a cool='true'>hy</a></p>"
173
+ @hash = s.new.parse(@xml)
174
+ @hash['p']['a'].should == ['COOL','TRUE']
175
+ end
176
+
177
+ it 'uses value of attribute using :attribute argument' do
178
+ s = new_sergio do
179
+ element 'a', 'link', :attribute => 'href'
180
+ end
181
+
182
+ @xml = "<a href='neat'>neat</a>"
183
+ @hash = s.new.parse(@xml)
184
+ @hash['link'].should == 'neat'
185
+ end
186
+
187
+ it 'accepts an array as the first argument to specify a path to an element' do
188
+ s = new_sergio do
189
+ element ['a', 'b'], 'c', :attribute => 'y'
190
+ end
191
+
192
+ @xml = '<a><b y="what"></b></a>'
193
+ @hash = s.new.parse(@xml)
194
+ @hash['c'].should == 'what'
195
+ end
196
+
197
+ it 'accepts an array as the second argument to specify a path to an element to merge to' do
198
+ s = new_sergio do
199
+ element 'a', ['b', 'c'], :attribute => 'y'
200
+ end
201
+
202
+ @xml = '<a y="what"></a>'
203
+ @hash = s.new.parse(@xml)
204
+ @hash['b'].should == {'c' => 'what'}
205
+ end
206
+
207
+ it 'accepts an array for both arguments to specify a path to an element to get value from and to merge to' do
208
+ s = new_sergio do
209
+ element 'p' do
210
+ element 'd'
211
+ element ['a', 'b'], ['b', 'c'], :attribute => 'y'
212
+ element 'a'
213
+ end
214
+ end
215
+
216
+ @xml = '<p><d>a</d><a y="what"><b y="hi"></b></a></p>'
217
+ @hash = s.new.parse(@xml)
218
+ @hash['p']['b'].should == {'c' => 'hi'}
219
+ end
220
+
221
+ it 'accepts a class which includes sergio as an argument' do
222
+ class Pozt
223
+ include Sergio
224
+
225
+ element 'b'
226
+ end
227
+
228
+ s = new_sergio do
229
+ element 'a', Pozt
230
+ end
231
+
232
+ @xml = '<a y="what"><b>hi</b></a>'
233
+ @hash = s.new.parse(@xml)
234
+ @hash['a']['b'].should == {'c' => 'what'}
235
+ end
236
+ end
237
+
238
+ context 'set_element' do
239
+ before do
240
+ @s = new_sergio
241
+ end
242
+
243
+ it 'sets the element at the appropriate point in the hierarchy' do
244
+ v = @s.new.sergio_parsed_document.set_element(['thing', 'stuff'], '1')
245
+ v.should == {'thing' => {'stuff' => '1'}}
246
+ end
247
+ end
248
+ end
metadata ADDED
@@ -0,0 +1,108 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sergio
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.0.1
6
+ platform: ruby
7
+ authors:
8
+ - Max Justus Spransy
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2011-05-25 00:00:00 -05:00
14
+ default_executable:
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: nokogiri
18
+ prerelease: false
19
+ requirement: &id001 !ruby/object:Gem::Requirement
20
+ none: false
21
+ requirements:
22
+ - - ">="
23
+ - !ruby/object:Gem::Version
24
+ version: "0"
25
+ type: :runtime
26
+ version_requirements: *id001
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ prerelease: false
30
+ requirement: &id002 !ruby/object:Gem::Requirement
31
+ none: false
32
+ requirements:
33
+ - - ~>
34
+ - !ruby/object:Gem::Version
35
+ version: 2.5.0
36
+ type: :development
37
+ version_requirements: *id002
38
+ - !ruby/object:Gem::Dependency
39
+ name: rake
40
+ prerelease: false
41
+ requirement: &id003 !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: "0"
47
+ type: :development
48
+ version_requirements: *id003
49
+ description: "\n Sergio provides a declarative syntax for parsing unruly xml into nice pretty hashes.\n "
50
+ email:
51
+ - maxjustus@gmail.com
52
+ executables: []
53
+
54
+ extensions: []
55
+
56
+ extra_rdoc_files: []
57
+
58
+ files:
59
+ - Gemfile
60
+ - Gemfile.lock
61
+ - Rakefile
62
+ - lib/sergio.rb
63
+ - lib/sergio/hash_methods.rb
64
+ - lib/sergio/sergio_config.rb
65
+ - lib/sergio/sergio_element.rb
66
+ - lib/sergio/sergio_parsed_document.rb
67
+ - lib/sergio/sergio_sax.rb
68
+ - spec/spec_helper.rb
69
+ - spec/support/buzz_activity_stream.xml
70
+ - spec/support/facebook_activity_stream.xml
71
+ - spec/support/twitter_activity_stream.xml
72
+ - spec/unit/sergio_hash_methods_spec.rb
73
+ - spec/unit/sergio_spec.rb
74
+ has_rdoc: true
75
+ homepage: https://github.com/maxjustus/Sergio
76
+ licenses: []
77
+
78
+ post_install_message:
79
+ rdoc_options: []
80
+
81
+ require_paths:
82
+ - lib
83
+ required_ruby_version: !ruby/object:Gem::Requirement
84
+ none: false
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: "0"
89
+ required_rubygems_version: !ruby/object:Gem::Requirement
90
+ none: false
91
+ requirements:
92
+ - - ">="
93
+ - !ruby/object:Gem::Version
94
+ version: "0"
95
+ requirements: []
96
+
97
+ rubyforge_project: sergio
98
+ rubygems_version: 1.6.2
99
+ signing_key:
100
+ specification_version: 3
101
+ summary: SAXy xml to hash transformation.
102
+ test_files:
103
+ - spec/spec_helper.rb
104
+ - spec/support/buzz_activity_stream.xml
105
+ - spec/support/facebook_activity_stream.xml
106
+ - spec/support/twitter_activity_stream.xml
107
+ - spec/unit/sergio_hash_methods_spec.rb
108
+ - spec/unit/sergio_spec.rb