Javascript userscript - Find and replace: textnodes and HTML - javascript

I'm trying to create a userscript (Tampermonkey) to add some helper buttons into a site and originally I was using the script below based on the one posted here.
setInterval(function() {
//RegEx for finding any string of digits after "ID: " up to the next space
var myRegexp = /ID:\s(\d*?)\s/gi;
];
var txtWalker = document.createTreeWalker (
document.body,
NodeFilter.SHOW_TEXT,
{ acceptNode: function (node) {
//-- Skip whitespace-only nodes
if (node.nodeValue.trim() )
return NodeFilter.FILTER_ACCEPT;
return NodeFilter.FILTER_SKIP;
}
},
false
);
var txtNode = null;
while (txtNode = txtWalker.nextNode () ) {
var oldTxt = txtNode.nodeValue;
//Find all instances
match = myRegexp.exec(oldTxt);
while (match != null) {
//Get group from match
var idNum = match[1]
//Replace current match with added info
oldTxt = oldTxt.(idNum, idNum+"| <SomeHTMLHere> "+idNum+" | ");
//Update text
txtNode.nodeValue = oldTxt;
//Check for remaining matches
match = myRegexp.exec(oldTxt);
}
}
}, 5000);
Now I would like to add a bit more functionality to the text, probably something clickable to copy to clipboard or insert elsewhere. Now I know I'm working with text nodes in the original script but I wanted to know if there was anyway of adapting the current script to insert HTML at these points without rewriting from scratch.
The main problem with the site is these ID:##### values I'm search for all appear within the same element like below so I could simply find them by element (or at least not with my limited JS knowledge).
<div>
ID: 1234567 | Text
ID: 45678 | Text
</div>
If someone could point me in the right direction that'd be great or at least tell me it isn't possible without a rewrite.

Okay, so rewriting it actually worked out pretty well. Works much more nicely and is more succinct. Hopefully this will help someone in the future. If anyone wants to suggest any improved, please feel free!
setInterval(function() {
//Regex
var reg = /ID:\s(\d*?)\s/gi;
var result;
//Get all classes that this applies to
$('.<parentClass>').each(function(i, obj) {
var text = $(this).html();
//Do until regex can't be found anymore
while (result = reg.exec(text)) {
//Get first regex group
var str = result[1];
//Add in desired HTML
var newhtml = $(this).html().replace(str, '|<span class="marked">' + str + '</span>|');
//Replace
$(this).html(newhtml);
}
});
//Click function for added HTML
$(".marked").click(function(){
//Get text inside added HTML
var id = $(this).text();
//Construct desired string
var Str = "someText " + id;
//Insert into message box
textarea = document.querySelector('.<inputArea>')
textarea.value = Str;
textarea.focus();
});
}, 5000);

Related

Get string from a successful regex?

I'm trying to manipulate a string that has tested as a positive match against my regex statement.
My regex statement is /\[table=\d](.*?)\[\/table] / gmi and an example of a positive match would be [table=1]Cell 1[c]Cell 2[/table]. I'm searching for matches within a certain div, which I'll call .foo in the code below.
However, once the search comes back saying it has found a match, I want to have the section that was identified as a match returned back to me so that I can start manipulating a specific section of it, namely count the number of times [c] appears and reference the number in [table=1].
(function(regexCheck) {
var regex = /\[table=\d](.*?)\[\/table] / gmi;
$('.foo').each(function() {
var html = $(this).html();
var change = false;
while (regex[0].test(html)) {
change = true;
//Somehow return string?
}
});
})(jQuery);
I'm quite new to javascript and especially new to RegEx, so I apologise if this code is crude.
Thanks for all of your help in advance.
Use exec instead of test and keep the resulting match object:
var match;
while ((match = regex[0].exec(html)) != null) {
change = true;
// use `match[0]` for the full match, or `match[1]` and onward for capture groups
}
Simple example (since your snippet isn't runnable, I've just created a simple one instead):
var str = "test 1 test 2 test 3";
var regex = /test (\d)/g;
var match;
while ((match = regex.exec(str)) !== null) {
console.log("match = " + JSON.stringify(match));
}

get only the last match? .match(word)

I have a regex to get #user from a textarea. When user type something with # I get it.
My problem is, I want to get just the last match, not all of them.
eg:
user type:
#josh and #marie = want to show #marie
#josh loves #marie and #anne = show #anne
my code is showing like this:
#josh,#marie,#anne
Can I get just the last #something entry? (while user is typing)
var word=/#(\w+)/ig;
$("#comment").on("keyup",function() {
var content = $(this).val();
var name = content.match(word);
var dataString = name;
if(name.length > 0) {
$("#result").text(name);
}
return false();
});
html
<textarea id=comment>#josh and #marie</textarea>
<div id=result></div>
https://jsfiddle.net/dcs5pat8/ (press on textarea)
Besides getting all matches and obtain the last one, you can use capture groups to get the last match:
var word=/.*(#\w+)/i;
var name = content.match(word)[1];
Or using exec, the whole would look like:
var word=/.*(#\w+)/i;
$("#comment").on("input",function() { //changed keyup to input
var content=$(this).val();
var match = word.exec(content);
if(match){
$("#result").text(match[1]);
}
});
Fiddle
PS, if your goal is a more generic approach and you need to switch between getting all words and a single one, I'd recommend keeping the global match and getting the last as in Jonas' answer.
My suggestion is that you show only the last entry of your results.
You can do that by changing the line:
var name = content.match(word);
to
var names = content.match(word);
var name = names[names.length - 1];
On more detail, what this does is it gets all the results from your regex, then it attributes the last item of the array to the name variable.
Hope this was helpful.
You can simply select or pop the last match in the array of match returned by .match()
var word=/#(\w+)/ig;
$("#comment").on("keyup",function() {
var content=$(this).val();
var matches = content.match(word);
var lastmatch = matches.pop();
//IF YOU NEED TO KEEP INTACT THE VAR MATCHES
//var lastmatch = matches[matches.length - 1];
if(name.length>0){
$("#result").text(lastmatch);
}
return false();
});
JSFiddle
Use this regex '/#(\w+)$/ig' insted of '/#(\w+)/ig'.
And then your code will run like a charm. ;)
var word=/#(\w+)$/ig;
$("#comment").on("keyup",function() {
var content=$(this).val();
var name = content.match(word);
var dataString = name;
if(name.length>0){
$("#result").text(name);
}
return false();
});
See it hear https://jsfiddle.net/dcs5pat8/1/
I do like the answer where you take your list with all of the #names,#name1,#name2 and just split off the last one, but here it is in just one step
//split on #something
//the penultimate item is our target
//if there is < 2 items there weren't any #somethings so return ''
user = (split = "testing #charlie testing".split(/(#[^ ]*)/)).length > 1 ? split.splice(-2,1)[0] : '';
https://jsfiddle.net/ek19h0fb/1/
To have only one line you can do
var name = content.match(word).reverse()[0];

Replace the last occurence of a random word

I have a little jQuery script that works like Emmet/ZenCoding and that expands keywords to a snippet on the press of the TAB key in a textarea.
It works fine when there's only one occurence of the keyword, but I don't know how to replace only the last occurence of a word in a string.
Here's an example string that would be typed in the textarea :
Have a look at the rules : rules
In this case, the user would press the TAB key after the second rules, and the script fetches the corresponding value in an array, to replace the word with an actual link to the rules. It should (in theory) be replaced by this :
Have a look at the rules : http://website.com/rules.php
My problem is that I'm using replace and it will replace all occurences of that word, which is not the desired behavior. Here is the relevant part of the script :
var content = $(this).val();
content = content.replace(lastWord, insertSnippets[lastWord]);
$(this).val(content);
It first gets the content in the textarea at the time, replace the word (rules) with the corresponding value (the actual link). Is there any way to replace only the last occurence? It won't always be the word "rules", there are a dozen of other keywords of unknown length.
TL;DR How do I replace only the last occurence of a word with jQuery/Javascript?
I was thinking of getting the current position of the caret and replace only the word to its left, but getting the position of the caret seems rather tedious - and I still wouldn't know how to do the replace.
This might do it, so long as your search string doesn't include any regex special characters:
var replaceLast = function(searchFor, replaceWith, str) {
return str.replace(
new RegExp("^(.*)" + searchFor + "(.*?)$"),
function(_, before, after) {return before + replaceWith + after;}
);
};
replaceLast("rules", "http://website.com/rules.php",
"Have a look at the rules : rules", "rules");
//=> "Have a look at the rules : http://website.com/rules.php"
If this function were passed to some curry function, then you could create a function such as updateRules via
var updateRules = replaceLast("rules", "http://website.com/rules.php");
// ... later
updateRules("Have a look at the rules : rules", "rules");
//=> "Have a look at the rules : http://website.com/rules.php"
But the original function might be all you need.
Here's what I came up with (only tested webkit) using a contenteditable field.
It does a few things nicely, though probably inefficiently:
Replaces word with a related snippet that live in an object
replaces only the current word your caret is on
Sets your cursor to the end of the line after tabbing to complete
Try typing in "blah blah blah rules" and hit tab after "rules". Try it with multiple "rules" in the string.
var content = document.getElementById('content')
content.addEventListener('keydown', function (event) {
var _this = this,
text = this.innerHTML;
if ( event.keyCode == 9 ) {
event.preventDefault()
var sel = window.getSelection(),
currentWord = getWord(),
val = sel.anchorNode.nodeValue,
snippet = tabComplete(currentWord),
range = sel.getRangeAt(0)
var selArray = sel.anchorNode.nodeValue.split(' ')
if ( snippet !== undefined ) {
var currentWordPosition = [ (range.endOffset - currentWord.length), range.endOffset ]
var newText = text.splice(currentWordPosition[0], currentWordPosition[1], ' '+snippet)
_this.innerHTML = newText;
sel.collapse(_this.firstChild, sel.anchorNode.nodeValue.length)
}
}
})
var snippets = {
'rules' : 'http://website.com/rules.php',
'faq' : 'http://website.com/faq.php'
}
function tabComplete(string) {
if ( typeof string !== 'string' ) { return false; }
var snippetKeys = Object.keys(snippets)
for ( var i=0; i<snippetKeys.length; i++ ) {
if ( snippets[string] ) {
return snippets[string]
}
}
}
function getWord() {
var range = window.getSelection().getRangeAt(0);
if ( range.collapsed ) {
text = range.startContainer.textContent.substring(0, range.startOffset+1);
return text.split(/\b/g).pop();
}
return '';
}
String.prototype.splice = function(start, length, replacement) {
return this.substr(0,start)+replacement+this.substr(start+length);
}
#content {
width:350px;
height:150px;
background-color: #333;
color: white;
font-family: sans-serif;
padding:10px;
}
<div id="content" contenteditable></div>
Use a regex and only match the end of the content string.
/yourWord$/
will match yourWord if it is at the very end of the string
Just tweak the regex a bit:
var content = $(this).val();
content = content.replace(lastWord+'.*?$', insertSnippets[lastWord]);
$(this).val(content);

Javascript Regex (replace) Issue

I am checking a collection and replacing all
<Localisation container="test">To translate</Localisation>
tags with text.
The next codes does what I want:
var localisationRegex = new RegExp("(?:<|<)(?:LocalisationKey|locale).+?(?:container|cont)=[\\\\]?(?:['\"]|("))(.+?)[\\\\]?(?:['\"]|(")).*?(?:>|>)(.*?)(?:<|<)/(?:LocalisationKey|locale)(?:>|>)", "ig");
match = localisationRegex.exec(parsedData);
while (match != null) {
var localeLength = match[0].length;
var value = match[4];
parsedData = parsedData.substr(0, match.index) + this.GetLocaleValue(value) + parsedData.substr(match.index + localeLength);
match = localisationRegex.exec(parsedData);
}
But, when the the string I replace with, Is longer then the original string, the index/place where it will start to search for the next match, is wrong (to far). This sometimes leads to tags not found.
Setting aside the (important) question as to whether the approach is a good one, if it were me I'd avoid the problem of indexing through the source text by using a function argument to the regex:
var localizer = this;
var result = parsedData.replace(localisationRegex, function(_, value) {
return localizer.GetLocaleValue(value);
});
That will replace the tags with the localized content.

Replace text but not the HTML tags?

I'm doing text matching on an Autocomplete in JQuery, by overloading the _renderItem method to check the JSON object for the text the user searched for. Upon finding it, I'm replacing it with a span tag with a "user_highlight_match" class.
I'm doing multi word searching, so "John Smi" would highlight John, then Smi, and searching "John James Smi" would highlight "John" and "Smi". The problem with this is that, say you search for "John Las", it will start matching against the class attribute of the span tag, if it's already there. How can I get around this?
Edit: Here's some code showing what I'm talking about:
renderItem = function(ul, item) {
li = $('<li>', id: item._id);
if this.term and not item.dialog
terms = this.term.split(' ');
for term in terms
re = new RegExp("("+term+")", 'i');
match = re.exec(item.name);
if match
item.name = item.name.replace re, "<span class=\"user_highlight_match\">+match[0]+</span>";
)
So the first term will match fine, but every time around after that, if you're searching for anything in the html tags, the partial match is replaced with a tag. So say there was a match on "las", it would become this:
<span c<span class="user_highlight_match">Last</span>s="user_highlight_match">Last</span>
You'll have to search text nodes and replace them with the html you want. See this question: Find word in HTML.
Edit: Here's a jQuery version of my answer there.
http://jsfiddle.net/gilly3/auJkG/
function searchHTML(searchString, htmlString) {
var expr = new RegExp(searchString, "gi");
var container = $("<div>").html(htmlString);
var elements = container.find("*").andSelf();
var textNodes = elements.contents().not(elements);
textNodes.each(function() {
var matches = this.nodeValue.match(expr);
if (matches) {
var parts = this.nodeValue.split(expr);
for (var n = 0; n < parts.length; n++) {
if (n) {
$("<span>").text(matches[n - 1]).insertBefore(this);
}
if (parts[n]) {
$(document.createTextNode(parts[n])).insertBefore(this);
}
}
$(this).remove();
}
});
return container.html();
}

Categories