/*! * VisualEditor DataModel Annotation class. * * @copyright 2011-2013 VisualEditor Team and others; see AUTHORS.txt * @license The MIT License (MIT); see LICENSE.txt */ /** * Generic DataModel annotation. * * This is an abstract class, annotations should extend this and call this constructor from their * constructor. You should not instantiate this class directly. * * Annotations in the linear model are instances of subclasses of this class. Subclasses should * only override static properties and functions. * * @class * @constructor * @param {Object} linmodAnnotation Linear model annotation */ ve.dm.Annotation = function VeDmAnnotation( linmodAnnotation ) { this.name = this.constructor.static.name; this.linmodAnnotation = linmodAnnotation; }; /* Static properties */ /** * Object containing static properties. * * ve#inheritClass contains special logic to make sure these properties are inherited by subclasses. * @static * @property * @inheritable */ ve.dm.Annotation.static = {}; // TODO create a single base class for dm.Node, dm.Annotation and dm.MetaItem /** * Symbolic name for the annotation class. * * Must be set to a unique string by every subclass. Must not conflict with names of other nodes, * annotations, or meta items. * * @static * @property {string} [static.name=null] * @inheritable */ ve.dm.Annotation.static.name = null; /** * Array of HTML tag names that the annotation should be a match candidate for. * * Empty array means none, null means any. * * @see ve.dm.ModelRegistry * * @static * @property {string[]} static.matchTagNames * @inheritable */ ve.dm.Annotation.static.matchTagNames = null; /** * Array of RDFa types that the annotation should be a match candidate for. * * Empty array means none, null means any. * * @see ve.dm.ModelRegistry * * @static * @property {Array} static.matchRdfaType Array of strings or regular expressions * @inheritable */ ve.dm.Annotation.static.matchRdfaTypes = null; /** * Optional function to determine whether the annotation should match a given element. * * Takes an HTMLElement and returns true or false. * * This function is only called if this annotation has a chance of "winning"; see * ve.dm.ModelRegistry for more information about element matching. * If set to null, this property is ignored. Setting this to null is not the same as unconditionally * returning true, because the presence or absence of a matchFunction affects the annotation's * specificity. * * NOTE: This function is NOT a method, within this function "this" will not refer to an instance * of this class (or to anything reasonable, for that matter). * @static * @property {Function} static.matchFunction * @inheritable */ ve.dm.Annotation.static.matchFunction = null; /** * Static function to convert a DOM element or set of sibling DOM elements to an annotation of * this type. * * This function is only called if this annotation "won" the matching for the first DOM element, so * domElements[0] will match this item's matching rule. For annotations, there is only one node in * domElements[]. * * This function is allowed to return an annotation even if context.expectingContent is false. * If that happens, the annotation will be put in a wrapper paragraph. If this function returns * null, the DOM element will be converted to an alien node. * * The returned linear model annotation must have a type property set to a registered annotation * name (usually the annotations's .static.name, but that's not required). It may optionally have an * attributes property set to an object with key-value pairs. Any other properties are not allowed. * * @static * @method * @param {HTMLElement[]} domElements DOM elements to convert. Only one element * @param {Object} context Object describing the current state of the converter * @param {boolean} context.expectingContent Whether this function is expected to return a content element * @param {boolean} context.inWrapper Whether this element is in a wrapper paragraph generated by the converter; * can only be true if context.expectingContent is also true * @param {boolean} context.canCloseWrapper Whether the current wrapper paragraph can be closed; * can only be true if context.inWrapper is also true * @returns {Object|null} Linear model annotation, or null to alienate */ ve.dm.Annotation.static.toDataElement = function ( /*domElements, context*/ ) { throw new Error( 've.dm.Annotation subclass must implement toDataElement' ); }; /** * Static function to convert a linear model annotation of this type back to a DOM element. * * This function returns an array of DOM elements for consistency, but annotations can only return * one DOM element, so any elements beyond the first are ignored. * * @static * @method * @param {Object} Linear model annotation with a type property and optionally an attributes property * @returns {HTMLElement[]} Array of one DOM element */ ve.dm.Annotation.static.toDomElements = function ( /*dataElement*/ ) { throw new Error( 've.dm.Annotation subclass must implement toDomElements' ); }; /** * About grouping is not supported for annotations; setting this to true has no effect. * * @static * @property {boolean} static.enableAboutGrouping */ ve.dm.Annotation.static.enableAboutGrouping = false; /** * Whether HTML attributes should be preserved for this annotation type. If true, the HTML attributes * of the DOM elements will be stored as attributes in the linear model annotation. The attribute * names will be html/i/attrName, where i is the index of the DOM element in the domElements array, * and attrName is the name of the attribute. * * This should generally be enabled, except for annotation types that store their entire HTML in an * attribute. * * @static * @property {boolean} static.storeHtmlAttributes * @inheritable */ ve.dm.Annotation.static.storeHtmlAttributes = true; /* Methods */ ve.dm.Annotation.prototype.getType = function () { return this.name; }; /** * Get the linear model object for this annotation * @returns {Object} Linear model annotation */ ve.dm.Annotation.prototype.getLinmodAnnotation = function () { return this.linmodAnnotation; }; /** * Get the value of an attribute * * Return value is by reference if array or object * * @method * @param {string} key Name of attribute to get * @returns {Mixed} Value of attribute, or undefined if no such attribute exists */ ve.dm.Annotation.prototype.getAttribute = function ( key ) { return this.linmodAnnotation && this.linmodAnnotation.attributes ? this.linmodAnnotation.attributes[key] : undefined; }; // FIXME code copied from ve.dm.Node /** * Get a copy of all attributes. * * Values are by reference if array or object, similar to using the getAttribute method. * * @method * @param {string} prefix Only return attributes with this prefix, and remove the prefix from them * @returns {Object} Attributes */ ve.dm.Annotation.prototype.getAttributes = function ( prefix ) { var key, filtered, attributes = this.element && this.linmodAnnotation.attributes ? this.linmodAnnotation.attributes : {}; if ( prefix ) { filtered = {}; for ( key in attributes ) { if ( key.indexOf( prefix ) === 0 ) { filtered[key.substr( prefix.length )] = attributes[key]; } } return filtered; } return ve.extendObject( {}, attributes ); }; /** * Convenience wrapper for .toDomElements() on the current annotation * @method * @see #toDomElements */ ve.dm.Annotation.prototype.getDomElements = function () { return this.constructor.static.toDomElements( this.linmodAnnotation ); };