UNPKG

processmaker-builder

Version:

The gulp task runner for ProcessMaker building

2,019 lines (1,550 loc) 194 kB
'use strict'; function Base() { } Base.prototype.get = function(name) { return this.$model.properties.get(this, name); }; Base.prototype.set = function(name, value) { this.$model.properties.set(this, name, value); }; // //module.exports = Base; var parseName = function(name, defaultPrefix) { var parts = name.split(/:/), localName, prefix; // no prefix (i.e. only local name) if (parts.length === 1) { localName = name; prefix = defaultPrefix; } else // prefix + local name if (parts.length === 2) { localName = parts[1]; prefix = parts[0]; } else { throw new Error('expected <prefix:localName> or <localName>, got ' + name); } name = (prefix ? prefix + ':' : '') + localName; return { name: name, prefix: prefix, localName: localName }; }; 'use strict'; /** * A utility that gets and sets properties of model elements. * * @param {Model} model */ function Properties(model) { this.model = model; } //module.exports = Properties; /** * Sets a named property on the target element * * @param {Object} target * @param {String} name * @param {Object} value */ Properties.prototype.set = function(target, name, value) { var property = this.model.getPropertyDescriptor(target, name); if (!property) { target.$attrs[name] = value; } else { Object.defineProperty(target, property.name, { enumerable: !property.isReference, writable: true, value: value }); } }; /** * Returns the named property of the given element * * @param {Object} target * @param {String} name * * @return {Object} */ Properties.prototype.get = function(target, name) { var property = this.model.getPropertyDescriptor(target, name); if (!property) { return target.$attrs[name]; } var propertyName = property.name; // check if access to collection property and lazily initialize it if (!target[propertyName] && property.isMany) { Object.defineProperty(target, propertyName, { enumerable: !property.isReference, writable: true, value: [] }); } return target[propertyName]; }; /** * Define a property on the target element * * @param {Object} target * @param {String} name * @param {Object} options */ Properties.prototype.define = function(target, name, options) { Object.defineProperty(target, name, options); }; /** * Define the descriptor for an element */ Properties.prototype.defineDescriptor = function(target, descriptor) { this.define(target, '$descriptor', { value: descriptor }); }; /** * Define the model for an element */ Properties.prototype.defineModel = function(target, model) { this.define(target, '$model', { value: model }); }; 'use strict'; //var _ = require('lodash'); // //var Base = require('./base'); function Factory(model, properties) { this.model = model; this.properties = properties; } //module.exports = Factory; Factory.prototype.createType = function(descriptor) { var model = this.model; var props = this.properties, prototype = Object.create(Base.prototype); // initialize default values _.forEach(descriptor.properties, function(p) { if (!p.isMany && p.default !== undefined) { prototype[p.name] = p.default; } }); props.defineModel(prototype, model); props.defineDescriptor(prototype, descriptor); var name = descriptor.ns.name; /** * The new type constructor */ function ModdleElement(attrs) { props.define(this, '$type', { value: name, enumerable: true }); props.define(this, '$attrs', { value: {} }); props.define(this, '$parent', { writable: true }); _.forEach(attrs, function(val, key) { this.set(key, val); }, this); } ModdleElement.prototype = prototype; ModdleElement.hasType = prototype.$instanceOf = this.model.hasType; // static links props.defineModel(ModdleElement, model); props.defineDescriptor(ModdleElement, descriptor); return ModdleElement; }; /** * Built-in moddle types */ var BUILTINS = { String: true, Boolean: true, Integer: true, Real: true, Element: true }; /** * Converters for built in types from string representations */ var TYPE_CONVERTERS = { String: function(s) { return s; }, Boolean: function(s) { return s === 'true'; }, Integer: function(s) { return parseInt(s, 10); }, Real: function(s) { return parseFloat(s, 10); } }; /** * Convert a type to its real representation */ var coerceType = function(type, value) { var converter = TYPE_CONVERTERS[type]; if (converter) { return converter(value); } else { return value; } }; /** * Return whether the given type is built-in */ var isBuiltIn = function(type) { return !!BUILTINS[type]; }; /** * Return whether the given type is simple */ var isSimple = function(type) { return !!TYPE_CONVERTERS[type]; }; 'use strict'; //var _ = require('lodash'); // //var Types = require('./types'); // DescriptorBuilder = require('./descriptor-builder'); // //var this.parseName = parseName; function Registry(packages, properties, options) { this.options = _.extend({ generateId: 'id' }, options || {}); this.packageMap = {}; this.typeMap = {}; this.packages = []; this.properties = properties; _.forEach(packages, this.registerPackage, this); } //module.exports = Registry; Registry.prototype.getPackage = function(uriOrPrefix) { return this.packageMap[uriOrPrefix]; }; Registry.prototype.getPackages = function() { return this.packages; }; Registry.prototype.registerPackage = function(pkg) { // register types _.forEach(pkg.types, function(descriptor) { this.registerType(descriptor, pkg); }, this); this.packageMap[pkg.uri] = this.packageMap[pkg.prefix] = pkg; this.packages.push(pkg); }; /** * Register a type from a specific package with us */ Registry.prototype.registerType = function(type, pkg) { var ns = this.parseName(type.name, pkg.prefix), name = ns.name, propertiesByName = {}; // parse properties _.forEach(type.properties, function(p) { // namespace property names var propertyNs = this.parseName(p.name, ns.prefix), propertyName = propertyNs.name; // namespace property types if (!isBuiltIn( p.type)) { p.type = this.parseName(p.type, propertyNs.prefix).name; } _.extend(p, { ns: propertyNs, name: propertyName }); propertiesByName[propertyName] = p; },this); // update ns + name _.extend(type, { ns: ns, name: name, propertiesByName: propertiesByName }); // link to package this.definePackage(type, pkg); // register this.typeMap[name] = type; }; /** * Traverse the type hierarchy from bottom to top. */ Registry.prototype.mapTypes = function(nsName, iterator) { var type = this.typeMap[nsName.name]; if (!type) { throw new Error('unknown type <' + nsName.name + '>'); } _.forEach(type.superClass, function(cls) { var parentNs = this.parseName(cls, nsName.prefix); this.mapTypes(parentNs, iterator); }, this); iterator(type); }; /** * Returns the effective descriptor for a type. * * @param {String} type the namespaced name (ns:localName) of the type * * @return {Descriptor} the resulting effective descriptor */ Registry.prototype.getEffectiveDescriptor = function(name) { var options = this.options, nsName = this.parseName(name); var builder = new DescriptorBuilder(nsName); this.mapTypes(nsName, function(type) { builder.addTrait(type); }); // check we have an id assigned var id = this.options.generateId; if (id && !builder.hasProperty(id)) { builder.addIdProperty(id); } var descriptor = builder.build(); // define package link this.definePackage(descriptor, descriptor.allTypes[descriptor.allTypes.length - 1].$pkg); return descriptor; }; Registry.prototype.definePackage = function(target, pkg) { this.properties.define(target, '$pkg', { value: pkg }); }; Registry.prototype.parseName = function(name, defaultPrefix) { var parts = name.split(/:/), localName, prefix; // no prefix (i.e. only local name) if (parts.length === 1) { localName = name; prefix = defaultPrefix; } else // prefix + local name if (parts.length === 2) { localName = parts[1]; prefix = parts[0]; } else { throw new Error('expected <prefix:localName> or <localName>, got ' + name); } name = (prefix ? prefix + ':' : '') + localName; return { name: name, prefix: prefix, localName: localName }; }; 'use strict'; //var _ = require('lodash'); // //var parseNameNs = require('./ns').parseName; function DescriptorBuilder(nameNs) { this.ns = nameNs; this.name = nameNs.name; this.allTypes = []; this.properties = []; this.propertiesByName = {}; } //module.exports = DescriptorBuilder; DescriptorBuilder.prototype.build = function() { return _.pick(this, [ 'ns', 'name', 'allTypes', 'properties', 'propertiesByName', 'bodyProperty' ]); }; DescriptorBuilder.prototype.addProperty = function(p, idx) { this.addNamedProperty(p, true); var properties = this.properties; if (idx !== undefined) { properties.splice(idx, 0, p); } else { properties.push(p); } }; DescriptorBuilder.prototype.replaceProperty = function(oldProperty, newProperty) { var oldNameNs = oldProperty.ns; var props = this.properties, propertiesByName = this.propertiesByName; if (oldProperty.isBody) { if (!newProperty.isBody) { throw new Error( 'property <' + newProperty.ns.name + '> must be body property ' + 'to refine <' + oldProperty.ns.name + '>'); } // TODO: Check compatibility this.setBodyProperty(newProperty, false); } this.addNamedProperty(newProperty, true); // replace old property at index with new one var idx = props.indexOf(oldProperty); if (idx === -1) { throw new Error('property <' + oldNameNs.name + '> not found in property list'); } props[idx] = newProperty; // replace propertiesByName entry with new property propertiesByName[oldNameNs.name] = propertiesByName[oldNameNs.localName] = newProperty; }; DescriptorBuilder.prototype.redefineProperty = function(p) { var nsPrefix = p.ns.prefix; var parts = p.redefines.split('#'); var name = parseNameNs(parts[0], nsPrefix); var attrName = parseNameNs(parts[1], name.prefix).name; var redefinedProperty = this.propertiesByName[attrName]; if (!redefinedProperty) { throw new Error('refined property <' + attrName + '> not found'); } else { this.replaceProperty(redefinedProperty, p); } delete p.redefines; }; DescriptorBuilder.prototype.addNamedProperty = function(p, validate) { var ns = p.ns, propsByName = this.propertiesByName; if (validate) { this.assertNotDefined(p, ns.name); this.assertNotDefined(p, ns.localName); } propsByName[ns.name] = propsByName[ns.localName] = p; }; DescriptorBuilder.prototype.removeNamedProperty = function(p) { var ns = p.ns, propsByName = this.propertiesByName; delete propsByName[ns.name]; delete propsByName[ns.localName]; }; DescriptorBuilder.prototype.setBodyProperty = function(p, validate) { if (validate && this.bodyProperty) { throw new Error( 'body property defined multiple times ' + '(<' + this.bodyProperty.ns.name + '>, <' + p.ns.name + '>)'); } this.bodyProperty = p; }; DescriptorBuilder.prototype.addIdProperty = function(name) { var nameNs = parseNameNs(name, this.ns.prefix); var p = { name: nameNs.localName, type: 'String', isAttr: true, ns: nameNs }; // ensure that id is always the first attribute (if present) this.addProperty(p, 0); }; DescriptorBuilder.prototype.assertNotDefined = function(p, name) { var propertyName = p.name, definedProperty = this.propertiesByName[propertyName]; if (definedProperty) { throw new Error( 'property <' + propertyName + '> already defined; ' + 'override of <' + definedProperty.definedBy.ns.name + '#' + definedProperty.ns.name + '> by ' + '<' + p.definedBy.ns.name + '#' + p.ns.name + '> not allowed without redefines'); } }; DescriptorBuilder.prototype.hasProperty = function(name) { return this.propertiesByName[name]; }; DescriptorBuilder.prototype.addTrait = function(t) { var allTypes = this.allTypes; if (allTypes.indexOf(t) !== -1) { return; } _.forEach(t.properties, function(p) { // clone property to allow extensions p = _.extend({}, p, { name: p.ns.localName }); Object.defineProperty(p, 'definedBy', { value: t }); // add redefine support if (p.redefines) { this.redefineProperty(p); } else { if (p.isBody) { this.setBodyProperty(p); } this.addProperty(p); } }, this); allTypes.push(t); }; 'use strict'; //var _ = require('lodash'); // //var Types = require('./types'), // Factory = require('./factory'), // Registry = require('./registry'), // Properties = require('./properties'); // //var parseNameNs = require('./ns').parseName; //// Moddle implementation ///////////////////////////////////////////////// /** * @class Moddle * * A model that can be used to create elements of a specific type. * * @example * * var Moddle = require('moddle'); * * var pkg = { * name: 'mypackage', * prefix: 'my', * types: [ * { name: 'Root' } * ] * }; * * var moddle = new Moddle([pkg]); * * @param {Array<Package>} packages the packages to contain * @param {Object} options additional options to pass to the model */ function Moddle(packages, options) { options = options || {}; this.properties = new Properties(this); this.factory = new Factory(this, this.properties); this.registry = new Registry(packages, this.properties, options); this.typeCache = {}; } //module.exports = Moddle; /** * Create an instance of the specified type. * * @method Moddle#create * * @example * * var foo = moddle.create('my:Foo'); * var bar = moddle.create('my:Bar', { id: 'BAR_1' }); * * @param {String|Object} descriptor the type descriptor or name know to the model * @param {Object} attrs a number of attributes to initialize the model instance with * @return {Object} model instance */ Moddle.prototype.create = function(descriptor, attrs) { var Type = this.getType(descriptor); if (!Type) { throw new Error('unknown type <' + descriptor + '>'); } return new Type(attrs); }; /** * Returns the type representing a given descriptor * * @method Moddle#getType * * @example * * var Foo = moddle.getType('my:Foo'); * var foo = new Foo({ 'id' : 'FOO_1' }); * * @param {String|Object} descriptor the type descriptor or name know to the model * @return {Object} the type representing the descriptor */ Moddle.prototype.getType = function(descriptor) { var cache = this.typeCache; var name = _.isString(descriptor) ? descriptor : descriptor.ns.name; var type = cache[name]; if (!type) { descriptor = this.registry.getEffectiveDescriptor(name); type = cache[descriptor.name] = this.factory.createType(descriptor); } return type; }; /** * Creates an any-element type to be used within model instances. * * This can be used to create custom elements that lie outside the meta-model. * The created element contains all the meta-data required to serialize it * as part of meta-model elements. * * @method Moddle#createAny * * @example * * var foo = moddle.createAny('vendor:Foo', 'http://vendor', { * value: 'bar' * }); * * var container = moddle.create('my:Container', 'http://my', { * any: [ foo ] * }); * * // go ahead and serialize the stuff * * * @param {String} name the name of the element * @param {String} nsUri the namespace uri of the element * @param {Object} [properties] a map of properties to initialize the instance with * @return {Object} the any type instance */ Moddle.prototype.createAny = function(name, nsUri, properties) { var nameNs = parseNameNs(name); var element = { $type: name }; var descriptor = { name: name, isGeneric: true, ns: { prefix: nameNs.prefix, localName: nameNs.localName, uri: nsUri } }; this.properties.defineDescriptor(element, descriptor); this.properties.defineModel(element, this); this.properties.define(element, '$parent', { enumerable: false, writable: true }); _.forEach(properties, function(a, key) { if (_.isObject(a) && a.value !== undefined) { element[a.name] = a.value; } else { element[key] = a; } }); return element; }; /** * Returns a registered package by uri or prefix * * @return {Object} the package */ Moddle.prototype.getPackage = function(uriOrPrefix) { return this.registry.getPackage(uriOrPrefix); }; /** * Returns a snapshot of all known packages * * @return {Object} the package */ Moddle.prototype.getPackages = function() { return this.registry.getPackages(); }; /** * Returns the descriptor for an element */ Moddle.prototype.getElementDescriptor = function(element) { return element.$descriptor; }; /** * Returns true if the given descriptor or instance * represents the given type. * * May be applied to this, if element is omitted. */ Moddle.prototype.hasType = function(element, type) { if (type === undefined) { type = element; element = this; } var descriptor = element.$model.getElementDescriptor(element); return !!_.find(descriptor.allTypes, function(t) { return t.name === type; }); }; /** * Returns the descriptor of an elements named property */ Moddle.prototype.getPropertyDescriptor = function(element, property) { return this.getElementDescriptor(element).propertiesByName[property]; }; 'use strict'; function capitalize(string) { return string.charAt(0).toUpperCase() + string.slice(1); } function lower(string) { return string.charAt(0).toLowerCase() + string.slice(1); } function hasLowerCaseAlias(pkg) { return pkg.xml && pkg.xml.alias === 'lowerCase'; } var aliasToName = function(alias, pkg) { if (hasLowerCaseAlias(pkg)) { return capitalize(alias); } else { return alias; } }; var nameToAlias = function(name, pkg) { if (hasLowerCaseAlias(pkg)) { return lower(name); } else { return name; } }; var DEFAULT_NS_MAP = { 'xsi': 'http://www.w3.org/2001/XMLSchema-instance' }; // //module.exports.DEFAULT_NS_MAP = { // 'xsi': 'http://www.w3.org/2001/XMLSchema-instance' //}; 'use strict'; //var sax = require('sax'), // _ = require('lodash'); // //var common = require('./common'), // Types = require('moddle').types, // Stack = require('tiny-stack'), // parseNameNs = require('moddle').ns.parseName, var parseNameNs = parseName; var aliasToName = aliasToName; function parseNodeAttributes(node) { var nodeAttrs = node.attributes; return _.reduce(nodeAttrs, function(result, v, k) { var name, ns; if (!v.local) { name = v.prefix; } else { ns = parseNameNs(v.name, v.prefix); name = ns.name; } result[name] = v.value; return result; }, {}); } /** * Normalizes namespaces for a node given an optional default namespace and a * number of mappings from uris to default prefixes. * * @param {XmlNode} node * @param {Model} model the model containing all registered namespaces * @param {Uri} defaultNsUri */ function normalizeNamespaces(node, model, defaultNsUri) { var uri, childUri, prefix; uri = node.uri || defaultNsUri; if (uri) { var pkg = model.getPackage(uri); if (pkg) { prefix = pkg.prefix; } else { prefix = node.prefix; } node.prefix = prefix; node.uri = uri; } _.forEach(node.attributes, function(attr) { normalizeNamespaces(attr, model, null); }); } /** * A parse context. * * @class * * @param {ElementHandler} parseRoot the root handler for parsing a document */ function Context(parseRoot) { var elementsById = {}; var references = []; var warnings = []; this.addReference = function(reference) { references.push(reference); }; this.addElement = function(id, element) { if (!id || !element) { throw new Error('[xml-reader] id or ctx must not be null'); } elementsById[id] = element; }; this.addWarning = function (w) { warnings.push(w); }; this.warnings = warnings; this.references = references; this.elementsById = elementsById; this.parseRoot = parseRoot; } function BaseHandler() {} BaseHandler.prototype.handleEnd = function() {}; BaseHandler.prototype.handleText = function() {}; BaseHandler.prototype.handleNode = function() {}; function BodyHandler() {} BodyHandler.prototype = new BaseHandler(); BodyHandler.prototype.handleText = function(text) { this.body = (this.body || '') + text; }; function ReferenceHandler(property, context) { this.property = property; this.context = context; } ReferenceHandler.prototype = new BodyHandler(); ReferenceHandler.prototype.handleNode = function(node) { if (this.element) { throw new Error('expected no sub nodes'); } else { this.element = this.createReference(node); } return this; }; ReferenceHandler.prototype.handleEnd = function() { this.element.id = this.body; }; ReferenceHandler.prototype.createReference = function() { return { property: this.property.ns.name, id: '' }; }; function ValueHandler(propertyDesc, element) { this.element = element; this.propertyDesc = propertyDesc; } ValueHandler.prototype = new BodyHandler(); ValueHandler.prototype.handleEnd = function() { var value = this.body, element = this.element, propertyDesc = this.propertyDesc; value = coerceType(propertyDesc.type, value); if (propertyDesc.isMany) { element.get(propertyDesc.name).push(value); } else { element.set(propertyDesc.name, value); } }; function BaseElementHandler() {} BaseElementHandler.prototype = Object.create(BodyHandler.prototype); BaseElementHandler.prototype.handleNode = function(node) { var parser = this; if (!this.element) { this.element = this.createElement(node); var id = this.element.id; if (id) { this.context.addElement(id, this.element); } } else { parser = this.handleChild(node); } return parser; }; /** * @class XMLReader.ElementHandler * */ function ElementHandler(model, type, context) { this.model = model; this.type = model.getType(type); this.context = context; } ElementHandler.prototype = new BaseElementHandler(); ElementHandler.prototype.addReference = function(reference) { this.context.addReference(reference); }; ElementHandler.prototype.handleEnd = function() { var value = this.body, element = this.element, descriptor = element.$descriptor, bodyProperty = descriptor.bodyProperty; if (bodyProperty && value !== undefined) { value = coerceType(bodyProperty.type, value); element.set(bodyProperty.name, value); } }; /** * Create an instance of the model from the given node. * * @param {Element} node the xml node */ ElementHandler.prototype.createElement = function(node) { var attributes = parseNodeAttributes(node), Type = this.type, descriptor = Type.$descriptor, context = this.context, instance = new Type({}); _.forEach(attributes, function(value, name) { var prop = descriptor.propertiesByName[name]; if (prop && prop.isReference) { context.addReference({ element: instance, property: prop.ns.name, id: value }); } else { if (prop) { value = coerceType(prop.type, value); } instance.set(name, value); } }); return instance; }; ElementHandler.prototype.getPropertyForElement = function(nameNs) { if (_.isString(nameNs)) { nameNs = parseNameNs(nameNs); } var type = this.type, model = this.model, descriptor = type.$descriptor; var propertyName = nameNs.name; var property = descriptor.propertiesByName[propertyName]; // search for properties by name first if (property) { return property; } var pkg = model.getPackage(nameNs.prefix); if (pkg) { var typeName = nameNs.prefix + ':' + aliasToName(nameNs.localName, descriptor.$pkg), elementType = model.getType(typeName); // search for collection members later property = _.find(descriptor.properties, function(p) { return !p.isVirtual && !p.isReference && !p.isAttribute && elementType.hasType(p.type); }); if (property) { return _.extend({}, property, { effectiveType: elementType.$descriptor.name }); } } else { // parse unknown element (maybe extension) property = _.find(descriptor.properties, function(p) { return !p.isReference && !p.isAttribute && p.type === 'Element'; }); if (property) { return property; } } throw new Error('unrecognized element <' + nameNs.name + '>'); }; ElementHandler.prototype.toString = function() { return 'ElementDescriptor[' + this.type.$descriptor.name + ']'; }; ElementHandler.prototype.valueHandler = function(propertyDesc, element) { return new ValueHandler(propertyDesc, element); }; ElementHandler.prototype.referenceHandler = function(propertyDesc) { return new ReferenceHandler(propertyDesc, this.context); }; ElementHandler.prototype.handler = function(type) { if (type === 'Element') { return new GenericElementHandler(this.model, type, this.context); } else { return new ElementHandler(this.model, type, this.context); } }; /** * Handle the child element parsing * * @param {Element} node the xml node */ ElementHandler.prototype.handleChild = function(node) { var nameNs = parseNameNs(node.local, node.prefix); var propertyDesc, type, element, childHandler; propertyDesc = this.getPropertyForElement(nameNs); element = this.element; type = propertyDesc.effectiveType || propertyDesc.type; if (isSimple(propertyDesc.type)) { return this.valueHandler(propertyDesc, element); } if (propertyDesc.isReference) { childHandler = this.referenceHandler(propertyDesc).handleNode(node); } else { childHandler = this.handler(type).handleNode(node); } var newElement = childHandler.element; // child handles may decide to skip elements // by not returning anything if (newElement !== undefined) { if (propertyDesc.isMany) { element.get(propertyDesc.name).push(newElement); } else { element.set(propertyDesc.name, newElement); } if (propertyDesc.isReference) { _.extend(newElement, { element: element }); this.context.addReference(newElement); } else { // establish child -> parent relationship newElement.$parent = element; } } return childHandler; }; function GenericElementHandler(model, type, context) { this.model = model; this.context = context; } GenericElementHandler.prototype = Object.create(BaseElementHandler.prototype); GenericElementHandler.prototype.createElement = function(node) { var name = node.name, prefix = node.prefix, uri = node.ns[prefix], attributes = node.attributes; return this.model.createAny(name, uri, attributes); }; GenericElementHandler.prototype.handleChild = function(node) { var handler = new GenericElementHandler(this.model, 'Element', this.context).handleNode(node), element = this.element; var newElement = handler.element, children; if (newElement !== undefined) { children = element.$children = element.$children || []; children.push(newElement); // establish child -> parent relationship newElement.$parent = element; } return handler; }; GenericElementHandler.prototype.handleText = function(text) { this.body = this.body || '' + text; }; GenericElementHandler.prototype.handleEnd = function() { if (this.body) { this.element.$body = this.body; } }; /** * A reader for a meta-model * * @class XMLReader * * @param {Model} model used to read xml files */ function XMLReader(model) { this.model = model; } XMLReader.prototype.fromXML = function(xml, rootHandler, done) { var context = new Context(rootHandler); var parser = sax.parser(true, { xmlns: true, trim: true }); var stackInstance = stack(); var model = this.model, self = this; rootHandler.context = context; // push root handler stackInstance.push(rootHandler); function resolveReferences() { var elementsById = context.elementsById; var references = context.references; var i, r; for (i = 0; !!(r = references[i]); i++) { var element = r.element; var reference = elementsById[r.id]; var property = element.$descriptor.propertiesByName[r.property]; if (!reference) { context.addWarning({ message: 'unresolved reference <' + r.id + '>', element: r.element, property: r.property, value: r.id }); } if (property.isMany) { var collection = element.get(property.name), idx = collection.indexOf(r); if (!reference) { // remove unresolvable reference collection.splice(idx, 1); } else { // update reference collection[idx] = reference; } } else { element.set(property.name, reference); } } } function handleClose(tagName) { stackInstance.pop().handleEnd(); } function handleOpen(node) { var handler = stackInstance.peek(); normalizeNamespaces(node, model); try { stackInstance.push(handler.handleNode(node)); } catch (e) { var line = this.line, column = this.column; throw new Error( 'unparsable content <' + node.name + '> detected\n\t' + 'line: ' + line + '\n\t' + 'column: ' + column + '\n\t' + 'nested error: ' + e.message); } } function handleText(text) { stackInstance.peek().handleText(text); } parser.onopentag = handleOpen; parser.oncdata = parser.ontext = handleText; parser.onclosetag = handleClose; parser.onend = resolveReferences; // deferred parse XML to make loading really ascnchronous // this ensures the execution environment (node or browser) // is kept responsive and that certain optimization strategies // can kick in _.defer(function() { var error; try { parser.write(xml).close(); } catch (e) { error = e; } done(error, error ? undefined : rootHandler.element, context); }); }; XMLReader.prototype.handler = function(name) { return new ElementHandler(this.model, name); }; 'use strict'; //var _ = require('lodash'); //var Types = require('moddle').types, // common = require('./common'), var parseNameNs = parseName; var nameToAlias = nameToAlias; var XML_PREAMBLE = '<?xml version="1.0" encoding="UTF-8"?>\n'; var CDATA_ESCAPE = /[<>"&]+/; var DEFAULT_NS_MAP = DEFAULT_NS_MAP; function nsName(ns) { if (_.isString(ns)) { return ns; } else { return (ns.prefix ? ns.prefix + ':' : '') + ns.localName; } } function getElementNs(ns, descriptor) { if (descriptor.isGeneric) { return descriptor.name; } else { return _.extend({ localName: nameToAlias(descriptor.ns.localName, descriptor.$pkg) }, ns); } } function getPropertyNs(ns, descriptor) { return _.extend({ localName: descriptor.ns.localName }, ns); } function getSerializableProperties(element) { var descriptor = element.$descriptor; return _.filter(descriptor.properties, function(p) { var name = p.name; // do not serialize defaults if (!element.hasOwnProperty(name)) { return false; } var value = element[name]; // do not serialize default equals if (value === p.default) { return false; } return p.isMany ? value.length : true; }); } /** * Escape a string attribute to not contain any bad values (line breaks, '"', ...) * * @param {String} str the string to escape * @return {String} the escaped string */ function escapeAttr(str) { var escapeMap = { '\n': '&#10;', '\n\r': '&#10;', '"': '&quot;' }; // ensure we are handling strings here str = _.isString(str) ? str : '' + str; return str.replace(/(\n|\n\r|")/g, function(str) { return escapeMap[str]; }); } function filterAttributes(props) { return _.filter(props, function(p) { return p.isAttr; }); } function filterContained(props) { return _.filter(props, function(p) { return !p.isAttr; }); } function ReferenceSerializer(parent, ns) { this.ns = ns; } ReferenceSerializer.prototype.build = function(element) { this.element = element; return this; }; ReferenceSerializer.prototype.serializeTo = function(writer) { writer .appendIndent() .append('<' + nsName(this.ns) + '>' + this.element.id + '</' + nsName(this.ns) + '>') .appendNewLine(); }; function BodySerializer() {} BodySerializer.prototype.serializeValue = BodySerializer.prototype.serializeTo = function(writer) { var value = this.value, escape = this.escape; if (escape) { writer.append('<![CDATA['); } writer.append(this.value); if (escape) { writer.append(']]>'); } }; BodySerializer.prototype.build = function(prop, value) { this.value = value; if (prop.type === 'String' && CDATA_ESCAPE.test(value)) { this.escape = true; } return this; }; function ValueSerializer(ns) { this.ns = ns; } ValueSerializer.prototype = new BodySerializer(); ValueSerializer.prototype.serializeTo = function(writer) { writer .appendIndent() .append('<' + nsName(this.ns) + '>'); this.serializeValue(writer); writer .append( '</' + nsName(this.ns) + '>') .appendNewLine(); }; function ElementSerializer(parent, ns) { this.body = []; this.attrs = []; this.parent = parent; this.ns = ns; } ElementSerializer.prototype.build = function(element) { this.element = element; var otherAttrs = this.parseNsAttributes(element); if (!this.ns) { this.ns = this.nsTagName(element.$descriptor); } if (element.$descriptor.isGeneric) { this.parseGeneric(element); } else { var properties = getSerializableProperties(element); this.parseAttributes(filterAttributes(properties)); this.parseContainments(filterContained(properties)); this.parseGenericAttributes(element, otherAttrs); } return this; }; ElementSerializer.prototype.nsTagName = function(descriptor) { var effectiveNs = this.logNamespaceUsed(descriptor.ns); return getElementNs(effectiveNs, descriptor); }; ElementSerializer.prototype.nsPropertyTagName = function(descriptor) { var effectiveNs = this.logNamespaceUsed(descriptor.ns); return getPropertyNs(effectiveNs, descriptor); }; ElementSerializer.prototype.isLocalNs = function(ns) { return ns.uri === this.ns.uri; }; ElementSerializer.prototype.nsAttributeName = function(element) { var ns; if (_.isString(element)) { ns = parseNameNs(element); } else if (element.ns) { ns = element.ns; } var effectiveNs = this.logNamespaceUsed(ns); // strip prefix if same namespace like parent if (this.isLocalNs(effectiveNs)) { return { localName: ns.localName }; } else { return _.extend({ localName: ns.localName }, effectiveNs); } }; ElementSerializer.prototype.parseGeneric = function(element) { var self = this, body = this.body, attrs = this.attrs; _.forEach(element, function(val, key) { if (key === '$body') { body.push(new BodySerializer().build({ type: 'String' }, val)); } else if (key === '$children') { _.forEach(val, function(child) { body.push(new ElementSerializer(self).build(child)); }); } else if (key.indexOf('$') !== 0) { attrs.push({ name: key, value: escapeAttr(val) }); } }); }; /** * Parse namespaces and return a list of left over generic attributes * * @param {Object} element * @return {Array<Object>} */ ElementSerializer.prototype.parseNsAttributes = function(element) { var self = this; var genericAttrs = element.$attrs; var attributes = []; // parse namespace attributes first // and log them. push non namespace attributes to a list // and process them later _.forEach(genericAttrs, function(value, name) { var nameNs = parseNameNs(name); if (nameNs.prefix === 'xmlns') { self.logNamespace({ prefix: nameNs.localName, uri: value }); } else if (!nameNs.prefix && nameNs.localName === 'xmlns') { self.logNamespace({ uri: value }); } else { attributes.push({ name: name, value: value }); } }); return attributes; }; ElementSerializer.prototype.parseGenericAttributes = function(element, attributes) { var self = this; _.forEach(attributes, function(attr) { try { self.addAttribute(self.nsAttributeName(attr.name), attr.value); } catch (e) { console.warn('[writer] missing namespace information for ', attr.name, '=', attr.value, 'on', element, e); } }); }; ElementSerializer.prototype.parseContainments = function(properties) { var self = this, body = this.body, element = this.element, typeDesc = element.$descriptor; _.forEach(properties, function(p) { var value = element.get(p.name), isReference = p.isReference, isMany = p.isMany; var ns = self.nsPropertyTagName(p); if (!isMany) { value = [ value ]; } if (p.isBody) { body.push(new BodySerializer().build(p, value[0])); } else //if (Types.isSimple(p.type)) { if (isSimple(p.type)) { _.forEach(value, function(v) { body.push(new ValueSerializer(ns).build(p, v)); }); } else if (isReference) { _.forEach(value, function(v) { body.push(new ReferenceSerializer(self, ns).build(v)); }); } else { // allow serialization via type // rather than element name var asType = p.serialize === 'xsi:type'; _.forEach(value, function(v) { var serializer; if (asType) { serializer = new TypeSerializer(self, ns); } else { serializer = new ElementSerializer(self); } body.push(serializer.build(v)); }); } }); }; ElementSerializer.prototype.getNamespaces = function() { if (!this.parent) { if (!this.namespaces) { this.namespaces = { prefixMap: {}, uriMap: {}, used: {} }; } } else { this.namespaces = this.parent.getNamespaces(); } return this.namespaces; }; ElementSerializer.prototype.logNamespace = function(ns) { var namespaces = this.getNamespaces(); var existing = namespaces.uriMap[ns.uri]; if (!existing) { namespaces.uriMap[ns.uri] = ns; } namespaces.prefixMap[ns.prefix] = ns.uri; return ns; }; ElementSerializer.prototype.logNamespaceUsed = function(ns) { var element = this.element, model = element.$model, namespaces = this.getNamespaces(); // ns may be // // * prefix only // * prefix:uri var prefix = ns.prefix; var uri = ns.uri || DEFAULT_NS_MAP[prefix] || namespaces.prefixMap[prefix] || (model ? (model.getPackage(prefix) || {}).uri : null); if (!uri) { throw new Error('no namespace uri given for prefix <' + ns.prefix + '>'); } ns = namespaces.uriMap[uri]; if (!ns) { ns = this.logNamespace({ prefix: prefix, uri: uri }); } if (!namespaces.used[ns.uri]) { namespaces.used[ns.uri] = ns; } return ns; }; ElementSerializer.prototype.parseAttributes = function(properties) { var self = this, element = this.element; _.forEach(properties, function(p) { self.logNamespaceUsed(p.ns); var value = element.get(p.name); if (p.isReference) { value = value.id; } self.addAttribute(self.nsAttributeName(p), value); }); }; ElementSerializer.prototype.addAttribute = function(name, value) { var attrs = this.attrs; if (_.isString(value)) { value = escapeAttr(value); } attrs.push({ name: name, value: value }); }; ElementSerializer.prototype.serializeAttributes = function(writer) { var element = this.element, attrs = this.attrs, root = !this.parent, namespaces = this.namespaces; function collectNsAttrs() { return _.collect(namespaces.used, function(ns) { var name = 'xmlns' + (ns.prefix ? ':' + ns.prefix : ''); return { name: name, value: ns.uri }; }); } if (root) { attrs = collectNsAttrs().concat(attrs); } _.forEach(attrs, function(a) { writer .append(' ') .append(nsName(a.name)).append('="').append(a.value).append('"'); }); }; ElementSerializer.prototype.serializeTo = function(writer) { var hasBody = this.body.length; writer .appendIndent() .append('<' + nsName(this.ns)); this.serializeAttributes(writer); writer .append(hasBody ? '>' : ' />') .appendNewLine(); writer.indent(); _.forEach(this.body, function(b) { b.serializeTo(writer); }); writer.unindent(); if (hasBody) { writer .appendIndent() .append('</' + nsName(this.ns) + '>') .appendNewLine(); } }; /** * A serializer for types that handles serialization of data types */ function TypeSerializer(parent, ns) { ElementSerializer.call(this, parent, ns); } TypeSerializer.prototype = new ElementSerializer(); TypeSerializer.prototype.build = function(element) { this.element = element; this.typeNs = this.nsTagName(element.$descriptor); return ElementSerializer.prototype.build.call(this, element); }; TypeSerializer.prototype.isLocalNs = function(ns) { return ns.uri === this.typeNs.uri; }; function SavingWriter() { this.value = ''; this.write = function(str) { this.value += str; }; } function FormatingWriter(out, format) { var indent = ['']; this.append = function(str) { out.write(str); return this; }; this.appendNewLine = function() { if (format) { out.write('\n'); } return this; }; this.appendIndent = function() { if (format) { out.write(indent.join(' ')); } return this; }; this.indent = function() { indent.push(''); return this; }; this.unindent = function() { indent.pop(); return this; }; } /** * A writer for meta-model backed document trees * * @class XMLWriter */ function XMLWriter(options) { options = _.extend({ format: false, preamble: true }, options || {}); function toXML(tree, writer) { var internalWriter = writer || new SavingWriter(); var formatingWriter = new FormatingWriter(internalWriter, options.format); if (options.preamble) { formatingWriter.append(XML_PREAMBLE); } new ElementSerializer().build(tree).serializeTo(formatingWriter); if (!writer) { return internalWriter.value; } } return { toXML: toXML }; } //module.exports = XMLWriter; 'use strict'; // //var _ = require('lodash'); // //var Moddle = require('bpmn-moddle'); //ModdleXml = require('moddle-xml'); // function createModel(packages) { return new Moddle(packages); } /** * A sub class of {@link Moddle} with support for import and export of BPMN 2.0 xml files. * * @class BpmnModdle * @extends Moddle * * @param {Object|Array} packages to use for instantiating the model * @param {Object} [options] additional options to pass over */ function BpmnModdle(packages, options) { var packages = { bpmn: { "name": "BPMN20", "uri": "http://www.omg.org/spec/BPMN/20100524/MODEL", "associations": [], "types": [ { "name": "Interface", "superClass": [ "RootElement" ], "properties": [ { "name": "name", "isAttr": true, "type": "String" }, { "name": "operations", "type": "Operation", "association": "A_operations_interface", "isMany": true }, { "name": "implementationRef", "type": "String", "isAttr": true } ] }, { "name": "Operation",