简体   繁体   中英

Iterate values of an attribute with XSLT

I have an XML-structure like

<Object id="id0000" link="id0010 id0020">
    <Data id="id1000">
      <DataValue name="Obj0000"/>
    </Data>
</Object>

I have several 'Object'-nodes, some of them have one link, some of them have two or more links. The links (ids) are separated by a white space. I already used the 'tokenize()'-function, to get the individual link ids

<xsl:variable name="links">
    <xsl:value-of select="tokenize(@link, ' ')"/>
</xsl:variable>

Each link refers to another 'Object' with some data.

Now I want to use the position of the link (ids) as a string, so my output should look something like

<Object>
    <hasName>Obj0000</hasName>
    <hasStartLink>id0010</hasStartLink>
</Object>

<LinkedObject>
    <hasID>id0010</hasID>
    <hasDescription>1. link of Obj0000</hasDescription>
    <hasNextLink>id0020</hasNextLink>
</LinkedObject>
<LinkedObject>
    <hasID>id0020</hasID>
    <hasDescription>2. link of Obj0000</hasDescription>
</LinkedObject>

I found many pages that said, iterating and saving the current position is something that cannot be done in this way, one has to work around somehow. I found something like

<!-- inside another template -->
    <xsl:call-template name="LinkedObj">
        <xsl:with-param name="count" select="1"/>
    </xsl:call-template>
<!-- end of another template -->

<xsl:template name="LinkedObj">
    <xsl:param name="count"/>

    <!-- do some stuff here -->
    <!-- use '$count' as position -->

    <xsl:call-template name="LinkedObj>
        <xsl:with-param name="count" select="$count + 1"/>
    </xsl:call-template>
</xsl:template>

But now I don't know at with point I could use such a template. I'd rather not call the template again in the template itself but in "another template". But inside this ("another") template I don't have the current count-variable, right? In the 'LinkedObj'-template I'm already in another context so I don't know how many times I should call another template (I have no 'links'). Currently I'm doing it with two parameters so I'm checking if my position is less than the number of attributes (links):

<xsl:template match="Object">
    <xsl:element name="Object>
        <xsl:element name="hasName">
            <xsl:value-of select="Data/DataValue/@name"/>
        </xsl:element>
        <xsl:element name="hasStartLink">
            <!-- here I'm also not sure how to get only the first separated id -->
        </xsl:element>
    </xsl:element>

    <xsl:call-template name="LinkedObj">
        <xsl:with-param name="position" select="1"/>
        <xsl:with-param name="count" select="count(tokenize(@link, ' '))"/>
    </xsl:call-template>
</xsl:template>

<xsl:template name="LinkedObj">
    <xsl:param name="position"/>
    <xsl:param name="count"/>

    <!-- do some stuff here -->
    <!-- use '$position' as position -->

    <xsl:if test="$position &lt; $count">
        <xsl:call-template name="LinkedObj>
            <xsl:with-param name="position" select="$position + 1"/>
            <xsl:with-param name="count" select="$count"/>
        </xsl:call-template>
    </xsl:if>
</xsl:template>

Any suggestions on this?

The next step would be not only to get the position and use it for a text output but to use the id of the links to call or apply new templates. As I commented in the last code block I don't know how to use the elements of a tokenized string as a node. I tried it with a for-each element but I did not work that way because there was some context-error (I have to reproduce it and post it here).

EDIT: I found a way to get it working, which differs from Tobias's idea, so I'm posting the basic code here:

<xsl:template match="Object">
    <xsl:element name="Object>
        <xsl:element name="hasName">
            <xsl:value-of select="Data/DataValue/@name"/>
        </xsl:element>
        <xsl:element name="hasStartLink">
            <!-- here I'm also not sure how to get only the first separated id -->
        </xsl:element>
    </xsl:element>

    <xsl:call-template name="LinkedObj">
        <xsl:with-param name="position" select="1"/>
        <xsl:with-param name="count" select="count(tokenize(@link, ' '))"/>
        <xsl:with-param name="links" select="tokenize(@link, ' ')"/>
    </xsl:call-template>
</xsl:template>

<xsl:template name="LinkedObj">
    <xsl:param name="position"/>
    <xsl:param name="count"/>
    <xsl:param name="links"/>

    <xsl:element name="hasID">
        <xsl:value-of select="$links[$position]"/>
    </xsl:element>

    <xsl:element name="hasDescription">
        <xsl:value-of select="concat($position, '. link of ', Data/DataValue/@name)"/>
    </xsl:element>

    <xsl:if test="$position &lt; $count">
        <xsl:element name="hasNextLink">
            <xsl:value-of select="$links[$position + 1]"/>
        </xsl:element>

        <xsl:call-template name="LinkedObj>
            <xsl:with-param name="position" select="$position + 1"/>
            <xsl:with-param name="count" select="$count"/>
            <xsl:with-param name="links" select="$links"/>
        </xsl:call-template>
    </xsl:if>
</xsl:template>

In this case it is possible to get the values of "Object" inside the "LinkedObj" template, since the template is called with a node (the "Object", as the context does not change).

Assuming you have multiple Objects I added a root element to your source file:

<?xml version="1.0" encoding="UTF-8"?>
<Objects>
<Object id="id0000" link="id0010 id0020">
    <Data id="id1000">
        <DataValue name="Obj0000"/>
    </Data>
</Object>
</Objects>

You don't have to use a recursive call off the LinkedObj template, you can just iterate over the links and call in the loop:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="xs" version="2.0">

<xsl:output indent="yes"/>

<xsl:template match="Objects">
    <Objects>
        <xsl:apply-templates select="Object"/>
    </Objects>
</xsl:template>

<xsl:template match="Object">
    <xsl:variable name="name" select="Data/DataValue/@name"/>
    <xsl:variable name="links" select="tokenize(@link, ' ')"/>
    <xsl:element name="Object">
        <xsl:element name="hasName">
            <xsl:value-of select="$name"/>
        </xsl:element>
        <xsl:element name="hasStartLink">
            <xsl:value-of select="$links[1]"/>
        </xsl:element>
    </xsl:element>

    <xsl:for-each select="$links">
        <xsl:variable name="position" select="position()"/>
        <xsl:call-template name="LinkedObj">
            <xsl:with-param name="position" select="$position"/>
            <xsl:with-param name="id" select="."/>
            <xsl:with-param name="nextLink" select="$links[$position + 1]"/>
            <xsl:with-param name="objectName" select="$name"/>
        </xsl:call-template>
    </xsl:for-each>

</xsl:template>

<xsl:template name="LinkedObj">
    <xsl:param name="position"/>
    <xsl:param name="id"/>
    <xsl:param name="nextLink"/>
    <xsl:param name="objectName"/>
    <LinkedObject>
        <hasID>
            <xsl:value-of select="$id"/>
        </hasID>
        <hasDescription>
            <xsl:value-of select="position()"/>
            <xsl:text>. link of </xsl:text>
            <xsl:value-of select="$objectName"/>
        </hasDescription>
        <xsl:if test="$nextLink!=''">
            <hasNextLink>
                <xsl:value-of select="$nextLink"/>
            </hasNextLink>
        </xsl:if>
    </LinkedObject>
</xsl:template>

</xsl:stylesheet>

Result:

<?xml version="1.0" encoding="UTF-8"?>
<Objects>
   <Object>
      <hasName>Obj0000</hasName>
      <hasStartLink>id0010</hasStartLink>
   </Object>
   <LinkedObject>
      <hasID>id0010</hasID>
      <hasDescription>1. link of Obj0000</hasDescription>
      <hasNextLink>id0020</hasNextLink>
   </LinkedObject>
   <LinkedObject>
      <hasID>id0020</hasID>
      <hasDescription>2. link of Obj0000</hasDescription>
   </LinkedObject>
</Objects>

Allthough this was the required output structure, not sure if this is correct, I'm guessing the LinkedObjects should be within each Object, however that change is trivial...

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM