venerdì, agosto 30, 2013

PHP - Analisi di un file XML


Questo post fa parte di una serie preparata qualche anno fa per delle lezioni su PHP.

Feed Atom come esempio di file XML

Prefiggiamoci come scopo quello di elaborare, per uso strettamente personale, un feed Atom come quello dell'ANSA relativo al Friuli-Venezia Giulia.

La struttura del file è la seguente:

<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<atom:link rel="self" type="application/rss+xml" href="http://www.ansa.it/web/notizie/regioni/friuliveneziagiulia/friuliveneziagiulia_rss.xml"></atom:link>
<title>News di 01. Friuli Venezia Giulia - ANSA.it</title>
<link>http://www.ansa.it/web/notizie/regioni/friuliveneziagiulia/friuliveneziagiulia.shtml</link><description>Updated every day - FOR PERSONAL USE ONLY</description>
<language>it</language>
<copyright>Copyright: (C) ANSA, http://www.ansa.it/web/static/disclaimer.html</copyright>
<item>
  <title><![CDATA[Mostre:Enzensberger...]]></title>
<description]]></title>

  <description><![CDATA[Per avvicinare...]]></description>
  <link>http://www.ansa.it/web/notizie/regioni/friuliveneziagiulia/2010/03/14/visualizza_new.html_1733110800.html</link>
  <pubDate>14 Mar 2010 18:44:00 +0100</pubDate>
  <guid>http://www.ansa.it/web/notizie/regioni/friuliveneziagiulia/2010/03/14/visualizza_new.html_1733110800.html</guid>
</item>
<item>
  <title><![CDATA[Regioni: sanita', in...]]></title>
  <description><![CDATA[Nel 2008 Molise...]]></description>
  <link>http://www.ansa.it/web/notizie/regioni/friuliveneziagiulia/2010/03/12/visualizza_new.html_1732935087.html</link>
  <pubDate>12 Mar 2010 18:47:00 +0100</pubDate>
  <guid>http://www.ansa.it/web/notizie/regioni/friuliveneziagiulia/2010/03/12/visualizza_new.html_1732935087.html</guid>
</item>
</channel>
</rss>

Come si vede, esiste una parte introduttiva, con il nome della testata, le informazioni sul copyright, ecc., seguita da una serie di articoli (item), per i quali viene riportato il titolo, una descrizione, un link, la data di pubblicazione e un identificativo univoco della risorsa (guid).

Prima versione: lettura semplice con simpleXml

In una prima versione, per capire se tutto funziona, possiamo scrivere codice di questo genere:

<pre>
<?php
$feed="http://www.ansa.it/web/notizie/regioni/friuliveneziagiulia/friuliveneziagiulia_rss.xml";
$xml=simplexml_load_file($feed);
echo "Title: " . chop($xml->channel->title) . "\n"; 
foreach ($xml->channel->item as $item)
{
  echo "title: " . $item->title . "\n";
  echo "  description: " . $item->description . "\n";
  echo "  link: " . $item->link . "\n";
}

che ci consente di ottenere:
Title: News di 01. Friuli Venezia Giulia - ANSA.it
title: Mostre:Enzensberger...
  description: Per avvicinare...
  link: http://www.ansa.it/web/notizie/regioni/friuliveneziagiulia/2010/03/14/visualizza_new.html_1733110800.html
title: Regioni: sanita', in...
  description: Nel 2008 Molise...
  link: http://www.ansa.it/web/notizie/regioni/friuliveneziagiulia/2010/03/12/visualizza_new.html_1732935087.html
title: Roma: spara...
  description: Arrestato...
  link: http://www.ansa.it/web/notizie/regioni/friuliveneziagiulia/2010/03/12/visualizza_new.html_1732933202.html

Seconda versione: MVC

Secondo la buona pratica della separazione tra modello (cicciotto), controller snello) e view (come serve), si potrebbe ripensare il codice in questo modo:

<?php
/* model */
class Article
{
  private
    $_title,
    $_description, 
    $_link,
    $_pubDate;

  public function __construct($title, $description='', $link='', $pubDate='')
  {
     $this->_title=$title;
     $this->_description=$description;
     $this->_link=$link;
     $this->_pubDate=$pubDate;
  }
  public function getTitle()
  {
    return $this->_title;
  }
  public function getLink()
  {
    return $this->_link;
  }
  public function getDescription()
  {
    return $this->_description;
  }
  public function getPubDate()
  {
    return $this->_pubDate;
  }
}

class NewsReader
{
  private
    $_feed,
    $_xml,
    $_articles;

  public function __construct($feed)
  {
    $this->_feed=$feed;
    $this->_xml=simplexml_load_file($this->_feed);
    $this->findArticles();
  }
  private function getXML()
  {
    return $this->_xml;
  }
  private function findArticles()
  {
    $this->_articles=Array();

    foreach ($this->getXML()->channel->item as $item)
    {
      $this->_articles[]=new Article(
        $item->title,
        $item->description,
        $item->link,
        $item->pubDate
      );
    }
  }

  public function getTitle()
  {
    return $this->getXML()->channel->title;
  }

  public function getArticles()
  {
    return $this->_articles;
  }
}


/* controller */

$feed="http://www.ansa.it/web/notizie/regioni/friuliveneziagiulia/friuliveneziagiulia_rss.xml";
$newsreader = new NewsReader($feed);

/* view */
?>
<html><head>...</head>
<body>
<h1><?php echo $newsreader->getTitle() ?></h1>

<ul>
<?php foreach($newsreader->getArticles() as $article): ?>
   <li><a href="<?php $article->getLink() ?>"><?php echo $article->getTitle() ?></a><br />
   <em><?php echo $article->getDescription() ?></em>
   <span class='pubdate'><?php echo $article->getPubDate() ?></span>
   </li>
<?php endforeach ?>
</ul>
</body>
</html>

Altre cose su XML

Esistono altri due modi per analizzare codice XML con PHP: SAX (che funziona ad eventi) e DOM (che mappa gli elementi in un albero in memoria, senza trasformarli in proprietà come fa SimpleXML).

Con SimpleXML ci possono essere alcuni problemi che riguardano la presenza di namespaces. Si veda l'articolo Using SimpleXML To Parse RSS Feeds di Stuart Hebert (o altri analoghi) per informazioni al riguardo.

Se un elemento contiene valori rappresentati con CDATA, bisogna caricare il file specificando di non interpretarne il codice (FIXME).

Esempio:

<pre>
<?php
$feed='http://www.ansa.it/web/notizie/regioni/friuliveneziagiulia/friuliveneziagiulia_rss.xml';
$xml=simplexml_load_file($feed, 'SimpleXMLElement', LIBXML_NOCDATA);
$items=$xml->xpath('channel/item');
foreach($items as $item)
{
    echo $item->title . "\n";
}

Se in un file XML ci sono più elementi fratelli (sibling), la proprietà con il nome dell'elemento rappresenta il primo dei fratelli, ma si possono specificare gli altri aggiungendo l'indice (FIXME):

echo $xml->channel->item  // primo elemento
echo $xml->channel->item[0]  // primo elemento
echo $xml->channel->item[1]  // secondo elemento

Esercizi

  1. Fare delle prove con altri tipi di feed
  2. Fare delle prove con altri tipi di file XML
  3. Fare delle prove con xpath

Nessun commento:

Posta un commento