Handle annotated inline nodes in the converter

Was broken both on the way in and on the way out.

* Move alien restoration (data->DOM) out of the main getDomFromData()
  function and into getDomElementFromDataElement(). This means the
  comment about District 9 is gone (sniff), but moving this here ensures
  all code paths hit it (previously, it was assumed annotated nodes
  could never be aliens).
* In the DOM->data converter, add annotation application to
  getDataElementFromDomElement() (for content nodes) and createAlien()
  (for aliens). Previously, these nodes would not get annotations.
** ve.AnnotationSet doesn't have a constructor that takes an array, we
   should fix that.

Change-Id: I65f8e9a322111ca3af275bf9997b0b1e7ee93769
This commit is contained in:
Catrope 2012-11-23 18:44:54 -08:00 committed by Trevor Parscal
parent 5e2c421b77
commit e123a39b4e
4 changed files with 82 additions and 38 deletions

View file

@ -1,3 +1,3 @@
<p>£1 <span typeof="mw:Entity">£</span>2 <span typeof="mw:Entity">£</span>3 <span typeof="mw:Entity">£</span>4 <span typeof="mw:Entity">£</span>5</p>
<p><b>£1 <span typeof="mw:Entity">£</span>2 <span typeof="mw:Entity">£</span>3 <span typeof="mw:Entity">£</span>4 <span typeof="mw:Entity">£</span>5</b></p>
<p>€1 <span typeof="mw:Entity"></span>2 <span typeof="mw:Entity"></span>3 <span typeof="mw:Entity"></span>4 <span typeof="mw:Entity"></span>5 <span typeof="mw:Entity"></span>6 <span typeof="mw:Entity"></span>7</p>

View file

@ -92,24 +92,38 @@ ve.dm.Converter.prototype.onNodeRegister = function ( dataElementType, construct
* Get the DOM element for a given linear model element.
*
* This invokes the toDomElement function registered for the element type.
* NOTE: alienBlock and alienInline elements are not supported, if you pass them this function
* will return false. (Opposite of District 9: no aliens allowed.)
*
* @method
* @param {Object} dataElement Linear model element
* @returns {HTMLElement|false} DOM element, or false if this element cannot be converted
*/
ve.dm.Converter.prototype.getDomElementFromDataElement = function ( dataElement ) {
var key, domElement, dataElementAttributes,
var key, domElement, dataElementAttributes, wrapper,
dataElementType = dataElement.type;
if (
// Aliens
dataElementType === 'alienInline' || dataElementType === 'alienBlock' ||
// Unsupported elements
!( dataElementType in this.elements.toDomElement)
) {
if ( dataElementType === 'alienInline' || dataElementType === 'alienBlock' ) {
// Alien
// Create nodes from source
wrapper = document.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 = document.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 ) ) {
// Unsupported element
return false;
}
domElement = this.elements.toDomElement[dataElementType]( dataElementType, dataElement );
dataElementAttributes = dataElement.attributes;
if ( dataElementAttributes ) {
@ -140,11 +154,13 @@ ve.dm.Converter.prototype.getDomElementFromDataElement = function ( dataElement
*
* @method
* @param {HTMLElement} domElement DOM element
* @param {Array} annotations Annotations to apply if the node is a content node
* @returns {Object|false} Linear model element, or false if this node cannot be converted
*/
ve.dm.Converter.prototype.getDataElementFromDomElement = function ( domElement ) {
ve.dm.Converter.prototype.getDataElementFromDomElement = function ( domElement, annotations ) {
var dataElement, domElementAttributes, dataElementAttributes, domElementAttribute, i,
domElementType = domElement.nodeName.toLowerCase();
annotationSet, domElementType = domElement.nodeName.toLowerCase();
annotations = annotations || [];
if (
// Unsupported elements
!( domElementType in this.elements.toDataElement )
@ -162,6 +178,14 @@ ve.dm.Converter.prototype.getDataElementFromDomElement = function ( domElement )
dataElementAttributes['html/' + domElementAttribute.name] = domElementAttribute.value;
}
}
if ( ve.dm.nodeFactory.isNodeContent( dataElement.type ) && annotations.length > 0 ) {
// Build annotation set
annotationSet = new ve.AnnotationSet();
for ( i = 0; i < annotations.length; i++ ) {
annotationSet.push( annotations[i] );
}
dataElement.annotations = annotationSet;
}
return dataElement;
};
@ -211,13 +235,14 @@ ve.dm.Converter.prototype.getDataFromDom = function ( domElement, annotations, d
// If we're in wrapping mode, we don't know if this alien is supposed to be block
// or inline, so detect it based on the HTML tag name.
var isInline = wrapping ? !ve.isBlockElement( domElement ) : branchIsContent,
type = isInline ? 'alienInline' : 'alienBlock', html;
type = isInline ? 'alienInline' : 'alienBlock',
html, alien, annotationSet, i;
if ( isWrapper ) {
html = $( domElement ).html();
} else {
html = $( '<div>' ).append( $( domElement ).clone() ).html();
}
return [
alien = [
{
'type': type,
'attributes': {
@ -226,6 +251,14 @@ ve.dm.Converter.prototype.getDataFromDom = function ( domElement, annotations, d
},
{ 'type': '/' + type }
];
if ( annotations.length > 0 ) {
annotationSet = new ve.AnnotationSet();
for ( i = 0; i < annotations.length; i++ ) {
annotationSet.push( annotations[i] );
}
alien[0].annotations = annotationSet;
}
return alien;
}
function addWhitespace( element, index, whitespace ) {
if ( !element.internal ) {
@ -446,7 +479,7 @@ ve.dm.Converter.prototype.getDataFromDom = function ( domElement, annotations, d
}
// Look up child element type
childDataElement = this.getDataElementFromDomElement( childDomElement );
childDataElement = this.getDataElementFromDomElement( childDomElement, annotations );
if ( childDataElement ) {
// End auto-wrapping of bare content from a previously processed node
// but only if childDataElement is a non-content element
@ -681,11 +714,12 @@ ve.dm.Converter.prototype.getDataFromDom = function ( domElement, annotations, d
*/
ve.dm.Converter.prototype.getDomFromData = function ( data ) {
var text, i, j, k, annotations, annotation, annotationElement, dataElement, arr,
wrapper, childDomElement, pre, ours, theirs, parentDomElement, startClosingAt,
childDomElement, pre, ours, theirs, parentDomElement, startClosingAt,
isContentNode, changed, parentChanged,
container = document.createElement( 'div' ),
domElement = container,
annotationStack = new ve.AnnotationSet();
for ( i = 0; i < data.length; i++ ) {
if ( typeof data[i] === 'string' ) {
// Text
@ -908,26 +942,8 @@ ve.dm.Converter.prototype.getDomFromData = function ( data ) {
// Ascend to parent node
domElement = parentDomElement;
} else {
if ( dataElement.type === 'alienBlock' || dataElement.type === 'alienInline' ) {
// Create nodes from source
wrapper = document.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 this function.
childDomElement = document.createElement( 'div' );
childDomElement.setAttribute( 'data-ve-multi-child-alien-wrapper', 'true' );
while ( wrapper.firstChild ) {
childDomElement.appendChild( wrapper.firstChild );
}
} else {
childDomElement = wrapper.firstChild;
}
} else {
// Create node from data
childDomElement = this.getDomElementFromDataElement( dataElement );
}
// Create node from data
childDomElement = this.getDomElementFromDataElement( dataElement );
// Add reference to internal data
if ( dataElement.internal ) {
childDomElement.veInternal = dataElement.internal;

View file

@ -35,7 +35,7 @@ QUnit.test( 'getDomElementFromDataElement', 20, function ( assert ) {
}
} );
QUnit.test( 'getDataFromDom', 41, function ( assert ) {
QUnit.test( 'getDataFromDom', 42, function ( assert ) {
var msg,
cases = ve.dm.example.domToDataCases;
@ -51,7 +51,7 @@ QUnit.test( 'getDataFromDom', 41, function ( assert ) {
}
} );
QUnit.test( 'getDomFromData', 45, function ( assert ) {
QUnit.test( 'getDomFromData', 46, function ( assert ) {
var msg,
cases = ve.dm.example.domToDataCases;

View file

@ -699,6 +699,34 @@ ve.dm.example.domToDataCases = {
{ 'type': '/paragraph' }
]
},
'annotated inline nodes': {
'html': '<p>a<b><tt class="foo">b</tt><i><span typeof="mw:Entity">c</span></i></b>' +
'<i><br/>d</i>e</p>',
'data': [
{ 'type': 'paragraph' },
'a',
{
'type': 'alienInline',
'attributes': { 'html': '<tt class="foo">b</tt>' },
'annotations': [ ve.dm.example.bold ]
},
{ 'type': '/alienInline' },
{
'type': 'MWentity',
'attributes': { 'character': 'c', 'html/typeof': 'mw:Entity' },
'annotations': [ ve.dm.example.bold, ve.dm.example.italic ]
},
{ 'type': '/MWentity' },
{
'type': 'break',
'annotations': [ ve.dm.example.italic ]
},
{ 'type': '/break' },
['d', [ ve.dm.example.italic ]],
'e',
{ 'type': '/paragraph' }
]
},
'wrapping of bare content': {
'html': 'abc',
'data': [