rng 0.3.7 → 0.3.8

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.
@@ -182,6 +182,54 @@ RSpec.describe Rng::RncParser do
182
182
  end
183
183
  end
184
184
 
185
+ context 'with occurrence markers on leaf patterns' do
186
+ def root_element_for(form, extra = '')
187
+ rnc = "start = element root { #{form} }\n#{extra}"
188
+ described_class.parse(rnc).start.first.element
189
+ end
190
+
191
+ # On Element, :ref is a collection slot but :text/:empty/etc. are singular,
192
+ # so "leaf is empty" needs to handle both shapes uniformly.
193
+ def leaf_empty?(container, slot)
194
+ v = container.public_send(slot)
195
+ v.nil? || (v.respond_to?(:empty?) && v.empty?)
196
+ end
197
+
198
+ # form => { slot: model accessor on Element/wrapper, extra: extra RNC needed }
199
+ patterns = {
200
+ 'item' => { slot: :ref, extra: "item = element item { text }\n" },
201
+ 'text' => { slot: :text },
202
+ 'empty' => { slot: :empty },
203
+ 'notAllowed' => { slot: :notAllowed },
204
+ '"foo"' => { slot: :value },
205
+ 'xsd:string' => { slot: :data }
206
+ }
207
+ wrappers = { '*' => :zeroOrMore, '+' => :oneOrMore, '?' => :optional }
208
+
209
+ patterns.each do |form, config|
210
+ slot = config[:slot]
211
+ extra = config[:extra].to_s
212
+
213
+ it "emits a bare <#{slot}> when there is no occurrence marker on #{form}" do
214
+ element = root_element_for(form, extra)
215
+ expect(leaf_empty?(element, slot)).to be(false)
216
+ expect(element.zeroOrMore).to be_empty
217
+ expect(element.oneOrMore).to be_empty
218
+ expect(element.optional).to be_empty
219
+ end
220
+
221
+ wrappers.each do |marker, wrapper|
222
+ it "wraps #{form}#{marker} in <#{wrapper}>" do
223
+ element = root_element_for("#{form}#{marker}", extra)
224
+ wrapper_coll = element.public_send(wrapper)
225
+ expect(wrapper_coll.length).to eq(1)
226
+ expect(wrapper_coll.first.public_send(slot).length).to eq(1)
227
+ expect(leaf_empty?(element, slot)).to be(true)
228
+ end
229
+ end
230
+ end
231
+ end
232
+
185
233
  context 'with pattern references in choice' do
186
234
  let(:input) do
187
235
  <<~RNC
@@ -97,6 +97,83 @@ RSpec.describe 'RNG to RNC Round-trip Tests' do
97
97
  expect(xml).to include('<choice')
98
98
  end
99
99
 
100
+ it 'preserves a mixed attribute choice of text and value' do
101
+ rnc = <<~RNC
102
+ start = element doc {
103
+ attribute a { text | "x" }
104
+ }
105
+ RNC
106
+ xml = Rng.parse_rnc(rnc).to_xml
107
+ expect(xml).to include('<choice>')
108
+ expect(xml).to include('<text>')
109
+ expect(xml).to include('<value>x</value>')
110
+ end
111
+
112
+ it 'preserves a mixed attribute choice of value and ref' do
113
+ rnc = <<~RNC
114
+ start = element doc {
115
+ attribute a { "x" | foo }
116
+ }
117
+ foo = element foo { text }
118
+ RNC
119
+ xml = Rng.parse_rnc(rnc).to_xml
120
+ expect(xml).to include('<choice>')
121
+ expect(xml).to include('<value>x</value>')
122
+ expect(xml).to include('<ref name="foo"/>')
123
+ end
124
+
125
+ it 'preserves a mixed attribute choice of text and ref' do
126
+ rnc = <<~RNC
127
+ start = element doc {
128
+ attribute a { text | foo }
129
+ }
130
+ foo = element foo { text }
131
+ RNC
132
+ xml = Rng.parse_rnc(rnc).to_xml
133
+ expect(xml).to include('<choice>')
134
+ expect(xml).to include('<text>')
135
+ expect(xml).to include('<ref name="foo"/>')
136
+ end
137
+
138
+ it 'emits a mixed attribute choice of text and value back to RNC' do
139
+ rng = <<~RNG
140
+ <grammar xmlns="http://relaxng.org/ns/structure/1.0">
141
+ <start><element name="doc"><attribute name="indent">
142
+ <choice><text/><value>adaptive</value></choice>
143
+ </attribute></element></start>
144
+ </grammar>
145
+ RNG
146
+ rnc = Rng.to_rnc(Rng.parse(rng))
147
+ expect(rnc).to match(/attribute indent \{[^}]*text[^}]*\}/)
148
+ expect(rnc).to include('"adaptive"')
149
+ end
150
+
151
+ it 'emits a mixed attribute choice of value and ref back to RNC' do
152
+ rng = <<~RNG
153
+ <grammar xmlns="http://relaxng.org/ns/structure/1.0">
154
+ <start><element name="doc"><attribute name="a">
155
+ <choice><value>x</value><ref name="foo"/></choice>
156
+ </attribute></element></start>
157
+ <define name="foo"><text/></define>
158
+ </grammar>
159
+ RNG
160
+ rnc = Rng.to_rnc(Rng.parse(rng))
161
+ expect(rnc).to match(/attribute a \{[^}]*foo[^}]*\}/)
162
+ expect(rnc).to include('"x"')
163
+ end
164
+
165
+ it 'round-trips a mixed attribute choice of text and value through RNC and RNG' do
166
+ rnc = <<~RNC
167
+ start = element doc {
168
+ attribute indent { text | "adaptive" }
169
+ }
170
+ RNC
171
+ regenerated = Rng.to_rnc(Rng.parse(Rng.parse_rnc(rnc).to_xml))
172
+ # Both members of the choice must survive a full RNC -> RNG -> RNC trip
173
+ expect(regenerated).to match(/attribute indent \{[^}]*text[^}]*\}/)
174
+ expect(regenerated).to include('"adaptive"')
175
+ end
176
+
100
177
  it 'preserves interleave' do
101
178
  rnc = <<~RNC
102
179
  start = element foo {
@@ -81,6 +81,24 @@ RSpec.describe Rng::Grammar do
81
81
  expect(start_element).to be_a(Rng::Element)
82
82
  expect(start_element.attr_name).to eq('addressBook')
83
83
  end
84
+
85
+ it 'resolves includes for in-memory RNC when location is the source directory' do
86
+ main_path = File.expand_path('spec/fixtures/rnc/main_with_include.rnc')
87
+ parsed = Rng.parse_rnc(File.read(main_path), location: File.dirname(main_path))
88
+
89
+ expect(parsed).to be_a(described_class)
90
+ expect(parsed.define.map(&:name)).to include('BasePattern', 'ExtendedPattern')
91
+ expect(parsed.start.first.element.attr_name).to eq('doc')
92
+ end
93
+
94
+ it 'matches parse_file include resolution for in-memory RNC when location is the source directory' do
95
+ main_path = File.expand_path('spec/fixtures/rnc/main_include_trailing.rnc')
96
+ from_memory = Rng.parse_rnc(File.read(main_path), location: File.dirname(main_path))
97
+ from_file = Rng.parse_file(main_path)
98
+
99
+ expect(from_memory.start.first.element.attr_name).to eq(from_file.start.first.element.attr_name)
100
+ expect(from_memory.define.map(&:name).sort).to eq(from_file.define.map(&:name).sort)
101
+ end
84
102
  end
85
103
 
86
104
  describe 'RNG to RNC conversion' do
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rng
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.7
4
+ version: 0.3.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-05-29 00:00:00.000000000 Z
11
+ date: 2026-10-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: lutaml-model
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 0.8.0
19
+ version: 0.8.114
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: 0.8.0
26
+ version: 0.8.114
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: nokogiri
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -136,7 +136,6 @@ files:
136
136
  - lib/rng/optional.rb
137
137
  - lib/rng/param.rb
138
138
  - lib/rng/parent_ref.rb
139
- - lib/rng/parse_rnc.rb
140
139
  - lib/rng/parse_tree_processor.rb
141
140
  - lib/rng/pattern.rb
142
141
  - lib/rng/ref.rb
@@ -166,6 +165,8 @@ files:
166
165
  - spec/fixtures/external/nested_chain.rng
167
166
  - spec/fixtures/external/nested_leaf.rng
168
167
  - spec/fixtures/external/nested_mid.rng
168
+ - spec/fixtures/external/order_lib.rng
169
+ - spec/fixtures/external/order_main.rng
169
170
  - spec/fixtures/metanorma/3gpp.rnc
170
171
  - spec/fixtures/metanorma/3gpp.rng
171
172
  - spec/fixtures/metanorma/basicdoc.rnc
@@ -254,6 +255,7 @@ files:
254
255
  - spec/rng/metanorma_conversion_spec.rb
255
256
  - spec/rng/namespace_declaration_spec.rb
256
257
  - spec/rng/namespace_support_spec.rb
258
+ - spec/rng/parse_rnc_spec.rb
257
259
  - spec/rng/rnc_parser_spec.rb
258
260
  - spec/rng/rnc_roundtrip_spec.rb
259
261
  - spec/rng/rng_generation_spec.rb
data/lib/rng/parse_rnc.rb DELETED
@@ -1,26 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Rng
4
- # RNC Parser module
5
- # Provides functionality to parse RELAX NG Compact Syntax (RNC) files
6
- module ParseRnc
7
- class << self
8
- # Parse RNC syntax and return an RNG schema object
9
- # @param rnc_string [String] The RNC content to parse
10
- # @return [Rng::Grammar] The parsed schema object
11
- def parse(rnc_string)
12
- # This is a placeholder implementation
13
- # The actual parsing logic would need to be implemented
14
-
15
- # For now, we delegate to RncParser which is our internal implementation
16
- # In the future, this could be expanded with additional functionality
17
- RncParser.parse(rnc_string)
18
- end
19
- end
20
- end
21
-
22
- # Add class-level parsing method
23
- def self.parse_rnc(rnc_string)
24
- ParseRnc.parse(rnc_string)
25
- end
26
- end