@tripsnek/tmf
Version:
TypeScript Modeling Framework - A TypeScript port of the Eclipse Modeling Framework (EMF)
358 lines • 13.3 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.EcoreStringWriter = void 0;
const eenum_impl_1 = require("../metamodel/eenum-impl");
const eclass_impl_1 = require("../metamodel/eclass-impl");
const edata_type_impl_1 = require("../metamodel/edata-type-impl");
const eattribute_impl_1 = require("../metamodel/eattribute-impl");
const tutils_1 = require("../tutils");
/**
* Writes EPackage metamodels to Ecore XML string format.
* This class handles the core writing logic without file system dependencies.
*/
class EcoreStringWriter {
indentLevel = 0;
indentString = ' ';
/**
* Converts an EPackage to Ecore XML string format.
* @param ePackage The root package to serialize
* @returns The XML string representation
*/
writeToString(ePackage) {
const xml = this.buildPackageXml(ePackage, true);
return `<?xml version="1.0" encoding="UTF-8"?>\n${xml}`;
}
/**
* Builds the XML for a package.
*/
buildPackageXml(ePackage, isRoot = false) {
const indent = this.getIndent();
let xml = '';
if (isRoot) {
// Root package with full namespace declarations
xml = `<ecore:EPackage xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:ecore="http://www.eclipse.org/emf/2002/Ecore"`;
}
else {
xml = `${indent}<eSubpackages`;
}
// Add package attributes
xml += ` name="${this.escapeXml(ePackage.getName())}"`;
if (ePackage.getNsURI()) {
xml += ` nsURI="${this.escapeXml(ePackage.getNsURI())}"`;
}
if (ePackage.getNsPrefix()) {
xml += ` nsPrefix="${this.escapeXml(ePackage.getNsPrefix())}"`;
}
xml += '>';
this.indentLevel++;
// Add classifiers
for (const classifier of ePackage.getEClassifiers()) {
xml += '\n' + this.buildClassifierXml(classifier, ePackage);
}
// Add subpackages
for (const subPackage of ePackage.getESubPackages()) {
xml += '\n' + this.buildPackageXml(subPackage, false);
}
this.indentLevel--;
if (isRoot) {
xml += '\n</ecore:EPackage>';
}
else {
xml += `\n${indent}</eSubpackages>`;
}
return xml;
}
/**
* Builds the XML for a classifier (EClass, EEnum, or EDataType).
*/
buildClassifierXml(classifier, containingPackage) {
const indent = this.getIndent();
if (classifier instanceof eenum_impl_1.EEnumImpl) {
return this.buildEnumXml(classifier);
}
else if (classifier instanceof eclass_impl_1.EClassImpl) {
return this.buildClassXml(classifier, containingPackage);
}
else if (classifier instanceof edata_type_impl_1.EDataTypeImpl) {
return this.buildDataTypeXml(classifier);
}
return '';
}
/**
* Builds the XML for an EClass.
*/
buildClassXml(eClass, containingPackage) {
const indent = this.getIndent();
let xml = `${indent}<eClassifiers xsi:type="ecore:EClass" name="${this.escapeXml(eClass.getName())}"`;
// Add super types if any
const superTypes = eClass.getESuperTypes();
if (superTypes && superTypes.size() > 0) {
const superTypeRefs = [];
for (let i = 0; i < superTypes.size(); i++) {
const superType = superTypes.get(i);
superTypeRefs.push(this.getClassifierReference(superType, containingPackage));
}
xml += ` eSuperTypes="${superTypeRefs.join(' ')}"`;
}
// Add abstract/interface flags if set
if (eClass.isAbstract()) {
xml += ' abstract="true"';
}
if (eClass.isInterface()) {
xml += ' interface="true"';
}
// Check if class has content
const hasFeatures = eClass.getEStructuralFeatures() && eClass.getEStructuralFeatures().size() > 0;
const hasOperations = eClass.getEOperations() && eClass.getEOperations().size() > 0;
if (!hasFeatures && !hasOperations) {
xml += '/>';
return xml;
}
xml += '>';
this.indentLevel++;
// Add operations
if (hasOperations) {
for (let i = 0; i < eClass.getEOperations().size(); i++) {
const operation = eClass.getEOperations().get(i);
xml += '\n' + this.buildOperationXml(operation, containingPackage);
}
}
// Add structural features
if (hasFeatures) {
for (let i = 0; i < eClass.getEStructuralFeatures().size(); i++) {
const feature = eClass.getEStructuralFeatures().get(i);
xml += '\n' + this.buildFeatureXml(feature, containingPackage);
}
}
this.indentLevel--;
xml += `\n${indent}</eClassifiers>`;
return xml;
}
/**
* Builds the XML for an EOperation.
*/
buildOperationXml(operation, containingPackage) {
const indent = this.getIndent();
let xml = `${indent}<eOperations name="${this.escapeXml(operation.getName())}"`;
// Add return type if present
if (operation.getEType()) {
xml += ` eType="${this.getClassifierReference(operation.getEType(), containingPackage)}"`;
}
// Add upper bound if not 1
if (operation.getUpperBound() == -1) {
xml += ` upperBound="${operation.getUpperBound()}"`;
}
// Check if operation has parameters
const hasParameters = operation.getEParameters() && operation.getEParameters().size() > 0;
if (!hasParameters) {
xml += '/>';
return xml;
}
xml += '>';
this.indentLevel++;
// Add parameters
for (let i = 0; i < operation.getEParameters().size(); i++) {
const param = operation.getEParameters().get(i);
xml += '\n' + this.buildParameterXml(param, containingPackage);
}
this.indentLevel--;
xml += `\n${indent}</eOperations>`;
return xml;
}
/**
* Builds the XML for an EParameter.
*/
buildParameterXml(parameter, containingPackage) {
const indent = this.getIndent();
let xml = `${indent}<eParameters name="${this.escapeXml(parameter.getName())}"`;
if (parameter.getUpperBound() == -1) {
xml += ` upperBound="${parameter.getUpperBound()}"`;
}
//TODO: lower bounds for optional parameters? since we can support those in typescript
if (parameter.getEType()) {
xml += ` eType="${this.getClassifierReference(parameter.getEType(), containingPackage)}"`;
}
xml += '/>';
return xml;
}
/**
* Builds the XML for a structural feature (EAttribute or EReference).
*/
buildFeatureXml(feature, containingPackage) {
const indent = this.getIndent();
const isAttribute = feature instanceof eattribute_impl_1.EAttributeImpl;
const featureType = isAttribute ? 'ecore:EAttribute' : 'ecore:EReference';
let xml = `${indent}<eStructuralFeatures xsi:type="${featureType}" name="${this.escapeXml(feature.getName())}"`;
// Add type
if (feature.getEType()) {
xml += ` eType="${this.getClassifierReference(feature.getEType(), containingPackage)}"`;
}
//TODO: lowerbound? only seems to make sense if we have 'required' features
if (feature.getUpperBound() == -1) {
xml += ` upperBound="${feature.getUpperBound()}"`;
}
// Add feature-specific attributes
if (isAttribute) {
const attr = feature;
if (attr.isId()) {
xml += ' iD="true"';
}
}
else {
const ref = feature;
if (ref.isContainment()) {
xml += ' containment="true"';
}
if (ref.getEOpposite()) {
xml += ` eOpposite="${this.getFeatureReference(ref.getEOpposite())}"`;
}
}
// Add other properties
if (feature.isTransient()) {
xml += ' transient="true"';
}
// if (feature.isDerived()) {
// xml += ' derived="true"';
// }
if (!feature.isChangeable()) {
xml += ' changeable="false"';
}
if (feature.isVolatile()) {
xml += ' volatile="true"';
}
if (feature.getDefaultValueLiteral()) {
xml += ` defaultValueLiteral="${this.escapeXml(feature.getDefaultValueLiteral())}"`;
}
xml += '/>';
return xml;
}
/**
* Builds the XML for an EEnum.
*/
buildEnumXml(eEnum) {
const indent = this.getIndent();
let xml = `${indent}<eClassifiers xsi:type="ecore:EEnum" name="${this.escapeXml(eEnum.getName())}"`;
const hasLiterals = eEnum.getELiterals() && eEnum.getELiterals().size() > 0;
if (!hasLiterals) {
xml += '/>';
return xml;
}
xml += '>';
this.indentLevel++;
// Add enum literals
for (let i = 0; i < eEnum.getELiterals().size(); i++) {
const literal = eEnum.getELiterals().get(i);
xml += '\n' + this.buildEnumLiteralXml(literal);
}
this.indentLevel--;
xml += `\n${indent}</eClassifiers>`;
return xml;
}
/**
* Builds the XML for an EEnumLiteral.
*/
buildEnumLiteralXml(literal) {
const indent = this.getIndent();
let xml = `${indent}<eLiterals name="${this.escapeXml(literal.getName())}"`;
if (literal.getValue() !== undefined && literal.getValue() !== null) {
xml += ` value="${literal.getValue()}"`;
}
if (literal.getLiteral() && literal.getLiteral() !== literal.getName()) {
xml += ` literal="${this.escapeXml(literal.getLiteral())}"`;
}
xml += '/>';
return xml;
}
/**
* Builds the XML for an EDataType.
*/
buildDataTypeXml(dataType) {
const indent = this.getIndent();
let xml = `${indent}<eClassifiers xsi:type="ecore:EDataType" name="${this.escapeXml(dataType.getName())}"`;
// if (dataType.getInstanceClassName()) {
// xml += ` instanceClassName="${this.escapeXml(dataType.getInstanceClassName())}"`;
// }
xml += '/>';
return xml;
}
/**
* Gets a reference string for a classifier.
*/
getClassifierReference(classifier, fromPackage) {
// Check if it's a primitive type
const primitiveTypes = tutils_1.TUtils.PRIMITIVES;
if (primitiveTypes.includes(classifier.getName())) {
return `ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//${classifier.getName()}`;
}
// Build reference path
return this.buildReferencePath(classifier, fromPackage);
}
/**
* Gets a reference string for a feature.
*/
getFeatureReference(feature) {
const containingClass = feature.getEContainingClass();
if (!containingClass) {
return `#//${feature.getName()}`;
}
//root packages don't require package paths
if (!containingClass.getEPackage().getESuperPackage())
return `#//${containingClass.getName()}/${feature.getName()}`;
else
return `#//${this.getPackagePath(containingClass.getEPackage())}/${containingClass.getName()}/${feature.getName()}`;
}
/**
* Builds a reference path to a classifier.
*/
buildReferencePath(classifier, fromPackage) {
const classifierPackage = classifier.getEPackage();
// If in same package, use simple reference
// if (classifierPackage === fromPackage) {
// return `#//${classifier.getName()}`;
// }
// Build full path
const path = this.getPackagePath(classifierPackage);
if (path.length > 0)
return `#//${path.join('/')}/${classifier.getName()}`;
//root package elements don't require paths
return `#//${classifier.getName()}`;
}
/**
* Gets the path to a package from root.
*/
getPackagePath(pkg) {
const path = [];
let current = pkg;
while (current) {
path.unshift(current.getName());
current = current.getESuperPackage();
}
// Remove root package name from path
if (path.length > 0) {
path.shift();
}
return path;
}
/**
* Escapes special XML characters.
*/
escapeXml(str) {
if (!str)
return '';
return str
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
/**
* Gets the current indentation string.
*/
getIndent() {
return this.indentString.repeat(this.indentLevel);
}
}
exports.EcoreStringWriter = EcoreStringWriter;
//# sourceMappingURL=ecore-string-writer.js.map