|
8a70a8ef
»
|
jnunemaker |
2008-12-06 |
Removed active support. Add... |
1 |
# Copyright (c) 2008 Sam Smoot. |
| |
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. |
| |
21 |
|
| |
22 |
class Object |
| |
23 |
# @return <TrueClass, FalseClass> |
| |
24 |
# |
| |
25 |
# @example [].blank? #=> true |
| |
26 |
# @example [1].blank? #=> false |
| |
27 |
# @example [nil].blank? #=> false |
| |
28 |
# |
| |
29 |
# Returns true if the object is nil or empty (if applicable) |
| |
30 |
def blank? |
| |
31 |
nil? || (respond_to?(:empty?) && empty?) |
| |
32 |
end |
| |
33 |
end # class Object |
| |
34 |
|
| |
35 |
class Numeric |
| |
36 |
# @return <TrueClass, FalseClass> |
| |
37 |
# |
| |
38 |
# Numerics can't be blank |
| |
39 |
def blank? |
| |
40 |
false |
| |
41 |
end |
| |
42 |
end # class Numeric |
| |
43 |
|
| |
44 |
class NilClass |
| |
45 |
# @return <TrueClass, FalseClass> |
| |
46 |
# |
| |
47 |
# Nils are always blank |
| |
48 |
def blank? |
| |
49 |
true |
| |
50 |
end |
| |
51 |
end # class NilClass |
| |
52 |
|
| |
53 |
class TrueClass |
| |
54 |
# @return <TrueClass, FalseClass> |
| |
55 |
# |
| |
56 |
# True is not blank. |
| |
57 |
def blank? |
| |
58 |
false |
| |
59 |
end |
| |
60 |
end # class TrueClass |
| |
61 |
|
| |
62 |
class FalseClass |
| |
63 |
# False is always blank. |
| |
64 |
def blank? |
| |
65 |
true |
| |
66 |
end |
| |
67 |
end # class FalseClass |
| |
68 |
|
| |
69 |
class String |
| |
70 |
# @example "".blank? #=> true |
| |
71 |
# @example " ".blank? #=> true |
| |
72 |
# @example " hey ho ".blank? #=> false |
| |
73 |
# |
| |
74 |
# @return <TrueClass, FalseClass> |
| |
75 |
# |
| |
76 |
# Strips out whitespace then tests if the string is empty. |
| |
77 |
def blank? |
| |
78 |
strip.empty? |
| |
79 |
end |
| |
80 |
end # class String |
|
7fab301e
»
|
jnunemaker |
2008-12-06 |
Moved xml stuff into core e... |
81 |
|
| |
82 |
require 'rexml/parsers/streamparser' |
| |
83 |
require 'rexml/parsers/baseparser' |
| |
84 |
require 'rexml/light/node' |
| |
85 |
|
| |
86 |
# This is a slighly modified version of the XMLUtilityNode from |
| |
87 |
# http://merb.devjavu.com/projects/merb/ticket/95 (has.sox@gmail.com) |
| |
88 |
# It's mainly just adding vowels, as I ht cd wth n vwls :) |
| |
89 |
# This represents the hard part of the work, all I did was change the |
| |
90 |
# underlying parser. |
| |
91 |
class REXMLUtilityNode |
| |
92 |
attr_accessor :name, :attributes, :children, :type |
| |
93 |
|
| |
94 |
def self.typecasts |
| |
95 |
@@typecasts |
| |
96 |
end |
| |
97 |
|
| |
98 |
def self.typecasts=(obj) |
| |
99 |
@@typecasts = obj |
| |
100 |
end |
| |
101 |
|
| |
102 |
def self.available_typecasts |
| |
103 |
@@typecasts |
| |
104 |
end |
| |
105 |
|
| |
106 |
def self.available_typecasts=(obj) |
| |
107 |
@@typecasts = obj |
| |
108 |
end |
| |
109 |
|
| |
110 |
self.typecasts = {} |
| |
111 |
self.typecasts["integer"] = lambda{|v| v.nil? ? nil : v.to_i} |
| |
112 |
self.typecasts["boolean"] = lambda{|v| v.nil? ? nil : (v.strip != "false")} |
| |
113 |
self.typecasts["datetime"] = lambda{|v| v.nil? ? nil : Time.parse(v).utc} |
| |
114 |
self.typecasts["date"] = lambda{|v| v.nil? ? nil : Date.parse(v)} |
| |
115 |
self.typecasts["dateTime"] = lambda{|v| v.nil? ? nil : Time.parse(v).utc} |
| |
116 |
self.typecasts["decimal"] = lambda{|v| BigDecimal(v)} |
| |
117 |
self.typecasts["double"] = lambda{|v| v.nil? ? nil : v.to_f} |
| |
118 |
self.typecasts["float"] = lambda{|v| v.nil? ? nil : v.to_f} |
| |
119 |
self.typecasts["symbol"] = lambda{|v| v.to_sym} |
| |
120 |
self.typecasts["string"] = lambda{|v| v.to_s} |
| |
121 |
self.typecasts["yaml"] = lambda{|v| v.nil? ? nil : YAML.load(v)} |
| |
122 |
self.typecasts["base64Binary"] = lambda{|v| v.unpack('m').first } |
| |
123 |
|
| |
124 |
self.available_typecasts = self.typecasts.keys |
| |
125 |
|
| |
126 |
def initialize(name, attributes = {}) |
| |
127 |
@name = name.tr("-", "_") |
| |
128 |
# leave the type alone if we don't know what it is |
| |
129 |
@type = self.class.available_typecasts.include?(attributes["type"]) ? attributes.delete("type") : attributes["type"] |
| |
130 |
|
| |
131 |
@nil_element = attributes.delete("nil") == "true" |
| |
132 |
@attributes = undasherize_keys(attributes) |
| |
133 |
@children = [] |
| |
134 |
@text = false |
| |
135 |
end |
| |
136 |
|
| |
137 |
def add_node(node) |
| |
138 |
@text = true if node.is_a? String |
| |
139 |
@children << node |
| |
140 |
end |
| |
141 |
|
| |
142 |
def to_hash |
| |
143 |
if @type == "file" |
| |
144 |
f = StringIO.new((@children.first || '').unpack('m').first) |
| |
145 |
class << f |
| |
146 |
attr_accessor :original_filename, :content_type |
| |
147 |
end |
| |
148 |
f.original_filename = attributes['name'] || 'untitled' |
| |
149 |
f.content_type = attributes['content_type'] || 'application/octet-stream' |
| |
150 |
return {name => f} |
| |
151 |
end |
| |
152 |
|
| |
153 |
if @text |
| |
154 |
return { name => typecast_value( translate_xml_entities( inner_html ) ) } |
| |
155 |
else |
| |
156 |
#change repeating groups into an array |
| |
157 |
groups = @children.inject({}) { |s,e| (s[e.name] ||= []) << e; s } |
| |
158 |
|
| |
159 |
out = nil |
| |
160 |
if @type == "array" |
| |
161 |
out = [] |
| |
162 |
groups.each do |k, v| |
| |
163 |
if v.size == 1 |
| |
164 |
out << v.first.to_hash.entries.first.last |
| |
165 |
else |
| |
166 |
out << v.map{|e| e.to_hash[k]} |
| |
167 |
end |
| |
168 |
end |
| |
169 |
out = out.flatten |
| |
170 |
|
| |
171 |
else # If Hash |
| |
172 |
out = {} |
| |
173 |
groups.each do |k,v| |
| |
174 |
if v.size == 1 |
| |
175 |
out.merge!(v.first) |
| |
176 |
else |
| |
177 |
out.merge!( k => v.map{|e| e.to_hash[k]}) |
| |
178 |
end |
| |
179 |
end |
| |
180 |
out.merge! attributes unless attributes.empty? |
| |
181 |
out = out.empty? ? nil : out |
| |
182 |
end |
| |
183 |
|
| |
184 |
if @type && out.nil? |
| |
185 |
{ name => typecast_value(out) } |
| |
186 |
else |
| |
187 |
{ name => out } |
| |
188 |
end |
| |
189 |
end |
| |
190 |
end |
| |
191 |
|
| |
192 |
# Typecasts a value based upon its type. For instance, if |
| |
193 |
# +node+ has #type == "integer", |
| |
194 |
# {{[node.typecast_value("12") #=> 12]}} |
| |
195 |
# |
| |
196 |
# @param value<String> The value that is being typecast. |
| |
197 |
# |
| |
198 |
# @details [:type options] |
| |
199 |
# "integer":: |
| |
200 |
# converts +value+ to an integer with #to_i |
| |
201 |
# "boolean":: |
| |
202 |
# checks whether +value+, after removing spaces, is the literal |
| |
203 |
# "true" |
| |
204 |
# "datetime":: |
| |
205 |
# Parses +value+ using Time.parse, and returns a UTC Time |
| |
206 |
# "date":: |
| |
207 |
# Parses +value+ using Date.parse |
| |
208 |
# |
| |
209 |
# @return <Integer, TrueClass, FalseClass, Time, Date, Object> |
| |
210 |
# The result of typecasting +value+. |
| |
211 |
# |
| |
212 |
# @note |
| |
213 |
# If +self+ does not have a "type" key, or if it's not one of the |
| |
214 |
# options specified above, the raw +value+ will be returned. |
| |
215 |
def typecast_value(value) |
| |
216 |
return value unless @type |
| |
217 |
proc = self.class.typecasts[@type] |
| |
218 |
proc.nil? ? value : proc.call(value) |
| |
219 |
end |
| |
220 |
|
| |
221 |
# Convert basic XML entities into their literal values. |
| |
222 |
# |
| |
223 |
# @param value<#gsub> An XML fragment. |
| |
224 |
# |
| |
225 |
# @return <#gsub> The XML fragment after converting entities. |
| |
226 |
def translate_xml_entities(value) |
| |
227 |
value.gsub(/</, "<"). |
| |
228 |
gsub(/>/, ">"). |
| |
229 |
gsub(/"/, '"'). |
| |
230 |
gsub(/'/, "'"). |
| |
231 |
gsub(/&/, "&") |
| |
232 |
end |
| |
233 |
|
| |
234 |
# Take keys of the form foo-bar and convert them to foo_bar |
| |
235 |
def undasherize_keys(params) |
| |
236 |
params.keys.each do |key, value| |
| |
237 |
params[key.tr("-", "_")] = params.delete(key) |
| |
238 |
end |
| |
239 |
params |
| |
240 |
end |
| |
241 |
|
| |
242 |
# Get the inner_html of the REXML node. |
| |
243 |
def inner_html |
| |
244 |
@children.join |
| |
245 |
end |
| |
246 |
|
| |
247 |
# Converts the node into a readable HTML node. |
| |
248 |
# |
| |
249 |
# @return <String> The HTML node in text form. |
| |
250 |
def to_html |
| |
251 |
attributes.merge!(:type => @type ) if @type |
| |
252 |
"<#{name}#{attributes.to_xml_attributes}>#{@nil_element ? '' : inner_html}</#{name}>" |
| |
253 |
end |
| |
254 |
|
| |
255 |
# @alias #to_html #to_s |
| |
256 |
def to_s |
| |
257 |
to_html |
| |
258 |
end |
| |
259 |
end |
| |
260 |
|
| |
261 |
class ToHashParser |
| |
262 |
def self.from_xml(xml) |
| |
263 |
stack = [] |
| |
264 |
parser = REXML::Parsers::BaseParser.new(xml) |
| |
265 |
|
| |
266 |
while true |
| |
267 |
event = parser.pull |
| |
268 |
case event[0] |
| |
269 |
when :end_document |
| |
270 |
break |
| |
271 |
when :end_doctype, :start_doctype |
| |
272 |
# do nothing |
| |
273 |
when :start_element |
| |
274 |
stack.push REXMLUtilityNode.new(event[1], event[2]) |
| |
275 |
when :end_element |
| |
276 |
if stack.size > 1 |
| |
277 |
temp = stack.pop |
| |
278 |
stack.last.add_node(temp) |
| |
279 |
end |
| |
280 |
when :text, :cdata |
| |
281 |
stack.last.add_node(event[1]) unless event[1].strip.length == 0 |
| |
282 |
end |
| |
283 |
end |
| |
284 |
stack.pop.to_hash |
| |
285 |
end |
| |
286 |
end |
| |
287 |
|
| |
288 |
class Hash |
| |
289 |
def self.from_xml(xml) |
| |
290 |
ToHashParser.from_xml(xml) |
| |
291 |
end |
|
41134082
»
|
jnunemaker |
2008-12-06 |
Fixed weird uri normalizing... |
292 |
|
| |
293 |
# @return <String> This hash as a query string |
| |
294 |
# |
| |
295 |
# @example |
| |
296 |
# { :name => "Bob", |
| |
297 |
# :address => { |
| |
298 |
# :street => '111 Ruby Ave.', |
| |
299 |
# :city => 'Ruby Central', |
| |
300 |
# :phones => ['111-111-1111', '222-222-2222'] |
| |
301 |
# } |
| |
302 |
# }.to_params |
| |
303 |
# #=> "name=Bob&address[city]=Ruby Central&address[phones][]=111-111-1111&address[phones][]=222-222-2222&address[street]=111 Ruby Ave." |
| |
304 |
def to_params |
| |
305 |
params = self.map { |k,v| normalize_param(k,v) }.join |
| |
306 |
params.chop! # trailing & |
| |
307 |
params |
| |
308 |
end |
| |
309 |
|
| |
310 |
# @param key<Object> The key for the param. |
| |
311 |
# @param value<Object> The value for the param. |
| |
312 |
# |
| |
313 |
# @return <String> This key value pair as a param |
| |
314 |
# |
| |
315 |
# @example normalize_param(:name, "Bob") #=> "name=Bob&" |
| |
316 |
def normalize_param(key, value) |
| |
317 |
param = '' |
| |
318 |
stack = [] |
| |
319 |
|
| |
320 |
if value.is_a?(Array) |
| |
321 |
param << value.map { |element| normalize_param("#{key}[]", element) }.join |
| |
322 |
elsif value.is_a?(Hash) |
| |
323 |
stack << [key,value] |
| |
324 |
else |
|
0b557978
»
|
jnunemaker |
2008-12-06 |
Added uri encode to Hash#to... |
325 |
param << "#{key}=#{URI.encode(value)}&" |
|
41134082
»
|
jnunemaker |
2008-12-06 |
Fixed weird uri normalizing... |
326 |
end |
| |
327 |
|
| |
328 |
stack.each do |parent, hash| |
| |
329 |
hash.each do |key, value| |
| |
330 |
if value.is_a?(Hash) |
| |
331 |
stack << ["#{parent}[#{key}]", value] |
| |
332 |
else |
| |
333 |
param << normalize_param("#{parent}[#{key}]", value) |
| |
334 |
end |
| |
335 |
end |
| |
336 |
end |
| |
337 |
|
| |
338 |
param |
| |
339 |
end |
|
7fab301e
»
|
jnunemaker |
2008-12-06 |
Moved xml stuff into core e... |
340 |
end |