123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233 |
- import {
- pick,
- assign,
- forEach,
- bind
- } from 'min-dash';
- import {
- parseName as parseNameNs
- } from './ns';
- /**
- * A utility to build element descriptors.
- */
- export default function DescriptorBuilder(nameNs) {
- this.ns = nameNs;
- this.name = nameNs.name;
- this.allTypes = [];
- this.allTypesByName = {};
- this.properties = [];
- this.propertiesByName = {};
- }
- DescriptorBuilder.prototype.build = function() {
- return pick(this, [
- 'ns',
- 'name',
- 'allTypes',
- 'allTypesByName',
- 'properties',
- 'propertiesByName',
- 'bodyProperty',
- 'idProperty'
- ]);
- };
- /**
- * Add property at given index.
- *
- * @param {Object} p
- * @param {Number} [idx]
- * @param {Boolean} [validate=true]
- */
- DescriptorBuilder.prototype.addProperty = function(p, idx, validate) {
- if (typeof idx === 'boolean') {
- validate = idx;
- idx = undefined;
- }
- this.addNamedProperty(p, validate !== false);
- var properties = this.properties;
- if (idx !== undefined) {
- properties.splice(idx, 0, p);
- } else {
- properties.push(p);
- }
- };
- DescriptorBuilder.prototype.replaceProperty = function(oldProperty, newProperty, replace) {
- var oldNameNs = oldProperty.ns;
- var props = this.properties,
- propertiesByName = this.propertiesByName,
- rename = oldProperty.name !== newProperty.name;
- if (oldProperty.isId) {
- if (!newProperty.isId) {
- throw new Error(
- 'property <' + newProperty.ns.name + '> must be id property ' +
- 'to refine <' + oldProperty.ns.name + '>');
- }
- this.setIdProperty(newProperty, false);
- }
- 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);
- }
- // validate existence and get location of old property
- var idx = props.indexOf(oldProperty);
- if (idx === -1) {
- throw new Error('property <' + oldNameNs.name + '> not found in property list');
- }
- // remove old property
- props.splice(idx, 1);
- // replacing the named property is intentional
- //
- // * validate only if this is a "rename" operation
- // * add at specific index unless we "replace"
- //
- this.addProperty(newProperty, replace ? undefined : idx, rename);
- // make new property available under old name
- propertiesByName[oldNameNs.name] = propertiesByName[oldNameNs.localName] = newProperty;
- };
- DescriptorBuilder.prototype.redefineProperty = function(p, targetPropertyName, replace) {
- var nsPrefix = p.ns.prefix;
- var parts = targetPropertyName.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, replace);
- }
- 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.setIdProperty = function(p, validate) {
- if (validate && this.idProperty) {
- throw new Error(
- 'id property defined multiple times ' +
- '(<' + this.idProperty.ns.name + '>, <' + p.ns.name + '>)');
- }
- this.idProperty = p;
- };
- 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, inherited) {
- var typesByName = this.allTypesByName,
- types = this.allTypes;
- var typeName = t.name;
- if (typeName in typesByName) {
- return;
- }
- forEach(t.properties, bind(function(p) {
- // clone property to allow extensions
- p = assign({}, p, {
- name: p.ns.localName,
- inherited: inherited
- });
- Object.defineProperty(p, 'definedBy', {
- value: t
- });
- var replaces = p.replaces,
- redefines = p.redefines;
- // add replace/redefine support
- if (replaces || redefines) {
- this.redefineProperty(p, replaces || redefines, replaces);
- } else {
- if (p.isBody) {
- this.setBodyProperty(p);
- }
- if (p.isId) {
- this.setIdProperty(p);
- }
- this.addProperty(p);
- }
- }, this));
- types.push(t);
- typesByName[typeName] = t;
- };
|