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,401 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ea
|
|
4
|
+
module Transformations
|
|
5
|
+
module Parsers
|
|
6
|
+
# QEA Parser implements the BaseParser interface for Enterprise Architect
|
|
7
|
+
# database files.
|
|
8
|
+
#
|
|
9
|
+
# This parser wraps the existing Ea::Qea functionality and adapts it
|
|
10
|
+
# to the new unified transformation architecture. It provides enhanced
|
|
11
|
+
# error handling, progress tracking, and configuration integration.
|
|
12
|
+
class QeaParser < BaseParser
|
|
13
|
+
# @return [Ea::Qea::Database] Loaded QEA database
|
|
14
|
+
attr_reader :qea_database
|
|
15
|
+
|
|
16
|
+
# @return [Hash] Database statistics
|
|
17
|
+
attr_reader :database_stats
|
|
18
|
+
|
|
19
|
+
# Get parser format name
|
|
20
|
+
#
|
|
21
|
+
# @return [String] Human-readable format name
|
|
22
|
+
def format_name
|
|
23
|
+
"Enterprise Architect Database (QEA)"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Get list of supported file extensions
|
|
27
|
+
#
|
|
28
|
+
# @return [Array<String>] List of extensions
|
|
29
|
+
def supported_extensions
|
|
30
|
+
[".qea", ".eap", ".eapx"]
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def content_patterns
|
|
34
|
+
[/^SQLite format/]
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def priority
|
|
38
|
+
90
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
protected
|
|
42
|
+
|
|
43
|
+
# Core parsing implementation for QEA files
|
|
44
|
+
#
|
|
45
|
+
# @param file_path [String] Path to the QEA file
|
|
46
|
+
# @return [Lutaml::Uml::Document] Parsed UML document
|
|
47
|
+
def parse_internal(file_path)
|
|
48
|
+
# Validate QEA file format
|
|
49
|
+
validate_qea_format!(file_path)
|
|
50
|
+
|
|
51
|
+
# Load QEA database with progress tracking
|
|
52
|
+
@qea_database = load_qea_database(file_path)
|
|
53
|
+
|
|
54
|
+
# Get database statistics
|
|
55
|
+
@database_stats = @qea_database.stats
|
|
56
|
+
|
|
57
|
+
# Transform to UML document using existing factory
|
|
58
|
+
document = transform_qea_to_uml(@qea_database, file_path)
|
|
59
|
+
|
|
60
|
+
# Post-process document
|
|
61
|
+
post_process_qea_document(document, file_path)
|
|
62
|
+
|
|
63
|
+
document
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Hook called before parsing starts
|
|
67
|
+
#
|
|
68
|
+
# @param file_path [String] Path to the file being parsed
|
|
69
|
+
# @return [void]
|
|
70
|
+
def before_parse(file_path) # rubocop:disable Metrics/MethodLength
|
|
71
|
+
add_info("Starting QEA parsing for: #{file_path}")
|
|
72
|
+
|
|
73
|
+
# Check file size and provide estimates
|
|
74
|
+
file_size = File.size(file_path)
|
|
75
|
+
add_info("QEA file size: #{format_file_size(file_size)}")
|
|
76
|
+
|
|
77
|
+
if file_size > 500 * 1024 * 1024 # 500MB
|
|
78
|
+
add_warning("Very large QEA file detected, " \
|
|
79
|
+
"parsing may take significant time")
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Quick database info check
|
|
83
|
+
begin
|
|
84
|
+
quick_stats = get_quick_database_stats(file_path)
|
|
85
|
+
add_info("Database contains approximately: " \
|
|
86
|
+
"#{format_database_stats(quick_stats)}")
|
|
87
|
+
rescue StandardError => e
|
|
88
|
+
add_warning("Could not get quick database stats: #{e.message}")
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Hook called after parsing completes
|
|
93
|
+
#
|
|
94
|
+
# @param document [Lutaml::Uml::Document] Parsed document
|
|
95
|
+
# @param file_path [String] Path to the source file
|
|
96
|
+
# @return [Lutaml::Uml::Document] Processed document
|
|
97
|
+
def after_parse(document, file_path)
|
|
98
|
+
# Add QEA-specific metadata
|
|
99
|
+
add_qea_metadata(document, file_path)
|
|
100
|
+
|
|
101
|
+
# Validate QEA-specific aspects
|
|
102
|
+
if @options[:validate_transformation]
|
|
103
|
+
validate_qea_transformation(document)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Add comprehensive statistics
|
|
107
|
+
add_transformation_statistics(document)
|
|
108
|
+
|
|
109
|
+
document
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Get default parsing options for QEA
|
|
113
|
+
#
|
|
114
|
+
# @return [Hash] Default options hash
|
|
115
|
+
def default_options
|
|
116
|
+
super.merge(
|
|
117
|
+
include_diagrams: true,
|
|
118
|
+
validate_transformation: false,
|
|
119
|
+
load_progress_callback: true,
|
|
120
|
+
cache_database: false,
|
|
121
|
+
strict_schema_validation: false,
|
|
122
|
+
)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
private
|
|
126
|
+
|
|
127
|
+
# Validate QEA file format
|
|
128
|
+
#
|
|
129
|
+
# @param file_path [String] Path to validate
|
|
130
|
+
# @raise [ParseError] if file is not valid QEA
|
|
131
|
+
def validate_qea_format!(file_path) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
|
|
132
|
+
# Check if it's a SQLite database (QEA files are SQLite)
|
|
133
|
+
File.open(file_path, "rb") do |file|
|
|
134
|
+
header = file.read(16)
|
|
135
|
+
|
|
136
|
+
unless header == "SQLite format 3\0"
|
|
137
|
+
add_error("File does not appear to be a SQLite database")
|
|
138
|
+
raise Parsers::ParseError.new("Invalid QEA format - " \
|
|
139
|
+
"not a SQLite database")
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Additional validation using QEA infrastructure
|
|
144
|
+
begin
|
|
145
|
+
Ea::Qea.connect(file_path).with_connection do |db|
|
|
146
|
+
# Check for required EA tables
|
|
147
|
+
required_tables = %w[t_object t_package t_connector t_attribute]
|
|
148
|
+
missing_tables = required_tables.reject do |table|
|
|
149
|
+
db.execute(
|
|
150
|
+
"SELECT name FROM sqlite_master " \
|
|
151
|
+
"WHERE type='table' AND name=?", table
|
|
152
|
+
).any?
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
if missing_tables.any?
|
|
156
|
+
add_warning("Missing expected EA tables: " \
|
|
157
|
+
"#{missing_tables.join(', ')}")
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
rescue StandardError => e
|
|
161
|
+
add_error("Failed to validate QEA database structure: #{e.message}")
|
|
162
|
+
raise Parsers::ParseError.new("QEA validation failed",
|
|
163
|
+
original_error: e)
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Load QEA database with progress tracking
|
|
168
|
+
#
|
|
169
|
+
# @param file_path [String] Path to QEA file
|
|
170
|
+
# @return [Ea::Qea::Database] Loaded database
|
|
171
|
+
def load_qea_database(file_path) # rubocop:disable Metrics/MethodLength
|
|
172
|
+
progress_callback = nil
|
|
173
|
+
|
|
174
|
+
if @options[:load_progress_callback]
|
|
175
|
+
progress_callback = create_progress_callback
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# Load database using existing QEA infrastructure
|
|
179
|
+
if progress_callback
|
|
180
|
+
Ea::Qea.load_database(file_path, &progress_callback)
|
|
181
|
+
else
|
|
182
|
+
Ea::Qea.load_database(file_path)
|
|
183
|
+
end
|
|
184
|
+
rescue StandardError => e
|
|
185
|
+
add_error("Failed to load QEA database: #{e.message}")
|
|
186
|
+
raise Parsers::ParseError.new("QEA database loading failed",
|
|
187
|
+
original_error: e)
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# Create progress callback for database loading
|
|
191
|
+
#
|
|
192
|
+
# @return [Proc] Progress callback procedure
|
|
193
|
+
def create_progress_callback
|
|
194
|
+
proc do |table_name, current, total|
|
|
195
|
+
percentage = (current.to_f / total * 100).round(1)
|
|
196
|
+
add_info("Loading #{table_name}: #{current}/#{total} " \
|
|
197
|
+
"(#{percentage}%)")
|
|
198
|
+
|
|
199
|
+
# Check if we should fail fast on too many errors
|
|
200
|
+
if should_fail_fast? && has_errors?
|
|
201
|
+
raise Parsers::ParseError.new("Failing fast due to errors " \
|
|
202
|
+
"during loading")
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
# Transform QEA database to UML document
|
|
208
|
+
#
|
|
209
|
+
# @param database [Ea::Qea::Database] QEA database
|
|
210
|
+
# @param file_path [String] Source file path
|
|
211
|
+
# @return [Lutaml::Uml::Document] UML document
|
|
212
|
+
def transform_qea_to_uml(database, file_path) # rubocop:disable Metrics/MethodLength
|
|
213
|
+
# Prepare transformation options
|
|
214
|
+
transform_options = prepare_transformation_options(file_path)
|
|
215
|
+
|
|
216
|
+
# Use existing QEA factory for transformation
|
|
217
|
+
factory = Ea::Qea::Factory::EaToUmlFactory.new(database,
|
|
218
|
+
transform_options)
|
|
219
|
+
|
|
220
|
+
# Apply custom transformers if configured
|
|
221
|
+
apply_custom_transformers(factory) if @options[:custom_transformers]
|
|
222
|
+
|
|
223
|
+
# Execute transformation
|
|
224
|
+
factory.create_document
|
|
225
|
+
rescue StandardError => e
|
|
226
|
+
add_error("Failed to transform QEA to UML: #{e.message}")
|
|
227
|
+
raise Parsers::ParseError.new("QEA transformation failed",
|
|
228
|
+
original_error: e)
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
# Prepare transformation options from parser configuration
|
|
232
|
+
#
|
|
233
|
+
# @param file_path [String] Source file path
|
|
234
|
+
# @return [Hash] Transformation options
|
|
235
|
+
def prepare_transformation_options(file_path)
|
|
236
|
+
{
|
|
237
|
+
include_diagrams: @options[:include_diagrams],
|
|
238
|
+
validate: @options[:validate_output],
|
|
239
|
+
document_name: extract_document_name(file_path),
|
|
240
|
+
}
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
# Extract document name from file path
|
|
244
|
+
#
|
|
245
|
+
# @param file_path [String] File path
|
|
246
|
+
# @return [String] Document name
|
|
247
|
+
def extract_document_name(file_path)
|
|
248
|
+
@options[:document_name] || File.basename(file_path, ".*")
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
# Apply custom transformers to factory
|
|
252
|
+
#
|
|
253
|
+
# @param factory [Ea::Qea::Factory::EaToUmlFactory]
|
|
254
|
+
# Factory to enhance
|
|
255
|
+
# @return [void]
|
|
256
|
+
def apply_custom_transformers(_factory)
|
|
257
|
+
# This is a placeholder for future custom transformer support
|
|
258
|
+
# Could allow configuration-driven transformer customization
|
|
259
|
+
add_info("Custom transformers would be applied here")
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
# Post-process QEA document
|
|
263
|
+
#
|
|
264
|
+
# @param document [Lutaml::Uml::Document] Document to process
|
|
265
|
+
# @param file_path [String] Source file path
|
|
266
|
+
# @return [void]
|
|
267
|
+
def post_process_qea_document(document, file_path)
|
|
268
|
+
assign_if_supported(document, :source_file=, file_path)
|
|
269
|
+
assign_if_supported(document, :source_format=, "QEA")
|
|
270
|
+
assign_if_supported(document, :database_stats=, @database_stats)
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
# Add QEA-specific metadata to document
|
|
274
|
+
#
|
|
275
|
+
# @param document [Lutaml::Uml::Document] Document to enhance
|
|
276
|
+
# @param file_path [String] Source file path
|
|
277
|
+
# @return [void]
|
|
278
|
+
def add_qea_metadata(document, file_path) # rubocop:disable Metrics/MethodLength
|
|
279
|
+
metadata = {
|
|
280
|
+
source_file: file_path,
|
|
281
|
+
source_format: "Enterprise Architect Database",
|
|
282
|
+
parsed_at: Time.now,
|
|
283
|
+
parser: self.class.name,
|
|
284
|
+
parser_version: "1.0",
|
|
285
|
+
database_stats: @database_stats,
|
|
286
|
+
qea_version: detect_qea_version,
|
|
287
|
+
options: @options,
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
assign_if_supported(document, :parsing_metadata=, metadata)
|
|
291
|
+
assign_if_supported(document, :metadata=, metadata)
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
# Detect QEA/EA version from database
|
|
295
|
+
#
|
|
296
|
+
# @return [String] Detected version or "unknown"
|
|
297
|
+
def detect_qea_version
|
|
298
|
+
return "unknown" unless @qea_database
|
|
299
|
+
|
|
300
|
+
# This is a simplified version detection
|
|
301
|
+
# In practice, you might check specific tables or metadata
|
|
302
|
+
"EA Database"
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
# Validate QEA transformation quality
|
|
306
|
+
#
|
|
307
|
+
# @param document [Lutaml::Uml::Document] Document to validate
|
|
308
|
+
# @return [void]
|
|
309
|
+
def validate_qea_transformation(document) # rubocop:disable Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
|
|
310
|
+
# Compare document stats with database stats
|
|
311
|
+
if @database_stats
|
|
312
|
+
doc_packages = document.packages&.size || 0
|
|
313
|
+
db_packages = @database_stats["packages"] || 0
|
|
314
|
+
|
|
315
|
+
if doc_packages < db_packages
|
|
316
|
+
add_warning("Document has fewer packages (#{doc_packages}) " \
|
|
317
|
+
"than database (#{db_packages})")
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
doc_classes = document.classes&.size || 0
|
|
321
|
+
db_objects = @database_stats["objects"] || 0
|
|
322
|
+
|
|
323
|
+
if doc_classes < db_objects * 0.8 # Allow some variance
|
|
324
|
+
add_warning("Document classes (#{doc_classes}) significantly " \
|
|
325
|
+
"fewer than database objects (#{db_objects})")
|
|
326
|
+
end
|
|
327
|
+
end
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
# Add comprehensive transformation statistics
|
|
331
|
+
#
|
|
332
|
+
# @param document [Lutaml::Uml::Document] Document to analyze
|
|
333
|
+
# @return [void]
|
|
334
|
+
def add_transformation_statistics(document) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
|
|
335
|
+
doc_stats = {
|
|
336
|
+
packages: document.packages&.size || 0,
|
|
337
|
+
classes: document.classes&.size || 0,
|
|
338
|
+
data_types: document.data_types&.size || 0,
|
|
339
|
+
enumerations: document.enums&.size || 0,
|
|
340
|
+
associations: document.associations&.size || 0,
|
|
341
|
+
diagrams: document.diagrams&.size || 0,
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
comparison = compare_stats_with_database(doc_stats)
|
|
345
|
+
|
|
346
|
+
add_info("QEA transformation completed: " \
|
|
347
|
+
"#{format_statistics(doc_stats)}")
|
|
348
|
+
if comparison.any?
|
|
349
|
+
add_info("Database comparison: #{comparison.join(', ')}")
|
|
350
|
+
end
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
# Compare document statistics with database statistics
|
|
354
|
+
#
|
|
355
|
+
# @param doc_stats [Hash] Document statistics
|
|
356
|
+
# @return [Array<String>] Comparison notes
|
|
357
|
+
def compare_stats_with_database(doc_stats) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
|
|
358
|
+
return [] unless @database_stats
|
|
359
|
+
|
|
360
|
+
comparisons = []
|
|
361
|
+
|
|
362
|
+
if @database_stats["packages"]
|
|
363
|
+
ratio = doc_stats[:packages].to_f / @database_stats["packages"]
|
|
364
|
+
comparisons << "packages #{(ratio * 100).round(1)}%"
|
|
365
|
+
end
|
|
366
|
+
|
|
367
|
+
if @database_stats["objects"]
|
|
368
|
+
ratio = doc_stats[:classes].to_f / @database_stats["objects"]
|
|
369
|
+
comparisons << "classes #{(ratio * 100).round(1)}%"
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
comparisons
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
# Get quick database statistics without full loading
|
|
376
|
+
#
|
|
377
|
+
# @param file_path [String] Path to QEA file
|
|
378
|
+
# @return [Hash] Quick statistics
|
|
379
|
+
def get_quick_database_stats(file_path)
|
|
380
|
+
Ea::Qea.database_info(file_path)
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
# Format database statistics for display
|
|
384
|
+
#
|
|
385
|
+
# @param stats [Hash] Statistics hash
|
|
386
|
+
# @return [String] Formatted string
|
|
387
|
+
def format_database_stats(stats)
|
|
388
|
+
return "unknown structure" if stats.empty?
|
|
389
|
+
|
|
390
|
+
parts = []
|
|
391
|
+
parts << "#{stats['objects']} objects" if stats["objects"]
|
|
392
|
+
parts << "#{stats['packages']} packages" if stats["packages"]
|
|
393
|
+
parts << "#{stats['connectors']} connectors" if stats["connectors"]
|
|
394
|
+
|
|
395
|
+
parts.join(", ")
|
|
396
|
+
end
|
|
397
|
+
|
|
398
|
+
end
|
|
399
|
+
end
|
|
400
|
+
end
|
|
401
|
+
end
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
module Ea
|
|
5
|
+
module Transformations
|
|
6
|
+
module Parsers
|
|
7
|
+
# XMI Parser implements the BaseParser interface for XML Metadata
|
|
8
|
+
# Interchange files.
|
|
9
|
+
#
|
|
10
|
+
# Delegates to Ea::Xmi::Parser for actual parsing.
|
|
11
|
+
class XmiParser < BaseParser
|
|
12
|
+
# Get parser format name
|
|
13
|
+
#
|
|
14
|
+
# @return [String] Human-readable format name
|
|
15
|
+
def format_name
|
|
16
|
+
"XML Metadata Interchange (XMI)"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Get list of supported file extensions
|
|
20
|
+
#
|
|
21
|
+
# @return [Array<String>] List of extensions
|
|
22
|
+
def supported_extensions
|
|
23
|
+
[".xmi", ".xml"]
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def content_patterns
|
|
27
|
+
[/xmi:version/]
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def priority
|
|
31
|
+
80
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
protected
|
|
35
|
+
|
|
36
|
+
# Core parsing implementation for XMI files
|
|
37
|
+
#
|
|
38
|
+
# @param file_path [String] Path to the XMI file
|
|
39
|
+
# @return [Lutaml::Uml::Document] Parsed UML document
|
|
40
|
+
def parse_internal(file_path)
|
|
41
|
+
# Validate XMI file format
|
|
42
|
+
validate_xmi_format!(file_path)
|
|
43
|
+
|
|
44
|
+
# Use Ea::Xmi::Parser for XMI parsing
|
|
45
|
+
document = Ea::Xmi::Parser.parse(File.new(file_path))
|
|
46
|
+
|
|
47
|
+
if document.nil?
|
|
48
|
+
add_error("No document found in XMI file")
|
|
49
|
+
raise Parsers::ParseError.new("Empty XMI file or parsing failed")
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Post-process document if needed
|
|
53
|
+
post_process_xmi_document(document, file_path)
|
|
54
|
+
|
|
55
|
+
document
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Hook called before parsing starts
|
|
59
|
+
#
|
|
60
|
+
# @param file_path [String] Path to the file being parsed
|
|
61
|
+
# @return [void]
|
|
62
|
+
def before_parse(file_path)
|
|
63
|
+
add_info("Starting XMI parsing for: #{file_path}")
|
|
64
|
+
|
|
65
|
+
# Check file size and warn if very large
|
|
66
|
+
file_size = File.size(file_path)
|
|
67
|
+
if file_size > 100 * 1024 * 1024 # 100MB
|
|
68
|
+
add_warning("Large XMI file detected " \
|
|
69
|
+
"(#{format_file_size(file_size)}), " \
|
|
70
|
+
"parsing may take time")
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Hook called after parsing completes
|
|
75
|
+
#
|
|
76
|
+
# @param document [Lutaml::Uml::Document] Parsed document
|
|
77
|
+
# @param file_path [String] Path to the source file
|
|
78
|
+
# @return [Lutaml::Uml::Document] Processed document
|
|
79
|
+
def after_parse(document, file_path)
|
|
80
|
+
# Add metadata about the parsing process
|
|
81
|
+
add_parsing_metadata(document, file_path)
|
|
82
|
+
|
|
83
|
+
# Validate references if requested
|
|
84
|
+
validate_references(document) if @options[:resolve_references]
|
|
85
|
+
|
|
86
|
+
# Count elements and add statistics
|
|
87
|
+
add_parsing_statistics(document)
|
|
88
|
+
|
|
89
|
+
document
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Get default parsing options for XMI
|
|
93
|
+
#
|
|
94
|
+
# @return [Hash] Default options hash
|
|
95
|
+
def default_options
|
|
96
|
+
super.merge(
|
|
97
|
+
validate_xml: true,
|
|
98
|
+
resolve_references: true,
|
|
99
|
+
preserve_namespaces: true,
|
|
100
|
+
include_documentation: true,
|
|
101
|
+
)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
private
|
|
105
|
+
|
|
106
|
+
# Validate XMI file format
|
|
107
|
+
#
|
|
108
|
+
# @param file_path [String] Path to validate
|
|
109
|
+
# @raise [ParseError] if file is not valid XMI
|
|
110
|
+
def validate_xmi_format!(file_path) # rubocop:disable Metrics/MethodLength
|
|
111
|
+
# Quick validation by reading first few lines
|
|
112
|
+
File.open(file_path, "r") do |file|
|
|
113
|
+
header = file.read(1024)
|
|
114
|
+
|
|
115
|
+
unless header.include?("<?xml")
|
|
116
|
+
add_error("File does not appear to be an XML file")
|
|
117
|
+
raise Parsers::ParseError.new("Invalid XML format")
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Check for XMI-specific elements
|
|
121
|
+
unless header.include?("xmi:") || header.include?("uml:")
|
|
122
|
+
add_warning("File may not be a valid XMI file " \
|
|
123
|
+
"(no XMI namespace found)")
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Post-process XMI document
|
|
129
|
+
#
|
|
130
|
+
# @param document [Lutaml::Uml::Document] Document to process
|
|
131
|
+
# @param file_path [String] Source file path
|
|
132
|
+
# @return [void]
|
|
133
|
+
def post_process_xmi_document(document, file_path)
|
|
134
|
+
assign_if_supported(document, :source_file=, file_path)
|
|
135
|
+
assign_if_supported(document, :parsed_at=, Time.now)
|
|
136
|
+
normalize_package_paths(document) if @options[:normalize_paths]
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Add parsing metadata to document
|
|
140
|
+
#
|
|
141
|
+
# @param document [Lutaml::Uml::Document] Document to enhance
|
|
142
|
+
# @param file_path [String] Source file path
|
|
143
|
+
# @return [void]
|
|
144
|
+
def add_parsing_metadata(document, file_path) # rubocop:disable Metrics/MethodLength
|
|
145
|
+
metadata = {
|
|
146
|
+
source_file: file_path,
|
|
147
|
+
parsed_at: Time.now,
|
|
148
|
+
parser: self.class.name,
|
|
149
|
+
parser_version: "1.0",
|
|
150
|
+
options: @options,
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
assign_if_supported(document, :parsing_metadata=, metadata)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# Validate document references
|
|
157
|
+
#
|
|
158
|
+
# @param document [Lutaml::Uml::Document] Document to validate
|
|
159
|
+
# @return [void]
|
|
160
|
+
def validate_references(document)
|
|
161
|
+
# Check for unresolved references
|
|
162
|
+
unresolved_refs = find_unresolved_references(document)
|
|
163
|
+
|
|
164
|
+
if unresolved_refs.any?
|
|
165
|
+
add_warning("Found #{unresolved_refs.size} unresolved references")
|
|
166
|
+
unresolved_refs.each do |ref|
|
|
167
|
+
add_warning("Unresolved reference: #{ref}")
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# Find unresolved references in document
|
|
173
|
+
#
|
|
174
|
+
# @param document [Lutaml::Uml::Document] Document to check
|
|
175
|
+
# @return [Array<String>] List of unresolved reference IDs
|
|
176
|
+
def find_unresolved_references(document) # rubocop:disable Metrics/CyclomaticComplexity,Metrics/MethodLength
|
|
177
|
+
unresolved = []
|
|
178
|
+
|
|
179
|
+
# This is a simplified implementation
|
|
180
|
+
# In practice, you would traverse the document structure
|
|
181
|
+
# and check for dangling references
|
|
182
|
+
|
|
183
|
+
# Check class generalizations
|
|
184
|
+
document.classes&.each do |klass|
|
|
185
|
+
klass.generalizations&.each do |gen|
|
|
186
|
+
# Reference by ID that might be unresolved
|
|
187
|
+
if gen.general.is_a?(String) && !find_element_by_id(document,
|
|
188
|
+
gen.general)
|
|
189
|
+
unresolved << gen.general
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
unresolved.uniq
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# Find element by ID in document
|
|
198
|
+
#
|
|
199
|
+
# @param document [Lutaml::Uml::Document] Document to search
|
|
200
|
+
# @param id [String] ID to find
|
|
201
|
+
# @return [Object, nil] Found element or nil
|
|
202
|
+
def find_element_by_id(document, id)
|
|
203
|
+
# Simplified implementation - in practice would use proper indexing
|
|
204
|
+
all_elements = []
|
|
205
|
+
all_elements.concat(document.packages || [])
|
|
206
|
+
all_elements.concat(document.classes || [])
|
|
207
|
+
all_elements.concat(document.data_types || [])
|
|
208
|
+
all_elements.concat(document.enums || [])
|
|
209
|
+
|
|
210
|
+
all_elements.find { |element| element.xmi_id == id }
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
# Add parsing statistics
|
|
214
|
+
#
|
|
215
|
+
# @param document [Lutaml::Uml::Document] Document to analyze
|
|
216
|
+
# @return [void]
|
|
217
|
+
def add_parsing_statistics(document) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
|
|
218
|
+
stats = {
|
|
219
|
+
packages: document.packages&.size || 0,
|
|
220
|
+
classes: document.classes&.size || 0,
|
|
221
|
+
data_types: document.data_types&.size || 0,
|
|
222
|
+
enumerations: document.enums&.size || 0,
|
|
223
|
+
associations: document.associations&.size || 0,
|
|
224
|
+
diagrams: document.diagrams&.size || 0,
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
add_info("Parsed XMI successfully: #{format_statistics(stats)}")
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
# Normalize package paths
|
|
231
|
+
#
|
|
232
|
+
# @param document [Lutaml::Uml::Document] Document to process
|
|
233
|
+
# @return [void]
|
|
234
|
+
def normalize_package_paths(_document)
|
|
235
|
+
# Implementation would normalize package path formats
|
|
236
|
+
# This is a placeholder for future enhancement
|
|
237
|
+
add_info("Package path normalization completed")
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
end
|
|
243
|
+
end
|