opencdd 0.1.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 +7 -0
- data/CLAUDE.md +486 -0
- data/README.md +304 -0
- data/bin/lint-no-raw-mdc +41 -0
- data/lib/cdd.rb +11 -0
- data/lib/opencdd/alias_table.rb +52 -0
- data/lib/opencdd/cddal/ast.rb +151 -0
- data/lib/opencdd/cddal/builder.rb +374 -0
- data/lib/opencdd/cddal/fetcher/in_memory.rb +32 -0
- data/lib/opencdd/cddal/fetcher/net_http.rb +84 -0
- data/lib/opencdd/cddal/fetcher.rb +18 -0
- data/lib/opencdd/cddal/generated_parser.rb +805 -0
- data/lib/opencdd/cddal/lexer.rb +193 -0
- data/lib/opencdd/cddal/parser.rb +19 -0
- data/lib/opencdd/cddal/resolver.rb +100 -0
- data/lib/opencdd/cddal/serializer.rb +210 -0
- data/lib/opencdd/cddal/value_serializer.rb +60 -0
- data/lib/opencdd/cddal.rb +63 -0
- data/lib/opencdd/class_tree.rb +80 -0
- data/lib/opencdd/class_type.rb +33 -0
- data/lib/opencdd/codegen/ts.rb +185 -0
- data/lib/opencdd/codegen.rb +7 -0
- data/lib/opencdd/composition_tree.rb +119 -0
- data/lib/opencdd/condition.rb +120 -0
- data/lib/opencdd/data_type.rb +143 -0
- data/lib/opencdd/database.rb +719 -0
- data/lib/opencdd/effective_properties.rb +119 -0
- data/lib/opencdd/entity/field_reader.rb +141 -0
- data/lib/opencdd/entity/field_registry.rb +99 -0
- data/lib/opencdd/entity/version_history.rb +62 -0
- data/lib/opencdd/entity.rb +255 -0
- data/lib/opencdd/exporters/json.rb +235 -0
- data/lib/opencdd/exporters/mermaid.rb +62 -0
- data/lib/opencdd/exporters/yaml.rb +16 -0
- data/lib/opencdd/exporters.rb +9 -0
- data/lib/opencdd/guid.rb +27 -0
- data/lib/opencdd/instance_rule.rb +70 -0
- data/lib/opencdd/irdi.rb +128 -0
- data/lib/opencdd/klass.rb +230 -0
- data/lib/opencdd/languages.rb +70 -0
- data/lib/opencdd/meta_class.rb +274 -0
- data/lib/opencdd/model/entity_store.rb +85 -0
- data/lib/opencdd/model/yaml_database.rb +49 -0
- data/lib/opencdd/model/yaml_entity.rb +196 -0
- data/lib/opencdd/model.rb +13 -0
- data/lib/opencdd/parcel/csv_reader.rb +35 -0
- data/lib/opencdd/parcel/csv_writer.rb +114 -0
- data/lib/opencdd/parcel/entity_manifest.rb +119 -0
- data/lib/opencdd/parcel/flat_dir_reader.rb +134 -0
- data/lib/opencdd/parcel/layout_detector.rb +115 -0
- data/lib/opencdd/parcel/metadata.rb +112 -0
- data/lib/opencdd/parcel/referenced_irdis.rb +91 -0
- data/lib/opencdd/parcel/scrape_verifier.rb +122 -0
- data/lib/opencdd/parcel/selector.rb +115 -0
- data/lib/opencdd/parcel/sharded_dir_reader.rb +151 -0
- data/lib/opencdd/parcel/sheet.rb +287 -0
- data/lib/opencdd/parcel/sheet_emitter.rb +171 -0
- data/lib/opencdd/parcel/sheet_schema.rb +253 -0
- data/lib/opencdd/parcel/workbook.rb +172 -0
- data/lib/opencdd/parcel/workbook_reader.rb +200 -0
- data/lib/opencdd/parcel/writer.rb +185 -0
- data/lib/opencdd/parcel.rb +175 -0
- data/lib/opencdd/parse_helpers.rb +73 -0
- data/lib/opencdd/property.rb +120 -0
- data/lib/opencdd/property_data_element_type.rb +44 -0
- data/lib/opencdd/property_ids.rb +202 -0
- data/lib/opencdd/reader.rb +103 -0
- data/lib/opencdd/relation.rb +88 -0
- data/lib/opencdd/relation_tree.rb +74 -0
- data/lib/opencdd/relation_type.rb +58 -0
- data/lib/opencdd/structured_values.rb +183 -0
- data/lib/opencdd/unit.rb +16 -0
- data/lib/opencdd/validator/class_reference_rule.rb +77 -0
- data/lib/opencdd/validator/condition_rule.rb +27 -0
- data/lib/opencdd/validator/data_type_rule.rb +26 -0
- data/lib/opencdd/validator/enum_rule.rb +27 -0
- data/lib/opencdd/validator/format_rule.rb +54 -0
- data/lib/opencdd/validator/hierarchy_rule.rb +65 -0
- data/lib/opencdd/validator/irdi_rule.rb +57 -0
- data/lib/opencdd/validator/mandatory_rule.rb +25 -0
- data/lib/opencdd/validator/pattern_rule.rb +26 -0
- data/lib/opencdd/validator/reference_rule.rb +36 -0
- data/lib/opencdd/validator/rule.rb +65 -0
- data/lib/opencdd/validator/runner.rb +101 -0
- data/lib/opencdd/validator/set_rule.rb +41 -0
- data/lib/opencdd/validator/synonym_rule.rb +30 -0
- data/lib/opencdd/validator/type_rule.rb +102 -0
- data/lib/opencdd/validator/uniqueness_rule.rb +32 -0
- data/lib/opencdd/validator.rb +68 -0
- data/lib/opencdd/value_format.rb +71 -0
- data/lib/opencdd/value_list.rb +42 -0
- data/lib/opencdd/value_term.rb +24 -0
- data/lib/opencdd/version.rb +5 -0
- data/lib/opencdd/view_control.rb +10 -0
- data/lib/opencdd/visitor.rb +93 -0
- data/lib/opencdd.rb +57 -0
- metadata +325 -0
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module Opencdd
|
|
6
|
+
module Exporters
|
|
7
|
+
class Json < Opencdd::Visitor
|
|
8
|
+
attr_reader :nodes
|
|
9
|
+
|
|
10
|
+
def initialize
|
|
11
|
+
super
|
|
12
|
+
@nodes = []
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def to_json(database, pretty: true)
|
|
16
|
+
reset!
|
|
17
|
+
@nodes.clear
|
|
18
|
+
visit_database(database)
|
|
19
|
+
pretty ? JSON.pretty_generate(@nodes) : JSON.generate(@nodes)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def visit_database(database)
|
|
23
|
+
@database = database
|
|
24
|
+
super
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def visit_class(klass)
|
|
28
|
+
@nodes << class_node(klass)
|
|
29
|
+
super
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def visit_property(prop)
|
|
33
|
+
@nodes << property_node(prop)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def visit_unit(unit)
|
|
37
|
+
@nodes << unit_node(unit)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def visit_value_list(vl)
|
|
41
|
+
@nodes << value_list_node(vl)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def visit_value_term(vt)
|
|
45
|
+
@nodes << value_term_node(vt)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def visit_relation(rel)
|
|
49
|
+
@nodes << relation_node(rel)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def visit_view_control(vc)
|
|
53
|
+
@nodes << view_control_node(vc)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
private
|
|
57
|
+
|
|
58
|
+
# ─────────────────────────────────────────────────────────────
|
|
59
|
+
# Open/closed payload builders
|
|
60
|
+
#
|
|
61
|
+
# Each per-type _node method is a thin wrapper that adds `type:`
|
|
62
|
+
# and any type-specific computed fields (e.g. Property's value_list
|
|
63
|
+
# cross-link). The bulk of every entity's payload comes from
|
|
64
|
+
# `entity_payload`, which iterates the field DSL registry
|
|
65
|
+
# (Opencdd::Entity::FieldRegistry.fields_for) — adding a field to
|
|
66
|
+
# the model is a single `field` declaration; no edits here.
|
|
67
|
+
# ─────────────────────────────────────────────────────────────
|
|
68
|
+
|
|
69
|
+
def class_node(klass)
|
|
70
|
+
entity_payload(klass).merge(type: "class").compact
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def property_node(prop)
|
|
74
|
+
entity_payload(prop).merge(
|
|
75
|
+
type: "property",
|
|
76
|
+
data_type: prop.parsed_data_type&.to_s,
|
|
77
|
+
value_list: value_list_irdi_of(prop),
|
|
78
|
+
).compact
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def unit_node(unit)
|
|
82
|
+
entity_payload(unit).merge(type: "unit").compact
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def value_list_node(vl)
|
|
86
|
+
entity_payload(vl).merge(type: "value_list").compact
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def value_term_node(vt)
|
|
90
|
+
entity_payload(vt).merge(type: "value_term").compact
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def relation_node(rel)
|
|
94
|
+
entity_payload(rel).merge(type: "relation").compact
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def view_control_node(vc)
|
|
98
|
+
entity_payload(vc).merge(type: "view_control").compact
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Iterates every declared field on the entity's class (walking
|
|
102
|
+
# the ancestor chain via FieldRegistry.fields_for). Each field's
|
|
103
|
+
# value is read by name (synthetic fields call their custom
|
|
104
|
+
# reader; pure fields go through FieldReader's typed coercion).
|
|
105
|
+
#
|
|
106
|
+
# Serialization is driven by the field's value_kind so the wire
|
|
107
|
+
# shape stays consistent: IRDI → string, set_of_refs → array of
|
|
108
|
+
# strings, synonym_pairs → array of {lang, name}, etc.
|
|
109
|
+
#
|
|
110
|
+
# Deduplicates by property_id: when two field names alias the
|
|
111
|
+
# same MDC_P### (e.g. source_document and source_document_of_definition),
|
|
112
|
+
# only the first-seen declaration is emitted. Declaration order
|
|
113
|
+
# is base-class first, so the canonical name lives on Entity.
|
|
114
|
+
def entity_payload(entity)
|
|
115
|
+
# Baseline identity fields. Always present (when value is non-nil).
|
|
116
|
+
# These mirror the existing wire shape and are emitted even
|
|
117
|
+
# though they're not DSL field declarations (the model's `irdi`
|
|
118
|
+
# accessor must return the Opencdd::IRDI object, not a string).
|
|
119
|
+
payload = {
|
|
120
|
+
irdi: entity.irdi&.to_s,
|
|
121
|
+
code: entity.code,
|
|
122
|
+
}.compact
|
|
123
|
+
|
|
124
|
+
seen_property_ids = Set.new
|
|
125
|
+
|
|
126
|
+
Opencdd::Entity::FieldRegistry.fields_for(entity.class).each do |field|
|
|
127
|
+
next if field.property_id && !seen_property_ids.add?(field.property_id)
|
|
128
|
+
|
|
129
|
+
value = entity.public_send(field.name)
|
|
130
|
+
next if value.nil?
|
|
131
|
+
|
|
132
|
+
serialized = serialize_value(value, field)
|
|
133
|
+
next if serialized.nil?
|
|
134
|
+
|
|
135
|
+
payload[field.wire_name] = serialized
|
|
136
|
+
|
|
137
|
+
# For multilingual fields, also emit a language map keyed
|
|
138
|
+
# as <wire_name>_ml. Scans entity.properties for every
|
|
139
|
+
# <property_id>.<lang> key. The browser can use this for
|
|
140
|
+
# a language switcher without parsing raw_properties.
|
|
141
|
+
if field.multilingual? && field.property_id
|
|
142
|
+
ml = build_language_map(entity, field)
|
|
143
|
+
payload["#{field.wire_name}_ml"] = ml if ml && !ml.empty?
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
payload
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def serialize_value(value, field)
|
|
150
|
+
# raw_properties: emit the full hash as-is, no coercion.
|
|
151
|
+
# This preserves every key from the .xls, including
|
|
152
|
+
# multilingual variants and C### workbook-specific codes.
|
|
153
|
+
return value if field.name == :raw_properties && value.is_a?(Hash)
|
|
154
|
+
|
|
155
|
+
case field.value_kind
|
|
156
|
+
when :synonym_pairs
|
|
157
|
+
return nil if value.nil?
|
|
158
|
+
value.map { |lang, name| { lang: lang, name: name } }
|
|
159
|
+
when :irdi, :identifier_ref, :class_ref
|
|
160
|
+
return nil if value.nil?
|
|
161
|
+
value.to_s
|
|
162
|
+
when :set_of_refs
|
|
163
|
+
return nil if value.nil?
|
|
164
|
+
value.map { |irdi| irdi.to_s }
|
|
165
|
+
when :string_list
|
|
166
|
+
return nil if value.nil?
|
|
167
|
+
value
|
|
168
|
+
else
|
|
169
|
+
serialize_basic(value)
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def serialize_basic(value)
|
|
174
|
+
case value
|
|
175
|
+
when Opencdd::IRDI then value.to_s
|
|
176
|
+
when Array
|
|
177
|
+
# Preserve empty arrays — callers use them as "no entries"
|
|
178
|
+
# signals (e.g. sub_class_selection: [] on a class with no
|
|
179
|
+
# composition children). The payload's final .compact only
|
|
180
|
+
# drops nils, not empties.
|
|
181
|
+
value.map { |v| serialize_basic(v) }
|
|
182
|
+
when Struct
|
|
183
|
+
hash = value.to_h.transform_values { |v| serialize_basic(v) }
|
|
184
|
+
hash.compact!
|
|
185
|
+
hash.empty? ? nil : hash
|
|
186
|
+
when Opencdd::Entity::VersionHistory
|
|
187
|
+
return nil if value.empty?
|
|
188
|
+
value.entries.map { |e| serialize_basic(e) }
|
|
189
|
+
when String, Integer, Float, TrueClass, FalseClass, NilClass, Symbol
|
|
190
|
+
value
|
|
191
|
+
else
|
|
192
|
+
# Value objects (Opencdd::ClassType, Opencdd::Condition,
|
|
193
|
+
# Opencdd::ValueFormat, Opencdd::PropertyDataTypeElement, etc.)
|
|
194
|
+
# serialize via their canonical to_s. Keeps the wire shape
|
|
195
|
+
# stable without per-type branching here.
|
|
196
|
+
value.to_s
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# Scans +entity.properties+ for +<property_id>.<lang>+ keys
|
|
201
|
+
# and builds a +{ lang => value }+ hash. Used for multilingual
|
|
202
|
+
# fields where the exporter emits a +<wire_name>_ml+ map
|
|
203
|
+
# alongside the source-language string.
|
|
204
|
+
def build_language_map(entity, field)
|
|
205
|
+
return nil unless field.property_id
|
|
206
|
+
prefix = "#{field.property_id}."
|
|
207
|
+
map = {}
|
|
208
|
+
entity.properties.each do |key, val|
|
|
209
|
+
next unless key.start_with?(prefix)
|
|
210
|
+
lang = key.sub(prefix, "")
|
|
211
|
+
next unless val && !val.to_s.strip.empty?
|
|
212
|
+
next unless lang =~ /\A[a-z]{2}(-[a-z0-9]+)?\z/i
|
|
213
|
+
map[lang] = val
|
|
214
|
+
end
|
|
215
|
+
bare = entity.properties[field.property_id]
|
|
216
|
+
if bare && map.empty?
|
|
217
|
+
map["en"] = bare
|
|
218
|
+
end
|
|
219
|
+
map
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
# when a property's data type is an enumeration, its values come
|
|
224
|
+
# from a named value list linked via a predication relation.
|
|
225
|
+
# The Database resolves this at finalize time; we emit the IRDI
|
|
226
|
+
# so the browser can render the cross-link without re-walking
|
|
227
|
+
# the relations.
|
|
228
|
+
def value_list_irdi_of(prop)
|
|
229
|
+
return nil unless @database
|
|
230
|
+
vl = @database.value_list_of(prop)
|
|
231
|
+
vl&.irdi&.to_s
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Opencdd
|
|
4
|
+
module Exporters
|
|
5
|
+
class Mermaid < Opencdd::Visitor
|
|
6
|
+
attr_reader :lines
|
|
7
|
+
|
|
8
|
+
def initialize
|
|
9
|
+
super
|
|
10
|
+
@lines = ["classDiagram"]
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def to_diagram(database)
|
|
14
|
+
reset!
|
|
15
|
+
@lines = ["classDiagram"]
|
|
16
|
+
visit_classes(database)
|
|
17
|
+
@lines.uniq.join("\n")
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def visit_class(klass)
|
|
21
|
+
emit_class_block(klass)
|
|
22
|
+
parent_ref = klass.parent_irdi || klass.superclass_irdi
|
|
23
|
+
if parent_ref
|
|
24
|
+
parent = klass.database&.find(parent_ref)
|
|
25
|
+
if parent
|
|
26
|
+
safe = mermaid_id(parent)
|
|
27
|
+
child = mermaid_id(klass)
|
|
28
|
+
@lines << " #{safe} <|-- #{child}"
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
klass.is_case_of_irdis.each do |ref|
|
|
32
|
+
target = klass.database&.find(ref)
|
|
33
|
+
next unless target
|
|
34
|
+
@lines << " #{mermaid_id(target)} <.. #{mermaid_id(klass)} : is_case_of"
|
|
35
|
+
end
|
|
36
|
+
super
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
def emit_class_block(klass)
|
|
42
|
+
id = mermaid_id(klass)
|
|
43
|
+
@lines << " class #{id} {"
|
|
44
|
+
@lines << " <<#{klass.class_type || 'ITEM_CLASS'}>>"
|
|
45
|
+
@lines << " +code #{klass.code}"
|
|
46
|
+
@lines << " }"
|
|
47
|
+
note = klass.preferred_name
|
|
48
|
+
@lines << " #{id} : #{quote_label(note)}" if note && !note.empty?
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def mermaid_id(klass)
|
|
52
|
+
code = klass.code&.to_s
|
|
53
|
+
return "Class_#{klass.irdi.to_s.gsub(/[^A-Za-z0-9]/, "_")}" if code.nil? || code.empty?
|
|
54
|
+
code.gsub(/[^A-Za-z0-9_]/, "_")
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def quote_label(text)
|
|
58
|
+
text.to_s.include?(":") ? "\"#{text}\"" : text.to_s
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
data/lib/opencdd/guid.rb
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "securerandom"
|
|
4
|
+
|
|
5
|
+
module Opencdd
|
|
6
|
+
module GUID
|
|
7
|
+
PATTERN = /\A[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\z/i.freeze
|
|
8
|
+
|
|
9
|
+
def self.generate
|
|
10
|
+
SecureRandom.uuid
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def self.valid?(value)
|
|
14
|
+
return false unless value.is_a?(String)
|
|
15
|
+
PATTERN.match?(value)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def self.set_on(entity)
|
|
19
|
+
guid = generate
|
|
20
|
+
# Use the field-access seam so writes go through canonical-id
|
|
21
|
+
# resolution. Keeps GUID writes consistent with reads and
|
|
22
|
+
# avoids bypassing the Field DSL.
|
|
23
|
+
entity.write_property!(Opencdd::PropertyIds::MDC_P066, guid)
|
|
24
|
+
guid
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Opencdd
|
|
4
|
+
class InstanceRule
|
|
5
|
+
Group = Struct.new(:name, :values_by_property, keyword_init: true)
|
|
6
|
+
Exception = Struct.new(:group_name, :line_id, keyword_init: true)
|
|
7
|
+
|
|
8
|
+
attr_reader :klass, :groups, :exceptions
|
|
9
|
+
|
|
10
|
+
def initialize(klass:, groups:, exceptions: [])
|
|
11
|
+
@klass = klass
|
|
12
|
+
@groups = groups
|
|
13
|
+
@exceptions = exceptions
|
|
14
|
+
freeze
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def expand
|
|
18
|
+
return [] if @groups.empty?
|
|
19
|
+
|
|
20
|
+
per_group = @groups.map { |g| expand_group(g) }
|
|
21
|
+
return [] if per_group.any?(&:empty?)
|
|
22
|
+
|
|
23
|
+
product = per_group.reduce do |acc, rows|
|
|
24
|
+
acc.product(rows).map { |a, b| a.merge(b) }
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
product.reject { |row| exception_match?(row) }
|
|
28
|
+
.map { |row| strip_internal(row) }
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def expand_group(group)
|
|
34
|
+
values = group.values_by_property.transform_values do |list|
|
|
35
|
+
Array(list)
|
|
36
|
+
end
|
|
37
|
+
return [] if values.empty?
|
|
38
|
+
|
|
39
|
+
property_ids = values.keys
|
|
40
|
+
length = values.values.map(&:length).max || 0
|
|
41
|
+
length.times.map do |i|
|
|
42
|
+
row = {}
|
|
43
|
+
property_ids.each do |pid|
|
|
44
|
+
v = values[pid][i]
|
|
45
|
+
row[pid] = v unless v.nil?
|
|
46
|
+
end
|
|
47
|
+
row["__line_id__"] = "LINE#{i + 1}"
|
|
48
|
+
row["__group_name__"] = group.name
|
|
49
|
+
row
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def exception_match?(row)
|
|
54
|
+
return false if @exceptions.empty?
|
|
55
|
+
group_name = row["__group_name__"]
|
|
56
|
+
line_id = row["__line_id__"]
|
|
57
|
+
@exceptions.any? do |ex|
|
|
58
|
+
ex.group_name == group_name && ex.line_id == line_id
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def strip_internal(row)
|
|
63
|
+
out = row.dup
|
|
64
|
+
out.delete("__line_id__")
|
|
65
|
+
out.delete("__group_name__")
|
|
66
|
+
out.delete("__group_indices__")
|
|
67
|
+
out
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
data/lib/opencdd/irdi.rb
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Opencdd
|
|
4
|
+
class IRDI
|
|
5
|
+
FULL_REGEX = %r{
|
|
6
|
+
\A
|
|
7
|
+
(?<registrant>[^/#\s]+)
|
|
8
|
+
/
|
|
9
|
+
(?<semantic>[^/#\s]*)
|
|
10
|
+
///
|
|
11
|
+
(?<scheme>[^/#\s]+)
|
|
12
|
+
\#
|
|
13
|
+
(?<code>[^#\s]+)
|
|
14
|
+
(?:\#\#(?<smver>\d+))?
|
|
15
|
+
\z
|
|
16
|
+
}x
|
|
17
|
+
|
|
18
|
+
SHORT_REGEX = /\A[^#\/\s]+\z/
|
|
19
|
+
|
|
20
|
+
TREE_REGEX = %r{
|
|
21
|
+
\A
|
|
22
|
+
(?<registrant>[^-]+)
|
|
23
|
+
-
|
|
24
|
+
(?<semantic>[^-]*)
|
|
25
|
+
---
|
|
26
|
+
(?<scheme>[^-]+)
|
|
27
|
+
%23
|
|
28
|
+
(?<code>[^%\s]+)
|
|
29
|
+
\z
|
|
30
|
+
}x
|
|
31
|
+
|
|
32
|
+
attr_reader :registrant, :semantic, :scheme, :code, :version
|
|
33
|
+
|
|
34
|
+
def self.parse(value)
|
|
35
|
+
return nil if value.nil?
|
|
36
|
+
s = value.to_s.strip
|
|
37
|
+
return nil if s.empty?
|
|
38
|
+
|
|
39
|
+
if (m = FULL_REGEX.match(s)) then from_match(m)
|
|
40
|
+
elsif (m = TREE_REGEX.match(s)) then from_match(m)
|
|
41
|
+
elsif SHORT_REGEX.match?(s) then from_short(s)
|
|
42
|
+
elsif s.include?("#") && (m = FULL_REGEX.match(s.gsub(/\s+/, ""))) then from_match(m)
|
|
43
|
+
else from_short(s)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def self.from_short(code)
|
|
48
|
+
new(registrant: nil, semantic: nil, scheme: nil, code: code.to_s)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def self.from_match(m)
|
|
52
|
+
caps = m.named_captures.transform_keys(&:to_sym)
|
|
53
|
+
new(
|
|
54
|
+
registrant: caps[:registrant],
|
|
55
|
+
semantic: caps[:semantic],
|
|
56
|
+
scheme: caps[:scheme],
|
|
57
|
+
code: caps[:code],
|
|
58
|
+
version: caps[:smver],
|
|
59
|
+
)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def initialize(registrant:, semantic:, scheme:, code:, version: nil)
|
|
63
|
+
@registrant = registrant&.to_s
|
|
64
|
+
@semantic = semantic&.to_s
|
|
65
|
+
@scheme = scheme&.to_s
|
|
66
|
+
@code = code.to_s
|
|
67
|
+
@version = version&.to_s
|
|
68
|
+
freeze
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def full?
|
|
72
|
+
!@registrant.nil?
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def short?
|
|
76
|
+
@registrant.nil?
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
alias_method :short, :code
|
|
80
|
+
|
|
81
|
+
def sheetmap_version
|
|
82
|
+
@version
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def to_s
|
|
86
|
+
return @code if short?
|
|
87
|
+
"#{@registrant}/#{@semantic}///#{@scheme}##{@code}"
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
alias_method :to_str, :to_s
|
|
91
|
+
|
|
92
|
+
def to_tree_path
|
|
93
|
+
return @code if short?
|
|
94
|
+
"#{@registrant}-#{@semantic}---#{@scheme}%23#{@code}"
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def with_code(new_code)
|
|
98
|
+
self.class.new(
|
|
99
|
+
registrant: @registrant,
|
|
100
|
+
semantic: @semantic,
|
|
101
|
+
scheme: @scheme,
|
|
102
|
+
code: new_code.to_s,
|
|
103
|
+
version: @version,
|
|
104
|
+
)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def eql?(other)
|
|
108
|
+
other.is_a?(Opencdd::IRDI) && to_s == other.to_s
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def hash
|
|
112
|
+
to_s.hash
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def ==(other)
|
|
116
|
+
eql?(other)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def inspect
|
|
120
|
+
"#<Opencdd::IRDI #{to_s.inspect}>"
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def self.coerce(value)
|
|
124
|
+
return value if value.is_a?(Opencdd::IRDI)
|
|
125
|
+
parse(value)
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|