Back to site
Since 2004, our University project has become the Internet's most widespread web hosting directory. Here we like to talk a lot about web servers, web development, networking and security services. It is, after all, our expertise. To make things better we've launched this science section with the free access to educational resources and important scientific material translated to different languages.

XSLT scripta za generisanje liste albuma iz Tunes XML




Kada sam nabavio iPod, primetio sam da priloženi listinzi dostupni na iTunes programu nisu toliko dobri (iako je interaktivni interfejs vrhunski), i nisam mogao da dođem do preciznne liste albuma. Kako iTunes čuva svoje biblioteke podataka kao XML, koristio sam XSLT za kreiranje liste koja mi više odgovara. Pošto češće slušam albume nego singlove, želeo sam da imam liste albuma grupisane po žanrovima.

Da bi ste koristili ovu skriptu, snimite albumList.xml i albumList.xsl kao što je prikazano ispod u My Music/iTunes folder, i otvorite albumList.xml u Internet Explorer 6+ ili Mozilla Firefox (IE5 ne radi, a nisam testirao ostale pretraživače).

iTunes XML ‘property list’

iTunes biblioteke se čuvaju kao XML u fajlu koji je nazvan iTunes Music Library.xml. Koristi generički XML jezik poznat kao property list, ili plist, koji sadrži informaciju kao key/value par. Umesto da služi samo za opis pesme, može se koristiti da opiše bilo koje osobine. Ovako izgleda 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>

To bi značilo da ukoliko želimo da nađemo ime albuma, moramo da uklopimo <string> sa prethodnim srodnim <key> iz ‘Album’:

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

To jest <string> element gde je prvi <key> element u prethodnoj-srodnoj osi i ima vrednost: ‘Album’.

Iako je ovo prikladan XML programski jezik opšte namene,definitivno je mnogo kompleksniji za manipulaciju od XML programskog jezika namenjenog za pretstavljanje numera.Zato, udahnite duboko zbog onoga što sledi!

Dobijanje liste albuma

iTunes XML je lista pesama. Album na kome se pesma nalazi je samo atribut pesme, tako da lako možemo dobiti ime albuma kada se pojavi izabrana pesma. Da bi došli do tačnih naziva albuma, ja koristim nekoliko zgodnih trikova koristeći element i key() i generate-id() funkcije.

Drugi ljudi su objasnili korišćenje bolje od mene, ali ukrako, <xsl:key> pravi listu od <dict> tačaka koje predstavljaju pesme. <xsl:for-each> element , a zatim bira <dict> (id pesma) elemente u kojima se automacki generisan id poklapa sa automatski generisanim id prve tačke vraćen od strane "key()" funkcije. I meni se od svega ovoga vrti u glavi. Rezultujuća šema za dobijanje liste albuma (sa izvođačima) je:

<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>

Ipak ovaj način pravi problem kada su u pitanju kompilacije, uzeće ime izođača prve pesme u kompilaciji što je na neki načn jako čudno. Da bi rešili to ja koristim ‘Part of a Compilation’ fleg na sledeći način:

<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>

Razložio sam osnovni templejt (template) u posebne templejte za albume i izvođače – XSLT može biti težak za pregled u najboljem slučaju, pa ja volim da modulišem templejte kad god je to moguće.

Lista albuma grupisanih po žanru

Problem sa ovom listom je način na koji je muzika pomešana. Stvarno mi se ne dopada St Matthew Passion rame uz rame sa Stop Making Sense.

Da bi grupisali albume po žanru, koristim tehniku koju je osmislio Steve Muench, Oracle ‘XML Tehnički jevanđelist’, nazvana ‘Muenchian grouping’.

I opet, drugi su ovo otpisali bolje nego što bih ja mogao, tako da je dovoljno reći (ako želite, pokušajte da pratite operacije XSL) da koristi generisanje-id() " generate-id() " za dobijanje-id funkcije XSL ključeva koji defiinišu pesmu po žanru i albumu, i do tačke gde templejt albumsInGenre primenjen, trenutni kontekst je prva <dict> pesma unutar svakog žanra.

U sklopu albumsInGenre templejta, koristi se ista tehnika za dobijanje prve pesme sa svakog albuma, da bi dosli do imena albuma i izvođača. Takođe koristim dodatni predikt za dalje filtriranje ove tačke-podešene na žanr kao parametar za templejt.

(Život bi nam bio lakši sa XSLT 2.0 koji će imati za svaku grupu "for-each-group" element).

Šema izgleda ovako:

<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>

Dobijena lista albuma bi trebalo da izgleda ovako:

Electronica/Dance
  Boulevard St. Germain
  Dehli9 Tosca
  On Land Brian Eno
Jazz
  Madar Jan Garbarek
  The Hot Fives & Sevens  Louis Armstrong & The Hot Five

Ako želite albume samo na vašem iPod-u, umesto svih albuma u iTunes biblioteci, onda moramo dodati novi uslov u unutršnji-najviše za-svaki "inner-most for-each", tako da dobijemo:

    <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]">

Dodavanje vremena trajanjanja muzičkih numera

Pošto smo već daleko dogurali, želeo sam da vidim vreme trajanja svakog albuma. Trajanje svake numere je zabeleženo (u milisekundama) u odnosu na "Total Time key", tako da je templejt za to:

<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>

Opet, ako želite da vidite vreme trajanja na vašem iPod-u umesto u iTunes biblioteci (kada su samo pojedine numere izabrane za prebacivanje na iPod), Dodatni uslov mora biti pridodat na napravljen izbor za promenljivu t:

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

Sličan templejt će nam dati ukupno vreme trajanja muzike u iTunes biblioteci (ili na iPod-u)

<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>

Ukoliko želite informaciju o količini memorije koju će vam zauzeti muzika, možete doći do toga preko "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>

Otvaranje liste direktno u pretraživaaču

Umesto da koristimo specijalne XML/XSLT alate, ja otvaram listu direktno u pretraživaču. Da bi ste uradili to, kreirajte ‘wrapper’ XML fajl koji definiše koju šemu ćemo primeniti u kom XML fajlu, koristeći <?xml-stylesheet> za obradu instrukcija i <incl> element:

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

Nakon toga postavljmo templejt na vrh XSL šeme koji se poklapa sa <wrapper> elementom koji sam postavio na obuhvaćenom XML fajlu, i primenjuje šeme na tom XML fajlu.

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

Ovo bi trebalo da radi u bilo kom pretraživaču koji se trenutno koristi; Ja sam isprobao Internet Explorer (6.0+) i Firefox.

Napomena: Alternativni pristup bi bio pre-procesuiranje p-liste XML u pogodniju strukturu, kao što je objašnjeno na: www.xmldatabases.org/WK/blog/1086?t=item , i nakon toga je moguća jednostavnija manipulacija u XSLT. Ja sam spreman da živim sa preceding-sibling spojem kako bih bio u mogućnosti da generišem listu u jednom potezu.

Kada sve ovo stavimo u šemu ispod. To će biti jedna prilično velika šema, ali sa informacijama iznad, moćićete da je razložite i da koristite delove koje želite.

... Tako da sada imate brzu referencu liste albuma.

Da bi ste koristili ovu skriptu, u svojoj osnovnoj ili potpunoj formi, snimite albumList.xml i albumList.xsl kao što je prikazano ispod u vaš My Music/iTunes folder, i otvorite "albumList.xml" u pretraživaču (IE6 ili Firefox). Ukoliko želite da prilagodite raspored, pemenjujući smernice koje su prikzane iznad moćićete da mešate i spajate komponente po potrebi. Možete slobodno da upotrebljavate skripte bez ograničenja,i garancije na kvalitet i funkcionalnost [without any warranty express or implied] za ličnu upotrebu. Ukoliko imate pitanja ili naiđete na problem molim Vas da me kontaktirate .

  File albumList.xml (specifies to apply albumList.xsl to 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>

  File albumList.xsl basic version (lists name & artist of albums on iPod):

<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>

  File albumList.xsl full version (includes complete iTunes library with times & totals):

<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>




Published (Last edited): 27-11-2012 , source: http://www.movable-type.co.uk/scripts/itunes-albumlist.html