Delphi - THTMLProducer komponens

Internet áruház 6. rész

forráskód letöltése
Múlt heti ígéretünkhöz híven elkészítünk egy olyan komponenst, mely nagymértékben segíti majd további munkánkat a web áruház készítésekor. A mellékelt példaprogram megnyitása előtt a következő teendőket kell elvégezni, hogy a példa működőképes legyen:
  • A HTMLProducer.pas-ban lévő komponenst telepítenie kell a Delphi alá.
  • Kérjük, hozza létre az ASWEBSHOP06 alias-t. Ez mutasson a mellékelt példaprogram Data könyvtárára, az adatbázis elérés végett.
  • A programban talál egy HTMDIR konstanst. Ez szintén a Data könyvtár elérési útvonalát kell hogy tartalmazza, ha Ön ettől eltérő helyre másolta a példát, akkor fordítás előtt kérjük módosítsa ezt.
  • A lefordított WS06.EXE-t helyezze a web szerver scripts könyvtárába, ahonnan az futtatható.
  • A mellékelt HTM könyvtár tartalmát pedig helyezze el a web szerveren úgy, hogy az egy böngészőn keresztül elérhető legyen. Például: C:\Inetpub\wwwroot\webshop\06\. Ekkor a böngészőben a www.animare.hu/webshop/06/ cím beírásakor meg kell hogy jelenjen a kezdő oldal. Persze a web szerver neve mindenkinél más és más lesz. A mellékelt példában a webshop\06\ könyvtárra több hivatkozás is van, így kérjük ettől Ön se térjen el.


A jelenlegi cikk elolvasása előtt javasoljuk a sorozat előző részeinek megismerését, mivel a mostani ismeretek csupán kiegészítői az előzőeknek, így azok ismerete nélkül e konkrét cikk tartalma sem érthető meg teljes egészében.

A web áruház jelenlegi verziója semmi újat nem tud a múlt hét óta, viszont az EXE-ben lévő komponens igen nagy mértékben leegyszerűsítette a fejlesztést és a továbbiakban is megkönnyíti majd az életünket.

Amint említettük a program ugyanannyit tud, mint múlt héten, de hasonlítsuk csak össze a Unit1.pas forráskódját. Láthatjuk, hogy a mostani szinte alig tartalmaz kódot, a WebModul-ban is csupán egyetlen THTMLProducer komponens van a táblán kívül, és nincs szükség három TPageProducer-re.

Nézzük mit is tud a THTMLProducer komponens. Erre a komponensre tekinthetünk úgy, mint egy továbbfejlesztett TPageProducer komponensre. Működésének a lényege is ugyanaz: a <#cimke> típusú címkéket cseréli egy-egy HTML web lapon belül egy általunk megadható tetszőleges sztringre.

Ennek módja viszont merőben más, mint a TPageProducer komponensnél.

Legyen bármilyen bonyolult is a web lapjaink szervezése, akár egy-egy web oldal dinamikusan összeszerkeszthető tetszőleges számú web lap részletekből, de kerülhet akár egy-egy részlet ismétlésre is egy web oldalon belül tetszőleges számban, mindehhez elegendő egyetlen THTMLProducer komponens az egész programba. Mivel a komponens akár egy web lap feldolgozása közben képes egy másik web lap feldolgozására is, így tényleg elegendő belőle egy darab, bármilyen összetett is a site. Cégünk web lapjait kezelő szoftverben is ez a most közreadott komponens egy változata tevékenykedik és annak ellenére, hogy egy 17 000 soros programról van szó, elegendő a komponensből egy darab.

Nézzük a működését:

A készítendő web lap létrehozásához egy Content nevű függvényt kell meghívnunk. Itt az egyetlen sztring paraméterében kell majd megadnunk a feldolgozandó HTML állományt. Visszatérési értékként kapjuk a feldolgozott HTML állományt sztringként.

Létrehoztunk egy FHTMLFile nevű változót, melyben az aktuálisan feldolgozandó állományt tároljuk el.
A Content nevű függvényben először átmenetileg egy lokális változóba elmentjük a FHTMLFile tartalmát, majd értékül adjuk az újat. Ezek után kerül meghívásra a Process függvény, mely elvégzi a tényleges feldolgozást. Miután végzett, visszatérési értékként megkapjuk a végleges web lapot, melyet a Content függvény is visszaad. Végül visszaállítjuk a FHTMLFile változó eredeti értékét. Erre azért van szükség, mert lehet, hogy a feldolgozás közben egy másik web lapot is fel kell dolgozni és ez a mentés és visszaállítás biztosítja azt, hogy a komponens Content függvényét akkor is meghívhatjuk, ha az előző futása még nem ért véget.

Nézzük, hogyan is történik a feldolgozás a Process függvényben.

Itt találunk egy lokális változót, mely nem mást, mint a már jól ismert TPageProducer komponens. Mivel a TPageProducer-nek megvan az a képessége, hogy a feldolgozandó web lapról kikeresi a címkéket, melyeket tetszőleges szövegre cserélhetünk le, így felhasználjuk ezt a komponenst a saját komponensünkön belül, hogy a címke kereső és cserélő funkciót már ne kelljen leprogramoznunk.

Tehát első lépésként létrehozunk egy TPageProducer komponenst, melybe a HTMLFile property-n keresztül megadjuk az aktuálisan feldolgozandó HTML állományt.

Ha a TPageProducer komponens címkét talál a feldolgozás közben, akkor létrejön az OnHTMLTag eseménye. Így ezt fel kell használnunk, ezért hozzárendelünk egy DoHTMLTag nevű eseménykezelő eljárást.

Más teendőnk nem marad, mint hogy meghívjuk a TPageProducer komponens Content függvényét, mely feldolgozza a web lapot és egyetlen sztringként visszaadja annak a tartalmát. Ezt az sztringet adjuk mi is a Process függvényünk visszatérési értékének.

Legvégül megszüntetjük a létrehozott TPageProducer komponenst a Free eljárás hívásával.
function THTMLProducer.Process(htmlfile: string): string;
var
  pp: TPageProducer;
begin
  pp:=TPageProducer.Create(nil);
  pp.HTMLFile:=FHTMLFile;
  pp.OnHTMLTag:=DoHTMLTag;
  inc(FCounter);
  result:=pp.Content;
  pp.Free;
end;
Következő lépésként most vizsgáljuk meg, hogy mi is történik akkor, ha egy címkét talál a TPageProducer komponens feldolgozás közben. Ekkor létrejön az OnHTMLTag eseménye, amelyhez a DoHTMLTag eljárást rendeltük. Nézzük, hogyan is működik ez az eljárás.

A THTMLProducer komponensünk használatakor be kell tartanunk egy olyan szabályt, hogy bár web lapon tetszőleges nevű címkék használhatóak, de mostantól az #include nevű címke egy fenntartott kulcsszó lesz. Ha ugyanis ilyen nevű címkét helyezünk el a web lapon, akkor a komponensünk ezt olyan parancsnak értelmezi, hogy itt most egy új web lapot kell beágyaznia a feldolgozandó web lapba.

Ennek megfelelően a DoHTMLTag eljárás először ellenőrzi, hogy a talált címke egyezik-e az "include" szóval. Ha igen, akkor megkezdődik az új web lap beágyazása.

Az "include" címke használata a következő: ha szeretnénk egy web lapba egy másikat beágyazni, akkor az "include" címke megadása után még két paramétert kell megadnunk. Az egyik arra vonatkozik, hogy a beágyazandó web lap melyik könyvtárban található, a másik pedig az állomány nevét fogja meghatározni. Ennek megfelelően lehet a következő címke:
<#include path=TMPDIR file=index.tmp>
Látható, hogy a könyvtár nevét a "path" nevű paraméter tárolja és az is észrevehető, hogy nem egy valós elérési útvonalat adtunk meg, hanem csupán egy azonosítót, egy alias-t. Ehhez majd programból rendeljük hozzá a tényleges elérési útvonalat. A másik paraméter "file" névvel szerepel. Itt megadjuk a beágyazni kívánt állomány nevét.

A THTMLProducer komponensben létrehoztunk két TStringList típusú property-t is DirAlias és DirPath néven. A DirAlias-ban sorolhatjuk fel azokat az azonosítókat, melyekkel elérési útvonalakat szeretnénk azonosítani. Fenti példában megadtunk egy TMPDIR nevű azonosítót is. A DirAlias property-ben kell tehát megadnunk ezt az azonosítót, majd a DirPath-ban megadhatjuk a hozzá tartozó tényleges elérési útvonalat.

Erre a bonyolításra azért van szükségünk, mert így nem kell állandóan a web lapokra begépelnünk a teljes elérési útvonalat, arról nem is beszélve, ha mondjuk szeretnénk megváltoztatni azt. Ilyen esetben elegendő csupán egyszer, egy helyen megadnunk az új elérési útvonalat és nem kell az összes web lapunkat módosítani. Ez az alias hasonló az adatbázisoknál használttal, ott sem kell állandóan az elérési útvonalat így begépelgetnünk.

Térjünk vissza a DoHTMLTag eljáráshoz. Tehát ha azonosítjuk az "include" címkét, akkor a kapott "path", illetve "file" paraméterekből, valamint a DirAlias és DirPath property-komponens segítségével meghatározhatjuk azt az állományt, melyet szeretnénk beágyazni az aktuális web lapra.

A beágyazás előtt szintén egy lokális változóba (prevhtml) eltároljuk az aktuálisan feldolgozás alatt álló web lap nevét. Majd a beágyazandó web lapot tesszük aktuálissá és egyszerűen meghívjuk ismét a Process eljárást.

Ez nem más, mint egy rekurzív eljárás hívás. Ekkor a Process létrehoz egy újabb TPageProducer típusú komponenst a lokális változójába, majd feldolgozza azt a DoHTMLTag eljárás segítségével, amely ha találna egy újabb "include" azonosítót, akkor ismét folytatódna a rekurzív állomány feldolgozás, mindaddig, amíg vannak feldolgozandó címkék.

Nézzük most a DoHTMLTag eljárás másik ágát, vagyis azt az esetet, amikor a talált címke nem az "include" szó. Ekkor van a komponensnek szüksége "külső" segítségre. Mivel azt már nem tudhatja, hogy az egyedi címkéket milyen tartalomra kell kicserélnie, ezért itt létrehozunk egy saját eseményt OnCustomHTMLTag névvel.

Mivel az állományok feldolgozása rekurzív, így nem is tudhatjuk, hogy a talált címke melyik állományban van. Erre nincs is szükségünk, ha betartunk egy újabb szabályt: bármely web lapot is készítjük, minden címkének egyedinek kell lennie, kivéve ha azonos funkciót szeretnénk végrehajtatni. Erre például a dátum kiírási probléma felel meg. Vagyis ha több web lapra is szeretnénk kiíratni az aktuális dátumot, akkor elegendő ezekre a web lapokra mondjuk a #datetime címkét elhelyezni. Ekkor bármelyik web lap is legyen feldolgozva, a #datetime címkére mindig ugyanaz a funkció hajtódik végre, vagyis ez a címke az aktuális dátum sztringjére lesz kicserélve.

A címkék egyszerűbb kezelésének érdekében létrehozunk egy harmadik TStrignList típusú property-t is a THTMLProducer komponensen belül, CustomTagList névvel.
Itt felsorolhatjuk a web lapjainkon előforduló címkék neveit.

Amikor a DoHTMLTag eljárás egy címkét talál, mely nem az "include" szó, akkor ebben a property-ben fogja keresni a talált szót és a generált eseményben paraméterként adja meg az adott címke sorszámát.

Ehhez az eseményhez viszont kell készítenünk egy saját típusú eseményt. Ez lesz a TCustomHTMLTagEvent típus, melyet az OnCustomHTMLTag eseménynél használunk fel.
Így az esemény első paraméterében a CustomTagList-ben lévő címke sorszámát kapjuk. Második paraméterként megkapjuk az adott címkéhez esetlegesen tartozó paramétereket. Majd a harmadik paraméter lesz az a sztring, melyre az adott címkét kell lecserélni.


A komponenshez létrehozunk egy új eseményt, mely akkor jön létre, ha a feldolgozás folyamán az "include" címke esetén egy beágyazandó web lap állománya nem található a megadott helyen. Ekkor generálja a komponens az OnFileNotFound eseményt. Az esemény paraméterében pedig megkapjuk a nem létező állomány nevét. Mivel a feldolgozás egy ilyen hiba esetén nem szakad meg, így a web lap elkészül, de nyilván a hiányzó állomány nem lesz beágyazva a végső web lapba.

Szükségessé válhat a valós felhasználás közben az is, hogy legyen egy olyan esemény párosunk, mely minden állomány beágyazás előtt, illetve után létrejön.
Ehhez hoztuk létre az OnBeforeProcessInclude és az OnAfterProcessInclude eseményeket.
Ha tehát a komponens feldolgozás közben valamely állományban megtalálja az "include" címkét, akkor mielőtt végrehajtaná a műveletet, meghívja az OnBeforeProcessInclude eseményt, majd ha végzett az adott állomány feldolgozásával, akkor jön az OnAfterProcessInclude esemény.

Az OnBeforeProcessInclude eseménynél lehetőségünk van arra, hogy a kapott Enabled logikai típusú paraméter segítségével letiltsunk egy-egy állomány beágyazást.

Mivel a THTMLProducer komponens egyetlen web lap készítésekor is igen nagy mennyiségű állományt dolgozhat fel, vagyis ágyazhat egymásba, így nem biztos, hogy a fenti két eseményre minden esetben, minden állománynál szükségünk van. Másik probléma, hogy amikor létrejön az esemény, akkor tudnunk kellene, hogy aktuálisan melyik is az az állomány, mely épp beágyazásra fog kerülni, vagy beágyazásra került.

E két probléma megoldására hoztuk létre a BeforeProcessFiles és az AfterProcessFiles property-ket. Mindkettő TStringList típusú. Használatuk kissé hasonló a CustomTagList property-éhez. Itt viszont azokat az állomány neveket kell felsorolnunk, melyek esetében szeretnénk, ha létrejönne az adott esemény.

Az esemény hívása előtt a komponensünk végignézi ezeket a listákat és ha megtalálja az adott állományt, csak akkor hozza létre az adott eseményt, melynek rögtön átadja az állomány TStringList-beli pozíciójának számát is. Ezt kapjuk az esemény Index nevű paraméterében.
Ezt felhasználva már el tudjuk ágaztatni a programunkat, így minden egyes állomány esetében más és más kódot rendelhetünk az adott eseményhez.


A komponens után most tekintsük át annak felhasználását.

Amikor belépünk az áruházba, akkor meg kell jeleníteni a termékek adatbázisában tárolt áruinkat. Ekkor fut a web modul első akciója:
procedure TWebModule1.WebModule1WebActionItem1Action
  (Sender: TObject; Request: TWebRequest; Response: 
  TWebResponse; var Handled: Boolean);
begin
  try
    Response.Content:=HTMLProducer1.Content(HTMDIR
      +'main.htm');
  except
    on e: exception do Response.Content:=e.message;
  end;
end;
Amint látható csak annyi a teendőnk, hogy a HTMLProducer1 komponens Content függvényét hívjuk, paraméterként pedig átadjuk a main.htm állományt feldolgozásra.

Ebbe a htm-be két címkét helyeztünk el. Mielőtt a feldolgozás megtörténik és a termékek adatbázisának web lapja, a product.dat beágyazásra kerül, létrejön az OnBeforeProcessInclude esemény. Itt megnyithatjuk a tProduct táblát.
procedure TWebModule1.HTMLProducer1BeforeProcessInclude
    (Index: Integer; var Enabled: Boolean);
begin
  case Index of
    0: tProduct.Open;
  end;
end;
Lezárására a feldolgozás után kerülhet sor:
procedure TWebModule1.HTMLProducer1AfterProcessInclude
    (Index: Integer);
begin
  case Index of
    0: tProduct.Close;
  end;
end;
Amikor az egyes rekordoknál lévő productitem.dat kerül feldolgozásra, akkor az ebben található címkéket az OnCustomHTMLTag eseménynél cserélhetjük az adatbázis megfelelő mezőire:
procedure TWebModule1.HTMLProducer1CustomHTMLTag
    (Index: Integer; TagParams: TStrings; var ReplaceText: String);
begin
  case Index of
    0: ReplaceText:=tProductNAME.Value;
    1: ReplaceText:=tProductPRICE.AsString+' Ft + '
         +tProductTAX.AsString+'% ÁFA = '
         +IntToStr(Round(tProductPRICE.Value
         *(tProductTAX.Value/100+1)))+' Ft';
    2: ReplaceText:=tProductNOTE.Value;
    3: ReplaceText:=IntToStr(HTMLProducer1.Counter);
  end;
end;
Hogy hány darab rekord kerüljön kiírásra azt az OnMoreData eseménynél adhatjuk meg:
procedure TWebModule1.HTMLProducer1MoreData
   (Index: Integer; var MoreData: Boolean);
begin
  case Index of
    0: begin
      MoreData:=not tProduct.EOF;
    end;
  end;
end;
Végső lépésként csak az adatbázist kell léptetnünk a következő rekordra miután az aktuálisnak a feldolgozása megtörtént.
procedure TWebModule1.HTMLProducer1AfterRepeatInclude
    (Index: Integer);
begin
  case Index of
    0: begin
      tProduct.Next;
    end;
  end;
end;

Internet áruház cikksorozat