nori 0.2.4 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of nori might be problematic. Click here for more details.
- data/CHANGELOG.md +27 -6
- data/README.md +43 -0
- data/lib/nori.rb +42 -3
- data/lib/nori/core_ext/hash.rb +1 -1
- data/lib/nori/core_ext/string.rb +11 -5
- data/lib/nori/parser/nokogiri.rb +1 -0
- data/lib/nori/parser/rexml.rb +1 -0
- data/lib/nori/version.rb +1 -1
- data/lib/nori/xml_utility_node.rb +35 -6
- data/spec/nori/core_ext/string_spec.rb +10 -10
- data/spec/nori/nori_spec.rb +112 -2
- metadata +6 -8
data/CHANGELOG.md
CHANGED
@@ -1,12 +1,33 @@
|
|
1
|
-
== 0.
|
1
|
+
== 1.0.0 (2011-06-20)
|
2
2
|
|
3
|
-
*
|
4
|
-
to prevent NameError's.
|
3
|
+
* Notice: As of v1.0.0, Nori will follow [Semantic Versioning](http://semver.org).
|
5
4
|
|
6
|
-
|
5
|
+
* Feature: Added somewhat advanced typecasting:
|
7
6
|
|
8
|
-
|
9
|
-
|
7
|
+
What this means:
|
8
|
+
|
9
|
+
* "true" and "false" are converted to TrueClass and FalseClass
|
10
|
+
* Strings matching an xs:time, xs:date and xs:dateTime are converted
|
11
|
+
to Time, Date and DateTime objects.
|
12
|
+
|
13
|
+
You can disable this feature via:
|
14
|
+
|
15
|
+
Nori.advanced_typecasting = false
|
16
|
+
|
17
|
+
* Feature: Added an option to strip the namespaces from every tag.
|
18
|
+
This feature might raise problems and is therefore disabled by default.
|
19
|
+
|
20
|
+
Nori.strip_namespaces = true
|
21
|
+
|
22
|
+
* Feature: Added an option to specify a custom formula to convert tags.
|
23
|
+
Here's an example:
|
24
|
+
|
25
|
+
Nori.configure do |config|
|
26
|
+
config.convert_tags_to { |tag| tag.snake_case.to_sym }
|
27
|
+
end
|
28
|
+
|
29
|
+
xml = '<userResponse><accountStatus>active</accountStatus></userResponse>'
|
30
|
+
parse(xml).should == { :user_response => { :account_status => "active" }
|
10
31
|
|
11
32
|
== 0.2.2 (2011-05-16)
|
12
33
|
|
data/README.md
CHANGED
@@ -19,3 +19,46 @@ Nori.parser = :nokogiri
|
|
19
19
|
|
20
20
|
Make sure Nokogiri is in your LOAD_PATH when parsing XML, because Nori tries to load it
|
21
21
|
when it's needed.
|
22
|
+
|
23
|
+
|
24
|
+
Typecasting
|
25
|
+
-----------
|
26
|
+
|
27
|
+
Besides regular typecasting, Nori features somewhat "advanced" typecasting:
|
28
|
+
|
29
|
+
* "true" and "false" String values are converted to `TrueClass` and `FalseClass`.
|
30
|
+
* String values matching xs:time, xs:date and xs:dateTime are converted
|
31
|
+
to `Time`, `Date` and `DateTime` objects.
|
32
|
+
|
33
|
+
You can disable this feature via:
|
34
|
+
|
35
|
+
``` ruby
|
36
|
+
Nori.advanced_typecasting = false
|
37
|
+
```
|
38
|
+
|
39
|
+
|
40
|
+
Namespaces
|
41
|
+
----------
|
42
|
+
|
43
|
+
Nori can strip the namespaces from your XML tags. This feature might raise
|
44
|
+
problems and is therefore disabled by default. Enable it via:
|
45
|
+
|
46
|
+
``` ruby
|
47
|
+
Nori.strip_namespaces = true
|
48
|
+
```
|
49
|
+
|
50
|
+
|
51
|
+
XML tags -> Hash keys
|
52
|
+
---------------------
|
53
|
+
|
54
|
+
Nori lets you specify a custom formula to convert XML tags to Hash keys.
|
55
|
+
Let me give you an example:
|
56
|
+
|
57
|
+
``` ruby
|
58
|
+
Nori.configure do |config|
|
59
|
+
config.convert_tags_to { |tag| tag.snake_case.to_sym }
|
60
|
+
end
|
61
|
+
|
62
|
+
xml = '<userResponse><accountStatus>active</accountStatus></userResponse>'
|
63
|
+
Nori.parse(xml) # => { :user_response => { :account_status => "active" }
|
64
|
+
```
|
data/lib/nori.rb
CHANGED
@@ -1,19 +1,58 @@
|
|
1
1
|
require "nori/version"
|
2
2
|
require "nori/core_ext"
|
3
3
|
require "nori/parser"
|
4
|
-
require "nori/xml_utility_node"
|
5
4
|
|
6
5
|
module Nori
|
6
|
+
extend self
|
7
7
|
|
8
8
|
# Translates the given +xml+ to a Hash. Accepts an optional +parser+ to use.
|
9
|
-
def
|
9
|
+
def parse(xml, parser = nil)
|
10
10
|
return {} if xml.blank?
|
11
11
|
Parser.parse xml, parser
|
12
12
|
end
|
13
13
|
|
14
14
|
# Sets the +parser+ to use.
|
15
|
-
def
|
15
|
+
def parser=(parser)
|
16
16
|
Parser.use = parser
|
17
17
|
end
|
18
18
|
|
19
|
+
# Yields +self+ for configuration.
|
20
|
+
def configure
|
21
|
+
yield self
|
22
|
+
end
|
23
|
+
|
24
|
+
# Sets whether to use advanced typecasting.
|
25
|
+
attr_writer :advanced_typecasting
|
26
|
+
|
27
|
+
# Returns whether to use advanced typecasting.
|
28
|
+
# Defaults to +true+.
|
29
|
+
def advanced_typecasting?
|
30
|
+
@advanced_typecasting != false
|
31
|
+
end
|
32
|
+
|
33
|
+
# Sets whether to strip namespaces.
|
34
|
+
attr_writer :strip_namespaces
|
35
|
+
|
36
|
+
# Returns whether to strip namespaces.
|
37
|
+
# Defaults to +false+.
|
38
|
+
def strip_namespaces?
|
39
|
+
@strip_namespaces
|
40
|
+
end
|
41
|
+
|
42
|
+
# Expects a +block+ which receives a tag to convert.
|
43
|
+
# Accepts +nil+ for a reset to the default behavior of not converting tags.
|
44
|
+
def convert_tags_to(reset = nil, &block)
|
45
|
+
@convert_tag = reset || block
|
46
|
+
end
|
47
|
+
|
48
|
+
# Transforms a given +tag+ using the specified conversion formula.
|
49
|
+
def convert_tag(tag)
|
50
|
+
@convert_tag.call(tag)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Returns whether to convert tags.
|
54
|
+
def convert_tags?
|
55
|
+
@convert_tag
|
56
|
+
end
|
57
|
+
|
19
58
|
end
|
data/lib/nori/core_ext/hash.rb
CHANGED
data/lib/nori/core_ext/string.rb
CHANGED
@@ -2,11 +2,17 @@ module Nori
|
|
2
2
|
module CoreExt
|
3
3
|
module String
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
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)
|
10
16
|
|
11
17
|
end
|
12
18
|
end
|
data/lib/nori/parser/nokogiri.rb
CHANGED
data/lib/nori/parser/rexml.rb
CHANGED
data/lib/nori/version.rb
CHANGED
@@ -18,6 +18,15 @@ module Nori
|
|
18
18
|
# underlying parser.
|
19
19
|
class XMLUtilityNode
|
20
20
|
|
21
|
+
# Simple xs:time Regexp.
|
22
|
+
XS_TIME = /\d{2}:\d{2}:\d{2}/
|
23
|
+
|
24
|
+
# Simple xs:date Regexp.
|
25
|
+
XS_DATE = /\d{4}-\d{2}-\d{2}/
|
26
|
+
|
27
|
+
# Simple xs:dateTime Regexp.
|
28
|
+
XS_DATE_TIME = /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/
|
29
|
+
|
21
30
|
def self.typecasts
|
22
31
|
@@typecasts
|
23
32
|
end
|
@@ -56,20 +65,23 @@ module Nori
|
|
56
65
|
[ key, unnormalize_xml_entities(value) ]
|
57
66
|
end.flatten]
|
58
67
|
|
59
|
-
@name
|
68
|
+
@name = name.tr("-", "_")
|
69
|
+
@name = @name.split(":").last if Nori.strip_namespaces?
|
70
|
+
@name = Nori.convert_tag(@name) if Nori.convert_tags?
|
71
|
+
|
60
72
|
# leave the type alone if we don't know what it is
|
61
|
-
@type
|
73
|
+
@type = self.class.available_typecasts.include?(attributes["type"]) ? attributes.delete("type") : attributes["type"]
|
62
74
|
|
63
|
-
@nil_element
|
75
|
+
@nil_element = false
|
64
76
|
attributes.keys.each do |key|
|
65
77
|
if result = /^((.*):)?nil$/.match(key)
|
66
78
|
@nil_element = attributes.delete(key) == "true"
|
67
79
|
attributes.delete("xmlns:#{result[2]}") if result[1]
|
68
80
|
end
|
69
81
|
end
|
70
|
-
@attributes
|
71
|
-
@children
|
72
|
-
@text
|
82
|
+
@attributes = undasherize_keys(attributes)
|
83
|
+
@children = []
|
84
|
+
@text = false
|
73
85
|
end
|
74
86
|
|
75
87
|
attr_accessor :name, :attributes, :children, :type
|
@@ -96,10 +108,13 @@ module Nori
|
|
96
108
|
|
97
109
|
if @text
|
98
110
|
t = typecast_value unnormalize_xml_entities(inner_html)
|
111
|
+
t = advanced_typecasting(t) if t.is_a?(String) && Nori.advanced_typecasting?
|
112
|
+
|
99
113
|
if t.is_a?(String)
|
100
114
|
t = StringWithAttributes.new(t)
|
101
115
|
t.attributes = attributes
|
102
116
|
end
|
117
|
+
|
103
118
|
return { name => t }
|
104
119
|
else
|
105
120
|
#change repeating groups into an array
|
@@ -167,6 +182,20 @@ module Nori
|
|
167
182
|
proc.nil? ? value : proc.call(value)
|
168
183
|
end
|
169
184
|
|
185
|
+
def advanced_typecasting(value)
|
186
|
+
split = value.split
|
187
|
+
return value if split.size > 1
|
188
|
+
|
189
|
+
case split.first
|
190
|
+
when "true" then true
|
191
|
+
when "false" then false
|
192
|
+
when XS_DATE_TIME then DateTime.parse(value)
|
193
|
+
when XS_DATE then Date.parse(value)
|
194
|
+
when XS_TIME then Time.parse(value)
|
195
|
+
else value
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
170
199
|
# Take keys of the form foo-bar and convert them to foo_bar
|
171
200
|
def undasherize_keys(params)
|
172
201
|
params.keys.each do |key, value|
|
@@ -2,31 +2,31 @@ require "spec_helper"
|
|
2
2
|
|
3
3
|
describe String do
|
4
4
|
|
5
|
-
describe "#
|
5
|
+
describe "#snakecase" do
|
6
6
|
it "lowercases one word CamelCase" do
|
7
|
-
"Merb".
|
7
|
+
"Merb".snakecase.should == "merb"
|
8
8
|
end
|
9
9
|
|
10
|
-
it "makes one underscore
|
11
|
-
"MerbCore".
|
10
|
+
it "makes one underscore snakecase two word CamelCase" do
|
11
|
+
"MerbCore".snakecase.should == "merb_core"
|
12
12
|
end
|
13
13
|
|
14
14
|
it "handles CamelCase with more than 2 words" do
|
15
|
-
"SoYouWantContributeToMerbCore".
|
15
|
+
"SoYouWantContributeToMerbCore".snakecase.should == "so_you_want_contribute_to_merb_core"
|
16
16
|
end
|
17
17
|
|
18
18
|
it "handles CamelCase with more than 2 capital letter in a row" do
|
19
|
-
"CNN".
|
20
|
-
"CNNNews".
|
21
|
-
"HeadlineCNNNews".
|
19
|
+
"CNN".snakecase.should == "cnn"
|
20
|
+
"CNNNews".snakecase.should == "cnn_news"
|
21
|
+
"HeadlineCNNNews".snakecase.should == "headline_cnn_news"
|
22
22
|
end
|
23
23
|
|
24
24
|
it "does NOT change one word lowercase" do
|
25
|
-
"merb".
|
25
|
+
"merb".snakecase.should == "merb"
|
26
26
|
end
|
27
27
|
|
28
28
|
it "leaves snake_case as is" do
|
29
|
-
"merb_core".
|
29
|
+
"merb_core".snakecase.should == "merb_core"
|
30
30
|
end
|
31
31
|
end
|
32
32
|
|
data/spec/nori/nori_spec.rb
CHANGED
@@ -78,12 +78,82 @@ describe Nori do
|
|
78
78
|
it "should prefix attributes with an @-sign to avoid problems with overwritten values" do
|
79
79
|
xml =<<-XML
|
80
80
|
<multiRef id="id1">
|
81
|
-
<
|
81
|
+
<login>grep</login>
|
82
82
|
<id>76737</id>
|
83
83
|
</multiRef>
|
84
84
|
XML
|
85
85
|
|
86
|
-
parse(xml)["multiRef"].should == { "
|
86
|
+
parse(xml)["multiRef"].should == { "login" => "grep", "@id" => "id1", "id" => "76737" }
|
87
|
+
end
|
88
|
+
|
89
|
+
context "without advanced typecasting" do
|
90
|
+
around do |example|
|
91
|
+
Nori.advanced_typecasting = false
|
92
|
+
example.run
|
93
|
+
Nori.advanced_typecasting = true
|
94
|
+
end
|
95
|
+
|
96
|
+
it "should not transform 'true'" do
|
97
|
+
parse("<value>true</value>")["value"].should == "true"
|
98
|
+
end
|
99
|
+
|
100
|
+
it "should not transform 'false'" do
|
101
|
+
parse("<value>false</value>")["value"].should == "false"
|
102
|
+
end
|
103
|
+
|
104
|
+
it "should not transform Strings matching the xs:time format" do
|
105
|
+
parse("<value>09:33:55Z</value>")["value"].should == "09:33:55Z"
|
106
|
+
end
|
107
|
+
|
108
|
+
it "should not transform Strings matching the xs:date format" do
|
109
|
+
parse("<value>1955-04-18-05:00</value>")["value"].should == "1955-04-18-05:00"
|
110
|
+
end
|
111
|
+
|
112
|
+
it "should not transform Strings matching the xs:dateTime format" do
|
113
|
+
parse("<value>1955-04-18T11:22:33-05:00</value>")["value"].should == "1955-04-18T11:22:33-05:00"
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
context "with advanced typecasting" do
|
118
|
+
around do |example|
|
119
|
+
Nori.advanced_typecasting = true
|
120
|
+
example.run
|
121
|
+
Nori.advanced_typecasting = false
|
122
|
+
end
|
123
|
+
|
124
|
+
it "should transform 'true' to TrueClass" do
|
125
|
+
parse("<value>true</value>")["value"].should == true
|
126
|
+
end
|
127
|
+
|
128
|
+
it "should transform 'false' to FalseClass" do
|
129
|
+
parse("<value>false</value>")["value"].should == false
|
130
|
+
end
|
131
|
+
|
132
|
+
it "should transform Strings matching the xs:time format to Time objects" do
|
133
|
+
parse("<value>09:33:55Z</value>")["value"].should == Time.parse("09:33:55Z")
|
134
|
+
end
|
135
|
+
|
136
|
+
it "should transform Strings matching the xs:date format to Date objects" do
|
137
|
+
parse("<value>1955-04-18-05:00</value>")["value"].should == Date.parse("1955-04-18-05:00")
|
138
|
+
end
|
139
|
+
|
140
|
+
it "should transform Strings matching the xs:dateTime format to DateTime objects" do
|
141
|
+
parse("<value>1955-04-18T11:22:33-05:00</value>")["value"].should ==
|
142
|
+
DateTime.parse("1955-04-18T11:22:33-05:00")
|
143
|
+
end
|
144
|
+
|
145
|
+
it "should not transform Strings containing an xs:time String and more" do
|
146
|
+
parse("<value>09:33:55Z is a time</value>")["value"].should == "09:33:55Z is a time"
|
147
|
+
end
|
148
|
+
|
149
|
+
it "should not transform Strings containing an xs:date String and more" do
|
150
|
+
parse("<value>1955-04-18-05:00 is a date</value>")["value"].should == "1955-04-18-05:00 is a date"
|
151
|
+
end
|
152
|
+
|
153
|
+
it "should not transform Strings containing an xs:dateTime String and more" do
|
154
|
+
parse("<value>1955-04-18T11:22:33-05:00 is a dateTime</value>")["value"].should ==
|
155
|
+
"1955-04-18T11:22:33-05:00 is a dateTime"
|
156
|
+
end
|
87
157
|
end
|
88
158
|
|
89
159
|
context "Parsing xml with text and attributes" do
|
@@ -191,6 +261,46 @@ describe Nori do
|
|
191
261
|
parse(xml)['tag_1'].keys.should include('@attr_1')
|
192
262
|
end
|
193
263
|
|
264
|
+
context "with strip_namespaces set to true" do
|
265
|
+
around do |example|
|
266
|
+
Nori.strip_namespaces = true
|
267
|
+
example.run
|
268
|
+
Nori.strip_namespaces = false
|
269
|
+
end
|
270
|
+
|
271
|
+
it "should strip the namespace from every tag" do
|
272
|
+
xml = '<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"></soap:Envelope>'
|
273
|
+
parse(xml).should have_key("Envelope")
|
274
|
+
end
|
275
|
+
|
276
|
+
it "converts namespaced entries to array elements" do
|
277
|
+
xml = <<-XML
|
278
|
+
<history
|
279
|
+
xmlns:ns10="http://ns10.example.com"
|
280
|
+
xmlns:ns11="http://ns10.example.com">
|
281
|
+
<ns10:case><ns10:name>a_name</ns10:name></ns10:case>
|
282
|
+
<ns11:case><ns11:name>another_name</ns11:name></ns11:case>
|
283
|
+
</history>
|
284
|
+
XML
|
285
|
+
|
286
|
+
expected_case = [{ "name" => "a_name" }, { "name" => "another_name" }]
|
287
|
+
parse(xml)["history"]["case"].should == expected_case
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
context "with convert_tags_to set to a custom formula" do
|
292
|
+
around do |example|
|
293
|
+
Nori.convert_tags_to { |tag| tag.snakecase.to_sym }
|
294
|
+
example.run
|
295
|
+
Nori.convert_tags_to(nil)
|
296
|
+
end
|
297
|
+
|
298
|
+
it "transforms the tags to snakecase Symbols" do
|
299
|
+
xml = '<userResponse><accountStatus>active</accountStatus></userResponse>'
|
300
|
+
parse(xml).should == { :user_response => { :account_status => "active" } }
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
194
304
|
it "should render nested content correctly" do
|
195
305
|
xml = "<root><tag1>Tag1 Content <em><strong>This is strong</strong></em></tag1></root>"
|
196
306
|
parse(xml)['root']['tag1'].should == "Tag1 Content <em><strong>This is strong</strong></em>"
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: nori
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 23
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
|
+
- 1
|
7
8
|
- 0
|
8
|
-
-
|
9
|
-
|
10
|
-
version: 0.2.4
|
9
|
+
- 0
|
10
|
+
version: 1.0.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Daniel Harrington
|
@@ -17,8 +17,7 @@ autorequire:
|
|
17
17
|
bindir: bin
|
18
18
|
cert_chain: []
|
19
19
|
|
20
|
-
date: 2011-06-
|
21
|
-
default_executable:
|
20
|
+
date: 2011-06-20 00:00:00 Z
|
22
21
|
dependencies:
|
23
22
|
- !ruby/object:Gem::Dependency
|
24
23
|
name: nokogiri
|
@@ -104,7 +103,6 @@ files:
|
|
104
103
|
- spec/nori/nori_spec.rb
|
105
104
|
- spec/nori/parser_spec.rb
|
106
105
|
- spec/spec_helper.rb
|
107
|
-
has_rdoc: true
|
108
106
|
homepage: http://github.com/rubiii/nori
|
109
107
|
licenses: []
|
110
108
|
|
@@ -134,7 +132,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
134
132
|
requirements: []
|
135
133
|
|
136
134
|
rubyforge_project: nori
|
137
|
-
rubygems_version: 1.
|
135
|
+
rubygems_version: 1.8.5
|
138
136
|
signing_key:
|
139
137
|
specification_version: 3
|
140
138
|
summary: XML to Hash translator
|