PageSourceSearch

https://www.haldensee-hotel.com/(cms)/module/static/default/conten…/findAndReplaceDOMText.js

js haldensee-hotel.com collected 2026-09-26 13:19:09 UTC 16,445 bytes, 631 lines download raw bytes

1/**
2 * findAndReplaceDOMText v 0.4.3
3 * @author James Padolsey http://james.padolsey.com
4 * @license http://unlicense.org/UNLICENSE
5 *
6 * Matches the text of a DOM node against a regular expression
7 * and replaces each match (or node-separated portions of the match)
8 * in the specified element.
9 */
10 (function (root, factory) {
11     if (typeof module === 'object' && module.exports) {
12         // Node/CommonJS
13         module.exports = factory();
14     } else if (typeof define === 'function' && define.amd) {
15         // AMD. Register as an anonymous module.
16         define(factory);
17     } else {
18         // Browser globals
19         root.findAndReplaceDOMText = factory();
20     }
21 }(this, function factory() {
22
23	var PORTION_MODE_RETAIN = 'retain';
24	var PORTION_MODE_FIRST = 'first';
25
26	var doc = document;
27	var toString = {}.toString;
28	var hasOwn = {}.hasOwnProperty;
29
30	function isArray(a) {
31		return toString.call(a) == '[object Array]';
32	}
33
34	function escapeRegExp(s) {
35		return String(s).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1');
36	}
37
38	function exposed() {
39		// Try deprecated arg signature first:
40		return deprecated.apply(null, arguments) || findAndReplaceDOMText.apply(null, arguments);
41	}
42
43	function deprecated(regex, node, replacement, captureGroup, elFilter) {
44		if ((node && !node.nodeType) && arguments.length <= 2) {
45			return false;
46		}
47		var isReplacementFunction = typeof replacement == 'function';
48
49		if (isReplacementFunction) {
50			replacement = (function(original) {
51				return function(portion, match) {
52					return original(portion.text, match.startIndex);
53				};
54			}(replacement));
55		}
56
57		// Awkward support for deprecated argument signature (<0.4.0)
58		var instance = findAndReplaceDOMText(node, {
59
60			find: regex,
61
62			wrap: isReplacementFunction ? null : replacement,
63			replace: isReplacementFunction ? replacement : '$' + (captureGroup || '&'),
64
65			prepMatch: function(m, mi) {
66
67				// Support captureGroup (a deprecated feature)
68
69				if (!m[0]) throw 'findAndReplaceDOMText cannot handle zero-length matches';
70
71				if (captureGroup > 0) {
72					var cg = m[captureGroup];
73					m.index += m[0].indexOf(cg);
74					m[0] = cg;
75				}
76		 
77				m.endIndex = m.index + m[0].length;
78				m.startIndex = m.index;
79				m.index = mi;
80
81				return m;
82			},
83			filterElements: elFilter
84		});
85
86		exposed.revert = function() {
87			return instance.revert();
88		};
89
90		return true;
91	}
92
93	/** 
94	 * findAndReplaceDOMText
95	 * 
96	 * Locates matches and replaces with replacementNode
97	 *
98	 * @param {Node} node Element or Text node to search within
99	 * @param {RegExp} options.find The regular expression to match
100	 * @param {String|Element} [options.wrap] A NodeName, or a Node to clone
101	 * @param {String|Function} [options.replace='$&'] What to replace each match with
102	 * @param {Function} [options.filterElements] A Function to be called to check whether to
103	 *	process an element. (returning true = process element,
104	 *	returning false = avoid element)
105	 */
106	function findAndReplaceDOMText(node, options) {
107		return new Finder(node, options);
108	}
109
110	exposed.NON_PROSE_ELEMENTS = {
111		br:1, hr:1,
112		// Media / Source elements:
113		script:1, style:1, img:1, video:1, audio:1, canvas:1, svg:1, map:1, object:1,
114		// Input elements
115		input:1, textarea:1, select:1, option:1, optgroup: 1, button:1
116	};
117
118	exposed.NON_CONTIGUOUS_PROSE_ELEMENTS = {
119
120		// Elements that will not contain prose or block elements where we don't
121		// want prose to be matches across element borders:
122
123		// Block Elements
124		address:1, article:1, aside:1, blockquote:1, dd:1, div:1,
125		dl:1, fieldset:1, figcaption:1, figure:1, footer:1, form:1, h1:1, h2:1, h3:1,
126		h4:1, h5:1, h6:1, header:1, hgroup:1, hr:1, main:1, nav:1, noscript:1, ol:1,
127		output:1, p:1, pre:1, section:1, ul:1,
128		// Other misc. elements that are not part of continuous inline prose:
129		br:1, li: 1, summary: 1, dt:1, details:1, rp:1, rt:1, rtc:1,
130		// Media / Source elements:
131		script:1, style:1, img:1, video:1, audio:1, canvas:1, svg:1, map:1, object:1,
132		// Input elements
133		input:1, textarea:1, select:1, option:1, optgroup: 1, button:1,
134		// Table related elements:
135		table:1, tbody:1, thead:1, th:1, tr:1, td:1, caption:1, col:1, tfoot:1, colgroup:1
136
137	};
138
139	exposed.NON_INLINE_PROSE = function(el) {
140		return hasOwn.call(exposed.NON_CONTIGUOUS_PROSE_ELEMENTS, el.nodeName.toLowerCase());
141	};
142
143	// Presets accessed via `options.preset` when calling findAndReplaceDOMText():
144	exposed.PRESETS = {
145		prose: {
146			forceContext: exposed.NON_INLINE_PROSE,
147			filterElements: function(el) {
148				return !hasOwn.call(exposed.NON_PROSE_ELEMENTS, el.nodeName.toLowerCase());
149			}
150		}
151	};
152
153	exposed.Finder = Finder;
154
155	/**
156	 * Finder -- encapsulates logic to find and replace.
157	 */
158	function Finder(node, options) {
159
160		var preset = options.preset && exposed.PRESETS[options.preset];
161
162		options.portionMode = options.portionMode || PORTION_MODE_RETAIN;
163
164		if (preset) {
165			for (var i in preset) {
166				if (hasOwn.call(preset, i) && !hasOwn.call(options, i)) {
167					options[i] = preset[i];
168				}
169			}
170		}
171
172		this.node = node;
173		this.options = options;
174
175		// ENable match-preparation method to be passed as option:
176		this.prepMatch = options.prepMatch || this.prepMatch;
177
178		this.reverts = [];
179
180		this.matches = this.search();
181
182		if (this.matches.length) {
183			this.processMatches();
184		}
185
186	}
187
188	Finder.prototype = {
189
190		/**
191		 * Searches for all matches that comply with the instance's 'match' option
192		 */
193		search: function() {
194
195			var match;
196			var matchIndex = 0;
197			var offset = 0;
198			var regex = this.options.find;
199			var textAggregation = this.getAggregateText();
200			var matches = [];
201			var self = this;
202
203			regex = typeof regex === 'string' ? RegExp(escapeRegExp(regex), 'g') : regex;
204
205			matchAggregation(textAggregation);
206
207			function matchAggregation(textAggregation) {
208				for (var i = 0, l = textAggregation.length; i < l; ++i) {
209
210					var text = textAggregation[i];
211
212					if (typeof text !== 'string') {
213						// Deal with nested contexts: (recursive)
214						matchAggregation(text);
215						continue;
216					}
217
218					if (regex.global) {
219						while (match = regex.exec(text)) {
220							matches.push(self.prepMatch(match, matchIndex++, offset));
221						}
222					} else {
223						if (match = text.match(regex)) {
224							matches.push(self.prepMatch(match, 0, offset));
225						}
226					}
227
228					offset += text.length;
229				}
230			}
231
232			return matches;
233
234		},
235
236		/**
237		 * Prepares a single match with useful meta info:
238		 */
239		prepMatch: function(match, matchIndex, characterOffset) {
240
241			if (!match[0]) {
242				throw new Error('findAndReplaceDOMText cannot handle zero-length matches');
243			}
244	 
245			match.endIndex = characterOffset + match.index + match[0].length;
246			match.startIndex = characterOffset + match.index;
247			match.index = matchIndex;
248
249			return match;
250		},
251
252		/**
253		 * Gets aggregate text within subject node
254		 */
255		getAggregateText: function() {
256
257			var elementFilter = this.options.filterElements;
258			var forceContext = this.options.forceContext;
259
260			return getText(this.node);
261
262			/**
263			 * Gets aggregate text of a node without resorting
264			 * to broken innerText/textContent
265			 */
266			function getText(node, txt) {
267
268				if (node.nodeType === 3) {
269					return [node.data];
270				}
271
272				if (elementFilter && !elementFilter(node)) {
273					return [];
274				}
275
276				var txt = [''];
277				var i = 0;
278
279				if (node = node.firstChild) do {
280
281					if (node.nodeType === 3) {
282						txt[i] += node.data;
283						continue;
284					}
285
286					var innerText = getText(node);
287
288					if (
289						forceContext &&
290						node.nodeType === 1 &&
291						(forceContext === true || forceContext(node))
292					) {
293						txt[++i] = innerText;
294						txt[++i] = '';
295					} else {
296						if (typeof innerText[0] === 'string') {
297							// Bridge nested text-node data so that they're
298							// not considered their own contexts:
299							// I.e. ['some', ['thing']] -> ['something']
300							txt[i] += innerText.shift();
301						}
302						if (innerText.length) {
303							txt[++i] = innerText;
304							txt[++i] = '';
305						}
306					}
307				} while (node = node.nextSibling);
308
309				return txt;
310
311			}
312			
313		},
314
315		/** 
316		 * Steps through the target node, looking for matches, and
317		 * calling replaceFn when a match is found.
318		 */
319		processMatches: function() {
320
321			var matches = this.matches;
322			var node = this.node;
323			var elementFilter = this.options.filterElements;
324
325			var startPortion,
326				endPortion,
327				innerPortions = [],
328				curNode = node,
329				match = matches.shift(),
330				atIndex = 0, // i.e. nodeAtIndex
331				matchIndex = 0,
332				portionIndex = 0,
333				doAvoidNode,
334				nodeStack = [node];
335
336			out: while (true) {
337
338				if (curNode.nodeType === 3) {
339
340					if (!endPortion && curNode.length + atIndex >= match.endIndex) {
341
342						// We've found the ending
343						endPortion = {
344							node: curNode,
345							index: portionIndex++,
346							text: curNode.data.substring(match.startIndex - atIndex, match.endIndex - atIndex),
347							indexInMatch: atIndex - match.startIndex,
348							indexInNode: match.startIndex - atIndex, // always zero for end-portions
349							endIndexInNode: match.endIndex - atIndex,
350							isEnd: true
351						};
352
353					} else if (startPortion) {
354						// Intersecting node
355						innerPortions.push({
356							node: curNode,
357							index: portionIndex++,
358							text: curNode.data,
359							indexInMatch: atIndex - match.startIndex,
360							indexInNode: 0 // always zero for inner-portions
361						});
362					}
363
364					if (!startPortion && curNode.length + atIndex > match.startIndex) {
365						// We've found the match start
366						startPortion = {
367							node: curNode,
368							index: portionIndex++,
369							indexInMatch: 0,
370							indexInNode: match.startIndex - atIndex,
371							endIndexInNode: match.endIndex - atIndex,
372							text: curNode.data.substring(match.startIndex - atIndex, match.endIndex - atIndex)
373						};
374					}
375
376					atIndex += curNode.data.length;
377
378				}
379
380				doAvoidNode = curNode.nodeType === 1 && elementFilter && !elementFilter(curNode);
381
382				if (startPortion && endPortion) {
383
384					curNode = this.replaceMatch(match, startPortion, innerPortions, endPortion);
385
386					// processMatches has to return the node that replaced the endNode
387					// and then we step back so we can continue from the end of the 
388					// match:
389
390					atIndex -= (endPortion.node.data.length - endPortion.endIndexInNode);
391
392					startPortion = null;
393					endPortion = null;
394					innerPortions = [];
395					match = matches.shift();
396					portionIndex = 0;
397					matchIndex++;
398
399					if (!match) {
400						break; // no more matches
401					}
402
403				} else if (
404					!doAvoidNode &&
405					(curNode.firstChild || curNode.nextSibling)
406				) {
407					// Move down or forward:
408					if (curNode.firstChild) {
409						nodeStack.push(curNode);
410						curNode = curNode.firstChild;
411					} else {
412						curNode = curNode.nextSibling;
413					}
414					continue;
415				}
416
417				// Move forward or up:
418				while (true) {
419					if (curNode.nextSibling) {
420						curNode = curNode.nextSibling;
421						break;
422					}
423					curNode = nodeStack.pop();
424					if (curNode === node) {
425						break out;
426					}
427				}
428
429			}
430
431		},
432
433		/**
434		 * Reverts ... TODO
435		 */
436		revert: function() {
437			// Reversion occurs backwards so as to avoid nodes subsequently
438			// replaced during the matching phase (a forward process):
439			for (var l = this.reverts.length; l--;) {
440				this.reverts[l]();
441			}
442			this.reverts = [];
443		},
444
445		prepareReplacementString: function(string, portion, match, matchIndex) {
446			var portionMode = this.options.portionMode;
447			if (
448				portionMode === PORTION_MODE_FIRST &&
449				portion.indexInMatch > 0
450			) {
451				return '';
452			}
453			string = string.replace(/\$(\d+|&|`|')/g, function($0, t) {
454				var replacement;
455				switch(t) {
456					case '&':
457						replacement = match[0];
458						break;
459					case '`':
460						replacement = match.input.substring(0, match.startIndex);
461						break;
462					case '\'':
463						replacement = match.input.substring(match.endIndex);
464						break;
465					default:
466						replacement = match[+t];
467				}
468				return replacement;
469			});
470
471			if (portionMode === PORTION_MODE_FIRST) {
472				return string;
473			}
474
475			if (portion.isEnd) {
476				return string.substring(portion.indexInMatch);
477			}
478
479			return string.substring(portion.indexInMatch, portion.indexInMatch + portion.text.length);
480		},
481
482		getPortionReplacementNode: function(portion, match, matchIndex) {
483
484			var replacement = this.options.replace || '$&';
485			var wrapper = this.options.wrap;
486
487			if (wrapper && wrapper.nodeType) {
488				// Wrapper has been provided as a stencil-node for us to clone:
489				var clone = doc.createElement('div');
490				clone.innerHTML = wrapper.outerHTML || new XMLSerializer().serializeToString(wrapper);
491				wrapper = clone.firstChild;
492			}
493
494			if (typeof replacement == 'function') {
495				replacement = replacement(portion, match, matchIndex);
496				if (replacement && replacement.nodeType) {
497					return replacement;
498				}
499				return doc.createTextNode(String(replacement));
500			}
501
502			var el = typeof wrapper == 'string' ? doc.createElement(wrapper) : wrapper;
503
504			replacement = doc.createTextNode(
505				this.prepareReplacementString(
506					replacement, portion, match, matchIndex
507				)
508			);
509
510			if (!replacement.data) {
511				return replacement;
512			}
513
514			if (!el) {
515				return replacement;
516			}
517
518			el.appendChild(replacement);
519
520			return el;
521		},
522
523		replaceMatch: function(match, startPortion, innerPortions, endPortion) {
524
525			var matchStartNode = startPortion.node;
526			var matchEndNode = endPortion.node;
527
528			var preceedingTextNode;
529			var followingTextNode;
530
531			if (matchStartNode === matchEndNode) {
532
533				var node = matchStartNode;
534
535				if (startPortion.indexInNode > 0) {
536					// Add `before` text node (before the match)
537					preceedingTextNode = doc.createTextNode(node.data.substring(0, startPortion.indexInNode));
538					node.parentNode.insertBefore(preceedingTextNode, node);
539				}
540
541				// Create the replacement node:
542				var newNode = this.getPortionReplacementNode(
543					endPortion,
544					match
545				);
546
547				node.parentNode.insertBefore(newNode, node);
548
549				if (endPortion.endIndexInNode < node.length) { // ?????
550					// Add `after` text node (after the match)
551					followingTextNode = doc.createTextNode(node.data.substring(endPortion.endIndexInNode));
552					node.parentNode.insertBefore(followingTextNode, node);
553				}
554
555				node.parentNode.removeChild(node);
556
557				this.reverts.push(function() {
558					if (preceedingTextNode === newNode.previousSibling) {
559						preceedingTextNode.parentNode.removeChild(preceedingTextNode);
560					}
561					if (followingTextNode === newNode.nextSibling) {
562						followingTextNode.parentNode.removeChild(followingTextNode);
563					}
564					newNode.parentNode.replaceChild(node, newNode);
565				});
566
567				return newNode;
568
569			} else {
570				// Replace matchStartNode -> [innerMatchNodes...] -> matchEndNode (in that order)
571
572
573				preceedingTextNode = doc.createTextNode(
574					matchStartNode.data.substring(0, startPortion.indexInNode)
575				);
576
577				followingTextNode = doc.createTextNode(
578					matchEndNode.data.substring(endPortion.endIndexInNode)
579				);
580
581				var firstNode = this.getPortionReplacementNode(
582					startPortion,
583					match
584				);
585
586				var innerNodes = [];
587
588				for (var i = 0, l = innerPortions.length; i < l; ++i) {
589					var portion = innerPortions[i];
590					var innerNode = this.getPortionReplacementNode(
591						portion,
592						match
593					);
594					portion.node.parentNode.replaceChild(innerNode, portion.node);
595					this.reverts.push((function(portion, innerNode) {
596						return function() {
597							innerNode.parentNode.replaceChild(portion.node, innerNode);
598						};
599					}(portion, innerNode)));
600					innerNodes.push(innerNode);
601				}
602
603				var lastNode = this.getPortionReplacementNode(
604					endPortion,
605					match
606				);
607
608				matchStartNode.parentNode.insertBefore(preceedingTextNode, matchStartNode);
609				matchStartNode.parentNode.insertBefore(firstNode, matchStartNode);
610				matchStartNode.parentNode.removeChild(matchStartNode);
611
612				matchEndNode.parentNode.insertBefore(lastNode, matchEndNode);
613				matchEndNode.parentNode.insertBefore(followingTextNode, matchEndNode);
614				matchEndNode.parentNode.removeChild(matchEndNode);
615
616				this.reverts.push(function() {
617					preceedingTextNode.parentNode.removeChild(preceedingTextNode);
618					firstNode.parentNode.replaceChild(matchStartNode, firstNode);
619					followingTextNode.parentNode.removeChild(followingTextNode);
620					lastNode.parentNode.replaceChild(matchEndNode, lastNode);
621				});
622
623				return lastNode;
624			}
625		}
626
627	};
628
629	return exposed;
630
631}));

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.