Replace anchor tags with spans, and place the href attribute into the span I'm required to perform a slight conversion on a string which contains html.

The conversion should transform a elements such that they become span elements. The conversion should also place the href attribute from the original a element into the span.

I have what I think is a working solution, but the code feels overcomplicated. I had hoped this may be possible with relatively little code, so I was wondering if anyone could suggest ways to make this code neater.

Browser compatibility is not an issue, as this will be transpiled by babel.

Here's what I have:



const testHtml = `
  &lt;p&gt;This is a test paragraph&lt;/p&gt;
  &lt;a style="font-weight: bold;" href="http://www.google.co.uk"&gt;Google is here&lt;/a&gt;
  &lt;a style="font-weight: bold;" href="http://www.msn.co.uk"&gt;Msn is here&lt;/a&gt;
  &lt;h2&gt;This is a heading&lt;/h2&gt;
`

const splitByLinkEnds = html =&gt; {
  const split = html.split('&lt;/a&gt;')
  const allButLast = split.slice(0, -1)
  const last = split.slice(-1)[0]

  return [
    ...allButLast.map(x =&gt; `${x}&lt;/a&gt;`),
    last
  ]
}

const splitByLinks = html =&gt; {
  const split = html.split('&lt;a');
  const [
    first,
    ...result
  ] = split

  const withReaddedOpeningTags = [
    first,
    ...result.map(x =&gt; `&lt;a${x}`)
  ]

  return withReaddedOpeningTags
    .reduce((prev, curr) =&gt; [
      ...prev,
      curr.indexOf('&lt;a') &gt; -1 ?
        splitByLinkEnds(curr) : curr
    ], []).flat()
}

const transformLinkToTextOnly = linkHtml =&gt; {
  const linkHref = linkHtml
    .match('href="[^"]*"')[0]
    .replace('href="', '')
    .replace('"', '')

  const htmlWithSpan = linkHtml
    .replace('&lt;a', '&lt;span')
    .replace('&lt;/a&gt;', '')

  return `${htmlWithSpan} (${linkHref})&lt;/span&gt;`
}

const transformLinksToTextOnly = html =&gt;
  splitByLinks(html)
    .map(x =&gt; x.indexOf('&lt;a') &gt; - 1 ?
      transformLinkToTextOnly(x) : x
    ).join('')

const result = transformLinksToTextOnly(testHtml)

document.body.innerHTML += testHtml
document.body.innerHTML += '&lt;hr /&gt;'
document.body.innerHTML += result






Edit
I should note that the solution ought to work in node, ideally. I appreciate that the fact I include dom manipulation in the question is quite misleading, I included this more to attempt to create an easy working example that to demonstrate how the code might actually be used.

A further note, the performance of this code is not particularly important. It's only for relatively small amounts of html and gets run once when the application loads.
One thing to consider is the whole working with HTML as string. In the answer I'll assume you work within the web-browser. The web-browser can parse the HTML for you, this way you'll only have to work with nodes.

Here is an example:



const testHtml = `
  &lt;p&gt;This is a test paragraph&lt;/p&gt;
  &lt;a style="font-weight: bold;" href="http://www.google.co.uk"&gt;Google is here&lt;/a&gt;
  &lt;a style="font-weight: bold;" href="http://www.msn.co.uk"&gt;Msn is here&lt;/a&gt;
  &lt;h2&gt;This is a heading&lt;/h2&gt;
`;

function transformLinksToTextOnly(htmlString) {
  const root     = document.createElement('root');
  root.innerHTML = htmlString;
  const anchors  = root.querySelectorAll('a');

  anchors.forEach(anchor =&gt; {
    const span = document.createElement('span');

    span.innerHTML = anchor.innerHTML;
    if (anchor.href) 
      span.innerHTML += ` (${anchor.href})`;

    anchor
      .getAttributeNames()
      .forEach(attrName =&gt; {
        const attrValue = anchor.getAttribute(attrName);
        span.setAttribute(attrName, attrValue);
      });

    anchor.parentNode.replaceChild(span, anchor);
  });

  return root.innerHTML;
}

const result = transformLinksToTextOnly(testHtml);

document.body.innerHTML += testHtml;
document.body.innerHTML += '&lt;hr /&gt;';
document.body.innerHTML += result;