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&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&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