Inleiding
Bij de bouw van dit weblog koos ik voor XML als opslagformaat voor de metadata van alle blogposts. Later volgde ook een commandline beheergedeelte in PHP. Sinds de lancering schreef ik bijna dagelijks een nieuw artikel. Zo duurde het niet lang voordat er een interessante programmeerfout aan het licht kwam.
Symptoom
Ik voerde via het beheergedeelte een nieuw artikel in. Ik koos als titel
"Digitale studio met Ardour & friends", maar wat gebeurde er? In de metadata was
de XML-node <title />
leeg! Ergens in het opslaan of wegschrijven van de XML
ging iets fout, en ik ging op zoek in de broncode van mijn weblog.
Diagnose
De code die de metadata van een BlogPost wegschrijft bevond zich in de functie
toXML()
. Ik beperk het voorbeeld hieronder tot één veld; $this->_title
.
<?php
/**
* Returns this post's metadata as XML
*
* @return string
*/
public function toXML()
{
$doc = new \DOMDocument();
// ...
$root = $doc->createElement('post');
// ...
$nodeTitle = $doc->createElement('title', $this->_title);
$root->appendChild($nodeTitle);
// ...
return $doc->saveXML();
}
Met een print_r()
vlak na de aanroep van createElement()
stelde ik vast dat
hier de fout zat: "unterminated entity reference". De functie struikelde over de
ampersand (&) in de titel van de blogpost. Gelukkig waren er meer mensen met
dezelfde problematiek. Zoveel zelfs dat de ontwikkelaars van PHP dit in hun
documentatie hebben opgenomen.
Oplossing
In diezelfde documentatie werd verwezen naar een functie die wel met
ampersands overweg kan: createTextNode()
. Ik maakte een statische functie
waarmee ik met één regel code een nieuwe node kon toevoegen. Hier gebruikte ik
dan de nieuwe methode:
/**
* Adds the given value as an XML tag to the given root
*
* @param \DOMDocument &$doc the XML document
* @param \DOMElement &$root the root to add the newly created node to
* @param string $element the tag's name
* @param string $value the tag's value (will be escaped)
*
* @return void
*/
private static function _addEscaped(
\DOMDocument &$doc, \DOMElement &$root, $element, $value
) {
$node = $doc->createElement($element);
$cont = $doc->createTextNode($value);
$node->appendChild($cont);
$root->appendChild($node);
return;
}
Het aanroepen van deze hulpfunctie is daarna heel eenvoudig en elegant:
/**
* Returns this post's metadata as XML
*
* @return string
*/
public function toXML()
{
$doc = new \DOMDocument();
// ...
$root = $doc->createElement('post');
// ...
self::_addEscaped($doc, $root, 'title', $this->_title);
// ...
return $doc->saveXML();
}
Nu wordt elke waarde van de XML metadata correct geconverteerd op het moment van toevoegen aan het document. Vanaf dat moment kon ik eindelijk beginnen aan het artikel over Ardour & friends :-)