Text operations: Detect replacement from clipboard - javascript

General Info
Working on my own implementation of the operational transformation algorithm. For those that don't know what this is: When multiple users work on the same document at the same time, this algorithm attempts to preserve each users intention and make sure all users end up with the same document.
The problem
To begin, I need a proper way of detecting text operations. Like insert and delete. Obviously I need to know exactly at which position this is happening so each operation can be correctly transformed by the server to preserve the intention of other users.
My code so far is doing a pretty decent job at this. But it gets in trouble when selecting a text range and replacing it with another. I rely on the input event for this, and it seems to be unable to detect both delete and insert operations at the same time. When doing this, it detects a delete operation on the selected text. But it does not detect the insert operation of the text pasted in from the clipboard.
My question is: How can I solve this issue?
My code (so far)
let txtArea = {};
let cursorPos = {};
let clientDoc = ""; // Shadow DOC
document.addEventListener("DOMContentLoaded", function(event){
txtArea = document.getElementById("test");
clientDoc = txtArea.value;
txtArea.addEventListener("input", function(){ handleInput(); });
txtArea.addEventListener("click", function(){ handleSelect(); });
});
/* Gets cursor position / selected text range */
function handleSelect(){
cursorPos = getCursorPos(txtArea);
}
/* Check whether the operation is insert or delete */
function handleInput(){
if(txtArea.value > clientDoc){
handleOperation("insert");
} else {
handleOperation("delete");
}
}
/* Checks text difference to know exactly what happened */
function handleOperation(operation){
let lines = "";
if(operation === "insert"){
lines = getDifference(clientDoc, txtArea.value);
} else if(operation === "delete"){
lines = getDifference(txtArea.value, clientDoc);
}
const obj = {
operation: operation,
lines: lines,
position: cursorPos
};
clientDoc = txtArea.value;
console.log(obj);
}
/* Simple function to get difference between 2 strings */
function getDifference(a, b)
{
let i = 0;
let j = 0;
let result = "";
while (j < b.length)
{
if (a[i] != b[j] || i == a.length){
result += b[j];
} else {
i++;
}
j++;
}
return result;
}
/* Function to get cursor position / selection range */
function getCursorPos(input) {
if ("selectionStart" in input && document.activeElement == input) {
return {
start: input.selectionStart,
end: input.selectionEnd
};
}
else if (input.createTextRange) {
var sel = document.selection.createRange();
if (sel.parentElement() === input) {
var rng = input.createTextRange();
rng.moveToBookmark(sel.getBookmark());
for (var len = 0;
rng.compareEndPoints("EndToStart", rng) > 0;
rng.moveEnd("character", -1)) {
len++;
}
rng.setEndPoint("StartToStart", input.createTextRange());
for (var pos = { start: 0, end: len };
rng.compareEndPoints("EndToStart", rng) > 0;
rng.moveEnd("character", -1)) {
pos.start++;
pos.end++;
}
return pos;
}
}
return -1;
}
#test {
width: 600px;
height: 400px;
}
<textarea id="test">test</textarea>

Managed to solve the problem myself, though not entirely sure if it's the best solution. I've used comments within the code to explain how I solved it:
function handleOperation(operation){
let lines = "";
if(operation === "insert"){
lines = getDifference(clientDoc, txtArea.value);
} else if(operation === "delete"){
lines = getDifference(txtArea.value, clientDoc);
}
// This handles situations where text is being selected and replaced
if(operation === "delete"){
// Create temporary shadow doc with the delete operation finished
const tempDoc = clientDoc.substr(0, cursorPos.start) + clientDoc.substr(cursorPos.end);
// In case the tempDoc is different from the actual textarea value, we know for sure we missed an insert operation
if(tempDoc !== txtArea.value){
let foo = "";
if(tempDoc.length > txtArea.value.length){
foo = getDifference(txtArea.value, tempDoc);
} else {
foo = getDifference(tempDoc, txtArea.value);
}
console.log("char(s) replaced detected: "+foo);
}
} else if(operation === "insert"){
// No need for a temporary shadow doc. Insert will always add length to our shadow doc. So if anything is replaced,
// the actual textarea length will never match
if(clientDoc.length + lines.length !== txtArea.value.length){
let foo = "";
if(clientDoc.length > txtArea.value.length){
foo = getDifference(txtArea.value, clientDoc);
} else {
foo = getDifference(clientDoc, txtArea.value);
}
console.log("char(s) removed detected: "+foo);
}
}
const obj = {
operation: operation,
lines: lines,
position: cursorPos
};
// Update our shadow doc
clientDoc = txtArea.value;
// Debugging
console.log(obj);
}
I'm still very much open to better solutions / tips / advise if you can give it to me.

Related

Javascript code not running, how can I fix?

My code is supposed to add 1 to the counter when I hit space and then change the magnifying glass to tilt the other direction. It just doesn't do anything when I hit space.
I've fixed every problem I saw, nothing worked. Maybe somebody else knows, I'm really not that good at js.
var hits = 0;
var hitElement = document.querySelector( '.hits' );
document.body.onkeydown = function(e) {
if( e.keyCode == 32 ) {
addHit();
}
}
var mag = "🔎";
var hitElement2 = document.querySelector( '.mag' );
document.body.onkeydown = function(f) {
if( f.keyCode == 32 ) {
if( mag.text == "🔎") {
mag = "🔍";
renderMag();
else
mag = "🔎";
renderMag();
}
}
}
var addHit = function() {
hits++;
renderHits();
}
var renderMag = function() {
hitElement2.innerHTML = mag;
}
var renderHits = function() {
hitElement.innerHTML = hits;
}
var resetHits = function() {
hits = 0;
renderHits();
}
Console says absolutely nothing too.
I fixed a few things and changed a few things that didn't need fixing. The three key points:
As mentioned, if a bit harshly, by Randy, your second event listener was overwriting your first, so hits was not increasing
The brackets in the if statement of your second event listener had some issues
Though checking the mag icon might work, using icons in the code feels like asking for something to break, so I changed that to a modulo check, to see whether hits was odd or even.
You were on the right track. When you have issues like this in the future, try putting console log statements in various places in the code. Make predictions about what variables should have what values at what points, then check if they do, then, if they don't, try to figure out why not.
var hits = 0;
var hitElement = document.querySelector('.hits');
var mag = "🔎";
var hitElement2 = document.querySelector('.mag');
document.body.onkeydown = function(f) {
if (f.key == ' ') {
addHit();
if (hits % 2 === 0) {
mag = "🔍";
renderMag();
} else {
mag = "🔎";
renderMag();
}
}
}
function addHit() {
hits++;
renderHits();
}
function renderMag() {
hitElement2.innerHTML = mag;
}
function renderHits() {
hitElement.innerHTML = hits;
}
function resetHits() {
hits = 0;
renderHits();
}
<div class="hits"></div>
<div class="mag"></div>

Illustrator JavaScript only works once - won't toggle

I'm writing a script which changes the colours of a stick-man's arms and legs. Once "he" is selected, it effectively just mirrors the colours:
//Declare and initialize variables
var msgType = "";
var app;
var docRef = app.activeDocument;
var testColor = docRef.swatches.getByName("CMYK Green");
var leftColor = docRef.swatches.getByName("LeftColor");
var rightColor = docRef.swatches.getByName("RightColor");
function sameColor(CMYKColor1, CMYKColor2) {
"use strict";
var isTheSameColor;
if ((CMYKColor1.cyan === CMYKColor2.cyan) && (CMYKColor1.magenta === CMYKColor2.magenta) && (CMYKColor1.yellow === CMYKColor2.yellow) && (CMYKColor1.black === CMYKColor2.black)) {
isTheSameColor = true;
} else {
isTheSameColor = false;
}
return isTheSameColor;
}
// check if a document is open in Illustrator.
if (app.documents.length > 0) {
var mySelection = app.activeDocument.selection;
var index;
// Loop through all selected objects
for (index = 0; index < mySelection.length; index += 1) {
// Switch left and right colours
if (sameColor(mySelection[index].strokeColor, leftColor.color)) {
mySelection[index].strokeColor = rightColor.color;
}
if (sameColor(mySelection[index].strokeColor, rightColor.color)) {
mySelection[index].strokeColor = leftColor.color;
}
if (sameColor(mySelection[index].fillColor, leftColor.color)) {
mySelection[index].fillColor = rightColor.color;
}
if (sameColor(mySelection[index].fillColor, rightColor.color)) {
mySelection[index].fillColor = leftColor.color;
}
}
}
It works, but it only works once (i.e. I can't toggle the change again). If I undo the change and try again it works again. Why is this?
After a lot of head-scratching / debugging it turns out that it was changing the CMYK values to be not quite the same (by a tiny fraction).
Changed the following:
if ((CMYKColor1.cyan === CMYKColor2.cyan) ...
to:
if ((Math.round(CMYKColor1.cyan) === Math.round(CMYKColor2.cyan)) ...
and it works fine now.

jQuery "keyup" crashing page when checking 'Word Count'

I have a word counter running on a DIV and after typing in a few words, the page crashes. The browser continues to work (par scrolling) and no errors are showing in Chrome's console. Not sure where I'm going wrong...
It all started when I passed "wordCount(q);" in "keyup". I only passed it there as it would split-out "NaN" instead of a number to countdown from.
JS:
wordCount();
$('#group_3_1').click(function(){
var spliced = 200;
wordCount(spliced);
}) ;
$('#group_3_2').click(function(){
var spliced = 600;
wordCount(spliced);
}) ;
function wordCount(q) {
var content_text = $('.message1').text(),
char_count = content_text.length;
if (char_count != 0)
var word_count = q - content_text.replace(/[^\w ]/g, "").split(/\s+/).length;
$('.word_count').html(word_count + " words remaining...");
$('.message1').keyup(function() {
wordCount(q);
});
try
{
if (new Number( word_count ) < 0) {
$(".word_count").attr("id","bad");
}
else {
$(".word_count").attr("id","good");
}
} catch (error)
{
//
}
};
HTML:
<input type="checkbox" name="entry.3.group" value="1/6" class="size1" id="group_3_1">
<input type="checkbox" name="entry.3.group" value="1/4" class="size1" id="group_3_2">
<div id="entry.8.single" class="message1" style="height: 400px; overflow-y:scroll; overflow-x:hidden;" contenteditable="true"> </div>
<span class="word_count" id="good"></span>
Thanks in advanced!
This is causing an infinite loop if (new Number(word_count) < 0) {.
Your code is a mess altogether. Just study and start with more basic concepts and start over. If you want to describe your project to me in a comment, I would be glad to show you a good, clean, readable approach.
Update:
Part of having a good architecture in your code is to keep different parts of your logic separate. No part of your code should know about or use anything that isn't directly relevant to it. Notice in my word counter that anything it does it immediately relevant to its word-counter-ness. Does a word counter care about what happens with the count? Nope. It just counts and sends the result away (wherever you tell it to, via the callback function). This isn't the only approach, but I just wanted to give you an idea of how to approach things more sensefully.
Live demo here (click).
/* what am I creating? A word counter.
* How do I want to use it?
* -Call a function, passing in an element and a callback function
* -Bind the word counter to that element
* -When the word count changes, pass the new count to the callback function
*/
window.onload = function() {
var countDiv = document.getElementById('count');
wordCounter.bind(countDiv, displayCount);
//you can pass in whatever function you want. I made one called displayCount, for example
};
var wordCounter = {
current : 0,
bind : function(elem, callback) {
this.ensureEditable(elem);
this.handleIfChanged(elem, callback);
var that = this;
elem.addEventListener('keyup', function(e) {
that.handleIfChanged(elem, callback);
});
},
handleIfChanged : function(elem, callback) {
var count = this.countWords(elem);
if (count !== this.current) {
this.current = count;
callback(count);
}
},
countWords : function(elem) {
var text = elem.textContent;
var words = text.match(/(\w+\b)/g);
return (words) ? words.length : 0;
},
ensureEditable : function(elem) {
if (
elem.getAttribute('contenteditable') !== 'true' &&
elem.nodeName !== 'TEXTAREA' &&
elem.nodeName !== 'INPUT'
) {
elem.setAttribute('contenteditable', true);
}
}
};
var display = document.getElementById('display');
function displayCount(count) {
//this function is called every time the word count changes
//do whatever you want...the word counter doesn't care.
display.textContent = 'Word count is: '+count;
}
I would do probably something like this
http://jsfiddle.net/6WW7Z/2/
var wordsLimit = 50;
$('#group_3_1').click(function () {
wordsLimit = 200;
wordCount();
});
$('#group_3_2').click(function () {
wordsLimit = 600;
wordCount();
});
$('.message1').keydown(function () {
wordCount();
});
function wordCount() {
var text = $('.message1').text(),
textLength = text.length,
wordsCount = 0,
wordsRemaining = wordsLimit;
if(textLength > 0) {
wordsCount = text.replace(/[^\w ]/g, '').split(/\s+/).length;
wordsRemaining = wordsRemaining - wordsCount;
}
$('.word_count')
.html(wordsRemaining + " words remaining...")
.attr('id', (parseInt(wordsRemaining) < 0 ? 'bad' : 'good'));
};
wordCount();
It's not perfect and complete but it may show you direction how to do this. You should use change event on checkboxes to change wordsLimit if checked/unchecked. For styling valid/invalid words remaining message use classes rather than ids.
I think you should use radio in place of checkboxes because you can limit 200 or 600 only at a time.
Try this like,
wordCount();
$('input[name="entry.3.group"]').click(function () {
wordCount();
$('.word_count').html($(this).data('val') + " words remaining...");
});
$('.message1').keyup(function () {
wordCount();
});
function wordCount() {
var q = $('input[name="entry.3.group"]:checked').data('val');
var content_text = $('.message1').text(),
char_count = content_text.length;
if (char_count != 0) var word_count = q - content_text.replace(/[^\w ]/g, "").split(/\s+/).length;
$('.word_count').html(word_count + " words remaining...");
try {
if (Number(word_count) < 0) {
$(".word_count").attr("id", "bad");
} else {
$(".word_count").attr("id", "good");
}
} catch (error) {
//
}
};
Also you can add if your span has bad id then key up should return false;
See Demo

Find out the 'line' (row) number of the cursor in a textarea

I would like to find out and keep track of the 'line number' (rows) of the cursor in a textarea. (The 'bigger picture' is to parse the text on the line every time a new line is created/modified/selected, if of course the text was not pasted in. This saves parsing the whole text un-necessarily at set intervals.)
There are a couple of posts on StackOverflow however none of them specifically answer my question, most questions are for cursor position in pixels or displaying lines numbers besides the textarea.
My attempt is below, it works fine when starting at line 1 and not leaving the textarea. It fails when clicking out of the textarea and back onto it on a different line. It also fails when pasting text into it because the starting line is not 1.
My JavaScript knowledge is pretty limited.
<html>
<head>
<title>DEVBug</title>
<script type="text/javascript">
var total_lines = 1; // total lines
var current_line = 1; // current line
var old_line_count;
// main editor function
function code(e) {
// declare some needed vars
var keypress_code = e.keyCode; // key press
var editor = document.getElementById('editor'); // the editor textarea
var source_code = editor.value; // contents of the editor
// work out how many lines we have used in total
var lines = source_code.split("\n");
var total_lines = lines.length;
// do stuff on key presses
if (keypress_code == '13') { // Enter
current_line += 1;
} else if (keypress_code == '8') { // Backspace
if (old_line_count > total_lines) { current_line -= 1; }
} else if (keypress_code == '38') { // Up
if (total_lines > 1 && current_line > 1) { current_line -= 1; }
} else if (keypress_code == '40') { // Down
if (total_lines > 1 && current_line < total_lines) { current_line += 1; }
} else {
//document.getElementById('keycodes').innerHTML += keypress_code;
}
// for some reason chrome doesn't enter a newline char on enter
// you have to press enter and then an additional key for \n to appear
// making the total_lines counter lag.
if (total_lines < current_line) { total_lines += 1 };
// putput the data
document.getElementById('total_lines').innerHTML = "Total lines: " + total_lines;
document.getElementById('current_line').innerHTML = "Current line: " + current_line;
// save the old line count for comparison on next run
old_line_count = total_lines;
}
</script>
</head>
<body>
<textarea id="editor" rows="30" cols="100" value="" onkeydown="code(event)"></textarea>
<div id="total_lines"></div>
<div id="current_line"></div>
</body>
</html>
You would want to use selectionStart to do this.
<textarea onkeyup="getLineNumber(this, document.getElementById('lineNo'));" onmouseup="this.onkeyup();"></textarea>
<div id="lineNo"></div>
<script>
function getLineNumber(textarea, indicator) {
indicator.innerHTML = textarea.value.substr(0, textarea.selectionStart).split("\n").length;
}
</script>
This works when you change the cursor position using the mouse as well.
This is tough because of word wrap. It's a very easy thing to count the number of line breaks present, but what happens when the new row is because of word wrap? To solve this problem, it's useful to create a mirror (credit: github.com/jevin). Here's the idea:
Create a mirror of the textarea
Send the content from the beginning of the textarea to the cursor to the mirror
Use the height of the mirror to extract the current row
On JSFiddle
jQuery.fn.trackRows = function() {
return this.each(function() {
var ininitalHeight, currentRow, firstIteration = true;
var createMirror = function(textarea) {
jQuery(textarea).after('<div class="autogrow-textarea-mirror"></div>');
return jQuery(textarea).next('.autogrow-textarea-mirror')[0];
}
var sendContentToMirror = function (textarea) {
mirror.innerHTML = String(textarea.value.substring(0,textarea.selectionStart-1)).replace(/&/g, '&').replace(/"/g, '"').replace(/'/g, ''').replace(/</g, '<').replace(/>/g, '>').replace(/\n/g, '<br />') + '.<br/>.';
calculateRowNumber();
}
var growTextarea = function () {
sendContentToMirror(this);
}
var calculateRowNumber = function () {
if(firstIteration){
ininitalHeight = $(mirror).height();
currentHeight = ininitalHeight;
firstIteration = false;
} else {
currentHeight = $(mirror).height();
}
// Assume that textarea.rows = 2 initially
currentRow = currentHeight/(ininitalHeight/2) - 1;
//remove tracker in production
$('.tracker').html('Current row: ' + currentRow);
}
// Create a mirror
var mirror = createMirror(this);
// Style the mirror
mirror.style.display = 'none';
mirror.style.wordWrap = 'break-word';
mirror.style.whiteSpace = 'normal';
mirror.style.padding = jQuery(this).css('padding');
mirror.style.width = jQuery(this).css('width');
mirror.style.fontFamily = jQuery(this).css('font-family');
mirror.style.fontSize = jQuery(this).css('font-size');
mirror.style.lineHeight = jQuery(this).css('line-height');
// Style the textarea
this.style.overflow = "hidden";
this.style.minHeight = this.rows+"em";
var ininitalHeight = $(mirror).height();
// Bind the textarea's event
this.onkeyup = growTextarea;
// Fire the event for text already present
// sendContentToMirror(this);
});
};
$(function(){
$('textarea').trackRows();
});
This worked for me:
function getLineNumber(textarea) {
return textarea.value.substr(0, textarea.selectionStart) // get the substring of the textarea's value up to the cursor position
.split("\n") // split on explicit line breaks
.map((line) => 1 + Math.floor(line.length / textarea.cols)) // count the number of line wraps for each split and add 1 for the explicit line break
.reduce((a, b) => a + b, 0); // add all of these together
};
Inspired by colab's answer as a starting point, this includes the number of word wraps without having to introduce a mirror (as in bradbarbin's answer).
The trick is simply counting how many times the number of columns textarea.cols can divide the length of each segment between explicit line breaks \n.
Note: this starts counting at 1.

JQuery/Javascript - Search DOM for text and insert HTML

How do I search the DOM for a certain string in the document's text (say, "cheese") then insert some HTML immediately after that string (say, "< b >is fantastic< /b >").
I have tried the following:
for (var tag in document.innerHTML) {
if (tag.matches(/cheese/) != undefined) {
document.innerHTML.append(<b>is fantastic</b>
}
}
(The above is more of an illustration of what I have tried, not the actual code. I expect the syntax is horribly wrong so please excuse any errors, they are not the problem).
Cheers,
Pete
There are native methods for finding text inside a document:
MSIE:textRange.findText()
Others: window.find()
Manipulate the given textRange if something was found.
Those methods should provide much more performance than the traversing of the whole document.
Example:
<html>
<head>
<script>
function fx(a,b)
{
if(window.find)
{
while(window.find(a))
{
var node=document.createElement('b');
node.appendChild(document.createTextNode(b));
var rng=window.getSelection().getRangeAt(0);
rng.collapse(false);
rng.insertNode(node);
}
}
else if(document.body.createTextRange)
{
var rng=document.body.createTextRange();
while(rng.findText(a))
{
rng.collapse(false);
rng.pasteHTML('<b>'+b+'</b>');
}
}
}
</script>
</head>
<body onload="fx('cheese','is wonderful')">
<p>I've made a wonderful cheesecake with some <i>cheese</i> from my <u>chees</u>e-factory!</p>
</body>
</html>
This is crude and not the way to do it, but;
document.body.innerHTML = document.body.innerHTML.replace(/cheese/, 'cheese <b>is fantastic</b>');
You can use this with JQuery:
$('*:contains("cheese")').each(function (idx, elem) {
var changed = $(elem).html().replace('cheese', 'cheese <b>is fantastic</b>');
$(elem).html(changed);
});
I haven't tested this, but something along these lines should work.
Note that * will match all elements, even html, so you may want to use body *:contains(...) instead to make sure only elements that are descendants of the document body are looked at.
Sample Solution:
<ul>
<li>cheese</li>
<li>cheese</li>
<li>cheese</li>
</ul>
Jquery codes:
$('ul li').each(function(index) {
if($(this).text()=="cheese")
{
$(this).text('cheese is fantastic');
}
});
The way to do this is to traverse the document and search each text node for the desired text. Any way involving innerHTML is hopelessly flawed.
Here's a function that works in all browsers and recursively traverses the DOM within the specified node and replaces occurrences of a piece of text with nodes copied from the supplied template node replacementNodeTemplate:
function replaceText(node, text, replacementNodeTemplate) {
if (node.nodeType == 3) {
while (node) {
var textIndex = node.data.indexOf(text), currentNode = node;
if (textIndex == -1) {
node = null;
} else {
// Split the text node after the text
var splitIndex = textIndex + text.length;
var replacementNode = replacementNodeTemplate.cloneNode(true);
if (splitIndex < node.length) {
node = node.splitText(textIndex + text.length);
node.parentNode.insertBefore(replacementNode, node);
} else {
node.parentNode.appendChild(replacementNode);
node = null;
}
currentNode.deleteData(textIndex, text.length);
}
}
} else {
var child = node.firstChild, nextChild;
while (child) {
nextChild = child.nextSibling;
replaceText(child, text, replacementNodeTemplate);
child = nextChild;
}
}
}
Here's an example use:
replaceText(document.body, "cheese", document.createTextNode("CHEESE IS GREAT"));
If you prefer, you can create a wrapper function to allow you to specify the replacement content as a string of HTML instead:
function replaceTextWithHtml(node, text, html) {
var div = document.createElement("div");
div.innerHTML = html;
var templateNode = document.createDocumentFragment();
while (div.firstChild) {
templateNode.appendChild(div.firstChild);
}
replaceText(node, text, templateNode);
}
Example:
replaceTextWithHtml(document.body, "cheese", "cheese <b>is fantastic</b>");
I've incorporated this into a jsfiddle example: http://jsfiddle.net/timdown/azZsa/
Works in all browsers except IE I think, need confirmation though.
This supports content in iframes as well.
Note, other examples I have seen, like the one above, are RECURSIVE which is potentially bad in javascript which can end in stack overflows, especially in a browser client which has limited memory for such things. Too much recursion can cause javascript to stop executing.
If you don't believe me, try the examples here yourself...
If anyone would like to contribute, the code is here.
function grepNodes(searchText, frameId) {
var matchedNodes = [];
var regXSearch;
if (typeof searchText === "string") {
regXSearch = new RegExp(searchText, "g");
}
else {
regXSearch = searchText;
}
var currentNode = null, matches = null;
if (frameId && !window.frames[frameId]) {
return null;
}
var theDoc = (frameId) ? window.frames[frameId].contentDocument : document;
var allNodes = (theDoc.all) ? theDoc.all : theDoc.getElementsByTagName('*');
for (var nodeIdx in allNodes) {
currentNode = allNodes[nodeIdx];
if (!currentNode.nodeName || currentNode.nodeName === undefined) {
break;
}
if (!(currentNode.nodeName.toLowerCase().match(/html|script|head|meta|link|object/))) {
matches = currentNode.innerText.match(regXSearch);
var totalMatches = 0;
if (matches) {
var totalChildElements = 0;
for (var i=0;i<currentNode.children.length;i++) {
if (!(currentNode.children[i].nodeName.toLowerCase().match(/html|script|head|meta|link|object/))) {
totalChildElements++;
}
}
matchedNodes.push({node: currentNode, numMatches: matches.length, childElementsWithMatch: 0, nodesYetTraversed: totalChildElements});
}
for (var i = matchedNodes.length - 1; i >= 0; i--) {
previousElement = matchedNodes[i - 1];
if (!previousElement) {
continue;
}
if (previousElement.nodesYetTraversed !== 0 && previousElement.numMatches !== previousElement.childElementsWithMatch) {
previousElement.childElementsWithMatch++;
previousElement.nodesYetTraversed--;
}
else if (previousElement.nodesYetTraversed !== 0) {
previousElement.nodesYetTraversed--;
}
}
}
}
var processedMatches = [];
for (var i =0; i < matchedNodes.length; i++) {
if (matchedNodes[i].numMatches > matchedNodes[i].childElementsWithMatch) {
processedMatches.push(matchedNodes[i].node);
}
}
return processedMatches;
};

Categories