nori-ng-1.6 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,75 @@
1
+ require "uri"
2
+
3
+ class Nori
4
+ module CoreExt
5
+ module Hash
6
+
7
+ # @return <String> This hash as a query string
8
+ #
9
+ # @example
10
+ # { :name => "Bob",
11
+ # :address => {
12
+ # :street => '111 Ruby Ave.',
13
+ # :city => 'Ruby Central',
14
+ # :phones => ['111-111-1111', '222-222-2222']
15
+ # }
16
+ # }.to_params
17
+ # #=> "name=Bob&address[city]=Ruby Central&address[phones][]=111-111-1111&address[phones][]=222-222-2222&address[street]=111 Ruby Ave."
18
+ def to_params
19
+ map { |k, v| normalize_param(k,v) }.flatten.join('&')
20
+ end
21
+
22
+ # @param key<Object> The key for the param.
23
+ # @param value<Object> The value for the param.
24
+ #
25
+ # @return <String> This key value pair as a param
26
+ #
27
+ # @example normalize_param(:name, "Bob Jones") #=> "name=Bob%20Jones"
28
+ def normalize_param(key, value)
29
+ if value.is_a?(Array)
30
+ normalize_array_params(key, value)
31
+ elsif value.is_a?(Hash)
32
+ normalize_hash_params(key, value)
33
+ else
34
+ normalize_simple_type_params(key, value)
35
+ end
36
+ end
37
+
38
+ # @return <String> The hash as attributes for an XML tag.
39
+ #
40
+ # @example
41
+ # { :one => 1, "two"=>"TWO" }.to_xml_attributes
42
+ # #=> 'one="1" two="TWO"'
43
+ def to_xml_attributes
44
+ map do |k, v|
45
+ %{#{k.to_s.snakecase.sub(/^(.{1,1})/) { |m| m.downcase }}="#{v}"}
46
+ end.join(' ')
47
+ end
48
+
49
+ private
50
+
51
+ def normalize_simple_type_params(key, value)
52
+ ["#{key}=#{encode_simple_value(value)}"]
53
+ end
54
+
55
+ def normalize_array_params(key, array)
56
+ array.map do |element|
57
+ normalize_param("#{key}[]", element)
58
+ end
59
+ end
60
+
61
+ def normalize_hash_params(key, hash)
62
+ hash.map do |nested_key, element|
63
+ normalize_param("#{key}[#{nested_key}]", element)
64
+ end
65
+ end
66
+
67
+ def encode_simple_value(value)
68
+ URI.encode(value.to_s, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
69
+ end
70
+
71
+ end
72
+ end
73
+ end
74
+
75
+ Hash.send :include, Nori::CoreExt::Hash
@@ -0,0 +1,13 @@
1
+ class Nori
2
+ module CoreExt
3
+ module Object
4
+
5
+ def blank?
6
+ respond_to?(:empty?) ? empty? : !self
7
+ end unless method_defined?(:blank?)
8
+
9
+ end
10
+ end
11
+ end
12
+
13
+ Object.send :include, Nori::CoreExt::Object
@@ -0,0 +1,21 @@
1
+ class Nori
2
+ module CoreExt
3
+ module String
4
+
5
+ # Returns the String in snake_case.
6
+ def snakecase
7
+ str = dup
8
+ str.gsub! /::/, '/'
9
+ str.gsub! /([A-Z]+)([A-Z][a-z])/, '\1_\2'
10
+ str.gsub! /([a-z\d])([A-Z])/, '\1_\2'
11
+ str.tr! ".", "_"
12
+ str.tr! "-", "_"
13
+ str.downcase!
14
+ str
15
+ end unless method_defined?(:snakecase)
16
+
17
+ end
18
+ end
19
+ end
20
+
21
+ String.send :include, Nori::CoreExt::String
@@ -0,0 +1,3 @@
1
+ require "nori/core_ext/object"
2
+ require "nori/core_ext/string"
3
+ require "nori/core_ext/hash"
@@ -0,0 +1,47 @@
1
+ require "nokogiri"
2
+
3
+ class Nori
4
+ module Parser
5
+
6
+ # = Nori::Parser::Nokogiri
7
+ #
8
+ # Nokogiri SAX parser.
9
+ module Nokogiri
10
+
11
+ class Document < ::Nokogiri::XML::SAX::Document
12
+ attr_accessor :options
13
+
14
+ def stack
15
+ @stack ||= []
16
+ end
17
+
18
+ def start_element(name, attrs = [])
19
+ stack.push Nori::XMLUtilityNode.new(options, name, Hash[*attrs.flatten])
20
+ end
21
+
22
+ def end_element(name)
23
+ if stack.size > 1
24
+ last = stack.pop
25
+ stack.last.add_node last
26
+ end
27
+ end
28
+
29
+ def characters(string)
30
+ stack.last.add_node(string) unless string.strip.length == 0 || stack.empty?
31
+ end
32
+
33
+ alias cdata_block characters
34
+
35
+ end
36
+
37
+ def self.parse(xml, options)
38
+ document = Document.new
39
+ document.options = options
40
+ parser = ::Nokogiri::XML::SAX::Parser.new document
41
+ parser.parse xml
42
+ document.stack.length > 0 ? document.stack.pop.to_hash : {}
43
+ end
44
+
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,51 @@
1
+ require "rexml/parsers/baseparser"
2
+ require "rexml/text"
3
+ require "rexml/document"
4
+
5
+ class Nori
6
+ module Parser
7
+
8
+ # = Nori::Parser::REXML
9
+ #
10
+ # REXML pull parser.
11
+ module REXML
12
+
13
+ def self.parse(xml, options)
14
+ stack = []
15
+ parser = ::REXML::Parsers::BaseParser.new(xml)
16
+
17
+ while true
18
+ event = unnormalize(parser.pull)
19
+ case event[0]
20
+ when :end_document
21
+ break
22
+ when :end_doctype, :start_doctype
23
+ # do nothing
24
+ when :start_element
25
+ stack.push Nori::XMLUtilityNode.new(options, event[1], event[2])
26
+ when :end_element
27
+ if stack.size > 1
28
+ temp = stack.pop
29
+ stack.last.add_node(temp)
30
+ end
31
+ when :text, :cdata
32
+ stack.last.add_node(event[1]) unless event[1].strip.length == 0 || stack.empty?
33
+ end
34
+ end
35
+ stack.length > 0 ? stack.pop.to_hash : {}
36
+ end
37
+
38
+ def self.unnormalize(event)
39
+ event.map! do |el|
40
+ if el.is_a?(String)
41
+ ::REXML::Text.unnormalize(el)
42
+ elsif el.is_a?(Hash)
43
+ el.each {|k,v| el[k] = ::REXML::Text.unnormalize(v)}
44
+ else
45
+ el
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,7 @@
1
+ class Nori
2
+ class StringIOFile < StringIO
3
+
4
+ attr_accessor :original_filename, :content_type
5
+
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ class Nori
2
+ class StringWithAttributes < String
3
+
4
+ attr_accessor :attributes
5
+
6
+ end
7
+ end
@@ -0,0 +1,5 @@
1
+ class Nori
2
+
3
+ VERSION = "2.3.0"
4
+
5
+ end
@@ -0,0 +1,253 @@
1
+ require "date"
2
+ require "time"
3
+ require "yaml"
4
+ require "bigdecimal"
5
+
6
+ require "nori/string_with_attributes"
7
+ require "nori/string_io_file"
8
+
9
+ class Nori
10
+
11
+ # This is a slighly modified version of the XMLUtilityNode from
12
+ # http://merb.devjavu.com/projects/merb/ticket/95 (has.sox@gmail.com)
13
+ #
14
+ # John Nunemaker:
15
+ # It's mainly just adding vowels, as I ht cd wth n vwls :)
16
+ # This represents the hard part of the work, all I did was change the
17
+ # underlying parser.
18
+ class XMLUtilityNode
19
+
20
+ # Simple xs:time Regexp.
21
+ # Valid xs:time formats
22
+ # 13:20:00 1:20 PM
23
+ # 13:20:30.5555 1:20 PM and 30.5555 seconds
24
+ # 13:20:00-05:00 1:20 PM, US Eastern Standard Time
25
+ # 13:20:00+02:00 1:20 PM, Central European Standard Time
26
+ # 13:20:00Z 1:20 PM, Coordinated Universal Time (UTC)
27
+ # 00:00:00 midnight
28
+ # 24:00:00 midnight
29
+
30
+ XS_TIME = /^\d{2}:\d{2}:\d{2}[Z\.\-\+]?\d*:?\d*$/
31
+
32
+ # Simple xs:date Regexp.
33
+ # Valid xs:date formats
34
+ # 2004-04-12 April 12, 2004
35
+ # -0045-01-01 January 1, 45 BC
36
+ # 12004-04-12 April 12, 12004
37
+ # 2004-04-12-05:00 April 12, 2004, US Eastern Standard Time, which is 5 hours behind Coordinated Universal Time (UTC)
38
+ # 2004-04-12+02:00 April 12, 2004, Central European Summer Time, which is 2 hours ahead of Coordinated Universal Time (UTC)
39
+ # 2004-04-12Z April 12, 2004, Coordinated Universal Time (UTC)
40
+
41
+ XS_DATE = /^[-]?\d{4}-\d{2}-\d{2}[Z\-\+]?\d*:?\d*$/
42
+
43
+ # Simple xs:dateTime Regexp.
44
+ # Valid xs:dateTime formats
45
+ # 2004-04-12T13:20:00 1:20 pm on April 12, 2004
46
+ # 2004-04-12T13:20:15.5 1:20 pm and 15.5 seconds on April 12, 2004
47
+ # 2004-04-12T13:20:00-05:00 1:20 pm on April 12, 2004, US Eastern Standard Time
48
+ # 2004-04-12T13:20:00+02:00 1:20 pm on April 12, 2004, Central European Summer Time
49
+ # 2004-04-12T13:20:15.5-05:00 1:20 pm and 15.5 seconds on April 12, 2004, US Eastern Standard Time
50
+ # 2004-04-12T13:20:00Z 1:20 pm on April 12, 2004, Coordinated Universal Time (UTC)
51
+
52
+ XS_DATE_TIME = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}[\.Z]?\d*[\-\+]?\d*:?\d*$/
53
+
54
+ def self.typecasts
55
+ @@typecasts
56
+ end
57
+
58
+ def self.typecasts=(obj)
59
+ @@typecasts = obj
60
+ end
61
+
62
+ def self.available_typecasts
63
+ @@available_typecasts
64
+ end
65
+
66
+ def self.available_typecasts=(obj)
67
+ @@available_typecasts = obj
68
+ end
69
+
70
+ self.typecasts = {}
71
+ self.typecasts["integer"] = lambda { |v| v.nil? ? nil : v.to_i }
72
+ self.typecasts["boolean"] = lambda { |v| v.nil? ? nil : (v.strip != "false") }
73
+ self.typecasts["datetime"] = lambda { |v| v.nil? ? nil : Time.parse(v).utc }
74
+ self.typecasts["date"] = lambda { |v| v.nil? ? nil : Date.parse(v) }
75
+ self.typecasts["dateTime"] = lambda { |v| v.nil? ? nil : Time.parse(v).utc }
76
+ self.typecasts["decimal"] = lambda { |v| v.nil? ? nil : BigDecimal(v.to_s) }
77
+ self.typecasts["double"] = lambda { |v| v.nil? ? nil : v.to_f }
78
+ self.typecasts["float"] = lambda { |v| v.nil? ? nil : v.to_f }
79
+ self.typecasts["string"] = lambda { |v| v.to_s }
80
+ self.typecasts["base64Binary"] = lambda { |v| v.unpack('m').first }
81
+
82
+ self.available_typecasts = self.typecasts.keys
83
+
84
+ def initialize(options, name, attributes = {})
85
+ @options = options
86
+ @name = Nori.hash_key(name, options)
87
+
88
+ # leave the type alone if we don't know what it is
89
+ @type = self.class.available_typecasts.include?(attributes["type"]) ? attributes.delete("type") : attributes["type"]
90
+
91
+ @nil_element = false
92
+ attributes.keys.each do |key|
93
+ if result = /^((.*):)?nil$/.match(key)
94
+ @nil_element = attributes.delete(key) == "true"
95
+ attributes.delete("xmlns:#{result[2]}") if result[1]
96
+ end
97
+ attributes.delete(key) if @options[:delete_namespace_attributes] && key[/^(xmlns|xsi)/]
98
+ end
99
+ @attributes = undasherize_keys(attributes)
100
+ @children = []
101
+ @text = false
102
+ end
103
+
104
+ attr_accessor :name, :attributes, :children, :type
105
+
106
+ def prefixed_attributes
107
+ attributes.inject({}) do |memo, (key, value)|
108
+ memo[prefixed_attribute_name("@#{key}")] = value
109
+ memo
110
+ end
111
+ end
112
+
113
+ def prefixed_attribute_name(attribute)
114
+ return attribute unless @options[:convert_tags_to].respond_to? :call
115
+ @options[:convert_tags_to].call(attribute)
116
+ end
117
+
118
+ def add_node(node)
119
+ @text = true if node.is_a? String
120
+ @children << node
121
+ end
122
+
123
+ def to_hash
124
+ if @type == "file"
125
+ f = StringIOFile.new((@children.first || '').unpack('m').first)
126
+ f.original_filename = attributes['name'] || 'untitled'
127
+ f.content_type = attributes['content_type'] || 'application/octet-stream'
128
+ return { name => f }
129
+ end
130
+
131
+ if @text
132
+ t = typecast_value(inner_html)
133
+ t = advanced_typecasting(t) if t.is_a?(String) && @options[:advanced_typecasting]
134
+
135
+ if t.is_a?(String)
136
+ t = StringWithAttributes.new(t)
137
+ t.attributes = attributes
138
+ end
139
+
140
+ return { name => t }
141
+ else
142
+ #change repeating groups into an array
143
+ groups = @children.inject({}) { |s,e| (s[e.name] ||= []) << e; s }
144
+
145
+ out = nil
146
+ if @type == "array"
147
+ out = []
148
+ groups.each do |k, v|
149
+ if v.size == 1
150
+ out << v.first.to_hash.entries.first.last
151
+ else
152
+ out << v.map{|e| e.to_hash[k]}
153
+ end
154
+ end
155
+ out = out.flatten
156
+
157
+ else # If Hash
158
+ out = {}
159
+ groups.each do |k,v|
160
+ if v.size == 1
161
+ out.merge!(v.first)
162
+ else
163
+ out.merge!( k => v.map{|e| e.to_hash[k]})
164
+ end
165
+ end
166
+ out.merge! prefixed_attributes unless attributes.empty?
167
+ out = out.empty? ? nil : out
168
+ end
169
+
170
+ if @type && out.nil?
171
+ { name => typecast_value(out) }
172
+ else
173
+ { name => out }
174
+ end
175
+ end
176
+ end
177
+
178
+ # Typecasts a value based upon its type. For instance, if
179
+ # +node+ has #type == "integer",
180
+ # {{[node.typecast_value("12") #=> 12]}}
181
+ #
182
+ # @param value<String> The value that is being typecast.
183
+ #
184
+ # @details [:type options]
185
+ # "integer"::
186
+ # converts +value+ to an integer with #to_i
187
+ # "boolean"::
188
+ # checks whether +value+, after removing spaces, is the literal
189
+ # "true"
190
+ # "datetime"::
191
+ # Parses +value+ using Time.parse, and returns a UTC Time
192
+ # "date"::
193
+ # Parses +value+ using Date.parse
194
+ #
195
+ # @return <Integer, TrueClass, FalseClass, Time, Date, Object>
196
+ # The result of typecasting +value+.
197
+ #
198
+ # @note
199
+ # If +self+ does not have a "type" key, or if it's not one of the
200
+ # options specified above, the raw +value+ will be returned.
201
+ def typecast_value(value)
202
+ return value unless @type
203
+ proc = self.class.typecasts[@type]
204
+ proc.nil? ? value : proc.call(value)
205
+ end
206
+
207
+ def advanced_typecasting(value)
208
+ split = value.split
209
+ return value if split.size > 1
210
+
211
+ case split.first
212
+ when "true" then true
213
+ when "false" then false
214
+ when XS_DATE_TIME then try_to_convert(value) {|x| DateTime.parse(x)}
215
+ when XS_DATE then try_to_convert(value) {|x| Date.parse(x)}
216
+ when XS_TIME then try_to_convert(value) {|x| Time.parse(x)}
217
+ else value
218
+ end
219
+ end
220
+
221
+ # Take keys of the form foo-bar and convert them to foo_bar
222
+ def undasherize_keys(params)
223
+ params.keys.each do |key, value|
224
+ params[key.tr("-", "_")] = params.delete(key)
225
+ end
226
+ params
227
+ end
228
+
229
+ # Get the inner_html of the REXML node.
230
+ def inner_html
231
+ @children.join
232
+ end
233
+
234
+ # Converts the node into a readable HTML node.
235
+ #
236
+ # @return <String> The HTML node in text form.
237
+ def to_html
238
+ attributes.merge!(:type => @type ) if @type
239
+ "<#{name}#{attributes.to_xml_attributes}>#{@nil_element ? '' : inner_html}</#{name}>"
240
+ end
241
+
242
+ alias to_s to_html
243
+
244
+ private
245
+
246
+ def try_to_convert(value, &block)
247
+ block.call(value)
248
+ rescue ArgumentError
249
+ value
250
+ end
251
+ end
252
+
253
+ end
data/lib/nori.rb ADDED
@@ -0,0 +1,72 @@
1
+ require "nori/version"
2
+ require "nori/core_ext"
3
+ require "nori/xml_utility_node"
4
+
5
+ class Nori
6
+
7
+ def self.hash_key(name, options = {})
8
+ name = name.tr("-", "_")
9
+ name = name.split(":").last if options[:strip_namespaces]
10
+ name = options[:convert_tags_to].call(name) if options[:convert_tags_to].respond_to? :call
11
+ name
12
+ end
13
+
14
+ PARSERS = { :rexml => "REXML", :nokogiri => "Nokogiri" }
15
+
16
+ def initialize(options = {})
17
+ defaults = {
18
+ :strip_namespaces => false,
19
+ :delete_namespace_attributes => false,
20
+ :convert_tags_to => nil,
21
+ :advanced_typecasting => true,
22
+ :parser => :nokogiri
23
+ }
24
+
25
+ validate_options! defaults.keys, options.keys
26
+ @options = defaults.merge(options)
27
+ end
28
+
29
+ def find(hash, *path)
30
+ return hash if path.empty?
31
+
32
+ key = path.shift
33
+ key = self.class.hash_key(key, @options)
34
+
35
+ value = find_value(hash, key)
36
+ find(value, *path) if value
37
+ end
38
+
39
+ def parse(xml)
40
+ cleaned_xml = xml.strip
41
+ return {} if cleaned_xml.empty?
42
+
43
+ parser = load_parser @options[:parser]
44
+ parser.parse(cleaned_xml, @options)
45
+ end
46
+
47
+ private
48
+
49
+ def load_parser(parser)
50
+ require "nori/parser/#{parser}"
51
+ Parser.const_get PARSERS[parser]
52
+ end
53
+
54
+ def validate_options!(available_options, options)
55
+ spurious_options = options - available_options
56
+
57
+ unless spurious_options.empty?
58
+ raise ArgumentError, "Spurious options: #{spurious_options.inspect}\n" \
59
+ "Available options are: #{available_options.inspect}"
60
+ end
61
+ end
62
+
63
+ def find_value(hash, key)
64
+ hash.each do |k, v|
65
+ key_without_namespace = k.to_s.split(':').last
66
+ return v if key_without_namespace == key.to_s
67
+ end
68
+
69
+ nil
70
+ end
71
+
72
+ end
data/nori.gemspec ADDED
@@ -0,0 +1,25 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "nori/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "nori-ng-1.6"
7
+ s.version = Nori::VERSION
8
+ s.authors = ["Rafael Reggiani Manzo"]
9
+ s.email = "rr.manzo@gmail.com"
10
+ s.homepage = "https://github.com/rafamanzo/nori.git"
11
+ s.summary = "This a fork from Daniel Harrington's Nori with nokogiri updated to 1.6"
12
+ s.description = s.summary
13
+
14
+ s.rubyforge_project = "nori"
15
+ s.license = "MIT"
16
+
17
+ s.add_development_dependency "rake", "~> 10.0"
18
+ s.add_development_dependency "nokogiri", ">= 1.4.0"
19
+ s.add_development_dependency "rspec", "~> 2.12"
20
+
21
+ s.files = `git ls-files`.split("\n")
22
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
23
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
24
+ s.require_paths = ["lib"]
25
+ end