Spring naar hoofdtekst

DOMDocument: Element vs. TextNode

Geplaatst op door ,
Laatste aanpassing op .

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:

<?php
/**
 * 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:

<?php
/**
 * 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 :-)

Inhoudsopgave

Klik op één van de onderstaande categorieën om de lijst met artikelen te filteren.