voxdolo-httparty 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. data/History +108 -0
  2. data/MIT-LICENSE +20 -0
  3. data/Manifest +55 -0
  4. data/README +35 -0
  5. data/Rakefile +47 -0
  6. data/bin/httparty +98 -0
  7. data/cucumber.yml +1 -0
  8. data/examples/aaws.rb +32 -0
  9. data/examples/basic.rb +11 -0
  10. data/examples/delicious.rb +37 -0
  11. data/examples/google.rb +16 -0
  12. data/examples/rubyurl.rb +14 -0
  13. data/examples/twitter.rb +31 -0
  14. data/examples/whoismyrep.rb +10 -0
  15. data/features/basic_authentication.feature +20 -0
  16. data/features/command_line.feature +7 -0
  17. data/features/deals_with_http_error_codes.feature +26 -0
  18. data/features/handles_multiple_formats.feature +34 -0
  19. data/features/steps/env.rb +15 -0
  20. data/features/steps/httparty_response_steps.rb +26 -0
  21. data/features/steps/httparty_steps.rb +15 -0
  22. data/features/steps/mongrel_helper.rb +55 -0
  23. data/features/steps/remote_service_steps.rb +47 -0
  24. data/features/supports_redirection.feature +22 -0
  25. data/httparty.gemspec +37 -0
  26. data/lib/core_extensions.rb +175 -0
  27. data/lib/httparty/cookie_hash.rb +9 -0
  28. data/lib/httparty/exceptions.rb +7 -0
  29. data/lib/httparty/module_inheritable_attributes.rb +25 -0
  30. data/lib/httparty/parsers/json.rb +74 -0
  31. data/lib/httparty/parsers/xml.rb +209 -0
  32. data/lib/httparty/parsers.rb +4 -0
  33. data/lib/httparty/request.rb +139 -0
  34. data/lib/httparty/response.rb +17 -0
  35. data/lib/httparty/version.rb +3 -0
  36. data/lib/httparty.rb +201 -0
  37. data/setup.rb +1585 -0
  38. data/spec/fixtures/delicious.xml +23 -0
  39. data/spec/fixtures/empty.xml +0 -0
  40. data/spec/fixtures/google.html +3 -0
  41. data/spec/fixtures/twitter.json +1 -0
  42. data/spec/fixtures/twitter.xml +403 -0
  43. data/spec/fixtures/undefined_method_add_node_for_nil.xml +2 -0
  44. data/spec/hash_spec.rb +49 -0
  45. data/spec/httparty/cookie_hash_spec.rb +38 -0
  46. data/spec/httparty/parsers/json_spec.rb +42 -0
  47. data/spec/httparty/parsers/xml_spec.rb +445 -0
  48. data/spec/httparty/request_spec.rb +205 -0
  49. data/spec/httparty/response_spec.rb +53 -0
  50. data/spec/httparty_spec.rb +259 -0
  51. data/spec/spec.opts +3 -0
  52. data/spec/spec_helper.rb +21 -0
  53. data/spec/string_spec.rb +27 -0
  54. data/website/css/common.css +47 -0
  55. data/website/index.html +74 -0
  56. metadata +133 -0
@@ -0,0 +1,15 @@
1
+ When /I call HTTParty#get with '(.*)'$/ do |url|
2
+ begin
3
+ @response_from_httparty = HTTParty.get("http://#{@host_and_port}#{url}")
4
+ rescue HTTParty::RedirectionTooDeep => e
5
+ @exception_from_httparty = e
6
+ end
7
+ end
8
+
9
+ When /I call HTTParty#get with '(.*)' and a basic_auth hash:/ do |url, auth_table|
10
+ h = auth_table.hashes.first
11
+ @response_from_httparty = HTTParty.get(
12
+ "http://#{@host_and_port}#{url}",
13
+ :basic_auth => { :username => h["username"], :password => h["password"] }
14
+ )
15
+ end
@@ -0,0 +1,55 @@
1
+ def basic_mongrel_handler
2
+ Class.new(Mongrel::HttpHandler) do
3
+ attr_writer :content_type, :response_body, :response_code
4
+
5
+ def initialize
6
+ @content_type = "text/html"
7
+ @response_body = ""
8
+ @response_code = 200
9
+ @custom_headers = {}
10
+ end
11
+
12
+ def process(request, response)
13
+ reply_with(response, @response_code, @response_body)
14
+ end
15
+
16
+ def reply_with(response, code, response_body)
17
+ response.start(code) do |head, body|
18
+ head["Content-Type"] = @content_type
19
+ @custom_headers.each { |k,v| head[k] = v }
20
+ body.write(response_body)
21
+ end
22
+ end
23
+ end
24
+ end
25
+
26
+ def new_mongrel_handler
27
+ basic_mongrel_handler.new
28
+ end
29
+
30
+ def add_basic_authentication_to(handler)
31
+ m = Module.new do
32
+ attr_writer :username, :password
33
+
34
+ def self.extended(base)
35
+ base.instance_eval { @custom_headers["WWW-Authenticate"] = 'Basic Realm="Super Secret Page"' }
36
+ base.class_eval { alias_method_chain :process, :basic_authentication }
37
+ end
38
+
39
+ def process_with_basic_authentication(request, response)
40
+ if authorized?(request) then process_without_basic_authentication(request, response)
41
+ else reply_with(response, 401, "Incorrect. You have 20 seconds to comply.")
42
+ end
43
+ end
44
+
45
+ def authorized?(request)
46
+ request.params["HTTP_AUTHORIZATION"] == "Basic " + Base64.encode64("#{@username}:#{@password}").strip
47
+ end
48
+ end
49
+ handler.extend(m)
50
+ end
51
+
52
+ def new_mongrel_redirector(target_url, relative_path = false)
53
+ target_url = "http://#{@host_and_port}#{target_url}" unless relative_path
54
+ Mongrel::RedirectHandler.new(target_url)
55
+ end
@@ -0,0 +1,47 @@
1
+ Given /a remote service that returns '(.*)'/ do |response_body|
2
+ @handler = new_mongrel_handler
3
+ Given "the response from the service has a body of '#{response_body}'"
4
+ end
5
+
6
+ Given /a remote service that returns a (\d+) status code/ do |code|
7
+ @handler = new_mongrel_handler
8
+ @handler.response_code = code
9
+ end
10
+
11
+ Given /that service is accessed at the path '(.*)'/ do |path|
12
+ @server.register(path, @handler)
13
+ end
14
+
15
+ Given /the response from the service has a Content-Type of '(.*)'/ do |content_type|
16
+ @handler.content_type = content_type
17
+ end
18
+
19
+ Given /the response from the service has a body of '(.*)'/ do |response_body|
20
+ @handler.response_body = response_body
21
+ end
22
+
23
+ Given /the url '(.*)' redirects to '(.*)'/ do |redirection_url, target_url|
24
+ @server.register redirection_url, new_mongrel_redirector(target_url)
25
+ end
26
+
27
+ Given /that service is protected by Basic Authentication/ do
28
+ add_basic_authentication_to @handler
29
+ end
30
+
31
+ Given /that service requires the username '(.*)' with the password '(.*)'/ do |username, password|
32
+ @handler.username = username
33
+ @handler.password = password
34
+ end
35
+
36
+ Given /a restricted page at '(.*)'/ do |url|
37
+ Given "a remote service that returns 'A response I will never see'"
38
+ And "that service is accessed at the path '#{url}'"
39
+ And "that service is protected by Basic Authentication"
40
+ And "that service requires the username 'something' with the password 'secret'"
41
+ end
42
+
43
+ # This joins the server thread, and halts cucumber, so you can actually hit the
44
+ # server with a browser. Runs until you kill it with Ctrl-c
45
+ Given /I want to hit this in a browser/ do
46
+ @server.acceptor.join
47
+ end
@@ -0,0 +1,22 @@
1
+ Feature: Supports Redirection
2
+
3
+ As a developer
4
+ I want to work with services that may redirect me
5
+ And I want it to follow a reasonable number of redirects
6
+ Because sometimes web services do that
7
+
8
+ Scenario: A service that redirects once
9
+ Given a remote service that returns 'Service Response'
10
+ And that service is accessed at the path '/service.html'
11
+ And the url '/redirector.html' redirects to '/service.html'
12
+ When I call HTTParty#get with '/redirector.html'
13
+ Then the return value should match 'Service Response'
14
+
15
+ # TODO: Look in to why this actually fails...
16
+ Scenario: A service that redirects to a relative URL
17
+
18
+ Scenario: A service that redirects infinitely
19
+ Given the url '/first.html' redirects to '/second.html'
20
+ And the url '/second.html' redirects to '/first.html'
21
+ When I call HTTParty#get with '/first.html'
22
+ Then it should raise an HTTParty::RedirectionTooDeep exception
data/httparty.gemspec ADDED
@@ -0,0 +1,37 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{httparty}
5
+ s.version = "0.3.1"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["John Nunemaker"]
9
+ s.date = %q{2009-02-10}
10
+ s.default_executable = %q{httparty}
11
+ s.description = %q{Makes http fun! Also, makes consuming restful web services dead easy.}
12
+ s.email = %q{nunemaker@gmail.com}
13
+ s.executables = ["httparty"]
14
+ s.extra_rdoc_files = ["bin/httparty", "lib/core_extensions.rb", "lib/httparty/cookie_hash.rb", "lib/httparty/exceptions.rb", "lib/httparty/module_inheritable_attributes.rb", "lib/httparty/parsers/json.rb", "lib/httparty/parsers/xml.rb", "lib/httparty/parsers.rb", "lib/httparty/request.rb", "lib/httparty/response.rb", "lib/httparty/version.rb", "lib/httparty.rb", "README"]
15
+ s.files = ["bin/httparty", "cucumber.yml", "examples/aaws.rb", "examples/basic.rb", "examples/delicious.rb", "examples/google.rb", "examples/rubyurl.rb", "examples/twitter.rb", "examples/whoismyrep.rb", "features/basic_authentication.feature", "features/command_line.feature", "features/deals_with_http_error_codes.feature", "features/handles_multiple_formats.feature", "features/steps/env.rb", "features/steps/httparty_response_steps.rb", "features/steps/httparty_steps.rb", "features/steps/mongrel_helper.rb", "features/steps/remote_service_steps.rb", "features/supports_redirection.feature", "History", "httparty.gemspec", "lib/core_extensions.rb", "lib/httparty/cookie_hash.rb", "lib/httparty/exceptions.rb", "lib/httparty/module_inheritable_attributes.rb", "lib/httparty/parsers/json.rb", "lib/httparty/parsers/xml.rb", "lib/httparty/parsers.rb", "lib/httparty/request.rb", "lib/httparty/response.rb", "lib/httparty/version.rb", "lib/httparty.rb", "Manifest", "MIT-LICENSE", "Rakefile", "README", "setup.rb", "spec/fixtures/delicious.xml", "spec/fixtures/empty.xml", "spec/fixtures/google.html", "spec/fixtures/twitter.json", "spec/fixtures/twitter.xml", "spec/fixtures/undefined_method_add_node_for_nil.xml", "spec/hash_spec.rb", "spec/httparty/cookie_hash_spec.rb", "spec/httparty/parsers/json_spec.rb", "spec/httparty/parsers/xml_spec.rb", "spec/httparty/request_spec.rb", "spec/httparty/response_spec.rb", "spec/httparty_spec.rb", "spec/spec.opts", "spec/spec_helper.rb", "spec/string_spec.rb", "website/css/common.css", "website/index.html"]
16
+ s.has_rdoc = true
17
+ s.homepage = %q{http://httparty.rubyforge.org}
18
+ s.post_install_message = %q{When you HTTParty, you must party hard!}
19
+ s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Httparty", "--main", "README"]
20
+ s.require_paths = ["lib"]
21
+ s.rubyforge_project = %q{httparty}
22
+ s.rubygems_version = %q{1.3.1}
23
+ s.summary = %q{Makes http fun! Also, makes consuming restful web services dead easy.}
24
+
25
+ if s.respond_to? :specification_version then
26
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
27
+ s.specification_version = 2
28
+
29
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
30
+ s.add_development_dependency(%q<echoe>, [">= 0"])
31
+ else
32
+ s.add_dependency(%q<echoe>, [">= 0"])
33
+ end
34
+ else
35
+ s.add_dependency(%q<echoe>, [">= 0"])
36
+ end
37
+ end
@@ -0,0 +1,175 @@
1
+ require 'time'
2
+
3
+ # Copyright (c) 2008 Sam Smoot.
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining
6
+ # a copy of this software and associated documentation files (the
7
+ # "Software"), to deal in the Software without restriction, including
8
+ # without limitation the rights to use, copy, modify, merge, publish,
9
+ # distribute, sublicense, and/or sell copies of the Software, and to
10
+ # permit persons to whom the Software is furnished to do so, subject to
11
+ # the following conditions:
12
+ #
13
+ # The above copyright notice and this permission notice shall be
14
+ # included in all copies or substantial portions of the Software.
15
+ #
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
+
24
+ class Object #:nodoc:
25
+ # @return <TrueClass, FalseClass>
26
+ #
27
+ # @example [].blank? #=> true
28
+ # @example [1].blank? #=> false
29
+ # @example [nil].blank? #=> false
30
+ #
31
+ # Returns true if the object is nil or empty (if applicable)
32
+ def blank?
33
+ nil? || (respond_to?(:empty?) && empty?)
34
+ end unless method_defined?(:blank?)
35
+ end # class Object
36
+
37
+ class Numeric #:nodoc:
38
+ # @return <TrueClass, FalseClass>
39
+ #
40
+ # Numerics can't be blank
41
+ def blank?
42
+ false
43
+ end unless method_defined?(:blank?)
44
+ end # class Numeric
45
+
46
+ class NilClass #:nodoc:
47
+ # @return <TrueClass, FalseClass>
48
+ #
49
+ # Nils are always blank
50
+ def blank?
51
+ true
52
+ end unless method_defined?(:blank?)
53
+ end # class NilClass
54
+
55
+ class TrueClass #:nodoc:
56
+ # @return <TrueClass, FalseClass>
57
+ #
58
+ # True is not blank.
59
+ def blank?
60
+ false
61
+ end unless method_defined?(:blank?)
62
+ end # class TrueClass
63
+
64
+ class FalseClass #:nodoc:
65
+ # False is always blank.
66
+ def blank?
67
+ true
68
+ end unless method_defined?(:blank?)
69
+ end # class FalseClass
70
+
71
+ class String #:nodoc:
72
+ # @example "".blank? #=> true
73
+ # @example " ".blank? #=> true
74
+ # @example " hey ho ".blank? #=> false
75
+ #
76
+ # @return <TrueClass, FalseClass>
77
+ #
78
+ # Strips out whitespace then tests if the string is empty.
79
+ def blank?
80
+ strip.empty?
81
+ end unless method_defined?(:blank?)
82
+
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
89
+
90
+ class Hash #:nodoc:
91
+ # @return <String> This hash as a query string
92
+ #
93
+ # @example
94
+ # { :name => "Bob",
95
+ # :address => {
96
+ # :street => '111 Ruby Ave.',
97
+ # :city => 'Ruby Central',
98
+ # :phones => ['111-111-1111', '222-222-2222']
99
+ # }
100
+ # }.to_params
101
+ # #=> "name=Bob&address[city]=Ruby Central&address[phones][]=111-111-1111&address[phones][]=222-222-2222&address[street]=111 Ruby Ave."
102
+ def to_params
103
+ params = self.map { |k,v| normalize_param(k,v) }.join
104
+ params.chop! # trailing &
105
+ params
106
+ end
107
+
108
+ # @param key<Object> The key for the param.
109
+ # @param value<Object> The value for the param.
110
+ #
111
+ # @return <String> This key value pair as a param
112
+ #
113
+ # @example normalize_param(:name, "Bob Jones") #=> "name=Bob%20Jones&"
114
+ def normalize_param(key, value)
115
+ param = ''
116
+ stack = []
117
+
118
+ if value.is_a?(Array)
119
+ param << value.map { |element| normalize_param("#{key}[]", element) }.join
120
+ elsif value.is_a?(Hash)
121
+ stack << [key,value]
122
+ else
123
+ param << "#{key}=#{URI.encode(value.to_s, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))}&"
124
+ end
125
+
126
+ stack.each do |parent, hash|
127
+ hash.each do |key, value|
128
+ if value.is_a?(Hash)
129
+ stack << ["#{parent}[#{key}]", value]
130
+ else
131
+ param << normalize_param("#{parent}[#{key}]", value)
132
+ end
133
+ end
134
+ end
135
+
136
+ param
137
+ end
138
+
139
+ # @return <String> The hash as attributes for an XML tag.
140
+ #
141
+ # @example
142
+ # { :one => 1, "two"=>"TWO" }.to_xml_attributes
143
+ # #=> 'one="1" two="TWO"'
144
+ def to_xml_attributes
145
+ map do |k,v|
146
+ %{#{k.to_s.snake_case.sub(/^(.{1,1})/) { |m| m.downcase }}="#{v}"}
147
+ end.join(' ')
148
+ 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
@@ -0,0 +1,7 @@
1
+ module HTTParty
2
+ # Exception raised when you attempt to set a non-existant format
3
+ class UnsupportedFormat < StandardError; end
4
+
5
+ # Exception that is raised when request has redirected too many times
6
+ class RedirectionTooDeep < StandardError; end
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