Connect with us

Cześć, czego szukasz?

Internet Maker

Analiza dokumentu DTD przy użyciu wyrażeń regularnych PCRE w PHP

Analiza składni, nazywana parsingiem, jest bardzo często pierwszym etapem przetwarzania. Parsing poprzedza między innymi wyświetlenie dokumentu HTML przez przeglądarkę (parsing dokumentu HTML) oraz stosowanie stylów (parsing dokumentu CSS). W artykule przedstawię przykładową implementację parsingu dokumentu DTD.

Wynikiem przetworzenia dokumentu DTD są pliki XML zawierające wszystkie informacje na temat składni języka HTML 4.01 strict. Opis w języku XML może być wykorzystywany do przygotowania walidatora HTML czy ściągawki zawierającej zestawienie wszystkich elementów i atrybutów języka HTML wraz z ograniczeniami użycia.

Parsing bez zagnieżdżeń

W wielu językach występuje ograniczenie zakazujące zagnieżdżania pewnych konstrukcji. Na przykład w języku HTML nie wolno zagnieżdżać komentarzy:

PRZYKŁAD POPRAWNY

PRZYKŁAD NIEPOPRAWNY

 dolor...
-->

Wartości atrybutów HTML należy ująć w cudzysłów, przy czym cudzysłowu nie wolno zagnieżdżać:

PRZYKŁAD POPRAWNY

...

PRZYKŁAD NIEPOPRAWNY

...

Nie wolno również zagnieżdżać znaków {stala}<{/stala}oraz {stala}>{/stala} w dokumencie HTML:

PRZYKŁAD POPRAWNY

lorem

PRZYKŁAD NIEPOPRAWNY

> lorem

komentarzy CSS:

PRZYKŁAD POPRAWNY

body {
   /* lorem ipsum dolor... */
}

PRZYKŁAD NIEPOPRAWNY

body {
   /* lorem /* ipsum */ dolor... */
}

wpisów ENTITY w dokumentach DTD:

PRZYKŁAD POPRAWNY

PRZYKŁAD NIEPOPRAWNY


>

ani komentarzy wewnątrz wpisów DTD:

PRZYKŁAD POPRAWNY

PRZYKŁAD NIEPOPRAWNY

Napisy te mogą być jednoznakowe:

\"(.*?)\"
<(.*?)>

wieloznakowe:


--(.*?)--

identyczne:

--(.*?)--
\"(.*?)\"

oraz różne:


<(.*?)>

Tabela 1 przedstawia sumaryczne zestawienie wyrażeń regularnych do parsingu niezagnieżdżonych zapisów wyznaczonych przez napis początkowy i końcowy.

Kwantyfikatory leniwe w wyrażeniach PCRE zapisujemy dodając znak zapytania (np. {stala}*?{/stala}) lub stosując modyfikator U (np. {stala}/…/U{/stala}).

Pamiętajmy, że znaczenie znacznika końcowego może być w wielu językach wyłączone. Tak się dzieje np. w przypadku napisu {stala}?>{/stala}, kończącego skrypt PHP. Znacznik {stala}?>{/stala}, który znajduje się wewnątrz napisu drukowanego instrukcją echo, nie kończy skryptu:

\';
?>

Podobna sytuacja ma miejsce w przypadku znaków {stala}\'{/stala} i {stala}\”{/stala} poprzedzonych backslashem:

echo \'I\\'m a king bee\';

Wyrażenia regularne podane w tabeli 1 wymagają pełnej gwarancji braku zagnieżdżenia, bez żadnych metod cytowania. W odniesieniu do napisów:

START = 

\';
?>

PRZYKŁAD NIEPOPRAWNY

WYRAŻENIE REGULARNE: 

START = \'
STOP = \'

\'I\\'m a king bee\'

PRZYKŁAD NIEPOPRAWNY

WYRAŻENIE REGULARNE: \'([^\']+)\'

otrzymane wyniki będą błędne.

Jedną z metod umieszczania znacznika końca w analizowanym napisie jest zamiana na inne znaki. W języku HTML znaki {stala}<{/stala} i {stala}>{/stala} są cytowane poprzez zamianę na encję {stala}<{/stala} oraz {stala}>{/stala}.

START = <
STOP = >

PRZYKŁAD POPRAWNY

WYRAŻENIE REGULARNE: <([^>]+)>

W przypadku takich metod cytowania opisana metoda oraz wyrażenia spisują się bez zarzutu.

We wszystkich przypadkach mamy do czynienia z wyodrębnieniem fragmentu rozpoczynającego się i kończącego przez zadane napisy. Napisy te mogą przyjąć postać pojedynczych znaków:

znacznik HTML: <....>
wartość atrybutu HTML: \"...\"

oraz ciągów znaków:

komentarz HTML: 
komentarz HTML: /*...*/
wpis DTD: 
komentarz wewnątrz ENTITY DTD: --...--

Ponadto napisy otwierający i zamykający mogą być identyczne:

wartość atrybutu HTML: \"...\"
komentarz wewnątrz wpisu DTD: --...--

lub różnić się:

znacznik HTML: <....>
komentarz HTML: 
komentarz HTML: /*...*/
ENTITY DTD: 

W jaki sposób wyodrębnić fragment tekstu ujęty w napisy, których nie wolno zagnieżdżać? Należy do tego wykorzystać wyrażenia regularne PCRE z zanegowanym zbiorem znaków (gdy napis końcowy jest pojedynczym znakiem) lub z kwantyfikatorem leniwym (gdy znacznik końca jest dłuższym napisem).

Jednoznakowy parsing bez zagnieżdżeń

W celu wyodrębnienia fragmentu napisu określonego przez znak początkowy (np. {stala}<{/stala}) i znak końcowy (np. {stala}>{/stala}) należy użyć wyrażenia regularnego:

<([^>]*)>

W przypadku znaków początkowego i końcowego {stala}\”{/stala} wyrażenie to przyjmie postać:

\"([^\"]*)\"

Ogólnie, wyrażenie regularne jest następujące:

XXX([^Y]*)Y

gdzie {stala}XXX{/stala} jest dowolnym niepustym napisem (napis ten może być wieloznakowy). Natomiast {stala}Y{/stala} jest pojedynczym znakiem.

Wieloznakowy parsing bez zagnieżdżeń

Do wyodrębnienia fragmentu wyznaczonego przez napisy początkowy (np. {stala}{/stala}) należy użyć wyrażenia regularnego:

Kwantyfikator leniwy {stala}*?{/stala} zatrzyma się na pierwszym wystąpieniu napisu końcowego {stala}–>{/stala}.

Z racji na konieczność cytowania znaków {stala}/{/stala} oraz {stala}*{/stala} w przypadku komentarzy CSS wyrażenie to przyjmie postać:

\/\*(.*?)\*\/

Komentarze wewnątrz wpisów DTD pasują do wyrażenia:

--(.*?)--

Zaś wpisy ENTITY do wyrażenia:

(Zauważmy, że napis końca jest w tym przypadku jednoznakowy. Dlatego wyrażenie to można zastąpić wyrażeniem z ogranicznikiem jednoznakowym {html}]*)>{/html}). Ogólnie, wyrażenie regularne przyjmie postać:

START(.*?)STOP

gdzie {stala}START{/stala} i {stala}STOP{/stala} są dowolnymi niepustymi napisami.

Parsing napisu zawierającego kilka rodzajów wpisów

Niech dany będzie napis {stala}$src{/stala}, który należy poddać parsingowi. Napis ten składa się z ciągu wpisów, których nie można zagnieżdżać i które być może są oddzielone białymi znakami. Załóżmy, że każdy z wpisów pasuje do jednego z wyrażeń regularnych: {stala}$re1, $re2, …, $reN{/stala} oraz, że podane wyrażenia regularne rozpoczynają się od znaku {stala}^{/stala} (tj. są zakotwiczone na początku).

Wówczas analiza całego napisu {stala}$src{/stala} z gwarancją wychwycenia wszystkich elementów niepoprawnych przyjmie postać przedstawioną na listingu 1. Oczywiście napis {stala}$src{/stala} może pochodzić z pliku:

$src = trim(file _ get _ contents(\'filename.txt\'));
$re1 = \'/^.../\';
$re2 = \'/^.../\';
...
$reN = \'/^.../\';

while ($src) {

    if (preg_match($re1, $src, $m)) {
        ...//przetwarzanie 1
    } elseif (preg_match($re2, $src, $m)) {
        ...//przetwarzanie 2
    } elseif (preg_match($re3, $src, $m)) {

    ...

    } elseif (preg_match($reN, $src, $m)) {
        ...//przetwarzanie N
    } else {
        die(\'ERROR\');
    }

    $src = trim(
        preg_replace(
            \'/^\' . preg_quote($m[0], \'/\') . \'/\',
            \'\',
            $src
        )
    );
}

Struktura dokumentu DTD

Składnia języków HTML oraz XHTML jest formalnie opisana przez dokumenty DTD (ang. Document Type Definition). Pliki DTD są zawarte w specyfikacji języka. Dokument DTD języka HTML 4.01 strict zawiera sześć rodzajów wpisów:

  • ENTITY
  • ELEMENT
  • ATTLIST
  • komentarze {stala}{/stala}
  • odwołania do plików zewnętrznych ({stala}%HTMLlat1;{/stala}, {stala}%HTMLsymbol;{/stala}, {stala}%HTMLspecial;{/stala})
  • wpisy zarezerwowane {html}{/html}.

Pierwszy z nich, ENTITY, służy do tworzenia makrodefinicji. Na przykład wpis:

definiuje makro postaci {stala}%fontstyle;{/stala}. Napisy {stala}%fontstyle;{/stala} zostają rozwinięte do postaci {stala}TT | I | B | BIG | SMALL{/stala}.

Drugi rodzaj wpisów definiuje poszczególne elementy języka HTML. Na przykład wpis:

definiuje dwa elementy: {stala}SUB{/stala} oraz {stala}SUP{/stala}.

Wreszcie trzeci rodzaj wpisów, ATTLIST, służy do ustalenia listy atrybutów wybranego elementu. Zapis:

oznacza, że elementy {stala}SUB{/stala} oraz {stala}SUP{/stala} zawierają atrybuty zdefiniowane encją {stala}%attrs;{/stala}.

Zauważmy, że wewnątrz wpisów ENTITY, ELEMENT, ATTLIST komentarze rozpoczynają się i kończą napisem {stala}–{/stala}:

-- wielolinijkowy komentarz wewnątrz
    wpisów ENTITY/ELEMENT/ATTLIST --

W ostatnim etapie przetwarzamy wpisy ATTLIST. Definiują one atrybuty elementów HTML.

Najpierw, w pętli, przetwarzamy wszystkie pliki z folderu {stala}attlist/{/stala}. Zawartość każdego pliku dopasowujemy do wyrażenia regularnego:

$re = \'|
   
|xs\';

Wyrażenie to jest najbardziej skomplikowane. Wynika to z faktu, że niektóre wpisy ATTLIST zawierają w pierwszej linii komentarz:


a niektóre nie:

W wyrażeniu regularnym PCRE konstrukcja:

(?(warunek) wyrażenie )

jest dopasowywana warunkowo. Podane wyrażenie pasuje wyłącznie, gdy spełniony jest warunek. W roli warunku natomiast użyte jest pozytywne przewidywanie:

(?=--)

Jeśli kolejnymi dwoma znakami są {html}--{/html} (pozytywne przewidywanie ({html}?=--{/html})), to dopasujemy wyrażenie {html}--.*?--\\s*{/html}. Takie jest znaczenie fragmentu:

(?(?=--)--.*?--\s*)

W ten sposób jednym wyrażeniem regularnym przetwarzamy oba rodzaje wpisów.

Po przetworzeniu plików tekstowych tworzona jest tablica {stala}$attlists{/stala}, w której następnie:

  • wymieniamy encje,
  • przetwarzamy wpisy w postaci alternatywy {stala}a|b|c{/stala},
  • po czym przetwarzamy listy atrybutów.

Atrybuty pasują do jednego z czterech wyrażeń regularnych. Do wyrażenia {stala}$reA{/stala}:

$reA = \'/
   ^
   (\S+)\s+      #nazwa
   (\S+)\s+      #typ wartosci
   (\S+)\s+      #wartosc domyslna
   --(.*)--    #komentarz
/sxU\';

pasują wpisy pełne:

disabled (disabled) #IMPLIED -- unavailable in this context --

Do wyrażenia {stala}$reB{/stala}:

$reB = \'/
   ^
   (\S+)\s+       #nazwa
   --(.*?)--    #komentarz
/sx\';

pasują wpisy bez podanych typów wartości oraz wartości domyślnej:

%coreattrs; -- id, class, style, title --

Wyrażenie regularne {stala}$reC{/stala}:

$reC = \"/
   ^
   (\S+)\s+    #nazwa
   (\S+)\s+    #typ wartosci
   ([^\n]+)+  #wartosc domyslna
   \n
/sx\";

odpowiada za wpisy bez komentarza:

enctype %ContentType; \"application/x-www- form-urlencoded\"

Zaś wyrażenie regularne {stala}$reD{/stala}:

$reD = \"/
   ^(\S+)$     #nazwa
/sx\";

odpowiada wpisom krótkim:

Po przeanalizowaniu atrybutów wyrażeniami regularnymi {stala}$reA{/stala}, {stala}$reB{/stala}, {stala}$reC{/stala} oraz {stala}$reD{/stala} otrzymane dane zapisujemy w formacie XML.

Podobnie jak w przypadku wpisów ELEMENT, główna charakterystyka każdego pliku XML jest wyrażona przez klasę: {stala}class=\"single\"{/stala} odpowiada wpisom, które definiują atrybuty pojedynczych elementów HTML, {stala}zaś class=\"multiple\"{/stala} to wpisy definiujące atrybuty wielu elementów naraz. Elementem głównym w pliku XML jest {stala}attlist{/stala}.

Listing 9 ilustruje zawartość XML opisującą atrybuty elementów {stala}DT{/stala} i {stala}DL{/stala}, zaś listing 10 atrybuty elementu {stala}FORM{/stala}.


    (DT|DD)
    
    
    
        DT
        DD
    
    
        %attrs;
    

    FORM
    
        %attrs;
    
    
        
            action
            %URI;
            #REQUIRED
            server-side form handler
        
        
            method
            (GET|POST)
            GET
            HTTP method used to submit the form
        
        ...
    

Gdy gotowe są cztery skrypty odpowiedzialne za kolejne etapy parsingu, możemy przygotować skrypt realizujący całe przetwarzanie. W skrypcie {stala}parse-dtd.php{/stala} umieszczamy cztery instrukcje {stala}include{/stala} dołączające skrypty odpowiedzialne za poszczególne fazy:

Wywołanie skryptu {stala}parse-dtd.php{/stala} będzie wykonywało kompletny parsing dokumentu DTD języka HTML 4.01 strict.

Podsumowanie

Jaki jest cel opisanego przeze mnie zadania? Widzę dwa poważne zastosowania. Pierwszym z nich jest opracowanie walidatora języka HTML, a drugim - pełnego zestawienia wszystkich elementów i atrybutów wraz z podstawowymi ograniczeniami składniowymi. Zestawienie tego typu jest dostępne pod adresem http://www.eskomo.com/~bloo/indexdot/ html/index.html.

W generowanych dokumentach XML umieszczam zawsze oryginalny wpis z pliku DTD. Pozwoli to na szybsze odnalezienie ewentualnych błędów czy nieścisłości. Pole to wymusza użycie formatu XML zamiast zwykłych plików tekstowych. Dzięki formatowi XML generowane dokumenty są czytelne i mogą zawierać długie fragment tekstu DTD (także ze znakami złamania wiersza).

Zauważmy, że pliki XML opisujące elementy HTML zawierają sekcje {stala}canContain{/stala}:


   UL
   
      LI
   


   LI
   
      P
      H1
      H2
      ...
   

Dzięki temu można w prosty sposób stwierdzić czy dany element może być zawarty w innym elemencie oraz wygenerować dwie listy:

  • co dany element może zawierać,
  • gdzie dany element może być umieszczony.

Dla elementu {stala}LI{/stala} listy te przyjmą postać:

  • może zawierać: {stala}P{/stala}, {stala}H1{/stala}, {stala}H2{/stala}, ...,
  • może być zawarty w: UL.

Wprawdzie podane listy nie wyczerpują możliwości poprawnego złożenia elementów (m.in. z racji na użycie kwantyfikatorów {stala}?{/stala} czy operatorów {stala}&{/stala} oraz, w DTD), jednak mogą się okazać przydatne przynajmniej do eliminacji zupełnie niepoprawnych złożeń.

Opisane rozwiązanie, po niewielkich modyfikacjach, będzie działało również w odniesieniu do DTD języka XHTML 1.0 strict. Dokumenty DTD języków HTML oraz XHTML są zawarte w specyfikacjach dostępnych na stronach W3C. Dokumentację języka HTML 4.01 znajdziemy pod adresem http://www.w3.org/TR/1999/REC-html401-19991224, zaś języka XHTML 1.0 pod adresem http://www.w3.org/TR/2002/REC-xhtml1-20020801.

Ogólny schemat przetwarzania plików z folderu {stala}entity/{/stala} jest następujący: wyszukujemy wszystkie pliki z folderu {stala}entity/{/stala} i kolejno każdy z nich:

  • odczytujemy,
  • sprawdzamy czy odczytana zawartość pasuje do wyrażenia regularnego,
  • dopasowaną zawartość przekształcamy w tablicę asocjacyjną.

W wyniku wykonania opisanego algorytmu, przedstawionego w skrócie na listingu 3, powstaje tablica asocjacyjna {stala}$entities{/stala}. Tablica ta jest w dalszej części skryptu poddawana kolejnym przekształceniom:

  • rozwinięcie encji (np. zastąpienie napisu {stala}%list;{/stala} wartością {stala}OL | UL{/stala}),
  • przetworzenie elementów w postaci alternatywy (np. {stala}H1|H2|H3{/stala}) w listę,
  • przetworzenie listy atrybutów,
  • dodanie atrybutów w postaci alternatywy,
  • przetworzenie listy wartości (np. {stala}left|center| right{/stala}),
  • ustalenie typu encji,
  • zapis XML,
  • zapis TXT.
$entities = array();

$pliki = glob(\'entity/*.txt\');
foreach ($pliki as $plik) {

    $p = trim(file_get_contents($plik));

    if (preg_match($re1, $p, $m)) {

        $ent = array();

        $ent[\'name\'] = trim($m[1]);
        $ent[\'dtd\'] = htmlspecialchars(trim($m[0]));
        $ent[\'definition\'] = trim($m[2]);
        ...

        array_push($entities, $ent);

    } elseif (preg_match($re2, $p, $m)) {
        ...
        array_push($entities, $ent);

    } elseif (preg_match($re3, $p, $m)) {
        ...
        array_push($entities, $ent);

    } else {
        echo \'***ERROR ***\';
    }
}

Wynikiem wykonania tego etapu są dwa pliki tekstowe oraz pliki XML. Pliki tekstowe {stala}entities-dictionary.txt{/stala} oraz {stala}entities-dictionary2.txt{/stala} posłużą do rozwijania encji podczas parsingu wpisów ELEMENT oraz ATTLIS. Pierwszy z nich zawiera wszystkie encje w pełnej postaci i ich rozwinięcie:

%LanguageCode; NAME
%Character; CDATA
%pre.exclusion; IMG|OBJECT|BIG|SMALL|SUB|SUP
%LinkTypes; CDATA
%MediaDesc; CDATA
%URI; CDATA
...

Drugi zawiera encje w postaci skróconej (tj. bez końcowego średnika):

%LanguageCode NAME
%Character CDATA
...

Separatorem kolumn w obu plikach jest znak tabulacji.

Wprawdzie dokumentacja HTML zawiera zalecenie, że encje powinny być zapisywane ze znakiem średnika, jednak w pliku DTD w jednym miejscu znajduje się wyjątek. Jest nim wpis ELEMENT STYLE. Z tego powodu potrzebny jest plik {stala}entities-dictionary2.txt{/stala}.

Pliki tekstowe zawierają jedynie kluczowe informacje o wpisach ENTITY, pozwalające na wymianę encji we wpisach ELEMENT oraz ATTLIST. Komplet informacji jest zawarty w plikach XML. Każdy wpis ENTITY jest opisany przez osobny plik XML. Elementem głównym w dokumentach XML jest element {stala}entity{/stala}. O rodzaju elementu informuje atrybut {stala}class{/stala}. Listingi 4 oraz 5 przedstawiają dwa przykładowe pliki XML. Pierwszy z nich opisuje wpis ENTITY, będący makrodefinicją zapisu list, a drugi stanowi skrócony zapis atrybutów rodziny i18n. (Oba listingi są - dla zwiększenia czytelności - w pewnym stopniu skrócone i zmienione.)


    list
    
    UL | OL
    
        UL
        OL
    

    i18n
    <!ENTITY % i18n ...>
    
        
            lang
            %LanguageCode;
            
            #IMPLIED
            language code
        
        
            dir
            (ltr|rtl)
            
            #IMPLIED
            direction for weak/neutral text
        
    

Automatyczna kategoryzacja wpisów ENTITY przysporzyła mi nieco kłopotów. Zrezygnowałem z niej. Podział wpisów ENTITY na poszczególne kategorie jest realizowany przy użyciu plików tekstowych. Pliki:

entity-elements.txt
entity-attributes.txt
entity-values.txt
entity-ignore.txt

utworzyłem ręcznie. Definiują one poszczególne grupy wpisów ENTITY.

W tym etapie analizie poddajemy wpisy definiujące elementy HTML. Wpisy możemy podzielić na dwa rodzaje: takie, które definiują pojedynczy element, oraz takie, które definiują wiele elementów.

Wpisem, który definiuje jeden element jest, na przykład, opis elementu {stala}SPAN{/stala}:

Natomiast definicja elementów {stala}SUB{/stala} oraz {stala}SUP{/stala} wykorzystuje alternatywę (tj. znak {stala}|{/stala}):

Jeszcze bardziej złożonym przypadkiem jest definicja nagłówków:

Analiza tego wpisu wymaga wymiany encji {stala}%heading;{/stala} na jej pełną postać {stala}H1H2|H3|H4|H5|H6|{/stala} opisaną przez:

Elementy opisane w DTD pasują do wyrażenia regularnego:

$re = \'|
   
|xs\';

Najpierw w pętli przetwarzamy wszystkie pliki z folderu {stala}element/{/stala}, dopasowując je do podanego wyrażenia regularnego. Wynikiem przetwarzania folderu jest tablica asocjacyjna {stala}$elements{/stala}:

$elements = array();
   $pliki = glob(\'element/*.txt\');
   foreach ($pliki as $plik) {
   $p = trim(file _ get _ contents($plik));

   if (preg _ match($re, $p, $m)) {

      $el = array();

      $el[\'name\'] = trim($m[1])
      ...

      array _ push($elements, $el);

   } else {
      echo \'*** ERROR ***\';
   }
}

Otrzymaną tablicę poddajemy kolejnym etapom przetwarzania:

  • wymieniamy encje,
  • przetwarzamy nazwy alternatywne (np. {stala}OL | UL{/stala}),
  • analizujemy dopuszczalną zawartość (włącznie z wykluczeniami),
  • generujemy zapis postaci {stala}a|b|c{/stala} zamiast np. {stala}(LI)+{/stala},
  • zapisujemy pliki XML.

Proces wymiany encji jest przedstawiony na listingu 6. Wykorzystujemy do tego przygotowany plik {stala}entities-dictionary.txt{/stala}. Plik wczytujemy ({stala}file_get_contents(){/stala}), kroimy ({stala}string2VArray(){/stala}), po czym w pętli wymieniamy encje ({stala}str_ replace(){/stala}).

$ec = count($elements);
$entities = file_get_contents(\'txt/entities-dictionary.txt\');
$entities = string2VArray($entities, \"\t\");
for ($i = 0; $i < $ec; $i++) {
    $elements[$i][\'nameExpanded\'] = trim(
        str_replace(
            $entities[2][0],
            $entities[2][1],
            $elements[$i][\'name\']
        ), \" \r\n\t()\"
    );
}

Wynikiem wykonania tego etapu przetwarzania są pliki XML: jeden plik na jeden wpis ELEMENT. Elementem głównym w dokumencie XML jest element. W zależności od tego czy wpis definiuje jeden element, czy wiele, stosujemy klasę {stala}class=\"single\"{/stala} oraz {stala}class=\"multiple\"{/stala}.

Listing 7 przedstawia wygenerowany opis XML elementów {stala}SUP{/stala} oraz {stala}SUB{/stala}, zaś listing 8 opis elementu UL.


    (SUB|SUP)
    
    subscript, superscript
    false
    -
    -
    (%inline;)*
    
        SUB
        SUP
    
    
        TT
        I
        B
        ...
    

    UL
    
    unordered list
    false
    -
    -
    (LI)+
    
        LI
    

W pierwszej fazie analizy oryginalny plik DTD kroimy na mniejsze fragmenty. Wycinamy z niego wpisy ENTITY, ELEMENT oraz ATTLIST. Wycięte definicje zapisujemy w osobnych plikach tekstowych.

Do sześciu rodzajów wpisów:

  • {html}{/html}
  • {html}{/html}
  • {html}{/html}
  • komentarz {html}{/html}
  • encja zewnętrzna {html}%HTMLlat1;{/html}
  • wpis zarezerwowany {html}{/html}

stosujemy sześć wyrażeń regularnych.

Do wpisu ENTITY:

$reEntity = \'|
   ^
   ]*
   >
|Usx\';

Do wpisu ELEMENT:

$reElement = \'|
   ^
   ]*
   >
|Usx\';

Do wpisu ATTLIST:

$reAttlist = \'|
   ^
   ]*
   >
|Usx\';

Do komentarzy:

$reComment = \'|
   ^
   
|Usx\';

Do zewnętrznych encji:

$reExternal = \'|
^
%\S+;
|Usx\';

Do wpisów zarezerwowanych:

$reBracket = \'|
   ^
   
|Usx\';

Każde z powyższych wyrażeń regularnych wykorzystuje fakt, że wpisów nie można

zagnieżdżać.

Schemat przetwarzania skryptu wycinającego wpisy ENTITY, ELEMENT oraz ATTLIST jest zgodny z listingiem 1:

  • odczytujemy cały plik DTD i usuwamy wiodące i końcowe białe znaki,
  • w pętli, dopóki odczytane DTD jest niepuste:
    • ucinamy początkowy fragment pasujący do jednego z sześciu wyrażeń regularnych,
    • wycięty fragment ENTITY, ELEMENT lub ATTLIST zapisujemy do pliku, pozostałe ignorujemy,
    • usuwamy wiodące białe znaki z DTD.

Jeśli trafimy na fragment, który nie pasuje do żadnego z wyrażeń regularnych, skrypt zgłasza błąd. Taki sposób przetwarzania gwarantuje, że cały plik zostanie przetworzony, każdy z wpisów musi pasować do jednego z wyrażeń regularnych. Kompletny skrypt jest przedstawiony na listingu 2. Wynikiem przetwarzania skryptu są pliki tekstowe, które zostają zapisane w folderach {stala}entity/{/stala}, {stala}element/{/stala} oraz {stala}attlist/{/stala}.

$filename = \'dtd/strict.dtd\';
$p = trim(file_get_contents($filename));

while ($p) {

    if (preg_match($reEntity, $p, $m)) {
        file_put_contents(\'entity/\' . $i . \'.txt\', $m[0]);
    } elseif (preg_match($reElement, $p, $m)) {
        file_put_contents(\'element/\' . $i . \'.txt\', $m[0]);
    } elseif (preg_match($reAttlist, $p, $m)) {
        file_put_contents(\'attlist/\' . $i . \'.txt\', $m[0]);
    } elseif (preg_match($reComment, $p, $m)) {
    } elseif (preg_match($reExternal, $p, $m)) {
    } elseif (preg_match($reBracket, $p, $m)) {
    } else {
        die(\'ERROR\');
    }

    $p = trim(
        preg_replace(
            \'/^\' . preg_quote($m[0], \'/\') . \'/\',
            \'\',
            $p
        )
    );
}

Wpisy ENTITY mogą definiować:

  • elementy HTML
  • atrybuty elementów
  • wartości atrybutów
  • rodzaj danych (CDATA)
  • odwołania zewnętrzne
  • wpisy do zignorowania
  • wpisy zarezerwowane
  • wpisy typu NAME

Najważniejszymi są definicje elementów:

definicje atrybutów:

definicje wartości atrybutów:

oraz definicje rodzaju danych:

Wymienione cztery rodzaje encji pasują do wyrażenia regularnego:

$re1 = \'|
   
|xs\';

Wpisy odwołujące do zewnętrznych plików pasują do wyrażenia:

$re2 = \'|
   
|xs\';

Natomiast wpisy zarezerwowane pasują do wyrażenia:

$re3 = \'|
   
|xs\';

Każdy z wpisów ENTITY pasuje do jednego z podanych trzech wyrażeń regularnych.

Może cię też zainteresować

Internet Maker

PHP zdobył przed laty popularność jako język skryptowy do tworzenia stron internetowych. Wzięła się ona z pewnością stąd, że jeszcze kilka lat temu nie było alternatywy dla szybkiego, prostego...

Internet Maker

To już trzecie wydanie książki Andrzeja Kierzkowskiego, tłumaczącej nie tylko podstawy języka PHP 5, ale także zawierającej wiele praktycznych ćwiczeń. Autor od lat pisze książki dotyczące programowania w tym języku,...

Internet Maker

Język PHP jest wykorzystywany najczęściej do tworzenia skryptów pracujących na tekście. Jednak dzięki dołączonej do PHP bibliotece GD, możliwa jest łatwa praca na grafice – od prostej obróbki po rysowanie...

Internet Maker

Symfony to jeden z najlepszych dostępnych obecnie frameworków w języku PHP. Dzięki jasnej strukturze oraz generatorom kodu przygotowanie kompletnej aplikacji WWW zajmuje kilku minut. Artykuł opisuje krok po kroku...