XSLT скрипт для создания альбомов списка из ITunes XML

Когда я получил себе в IPOD , я нашел , что печатные списки доступны из ITunes программы не были особенно велики (несмотря на интерактивный интерфейс , будучи превосходным), и я не мог получить краткий сводный список альбомов. Поскольку ITunes сохраняет свою библиотеку как XML, я использовал XSLT для создания списка больше по душе. Так как я склонен слушать альбомы , а не синглы, я хотел список альбомов , сгруппированных по жанру .

Чтобы использовать этот сценарий, сохраните albumList.xml и albumList.xsl показано ниже вашей Музыка / ITunes папку, и откройте albumList.xml в Internet Explorer 6+ или Mozilla Firefox (IE5 не будет работать, я не проверял любой другие браузеры).

‘Список свойств’ The Itunes XML

Библиотека ITunes хранится в виде XML в файле под названием ITunes Music Library.xml . Он использует общий язык XML , известный как список свойств или списка свойств , который содержит информацию в виде пар ключ / значение. Вместо того , чтобы быть адаптированы к описанию песен, он может быть использован для описания свойств более или менее ничего. Это как XML выглядит:

<plist version="1.0">
  <dict>
    <key>Tracks</key>
    <dict>
      <key>638</key>
      <dict>
        <key>Track ID</key><integer>638</integer>
        <key>Name</key><string>Take Me To The River</string>
        <key>Artist</key><string>Talking Heads</string>
        <key>Composer</key><string>Al Green &#38; M. Hodges</string>
        <key>Album</key><string>Stop Making Sense</string>
        <key>Genre</key><string>Alternative &#38; Punk</string>
        ... etc ...
      </dict>
    </dict>
  </dict>
</plist>

 

Это означает, что найти, например, название альбома, мы должны соответствовать <string> с непосредственно предшествующим собрата <key> из «Альбома»:

string[preceding-sibling::key[1]='Album']

То есть, элемент <string> где первая <key> в осях предшествующего-родственных имеет значение «Альбом».

Хотя это удобный универсальный язык XML, это, безусловно, сложнее манипулировать, чем язык XML, приспособленный для представления списка дорожек, поэтому сделайте глубокий вдох для того, что следует!

Получение списка альбомов

XML ITunes список песен. Альбом появится песня на это не более, на самом деле, чем атрибут песни, так что простой подход даст название альбома один раз для каждой песни , которая появляется на нем. Таким образом , чтобы получить правильный список альбомов, я использую довольно хитрые трюки , доступные с : <xsl key> элементом и key() и  generate-id()  функций.

Другие люди объяснили использование <xsl: key> лучше, чем я могу, но вкратце, то здесь, то <xsl: key> элемент строит список <dict> узлы, представляющие песни. <xsl:for-each> элемент затем выбирает <dict> (т.е. песня) элементов, в которых автоматически генерируется идентификатор совпадения автоматически созданный идентификатор первого узла, возвращаемого функцией key(). Нет, это делает мою голову спина тоже. Полученная таблица стилей, чтобы получить список альбомов (с прилагаемыми художниками) является:

<xsl:key name="songsByAlbum" match="dict"
  use="string[preceding-sibling::key[1]='Album']"/>

<xsl:template match="/plist/dict/dict">
  <html>
    <body>
      <table>

        <xsl:for-each select="dict[generate-id(.)=
            generate-id(key('songsByAlbum',string)[1])]">
          <xsl:sort select="string[preceding-sibling::key[1]='Album']"/>
          <tr>
            <td><xsl:value-of select="string[preceding-sibling::key[1]='Album']"/></td>
            <td><xsl:value-of select="string[preceding-sibling::key[1]='Artist']"/></td>
          </tr>
        </xsl:for-each>

      </table>
    </body>
  </html>
</xsl:template>

Это имеет проблемы с компиляций, хотя. Он подберет тот, кто является художником для первой песни компиляции, которая выглядит несколько странно. Чтобы это исправить, я использую «Часть Сборник» флаг следующим образом:

<xsl:key name="songsByAlbum" match="dict"
  use="string[preceding-sibling::key[1]='Album']"/>

<xsl:template match="/plist/dict/dict">
  <html>
    <body>
      <table>

        <xsl:for-each select="dict[generate-id(.)=generate-id(key('albums',string)[1])]">
          <xsl:sort select="string[preceding-sibling::key[1]='Album']"/>
          <tr>
            <td><xsl:call-template name="albumName"/></td>
            <td><xsl:call-template name="artistName"/></td>
          </tr>
        </xsl:for-each>

      </table>
    </body>
  </html>
</xsl:template>

<xsl:template name="albumName">
  <xsl:value-of select="string[preceding-sibling::key[1]='Album']"/>
</xsl:template>

<xsl:template name="artistName">
  <xsl:choose>
    <xsl:when test="true[preceding-sibling::key[1]='Compilation']">
      <i>Compilation</i>
    </xsl:when>
    <xsl:otherwise>
      <xsl:value-of select="string[preceding-sibling::key[1]='Artist']"/>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

Я сломал основной шаблон в отдельные шаблоны для альбома и исполнителя – XSLT может быть трудно следовать в лучшие времена, поэтому я хотел на модули шаблонов столько, сколько я могу.

Список альбомов сгруппированы по жанру

Проблема с этим списком является то , как музыка всех пюре вместе. Я на самом деле не как иметь страсть Матфея трущиеся плечи с Stop Making Sense .

Чтобы сгруппировать альбомы по жанру, я использую технику, разработанный Стив Муэнч, Oracle, «XML технического евангелист», получившее название «Muenchian группировки».

Опять же , другие люди объяснили это лучше , чем я мог, поэтому достаточно сказать (если вы хотите , чтобы попытаться следовать работы XSL) , который использует функциюgenerate-id()  XSL key , определенных на песни по жанру и Альбом, и в точке , где шаблон albumsInGenre называется, текущий контекст является первым <dict> песня в каждом жанре.

В albumsInGenre шаблона, тот же метод используется для получения первой композиции в пределах каждого альбома, чтобы получить название альбома и имя исполнителя. Я также использовать дополнительный предикат для дальнейшей фильтрации этого набора узлов к жанру передается в качестве параметра шаблона.

Таблица стилей выглядит следующим образом:

<xsl:key name="songsByAlbum" match="dict"
  use="string[preceding-sibling::key[1]='Album']"/>
<xsl:key name="songsByGenre" match="dict"
  use="string[preceding-sibling::key[1]='Genre']"/>


<xsl:template match="/plist/dict/dict">
  <html>
    <body>
      <table>

        <xsl:for-each select="dict[generate-id(.)=
            generate-id(key('songsByGenre',string)[1])]">
          <xsl:sort select="string[preceding-sibling::key[1]='Genre']"/>
          <xsl:for-each select="key('songsByGenre',string)[1]">
            <xsl:call-template name="albumsInGenre">
              <xsl:with-param name="genre"
                  select="string[preceding-sibling::key[1]='Genre']"/>
            </xsl:call-template>
          </xsl:for-each>
        </xsl:for-each>

      </table>
    </body>
  </html>
</xsl:template>


<xsl:template name="albumsInGenre">
  <xsl:param name="genre"/>

  <tr><td colspan='3'><b><xsl:value-of select="$genre"/></b></td></tr>

  <xsl:variable name="song" select="/plist/dict/dict/dict"/>
  <xsl:for-each select="$song[generate-id(.)=
      generate-id(key('songsByAlbum',string[preceding-sibling::key[1]='Album'])[1])]">
    <xsl:sort select="string[preceding-sibling::key[1]='Album']"/>
    <xsl:for-each select="key('songsByAlbum',string[preceding-sibling::key[1]='Album'])
        [string[preceding-sibling::key[1]='Genre']=$genre][1]">
      <tr>
        <td> </td>
        <td><xsl:call-template name="albumName"/></td>
        <td><xsl:call-template name="artistName"/></td>
      </tr>
    </xsl:for-each>
  </xsl:for-each>
</xsl:template>

И результирующий список альбомов выглядит примерно так:

Electronica / Танец
бульвар Сен-Жермен
Dehli9 Тоска
На земле Брайан Эно
Джаз
Madar Ян Гарбарк
Эти горячие Пятерки и Семерки Луис Армстронг & The Hot Five

Если вы хотите только альбомы на IPod, а не все альбомов в библиотеке ITunes, то дополнительное условие должно быть добавлено к внутреннему наиболее для-каждому, так что она будет выглядеть так:

<xsl:for-each select="key('songsByAlbum',string[preceding-sibling::key[1]='Album'])
        [string[preceding-sibling::key[1]='Genre']=$genre]
        [not(true[preceding-sibling::key[1]='Disabled'])][1]">

Добавление времени трека

Получив это далеко, я хотел бы видеть продолжительность каждого альбома. Продолжительность каждой песни сохраняется (в миллисекундах) против Общего времени ключа, поэтому шаблон , чтобы сделать это:

<xsl:template name="iTunesTimeAlbum">
  <xsl:variable name="tracksInAlbum"
      select="key('songsByAlbum',string[preceding-sibling::key[1]='Album'])"/>
  <xsl:variable name="t"
      select="sum($tracksInAlbum/integer[preceding-sibling::key[1]='Total Time'])"/>
  <xsl:call-template name="formatTime">
    <xsl:with-param name="t" select="$t"/>
  </xsl:call-template>
</xsl:template>

<xsl:template name="formatTime">
  <xsl:param name="t"/>
  <xsl:if test="$t != 0">
    <xsl:variable name="h" select="floor(($t div (1000*60*60)))"/>
    <xsl:variable name="m" select="floor(($t div (1000*60)) mod 60)"/>
    <xsl:variable name="s" select="floor(($t div 1000) mod 60)"/>
    <xsl:if test="$h != 0"><xsl:value-of select="$h"/>h</xsl:if>
    <xsl:value-of select="format-number($m,'00')"/>m
    <xsl:value-of select="format-number($s,'00')"/>s
  </xsl:if>
</xsl:template>

Опять же, если вы хотите увидеть продолжительность на IPOD, а не в библиотеке ITunes (если только некоторые треки на альбоме были выбраны для передачи на Айпод), дополнительное условие должно быть добавлено для выбора для переменного t:

<xsl:variable name="t"
      select="sum($tracksInAlbum/integer[preceding-sibling::key[1]='Total Time']
      [not(../true[preceding-sibling::key[1]='Disabled'])])"/>

Подобный шаблон даст общее время музыки в библиотеке ITunes (или на Айпод):

<xsl:template name="iTunesTimeTotal">
  <xsl:variable name="t"
      select="sum(dict/integer[preceding-sibling::key[1]='Total Time'])"/>
  <xsl:call-template name="formatTime">
    <xsl:with-param name="t" select="$t"/>
  </xsl:call-template>
</xsl:template>

И если вы хотите иметь общий размер диска песен, он доступен против Size key:

<xsl:template name="iTunesSizeTotal">
  <xsl:variable name="s" select="sum(dict/integer[preceding-sibling::key[1]='Size'])"/>
  <xsl:value-of select="floor($s div (1000*1000)) div 1000"/>GB
</xsl:template>

Открытие листинга непосредственно в браузере

Вместо того чтобы использовать специальные инструменты, XML / XSLT, я открываю список непосредственно в браузере. Для этого я создаю «обертку» файл XML, который определяет, какие таблицы стилей, чтобы применить к какому файлу XML, используя кнопку <?xml-stylesheet> инструкции по <incl> элемента:

<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet href="albumList.xsl" type="text/xsl"?>
<wrapper>
  <incl file="iTunes Music Library.xml"/>
</wrapper>

Затем я поставил шаблон в верхней части XSL таблицы стилей, которая соответствует элементу <wrapper>  я положил вокруг включаемого файла XML, и применяю шаблоны стилей для включаемого файла XML.

<xsl:template match="/wrapper">
    <xsl:apply-templates select="document(incl/@file)"/>
  </xsl:template>

Это должно работать в любом текущем браузере; Я проверил это на Internet Explorer (6.0+) и Firefox.

Примечание: альтернативный подход был бы предварительно обработать в п-список XML в более традиционную структуру, как объяснено в www.xmldatabases.org/WK/blog/1086?t=item , который затем будет проще манипулировать с помощью XSLT , Я готов жить с preceding-sibling  контсрукции  для того , чтобы иметь возможность генерировать список в одном шаге.


Все это вместе взятые в таблице стилей ниже. Это делает для довольно большой таблицы стилей, но с вышеуказанной информацией, вы должны быть в состоянии поднять его на части и использовать детали, которые вы хотите.

… так что теперь у меня есть краткий справочник список альбомов на айподе.


Чтобы использовать этот сценарий, в своей основной или полной форме, сохранить albumList.xml и albumList.xsl показано ниже вашей Музыка / ITunes папку, и откройте albumList.xml в вашем браузере (IE6 или Firefox). Если вы хотите , чтобы адаптировать макет, с руководящими принципами выше , вы должны иметь возможность смешивать и сочетать компоненты по мере необходимости. Вы можете повторно использовать эти сценарии [без каких – либо гарантий явных или подразумеваемых] для личного использования

Файл albumList.xml (указывает , применять albumList.xsl к ITunes Music Library.xml ):

<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet href="albumList.xsl" type="text/xsl"?>
<wrapper>
  <incl file="iTunes Music Library.xml"/>
</wrapper>

Файл albumList.xsl базовой версии (списки имя & группа альбомов на Айпод):

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
  <!-- (c) Chris Veness 2005 -->
  <xsl:output method="html" encoding="UTF-8" indent="yes"/>

  <!-- match the wrapper and apply templates to the <incl> xml file -->
  <xsl:template match="/wrapper">
    <xsl:apply-templates select="document(incl/@file)/plist/dict/dict"/>
  </xsl:template>


  <xsl:key name="songsByGenre" match="dict" use="string[preceding-sibling::key[1]='Genre']"/>
  <xsl:key name="songsByAlbum" match="dict" use="string[preceding-sibling::key[1]='Album']"/>


  <xsl:template match="dict">
    <html>
      <head>
        <title>iPod Album Listing</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
      </head>
      <body>
        <table>

          <xsl:for-each select="dict[generate-id(.)=generate-id(key('songsByGenre',string)[1])]">
            <xsl:sort select="string[preceding-sibling::key[1]='Genre']"/>
            <xsl:for-each select="key('songsByGenre',string)[1]">
              <xsl:call-template name="albumsInGenre">
                <xsl:with-param name="genre" select="string[preceding-sibling::key[1]='Genre']"/>
              </xsl:call-template>
            </xsl:for-each>
          </xsl:for-each>

        </table>
      </body>
    </html>
  </xsl:template>


  <xsl:template name="albumsInGenre">
    <xsl:param name="genre"/>

    <!-- genre header -->
    <tr><td colspan='3'><b><xsl:value-of select="$genre"/></b></td></tr>

    <!-- output each album in genre -->
    <xsl:variable name="song" select="/plist/dict/dict/dict"/>
    <xsl:for-each select="$song[generate-id(.)=
        generate-id(key('songsByAlbum',string[preceding-sibling::key[1]='Album'])[1])]">
      <xsl:sort select="string[preceding-sibling::key[1]='Album']"/>
      <xsl:for-each select="key('songsByAlbum',string[preceding-sibling::key[1]='Album'])
          [string[preceding-sibling::key[1]='Genre']=$genre]
          [not(true[preceding-sibling::key[1]='Disabled'])][1]">
        <tr valign='top'>
          <td width='20'> </td>
          <!-- the album name: -->
          <td><xsl:value-of select="string[preceding-sibling::key[1]='Album']"/></td>
          <!-- the artist: -->
          <td>
            <xsl:choose>
              <xsl:when test="true[preceding-sibling::key[1]='Compilation']">
                <i>Compilation</i>
              </xsl:when>
              <xsl:otherwise>
                <xsl:value-of select="string[preceding-sibling::key[1]='Artist']"/>
              </xsl:otherwise>
            </xsl:choose>
          </td>
        </tr>
      </xsl:for-each>
    </xsl:for-each>
  </xsl:template>

</xsl:stylesheet>

Файл albumList.xsl полной версии (включает в себя полную библиотеку ITunes с раз и итогами):

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
  <!-- (c) Chris Veness 2005-2006 -->
  <xsl:output method="html" encoding="UTF-8" indent="yes"/>


  <!-- match the wrapper and apply templates to the <incl> xml file -->
  <xsl:template match="/wrapper">
    <xsl:apply-templates select="document(incl/@file)/plist/dict/dict"/>
  </xsl:template>


  <xsl:key name="songsByGenre" match="dict" use="string[preceding-sibling::key[1]='Genre']"/>
  <xsl:key name="songsByAlbum" match="dict" use="string[preceding-sibling::key[1]='Album']"/>


  <xsl:template match="dict">
    <html>
      <head>
        <title>iTunes Album Listing</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
        <style type='text/css'> td { vertical-align: top; padding-right: 1em; } </style>
      </head>
      <body>
        <table>

          <xsl:for-each select="dict[generate-id(.)=generate-id(key('songsByGenre',string)[1])]">
            <xsl:sort select="string[preceding-sibling::key[1]='Genre']"/>
            <xsl:for-each select="key('songsByGenre',string)[1]">
              <xsl:call-template name="albumsInGenre">
                <xsl:with-param name="genre" select="string[preceding-sibling::key[1]='Genre']"/>
              </xsl:call-template>
            </xsl:for-each>
          </xsl:for-each>

          <!-- totals -->
          <tr>
            <td colspan='4' style='color: gray'><b>Total</b></td>
            <td style='color: gray' align='right'><xsl:call-template name="iPodTimeTotal"/></td>
            <td style='color: gray' align='right'><xsl:call-template name="iTunesTimeTotal"/></td>
          </tr>
          <tr>
            <td colspan='4'> </td>
            <td style='color: gray' align='right'><xsl:call-template name="iPodSizeTotal"/></td>
            <td style='color: gray' align='right'><xsl:call-template name="iTunesSizeTotal"/></td>
          </tr>

        </table>
      </body>
    </html>
  </xsl:template>


  <xsl:template name="albumsInGenre">
    <xsl:param name="genre"/>

    <tr>  <!-- genre header -->
      <td colspan='4'><b><xsl:value-of select="$genre"/></b></td>
      <td align='right' style='color: gray'><i>iPod</i></td>
      <td align='right' style='color: gray'><i>iTunes</i></td>
    </tr>

    <xsl:variable name="song" select="/plist/dict/dict/dict"/>
    <xsl:for-each select="$song[generate-id(.)=
        generate-id(key('songsByAlbum',string[preceding-sibling::key[1]='Album'])[1])]">
      <xsl:sort select="string[preceding-sibling::key[1]='Album']"/>
      <xsl:for-each select="key('songsByAlbum',string[preceding-sibling::key[1]='Album'])
          [string[preceding-sibling::key[1]='Genre']=$genre]
          [1]">
          <!--  for albums on iPod only, add
                [not(true[preceding-sibling::key[1]='Disabled'])] -->
        <tr>
          <td> </td>
          <td><xsl:call-template name="album"/></td>
          <td><xsl:call-template name="artist"/></td>
          <td align='right'><xsl:call-template name="iPodTimeAlbum"/></td>
          <td align='right'><xsl:call-template name="iTunesTimeAlbum"/></td>
        </tr>
      </xsl:for-each>
    </xsl:for-each>
    <tr><td colspan='6'>&#160;</td></tr>  <!-- space between genres -->
  </xsl:template>


  <xsl:template name="album">
    <xsl:value-of select="string[preceding-sibling::key[1]='Album']"/>
  </xsl:template>


  <xsl:template name="artist">
    <xsl:choose>
      <xsl:when test="true[preceding-sibling::key[1]='Compilation']">
        <i>Compilation</i>
      </xsl:when>
      <xsl:otherwise>
        <xsl:value-of select="string[preceding-sibling::key[1]='Artist']"/>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>


  <xsl:template name="iPodTimeAlbum">
    <xsl:variable name="tracksInAlbum"
        select="key('songsByAlbum',string[preceding-sibling::key[1]='Album'])"/>
    <xsl:variable name="t"
        select="sum($tracksInAlbum/integer[preceding-sibling::key[1]='Total Time']
            [not(../true[preceding-sibling::key[1]='Disabled'])])"/>
    <xsl:call-template name="formatTime">
      <xsl:with-param name="t" select="$t"/>
    </xsl:call-template>
  </xsl:template>


  <xsl:template name="iTunesTimeAlbum">
    <xsl:variable name="tracksInAlbum"
        select="key('songsByAlbum',string[preceding-sibling::key[1]='Album'])"/>
    <xsl:variable name="t"
        select="sum($tracksInAlbum/integer[preceding-sibling::key[1]='Total Time'])"/>
    <xsl:call-template name="formatTime">
      <xsl:with-param name="t" select="$t"/>
    </xsl:call-template>
  </xsl:template>


  <xsl:template name="iPodTimeTotal">
    <xsl:variable name="t" select="sum(dict/integer[preceding-sibling::key[1]='Total Time']
        [not(../true[preceding-sibling::key[1]='Disabled'])])"/>
    <xsl:call-template name="formatTime">
      <xsl:with-param name="t" select="$t"/>
    </xsl:call-template>
  </xsl:template>


  <xsl:template name="iTunesTimeTotal">
    <xsl:variable name="t" select="sum(dict/integer[preceding-sibling::key[1]='Total Time'])"/>
    <xsl:call-template name="formatTime">
      <xsl:with-param name="t" select="$t"/>
    </xsl:call-template>
  </xsl:template>


  <xsl:template name="iPodSizeTotal">
    <xsl:variable name="s" select="sum(dict/integer[preceding-sibling::key[1]='Size']
        [not(../true[preceding-sibling::key[1]='Disabled'])])"/>
    <xsl:value-of select="floor($s div (1000000)) div 1000"/>GB
  </xsl:template>


  <xsl:template name="iTunesSizeTotal">
    <xsl:variable name="s" select="sum(dict/integer[preceding-sibling::key[1]='Size'])"/>
    <xsl:value-of select="floor($s div (1000000)) div 1000"/>GB
  </xsl:template>


  <xsl:template name="formatTime">
    <xsl:param name="t"/>
    <xsl:if test="$t != 0">
      <xsl:variable name="h" select="floor(($t div (1000*60*60)))"/>
      <xsl:variable name="m" select="floor(($t div (1000*60)) mod 60)"/>
      <xsl:variable name="s" select="floor(($t div 1000) mod 60)"/>
      <xsl:if test="$h != 0"><xsl:value-of select="$h"/>:</xsl:if>
      <xsl:value-of select="format-number($m,'00')"/>:<xsl:value-of select="format-number($s,'00')"/>
    </xsl:if>
  </xsl:template>

</xsl:stylesheet>

Ссылка на оригинал статьи: http://www.movable-type.co.uk/scripts/itunes-albumlist.html

Leave a Reply

Your email address will not be published. Required fields are marked *