Mirin webspace

Nejbohatší život má ten, kdo žije s minimem nároků

29. 1. 2008 - Komentáře (2) PHP

Napiš si své texy - část 3

Poslední část mozaiky pro dokončení implementace vlastního značkovacího jazyka je implementace Rendereru. Jak jsme si řekli v prvním dílu, Renderer je zodpovědný za zobrazení výsledného dokumentu. Aby byla zajištěna nezávislost na použitých značkách (Lexeru), tak Renderer vytváří dokument na základě instrukcí od Handleru (viz. druhý díl).

Pole instrukcí od Handleru vypadá například takto (pro názornost jen jeho část).

Array
(
    [0] => Array
        (
            [0] => document_start
            [1] => Array
                (
                )
            [2] => 0
        )
    [1] => Array
        (
            [0] => p_open
            [1] => Array
                (
                )
            [2] => 0
        )
    [2] => Array
        (
            [0] => cdata
            [1] => Array
                (
                    [0] => abc
                )
            [2] => 0
        )
    [3] => Array
        (
            [0] => p_close
            [1] => Array
                (
                )
            [2] => 5
        )
 /*
 ......
 ......
 */
   [37] => Array
        (
            [0] => document_end
            [1] => Array
                (
                )
           [2] => 122
        )
)

Každá instrukce představuje jeden element pole, přičemž v prvku 0 je jméno instrukce, v prvku 1 je pole argumentů, v prvku 2 pak pozice odpovídajícího tokenu ve vstupním souboru. Každá instrukce má odpovídající metodu (callback) v Rendereru. Renderer tak definuje API, kde většina metod odpovídá 1:1 generovanému výstupu. Ve výpisu instrukcí výše je možné nalézt instrukce p_open a p_close. Budou odpovídat tagům <p> a </p>. Instrukce c_data označuje obsah odstavce.

Použití Rendereru pak bude vypadat nějak takto.

//Get a list of instructions from the Handler via Parser, main entry point
$instructions = $parser->parse($rawDoc);
 
//Create a renderer
$renderer = new WikiText_Renderer_Xhtml();
 
//Loop through the instructions
foreach ( $instructions as $instruction ) {
    // Execute the callback against the Renderer
    call_user_func_array(array(&$Renderer, $instruction[0]),$instruction[1]);
}

WikiText_Renderer_Xhtml je potomek abstraktní třídy WikiText_Renderer, která je definicí celého API pro Renderer. Tímto způsobem můžeme vytvořit Renderery pro HTML, XML, Latex a jiné výstupy. Renderer je nezávislý na použitých tokenech, metody pro značky, které v daném výstupním formátu nepůjdou zobrazit, můžeme ponechat prázdné. Kód, který nám ze vstupního textu s naším značkovacím jazykem udělá HTML pak bude vypadat přibližně následovně.

$text=file_get_contents("wikitext.txt");
 
//Create the parser
$parser = new WikiText_Parser();
 
//Add the Handler
$parser->setHandler(new WikiText_Handler());
 
//Register Lexer modes with parser
 
$parser->addMode('listblock',new WikiText_Parser_Mode_Listblock());
$parser->addMode('preformatted',new WikiText_Parser_Mode_PreFormatted());
$parser->addMode('unformatted',new WikiText_Parser_Mode_Unformatted());
$parser->addMode('header',new WikiText_Parser_Mode_Header());
$parser->addMode('table',new WikiText_Parser_Mode_Table());
$parser->addMode('linebreak',new WikiText_Parser_Mode_LineBreak());
$parser->addMode('footnote',new WikiText_Parser_Mode_FootNote());
$parser->addMode('externallink',new WikiText_Parser_Mode_ExternalLink());
 
/*
 ...
 all modes, the order of modes is little bit important
 ...
*/
 
$renderer = new WikiText_Renderer_Xhtml();
 
//Loop through the instructions
foreach ( $instructions as $instruction ) {
 // Execute the callback against the Renderer
 call_user_func_array(array(&$renderer, $instruction[0]),$instruction[1]);
}
 
echo $renderer->getOutput();

Pořadí v jakém jsou registrovány módy je důležité v tom smyslu, že Lexer při testování vstupního řetězce proti zaregistrovaným vzorům reg. výrazů postupuje sekvenčně tak, jak jsou registrovány. Takže je třeba si dát pozor na to, aby nedošlo k tomu, že dojde ke shodě řetězce regulárním výrazem pro jiný token, než jsme chtěli. Je proto dobré volit reg. výrazy a tím i naše značky tak "odlišné", aby k tomu nedošlo. Pak nezáleží ani na pořadí, v jakém módy registrujeme.

To je vše, informace to byly jen základní, ale pro objasnění principu, jak si takový analyzátor a renderer pro vlastní značkovací jazyk napsat, to myslím stačí. Napsat si takový nástroj není zas tak složité, ovšem pokud Vaši práci začne někdo používat, nebo to přímo plánujete dát k dispozici jako API, dostanete se určitě k věcem, které jdou řešit velmi obtížně, případně vůbec. To se holt nedá nic dělat :-), Andreas Gohr, autor Dokuwiki, nebo David Grudl autor Texy by určitě mohli vyprávět.

Intergrace se Zend Framework based projektem

Upravenou verzi dokuwiki používám i já na tomto blogu. Musel jsem většinu věcí mírně upravit, aby se třídy mohly načítat prostřednictvím Zend_Loaderu a byly lépe uzpůsobené pro běh pod PHP5. Pro vlastní převod článků z wiki syntaxe do XHTML jsem se nakonec rozhodl napsat si view helper. Ten dostává jako parametr instance článku (modelu). Použití ve view šabloně může vypadat takto.

<?php echo $this->textRenderer($this->article)?>

Kde $this->article je instance modelu pro článek. Protože jsem zpětně kompatibilní, tak podporuji i starou syntaxi článků přímo v HTML. Helper se proto podle typu syntaxe pak rozhodne, zda použije analýzu pro wiki, nebo zobrazí HTML přímo. Protože analýza je poměrně náročná, tak helper používá cache (Zend_Cache) pro výsledné HTML článků. Tu je samozřejmě nutné čistit, pokud článek změníme nebo smažeme. Vše si můžete prohlédnout přes websvn.


Komentáře (2)

  1. Marek Hrabě - 4. 2. 2008 12:39

    Moc pěkný článek. Pouze bych chtěl podotknout, že dgx je David Grudl, ne Grundl.

  2. koubel - 4. 2. 2008 15:39

    [1] Opraveno, jsem slepý dysgrafik a dyslektik

Komentáře jsou uzavřeny.