How do I go about assigning certain words a unique id tag using vanilla javascript? For example:
<p>All the king's horses ran away.</p>
The word "All" gets the id "term1", "King's" gets "term2", "away" gets "term3", etc. but not every word in the sentence will get assigned an id.
I am currently using the replace method but I think it's the wrong approach:
var str = document.getElementsByTagName('p')[0].innerHTML;
function addId() {
txt = str.replace(/all/i, '<span id="term1">$&</span>').replace(/king's/i, '<span id="term2">$&</span>').replace(/away/i, '<span id="term3">$&</span>');
document.getElementsByTagName('p')[0].innerHTML = txt;
}
window.onload = function() {
addId();
};
<p>All the king's horses ran away.</p>
This forces me to chain a bunch of replace commands, changing the id name each time. I don't think this is the best solution. What is best way to do this? Thanks for your help!
I believe this should make the job done. You can replace blacklist with whitelist. That will depend on your use case. Also, addIds can use Array.map instead of Array.forEach that will make the whole function a one-liner. This example is imperative because it will be more readable.
// string - string where we want to add ids
// blackList - words we want to skip (can be whiteliste black list is more general)
function addIds(string, blackList = ['the', 'a']) {
const stringArray = string.split(' ') // split string into separate words
let stringWithIds = string // this will be final string
stringArray.forEach((item, index) => {
// skip word if black listed
if (blackList.indexOf(item.toLowerCase()) !== -1) {
return
}
stringWithIds = stringWithIds.replace(item, `<span id="term${index}">${item}</span>`) // add id to word if not black listed
})
// replace string with our string with ids
document.getElementsByTagName('p')[0].innerHTML = stringWithIds;
}
window.onload = function() {
const str = document.getElementsByTagName('p')[0].innerHTML;
addIds(str); // you can pass custom blacklist as optional second parameter
};
<p>All the king's horses ran away.</p>
I think this function would be flexible enough for the task:
// Here, your logic to decide if the word must be tagged
const shouldBeTagged = word => {
return true
}
function addId(str) {
// Split the string into individual words to deal with each of them
// one by one
let txt = str.split(' ').map((word, i) => {
// Should this word be tagged
if (shouldBeTagged(word)) {
// If so, tag it
return '<span id="term' + i + '">' + word + '</span>'
} else {
// Otherwise, return the naked word
return word
}
// Join the words together again
}).join(' ')
document.getElementsByTagName('p')[0].innerHTML = txt;
}
window.onload = function() {
addId(document.getElementsByTagName('p')[0].innerHTML);
};
<p>All the king's horses ran away.</p>
Related
I've been trying to put together an input where the text automatically capitalizes the first letter of each word and makes all other letters lowercase for that word. I had some success using that for just making everything lower case after the first letter for the input with this:
<input type = "text" size="8" name="textfield1" id="textfield1" />
with the javascript being
document.getElementById('textfield1').addEventListener("keyup", () => {
var inputValue = document.getElementById('textfield1')['value'];
if (inputValue[0] === ' ') {
inputValue = '';
} else if (inputValue) {
inputValue = inputValue[0].toUpperCase() + inputValue.slice(1).toLowerCase();
}
document.getElementById('textfield1')['value'] = inputValue;
});
I tried adding map(), split(), and join() in various ways based off of lessons I've found (I'm learning on my own, no formal training since high school) for use in a string with the console.log methods but I'm confused on how I can apply this to an input. It would take too long to note everything I've tried but one thing I did was this:
document.getElementById('textfield1').addEventListener("keyup", () => {
var inputValue = document.getElementById('textfield1')['value'];
if (inputValue[0] === ' ') {
inputValue = '';
} else if (inputValue) {
input.content = input.content.split(' ').map(function(inputValue) {
return inputValue[0].toUpperCase() + inputValue.slice(1).toLowerCase();
}).join(' ');
}
document.getElementById('textfield1')['value'] = inputValue;
});
I'm not sure what I'm missing here. I'm sure there's something that I'm not seeing or understanding. I also tried looking to see if there was something similar listed on here or elsewhere in relation to this and inputs but I didn't see anything specific to what I was looking for.
I want the input to remain the same for what comes up with the output into another box when it gets copied over.
Example of what I'm trying to do is:
input of textfield: heLlo OuT thERe!
output to another textarea with the click of a button: Hello Out There!
Your second version would be correct. However in this code:
else if (inputValue) {
input.content = input.content.split(' ').map(function(inputValue) {
return inputValue[0].toUpperCase() + inputValue.slice(1).toLowerCase();
}).join(' ');
}
You started to use some input.content instead of the inputValue variable. Most probably yout mistake lies there.
You can use this regex replace to change a sentence to Proper Case:
const regex = /\b(\w)(\w*)/g;
const input = 'Fix this lower and UPPER to Proper'
let result = input.replace(regex, function(m, c1, c2) {
return c1.toUpperCase() + c2.toLowerCase()
});
console.log('result: ', result);
// => 'Fix This Lower And Upper To Proper'
Explanation of regex:
\b -- word boundary
(\w) -- capture group 1: a singe word character
(\w*) --capture group 2: zero to multiple word characters
g -- flag for global, e.g. run pattern multiple times
replace function: parameter c1 contains capture group 1, c2 contains capture group 2
This question already has answers here:
How can I change an element's text without changing its child elements?
(16 answers)
Closed 5 years ago.
I need to write a second RegExp to find variable d inside sentence that is not in tags. So variable in tags should be skipped.
Regex '(?:^|\\b)('+d+')(?=\\b|$)' will find d variable but i need to exclude <span> tag with class="description".
New sentence is wrapped in a new tag.
sentence = "This is some word. <span class='description'>word</span> in tag should be skipped"
d = 'word'
re = new RegExp('(?:^|\\b)('+d+')(?=\\b|$)', 'gi')
sentence = sentence.replace(re, "<span>$1</span>")
Result I'm trying to achieve is:
"This is some <span>word</span>. <span class='description'>word</span> in tag should be skipped"
I'm using coffeescript, thanks for the help.
Try this one: (word)(?![^<>]*<\/)
Full code:
var sentence = "This is some word. <span class='description'>word</span> in tag should be skipped"
var d = 'word'
var re = new RegExp('('+d+')(?![^<>]*<\/)', 'gi')
sentence = sentence.replace(re, "<span>$1</span>")
I based this answer on this snippet: https://regex101.com/library/gN4vI6
Trying to manipulate HTML with regular expressions is not a good idea: sooner or later you'll bump into some boundary condition where it fails. Maybe some < or > occur inside attribute values, or even inside text nodes, while the searched term may also occur at unexpected places, like in HTML comments, attribute values, or script tags, ... The list of boundary cases is long.
Furthermore, your search term may contain characters that have a special meaning in regular expression syntax, so you should at least escape those.
Here is a solution that interprets the string as HTML, using the DOM capabilities, and only replaces text in text nodes:
function escapeRegExp(str) {
return str.replace(/[\[\]\/{}()*+?.\\^$|-]/g, "\\$&");
}
function wrapText(sentence, word) {
const re = new RegExp("\\b(" + escapeRegExp(word) + ")\\b", "gi"),
span = document.createElement('span');
span.innerHTML = sentence;
Array.from(span.childNodes, function (node) {
if (node.nodeType !== 3) return;
node.nodeValue.split(re).forEach(function (part, i) {
let add;
if (i%2) {
add = document.createElement('span');
add.textContent = part;
add.className = 'someClass';
} else {
add = document.createTextNode(part);
}
span.insertBefore(add, node);
});
span.removeChild(node);
});
return span.innerHTML;
}
const html = 'This is some word. <span class="word">word</span> should stay',
result = wrapText(html, 'word');
console.log(result);
Recursing into elements
In comments you mentioned that you would now also like to have the replacements happening within some tags, like p.
I'll assume that you want this to happen for all elements, except those that have a certain class, e.g. the class that you use for the wrapping span elements, but you can of course customise the condition to your needs (like only recursing into p, or ...).
The code needs only a few modifications:
function escapeRegExp(str) {
return str.replace(/[\[\]\/{}()*+?.\\^$|-]/g, "\\$&");
}
function wrapText(sentence, word) {
const re = new RegExp("\\b(" + escapeRegExp(word) + ")\\b", "gi"),
doc = document.createElement('span');
doc.innerHTML = sentence;
(function recurse(elem) {
Array.from(elem.childNodes, function (node) {
// Customise this condition as needed:
if (node.classList && !node.classList.contains('someClass')) recurse(node);
if (node.nodeType !== 3) return;
node.nodeValue.split(re).forEach(function (part, i) {
let add;
if (i%2) {
add = document.createElement('span');
add.textContent = part;
add.className = 'someClass';
} else {
add = document.createTextNode(part);
}
elem.insertBefore(add, node);
});
elem.removeChild(node);
});
})(doc);
return doc.innerHTML;
}
const html = '<p><b>Some word</b></p>. <span class="someClass">word</span> should stay',
result = wrapText(html, 'word');
console.log(result);
If I have the following string: This is another song you should listen to
I was trying to write a function to only highlight the word based on the word count. For example, I want to highlight the 4th word using a method like: highlightWord(originalString,nthWord). And the returned string would be:
This is another <span class=\"highlighted\">song</span> you should listen to
I tried using a highlightMatch function that I wrote, one by passing a search string (regexMatch = "is"), but the problem is that it highlights the is in This instead of is:
function highlightMatch(originalValue,regexMatch) {
var tempInnerHTML = stripTags(originalValue);
originalValue.innerHTML = tempInnerHTML.replace(regexMatch,'<span class="highlighted">'+regexMatch+'</span>');
}
I also tried using this highlightNthWord method:
function highlightNthWord(string,n) {
var m = string.match(new RegExp('^(?:\\w+\\W+){' + n + '}(\\w+)'));
return m && '<b>'+m[1]+'</b>';
}
But this one just returns only the nth word highlighted, not the whole sentence with the nth word highlighted.
I'm hoping to stick with javascript only (no jquery).
You can split the string be space and then build the string back:
function highlightNthWord(string,n) {
var m = string.split(new RegExp(/\s+/));
return m.splice(0, n-1).join(" ") + (' <b>'+m.splice(0, 1)+'</b> ') + m.join(" ");
}
console.log(highlightNthWord('This is another song you should listen to', 4))
Try this
function highlightMatch(originalValue, matchString) {
var re = new RegExp(matchString, "igm");
var replace = '<span class="highlighted">'+matchString+'</span>';
return originalValue.replace(re ,replace);
}
I have a forum and I would like to automatically parse some of the major links. For example, if a user makes a post like this:
You should visit StackOverflow. I found it on Wikipedia.
it would automatically parse it like this:
You should visit StackOverflow. I found it on Wikipedia.
Is this even doable using JavaScript only?
Thanks for assistance. :-)
What you want to create a clean and extensible code is create a library of word => link then you can iterate over that and do your replace inside your code.
Here is a fiddle demo doing that http://jsfiddle.net/MjV84/
$(function() {
var text = $('#foo').text(),
library = {
stackoverflow: 'http://stackoverflow.com',
wikipedia: 'http://wikipedia.com'
},
name;
for (name in library) {
text = text.replace(new RegExp(name, 'gi'), function(word) {
return ''+word+'';
});
};
$('#foo ').html(text);
});
If you're pre-processing the text, you can use the replace function with a callback and a regular expression using an alternation:
var str = "You should visit StackOverflow. I found it on Wikipedia.";
str = str.replace(/StackOverflow|Wikipedia|etc/gi, function(m) {
var href;
switch (m.toLowerCase()) {
case "stackoverflow";
href = "http://stackoverflow.com";
break;
case "wikipedia";
href = "http://en.wikipedia.org";
break;
// ...and so on...
}
return '' + m + '';
});
YMMD points out that the above requires defining each keyword twice, which is true. When I've had to do this with a large number of keywords, I've done it by having an object with the keywords as keys, the href values as values, and built the expression dynamically:
// ==== Setup code you presumably do once
// The substitutions -- make sure the keys are in lower case
var substitutions = {
"stackoverflow": "http://stackoverflow.com",
"wikipedia": "http://en.wikipedia.org",
// ...and so on...
};
// Build the regex. Here I've used `Object.keys` which is an ES5 feature
// but you can use an ES5 shim (since it's something a shim can provide).
// Note that if your keywords include any special regular expression
// characters, you'll have to loop through the keys manually and escape
// those.
var subrex = new RegExp(Object.keys(substitutions).join("|"), "gi");
// ==== Where you're using it
var str = "You should visit StackOverflow. I found it on Wikipedia.";
str = str.replace(subrex, function(m) {
return '' + m + '';
});
Live example | source
Yes, use String.replace(regex, replaceString) to do that.
Here is an example:
var text = "You should visit StackOverflow. I found it on Wikipedia.";
var newText=text.replace(/stackoverflow/gi,
"<a href='http://www.stackoverflow.com/'>StackOverflow</a>");
The g stands for global, so it will replace all instances, and the i means case-insensitive search.
In case you are replacing common words, like "dictionary" to link to dictionary.com it would be better if you only replaced it if your users added a special tag, for example:
"You should visit StackOverflow. I found it on Wikipedia."
shouldn't be replaced with links unless it was written like this:
"You should visit &StackOverflow. I found it on Wikipedia."
Then your method would just need to add the special symbol.
Also, I would have the data in an array like this:
var linkArray = [ ["StackOverflow", "http://www.stackoverflow.com/", "Description"],
["Wikipedia", "http://wikipedia.org/", "Free encyclopedia"] ];
Then create a loop to find and replace the instances:
function addLinks(textInput) {
for (var i=0; i<linkArray.length; i++) {
textInput = addLink(textInput, linkArray[i]);
}
return textInput;
}
function addLink(textInput, link) {
var replaceString = "<a href=\"" + link[1] + "\" title=\""
+ link[2] + "\">"
+ link[0] + "</a>";
return textInput.replace(new RegExp("&"+link[0], "gi"), replaceString);
}
All the previous answers using the i modifier on the regular expression fail if the target
string contains variants of the substitution strings differing by case. This is because the
target string substring does not match the substitutions attribute name.
This version solves this by capturing each of the substitution strings and searching the arguments array for the found string.
function substitute (str) { 'use strict';
var substitutions = {
"Stack Overflow": "http://stackoverflow.com",
"Wikipedia": "http://en.wikipedia.org",
// ...and so on...
},
skeys = Object.keys (substitutions);
// build regexp which will capture each match separtely
return str.replace (new RegExp ('(' + skeys.join(")|(") + ')', "gi"), function (m0) {
// Now scan the arguments array (omitting the last two arugments which
// are the source string and match index)
for (var ai, i = arguments.length - 2; --i;) {
// The index of the argument (less 1) corresponds to the index in skeys of
// the name in the substitutions
if ((ai = arguments[i])) {
return '' + ai + '';
}
}
return m0;
});
}
var str = "You should visit stack overflow. I found it on Wikipedia.";
// check in console log that links are correctly built.
console.log (substitute (str));
document.write (substitute (str));
See the jsfiddle : http://jsfiddle.net/NmGGN/
I need to find the most efficient way of matching multiple regular expressions on a single block of text. To give an example of what I need, consider a block of text:
"Hello World what a beautiful day"
I want to replace Hello with "Bye" and "World" with Universe. I can always do this in a loop ofcourse, using something like String.replace functions availiable in various languages.
However, I could have a huge block of text with multiple string patterns, that I need to match and replace.
I was wondering if I can use Regular Expressions to do this efficiently or do I have to use a Parser like LALR.
I need to do this in JavaScript, so if anyone knows tools that can get it done, it would be appreciated.
Edit
6 years after my original answer (below) I would solve this problem differently
function mreplace (replacements, str) {
let result = str;
for (let [x, y] of replacements)
result = result.replace(x, y);
return result;
}
let input = 'Hello World what a beautiful day';
let output = mreplace ([
[/Hello/, 'Bye'],
[/World/, 'Universe']
], input);
console.log(output);
// "Bye Universe what a beautiful day"
This has as tremendous advantage over the previous answer which required you to write each match twice. It also gives you individual control over each match. For example:
function mreplace (replacements, str) {
let result = str;
for (let [x, y] of replacements)
result = result.replace(x, y);
return result;
}
let input = 'Hello World what a beautiful day';
let output = mreplace ([
//replace static strings
['day', 'night'],
// use regexp and flags where you want them: replace all vowels with nothing
[/[aeiou]/g, ''],
// use captures and callbacks! replace first capital letter with lowercase
[/([A-Z])/, $0 => $0.toLowerCase()]
], input);
console.log(output);
// "hll Wrld wht btfl nght"
Original answer
Andy E's answer can be modified to make adding replacement definitions easier.
var text = "Hello World what a beautiful day";
text.replace(/(Hello|World)/g, function ($0){
var index = {
'Hello': 'Bye',
'World': 'Universe'
};
return index[$0] != undefined ? index[$0] : $0;
});
// "Bye Universe what a beautiful day";
You can pass a function to replace:
var hello = "Hello World what a beautiful day";
hello.replace(/Hello|World/g, function ($0, $1, $2) // $3, $4... $n for captures
{
if ($0 == "Hello")
return "Bye";
else if ($0 == "World")
return "Universe";
});
// Output: "Bye Universe what a beautiful day";
An improved answer:
var index = {
'Hello': 'Bye',
'World': 'Universe'
};
var pattern = '';
for (var i in index) {
if (pattern != '') pattern += '|';
pattern += i;
}
var text = "Hello World what a beautiful day";
text.replace(new RegExp(pattern, 'g'), function($0) {
return index[$0] != undefined ? index[$0] : $0;
});
If the question is how to replace multiple generic patterns with corresponding replacements - either strings or functions, it's quite tricky because of special characters, capturing groups and backreference matching.
You can use https://www.npmjs.com/package/union-replacer for this exact purpose. It is basically a string.replace(regexp, string|function) counterpart, which allows multiple replaces to happen in one pass while preserving full power of string.replace(...).
Disclosure: I am the author and the library was developed because we had to support user-configured replaces.
A common task involving replacing a number of patterns is making a user's or other string "safe" for rendering on Web pages, which means preventing HTML tags from being active. This can be done in JavaScript using HTML entities and the forEach function, allowing a set of exceptions (that is, a set of HTML tags that will be allowed to render).
This is a common task, and here is a fairly brief way to accomplish it:
// Make a string safe for rendering or storing on a Web page
function SafeHTML(str)
{
// Make all HTML tags safe
let s=str.replace(/</gi,'<');
// Allow certain safe tags to be rendered
['br','strong'].forEach(item=>
{
let p=new RegExp('<(/?)'+item+'>','gi');
s=s.replace(p,'<$1'+item+'>');
});
return s;
} // SafeHTML