nori-ng-1.6 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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