解决XML属性转元素时XQuery报错与XSLT输出位置异常问题
问题概述
需要将XML中的id属性转换为元素,现有转换代码遇到报错、输出不符合预期的问题。
示例XML
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>
报错信息
转换two-attr.xml时遇到如下错误:
XDMP-ATTRSEQ: (err:XQTY0024) $node/@*[fn:local-name(.) = $attr] -- Attribute node cannot follow non-attribute node in element content
期望输出
<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>
修正方案
XQuery修正
原XQuery报错的核心原因是remove-attr-except函数逻辑混乱,属性处理和子节点返回的顺序、位置不符合XQuery元素构造规则,同时没有区分简单文本元素和复杂元素的id放置位置。修正后代码如下:
declare namespace hof = "http://fc.fasset/function"; declare function hof:process-element ( $node as element(), $newNs as xs:string, $keepAttr as xs:string* ) as node()* { let $keep-attrs := $node/@*[local-name(.) = $keepAttr] let $other-attrs := $node/@*[not(local-name(.) = $keepAttr)] let $has-child-elements := exists($node/*) let $transformed-children := hof:transform-ns-root-flatten($node/node(), $newNs, $keepAttr) let $current-element := element {fn:QName($newNs, local-name($node))} { (: 非保留属性直接保留,必须放在最前面 :) $other-attrs, (: 复杂元素把保留属性转成元素放在内部最前面 :) if ($has-child-elements) then ( for $attr in $keep-attrs return element {fn:QName($newNs, local-name($attr))} {data($attr)}, $transformed-children ) else ( $transformed-children ) } return ( $current-element, (: 简单文本元素把保留属性转成元素放在当前元素同级后面 :) if (not($has-child-elements)) then ( for $attr in $keep-attrs return element {fn:QName($newNs, local-name($attr))} {data($attr)} ) else () ) }; 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 hof:process-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 }; 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/*/* 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) }
XSLT修正
原XSLT没有区分元素类型,id一律放在当前元素内部,修正后按规则放置id即可:
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="xs"> <xsl:param name="ns" as="xs:string">schema://fc.fasset/execution</xsl:param> <xsl:param name="attr" static="yes" as="xs:string*" select="'href', 'id'"/> <!-- 复制节点,修改命名空间 --> <xsl:template match="*"> <xsl:element name="{local-name()}" namespace="{$ns}"> <xsl:copy-of select="@*[not(local-name() = $attr)]"/> <!-- 复杂元素:id放在内部最前面 --> <xsl:if test="exists(*)"> <xsl:apply-templates select="@*[local-name() = $attr]"/> </xsl:if> <xsl:apply-templates/> </xsl:element> <!-- 简单文本元素:id放在同级后面 --> <xsl:if test="not(exists(*))"> <xsl:apply-templates select="@*[local-name() = $attr]"/> </xsl:if> </xsl:template> <!-- 属性转元素模板 --> <xsl:template match="@*"> <xsl:element name="{local-name()}" namespace="{$ns}"> <xsl:value-of select="."/> </xsl:element> </xsl:template> <!-- 根节点处理 --> <xsl:template match="/requestConfirmation"> <xsl:element name="executionReport" namespace="{$ns}"> <xsl:apply-templates select="trade"/> </xsl:element> </xsl:template> </xsl:stylesheet>
内容的提问来源于stack exchange,提问作者Fiona Chen
相关产品推荐
相关产品推荐

