opencdd 0.3.0 → 0.3.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/bin/lint-no-raw-mdc +47 -23
- data/lib/cdd.rb +1 -1
- data/lib/opencdd/cddal/serializer.rb +0 -10
- data/lib/opencdd/condition.rb +65 -0
- data/lib/opencdd/entity/name_synthesizer.rb +5 -49
- data/lib/opencdd/languages.rb +8 -2
- data/lib/opencdd/parcel/language_aliases.rb +42 -0
- data/lib/opencdd/parcel/sheet_schema.rb +28 -2
- data/lib/opencdd/parcel/writer.rb +5 -1
- data/lib/opencdd/parcel.rb +1 -0
- data/lib/opencdd/property.rb +0 -7
- data/lib/opencdd/property_ids.rb +52 -0
- data/lib/opencdd/validator/condition_rule.rb +3 -0
- data/lib/opencdd/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 907feca38dccee34a74c306983cdbe506bf3b18843640000501e7482cea75414
|
|
4
|
+
data.tar.gz: 2581c9dfac24e7bb1b8fcb973ef311854d8096947daf9a6d01ca726e672d2612
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: eb42bba181fa6e5802a6c3ed1ea46dce2d7ee9197dea2f929bd8b2e9565826cdd19dc503ba58a231b5555208244e5d8e084b92199944d8b7d30aa1e2e6879c2f
|
|
7
|
+
data.tar.gz: 74ab77f45f8365a433a638414c982d092140b07f9084fbb97ea94d846cc60a0c3787a29a2180d729a424715896514008c6ec0b2d5e9060ca8b3c60197d09f2fb
|
data/bin/lint-no-raw-mdc
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
#
|
|
1
4
|
# lint-no-raw-mdc — fail if any raw MDC_P### / MDC_C### / EXT_P### /
|
|
2
5
|
# EXT_C### / CIM_P### string literal appears in lib/opencdd/ outside
|
|
3
6
|
# the designated SSOT files.
|
|
@@ -13,29 +16,50 @@
|
|
|
13
16
|
#
|
|
14
17
|
# Comment lines (lines whose first non-whitespace character is '#')
|
|
15
18
|
# are also exempt.
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
#
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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 }
|
|
38
62
|
exit 1
|
|
39
|
-
|
|
63
|
+
end
|
|
40
64
|
|
|
41
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/condition.rb
CHANGED
|
@@ -13,6 +13,12 @@ module Opencdd
|
|
|
13
13
|
(?<right>.+)
|
|
14
14
|
\z/x.freeze
|
|
15
15
|
|
|
16
|
+
# A single bare token (IRDI or short code) without internal whitespace.
|
|
17
|
+
# Used to detect class-reference conditions of the form
|
|
18
|
+
# `0112/2///62683#ACE132` and distinguish them from malformed input
|
|
19
|
+
# like "no operator here" (which has internal whitespace).
|
|
20
|
+
SINGLE_TOKEN_PATTERN = /\A\S+\z/.freeze
|
|
21
|
+
|
|
16
22
|
attr_reader :left, :operator, :right
|
|
17
23
|
|
|
18
24
|
def initialize(left, operator, right)
|
|
@@ -24,6 +30,10 @@ module Opencdd
|
|
|
24
30
|
@right = right
|
|
25
31
|
end
|
|
26
32
|
|
|
33
|
+
def class_reference?
|
|
34
|
+
false
|
|
35
|
+
end
|
|
36
|
+
|
|
27
37
|
def set?
|
|
28
38
|
@right.is_a?(Set)
|
|
29
39
|
end
|
|
@@ -76,6 +86,12 @@ module Opencdd
|
|
|
76
86
|
op = Regexp.last_match(:op)
|
|
77
87
|
raw_right = Regexp.last_match(:right).strip
|
|
78
88
|
new(left, op, parse_rhs(raw_right))
|
|
89
|
+
elsif s =~ SET_PATTERN
|
|
90
|
+
body = Regexp.last_match(:body)
|
|
91
|
+
elements = body.split(/[,\s]+/).map(&:strip).reject(&:empty?)
|
|
92
|
+
ClassReference.new(irdis: elements)
|
|
93
|
+
elsif SINGLE_TOKEN_PATTERN.match?(s)
|
|
94
|
+
ClassReference.new(irdis: [s])
|
|
79
95
|
else
|
|
80
96
|
raise ArgumentError, "invalid condition expression: #{s.inspect}"
|
|
81
97
|
end
|
|
@@ -116,5 +132,54 @@ module Opencdd
|
|
|
116
132
|
def normalize_for_eq(value)
|
|
117
133
|
value.is_a?(Set) ? value.to_a.sort : value
|
|
118
134
|
end
|
|
135
|
+
|
|
136
|
+
# A condition of the form `{0112/2///62683#ACE132}` or a bare
|
|
137
|
+
# `0112/2///62683#ACE132` — meaning "this property applies when the
|
|
138
|
+
# host class is one of the listed IRDIs". IEC 62683 stores property
|
|
139
|
+
# conditions this way; the regular boolean-expression grammar in
|
|
140
|
+
# +Condition+ cannot represent it.
|
|
141
|
+
class ClassReference < Condition
|
|
142
|
+
attr_reader :irdis
|
|
143
|
+
|
|
144
|
+
# The binding key (string or symbol) consulted by +#satisfied_by?+
|
|
145
|
+
# to discover the host class IRDI. Callers that resolve the host
|
|
146
|
+
# class via a different key should override +satisfied_by?+.
|
|
147
|
+
HOST_CLASS_KEYS = [:class, :host_class, "class", "host_class"].freeze
|
|
148
|
+
|
|
149
|
+
def initialize(irdis:)
|
|
150
|
+
@irdis = Array(irdis).map(&:to_s)
|
|
151
|
+
@left = nil
|
|
152
|
+
@operator = nil
|
|
153
|
+
@right = nil
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def class_reference?
|
|
157
|
+
true
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def set?
|
|
161
|
+
@irdis.size > 1
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def satisfied_by?(bindings)
|
|
165
|
+
hash = bindings.is_a?(Hash) ? bindings : bindings.to_h
|
|
166
|
+
actual = HOST_CLASS_KEYS.map { |k| hash[k] }.compact.first
|
|
167
|
+
return true if actual.nil?
|
|
168
|
+
@irdis.any? { |irdi| actual.to_s == irdi }
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def to_s
|
|
172
|
+
@irdis.size == 1 ? @irdis.first : "{#{@irdis.join(', ')}}"
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def ==(other)
|
|
176
|
+
other.is_a?(ClassReference) && Set.new(@irdis) == Set.new(other.irdis)
|
|
177
|
+
end
|
|
178
|
+
alias_method :eql?, :==
|
|
179
|
+
|
|
180
|
+
def hash
|
|
181
|
+
Set.new(@irdis).hash
|
|
182
|
+
end
|
|
183
|
+
end
|
|
119
184
|
end
|
|
120
185
|
end
|
|
@@ -12,56 +12,12 @@ module Opencdd
|
|
|
12
12
|
# Extracted from SheetSchema so SheetSchema focuses on header
|
|
13
13
|
# parsing + Column construction. Name synthesis is a separate
|
|
14
14
|
# concern with its own test surface.
|
|
15
|
+
#
|
|
16
|
+
# The name→ID table itself lives in Opencdd::PropertyIds::IEC_COLUMN_NAMES
|
|
17
|
+
# (the lint-exempt SSOT). NAME_TO_PROPERTY_ID is kept here as a
|
|
18
|
+
# back-compat alias so existing callers see no public-API change.
|
|
15
19
|
module NameSynthesizer
|
|
16
|
-
|
|
17
|
-
# property IDs. These names appear in the #PROPERTY_NAME
|
|
18
|
-
# directive row when #PROPERTY_ID is absent.
|
|
19
|
-
NAME_TO_PROPERTY_ID = {
|
|
20
|
-
"Code" => "MDC_P001_5",
|
|
21
|
-
"Version" => "MDC_P002_1",
|
|
22
|
-
"Revision" => "MDC_P002_2",
|
|
23
|
-
"VersionInitiationDate" => "MDC_P003_1",
|
|
24
|
-
"VersionReleaseDate" => "MDC_P003_2",
|
|
25
|
-
"RevisionReleaseDate" => "MDC_P003_3",
|
|
26
|
-
"PreferredName" => "MDC_P004",
|
|
27
|
-
"SynonymousName" => "MDC_P007",
|
|
28
|
-
"ShortName" => "MDC_P005",
|
|
29
|
-
"Definition" => "MDC_P006",
|
|
30
|
-
"DefinitionSource" => "MDC_P006_1",
|
|
31
|
-
"Note" => "MDC_P008",
|
|
32
|
-
"Remark" => "MDC_P009",
|
|
33
|
-
"Drawing" => "MDC_P008_1",
|
|
34
|
-
"GUID" => "MDC_P066",
|
|
35
|
-
"TimeStamp" => "MDC_P067",
|
|
36
|
-
"ClassType" => "MDC_P011",
|
|
37
|
-
"Superclass" => "MDC_P010",
|
|
38
|
-
"IsCaseOf" => "MDC_P013",
|
|
39
|
-
"ApplicableProperties" => "MDC_P014",
|
|
40
|
-
"ImportedProperties" => "MDC_P090",
|
|
41
|
-
"SubClassSelection" => "MDC_P016",
|
|
42
|
-
"DataType" => "MDC_P022",
|
|
43
|
-
"ValueFormat" => "MDC_P024",
|
|
44
|
-
"DefinitionClass" => "MDC_P021",
|
|
45
|
-
"Unit" => "MDC_P041",
|
|
46
|
-
"Condition" => "MDC_P028",
|
|
47
|
-
"ListType" => "MDC_P046",
|
|
48
|
-
"CodeList" => "MDC_P044",
|
|
49
|
-
"TermList" => "MDC_P043",
|
|
50
|
-
"EnumerationCode" => "MDC_P044",
|
|
51
|
-
"RelationType" => "MDC_P200",
|
|
52
|
-
"RelationDomain" => "MDC_P201",
|
|
53
|
-
"FunctionDomain" => "MDC_P202",
|
|
54
|
-
"FunctionCodomain" => "MDC_P203",
|
|
55
|
-
"Formula" => "MDC_P204",
|
|
56
|
-
"FormulaLanguage" => "MDC_P205",
|
|
57
|
-
"FormulaExternalSolver" => "MDC_P206",
|
|
58
|
-
"TriggerEvent" => "MDC_P207",
|
|
59
|
-
"DomainElementType" => "MDC_P208",
|
|
60
|
-
"CodomainElementType" => "MDC_P209",
|
|
61
|
-
"Role" => "MDC_P210",
|
|
62
|
-
"Segment" => "MDC_P211",
|
|
63
|
-
"SuperRelation" => "MDC_P212",
|
|
64
|
-
}.freeze
|
|
20
|
+
NAME_TO_PROPERTY_ID = Opencdd::PropertyIds::IEC_COLUMN_NAMES
|
|
65
21
|
|
|
66
22
|
# Given a PROPERTY_NAME directive row (Array of name strings),
|
|
67
23
|
# returns a parallel Array of property IDs (or nil where the
|
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
|
|
@@ -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
|
|
@@ -22,6 +22,8 @@ module Opencdd
|
|
|
22
22
|
# Canonicalize a Parcel column ID. Splits language tags
|
|
23
23
|
# (<id>.<lang>) so they round-trip cleanly. Returns the
|
|
24
24
|
# canonical ID (or the input unchanged if no mapping exists).
|
|
25
|
+
# Language codes are normalized to ISO 639-1 via
|
|
26
|
+
# +Opencdd::Parcel::LanguageAliases.normalize+.
|
|
25
27
|
def self.canonical_id(raw_id)
|
|
26
28
|
return nil if raw_id.nil?
|
|
27
29
|
s = raw_id.to_s.strip
|
|
@@ -30,7 +32,7 @@ module Opencdd
|
|
|
30
32
|
base = match ? match.pre_match : s
|
|
31
33
|
lang = match && match[:lang]
|
|
32
34
|
canonical = VARIANT_TO_CANONICAL[base] || base
|
|
33
|
-
lang ? "#{canonical}.#{lang}" : canonical
|
|
35
|
+
lang ? "#{canonical}.#{Opencdd::Parcel::LanguageAliases.normalize(lang)}" : canonical
|
|
34
36
|
end
|
|
35
37
|
|
|
36
38
|
DIRECTIVE_ROWS = %w[
|
|
@@ -110,7 +112,7 @@ module Opencdd
|
|
|
110
112
|
|
|
111
113
|
DIRECTIVE_ROW_PREFIX = "#".freeze
|
|
112
114
|
|
|
113
|
-
attr_reader :columns, :columns_by_id, :column_directives
|
|
115
|
+
attr_reader :columns, :columns_by_id, :column_directives, :normalized_language_codes
|
|
114
116
|
|
|
115
117
|
def initialize
|
|
116
118
|
@columns = []
|
|
@@ -194,6 +196,7 @@ module Opencdd
|
|
|
194
196
|
@columns_by_id[col.property_id] = col
|
|
195
197
|
end
|
|
196
198
|
|
|
199
|
+
@normalized_language_codes = compute_normalized_language_codes.freeze
|
|
197
200
|
freeze
|
|
198
201
|
end
|
|
199
202
|
|
|
@@ -260,11 +263,34 @@ module Opencdd
|
|
|
260
263
|
next if v.nil?
|
|
261
264
|
s = v.to_s.strip
|
|
262
265
|
next if s.empty?
|
|
266
|
+
lang = Opencdd::Parcel::LanguageAliases.normalize(lang)
|
|
263
267
|
lang = "en" if lang.nil? || lang.empty?
|
|
264
268
|
h[lang] = s
|
|
265
269
|
end
|
|
266
270
|
h
|
|
267
271
|
end
|
|
272
|
+
|
|
273
|
+
# Audit trail of language-code normalizations applied at this
|
|
274
|
+
# schema's ingestion boundary. Returns a frozen Hash mapping each
|
|
275
|
+
# original (non-conformant) language code seen in the source to
|
|
276
|
+
# its ISO 639-1 equivalent. Empty when the source spoke ISO.
|
|
277
|
+
def compute_normalized_language_codes
|
|
278
|
+
seen = {}
|
|
279
|
+
@column_directives.each_key do |key|
|
|
280
|
+
_, lang = key.to_s.split(".", 2)
|
|
281
|
+
next if lang.nil? || lang.empty?
|
|
282
|
+
normalized = Opencdd::Parcel::LanguageAliases.normalize(lang)
|
|
283
|
+
seen[lang] = normalized if normalized != lang
|
|
284
|
+
end
|
|
285
|
+
@columns.each do |col|
|
|
286
|
+
next if col.raw_property_id.nil?
|
|
287
|
+
_, lang = col.raw_property_id.split(".", 2)
|
|
288
|
+
next if lang.nil? || lang.empty?
|
|
289
|
+
normalized = Opencdd::Parcel::LanguageAliases.normalize(lang)
|
|
290
|
+
seen[lang] = normalized if normalized != lang
|
|
291
|
+
end
|
|
292
|
+
seen
|
|
293
|
+
end
|
|
268
294
|
end
|
|
269
295
|
end
|
|
270
296
|
end
|
|
@@ -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/property.rb
CHANGED
|
@@ -69,13 +69,6 @@ module Opencdd
|
|
|
69
69
|
end
|
|
70
70
|
field :condition, synthetic: true do
|
|
71
71
|
@condition ||= Opencdd::Condition.parse(properties[Opencdd::PropertyIds::MDC_P028])
|
|
72
|
-
rescue ArgumentError
|
|
73
|
-
# IEC CDD occasionally stores a bare class-reference set in the
|
|
74
|
-
# condition column (e.g. "{0112/2///62683#ACE132}") instead of
|
|
75
|
-
# a `left OP right` boolean expression. Condition's grammar
|
|
76
|
-
# rejects that shape; treat it as nil rather than crashing the
|
|
77
|
-
# import. Tracked in TODO.full-cdd/17-condition-grammar-fix.md.
|
|
78
|
-
@condition = nil
|
|
79
72
|
end
|
|
80
73
|
field :formula, synthetic: true do
|
|
81
74
|
properties[Opencdd::PropertyIds::MDC_P027_1] ||
|
data/lib/opencdd/property_ids.rb
CHANGED
|
@@ -142,6 +142,58 @@ module Opencdd
|
|
|
142
142
|
# ontology registry). Kept here as an alias for back-compat.
|
|
143
143
|
PARCEL_VARIANT_TO_CANONICAL = Opencdd::Parcel::SheetSchema::VARIANT_TO_CANONICAL
|
|
144
144
|
|
|
145
|
+
# IEC 62656-1 defined PROPERTY_NAME directive column names mapped
|
|
146
|
+
# to canonical property IDs. Used by Opencdd::Entity::NameSynthesizer
|
|
147
|
+
# when a Parcel sheet omits the #PROPERTY_ID directive row and
|
|
148
|
+
# only carries #PROPERTY_NAME. Declared here so the raw IDs live
|
|
149
|
+
# in the lint-exempt SSOT instead of in the synthesis module.
|
|
150
|
+
IEC_COLUMN_NAMES = {
|
|
151
|
+
"Code" => "MDC_P001_5",
|
|
152
|
+
"Version" => "MDC_P002_1",
|
|
153
|
+
"Revision" => "MDC_P002_2",
|
|
154
|
+
"VersionInitiationDate" => "MDC_P003_1",
|
|
155
|
+
"VersionReleaseDate" => "MDC_P003_2",
|
|
156
|
+
"RevisionReleaseDate" => "MDC_P003_3",
|
|
157
|
+
"PreferredName" => "MDC_P004",
|
|
158
|
+
"SynonymousName" => "MDC_P007",
|
|
159
|
+
"ShortName" => "MDC_P005",
|
|
160
|
+
"Definition" => "MDC_P006",
|
|
161
|
+
"DefinitionSource" => "MDC_P006_1",
|
|
162
|
+
"Note" => "MDC_P008",
|
|
163
|
+
"Remark" => "MDC_P009",
|
|
164
|
+
"Drawing" => "MDC_P008_1",
|
|
165
|
+
"GUID" => "MDC_P066",
|
|
166
|
+
"TimeStamp" => "MDC_P067",
|
|
167
|
+
"ClassType" => "MDC_P011",
|
|
168
|
+
"Superclass" => "MDC_P010",
|
|
169
|
+
"IsCaseOf" => "MDC_P013",
|
|
170
|
+
"ApplicableProperties" => "MDC_P014",
|
|
171
|
+
"ImportedProperties" => "MDC_P090",
|
|
172
|
+
"SubClassSelection" => "MDC_P016",
|
|
173
|
+
"DataType" => "MDC_P022",
|
|
174
|
+
"ValueFormat" => "MDC_P024",
|
|
175
|
+
"DefinitionClass" => "MDC_P021",
|
|
176
|
+
"Unit" => "MDC_P041",
|
|
177
|
+
"Condition" => "MDC_P028",
|
|
178
|
+
"ListType" => "MDC_P046",
|
|
179
|
+
"CodeList" => "MDC_P044",
|
|
180
|
+
"TermList" => "MDC_P043",
|
|
181
|
+
"EnumerationCode" => "MDC_P044",
|
|
182
|
+
"RelationType" => "MDC_P200",
|
|
183
|
+
"RelationDomain" => "MDC_P201",
|
|
184
|
+
"FunctionDomain" => "MDC_P202",
|
|
185
|
+
"FunctionCodomain" => "MDC_P203",
|
|
186
|
+
"Formula" => "MDC_P204",
|
|
187
|
+
"FormulaLanguage" => "MDC_P205",
|
|
188
|
+
"FormulaExternalSolver" => "MDC_P206",
|
|
189
|
+
"TriggerEvent" => "MDC_P207",
|
|
190
|
+
"DomainElementType" => "MDC_P208",
|
|
191
|
+
"CodomainElementType" => "MDC_P209",
|
|
192
|
+
"Role" => "MDC_P210",
|
|
193
|
+
"Segment" => "MDC_P211",
|
|
194
|
+
"SuperRelation" => "MDC_P212",
|
|
195
|
+
}.freeze
|
|
196
|
+
|
|
145
197
|
REGISTRY.each_key do |id|
|
|
146
198
|
const_set(id, id.freeze) unless const_defined?(id, false)
|
|
147
199
|
end
|
|
@@ -14,6 +14,9 @@ module Opencdd
|
|
|
14
14
|
|
|
15
15
|
def call(value, _context)
|
|
16
16
|
return true if value.nil? || value.to_s.strip.empty?
|
|
17
|
+
# parse returns a boolean Condition or a Condition::ClassReference
|
|
18
|
+
# for bare-IRDI / bare-set conditions (IEC 62683 shape). Both are
|
|
19
|
+
# valid; only malformed input raises ArgumentError.
|
|
17
20
|
!Opencdd::Condition.parse(value.to_s).nil?
|
|
18
21
|
rescue ArgumentError
|
|
19
22
|
false
|
data/lib/opencdd/version.rb
CHANGED
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.2
|
|
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-07
|
|
11
|
+
date: 2026-08-07 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: roo
|
|
@@ -263,6 +263,7 @@ files:
|
|
|
263
263
|
- lib/opencdd/parcel/csv_writer.rb
|
|
264
264
|
- lib/opencdd/parcel/entity_manifest.rb
|
|
265
265
|
- lib/opencdd/parcel/flat_dir_reader.rb
|
|
266
|
+
- lib/opencdd/parcel/language_aliases.rb
|
|
266
267
|
- lib/opencdd/parcel/layout_detector.rb
|
|
267
268
|
- lib/opencdd/parcel/metadata.rb
|
|
268
269
|
- lib/opencdd/parcel/referenced_irdis.rb
|