mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/VisualEditor
synced 2024-11-24 22:35:41 +00:00
Store inner whitespace of the body and compare it on conversion
Calculate and store the two inner whitespace values of the body in the dm.Document. When converting back, make sure the first/last nodes pre/post outer whitespace matches the inner left/right whitespace of the body. Bug: 54964 Change-Id: I45f1ffd63669f25a6cae878400bfe21719ed58ee
This commit is contained in:
parent
91f6b1d86c
commit
3838498e8b
|
@ -736,11 +736,11 @@ ve.init.mw.ViewPageTarget.prototype.onSaveDialogReview = function () {
|
|||
if ( this.pageExists ) {
|
||||
// Has no callback, handled via target.onShowChanges
|
||||
this.showChanges(
|
||||
ve.dm.converter.getDomFromData( doc.getFullData(), doc.getStore(), doc.getInternalList() )
|
||||
ve.dm.converter.getDomFromData( doc.getFullData(), doc.getStore(), doc.getInternalList(), doc.getInnerWhitespace() )
|
||||
);
|
||||
} else {
|
||||
this.serialize(
|
||||
ve.dm.converter.getDomFromData( doc.getFullData(), doc.getStore(), doc.getInternalList() ),
|
||||
ve.dm.converter.getDomFromData( doc.getFullData(), doc.getStore(), doc.getInternalList(), doc.getInnerWhitespace() ),
|
||||
ve.bind( this.onSerialize, this )
|
||||
);
|
||||
}
|
||||
|
@ -786,7 +786,7 @@ ve.init.mw.ViewPageTarget.prototype.saveDocument = function () {
|
|||
this.saveDialog.saveButton.setDisabled( true );
|
||||
this.saveDialog.$loadingIcon.show();
|
||||
this.save(
|
||||
ve.dm.converter.getDomFromData( doc.getFullData(), doc.getStore(), doc.getInternalList() ),
|
||||
ve.dm.converter.getDomFromData( doc.getFullData(), doc.getStore(), doc.getInternalList(), doc.getInnerWhitespace() ),
|
||||
saveOptions
|
||||
);
|
||||
}
|
||||
|
@ -856,10 +856,11 @@ ve.init.mw.ViewPageTarget.prototype.setUpSurface = function ( doc, callback ) {
|
|||
// Build linmod
|
||||
var store = new ve.dm.IndexValueStore(),
|
||||
internalList = new ve.dm.InternalList(),
|
||||
data = ve.dm.converter.getDataFromDom( doc, store, internalList );
|
||||
innerWhitespace = new Array( 2 ),
|
||||
data = ve.dm.converter.getDataFromDom( doc, store, internalList, innerWhitespace );
|
||||
setTimeout( function () {
|
||||
// Build DM tree
|
||||
var dmDoc = new ve.dm.Document( data, doc, undefined, internalList );
|
||||
var dmDoc = new ve.dm.Document( data, doc, undefined, internalList, innerWhitespace );
|
||||
setTimeout( function () {
|
||||
// Create ui.Surface (also creates ce.Surface and dm.Surface and builds CE tree)
|
||||
target.surface = new ve.ui.Surface( dmDoc, target.surfaceOptions );
|
||||
|
@ -929,8 +930,8 @@ ve.init.mw.ViewPageTarget.prototype.startSanityCheck = function () {
|
|||
// <body> were ignored in the conversion. So compare each child separately.
|
||||
var i,
|
||||
len = oldDom.body.childNodes.length,
|
||||
newDoc = new ve.dm.Document( data, oldDom, undefined, doc.getInternalList() ),
|
||||
newDom = ve.dm.converter.getDomFromData( newDoc.getFullData(), newDoc.getStore(), newDoc.getInternalList() );
|
||||
newDoc = new ve.dm.Document( data, oldDom, undefined, doc.getInternalList(), doc.getInnerWhitespace() ),
|
||||
newDom = ve.dm.converter.getDomFromData( newDoc.getFullData(), newDoc.getStore(), newDoc.getInternalList(), newDoc.getInnerWhitespace() );
|
||||
|
||||
// Explicitly unlink our full copy of the original version of the document data
|
||||
data = undefined;
|
||||
|
|
|
@ -385,9 +385,10 @@ ve.dm.Converter.prototype.getDomElementFromDataAnnotation = function ( dataAnnot
|
|||
* @param {HTMLDocument} doc HTML document to convert
|
||||
* @param {ve.dm.IndexValueStore} store Index-value store
|
||||
* @param {ve.dm.InternalList} internalList Internal list
|
||||
* @param {Array} innerWhitespace Inner whitespace
|
||||
* @returns {ve.dm.FlatLinearData} Linear model data
|
||||
*/
|
||||
ve.dm.Converter.prototype.getDataFromDom = function ( doc, store, internalList ) {
|
||||
ve.dm.Converter.prototype.getDataFromDom = function ( doc, store, internalList, innerWhitespace ) {
|
||||
var linearData, refData;
|
||||
|
||||
// Set up the converter state
|
||||
|
@ -404,6 +405,8 @@ ve.dm.Converter.prototype.getDataFromDom = function ( doc, store, internalList )
|
|||
refData = this.internalList.convertToData( this, doc );
|
||||
linearData.batchSplice( linearData.getLength(), 0, refData );
|
||||
|
||||
this.setInnerWhitespace( innerWhitespace, linearData );
|
||||
|
||||
// Clear the state
|
||||
this.doc = null;
|
||||
this.store = null;
|
||||
|
@ -976,6 +979,39 @@ ve.dm.Converter.prototype.getDataFromDomRecursion = function ( domElement, wrapp
|
|||
return data;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set inner whitespace from linear data
|
||||
*
|
||||
* @param {Array} innerWhitespace Inner whitespace
|
||||
* @param {ve.dm.FlatLinearData} data Linear model data
|
||||
*/
|
||||
ve.dm.Converter.prototype.setInnerWhitespace = function ( innerWhitespace, data ) {
|
||||
var whitespace,
|
||||
stack = 0,
|
||||
last = data.getLength() - 1;
|
||||
|
||||
if ( data.isOpenElementData( 0 ) ) {
|
||||
whitespace = ve.getProp( data.getData( 0 ), 'internal', 'whitespace' );
|
||||
innerWhitespace[0] = whitespace ? whitespace[0] : undefined;
|
||||
}
|
||||
if ( data.isCloseElementData( last ) ) {
|
||||
// Find matching opening tag of the last close tag
|
||||
stack++;
|
||||
while ( --last ) {
|
||||
if ( data.isCloseElementData( last ) ) {
|
||||
stack++;
|
||||
} else if ( data.isOpenElementData( last ) ) {
|
||||
stack--;
|
||||
if ( stack === 0 && data.getType( last ) !== 'internalList' ) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
whitespace = ve.getProp( data.getData( last ), 'internal', 'whitespace' );
|
||||
innerWhitespace[1] = whitespace ? whitespace[3] : undefined;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if all the domElements provided are metadata or whitespace.
|
||||
*
|
||||
|
@ -1031,9 +1067,10 @@ ve.dm.Converter.prototype.isDomAllMetaOrWhitespace = function ( domElements, exc
|
|||
* @param {Array} documentData Linear model data
|
||||
* @param {ve.dm.IndexValueStore} store Index-value store
|
||||
* @param {ve.dm.InternalList} internalList Internal list
|
||||
* @param {Array} innerWhitespace Inner whitespace
|
||||
* @returns {HTMLDocument} Document containing the resulting HTML
|
||||
*/
|
||||
ve.dm.Converter.prototype.getDomFromData = function ( documentData, store, internalList ) {
|
||||
ve.dm.Converter.prototype.getDomFromData = function ( documentData, store, internalList, innerWhitespace ) {
|
||||
var doc = ve.createDocumentFromHtml( '' );
|
||||
|
||||
// Set up the converter state
|
||||
|
@ -1041,7 +1078,7 @@ ve.dm.Converter.prototype.getDomFromData = function ( documentData, store, inter
|
|||
this.store = store;
|
||||
this.internalList = internalList;
|
||||
|
||||
this.getDomSubtreeFromData( documentData, doc.body );
|
||||
this.getDomSubtreeFromData( documentData, doc.body, innerWhitespace );
|
||||
|
||||
// Clear the state
|
||||
this.documentData = null;
|
||||
|
@ -1056,9 +1093,10 @@ ve.dm.Converter.prototype.getDomFromData = function ( documentData, store, inter
|
|||
*
|
||||
* @param {Array} data Linear model data
|
||||
* @param {HTMLElement} container DOM element to add the generated elements to. Should be empty.
|
||||
* @param {Array} [innerWhitespace] Inner whitespace if the container is the body
|
||||
* @throws Unbalanced data: looking for closing /type
|
||||
*/
|
||||
ve.dm.Converter.prototype.getDomSubtreeFromData = function ( data, container ) {
|
||||
ve.dm.Converter.prototype.getDomSubtreeFromData = function ( data, container, innerWhitespace ) {
|
||||
var text, i, j, isStart, annotations, dataElement, dataElementOrSlice,
|
||||
childDomElements, pre, ours, theirs, parentDomElement, lastChild, isContentNode, sibling,
|
||||
previousSiblings, doUnwrap, textNode, type, annotatedDomElementStack, annotatedDomElements,
|
||||
|
@ -1440,9 +1478,8 @@ ve.dm.Converter.prototype.getDomSubtreeFromData = function ( data, container ) {
|
|||
// Get previous sibling's outerPost
|
||||
theirs = parentDomElement.lastOuterPost;
|
||||
} else if ( parentDomElement === container ) {
|
||||
// outerPre of the very first node in the document, this one
|
||||
// has no duplicate
|
||||
theirs = ours;
|
||||
// outerPre of the very first node in the document, check against body innerWhitespace
|
||||
theirs = innerWhitespace ? innerWhitespace[0] : ours;
|
||||
} else {
|
||||
// First child, get parent's innerPre
|
||||
if (
|
||||
|
@ -1473,8 +1510,11 @@ ve.dm.Converter.prototype.getDomSubtreeFromData = function ( data, container ) {
|
|||
}
|
||||
}
|
||||
}
|
||||
// Process the outerPost whitespace of the very last node
|
||||
if ( container.lastOuterPost !== undefined ) {
|
||||
// Check outerPost whitespace of the very last node against body innerWhitespace
|
||||
if (
|
||||
container.lastOuterPost !== undefined &&
|
||||
( !innerWhitespace || container.lastOuterPost === innerWhitespace[1] )
|
||||
) {
|
||||
if ( container.lastChild && container.lastChild.nodeType === Node.TEXT_NODE ) {
|
||||
// Last child is a TextNode, append to it
|
||||
container.lastChild.appendData( container.lastOuterPost );
|
||||
|
|
|
@ -23,8 +23,9 @@
|
|||
* ignored.
|
||||
* @param {ve.dm.Document} [parentDocument] Document to use as root for created nodes
|
||||
* @param {ve.dm.InternalList} [internalList] Internal list to clone; passed when creating a document slice
|
||||
* @param {Array} [innerWhitespace] Inner whitespace to clone; passed when creating a document slice
|
||||
*/
|
||||
ve.dm.Document = function VeDmDocument( data, htmlDocument, parentDocument, internalList ) {
|
||||
ve.dm.Document = function VeDmDocument( data, htmlDocument, parentDocument, internalList, innerWhitespace ) {
|
||||
// Parent constructor
|
||||
ve.Document.call( this, new ve.dm.DocumentNode() );
|
||||
|
||||
|
@ -37,6 +38,7 @@ ve.dm.Document = function VeDmDocument( data, htmlDocument, parentDocument, inte
|
|||
this.documentNode.setRoot( root );
|
||||
this.documentNode.setDocument( doc );
|
||||
this.internalList = internalList ? internalList.clone( this ) : new ve.dm.InternalList( this );
|
||||
this.innerWhitespace = innerWhitespace ? ve.copy( innerWhitespace ) : new Array( 2 );
|
||||
|
||||
// Properties
|
||||
this.parentDocument = parentDocument;
|
||||
|
@ -51,7 +53,7 @@ ve.dm.Document = function VeDmDocument( data, htmlDocument, parentDocument, inte
|
|||
fullData = data;
|
||||
} else if ( !ve.isArray( data ) && typeof data === 'object' ) {
|
||||
// HTMLDocument
|
||||
fullData = ve.dm.converter.getDataFromDom( data, new ve.dm.IndexValueStore(), this.getInternalList() );
|
||||
fullData = ve.dm.converter.getDataFromDom( data, new ve.dm.IndexValueStore(), this.getInternalList(), this.getInnerWhitespace() );
|
||||
htmlDocument = data;
|
||||
} else {
|
||||
// Raw linear model data
|
||||
|
@ -352,6 +354,14 @@ ve.dm.Document.prototype.getInternalList = function () {
|
|||
return this.internalList;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the document's inner whitespace
|
||||
* @returns {Array} The document's inner whitespace
|
||||
*/
|
||||
ve.dm.Document.prototype.getInnerWhitespace = function () {
|
||||
return this.innerWhitespace;
|
||||
};
|
||||
|
||||
/**
|
||||
* Clone a sub-document from a range in this document. The new document's store and internal list will be
|
||||
* clones of the ones in this document.
|
||||
|
|
|
@ -1787,7 +1787,8 @@ ve.dm.example.domToDataCases = {
|
|||
{ 'type': '/list' },
|
||||
{ 'type': 'internalList' },
|
||||
{ 'type': '/internalList' }
|
||||
]
|
||||
],
|
||||
'innerWhitespace': [ '\n', '\t\n\t\n' ]
|
||||
},
|
||||
'outer whitespace preservation in a list with bare text and a sublist': {
|
||||
'body': '<ul>\n<li>\n\nBa re\n\n\n<ul>\n\n\n\n<li> <p> P </p> </li>\t</ul>\t\t</li>\t\t\t</ul>',
|
||||
|
@ -1855,7 +1856,8 @@ ve.dm.example.domToDataCases = {
|
|||
{ 'type': '/paragraph' },
|
||||
{ 'type': 'internalList' },
|
||||
{ 'type': '/internalList' }
|
||||
]
|
||||
],
|
||||
'innerWhitespace': [ undefined, ' ' ]
|
||||
},
|
||||
'whitespace preservation with non-edge content whitespace with nested annotations': {
|
||||
'body': '<p> A B <b> C\t<i>\t\tD\t\t\t</i>\t\t\t\tE\n</b>\n\nF\n\n\n</p>',
|
||||
|
@ -2026,7 +2028,8 @@ ve.dm.example.domToDataCases = {
|
|||
{ 'type': '/alienBlock' },
|
||||
{ 'type': 'internalList' },
|
||||
{ 'type': '/internalList' }
|
||||
]
|
||||
],
|
||||
'innerWhitespace': [ ' ', ' \n ' ]
|
||||
},
|
||||
'whitespace preservation not triggered inside <pre>': {
|
||||
'body': '\n<pre>\n\n\nFoo\n\n\nBar\n\n\n\n</pre>\n\n\n\n\n',
|
||||
|
@ -2050,7 +2053,8 @@ ve.dm.example.domToDataCases = {
|
|||
{ 'type': '/preformatted' },
|
||||
{ 'type': 'internalList' },
|
||||
{ 'type': '/internalList' }
|
||||
]
|
||||
],
|
||||
'innerWhitespace': [ '\n', '\n\n\n\n\n' ]
|
||||
},
|
||||
'whitespace preservation in table cell starting with text and ending with annotation': {
|
||||
'body': '<table><tbody><tr><td>Foo <b>Bar</b></td></tr></tbody></table>',
|
||||
|
@ -2343,7 +2347,8 @@ ve.dm.example.domToDataCases = {
|
|||
{ 'type': 'internalList' },
|
||||
{ 'type': '/internalList' }
|
||||
],
|
||||
'normalizedBody': ' <ul><li><p>\tA\n</p> <p>B</p></li></ul> '
|
||||
'innerWhitespace': [ '\t', '\n' ],
|
||||
'normalizedBody': '<ul><li><p>\tA\n</p> <p>B</p></li></ul>'
|
||||
},
|
||||
'order of nested annotations is preserved': {
|
||||
'body': '<p><b><u><i>Foo</i></u></b></p>',
|
||||
|
@ -2556,7 +2561,8 @@ ve.dm.example.domToDataCases = {
|
|||
{ 'type': '/alienBlock' },
|
||||
{ 'type': 'internalList' },
|
||||
{ 'type': '/internalList' }
|
||||
]
|
||||
],
|
||||
'innerWhitespace': [ ' ', ' ' ]
|
||||
},
|
||||
'block node inside annotation node is alienated': {
|
||||
'body': '<span>\n<p>Bar</p></span>',
|
||||
|
|
|
@ -48,7 +48,7 @@ ve.test.utils.runFormatConverterTest = function ( assert, range, type, attribute
|
|||
};
|
||||
|
||||
ve.test.utils.runGetDataFromDomTests = function( assert, cases ) {
|
||||
var msg, doc, store, internalList, i, length, hash, data, html, n = 0;
|
||||
var msg, doc, store, i, length, hash, data, html, n = 0;
|
||||
|
||||
// TODO: this is a hack to make normal heading/preformatted
|
||||
// nodes the most recently registered, instead of the MW versions
|
||||
|
@ -57,7 +57,7 @@ ve.test.utils.runGetDataFromDomTests = function( assert, cases ) {
|
|||
|
||||
for ( msg in cases ) {
|
||||
if ( cases[msg].head !== undefined || cases[msg].body !== undefined ) {
|
||||
n++;
|
||||
n += 2;
|
||||
if ( cases[msg].storeItems ) {
|
||||
n += cases[msg].storeItems.length;
|
||||
}
|
||||
|
@ -69,14 +69,14 @@ ve.test.utils.runGetDataFromDomTests = function( assert, cases ) {
|
|||
if ( cases[msg].head !== undefined || cases[msg].body !== undefined ) {
|
||||
doc = new ve.dm.Document( [] );
|
||||
store = doc.getStore();
|
||||
internalList = doc.getInternalList();
|
||||
|
||||
html = '<head>' + ( cases[msg].head || '' ) + '</head><body>' + cases[msg].body + '</body>';
|
||||
data = ve.dm.converter.getDataFromDom(
|
||||
ve.createDocumentFromHtml( html ), store, internalList
|
||||
).getData();
|
||||
ve.createDocumentFromHtml( html ), store, doc.getInternalList(), doc.getInnerWhitespace()
|
||||
);
|
||||
ve.dm.example.preprocessAnnotations( cases[msg].data, store );
|
||||
assert.deepEqualWithDomElements( data, cases[msg].data, msg );
|
||||
assert.deepEqualWithDomElements( data.getData(), cases[msg].data, msg + ': data' );
|
||||
assert.deepEqual( doc.getInnerWhitespace(), cases[msg].innerWhitespace || new Array( 2 ), msg + ': inner whitespace' );
|
||||
// check storeItems have been added to store
|
||||
if ( cases[msg].storeItems ) {
|
||||
for ( i = 0, length = cases[msg].storeItems.length; i < length; i++ ) {
|
||||
|
@ -112,10 +112,11 @@ ve.test.utils.runGetDomFromDataTests = function( assert, cases ) {
|
|||
cases[msg].modify( cases[msg].data );
|
||||
}
|
||||
doc = new ve.dm.Document( ve.dm.example.preprocessAnnotations( cases[msg].data, store ) );
|
||||
doc.innerWhitespace = cases[msg].innerWhitespace ? ve.copy( cases[msg].innerWhitespace ) : new Array( 2 );
|
||||
originalData = ve.copy( doc.getFullData() );
|
||||
html = '<body>' + ( cases[msg].normalizedBody || cases[msg].body ) + '</body>';
|
||||
assert.equalDomElement(
|
||||
ve.dm.converter.getDomFromData( doc.getFullData(), doc.getStore(), doc.getInternalList() ),
|
||||
ve.dm.converter.getDomFromData( doc.getFullData(), doc.getStore(), doc.getInternalList(), doc.getInnerWhitespace() ),
|
||||
ve.createDocumentFromHtml( html ),
|
||||
msg
|
||||
);
|
||||
|
|
Loading…
Reference in a new issue