/
DOMTraverser.js
164 lines (148 loc) · 4.64 KB
/
DOMTraverser.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
/**
* Pre-order depth-first DOM traversal helper.
*
* @module
*/
'use strict';
var DOMUtils = require('./DOMUtils.js').DOMUtils;
var JSUtils = require('./jsutils.js').JSUtils;
var WTUtils = require('./WTUtils.js').WTUtils;
/**
* Class for helping us traverse the DOM.
*
* This class currently does a pre-order depth-first traversal.
* See {@link DOMPostOrder} for post-order traversal.
*
* @class
*/
function DOMTraverser() {
this.handlers = [];
}
/**
* DOM traversal handler.
*
* @callback module:utils/DOMTraverser~traverserHandler
* @param {Node} node
* @param {MWParserEnvironment} env
* @param {boolean} atTopLevel
* @param {Object} tplInfo Template information.
* @return {Node|null|false|true}
* Return false if you want to stop any further callbacks from being
* called on the node. Return the new node if you need to replace it or
* change its siblings; traversal will continue with the new node.
*/
/**
* Add a handler to the DOM traverser.
*
* @param {string} nodeName
* @param {traverserHandler} action
* A callback, called on each node we traverse that matches nodeName.
*/
DOMTraverser.prototype.addHandler = function(nodeName, action) {
this.handlers.push({ action, nodeName });
};
/**
* @param node
* @param env
* @param atTopLevel
* @param tplInfo
* @private
*/
DOMTraverser.prototype.callHandlers = function(node, env, atTopLevel, tplInfo) {
var name = (node.nodeName || '').toLowerCase();
for (const handler of this.handlers) {
if (handler.nodeName === null || handler.nodeName === name) {
var result = handler.action(node, env, atTopLevel, tplInfo);
if (result !== true) {
if (result === undefined) {
env.log("error",
'DOMPostProcessor.traverse: undefined return!',
'Bug in', handler.action.toString(),
' when handling ', node.outerHTML);
}
// abort processing for this node
return result;
}
}
}
return true;
};
/**
* Traverse the DOM and fire the handlers that are registered.
*
* Handlers can return
* - the next node to process
* - aborts processing for current node, continues with returned node
* - can also be `null`, so returning `workNode.nextSibling` works even when
* workNode is a last child of its parent
* - `true`
* - continue regular processing on current node.
*
* @param {Node} workNode
* @param {MWParserEnvironment} env
* @param {Object} options
* @param {boolean} atTopLevel
*/
DOMTraverser.prototype.traverse = function(workNode, env, options, atTopLevel) {
this.traverseInternal(true, null, workNode, env, options, atTopLevel);
};
/**
* @param {boolean} isRootNode
* @param {Object} tplInfo Template information.
* @param {Node} workNode
* @param {MWParserEnvironment} env
* @param {Object} options
* @param {boolean} atTopLevel
*/
DOMTraverser.prototype.traverseInternal = function(isRootNode, tplInfo, workNode, env, options, atTopLevel) {
while (workNode !== null) {
if (DOMUtils.isElt(workNode)) {
// Identify the first template/extension node.
// You'd think the !tplInfo check isn't necessary since
// we don't have nested transclusions, however, you can
// get extensions in transclusions.
if (!tplInfo && WTUtils.isFirstEncapsulationWrapperNode(workNode)
// Encapsulation info on sections should not be used to
// traverse with since it's designed to be dropped and
// may have expanded ranges.
&& !WTUtils.isParsoidSectionTag(workNode)) {
var about = workNode.getAttribute("about") || '';
tplInfo = {
first: workNode,
last: JSUtils.lastItem(WTUtils.getAboutSiblings(workNode, about)),
clear: false,
};
}
}
// Call the handlers on this workNode
var possibleNext = this.callHandlers(workNode, env, atTopLevel, tplInfo);
// We may have walked passed the last about sibling or want to
// ignore the template info in future processing.
if (tplInfo && tplInfo.clear) {
tplInfo = null;
}
if (possibleNext === true) {
// the 'continue processing' case
if (DOMUtils.isElt(workNode) && workNode.hasChildNodes()) {
this.traverseInternal(false, tplInfo, workNode.firstChild, env, options, atTopLevel);
}
if (isRootNode) {
// Confine the traverse to the tree rooted as the root node.
// `workNode.nextSibling` would take us outside that.
possibleNext = null;
} else {
possibleNext = workNode.nextSibling;
}
} else if (isRootNode && possibleNext !== workNode) {
isRootNode = false;
}
// Clear the template info after reaching the last about sibling.
if (tplInfo && tplInfo.last === workNode) {
tplInfo = null;
}
workNode = possibleNext;
}
};
if (typeof module === "object") {
module.exports.DOMTraverser = DOMTraverser;
}