UNPKG

canonical-uml-xmi-parser

Version:

Canonical XMI parser for uml-model package

1,036 lines (958 loc) 36.5 kB
/** */ let CanonicalUmlXmiParser = function (opts = {}) { const returnParm1 = x => x const NormalizePrimitiveType = opts.normalizePrimitiveType || returnParm1 const NormalizeAssociation = opts.normalizeAssociation || returnParm1 const NormalizeProperty = opts.normalizeProperty || returnParm1 let ViewPattern = opts.viewPattern || null let NameMap = opts.nameMap || { } const UmlModel = opts.umlModel || require('uml-model')() var UPPER_UNLIMITED = '*' function parseName (elt) { let ret = 'name' in elt.$ ? elt.$.name : 'name' in elt ? elt.name[0] : null return !ret ? ret : ret in NameMap ? NameMap[ret] : expandPrefix(ret) } function parseValue (property, attrName, deflt, id) { // 'default' is a reserved word if (!(attrName in property)) { if (opts.expectCardinalities) { return opts.expectCardinalities(id, attrName, deflt) } else { return deflt } } const elt = property[attrName][0] return 'value' in elt.$ ? elt.$.value : 'value' in elt ? elt.value[0] : deflt } function parseGeneral (elt) { return 'general' in elt.$ ? elt.$.general : 'general' in elt ? elt.general[0].$['xmi:idref'] : null } function parseAssociation (elt) { return 'association' in elt.$ ? elt.$.association : 'association' in elt ? elt.association[0].$['xmi:idref'] : null } function parseComments (elt) { return 'ownedComment' in elt ? elt.ownedComment.map( commentElt => commentElt.body[0] ) : [] } function parseIsAbstract (elt) { return 'isAbstract' in elt.$ ? elt.$.isAbstract === 'true' : 'isAbstract' in elt ? elt.isAbstract[0] === 'true' : false } function parseProperties (model, elts, classId) { let ret = { properties: [], associations: {}, comments: [] } elts.forEach(elt => { console.assert(elt.$['xmi:type'] === 'uml:Property') let id = elt.$['xmi:id'] let name = parseName(elt) let association = parseAssociation(elt) const min = parseValue(elt, 'lowerValue', 0, id) const max = parseValue(elt, 'upperValue', UPPER_UNLIMITED, id) if (association) association = NormalizeAssociation(association, id, classId) else name = NormalizeProperty(name, id, classId) let newPropertyRec = new PropertyRecord( model, classId, id, name, elt.type[0].$['xmi:idref'], NormalizePrimitiveType(elt.type[0].$['href'], id, classId), min, max, parseComments(elt)) ret.properties.push(newPropertyRec) if (association) { /* <ownedAttribute xmi:type="uml:Property" name="AgentIndicator" xmi:id="AgentIndicator_member_source" association="AgentIndicator_member_association"> <type xmi:idref="Agent"/> <lowerValue xmi:type="uml:LiteralInteger" xmi:id="AgentIndicator_member_lower"/> <upperValue xmi:type="uml:LiteralUnlimitedNatural" xmi:id="AgentIndicator_member_upper" value="-1"/> </ownedAttribute> */ ret.associations[id] = Object.assign(new AssocRefRecord(id, name), { propertyRecord: newPropertyRec, classId: classId, type: elt.type[0].$['xmi:idref'], lower: min, upper: max, comments: parseComments(elt) }) if ('aggregation' in elt) { newPropertyRec.aggregation = (elt.aggregation[0] === "shared" ? UmlModel.Aggregation.shared : elt.aggregation[0] === "composite" ? UmlModel.Aggregation.composite : elt.aggregation[0]) // unknown aggregation state. } } else if (!name) { // e.g. canonical *-owned-attribute-n properties. // throw Error('expected name in ' + JSON.stringify(elt.$) + ' in ' + parent) } }) return ret } function parseEAViews (diagrams) { return diagrams.filter( diagram => '$' in diagram // eliminate the empty <diagram> element containing datatypes ).map( diagram => { return Object.assign(new ViewRecord(), { id: diagram['$']['xmi:id'], name: diagram.model[0].$.package, members: diagram.elements[0].element.map( member => member.$.subject ) }) } ) } function parseCanonicalViews (elt) { return elt.packagedElement.map(view => { return Object.assign(new ViewRecord(), { id: view.$['xmi:id'], name: parseName(view), members: view.elementImport.map( imp => imp.importedElement[0].$['xmi:idref'] ), comments: (view.ownedComment || []).map( cmnt => cmnt.body[0] ) }) }) } function parseModel (document, source) { // makeHierarchy.test() // convenience variables let packages = {} let classes = {} let datatypes = {} let properties = {} let enums = {} let primitiveTypes = {} let imports = {} let classHierarchy = makeHierarchy() let datatypeHierarchy = makeHierarchy() let packageHierarchy = makeHierarchy() let associations = {} let assocSrcToClass = {} let modelRoot = document['xmi:XMI']['uml:Model'][0] let byId = { } // return structure let model = Object.assign(new ModelRecord(), { id: modelRoot.$['xmi:id'], name: parseName(modelRoot), source: source, packages: packages, classes: classes, datatypes: datatypes, properties: properties, enums: enums, primitiveTypes: primitiveTypes, imports: imports, classHierarchy: classHierarchy, datatypeHierarchy: datatypeHierarchy, packageHierarchy: packageHierarchy, associations: associations }) // Build the model modelRoot.packagedElement.forEach(sub => { visitPackage(sub, []) }) // Turn associations into properties. // !! this overrides the property name // !! should be a utility function called electively by client and should be sensitive to pre-existing ownedAttributes referencing an association. Object.keys(associations).forEach( assocId => { let a = associations[assocId] let c = classes[assocSrcToClass[a.from]] let aref = c.associations[a.from] let name = aref.name || opts.useAssocationNameForReference ? a.name : null let prec = new PropertyRecord(model, aref.classId, aref.id, name, aref.type, undefined, aref.lower, aref.upper, aref.comments.concat(a.comments)); if ('aggregation' in aref) { prec.aggregation = aref.aggregation; } // update aref.propertyRecord aref.propertyRecord.name = name aref.propertyRecord.comments = prec.comments } ) /* Object.keys(classes).forEach( classId => { let classRecord = classes[classId] classRecord.properties.forEach( field => { if (!(field.name in properties)) { properties[field.name] = {sources: []} } properties[field.name].sources.push(field) }) }) */ // Change relations to primitiveTypes to be attributes. // Change relations to the classes and enums to reference the name. /* Object.keys(properties).forEach( p => properties[p].sources.forEach( s => { if (s.idref in primitiveTypes) { // console.log('changing property ' + p + ' to have attribute type ' + primitiveTypes[s.idref].name) // s.href = primitiveTypes[s.idref].name s.href = s.idref s.idref = undefined } else if (s.idref in classes) { // s.idref = classes[s.idref].name } else if (s.idref in enums) { // s.idref = enums[s.idref].name } })) */ /* idref => idref in primitiveTypes ? NodeConstraint : ShapeRef href => NodeConstraint type => NodeConstraint */ updateReferees(model) return model function eltToString (elt) { return '<' + Object.keys(elt.$).reduce((acc, attr) => acc.concat(attr + '="' + elt.$[attr] + '"'), ['packagedElement']).join(' ') + '/>' } function visitPackage (elt, parents) { let parent = parents[0] let type = elt.$['xmi:type'] if (!('xmi:id' in elt.$)) { throw Error('no id in ' + eltToString(elt)) } let id = elt.$['xmi:id'] if (id in byId) { throw Error(`duplicate defintion of xml:id in ${eltToString(elt)}; previous: ${eltToString(byId[id])}`) } byId[id] = elt let name = parseName(elt) // Could keep id to elt map around with this: // index[id] = { element: elt, packages: parents } switch (type) { case 'uml:Class': case 'uml:DataType': const typeLabel = type === 'uml:Class' ? 'class' : 'datatype' const myIdMap = type === 'uml:Class' ? classes : datatypes const myHierarchy = type === 'uml:Class' ? classHierarchy : datatypeHierarchy const constructor = type === 'uml:Class' ? ClassRecord : DatatypeRecord let ownedAttrs = parseProperties( model, elt.ownedAttribute || [], // SentinelConceptualDomain has no props id) myIdMap[id] = Object.assign( new constructor(id, name), ownedAttrs, { packages: parents, superClasses: [], isAbstract: parseIsAbstract(elt), referees: [], comments: parseComments(elt) } ) packages[parent].elements.push({type: typeLabel, id: id}) Object.keys(ownedAttrs.associations).forEach( assocSourceId => { assocSrcToClass[assocSourceId] = id } ) // record class hierarchy (allows multiple inheritance) if ('generalization' in elt) { elt.generalization.forEach( parentElt => { let superClassId = parseGeneral(parentElt) myHierarchy.add(superClassId, id) myIdMap[id].superClasses.push(superClassId) }) } break case 'uml:Enumeration': enums[id] = Object.assign(new EnumRecord(), { id: id, name: name, values: elt.ownedLiteral.map( l => parseName(l) ), packages: parents, referees: [] }) packages[parent].elements.push({type: 'enumeration', id: id}) // record class hierarchy if ('generalization' in elt) { throw Error("need to handle inherited enumeration " + parseGeneral(elt.generalization[0]) + " " + name) } break case 'uml:PrimitiveType': primitiveTypes[id] = Object.assign(new PrimitiveTypeRecord(), { name: name, id: id, packages: parents, referees: [] }) packages[parent].elements.push({type: 'primitiveType', id: id}) // record class hierarchy if ('generalization' in elt) { throw Error("need to handle inherited primitiveType " + parseGeneral(elt.generalization[0]) + " " + name) } break case 'uml:Model': case 'uml:Package': let recurse = true if (ViewPattern && id.match(ViewPattern)) { model.views = parseCanonicalViews(elt) recurse = false // elide canonical views package in package hierarcy } else { packages[id] = Object.assign(new PackageRecord(), { name: name, id: id, packages: parents, elements: [], comments: (elt.ownedComment || []).map( cmnt => cmnt.body[0] ) }) if (parents.length) { if (id.match(/Pattern999/)) { // !! DDI-specific recurse = false // don't record Pattern packages. } else { packageHierarchy.add(parent, id) packages[parent].elements.push({type: 'package', id: id}) } } if (recurse) { if ('elementImport' in elt) { elt.elementImport.forEach(sub => { // visitPackage(sub, [id].concat(parents)) let importId = sub.$['xmi:id'] let ref = sub.importedElement[0].$['xmi:idref'] imports[importId] = new ImportedElementRecord(importId, ref) packages[id].elements.push({type: 'import', id: importId}) }) } if ('packagedElement' in elt) { // walk desendents elt.packagedElement.forEach(sub => { visitPackage(sub, [id].concat(parents)) }) } } } break // Pass through to get to nested goodies. case 'uml:Association': let from = elt.memberEnd.map(end => end.$['xmi:idref']).filter(id => id !== elt.ownedEnd[0].$['xmi:id'])[0] associations[id] = Object.assign(new AssociationRecord(id, name), { from: from, comments: parseComments(elt) // type: elt.ownedEnd[0].type[0].$['xmi:idref'] }) /* <packagedElement xmi:id="AgentIndicator-member-association" xmi:type="uml:Association"> <name>member</name> <memberEnd xmi:idref="AgentIndicator-member-source"/> <memberEnd xmi:idref="AgentIndicator-member-target"/> <ownedEnd xmi:id="AgentIndicator-member-target" xmi:type="uml:Property"> <association xmi:idref="AgentIndicator-member-association"/> <type xmi:idref="AgentIndicator"/> <lower><value>1</value></lowerValue> <upper><value>1</value></uppervalue> </ownedEnd> </packagedElement> */ break default: console.warn('need handler for ' + type) } } } function ClassRecord (id, name) { this.id = id this.name = name } function DatatypeRecord (id, name) { this.id = id this.name = name } function PropertyRecord (model, classId, id, name, idref, href, lower, upper, comments) { if (model === undefined) { return // short-cut for objectify } if (classId === null) { console.warn('no class name for PropertyRecord ' + id) } this.classId = classId this.id = id this.name = name this.idref = idref this.href = href this.lower = lower this.upper = upper this.comments = comments if (this.upper === '-1') { this.upper = UPPER_UNLIMITED } } function RefereeRecord (classId, propName) { // if (classId === null) { // throw Error('no class id for ReferenceRecord with property name ' + propName) // } this.classId = classId this.propName = propName } function ModelRecord () { } function PackageRecord () { } function EnumRecord () { } function PrimitiveTypeRecord () { } function ViewRecord () { } /** * if attrName is null, we'll use the AssociationRecord's name. <packagedElement xmi:id="<classId>" xmi:type="uml:Class"> <ownedAttribute xmi:id="<classId>-ownedAttribute-<n>" xmi:type="uml:Property"> <type xmi:idref="<refType>"/> <lowerValue/> <upperValue/> <name>attrName</name> </ownedAttribute> </packagedElement> */ function AssocRefRecord (id, name) { // if (name === null) { // throw Error('no name for AssociationRecord ' + id) // } this.id = id this.name = name } /** <packagedElement xmi:id="<classId>" xmi:type="uml:Association"> <!-- can duplicate classId --> <memberEnd xmi:idref="<classId>-ownedAttribute-<n>"/> <memberEnd xmi:idref="<classId>-ownedEnd"/> <ownedEnd xmi:id="<classId>-ownedEnd" xmi:type="uml:Property"> <type xmi:idref="<classId>"/> <lowerValue /> <upperValue /> <association xmi:idref="<classId>"/> </ownedEnd> <name>assocName</name> </packagedElement> */ function AssociationRecord (id, name) { // if (name === null) { // throw Error('no name for AssociationRecord ' + id) // } this.id = id this.name = name } function ImportedElementRecord (id, idref) { this.id = id this.idref = idref } function updateReferees (model) { // Find set of types for each property. Object.keys(model.properties).forEach(propName => { let p = model.properties[propName] p.uniformType = findMinimalTypes(model, p) p.sources.forEach(s => { let t = s.href || s.idref let referent = t in model.classes ? model.classes[t] : t in model.datatypes ? model.datatypes[t] : t in model.enums ? model.enums[t] : t in model.primitiveTypes ? model.primitiveTypes[t] : null if (referent) { referent.referees.push(new RefereeRecord(s.classId, propName)) } else { // console.warn('referent not found: ' + referent) } }, []) }, []) } function getView (model, source, viewLabels, followReferencedClasses, followReferentHierarchy, nestInlinableStructure) { if (viewLabels.constructor !== Array) { viewLabels = [viewLabels] } let ret = Object.assign(new ModelRecord(), { source: Object.assign({}, source, { viewLabels }), packages: {}, classes: {}, datatypes: {}, properties: {}, enums: {}, primitiveTypes: {}, classHierarchy: makeHierarchy(), packageHierarchy: makeHierarchy(), views: model.views.filter( v => viewLabels.indexOf(v.name) !== -1 ) }) // ret.enums = Object.keys(model.enums).forEach( // enumId => copyEnum(ret, model, enumId) // ) // ret.primitiveTypes = Object.keys(model.primitiveTypes).forEach( // primitiveTypeId => copyPrimitiveType(ret, model, primitiveTypeId) // ) // !! extend to include datatypes let classIds = ret.views.reduce( (classIds, view) => classIds.concat(view.members.reduce( (x, member) => { let parents = model.classHierarchy.parents[member] || [] // has no parents return x.concat(member, parents.filter( classId => x.indexOf(classId) === -1 )) }, [])) , []) addDependentClasses(classIds, true) updateReferees(ret) return ret // let properties = Object.keys(model.properties).filter( // propName => model.properties[propName].sources.find(includedSource) // ).reduce( // (acc, propName) => { // let sources = model.properties[propName].sources.filter(includedSource) // return addKey(acc, propName, { // sources: sources, // uniformType: findMinimalTypes(ret, {sources: sources}) // }) // }, []) function copyEnum (to, from, enumId) { let old = from.enums[enumId] if (old.id in to.enums) { return } let e = { id: old.id, name: old.name, values: old.values.slice(), packages: old.packages.slice(), referees: [] } addPackages(to, model, e.packages) ret.packages[old.packages[0]].elements.push({ type: 'enumeration', id: old.id }) to.enums[enumId] = e } function copyPrimitiveType (to, from, primitiveTypeId) { let old = from.primitiveTypes[primitiveTypeId] if (old.id in to.primitiveTypes) { return } let e = { id: old.id, name: old.name, packages: old.packages.slice(), referees: [] } addPackages(to, model, e.packages) ret.packages[old.packages[0]].elements.push({ type: 'primitiveType', id: old.id }) to.primitiveTypes[primitiveTypeId] = e } function addDependentClasses (classIds, followParents) { classIds.forEach( classId => { if (classId in ret.classes) { // a recursive walk of the superClasses return // may result in redundant insertions. } let old = model.classes[classId] let dependentClassIds = [] let c = { id: old.id, name: old.name, properties: [], comments: old.comments.slice(), packages: old.packages.slice(), superClasses: old.superClasses.slice(), isAbstract: old.isAbstract, referees: [], comments: [] } // was deepCopy(old) ret.classes[classId] = c old.properties.forEach( p => { let id = p.idref || p.href if (id in model.enums) { copyEnum(ret, model, id) } if (id in model.primitiveTypes) { copyPrimitiveType(ret, model, id) } if (followReferencedClasses && id in model.classes) { dependentClassIds.push(id) } c.properties.push(new PropertyRecord(ret, c.id, p.id, p.name, p.idref, p.href, p.lower, p.upper)) } ) addPackages(ret, model, c.packages) ret.packages[old.packages[0]].elements.push({ type: 'class', id: old.id }) c.superClasses.forEach( suClass => ret.classHierarchy.add(suClass, c.id) ) let x = dependentClassIds if (followParents) x = x.concat(c.superClasses) addDependentClasses(x, followReferentHierarchy) } ) } function addPackages (to, from, packageIds) { for (let i = 0; i < packageIds.length; ++i) { let pid = packageIds[i] let old = from.packages[pid] let p = pid in to.packages ? to.packages[pid] : { name: old.name, id: pid, elements: [], packages: old.packages.slice() } if (!(pid in to.packages)) { to.packages[pid] = p } if (i > 0) { // add [0],[1] [1],[2] [2],[3]... to.packageHierarchy.add(pid, packageIds[i - 1]) } } } function includedSource (source) { // properties with a source in classIds return classIds.indexOf(source.classId) !== -1 } } function makeHierarchy () { let roots = {} let parents = {} let children = {} let holders = {} return { add: function (parent, child) { if (parent in children && children[parent].indexOf(child) !== -1) { // already seen return } let target = parent in holders ? getNode(parent) : (roots[parent] = getNode(parent)) // add new parents to roots. let value = getNode(child) target[child] = value if (child in roots) { delete roots[child] } // // maintain hierarchy (direct and confusing) // children[parent] = children[parent].concat(child, children[child]) // children[child].forEach(c => parents[c] = parents[c].concat(parent, parents[parent])) // parents[child] = parents[child].concat(parent, parents[parent]) // parents[parent].forEach(p => children[p] = children[p].concat(child, children[child])) // maintain hierarchy (generic and confusing) updateClosure(children, parents, child, parent) updateClosure(parents, children, parent, child) function updateClosure (container, members, near, far) { container[far] = container[far].concat(near, container[near]) container[near].forEach( n => (members[n] = members[n].concat(far, members[far])) ) } function getNode (node) { if (!(node in holders)) { parents[node] = [] children[node] = [] holders[node] = {} } return holders[node] } }, roots: roots, parents: parents, children: children } } makeHierarchy.test = function () { let t = makeHierarchy() t.add('B', 'C') t.add('C', 'D') t.add('F', 'G') t.add('E', 'F') t.add('D', 'E') t.add('A', 'B') t.add('G', 'H') console.dir(t) } function walkHierarchy (n, f, p) { return Object.keys(n).reduce((ret, k) => { return ret.concat( walkHierarchy(n[k], f, k), p ? f(k, p) : []) // outer invocation can have null parent }, []) } function expandPrefix (pname) { let i = pname.indexOf(':') if (i === -1) { return pname // e.g. LanguageSpecification } let prefix = pname.substr(0, i) let rest = pname.substr(i + 1) let ret = KnownPrefixes.map( pair => pair.prefix === prefix ? pair.url + rest : null ).find(v => v) return ret || pname } /** find the unique object types for a property */ function findMinimalTypes (model, p) { return p.sources.reduce((acc, s) => { let t = s.href || s.idref if (acc.length > 0 && acc.indexOf(t) === -1) { // debugger; // a.find(i => b.indexOf(i) !== -1) } return acc.indexOf(t) === -1 ? acc.concat(t) : acc }, []) } function add (obj, key, value) { let toAdd = { } toAdd[key] = value return Object.assign(obj, toAdd) } /** convert parsed structure to have correct prototypes */ function objectify (modelStruct) { return Object.assign(new ModelRecord(), { source: Object.assign({}, modelStruct.source), packages: Object.keys(modelStruct.packages).reduce( (acc, packageId) => add(acc, packageId, Object.assign(new PackageRecord(), modelStruct.packages[packageId])), {} ), classes: Object.keys(modelStruct.classes).reduce( (acc, classId) => add(acc, classId, Object.assign(new ClassRecord(), modelStruct.classes[classId], { properties: modelStruct.classes[classId].properties.map( prop => Object.assign(new PropertyRecord(), prop) ) }, referees(modelStruct.classes[classId]))), {} ), datatypes: Object.keys(modelStruct.datatypes).reduce( (acc, datatypeId) => add(acc, datatypeId, Object.assign(new DatatypeRecord(), modelStruct.datatypes[datatypeId], { properties: modelStruct.datatypes[datatypeId].properties.map( prop => Object.assign(new PropertyRecord(), prop) ) }, referees(modelStruct.datatypes[datatypeId]))), {} ), properties: Object.keys(modelStruct.properties).reduce( (acc, propertyName) => add(acc, propertyName, Object.assign({}, modelStruct.properties[propertyName], { sources: modelStruct.properties[propertyName].sources.map( propertyRecord => Object.assign(new PropertyRecord(), propertyRecord) ) })), {} ), enums: simpleCopy(modelStruct.enums, EnumRecord), primitiveTypes: simpleCopy(modelStruct.primitiveTypes, PrimitiveTypeRecord), classHierarchy: Object.assign({}, modelStruct.classHierarchy), datatypeHierarchy: Object.assign({}, modelStruct.datatypeHierarchy), packageHierarchy: Object.assign({}, modelStruct.packageHierarchy), associations: Object.keys(modelStruct.associations).reduce( (acc, associationId) => add(acc, associationId, Object.assign(new AssociationRecord(), modelStruct.associations[associationId])), {} ), views: modelStruct.views.map( view => Object.assign(new ViewRecord(), view) ) }) function simpleCopy (obj, f) { return Object.keys(obj).reduce( (acc, key) => add(acc, key, Object.assign(new f(), obj[key], referees(obj[key]))), {} ) } function referees (obj) { return { referees: obj.referees.map( prop => Object.assign(new RefereeRecord(), prop) ) } } } return { parseJSON: function (jsonText, source, cb) { try { let model = objectify(JSON.parse(jsonText)) model.source = source model.getView = getView cb(null, model) } catch (err) { cb(err) } }, parseXMI: function (xmiText, source, cb) { require('xml2js').Parser().parseString(xmiText, function (err, document) { if (err) { cb(err) } else { let model = parseModel(document, source) model.getView = getView cb(null, model) } }) }, duplicateGraph: function (xmiGraph) { return objectify(JSON.parse(JSON.stringify(xmiGraph))) }, toUML: function (xmiGraph) { let packages = {} let enums = {} let classes = {} let datatypes = {} let primitiveTypes = {} let associations = {} let imports = {} let missingElements = {} let ret = new UmlModel.Model( xmiGraph.id, xmiGraph.name, xmiGraph.source, null, missingElements ) ret.elements = Object.keys(xmiGraph.packageHierarchy.roots).map( packageId => createPackage(packageId, ret) ) return ret function mapElementByXmiReference (xmiRef, reference) { switch (xmiRef.type) { case 'import': return followImport(xmiRef.id, reference) case 'package': return createPackage(xmiRef.id, reference) case 'enumeration': return createEnumeration(xmiRef.id, reference) case 'primitiveType': return createPrimitiveType(xmiRef.id, reference) case 'class': return createClass(xmiRef.id, reference) case 'datatype': return createDatatype(xmiRef.id, reference) default: throw Error('mapElementByXmiReference: unknown reference type in ' + JSON.stringify(xmiRef)) } } function followImport (importId, reference) { if (importId in imports) { throw Error('import id "' + importId + '" already used for ' + JSON.stringify(imports[importId])) // imports[importId].references.push(reference) // return imports[importId] } const importRecord = xmiGraph.imports[importId] // let ref = createdReferencedValueType(importRecord.idref) // let ret = imports[importId] = new UmlModel.Import(importId, ref) let ret = imports[importId] = new UmlModel.Import(importId, null, reference) ret.target = createdReferencedValueType(importRecord.idref, ret) return ret // imports[importId] = createdReferencedValueType(importRecord.idref) // imports[importId].importId = importId // write down that it's an import for round-tripping // return imports[importId] } function createdReferencedValueType (target, reference) { if (target in xmiGraph.packages) { return createPackage(target, reference) } if (target in xmiGraph.enums) { return createEnumeration(target, reference) } if (target in xmiGraph.primitiveTypes) { return createPrimitiveType(target, reference) } if (target in xmiGraph.classes) { return createClass(target, reference) } if (target in xmiGraph.datatypes) { return createDatatype(target, reference) } return missingElements[target] = createMissingElement(target, reference) } function mapElementByIdref (propertyRecord, reference) { if (propertyRecord.href) { if (propertyRecord.href in primitiveTypes) { primitiveTypes[propertyRecord.href].references.push(reference) return primitiveTypes[propertyRecord.href] } return primitiveTypes[propertyRecord.href] = new UmlModel.PrimitiveType(propertyRecord.href, [reference], propertyRecord.href, true, null, []) } return createdReferencedValueType(propertyRecord.idref, reference) } function createPackage (packageId, reference) { if (packageId in packages) { throw Error('package id "' + packageId + '" already used for ' + JSON.stringify(packages[packageId])) } const packageRecord = xmiGraph.packages[packageId] let ret = packages[packageId] = new UmlModel.Package(packageId, reference, packageRecord.name, null, reference, packageRecord.comments) ret.elements = packageRecord.elements.map( xmiReference => mapElementByXmiReference(xmiReference, ret) ) return ret } function createEnumeration (enumerationId, reference) { if (enumerationId in enums) { enums[enumerationId].references.push(reference) return enums[enumerationId] } const enumerationRecord = xmiGraph.enums[enumerationId] return enums[enumerationId] = new UmlModel.Enumeration(enumerationId, [reference], enumerationRecord.name, enumerationRecord.values, reference, enumerationRecord.comments) } function createPrimitiveType (primitiveTypeId, reference) { if (primitiveTypeId in primitiveTypes) { primitiveTypes[primitiveTypeId].references.push(reference) return primitiveTypes[primitiveTypeId] } const primitiveTypeRecord = xmiGraph.primitiveTypes[primitiveTypeId] return primitiveTypes[primitiveTypeId] = new UmlModel.PrimitiveType(primitiveTypeId, [reference], primitiveTypeRecord.name, false, reference, primitiveTypeRecord.comments) } function createClassifier (classId, reference, constructor, myIdMap, xmiGraphIdMap) { if (classId in myIdMap) { myIdMap[classId].references.push(reference) return myIdMap[classId] } const classRecord = xmiGraphIdMap[classId] let ret = myIdMap[classId] = new constructor(classId, [reference], classRecord.name, null, [], classRecord.isAbstract, reference, classRecord.comments) // avoid cycles like Identifiable { basedOn Identifiable } if (classRecord.superClasses) { ret.generalizations = classRecord.superClasses.map( superClass => createdReferencedValueType(superClass, ret) ) } ret.properties = classRecord.properties.map( propertyRecord => createProperty(propertyRecord, ret)) return ret } function createClass (classId, reference, constructor) { return createClassifier(classId, reference, UmlModel.Class, classes, xmiGraph.classes); } function createDatatype (classId, reference, constructor) { return createClassifier(classId, reference, UmlModel.Class, datatypes, xmiGraph.datatypes); } function createMissingElement (missingElementId, reference) { if (missingElementId in missingElements) { missingElements[missingElementId].references.push(reference) return missingElements[missingElementId] } return missingElements[missingElementId] = new UmlModel.MissingElement(missingElementId, [reference]) } function createProperty (propertyRecord, inClassifier) { let ret = new UmlModel.Property(propertyRecord.id, inClassifier, propertyRecord.name, null, // so we can pass the Property to unresolved types propertyRecord.lower, propertyRecord.upper, propertyRecord.association, propertyRecord.aggregation, propertyRecord.comments) ret.type = mapElementByIdref(propertyRecord, ret) return ret } }, ModelRecord: ModelRecord, PropertyRecord: PropertyRecord, ClassRecord: ClassRecord, DatatypeRecord: DatatypeRecord, PackageRecord: PackageRecord, EnumRecord: EnumRecord, PrimitiveTypeRecord: PrimitiveTypeRecord, ViewRecord: ViewRecord, AssociationRecord: AssociationRecord, AssocRefRecord: AssocRefRecord, RefereeRecord: RefereeRecord, ImportedElementRecord: ImportedElementRecord, } } module.exports = CanonicalUmlXmiParser