Serwis prównujący ceny z wielu sklepów internetowych

Transkrypt

Serwis prównujący ceny z wielu sklepów internetowych
Warsztat
Integracja danych
Przemysław Pokrywka,
Przemysław Kazienko
Na CD:
Na płycie CD umieściliśmy spakowane źródła systemu Cocoon w wersji 2.1.2
(cocoon/cocoon2.1.2-src), kod źródłowy zmodyfikowanego komponentu HTML
Generator (cocoon/
HTMLGenerator.java),
aplikację zaprezentowaną w artykule (katalog
zestawienie-cen).
Serwis prównujący ceny z
wielu sklepów internetowych
D
zięki wielości sklepów internetowych,
konsumenci zainteresowani kupnem
określonego towaru mogą wybrać ofertę najlepiej spełniającą ich oczekiwania. To jednak tylko teoria. W praktyce na odnalezienie najkorzystniejszej oferty traci się dużo czasu. Z pomocą przychodzą wtedy serwisy porównujące
ceny z wielu sklepów. W artykule krok po kroku
zbudujemy taki mocno uproszczony, lecz działający i dający się rozszerzać serwis, zestawiający ze sobą ceny procesorów z trzech sklepów internetowych.
Problemy do rozwiązania
Serwis, który zamierzamy stworzyć, będzie możliwie najprostszy. Składał się będzie z pojedynczej strony, zawierającej tabelę cen procesorów
w trzech sklepach (Rysunek 3). Każdemu wierszowi będzie przypisany jeden procesor, każdej
kolumnie jeden sklep, a na przecięciu wiersza
i kolumny znajdzie się cena procesora w danym
sklepie. Dzięki takiemu układowi będziemy mogli szybko zorientować się, w którym sklepie możemy zakupić interesujący nas procesor po najniższej cenie.
Budowa jednak nawet tak prostego serwisu postawi nas w obliczu poważnych problemów. Musimy odpowiedzieć sobie na pytanie,
jak z poziomu programu uzyskać potrzebne
nam dane, w szczególności aktualne ceny in-
Przemysław Pokrywka jest studentem ostatniego
roku informatyki na Wydziale Informatyki i Zarządzania Politechniki Wrocławskiej. Szczególnie interesuje się językiem Java, w którym programuje od
6 lat, a także dobrymi praktykami rozwoju oprogramowania, wzorcami projektowymi, refaktoryzacją i
programowaniem ekstremalnym. Jest entuzjastą
oprogramowania open source.
Kontakt z autorem: [email protected]
Przemysław Kazienko od 5 lat prowadzi wykład
z języka XML na Wydziale Informatyki i Zarządzania
Politechniki Wrocławskiej. Jest także współautorem
książki XML na poważnie oraz licznych artykułów o
tym języku. Wraz ze studentami prowadzi serwis
poświęcony językowi XML Centrum XML, dostępny
pod adresem: http://www.centrumxml.pwr.wroc.pl.
Kontakt z autorem: [email protected],
http://www.pwr.wroc.pl/~kazienko.
70
Rysunek 1. Witryny trzech sklepów
internetowych
teresujących nas procesorów, oraz jak poradzić sobie z problemem odmiennego nazywania tych samych procesorów w różnych sklepach.
Dostęp do danych HTML
Mamy szczęście, jeśli sklepy, których oferty porównujemy, udostępniły nam swoje bazy danych
przez protokoły w rodzaju ODBC. Wówczas sprawa pobrania potrzebnych danych sprowadza się
do oprogramowania dostępu do tych baz za pomocą dobrze znanych technik. Co jednak zrobić,
gdy jedyne dane, jakimi dysponujemy, to strony
internetowe tych sklepów w formacie HTML? Tu
zaczynają się schody, bo z gąszczu nie zawsze
poprawnych składniowo znaczników, opisujących sposób prezentacji, trzeba wydobyć interesujące nas informacje: kategorię produktu, producentów, nazwy, ceny oraz dostępność towaru.
Oczywiście moglibyśmy posłużyć się algorytmami analizy tekstu. Z pewnością pomyślałeś już,
Czytelniku, o wyrażeniach regularnych, techniki
te jednak wciąż mają swoje wady. Wyrażenia regularne służące do wydobywania interesujących
nas danych potrafią być bardzo skomplikowane, a przez to trudne do modyfikacji, gdy strona,
z której pobieramy dane, zostanie zmodyfikowana. Niemal każda zmiana na stronie wymusza
zmianę w wyrażeniu regularnym. Dodatkowo, dla
niektórych stron opracowanie odpowiednich wyrażeń może być bardzo trudne lub jest wręcz niemożliwe.
Zupełnie inaczej sprawa wyglądałaby, gdyby
sklep udostępniał swoją ofertę w postaci pliku
XML, na przykład poprzez mechanizm web servi-
www.software.com.pl
Software 2.0 06/2004
Serwis porównujący ceny z wielu sklepów internetowych
Szybki start
Do uruchomienia systemu niezbędny jest J2SDK w wersji
1.4. Zaczynamy od rozpakowania dystrybucji Cocoona i podmienienia pliku src/blocks/html/java/org/apache/cocoon/
generation/HTMLGenerator.java na jego nową wersję (z katalogu cocoon na CD). Ustawiamy zmienną środowiskową JAVA_
HOME tak, by wskazywała katalog z Javą, na przykład:
set JAVA_HOME=c:\j2sdk1.4.2._04
Następnie kompilujemy Cocoona, uruchamiając w jego głównym katalogu skrypt build.bat oraz kopiujemy katalog zestawienie-cen z CD do podkatalogu build/webapp. Po tych zabiegach możemy już uruchomić platformę Cocoon poleceniem:
cocoon.bat servlet
Działający przykład możemy teraz oglądać wpisując w przeglądarce lokalny adres:
http://localhost:8888/zestawienie-cen/zestawienie
ces. Nawet gdyby różne sklepy stosowały swoje własne
formaty XML, wydobycie interesujących nas danych byłoby proste – wystarczy rzut oka na dokument w takim formacie, by wiedzieć, jakich wyrażeń XPath użyć.
R
E
K
Niestety, rzeczywistość nas nie rozpieszcza. Sklepy,
mając na uwadze przeciętnych internautów, udostępniają swoją ofertę wyłącznie w wersji HTML i nie widzą sensu inwestowania w inne sposoby prezentacji swoich danych. A gdyby ów HTML w “cudowny” sposób zamienił się
w XML-a, z którego można by wydobywać dane za pomocą prostych ścieżek XPath?
Mamy dobre wieści: taka możliwość istnieje i to zupełnie za darmo. Dostarcza ją Apache Cocoon, darmowe środowisko publikacyjne, oparte na XML-u, rozpowszechniane na zasadach liberalnej (Apache-style) licencji open-source. Transformacji niepoprawnego HTML-a do poprawnego składniowo XHTML-a potrafi mianowicie dokonać jeden z komponentów Cocoona
– HTML Generator. Samo środowisko Cocoon, ze względu na bogactwo oferowanych możliwości, zasługuje na
oddzielny artykuł (polecamy warsztaty Szymona Zioło
XML i Cocoon – książka kucharska, Software 2.0, nr
9/2003), nam jednak do korzystania z niego wystarczy poznanie samych jego podstaw. Wyjątkiem od tej
reguły będzie konieczność wejścia w dosyć intymny kontakt z kodem źródłowym HTML Generatora. Uczynimy to
w celu dodania do niego pełnej obsługi polskich znaków, ograniczonej domyślnie do Unikodu. Polskie strony
WWW najczęściej bywają bowiem mniej łaskawe i serwisy sklepów internetowych kodowane są w większości za
pomocą ISO-8859-2 (Latin 2) lub Windows-1250.
L
A
M
A
Warsztat
Integracja danych
Cocoon
Listing 1. Słownik towarów
Cocoon jest opartym na XML-u środowiskiem do budowy aplikacji webowych. Od swoich dalekich krewnych – PHP, ASP, JSP
i wszelkiego rodzaju skryptów server-side – różni się filozofią
działania. Nacisk położony jest w nim przede wszystkim na
separację zagadnień (ang. separation of concerns). Aplikacje
w nim budowane można łatwiej niż gdzie indziej podzielić na
warstwy danych, logiki i prezentacji. Osiąga się to poprzez:
<?xml version="1.0" encoding="UTF-8"?>
<slownik>
<kategoria nazwa="procesory">
<producent nazwa="AMD">
<czesc nazwa="AMD Duron 1800 MHz">
<askryptor>AMD Duron 1.8GHz</askryptor>
<askryptor>Procesor AMD DURON 1800 MHz</askryptor>
•
•
•
korzystanie z danych źródłowych w formacie XML, neutralnym pod względem prezentacji,
wydzielenie – zazwyczaj z wykorzystaniem XSLT – opisu
sposobu transformacji danych do formatów wyjściowych,
m.in. HTML, PDF, SVG, JPEG, PNG, TXT, WML, RDF, plików MS Excela, VRML,
wyspecyfikowanie logiki przetwarzania, czyli kolejności
i rodzaju przekształceń, w planie witryny (ang. sitemap)
– dedykowanym pliku konfiguracyjnym aplikacji webowej
(oczywiście w formacie XML).
<askryptor>Duron 1800Mhz Applebread</askryptor>
</czesc>
<czesc nazwa="AMD Athlon XP 2500+ Barton">
<askryptor>AMD Athlon XP 2500+ Barton</askryptor>
<askryptor>Procesor AMD ATHLON XP 2500+ - 333MHz
</askryptor>
<askryptor>Athlon XP 2500+ Barton</askryptor>
</czesc>
<czesc nazwa="AMD Athlon XP 2200+">
<askryptor>AMD Athlon XP 2200+</askryptor>
Drugą zasadniczą cechą Cocoona jest jego budowa komponentowa. Każdy komponent realizuje jeden określony krok
przetwarzania. Może to być pobranie danych z pliku, z sieci
lub z bazy danych, przekształcenie danych arkuszem XSLT, czy
w końcu przekazanie wersji HTML przeglądarce. Komponenty
komunikują się ze sobą za pomocą zdarzeń SAX, dzięki czemu
można je łączyć w potoki (ang. pipelines), podobnie jak w przypadku zestawiania procesów w Unixie, przekazując wyjście jednego komponentu na wejście drugiego. W planie witryny deklarujemy, których komponentów i w jakiej kolejności używamy
w celu zrealizowania określonego żądania.
Wraz z instalacją Cocoona otrzymujemy bogaty zestaw
komponentów, wystarczających do realizacji większości typowych zadań. W razie potrzeby możemy także wspomóc się
napisanymi ręcznie stronami JSP (Cocoon jest napisany w Javie i działa w kontenerze serwletów) lub XSP (eXtensible Server Pages), czyli w pełni XML-owymi, rozszerzalnymi stronami interpretowanymi po stronie serwera. W każdej chwili możemy też rozbudować Cocoona o własne komponenty – wymaga to jednak znajomości Javy i wykorzystywanej w Cocoonie architektury komponentów Avalon.
<askryptor>Procesor AMD ATHLON XP 2200+ - 266MHz
</askryptor>
<askryptor>Athlon XP 2200+</askryptor>
</czesc>
</producent>
<producent nazwa="Intel">
<czesc nazwa="Intel Pentium 4 2.8 GHz BOX">
<askryptor>Intel Pentium4 2.8GHz Prescott BOX
</askryptor>
<askryptor>Procesor INTEL PENTIUM 4 2.8 GHz 800MHz
BOX</askryptor>
<askryptor>Intel PIV 2,80GHZ 800 Socket 478 BOX
</askryptor>
</czesc>
<czesc nazwa="Intel Pentium 4 3.0 GHz BOX">
<askryptor>Intel Pentium4 3.0GHz Hyper-Threading
BOX</askryptor>
<askryptor>Procesor INTEL PENTIUM 4 3.0 GHz 800MHz
BOX</askryptor>
<askryptor>Intel PIV 3,00GHZ 800 Socket 478 BOX
</askryptor>
Unifikacja nazw towarów
</czesc>
Skąd wiadomo, że Procesor AMD DURON 1800 MHz oznacza to samo, co Duron 1800Mhz Applebread lub AMD Duron 1.8GHz? Dla każdego średnio zorientowanego użytkownika może to być oczywiste. Sęk w tym, że dla komputera to zupełnie różne ciągi znaków. Żeby móc wygenerować
sensowne zestawienie, trzeba w jakiś sposób przekazać
maszynie, że te trzy napisy oznaczają dokładnie to samo.
Można w tym celu spróbować zastosować jakąś funkcję
badającą podobieństwo tekstów. Można też próbować normalizować teksty: sortować wyrazy wewnątrz nazwy, usuwać białe znaki, zamieniać wszystkie duże litery na małe,
a znaki na słowa (na przykład „+” na „plus”), itd. Tych podejść nie będziemy jednak stosować w naszym systemie,
choćby ze względu na chęć zachowania jego prostoty. Do
naszych celów doskonale nada się prosty słownik nazw,
72
<czesc nazwa="Intel Celeron 2600 MHz">
<askryptor>Intel Celeron 2.6GHz Northwood
</askryptor>
<askryptor>Procesor INTEL CELERON 2600 MHz
</askryptor>
<askryptor>Intel Celeron 2600 SOC.478 OEM
</askryptor>
</czesc>
</producent>
</kategoria>
<!-- inne kategorie -->
</slownik>
www.software.com.pl
Software 2.0 06/2004
Serwis porównujący ceny z wielu sklepów internetowych
Listing 2. Budowa serwisu – plik sitemap.xmap
<?xml version="1.0" encoding="ISO-8859-2"?>
<map:serialize type="xml"/>
<map:sitemap
</map:match>
xmlns:map="http://apache.org/cocoon/sitemap/1.0">
<map:components>
<map:match pattern="arest">
S
<map:generate src="http://www.arest.pl/
<map:generators default="file">
index.php?inc=cennik&amp;katid=1"
<map:generator name="html"
type="html">
S
<map:parameter name="in-encoding"
src="org.apache.cocoon.
generation.HTMLGenerator">
value="windows-1250"/>
<jtidy-config>jtidy.properties</jtidy-config>
<in-encoding>ISO-8859-2</in-encoding>
</map:generate>
<map:serialize type="xml"/>
</map:generator>
</map:match>
</map:generators>
<map:match pattern="proline">
<map:transformers default="xslt"/>
<map:generate
<map:serializers default="html"/>
S
src="http://www.proline.pl/
<map:readers default="resource"/>
shop.php?kat=Procesory"
<map:matchers default="wildcard"/>
type="html"/>
<map:pipes default="caching"/>
<map:serialize type="xml"/>
</map:components>
</map:match>
</map:pipeline>
<map:pipelines>
<map:pipeline>
<map:pipeline>
<map:match pattern="zestawienie">
<!-- Aktualizacja danych
<map:generate src="slownik.xml"/>
źródłowych co 12 godz. -->
<map:parameter name="expires"
<map:transform src="glowny.xsl"/>
<map:serialize type="xhtml"/>
value="access plus 12 hours"/>
<map:match pattern="age">
S
</map:match>
<map:generate src="http://www.sklep.age.pl/
</map:pipeline>
list.php?m_grupa=PR&amp;m_sort=n_nazwa"
</map:pipelines>
</map:sitemap>
type="html"/>
zapisany w postaci dokumentu XML (Listing 1), w którym
wymienione zostaną reprezentacyjne nazwy części komputerowych (te, które będą wyświetlane w docelowym zestawieniu) – deskryptory, oraz ich nie zalecane odpowiedniki (te, które występują w sklepach) – askryptory. W celu
większej przejrzystości ostatecznego zestawienia, części
pogrupujemy wg kategorii i producenta.
W naszym przykładzie z procesorami oznaczałoby to,
że deskryptorem byłby na przykład AMD Duron 1800 MHz,
a wymienione wcześniej nazwy byłyby askryptorami. System, po napotkaniu nazwy na stronie sklepu, wyszuka ją
w słowniku, i w przypadku odnalezienia, dalej posłuży się
już tylko jej deskryptorem. Takie rozwiązanie pociąga za
sobą konieczność ciągłej „ręcznej” aktualizacji słownika
nazw towarów. W zamian daje pewność poprawnego kojarzenia towarów występujących na stronach różnych sklepów, czego tak łatwo nie bylibyśmy w stanie zagwarantować, nawet używając skomplikowanych algorytmów porównywania tekstów.
Uzyskaliśmy odpowiedzi na zasadnicze pytania: w jaki sposób uzyskać dane ze sklepów, oraz jak je unifikować. Pora teraz na wykorzystanie tej wiedzy w praktyce.
Naszym pierwszym krokiem będzie...
Software 2.0 06/2004
Konfiguracja środowiska pracy
Do instalacji Cocoona potrzebny nam będzie Java 2 Software Development Kit. Sama maszyna wirtualna nie
wystarczy, gdyż Cocoon jest od pewnego czasu rozpowszechniany tylko w postaci źródeł i wymagana jest jego
kompilacja. Ściągnięty J2SDK instalujemy do wybranego
przez nas katalogu (najlepiej, żeby jego ścieżka nie zawierała spacji) i ustawiamy zmienną środowiskową JAVA_HOME
tak, by wskazywała na ten katalog. Zmiennej tej potrzebuje skrypt kompilujący Cocoona, żeby wiedzieć, gdzie jest
kompilator Javy.
Gdy mamy już zainstalowaną i skonfigurowaną Javę,
instalujemy samego Cocoona, rozpakowując jego dystrybucję do wybranego katalogu – znów bez spacji w ścieżce. Zostanie w nim utworzony dodatkowy katalog, cocoon-2.1.2-src, zawierający źródła. Są one gotowe do kompilacji, jednak zanim przejdziemy do tego kroku, musimy
zmodyfikować źródło komponentu HTML Generator, aby
umożliwić poprawną obsługę polskich znaków oraz adresów URL przekazanych mu z planu witryny. W tym celu kopiujemy przygotowaną wersję HTML Generatora (na CD –
HTMLGenerator.java) do katalogu src\blocks\html\java\
org\apache\cocoon\generation (wewnątrz katalogu coco-
www.software.com.pl
73
Warsztat
Integracja danych
Tidy
Skromny, darmowy programik Dave'a Raggetta, goszczący niegdyś na stronach WWW Consortium (W3C), a obecnie na SourceForge, którego autor prosi o pocztówkę, jeśli jego dzieło
komuś się przyda, reklamuje się jako automatyczny oczyszczacz niedbale napisanych stron WWW, radzący sobie szczególnie dobrze z HTML-em generowanym automatycznie przez
narzędzia i zwracający uwagę na dostępność stron dla osób
niepełnosprawnych. Tidy rozwiązuje szeroką gamę problemów
w dokumentach HTML i pseudo-HTML, takich jak:
•
•
•
•
•
niepodomykane znaczniki,
elementy zachodzące na siebie,
atrybuty nie przewidziane dla danego elementu HTML,
wartości atrybutów nie otoczone apostrofami lub cudzysłowami,
elementy spoza HTML-a.
Tidy generuje dokumenty XHTML. Do jego znanych słabości
należą m.in.: tolerowanie podwójnych wystąpień atrybutów
w elemencie i słaba obsługa nieprawidłowo osadzonych skryptów JavaScript.
on-2.1.2, powstałego po rozpakowaniu źródeł). Dzięki tej
poprawce będziemy mogli w planie witryny specyfikować
rodzaj kodowania znaków, który ma być użyty przy przetwarzaniu danej strony WWW.
Teraz już możemy uruchomić proces kompilacji. W tym
celu w katalogu cocoon-2.1.2 uruchamiamy plik wsadowy
build.bat . Po zakończonej kompilacji w tym samym katalogu pojawia się katalog build, zawierający katalog webapp. W katalogu webapp założymy katalog naszego serwisu zestawienie-cen, w którym utworzymy:
•
•
•
•
plik konfiguracyjny HTML Generatora jtidy.properties,
plik słownika slownik.xml (Listing 1),
plan witryny sitemap.xmap,
przekształcenie generujące gotowe zestawienie glowny.xsl.
Plik jtidy.properties określa ustawienia, jakie mają
być brane pod uwagę przy przekształcaniu HTML-a do
XHTML-a. Są to faktycznie ustawienia programu JTidy (Javowej wersji programu Tidy), który wykorzystywany jest
do czyszczenia niepoprawnych składniowo dokumentów
HTML wewnątrz HTML Generatora. Szczegółowa znajomość opcji tego programu nie jest nam potrzebna, warto jednak wiedzieć, że aby transformacje w naszej aplikacji przebiegały poprawnie, musimy nakazać JTidy zwracać
dokumenty w kodowaniu UTF-8, ustawiając opcję:
char-encoding: utf8
a także opcję:
output-xhtml: true
aby zwracane przezeń dokumenty były w formacie
XHTML.
Konfiguracja serwisu
Nasz serwis nie jest skomplikowaną witryną. Jej konfiguracja zawarta jest w planie witryny, tj. w pliku sitemap.xmap (Listing 2). Jest to dokument XML złożony
z dwóch podstawowych sekcji: components i pipelines.
W sekcji <map:components> deklarowane są wykorzystywane przez dany plan witryny komponenty (generatory,
serializery, transformatory, itd.) HTML Generator, wymieniony na naszej liście komponentów, posiada dwa parametry: kodowanie pobieranych stron (domyślnie ustawione na ISO-8859-2) oraz położenie pliku konfiguracyjnego
JTidy. Inne istotne komponenty to: procesor XSLT (transformator) oraz serializer do formatu XHTML, którego zadaniem będzie utworzenie dokumentu dla przeglądarki.
Sekcja <map:pipelines> zawiera konfigurację poszczególnych potoków, wraz z wzorcami ich wywołania (<map:
match pattern =”…”>). Nasza aplikacja zawiera łącznie
cztery potoki: po jednym potoku dla każdego sklepu oraz
potok generujący docelowe zestawienie (Rysunek 2).
Osobne potoki dla poszczególnych sklepów są niezbędne po to, aby wskazać źródła pobieranych stron oraz
określić sposób ich oczyszczania do formatu XHTML.
Każdy ze sklepów ma inny wzorzec swojego wywołania,
na przykład pattern ="age", co wykorzystamy później w odpowiednich odwołaniach w przekształceniu XSLT. W na-
Rysunek 2. Budowa serwisu integrującego dane z trzech sklepów internetowych
74
www.software.com.pl
Software 2.0 06/2004
Serwis porównujący ceny z wielu sklepów internetowych
szym przypadku wartość age możemy traktować jako nazwę potoku.
Każdy ze sklepów ma poza tym inny parametr generaListing 3. Transformacja generująca zestawienie cen
(fragmenty)
<xsl:variable name="age"
select="document('cocoon:/age')"/>
<xsl:variable name="arest"
select="document('cocoon:/arest')" />
<xsl:variable name="proline"
select="document('cocoon:/proline')" />
<xsl:template match="czesc">
<tr>
<td>
<p><xsl:value-of select="@nazwa"/></p>
</td>
<td>
Generowanie zestawienia
– arkusz XSLT
<xsl:for-each select="askryptor">
<xsl:variable name="czesc"
select="$age//*[.=current()]"/>
<xsl:if test="$czesc">
<a href=";" title="{.}">
<xsl:value-of select="$czesc/../../h:td[3]"/>
</a>
</xsl:if>
</xsl:for-each>
</td>
<td>
<xsl:for-each select="askryptor">
<xsl:variable name="czesc"
select="$arest//*[.=current()]"/>
<xsl:if test="$czesc">
<a href=";" title="{.}">
<xsl:value-of
select="$czesc/../../h:td[4]/h:a"/>
</a>
</xsl:if>
</xsl:for-each>
</td>
<td>
<xsl:for-each select="askryptor">
<xsl:variable name="czesc"
select="$proline//*[.=current()]"/>
<xsl:if test="$czesc">
<a href=";" title="{.}">
<xsl:value-of
select="$czesc/../../../h:td[3]"/>
</a>
</xsl:if>
</xsl:for-each>
</td>
</tr>
</xsl:template>
Software 2.0 06/2004
tora (src =”http://…” ), określający adres internetowy pobieranej strony. Ponieważ domyślnym generatorem jest
HTML Generator, więc będzie on wywoływany dla każdego sklepu. Uważny Czytelnik od razu spostrzeże coś, co
różni sklep Arest od innych. Chodzi o zmianę domyślnego
kodowania. Otóż sklep ten, w przeciwieństwie do dwóch
pozostałych, używa zamiast Latin 2 strony kodowej Windows 1250.
Wszystkie potoki sklepów kończą się serializacją przekształconego dokumentu źródłowego.
Potok zestawienia cen jest uruchamiany zapytaniem
HTTP zawierającym jego nazwę (zestawienie ). Pobiera on
słownik towarów (slownik.xml) i wykonuje na nim transformację XSLT zdefiniowaną w pliku glowny.xsl. Transformacja ta odwołuje się z kolei do potoków poszczególnych
sklepów, dzięki czemu integrujemy dane z nich pochodzące. Końcowym akcentem jest wygenerowanie docelowego
dokumentu HTML przez serializer i przekazanie go przeglądarce.
Przyjrzyjmy się teraz sercu naszego systemu – arkuszowi stylów XSLT, którego zadaniem jest generowanie zestawienia cen. Listing 3 przedstawia jego najważniejsze fragmenty, odpowiedzialne za integrowanie danych z poszczególnych sklepów oraz generowanie zestawienia dla konkretnej części.
Integracja danych z potoków poszczególnych sklepów została zrealizowana dzięki funkcji document()
dostępnej w XSLT. Przy jej pomocy ładujemy zewnętrzne dokumenty XML, przypisując ich zawartość
do zmiennych, a następnie wykorzystujemy je w przekształceniu na równi z zawartością dokumentu wejściowego. Cocoon oferuje wprawdzie bardziej eleganckie i elastyczne metody integrowania danych, na
przykład agregatory czy transformator XInclude, jednak proste rozwiązanie, na które się zdecydowaliśmy,
ma jedną podstawową zaletę: jest uniwersalne i można je wykorzystać także samodzielnie, bez użycia Cocoona.
Wygenerowanie tabeli cen polega na przejściu w pętli po wszystkich wpisach ze słownika. Każdy wiersz zawiera w pierwszej komórce nazwę części, pobraną ze słownika. Następne komórki zawierają ceny tej części w kolejnych sklepach, bądź są puste, jeśli dany sklep nie ma
w ofercie danej części. Jeśli część pojawia się w sklepie, to oprócz jej ceny udostępniamy użytkownikowi
odpowiadający jej askryptor, wyświetlany po najechaniu myszką na cenę. Dzięki temu użytkownik, dziwiący się ogromnej różnicy w cenach tego samego procesora, może się ewentualnie przekonać, że tak naprawdę w zestawieniu znalazły się dwa różne modele
(na przykład na skutek błędu autora zestawienia).
Pomińmy – nieistotne z punktu widzenia naszego
wywodu – szczegóły generowania układu strony oraz
www.software.com.pl
75
Warsztat
Integracja danych
niezwykle prosta, i w dodatku jednakowa dla wszystkich sklepów:
//*
Analizując treść strony w bezpośredniej okolicy
askryptora, łatwo jest też odkryć, w jaki sposób dotrzeć do odpowiedniego węzła z ceną. W przypadku
sklepu Age, ścieżka od nazwy części do jej ceny ma
postać:
../../h:td[3]
gdzie h jest przedrostkiem przestrzeni nazw XHTML.
Zatem aby we wzorcu czesc wypisać cenę konkretnej
części, musimy przeiterować po wszystkich askryptorach
tej części (nie wiemy bowiem, który askryptor występuje
w danym sklepie), i spróbować znaleźć bieżący askryptor
na stronie sklepu. W przypadku sklepu Age robimy to wyrażeniem:
Rysunek 3. Zestawienie cen z trzech sklepów
internetowych
nagłówków tabel dla poszczególnych kategorii i producentów, i przyjrzyjmy się szablonowi dla elementu
czesc , wypisującemu wiersz z nazwą części i jej cenami w kolejnych sklepach. Nie jest problemem wypisanie nazwy części – pochodzi ona ze słownika. Większe wyzwanie stanowi odnalezienie ceny tej części
w danym sklepie.
Podejście, które zastosujemy, opiera się na obserwacji, że sklepy publikują swoje cenniki HTML mimo wszystko w uporządkowanej, powtarzalnej postaci. Zazwyczaj nazwa części i jej cena znajdują się
w jednym wierszu tabeli, choć mogą być pozagnieżdżane w elementach <a>, <img>, <b>, itp. Istotne jest
to, że dysponując węzłem nazwy części, możemy –
używając względnego wyrażenia XPath – przejść do
węzła zawierającego jej cenę. Dla każdego węzła nazwy na stronie danego sklepu zrobimy to w ten sam
sposób. Aby więc umieć odnaleźć cenę danej części
ze słownika na konkretnej stronie, musimy:
• potrafić odnaleźć askryptor szukanej części w treści
strony,
• wymyślić względną ścieżkę XPath, która z nazwy części skieruje nas do jej ceny.
Spójrzmy, gdzie w strukturze strony dowolnego
z trzech sklepów mogą znajdować się nazwy części.
Wprawdzie strukturę tę można badać analizując źródło strony, to jednak trzeba się przy tym sporo napracować i łatwo się jest przy tym pomylić. Na stronach
WWW sklepów internetowych normą jest wielokrotne
zagnieżdżanie tabel (element <table>), dochodzą do
tego elementy <div>, <center>, <a>, <b> i tym podobne. Dlatego łatwiej i bezpieczniej będzie skorzystać ze
spostrzeżenia, że askryptorów możemy szukać po prostu w całej zawartości strony! Ścieżka XPath wskazująca położenie askryptora w treści strony będzie więc
76
$age//*[.=current()]
Wartość wyrażenia (węzeł zawierający askryptor) zapamiętujemy na zmiennej czesc w celu późniejszego przeniesienia się z niej do węzła ceny. Znając (specyficzną dla
każdego sklepu) drogę od węzła części do jej ceny, wypisujemy ją, dodatkowo umieszczając w atrybucie title łącza sam askryptor.
Efekt
Mając przygotowaną witrynę, nie pozostaje nam nic
innego, niż ją uruchomić i cieszyć się efektem. Uruchamiamy Cocoona z jego głównego katalogu poleceniem:
cocoon servlet
a następnie wprowadzamy w przeglądarce adres:
http://localhost:8888/zestawienie-cen/zestawienie
Po pewnym czasie, niezbędnym do ściągnięcia danych ze
sklepów oraz ich zintegrowania, ujrzymy stronę zestawienia.
Stworzony w ten sposób serwis jest bardzo uproszczony i ma charakter prototypowy. Brakuje mu prawdziwej szaty graficznej, nie ma opcji wyszukiwania interesujących nas towarów, ale działa na prawdziwych
danych.
Ci z Czytelników, którym pomysł integracji się spodobał, mogą przekształcić zaproponowany system tak, aby
działał również dla innych sklepów internetowych z częściami komputerowymi a także dla kategorii innych niż
procesory. Powodzenia!
www.software.com.pl
Software 2.0 06/2004

Podobne dokumenty