如何按给定顺序对 XML 进行排序? (弄乱导出的 iTunes 播放列表)

How to sort XML in given order? (messing with exported iTunes playlists)

在 iTunes 中,当您在 XML 中导出播放列表时,它会自动按照 "date added" 排序的方式导出,而不是按照用户订购的顺序排序。在 XML 文件的末尾,它列出了用户按歌曲 ID 订购的播放列表的顺序,如下所示:

<key>Playlist Items</key>
            <array>
                <dict>
                    <key>Track ID</key><integer>5365</integer>
                </dict>
                <dict>
                    <key>Track ID</key><integer>5317</integer>
                </dict>
                <dict>
                    <key>Track ID</key><integer>5235</integer>
                </dict>
            <array>

我使用此 XSL 转换了 XML 文档:

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

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
    <xsl:template match="/">
        <table>
            <tr>
                <th>Name</th>
                <th>Artist</th>
                <th>Year</th>

            </tr>
            <xsl:call-template name="records" />
        </table>
    </xsl:template>

    <xsl:template name="records">
        <xsl:for-each select="/*/*/dict[1]/dict">
            <xsl:element name="tr">
                <xsl:call-template name="songs" />
            </xsl:element>
        </xsl:for-each>
    </xsl:template>

    <xsl:template name="songs">

        <td>
            <xsl:value-of select="child::*[preceding-sibling::* = 'Track ID']" />
        </td>
        <td>
            <xsl:value-of select="child::*[preceding-sibling::* = 'Name']" />
        </td>
        <td>
            <xsl:value-of select="child::*[preceding-sibling::* = 'Artist']" />
        </td>

        </xsl:template>
</xsl:stylesheet>

我想知道如何编辑我的 XSL 以转换我的播放列表以根据出现在原始 XML 文件末尾的用户给定顺序重新排序所有内容,而不是通过 "date added"?

编辑 - 添加更多原始 XML。这是与我所做的 XSL 相关的片段:

<key>5189</key>
        <dict>
            <key>Track ID</key><integer>5189</integer>
            <key>Name</key><string>varsity jacket</string>
            <key>Artist</key><string>bayou</string>
            <key>Kind</key><string>MPEG audio file</string>
            <key>Size</key><integer>3532008</integer>
            <key>Total Time</key><integer>220107</integer>
            <key>Date Modified</key><date>2015-07-26T14:04:34Z</date>
            <key>Date Added</key><date>2015-07-26T14:04:17Z</date>
            <key>Bit Rate</key><integer>128</integer>
            <key>Sample Rate</key><integer>44100</integer>
            <key>Play Count</key><integer>1</integer>
            <key>Play Date</key><integer>3520959994</integer>
            <key>Play Date UTC</key><date>2015-07-29T00:26:34Z</date>
            <key>Skip Count</key><integer>2</integer>
            <key>Skip Date</key><date>2015-08-29T05:12:33Z</date>
            <key>Persistent ID</key><string>BE57D36AF01737E3</string>
            <key>Track Type</key><string>File</string>
            <key>Location</key><string>file:///Users/jason/Music/iTunes/iTunes%20Music/bayou/Unknown%20Album/varsity%20jacket.mp3</string>
            <key>File Folder Count</key><integer>4</integer>
            <key>Library Folder Count</key><integer>1</integer>
        </dict>

对您的代码的基本但友好的建议:如果您可以使用 xsl:apply-templates,请不要将 xsl:call-template 与上下文一起使用,因为它会限制您的流程并使您的代码复杂化。

查看我的编辑。我采用了您的结构,但通过删除 xsl:call-template 进行了简化,这没有任何其他目的。

要排序,只需使用 xsl:sort 作为 xsl:apply-templates 的子项。这里我按艺术家的名字排序。如果需要,您可以通过添加更多 xsl:sort 元素来添加更多排序键。

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">

     <!-- from SO:  -->

    <xsl:output indent="yes" />

    <xsl:template match="/">
        <table>
            <tr>
                <th>Name</th>
                <th>Artist</th>
                <th>Year</th>

            </tr>
            <xsl:apply-templates select="*/*/dict[1]/dict" >
                <!-- sort by value of the name of the song -->
                <xsl:sort select="key[. = 'Name']/following-sibling::*[1]" />
            </xsl:apply-templates>
        </table>
    </xsl:template>

    <xsl:template match="dict">
        <tr>
            <xsl:apply-templates select="*" />
        </tr>
    </xsl:template>

    <xsl:template match="dict/key[. = 'Track ID' or . = 'Name' or . = 'Artist']">
        <td>
            <xsl:value-of select="following-sibling::*[1]" />
        </td>
    </xsl:template>

    <!-- ignore what we do not need -->
    <xsl:template match="dict/*" priority="-1" />
</xsl:stylesheet>

因为你没有提供有效的 XML 作为源文档(它有两个根元素),所以我发明了适合你的原始代码的我自己的(即实际输出一些东西)并添加了第二个记录以确保排序有效。一个友好的建议,如果您将来询问 XSLT 问题,如果您包含一个(最小的)源文档,我们可以使用它来测试对您的代码的修复,这将使我们更容易。

新的源文档:

<root>
    <record>
        <dict>
            <key>5189</key>
            <dict>
                <key>Track ID</key><integer>5189</integer>
                <key>Name</key><string>varsity jacket</string>
                <key>Artist</key><string>bayou</string>
                <key>Kind</key><string>MPEG audio file</string>
                <key>Size</key><integer>3532008</integer>
                <key>Total Time</key><integer>220107</integer>
                <key>Date Modified</key><date>2015-07-26T14:04:34Z</date>
                <key>Date Added</key><date>2015-07-26T14:04:17Z</date>
                <key>Bit Rate</key><integer>128</integer>
                <key>Sample Rate</key><integer>44100</integer>
                <key>Play Count</key><integer>1</integer>
                <key>Play Date</key><integer>3520959994</integer>
                <key>Play Date UTC</key><date>2015-07-29T00:26:34Z</date>
                <key>Skip Count</key><integer>2</integer>
                <key>Skip Date</key><date>2015-08-29T05:12:33Z</date>
                <key>Persistent ID</key><string>BE57D36AF01737E3</string>
                <key>Track Type</key><string>File</string>
                <key>Location</key><string>file:///Users/jason/Music/iTunes/iTunes%20Music/bayou/Unknown%20Album/varsity%20jacket.mp3</string>
                <key>File Folder Count</key><integer>4</integer>
                <key>Library Folder Count</key><integer>1</integer>
            </dict>
            <key>5190</key>
            <dict>
                <key>Track ID</key><integer>5190</integer>
                <key>Name</key><string>some song</string>
                <key>Artist</key><string>anton</string>
                <key>Kind</key><string>MPEG audio file</string>
                <key>Size</key><integer>3532008</integer>
                <key>Total Time</key><integer>220107</integer>
                <key>Date Modified</key><date>2015-07-26T14:04:34Z</date>
                <key>Date Added</key><date>2015-07-26T14:04:17Z</date>
                <key>Bit Rate</key><integer>128</integer>
                <key>Sample Rate</key><integer>44100</integer>
                <key>Play Count</key><integer>1</integer>
                <key>Play Date</key><integer>3520959994</integer>
                <key>Play Date UTC</key><date>2015-07-29T00:26:34Z</date>
                <key>Skip Count</key><integer>2</integer>
                <key>Skip Date</key><date>2015-08-29T05:12:33Z</date>
                <key>Persistent ID</key><string>BE57D36AF01737E3</string>
                <key>Track Type</key><string>File</string>
                <key>Location</key><string>file:///Users/jason/Music/iTunes/iTunes%20Music/bayou/Unknown%20Album/varsity%20jacket.mp3</string>
                <key>File Folder Count</key><integer>4</integer>
                <key>Library Folder Count</key><integer>1</integer>
            </dict>
        </dict>
    </record>
</root>

以及新的按名称排序的输出:

<table>
   <tr>
      <th>Name</th>
      <th>Artist</th>
      <th>Year</th>
   </tr>
   <tr>
      <td>5190</td>
      <td>some song</td>
      <td>anton</td>
   </tr>
   <tr>
      <td>5189</td>
      <td>varsity jacket</td>
      <td>bayou</td>
   </tr>
</table>

另请注意:您的第一个代码段有 array/dict,您的第二个代码段只有 dict,但您的 XSLT 使用 dict/dict,所以我更改了源结构以使其适合您的XSLT 代码。


你说:

sort [...] according to the user-given order that appears at the end of the original XML file rather than by "date added"

您需要扩展我上面给出的示例。由于我不知道这部分XML的结构,而且问题中也没有给出,所以无法应用。不过,我假设它会是底部的一个元素,所以应该很容易使用它。

例如,假设订单由 /*/userorder/@by 给出,并且 @by 包含匹配 key 元素的值,您可以这样做:

<xsl:sort select="key[. = /*/userorder/@by]/following-sibling::*[1]" />