partigirb 0.2.7

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ *.sw?
2
+ .DS_Store
3
+ coverage
4
+ rdoc
5
+ pkg
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Alvaro Bautista & Fernando Blat
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.markdown ADDED
@@ -0,0 +1,110 @@
1
+ # partigirb
2
+
3
+ A Ruby wrapper for the Partigi API, adapted from [Grackle](http://github.com/hayesdavis/grackle/tree/master) by Hayes Davis.
4
+
5
+ ## What is Partigi?
6
+
7
+ [Partigi](http://www.partigi.com) is a service that helps you choose your next cultural items, share short reviews and keep track of what you have consumed and own.
8
+
9
+ The Partigi API is an almost-REST based Atom API where we offer you almost all data and functionality that you have in the website.
10
+
11
+ There is also a [complete documentation of the API](http://partigi.pbworks.com/).
12
+
13
+ ## Install
14
+
15
+ gem install partigirb -s http://gemcutter.org
16
+
17
+ ## Usage
18
+
19
+ ### Creating a client
20
+
21
+ #### Without Authentication
22
+
23
+ client = Partigirb::Client.new
24
+
25
+ #### With Authentication
26
+
27
+ client = Partigirb::Client.new(:auth => {:login => 'SOMEUSERLOGIN', :api_secret => 'SECRETGENERATEDFORTHEUSER'})
28
+
29
+ #### With specific API version
30
+
31
+ client = Partigirb::Client.new(:api_version => 2)
32
+
33
+ ### Request methods
34
+
35
+ A request to Partigi servers is done by translating Partigi URL paths into a set of chained method calls, just changing slashes by dots. Each call is used to build the request URL until a format call is found which causes the request to be sent. In order to specify the HTTP method use:
36
+
37
+ - `?` for HTTP GET
38
+ - `!` for HTTP POST (in this case you will need to use a client with authentication)
39
+
40
+ A request is not performed until either you add the above signs to your last method call or you use a format method call.
41
+
42
+ **Note:** Any method call that is not part of a valid API request path will be chained to the request that Partigirb sends to the server, so that when the client finds a format call (method call ending with ? or !) a wrong request will be sent and a PartigiError will be raised. For example:
43
+
44
+ client.wrong.items.index?
45
+
46
+ or
47
+
48
+ client.wrong
49
+ client.items.index?
50
+
51
+ In the second case we do the wrong call and the right one in separated sentences, however the wrong call is chained anyway (in that case the client method `clear` may be used to flush the chain).
52
+
53
+ ### Formats
54
+
55
+ The response format is specified by a method call with the format name (atom, json or xml). Notice that the only format fully implemented on Partigi API at the moment is atom, which is the default used by the wrapper.
56
+
57
+ ### Example requests
58
+
59
+ The simplest way of executing a GET request is to use the `?` notation, using the default format.
60
+
61
+ client.users.show? :id => 'johnwayne' # http://www.partigi.com/api/v1/users/show.atom?id=johnwayne
62
+
63
+ Also you can force the format:
64
+
65
+ client.users.show.json? :id => 'johnwayne' # http://www.partigi.com/api/v1/users/show.json?id=johnwayne
66
+
67
+ For POST requests just change `?` by `!`:
68
+
69
+ client.reviews.update! :id => 123, :status => 1 # POST to http://www.partigi.com/api/v1/reviews/update.atom
70
+
71
+
72
+ ### Parameter handling
73
+
74
+ - All parameters are URL encoded as necessary.
75
+ - If you use a File object as a parameter it will be POSTed to Partigi in a multipart request.
76
+ - If you use a Time object as a parameter, .httpdate will be called on it and that value will be used
77
+
78
+ ### Return values
79
+
80
+ The returned values are always OpenStruct objects (wrapped in Partigirb::PartigiStruct) containing the response values as attributes.
81
+
82
+ If the response contains several entries the client returns an Array of OpenStruct objects.
83
+
84
+ When using Atom format Partigi returns some XML elements using namespaces. In those cases the elements are mapped to attributes by convention, for example: `namespaceName:attribute` becomes `namespaceName_attribute`
85
+
86
+ #### Special cases
87
+
88
+ There are two special cases to be aware of in regard to PartigiStruct:
89
+
90
+ - Every attribute which name is equal to any of the Ruby Object methods (e.g `type`) will be mapped to a method on the struct starting with an underscore (e.g `_type`).
91
+
92
+ - XML elements that appear repeated with different type values will turn into a unique struct with one method per type. For instance:
93
+
94
+ <content type="text">Some text</content>
95
+ <content type="html"><p>Some html</p></content>
96
+
97
+ Will be accessed by `result.content.text` and `result.content.html`, both returning a ruby string.
98
+
99
+ ### Error handling
100
+
101
+ In case Partigi returns an error response, this is turned into a PartigiError object which message attribute is set to the error string returned in the XML response.
102
+
103
+ ## Requirements
104
+
105
+ - json
106
+ - mime-types
107
+
108
+ ## Copyright
109
+
110
+ Copyright (c) 2009 Alvaro Bautista & Fernando Blat, released under MIT license
data/Rakefile ADDED
@@ -0,0 +1,56 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "partigirb"
8
+ gem.summary = %q{A Ruby wrapper for the Partigi API}
9
+ gem.email = ["alvarobp@gmail.com", "ferblape@gmail.com"]
10
+ gem.homepage = "http://github.com/partigi/partigirb"
11
+ gem.authors = ["Alvaro Bautista", "Fernando Blat"]
12
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
13
+ end
14
+ Jeweler::GemcutterTasks.new
15
+ rescue LoadError
16
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
17
+ end
18
+
19
+ require 'rake/testtask'
20
+ Rake::TestTask.new(:test) do |test|
21
+ test.libs << 'lib' << 'test'
22
+ test.pattern = 'test/**/*_test.rb'
23
+ test.verbose = true
24
+ end
25
+
26
+ begin
27
+ require 'rcov/rcovtask'
28
+ Rcov::RcovTask.new do |test|
29
+ test.libs << 'test'
30
+ test.pattern = 'test/**/*_test.rb'
31
+ test.verbose = true
32
+ end
33
+ rescue LoadError
34
+ task :rcov do
35
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
36
+ end
37
+ end
38
+
39
+
40
+ task :default => :test
41
+
42
+ require 'rake/rdoctask'
43
+ Rake::RDocTask.new do |rdoc|
44
+ if File.exist?('VERSION.yml')
45
+ config = YAML.load(File.read('VERSION.yml'))
46
+ version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
47
+ else
48
+ version = ""
49
+ end
50
+
51
+ rdoc.rdoc_dir = 'rdoc'
52
+ rdoc.title = "partigirb #{version}"
53
+ rdoc.rdoc_files.include('README*')
54
+ rdoc.rdoc_files.include('lib/**/*.rb')
55
+ end
56
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.2.7
@@ -0,0 +1,34 @@
1
+ require File.dirname(__FILE__) + '/../lib/partigirb'
2
+
3
+ if ARGV.empty?
4
+ puts "\nUsage: ruby #{__FILE__} user_id_or_login\n\n"
5
+ exit
6
+ end
7
+
8
+ user_id = ARGV.first
9
+
10
+ def show_reviews(client, reviews, title)
11
+ puts
12
+ puts title
13
+ puts "-" * title.size
14
+ puts
15
+
16
+ reviews.each do |review|
17
+ film = client.items.show? :id => review.ptItem_id, :type => 'film'
18
+ puts "- #{film.title}"
19
+ puts " Comment: #{review.content.text}"
20
+ puts
21
+ end
22
+ puts
23
+ end
24
+
25
+ client = Partigirb::Client.new
26
+
27
+ reviews = client.reviews.index? :user_id => user_id, :per_page => 5, :status => 0, :order => 'desc'
28
+ show_reviews(client, reviews, "Latest 5 films you want to watch")
29
+
30
+ reviews = client.reviews.index? :user_id => user_id, :per_page => 5, :status => 1, :order => 'desc'
31
+ show_reviews(client, reviews, "Latest 5 films you have seen")
32
+
33
+ reviews = client.reviews.index? :user_id => user_id, :per_page => 5, :status => 2, :order => 'desc'
34
+ show_reviews(client, reviews, "Latest 5 films you own")
@@ -0,0 +1,35 @@
1
+ require File.dirname(__FILE__) + '/../lib/partigirb'
2
+
3
+ if ARGV.empty?
4
+ puts "\nUsage: #{__FILE__} user_id_or_login\n\n"
5
+ exit
6
+ end
7
+
8
+ user_id = ARGV.first
9
+
10
+ client = Partigirb::Client.new
11
+
12
+ traitors = []
13
+
14
+ page = 1
15
+ friends = []
16
+
17
+ # Get logins of people user is following
18
+ begin
19
+ friends = client.friendships.index? :user_id => user_id, :type => 'follows', :page => page
20
+
21
+ friends.each do |friend|
22
+ relationship = client.friendships.show? :source_id => user_id, :target_id => friend.ptUser_id
23
+ traitors << friend.ptUser_login if relationship.ptRelationship_source.ptRelationship_followed_by == 'false'
24
+ end
25
+
26
+ page += 1
27
+ end while !friends.empty?
28
+
29
+ if traitors.empty?
30
+ "Everything is fine. Everyone you follow is following you."
31
+ else
32
+ puts "Those are the ones that don't want to know about you:"
33
+ puts
34
+ traitors.each {|t| puts " - #{t}"}
35
+ end
@@ -0,0 +1,190 @@
1
+ module Partigirb
2
+
3
+ class PartigiStruct < OpenStruct
4
+ attr_accessor :id
5
+ end
6
+
7
+ # Raised by methods which call the API if a non-200 response status is received
8
+ class PartigiError < StandardError
9
+ end
10
+
11
+ class Client
12
+ class Request #:nodoc:
13
+ attr_accessor :client, :path, :method, :api_version
14
+
15
+ def initialize(client,api_version=Partigirb::CURRENT_API_VERSION)
16
+ self.client = client
17
+ self.api_version = api_version
18
+ self.method = :get
19
+ self.path = ''
20
+ end
21
+
22
+ def <<(path)
23
+ self.path << path
24
+ end
25
+
26
+ def path?
27
+ path.length > 0
28
+ end
29
+
30
+ def url
31
+ "#{scheme}://#{host}/api/v#{self.api_version}#{path}"
32
+ end
33
+
34
+ def host
35
+ client.api_host
36
+ end
37
+
38
+ def scheme
39
+ 'http'
40
+ end
41
+ end
42
+
43
+ VALID_METHODS = [:get,:post,:put,:delete]
44
+ VALID_FORMATS = [:atom,:xml,:json]
45
+
46
+ PARTIGI_API_HOST = "www.partigi.com"
47
+ TIMESTAMP_FORMAT = '%Y-%m-%dT%H:%M:%SZ'
48
+
49
+ attr_accessor :default_format, :headers, :api_version, :transport, :request, :api_host, :auth, :handlers
50
+
51
+ def initialize(options={})
52
+ self.transport = Transport.new
53
+ self.api_host = PARTIGI_API_HOST.clone
54
+ self.api_version = options[:api_version] || Partigirb::CURRENT_API_VERSION
55
+ self.headers = {"User-Agent"=>"Partigirb/#{Partigirb::VERSION}"}.merge!(options[:headers]||{})
56
+ self.default_format = options[:default_format] || :atom
57
+ self.handlers = {
58
+ :json => Partigirb::Handlers::JSONHandler.new,
59
+ :xml => Partigirb::Handlers::XMLHandler.new,
60
+ :atom => Partigirb::Handlers::AtomHandler.new,
61
+ :unknown => Partigirb::Handlers::StringHandler.new
62
+ }
63
+ self.handlers.merge!(options[:handlers]||{})
64
+
65
+ # Authentication param should be a hash with keys:
66
+ # login (required)
67
+ # api_secret (required)
68
+ # nonce (optional, would be automatically generated if missing)
69
+ # timestamp (optional, current timestamp will be automatically used if missing)
70
+ self.auth = options[:auth]
71
+ end
72
+
73
+ def method_missing(name,*args)
74
+ # If method is a format name, execute using that format
75
+ if format_invocation?(name)
76
+ return call_with_format(name,*args)
77
+ end
78
+ # If method ends in ! or ? use that to determine post or get
79
+ if name.to_s =~ /^(.*)(!|\?)$/
80
+ name = $1.to_sym
81
+ # ! is a post, ? is a get
82
+ self.request.method = ($2 == '!' ? :post : :get)
83
+ if format_invocation?(name)
84
+ return call_with_format(name,*args)
85
+ else
86
+ self.request << "/#{$1}"
87
+ return call_with_format(self.default_format,*args)
88
+ end
89
+ end
90
+ # Else add to the request path
91
+ self.request << "/#{name}"
92
+ self
93
+ end
94
+
95
+ # Clears any pending request built up by chained methods but not executed
96
+ def clear
97
+ self.request = nil
98
+ end
99
+
100
+ def request
101
+ @request ||= Request.new(self,api_version)
102
+ end
103
+
104
+ protected
105
+
106
+ def call_with_format(format,params={})
107
+ request << ".#{format}"
108
+ res = send_request(params)
109
+ process_response(format,res)
110
+ ensure
111
+ clear
112
+ end
113
+
114
+ def send_request(params)
115
+ begin
116
+ set_authentication_headers
117
+
118
+ transport.request(
119
+ request.method, request.url, :headers=>headers, :params=>params
120
+ )
121
+ rescue => e
122
+ puts e
123
+ end
124
+ end
125
+
126
+ def process_response(format, res)
127
+ fmt_handler = handler(format)
128
+
129
+ begin
130
+ if res.code.to_i != 200
131
+ handle_error_response(res, Partigirb::Handlers::XMLHandler.new)
132
+ else
133
+ fmt_handler.decode_response(res.body)
134
+ end
135
+ end
136
+ end
137
+
138
+ # TODO: Test for errors
139
+ def handle_error_response(res, handler)
140
+ # Response for errors is an XML document containing an error tag as a root,
141
+ # having a text node with error name. As XMLHandler starts building the Struct
142
+ # on root node the returned value from the handler will always be the error name text.
143
+ raise PartigiError.new(handler.decode_response(res.body))
144
+ end
145
+
146
+ def format_invocation?(name)
147
+ self.request.path? && VALID_FORMATS.include?(name)
148
+ end
149
+
150
+ def handler(format)
151
+ handlers[format] || handlers[:unknown]
152
+ end
153
+
154
+ # Adds the proper WSSE headers if there are the right authentication parameters
155
+ def set_authentication_headers
156
+ unless self.auth.nil? || self.auth === Hash || self.auth.empty?
157
+ auths = self.auth.stringify_keys
158
+
159
+ if auths.has_key?('login') && auths.has_key?('api_secret')
160
+ if !auths['timestamp'].nil?
161
+ timestamp = case auths['timestamp']
162
+ when Time
163
+ auths['timestamp'].strftime(TIMESTAMP_FORMAT)
164
+ when String
165
+ auths['timestamp']
166
+ end
167
+ else
168
+ timestamp = Time.now.strftime(TIMESTAMP_FORMAT) if timestamp.nil?
169
+ end
170
+
171
+ nonce = auths['nonce'] || generate_nonce
172
+ password_digest = generate_password_digest(nonce, timestamp, auths['login'], auths['api_secret'])
173
+ headers.merge!({
174
+ 'Authorization' => "WSSE realm=\"#{PARTIGI_API_HOST}\", profile=\"UsernameToken\"",
175
+ 'X-WSSE' => "UsernameToken Username=\"#{auths['login']}\", PasswordDigest=\"#{password_digest}\", Nonce=\"#{nonce}\", Created=\"#{timestamp}\""
176
+ })
177
+ end
178
+ end
179
+ end
180
+
181
+ def generate_nonce
182
+ o = [('a'..'z'),('A'..'Z')].map{|i| i.to_a}.flatten
183
+ Digest::MD5.hexdigest((0..10).map{o[rand(o.length)]}.join)
184
+ end
185
+
186
+ def generate_password_digest(nonce, timestamp, login, secret)
187
+ Base64.encode64(Digest::SHA1.hexdigest("#{nonce}#{timestamp}#{login}#{secret}")).chomp
188
+ end
189
+ end
190
+ end
@@ -0,0 +1,78 @@
1
+ # Ruby Hash extensions from ActiveSupport
2
+
3
+ class Hash
4
+ # Return a new hash with all keys converted to strings.
5
+ def stringify_keys
6
+ inject({}) do |options, (key, value)|
7
+ options[key.to_s] = value
8
+ options
9
+ end
10
+ end
11
+
12
+ # Destructively convert all keys to strings.
13
+ def stringify_keys!
14
+ keys.each do |key|
15
+ self[key.to_s] = delete(key)
16
+ end
17
+ self
18
+ end
19
+ end
20
+
21
+ class Object
22
+ # An object is blank if it's false, empty, or a whitespace string.
23
+ # For example, "", " ", +nil+, [], and {} are blank.
24
+ #
25
+ # This simplifies
26
+ #
27
+ # if !address.nil? && !address.empty?
28
+ #
29
+ # to
30
+ #
31
+ # if !address.blank?
32
+ def blank?
33
+ respond_to?(:empty?) ? empty? : !self
34
+ end
35
+
36
+ # An object is present if it's not blank.
37
+ def present?
38
+ !blank?
39
+ end
40
+ end
41
+
42
+ class NilClass #:nodoc:
43
+ def blank?
44
+ true
45
+ end
46
+ end
47
+
48
+ class FalseClass #:nodoc:
49
+ def blank?
50
+ true
51
+ end
52
+ end
53
+
54
+ class TrueClass #:nodoc:
55
+ def blank?
56
+ false
57
+ end
58
+ end
59
+
60
+ class Array #:nodoc:
61
+ alias_method :blank?, :empty?
62
+ end
63
+
64
+ class Hash #:nodoc:
65
+ alias_method :blank?, :empty?
66
+ end
67
+
68
+ class String #:nodoc:
69
+ def blank?
70
+ self !~ /\S/
71
+ end
72
+ end
73
+
74
+ class Numeric #:nodoc:
75
+ def blank?
76
+ false
77
+ end
78
+ end
@@ -0,0 +1,24 @@
1
+ module Partigirb
2
+ module Handlers
3
+ class AtomHandler < XMLHandler
4
+ def decode_response(body)
5
+ return REXML::Document.new if body.blank?
6
+ xml = REXML::Document.new(body.gsub(/>\s+</,'><'))
7
+
8
+ if xml.root.name == 'feed'
9
+ entries = xml.root.get_elements('entry')
10
+
11
+ # Depending on whether we have one or more entries we return an PartigiStruct or an array of PartigiStruct
12
+ if entries.size == 1
13
+ load_recursive(entries.first)
14
+ else
15
+ entries.map{|e| load_recursive(e)}
16
+ end
17
+ else
18
+ # We just parse as a common XML
19
+ load_recursive(xml.root)
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,10 @@
1
+ module Partigirb
2
+ module Handlers
3
+ class JSONHandler
4
+ def decode_response( body )
5
+ # TODO: Implement when API JSON format is ready
6
+ body
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,9 @@
1
+ module Partigirb
2
+ module Handlers
3
+ class StringHandler
4
+ def decode_response( body )
5
+ body
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,111 @@
1
+ module Partigirb
2
+ module Handlers
3
+ class XMLHandler
4
+ def decode_response(body)
5
+ return REXML::Document.new if body.blank?
6
+ xml = REXML::Document.new(body.gsub(/>\s+</,'><'))
7
+ load_recursive(xml.root)
8
+ end
9
+
10
+ private
11
+ def load_recursive(node)
12
+ if array_node?(node)
13
+ node.elements.map {|e| load_recursive(e)}
14
+ elsif cdata_node?(node)
15
+ node.cdatas.first.to_s
16
+ elsif raw_node?(node)
17
+ node.text
18
+ elsif (node.elements.size > 0 || node.attributes.size > 0) && !ignore_attributes?(node)
19
+ build_struct(node)
20
+ else
21
+ value = node.text
22
+ fixnum?(value) ? value.to_i : value
23
+ end
24
+ end
25
+
26
+ def build_struct(node)
27
+ ts = PartigiStruct.new
28
+
29
+ node.attributes.each do |a,v|
30
+ # In case the Struct object already responds to a method
31
+ # with same name like type case
32
+ if ts.respond_to?(a)
33
+ ts.send("_#{a}=",v)
34
+ else
35
+ ts.send("#{a}=", v) unless a =~ /^xmlns/
36
+ end
37
+ end
38
+
39
+ links = node.elements.delete_all('link')
40
+
41
+ node.elements.each do |e|
42
+ property = ""
43
+
44
+ if ns = node_namespace(e)
45
+ property << "#{ns}_" unless ns == 'xmlns'
46
+ end
47
+
48
+ property << e.name
49
+
50
+ # Multiple type is the case of content elements, which appear twice, with type="text" and type="html"
51
+ if multiple_type?(e)
52
+ if ts.respond_to?(property)
53
+ ts.send(property).send("#{e.attributes['type']}=", load_recursive(e))
54
+ else
55
+ ts.send("#{property}=", PartigiStruct.new)
56
+ ts.send(property).send("#{e.attributes['type']}=", load_recursive(e))
57
+ end
58
+ else
59
+ ts.send("#{property}=", load_recursive(e))
60
+ end
61
+ end
62
+
63
+ unless links.empty?
64
+ ts.send("links=", links.map{|l| build_struct(l)})
65
+ end
66
+
67
+ ts
68
+ end
69
+
70
+ # Most of the time Twitter specifies nodes that contain an array of
71
+ # sub-nodes with a type="array" attribute. There are some nodes that
72
+ # they dont' do that for, though, including the <ids> node returned
73
+ # by the social graph methods. This method tries to work in both situations.
74
+ def array_node?(node)
75
+ node.attributes['type'] == 'collection'
76
+ end
77
+
78
+ # Nodes which content must not be processed
79
+ def cdata_node?(node)
80
+ ['xhtml', 'html'].include?(node.attributes['type']) && !node.cdatas.empty?
81
+ end
82
+
83
+ def raw_node?(node)
84
+ node.name == 'content' && node.attributes['type'] == 'text'
85
+ end
86
+
87
+ # Nodes corresponding to an element repeated with different types
88
+ def multiple_type?(node)
89
+ node.name == 'content' && !node.attributes['type'].nil?
90
+ end
91
+
92
+ def fixnum?(value)
93
+ value =~ /^\d+$/
94
+ end
95
+
96
+ def node_namespace(node)
97
+ node.namespace.blank? ? nil : node.namespaces.invert[node.namespace]
98
+ end
99
+
100
+ def ignore_attributes?(node)
101
+ element_name = node.name
102
+ ns = node_namespace(node)
103
+ element_name.insert(0, "#{ns}:") if ns
104
+
105
+ IGNORE_ATTRIBUTES_FOR.include?(element_name)
106
+ end
107
+
108
+ IGNORE_ATTRIBUTES_FOR = ['ptItem:synopsis', 'ptItem:title']
109
+ end
110
+ end
111
+ end