nori 2.7.1 → 2.9.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e562b494afc72df387f136e51af0c5e0445b528b5515eedad7f60b3d49d207a5
4
- data.tar.gz: 5eabad83b50392e39973058c70d6307ddaaeea47934c292fbd472ee0f93680f9
3
+ metadata.gz: d2ffd841cec28588977a4cf413ade2a33ceba13466046a1299e27016231db0ef
4
+ data.tar.gz: 51edbdf4ab6413ca7583bb02e05a88ad13fac97741797cf5dde6135edd99d5f8
5
5
  SHA512:
6
- metadata.gz: 6366a2ac0d9dd99e4fd7b0f32d95dfb21f76885ab18fc7f3f7767c5e257b61e3817d2bf13f9a113feb2482fdb6f5a912956ef5d17a3f868a9139e9b55683402f
7
- data.tar.gz: cf295850b06961ed67644a8c06c61152cecf5c8512f8a74a03115e47fe8f9a567300d9af8d91504013777400882869cb7016c80563015e6423e86718c7b828bd
6
+ metadata.gz: 1700483ec9b91f559ab0fa138c5760fe237f7bf92f5b927bbde60dc437175af5e8c8cc12998195bd875ea9272f62d25727e3fe85a5954918cc0ea31aa5a413a3
7
+ data.tar.gz: 8713852dfe40adfe96ae00debff569d58f6d8c394fd48fe8df7ad34a767e886605df4163d15cd8573922926ad7abb9f3af1135ddd7180301172dfef5b6b718c6
data/CHANGELOG.md CHANGED
@@ -1,19 +1,48 @@
1
- # 2.7.1 (2024-07-28)
1
+ # Nori changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [2.9.0] - 2026-07-05
9
+
10
+ ### Changed
11
+
12
+ * The `:standards` profile no longer applies schema-less typing. Under `Nori.new(standards: true)`, `:advanced_typecasting` now defaults to `false` (an explicit `advanced_typecasting: true` still wins), and the bare un-namespaced `type=` and `nil=` attributes become ordinary attributes instead of casting instructions. No more `type=` conversions (`integer`, `date`, `decimal`, `array`, `file`, ...), and only a prefixed `xsi:nil="true"` marks an element nil. These conventions come from Rails' `Hash.from_xml` (inherited via crack and merb), not from any XML spec. Without a schema, character data is just text. Knowing that `<id>123</id>` holds an integer is the business of a schema-aware layer, not a guess from string shape. Parsing without the profile is completely unchanged.
13
+
14
+ ### Added
15
+
16
+ * Add the `:serializable` profile option. `Nori.new(serializable: true)` returns plain, directly-serializable data with no custom value classes. A text node that also has attributes becomes a Hash in the XML JSON convention (`{"#text" => content}` merged with our existing `@`-prefixed attributes instead of a `Nori::StringWithAttributes`, and a text node without attributes becomes a plain `String`. A `type="file"` node is no longer decoded into a `Nori::StringIOFile`, it folds into text and attributes like any other node, leaving the base64 content untouched for the caller to decode. That `type=` decoding is a Rails/ActiveSupport `Hash.from_xml` convention Nori inherited via crack and merb, not part of any XML spec, which is why the plain-data profile drops it. With this, values survive `to_json`, `to_yaml`, and `Marshal`. Opt-in on the 2.x line, probably the default in Nori 3.0. Reported by @ArnoldMEDLINQ ([#107](https://github.com/savonrb/nori/issues/107)), thanks to @dub357 for pinpointing the cause and finding [#106](https://github.com/savonrb/nori/pull/106), and thanks to @ekzobrain whose hash-shape approach this builds on.
17
+
18
+ ## [2.8.0] - 2026-07-04
19
+
20
+ ### Added
21
+
22
+ * Add the `:standards` profile option. `Nori.new(standards: true)` turns on Nori's spec-correct parsing as a group rather than one flag at a time. Its first members are xml:space honoring (whitespace-only text under `xml:space="preserve"` is kept instead of stripped, with inheritance and `xml:space="default"` reset per [XML 1.0 §2.10](https://www.w3.org/TR/xml/#sec-white-space)) and the XML string-value model for empty elements (implies `:consistent_empty_tags` with an empty-string `:empty_tag_value`, both still overridable). Opt-in on the 2.x line, default in 3.0. Thanks to @md5 for raising the `xml:space="preserve"` case on [#97](https://github.com/savonrb/nori/issues/97).
23
+
24
+ * [#97](https://github.com/savonrb/nori/issues/97) Add the `:consistent_empty_tags` option ([#109](https://github.com/savonrb/nori/pull/109)). When enabled, every empty tag becomes the `:empty_tag_value`, whether it has attributes or not. A string value keeps the attributes accessible via `#attributes`, and an explicit `xsi:nil="true"` always becomes `nil`. Reported by @lukasbischof, who also proposed a fix in [#98](https://github.com/savonrb/nori/pull/98). Thanks to @md5 for surfacing another instance of it and pinpointing the cause.
25
+
26
+ ### Fixed
27
+
28
+ * Whitespace-only CDATA content is no longer stripped. A `<![CDATA[ ]]>` section is the author's explicit literal-data marker, so its content is now preserved as text instead of being dropped, on both the Nokogiri and REXML parsers. Previously such a tag parsed to `nil` (or, with attributes, dropped the content and kept only the attributes). Non-whitespace CDATA is unchanged, and ordinary whitespace-only text is still stripped as before. Surfaced while auditing whitespace handling for [#97](https://github.com/savonrb/nori/issues/97), the same empty-tag/whitespace spec-compliance work as the `:consistent_empty_tags` option above.
29
+
30
+ ## 2.7.1 (2024-07-28)
2
31
 
3
32
  * Stop monkey-patching String with #snakecase by @mchu in https://github.com/savonrb/nori/pull/102
4
33
 
5
- # 2.7.0 (2024-02-13)
34
+ ## 2.7.0 (2024-02-13)
6
35
 
7
36
  * Added support for ruby 3.1, 3.2, 3.3. Dropped support for ruby 2.7 and below.
8
37
  * Feature: `Nori::Parser` has a new option, `:scrub_xml`, which defaults to true, for scrubbing invalid characters ([#72](https://github.com/savonrb/nori/pull/72)). This should allow documents containing invalid characters to still be parsed.
9
38
  * Fix: REXML parser changes `&lt;` inside CDATA to `<` ([#94](https://github.com/savonrb/nori/pull/94))
10
39
  * Change: `Object#blank?` is no longer patched in.
11
40
 
12
- # 2.6.0 (2015-05-06)
41
+ ## 2.6.0 (2015-05-06)
13
42
 
14
43
  * Feature: [#69](https://github.com/savonrb/nori/pull/69) Add option to convert empty tags to a value other than nil.
15
44
 
16
- # 2.5.0 (2015-03-31)
45
+ ## 2.5.0 (2015-03-31)
17
46
 
18
47
  * Formally drop support for ruby 1.8.7. Installing Nori from rubygems for that version should no longer attempt to install versions that will not work.
19
48
  * BREAKING CHANGE: Newlines are now preserved when present in the value of inner text nodes. See the example below:
@@ -31,7 +60,7 @@ Nori.new.parse("<outer>\n&lt;embedded&gt;\n&lt;one&gt;&lt;/one&gt;\n&lt;two&gt;&
31
60
  => {"outer"=>"<embedded>\n<one></one>\n<two></two>\n<embedded>\n"}
32
61
  ```
33
62
 
34
- # 2.4.0 (2014-04-19)
63
+ ## 2.4.0 (2014-04-19)
35
64
 
36
65
  * Change: Dropped support for ruby 1.8, rubinius and ree
37
66
 
@@ -39,42 +68,42 @@ Nori.new.parse("<outer>\n&lt;embedded&gt;\n&lt;one&gt;&lt;/one&gt;\n&lt;two&gt;&
39
68
 
40
69
  * Feature: Added `:convert_dashes_to_underscore` option
41
70
 
42
- # 2.3.0 (2013-07-26)
71
+ ## 2.3.0 (2013-07-26)
43
72
 
44
73
  * Change: `Nori#find` now ignores namespace prefixes in Hash keys it is searching through.
45
74
 
46
75
  * Fix: Limited Nokogiri to < 1.6, because v1.6 dropped support for Ruby 1.8.
47
76
 
48
- # 2.2.0 (2013-04-25)
77
+ ## 2.2.0 (2013-04-25)
49
78
 
50
79
  * Feature: [#42](https://github.com/savonrb/nori/pull/42) adds the `:delete_namespace_attributes`
51
80
  option to remove namespace attributes like `xmlns:*` or `xsi:*`.
52
81
 
53
- # 2.1.0 (2013-04-21)
82
+ ## 2.1.0 (2013-04-21)
54
83
 
55
84
  * Feature: Added `Nori.hash_key` and `Nori#find` to work with Hash keys generated by Nori.
56
85
  Original issue: [savonrb/savon#393](https://github.com/savonrb/savon/pull/393)
57
86
 
58
- # 2.0.4 (2013-02-26)
87
+ ## 2.0.4 (2013-02-26)
59
88
 
60
89
  * Fix: [#37](https://github.com/savonrb/nori/issues/37) special characters
61
90
  problem on Ruby 1.9.3-p392.
62
91
 
63
- # 2.0.3 (2013-01-10)
92
+ ## 2.0.3 (2013-01-10)
64
93
 
65
94
  * Fix for remote code execution bug. For more in-depth information, read about the
66
95
  recent [Rails hotfix](https://groups.google.com/forum/?fromgroups=#!topic/rubyonrails-security/61bkgvnSGTQ).
67
96
  Please make sure to upgrade now!
68
97
 
69
- # 2.0.2 (YANKED)
98
+ ## 2.0.2 (YANKED)
70
99
 
71
100
  * Yanked because of a problem with XML that starts with an instruction tag.
72
101
 
73
- # 2.0.1 (YANKED)
102
+ ## 2.0.1 (YANKED)
74
103
 
75
104
  * Yanked because of a problem with XML that starts with an instruction tag.
76
105
 
77
- # 2.0.0 (2012-12-12)
106
+ ## 2.0.0 (2012-12-12)
78
107
 
79
108
  Please make sure to read the updated README for how to use the new version.
80
109
 
@@ -235,3 +264,6 @@ Please make sure to read the updated README for how to use the new version.
235
264
 
236
265
  ## 0.1.0 2009-03-28
237
266
  * Initial release.
267
+
268
+ [2.9.0]: https://github.com/savonrb/nori/compare/v2.8.0...v2.9.0
269
+ [2.8.0]: https://github.com/savonrb/nori/compare/v2.7.1...v2.8.0
data/README.md CHANGED
@@ -2,8 +2,8 @@ Nori
2
2
  ====
3
3
 
4
4
  [![CI](https://github.com/savonrb/nori/actions/workflows/test.yml/badge.svg)](https://github.com/savonrb/nori/actions/workflows/test.yml)
5
- [![Gem Version](https://badge.fury.io/rb/nori.svg)](http://badge.fury.io/rb/nori)
6
- [![Code Climate](https://codeclimate.com/github/savonrb/nori.svg)](https://codeclimate.com/github/savonrb/nori)
5
+ [![Gem Version](https://img.shields.io/gem/v/nori.svg)](https://rubygems.org/gems/nori)
6
+ [![Coverage Status](https://coveralls.io/repos/github/savonrb/nori/badge.svg?branch=main)](https://coveralls.io/github/savonrb/nori?branch=main)
7
7
 
8
8
  Really simple XML parsing ripped from Crack, which ripped it from Merb.
9
9
 
@@ -50,6 +50,11 @@ result["foo"].attributes
50
50
  # => {"bar"=>"baz"}
51
51
  ```
52
52
 
53
+ Because `StringWithAttributes` hides the attributes on an accessor, they are
54
+ lost on a plain `to_json` and callers have to type-check each value. The
55
+ [`serializable`](#serializable) profile returns a plain Hash instead and will
56
+ probably be the default in 3.0.
57
+
53
58
  ## advanced_typecasting
54
59
 
55
60
  Nori can automatically convert string values to `TrueClass`, `FalseClass`, `Time`, `Date`, and `DateTime`:
@@ -107,3 +112,132 @@ parser = Nori.new(:convert_dashes_to_underscores => false)
107
112
  parser.parse('<any-tag>foo bar</any-tag>')
108
113
  # => {"any-tag"=>"foo bar"}
109
114
  ```
115
+
116
+ ## empty_tag_value
117
+
118
+ By default, an empty tag becomes `nil`. The `empty_tag_value` option replaces that value.
119
+
120
+ ```ruby
121
+ Nori.new.parse('<foo/>')
122
+ # => {"foo"=>nil}
123
+
124
+ Nori.new(:empty_tag_value => "").parse('<foo/>')
125
+ # => {"foo"=>""}
126
+ ```
127
+
128
+ The option does not apply to empty tags with attributes.
129
+ They become a hash of their prefixed attributes instead.
130
+
131
+ ```ruby
132
+ Nori.new(:empty_tag_value => "").parse('<foo bar="baz"/>')
133
+ # => {"foo"=>{"@bar"=>"baz"}}
134
+ ```
135
+
136
+ ## consistent_empty_tags
137
+
138
+ With `consistent_empty_tags`, every empty tag (with or without attributes) becomes the `empty_tag_value`.
139
+
140
+ ```ruby
141
+ Nori.new(:consistent_empty_tags => true).parse('<foo bar="baz"/>')
142
+ # => {"foo"=>nil}
143
+ ```
144
+
145
+ A string `empty_tag_value` keeps the attributes accessible on the value,
146
+ and an explicit `xsi:nil="true"` always becomes `nil`.
147
+
148
+ ```ruby
149
+ parser = Nori.new(:consistent_empty_tags => true, :empty_tag_value => "")
150
+
151
+ result = parser.parse('<foo bar="baz"/>')
152
+ # => {"foo"=>""}
153
+
154
+ result["foo"].attributes
155
+ # => {"bar"=>"baz"}
156
+
157
+ parser.parse('<foo xsi:nil="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/>')
158
+ # => {"foo"=>nil}
159
+ ```
160
+
161
+ ## standards
162
+
163
+ `standards` is a profile that turns on Nori's spec-correct parsing as a group,
164
+ rather than one flag at a time. It is opt-in on the 2.x line and becomes the
165
+ default in Nori 3.0.
166
+
167
+ ```ruby
168
+ Nori.new(:standards => true)
169
+ ```
170
+
171
+ Members of the profile:
172
+
173
+ - **xml:space honoring** ([XML 1.0 §2.10](https://www.w3.org/TR/xml/#sec-white-space)).
174
+ Whitespace-only text under `xml:space="preserve"` is kept instead of stripped.
175
+ The nearest ancestor that sets `xml:space` wins, and `xml:space="default"`
176
+ resets to the stripping behavior.
177
+
178
+ ```ruby
179
+ Nori.new(:standards => true).parse('<name xml:space="preserve"> </name>')
180
+ # => {"name"=>" "}
181
+ ```
182
+
183
+ - **the XML string-value model for empty elements.** The profile implies
184
+ `consistent_empty_tags: true` with `empty_tag_value: ""`. Both remain
185
+ overridable, so passing either option explicitly wins over the profile.
186
+
187
+ ```ruby
188
+ Nori.new(:standards => true).parse('<foo bar="baz"/>')
189
+ # => {"foo"=>""}
190
+ ```
191
+
192
+ - **no schema-less typing.** Without a schema, character data is just text, so
193
+ the profile implies `advanced_typecasting: false` (overridable) and ignores
194
+ the bare `type=` and `nil=` attribute conventions that Nori inherited from
195
+ Rails' `Hash.from_xml`. A `type="integer"` no longer casts, a `type="array"`
196
+ no longer folds children into an Array, and both stay visible as ordinary
197
+ attributes. Only the prefixed `xsi:nil="true"` still marks an element nil,
198
+ because that convention comes from XML Schema Instance itself.
199
+
200
+ ```ruby
201
+ Nori.new(:standards => true).parse('<id type="integer">123</id>')
202
+ # => {"id"=>"123"} (with "type" accessible via #attributes)
203
+ ```
204
+
205
+ ## serializable
206
+
207
+ `serializable` is a profile that makes Nori return plain, directly-serializable
208
+ data with no custom value classes. It is opt-in on the 2.x line and will
209
+ probably become the default in 3.0.
210
+
211
+ ```ruby
212
+ Nori.new(:serializable => true)
213
+ ```
214
+
215
+ Members of the profile:
216
+
217
+ - **text nodes with attributes become a Hash.** A tag with both text content
218
+ and attributes maps to the XML JSON convention (`{"#text" => content}` merged
219
+ with the `@`-prefixed attributes) instead of a `Nori::StringWithAttributes`.
220
+ This is the same `@`-keyed shape element nodes already use, so the attributes
221
+ survive `to_json`, `to_yaml`, and `Marshal`. A text node without attributes
222
+ stays a plain `String`.
223
+
224
+ ```ruby
225
+ Nori.new(:serializable => true).parse('<foo bar="baz">Content</foo>')
226
+ # => {"foo"=>{"#text"=>"Content", "@bar"=>"baz"}}
227
+
228
+ Nori.new(:serializable => true).parse('<foo>Content</foo>')
229
+ # => {"foo"=>"Content"}
230
+ ```
231
+
232
+ - **`type="file"` nodes are not decoded.** Without the profile, a node carrying
233
+ `type="file"` is base64-decoded into a `Nori::StringIOFile` with its filename
234
+ and content type. The profile skips that. The node folds into text and
235
+ attributes like any other, leaving the base64 content as a plain `String` for
236
+ the caller to decode. This `type=` decoding is a Rails/ActiveSupport
237
+ `Hash.from_xml` convention Nori inherited through crack and merb, not part of
238
+ any XML specification, so the plain-data profile leaves it behind.
239
+
240
+ ```ruby
241
+ Nori.new(:serializable => true).parse('<doc type="file" name="x.pdf">aGVsbG8=</doc>')
242
+ # => {"doc"=>{"#text"=>"aGVsbG8=", "@type"=>"file", "@name"=>"x.pdf"}}
243
+ ```
@@ -9,23 +9,44 @@ class Nori
9
9
  module Nokogiri
10
10
 
11
11
  class Document < ::Nokogiri::XML::SAX::Document
12
+ # Marks a text node that came from a CDATA section so the whitespace
13
+ # stripping in {#end_element} and {#characters} leaves it alone.
14
+ # A CDATA section is the author's explicit literal-data marker
15
+ # (XML 1.0 §2.7), so its content is real even when it is only
16
+ # whitespace and is never trimmed. The marker never reaches the
17
+ # output because {XMLUtilityNode#inner_html} joins children into
18
+ # a plain string.
19
+ class CDataText < ::String; end
20
+
12
21
  attr_accessor :options
13
22
 
14
23
  def stack
15
24
  @stack ||= []
16
25
  end
17
26
 
27
+ # Tracks the xml:space value in effect for each open element so
28
+ # {#preserve_whitespace?} can honor XML 1.0 §2.10 inheritance.
29
+ def space_stack
30
+ @space_stack ||= []
31
+ end
32
+
18
33
  def start_element(name, attrs = [])
19
- stack.push Nori::XMLUtilityNode.new(options, name, Hash[*attrs.flatten])
34
+ attributes = Hash[*attrs.flatten]
35
+ space_stack.push xml_space(attributes)
36
+ stack.push Nori::XMLUtilityNode.new(options, name, attributes)
20
37
  end
21
38
 
22
39
  # To keep backward behaviour compatibility
23
- # delete last child if it is a space-only text node
40
+ # delete last child if it is a space-only text node.
41
+ # CDATA content is literal and is never stripped, and under
42
+ # xml:space="preserve" whitespace-only text is kept too.
24
43
  def end_element(name)
44
+ preserve = preserve_whitespace?
45
+ space_stack.pop
25
46
  if stack.size > 1
26
47
  last = stack.pop
27
48
  maybe_string = last.children.last
28
- if maybe_string.is_a?(String) and maybe_string.strip.empty?
49
+ if !preserve and maybe_string.is_a?(String) and !maybe_string.is_a?(CDataText) and maybe_string.strip.empty?
29
50
  last.children.pop
30
51
  end
31
52
  stack.last.add_node last
@@ -34,15 +55,41 @@ class Nori
34
55
 
35
56
  # If this node is a successive character then add it as is.
36
57
  # First child being a space-only text node will not be added
37
- # because there is no previous characters.
58
+ # because there is no previous characters, unless xml:space="preserve"
59
+ # is in effect.
38
60
  def characters(string)
39
61
  last = stack.last
40
- if last and last.children.last.is_a?(String) or string.strip.size > 0
62
+ if last and (preserve_whitespace? or last.children.last.is_a?(String) or string.strip.size > 0)
41
63
  last.add_node(string)
42
64
  end
43
65
  end
44
66
 
45
- alias cdata_block characters
67
+ # Adds the CDATA section content verbatim.
68
+ def cdata_block(string)
69
+ last = stack.last
70
+ last.add_node(CDataText.new(string)) if last
71
+ end
72
+
73
+ private
74
+
75
+ # The xml:space value for an element. Its own declaration if it has one,
76
+ # otherwise the value inherited from the nearest ancestor.
77
+ # Defaults to "default". See XML 1.0 §2.10.
78
+ #
79
+ # @param attributes [Hash{String => String}] the element's attributes
80
+ # @return [String] "preserve", "default", or an inherited value
81
+ def xml_space(attributes)
82
+ attributes["xml:space"] || space_stack.last || "default"
83
+ end
84
+
85
+ # Whether whitespace-only text must be kept for the current element.
86
+ # Only the +:standards+ profile honors xml:space. Otherwise
87
+ # insignificant whitespace is stripped as before.
88
+ #
89
+ # @return [Boolean]
90
+ def preserve_whitespace?
91
+ options[:standards] && space_stack.last == "preserve"
92
+ end
46
93
 
47
94
  end
48
95
 
@@ -12,6 +12,9 @@ class Nori
12
12
 
13
13
  def self.parse(xml, options)
14
14
  stack = []
15
+ # Tracks the xml:space value in effect for each open element so
16
+ # whitespace-only text can be kept under xml:space="preserve".
17
+ space_stack = []
15
18
  parser = ::REXML::Parsers::BaseParser.new(xml)
16
19
 
17
20
  while true
@@ -23,21 +26,47 @@ class Nori
23
26
  when :end_doctype, :start_doctype
24
27
  # do nothing
25
28
  when :start_element
29
+ space_stack.push xml_space(event[2], space_stack)
26
30
  stack.push Nori::XMLUtilityNode.new(options, event[1], event[2])
27
31
  when :end_element
32
+ space_stack.pop
28
33
  if stack.size > 1
29
34
  temp = stack.pop
30
35
  stack.last.add_node(temp)
31
36
  end
32
37
  when :text
33
- stack.last.add_node(event[1]) unless event[1].strip.length == 0 || stack.empty?
38
+ # Whitespace-only text is insignificant and stripped, unless
39
+ # xml:space="preserve" is in effect under the :standards profile.
40
+ keep = preserve_whitespace?(options, space_stack) || event[1].strip.length > 0
41
+ stack.last.add_node(event[1]) if keep && !stack.empty?
34
42
  when :cdata
35
- stack.last.add_node(raw_data[1]) unless raw_data[1].strip.length == 0 || stack.empty?
43
+ # CDATA is the author's explicit literal-data marker (XML 1.0 §2.7),
44
+ # so whitespace-only CDATA is kept rather than stripped like text.
45
+ stack.last.add_node(raw_data[1]) unless stack.empty?
36
46
  end
37
47
  end
38
48
  stack.length > 0 ? stack.pop.to_hash : {}
39
49
  end
40
50
 
51
+ # The xml:space value for an element: its own declaration if it has
52
+ # one, otherwise the value inherited from the nearest ancestor.
53
+ # Defaults to "default". See XML 1.0 §2.10.
54
+ #
55
+ # @param attributes [Hash{String => String}] the element's attributes
56
+ # @param space_stack [Array<String>] the ancestor xml:space values
57
+ # @return [String] "preserve", "default", or an inherited value
58
+ def self.xml_space(attributes, space_stack)
59
+ (attributes && attributes["xml:space"]) || space_stack.last || "default"
60
+ end
61
+
62
+ # Whether whitespace-only text must be kept for the current element.
63
+ # Only the +:standards+ profile honors xml:space.
64
+ #
65
+ # @return [Boolean]
66
+ def self.preserve_whitespace?(options, space_stack)
67
+ options[:standards] && space_stack.last == "preserve"
68
+ end
69
+
41
70
  def self.unnormalize(event)
42
71
  event.map do |el|
43
72
  if el.is_a?(String)
@@ -1,3 +1,5 @@
1
+ require "stringio"
2
+
1
3
  class Nori
2
4
  class StringIOFile < StringIO
3
5
 
data/lib/nori/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  class Nori
2
- VERSION = '2.7.1'
2
+ VERSION = '2.9.0'
3
3
  end