I am trying to highlight a specific occurrence of a word in a text using jquery/js. Trying to find out if there are any existing libraries I can use. I read about mark.js but it does not offer the functionality I need.
Example Text: "In a home, there is a room, the room has a door"
Highlight word: "room"
Occurrence: 2
The second "room" in the text needs to be highlights.
Please suggest. Thanks!
Just pass in the specific index of the token (character sequence you are looking for) to a function that takes the string, token, and index as parameters. You can now use the 2nd parameter of indexOf to update the beginning of where the string will be searched from using the last result:
const highlighter = (string, token, index) => {
let n = -1
for (let i = 0; i <= index; i++) {
n = string.indexOf(token, n + 1)
}
return string.slice(0, n) + string.slice(n).replace(token, '<span class="highlight">' + token + '</span>')
}
const text = 'In a home, there is a room, the room has a door.<br>'
const firstRoom = highlighter(text, 'room', 0)
const secondRoom = highlighter(text, 'room', 1)
$('#result').append(firstRoom)
$('#result').append(secondRoom)
.highlight {
background-color: lightblue;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="result"></div>
The -1 is important since this function would otherwise miss the first token occurence if it appears at the start of the string.
You could do it like this:
let text = "In a home, there is a room, the room has a door.";
let search = "room";
var n = text.lastIndexOf(search);
text = text.slice(0, n) + text.slice(n).replace(search, "<span class='highlight'>" + search + "</span>");
$("#result").append(text);
.highlight {
color:red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="result">
</div>
// To replace all (commented out so it doesn't interfere with the other one)
/*
var p = document.getElementById("text"); // Gets the <p>
var text = p.innerText; // Gets the text it contains
var wordToReplace = "room"; // Which word should be replaced
var newtext = text.replaceAll(wordToReplace, '<span class="highlight">' + wordToReplace + '</span>'); // Replaces the word with the word wrapped in <span> tags, so it will look highlighted
p.innerHTML = newtext; // Change it back
*/
// To replace specific occurences
var paragraph = document.getElementById("text"); // Gets the <p>
var txt = paragraph.innerText; // Gets the text it contains
var textToReplace = 'room' // Word to be replaced
var replace = RegExp(textToReplace, 'g');
var matches = txt.matchAll(replace); // Gets all places where the text matches the word
var replacementPositions = [0, 2]; // Occurences which should be highlighted
var i = 0; // Which match this is; starts at 0
for (const match of matches) { // For each match...
var text = match[0]; // The matching text
var start = match.index; // Start position
var end = match.index + text.length; // End position
if (replacementPositions.includes(i)) { // If it should be replaced (in the replacementPositions array)
var startText = txt.substring(0, start - 1); // Text before match
var endText = txt.substring(start); // Text after match
endText = endText.substring(text.length); // Removes matching text from the text after the match
txt = startText + '<span class="highlight">' + text + '</span>' + endText; // Insert the match back in, wrapped in a <span>
}
i++; // Increment
}
paragraph.innerHTML = txt; // Set the paragraph text back
.highlight {
background-color: yellow;
}
<p id="text">First: room. Another one: room. Last time: room</p>
The first method detects all occurrences of the word and wraps them in a <span>. The <span> has a style that sets the background color of the text, so it looks 'highlighted'.
The second method goes through every occurrence of the word and checks if it should be highlighted. If so, it does some operations that wrap that occurrence in a <span>, like the method above.
Related
In the following example, first I replace successfully the 'my own' with its own span '<span id="sMyOwn">my own</span>'. Next, I want to replace, all at once, the rest of the words (that is, 'This', 'is' and 'example') with a simple span. For this, I use the function fReplace, in order to do it conditionally according to #Abhilash comment that follows, but it fails. The second and the third solutions I applied are based on a simple loop solution, but they fail too. Could someone please help me. The last line is just to present the results.
<div id='MyOwnExample'>This is my own example</div>
<div id='AA'></div>
<script>
// 1st part: It works
v1 = document.getElementById('MyOwnExample').innerHTML;
const regex = new RegExp('my own', 'gi')
v1 = v1.replace(regex,`<span id = 'sMyOwn'>my own</span>`)
document.getElementById('MyOwnExample').innerHTML = v1;
// 2nd part: First solution that fails
function fReplace(word, index) {
const regex = new RegExp(word, 'gi')
if (span.className != 'sMyOwn') {
v1 = v1.replace(regex,`<span>${word}</span> `)
}
}
v1.forEach(fReplace);
document.getElementById('MyOwnExample').innerHTML = v1;
// 2nd part: Second solution that fails too
const regex2 = new RegExp(word, 'gi')
for (word of v1) {
if (span.className != 'sMyOwn') {
v1 = v1.replace(regex2,`<span>${word}</span> `)
}
}
document.getElementById('MyOwnExample').innerHTML = v1;
// 2nd part: Third solution that fails too
for (word of v1) {
if (typeof(span) == 'undefined') {
v1 = v1.replace('${word} ', `<span>${word}</span> `)
}
}
document.getElementById('MyOwnExample').innerHTML = v1;
// 4th part: Results
document.getElementById('AA').innerText = document.getElementById('MyOwnExample').innerHTML;
</script>
Looking at the code in your question you wanted to see the output in raw text and not in html but just incase you need it in html uncomment the last line.
<div id='MyOwnExample'>This is my own example</div>
<div id='AA'></div>
<script>
/**
First wrap the special phrase with a span as you want
Now take out the special phrase and split the remaining text into array while you use '_' as the special phrase placeholder for later replacement.
Replace each wrap each word with span as you want it
Put them back while replacing the '_' placeholder with the wrapped special phrase
**/
const transformer = (word,specialPhrase)=>{
const wrapSpecialPhrase = `<span id="sMyOwn"> ${specialPhrase}</span>`;
const stripoffSpecialPhrase = (word.split(specialPhrase)).join("_").split(" ");
const formed = stripoffSpecialPhrase.map(wd=> wd !=='_' ?`<span id='${wd}'> ${wd} </span>` : wd);
return formed.join(" ").replace('_',wrapSpecialPhrase);
}
const aaElement = document.getElementById('AA');
const myOWnExampleElement = document.getElementById('MyOwnExample')
const phrase = myOWnExampleElement.innerText;
const transformed = transformer(phrase,"my own");
aaElement.innerText= transformed; // This when you want it in plain text
/* aaElement.innerHTML= transformed; // This when you want it in html */
</script>
A regex would be helpful in safely splitting the sentence by retaining group of words.
const text = "This is my own example";
const yourHTML = text.split(/( |my own)/g)
.filter(v => v.trim())
.map(v => "<span"+ (v == "my own" ? " id='sMyOwn'" : "") + ">" + v + "</span>")
.join("")
console.log(yourHTML)
Output:
<span>This</span><span>is</span><span>my own</span><span>example</span>
Here is the working example:
const v1 = document.getElementById('MyOwnExample').innerText;
const arr = v1.split(' ');
for(let i=0; i < arr.length; i++) {
let str = arr[i];
arr[i] = arr[i].replace(new RegExp(str, 'gi'),`<span id='id_${str}'>${str}</span>`);
}
console.log(arr);
document.getElementById('MyOwnExample').innerHTML = arr.join(' ');
document.getElementById('AA').innerText = document.getElementById('MyOwnExample').innerHTML;
#MyOwnExample span {
background-color: #dedede;
display: inline-block;
margin: 2px;
}
<div id='MyOwnExample'>This is my own example</div>
<div id='AA'></div>
Say for example I have the words below
THIS TEXT IS A SAMPLE TEXT
I am given character index 7.
Then I have to return index 1 when I split the sentence into words which is the index of the word that contains the index of character not 5 which matches the word that composes the index of character exactly but not the correct index where character lies.
basically I am trying to return the correct word index of where character lies (when split into words) with character index (when split with characters)
I thought I would reconstruct the word with something like below to find the word at the character
let curString = 'find a word from here';
let initialPositin = 5
let position = initialPositin
let stringBuilder = '';
while(position > -1 && curString.charAt(position) !== ' '){
console.log('run 1')
console.log(position);
stringBuilder = curString.charAt(position) + stringBuilder;
position --;
}
console.log(stringBuilder)
position = initialPositin + 1;
while(position < curString.length && curString.charAt(position) !== ' '){
console.log('run 2')
stringBuilder += curString.charAt(position);
position ++;
}
console.log(stringBuilder);
Then split the sentence into words then find all the index of the word that contains the word that I have constructed. Then go through all the found words and reconstruct the previous words to see if the index of the target character in the reconstruction matches the character position given.
It doesn't really feel efficient. Does anyone have better suggestions?
I prefer javascript but I can try to translate any other language myself
I think you could just count spaces that occurs before given index, something like
let curString = 'find a word from here';
let givenIndex = 9;
let spaceIndex = 0;
for (var i = 0; i < curString.length; i++) {
if(curString.charAt(i) == ' ') {
if (i < givenIndex) {
spaceIndex++;
} else {
// found what we need
console.log(spaceIndex);
}
}
}
Maybe you could build a function that returns the position of all spaces.
Then you can see where the character index fits in that list of space positions.
text = "THIS TEXT IS A SAMPLE TEXT"
indexes = []
current_word = 0
for i in range(0, len(text)):
if text[i] == ' ':
current_word += 1 # After a ' ' character, we passed a word
else:
indexes.append(current_word) # current character belongs to current word
You can build indexes array for once with this piece of code(written in Python3) then you can use it for every indice. If you want to count ' ' characters in indexes array as well, you can simple add them in for loop(in if statement).
I ended up using below code
let content = 'THIS IS A SAMPLE SENTENCE';
let target = 13;
let spaceCount = 0;
let index = 0;
while(index < target) {
if (content.charAt(index) === ' ') {
spaceCount++;
}
index++;
}
let splitContent = content.split(' ');
splitContent[spaceCount] = '#' + value
console.log(splitContent.join(' '))
Worked very nicely
Just like the answer from #miradham this function counts the spaces before the given index, but with builtin functions to count character occurrences.
function wordIndexOfCharacterIndexInString(index, string) {
const stringUpToCharacter = string.slice(0, index)
return (stringUpToCharacter.match(/ /g) || []).length
}
console.log(wordIndexOfCharacterIndexInString(7, "THIS TEXT IS A SAMPLE TEXT"))
I'm trying to insert a randomly selected string into each instance of whitespace within another string.
var boom = 'hey there buddy roe';
var space = ' ';
var words = ['cool','rad','tubular','woah', 'noice'];
var random_words = words[Math.floor(Math.random()*words.length)];
for(var i=0; i<boom.length; i++) {
boom.split(' ').join(space + random_words + space);
}
Output comes to:
=> 'hey woah there woah buddy woah roe'
I am randomly selecting an item from the array, but it uses the same word for each instance of whitespace. I want a word randomly generated each time the loop encounters whitespace.
What I want is more like:
=> 'hey cool there noice buddy tubular roe'
Thanks for taking a look.
(This is beta for a Boomhauer twitter bot, excuse the variables / strings 😅)
Maybe you can use regex instead however, you are not seeing the result you desire because you are randomly selecting one word and then replacing all occurrences of a space with it.
The regular expression below replaces occurrences of a space with a dynamic value returned by a callback. You could compare this callback to your for-loop but instead, it's iterating over the spaces found and by doing so you can replace each occurrence with a 'unique' random word.
const boom = 'hey there buddy roe';
const words = ['cool', 'rad', 'tubular', 'woah', 'noice'];
const random = () => Math.floor(Math.random() * words.length);
let replace = boom.replace(/ /g, () => ` ${words[random()]} `);
console.log(replace);
The problem is, that random_words is set to a single word.
Try this instead:
var boom = 'hey there buddy roe';
var words = ['cool','rad','tubular','woah', 'noice'];
boom.replace(/ /g, (space)=> space + words[Math.floor(Math.random()*words.length)] + space);
To get the effect you desire, you need to do the word selecting inside the loop, not outside of it.
for(var i=0; i<boom.length; i++) {
// get a new number every loop
var random_words = words[Math.floor(Math.random()*words.length)];
boom.split(' ').join(space + random_words + space);
}
What is wrong with OP's code: random_words is initialized once only, with a random word. Intention there is, however, to select random word for every whitespace encountered instead.
You can either go with:
for(var i=0; i<boom.length; i++) {
boom.split(' ').join(space + words[Math.floor(Math.random()*words.length)] + space);
}
... or make random_words a function that returns a random word, then call it in your 'boom' loop. With every call, a new word selection will occur.
You need to recalculate the random word on each loop. Right now you have picked out a single random word, stored it in the random_words variable, and you reuse it each time. You could modify your code like this:
var boom = 'hey there buddy roe';
var space = ' ';
var words = ['cool','rad','tubular','woah', 'noice'];
function getRandomWord() {
return words[Math.floor(Math.random()*words.length)];
}
// Uses the same because the value given to join is not recalculated each time:
console.log(boom.split(' ').join(space + getRandomWord() + space));
// You could do this with a standard for loop:
let result = "";
let split = boom.split(' ')
for(var i=0; i<split.length; i++) {
result += split[i] + space;
if (i === split.length - 1) break;
result += getRandomWord() + space;
}
console.log(result);
// Or you can use a reduce:
let otherResult = boom.split(' ').reduce((res, word, index, split) => {
if (index === split.length - 1) return res + space + word;
return res + space + word + space + getRandomWord();
});
console.log(otherResult)
I am working on inserting text to the bottom of certain wordpress posts based on the amount of times a string occurs. I've managed to add the text to the bottom with append but instead I would like to insert at a specific location using indexOf.
here is the original text:
if ($('body').hasClass("single-post")){
var count = 0;
var cSearch = $('body').text();
var words = cSearch.indexOf('Words To Find')
while (words !== -1){
count++;
words = cSearch.indexOf('Words To Find', words + 1);
}
if( count >= 2){
$('.entry-content').append('<br><br>Sample Text');
}
}
Here is how I will get the location I want to insert before:
var insertLocation = cSearch.indexOf('Show what ya');
How can I splice the "Sample Text" into the location specified with insertLocation?
I found a bit about using polyfil for .splice but I'm not sure it works for this. Using it such as:
$(cSearch).splice( insertLocation, -1 ).text( "Sample Text" );
Can someone suggest a way to do this? Thanks!
Try creating variables representing total matches , indexOf() match plus matched text .length , using .slice() to insert original text before second match , new text ti insert , followed by remainder of original text
var text = $("div").text(),
match = "Words To Find",
txt = " Sample Text ",
matches = 0,
n = 0,
// `max` : total number of `matches` before inserting `txt`
max = 2;
while (matches < max) {
if (text.indexOf(match, n) !== -1) {
i = text.indexOf(match, n);
n = i + match.length;
++matches
}
}
// insert `re` after second match of `"Words To find"`
var re = text.slice(0, i + match.length) + txt;
$("div").text(re + text.slice(i));
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js">
</script>
<body>
<div>Words To Find abc def Words To Find ghi Words To Find</div>
</body>
I would like to use a regexp to markup some text that may span any number of tags in HTML.
Ex. given the regex "brown\ fox.*lazy\ dog"
<div>The quick brown fox</div>
<div>jumps over</div>
<div>the lazy dog</div>
would be transformed to
<div>The quick <strong>brown </strong><strong>fox</strong></div>
<div><strong>jumps over</strong></div>
<div><strong>the lazy </strong><strong>dog</strong></div>
Having an empty <strong> element between the close tags would be fine too. Using any Javascript libraries is fine. It can be browser specific.
I would do it in two pass, first by locating the whole sentence and secondly by putting each word in strong.
And as I don't find practical to build the regexes by hand, I generate them :
var sentence = 'the quick brown fox jumps over the lazy dog';
var r1 = new RegExp(sentence.split(' ').join('\\s*(<[^>]*>\\s*)*'), 'i');
var r2 = new RegExp('('+sentence.split(' ').join('|')+')', 'gi');
str = str.replace(r1, function(sentence) {
return sentence.replace(r2, '<strong>$1</strong>')
});
Demonstration
I don't guarantee it works in all cases but I don't see any case of failure right now. This code ensures the sentence is complete, doesn't include words outside tags, and that the order of the words is correct.
I was hoping someone could come up with a simpler solution. Here's what I came up with. http://jsbin.com/usapej/4
// Initial values
var html = $('#text').html();
var re = /brown fox(.|[\r\n])*lazy dog/;
var openTag = "<strong>";
var closeTag = "</strong>";
// build a list of tags in the HTML
var tagRe = /<[^>]*>/g;
var matches = [];
var tagResult;
var offset = 0;
while((tagResult = tagRe.exec(html)) !== null) {
// Make the index relative to the start of the string w/o the tags
tagResult.index -= offset;
offset += tagResult[0].length;
matches.push(tagResult);
}
// put our markup in the HTML
var text = $('#text').text();
var result = re.exec(text);
text = text.substring(0, result.index) + openTag + result[0] + closeTag + text.substring(result.index + result[0].length);
// Put the original tags back in surrounded by our close and open tags if it's inside our match
offset = 0;
var p;
for(var i = 0; i < matches.length; i++) {
var m = matches[i];
if(m.index <= result.index) {
text = text.substring(0, m.index + offset) + m[0] + text.substring(m.index + offset);
offset += m[0].length;
} else if(m.index > result.index + result[0].length) {
p = m.index + offset + openTag.length + closeTag.length;
text = text.substring(0, p) + m[0] + text.substring(p);
offset += m[0].length;
} else {
p = m.index + offset + openTag.length;
var t = closeTag + m[0] + openTag;
text = text.substring(0, p) + t + text.substring(p);
offset += t.length;
}
}
// put the HTML back into the document
$('#text').html(text);