sergio 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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