dcc 0.2.0 → 0.2.2

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 (176) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +19 -0
  3. data/.github/workflows/release.yml +28 -0
  4. data/.gitignore +4 -0
  5. data/.rubocop_todo.yml +560 -10
  6. data/CHANGELOG.adoc +34 -1
  7. data/CONTRIBUTING.adoc +8 -1
  8. data/Gemfile +5 -7
  9. data/README.adoc +162 -7
  10. data/dcc.gemspec +9 -2
  11. data/docs/schema-versions.adoc +20 -7
  12. data/examples/build_minimal_dcc.rb +0 -0
  13. data/examples/convert_all_formats.rb +2 -1
  14. data/examples/parse_and_inspect.rb +0 -0
  15. data/examples/quantity_math_demo.rb +7 -7
  16. data/examples/validate_full.rb +1 -1
  17. data/lib/dcc/base/calibration_laboratory.rb +2 -1
  18. data/lib/dcc/base/condition.rb +5 -2
  19. data/lib/dcc/base/core_data.rb +8 -3
  20. data/lib/dcc/base/formula.rb +3 -3
  21. data/lib/dcc/base/identification.rb +6 -3
  22. data/lib/dcc/base/mathml.rb +33 -0
  23. data/lib/dcc/base/quantity.rb +2 -1
  24. data/lib/dcc/base/resp_person.rb +4 -2
  25. data/lib/dcc/base/text.rb +1 -1
  26. data/lib/dcc/base.rb +2 -1
  27. data/lib/dcc/builder/dsl.rb +1 -1
  28. data/lib/dcc/builder/session.rb +16 -14
  29. data/lib/dcc/builder.rb +3 -3
  30. data/lib/dcc/cli/cli.rb +152 -26
  31. data/lib/dcc/cli/formatters.rb +35 -7
  32. data/lib/dcc/cli.rb +1 -1
  33. data/lib/dcc/context_configuration.rb +2 -1
  34. data/lib/dcc/context_options.rb +4 -2
  35. data/lib/dcc/convert/csv.rb +27 -9
  36. data/lib/dcc/convert/html.rb +26 -110
  37. data/lib/dcc/convert/json.rb +10 -7
  38. data/lib/dcc/convert/liquid_filters.rb +58 -0
  39. data/lib/dcc/convert/result.rb +1 -1
  40. data/lib/dcc/convert/templates/report.liquid +250 -0
  41. data/lib/dcc/convert/yaml.rb +1 -1
  42. data/lib/dcc/convert.rb +1 -0
  43. data/lib/dcc/diff/change.rb +1 -1
  44. data/lib/dcc/diff/result.rb +8 -4
  45. data/lib/dcc/diff.rb +43 -11
  46. data/lib/dcc/error.rb +7 -0
  47. data/lib/dcc/extract/file.rb +3 -4
  48. data/lib/dcc/extract/formula/ast.rb +137 -0
  49. data/lib/dcc/extract/formula/evaluator.rb +357 -0
  50. data/lib/dcc/extract/formula/parser.rb +305 -0
  51. data/lib/dcc/extract/formula/quantity.rb +89 -0
  52. data/lib/dcc/extract/formula.rb +110 -0
  53. data/lib/dcc/extract/ring.rb +1 -1
  54. data/lib/dcc/extract.rb +1 -1
  55. data/lib/dcc/i18n/text_lookup.rb +8 -4
  56. data/lib/dcc/i18n.rb +1 -1
  57. data/lib/dcc/inspect/report.rb +51 -14
  58. data/lib/dcc/inspect.rb +1 -1
  59. data/lib/dcc/migrate/route.rb +44 -0
  60. data/lib/dcc/migrate/v2_to_v3.rb +445 -0
  61. data/lib/dcc/migrate.rb +130 -26
  62. data/lib/dcc/plugin/base.rb +64 -0
  63. data/lib/dcc/plugin/registry.rb +7 -2
  64. data/lib/dcc/plugin.rb +6 -2
  65. data/lib/dcc/quantity_format/formatter.rb +16 -10
  66. data/lib/dcc/quantity_format.rb +1 -1
  67. data/lib/dcc/quantity_math/complex.rb +252 -0
  68. data/lib/dcc/quantity_math/matrix.rb +217 -0
  69. data/lib/dcc/quantity_math/quantity.rb +1 -1
  70. data/lib/dcc/quantity_math/real.rb +6 -6
  71. data/lib/dcc/quantity_math.rb +31 -3
  72. data/lib/dcc/schema/version.rb +2 -2
  73. data/lib/dcc/server/app.html +282 -0
  74. data/lib/dcc/server/app.rb +171 -87
  75. data/lib/dcc/server/demo_cert.xml +244 -0
  76. data/lib/dcc/server/storage/entry.rb +1 -1
  77. data/lib/dcc/server/storage/memory.rb +3 -2
  78. data/lib/dcc/server/storage.rb +1 -1
  79. data/lib/dcc/server.rb +1 -1
  80. data/lib/dcc/si/base/complex.rb +1 -1
  81. data/lib/dcc/si/base/complex_list_xml_list.rb +1 -1
  82. data/lib/dcc/si/base/constant.rb +1 -1
  83. data/lib/dcc/si/base/coverage_interval.rb +36 -0
  84. data/lib/dcc/si/base/coverage_interval_mu.rb +1 -1
  85. data/lib/dcc/si/base/expanded_mu.rb +1 -1
  86. data/lib/dcc/si/base/expanded_unc.rb +34 -0
  87. data/lib/dcc/si/base/expanded_unc_xml_list.rb +3 -2
  88. data/lib/dcc/si/base/hybrid.rb +3 -2
  89. data/lib/dcc/si/base/measurement_uncertainty_univariate.rb +1 -1
  90. data/lib/dcc/si/base/real.rb +13 -3
  91. data/lib/dcc/si/base/real_list_xml_list.rb +5 -3
  92. data/lib/dcc/si/base/si_list.rb +3 -2
  93. data/lib/dcc/si/base/standard_mu.rb +1 -1
  94. data/lib/dcc/si/base.rb +5 -2
  95. data/lib/dcc/si/v1/coverage_interval.rb +8 -0
  96. data/lib/dcc/si/v1/expanded_unc.rb +1 -16
  97. data/lib/dcc/si/v1/measurement_uncertainty_univariate.rb +2 -1
  98. data/lib/dcc/si/v1.rb +5 -2
  99. data/lib/dcc/si/v2/coverage_interval.rb +8 -0
  100. data/lib/dcc/si/v2/coverage_interval_mu_xml_list.rb +4 -2
  101. data/lib/dcc/si/v2/expanded_mu_xml_list.rb +2 -1
  102. data/lib/dcc/si/v2/expanded_unc.rb +8 -0
  103. data/lib/dcc/si/v2/measurement_uncertainty_univariate.rb +2 -1
  104. data/lib/dcc/si/v2.rb +8 -3
  105. data/lib/dcc/signature/result.rb +2 -2
  106. data/lib/dcc/signature/signer.rb +31 -18
  107. data/lib/dcc/signature/verifier.rb +23 -21
  108. data/lib/dcc/signature.rb +1 -1
  109. data/lib/dcc/streaming/reader.rb +246 -0
  110. data/lib/dcc/streaming.rb +79 -0
  111. data/lib/dcc/transform/result.rb +1 -1
  112. data/lib/dcc/transform/xslt.rb +22 -12
  113. data/lib/dcc/transform.rb +5 -4
  114. data/lib/dcc/type/base64_binary.rb +5 -3
  115. data/lib/dcc/type/decimal_xml_list.rb +99 -13
  116. data/lib/dcc/type/iso_country_code.rb +3 -2
  117. data/lib/dcc/type/iso_language_code.rb +3 -2
  118. data/lib/dcc/type/schema_version.rb +3 -2
  119. data/lib/dcc/type/si_unit.rb +3 -2
  120. data/lib/dcc/type_guards.rb +3 -3
  121. data/lib/dcc/v2/calibration_laboratory.rb +2 -1
  122. data/lib/dcc/v2/condition.rb +22 -1
  123. data/lib/dcc/v2/configuration.rb +1 -1
  124. data/lib/dcc/v2/digital_calibration_certificate.rb +2 -1
  125. data/lib/dcc/v2/identification.rb +14 -1
  126. data/lib/dcc/v2/mathml.rb +8 -0
  127. data/lib/dcc/v2/measurement_meta_data_list.rb +2 -1
  128. data/lib/dcc/v2/quantity.rb +4 -2
  129. data/lib/dcc/v2.rb +4 -2
  130. data/lib/dcc/v3/calibration_laboratory.rb +2 -1
  131. data/lib/dcc/v3/configuration.rb +1 -1
  132. data/lib/dcc/v3/digital_calibration_certificate.rb +2 -1
  133. data/lib/dcc/v3/mathml.rb +8 -0
  134. data/lib/dcc/v3/measurement_meta_data_list.rb +2 -1
  135. data/lib/dcc/v3/quantity.rb +4 -2
  136. data/lib/dcc/v3.rb +12 -2
  137. data/lib/dcc/validate/business_rules.rb +23 -9
  138. data/lib/dcc/validate/issue.rb +2 -1
  139. data/lib/dcc/validate/result.rb +6 -4
  140. data/lib/dcc/validate/schematron/profile.rb +73 -18
  141. data/lib/dcc/validate/schematron/rule.rb +13 -3
  142. data/lib/dcc/validate/schematron/rules/administrative_data_completeness.rb +11 -4
  143. data/lib/dcc/validate/schematron/rules/base.rb +1 -2
  144. data/lib/dcc/validate/schematron/rules/date_range_check.rb +11 -5
  145. data/lib/dcc/validate/schematron/rules/id_refid_linking.rb +0 -3
  146. data/lib/dcc/validate/schematron/rules/influence_conditions_placement.rb +22 -12
  147. data/lib/dcc/validate/schematron/rules/iso_code_validation.rb +31 -14
  148. data/lib/dcc/validate/schematron/rules/language_code_dedup.rb +12 -5
  149. data/lib/dcc/validate/schematron/rules/non_si_declaration.rb +7 -4
  150. data/lib/dcc/validate/schematron/rules/release_format_check.rb +7 -4
  151. data/lib/dcc/validate/schematron/rules/schema_version_check.rb +13 -9
  152. data/lib/dcc/validate/schematron/rules/uncertainty_consistency.rb +39 -10
  153. data/lib/dcc/validate/schematron/rules/unit_format_check.rb +2 -3
  154. data/lib/dcc/validate/schematron/rules/used_methods_placement.rb +15 -10
  155. data/lib/dcc/validate/schematron/rules/used_software_placement.rb +15 -10
  156. data/lib/dcc/validate/schematron/rules/xml_list_spacing.rb +4 -3
  157. data/lib/dcc/validate/schematron/rules.rb +28 -14
  158. data/lib/dcc/validate/schematron.rb +1 -1
  159. data/lib/dcc/validate/xsd.rb +9 -3
  160. data/lib/dcc/validate.rb +1 -1
  161. data/lib/dcc/version.rb +1 -1
  162. data/lib/dcc/versioned_parser.rb +1 -1
  163. data/lib/dcc.rb +68 -10
  164. data/sample_dcc.xml +140 -0
  165. data/web/index.html +12 -0
  166. data/web/package-lock.json +1599 -0
  167. data/web/package.json +22 -0
  168. data/web/src/App.vue +349 -0
  169. data/web/src/lib/builder.ts +171 -0
  170. data/web/src/lib/dcc.ts +338 -0
  171. data/web/src/lib/units.json +5581 -0
  172. data/web/src/main.ts +5 -0
  173. data/web/src/style.css +27 -0
  174. data/web/tsconfig.json +15 -0
  175. data/web/vite.config.ts +14 -0
  176. metadata +148 -9
@@ -4,5 +4,6 @@ module Dcc::V2
4
4
  class CalibrationLaboratory < CommonElements
5
5
  include ::Dcc::Base::CalibrationLaboratory
6
6
  end
7
- Configuration.register_model(CalibrationLaboratory, id: :calibrationLaboratory)
7
+ Configuration.register_model(CalibrationLaboratory,
8
+ id: :calibrationLaboratory)
8
9
  end
@@ -1,8 +1,29 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Dcc::V2
4
+ # v2 calls the influence condition's state element `state`; v3 renamed it to
5
+ # `status`, which the shared base module carries.
4
6
  class Condition < CommonElements
5
- include ::Dcc::Base::Condition
7
+ attribute :id, :string
8
+ attribute :ref_id, :string
9
+ attribute :ref_type, :string
10
+ attribute :name, :text
11
+ attribute :description, :text
12
+ attribute :status, :string
13
+ attribute :data, :data, collection: true
14
+
15
+ xml do
16
+ namespace ::Dcc::Namespace::Dcc
17
+ element "influenceCondition"
18
+ ordered
19
+ map_attribute "id", to: :id
20
+ map_attribute "refId", to: :ref_id
21
+ map_attribute "refType", to: :ref_type
22
+ map_element "name", to: :name
23
+ map_element "description", to: :description
24
+ map_element "state", to: :status
25
+ map_element "data", to: :data
26
+ end
6
27
  end
7
28
  Configuration.register_model(Condition, id: :influenceCondition)
8
29
  end
@@ -10,7 +10,7 @@ module Dcc
10
10
  # V2 DCC imports D-SI v1.x (registered as :dsi_v1). We also fall back
11
11
  # to :dsi_v2 for forward-compatible parsing.
12
12
  def self.fallback_contexts
13
- [:dsi_v1, :dsi_v2, :default]
13
+ %i[dsi_v1 dsi_v2 default]
14
14
  end
15
15
  end
16
16
  end
@@ -4,5 +4,6 @@ module Dcc::V2
4
4
  class DigitalCalibrationCertificate < CommonElements
5
5
  include ::Dcc::Base::DigitalCalibrationCertificate
6
6
  end
7
- Configuration.register_model(DigitalCalibrationCertificate, id: :digitalCalibrationCertificate)
7
+ Configuration.register_model(DigitalCalibrationCertificate,
8
+ id: :digitalCalibrationCertificate)
8
9
  end
@@ -1,8 +1,21 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Dcc::V2
4
+ # v2 calls the identification's third child `description`; v3 renamed it to
5
+ # `name`, which the shared base module carries.
4
6
  class Identification < CommonElements
5
- include ::Dcc::Base::Identification
7
+ attribute :issuer, :string
8
+ attribute :value, :string
9
+ attribute :description, :text
10
+
11
+ xml do
12
+ namespace ::Dcc::Namespace::Dcc
13
+ element "identification"
14
+ ordered
15
+ map_element "issuer", to: :issuer
16
+ map_element "value", to: :value
17
+ map_element "description", to: :description
18
+ end
6
19
  end
7
20
  Configuration.register_model(Identification, id: :identification)
8
21
  end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dcc::V2
4
+ class Mathml < CommonElements
5
+ include ::Dcc::Base::Mathml
6
+ end
7
+ Configuration.register_model(Mathml, id: :mathml)
8
+ end
@@ -4,5 +4,6 @@ module Dcc::V2
4
4
  class MeasurementMetaDataList < CommonElements
5
5
  include ::Dcc::Base::MeasurementMetaDataList
6
6
  end
7
- Configuration.register_model(MeasurementMetaDataList, id: :measurementMetaData)
7
+ Configuration.register_model(MeasurementMetaDataList,
8
+ id: :measurementMetaData)
8
9
  end
@@ -19,8 +19,10 @@ module Dcc::V2
19
19
  attribute :complex, ::Dcc::Si::V1::Complex, collection: true
20
20
  attribute :constant, ::Dcc::Si::V1::Constant, collection: true
21
21
  attribute :hybrid, ::Dcc::Si::V1::Hybrid, collection: true
22
- attribute :real_list_xml_list, ::Dcc::Si::V1::RealListXmlList, collection: true
23
- attribute :complex_list_xml_list, ::Dcc::Si::V1::ComplexListXmlList, collection: true
22
+ attribute :real_list_xml_list, ::Dcc::Si::V1::RealListXmlList,
23
+ collection: true
24
+ attribute :complex_list_xml_list, ::Dcc::Si::V1::ComplexListXmlList,
25
+ collection: true
24
26
  attribute :list, ::Dcc::Si::V1::SiList, collection: true
25
27
 
26
28
  xml do
data/lib/dcc/v2.rb CHANGED
@@ -14,6 +14,7 @@ module Dcc
14
14
  autoload :Text, "dcc/v2/text"
15
15
  autoload :ByteData, "dcc/v2/byte_data"
16
16
  autoload :Formula, "dcc/v2/formula"
17
+ autoload :Mathml, "dcc/v2/mathml"
17
18
  autoload :RichContent, "dcc/v2/rich_content"
18
19
  autoload :XmlBlob, "dcc/v2/xml_blob"
19
20
  autoload :Comment, "dcc/v2/comment"
@@ -53,14 +54,15 @@ module Dcc
53
54
  autoload :MeasurementResult, "dcc/v2/measurement_result"
54
55
  autoload :MeasurementResultList, "dcc/v2/measurement_result_list"
55
56
  autoload :AdministrativeData, "dcc/v2/administrative_data"
56
- autoload :DigitalCalibrationCertificate, "dcc/v2/digital_calibration_certificate"
57
+ autoload :DigitalCalibrationCertificate,
58
+ "dcc/v2/digital_calibration_certificate"
57
59
 
58
60
  ROOT_ELEMENT_TAG = "digitalCalibrationCertificate"
59
61
 
60
62
  extend ::Dcc::VersionedParser
61
63
 
62
64
  ELEMENT_CLASSES = %i[
63
- StringWithLang Text ByteData Formula RichContent XmlBlob Comment
65
+ StringWithLang Text ByteData Mathml Formula RichContent XmlBlob Comment
64
66
  EquipmentClass Location Contact ContactNotStrict HashType
65
67
  Identification Identifications Software SoftwareList
66
68
  RefTypeDefinition RefTypeDefinitionList
@@ -4,5 +4,6 @@ module Dcc::V3
4
4
  class CalibrationLaboratory < CommonElements
5
5
  include ::Dcc::Base::CalibrationLaboratory
6
6
  end
7
- Configuration.register_model(CalibrationLaboratory, id: :calibrationLaboratory)
7
+ Configuration.register_model(CalibrationLaboratory,
8
+ id: :calibrationLaboratory)
8
9
  end
@@ -10,7 +10,7 @@ module Dcc
10
10
  # V3 DCC imports D-SI v2.x for quantity expressions. Returned as
11
11
  # instance method (becomes class method via `extend`).
12
12
  def fallback_contexts
13
- [:dsi_v2, :default]
13
+ %i[dsi_v2 default]
14
14
  end
15
15
  end
16
16
  end
@@ -4,5 +4,6 @@ module Dcc::V3
4
4
  class DigitalCalibrationCertificate < CommonElements
5
5
  include ::Dcc::Base::DigitalCalibrationCertificate
6
6
  end
7
- Configuration.register_model(DigitalCalibrationCertificate, id: :digitalCalibrationCertificate)
7
+ Configuration.register_model(DigitalCalibrationCertificate,
8
+ id: :digitalCalibrationCertificate)
8
9
  end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dcc::V3
4
+ class Mathml < CommonElements
5
+ include ::Dcc::Base::Mathml
6
+ end
7
+ Configuration.register_model(Mathml, id: :mathml)
8
+ end
@@ -4,5 +4,6 @@ module Dcc::V3
4
4
  class MeasurementMetaDataList < CommonElements
5
5
  include ::Dcc::Base::MeasurementMetaDataList
6
6
  end
7
- Configuration.register_model(MeasurementMetaDataList, id: :measurementMetaData)
7
+ Configuration.register_model(MeasurementMetaDataList,
8
+ id: :measurementMetaData)
8
9
  end
@@ -19,8 +19,10 @@ module Dcc::V3
19
19
  attribute :complex, ::Dcc::Si::V2::Complex, collection: true
20
20
  attribute :constant, ::Dcc::Si::V2::Constant, collection: true
21
21
  attribute :hybrid, ::Dcc::Si::V2::Hybrid, collection: true
22
- attribute :real_list_xml_list, ::Dcc::Si::V2::RealListXmlList, collection: true
23
- attribute :complex_list_xml_list, ::Dcc::Si::V2::ComplexListXmlList, collection: true
22
+ attribute :real_list_xml_list, ::Dcc::Si::V2::RealListXmlList,
23
+ collection: true
24
+ attribute :complex_list_xml_list, ::Dcc::Si::V2::ComplexListXmlList,
25
+ collection: true
24
26
  attribute :list, ::Dcc::Si::V2::SiList, collection: true
25
27
 
26
28
  xml do
data/lib/dcc/v3.rb CHANGED
@@ -14,6 +14,7 @@ module Dcc
14
14
  autoload :Text, "dcc/v3/text"
15
15
  autoload :ByteData, "dcc/v3/byte_data"
16
16
  autoload :Formula, "dcc/v3/formula"
17
+ autoload :Mathml, "dcc/v3/mathml"
17
18
  autoload :RichContent, "dcc/v3/rich_content"
18
19
  autoload :XmlBlob, "dcc/v3/xml_blob"
19
20
  autoload :Comment, "dcc/v3/comment"
@@ -53,7 +54,8 @@ module Dcc
53
54
  autoload :MeasurementResult, "dcc/v3/measurement_result"
54
55
  autoload :MeasurementResultList, "dcc/v3/measurement_result_list"
55
56
  autoload :AdministrativeData, "dcc/v3/administrative_data"
56
- autoload :DigitalCalibrationCertificate, "dcc/v3/digital_calibration_certificate"
57
+ autoload :DigitalCalibrationCertificate,
58
+ "dcc/v3/digital_calibration_certificate"
57
59
 
58
60
  ROOT_ELEMENT_TAG = "digitalCalibrationCertificate"
59
61
 
@@ -63,7 +65,7 @@ module Dcc
63
65
  # `Configuration.populate_context!` can register them. Iterating
64
66
  # `constants` triggers each autoload lazily.
65
67
  ELEMENT_CLASSES = %i[
66
- StringWithLang Text ByteData Formula RichContent XmlBlob Comment
68
+ StringWithLang Text ByteData Mathml Formula RichContent XmlBlob Comment
67
69
  EquipmentClass Location Contact ContactNotStrict HashType
68
70
  Identification Identifications Software SoftwareList
69
71
  RefTypeDefinition RefTypeDefinitionList
@@ -90,9 +92,17 @@ module Dcc
90
92
 
91
93
  # Re-export D-SI v2 type registrations into the DCC v3 context. Uses
92
94
  # only the public `register_model` API (no instance_variable access).
95
+ #
96
+ # Ids DCC already owns are skipped: `register_model` overwrites on
97
+ # duplicate, and both DCC and D-SI define a `list` element, so without this
98
+ # the D-SI one would displace `Dcc::V3::List` and every `dcc:list` would
99
+ # parse as an empty `si:list`.
93
100
  def self.register_dsi_types!
94
101
  ::Dcc::Si::V2.load_all!
102
+ own_ids = Configuration.registered_model_ids
95
103
  ::Dcc::Si::V2::Configuration.registered_model_ids.each do |id|
104
+ next if own_ids.include?(id)
105
+
96
106
  klass = ::Dcc::Si::V2::Configuration.registered_model_class(id)
97
107
  Configuration.register_model(klass, id: id)
98
108
  end
@@ -43,7 +43,9 @@ module Dcc
43
43
  end
44
44
 
45
45
  def code
46
- "dcc.business_rule.#{self.class.name.split('::').last.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2').gsub(/([a-z\d])([A-Z])/, '\1_\2').downcase}"
46
+ "dcc.business_rule.#{self.class.name.split('::').last.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2').gsub(
47
+ /([a-z\d])([A-Z])/, '\1_\2'
48
+ ).downcase}"
47
49
  end
48
50
 
49
51
  # @param _dcc [Lutaml::Model::Serializable]
@@ -68,10 +70,12 @@ module Dcc
68
70
  # Require exactly one respPerson with mainSigner = true.
69
71
  class MainSignerSingle < Rule
70
72
  def check_on(dcc)
71
- return [] unless Dcc::TypeGuards.has_attribute?(dcc, :administrative_data)
73
+ return [] unless Dcc::TypeGuards.has_attribute?(dcc,
74
+ :administrative_data)
72
75
 
73
76
  admin = dcc.administrative_data
74
- return [] unless admin && Dcc::TypeGuards.has_attribute?(admin, :resp_persons) && admin.resp_persons
77
+ return [] unless admin && Dcc::TypeGuards.has_attribute?(admin,
78
+ :resp_persons) && admin.resp_persons
75
79
 
76
80
  resp_persons = Array(admin.resp_persons.resp_person)
77
81
  main_signers = resp_persons.select { |p| Dcc::TypeGuards.has_attribute?(p, :main_signer) && p.main_signer }
@@ -89,15 +93,20 @@ module Dcc
89
93
  # Require non-empty uniqueIdentifier.
90
94
  class UniqueIdentifierPresent < Rule
91
95
  def check_on(dcc)
92
- return [] unless Dcc::TypeGuards.has_attribute?(dcc, :administrative_data)
96
+ return [] unless Dcc::TypeGuards.has_attribute?(dcc,
97
+ :administrative_data)
93
98
 
94
99
  core = dcc.administrative_data&.core_data
95
- return [issue(severity: :error, message: "coreData is missing")] unless core
100
+ unless core
101
+ return [issue(severity: :error,
102
+ message: "coreData is missing")]
103
+ end
96
104
 
97
105
  id = core.unique_identifier
98
106
  return [] if id && !id.to_s.empty?
99
107
 
100
- [issue(severity: :error, message: "uniqueIdentifier is missing or empty")]
108
+ [issue(severity: :error,
109
+ message: "uniqueIdentifier is missing or empty")]
101
110
  end
102
111
  end
103
112
 
@@ -108,14 +117,19 @@ module Dcc
108
117
  # @param dcc [Lutaml::Model::Serializable]
109
118
  # @return [Dcc::Validate::BusinessRules::Result]
110
119
  def call(dcc)
111
- issues = Registry.all.flat_map { |rule_class| rule_class.new.check_on(dcc) }
120
+ issues = Registry.all.flat_map do |rule_class|
121
+ rule_class.new.check_on(dcc)
122
+ end
112
123
  Result.new(
113
124
  issues: issues,
114
- schema_version: Dcc::TypeGuards.has_attribute?(dcc, :schema_version) ? dcc.schema_version.to_s : nil,
125
+ schema_version: if Dcc::TypeGuards.has_attribute?(dcc,
126
+ :schema_version)
127
+ dcc.schema_version.to_s
128
+ end,
115
129
  source: "business_rule",
116
130
  )
117
131
  end
118
132
  end
119
133
  end
120
134
  end
121
- end
135
+ end
@@ -24,7 +24,8 @@ module Dcc
24
24
  # @param column [Integer, nil] XML column number if known.
25
25
  # @param path [String, nil] XPath / element path if known.
26
26
  # @param source [String, nil] validator name (e.g. "xsd", "schematron").
27
- def self.build(severity:, message:, code: nil, line: nil, column: nil, path: nil, source: nil)
27
+ def self.build(severity:, message:, code: nil, line: nil, column: nil,
28
+ path: nil, source: nil)
28
29
  new(
29
30
  severity: ::Dcc::Validate::Severity.normalize(severity),
30
31
  message: message,
@@ -58,12 +58,12 @@ module Dcc
58
58
 
59
59
  def to_s
60
60
  if ok?
61
- "OK: no errors found#{schema_version ? " (schemaVersion #{schema_version})" : ''}"
61
+ "OK: no errors found#{" (schemaVersion #{schema_version})" if schema_version}"
62
62
  else
63
63
  header = "#{errors.size} error(s), #{warnings.size} warning(s)"
64
64
  header += " (schemaVersion #{schema_version})" if schema_version
65
- body = errors.map(&:to_s).unshift(header).join("\n")
66
- body
65
+ errors.map(&:to_s).unshift(header).join("\n")
66
+
67
67
  end
68
68
  end
69
69
 
@@ -97,7 +97,9 @@ module Dcc
97
97
  ok: ok?,
98
98
  source: source,
99
99
  schema_version: schema_version,
100
- issues: issues.map { |i| [i.severity.to_s, i.message, i.line, i.path] },
100
+ issues: issues.map do |i|
101
+ [i.severity.to_s, i.message, i.line, i.path]
102
+ end,
101
103
  }.to_yaml
102
104
  end
103
105
  end
@@ -15,37 +15,92 @@ module Dcc
15
15
  # result.ok? # => true
16
16
  # result.issues.size # => 0
17
17
  class Profile
18
+ # The rules shipped with the gem, in execution order.
19
+ DEFAULT_RULES = [
20
+ Rules::UsedMethodsPlacement,
21
+ Rules::UsedSoftwarePlacement,
22
+ Rules::InfluenceConditionsPlacement,
23
+ Rules::SchemaVersionCheck,
24
+ Rules::IdRefIdLinking,
25
+ Rules::IsoCodeValidation,
26
+ Rules::DateRangeCheck,
27
+ Rules::ReleaseFormatCheck,
28
+ Rules::UncertaintyConsistency,
29
+ Rules::UnitFormatCheck,
30
+ Rules::NonSiDeclaration,
31
+ Rules::LanguageCodeDedup,
32
+ Rules::XmlListSpacing,
33
+ ].freeze
34
+ private_constant :DEFAULT_RULES
35
+
18
36
  attr_reader :dcc, :rules
19
37
 
38
+ # Plugin validators are read here, not at class-definition time, so
39
+ # a plugin loaded after this class still takes effect. Frozen because
40
+ # `attr_reader :rules` hands the array straight to callers.
20
41
  def initialize(dcc)
21
42
  @dcc = dcc
22
- @rules = [
23
- Rules::UsedMethodsPlacement,
24
- Rules::UsedSoftwarePlacement,
25
- Rules::InfluenceConditionsPlacement,
26
- Rules::SchemaVersionCheck,
27
- Rules::IdRefIdLinking,
28
- Rules::IsoCodeValidation,
29
- Rules::DateRangeCheck,
30
- Rules::ReleaseFormatCheck,
31
- Rules::UncertaintyConsistency,
32
- Rules::UnitFormatCheck,
33
- Rules::NonSiDeclaration,
34
- Rules::LanguageCodeDedup,
35
- Rules::XmlListSpacing,
36
- ]
43
+ @plugin_rules = ::Dcc::Plugin.all(:validators).freeze
44
+ @rules = (DEFAULT_RULES + @plugin_rules).freeze
37
45
  end
38
46
 
39
47
  # Run all rules and return a `Dcc::Validate::Result`.
48
+ #
49
+ # Built-in rules fail fast: a crash there is a bug in this gem. A
50
+ # plugin rule is third-party code, so its failure is contained and
51
+ # reported as an error issue naming the rule class — one broken
52
+ # plugin must not cost the caller every built-in finding.
40
53
  def call
41
54
  issues = []
42
55
  rules.each do |rule_class|
43
- issues.concat(rule_class.new.check_on(dcc))
56
+ issues.concat(
57
+ if @plugin_rules.include?(rule_class)
58
+ run_plugin_rule(rule_class)
59
+ else
60
+ rule_class.new.check_on(dcc)
61
+ end,
62
+ )
44
63
  end
45
64
 
46
65
  ::Dcc::Validate::Result.new(
47
66
  issues: issues,
48
- schema_version: Dcc::TypeGuards.has_attribute?(dcc, :schema_version) ? dcc.schema_version.to_s : nil,
67
+ schema_version: if Dcc::TypeGuards.has_attribute?(dcc,
68
+ :schema_version)
69
+ dcc.schema_version.to_s
70
+ end,
71
+ source: "schematron",
72
+ )
73
+ end
74
+
75
+ private
76
+
77
+ # Run one plugin-supplied rule with fault isolation. Instantiation
78
+ # and #check_on both run inside the rescue, and a non-Array return
79
+ # (nil included) is reported rather than passed to Array#concat.
80
+ #
81
+ # @param rule_class [Class]
82
+ # @return [Array<Dcc::Validate::Issue>]
83
+ def run_plugin_rule(rule_class)
84
+ result = rule_class.new.check_on(dcc)
85
+ return result if result.is_a?(::Array)
86
+
87
+ [plugin_failure_issue(
88
+ rule_class,
89
+ "returned #{result.inspect} instead of an Array of issues",
90
+ )]
91
+ rescue ::StandardError => e
92
+ [plugin_failure_issue(rule_class, "raised #{e.class}: #{e.message}")]
93
+ end
94
+
95
+ # @param rule_class [Class]
96
+ # @param detail [String]
97
+ # @return [Dcc::Validate::Issue]
98
+ def plugin_failure_issue(rule_class, detail)
99
+ name = rule_class.name || rule_class.inspect
100
+ ::Dcc::Validate::Issue.build(
101
+ severity: ::Dcc::Validate::Severity::ERROR,
102
+ message: "plugin validator #{name} #{detail}",
103
+ code: "dcc.schematron.plugin_failure",
49
104
  source: "schematron",
50
105
  )
51
106
  end
@@ -61,4 +116,4 @@ module Dcc
61
116
  end
62
117
  end
63
118
  end
64
- end
119
+ end
@@ -13,9 +13,18 @@ module Dcc
13
13
  ::Dcc::Validate::Severity::ERROR
14
14
  end
15
15
 
16
+ # A plugin can register an anonymous rule class, whose `name` is nil.
17
+ # Falling back keeps one such rule from taking the whole run down and
18
+ # losing every issue the built-in rules already found.
19
+ #
16
20
  # @return [String] rule code used in `Issue#code`.
17
21
  def code
18
- "dcc.schematron.#{self.class.name.split('::').last.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2').gsub(/([a-z\d])([A-Z])/, '\1_\2').downcase}"
22
+ class_name = self.class.name
23
+ return "dcc.schematron.anonymous" unless class_name
24
+
25
+ "dcc.schematron.#{class_name.split('::').last.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2').gsub(
26
+ /([a-z\d])([A-Z])/, '\1_\2'
27
+ ).downcase}"
19
28
  end
20
29
 
21
30
  # @param dcc [Lutaml::Model::Serializable]
@@ -32,7 +41,8 @@ module Dcc
32
41
  # Override in subclasses to traverse the model tree.
33
42
  end
34
43
 
35
- def issue(severity:, message:, line: nil, column: nil, path: nil, source: "schematron")
44
+ def issue(severity:, message:, line: nil, column: nil, path: nil,
45
+ source: "schematron")
36
46
  ::Dcc::Validate::Issue.build(
37
47
  severity: severity,
38
48
  message: message,
@@ -46,4 +56,4 @@ module Dcc
46
56
  end
47
57
  end
48
58
  end
49
- end
59
+ end
@@ -19,13 +19,20 @@ module Dcc
19
19
 
20
20
  def check_on(dcc)
21
21
  issues = []
22
- return issues unless Dcc::TypeGuards.has_attribute?(dcc, :administrative_data)
22
+ return issues unless Dcc::TypeGuards.has_attribute?(dcc,
23
+ :administrative_data)
23
24
 
24
25
  admin = dcc.administrative_data
25
- return [issue(severity: :error, message: "administrativeData is missing")] unless admin
26
+ unless admin
27
+ return [issue(severity: :error,
28
+ message: "administrativeData is missing")]
29
+ end
26
30
 
27
31
  REQUIRED_SECTIONS.each do |attr, label|
28
- value = Dcc::TypeGuards.has_attribute?(admin, attr) ? admin.public_send(attr) : nil
32
+ value = if Dcc::TypeGuards.has_attribute?(admin,
33
+ attr)
34
+ admin.public_send(attr)
35
+ end
29
36
  next unless value.nil? || ((value.is_a?(::String) || value.is_a?(::Array)) && value.empty?)
30
37
 
31
38
  issues << issue(
@@ -39,4 +46,4 @@ module Dcc
39
46
  end
40
47
  end
41
48
  end
42
- end
49
+ end
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
-
4
3
  module Dcc
5
4
  module Validate
6
5
  module Schematron
@@ -12,4 +11,4 @@ module Dcc
12
11
  end
13
12
  end
14
13
  end
15
- end
14
+ end
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
-
4
3
  module Dcc
5
4
  module Validate
6
5
  module Schematron
@@ -8,13 +7,20 @@ module Dcc
8
7
  # Errors if `endPerformanceDate` precedes `beginPerformanceDate`.
9
8
  class DateRangeCheck < Base
10
9
  def check_on(dcc)
11
- return [] unless Dcc::TypeGuards.has_attribute?(dcc, :administrative_data)
10
+ return [] unless Dcc::TypeGuards.has_attribute?(dcc,
11
+ :administrative_data)
12
12
 
13
13
  core = safe_attr(dcc.administrative_data, :core_data)
14
14
  return [] if core.nil?
15
15
 
16
- begin_d = Dcc::TypeGuards.has_attribute?(core, :begin_performance_date) ? core.begin_performance_date : nil
17
- end_d = Dcc::TypeGuards.has_attribute?(core, :end_performance_date) ? core.end_performance_date : nil
16
+ begin_d = if Dcc::TypeGuards.has_attribute?(core,
17
+ :begin_performance_date)
18
+ core.begin_performance_date
19
+ end
20
+ end_d = if Dcc::TypeGuards.has_attribute?(core,
21
+ :end_performance_date)
22
+ core.end_performance_date
23
+ end
18
24
 
19
25
  return [] if begin_d.nil? || end_d.nil?
20
26
  return [] unless end_d < begin_d
@@ -38,4 +44,4 @@ module Dcc
38
44
  end
39
45
  end
40
46
  end
41
- end
47
+ end
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
-
4
3
  module Dcc
5
4
  module Validate
6
5
  module Schematron
@@ -57,5 +56,3 @@ module Dcc
57
56
  end
58
57
  end
59
58
  end
60
-
61
- require "set"