Forum Bugs

Get "svg: use: @xlink:href required" when <use> element is configured via JavaScript or rendered from React

natevw
I'm running into an issue where Prince 13.1 fails to recognize the "xlink:href" attribute I set on a number of SVG <use /> elements from JavaScript. For example the following code properly adds a reference to another node so that it is rendered by browsers:

useEl.setAttributeNS("http://www.w3.org/1999/xlink", 'href', "#testing123");


But in Prince I instead get a warning and the element is not rendered:

prince: prince_svg_js_namespace.html: warning: svg: use: @xlink:href required.


A complete reproduction is available at https://gist.github.com/natevw/59962c3499a72e9c3ced42150e36b21e — it should render a green square next to a red one, but Prince's output includes only the red square and logs "use: @xlink:href required" for the other.

Note that this can be reproduced with direct JavaScript DOM manipulation but is actually being encountered within a large React codebase where the SVG attribute in question can not simply be moved to any inline HTML.

Is there any JavaScript-based workaround we can use? I already tried both of these [otherwise incorrect solutions] on a lark, but both leave me with the same error:

// attempt #1
useEl.setAttribute('xlink:href', "#testing123");

// attempt #2
useEl.setAttribute('@xlink:href', "#testing123");


(The latter attempt throws a DOMException in Chrome but not under Prince.)
  1. prince_svg_js_namespace.html0.6 kB
    copy of current gist content

Edited by natevw

mikeday
Prince doesn't support "let", but if you change it to "var" and run with --javascript then it works.
natevw
Hmm, indeed! Slightly premature report then.

I guess I will need to look more deeply into what React itself is doing since my actual problem originated there (and that JavaScript is indeed being run ;-) and persists there despite me not having found a simplified repro for it in this case yet.

Hopefully worst case I will be able to use my (actually working) direct JavaScript attribute handling "example" to fix up whatever React is not setting to Prince's satisfacftion.
natevw
Okay, I've updated my gist with the `var` thing and to more accurately reflect what I think React is doing. In short, Prince is not happy when the attribute is set as

useEl.setAttributeNS("http://www.w3.org/1999/xlink", 'xlink:href', "#testing123");


Having not used `setAttributeNS` in a while, I was initially surprised that this wasn't an error on React's part since the "xlink:" would seem redundant given the full namespace is already provided. But the spec https://dom.spec.whatwg.org/#dom-element-setattributens does define this as taking a "qualifiedName" and MDN notes that "setAttributeNS is the only method for namespaced attributes which expects the fully qualified name, i.e. 'namespace:localname'."

So the resulting attribute node React (or the updated raw JavaScript) creates looks like this:

{
  namespaceURI: "http://www.w3.org/1999/xlink",
  prefix: "xlink",
  localName: "href",
  name: "xlink:href"
}




I confess that it's a bit strange to me that the "validate and extract" algorithm (https://dom.spec.whatwg.org/#validate-and-extract) is picky about the xml/xmlns prefixes but otherwise doesn't care, and I couldn't explain what the semantics of passing any other arbitrary/mismatched prefix to the setAttributeNS would be but that's something of a tangent.

The situation here would seem to be that:

1. React — properly afaict — converts an incoming `xlinkHref` property into an attribute using a qualified name. I.e. it not only provides the "http://www.w3.org/1999/xlink" but also includes the conventional "xlink:" prefix in the attribute name.
2. Prince — improperly afaict — does not find these attributes due to the presence of the qualifying prefix in the attribute name

In terms of the Attr (https://dom.spec.whatwg.org/#interface-attr) interface, Prince seems to be looking for a particular (namespace, name) combo rather than a particular (namespace, localName) combo as would seem more correct.

mikeday
Thanks for the detailed breakdown! We will investigate this issue.
natevw
You're welcome. I developed the following workaround for now:


function workaroundPrinceSvgNamespacing(rootEl) {
    var sel = rootEl.querySelectorAll('svg use');
    Array.prototype.forEach.call(sel, function (el) {
      var val = el.getAttributeNS("http://www.w3.org/1999/xlink", 'xlink:href');
      // NOTE: browsers return `null` when passed 'xlink:href' instead of 'href' above
      if (val) el.setAttributeNS("http://www.w3.org/1999/xlink", 'href', val);
    });
}


This reveals another discrepancy with Prince namespace handling, in that in other engines there is an asymmetry required between the two calls:

var val0 = "#testing123";
el.setAttributeNS("http://www.w3.org/1999/xlink", 'xlink:href', val);

var valX = el.getAttributeNS("http://www.w3.org/1999/xlink", 'xlink:href'),
    val1 = el.getAttributeNS("http://www.w3.org/1999/xlink", 'href', val);
assert(valX === null);
assert(val1 === val0);


Whereas Prince the expected value is found via `valX` rather than `val1`, i.e. its `getAttributeNS` is also using the `name` rather than the `localName` as it should.

Edited by natevw

mikeday
We have fixed these issues in Prince 13.2, available now, thanks again for letting us know! :D