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.
- checksums.yaml +7 -0
- data/.gitignore +8 -0
- data/.rspec +1 -0
- data/.travis.yml +13 -0
- data/CHANGELOG.md +196 -0
- data/Gemfile +2 -0
- data/LICENSE +20 -0
- data/README.md +69 -0
- data/Rakefile +12 -0
- data/benchmark/benchmark.rb +19 -0
- data/benchmark/soap_response.xml +266 -0
- data/lib/nori/core_ext/hash.rb +75 -0
- data/lib/nori/core_ext/object.rb +13 -0
- data/lib/nori/core_ext/string.rb +21 -0
- data/lib/nori/core_ext.rb +3 -0
- data/lib/nori/parser/nokogiri.rb +47 -0
- data/lib/nori/parser/rexml.rb +51 -0
- data/lib/nori/string_io_file.rb +7 -0
- data/lib/nori/string_with_attributes.rb +7 -0
- data/lib/nori/version.rb +5 -0
- data/lib/nori/xml_utility_node.rb +253 -0
- data/lib/nori.rb +72 -0
- data/nori.gemspec +25 -0
- data/spec/nori/api_spec.rb +169 -0
- data/spec/nori/core_ext/hash_spec.rb +60 -0
- data/spec/nori/core_ext/object_spec.rb +19 -0
- data/spec/nori/core_ext/string_spec.rb +33 -0
- data/spec/nori/nori_spec.rb +630 -0
- data/spec/spec_helper.rb +2 -0
- metadata +113 -0
@@ -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,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,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,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
|