saxy 0.2.3 → 0.7.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -1
- data/.travis.yml +14 -8
- data/Appraisals +7 -0
- data/CHANGELOG.md +35 -0
- data/LICENSE.txt +20 -0
- data/README.md +143 -49
- data/gemfiles/nokogiri_1.6.gemfile +7 -0
- data/gemfiles/nokogiri_latest.gemfile +7 -0
- data/lib/saxy/element.rb +27 -16
- data/lib/saxy/parser.rb +35 -13
- data/lib/saxy/parsing_error.rb +8 -2
- data/lib/saxy/version.rb +1 -1
- data/lib/saxy.rb +2 -6
- data/saxy.gemspec +6 -3
- data/spec/fixtures/invalid.xml +9 -0
- data/spec/saxy/element_spec.rb +18 -16
- data/spec/saxy/parser_spec.rb +67 -34
- data/spec/saxy_spec.rb +21 -20
- data/spec/spec_helper.rb +1 -2
- data/spec/support/io_like.rb +9 -0
- metadata +30 -27
- data/LICENSE +0 -22
- data/gemfiles/gemfile-1.8 +0 -6
- data/lib/saxy/ostruct.rb +0 -11
- data/spec/saxy/ostruct_spec.rb +0 -14
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 52a8b74342fe631ae2787bd38fedcb3600b24730
|
4
|
+
data.tar.gz: 37ef8a7e143494b48ba13c3d7470c84e459d0bf7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 468d1551a3fdafc0c0b37cdffb5482f1ecd5cf2fac2c8f63b23c7d75dc38c1d13b47ac8a7d195b8031251d62a5ea13492674cf418f25cf732d30e073a8f99f9b
|
7
|
+
data.tar.gz: d026bfaa8643b1eeb12ce00eecc13e4f1d27e531308706e14d05f1a9bc2d22755a5e2479482d5024ce298732373ea62498314d083fe4a2fa2aa8e5da3399c6a9
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
@@ -1,14 +1,20 @@
|
|
1
1
|
script: "bundle exec rspec ./spec/"
|
2
|
+
sudo: false
|
2
3
|
language: ruby
|
3
4
|
rvm:
|
5
|
+
- 2.5
|
6
|
+
- 2.4
|
7
|
+
- 2.3
|
8
|
+
- 2.2
|
9
|
+
- 2.1
|
4
10
|
- 2.0.0
|
5
11
|
- 1.9.3
|
6
|
-
|
12
|
+
gemfile:
|
13
|
+
- gemfiles/nokogiri_1.6.gemfile
|
14
|
+
- gemfiles/nokogiri_latest.gemfile
|
7
15
|
matrix:
|
8
|
-
|
9
|
-
- rvm:
|
10
|
-
gemfile: gemfiles/
|
11
|
-
- rvm:
|
12
|
-
gemfile: gemfiles/
|
13
|
-
|
14
|
-
|
16
|
+
exclude:
|
17
|
+
- rvm: 2.0.0
|
18
|
+
gemfile: gemfiles/nokogiri_latest.gemfile
|
19
|
+
- rvm: 1.9.3
|
20
|
+
gemfile: gemfiles/nokogiri_latest.gemfile
|
data/Appraisals
ADDED
data/CHANGELOG.md
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# Saxy Changelog
|
2
|
+
|
3
|
+
## 0.7.1
|
4
|
+
|
5
|
+
* Fixed whitespace stripping from elements' contents, now only leading and trailing whitespace is stripped (reported by @wzcolon, thanks!).
|
6
|
+
* Added `error_handler` option to make it possible to handle errors on-the-fly instead of raising them.
|
7
|
+
|
8
|
+
## 0.7.0
|
9
|
+
|
10
|
+
* [BREAKING] Yielded hashes now have strings as keys instead of symbols (performance and security fix).
|
11
|
+
|
12
|
+
## 0.6.1
|
13
|
+
|
14
|
+
* Fixed passing options from `Saxy.parse` to parser's initializer
|
15
|
+
|
16
|
+
## 0.6.0
|
17
|
+
|
18
|
+
* [BREAKING] `Saxy::ParsingError` now inherits from `StandardError`, not `Exception`.
|
19
|
+
* [BREAKING] Forced encoding is now an option instead of third argument of `Saxy.parse` method.
|
20
|
+
* Added `recovery` and `replace_entities` options that are internally passed to `Nokogiri::XML::SAX::ParserContext`
|
21
|
+
* Added `context` method to `Saxy::ParsingError` that holds parser context at the time of error.
|
22
|
+
|
23
|
+
## 0.5.2
|
24
|
+
|
25
|
+
* Added optional `encoding` argument to `Saxy.parse`
|
26
|
+
|
27
|
+
## 0.5.1
|
28
|
+
|
29
|
+
* Removed `activesupport` dependency
|
30
|
+
|
31
|
+
## 0.5.0
|
32
|
+
|
33
|
+
* [BREAKING] Dropped support for ruby 1.9.2 and lower
|
34
|
+
* [BREAKING] Yields hashes instead of `OpenStruct`s
|
35
|
+
* Added support for `IO`-like objects
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2012-2016 Humante LLC
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
6
|
+
this software and associated documentation files (the "Software"), to deal in
|
7
|
+
the Software without restriction, including without limitation the rights to
|
8
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
9
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
10
|
+
subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
17
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
18
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
19
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
20
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
CHANGED
@@ -1,10 +1,13 @@
|
|
1
1
|
# Saxy
|
2
2
|
|
3
|
-
[![
|
3
|
+
[![Gem Version](https://badge.fury.io/rb/saxy.svg)](https://badge.fury.io/rb/saxy)
|
4
|
+
[![Build Status](https://api.travis-ci.org/humante/saxy.svg)](http://travis-ci.org/humante/saxy)
|
4
5
|
|
5
|
-
Memory-efficient XML parser. Finds object definitions in XML and translates them into Ruby
|
6
|
+
Memory-efficient XML parser. Finds object definitions in XML and translates them into Ruby hashes.
|
6
7
|
|
7
|
-
It uses SAX parser under the hood, which means that it doesn't load the whole XML file into memory. It goes once
|
8
|
+
It uses SAX parser (provided by Nokogiri gem) under the hood, which means that it doesn't load the whole XML file into memory. It goes once through it and yields hashes along the way.
|
9
|
+
|
10
|
+
In result the memory footprint of the parser remains small and more or less constant irrespective of the size of the XML file, be it few KB or hundreds of GB.
|
8
11
|
|
9
12
|
## Installation
|
10
13
|
|
@@ -20,63 +23,146 @@ Or install it yourself as:
|
|
20
23
|
|
21
24
|
$ gem install saxy
|
22
25
|
|
26
|
+
## Requirements
|
27
|
+
|
28
|
+
As of `0.5.0` version `saxy` requires ruby 1.9.3 or higher. Previous versions of the gem work with ruby 1.8 and 1.9.2 (see below), but they are not maintained anymore.
|
29
|
+
|
30
|
+
### Ruby 1.8 support
|
31
|
+
|
32
|
+
See `ruby-1.8` branch. Install with:
|
33
|
+
|
34
|
+
gem 'saxy', '~> 0.3.0'
|
35
|
+
|
36
|
+
### Ruby 1.9.2 support
|
37
|
+
|
38
|
+
See `ruby-1.9.2` branch. Install with:
|
39
|
+
|
40
|
+
gem 'saxy', '~> 0.4.0'
|
41
|
+
|
42
|
+
## Changelog
|
43
|
+
|
44
|
+
See `CHANGELOG.md` file.
|
45
|
+
|
23
46
|
## Usage
|
24
47
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
48
|
+
You instantiate the parser by passing path to XML file or an IO-like object, object-identifying tag name and options hash (optionally) as its arguments.
|
49
|
+
|
50
|
+
```ruby
|
51
|
+
parser = Saxy.parse(path_or_io, object_tag, options = {})
|
52
|
+
```
|
53
|
+
|
54
|
+
Then iterate over it using `each` (or any of convenient methods provided by `Enumerable` mix-in).
|
55
|
+
|
56
|
+
```ruby
|
57
|
+
parser.each do |object|
|
58
|
+
...
|
59
|
+
end
|
60
|
+
```
|
61
|
+
|
62
|
+
### Options
|
63
|
+
|
64
|
+
* `encoding` - Forces the parser to work in given encoding
|
65
|
+
* `recovery` - Should this parser recover from structural errors? It will not stop processing file on structural errors if set to `true`.
|
66
|
+
* `replace_entities` - Should this parser replace entities? `&` will get converted to `&` if set to `true`.
|
67
|
+
* `error_handler` - If set to a callable, parser will call it with any error it encounters instead of raising exceptions.
|
68
|
+
|
69
|
+
Combination of `error_handler` and `recovery` options allows for continued processing when encountering recoverable errors (e.g. unescaped [predefined entities](https://en.wikipedia.org/wiki/List_of_XML_and_HTML_character_entity_references#Predefined_entities_in_XML)).
|
70
|
+
|
71
|
+
```ruby
|
72
|
+
error_handler = proc { |e| $stderr.puts "#{e.message} at line #{e.context.line}, column #{e.context.column}." }
|
73
|
+
Saxy.parse(path_or_io, object_tag, error_handler: error_handler, recovery: true) { ... }
|
74
|
+
```
|
75
|
+
|
76
|
+
## Example
|
77
|
+
|
78
|
+
Assume the XML file (an imaginary product feed):
|
79
|
+
|
80
|
+
````xml
|
81
|
+
<?xml version='1.0' encoding='UTF-8'?>
|
82
|
+
<webstore>
|
83
|
+
<name>Amazon</name>
|
84
|
+
<products>
|
85
|
+
<product>
|
86
|
+
<name>Kindle - The world's best-selling e-reader.</name>
|
87
|
+
<images>
|
88
|
+
<thumbSize width="80" height="60">http://amazon.com/kindle_thumb.jpg</thumbSize>
|
89
|
+
</images>
|
90
|
+
</product>
|
91
|
+
<product>
|
92
|
+
<name>Kindle Touch - Simple-to-use touchscreen with built-in WIFI.</name>
|
93
|
+
<images>
|
94
|
+
<thumbSize width="120" height="90">http://amazon.com/kindle_touch_thumb.jpg</thumbSize>
|
95
|
+
</images>
|
96
|
+
</product>
|
97
|
+
</products>
|
98
|
+
</webstore>
|
99
|
+
````
|
100
|
+
|
101
|
+
The following will parse the XML, find product definitions (inside `<product>` and `</product>` tags), build `Hash`es and yield them inside the block.
|
102
|
+
|
103
|
+
Usage with a file path:
|
104
|
+
|
105
|
+
````ruby
|
106
|
+
Saxy.parse("filename.xml", "product").each do |product|
|
107
|
+
puts product["name"]
|
108
|
+
puts product["images"]["thumb_size"]["contents"]
|
109
|
+
puts "#{product["images"]["thumb_size"]["width"]}x#{product["images"]["thumb_size"]["height"]}"
|
110
|
+
end
|
111
|
+
|
112
|
+
# =>
|
113
|
+
"Kindle - The world's best-selling e-reader."
|
114
|
+
"http://amazon.com/kindle_thumb.jpg"
|
115
|
+
"80x60"
|
116
|
+
"Kindle Touch - Simple-to-use touchscreen with built-in WIFI."
|
117
|
+
"http://amazon.com/kindle_touch_thumb.jpg"
|
118
|
+
"120x90"
|
119
|
+
````
|
120
|
+
|
121
|
+
Usage with an IO-like object `ARGF` or `$stdin`:
|
122
|
+
|
123
|
+
````ruby
|
124
|
+
# > cat filename.xml | ruby this_script.rb
|
125
|
+
Saxy.parse(ARGF, "product").each do |product|
|
126
|
+
puts product["name"]
|
127
|
+
end
|
128
|
+
|
129
|
+
# =>
|
130
|
+
"Kindle - The world's best-selling e-reader."
|
131
|
+
````
|
64
132
|
|
65
133
|
Saxy supports Enumerable, so you can use its goodies to your comfort without building intermediate arrays:
|
66
134
|
|
67
|
-
|
68
|
-
|
69
|
-
|
135
|
+
````ruby
|
136
|
+
Saxy.parse("filename.xml", "product").map do |object|
|
137
|
+
# map yielded Hash to ActiveRecord instances, etc.
|
138
|
+
end
|
139
|
+
````
|
70
140
|
|
71
141
|
You can also grab an Enumerator for external use (e.g. lazy evaluation, etc.):
|
72
142
|
|
73
|
-
|
74
|
-
|
143
|
+
````ruby
|
144
|
+
enumerator = Saxy.parse("filename.xml", "product").each
|
145
|
+
lazy = Saxy.parse("filename.xml", "product").lazy # Ruby 2.0
|
146
|
+
````
|
75
147
|
|
76
148
|
Multiple definitions of child objects are grouped in arrays:
|
77
149
|
|
78
|
-
|
79
|
-
|
150
|
+
````ruby
|
151
|
+
webstore = Saxy.parse("filename.xml", "webstore").first
|
152
|
+
webstore["products"]["product"].size # => 2
|
153
|
+
````
|
154
|
+
|
155
|
+
## Debugging
|
156
|
+
|
157
|
+
Invalid XML files happen a lot and error messages are not always extremely helpful. In case of a parsing error, some additional information can be retrieved from parser's context.
|
158
|
+
|
159
|
+
```ruby
|
160
|
+
begin
|
161
|
+
Saxy.parse(...) { ... }
|
162
|
+
rescue e => Saxy::ParsingError
|
163
|
+
puts "#{e.message} at #{e.context.line} line and #{e.context.column}"
|
164
|
+
end
|
165
|
+
```
|
80
166
|
|
81
167
|
## Contributing
|
82
168
|
|
@@ -85,3 +171,11 @@ Multiple definitions of child objects are grouped in arrays:
|
|
85
171
|
3. Commit your changes (`git commit -am 'Added some feature'`)
|
86
172
|
4. Push to the branch (`git push origin my-new-feature`)
|
87
173
|
5. Create new Pull Request
|
174
|
+
|
175
|
+
## License
|
176
|
+
|
177
|
+
See `LICENSE.txt` file.
|
178
|
+
|
179
|
+
## Author
|
180
|
+
|
181
|
+
Michał Szajbe, [@szajbus](https://twitter.com/szajbus), [szajbe.pl](http://szajbe.pl)
|
data/lib/saxy/element.rb
CHANGED
@@ -1,8 +1,6 @@
|
|
1
|
-
require 'active_support/core_ext/string/inflections'
|
2
|
-
|
3
1
|
module Saxy
|
4
2
|
class Element
|
5
|
-
attr_reader :attributes
|
3
|
+
attr_reader :attributes
|
6
4
|
|
7
5
|
def initialize
|
8
6
|
@attributes = {}
|
@@ -11,33 +9,46 @@ module Saxy
|
|
11
9
|
|
12
10
|
def set_attribute(name, value)
|
13
11
|
name = attribute_name(name)
|
12
|
+
|
14
13
|
attributes[name] ||= []
|
15
14
|
attributes[name] << value
|
16
15
|
end
|
17
16
|
|
18
17
|
def append_value(string)
|
19
|
-
|
18
|
+
if @value || !string.strip.empty?
|
20
19
|
@value ||= ""
|
21
20
|
@value << string
|
22
21
|
end
|
23
22
|
end
|
24
23
|
|
25
|
-
def
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
value
|
24
|
+
def value
|
25
|
+
@value && @value.strip
|
26
|
+
end
|
27
|
+
|
28
|
+
def to_h
|
29
|
+
return value unless attributes.any?
|
30
|
+
|
31
|
+
data = attributes.reduce({}) do |memo, (name, value)|
|
32
|
+
memo[name] = value.size == 1 ? value.first : value
|
33
|
+
memo
|
36
34
|
end
|
35
|
+
|
36
|
+
data["contents"] = value
|
37
|
+
data
|
37
38
|
end
|
38
39
|
|
39
40
|
def attribute_name(name)
|
40
|
-
name
|
41
|
+
underscore(name)
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def underscore(word)
|
47
|
+
word
|
48
|
+
.gsub(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2')
|
49
|
+
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
|
50
|
+
.tr("-", "_")
|
51
|
+
.downcase
|
41
52
|
end
|
42
53
|
end
|
43
54
|
end
|
data/lib/saxy/parser.rb
CHANGED
@@ -16,8 +16,14 @@ module Saxy
|
|
16
16
|
# Will yield objects inside the callback after they're built
|
17
17
|
attr_reader :callback
|
18
18
|
|
19
|
-
|
20
|
-
|
19
|
+
# Parser context
|
20
|
+
attr_reader :context
|
21
|
+
|
22
|
+
# Parser options
|
23
|
+
attr_reader :options
|
24
|
+
|
25
|
+
def initialize(object, object_tag, options={})
|
26
|
+
@object, @object_tag, @options = object, object_tag, options
|
21
27
|
@tags, @elements = [], []
|
22
28
|
end
|
23
29
|
|
@@ -35,8 +41,9 @@ module Saxy
|
|
35
41
|
|
36
42
|
def end_element(tag)
|
37
43
|
tags.pop
|
44
|
+
|
38
45
|
if element = elements.pop
|
39
|
-
object = element.
|
46
|
+
object = element.to_h
|
40
47
|
|
41
48
|
if current_element
|
42
49
|
current_element.set_attribute(tag, object)
|
@@ -55,7 +62,13 @@ module Saxy
|
|
55
62
|
end
|
56
63
|
|
57
64
|
def error(message)
|
58
|
-
|
65
|
+
error = ParsingError.new(message, context)
|
66
|
+
|
67
|
+
if options[:error_handler].respond_to?(:call)
|
68
|
+
options[:error_handler].call(error)
|
69
|
+
else
|
70
|
+
raise error
|
71
|
+
end
|
59
72
|
end
|
60
73
|
|
61
74
|
def current_element
|
@@ -63,19 +76,28 @@ module Saxy
|
|
63
76
|
end
|
64
77
|
|
65
78
|
def each(&blk)
|
66
|
-
|
67
|
-
@callback = blk
|
79
|
+
return to_enum unless blk
|
68
80
|
|
69
|
-
|
81
|
+
@callback = blk
|
70
82
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
83
|
+
args = [self, options[:encoding]].compact
|
84
|
+
parser = Nokogiri::XML::SAX::Parser.new(*args)
|
85
|
+
|
86
|
+
if @object.respond_to?(:read) && @object.respond_to?(:close)
|
87
|
+
parser.parse_io(@object, &context_blk)
|
76
88
|
else
|
77
|
-
|
89
|
+
parser.parse_file(@object, &context_blk)
|
78
90
|
end
|
79
91
|
end
|
92
|
+
|
93
|
+
def context_blk
|
94
|
+
proc { |context|
|
95
|
+
[:recovery, :replace_entities].each do |key|
|
96
|
+
context.send("#{key}=", options[key]) if options.has_key?(key)
|
97
|
+
end
|
98
|
+
|
99
|
+
@context = context
|
100
|
+
}
|
101
|
+
end
|
80
102
|
end
|
81
103
|
end
|
data/lib/saxy/parsing_error.rb
CHANGED
data/lib/saxy/version.rb
CHANGED
data/lib/saxy.rb
CHANGED
@@ -2,8 +2,8 @@ require 'saxy/version'
|
|
2
2
|
|
3
3
|
module Saxy
|
4
4
|
class << self
|
5
|
-
def parse(
|
6
|
-
parser = Parser.new(
|
5
|
+
def parse(object, object_tag, options={}, &blk)
|
6
|
+
parser = Parser.new(object, object_tag, options)
|
7
7
|
|
8
8
|
if blk
|
9
9
|
parser.each(blk)
|
@@ -12,13 +12,9 @@ module Saxy
|
|
12
12
|
end
|
13
13
|
end
|
14
14
|
|
15
|
-
def ruby_18?
|
16
|
-
@ruby_18 ||= RUBY_VERSION =~ /^1\.8/
|
17
|
-
end
|
18
15
|
end
|
19
16
|
end
|
20
17
|
|
21
18
|
require 'saxy/element'
|
22
|
-
require 'saxy/ostruct'
|
23
19
|
require 'saxy/parser'
|
24
20
|
require 'saxy/parsing_error'
|
data/saxy.gemspec
CHANGED
@@ -4,9 +4,9 @@ require File.expand_path('../lib/saxy/version', __FILE__)
|
|
4
4
|
Gem::Specification.new do |gem|
|
5
5
|
gem.authors = ["Michał Szajbe"]
|
6
6
|
gem.email = ["michal.szajbe@gmail.com"]
|
7
|
-
gem.description = %q{Saxy finds object definitions in XML files and translates them into Ruby objects. It uses SAX parser under the hood, which means that it doesn't load the whole XML file into memory. It goes once
|
7
|
+
gem.description = %q{Saxy finds object definitions in XML files and translates them into Ruby objects. It uses SAX parser under the hood, which means that it doesn't load the whole XML file into memory. It goes once through it and yields objects along the way.}
|
8
8
|
gem.summary = %q{Memory-efficient XML parser. Finds object definitions and translates them into Ruby objects.}
|
9
|
-
gem.homepage = "http://github.com/
|
9
|
+
gem.homepage = "http://github.com/humante/saxy"
|
10
10
|
|
11
11
|
gem.files = `git ls-files`.split($\)
|
12
12
|
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
@@ -15,7 +15,10 @@ Gem::Specification.new do |gem|
|
|
15
15
|
gem.require_paths = ["lib"]
|
16
16
|
gem.version = Saxy::VERSION
|
17
17
|
|
18
|
-
gem.
|
18
|
+
gem.required_ruby_version = ">= 1.9.3"
|
19
|
+
|
19
20
|
gem.add_dependency "nokogiri"
|
21
|
+
|
20
22
|
gem.add_development_dependency "rspec"
|
23
|
+
gem.add_development_dependency "appraisal"
|
21
24
|
end
|
data/spec/saxy/element_spec.rb
CHANGED
@@ -5,45 +5,47 @@ describe Saxy::Element do
|
|
5
5
|
|
6
6
|
it "should not append empty string as value" do
|
7
7
|
element.append_value("")
|
8
|
-
element.value.
|
8
|
+
expect(element.value).to be_nil
|
9
9
|
end
|
10
10
|
|
11
|
-
it "should
|
12
|
-
element.append_value("
|
13
|
-
element.append_value("
|
14
|
-
element.
|
11
|
+
it "should strip leading and trailing whitespace" do
|
12
|
+
element.append_value(" one two ")
|
13
|
+
element.append_value("&")
|
14
|
+
element.append_value(" three\nfour")
|
15
|
+
element.append_value("\t\t")
|
16
|
+
expect(element.value).to eq("one two & three\nfour")
|
15
17
|
end
|
16
18
|
|
17
19
|
it "should dump as string when no attributes are set" do
|
18
|
-
element.
|
19
|
-
element.
|
20
|
+
expect(element).to receive(:value).and_return("foo")
|
21
|
+
expect(element.to_h).to eq("foo")
|
20
22
|
end
|
21
23
|
|
22
24
|
it "should dump as object when attributes are set" do
|
23
|
-
element.
|
24
|
-
object = element.
|
25
|
+
expect(element).to receive(:attributes).at_least(:once).and_return("foo" => 1, "bar" => 2)
|
26
|
+
object = element.to_h
|
25
27
|
|
26
|
-
object
|
27
|
-
object
|
28
|
+
expect(object["foo"]).to eq(1)
|
29
|
+
expect(object["bar"]).to eq(2)
|
28
30
|
end
|
29
31
|
|
30
32
|
it "should dump as object with value when attributes and contents are set" do
|
31
33
|
element.set_attribute("foo", "bar")
|
32
34
|
element.append_value("value")
|
33
|
-
object = element.
|
35
|
+
object = element.to_h
|
34
36
|
|
35
|
-
object
|
36
|
-
object
|
37
|
+
expect(object["foo"]).to eq("bar")
|
38
|
+
expect(object["contents"]).to eq("value")
|
37
39
|
end
|
38
40
|
|
39
41
|
it "should add attributes under underscored names" do
|
40
42
|
element.set_attribute("FooBar", "baz")
|
41
|
-
element.
|
43
|
+
expect(element.to_h["foo_bar"]).to eq("baz")
|
42
44
|
end
|
43
45
|
|
44
46
|
it "should create array if adding multiple attributtes with the same name" do
|
45
47
|
element.set_attribute("foo", "bar")
|
46
48
|
element.set_attribute("foo", "baz")
|
47
|
-
element.
|
49
|
+
expect(element.to_h["foo"]).to eq(["bar", "baz"])
|
48
50
|
end
|
49
51
|
end
|
data/spec/saxy/parser_spec.rb
CHANGED
@@ -4,43 +4,62 @@ describe Saxy::Parser do
|
|
4
4
|
include FixturesHelper
|
5
5
|
|
6
6
|
let(:parser) { Saxy::Parser.new(fixture_file("webstore.xml"), "product") }
|
7
|
+
let(:file_io) { File.new(fixture_file("webstore.xml")) }
|
8
|
+
let(:io_like) { IOLike.new(file_io) }
|
7
9
|
|
8
|
-
it "should accept string filename
|
10
|
+
it "should accept string filename for parsing" do
|
9
11
|
xml_file = fixture_file("webstore.xml")
|
10
12
|
parser = Saxy::Parser.new(xml_file, "product")
|
11
|
-
parser.each.to_a.size.
|
13
|
+
expect(parser.each.to_a.size).to eq(2)
|
12
14
|
end
|
13
15
|
|
14
|
-
it "should accept IO
|
15
|
-
|
16
|
-
parser
|
17
|
-
|
16
|
+
it "should accept IO for parsing" do
|
17
|
+
parser = Saxy::Parser.new(file_io, "product")
|
18
|
+
expect(parser.each.to_a.size).to eq(2)
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should accept optional force-encoding" do
|
22
|
+
parser = Saxy::Parser.new(file_io, "product", encoding: "UTF-8")
|
23
|
+
expect(Nokogiri::XML::SAX::Parser).to receive(:new).with(parser, "UTF-8").and_call_original
|
24
|
+
expect(parser.each.to_a.size).to eq(2)
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should pass options to parser context" do
|
28
|
+
parser = Saxy::Parser.new(file_io, "product", recovery: true, replace_entities: true)
|
29
|
+
parser.each.to_a
|
30
|
+
expect(parser.context.recovery).to be_truthy
|
31
|
+
expect(parser.context.replace_entities).to be_truthy
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should accept an IO-like for parsing" do
|
35
|
+
parser = Saxy::Parser.new(io_like, "product")
|
36
|
+
expect(parser.each.to_a.size).to eq(2)
|
18
37
|
end
|
19
38
|
|
20
39
|
it "should have empty tag stack" do
|
21
|
-
parser.tags.
|
40
|
+
expect(parser.tags).to eq(%w( ))
|
22
41
|
end
|
23
42
|
|
24
43
|
it "should push/pop tag names on/from tag stack when going down/up the XML tree" do
|
25
|
-
parser.tags.
|
44
|
+
expect(parser.tags).to eq(%w( ))
|
26
45
|
|
27
46
|
parser.start_element('webstore')
|
28
|
-
parser.tags.
|
47
|
+
expect(parser.tags).to eq(%w( webstore ))
|
29
48
|
|
30
49
|
parser.start_element('products')
|
31
|
-
parser.tags.
|
50
|
+
expect(parser.tags).to eq(%w( webstore products ))
|
32
51
|
|
33
52
|
parser.start_element('product')
|
34
|
-
parser.tags.
|
53
|
+
expect(parser.tags).to eq(%w( webstore products product ))
|
35
54
|
|
36
55
|
parser.end_element('product')
|
37
|
-
parser.tags.
|
56
|
+
expect(parser.tags).to eq(%w( webstore products ))
|
38
57
|
|
39
58
|
parser.end_element('products')
|
40
|
-
parser.tags.
|
59
|
+
expect(parser.tags).to eq(%w( webstore ))
|
41
60
|
|
42
61
|
parser.end_element('webstore')
|
43
|
-
parser.tags.
|
62
|
+
expect(parser.tags).to eq(%w( ))
|
44
63
|
end
|
45
64
|
|
46
65
|
context "when detecting object tag opening" do
|
@@ -49,7 +68,7 @@ describe Saxy::Parser do
|
|
49
68
|
end
|
50
69
|
|
51
70
|
it "should add new element to stack" do
|
52
|
-
parser.elements.size.
|
71
|
+
expect(parser.elements.size).to eq(1)
|
53
72
|
end
|
54
73
|
end
|
55
74
|
|
@@ -59,14 +78,14 @@ describe Saxy::Parser do
|
|
59
78
|
end
|
60
79
|
|
61
80
|
it "should not add new element to stack" do
|
62
|
-
parser.elements.
|
81
|
+
expect(parser.elements).to be_empty
|
63
82
|
end
|
64
83
|
end
|
65
84
|
|
66
85
|
context "with non-empty element stack" do
|
67
86
|
before do
|
68
87
|
parser.start_element("product")
|
69
|
-
parser.elements.
|
88
|
+
expect(parser.elements).to_not be_empty
|
70
89
|
end
|
71
90
|
|
72
91
|
context "when detecting object tag opening" do
|
@@ -75,7 +94,7 @@ describe Saxy::Parser do
|
|
75
94
|
end
|
76
95
|
|
77
96
|
it "should add new element to stack" do
|
78
|
-
parser.elements.size.
|
97
|
+
expect(parser.elements.size).to eq(2)
|
79
98
|
end
|
80
99
|
end
|
81
100
|
|
@@ -85,7 +104,7 @@ describe Saxy::Parser do
|
|
85
104
|
end
|
86
105
|
|
87
106
|
it "should not add new element to stack" do
|
88
|
-
parser.elements.size.
|
107
|
+
expect(parser.elements.size).to eq(2)
|
89
108
|
end
|
90
109
|
end
|
91
110
|
|
@@ -95,36 +114,36 @@ describe Saxy::Parser do
|
|
95
114
|
end
|
96
115
|
|
97
116
|
it "should pop element from stack" do
|
98
|
-
parser.elements.
|
117
|
+
expect(parser.elements).to be_empty
|
99
118
|
end
|
100
119
|
end
|
101
120
|
|
102
121
|
context "with callback defined" do
|
103
122
|
before do
|
104
123
|
@callback = lambda { |object| object }
|
105
|
-
parser.
|
124
|
+
allow(parser).to receive(:callback).and_return(@callback)
|
106
125
|
end
|
107
126
|
|
108
127
|
it "should yield the object inside the callback after detecting object tag closing" do
|
109
|
-
@callback.
|
128
|
+
expect(@callback).to receive(:call).with(parser.current_element.to_h)
|
110
129
|
parser.end_element("product")
|
111
130
|
end
|
112
131
|
|
113
132
|
it "should not yield the object inside the callback after detecting other tag closing" do
|
114
133
|
parser.start_element("other")
|
115
|
-
@callback.
|
134
|
+
expect(@callback).to_not receive(:call)
|
116
135
|
parser.end_element("other")
|
117
136
|
end
|
118
137
|
end
|
119
138
|
|
120
139
|
it "should append cdata block's contents to top element's value when detecting cdata block" do
|
121
|
-
parser.current_element.
|
140
|
+
expect(parser.current_element).to receive(:append_value).with("foo")
|
122
141
|
parser.cdata_block("foo")
|
123
142
|
end
|
124
143
|
|
125
144
|
it "should append characters to top element's value when detecting characters block" do
|
126
|
-
parser.current_element.
|
127
|
-
parser.current_element.
|
145
|
+
expect(parser.current_element).to receive(:append_value).with("foo")
|
146
|
+
expect(parser.current_element).to receive(:append_value).with("bar")
|
128
147
|
parser.characters("foo")
|
129
148
|
parser.characters("bar")
|
130
149
|
end
|
@@ -132,7 +151,7 @@ describe Saxy::Parser do
|
|
132
151
|
it "should set element's attribute after processing tags" do
|
133
152
|
element = parser.current_element
|
134
153
|
|
135
|
-
element.
|
154
|
+
expect(element).to receive(:set_attribute).with("foo", "bar")
|
136
155
|
|
137
156
|
parser.start_element("foo")
|
138
157
|
parser.characters("bar")
|
@@ -141,19 +160,33 @@ describe Saxy::Parser do
|
|
141
160
|
|
142
161
|
it "should set element's attributes when opening tag with attributes" do
|
143
162
|
parser.start_element("foo", [["bar", "baz"]])
|
144
|
-
parser.current_element.
|
163
|
+
expect(parser.current_element.to_h["bar"]).to eq("baz")
|
145
164
|
end
|
146
165
|
end
|
147
166
|
|
148
|
-
|
149
|
-
|
167
|
+
context "when error handler is not set" do
|
168
|
+
let(:parser) { Saxy::Parser.new(fixture_file("invalid.xml"), "product") }
|
169
|
+
|
170
|
+
it "should raise Saxy::ParsingError on error" do
|
171
|
+
expect { parser.each.to_a }.to raise_error { |error|
|
172
|
+
expect(error).to be_a(Saxy::ParsingError)
|
173
|
+
expect(error.message).to match(/xmlParseEntityRef: no name/)
|
174
|
+
expect(error.context).to be_a(Nokogiri::XML::SAX::ParserContext)
|
175
|
+
}
|
176
|
+
end
|
150
177
|
end
|
151
178
|
|
152
|
-
|
153
|
-
|
179
|
+
context "when error handler is set" do
|
180
|
+
let(:handler) { proc { |error| error } }
|
181
|
+
let(:parser) { Saxy::Parser.new(fixture_file("invalid.xml"), "product", error_handler: handler) }
|
182
|
+
|
183
|
+
it "should call error handler passing Saxy::ParsingError instance" do
|
184
|
+
expect(handler).to receive(:call).with(Saxy::ParsingError)
|
185
|
+
parser.each.to_a
|
186
|
+
end
|
154
187
|
end
|
155
188
|
|
156
|
-
it "should return Enumerator when calling #each without a block"
|
157
|
-
parser.each.
|
189
|
+
it "should return Enumerator when calling #each without a block" do
|
190
|
+
expect(parser.each).to be_an(Enumerator)
|
158
191
|
end
|
159
192
|
end
|
data/spec/saxy_spec.rb
CHANGED
@@ -9,33 +9,34 @@ describe Saxy do
|
|
9
9
|
arr
|
10
10
|
end
|
11
11
|
|
12
|
-
products[0]
|
13
|
-
products[0]
|
14
|
-
products[0]
|
15
|
-
products[0]
|
16
|
-
products[0]
|
17
|
-
products[0]
|
18
|
-
|
19
|
-
products[1]
|
20
|
-
products[1]
|
21
|
-
products[1]
|
22
|
-
products[1]
|
23
|
-
products[1]
|
24
|
-
products[1]
|
12
|
+
expect(products[0]["uid"]).to eq("FFCF177")
|
13
|
+
expect(products[0]["name"]).to eq("Kindle")
|
14
|
+
expect(products[0]["description"]).to eq("The world's best-selling e-reader.")
|
15
|
+
expect(products[0]["price"]).to eq("$109")
|
16
|
+
expect(products[0]["images"]["thumb"]).to eq("http://amazon.com/kindle_thumb.jpg")
|
17
|
+
expect(products[0]["images"]["large"]).to eq("http://amazon.com/kindle.jpg")
|
18
|
+
|
19
|
+
expect(products[1]["uid"]).to eq("YD26NT")
|
20
|
+
expect(products[1]["name"]).to eq("Kindle Touch")
|
21
|
+
expect(products[1]["description"]).to eq("Simple-to-use touchscreen with built-in WIFI.")
|
22
|
+
expect(products[1]["price"]).to eq("$79")
|
23
|
+
expect(products[1]["images"]["thumb"]).to eq("http://amazon.com/kindle_touch_thumb.jpg")
|
24
|
+
expect(products[1]["images"]["large"]).to eq("http://amazon.com/kindle_touch.jpg")
|
25
25
|
end
|
26
26
|
|
27
27
|
it "should group multiple definitions of child objects into arrays" do
|
28
28
|
webstore = Saxy.parse(fixture_file("webstore.xml"), "webstore").first
|
29
29
|
|
30
|
-
webstore
|
31
|
-
webstore
|
30
|
+
expect(webstore["products"]["product"]).to be_an(Array)
|
31
|
+
expect(webstore["products"]["product"].size).to eq(2)
|
32
32
|
end
|
33
33
|
|
34
|
-
it "should return Enumerator when calling #parse without a block"
|
35
|
-
Saxy.parse(fixture_file("webstore.xml"), "product").each.
|
34
|
+
it "should return Enumerator when calling #parse without a block" do
|
35
|
+
expect(Saxy.parse(fixture_file("webstore.xml"), "product").each).to be_an(Enumerator)
|
36
36
|
end
|
37
37
|
|
38
|
-
it "should
|
39
|
-
Saxy.
|
38
|
+
it "should pass options to Parser's initializer" do
|
39
|
+
expect(Saxy::Parser).to receive(:new).with("filename", "object_tag", { foo: 'bar' }).and_call_original
|
40
|
+
Saxy.parse("filename", "object_tag", { foo: 'bar' })
|
40
41
|
end
|
41
|
-
end
|
42
|
+
end
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,60 +1,60 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: saxy
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.7.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Michał Szajbe
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2018-04-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: nokogiri
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- -
|
17
|
+
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
19
|
version: '0'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- -
|
24
|
+
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
28
|
+
name: rspec
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- -
|
31
|
+
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
33
|
version: '0'
|
34
|
-
type: :
|
34
|
+
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- -
|
38
|
+
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '0'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
|
-
name:
|
42
|
+
name: appraisal
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
|
-
- -
|
45
|
+
- - ">="
|
46
46
|
- !ruby/object:Gem::Version
|
47
47
|
version: '0'
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
|
-
- -
|
52
|
+
- - ">="
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '0'
|
55
55
|
description: Saxy finds object definitions in XML files and translates them into Ruby
|
56
56
|
objects. It uses SAX parser under the hood, which means that it doesn't load the
|
57
|
-
whole XML file into memory. It goes once
|
57
|
+
whole XML file into memory. It goes once through it and yields objects along the
|
58
58
|
way.
|
59
59
|
email:
|
60
60
|
- michal.szajbe@gmail.com
|
@@ -62,29 +62,32 @@ executables: []
|
|
62
62
|
extensions: []
|
63
63
|
extra_rdoc_files: []
|
64
64
|
files:
|
65
|
-
- .gitignore
|
66
|
-
- .rspec
|
67
|
-
- .travis.yml
|
65
|
+
- ".gitignore"
|
66
|
+
- ".rspec"
|
67
|
+
- ".travis.yml"
|
68
|
+
- Appraisals
|
69
|
+
- CHANGELOG.md
|
68
70
|
- Gemfile
|
69
|
-
- LICENSE
|
71
|
+
- LICENSE.txt
|
70
72
|
- README.md
|
71
73
|
- Rakefile
|
72
|
-
- gemfiles/gemfile
|
74
|
+
- gemfiles/nokogiri_1.6.gemfile
|
75
|
+
- gemfiles/nokogiri_latest.gemfile
|
73
76
|
- lib/saxy.rb
|
74
77
|
- lib/saxy/element.rb
|
75
|
-
- lib/saxy/ostruct.rb
|
76
78
|
- lib/saxy/parser.rb
|
77
79
|
- lib/saxy/parsing_error.rb
|
78
80
|
- lib/saxy/version.rb
|
79
81
|
- saxy.gemspec
|
82
|
+
- spec/fixtures/invalid.xml
|
80
83
|
- spec/fixtures/webstore.xml
|
81
84
|
- spec/fixtures_helper.rb
|
82
85
|
- spec/saxy/element_spec.rb
|
83
|
-
- spec/saxy/ostruct_spec.rb
|
84
86
|
- spec/saxy/parser_spec.rb
|
85
87
|
- spec/saxy_spec.rb
|
86
88
|
- spec/spec_helper.rb
|
87
|
-
|
89
|
+
- spec/support/io_like.rb
|
90
|
+
homepage: http://github.com/humante/saxy
|
88
91
|
licenses: []
|
89
92
|
metadata: {}
|
90
93
|
post_install_message:
|
@@ -93,27 +96,27 @@ require_paths:
|
|
93
96
|
- lib
|
94
97
|
required_ruby_version: !ruby/object:Gem::Requirement
|
95
98
|
requirements:
|
96
|
-
- -
|
99
|
+
- - ">="
|
97
100
|
- !ruby/object:Gem::Version
|
98
|
-
version:
|
101
|
+
version: 1.9.3
|
99
102
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
100
103
|
requirements:
|
101
|
-
- -
|
104
|
+
- - ">="
|
102
105
|
- !ruby/object:Gem::Version
|
103
106
|
version: '0'
|
104
107
|
requirements: []
|
105
108
|
rubyforge_project:
|
106
|
-
rubygems_version: 2.
|
109
|
+
rubygems_version: 2.5.1
|
107
110
|
signing_key:
|
108
111
|
specification_version: 4
|
109
112
|
summary: Memory-efficient XML parser. Finds object definitions and translates them
|
110
113
|
into Ruby objects.
|
111
114
|
test_files:
|
115
|
+
- spec/fixtures/invalid.xml
|
112
116
|
- spec/fixtures/webstore.xml
|
113
117
|
- spec/fixtures_helper.rb
|
114
118
|
- spec/saxy/element_spec.rb
|
115
|
-
- spec/saxy/ostruct_spec.rb
|
116
119
|
- spec/saxy/parser_spec.rb
|
117
120
|
- spec/saxy_spec.rb
|
118
121
|
- spec/spec_helper.rb
|
119
|
-
|
122
|
+
- spec/support/io_like.rb
|
data/LICENSE
DELETED
@@ -1,22 +0,0 @@
|
|
1
|
-
Copyright (c) 2012 Michał Szajbe
|
2
|
-
|
3
|
-
MIT License
|
4
|
-
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
-
a copy of this software and associated documentation files (the
|
7
|
-
"Software"), to deal in the Software without restriction, including
|
8
|
-
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
-
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
-
permit persons to whom the Software is furnished to do so, subject to
|
11
|
-
the following conditions:
|
12
|
-
|
13
|
-
The above copyright notice and this permission notice shall be
|
14
|
-
included in all copies or substantial portions of the Software.
|
15
|
-
|
16
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
-
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
-
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
-
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
-
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
-
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
-
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/gemfiles/gemfile-1.8
DELETED
data/lib/saxy/ostruct.rb
DELETED
data/spec/saxy/ostruct_spec.rb
DELETED
@@ -1,14 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
describe Saxy::OpenStruct do
|
4
|
-
let(:object) { Saxy::OpenStruct.new }
|
5
|
-
|
6
|
-
if Saxy.ruby_18?
|
7
|
-
context "in Ruby 1.8" do
|
8
|
-
it "should correctly set id attribute" do
|
9
|
-
object.id = 1
|
10
|
-
object.id.should == 1
|
11
|
-
end
|
12
|
-
end
|
13
|
-
end
|
14
|
-
end
|