src/pyams_skin/resources/js/ext/tinymce/dev/plugins/spellchecker/classes/DomTextMatcher.js
changeset 557 bca7a7e058a3
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_skin/resources/js/ext/tinymce/dev/plugins/spellchecker/classes/DomTextMatcher.js	Thu Feb 13 11:43:31 2020 +0100
@@ -0,0 +1,470 @@
+/**
+ * DomTextMatcher.js
+ *
+ * Copyright, Moxiecode Systems AB
+ * Released under LGPL License.
+ *
+ * License: http://www.tinymce.com/license
+ * Contributing: http://www.tinymce.com/contributing
+ */
+
+/*eslint no-labels:0, no-constant-condition: 0 */
+
+/**
+ * This class logic for filtering text and matching words.
+ *
+ * @class tinymce.spellcheckerplugin.TextFilter
+ * @private
+ */
+define("tinymce/spellcheckerplugin/DomTextMatcher", [], function() {
+	// Based on work developed by: James Padolsey http://james.padolsey.com
+	// released under UNLICENSE that is compatible with LGPL
+	// TODO: Handle contentEditable edgecase:
+	// <p>text<span contentEditable="false">text<span contentEditable="true">text</span>text</span>text</p>
+	return function(node, editor) {
+		var m, matches = [], text, dom = editor.dom;
+		var blockElementsMap, hiddenTextElementsMap, shortEndedElementsMap;
+
+		blockElementsMap = editor.schema.getBlockElements(); // H1-H6, P, TD etc
+		hiddenTextElementsMap = editor.schema.getWhiteSpaceElements(); // TEXTAREA, PRE, STYLE, SCRIPT
+		shortEndedElementsMap = editor.schema.getShortEndedElements(); // BR, IMG, INPUT
+
+		function createMatch(m, data) {
+			if (!m[0]) {
+				throw 'findAndReplaceDOMText cannot handle zero-length matches';
+			}
+
+			return {
+				start: m.index,
+				end: m.index + m[0].length,
+				text: m[0],
+				data: data
+			};
+		}
+
+		function getText(node) {
+			var txt;
+
+			if (node.nodeType === 3) {
+				return node.data;
+			}
+
+			if (hiddenTextElementsMap[node.nodeName] && !blockElementsMap[node.nodeName]) {
+				return '';
+			}
+
+			txt = '';
+
+			if (blockElementsMap[node.nodeName] || shortEndedElementsMap[node.nodeName]) {
+				txt += '\n';
+			}
+
+			if ((node = node.firstChild)) {
+				do {
+					txt += getText(node);
+				} while ((node = node.nextSibling));
+			}
+
+			return txt;
+		}
+
+		function stepThroughMatches(node, matches, replaceFn) {
+			var startNode, endNode, startNodeIndex,
+				endNodeIndex, innerNodes = [], atIndex = 0, curNode = node,
+				matchLocation, matchIndex = 0;
+
+			matches = matches.slice(0);
+			matches.sort(function(a, b) {
+				return a.start - b.start;
+			});
+
+			matchLocation = matches.shift();
+
+			out: while (true) {
+				if (blockElementsMap[curNode.nodeName] || shortEndedElementsMap[curNode.nodeName]) {
+					atIndex++;
+				}
+
+				if (curNode.nodeType === 3) {
+					if (!endNode && curNode.length + atIndex >= matchLocation.end) {
+						// We've found the ending
+						endNode = curNode;
+						endNodeIndex = matchLocation.end - atIndex;
+					} else if (startNode) {
+						// Intersecting node
+						innerNodes.push(curNode);
+					}
+
+					if (!startNode && curNode.length + atIndex > matchLocation.start) {
+						// We've found the match start
+						startNode = curNode;
+						startNodeIndex = matchLocation.start - atIndex;
+					}
+
+					atIndex += curNode.length;
+				}
+
+				if (startNode && endNode) {
+					curNode = replaceFn({
+						startNode: startNode,
+						startNodeIndex: startNodeIndex,
+						endNode: endNode,
+						endNodeIndex: endNodeIndex,
+						innerNodes: innerNodes,
+						match: matchLocation.text,
+						matchIndex: matchIndex
+					});
+
+					// replaceFn 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 -= (endNode.length - endNodeIndex);
+					startNode = null;
+					endNode = null;
+					innerNodes = [];
+					matchLocation = matches.shift();
+					matchIndex++;
+
+					if (!matchLocation) {
+						break; // no more matches
+					}
+				} else if ((!hiddenTextElementsMap[curNode.nodeName] || blockElementsMap[curNode.nodeName]) && curNode.firstChild) {
+					// Move down
+					curNode = curNode.firstChild;
+					continue;
+				} else if (curNode.nextSibling) {
+					// Move forward:
+					curNode = 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;
+					}
+				}
+			}
+		}
+
+		/**
+		* Generates the actual replaceFn which splits up text nodes
+		* and inserts the replacement element.
+		*/
+		function genReplacer(callback) {
+			function makeReplacementNode(fill, matchIndex) {
+				var match = matches[matchIndex];
+
+				if (!match.stencil) {
+					match.stencil = callback(match);
+				}
+
+				var clone = match.stencil.cloneNode(false);
+				clone.setAttribute('data-mce-index', matchIndex);
+
+				if (fill) {
+					clone.appendChild(dom.doc.createTextNode(fill));
+				}
+
+				return clone;
+			}
+
+			return function(range) {
+				var before, after, parentNode, startNode = range.startNode,
+					endNode = range.endNode, matchIndex = range.matchIndex,
+					doc = dom.doc;
+
+				if (startNode === endNode) {
+					var node = startNode;
+
+					parentNode = node.parentNode;
+					if (range.startNodeIndex > 0) {
+						// Add "before" text node (before the match)
+						before = doc.createTextNode(node.data.substring(0, range.startNodeIndex));
+						parentNode.insertBefore(before, node);
+					}
+
+					// Create the replacement node:
+					var el = makeReplacementNode(range.match, matchIndex);
+					parentNode.insertBefore(el, node);
+					if (range.endNodeIndex < node.length) {
+						// Add "after" text node (after the match)
+						after = doc.createTextNode(node.data.substring(range.endNodeIndex));
+						parentNode.insertBefore(after, node);
+					}
+
+					node.parentNode.removeChild(node);
+
+					return el;
+				} else {
+					// Replace startNode -> [innerNodes...] -> endNode (in that order)
+					before = doc.createTextNode(startNode.data.substring(0, range.startNodeIndex));
+					after = doc.createTextNode(endNode.data.substring(range.endNodeIndex));
+					var elA = makeReplacementNode(startNode.data.substring(range.startNodeIndex), matchIndex);
+					var innerEls = [];
+
+					for (var i = 0, l = range.innerNodes.length; i < l; ++i) {
+						var innerNode = range.innerNodes[i];
+						var innerEl = makeReplacementNode(innerNode.data, matchIndex);
+						innerNode.parentNode.replaceChild(innerEl, innerNode);
+						innerEls.push(innerEl);
+					}
+
+					var elB = makeReplacementNode(endNode.data.substring(0, range.endNodeIndex), matchIndex);
+
+					parentNode = startNode.parentNode;
+					parentNode.insertBefore(before, startNode);
+					parentNode.insertBefore(elA, startNode);
+					parentNode.removeChild(startNode);
+
+					parentNode = endNode.parentNode;
+					parentNode.insertBefore(elB, endNode);
+					parentNode.insertBefore(after, endNode);
+					parentNode.removeChild(endNode);
+
+					return elB;
+				}
+			};
+		}
+
+		function unwrapElement(element) {
+			var parentNode = element.parentNode;
+			parentNode.insertBefore(element.firstChild, element);
+			element.parentNode.removeChild(element);
+		}
+
+		function getWrappersByIndex(index) {
+			var elements = node.getElementsByTagName('*'), wrappers = [];
+
+			index = typeof index == "number" ? "" + index : null;
+
+			for (var i = 0; i < elements.length; i++) {
+				var element = elements[i], dataIndex = element.getAttribute('data-mce-index');
+
+				if (dataIndex !== null && dataIndex.length) {
+					if (dataIndex === index || index === null) {
+						wrappers.push(element);
+					}
+				}
+			}
+
+			return wrappers;
+		}
+
+		/**
+		 * Returns the index of a specific match object or -1 if it isn't found.
+		 *
+		 * @param  {Match} match Text match object.
+		 * @return {Number} Index of match or -1 if it isn't found.
+		 */
+		function indexOf(match) {
+			var i = matches.length;
+			while (i--) {
+				if (matches[i] === match) {
+					return i;
+				}
+			}
+
+			return -1;
+		}
+
+		/**
+		 * Filters the matches. If the callback returns true it stays if not it gets removed.
+		 *
+		 * @param {Function} callback Callback to execute for each match.
+		 * @return {DomTextMatcher} Current DomTextMatcher instance.
+		 */
+		function filter(callback) {
+			var filteredMatches = [];
+
+			each(function(match, i) {
+				if (callback(match, i)) {
+					filteredMatches.push(match);
+				}
+			});
+
+			matches = filteredMatches;
+
+			/*jshint validthis:true*/
+			return this;
+		}
+
+		/**
+		 * Executes the specified callback for each match.
+		 *
+		 * @param {Function} callback  Callback to execute for each match.
+		 * @return {DomTextMatcher} Current DomTextMatcher instance.
+		 */
+		function each(callback) {
+			for (var i = 0, l = matches.length; i < l; i++) {
+				if (callback(matches[i], i) === false) {
+					break;
+				}
+			}
+
+			/*jshint validthis:true*/
+			return this;
+		}
+
+		/**
+		 * Wraps the current matches with nodes created by the specified callback.
+		 * Multiple clones of these matches might occur on matches that are on multiple nodex.
+		 *
+		 * @param {Function} callback Callback to execute in order to create elements for matches.
+		 * @return {DomTextMatcher} Current DomTextMatcher instance.
+		 */
+		function wrap(callback) {
+			if (matches.length) {
+				stepThroughMatches(node, matches, genReplacer(callback));
+			}
+
+			/*jshint validthis:true*/
+			return this;
+		}
+
+		/**
+		 * Finds the specified regexp and adds them to the matches collection.
+		 *
+		 * @param {RegExp} regex Global regexp to search the current node by.
+		 * @param {Object} [data] Optional custom data element for the match.
+		 * @return {DomTextMatcher} Current DomTextMatcher instance.
+		 */
+		function find(regex, data) {
+			if (text && regex.global) {
+				while ((m = regex.exec(text))) {
+					matches.push(createMatch(m, data));
+				}
+			}
+
+			return this;
+		}
+
+		/**
+		 * Unwraps the specified match object or all matches if unspecified.
+		 *
+		 * @param {Object} [match] Optional match object.
+		 * @return {DomTextMatcher} Current DomTextMatcher instance.
+		 */
+		function unwrap(match) {
+			var i, elements = getWrappersByIndex(match ? indexOf(match) : null);
+
+			i = elements.length;
+			while (i--) {
+				unwrapElement(elements[i]);
+			}
+
+			return this;
+		}
+
+		/**
+		 * Returns a match object by the specified DOM element.
+		 *
+		 * @param {DOMElement} element Element to return match object for.
+		 * @return {Object} Match object for the specified element.
+		 */
+		function matchFromElement(element) {
+			return matches[element.getAttribute('data-mce-index')];
+		}
+
+		/**
+		 * Returns a DOM element from the specified match element. This will be the first element if it's split
+		 * on multiple nodes.
+		 *
+		 * @param {Object} match Match element to get first element of.
+		 * @return {DOMElement} DOM element for the specified match object.
+		 */
+		function elementFromMatch(match) {
+			return getWrappersByIndex(indexOf(match))[0];
+		}
+
+		/**
+		 * Adds match the specified range for example a grammar line.
+		 *
+		 * @param {Number} start Start offset.
+		 * @param {Number} length Length of the text.
+		 * @param {Object} data Custom data object for match.
+		 * @return {DomTextMatcher} Current DomTextMatcher instance.
+		 */
+		function add(start, length, data) {
+			matches.push({
+				start: start,
+				end: start + length,
+				text: text.substr(start, length),
+				data: data
+			});
+
+			return this;
+		}
+
+		/**
+		 * Returns a DOM range for the specified match.
+		 *
+		 * @param  {Object} match Match object to get range for.
+		 * @return {DOMRange} DOM Range for the specified match.
+		 */
+		function rangeFromMatch(match) {
+			var wrappers = getWrappersByIndex(indexOf(match));
+
+			var rng = editor.dom.createRng();
+			rng.setStartBefore(wrappers[0]);
+			rng.setEndAfter(wrappers[wrappers.length - 1]);
+
+			return rng;
+		}
+
+		/**
+		 * Replaces the specified match with the specified text.
+		 *
+		 * @param {Object} match Match object to replace.
+		 * @param {String} text Text to replace the match with.
+		 * @return {DOMRange} DOM range produced after the replace.
+		 */
+		function replace(match, text) {
+			var rng = rangeFromMatch(match);
+
+			rng.deleteContents();
+
+			if (text.length > 0) {
+				rng.insertNode(editor.dom.doc.createTextNode(text));
+			}
+
+			return rng;
+		}
+
+		/**
+		 * Resets the DomTextMatcher instance. This will remove any wrapped nodes and remove any matches.
+		 *
+		 * @return {[type]} [description]
+		 */
+		function reset() {
+			matches.splice(0, matches.length);
+			unwrap();
+
+			return this;
+		}
+
+		text = getText(node);
+
+		return {
+			text: text,
+			matches: matches,
+			each: each,
+			filter: filter,
+			reset: reset,
+			matchFromElement: matchFromElement,
+			elementFromMatch: elementFromMatch,
+			find: find,
+			add: add,
+			wrap: wrap,
+			unwrap: unwrap,
+			replace: replace,
+			rangeFromMatch: rangeFromMatch,
+			indexOf: indexOf
+		};
+	};
+});