使用xslt将给定的xml转换为另一个xml

时间:2013-09-26 08:51:28

标签: xml xslt

我有一个输入xml,如
XML输入:

    <parent>
        <child>
            <child2 name="Action">Change</child2>
            <child1 name="Change">

                <child2 name="Dest_Author">Author1</child2>

                <child2 name="Book1">BookID1</child2>
                <child2 name="Type1">General</child2>
                <child2 name="Pages1">100</child2>

                <child2 name="Book2">BookID2</child2>
                <child2 name="Type2">Cooking</child2>
                <child2 name="Pages2">200</child2>

                <child2 name="Orig_Author">Author2</child2>

                <child2 name="Book1">BookID3</child2>
                <child2 name="Type1">General</child2>
                <child2 name="Pages1">150</child2>

                <child2 name="Book2">BookID4</child2>
                <child2 name="Type2">Cooking</child2>
                <child2 name="Pages2">120</child2>

            </child1>
        </child>
    </parent>

我想使用xslt转换将其转换为以下xml 预期的XML输出:

    <list>
        <author id="Author1">
            <book>
                <id>BookID1</id>
                <type>General</type>
                <pages>100</pages>
            </book>
            <book>
                <id>BookID1</id>
                <type>Cooking</type>
                <pages>200</pages>
            </book>
        </author>    
        <author id="Author2">
            <book>
                <id>BookID1</id>
                <type>General</type>
                <pages>150</pages>
            </book>
            <book>
                <id>BookID1</id>
                <type>Cooking</type>
                <pages>120</pages>
            </book>
        </author>
    </list>

我尝试使用xslt转换它,但无法这样做。 我的xslt如下所示

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"  version="1.0">
    <xsl:output method="xml" omit-xml-declaration="yes" />
    <xsl:strip-space elements="*" />
    <xsl:template match="/">
        <xsl:apply-templates />
    </xsl:template>
    <xsl:template match="parent">
        <list>
            <xsl:apply-templates />
        </list>
    </xsl:template>
    <xsl:template match="child">
        <xsl:apply-templates />
    </xsl:template>
    <xsl:template match="child1">
        <xsl:choose>
            <xsl:when test="./@name='Change'">
                <author>
                    <xsl:attribute name="id"><xsl:value-of
                        select="./child2[@name='Dest_Author']" /></xsl:attribute>
                    <xsl:apply-templates />
                </author>
            </xsl:when>
        </xsl:choose>
    </xsl:template>
    <xsl:template match="child2">
        <xsl:choose>
            <xsl:when test="contains(@name,'Orig_Author')">
                <!-- I thought of changing the below code(line no 36-38) as
                </author>
                <author>
                    <xsl:attribute name="id"><xsl:value-of select="text()"/></xsl:attribute>
                but if do this it throws error "The element type "xsl:when" must be terminated by the matching end-tag "</xsl:when>"."
            -->
                <author>
                    <xsl:attribute name="id"><xsl:value-of select="text()"/></xsl:attribute>
                </author>
            </xsl:when>

            <xsl:when test="contains(@name,'Book')">
                <book>
                    <id>
                        <xsl:value-of select="text()" />
                    </id>
                    <xsl:variable name="next"
                        select="./following-sibling::node()[@name!=''][1]"></xsl:variable>
                    <xsl:choose>
                        <xsl:when test="contains($next/@name,'Type')">
                            <type>
                                <xsl:value-of select="$next" />
                            </type>
                            <pages>
                                <xsl:value-of select="$next/following-sibling::node()[@name!=''][1]" />
                            </pages>
                        </xsl:when>
                        <xsl:when test="contains($next/@name,'Pages')">
                            <pages>
                                <xsl:value-of select="$next" />
                            </pages>
                        </xsl:when>
                    </xsl:choose>
                </book>
            </xsl:when>
            <xsl:otherwise>    
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>
</xsl:stylesheet>

XSLT输出:

<list>
    <author id="Author1">
        <book>
            <id>BookID1</id>
            <type>General</type>
            <pages>100</pages>
        </book>
        <book>
            <id>BookID2</id>
            <type>Cooking</type>
            <pages>200</pages>
        </book>
        <author id="Author2"/>
        <book>
            <id>BookID3</id>
            <type>General</type>
            <pages>150</pages>
        </book>
        <book>
            <id>BookID4</id>
            <type>Cooking</type>
            <pages>120</pages>
        </book>
    </author>
</list>

有人可以帮助我并告诉xslt代码需要哪些更改才能获得正确的输出

1 个答案:

答案 0 :(得分:0)

我会将此视为分组问题。您希望将名称​​ not 包含child2的每个_Author附加到其最近的前一个兄弟,其名称​​ 包括_Author,并且每组生成一个author元素。然后,您希望将每个非Book元素附加到其最近的前一个Book,并为每个组生成一个book元素。

XSLT具有 keys 的概念,可用于对元素进行分组。我会定义两个键,第一个

<xsl:key name="booksByAuthor" match="child2[starts-with(@name, 'Book')]"
   use="generate-id(preceding-sibling::child2[contains(@name, '_Author')][1])" />

取每个Book(即child2[@name = 'Book1' or @name='Book2' or ....]),并根据最近的前作者的身份(generate-id)为其分配一个分组键。第二个关键

<xsl:key name="bookDetails"
   match="child2[not(contains(@name, '_Author') or starts-with(@name, 'Book'))]"
   use="generate-id(preceding-sibling::child2[starts-with(@name, 'Book')][1])" />

将所有“详细信息”元素(不是Something_AuthorBookN)的元素按其最近的前一个图书分组。这些键为您提供了层次结构,并允许您将样式表的其余部分编码为模板匹配的常规系统。

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

    <xsl:key name="booksByAuthor" match="child2[starts-with(@name, 'Book')]"
       use="generate-id(preceding-sibling::child2[contains(@name, '_Author')][1])" />

    <xsl:key name="bookDetails"
       match="child2[not(contains(@name, '_Author') or starts-with(@name, 'Book'))]"
       use="generate-id(preceding-sibling::child2[starts-with(@name, 'Book')][1])" />

    <xsl:template match="/parent">
        <list>
            <!-- process all the authors -->
            <xsl:apply-templates select=".//child2[contains(@name, '_Author')]" />
        </list>
    </xsl:template>

    <xsl:template match="child2[contains(@name, '_Author')]">
        <!-- for each author, create an author element -->
        <author id="{.}">
            <!-- and process this author's books -->
            <xsl:apply-templates select="key('booksByAuthor', generate-id())" />
        </author>
    </xsl:template>

    <xsl:template match="child2[starts-with(@name, 'Book')]">
        <!-- for each book, create a book element -->
        <book>
            <id><xsl:value-of select="." /></id>
            <!-- and process this book's details -->
            <xsl:apply-templates select="key('bookDetails', generate-id())" />
        </book>
    </xsl:template>

    <xsl:template match="child2[starts-with(@name, 'Type')]">
        <type><xsl:value-of select="." /></type>
    </xsl:template>

    <xsl:template match="child2[starts-with(@name, 'Pages')]">
        <pages><xsl:value-of select="." /></pages>
    </xsl:template>
</xsl:stylesheet>

如果您有其他子类型,例如Publisher1Publisher2,...那么您只需添加与TypePages相同的其他模板的。