Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JavaScript - Insert HTML markup into string given styling indexes

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;
};
};
like image 548
JackWilson Avatar asked Mar 04 '26 03:03

JackWilson


1 Answers

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.

like image 167
Robby Cornelissen Avatar answered Mar 06 '26 15:03

Robby Cornelissen



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!