Merge "(bug 45062) Implement the new node API in the converter"

This commit is contained in:
jenkins-bot 2013-02-22 23:26:18 +00:00 committed by Gerrit Code Review
commit fb11cc8a8c
22 changed files with 275 additions and 460 deletions

View file

@ -35,20 +35,29 @@ ve.dm.AlienNode.static.enableAboutGrouping = true;
ve.dm.AlienNode.static.storeHtmlAttributes = false; ve.dm.AlienNode.static.storeHtmlAttributes = false;
ve.dm.AlienNode.static.toDataElement = function ( domElement, context ) { ve.dm.AlienNode.static.toDataElement = function ( domElements, context ) {
var i, isInline, allTagsInline, type, html;
// Check whether all elements are inline elements
allTagsInline = true;
for ( i = 0; i < domElements.length; i++ ) {
if ( ve.isBlockElement( domElements[i] ) ) {
allTagsInline = false;
break;
}
}
// We generate alienBlock elements for block tags and alienInline elements for // We generate alienBlock elements for block tags and alienInline elements for
// inline tags; unless we're in a content location, in which case we have no choice // inline tags; unless we're in a content location, in which case we have no choice
// but to generate an alienInline element. // but to generate an alienInline element.
var isInline = isInline =
// Force inline in content locations (but not wrappers) // Force inline in content locations (but not wrappers)
( context.expectingContent && !context.inWrapper ) || ( context.expectingContent && !context.inWrapper ) ||
// Also force inline in wrappers that we can't close // Also force inline in wrappers that we can't close
( context.inWrapper && !context.canCloseWrapper ) || ( context.inWrapper && !context.canCloseWrapper ) ||
// Look at the tag name otherwise // Look at the tag names otherwise
!ve.isBlockElement( domElement ), allTagsInline;
type = isInline ? 'alienInline' : 'alienBlock', type = isInline ? 'alienInline' : 'alienBlock';
// TODO handle about groups somehow html = $( '<div>', domElements[0].ownerDocument ).append( $( domElements ).clone() ).html();
html = $( '<div>' ).append( $( domElement ).clone() ).html();
return { return {
'type': type, 'type': type,
'attributes': { 'attributes': {
@ -57,11 +66,11 @@ ve.dm.AlienNode.static.toDataElement = function ( domElement, context ) {
}; };
}; };
ve.dm.AlienNode.static.toDomElement = function ( dataElement ) { ve.dm.AlienNode.static.toDomElements = function ( dataElement ) {
var wrapper = document.createElement( 'div' ); var wrapper = document.createElement( 'div' );
wrapper.innerHTML = dataElement.attributes.html; wrapper.innerHTML = dataElement.attributes.html;
// TODO handle multiple nodes (from about groups) somehow // Convert wrapper.children to an array
return wrapper.firstChild; return Array.prototype.slice.call( wrapper.childNodes, 0 );
}; };
/* Concrete subclasses */ /* Concrete subclasses */

View file

@ -35,8 +35,8 @@ ve.dm.BreakNode.static.toDataElement = function () {
return { 'type': 'break' }; return { 'type': 'break' };
}; };
ve.dm.BreakNode.static.toDomElement = function () { ve.dm.BreakNode.static.toDomElements = function () {
return document.createElement( 'br' ); return [ document.createElement( 'br' ) ];
}; };
/* Registration */ /* Registration */

View file

@ -33,8 +33,8 @@ ve.dm.CenterNode.static.toDataElement = function () {
return { 'type': 'center' }; return { 'type': 'center' };
}; };
ve.dm.CenterNode.static.toDomElement = function () { ve.dm.CenterNode.static.toDomElements = function () {
return document.createElement( 'center' ); return [ document.createElement( 'center' ) ];
}; };
/* Registration */ /* Registration */

View file

@ -35,14 +35,14 @@ ve.dm.DefinitionListItemNode.static.defaultAttributes = {
ve.dm.DefinitionListItemNode.static.matchTagNames = [ 'dt', 'dd' ]; ve.dm.DefinitionListItemNode.static.matchTagNames = [ 'dt', 'dd' ];
ve.dm.DefinitionListItemNode.static.toDataElement = function ( domElement ) { ve.dm.DefinitionListItemNode.static.toDataElement = function ( domElements ) {
var style = domElement.nodeName.toLowerCase() === 'dt' ? 'term' : 'definition'; var style = domElements[0].nodeName.toLowerCase() === 'dt' ? 'term' : 'definition';
return { 'type': 'definitionListItem', 'attributes': { 'style': style } }; return { 'type': 'definitionListItem', 'attributes': { 'style': style } };
}; };
ve.dm.DefinitionListItemNode.static.toDomElement = function ( dataElement ) { ve.dm.DefinitionListItemNode.static.toDomElements = function ( dataElement ) {
var tag = dataElement.attributes && dataElement.attributes.style === 'term' ? 'dt' : 'dd'; var tag = dataElement.attributes && dataElement.attributes.style === 'term' ? 'dt' : 'dd';
return document.createElement( tag ); return [ document.createElement( tag ) ];
}; };
/* Registration */ /* Registration */

View file

@ -35,8 +35,8 @@ ve.dm.DefinitionListNode.static.toDataElement = function () {
return { 'type': 'definitionList' }; return { 'type': 'definitionList' };
}; };
ve.dm.DefinitionListNode.static.toDomElement = function () { ve.dm.DefinitionListNode.static.toDomElements = function () {
return document.createElement( 'dl' ); return [ document.createElement( 'dl' ) ];
}; };
/* Registration */ /* Registration */

View file

@ -35,7 +35,7 @@ ve.dm.HeadingNode.static.defaultAttributes = {
ve.dm.HeadingNode.static.matchTagNames = [ 'h1', 'h2', 'h3', 'h4', 'h5', 'h6' ]; ve.dm.HeadingNode.static.matchTagNames = [ 'h1', 'h2', 'h3', 'h4', 'h5', 'h6' ];
ve.dm.HeadingNode.static.toDataElement = function ( domElement ) { ve.dm.HeadingNode.static.toDataElement = function ( domElements ) {
var levels = { var levels = {
'h1': 1, 'h1': 1,
'h2': 2, 'h2': 2,
@ -44,13 +44,13 @@ ve.dm.HeadingNode.static.toDataElement = function ( domElement ) {
'h5': 5, 'h5': 5,
'h6': 6 'h6': 6
}, },
level = levels[domElement.nodeName.toLowerCase()]; level = levels[domElements[0].nodeName.toLowerCase()];
return { 'type': 'heading', 'attributes': { 'level': level } }; return { 'type': 'heading', 'attributes': { 'level': level } };
}; };
ve.dm.HeadingNode.static.toDomElement = function ( dataElement ) { ve.dm.HeadingNode.static.toDomElements = function ( dataElement ) {
var level = dataElement.attributes && dataElement.attributes.level || 1; var level = dataElement.attributes && dataElement.attributes.level || 1;
return document.createElement( 'h' + level ); return [ document.createElement( 'h' + level ) ];
}; };
/* Registration */ /* Registration */

View file

@ -35,8 +35,8 @@ ve.dm.ImageNode.static.toDataElement = function () {
return { 'type': 'image' }; return { 'type': 'image' };
}; };
ve.dm.ImageNode.static.toDomElement = function () { ve.dm.ImageNode.static.toDomElements = function () {
return document.createElement( 'img' ); return [ document.createElement( 'img' ) ];
}; };
/* Registration */ /* Registration */

View file

@ -35,8 +35,8 @@ ve.dm.ListItemNode.static.toDataElement = function () {
return { 'type': 'listItem' }; return { 'type': 'listItem' };
}; };
ve.dm.ListItemNode.static.toDomElement = function () { ve.dm.ListItemNode.static.toDomElements = function () {
return document.createElement( 'li' ); return [ document.createElement( 'li' ) ];
}; };
/* Registration */ /* Registration */

View file

@ -35,14 +35,14 @@ ve.dm.ListNode.static.defaultAttributes = {
ve.dm.ListNode.static.matchTagNames = [ 'ul', 'ol' ]; ve.dm.ListNode.static.matchTagNames = [ 'ul', 'ol' ];
ve.dm.ListNode.static.toDataElement = function ( domElement ) { ve.dm.ListNode.static.toDataElement = function ( domElements ) {
var style = domElement.nodeName.toLowerCase() === 'ol' ? 'number' : 'bullet'; var style = domElements[0].nodeName.toLowerCase() === 'ol' ? 'number' : 'bullet';
return { 'type': 'list', 'attributes': { 'style': style } }; return { 'type': 'list', 'attributes': { 'style': style } };
}; };
ve.dm.ListNode.static.toDomElement = function ( dataElement ) { ve.dm.ListNode.static.toDomElements = function ( dataElement ) {
var tag = dataElement.attributes && dataElement.attributes.style === 'number' ? 'ol' : 'ul'; var tag = dataElement.attributes && dataElement.attributes.style === 'number' ? 'ol' : 'ul';
return document.createElement( tag ); return [ document.createElement( tag ) ];
}; };

View file

@ -31,18 +31,18 @@ ve.dm.MWEntityNode.static.isContent = true;
ve.dm.MWEntityNode.static.matchTagNames = [ 'span' ]; ve.dm.MWEntityNode.static.matchTagNames = [ 'span' ];
ve.dm.MWEntityNode.static.matchRdfaTypes = [ 'mw:Entity' ]; // TODO ignored, still using a converter hack ve.dm.MWEntityNode.static.matchRdfaTypes = [ 'mw:Entity' ];
ve.dm.MWEntityNode.static.toDataElement = function ( domElement ) { ve.dm.MWEntityNode.static.toDataElement = function ( domElements ) {
return { 'type': 'MWentity', 'attributes': { 'character': domElement.textContent } }; return { 'type': 'MWentity', 'attributes': { 'character': domElements[0].textContent } };
}; };
ve.dm.MWEntityNode.static.toDomElement = function ( dataElement ) { ve.dm.MWEntityNode.static.toDomElements = function ( dataElement ) {
var domElement = document.createElement( 'span' ), var domElement = document.createElement( 'span' ),
textNode = document.createTextNode( dataElement.attributes.character ); textNode = document.createTextNode( dataElement.attributes.character );
domElement.setAttribute( 'typeof', 'mw:Entity' ); domElement.setAttribute( 'typeof', 'mw:Entity' );
domElement.appendChild( textNode ); domElement.appendChild( textNode );
return domElement; return [ domElement ];
}; };
/* Registration */ /* Registration */

View file

@ -33,24 +33,25 @@ ve.dm.MetaNode.static.isMeta = true;
ve.dm.MetaNode.static.matchTagNames = [ 'meta', 'link' ]; ve.dm.MetaNode.static.matchTagNames = [ 'meta', 'link' ];
ve.dm.MetaNode.static.toDataElement = function ( domElement, context ) { ve.dm.MetaNode.static.toDataElement = function ( domElements, context ) {
var isLink = domElement.nodeName.toLowerCase() === 'link', var firstDomElement = domElements[0],
isLink = firstDomElement.nodeName.toLowerCase() === 'link',
keyAttr = isLink ? 'rel' : 'property', keyAttr = isLink ? 'rel' : 'property',
valueAttr = isLink ? 'href' : 'content', valueAttr = isLink ? 'href' : 'content',
dataElement = { dataElement = {
'type': context.expectingContent ? 'metaInline' : 'metaBlock', 'type': context.expectingContent ? 'metaInline' : 'metaBlock',
'attributes': { 'attributes': {
'style': isLink ? 'link' : 'meta', 'style': isLink ? 'link' : 'meta',
'key': domElement.getAttribute( keyAttr ) 'key': firstDomElement.getAttribute( keyAttr )
} }
}; };
if ( domElement.hasAttribute( valueAttr ) ) { if ( firstDomElement.hasAttribute( valueAttr ) ) {
dataElement.attributes.value = domElement.getAttribute( valueAttr ); dataElement.attributes.value = firstDomElement.getAttribute( valueAttr );
} }
return dataElement; return dataElement;
}; };
ve.dm.MetaNode.static.toDomElement = function ( dataElement ) { ve.dm.MetaNode.static.toDomElements = function ( dataElement ) {
var style = dataElement.attributes && dataElement.attributes.style || 'meta', var style = dataElement.attributes && dataElement.attributes.style || 'meta',
isLink = style === 'link', isLink = style === 'link',
tag = isLink ? 'link' : 'meta', tag = isLink ? 'link' : 'meta',
@ -58,7 +59,7 @@ ve.dm.MetaNode.static.toDomElement = function ( dataElement ) {
valueAttr = isLink ? 'href' : 'content', valueAttr = isLink ? 'href' : 'content',
domElement; domElement;
if ( style === 'comment' ) { if ( style === 'comment' ) {
return document.createComment( dataElement.attributes && dataElement.attributes.text || '' ); return [ document.createComment( dataElement.attributes && dataElement.attributes.text || '' ) ];
} }
domElement = document.createElement( tag ); domElement = document.createElement( tag );
if ( dataElement.attributes && dataElement.attributes.key !== null ) { if ( dataElement.attributes && dataElement.attributes.key !== null ) {
@ -67,7 +68,7 @@ ve.dm.MetaNode.static.toDomElement = function ( dataElement ) {
if ( dataElement.attributes && dataElement.attributes.value ) { if ( dataElement.attributes && dataElement.attributes.value ) {
domElement.setAttribute( valueAttr, dataElement.attributes.value ); domElement.setAttribute( valueAttr, dataElement.attributes.value );
} }
return domElement; return [ domElement ];
}; };
/* Concrete subclasses */ /* Concrete subclasses */

View file

@ -35,8 +35,8 @@ ve.dm.ParagraphNode.static.toDataElement = function () {
return { 'type': 'paragraph' }; return { 'type': 'paragraph' };
}; };
ve.dm.ParagraphNode.static.toDomElement = function () { ve.dm.ParagraphNode.static.toDomElements = function () {
return document.createElement( 'p' ); return [ document.createElement( 'p' ) ];
}; };
/* Registration */ /* Registration */

View file

@ -37,8 +37,8 @@ ve.dm.PreformattedNode.static.toDataElement = function () {
return { 'type': 'preformatted' }; return { 'type': 'preformatted' };
}; };
ve.dm.PreformattedNode.static.toDomElement = function () { ve.dm.PreformattedNode.static.toDomElements = function () {
return document.createElement( 'pre' ); return [ document.createElement( 'pre' ) ];
}; };
/* Registration */ /* Registration */

View file

@ -35,14 +35,14 @@ ve.dm.TableCellNode.static.defaultAttributes = {
ve.dm.TableCellNode.static.matchTagNames = [ 'td', 'th' ]; ve.dm.TableCellNode.static.matchTagNames = [ 'td', 'th' ];
ve.dm.TableCellNode.static.toDataElement = function ( domElement ) { ve.dm.TableCellNode.static.toDataElement = function ( domElements ) {
var style = domElement.nodeName.toLowerCase() === 'th' ? 'header' : 'data'; var style = domElements[0].nodeName.toLowerCase() === 'th' ? 'header' : 'data';
return { 'type': 'tableCell', 'attributes': { 'style': style } }; return { 'type': 'tableCell', 'attributes': { 'style': style } };
}; };
ve.dm.TableCellNode.static.toDomElement = function ( dataElement ) { ve.dm.TableCellNode.static.toDomElements = function ( dataElement ) {
var tag = dataElement.attributes && dataElement.attributes.style === 'header' ? 'th' : 'td'; var tag = dataElement.attributes && dataElement.attributes.style === 'header' ? 'th' : 'td';
return document.createElement( tag ); return [ document.createElement( tag ) ];
}; };
/* Registration */ /* Registration */

View file

@ -35,8 +35,8 @@ ve.dm.TableNode.static.toDataElement = function () {
return { 'type': 'table' }; return { 'type': 'table' };
}; };
ve.dm.TableNode.static.toDomElement = function () { ve.dm.TableNode.static.toDomElements = function () {
return document.createElement( 'table' ); return [ document.createElement( 'table' ) ];
}; };
/* Registration */ /* Registration */

View file

@ -37,8 +37,8 @@ ve.dm.TableRowNode.static.toDataElement = function () {
return { 'type': 'tableRow' }; return { 'type': 'tableRow' };
}; };
ve.dm.TableRowNode.static.toDomElement = function () { ve.dm.TableRowNode.static.toDomElements = function () {
return document.createElement( 'tr' ); return [ document.createElement( 'tr' ) ];
}; };
/* Registration */ /* Registration */

View file

@ -37,24 +37,24 @@ ve.dm.TableSectionNode.static.defaultAttributes = {
ve.dm.TableSectionNode.static.matchTagNames = [ 'thead', 'tbody', 'tfoot' ]; ve.dm.TableSectionNode.static.matchTagNames = [ 'thead', 'tbody', 'tfoot' ];
ve.dm.TableSectionNode.static.toDataElement = function ( domElement ) { ve.dm.TableSectionNode.static.toDataElement = function ( domElements ) {
var styles = { var styles = {
'thead': 'header', 'thead': 'header',
'tbody': 'body', 'tbody': 'body',
'tfoot': 'footer' 'tfoot': 'footer'
}, },
style = styles[domElement.nodeName.toLowerCase()] || 'body'; style = styles[domElements[0].nodeName.toLowerCase()] || 'body';
return { 'type': 'tableSection', 'attributes': { 'style': style } }; return { 'type': 'tableSection', 'attributes': { 'style': style } };
}; };
ve.dm.TableSectionNode.static.toDomElement = function ( dataElement ) { ve.dm.TableSectionNode.static.toDomElements = function ( dataElement ) {
var tags = { var tags = {
'header': 'thead', 'header': 'thead',
'body': 'tbody', 'body': 'tbody',
'footer': 'tfoot' 'footer': 'tfoot'
}, },
tag = tags[dataElement.attributes && dataElement.attributes.style || 'body']; tag = tags[dataElement.attributes && dataElement.attributes.style || 'body'];
return document.createElement( tag ); return [ document.createElement( tag ) ];
}; };
/* Registration */ /* Registration */

View file

@ -12,21 +12,15 @@
* *
* @class * @class
* @constructor * @constructor
* @param {ve.dm.ModelRegistry} modelRegistry
* @param {ve.dm.NodeFactory} nodeFactory * @param {ve.dm.NodeFactory} nodeFactory
* @param {ve.dm.AnnotationFactory} annotationFactory * @param {ve.dm.AnnotationFactory} annotationFactory
*/ */
ve.dm.Converter = function VeDmConverter( nodeFactory, annotationFactory ) { ve.dm.Converter = function VeDmConverter( modelRegistry, nodeFactory, annotationFactory ) {
// Properties // Properties
this.modelRegistry = modelRegistry;
this.nodeFactory = nodeFactory; this.nodeFactory = nodeFactory;
this.annotationFactory = annotationFactory; this.annotationFactory = annotationFactory;
this.elements = {
'toDomElement': {},
'toDataElement': {},
'dataElementTypes': {}
};
// Events
this.nodeFactory.addListenerMethod( this, 'register', 'onNodeRegister' );
}; };
/* Static Methods */ /* Static Methods */
@ -58,80 +52,36 @@ ve.dm.Converter.getDataContentFromText = function ( text, annotations ) {
/* Methods */ /* Methods */
/**
* Handle register events from the node factory.
*
* FIXME
* If a node is special; such as document, alienInline, alienBlock and text; its {converters}
* property should be set to null, as to distinguish it from a new node type that someone has simply
* forgotten to implement converters for.
*
* @method
* @param {string} type Node type
* @param {Function} constructor Node constructor
* @throws {Error} Missing conversion data in node implementation
*/
ve.dm.Converter.prototype.onNodeRegister = function ( dataElementType, constructor ) {
if ( !constructor.static.toDomElement || !constructor.static.toDataElement ) {
throw new Error( 'Missing static properties in node implementation of ' + dataElementType );
} else {
var i,
domElementTypes = constructor.static.matchTagNames || [],
toDomElement = constructor.static.toDomElement,
toDataElement = constructor.static.toDataElement;
// Registration
this.elements.toDomElement[dataElementType] = toDomElement;
for ( i = 0; i < domElementTypes.length; i++ ) {
this.elements.toDataElement[domElementTypes[i]] = toDataElement;
this.elements.dataElementTypes[domElementTypes[i]] = dataElementType;
}
}
};
/** /**
* Get the DOM element for a given linear model element. * Get the DOM element for a given linear model element.
* *
* This invokes the toDomElement function registered for the element type. * This invokes the toDomElements function registered for the element type.
* *
* @method * @method
* @param {Object} dataElement Linear model element * @param {Object} dataElement Linear model element
* @param {HTMLDocument} doc Document to create DOM elements in * @param {HTMLDocument} doc Document to create DOM elements in
* @returns {HTMLElement|boolean} DOM element, or false if the element cannot be converted * @returns {HTMLElement|boolean} DOM element, or false if the element cannot be converted
*/ */
ve.dm.Converter.prototype.getDomElementFromDataElement = function ( dataElement, doc ) { ve.dm.Converter.prototype.getDomElementsFromDataElement = function ( dataElement, doc ) {
var key, domElement, dataElementAttributes, wrapper, var domElements, dataElementAttributes, key, matches,
dataElementType = dataElement.type; nodeClass = this.nodeFactory.lookup( dataElement.type );
if ( dataElementType === 'alienInline' || dataElementType === 'alienBlock' ) { if ( !nodeClass ) {
// Alien throw new Error( 'Attempting to convert unknown data element type ' + dataElement.type );
// Create nodes from source
wrapper = doc.createElement( 'div' );
wrapper.innerHTML = dataElement.attributes.html;
if ( wrapper.childNodes.length > 1 ) {
// Wrap the HTML in a single element, this makes
// it much easier to deal with. It'll be unwrapped
// at the end of getDomFromData().
domElement = doc.createElement( 'div' );
domElement.setAttribute( 'data-ve-multi-child-alien-wrapper', 'true' );
while ( wrapper.firstChild ) {
domElement.appendChild( wrapper.firstChild );
}
} else {
domElement = wrapper.firstChild;
}
return domElement;
} }
if ( !( dataElementType in this.elements.toDomElement ) ) { domElements = nodeClass.static.toDomElements( dataElement, doc );
// Unsupported element if ( !domElements || !domElements.length ) {
return false; throw new Error( 'toDomElements() failed to return an array when converting element of type ' + dataElement.type );
} }
domElement = this.elements.toDomElement[dataElementType]( dataElement );
dataElementAttributes = dataElement.attributes; dataElementAttributes = dataElement.attributes;
if ( dataElementAttributes ) { if ( dataElementAttributes ) {
for ( key in dataElementAttributes ) { for ( key in dataElementAttributes ) {
// Only include 'html/0/*' attributes and strip the 'html/0/' from the beginning of the name // Only include 'html/i/*' attributes and strip the 'html/i/' from the beginning of the name
if ( key.indexOf( 'html/0/' ) === 0 ) { /*jshint regexp:false */
domElement.setAttribute( key.substr( 7 ), dataElementAttributes[key] ); matches = key.match( /^html\/(\d+)\/(.*)$/ );
if ( matches ) {
if ( domElements[matches[1]] && !domElements[matches[1]].hasAttribute( matches[2] ) ) {
domElements[matches[1]].setAttribute( matches[2], dataElementAttributes[key] );
}
} }
} }
} }
@ -141,66 +91,34 @@ ve.dm.Converter.prototype.getDomElementFromDataElement = function ( dataElement,
!ve.isEmptyObject( dataElement.internal.changed ) && !ve.isEmptyObject( dataElement.internal.changed ) &&
ve.init.platform.useChangeMarkers() ve.init.platform.useChangeMarkers()
) { ) {
domElement.setAttribute( 'data-ve-changed', domElements[0].setAttribute( 'data-ve-changed',
JSON.stringify( dataElement.internal.changed ) JSON.stringify( dataElement.internal.changed )
); );
} }
return domElement;
return domElements;
}; };
/** ve.dm.Converter.prototype.createDataElement = function ( modelClass, domElements, context ) {
* Get the linear model data element for a given DOM element. var i, j, dataElement, dataElementAttributes, domElementAttributes, domElementAttribute;
* dataElement = modelClass.static.toDataElement( domElements, ve.copyObject( context ) );
* This invokes the toDataElement function registered for the element type if ( modelClass.static.storeHTMLAttributes && dataElement ) {
* for ( i = 0; i < domElements.length; i++ ) {
* @method domElementAttributes = domElements[i].attributes;
* @param {HTMLElement} domElement DOM element if ( domElementAttributes && domElementAttributes.length ) {
* @param {ve.AnnotationSet} annotations Annotations to apply if the node is a content node dataElementAttributes = dataElement.attributes = dataElement.attributes || {};
* @returns {Object|boolean} Linear model element, or false if the node cannot be converted // Include all attributes and prepend 'html/i/' to each attribute name
*/ for ( j = 0; j < domElementAttributes.length; j++ ) {
ve.dm.Converter.prototype.getDataElementFromDomElement = function ( domElement, annotations ) { domElementAttribute = domElementAttributes[j];
var dataElement, domElementAttributes, dataElementAttributes, domElementAttribute, i, dataElementAttributes['html/' + i + '/' + domElementAttribute.name] =
domElementType = domElement.nodeName.toLowerCase(); domElementAttribute.value;
annotations = annotations || new ve.AnnotationSet(); }
if ( }
// Unsupported elements
!( domElementType in this.elements.toDataElement )
// TODO check for generated elements
) {
return false;
}
dataElement = this.elements.toDataElement[domElementType]( domElement );
domElementAttributes = domElement.attributes;
if (
dataElement && ve.dm.nodeFactory.doesNodeStoreHtmlAttributes( dataElement.type ) &&
domElementAttributes.length
) {
dataElementAttributes = dataElement.attributes = dataElement.attributes || {};
// Include all attributes and prepend 'html/0/' to each attribute name
for ( i = 0; i < domElementAttributes.length; i++ ) {
domElementAttribute = domElementAttributes[i];
dataElementAttributes['html/0/' + domElementAttribute.name] = domElementAttribute.value;
} }
} }
if ( this.nodeFactory.isNodeContent( dataElement.type ) && !annotations.isEmpty() ) {
dataElement.annotations = annotations.clone();
}
return dataElement; return dataElement;
}; };
/**
* Check if an HTML DOM node represents an annotation, and if so, build an annotation object for it.
*
* Annotation Object:
* { 'type': 'type', data: { 'key': 'value', ... } }
*
* @param {HTMLElement} domElement HTML DOM node
* @returns {Object|boolean} Annotation object, or false if the node is not an annotation
*/
ve.dm.Converter.prototype.getDataAnnotationFromDomElement = function ( domElement ) {
return this.annotationFactory.createFromElement( domElement ) || false;
};
/** /**
* Build an HTML DOM node for a linear model annotation. * Build an HTML DOM node for a linear model annotation.
* *
@ -238,38 +156,6 @@ ve.dm.Converter.prototype.getDataFromDom = function ( doc ) {
*/ */
ve.dm.Converter.prototype.getDataFromDomRecursion = function ( domElement, annotations, ve.dm.Converter.prototype.getDataFromDomRecursion = function ( domElement, annotations,
dataElement, path, alreadyWrapped ) { dataElement, path, alreadyWrapped ) {
function createAlien( domElement, context, isWrapper ) {
// We generate alienBlock elements for block tags and alienInline elements for
// inline tags; unless we're in a content location, in which case we have no choice
// but to generate an alienInline element.
var isInline =
// Force inline in content locations (but not wrappers)
( !context.inWrapper && context.expectingContent ) ||
// Also force inline in wrappers that we can't close
( context.inWrapper && !context.canCloseWrapper ) ||
// Look at the tag name otherwise
!ve.isBlockElement( domElement ),
type = isInline ? 'alienInline' : 'alienBlock',
html, alien;
if ( isWrapper ) {
html = $( domElement ).html();
} else {
html = $( '<div>', doc ).append( $( domElement ).clone() ).html();
}
alien = [
{
'type': type,
'attributes': {
'html': html
}
},
{ 'type': '/' + type }
];
if ( !annotations.isEmpty() ) {
alien[0].annotations = annotations.clone();
}
return alien;
}
function addWhitespace( element, index, whitespace ) { function addWhitespace( element, index, whitespace ) {
if ( !element.internal ) { if ( !element.internal ) {
element.internal = {}; element.internal = {};
@ -321,76 +207,40 @@ ve.dm.Converter.prototype.getDataFromDomRecursion = function ( domElement, annot
context.expectingContent = originallyExpectingContent; context.expectingContent = originallyExpectingContent;
} }
/** function getAboutGroup( el ) {
* Helper function to group adjacent child elements with the same about attribute together. var textNodes = [], aboutGroup = [ el ], elAbout, node;
* If there are multiple adjacent child nodes with the same about attribute, they are if ( !el.getAttribute || el.getAttribute( 'about' ) === null ) {
* wrapped in a `<div>` with the data-ve-aboutgroup attribute set. return aboutGroup;
* }
* This function does not wrap single-element about groups, and does not descend into the elAbout = el.getAttribute( 'about' );
* child elements. for ( node = el.nextSibling; node; node = node.nextSibling ) {
* if ( !node.getAttribute ) {
* @private
* @param element {HTMLElement} Element to process
*/
function doAboutGrouping( element ) {
var child = element.firstChild, textNodes = [],
prevChild, aboutGroup, aboutWrapper, childAbout, nextChild, i;
while ( child ) {
nextChild = child.nextSibling;
if ( !child.getAttribute ) {
// Text nodes don't have a getAttribute() method. Thanks HTML DOM, // Text nodes don't have a getAttribute() method. Thanks HTML DOM,
// that's really helpful ^^ // that's really helpful ^^
textNodes.push( child ); textNodes.push( node );
child = nextChild;
continue; continue;
} }
childAbout = child.getAttribute( 'about' ); if ( node.getAttribute( 'about' ) === elAbout ) {
if ( childAbout && !aboutGroup ) { aboutGroup = aboutGroup.concat( textNodes );
// Start of a new about group textNodes = [];
aboutGroup = childAbout; aboutGroup.push( node );
} else if ( childAbout && childAbout === aboutGroup ) { } else {
// Continuation of the current about group break;
if ( !aboutWrapper ) {
// This is the second child in this group, so the
// previous child is the first child in this group.
// Wrap the previous child
aboutWrapper = doc.createElement( 'div' );
aboutWrapper.setAttribute( 'data-ve-aboutgroup', aboutGroup );
element.insertBefore( aboutWrapper, prevChild );
aboutWrapper.appendChild( prevChild );
}
// Append any outstanding text nodes to the wrapper
for ( i = 0; i < textNodes.length; i++ ) {
aboutWrapper.appendChild( textNodes[i] );
}
// Append this child to the wrapper
aboutWrapper.appendChild( child );
} else if ( aboutGroup ) {
// This child isn't in the current about group
aboutGroup = undefined;
aboutWrapper = undefined;
if ( childAbout ) {
// Start of a new about group
aboutGroup = childAbout;
}
} }
prevChild = child;
child = nextChild;
textNodes = [];
} }
return aboutGroup;
} }
// Fallback to defaults // Fallback to defaults
annotations = annotations || new ve.AnnotationSet(); annotations = annotations || new ve.AnnotationSet();
path = path || ['document']; path = path || ['document'];
var i, j, childDomElement, annotation, childDataElement, text, childTypes, matches, var i, childDomElement, childDomElements, childDataElement, text, childTypes, matches,
wrappingParagraph, prevElement, alien, rdfaType, isLink, childAnnotations, wrappingParagraph, prevElement, childAnnotations, modelName, modelClass,
doc = domElement.ownerDocument, annotation, childIsContent, aboutGroup,
data = [], data = [],
branchType = path[path.length - 1], branchType = path[path.length - 1],
branchHasContent = this.nodeFactory.canNodeContainContent( branchType ), branchHasContent = this.nodeFactory.canNodeContainContent( branchType ),
originallyExpectingContent = branchHasContent || !annotations.isEmpty(), originallyExpectingContent = branchHasContent || !annotations.isEmpty(),
childIsContent,
nextWhitespace = '', nextWhitespace = '',
wrappedWhitespace = '', wrappedWhitespace = '',
wrappedWhitespaceIndex, wrappedWhitespaceIndex,
@ -403,101 +253,25 @@ ve.dm.Converter.prototype.getDataFromDomRecursion = function ( domElement, annot
if ( dataElement ) { if ( dataElement ) {
data.push( dataElement ); data.push( dataElement );
} }
// Do about grouping
// FIXME this assumes every about group is an alien
doAboutGrouping( domElement );
// Add contents // Add contents
for ( i = 0; i < domElement.childNodes.length; i++ ) { for ( i = 0; i < domElement.childNodes.length; i++ ) {
childDomElement = domElement.childNodes[i]; childDomElement = domElement.childNodes[i];
switch ( childDomElement.nodeType ) { switch ( childDomElement.nodeType ) {
case Node.ELEMENT_NODE: case Node.ELEMENT_NODE:
// Alienate about groups modelName = this.modelRegistry.matchElement( childDomElement );
if ( childDomElement.hasAttribute( 'data-ve-aboutgroup' ) ) { modelClass = this.modelRegistry.lookup( modelName ) || ve.dm.AlienNode;
alien = createAlien( childDomElement, context, true ); // HACK: force MetaNode for <meta>/<link> even if they have an mw: type
if ( context.inWrapper && alien[0].type === 'alienBlock' ) { // FIXME EWWWWWW find a better way to handle this
stopWrapping();
} else if (
!context.inWrapper && !context.expectingContent &&
alien[0].type === 'alienInline'
) {
startWrapping();
}
data = data.concat( alien );
processNextWhitespace( alien[0] );
prevElement = alien[0];
break;
}
// HACK handle <meta>/<link> separately because of the
// metaInline/metaBlock distinction
if ( if (
childDomElement.nodeName.toLowerCase() === 'meta' || ( childDomElement.nodeName.toLowerCase() === 'meta' || childDomElement.nodeName.toLowerCase() === 'link' ) &&
childDomElement.nodeName.toLowerCase() === 'link' ( modelClass.prototype instanceof ve.dm.AlienNode || modelClass === ve.dm.AlienNode )
) { ) {
isLink = childDomElement.nodeName.toLowerCase() === 'link'; modelClass = ve.dm.MetaNode;
childDataElement = {
'type': context.expectingContent ? 'metaInline' : 'metaBlock',
'attributes': {
'style': isLink ? 'link' : 'meta',
'key': childDomElement.getAttribute( isLink ? 'rel' : 'property' )
}
};
if ( childDomElement.hasAttribute( isLink ? 'href' : 'content' ) ) {
childDataElement.attributes.value = childDomElement.getAttribute( isLink ? 'href' : 'content' );
}
// Preserve HTML attributes
// FIXME the following is duplicated from getDataElementFromDomElement()
// Include all attributes and prepend 'html/0/' to each attribute name
for ( j = 0; j < childDomElement.attributes.length; j++ ) {
// ..but exclude attributes we've already processed,
// because they'll be overwritten otherwise *sigh*
// FIXME this sucks, we need a new node type API so bad
if (
childDomElement.attributes[j].name !== ( isLink ? 'rel' : 'property' ) &&
childDomElement.attributes[j].name !== ( isLink ? 'href' : 'content' )
) {
childDataElement.attributes['html/0/' + childDomElement.attributes[j].name] = childDomElement.attributes[j].value;
}
}
data.push( childDataElement );
data.push( { 'type': context.expectingContent ? '/metaInline' : '/metaBlock' } );
processNextWhitespace( childDataElement );
prevElement = childDataElement;
break;
}
// Alienate anything with a mw: type that isn't registered
// HACK because we don't actually have an RDFa type registry yet,
// this hardcodes the set of recognized types
rdfaType = childDomElement.getAttribute( 'rel' ) ||
childDomElement.getAttribute( 'typeof' ) ||
childDomElement.getAttribute( 'property' );
if (
rdfaType &&
rdfaType.match( /^mw:/ ) &&
!rdfaType.match( /^mw:WikiLink/ ) &&
!rdfaType.match( /^mw:ExtLink/ ) &&
!rdfaType.match( /^mw:Entity/ )
) {
alien = createAlien( childDomElement, context );
if ( context.inWrapper && alien[0].type === 'alienBlock' ) {
stopWrapping();
} else if (
!context.inWrapper && !context.expectingContent &&
alien[0].type === 'alienInline'
) {
startWrapping();
}
data = data.concat( alien );
processNextWhitespace( alien[0] );
prevElement = alien[0];
break;
} }
if ( modelClass.prototype instanceof ve.dm.Annotation ) {
annotation = this.annotationFactory.create( modelName, childDomElement );
// Detect and handle annotated content // Start wrapping if needed
// HACK except for mw:Entity. We need a node API rewrite, badly
annotation = this.getDataAnnotationFromDomElement( childDomElement );
if ( annotation && rdfaType !== 'mw:Entity' ) {
// Start auto-wrapping of bare content
if ( !context.inWrapper && !context.expectingContent ) { if ( !context.inWrapper && !context.expectingContent ) {
startWrapping(); startWrapping();
prevElement = wrappingParagraph; prevElement = wrappingParagraph;
@ -511,65 +285,62 @@ ve.dm.Converter.prototype.getDataFromDomRecursion = function ( domElement, annot
undefined, path, context.inWrapper undefined, path, context.inWrapper
) )
); );
break; } else {
} aboutGroup = getAboutGroup( childDomElement );
childDomElements = modelClass.static.enableAboutGrouping ?
// Look up child element type aboutGroup : [ childDomElement ];
childDataElement = this.getDataElementFromDomElement( childDomElement, annotations ); childDataElement = this.createDataElement( modelClass, childDomElements, context );
if ( childDataElement ) {
childIsContent = this.nodeFactory.isNodeContent( childDataElement.type ); childIsContent = this.nodeFactory.isNodeContent( childDataElement.type );
// Check that something isn't terribly wrong
if ( !( // If childIsContent isn't what we expect, adjust
// Non-content child in a content container if ( !context.expectingContent && childIsContent ) {
( originallyExpectingContent && !childIsContent ) || startWrapping();
// Non-content child trying to break wrapping at prevElement = wrappingParagraph;
// the wrong level } else if ( context.expectingContent && !childIsContent ) {
( context.inWrapper && !context.canCloseWrapper && !childIsContent ) if ( context.inWrapper && context.canCloseWrapper ) {
) ) {
// End auto-wrapping of bare content from a previously processed node
// but only if childDataElement is a non-content element
if ( context.inWrapper && context.canCloseWrapper && !childIsContent ) {
stopWrapping(); stopWrapping();
} else if ( !context.inWrapper && !context.expectingContent && childIsContent ) {
startWrapping();
prevElement = wrappingParagraph;
}
if ( this.nodeFactory.canNodeHaveChildren( childDataElement.type ) ) {
// Append child element data
data = data.concat(
this.getDataFromDomRecursion(
childDomElement,
new ve.AnnotationSet(),
childDataElement,
path.concat( childDataElement.type ),
context.inWrapper
)
);
} else { } else {
// Append empty node // Alienate
data.push( childDataElement ); modelClass = ve.dm.AlienNode;
data.push( { 'type': '/' + childDataElement.type } ); childDomElements = modelClass.static.enableAboutGrouping ?
aboutGroup : [ childDomElement ];
childDataElement = this.createDataElement( modelClass, childDomElements, context );
childIsContent = this.nodeFactory.isNodeContent( childDataElement.type );
} }
processNextWhitespace( childDataElement );
prevElement = childDataElement;
break;
} }
// If something is wrong, fall through, and the bad child
// will be alienated below. // Annotate child
if ( childIsContent && !annotations.isEmpty() ) {
childDataElement.annotations = annotations.clone();
}
// Output child and its children, if any
if (
childDomElements.length === 1 &&
this.nodeFactory.canNodeHaveChildren( childDataElement.type )
) {
// Recursion
// Opening and closing elements are added by the recursion too
data = data.concat(
this.getDataFromDomRecursion(
childDomElement,
new ve.AnnotationSet(),
childDataElement,
path.concat( childDataElement.type ),
context.inWrapper
)
);
} else {
// Write an opening and closing
data.push( childDataElement );
data.push( { 'type': '/' + childDataElement.type } );
}
processNextWhitespace( childDataElement );
prevElement = childDataElement;
// In case we consumed multiple childDomElements, adjust i accordingly
i += childDomElements.length - 1;
} }
// We don't know what this is, fall back to alien.
alien = createAlien( childDomElement, context );
if ( context.inWrapper && alien[0].type === 'alienBlock' ) {
stopWrapping();
} else if (
!context.inWrapper && !context.expectingContent &&
alien[0].type === 'alienInline'
) {
startWrapping();
}
data = data.concat( alien );
processNextWhitespace( alien[0] );
prevElement = alien[0];
break; break;
case Node.TEXT_NODE: case Node.TEXT_NODE:
text = childDomElement.data; text = childDomElement.data;
@ -700,6 +471,7 @@ ve.dm.Converter.prototype.getDataFromDomRecursion = function ( domElement, annot
); );
break; break;
case Node.COMMENT_NODE: case Node.COMMENT_NODE:
// TODO treat this as a node with nodeName #comment
childDataElement = { childDataElement = {
'type': context.expectingContent ? 'metaInline' : 'metaBlock', 'type': context.expectingContent ? 'metaInline' : 'metaBlock',
'attributes': { 'attributes': {
@ -761,7 +533,7 @@ ve.dm.Converter.prototype.getDataFromDomRecursion = function ( domElement, annot
*/ */
ve.dm.Converter.prototype.getDomFromData = function ( data ) { ve.dm.Converter.prototype.getDomFromData = function ( data ) {
var text, i, j, k, annotations, annotation, annotationElement, dataElement, arr, var text, i, j, k, annotations, annotation, annotationElement, dataElement, arr,
childDomElement, pre, ours, theirs, parentDomElement, startClosingAt, childDomElements, pre, ours, theirs, parentDomElement, lastChild, startClosingAt,
isContentNode, changed, parentChanged, isContentNode, changed, parentChanged,
doc = ve.createDocumentFromHTML( '' ), doc = ve.createDocumentFromHTML( '' ),
container = doc.body, container = doc.body,
@ -855,8 +627,11 @@ ve.dm.Converter.prototype.getDomFromData = function ( data ) {
domElement.appendChild( doc.createTextNode( text ) ); domElement.appendChild( doc.createTextNode( text ) );
text = ''; text = '';
} }
// Insert the element // Insert the elements
domElement.appendChild( this.getDomElementFromDataElement( data[i], doc ) ); childDomElements = this.getDomElementsFromDataElement( data[i], doc );
for ( j = 0; j < childDomElements.length; j++ ) {
domElement.appendChild( childDomElements[j] );
}
// Increment i once more so we skip over the closing as well // Increment i once more so we skip over the closing as well
i++; i++;
} }
@ -909,6 +684,11 @@ ve.dm.Converter.prototype.getDomFromData = function ( data ) {
); );
} }
} }
lastChild = domElement.veInternal.childDomElements ?
domElement.veInternal
.childDomElements[domElement.veInternal.childDomElements.length - 1]
.lastChild :
domElement.lastChild;
ours = domElement.veInternal.whitespace[2]; ours = domElement.veInternal.whitespace[2];
if ( domElement.lastOuterPost === undefined ) { if ( domElement.lastOuterPost === undefined ) {
// This node didn't have any structural children // This node didn't have any structural children
@ -919,10 +699,7 @@ ve.dm.Converter.prototype.getDomFromData = function ( data ) {
theirs = domElement.lastOuterPost; theirs = domElement.lastOuterPost;
} }
if ( ours && ours === theirs ) { if ( ours && ours === theirs ) {
if ( if ( lastChild && lastChild.nodeType === 3 ) {
domElement.lastChild &&
domElement.lastChild.nodeType === 3
) {
// Last child is a TextNode, append to it // Last child is a TextNode, append to it
domElement.lastChild.appendData( ours ); domElement.lastChild.appendData( ours );
} else { } else {
@ -991,16 +768,19 @@ ve.dm.Converter.prototype.getDomFromData = function ( data ) {
domElement = parentDomElement; domElement = parentDomElement;
} else { } else {
// Create node from data // Create node from data
childDomElement = this.getDomElementFromDataElement( dataElement, doc ); childDomElements = this.getDomElementsFromDataElement( dataElement, doc );
// Add reference to internal data // Add reference to internal data
if ( dataElement.internal ) { childDomElements[0].veInternal = ve.extendObject(
childDomElement.veInternal = dataElement.internal; { 'childDomElements': childDomElements },
dataElement.internal || {}
);
// Add elements
for ( j = 0; j < childDomElements.length; j++ ) {
domElement.appendChild( childDomElements[j] );
} }
// Add element // Descend into the first child node
domElement.appendChild( childDomElement );
// Descend into child node
parentDomElement = domElement; parentDomElement = domElement;
domElement = childDomElement; domElement = childDomElements[0];
// Process outer whitespace // Process outer whitespace
// Every piece of outer whitespace is duplicated somewhere: // Every piece of outer whitespace is duplicated somewhere:
@ -1057,11 +837,6 @@ ve.dm.Converter.prototype.getDomFromData = function ( data ) {
delete container.lastOuterPost; delete container.lastOuterPost;
} }
// Unwrap multi-child alien wrappers
$( container ).find( '[data-ve-multi-child-alien-wrapper]' ).each( function() {
$( this ).replaceWith( $( this ).contents() );
} );
// Workaround for bug 42469: if a <pre> starts with a newline, that means .innerHTML will // Workaround for bug 42469: if a <pre> starts with a newline, that means .innerHTML will
// screw up and stringify it with one fewer newline. Work around this by adding a newline. // screw up and stringify it with one fewer newline. Work around this by adding a newline.
// If we don't see a leading newline, we still don't know if the original HTML was // If we don't see a leading newline, we still don't know if the original HTML was
@ -1082,4 +857,4 @@ ve.dm.Converter.prototype.getDomFromData = function ( data ) {
/* Initialization */ /* Initialization */
ve.dm.converter = new ve.dm.Converter( ve.dm.nodeFactory, ve.dm.annotationFactory ); ve.dm.converter = new ve.dm.Converter( ve.dm.modelRegistry, ve.dm.nodeFactory, ve.dm.annotationFactory );

View file

@ -87,10 +87,14 @@ ve.dm.Node.static.matchRdfaTypes = null;
ve.dm.Node.static.matchFunction = null; ve.dm.Node.static.matchFunction = null;
/** /**
* Static function to convert a DOM element to a linear model data element for this node type. * Static function to convert a DOM element or set of sibling DOM elements to a linear model data
* element for this node type.
* *
* This function is only called if this node "won" the matching for the DOM element, so domElement * This function is only called if this node "won" the matching for the first DOM element, so
* will match this node's matching rule. * domElements[0] will match this node's matching rule. There is usually only one node in
* domElements[]. Multiple nodes will only be passed if this node supports about groups.
* If there are multiple nodes, the nodes are all adjacent siblings in the same about group
* (i.e. they are grouped together because they have the same value for the about attribute).
* *
* This function is allowed to return a content element when context indicates that a non-content * This function is allowed to return a content element when context indicates that a non-content
* element is expected or vice versa. If that happens, the converter deals with it in the following way: * element is expected or vice versa. If that happens, the converter deals with it in the following way:
@ -114,7 +118,7 @@ ve.dm.Node.static.matchFunction = null;
* *
* @static * @static
* @method * @method
* @param {HTMLElement} domElement DOM element to convert * @param {HTMLElement[]} domElements DOM elements to convert. Usually only one element
* @param {Object} context Object describing the current state of the converter * @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.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; * @param {boolean} context.inWrapper Whether this element is in a wrapper paragraph generated by the converter;
@ -123,20 +127,24 @@ ve.dm.Node.static.matchFunction = null;
* can only be true if context.inWrapper is also true * can only be true if context.inWrapper is also true
* @returns {Object|null} Linear model element, or null to alienate * @returns {Object|null} Linear model element, or null to alienate
*/ */
ve.dm.Node.static.toDataElement = function ( /*domElement, context*/ ) { ve.dm.Node.static.toDataElement = function ( /*domElements, context*/ ) {
throw new Error( 've.dm.Node subclass must implement toDataElement' ); throw new Error( 've.dm.Node subclass must implement toDataElement' );
}; };
/** /**
* Static function to convert a linear model data element for this node type back to a DOM element. * Static function to convert a linear model data element for this node type back to one or more
* DOM elements.
*
* NOTE: If this function returns multiple DOM elements, the DOM elements produced by the children
* of this node (if any) will be attached to the first DOM element in the array.
* *
* @static * @static
* @method * @method
* @param {Object} Linear model element with a type property and optionally an attributes property * @param {Object} Linear model element with a type property and optionally an attributes property
* @returns {HTMLElement} DOM element * @returns {HTMLElement[]} DOM elements
*/ */
ve.dm.Node.static.toDomElement = function ( /*dataElement*/ ) { ve.dm.Node.static.toDomElements = function ( /*dataElement*/ ) {
throw new Error( 've.dm.Node subclass must implement toDomElement' ); throw new Error( 've.dm.Node subclass must implement toDomElements' );
}; };
/** /**
@ -169,7 +177,8 @@ ve.dm.Node.static.enableAboutGrouping = false;
/** /**
* Whether HTML attributes should be preserved for this node type. If true, the HTML attributes * Whether HTML attributes should be preserved for this node type. If true, the HTML attributes
* of the DOM elements will be stored as linear model attributes. The attribute names be * of the DOM elements will be stored as linear model attributes. The attribute names be
* html/0/attrName, where attrName is the name of the attribute. * 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 node types that store their entire HTML in an * This should generally be enabled, except for node types that store their entire HTML in an
* attribute. * attribute.

View file

@ -9,6 +9,8 @@ QUnit.module( 've.dm.Converter' );
/* Tests */ /* Tests */
// TODO rewrite to test getDataElementOrAnnotationFromDomElement
/*
QUnit.test( 'getDataElementFromDomElement', 20, function ( assert ) { QUnit.test( 'getDataElementFromDomElement', 20, function ( assert ) {
var msg, conversion; var msg, conversion;
@ -21,14 +23,15 @@ QUnit.test( 'getDataElementFromDomElement', 20, function ( assert ) {
); );
} }
} ); } );
*/
QUnit.test( 'getDomElementFromDataElement', 20, function ( assert ) { QUnit.test( 'getDomElementsFromDataElement', 20, function ( assert ) {
var msg, conversion; var msg, conversion;
for ( msg in ve.dm.example.conversions ) { for ( msg in ve.dm.example.conversions ) {
conversion = ve.dm.example.conversions[msg]; conversion = ve.dm.example.conversions[msg];
assert.equalDomElement( assert.equalDomElement(
ve.dm.converter.getDomElementFromDataElement( conversion.dataElement ), ve.dm.converter.getDomElementsFromDataElement( conversion.dataElement )[0],
conversion.domElement, conversion.domElement,
msg msg
); );

View file

@ -81,7 +81,7 @@ ve.dm.StubBarNode.static.matchRdfaTypes = ['bar'];
// HACK keep ve.dm.Converter happy for now // HACK keep ve.dm.Converter happy for now
// TODO once ve.dm.Converter is rewritten, this can be removed // TODO once ve.dm.Converter is rewritten, this can be removed
ve.dm.StubBarNode.static.toDataElement = function () {}; ve.dm.StubBarNode.static.toDataElement = function () {};
ve.dm.StubBarNode.static.toDomElement = function () {}; ve.dm.StubBarNode.static.toDomElements = function () {};
/* Tests */ /* Tests */

View file

@ -302,7 +302,8 @@ ve.dm.example.withMeta = [
'type': 'metaBlock', 'type': 'metaBlock',
'attributes': { 'attributes': {
'style': 'meta', 'style': 'meta',
'key': 'mw:PageProp/nocc' 'key': 'mw:PageProp/nocc',
'html/0/property': 'mw:PageProp/nocc'
} }
}, },
{ 'type': '/metaBlock' }, { 'type': '/metaBlock' },
@ -315,7 +316,9 @@ ve.dm.example.withMeta = [
'attributes': { 'attributes': {
'style': 'link', 'style': 'link',
'key': 'mw:WikiLink/Category', 'key': 'mw:WikiLink/Category',
'value': './Category:Bar' 'value': './Category:Bar',
'html/0/href': './Category:Bar',
'html/0/rel': 'mw:WikiLink/Category'
} }
}, },
{ 'type': '/metaInline' }, { 'type': '/metaInline' },
@ -327,7 +330,9 @@ ve.dm.example.withMeta = [
'attributes': { 'attributes': {
'style': 'meta', 'style': 'meta',
'key': 'mw:foo', 'key': 'mw:foo',
'value': 'bar' 'value': 'bar',
'html/0/content': 'bar',
'html/0/property': 'mw:foo'
} }
}, },
{ 'type': '/metaInline' }, { 'type': '/metaInline' },
@ -348,7 +353,9 @@ ve.dm.example.withMeta = [
'attributes': { 'attributes': {
'style': 'meta', 'style': 'meta',
'key': 'mw:bar', 'key': 'mw:bar',
'value': 'baz' 'value': 'baz',
'html/0/content': 'baz',
'html/0/property': 'mw:bar'
} }
}, },
{ 'type': '/metaBlock' }, { 'type': '/metaBlock' },
@ -365,7 +372,9 @@ ve.dm.example.withMeta = [
'attributes': { 'attributes': {
'style': 'link', 'style': 'link',
'key': 'mw:WikiLink/Category', 'key': 'mw:WikiLink/Category',
'value': './Category:Foo#Bar baz%23quux' 'value': './Category:Foo#Bar baz%23quux',
'html/0/href': './Category:Foo#Bar baz%23quux',
'html/0/rel': 'mw:WikiLink/Category'
} }
}, },
{ 'type': '/metaBlock' }, { 'type': '/metaBlock' },
@ -408,7 +417,8 @@ ve.dm.example.withMetaMetaData = [
'type': 'metaBlock', 'type': 'metaBlock',
'attributes': { 'attributes': {
'style': 'meta', 'style': 'meta',
'key': 'mw:PageProp/nocc' 'key': 'mw:PageProp/nocc',
'html/0/property': 'mw:PageProp/nocc'
} }
} }
], ],
@ -421,7 +431,9 @@ ve.dm.example.withMetaMetaData = [
'attributes': { 'attributes': {
'style': 'link', 'style': 'link',
'key': 'mw:WikiLink/Category', 'key': 'mw:WikiLink/Category',
'value': './Category:Bar' 'value': './Category:Bar',
'html/0/href': './Category:Bar',
'html/0/rel': 'mw:WikiLink/Category'
} }
} }
], ],
@ -433,7 +445,9 @@ ve.dm.example.withMetaMetaData = [
'attributes': { 'attributes': {
'style': 'meta', 'style': 'meta',
'key': 'mw:foo', 'key': 'mw:foo',
'value': 'bar' 'value': 'bar',
'html/0/content': 'bar',
'html/0/property': 'mw:foo'
} }
} }
], ],
@ -454,7 +468,9 @@ ve.dm.example.withMetaMetaData = [
'attributes': { 'attributes': {
'style': 'meta', 'style': 'meta',
'key': 'mw:bar', 'key': 'mw:bar',
'value': 'baz' 'value': 'baz',
'html/0/content': 'baz',
'html/0/property': 'mw:bar'
} }
}, },
{ {
@ -469,7 +485,9 @@ ve.dm.example.withMetaMetaData = [
'attributes': { 'attributes': {
'style': 'link', 'style': 'link',
'key': 'mw:WikiLink/Category', 'key': 'mw:WikiLink/Category',
'value': './Category:Foo#Bar baz%23quux' 'value': './Category:Foo#Bar baz%23quux',
'html/0/href': './Category:Foo#Bar baz%23quux',
'html/0/rel': 'mw:WikiLink/Category'
} }
}, },
{ {
@ -478,7 +496,7 @@ ve.dm.example.withMetaMetaData = [
'style': 'meta', 'style': 'meta',
'key': null, 'key': null,
'html/0/typeof': 'mw:Placeholder', 'html/0/typeof': 'mw:Placeholder',
'html/0/data-parsoid': 'foobar', 'html/0/data-parsoid': 'foobar'
} }
} }
] ]
@ -865,13 +883,11 @@ ve.dm.example.domToDataCases = {
'data': [ 'data': [
{ 'type': 'paragraph', 'internal': { 'generated': 'wrapper' } }, { 'type': 'paragraph', 'internal': { 'generated': 'wrapper' } },
'1', '1',
{ 'type': '/paragraph' },
{ {
'type': 'alienBlock', 'type': 'alienInline',
'attributes': { 'html': '<tt about="#mwt1">foo</tt><tt about="#mwt1">bar</tt>' } 'attributes': { 'html': '<tt about="#mwt1">foo</tt><tt about="#mwt1">bar</tt>' }
}, },
{ 'type': '/alienBlock' }, { 'type': '/alienInline' },
{ 'type': 'paragraph', 'internal': { 'generated': 'wrapper' } },
'2', '2',
{ 'type': '/paragraph' } { 'type': '/paragraph' }
] ]
@ -1973,7 +1989,9 @@ ve.dm.example.domToDataCases = {
'attributes': { 'attributes': {
'style': 'meta', 'style': 'meta',
'key': 'mw:foo', 'key': 'mw:foo',
'value': 'bar' 'value': 'bar',
'html/0/content': 'bar',
'html/0/property': 'mw:foo'
} }
}, },
{ 'type': '/metaInline' }, { 'type': '/metaInline' },
@ -2075,4 +2093,4 @@ ve.dm.example.isolationData = [
{ 'type': '/preformatted' }, { 'type': '/preformatted' },
{ 'type': '/listItem' }, { 'type': '/listItem' },
{ 'type': '/list' } { 'type': '/list' }
]; ];