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.
- checksums.yaml +4 -4
- data/lib/rng/external_ref_resolver.rb +86 -157
- data/lib/rng/grammar.rb +8 -0
- data/lib/rng/include_processor.rb +64 -90
- data/lib/rng/rnc_builder.rb +3 -14
- data/lib/rng/rnc_parser.rb +36 -16
- data/lib/rng/rnc_to_rng_converter.rb +124 -114
- data/lib/rng/version.rb +1 -1
- data/lib/rng.rb +7 -5
- data/rng.gemspec +1 -1
- data/spec/fixtures/external/order_lib.rng +13 -0
- data/spec/fixtures/external/order_main.rng +3 -0
- data/spec/rng/advanced_rnc_spec.rb +123 -0
- data/spec/rng/external_ref_resolver_spec.rb +23 -0
- data/spec/rng/parse_rnc_spec.rb +75 -0
- data/spec/rng/rnc_parser_spec.rb +48 -0
- data/spec/rng/rnc_roundtrip_spec.rb +77 -0
- data/spec/rng/schema_spec.rb +18 -0
- metadata +7 -5
- data/lib/rng/parse_rnc.rb +0 -26
data/spec/rng/rnc_parser_spec.rb
CHANGED
|
@@ -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 {
|
data/spec/rng/schema_spec.rb
CHANGED
|
@@ -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.
|
|
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-
|
|
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.
|
|
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.
|
|
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
|