It didn’t take too long to notice that I had f–ked up the HTMLParser class a little bit. It was how entities was handled that didn’t really worked as expected – entities in tag attributes was duplicated and inserted in the tag content – but the good side of it is that I learned about the HTMLParser method of the HTMLParser Pike class. I only want to match entities in the data section – i.e. tag content – and not in attributes and the HTMLParser method tells you, as the name implies, in what context the entity is found. So my entity callback function now looks like:
6 lines of Pike
//! Entity callback
protectedvoid ecb(Parser.HTML p,string _data)
{
if(p->context()=="data")
line+= colorize(entify(_data),"entity");
}
which hopefully will be completely bug free now. So one down 854 to go
So I thought I should try to port my syntax highlighting script, Syntaxer, written in PHP to Pike. Mostly for the fun of it but also to improve my knowledge of string handling in Pike. The greatest concern here is that PHP is a dynamic language and Pike is not (in the same sense) and the PHP version of Syntaxer heavily depends on dynamic loading of PHP files. The reason for this is that I generate the “syntax maps” dynamically from syntax files of Edit+. That means that if you want support for a new language just drop a .stx file in the right location and there you go. My script will convert that into a static PHP file, so that the conversion only needs to be done once, and load that file on the fly when that particular language is requested.
I thought that this method would be hard to implement in Pike – although it might be possible – so I had to come up with a slightly different approach. Frankly; it’s not that often you alter the .stx files or implement support for new languages so my solution is to manually create definitions for what ever language. But I still use the .stx files from Edit+ although one needs to copy and paste bit.
In the Pike solution each language is its own class that inherits the master class .stx. The only thing you pretty much need to put in the derived class is some .stx, .stx and .stx that specify what is what in the language. For example, the C++ definition looks like this:
//| Override the default since # is no line comment in C++
protectedarray(string) linecomments =({"//"});
void create()
{
::create();
colors +=(["compiler":"#060"]);
styles +=(["compiler":({"<b>","</b>"})]);
}
And you really don’t need to make it more fancy than that. For most C-based languages the definitions in the master class .stx is enough. Just add the keywords to the .stx mapping and it looks better than nothing
HTML parser
One thing that differs from the PHP version of Syntaxer is that SGML-based, or tag based, languages will be run through a HTML-parser. The downside of the PHP version is that tag content will be highlighted as well, which of course isn’t what we want, but since Pike has a decent HTML parser that behaves like a SAX parser so I wrote a class, .stx, that uses that for highlighting tag based stuff. The .stx class also inherits .stx so the methods and members are the same.
I wonder why there’s no, built-in, HTML parser for PHP?
A Roxen tag module
Of course I had to write a Roxen tag module so that we can highlight source code in Roxen web pages. This was the reason for writing the Pike module at all. The tag is named .stx which might not be the most innovative name but what the heck! The beauty of it is that I made it possible, in the module settings tab, to create a surrounding HTML template for the output. When you run some code through the parser you get the highlighted source code as well as the name of the language and how many lines of code was highlighted and it might be nice to present that as well (just like the code blocks on this site). It’s tedious writing that surrounding HTML every time so now it’s just to put that in the settings and the code blocks will always look the same.
Finally
There’s some stuff left to do but the code works well enough to be usable. And I must say that the speed of the Pike version is like a thousand times faster than the PHP version!
Oh, and I have implemented support for the following language:
When you’r used to one programming language and start learning a new one you sometimes miss some features from the former. That happened to me a couple of years ago when we started using Roxen at work. What I missed was the trim functions from PHP. Sure, Pike and RXML can trim strings from whitespace but the beauty of trim, trim and trim in PHP is that you can trim characters as well as whitespace. And to my knowledge there’s no equivalent to trim and trim – that is only trim the left and right side respectively of the string.
So I wrote a Pike module that did that and also created an RXML tag of it. Just yesterday I rewrote that code since I do think I’ve become a better programmer and have come to learn Pike a lot better since my first try.
And I also wrote a method to shorten a string from the center and out, i.e. trim.
So here are some examples of usage:
12 lines of Pike
import.trim;
string path ="/this/is/a/path/";
write("%s\\n", rtrim(path,"/"));
// Will output: /this/is/a/path
write("%s\\n", trim(path,"/"));
// Will output: this/is/a/path
string long_str ="This is some string that's too long for us";
write("%s\\n", ctrim(long_str,20));
// Will output: This is...for us
And here’s the RXML implementation:
5 lines of RoXen Macro Language
<trim right="" char="/">/this/is/a/path/</trim>
<!-- /this/is/a/path -->
<trim center="" length="20">This is some string that's too long for us</trim>
It’s fascinating: I’ve been using Pike for little over two years now and I have never really understood the Pike data type “multiset”. A multiset is the keys in an associative array – or mapping as they are called in Pike, or hash in Perl, or HashTable in C# – with the values left out. So if you have a Pike mapping that looks like ([ "key1" : 1, "key2" : 2, "key3" : 3 ]) a multiset of that would look like ([ "key1" : 1, "key2" : 2, "key3" : 3 ]) and an array would be ([ "key1" : 1, "key2" : 2, "key3" : 3 ]). Mappings and arrays I have used a lot, of course, but it was quite recently it came to me what the multiset is good for!
Lets say you have a function that takes a string as argument and that argument can have like 12 different values but you only want some action to take place if the value is one of three out of the twelve possibilities. In many languages that could be written like this Pike example:
One time or another you come to the point, at least when you work as a web developer, when you need to generate PDFs on the fly. In principle this wouldn’t be too hard: pass the document you want to convert through a PDF printer. Well, it doesn’t sound that hard but talk is one thing and implementation another! I came to the point recently where I, or we, really needed to generate PDFs on the fly. The question of just converting one document to a PDF was not the only problem ahead: We also needed to alter the content, which is some tremendously complex content, of the PDF on the fly.
The workflow is as follows: A person submits a web based application through a web form. The commision is received by a handling officer who continues the application. The commision is sent back to the applier who in turn continues the application. The commision is once again sent to the handling officer who finish the application.
The application form, originally a Word document with loads of checkboxes, input fields and what not it’s the document at the top), at hand here is really, really complex and more or less industry standard. The pure complexity of the form is the reason why we wanted to digitalize it and make it into a more workflow like thing. Since the application form is more or less a standard we wanted the ability to see what the web based application would look like in reality if it was handled the old way through the Word document and of course give us the ability to archive the application in a constant format. And this is the type of document that people like to put in a binder so it needs to be printable and look like it always has looked like.
The first problem
So how do you go about and fill the Word document with values from a database, or how do you dynamically create a PDF that looks exactly like that complex Word document? Creating simple PDFs from a PDF API is not that hard but this really isn’t a simple structured document we’re talkning about so that didn’t sound likely. So I started looking at Adobes LiveCycle products but that would be really expensive (I mean really, really, really expensive) so we had to throw that idea away.
Then it hit me: We’re running Windows servers at work so couldn’t I build a COM based solution and use the COM interface for Word and in that way use the original Word document as a template and dynmialcally fill it with content from the database? I had never touched COM before but I thoght nothing is impossible!
The first solution
So I fired up Visual C# Express and began writing a console application that could be called from the web server. The first thing to solve was how to get all the data from the web server to the console application. Of course the console application could call the database directly but that would make the application hard to maintain, it would make it less dynamic and in general no good (and there are more documents to create and alter than the one I’ve mentioned). XML is no news to me so I though that passing an XML tree as an argument with what fields to fill and with what values would be a good idea. And since during the commision there are several types of documents to generate another argument would be what Word template to use.
So the question of how to get what information to fill the Word document with to the console application was easily solved (I love XPath).
The next question was how to use the Word COM interface, and frankly that was quite easy (thanks to Google). If you use bookmarks in the Word document you can loop over them with ease and just as easily fill them with content. So the principle I used was the following: The XML tree passed to the console application looks like this:
so the name attribute is correponding to the name of the bookmark/data field to alter and the node value is quite obvious the value to assign to the bookmark/data field. Text fields and checkboxes is delt with in different ways in the COM API so that’s why the name attribute.
When the bookmarks/data fields has been altered the document is saved in a temporary directory with a uniq name and the path to that document is returned by the console application.
Up to this point…
This is how things work so far:
The console application is called with two aruments:
What template to use
An XML tree with what bookmarks/data fields to fill with what values
The console application opens the template and fill it with data according to the XML tree passed as the second argument.
The console application saves the document with a uniq name in a temporary directory and the path to that document is returned by the console application.
Quite clear I’d say
The second problem
Okey, so now this application needed to be callable from a web page. And frankly that seemed like a minor problem, and so it was. The only minor concern was that the overall web based application workflow was (is) developed by Roxen as a consultant job so the implementaion of this little Word to PDF thing needed to be as simple as possible. So it was time to fire up JEdit and do some Pike hacking.
The second solution
Thanks to the fact that there’s a module in Roxen that can execute external applications and thanks to the fact that the source code of Roxen is available I borrowed some of the code written by Marcus Wellhardt at Roxen . I’m no wicked Pike expert like the dudes at Roxen so I borrowed the code from name that creates a background process and altered it slightly to fit my needs. On top of that I wrote a RXML tag that would be the wrapper to call to create the background process. The tag, a container tag, takes one required argument, the Word template to use, and one optional argument, the path to the directory where the template is stored (this argument is optional because it can be set globally in the module settings), and the XML tree with the bookmarks to fill as content. So the tag would be used like this (still, not all functionallity is there yet):
So I tried the tag and indeed a Word document with my given data was created in the temp directory on the server!
The third problem
Now it was time to create a PDF from the temporary Word document. I found an open source PDF printer software called PDFCreator and it looked promising so I went for it. PDFCreator can be installed in “server mode” which was what I wanted and thanks to this guide it was easy to set it up as a service as well.
First I wrote a standalone Pike script for debugging and testing that would kind of emulate a web page request (it’s some what tiresome developing new thing directly through Roxen since the module needs to be reloaded/re-compiled for every change you make to the code). Anyhow, things started to work pretty well: The script created a Word document by calling my console application, the script passed the path to that document to PDFCreator and MS Word was launched and a PDF was printed to the given temp directory. Yes! PDFCreator relies on the software the orginal document is associated with.
Okey, so the code worked fine so I altered my Roxen tag module so that my tag name would behave in the same manner as the standalone Pike script. But when I ran the tag through a web page request PDFCreator hung, or rather the MS Word process created by PDFCreator hung!
The third solution
It really puzzeled me: Everything went ok if I ran the process as my user through my standalone Pike script, but when the process was created by the web server (local system) MS Word got stuck. So I changed the server process to run as an ordinary user account, my user (local administrator) and now it worked perfectly. Then it hit me: maybe the MS Office applications relies on the directory name in name and the user name doesn’t have that (it’s no ordinary user). So it seems that to get MS office apps to run from another process, that process needs to have been created by a normal user account. I dunno, maybe there’s a way around this, like installing MS Office in some kind of server mode or something like that!
Anyhow, my little RXML tag name now did what I wanted and the result of the tag is the raw data of the created PDF, the temporary Word document and the PDF is wiped clean from the server, so we can just store the raw data in a database and deliver it easily to a user when the PDF is requested.
Word2pdf wrapped up
I’m quite satisfied with the solution I came up with: The console application I wrote (I call it WordWriter) seems to work like a charm and the fact that what data to fill the template with is passed as an XML tree makes it really easy to use for more than the specific purpose it was developed for. Plus, if the over all web application Roxen developed (is developing) is changed I don’t need to change the WordWriter application
Finally this is how this little name can be used in real life:
25 lines of RoXen Macro Language
<?comment
Here data is pulled from the database according to the
query="UPDATE table SET pdf1 = :pdf WHERE id = :id"
bindings="pdf=var.pdfdata, id=sql.id"
/>
And that should do the trick!
The synergy
At my job we’re like 1200 people and sometimes people want to create PDFs either to put on our web site or e-mail to someone and of course not all computers have Adobe Acrobat installed so wouldn’t it be nice to have a form on the intranet where people could upload a file and get it back as a PDF? Since most of the PDF generating functionality was already at hand I just had to write another RXML tag that just creates a PDF from a document. Said and done so now we have PDF generating form on the intranet.
Finally
This has been a really fun thing to do from which I have learned alot of new stuff, and that is probably why I love my work so much: There’s always new things to dive into.
I’ve released a new version of my generic syntax highlighting script Syntaxer. I fixed a potential bug where code generated on an operating system that only use \r to define a newline would be messed up (thank you jOOOL at PHPPortalen. Now I’ve bullet proofed (I hope) the way newlines are handled: I replace all \r\n with \r and then replace all \r with \n which means that we always end up with a single \n as the newline character.
A couple of years ago I wrote a generic syntax highlighting script. What I did was using the syntax files from Edit+ to determine how to parse a given language. All languages have different keywords, function names, delimiters and so on, and to know how to highlight a certain language you need to know these things. The Edit+ .stx files describes all these things.
Since I have gotten a few years more of knowledge, and PHP5 has arrived, I though I should write a new version of it. I could reuse some of the code but alot was rewritten and redesigned totally. The script has two classes:
One class to parse the Edit+ syntax files which gets converted into PHP files so that the syntax files doesn’t have to to be parsed for every request. If the given .stx file has a newer timestamp than the cached PHP file the PHP file will be regenerated. Alot of this code could be reused from the older version
The actual highlighting class. This class was almost entirely rewritten. Here I loop through every character of the code to highlight. When a keyword, delimiter or something else detectable is matched I grab that and searches forward to where the rule ends. In the older version I had a different approach where the code was splitted on newlines so I looped through line by line and for each line I looped throuh each charachter and did a similar match as in the new version.
The new approach has some advantages:
There’s no need to duplicate the code wich means a lot less memory is used.
Fewer flags is needed since when I match a detectable rule I at once search for the end of the rule. This means that fewer .stx statements is needed wich speeds thing up alot.
And foremost the code got much much cleaner!
Anyhow! There are a few minor bugs but the code is pretty usable (I have implemented it here in the blogging system). I added the scripts with documentation and a simple example on the server for anyone to download.
The Syntaxer2 can be found and downloaded over here. Some bug fixes and more examples will be done in the very near future.
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:
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';
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:
interfaceIDB
The connection interface
interfaceIDBResult
The query result interface
abstract classDB
The connection master class
abstract classDBResult
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.
$res=$db->Query("SELECT * FROM table WHERE cat_id = '%d'",$category);
if($res->NumRows()>0){
while($row=$res->Fetch()){
echo"Title: ".$row->title ."<br/>";
}
}
}
//! Could be DBDriverNotFound, DBConnectionFail, DBNotFound, DBQueryFail
catch(Exception $e){
die("Error: ".$e->getMessage();
}
//! SQLite example
try{
$category=$_GET['category'];
$db= DB::Create('SQLite','mysqlitedb');
$db->Connect();
$res=$db->Query("SELECT * FROM table WHERE cat_id = '%d'",$category);
if($res->NumRows()>0){
while($row=$res->Fetch()){
echo"Title: ".$row->title ."<br/>";
}
}
}
//! Could be DBDriverNotFound, DBConnectionFail, DBNotFound, DBQueryFail
catch(Exception $e){
die("Error: ".$e->getMessage();
}
?>
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.
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
application/x-gnumeric gnumeric
application/x-go-sgf sgf
application/x-graphing-calculator gcf
application/x-gtar gtar tgz taz
application/x-hdf hdf
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
$_MIMETYPES=array(
"mime/type"=>array("ext1","ext2","ext3")
);
When we’ve got this array populated we can easily look up a mimetype for an extention (and vice versa) by these two functions:
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
#!/usr/bin/php
<?php
/**
* mimetyper.php
* Turn the /etc/mime.types database into a PHP array
* @author Pontus Ostlund <spam@poppa.se>
*/
//! Mimetype file
$mime_db='/etc/mime.types';
//! Resulting file for the PHP array
$php_file='mimetypes.php';
fileexists($mimedb) or die("No mimetype database ($mime_db) found\\n");
$lines=@file($mimedb) or die("Couldn't read $mimedb");
$fh=@fopen($phpfile,"w+") or die("Couldn't create $phpfile");
$date=strftime("%A %B %d %Y",time());
//! Write the PHP header to the file.
@fwrite($fh,
"<?php
/
* Find mimetype for file extension
* Generated $date by ".basename(<strong>FILE</strong>)."
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
<?xmlversion="1.0"encoding="ISO-8859-1"?>
<discography>
<artist>
<name>Dream Theater</name>
<country>USA</country>
<album>
<title>Images and words</title>
<year>1992</year>
<tracks>8</tracks>
</album>
<album>
<title>Awake</title>
<year>1995</year>
<tracks>11</tracks>
</album>
</artist>
<artist>
<name>Dave Matthews Band</name>
<country>USA</country>
<album>
<title>Under the table and dreaming</title>
<year>1994</year>
<tracks>11</tracks>
</album>
<album>
<title>Crash</title>
<year>1996</year>
<tracks>12</tracks>
</album>
</artist>
</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:
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.
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:
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:
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:
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
<xsl:templatematch="discography">
<xsl:apply-templates/>
</xsl:template>
<xsl:templatematch="artist">
<h2>
<xsl:value-ofselect="name"/>
<xsl:text></xsl:text>
<small><xsl:value-ofselect="country"/></small>
</h2>
<ul><xsl:apply-templatesselect="album"/></ul>
</xsl:template>
<xsl:templatematch="album">
<li>
<strong><xsl:value-ofselect="title"/></strong>
<xsl:text></xsl:text>
<small>(<xsl:value-ofselect="year"/>)</small>
<br/>
<xsl:value-ofselect="tracks"/> tracks
</li>
</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.