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.
Files changed (180) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/docs.yml +63 -0
  3. data/.github/workflows/release.yml +8 -3
  4. data/.gitignore +11 -0
  5. data/.rubocop.yml +10 -7
  6. data/.rubocop_todo.yml +229 -23
  7. data/CHANGELOG.md +317 -0
  8. data/CLAUDE.md +139 -0
  9. data/Gemfile +11 -12
  10. data/README.adoc +1538 -11
  11. data/Rakefile +11 -3
  12. data/docs/Gemfile +8 -0
  13. data/docs/_config.yml +23 -0
  14. data/docs/getting-started/index.adoc +75 -0
  15. data/docs/guides/error-handling.adoc +137 -0
  16. data/docs/guides/external-references.adoc +128 -0
  17. data/docs/guides/index.adoc +24 -0
  18. data/docs/guides/parsing-rnc.adoc +141 -0
  19. data/docs/guides/parsing-rng-xml.adoc +81 -0
  20. data/docs/guides/rng-to-rnc.adoc +101 -0
  21. data/docs/guides/validation.adoc +85 -0
  22. data/docs/index.adoc +52 -0
  23. data/docs/reference/api.adoc +126 -0
  24. data/docs/reference/cli.adoc +182 -0
  25. data/docs/understanding/architecture.adoc +58 -0
  26. data/docs/understanding/rng-vs-rnc.adoc +118 -0
  27. data/exe/rng +5 -0
  28. data/lib/rng/any_name.rb +10 -8
  29. data/lib/rng/attribute.rb +28 -26
  30. data/lib/rng/choice.rb +24 -24
  31. data/lib/rng/cli.rb +607 -0
  32. data/lib/rng/data.rb +10 -10
  33. data/lib/rng/datatype_declaration.rb +26 -0
  34. data/lib/rng/define.rb +44 -41
  35. data/lib/rng/div.rb +36 -0
  36. data/lib/rng/documentation.rb +9 -0
  37. data/lib/rng/element.rb +39 -37
  38. data/lib/rng/empty.rb +7 -7
  39. data/lib/rng/except.rb +25 -25
  40. data/lib/rng/external_ref.rb +8 -8
  41. data/lib/rng/external_ref_resolver.rb +582 -0
  42. data/lib/rng/foreign_attribute.rb +26 -0
  43. data/lib/rng/foreign_element.rb +33 -0
  44. data/lib/rng/grammar.rb +14 -12
  45. data/lib/rng/group.rb +26 -24
  46. data/lib/rng/include.rb +5 -6
  47. data/lib/rng/include_processor.rb +461 -0
  48. data/lib/rng/interleave.rb +23 -23
  49. data/lib/rng/list.rb +22 -22
  50. data/lib/rng/mixed.rb +23 -23
  51. data/lib/rng/name.rb +7 -7
  52. data/lib/rng/namespace_declaration.rb +47 -0
  53. data/lib/rng/namespaces.rb +15 -0
  54. data/lib/rng/not_allowed.rb +7 -7
  55. data/lib/rng/ns_name.rb +9 -9
  56. data/lib/rng/one_or_more.rb +23 -23
  57. data/lib/rng/optional.rb +23 -23
  58. data/lib/rng/param.rb +8 -8
  59. data/lib/rng/parent_ref.rb +8 -8
  60. data/lib/rng/parse_tree_processor.rb +695 -0
  61. data/lib/rng/pattern.rb +7 -7
  62. data/lib/rng/ref.rb +8 -8
  63. data/lib/rng/rnc_builder.rb +927 -0
  64. data/lib/rng/rnc_parser.rb +605 -305
  65. data/lib/rng/rnc_to_rng_converter.rb +1408 -0
  66. data/lib/rng/schema_preamble.rb +73 -0
  67. data/lib/rng/schema_validator.rb +1622 -0
  68. data/lib/rng/start.rb +27 -25
  69. data/lib/rng/test_suite_parser.rb +168 -0
  70. data/lib/rng/text.rb +11 -8
  71. data/lib/rng/to_rnc.rb +4 -35
  72. data/lib/rng/value.rb +6 -7
  73. data/lib/rng/version.rb +1 -1
  74. data/lib/rng/zero_or_more.rb +23 -23
  75. data/lib/rng.rb +68 -17
  76. data/rng.gemspec +18 -19
  77. data/scripts/extract_spectest_resources.rb +96 -0
  78. data/spec/fixtures/compacttest.xml +2511 -0
  79. data/spec/fixtures/external/circular_a.rng +7 -0
  80. data/spec/fixtures/external/circular_b.rng +7 -0
  81. data/spec/fixtures/external/circular_main.rng +7 -0
  82. data/spec/fixtures/external/external_ref_lib.rng +7 -0
  83. data/spec/fixtures/external/external_ref_main.rng +7 -0
  84. data/spec/fixtures/external/include_lib.rng +7 -0
  85. data/spec/fixtures/external/include_main.rng +3 -0
  86. data/spec/fixtures/external/nested_chain.rng +6 -0
  87. data/spec/fixtures/external/nested_leaf.rng +7 -0
  88. data/spec/fixtures/external/nested_mid.rng +8 -0
  89. data/spec/fixtures/metanorma/3gpp.rnc +35 -0
  90. data/spec/fixtures/metanorma/3gpp.rng +105 -0
  91. data/spec/fixtures/metanorma/basicdoc.rnc +11 -0
  92. data/spec/fixtures/metanorma/bipm.rnc +148 -0
  93. data/spec/fixtures/metanorma/bipm.rng +376 -0
  94. data/spec/fixtures/metanorma/bsi.rnc +104 -0
  95. data/spec/fixtures/metanorma/bsi.rng +332 -0
  96. data/spec/fixtures/metanorma/csa.rnc +45 -0
  97. data/spec/fixtures/metanorma/csa.rng +131 -0
  98. data/spec/fixtures/metanorma/csd.rnc +43 -0
  99. data/spec/fixtures/metanorma/csd.rng +132 -0
  100. data/spec/fixtures/metanorma/gbstandard.rnc +99 -0
  101. data/spec/fixtures/metanorma/gbstandard.rng +316 -0
  102. data/spec/fixtures/metanorma/iec.rnc +49 -0
  103. data/spec/fixtures/metanorma/iec.rng +193 -0
  104. data/spec/fixtures/metanorma/ietf.rnc +275 -0
  105. data/spec/fixtures/metanorma/ietf.rng +925 -0
  106. data/spec/fixtures/metanorma/iho.rnc +58 -0
  107. data/spec/fixtures/metanorma/iho.rng +179 -0
  108. data/spec/fixtures/metanorma/isodoc.rnc +873 -0
  109. data/spec/fixtures/metanorma/isodoc.rng +2704 -0
  110. data/spec/fixtures/metanorma/isostandard-amd.rnc +43 -0
  111. data/spec/fixtures/metanorma/isostandard-amd.rng +108 -0
  112. data/spec/fixtures/metanorma/isostandard.rnc +166 -0
  113. data/spec/fixtures/metanorma/isostandard.rng +494 -0
  114. data/spec/fixtures/metanorma/itu.rnc +122 -0
  115. data/spec/fixtures/metanorma/itu.rng +377 -0
  116. data/spec/fixtures/metanorma/m3d.rnc +41 -0
  117. data/spec/fixtures/metanorma/m3d.rng +122 -0
  118. data/spec/fixtures/metanorma/mpfd.rnc +36 -0
  119. data/spec/fixtures/metanorma/mpfd.rng +95 -0
  120. data/spec/fixtures/metanorma/nist.rnc +77 -0
  121. data/spec/fixtures/metanorma/nist.rng +216 -0
  122. data/spec/fixtures/metanorma/ogc.rnc +51 -0
  123. data/spec/fixtures/metanorma/ogc.rng +151 -0
  124. data/spec/fixtures/metanorma/reqt.rnc +6 -0
  125. data/spec/fixtures/metanorma/rsd.rnc +36 -0
  126. data/spec/fixtures/metanorma/rsd.rng +95 -0
  127. data/spec/fixtures/metanorma/un.rnc +103 -0
  128. data/spec/fixtures/metanorma/un.rng +367 -0
  129. data/spec/fixtures/rnc/base.rnc +4 -0
  130. data/spec/fixtures/rnc/grammar_with_trailing.rnc +8 -0
  131. data/spec/fixtures/rnc/main_include_trailing.rnc +3 -0
  132. data/spec/fixtures/rnc/main_with_include.rnc +5 -0
  133. data/spec/fixtures/rnc/test_augment.rnc +10 -0
  134. data/spec/fixtures/rnc/test_isodoc_simple.rnc +9 -0
  135. data/spec/fixtures/rnc/top_level_include.rnc +8 -0
  136. data/spec/fixtures/spectest_external/case_10_4.7/x +3 -0
  137. data/spec/fixtures/spectest_external/case_10_4.7/y +7 -0
  138. data/spec/fixtures/spectest_external/case_11_4.7/x +3 -0
  139. data/spec/fixtures/spectest_external/case_12_4.7/x +3 -0
  140. data/spec/fixtures/spectest_external/case_13_4.7/x +3 -0
  141. data/spec/fixtures/spectest_external/case_13_4.7/y +3 -0
  142. data/spec/fixtures/spectest_external/case_14_4.7/x +7 -0
  143. data/spec/fixtures/spectest_external/case_15_4.7/x +7 -0
  144. data/spec/fixtures/spectest_external/case_16_4.7/x +5 -0
  145. data/spec/fixtures/spectest_external/case_17_4.7/x +5 -0
  146. data/spec/fixtures/spectest_external/case_18_4.7/x +7 -0
  147. data/spec/fixtures/spectest_external/case_19_4.7/level1.rng +9 -0
  148. data/spec/fixtures/spectest_external/case_19_4.7/level2.rng +7 -0
  149. data/spec/fixtures/spectest_external/case_1_4.5/sub1/x +3 -0
  150. data/spec/fixtures/spectest_external/case_1_4.5/sub3/x +3 -0
  151. data/spec/fixtures/spectest_external/case_1_4.5/x +3 -0
  152. data/spec/fixtures/spectest_external/case_20_4.6/x +3 -0
  153. data/spec/fixtures/spectest_external/case_2_4.5/x +3 -0
  154. data/spec/fixtures/spectest_external/case_3_4.6/x +3 -0
  155. data/spec/fixtures/spectest_external/case_4_4.6/x +3 -0
  156. data/spec/fixtures/spectest_external/case_5_4.6/x +1 -0
  157. data/spec/fixtures/spectest_external/case_6_4.6/x +5 -0
  158. data/spec/fixtures/spectest_external/case_7_4.6/x +1 -0
  159. data/spec/fixtures/spectest_external/case_7_4.6/y +1 -0
  160. data/spec/fixtures/spectest_external/case_8_4.7/x +7 -0
  161. data/spec/fixtures/spectest_external/case_9_4.7/x +7 -0
  162. data/spec/fixtures/spectest_external/resources.json +149 -0
  163. data/spec/rng/advanced_rnc_spec.rb +101 -0
  164. data/spec/rng/compacttest_spec.rb +197 -0
  165. data/spec/rng/datatype_declaration_spec.rb +28 -0
  166. data/spec/rng/div_spec.rb +207 -0
  167. data/spec/rng/external_ref_resolver_spec.rb +122 -0
  168. data/spec/rng/metanorma_conversion_spec.rb +159 -0
  169. data/spec/rng/namespace_declaration_spec.rb +60 -0
  170. data/spec/rng/namespace_support_spec.rb +199 -0
  171. data/spec/rng/rnc_parser_spec.rb +498 -22
  172. data/spec/rng/rnc_roundtrip_spec.rb +96 -82
  173. data/spec/rng/rng_generation_spec.rb +288 -0
  174. data/spec/rng/roundtrip_spec.rb +342 -0
  175. data/spec/rng/schema_preamble_spec.rb +145 -0
  176. data/spec/rng/schema_spec.rb +68 -64
  177. data/spec/rng/spectest_spec.rb +168 -90
  178. data/spec/rng_spec.rb +2 -2
  179. data/spec/spec_helper.rb +7 -42
  180. 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