alexvollmer-httparty 0.2.6 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. data/History +34 -0
  2. data/Manifest +25 -2
  3. data/README +2 -2
  4. data/Rakefile +6 -2
  5. data/bin/httparty +39 -44
  6. data/cucumber.yml +1 -0
  7. data/examples/basic.rb +6 -1
  8. data/examples/delicious.rb +1 -0
  9. data/examples/rubyurl.rb +1 -1
  10. data/features/basic_authentication.feature +20 -0
  11. data/features/command_line.feature +7 -0
  12. data/features/deals_with_http_error_codes.feature +26 -0
  13. data/features/handles_multiple_formats.feature +34 -0
  14. data/features/steps/env.rb +15 -0
  15. data/features/steps/httparty_response_steps.rb +26 -0
  16. data/features/steps/httparty_steps.rb +15 -0
  17. data/features/steps/mongrel_helper.rb +55 -0
  18. data/features/steps/remote_service_steps.rb +47 -0
  19. data/features/supports_redirection.feature +22 -0
  20. data/httparty.gemspec +4 -7
  21. data/lib/core_extensions.rb +48 -222
  22. data/lib/httparty/cookie_hash.rb +9 -0
  23. data/lib/httparty/exceptions.rb +3 -0
  24. data/lib/httparty/module_inheritable_attributes.rb +25 -0
  25. data/lib/httparty/parsers/json.rb +74 -0
  26. data/lib/httparty/parsers/xml.rb +209 -0
  27. data/lib/httparty/parsers.rb +4 -0
  28. data/lib/httparty/request.rb +63 -76
  29. data/lib/httparty/response.rb +17 -0
  30. data/lib/httparty/version.rb +2 -2
  31. data/lib/httparty.rb +108 -19
  32. data/spec/fixtures/empty.xml +0 -0
  33. data/spec/fixtures/undefined_method_add_node_for_nil.xml +2 -0
  34. data/spec/hash_spec.rb +49 -0
  35. data/spec/httparty/cookie_hash_spec.rb +38 -0
  36. data/spec/httparty/parsers/json_spec.rb +42 -0
  37. data/spec/httparty/parsers/xml_spec.rb +445 -0
  38. data/spec/httparty/request_spec.rb +219 -80
  39. data/spec/httparty/response_spec.rb +53 -0
  40. data/spec/httparty_spec.rb +125 -64
  41. data/spec/spec_helper.rb +5 -8
  42. data/spec/string_spec.rb +27 -0
  43. metadata +34 -14
  44. data/lib/module_level_inheritable_attributes.rb +0 -25
  45. data/spec/as_buggery_spec.rb +0 -16
@@ -21,7 +21,7 @@ require 'time'
21
21
  # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
22
  # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
23
 
24
- class Object
24
+ class Object #:nodoc:
25
25
  # @return <TrueClass, FalseClass>
26
26
  #
27
27
  # @example [].blank? #=> true
@@ -31,44 +31,44 @@ class Object
31
31
  # Returns true if the object is nil or empty (if applicable)
32
32
  def blank?
33
33
  nil? || (respond_to?(:empty?) && empty?)
34
- end
34
+ end unless method_defined?(:blank?)
35
35
  end # class Object
36
36
 
37
- class Numeric
37
+ class Numeric #:nodoc:
38
38
  # @return <TrueClass, FalseClass>
39
39
  #
40
40
  # Numerics can't be blank
41
41
  def blank?
42
42
  false
43
- end
43
+ end unless method_defined?(:blank?)
44
44
  end # class Numeric
45
45
 
46
- class NilClass
46
+ class NilClass #:nodoc:
47
47
  # @return <TrueClass, FalseClass>
48
48
  #
49
49
  # Nils are always blank
50
50
  def blank?
51
51
  true
52
- end
52
+ end unless method_defined?(:blank?)
53
53
  end # class NilClass
54
54
 
55
- class TrueClass
55
+ class TrueClass #:nodoc:
56
56
  # @return <TrueClass, FalseClass>
57
57
  #
58
58
  # True is not blank.
59
59
  def blank?
60
60
  false
61
- end
61
+ end unless method_defined?(:blank?)
62
62
  end # class TrueClass
63
63
 
64
- class FalseClass
64
+ class FalseClass #:nodoc:
65
65
  # False is always blank.
66
66
  def blank?
67
67
  true
68
- end
68
+ end unless method_defined?(:blank?)
69
69
  end # class FalseClass
70
70
 
71
- class String
71
+ class String #:nodoc:
72
72
  # @example "".blank? #=> true
73
73
  # @example " ".blank? #=> true
74
74
  # @example " hey ho ".blank? #=> false
@@ -78,216 +78,16 @@ class String
78
78
  # Strips out whitespace then tests if the string is empty.
79
79
  def blank?
80
80
  strip.empty?
81
- end
82
- end # class String
83
-
84
- require 'rexml/parsers/streamparser'
85
- require 'rexml/parsers/baseparser'
86
- require 'rexml/light/node'
87
-
88
- # This is a slighly modified version of the XMLUtilityNode from
89
- # http://merb.devjavu.com/projects/merb/ticket/95 (has.sox@gmail.com)
90
- # It's mainly just adding vowels, as I ht cd wth n vwls :)
91
- # This represents the hard part of the work, all I did was change the
92
- # underlying parser.
93
- class REXMLUtilityNode
94
- attr_accessor :name, :attributes, :children, :type
95
-
96
- def self.typecasts
97
- @@typecasts
98
- end
81
+ end unless method_defined?(:blank?)
99
82
 
100
- def self.typecasts=(obj)
101
- @@typecasts = obj
102
- end
103
-
104
- def self.available_typecasts
105
- @@available_typecasts
106
- end
107
-
108
- def self.available_typecasts=(obj)
109
- @@available_typecasts = obj
110
- end
111
-
112
- self.typecasts = {}
113
- self.typecasts["integer"] = lambda{|v| v.nil? ? nil : v.to_i}
114
- self.typecasts["boolean"] = lambda{|v| v.nil? ? nil : (v.strip != "false")}
115
- self.typecasts["datetime"] = lambda{|v| v.nil? ? nil : Time.parse(v).utc}
116
- self.typecasts["date"] = lambda{|v| v.nil? ? nil : Date.parse(v)}
117
- self.typecasts["dateTime"] = lambda{|v| v.nil? ? nil : Time.parse(v).utc}
118
- self.typecasts["decimal"] = lambda{|v| BigDecimal(v)}
119
- self.typecasts["double"] = lambda{|v| v.nil? ? nil : v.to_f}
120
- self.typecasts["float"] = lambda{|v| v.nil? ? nil : v.to_f}
121
- self.typecasts["symbol"] = lambda{|v| v.to_sym}
122
- self.typecasts["string"] = lambda{|v| v.to_s}
123
- self.typecasts["yaml"] = lambda{|v| v.nil? ? nil : YAML.load(v)}
124
- self.typecasts["base64Binary"] = lambda{|v| v.unpack('m').first }
125
-
126
- self.available_typecasts = self.typecasts.keys
127
-
128
- def initialize(name, attributes = {})
129
- @name = name.tr("-", "_")
130
- # leave the type alone if we don't know what it is
131
- @type = self.class.available_typecasts.include?(attributes["type"]) ? attributes.delete("type") : attributes["type"]
132
-
133
- @nil_element = attributes.delete("nil") == "true"
134
- @attributes = undasherize_keys(attributes)
135
- @children = []
136
- @text = false
137
- end
138
-
139
- def add_node(node)
140
- @text = true if node.is_a? String
141
- @children << node
142
- end
143
-
144
- def to_hash
145
- if @type == "file"
146
- f = StringIO.new((@children.first || '').unpack('m').first)
147
- class << f
148
- attr_accessor :original_filename, :content_type
149
- end
150
- f.original_filename = attributes['name'] || 'untitled'
151
- f.content_type = attributes['content_type'] || 'application/octet-stream'
152
- return {name => f}
153
- end
154
-
155
- if @text
156
- return { name => typecast_value( translate_xml_entities( inner_html ) ) }
157
- else
158
- #change repeating groups into an array
159
- groups = @children.inject({}) { |s,e| (s[e.name] ||= []) << e; s }
160
-
161
- out = nil
162
- if @type == "array"
163
- out = []
164
- groups.each do |k, v|
165
- if v.size == 1
166
- out << v.first.to_hash.entries.first.last
167
- else
168
- out << v.map{|e| e.to_hash[k]}
169
- end
170
- end
171
- out = out.flatten
172
-
173
- else # If Hash
174
- out = {}
175
- groups.each do |k,v|
176
- if v.size == 1
177
- out.merge!(v.first)
178
- else
179
- out.merge!( k => v.map{|e| e.to_hash[k]})
180
- end
181
- end
182
- out.merge! attributes unless attributes.empty?
183
- out = out.empty? ? nil : out
184
- end
185
-
186
- if @type && out.nil?
187
- { name => typecast_value(out) }
188
- else
189
- { name => out }
190
- end
191
- end
192
- end
193
-
194
- # Typecasts a value based upon its type. For instance, if
195
- # +node+ has #type == "integer",
196
- # {{[node.typecast_value("12") #=> 12]}}
197
- #
198
- # @param value<String> The value that is being typecast.
199
- #
200
- # @details [:type options]
201
- # "integer"::
202
- # converts +value+ to an integer with #to_i
203
- # "boolean"::
204
- # checks whether +value+, after removing spaces, is the literal
205
- # "true"
206
- # "datetime"::
207
- # Parses +value+ using Time.parse, and returns a UTC Time
208
- # "date"::
209
- # Parses +value+ using Date.parse
210
- #
211
- # @return <Integer, TrueClass, FalseClass, Time, Date, Object>
212
- # The result of typecasting +value+.
213
- #
214
- # @note
215
- # If +self+ does not have a "type" key, or if it's not one of the
216
- # options specified above, the raw +value+ will be returned.
217
- def typecast_value(value)
218
- return value unless @type
219
- proc = self.class.typecasts[@type]
220
- proc.nil? ? value : proc.call(value)
221
- end
222
-
223
- # Convert basic XML entities into their literal values.
224
- #
225
- # @param value<#gsub> An XML fragment.
226
- #
227
- # @return <#gsub> The XML fragment after converting entities.
228
- def translate_xml_entities(value)
229
- value.gsub(/&lt;/, "<").
230
- gsub(/&gt;/, ">").
231
- gsub(/&quot;/, '"').
232
- gsub(/&apos;/, "'").
233
- gsub(/&amp;/, "&")
234
- end
235
-
236
- # Take keys of the form foo-bar and convert them to foo_bar
237
- def undasherize_keys(params)
238
- params.keys.each do |key, value|
239
- params[key.tr("-", "_")] = params.delete(key)
240
- end
241
- params
242
- end
243
-
244
- # Get the inner_html of the REXML node.
245
- def inner_html
246
- @children.join
247
- end
248
-
249
- # Converts the node into a readable HTML node.
250
- #
251
- # @return <String> The HTML node in text form.
252
- def to_html
253
- attributes.merge!(:type => @type ) if @type
254
- "<#{name}#{attributes.to_xml_attributes}>#{@nil_element ? '' : inner_html}</#{name}>"
255
- end
256
-
257
- # @alias #to_html #to_s
258
- def to_s
259
- to_html
260
- end
261
- end
262
-
263
- class ToHashParser
264
- def self.from_xml(xml)
265
- stack = []
266
- parser = REXML::Parsers::BaseParser.new(xml)
267
-
268
- while true
269
- event = parser.pull
270
- case event[0]
271
- when :end_document
272
- break
273
- when :end_doctype, :start_doctype
274
- # do nothing
275
- when :start_element
276
- stack.push REXMLUtilityNode.new(event[1], event[2])
277
- when :end_element
278
- if stack.size > 1
279
- temp = stack.pop
280
- stack.last.add_node(temp)
281
- end
282
- when :text, :cdata
283
- stack.last.add_node(event[1]) unless event[1].strip.length == 0
284
- end
285
- end
286
- stack.pop.to_hash
287
- end
288
- end
83
+ def snake_case
84
+ return self.downcase if self =~ /^[A-Z]+$/
85
+ self.gsub(/([A-Z]+)(?=[A-Z][a-z]?)|\B[A-Z]/, '_\&') =~ /_*(.*)/
86
+ return $+.downcase
87
+ end unless method_defined?(:snake_case)
88
+ end # class String
289
89
 
290
- class Hash
90
+ class Hash #:nodoc:
291
91
  # @return <String> This hash as a query string
292
92
  #
293
93
  # @example
@@ -310,7 +110,7 @@ class Hash
310
110
  #
311
111
  # @return <String> This key value pair as a param
312
112
  #
313
- # @example normalize_param(:name, "Bob") #=> "name=Bob&"
113
+ # @example normalize_param(:name, "Bob Jones") #=> "name=Bob%20Jones&"
314
114
  def normalize_param(key, value)
315
115
  param = ''
316
116
  stack = []
@@ -320,7 +120,7 @@ class Hash
320
120
  elsif value.is_a?(Hash)
321
121
  stack << [key,value]
322
122
  else
323
- param << "#{key}=#{URI.encode(value.to_s)}&"
123
+ param << "#{key}=#{URI.encode(value.to_s, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))}&"
324
124
  end
325
125
 
326
126
  stack.each do |parent, hash|
@@ -346,4 +146,30 @@ class Hash
346
146
  %{#{k.to_s.snake_case.sub(/^(.{1,1})/) { |m| m.downcase }}="#{v}"}
347
147
  end.join(' ')
348
148
  end
349
- end
149
+ end
150
+
151
+ class BlankSlate #:nodoc:
152
+ instance_methods.each { |m| undef_method m unless m =~ /^__/ }
153
+ end
154
+
155
+ # 1.8.6 has mistyping of transitive in if statement
156
+ require "rexml/document"
157
+ module REXML #:nodoc:
158
+ class Document < Element #:nodoc:
159
+ def write( output=$stdout, indent=-1, transitive=false, ie_hack=false )
160
+ if xml_decl.encoding != "UTF-8" && !output.kind_of?(Output)
161
+ output = Output.new( output, xml_decl.encoding )
162
+ end
163
+ formatter = if indent > -1
164
+ if transitive
165
+ REXML::Formatters::Transitive.new( indent, ie_hack )
166
+ else
167
+ REXML::Formatters::Pretty.new( indent, ie_hack )
168
+ end
169
+ else
170
+ REXML::Formatters::Default.new( ie_hack )
171
+ end
172
+ formatter.write( self, output )
173
+ end
174
+ end
175
+ end
@@ -0,0 +1,9 @@
1
+ class HTTParty::CookieHash < Hash #:nodoc:
2
+ def add_cookies(hash)
3
+ merge!(hash)
4
+ end
5
+
6
+ def to_cookie_string
7
+ collect { |k, v| "#{k}=#{v}" }.join("; ")
8
+ end
9
+ end
@@ -1,4 +1,7 @@
1
1
  module HTTParty
2
+ # Exception raised when you attempt to set a non-existant format
2
3
  class UnsupportedFormat < StandardError; end
4
+
5
+ # Exception that is raised when request has redirected too many times
3
6
  class RedirectionTooDeep < StandardError; end
4
7
  end
@@ -0,0 +1,25 @@
1
+ module HTTParty
2
+ module ModuleInheritableAttributes #:nodoc:
3
+ def self.included(base)
4
+ base.extend(ClassMethods)
5
+ end
6
+
7
+ module ClassMethods #:nodoc:
8
+ def mattr_inheritable(*args)
9
+ @mattr_inheritable_attrs ||= [:mattr_inheritable_attrs]
10
+ @mattr_inheritable_attrs += args
11
+ args.each do |arg|
12
+ module_eval %(class << self; attr_accessor :#{arg} end)
13
+ end
14
+ @mattr_inheritable_attrs
15
+ end
16
+
17
+ def inherited(subclass)
18
+ @mattr_inheritable_attrs.each do |inheritable_attribute|
19
+ instance_var = "@#{inheritable_attribute}"
20
+ subclass.instance_variable_set(instance_var, instance_variable_get(instance_var))
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,74 @@
1
+ # Copyright (c) 2004-2008 David Heinemeier Hansson
2
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
3
+ # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
4
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
5
+
6
+ require 'yaml'
7
+ require 'strscan'
8
+
9
+ module HTTParty
10
+ module Parsers #:nodoc:
11
+ module JSON #:nodoc:
12
+ class ParseError < StandardError #:nodoc:
13
+ end
14
+
15
+ def self.decode(json)
16
+ YAML.load(unescape(convert_json_to_yaml(json)))
17
+ rescue ArgumentError => e
18
+ raise ParseError, "Invalid JSON string"
19
+ end
20
+
21
+ protected
22
+
23
+ def self.unescape(str)
24
+ str.gsub(/\\u([0-9a-f]{4})/) {
25
+ [$1.hex].pack("U")
26
+ }
27
+ end
28
+
29
+ # matches YAML-formatted dates
30
+ DATE_REGEX = /^\d{4}-\d{2}-\d{2}|\d{4}-\d{1,2}-\d{1,2}[ \t]+\d{1,2}:\d{2}:\d{2}(\.[0-9]*)?(([ \t]*)Z|[-+]\d{2}?(:\d{2})?)?$/
31
+
32
+ # Ensure that ":" and "," are always followed by a space
33
+ def self.convert_json_to_yaml(json) #:nodoc:
34
+ scanner, quoting, marks, pos, times = StringScanner.new(json), false, [], nil, []
35
+ while scanner.scan_until(/(\\['"]|['":,\\]|\\.)/)
36
+ case char = scanner[1]
37
+ when '"', "'"
38
+ if !quoting
39
+ quoting = char
40
+ pos = scanner.pos
41
+ elsif quoting == char
42
+ if json[pos..scanner.pos-2] =~ DATE_REGEX
43
+ # found a date, track the exact positions of the quotes so we can remove them later.
44
+ # oh, and increment them for each current mark, each one is an extra padded space that bumps
45
+ # the position in the final YAML output
46
+ total_marks = marks.size
47
+ times << pos+total_marks << scanner.pos+total_marks
48
+ end
49
+ quoting = false
50
+ end
51
+ when ":",","
52
+ marks << scanner.pos - 1 unless quoting
53
+ end
54
+ end
55
+
56
+ if marks.empty?
57
+ json.gsub(/\\\//, '/')
58
+ else
59
+ left_pos = [-1].push(*marks)
60
+ right_pos = marks << json.length
61
+ output = []
62
+ left_pos.each_with_index do |left, i|
63
+ output << json[left.succ..right_pos[i]]
64
+ end
65
+ output = output * " "
66
+
67
+ times.each { |i| output[i-1] = ' ' }
68
+ output.gsub!(/\\\//, '/')
69
+ output
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,209 @@
1
+ require 'rexml/parsers/streamparser'
2
+ require 'rexml/parsers/baseparser'
3
+ require 'rexml/light/node'
4
+
5
+ # This is a slighly modified version of the XMLUtilityNode from
6
+ # http://merb.devjavu.com/projects/merb/ticket/95 (has.sox@gmail.com)
7
+ # It's mainly just adding vowels, as I ht cd wth n vwls :)
8
+ # This represents the hard part of the work, all I did was change the
9
+ # underlying parser.
10
+ class REXMLUtilityNode #:nodoc:
11
+ attr_accessor :name, :attributes, :children, :type
12
+
13
+ def self.typecasts
14
+ @@typecasts
15
+ end
16
+
17
+ def self.typecasts=(obj)
18
+ @@typecasts = obj
19
+ end
20
+
21
+ def self.available_typecasts
22
+ @@available_typecasts
23
+ end
24
+
25
+ def self.available_typecasts=(obj)
26
+ @@available_typecasts = obj
27
+ end
28
+
29
+ self.typecasts = {}
30
+ self.typecasts["integer"] = lambda{|v| v.nil? ? nil : v.to_i}
31
+ self.typecasts["boolean"] = lambda{|v| v.nil? ? nil : (v.strip != "false")}
32
+ self.typecasts["datetime"] = lambda{|v| v.nil? ? nil : Time.parse(v).utc}
33
+ self.typecasts["date"] = lambda{|v| v.nil? ? nil : Date.parse(v)}
34
+ self.typecasts["dateTime"] = lambda{|v| v.nil? ? nil : Time.parse(v).utc}
35
+ self.typecasts["decimal"] = lambda{|v| v.nil? ? nil : BigDecimal(v.to_s)}
36
+ self.typecasts["double"] = lambda{|v| v.nil? ? nil : v.to_f}
37
+ self.typecasts["float"] = lambda{|v| v.nil? ? nil : v.to_f}
38
+ self.typecasts["symbol"] = lambda{|v| v.nil? ? nil : v.to_sym}
39
+ self.typecasts["string"] = lambda{|v| v.to_s}
40
+ self.typecasts["yaml"] = lambda{|v| v.nil? ? nil : YAML.load(v)}
41
+ self.typecasts["base64Binary"] = lambda{|v| v.unpack('m').first }
42
+
43
+ self.available_typecasts = self.typecasts.keys
44
+
45
+ def initialize(name, attributes = {})
46
+ @name = name.tr("-", "_")
47
+ # leave the type alone if we don't know what it is
48
+ @type = self.class.available_typecasts.include?(attributes["type"]) ? attributes.delete("type") : attributes["type"]
49
+
50
+ @nil_element = attributes.delete("nil") == "true"
51
+ @attributes = undasherize_keys(attributes)
52
+ @children = []
53
+ @text = false
54
+ end
55
+
56
+ def add_node(node)
57
+ @text = true if node.is_a? String
58
+ @children << node
59
+ end
60
+
61
+ def to_hash
62
+ if @type == "file"
63
+ f = StringIO.new((@children.first || '').unpack('m').first)
64
+ class << f
65
+ attr_accessor :original_filename, :content_type
66
+ end
67
+ f.original_filename = attributes['name'] || 'untitled'
68
+ f.content_type = attributes['content_type'] || 'application/octet-stream'
69
+ return {name => f}
70
+ end
71
+
72
+ if @text
73
+ return { name => typecast_value( translate_xml_entities( inner_html ) ) }
74
+ else
75
+ #change repeating groups into an array
76
+ groups = @children.inject({}) { |s,e| (s[e.name] ||= []) << e; s }
77
+
78
+ out = nil
79
+ if @type == "array"
80
+ out = []
81
+ groups.each do |k, v|
82
+ if v.size == 1
83
+ out << v.first.to_hash.entries.first.last
84
+ else
85
+ out << v.map{|e| e.to_hash[k]}
86
+ end
87
+ end
88
+ out = out.flatten
89
+
90
+ else # If Hash
91
+ out = {}
92
+ groups.each do |k,v|
93
+ if v.size == 1
94
+ out.merge!(v.first)
95
+ else
96
+ out.merge!( k => v.map{|e| e.to_hash[k]})
97
+ end
98
+ end
99
+ out.merge! attributes unless attributes.empty?
100
+ out = out.empty? ? nil : out
101
+ end
102
+
103
+ if @type && out.nil?
104
+ { name => typecast_value(out) }
105
+ else
106
+ { name => out }
107
+ end
108
+ end
109
+ end
110
+
111
+ # Typecasts a value based upon its type. For instance, if
112
+ # +node+ has #type == "integer",
113
+ # {{[node.typecast_value("12") #=> 12]}}
114
+ #
115
+ # @param value<String> The value that is being typecast.
116
+ #
117
+ # @details [:type options]
118
+ # "integer"::
119
+ # converts +value+ to an integer with #to_i
120
+ # "boolean"::
121
+ # checks whether +value+, after removing spaces, is the literal
122
+ # "true"
123
+ # "datetime"::
124
+ # Parses +value+ using Time.parse, and returns a UTC Time
125
+ # "date"::
126
+ # Parses +value+ using Date.parse
127
+ #
128
+ # @return <Integer, TrueClass, FalseClass, Time, Date, Object>
129
+ # The result of typecasting +value+.
130
+ #
131
+ # @note
132
+ # If +self+ does not have a "type" key, or if it's not one of the
133
+ # options specified above, the raw +value+ will be returned.
134
+ def typecast_value(value)
135
+ return value unless @type
136
+ proc = self.class.typecasts[@type]
137
+ proc.nil? ? value : proc.call(value)
138
+ end
139
+
140
+ # Convert basic XML entities into their literal values.
141
+ #
142
+ # @param value<#gsub> An XML fragment.
143
+ #
144
+ # @return <#gsub> The XML fragment after converting entities.
145
+ def translate_xml_entities(value)
146
+ value.gsub(/&lt;/, "<").
147
+ gsub(/&gt;/, ">").
148
+ gsub(/&quot;/, '"').
149
+ gsub(/&apos;/, "'").
150
+ gsub(/&amp;/, "&")
151
+ end
152
+
153
+ # Take keys of the form foo-bar and convert them to foo_bar
154
+ def undasherize_keys(params)
155
+ params.keys.each do |key, value|
156
+ params[key.tr("-", "_")] = params.delete(key)
157
+ end
158
+ params
159
+ end
160
+
161
+ # Get the inner_html of the REXML node.
162
+ def inner_html
163
+ @children.join
164
+ end
165
+
166
+ # Converts the node into a readable HTML node.
167
+ #
168
+ # @return <String> The HTML node in text form.
169
+ def to_html
170
+ attributes.merge!(:type => @type ) if @type
171
+ "<#{name}#{attributes.to_xml_attributes}>#{@nil_element ? '' : inner_html}</#{name}>"
172
+ end
173
+
174
+ # @alias #to_html #to_s
175
+ def to_s
176
+ to_html
177
+ end
178
+ end
179
+
180
+ module HTTParty
181
+ module Parsers #:nodoc:
182
+ module XML #:nodoc:
183
+ def self.parse(xml)
184
+ stack = []
185
+ parser = REXML::Parsers::BaseParser.new(xml)
186
+
187
+ while true
188
+ event = parser.pull
189
+ case event[0]
190
+ when :end_document
191
+ break
192
+ when :end_doctype, :start_doctype
193
+ # do nothing
194
+ when :start_element
195
+ stack.push REXMLUtilityNode.new(event[1], event[2])
196
+ when :end_element
197
+ if stack.size > 1
198
+ temp = stack.pop
199
+ stack.last.add_node(temp)
200
+ end
201
+ when :text, :cdata
202
+ stack.last.add_node(event[1]) unless event[1].strip.length == 0 || stack.empty?
203
+ end
204
+ end
205
+ stack.pop.to_hash
206
+ end
207
+ end
208
+ end
209
+ end
@@ -0,0 +1,4 @@
1
+ Dir[File.dirname(__FILE__) + "/parsers/*.rb"].sort.each do |path|
2
+ filename = File.basename(path)
3
+ require "httparty/parsers/#{filename}"
4
+ end