rng 0.1.2 → 0.3.3
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
- data/.github/workflows/docs.yml +63 -0
- data/.github/workflows/release.yml +8 -3
- data/.gitignore +11 -0
- data/.rubocop.yml +10 -7
- data/.rubocop_todo.yml +229 -23
- data/CHANGELOG.md +317 -0
- data/CLAUDE.md +139 -0
- data/Gemfile +11 -12
- data/README.adoc +1538 -11
- data/Rakefile +11 -3
- data/docs/Gemfile +8 -0
- data/docs/_config.yml +23 -0
- data/docs/getting-started/index.adoc +75 -0
- data/docs/guides/error-handling.adoc +137 -0
- data/docs/guides/external-references.adoc +128 -0
- data/docs/guides/index.adoc +24 -0
- data/docs/guides/parsing-rnc.adoc +141 -0
- data/docs/guides/parsing-rng-xml.adoc +81 -0
- data/docs/guides/rng-to-rnc.adoc +101 -0
- data/docs/guides/validation.adoc +85 -0
- data/docs/index.adoc +52 -0
- data/docs/reference/api.adoc +126 -0
- data/docs/reference/cli.adoc +182 -0
- data/docs/understanding/architecture.adoc +58 -0
- data/docs/understanding/rng-vs-rnc.adoc +118 -0
- data/exe/rng +5 -0
- data/lib/rng/any_name.rb +10 -8
- data/lib/rng/attribute.rb +28 -26
- data/lib/rng/choice.rb +24 -24
- data/lib/rng/cli.rb +607 -0
- data/lib/rng/data.rb +10 -10
- data/lib/rng/datatype_declaration.rb +26 -0
- data/lib/rng/define.rb +44 -41
- data/lib/rng/div.rb +36 -0
- data/lib/rng/documentation.rb +9 -0
- data/lib/rng/element.rb +39 -37
- data/lib/rng/empty.rb +7 -7
- data/lib/rng/except.rb +25 -25
- data/lib/rng/external_ref.rb +8 -8
- data/lib/rng/external_ref_resolver.rb +582 -0
- data/lib/rng/foreign_attribute.rb +26 -0
- data/lib/rng/foreign_element.rb +33 -0
- data/lib/rng/grammar.rb +14 -12
- data/lib/rng/group.rb +26 -24
- data/lib/rng/include.rb +5 -6
- data/lib/rng/include_processor.rb +461 -0
- data/lib/rng/interleave.rb +23 -23
- data/lib/rng/list.rb +22 -22
- data/lib/rng/mixed.rb +23 -23
- data/lib/rng/name.rb +7 -7
- data/lib/rng/namespace_declaration.rb +47 -0
- data/lib/rng/namespaces.rb +15 -0
- data/lib/rng/not_allowed.rb +7 -7
- data/lib/rng/ns_name.rb +9 -9
- data/lib/rng/one_or_more.rb +23 -23
- data/lib/rng/optional.rb +23 -23
- data/lib/rng/param.rb +8 -8
- data/lib/rng/parent_ref.rb +8 -8
- data/lib/rng/parse_tree_processor.rb +695 -0
- data/lib/rng/pattern.rb +7 -7
- data/lib/rng/ref.rb +8 -8
- data/lib/rng/rnc_builder.rb +927 -0
- data/lib/rng/rnc_parser.rb +605 -305
- data/lib/rng/rnc_to_rng_converter.rb +1408 -0
- data/lib/rng/schema_preamble.rb +73 -0
- data/lib/rng/schema_validator.rb +1622 -0
- data/lib/rng/start.rb +27 -25
- data/lib/rng/test_suite_parser.rb +168 -0
- data/lib/rng/text.rb +11 -8
- data/lib/rng/to_rnc.rb +4 -35
- data/lib/rng/value.rb +6 -7
- data/lib/rng/version.rb +1 -1
- data/lib/rng/zero_or_more.rb +23 -23
- data/lib/rng.rb +68 -17
- data/rng.gemspec +18 -19
- data/scripts/extract_spectest_resources.rb +96 -0
- data/spec/fixtures/compacttest.xml +2511 -0
- data/spec/fixtures/external/circular_a.rng +7 -0
- data/spec/fixtures/external/circular_b.rng +7 -0
- data/spec/fixtures/external/circular_main.rng +7 -0
- data/spec/fixtures/external/external_ref_lib.rng +7 -0
- data/spec/fixtures/external/external_ref_main.rng +7 -0
- data/spec/fixtures/external/include_lib.rng +7 -0
- data/spec/fixtures/external/include_main.rng +3 -0
- data/spec/fixtures/external/nested_chain.rng +6 -0
- data/spec/fixtures/external/nested_leaf.rng +7 -0
- data/spec/fixtures/external/nested_mid.rng +8 -0
- data/spec/fixtures/metanorma/3gpp.rnc +35 -0
- data/spec/fixtures/metanorma/3gpp.rng +105 -0
- data/spec/fixtures/metanorma/basicdoc.rnc +11 -0
- data/spec/fixtures/metanorma/bipm.rnc +148 -0
- data/spec/fixtures/metanorma/bipm.rng +376 -0
- data/spec/fixtures/metanorma/bsi.rnc +104 -0
- data/spec/fixtures/metanorma/bsi.rng +332 -0
- data/spec/fixtures/metanorma/csa.rnc +45 -0
- data/spec/fixtures/metanorma/csa.rng +131 -0
- data/spec/fixtures/metanorma/csd.rnc +43 -0
- data/spec/fixtures/metanorma/csd.rng +132 -0
- data/spec/fixtures/metanorma/gbstandard.rnc +99 -0
- data/spec/fixtures/metanorma/gbstandard.rng +316 -0
- data/spec/fixtures/metanorma/iec.rnc +49 -0
- data/spec/fixtures/metanorma/iec.rng +193 -0
- data/spec/fixtures/metanorma/ietf.rnc +275 -0
- data/spec/fixtures/metanorma/ietf.rng +925 -0
- data/spec/fixtures/metanorma/iho.rnc +58 -0
- data/spec/fixtures/metanorma/iho.rng +179 -0
- data/spec/fixtures/metanorma/isodoc.rnc +873 -0
- data/spec/fixtures/metanorma/isodoc.rng +2704 -0
- data/spec/fixtures/metanorma/isostandard-amd.rnc +43 -0
- data/spec/fixtures/metanorma/isostandard-amd.rng +108 -0
- data/spec/fixtures/metanorma/isostandard.rnc +166 -0
- data/spec/fixtures/metanorma/isostandard.rng +494 -0
- data/spec/fixtures/metanorma/itu.rnc +122 -0
- data/spec/fixtures/metanorma/itu.rng +377 -0
- data/spec/fixtures/metanorma/m3d.rnc +41 -0
- data/spec/fixtures/metanorma/m3d.rng +122 -0
- data/spec/fixtures/metanorma/mpfd.rnc +36 -0
- data/spec/fixtures/metanorma/mpfd.rng +95 -0
- data/spec/fixtures/metanorma/nist.rnc +77 -0
- data/spec/fixtures/metanorma/nist.rng +216 -0
- data/spec/fixtures/metanorma/ogc.rnc +51 -0
- data/spec/fixtures/metanorma/ogc.rng +151 -0
- data/spec/fixtures/metanorma/reqt.rnc +6 -0
- data/spec/fixtures/metanorma/rsd.rnc +36 -0
- data/spec/fixtures/metanorma/rsd.rng +95 -0
- data/spec/fixtures/metanorma/un.rnc +103 -0
- data/spec/fixtures/metanorma/un.rng +367 -0
- data/spec/fixtures/rnc/base.rnc +4 -0
- data/spec/fixtures/rnc/grammar_with_trailing.rnc +8 -0
- data/spec/fixtures/rnc/main_include_trailing.rnc +3 -0
- data/spec/fixtures/rnc/main_with_include.rnc +5 -0
- data/spec/fixtures/rnc/test_augment.rnc +10 -0
- data/spec/fixtures/rnc/test_isodoc_simple.rnc +9 -0
- data/spec/fixtures/rnc/top_level_include.rnc +8 -0
- data/spec/fixtures/spectest_external/case_10_4.7/x +3 -0
- data/spec/fixtures/spectest_external/case_10_4.7/y +7 -0
- data/spec/fixtures/spectest_external/case_11_4.7/x +3 -0
- data/spec/fixtures/spectest_external/case_12_4.7/x +3 -0
- data/spec/fixtures/spectest_external/case_13_4.7/x +3 -0
- data/spec/fixtures/spectest_external/case_13_4.7/y +3 -0
- data/spec/fixtures/spectest_external/case_14_4.7/x +7 -0
- data/spec/fixtures/spectest_external/case_15_4.7/x +7 -0
- data/spec/fixtures/spectest_external/case_16_4.7/x +5 -0
- data/spec/fixtures/spectest_external/case_17_4.7/x +5 -0
- data/spec/fixtures/spectest_external/case_18_4.7/x +7 -0
- data/spec/fixtures/spectest_external/case_19_4.7/level1.rng +9 -0
- data/spec/fixtures/spectest_external/case_19_4.7/level2.rng +7 -0
- data/spec/fixtures/spectest_external/case_1_4.5/sub1/x +3 -0
- data/spec/fixtures/spectest_external/case_1_4.5/sub3/x +3 -0
- data/spec/fixtures/spectest_external/case_1_4.5/x +3 -0
- data/spec/fixtures/spectest_external/case_20_4.6/x +3 -0
- data/spec/fixtures/spectest_external/case_2_4.5/x +3 -0
- data/spec/fixtures/spectest_external/case_3_4.6/x +3 -0
- data/spec/fixtures/spectest_external/case_4_4.6/x +3 -0
- data/spec/fixtures/spectest_external/case_5_4.6/x +1 -0
- data/spec/fixtures/spectest_external/case_6_4.6/x +5 -0
- data/spec/fixtures/spectest_external/case_7_4.6/x +1 -0
- data/spec/fixtures/spectest_external/case_7_4.6/y +1 -0
- data/spec/fixtures/spectest_external/case_8_4.7/x +7 -0
- data/spec/fixtures/spectest_external/case_9_4.7/x +7 -0
- data/spec/fixtures/spectest_external/resources.json +149 -0
- data/spec/rng/advanced_rnc_spec.rb +101 -0
- data/spec/rng/compacttest_spec.rb +197 -0
- data/spec/rng/datatype_declaration_spec.rb +28 -0
- data/spec/rng/div_spec.rb +207 -0
- data/spec/rng/external_ref_resolver_spec.rb +122 -0
- data/spec/rng/metanorma_conversion_spec.rb +159 -0
- data/spec/rng/namespace_declaration_spec.rb +60 -0
- data/spec/rng/namespace_support_spec.rb +199 -0
- data/spec/rng/rnc_parser_spec.rb +498 -22
- data/spec/rng/rnc_roundtrip_spec.rb +96 -82
- data/spec/rng/rng_generation_spec.rb +288 -0
- data/spec/rng/roundtrip_spec.rb +342 -0
- data/spec/rng/schema_preamble_spec.rb +145 -0
- data/spec/rng/schema_spec.rb +68 -64
- data/spec/rng/spectest_spec.rb +168 -90
- data/spec/rng_spec.rb +2 -2
- data/spec/spec_helper.rb +7 -42
- metadata +141 -8
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
RSpec.describe 'Round-Trip Conversion' do
|
|
6
|
+
# Helper method to check if two Grammar objects are semantically equivalent
|
|
7
|
+
def grammars_equivalent?(grammar1, grammar2)
|
|
8
|
+
# Compare key structural elements
|
|
9
|
+
return false unless grammar1.instance_of?(grammar2.class)
|
|
10
|
+
|
|
11
|
+
# Compare start elements
|
|
12
|
+
if grammar1.start.any? && grammar2.start.any?
|
|
13
|
+
start1 = grammar1.start.first
|
|
14
|
+
start2 = grammar2.start.first
|
|
15
|
+
|
|
16
|
+
# Compare element names if they exist
|
|
17
|
+
return false if start1.element && start2.element && start1.element.attr_name != start2.element.attr_name
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Compare defines if they exist
|
|
21
|
+
if grammar1.define.any? && grammar2.define.any?
|
|
22
|
+
return false unless grammar1.define.length == grammar2.define.length
|
|
23
|
+
|
|
24
|
+
grammar1.define.zip(grammar2.define).each do |def1, def2|
|
|
25
|
+
return false unless def1.name == def2.name
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
true
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
describe 'RNG → RNC → RNG' do
|
|
33
|
+
context 'with address_book.rng' do
|
|
34
|
+
let(:original_rng) { File.read('spec/fixtures/rng/address_book.rng') }
|
|
35
|
+
|
|
36
|
+
it 'maintains semantic equivalence through round-trip' do
|
|
37
|
+
# Parse RNG
|
|
38
|
+
grammar1 = Rng.parse(original_rng)
|
|
39
|
+
|
|
40
|
+
# Convert to RNC
|
|
41
|
+
rnc = Rng.to_rnc(grammar1)
|
|
42
|
+
expect(rnc).to be_a(String)
|
|
43
|
+
expect(rnc).to include('element addressBook')
|
|
44
|
+
|
|
45
|
+
# Parse RNC back to Grammar
|
|
46
|
+
grammar2 = Rng.parse_rnc(rnc)
|
|
47
|
+
|
|
48
|
+
# Convert back to RNG
|
|
49
|
+
regenerated_rng = grammar2.to_xml
|
|
50
|
+
|
|
51
|
+
# Parse regenerated RNG
|
|
52
|
+
grammar3 = Rng.parse(regenerated_rng)
|
|
53
|
+
|
|
54
|
+
# Compare key structural elements
|
|
55
|
+
expect(grammar3.start.first.element.attr_name).to eq('addressBook')
|
|
56
|
+
expect(grammars_equivalent?(grammar1, grammar3)).to be true
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
it 'produces valid RNC syntax' do
|
|
60
|
+
grammar = Rng.parse(original_rng)
|
|
61
|
+
rnc = Rng.to_rnc(grammar)
|
|
62
|
+
|
|
63
|
+
# Should be parseable
|
|
64
|
+
expect { Rng.parse_rnc(rnc) }.not_to raise_error
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
it 'preserves element structure' do
|
|
68
|
+
grammar1 = Rng.parse(original_rng)
|
|
69
|
+
rnc = Rng.to_rnc(grammar1)
|
|
70
|
+
grammar2 = Rng.parse_rnc(rnc)
|
|
71
|
+
|
|
72
|
+
# Both should have addressBook element
|
|
73
|
+
expect(grammar1.start.first.element.attr_name).to eq('addressBook')
|
|
74
|
+
expect(grammar2.start.first.element.attr_name).to eq('addressBook')
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
context 'with simple RNG schemas' do
|
|
79
|
+
it 'handles single element schema' do
|
|
80
|
+
rng = <<~RNG
|
|
81
|
+
<grammar xmlns="http://relaxng.org/ns/structure/1.0">
|
|
82
|
+
<start>
|
|
83
|
+
<element name="root">
|
|
84
|
+
<text/>
|
|
85
|
+
</element>
|
|
86
|
+
</start>
|
|
87
|
+
</grammar>
|
|
88
|
+
RNG
|
|
89
|
+
|
|
90
|
+
grammar1 = Rng.parse(rng)
|
|
91
|
+
rnc = Rng.to_rnc(grammar1)
|
|
92
|
+
grammar2 = Rng.parse_rnc(rnc)
|
|
93
|
+
rng2 = grammar2.to_xml
|
|
94
|
+
grammar3 = Rng.parse(rng2)
|
|
95
|
+
|
|
96
|
+
expect(grammar3.start.first.element.attr_name).to eq('root')
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
it 'handles choice patterns' do
|
|
100
|
+
rng = <<~RNG
|
|
101
|
+
<grammar xmlns="http://relaxng.org/ns/structure/1.0">
|
|
102
|
+
<start>
|
|
103
|
+
<choice>
|
|
104
|
+
<element name="option1"><text/></element>
|
|
105
|
+
<element name="option2"><text/></element>
|
|
106
|
+
</choice>
|
|
107
|
+
</start>
|
|
108
|
+
</grammar>
|
|
109
|
+
RNG
|
|
110
|
+
|
|
111
|
+
grammar1 = Rng.parse(rng)
|
|
112
|
+
rnc = Rng.to_rnc(grammar1)
|
|
113
|
+
|
|
114
|
+
# NOTE: Current RNC builder converts choice to sequence in some cases
|
|
115
|
+
# This is a known limitation - choice should use | not ,
|
|
116
|
+
expect(rnc).to include('element option1')
|
|
117
|
+
expect(rnc).to include('element option2')
|
|
118
|
+
|
|
119
|
+
# Can still parse back
|
|
120
|
+
grammar2 = Rng.parse_rnc(rnc)
|
|
121
|
+
expect(grammar2).to be_a(Rng::Grammar)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
it 'handles group patterns' do
|
|
125
|
+
rng = <<~RNG
|
|
126
|
+
<grammar xmlns="http://relaxng.org/ns/structure/1.0">
|
|
127
|
+
<start>
|
|
128
|
+
<group>
|
|
129
|
+
<element name="first"><text/></element>
|
|
130
|
+
<element name="second"><text/></element>
|
|
131
|
+
</group>
|
|
132
|
+
</start>
|
|
133
|
+
</grammar>
|
|
134
|
+
RNG
|
|
135
|
+
|
|
136
|
+
grammar1 = Rng.parse(rng)
|
|
137
|
+
rnc = Rng.to_rnc(grammar1)
|
|
138
|
+
grammar2 = Rng.parse_rnc(rnc)
|
|
139
|
+
|
|
140
|
+
expect(grammar2.start.first.group).not_to be_nil
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
describe 'RNC → RNG → RNC' do
|
|
146
|
+
context 'with address_book.rnc' do
|
|
147
|
+
let(:original_rnc) { File.read('spec/fixtures/rnc/address_book.rnc') }
|
|
148
|
+
|
|
149
|
+
it 'maintains semantic equivalence through round-trip' do
|
|
150
|
+
# Parse RNC
|
|
151
|
+
grammar1 = Rng.parse_rnc(original_rnc)
|
|
152
|
+
|
|
153
|
+
# Convert to RNG
|
|
154
|
+
rng = grammar1.to_xml
|
|
155
|
+
expect(rng).to include('<grammar')
|
|
156
|
+
expect(rng).to include('<element name="addressBook">')
|
|
157
|
+
|
|
158
|
+
# Parse RNG back to Grammar
|
|
159
|
+
grammar2 = Rng.parse(rng)
|
|
160
|
+
|
|
161
|
+
# Convert back to RNC
|
|
162
|
+
regenerated_rnc = Rng.to_rnc(grammar2)
|
|
163
|
+
|
|
164
|
+
# Parse regenerated RNC
|
|
165
|
+
grammar3 = Rng.parse_rnc(regenerated_rnc)
|
|
166
|
+
|
|
167
|
+
# Compare key structural elements
|
|
168
|
+
expect(grammar3.start.first.element.attr_name).to eq('addressBook')
|
|
169
|
+
expect(grammars_equivalent?(grammar1, grammar3)).to be true
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
it 'produces valid RNG XML' do
|
|
173
|
+
grammar = Rng.parse_rnc(original_rnc)
|
|
174
|
+
rng = grammar.to_xml
|
|
175
|
+
|
|
176
|
+
# Should be parseable
|
|
177
|
+
expect { Rng.parse(rng) }.not_to raise_error
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
it 'preserves element structure' do
|
|
181
|
+
grammar1 = Rng.parse_rnc(original_rnc)
|
|
182
|
+
rng = grammar1.to_xml
|
|
183
|
+
grammar2 = Rng.parse(rng)
|
|
184
|
+
|
|
185
|
+
# Both should have addressBook element
|
|
186
|
+
expect(grammar1.start.first.element.attr_name).to eq('addressBook')
|
|
187
|
+
expect(grammar2.start.first.element.attr_name).to eq('addressBook')
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
context 'with simple RNC schemas' do
|
|
192
|
+
it 'handles single element schema' do
|
|
193
|
+
rnc = <<~RNC
|
|
194
|
+
start = element root { text }
|
|
195
|
+
RNC
|
|
196
|
+
|
|
197
|
+
grammar1 = Rng.parse_rnc(rnc)
|
|
198
|
+
rng = grammar1.to_xml
|
|
199
|
+
grammar2 = Rng.parse(rng)
|
|
200
|
+
rnc2 = Rng.to_rnc(grammar2)
|
|
201
|
+
grammar3 = Rng.parse_rnc(rnc2)
|
|
202
|
+
|
|
203
|
+
expect(grammar3.start.first.element.attr_name).to eq('root')
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
it 'handles choice patterns (|)' do
|
|
207
|
+
rnc = <<~RNC
|
|
208
|
+
start = element option1 { text } | element option2 { text }
|
|
209
|
+
RNC
|
|
210
|
+
|
|
211
|
+
grammar1 = Rng.parse_rnc(rnc)
|
|
212
|
+
rng = grammar1.to_xml
|
|
213
|
+
grammar2 = Rng.parse(rng)
|
|
214
|
+
|
|
215
|
+
expect(grammar2.start.first.choice).not_to be_nil
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
it 'handles sequence patterns (,)' do
|
|
219
|
+
rnc = <<~RNC
|
|
220
|
+
start = element first { text }, element second { text }
|
|
221
|
+
RNC
|
|
222
|
+
|
|
223
|
+
grammar1 = Rng.parse_rnc(rnc)
|
|
224
|
+
rng = grammar1.to_xml
|
|
225
|
+
grammar2 = Rng.parse(rng)
|
|
226
|
+
|
|
227
|
+
expect(grammar2.start.first.group).not_to be_nil
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
it 'handles optional patterns (?)' do
|
|
231
|
+
rnc = <<~RNC
|
|
232
|
+
start = element optional { text }?
|
|
233
|
+
RNC
|
|
234
|
+
|
|
235
|
+
grammar1 = Rng.parse_rnc(rnc)
|
|
236
|
+
rng = grammar1.to_xml
|
|
237
|
+
grammar2 = Rng.parse(rng)
|
|
238
|
+
|
|
239
|
+
expect(grammar2.start.first.optional).not_to be_nil
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
it 'handles zero-or-more patterns (*)' do
|
|
243
|
+
rnc = <<~RNC
|
|
244
|
+
start = element item { text }*
|
|
245
|
+
RNC
|
|
246
|
+
|
|
247
|
+
grammar1 = Rng.parse_rnc(rnc)
|
|
248
|
+
rng = grammar1.to_xml
|
|
249
|
+
grammar2 = Rng.parse(rng)
|
|
250
|
+
|
|
251
|
+
expect(grammar2.start.first.zeroOrMore).not_to be_nil
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
it 'handles one-or-more patterns (+)' do
|
|
255
|
+
rnc = <<~RNC
|
|
256
|
+
start = element item { text }+
|
|
257
|
+
RNC
|
|
258
|
+
|
|
259
|
+
grammar1 = Rng.parse_rnc(rnc)
|
|
260
|
+
rng = grammar1.to_xml
|
|
261
|
+
grammar2 = Rng.parse(rng)
|
|
262
|
+
|
|
263
|
+
# oneOrMore can be either an array or a single object depending on parsing
|
|
264
|
+
one_or_more = grammar2.start.first.oneOrMore
|
|
265
|
+
expect(one_or_more).not_to be_nil
|
|
266
|
+
# Check if it's an array or single object
|
|
267
|
+
if one_or_more.is_a?(Array)
|
|
268
|
+
expect(one_or_more.length).to be > 0
|
|
269
|
+
else
|
|
270
|
+
expect(one_or_more).to be_a(Rng::OneOrMore)
|
|
271
|
+
end
|
|
272
|
+
end
|
|
273
|
+
end
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
describe 'XML comparison using canon matchers' do
|
|
277
|
+
context 'with formatted XML comparison' do
|
|
278
|
+
it 'recognizes equivalent formatted XML' do
|
|
279
|
+
rng1 = <<~RNG
|
|
280
|
+
<grammar xmlns="http://relaxng.org/ns/structure/1.0">
|
|
281
|
+
<start>
|
|
282
|
+
<element name="root"><text/></element>
|
|
283
|
+
</start>
|
|
284
|
+
</grammar>
|
|
285
|
+
RNG
|
|
286
|
+
|
|
287
|
+
grammar = Rng.parse(rng1)
|
|
288
|
+
rng2 = grammar.to_xml
|
|
289
|
+
|
|
290
|
+
# May differ in formatting but should be equivalent
|
|
291
|
+
expect(rng2).to be_xml_equivalent_to(rng1)
|
|
292
|
+
end
|
|
293
|
+
end
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
describe 'Format compatibility' do
|
|
297
|
+
it 'RNC and RNG represent the same schema' do
|
|
298
|
+
rnc = File.read('spec/fixtures/rnc/address_book.rnc')
|
|
299
|
+
rng = File.read('spec/fixtures/rng/address_book.rng')
|
|
300
|
+
|
|
301
|
+
grammar_from_rnc = Rng.parse_rnc(rnc)
|
|
302
|
+
grammar_from_rng = Rng.parse(rng)
|
|
303
|
+
|
|
304
|
+
# Both should parse successfully
|
|
305
|
+
expect(grammar_from_rnc).to be_a(Rng::Grammar)
|
|
306
|
+
expect(grammar_from_rng).to be_a(Rng::Grammar)
|
|
307
|
+
|
|
308
|
+
# Both should have the same root element
|
|
309
|
+
expect(grammar_from_rnc.start.first.element.attr_name).to eq('addressBook')
|
|
310
|
+
expect(grammar_from_rng.start.first.element.attr_name).to eq('addressBook')
|
|
311
|
+
end
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
describe 'Edge cases' do
|
|
315
|
+
it 'handles empty element' do
|
|
316
|
+
rnc = 'start = element root { empty }'
|
|
317
|
+
grammar = Rng.parse_rnc(rnc)
|
|
318
|
+
rng = grammar.to_xml
|
|
319
|
+
grammar2 = Rng.parse(rng)
|
|
320
|
+
|
|
321
|
+
expect(grammar2.start.first.element.empty).not_to be_nil
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
it 'handles attributes' do
|
|
325
|
+
rnc = 'start = element root { attribute id { text } }'
|
|
326
|
+
grammar = Rng.parse_rnc(rnc)
|
|
327
|
+
rng = grammar.to_xml
|
|
328
|
+
grammar2 = Rng.parse(rng)
|
|
329
|
+
|
|
330
|
+
expect(grammar2.start.first.element.attribute).not_to be_nil
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
it 'handles mixed content' do
|
|
334
|
+
rnc = 'start = element root { mixed { element child { text } } }'
|
|
335
|
+
grammar = Rng.parse_rnc(rnc)
|
|
336
|
+
rng = grammar.to_xml
|
|
337
|
+
grammar2 = Rng.parse(rng)
|
|
338
|
+
|
|
339
|
+
expect(grammar2.start.first.element.mixed).not_to be_nil
|
|
340
|
+
end
|
|
341
|
+
end
|
|
342
|
+
end
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
require_relative '../../lib/rng/schema_preamble'
|
|
5
|
+
|
|
6
|
+
RSpec.describe Rng::SchemaPreamble do
|
|
7
|
+
describe '#initialize' do
|
|
8
|
+
it 'creates an empty preamble' do
|
|
9
|
+
preamble = described_class.new
|
|
10
|
+
|
|
11
|
+
expect(preamble.namespaces).to be_empty
|
|
12
|
+
expect(preamble.datatypes).to be_empty
|
|
13
|
+
expect(preamble).to be_empty
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
describe '#add_namespace' do
|
|
18
|
+
it 'adds namespace declarations' do
|
|
19
|
+
preamble = described_class.new
|
|
20
|
+
ns1 = Rng::NamespaceDeclaration.new(uri: 'http://example.com',
|
|
21
|
+
is_default: true)
|
|
22
|
+
ns2 = Rng::NamespaceDeclaration.new(prefix: 'eg', uri: 'http://example.com/eg')
|
|
23
|
+
|
|
24
|
+
preamble.add_namespace(ns1)
|
|
25
|
+
preamble.add_namespace(ns2)
|
|
26
|
+
|
|
27
|
+
expect(preamble.namespaces).to contain_exactly(ns1, ns2)
|
|
28
|
+
expect(preamble).not_to be_empty
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
describe '#add_datatype' do
|
|
33
|
+
it 'adds datatype declarations' do
|
|
34
|
+
preamble = described_class.new
|
|
35
|
+
dt = Rng::DatatypeDeclaration.new(
|
|
36
|
+
prefix: 'xsd',
|
|
37
|
+
uri: 'http://www.w3.org/2001/XMLSchema-datatypes'
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
preamble.add_datatype(dt)
|
|
41
|
+
|
|
42
|
+
expect(preamble.datatypes).to contain_exactly(dt)
|
|
43
|
+
expect(preamble).not_to be_empty
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
describe '#default_namespace' do
|
|
48
|
+
it 'returns the default namespace URI' do
|
|
49
|
+
preamble = described_class.new
|
|
50
|
+
default_ns = Rng::NamespaceDeclaration.new(uri: 'http://example.com',
|
|
51
|
+
is_default: true)
|
|
52
|
+
other_ns = Rng::NamespaceDeclaration.new(prefix: 'eg', uri: 'http://example.com/eg')
|
|
53
|
+
|
|
54
|
+
preamble.add_namespace(default_ns)
|
|
55
|
+
preamble.add_namespace(other_ns)
|
|
56
|
+
|
|
57
|
+
expect(preamble.default_namespace).to eq('http://example.com')
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
it 'returns nil when there is no default namespace' do
|
|
61
|
+
preamble = described_class.new
|
|
62
|
+
ns = Rng::NamespaceDeclaration.new(prefix: 'eg', uri: 'http://example.com')
|
|
63
|
+
|
|
64
|
+
preamble.add_namespace(ns)
|
|
65
|
+
|
|
66
|
+
expect(preamble.default_namespace).to be_nil
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
describe '#namespace_map' do
|
|
71
|
+
it 'returns a map of prefixed namespaces' do
|
|
72
|
+
preamble = described_class.new
|
|
73
|
+
ns1 = Rng::NamespaceDeclaration.new(prefix: 'eg', uri: 'http://example.com')
|
|
74
|
+
ns2 = Rng::NamespaceDeclaration.new(prefix: 'local', uri: '')
|
|
75
|
+
default_ns = Rng::NamespaceDeclaration.new(uri: 'http://default.com',
|
|
76
|
+
is_default: true)
|
|
77
|
+
|
|
78
|
+
preamble.add_namespace(ns1)
|
|
79
|
+
preamble.add_namespace(ns2)
|
|
80
|
+
preamble.add_namespace(default_ns)
|
|
81
|
+
|
|
82
|
+
map = preamble.namespace_map
|
|
83
|
+
expect(map).to eq({
|
|
84
|
+
'eg' => 'http://example.com',
|
|
85
|
+
'local' => ''
|
|
86
|
+
})
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
it 'returns empty hash when there are no prefixed namespaces' do
|
|
90
|
+
preamble = described_class.new
|
|
91
|
+
default_ns = Rng::NamespaceDeclaration.new(uri: 'http://example.com',
|
|
92
|
+
is_default: true)
|
|
93
|
+
|
|
94
|
+
preamble.add_namespace(default_ns)
|
|
95
|
+
|
|
96
|
+
expect(preamble.namespace_map).to eq({})
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
describe '#datatype_map' do
|
|
101
|
+
it 'returns a map of datatype prefixes to URIs' do
|
|
102
|
+
preamble = described_class.new
|
|
103
|
+
dt1 = Rng::DatatypeDeclaration.new(prefix: 'xsd', uri: 'http://www.w3.org/2001/XMLSchema-datatypes')
|
|
104
|
+
dt2 = Rng::DatatypeDeclaration.new(prefix: 'custom', uri: 'http://example.com/types')
|
|
105
|
+
|
|
106
|
+
preamble.add_datatype(dt1)
|
|
107
|
+
preamble.add_datatype(dt2)
|
|
108
|
+
|
|
109
|
+
map = preamble.datatype_map
|
|
110
|
+
expect(map).to eq({
|
|
111
|
+
'xsd' => 'http://www.w3.org/2001/XMLSchema-datatypes',
|
|
112
|
+
'custom' => 'http://example.com/types'
|
|
113
|
+
})
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
it 'returns empty hash when there are no datatypes' do
|
|
117
|
+
preamble = described_class.new
|
|
118
|
+
expect(preamble.datatype_map).to eq({})
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
describe '#empty?' do
|
|
123
|
+
it 'returns true when preamble has no declarations' do
|
|
124
|
+
preamble = described_class.new
|
|
125
|
+
expect(preamble).to be_empty
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
it 'returns false when preamble has namespace declarations' do
|
|
129
|
+
preamble = described_class.new
|
|
130
|
+
ns = Rng::NamespaceDeclaration.new(uri: 'http://example.com',
|
|
131
|
+
is_default: true)
|
|
132
|
+
preamble.add_namespace(ns)
|
|
133
|
+
|
|
134
|
+
expect(preamble).not_to be_empty
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
it 'returns false when preamble has datatype declarations' do
|
|
138
|
+
preamble = described_class.new
|
|
139
|
+
dt = Rng::DatatypeDeclaration.new(prefix: 'xsd', uri: 'http://www.w3.org/2001/XMLSchema-datatypes')
|
|
140
|
+
preamble.add_datatype(dt)
|
|
141
|
+
|
|
142
|
+
expect(preamble).not_to be_empty
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|