New Plogger version

Published 27 February, 2007 in Misc - 0 Comments

So, now I have worked some more on my Blogging engine Plogger (and I have just realized that there’s a web photo gallery named Plogger, so I will try to come up with a new name). I’ve added a few new features:

  • RSS feed generation
    Now we can subscribe to a latest entries feed as well as to a latest comments feed. The feeds are made in that way that I first generate an arbitrary XML tree that only contain the items and some meta information. I then pass this XML tree through and XSL template that generates the actual RSS feed. In this way it’s really simple to generate what version of RSS you want, and there would be no problems generating RDFs or what not.
  • Individual templating
    Now we can also assign different XSL-templates to individual entries. These temlplates must reside in the dynamic-templates directory. If there are templates in here they will occur in a drop down list in the “entry form”.
  • Enable/disable commenting
    If we don’t want a given entry to be commentable we just check a checkbox in the “entry form” and that’s that.

I set the version to 0.2 for this release.

The desktop client

I also added the desktop client to the SVN repository, and added the above functionality – individual templating and enableing/disableing of comments – to the client, wich means the SOAP class and the WSDL file got their share of more code.

Entry form with template selection and comment enableing/disableing

Entry form

What’s next?

Next step is adding some preview functionality, which wouldn’t be too difficult!

I’ve been thinking of adding support for TrackBacking and I will implement support for OpenID.

It seems like I have good fun ahead of me!

Handy functions

Published 19 February, 2007 in Programming - 0 Comments

How many times don’t you write code like this: $var = isset($_GET['var']) ? $_GET['var'] : 'default';? I read an article in Linux Format (the paper issue) about the up and comming PHP6 (well, it will probably take some time until it gets stable but you can already download it) and some of its new functions and stuff and learned that there will be a function called $var = isset($_GET['var']) ? $_GET['var'] : 'default';. This function will work like this:
$var = isset($_GET['var']) ? $_GET['var'] : 'default';. Pretty much nicer than the old way!

So I though: How hard can it be to implement the same kind of functionality in a user defined function. Well, it’s not hard att all, and here’s how simple it is:

22 lines of PHP
  1. /**
  2. * issetor
  3. *
  4. * @param mixed &$what Assoc array key
  5. * @param mixed $else Default value
  6. * @return mixed
  7. */
  8. function issetor(&$what, $else)
  9. {
  10. return isset($what) ? $what : $else;
  11. }
  12. #=================================================
  13. $arr = array(‘key1’ => ‘value1’, ‘key2’ => 1);
  14. $var1 = issetor($arr[‘key1’], ‘some value’);
  15. echo $var1; # will echo: value1
  16. $var2 = issetor($arr[‘key5’], ‘default for key 5’);
  17. # will echo: default for key 5
  18. echo $var2;

The only thing noticable here is that the first argument needs to be passed by reference or PHP will throw a warning (depending on the error/warning level set).

Of cource you can escape this issue all together just by choking PHP errors/warnings – $var = isset($_GET['var']) ? $_GET['var'] : 'default'; – but that’s just pure ugly in my opinion.

Just by modifying the function above slightly we also get a nice way for writing:
$var = isset($_GET['var']) ? $_GET['var'] : 'default';

23 lines of PHP
  1. /**
  2. * isemptyor
  3. *
  4. * @param mixed &$what Assoc array key
  5. * @param mixed $else
  6. * @return mixed
  7. */
  8. function isemptyor(&$what, $else)
  9. {
  10. return empty($what) ? $else : $what;
  11. }
  12. #=================================================
  13. $var1 = “Hello”;
  14. $var2 = “”;
  15. $myvar = isemptyor($var1, ‘Default’);
  16. echo $myvar; # will echo: Hello
  17. $myvar = isemptyor($var2, ‘Default’);
  18. # will echo: Default
  19. echo $myvar;

And that’s it. I use them all the time!

Database abstraction layer

Published 17 February, 2007 in Programming - 0 Comments
These classes is part of PLib

I can’t remember the last time I made a web site that wasn’t hooked up to a database in one way or another! There’s nothing wrong with using the database functions that PHP provide directly but if you work with different types of databases – MySQL, Postgres, Oracle, SQL Server, SQLite and so on – you need to remeber how the functions for these databases work, in what order to pass arguments and what not. And maybe you write an application that teoretically could use an abritrary database and not MySQL that you developed the application for, and then it would be nice to use the same function calls no matter what database is being used.

This is where the database abstraction layer comes in handy. There already exist many of them so, again and as always, why bother writing another one? Well, it’s fun and you always learn new stuff and, for good and bad, third party APIs tend to be packed with features that you never use so it get quite tedious learning the API.

So I wrote my own database abstraction layer that only contain the functionality I mostly use. If I need more functionaly it’s easy to implement. When I wrote the abstraction layer I tried to evolve my OOP (object orientated programming) skills (which isn’t too great) so that in it self was I goal.

The abstraction layer contains of two interfaces and two master classes: A connection interface and class and a result interface and class. To create a driver for a new type of database you inherit the master classes and implement the interfaces. This is how the skelleton looks like:

  • interface IDB
    The connection interface
  • interface IDBResult
    The query result interface
  • abstract class DB
    The connection master class
  • abstract class DBResult
    The query result master class

So a database driver should have two classes: One that extends the DB class and implements the IDB interface and one class that extends the DBResult class and implements the IDBResult interface.

Example

Here’s a simple example of usage:

41 lines of PHP
  1. <?php
  2. require_once ‘Database.php’;
  3. //! MySQL example
  4. try {
  5. $category = $_GET[‘category’];
  6. $db = DB::Create(‘MySQL’, ‘localhost’, ‘uname’, ‘pw0rd’, ‘mydatabase’);
  7. $db>Connect();
  8. $res = $db>Query(“SELECT * FROM table WHERE cat_id = ‘%d'”, $category);
  9. if ($res>NumRows() > 0) {
  10. while ($row = $res>Fetch()) {
  11. echo “Title: “ . $row>title . “<br/>”;
  12. }
  13. }
  14. }
  15. //! Could be DBDriverNotFound, DBConnectionFail, DBNotFound, DBQueryFail
  16. catch (Exception $e) {
  17. die(“Error: “ . $e>getMessage();
  18. }
  19. //! SQLite example
  20. try {
  21. $category = $_GET[‘category’];
  22. $db = DB::Create(‘SQLite’, ‘mysqlitedb’);
  23. $db>Connect();
  24. $res = $db>Query(“SELECT * FROM table WHERE cat_id = ‘%d'”, $category);
  25. if ($res>NumRows() > 0) {
  26. while ($row = $res>Fetch()) {
  27. echo “Title: “ . $row>title . “<br/>”;
  28. }
  29. }
  30. }
  31. //! Could be DBDriverNotFound, DBConnectionFail, DBNotFound, DBQueryFail
  32. catch (Exception $e) {
  33. die(“Error: “ . $e>getMessage();
  34. }
  35. ?>

As you can see here we’re using exactly the same code, except for the instantiation of the database object, wether we’re using MySQL or SQLite, and that’s the meaning of the database abstraction layer.

Sources

I currently have two implementations for MySQL and SQLite. The MySQL implementation is what I’m using for this blog and the SQLite implementation is not very well tested yet.

The classes are pretty well documented and the documentation is bundled in the package below.

Database abstraction layer 17:31, Sat 17 October 2009 :: 42.1 kB

Get mimetype for a file

Published 17 February, 2007 in Programming - 0 Comments

Sometimes you need/want to find out what mimetype a particular file has. If you have access to the PEAR “Fileinfo Functions” you can use them, but if you don’t here’s a simple solution that might be helpful.

NOTE! This is not as reliable as the Fileinfo Functions but it works in most cases.

In most GNU/Linux systems (if not all) there’s a file called mime.types (in /etc/) which is mapping of mimetype -> extension. The file looks like this:

6 lines of Plain text
  1. application/x-gnumeric gnumeric
  2. application/x-go-sgf sgf
  3. application/x-graphing-calculator gcf
  4. application/x-gtar gtar tgz taz
  5. application/x-hdf hdf
  6. application/x-httpd-php phtml pht php

Quite obvious you could build an array from this file where the mimetype could be the key and the extension(s) the value as an array:

3 lines of PHP
  1. $_MIMETYPES = array(
  2. “mime/type” => array(“ext1”, “ext2”, “ext3”)
  3. );

When we’ve got this array populated we can easily look up a mimetype for an extention (and vice versa) by these two functions:

26 lines of PHP
  1. /**
  2. * Find mimetype for extension
  3. * @global array $_MIMETYPES
  4. * @param string $ext
  5. * @return string|bool
  6. */
  7. function find_mimetype($ext)
  8. {
  9. global $_MIMETYPES;
  10. foreach ($_MIMETYPES as $mime => $exts)
  11. if (in_array($ext, $exts))
  12. return $mime;
  13. return false;
  14. }
  15. /**
  16. * Get extension(s) for a given mimetype
  17. * @global array $_MIMETYPES
  18. * @param string $mimetype
  19. * @return array
  20. */
  21. function findextensionfor_mimetype($mimetype)
  22. {
  23. global $_MIMETYPES;
  24. return isset($MIMETYPES[$mimetype]) ? $MIMETYPES[$mimetype] : array();
  25. }

The script to convert the mime.types database into an array an auto generate the two functions above looks like this (note that this PHP script is intended to run as a command line (cli) script, thus the shebang (#!/usr/bin/php), since we don’t need to do this conversion run time but only once):

86 lines of PHP
  1. #!/usr/bin/php
  2. <?php
  3. /**
  4. * mimetyper.php
  5. * Turn the /etc/mime.types database into a PHP array
  6. * @author Pontus Ostlund <spam@poppa.se>
  7. */
  8. //! Mimetype file
  9. $mime_db = ‘/etc/mime.types’;
  10. //! Resulting file for the PHP array
  11. $php_file = ‘mimetypes.php’;
  12. fileexists($mimedb) or die(“No mimetype database ($mime_db) found\\n”);
  13. $lines = @file($mimedb) or die(“Couldn’t read $mimedb”);
  14. $fh = @fopen($phpfile, “w+”) or die(“Couldn’t create $phpfile”);
  15. $date = strftime(“%A %B %d %Y”, time());
  16. //! Write the PHP header to the file.
  17. @fwrite($fh,
  18. “<?php
  19. /
  20. * Find mimetype for file extension
  21. * Generated $date by “ . basename(<strong>FILE</strong>) .
  22. */
  23. $_MIMETYPES = array(n”
  24. );
  25. //! Loop through the lines array
  26. while ($line = array_shift($lines))
  27. {
  28. //! Match a comment or a blank line
  29. if ($line{0} == “#” || $line{0} == “\\n”)
  30. continue;
  31. //! A line looks like:
  32. //! mime.type extention[ extension[ extension]]
  33. //! so lets grab the mimetype and extensions
  34. preg_match(‘/^(.?)\\s+(.?)$/’, trim($line), $match);
  35. list(, $mime, $ext) = $match;
  36. if (empty($mime)) continue;
  37. //! This converts “ext ext ext” to “ext’,’ext’,’ext”
  38. $ext = join(“‘, ‘”, explode(‘ ‘, $ext));
  39. //! And lets write the new array index to the file
  40. fwrite($fh, sprintf(
  41. “\\t’%s’ => array(‘%s’)%s\\n”,
  42. $mime, $ext, (empty($lines) ? “” : “,”))
  43. );
  44. }
  45. //! Close the array, add a nifty search function, and close the PHP block
  46. //! and we’re done!
  47. fwrite($fh, “);
  48. /**
  49. * Find mimetype for extension
  50. * @global array $_MIMETYPES
  51. * @param string $ext
  52. * @return string|bool
  53. */
  54. function find_mimetype($ext)
  55. {
  56. global $_MIMETYPES;
  57. foreach ($_MIMETYPES as $mime => $exts)
  58. if (in_array($ext, $exts))
  59. return $mime;

  60. return false;
  61. }
  62. /**
  63. * Get extension(s) for a given mimetype
  64. * @global array $_MIMETYPES
  65. * @param string $mimetype
  66. * @return array
  67. */
  68. function findextensionfor_mimetype($mimetype)
  69. {
  70. global $_MIMETYPES;
  71. return isset($MIMETYPES[$mimetype]) ? $MIMETYPES[$mimetype] : array();
  72. }
  73. ?>”);
  74. fclose($fh);
  75. ?>

And this should result in a file called mimetypes.php that you just can include in you PHP scripts.

Download the scripts 17:31, Sat 17 October 2009 :: 4.5 kB

New site, new blog

Published 11 February, 2007 in Misc - 0 Comments

Plogger, the badly named blogging system

So, I’ve been working on this blogging system for a while and now it’s completed enough to run. One can wonder why one would write a blogging system when there already exist a zillion of them? Well I guess it’s the callenge that make it worth the effort, or that it’s just great fun programming! And there’s always new things to learn, or new methods for how to do things and so on.

Anyhow, the system is still a bit rough around the edges so I wont share it just yet, but as soon as I’m satisfied I will release it under the GPL license.

Here’s a little summary of the project:

29 lines of Plain text
  1. ———————————————–
  2. Plogger PROJECT SUMMARY
  3. ———————————————–
  4. ent: 3 st
  5. xml: 1 st
  6. jpg: 3 st
  7. stx: 25 st
  8. swp: 1 st
  9. cache: 1 st
  10. text: 1 st
  11. php: 51 st
  12. css: 10 st
  13. ini: 1 st
  14. gif: 3 st
  15. txt: 3 st
  16. xsl: 20 st
  17. pdf: 1 st
  18. gz: 1 st
  19. wsdl: 1 st
  20. odt: 1 st
  21. dtd: 2 st
  22. js: 8 st
  23. png: 206 st
  24. unknown: 10 st
  25. ———————————————–
  26. Number of files: 353 st
  27. Directories: 39 st
  28. Lines of code: 19030 st
  29. ———————————————–

SOAP enabled

During the last months I have begun looking into C# so I thought a good project to start off with was to write a desktopk client for my blogging system. I also wanted to explore the SOAP protocol a little bit more, I’m up to some SOAP stuff at work soon, so I developed a SOAP API for the blogging system that I could use for the client.

So far the client is starting to work pretty well and you can create new blog post, edit blog post, delete blog posts and handle comments. Since I mostly use GNU/Linux at home the client runs on the Mono platform and uses GTK# for the GUI. My goal is also to port the client to Windows and Mac when I’m satisfied with the GNU/Linux version.

Here’s some screen dumps of the client (click on the images for larger versions):

Entry list

Entry list dialog

Settings

Settings dialog

Entry form

Entry dialog

Handle comments

Comments dialog

Don’t be hassled by the mix of Swedish and English in the interface. GTK Stock buttons get their text from the surrounding environment and thus gets translated automatically.

The client is pretty stable but there’s some exception handling – and function implemetation – left to do, but that will come with time and along with using the client.

Templating

Since I really love XSL that’s what I’m using for the templating system for the blog system. I will give you a short example of why I love XSL. All external links on this site has a little icon appended to the link text. That little feature is handled by this code:

9 lines of XSL
  1. <xsl:template match=“a[contains(@href, ‘http://’)]”>
  2. <a>
  3. <xsl:copy-of select=“@*” />
  4. <xsl:copy-of select=“node()”/>
  5. <img src=“{$tmpl.path}/img/a-external.png” width=“12” height=“9”
  6. alt=“External URL”
  7. />
  8. </a>
  9. </xsl:template>

This template matches all <a> tags where the arrtibute href contains http:// and append the <img> tag after the link text. Now isn’t that lovely so say!

That’s that for now. When I’ve cleaned up the code a little bit I will release it for those curious.

Howto install Zend Studio on Linux with an AMD64 processor

Published 7 January, 2007 in Linux - 0 Comments

Sometimes you regret running a 64-bit processor, but as almost always anything ca be fixed. The Zend Studio installer complains about finding some lib files:

8 lines of Plain text
  1. Configuring the installer for this system’s environment…
  2. nawk: error while loading shared libraries: libm.so.6: cannot open shared object file: No such file or directory
  3. dirname: error while loading shared libraries: libc.so.6: cannot open shared object file: No such file or directory
  4. /bin/ls: error while loading shared libraries: librt.so.1: cannot open shared object file: No such file or directory
  5. basename: error while loading shared libraries: libc.so.6: cannot open shared object file: No such file or directory
  6. dirname: error while loading shared libraries: libc.so.6: cannot open shared object file: No such file or directory
  7. basename: error while loading shared libraries: libc.so.6: cannot open shared object file: No such file or directory
  8. hostname: error while loading shared libraries: libc.so.6: cannot open shared object file: No such file or directory

Of course there’s a fix for this, and this is how to do it (please change the version numbers according to your version):

8 lines of Bash
  1. # First make a copy of the installer file:
  2. cp ZendStudio-5_2_0.bin ZendStudio-5_2_0.bak
  3. # Then run this command
  4. cat ZendStudio-5_2_0.bak | sed “s/export LD_ASSUME_KERNEL/#xport LD_ASSUME_KERNEL/” > ZendStudio-5_2_0.bin
  5. #Then you can throw the .bak file away
  6. rm ZendStudio-5_2_0.bak

Hopefully you can now run the installer file.

Okey, when Zend Studio has been installed we need to redo these steps but for the executable ZDE file which is located in the bin directory of the directory where ZDE was installed. I installed it in my home directory so the path for me is:

1 lines of Plain text
  1. /home/poppa/ZendStudio-5.2.0/bin/ZDE

Okey, so lets go to that directory and make a copy of ZDE and then:

1 lines of Bash
  1. cat ZDE.bak | sed “s/export LD_ASSUME_KERNEL/#xport LD_ASSUME_KERNEL/” > ZDE

And now we’re fit to go (hopefully)!

If you by any chance during any of these steps get complaints about the xxx/bin/java not being found it can be fixed by explicitly declaring the path to Java:

5 lines of Bash
  1. # For the installer
  2. ./ ZendStudio-5.2.0.bin LAX_VM=/path/to/java
  3. # Or for the executable
  4. ./ZDE LAX_VM=/path/to/java

And that’s that!

Upgraded computer

Published 6 January, 2007 in Misc - 0 Comments

Since I became a Mac user (and an Apple Cinema Display owner) my old PC has just been collecting dust since the new monitor has DVI connections and I had no DVI support on my old video card. So I thought: lets buy a new video card and put the old working horse back in business!

Well, stupid as I am I didn’t check what interface there was on my old mother board so I ended up with a video card with PCI Express and a mother board with AGP! Smart ass (I’m a hardware lamer indeed)! I couldn’t bother going back to the store to return the video card so I turned to the Internet and bought a new mother board and a new processor. And this time I really checked that the new mother board would support my old memory sticks. At least I thought so! When the stuff arrived the memory sticks didn’t fit, of course!

So back to the Internet for some new memory sticks! Okey dokey, the sticks arrived and I was fit to go putting the stuff together. Everything went smoothly until I was to connect the power supply to the mother board! It didn’t fit, surprise! So I went downtown and bought a new power supply unit and finally everything fit together.

And while I was at it I bought a new hard drive and a DVD/CD burner. So from just planning on buying a new video card I ended up with a brand new computer. It wasn’t like it wasn’t needed so all in all it was a rather good thing (although a lot more expensive than what I had in mind).

So what did I end up with?

  • Mother board: ASUS A8N5X with NVIDIA nForce 4 chipset.
  • Processor: AMD64 Athlon 3500+
  • Video card: GeForce 7300 GS with 256mb DDR2.
  • Memory: 2x Kingston KVR 400 512mb
  • Hard drive: WD 120gb 7200
  • DVD/CD burner: Samsung S182D
  • And of course a brand new installation of Ubuntu Linux

And all in all I have to say I’m quite satisfied. When logging in to Gnome it takes like 4 seconds for the desktop to load and become usable! Firefox launches almost instantaneously and the speed is in general light years ahead of my old hardware!

Nice!

The power of XSL (pt.1)

Published 26 December, 2006 in Programming , Tutorials , Xsl/Xslt - 0 Comments

I’m a lucky bastard! At work we use the beautiful Roxen CMS which utilize XSL for the entire templating system. I was a little bit familiar with XSL before we got Roxen CMS but I hadn’t really understood the power of it. It didn’t take long before I really started to love XSL.

My aim with this tutorial series is to shed some light on why I think XSL is so great and I hope that who ever reads this will get a more pleasant future (web development-wise that is).

I would like to make a note: I will not go through the fundamentals of XSL and XML here. I presume that some knowledge and preconception of programming and structure of XML already is at hand.

The X in XSL

As you already know, or have come to understand, XSL is used for styling or formatting, or parsing if you like, XML. XML is beautiful in that way that it is platform and device independent, standardized and read by virtually any programming language. XML data can be parsed, styled, used, displayed and so on in virtually any way and by any technique you can imagine.

So if any programming language can parse XML why do we need XSL? Well, the answer is quite simple: What if you by any reason have to implement your solution in a different programming language? You would have to rewrite everything and that’s not to fun. If you use XSL you can, as long as the language your moving to have an XSLT engine. So you could say that your solution becomes far more portable if it’s utilizing XSL.

Let’s begin with some code

Enough with the b s. In the first few examples we will use no programming language, the XSL transformation will be handled by an ordinary web browser. Both Gecko-based browsers (Mozilla*) and Internet Explorer handles client side XSL transformation. So we will only need an XML document and an XSL style sheet.

The XML

Here comes the XML we will start off with. I’ll use the classic discography example:

31 lines of XML
  1. <?xml version=“1.0” encoding=“ISO-8859-1”?>
  2. <discography>
  3. <artist>
  4. <name>Dream Theater</name>
  5. <country>USA</country>
  6. <album>
  7. <title>Images and words</title>
  8. <year>1992</year>
  9. <tracks>8</tracks>
  10. </album>
  11. <album>
  12. <title>Awake</title>
  13. <year>1995</year>
  14. <tracks>11</tracks>
  15. </album>
  16. </artist>
  17. <artist>
  18. <name>Dave Matthews Band</name>
  19. <country>USA</country>
  20. <album>
  21. <title>Under the table and dreaming</title>
  22. <year>1994</year>
  23. <tracks>11</tracks>
  24. </album>
  25. <album>
  26. <title>Crash</title>
  27. <year>1996</year>
  28. <tracks>12</tracks>
  29. </album>
  30. </artist>
  31. </discography>

Okey, there’s nothing strange about this. We have a structure with a depth of three nodes: discography --> artist --> album.

The XSL

First lets build an XSL skeleton. This is how we’ll start every XSL document:

4 lines of XSL
  1. <?xml version=“1.0” encoding=“ISO-8859-1”?>
  2. <xsl:stylesheet version=“1.0” xmlns:xsl=“http://www.w3.org/1999/XSL/Transform”>
  3. <!– We’ll insert our XSL code here –>
  4. </xsl:stylesheet>

Every XSL shall contain the xsl:stylesheet definition. The xmlns:xsl defines what name space we’ll be using, in this case the official one from W3C. We also say that we’ll be using version 1 of XSL transform.

This won’t do much for us. Let’s pop some more stuff in there. You can transform an XML document into a variety of other document formats; HTML, XML, Text and so on. Which format to output is done with the top level instruction xsl:output. Since this will be our main template we also need to write an instruction that will match the root node of our XML document (that is the discography node). To match a node we use xsl:template with the attribute match.

11 lines of XSL
  1. <?xml version=“1.0” encoding=“ISO-8859-1”?>
  2. <xsl:stylesheet version=“1.0” xmlns:xsl=“http://www.w3.org/1999/XSL/Transform”>
  3. <!– Output as HTML –>
  4. <xsl:output method=“html”/>
  5. <!– Match the root of our XML document –>
  6. <xsl:template match=“/”>
  7. <!– – This is where we’ll start our HTML code —>
  8. <h1>Code goes here</h1>
  9. </xsl:template>
  10. </xsl:stylesheet>

Before we do an initial test we need to assign the XSL to the XML. This is done in a similar way as assigning a CSS to an HTML document (). To assign an XSL to an XML we need to “import” the XSL in the XML:

5 lines of XML
  1. <?xml version=“1.0” encoding=“ISO-8859-1”?>
  2. <?xml-stylesheet type=“text/xsl” href=“the-xsl.xsl”?>
  3. <discography>
  4. <!– … –>
  5. </discography>

Okey, so lets save the XML as discography.xml and the XSL as discography.xsl in a folder and load the XML in our browser. The result should look something like below:

Example 1

Game, set xsl:template match

Okey, the first output here is not that exciting, so lets move on to some more interesting code. Let us put some HTML in our XSL template so we can see the basic structure:

18 lines of XSL
  1. <?xml version=“1.0” encoding=“ISO-8859-1”?>
  2. <xsl:stylesheet version=“1.0” xmlns:xsl=“http://www.w3.org/1999/XSL/Transform”>
  3. <!– Output as HTML –>
  4. <xsl:output method=“html”/>
  5. <!– Match the root of our XML document –>
  6. <xsl:template match=“/”>
  7. <html>
  8. <head>
  9. <title>My CD collection</title>
  10. </head>
  11. <body>
  12. <h1>My CD Collection</h1>
  13. <xsl:apply-templates/>
  14. </body>
  15. </html>
  16. </xsl:template>
  17. </xsl:stylesheet>

Except for the basic HTML here the one interesting thing to note here is the instruction xsl:apply-templates. In a way we’re gonna loop (not in the way as ordinary loops in regular programming languages, although you can do that too in XSL, but that I’ll leave for now) through the XML document and you can say that xsl:apply-templates just passes on the XML data to the next matching rule. Remember: we have xsl:template match="/" which matches the root of our XML document. Now we’re gonna write some more xsl:template match="..." instructions that will match the root’s child nodes.

We have the following structure: discography --> artist --> album. So we’ll write one xsl:template match="..." for each one of them. I’ll leave out the XSL skeleton now just for space reasons:

22 lines of XSL
  1. <xsl:template match=“discography”>
  2. <xsl:apply-templates/>
  3. </xsl:template>
  4. <xsl:template match=“artist”>
  5. <h2>
  6. <xsl:value-of select=“name”/>
  7. <xsl:text> </xsl:text>
  8. <small><xsl:value-of select=“country”/></small>
  9. </h2>
  10. <ul><xsl:apply-templates select=“album”/></ul>
  11. </xsl:template>
  12. <xsl:template match=“album”>
  13. <li>
  14. <strong><xsl:value-of select=“title”/></strong>
  15. <xsl:text> </xsl:text>
  16. <small>(<xsl:value-of select=“year”/>)</small>
  17. <br/>
  18. <xsl:value-of select=“tracks”/> tracks
  19. </li>
  20. </xsl:template>

So what’s happening here is that we first match the discography node and just continue applying templates. It may seem unnecessary to have this template since all we do is continue applying templates but if we had left this template out the discography node from the XML document would be output in our resulting HTML document. (There’s other ways to handle this but that’s out of the scope for now).

The xsl:apply-templates in the discography template will move us on to the artist template. The artist node have a child node named name. The value of this node we output with .

To output whitespace in XSL we use and that’s what we’re doing to get a space between the value of the name node and the value of the country node. Now we’re done with the artist node so let’s move on to the album node by xsl:apply-templates. Note that I’ve added select="album" in the xsl:apply-templates here. The reason for this that when ever XSL find nodes it can not find a template for it just outputs that node into the result. If you remove the select="album" you’ll see that the artist name and country will be output twice. To prevent this we specify that we want to apply-templates for the artist node and thus we catch that node there and we’ll be fine. There are several ways to fix this but that’s a more advanced action so I leave that out for now.

There’s nothing new in the album template so we need no further explanation there.

The entire XSL

41 lines of XSL
  1. <?xml version=“1.0” encoding=“ISO-8859-1”?>
  2. <xsl:stylesheet version=“1.0” xmlns:xsl=“http://www.w3.org/1999/XSL/Transform”>
  3. <!– Output as HTML –>
  4. <xsl:output method=“html”/>
  5. <!– Match the root of our XML document –>
  6. <xsl:template match=“/”>
  7. <html>
  8. <head>
  9. <title>My CD collection</title>
  10. </head>
  11. <body>
  12. <h1>My CD Collection</h1>
  13. <xsl:apply-templates/>
  14. </body>
  15. </html>
  16. </xsl:template>
  17. <xsl:template match=“discography”>
  18. <xsl:apply-templates/>
  19. </xsl:template>
  20. <xsl:template match=“artist”>
  21. <h2>
  22. <xsl:value-of select=“name”/>
  23. <xsl:text> </xsl:text>
  24. <small><xsl:value-of select=“country”/></small>
  25. </h2>
  26. <ul><xsl:apply-templates select=“album”/></ul>
  27. </xsl:template>
  28. <xsl:template match=“album”>
  29. <li>
  30. <strong><xsl:value-of select=“title”/></strong>
  31. <xsl:text> </xsl:text>
  32. <small>(<xsl:value-of select=“year”/>)</small>
  33. <br/>
  34. <xsl:value-of select=“tracks”/> tracks
  35. </li>
  36. </xsl:template>
  37. </xsl:stylesheet>

The result of this should look something like below:

Example 2

And that’s that for this time…

1 5 6 7