genericode 0.1.0 → 0.1.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/.rubocop.yml +27 -1
- data/.rubocop_todo.yml +66 -0
- data/README.adoc +282 -0
- data/exe/genericode +7 -0
- data/lib/genericode/agency.rb +25 -5
- data/lib/genericode/annotation.rb +10 -4
- data/lib/genericode/any_other_content.rb +2 -2
- data/lib/genericode/any_other_language_content.rb +3 -3
- data/lib/genericode/canonical_uri.rb +32 -0
- data/lib/genericode/cli/code_lister.rb +67 -0
- data/lib/genericode/cli/code_lookup.rb +22 -0
- data/lib/genericode/cli/commands.rb +76 -0
- data/lib/genericode/cli/converter.rb +35 -0
- data/lib/genericode/cli/validator.rb +21 -0
- data/lib/genericode/cli.rb +8 -0
- data/lib/genericode/code_list.rb +257 -9
- data/lib/genericode/code_list_ref.rb +9 -6
- data/lib/genericode/code_list_set.rb +39 -2
- data/lib/genericode/code_list_set_ref.rb +9 -6
- data/lib/genericode/column.rb +37 -11
- data/lib/genericode/column_ref.rb +7 -7
- data/lib/genericode/column_set.rb +3 -3
- data/lib/genericode/column_set_ref.rb +4 -4
- data/lib/genericode/data.rb +5 -5
- data/lib/genericode/data_restrictions.rb +3 -3
- data/lib/genericode/datatype_facet.rb +10 -7
- data/lib/genericode/general_identifier.rb +6 -6
- data/lib/genericode/identification.rb +53 -11
- data/lib/genericode/json/canonical_uri_mixin.rb +17 -0
- data/lib/genericode/json/short_name_mixin.rb +17 -0
- data/lib/genericode/key.rb +34 -9
- data/lib/genericode/key_column_ref.rb +3 -3
- data/lib/genericode/key_ref.rb +6 -6
- data/lib/genericode/long_name.rb +17 -7
- data/lib/genericode/mime_typed_uri.rb +5 -5
- data/lib/genericode/row.rb +2 -2
- data/lib/genericode/short_name.rb +10 -5
- data/lib/genericode/simple_code_list.rb +2 -2
- data/lib/genericode/simple_value.rb +3 -3
- data/lib/genericode/utils.rb +50 -0
- data/lib/genericode/value.rb +5 -4
- data/lib/genericode/version.rb +1 -1
- data/lib/genericode.rb +12 -0
- data/oasis-reqs-implemented.md +31 -0
- data/oasis-requirements-1.0.md +56 -0
- metadata +28 -54
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "thor"
|
4
|
+
require_relative "validator"
|
5
|
+
require_relative "converter"
|
6
|
+
require_relative "code_lister"
|
7
|
+
require_relative "code_lookup"
|
8
|
+
|
9
|
+
module Genericode
|
10
|
+
module Cli
|
11
|
+
class Commands < Thor
|
12
|
+
desc "convert INPUT OUTPUT", "Convert between Genericode XML and JSON formats"
|
13
|
+
|
14
|
+
def convert(input, output)
|
15
|
+
puts "Conversion successful." if Converter.convert(input, output)
|
16
|
+
rescue Error => e
|
17
|
+
puts "Conversion failed: #{e.message}"
|
18
|
+
end
|
19
|
+
|
20
|
+
desc "validate FILE", "Validate a Genericode file"
|
21
|
+
option :verbose, type: :boolean, desc: "Show detailed validation results"
|
22
|
+
|
23
|
+
def validate(file)
|
24
|
+
code_list = CodeList.from_file(file)
|
25
|
+
if options[:verbose]
|
26
|
+
results = code_list.validate_verbose
|
27
|
+
if results.empty?
|
28
|
+
puts "File is valid."
|
29
|
+
else
|
30
|
+
puts "File is invalid. Issues found:"
|
31
|
+
results.each do |error|
|
32
|
+
puts " [#{error[:code]}] #{error[:message]}"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
elsif code_list.valid?
|
36
|
+
puts "File is valid."
|
37
|
+
else
|
38
|
+
puts "File is invalid."
|
39
|
+
end
|
40
|
+
rescue Error => e
|
41
|
+
puts "Validation failed: #{e.message}"
|
42
|
+
end
|
43
|
+
|
44
|
+
desc "list_codes FILE", "List all codes and their associated data in a Genericode file"
|
45
|
+
option :format, type: :string, default: "tsv", enum: %w[tsv table], desc: "Output format (tsv or table)"
|
46
|
+
option :output, type: :string, desc: "Output file path (default: stdout)"
|
47
|
+
|
48
|
+
def list_codes(file)
|
49
|
+
format = (options[:format] || "tsv").to_sym
|
50
|
+
result = CodeLister.list_codes(file, format: format)
|
51
|
+
|
52
|
+
if options[:output]
|
53
|
+
File.write(options[:output], result)
|
54
|
+
puts "Codes listed in #{options[:output]}"
|
55
|
+
else
|
56
|
+
puts result
|
57
|
+
end
|
58
|
+
rescue Error => e
|
59
|
+
puts "Listing codes failed: #{e.message}"
|
60
|
+
end
|
61
|
+
|
62
|
+
desc "lookup FILE PATH", "Look up a particular code using Genericode path"
|
63
|
+
|
64
|
+
def lookup(file, path)
|
65
|
+
result = CodeLookup.lookup(file, path)
|
66
|
+
if result.is_a?(Hash)
|
67
|
+
result.each { |k, v| puts "#{k}: #{v}" }
|
68
|
+
else
|
69
|
+
puts result
|
70
|
+
end
|
71
|
+
rescue Error => e
|
72
|
+
puts "Lookup failed: #{e.message}"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Genericode
|
4
|
+
module Cli
|
5
|
+
class Converter
|
6
|
+
def self.convert(input_path, output_path)
|
7
|
+
input_format = File.extname(input_path)
|
8
|
+
output_format = File.extname(output_path)
|
9
|
+
|
10
|
+
raise Error, "Invalid input format" unless [".gc", ".gcj"].include?(input_format)
|
11
|
+
raise Error, "Invalid output format" unless [".gc", ".gcj"].include?(output_format)
|
12
|
+
raise Error, "Input and output formats are the same" if input_format == output_format
|
13
|
+
|
14
|
+
# begin
|
15
|
+
code_list = CodeList.from_file(input_path)
|
16
|
+
|
17
|
+
result = if output_format == ".gcj"
|
18
|
+
code_list.to_json
|
19
|
+
else
|
20
|
+
code_list.to_xml
|
21
|
+
end
|
22
|
+
|
23
|
+
File.write(output_path, result)
|
24
|
+
true
|
25
|
+
# rescue JSON::ParserError => e
|
26
|
+
# raise Error, "Invalid JSON in input file: #{e.message}"
|
27
|
+
# rescue Shale::ParseError => e
|
28
|
+
# raise Error, "Invalid XML in input file: #{e.message}"
|
29
|
+
# rescue StandardError => e
|
30
|
+
# raise Error, "Conversion error: #{e.message}"
|
31
|
+
# end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Genericode
|
4
|
+
module Cli
|
5
|
+
class Validator
|
6
|
+
def self.validate(file_path)
|
7
|
+
raise Error, "File does not exist" unless File.exist?(file_path)
|
8
|
+
raise Error, "Invalid file format" unless file_path.end_with?(".gc", ".gcj")
|
9
|
+
|
10
|
+
code_list = CodeList.from_file(file_path)
|
11
|
+
|
12
|
+
raise Error, "No columns defined" if code_list.column_set.nil? || code_list.column_set.column.empty?
|
13
|
+
raise Error, "No rows defined" if code_list.simple_code_list.nil? || code_list.simple_code_list.row.empty?
|
14
|
+
|
15
|
+
raise Error, "Invalid Genericode structure" unless code_list.valid?
|
16
|
+
|
17
|
+
true
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/lib/genericode/code_list.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "
|
3
|
+
require "lutaml/model"
|
4
|
+
require "uri"
|
4
5
|
|
5
6
|
require_relative "annotation"
|
6
7
|
require_relative "column_set"
|
@@ -9,35 +10,282 @@ require_relative "identification"
|
|
9
10
|
require_relative "simple_code_list"
|
10
11
|
|
11
12
|
module Genericode
|
12
|
-
class CodeList <
|
13
|
+
class CodeList < Lutaml::Model::Serializable
|
13
14
|
attribute :annotation, Annotation
|
14
15
|
attribute :identification, Identification
|
15
16
|
attribute :column_set, ColumnSet
|
16
17
|
attribute :column_set_ref, ColumnSetRef
|
17
18
|
attribute :simple_code_list, SimpleCodeList
|
18
|
-
|
19
|
+
|
20
|
+
def self.from_file(file_path)
|
21
|
+
content = File.read(file_path)
|
22
|
+
if file_path.end_with?(".gc")
|
23
|
+
from_xml(content)
|
24
|
+
elsif file_path.end_with?(".gcj")
|
25
|
+
from_json(content)
|
26
|
+
else
|
27
|
+
raise Error, "Unsupported file format. Expected .gc or .gcj file."
|
28
|
+
end
|
29
|
+
end
|
19
30
|
|
20
31
|
json do
|
21
32
|
map "Annotation", to: :annotation
|
22
33
|
map "Identification", to: :identification
|
23
|
-
map "
|
34
|
+
map "Columns", to: :column_set, with: { from: :column_set_from_json, to: :column_set_to_json }
|
24
35
|
map "ColumnSetRef", to: :column_set_ref
|
25
|
-
map "
|
36
|
+
map "Keys", to: :key, delegate: :column_set, with: { from: :key_from_json, to: :key_to_json }
|
37
|
+
map "Codes", to: :simple_code_list, with: { from: :simple_code_list_from_json, to: :simple_code_list_to_json }
|
38
|
+
end
|
39
|
+
|
40
|
+
def column_set_from_json(model, value)
|
41
|
+
model.column_set = ColumnSet.of_json({ "Column" => value })
|
42
|
+
end
|
43
|
+
|
44
|
+
def column_set_to_json(model, doc)
|
45
|
+
doc["Columns"] = Column.as_json(model.column_set.column)
|
46
|
+
end
|
47
|
+
|
48
|
+
def key_from_json(model, value)
|
49
|
+
model.column_set.key = Key.of_json(value)
|
50
|
+
end
|
51
|
+
|
52
|
+
def key_to_json(model, doc)
|
53
|
+
doc["Keys"] = Key.as_json(model.column_set.key)
|
54
|
+
end
|
55
|
+
|
56
|
+
def simple_code_list_from_json(model, value)
|
57
|
+
rows = value.map do |x|
|
58
|
+
values = x.map do |k, v|
|
59
|
+
Value.new(column_ref: k, simple_value: SimpleValue.new(content: v))
|
60
|
+
end
|
61
|
+
|
62
|
+
Row.new(value: values)
|
63
|
+
end
|
64
|
+
|
65
|
+
model.simple_code_list = SimpleCodeList.new(row: rows)
|
66
|
+
end
|
67
|
+
|
68
|
+
def simple_code_list_to_json(model, doc)
|
69
|
+
doc["Codes"] = model.simple_code_list.row.map do |row|
|
70
|
+
row.value.to_h { |v| [v.column_ref, v.simple_value.content] }
|
71
|
+
end
|
26
72
|
end
|
27
73
|
|
28
74
|
xml do
|
29
75
|
root "CodeList"
|
30
76
|
namespace "http://docs.oasis-open.org/codelist/ns/genericode/1.0/", "gc"
|
31
77
|
|
32
|
-
map_attribute "schemaLocation", to: :schema_location,
|
33
|
-
namespace: "http://www.w3.org/2001/XMLSchema-instance",
|
34
|
-
prefix: "xsi"
|
35
|
-
|
36
78
|
map_element "Annotation", to: :annotation, prefix: nil, namespace: nil
|
37
79
|
map_element "Identification", to: :identification, prefix: nil, namespace: nil
|
38
80
|
map_element "ColumnSet", to: :column_set, prefix: nil, namespace: nil
|
39
81
|
map_element "ColumnSetRef", to: :column_set_ref, prefix: nil, namespace: nil
|
40
82
|
map_element "SimpleCodeList", to: :simple_code_list, prefix: nil, namespace: nil
|
41
83
|
end
|
84
|
+
|
85
|
+
def lookup(path)
|
86
|
+
parts = path.split(">")
|
87
|
+
conditions = parts[0].split(",").to_h { |c| c.split(":") }
|
88
|
+
target_column = parts[1]
|
89
|
+
|
90
|
+
result = simple_code_list.row.find do |row|
|
91
|
+
conditions.all? do |col, value|
|
92
|
+
column = column_set.column.find { |c| c.short_name.content.downcase == col.downcase }
|
93
|
+
raise Error, "Column not found: #{col}" unless column
|
94
|
+
|
95
|
+
row_value = row.value.find { |v| v.column_ref == column.id }&.simple_value&.content
|
96
|
+
row_value == value
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
raise Error, "No matching row found for path: #{path}" unless result
|
101
|
+
|
102
|
+
if target_column
|
103
|
+
column = column_set.column.find { |c| c.short_name.content.downcase == target_column.downcase }
|
104
|
+
raise Error, "Target column not found: #{target_column}" unless column
|
105
|
+
|
106
|
+
result.value.find { |v| v.column_ref == column.id }&.simple_value&.content
|
107
|
+
else
|
108
|
+
result.value.to_h do |v|
|
109
|
+
[column_set.column.find do |c|
|
110
|
+
c.id == v.column_ref
|
111
|
+
end.short_name.content, v.simple_value.content,]
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def valid?
|
117
|
+
validate_verbose.empty?
|
118
|
+
end
|
119
|
+
|
120
|
+
def validate_verbose
|
121
|
+
errors = []
|
122
|
+
|
123
|
+
# Rule 1: ColumnSet presence
|
124
|
+
if column_set.nil? || column_set.column.empty?
|
125
|
+
errors << { code: "MISSING_COLUMN_SET",
|
126
|
+
message: "ColumnSet is missing or empty", }
|
127
|
+
end
|
128
|
+
|
129
|
+
# Rule 2: SimpleCodeList presence
|
130
|
+
if simple_code_list.nil? || simple_code_list.row.empty?
|
131
|
+
errors << { code: "MISSING_SIMPLE_CODE_LIST",
|
132
|
+
message: "SimpleCodeList is missing or empty", }
|
133
|
+
end
|
134
|
+
|
135
|
+
# Rule 3: Unique column IDs
|
136
|
+
column_ids = column_set&.column&.map(&:id) || []
|
137
|
+
if column_ids.uniq.length != column_ids.length
|
138
|
+
errors << { code: "DUPLICATE_COLUMN_IDS", message: "Duplicate column IDs found" }
|
139
|
+
end
|
140
|
+
|
141
|
+
# Rule 4: Verify ColumnRef values
|
142
|
+
simple_code_list&.row&.each_with_index do |row, index|
|
143
|
+
row.value.each do |value|
|
144
|
+
unless column_ids.include?(value.column_ref)
|
145
|
+
errors << { code: "INVALID_COLUMN_REF",
|
146
|
+
message: "Invalid ColumnRef '#{value.column_ref}' in row #{index + 1}", }
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
# Rule 5: Unique values in columns
|
152
|
+
column_set&.column&.each do |col|
|
153
|
+
column_values = (simple_code_list&.row&.map do |row|
|
154
|
+
row.value.find { |v| v.column_ref == col.id }&.simple_value&.content
|
155
|
+
end || []).compact
|
156
|
+
|
157
|
+
if column_values.uniq.length != column_values.length
|
158
|
+
errors << { code: "DUPLICATE_VALUES", message: "Duplicate values found in column '#{col.id}'" }
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
# Rule 6: Required column values
|
163
|
+
required_columns = column_set&.column&.select { |col| col.use == "required" } || []
|
164
|
+
simple_code_list&.row&.each_with_index do |row, index|
|
165
|
+
required_columns.each do |col|
|
166
|
+
unless row.value.any? { |v| v.column_ref == col.id && v.simple_value&.content }
|
167
|
+
errors << { code: "MISSING_REQUIRED_VALUE",
|
168
|
+
message: "Missing value for required column '#{col.short_name&.content}' in row #{index + 1}", }
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
# Rule 7: Data type consistency
|
174
|
+
column_set&.column&.each do |col|
|
175
|
+
data_type = col.data&.type
|
176
|
+
simple_code_list&.row&.each_with_index do |row, index|
|
177
|
+
value = row.value.find { |v| v.column_ref == col.id }&.simple_value&.content
|
178
|
+
unless value_matches_type?(value, data_type)
|
179
|
+
errors << { code: "INVALID_DATA_TYPE",
|
180
|
+
message: "Invalid data type for column '#{col.short_name&.content}' in row #{index + 1}", }
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
# Rule 8: Valid canonical URIs
|
186
|
+
if identification&.canonical_uri && !valid_uri?(identification.canonical_uri)
|
187
|
+
errors << { code: "INVALID_CANONICAL_URI", message: "Invalid canonical URI" }
|
188
|
+
end
|
189
|
+
|
190
|
+
# Rule 19: Datatype ID validation
|
191
|
+
column_set&.column&.each do |col|
|
192
|
+
if col.data&.type && !valid_datatype_id?(col.data.type)
|
193
|
+
errors << { code: "INVALID_DATATYPE_ID",
|
194
|
+
message: "Invalid datatype ID for column '#{col.short_name&.content}'", }
|
195
|
+
end
|
196
|
+
|
197
|
+
# Rule 20 and 22: Complex data validation
|
198
|
+
if col.data&.type == "*" && col.data&.datatype_library != "*"
|
199
|
+
errors << { code: "INVALID_COMPLEX_DATA",
|
200
|
+
message: "Invalid complex data configuration for column '#{col.short_name&.content}'", }
|
201
|
+
end
|
202
|
+
|
203
|
+
# Rule 23: Language attribute validation
|
204
|
+
if col.data&.lang && col.data_restrictions&.lang
|
205
|
+
errors << { code: "DUPLICATE_LANG_ATTRIBUTE",
|
206
|
+
message: "Duplicate lang attribute for column '#{col.short_name&.content}'", }
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
# Rule 38: Implicit column reference
|
211
|
+
simple_code_list&.row&.each_with_index do |row, index|
|
212
|
+
unless row.value.all?(&:column_ref)
|
213
|
+
errors << { code: "MISSING_COLUMN_REF", message: "Missing explicit column reference in row #{index + 1}" }
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
# Rule 39: ShortName whitespace check
|
218
|
+
column_set&.column&.each do |col|
|
219
|
+
if col.short_name&.content&.match?(/\s/)
|
220
|
+
errors << { code: "INVALID_SHORT_NAME",
|
221
|
+
message: "ShortName '#{col.short_name&.content}' contains whitespace", }
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
# Rule 42 and 43: ComplexValue validation
|
226
|
+
simple_code_list&.row&.each_with_index do |row, index|
|
227
|
+
row.value.each do |value|
|
228
|
+
next unless value.complex_value
|
229
|
+
|
230
|
+
unless valid_complex_value?(value.complex_value, column_set&.column&.find { |c| c.id == value.column_ref })
|
231
|
+
errors << { code: "INVALID_COMPLEX_VALUE",
|
232
|
+
message: "Invalid ComplexValue in row #{index + 1}, column '#{value.column_ref}'", }
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
errors
|
238
|
+
end
|
239
|
+
|
240
|
+
private
|
241
|
+
|
242
|
+
def value_matches_type?(value, type)
|
243
|
+
case type
|
244
|
+
when "string"
|
245
|
+
true # All values can be considered strings
|
246
|
+
when "integer"
|
247
|
+
value.to_i.to_s == value
|
248
|
+
when "decimal"
|
249
|
+
begin
|
250
|
+
Float(value)
|
251
|
+
rescue StandardError
|
252
|
+
false
|
253
|
+
end
|
254
|
+
when "date"
|
255
|
+
begin
|
256
|
+
Date.parse(value)
|
257
|
+
rescue StandardError
|
258
|
+
false
|
259
|
+
end
|
260
|
+
else
|
261
|
+
true # If type is unknown, consider it valid
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
def valid_uri?(uri)
|
266
|
+
uri =~ URI::DEFAULT_PARSER.make_regexp
|
267
|
+
end
|
268
|
+
|
269
|
+
def valid_datatype_id?(id)
|
270
|
+
%w[string token boolean decimal integer date].include?(id)
|
271
|
+
end
|
272
|
+
|
273
|
+
def valid_complex_value?(complex_value, column)
|
274
|
+
return true unless complex_value && column&.data
|
275
|
+
|
276
|
+
if column.data.type == "*"
|
277
|
+
true # Any element is allowed
|
278
|
+
else
|
279
|
+
# Check if the root element name matches the datatype ID
|
280
|
+
complex_value.name == column.data.type
|
281
|
+
end
|
282
|
+
|
283
|
+
if column.data.datatype_library == "*"
|
284
|
+
true # Any namespace is allowed
|
285
|
+
else
|
286
|
+
# Check if the namespace matches the datatype library
|
287
|
+
complex_value.namespace == column.data.datatype_library
|
288
|
+
end
|
289
|
+
end
|
42
290
|
end
|
43
291
|
end
|
@@ -1,19 +1,22 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "
|
3
|
+
require "lutaml/model"
|
4
4
|
|
5
5
|
require_relative "annotation"
|
6
|
+
require_relative "json/canonical_uri_mixin"
|
6
7
|
|
7
8
|
module Genericode
|
8
|
-
class CodeListRef <
|
9
|
+
class CodeListRef < Lutaml::Model::Serializable
|
10
|
+
include Json::CanonicalUriMixin
|
11
|
+
|
9
12
|
attribute :annotation, Annotation
|
10
|
-
attribute :canonical_uri,
|
11
|
-
attribute :canonical_version_uri,
|
12
|
-
attribute :location_uri,
|
13
|
+
attribute :canonical_uri, CanonicalUri
|
14
|
+
attribute :canonical_version_uri, :string
|
15
|
+
attribute :location_uri, :string, collection: true
|
13
16
|
|
14
17
|
json do
|
15
18
|
map "Annotation", to: :annotation
|
16
|
-
map "CanonicalUri", to: :canonical_uri
|
19
|
+
map "CanonicalUri", to: :canonical_uri, with: { from: :canonical_uri_from_json, to: :canonical_uri_to_json }
|
17
20
|
map "CanonicalVersionUri", to: :canonical_version_uri
|
18
21
|
map "LocationUri", to: :location_uri
|
19
22
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "
|
3
|
+
require "lutaml/model"
|
4
4
|
|
5
5
|
require_relative "annotation"
|
6
6
|
require_relative "code_list_ref"
|
@@ -8,7 +8,7 @@ require_relative "code_list_set_ref"
|
|
8
8
|
require_relative "identification"
|
9
9
|
|
10
10
|
module Genericode
|
11
|
-
class CodeListSet <
|
11
|
+
class CodeListSet < Lutaml::Model::Serializable
|
12
12
|
attribute :annotation, Annotation
|
13
13
|
attribute :identification, Identification
|
14
14
|
attribute :code_list_ref, CodeListRef, collection: true
|
@@ -33,5 +33,42 @@ module Genericode
|
|
33
33
|
map_element "CodeListSet", to: :code_list_set, prefix: nil, namespace: nil
|
34
34
|
map_element "CodeListSetRef", to: :code_list_set_ref, prefix: nil, namespace: nil
|
35
35
|
end
|
36
|
+
|
37
|
+
def validate_verbose
|
38
|
+
errors = []
|
39
|
+
|
40
|
+
# Rule 47: CodeListSet reference validation
|
41
|
+
code_list_set_ref&.each do |ref|
|
42
|
+
unless valid_uri?(ref.canonical_uri) && valid_uri?(ref.canonical_version_uri)
|
43
|
+
errors << { code: "INVALID_CODELIST_SET_REF", message: "Invalid CodeListSet reference URI" }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# Rule 48-51: URI validations
|
48
|
+
[canonical_uri, canonical_version_uri].each do |uri|
|
49
|
+
errors << { code: "INVALID_URI", message: "Invalid URI: #{uri}" } unless valid_uri?(uri)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Rule 52-53: LocationUri validation
|
53
|
+
location_uri&.each do |uri|
|
54
|
+
unless valid_genericode_uri?(uri)
|
55
|
+
errors << { code: "INVALID_LOCATION_URI", message: "Invalid LocationUri: #{uri}" }
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
errors
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def valid_uri?(uri)
|
65
|
+
uri =~ URI::DEFAULT_PARSER.make_regexp
|
66
|
+
end
|
67
|
+
|
68
|
+
def valid_genericode_uri?(uri)
|
69
|
+
# Add logic to check if the URI points to a valid genericode document
|
70
|
+
# This might involve making an HTTP request or checking file extensions
|
71
|
+
uri.end_with?(".gc", ".gcj")
|
72
|
+
end
|
36
73
|
end
|
37
74
|
end
|
@@ -1,19 +1,22 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "
|
3
|
+
require "lutaml/model"
|
4
4
|
|
5
5
|
require_relative "annotation"
|
6
|
+
require_relative "canonical_uri"
|
7
|
+
require_relative "json/canonical_uri_mixin"
|
6
8
|
|
7
9
|
module Genericode
|
8
|
-
class CodeListSetRef <
|
10
|
+
class CodeListSetRef < Lutaml::Model::Serializable
|
11
|
+
include Json::CanonicalUriMixin
|
9
12
|
attribute :annotation, Annotation
|
10
|
-
attribute :canonical_uri,
|
11
|
-
attribute :canonical_version_uri,
|
12
|
-
attribute :location_uri,
|
13
|
+
attribute :canonical_uri, CanonicalUri
|
14
|
+
attribute :canonical_version_uri, :string
|
15
|
+
attribute :location_uri, :string, collection: true
|
13
16
|
|
14
17
|
json do
|
15
18
|
map "Annotation", to: :annotation
|
16
|
-
map "CanonicalUri", to: :canonical_uri
|
19
|
+
map "CanonicalUri", to: :canonical_uri, with: { from: :canonical_uri_from_json, to: :canonical_uri_to_json }
|
17
20
|
map "CanonicalVersionUri", to: :canonical_version_uri
|
18
21
|
map "LocationUri", to: :location_uri
|
19
22
|
end
|
data/lib/genericode/column.rb
CHANGED
@@ -1,32 +1,58 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "
|
3
|
+
require "lutaml/model"
|
4
4
|
|
5
5
|
require_relative "annotation"
|
6
6
|
require_relative "data"
|
7
|
+
require_relative "canonical_uri"
|
7
8
|
require_relative "long_name"
|
8
9
|
require_relative "short_name"
|
10
|
+
require_relative "json/short_name_mixin"
|
11
|
+
require_relative "json/canonical_uri_mixin"
|
12
|
+
require_relative "utils"
|
9
13
|
|
10
14
|
module Genericode
|
11
|
-
class Column <
|
12
|
-
|
13
|
-
|
15
|
+
class Column < Lutaml::Model::Serializable
|
16
|
+
include Json::CanonicalUriMixin
|
17
|
+
include Json::ShortNameMixin
|
18
|
+
|
19
|
+
attribute :id, :string
|
20
|
+
attribute :use, :string, default: -> { "optional" }
|
14
21
|
attribute :annotation, Annotation
|
15
22
|
attribute :short_name, ShortName
|
16
23
|
attribute :long_name, LongName, collection: true
|
17
|
-
attribute :canonical_uri,
|
18
|
-
attribute :canonical_version_uri,
|
24
|
+
attribute :canonical_uri, CanonicalUri
|
25
|
+
attribute :canonical_version_uri, :string
|
19
26
|
attribute :data, Data
|
20
27
|
|
21
28
|
json do
|
29
|
+
map "Required", to: :use, with: { from: :use_from_json, to: :use_to_json }
|
22
30
|
map "Id", to: :id
|
23
|
-
map "Use", to: :use
|
24
31
|
map "Annotation", to: :annotation
|
25
|
-
map "ShortName", to: :short_name
|
26
|
-
map "LongName", to: :long_name
|
27
|
-
map "CanonicalUri", to: :canonical_uri
|
32
|
+
map "ShortName", to: :short_name, with: { from: :short_name_from_json, to: :short_name_to_json }
|
33
|
+
map "LongName", to: :long_name, with: { from: :long_name_from_json, to: :long_name_to_json }
|
34
|
+
map "CanonicalUri", to: :canonical_uri, with: { from: :canonical_uri_from_json, to: :canonical_uri_to_json }
|
28
35
|
map "CanonicalVersionUri", to: :canonical_version_uri
|
29
|
-
map "
|
36
|
+
map "DataType", to: :type, delegate: :data
|
37
|
+
map "DataLanguage", to: :lang, delegate: :data
|
38
|
+
end
|
39
|
+
|
40
|
+
def use_from_json(model, value)
|
41
|
+
model.use = value == "true" ? "required" : "optional"
|
42
|
+
end
|
43
|
+
|
44
|
+
def use_to_json(model, doc)
|
45
|
+
doc["Required"] = "true" if model.use == "required"
|
46
|
+
end
|
47
|
+
|
48
|
+
def long_name_from_json(model, value)
|
49
|
+
model.long_name = LongName.of_json(Utils.array_wrap(value))
|
50
|
+
end
|
51
|
+
|
52
|
+
def long_name_to_json(model, doc)
|
53
|
+
return if model.long_name.empty?
|
54
|
+
|
55
|
+
doc["LongName"] = LongName.as_json(Utils.one_or_all(model.long_name))
|
30
56
|
end
|
31
57
|
|
32
58
|
xml do
|
@@ -1,18 +1,18 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "
|
3
|
+
require "lutaml/model"
|
4
4
|
|
5
5
|
require_relative "annotation"
|
6
6
|
require_relative "data_restrictions"
|
7
7
|
|
8
8
|
module Genericode
|
9
|
-
class ColumnRef <
|
10
|
-
attribute :id,
|
11
|
-
attribute :external_ref,
|
12
|
-
attribute :use,
|
9
|
+
class ColumnRef < Lutaml::Model::Serializable
|
10
|
+
attribute :id, :string
|
11
|
+
attribute :external_ref, :string
|
12
|
+
attribute :use, :string
|
13
13
|
attribute :annotation, Annotation
|
14
|
-
attribute :canonical_version_uri,
|
15
|
-
attribute :location_uri,
|
14
|
+
attribute :canonical_version_uri, :string
|
15
|
+
attribute :location_uri, :string, collection: true
|
16
16
|
attribute :data, DataRestrictions
|
17
17
|
|
18
18
|
json do
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "
|
3
|
+
require "lutaml/model"
|
4
4
|
|
5
5
|
require_relative "annotation"
|
6
6
|
require_relative "column"
|
@@ -10,8 +10,8 @@ require_relative "key"
|
|
10
10
|
require_relative "key_ref"
|
11
11
|
|
12
12
|
module Genericode
|
13
|
-
class ColumnSet <
|
14
|
-
attribute :datatype_library,
|
13
|
+
class ColumnSet < Lutaml::Model::Serializable
|
14
|
+
attribute :datatype_library, :string
|
15
15
|
attribute :annotation, Annotation
|
16
16
|
attribute :identification, Identification
|
17
17
|
attribute :column, Column, collection: true
|