lutaml-lml 0.1.8 → 0.2.0
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/lib/lutaml/lml/cli.rb +12 -3
- data/lib/lutaml/lml/data_processor/attribute_processing.rb +49 -9
- data/lib/lutaml/lml/data_processor/value_declaration_processing.rb +62 -0
- data/lib/lutaml/lml/data_processor.rb +3 -0
- data/lib/lutaml/lml/document_builder.rb +161 -6
- data/lib/lutaml/lml/grammar/lml.artifact.json +5837 -2815
- data/lib/lutaml/lml/grammar/lml.parg +64 -17
- data/lib/lutaml/lml/model_compiler.rb +209 -18
- data/lib/lutaml/lml/models/data_type.rb +1 -0
- data/lib/lutaml/lml/models/document.rb +2 -0
- data/lib/lutaml/lml/models/enum.rb +2 -0
- data/lib/lutaml/lml/models/instance.rb +1 -0
- data/lib/lutaml/lml/models/instance_collection.rb +1 -0
- data/lib/lutaml/lml/models/mapping_rule.rb +16 -0
- data/lib/lutaml/lml/models/namespace.rb +14 -0
- data/lib/lutaml/lml/models/serialization_mapping.rb +17 -0
- data/lib/lutaml/lml/models/top_element_attribute.rb +4 -0
- data/lib/lutaml/lml/models/uml_class.rb +1 -0
- data/lib/lutaml/lml/preprocessor.rb +82 -6
- data/lib/lutaml/lml/validator.rb +276 -0
- data/lib/lutaml/lml/version.rb +1 -1
- data/lib/lutaml/lml.rb +4 -0
- metadata +6 -1
|
@@ -38,7 +38,7 @@ grammar Lml version "1.0.0" {
|
|
|
38
38
|
boolean = ( "true" / "false" ) as boolean
|
|
39
39
|
number = ( 1*%x30-39 "." 1*%x30-39 ) as float / ( 1*%x30-39 ) as number
|
|
40
40
|
variable = quoted_string / 1*word
|
|
41
|
-
reference = "reference:(" ( variable *( "." variable ) ) as reference ")"
|
|
41
|
+
reference = ( "reference:(" / "ref:(" ) ( variable *( "." variable ) ) as reference ")"
|
|
42
42
|
range = ( variable as start ".." variable as end ) as range
|
|
43
43
|
namespaced_identifier = variable *( "::" variable )
|
|
44
44
|
comment_definition = [ ws ] ( "**" / "#" ) ( *( !newline any_char ) ) as comments
|
|
@@ -49,8 +49,8 @@ grammar Lml version "1.0.0" {
|
|
|
49
49
|
cardinality_body_definition = cardchar as min [ ".." [ cardchar as max ] ]
|
|
50
50
|
cardinality = "[" cardinality_body_definition as cardinality "]"
|
|
51
51
|
|
|
52
|
-
|
|
53
|
-
key_value_pair = variable as key ws [ "=" ] [ ws ]
|
|
52
|
+
data_value = boolean / reference / range / number / quoted_string
|
|
53
|
+
key_value_pair = variable as key ws [ "=" ] [ ws ] data_value as value
|
|
54
54
|
key_value_map = "{" [ whitespace ] ( *( key_value_pair whitespace ) ) as key_value_map "}"
|
|
55
55
|
kw_visibility_modifier = "+" / "-" / "#" / "~"
|
|
56
56
|
|
|
@@ -69,8 +69,11 @@ grammar Lml version "1.0.0" {
|
|
|
69
69
|
|
|
70
70
|
keyword_type_argument = "type" [ ws ] ( 1*nonspace ) as type [ whitespace ]
|
|
71
71
|
keyword_cardinality_argument = "cardinality" [ ws ] cardinality_body_definition as cardinality [ whitespace ]
|
|
72
|
-
keyword_any_argument = [ ws ] ( 1*nonspace ) as name ws [ "=" ] [ ws ] attribute_value as value [ whitespace ]
|
|
73
|
-
|
|
72
|
+
keyword_any_argument = [ ws ] ( 1*nonspace ) as name ws [ "=" / ":" ] [ ws ] attribute_value as value [ whitespace ]
|
|
73
|
+
keyword_values_argument = "values" ws ( ( value_set_body ) as value_set / ( variable ) as value_set_ref ) [ whitespace ]
|
|
74
|
+
keyword_pattern_argument = ( "pattern" ws "/" ( *( ( "\\" any_char ) / ( !"/" any_char ) ) ) as pattern "/" [ whitespace ] )
|
|
75
|
+
value_set_body = "{" [ whitespace ] ( *( ( ( data_value / variable ) as item ) [ "," ] [ whitespace ] ) ) as items "}"
|
|
76
|
+
keyword_attribute_options = *( keyword_type_argument / keyword_cardinality_argument / keyword_values_argument / keyword_pattern_argument / ( keyword_any_argument ) as properties )
|
|
74
77
|
keyword_attribute_body = "{" [ whitespace ] keyword_attribute_options [ whitespace ] "}"
|
|
75
78
|
keyword_attribute_definition = ( "attribute" ws attribute_name [ ws ] keyword_attribute_body ) as attributes
|
|
76
79
|
|
|
@@ -89,18 +92,43 @@ grammar Lml version "1.0.0" {
|
|
|
89
92
|
|
|
90
93
|
# ---- Definitions ----
|
|
91
94
|
class_modifier = [ ( [ whitespace ] ( "abstract" / "interface" ) ) as modifier ws ]
|
|
95
|
+
# ---- Namespaces (RS 3010 extension: lutaml-model XmlNamespace) ----
|
|
96
|
+
namespace_uri_line = ( [ ws ] "uri" ws ( quoted_string / ( 1*word ) ) as uri [ whitespace ] )
|
|
97
|
+
namespace_prefix_line = ( [ ws ] "prefix" ws ( quoted_string / ( 1*word ) ) as prefix [ whitespace ] )
|
|
98
|
+
namespace_body = "{" [ whitespace ] ( *( namespace_uri_line / namespace_prefix_line ) ) as members "}"
|
|
99
|
+
namespace_inline = ( ws ( quoted_string ) as uri [ ws "prefix" ws ( quoted_string / ( 1*word ) ) as prefix ] )
|
|
100
|
+
namespace_definition = ( [ whitespace ] "namespace" ws ( 1*word ) as name [ ws ] ( namespace_body / namespace_inline ) [ whitespace ] ) as namespaces
|
|
101
|
+
|
|
102
|
+
# ---- Serialization mappings (RS 3010 extension: sibling-linked) ----
|
|
103
|
+
serialization_body = "{" [ whitespace ] ( *( serialization_line ) ) as rules "}"
|
|
104
|
+
serialization_line = ( mapping_element_name_line / mapping_element_line / mapping_attribute_line / mapping_content_line / mapping_namespace_line / mapping_mixed_line / legacy_map_line / mapping_map_line / mapping_map_key_line / mapping_map_value_line ) [ whitespace ]
|
|
105
|
+
legacy_map_line = ( [ ws ] "map" ws dq_string as wire "," ws "to:" ws ( quoted_string / ( 1*word ) ) as to [ whitespace ] ) as legacy_map
|
|
106
|
+
mapping_element_name_line = ( [ ws ] "element" ws dq_string as element_name [ whitespace ] ) as element_name
|
|
107
|
+
mapping_element_line = ( [ ws ] "map_element" ws ( element_args ) ) as map_element
|
|
108
|
+
element_args = ( dq_string as wire [ ws ( 1*word ) as field ] / ( 1*word ) as field )
|
|
109
|
+
mapping_attribute_line = ( [ ws ] "map_attribute" ws ( attribute_args ) ) as map_attribute
|
|
110
|
+
attribute_args = ( dq_string as wire [ ws ( 1*word ) as field ] / ( 1*word ) as field )
|
|
111
|
+
mapping_content_line = ( [ ws ] "map_content" ws ( 1*word ) as field ) as map_content
|
|
112
|
+
mapping_namespace_line = ( [ ws ] "namespace" ws ( 1*word ) as namespace_ref ) as map_namespace
|
|
113
|
+
mapping_mixed_line = ( [ ws ] "mixed_content" ) as mixed_content
|
|
114
|
+
mapping_map_line = ( [ ws ] "map" ws map_args ) as map
|
|
115
|
+
map_args = ( dq_string as wire ws ( 1*word ) as field / dq_string as wire / ( 1*word ) as field )
|
|
116
|
+
mapping_map_key_line = ( [ ws ] "map_key" ws "to_instance" ws ( 1*word ) as field ) as map_key
|
|
117
|
+
mapping_map_value_line = ( [ ws ] "map_value" ws ( "to_instance" / "as_attribute" ) as mode ws ( 1*word ) as field ) as map_value
|
|
118
|
+
mapping_definition = ( [ whitespace ] "mapping" ws ( 1*word ) as format ws ( 1*word ) as target [ ws ] serialization_body [ whitespace ] ) as serialization_mappings
|
|
119
|
+
|
|
92
120
|
# ---- Mapping blocks (lutaml-model wire names) ----
|
|
93
121
|
map_name = ( quoted_string / 1*( word / "-" / "." ) )
|
|
94
122
|
map_line = [ ws ] "map" ws ( map_name ) as wire "," ws "to:" ws ( map_name ) as to [ whitespace ]
|
|
95
123
|
mapping_body = [ ws ] "{" [ whitespace ] ( *( map_line ) ) as maps "}"
|
|
96
|
-
|
|
124
|
+
class_mapping_section = ( [ whitespace ] "mapping" ws ( 1*word ) as format [ ws ] serialization_body [ whitespace ] ) as serialization_mappings
|
|
97
125
|
|
|
98
126
|
derive_definition = ( [ whitespace ] "derive" ws attribute_name [ ws ] ":" [ ws ] [ quotes ] ( 1*typechar ) as type [ quotes ] ) as derived_field [ whitespace ]
|
|
99
127
|
string_format_item = ( [ ws ] ( "artifact" / "root" ) as key ws ( quoted_string / 1*nonspace ) as value [ whitespace ] )
|
|
100
128
|
string_format_body = "{" [ whitespace ] ( *( string_format_item ) ) as items [ whitespace ] "}"
|
|
101
129
|
string_format_definition = ( [ whitespace ] "string_format" ws ( variable ) as format [ ws ] string_format_body ) as string_format
|
|
102
130
|
|
|
103
|
-
class_inner_definitions = definition_body /
|
|
131
|
+
class_inner_definitions = definition_body / definition_line / class_mapping_section / derive_definition / string_format_definition / attribute_comma_definition / ( !( "attribute" ws ) attribute_definition ) / keyword_attribute_definition / comment_definition / comment_multiline_definition
|
|
104
132
|
class_inner_definition = class_inner_definitions [ whitespace ]
|
|
105
133
|
class_body = [ ws ] "{" [ whitespace ] ( *( class_inner_definition ) ) as members "}"
|
|
106
134
|
parent_class = [ ws ] "<" [ ws ] ( class_name_chars ) as parent_class
|
|
@@ -109,12 +137,26 @@ grammar Lml version "1.0.0" {
|
|
|
109
137
|
# Balanced-brace definition text: nested "{...}" and "\"-escapes allowed
|
|
110
138
|
definition_content = *( ( "\\" any_char ) / ( "{" definition_content "}" ) / ( !"}" any_char ) )
|
|
111
139
|
definition_body = [ ws ] "definition" [ whitespace ] "{" ( definition_content ) as definition "}"
|
|
140
|
+
definition_line = ( [ ws ] "definition" ws ( dq_string / sq_string ) ) as definition [ whitespace ]
|
|
112
141
|
|
|
113
142
|
member_field_line = [ ws ] attribute_name [ ws ] ( 1*nonspace ) as member_field_type [ whitespace ]
|
|
114
143
|
member_fields_definition = ( [ whitespace ] "member_fields" [ whitespace ] "{" [ whitespace ] ( *( member_field_line ) ) as member_fields [ whitespace ] "}" ) as member_fields_decl
|
|
144
|
+
attribute_comma_definition = ( [ whitespace ] "attribute" ws attribute_name "," [ ws ] ( 1*typechar ) as type [ ws ] [ "{" [ whitespace ] keyword_attribute_options [ whitespace ] "}" ] [ whitespace ] ) as attributes
|
|
115
145
|
enum_member_with_payload = ( [ whitespace ] attribute_name [ ws ] ( class_body / keyword_attribute_body ) ) as attributes
|
|
116
146
|
|
|
117
|
-
|
|
147
|
+
# ---- Canonical value construct (RS 3001 par. Value / par. Enums) ----
|
|
148
|
+
# `value <name> { block }` is the canonical enum member; the bare
|
|
149
|
+
# identifier member is its documented shortcut. from_table expands to
|
|
150
|
+
# value members at compile time.
|
|
151
|
+
value_name = quoted_string / map_name
|
|
152
|
+
value_inner_definition = ( definition_body / definition_line / instance / attribute_line / comment_definition / comment_multiline_definition ) [ whitespace ]
|
|
153
|
+
value_body = "{" [ whitespace ] ( *( value_inner_definition ) ) as members "}"
|
|
154
|
+
value_definition = ( [ whitespace ] "value" ws ( value_name ) as value_name [ ws ] [ value_body ] [ whitespace ] ) as value_decl
|
|
155
|
+
|
|
156
|
+
from_table_definition = ( [ whitespace ] "from_table" [ ws ] ( map_name ) as from_table_name [ ws ] [ "{" [ whitespace ] *( "artifact" [ ws ] ( map_name ) as artifact_table [ whitespace ] ) "}" ] [ whitespace ] ) as from_table_decl
|
|
157
|
+
|
|
158
|
+
enum_values_shorthand = ( [ whitespace ] "values" ws value_set_body [ whitespace ] ) as value_set_decl
|
|
159
|
+
enum_inner_definitions = definition_body / definition_line / member_fields_definition / from_table_definition / enum_values_shorthand / value_definition / enum_member_with_payload / attribute_comma_definition / attribute_definition / comment_definition / comment_multiline_definition
|
|
118
160
|
enum_inner_definition = enum_inner_definitions [ whitespace ]
|
|
119
161
|
enum_body = [ ws ] "{" [ whitespace ] ( *( enum_inner_definition ) ) as members "}"
|
|
120
162
|
enum_definition = [ whitespace ] "enum" ws [ quotes ] class_name as name [ quotes ] [ attribute_stereotype ] [ enum_body ]
|
|
@@ -122,12 +164,14 @@ grammar Lml version "1.0.0" {
|
|
|
122
164
|
data_type_definition = [ whitespace ] "data_type" ws [ quotes ] class_name as name [ quotes ] [ attribute_stereotype ] [ data_type_body ]
|
|
123
165
|
primitive_definition = [ whitespace ] "primitive" ws [ quotes ] class_name as name [ quotes ]
|
|
124
166
|
|
|
125
|
-
|
|
167
|
+
package_body = [ ws ] "{" [ whitespace ] ( *( diagram_inner_definition ) ) as members "}"
|
|
168
|
+
package_definition = ( [ whitespace ] "package" ws class_name as name [ ws ] package_body [ whitespace ] ) as packages
|
|
169
|
+
diagram_inner_definitions = namespace_definition / mapping_definition / package_definition / title_definition / caption_definition / fontname_definition / ( class_definition ) as classes / ( enum_definition ) as enums / ( primitive_definition ) as primitives / ( data_type_definition ) as data_types / ( association_definition ) as associations / comment_definition / comment_multiline_definition
|
|
126
170
|
diagram_inner_definition = diagram_inner_definitions [ whitespace ]
|
|
127
171
|
diagram_body = [ ws ] "{" [ whitespace ] ( *( diagram_inner_definition ) ) as members "}"
|
|
128
172
|
diagram_definition = [ whitespace ] "diagram" [ ws ] class_name as name diagram_body [ whitespace ]
|
|
129
173
|
diagram_definitions = diagram_definition [ whitespace ]
|
|
130
|
-
model_definitions = ( *( diagram_inner_definition ) ) as members
|
|
174
|
+
model_definitions = ( 1*( diagram_inner_definition ) ) as members
|
|
131
175
|
|
|
132
176
|
view_only_definitions = ( view_import ) as view_imports / show_directive / hide_directive
|
|
133
177
|
view_inner_definitions = view_only_definitions / diagram_inner_definitions
|
|
@@ -156,7 +200,8 @@ grammar Lml version "1.0.0" {
|
|
|
156
200
|
require_block = ( *( require_stmt [ whitespace ] ) ) as requires
|
|
157
201
|
|
|
158
202
|
# ---- Instances ----
|
|
159
|
-
instance = keyword_instance / class_instance
|
|
203
|
+
instance = instance_named / keyword_instance / class_instance
|
|
204
|
+
instance_named = ( [ whitespace ] "instance" ws quoted_string as name [ ws ] [ ( namespaced_identifier ) as instance_type ] [ ws ] "{" [ whitespace ] ( ( [ ws ] instance ) / attributes ) "}" [ whitespace ] ) as instance
|
|
160
205
|
isa_definition = [ whitespace ] "isa" ws ( namespaced_identifier ) as isa [ whitespace ]
|
|
161
206
|
keyword_instance = ( [ whitespace ] "instance" [ ws ( namespaced_identifier ) as instance_type ] [ ws ] "{" [ whitespace ] [ isa_definition ] ( ( [ ws ] instance ) / attributes ) "}" ) as instance [ whitespace ]
|
|
162
207
|
class_instance = ( variable as instance_type [ whitespace ] quoted_string as name [ whitespace ] [ [ whitespace ] "extends" [ whitespace ] quoted_string as parent [ whitespace ] ] "{" [ whitespace ] [ lml_instance_body ] "}" ) as instance [ whitespace ]
|
|
@@ -164,11 +209,11 @@ grammar Lml version "1.0.0" {
|
|
|
164
209
|
instance_template = [ whitespace ] "template" [ whitespace ] "{" [ whitespace ] attributes [ whitespace ] "}"
|
|
165
210
|
lml_instance_members = ( instance_template ) as template / attributes
|
|
166
211
|
models = [ whitespace ] "models" [ whitespace ] variable as name [ whitespace ] "{" ( *( ( ( string_format_definition ) as default_string_formats ) / model_body ) ) as members "}" [ whitespace ]
|
|
167
|
-
model_body =
|
|
212
|
+
model_body = diagram_inner_definition
|
|
168
213
|
|
|
169
214
|
# ---- Data structures ----
|
|
170
|
-
attribute_value = instance / list / key_value_map /
|
|
171
|
-
list_item = instance /
|
|
215
|
+
attribute_value = instance / list / key_value_map / data_value / 1*( !%x0A any_char )
|
|
216
|
+
list_item = instance / data_value
|
|
172
217
|
list = "[" [ whitespace ] ( *( list_item [ ws ] [ "," ] [ whitespace ] ) ) as list [ whitespace ] "]"
|
|
173
218
|
collection = [ whitespace ] "collection" ws quoted_string as name [ ws ] "{" [ whitespace ] [ includes ] [ whitespace ] [ validation ] [ whitespace ] "}" [ whitespace ]
|
|
174
219
|
includes = [ whitespace ] "includes" [ ws ] ( list ) as includes
|
|
@@ -176,14 +221,16 @@ grammar Lml version "1.0.0" {
|
|
|
176
221
|
condition = [ whitespace ] "condition" ws quoted_string as condition [ whitespace ]
|
|
177
222
|
import = [ whitespace ] "import" [ ws ] "{" [ whitespace ] ( *( import_definition ) ) as imports "}" [ whitespace ]
|
|
178
223
|
import_definition = ( 1*nonspace ) as format_type [ ws ] quoted_string as file [ whitespace ] "{" [ whitespace ] attributes [ whitespace ] "}" [ whitespace ]
|
|
179
|
-
instances = [ whitespace ] "instances" [ whitespace ] "{" [ whitespace ] [ instances_body ] "}" [ whitespace ]
|
|
224
|
+
instances = [ whitespace ] "instances" [ ws ] [ ( quoted_string / variable ) as name ] [ whitespace ] "{" [ whitespace ] [ instances_body ] "}" [ whitespace ]
|
|
180
225
|
instances_body = ( *( instances_member [ whitespace ] ) ) as instances
|
|
181
226
|
instances_member = import / ( collection ) as collections / export / instance
|
|
182
227
|
export = [ whitespace ] "export" [ whitespace ] "{" [ whitespace ] ( *( export_format [ whitespace ] ) ) as exports "}" [ whitespace ]
|
|
183
228
|
export_format = [ whitespace ] "format" ws variable as format_type [ whitespace ] "{" [ whitespace ] attributes "}" [ whitespace ]
|
|
184
229
|
|
|
185
|
-
# ---- Root (
|
|
186
|
-
diagram = [ require_block ] (
|
|
230
|
+
# ---- Root (RS 3001: a document interleaves declarations) ----
|
|
231
|
+
diagram = [ require_block ] ( *( top_level_definition ) ) as members
|
|
232
|
+
|
|
233
|
+
top_level_definition = ( models / diagram_definitions / view_definitions / instances / instance / model_definitions ) [ whitespace ]
|
|
187
234
|
}
|
|
188
235
|
|
|
189
236
|
entry diagram: diagram
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'json'
|
|
4
|
+
|
|
3
5
|
module Lutaml
|
|
4
6
|
module Lml
|
|
5
7
|
class ModelCompiler
|
|
@@ -7,9 +9,13 @@ module Lutaml
|
|
|
7
9
|
|
|
8
10
|
TYPE_MAP = {
|
|
9
11
|
'String' => :string,
|
|
12
|
+
'string' => :string,
|
|
10
13
|
'Integer' => :integer,
|
|
14
|
+
'integer' => :integer,
|
|
11
15
|
'Boolean' => :boolean,
|
|
16
|
+
'boolean' => :boolean,
|
|
12
17
|
'Float' => :float,
|
|
18
|
+
'float' => :float,
|
|
13
19
|
'Date' => :date,
|
|
14
20
|
'date_time' => :date_time,
|
|
15
21
|
'DateTime' => :date_time,
|
|
@@ -21,13 +27,17 @@ module Lutaml
|
|
|
21
27
|
# declarations. Anything else must parse as an Integer.
|
|
22
28
|
UNBOUNDED_TOKENS = %w[* n N unbounded].freeze
|
|
23
29
|
|
|
24
|
-
def initialize(namespace: nil, artifact_paths: {})
|
|
30
|
+
def initialize(namespace: nil, artifact_paths: {}, default_artifact: nil)
|
|
25
31
|
@namespace = namespace
|
|
26
32
|
@artifact_paths = artifact_paths
|
|
33
|
+
@default_artifact = default_artifact
|
|
27
34
|
@compiled = {}
|
|
28
35
|
@forward_refs = {}
|
|
29
36
|
@enum_names = Set.new
|
|
30
37
|
@enum_classes = {}
|
|
38
|
+
@declared_namespaces = []
|
|
39
|
+
@serialization_mappings = []
|
|
40
|
+
@namespace_classes = {}
|
|
31
41
|
end
|
|
32
42
|
|
|
33
43
|
def compile(input)
|
|
@@ -183,14 +193,30 @@ module Lutaml
|
|
|
183
193
|
end
|
|
184
194
|
|
|
185
195
|
def compile_document(doc)
|
|
196
|
+
@declared_namespaces = Array(doc.namespaces)
|
|
197
|
+
@serialization_mappings = Array(doc.serialization_mappings)
|
|
186
198
|
doc.classes.each { |c| compile_class(c) }
|
|
199
|
+
hoist_class_serialization_mappings(doc)
|
|
187
200
|
doc.enums.each { |e| compile_enum(e) }
|
|
188
201
|
doc.data_types.each { |dt| compile_data_type(dt) }
|
|
189
202
|
resolve_forward_references
|
|
190
203
|
apply_xml_mappings
|
|
204
|
+
apply_key_value_mappings
|
|
191
205
|
register_string_formats(doc)
|
|
192
206
|
end
|
|
193
207
|
|
|
208
|
+
# In-class `mapping <format> { … }` is the shortcut for a sibling
|
|
209
|
+
# mapping targeting the enclosing class; hoist them so emission is
|
|
210
|
+
# uniform.
|
|
211
|
+
def hoist_class_serialization_mappings(doc)
|
|
212
|
+
(Array(doc.classes) + Array(doc.data_types)).each do |klass_def|
|
|
213
|
+
Array(klass_def.serialization_mappings).each do |mapping|
|
|
214
|
+
mapping.target = klass_def.name.to_s
|
|
215
|
+
@serialization_mappings << mapping
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
|
|
194
220
|
def resolve_forward_references
|
|
195
221
|
@forward_refs.each do |class_name, deferred_attrs|
|
|
196
222
|
klass = @compiled[class_name]
|
|
@@ -232,6 +258,7 @@ module Lutaml
|
|
|
232
258
|
|
|
233
259
|
def compile_enum(enum_def)
|
|
234
260
|
name = enum_def.name.to_s
|
|
261
|
+
expand_from_table(enum_def)
|
|
235
262
|
values = extract_enum_values(enum_def)
|
|
236
263
|
|
|
237
264
|
payload_fields = Array(enum_def.member_fields).map(&:name).map(&:to_s)
|
|
@@ -246,7 +273,8 @@ module Lutaml
|
|
|
246
273
|
end
|
|
247
274
|
|
|
248
275
|
values.each do |value|
|
|
249
|
-
|
|
276
|
+
accessor = value.name.to_s.gsub(/[^a-zA-Z0-9_]/, '_')
|
|
277
|
+
compiled_klass.define_singleton_method(accessor) do
|
|
250
278
|
new({ value: value.name, description: value.description }.merge(value.payload || {}))
|
|
251
279
|
end
|
|
252
280
|
end
|
|
@@ -297,6 +325,9 @@ module Lutaml
|
|
|
297
325
|
@compiled.each do |name, klass|
|
|
298
326
|
next if @enum_names.include?(name)
|
|
299
327
|
|
|
328
|
+
custom = serialization_mapping_for(name, 'xml')
|
|
329
|
+
next apply_custom_xml_mapping(klass, custom) if custom
|
|
330
|
+
|
|
300
331
|
apply_xml_mapping(klass, name)
|
|
301
332
|
end
|
|
302
333
|
end
|
|
@@ -313,16 +344,126 @@ module Lutaml
|
|
|
313
344
|
end
|
|
314
345
|
end
|
|
315
346
|
|
|
347
|
+
# RS 3010 extension: key-value mapping blocks for the declared
|
|
348
|
+
# formats (yaml/json/toml share lutaml-model's key_value model).
|
|
349
|
+
def apply_key_value_mappings
|
|
350
|
+
@compiled.each do |name, klass|
|
|
351
|
+
next if @enum_names.include?(name)
|
|
352
|
+
|
|
353
|
+
custom = serialization_mapping_for(name, 'key_value')
|
|
354
|
+
apply_custom_key_value_mapping(klass, custom) if custom
|
|
355
|
+
end
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
def serialization_mapping_for(target, format)
|
|
359
|
+
@serialization_mappings.find do |m|
|
|
360
|
+
m.target.to_s == target && serialization_format(m.format) == format
|
|
361
|
+
end
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
def serialization_format(format)
|
|
365
|
+
%w[yaml json toml].include?(format.to_s) ? 'key_value' : format.to_s
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
def apply_custom_xml_mapping(klass, mapping)
|
|
369
|
+
check_namespace_ambiguity(mapping)
|
|
370
|
+
ns_class = namespace_class_for(mapping.namespace_ref) if mapping.namespace_ref
|
|
371
|
+
rule_ns = mapping.rules.select { |r| r.kind == 'namespace' }
|
|
372
|
+
.to_h { |r| [r.field, namespace_class_for(r.field)] }
|
|
373
|
+
klass.xml do |m|
|
|
374
|
+
m.root(mapping.element_name) if mapping.element_name
|
|
375
|
+
m.namespace(ns_class) if ns_class
|
|
376
|
+
m.mixed_content if mapping.mixed_content
|
|
377
|
+
mapping.rules.each do |rule|
|
|
378
|
+
case rule.kind
|
|
379
|
+
when 'element' then m.map_element(rule.wire, to: rule.field.to_sym)
|
|
380
|
+
when 'attribute' then m.map_attribute(rule.wire, to: rule.field.to_sym)
|
|
381
|
+
when 'content' then m.map_content(to: rule.field.to_sym)
|
|
382
|
+
when 'namespace' then m.namespace(rule_ns[rule.field])
|
|
383
|
+
end
|
|
384
|
+
end
|
|
385
|
+
end
|
|
386
|
+
end
|
|
387
|
+
|
|
388
|
+
def apply_custom_key_value_mapping(klass, mapping)
|
|
389
|
+
formats = mapping.format.to_s == 'key_value' ? %w[yaml json toml] : [mapping.format.to_s]
|
|
390
|
+
klass.instance_eval do
|
|
391
|
+
formats.each do |format|
|
|
392
|
+
public_send(format) do |m|
|
|
393
|
+
mapping.rules.each do |rule|
|
|
394
|
+
case rule.kind
|
|
395
|
+
when 'map' then m.map(rule.wire, to: rule.field.to_sym)
|
|
396
|
+
when 'key' then m.map_key(to_instance: rule.field.to_sym)
|
|
397
|
+
when 'value'
|
|
398
|
+
if rule.mode == 'as_attribute'
|
|
399
|
+
m.map_value(as_attribute: rule.field.to_sym)
|
|
400
|
+
else
|
|
401
|
+
m.map_value(to_instance: rule.field.to_sym)
|
|
402
|
+
end
|
|
403
|
+
end
|
|
404
|
+
end
|
|
405
|
+
end
|
|
406
|
+
end
|
|
407
|
+
end
|
|
408
|
+
end
|
|
409
|
+
|
|
410
|
+
def check_namespace_ambiguity(mapping)
|
|
411
|
+
return unless mapping.format.to_s == 'xml'
|
|
412
|
+
return if mapping.namespace_ref || @declared_namespaces.length < 2
|
|
413
|
+
|
|
414
|
+
raise ArgumentError,
|
|
415
|
+
"mapping #{mapping.format} #{mapping.target}: ambiguous default " \
|
|
416
|
+
"namespace — #{@declared_namespaces.length} namespaces in scope " \
|
|
417
|
+
"(#{@declared_namespaces.map(&:name).join(', ')}); declare one with " \
|
|
418
|
+
"'namespace <name>'"
|
|
419
|
+
end
|
|
420
|
+
|
|
421
|
+
def namespace_class_for(ref)
|
|
422
|
+
return nil if ref.nil?
|
|
423
|
+
|
|
424
|
+
require "lutaml/xml/namespace" unless defined?(Lutaml::Xml::Namespace)
|
|
425
|
+
|
|
426
|
+
ns_class = @namespace_classes[ref]
|
|
427
|
+
return ns_class if ns_class
|
|
428
|
+
|
|
429
|
+
declared = @declared_namespaces.find { |ns| ns.name.to_s == ref }
|
|
430
|
+
raise ArgumentError, "unknown namespace '#{ref}'" unless declared
|
|
431
|
+
|
|
432
|
+
ns_class = Class.new(Lutaml::Xml::Namespace)
|
|
433
|
+
uri = declared.uri
|
|
434
|
+
prefix = declared.prefix.to_s
|
|
435
|
+
ns_class.define_singleton_method(:uri) { uri }
|
|
436
|
+
ns_class.define_singleton_method(:prefix_default) { prefix }
|
|
437
|
+
@namespace_classes[ref] = ns_class
|
|
438
|
+
end
|
|
439
|
+
|
|
316
440
|
def build_attributes(klass_def)
|
|
317
441
|
Array(klass_def.attributes).map do |attr|
|
|
318
442
|
attr_name = attr.name.to_sym
|
|
319
|
-
raw_type = attr
|
|
443
|
+
raw_type = resolve_values_type(klass_def, attr)
|
|
320
444
|
type = resolve_type(raw_type)
|
|
321
445
|
options = build_options(attr)
|
|
322
446
|
[attr_name, raw_type, type, options]
|
|
323
447
|
end
|
|
324
448
|
end
|
|
325
449
|
|
|
450
|
+
# RS 3001 §Attribute: `values { v1, v2 }` expands into an anonymous
|
|
451
|
+
# enum baked from the inline set; the attribute is typed with it.
|
|
452
|
+
def resolve_values_type(klass_def, attr)
|
|
453
|
+
return attr.type.to_s unless attr.value_set&.any?
|
|
454
|
+
|
|
455
|
+
enum_name = anonymous_enum_name(klass_def.name, attr.name)
|
|
456
|
+
members = attr.value_set.map { |v| ::Lutaml::Lml::TopElementAttribute.new(name: v.to_s) }
|
|
457
|
+
compile_enum(::Lutaml::Lml::Enum.new(name: enum_name, attributes: members))
|
|
458
|
+
enum_name
|
|
459
|
+
end
|
|
460
|
+
|
|
461
|
+
def anonymous_enum_name(class_name, attr_name)
|
|
462
|
+
base = class_name.to_s.split('::').last
|
|
463
|
+
attr_part = attr_name.to_s.split('_').map(&:capitalize).join
|
|
464
|
+
"#{base}#{attr_part}Values"
|
|
465
|
+
end
|
|
466
|
+
|
|
326
467
|
def resolve_type(type_name)
|
|
327
468
|
if TYPE_MAP.key?(type_name)
|
|
328
469
|
TYPE_MAP[type_name]
|
|
@@ -391,20 +532,76 @@ module Lutaml
|
|
|
391
532
|
raise ValidationError, "Unrecognized cardinality token: #{val.inspect}"
|
|
392
533
|
end
|
|
393
534
|
|
|
535
|
+
# L7a point 3: from_table members are baked at compile time — the
|
|
536
|
+
# artifact table's rows become the values, validated against the
|
|
537
|
+
# declared member_fields (unknown field = error; missing = nil).
|
|
538
|
+
# The member name comes from the row's "name" column.
|
|
539
|
+
def expand_from_table(enum_def)
|
|
540
|
+
return unless enum_def.from_table_name
|
|
541
|
+
|
|
542
|
+
rows = artifact_table_rows(enum_def)
|
|
543
|
+
known = Array(enum_def.member_fields).map { |f| f.name.to_s }
|
|
544
|
+
rows.each do |row|
|
|
545
|
+
row = row.transform_keys(&:to_s)
|
|
546
|
+
member_name = row.fetch('name')
|
|
547
|
+
unknown = row.keys - known - ['name']
|
|
548
|
+
if unknown.any?
|
|
549
|
+
raise ArgumentError,
|
|
550
|
+
"enum #{enum_def.name}: table row #{member_name.inspect} has " \
|
|
551
|
+
"undeclared fields #{unknown.inspect} (declared: #{known.inspect})"
|
|
552
|
+
end
|
|
553
|
+
payload = row.slice(*known).compact
|
|
554
|
+
enum_def.attributes << TopElementAttribute.new(
|
|
555
|
+
name: member_name,
|
|
556
|
+
properties: payload.map do |field, value|
|
|
557
|
+
TopElementAttribute.new(name: field, value: value)
|
|
558
|
+
end
|
|
559
|
+
)
|
|
560
|
+
end
|
|
561
|
+
end
|
|
562
|
+
|
|
563
|
+
def artifact_table_rows(enum_def)
|
|
564
|
+
reference = enum_def.from_table_artifact || @default_artifact
|
|
565
|
+
path = artifact_path_for(reference.to_s)
|
|
566
|
+
unless path
|
|
567
|
+
raise ArgumentError,
|
|
568
|
+
"enum #{enum_def.name}: from_table artifact #{reference.inspect} not found"
|
|
569
|
+
end
|
|
570
|
+
|
|
571
|
+
envelope = JSON.parse(File.read(path))
|
|
572
|
+
table = envelope['tables'].to_h[enum_def.from_table_name]
|
|
573
|
+
unless table
|
|
574
|
+
raise ArgumentError,
|
|
575
|
+
"enum #{enum_def.name}: table #{enum_def.from_table_name.inspect} " \
|
|
576
|
+
"not found in artifact #{reference.inspect}"
|
|
577
|
+
end
|
|
578
|
+
|
|
579
|
+
table['rows'].to_a
|
|
580
|
+
end
|
|
581
|
+
|
|
394
582
|
def extract_enum_values(enum_def)
|
|
395
583
|
member_fields = Array(enum_def.member_fields).map do |f|
|
|
396
584
|
[f.name.to_s, f.type.to_s]
|
|
397
585
|
end
|
|
398
586
|
enum_def.attributes.map do |attr|
|
|
399
|
-
description =
|
|
400
|
-
nested.name == 'description'
|
|
401
|
-
end
|
|
587
|
+
description = member_description(attr)
|
|
402
588
|
payload = extract_member_payload(attr)
|
|
403
589
|
validate_member_payload(enum_def, attr, payload, member_fields)
|
|
404
|
-
EnumValue.new(attr.name.to_s, description
|
|
590
|
+
EnumValue.new(attr.name.to_s, description, payload)
|
|
405
591
|
end
|
|
406
592
|
end
|
|
407
593
|
|
|
594
|
+
# The member description rides a nested attribute named `description`
|
|
595
|
+
# (bare-member form) or `definition` (RS 3001 `value` blocks); both
|
|
596
|
+
# spellings are accepted.
|
|
597
|
+
def member_description(attr)
|
|
598
|
+
nested = Array(attr.attributes).find do |n|
|
|
599
|
+
%w[description definition].include?(n.name.to_s)
|
|
600
|
+
end
|
|
601
|
+
text = nested&.type || nested&.definition || nested&.value
|
|
602
|
+
text.is_a?(Hash) && text.key?(:string) ? text[:string] : text
|
|
603
|
+
end
|
|
604
|
+
|
|
408
605
|
# L7a: per-member payload lines ride the keyword-attribute
|
|
409
606
|
# properties (`abbreviation = "WD"`)
|
|
410
607
|
def extract_member_payload(attr)
|
|
@@ -457,18 +654,12 @@ module Lutaml
|
|
|
457
654
|
def register_string_formats(doc)
|
|
458
655
|
defaults = doc.packages.flat_map(&:default_string_formats)
|
|
459
656
|
doc.classes.each do |class_def|
|
|
460
|
-
Array(class_def.string_formats)
|
|
461
|
-
|
|
462
|
-
end
|
|
463
|
-
next if Array(class_def.string_formats).any? do |f|
|
|
464
|
-
f.format == defaults.first&.format
|
|
465
|
-
end
|
|
466
|
-
|
|
467
|
-
defaults.each do |fmt|
|
|
468
|
-
next if Array(class_def.string_formats).map(&:format).include?(fmt.format)
|
|
657
|
+
declared = Array(class_def.string_formats)
|
|
658
|
+
declared.each { |fmt| register_string_format(class_def.name.to_s, fmt) }
|
|
469
659
|
|
|
470
|
-
|
|
471
|
-
|
|
660
|
+
declared_formats = declared.map(&:format)
|
|
661
|
+
defaults.reject { |fmt| declared_formats.include?(fmt.format) }
|
|
662
|
+
.each { |fmt| register_string_format(class_def.name.to_s, fmt) }
|
|
472
663
|
end
|
|
473
664
|
end
|
|
474
665
|
|
|
@@ -7,6 +7,7 @@ module Lutaml
|
|
|
7
7
|
attribute :name, :string
|
|
8
8
|
attribute :wire_mappings, "Lutaml::Lml::Mapping", collection: true, default: -> { [] }
|
|
9
9
|
attribute :derived_fields, "Lutaml::Lml::DerivedField", collection: true, default: -> { [] }
|
|
10
|
+
attribute :serialization_mappings, "Lutaml::Lml::SerializationMapping", collection: true, default: -> { [] }
|
|
10
11
|
attribute :definition, Lutaml::Lml::Types::TextType
|
|
11
12
|
attribute :keyword, :string, default: "dataType"
|
|
12
13
|
attribute :stereotype, :string, collection: true, default: -> { [] }
|
|
@@ -16,6 +16,8 @@ module Lutaml
|
|
|
16
16
|
attribute :data_types, "Lutaml::Lml::DataType", collection: true, default: -> { [] }
|
|
17
17
|
attribute :enums, "Lutaml::Lml::Enum", collection: true, default: -> { [] }
|
|
18
18
|
attribute :packages, "Lutaml::Lml::Package", collection: true, default: -> { [] }
|
|
19
|
+
attribute :namespaces, "Lutaml::Lml::Namespace", collection: true, default: -> { [] }
|
|
20
|
+
attribute :serialization_mappings, "Lutaml::Lml::SerializationMapping", collection: true, default: -> { [] }
|
|
19
21
|
attribute :primitives, "Lutaml::Lml::PrimitiveType", collection: true, default: -> { [] }
|
|
20
22
|
attribute :associations, "Lutaml::Lml::Association", collection: true, default: -> { [] }
|
|
21
23
|
attribute :diagrams, "Lutaml::Lml::Diagram", collection: true, default: -> { [] }
|
|
@@ -18,6 +18,8 @@ module Lutaml
|
|
|
18
18
|
default: -> { [] }
|
|
19
19
|
attribute :modifier, :string
|
|
20
20
|
attribute :member_fields, "Lutaml::Lml::MemberField", collection: true, default: -> { [] }
|
|
21
|
+
attribute :from_table_name, :string
|
|
22
|
+
attribute :from_table_artifact, :string
|
|
21
23
|
attribute :operations, "Lutaml::Lml::Operation", collection: true, default: -> { [] }
|
|
22
24
|
attribute :values, "Lutaml::Lml::Value", collection: true, default: -> { [] }
|
|
23
25
|
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
module Lutaml
|
|
4
4
|
module Lml
|
|
5
5
|
class InstanceCollection < Lutaml::Model::Serializable
|
|
6
|
+
attribute :name, :string
|
|
6
7
|
attribute :instances, "Lutaml::Lml::Instance", collection: true, default: []
|
|
7
8
|
attribute :imports, "Lutaml::Lml::InstancesImport", collection: true, default: []
|
|
8
9
|
attribute :exports, "Lutaml::Lml::InstancesExport", collection: true, default: []
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module Lml
|
|
5
|
+
# One line of a serialization mapping: what kind of mapping (element,
|
|
6
|
+
# attribute, content, map, key, value), the serialization-side wire
|
|
7
|
+
# name, the model attribute it binds, and the qualifier mode for
|
|
8
|
+
# key/value collections (to_instance, as_attribute).
|
|
9
|
+
class MappingRule < Lutaml::Model::Serializable
|
|
10
|
+
attribute :kind, :string
|
|
11
|
+
attribute :wire, :string
|
|
12
|
+
attribute :field, :string
|
|
13
|
+
attribute :mode, :string
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module Lml
|
|
5
|
+
# RS 3010 extension: a declared XML namespace (uri + optional prefix).
|
|
6
|
+
# The compiler generates the corresponding Lutaml::Model::XmlNamespace
|
|
7
|
+
# subclass for the compiled model namespace.
|
|
8
|
+
class Namespace < Lutaml::Model::Serializable
|
|
9
|
+
attribute :name, :string
|
|
10
|
+
attribute :uri, :string
|
|
11
|
+
attribute :prefix, :string
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module Lml
|
|
5
|
+
# RS 3010 extension: a serialization mapping declared separately from
|
|
6
|
+
# its class and linked by target name —
|
|
7
|
+
# mapping xml Ceramic { element "ceramic" … }
|
|
8
|
+
class SerializationMapping < Lutaml::Model::Serializable
|
|
9
|
+
attribute :format, :string
|
|
10
|
+
attribute :target, :string
|
|
11
|
+
attribute :element_name, :string
|
|
12
|
+
attribute :namespace_ref, :string
|
|
13
|
+
attribute :mixed_content, :boolean, default: false
|
|
14
|
+
attribute :rules, 'Lutaml::Lml::MappingRule', collection: true, default: -> { [] }
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -19,6 +19,8 @@ module Lutaml
|
|
|
19
19
|
attribute :definition, Lutaml::Lml::Types::TextType
|
|
20
20
|
attribute :association, :string
|
|
21
21
|
attribute :default, :string
|
|
22
|
+
attribute :value_set, :string, collection: true, default: -> { [] }
|
|
23
|
+
attribute :pattern, :string
|
|
22
24
|
|
|
23
25
|
# LML-specific attributes
|
|
24
26
|
attribute :properties, "Lutaml::Lml::TopElementAttribute", collection: true, default: -> { [] }
|
|
@@ -53,6 +55,8 @@ module Lutaml
|
|
|
53
55
|
map "definition", to: :definition
|
|
54
56
|
map "association", to: :association
|
|
55
57
|
map "default", to: :default
|
|
58
|
+
map "value_set", to: :value_set
|
|
59
|
+
map "pattern", to: :pattern
|
|
56
60
|
map "properties", to: :properties
|
|
57
61
|
map "value", to: :value, with: { to: :value_to, from: :value_from }
|
|
58
62
|
map "attributes", to: :attributes
|
|
@@ -26,6 +26,7 @@ module Lutaml
|
|
|
26
26
|
attribute :wire_mappings, "Lutaml::Lml::Mapping", collection: true, default: -> { [] }
|
|
27
27
|
attribute :string_formats, "Lutaml::Lml::StringFormat", collection: true, default: -> { [] }
|
|
28
28
|
attribute :derived_fields, "Lutaml::Lml::DerivedField", collection: true, default: -> { [] }
|
|
29
|
+
attribute :serialization_mappings, "Lutaml::Lml::SerializationMapping", collection: true, default: -> { [] }
|
|
29
30
|
|
|
30
31
|
# LML-specific
|
|
31
32
|
attribute :parent_class, :string
|