jenkins-bot has submitted this change and it was merged.
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, 163 insertions(+), 65 deletions(-)
Approvals:
Esanders: Looks good to me, approved
jenkins-bot: Verified
diff --git a/modules/ve/dm/ve.dm.Converter.js b/modules/ve/dm/ve.dm.Converter.js
index 5fc438c..0928c1c 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 */
@@ -54,6 +57,7 @@
*
* Note that currentSet will be modified, and will be equal to targetSet once
this function returns.
*
+ * @static
* @param {ve.dm.AnnotationSet} currentSet The set of annotations currently
opened. Will be modified.
* @param {ve.dm.AnnotationSet} targetSet The set of annotations we want to
have.
* @param {Function} open Callback called when an annotation is opened. Passed
a ve.dm.Annotation.
@@ -95,6 +99,94 @@
};
/* Methods */
+
+/**
+ * Check whether this converter instance is currently inside a
getDataFromDom() conversion.
+ *
+ * @method
+ * @returns {Boolean} Whether we're converting
+ */
+ve.dm.Converter.prototype.isConverting = function () {
+ return this.contextStack !== null;
+};
+
+/**
+ * Get the IndexValueStore used for the current conversion.
+ *
+ * @method
+ * @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
+ *
+ * @method
+ * @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().
+ *
+ * @method
+ * @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.
+ *
+ * @method
+ * @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.
+ *
+ * @method
+ * @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.
+ *
+ * @method
+ * @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.
+ *
+ * @method
+ * @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.
@@ -192,27 +284,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 = {};
@@ -261,7 +360,7 @@
wrappingParagraph = undefined;
context.inWrapper = false;
context.canCloseWrapper = false;
- context.expectingContent = originallyExpectingContent;
+ context.expectingContent = context.originallyExpectingContent;
}
function getAboutGroup( el ) {
@@ -288,27 +387,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++ ) {
@@ -328,20 +432,17 @@
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
aboutGroup = getAboutGroup(
childDomElement );
childDomElements =
modelClass.static.enableAboutGrouping ?
aboutGroup : [ childDomElement
];
- childDataElement =
this.createDataElement( modelClass, childDomElements, context, store );
+ childDataElement =
this.createDataElement( modelClass, childDomElements, context, this.store );
if ( modelClass.prototype instanceof
ve.dm.MetaItem ) {
// No additional processing
needed
@@ -367,14 +468,14 @@
modelClass =
ve.dm.AlienNode;
childDomElements =
modelClass.static.enableAboutGrouping ?
aboutGroup : [
childDomElement ];
- childDataElement =
this.createDataElement( modelClass, childDomElements, context, store );
+ childDataElement =
this.createDataElement( modelClass, childDomElements, context, this.store );
childIsContent =
this.nodeFactory.isNodeContent( childDataElement.type );
}
}
// 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
@@ -385,13 +486,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 {
@@ -412,7 +508,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
@@ -423,15 +519,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
@@ -458,10 +554,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
@@ -475,12 +571,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]
@@ -493,7 +589,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;
@@ -504,26 +600,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 );
}
@@ -531,7 +627,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:
@@ -559,10 +655,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' } } );
@@ -570,21 +666,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: merged
Gerrit-Change-Id: I89574c42385267e08704f018c0892d63014376a6
Gerrit-PatchSet: 4
Gerrit-Project: mediawiki/extensions/VisualEditor
Gerrit-Branch: master
Gerrit-Owner: Catrope <[email protected]>
Gerrit-Reviewer: Catrope <[email protected]>
Gerrit-Reviewer: Esanders <[email protected]>
Gerrit-Reviewer: jenkins-bot
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits