How to iterate multiple cross-references?

2019-08-02 03:40发布

My XML source specifies cross-references in linkGrp. Based on those cross-references specified by the @target attribute, I need to group the values of the s elements. If the target attribute contains one-to-one reference, the code I have works well as follows:

<DIV>
    <div id="e">
        <s id="e1">AAAAA</s>
        <s id="e2">BBBBB</s>
        <s id="e3">CCCCC</s>
    </div>
    <div id="fr">
        <s id="fr1">DDDDD</s>
        <s id="fr2">EEEEE</s>
        <s id="fr3">FFFFF</s>
    </div>
    <linkGrp type="alignment" domains="e fr">
        <link target="#e1 #fr1"/>
        <link target="#e2 #fr3"/>
        <link target="#e3 #fr2"/>
    </linkGrp>
</DIV>

The requested HTML output I get is this:

*AAAAA
DDDDD

*BBBBB
FFFFF

*CCCCC
EEEEE

To get this result I use the following XSLT code:

 <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:key name="linkId" match="s" use="@id"/>
    <xsl:template match="/DIV">
        <html>
            <head>
                <title>Test</title>
            </head>
            <body>
                <xsl:apply-templates/>
            </body>
        </html>
    </xsl:template>
    <xsl:template match="link">
        <ul>
            <li>
                <xsl:value-of select="key('linkId',substring-before(substring-after(@target, '#'), ' '))"/>
                <br/>
                <xsl:value-of select="key('linkId',substring-after(@target, ' #'))"/>
            </li>
        </ul>
    </xsl:template>
    <xsl:template match="div"/>
    <xsl:template match="s"/>
    <xsl:template match="*">
        <xsl:message terminate="no">
            WARNING: Unmatched element: <xsl:value-of select="name()"/>
        </xsl:message>
        <xsl:apply-templates/>
    </xsl:template>
</xsl:stylesheet>

However, my XSLT code does not work that well when I add more cross-references for one @target in the link elements as this:

<DIV>
    <div id="e">
        <s id="e1">AAAAA</s>
        <s id="e2">BBBBB</s>
        <s id="e3">CCCCC</s>
    </div>
    <div id="fr">
        <s id="fr1">DDDDD</s>
        <s id="fr2">EEEEE</s>
        <s id="fr3">FFFFF</s>
    </div>
    <linkGrp type="alignment" domains="e fr">
        <link target="#e1 #fr1"/>
        <link target="#e2 #fr3 #fr1 #fr2"/>
        <link target="#e3 #fr2 #fr3"/>
    </linkGrp>
</DIV>

With multiple cross-references in the XML source, my XSLT code does not fetch any match. And I need my code to retrieve all matches specified in the @target attribute. Can anyone point the mistake in my XSLT code or offer alternative solution?

标签: xml xslt
1条回答
The star\"
2楼-- · 2019-08-02 03:54

With xslt-1.0 you may use a recursive template call to split up the ids. Try something like this:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:key name="linkId" match="s" use="@id"/>
    <xsl:template match="/DIV">
        <html>
            <head>
                <title>Test</title>
            </head>
            <body>
                <xsl:apply-templates/>
            </body>
        </html>
    </xsl:template>
    <xsl:template match="link">
        <ul>
            <li>
                <xsl:call-template name="findtarget">
                    <xsl:with-param name="targets" select="@target" />
                </xsl:call-template>
            </li>
        </ul>
    </xsl:template>


    <xsl:template name="findtarget">
        <xsl:param name="targets" />
        <xsl:choose>
            <xsl:when test="contains($targets, ' ')">
                <xsl:call-template name="findtarget">
                    <xsl:with-param name="targets" select="substring-before($targets,' ')" />
                </xsl:call-template>
                <br/>
                <xsl:call-template name="findtarget">
                    <xsl:with-param name="targets" select="substring-after($targets,' ')" />
                </xsl:call-template>
            </xsl:when>
            <xsl:otherwise>
                <xsl:value-of select="key('linkId',substring-after($targets, '#'))"/>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>

    <xsl:template match="div"/>
    <xsl:template match="s"/>
    <xsl:template match="*">
        <xsl:message terminate="no">
            WARNING: Unmatched element: <xsl:value-of select="name()"/>
        </xsl:message>
        <xsl:apply-templates/>
    </xsl:template>
</xsl:stylesheet>

Which will generate the following output:

                     WARNING: Unmatched element: linkGrp
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Test</title>
</head>
<body>



                <ul><li>AAAAA<br>DDDDD</li></ul>
                <ul><li>BBBBB<br>FFFFF<br>DDDDD<br>EEEEE</li></ul>
                <ul><li>CCCCC<br>EEEEE<br>FFFFF</li></ul>

</body>
</html>

An alternative solution may be to use the xpath function id(). But to make it work your XML need to have a DTD DOCTYPE. Something like this:

<!DOCTYPE DIV  [
<!ATTLIST s
  id     ID    #REQUIRED
>
]>
<DIV>
    <div id="e">
        <s id="e1">AAAAA</s>
        <s id="e2">BBBBB</s>
        <s id="e3">CCCCC</s>
    </div>
    <div id="fr">
        <s id="fr1">DDDDD</s>
        <s id="fr2">EEEEE</s>
        <s id="fr3">FFFFF</s>
    </div>
    <linkGrp type="alignment" domains="e fr">
        <link target="#e1 #fr1"/>
        <link target="#e2 #fr3 #fr1 #fr2"/>
        <link target="#e3 #fr2 #fr3"/>
    </linkGrp>
</DIV>

With this XML your link template could be change as following:

<xsl:template match="link">
    <ul>
        <li>
            <xsl:for-each select="id(translate(@target,'#', ''))">
                <xsl:if test="position() > 1">
                    <br/>
                </xsl:if>
                <xsl:value-of select="."/>
            </xsl:for-each>
        </li>
    </ul>
</xsl:template>
查看更多
登录 后发表回答