XSLT 1.0: extract sequential infromation from external document

南笙酒味 提交于 2020-01-17 04:23:08

问题


I am trying to use xslt to perform an xml to xml transform, such that each element is given a GUID, pulled in sequential order from an external xml document.

Source xml:

<?xml version="1.0"?>
<dataSets>
  <data name="foo"/>
  <data name="bar"/>
  ...
</dataSets>

List of IDs:

<?xml version="1.0"?>
<ids>
  <id>some-GUID</id>
  <id>another-GUID</id>
  ...
</ids>

Desired output:

<?xml version="1.0"?>
<dataSets>
  <data name="foo" id="some-GUID"/>
  <data name="bar" id="another-GUID"/>
  ...
</dataSets>

But I'm just getting the same, first ID each time:

<?xml version="1.0"?>
<dataSets>
  <data name="foo" id="some-GUID"/>
  <data name="bar" id="some-GUID"/>
  ...
</dataSets>

So far this is the xsl I've got:

<?xml version="1.0"?>
<xsl:transform version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

  <xsl:template match="/|node()|@*">
    <xsl:copy>
      <xsl:apply-templates select="node()|@*"/>
    </xsl:copy>
  </xsl:template>

  <xsl:template match="dataSets">
    <xsl:for-each select="data">
      <xsl:variable name="c">
        <xsl:number value="count(preceding-sibling::*|self::*)"/>
      </xsl:variable>

      <xsl:copy>
        <xsl:attribute name="id">
          <xsl:value-of select="document('idList.xml')/ids/id[$c]"/>
        </xsl:attribute>
        <xsl:apply-templates select="node()|@*"/>
      </xsl:copy>

    </xsl:for-each>
  </xsl:template>

</xsl:transform>

I've tried adding <xsl:attribute name="num"><xsl:value-of select="$c"/></xsl:attribute> to the xsl to see what the variable is each time it iterates and it starts at 1 and increments each time through the for-each just as I'd expect so I've no idea why it's not working.
Any help would be appreciated.


回答1:


How about:

XSLT 1.0

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>

<!-- identity transform -->
<xsl:template match="@*|node()">
    <xsl:copy>
        <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
</xsl:template>

<xsl:template match="data">
    <xsl:copy>
        <xsl:copy-of select="@*"/>
        <xsl:variable name="i" select="count(preceding::data) + 1" />
        <xsl:attribute name="id">
            <xsl:value-of select="document('idList.xml')/ids/id[$i]"/>
        </xsl:attribute>
    </xsl:copy>
</xsl:template>

</xsl:stylesheet>

Note:

  1. If all data elements are siblings under the same dataSets parent, you can expedite this by counting only the preceding siblings:

    <xsl:variable name="i" select="count(preceding-sibling::data) + 1" />
    
  2. There are probably better ways to assign GUIDs to nodes.


Addditional explanation:

The reason why your attempt does not work is a combination of two factors:

First, the way you define your variable:

<xsl:variable name="c">
   <xsl:number value="count(preceding-sibling::*|self::*)"/>
</xsl:variable>

results in the variable being a result tree fragment. The variable contains a single root node, and the string generated by xsl:number is a child of that node.

Next, you try to use the variable as a numeric predicate in the expression:

<xsl:value-of select="document('idList.xml')/ids/id[$c]"/>

However, since the variable is not a number, it is evaluated as a boolean - and being non-empty it returns true, causing all id nodes to pass the test (and, of course, in XSLT 1.0 xsl:value will only return the value of the first one of these).

How to fix it:

There are three ways you can fix this problem:

  1. Do not use the variable as a numeric predicate. Instead, put it in an expression that compares it to the position explicitly (as suggested in the answer by @potame):

    <xsl:value-of select="document('idList.xml')/ids/id[position()=$c]"/>
    
  2. Convert it to number before using it as a predicate:

    <xsl:value-of select="document('idList.xml')/ids/id[number($c)]"/>
    
  3. Eliminate the problem right from the start by defining the variable using the select attribute, which will cause the variable to become a number to begin with:

    <xsl:variable name="c" select="count(preceding-sibling::*|self::*)" />
    

See also:
http://www.w3.org/TR/xslt/#variable-values
http://www.w3.org/TR/xpath/#predicates




回答2:


You just need to slighly change the instruction to retrieve element at the desired index, by the use of position():

 <xsl:value-of select="document('idList.xml')/ids/id[position() = $c]"/>

I suggest you to use a variable to avoid parsing the document several times:

<xsl:template match="dataSets">
    <xsl:variable name="idList" select="document('idList.xml')/ids"/>

    <xsl:for-each select="data">
        <xsl:variable name="c">
            <xsl:number value="count(preceding-sibling::*|self::*)"/>
        </xsl:variable>

        <xsl:copy>
            <xsl:attribute name="id">
                <xsl:value-of select="$idList/id[position() = $c]"/>
            </xsl:attribute>

            <xsl:apply-templates select="node()|@*"/>
            <xsl:value-of select="$c" />
        </xsl:copy>

    </xsl:for-each>
</xsl:template>


来源:https://stackoverflow.com/questions/29823166/xslt-1-0-extract-sequential-infromation-from-external-document

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!