nokogiri-xml-range 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,124 @@
1
+ module Nokogiri::XML
2
+ class Range
3
+ module Refinement
4
+ refine Node do
5
+ def ancestors_to(node)
6
+ nodes = NodeSet.new(document)
7
+ current = self
8
+ root = document.root
9
+ nodes << current
10
+ until current == node or current == root
11
+ current = current.parent
12
+ nodes << current
13
+ end
14
+ return unless current == node
15
+ nodes
16
+ end
17
+
18
+ def length
19
+ case type
20
+ when Node::DOCUMENT_TYPE_NODE
21
+ 0
22
+ when Node::TEXT_NODE, Node::CDATA_SECTION_NODE, Node::PI_NODE, Node::COMMENT_NODE
23
+ content.encode('UTF-16LE').bytesize / 2
24
+ else
25
+ children.length
26
+ end
27
+ end
28
+
29
+ def following_node
30
+ child || next_sibling
31
+ end
32
+
33
+ def preceding_node
34
+ previous_sibling || parent
35
+ end
36
+
37
+ def inclusive_ancestors
38
+ [self] + ancestors
39
+ end
40
+
41
+ def inclusive_ancestor?(node)
42
+ inclusive_ancestors.include? node
43
+ end
44
+
45
+ def host_including_inclusive_ancestor?(node)
46
+ return true if inclusive_ancestor? node
47
+ root_node = ancestors.last
48
+ root_node.fragment? and
49
+ root_node.host.host_including_inclusive_ancestor?(node)
50
+ end
51
+
52
+ def replace_all_with(node)
53
+ document.adopt node if node
54
+ children.each &:remove
55
+ if node
56
+ add_child node
57
+ end
58
+ end
59
+
60
+ def replacable?
61
+ kind_of? Nokogiri::XML::Replacable
62
+ end
63
+
64
+ def validate_pre_insertion(parent, child)
65
+ unless [Node::DOCUMENT_TYPE_NODE, Node::DOCUMENT_FRAG_NODE, Node::ELEMENT_NODE].include? parent.type
66
+ raise HierarchyRequestError
67
+ end
68
+ raise HierarchyRequestError if parent.host_including_inclusive_ancestor? self
69
+ raise NotFoundError if child and child.parent != parent
70
+ unless [Node::DOCUMENT_FRAG_NODE, Node::DOCUMENT_TYPE_NODE, Node::ELEMENT_NODE, Node::TEXT_NODE, Node::PI_NODE, Node::COMMENT_NODE].include? type
71
+ raise HierarchyRequestError
72
+ end
73
+ raise HierarchyRequestError if text? && parent.document?
74
+ raise HierarchyRequestError if type == Node::DOCUMENT_TYPE_NODE and !parent.document?
75
+ return unless parent.document?
76
+ case type
77
+ when Node::DOCUMENT_FRAG_NODE
78
+ child_element_count = 0
79
+ children.each do |node|
80
+ raise HierarchyRequestError if node.text?
81
+ child_element_count += 1 if node.element?
82
+ raise HierarchyRequestError if child_element_count > 1
83
+ end
84
+ if child_element_count == 1
85
+ raise HierarchyRequestError if parent.children.any?(&:element?)
86
+ return unless child
87
+ raise HierarchyRequestError if child.type = Node::DOCUMENT_TYPE_NODE
88
+ raise HierarchyRequestError if child.following_node.type == Node::DOCUMENT_TYPE_NODE
89
+ end
90
+ when Node::ELEMENT_NODE
91
+ raise HierarchyRequestError if parent.children.any?(&:element?)
92
+ return unless child
93
+ raise HierarchyRequestError if child.type == Node::DOCUMENT_TYPE_NODE
94
+ raise HierarchyRequestError if child.following_node.type == Node::DOCUMENT_TYPE_NODE
95
+ when Node::DOCUMENT_TYPE_NODE
96
+ raise HierarchyRequestError if parent.children.any? {|node|
97
+ node.type == Node::DOCUMENT_TYPE_NODE
98
+ }
99
+ return unless child
100
+ raise HierarchyRequestError if child.preceding_node.element?
101
+ raise HierarchyRequestError if parent.children.any?(&:element?)
102
+ end
103
+ end
104
+ end
105
+
106
+ refine Document do
107
+ def adopt(node)
108
+ old_document = node.document
109
+ unless node.document == self
110
+ root << node
111
+ node.remove
112
+ end
113
+ if block_given?
114
+ yield node, old_document
115
+ end
116
+ end
117
+ end
118
+
119
+ refine DocumentFragment do
120
+ attr_accessor :host
121
+ end
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,8 @@
1
+ module Nokogiri
2
+ module XML
3
+ class Range
4
+ # nokogiri-xml-range version
5
+ VERSION = "0.1.0"
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,39 @@
1
+ using Nokogiri::XML::Range::Refinement
2
+
3
+ module Nokogiri::XML
4
+ module Replacable
5
+ def replace_data(offset, count, data)
6
+ len = length
7
+ raise IndexSizeError, 'offset is greater than node length' if offset > len
8
+
9
+ count = len - offset if offset + count > len
10
+ encoding = content.encoding
11
+ utf16_content = content.encode('UTF-16LE')
12
+ utf16_data = data.encode('UTF-16LE')
13
+ result = utf16_content.byteslice(0, offset * 2) + utf16_data + utf16_content.byteslice(offset * 2, utf16_content.bytesize)
14
+ delete_offset = offset + utf16_data.bytesize / 2
15
+ result = result.byteslice(0, delete_offset * 2) + result.byteslice((delete_offset + count) * 2, result.bytesize)
16
+
17
+ self.content = result.encode(encoding)
18
+ end
19
+
20
+ def substring_data(offset, count)
21
+ len = length
22
+ raise IndexSizeError, 'offset is greater than node length' if offset > len
23
+
24
+ encoding = content.encoding
25
+ utf16_content = content.encode('UTF-16LE')
26
+
27
+ byte_length = [utf16_content.bytesize - offset * 2, count * 2].min
28
+ utf16_content.byteslice(offset * 2, byte_length).encode(encoding)
29
+ end
30
+ end
31
+
32
+ class CharacterData
33
+ include Replacable
34
+ end
35
+
36
+ class ProcessingInstruction
37
+ include Replacable
38
+ end
39
+ end
@@ -0,0 +1,31 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require File.expand_path('../lib/nokogiri/xml/range/version', __FILE__)
4
+
5
+ Gem::Specification.new do |gem|
6
+ gem.name = "nokogiri-xml-range"
7
+ gem.version = Nokogiri::XML::Range::VERSION
8
+ gem.summary = %q{DOM Range implementation on Nokogiri}
9
+ gem.description = %q{Nokogiri DOM Range Implementatin based on DOM Standard specification.}
10
+ gem.license = "LGPL"
11
+ gem.authors = ["KITAITI Makoto"]
12
+ gem.email = "KitaitiMakoto@gmail.com"
13
+ gem.homepage = "https://rubygems.org/gems/nokogiri-xml-range"
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ['lib']
19
+
20
+ gem.add_runtime_dependency 'nokogiri'
21
+
22
+ gem.add_development_dependency 'test-unit', '~> 3'
23
+ gem.add_development_dependency 'test-unit-notify'
24
+ gem.add_development_dependency 'rubygems-tasks', '~> 0.2'
25
+ gem.add_development_dependency 'yard', '~> 0.8'
26
+ gem.add_development_dependency 'rake'
27
+ gem.add_development_dependency 'bundler'
28
+ gem.add_development_dependency 'pry'
29
+ gem.add_development_dependency 'simplecov'
30
+ gem.add_development_dependency 'coveralls'
31
+ end
@@ -0,0 +1,19 @@
1
+ require 'simplecov'
2
+ require 'coveralls'
3
+ Coveralls.wear!
4
+ SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
5
+ SimpleCov::Formatter::HTMLFormatter,
6
+ Coveralls::SimpleCov::Formatter
7
+ ]
8
+ SimpleCov.start do
9
+ add_filter '/test/'
10
+ end
11
+
12
+ require 'pp'
13
+ require 'rubygems'
14
+ require 'test/unit'
15
+ require 'test/unit/notify'
16
+ require 'pry'
17
+
18
+ class Test::Unit::TestCase
19
+ end
@@ -0,0 +1,65 @@
1
+ require 'helper'
2
+ require 'nokogiri'
3
+ require 'nokogiri/xml/range/refinement'
4
+ require 'nokogiri/xml/replacable'
5
+
6
+ class TestNokogiriXMLRangeRefinement < Test::Unit::TestCase
7
+ using Nokogiri::XML::Range::Refinement
8
+
9
+ def setup
10
+ @doc = Nokogiri.XML(<<EOD)
11
+ <root>
12
+ <parent>
13
+ <child>child 1</child>
14
+ <child>child 2</child>
15
+ </parent>
16
+ </root>
17
+ EOD
18
+ @root = @doc.root
19
+ @parent = @root.search('parent').first
20
+ @child1 = @parent.search('child')[0]
21
+ @child2 = @parent.search('child')[1]
22
+ end
23
+
24
+ data({
25
+ 'root' => [:root, 3],
26
+ 'parent' => [:parent, 5],
27
+ 'child1' => [:child1, 1],
28
+ })
29
+ def test_element_length(data)
30
+ node_name, length = data
31
+ node = instance_variable_get("@#{node_name}")
32
+ assert_equal length, node.length
33
+ end
34
+
35
+ def test_text_length
36
+ assert_equal 7, @child1.children[0].length
37
+ end
38
+
39
+ def test_inclusive_ancestor?
40
+ [@root, @parent, @child1].each do |node|
41
+ assert_true @child1.inclusive_ancestor? node
42
+ end
43
+ assert_false @child1.inclusive_ancestor? @child2
44
+ end
45
+
46
+ def test_host_including_inclusive_ancestor?
47
+ fragment = Nokogiri::XML::DocumentFragment.new(@doc)
48
+ fragment.host = @parent
49
+ child = Nokogiri::XML::Element.new('child', @doc)
50
+ fragment << child
51
+ assert_true child.host_including_inclusive_ancestor? @root
52
+ end
53
+
54
+ def test_adopt
55
+ @doc.adopt @child1
56
+ assert_equal @doc, @child1.document
57
+
58
+ another_doc = Nokogiri.XML('<root><child/></root>')
59
+ elem = another_doc.search('root').first
60
+ @doc.adopt elem
61
+ assert_equal @doc, elem.document
62
+ assert_equal @doc, elem.child.document
63
+ assert_equal Nokogiri.XML('').to_s, another_doc.to_s
64
+ end
65
+ end
@@ -0,0 +1,316 @@
1
+ # coding: utf-8
2
+ require 'helper'
3
+ require 'nokogiri/xml/range'
4
+
5
+ class TestNokogiriXMLRange < Test::Unit::TestCase
6
+ def setup
7
+ @doc = Nokogiri.XML(<<EOX)
8
+ <root>
9
+ <parent>
10
+ <child>child 1</child>
11
+ <child>child 2</child>
12
+ </parent>
13
+ </root>
14
+ EOX
15
+ @root = @doc.search('root')[0]
16
+ @parent = @doc.search('parent')[0]
17
+ @child1 = @doc.search('child')[0]
18
+ @child2 = @doc.search('child')[1]
19
+ end
20
+
21
+ def test_version
22
+ version = Nokogiri::XML::Range.const_get('VERSION')
23
+
24
+ assert !version.empty?, 'should have a VERSION constant'
25
+ end
26
+
27
+ def test_compare_points
28
+ assert_equal -1, Nokogiri::XML::Range.compare_points(@child1, 0, @child1, 1)
29
+ assert_equal 1, Nokogiri::XML::Range.compare_points(@child2, 0, @child1, 0)
30
+ assert_equal 0, Nokogiri::XML::Range.compare_points(@child1, 1, @child1, 1)
31
+ assert_equal -1, Nokogiri::XML::Range.compare_points(@doc.root, 0, @child1, 0)
32
+ assert_equal 1, Nokogiri::XML::Range.compare_points(@doc.root, 2, @child1, 1)
33
+ assert_equal -1, Nokogiri::XML::Range.compare_points(@child1, 0, @child2, 3)
34
+ end
35
+
36
+ def test_contain_node?
37
+ assert_true Nokogiri::XML::Range.new(@doc.root, 0, @child2, 0).contain_node?(@child1)
38
+ assert_false Nokogiri::XML::Range.new(@doc.root, 0, @child1, 1).contain_node?(@child2)
39
+ end
40
+
41
+ def test_partially_contain_node?
42
+ assert_false Nokogiri::XML::Range.new(@root, 0, @child1, 0).partially_contain_node?(@child2)
43
+ assert_false Nokogiri::XML::Range.new(@child1, 0, @child2, 0).partially_contain_node?(@root)
44
+ assert_false Nokogiri::XML::Range.new(@child1, 0, @child1, 1).partially_contain_node?(@child2)
45
+ assert_true Nokogiri::XML::Range.new(@child1.children[0], 0, @child2, 0).partially_contain_node?(@child1)
46
+ end
47
+
48
+ def test_common_ancestor_container
49
+ assert_equal @parent, Nokogiri::XML::Range.new(@child1, 0, @child2.children[0], 0).common_ancestor_container
50
+ end
51
+
52
+ def test_set_boundary_point
53
+ range1 = Nokogiri::XML::Range.new(@child1, 0, @child2, 0)
54
+ range2 = range1.dup
55
+
56
+ range1.set_start @parent, 1
57
+ assert_equal [@parent, 1], range1.start_point
58
+
59
+ range2.set_end @parent, 1
60
+ assert_equal [@parent, 1], range2.end_point
61
+ assert_equal [@parent, 1], range2.start_point
62
+ end
63
+
64
+ def test_set_start_before
65
+ range = Nokogiri::XML::Range.new(@child2, 0, @child2, 1)
66
+ range.set_start_before(@child1)
67
+
68
+ assert_equal [@parent, 1], range.start_point
69
+ end
70
+
71
+ def test_set_start_after
72
+ range = Nokogiri::XML::Range.new(@child2, 0, @child2, 1)
73
+ range.set_start_after(@child1)
74
+
75
+ assert_equal [@parent, 2], range.start_point
76
+ end
77
+
78
+ def test_set_end_before
79
+ range = Nokogiri::XML::Range.new(@child1, 0, @child2, 1)
80
+ range.set_end_before(@parent)
81
+
82
+ assert_equal [@root, 1], range.end_point
83
+ end
84
+
85
+ def test_set_end_after
86
+ range = Nokogiri::XML::Range.new(@child1, 0, @child2, 1)
87
+ range.set_end_after(@parent)
88
+
89
+ assert_equal [@root, 2], range.end_point
90
+ end
91
+
92
+ def test_collapse
93
+ range = Nokogiri::XML::Range.new(@child1, 0, @child2, 1)
94
+ range.collapse!
95
+
96
+ assert_equal [@child2, 1], range.start_point
97
+ assert_equal [@child2, 1], range.end_point
98
+ end
99
+
100
+ def test_select_node
101
+ range = Nokogiri::XML::Range.new(@child1, 0, @child2, 1)
102
+ range.select_node(@parent)
103
+
104
+ assert_equal [@root, 1], range.start_point
105
+ assert_equal [@root, 2], range.end_point
106
+ end
107
+
108
+ def test_select_node_contents
109
+ range = Nokogiri::XML::Range.new(@child1, 0, @child2, 1)
110
+ range.select_node_contents @child2
111
+
112
+ assert_equal [@child2, 0], range.start_point
113
+ assert_equal [@child2, 1], range.end_point
114
+ end
115
+
116
+ data(
117
+ {
118
+ 'START_TO_START' => [-1, Nokogiri::XML::Range::START_TO_START],
119
+ 'START_TO_END' => [1, Nokogiri::XML::Range::START_TO_END],
120
+ 'END_TO_END' => [1, Nokogiri::XML::Range::END_TO_END],
121
+ 'END_TO_START' => [-1, Nokogiri::XML::Range::END_TO_START]
122
+ }
123
+ )
124
+ def test_compare_boundary_points(data)
125
+ comparison, how = data
126
+
127
+ range1 = Nokogiri::XML::Range.new(@parent, 0, @child2, 1)
128
+ range2 = Nokogiri::XML::Range.new(@child1, 0, @parent, 3)
129
+
130
+ assert_equal comparison, range1.compare_boundary_points(how, range2)
131
+ end
132
+
133
+ def test_delete_contents_child_elements
134
+ range = Nokogiri::XML::Range.new(@child1, 0, @parent, 4)
135
+ range.delete_contents
136
+
137
+ assert_equal Nokogiri.XML(<<EXPECTED).to_s, @doc.to_s
138
+ <root>
139
+ <parent>
140
+ <child/>
141
+ </parent>
142
+ </root>
143
+ EXPECTED
144
+ end
145
+
146
+ def test_delete_contents_text
147
+ range = Nokogiri::XML::Range.new(@child1.children[0], 1, @child1.children[0], 5)
148
+ range.delete_contents
149
+
150
+ assert_equal Nokogiri.XML(<<EXPECTED).to_s, @doc.to_s
151
+ <root>
152
+ <parent>
153
+ <child>c 1</child>
154
+ <child>child 2</child>
155
+ </parent>
156
+ </root>
157
+ EXPECTED
158
+ end
159
+
160
+ def test_extract_contents_child_elements
161
+ range = Nokogiri::XML::Range.new(@child1, 0, @parent, 4)
162
+ range.extract_contents
163
+
164
+ assert_equal Nokogiri.XML(<<EXPECTED).to_s, @doc.to_s
165
+ <root>
166
+ <parent>
167
+ <child/>
168
+ </parent>
169
+ </root>
170
+ EXPECTED
171
+ end
172
+
173
+ def test_extract_contents_text
174
+ range = Nokogiri::XML::Range.new(@child1.children[0], 1, @child1.children[0], 5)
175
+ range.extract_contents
176
+
177
+ assert_equal Nokogiri.XML(<<EXPECTED).to_s, @doc.to_s
178
+ <root>
179
+ <parent>
180
+ <child>c 1</child>
181
+ <child>child 2</child>
182
+ </parent>
183
+ </root>
184
+ EXPECTED
185
+ end
186
+
187
+ def test_extract_contents_from_elements
188
+ range = Nokogiri::XML::Range.new(@child1.child, 1, @child2.child, 5)
189
+ extracted = range.extract_contents
190
+
191
+ assert_equal Nokogiri.XML(<<REMAINED).to_s, @doc.to_s
192
+ <root>
193
+ <parent>
194
+ <child>c</child><child> 2</child>
195
+ </parent>
196
+ </root>
197
+ REMAINED
198
+ assert_equal <<EXTRACTED.chomp, extracted.to_s.chomp
199
+ <child>hild 1</child>
200
+ <child>child</child>
201
+ EXTRACTED
202
+ end
203
+
204
+ def test_extract_contents_from_text
205
+ range = Nokogiri::XML::Range.new(@child1.child, 1, @child1.child, 5)
206
+ extracted = range.extract_contents
207
+
208
+ assert_equal Nokogiri.XML(<<REMAINED).to_s, @doc.to_s
209
+ <root>
210
+ <parent>
211
+ <child>c 1</child>
212
+ <child>child 2</child>
213
+ </parent>
214
+ </root>
215
+ REMAINED
216
+ assert_equal 'hild', extracted.to_s
217
+ end
218
+
219
+ def test_clone_contents_from_elements
220
+ range = Nokogiri::XML::Range.new(@child1.child, 1, @child2.child, 5)
221
+ cloned = range.clone_contents
222
+
223
+ assert_equal <<CLONED.chomp, cloned.to_s.chomp
224
+ <child>hild 1</child>
225
+ <child>child</child>
226
+ CLONED
227
+ end
228
+
229
+ def test_clone_contents_from_text
230
+ range = Nokogiri::XML::Range.new(@child1.child, 1, @child1.child, 5)
231
+ cloned = range.clone_contents
232
+
233
+ assert_equal 'hild', cloned.to_s
234
+ end
235
+
236
+ def test_insert_node_to_element
237
+ range = Nokogiri::XML::Range.new(@parent, 1, @parent, 3)
238
+ grand_child = Nokogiri::XML::Element.new('grand-child', @doc)
239
+ range.insert_node grand_child
240
+
241
+ assert_equal Nokogiri.XML(<<EOX).to_s, @doc.to_s
242
+ <root>
243
+ <parent>
244
+ <grand-child/><child>child 1</child>
245
+ <child>child 2</child>
246
+ </parent>
247
+ </root>
248
+ EOX
249
+ end
250
+
251
+ def test_insert_node_to_text
252
+ range = Nokogiri::XML::Range.new(@child1.child, 5, @child1.child, 8)
253
+ text = Nokogiri::XML::Text.new('inserted', @doc)
254
+ range.insert_node text
255
+
256
+ assert_equal Nokogiri.XML(<<EOX).to_s, @doc.to_s
257
+ <root>
258
+ <parent>
259
+ <child>childinserted 1</child>
260
+ <child>child 2</child>
261
+ </parent>
262
+ </root>
263
+ EOX
264
+ end
265
+
266
+ def test_surround_contents
267
+ range = Nokogiri::XML::Range.new(@child1.child, 6, @child1.child, 7)
268
+ number = Nokogiri::XML::Element.new('number', @doc)
269
+ range.surround_contents number
270
+
271
+ assert_equal Nokogiri.XML(<<EOX).to_s, @doc.to_s
272
+ <root>
273
+ <parent>
274
+ <child>child <number>1</number></child>
275
+ <child>child 2</child>
276
+ </parent>
277
+ </root>
278
+ EOX
279
+ end
280
+
281
+ def test_point_in_range?
282
+ range = Nokogiri::XML::Range.new(@child1.child, 2, @parent, 4)
283
+
284
+ assert_true range.point_in_range? @child2, 0
285
+ assert_true range.point_in_range? @child1.child, 3
286
+ end
287
+
288
+ def test_compare_point
289
+ range = Nokogiri::XML::Range.new(@child1.child, 2, @parent, 4)
290
+
291
+ assert_equal -1, range.compare_point(@parent, 0)
292
+ assert_equal 0, range.compare_point(@child2, 0)
293
+ assert_equal 1, range.compare_point(@root, 2)
294
+ end
295
+
296
+ def test_intersect_node?
297
+ range = Nokogiri::XML::Range.new(@child1.child, 2, @parent, 4)
298
+
299
+ assert_true range.intersect_node? @child1.child
300
+ end
301
+
302
+ def test_to_s
303
+ assert_equal 'hild', Nokogiri::XML::Range.new(@child1.child, 1, @child1.child, 5).to_s
304
+ assert_equal <<EOS.chomp, Nokogiri::XML::Range.new(@root, 0, @root, 2).to_s
305
+
306
+
307
+ child 1
308
+ child 2
309
+
310
+ EOS
311
+ assert_equal <<EOS.chomp, Nokogiri::XML::Range.new(@parent, 1, @child2.child, 5).to_s
312
+ child 1
313
+ child
314
+ EOS
315
+ end
316
+ end