UNPKG

htmlnano

Version:

Modular HTML minifier, built on top of the PostHTML

51 lines (49 loc) 2.19 kB
// The only xmlns value that is meaningless (and thus removable) on <html> in an HTML document. const XHTML_NAMESPACE = 'http://www.w3.org/1999/xhtml'; function findAttrEntry(attrs, name) { const targetName = name.toLowerCase(); for (const [attrName, attrValue] of Object.entries(attrs)){ if (attrName.toLowerCase() === targetName) { return { name: attrName, value: attrValue }; } } return null; } function normalizeValue(value) { return typeof value === 'string' ? value.trim().toLowerCase() : null; } /** Removes XHTML/XML leftovers that are meaningless in HTML documents */ const mod = { onAttrs () { return (attrs, node)=>{ const attrsRecord = attrs; // Remove xmlns="http://www.w3.org/1999/xhtml" from <html>. // The HTML parser ignores it — <html> is always in the HTML namespace. // Scoped strictly to the <html> tag so xmlns on <svg>/<math> is left untouched. if (node.tag === 'html') { const xmlnsEntry = findAttrEntry(attrsRecord, 'xmlns'); if (xmlnsEntry && normalizeValue(xmlnsEntry.value) === XHTML_NAMESPACE) { delete attrsRecord[xmlnsEntry.name]; } } // Remove xml:lang when an identical (case-insensitive) lang attribute is present // on the same element. Without a matching lang, xml:lang is left alone since it // may be the only language signal for XML tooling. const xmlLangEntry = findAttrEntry(attrsRecord, 'xml:lang'); if (xmlLangEntry) { const langEntry = findAttrEntry(attrsRecord, 'lang'); if (langEntry) { const xmlLangValue = normalizeValue(xmlLangEntry.value); const langValue = normalizeValue(langEntry.value); if (xmlLangValue !== null && xmlLangValue === langValue) { delete attrsRecord[xmlLangEntry.name]; } } } return attrsRecord; }; } }; export { mod as default };