lutaml-lml 0.1.8 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/lutaml/lml/cli.rb +20 -4
- 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 +230 -6
- data/lib/lutaml/lml/format/adapter/standard_adapter.rb +17 -4
- data/lib/lutaml/lml/formatter/elk.rb +42 -0
- data/lib/lutaml/lml/formatter/graphviz/node_formatter.rb +2 -1
- data/lib/lutaml/lml/formatter.rb +1 -0
- data/lib/lutaml/lml/grammar/lml.artifact.json +8659 -3193
- data/lib/lutaml/lml/grammar/lml.parg +236 -90
- 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.rb +231 -19
- data/lib/lutaml/lml/models/data_type.rb +1 -0
- data/lib/lutaml/lml/models/document.rb +31 -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/operation_parameter.rb +1 -0
- data/lib/lutaml/lml/models/reference.rb +23 -0
- data/lib/lutaml/lml/models/serialization_mapping.rb +17 -0
- data/lib/lutaml/lml/models/top_element_attribute.rb +16 -0
- data/lib/lutaml/lml/models/uml_class.rb +2 -1
- data/lib/lutaml/lml/preprocessor.rb +49 -8
- data/lib/lutaml/lml/transform.rb +1 -1
- data/lib/lutaml/lml/validator.rb +344 -0
- data/lib/lutaml/lml/version.rb +1 -1
- data/lib/lutaml/lml.rb +5 -0
- metadata +15 -4
|
@@ -10,180 +10,326 @@
|
|
|
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
86
|
# quoted('"', :string) | quoted("'", :string)
|
|
31
|
-
dq_string = %x22
|
|
32
|
-
sq_string = %x27
|
|
87
|
+
atomic dq_string = %x22 until "\"" as string %x22
|
|
88
|
+
atomic sq_string = %x27 until "'" as string %x27
|
|
33
89
|
quoted_string = dq_string / sq_string
|
|
34
90
|
# any_quoted(:path) for view import
|
|
35
|
-
dq_path = %x22
|
|
36
|
-
sq_path = %x27
|
|
91
|
+
atomic dq_path = %x22 until "\"" as path %x22
|
|
92
|
+
atomic sq_path = %x27 until "'" as path %x27
|
|
37
93
|
|
|
38
94
|
boolean = ( "true" / "false" ) as boolean
|
|
39
95
|
number = ( 1*%x30-39 "." 1*%x30-39 ) as float / ( 1*%x30-39 ) as number
|
|
40
96
|
variable = quoted_string / 1*word
|
|
41
|
-
reference = "reference:(" ( variable *( "." variable ) ) as reference ")"
|
|
97
|
+
reference = ( "reference:(" / "ref:(" ) ( variable *( "." variable ) ) as reference ")"
|
|
42
98
|
range = ( variable as start ".." variable as end ) as range
|
|
43
99
|
namespaced_identifier = variable *( "::" variable )
|
|
44
|
-
comment_definition =
|
|
45
|
-
comment_multiline_definition =
|
|
100
|
+
comment_definition = ( "**" / "#" ) ( *( !newline ANY ) ) as comments
|
|
101
|
+
comment_multiline_definition = "*|" until "|*" as comments [ whitespace ] "|*"
|
|
46
102
|
|
|
47
103
|
class_name_chars = 1*( %x41-5A / %x61-7A / %x30-39 / " " / "_" / "-" / ":" / "." )
|
|
48
104
|
class_name = class_name_chars [ "(" class_name_chars ")" ]
|
|
49
105
|
cardinality_body_definition = cardchar as min [ ".." [ cardchar as max ] ]
|
|
50
106
|
cardinality = "[" cardinality_body_definition as cardinality "]"
|
|
51
107
|
|
|
52
|
-
|
|
53
|
-
key_value_pair = variable as key
|
|
54
|
-
key_value_map = "{" [ whitespace ] ( *( key_value_pair whitespace ) ) as key_value_map "}"
|
|
108
|
+
data_value = boolean / reference / range / number / quoted_string
|
|
109
|
+
key_value_pair = variable as key [ "=" ] data_value as value
|
|
110
|
+
key_value_map = "{" [ whitespace ] ( *( key_value_pair [ whitespace ] ) ) as key_value_map "}"
|
|
55
111
|
kw_visibility_modifier = "+" / "-" / "#" / "~"
|
|
56
112
|
|
|
57
113
|
# ---- Attributes ----
|
|
58
|
-
attribute = comment_definition / ( variable as key [
|
|
59
|
-
attributes = ( *( attribute_line / whitespace ) ) as attributes
|
|
60
|
-
attribute_line =
|
|
114
|
+
attribute = comment_definition / ( variable as key [ "+" as add ] [ "=" ] attribute_value as value )
|
|
115
|
+
attributes = ( *( definition_body / definition_line / attribute_line / whitespace ) ) as attributes
|
|
116
|
+
attribute_line = attribute [ [ "," ] whitespace ]
|
|
61
117
|
visibility = kw_visibility_modifier as visibility_modifier
|
|
62
118
|
attribute_stereotype = "<<" ( 1*( %x41-5A / %x61-7A / %x30-39 / "_" / "-" / "/" ) ) as stereotype ">>"
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
119
|
+
atomic type_token = 1*( !( "//" / "/*" ) typechar )
|
|
120
|
+
atomic typechar = %x41-5A / %x61-7A / %x30-39 / "_" / "-" / " " / ":" / "/" / "+"
|
|
121
|
+
atomic namechar = %x41-5A / %x61-7A / %x30-39 / "_" / "-" / "/" / "+"
|
|
122
|
+
attribute_type = [ ":" ] [ attribute_stereotype ] [ quotes as type_literal ] ( type_token ) as type [ quotes ]
|
|
66
123
|
attribute_name = ( 1*namechar ) as name
|
|
67
124
|
attribute_definition_name = ( quotes ( 1*( %x41-5A / %x61-7A / %x30-39 / "_" / "-" / " " / "/" / "+" ) ) as name quotes ) / attribute_name
|
|
68
|
-
attribute_definition = ( [ visibility ] as visibility
|
|
125
|
+
attribute_definition = ( [ visibility ] as visibility attribute_definition_name [ attribute_type ] [ cardinality ] [ class_body ] ) as attributes
|
|
69
126
|
|
|
70
|
-
keyword_type_argument = "type"
|
|
71
|
-
keyword_cardinality_argument = "cardinality"
|
|
72
|
-
keyword_any_argument =
|
|
73
|
-
|
|
127
|
+
keyword_type_argument = "type" ( 1*nonspace ) as type [ whitespace ]
|
|
128
|
+
keyword_cardinality_argument = "cardinality" cardinality_body_definition as cardinality [ whitespace ]
|
|
129
|
+
keyword_any_argument = ( keyword_any_name ) as name [ "=" / ":" ] attribute_value as value [ whitespace ]
|
|
130
|
+
atomic keyword_any_name = 1*( !( "{" / "}" ) nonspace )
|
|
131
|
+
keyword_values_argument = kw_values_sp ( ( value_set_body ) as value_set / ( variable ) as value_set_ref ) [ whitespace ]
|
|
132
|
+
keyword_pattern_argument = ( kw_pattern_sp "/" ( *( ( "\\" any_char ) / ( !"/" any_char ) ) ) as pattern "/" [ whitespace ] )
|
|
133
|
+
value_set_body = "{" [ whitespace ] ( *( ( ( data_value / variable ) as item ) [ "," ] [ whitespace ] ) ) as items "}"
|
|
134
|
+
keyword_attribute_options = *( keyword_type_argument / keyword_cardinality_argument / keyword_values_argument / keyword_pattern_argument / ( keyword_any_argument ) as properties )
|
|
74
135
|
keyword_attribute_body = "{" [ whitespace ] keyword_attribute_options [ whitespace ] "}"
|
|
75
|
-
keyword_attribute_definition = (
|
|
136
|
+
keyword_attribute_definition = ( kw_attribute_sp attribute_name keyword_attribute_body ) as attributes
|
|
76
137
|
|
|
77
138
|
# ---- Associations ----
|
|
78
|
-
owner_cardinality =
|
|
79
|
-
owner_attribute_name = "#" [ visibility ]
|
|
80
|
-
owner_definition = [ whitespace ]
|
|
81
|
-
owner_type = [ whitespace ]
|
|
82
|
-
member_cardinality =
|
|
83
|
-
member_attribute_name = "#" [ visibility ]
|
|
84
|
-
member_definition = [ whitespace ]
|
|
85
|
-
member_type = [ whitespace ]
|
|
139
|
+
owner_cardinality = "[" cardinality_body_definition as owner_end_cardinality "]"
|
|
140
|
+
owner_attribute_name = "#" [ visibility ] variable as owner_end_attribute_name
|
|
141
|
+
owner_definition = [ whitespace ] kw_owner_sp variable as owner_end [ owner_attribute_name ] [ owner_cardinality ]
|
|
142
|
+
owner_type = [ whitespace ] kw_owner_type_sp variable as owner_end_type
|
|
143
|
+
member_cardinality = "[" cardinality_body_definition as member_end_cardinality "]"
|
|
144
|
+
member_attribute_name = "#" [ visibility ] variable as member_end_attribute_name
|
|
145
|
+
member_definition = [ whitespace ] kw_member_sp variable as member_end [ member_attribute_name ] [ member_cardinality ]
|
|
146
|
+
member_type = [ whitespace ] kw_member_type_sp variable as member_end_type
|
|
86
147
|
association_inner_definition = ( owner_type / member_type / owner_definition / member_definition / comment_definition / comment_multiline_definition ) [ whitespace ]
|
|
87
|
-
association_body =
|
|
88
|
-
association_definition = [ whitespace ]
|
|
148
|
+
association_body = "{" [ whitespace ] ( *( association_inner_definition ) ) as members "}"
|
|
149
|
+
association_definition = [ whitespace ] kw_association_sp [ variable as name ] association_body
|
|
89
150
|
|
|
90
151
|
# ---- Definitions ----
|
|
91
|
-
|
|
152
|
+
# ---- Operations (class methods) ----
|
|
153
|
+
# Symbol visibility reuses the attribute prefix (+ - # ~); the
|
|
154
|
+
# transform maps it to public/private/protected/friendly.
|
|
155
|
+
operation_definition = [ whitespace ] [ visibility ] ( 1*word ) as name "(" [ ( operation_params ) as owned_parameter ] ")" [ operation_return ] [ operation_body ] [ whitespace ]
|
|
156
|
+
operation_params = operation_param *( "," [ whitespace ] operation_param )
|
|
157
|
+
operation_return = ":" ( type_token ) as return_type [ cardinality ]
|
|
158
|
+
# A parenthesized alternative is not an Entity, so an optional body
|
|
159
|
+
# of `x as k` around it gets no skip injection - the blank after "="
|
|
160
|
+
# was unconsumable. Reference a rule instead.
|
|
161
|
+
operation_param = ( [ ( "in" / "out" / "inout" ) as direction 1*" " ] ( 1*word ) as name [ ":" ] param_type as type [ "=" ] param_default_value as param_default )
|
|
162
|
+
param_default_value = [ quoted_string / ( 1*word ) / number ]
|
|
163
|
+
# The attribute pattern: separator as its own optional, value as a
|
|
164
|
+
# rule sibling. A `x as k` optional body gets no interior skip
|
|
165
|
+
# injection, so [ ":" type as t ] cannot consume the blank after
|
|
166
|
+
# the separator.
|
|
167
|
+
param_type = [ type_token ]
|
|
168
|
+
operation_body = "{" [ whitespace ] [ ( *( ( 1*word ) as stereotype [ "," ] [ whitespace ] ) ) as stereotype ] [ whitespace ] "}"
|
|
169
|
+
|
|
170
|
+
class_modifier = [ [ whitespace ] modifier_token ]
|
|
171
|
+
atomic modifier_token = ( ( "abstract" / "interface" ) as modifier 1*" " )
|
|
172
|
+
# RS 3001 par. Declaration modifiers: keyword prefix (sugar) and the
|
|
173
|
+
# canonical in-block declarations. The `package` visibility keyword is
|
|
174
|
+
# guarded: it must be followed by a declaration keyword, so a bare
|
|
175
|
+
# `package P { }` still binds as a package declaration.
|
|
176
|
+
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*" " )
|
|
177
|
+
visibility_declaration = kw_visibility_sp ( "public" / "private" / "protected" / "package" ) as visibility
|
|
178
|
+
abstract_declaration = kw_abstract_sp ( "true" / "false" ) as abstract
|
|
179
|
+
# ---- Namespaces (RS 3010 extension: lutaml-model XmlNamespace) ----
|
|
180
|
+
namespace_uri_line = ( kw_uri_sp ( quoted_string / ( 1*word ) ) as uri [ whitespace ] )
|
|
181
|
+
namespace_prefix_line = ( kw_prefix_sp ( quoted_string / ( 1*word ) ) as prefix [ whitespace ] )
|
|
182
|
+
namespace_body = "{" [ whitespace ] ( *( namespace_uri_line / namespace_prefix_line ) ) as members "}"
|
|
183
|
+
namespace_inline = ( ( quoted_string ) as uri [ kw_prefix_sp ( quoted_string / ( 1*word ) ) as prefix ] )
|
|
184
|
+
namespace_definition = ( [ whitespace ] kw_namespace_sp ( 1*word ) as name ( namespace_body / namespace_inline ) [ whitespace ] ) as namespaces
|
|
185
|
+
|
|
186
|
+
# ---- Serialization mappings (RS 3010 extension: sibling-linked) ----
|
|
187
|
+
serialization_body = "{" [ whitespace ] ( *( serialization_line ) ) as rules "}"
|
|
188
|
+
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 ]
|
|
189
|
+
legacy_map_line = ( kw_map_sp dq_string as wire "," "to:" ( quoted_string / ( 1*word ) ) as to [ whitespace ] ) as legacy_map
|
|
190
|
+
mapping_element_name_line = ( kw_element_sp dq_string as element_name [ whitespace ] ) as element_name
|
|
191
|
+
mapping_element_line = ( kw_map_element_sp ( element_args ) ) as map_element
|
|
192
|
+
element_args = ( dq_string as wire [ ( 1*word ) as field ] / ( 1*word ) as field )
|
|
193
|
+
mapping_attribute_line = ( kw_map_attribute_sp ( attribute_args ) ) as map_attribute
|
|
194
|
+
attribute_args = ( dq_string as wire [ ( 1*word ) as field ] / ( 1*word ) as field )
|
|
195
|
+
mapping_content_line = ( kw_map_content_sp ( 1*word ) as field ) as map_content
|
|
196
|
+
mapping_namespace_line = ( kw_namespace_sp ( 1*word ) as namespace_ref ) as map_namespace
|
|
197
|
+
mapping_mixed_line = ( "mixed_content" ) as mixed_content
|
|
198
|
+
mapping_map_line = ( kw_map_sp map_args ) as map
|
|
199
|
+
map_args = ( dq_string as wire ( 1*word ) as field / dq_string as wire / ( 1*word ) as field )
|
|
200
|
+
mapping_map_key_line = ( kw_map_key_sp kw_to_instance_sp ( 1*word ) as field ) as map_key
|
|
201
|
+
mapping_map_value_line = ( kw_map_value_sp map_value_mode ( 1*word ) as field ) as map_value
|
|
202
|
+
atomic map_value_mode = ( ( "to_instance" / "as_attribute" ) as mode 1*" " )
|
|
203
|
+
mapping_definition = ( [ whitespace ] kw_mapping_sp ( 1*word ) as format ( 1*word ) as target serialization_body [ whitespace ] ) as serialization_mappings
|
|
204
|
+
|
|
92
205
|
# ---- Mapping blocks (lutaml-model wire names) ----
|
|
93
206
|
map_name = ( quoted_string / 1*( word / "-" / "." ) )
|
|
94
|
-
map_line =
|
|
95
|
-
mapping_body =
|
|
96
|
-
|
|
207
|
+
map_line = kw_map_sp ( map_name ) as wire "," "to:" ( map_name ) as to [ whitespace ]
|
|
208
|
+
mapping_body = "{" [ whitespace ] ( *( map_line ) ) as maps "}"
|
|
209
|
+
class_mapping_section = ( [ whitespace ] kw_mapping_sp ( 1*word ) as format serialization_body [ whitespace ] ) as serialization_mappings
|
|
97
210
|
|
|
98
|
-
derive_definition = ( [ whitespace ]
|
|
99
|
-
string_format_item = (
|
|
211
|
+
derive_definition = ( [ whitespace ] kw_derive_sp attribute_name ":" [ quotes ] ( type_token ) as type [ quotes ] ) as derived_field [ whitespace ]
|
|
212
|
+
string_format_item = ( sf_key_token ( quoted_string / 1*nonspace ) as value [ whitespace ] )
|
|
213
|
+
atomic sf_key_token = ( ( "artifact" / "root" ) as key 1*" " )
|
|
100
214
|
string_format_body = "{" [ whitespace ] ( *( string_format_item ) ) as items [ whitespace ] "}"
|
|
101
|
-
string_format_definition = ( [ whitespace ]
|
|
215
|
+
string_format_definition = ( [ whitespace ] kw_string_format_sp ( variable ) as format string_format_body ) as string_format
|
|
102
216
|
|
|
103
|
-
class_inner_definitions = definition_body /
|
|
217
|
+
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
|
|
104
218
|
class_inner_definition = class_inner_definitions [ whitespace ]
|
|
105
|
-
class_body =
|
|
106
|
-
parent_class =
|
|
107
|
-
class_definition = class_modifier [ whitespace ]
|
|
219
|
+
class_body = "{" [ whitespace ] ( *( class_inner_definition ) ) as members "}"
|
|
220
|
+
parent_class = "<" ( class_name_chars ) as parent_class
|
|
221
|
+
class_definition = [ element_visibility ] class_modifier [ whitespace ] kw_class_sp class_name as name [ parent_class ] [ attribute_stereotype ] [ class_body ]
|
|
108
222
|
|
|
109
223
|
# Balanced-brace definition text: nested "{...}" and "\"-escapes allowed
|
|
110
224
|
definition_content = *( ( "\\" any_char ) / ( "{" definition_content "}" ) / ( !"}" any_char ) )
|
|
111
|
-
definition_body =
|
|
225
|
+
definition_body = "definition" [ whitespace ] "{" ( definition_content ) as definition "}"
|
|
226
|
+
definition_line = ( kw_definition_sp ( dq_string / sq_string ) ) as definition [ whitespace ]
|
|
112
227
|
|
|
113
|
-
member_field_line =
|
|
228
|
+
member_field_line = attribute_name ( 1*nonspace ) as member_field_type [ whitespace ]
|
|
114
229
|
member_fields_definition = ( [ whitespace ] "member_fields" [ whitespace ] "{" [ whitespace ] ( *( member_field_line ) ) as member_fields [ whitespace ] "}" ) as member_fields_decl
|
|
115
|
-
|
|
230
|
+
attribute_comma_definition = ( [ whitespace ] kw_attribute_sp attribute_name "," ( type_token ) as type [ "{" [ whitespace ] keyword_attribute_options [ whitespace ] "}" ] [ whitespace ] ) as attributes
|
|
231
|
+
enum_member_with_payload = ( [ whitespace ] attribute_name ( class_body / keyword_attribute_body ) ) as attributes
|
|
232
|
+
|
|
233
|
+
# ---- Canonical value construct (RS 3001 par. Value / par. Enums) ----
|
|
234
|
+
# `value <name> { block }` is the canonical enum member; the bare
|
|
235
|
+
# identifier member is its documented shortcut. from_table expands to
|
|
236
|
+
# value members at compile time.
|
|
237
|
+
value_name = quoted_string / map_name
|
|
238
|
+
value_inner_definition = ( definition_body / definition_line / instance / attribute_line / comment_definition / comment_multiline_definition ) [ whitespace ]
|
|
239
|
+
value_body = "{" [ whitespace ] ( *( value_inner_definition ) ) as members "}"
|
|
240
|
+
value_definition = ( [ whitespace ] kw_value_sp ( value_name ) as value_name [ value_body ] [ whitespace ] ) as value_decl
|
|
116
241
|
|
|
117
|
-
|
|
242
|
+
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
|
|
243
|
+
|
|
244
|
+
enum_values_shorthand = ( [ whitespace ] kw_values_sp value_set_body [ whitespace ] ) as value_set_decl
|
|
245
|
+
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
|
|
118
246
|
enum_inner_definition = enum_inner_definitions [ whitespace ]
|
|
119
|
-
enum_body =
|
|
120
|
-
enum_definition = [ whitespace ]
|
|
121
|
-
data_type_body =
|
|
122
|
-
data_type_definition = [ whitespace ]
|
|
123
|
-
primitive_definition = [ whitespace ]
|
|
247
|
+
enum_body = "{" [ whitespace ] ( *( enum_inner_definition ) ) as members "}"
|
|
248
|
+
enum_definition = [ whitespace ] [ element_visibility ] kw_enum_sp [ quotes ] class_name as name [ quotes ] [ attribute_stereotype ] [ enum_body ]
|
|
249
|
+
data_type_body = "{" [ whitespace ] ( *( enum_inner_definition ) ) as members "}"
|
|
250
|
+
data_type_definition = [ whitespace ] [ element_visibility ] kw_data_type_sp [ quotes ] class_name as name [ quotes ] [ attribute_stereotype ] [ data_type_body ]
|
|
251
|
+
primitive_definition = [ whitespace ] [ element_visibility ] kw_primitive_sp [ quotes ] class_name as name [ quotes ]
|
|
124
252
|
|
|
125
|
-
|
|
253
|
+
package_body = "{" [ whitespace ] ( *( diagram_inner_definition ) ) as members "}"
|
|
254
|
+
package_definition = ( [ whitespace ] [ element_visibility ] kw_package_sp class_name as name package_body [ whitespace ] ) as packages
|
|
255
|
+
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
|
|
126
256
|
diagram_inner_definition = diagram_inner_definitions [ whitespace ]
|
|
127
|
-
diagram_body =
|
|
128
|
-
diagram_definition = [ whitespace ] "diagram"
|
|
257
|
+
diagram_body = "{" [ whitespace ] ( *( diagram_inner_definition ) ) as members "}"
|
|
258
|
+
diagram_definition = [ whitespace ] "diagram" class_name as name diagram_body [ whitespace ]
|
|
129
259
|
diagram_definitions = diagram_definition [ whitespace ]
|
|
130
|
-
model_definitions = ( *( diagram_inner_definition ) ) as members
|
|
260
|
+
model_definitions = ( 1*( diagram_inner_definition ) ) as members
|
|
131
261
|
|
|
132
262
|
view_only_definitions = ( view_import ) as view_imports / show_directive / hide_directive
|
|
133
263
|
view_inner_definitions = view_only_definitions / diagram_inner_definitions
|
|
134
264
|
view_inner_definition = view_inner_definitions [ whitespace ]
|
|
135
|
-
view_body =
|
|
136
|
-
view_definition = [ whitespace ] "view"
|
|
265
|
+
view_body = "{" [ whitespace ] ( *( view_inner_definition ) ) as members "}"
|
|
266
|
+
view_definition = [ whitespace ] "view" class_name as name view_body [ whitespace ]
|
|
137
267
|
view_definitions = view_definition [ whitespace ]
|
|
138
268
|
|
|
139
|
-
title_text = ( %x22 ( 1*( !%x22
|
|
140
|
-
title_definition = [ whitespace ]
|
|
141
|
-
caption_text = ( %x22 ( 1*( !%x22
|
|
142
|
-
caption_definition = [ whitespace ]
|
|
269
|
+
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
|
|
270
|
+
title_definition = [ whitespace ] kw_title_sp title_text
|
|
271
|
+
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
|
|
272
|
+
caption_definition = [ whitespace ] kw_caption_sp caption_text
|
|
143
273
|
fontname_text = [ quotes ] ( 1*( %x41-5A / %x61-7A / %x30-39 / "_" / "-" / " " ) ) as fontname [ quotes ]
|
|
144
|
-
fontname_definition = [ whitespace ]
|
|
274
|
+
fontname_definition = [ whitespace ] kw_fontname_sp fontname_text
|
|
145
275
|
|
|
146
276
|
# ---- Views ----
|
|
147
277
|
# `import` here carries a required trailing space (word boundary)
|
|
148
|
-
view_import = [ whitespace ]
|
|
278
|
+
view_import = [ whitespace ] kw_import_sp ( dq_path / sq_path ) [ whitespace ]
|
|
149
279
|
entity_name_atom = ( 1*( %x41-5A / %x61-7A / %x30-39 / "_" / "." / ":" / "-" ) ) as entity_name
|
|
150
|
-
entity_name_list = entity_name_atom *(
|
|
151
|
-
show_directive = [ whitespace ]
|
|
152
|
-
hide_directive = [ whitespace ]
|
|
280
|
+
entity_name_list = entity_name_atom *( "," entity_name_atom )
|
|
281
|
+
show_directive = [ whitespace ] kw_show_sp ( entity_name_list ) as show_list [ whitespace ]
|
|
282
|
+
hide_directive = [ whitespace ] kw_hide_sp ( entity_name_list ) as hide_list [ whitespace ]
|
|
153
283
|
|
|
154
284
|
# ---- Requires ----
|
|
155
|
-
require_stmt = [ whitespace ]
|
|
285
|
+
require_stmt = [ whitespace ] kw_require_sp quoted_string as require [ whitespace ]
|
|
156
286
|
require_block = ( *( require_stmt [ whitespace ] ) ) as requires
|
|
157
287
|
|
|
158
288
|
# ---- Instances ----
|
|
159
|
-
instance = keyword_instance / class_instance
|
|
160
|
-
|
|
161
|
-
|
|
289
|
+
instance = instance_named / keyword_instance / class_instance
|
|
290
|
+
instance_named = ( [ whitespace ] kw_instance_sp quoted_string as name [ ( namespaced_identifier ) as instance_type ] "{" [ whitespace ] ( ( instance ) / attributes ) "}" [ whitespace ] ) as instance
|
|
291
|
+
isa_definition = [ whitespace ] kw_isa_sp ( namespaced_identifier ) as isa [ whitespace ]
|
|
292
|
+
keyword_instance = ( [ whitespace ] "instance" [ ( namespaced_identifier ) as instance_type ] "{" [ whitespace ] [ isa_definition ] ( ( instance ) / attributes ) "}" ) as instance [ whitespace ]
|
|
162
293
|
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 ]
|
|
163
294
|
lml_instance_body = lml_instance_members [ whitespace ]
|
|
164
295
|
instance_template = [ whitespace ] "template" [ whitespace ] "{" [ whitespace ] attributes [ whitespace ] "}"
|
|
165
296
|
lml_instance_members = ( instance_template ) as template / attributes
|
|
166
|
-
models = [ whitespace ] "models" [ whitespace ] variable as name [ whitespace ] "{" ( *( ( ( string_format_definition ) as default_string_formats ) / model_body ) ) as members "}" [ whitespace ]
|
|
167
|
-
model_body =
|
|
297
|
+
models = [ whitespace ] "models" [ whitespace ] variable as name [ version_declaration ] [ whitespace ] "{" ( *( ( ( string_format_definition ) as default_string_formats ) / model_body ) ) as members "}" [ whitespace ]
|
|
298
|
+
model_body = diagram_inner_definition
|
|
168
299
|
|
|
169
300
|
# ---- Data structures ----
|
|
170
|
-
attribute_value = instance / list / key_value_map /
|
|
171
|
-
list_item = instance /
|
|
172
|
-
list = "[" [ whitespace ] ( *( list_item [
|
|
173
|
-
collection = [ whitespace ]
|
|
174
|
-
includes = [ whitespace ] "includes"
|
|
175
|
-
validation = [ whitespace ] "validation"
|
|
176
|
-
condition = [ whitespace ]
|
|
177
|
-
import = [ whitespace ] "import"
|
|
178
|
-
import_definition = ( 1*nonspace ) as format_type
|
|
179
|
-
instances = [ whitespace ] "instances" [ whitespace ] "{" [ whitespace ] [ instances_body ] "}" [ whitespace ]
|
|
301
|
+
attribute_value = instance / list / key_value_map / data_value / 1*( !%x0A ANY )
|
|
302
|
+
list_item = instance / data_value
|
|
303
|
+
list = "[" [ whitespace ] ( *( list_item [ "," ] [ whitespace ] ) ) as list [ whitespace ] "]"
|
|
304
|
+
collection = [ whitespace ] kw_collection_sp quoted_string as name "{" [ whitespace ] [ includes ] [ whitespace ] [ validation ] [ whitespace ] "}" [ whitespace ]
|
|
305
|
+
includes = [ whitespace ] "includes" ( list ) as includes
|
|
306
|
+
validation = [ whitespace ] "validation" "{" [ whitespace ] ( *( condition ) ) as validations "}"
|
|
307
|
+
condition = [ whitespace ] kw_condition_sp quoted_string as condition [ whitespace ]
|
|
308
|
+
import = [ whitespace ] "import" "{" [ whitespace ] ( *( import_definition ) ) as imports "}" [ whitespace ]
|
|
309
|
+
import_definition = ( 1*nonspace ) as format_type quoted_string as file [ whitespace ] "{" [ whitespace ] attributes [ whitespace ] "}" [ whitespace ]
|
|
310
|
+
instances = [ whitespace ] "instances" [ ( quoted_string / variable ) as name ] [ whitespace ] "{" [ whitespace ] [ instances_body ] "}" [ whitespace ]
|
|
180
311
|
instances_body = ( *( instances_member [ whitespace ] ) ) as instances
|
|
181
312
|
instances_member = import / ( collection ) as collections / export / instance
|
|
182
313
|
export = [ whitespace ] "export" [ whitespace ] "{" [ whitespace ] ( *( export_format [ whitespace ] ) ) as exports "}" [ whitespace ]
|
|
183
|
-
export_format = [ whitespace ]
|
|
314
|
+
export_format = [ whitespace ] kw_format_sp variable as format_type [ whitespace ] "{" [ whitespace ] attributes "}" [ whitespace ]
|
|
315
|
+
|
|
316
|
+
# ---- Root (RS 3001: a document interleaves declarations) ----
|
|
317
|
+
diagram = [ require_block ] ( *( top_level_definition ) ) as members
|
|
184
318
|
|
|
185
|
-
|
|
186
|
-
diagram = [ require_block ] ( models / diagram_definitions / view_definitions / instances / instance / model_definitions )
|
|
319
|
+
top_level_definition = ( models / diagram_definitions / view_definitions / instances / instance / model_definitions ) [ whitespace ]
|
|
187
320
|
}
|
|
188
321
|
|
|
189
322
|
entry diagram: diagram
|
|
323
|
+
|
|
324
|
+
# Compile-time contract tests for the trivia/skip behavior (run by
|
|
325
|
+
# `rake parg` and any in-memory compile; a failure fails the build).
|
|
326
|
+
test diagram {
|
|
327
|
+
accept "// leading\nclass Ceramic { }\n"
|
|
328
|
+
accept "class /* inline */ Ceramic { }\n"
|
|
329
|
+
accept "class Ceramic { } // trailing"
|
|
330
|
+
accept "instances {\n P \"p\" {\n url = \"http://a//b\"\n }\n}\n"
|
|
331
|
+
reject "classCeramic Foo { }"
|
|
332
|
+
reject "abstractclass Ceramic { }"
|
|
333
|
+
reject "class\tCeramic { }"
|
|
334
|
+
reject "class Ceramic { /* unterminated"
|
|
335
|
+
}
|
|
@@ -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
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module Layout
|
|
5
|
+
# SVG arrow-marker vocabulary and the <defs> block for ElkSvgRenderer.
|
|
6
|
+
# Marker ids are keyed by association member_end_type.
|
|
7
|
+
module ElkSvgMarkers
|
|
8
|
+
EDGE_STROKE = '#666666'
|
|
9
|
+
|
|
10
|
+
ARROW_MARKERS = {
|
|
11
|
+
'association' => 'arrow',
|
|
12
|
+
'aggregation' => 'diamond',
|
|
13
|
+
'composition' => 'diamond_filled',
|
|
14
|
+
'generalization' => 'triangle',
|
|
15
|
+
'uses' => 'arrow',
|
|
16
|
+
'direct' => 'arrow'
|
|
17
|
+
}.freeze
|
|
18
|
+
|
|
19
|
+
MARKER_SHAPES = {
|
|
20
|
+
'arrow' => %(<path d="M 0 0 L 10 5 L 0 10 z" fill="#{EDGE_STROKE}"/>),
|
|
21
|
+
'triangle' => %(<path d="M 0 0 L 12 5 L 0 10 z" fill="white" stroke="#{EDGE_STROKE}"/>),
|
|
22
|
+
'diamond' => %(<path d="M 0 4 L 7 0 L 14 4 L 7 8 z" fill="white" stroke="#{EDGE_STROKE}"/>),
|
|
23
|
+
'diamond_filled' => %(<path d="M 0 4 L 7 0 L 14 4 L 7 8 z" fill="#{EDGE_STROKE}"/>)
|
|
24
|
+
}.freeze
|
|
25
|
+
|
|
26
|
+
def arrow_marker(edge)
|
|
27
|
+
edge_type = edge.properties&.fetch('member_end_type', nil)
|
|
28
|
+
ARROW_MARKERS.fetch(edge_type.to_s, 'arrow')
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def defs
|
|
32
|
+
MARKER_SHAPES.filter_map { |id, path| marker_def(id, path) }.join("\n")
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def marker_def(id, path)
|
|
38
|
+
w, h = id.start_with?('diamond') ? %w[14 8] : %w[10 10]
|
|
39
|
+
%(<marker id="#{id}" viewBox="0 0 #{w} #{h}" refX="9" refY="4" ) +
|
|
40
|
+
%(markerWidth="#{w}" markerHeight="#{h}" ) +
|
|
41
|
+
%(orient="auto-start-reverse">#{path}</marker>)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|