2

I have a document that I need to transform such that most elements are copied as-is, with some exceptions: for certain specified nodes, child elements need to be appended, and some of these child elements need to reference back to specific elements in the source document. A separate "model/crosswalk" xml file contains the elements to add. The elements in the crosswalk that need to refer back to the source document have "source" attributes that point them to specific elements. Of course, my actual source docs (& the actual crosswalk) are more complex and varied than these examples.

Here is a source document example:

<?xml version="1.0" encoding="UTF-8"?>
<root>
    <album>
        <artist>Frank Sinatra</artist>
        <title>Greatest Hits</title>
    </album>
    <album>
        <artist>Miles Davis</artist>
        <title>Kind Of Blue</title>
    </album>
    <movie>
        <title>ET</title>
        <director>Steven Spielberg</director>
    </movie>
    <movie>
        <title>Blues Brothers</title>
        <director>John Landis</director>
    </movie>
</root>

Here is the "crosswalk" (crswlk.xml):

<?xml version="1.0" encoding="UTF-8"?>

<root>
    <album>
        <artist-info>
            <artist2 source="artist"/>
        </artist-info>
    </album>
    <movie>
        <director-info>
            <director2 source="director"/>
        </director-info>
    </movie>
</root>

And here is the desired output:

<?xml version="1.0" encoding="UTF-8"?>
<root>
    <album>
        <artist>Frank Sinatra</artist>
        <title>Greatest Hits</title>
        <artist-info>
            <artist2>Frank Sinatra</artist2>
        </artist-info>
    </album>
    <album>
        <artist>Miles Davis</artist>
        <title>Kind Of Blue</title>
        <artist-info>
            <artist2>Miles Davis</artist2>
        </artist-info>
    </album>
    <movie>
        <title>ET</title>
        <director>Steven Spielberg</director>
        <director-info>
            <director2>Steven Spielberg</director2>
        </director-info>
    </movie>
    <movie>
        <title>Blues Brothers</title>
        <director>John Landis</director>
        <director-info>
            <director2>John Landis</director2>
        </director-info>
    </movie>
</root>

Here's my xslt:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.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="*"/>

    <xsl:variable name="crosswalk" select="document('crswlk.xml')"/>

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

    <xsl:template match="*/album|movie">
        <xsl:variable name="theNode" select="."/>
        <xsl:variable name="nodeName" select="name()"/>
        <xsl:element name="{$nodeName}">
            <xsl:apply-templates select="@* | node()"/>
            <xsl:apply-templates select="$crosswalk//*[name()=$nodeName]/*">
                <xsl:with-param name="curNode" select="$theNode"/>
            </xsl:apply-templates>
        </xsl:element>
    </xsl:template>

    <xsl:template match="*[@source]">
        <xsl:param name="curNode" />
        <xsl:variable name="sourceNodeName" select="@source"/>
        <xsl:element name="{name()}">
            <xsl:value-of select="$curNode//*[name()=$sourceNodeName]"/>
        </xsl:element>
    </xsl:template>

</xsl:stylesheet>

This stops processing once that last template tries to access $curNode for the first time. When I run the xslt in Eclipse (Xalan 2.7.1) it throws this error: " java.lang.ClassCastException: org.apache.xpath.objects.XString cannot be cast to org.apache.xpath.objects.XNodeSet".

If I pass a similar nodeset as a parameter to a template that matches nodes from the source document, it works as expected - the nodeset is accessible. However, passing the nodeset to the last template above doesn't work. Is it because the template matches nodes from the external document? I sure don't know. Any help much appreciated, it took me a while just to get to this point. Thanks!

4

2 回答 2

4

看起来你需要改变两件事:

  • 添加tunnel="yes"xsl:with-paramxsl:param
  • 去掉'$sourceNodeName'谓词中的撇号xsl:value-of

更新的 XSLT:

<xsl:stylesheet version="2.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="*"/>

    <xsl:variable name="crosswalk" select="document('crswlk.xml')"/>

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

    <xsl:template match="*/album|movie">
        <xsl:variable name="theNode" select="."/>
        <xsl:variable name="nodeName" select="name()"/>
        <xsl:element name="{$nodeName}">
            <xsl:apply-templates select="@* | node()"/>
            <xsl:apply-templates select="$crosswalk//*[name()=$nodeName]/*">
                <xsl:with-param name="curNode" select="$theNode" tunnel="yes"/>
            </xsl:apply-templates>
        </xsl:element>
    </xsl:template>

    <xsl:template match="*[@source]">
        <xsl:param name="curNode" tunnel="yes"/>
        <xsl:variable name="sourceNodeName" select="@source"/>
        <xsl:element name="{name()}">
            <xsl:value-of select="$curNode//*[name()=$sourceNodeName]"/>
        </xsl:element>
    </xsl:template>

</xsl:stylesheet>

此外,您可以删除其中一些额外的xsl:variables...

<xsl:stylesheet version="2.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="*"/>

    <xsl:variable name="crosswalk" select="document('crswlk.xml')"/>

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

    <xsl:template match="album|movie">
        <xsl:copy>
            <xsl:apply-templates select="@* | node()"/>
            <xsl:apply-templates select="$crosswalk/*/*[name()=current()/name()]/*">
                <xsl:with-param name="curNode" select="." tunnel="yes"/>
            </xsl:apply-templates>
        </xsl:copy>
    </xsl:template>

    <xsl:template match="*[@source]">
        <xsl:param name="curNode" tunnel="yes"/>
        <xsl:copy>
            <xsl:value-of select="$curNode//*[name()=current()/@source]"/>
        </xsl:copy>
    </xsl:template>

</xsl:stylesheet>
于 2013-07-12T22:16:32.250 回答
3

如果您使用的是 XSLT 2.0,那么 Daniel Haley 对隧道的回答肯定是要走的路。但是,如果您实际使用的是 Xalan,因此只使用 XSLT 1.0,则需要采用不同的方法。

问题从这一行开始:

<xsl:apply-templates select="$crosswalk//*[name()=$nodeName]/*">

这将在您的 cross walk 文档中选择艺术家信息导演信息,但您没有与这些匹配的特定模板,因此您使用的通用身份模板将与它们匹配

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

但这不带任何参数,也不传递任何参数。因此,当您的最后一个模板<xsl:template match="*[@source]">匹配时,curNode将为空(一个空字符串),这不是节点集,这让 Xalan 感到难过。

因此,要在 XSLT1.0 中解决这个问题,只需将参数添加到标识模板,然后将其传递:

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

如果模板在没有传递参数时匹配,它只会传递一个空参数而没有任何问题。

这是完整的 XSLT(也从 xsl:value-of 中删除了撇号的更正,如 Daniel 的回答中所述)。

<xsl:stylesheet version="2.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="*"/>

    <xsl:variable name="crosswalk" select="document('crswlk.xml')"/>

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

    <xsl:template match="*/album|movie">
        <xsl:variable name="theNode" select="."/>
        <xsl:variable name="nodeName" select="name()"/>
        <xsl:element name="{$nodeName}">
            <xsl:apply-templates select="@* | node()"/>
            <xsl:apply-templates select="$crosswalk//*[name()=$nodeName]/*">
                <xsl:with-param name="curNode" select="$theNode"/>
            </xsl:apply-templates>
        </xsl:element>
    </xsl:template>

    <xsl:template match="*[@source]">
        <xsl:param name="curNode" />
        <xsl:variable name="sourceNodeName" select="@source"/>
        <xsl:element name="{name()}">
            <xsl:value-of select="$curNode//*[name()=$sourceNodeName]"/>
        </xsl:element>
    </xsl:template>
</xsl:stylesheet>

当应用于您的 XML 文档时,将输出以下内容

<root>
   <album>
      <artist>Frank Sinatra</artist>
      <title>Greatest Hits</title>
      <artist-info>
         <artist2>Frank Sinatra</artist2>
      </artist-info>
   </album>
   <album>
      <artist>Miles Davis</artist>
      <title>Kind Of Blue</title>
      <artist-info>
         <artist2>Miles Davis</artist2>
      </artist-info>
   </album>
   <movie>
      <title>ET</title>
      <director>Steven Spielberg</director>
      <director-info>
         <director2>Steven Spielberg</director2>
      </director-info>
   </movie>
   <movie>
      <title>Blues Brothers</title>
      <director>John Landis</director>
      <director-info>
         <director2>John Landis</director2>
      </director-info>
   </movie>
</root>
于 2013-07-13T09:28:18.150 回答