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/cli.rb
ADDED
|
@@ -0,0 +1,607 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'thor'
|
|
4
|
+
require 'rng'
|
|
5
|
+
require 'rng/version'
|
|
6
|
+
|
|
7
|
+
module Rng
|
|
8
|
+
# Base error class for CLI errors (inherits from Thor::Error for proper error display)
|
|
9
|
+
class CLIError < Thor::Error; end
|
|
10
|
+
|
|
11
|
+
# Validation error - document failed validation
|
|
12
|
+
class ValidationError < CLIError; end
|
|
13
|
+
|
|
14
|
+
# File error - file not found or not readable
|
|
15
|
+
class FileError < CLIError; end
|
|
16
|
+
|
|
17
|
+
# Parse error - schema could not be parsed
|
|
18
|
+
class ParseError < CLIError; end
|
|
19
|
+
|
|
20
|
+
# Conversion error - could not convert between formats
|
|
21
|
+
class ConversionError < CLIError; end
|
|
22
|
+
|
|
23
|
+
class CLI < Thor
|
|
24
|
+
VERSION = Rng::VERSION
|
|
25
|
+
|
|
26
|
+
class_option :verbose, type: :boolean, aliases: '-v', desc: 'Verbose output'
|
|
27
|
+
class_option :quiet, type: :boolean, aliases: '-q', desc: 'Suppress non-error output'
|
|
28
|
+
|
|
29
|
+
def self.exit_on_failure?
|
|
30
|
+
true
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
desc 'validate SCHEMA [DOCUMENT]', 'Validate XML document against RELAX NG schema'
|
|
34
|
+
long_desc <<~DESC
|
|
35
|
+
Validate an XML document against a RELAX NG schema (replaces jing).
|
|
36
|
+
|
|
37
|
+
SCHEMA is the path to the RELAX NG schema file (RNG or RNC format).
|
|
38
|
+
DOCUMENT is the path to the XML document to validate (optional).
|
|
39
|
+
|
|
40
|
+
If DOCUMENT is not provided, validates the schema itself.
|
|
41
|
+
DESC
|
|
42
|
+
method_option :compact, type: :boolean, aliases: '-c', desc: 'Schema is in RNC compact format'
|
|
43
|
+
method_option :xml, type: :boolean, aliases: '-x', desc: 'Schema is in RNG XML format (default: auto-detect)'
|
|
44
|
+
method_option :ignore_whitespace, type: :boolean, aliases: '-i', desc: 'Ignore insignificant whitespace'
|
|
45
|
+
method_option :output, type: :string, aliases: '-o', desc: 'Output format: text, xml, json (default: text)'
|
|
46
|
+
def validate(schema, document = nil)
|
|
47
|
+
if document
|
|
48
|
+
validate_document(schema, document)
|
|
49
|
+
else
|
|
50
|
+
validate_schema(schema)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
desc 'convert INPUT [OUTPUT]', 'Convert between RNC, RNG, and other schema formats'
|
|
55
|
+
long_desc <<~DESC
|
|
56
|
+
Convert between RELAX NG Compact (RNC) and XML (RNG) formats (replaces trang).
|
|
57
|
+
|
|
58
|
+
INPUT is the path to the input schema file.
|
|
59
|
+
OUTPUT is the path to the output file (default: stdout).
|
|
60
|
+
DESC
|
|
61
|
+
method_option :input_format, type: :string, aliases: '-I', desc: 'Input format: rng, rnc, auto (default: auto)'
|
|
62
|
+
method_option :output_format, type: :string, aliases: '-O', desc: 'Output format: rng, rnc, auto (default: auto)'
|
|
63
|
+
method_option :inherit, type: :boolean, desc: 'Inherit namespaces from input'
|
|
64
|
+
def convert(input, output = nil)
|
|
65
|
+
convert_schema(input, output)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
desc 'parse SCHEMA', 'Parse and display schema structure'
|
|
69
|
+
long_desc <<~DESC
|
|
70
|
+
Parse a schema and display its structure in various formats.
|
|
71
|
+
|
|
72
|
+
SCHEMA is the path to the schema file (RNG or RNC format).
|
|
73
|
+
DESC
|
|
74
|
+
method_option :format, type: :string, aliases: '-f', desc: 'Output format: text, json, yaml, xml (default: text)'
|
|
75
|
+
method_option :ast, type: :boolean, desc: 'Show abstract syntax tree (RNC only)'
|
|
76
|
+
method_option :locations, type: :boolean, desc: 'Show source locations'
|
|
77
|
+
def parse(schema)
|
|
78
|
+
parse_schema(schema)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
desc 'info SCHEMA', 'Show information about a schema'
|
|
82
|
+
long_desc <<~DESC
|
|
83
|
+
Show information about a RELAX NG schema.
|
|
84
|
+
|
|
85
|
+
SCHEMA is the path to the schema file.
|
|
86
|
+
DESC
|
|
87
|
+
method_option :statistics, type: :boolean, desc: 'Show pattern statistics'
|
|
88
|
+
method_option :namespaces, type: :boolean, desc: 'List used namespaces'
|
|
89
|
+
def info(schema)
|
|
90
|
+
info_schema(schema)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
desc 'version', 'Show version number'
|
|
94
|
+
def version
|
|
95
|
+
puts "rng version #{VERSION}"
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
map %w[--version -V] => :version
|
|
99
|
+
|
|
100
|
+
private
|
|
101
|
+
|
|
102
|
+
def detect_format(path)
|
|
103
|
+
return :rnc if path.end_with?('.rnc')
|
|
104
|
+
return :rng if path.end_with?('.rng')
|
|
105
|
+
return :xml if path.end_with?('.xml')
|
|
106
|
+
|
|
107
|
+
:auto
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def validate_document(schema_path, document_path)
|
|
111
|
+
raise FileError, "Schema file not found: #{schema_path}" unless File.exist?(schema_path)
|
|
112
|
+
raise FileError, "Document file not found: #{document_path}" unless File.exist?(document_path)
|
|
113
|
+
|
|
114
|
+
say "Validating #{document_path} against #{schema_path}...", :green
|
|
115
|
+
|
|
116
|
+
schema_content = File.read(schema_path)
|
|
117
|
+
document_content = File.read(document_path)
|
|
118
|
+
|
|
119
|
+
format = if options[:compact]
|
|
120
|
+
:rnc
|
|
121
|
+
elsif options[:xml]
|
|
122
|
+
:rng
|
|
123
|
+
else
|
|
124
|
+
detect_format(schema_path)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
grammar = if format == :rnc
|
|
128
|
+
Rng.parse_rnc(schema_content)
|
|
129
|
+
else
|
|
130
|
+
Rng.parse(schema_content)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Use SchemaValidator for validation
|
|
134
|
+
Rng::SchemaValidator.validate(document_content, grammar: grammar)
|
|
135
|
+
|
|
136
|
+
say 'Document is valid', :green
|
|
137
|
+
rescue Errno::ENOENT => e
|
|
138
|
+
raise FileError, "File not found: #{e.filename}"
|
|
139
|
+
rescue Rng::SchemaValidationError => e
|
|
140
|
+
raise ValidationError, e.message
|
|
141
|
+
rescue StandardError => e
|
|
142
|
+
raise e if e.is_a?(CLIError)
|
|
143
|
+
|
|
144
|
+
raise ParseError, "#{e.class}: #{e.message}"
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def validate_schema(schema_path)
|
|
148
|
+
raise FileError, "Schema file not found: #{schema_path}" unless File.exist?(schema_path)
|
|
149
|
+
|
|
150
|
+
say "Validating schema #{schema_path}...", :green
|
|
151
|
+
|
|
152
|
+
content = File.read(schema_path)
|
|
153
|
+
|
|
154
|
+
format = if options[:compact]
|
|
155
|
+
:rnc
|
|
156
|
+
elsif options[:xml]
|
|
157
|
+
:rng
|
|
158
|
+
else
|
|
159
|
+
detect_format(schema_path)
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
grammar = if format == :rnc
|
|
163
|
+
Rng.parse_rnc(content)
|
|
164
|
+
else
|
|
165
|
+
Rng.parse(content)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
say 'Schema is valid', :green
|
|
169
|
+
say "Found #{grammar.define.count} definitions" unless options[:quiet]
|
|
170
|
+
rescue StandardError => e
|
|
171
|
+
raise e if e.is_a?(CLIError)
|
|
172
|
+
|
|
173
|
+
raise ParseError, "#{e.class}: #{e.message}"
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def convert_schema(input_path, output_path)
|
|
177
|
+
raise FileError, "Input file not found: #{input_path}" unless File.exist?(input_path)
|
|
178
|
+
|
|
179
|
+
input_content = File.read(input_path)
|
|
180
|
+
|
|
181
|
+
input_format = if options[:input_format] && options[:input_format] != 'auto'
|
|
182
|
+
options[:input_format].to_sym
|
|
183
|
+
else
|
|
184
|
+
detect_format(input_path)
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
output_format = if options[:output_format] && options[:output_format] != 'auto'
|
|
188
|
+
options[:output_format].to_sym
|
|
189
|
+
elsif input_format == :rnc
|
|
190
|
+
:rng
|
|
191
|
+
else
|
|
192
|
+
:rnc
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
say "Converting #{input_path} (#{input_format}) to #{output_format}...", :green
|
|
196
|
+
|
|
197
|
+
grammar = case input_format
|
|
198
|
+
when :rnc
|
|
199
|
+
Rng.parse_rnc(input_content)
|
|
200
|
+
when :rng
|
|
201
|
+
Rng.parse(input_content)
|
|
202
|
+
else
|
|
203
|
+
begin
|
|
204
|
+
Rng.parse(input_content)
|
|
205
|
+
rescue StandardError
|
|
206
|
+
Rng.parse_rnc(input_content)
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
result = case output_format
|
|
211
|
+
when :rnc
|
|
212
|
+
Rng.to_rnc(grammar)
|
|
213
|
+
when :rng
|
|
214
|
+
grammar.to_xml
|
|
215
|
+
else
|
|
216
|
+
raise ConversionError, "Unknown output format: #{output_format}"
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
if output_path
|
|
220
|
+
File.write(output_path, result)
|
|
221
|
+
say "Written to #{output_path}", :green
|
|
222
|
+
else
|
|
223
|
+
puts result
|
|
224
|
+
end
|
|
225
|
+
rescue StandardError => e
|
|
226
|
+
raise e if e.is_a?(CLIError)
|
|
227
|
+
|
|
228
|
+
raise ConversionError, "#{e.class}: #{e.message}"
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
def parse_schema(schema_path)
|
|
232
|
+
raise FileError, "Schema file not found: #{schema_path}" unless File.exist?(schema_path)
|
|
233
|
+
|
|
234
|
+
content = File.read(schema_path)
|
|
235
|
+
output_format = (options[:format] || 'text').to_sym
|
|
236
|
+
|
|
237
|
+
format = if options[:compact]
|
|
238
|
+
:rnc
|
|
239
|
+
elsif options[:xml]
|
|
240
|
+
:rng
|
|
241
|
+
else
|
|
242
|
+
detect_format(schema_path)
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
grammar = case format
|
|
246
|
+
when :rnc
|
|
247
|
+
Rng.parse_rnc(content)
|
|
248
|
+
when :rng
|
|
249
|
+
Rng.parse(content)
|
|
250
|
+
else
|
|
251
|
+
begin
|
|
252
|
+
Rng.parse(content)
|
|
253
|
+
rescue StandardError
|
|
254
|
+
Rng.parse_rnc(content)
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
case output_format
|
|
259
|
+
when :json
|
|
260
|
+
puts JSON.pretty_generate(grammar.to_h)
|
|
261
|
+
when :yaml
|
|
262
|
+
require 'yaml'
|
|
263
|
+
puts YAML.dump(grammar.to_h)
|
|
264
|
+
when :xml
|
|
265
|
+
puts grammar.to_xml
|
|
266
|
+
else
|
|
267
|
+
display_schema_text(grammar)
|
|
268
|
+
end
|
|
269
|
+
rescue StandardError => e
|
|
270
|
+
raise e if e.is_a?(CLIError)
|
|
271
|
+
|
|
272
|
+
raise ParseError, "#{e.class}: #{e.message}"
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
def display_schema_text(grammar)
|
|
276
|
+
say 'Schema Structure:', :green
|
|
277
|
+
start = grammar.start
|
|
278
|
+
if start && !start.empty?
|
|
279
|
+
say " Start: #{start.first ? pattern_name(start.first) : 'none'}"
|
|
280
|
+
else
|
|
281
|
+
say ' Start: none'
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
defines = grammar.define
|
|
285
|
+
if defines && !defines.empty?
|
|
286
|
+
say ' Definitions:', :green
|
|
287
|
+
defines.each do |define|
|
|
288
|
+
pattern_types = collect_pattern_types(define)
|
|
289
|
+
say " #{define.name}: #{pattern_types.join(', ')}"
|
|
290
|
+
end
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
includes = grammar.include
|
|
294
|
+
return unless includes && !includes.empty?
|
|
295
|
+
|
|
296
|
+
say ' Includes:', :green
|
|
297
|
+
includes.each do |inc|
|
|
298
|
+
say " #{inc.href}"
|
|
299
|
+
end
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
def collect_pattern_types(define)
|
|
303
|
+
types = []
|
|
304
|
+
types << 'ref' if define.ref&.any?
|
|
305
|
+
types << 'element' if define.element&.any?
|
|
306
|
+
types << 'choice' if define.choice&.any?
|
|
307
|
+
types << 'group' if define.group&.any?
|
|
308
|
+
types << 'interleave' if define.interleave&.any?
|
|
309
|
+
types << 'mixed' if define.mixed&.any?
|
|
310
|
+
types << 'optional' if define.optional&.any?
|
|
311
|
+
types << 'zeroOrMore' if define.zeroOrMore&.any?
|
|
312
|
+
types << 'oneOrMore' if define.oneOrMore&.any?
|
|
313
|
+
types << 'text' if define.text&.any?
|
|
314
|
+
types << 'empty' if define.empty&.any?
|
|
315
|
+
types << 'value' if define.value&.any?
|
|
316
|
+
types << 'data' if define.data&.any?
|
|
317
|
+
types << 'list' if define.list&.any?
|
|
318
|
+
types << 'notAllowed' if define.notAllowed&.any?
|
|
319
|
+
types << 'attribute' if define.attribute&.any?
|
|
320
|
+
types
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
def pattern_name(pattern)
|
|
324
|
+
return 'empty' unless pattern
|
|
325
|
+
|
|
326
|
+
pattern.class.name.split('::').last
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
def info_schema(schema_path)
|
|
330
|
+
raise FileError, "Schema file not found: #{schema_path}" unless File.exist?(schema_path)
|
|
331
|
+
|
|
332
|
+
content = File.read(schema_path)
|
|
333
|
+
|
|
334
|
+
format = if options[:compact]
|
|
335
|
+
:rnc
|
|
336
|
+
elsif options[:xml]
|
|
337
|
+
:rng
|
|
338
|
+
else
|
|
339
|
+
detect_format(schema_path)
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
grammar = case format
|
|
343
|
+
when :rnc
|
|
344
|
+
Rng.parse_rnc(content)
|
|
345
|
+
when :rng
|
|
346
|
+
Rng.parse(content)
|
|
347
|
+
else
|
|
348
|
+
begin
|
|
349
|
+
Rng.parse(content)
|
|
350
|
+
rescue StandardError
|
|
351
|
+
Rng.parse_rnc(content)
|
|
352
|
+
end
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
say 'Schema Information:', :green
|
|
356
|
+
say " File: #{schema_path}"
|
|
357
|
+
say " Format: #{format}"
|
|
358
|
+
|
|
359
|
+
if options[:statistics]
|
|
360
|
+
stats = compute_statistics(grammar)
|
|
361
|
+
say ' Statistics:', :green
|
|
362
|
+
stats.each do |key, value|
|
|
363
|
+
say " #{key}: #{value}"
|
|
364
|
+
end
|
|
365
|
+
end
|
|
366
|
+
|
|
367
|
+
if options[:namespaces]
|
|
368
|
+
namespaces = collect_namespaces(grammar)
|
|
369
|
+
say ' Namespaces:', :green
|
|
370
|
+
namespaces.each do |ns|
|
|
371
|
+
say " #{ns}"
|
|
372
|
+
end
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
return unless !options[:statistics] && !options[:namespaces]
|
|
376
|
+
|
|
377
|
+
say " Definitions: #{grammar.define.count}"
|
|
378
|
+
say " Elements: #{count_elements(grammar)}"
|
|
379
|
+
rescue StandardError => e
|
|
380
|
+
raise e if e.is_a?(CLIError)
|
|
381
|
+
|
|
382
|
+
raise ParseError, "#{e.class}: #{e.message}"
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
def compute_statistics(grammar)
|
|
386
|
+
stats = {
|
|
387
|
+
definitions: grammar.define.count,
|
|
388
|
+
elements: count_elements(grammar),
|
|
389
|
+
attributes: count_attributes(grammar),
|
|
390
|
+
choices: count_patterns(grammar, 'Choice'),
|
|
391
|
+
groups: count_patterns(grammar, 'Group'),
|
|
392
|
+
interleaves: count_patterns(grammar, 'Interleave'),
|
|
393
|
+
optionals: count_patterns(grammar, 'Optional'),
|
|
394
|
+
zero_or_more: count_patterns(grammar, 'ZeroOrMore'),
|
|
395
|
+
one_or_more: count_patterns(grammar, 'OneOrMore')
|
|
396
|
+
}
|
|
397
|
+
stats[:text] = count_patterns(grammar, 'Text')
|
|
398
|
+
stats[:empty] = count_patterns(grammar, 'Empty')
|
|
399
|
+
stats[:value] = count_patterns(grammar, 'Value')
|
|
400
|
+
stats[:data] = count_patterns(grammar, 'Data')
|
|
401
|
+
stats
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
def count_elements(grammar)
|
|
405
|
+
count = 0
|
|
406
|
+
grammar.define.each do |define|
|
|
407
|
+
count += define.element.count
|
|
408
|
+
define.element.each { |e| count += count_elements_in(e) }
|
|
409
|
+
end
|
|
410
|
+
count
|
|
411
|
+
end
|
|
412
|
+
|
|
413
|
+
def count_elements_in(element)
|
|
414
|
+
return 0 unless element
|
|
415
|
+
|
|
416
|
+
count = 0
|
|
417
|
+
element.element.each { |e| count += 1 + count_elements_in(e) } if element.respond_to?(:element) && element.element
|
|
418
|
+
if element.respond_to?(:choice) && element.choice
|
|
419
|
+
element.choice.each do |c|
|
|
420
|
+
next unless c.respond_to?(:element) && c.element
|
|
421
|
+
|
|
422
|
+
c.element.each do |e|
|
|
423
|
+
count += count_elements_in(e)
|
|
424
|
+
end
|
|
425
|
+
end
|
|
426
|
+
end
|
|
427
|
+
if element.respond_to?(:group) && element.group
|
|
428
|
+
element.group.each do |g|
|
|
429
|
+
next unless g.respond_to?(:element) && g.element
|
|
430
|
+
|
|
431
|
+
g.element.each do |e|
|
|
432
|
+
count += count_elements_in(e)
|
|
433
|
+
end
|
|
434
|
+
end
|
|
435
|
+
end
|
|
436
|
+
if element.respond_to?(:interleave) && element.interleave
|
|
437
|
+
element.interleave.each do |i|
|
|
438
|
+
next unless i.respond_to?(:element) && i.element
|
|
439
|
+
|
|
440
|
+
i.element.each do |e|
|
|
441
|
+
count += count_elements_in(e)
|
|
442
|
+
end
|
|
443
|
+
end
|
|
444
|
+
end
|
|
445
|
+
if element.respond_to?(:optional) && element.optional
|
|
446
|
+
element.optional.each { |o| count += count_elements_in(o) if o.respond_to?(:element) && o.element }
|
|
447
|
+
end
|
|
448
|
+
if element.respond_to?(:zeroOrMore) && element.zeroOrMore
|
|
449
|
+
element.zeroOrMore.each { |z| count += count_elements_in(z) if z.respond_to?(:element) && z.element }
|
|
450
|
+
end
|
|
451
|
+
if element.respond_to?(:oneOrMore) && element.oneOrMore
|
|
452
|
+
element.oneOrMore.each { |o| count += count_elements_in(o) if o.respond_to?(:element) && o.element }
|
|
453
|
+
end
|
|
454
|
+
count
|
|
455
|
+
end
|
|
456
|
+
|
|
457
|
+
def count_attributes(grammar)
|
|
458
|
+
count = 0
|
|
459
|
+
grammar.define.each do |define|
|
|
460
|
+
count += define.attribute.count
|
|
461
|
+
define.element.each { |e| count += count_attributes_in(e) }
|
|
462
|
+
end
|
|
463
|
+
count
|
|
464
|
+
end
|
|
465
|
+
|
|
466
|
+
def count_attributes_in(element)
|
|
467
|
+
count = 0
|
|
468
|
+
return count unless element
|
|
469
|
+
|
|
470
|
+
count += element.attribute.count if element.respond_to?(:attribute) && element.attribute
|
|
471
|
+
element.element.each { |e| count += count_attributes_in(e) } if element.respond_to?(:element) && element.element
|
|
472
|
+
if element.respond_to?(:choice) && element.choice
|
|
473
|
+
element.choice.each do |c|
|
|
474
|
+
next unless c.respond_to?(:element) && c.element
|
|
475
|
+
|
|
476
|
+
c.element.each do |e|
|
|
477
|
+
count += count_attributes_in(e)
|
|
478
|
+
end
|
|
479
|
+
end
|
|
480
|
+
end
|
|
481
|
+
if element.respond_to?(:group) && element.group
|
|
482
|
+
element.group.each do |g|
|
|
483
|
+
next unless g.respond_to?(:element) && g.element
|
|
484
|
+
|
|
485
|
+
g.element.each do |e|
|
|
486
|
+
count += count_attributes_in(e)
|
|
487
|
+
end
|
|
488
|
+
end
|
|
489
|
+
end
|
|
490
|
+
if element.respond_to?(:interleave) && element.interleave
|
|
491
|
+
element.interleave.each do |i|
|
|
492
|
+
next unless i.respond_to?(:element) && i.element
|
|
493
|
+
|
|
494
|
+
i.element.each do |e|
|
|
495
|
+
count += count_attributes_in(e)
|
|
496
|
+
end
|
|
497
|
+
end
|
|
498
|
+
end
|
|
499
|
+
if element.respond_to?(:optional) && element.optional
|
|
500
|
+
element.optional.each { |o| count += count_attributes_in(o) if o.respond_to?(:element) && o.element }
|
|
501
|
+
end
|
|
502
|
+
if element.respond_to?(:zeroOrMore) && element.zeroOrMore
|
|
503
|
+
element.zeroOrMore.each { |z| count += count_attributes_in(z) if z.respond_to?(:element) && z.element }
|
|
504
|
+
end
|
|
505
|
+
if element.respond_to?(:oneOrMore) && element.oneOrMore
|
|
506
|
+
element.oneOrMore.each { |o| count += count_attributes_in(o) if o.respond_to?(:element) && o.element }
|
|
507
|
+
end
|
|
508
|
+
count
|
|
509
|
+
end
|
|
510
|
+
|
|
511
|
+
def count_patterns(grammar, class_name)
|
|
512
|
+
count = 0
|
|
513
|
+
grammar.define.each do |define|
|
|
514
|
+
count += define.send(class_name.downcase).count if define.respond_to?(class_name.downcase)
|
|
515
|
+
end
|
|
516
|
+
count
|
|
517
|
+
end
|
|
518
|
+
|
|
519
|
+
def collect_namespaces(grammar)
|
|
520
|
+
namespaces = Set.new
|
|
521
|
+
namespaces.add(grammar.datatype_library) if grammar.datatype_library
|
|
522
|
+
grammar.define.each do |define|
|
|
523
|
+
collect_namespaces_in_define(define, namespaces)
|
|
524
|
+
end
|
|
525
|
+
namespaces.to_a.sort
|
|
526
|
+
end
|
|
527
|
+
|
|
528
|
+
def collect_namespaces_in_define(define, namespaces)
|
|
529
|
+
define.element.each { |e| collect_namespaces_in_element(e, namespaces) }
|
|
530
|
+
define.group.each { |g| g.element.each { |e| collect_namespaces_in_element(e, namespaces) } }
|
|
531
|
+
define.choice.each { |c| c.element.each { |e| collect_namespaces_in_element(e, namespaces) } }
|
|
532
|
+
define.interleave.each { |i| i.element.each { |e| collect_namespaces_in_element(e, namespaces) } }
|
|
533
|
+
define.optional.each do |o|
|
|
534
|
+
next unless o.respond_to?(:element)
|
|
535
|
+
|
|
536
|
+
o.element.each do |e|
|
|
537
|
+
collect_namespaces_in_element(e, namespaces)
|
|
538
|
+
end
|
|
539
|
+
end
|
|
540
|
+
define.zeroOrMore.each do |z|
|
|
541
|
+
next unless z.respond_to?(:element)
|
|
542
|
+
|
|
543
|
+
z.element.each do |e|
|
|
544
|
+
collect_namespaces_in_element(e, namespaces)
|
|
545
|
+
end
|
|
546
|
+
end
|
|
547
|
+
define.oneOrMore.each do |o|
|
|
548
|
+
next unless o.respond_to?(:element)
|
|
549
|
+
|
|
550
|
+
o.element.each do |e|
|
|
551
|
+
collect_namespaces_in_element(e, namespaces)
|
|
552
|
+
end
|
|
553
|
+
end
|
|
554
|
+
end
|
|
555
|
+
|
|
556
|
+
def collect_namespaces_in_element(element, namespaces)
|
|
557
|
+
return unless element
|
|
558
|
+
|
|
559
|
+
namespaces.add(element.ns) if element.respond_to?(:ns) && element.ns
|
|
560
|
+
if element.respond_to?(:element) && element.element
|
|
561
|
+
element.element.each do |e|
|
|
562
|
+
collect_namespaces_in_element(e, namespaces)
|
|
563
|
+
end
|
|
564
|
+
end
|
|
565
|
+
if element.respond_to?(:choice) && element.choice
|
|
566
|
+
element.choice.each do |c|
|
|
567
|
+
next unless c.respond_to?(:element) && c.element
|
|
568
|
+
|
|
569
|
+
c.element.each do |e|
|
|
570
|
+
collect_namespaces_in_element(e, namespaces)
|
|
571
|
+
end
|
|
572
|
+
end
|
|
573
|
+
end
|
|
574
|
+
if element.respond_to?(:group) && element.group
|
|
575
|
+
element.group.each do |g|
|
|
576
|
+
next unless g.respond_to?(:element) && g.element
|
|
577
|
+
|
|
578
|
+
g.element.each do |e|
|
|
579
|
+
collect_namespaces_in_element(e, namespaces)
|
|
580
|
+
end
|
|
581
|
+
end
|
|
582
|
+
end
|
|
583
|
+
if element.respond_to?(:interleave) && element.interleave
|
|
584
|
+
element.interleave.each do |i|
|
|
585
|
+
next unless i.respond_to?(:element) && i.element
|
|
586
|
+
|
|
587
|
+
i.element.each do |e|
|
|
588
|
+
collect_namespaces_in_element(e, namespaces)
|
|
589
|
+
end
|
|
590
|
+
end
|
|
591
|
+
end
|
|
592
|
+
if element.respond_to?(:optional) && element.optional
|
|
593
|
+
element.optional.each do |o|
|
|
594
|
+
collect_namespaces_in_element(o, namespaces)
|
|
595
|
+
end
|
|
596
|
+
end
|
|
597
|
+
if element.respond_to?(:zeroOrMore) && element.zeroOrMore
|
|
598
|
+
element.zeroOrMore.each do |z|
|
|
599
|
+
collect_namespaces_in_element(z, namespaces)
|
|
600
|
+
end
|
|
601
|
+
end
|
|
602
|
+
return unless element.respond_to?(:oneOrMore) && element.oneOrMore
|
|
603
|
+
|
|
604
|
+
element.oneOrMore.each { |o| collect_namespaces_in_element(o, namespaces) }
|
|
605
|
+
end
|
|
606
|
+
end
|
|
607
|
+
end
|
data/lib/rng/data.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 Data < Lutaml::Model::Serializable
|
|
7
5
|
attribute :id, :string
|
|
@@ -12,21 +10,23 @@ module Rng
|
|
|
12
10
|
attribute :except, Except
|
|
13
11
|
|
|
14
12
|
xml do
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
element 'data'
|
|
14
|
+
ordered
|
|
15
|
+
|
|
16
|
+
namespace ::Rng::Namespaces::RngNamespace
|
|
17
17
|
|
|
18
|
-
map_attribute
|
|
19
|
-
map_attribute
|
|
18
|
+
map_attribute 'id', to: :id
|
|
19
|
+
map_attribute 'ns', to: :ns, value_map: {
|
|
20
20
|
from: { empty: :empty, omitted: :omitted, nil: :nil },
|
|
21
21
|
to: { empty: :empty, omitted: :omitted, nil: :nil }
|
|
22
22
|
}
|
|
23
|
-
map_attribute
|
|
23
|
+
map_attribute 'datatypeLibrary', to: :datatypeLibrary, value_map: {
|
|
24
24
|
from: { empty: :empty, omitted: :omitted, nil: :nil },
|
|
25
25
|
to: { empty: :empty, omitted: :omitted, nil: :nil }
|
|
26
26
|
}
|
|
27
|
-
map_attribute
|
|
28
|
-
map_element
|
|
29
|
-
map_element
|
|
27
|
+
map_attribute 'type', to: :type
|
|
28
|
+
map_element 'param', to: :param
|
|
29
|
+
map_element 'except', to: :except
|
|
30
30
|
end
|
|
31
31
|
end
|
|
32
32
|
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rng
|
|
4
|
+
# Represents a datatype library declaration in RNC
|
|
5
|
+
#
|
|
6
|
+
# Example: datatypes xsd = "http://www.w3.org/2001/XMLSchema-datatypes"
|
|
7
|
+
#
|
|
8
|
+
# @example Creating a datatype declaration
|
|
9
|
+
# decl = DatatypeDeclaration.new(
|
|
10
|
+
# prefix: "xsd",
|
|
11
|
+
# uri: "http://www.w3.org/2001/XMLSchema-datatypes"
|
|
12
|
+
# )
|
|
13
|
+
#
|
|
14
|
+
class DatatypeDeclaration
|
|
15
|
+
attr_reader :prefix, :uri
|
|
16
|
+
|
|
17
|
+
# Initialize a datatype library declaration
|
|
18
|
+
#
|
|
19
|
+
# @param prefix [String] Datatype library prefix
|
|
20
|
+
# @param uri [String] Datatype library URI
|
|
21
|
+
def initialize(prefix:, uri:)
|
|
22
|
+
@prefix = prefix
|
|
23
|
+
@uri = uri
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|