Parser kanałów Atom. Krok po kroku.


1. Wstęp

Wraz z upowszechnieniem XML-a, a szczególnie opartego na nim standardu Atom warto zastanowić się nad napisaniem własnego parsera. Pomysł wymaga rozwiązania kilku dylematów: jaki język wybrać - Ruby, Python, PHP, JavaScript, na co postawić: szybkość, niezawodność, dostępność? Wszystkie języki mają zalety i wady, Ruby i Python muszą być suportowane przez serwer hostingowy (o co w Polsce raczej trudno, choć może się to zmieni wraz z rosnącą popularnością Django i Pylons?). Z kolei JavaScript wymusza obsługę ze strony przeglądarki (abstrahujemy od tego, co się stanie, gdy wyłączymy JavaScript), zaś technika Ajax ogranicza URL-e tylko do własnej domeny. PHP, szkoda mówić - wolny, pamięciożerny, słabo usystemtyzowany, wymagający przeładowania strony, inicjujący zmienne "kiedy się tylko da", ale za to tani i ogólnie dostępny.

Wybór w pewnym stopniu determinuje odpowiedź na pytanie: do czego ma służyć, a właściwie co ma robić nasz parser, czy tylko parsować? Otóż, musi on wykonywać dwie czynności - pobierać i zapisywać zawartość kanałów na dysku lokalnym (czytaj: serwera) oraz wyświetlać ją na ekranie, unikając przeładowania strony.

Wracając do naszych dylematów, pierwszą czynność a mianowicie pobranie zawartości kanałów i zapisanie ich na dysku lokalnym, ze względu na popularność i niskie koszty wdrożenia, powierzymy PHP, drugą, czyli sparsowanie i wyświetlenie pobranej zawartości na ekranie - JavaScript.

Pracę podzielimy na kilka etapów:
  1. Przygotowanie pliku XML z listą kanałów (plik: lista_kanalow.xml)
  2. Napisanie klasy PHP aktualizującej pliki kanałów Atom z wykorzystaniem biblioteki simpleXML (wymagana wersja PHP 5 lub wyższa), (plik: aktualizuj_kanaly.inc)
  3. Napisanie klasy JavaScript parsującej pliki kanałów Atom z wykorzystaniem biblioteki jQuery i techniki Ajax (plik: parsuj_kanaly.js)
  4. Stylowanie wyświetlanej zawartości (HTML, CSS), (plik: demo.html, parsuj_kanaly.css)

2. Przygotowanie pliku XML z listą kanałów

Aby skorzystać z zaimplementowanych parserów PHP i JavaScript, plik XML musi posiadać prawidłową strukturę i składnię, zgodną z obowiązującą standaryzacją WC3. Poza deklaracją o wersji i trybie kodowania nasz plik XML będzie zawierać węzeł główny KANALY oraz węzły potomne KANAL z następującymi atrybutami:

  • NAZWA = "nazwa kanału wyświetlana na liście rozwijanej"
  • URL       = "adres URL, z którego pobierana będzie zawartość kanału Atom"
  • PLIK      = "nazwa pliku lub ścieżka do pliku, w którym zapisywana będzie pobrana zawartość kanału Atom"

Listing 1. Plik lista_kanalow.xml z listą kanałów.

<?xml version="1.0" encoding="UTF-8"?>
<KANALY>
<KANAL NAZWA="Wybierz kanał" URL="" PLIK=""></KANAL>
<KANAL NAZWA="WP - wiadomości najnowsze" URL="http://wiadomosci.wp.pl/kat,1329,ver,rss,rss.xml" PLIK="xml/wp_wiadomosci.xml"></KANAL>
<KANAL NAZWA="Interia - wiadomości najnowsze" URL="http://kanaly.rss.interia.pl/fakty.xml" PLIK="xml/interia_wiadomosci.xml"></KANAL>
<KANAL NAZWA="Gazeta - wiadomości najnowsze" URL="http://rss.gazeta.pl/pub/rss/najnowsze_wyborcza.xml" PLIK="xml/gazeta_wiadomosci.xml"></KANAL>
</KANALY>

3. Klasa PHP generująca i aktualizująca pliki z zawartością kanałów Atom

Jak nadmieniliśmy, klasa PHP ma za zadanie sparsować utworzony przez nas plik z listą kanałów (lista_kanalow.xml) i na podstawie uzyskanych informacji zapisać zawartość kanałów na dysk lokalny. W celu sparsowania pliku wykorzystamy bibliotekę SimpleXML, umożliwiającą pobranie danych, sprawdzenie poprawności struktury XML oraz odczytanie wartości atrybutów podanych węzłów. Funkcje file_get_contents() i file_put_contents() dokonają reszty, pierwsza na podstawie wartości URL pobierze zawartość kanałów Atom, druga korzystając z wartości PLIK zapisze je na dysk serwera.

Klasę rozszerzymy o konstruktor i destruktor, czyli metody automatycznie wywoływane podczas tworzenia i niszczenia obiektu. Konstruktor może pobierać argumenty ale nie może ich zwracać, co zapobiega dalszemu przetwarzaniu kodu w momencie błędnego zainicjowania obiektu, a tym samym zwiększa bezpieczeństwo wykonywania skryptu. W naszym przypadku, jedynym zadaniem konstruktora będzie pobranie nazwy pliku z listą kanałów (Listing 2. 03-06). Dzięki użyciu destruktora, zniszczymy natomiast wartości pól obiektu, o ile istnieją i są niepuste, zwalniając zasoby (Listing 2. 07-16). Metody powołujemy odpowiednio za pomocą: __construct(), __destruct().

Uwaga: Należy koniecznie pamiętać o nadaniu prawa tylko do odczytu plikowi lista_kanalow.xml, prawa do zapisu plikom/katalogom wskazywanym przez atrybut PLIK w lista_kanalow.xml oraz plikowi xml_parser.log (o ile go wykorzystujemy).

Listing 2. Klasa PHP generująca i aktualizująca pliki z zawartością kanałów Atom. Plik: aktualizuj_kanaly.inc

<?php
01.  class CAktKan
02.  {
03.      public function __construct($X_Plik)
04.      {
05.          $this->X_Plik = $X_Plik;
06.      } 
07.      public function __destruct()
08.      {
09.          if (isset($this->X_Plik)) unset($this->X_Plik);
10.          if (isset($this->X_Zaw)) unset($this->X_Zaw);
11.          if (isset($this->X_ListKan)) unset($this->X_ListKan);
12.          if (isset($this->X_LiczbKan)) unset($this->X_LiczbKan);
13.          if (isset($this->A_Zaw)) unset($this->A_Zaw);
14.          if (isset($this->A_Plik)) unset($this->A_Plik);
15.          if (isset($this->A_URL)) unset($this->A_URL);
16.      } 
17.      public function FAktKan() 
18.      {
19.          $this->X_Zaw = simpleXML_load_file($this->X_Plik, "SimpleXMLElement", LIBXML_NOCDATA); 
20.          $this->X_ListKan = $this->X_Zaw->xpath('KANAL');
21.          $this->X_LiczbKan = ceil(count($this->X_ListKan));
22.  
23.          for ($i=1; $i<$this->X_LiczbKan; $i++) 
24.          { 
25.              $this->A_URL = $this->X_ListKan[$i]->attributes()->URL;
26.              // $this->A_URL_HEADERS = @get_headers($this->A_URL); // Mozna sprawdzac naglowek pliku
27.              // if($this->A_URL_HEADERS[0] != 'HTTP/1.1 404 Not Found') {
28.                  $this->A_Plik = $this->X_ListKan[$i]->attributes()->PLIK;
29.                  $this->A_Zaw = file_get_contents($this->A_URL);
30.                  // if ($this->A_Zaw ==  TRUE) { // lub zamiast naglowka sprawdzac zawartosc
31.                      $this->A_Zapis = file_put_contents($this->A_Plik, $this->A_Zaw, LOCK_EX);
32.                  // }
33.              // }
34.          }
35.
36.      }
37.  }
?>
Przeanalizujmy krótko, jakie działania wykonuje metoda FAktKan() (Listing 2. 17-35). Najpierw pobiera ona zawartość pliku z listą kanałów (Listing 2. 19), a następnie tablicę z węzłami o nazwie KANAL (Listing 2. 20) i oblicza jej wielkość (Listing 2. 21). Dalej pętla for (Listing 2. 23-32) iteruje po wszystkich elementach tablicy, oprócz elementu pierwszego (iteracja od 1 pomija "Wybierz kanał"), odczytując wartości atrybutów: URL (Listing 2. 25) oraz PLIK (Listing 2. 28), po czym na ich podstawie (Listing 2. 29-31) pobierając i zapisując zawartość kanałów Atom. W razie konieczności, możemy dodatkowo sprawdzać, czy podany URL istnieje - czyli czy posiada prawidłowy nagłówek (Listing 2. 27) lub czy funkcja file_get_contents() zwraca true (Listing 2. 30).

Klasę możemy wzbogacić o własną obsługę błędów, np. w przypadku, gdy podany plik xml nie istnieje, jest pusty, ma nieprawidłową strukturę, czy błędne wartości argumentów. Umieszczając znak @ przed funkcjami PHP, wyłączamy wyświetlanie komunikatów o błędach. Poniżej listing kodu, zapisującego informacje o wyniku aktualizacji kanałów Atom do pliku xml_parser.log (zamiennie zamiast zapisywać do pliku można wyświetlać komunikaty na ekranie).

Uwaga: Na poniższym listingu brak jest weryfikacji pobieranej zawartości kanału Atom.

Listing 3. Klasa PHP generująca i aktualizująca pliki z zawartością kanałów Atom z własną obsługą błędów. Plik: aktualizuj_kanaly.inc

<?php
01.  class CAktKan
02.  {
03.      public function __construct($X_Plik)
04.      {
05.          $this->X_Plik = $X_Plik;
06.      } 
07.      public function __destruct()
08.      {
09.          if (isset($this->X_Plik)) unset($this->X_Plik);
10.          if (isset($this->X_Zaw)) unset($this->X_Zaw);
11.          if (isset($this->X_ListKan)) unset($this->X_ListKan);
12.          if (isset($this->X_LiczbKan)) unset($this->X_LiczbKan);
13.          if (isset($this->A_Zaw)) unset($this->A_Zaw);
14.          if (isset($this->A_Plik)) unset($this->A_Plik);
15.          if (isset($this->A_URL)) unset($this->A_URL);
16.      } 
17.      public function FAktKan() 
18.      {
19.          date_default_timezone_set('Europe/Warsaw');
20.          if (!file_exists($this->X_Plik)) // Obsluga bledow: Jezeli, nie istnieje plik XML o podanej nazwie
21.          { 
22.               $this->Komunikat = 'Brak pliku: ' . $this->X_Plik . '. Sprawdź, czy istnieje podany plik.'; 
23.               $this->X_Log = file_put_contents('xml_parser.log', date('Y-m-d') . ' ' . $this->Komunikat.PHP_EOL, FILE_APPEND | LOCK_EX);
24.               // echo $this->Komunikat;
25.          }
26.          else // W przeciwnym razie, czyli jezeli istnieje plik XML, pobierz zawartosc pliku
27.          { 
28.               @ $this->X_Zaw = simpleXML_load_file($this->X_Plik, "SimpleXMLElement", LIBXML_NOCDATA);
29.               if ($this->X_Zaw ===  FALSE) // Obsluga bledow: Jezeli dane XML nie sa poprawne
30.               { 
31.                    $this->Komunikat = 'Nieprawidłowa struktura pliku: ' . $this->X_Plik . '. Sprawdź strukturę podanego pliku.'; 
32.                    $this->X_Log = file_put_contents('xml_parser.log', date('Y-m-d') . ' ' . $this->Komunikat.PHP_EOL, FILE_APPEND | LOCK_EX);
33.                    // echo $this->Komunikat;
34.                    return;
35.               }
36.               else // W przeciwnym razie, czyli jezeli dane XML sa poprawne, parsuj plik wg wezla 'KANAL'
37.               { 
38.                    $this->X_ListKan = $this->X_Zaw->xpath('KANAL');
39.                    $this->X_LiczbKan = ceil(count($this->X_ListKan));
40.
41.                    for ($i=1; $i<$this->X_LiczbKan; $i++) 
42.                    { 
43.                         $this->A_URL = $this->X_ListKan[$i]->attributes()->URL;
44.                         $this->A_Plik = $this->X_ListKan[$i]->attributes()->PLIK;
45.                         @ $this->A_Zaw = file_get_contents($this->A_URL);
46.                         if ($this->A_Zaw ==  FALSE)
47.                         {
48.                              $this->Komunikat =  'Wystąpił błąd przy próbie odczytu pliku: ' . $this->A_URL . '. Sprawdź, podany URL.';
49.                              $this->X_Log = file_put_contents('xml_parser.log', date('Y-m-d') . ' ' . $this->Komunikat.PHP_EOL, FILE_APPEND | LOCK_EX);
50.                              // echo $this->Komunikat;
51.                              return;
52.                         }
53.                         else
54.                         {
55.                              @ $this->A_Zapis = file_put_contents($this->A_Plik, $this->A_Zaw, LOCK_EX);
56.                              if(!$this->A_Zapis)
57.                              {
58.                                   $this->Komunikat =  'Wystąpił błąd przy próbie zapisu do pliku: ' . $this->A_Plik . '. Sprawdź, czy katalog/plik posiada atrybut do zapisu (chmod 777, 666).';
59.                                   $this->X_Log = file_put_contents('xml_parser.log', date('Y-m-d') . ' ' . $this->Komunikat.PHP_EOL, FILE_APPEND | LOCK_EX);
60.                                   // echo $this->Komunikat;
61.                                   return;
62.                              }
63.                              else
64.                              {
65.                                   $this->Komunikat =  'Wygenerowano pliki XML.';
66.                              }
67.                         }
68.                    }		
69.
70.                    $this->X_Log = file_put_contents('xml_parser.log', date('Y-m-d') . ' ' . $this->Komunikat.PHP_EOL, FILE_APPEND | LOCK_EX);
71.                    // echo $this->Komunikat;
72.                    // echo "<script>setTimeout('document.location = \"javascript:history.go(-1)\"', 1);</script>";
73.               }
74.          }
75.      }
76.  }
?>

Przykład. Zawartość pliku xml_parser.log.

2012-01-20 Brak pliku: kanaly.xml. Sprawdź, czy istnieje podany plik.
2012-01-21 Nieprawidłowa struktura pliku: kanaly.xml. Sprawdź strukturę podanego pliku.
2012-01-22 Wygenerowano pliki XML.
2012-01-23 Wystąpił błąd przy próbie odczytu pliku: http://wiadomosci.wp.pl/kat,1329,ver,rss,rss.xml1. Sprawdź, podany URL.
2012-01-24 Wystąpił błąd przy próbie zapisu danych do pliku: tvn24_wiadomosci.xml. Sprawdź, czy katalog oraz plik docelowy posiada atrybut do zapisu (chmod 777, 666).
2012-01-25 Wygenerowano pliki XML.
2012-01-26 Wygenerowano pliki XML.
2012-01-27 Wygenerowano pliki XML.
Obiekty tworzymy w osobnym pliku, który możemy uruchamiać jako zadanie crona lub ręcznie.

Przykład. Tworzenie obiektu klasy CAktKan.

<?php
$AktKan = new CAktKan('kanaly.xml');
$AktKan->FAktKan();
?>

4. Klasa JavaScript parsująca pliki z zawartością kanałów Atom

Przystępując do pisania klasy JavaScript, musimy wiedzieć, że stanowić będzie ona "serce" naszego parsera, gdyż na niej spocznie cały ciężar interakcji z użytkownikiem. Zadaniem klasy będzie utworzenie listy opcji z nazwami kanałów i wyświetlenie ich zawartości po wybraniu opcji. Jak łatwo się domyślić, nazwy kanałów i nazwy plików z zawartością kanałów zostaną pobrane z pliku lista_kanalow.xml, odpowiednio z atrybutów: NAZWA, PLIK. A ponieważ z pliku tego, korzysta również klasa PHP, odwołując się do atrybutów: URL, PLIK, to po wybraniu opcji, powinna zostać wyświetlona zawartość pliku zapisana przez klasę PHP. Innymi słowy, klasa PHP i klasa JavaScript korzystać będzie z tej samej wartości atrybutu PLIK danego węzła. Przedstawia to poniższy schemat.

Przykład. Zależność między klasą PHP i klasą JavaScript.

Klasa PHP

Pobierz z pliku lista_kanalow.xml wartości atrybutów węzła n1: URL, PLIK, potem na podstawie wartości URL-a pobierz zawartość kanału Atom i zapisz ją w wartości PLIK
Pobierz z pliku lista_kanalow.xml wartości atrybutów węzła n2: URL, PLIK, potem na podstawie wartości URL-a pobierz zawartość kanału Atom i zapisz ją w wartości PLIK
... itd.

Klasa JavaScript

Pobierz z pliku lista_kanalow.xml wartości atrybutów węzła n1: NAZWA, PLIK, potem na podstawie wartości NAZWA wyświetl tekst opcji, a na podstawie wartości PLIK zawartość pliku
Pobierz z pliku lista_kanalow.xml wartości atrybutów węzła n2: NAZWA, PLIK, potem na podstawie wartości NAZWA wyświetl tekst opcji, a na podstawie wartości PLIK zawartość pliku
... itd.

Aby jeszce dokładniej zobrazować działanie kodu, odwołajmy się bezpośrednio do pliku lista_kanalow.xml.

Przykład. Zależność między klasą PHP i klasą JavaScript a plikiem z listą kanałów.

Plik lista_kanalow.xml

<KANAL NAZWA="WP - wiadomości najnowsze" URL="http://wiadomosci.wp.pl/kat,1329,ver,rss,rss.xml" PLIK="xml/wp_wiadomosci.xml"></KANAL>
<KANAL NAZWA="Interia - wiadomości najnowsze" URL="http://kanaly.rss.interia.pl/fakty.xml" PLIK="xml/interia_wiadomosci.xml"></KANAL>

Klasa PHP wykona kolejno:

Pobierz zawartość: http://wiadomosci.wp.pl/kat,1329,ver,rss,rss.xml i zapisz ją w: xml/wp_wiadomosci.xml
Pobierz zawartość: http://kanaly.rss.interia.pl/fakty.xml i zapisz ją w: xml/interia_wiadomosci.xml

Klasa JavaScript wykona kolejno:

Pobierz nazwę: WP - wiadomości najnowsze i wyświetl zawartość: xml/wp_wiadomosci.xml
Pobierz nazwę: Interia - wiadomości najnowsze i wyświetl zawartość: xml/interia_wiadomosci.xml

Tworząc klasę JavaScript skorzystamy z biblioteki jQuery oraz techniki Ajax opartej na notacji JSON. Stworzymy również prototyp odpowiedzialny za pobieranie zawartości kanałów i usuwanie wyświetlanego na ekranie tekstu.

Listing 4. Klasa JavaScript wyświetlająca zawartość kanałów Atom. Plik: parsuj_kanaly.js

01.  // Script: Atom Channel Parser 1.0.  Require jQuery.
02.  // Author: Ireneusz Sekula, http://secom.pl, 2011-01-01
03.  // Free to use and abuse under the MIT license.
04.  var FListRoz = function(X_Plik, NazListOpc, IDListOpc)
05.  { 
06.      this.X_ListOpc = document.getElementById(IDListOpc);
07.      $.ajax({type: "GET", url: X_Plik, dataType: "xml", 
08.          success: function(X_Zaw)
09.          { 	
10.               $(X_Zaw).find("KANAL").each(function()
11.               {
12.                    $("."+NazListOpc).append('<option value="' + $(this).attr('PLIK') + '">' + $(this).attr('NAZWA') + '</option>' );
13.               });
14.          }
15.      });
16.  };
17.  FListRoz.prototype.Wyb = function()
18.  {
19.      $(this.X_ListOpc).change(function()
20.      {
21.          this.A_DivIDKan = document.getElementById('A_DivIDKan');	
22.          this.A_WybrKan = $('select option:selected');
23.          this.A_WybrKan = $(this).val();
24.          while (this.A_DivIDKan.hasChildNodes())
25.          { // Usuwanie tresci kanalu
26.               this.A_DivIDKan.removeChild(this.A_DivIDKan.lastChild);
27.          }
28.          if (this.A_WybrKan != "")
29.          {
30.               $.ajax({ type: "GET", cache: false, url: this.A_WybrKan, dataType: "xml",  
31.                    success: function(A_Zaw)
32.                    {
33.                         $(A_Zaw).find("title").each(function()
34.                         {
35.                              if($(this).parent().get(0).tagName == "channel")
36.                              {
37.                                   $("#A_DivIDKan").append('<div class="nagl">' + $(this).text() + '</div>');
38.                              }
39.                         });
40.                         $(A_Zaw).find("item").each(function()
41.                         {
42.                              if($(this).children().size() > 0)
43.                              {
44.                                   $("#A_DivIDKan").append('<div class="separ"></div><div class="tyt">'
45.                                   + $(this).find("title").text() + '</div><div class="tresc">'
46.                                   + $(this).find("description").text() + '</div><div class="data_pub">'
47.                                   + $(this).find("pubDate").text() + '</div><div class="link"><a href="'
48.                                   + $(this).find("link").text() + '" target="_blank">Więcej</a>...</div>');
49.                              }
50.                         });
51.                         $("#A_DivIDKan").append('<div class="separ"></div>');
52.                    }
53.               });
54.          }
55.      });
56.  };

Listing 5. Tworzenie obiektu klasy FListRoz. Plik: demo.html

01.  <form action="" method="post"><select class="C_LK1" id="I_LK1"></select></form><!-- Obowiązkowa unikatowa nazwa klasy i id listy -->
02.  <form action="" method="post"><select class="C_LK2" id="I_LK2"></select></form><!-- Obowiązkowa unikatowa nazwa klasy i id listy -->
03.  <div class="A_DivKlasaKan" id="A_DivIDKan"><!-- Tu zapisywana jest zawartosc kanalu. Nie zmieniaj id warstwy --></div>
04.  <script src="parsuj_kanaly.js" type="text/javascript"></script>
05.  <script type="text/javascript">
06.      ListKan1 = new FListRoz('kanaly1.xml', 'C_LK1', 'I_LK1');
07.      ListKan1.Wyb();
08.      ListKan2 = new FListRoz('kanaly2.xml', 'C_LK2', 'I_LK2');
09.      ListKan2.Wyb();
10.  </script>

Przyjrzyjmy się, co robi nasza klasa. Najpierw metoda FListRoz, pełniąca rolę konstruktora, pobiera trzy argumenty podawane przez nas podczas tworzenia obiektu (Listing 5. 06, Listing 5. 08), a mianowicie: NAZWĘ PLIKU (z listą kanałów), NAZWĘ KLASY oraz ID LISTY (rozwijanej) (Listing 4. 04). Dalej na podstawie ID LISTY, pobiera węzeł listy w dokumencie (Listing 4. 06), po czym korzystając z Ajaxa - na podstawie NAZWY PLIKU - pobiera zawartość listy kanałów w formacie XML (Listing 4. 07). Jeżeli operacja kończy się sukcesem, wywołuje funkcję, przekazując jej pobraną zawartość (listę kanałów), którą dalej przetwarza metoda jQuery o nazwie .each(). .each() wyszukuje wszystkie węzły KANAL (Listing 4. 10) i posługując się NAZWĄ KLASY, dodaje opcje do listy rozwijanej o jej nazwie, na zasadzie (Listing 4. 12):
   tekst opcji = wartość atrybutu NAZWA węzła KANAL
   wartość opcji = wartość atrybutu PLIK węzła KANAL (czyt. nazwa pliku z zawartością kanału)

Metoda Wyb jest prototypem metody FListRoz wywoływanym po zainicjowaniu obiektu (Listing 5. 07, Listing 5. 09). Swoje działanie rozpoczyna od obsługi zdarzenia związanego z wyborem opcji (czyt. nazwy kanału). W tym celu korzysta z metody jQuery o nazwie .change(), która jako argument przyjmuje pobrany przez konstruktor węzeł aktywnej listy rozwijanej (Listing 4. 19). Metoda .change() pobiera węzeł warstwy odpowiedzialnej za wyświetlanie treści kanału (Listing 4. 21), a następnie obsługuje aktualnie wybraną opcję (Listing 4. 20), odczytując jej wartość czyli nazwę PLIKU (Listing 4. 20). W pętli while (Listing 4. 24-27), usuwana jest - o ile istnieje - treść wyświetlanego kanału, a dokładnie rzecz biorąc wszystkie elementy potomne warstwy 'div'* wyświetlającej kanał (Listing 4. 26), o ile i dopóki istnieją (Listing 4. 24). Instrukcja if sprawdza, czy wartość wybranej opcji (czyt. nazwa PLIKU) nie jest pusta (Listing 4. 28) i jeżeli warunek jest spełniony, pobiera za pośrednictwem Ajaxa zawartość pliku (Listing 4. 30). Cache ustawiony na false oznacza, że zawartość nie będzie cache'owana przez przeglądarkę, dzięki czemu od razu będzie widoczna każda aktualizacja pliku. W przypadku powodzenia Ajax inicjuje funkcję parsującą, przekazując jej jako argument pobraną zawartość (Listing 4. 31).

Parsowanie zawartości podzielone jest na dwa etapy (Listing 4. 31-53), oba obsługiwane przez znaną nam już metodę .each(). Pierwsza (Listing 4. 33-39), wyszukuje wszystkie węzły 'title' (Listing 4. 33) i jeżeli rodzicem znalezionego węzła jest 'channel' (Listing 4. 35), wyświetla za pomocą .append() zawarty w nim węzeł tekstowy (.text()) (Listing 4. 37). W efekcie na ekranie pojawia się nazwa kanału. Druga (Listing 4. 40-50), sprawdza wielkość potomków węzła 'item' i jeżeli posiada on choć jeden węzeł potomny z niepustą zawartością (Listing 4. 42) podejmuje próbę jego sparsowania (Listing 4. 44-48).

W węźle 'item' po kolei wyszukiwane są węzły tekstowe (.text()) według standaryzacji nazewnictwa Atom: 'title' (Listing 4. 45), 'description' (Listing 4. 46), 'pubDate' (Listing 4. 47), 'link' (Listing 4. 46). Wyszukana zawartość wraz z warstwami pomocniczymi i stylami (CSS) w całości wyświetlana jest w warstwie 'div'* przy użyciu .append() (Listing 4. 44). Na końcu wyświetlany jest separator (Listing 4. 51), w naszym przypadku zwykła linia oddzielająca zawartość poszczególnych węzłów 'item'.

*Uwaga: Warstwa 'div' musi posiadać id="A_DivIDKan". Jest ono wykorzystywane do wyświetlenia i usuwania treści.

5. Stylowanie wyświetlanej zawartości

Wyświetlaną zawartość stylujemy za pomocą arkusza stylów, posługując się nazwami klas warstw 'div' z Listingu 4 (Listing 4. 37, 44-48), czyli:

class="A_DivKlasaKan" - warstwa główna, odpowiedzialna za całą wyświetlaną zawartość kanału
class="nagl" - nazwa kanału
class="separ" - separator oddzielający nagłówek oraz poszczególne wiadmości
class="tresc" - treść wiadomości
class="data_pub" - data publikacji
class="link" - link

Osobno wedle upodobania stylujemy listy rozwijane. Poniżej przykładowy plik CSS (wszystkie warstwy dziedziczą po warstwie: A_DivKlasaKan).

Listing 6. Przykładowy plik CSS. Plik: parsuj_kanaly.css

div.A_DivKlasaKan                          { width:800px; clear:both; padding:5px 0 0 0; }
div.A_DivKlasaKan div.separ           { height:20px; clear:both; margin:0px 0 20px 0; border-width:1px; border-style:solid; border-color:#e5e5e5; } 
div.A_DivKlasaKan div.nagl             { margin:20px 0 0 0; padding:0; font-weight:bold; } 
div.A_DivKlasaKan div.tyt               { margin:0 0 5px 0; padding:0; font-weight:bold; } 
div.A_DivKlasaKan div.tresc            {  } 
div.A_DivKlasaKan div.tresc img     { margin:0 10px 0 0; padding:0; /*display:none*/ /*odznacz, jezeli nie chcesz wyswietlac obrazkow */ }
div.A_DivKlasaKan div.data_pub     { clear:both; padding:0 40px 0 0; color:#999; font-style:italic; font-size:13px; text-align:right;  }
div.A_DivKlasaKan div.link              { clear:both; padding:0 40px 0 0; text-align:right; }
div.A_DivKlasaKan div.link a           { color:black; text-decoration:none; }
div.A_DivKlasaKan div.link a:hover { text-decoration:underline; }

6. Dodatek

Aby pobierać dane bezpośrednio z zewnętrznych URL-i (domen), z pominięciem klasy PHP (czyli zapisywania zawartości kanałów na dysk lokalny), dane muszą posiadać format JSON i nie mogą posiadać nagłówka MIME. Po przygotowaniu danych, należy opdowiednio zmienić Ajaxowego requesta (Listing 4. 30). Powinien mieć on poniższą postać:

$.ajax({ type: "GET", cache: false, url: this.A_WybrKan, dataType: "jsonp", crossDomain: true, jsonpCallback: 'jsonp_callback', //success: 

Zobacz demo: Parser kanałów Atom

Ireneusz Sekuła, JavaScript dla zielonych