I was trying to highlight substring within string using start and end index. But i am facing issue while highlighting overlapped substring. e.g.
I have a text
and an array
for highlighting.
Is there any way, how can I avoid the overlapping tags.
I tried with regex
match of the substring, but couldn't able to solve it.
let tagObject = {
"text": "I am asking a question in stackoverflow",
"tags": [{
start: 0,
end: 1,
value: "I",
tag: "TAG1"
},
{
start: 0,
end: 4,
value: "I am",
tag: "TAG2"
},
{
start: 5,
end: 22,
value: "asking a question",
tag: "TAG3"
},
{
start: 14,
end: 22,
value: "question",
tag: "TAG4"
},
{
start: 14,
end: 25,
value: "question in",
tag: "TAG5"
}
]
}
const color = {
outer: "color: #1d39c4; background: #f0f5ff; border-color: #adc6ff;",
inner: "background: #adc6ff;",
bg: "#f0f5ff"
}
const createStyledString = (obj) => {
let {
text,
tags
} = obj;
tags.sort((a, b) => b.start - a.start);
tags.forEach(t => {
const {
start,
end,
value,
tag
} = t;
text = text.substring(0, start)
`<span contenteditable="false" data-action="${value}---${start}---${end}---${color.bg}---${tag}" unselectable="on" onselectstart="return false;" name="tag" style="${color.outer}">${value}<span unselectable="on" onselectstart="return false;" data-action="${value}---${start}---${end}---${color.bg}---${tag}" style="${color.inner}">${tag}</span></span>`
text.substring(end)
});
return text;
}
document.body.innerHTML=createStyledString(tagObject)
CodePudding user response:
There are two main problems in your approach.
- First of all, if you have nested values you are appending them more than once.
- Second, you are treating the start and end positions as if they didn't change after the string was modified.
So a possible solution would be the following:
- Don't nest values, just take the same substring once again.
- Keep track of the real position as well as the initial, "virtual", position, and use it to insert the code in the correct place.
Here's a working snippet.
let tagObject = {
"text": "I am asking a question in stackoverflow",
"tags": [{
start: 0,
end: 1,
value: "I",
tag: "TAG1"
},
{
start: 0,
end: 4,
value: "I am",
tag: "TAG2"
},
{
start: 5,
end: 22,
value: "asking a question",
tag: "TAG3"
},
{
start: 14,
end: 22,
value: "question",
tag: "TAG4"
},
{
start: 14,
end: 25,
value: "question in",
tag: "TAG5"
}
]
}
const color = {
outer: "color: #1d39c4; background: #f0f5ff; border-color: #adc6ff;",
inner: "background: #adc6ff;",
bg: "#f0f5ff"
}
const createStyledString = (obj) => {
let {
text,
tags
} = obj;
tags.sort((a, b) => b.start - a.start);
// TO KEEP TRACK OF INSERTED TEXT
let insertedAmmount = []
tags.forEach(t => {
const {
start,
end,
value,
tag
} = t;
// COMPUTE THE REAL START AND END POSITIONS
// TAKING INSERTED TEXT INTO ACCOUNT
let realStart = start
let realEnd = end
for(let idx in insertedAmmount){
if(idx < start){
realStart = insertedAmmount[idx]
}
if(idx <= end){
realEnd = insertedAmmount[idx]
}
}
let pre = `<span contenteditable="false" data-action="${value}---${start}---${end}---${color.bg}---${tag}" unselectable="on" onselectstart="return false;" name="tag" style="${color.outer}">`
let pos = `<span unselectable="on" onselectstart="return false;" data-action="${value}---${start}---${end}---${color.bg}---${tag}" style="${color.inner}">${tag}</span></span>`
// UPDATE THE INFORMATION ABOUT INSERTED TEXT
insertedAmmount[start] = (insertedAmmount[start] || 0) pre.length
insertedAmmount[end] = (insertedAmmount[end] || 0) pos.length
// DON'T INSERT DIRECTLY THE VALUE BUT THE ALREADY EXISTENT TEXT
text = text.substring(0, realStart)
pre
text.substring(realStart, realEnd)
pos
text.substring(realEnd)
});
return text;
}
document.body.innerHTML=createStyledString(tagObject)