jenkins-bot has submitted this change and it was merged.
Change subject: Rename TagHandlers.js to DOMHandlers.js
......................................................................
Rename TagHandlers.js to DOMHandlers.js
* And move some of the contents from WSP._getDOMHandler there.
* This consolidates everything that implements what's loosely called
the `domHandler` interface in one file.
Change-Id: I692d92a346c99ef3fb69edbdfce891ed276ac878
---
R lib/html2wt/DOMHandlers.js
M lib/html2wt/WikitextSerializer.js
M lib/html2wt/separators.js
3 files changed, 253 insertions(+), 220 deletions(-)
Approvals:
Subramanya Sastry: Looks good to me, approved
jenkins-bot: Verified
diff --git a/lib/html2wt/TagHandlers.js b/lib/html2wt/DOMHandlers.js
similarity index 83%
rename from lib/html2wt/TagHandlers.js
rename to lib/html2wt/DOMHandlers.js
index 1519c79..86036ed 100644
--- a/lib/html2wt/TagHandlers.js
+++ b/lib/html2wt/DOMHandlers.js
@@ -5,10 +5,26 @@
var Util = require('../utils/Util.js').Util;
var DU = require('../utils/DOMUtils.js').DOMUtils;
var WTSUtils = require('./WTSUtils.js').WTSUtils;
-var wtConsts = require('../config/WikitextConstants.js');
+var Consts = require('../config/WikitextConstants.js').WikitextConstants;
-var Consts = wtConsts.WikitextConstants;
+/**
+ * Definitions of what's loosely defined as the `domHandler` interface.
+ *
+ * FIXME: Solidify the interface in code.
+ *
+ * var domHandler = {
+ * handle: function(node, state, wrapperUnmodified) { ... },
+ * sepnls: {
+ * before: (node, otherNode, state) => { min: 1, max: 2 },
+ * after: (node, otherNode, state) => { ... },
+ * firstChild: (node, otherNode, state) => { ... },
+ * lastChild: (node, otherNode, state) => { ... },
+ * },
+ * };
+ */
+// Forward declaration
+var _htmlElementHandler;
function id(v) {
return function() { return v; };
@@ -489,7 +505,12 @@
return prev !== null && prev.nodeName === node.nodeName;
}
-var TagHandlers = {
+/**
+ * A map of `domHandler`s keyed on nodeNames.
+ *
+ * Includes specialized keys of the form: nodeName + '_' + dp.stx
+ */
+var tagHandlers = JSUtils.mapObject({
b: buildQuoteHandler("'''"),
i: buildQuoteHandler("''"),
@@ -886,7 +907,7 @@
// HTML pre
pre_html: {
handle: function(node, state) {
- return state.serializer._htmlElementHandler(node,
state);
+ return _htmlElementHandler(node, state);
},
sepnls: {
before: id({}),
@@ -923,7 +944,7 @@
}
state.emitChunk(out, node);
} else {
-
state.serializer._htmlElementHandler(node, state);
+ _htmlElementHandler(node, state);
}
} else if (type) {
switch (type) {
@@ -950,11 +971,11 @@
// just ignore it
break;
default:
-
state.serializer._htmlElementHandler(node, state);
+ _htmlElementHandler(node, state);
break;
}
} else {
- state.serializer._htmlElementHandler(node,
state);
+ _htmlElementHandler(node, state);
}
},
sepnls: {
@@ -1038,7 +1059,7 @@
state.emitChunk('
'.repeat(node.firstChild.nodeValue.length),
node.firstChild);
} else {
-
state.serializer._htmlElementHandler(node, state);
+ _htmlElementHandler(node,
state);
}
}
} else {
@@ -1056,7 +1077,7 @@
} else {
// Fall back to plain HTML
serialization for spans created
// by the editor.
-
state.serializer._htmlElementHandler(node, state);
+ _htmlElementHandler(node, state);
}
}
},
@@ -1201,8 +1222,206 @@
lastChild: id({ min: 0, max: 1 }),
},
},
+});
+
+/**
+ * Function returning `domHandler`s for nodes with encapsulated content.
+ */
+var _getEncapsulatedContentHandler = function(node) {
+ var self = this;
+ var env = this.env;
+ var state = this.state;
+ var dp = DU.getDataParsoid(node);
+ var typeOf = node.getAttribute('typeof') || '';
+
+ if (DU.isFirstEncapsulationWrapperNode(node)) {
+ return {
+ handle: function() {
+ var src, dataMW;
+ if
(/(?:^|\s)mw:Transclusion(?=$|\s)/.test(typeOf)) {
+ dataMW = DU.getDataMw(node);
+ if (dataMW.parts) {
+ src =
self._buildTemplateWT(node, dataMW.parts);
+ } else if (dp.src) {
+ env.log("error", "data-mw
missing in: " + node.outerHTML);
+ src = dp.src;
+ } else {
+ throw new Error("Cannot
serialize transclusion without data-mw.parts or data-parsoid.src.");
+ }
+ } else if
(/(?:^|\s)mw:Param(?=$|\s)/.test(typeOf)) {
+ if (dp.src) {
+ src = dp.src;
+ } else {
+ throw new Error("No source for
params.");
+ }
+ } else if
(/(?:^|\s)mw:Extension\/LabeledSectionTransclusion/.test(typeOf)) {
+ // FIXME: Special case for <section>
until LST is implemented
+ // natively in Parsoid
+ if (dp.src) {
+ src = dp.src;
+ } else if (typeOf.match('begin')) {
+ src = '<section begin="' +
node.getAttribute('content') + '" />';
+ } else if (typeOf.match('end')) {
+ src = '<section end="' +
node.getAttribute('content') + '" />';
+ } else {
+ env.log("error", "LST <section>
without content in: " + node.outerHTML);
+ src = '<section />';
+ }
+ } else if
(/(?:^|\s)mw:Extension\//.test(typeOf)) {
+ dataMW = DU.getDataMw(node);
+ if (dataMW.name) {
+ if (dp.autoInsertedRefs &&
state.rtTestMode) {
+ // Eliminate
auto-inserted <references /> noise in rt-testing
+ src = '';
+ } else {
+ src =
self._buildExtensionWT(node, dataMW);
+ }
+ } else if (dp.src) {
+ env.log("error", "data-mw
missing in: " + node.outerHTML);
+ src = dp.src;
+ } else {
+ // If there was no typeOf name,
and no dp.src, try getting
+ // the name out of the
mw:Extension type. This will
+ // generate an empty extension
tag, but it's better than
+ // just an error.
+ var extGivenName =
typeOf.replace(/(?:^|\s)mw:Extension\/([^\s]+)/, "$1");
+ if (extGivenName) {
+ env.log("error", "no
data-mw name for extension in: ", node.outerHTML);
+ dataMW.name =
extGivenName;
+ src =
self._buildExtensionWT(node, dataMW);
+ } else {
+ throw new Error("Cannot
serialize extension without data-mw.name or data-parsoid.src.");
+ }
+ }
+ } else {
+ throw new Error("Should never reach
here");
+ }
+
+ state.singleLineContext.disable();
+ self.emitWikitext(src, node);
+ state.singleLineContext.pop();
+ return DU.skipOverEncapsulatedContent(node);
+ },
+ sepnls: {
+ // XXX: This is questionable, as the template
can expand
+ // to newlines too. Which default should we
pick for new
+ // content? We don't really want to make
separator
+ // newlines in HTML significant for the
semantics of the
+ // template content.
+ before: function(node, otherNode) {
+ if (DU.isNewElt(node)
+ &&
/(?:^|\s)mw:Extension\/references(?:\s|$)/
+ .test(typeOf)
+ // Only apply to plain
references tags
+ &&
!/(?:^|\s)mw:Transclusion(?:\s|$)/
+ .test(typeOf)) {
+ // Serialize new references
tags on a new line
+ return { min: 1, max: 2 };
+ } else {
+ return { min: 0, max: 2 };
+ }
+ },
+ },
+ };
+ }
+
+ if (dp.src !== undefined) {
+ // Uneditable forms wrapped with mw:Placeholder tags
+ // OR unedited nowikis
+ if (/(^|\s)mw:Placeholder(\/\w*)?$/.test(typeOf) ||
+ (typeOf === "mw:Nowiki" && node.textContent ===
dp.src)) {
+ // implement generic src round-tripping:
+ // return src, and drop the generated content
+ return {
+ handle: function() {
+ // FIXME: Should this also check for
tabs and plain space chars
+ // interspersed with newlines?
+ if (dp.src.match(/^\n+$/)) {
+ state.sep.src = (state.sep.src
|| '') + dp.src;
+ } else {
+ self.emitWikitext(dp.src, node);
+ }
+ },
+ };
+ }
+
+ // Entities
+ if (typeOf === "mw:Entity" && node.childNodes.length === 1) {
+ var contentSrc = node.textContent || node.innerHTML;
+ return {
+ handle: function() {
+ if (contentSrc === dp.srcContent) {
+ self.emitWikitext(dp.src, node);
+ } else {
+ self.emitWikitext(contentSrc,
node);
+ }
+ },
+ };
+ }
+ }
+
+ return null;
};
+
+/**
+ * Just the handle for the htmlElementHandler defined below.
+ * It's used as a fallback in some of the tagHandlers above.
+ */
+_htmlElementHandler = function(node, state, wrapperUnmodified) {
+ var serializer = state.serializer;
+
+ // Wikitext supports the following list syntax:
+ //
+ // * <li class="a"> hello world
+ //
+ // The "LI Hack" gives support for this syntax, and we need to
+ // specially reconstruct the above from a single <li> tag.
+ serializer._handleLIHackIfApplicable(node);
+
+ var tag = serializer._serializeHTMLTag(node, wrapperUnmodified);
+ WTSUtils.emitStartTag(tag, node, state);
+
+ if (node.childNodes.length) {
+ var inPHPBlock = state.inPHPBlock;
+ if (Util.tagOpensBlockScope(node.nodeName.toLowerCase())) {
+ state.inPHPBlock = true;
+ }
+
+ if (node.nodeName === 'PRE') {
+ // Handle html-pres specially
+ // 1. If the node has a leading newline, add one like
it (logic copied from VE)
+ // 2. If not, and it has a data-parsoid strippedNL
flag, add it back.
+ // This patched DOM will serialize html-pres correctly.
+
+ var lostLine = '';
+ var fc = node.firstChild;
+ if (fc && DU.isText(fc)) {
+ var m = fc.nodeValue.match(/^\n/);
+ lostLine = m && m[0] || '';
+ }
+
+ if (!lostLine && DU.getDataParsoid(node).strippedNL) {
+ lostLine = '\n';
+ }
+
+ state.emitChunk(lostLine, node);
+ }
+
+ state.serializeChildren(node);
+ state.inPHPBlock = inPHPBlock;
+ }
+
+ tag = serializer._serializeHTMLEndTag(node, wrapperUnmodified);
+ WTSUtils.emitEndTag(tag, node, state);
+};
+
+var htmlElementHandler = { handle: _htmlElementHandler };
+
+
if (typeof module === "object") {
- module.exports.TagHandlers = TagHandlers;
+ module.exports.tagHandlers = tagHandlers;
+ module.exports.htmlElementHandler = htmlElementHandler;
+ module.exports._getEncapsulatedContentHandler =
+ _getEncapsulatedContentHandler;
}
diff --git a/lib/html2wt/WikitextSerializer.js
b/lib/html2wt/WikitextSerializer.js
index 63b5b13..fdffa20 100644
--- a/lib/html2wt/WikitextSerializer.js
+++ b/lib/html2wt/WikitextSerializer.js
@@ -35,12 +35,14 @@
var ConstrainedText = require('./ConstrainedText.js').ConstrainedText;
var Normalizer = require('./normalizeDOM.js').Normalizer;
var SerializerState = require('./SerializerState.js').SerializerState;
-var TagHandlers = require('./TagHandlers.js').TagHandlers;
+var DOMHandlers = require('./DOMHandlers.js');
var LinkHandlersModule = require('./LinkHandler.js');
var SeparatorsModule = require('./separators.js');
var WTEModule = require('./escapeWikitext.js');
var Consts = wtConsts.WikitextConstants;
+var tagHandlers = DOMHandlers.tagHandlers;
+var htmlElementHandler = DOMHandlers.htmlElementHandler;
/**
@@ -74,7 +76,7 @@
var WSP = WikitextSerializer.prototype;
// Tag handlers
-WSP.tagHandlers = TagHandlers;
+WSP._getEncapsulatedContentHandler =
DOMHandlers._getEncapsulatedContentHandler;
// Used in multiple tag handlers, and hence added as top-level properties
// - linkHandler is used by <a> and <link>
@@ -355,50 +357,6 @@
}
};
-WSP._htmlElementHandler = function(node, state, wrapperUnmodified) {
- // Wikitext supports the following list syntax:
- //
- // * <li class="a"> hello world
- //
- // The "LI Hack" gives support for this syntax, and we need to
- // specially reconstruct the above from a single <li> tag.
- this._handleLIHackIfApplicable(node);
-
- WTSUtils.emitStartTag(this._serializeHTMLTag(node, wrapperUnmodified),
- node, state);
- if (node.childNodes.length) {
- var inPHPBlock = state.inPHPBlock;
- if (Util.tagOpensBlockScope(node.nodeName.toLowerCase())) {
- state.inPHPBlock = true;
- }
-
- if (node.nodeName === 'PRE') {
- // Handle html-pres specially
- // 1. If the node has a leading newline, add one like
it (logic copied from VE)
- // 2. If not, and it has a data-parsoid strippedNL
flag, add it back.
- // This patched DOM will serialize html-pres correctly.
-
- var lostLine = '';
- var fc = node.firstChild;
- if (fc && DU.isText(fc)) {
- var m = fc.nodeValue.match(/^\n/);
- lostLine = m && m[0] || '';
- }
-
- if (!lostLine && DU.getDataParsoid(node).strippedNL) {
- lostLine = '\n';
- }
-
- state.emitChunk(lostLine, node);
- }
-
- state.serializeChildren(node);
- state.inPHPBlock = inPHPBlock;
- }
- WTSUtils.emitEndTag(this._serializeHTMLEndTag(node, wrapperUnmodified),
- node, state);
-};
-
WSP._buildTemplateWT = function(node, srcParts) {
function countPositionalArgs(tpl, paramInfos) {
var res = 0;
@@ -654,156 +612,25 @@
};
/**
- * Get a DOM-based handler for an element node
+ * Get a `domHandler` for an element node.
*/
WSP._getDOMHandler = function(node) {
- if (!node || !DU.isElt(node)) {
- return {};
- }
+ if (!node || !DU.isElt(node)) { return {}; }
- var self = this;
- var state = this.state;
+ var handler = this._getEncapsulatedContentHandler(node);
+ if (handler !== null) { return handler; }
+
var dp = DU.getDataParsoid(node);
- var typeOf = node.getAttribute('typeof') || '';
-
- // XXX: Convert into separate handlers?
- if (DU.isFirstEncapsulationWrapperNode(node)) {
- return {
- handle: function() {
- var src, dataMW;
- if
(/(?:^|\s)mw:Transclusion(?=$|\s)/.test(typeOf)) {
- dataMW = DU.getDataMw(node);
- if (dataMW.parts) {
- src =
self._buildTemplateWT(node, dataMW.parts);
- } else if (dp.src) {
- self.env.log("error", "data-mw
missing in: " + node.outerHTML);
- src = dp.src;
- } else {
- throw new Error("Cannot
serialize transclusion without data-mw.parts or data-parsoid.src.");
- }
- } else if
(/(?:^|\s)mw:Param(?=$|\s)/.test(typeOf)) {
- if (dp.src) {
- src = dp.src;
- } else {
- throw new Error("No source for
params.");
- }
- } else if
(/(?:^|\s)mw:Extension\/LabeledSectionTransclusion/.test(typeOf)) {
- // FIXME: Special case for <section>
until LST is implemented
- // natively in Parsoid
- if (dp.src) {
- src = dp.src;
- } else if (typeOf.match('begin')) {
- src = '<section begin="' +
node.getAttribute('content') + '" />';
- } else if (typeOf.match('end')) {
- src = '<section end="' +
node.getAttribute('content') + '" />';
- } else {
- self.env.log("error", "LST
<section> without content in: " + node.outerHTML);
- src = '<section />';
- }
- } else if
(/(?:^|\s)mw:Extension\//.test(typeOf)) {
- dataMW = DU.getDataMw(node);
- if (dataMW.name) {
- if (dp.autoInsertedRefs &&
state.rtTestMode) {
- // Eliminate
auto-inserted <references /> noise in rt-testing
- src = '';
- } else {
- src =
self._buildExtensionWT(node, dataMW);
- }
- } else if (dp.src) {
- self.env.log("error", "data-mw
missing in: " + node.outerHTML);
- src = dp.src;
- } else {
- // If there was no typeOf name,
and no dp.src, try getting
- // the name out of the
mw:Extension type. This will
- // generate an empty extension
tag, but it's better than
- // just an error.
- var extGivenName =
typeOf.replace(/(?:^|\s)mw:Extension\/([^\s]+)/, "$1");
- if (extGivenName) {
- self.env.log("error",
"no data-mw name for extension in: ", node.outerHTML);
- dataMW.name =
extGivenName;
- src =
self._buildExtensionWT(node, dataMW);
- } else {
- throw new Error("Cannot
serialize extension without data-mw.name or data-parsoid.src.");
- }
- }
- } else {
- throw new Error("Should never reach
here");
- }
-
- state.singleLineContext.disable();
- self.emitWikitext(src, node);
- state.singleLineContext.pop();
- return DU.skipOverEncapsulatedContent(node);
- },
- sepnls: {
- // XXX: This is questionable, as the template
can expand
- // to newlines too. Which default should we
pick for new
- // content? We don't really want to make
separator
- // newlines in HTML significant for the
semantics of the
- // template content.
- before: function(node, otherNode) {
- if (DU.isNewElt(node)
- &&
/(?:^|\s)mw:Extension\/references(?:\s|$)/
-
.test(node.getAttribute('typeof'))
- // Only apply to plain
references tags
- &&
!/(?:^|\s)mw:Transclusion(?:\s|$)/
-
.test(node.getAttribute('typeof'))) {
- // Serialize new references
tags on a new line
- return { min: 1, max: 2 };
- } else {
- return { min: 0, max: 2 };
- }
- },
- },
- };
- }
-
- if (dp.src !== undefined) {
- // Uneditable forms wrapped with mw:Placeholder tags
- // OR unedited nowikis
- if (/(^|\s)mw:Placeholder(\/\w*)?$/.test(typeOf) ||
- (typeOf === "mw:Nowiki" && node.textContent ===
dp.src)) {
- // implement generic src round-tripping:
- // return src, and drop the generated content
- return {
- handle: function() {
- // FIXME: Should this also check for
tabs and plain space chars
- // interspersed with newlines?
- if (dp.src.match(/^\n+$/)) {
- state.sep.src = (state.sep.src
|| '') + dp.src;
- } else {
- self.emitWikitext(dp.src, node);
- }
- },
- };
- }
-
- // Entities
- if (typeOf === "mw:Entity" && node.childNodes.length === 1) {
- var contentSrc = node.textContent || node.innerHTML;
- return {
- handle: function() {
- if (contentSrc === dp.srcContent) {
- self.emitWikitext(dp.src, node);
- } else {
- self.emitWikitext(contentSrc,
node);
- }
- },
- };
- }
- }
-
- var htmlTagHandler = { handle: self._htmlElementHandler.bind(self) };
+ var nodeName = node.nodeName.toLowerCase();
// If available, use a specialized handler for serializing
// to the specialized syntactic form of the tag.
- var nodeName = node.nodeName.toLowerCase();
- var altHandler = self.tagHandlers[nodeName + "_" + dp.stx];
+ handler = tagHandlers.get(nodeName + '_' + dp.stx);
// Unless a specialized handler is available, use the HTML handler
// for html-stx tags. But, <a> tags should never serialize as HTML.
- if (!altHandler && dp.stx === 'html' && nodeName !== 'a') {
- return htmlTagHandler;
+ if (!handler && dp.stx === 'html' && nodeName !== 'a') {
+ return htmlElementHandler;
}
// If parent node is a list or table tag in html-syntax, then serialize
@@ -814,11 +641,11 @@
((DU.isList(node.parentNode) && DU.isListItem(node)) ||
(Consts.ParentTableTags.has(node.parentNode.nodeName) &&
Consts.ChildTableTags.has(node.nodeName)))) {
- return htmlTagHandler;
+ return htmlElementHandler;
}
// Pick the best available handler
- return altHandler || self.tagHandlers[nodeName] || htmlTagHandler;
+ return handler || tagHandlers.get(nodeName) || htmlElementHandler;
};
WSP.separatorREs = {
diff --git a/lib/html2wt/separators.js b/lib/html2wt/separators.js
index adfa60a..eeb0d0b 100644
--- a/lib/html2wt/separators.js
+++ b/lib/html2wt/separators.js
@@ -314,19 +314,6 @@
/**
* Figure out separator constraints and merge them with existing constraints
* in state so that they can be emitted when the next content emits source.
- *
- * node handlers:
- *
- * body: {
- * handle: function(node, state) {},
- * // responsible for calling
- * sepnls: {
- * before: function(node) -> {min: 1, max: 2}
- * after: function(node)
- * firstChild: function(node)
- * lastChild: function(node)
- * }
- * }
*/
var updateSeparatorConstraints = function(nodeA, handlerA, nodeB, handlerB) {
var nlConstraints;
@@ -334,6 +321,7 @@
var sepHandlerA = handlerA && handlerA.sepnls || {};
var sepHandlerB = handlerB && handlerB.sepnls || {};
var sepType = null;
+
if (nodeA.nextSibling === nodeB) {
// sibling separator
sepType = "sibling";
@@ -362,20 +350,19 @@
if (state.sep.constraints) {
// Merge the constraints
- state.sep.constraints = mergeConstraints(this.env,
state.sep.constraints, nlConstraints);
- // if (state.sep.lastSourceNode &&
DU.isText(state.sep.lastSourceNode) {
- // state.sep.lastSourceNode = nodeA;
- // }
+ state.sep.constraints = mergeConstraints(this.env,
+ state.sep.constraints, nlConstraints);
} else {
state.sep.constraints = nlConstraints;
- // state.sep.lastSourceNode = state.sep.lastSourceNode || nodeA;
}
this.env.log('debug/wts/sep', function() {
- return 'constraint | ' + sepType + " | <" + nodeA.nodeName +
"," + nodeB.nodeName + "> | " +
- JSON.stringify(state.sep.constraints) +
- " | " + DU.debugOut(nodeA) +
- " | " + DU.debugOut(nodeB);
+ return 'constraint' +
+ ' | ' + sepType +
+ ' | <' + nodeA.nodeName + ',' + nodeB.nodeName + '>' +
+ ' | ' + JSON.stringify(state.sep.constraints) +
+ ' | ' + DU.debugOut(nodeA) +
+ ' | ' + DU.debugOut(nodeB);
});
state.sep.constraints.constraintInfo = {
--
To view, visit https://gerrit.wikimedia.org/r/251011
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: I692d92a346c99ef3fb69edbdfce891ed276ac878
Gerrit-PatchSet: 2
Gerrit-Project: mediawiki/services/parsoid
Gerrit-Branch: master
Gerrit-Owner: Arlolra <[email protected]>
Gerrit-Reviewer: Arlolra <[email protected]>
Gerrit-Reviewer: Cscott <[email protected]>
Gerrit-Reviewer: Subramanya Sastry <[email protected]>
Gerrit-Reviewer: Tim Starling <[email protected]>
Gerrit-Reviewer: jenkins-bot <>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits