您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Matches the text of a DOM node against a regular expression and replaces each match (or node-separated portions of the match) in the specified element.
当前为
此脚本不应直接安装,它是供其他脚本使用的外部库。如果您需要使用该库,请在脚本元属性加入:// @require https://update.cn-greasyfork.org/scripts/423347/911450/findAndReplaceDOMText%20v%20040.js
- /**
- * findAndReplaceDOMText v 0.4.0
- * @author James Padolsey http://james.padolsey.com
- * @license http://unlicense.org/UNLICENSE
- *
- * Matches the text of a DOM node against a regular expression
- * and replaces each match (or node-separated portions of the match)
- * in the specified element.
- */
- window.findAndReplaceDOMText = (function() {
- var PORTION_MODE_RETAIN = 'retain';
- var PORTION_MODE_FIRST = 'first';
- var doc = document;
- var toString = {}.toString;
- function isArray(a) {
- return toString.call(a) == '[object Array]';
- }
- function escapeRegExp(s) {
- return String(s).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1');
- }
- function exposed() {
- // Try deprecated arg signature first:
- return deprecated.apply(null, arguments) || findAndReplaceDOMText.apply(null, arguments);
- }
- function deprecated(regex, node, replacement, captureGroup, elFilter) {
- if ((node && !node.nodeType) && arguments.length <= 2) {
- return false;
- }
- var isReplacementFunction = typeof replacement == 'function';
- if (isReplacementFunction) {
- replacement = (function(original) {
- return function(portion, match) {
- return original(portion.text, match.startIndex);
- };
- }(replacement));
- }
- // Awkward support for deprecated argument signature (<0.4.0)
- var instance = findAndReplaceDOMText(node, {
- find: regex,
- wrap: isReplacementFunction ? null : replacement,
- replace: isReplacementFunction ? replacement : '$' + (captureGroup || '&'),
- prepMatch: function(m, mi) {
- // Support captureGroup (a deprecated feature)
- if (!m[0]) throw 'findAndReplaceDOMText cannot handle zero-length matches';
- if (captureGroup > 0) {
- var cg = m[captureGroup];
- m.index += m[0].indexOf(cg);
- m[0] = cg;
- }
- m.endIndex = m.index + m[0].length;
- m.startIndex = m.index;
- m.index = mi;
- return m;
- },
- filterElements: elFilter
- });
- exposed.revert = function() {
- return instance.revert();
- };
- return true;
- }
- /**
- * findAndReplaceDOMText
- *
- * Locates matches and replaces with replacementNode
- *
- * @param {Node} node Element or Text node to search within
- * @param {RegExp} options.find The regular expression to match
- * @param {String|Element} [options.wrap] A NodeName, or a Node to clone
- * @param {String|Function} [options.replace='$&'] What to replace each match with
- * @param {Function} [options.filterElements] A Function to be called to check whether to
- * process an element. (returning true = process element,
- * returning false = avoid element)
- */
- function findAndReplaceDOMText(node, options) {
- return new Finder(node, options);
- }
- exposed.Finder = Finder;
- /**
- * Finder -- encapsulates logic to find and replace.
- */
- function Finder(node, options) {
- options.portionMode = options.portionMode || PORTION_MODE_RETAIN;
- this.node = node;
- this.options = options;
- // ENable match-preparation method to be passed as option:
- this.prepMatch = options.prepMatch || this.prepMatch;
- this.reverts = [];
- this.matches = this.search();
- if (this.matches.length) {
- this.processMatches();
- }
- }
- Finder.prototype = {
- /**
- * Searches for all matches that comply with the instance's 'match' option
- */
- search: function() {
- var match;
- var matchIndex = 0;
- var regex = this.options.find;
- var text = this.getAggregateText();
- var matches = [];
- regex = typeof regex === 'string' ? RegExp(escapeRegExp(regex), 'g') : regex;
- if (regex.global) {
- while (match = regex.exec(text)) {
- matches.push(this.prepMatch(match, matchIndex++));
- }
- } else {
- if (match = text.match(regex)) {
- matches.push(this.prepMatch(match, 0));
- }
- }
- return matches;
- },
- /**
- * Prepares a single match with useful meta info:
- */
- prepMatch: function(match, matchIndex) {
- if (!match[0]) {
- throw new Error('findAndReplaceDOMText cannot handle zero-length matches');
- }
- match.endIndex = match.index + match[0].length;
- match.startIndex = match.index;
- match.index = matchIndex;
- return match;
- },
- /**
- * Gets aggregate text within subject node
- */
- getAggregateText: function() {
- var elementFilter = this.options.filterElements;
- return getText(this.node);
- /**
- * Gets aggregate text of a node without resorting
- * to broken innerText/textContent
- */
- function getText(node) {
- if (node.nodeType === 3) {
- return node.data;
- }
- if (elementFilter && !elementFilter(node)) {
- return '';
- }
- var txt = '';
- if (node = node.firstChild) do {
- txt += getText(node);
- } while (node = node.nextSibling);
- return txt;
- }
- },
- /**
- * Steps through the target node, looking for matches, and
- * calling replaceFn when a match is found.
- */
- processMatches: function() {
- var matches = this.matches;
- var node = this.node;
- var elementFilter = this.options.filterElements;
- var startPortion,
- endPortion,
- innerPortions = [],
- curNode = node,
- match = matches.shift(),
- atIndex = 0, // i.e. nodeAtIndex
- matchIndex = 0,
- portionIndex = 0,
- doAvoidNode;
- out: while (true) {
- if (curNode.nodeType === 3) {
- if (!endPortion && curNode.length + atIndex >= match.endIndex) {
- // We've found the ending
- endPortion = {
- node: curNode,
- index: portionIndex++,
- text: curNode.data.substring(match.startIndex - atIndex, match.endIndex - atIndex),
- indexInMatch: atIndex - match.startIndex,
- indexInNode: match.startIndex - atIndex, // always zero for end-portions
- endIndexInNode: match.endIndex - atIndex,
- isEnd: true
- };
- } else if (startPortion) {
- // Intersecting node
- innerPortions.push({
- node: curNode,
- index: portionIndex++,
- text: curNode.data,
- indexInMatch: atIndex - match.startIndex,
- indexInNode: 0 // always zero for inner-portions
- });
- }
- if (!startPortion && curNode.length + atIndex > match.startIndex) {
- // We've found the match start
- startPortion = {
- node: curNode,
- index: portionIndex++,
- indexInMatch: 0,
- indexInNode: match.startIndex - atIndex,
- endIndexInNode: match.endIndex - atIndex,
- text: curNode.data.substring(match.startIndex - atIndex, match.endIndex - atIndex)
- };
- }
- atIndex += curNode.data.length;
- }
- doAvoidNode = curNode.nodeType === 1 && elementFilter && !elementFilter(curNode);
- if (startPortion && endPortion) {
- curNode = this.replaceMatch(match, startPortion, innerPortions, endPortion);
- // processMatches has to return the node that replaced the endNode
- // and then we step back so we can continue from the end of the
- // match:
- atIndex -= (endPortion.node.data.length - endPortion.endIndexInNode);
- startPortion = null;
- endPortion = null;
- innerPortions = [];
- match = matches.shift();
- portionIndex = 0;
- matchIndex++;
- if (!match) {
- break; // no more matches
- }
- } else if (
- !doAvoidNode &&
- (curNode.firstChild || curNode.nextSibling)
- ) {
- // Move down or forward:
- curNode = curNode.firstChild || curNode.nextSibling;
- continue;
- }
- // Move forward or up:
- while (true) {
- if (curNode.nextSibling) {
- curNode = curNode.nextSibling;
- break;
- } else if (curNode.parentNode !== node) {
- curNode = curNode.parentNode;
- } else {
- break out;
- }
- }
- }
- },
- /**
- * Reverts ... TODO
- */
- revert: function() {
- // Reversion occurs backwards so as to avoid nodes subsequently
- // replaced during the matching phase (a forward process):
- for (var l = this.reverts.length; l--;) {
- this.reverts[l]();
- }
- this.reverts = [];
- },
- prepareReplacementString: function(string, portion, match, matchIndex) {
- var portionMode = this.options.portionMode;
- if (
- portionMode === PORTION_MODE_FIRST &&
- portion.indexInMatch > 0
- ) {
- return '';
- }
- string = string.replace(/\$(\d+|&|`|')/g, function($0, t) {
- var replacement;
- switch(t) {
- case '&':
- replacement = match[0];
- break;
- case '`':
- replacement = match.input.substring(0, match.startIndex);
- break;
- case '\'':
- replacement = match.input.substring(match.endIndex);
- break;
- default:
- replacement = match[+t];
- }
- return replacement;
- });
- if (portionMode === PORTION_MODE_FIRST) {
- return string;
- }
- if (portion.isEnd) {
- return string.substring(portion.indexInMatch);
- }
- return string.substring(portion.indexInMatch, portion.indexInMatch + portion.text.length);
- },
- getPortionReplacementNode: function(portion, match, matchIndex) {
- var replacement = this.options.replace || '$&';
- var wrapper = this.options.wrap;
- if (wrapper && wrapper.nodeType) {
- // Wrapper has been provided as a stencil-node for us to clone:
- var clone = doc.createElement('div');
- clone.innerHTML = wrapper.outerHTML || new XMLSerializer().serializeToString(wrapper);
- wrapper = clone.firstChild;
- }
- if (typeof replacement == 'function') {
- replacement = replacement(portion, match, matchIndex);
- if (replacement && replacement.nodeType) {
- return replacement;
- }
- return doc.createTextNode(String(replacement));
- }
- var el = typeof wrapper == 'string' ? doc.createElement(wrapper) : wrapper;
- replacement = doc.createTextNode(
- this.prepareReplacementString(
- replacement, portion, match, matchIndex
- )
- );
- if (!el) {
- return replacement;
- }
- el.appendChild(replacement);
- return el;
- },
- replaceMatch: function(match, startPortion, innerPortions, endPortion) {
- var matchStartNode = startPortion.node;
- var matchEndNode = endPortion.node;
- var preceedingTextNode;
- var followingTextNode;
- if (matchStartNode === matchEndNode) {
- var node = matchStartNode;
- if (startPortion.indexInNode > 0) {
- // Add `before` text node (before the match)
- preceedingTextNode = doc.createTextNode(node.data.substring(0, startPortion.indexInNode));
- node.parentNode.insertBefore(preceedingTextNode, node);
- }
- // Create the replacement node:
- var newNode = this.getPortionReplacementNode(
- endPortion,
- match
- );
- node.parentNode.insertBefore(newNode, node);
- if (endPortion.endIndexInNode < node.length) { // ?????
- // Add `after` text node (after the match)
- followingTextNode = doc.createTextNode(node.data.substring(endPortion.endIndexInNode));
- node.parentNode.insertBefore(followingTextNode, node);
- }
- node.parentNode.removeChild(node);
- this.reverts.push(function() {
- if (preceedingTextNode === newNode.previousSibling) {
- preceedingTextNode.parentNode.removeChild(preceedingTextNode);
- }
- if (followingTextNode === newNode.nextSibling) {
- followingTextNode.parentNode.removeChild(followingTextNode);
- }
- newNode.parentNode.replaceChild(node, newNode);
- });
- return newNode;
- } else {
- // Replace matchStartNode -> [innerMatchNodes...] -> matchEndNode (in that order)
- preceedingTextNode = doc.createTextNode(
- matchStartNode.data.substring(0, startPortion.indexInNode)
- );
- followingTextNode = doc.createTextNode(
- matchEndNode.data.substring(endPortion.endIndexInNode)
- );
- var firstNode = this.getPortionReplacementNode(
- startPortion,
- match
- );
- var innerNodes = [];
- for (var i = 0, l = innerPortions.length; i < l; ++i) {
- var portion = innerPortions[i];
- var innerNode = this.getPortionReplacementNode(
- portion,
- match
- );
- portion.node.parentNode.replaceChild(innerNode, portion.node);
- this.reverts.push((function(portion, innerNode) {
- return function() {
- innerNode.parentNode.replaceChild(portion.node, innerNode);
- };
- }(portion, innerNode)));
- innerNodes.push(innerNode);
- }
- var lastNode = this.getPortionReplacementNode(
- endPortion,
- match
- );
- matchStartNode.parentNode.insertBefore(preceedingTextNode, matchStartNode);
- matchStartNode.parentNode.insertBefore(firstNode, matchStartNode);
- matchStartNode.parentNode.removeChild(matchStartNode);
- matchEndNode.parentNode.insertBefore(lastNode, matchEndNode);
- matchEndNode.parentNode.insertBefore(followingTextNode, matchEndNode);
- matchEndNode.parentNode.removeChild(matchEndNode);
- this.reverts.push(function() {
- preceedingTextNode.parentNode.removeChild(preceedingTextNode);
- firstNode.parentNode.replaceChild(matchStartNode, firstNode);
- followingTextNode.parentNode.removeChild(followingTextNode);
- lastNode.parentNode.replaceChild(matchEndNode, lastNode);
- });
- return lastNode;
- }
- }
- };
- return exposed;
- }());