使用xslt,如何添加在第一次出现之前开始,在最后一次出现项目之后结束的xml节点?

使用xslt,如何添加在第一次出现之前开始,在最后一次出现项目之后结束的xml节点?,xml,xslt,Xml,Xslt,我有一个如下所示的xml: <ShoppingList> <MoneyIHaveToSpend>20.00</MoneyIHaveToSpend> <Item> <Name>Apples</Name> <Price>1.00</Price> </Item> <Item> <Name>Or

我有一个如下所示的xml:

<ShoppingList>
    <MoneyIHaveToSpend>20.00</MoneyIHaveToSpend>
    <Item>
        <Name>Apples</Name>
        <Price>1.00</Price>
    </Item>
    <Item>
        <Name>Oranges</Name>
        <Price>1.00</Price>
    </Item>
    <AdditionalInfo>...</AdditionalInfo>
</ShoppingList>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output indent="yes"/>
    <xsl:strip-space elements="*"/>

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

    <xsl:template match="Item[1]">
        <GroceryList>
            <xsl:apply-templates select=".|following-sibling::Item" mode="keep"/>
        </GroceryList>
    </xsl:template>

    <xsl:template match="Item" mode="keep">
        <!--You could overwrite identity transform here.-->
        <xsl:call-template name="ident"/>
    </xsl:template>

    <xsl:template match="Item"/>

</xsl:stylesheet>

20
苹果
1
橘子
1
...
我想将“项目”包装到杂货店列表中,如下所示:

<ShoppingList>
    <MoneyIHaveToSpend>20.00</MoneyIHaveToSpend>
    <GroceryList>
        <Item>
            <Name>Apples</Name>
            <Price>1.00</Price>
        </Item>
        <Item>
            <Name>Oranges</Name>
            <Price>1.00</Price>
        </Item>
    </GroceryList>
    <AdditionalInfo>...</AdditionalInfo>
</ShoppingList>

20
苹果
1
橘子
1
...
“ShoppingList”中只有一个子列表。我将如何使用xslt来实现这一点

我试着做如下的事情,但是将
分开会导致编译错误。我意识到其余部分无论如何都是错误的,但由于编译错误,我甚至无法测试或处理它:

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

<xsl:template match="ShoppingList">
    <xsl:apply-templates select="/ShoppingList/Item"/>
    <xsl:if test="position() = 1">
        <GroceryList> <!-- Error: "XML element is not closed" -->
        <xsl:apply-templates/>
    </xsl:if>
    <xsl:if test="???">
        </GroceryList> <!-- Error: "No open tag found" -->
    </xsl:if>
</xsl:template>

您的XSLT必须是格式良好的XML,因此不能在一个
xsl:if
中启动一个元素,然后在另一个中关闭它

就像C.M.斯珀伯格·麦奎因建议的那样,从一个开始,然后从那里重写

示例(如果
购物列表
子项的顺序无关紧要):


如果顺序确实重要,您可以这样做:

<ShoppingList>
    <MoneyIHaveToSpend>20.00</MoneyIHaveToSpend>
    <Item>
        <Name>Apples</Name>
        <Price>1.00</Price>
    </Item>
    <Item>
        <Name>Oranges</Name>
        <Price>1.00</Price>
    </Item>
    <AdditionalInfo>...</AdditionalInfo>
</ShoppingList>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output indent="yes"/>
    <xsl:strip-space elements="*"/>

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

    <xsl:template match="Item[1]">
        <GroceryList>
            <xsl:apply-templates select=".|following-sibling::Item" mode="keep"/>
        </GroceryList>
    </xsl:template>

    <xsl:template match="Item" mode="keep">
        <!--You could overwrite identity transform here.-->
        <xsl:call-template name="ident"/>
    </xsl:template>

    <xsl:template match="Item"/>

</xsl:stylesheet>

上述内容非常通用,但如果您始终知道子项
ShoppingList
将包含哪些内容,或者不需要修改任何元素,则可以简化。(您可以使用
xsl:copy of
而不是
xsl:apply templates

另一种解决方案:

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

    <xsl:output encoding="utf-8" method="xml"/>

    <xsl:template match="ShoppingList">

        <ShoppingList>
            <xsl:variable name="firstItemName"><xsl:value-of select="./Item/Name/text()"/></xsl:variable>
            <xsl:apply-templates>
                <xsl:with-param name="firstItem"><xsl:value-of select="$firstItemName"/></xsl:with-param>
            </xsl:apply-templates>
        </ShoppingList>
    </xsl:template>

    <xsl:template match="Item">
        <xsl:param name="firstItem"/>

        <xsl:choose>
            <xsl:when test="./Name/text()=$firstItem">
                <xsl:element name="GroceryList">
                    <xsl:apply-templates select="../Item"/>
                </xsl:element>
            </xsl:when>
            <xsl:otherwise>
                <xsl:copy-of select="."/>
            </xsl:otherwise>
        </xsl:choose>

        </xsl:template>

    <xsl:template match="MoneyIHaveToSpend|AdditionalInfo|text()">
        <xsl:copy-of select="."/>
    </xsl:template>

</xsl:stylesheet>

节点不是“开始”和“结束”,它们是具有父节点和子节点的树中的点。您试图编写XSLT,就好像它将开始和结束标记作为可分离的操作输出一样,而不是构建节点树。树上的节点是不可分割的

如果分组问题如示例所示简单,则最简单的解决方案将使用以下模板:

<xsl:template match="ShoppingList">
  <xsl:apply-templates select="MoneyIHaveToSpend"/>
  <GroceryList>
    <xsl:apply-templates select="Item"/>
  </GroceryList>
  <xsl:apply-templates select="AdditionalInfo"/>
</xsl:template>


当你读到这篇文章时,不要把
看作是单独的说明。模板正文包含一个由三条指令组成的序列:apply templates、GroceryList和apply templates,GroceryList指令的内容(“序列构造函数”)包含生成GroceryList节点内容的指令。

我还没有试着运行它,但我认为它不能作为XSLT2.0样式表使用。在2.0中,的
xsl:value中的
select
返回序列中的所有项。在1.0中,它只返回第一个。因此,如果使用
,变量实际上将有两项:
苹果
橙子
。在1.0中,您只需要
苹果
。如果您将版本更改为1.0,此样式表可能会工作,但我认为您仍然会有一个额外的
Oranges
项输出。此外,当您使用
xsl:variable
(或
xsl:param
xsl:attribute
等)时,请尝试使用
选择
属性。如果不需要,则在不需要时创建一个RTF(结果树片段)。下面是您的
firstItemName
重写的示例:
@DanielHaley-感谢您的指点!我在1.0中测试了它,没问题,但是你提出了一些非常好的观点。