abhi
abhi

Reputation: 379

Javascript extract anchor tag from a string and create new React component from it

I am using React and Javascript and have a requirement where I need to extract certain anchor tags, take their attributes like id, href and create a custom React link component out of it. My string looks like this "<p>Bla bla bla bla.</p>\n<ul>\n<li><strong>Bla bla</strong>. Bla bla bla.</li>\n<li><strong>Bla bla bla</strong>.<a href='#footnote1' id='sup1'><sup>1</sup></a> Bla bla bla.</li>\n<li><strong>Bla bla bla.<a href='#footnote2' id='sup2'><sup>2</sup></a>\r\n</strong>&#160; Bla bla bla.</li>\n<li><strong>Bla bla:</strong>Bla bla <a href=\"https://google.com\" target=\"no\">Google</a> * bla bla bla.<a href='#footnote1' id='sup3'><sup>1</sup></a></li>\n</ul>\n<p>&#160;</p>"

I want to only extract all those <a> that have <sup> inside them. I want to then take the attributes of such <a> and create a custom React link component like

<MyLink
      id={id of the anchor tag}
      addClasses={"footnote-link"}
      href={href of anchor tag}
      handleClick={myClickHandler}
      ariaProps={{
        label: {text inside the sup tag}
      }}
    >
      <sup className={href of anchor tag !== null ? "custom-class" : ""}>{text inside the sup tag}</sup>
    </MyLink>

I then want to replace those <a> with my custom component in the string. Reason I am trying to convert <a> to custom component is that I want the new anchor tag to be A11Y compliant and anchor tags and the source string are not in my control.

I tried the combination of How to get the href value of an anchor tag with javascript from a string and javascript regex to extract anchor text and URL from anchor tags, but couldn't make much progress. I also tried react-string-replace, but that didn't help much either.

I also have an option of getting <a href='#footnote1' id='sup1'><sup>1</sup></a> replaced with something like #FootNote({id:\"sup1-CB\", goto:\"sup1\", data:\"1\"})#FootNote and then use "id", "goto" and "data" to create the custom link.

Can anyone provide some guidance on how to achieve this? I apologize for the long question.

Upvotes: 0

Views: 2831

Answers (2)

ray
ray

Reputation: 27265

You might consider parsing the HTML and modifying it using DOM methods, then rendering it back out as a string. The demo below is just a proof of concept, but it could be incorporated into a React component that does the same sort of thing.

Note: This is similar to Dean's answer which has a more React-specific (and arguably cleaner) example.

const input = "<p>Bla bla bla bla.</p>\n<ul>\n<li><strong>Bla bla</strong>. Bla bla bla.</li>\n<li><strong>Bla bla bla</strong>.<a href='#footnote1' id='sup1'><sup>1</sup></a> Bla bla bla.</li>\n<li><strong>Bla bla bla.<a href='#footnote2' id='sup2'><sup>2</sup></a>\r\n</strong>&#160; Bla bla bla.</li>\n<li><strong>Bla bla:</strong>Bla bla <a href=\"https://google.com\" target=\"no\">Google</a> * bla bla bla.<a href='#footnote1' id='sup3'><sup>1</sup></a></li>\n</ul>\n<p>&#160;</p>"

const parser = new DOMParser();

const doc = parser.parseFromString(input, 'text/html');

// might be a more efficient way to do this. just querying for anchors
// and filtering out the ones that don't have a <sup> child
const anchors = [...doc.querySelectorAll('a')].filter(elem => elem.querySelector('sup'));

anchors.forEach(a => {
  const sup = a.querySelector('sup');
  a.setAttribute('aria-label', sup.innerText);
  // etc.
})

document.getElementById('demo').innerHTML = doc.body.innerHTML;
document.getElementById('source').innerText = doc.body.innerHTML;
<h2>Resulting html</h2>
<pre id="source"></pre>

<hr/>

<h2>Rendered result</h2>
<div id="demo"></div>

Upvotes: 0

Dean James
Dean James

Reputation: 2623

Here's an example that uses a created tag to parse your html, then use DOM methods to extract the data you need:

const str =
  "<p>Bla bla bla bla.</p>\n<ul>\n<li><strong>Bla bla</strong>. Bla bla bla.</li>\n<li><strong>Bla bla bla</strong>.<a href='#footnote1' id='sup1'><sup>1</sup></a> Bla bla bla.</li>\n<li><strong>Bla bla bla.<a href='#footnote2' id='sup2'><sup>2</sup></a>\r\n</strong>&#160; Bla bla bla.</li>\n<li><strong>Bla bla:</strong>Bla bla <a href=\"https://google.com\" target=\"no\">Google</a> * bla bla bla.<a href='#footnote1' id='sup3'><sup>1</sup></a></li>\n</ul>\n<p>&#160;</p>";

const el = document.createElement("html");
el.innerHTML = str;
const anchors = el.getElementsByTagName("a");

const MyLink = () => null;
const myClickHandler = () => null;

export default () =>
  [...anchors].map((anchor, index) => {
    const sup = anchor.getElementsByTagName("sup");
    if (sup.length === 0) {
      return null;
    }

    return (
      <MyLink
        key={index.toString()}
        id={anchor.id}
        addClasses={"footnote-link"}
        href={anchor.href}
        handleClick={myClickHandler}
        ariaProps={{
          label: sup[0].innerHTML
        }}
      >
        <sup className={anchor.href ? "custom-class" : ""}>{sup[0].innerHTML}</sup>
      </MyLink>
    );
  });


Replace MyLink and myClickHandler with your own.

Upvotes: 1

Related Questions