sergio 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +5 -0
- data/Gemfile.lock +24 -0
- data/Rakefile +12 -0
- data/lib/sergio.rb +32 -0
- data/lib/sergio/hash_methods.rb +58 -0
- data/lib/sergio/sergio_config.rb +81 -0
- data/lib/sergio/sergio_element.rb +8 -0
- data/lib/sergio/sergio_parsed_document.rb +32 -0
- data/lib/sergio/sergio_sax.rb +45 -0
- data/spec/spec_helper.rb +20 -0
- data/spec/support/buzz_activity_stream.xml +48 -0
- data/spec/support/facebook_activity_stream.xml +43 -0
- data/spec/support/twitter_activity_stream.xml +52 -0
- data/spec/unit/sergio_hash_methods_spec.rb +79 -0
- data/spec/unit/sergio_spec.rb +248 -0
- metadata +108 -0
data/Gemfile
ADDED
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,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
|
data/spec/spec_helper.rb
ADDED
@@ -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&gadget=a&resize_h=100&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&gadget=a&resize_h=100&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&v=wall&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&type=post&limit=75&access_token=&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&v=wall&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
|