I have a piece of text.
"This is a test to see whether nested style spans work properly."
I have the styling information as a JSON object, e.g.
0: {start: 22, end: 54, type: "strong"}
1: {start: 30, end: 36, type: "hyperlink", data: {…}}
2: {start: 37, end: 48, type: "em"}
3: {start: 43, end: 48, type: "hyperlink", data: {…}}
The text is supposed to look like
<p>This is a test to see <strong>whether <a href="https://www.google.co.za">nested</a> <em>style <a href="https://www.google.co.za">spans</a></em> work </strong>properly.</p>
What should be the general approach for an algorithm? The problem is that the indexes contained in the styling information obviously become obsolete once I start inserting markup into the text to style it.
I tried keeping track of the length of characters I'm inserting through a buffer (so that the indexes could be adjusted with the buffer length), but that became an issue with nested tags. My entire 'solution' feels incredibly clumsy and unwieldy, I'm sure there must be a better approach.
This is the code I've attempted.
NewsUtils.styleSpanCSS = span => {
let styledSpan = {};
switch (span.type) {
case "em":
styledSpan.opening = "<span";
styledSpan.style = ' class="italic">';
styledSpan.closing = "</span>";
break;
case "hyperlink":
styledSpan.opening = `<a href="${span.data.url}">`;
styledSpan.style = "";
styledSpan.closing = "</a>";
break;
case "strong":
styledSpan.opening = "<span";
styledSpan.style = ' class="bold">';
styledSpan.closing = "</span>";
break;
default:
styledSpan.opening = "";
styledSpan.style = "";
styledSpan.closing = "";
}
styledSpan.length =
styledSpan.opening.length +
styledSpan.style.length +
styledSpan.closing.length;
return styledSpan;
};
NewsUtils.styleParagraph = elem => {
if (elem.spans.length > 0) {
let buffer = 0;
elem.spans.map(span => {
let elementToInsert = NewsUtils.styleSpanCSS(span);
let spanLength =
elementToInsert.opening.length +
elementToInsert.style.length +
elementToInsert.closing.length;
elem.text =
elem.text.substring(0, span.start + buffer) +
elementToInsert.opening +
elementToInsert.style +
elem.text.substring(span.start + buffer, span.end + buffer) +
elementToInsert.closing +
elem.text.substring(span.end + buffer, elem.text.length + buffer);
buffer += spanLength;);
});
return <p dangerouslySetInnerHTML={{ __html: elem.text }} />;
}
return <p dangerouslySetInnerHTML={{ __html: elem.text }} />;
};
NewsUtils.markupParagraphs = post => {
const postDetails = post.data.text.map(elem => {
switch (elem.type) {
case "paragraph":
return NewsUtils.styleParagraph(elem);
case "image":
return (
<img
src={elem.url}
width={elem.dimensions.width}
height={elem.dimensions.height}
/>
);
case "embed":
let url = elem.oembed.embed_url;
url = url.substring(0, url.indexOf("&"));
url = url.replace("watch?v=", "embed/");
url = url.replace("vimeo.com", "player.vimeo.com/video");
return <iframe src={url} frameBorder="0" allowFullScreen />;
default:
return null;
}
});
return postDetails;
};
};
Here's a basic implementation. I got rid of the hyperlink special handling to demonstrate the algorithm itself, but it should be easy to add that logic back in:
const text = 'This is a test to see whether nested style spans work properly.'
const styling = [
{start: 22, end: 54, type: "strong"},
{start: 30, end: 36, type: "a"},
{start: 37, end: 48, type: "em"},
{start: 43, end: 48, type: "a"}
];
const result = [...text].reduce((a, v, i) => {
styling.filter(s => s.start === i).forEach(s => a += `<${s.type}>`);
styling.filter(s => s.end === i).forEach(s => a += `</${s.type}>`);
return a + v;
}, '');
document.body.innerHTML = result;
Output:
This is a test to see <strong>whether <a>nested</a> <em>style <a>spans</em></a> work </strong>properly.
If your input and styling array are large, you might want to create temporary lookup objects to increase performance.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With