rxerces 0.1.0 → 0.3.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.
data/spec/node_spec.rb ADDED
@@ -0,0 +1,472 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe RXerces::XML::Node do
4
+ let(:xml) do
5
+ <<-XML
6
+ <root>
7
+ <person id="1" name="Alice">
8
+ <age>30</age>
9
+ <city>New York</city>
10
+ </person>
11
+ <person id="2" name="Bob">
12
+ <age>25</age>
13
+ </person>
14
+ </root>
15
+ XML
16
+ end
17
+
18
+ let(:doc) { RXerces::XML::Document.parse(xml) }
19
+ let(:root) { doc.root }
20
+
21
+ describe "#name" do
22
+ it "returns the node name" do
23
+ expect(root.name).to eq('root')
24
+ end
25
+
26
+ it "returns child element names" do
27
+ person = root.children.find { |n| n.is_a?(RXerces::XML::Element) }
28
+ expect(person.name).to eq('person')
29
+ end
30
+ end
31
+
32
+ describe "#text" do
33
+ it "returns text content" do
34
+ person = root.children.find { |n| n.is_a?(RXerces::XML::Element) }
35
+ age = person.children.find { |n| n.name == 'age' }
36
+ expect(age.text.strip).to eq('30')
37
+ end
38
+
39
+ it "returns empty string for nodes without text" do
40
+ expect(root.text).to be_a(String)
41
+ end
42
+ end
43
+
44
+ describe "#content" do
45
+ it "is an alias for text" do
46
+ person = root.children.find { |n| n.is_a?(RXerces::XML::Element) }
47
+ age = person.children.find { |n| n.name == 'age' }
48
+ expect(age.content).to eq(age.text)
49
+ end
50
+ end
51
+
52
+ describe "#text=" do
53
+ it "sets text content" do
54
+ person = root.children.find { |n| n.is_a?(RXerces::XML::Element) }
55
+ age = person.children.find { |n| n.name == 'age' }
56
+ age.text = '35'
57
+ expect(age.text.strip).to eq('35')
58
+ end
59
+ end
60
+
61
+ describe "#[]" do
62
+ it "gets attribute value" do
63
+ person = root.children.find { |n| n.is_a?(RXerces::XML::Element) }
64
+ expect(person['id']).to eq('1')
65
+ expect(person['name']).to eq('Alice')
66
+ end
67
+
68
+ it "returns nil for non-existent attribute" do
69
+ expect(root['nonexistent']).to be_nil
70
+ end
71
+ end
72
+
73
+ describe "#[]=" do
74
+ it "sets attribute value" do
75
+ person = root.children.find { |n| n.is_a?(RXerces::XML::Element) }
76
+ person['id'] = '100'
77
+ expect(person['id']).to eq('100')
78
+ end
79
+
80
+ it "creates new attribute" do
81
+ person = root.children.find { |n| n.is_a?(RXerces::XML::Element) }
82
+ person['email'] = 'alice@example.com'
83
+ expect(person['email']).to eq('alice@example.com')
84
+ end
85
+ end
86
+
87
+ describe "#children" do
88
+ it "returns an array of child nodes" do
89
+ children = root.children
90
+ expect(children).to be_an(Array)
91
+ expect(children.length).to be > 0
92
+ end
93
+
94
+ it "includes element nodes" do
95
+ person_nodes = root.children.select { |n| n.is_a?(RXerces::XML::Element) }
96
+ expect(person_nodes.length).to eq(2)
97
+ end
98
+
99
+ it "includes text nodes" do
100
+ text_nodes = root.children.select { |n| n.is_a?(RXerces::XML::Text) }
101
+ expect(text_nodes.length).to be > 0
102
+ end
103
+ end
104
+
105
+ describe "#parent" do
106
+ it "returns the parent node" do
107
+ person = root.children.find { |n| n.is_a?(RXerces::XML::Element) }
108
+ parent = person.parent
109
+ expect(parent).to be_a(RXerces::XML::Element)
110
+ expect(parent.name).to eq('root')
111
+ end
112
+
113
+ it "returns the parent for nested elements" do
114
+ person = root.children.find { |n| n.is_a?(RXerces::XML::Element) }
115
+ age = person.children.find { |n| n.name == 'age' }
116
+ parent = age.parent
117
+ expect(parent).to be_a(RXerces::XML::Element)
118
+ expect(parent.name).to eq('person')
119
+ expect(parent['id']).to eq('1')
120
+ end
121
+
122
+ it "returns the document for root element" do
123
+ parent = root.parent
124
+ expect(parent).not_to be_nil
125
+ expect(parent.name).to eq('#document')
126
+ end
127
+
128
+ it "returns nil for nodes without parent" do
129
+ # This is edge case - all nodes in a parsed document have parents
130
+ # but we test the safety of the method
131
+ expect(root.parent).not_to be_nil
132
+ end
133
+ end
134
+
135
+ describe "#attributes" do
136
+ it "returns a hash of attributes" do
137
+ person = root.children.find { |n| n.is_a?(RXerces::XML::Element) }
138
+ attrs = person.attributes
139
+ expect(attrs).to be_a(Hash)
140
+ expect(attrs['id']).to eq('1')
141
+ expect(attrs['name']).to eq('Alice')
142
+ end
143
+
144
+ it "returns all attributes" do
145
+ person = root.children.find { |n| n.is_a?(RXerces::XML::Element) }
146
+ attrs = person.attributes
147
+ expect(attrs.keys).to match_array(['id', 'name'])
148
+ end
149
+
150
+ it "returns empty hash for elements without attributes" do
151
+ person = root.children.find { |n| n.is_a?(RXerces::XML::Element) }
152
+ age = person.children.find { |n| n.name == 'age' }
153
+ attrs = age.attributes
154
+ expect(attrs).to be_a(Hash)
155
+ expect(attrs).to be_empty
156
+ end
157
+
158
+ it "returns empty hash for text nodes" do
159
+ person = root.children.find { |n| n.is_a?(RXerces::XML::Element) }
160
+ text_node = person.children.find { |n| n.is_a?(RXerces::XML::Text) }
161
+ attrs = text_node.attributes
162
+ expect(attrs).to be_a(Hash)
163
+ expect(attrs).to be_empty
164
+ end
165
+ end
166
+
167
+ describe "#next_sibling" do
168
+ it "returns the next sibling node" do
169
+ people = root.children.select { |n| n.is_a?(RXerces::XML::Element) }
170
+ first_person = people[0]
171
+ next_node = first_person.next_sibling
172
+
173
+ # May be a text node (whitespace) between elements
174
+ # Skip to next element if needed
175
+ while next_node && next_node.is_a?(RXerces::XML::Text)
176
+ next_node = next_node.next_sibling
177
+ end
178
+
179
+ expect(next_node).to be_a(RXerces::XML::Element)
180
+ expect(next_node.name).to eq('person')
181
+ expect(next_node['id']).to eq('2')
182
+ end
183
+
184
+ it "returns nil for last sibling" do
185
+ people = root.children.select { |n| n.is_a?(RXerces::XML::Element) }
186
+ last_person = people.last
187
+
188
+ # Navigate past any trailing whitespace
189
+ next_node = last_person.next_sibling
190
+ while next_node && next_node.is_a?(RXerces::XML::Text)
191
+ next_node = next_node.next_sibling
192
+ end
193
+
194
+ expect(next_node).to be_nil
195
+ end
196
+
197
+ it "can navigate through all siblings" do
198
+ first_element = root.children.find { |n| n.is_a?(RXerces::XML::Element) }
199
+ siblings = []
200
+ current = first_element
201
+
202
+ while current
203
+ siblings << current if current.is_a?(RXerces::XML::Element)
204
+ current = current.next_sibling
205
+ end
206
+
207
+ expect(siblings.length).to eq(2)
208
+ expect(siblings[0]['id']).to eq('1')
209
+ expect(siblings[1]['id']).to eq('2')
210
+ end
211
+ end
212
+
213
+ describe "#previous_sibling" do
214
+ it "returns the previous sibling node" do
215
+ people = root.children.select { |n| n.is_a?(RXerces::XML::Element) }
216
+ second_person = people[1]
217
+ prev_node = second_person.previous_sibling
218
+
219
+ # May be a text node (whitespace) between elements
220
+ # Skip to previous element if needed
221
+ while prev_node && prev_node.is_a?(RXerces::XML::Text)
222
+ prev_node = prev_node.previous_sibling
223
+ end
224
+
225
+ expect(prev_node).to be_a(RXerces::XML::Element)
226
+ expect(prev_node.name).to eq('person')
227
+ expect(prev_node['id']).to eq('1')
228
+ end
229
+
230
+ it "returns nil for first sibling" do
231
+ first_element = root.children.find { |n| n.is_a?(RXerces::XML::Element) }
232
+
233
+ # Navigate past any leading whitespace
234
+ prev_node = first_element.previous_sibling
235
+ while prev_node && prev_node.is_a?(RXerces::XML::Text)
236
+ prev_node = prev_node.previous_sibling
237
+ end
238
+
239
+ expect(prev_node).to be_nil
240
+ end
241
+
242
+ it "can navigate backward through all siblings" do
243
+ last_element = root.children.select { |n| n.is_a?(RXerces::XML::Element) }.last
244
+ siblings = []
245
+ current = last_element
246
+
247
+ while current
248
+ siblings.unshift(current) if current.is_a?(RXerces::XML::Element)
249
+ current = current.previous_sibling
250
+ end
251
+
252
+ expect(siblings.length).to eq(2)
253
+ expect(siblings[0]['id']).to eq('1')
254
+ expect(siblings[1]['id']).to eq('2')
255
+ end
256
+ end
257
+
258
+ describe "#add_child" do
259
+ it "adds a text node from a string" do
260
+ person = root.children.find { |n| n.is_a?(RXerces::XML::Element) }
261
+ initial_count = person.children.length
262
+
263
+ person.add_child("New text content")
264
+
265
+ expect(person.children.length).to eq(initial_count + 1)
266
+ last_child = person.children.last
267
+ expect(last_child).to be_a(RXerces::XML::Text)
268
+ expect(last_child.text).to eq("New text content")
269
+ end
270
+
271
+ it "adds a new element to another element" do
272
+ # Create a simple document to test with
273
+ simple_xml = '<root><parent></parent></root>'
274
+ simple_doc = RXerces::XML::Document.parse(simple_xml)
275
+ parent = simple_doc.root.children.find { |n| n.is_a?(RXerces::XML::Element) }
276
+
277
+ # Add text child
278
+ parent.add_child("Hello World")
279
+
280
+ expect(parent.children.length).to be > 0
281
+ text_child = parent.children.find { |n| n.is_a?(RXerces::XML::Text) }
282
+ expect(text_child.text).to eq("Hello World")
283
+ end
284
+
285
+ it "allows building a document structure" do
286
+ simple_xml = '<root></root>'
287
+ simple_doc = RXerces::XML::Document.parse(simple_xml)
288
+ root = simple_doc.root
289
+
290
+ # Add multiple children
291
+ root.add_child("First text")
292
+ root.add_child("Second text")
293
+
294
+ text_nodes = root.children.select { |n| n.is_a?(RXerces::XML::Text) }
295
+ expect(text_nodes.length).to eq(2)
296
+ expect(text_nodes[0].text).to eq("First text")
297
+ expect(text_nodes[1].text).to eq("Second text")
298
+ end
299
+
300
+ it "modifies the document" do
301
+ simple_xml = '<item></item>'
302
+ simple_doc = RXerces::XML::Document.parse(simple_xml)
303
+ item = simple_doc.root
304
+
305
+ item.add_child("Content")
306
+
307
+ xml_output = simple_doc.to_s
308
+ expect(xml_output).to include("Content")
309
+ end
310
+ end
311
+
312
+ describe "#remove" do
313
+ it "removes a node from its parent" do
314
+ person = root.children.find { |n| n.is_a?(RXerces::XML::Element) }
315
+ initial_count = root.children.select { |n| n.is_a?(RXerces::XML::Element) }.length
316
+
317
+ person.remove
318
+
319
+ remaining = root.children.select { |n| n.is_a?(RXerces::XML::Element) }
320
+ expect(remaining.length).to eq(initial_count - 1)
321
+ end
322
+
323
+ it "removes a child element from parent" do
324
+ person = root.children.find { |n| n.is_a?(RXerces::XML::Element) }
325
+ age = person.children.find { |n| n.name == 'age' }
326
+ initial_count = person.children.select { |n| n.is_a?(RXerces::XML::Element) }.length
327
+
328
+ age.remove
329
+
330
+ remaining = person.children.select { |n| n.is_a?(RXerces::XML::Element) }
331
+ expect(remaining.length).to eq(initial_count - 1)
332
+ expect(person.children.find { |n| n.name == 'age' }).to be_nil
333
+ end
334
+
335
+ it "modifies the document" do
336
+ simple_xml = '<root><item>Remove me</item><keep>Keep me</keep></root>'
337
+ simple_doc = RXerces::XML::Document.parse(simple_xml)
338
+ item = simple_doc.xpath('//item').first
339
+
340
+ item.remove
341
+
342
+ xml_output = simple_doc.to_s
343
+ expect(xml_output).not_to include("Remove me")
344
+ expect(xml_output).to include("Keep me")
345
+ end
346
+
347
+ it "raises error when node has no parent" do
348
+ # The root element's parent is the document, so this should work
349
+ # We'll test with a document node instead
350
+ expect {
351
+ root.parent.remove
352
+ }.to raise_error(RuntimeError, /no parent/)
353
+ end
354
+ end
355
+
356
+ describe "#unlink" do
357
+ it "is an alias for remove" do
358
+ simple_xml = '<root><item>Test</item></root>'
359
+ simple_doc = RXerces::XML::Document.parse(simple_xml)
360
+ item = simple_doc.xpath('//item').first
361
+
362
+ expect(item).to respond_to(:unlink)
363
+ item.unlink
364
+
365
+ xml_output = simple_doc.to_s
366
+ expect(xml_output).not_to include("Test")
367
+ end
368
+ end
369
+
370
+ describe "#xpath" do
371
+ it "returns a NodeSet" do
372
+ result = root.xpath('.//age')
373
+ expect(result).to be_a(RXerces::XML::NodeSet)
374
+ end
375
+ end
376
+
377
+ describe "#inner_html" do
378
+ it "returns the XML content of children without parent tags" do
379
+ person = root.xpath('//person').first
380
+ inner = person.inner_html
381
+ expect(inner).to include('<age>')
382
+ expect(inner).to include('<city>')
383
+ expect(inner).not_to include('<person')
384
+ end
385
+
386
+ it "returns empty string for nodes without children" do
387
+ age = root.xpath('//age').first
388
+ inner = age.inner_html
389
+ expect(inner).to eq('30')
390
+ end
391
+
392
+ it "includes multiple children" do
393
+ person = root.xpath('//person').first
394
+ inner = person.inner_html
395
+ expect(inner).to include('<age>30</age>')
396
+ expect(inner).to include('<city>New York</city>')
397
+ end
398
+ end
399
+
400
+ describe "#inner_xml" do
401
+ it "is an alias for inner_html" do
402
+ person = root.xpath('//person').first
403
+ expect(person.inner_xml).to eq(person.inner_html)
404
+ end
405
+
406
+ it "returns the same XML content" do
407
+ person = root.xpath('//person').first
408
+ inner = person.inner_xml
409
+ expect(inner).to include('<age>')
410
+ expect(inner).to include('<city>')
411
+ end
412
+ end
413
+
414
+ describe "#path" do
415
+ it "returns the XPath to the root element" do
416
+ expect(root.path).to eq('/root[1]')
417
+ end
418
+
419
+ it "returns the XPath to a nested element" do
420
+ person = root.xpath('//person').first
421
+ expect(person.path).to eq('/root[1]/person[1]')
422
+ end
423
+
424
+ it "returns the XPath to the second person element" do
425
+ people = root.xpath('//person')
426
+ second_person = people[1]
427
+ expect(second_person.path).to eq('/root[1]/person[2]')
428
+ end
429
+
430
+ it "returns the XPath to deeply nested elements" do
431
+ age = root.xpath('//age').first
432
+ expect(age.path).to eq('/root[1]/person[1]/age[1]')
433
+ end
434
+ end
435
+
436
+ describe "#blank?" do
437
+ let(:blank_xml) { '<root><empty></empty><whitespace> </whitespace><content>Hello</content></root>' }
438
+ let(:blank_doc) { RXerces::XML::Document.parse(blank_xml) }
439
+
440
+ it "returns false for elements with text content" do
441
+ content = blank_doc.xpath('//content').first
442
+ expect(content.blank?).to be false
443
+ end
444
+
445
+ it "returns true for empty elements" do
446
+ empty = blank_doc.xpath('//empty').first
447
+ expect(empty.blank?).to be true
448
+ end
449
+
450
+ it "returns true for elements with only whitespace" do
451
+ whitespace = blank_doc.xpath('//whitespace').first
452
+ expect(whitespace.blank?).to be true
453
+ end
454
+
455
+ it "returns false for elements with child elements" do
456
+ root = blank_doc.root
457
+ expect(root.blank?).to be false
458
+ end
459
+
460
+ it "returns false for text nodes with content" do
461
+ content = blank_doc.xpath('//content').first
462
+ text_node = content.children.first
463
+ expect(text_node.blank?).to be false
464
+ end
465
+
466
+ it "returns true for text nodes with only whitespace" do
467
+ whitespace = blank_doc.xpath('//whitespace').first
468
+ text_node = whitespace.children.first
469
+ expect(text_node.blank?).to be true
470
+ end
471
+ end
472
+ end
@@ -0,0 +1,92 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe RXerces::XML::NodeSet do
4
+ let(:xml) do
5
+ <<-XML
6
+ <root>
7
+ <item>First</item>
8
+ <item>Second</item>
9
+ <item>Third</item>
10
+ </root>
11
+ XML
12
+ end
13
+
14
+ let(:doc) { RXerces::XML::Document.parse(xml) }
15
+ let(:nodeset) { doc.xpath('//item') }
16
+ let(:empty_nodeset) { doc.xpath('//nonexistent') }
17
+
18
+ describe "#length" do
19
+ it "returns the number of nodes" do
20
+ expect(nodeset.length).to be_a(Integer)
21
+ expect(nodeset.length).to eq(3)
22
+ end
23
+
24
+ it "returns 0 for empty nodeset" do
25
+ expect(empty_nodeset.length).to eq(0)
26
+ end
27
+ end
28
+
29
+ describe "#size" do
30
+ it "is an alias for length" do
31
+ expect(nodeset.size).to eq(nodeset.length)
32
+ end
33
+ end
34
+
35
+ describe "#[]" do
36
+ it "returns node at index" do
37
+ item = nodeset[0]
38
+ expect(item).to be_a(RXerces::XML::Element)
39
+ expect(item.text.strip).to eq('First')
40
+ end
41
+
42
+ it "returns nil for out of bounds index" do
43
+ expect(nodeset[999]).to be_nil
44
+ end
45
+
46
+ it "returns nil for empty nodeset" do
47
+ expect(empty_nodeset[0]).to be_nil
48
+ end
49
+ end
50
+
51
+ describe "#each" do
52
+ it "is enumerable" do
53
+ expect(nodeset).to respond_to(:each)
54
+ end
55
+
56
+ it "returns enumerator when no block given" do
57
+ expect(nodeset.each).to be_a(Enumerator)
58
+ end
59
+
60
+ it "yields all items in nodeset" do
61
+ count = 0
62
+ nodeset.each { count += 1 }
63
+ expect(count).to eq(3)
64
+ end
65
+
66
+ it "yields nothing for empty nodeset" do
67
+ count = 0
68
+ empty_nodeset.each { count += 1 }
69
+ expect(count).to eq(0)
70
+ end
71
+
72
+ it "can iterate and access node properties" do
73
+ texts = []
74
+ nodeset.each do |item|
75
+ texts << item.text.strip
76
+ end
77
+ expect(texts).to eq(['First', 'Second', 'Third'])
78
+ end
79
+ end
80
+
81
+ describe "#to_a" do
82
+ it "converts to array" do
83
+ result = nodeset.to_a
84
+ expect(result).to be_an(Array)
85
+ expect(result.length).to eq(3)
86
+ end
87
+ end
88
+
89
+ it "includes Enumerable" do
90
+ expect(RXerces::XML::NodeSet.ancestors).to include(Enumerable)
91
+ end
92
+ end