“属性节点不能跟随元素内容中的非属性节点”告诉我什么

What “Attribute node cannot follow non-attribute node in element content” tells me

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 like two-attr.xml out of 70K documents. Apparently, the currency element already has value USD. I got below error in the ML QConsole when transforming two-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>

问题来了:它看起来不像一个有效的 XML。因为虽然我可以获得 currency 文本值

doc("/product/eqd/a7c1db2d.xml")//prod:trade//prod:amount/prod:currency/text()

,我希望以下结果有助于搜索引擎:

<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?

您不能在开始创建子节点后创建属性。因此,如果您要将 @id 转换为 <id>,那么您必须在复制其他属性后执行此操作。

避免该问题的最短且最简单的方法是对属性进行排序,确保先处理将向前复制的属性,然后再处理将转换为元素的属性。

您可以通过对从 hof:remove-attr-except() 函数返回的项目序列进行排序来实现这一点,确保该序列具有属性,然后是元素:

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 }

您也可以只使用两个带有 where 子句的单独 FLWOR,该子句处理 $keepAttr,然后那些将被转换为元素的元素:

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)}    
  };

但是如果你希望那些新元素在原始元素之外,并且你不想保留属性那么我会改变你typeswitch中元素的处理,这样你调用将这些属性转换为元素构造函数之外的元素的函数:

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 
  };

上面的代码根据提供的输入生成以下输出 XML:

<executionReport xmlns="schema://fc.fasset/execution">
  <amount>
    <currency>USD</currency>
    <id>settlementCurrency</id>
    <referenceAmount>StandardISDA</referenceAmount>
    <cashSettlement>true</cashSettlement>
  </amount>
</executionReport>