nokogiri-xml-range 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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