rxerces 0.2.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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/CHANGES.md +12 -0
- data/examples/schema_example.rb +107 -0
- data/ext/rxerces/rxerces.cpp +623 -1
- data/lib/rxerces/nokogiri.rb +1 -0
- data/lib/rxerces/version.rb +1 -1
- data/rxerces.gemspec +1 -1
- data/spec/document_spec.rb +35 -0
- data/spec/node_spec.rb +361 -0
- data/spec/nokogiri_compatibility_spec.rb +69 -0
- data/spec/rxerces_shared.rb +1 -1
- data/spec/schema_spec.rb +76 -0
- data.tar.gz.sig +0 -0
- metadata +4 -1
- metadata.gz.sig +2 -2
data/lib/rxerces/version.rb
CHANGED
data/rxerces.gemspec
CHANGED
data/spec/document_spec.rb
CHANGED
|
@@ -63,4 +63,39 @@ RSpec.describe RXerces::XML::Document do
|
|
|
63
63
|
expect(result).to be_a(RXerces::XML::NodeSet)
|
|
64
64
|
end
|
|
65
65
|
end
|
|
66
|
+
|
|
67
|
+
describe "#create_element" do
|
|
68
|
+
let(:doc) { RXerces::XML::Document.parse(simple_xml) }
|
|
69
|
+
|
|
70
|
+
it "creates a new element with the specified name" do
|
|
71
|
+
element = doc.create_element('book')
|
|
72
|
+
expect(element).to be_a(RXerces::XML::Element)
|
|
73
|
+
expect(element.name).to eq('book')
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
it "creates an element that can have attributes set" do
|
|
77
|
+
element = doc.create_element('book')
|
|
78
|
+
attributes = element.attributes
|
|
79
|
+
expect(attributes).to be_a(Hash)
|
|
80
|
+
expect(attributes).to be_empty
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
it "creates an element that can have children added" do
|
|
84
|
+
element = doc.create_element('book')
|
|
85
|
+
element.add_child('Test Content')
|
|
86
|
+
expect(element.text).to eq('Test Content')
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
it "creates an element that can be added to the document" do
|
|
90
|
+
root = doc.root
|
|
91
|
+
new_element = doc.create_element('new_child')
|
|
92
|
+
new_element.add_child('New content')
|
|
93
|
+
root.add_child(new_element)
|
|
94
|
+
|
|
95
|
+
# Verify the new element is in the document
|
|
96
|
+
result = doc.xpath('//new_child')
|
|
97
|
+
expect(result.length).to eq(1)
|
|
98
|
+
expect(result.first.text).to eq('New content')
|
|
99
|
+
end
|
|
100
|
+
end
|
|
66
101
|
end
|
data/spec/node_spec.rb
CHANGED
|
@@ -102,10 +102,371 @@ RSpec.describe RXerces::XML::Node do
|
|
|
102
102
|
end
|
|
103
103
|
end
|
|
104
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
|
+
|
|
105
370
|
describe "#xpath" do
|
|
106
371
|
it "returns a NodeSet" do
|
|
107
372
|
result = root.xpath('.//age')
|
|
108
373
|
expect(result).to be_a(RXerces::XML::NodeSet)
|
|
109
374
|
end
|
|
110
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
|
|
111
472
|
end
|
|
@@ -61,6 +61,12 @@ RSpec.describe "Nokogiri compatibility" do
|
|
|
61
61
|
end
|
|
62
62
|
end
|
|
63
63
|
|
|
64
|
+
describe "Nokogiri::XML::Schema" do
|
|
65
|
+
it "is an alias for RXerces::XML::Schema" do
|
|
66
|
+
expect(Nokogiri::XML::Schema).to eq(RXerces::XML::Schema)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
64
70
|
describe "API compatibility" do
|
|
65
71
|
let(:doc) { Nokogiri.XML(simple_xml) }
|
|
66
72
|
|
|
@@ -95,4 +101,67 @@ RSpec.describe "Nokogiri compatibility" do
|
|
|
95
101
|
expect(children).to be_an(Array)
|
|
96
102
|
end
|
|
97
103
|
end
|
|
104
|
+
|
|
105
|
+
describe "Schema validation compatibility" do
|
|
106
|
+
let(:xsd) do
|
|
107
|
+
<<~XSD
|
|
108
|
+
<?xml version="1.0"?>
|
|
109
|
+
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
|
110
|
+
<xs:element name="person">
|
|
111
|
+
<xs:complexType>
|
|
112
|
+
<xs:sequence>
|
|
113
|
+
<xs:element name="name" type="xs:string"/>
|
|
114
|
+
<xs:element name="age" type="xs:integer"/>
|
|
115
|
+
</xs:sequence>
|
|
116
|
+
</xs:complexType>
|
|
117
|
+
</xs:element>
|
|
118
|
+
</xs:schema>
|
|
119
|
+
XSD
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
let(:valid_xml) do
|
|
123
|
+
<<~XML
|
|
124
|
+
<?xml version="1.0"?>
|
|
125
|
+
<person>
|
|
126
|
+
<name>John</name>
|
|
127
|
+
<age>30</age>
|
|
128
|
+
</person>
|
|
129
|
+
XML
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
let(:invalid_xml) do
|
|
133
|
+
<<~XML
|
|
134
|
+
<?xml version="1.0"?>
|
|
135
|
+
<person>
|
|
136
|
+
<name>Jane</name>
|
|
137
|
+
<age>invalid</age>
|
|
138
|
+
</person>
|
|
139
|
+
XML
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
it "provides Schema.from_string method" do
|
|
143
|
+
schema = Nokogiri::XML::Schema.from_string(xsd)
|
|
144
|
+
expect(schema).to be_a(Nokogiri::XML::Schema)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
it "provides Schema.from_document method" do
|
|
148
|
+
doc = Nokogiri::XML.parse(xsd)
|
|
149
|
+
schema = Nokogiri::XML::Schema.from_document(doc)
|
|
150
|
+
expect(schema).to be_a(Nokogiri::XML::Schema)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
it "validates a valid document" do
|
|
154
|
+
schema = Nokogiri::XML::Schema.from_string(xsd)
|
|
155
|
+
doc = Nokogiri::XML.parse(valid_xml)
|
|
156
|
+
errors = doc.validate(schema)
|
|
157
|
+
expect(errors).to be_empty
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
it "returns errors for an invalid document" do
|
|
161
|
+
schema = Nokogiri::XML::Schema.from_string(xsd)
|
|
162
|
+
doc = Nokogiri::XML.parse(invalid_xml)
|
|
163
|
+
errors = doc.validate(schema)
|
|
164
|
+
expect(errors).not_to be_empty
|
|
165
|
+
end
|
|
166
|
+
end
|
|
98
167
|
end
|
data/spec/rxerces_shared.rb
CHANGED
data/spec/schema_spec.rb
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
RSpec.describe RXerces::XML::Schema do
|
|
4
|
+
let(:simple_xsd) do
|
|
5
|
+
<<~XSD
|
|
6
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
7
|
+
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
|
8
|
+
<xs:element name="root">
|
|
9
|
+
<xs:complexType>
|
|
10
|
+
<xs:sequence>
|
|
11
|
+
<xs:element name="name" type="xs:string"/>
|
|
12
|
+
<xs:element name="age" type="xs:integer"/>
|
|
13
|
+
</xs:sequence>
|
|
14
|
+
</xs:complexType>
|
|
15
|
+
</xs:element>
|
|
16
|
+
</xs:schema>
|
|
17
|
+
XSD
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
let(:valid_xml) do
|
|
21
|
+
<<~XML
|
|
22
|
+
<?xml version="1.0"?>
|
|
23
|
+
<root>
|
|
24
|
+
<name>John</name>
|
|
25
|
+
<age>30</age>
|
|
26
|
+
</root>
|
|
27
|
+
XML
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
let(:invalid_xml) do
|
|
31
|
+
<<~XML
|
|
32
|
+
<?xml version="1.0"?>
|
|
33
|
+
<root>
|
|
34
|
+
<name>John</name>
|
|
35
|
+
<age>not-a-number</age>
|
|
36
|
+
</root>
|
|
37
|
+
XML
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
describe '.from_string' do
|
|
41
|
+
it 'creates a schema from an XSD string' do
|
|
42
|
+
schema = described_class.from_string(simple_xsd)
|
|
43
|
+
expect(schema).to be_a(described_class)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Note: Xerces-C parser is very tolerant of invalid XML
|
|
47
|
+
# So we just skip testing for invalid XML for now
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
describe '.from_document' do
|
|
51
|
+
it 'creates a schema from a Document' do
|
|
52
|
+
schema_doc = RXerces::XML::Document.parse(simple_xsd)
|
|
53
|
+
schema = described_class.from_document(schema_doc)
|
|
54
|
+
expect(schema).to be_a(described_class)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
describe 'validation' do
|
|
59
|
+
let(:schema) { described_class.from_string(simple_xsd) }
|
|
60
|
+
|
|
61
|
+
it 'validates a valid document' do
|
|
62
|
+
doc = RXerces::XML::Document.parse(valid_xml)
|
|
63
|
+
errors = doc.validate(schema)
|
|
64
|
+
expect(errors).to be_a(Array)
|
|
65
|
+
expect(errors).to be_empty
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
it 'returns validation errors for an invalid document' do
|
|
69
|
+
doc = RXerces::XML::Document.parse(invalid_xml)
|
|
70
|
+
errors = doc.validate(schema)
|
|
71
|
+
expect(errors).to be_a(Array)
|
|
72
|
+
expect(errors).not_to be_empty
|
|
73
|
+
expect(errors.first).to include('not-a-number')
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
data.tar.gz.sig
CHANGED
|
Binary file
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rxerces
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Daniel J. Berger
|
|
@@ -93,6 +93,7 @@ files:
|
|
|
93
93
|
- Rakefile
|
|
94
94
|
- certs/djberg96_pub.pem
|
|
95
95
|
- examples/basic_usage.rb
|
|
96
|
+
- examples/schema_example.rb
|
|
96
97
|
- examples/simple_example.rb
|
|
97
98
|
- examples/xpath_example.rb
|
|
98
99
|
- ext/rxerces/extconf.rb
|
|
@@ -109,6 +110,7 @@ files:
|
|
|
109
110
|
- spec/nokogiri_compatibility_spec.rb
|
|
110
111
|
- spec/rxerces_shared.rb
|
|
111
112
|
- spec/rxerces_spec.rb
|
|
113
|
+
- spec/schema_spec.rb
|
|
112
114
|
- spec/spec_helper.rb
|
|
113
115
|
- spec/xpath_spec.rb
|
|
114
116
|
homepage: http://github.com/djberg96/rxerces
|
|
@@ -148,4 +150,5 @@ test_files:
|
|
|
148
150
|
- spec/nodeset_spec.rb
|
|
149
151
|
- spec/nokogiri_compatibility_spec.rb
|
|
150
152
|
- spec/rxerces_spec.rb
|
|
153
|
+
- spec/schema_spec.rb
|
|
151
154
|
- spec/xpath_spec.rb
|
metadata.gz.sig
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
s
|
|
1
|
+
Q� �q��o�{�;?��-6��%�:��-ܪB�h�y,�P�U}�#loyp�w��c����M@X+>��]U�.��<z5��a'�m�z]G���1laA�x���w�V�`���b6��U�����s��z��=u��$�6��Vj6��~��x:�����
|
|
2
|
+
O��Е>d~�s��cYs���� ������tHgd�%�Y��@TtcԁI��S
|