lutaml-lml 0.2.0 → 0.3.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 +84 -18
- data/lib/lutaml/lml/data_processor/value_processing.rb +6 -1
- data/lib/lutaml/lml/document_builder.rb +72 -3
- data/lib/lutaml/lml/format/adapter/mapping.rb +12 -0
- data/lib/lutaml/lml/format/adapter/standard_adapter.rb +41 -12
- data/lib/lutaml/lml/formatter/elk.rb +45 -0
- data/lib/lutaml/lml/formatter/graphviz/node_formatter.rb +2 -1
- data/lib/lutaml/lml/formatter/svg_pdf/path_parser.rb +134 -0
- data/lib/lutaml/lml/formatter/svg_pdf.rb +281 -0
- data/lib/lutaml/lml/formatter.rb +2 -0
- data/lib/lutaml/lml/grammar/lml.artifact.json +7265 -4550
- data/lib/lutaml/lml/grammar/lml.parg +230 -115
- data/lib/lutaml/lml/layout/elk_engine.rb +15 -0
- data/lib/lutaml/lml/layout/elk_graph_builder.rb +129 -0
- data/lib/lutaml/lml/layout/elk_svg_markers.rb +45 -0
- data/lib/lutaml/lml/layout/elk_svg_renderer.rb +119 -0
- data/lib/lutaml/lml/layout/graph_viz_engine.rb +1 -1
- data/lib/lutaml/lml/layout.rb +4 -0
- data/lib/lutaml/lml/model_compiler/enums.rb +172 -0
- data/lib/lutaml/lml/model_compiler/hydration.rb +123 -0
- data/lib/lutaml/lml/model_compiler/serialization_mappings.rb +162 -0
- data/lib/lutaml/lml/model_compiler/string_formats.rb +42 -0
- data/lib/lutaml/lml/model_compiler/validation.rb +65 -0
- data/lib/lutaml/lml/model_compiler.rb +46 -453
- data/lib/lutaml/lml/models/document.rb +29 -0
- data/lib/lutaml/lml/models/operation_parameter.rb +1 -0
- data/lib/lutaml/lml/models/reference.rb +23 -0
- data/lib/lutaml/lml/models/top_element_attribute.rb +12 -0
- data/lib/lutaml/lml/models/uml_class.rb +1 -1
- data/lib/lutaml/lml/preprocessor.rb +44 -79
- data/lib/lutaml/lml/transform.rb +54 -6
- data/lib/lutaml/lml/validator.rb +88 -19
- data/lib/lutaml/lml/version.rb +1 -1
- data/lib/lutaml/lml.rb +19 -3
- metadata +17 -4
|
@@ -10,139 +10,241 @@
|
|
|
10
10
|
# `[ x as k ]` yields k: nil when absent (Parslet omits the key —
|
|
11
11
|
# consumers treat nil and absent as equivalent).
|
|
12
12
|
grammar Lml version "1.0.0" {
|
|
13
|
+
# ---- Trivia (parsanol-ruby#134 skip rule) ----
|
|
14
|
+
# Grammar-native trivia replaces the pre-parse comment stripping:
|
|
15
|
+
# `//` line and `/* */` block comments (RS 3001 par. Comment) plus
|
|
16
|
+
# blanks/tabs are consumed and ignored between tokens. Newlines stay
|
|
17
|
+
# grammar-visible (line-oriented rules keep their semantics; the
|
|
18
|
+
# preprocessor preserved them too). Atomic token rules keep string
|
|
19
|
+
# literals and identifiers contiguous.
|
|
20
|
+
skip = trivia
|
|
21
|
+
trivia = 1*( ( 1*( " " / %x09 ) ) / ( line_comment [ ( [ %x0D ] %x0A / %x0D ) ] ) / block_comment )
|
|
22
|
+
line_comment = "//" ( *( !newline any_char ) )
|
|
23
|
+
block_comment = "/*" ( *( !"*/" any_char ) ) "*/"
|
|
24
|
+
atomic kw_association_sp = "association" 1*" "
|
|
25
|
+
atomic kw_artifact_sp = "artifact" 1*" "
|
|
26
|
+
atomic kw_attribute_sp = "attribute" 1*" "
|
|
27
|
+
atomic kw_caption_sp = "caption" 1*" "
|
|
28
|
+
atomic kw_class_sp = "class" 1*" "
|
|
29
|
+
atomic kw_visibility_sp = "visibility" 1*" "
|
|
30
|
+
atomic kw_version_sp = "version" 1*" "
|
|
31
|
+
version_declaration = kw_version_sp quoted_string as version
|
|
32
|
+
atomic kw_abstract_sp = "abstract" 1*" "
|
|
33
|
+
atomic kw_collection_sp = "collection" 1*" "
|
|
34
|
+
atomic kw_condition_sp = "condition" 1*" "
|
|
35
|
+
atomic kw_data_type_sp = "data_type" 1*" "
|
|
36
|
+
atomic kw_definition_sp = "definition" 1*" "
|
|
37
|
+
atomic kw_derive_sp = "derive" 1*" "
|
|
38
|
+
atomic kw_element_sp = "element" 1*" "
|
|
39
|
+
atomic kw_enum_sp = "enum" 1*" "
|
|
40
|
+
atomic kw_fontname_sp = "fontname" 1*" "
|
|
41
|
+
atomic kw_format_sp = "format" 1*" "
|
|
42
|
+
atomic kw_hide_sp = "hide" 1*" "
|
|
43
|
+
atomic kw_import_sp = "import" 1*" "
|
|
44
|
+
atomic kw_instance_sp = "instance" 1*" "
|
|
45
|
+
atomic kw_isa_sp = "isa" 1*" "
|
|
46
|
+
atomic kw_map_sp = "map" 1*" "
|
|
47
|
+
atomic kw_map_attribute_sp = "map_attribute" 1*" "
|
|
48
|
+
atomic kw_map_content_sp = "map_content" 1*" "
|
|
49
|
+
atomic kw_map_element_sp = "map_element" 1*" "
|
|
50
|
+
atomic kw_map_key_sp = "map_key" 1*" "
|
|
51
|
+
atomic kw_map_value_sp = "map_value" 1*" "
|
|
52
|
+
atomic kw_mapping_sp = "mapping" 1*" "
|
|
53
|
+
atomic kw_member_sp = "member" 1*" "
|
|
54
|
+
atomic kw_member_type_sp = "member_type" 1*" "
|
|
55
|
+
atomic kw_namespace_sp = "namespace" 1*" "
|
|
56
|
+
atomic kw_owner_sp = "owner" 1*" "
|
|
57
|
+
atomic kw_owner_type_sp = "owner_type" 1*" "
|
|
58
|
+
atomic kw_package_sp = "package" 1*" "
|
|
59
|
+
atomic kw_pattern_sp = "pattern" 1*" "
|
|
60
|
+
atomic kw_prefix_sp = "prefix" 1*" "
|
|
61
|
+
atomic kw_primitive_sp = "primitive" 1*" "
|
|
62
|
+
atomic kw_require_sp = "require" 1*" "
|
|
63
|
+
atomic kw_show_sp = "show" 1*" "
|
|
64
|
+
atomic kw_string_format_sp = "string_format" 1*" "
|
|
65
|
+
atomic kw_title_sp = "title" 1*" "
|
|
66
|
+
atomic kw_to_instance_sp = "to_instance" 1*" "
|
|
67
|
+
atomic kw_uri_sp = "uri" 1*" "
|
|
68
|
+
atomic kw_value_sp = "value" 1*" "
|
|
69
|
+
atomic kw_values_sp = "values" 1*" "
|
|
13
70
|
# ---- Character primitives ----
|
|
14
71
|
alpha = %x41-5A / %x61-7A
|
|
15
|
-
word = %x41-5A / %x61-7A / %x30-39 / "_"
|
|
72
|
+
atomic word = %x41-5A / %x61-7A / %x30-39 / "_"
|
|
16
73
|
cardchar = %x30-39 / %x61-7A / "*"
|
|
17
74
|
# Parslet's `any`: any Unicode character (not just bytes)
|
|
18
75
|
any_char = %x00-10FFFF
|
|
19
76
|
# [^\s\n\r] — regex \s covers space, tab, CR, LF, VT, FF
|
|
20
|
-
nonspace = !" " !%x09 !%x0A !%x0B !%x0C !%x0D any_char
|
|
77
|
+
atomic nonspace = !" " !%x09 !%x0A !%x0B !%x0C !%x0D any_char
|
|
21
78
|
quote = %x22 / %x27
|
|
22
79
|
newline = %x0D / %x0A
|
|
23
80
|
|
|
24
|
-
|
|
25
|
-
whitespace = 1*( " " / %x09 / [ %x0D ] %x0A / %x0D / ";" )
|
|
81
|
+
whitespace = 1*( ( 1*( " " / %x09 ) ) / ( [ %x0D ] %x0A ) / %x0D / ";" / line_comment / block_comment )
|
|
26
82
|
|
|
27
83
|
# match['"\'']
|
|
28
84
|
quotes = %x22 / %x27
|
|
29
85
|
|
|
30
|
-
#
|
|
31
|
-
|
|
32
|
-
|
|
86
|
+
# RS 3001 §String values: exactly two escape sequences, \" and \\,
|
|
87
|
+
# strict in both quote kinds; a backslash before anything else is not
|
|
88
|
+
# a string. atomic keeps the scan verbatim (skip trivia must not eat
|
|
89
|
+
# interior runs).
|
|
90
|
+
atomic dq_string = %x22 ( *( ( %x5C %x22 ) / ( %x5C %x5C ) / ( !( %x22 / %x5C ) %x00-10FFFF ) ) ) as string %x22
|
|
91
|
+
atomic sq_string = %x27 ( *( ( %x5C %x27 ) / ( %x5C %x5C ) / ( !( %x27 / %x5C ) %x00-10FFFF ) ) ) as string %x27
|
|
33
92
|
quoted_string = dq_string / sq_string
|
|
34
93
|
# any_quoted(:path) for view import
|
|
35
|
-
dq_path = %x22
|
|
36
|
-
sq_path = %x27
|
|
94
|
+
atomic dq_path = %x22 until "\"" as path %x22
|
|
95
|
+
atomic sq_path = %x27 until "'" as path %x27
|
|
37
96
|
|
|
38
97
|
boolean = ( "true" / "false" ) as boolean
|
|
98
|
+
null_literal = "null" as null
|
|
39
99
|
number = ( 1*%x30-39 "." 1*%x30-39 ) as float / ( 1*%x30-39 ) as number
|
|
40
100
|
variable = quoted_string / 1*word
|
|
41
101
|
reference = ( "reference:(" / "ref:(" ) ( variable *( "." variable ) ) as reference ")"
|
|
42
102
|
range = ( variable as start ".." variable as end ) as range
|
|
43
103
|
namespaced_identifier = variable *( "::" variable )
|
|
44
|
-
comment_definition =
|
|
45
|
-
comment_multiline_definition =
|
|
104
|
+
comment_definition = ( "**" / "#" ) ( *( !newline ANY ) ) as comments
|
|
105
|
+
comment_multiline_definition = "*|" until "|*" as comments [ whitespace ] "|*"
|
|
46
106
|
|
|
47
|
-
|
|
107
|
+
# Spaces are interior-only in name and type tokens: a run may be
|
|
108
|
+
# followed by space-then-run, never a trailing space. The trailing
|
|
109
|
+
# separator is left for skip trivia, so captures need no post-strip
|
|
110
|
+
# and quoted string values keep their padding verbatim (RS 3001
|
|
111
|
+
# §String values).
|
|
112
|
+
# Atomic composite: the interior %x20 must match verbatim — skip
|
|
113
|
+
# trivia injects before sequence children and would otherwise eat the
|
|
114
|
+
# space before the grammar's own literal can (the type_token
|
|
115
|
+
# precedent).
|
|
116
|
+
atomic class_run = 1*( %x41-5A / %x61-7A / %x30-39 / "_" / "-" / ":" / "." )
|
|
117
|
+
atomic class_name_chars = class_run *( %x20 class_run )
|
|
48
118
|
class_name = class_name_chars [ "(" class_name_chars ")" ]
|
|
49
119
|
cardinality_body_definition = cardchar as min [ ".." [ cardchar as max ] ]
|
|
50
120
|
cardinality = "[" cardinality_body_definition as cardinality "]"
|
|
51
121
|
|
|
52
|
-
data_value = boolean / reference / range / number / quoted_string
|
|
53
|
-
key_value_pair = variable as key
|
|
54
|
-
key_value_map = "{" [ whitespace ] ( *( key_value_pair whitespace ) ) as key_value_map "}"
|
|
122
|
+
data_value = boolean / null_literal / reference / range / number / quoted_string
|
|
123
|
+
key_value_pair = variable as key [ "=" ] data_value as value
|
|
124
|
+
key_value_map = "{" [ whitespace ] ( *( key_value_pair [ whitespace ] ) ) as key_value_map "}"
|
|
55
125
|
kw_visibility_modifier = "+" / "-" / "#" / "~"
|
|
56
126
|
|
|
57
127
|
# ---- Attributes ----
|
|
58
|
-
attribute = comment_definition / ( variable as key [
|
|
59
|
-
attributes = ( *( attribute_line / whitespace ) ) as attributes
|
|
60
|
-
attribute_line =
|
|
128
|
+
attribute = comment_definition / ( variable as key [ "+" as add ] [ "=" ] attribute_value as value )
|
|
129
|
+
attributes = ( *( definition_body / definition_line / attribute_line / whitespace ) ) as attributes
|
|
130
|
+
attribute_line = attribute [ [ "," ] whitespace ]
|
|
61
131
|
visibility = kw_visibility_modifier as visibility_modifier
|
|
62
132
|
attribute_stereotype = "<<" ( 1*( %x41-5A / %x61-7A / %x30-39 / "_" / "-" / "/" ) ) as stereotype ">>"
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
133
|
+
atomic type_run = 1*( !( "//" / "/*" ) ( %x41-5A / %x61-7A / %x30-39 / "_" / "-" / ":" / "/" / "+" ) )
|
|
134
|
+
atomic type_token = type_run *( %x20 type_run )
|
|
135
|
+
atomic namechar = %x41-5A / %x61-7A / %x30-39 / "_" / "-" / "/" / "+"
|
|
136
|
+
attribute_type = [ ":" ] [ attribute_stereotype ] [ quotes as type_literal ] ( type_token ) as type [ quotes ]
|
|
66
137
|
attribute_name = ( 1*namechar ) as name
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
138
|
+
atomic quoted_name_run = 1*( %x41-5A / %x61-7A / %x30-39 / "_" / "-" / "/" / "+" )
|
|
139
|
+
atomic quoted_name_body = quoted_name_run *( %x20 quoted_name_run )
|
|
140
|
+
attribute_definition_name = ( quotes ( quoted_name_body ) as name quotes ) / attribute_name
|
|
141
|
+
attribute_definition = ( [ visibility ] as visibility attribute_definition_name [ attribute_type ] [ cardinality ] [ class_body ] ) as attributes
|
|
142
|
+
|
|
143
|
+
keyword_type_argument = "type" ( 1*nonspace ) as type [ whitespace ]
|
|
144
|
+
keyword_cardinality_argument = "cardinality" cardinality_body_definition as cardinality [ whitespace ]
|
|
145
|
+
keyword_any_argument = ( keyword_any_name ) as name [ "=" / ":" ] attribute_value as value [ whitespace ]
|
|
146
|
+
atomic keyword_any_name = 1*( !( "{" / "}" ) nonspace )
|
|
147
|
+
keyword_values_argument = kw_values_sp ( ( value_set_body ) as value_set / ( variable ) as value_set_ref ) [ whitespace ]
|
|
148
|
+
keyword_pattern_argument = ( kw_pattern_sp "/" ( *( ( "\\" any_char ) / ( !"/" any_char ) ) ) as pattern "/" [ whitespace ] )
|
|
75
149
|
value_set_body = "{" [ whitespace ] ( *( ( ( data_value / variable ) as item ) [ "," ] [ whitespace ] ) ) as items "}"
|
|
76
150
|
keyword_attribute_options = *( keyword_type_argument / keyword_cardinality_argument / keyword_values_argument / keyword_pattern_argument / ( keyword_any_argument ) as properties )
|
|
77
151
|
keyword_attribute_body = "{" [ whitespace ] keyword_attribute_options [ whitespace ] "}"
|
|
78
|
-
keyword_attribute_definition = (
|
|
152
|
+
keyword_attribute_definition = ( kw_attribute_sp attribute_name keyword_attribute_body ) as attributes
|
|
79
153
|
|
|
80
154
|
# ---- Associations ----
|
|
81
|
-
owner_cardinality =
|
|
82
|
-
owner_attribute_name = "#" [ visibility ]
|
|
83
|
-
owner_definition = [ whitespace ]
|
|
84
|
-
owner_type = [ whitespace ]
|
|
85
|
-
member_cardinality =
|
|
86
|
-
member_attribute_name = "#" [ visibility ]
|
|
87
|
-
member_definition = [ whitespace ]
|
|
88
|
-
member_type = [ whitespace ]
|
|
155
|
+
owner_cardinality = "[" cardinality_body_definition as owner_end_cardinality "]"
|
|
156
|
+
owner_attribute_name = "#" [ visibility ] variable as owner_end_attribute_name
|
|
157
|
+
owner_definition = [ whitespace ] kw_owner_sp variable as owner_end [ owner_attribute_name ] [ owner_cardinality ]
|
|
158
|
+
owner_type = [ whitespace ] kw_owner_type_sp variable as owner_end_type
|
|
159
|
+
member_cardinality = "[" cardinality_body_definition as member_end_cardinality "]"
|
|
160
|
+
member_attribute_name = "#" [ visibility ] variable as member_end_attribute_name
|
|
161
|
+
member_definition = [ whitespace ] kw_member_sp variable as member_end [ member_attribute_name ] [ member_cardinality ]
|
|
162
|
+
member_type = [ whitespace ] kw_member_type_sp variable as member_end_type
|
|
89
163
|
association_inner_definition = ( owner_type / member_type / owner_definition / member_definition / comment_definition / comment_multiline_definition ) [ whitespace ]
|
|
90
|
-
association_body =
|
|
91
|
-
association_definition = [ whitespace ]
|
|
164
|
+
association_body = "{" [ whitespace ] ( *( association_inner_definition ) ) as members "}"
|
|
165
|
+
association_definition = [ whitespace ] kw_association_sp [ variable as name ] association_body
|
|
92
166
|
|
|
93
167
|
# ---- Definitions ----
|
|
94
|
-
|
|
168
|
+
# ---- Operations (class methods) ----
|
|
169
|
+
# Symbol visibility reuses the attribute prefix (+ - # ~); the
|
|
170
|
+
# transform maps it to public/private/protected/friendly.
|
|
171
|
+
operation_definition = [ whitespace ] [ visibility ] ( 1*word ) as name "(" [ ( operation_params ) as owned_parameter ] ")" [ operation_return ] [ operation_body ] [ whitespace ]
|
|
172
|
+
operation_params = operation_param *( "," [ whitespace ] operation_param )
|
|
173
|
+
operation_return = ":" ( type_token ) as return_type [ cardinality ]
|
|
174
|
+
# A parenthesized alternative is not an Entity, so an optional body
|
|
175
|
+
# of `x as k` around it gets no skip injection - the blank after "="
|
|
176
|
+
# was unconsumable. Reference a rule instead.
|
|
177
|
+
operation_param = ( [ ( "in" / "out" / "inout" ) as direction 1*" " ] ( 1*word ) as name [ ":" ] param_type as type [ "=" ] param_default_value as param_default )
|
|
178
|
+
param_default_value = [ quoted_string / ( 1*word ) / number ]
|
|
179
|
+
# The attribute pattern: separator as its own optional, value as a
|
|
180
|
+
# rule sibling. A `x as k` optional body gets no interior skip
|
|
181
|
+
# injection, so [ ":" type as t ] cannot consume the blank after
|
|
182
|
+
# the separator.
|
|
183
|
+
param_type = [ type_token ]
|
|
184
|
+
operation_body = "{" [ whitespace ] [ ( *( ( 1*word ) as stereotype [ "," ] [ whitespace ] ) ) as stereotype ] [ whitespace ] "}"
|
|
185
|
+
|
|
186
|
+
class_modifier = [ [ whitespace ] modifier_token ]
|
|
187
|
+
atomic modifier_token = ( ( "abstract" / "interface" ) as modifier 1*" " )
|
|
188
|
+
# RS 3001 par. Declaration modifiers: keyword prefix (sugar) and the
|
|
189
|
+
# canonical in-block declarations. The `package` visibility keyword is
|
|
190
|
+
# guarded: it must be followed by a declaration keyword, so a bare
|
|
191
|
+
# `package P { }` still binds as a package declaration.
|
|
192
|
+
atomic element_visibility = ( ( "public" / "private" / "protected" / ( "package" &( 1*" " ( kw_class_sp / kw_enum_sp / kw_data_type_sp / kw_primitive_sp / kw_package_sp ) ) ) ) as visibility 1*" " )
|
|
193
|
+
visibility_declaration = kw_visibility_sp ( "public" / "private" / "protected" / "package" ) as visibility
|
|
194
|
+
abstract_declaration = kw_abstract_sp ( "true" / "false" ) as abstract
|
|
95
195
|
# ---- Namespaces (RS 3010 extension: lutaml-model XmlNamespace) ----
|
|
96
|
-
namespace_uri_line = (
|
|
97
|
-
namespace_prefix_line = (
|
|
196
|
+
namespace_uri_line = ( kw_uri_sp ( quoted_string / ( 1*word ) ) as uri [ whitespace ] )
|
|
197
|
+
namespace_prefix_line = ( kw_prefix_sp ( quoted_string / ( 1*word ) ) as prefix [ whitespace ] )
|
|
98
198
|
namespace_body = "{" [ whitespace ] ( *( namespace_uri_line / namespace_prefix_line ) ) as members "}"
|
|
99
|
-
namespace_inline = (
|
|
100
|
-
namespace_definition = ( [ whitespace ]
|
|
199
|
+
namespace_inline = ( ( quoted_string ) as uri [ kw_prefix_sp ( quoted_string / ( 1*word ) ) as prefix ] )
|
|
200
|
+
namespace_definition = ( [ whitespace ] kw_namespace_sp ( 1*word ) as name ( namespace_body / namespace_inline ) [ whitespace ] ) as namespaces
|
|
101
201
|
|
|
102
202
|
# ---- Serialization mappings (RS 3010 extension: sibling-linked) ----
|
|
103
203
|
serialization_body = "{" [ whitespace ] ( *( serialization_line ) ) as rules "}"
|
|
104
204
|
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 = (
|
|
106
|
-
mapping_element_name_line = (
|
|
107
|
-
mapping_element_line = (
|
|
108
|
-
element_args = ( dq_string as wire [
|
|
109
|
-
mapping_attribute_line = (
|
|
110
|
-
attribute_args = ( dq_string as wire [
|
|
111
|
-
mapping_content_line = (
|
|
112
|
-
mapping_namespace_line = (
|
|
113
|
-
mapping_mixed_line = (
|
|
114
|
-
mapping_map_line = (
|
|
115
|
-
map_args = ( dq_string as wire
|
|
116
|
-
mapping_map_key_line = (
|
|
117
|
-
mapping_map_value_line = (
|
|
118
|
-
|
|
205
|
+
legacy_map_line = ( kw_map_sp dq_string as wire "," "to:" ( quoted_string / ( 1*word ) ) as to [ whitespace ] ) as legacy_map
|
|
206
|
+
mapping_element_name_line = ( kw_element_sp dq_string as element_name [ whitespace ] ) as element_name
|
|
207
|
+
mapping_element_line = ( kw_map_element_sp ( element_args ) ) as map_element
|
|
208
|
+
element_args = ( dq_string as wire [ ( 1*word ) as field ] / ( 1*word ) as field )
|
|
209
|
+
mapping_attribute_line = ( kw_map_attribute_sp ( attribute_args ) ) as map_attribute
|
|
210
|
+
attribute_args = ( dq_string as wire [ ( 1*word ) as field ] / ( 1*word ) as field )
|
|
211
|
+
mapping_content_line = ( kw_map_content_sp ( 1*word ) as field ) as map_content
|
|
212
|
+
mapping_namespace_line = ( kw_namespace_sp ( 1*word ) as namespace_ref ) as map_namespace
|
|
213
|
+
mapping_mixed_line = ( "mixed_content" ) as mixed_content
|
|
214
|
+
mapping_map_line = ( kw_map_sp map_args ) as map
|
|
215
|
+
map_args = ( dq_string as wire ( 1*word ) as field / dq_string as wire / ( 1*word ) as field )
|
|
216
|
+
mapping_map_key_line = ( kw_map_key_sp kw_to_instance_sp ( 1*word ) as field ) as map_key
|
|
217
|
+
mapping_map_value_line = ( kw_map_value_sp map_value_mode ( 1*word ) as field ) as map_value
|
|
218
|
+
atomic map_value_mode = ( ( "to_instance" / "as_attribute" ) as mode 1*" " )
|
|
219
|
+
mapping_definition = ( [ whitespace ] kw_mapping_sp ( 1*word ) as format ( 1*word ) as target serialization_body [ whitespace ] ) as serialization_mappings
|
|
119
220
|
|
|
120
221
|
# ---- Mapping blocks (lutaml-model wire names) ----
|
|
121
222
|
map_name = ( quoted_string / 1*( word / "-" / "." ) )
|
|
122
|
-
map_line =
|
|
123
|
-
mapping_body =
|
|
124
|
-
class_mapping_section = ( [ whitespace ]
|
|
223
|
+
map_line = kw_map_sp ( map_name ) as wire "," "to:" ( map_name ) as to [ whitespace ]
|
|
224
|
+
mapping_body = "{" [ whitespace ] ( *( map_line ) ) as maps "}"
|
|
225
|
+
class_mapping_section = ( [ whitespace ] kw_mapping_sp ( 1*word ) as format serialization_body [ whitespace ] ) as serialization_mappings
|
|
125
226
|
|
|
126
|
-
derive_definition = ( [ whitespace ]
|
|
127
|
-
string_format_item = (
|
|
227
|
+
derive_definition = ( [ whitespace ] kw_derive_sp attribute_name ":" [ quotes ] ( type_token ) as type [ quotes ] ) as derived_field [ whitespace ]
|
|
228
|
+
string_format_item = ( sf_key_token ( quoted_string / 1*nonspace ) as value [ whitespace ] )
|
|
229
|
+
atomic sf_key_token = ( ( "artifact" / "root" ) as key 1*" " )
|
|
128
230
|
string_format_body = "{" [ whitespace ] ( *( string_format_item ) ) as items [ whitespace ] "}"
|
|
129
|
-
string_format_definition = ( [ whitespace ]
|
|
231
|
+
string_format_definition = ( [ whitespace ] kw_string_format_sp ( variable ) as format string_format_body ) as string_format
|
|
130
232
|
|
|
131
|
-
class_inner_definitions = definition_body / definition_line / class_mapping_section / derive_definition / string_format_definition / attribute_comma_definition / ( !(
|
|
233
|
+
class_inner_definitions = visibility_declaration / abstract_declaration / definition_body / definition_line / class_mapping_section / derive_definition / string_format_definition / ( operation_definition ) as operations / attribute_comma_definition / ( !( kw_attribute_sp ) attribute_definition ) / keyword_attribute_definition / comment_definition / comment_multiline_definition
|
|
132
234
|
class_inner_definition = class_inner_definitions [ whitespace ]
|
|
133
|
-
class_body =
|
|
134
|
-
parent_class =
|
|
135
|
-
class_definition = class_modifier [ whitespace ]
|
|
235
|
+
class_body = "{" [ whitespace ] ( *( class_inner_definition ) ) as members "}"
|
|
236
|
+
parent_class = "<" ( class_name_chars ) as parent_class
|
|
237
|
+
class_definition = [ whitespace ] [ element_visibility ] [ whitespace ] class_modifier [ whitespace ] kw_class_sp class_name as name [ parent_class ] [ attribute_stereotype ] [ class_body ]
|
|
136
238
|
|
|
137
239
|
# Balanced-brace definition text: nested "{...}" and "\"-escapes allowed
|
|
138
240
|
definition_content = *( ( "\\" any_char ) / ( "{" definition_content "}" ) / ( !"}" any_char ) )
|
|
139
|
-
definition_body =
|
|
140
|
-
definition_line = (
|
|
241
|
+
definition_body = "definition" [ whitespace ] "{" ( definition_content ) as definition "}"
|
|
242
|
+
definition_line = ( kw_definition_sp ( dq_string / sq_string ) ) as definition [ whitespace ]
|
|
141
243
|
|
|
142
|
-
member_field_line =
|
|
244
|
+
member_field_line = attribute_name ( 1*nonspace ) as member_field_type [ whitespace ]
|
|
143
245
|
member_fields_definition = ( [ whitespace ] "member_fields" [ whitespace ] "{" [ whitespace ] ( *( member_field_line ) ) as member_fields [ whitespace ] "}" ) as member_fields_decl
|
|
144
|
-
attribute_comma_definition = ( [ whitespace ]
|
|
145
|
-
enum_member_with_payload = ( [ whitespace ] attribute_name
|
|
246
|
+
attribute_comma_definition = ( [ whitespace ] kw_attribute_sp attribute_name "," ( type_token ) as type [ "{" [ whitespace ] keyword_attribute_options [ whitespace ] "}" ] [ whitespace ] ) as attributes
|
|
247
|
+
enum_member_with_payload = ( [ whitespace ] attribute_name ( class_body / keyword_attribute_body ) ) as attributes
|
|
146
248
|
|
|
147
249
|
# ---- Canonical value construct (RS 3001 par. Value / par. Enums) ----
|
|
148
250
|
# `value <name> { block }` is the canonical enum member; the bare
|
|
@@ -151,81 +253,81 @@ grammar Lml version "1.0.0" {
|
|
|
151
253
|
value_name = quoted_string / map_name
|
|
152
254
|
value_inner_definition = ( definition_body / definition_line / instance / attribute_line / comment_definition / comment_multiline_definition ) [ whitespace ]
|
|
153
255
|
value_body = "{" [ whitespace ] ( *( value_inner_definition ) ) as members "}"
|
|
154
|
-
value_definition = ( [ whitespace ]
|
|
256
|
+
value_definition = ( [ whitespace ] kw_value_sp ( value_name ) as value_name [ value_body ] [ whitespace ] ) as value_decl
|
|
155
257
|
|
|
156
|
-
from_table_definition = ( [ whitespace ] "from_table"
|
|
258
|
+
from_table_definition = ( [ whitespace ] "from_table" ( map_name ) as from_table_name [ "{" [ whitespace ] *( kw_artifact_sp ( map_name ) as artifact_table [ whitespace ] ) "}" ] [ whitespace ] ) as from_table_decl
|
|
157
259
|
|
|
158
|
-
enum_values_shorthand = ( [ whitespace ]
|
|
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
|
|
260
|
+
enum_values_shorthand = ( [ whitespace ] kw_values_sp value_set_body [ whitespace ] ) as value_set_decl
|
|
261
|
+
enum_inner_definitions = visibility_declaration / abstract_declaration / 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
|
|
160
262
|
enum_inner_definition = enum_inner_definitions [ whitespace ]
|
|
161
|
-
enum_body =
|
|
162
|
-
enum_definition = [ whitespace ]
|
|
163
|
-
data_type_body =
|
|
164
|
-
data_type_definition = [ whitespace ]
|
|
165
|
-
primitive_definition = [ whitespace ]
|
|
166
|
-
|
|
167
|
-
package_body =
|
|
168
|
-
package_definition = ( [ whitespace ]
|
|
169
|
-
diagram_inner_definitions = namespace_definition / mapping_definition /
|
|
263
|
+
enum_body = "{" [ whitespace ] ( *( enum_inner_definition ) ) as members "}"
|
|
264
|
+
enum_definition = [ whitespace ] [ element_visibility ] kw_enum_sp [ quotes ] class_name as name [ quotes ] [ attribute_stereotype ] [ enum_body ]
|
|
265
|
+
data_type_body = "{" [ whitespace ] ( *( enum_inner_definition ) ) as members "}"
|
|
266
|
+
data_type_definition = [ whitespace ] [ element_visibility ] kw_data_type_sp [ quotes ] class_name as name [ quotes ] [ attribute_stereotype ] [ data_type_body ]
|
|
267
|
+
primitive_definition = [ whitespace ] [ element_visibility ] kw_primitive_sp [ quotes ] class_name as name [ quotes ]
|
|
268
|
+
|
|
269
|
+
package_body = "{" [ whitespace ] ( *( diagram_inner_definition ) ) as members "}"
|
|
270
|
+
package_definition = ( [ whitespace ] [ element_visibility ] kw_package_sp class_name as name package_body [ whitespace ] ) as packages
|
|
271
|
+
diagram_inner_definitions = namespace_definition / mapping_definition / ( class_definition ) as classes / ( enum_definition ) as enums / ( primitive_definition ) as primitives / ( data_type_definition ) as data_types / package_definition / title_definition / caption_definition / fontname_definition / ( association_definition ) as associations / comment_definition / comment_multiline_definition
|
|
170
272
|
diagram_inner_definition = diagram_inner_definitions [ whitespace ]
|
|
171
|
-
diagram_body =
|
|
172
|
-
diagram_definition = [ whitespace ] "diagram"
|
|
273
|
+
diagram_body = "{" [ whitespace ] ( *( diagram_inner_definition ) ) as members "}"
|
|
274
|
+
diagram_definition = [ whitespace ] "diagram" class_name as name diagram_body [ whitespace ]
|
|
173
275
|
diagram_definitions = diagram_definition [ whitespace ]
|
|
174
276
|
model_definitions = ( 1*( diagram_inner_definition ) ) as members
|
|
175
277
|
|
|
176
278
|
view_only_definitions = ( view_import ) as view_imports / show_directive / hide_directive
|
|
177
279
|
view_inner_definitions = view_only_definitions / diagram_inner_definitions
|
|
178
280
|
view_inner_definition = view_inner_definitions [ whitespace ]
|
|
179
|
-
view_body =
|
|
180
|
-
view_definition = [ whitespace ] "view"
|
|
281
|
+
view_body = "{" [ whitespace ] ( *( view_inner_definition ) ) as members "}"
|
|
282
|
+
view_definition = [ whitespace ] "view" class_name as name view_body [ whitespace ]
|
|
181
283
|
view_definitions = view_definition [ whitespace ]
|
|
182
284
|
|
|
183
|
-
title_text = ( %x22 ( 1*( !%x22
|
|
184
|
-
title_definition = [ whitespace ]
|
|
185
|
-
caption_text = ( %x22 ( 1*( !%x22
|
|
186
|
-
caption_definition = [ whitespace ]
|
|
285
|
+
atomic title_text = ( %x22 ( 1*( !%x22 ANY ) ) as title %x22 ) / ( %x27 ( 1*( !%x27 ANY ) ) as title %x27 ) / ( 1*( %x41-5A / %x61-7A / %x30-39 / "_" / "-" / " " / "," / "." / ":" / ";" / "(" / ")" / "+" / "'" / "/" ) ) as title
|
|
286
|
+
title_definition = [ whitespace ] kw_title_sp title_text
|
|
287
|
+
atomic caption_text = ( %x22 ( 1*( !%x22 ANY ) ) as caption %x22 ) / ( %x27 ( 1*( !%x27 ANY ) ) as caption %x27 ) / ( 1*( %x41-5A / %x61-7A / %x30-39 / "_" / "-" / " " / "," / "." / ":" / ";" / "(" / ")" / "+" / "'" / "/" ) ) as caption
|
|
288
|
+
caption_definition = [ whitespace ] kw_caption_sp caption_text
|
|
187
289
|
fontname_text = [ quotes ] ( 1*( %x41-5A / %x61-7A / %x30-39 / "_" / "-" / " " ) ) as fontname [ quotes ]
|
|
188
|
-
fontname_definition = [ whitespace ]
|
|
290
|
+
fontname_definition = [ whitespace ] kw_fontname_sp fontname_text
|
|
189
291
|
|
|
190
292
|
# ---- Views ----
|
|
191
293
|
# `import` here carries a required trailing space (word boundary)
|
|
192
|
-
view_import = [ whitespace ]
|
|
294
|
+
view_import = [ whitespace ] kw_import_sp ( dq_path / sq_path ) [ whitespace ]
|
|
193
295
|
entity_name_atom = ( 1*( %x41-5A / %x61-7A / %x30-39 / "_" / "." / ":" / "-" ) ) as entity_name
|
|
194
|
-
entity_name_list = entity_name_atom *(
|
|
195
|
-
show_directive = [ whitespace ]
|
|
196
|
-
hide_directive = [ whitespace ]
|
|
296
|
+
entity_name_list = entity_name_atom *( "," entity_name_atom )
|
|
297
|
+
show_directive = [ whitespace ] kw_show_sp ( entity_name_list ) as show_list [ whitespace ]
|
|
298
|
+
hide_directive = [ whitespace ] kw_hide_sp ( entity_name_list ) as hide_list [ whitespace ]
|
|
197
299
|
|
|
198
300
|
# ---- Requires ----
|
|
199
|
-
require_stmt = [ whitespace ]
|
|
301
|
+
require_stmt = [ whitespace ] kw_require_sp quoted_string as require [ whitespace ]
|
|
200
302
|
require_block = ( *( require_stmt [ whitespace ] ) ) as requires
|
|
201
303
|
|
|
202
304
|
# ---- Instances ----
|
|
203
305
|
instance = instance_named / keyword_instance / class_instance
|
|
204
|
-
instance_named = ( [ whitespace ]
|
|
205
|
-
isa_definition = [ whitespace ]
|
|
206
|
-
keyword_instance = ( [ whitespace ] "instance" [
|
|
306
|
+
instance_named = ( [ whitespace ] kw_instance_sp quoted_string as name [ ( namespaced_identifier ) as instance_type ] "{" [ whitespace ] ( ( instance ) / attributes ) "}" [ whitespace ] ) as instance
|
|
307
|
+
isa_definition = [ whitespace ] kw_isa_sp ( namespaced_identifier ) as isa [ whitespace ]
|
|
308
|
+
keyword_instance = ( [ whitespace ] "instance" [ ( namespaced_identifier ) as instance_type ] "{" [ whitespace ] [ isa_definition ] ( ( instance ) / attributes ) "}" ) as instance [ whitespace ]
|
|
207
309
|
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 ]
|
|
208
310
|
lml_instance_body = lml_instance_members [ whitespace ]
|
|
209
311
|
instance_template = [ whitespace ] "template" [ whitespace ] "{" [ whitespace ] attributes [ whitespace ] "}"
|
|
210
312
|
lml_instance_members = ( instance_template ) as template / attributes
|
|
211
|
-
models = [ whitespace ] "models" [ whitespace ] variable as name [ whitespace ] "{" ( *( ( ( string_format_definition ) as default_string_formats ) / model_body ) ) as members "}" [ whitespace ]
|
|
313
|
+
models = [ whitespace ] "models" [ whitespace ] variable as name [ version_declaration ] [ whitespace ] "{" ( *( ( ( string_format_definition ) as default_string_formats ) / model_body ) ) as members "}" [ whitespace ]
|
|
212
314
|
model_body = diagram_inner_definition
|
|
213
315
|
|
|
214
316
|
# ---- Data structures ----
|
|
215
|
-
attribute_value = instance / list / key_value_map / data_value / 1*( !%x0A
|
|
317
|
+
attribute_value = instance / list / key_value_map / data_value / 1*( !%x0A ANY )
|
|
216
318
|
list_item = instance / data_value
|
|
217
|
-
list = "[" [ whitespace ] ( *( list_item [
|
|
218
|
-
collection = [ whitespace ]
|
|
219
|
-
includes = [ whitespace ] "includes"
|
|
220
|
-
validation = [ whitespace ] "validation"
|
|
221
|
-
condition = [ whitespace ]
|
|
222
|
-
import = [ whitespace ] "import"
|
|
223
|
-
import_definition = ( 1*nonspace ) as format_type
|
|
224
|
-
instances = [ whitespace ] "instances" [
|
|
319
|
+
list = "[" [ whitespace ] ( *( list_item [ "," ] [ whitespace ] ) ) as list [ whitespace ] "]"
|
|
320
|
+
collection = [ whitespace ] kw_collection_sp quoted_string as name "{" [ whitespace ] [ includes ] [ whitespace ] [ validation ] [ whitespace ] "}" [ whitespace ]
|
|
321
|
+
includes = [ whitespace ] "includes" ( list ) as includes
|
|
322
|
+
validation = [ whitespace ] "validation" "{" [ whitespace ] ( *( condition ) ) as validations "}"
|
|
323
|
+
condition = [ whitespace ] kw_condition_sp quoted_string as condition [ whitespace ]
|
|
324
|
+
import = [ whitespace ] "import" "{" [ whitespace ] ( *( import_definition ) ) as imports "}" [ whitespace ]
|
|
325
|
+
import_definition = ( 1*nonspace ) as format_type quoted_string as file [ whitespace ] "{" [ whitespace ] attributes [ whitespace ] "}" [ whitespace ]
|
|
326
|
+
instances = [ whitespace ] "instances" [ ( quoted_string / variable ) as name ] [ whitespace ] "{" [ whitespace ] [ instances_body ] "}" [ whitespace ]
|
|
225
327
|
instances_body = ( *( instances_member [ whitespace ] ) ) as instances
|
|
226
328
|
instances_member = import / ( collection ) as collections / export / instance
|
|
227
329
|
export = [ whitespace ] "export" [ whitespace ] "{" [ whitespace ] ( *( export_format [ whitespace ] ) ) as exports "}" [ whitespace ]
|
|
228
|
-
export_format = [ whitespace ]
|
|
330
|
+
export_format = [ whitespace ] kw_format_sp variable as format_type [ whitespace ] "{" [ whitespace ] attributes "}" [ whitespace ]
|
|
229
331
|
|
|
230
332
|
# ---- Root (RS 3001: a document interleaves declarations) ----
|
|
231
333
|
diagram = [ require_block ] ( *( top_level_definition ) ) as members
|
|
@@ -234,3 +336,16 @@ grammar Lml version "1.0.0" {
|
|
|
234
336
|
}
|
|
235
337
|
|
|
236
338
|
entry diagram: diagram
|
|
339
|
+
|
|
340
|
+
# Compile-time contract tests for the trivia/skip behavior (run by
|
|
341
|
+
# `rake parg` and any in-memory compile; a failure fails the build).
|
|
342
|
+
test diagram {
|
|
343
|
+
accept "// leading\nclass Ceramic { }\n"
|
|
344
|
+
accept "class /* inline */ Ceramic { }\n"
|
|
345
|
+
accept "class Ceramic { } // trailing"
|
|
346
|
+
accept "instances {\n P \"p\" {\n url = \"http://a//b\"\n }\n}\n"
|
|
347
|
+
reject "classCeramic Foo { }"
|
|
348
|
+
reject "abstractclass Ceramic { }"
|
|
349
|
+
reject "class\tCeramic { }"
|
|
350
|
+
reject "class Ceramic { /* unterminated"
|
|
351
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module Layout
|
|
5
|
+
# In-process layout engine backed by elkrb (pure-Ruby ELK). The input
|
|
6
|
+
# is an already-built Elkrb::Graph; rendering produces standalone SVG.
|
|
7
|
+
class ElkEngine < Engine
|
|
8
|
+
def render(_type)
|
|
9
|
+
require 'elkrb'
|
|
10
|
+
Elkrb::Layout::LayoutEngine.layout(@input, algorithm: 'layered')
|
|
11
|
+
ElkSvgRenderer.new.render(@input)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module Layout
|
|
5
|
+
# Builds an Elkrb::Graph: a node per classifier (name, definition,
|
|
6
|
+
# members); an edge per association and parent specializer.
|
|
7
|
+
class ElkGraphBuilder
|
|
8
|
+
CHAR_WIDTH = 7.2
|
|
9
|
+
LINE_HEIGHT = 16.0
|
|
10
|
+
PADDING = 14.0
|
|
11
|
+
MIN_WIDTH = 120.0
|
|
12
|
+
COLLECTIONS = %i[classes enums primitives data_types].freeze
|
|
13
|
+
def build(document)
|
|
14
|
+
graph = elkrb_graph
|
|
15
|
+
add_entities(graph, document)
|
|
16
|
+
add_association_edges(graph, document)
|
|
17
|
+
add_parent_edges(graph, document)
|
|
18
|
+
graph
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def elkrb_graph
|
|
24
|
+
require 'elkrb'
|
|
25
|
+
Elkrb::Graph::Graph.new(id: 'diagram')
|
|
26
|
+
rescue LoadError
|
|
27
|
+
raise Error, 'The elkrb gem is required for ELK layout: add `gem "elkrb"` to your Gemfile'
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def entities(document)
|
|
31
|
+
([document] + Array(document.packages)).flat_map { |scope| scope_entities(scope) }.flatten.compact
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def scope_entities(scope)
|
|
35
|
+
COLLECTIONS.filter_map do |collection|
|
|
36
|
+
next unless scope.respond_to?(collection)
|
|
37
|
+
|
|
38
|
+
scope.public_send(collection)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def node_for(entity)
|
|
43
|
+
lines = label_lines(entity)
|
|
44
|
+
Elkrb::Graph::Node.new(
|
|
45
|
+
id: entity_id_for(entity.name),
|
|
46
|
+
width: node_width(lines),
|
|
47
|
+
height: PADDING + lines.size * LINE_HEIGHT,
|
|
48
|
+
labels: lines.each_with_index.map do |line, i|
|
|
49
|
+
Elkrb::Graph::Label.new(text: line, x: 0, y: i * LINE_HEIGHT)
|
|
50
|
+
end
|
|
51
|
+
)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def label_lines(entity)
|
|
55
|
+
lines = [entity.name.to_s]
|
|
56
|
+
if entity.respond_to?(:definition) && !entity.definition.to_s.empty?
|
|
57
|
+
lines << entity.definition.to_s.gsub("\n", ' ')
|
|
58
|
+
end
|
|
59
|
+
Array(entity.attributes).each do |attr|
|
|
60
|
+
lines << attr_line(attr)
|
|
61
|
+
end
|
|
62
|
+
lines
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def attr_line(attr)
|
|
66
|
+
line = "+ #{attr.name}"
|
|
67
|
+
line += ": #{attr.type}" if attr.type && !attr.type.to_s.empty?
|
|
68
|
+
line
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def node_width(lines)
|
|
72
|
+
widest = lines.map(&:length).max || 0
|
|
73
|
+
[widest * CHAR_WIDTH + PADDING, MIN_WIDTH].max
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def entity_id(entity)
|
|
77
|
+
entity.name.to_s.gsub(/[^0-9a-zA-Z_]/, '_')
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def associations(document)
|
|
81
|
+
Array(document.associations)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def parent_edges(document)
|
|
85
|
+
Array(document.classes).filter_map do |klass|
|
|
86
|
+
next unless klass.respond_to?(:parent_class) && klass.parent_class
|
|
87
|
+
|
|
88
|
+
{ owner: klass.parent_class, member: klass.name.to_s,
|
|
89
|
+
member_end_type: 'generalization', name: nil }
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def add_entities(graph, document)
|
|
94
|
+
entities(document).each { |entity| graph.children << node_for(entity) }
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def add_association_edges(graph, document)
|
|
98
|
+
associations(document).each { |assoc| graph.edges << edge_for(association_spec(assoc)) }
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def add_parent_edges(graph, document)
|
|
102
|
+
parent_edges(document).each { |edge| graph.edges << edge_for(edge) }
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def edge_for(spec)
|
|
106
|
+
edge = elkrb_edge(spec)
|
|
107
|
+
edge.properties = { 'member_end_type' => spec[:member_end_type].to_s }
|
|
108
|
+
edge.labels = [Elkrb::Graph::Label.new(text: spec[:name].to_s)] if spec[:name]
|
|
109
|
+
edge
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def elkrb_edge(spec)
|
|
113
|
+
require 'elkrb'
|
|
114
|
+
Elkrb::Graph::Edge.new(
|
|
115
|
+
id: "edge_#{spec[:owner]}_#{spec[:member]}",
|
|
116
|
+
sources: [entity_id_for(spec[:owner])],
|
|
117
|
+
targets: [entity_id_for(spec[:member])]
|
|
118
|
+
)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def association_spec(assoc)
|
|
122
|
+
{ owner: assoc.owner_end.to_s, member: assoc.member_end.to_s,
|
|
123
|
+
member_end_type: assoc.member_end_type.to_s, name: assoc.name }
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def entity_id_for(name) = name.to_s.gsub(/[^0-9a-zA-Z_]/, '_')
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|