diff --git a/apps/builder/app/builder/features/settings-panel/controls/code.tsx b/apps/builder/app/builder/features/settings-panel/controls/code.tsx index 61bbc9e58ffb..9c46f490783d 100644 --- a/apps/builder/app/builder/features/settings-panel/controls/code.tsx +++ b/apps/builder/app/builder/features/settings-panel/controls/code.tsx @@ -67,30 +67,58 @@ const ErrorInfo = ({ type Error = { message: string; value: string; expected: string }; +/** + * Use DOMParser in xml mode to parse potential svg + */ +const parseSvg = (value: string) => { + const doc = new DOMParser().parseFromString(value, "application/xml"); + const errorNode = doc.querySelector("parsererror"); + if (errorNode) { + return ""; + } + return doc.documentElement.outerHTML; +}; + +const parseHtml = (value: string) => { + const div = document.createElement("div"); + div.innerHTML = value; + return div.innerHTML; +}; + // The problem is to identify broken HTML and because browser is flexible and always tries to fix it we never // know if something is actually broken. -// 1. Parse the HTML using DOM -// 2. Get HTML via innerHTML -// 3. Compare the original HTML with innerHTML -// 4. We try to minimize the amount of false positives by removing +// 1. Parse potential SVG with XML parser and serialize +// 2. Compare the original SVG with resulting value +// 3. Parse the HTML using DOM parser and serialize +// 4. Compare the original HTML with resulting value +// 5. We try to minimize the amount of false positives by removing // - different amount of whitespace // - unifying `boolean=""` is the same as `boolean` +// - xmlns attirbute which is always reordered first const validateHtml = (value: string): Error | undefined => { - const div = document.createElement("div"); - div.innerHTML = value; - const expected = div.innerHTML; const clean = (value: string) => { return ( value // Compare without whitespace to avoid false positives - .replace(/\s/g, "") + .replaceAll(/\s/g, "") // normalize boolean attributes by turning `boolean=""` into `boolean` - .replace('=""', "") + .replaceAll('=""', "") + // namespace attribute is always reordered first + .replaceAll('xmlns="http://www.w3.org/2000/svg"', "") ); }; - if (clean(value) !== clean(expected)) { - return { message: "Invalid HTML detected", value, expected }; + // in many cases svg is valid xml so serialize in xml mode first + // to avoid false positive of auto closing svg tags, for example + // -> + const xml = parseSvg(value); + if (clean(xml) === clean(value)) { + return; + } + const html = parseHtml(value); + if (clean(html) === clean(value)) { + return; } + return { message: "Invalid HTML detected", value, expected: html ?? "" }; }; export const CodeControl = ({