ea 0.1.0 → 0.1.4
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/CLAUDE.md +125 -0
- data/Rakefile +12 -4
- data/TODO.next/00-publish-blocking-bugs.md +74 -0
- data/TODO.next/01-standalone-ea-gem-identity.md +76 -0
- data/TODO.next/02-optional-lutaml-uml-dependency.md +47 -0
- data/TODO.next/03-slim-lutaml-uml.md +79 -0
- data/TODO.next/04-loader-registry-for-uml-repository.md +49 -0
- data/TODO.next/05-extract-shared-transformer-methods.md +14 -0
- data/TODO.next/06-deduplicate-stereotype-loading.md +17 -0
- data/TODO.next/07-transformer-registry-in-factory.md +20 -0
- data/TODO.next/08-connector-type-registry.md +27 -0
- data/TODO.next/09-element-renderer-registry.md +29 -0
- data/TODO.next/10-connector-renderer-lsp.md +18 -0
- data/TODO.next/11-consolidate-style-knowledge.md +33 -0
- data/TODO.next/12-data-driven-from-db-row.md +24 -0
- data/TODO.next/13-extract-duplicated-methods.md +17 -0
- data/TODO.next/14-remove-dead-code.md +10 -0
- data/TODO.next/15-narrow-exception-handling.md +39 -0
- data/TODO.next/16-repository-indexes.md +28 -0
- data/TODO.next/17-fix-spec-quality-and-coverage.md +32 -0
- data/TODO.next/18-xmi-tool-specific-parser-architecture.md +172 -0
- data/TODO.next/19-fix-ea-gemspec-dependency-declarations.md +56 -0
- data/TODO.next/20-ci-requires-unreleased-lutaml-uml.md +63 -0
- data/TODO.next/21-qeatoxmi-via-xmi-gem.md +340 -0
- data/TODO.next/22-strip-respond-to-from-qeatoxmi-specs.md +32 -0
- data/TODO.next/23-cleanup-idallocator.md +41 -0
- data/TODO.next/24-tighten-parity-specs.md +42 -0
- data/TODO.next/25-sparx-eaid-format-for-synthesized-ids.md +62 -0
- data/TODO.next/26-fix-uppervalue-lowervalue-count-gap.md +51 -0
- data/TODO.next/27-extract-cardinality-module.md +68 -0
- data/TODO.next/28-extract-xml-sanitizer.md +51 -0
- data/TODO.next/29-ocp-registry-for-classifier-builders.md +58 -0
- data/TODO.next/30-struct-return-for-association-end.md +37 -0
- data/TODO.next/31-idallocator-specs.md +27 -0
- data/TODO.next/32-phase2-gap-sentinel-specs.md +53 -0
- data/TODO.next/33-normalize-lower-cleanup.md +30 -0
- data/TODO.next/34-document-member-end-order-rt-prefix.md +29 -0
- data/TODO.next/35-walk-runstate-for-instance-slots.md +76 -0
- data/TODO.next/36-wire-interface-realization.md +50 -0
- data/TODO.next/37-visibility-returns-real-booleans.md +36 -0
- data/config/diagram_styles.yml +200 -0
- data/config/model_transformations.yml +266 -0
- data/config/qea_schema.yml +1024 -0
- data/docs/ea_to_uml_type_mapping.md +89 -0
- data/docs/xmi_qea_conversion_capabilities.md +99 -0
- data/examples/lur/20251010_current_plateau_v5.1.lur +0 -0
- data/examples/lur/basic.lur +0 -0
- data/examples/lur/test-output.lur +0 -0
- data/examples/lur/test.lur +0 -0
- data/examples/lur_basic_usage.rb +221 -0
- data/examples/lur_cli_workflow.rb +263 -0
- data/examples/lur_statistics.rb +326 -0
- data/examples/qea/20251010_current_plateau_v5.1.qea +0 -0
- data/examples/qea/ArcGISWorkspace_template.qea +0 -0
- data/examples/qea/README_qea_parser.adoc +230 -0
- data/examples/qea/UmlModel_template.qea +0 -0
- data/examples/qea/basic.qea +0 -0
- data/examples/qea/simple.qea +0 -0
- data/examples/qea/simple_example.qea +0 -0
- data/examples/qea/test.qea +0 -0
- data/examples/qea_standalone_query.rb +73 -0
- data/examples/qea_to_repository.rb +51 -0
- data/examples/smoke_test_real_qea.rb +81 -0
- data/exe/ea +7 -0
- data/lib/ea/cli/app.rb +72 -0
- data/lib/ea/cli/command/base.rb +80 -0
- data/lib/ea/cli/command/convert.rb +62 -0
- data/lib/ea/cli/command/diagrams.rb +81 -0
- data/lib/ea/cli/command/list.rb +61 -0
- data/lib/ea/cli/command/parse.rb +29 -0
- data/lib/ea/cli/command/stats.rb +20 -0
- data/lib/ea/cli/command/validate.rb +41 -0
- data/lib/ea/cli/command.rb +15 -0
- data/lib/ea/cli/error.rb +34 -0
- data/lib/ea/cli/output/formatter.rb +34 -0
- data/lib/ea/cli/output/json_formatter.rb +20 -0
- data/lib/ea/cli/output/table_formatter.rb +42 -0
- data/lib/ea/cli/output/yaml_formatter.rb +20 -0
- data/lib/ea/cli/output.rb +56 -0
- data/lib/ea/cli.rb +17 -0
- data/lib/ea/diagram/configuration.rb +379 -0
- data/lib/ea/diagram/element_renderers/base_renderer.rb +77 -0
- data/lib/ea/diagram/element_renderers/class_renderer.rb +323 -0
- data/lib/ea/diagram/element_renderers/connector_renderer.rb +41 -0
- data/lib/ea/diagram/element_renderers/package_renderer.rb +61 -0
- data/lib/ea/diagram/element_renderers.rb +43 -0
- data/lib/ea/diagram/extractor.rb +560 -0
- data/lib/ea/diagram/layout_engine.rb +170 -0
- data/lib/ea/diagram/path_builder.rb +202 -0
- data/lib/ea/diagram/style_parser.rb +42 -0
- data/lib/ea/diagram/style_resolver.rb +276 -0
- data/lib/ea/diagram/svg_renderer.rb +274 -0
- data/lib/ea/diagram/util.rb +73 -0
- data/lib/ea/diagram.rb +47 -0
- data/lib/ea/qea/benchmark.rb +210 -0
- data/lib/ea/qea/database.rb +308 -0
- data/lib/ea/qea/factory/association_builder.rb +203 -0
- data/lib/ea/qea/factory/association_transformer.rb +91 -0
- data/lib/ea/qea/factory/attribute_tag_transformer.rb +57 -0
- data/lib/ea/qea/factory/attribute_transformer.rb +93 -0
- data/lib/ea/qea/factory/base_transformer.rb +177 -0
- data/lib/ea/qea/factory/class_transformer.rb +116 -0
- data/lib/ea/qea/factory/constraint_transformer.rb +75 -0
- data/lib/ea/qea/factory/data_type_transformer.rb +77 -0
- data/lib/ea/qea/factory/diagram_transformer.rb +157 -0
- data/lib/ea/qea/factory/document_builder.rb +283 -0
- data/lib/ea/qea/factory/ea_to_uml_factory.rb +229 -0
- data/lib/ea/qea/factory/enum_transformer.rb +74 -0
- data/lib/ea/qea/factory/generalization_builder.rb +227 -0
- data/lib/ea/qea/factory/generalization_transformer.rb +98 -0
- data/lib/ea/qea/factory/instance_transformer.rb +68 -0
- data/lib/ea/qea/factory/object_property_transformer.rb +58 -0
- data/lib/ea/qea/factory/operation_transformer.rb +66 -0
- data/lib/ea/qea/factory/package_transformer.rb +145 -0
- data/lib/ea/qea/factory/reference_resolver.rb +99 -0
- data/lib/ea/qea/factory/stereotype_loader.rb +39 -0
- data/lib/ea/qea/factory/tagged_value_transformer.rb +38 -0
- data/lib/ea/qea/factory/transformer_registry.rb +80 -0
- data/lib/ea/qea/factory.rb +37 -0
- data/lib/ea/qea/file_detector.rb +178 -0
- data/lib/ea/qea/infrastructure/database_connection.rb +100 -0
- data/lib/ea/qea/infrastructure/schema_reader.rb +136 -0
- data/lib/ea/qea/infrastructure/table_reader.rb +224 -0
- data/lib/ea/qea/infrastructure.rb +12 -0
- data/lib/ea/qea/models/base_model.rb +59 -0
- data/lib/ea/qea/models/ea_attribute.rb +109 -0
- data/lib/ea/qea/models/ea_attribute_tag.rb +100 -0
- data/lib/ea/qea/models/ea_complexity_type.rb +79 -0
- data/lib/ea/qea/models/ea_connector.rb +160 -0
- data/lib/ea/qea/models/ea_connector_type.rb +60 -0
- data/lib/ea/qea/models/ea_constraint_type.rb +63 -0
- data/lib/ea/qea/models/ea_datatype.rb +104 -0
- data/lib/ea/qea/models/ea_diagram.rb +115 -0
- data/lib/ea/qea/models/ea_diagram_link.rb +78 -0
- data/lib/ea/qea/models/ea_diagram_object.rb +73 -0
- data/lib/ea/qea/models/ea_diagram_type.rb +56 -0
- data/lib/ea/qea/models/ea_document.rb +63 -0
- data/lib/ea/qea/models/ea_object.rb +223 -0
- data/lib/ea/qea/models/ea_object_constraint.rb +53 -0
- data/lib/ea/qea/models/ea_object_property.rb +87 -0
- data/lib/ea/qea/models/ea_object_type.rb +73 -0
- data/lib/ea/qea/models/ea_operation.rb +127 -0
- data/lib/ea/qea/models/ea_operation_param.rb +76 -0
- data/lib/ea/qea/models/ea_package.rb +78 -0
- data/lib/ea/qea/models/ea_script.rb +62 -0
- data/lib/ea/qea/models/ea_status_type.rb +66 -0
- data/lib/ea/qea/models/ea_stereotype.rb +57 -0
- data/lib/ea/qea/models/ea_tagged_value.rb +99 -0
- data/lib/ea/qea/models/ea_xref.rb +165 -0
- data/lib/ea/qea/models.rb +35 -0
- data/lib/ea/qea/repositories/base_repository.rb +225 -0
- data/lib/ea/qea/repositories/object_repository.rb +219 -0
- data/lib/ea/qea/repositories.rb +10 -0
- data/lib/ea/qea/services/configuration.rb +211 -0
- data/lib/ea/qea/services/database_loader.rb +191 -0
- data/lib/ea/qea/services.rb +10 -0
- data/lib/ea/qea/validation/association_validator.rb +73 -0
- data/lib/ea/qea/validation/attribute_validator.rb +91 -0
- data/lib/ea/qea/validation/base_validator.rb +331 -0
- data/lib/ea/qea/validation/class_validator.rb +121 -0
- data/lib/ea/qea/validation/database/circular_reference_validator.rb +109 -0
- data/lib/ea/qea/validation/database/orphan_validator.rb +153 -0
- data/lib/ea/qea/validation/database/referential_integrity_validator.rb +128 -0
- data/lib/ea/qea/validation/database.rb +16 -0
- data/lib/ea/qea/validation/diagram_validator.rb +112 -0
- data/lib/ea/qea/validation/formatters/json_formatter.rb +137 -0
- data/lib/ea/qea/validation/formatters/text_formatter.rb +235 -0
- data/lib/ea/qea/validation/formatters.rb +12 -0
- data/lib/ea/qea/validation/operation_validator.rb +71 -0
- data/lib/ea/qea/validation/package_validator.rb +111 -0
- data/lib/ea/qea/validation/validation_engine.rb +387 -0
- data/lib/ea/qea/validation/validation_message.rb +144 -0
- data/lib/ea/qea/validation/validation_result.rb +210 -0
- data/lib/ea/qea/validation/validator_registry.rb +134 -0
- data/lib/ea/qea/validation.rb +28 -0
- data/lib/ea/qea/verification/comparison_result.rb +264 -0
- data/lib/ea/qea/verification/document_normalizer.rb +169 -0
- data/lib/ea/qea/verification/document_verifier.rb +322 -0
- data/lib/ea/qea/verification/element_comparator.rb +277 -0
- data/lib/ea/qea/verification/structure_matcher.rb +287 -0
- data/lib/ea/qea/verification.rb +14 -0
- data/lib/ea/qea.rb +185 -0
- data/lib/ea/transformations/configuration.rb +333 -0
- data/lib/ea/transformations/format_registry.rb +366 -0
- data/lib/ea/transformations/parsers/base_parser.rb +482 -0
- data/lib/ea/transformations/parsers/qea_parser.rb +401 -0
- data/lib/ea/transformations/parsers/xmi_parser.rb +243 -0
- data/lib/ea/transformations/transformation_engine.rb +390 -0
- data/lib/ea/transformations.rb +85 -0
- data/lib/ea/transformers/qea_to_xmi/association_end.rb +19 -0
- data/lib/ea/transformers/qea_to_xmi/cardinality.rb +96 -0
- data/lib/ea/transformers/qea_to_xmi/context.rb +106 -0
- data/lib/ea/transformers/qea_to_xmi/guid_format.rb +56 -0
- data/lib/ea/transformers/qea_to_xmi/id_allocator.rb +92 -0
- data/lib/ea/transformers/qea_to_xmi/run_state.rb +107 -0
- data/lib/ea/transformers/qea_to_xmi/transformer.rb +607 -0
- data/lib/ea/transformers/qea_to_xmi/visibility.rb +73 -0
- data/lib/ea/transformers/qea_to_xmi.rb +29 -0
- data/lib/ea/transformers/uml_to_xmi/id_generator.rb +54 -0
- data/lib/ea/transformers/uml_to_xmi/transformer.rb +152 -0
- data/lib/ea/transformers/uml_to_xmi/writer.rb +96 -0
- data/lib/ea/transformers/uml_to_xmi.rb +16 -0
- data/lib/ea/transformers.rb +34 -0
- data/lib/ea/version.rb +1 -1
- data/lib/ea/xmi/liquid_drops/association_drop.rb +56 -0
- data/lib/ea/xmi/liquid_drops/attribute_drop.rb +72 -0
- data/lib/ea/xmi/liquid_drops/cardinality_drop.rb +35 -0
- data/lib/ea/xmi/liquid_drops/connector_drop.rb +54 -0
- data/lib/ea/xmi/liquid_drops/constraint_drop.rb +29 -0
- data/lib/ea/xmi/liquid_drops/data_type_drop.rb +63 -0
- data/lib/ea/xmi/liquid_drops/dependency_drop.rb +36 -0
- data/lib/ea/xmi/liquid_drops/diagram_drop.rb +34 -0
- data/lib/ea/xmi/liquid_drops/enum_drop.rb +49 -0
- data/lib/ea/xmi/liquid_drops/enum_owned_literal_drop.rb +25 -0
- data/lib/ea/xmi/liquid_drops/generalization_attribute_drop.rb +87 -0
- data/lib/ea/xmi/liquid_drops/generalization_drop.rb +127 -0
- data/lib/ea/xmi/liquid_drops/klass_drop.rb +191 -0
- data/lib/ea/xmi/liquid_drops/operation_drop.rb +29 -0
- data/lib/ea/xmi/liquid_drops/package_drop.rb +108 -0
- data/lib/ea/xmi/liquid_drops/root_drop.rb +34 -0
- data/lib/ea/xmi/liquid_drops/source_target_drop.rb +43 -0
- data/lib/ea/xmi/lookup_service.rb +89 -0
- data/lib/ea/xmi/parser.rb +919 -0
- data/lib/ea/xmi.rb +35 -0
- data/lib/ea.rb +10 -1
- metadata +382 -9
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ea
|
|
4
|
+
module Transformers
|
|
5
|
+
module QeaToXmi
|
|
6
|
+
# Pure conversions between Sparx EA GUID strings and XMI identifier
|
|
7
|
+
# strings. No state, no I/O — same input always yields same output.
|
|
8
|
+
#
|
|
9
|
+
# EA stores identifiers as `{ABCD-1234-...}` braced GUID strings. XMI
|
|
10
|
+
# serializations use unbraced, dash→underscore forms prefixed with
|
|
11
|
+
# `EAID_` (most elements) or `EAPK_` (packages at the model root).
|
|
12
|
+
module GuidFormat
|
|
13
|
+
# Braces and dashes both become underscores; collapse runs of
|
|
14
|
+
# consecutive underscores so `{AB-CD}` → `AB_CD` (not `_AB_CD_`).
|
|
15
|
+
SEP = /[-{}]/
|
|
16
|
+
|
|
17
|
+
module_function
|
|
18
|
+
|
|
19
|
+
# @param ea_guid [String, nil] e.g. "{AB-CD-EF}"
|
|
20
|
+
# @param prefix [String] "EAID" (default) or "EAPK"
|
|
21
|
+
# @return [String, nil] e.g. "EAID_AB_CD_EF"
|
|
22
|
+
def ea_guid_to_xmi_id(ea_guid, prefix: "EAID")
|
|
23
|
+
return nil if ea_guid.nil? || ea_guid.empty?
|
|
24
|
+
|
|
25
|
+
clean = ea_guid.gsub(SEP, "_").gsub(/_+/, "_").gsub(/\A_/, "").gsub(/_\z/, "")
|
|
26
|
+
"#{prefix}_#{clean}"
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# @param xmi_id [String, nil] e.g. "EAID_AB_CD_EF"
|
|
30
|
+
# @return [String, nil] e.g. "{AB-CD-EF}"
|
|
31
|
+
def xmi_id_to_ea_guid(xmi_id)
|
|
32
|
+
return nil if xmi_id.nil? || xmi_id.empty?
|
|
33
|
+
|
|
34
|
+
body = xmi_id.sub(/\A(?:EAID|EAPK)_/, "")
|
|
35
|
+
"{#{body.tr('_', '-')}}"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Build a member-end xmi:id for one side of a connector.
|
|
39
|
+
#
|
|
40
|
+
# Sparx EA's convention: take the connector's GUID, drop the first two
|
|
41
|
+
# hex characters of its first segment, and prepend `src` or `dst`.
|
|
42
|
+
#
|
|
43
|
+
# @param connector_xmi_id [String] e.g. "EAID_AB12CDEF_..."
|
|
44
|
+
# @param side [Symbol] :source or :destination
|
|
45
|
+
# @return [String] e.g. "EAID_src2CDEF_..."
|
|
46
|
+
def connector_end_xmi_id(connector_xmi_id, side:)
|
|
47
|
+
tag = side == :source ? "src" : "dst"
|
|
48
|
+
body = connector_xmi_id.sub(/\A(?:EAID|EAPK)_/, "")
|
|
49
|
+
first_segment, rest = body.split("_", 2)
|
|
50
|
+
trimmed = first_segment.length > 2 ? first_segment[2..] : first_segment
|
|
51
|
+
rest ? "EAID_#{tag}#{trimmed}_#{rest}" : "EAID_#{tag}#{trimmed}"
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ea
|
|
4
|
+
module Transformers
|
|
5
|
+
module QeaToXmi
|
|
6
|
+
# Allocates synthetic xmi:id values for elements that don't have a
|
|
7
|
+
# natural GUID-based one — e.g. literal `<lowerValue>` nodes, or
|
|
8
|
+
# `<ownedComment>` bodies synthesized from EA's Note objects.
|
|
9
|
+
#
|
|
10
|
+
# Sparx's EAID format reserves prefixes like `LI` (LiteralInt),
|
|
11
|
+
# `SL` (Slot), `NL` (NameLabel), `DB` (DiagramBounds), `OE` (OpaqueExpr),
|
|
12
|
+
# `RT` (Return parameter) for these synthesized identifiers.
|
|
13
|
+
#
|
|
14
|
+
# Output shape: `EAID_<PREFIX><NNNNNN><GUID_TAIL>` where:
|
|
15
|
+
#
|
|
16
|
+
# - `EAID_` matches the prefix used by all other Sparx XMI element IDs.
|
|
17
|
+
# - `<PREFIX>` is a Sparx-reserved literal prefix (LI, SL, OE, ...).
|
|
18
|
+
# - `<NNNNNN>` is a 6-digit zero-padded counter, scoped to the
|
|
19
|
+
# IdAllocator instance (one per `Transformer#serialize` call).
|
|
20
|
+
# - `<GUID_TAIL>` is the parent element's EA GUID normalised to
|
|
21
|
+
# Sparx's wire form. The leading underscore from the opening
|
|
22
|
+
# brace of `{GUID-...}` is preserved so the output matches
|
|
23
|
+
# real Sparx XMI byte-for-byte (e.g. `EAID_LI000001__EEB1_...`).
|
|
24
|
+
# When `parent_guid` is nil, no tail is emitted.
|
|
25
|
+
#
|
|
26
|
+
# The allocator is memoised by `seed`: same seed returns the same
|
|
27
|
+
# allocated ID. Different seeds get different counters.
|
|
28
|
+
class IdAllocator
|
|
29
|
+
# Well-known prefixes Sparx uses for synthesized IDs.
|
|
30
|
+
LITERAL_INTEGER = "LI"
|
|
31
|
+
OPAQUE_EXPRESSION = "OE"
|
|
32
|
+
SLOT = "SL"
|
|
33
|
+
NAME_LABEL = "NL"
|
|
34
|
+
DIAGRAM_BOUNDS = "DB"
|
|
35
|
+
RETURN_PARAMETER = "RT"
|
|
36
|
+
|
|
37
|
+
# Leading-underscore-preserving normalisation: `{AB-CD}` → `_AB_CD`.
|
|
38
|
+
# Matches the wire form Sparx emits for parent-guid-suffixed IDs.
|
|
39
|
+
GUID_BRACE_OR_DASH = /[-{}]/
|
|
40
|
+
|
|
41
|
+
def initialize
|
|
42
|
+
@counter = 0
|
|
43
|
+
@assigned = {}
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# @param prefix [String] one of LITERAL_INTEGER, OPAQUE_EXPRESSION, ...
|
|
47
|
+
# @param seed [String, nil] stable seed for memoization (e.g. the
|
|
48
|
+
# source record's object_id). Same seed returns same allocated id.
|
|
49
|
+
# @param parent_guid [String, nil] the owning element's EA GUID
|
|
50
|
+
# (e.g. `{EEB1-...}`). When provided, the synthesised ID carries
|
|
51
|
+
# the parent GUID tail so it is traceable and round-trip-safe.
|
|
52
|
+
# @return [String] e.g. "EAID_LI000001__EEB1_4de7_98F5_670D6EE4A52B"
|
|
53
|
+
def allocate(prefix:, seed: nil, parent_guid: nil)
|
|
54
|
+
return @assigned[seed] if seed && @assigned.key?(seed)
|
|
55
|
+
|
|
56
|
+
@counter += 1
|
|
57
|
+
id = compose_id(prefix: prefix, n: @counter, parent_guid: parent_guid)
|
|
58
|
+
@assigned[seed] = id if seed
|
|
59
|
+
id
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
private
|
|
63
|
+
|
|
64
|
+
def compose_id(prefix:, n:, parent_guid:)
|
|
65
|
+
tail = guid_tail(parent_guid)
|
|
66
|
+
if tail
|
|
67
|
+
# The format is `EAID_LI<NN>_<guid_tail>` where guid_tail
|
|
68
|
+
# preserves its leading underscore. Together with the
|
|
69
|
+
# separator `_` that yields the double-underscore form
|
|
70
|
+
# Sparx emits (`EAID_LI000001__EEB1_...`).
|
|
71
|
+
format("EAID_%<prefix>s%<n>06d_%<tail>s", prefix: prefix, n: n, tail: tail)
|
|
72
|
+
else
|
|
73
|
+
format("EAID_%<prefix>s%<n>06d", prefix: prefix, n: n)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Sparx preserves the leading underscore that comes from the
|
|
78
|
+
# opening brace of the EA GUID. Strip the closing-brace trailing
|
|
79
|
+
# underscore only.
|
|
80
|
+
#
|
|
81
|
+
# `{EEB1-4de7-98F5-670D6EE4A52B}` → `_EEB1_4de7_98F5_670D6EE4A52B`
|
|
82
|
+
def guid_tail(parent_guid)
|
|
83
|
+
return nil if parent_guid.nil? || parent_guid.empty?
|
|
84
|
+
|
|
85
|
+
parent_guid
|
|
86
|
+
.gsub(GUID_BRACE_OR_DASH, "_")
|
|
87
|
+
.sub(/_\z/, "")
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "strscan"
|
|
4
|
+
|
|
5
|
+
module Ea
|
|
6
|
+
module Transformers
|
|
7
|
+
module QeaToXmi
|
|
8
|
+
# Pure-function parser for EA's RunState column on `t_object`.
|
|
9
|
+
#
|
|
10
|
+
# EA serialises instance-specification run-state as a delimited
|
|
11
|
+
# string of the form:
|
|
12
|
+
#
|
|
13
|
+
# @VAR;Variable=<name>;Value=<value>;Op=<op>;@ENDVAR;
|
|
14
|
+
#
|
|
15
|
+
# Multiple variables concatenate directly:
|
|
16
|
+
#
|
|
17
|
+
# @VAR;Variable=a;Value=1;Op==;@ENDVAR;@VAR;Variable=b;Value=2;Op==;@ENDVAR;
|
|
18
|
+
#
|
|
19
|
+
# Each `@VAR ... @ENDVAR;` block maps to one UML Slot. The
|
|
20
|
+
# `Variable` is the attribute name on the classifier (used to
|
|
21
|
+
# resolve the definingFeature EAID at transformation time).
|
|
22
|
+
# The `Value` plus `Op` form the OpaqueExpression body — Sparx
|
|
23
|
+
# prepends the operator character to the value (`Op==` →
|
|
24
|
+
# `body="=Value"`).
|
|
25
|
+
#
|
|
26
|
+
# The parser is pure: no I/O, no state. Same input always yields
|
|
27
|
+
# the same output array of {Variable, Value, Op} structs.
|
|
28
|
+
module RunState
|
|
29
|
+
# A single parsed RunState variable binding.
|
|
30
|
+
#
|
|
31
|
+
# @!attribute variable [String] the attribute name on the classifier
|
|
32
|
+
# @!attribute value [String] the literal value text
|
|
33
|
+
# @!attribute op [String] the operator character(s) EA stored
|
|
34
|
+
Binding = Struct.new(:variable, :value, :op) do
|
|
35
|
+
# Sparx serialises the value with the operator character
|
|
36
|
+
# prepended (`Op==` → `body="=Value"`). For other operators
|
|
37
|
+
# (`!=`, `<`, `>`) the full operator string is prepended.
|
|
38
|
+
#
|
|
39
|
+
# @return [String] the body to set on the OpaqueExpression
|
|
40
|
+
def body
|
|
41
|
+
return value if op.nil? || op.empty?
|
|
42
|
+
|
|
43
|
+
"#{op[0]}#{value}"
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
module_function
|
|
48
|
+
|
|
49
|
+
# @param raw [String, nil] the EA RunState column content
|
|
50
|
+
# @return [Array<Binding>] one Binding per @VAR block; empty
|
|
51
|
+
# array for nil/empty/blank input.
|
|
52
|
+
def parse(raw)
|
|
53
|
+
return [] if raw.nil? || raw.to_s.strip.empty?
|
|
54
|
+
|
|
55
|
+
stripped = raw.to_s
|
|
56
|
+
each_var_block(stripped).map do |block|
|
|
57
|
+
parse_binding(block)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# ---- Internal helpers ----
|
|
62
|
+
|
|
63
|
+
# Yields each `@VAR;...;@ENDVAR;` block body (without the
|
|
64
|
+
# delimiters). Tolerant of multiple blocks concatenated
|
|
65
|
+
# without separation.
|
|
66
|
+
def each_var_block(raw)
|
|
67
|
+
return enum_for(:each_var_block, raw) unless block_given?
|
|
68
|
+
|
|
69
|
+
scanner = StringScanner.new(raw)
|
|
70
|
+
until scanner.eos?
|
|
71
|
+
scanner.scan_until(/@VAR;/) || break
|
|
72
|
+
body = scanner.scan_until(/@ENDVAR;/)
|
|
73
|
+
break if body.nil?
|
|
74
|
+
|
|
75
|
+
yield body.sub(/@ENDVAR;\z/, "")
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# @param block [String] the inside of one @VAR;...;@ENDVAR;
|
|
80
|
+
# block, e.g. `Variable=a;Value=1;Op==;`
|
|
81
|
+
# @return [Binding]
|
|
82
|
+
def parse_binding(block)
|
|
83
|
+
fields = parse_fields(block)
|
|
84
|
+
Binding.new(
|
|
85
|
+
fields["Variable"] || "",
|
|
86
|
+
fields["Value"] || "",
|
|
87
|
+
fields["Op"] || "",
|
|
88
|
+
)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Parse `Key=value;` pairs into a Hash. Values may contain
|
|
92
|
+
# `;` literally — only split on `;` immediately followed by a
|
|
93
|
+
# known key boundary. In practice EA's values rarely contain
|
|
94
|
+
# semicolons, so a simple split is sufficient.
|
|
95
|
+
#
|
|
96
|
+
# @param block [String]
|
|
97
|
+
# @return [Hash{String=>String}]
|
|
98
|
+
def parse_fields(block)
|
|
99
|
+
block.split(";").each_with_object({}) do |pair, hash|
|
|
100
|
+
key, value = pair.split("=", 2)
|
|
101
|
+
hash[key] = value.to_s if key && !key.empty?
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|