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.
- checksums.yaml +4 -4
- data/.github/workflows/docs.yml +63 -0
- data/.github/workflows/release.yml +8 -3
- data/.gitignore +11 -0
- data/.rubocop.yml +10 -7
- data/.rubocop_todo.yml +229 -23
- data/CHANGELOG.md +317 -0
- data/CLAUDE.md +139 -0
- data/Gemfile +11 -12
- data/README.adoc +1538 -11
- data/Rakefile +11 -3
- data/docs/Gemfile +8 -0
- data/docs/_config.yml +23 -0
- data/docs/getting-started/index.adoc +75 -0
- data/docs/guides/error-handling.adoc +137 -0
- data/docs/guides/external-references.adoc +128 -0
- data/docs/guides/index.adoc +24 -0
- data/docs/guides/parsing-rnc.adoc +141 -0
- data/docs/guides/parsing-rng-xml.adoc +81 -0
- data/docs/guides/rng-to-rnc.adoc +101 -0
- data/docs/guides/validation.adoc +85 -0
- data/docs/index.adoc +52 -0
- data/docs/reference/api.adoc +126 -0
- data/docs/reference/cli.adoc +182 -0
- data/docs/understanding/architecture.adoc +58 -0
- data/docs/understanding/rng-vs-rnc.adoc +118 -0
- data/exe/rng +5 -0
- data/lib/rng/any_name.rb +10 -8
- data/lib/rng/attribute.rb +28 -26
- data/lib/rng/choice.rb +24 -24
- data/lib/rng/cli.rb +607 -0
- data/lib/rng/data.rb +10 -10
- data/lib/rng/datatype_declaration.rb +26 -0
- data/lib/rng/define.rb +44 -41
- data/lib/rng/div.rb +36 -0
- data/lib/rng/documentation.rb +9 -0
- data/lib/rng/element.rb +39 -37
- data/lib/rng/empty.rb +7 -7
- data/lib/rng/except.rb +25 -25
- data/lib/rng/external_ref.rb +8 -8
- data/lib/rng/external_ref_resolver.rb +602 -0
- data/lib/rng/foreign_attribute.rb +26 -0
- data/lib/rng/foreign_element.rb +33 -0
- data/lib/rng/grammar.rb +14 -12
- data/lib/rng/group.rb +26 -24
- data/lib/rng/include.rb +5 -6
- data/lib/rng/include_processor.rb +461 -0
- data/lib/rng/interleave.rb +23 -23
- data/lib/rng/list.rb +22 -22
- data/lib/rng/mixed.rb +23 -23
- data/lib/rng/name.rb +6 -7
- data/lib/rng/namespace_declaration.rb +47 -0
- data/lib/rng/namespaces.rb +15 -0
- data/lib/rng/not_allowed.rb +7 -7
- data/lib/rng/ns_name.rb +9 -9
- data/lib/rng/one_or_more.rb +23 -23
- data/lib/rng/optional.rb +23 -23
- data/lib/rng/param.rb +7 -8
- data/lib/rng/parent_ref.rb +8 -8
- data/lib/rng/parse_tree_processor.rb +695 -0
- data/lib/rng/pattern.rb +7 -7
- data/lib/rng/ref.rb +8 -8
- data/lib/rng/rnc_builder.rb +927 -0
- data/lib/rng/rnc_parser.rb +605 -305
- data/lib/rng/rnc_to_rng_converter.rb +1408 -0
- data/lib/rng/schema_preamble.rb +73 -0
- data/lib/rng/schema_validator.rb +1622 -0
- data/lib/rng/start.rb +27 -25
- data/lib/rng/test_suite_parser.rb +168 -0
- data/lib/rng/text.rb +11 -8
- data/lib/rng/to_rnc.rb +4 -35
- data/lib/rng/value.rb +6 -7
- data/lib/rng/version.rb +1 -1
- data/lib/rng/zero_or_more.rb +23 -23
- data/lib/rng.rb +68 -17
- data/rng.gemspec +18 -19
- data/scripts/extract_spectest_resources.rb +96 -0
- data/spec/fixtures/compacttest.xml +2511 -0
- data/spec/fixtures/external/circular_a.rng +7 -0
- data/spec/fixtures/external/circular_b.rng +7 -0
- data/spec/fixtures/external/circular_main.rng +7 -0
- data/spec/fixtures/external/external_ref_lib.rng +7 -0
- data/spec/fixtures/external/external_ref_main.rng +7 -0
- data/spec/fixtures/external/include_lib.rng +7 -0
- data/spec/fixtures/external/include_main.rng +3 -0
- data/spec/fixtures/external/nested_chain.rng +6 -0
- data/spec/fixtures/external/nested_leaf.rng +7 -0
- data/spec/fixtures/external/nested_mid.rng +8 -0
- data/spec/fixtures/metanorma/3gpp.rnc +35 -0
- data/spec/fixtures/metanorma/3gpp.rng +105 -0
- data/spec/fixtures/metanorma/basicdoc.rnc +11 -0
- data/spec/fixtures/metanorma/bipm.rnc +148 -0
- data/spec/fixtures/metanorma/bipm.rng +376 -0
- data/spec/fixtures/metanorma/bsi.rnc +104 -0
- data/spec/fixtures/metanorma/bsi.rng +332 -0
- data/spec/fixtures/metanorma/csa.rnc +45 -0
- data/spec/fixtures/metanorma/csa.rng +131 -0
- data/spec/fixtures/metanorma/csd.rnc +43 -0
- data/spec/fixtures/metanorma/csd.rng +132 -0
- data/spec/fixtures/metanorma/gbstandard.rnc +99 -0
- data/spec/fixtures/metanorma/gbstandard.rng +316 -0
- data/spec/fixtures/metanorma/iec.rnc +49 -0
- data/spec/fixtures/metanorma/iec.rng +193 -0
- data/spec/fixtures/metanorma/ietf.rnc +275 -0
- data/spec/fixtures/metanorma/ietf.rng +925 -0
- data/spec/fixtures/metanorma/iho.rnc +58 -0
- data/spec/fixtures/metanorma/iho.rng +179 -0
- data/spec/fixtures/metanorma/isodoc.rnc +873 -0
- data/spec/fixtures/metanorma/isodoc.rng +2704 -0
- data/spec/fixtures/metanorma/isostandard-amd.rnc +43 -0
- data/spec/fixtures/metanorma/isostandard-amd.rng +108 -0
- data/spec/fixtures/metanorma/isostandard.rnc +166 -0
- data/spec/fixtures/metanorma/isostandard.rng +494 -0
- data/spec/fixtures/metanorma/itu.rnc +122 -0
- data/spec/fixtures/metanorma/itu.rng +377 -0
- data/spec/fixtures/metanorma/m3d.rnc +41 -0
- data/spec/fixtures/metanorma/m3d.rng +122 -0
- data/spec/fixtures/metanorma/mpfd.rnc +36 -0
- data/spec/fixtures/metanorma/mpfd.rng +95 -0
- data/spec/fixtures/metanorma/nist.rnc +77 -0
- data/spec/fixtures/metanorma/nist.rng +216 -0
- data/spec/fixtures/metanorma/ogc.rnc +51 -0
- data/spec/fixtures/metanorma/ogc.rng +151 -0
- data/spec/fixtures/metanorma/reqt.rnc +6 -0
- data/spec/fixtures/metanorma/rsd.rnc +36 -0
- data/spec/fixtures/metanorma/rsd.rng +95 -0
- data/spec/fixtures/metanorma/un.rnc +103 -0
- data/spec/fixtures/metanorma/un.rng +367 -0
- data/spec/fixtures/rnc/base.rnc +4 -0
- data/spec/fixtures/rnc/grammar_with_trailing.rnc +8 -0
- data/spec/fixtures/rnc/main_include_trailing.rnc +3 -0
- data/spec/fixtures/rnc/main_with_include.rnc +5 -0
- data/spec/fixtures/rnc/test_augment.rnc +10 -0
- data/spec/fixtures/rnc/test_isodoc_simple.rnc +9 -0
- data/spec/fixtures/rnc/top_level_include.rnc +8 -0
- data/spec/fixtures/spectest_external/case_10_4.7/x +3 -0
- data/spec/fixtures/spectest_external/case_10_4.7/y +7 -0
- data/spec/fixtures/spectest_external/case_11_4.7/x +3 -0
- data/spec/fixtures/spectest_external/case_12_4.7/x +3 -0
- data/spec/fixtures/spectest_external/case_13_4.7/x +3 -0
- data/spec/fixtures/spectest_external/case_13_4.7/y +3 -0
- data/spec/fixtures/spectest_external/case_14_4.7/x +7 -0
- data/spec/fixtures/spectest_external/case_15_4.7/x +7 -0
- data/spec/fixtures/spectest_external/case_16_4.7/x +5 -0
- data/spec/fixtures/spectest_external/case_17_4.7/x +5 -0
- data/spec/fixtures/spectest_external/case_18_4.7/x +7 -0
- data/spec/fixtures/spectest_external/case_19_4.7/level1.rng +9 -0
- data/spec/fixtures/spectest_external/case_19_4.7/level2.rng +7 -0
- data/spec/fixtures/spectest_external/case_1_4.5/sub1/x +3 -0
- data/spec/fixtures/spectest_external/case_1_4.5/sub3/x +3 -0
- data/spec/fixtures/spectest_external/case_1_4.5/x +3 -0
- data/spec/fixtures/spectest_external/case_20_4.6/x +3 -0
- data/spec/fixtures/spectest_external/case_2_4.5/x +3 -0
- data/spec/fixtures/spectest_external/case_3_4.6/x +3 -0
- data/spec/fixtures/spectest_external/case_4_4.6/x +3 -0
- data/spec/fixtures/spectest_external/case_5_4.6/x +1 -0
- data/spec/fixtures/spectest_external/case_6_4.6/x +5 -0
- data/spec/fixtures/spectest_external/case_7_4.6/x +1 -0
- data/spec/fixtures/spectest_external/case_7_4.6/y +1 -0
- data/spec/fixtures/spectest_external/case_8_4.7/x +7 -0
- data/spec/fixtures/spectest_external/case_9_4.7/x +7 -0
- data/spec/fixtures/spectest_external/resources.json +149 -0
- data/spec/rng/advanced_rnc_spec.rb +101 -0
- data/spec/rng/compacttest_spec.rb +197 -0
- data/spec/rng/datatype_declaration_spec.rb +28 -0
- data/spec/rng/div_spec.rb +207 -0
- data/spec/rng/external_ref_resolver_spec.rb +122 -0
- data/spec/rng/metanorma_conversion_spec.rb +159 -0
- data/spec/rng/namespace_declaration_spec.rb +60 -0
- data/spec/rng/namespace_support_spec.rb +199 -0
- data/spec/rng/rnc_parser_spec.rb +498 -22
- data/spec/rng/rnc_roundtrip_spec.rb +96 -82
- data/spec/rng/rng_generation_spec.rb +288 -0
- data/spec/rng/roundtrip_spec.rb +342 -0
- data/spec/rng/schema_preamble_spec.rb +145 -0
- data/spec/rng/schema_spec.rb +68 -64
- data/spec/rng/spectest_spec.rb +168 -90
- data/spec/rng_spec.rb +2 -2
- data/spec/spec_helper.rb +7 -42
- metadata +141 -8
data/lib/rng/grammar.rb
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "lutaml/model"
|
|
4
|
-
|
|
5
3
|
module Rng
|
|
6
4
|
# This represents the RNG schema
|
|
7
5
|
class Grammar < Lutaml::Model::Serializable
|
|
@@ -10,27 +8,31 @@ module Rng
|
|
|
10
8
|
attribute :datatypeLibrary, :string
|
|
11
9
|
attribute :start, Start, collection: true
|
|
12
10
|
attribute :define, Define, collection: true, initialize_empty: true
|
|
13
|
-
attribute :element, Element, collection: true
|
|
11
|
+
attribute :element, Element, collection: true, initialize_empty: true
|
|
14
12
|
attribute :include, Include, collection: true
|
|
13
|
+
attribute :div, Div, collection: true, initialize_empty: true
|
|
15
14
|
|
|
16
15
|
xml do
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
element 'grammar'
|
|
17
|
+
ordered
|
|
18
|
+
|
|
19
|
+
namespace ::Rng::Namespaces::RngNamespace
|
|
19
20
|
|
|
20
|
-
map_attribute
|
|
21
|
+
map_attribute 'datatypeLibrary', to: :datatypeLibrary, value_map: {
|
|
21
22
|
from: { empty: :empty, omitted: :omitted, nil: :nil },
|
|
22
23
|
to: { empty: :empty, omitted: :omitted, nil: :nil }
|
|
23
24
|
}
|
|
24
|
-
map_attribute
|
|
25
|
+
map_attribute 'ns', to: :ns, value_map: {
|
|
25
26
|
from: { empty: :empty, omitted: :omitted, nil: :nil },
|
|
26
27
|
to: { empty: :empty, omitted: :omitted, nil: :nil }
|
|
27
28
|
}
|
|
28
|
-
map_attribute
|
|
29
|
+
map_attribute 'id', to: :id
|
|
29
30
|
|
|
30
|
-
map_element
|
|
31
|
-
map_element
|
|
32
|
-
map_element
|
|
33
|
-
map_element
|
|
31
|
+
map_element 'start', to: :start
|
|
32
|
+
map_element 'define', to: :define
|
|
33
|
+
map_element 'element', to: :element
|
|
34
|
+
map_element 'include', to: :include
|
|
35
|
+
map_element 'div', to: :div
|
|
34
36
|
end
|
|
35
37
|
end
|
|
36
38
|
end
|
data/lib/rng/group.rb
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "lutaml/model"
|
|
4
|
-
|
|
5
3
|
module Rng
|
|
6
4
|
class Group < Lutaml::Model::Serializable
|
|
7
5
|
attribute :id, :string
|
|
@@ -24,37 +22,41 @@ module Rng
|
|
|
24
22
|
attribute :data, Data, collection: true, initialize_empty: true
|
|
25
23
|
attribute :list, List, collection: true, initialize_empty: true
|
|
26
24
|
attribute :notAllowed, NotAllowed, collection: true, initialize_empty: true
|
|
25
|
+
attribute :base, Lutaml::Xml::W3c::XmlBaseType
|
|
27
26
|
|
|
28
27
|
xml do
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
element 'group'
|
|
29
|
+
ordered
|
|
30
|
+
|
|
31
|
+
namespace ::Rng::Namespaces::RngNamespace
|
|
31
32
|
|
|
32
|
-
map_attribute
|
|
33
|
-
|
|
33
|
+
map_attribute 'id', to: :id
|
|
34
|
+
w3c_attributes :base
|
|
35
|
+
map_attribute 'ns', to: :ns, value_map: {
|
|
34
36
|
from: { empty: :empty, omitted: :omitted, nil: :nil },
|
|
35
37
|
to: { empty: :empty, omitted: :omitted, nil: :nil }
|
|
36
38
|
}
|
|
37
|
-
map_attribute
|
|
39
|
+
map_attribute 'datatypeLibrary', to: :datatypeLibrary, value_map: {
|
|
38
40
|
from: { empty: :empty, omitted: :omitted, nil: :nil },
|
|
39
41
|
to: { empty: :empty, omitted: :omitted, nil: :nil }
|
|
40
42
|
}
|
|
41
|
-
map_element
|
|
42
|
-
map_element
|
|
43
|
-
map_element
|
|
44
|
-
map_element
|
|
45
|
-
map_element
|
|
46
|
-
map_element
|
|
47
|
-
map_element
|
|
48
|
-
map_element
|
|
49
|
-
map_element
|
|
50
|
-
map_element
|
|
51
|
-
map_element
|
|
52
|
-
map_element
|
|
53
|
-
map_element
|
|
54
|
-
map_element
|
|
55
|
-
map_element
|
|
56
|
-
map_element
|
|
57
|
-
map_element
|
|
43
|
+
map_element 'element', to: :element
|
|
44
|
+
map_element 'attribute', to: :attribute
|
|
45
|
+
map_element 'ref', to: :ref
|
|
46
|
+
map_element 'choice', to: :choice
|
|
47
|
+
map_element 'group', to: :group
|
|
48
|
+
map_element 'interleave', to: :interleave
|
|
49
|
+
map_element 'mixed', to: :mixed
|
|
50
|
+
map_element 'optional', to: :optional
|
|
51
|
+
map_element 'zeroOrMore', to: :zeroOrMore
|
|
52
|
+
map_element 'oneOrMore', to: :oneOrMore
|
|
53
|
+
map_element 'text', to: :text
|
|
54
|
+
map_element 'empty', to: :empty
|
|
55
|
+
map_element 'value', to: :value
|
|
56
|
+
map_element 'data', to: :data
|
|
57
|
+
map_element 'list', to: :list
|
|
58
|
+
map_element 'notAllowed', to: :notAllowed
|
|
59
|
+
map_element 'externalRef', to: :externalRef
|
|
58
60
|
end
|
|
59
61
|
end
|
|
60
62
|
end
|
data/lib/rng/include.rb
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "lutaml/model"
|
|
4
|
-
|
|
5
3
|
module Rng
|
|
6
4
|
class Include < Lutaml::Model::Serializable
|
|
7
5
|
attribute :href, :string
|
|
@@ -10,15 +8,16 @@ module Rng
|
|
|
10
8
|
attribute :grammar, Grammar
|
|
11
9
|
|
|
12
10
|
xml do
|
|
13
|
-
|
|
11
|
+
element 'include'
|
|
12
|
+
namespace ::Rng::Namespaces::RngNamespace
|
|
14
13
|
|
|
15
|
-
map_attribute
|
|
16
|
-
map_attribute
|
|
14
|
+
map_attribute 'href', to: :href
|
|
15
|
+
map_attribute 'ns', to: :ns, value_map: {
|
|
17
16
|
from: { empty: :empty, omitted: :omitted, nil: :nil },
|
|
18
17
|
to: { empty: :empty, omitted: :omitted, nil: :nil }
|
|
19
18
|
}
|
|
20
19
|
map_content to: :grammar
|
|
21
|
-
map_element
|
|
20
|
+
map_element 'define', to: :define
|
|
22
21
|
end
|
|
23
22
|
end
|
|
24
23
|
end
|
|
@@ -0,0 +1,461 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'set'
|
|
4
|
+
|
|
5
|
+
module Rng
|
|
6
|
+
# Handles RNC file inclusion and grammar merging
|
|
7
|
+
#
|
|
8
|
+
# This class processes include directives in RNC files, resolving them
|
|
9
|
+
# recursively while preventing circular includes. It supports both:
|
|
10
|
+
# - Grammar-level includes (inside grammar blocks)
|
|
11
|
+
# - Top-level includes (Metanorma-style schemas)
|
|
12
|
+
#
|
|
13
|
+
# @example Parse a file with includes
|
|
14
|
+
# processor = Rng::IncludeProcessor.new
|
|
15
|
+
# grammar = processor.parse_file("schema.rnc")
|
|
16
|
+
#
|
|
17
|
+
class IncludeProcessor
|
|
18
|
+
# Initialize with optional converter
|
|
19
|
+
#
|
|
20
|
+
# @param converter [RncToRngConverter] Converter for parse tree to RNG XML
|
|
21
|
+
def initialize(converter = RncToRngConverter.new)
|
|
22
|
+
@converter = converter
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Parse a file with include resolution
|
|
26
|
+
#
|
|
27
|
+
# @param file_path [String] Path to RNC file
|
|
28
|
+
# @param base_dir [String, nil] Base directory for resolving relative paths
|
|
29
|
+
# @param visited_files [Set] Set of already visited file paths (for circular detection)
|
|
30
|
+
# @return [Grammar] Parsed grammar object
|
|
31
|
+
def parse_file(file_path, base_dir = nil, visited_files = Set.new)
|
|
32
|
+
tree = parse_file_to_tree(file_path, base_dir, visited_files)
|
|
33
|
+
|
|
34
|
+
# Process raw_grammar/raw_override/raw_patterns first (before include resolution)
|
|
35
|
+
# The include processor needs parsed content in the tree
|
|
36
|
+
process_raw_nodes!(tree)
|
|
37
|
+
|
|
38
|
+
# Process any includes in the tree (top-level or grammar-level)
|
|
39
|
+
process_includes(tree, base_dir || File.dirname(File.expand_path(file_path)),
|
|
40
|
+
visited_files)
|
|
41
|
+
|
|
42
|
+
# Extract namespace from wrapper level if present
|
|
43
|
+
namespace = tree[:namespace]
|
|
44
|
+
|
|
45
|
+
# Build grammar tree from different structures
|
|
46
|
+
grammar_tree = build_grammar_tree(tree)
|
|
47
|
+
grammar_tree[:namespace] = namespace if namespace
|
|
48
|
+
|
|
49
|
+
# Convert to RNG XML and then to Grammar object
|
|
50
|
+
rng_xml = @converter.convert(grammar_tree)
|
|
51
|
+
Grammar.from_xml(rng_xml)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
# Parse file to parse tree (not Grammar object)
|
|
57
|
+
#
|
|
58
|
+
# @param file_path [String] Path to RNC file
|
|
59
|
+
# @param base_dir [String, nil] Base directory for resolving relative paths
|
|
60
|
+
# @param visited_files [Set] Set of already visited file paths
|
|
61
|
+
# @return [Hash] Parse tree
|
|
62
|
+
def parse_file_to_tree(file_path, base_dir = nil, visited_files = Set.new)
|
|
63
|
+
# Resolve absolute path to prevent circular includes
|
|
64
|
+
abs_path = File.expand_path(file_path, base_dir)
|
|
65
|
+
|
|
66
|
+
# Check for circular includes
|
|
67
|
+
raise "Circular include detected: #{abs_path}" if visited_files.include?(abs_path)
|
|
68
|
+
|
|
69
|
+
# Mark file as visited
|
|
70
|
+
visited_files.add(abs_path)
|
|
71
|
+
|
|
72
|
+
# Read file content
|
|
73
|
+
raise "Include file not found: #{abs_path}" unless File.exist?(abs_path)
|
|
74
|
+
|
|
75
|
+
content = File.read(abs_path)
|
|
76
|
+
|
|
77
|
+
# Parse with includes, passing the directory for relative path resolution
|
|
78
|
+
parse_with_includes(content, File.dirname(abs_path), visited_files)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Parse RNC content and resolve includes
|
|
82
|
+
#
|
|
83
|
+
# @param content [String] RNC content
|
|
84
|
+
# @param base_dir [String, nil] Base directory for resolving relative paths
|
|
85
|
+
# @param visited_files [Set] Set of already visited file paths
|
|
86
|
+
# @return [Hash] Parse tree with includes resolved
|
|
87
|
+
def parse_with_includes(content, base_dir = nil, visited_files = Set.new)
|
|
88
|
+
parser = RncParser.new
|
|
89
|
+
tree = parser.parse(content.strip)
|
|
90
|
+
|
|
91
|
+
# Process includes in the tree
|
|
92
|
+
process_includes(tree, base_dir, visited_files)
|
|
93
|
+
|
|
94
|
+
tree
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Process include directives by recursively parsing included files
|
|
98
|
+
#
|
|
99
|
+
# @param tree [Hash] Parse tree
|
|
100
|
+
# @param base_dir [String] Base directory for resolving relative paths
|
|
101
|
+
# @param visited_files [Set] Set of already visited file paths
|
|
102
|
+
def process_includes(tree, base_dir, visited_files)
|
|
103
|
+
# Handle top-level includes first (Metanorma-style schemas)
|
|
104
|
+
if tree.key?(:top_includes)
|
|
105
|
+
process_top_level_includes(tree, base_dir, visited_files)
|
|
106
|
+
return
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
return if tree[:includes] && tree[:includes].empty?
|
|
110
|
+
|
|
111
|
+
# Handle grammar-level includes (existing logic)
|
|
112
|
+
grammar_tree = extract_grammar_tree(tree)
|
|
113
|
+
|
|
114
|
+
return unless grammar_tree[:includes] && !grammar_tree[:includes].empty?
|
|
115
|
+
|
|
116
|
+
# Process each include
|
|
117
|
+
grammar_tree[:includes].each do |include_item|
|
|
118
|
+
process_single_include(grammar_tree, include_item, base_dir,
|
|
119
|
+
visited_files)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Remove includes array after processing (no longer needed in conversion)
|
|
123
|
+
grammar_tree.delete(:includes)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Process top-level includes (Metanorma-style schemas)
|
|
127
|
+
#
|
|
128
|
+
# @param tree [Hash] Parse tree
|
|
129
|
+
# @param base_dir [String] Base directory for resolving relative paths
|
|
130
|
+
# @param visited_files [Set] Set of already visited file paths
|
|
131
|
+
def process_top_level_includes(tree, base_dir, visited_files)
|
|
132
|
+
# Create a temporary grammar_tree to hold merged content
|
|
133
|
+
grammar_tree = {
|
|
134
|
+
start: nil,
|
|
135
|
+
definitions: []
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
# Process each top-level include
|
|
139
|
+
tree[:top_includes].each do |include_item|
|
|
140
|
+
href = extract_string_literal(include_item[:href])
|
|
141
|
+
override = parse_override(include_item[:override])
|
|
142
|
+
|
|
143
|
+
# Resolve file path relative to base_dir
|
|
144
|
+
included_file_path = base_dir ? File.join(base_dir, href) : href
|
|
145
|
+
|
|
146
|
+
# Parse included file recursively
|
|
147
|
+
included_tree = parse_file_to_tree(included_file_path, base_dir,
|
|
148
|
+
visited_files.dup)
|
|
149
|
+
|
|
150
|
+
# Process raw grammar/override/patterns before extracting
|
|
151
|
+
process_raw_nodes!(included_tree)
|
|
152
|
+
|
|
153
|
+
# Extract grammar from included tree
|
|
154
|
+
included_grammar = extract_grammar_tree(included_tree)
|
|
155
|
+
|
|
156
|
+
# Recursively process includes in the included file
|
|
157
|
+
if included_grammar[:includes] && !included_grammar[:includes].empty?
|
|
158
|
+
process_includes(included_tree, File.dirname(included_file_path),
|
|
159
|
+
visited_files.dup)
|
|
160
|
+
|
|
161
|
+
# Re-extract grammar after processing its includes
|
|
162
|
+
included_grammar = extract_grammar_tree(included_tree)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# Merge included definitions into temporary grammar_tree
|
|
166
|
+
merge_included_grammar(grammar_tree, included_grammar, override)
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# Merge the resolved grammar_tree back into the main tree
|
|
170
|
+
tree[:start] = grammar_tree[:start] if grammar_tree[:start]
|
|
171
|
+
if grammar_tree[:definitions]
|
|
172
|
+
tree[:definitions] =
|
|
173
|
+
grammar_tree[:definitions]
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# Process raw_trailing if present (named patterns after includes)
|
|
177
|
+
process_raw_trailing!(tree, grammar_tree) if tree[:raw_trailing]
|
|
178
|
+
|
|
179
|
+
# Clean up - remove top_includes key as it's been processed
|
|
180
|
+
tree.delete(:top_includes)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# Process a single include directive
|
|
184
|
+
#
|
|
185
|
+
# @param grammar_tree [Hash] Grammar tree to merge into
|
|
186
|
+
# @param include_item [Hash] Include item from parse tree
|
|
187
|
+
# @param base_dir [String] Base directory for resolving relative paths
|
|
188
|
+
# @param visited_files [Set] Set of already visited file paths
|
|
189
|
+
def process_single_include(grammar_tree, include_item, base_dir,
|
|
190
|
+
visited_files)
|
|
191
|
+
href = extract_string_literal(include_item[:href])
|
|
192
|
+
override = parse_override(include_item[:override])
|
|
193
|
+
|
|
194
|
+
# Resolve file path relative to base_dir
|
|
195
|
+
included_file_path = base_dir ? File.join(base_dir, href) : href
|
|
196
|
+
|
|
197
|
+
# Parse included file recursively
|
|
198
|
+
included_tree = parse_file_to_tree(included_file_path, base_dir,
|
|
199
|
+
visited_files.dup)
|
|
200
|
+
|
|
201
|
+
# Process raw grammar/override/patterns before extracting
|
|
202
|
+
process_raw_nodes!(included_tree)
|
|
203
|
+
|
|
204
|
+
# Extract grammar from included tree
|
|
205
|
+
included_grammar = extract_grammar_tree(included_tree)
|
|
206
|
+
|
|
207
|
+
# Recursively process includes in the included file
|
|
208
|
+
if included_grammar[:includes] && !included_grammar[:includes].empty?
|
|
209
|
+
# Process includes in the included file
|
|
210
|
+
process_includes(included_tree, File.dirname(included_file_path),
|
|
211
|
+
visited_files.dup)
|
|
212
|
+
|
|
213
|
+
# Re-extract grammar after processing its includes
|
|
214
|
+
included_grammar = extract_grammar_tree(included_tree)
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
# Merge included definitions into current tree
|
|
218
|
+
merge_included_grammar(grammar_tree, included_grammar, override)
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# Extract grammar tree from parse tree
|
|
222
|
+
#
|
|
223
|
+
# @param tree [Hash] Parse tree
|
|
224
|
+
# @return [Hash] Grammar tree
|
|
225
|
+
def extract_grammar_tree(tree)
|
|
226
|
+
if tree.key?(:inner_grammar)
|
|
227
|
+
tree[:inner_grammar]
|
|
228
|
+
else
|
|
229
|
+
tree
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
# Process raw_grammar/raw_override/raw_patterns nodes in-place
|
|
234
|
+
#
|
|
235
|
+
# This is needed when included files contain grammar blocks or
|
|
236
|
+
# overrides that are captured as raw text by the parser.
|
|
237
|
+
#
|
|
238
|
+
# @param tree [Hash] Parse tree to process in-place
|
|
239
|
+
def process_raw_nodes!(tree)
|
|
240
|
+
ParseTreeProcessor.new(tree).send(:process_raw_overrides!, tree)
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
# Process raw_trailing content (named patterns after top-level includes)
|
|
244
|
+
#
|
|
245
|
+
# For schemas like ietf.rnc where include directives are followed by
|
|
246
|
+
# named pattern definitions, the trailing content is captured as raw_trailing.
|
|
247
|
+
# This method parses that content and adds definitions to grammar_tree.
|
|
248
|
+
#
|
|
249
|
+
# @param tree [Hash] Parse tree containing raw_trailing
|
|
250
|
+
# @param grammar_tree [Hash] Grammar tree to merge definitions into
|
|
251
|
+
def process_raw_trailing!(tree, grammar_tree)
|
|
252
|
+
raw = tree[:raw_trailing]
|
|
253
|
+
return unless raw
|
|
254
|
+
|
|
255
|
+
text = if raw.is_a?(Array)
|
|
256
|
+
raw.map { |r| r.respond_to?(:str) ? r.str : r.to_s }.join
|
|
257
|
+
else
|
|
258
|
+
(raw.respond_to?(:str) ? raw.str : raw.to_s)
|
|
259
|
+
end
|
|
260
|
+
return if text.strip.empty?
|
|
261
|
+
|
|
262
|
+
# Parse raw_trailing as a grammar (which handles named patterns)
|
|
263
|
+
parser = Rng::RncParser.new
|
|
264
|
+
begin
|
|
265
|
+
parsed = parser.grammar.parse(text.strip)
|
|
266
|
+
patterns = parsed[:patterns] || []
|
|
267
|
+
grammar_tree[:definitions] ||= []
|
|
268
|
+
grammar_tree[:definitions].concat(patterns)
|
|
269
|
+
rescue Parslet::ParseFailed => e
|
|
270
|
+
warn "Warning: Failed to parse trailing content: #{e.message}" if ENV['RNG_VERBOSE']
|
|
271
|
+
ensure
|
|
272
|
+
tree.delete(:raw_trailing)
|
|
273
|
+
end
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
# Build grammar tree from different tree structures
|
|
277
|
+
#
|
|
278
|
+
# Handles:
|
|
279
|
+
# - Top-level includes (Metanorma style)
|
|
280
|
+
# - Grammar block wrapper
|
|
281
|
+
# - Flat grammar
|
|
282
|
+
#
|
|
283
|
+
# @param tree [Hash] Parse tree
|
|
284
|
+
# @return [Hash] Normalized grammar tree
|
|
285
|
+
def build_grammar_tree(tree)
|
|
286
|
+
ParseTreeProcessor.new(tree).normalize.grammar_tree
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
# Merge included grammar into current grammar, applying overrides
|
|
290
|
+
#
|
|
291
|
+
# @param target_tree [Hash] Target grammar tree to merge into
|
|
292
|
+
# @param source_tree [Hash] Source grammar tree to merge from
|
|
293
|
+
# @param override [Hash, nil] Override definitions from include directive
|
|
294
|
+
def merge_included_grammar(target_tree, source_tree, override)
|
|
295
|
+
# Merge datatype library if not already set
|
|
296
|
+
if source_tree[:datatype_library] && !target_tree[:datatype_library]
|
|
297
|
+
target_tree[:datatype_library] =
|
|
298
|
+
source_tree[:datatype_library]
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
# Merge start pattern if not overridden
|
|
302
|
+
if source_tree[:start] && !override
|
|
303
|
+
# If target has no start, use source start
|
|
304
|
+
target_tree[:start] ||= source_tree[:start]
|
|
305
|
+
elsif override && override[:start]
|
|
306
|
+
# Use override start
|
|
307
|
+
target_tree[:start] = override[:start]
|
|
308
|
+
elsif source_tree[:start] && !target_tree[:start]
|
|
309
|
+
target_tree[:start] = source_tree[:start]
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
# Initialize definitions array if needed
|
|
313
|
+
target_tree[:definitions] ||= []
|
|
314
|
+
|
|
315
|
+
# Merge definitions from source
|
|
316
|
+
source_tree[:definitions]&.each do |source_def|
|
|
317
|
+
# Check if this definition is overridden
|
|
318
|
+
overridden = false
|
|
319
|
+
|
|
320
|
+
if override && override[:definitions]
|
|
321
|
+
override[:definitions].each do |override_def|
|
|
322
|
+
# Check if names match
|
|
323
|
+
next unless source_def[:name] && override_def[:name] &&
|
|
324
|
+
extract_string(source_def[:name][:identifier]) == extract_string(override_def[:name][:identifier])
|
|
325
|
+
|
|
326
|
+
# Use override instead of source
|
|
327
|
+
target_tree[:definitions] << override_def
|
|
328
|
+
overridden = true
|
|
329
|
+
break
|
|
330
|
+
end
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
# If not overridden, add source definition
|
|
334
|
+
target_tree[:definitions] << source_def unless overridden
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
# Add any override definitions not matched with source
|
|
338
|
+
return unless override && override[:definitions]
|
|
339
|
+
|
|
340
|
+
override[:definitions].each do |override_def|
|
|
341
|
+
# Check if this override matched any source definition
|
|
342
|
+
matched = false
|
|
343
|
+
|
|
344
|
+
source_tree[:definitions]&.each do |source_def|
|
|
345
|
+
next unless source_def[:name] && override_def[:name] &&
|
|
346
|
+
extract_string(source_def[:name][:identifier]) == extract_string(override_def[:name][:identifier])
|
|
347
|
+
|
|
348
|
+
matched = true
|
|
349
|
+
break
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
# If no match, this is a new definition - add it
|
|
353
|
+
target_tree[:definitions] << override_def unless matched
|
|
354
|
+
end
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
# Helper method to extract clean string without Parslet position markers
|
|
358
|
+
#
|
|
359
|
+
# @param obj [Object] Parslet::Slice or String
|
|
360
|
+
# @return [String] Clean string
|
|
361
|
+
def extract_string(obj)
|
|
362
|
+
RncParser.extract_string(obj)
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
# Parse raw override string into structured override hash
|
|
366
|
+
#
|
|
367
|
+
# @param override [Hash, nil] Override hash potentially containing :raw_override
|
|
368
|
+
# @return [Hash, nil] Parsed override with :start and :definitions, or nil
|
|
369
|
+
def parse_override(override)
|
|
370
|
+
return nil unless override
|
|
371
|
+
return override unless override[:raw_override]
|
|
372
|
+
|
|
373
|
+
raw = extract_string(override[:raw_override])
|
|
374
|
+
return nil if raw.nil? || raw.strip.empty?
|
|
375
|
+
|
|
376
|
+
# Parse the raw override content as RNC directly (top-level style)
|
|
377
|
+
parser = RncParser.new
|
|
378
|
+
begin
|
|
379
|
+
parsed = parser.parse(raw.strip)
|
|
380
|
+
# Normalize the parsed override
|
|
381
|
+
processor = ParseTreeProcessor.new(parsed)
|
|
382
|
+
normalized = processor.normalize
|
|
383
|
+
tree = normalized.grammar_tree
|
|
384
|
+
result = {}
|
|
385
|
+
result[:start] = tree[:start] if tree[:start]
|
|
386
|
+
result[:definitions] = tree[:definitions] if tree[:definitions] && !tree[:definitions].empty?
|
|
387
|
+
result.empty? ? nil : result
|
|
388
|
+
rescue Parslet::ParseFailed
|
|
389
|
+
nil
|
|
390
|
+
end
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
# Helper method to extract string literal with concatenations
|
|
394
|
+
#
|
|
395
|
+
# @param lit [Hash] String literal with :string_parts and :concatenations
|
|
396
|
+
# @return [String] Extracted string
|
|
397
|
+
def extract_string_literal(lit)
|
|
398
|
+
return '' unless lit
|
|
399
|
+
|
|
400
|
+
# Extract main string parts
|
|
401
|
+
result = extract_string_parts(lit[:string_parts])
|
|
402
|
+
|
|
403
|
+
# Handle concatenations if present
|
|
404
|
+
if lit[:concatenations].is_a?(Array)
|
|
405
|
+
lit[:concatenations].each do |concat|
|
|
406
|
+
result += extract_string_parts(concat[:concat_string_parts])
|
|
407
|
+
end
|
|
408
|
+
end
|
|
409
|
+
|
|
410
|
+
result
|
|
411
|
+
end
|
|
412
|
+
|
|
413
|
+
# Extract string from string_parts array
|
|
414
|
+
#
|
|
415
|
+
# @param parts [Array, String] String parts
|
|
416
|
+
# @return [String] Extracted string
|
|
417
|
+
def extract_string_parts(parts)
|
|
418
|
+
return '' unless parts
|
|
419
|
+
return parts if parts.is_a?(String)
|
|
420
|
+
return parts.str if parts.respond_to?(:str)
|
|
421
|
+
|
|
422
|
+
return '' unless parts.is_a?(Array)
|
|
423
|
+
|
|
424
|
+
parts.map do |part|
|
|
425
|
+
if part.is_a?(String)
|
|
426
|
+
part
|
|
427
|
+
elsif part.respond_to?(:str)
|
|
428
|
+
part.str
|
|
429
|
+
elsif part[:hex_escape]
|
|
430
|
+
# Handle \x{HEX}
|
|
431
|
+
hex_str = part[:hex_escape][:hex]
|
|
432
|
+
hex_str = hex_str.str if hex_str.respond_to?(:str)
|
|
433
|
+
[hex_str.to_i(16)].pack('U')
|
|
434
|
+
elsif part[:char_escape]
|
|
435
|
+
# Handle \", \\, \n, \r, \t, and RELAX NG class escapes \i, \c, \d, \w
|
|
436
|
+
char = part[:char_escape][:char]
|
|
437
|
+
char = char.str if char.respond_to?(:str)
|
|
438
|
+
case char
|
|
439
|
+
when '"' then '"'
|
|
440
|
+
when '\\' then '\\'
|
|
441
|
+
when 'n' then "\n"
|
|
442
|
+
when 'r' then "\r"
|
|
443
|
+
when 't' then "\t"
|
|
444
|
+
when 'i' then '\\i'
|
|
445
|
+
when 'c' then '\\c'
|
|
446
|
+
when 'd' then '\\d'
|
|
447
|
+
when 'w' then '\\w'
|
|
448
|
+
else char
|
|
449
|
+
end
|
|
450
|
+
elsif part[:char]
|
|
451
|
+
# Regular character (plain char in string literal)
|
|
452
|
+
c = part[:char]
|
|
453
|
+
c = c.str if c.respond_to?(:str)
|
|
454
|
+
c.to_s
|
|
455
|
+
else
|
|
456
|
+
part.to_s
|
|
457
|
+
end
|
|
458
|
+
end.join
|
|
459
|
+
end
|
|
460
|
+
end
|
|
461
|
+
end
|
data/lib/rng/interleave.rb
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "lutaml/model"
|
|
4
|
-
|
|
5
3
|
module Rng
|
|
6
4
|
class Interleave < Lutaml::Model::Serializable
|
|
7
5
|
attribute :id, :string
|
|
@@ -25,34 +23,36 @@ module Rng
|
|
|
25
23
|
attribute :notAllowed, NotAllowed, collection: true, initialize_empty: true
|
|
26
24
|
|
|
27
25
|
xml do
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
element 'interleave'
|
|
27
|
+
ordered
|
|
28
|
+
|
|
29
|
+
namespace ::Rng::Namespaces::RngNamespace
|
|
30
30
|
|
|
31
|
-
map_attribute
|
|
32
|
-
map_attribute
|
|
31
|
+
map_attribute 'id', to: :id
|
|
32
|
+
map_attribute 'ns', to: :ns, value_map: {
|
|
33
33
|
from: { empty: :empty, omitted: :omitted, nil: :nil },
|
|
34
34
|
to: { empty: :empty, omitted: :omitted, nil: :nil }
|
|
35
35
|
}
|
|
36
|
-
map_attribute
|
|
36
|
+
map_attribute 'datatypeLibrary', to: :datatypeLibrary, value_map: {
|
|
37
37
|
from: { empty: :empty, omitted: :omitted, nil: :nil },
|
|
38
38
|
to: { empty: :empty, omitted: :omitted, nil: :nil }
|
|
39
39
|
}
|
|
40
|
-
map_element
|
|
41
|
-
map_element
|
|
42
|
-
map_element
|
|
43
|
-
map_element
|
|
44
|
-
map_element
|
|
45
|
-
map_element
|
|
46
|
-
map_element
|
|
47
|
-
map_element
|
|
48
|
-
map_element
|
|
49
|
-
map_element
|
|
50
|
-
map_element
|
|
51
|
-
map_element
|
|
52
|
-
map_element
|
|
53
|
-
map_element
|
|
54
|
-
map_element
|
|
55
|
-
map_element
|
|
40
|
+
map_element 'element', to: :element
|
|
41
|
+
map_element 'attribute', to: :attribute
|
|
42
|
+
map_element 'ref', to: :ref
|
|
43
|
+
map_element 'choice', to: :choice
|
|
44
|
+
map_element 'group', to: :group
|
|
45
|
+
map_element 'interleave', to: :interleave
|
|
46
|
+
map_element 'mixed', to: :mixed
|
|
47
|
+
map_element 'optional', to: :optional
|
|
48
|
+
map_element 'zeroOrMore', to: :zeroOrMore
|
|
49
|
+
map_element 'oneOrMore', to: :oneOrMore
|
|
50
|
+
map_element 'text', to: :text
|
|
51
|
+
map_element 'empty', to: :empty
|
|
52
|
+
map_element 'value', to: :value
|
|
53
|
+
map_element 'data', to: :data
|
|
54
|
+
map_element 'list', to: :list
|
|
55
|
+
map_element 'notAllowed', to: :notAllowed
|
|
56
56
|
end
|
|
57
57
|
end
|
|
58
58
|
end
|