Feedarator

2010-12-26 22:46:25 Post #1 Comandeer

 
Przez święta mnie natchnęło i napisałem prostą, badziewną klasę do parsowania RSS i ATOM (pewnie do czegoś się kiedyś przyda ;D). Zapraszam do oceny - wieszajcie na mnie psy, bo pisałem to 15 minut (z czego 5 przeznaczyłem na zjedzenie kolacji).


<?php
/*
@package: VEF
@version: 0.1
@author: Comandeer
@desc: Klasa pobierająca RSS/Atom/XML (jak chcesz to możesz nawet JSON dorobić...) z podanego URI
*/
class helper_feeds_Feedarator
{
/*parse
@params: string $URI => URI feeda
@return: array $feed => zawartość feeda jako tablica
@author: Comandeer
@desc: Pobiera feeda z danego URI, obrabia go i zwraca jako tablicę
*/
public function parse($URI)
{
if(!filter_var($URI,FILTER_VALIDATE_URL)) //podany URI nie jest poprawnym linkiem
return false;
$feed=simpleXML_load_file($URI); //w końcu RSS i ATOM to poprawne dokumenty XML (w teorii...)
$type=$feed->getName(); //pobieramy nazwę roota
$method='parse'.$type;
if(method_exists($this,$method)) //jeśli parser takiego typu dokumentu istnieje (może zamiast root element brać po przestrzeni nazw? Mniejsza szansa na pomyłkę... Dupa... RSS nie ma przestrzeni nazw!)
return $this->$method($feed);
return false;
}
/*parseRSS
@params: object: SimpleXML $XML => feed RSS
@return array $feed => przeparsowana zawartość feeda
@author: Comandeer
@desc: Parsuje feeda danego jako parametr i zwraca odpowiednią tablicę
*/
private function parseRSS($XML)
{
$channel=$XML->channel;
$feed=array
(
'link'=>(string)$channel->link,
'title'=>(isset($channel->title))?(string)$channel->title:'(brak tytułu)',
'description'=>(isset($channel->description))?(string)$channel->description:'Brak opisu',
'language'=>(isset($channel->language))?(string)$channel->language:'default',
'image'=>isset($channel->image)?array
(
'title'=>(string)$channel->image->title,
'src'=>(string)$channel->image->url
):NULL
);
foreach($channel->item as $item) //jakby nie było tego tablicowego interfejsu dla obiektu SimpleXML, to sobie nie wyobrażam jego użytkowania...
$feed['items'][]=array
(
'title'=>(isset($item->title))?(string)$item->title:'(brak tytułu)',
'link'=>(string)$item->link,
'description'=>(string)$item->description,
'author'=>(isset($item->author))?(string)$item->author:'Anonim',
'pubdate'=>(isset($item->pubdate))?(string)$item->pubdate:time()
);
return $feed;
}
/*parseFeed
@params: object: SimpleXML $XML => feed Atom
@return array $feed => przeparsowana zawartość feeda
@author: Comandeer
@desc: Parsuje feeda danego jako parametr i zwraca odpowiednią tablicę
*/
private function parseFeed($XML)
{
$channel=$XML;
//roota feed może mieć trzy i trochę formatów - dla pewności sprawdzamy jeszcze przestrzeń nazw
$rattr=(array)$channel->attributes('xml');
$ns=(array)$channel->getDocNamespaces();
if(strtolower((string)$ns['']!='http://www.w3.org/2005/atom'||strtolower((string)$ns['atom']!='http://www.w3.org/2005/atom')
$author=(isset($channel->author))?(string)$channel->author->name:'Anonim'; //pobieramy autora feeda
foreach($channel->link as $link) //pobieramy linka do feeda
{
$attr=$link->attributes();
if(!isset($attr['rel']||$attr['rel']=='self')
$link=(string)$attr['href'];
}
$feed=array
(
'link'=>(isset($link))?$link:NULL,
'title'=>(isset($channel->title))?(string)$channel->title:'(brak tytułu)', //nie wiem po co ten workaround, bo w ATOM title jest wymagany
'language'=>(isset($rattr['lang'])?(string)$rattr['lang']:'default', //Ha, od czego mamy atrybut xml:lang?
'description'=>(isset($channel->description))?(string)$channel->descriptionisset($channel->subtitle)?(string)$channel->subtitle:'Brak opisu') //$channel->description jest z d wyciągnięte... Subtitle szybciej będzie
);
foreach($channel->entry as $item)
{
foreach($item->link as $link) //wyciągnięcie adresu WWW nie jest zadaniem prostym... ATOM jest bardziej skomplikowany niż RSS
{
$attr=$link->attributes();
if(!isset($attr['rel']||$attr['rel']=='alternate') //jak alternate nie prowadzi do strony WWW, to trudno... Ma prowadzić i już (przynajmniej według kursu na BrowseHappy.pl)
$link=(string)$attr['href'];
}
$feed['items'][]=array
(
'title'=>(isset($item->title))?(string)$item->title:'(brak tytułu)',
'link'=>(isset($link))?$link:'',
'description'=>(isset($item->content))?(string)$item->contentisset($item->summary)?(string)$item->summary:'Brak opisu'), //content przechowuje zawartość, summary streszczenie
'author'=>(isset($author))?(string)$author:'Anonim',
'pubdate'=>(isset($item->updated))?(string)$item->updated:time()
);
}
return $feed;
}
}

Nie bójcie się śmiesznej nazwy klasy, bo mam po prostu taką konwencję nazewnictwa (hint: własny framework z tandetnym hackiem imitującym namespace).
Przykład zastosowania?
1
2
3
4
5
6
7
8
<?php
    
require_once('Feedarator.class.php');
    
$f=new helper_feeds_Feedarator;
    
$rss=$f->parse('http://kanaly.rss.interia.pl/fakty.xml');
    
$atom=$f->parse('http://pornel.net/pornelski.atom');
    echo 
'<pre>RSS:';var_dump($rss);echo '</pre>';
    echo 
'<pre>Atom:';var_dump($atom);echo '</pre>';

Życzę miłego oceniania ;D

2010-12-27 09:16:51 Post #2 nospor

 
$feed=simpleXML_load_file($URI); //w końcu RSS i ATOM to poprawne dokumenty XML (w teorii...)

No wlasnie, w teorii.... Pamietaj by sprawdzać mimo wszystko czy sie udało pobrać czy nie. W praktyce spotkałem dużo serwisów, gdzie były to złe xml'e

"Brak opisu", "Brak tytułu"
Tu lepiej wstawiaj poprostu NULL. Nie wymuszaj jakiś tekstów.

Pozatym wydaje się że działa

2010-12-27 12:37:23 Post #3 Comandeer

 
Gdybym nie pisał tego o 23:58, pewnie bym tego ifa przy pobieraniu dorzucił ;D
Powiadasz, że NULL jest lepszy od '(brak tytułu)'? Ja jakoś wolę mieć już od razu gotowy tekst, zamiast później w czytniku bawić się ze sprawdzaniem czy tytuł jest pusty i robienia na szybko zamiennika. Zresztą - jak komuś wygodniej z NULL, to se zmieni ;D

2010-12-27 13:20:44 Post #4 nospor

 
Chodzi o to, ze ktos moze chciec miec inny tekst niz "brak tytułu". wowczas musi ifowac po Twoim "brak tytułu". A jak kiedys zmienisz klase i zamiast "brak tytulu" dasz "brak tytulku" i jak ktos zrobi upgrade klasy to bedzie mial zonk, znowy bedzie musial IFa zmieniac. NULL jest bardziej uniwersalny.

2010-12-27 21:09:07 Post #5 Comandeer

 
Nastąpił mały update
Z racji non-working BBCode, klasę można zassać stąd:
http://wklej.org/id/445665/
Przykład zastosowania:
1
2
3
4
5
6
7
8
<?php
    
require_once('Feedarator.class.php');
    
$f='helper_feeds_Feedarator';
    
$rss=$f::parse('http://kanaly.rss.interia.pl/fakty.xml');
    
$atom=$f::parse('http://pornel.net/pornelski.atom');
    echo 
'<pre>RSS:';var_dump($rss);echo '</pre>';
    echo 
'<pre>Atom:';var_dump($atom);echo '</pre>';

Co się zmieniło?
1) Klasa nie musi być dłużej instancjonowana - działa równie dobrze jako statyczna
2) Kod jest "only PHP 5.3 +" (a to za sprawą __CLASS__ - niby dupstwo, a jak wszystko zmienia ;D)
3) Dodałem tego bzdurnego if'a przy wczytywaniu feeda
4) 'Brak tytułu' i 'Anonim' zmieniłem na NULL (jakbym jakieś przeoczył, to se już end-user zmieni)
To by było tyle. Za drobną opłatą mogę pomóc w zintegrowaniu mojej klasy z istniejącymi aplikacjami webowymi

2010-12-28 08:51:23 Post #6 nospor

 
Hmmm... zaskoczyło mnie, że używasz funkcji jako statycznie, mimo iż statycznymi nie są a php nie rzuca błędem. Dlaczego nie rzuca? Zawsze do tej pory przy takich numerach mi rzucał.

2010-12-28 14:30:06 Post #7 Comandeer

 
Bodajże od 5.3.0 nie rzuca, ale nie dam se uciąć głowy.

Odpowiedz