XHTML Output

RichTextEditor saves HTML— there is no XHTML mode built in. When a publishing pipeline needs stricter markup you convert on the way out, and the browser does almost all of it: parse as HTML, serialise as XML. This page runs that conversion live and re-parses the result as XML to prove it is well-formed.

Edit here and watch the XHTML below update.

Void elements are the interesting part — a line break,
an image Sample, and an unquoted-attribute link.

  • List item

XHTML produced from the saved HTML

Loading…

The converter

function htmlToXHTML(html) {
    // Parse as HTML (forgiving), serialise as XML (strict). The browser does
    // the hard part: void elements come back self-closed, attribute values are
    // quoted, and tag names are lower-cased.
    const doc = new DOMParser().parseFromString(html, "text/html");
    const xml = new XMLSerializer().serializeToString(doc.body);

    return xml
        // Drop the wrapper the serialiser adds around body.
        .replace(/^<body[^>]*>/, "").replace(/<\/body>$/, "")
        // XHTML wants a space before the self-closing slash.
        .replace(/([^ ])\/>/g, "$1 />")
        .trim();
}

// XHTML only accepts five named entities, so anything else must be numeric.
function toNumericEntities(s) {
    return s.replace(/&(?!(?:amp|lt|gt|quot|apos|#\d+|#x[0-9a-f]+);)([a-z]+);/gi,
        (m, name) => {
            const el = document.createElement("textarea");
            el.innerHTML = m;
            const ch = el.value;
            return ch.length === 1 ? "&#" + ch.charCodeAt(0) + ";" : m;
        });
}

const xhtml = toNumericEntities(htmlToXHTML(editor.getHTMLCode()));

Two details the serialiser will not do for you: XHTML recognises only five named entities, so everything else has to become numeric, and if you are emitting a full document you still need the XML declaration and an xmlns on the root element.

Getting HTML content from the editor