one-attr.xml
<requestConfirmation xmlns="http://example/confirmation">
<trade>
<amount>
<currency id="settlementCurrency">USD</currency>
<referenceAmount>StandardISDA</referenceAmount>
<cashSettlement>true</cashSettlement>
</amount>
</trade>
</requestConfirmation>
two-attr.xml
<requestConfirmation xmlns="http://example/confirmation">
<trade>
<cal>
<c>PRECEDING</c>
<bcs id="businessCenters">
<bc>USNY</bc>
<bc>GBLO</bc>
</bcs>
</cal>
<amount>
<currency id="settlementCurrency" currencyScheme="http://example/iso4">USD</currency>
<referenceAmount>StandardISDA</referenceAmount>
<cashSettlement>true</cashSettlement>
</amount>
</trade>
</requestConfirmation>
I use XQuery to transform the
id
attribute into element. There are only two documents liketwo-attr.xml
out of 70K documents. Apparently, thecurrency
element already has valueUSD
. I got below error in the ML QConsole when transformingtwo-attr.xml
. I got very similar error in Oxygen.
XDMP-ATTRSEQ: (err:XQTY0024) $node/@*[fn:local-name(.) = $attr] -- Attribute node cannot follow non-attribute node in element content
My XQuery module:
declare namespace hof = "http://fc.fasset/function";
declare function hof:remove-attr-except
( $node as node()* ,
$newNs as xs:string ,
$keepAttr as xs:string*
) as node()*
{
for $attr in $node/@*
return
if (local-name($attr) = $keepAttr)
then (element {fn:QName ($newNs, name($attr))} {data($attr)})
else
$node/@*[name() = $keepAttr], hof:transform-ns-root-flatten($node/node(), $newNs, $keepAttr)
};
declare function hof:transform-ns-root-flatten
( $nodes as node()* ,
$newNs as xs:string ,
$keepAttr as xs:string*
) as node()*
{
for $node in $nodes
return
typeswitch($node)
case $node as element()
return (element { fn:QName ($newNs, local-name($node)) }
{ hof:remove-attr-except($node, $newNs, $keepAttr) }
)
case $node as document-node()
return hof:transform-ns-root-flatten($node/node(), $newNs, fn:normalize-space($keepAttr))
default return $node
};
(: let $inXML := doc("/fasset/bug/two-attr.xml") :)
let $inXML :=
let $inXML :=
<requestConfirmation xmlns="http://example/confirmation">
<trade>
<cal>
<c>PRECEDING</c>
<bcs id="businessCenters">
<bc>USNY</bc>
<bc>GBLO</bc>
</bcs>
</cal>
<amount>
<currency id="settlementCurrency" currencyScheme="http://example/iso4">USD</currency>
<referenceAmount>StandardISDA</referenceAmount>
<cashSettlement>true</cashSettlement>
</amount>
</trade>
</requestConfirmation>
let $input := $inXML/*[name() = name($inXML/*)]/*
let $ns := "schema://fc.fasset/execution"
let $root := "executionReport"
let $keep := "id"
return
element { fn:QName ($ns, $root) }
{ hof:transform-ns-root-flatten($input, $ns, $keep) }
Then I switch XSLT to transform
two-attr.xml
. Surprisingly, the XSLT transform is a success.
<xsl:param name="ns" as="xs:string">schema://fc.fasset/product</xsl:param>
<xsl:param name="attr" static="yes" as="xs:string*" select="'href', 'id'"/>
=================================
<xsl:template match="@*">
<xsl:choose>
<xsl:when test="local-name() = $attr">
<xsl:element name="{local-name()}" namespace="{$ns}">
<xsl:value-of select="."/>
</xsl:element>
</xsl:when>
</xsl:choose>
</xsl:template>
The collective successful underlying transform is against the
one-attr.xml
model. Java|ML API, Oxygen, XSLT returns the same result:
<amount>
<currency>
<id>settlementCurrency</id>USD</currency>
<referenceAmount>StandardISDA</referenceAmount>
<cashSettlement>true</cashSettlement>
</amount>
Now here is the rub: it doesn’t look like a valid XML. For although I can get the currency
text value
doc("/product/eqd/a7c1db2d.xml")//prod:trade//prod:amount/prod:currency/text()
, I expect below result to facilitate the search engine:
<executionReport xmlns="schema://fc.fasset/execution">
<trade>
<cal>
<c>PRECEDING</c>
<bcs>
<id>businessCenters</id>
<bc>USNY</bc>
<bc>GBLO</bc>
</bcs>
</cal>
<amount>
<currency>USD</currency>
<id>settlementCurrency</id>
<referenceAmount>StandardISDA</referenceAmount>
<cashSettlement>true</cashSettlement>
</amount>
</trade>
</executionReport>
Among the following solutions, the latest result is as below:
<executionReport xmlns="schema://fc.fasset/execution">
<cal>
<c>PRECEDING</c>
<bcs>
<bc>USNY</bc>
<bc>GBLO</bc>
</bcs>
<!-- Line9: id is out of <bcs> element and its context is completed lost! -->
<id>businessCenters</id>
</cal>
<amount>
<currency>USD</currency>
<!-- Line14: id is in the correct position! -->
<id>settlementCurrency</id>
<referenceAmount>StandardISDA</referenceAmount>
<cashSettlement>true</cashSettlement>
</amount>
</executionReport>
How can I get my XQuery and XSLT module work?
You can't create attributes after you have started creating child nodes. So, if you are transforming the @id
into <id>
then you have to do that AFTER you have copied the other attributes.
The shortest and easiest way to avoid the problem is to sort the attributes, ensuring that the ones that will be copied forward are processed first, then the ones that will be converted to elements.
You could achieve that by sorting the sequence of items returned from the hof:remove-attr-except()
function, ensuring that the sequence has attributes and then the elements:
element { fn:QName ($newNs, local-name($node)) }
{ for $item in (hof:remove-attr-except($node, $newNs, $keepAttr))
order by $item instance of attribute() descending
return $item }
You could also just have two separate FLWOR with a where
clause that processes the $keepAttr
and then those that will be converted into elements:
declare function hof:remove-attr-except
( $node as node()* ,
$newNs as xs:string ,
$keepAttr as xs:string*
) as node()*
{
for $attr in $node/@*
where not(local-name($attr) = $keepAttr)
return
$node/@*[name() = $keepAttr], hof:transform-ns-root-flatten($node/node(), $newNs, $keepAttr)
,
for $attr in $node/@*
where local-name($attr) = $keepAttr
return
element {fn:QName ($newNs, name($attr))} {data($attr)}
};
But if you want those new elements to be outside of the original element, and you don't want to retain the attributes then I would change the processing of the element in your typeswitch
, so that you call the function that converts those attributes into elements outside of the element constructor:
declare namespace hof = "http://fc.fasset/function";
declare function hof:attr-to-element
( $node as node()* ,
$newNs as xs:string ,
$keepAttr as xs:string*
) as node()*
{
for $attr in $node/@*
where local-name($attr) = $keepAttr
return
element {fn:QName ($newNs, name($attr))} {data($attr)}
};
declare function hof:transform-ns-root-flatten
( $nodes as node()* ,
$newNs as xs:string ,
$keepAttr as xs:string*
) as node()*
{
for $node in $nodes
return
typeswitch($node)
case $node as element()
return (element { fn:QName ($newNs, local-name($node)) }
{ hof:transform-ns-root-flatten($node/node(), $newNs, $keepAttr) }
,
hof:attr-to-element($node, $newNs, $keepAttr)
)
case $node as document-node()
return hof:transform-ns-root-flatten($node/node(), $newNs, fn:normalize-space($keepAttr))
default return $node
};
The code above produces the following output from the provided input XML:
<executionReport xmlns="schema://fc.fasset/execution">
<amount>
<currency>USD</currency>
<id>settlementCurrency</id>
<referenceAmount>StandardISDA</referenceAmount>
<cashSettlement>true</cashSettlement>
</amount>
</executionReport>