admin管理员组文章数量:1278789
I have a string like this:
"Size: 40; Color: 30"
I want to create tooltips for them such that it looks like this:
<span class='tooltip' data-tooltip='The Size of a Unit is controlled by the Color of the Unit.'>Size</span>: 40; <span class='tooltip' data-tooltip='The Color of a Unit is a standard Setting.'>Color</span>: 30
Using a naive replacement however I end up with this:
<span class='tooltip' data-tooltip='The Size of a Unit is controlled by the <span class='tooltip' data-tooltip='The Color of a Unit is a standard Setting.'>Color</span> of the Unit.'>Size</span>: 40; <span class='tooltip' data-tooltip='The Color of a Unit is a standard Setting.'>Color</span>: 30
Which is not what I want. How do I write a regex or do a replacement in such a way that it doesn't replace text that's already part of the tooltip?
Edit: I didn't make it clear that the replacements are not Size and Color, they're just examples. I'm adding an arbitrary amount, usually 20+ tooltips to any string.
Here are some testables:
var tooltips = {
"Size":"The Size of a Unit is controlled by the Color",
"Color": "bar",
"Time and Size": "foo"
}
"Here we have something of <b>Size</b> 20 and Color red. it's very important that the Time and Size of the work and kept in sync."
Should result in:
"Here we have something of <b><span class='tooltip' data-tooltip='The Size of a Unit is controlled by the Color'>Size<span></b> 20 and <span class='tooltip' data-tooltip='bar'>Color<span> red. it's very important that the <span class='tooltip' data-tooltip='foo'>Time and Size<span> of the work and kept in sync."
The longer match should take precedence over shorter matches. It should match on only whole words and not parts of words.
Edit: Forgot to state yet another requirement.
It should still match strings that are wrapped with tags that are not tooltips.
I have a string like this:
"Size: 40; Color: 30"
I want to create tooltips for them such that it looks like this:
<span class='tooltip' data-tooltip='The Size of a Unit is controlled by the Color of the Unit.'>Size</span>: 40; <span class='tooltip' data-tooltip='The Color of a Unit is a standard Setting.'>Color</span>: 30
Using a naive replacement however I end up with this:
<span class='tooltip' data-tooltip='The Size of a Unit is controlled by the <span class='tooltip' data-tooltip='The Color of a Unit is a standard Setting.'>Color</span> of the Unit.'>Size</span>: 40; <span class='tooltip' data-tooltip='The Color of a Unit is a standard Setting.'>Color</span>: 30
Which is not what I want. How do I write a regex or do a replacement in such a way that it doesn't replace text that's already part of the tooltip?
Edit: I didn't make it clear that the replacements are not Size and Color, they're just examples. I'm adding an arbitrary amount, usually 20+ tooltips to any string.
Here are some testables:
var tooltips = {
"Size":"The Size of a Unit is controlled by the Color",
"Color": "bar",
"Time and Size": "foo"
}
"Here we have something of <b>Size</b> 20 and Color red. it's very important that the Time and Size of the work and kept in sync."
Should result in:
"Here we have something of <b><span class='tooltip' data-tooltip='The Size of a Unit is controlled by the Color'>Size<span></b> 20 and <span class='tooltip' data-tooltip='bar'>Color<span> red. it's very important that the <span class='tooltip' data-tooltip='foo'>Time and Size<span> of the work and kept in sync."
The longer match should take precedence over shorter matches. It should match on only whole words and not parts of words.
Edit: Forgot to state yet another requirement.
It should still match strings that are wrapped with tags that are not tooltips.
Share Improve this question edited Jan 13, 2014 at 10:57 Harry asked Jan 11, 2014 at 10:01 HarryHarry 55k76 gold badges185 silver badges270 bronze badges 8-
Why don't you replace
Color
first and thenSize
? – thefourtheye Commented Jan 11, 2014 at 10:06 - 2 As you said that Size and Color is just samples how do you know the text to put on the data-tooltip ? – Jorge Campos Commented Jan 13, 2014 at 10:26
- @JorgeCampos Oh it es from user input. User gives the tooltip text and the keyword(s) to replace. – Harry Commented Jan 13, 2014 at 10:31
-
Ok, another question, if it is a user input does the user provide some sort of replacement string to put the words (eg: Color) or they just put the text for each string like: The string is
"XPTO: 40; TUFF: 30"
and user the provide: 'bla XPTO blah TUFF bleh' and 'The bleh blah bli TUFF' ? You should provide on your question some data input samples from the user, so we can analyse and help you in correct way. Otherwise you will get all kinds of answer trying to responde to an inplete question. – Jorge Campos Commented Jan 13, 2014 at 10:39 - @JorgeCampos Everything is a user input. Sorry I should get better at asking questions, you'd think I'd be pretty good at it by now with 3k points but heh. – Harry Commented Jan 13, 2014 at 10:43
8 Answers
Reset to default 2 +100I think a single str.replace will do the work if got the right regexp pattern.
function replaceTooltips(str, tooltips) {
//copy from https://developer.mozilla/en/docs/Web/JavaScript/Guide/Regular_Expressions
function escapeRegExp(string) {
return string.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1");
}
var keys = [];
for ( var k in tooltips) {
keys.push(escapeRegExp(k));
}
//create a regexp like (Size)|(Color)|(Time and Size)
var ptn = new RegExp('(' + keys.join(')|(') + ')', 'g');
str = str.replace(ptn, function(mat) {
if (!tooltips[mat]) {
return mat;
}
return '<span class="tooltip" data-tooltip="' + tooltips[mat].replace(/"/g, '"') + '">' + mat + '</span>';
});
return str;
}
http://jsfiddle/rooseve/6wRUF/
Many of the other answers are just band-aids on the core problem: sometimes the searches are found in the replacements of other strings.
For examples some of the other naive solutions that will break easily:
- changing the order (doesn't work if both contain the other)
- looking ahead or back (doesn't work if replace template changes, plus HTML regex parsing is notoriously hard)
But if we think back to the core problem there is an obvious solution:
Don't let the replacements be replaceable.
So, do two passes instead. Steps:
- Instead of a object/dictionary use an array of objects. Each item should be
{ search: "Size", tooltip: "Long description" }
. - Sort the array by the length of the search string (you said you wanted longer to take precedence, so this is how it happens)
- Iterate through array and replace in order all keywords with a unique string involving its index in the array. For example Size and color bees ###replace0###.
- Iterate again and replace all unique strings with the tooltip, so ###replace0### bees
<span class='tooltip' data-tooltip='The size is one thing, the color is another.'>Size and color</span>
.
This way the whole operation acts as one search/replace and does not have the possibility of replacing matches in other replacements.
I'd use the following:
//usual regex
var re = new RegExp("("+key+")(?!(?:<|.+?'>))", "g");
//regex to be applied on reversed string, same concept as regex above
var reRev = new RegExp("("+key.reverse()+")(?!(?:.+'=|>))", "g");
//start of the span tag including the tooltip
var repl = "<span class='tooltip' data-tooltip='"+value+"'>";
//end of the span tag
var replEnd = "</span>";
//first replacement
output = output.replace(re, repl+"$1"+replEnd);
//now reverse the whole string and do a second replacement,
//this, together with the reversed regex (which uses a lookahead) mimics a lookbehind (which is unfortunately not available in JS)
output = output.reverse().replace(reRev, replEnd.reverse()+"$1"+repl.reverse()).reverse();
Regex-Demo @ regex101
JS-Demo @ JSFiddle
See the JSFiddle as for the replacement of sentences you have to order the input-array first!
For every replacement the regex matches we replace it with the according span-tooltip-construct.
As JS has no lookbehind we have to mimic it to replace every occurence of the keywords, because the first regex will fail to match keywords before an opening <span>
-tag, which could easily be solved with a lookbehind:
To mimic a lookbehind we use a lookahead, but simply reverse the whole text beforehand. The only downside is, that you have to reverse your regex, too... manually! For bigger expressions this will be a pain, but in this case it's fairly easy. If you reverse your input you don't want to match keywords if there es a .+'=
or a >
afterwards.
For the regex itself:
it matches only the keyword if it is not followed by a <
(which would mark the </span>
tag) and if it is not followed by .+'>
, which means several chars and a '>
which would mark the end of a data-tooltip
attribute.
I made it case sensitive. If you want it to be case insensitive use the gi
flags instead of only the g
flag.
This way you're not limited to single words, you may replace "I am a sentence" with a tooltip of your choice, too. Same concept applies to the reversed regex.
You may need to adjust the replacement according to your datastructure. If it es from user input, then an associative array may be right for you:
var replacements = new Array ();
replacements['Size'] = "The Size of a Unit is controlled by the Color of the Unit.";
replacements['Color'] = "The Color of a Unit is a standard Setting.";
It could be this simple if there were no multi-word replacements.
var tooltips = {
"Size":"The Size of a Unit is controlled by the Color",
"Color": "bar",
"Time and Size": "foo"
}
var text = "Here we have something of Size 20 and Color red. it's very important that the Time and Size of the work and kept in sync."
var replaced = text.split(" ").map(function(token) {
return tooltips[token] || token;
}).join(" ");
Simply loop through the words of your string and if a tooltip is found for a word - replace it with the tooltip span:
var s1 = 'Here we have something of <b>Size</b> 20 and Color red. it\'s very important that the Time and Size of the work and kept in sync.',
// replace object
rep = {
size: '<span class="tooltip" data-tooltip="The Size of a Unit is controlled by the Color of the Unit.">Size</span>',
color: '<span class="tooltip" data-tooltip="The Color of a Unit is a standard Setting.">Color</span>',
'time and size': '<span class="tooltip" data-tooltip="Time and Size tooltip">Foo</span>'
},
// build RegExp out of replace object keys
reg = new RegExp('(' + Object.keys(rep).join(')|(') + ')', 'ig');
// replace
s2 = s1.replace(reg, function(s) {
return rep[s.toLowerCase()] ? rep[s.toLowerCase()] : s;
});
console.log(s2);
You can use jQuery to locate all text nodes inside an element. Afterwards, you can use DOM functions (instead of regex) to split the text around the specified word, then wrap the word inside tooltip. Here is an exmple:
function replaceTextWithSpan(node, text, options) {
var searchText = text.toLowerCase(),
currentNode = node,
matchIndex,
newTextNode,
newSpanNode;
while ((matchIndex = currentNode.data.toLowerCase().indexOf(searchText)) >= 0) {
newTextNode = currentNode.splitText(matchIndex);
currentNode = newTextNode.splitText(searchText.length);
newSpanNode = document.createElement("span");
newSpanNode.className = "tooltip";
newSpanNode.setAttribute("data-tooltip", options["data-tooltip"]);
currentNode.parentNode.insertBefore(newSpanNode, currentNode);
newSpanNode.appendChild(newTextNode);
}
}
And a test:
<div id="test">Size: 40; Color: 30; <b>Bold Size Test:</b> 20; <span>Another Size Test: 10</span></div>
$("#test, #test *").contents().filter(function () {
return this.nodeType == this.TEXT_NODE;
}).each(function () {
replaceTextWithSpan(this, "Size", { "data-tooltip": "The Size of a Unit is controlled by the Color of the Unit." });
});
$("#test, #test *").contents().filter(function () {
return this.nodeType == this.TEXT_NODE;
}).each(function () {
replaceTextWithSpan(this, "Color", { "data-tooltip": "The Color of a Unit is a standard Setting." });
});
alert($("#test").html());
And result:
<span class="tooltip" data-tooltip="The Size of a Unit is controlled by the Color of the Unit.">Size</span>: 40;
<span class="tooltip" data-tooltip="The Color of a Unit is a standard Setting.">Color</span>: 30;
<b>Bold <span class="tooltip" data-tooltip="The Size of a Unit is controlled by the Color of the Unit.">Size</span> Test:</b> 20;
<span>Another <span class="tooltip" data-tooltip="The Size of a Unit is controlled by the Color of the Unit.">Size</span> Test: 10</span>
Demo here
Original answer: Here is a solution that does not use RegEx:
- Loop over the text/HTML child nodes inside the element
- Skip HTML nodes and text nodes that do not contain the search string
- Break the text node before and after the search string (so that you end up with three text nodes)
- Wrap the middle node inside a span
Here is the code (I undestand that it is plicated):
function replaceTextWithSpan(node, text, options) {
var searchText = text.toLowerCase(),
currentNode = node.firstChild,
matchIndex,
newTextNode,
newSpanNode;
while (currentNode) {
matchIndex = currentNode.nodeType === currentNode.TEXT_NODE
? currentNode.data.toLowerCase().indexOf(searchText)
: -1;
if (matchIndex >= 0) {
newTextNode = currentNode.splitText(matchIndex);
currentNode = newTextNode.splitText(searchText.length);
newSpanNode = document.createElement("span");
// the following line can be replaced with for...in
// loop to assign multiple attributes to the span
newSpanNode.className = options.className;
currentNode.parentNode.insertBefore(newSpanNode, currentNode);
newSpanNode.appendChild(newTextNode);
} else {
currentNode = currentNode.nextSibling;
}
}
}
Here is a test:
var node = document.createElement("div");
node.innerHTML = "Size: 40; Color: 30; Span: For testing";
replaceTextWithSpan(node, "Size", { className: "highlight" });
replaceTextWithSpan(node, "Color", { className: "highlight" });
replaceTextWithSpan(node, "Span", { className: "highlight" });
alert(node.innerHTML);
This produces following output (pretty printed):
<span class="highlight">Size</span>: 40;
<span class="highlight">Color</span>: 30;
<span class="highlight">Span</span>: For testing
Demo here
If they are always separated by ;
then you should split the string there, replace each part by the appropriate string and then join them again..
Something like
var tooltips = {
'size': 'The Size of a Unit is controlled by the Color of the Unit.',
'color': 'The Color of a Unit is a standard Setting.'
..etc..
},
myString = "Size: 40; Color: 30",
stringParts = myString.split(';');
for (var i = 0, len = stringParts.length; i < len; i++){
var pair = stringParts[i].split(':'),
key = pair[0].trim().toLowerCase(),
tip = tooltips[key];
if (tip){
pair[0] = '<span class="tooltip" data-tooltip="'+ tip +'">' + key + '</span>';
}
}
stringParts[i] = pair.join(':');
}
alert( stringParts.join('; ') );
if your browser does not natively support the .trim()
function then find an implementation at Trim string in JavaScript?
You can use the following code :
var tooltips = {
"Size":"The Size of a Unit is controlled by the Color",
"Color": "bar",
"Time and Size": "foo"
}
var str="Here we have something of <b>Size</b> 20 and Color red. it's very important that the Time and Size of the work and kept in sync."
var len=0;
var res=str;
var rep="<span class='tooltip' data-tooltip='";
$.each(tooltips, function(key, value) {
var patt1=new RegExp(key);
var ar=patt1.exec(str);
var repstr=rep+value+"'>"+key+"<span>";
res=res.substr(0,ar.index+len)+repstr+res.substr(ar[0].length+ar.index+len);
len+=repstr.length-key.length;
});
alert("result:" +res);
版权声明:本文标题:regex - javascript create tooltip for part of the string that's not already under a tooltip - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1741283644a2370151.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论