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.
Files changed (180) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/docs.yml +63 -0
  3. data/.github/workflows/release.yml +8 -3
  4. data/.gitignore +11 -0
  5. data/.rubocop.yml +10 -7
  6. data/.rubocop_todo.yml +229 -23
  7. data/CHANGELOG.md +317 -0
  8. data/CLAUDE.md +139 -0
  9. data/Gemfile +11 -12
  10. data/README.adoc +1538 -11
  11. data/Rakefile +11 -3
  12. data/docs/Gemfile +8 -0
  13. data/docs/_config.yml +23 -0
  14. data/docs/getting-started/index.adoc +75 -0
  15. data/docs/guides/error-handling.adoc +137 -0
  16. data/docs/guides/external-references.adoc +128 -0
  17. data/docs/guides/index.adoc +24 -0
  18. data/docs/guides/parsing-rnc.adoc +141 -0
  19. data/docs/guides/parsing-rng-xml.adoc +81 -0
  20. data/docs/guides/rng-to-rnc.adoc +101 -0
  21. data/docs/guides/validation.adoc +85 -0
  22. data/docs/index.adoc +52 -0
  23. data/docs/reference/api.adoc +126 -0
  24. data/docs/reference/cli.adoc +182 -0
  25. data/docs/understanding/architecture.adoc +58 -0
  26. data/docs/understanding/rng-vs-rnc.adoc +118 -0
  27. data/exe/rng +5 -0
  28. data/lib/rng/any_name.rb +10 -8
  29. data/lib/rng/attribute.rb +28 -26
  30. data/lib/rng/choice.rb +24 -24
  31. data/lib/rng/cli.rb +607 -0
  32. data/lib/rng/data.rb +10 -10
  33. data/lib/rng/datatype_declaration.rb +26 -0
  34. data/lib/rng/define.rb +44 -41
  35. data/lib/rng/div.rb +36 -0
  36. data/lib/rng/documentation.rb +9 -0
  37. data/lib/rng/element.rb +39 -37
  38. data/lib/rng/empty.rb +7 -7
  39. data/lib/rng/except.rb +25 -25
  40. data/lib/rng/external_ref.rb +8 -8
  41. data/lib/rng/external_ref_resolver.rb +602 -0
  42. data/lib/rng/foreign_attribute.rb +26 -0
  43. data/lib/rng/foreign_element.rb +33 -0
  44. data/lib/rng/grammar.rb +14 -12
  45. data/lib/rng/group.rb +26 -24
  46. data/lib/rng/include.rb +5 -6
  47. data/lib/rng/include_processor.rb +461 -0
  48. data/lib/rng/interleave.rb +23 -23
  49. data/lib/rng/list.rb +22 -22
  50. data/lib/rng/mixed.rb +23 -23
  51. data/lib/rng/name.rb +6 -7
  52. data/lib/rng/namespace_declaration.rb +47 -0
  53. data/lib/rng/namespaces.rb +15 -0
  54. data/lib/rng/not_allowed.rb +7 -7
  55. data/lib/rng/ns_name.rb +9 -9
  56. data/lib/rng/one_or_more.rb +23 -23
  57. data/lib/rng/optional.rb +23 -23
  58. data/lib/rng/param.rb +7 -8
  59. data/lib/rng/parent_ref.rb +8 -8
  60. data/lib/rng/parse_tree_processor.rb +695 -0
  61. data/lib/rng/pattern.rb +7 -7
  62. data/lib/rng/ref.rb +8 -8
  63. data/lib/rng/rnc_builder.rb +927 -0
  64. data/lib/rng/rnc_parser.rb +605 -305
  65. data/lib/rng/rnc_to_rng_converter.rb +1408 -0
  66. data/lib/rng/schema_preamble.rb +73 -0
  67. data/lib/rng/schema_validator.rb +1622 -0
  68. data/lib/rng/start.rb +27 -25
  69. data/lib/rng/test_suite_parser.rb +168 -0
  70. data/lib/rng/text.rb +11 -8
  71. data/lib/rng/to_rnc.rb +4 -35
  72. data/lib/rng/value.rb +6 -7
  73. data/lib/rng/version.rb +1 -1
  74. data/lib/rng/zero_or_more.rb +23 -23
  75. data/lib/rng.rb +68 -17
  76. data/rng.gemspec +18 -19
  77. data/scripts/extract_spectest_resources.rb +96 -0
  78. data/spec/fixtures/compacttest.xml +2511 -0
  79. data/spec/fixtures/external/circular_a.rng +7 -0
  80. data/spec/fixtures/external/circular_b.rng +7 -0
  81. data/spec/fixtures/external/circular_main.rng +7 -0
  82. data/spec/fixtures/external/external_ref_lib.rng +7 -0
  83. data/spec/fixtures/external/external_ref_main.rng +7 -0
  84. data/spec/fixtures/external/include_lib.rng +7 -0
  85. data/spec/fixtures/external/include_main.rng +3 -0
  86. data/spec/fixtures/external/nested_chain.rng +6 -0
  87. data/spec/fixtures/external/nested_leaf.rng +7 -0
  88. data/spec/fixtures/external/nested_mid.rng +8 -0
  89. data/spec/fixtures/metanorma/3gpp.rnc +35 -0
  90. data/spec/fixtures/metanorma/3gpp.rng +105 -0
  91. data/spec/fixtures/metanorma/basicdoc.rnc +11 -0
  92. data/spec/fixtures/metanorma/bipm.rnc +148 -0
  93. data/spec/fixtures/metanorma/bipm.rng +376 -0
  94. data/spec/fixtures/metanorma/bsi.rnc +104 -0
  95. data/spec/fixtures/metanorma/bsi.rng +332 -0
  96. data/spec/fixtures/metanorma/csa.rnc +45 -0
  97. data/spec/fixtures/metanorma/csa.rng +131 -0
  98. data/spec/fixtures/metanorma/csd.rnc +43 -0
  99. data/spec/fixtures/metanorma/csd.rng +132 -0
  100. data/spec/fixtures/metanorma/gbstandard.rnc +99 -0
  101. data/spec/fixtures/metanorma/gbstandard.rng +316 -0
  102. data/spec/fixtures/metanorma/iec.rnc +49 -0
  103. data/spec/fixtures/metanorma/iec.rng +193 -0
  104. data/spec/fixtures/metanorma/ietf.rnc +275 -0
  105. data/spec/fixtures/metanorma/ietf.rng +925 -0
  106. data/spec/fixtures/metanorma/iho.rnc +58 -0
  107. data/spec/fixtures/metanorma/iho.rng +179 -0
  108. data/spec/fixtures/metanorma/isodoc.rnc +873 -0
  109. data/spec/fixtures/metanorma/isodoc.rng +2704 -0
  110. data/spec/fixtures/metanorma/isostandard-amd.rnc +43 -0
  111. data/spec/fixtures/metanorma/isostandard-amd.rng +108 -0
  112. data/spec/fixtures/metanorma/isostandard.rnc +166 -0
  113. data/spec/fixtures/metanorma/isostandard.rng +494 -0
  114. data/spec/fixtures/metanorma/itu.rnc +122 -0
  115. data/spec/fixtures/metanorma/itu.rng +377 -0
  116. data/spec/fixtures/metanorma/m3d.rnc +41 -0
  117. data/spec/fixtures/metanorma/m3d.rng +122 -0
  118. data/spec/fixtures/metanorma/mpfd.rnc +36 -0
  119. data/spec/fixtures/metanorma/mpfd.rng +95 -0
  120. data/spec/fixtures/metanorma/nist.rnc +77 -0
  121. data/spec/fixtures/metanorma/nist.rng +216 -0
  122. data/spec/fixtures/metanorma/ogc.rnc +51 -0
  123. data/spec/fixtures/metanorma/ogc.rng +151 -0
  124. data/spec/fixtures/metanorma/reqt.rnc +6 -0
  125. data/spec/fixtures/metanorma/rsd.rnc +36 -0
  126. data/spec/fixtures/metanorma/rsd.rng +95 -0
  127. data/spec/fixtures/metanorma/un.rnc +103 -0
  128. data/spec/fixtures/metanorma/un.rng +367 -0
  129. data/spec/fixtures/rnc/base.rnc +4 -0
  130. data/spec/fixtures/rnc/grammar_with_trailing.rnc +8 -0
  131. data/spec/fixtures/rnc/main_include_trailing.rnc +3 -0
  132. data/spec/fixtures/rnc/main_with_include.rnc +5 -0
  133. data/spec/fixtures/rnc/test_augment.rnc +10 -0
  134. data/spec/fixtures/rnc/test_isodoc_simple.rnc +9 -0
  135. data/spec/fixtures/rnc/top_level_include.rnc +8 -0
  136. data/spec/fixtures/spectest_external/case_10_4.7/x +3 -0
  137. data/spec/fixtures/spectest_external/case_10_4.7/y +7 -0
  138. data/spec/fixtures/spectest_external/case_11_4.7/x +3 -0
  139. data/spec/fixtures/spectest_external/case_12_4.7/x +3 -0
  140. data/spec/fixtures/spectest_external/case_13_4.7/x +3 -0
  141. data/spec/fixtures/spectest_external/case_13_4.7/y +3 -0
  142. data/spec/fixtures/spectest_external/case_14_4.7/x +7 -0
  143. data/spec/fixtures/spectest_external/case_15_4.7/x +7 -0
  144. data/spec/fixtures/spectest_external/case_16_4.7/x +5 -0
  145. data/spec/fixtures/spectest_external/case_17_4.7/x +5 -0
  146. data/spec/fixtures/spectest_external/case_18_4.7/x +7 -0
  147. data/spec/fixtures/spectest_external/case_19_4.7/level1.rng +9 -0
  148. data/spec/fixtures/spectest_external/case_19_4.7/level2.rng +7 -0
  149. data/spec/fixtures/spectest_external/case_1_4.5/sub1/x +3 -0
  150. data/spec/fixtures/spectest_external/case_1_4.5/sub3/x +3 -0
  151. data/spec/fixtures/spectest_external/case_1_4.5/x +3 -0
  152. data/spec/fixtures/spectest_external/case_20_4.6/x +3 -0
  153. data/spec/fixtures/spectest_external/case_2_4.5/x +3 -0
  154. data/spec/fixtures/spectest_external/case_3_4.6/x +3 -0
  155. data/spec/fixtures/spectest_external/case_4_4.6/x +3 -0
  156. data/spec/fixtures/spectest_external/case_5_4.6/x +1 -0
  157. data/spec/fixtures/spectest_external/case_6_4.6/x +5 -0
  158. data/spec/fixtures/spectest_external/case_7_4.6/x +1 -0
  159. data/spec/fixtures/spectest_external/case_7_4.6/y +1 -0
  160. data/spec/fixtures/spectest_external/case_8_4.7/x +7 -0
  161. data/spec/fixtures/spectest_external/case_9_4.7/x +7 -0
  162. data/spec/fixtures/spectest_external/resources.json +149 -0
  163. data/spec/rng/advanced_rnc_spec.rb +101 -0
  164. data/spec/rng/compacttest_spec.rb +197 -0
  165. data/spec/rng/datatype_declaration_spec.rb +28 -0
  166. data/spec/rng/div_spec.rb +207 -0
  167. data/spec/rng/external_ref_resolver_spec.rb +122 -0
  168. data/spec/rng/metanorma_conversion_spec.rb +159 -0
  169. data/spec/rng/namespace_declaration_spec.rb +60 -0
  170. data/spec/rng/namespace_support_spec.rb +199 -0
  171. data/spec/rng/rnc_parser_spec.rb +498 -22
  172. data/spec/rng/rnc_roundtrip_spec.rb +96 -82
  173. data/spec/rng/rng_generation_spec.rb +288 -0
  174. data/spec/rng/roundtrip_spec.rb +342 -0
  175. data/spec/rng/schema_preamble_spec.rb +145 -0
  176. data/spec/rng/schema_spec.rb +68 -64
  177. data/spec/rng/spectest_spec.rb +168 -90
  178. data/spec/rng_spec.rb +2 -2
  179. data/spec/spec_helper.rb +7 -42
  180. 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
- root "data", ordered: true
16
- namespace "http://relaxng.org/ns/structure/1.0"
13
+ element 'data'
14
+ ordered
15
+
16
+ namespace ::Rng::Namespaces::RngNamespace
17
17
 
18
- map_attribute "id", to: :id
19
- map_attribute "ns", to: :ns, value_map: {
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 "datatypeLibrary", to: :datatypeLibrary, value_map: {
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 "type", to: :type
28
- map_element "param", to: :param
29
- map_element "except", to: :except
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