You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The Blue Core project was noticing duplicate bf:relation nodes introduced when converting MARC to BIBFRAME. I worked with Claude on diagnosing this problem, which you can find a description of below. I can send a PR with the one line fix.
A 490 carrying a $v or $x alongside its $a converts to two identical bf:relation nodes on the Work instead of one. The two bf:Relation nodes
have the same bf:relationship, the same bf:seriesEnumeration and an
equivalent bf:associatedResource; nothing distinguishes them.
This does not happen under Saxon. It happens under libxslt, so it reaches
anyone consuming the stylesheets through lxml (Python), Nokogiri (Ruby),
PHP's XSL extension, XML::LibXSLT (Perl) or plain xsltproc. The cause is a
libxslt bug, but the stylesheet is relying on the corner of XPath that libxslt
gets wrong, and a one-line change sidesteps it.
$a alone gives one relation under both. Adding either $v or $x splits it
in two under libxslt. Same for 440, and for ind1="1".
Cause
ConvSpec-Process6-Series.xsl builds a result tree fragment holding a bf:title, bf:identifiedBy and bf:seriesEnumeration per group of 490
subfields, each tagged with a groupNum, then takes the distinct group numbers
to decide how many bf:Relation nodes to emit
(ConvSpec-Process6-Series.xsl#L66):
That distinct-values idiom asks for preceding::bf:*/@groupNum. libxslt
gets the preceding:: axis wrong when the node-set came from exsl:node-set(): it omits the fragment's first top-level element.
Reduced to a standalone stylesheet, with no marc2bibframe2 involved — three
siblings in a fragment, asking each of them for preceding::*:
from b: libxslt [deep] Saxon [a deep]
from c: libxslt [deep b] Saxon [a deep b]
a is missing in both cases, whether the context node is an element or one of
its attributes. It looks like libxslt treats the fragment's first child as the
document element and excludes it as an ancestor — correct for a real document,
wrong for a fragment with several roots.
XPath 1.0 §2.2 says the preceding axis holds every node before the context
node in document order, excluding ancestors — a qualifies, so Saxon is right.
Move the same markup into a source document rather than a fragment and libxslt
agrees with Saxon, so the bug is specific to fragments.
The consequence here: the groupNum to compare against is never in the
node-set, so not(...) is always true, nothing is filtered, and every child of
the fragment gets its own bf:Relation. With $a only there is one child and
the output happens to be right. With $a $v there are two.
Suggested fix
Compare preceding-sibling::*/@groupNum from the element instead of preceding::bf:*/@groupNum from the attribute. Every groupNum in the
fragment sits on a top-level child, so the sibling axis reaches all of them,
and the axis libxslt mishandles is not used at all.
Environment
marc2bibframe2 v3.1.0 (ed9abb0)
libxslt 1.1.43 (current release, via lxml 6.1.2) and libxslt 1.1.35 (macOS
system xsltproc) — both affected
The Blue Core project was noticing duplicate bf:relation nodes introduced when converting MARC to BIBFRAME. I worked with Claude on diagnosing this problem, which you can find a description of below. I can send a PR with the one line fix.
A
490carrying a$vor$xalongside its$aconverts to two identicalbf:relationnodes on the Work instead of one. The twobf:Relationnodeshave the same
bf:relationship, the samebf:seriesEnumerationand anequivalent
bf:associatedResource; nothing distinguishes them.This does not happen under Saxon. It happens under libxslt, so it reaches
anyone consuming the stylesheets through lxml (Python), Nokogiri (Ruby),
PHP's XSL extension, XML::LibXSLT (Perl) or plain
xsltproc. The cause is alibxslt bug, but the stylesheet is relying on the corner of XPath that libxslt
gets wrong, and a one-line change sidesteps it.
Reproducing
$aalone gives one relation under both. Adding either$vor$xsplits itin two under libxslt. Same for
440, and forind1="1".Cause
ConvSpec-Process6-Series.xslbuilds a result tree fragment holding abf:title,bf:identifiedByandbf:seriesEnumerationper group of 490subfields, each tagged with a
groupNum, then takes the distinct group numbersto decide how many
bf:Relationnodes to emit(ConvSpec-Process6-Series.xsl#L66):
That distinct-values idiom asks for
preceding::bf:*/@groupNum. libxsltgets the
preceding::axis wrong when the node-set came fromexsl:node-set(): it omits the fragment's first top-level element.Reduced to a standalone stylesheet, with no marc2bibframe2 involved — three
siblings in a fragment, asking each of them for
preceding::*:ais missing in both cases, whether the context node is an element or one ofits attributes. It looks like libxslt treats the fragment's first child as the
document element and excludes it as an ancestor — correct for a real document,
wrong for a fragment with several roots.
XPath 1.0 §2.2 says the
precedingaxis holds every node before the contextnode in document order, excluding ancestors —
aqualifies, so Saxon is right.Move the same markup into a source document rather than a fragment and libxslt
agrees with Saxon, so the bug is specific to fragments.
The consequence here: the
groupNumto compare against is never in thenode-set, so
not(...)is always true, nothing is filtered, and every child ofthe fragment gets its own
bf:Relation. With$aonly there is one child andthe output happens to be right. With
$a $vthere are two.Suggested fix
Compare
preceding-sibling::*/@groupNumfrom the element instead ofpreceding::bf:*/@groupNumfrom the attribute. EverygroupNumin thefragment sits on a top-level child, so the sibling axis reaches all of them,
and the axis libxslt mishandles is not used at all.
Environment
ed9abb0)system
xsltproc) — both affectedduplicate-value check on the resulting graph rejected the record