rng 0.3.8 → 0.3.9
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/Gemfile +2 -1
- data/lib/rng/external_ref_resolver.rb +2 -2
- data/lib/rng/include_processor.rb +12 -1
- data/lib/rng/parse_tree_processor.rb +8 -2
- data/lib/rng/rnc_parser.rb +0 -1
- data/lib/rng/rnc_to_rng_converter.rb +40 -31
- data/lib/rng/schema_validator.rb +33 -37
- data/lib/rng/test_suite_parser.rb +5 -5
- data/lib/rng/version.rb +1 -1
- data/rng.gemspec +1 -1
- data/spec/rng/include_processor_spec.rb +61 -0
- data/spec/rng/rnc_parser_spec.rb +8 -4
- metadata +4 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b2843a057a16c8935ca21fb4c2477a708910d5a6e7d06a362de75bbcd8f5c916
|
|
4
|
+
data.tar.gz: afe6c5abef5589d6343c4f78dc53b88d5a9dc7596d1877c47d24f015e9e2dc16
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 41f2961661e30a55299210fa900f87f928de9e8765fd4627132dc1e097166d6093d7daca35a83fcb5050a21ca1045b356bb65377f866f23e2ace45e40b1c49fb
|
|
7
|
+
data.tar.gz: d309142ce39bb44c264eec7fea4c93d14b739e9c245099662bae705304e6cbbf9b6c2ec2f201baa33e15f36715f87c7b8d4e1a870949f38a52f505f189080886
|
data/Gemfile
CHANGED
|
@@ -190,7 +190,7 @@ module Rng
|
|
|
190
190
|
# Deep dup a pattern object
|
|
191
191
|
#
|
|
192
192
|
# Uses recursive copying instead of Marshal to handle objects
|
|
193
|
-
# containing
|
|
193
|
+
# containing Moxml element nodes (stored in element_order
|
|
194
194
|
# by lutaml-model), which cannot be serialized by Marshal.
|
|
195
195
|
#
|
|
196
196
|
# @param obj [Object] Object to deep copy
|
|
@@ -219,7 +219,7 @@ module Rng
|
|
|
219
219
|
|
|
220
220
|
# Carry lutaml's document-order ivars onto a deep-dup'd object so to_xml
|
|
221
221
|
# emits children in source order. These hold Lutaml::Xml::Element value
|
|
222
|
-
# objects (name/type), not live
|
|
222
|
+
# objects (name/type), not live Moxml nodes.
|
|
223
223
|
#
|
|
224
224
|
# @param source [Object] Original object
|
|
225
225
|
# @param result [Object] Deep-dup'd copy
|
|
@@ -365,7 +365,18 @@ module Rng
|
|
|
365
365
|
# @return [Hash, nil] Parsed override with :start and :definitions, or nil
|
|
366
366
|
def parse_override(override)
|
|
367
367
|
return nil unless override
|
|
368
|
-
|
|
368
|
+
|
|
369
|
+
# A non-raw override arrives already expanded as {start:, patterns:}
|
|
370
|
+
# (the parse tree spells defines :patterns); the merge logic consumes
|
|
371
|
+
# :definitions, so normalize here or include-block overrides never
|
|
372
|
+
# apply and the included grammar's base definition wins silently.
|
|
373
|
+
unless override[:raw_override]
|
|
374
|
+
defs = override[:definitions] || override[:patterns]
|
|
375
|
+
result = {}
|
|
376
|
+
result[:start] = override[:start] if override[:start]
|
|
377
|
+
result[:definitions] = defs if defs && !defs.empty?
|
|
378
|
+
return result.empty? ? nil : result
|
|
379
|
+
end
|
|
369
380
|
|
|
370
381
|
raw = extract_string(override[:raw_override])
|
|
371
382
|
return nil if raw.nil? || raw.strip.empty?
|
|
@@ -433,8 +433,13 @@ module Rng
|
|
|
433
433
|
grammar = @tree[:inner_grammar].dup
|
|
434
434
|
|
|
435
435
|
# Normalize :includes and :patterns into :definitions
|
|
436
|
+
#
|
|
437
|
+
# :definitions may already hold the include-merge result
|
|
438
|
+
# (IncludeProcessor#merge_included_grammar writes it); fold the
|
|
439
|
+
# includes and own patterns INTO it instead of overwriting, or every
|
|
440
|
+
# grammar-block file with includes loses all of its included defines.
|
|
436
441
|
if grammar.key?(:includes) || grammar.key?(:patterns)
|
|
437
|
-
definitions = []
|
|
442
|
+
definitions = Array(grammar[:definitions])
|
|
438
443
|
definitions.concat(grammar.delete(:includes)) if grammar[:includes]
|
|
439
444
|
definitions.concat(grammar.delete(:patterns)) if grammar[:patterns]
|
|
440
445
|
grammar[:definitions] = definitions unless definitions.empty?
|
|
@@ -451,8 +456,9 @@ module Rng
|
|
|
451
456
|
grammar = @tree.dup
|
|
452
457
|
|
|
453
458
|
# Normalize :includes and :patterns into :definitions for flat grammars too
|
|
459
|
+
# (see build_grammar_block_grammar — never discard existing definitions)
|
|
454
460
|
if grammar.key?(:includes) || grammar.key?(:patterns)
|
|
455
|
-
definitions = []
|
|
461
|
+
definitions = Array(grammar[:definitions])
|
|
456
462
|
definitions.concat(grammar.delete(:includes)) if grammar[:includes]
|
|
457
463
|
definitions.concat(grammar.delete(:patterns)) if grammar[:patterns]
|
|
458
464
|
grammar[:definitions] = definitions unless definitions.empty?
|
data/lib/rng/rnc_parser.rb
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require '
|
|
3
|
+
require 'moxml'
|
|
4
4
|
require 'set'
|
|
5
5
|
|
|
6
6
|
module Rng
|
|
7
7
|
# RncToRngConverter converts RNC parse trees to RNG XML format.
|
|
8
8
|
#
|
|
9
9
|
# This class takes the parse tree output from the Parslet RNC parser and
|
|
10
|
-
# converts it to RNG XML using
|
|
10
|
+
# converts it to RNG XML using Moxml's Nokogiri-compatible XML builder.
|
|
11
|
+
# The resulting XML
|
|
11
12
|
# can then be deserialized into Grammar objects using Lutaml::Model.
|
|
12
13
|
#
|
|
13
14
|
# @example Convert a parse tree to RNG XML
|
|
@@ -38,30 +39,32 @@ module Rng
|
|
|
38
39
|
# Validate that element notations in preamble annotations are only used with element/attribute patterns
|
|
39
40
|
validate_preamble_element_notation_usage(tree[:preamble_items], tree[:definitions])
|
|
40
41
|
|
|
41
|
-
|
|
42
|
+
# Build without Context#build: its block is instance_eval'd, which
|
|
43
|
+
# would re-bind self and turn every converter helper call into a
|
|
44
|
+
# builder method_missing tag. A lambda preserves self; Moxml's tag
|
|
45
|
+
# blocks use block.call, so caller context survives down the tree.
|
|
46
|
+
builder = Moxml::Builder.new(Moxml::Context.new)
|
|
47
|
+
emit = lambda do |xml|
|
|
48
|
+
xml.declaration
|
|
42
49
|
grammar_attrs = { xmlns: 'http://relaxng.org/ns/structure/1.0' }
|
|
43
50
|
if @has_documentation
|
|
44
51
|
grammar_attrs[:'xmlns:a'] =
|
|
45
52
|
'http://relaxng.org/ns/compatibility/annotations/1.0'
|
|
46
53
|
end
|
|
54
|
+
if tree[:namespace]
|
|
55
|
+
grammar_attrs[:ns] =
|
|
56
|
+
process_string_literal(tree[:namespace][:namespace_uri])
|
|
57
|
+
end
|
|
58
|
+
if tree[:datatype_library]
|
|
59
|
+
grammar_attrs[:datatypeLibrary] =
|
|
60
|
+
process_string_literal(tree[:datatype_library][:uri])
|
|
61
|
+
elsif tree[:datatype_map] && !tree[:datatype_map].empty?
|
|
62
|
+
# From ParseTreeProcessor - datatype_map is {prefix => uri}
|
|
63
|
+
# Use the first (typically only) datatype library
|
|
64
|
+
grammar_attrs[:datatypeLibrary] = tree[:datatype_map].values.first
|
|
65
|
+
end
|
|
47
66
|
|
|
48
67
|
xml.grammar(grammar_attrs) do
|
|
49
|
-
# Add namespace if present
|
|
50
|
-
if tree[:namespace]
|
|
51
|
-
xml.parent[:ns] =
|
|
52
|
-
process_string_literal(tree[:namespace][:namespace_uri])
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
# Add datatype library if present
|
|
56
|
-
if tree[:datatype_library]
|
|
57
|
-
xml.parent[:datatypeLibrary] =
|
|
58
|
-
process_string_literal(tree[:datatype_library][:uri])
|
|
59
|
-
elsif tree[:datatype_map] && !tree[:datatype_map].empty?
|
|
60
|
-
# From ParseTreeProcessor - datatype_map is {prefix => uri}
|
|
61
|
-
# Use the first (typically only) datatype library
|
|
62
|
-
xml.parent[:datatypeLibrary] = tree[:datatype_map].values.first
|
|
63
|
-
end
|
|
64
|
-
|
|
65
68
|
# A leading `start` (tree[:start]) or a non-leading one (emitted as
|
|
66
69
|
# <start> in the definitions loop) both suppress auto-wrapping.
|
|
67
70
|
has_explicit_start =
|
|
@@ -178,7 +181,8 @@ module Rng
|
|
|
178
181
|
end
|
|
179
182
|
end
|
|
180
183
|
|
|
181
|
-
builder
|
|
184
|
+
emit.call(builder)
|
|
185
|
+
builder.document.to_xml
|
|
182
186
|
end
|
|
183
187
|
|
|
184
188
|
private
|
|
@@ -230,6 +234,11 @@ module Rng
|
|
|
230
234
|
end
|
|
231
235
|
|
|
232
236
|
# Add annotations (foreign attributes and elements) if present
|
|
237
|
+
#
|
|
238
|
+
# NOTE: currently uncalled; kept for upcoming annotation emission. The
|
|
239
|
+
# foreign-attribute branch below needs the enclosing element, which the
|
|
240
|
+
# Moxml builder does not expose yet — port it together with that hook
|
|
241
|
+
# rather than reintroducing Nokogiri-style parent mutation.
|
|
233
242
|
def add_annotations(xml, item, processor = nil)
|
|
234
243
|
return unless item[:annotations]
|
|
235
244
|
|
|
@@ -741,7 +750,7 @@ module Rng
|
|
|
741
750
|
# Bare anyName wildcard: *
|
|
742
751
|
any_name_info = item[:bare_any_name]
|
|
743
752
|
except_clause = any_name_info[:any_name_except]
|
|
744
|
-
xml.element do
|
|
753
|
+
xml.element({}) do
|
|
745
754
|
xml.anyName do
|
|
746
755
|
if except_clause
|
|
747
756
|
xml.except_ do
|
|
@@ -915,7 +924,7 @@ module Rng
|
|
|
915
924
|
case element_name_type
|
|
916
925
|
when :any_name
|
|
917
926
|
# Generate <anyName> with optional <except>
|
|
918
|
-
xml_ctx.element do
|
|
927
|
+
xml_ctx.element({}) do
|
|
919
928
|
add_documentation(xml_ctx, item) if item[:docs]
|
|
920
929
|
xml_ctx.anyName do
|
|
921
930
|
if except_clause
|
|
@@ -928,7 +937,7 @@ module Rng
|
|
|
928
937
|
end
|
|
929
938
|
when :ns_name
|
|
930
939
|
# Generate <nsName> with ns attribute and optional <except>
|
|
931
|
-
xml_ctx.element do
|
|
940
|
+
xml_ctx.element({}) do
|
|
932
941
|
add_documentation(xml_ctx, item) if item[:docs]
|
|
933
942
|
xml_ctx.nsName(ns: ns_prefix) do
|
|
934
943
|
if except_clause
|
|
@@ -941,7 +950,7 @@ module Rng
|
|
|
941
950
|
end
|
|
942
951
|
when :name_choice
|
|
943
952
|
# Element with choice of names (e.g., element foo|bar { ... })
|
|
944
|
-
xml_ctx.element do
|
|
953
|
+
xml_ctx.element({}) do
|
|
945
954
|
add_documentation(xml_ctx, item) if item[:docs]
|
|
946
955
|
xml_ctx.choice do
|
|
947
956
|
choice_names.each do |name_part|
|
|
@@ -971,15 +980,15 @@ module Rng
|
|
|
971
980
|
wrap_with_occurrence(xml, occurrence) { element_block.call(xml) }
|
|
972
981
|
elsif item.key?(:text)
|
|
973
982
|
wrap_with_occurrence(xml, item[:occurrence]) do
|
|
974
|
-
xml.
|
|
983
|
+
xml.text_
|
|
975
984
|
end
|
|
976
985
|
elsif item.key?(:empty)
|
|
977
986
|
wrap_with_occurrence(xml, item[:occurrence]) do
|
|
978
|
-
xml.
|
|
987
|
+
xml.empty_
|
|
979
988
|
end
|
|
980
989
|
elsif item.key?(:not_allowed)
|
|
981
990
|
wrap_with_occurrence(xml, item[:occurrence]) do
|
|
982
|
-
xml.
|
|
991
|
+
xml.notAllowed_
|
|
983
992
|
end
|
|
984
993
|
elsif item.key?(:list_content)
|
|
985
994
|
xml.list do
|
|
@@ -1174,7 +1183,7 @@ module Rng
|
|
|
1174
1183
|
def process_list_item(xml, item)
|
|
1175
1184
|
item_block = lambda do |xml_ctx|
|
|
1176
1185
|
if item.key?(:text)
|
|
1177
|
-
xml_ctx.
|
|
1186
|
+
xml_ctx.text_
|
|
1178
1187
|
elsif item.key?(:prefix)
|
|
1179
1188
|
# Datatype reference
|
|
1180
1189
|
data_attrs = {
|
|
@@ -1254,7 +1263,7 @@ module Rng
|
|
|
1254
1263
|
end
|
|
1255
1264
|
end
|
|
1256
1265
|
elsif type_info == 'text' || (type_info.is_a?(Hash) && type_info.key?(:text_type))
|
|
1257
|
-
xml.
|
|
1266
|
+
xml.text_
|
|
1258
1267
|
elsif type_info.is_a?(Hash) && type_info.key?(:value)
|
|
1259
1268
|
# Single value literal
|
|
1260
1269
|
xml.value(process_string_literal(type_info[:value]))
|
|
@@ -1284,7 +1293,7 @@ module Rng
|
|
|
1284
1293
|
|
|
1285
1294
|
def emit_attribute_choice_item(xml, item)
|
|
1286
1295
|
if item.key?(:text_type)
|
|
1287
|
-
xml.
|
|
1296
|
+
xml.text_
|
|
1288
1297
|
elsif item.key?(:value)
|
|
1289
1298
|
xml.value(process_string_literal(item[:value]))
|
|
1290
1299
|
elsif item.key?(:ref)
|
|
@@ -1412,7 +1421,7 @@ module Rng
|
|
|
1412
1421
|
end
|
|
1413
1422
|
|
|
1414
1423
|
# Emit as an empty foreign element (content is annotation-only, ignored in RNG)
|
|
1415
|
-
xml.
|
|
1424
|
+
xml.create_tag(element_name)
|
|
1416
1425
|
end
|
|
1417
1426
|
end
|
|
1418
1427
|
end
|
data/lib/rng/schema_validator.rb
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require '
|
|
3
|
+
require 'moxml'
|
|
4
4
|
require 'uri'
|
|
5
5
|
|
|
6
6
|
module Rng
|
|
@@ -22,7 +22,7 @@ module Rng
|
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
# Validates raw XML against RELAX NG structural rules.
|
|
25
|
-
# Operates on the
|
|
25
|
+
# Operates on the Moxml XML tree BEFORE Lutaml deserialization,
|
|
26
26
|
# because Lutaml silently drops unmapped content.
|
|
27
27
|
class SchemaValidator
|
|
28
28
|
RNG_NS = 'http://relaxng.org/ns/structure/1.0'
|
|
@@ -31,6 +31,8 @@ module Rng
|
|
|
31
31
|
LEAF_ELEMENTS = %w[empty text notAllowed ref parentRef value].freeze
|
|
32
32
|
OBSOLETE_ELEMENTS = %w[not difference key keyRef].freeze
|
|
33
33
|
OBSOLETE_ATTRS = %w[key keyRef global].freeze
|
|
34
|
+
OBSOLETE_ATTR_CONTEXTS = [%w[start name], %w[attribute global], %w[data key],
|
|
35
|
+
%w[data keyRef]].freeze
|
|
34
36
|
NAME_REQUIRED = %w[element attribute].freeze
|
|
35
37
|
CONTAINER_ELEMENTS = %w[group choice interleave optional zeroOrMore oneOrMore list mixed define start].freeze
|
|
36
38
|
VALID_ROOT_ELEMENTS = %w[grammar element group choice interleave notAllowed externalRef data].freeze
|
|
@@ -66,7 +68,7 @@ module Rng
|
|
|
66
68
|
|
|
67
69
|
class << self
|
|
68
70
|
def validate(xml_input)
|
|
69
|
-
doc =
|
|
71
|
+
doc = Moxml::Context.new.parse(xml_input)
|
|
70
72
|
root = doc.root
|
|
71
73
|
report_error('Document has no root element') unless root
|
|
72
74
|
validator = new
|
|
@@ -75,7 +77,7 @@ module Rng
|
|
|
75
77
|
end
|
|
76
78
|
|
|
77
79
|
def validate_all(xml_input)
|
|
78
|
-
doc =
|
|
80
|
+
doc = Moxml::Context.new.parse(xml_input)
|
|
79
81
|
root = doc.root
|
|
80
82
|
return [SchemaValidationError.new('Document has no root element')] unless root
|
|
81
83
|
|
|
@@ -89,7 +91,7 @@ module Rng
|
|
|
89
91
|
end
|
|
90
92
|
|
|
91
93
|
def valid?(xml_input)
|
|
92
|
-
doc =
|
|
94
|
+
doc = Moxml::Context.new.parse(xml_input)
|
|
93
95
|
root = doc.root
|
|
94
96
|
return false unless root
|
|
95
97
|
|
|
@@ -113,9 +115,9 @@ module Rng
|
|
|
113
115
|
end
|
|
114
116
|
|
|
115
117
|
def validate_node(node, parent_context: nil)
|
|
116
|
-
return unless node.is_a?(
|
|
118
|
+
return unless node.is_a?(Moxml::Element)
|
|
117
119
|
|
|
118
|
-
ns = node.namespace&.
|
|
120
|
+
ns = node.namespace&.uri
|
|
119
121
|
local_name = node.name
|
|
120
122
|
is_root = node.parent&.document&.root == node
|
|
121
123
|
xpath = node.path.delete_prefix('/')
|
|
@@ -192,15 +194,15 @@ module Rng
|
|
|
192
194
|
validate_oneOrMore_infinite_attribute_name(local_name, node, xpath)
|
|
193
195
|
|
|
194
196
|
# Recurse
|
|
195
|
-
node.
|
|
196
|
-
next if child.namespace&.
|
|
197
|
+
node.children.select(&:element?).each do |child|
|
|
198
|
+
next if child.namespace&.uri == ANNOTATIONS_NS
|
|
197
199
|
|
|
198
200
|
validate_node(child, parent_context: context_for_child(local_name, node))
|
|
199
201
|
end
|
|
200
202
|
end
|
|
201
203
|
|
|
202
204
|
def report_error(message, xpath: nil, node: nil)
|
|
203
|
-
line = node&.
|
|
205
|
+
line = node&.line_number
|
|
204
206
|
error = SchemaValidationError.new(message, xpath: xpath, line: line)
|
|
205
207
|
raise error unless @collect_all
|
|
206
208
|
|
|
@@ -211,7 +213,7 @@ module Rng
|
|
|
211
213
|
private
|
|
212
214
|
|
|
213
215
|
def rng_ns_children(node)
|
|
214
|
-
node.
|
|
216
|
+
node.children.select(&:element?).select { |c| (c.namespace&.uri == RNG_NS) || (c.namespace.nil? && !c.name.empty?) }
|
|
215
217
|
end
|
|
216
218
|
|
|
217
219
|
def context_for_child(parent_name, _parent_node)
|
|
@@ -227,7 +229,7 @@ module Rng
|
|
|
227
229
|
current = node.parent
|
|
228
230
|
while current
|
|
229
231
|
return current if current.name == name &&
|
|
230
|
-
(current.namespace&.
|
|
232
|
+
(current.namespace&.uri == RNG_NS || (current.namespace.nil? && !current.name.empty?))
|
|
231
233
|
|
|
232
234
|
current = current.parent
|
|
233
235
|
end
|
|
@@ -436,21 +438,13 @@ module Rng
|
|
|
436
438
|
end
|
|
437
439
|
|
|
438
440
|
def validate_obsolete_attrs(node, xpath)
|
|
439
|
-
node.attributes.each do |
|
|
440
|
-
attr_ns = attr.namespace&.
|
|
441
|
-
if attr_ns == RNG_NS && OBSOLETE_ATTRS.include?(name)
|
|
442
|
-
report_error("Attribute '#{name}' on '#{node.name}' is obsolete",
|
|
443
|
-
xpath: xpath)
|
|
444
|
-
end
|
|
441
|
+
node.attributes.each do |attr|
|
|
442
|
+
attr_ns = attr.namespace&.uri
|
|
443
|
+
report_error("Attribute '#{attr.name}' on '#{node.name}' is obsolete", xpath: xpath) if attr_ns == RNG_NS && OBSOLETE_ATTRS.include?(attr.name)
|
|
445
444
|
next unless attr_ns.nil? && attr.namespace&.prefix.nil?
|
|
445
|
+
next unless OBSOLETE_ATTR_CONTEXTS.include?([node.name, attr.name])
|
|
446
446
|
|
|
447
|
-
report_error("Attribute 'name' on '
|
|
448
|
-
if name == 'global' && node.name == 'attribute'
|
|
449
|
-
report_error("Attribute 'global' on 'attribute' is obsolete",
|
|
450
|
-
xpath: xpath)
|
|
451
|
-
end
|
|
452
|
-
report_error("Attribute '#{name}' on 'data' is obsolete", xpath: xpath) if %w[key
|
|
453
|
-
keyRef].include?(name) && node.name == 'data'
|
|
447
|
+
report_error("Attribute '#{attr.name}' on '#{node.name}' is obsolete", xpath: xpath)
|
|
454
448
|
end
|
|
455
449
|
end
|
|
456
450
|
|
|
@@ -529,7 +523,7 @@ module Rng
|
|
|
529
523
|
return false unless node
|
|
530
524
|
|
|
531
525
|
current = node
|
|
532
|
-
while current.is_a?(
|
|
526
|
+
while current.is_a?(Moxml::Element)
|
|
533
527
|
current.namespace_definitions.each do |ns|
|
|
534
528
|
return true if ns.prefix == prefix
|
|
535
529
|
end
|
|
@@ -758,7 +752,7 @@ module Rng
|
|
|
758
752
|
def validate_name_value_purity(local_name, node, xpath)
|
|
759
753
|
return unless %w[name value].include?(local_name)
|
|
760
754
|
|
|
761
|
-
return unless node.
|
|
755
|
+
return unless node.children.any?(&:element?)
|
|
762
756
|
|
|
763
757
|
report_error("'#{local_name}' must not contain child elements", xpath: xpath)
|
|
764
758
|
end
|
|
@@ -827,8 +821,9 @@ module Rng
|
|
|
827
821
|
def validate_leaf_no_attrs(local_name, node, xpath)
|
|
828
822
|
return unless NO_ATTR_LEAF_ELEMENTS.include?(local_name)
|
|
829
823
|
|
|
830
|
-
node.attributes.each do |
|
|
831
|
-
|
|
824
|
+
node.attributes.each do |attr|
|
|
825
|
+
attr_name = attr.name
|
|
826
|
+
attr_ns = attr.namespace&.uri
|
|
832
827
|
next if attr_ns && attr_ns != RNG_NS
|
|
833
828
|
next if attr_name == 'xmlns' || attr_name.start_with?('xmlns:')
|
|
834
829
|
|
|
@@ -838,10 +833,11 @@ module Rng
|
|
|
838
833
|
end
|
|
839
834
|
|
|
840
835
|
def validate_unknown_attrs(node, xpath)
|
|
841
|
-
node.attributes.each do |
|
|
836
|
+
node.attributes.each do |attr|
|
|
837
|
+
attr_name = attr.name
|
|
842
838
|
next if attr_name == 'xmlns'
|
|
843
839
|
|
|
844
|
-
attr_ns = attr.namespace&.
|
|
840
|
+
attr_ns = attr.namespace&.uri
|
|
845
841
|
if (attr_ns == RNG_NS) && !known_rng_attr?(
|
|
846
842
|
node.name, attr_name
|
|
847
843
|
)
|
|
@@ -1407,9 +1403,9 @@ module Rng
|
|
|
1407
1403
|
|
|
1408
1404
|
def collect_all_refs(node)
|
|
1409
1405
|
refs = []
|
|
1410
|
-
return refs unless node.is_a?(
|
|
1406
|
+
return refs unless node.is_a?(Moxml::Element)
|
|
1411
1407
|
|
|
1412
|
-
ns = node.namespace&.
|
|
1408
|
+
ns = node.namespace&.uri
|
|
1413
1409
|
local = node.name
|
|
1414
1410
|
if ns == RNG_NS || (ns.nil? && !local.empty?)
|
|
1415
1411
|
case local
|
|
@@ -1418,7 +1414,7 @@ module Rng
|
|
|
1418
1414
|
when 'parentRef'
|
|
1419
1415
|
refs << { name: node['name'], type: 'parentRef' } if node['name']
|
|
1420
1416
|
end
|
|
1421
|
-
node.
|
|
1417
|
+
node.children.select(&:element?).each do |child|
|
|
1422
1418
|
refs.concat(collect_all_refs(child))
|
|
1423
1419
|
end
|
|
1424
1420
|
end
|
|
@@ -1444,9 +1440,9 @@ module Rng
|
|
|
1444
1440
|
|
|
1445
1441
|
def collect_direct_refs(node)
|
|
1446
1442
|
refs = []
|
|
1447
|
-
return refs unless node.is_a?(
|
|
1443
|
+
return refs unless node.is_a?(Moxml::Element)
|
|
1448
1444
|
|
|
1449
|
-
ns = node.namespace&.
|
|
1445
|
+
ns = node.namespace&.uri
|
|
1450
1446
|
local = node.name
|
|
1451
1447
|
if ns == RNG_NS || (ns.nil? && !local.empty?)
|
|
1452
1448
|
case local
|
|
@@ -1455,7 +1451,7 @@ module Rng
|
|
|
1455
1451
|
when 'parentRef'
|
|
1456
1452
|
refs << node['name'] if node['name']
|
|
1457
1453
|
end
|
|
1458
|
-
node.
|
|
1454
|
+
node.children.select(&:element?).each do |child|
|
|
1459
1455
|
next if child.name == 'define' # Don't recurse into nested grammar's defines
|
|
1460
1456
|
|
|
1461
1457
|
refs.concat(collect_direct_refs(child))
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require '
|
|
3
|
+
require 'moxml'
|
|
4
4
|
|
|
5
5
|
module Rng
|
|
6
6
|
# Parses RELAX NG test suite XML files (like compacttest.xml)
|
|
@@ -53,7 +53,7 @@ module Rng
|
|
|
53
53
|
attr_reader :test_cases
|
|
54
54
|
|
|
55
55
|
def initialize(xml_content)
|
|
56
|
-
@doc =
|
|
56
|
+
@doc = Moxml::Context.new.parse(xml_content)
|
|
57
57
|
@test_cases = []
|
|
58
58
|
parse_test_cases
|
|
59
59
|
end
|
|
@@ -138,7 +138,7 @@ module Rng
|
|
|
138
138
|
xml_node.xpath('resource').each do |resource_node|
|
|
139
139
|
name = resource_node['name']
|
|
140
140
|
# Get the first element child as RNG content
|
|
141
|
-
content_node = resource_node.
|
|
141
|
+
content_node = resource_node.children.find(&:element?)
|
|
142
142
|
content = content_node ? content_node.to_xml : ''
|
|
143
143
|
test_case.xml_resources[name] = content
|
|
144
144
|
end
|
|
@@ -147,7 +147,7 @@ module Rng
|
|
|
147
147
|
correct_node = xml_node.at_xpath('correct')
|
|
148
148
|
if correct_node
|
|
149
149
|
# Get the first element child as RNG content
|
|
150
|
-
rng_node = correct_node.
|
|
150
|
+
rng_node = correct_node.children.find(&:element?)
|
|
151
151
|
test_case.instance_variable_set(
|
|
152
152
|
:@xml_correct,
|
|
153
153
|
rng_node ? rng_node.to_xml : ''
|
|
@@ -158,7 +158,7 @@ module Rng
|
|
|
158
158
|
incorrect_node = xml_node.at_xpath('incorrect')
|
|
159
159
|
return unless incorrect_node
|
|
160
160
|
|
|
161
|
-
rng_node = incorrect_node.
|
|
161
|
+
rng_node = incorrect_node.children.find(&:element?)
|
|
162
162
|
test_case.instance_variable_set(
|
|
163
163
|
:@xml_incorrect,
|
|
164
164
|
rng_node ? rng_node.to_xml : ''
|
data/lib/rng/version.rb
CHANGED
data/rng.gemspec
CHANGED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
require 'tmpdir'
|
|
5
|
+
|
|
6
|
+
RSpec.describe Rng::IncludeProcessor do
|
|
7
|
+
# A grammar-block RNC that includes another grammar and then defines its
|
|
8
|
+
# own named patterns after the include. The merged grammar must carry BOTH
|
|
9
|
+
# the included grammar's definitions and the including file's own patterns.
|
|
10
|
+
# ParseTreeProcessor used to overwrite :definitions (the include merge
|
|
11
|
+
# result) with only :includes + :patterns, dropping every included define.
|
|
12
|
+
it 'keeps included definitions and own patterns in a grammar block file' do
|
|
13
|
+
Dir.mktmpdir do |dir|
|
|
14
|
+
File.write(File.join(dir, 'base.rnc'), <<~RNC)
|
|
15
|
+
start = doc
|
|
16
|
+
doc = element doc { sections }
|
|
17
|
+
sections = element sections { element a { text } }
|
|
18
|
+
RNC
|
|
19
|
+
|
|
20
|
+
File.write(File.join(dir, 'derived.rnc'), <<~RNC)
|
|
21
|
+
include "base.rnc" {
|
|
22
|
+
doc = element doc { sections }
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
start = derived
|
|
26
|
+
derived = element derived { sections }
|
|
27
|
+
note = element note { text }
|
|
28
|
+
RNC
|
|
29
|
+
|
|
30
|
+
grammar = Rng.parse_file(File.join(dir, 'derived.rnc'))
|
|
31
|
+
names = grammar.define.map(&:name)
|
|
32
|
+
|
|
33
|
+
expect(names).to include('sections')
|
|
34
|
+
expect(names).to include('note')
|
|
35
|
+
expect(names).to include('derived')
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
it 'applies include-block overrides, replacing the base definition' do
|
|
40
|
+
Dir.mktmpdir do |dir|
|
|
41
|
+
File.write(File.join(dir, 'base.rnc'), <<~RNC)
|
|
42
|
+
start = doc
|
|
43
|
+
doc = element doc { sections }
|
|
44
|
+
sections = element sections { element a { text } }
|
|
45
|
+
RNC
|
|
46
|
+
|
|
47
|
+
File.write(File.join(dir, 'derived.rnc'), <<~RNC)
|
|
48
|
+
include "base.rnc" {
|
|
49
|
+
sections = element sections { element b { text } }
|
|
50
|
+
}
|
|
51
|
+
RNC
|
|
52
|
+
|
|
53
|
+
grammar = Rng.parse_file(File.join(dir, 'derived.rnc'))
|
|
54
|
+
sections = grammar.define.find { |d| d.name == 'sections' }
|
|
55
|
+
|
|
56
|
+
expect(sections).not_to be_nil
|
|
57
|
+
expect(sections.to_xml).to include('name="b"')
|
|
58
|
+
expect(sections.to_xml).not_to include('name="a"')
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
data/spec/rng/rnc_parser_spec.rb
CHANGED
|
@@ -646,8 +646,11 @@ RSpec.describe Rng::RncParser do
|
|
|
646
646
|
|
|
647
647
|
avg_time = elapsed / 10
|
|
648
648
|
|
|
649
|
-
#
|
|
650
|
-
|
|
649
|
+
# Pathological-regression guard, not a micro-benchmark: the
|
|
650
|
+
# largest grammar (isodoc) measures 2-3.1s across CI runners
|
|
651
|
+
# and developer machines, so the budget must tolerate runner
|
|
652
|
+
# variance while still catching order-of-magnitude regressions.
|
|
653
|
+
expect(avg_time).to be < 5.0
|
|
651
654
|
end
|
|
652
655
|
else
|
|
653
656
|
it 'has schema files for performance testing' do
|
|
@@ -681,8 +684,9 @@ RSpec.describe Rng::RncParser do
|
|
|
681
684
|
elapsed = Time.now - start_time
|
|
682
685
|
avg_time = elapsed / 100
|
|
683
686
|
|
|
684
|
-
#
|
|
685
|
-
|
|
687
|
+
# Pathological-regression guard: ~50-70ms depending on machine
|
|
688
|
+
# load; 100ms still catches order-of-magnitude regressions.
|
|
689
|
+
expect(avg_time).to be < 0.1
|
|
686
690
|
end
|
|
687
691
|
end
|
|
688
692
|
end
|
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.9
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-10-
|
|
11
|
+
date: 2026-10-21 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: lutaml-model
|
|
@@ -25,7 +25,7 @@ dependencies:
|
|
|
25
25
|
- !ruby/object:Gem::Version
|
|
26
26
|
version: 0.8.114
|
|
27
27
|
- !ruby/object:Gem::Dependency
|
|
28
|
-
name:
|
|
28
|
+
name: moxml
|
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
|
30
30
|
requirements:
|
|
31
31
|
- - ">="
|
|
@@ -252,6 +252,7 @@ files:
|
|
|
252
252
|
- spec/rng/datatype_declaration_spec.rb
|
|
253
253
|
- spec/rng/div_spec.rb
|
|
254
254
|
- spec/rng/external_ref_resolver_spec.rb
|
|
255
|
+
- spec/rng/include_processor_spec.rb
|
|
255
256
|
- spec/rng/metanorma_conversion_spec.rb
|
|
256
257
|
- spec/rng/namespace_declaration_spec.rb
|
|
257
258
|
- spec/rng/namespace_support_spec.rb
|