# Replacing DOM elements that have no attributes

The institution I work for uses an application that has an awful software bug causing useless <code>span</code> tags to appear next to "misspelled" words in iframes.

For instance, HTML that should look like this:

<span class="myClass">Hello! My name is Tabitha.</span>


<span class="myClass">Hello! My name is <span><span><span><span><span>Tabitha.</span></span></span></span></span></span>


My band-aid solution, while we wait for a fix from the application developer, has been to add a script that finds all attribute-less span tags and replaces them with a new <code>textNode</code>. This works because these extra tags are always the direct parent of the <code>textNode</code> containing the misspelled word (or another extra span element). Any formatting or inline styling would occur outside that scope and would thus be unaffected.

But, something tells me there's got to be a better way to do it. Any thoughts?

function fixSpan(iframe) {
var doc = iframe.contentDocument;
var spans = doc.getElementsByTagName('span'); //find all <span> elements
for (var i = 0; i < spans.length; i++) {
var span = spans[i];
var tag = span.outerHTML.slice(0,6);
if (tag === '<span>') { //if the span has no attributes
//replace it with a new textNode containing its textContent
console.log('FOUND: <span>' + span.textContent + '</span>');
span.parentNode.replaceChild(document.createTextNode(span.textContent), span);
}
}

if (doc.documentElement.innerHTML.includes('<span>')) {
//if any attribute-less tags remain, run the method again
console.log('Fix not complete, re-running...');
fixSpan(iframe);
} else {
console.log('Fix complete.');
}
}

• What is the harm in leaving the extra tags there? What is the benefit of doing the cleanup? – 200_success Jun 14 '16 at 2:12

1) You work with the serialized form of the HTML, i.e. with innerHTML and outerHTML. In some (rare) occurrences, it may be acceptable to do so, but most of the time working with the provided API is cleaner. I don't think that the serialized representation is guaranteed to be the same across user agents (attribute ordering, legacy uppercasing of HTML...) - plus what if some inline script declares some '<span>' text... like yours?

2) Re-running the method is a bit dubious, you should be able to remove all spans in a single pass. In your case, the DOM collection is live, so each time you replace a span element, the current index must be shifted back. In itself, manually updating the index of a loop is a bad practice, but Javascript has limited iteration mechanisms, so you have to live with it. It's actually not that unusual in DOM manipulation.

3) Removing the spans without attributes is well and good, but even an empty span may be styled, as part of another selector (e.g. .foo > span). Your fix may bring new problems.

4) Comments should not repeat the code, but bring enlightenment to the reader. Indicating "find all <span> elements" aside a getElementsByTagName is probably unneeded. Indicating why you shift a loop index, if you decide to do it, is necessary.

function fixSpan(doc) {
var spans = doc.getElementsByTagName("span");
for (var index = 0; index < spans.length; index++) {
var span = spans[index];
if (span.attributes.length == 0) {
span.parentNode.replaceChild(document.createTextNode(span.textContent), span);
index--; // Tree is live; removing one span requires shifting the index.
}
}
}

• Regarding your second point: use document.querySelectorAll() which returns a non-live NodeList – Andreas Jun 14 '16 at 12:21
• @Andreas Thanks for the pointer. I've never used this method, but it looks interesting. – Elegie Jun 14 '16 at 12:58