rng 0.1.2 → 0.3.4

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 +602 -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 +6 -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 +7 -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
@@ -1,120 +1,134 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "spec_helper"
3
+ require 'spec_helper'
4
4
 
5
- RSpec.describe "RNG to RNC Round-trip Tests" do
6
- describe "RNC Parser" do
7
- it "exists as a class" do
8
- expect(defined?(Rng::RncParser)).to eq("constant")
5
+ RSpec.describe 'RNG to RNC Round-trip Tests' do
6
+ describe 'RNC Parser' do
7
+ it 'exists as a class' do
8
+ expect(defined?(Rng::RncParser)).to eq('constant')
9
9
  end
10
10
 
11
- it "has a parse method" do
11
+ it 'has a parse method' do
12
12
  expect(Rng::RncParser).to respond_to(:parse)
13
13
  end
14
14
  end
15
15
 
16
- describe "RNG to RNC Conversion" do
17
- context "with simple schemas" do
18
- let(:simple_rng) do
19
- <<~XML
20
- <element name="foo" xmlns="http://relaxng.org/ns/structure/1.0">
21
- <empty/>
22
- </element>
23
- XML
16
+ describe 'RNG to RNC Conversion' do
17
+ context 'with the address book schema' do
18
+ let(:address_book_rng) do
19
+ File.read('spec/fixtures/rng/address_book.rng')
24
20
  end
25
21
 
26
- it "can convert RNG to RNC" do
27
- skip "RNG to RNC conversion not yet implemented"
28
- rng_schema = Rng::Grammar.from_xml(simple_rng)
22
+ let(:address_book_rnc) do
23
+ File.read('spec/fixtures/rnc/address_book.rnc')
24
+ end
25
+
26
+ it 'can convert RNG to RNC' do
27
+ rng_schema = Rng.parse(address_book_rng)
29
28
  expect(rng_schema).not_to be_nil
30
29
 
31
- # This would be the method to convert RNG to RNC
32
- # rnc = Rng.to_rnc(rng_schema)
33
- # expect(rnc).to include("element foo { empty }")
30
+ rnc = Rng.to_rnc(rng_schema)
31
+ expect(rnc).to include('element addressBook')
32
+ expect(rnc).to include('element card')
33
+ expect(rnc).to include('element name')
34
34
  end
35
35
 
36
- it "can parse RNC back to RNG" do
37
- skip "RNC parser not yet implemented"
38
- "element foo { empty }"
36
+ it 'can parse RNC back to RNG' do
37
+ rng_schema = Rng.parse_rnc(address_book_rnc)
38
+ expect(rng_schema).to be_a(Rng::Grammar)
39
39
 
40
- # This would be the method to parse RNC to RNG model
41
- # rng_schema = Rng.parse_rnc(simple_rnc)
42
- # expect(rng_schema).to be_a(Rng::Grammar)
43
- # expect(rng_schema.element.name).to eq("foo")
40
+ xml = rng_schema.to_xml
41
+ expect(xml).to include('<grammar')
42
+ expect(xml).to include('name="addressBook"')
44
43
  end
45
44
 
46
- it "supports round-trip conversion" do
47
- skip "Round-trip conversion not yet implemented"
48
- # rng_schema = Rng::Grammar.from_xml(simple_rng)
49
- # rnc = Rng.to_rnc(rng_schema)
50
- # rng_schema_from_rnc = Rng.parse_rnc(rnc)
45
+ it 'supports round-trip conversion' do
46
+ rng_schema = Rng.parse(address_book_rng)
47
+ rnc = Rng.to_rnc(rng_schema)
48
+ rng_schema_from_rnc = Rng.parse_rnc(rnc)
51
49
 
52
- # Verify key properties are preserved
53
- # expect(rng_schema_from_rnc.element.name).to eq(rng_schema.element.name)
50
+ expect(rng_schema_from_rnc).to be_a(Rng::Grammar)
51
+
52
+ xml = rng_schema_from_rnc.to_xml
53
+ expect(xml).to include('name="addressBook"')
54
+ expect(xml).to include('name="card"')
54
55
  end
55
56
  end
56
57
  end
57
58
 
58
- describe "RNC to RNG Conversion" do
59
- context "with the address book example" do
60
- # This is a sample RNC representation of an address book
61
- let(:address_book_rnc) do
62
- <<~RNC
63
- element addressBook {
64
- element card {
65
- element name { text },
66
- element email { text },
67
- element note { text }?
68
- }*
69
- }
70
- RNC
71
- end
72
-
73
- # Sample expected XML output after conversion
74
- let(:expected_rng_pattern) do
75
- %r{<element.*name="addressBook".*>.*<element.*name="card".*>.*<element.*name="name".*>.*<text/>.*</element>.*<element.*name="email".*>.*<text/>.*</element>.*<optional>.*<element.*name="note".*>.*<text/>.*</element>.*</optional>.*</element>.*</element>}m
76
- end
77
-
78
- it "can convert RNC to RNG" do
79
- skip "RNC to RNG conversion not yet implemented"
80
- # xml_output = Rng::RncParser.parse(address_book_rnc)
81
- # expect(xml_output).to match(expected_rng_pattern)
82
-
83
- # Check if we can load the generated XML as a schema
84
- # rng_schema = Rng::Grammar.from_xml(xml_output)
85
- # expect(rng_schema).not_to be_nil
59
+ describe 'RNC to RNG Conversion' do
60
+ context 'with the address book example' do
61
+ it 'can convert RNC to RNG' do
62
+ rnc = File.read('spec/fixtures/rnc/address_book.rnc')
63
+ xml_output = Rng.parse_rnc(rnc).to_xml
64
+ expect(xml_output).to include('<grammar')
65
+ expect(xml_output).to include('xmlns="http://relaxng.org/ns/structure/1.0"')
66
+
67
+ # Verify it produces parseable RNG XML
68
+ rng_schema = Rng.parse(xml_output)
69
+ expect(rng_schema).to be_a(Rng::Grammar)
86
70
  end
87
71
  end
88
72
  end
89
73
 
90
- describe "Round-trip with complex features" do
91
- # Test cases for more complex RELAX NG features that should be preserved
92
- # in round-trip conversion
93
-
94
- describe "RELAX NG pattern features" do
95
- it "preserves attribute definitions" do
96
- skip "Feature not yet implemented"
97
- # Test attribute handling
74
+ describe 'Round-trip with complex features' do
75
+ describe 'RELAX NG pattern features' do
76
+ it 'preserves attribute definitions' do
77
+ rnc = <<~RNC
78
+ start = element foo {
79
+ attribute bar { text },
80
+ element baz { text }
81
+ }
82
+ RNC
83
+ grammar = Rng.parse_rnc(rnc)
84
+ xml = grammar.to_xml
85
+ expect(xml).to include('<attribute')
86
+ expect(xml).to include('name="bar"')
98
87
  end
99
88
 
100
- it "preserves choices" do
101
- skip "Feature not yet implemented"
102
- # Test choice patterns
89
+ it 'preserves choices' do
90
+ rnc = <<~RNC
91
+ start = element foo {
92
+ (element a { text } | element b { text })
93
+ }
94
+ RNC
95
+ grammar = Rng.parse_rnc(rnc)
96
+ xml = grammar.to_xml
97
+ expect(xml).to include('<choice')
103
98
  end
104
99
 
105
- it "preserves interleave" do
106
- skip "Feature not yet implemented"
107
- # Test interleave patterns
100
+ it 'preserves interleave' do
101
+ rnc = <<~RNC
102
+ start = element foo {
103
+ element a { text } & element b { text }
104
+ }
105
+ RNC
106
+ grammar = Rng.parse_rnc(rnc)
107
+ xml = grammar.to_xml
108
+ expect(xml).to include('<interleave')
108
109
  end
109
110
 
110
- it "preserves data types" do
111
- skip "Feature not yet implemented"
112
- # Test datatype definitions
111
+ it 'preserves data types' do
112
+ rnc = <<~RNC
113
+ start = element foo {
114
+ attribute count { xsd:integer }
115
+ }
116
+ RNC
117
+ grammar = Rng.parse_rnc(rnc)
118
+ xml = grammar.to_xml
119
+ expect(xml).to include('<data')
120
+ expect(xml).to include('integer')
113
121
  end
114
122
 
115
- it "preserves namespaces" do
116
- skip "Feature not yet implemented"
117
- # Test namespace handling
123
+ it 'preserves namespaces' do
124
+ rnc = <<~RNC
125
+ default namespace = "http://example.org"
126
+ start = element foo { text }
127
+ RNC
128
+ grammar = Rng.parse_rnc(rnc)
129
+ xml = grammar.to_xml
130
+ expect(xml).to include('<grammar')
131
+ expect(grammar.ns).to eq('http://example.org')
118
132
  end
119
133
  end
120
134
  end
@@ -0,0 +1,288 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe 'RNG Generation' do
6
+ describe 'Grammar.to_xml()' do
7
+ context 'with simple manually constructed schema' do
8
+ it 'generates valid RNG XML from a Grammar object' do
9
+ # Create simple grammar manually
10
+ grammar = Rng::Grammar.new
11
+ grammar.datatypeLibrary = 'http://www.w3.org/2001/XMLSchema-datatypes'
12
+
13
+ # Create start with element
14
+ start = Rng::Start.new
15
+ element = Rng::Element.new
16
+ element.attr_name = 'root'
17
+ text = Rng::Text.new
18
+ element.text = text
19
+ start.element = element
20
+ grammar.start = [start]
21
+
22
+ # Generate XML (note: to_xml() doesn't include XML declaration)
23
+ xml = grammar.to_xml
24
+
25
+ # Verify structure
26
+ expect(xml).to include('<grammar')
27
+ expect(xml).to include('xmlns="http://relaxng.org/ns/structure/1.0"')
28
+ expect(xml).to include('<start')
29
+ expect(xml).to include('<element name="root"')
30
+ expect(xml).to include('<text')
31
+ expect(xml).to include('</element>')
32
+ expect(xml).to include('</start>')
33
+ expect(xml).to include('</grammar>')
34
+ end
35
+
36
+ it 'handles namespace attributes correctly' do
37
+ grammar = Rng::Grammar.new
38
+ grammar.ns = 'http://example.com/ns'
39
+
40
+ xml = grammar.to_xml
41
+ expect(xml).to include('ns="http://example.com/ns"')
42
+ end
43
+
44
+ it 'handles datatype library attributes correctly' do
45
+ grammar = Rng::Grammar.new
46
+ grammar.datatypeLibrary = 'http://www.w3.org/2001/XMLSchema-datatypes'
47
+
48
+ xml = grammar.to_xml
49
+ expect(xml).to include('datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes"')
50
+ end
51
+ end
52
+
53
+ context 'with parsed RNG schema' do
54
+ let(:address_book_rng) { File.read('spec/fixtures/rng/address_book.rng') }
55
+
56
+ it 'regenerates XML from parsed RNG' do
57
+ parsed = Rng.parse(address_book_rng)
58
+ regenerated = parsed.to_xml
59
+
60
+ # Verify it's valid XML (note: to_xml() doesn't include XML declaration)
61
+ expect(regenerated).to include('<grammar')
62
+ expect(regenerated).to include('xmlns="http://relaxng.org/ns/structure/1.0"')
63
+
64
+ # Verify structure
65
+ expect(regenerated).to include('<start')
66
+ expect(regenerated).to include('<element name="addressBook"')
67
+ expect(regenerated).to include('name="cardContent"')
68
+ end
69
+
70
+ it 'maintains semantic equivalence (round-trip)' do
71
+ parsed = Rng.parse(address_book_rng)
72
+ regenerated = parsed.to_xml
73
+ reparsed = Rng.parse(regenerated)
74
+
75
+ # Verify key structure is maintained
76
+ expect(reparsed.start.first.element.attr_name).to eq('addressBook')
77
+ expect(reparsed.define.first.name).to eq('cardContent')
78
+ end
79
+ end
80
+
81
+ context 'with parsed RNC schema' do
82
+ let(:address_book_rnc) { File.read('spec/fixtures/rnc/address_book.rnc') }
83
+
84
+ it 'generates RNG XML from parsed RNC' do
85
+ parsed = Rng.parse_rnc(address_book_rnc)
86
+ xml = parsed.to_xml
87
+
88
+ # Verify it's valid RNG XML (note: to_xml() doesn't include XML declaration)
89
+ expect(xml).to include('<grammar')
90
+ expect(xml).to include('xmlns="http://relaxng.org/ns/structure/1.0"')
91
+ expect(xml).to include('<start')
92
+ expect(xml).to include('<element name="addressBook"')
93
+ # NOTE: Current RNC parser doesn't populate Grammar.define array
94
+ # It inlines pattern definitions instead of creating separate <define> elements
95
+ expect(xml).to include('<ref name="cardContent"')
96
+ end
97
+
98
+ it 'produces semantically valid schema' do
99
+ parsed = Rng.parse_rnc(address_book_rnc)
100
+ xml = parsed.to_xml
101
+ reparsed = Rng.parse(xml)
102
+
103
+ # Verify structure
104
+ expect(reparsed).to be_a(Rng::Grammar)
105
+ expect(reparsed.start.first.element.attr_name).to eq('addressBook')
106
+ end
107
+ end
108
+
109
+ context 'with special attribute values' do
110
+ it 'handles nil namespace (attribute omitted)' do
111
+ grammar = Rng::Grammar.new
112
+ grammar.ns = nil
113
+
114
+ xml = grammar.to_xml
115
+ # nil values cause the attribute to be omitted
116
+ expect(xml).not_to match(/\bns="/)
117
+ end
118
+
119
+ it 'handles nil datatype library (attribute omitted)' do
120
+ grammar = Rng::Grammar.new
121
+ grammar.datatypeLibrary = nil
122
+
123
+ xml = grammar.to_xml
124
+ # nil values cause the attribute to be omitted
125
+ expect(xml).not_to include('datatypeLibrary=')
126
+ end
127
+
128
+ it 'handles empty strings correctly' do
129
+ grammar = Rng::Grammar.new
130
+ grammar.ns = ''
131
+
132
+ xml = grammar.to_xml
133
+ expect(xml).to include('ns=""')
134
+ end
135
+ end
136
+
137
+ context 'with complex patterns' do
138
+ it 'handles choice patterns' do
139
+ grammar = Rng::Grammar.new
140
+ start = Rng::Start.new
141
+ choice = Rng::Choice.new
142
+
143
+ elem1 = Rng::Element.new
144
+ elem1.attr_name = 'option1'
145
+ elem1.text = Rng::Text.new
146
+
147
+ elem2 = Rng::Element.new
148
+ elem2.attr_name = 'option2'
149
+ elem2.text = Rng::Text.new
150
+
151
+ choice.element = [elem1, elem2]
152
+ start.choice = choice
153
+ grammar.start = [start]
154
+
155
+ xml = grammar.to_xml
156
+ expect(xml).to include('<choice>')
157
+ expect(xml).to include('<element name="option1">')
158
+ expect(xml).to include('<element name="option2">')
159
+ end
160
+
161
+ it 'handles group patterns' do
162
+ grammar = Rng::Grammar.new
163
+ start = Rng::Start.new
164
+ group = Rng::Group.new
165
+
166
+ elem1 = Rng::Element.new
167
+ elem1.attr_name = 'first'
168
+ elem1.text = Rng::Text.new
169
+
170
+ elem2 = Rng::Element.new
171
+ elem2.attr_name = 'second'
172
+ elem2.text = Rng::Text.new
173
+
174
+ group.element = [elem1, elem2]
175
+ start.group = group
176
+ grammar.start = [start]
177
+
178
+ xml = grammar.to_xml
179
+ expect(xml).to include('<group>')
180
+ expect(xml).to include('<element name="first">')
181
+ expect(xml).to include('<element name="second">')
182
+ end
183
+
184
+ it 'handles optional patterns' do
185
+ grammar = Rng::Grammar.new
186
+ start = Rng::Start.new
187
+ optional = Rng::Optional.new
188
+
189
+ elem = Rng::Element.new
190
+ elem.attr_name = 'optional'
191
+ elem.text = Rng::Text.new
192
+ optional.element = elem
193
+
194
+ start.optional = optional
195
+ grammar.start = [start]
196
+
197
+ xml = grammar.to_xml
198
+ expect(xml).to include('<optional>')
199
+ expect(xml).to include('<element name="optional">')
200
+ end
201
+
202
+ it 'handles zeroOrMore patterns' do
203
+ grammar = Rng::Grammar.new
204
+ start = Rng::Start.new
205
+ zero_or_more = Rng::ZeroOrMore.new
206
+
207
+ elem = Rng::Element.new
208
+ elem.attr_name = 'item'
209
+ elem.text = Rng::Text.new
210
+ zero_or_more.element = elem
211
+
212
+ start.zeroOrMore = zero_or_more
213
+ grammar.start = [start]
214
+
215
+ xml = grammar.to_xml
216
+ expect(xml).to include('<zeroOrMore>')
217
+ expect(xml).to include('<element name="item">')
218
+ end
219
+
220
+ it 'handles oneOrMore patterns' do
221
+ grammar = Rng::Grammar.new
222
+ start = Rng::Start.new
223
+ one_or_more = Rng::OneOrMore.new
224
+
225
+ elem = Rng::Element.new
226
+ elem.attr_name = 'item'
227
+ elem.text = Rng::Text.new
228
+ one_or_more.element = elem
229
+
230
+ start.oneOrMore = [one_or_more]
231
+ grammar.start = [start]
232
+
233
+ xml = grammar.to_xml
234
+ expect(xml).to include('<oneOrMore>')
235
+ expect(xml).to include('<element name="item">')
236
+ end
237
+ end
238
+
239
+ context 'with references' do
240
+ it 'handles ref patterns' do
241
+ grammar = Rng::Grammar.new
242
+
243
+ # Define pattern
244
+ define = Rng::Define.new
245
+ define.name = 'myPattern'
246
+ elem = Rng::Element.new
247
+ elem.attr_name = 'test'
248
+ elem.text = Rng::Text.new
249
+ define.element = elem
250
+ grammar.define = [define]
251
+
252
+ # Reference pattern
253
+ start = Rng::Start.new
254
+ ref = Rng::Ref.new
255
+ ref.name = 'myPattern'
256
+ start.ref = ref
257
+ grammar.start = [start]
258
+
259
+ xml = grammar.to_xml
260
+ expect(xml).to match(/<define[^>]+name="myPattern"/)
261
+ expect(xml).to include('<ref name="myPattern"')
262
+ end
263
+ end
264
+
265
+ context 'with attributes' do
266
+ it 'handles attribute patterns' do
267
+ grammar = Rng::Grammar.new
268
+ start = Rng::Start.new
269
+
270
+ elem = Rng::Element.new
271
+ elem.attr_name = 'root'
272
+
273
+ attr = Rng::Attribute.new
274
+ attr.attr_name = 'id'
275
+ attr.text = Rng::Text.new
276
+ elem.attribute = attr
277
+
278
+ start.element = elem
279
+ grammar.start = [start]
280
+
281
+ xml = grammar.to_xml
282
+ expect(xml).to include('<element name="root"')
283
+ expect(xml).to include('<attribute name="id"')
284
+ expect(xml).to include('<text/')
285
+ end
286
+ end
287
+ end
288
+ end