opencdd 0.3.1 → 0.3.3
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/bin/lint-no-raw-mdc +47 -24
- data/lib/cdd.rb +1 -1
- data/lib/opencdd/cddal/serializer.rb +0 -10
- data/lib/opencdd/database.rb +1 -0
- data/lib/opencdd/det_classification.rb +26 -0
- data/lib/opencdd/exporters/json.rb +13 -8
- data/lib/opencdd/languages.rb +8 -2
- data/lib/opencdd/meta_class.rb +15 -0
- data/lib/opencdd/parcel/flat_dir_reader.rb +1 -0
- data/lib/opencdd/parcel/language_aliases.rb +42 -0
- data/lib/opencdd/parcel/layout_detector.rb +1 -1
- data/lib/opencdd/parcel/scrape_verifier.rb +1 -1
- data/lib/opencdd/parcel/sharded_dir_reader.rb +1 -1
- data/lib/opencdd/parcel/sheet_schema.rb +52 -6
- data/lib/opencdd/parcel/versioned_reader.rb +1 -1
- data/lib/opencdd/parcel/workbook_reader.rb +4 -0
- data/lib/opencdd/parcel/writer.rb +5 -1
- data/lib/opencdd/parcel.rb +1 -0
- data/lib/opencdd/version.rb +1 -1
- data/lib/opencdd.rb +1 -0
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c3b245ffe7055b867a634912373305b044261c4fab4af420029ad62be539321f
|
|
4
|
+
data.tar.gz: c26921adb3a18950743b39274420522d3b41ff68e1b478ee1c27fb1e0c19d374
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 96952a531d921778810b8c6f929589d933d536dbbac9ceed1c3f5b41006f710b4b649262420241a05554cba496d8ff45ab6b9b54e8caa58883fb865180fac96f
|
|
7
|
+
data.tar.gz: 625fd243c5ca85e9cca57b2c2c4eae0c1f9024b9444255ba0df60773175c2029ec76ca4e3acb070e527bd9190066c38682130c1be046fca22c00f60b17b0f8be
|
data/bin/lint-no-raw-mdc
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
#!/usr/bin/env
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
#
|
|
2
4
|
# lint-no-raw-mdc — fail if any raw MDC_P### / MDC_C### / EXT_P### /
|
|
3
5
|
# EXT_C### / CIM_P### string literal appears in lib/opencdd/ outside
|
|
4
6
|
# the designated SSOT files.
|
|
@@ -14,29 +16,50 @@
|
|
|
14
16
|
#
|
|
15
17
|
# Comment lines (lines whose first non-whitespace character is '#')
|
|
16
18
|
# are also exempt.
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
#
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
19
|
+
|
|
20
|
+
require "open3"
|
|
21
|
+
|
|
22
|
+
EXCLUDED_PATHS = %w[
|
|
23
|
+
lib/opencdd/property_ids.rb
|
|
24
|
+
lib/opencdd/meta_class.rb
|
|
25
|
+
lib/opencdd/alias_table.rb
|
|
26
|
+
lib/opencdd/parcel/sheet_schema.rb
|
|
27
|
+
lib/opencdd/cddal/generated_parser.rb
|
|
28
|
+
].freeze
|
|
29
|
+
|
|
30
|
+
# Match "MDC_P###", "MDC_C###", "EXT_P###", "EXT_C###", "CIM_P###"
|
|
31
|
+
# with optional _### sub-ids, as a double-quoted Ruby string literal.
|
|
32
|
+
# Source string is POSIX ERE (not Ruby regex) — git grep -E requires it.
|
|
33
|
+
LITERAL_PATTERN_SOURCE = '"(MDC_[CP][0-9]+(_[0-9]+)?|EXT_[CP][0-9]+|CIM_P[0-9]+)"'.freeze
|
|
34
|
+
|
|
35
|
+
# Each git grep hit looks like: <path>:<lineno>:<content>
|
|
36
|
+
# Skip comment-only lines (content's first non-space char is '#').
|
|
37
|
+
HIT_LINE_PATTERN = /\A(?<path>[^:]+):(?<lineno>\d+):(?<content>.*)\z/m.freeze
|
|
38
|
+
|
|
39
|
+
# lib/opencdd/**/*.rb minus the excluded SSOT files.
|
|
40
|
+
pathspecs = ["lib/opencdd/**/*.rb"].concat(EXCLUDED_PATHS.map { |p| ":!#{p}" })
|
|
41
|
+
|
|
42
|
+
cmd = ["git", "grep", "-n", "-E", LITERAL_PATTERN_SOURCE] + ["--"] + pathspecs
|
|
43
|
+
stdout, _stderr, status = Open3.capture3(*cmd)
|
|
44
|
+
|
|
45
|
+
# git grep returns 1 when there are no matches, which is the success
|
|
46
|
+
# case. Treat anything other than 0/1 as a real failure.
|
|
47
|
+
unless [0, 1].include?(status.exitstatus)
|
|
48
|
+
warn "ERROR: git grep failed (exit #{status.exitstatus})"
|
|
49
|
+
exit 2
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
hits = stdout.each_line.map(&:chomp).reject do |line|
|
|
53
|
+
match = HIT_LINE_PATTERN.match(line)
|
|
54
|
+
match && match[:content].lstrip.start_with?("#")
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
if hits.any?
|
|
58
|
+
warn "ERROR: raw MDC/EXT/CIM literal found outside designated SSOT files."
|
|
59
|
+
warn "Use Opencdd::PropertyIds::REGISTRY / Opencdd::MetaClasses::REGISTRY instead."
|
|
60
|
+
warn ""
|
|
61
|
+
hits.each { |hit| warn hit }
|
|
39
62
|
exit 1
|
|
40
|
-
|
|
63
|
+
end
|
|
41
64
|
|
|
42
65
|
exit 0
|
data/lib/cdd.rb
CHANGED
|
@@ -6,6 +6,6 @@
|
|
|
6
6
|
require "opencdd"
|
|
7
7
|
|
|
8
8
|
# Alias for backward compatibility. The module was renamed from
|
|
9
|
-
# Cdd to Opencdd, but many callers (
|
|
9
|
+
# Cdd to Opencdd, but many callers (data-private Rakefile, specs, etc.)
|
|
10
10
|
# still reference Cdd::. This alias lets them keep working.
|
|
11
11
|
Cdd = Opencdd unless defined?(Cdd)
|
|
@@ -174,7 +174,6 @@ module Opencdd
|
|
|
174
174
|
def format_value(value)
|
|
175
175
|
s = value.to_s
|
|
176
176
|
return quote_string(s) if string_literal?(s)
|
|
177
|
-
return format_set(s) if set_like?(s)
|
|
178
177
|
s
|
|
179
178
|
end
|
|
180
179
|
|
|
@@ -194,15 +193,6 @@ module Opencdd
|
|
|
194
193
|
"\"#{escaped}\""
|
|
195
194
|
end
|
|
196
195
|
|
|
197
|
-
def set_like?(s)
|
|
198
|
-
s.start_with?("(") && s.end_with?(")")
|
|
199
|
-
end
|
|
200
|
-
|
|
201
|
-
def format_set(s)
|
|
202
|
-
elements = Opencdd::StructuredValues.unwrap_and_split(s)
|
|
203
|
-
"{ #{elements.join(', ')} }"
|
|
204
|
-
end
|
|
205
|
-
|
|
206
196
|
def symbol_name_for(entity)
|
|
207
197
|
code = entity.code&.to_s
|
|
208
198
|
return code if code && !code.empty?
|
data/lib/opencdd/database.rb
CHANGED
|
@@ -110,6 +110,7 @@ module Opencdd
|
|
|
110
110
|
def relations ; @entities_by_type[:relation] || [] ; end
|
|
111
111
|
def view_controls ; @entities_by_type[:view_control] || [] ; end
|
|
112
112
|
def list_of_units ; @entities_by_type[:list_of_unit] || [] ; end
|
|
113
|
+
def det_classifications ; @entities_by_type[:det_classification] || [] ; end
|
|
113
114
|
|
|
114
115
|
def entities_of_type(type)
|
|
115
116
|
@entities_by_type[type.to_sym] || []
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Opencdd
|
|
4
|
+
class DetClassification < Opencdd::Entity
|
|
5
|
+
# The cdd.iec.ch search-export DET classification .xls stores the
|
|
6
|
+
# entity code as a short string (e.g. "A11") in column 1. We
|
|
7
|
+
# need to synthesize the full IRDI by prepending the meta-class's
|
|
8
|
+
# supplier prefix (IECCDD_001) — the standard from_row would
|
|
9
|
+
# IRDI.parse("A11") which fails (no namespace).
|
|
10
|
+
DET_CLASSIFICATION_SUPPLIER = "0112/2///IECCDD_001".freeze
|
|
11
|
+
|
|
12
|
+
def self.from_row(row, schema:, meta_class_irdi:, code_property_id: nil)
|
|
13
|
+
row = row.dup
|
|
14
|
+
if meta_class_irdi&.code == "MDC_C0101"
|
|
15
|
+
# Promote the short code to a full IRDI before parent handles it.
|
|
16
|
+
# code_property_id for MDC_C0101 is the MDC code ID (MDC_P001_5).
|
|
17
|
+
cid = code_property_id || Opencdd::MetaClasses.code_property_id_for("MDC_C0101")
|
|
18
|
+
short = cid && row[cid]
|
|
19
|
+
if short && !short.to_s.include?("#")
|
|
20
|
+
row[cid] = "#{DET_CLASSIFICATION_SUPPLIER}##{short}"
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
super
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -70,14 +70,15 @@ module Opencdd
|
|
|
70
70
|
# omitted (payload still has all the entity's own fields).
|
|
71
71
|
# ─────────────────────────────────────────────────────────────
|
|
72
72
|
PAYLOAD_BUILDERS = {
|
|
73
|
-
Opencdd::Klass
|
|
74
|
-
Opencdd::Property
|
|
75
|
-
Opencdd::Unit
|
|
76
|
-
Opencdd::ValueList
|
|
77
|
-
Opencdd::ValueTerm
|
|
78
|
-
Opencdd::Relation
|
|
79
|
-
Opencdd::ViewControl
|
|
80
|
-
Opencdd::ListUnit
|
|
73
|
+
Opencdd::Klass => :class_node,
|
|
74
|
+
Opencdd::Property => :property_node,
|
|
75
|
+
Opencdd::Unit => :unit_node,
|
|
76
|
+
Opencdd::ValueList => :value_list_node,
|
|
77
|
+
Opencdd::ValueTerm => :value_term_node,
|
|
78
|
+
Opencdd::Relation => :relation_node,
|
|
79
|
+
Opencdd::ViewControl => :view_control_node,
|
|
80
|
+
Opencdd::ListUnit => :list_of_unit_node,
|
|
81
|
+
Opencdd::DetClassification => :det_classification_node,
|
|
81
82
|
}.freeze
|
|
82
83
|
|
|
83
84
|
def payload_for(entity, database: nil)
|
|
@@ -134,6 +135,10 @@ module Opencdd
|
|
|
134
135
|
entity_payload(lou).merge(type: "list_of_unit").compact
|
|
135
136
|
end
|
|
136
137
|
|
|
138
|
+
def det_classification_node(det)
|
|
139
|
+
entity_payload(det).merge(type: "det_classification").compact
|
|
140
|
+
end
|
|
141
|
+
|
|
137
142
|
private
|
|
138
143
|
|
|
139
144
|
# Iterates every declared field on the entity's class (walking
|
data/lib/opencdd/languages.rb
CHANGED
|
@@ -12,6 +12,12 @@ module Opencdd
|
|
|
12
12
|
# "what languages does this dictionary have?" — used by the browser
|
|
13
13
|
# to render a language switcher and by the data pipeline to report
|
|
14
14
|
# language coverage.
|
|
15
|
+
#
|
|
16
|
+
# This is a model object. It does not normalize language codes:
|
|
17
|
+
# source-format quirks (e.g. IEC CDD .xls files using "jp" for
|
|
18
|
+
# Japanese) are normalized at the SheetSchema ingestion boundary
|
|
19
|
+
# via +Opencdd::Parcel::LanguageAliases+, so every consumer of
|
|
20
|
+
# this class can assume ISO 639-1 codes unconditionally.
|
|
15
21
|
class Languages
|
|
16
22
|
attr_reader :source, :translations
|
|
17
23
|
|
|
@@ -58,7 +64,7 @@ module Opencdd
|
|
|
58
64
|
def self.from_properties(properties, default_source: "en")
|
|
59
65
|
langs = properties.keys.each_with_object(Set.new) do |key, acc|
|
|
60
66
|
next unless key.include?(".")
|
|
61
|
-
|
|
67
|
+
_, lang = key.split(".", 2)
|
|
62
68
|
next unless lang =~ /\A[a-z]{2}(-[a-z0-9]+)?\z/i
|
|
63
69
|
acc << lang
|
|
64
70
|
end
|
|
@@ -67,4 +73,4 @@ module Opencdd
|
|
|
67
73
|
new(source: source, translations: translations)
|
|
68
74
|
end
|
|
69
75
|
end
|
|
70
|
-
end
|
|
76
|
+
end
|
data/lib/opencdd/meta_class.rb
CHANGED
|
@@ -80,6 +80,7 @@ module Opencdd
|
|
|
80
80
|
"MDC_C009" => "MDC_P001_10",
|
|
81
81
|
"MDC_C010" => "MDC_P001_11",
|
|
82
82
|
"MDC_C0100" => "C0101",
|
|
83
|
+
"MDC_C0101" => "MDC_P001_5",
|
|
83
84
|
"EXT_C001" => "EXT_P001",
|
|
84
85
|
}.freeze
|
|
85
86
|
|
|
@@ -91,6 +92,8 @@ module Opencdd
|
|
|
91
92
|
"MDC_C009" => :unit,
|
|
92
93
|
"MDC_C010" => :value_term,
|
|
93
94
|
"MDC_C0100" => :list_of_unit,
|
|
95
|
+
"MDC_C0101" => :det_classification,
|
|
96
|
+
"IECCDD_001" => :det_classification,
|
|
94
97
|
"EXT_C001" => :view_control,
|
|
95
98
|
}.freeze
|
|
96
99
|
|
|
@@ -261,6 +264,17 @@ module Opencdd
|
|
|
261
264
|
type: :list_of_unit,
|
|
262
265
|
allowed_property_ids: common,
|
|
263
266
|
)
|
|
267
|
+
# cdd.iec.ch's search-export uses CLASS_ID:=IECCDD_001 (an
|
|
268
|
+
# IEC-internal supplier scheme) — not a Parcel meta-class IRDI.
|
|
269
|
+
# We register MDC_C0101 as the canonical meta-class IRDI; the
|
|
270
|
+
# file's CLASS_ID is documentation, not the parsing gate.
|
|
271
|
+
det_classification = MetaClass.new(
|
|
272
|
+
irdi: "MDC_C0101",
|
|
273
|
+
name: "DetClassification",
|
|
274
|
+
entity_class: Opencdd::DetClassification,
|
|
275
|
+
type: :det_classification,
|
|
276
|
+
allowed_property_ids: common,
|
|
277
|
+
)
|
|
264
278
|
{
|
|
265
279
|
klass_class.irdi => klass_class,
|
|
266
280
|
property_class.irdi => property_class,
|
|
@@ -270,6 +284,7 @@ module Opencdd
|
|
|
270
284
|
value_term.irdi => value_term,
|
|
271
285
|
view_control.irdi => view_control,
|
|
272
286
|
list_of_unit.irdi => list_of_unit,
|
|
287
|
+
det_classification.irdi => det_classification,
|
|
273
288
|
}
|
|
274
289
|
end
|
|
275
290
|
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Opencdd
|
|
4
|
+
module Parcel
|
|
5
|
+
# Maps non-conformant language codes found in IEC CDD source .xls
|
|
6
|
+
# exports to their ISO 639-1 equivalents.
|
|
7
|
+
#
|
|
8
|
+
# This is a Parcel-layer concern, not a model concern: the aliases
|
|
9
|
+
# exist because one specific source format (IEC CDD XLS) uses
|
|
10
|
+
# non-standard codes in its property ID suffixes and directive
|
|
11
|
+
# rows. The +Languages+ value object — and every downstream
|
|
12
|
+
# consumer (JSON wire format, browser, TS model) — speaks ISO
|
|
13
|
+
# 639-1 unconditionally.
|
|
14
|
+
#
|
|
15
|
+
# The normalization happens at the SheetSchema ingestion
|
|
16
|
+
# boundary so downstream code never sees an alias. SheetSchema
|
|
17
|
+
# also exposes the set of original-to-canonical mappings it
|
|
18
|
+
# applied via +#normalized_language_codes+ for data-quality
|
|
19
|
+
# reporting.
|
|
20
|
+
module LanguageAliases
|
|
21
|
+
ALIASES = {
|
|
22
|
+
"jp" => "ja",
|
|
23
|
+
}.freeze
|
|
24
|
+
|
|
25
|
+
# Returns the ISO 639-1 form of +code+, or the input unchanged
|
|
26
|
+
# if it is already standard. Pure function: no I/O, no side
|
|
27
|
+
# effects.
|
|
28
|
+
def self.normalize(code)
|
|
29
|
+
return code if code.nil?
|
|
30
|
+
s = code.to_s.strip
|
|
31
|
+
return s if s.empty?
|
|
32
|
+
ALIASES.fetch(s, s)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# True when +code+ would be rewritten by +normalize+.
|
|
36
|
+
def self.alias?(code)
|
|
37
|
+
return false if code.nil?
|
|
38
|
+
ALIASES.key?(code.to_s.strip)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -32,7 +32,7 @@ module Opencdd
|
|
|
32
32
|
|
|
33
33
|
# Per-type export filename produced by cdd.iec.ch's download
|
|
34
34
|
# endpoint.
|
|
35
|
-
FILE_PATTERN = /\Aexport_(CLASS|PROPERTY|RELATION|UNIT|VALUELIST|VALUETERMS|LISTOFUNITS)_[^\.]+\.(xls|xlsx)\z/i.freeze
|
|
35
|
+
FILE_PATTERN = /\Aexport_(CLASS|PROPERTY|RELATION|UNIT|VALUELIST|VALUETERMS|LISTOFUNITS|DETCLASSIFICATION)_[^\.]+\.(xls|xlsx)\z/i.freeze
|
|
36
36
|
|
|
37
37
|
module_function
|
|
38
38
|
|
|
@@ -4,7 +4,7 @@ module Opencdd
|
|
|
4
4
|
module Parcel
|
|
5
5
|
# Verifies that scrape output produced populated .xls files, not
|
|
6
6
|
# just schema stubs. Catches the silent failure mode discovered
|
|
7
|
-
# on 2026-07-08: iec61360/
|
|
7
|
+
# on 2026-07-08: iec61360/iec-63213/iec-61360-7 scrapes appeared
|
|
8
8
|
# complete (10,338 PROPERTY .xls files for iec61360) but every
|
|
9
9
|
# file had 0 data rows. The build pipeline exported 574 classes
|
|
10
10
|
# + 0 properties, and the browser showed "No declared properties"
|
|
@@ -26,7 +26,7 @@ module Opencdd
|
|
|
26
26
|
# +.xls+ files into a Workbook.
|
|
27
27
|
#
|
|
28
28
|
# Example:
|
|
29
|
-
# reader = Opencdd::Parcel::ShardedDirReader.new("downloads/
|
|
29
|
+
# reader = Opencdd::Parcel::ShardedDirReader.new("downloads/iec-63213")
|
|
30
30
|
# db = Opencdd::Database.new
|
|
31
31
|
# reader.load_into(db)
|
|
32
32
|
#
|
|
@@ -3,11 +3,15 @@
|
|
|
3
3
|
module Opencdd
|
|
4
4
|
module Parcel
|
|
5
5
|
class SheetSchema
|
|
6
|
-
# Parcel-specific column-ID canonicalization. Maps
|
|
7
|
-
# ParcelMaker variant headers (MDC_P004_1, MDC_P005, ...)
|
|
8
|
-
# to the canonical IEC 61360 IDs (MDC_P004, MDC_P006, ...).
|
|
6
|
+
# Parcel-specific column-ID canonicalization. Maps:
|
|
9
7
|
#
|
|
10
|
-
#
|
|
8
|
+
# - ParcelMaker language-variant headers (MDC_P004_1, MDC_P005, ...)
|
|
9
|
+
# to the canonical IEC 61360 IDs (MDC_P004, MDC_P006, ...).
|
|
10
|
+
# - cdd.iec.ch search-export's project-specific IDs for DET
|
|
11
|
+
# classification (IECCDD_001_C0001, IECCDD_001_C0002.<lang>, ...)
|
|
12
|
+
# to the canonical IEC 61360 IDs (MDC_P001_5, MDC_P004_1.<lang>, ...).
|
|
13
|
+
#
|
|
14
|
+
# Owned by SheetSchema because these mappings only exist for
|
|
11
15
|
# the Parcel sheet layout — the PropertyIds registry is
|
|
12
16
|
# ontology-only and shouldn't carry format-specific details.
|
|
13
17
|
VARIANT_TO_CANONICAL = {
|
|
@@ -17,11 +21,29 @@ module Opencdd
|
|
|
17
21
|
"MDC_P005" => "MDC_P006",
|
|
18
22
|
"MDC_P007_1" => "MDC_P008",
|
|
19
23
|
"MDC_P007_2" => "MDC_P009",
|
|
24
|
+
|
|
25
|
+
# cdd.iec.ch search-export DET classification aliases.
|
|
26
|
+
# The file uses IEC-internal supplier scheme (IECCDD_001) and
|
|
27
|
+
# project-specific column IDs (C0001 = code, C0002.<lang> = name)
|
|
28
|
+
# rather than the standard MDC codes. Without these mappings,
|
|
29
|
+
# every DET classification entity would have nil code + name.
|
|
30
|
+
#
|
|
31
|
+
# C0002 maps to MDC_P004 (preferred_name), not MDC_P004_1
|
|
32
|
+
# (preferred_name_localized), because canonical_id splits the
|
|
33
|
+
# language suffix off and reapplies it after the base lookup —
|
|
34
|
+
# so the per-language entries (C0002.en, C0002.fr, etc.) below
|
|
35
|
+
# would be dead code. The base entry alone produces
|
|
36
|
+
# MDC_P004.<lang>, which is what the preferred_name field DSL
|
|
37
|
+
# reads via FieldRegistry.
|
|
38
|
+
"IECCDD_001_C0001" => "MDC_P001_5",
|
|
39
|
+
"IECCDD_001_C0002" => "MDC_P004",
|
|
20
40
|
}.freeze
|
|
21
41
|
|
|
22
42
|
# Canonicalize a Parcel column ID. Splits language tags
|
|
23
43
|
# (<id>.<lang>) so they round-trip cleanly. Returns the
|
|
24
44
|
# canonical ID (or the input unchanged if no mapping exists).
|
|
45
|
+
# Language codes are normalized to ISO 639-1 via
|
|
46
|
+
# +Opencdd::Parcel::LanguageAliases.normalize+.
|
|
25
47
|
def self.canonical_id(raw_id)
|
|
26
48
|
return nil if raw_id.nil?
|
|
27
49
|
s = raw_id.to_s.strip
|
|
@@ -30,7 +52,7 @@ module Opencdd
|
|
|
30
52
|
base = match ? match.pre_match : s
|
|
31
53
|
lang = match && match[:lang]
|
|
32
54
|
canonical = VARIANT_TO_CANONICAL[base] || base
|
|
33
|
-
lang ? "#{canonical}.#{lang}" : canonical
|
|
55
|
+
lang ? "#{canonical}.#{Opencdd::Parcel::LanguageAliases.normalize(lang)}" : canonical
|
|
34
56
|
end
|
|
35
57
|
|
|
36
58
|
DIRECTIVE_ROWS = %w[
|
|
@@ -110,7 +132,7 @@ module Opencdd
|
|
|
110
132
|
|
|
111
133
|
DIRECTIVE_ROW_PREFIX = "#".freeze
|
|
112
134
|
|
|
113
|
-
attr_reader :columns, :columns_by_id, :column_directives
|
|
135
|
+
attr_reader :columns, :columns_by_id, :column_directives, :normalized_language_codes
|
|
114
136
|
|
|
115
137
|
def initialize
|
|
116
138
|
@columns = []
|
|
@@ -194,6 +216,7 @@ module Opencdd
|
|
|
194
216
|
@columns_by_id[col.property_id] = col
|
|
195
217
|
end
|
|
196
218
|
|
|
219
|
+
@normalized_language_codes = compute_normalized_language_codes.freeze
|
|
197
220
|
freeze
|
|
198
221
|
end
|
|
199
222
|
|
|
@@ -260,11 +283,34 @@ module Opencdd
|
|
|
260
283
|
next if v.nil?
|
|
261
284
|
s = v.to_s.strip
|
|
262
285
|
next if s.empty?
|
|
286
|
+
lang = Opencdd::Parcel::LanguageAliases.normalize(lang)
|
|
263
287
|
lang = "en" if lang.nil? || lang.empty?
|
|
264
288
|
h[lang] = s
|
|
265
289
|
end
|
|
266
290
|
h
|
|
267
291
|
end
|
|
292
|
+
|
|
293
|
+
# Audit trail of language-code normalizations applied at this
|
|
294
|
+
# schema's ingestion boundary. Returns a frozen Hash mapping each
|
|
295
|
+
# original (non-conformant) language code seen in the source to
|
|
296
|
+
# its ISO 639-1 equivalent. Empty when the source spoke ISO.
|
|
297
|
+
def compute_normalized_language_codes
|
|
298
|
+
seen = {}
|
|
299
|
+
@column_directives.each_key do |key|
|
|
300
|
+
_, lang = key.to_s.split(".", 2)
|
|
301
|
+
next if lang.nil? || lang.empty?
|
|
302
|
+
normalized = Opencdd::Parcel::LanguageAliases.normalize(lang)
|
|
303
|
+
seen[lang] = normalized if normalized != lang
|
|
304
|
+
end
|
|
305
|
+
@columns.each do |col|
|
|
306
|
+
next if col.raw_property_id.nil?
|
|
307
|
+
_, lang = col.raw_property_id.split(".", 2)
|
|
308
|
+
next if lang.nil? || lang.empty?
|
|
309
|
+
normalized = Opencdd::Parcel::LanguageAliases.normalize(lang)
|
|
310
|
+
seen[lang] = normalized if normalized != lang
|
|
311
|
+
end
|
|
312
|
+
seen
|
|
313
|
+
end
|
|
268
314
|
end
|
|
269
315
|
end
|
|
270
316
|
end
|
|
@@ -17,7 +17,7 @@ module Opencdd
|
|
|
17
17
|
# version's subfolder.
|
|
18
18
|
#
|
|
19
19
|
# Example:
|
|
20
|
-
# reader = Opencdd::Parcel::VersionedReader.new("downloads/
|
|
20
|
+
# reader = Opencdd::Parcel::VersionedReader.new("downloads/iec-63213")
|
|
21
21
|
# reader.versions_for("KEA012") # => 3 VersionHistory::Entry
|
|
22
22
|
# reader.load_version("KEA012", "ABC123...") # => Database with v002 content
|
|
23
23
|
class VersionedReader
|
|
@@ -18,6 +18,8 @@ module Opencdd
|
|
|
18
18
|
"UNIT" => :unit,
|
|
19
19
|
"VALUELIST" => :value_list,
|
|
20
20
|
"VALUETERMS" => :value_term,
|
|
21
|
+
"LISTOFUNITS" => :list_of_unit,
|
|
22
|
+
"DETCLASSIFICATION" => :det_classification,
|
|
21
23
|
}.freeze
|
|
22
24
|
|
|
23
25
|
LEGACY_TYPE_TO_PARCEL_NAME = {
|
|
@@ -27,6 +29,8 @@ module Opencdd
|
|
|
27
29
|
value_term: "TERMINOLOGY",
|
|
28
30
|
unit: "UoM",
|
|
29
31
|
relation: "RELATION",
|
|
32
|
+
list_of_unit: "LISTOFUNITS",
|
|
33
|
+
det_classification: "DETCLASSIFICATION",
|
|
30
34
|
}.freeze
|
|
31
35
|
|
|
32
36
|
META_CLASS_BY_LEGACY_TYPE = Opencdd::MetaClasses::TYPE_BY_META_CLASS.invert.freeze
|
|
@@ -147,7 +147,11 @@ module Opencdd
|
|
|
147
147
|
rows = emitter.emit(built.sheet, built.entities)
|
|
148
148
|
workbook.add_worksheet(name: built.sheet.name) do |ws|
|
|
149
149
|
rows.each do |row|
|
|
150
|
-
|
|
150
|
+
# All property values are strings per IEC 61360 wire format.
|
|
151
|
+
# Force string cell type so values like "001" survive round-trip
|
|
152
|
+
# (otherwise caxlsx auto-types numeric-looking strings and roo
|
|
153
|
+
# reads them back as integers, breaking semantically_equal?).
|
|
154
|
+
emitted = ws.add_row(row, types: :string)
|
|
151
155
|
emitted.hidden = true if row_hidden?(row, hidden_directives)
|
|
152
156
|
end
|
|
153
157
|
end
|
data/lib/opencdd/parcel.rb
CHANGED
|
@@ -7,6 +7,7 @@ module Opencdd
|
|
|
7
7
|
|
|
8
8
|
autoload :Metadata, "opencdd/parcel/metadata"
|
|
9
9
|
autoload :SheetSchema, "opencdd/parcel/sheet_schema"
|
|
10
|
+
autoload :LanguageAliases, "opencdd/parcel/language_aliases"
|
|
10
11
|
autoload :Sheet, "opencdd/parcel/sheet"
|
|
11
12
|
autoload :Workbook, "opencdd/parcel/workbook"
|
|
12
13
|
autoload :WorkbookReader, "opencdd/parcel/workbook_reader"
|
data/lib/opencdd/version.rb
CHANGED
data/lib/opencdd.rb
CHANGED
|
@@ -29,6 +29,7 @@ module Opencdd
|
|
|
29
29
|
autoload :Relation, "opencdd/relation"
|
|
30
30
|
autoload :ViewControl, "opencdd/view_control"
|
|
31
31
|
autoload :ListUnit, "opencdd/list_unit"
|
|
32
|
+
autoload :DetClassification, "opencdd/det_classification"
|
|
32
33
|
|
|
33
34
|
autoload :Database, "opencdd/database"
|
|
34
35
|
autoload :EffectiveProperties, "opencdd/effective_properties"
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: opencdd
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.3.
|
|
4
|
+
version: 0.3.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- OpenCDD contributors
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-08-08 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: roo
|
|
@@ -236,6 +236,7 @@ files:
|
|
|
236
236
|
- lib/opencdd/database/parcel_integration.rb
|
|
237
237
|
- lib/opencdd/database/persistence.rb
|
|
238
238
|
- lib/opencdd/database/queries.rb
|
|
239
|
+
- lib/opencdd/det_classification.rb
|
|
239
240
|
- lib/opencdd/effective_properties.rb
|
|
240
241
|
- lib/opencdd/entity.rb
|
|
241
242
|
- lib/opencdd/entity/field_reader.rb
|
|
@@ -263,6 +264,7 @@ files:
|
|
|
263
264
|
- lib/opencdd/parcel/csv_writer.rb
|
|
264
265
|
- lib/opencdd/parcel/entity_manifest.rb
|
|
265
266
|
- lib/opencdd/parcel/flat_dir_reader.rb
|
|
267
|
+
- lib/opencdd/parcel/language_aliases.rb
|
|
266
268
|
- lib/opencdd/parcel/layout_detector.rb
|
|
267
269
|
- lib/opencdd/parcel/metadata.rb
|
|
268
270
|
- lib/opencdd/parcel/referenced_irdis.rb
|