Catrope has uploaded a new change for review.
https://gerrit.wikimedia.org/r/58642
Change subject: Make getDataFromDomRecursion() use a context stack to pass
context info
......................................................................
Make getDataFromDomRecursion() use a context stack to pass context info
This will allow toDataElement() functions to just call this function
with a DOM element, rather than having to have all the recursion context
data to pass in.
Also expose this information using getters.
Change-Id: I89574c42385267e08704f018c0892d63014376a6
---
M modules/ve/dm/ve.dm.Converter.js
1 file changed, 144 insertions(+), 63 deletions(-)
git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/VisualEditor
refs/changes/42/58642/1
diff --git a/modules/ve/dm/ve.dm.Converter.js b/modules/ve/dm/ve.dm.Converter.js
index 8b23684..b8d28e5 100644
--- a/modules/ve/dm/ve.dm.Converter.js
+++ b/modules/ve/dm/ve.dm.Converter.js
@@ -22,6 +22,9 @@
this.nodeFactory = nodeFactory;
this.annotationFactory = annotationFactory;
this.metaItemFactory = metaItemFactory;
+ this.doc = null;
+ this.store = null;
+ this.contextStack = null;
};
/* Static Methods */
@@ -95,6 +98,78 @@
};
/* Methods */
+
+/**
+ * Check whether this converter instance is currently inside a
getDataFromDom() conversion.
+ * @returns {Boolean} Whether we're converting
+ */
+ve.dm.Converter.prototype.isConverting = function () {
+ return this.contextStack !== null;
+};
+
+/**
+ * Get the IndexValueStore used for the current conversion.
+ * @returns {ve.dm.IndexValueStore|null} Current store, or null if not
converting
+ */
+ve.dm.Converter.prototype.getStore = function () {
+ return this.store;
+};
+
+/**
+ * Get the HTML document currently being converted
+ * @returns {HTMLDocument|null} HTML document being converted, or null if not
converting
+ */
+ve.dm.Converter.prototype.getHtmlDocument = function () {
+ return this.doc;
+};
+
+/**
+ * Get the current conversion context. This is the recursion state of
getDataFromDomRecursion().
+ * @returns {Object|null} Context object, or null if not converting
+ */
+ve.dm.Converter.prototype.getCurrentContext = function () {
+ return this.contextStack === null ? null :
this.contextStack[this.contextStack.length - 1];
+};
+
+/**
+ * Get the annotations currently being applied by the converter. Note that
this is specific to
+ * the current recursion level.
+ * @returns {ve.dm.AnnotationSet|null} Annotation set, or null if not
converting
+ */
+ve.dm.Converter.prototype.getActiveAnnotations = function () {
+ var context = this.getCurrentContext();
+ return context ? context.annotations : null;
+};
+
+/**
+ * Whether the converter is currently expecting content. Note that this is
specific to the current
+ * recursion level.
+ * @returns {boolean|null} Boolean indicating whether content is expected, or
null if not converting
+ */
+ve.dm.Converter.prototype.isExpectingContent = function () {
+ var context = this.getCurrentContext();
+ return context ? context.expectingContent : null;
+};
+
+/**
+ * Whether the conversion is currently inside a wrapper paragraph generated by
the converter.
+ * Note that this is specific to the current recursion level.
+ * @returns {boolean|null} Boolean indicating whether we're wrapping, or null
if not converting
+ */
+ve.dm.Converter.prototype.isInWrapper = function () {
+ var context = this.getCurrentContext();
+ return context ? context.inWrapper : null;
+};
+
+/**
+ * Whether the active wrapper can be closed. Note that this is specific to the
current recursion
+ * level. If there is no active wrapper, this returns false.
+ * @returns {boolean|null} Boolean indicating whether the wrapper can be
closed, or null if not converting
+ */
+ve.dm.Converter.prototype.canCloseWrapper = function () {
+ var context = this.getCurrentContext();
+ return context ? context.canCloseWrapper : null;
+};
/**
* Get the DOM element for a given linear model element.
@@ -191,27 +266,34 @@
* @returns {ve.dm.ElementLinearData} Linear model data
*/
ve.dm.Converter.prototype.getDataFromDom = function ( store, doc ) {
+ var result;
+ // Set up the converter state
+ this.doc = doc;
+ this.store = store;
+ this.contextStack = [];
// Possibly do things with doc and the head in the future
- return new ve.dm.ElementLinearData(
+ result = new ve.dm.ElementLinearData(
store,
- this.getDataFromDomRecursion( store, doc.body )
+ this.getDataFromDomRecursion( doc.body )
);
+ // Clear the state
+ this.doc = null;
+ this.store = null;
+ this.contextStack = null;
+ return result;
};
/**
- * Recursive implementation of getDataFromDom(). For internal use.
+ * Recursive implementation of getDataFromDom(). For internal use, and for use
in
+ * ve.dm.Model.static.toDataElement() implementations.
*
* @method
- * @param {ve.dm.IndexValueStore} store Index-value store
* @param {HTMLElement} domElement HTML element to convert
- * @param {ve.dm.AnnotationSet} [annotations] Annotations to apply to the
generated data
- * @param {Object} [dataElement] Data element to wrap the returned data in
- * @param {Array} [path] Array of linear model element types
- * @param {boolean} [alreadyWrapped] Whether the caller has already started
wrapping bare content in a paragraph
+ * @param {Object} [wrapperElement] Data element to wrap the returned data in
+ * @param {ve.dm.AnnotationSet} [annotationSet] Override the set of
annotations to use
* @returns {Array} Linear model data
*/
-ve.dm.Converter.prototype.getDataFromDomRecursion = function ( store,
domElement, annotations,
- dataElement, path, alreadyWrapped ) {
+ve.dm.Converter.prototype.getDataFromDomRecursion = function ( domElement,
wrapperElement, annotationSet ) {
function addWhitespace( element, index, whitespace ) {
if ( !element.internal ) {
element.internal = {};
@@ -260,7 +342,7 @@
wrappingParagraph = undefined;
context.inWrapper = false;
context.canCloseWrapper = false;
- context.expectingContent = originallyExpectingContent;
+ context.expectingContent = context.originallyExpectingContent;
}
function getAboutGroup( el ) {
@@ -287,27 +369,32 @@
return aboutGroup;
}
- // Fallback to defaults
- annotations = annotations || new ve.dm.AnnotationSet( store );
- path = path || ['document'];
var i, childDomElement, childDomElements, childDataElement, text,
childTypes, matches,
wrappingParagraph, prevElement, childAnnotations, modelName,
modelClass,
annotation, annotationData, childIsContent, aboutGroup,
data = [],
- branchType = path[path.length - 1],
- branchHasContent = this.nodeFactory.canNodeContainContent(
branchType ),
- originallyExpectingContent = branchHasContent ||
!annotations.isEmpty(),
nextWhitespace = '',
wrappedWhitespace = '',
wrappedWhitespaceIndex,
- context = {
- 'expectingContent': originallyExpectingContent,
- 'inWrapper': alreadyWrapped,
- 'canCloseWrapper': false
- };
+ context = {},
+ prevContext = this.contextStack.length ?
this.contextStack[this.contextStack.length - 1] : null;
+
+ context.annotations = annotationSet || (
+ prevContext ? prevContext.annotations.clone() : new
ve.dm.AnnotationSet( this.store )
+ );
+ context.branchType = wrapperElement ? wrapperElement.type : (
+ prevContext ? prevContext.branchType : 'document'
+ );
+ context.branchHasContent = this.nodeFactory.canNodeContainContent(
context.branchType );
+ context.originallyExpectingContent = context.branchHasContent ||
!context.annotations.isEmpty();
+ context.expectingContent = context.originallyExpectingContent;
+ context.inWrapper = prevContext ? prevContext.inWrapper : false;
+ context.canCloseWrapper = false;
+ this.contextStack.push( context );
+
// Open element
- if ( dataElement ) {
- data.push( dataElement );
+ if ( wrapperElement ) {
+ data.push( wrapperElement );
}
// Add contents
for ( i = 0; i < domElement.childNodes.length; i++ ) {
@@ -327,13 +414,10 @@
prevElement = wrappingParagraph;
}
// Append child element data
- childAnnotations = annotations.clone();
+ childAnnotations =
context.annotations.clone();
childAnnotations.push( annotation );
data = data.concat(
- this.getDataFromDomRecursion(
- store, childDomElement,
childAnnotations,
- undefined, path,
context.inWrapper
- )
+ this.getDataFromDomRecursion(
childDomElement, undefined, childAnnotations )
);
} else {
// Node or meta item
@@ -372,8 +456,8 @@
}
// Annotate child
- if ( childIsContent &&
!annotations.isEmpty() ) {
- childDataElement.annotations =
annotations.getIndexes().slice();
+ if ( childIsContent &&
!context.annotations.isEmpty() ) {
+ childDataElement.annotations =
context.annotations.getIndexes().slice();
}
// Output child and its children, if any
@@ -384,13 +468,8 @@
// Recursion
// Opening and closing elements
are added by the recursion too
data = data.concat(
-
this.getDataFromDomRecursion(
- store,
- childDomElement,
- new
ve.dm.AnnotationSet( store ),
-
childDataElement,
- path.concat(
childDataElement.type ),
-
context.inWrapper
+
this.getDataFromDomRecursion( childDomElement, childDataElement,
+ new
ve.dm.AnnotationSet( this.store )
)
);
} else {
@@ -411,7 +490,7 @@
// Empty text node?!?
break;
}
- if ( !originallyExpectingContent ) {
+ if ( !context.originallyExpectingContent ) {
// Strip and store outer whitespace
if ( text.match( /^\s+$/ ) ) {
// This text node is whitespace
only
@@ -422,15 +501,15 @@
wrappedWhitespace =
text;
wrappedWhitespaceIndex
= data.length;
data = data.concat(
-
ve.dm.Converter.getDataContentFromText( wrappedWhitespace, annotations )
+
ve.dm.Converter.getDataContentFromText( wrappedWhitespace, context.annotations )
);
} else {
// We're not in
wrapping mode, store this whitespace
if ( !prevElement ) {
- if (
dataElement ) {
+ if (
wrapperElement ) {
//
First child, store as inner
//
whitespace in the parent
-
addWhitespace( dataElement, 1, text );
+
addWhitespace( wrapperElement, 1, text );
}
// Else, WTF?!?
This is not supposed to
// happen, but
it's not worth
@@ -457,10 +536,10 @@
// started wrapping
if ( matches[1] !== ''
) {
if (
!prevElement ) {
- if (
dataElement ) {
+ if (
wrapperElement ) {
// First child, store as inner
// whitespace in the parent
-
addWhitespace( dataElement, 1, matches[1] );
+
addWhitespace( wrapperElement, 1, matches[1] );
}
//
Else, WTF?!? This is not supposed to
//
happen, but it's not worth
@@ -474,12 +553,12 @@
// We were already
wrapping in a paragraph,
// so the leading
whitespace must be output
data = data.concat(
-
ve.dm.Converter.getDataContentFromText( matches[1], annotations )
+
ve.dm.Converter.getDataContentFromText( matches[1], context.annotations )
);
}
// Output the text sans
whitespace
data = data.concat(
-
ve.dm.Converter.getDataContentFromText( matches[2], annotations )
+
ve.dm.Converter.getDataContentFromText( matches[2], context.annotations )
);
// Don't store this in
wrappingParagraph.internal.whitespace[3]
@@ -492,7 +571,7 @@
wrappedWhitespace = matches[3];
wrappedWhitespaceIndex =
data.length;
data = data.concat(
-
ve.dm.Converter.getDataContentFromText( wrappedWhitespace, annotations )
+
ve.dm.Converter.getDataContentFromText( wrappedWhitespace, context.annotations )
);
prevElement = wrappingParagraph;
break;
@@ -503,26 +582,26 @@
// (but only in non-annotation nodes)
// and store it so it can be restored later.
if (
- annotations.isEmpty() && i === 0 &&
dataElement &&
-
!this.nodeFactory.doesNodeHaveSignificantWhitespace( dataElement.type )
+ context.annotations.isEmpty() && i ===
0 && wrapperElement &&
+
!this.nodeFactory.doesNodeHaveSignificantWhitespace( wrapperElement.type )
) {
// Strip leading whitespace from the
first child
matches = text.match( /^\s+/ );
if ( matches && matches[0] !== '' ) {
- addWhitespace( dataElement, 1,
matches[0] );
+ addWhitespace( wrapperElement,
1, matches[0] );
text = text.substring(
matches[0].length );
}
}
if (
- annotations.isEmpty() &&
+ context.annotations.isEmpty() &&
i === domElement.childNodes.length - 1
&&
- dataElement &&
-
!this.nodeFactory.doesNodeHaveSignificantWhitespace( dataElement.type )
+ wrapperElement &&
+
!this.nodeFactory.doesNodeHaveSignificantWhitespace( wrapperElement.type )
) {
// Strip trailing whitespace from the
last child
matches = text.match( /\s+$/ );
if ( matches && matches[0] !== '' ) {
- addWhitespace( dataElement, 2,
matches[0] );
+ addWhitespace( wrapperElement,
2, matches[0] );
text = text.substring( 0,
text.length -
matches[0].length );
}
@@ -530,7 +609,7 @@
// Annotate the text and output it
data = data.concat(
- ve.dm.Converter.getDataContentFromText(
text, annotations )
+ ve.dm.Converter.getDataContentFromText(
text, context.annotations )
);
break;
case Node.COMMENT_NODE:
@@ -558,10 +637,10 @@
// If we're closing a node that doesn't have any children, but could
contain a paragraph,
// add a paragraph. This prevents things like empty list items
- childTypes = this.nodeFactory.getChildNodeTypes( branchType );
- if ( branchType !== 'paragraph' && dataElement && data[data.length - 1]
=== dataElement &&
- !context.inWrapper && !this.nodeFactory.canNodeContainContent(
branchType ) &&
- !this.nodeFactory.isNodeContent( branchType ) &&
+ childTypes = this.nodeFactory.getChildNodeTypes( context.branchType );
+ if ( context.branchType !== 'paragraph' && wrapperElement &&
data[data.length - 1] === wrapperElement &&
+ !context.inWrapper && !this.nodeFactory.canNodeContainContent(
context.branchType ) &&
+ !this.nodeFactory.isNodeContent( context.branchType ) &&
( childTypes === null || ve.indexOf( 'paragraph', childTypes )
!== -1 )
) {
data.push( { 'type': 'paragraph', 'internal': { 'generated':
'empty' } } );
@@ -569,21 +648,23 @@
}
// Close element
- if ( dataElement ) {
- data.push( { 'type': '/' + dataElement.type } );
+ if ( wrapperElement ) {
+ data.push( { 'type': '/' + wrapperElement.type } );
// Add the whitespace after the last child to the parent as
innerPost
if ( nextWhitespace !== '' ) {
- addWhitespace( dataElement, 2, nextWhitespace );
+ addWhitespace( wrapperElement, 2, nextWhitespace );
nextWhitespace = '';
}
}
// Don't return an empty document
- if ( branchType === 'document' && data.length === 0 ) {
+ if ( context.branchType === 'document' && data.length === 0 ) {
return [
{ 'type': 'paragraph', 'internal': { 'generated':
'empty' } },
{ 'type': '/paragraph' }
];
}
+
+ this.contextStack.pop();
return data;
};
--
To view, visit https://gerrit.wikimedia.org/r/58642
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: I89574c42385267e08704f018c0892d63014376a6
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/VisualEditor
Gerrit-Branch: master
Gerrit-Owner: Catrope <[email protected]>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits