0

When I include a non-breaking space (character encoded as \u00A0) in an SVG text element, it works fine on a HTML page, but the resulting SVG contains an   character and is therefore not valid as an SVG file. For example, if I save the SVG to a file (using a function like this) and open that file in Chrome, it reports an error "Entity 'nbsp' not defined" and does not display the text.

const NS = { SVG: "http://www.w3.org/2000/svg" };
const svg = document.createElementNS(NS.SVG, 'svg');
const text = document.createElementNS(NS.SVG, "text");
text.setAttribute("y", 20);
const s = "Non-breaking> <space"; // the space here is a non-breaking one
text.appendChild(document.createTextNode(s));
svg.appendChild(text);
document.body.appendChild(svg);
console.log(svg.outerHTML);

I can easily replace the problematic character with s.replace("\u00A0", " "). But is there a more general way of sanitizing text going into an SVG to ensure it is valid and replacing the non-valid characters?

Stuart
  • 9,597
  • 1
  • 21
  • 30
  • serve the HTML page as application/xhtml+xml rather than text/html – Robert Longson Jun 24 '21 at 20:47
  • The &nbsp (non-breaking space) is used to create a space in a line that cannot be broken by word wrap. In SVG is not posible to breake the line. You can safely use a space here – enxaneta Jun 24 '21 at 20:48
  • or migrate/clone the contents you want to serialize (the SVG) so it's owned by an XMLDocument rather than a HTMLDocument. – Robert Longson Jun 24 '21 at 21:12
  • @RobertLongson Thanks, yes I think these suggestions would work. I initially didn't understand that generating the SVG in an HTML document was causing the unwanted character replacement and was trying to clean the individual text nodes. – Stuart Jun 24 '21 at 21:44

1 Answers1

1

Since svg recognizes only 5 html entities: &amp;, &quot;, &apos;, &lt;, and &gt;, we can filter out the rest:

const NS = { SVG: "http://www.w3.org/2000/svg" };
const svg = document.createElementNS(NS.SVG, 'svg');
const text = document.createElementNS(NS.SVG, "text");
text.setAttribute("y", 20);
const s = "Non-breaking> <space & other\r\u0009characters"; // the space here is a non-breaking one

text.appendChild(document.createTextNode(s));
svg.appendChild(text);
document.body.appendChild(svg);
console.log(svg.outerHTML);

saveSvg(svg, "test")

function saveSvg(svgEl, name) {
    svgEl.setAttribute("xmlns", "http://www.w3.org/2000/svg");
    const dummy = document.createElement("div");
    /* find all html entities and replace them with real value */
    var svgData = svgEl.outerHTML.replace(/(&(?!(amp|gt|lt|quot|apos))[^;]+;)/g, a =>
    {
      dummy.innerHTML = a;
      return dummy.textContent;
    });
    var preface = '<?xml version="1.0" standalone="no"?>\r\n';
    var svgBlob = new Blob([preface, svgData], {type:"image/svg+xml;charset=utf-8"});
    var svgUrl = URL.createObjectURL(svgBlob);
    var downloadLink = document.createElement("a");
    downloadLink.href = svgUrl;
    downloadLink.download = name;
    downloadLink.textContent = "download";
    document.body.appendChild(downloadLink);
    downloadLink.click();
//    document.body.removeChild(downloadLink);
}
vanowm
  • 9,466
  • 2
  • 21
  • 37