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.
- 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 +9 -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 +152 -26
- 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/formula/ast.rb +137 -0
- data/lib/dcc/extract/formula/evaluator.rb +357 -0
- data/lib/dcc/extract/formula/parser.rb +305 -0
- data/lib/dcc/extract/formula/quantity.rb +89 -0
- data/lib/dcc/extract/formula.rb +110 -0
- 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/complex.rb +252 -0
- data/lib/dcc/quantity_math/matrix.rb +217 -0
- data/lib/dcc/quantity_math/quantity.rb +1 -1
- data/lib/dcc/quantity_math/real.rb +6 -6
- data/lib/dcc/quantity_math.rb +31 -3
- 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/streaming/reader.rb +246 -0
- data/lib/dcc/streaming.rb +79 -0
- 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 +68 -10
- 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 +148 -9
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
|
data/lib/dcc/convert/result.rb
CHANGED
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="{{ page_lang | default: 'en' }}">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>DCC {{ dcc.administrativeData.coreData.uniqueIdentifier | default: "Certificate" }}</title>
|
|
7
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
8
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
9
|
+
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;700&family=IBM+Plex+Mono:wght@300;400;500&family=IBM+Plex+Sans:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
|
10
|
+
<style>
|
|
11
|
+
:root {
|
|
12
|
+
--bg: #0a0e1a; --surface: #111827; --surface-2: #1a2236;
|
|
13
|
+
--border: #1e293b; --border-hi: #334155;
|
|
14
|
+
--text: #e2e8f0; --text-dim: #94a3b8; --text-mute: #64748b;
|
|
15
|
+
--accent: #38bdf8; --accent-glow: rgba(56,189,248,0.12);
|
|
16
|
+
--green: #4ade80; --amber: #fbbf24; --red: #f87171;
|
|
17
|
+
}
|
|
18
|
+
* { margin:0; padding:0; box-sizing:border-box; }
|
|
19
|
+
body { background:var(--bg); color:var(--text); font-family:'IBM Plex Sans',sans-serif; font-size:14px; line-height:1.6; -webkit-font-smoothing:antialiased; }
|
|
20
|
+
.mono { font-family:'IBM Plex Mono',monospace; }
|
|
21
|
+
.display { font-family:'DM Sans',sans-serif; letter-spacing:-0.02em; }
|
|
22
|
+
.page { max-width:1100px; margin:0 auto; padding:2.5rem 1.5rem 4rem; }
|
|
23
|
+
|
|
24
|
+
.header { position:relative; border:1px solid var(--border); border-radius:16px; padding:2.5rem; background:linear-gradient(135deg,var(--surface),var(--surface-2)); overflow:hidden; margin-bottom:1.5rem; }
|
|
25
|
+
.header::before { content:''; position:absolute; top:-50%; right:-20%; width:60%; height:200%; background:radial-gradient(ellipse,var(--accent-glow) 0%,transparent 60%); pointer-events:none; }
|
|
26
|
+
.header::after { content:''; position:absolute; top:0; left:0; right:0; height:1px; background:linear-gradient(90deg,transparent,var(--accent),transparent); opacity:0.5; }
|
|
27
|
+
.header-grid { display:grid; grid-template-columns:1fr auto; gap:2rem; align-items:start; position:relative; z-index:1; }
|
|
28
|
+
.badge { display:inline-flex; align-items:center; gap:0.5rem; padding:0.35rem 0.8rem; border:1px solid var(--accent); border-radius:100px; font-size:0.7rem; font-weight:500; letter-spacing:0.1em; text-transform:uppercase; color:var(--accent); background:var(--accent-glow); margin-bottom:1rem; }
|
|
29
|
+
.badge-dot { width:6px; height:6px; border-radius:50%; background:var(--accent); animation:pulse 2s ease-in-out infinite; }
|
|
30
|
+
@keyframes pulse { 0%,100% { opacity:1; } 50% { opacity:0.3; } }
|
|
31
|
+
.header h1 { font-size:2.2rem; font-weight:700; line-height:1.1; margin-bottom:0.5rem; background:linear-gradient(180deg,#f8fafc,#94a3b8); -webkit-background-clip:text; -webkit-text-fill-color:transparent; }
|
|
32
|
+
.header .subtitle { color:var(--text-dim); font-size:0.95rem; }
|
|
33
|
+
.header .uid { font-family:'IBM Plex Mono',monospace; font-size:0.85rem; color:var(--text-mute); margin-top:0.75rem; }
|
|
34
|
+
.header-side { text-align:right; border-left:1px solid var(--border-hi); padding-left:2rem; }
|
|
35
|
+
.version-tag { font-family:'IBM Plex Mono',monospace; font-size:1.1rem; font-weight:500; color:var(--green); }
|
|
36
|
+
.version-label { font-size:0.7rem; text-transform:uppercase; letter-spacing:0.1em; color:var(--text-mute); }
|
|
37
|
+
|
|
38
|
+
.card { border:1px solid var(--border); border-radius:12px; background:var(--surface); overflow:hidden; animation:slideUp 0.5s ease-out both; }
|
|
39
|
+
.card + .card { margin-top:1.5rem; }
|
|
40
|
+
.card:nth-child(2) { animation-delay:0.08s; }
|
|
41
|
+
.card:nth-child(3) { animation-delay:0.16s; }
|
|
42
|
+
.card:nth-child(4) { animation-delay:0.24s; }
|
|
43
|
+
.card:nth-child(5) { animation-delay:0.32s; }
|
|
44
|
+
.card:nth-child(6) { animation-delay:0.4s; }
|
|
45
|
+
@keyframes slideUp { from { opacity:0; transform:translateY(12px); } to { opacity:1; transform:translateY(0); } }
|
|
46
|
+
.card-header { padding:1.25rem 1.5rem; border-bottom:1px solid var(--border); display:flex; align-items:center; gap:0.75rem; }
|
|
47
|
+
.card-icon { width:32px; height:32px; border-radius:8px; display:flex; align-items:center; justify-content:center; font-size:1rem; border:1px solid var(--border-hi); background:var(--surface-2); }
|
|
48
|
+
.card-title { font-size:0.85rem; font-weight:600; letter-spacing:0.08em; text-transform:uppercase; color:var(--text-dim); }
|
|
49
|
+
.card-body { padding:1.5rem; }
|
|
50
|
+
|
|
51
|
+
.data-grid { display:grid; grid-template-columns:repeat(auto-fill,minmax(250px,1fr)); gap:1px; background:var(--border); border:1px solid var(--border); border-radius:8px; overflow:hidden; }
|
|
52
|
+
.data-cell { background:var(--surface); padding:1rem 1.25rem; }
|
|
53
|
+
.data-label { font-size:0.7rem; text-transform:uppercase; letter-spacing:0.1em; color:var(--text-mute); margin-bottom:0.25rem; }
|
|
54
|
+
.data-value { font-size:0.95rem; color:var(--text); }
|
|
55
|
+
.data-value.mono { font-family:'IBM Plex Mono',monospace; font-size:0.9rem; }
|
|
56
|
+
|
|
57
|
+
.data-table { width:100%; border-collapse:collapse; }
|
|
58
|
+
.data-table th { text-align:left; padding:0.6rem 1rem; font-size:0.7rem; text-transform:uppercase; letter-spacing:0.08em; color:var(--text-mute); font-weight:500; border-bottom:1px solid var(--border-hi); }
|
|
59
|
+
.data-table td { padding:0.75rem 1rem; border-bottom:1px solid var(--border); font-size:0.875rem; }
|
|
60
|
+
.data-table tr:last-child td { border-bottom:none; }
|
|
61
|
+
.data-table tr:hover td { background:var(--surface-2); }
|
|
62
|
+
|
|
63
|
+
.signer { display:flex; align-items:center; gap:0.75rem; padding:0.75rem 1rem; border:1px solid var(--border); border-radius:8px; background:var(--surface-2); }
|
|
64
|
+
.signer + .signer { margin-top:0.5rem; }
|
|
65
|
+
.signer-avatar { width:36px; height:36px; border-radius:50%; display:flex; align-items:center; justify-content:center; font-weight:600; font-size:0.8rem; color:var(--surface); background:var(--accent); flex-shrink:0; }
|
|
66
|
+
.signer-name { font-weight:500; font-size:0.875rem; }
|
|
67
|
+
.signer-role { font-size:0.75rem; color:var(--text-mute); }
|
|
68
|
+
.signer-main { margin-left:auto; padding:0.2rem 0.6rem; border-radius:100px; font-size:0.65rem; font-weight:600; text-transform:uppercase; letter-spacing:0.08em; color:var(--green); border:1px solid var(--green); background:rgba(74,222,128,0.1); }
|
|
69
|
+
|
|
70
|
+
.mr-block { border:1px solid var(--border); border-radius:8px; padding:1.25rem; background:var(--surface-2); }
|
|
71
|
+
.mr-block + .mr-block { margin-top:0.75rem; }
|
|
72
|
+
.mr-title { font-weight:500; margin-bottom:0.75rem; }
|
|
73
|
+
|
|
74
|
+
@media (max-width:700px) { .header-grid { grid-template-columns:1fr; } .header-side { border-left:none; border-top:1px solid var(--border-hi); padding-left:0; padding-top:1rem; text-align:left; } }
|
|
75
|
+
</style>
|
|
76
|
+
</head>
|
|
77
|
+
<body>
|
|
78
|
+
<div class="page">
|
|
79
|
+
|
|
80
|
+
{% assign admin = dcc.administrativeData %}
|
|
81
|
+
{% assign core = admin.coreData %}
|
|
82
|
+
{% assign lab = admin.calibrationLaboratory %}
|
|
83
|
+
{% assign results = dcc.measurementResults %}
|
|
84
|
+
|
|
85
|
+
<!-- Header -->
|
|
86
|
+
<div class="header">
|
|
87
|
+
<div class="header-grid">
|
|
88
|
+
<div>
|
|
89
|
+
<div class="badge"><span class="badge-dot"></span> Digital Calibration Certificate</div>
|
|
90
|
+
{% assign item_obj = admin.items.item %}
|
|
91
|
+
<h1 class="display">{{ item_obj.name.content | dcc_text | default: "Calibration Certificate" }}</h1>
|
|
92
|
+
<p class="subtitle">{{ lab.contact.name.content | dcc_text | default: "" }}</p>
|
|
93
|
+
<p class="uid mono">ID: {{ core.uniqueIdentifier | default: "—" }}</p>
|
|
94
|
+
</div>
|
|
95
|
+
<div class="header-side">
|
|
96
|
+
<div class="version-label">Schema</div>
|
|
97
|
+
<div class="version-tag mono">v{{ dcc.schemaVersion }}</div>
|
|
98
|
+
{% if core.countryCodeISO3166_1 %}
|
|
99
|
+
<div style="margin-top:0.5rem;">
|
|
100
|
+
<div class="version-label">Country</div>
|
|
101
|
+
<div class="mono" style="font-size:1.1rem;">{{ core.countryCodeISO3166_1 }}</div>
|
|
102
|
+
</div>
|
|
103
|
+
{% endif %}
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
107
|
+
|
|
108
|
+
<!-- Core Data -->
|
|
109
|
+
<div class="card">
|
|
110
|
+
<div class="card-header"><div class="card-icon">⚙</div><span class="card-title">Core Data</span></div>
|
|
111
|
+
<div class="card-body">
|
|
112
|
+
<div class="data-grid">
|
|
113
|
+
<div class="data-cell"><div class="data-label">Unique ID</div><div class="data-value mono">{{ core.uniqueIdentifier | default: "—" }}</div></div>
|
|
114
|
+
{% if core.receiptDate %}<div class="data-cell"><div class="data-label">Receipt Date</div><div class="data-value mono">{{ core.receiptDate }}</div></div>{% endif %}
|
|
115
|
+
{% if core.beginPerformanceDate %}<div class="data-cell"><div class="data-label">Performance Begin</div><div class="data-value mono">{{ core.beginPerformanceDate }}</div></div>{% endif %}
|
|
116
|
+
{% if core.endPerformanceDate %}<div class="data-cell"><div class="data-label">Performance End</div><div class="data-value mono">{{ core.endPerformanceDate }}</div></div>{% endif %}
|
|
117
|
+
{% if core.usedLangCodeISO639_1 %}<div class="data-cell"><div class="data-label">Languages</div><div class="data-value mono">{{ core.usedLangCodeISO639_1 | dcc_items | join: ", " }}</div></div>{% endif %}
|
|
118
|
+
{% if core.mandatoryLangCodeISO639_1 %}<div class="data-cell"><div class="data-label">Mandatory Language</div><div class="data-value mono">{{ core.mandatoryLangCodeISO639_1 }}</div></div>{% endif %}
|
|
119
|
+
</div>
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
122
|
+
|
|
123
|
+
<!-- Items -->
|
|
124
|
+
{% assign items_data = admin.items.item %}
|
|
125
|
+
{% if items_data %}
|
|
126
|
+
<div class="card">
|
|
127
|
+
<div class="card-header"><div class="card-icon">☰</div><span class="card-title">Calibrated Item</span></div>
|
|
128
|
+
<div class="card-body">
|
|
129
|
+
{% if items_data.name %}
|
|
130
|
+
{% assign item = items_data %}
|
|
131
|
+
<div class="data-grid">
|
|
132
|
+
<div class="data-cell"><div class="data-label">Name</div><div class="data-value">{{ item.name.content | dcc_text | default: "—" }}</div></div>
|
|
133
|
+
{% if item.model %}<div class="data-cell"><div class="data-label">Model</div><div class="data-value">{{ item.model }}</div></div>{% endif %}
|
|
134
|
+
{% if item.manufacturer %}<div class="data-cell"><div class="data-label">Manufacturer</div><div class="data-value">{{ item.manufacturer.name.content | dcc_text }}</div></div>{% endif %}
|
|
135
|
+
</div>
|
|
136
|
+
{% if item.identifications %}
|
|
137
|
+
<table class="data-table" style="margin-top:0.75rem;">
|
|
138
|
+
<thead><tr><th>Identification</th><th>Issuer</th></tr></thead>
|
|
139
|
+
<tbody>
|
|
140
|
+
{% assign ids = item.identifications.identification %}
|
|
141
|
+
{% if ids.name %}
|
|
142
|
+
<tr><td class="mono">{{ ids.value }}</td><td>{{ ids.issuer }}</td></tr>
|
|
143
|
+
{% else %}
|
|
144
|
+
{% for id in ids %}<tr><td class="mono">{{ id.value }}</td><td>{{ id.issuer }}</td></tr>{% endfor %}
|
|
145
|
+
{% endif %}
|
|
146
|
+
</tbody>
|
|
147
|
+
</table>
|
|
148
|
+
{% endif %}
|
|
149
|
+
{% else %}
|
|
150
|
+
{% for item in items_data %}
|
|
151
|
+
<div class="data-grid">
|
|
152
|
+
<div class="data-cell"><div class="data-label">Name</div><div class="data-value">{{ item.name.content | dcc_text | default: "—" }}</div></div>
|
|
153
|
+
{% if item.model %}<div class="data-cell"><div class="data-label">Model</div><div class="data-value">{{ item.model }}</div></div>{% endif %}
|
|
154
|
+
{% if item.manufacturer %}<div class="data-cell"><div class="data-label">Manufacturer</div><div class="data-value">{{ item.manufacturer.name.content | dcc_text }}</div></div>{% endif %}
|
|
155
|
+
</div>
|
|
156
|
+
{% endfor %}
|
|
157
|
+
{% endif %}
|
|
158
|
+
</div>
|
|
159
|
+
</div>
|
|
160
|
+
{% endif %}
|
|
161
|
+
|
|
162
|
+
<!-- Calibration Laboratory -->
|
|
163
|
+
{% if lab.contact %}
|
|
164
|
+
<div class="card">
|
|
165
|
+
<div class="card-header"><div class="card-icon">☑</div><span class="card-title">Calibration Laboratory</span></div>
|
|
166
|
+
<div class="card-body">
|
|
167
|
+
<div class="data-grid">
|
|
168
|
+
<div class="data-cell"><div class="data-label">Name</div><div class="data-value">{{ lab.contact.name.content | dcc_text }}</div></div>
|
|
169
|
+
{% if lab.contact.eMail %}<div class="data-cell"><div class="data-label">Email</div><div class="data-value mono">{{ lab.contact.eMail }}</div></div>{% endif %}
|
|
170
|
+
{% if lab.contact.phone %}<div class="data-cell"><div class="data-label">Phone</div><div class="data-value mono">{{ lab.contact.phone }}</div></div>{% endif %}
|
|
171
|
+
{% if lab.contact.location %}
|
|
172
|
+
<div class="data-cell"><div class="data-label">Address</div><div class="data-value">
|
|
173
|
+
{% if lab.contact.location.street %}{{ lab.contact.location.street }} {{ lab.contact.location.streetNo }}{% endif %}
|
|
174
|
+
{% if lab.contact.location.postCode %}<br>{{ lab.contact.location.postCode }} {{ lab.contact.location.city }}{% endif %}
|
|
175
|
+
{% if lab.contact.location.countryCode %}<br>{{ lab.contact.location.countryCode }}{% endif %}
|
|
176
|
+
</div></div>
|
|
177
|
+
{% endif %}
|
|
178
|
+
</div>
|
|
179
|
+
</div>
|
|
180
|
+
</div>
|
|
181
|
+
{% endif %}
|
|
182
|
+
|
|
183
|
+
<!-- Responsible Persons -->
|
|
184
|
+
{% assign persons = admin.respPersons.respPerson | dcc_items %}
|
|
185
|
+
{% if persons.size > 0 %}
|
|
186
|
+
<div class="card">
|
|
187
|
+
<div class="card-header"><div class="card-icon">♂</div><span class="card-title">Responsible Persons</span></div>
|
|
188
|
+
<div class="card-body">
|
|
189
|
+
{% for p in persons %}
|
|
190
|
+
{% assign pname = p.person.name.content | dcc_text %}
|
|
191
|
+
<div class="signer">
|
|
192
|
+
<div class="signer-avatar">{{ pname | dcc_initials }}</div>
|
|
193
|
+
<div>
|
|
194
|
+
<div class="signer-name">{{ pname }}</div>
|
|
195
|
+
{% if p.person.eMail %}<div class="signer-role mono">{{ p.person.eMail }}</div>{% endif %}
|
|
196
|
+
</div>
|
|
197
|
+
{% if p.mainSigner %}<span class="signer-main">Main Signer</span>{% endif %}
|
|
198
|
+
</div>
|
|
199
|
+
{% endfor %}
|
|
200
|
+
</div>
|
|
201
|
+
</div>
|
|
202
|
+
{% endif %}
|
|
203
|
+
|
|
204
|
+
<!-- Measurement Results -->
|
|
205
|
+
{% assign mrs = results.measurementResult | dcc_items %}
|
|
206
|
+
{% if mrs.size > 0 %}
|
|
207
|
+
<div class="card">
|
|
208
|
+
<div class="card-header"><div class="card-icon">⚡</div><span class="card-title">Measurement Results</span></div>
|
|
209
|
+
<div class="card-body">
|
|
210
|
+
{% for mr in mrs %}
|
|
211
|
+
<div class="mr-block">
|
|
212
|
+
<div class="mr-title">{{ mr.name.content | dcc_text | default: "Untitled Result" }}</div>
|
|
213
|
+
{% if mr.usedMethods %}
|
|
214
|
+
{% for method in mr.usedMethods.usedMethod | dcc_items %}
|
|
215
|
+
{% if method.name %}
|
|
216
|
+
<div style="color:var(--text-dim); font-size:0.8rem; padding-bottom:0.25rem;">{{ method.name.content | dcc_text }}</div>
|
|
217
|
+
{% endif %}
|
|
218
|
+
{% endfor %}
|
|
219
|
+
{% endif %}
|
|
220
|
+
{% if mr.results %}
|
|
221
|
+
{% for r in mr.results.result | dcc_items %}
|
|
222
|
+
{% if r.name %}
|
|
223
|
+
<div style="padding:0.5rem 0.75rem; border-left:2px solid var(--accent); margin-top:0.5rem; background:rgba(56,189,248,0.05); border-radius:0 6px 6px 0;">
|
|
224
|
+
{{ r.name.content | dcc_text }}
|
|
225
|
+
</div>
|
|
226
|
+
{% endif %}
|
|
227
|
+
{% endfor %}
|
|
228
|
+
{% endif %}
|
|
229
|
+
</div>
|
|
230
|
+
{% endfor %}
|
|
231
|
+
</div>
|
|
232
|
+
</div>
|
|
233
|
+
{% endif %}
|
|
234
|
+
|
|
235
|
+
<!-- Customer -->
|
|
236
|
+
{% if admin.customer %}
|
|
237
|
+
<div class="card">
|
|
238
|
+
<div class="card-header"><div class="card-icon">➤</div><span class="card-title">Customer</span></div>
|
|
239
|
+
<div class="card-body">
|
|
240
|
+
<div class="data-grid">
|
|
241
|
+
<div class="data-cell"><div class="data-label">Name</div><div class="data-value">{{ admin.customer.name.content | dcc_text }}</div></div>
|
|
242
|
+
{% if admin.customer.eMail %}<div class="data-cell"><div class="data-label">Email</div><div class="data-value mono">{{ admin.customer.eMail }}</div></div>{% endif %}
|
|
243
|
+
</div>
|
|
244
|
+
</div>
|
|
245
|
+
</div>
|
|
246
|
+
{% endif %}
|
|
247
|
+
|
|
248
|
+
</div>
|
|
249
|
+
</body>
|
|
250
|
+
</html>
|
data/lib/dcc/convert/yaml.rb
CHANGED
data/lib/dcc/convert.rb
CHANGED
data/lib/dcc/diff/change.rb
CHANGED
data/lib/dcc/diff/result.rb
CHANGED
|
@@ -31,7 +31,7 @@ module Dcc
|
|
|
31
31
|
"(no structural changes)"
|
|
32
32
|
else
|
|
33
33
|
header = "#{changes.size} change(s): " \
|
|
34
|
-
|
|
34
|
+
"#{additions.size} added, #{removals.size} removed, #{modifications.size} changed"
|
|
35
35
|
([header] + changes.map(&:to_s)).join("\n")
|
|
36
36
|
end
|
|
37
37
|
end
|
|
@@ -41,7 +41,9 @@ module Dcc
|
|
|
41
41
|
::JSON.pretty_generate(
|
|
42
42
|
additions: additions.map { |c| { path: c.path, value: c.after } },
|
|
43
43
|
removals: removals.map { |c| { path: c.path, value: c.before } },
|
|
44
|
-
modifications: modifications.map
|
|
44
|
+
modifications: modifications.map do |c|
|
|
45
|
+
{ path: c.path, before: c.before, after: c.after }
|
|
46
|
+
end,
|
|
45
47
|
)
|
|
46
48
|
end
|
|
47
49
|
|
|
@@ -50,9 +52,11 @@ module Dcc
|
|
|
50
52
|
{
|
|
51
53
|
additions: additions.map { |c| { path: c.path, value: c.after } },
|
|
52
54
|
removals: removals.map { |c| { path: c.path, value: c.before } },
|
|
53
|
-
modifications: modifications.map
|
|
55
|
+
modifications: modifications.map do |c|
|
|
56
|
+
{ path: c.path, before: c.before, after: c.after }
|
|
57
|
+
end,
|
|
54
58
|
}.to_yaml
|
|
55
59
|
end
|
|
56
60
|
end
|
|
57
61
|
end
|
|
58
|
-
end
|
|
62
|
+
end
|
data/lib/dcc/diff.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "bigdecimal"
|
|
4
|
+
require "date"
|
|
4
5
|
require "lutaml/model"
|
|
5
6
|
|
|
6
7
|
module Dcc
|
|
@@ -12,6 +13,12 @@ module Dcc
|
|
|
12
13
|
autoload :Result, "dcc/diff/result"
|
|
13
14
|
autoload :Change, "dcc/diff/change"
|
|
14
15
|
|
|
16
|
+
# Leaf types compared directly rather than descended into. Numeric covers
|
|
17
|
+
# BigDecimal and Date covers DateTime, so neither needs its own entry.
|
|
18
|
+
PRIMITIVE_TYPES = [
|
|
19
|
+
::String, ::Numeric, ::Symbol, ::Time, ::Date
|
|
20
|
+
].freeze
|
|
21
|
+
|
|
15
22
|
class << self
|
|
16
23
|
# @param a [Lutaml::Model::Serializable]
|
|
17
24
|
# @param b [Lutaml::Model::Serializable]
|
|
@@ -27,20 +34,24 @@ module Dcc
|
|
|
27
34
|
def collect_differences(a, b, path, changes)
|
|
28
35
|
# Both nil - no diff
|
|
29
36
|
return if a.nil? && b.nil?
|
|
37
|
+
|
|
30
38
|
# One is nil - add/remove
|
|
31
39
|
if a.nil?
|
|
32
|
-
changes << Change.new(path: path, kind: :add, before: nil,
|
|
40
|
+
changes << Change.new(path: path, kind: :add, before: nil,
|
|
41
|
+
after: summarize(b))
|
|
33
42
|
return
|
|
34
43
|
end
|
|
35
44
|
if b.nil?
|
|
36
|
-
changes << Change.new(path: path, kind: :remove,
|
|
45
|
+
changes << Change.new(path: path, kind: :remove,
|
|
46
|
+
before: summarize(a), after: nil)
|
|
37
47
|
return
|
|
38
48
|
end
|
|
39
49
|
|
|
40
50
|
# Both primitives - compare values
|
|
41
51
|
if primitive?(a) || primitive?(b)
|
|
42
|
-
|
|
43
|
-
changes << Change.new(path: path, kind: :change, before: a,
|
|
52
|
+
unless equivalent?(a, b)
|
|
53
|
+
changes << Change.new(path: path, kind: :change, before: a,
|
|
54
|
+
after: b)
|
|
44
55
|
end
|
|
45
56
|
return
|
|
46
57
|
end
|
|
@@ -63,14 +74,35 @@ module Dcc
|
|
|
63
74
|
end
|
|
64
75
|
end
|
|
65
76
|
|
|
77
|
+
# "Did this document change?" is not "are these numbers equal?".
|
|
78
|
+
# `BigDecimal("NaN") == BigDecimal("NaN")` is false by IEEE, and
|
|
79
|
+
# `Values#==` honours that, so two documents carrying the same
|
|
80
|
+
# not-measured entry would otherwise report a change to itself.
|
|
81
|
+
def equivalent?(a, b)
|
|
82
|
+
values = ::Dcc::Type::DecimalXmlList::Values
|
|
83
|
+
return a == b unless a.is_a?(values) && b.is_a?(values)
|
|
84
|
+
|
|
85
|
+
a.size == b.size && a.to_a.zip(b.to_a).all? { |x, y| same_decimal?(x, y) }
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# `Values.new` takes whatever it is handed, so a list assigned in Ruby
|
|
89
|
+
# rather than parsed can hold anything. Only BigDecimals are asked
|
|
90
|
+
# whether they are NaN.
|
|
91
|
+
def same_decimal?(left, right)
|
|
92
|
+
return true if left == right
|
|
93
|
+
|
|
94
|
+
left.is_a?(::BigDecimal) && right.is_a?(::BigDecimal) &&
|
|
95
|
+
left.nan? && right.nan?
|
|
96
|
+
end
|
|
97
|
+
|
|
66
98
|
def primitive?(value)
|
|
67
|
-
return true if value.nil?
|
|
68
|
-
return
|
|
69
|
-
return true if
|
|
70
|
-
return true if value.is_a?(::Time) || value.is_a?(::Date) || value.is_a?(::DateTime)
|
|
71
|
-
return true if value.is_a?(::BigDecimal)
|
|
99
|
+
return true if value.nil? || [true, false].include?(value)
|
|
100
|
+
return false if value.is_a?(::Array)
|
|
101
|
+
return true if PRIMITIVE_TYPES.any? { |type| value.is_a?(type) }
|
|
72
102
|
|
|
73
|
-
|
|
103
|
+
# Anything we cannot descend into is compared with `==`, which covers
|
|
104
|
+
# value objects such as decimal XML lists.
|
|
105
|
+
!value.class.respond_to?(:attributes)
|
|
74
106
|
end
|
|
75
107
|
|
|
76
108
|
def summarize(node)
|
|
@@ -80,4 +112,4 @@ module Dcc
|
|
|
80
112
|
end
|
|
81
113
|
end
|
|
82
114
|
end
|
|
83
|
-
end
|
|
115
|
+
end
|