dcc 0.2.0 → 0.2.1
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/ci.yml +19 -0
- data/.github/workflows/release.yml +28 -0
- data/.gitignore +4 -0
- data/.rubocop_todo.yml +560 -10
- data/CHANGELOG.adoc +34 -1
- data/CONTRIBUTING.adoc +8 -1
- data/Gemfile +5 -7
- data/README.adoc +162 -7
- data/dcc.gemspec +8 -2
- data/docs/schema-versions.adoc +20 -7
- data/examples/build_minimal_dcc.rb +0 -0
- data/examples/convert_all_formats.rb +2 -1
- data/examples/parse_and_inspect.rb +0 -0
- data/examples/quantity_math_demo.rb +7 -7
- data/examples/validate_full.rb +1 -1
- data/lib/dcc/base/calibration_laboratory.rb +2 -1
- data/lib/dcc/base/condition.rb +5 -2
- data/lib/dcc/base/core_data.rb +8 -3
- data/lib/dcc/base/formula.rb +3 -3
- data/lib/dcc/base/identification.rb +6 -3
- data/lib/dcc/base/mathml.rb +33 -0
- data/lib/dcc/base/quantity.rb +2 -1
- data/lib/dcc/base/resp_person.rb +4 -2
- data/lib/dcc/base/text.rb +1 -1
- data/lib/dcc/base.rb +2 -1
- data/lib/dcc/builder/dsl.rb +1 -1
- data/lib/dcc/builder/session.rb +16 -14
- data/lib/dcc/builder.rb +3 -3
- data/lib/dcc/cli/cli.rb +36 -18
- data/lib/dcc/cli/formatters.rb +35 -7
- data/lib/dcc/cli.rb +1 -1
- data/lib/dcc/context_configuration.rb +2 -1
- data/lib/dcc/context_options.rb +4 -2
- data/lib/dcc/convert/csv.rb +27 -9
- data/lib/dcc/convert/html.rb +26 -110
- data/lib/dcc/convert/json.rb +10 -7
- data/lib/dcc/convert/liquid_filters.rb +58 -0
- data/lib/dcc/convert/result.rb +1 -1
- data/lib/dcc/convert/templates/report.liquid +250 -0
- data/lib/dcc/convert/yaml.rb +1 -1
- data/lib/dcc/convert.rb +1 -0
- data/lib/dcc/diff/change.rb +1 -1
- data/lib/dcc/diff/result.rb +8 -4
- data/lib/dcc/diff.rb +43 -11
- data/lib/dcc/error.rb +7 -0
- data/lib/dcc/extract/file.rb +3 -4
- data/lib/dcc/extract/ring.rb +1 -1
- data/lib/dcc/extract.rb +1 -1
- data/lib/dcc/i18n/text_lookup.rb +8 -4
- data/lib/dcc/i18n.rb +1 -1
- data/lib/dcc/inspect/report.rb +51 -14
- data/lib/dcc/inspect.rb +1 -1
- data/lib/dcc/migrate/route.rb +44 -0
- data/lib/dcc/migrate/v2_to_v3.rb +445 -0
- data/lib/dcc/migrate.rb +130 -26
- data/lib/dcc/plugin/base.rb +64 -0
- data/lib/dcc/plugin/registry.rb +7 -2
- data/lib/dcc/plugin.rb +6 -2
- data/lib/dcc/quantity_format/formatter.rb +16 -10
- data/lib/dcc/quantity_format.rb +1 -1
- data/lib/dcc/quantity_math/quantity.rb +1 -1
- data/lib/dcc/quantity_math/real.rb +6 -6
- data/lib/dcc/quantity_math.rb +1 -1
- data/lib/dcc/schema/version.rb +2 -2
- data/lib/dcc/server/app.html +282 -0
- data/lib/dcc/server/app.rb +171 -87
- data/lib/dcc/server/demo_cert.xml +244 -0
- data/lib/dcc/server/storage/entry.rb +1 -1
- data/lib/dcc/server/storage/memory.rb +3 -2
- data/lib/dcc/server/storage.rb +1 -1
- data/lib/dcc/server.rb +1 -1
- data/lib/dcc/si/base/complex.rb +1 -1
- data/lib/dcc/si/base/complex_list_xml_list.rb +1 -1
- data/lib/dcc/si/base/constant.rb +1 -1
- data/lib/dcc/si/base/coverage_interval.rb +36 -0
- data/lib/dcc/si/base/coverage_interval_mu.rb +1 -1
- data/lib/dcc/si/base/expanded_mu.rb +1 -1
- data/lib/dcc/si/base/expanded_unc.rb +34 -0
- data/lib/dcc/si/base/expanded_unc_xml_list.rb +3 -2
- data/lib/dcc/si/base/hybrid.rb +3 -2
- data/lib/dcc/si/base/measurement_uncertainty_univariate.rb +1 -1
- data/lib/dcc/si/base/real.rb +13 -3
- data/lib/dcc/si/base/real_list_xml_list.rb +5 -3
- data/lib/dcc/si/base/si_list.rb +3 -2
- data/lib/dcc/si/base/standard_mu.rb +1 -1
- data/lib/dcc/si/base.rb +5 -2
- data/lib/dcc/si/v1/coverage_interval.rb +8 -0
- data/lib/dcc/si/v1/expanded_unc.rb +1 -16
- data/lib/dcc/si/v1/measurement_uncertainty_univariate.rb +2 -1
- data/lib/dcc/si/v1.rb +5 -2
- data/lib/dcc/si/v2/coverage_interval.rb +8 -0
- data/lib/dcc/si/v2/coverage_interval_mu_xml_list.rb +4 -2
- data/lib/dcc/si/v2/expanded_mu_xml_list.rb +2 -1
- data/lib/dcc/si/v2/expanded_unc.rb +8 -0
- data/lib/dcc/si/v2/measurement_uncertainty_univariate.rb +2 -1
- data/lib/dcc/si/v2.rb +8 -3
- data/lib/dcc/signature/result.rb +2 -2
- data/lib/dcc/signature/signer.rb +31 -18
- data/lib/dcc/signature/verifier.rb +23 -21
- data/lib/dcc/signature.rb +1 -1
- data/lib/dcc/transform/result.rb +1 -1
- data/lib/dcc/transform/xslt.rb +22 -12
- data/lib/dcc/transform.rb +5 -4
- data/lib/dcc/type/base64_binary.rb +5 -3
- data/lib/dcc/type/decimal_xml_list.rb +99 -13
- data/lib/dcc/type/iso_country_code.rb +3 -2
- data/lib/dcc/type/iso_language_code.rb +3 -2
- data/lib/dcc/type/schema_version.rb +3 -2
- data/lib/dcc/type/si_unit.rb +3 -2
- data/lib/dcc/type_guards.rb +3 -3
- data/lib/dcc/v2/calibration_laboratory.rb +2 -1
- data/lib/dcc/v2/condition.rb +22 -1
- data/lib/dcc/v2/configuration.rb +1 -1
- data/lib/dcc/v2/digital_calibration_certificate.rb +2 -1
- data/lib/dcc/v2/identification.rb +14 -1
- data/lib/dcc/v2/mathml.rb +8 -0
- data/lib/dcc/v2/measurement_meta_data_list.rb +2 -1
- data/lib/dcc/v2/quantity.rb +4 -2
- data/lib/dcc/v2.rb +4 -2
- data/lib/dcc/v3/calibration_laboratory.rb +2 -1
- data/lib/dcc/v3/configuration.rb +1 -1
- data/lib/dcc/v3/digital_calibration_certificate.rb +2 -1
- data/lib/dcc/v3/mathml.rb +8 -0
- data/lib/dcc/v3/measurement_meta_data_list.rb +2 -1
- data/lib/dcc/v3/quantity.rb +4 -2
- data/lib/dcc/v3.rb +12 -2
- data/lib/dcc/validate/business_rules.rb +23 -9
- data/lib/dcc/validate/issue.rb +2 -1
- data/lib/dcc/validate/result.rb +6 -4
- data/lib/dcc/validate/schematron/profile.rb +73 -18
- data/lib/dcc/validate/schematron/rule.rb +13 -3
- data/lib/dcc/validate/schematron/rules/administrative_data_completeness.rb +11 -4
- data/lib/dcc/validate/schematron/rules/base.rb +1 -2
- data/lib/dcc/validate/schematron/rules/date_range_check.rb +11 -5
- data/lib/dcc/validate/schematron/rules/id_refid_linking.rb +0 -3
- data/lib/dcc/validate/schematron/rules/influence_conditions_placement.rb +22 -12
- data/lib/dcc/validate/schematron/rules/iso_code_validation.rb +31 -14
- data/lib/dcc/validate/schematron/rules/language_code_dedup.rb +12 -5
- data/lib/dcc/validate/schematron/rules/non_si_declaration.rb +7 -4
- data/lib/dcc/validate/schematron/rules/release_format_check.rb +7 -4
- data/lib/dcc/validate/schematron/rules/schema_version_check.rb +13 -9
- data/lib/dcc/validate/schematron/rules/uncertainty_consistency.rb +39 -10
- data/lib/dcc/validate/schematron/rules/unit_format_check.rb +2 -3
- data/lib/dcc/validate/schematron/rules/used_methods_placement.rb +15 -10
- data/lib/dcc/validate/schematron/rules/used_software_placement.rb +15 -10
- data/lib/dcc/validate/schematron/rules/xml_list_spacing.rb +4 -3
- data/lib/dcc/validate/schematron/rules.rb +28 -14
- data/lib/dcc/validate/schematron.rb +1 -1
- data/lib/dcc/validate/xsd.rb +9 -3
- data/lib/dcc/validate.rb +1 -1
- data/lib/dcc/version.rb +1 -1
- data/lib/dcc/versioned_parser.rb +1 -1
- data/lib/dcc.rb +55 -7
- data/sample_dcc.xml +140 -0
- data/web/index.html +12 -0
- data/web/package-lock.json +1599 -0
- data/web/package.json +22 -0
- data/web/src/App.vue +349 -0
- data/web/src/lib/builder.ts +171 -0
- data/web/src/lib/dcc.ts +338 -0
- data/web/src/lib/units.json +5581 -0
- data/web/src/main.ts +5 -0
- data/web/src/style.css +27 -0
- data/web/tsconfig.json +15 -0
- data/web/vite.config.ts +14 -0
- metadata +125 -9
data/lib/dcc/builder/session.rb
CHANGED
|
@@ -25,32 +25,32 @@ module Dcc
|
|
|
25
25
|
|
|
26
26
|
# Top-level DSL methods. Each opens a nested container that captures
|
|
27
27
|
# subsequent setter calls via instance_eval.
|
|
28
|
-
def administrative_data(**attrs, &
|
|
28
|
+
def administrative_data(**attrs, &)
|
|
29
29
|
apply_attrs(@administrative_in_progress, attrs)
|
|
30
|
-
instance_eval(&
|
|
30
|
+
instance_eval(&) if block_given?
|
|
31
31
|
@administrative_in_progress
|
|
32
32
|
end
|
|
33
33
|
|
|
34
|
-
def core_data(**attrs, &
|
|
34
|
+
def core_data(**attrs, &)
|
|
35
35
|
cd = new_core_data
|
|
36
36
|
apply_attrs(cd, attrs)
|
|
37
37
|
@administrative_in_progress.core_data = cd
|
|
38
|
-
instance_eval(&
|
|
38
|
+
instance_eval(&) if block_given?
|
|
39
39
|
cd
|
|
40
40
|
end
|
|
41
41
|
|
|
42
|
-
def items(**attrs, &
|
|
42
|
+
def items(**attrs, &)
|
|
43
43
|
items_obj = new_items
|
|
44
44
|
apply_attrs(items_obj, attrs)
|
|
45
45
|
@administrative_in_progress.items = items_obj
|
|
46
|
-
instance_eval(&
|
|
46
|
+
instance_eval(&) if block_given?
|
|
47
47
|
items_obj
|
|
48
48
|
end
|
|
49
49
|
|
|
50
|
-
def item(**attrs, &
|
|
50
|
+
def item(**attrs, &)
|
|
51
51
|
it = new_item
|
|
52
52
|
apply_attrs(it, attrs)
|
|
53
|
-
instance_eval(&
|
|
53
|
+
instance_eval(&) if block_given?
|
|
54
54
|
(@items_in_progress ||= []) << it
|
|
55
55
|
# Wire into the open items container if present.
|
|
56
56
|
if @administrative_in_progress&.items
|
|
@@ -60,14 +60,14 @@ module Dcc
|
|
|
60
60
|
it
|
|
61
61
|
end
|
|
62
62
|
|
|
63
|
-
def measurement_results(**_attrs, &
|
|
64
|
-
instance_eval(&
|
|
63
|
+
def measurement_results(**_attrs, &)
|
|
64
|
+
instance_eval(&) if block_given?
|
|
65
65
|
end
|
|
66
66
|
|
|
67
|
-
def measurement_result(**attrs, &
|
|
67
|
+
def measurement_result(**attrs, &)
|
|
68
68
|
mr = new_measurement_result
|
|
69
69
|
apply_attrs(mr, attrs)
|
|
70
|
-
instance_eval(&
|
|
70
|
+
instance_eval(&) if block_given?
|
|
71
71
|
@results_in_progress << mr
|
|
72
72
|
mr
|
|
73
73
|
end
|
|
@@ -130,7 +130,9 @@ module Dcc
|
|
|
130
130
|
def apply_attrs(target, attrs)
|
|
131
131
|
attrs.each do |k, v|
|
|
132
132
|
setter = :"#{k}="
|
|
133
|
-
target.public_send(setter, v) if Dcc::TypeGuards.has_attribute?(
|
|
133
|
+
target.public_send(setter, v) if Dcc::TypeGuards.has_attribute?(
|
|
134
|
+
target, setter
|
|
135
|
+
)
|
|
134
136
|
end
|
|
135
137
|
end
|
|
136
138
|
|
|
@@ -167,4 +169,4 @@ module Dcc
|
|
|
167
169
|
end
|
|
168
170
|
end
|
|
169
171
|
end
|
|
170
|
-
end
|
|
172
|
+
end
|
data/lib/dcc/builder.rb
CHANGED
|
@@ -23,12 +23,12 @@ module Dcc
|
|
|
23
23
|
# @param version [Integer, nil] major DCC version (default: 3).
|
|
24
24
|
# @yield [Dcc::Builder::Session] builder session.
|
|
25
25
|
# @return [Dcc::V2::DigitalCalibrationCertificate, Dcc::V3::DigitalCalibrationCertificate]
|
|
26
|
-
def call(version: 3, &
|
|
26
|
+
def call(version: 3, &)
|
|
27
27
|
::Dcc.load_all!
|
|
28
28
|
session = Session.new(version: version)
|
|
29
|
-
session.instance_eval(&
|
|
29
|
+
session.instance_eval(&) if block_given?
|
|
30
30
|
session.build
|
|
31
31
|
end
|
|
32
32
|
end
|
|
33
33
|
end
|
|
34
|
-
end
|
|
34
|
+
end
|
data/lib/dcc/cli/cli.rb
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "thor"
|
|
4
|
-
require "tty-table"
|
|
5
4
|
|
|
6
5
|
# `Dcc::Cli::Cli` is the Thor-based command-line interface. Each subcommand
|
|
7
6
|
# (validate, convert, extract, signature, transform, inspect, diff, issue)
|
|
@@ -27,7 +26,8 @@ module Dcc
|
|
|
27
26
|
true
|
|
28
27
|
end
|
|
29
28
|
|
|
30
|
-
desc "validate SCHEMA FILE",
|
|
29
|
+
desc "validate SCHEMA FILE",
|
|
30
|
+
"Validate a DCC XML against XSD, Schematron, or both"
|
|
31
31
|
long_desc <<~DESC
|
|
32
32
|
Validates the given DCC XML file.
|
|
33
33
|
|
|
@@ -37,20 +37,21 @@ module Dcc
|
|
|
37
37
|
$ dcc validate all cert.xml
|
|
38
38
|
DESC
|
|
39
39
|
method_option :version, type: :string, default: "auto",
|
|
40
|
-
|
|
40
|
+
desc: "Schema version (e.g. '3.3.0', 'auto')"
|
|
41
41
|
method_option :format, type: :string, default: "text",
|
|
42
|
-
|
|
42
|
+
enum: %w[text json yaml]
|
|
43
43
|
def validate(schema, file)
|
|
44
44
|
Cli.ensure_loaded!
|
|
45
45
|
xml = File.read(file)
|
|
46
46
|
version = options[:version]
|
|
47
47
|
result =
|
|
48
48
|
case schema
|
|
49
|
-
when "xsd" then ::Dcc::Validate::Xsd.call(xml,
|
|
49
|
+
when "xsd" then ::Dcc::Validate::Xsd.call(xml,
|
|
50
|
+
version: version)
|
|
50
51
|
when "schematron" then ::Dcc::Validate::Schematron.call(Dcc.parse(xml))
|
|
51
52
|
when "all"
|
|
52
53
|
::Dcc::Validate::Xsd.call(xml, version: version)
|
|
53
|
-
|
|
54
|
+
.merge(::Dcc::Validate::Schematron.call(Dcc.parse(xml)))
|
|
54
55
|
else
|
|
55
56
|
abort "Unknown validation target: #{schema} (expected xsd|schematron|all)"
|
|
56
57
|
end
|
|
@@ -58,17 +59,18 @@ module Dcc
|
|
|
58
59
|
exit(1) unless result.ok?
|
|
59
60
|
end
|
|
60
61
|
|
|
61
|
-
desc "convert FORMAT FILE",
|
|
62
|
+
desc "convert FORMAT FILE",
|
|
63
|
+
"Convert a DCC XML file to json / yaml / csv / html"
|
|
62
64
|
method_option :output, type: :string, banner: "PATH",
|
|
63
|
-
|
|
65
|
+
desc: "Write output to a file instead of stdout"
|
|
64
66
|
def convert(format, file)
|
|
65
67
|
Cli.ensure_loaded!
|
|
66
68
|
xml = File.read(file)
|
|
67
|
-
dcc = Dcc.parse(xml)
|
|
68
69
|
result =
|
|
69
70
|
case format
|
|
70
|
-
when "json" then ::Dcc::Convert::Json.call(
|
|
71
|
-
when "yaml" then ::Dcc::Convert::Yaml.call(
|
|
71
|
+
when "json" then ::Dcc::Convert::Json.call(Dcc.parse(xml))
|
|
72
|
+
when "yaml" then ::Dcc::Convert::Yaml.call(Dcc.parse(xml))
|
|
73
|
+
when "html" then ::Dcc::Convert::Html.call(xml)
|
|
72
74
|
else
|
|
73
75
|
abort "Format not yet implemented: #{format}"
|
|
74
76
|
end
|
|
@@ -80,19 +82,24 @@ module Dcc
|
|
|
80
82
|
end
|
|
81
83
|
end
|
|
82
84
|
|
|
83
|
-
desc "extract TARGET FILE",
|
|
85
|
+
desc "extract TARGET FILE",
|
|
86
|
+
"Extract embedded files or quantities from a DCC"
|
|
84
87
|
method_option :index, type: :numeric, desc: "Index of the file to extract"
|
|
85
88
|
method_option :output, type: :string, banner: "PATH",
|
|
86
|
-
|
|
89
|
+
desc: "Write extracted payload to PATH"
|
|
87
90
|
method_option :ring, type: :string,
|
|
88
|
-
|
|
91
|
+
enum: %w[administrativeData measurementResults comment document]
|
|
89
92
|
def extract(target, file)
|
|
90
93
|
Cli.ensure_loaded!
|
|
91
94
|
dcc = Dcc.parse(File.read(file))
|
|
92
95
|
case target
|
|
93
96
|
when "files"
|
|
94
97
|
files = ::Dcc::Extract::File.each(dcc)
|
|
95
|
-
|
|
98
|
+
if options[:ring]
|
|
99
|
+
files = files.select do |f|
|
|
100
|
+
f.ring.to_s == options[:ring]
|
|
101
|
+
end
|
|
102
|
+
end
|
|
96
103
|
if options[:index]
|
|
97
104
|
f = files[options[:index].to_i] || abort("No file at index #{options[:index]}")
|
|
98
105
|
output_to(f.data, options[:output], f.file_name)
|
|
@@ -106,7 +113,7 @@ module Dcc
|
|
|
106
113
|
|
|
107
114
|
desc "inspect FILE", "Show a human-readable summary of a DCC"
|
|
108
115
|
method_option :format, type: :string, default: "text",
|
|
109
|
-
|
|
116
|
+
enum: %w[text json yaml]
|
|
110
117
|
def inspect(file)
|
|
111
118
|
Cli.ensure_loaded!
|
|
112
119
|
dcc = Dcc.parse(File.read(file))
|
|
@@ -116,7 +123,7 @@ module Dcc
|
|
|
116
123
|
|
|
117
124
|
desc "diff FILE1 FILE2", "Compare two DCC files structurally"
|
|
118
125
|
method_option :format, type: :string, default: "text",
|
|
119
|
-
|
|
126
|
+
enum: %w[text json]
|
|
120
127
|
def diff(file1, file2)
|
|
121
128
|
Cli.ensure_loaded!
|
|
122
129
|
dcc1 = Dcc.parse(File.read(file1))
|
|
@@ -125,6 +132,17 @@ module Dcc
|
|
|
125
132
|
::Dcc::Cli::Formatters.print(diff, format: options[:format])
|
|
126
133
|
end
|
|
127
134
|
|
|
135
|
+
desc "serve", "Start the DCC web validator (http://localhost:4567)"
|
|
136
|
+
method_option :port, type: :numeric, default: 4567, desc: "Port"
|
|
137
|
+
method_option :bind, type: :string, default: "0.0.0.0",
|
|
138
|
+
desc: "Bind address"
|
|
139
|
+
def serve
|
|
140
|
+
require "dcc/server"
|
|
141
|
+
::Dcc.load_all!
|
|
142
|
+
puts "Starting DCC Validator on http://localhost:#{options[:port]}"
|
|
143
|
+
::Dcc::Server::App.run!(port: options[:port], bind: options[:bind])
|
|
144
|
+
end
|
|
145
|
+
|
|
128
146
|
desc "version", "Print the dcc gem version"
|
|
129
147
|
def version
|
|
130
148
|
puts "dcc #{::Dcc::VERSION}"
|
|
@@ -146,4 +164,4 @@ module Dcc
|
|
|
146
164
|
end
|
|
147
165
|
end
|
|
148
166
|
end
|
|
149
|
-
end
|
|
167
|
+
end
|
data/lib/dcc/cli/formatters.rb
CHANGED
|
@@ -1,12 +1,30 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "tty-table"
|
|
4
|
-
|
|
5
3
|
module Dcc
|
|
6
4
|
module Cli
|
|
7
5
|
# `Dcc::Cli::Formatters` picks the right text/JSON/YAML representation
|
|
8
6
|
# of a result model and prints it.
|
|
9
7
|
module Formatters
|
|
8
|
+
# Goes to stderr, so a piped `extract files` still yields clean data.
|
|
9
|
+
TTY_TABLE_HINT = "note: install tty-table for aligned output " \
|
|
10
|
+
"(gem install tty-table)"
|
|
11
|
+
|
|
12
|
+
# `tty-table` is optional. It pulls in eight further gems to prettify
|
|
13
|
+
# one command, which every library user would otherwise install just
|
|
14
|
+
# to parse XML. Loaded on first use, and its absence falls back to the
|
|
15
|
+
# plain renderer rather than raising the way `Dcc::Server` does.
|
|
16
|
+
def self.table_available?
|
|
17
|
+
return @table_available unless @table_available.nil?
|
|
18
|
+
|
|
19
|
+
begin
|
|
20
|
+
require "tty-table"
|
|
21
|
+
@table_available = true
|
|
22
|
+
rescue ::LoadError
|
|
23
|
+
@table_available = false
|
|
24
|
+
end
|
|
25
|
+
@table_available
|
|
26
|
+
end
|
|
27
|
+
|
|
10
28
|
module_function
|
|
11
29
|
|
|
12
30
|
def print(object, format: "text")
|
|
@@ -14,7 +32,7 @@ module Dcc
|
|
|
14
32
|
when "json" then puts object.to_json
|
|
15
33
|
when "yaml" then puts object.to_yaml
|
|
16
34
|
else
|
|
17
|
-
puts object
|
|
35
|
+
puts object
|
|
18
36
|
end
|
|
19
37
|
end
|
|
20
38
|
|
|
@@ -24,6 +42,14 @@ module Dcc
|
|
|
24
42
|
return
|
|
25
43
|
end
|
|
26
44
|
|
|
45
|
+
unless table_available?
|
|
46
|
+
# Hint first: a stderr that raises is caught by the rescue below,
|
|
47
|
+
# which re-runs the renderer. Printing first would duplicate it.
|
|
48
|
+
warn TTY_TABLE_HINT
|
|
49
|
+
print_files_plain(files)
|
|
50
|
+
return
|
|
51
|
+
end
|
|
52
|
+
|
|
27
53
|
# TTY::Table calls ioctl which fails on StringIO / pipes. Fall back
|
|
28
54
|
# to a plain-text rendering when stdout isn't a real terminal.
|
|
29
55
|
if $stdout.is_a?(::IO) && !$stdout.tty?
|
|
@@ -34,7 +60,8 @@ module Dcc
|
|
|
34
60
|
table = ::TTY::Table.new(
|
|
35
61
|
["Index", "Name", "File Name", "MIME", "Ring", "Size"],
|
|
36
62
|
files.each_with_index.map do |f, i|
|
|
37
|
-
[i, f.name.to_s, f.file_name.to_s, f.mime_type.to_s, f.ring,
|
|
63
|
+
[i, f.name.to_s, f.file_name.to_s, f.mime_type.to_s, f.ring,
|
|
64
|
+
f.data.bytesize]
|
|
38
65
|
end,
|
|
39
66
|
)
|
|
40
67
|
puts table.render
|
|
@@ -45,8 +72,9 @@ module Dcc
|
|
|
45
72
|
|
|
46
73
|
def print_files_plain(files)
|
|
47
74
|
# Widths
|
|
48
|
-
widths = ["Index".size, "Name".size, "File Name".size, "MIME".size,
|
|
49
|
-
|
|
75
|
+
widths = ["Index".size, "Name".size, "File Name".size, "MIME".size,
|
|
76
|
+
"Ring".size, "Size".size]
|
|
77
|
+
files.each_with_index.map do |f, i|
|
|
50
78
|
[
|
|
51
79
|
i.to_s,
|
|
52
80
|
f.name.to_s,
|
|
@@ -74,4 +102,4 @@ module Dcc
|
|
|
74
102
|
end
|
|
75
103
|
end
|
|
76
104
|
end
|
|
77
|
-
end
|
|
105
|
+
end
|
data/lib/dcc/cli.rb
CHANGED
|
@@ -34,7 +34,8 @@ module Dcc
|
|
|
34
34
|
# @param registry [Lutaml::Model::TypeRegistry, nil] custom registry.
|
|
35
35
|
# @param fallback_to [Array<Symbol>] fallback chain for type lookup.
|
|
36
36
|
# @param substitutions [Array<Hash{from_type:, to_type:}>] type substitutions.
|
|
37
|
-
def create_context(id:, registry: nil, fallback_to: [context_id],
|
|
37
|
+
def create_context(id:, registry: nil, fallback_to: [context_id],
|
|
38
|
+
substitutions: [])
|
|
38
39
|
normalized_id = ::Dcc::ContextOptions.normalize_context_reference(id)
|
|
39
40
|
return populate_context! if normalized_id == context_id
|
|
40
41
|
|
data/lib/dcc/context_options.rb
CHANGED
|
@@ -13,7 +13,8 @@ module Dcc
|
|
|
13
13
|
# @param register [Symbol, String, nil] deprecated alias for `context:`.
|
|
14
14
|
# @param default_context [Symbol, nil] returned when neither is given.
|
|
15
15
|
# @param warning_source [String] caller name for deprecation warnings.
|
|
16
|
-
def normalize_context_option(context:, register:, default_context:,
|
|
16
|
+
def normalize_context_option(context:, register:, default_context:,
|
|
17
|
+
warning_source:)
|
|
17
18
|
raise_if_conflicting!(context, register)
|
|
18
19
|
return normalize_context_reference(register) || default_context if register
|
|
19
20
|
return default_context unless context_specified?(context)
|
|
@@ -29,7 +30,8 @@ module Dcc
|
|
|
29
30
|
return reference.to_sym if reference.is_a?(String)
|
|
30
31
|
return reference.id.to_sym if reference.is_a?(::Lutaml::Xml::Namespace)
|
|
31
32
|
|
|
32
|
-
raise ArgumentError,
|
|
33
|
+
raise ArgumentError,
|
|
34
|
+
"Unsupported context/register reference: #{reference.inspect}"
|
|
33
35
|
end
|
|
34
36
|
|
|
35
37
|
def context_specified?(context)
|
data/lib/dcc/convert/csv.rb
CHANGED
|
@@ -7,7 +7,8 @@ module Dcc
|
|
|
7
7
|
# `Dcc::Convert::Csv` flattens a parsed DCC into a tabular CSV with
|
|
8
8
|
# one row per measurement quantity.
|
|
9
9
|
module Csv
|
|
10
|
-
HEADERS = %w[result_name quantity_name quantity_type value unit
|
|
10
|
+
HEADERS = %w[result_name quantity_name quantity_type value unit
|
|
11
|
+
uncertainty coverage_factor].freeze
|
|
11
12
|
|
|
12
13
|
class << self
|
|
13
14
|
# @param dcc [Lutaml::Model::Serializable]
|
|
@@ -31,7 +32,8 @@ module Dcc
|
|
|
31
32
|
|
|
32
33
|
def collect_quantities(dcc)
|
|
33
34
|
rows = []
|
|
34
|
-
return rows unless Dcc::TypeGuards.has_attribute?(dcc,
|
|
35
|
+
return rows unless Dcc::TypeGuards.has_attribute?(dcc,
|
|
36
|
+
:measurement_results)
|
|
35
37
|
|
|
36
38
|
Array(dcc.measurement_results.measurement_result).each do |mr|
|
|
37
39
|
mr_name = name_of(mr)
|
|
@@ -53,7 +55,9 @@ module Dcc
|
|
|
53
55
|
walk_quantities(Array(data.quantity), mr_name, result_name, rows)
|
|
54
56
|
Array(data.list).each do |list|
|
|
55
57
|
walk_quantities(Array(list.quantity), mr_name, result_name, rows)
|
|
56
|
-
Array(list.list).each
|
|
58
|
+
Array(list.list).each do |sub|
|
|
59
|
+
walk_data(sub, mr_name, result_name, rows)
|
|
60
|
+
end
|
|
57
61
|
end
|
|
58
62
|
end
|
|
59
63
|
|
|
@@ -63,10 +67,10 @@ module Dcc
|
|
|
63
67
|
end
|
|
64
68
|
end
|
|
65
69
|
|
|
66
|
-
def push_quantity(q, mr_name,
|
|
70
|
+
def push_quantity(q, mr_name, _result_name, kind, rows)
|
|
67
71
|
rows << [
|
|
68
72
|
mr_name, name_of(q), kind,
|
|
69
|
-
value_of(q), unit_of(q), uncertainty_of(q), k_factor_of(q)
|
|
73
|
+
value_of(q), unit_of(q), uncertainty_of(q), k_factor_of(q)
|
|
70
74
|
]
|
|
71
75
|
end
|
|
72
76
|
|
|
@@ -74,11 +78,21 @@ module Dcc
|
|
|
74
78
|
return "" unless node && Dcc::TypeGuards.has_attribute?(node, :name)
|
|
75
79
|
return "" unless node.name
|
|
76
80
|
|
|
77
|
-
content = Dcc::TypeGuards.has_attribute?(node.name,
|
|
81
|
+
content = if Dcc::TypeGuards.has_attribute?(node.name,
|
|
82
|
+
:content)
|
|
83
|
+
node.name.content
|
|
84
|
+
else
|
|
85
|
+
[]
|
|
86
|
+
end
|
|
78
87
|
first = Array(content).first
|
|
79
88
|
return "" unless first
|
|
80
89
|
|
|
81
|
-
vals = Dcc::TypeGuards.has_attribute?(first,
|
|
90
|
+
vals = if Dcc::TypeGuards.has_attribute?(first,
|
|
91
|
+
:value)
|
|
92
|
+
first.value
|
|
93
|
+
else
|
|
94
|
+
[]
|
|
95
|
+
end
|
|
82
96
|
Array(vals).first.to_s
|
|
83
97
|
end
|
|
84
98
|
|
|
@@ -87,7 +101,11 @@ module Dcc
|
|
|
87
101
|
return reals.first.value.to_s if reals.any?
|
|
88
102
|
|
|
89
103
|
lists = Array(q.real_list_xml_list)
|
|
90
|
-
|
|
104
|
+
# Serialized rather than mapped over: BigDecimal#to_s renders 0.072
|
|
105
|
+
# as "0.72e-1", which is not what the document said.
|
|
106
|
+
if lists.any?
|
|
107
|
+
return ::Dcc::Type::DecimalXmlList.serialize(lists.first.value_xml_list)
|
|
108
|
+
end
|
|
91
109
|
|
|
92
110
|
""
|
|
93
111
|
end
|
|
@@ -126,4 +144,4 @@ module Dcc
|
|
|
126
144
|
end
|
|
127
145
|
end
|
|
128
146
|
end
|
|
129
|
-
end
|
|
147
|
+
end
|
data/lib/dcc/convert/html.rb
CHANGED
|
@@ -1,127 +1,43 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "liquid"
|
|
4
|
+
|
|
3
5
|
module Dcc
|
|
4
6
|
module Convert
|
|
5
|
-
# `Dcc::Convert::Html` renders a parsed DCC into a human-readable HTML
|
|
6
|
-
# report with collapsible sections per administrative section and
|
|
7
|
-
# measurement result.
|
|
8
7
|
module Html
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
# @return [Dcc::Convert::Result]
|
|
12
|
-
def call(dcc)
|
|
13
|
-
payload = <<~HTML
|
|
14
|
-
<!DOCTYPE html>
|
|
15
|
-
<html lang="en">
|
|
16
|
-
<head>
|
|
17
|
-
<meta charset="utf-8">
|
|
18
|
-
<title>DCC Report #{escape(unique_identifier_of(dcc))}</title>
|
|
19
|
-
<style>
|
|
20
|
-
body { font-family: -apple-system, system-ui, sans-serif; margin: 2rem; }
|
|
21
|
-
h1, h2, h3 { color: #333; }
|
|
22
|
-
section { margin-bottom: 1.5rem; padding: 1rem; border: 1px solid #eee; border-radius: 4px; }
|
|
23
|
-
table { border-collapse: collapse; }
|
|
24
|
-
th, td { border: 1px solid #ddd; padding: 0.5rem 1rem; text-align: left; }
|
|
25
|
-
th { background: #f5f5f5; }
|
|
26
|
-
</style>
|
|
27
|
-
</head>
|
|
28
|
-
<body>
|
|
29
|
-
<h1>Digital Calibration Certificate</h1>
|
|
30
|
-
#{summary_table(dcc)}
|
|
31
|
-
#{administrative_section(dcc)}
|
|
32
|
-
#{measurement_section(dcc)}
|
|
33
|
-
</body>
|
|
34
|
-
</html>
|
|
35
|
-
HTML
|
|
8
|
+
TEMPLATE_PATH = File.expand_path("templates/report.liquid",
|
|
9
|
+
__dir__).freeze
|
|
36
10
|
|
|
11
|
+
class << self
|
|
12
|
+
def call(dcc_or_xml, lang: nil)
|
|
13
|
+
if dcc_or_xml.is_a?(::String)
|
|
14
|
+
xml = dcc_or_xml
|
|
15
|
+
hash = ::Dcc::Convert::Json.xml_to_hash(xml)
|
|
16
|
+
schema_version = hash["schemaVersion"]
|
|
17
|
+
source_class = "xml"
|
|
18
|
+
else
|
|
19
|
+
xml = dcc_or_xml.to_xml
|
|
20
|
+
hash = ::Dcc::Convert::Json.xml_to_hash(xml)
|
|
21
|
+
schema_version = dcc_or_xml.schema_version.to_s
|
|
22
|
+
source_class = dcc_or_xml.class.name
|
|
23
|
+
end
|
|
24
|
+
rendered = render_template(hash, lang: lang)
|
|
37
25
|
::Dcc::Convert::Result.new(
|
|
38
26
|
format: :html,
|
|
39
|
-
payload:
|
|
40
|
-
source_class:
|
|
41
|
-
schema_version:
|
|
27
|
+
payload: rendered,
|
|
28
|
+
source_class: source_class,
|
|
29
|
+
schema_version: schema_version,
|
|
42
30
|
)
|
|
43
31
|
end
|
|
44
32
|
|
|
45
33
|
private
|
|
46
34
|
|
|
47
|
-
def
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
<table>
|
|
52
|
-
<tr><th>Schema version</th><td>#{escape(dcc.schema_version)}</td></tr>
|
|
53
|
-
<tr><th>Unique identifier</th><td>#{escape(unique_identifier_of(dcc))}</td></tr>
|
|
54
|
-
<tr><th>Country</th><td>#{escape(country_of(dcc))}</td></tr>
|
|
55
|
-
</table>
|
|
56
|
-
</section>
|
|
57
|
-
HTML
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
def administrative_section(dcc)
|
|
61
|
-
return "" unless Dcc::TypeGuards.has_attribute?(dcc, :administrative_data) && dcc.administrative_data
|
|
62
|
-
|
|
63
|
-
admin = dcc.administrative_data
|
|
64
|
-
items = Dcc::TypeGuards.has_attribute?(admin, :items) && admin.items ? Array(admin.items.item) : []
|
|
65
|
-
<<~HTML
|
|
66
|
-
<section>
|
|
67
|
-
<h2>Items (#{items.size})</h2>
|
|
68
|
-
<ul>
|
|
69
|
-
#{items.map { |i| "<li>#{escape(name_of(i))}</li>" }.join("\n")}
|
|
70
|
-
</ul>
|
|
71
|
-
</section>
|
|
72
|
-
HTML
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
def measurement_section(dcc)
|
|
76
|
-
return "" unless Dcc::TypeGuards.has_attribute?(dcc, :measurement_results) && dcc.measurement_results
|
|
77
|
-
|
|
78
|
-
mrs = Array(dcc.measurement_results.measurement_result)
|
|
79
|
-
<<~HTML
|
|
80
|
-
<section>
|
|
81
|
-
<h2>Measurement Results (#{mrs.size})</h2>
|
|
82
|
-
<ul>
|
|
83
|
-
#{mrs.map { |mr| "<li>#{escape(name_of(mr))}</li>" }.join("\n")}
|
|
84
|
-
</ul>
|
|
85
|
-
</section>
|
|
86
|
-
HTML
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
def unique_identifier_of(dcc)
|
|
90
|
-
Dcc::TypeGuards.has_attribute?(dcc, :administrative_data) &&
|
|
91
|
-
Dcc::TypeGuards.has_attribute?(dcc.administrative_data, :core_data) &&
|
|
92
|
-
dcc.administrative_data.core_data &&
|
|
93
|
-
dcc.administrative_data.core_data.unique_identifier.to_s
|
|
94
|
-
end
|
|
95
|
-
|
|
96
|
-
def country_of(dcc)
|
|
97
|
-
Dcc::TypeGuards.has_attribute?(dcc, :administrative_data) &&
|
|
98
|
-
Dcc::TypeGuards.has_attribute?(dcc.administrative_data, :core_data) &&
|
|
99
|
-
dcc.administrative_data.core_data &&
|
|
100
|
-
dcc.administrative_data.core_data.country_code_iso_3166_1.to_s
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
def name_of(node)
|
|
104
|
-
return "" unless node && Dcc::TypeGuards.has_attribute?(node, :name)
|
|
105
|
-
return "" unless node.name
|
|
106
|
-
|
|
107
|
-
content = Dcc::TypeGuards.has_attribute?(node.name, :content) ? node.name.content : []
|
|
108
|
-
first = Array(content).first
|
|
109
|
-
return "" unless first
|
|
110
|
-
|
|
111
|
-
vals = Dcc::TypeGuards.has_attribute?(first, :value) ? first.value : []
|
|
112
|
-
Array(vals).first.to_s
|
|
113
|
-
end
|
|
114
|
-
|
|
115
|
-
def escape(s)
|
|
116
|
-
return "" if s.nil?
|
|
117
|
-
|
|
118
|
-
s.to_s
|
|
119
|
-
.gsub("&", "&")
|
|
120
|
-
.gsub("<", "<")
|
|
121
|
-
.gsub(">", ">")
|
|
122
|
-
.gsub('"', """)
|
|
35
|
+
def render_template(hash, lang:)
|
|
36
|
+
template = ::Liquid::Template.parse(File.read(TEMPLATE_PATH))
|
|
37
|
+
template.render({ "dcc" => hash, "page_lang" => lang },
|
|
38
|
+
filters: [::Dcc::Convert::LiquidFilters])
|
|
123
39
|
end
|
|
124
40
|
end
|
|
125
41
|
end
|
|
126
42
|
end
|
|
127
|
-
end
|
|
43
|
+
end
|
data/lib/dcc/convert/json.rb
CHANGED
|
@@ -45,18 +45,21 @@ module Dcc
|
|
|
45
45
|
return hash
|
|
46
46
|
end
|
|
47
47
|
|
|
48
|
+
grouped = {}
|
|
48
49
|
children.each do |child|
|
|
49
50
|
name = child.name.to_s.split(":").last
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
hash[name] = Array(hash[name]) << value
|
|
53
|
-
else
|
|
54
|
-
hash[name] = value
|
|
55
|
-
end
|
|
51
|
+
grouped[name] ||= []
|
|
52
|
+
grouped[name] << child
|
|
56
53
|
end
|
|
54
|
+
|
|
55
|
+
grouped.each do |name, child_list|
|
|
56
|
+
values = child_list.map { |c| element_to_hash(c) }
|
|
57
|
+
hash[name] = values.size == 1 ? values.first : values
|
|
58
|
+
end
|
|
59
|
+
hash["_text"] = combined_text unless combined_text.empty?
|
|
57
60
|
hash
|
|
58
61
|
end
|
|
59
62
|
end
|
|
60
63
|
end
|
|
61
64
|
end
|
|
62
|
-
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Dcc
|
|
4
|
+
module Convert
|
|
5
|
+
module LiquidFilters
|
|
6
|
+
def dcc_text(content, lang = nil)
|
|
7
|
+
return "" if content.nil? || content == ""
|
|
8
|
+
|
|
9
|
+
case content
|
|
10
|
+
when ::String
|
|
11
|
+
content
|
|
12
|
+
when ::Hash
|
|
13
|
+
content["_text"] || content[:_text] || content.to_s
|
|
14
|
+
when ::Array
|
|
15
|
+
if lang
|
|
16
|
+
entry = content.find do |c|
|
|
17
|
+
c.is_a?(::Hash) && (c["lang"] == lang || c[:lang] == lang)
|
|
18
|
+
end
|
|
19
|
+
return dcc_text(entry, lang) if entry
|
|
20
|
+
end
|
|
21
|
+
first = content.find { |c| c.is_a?(::Hash) }
|
|
22
|
+
first ? (first["_text"] || first[:_text] || "") : ""
|
|
23
|
+
else
|
|
24
|
+
content.to_s
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def dcc_initials(name)
|
|
29
|
+
return "?" if name.nil? || name.empty?
|
|
30
|
+
|
|
31
|
+
parts = name.split
|
|
32
|
+
return parts.first[0..0].upcase if parts.size == 1
|
|
33
|
+
|
|
34
|
+
(parts.first[0] + parts.last[0]).upcase
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def dcc_count(obj)
|
|
38
|
+
return 0 if obj.nil?
|
|
39
|
+
|
|
40
|
+
case obj
|
|
41
|
+
when ::Array then obj.size
|
|
42
|
+
when ::Hash then 1
|
|
43
|
+
else 1
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def dcc_items(obj)
|
|
48
|
+
return [] if obj.nil?
|
|
49
|
+
|
|
50
|
+
case obj
|
|
51
|
+
when ::Array then obj
|
|
52
|
+
when ::Hash then [obj]
|
|
53
|
+
else [obj]
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|