Jak implementovat GeSHi

Zdroj: SOOM.cz [ISSN 1804-7270]
Autor: .cCuMiNn.
Datum: 26.9.2012
Hodnocení/Hlasovalo: 1/4

Provozujete webové stránky v PHP, na kterých čas od času, nebo pravidelně vystavujete zdrojové kódy? Pokud ano, pak by Vás mohlo zajímat, jak snadno implementovat zvýrazňovač syntaxe, který vašim kódům dodá přehlednější a líbivější vzezření.

Stejný problém jsme před časem řešili i na tomto serveru, kde jsme se nakonec rozhodli pro knihovnu GeSHi. Důvodů proč jsme zvolili právě ji, je hned několik. Zaprvé se jedná o freeware aplikaci, za jejíž použití tedy není nutné platit, za druhé tato knihovna obsahuje velikou škálu programovacích a skriptovacích jazyků, jejichž syntaxi dokáže obarvit. V neposlední řadě je předností také její snadná implementace do stávajících webových aplikací. Rozhodnete-li se pro GeSHi i vy, pak touto volbou rozhodně nic nepokazíte.


Zvýrazňujeme syntaxi

Pojďme si tedy ukázat postup, kterým je možné GeSHi do své aplikace zakomponovat. Prvním místem, kam by se měly ubírat vaše kroky, bude určitě oficiální domovská stránka projektu GeSHi. Ta Vás již navede na místo, kde se nachází download nejnovější verze. Po stažení a rozbalení archivu, z něj s klidným svědomím můžete odstranit adresáře contrib a docs. Důležitými částmi celé GeSHi jsou pouze soubor geshi.php, který představuje srdce této aplikace a dále pak adresář geshi, který obsahuje soubory popisující syntaxi jednotlivých programovacích jazyků. Zmíněný soubor a adresář uploadujte buďto do vámi zvolené složky svého webu, nebo přímo do jeho rootu.

Tím bychom měli vše připraveno pro vlastní implementaci. Pojďme tedy nahlédnout do dokumentace, která je velice podrobná a přehledná. Hned v jejím úvodu najdete krátký příklad, jak GeSHi použít:

include_once 'geshi.php'; 

$source =
 '$foo = 45;
  for ( $i = 1; $i < $foo; $i++ ){
    echo "$foo\n";  --$foo;
  }';

$language = 'php';

$geshi = new GeSHi($source, $language);

echo $geshi->parse_code();

Pojďme si přeci jen trochu popsat i tento nejjednodušší příklad.

Hned na prvním řádku includujeme soubor geshi.php, bez kterého se neobejdeme. Pokud se tento soubor nachází v jiném adresáři, než ve kterém máte uložen skript, který geshi.php includuje, musíte samozřejmě v prvním řádku upravit cestu k tomuto souboru.

Na řádcích 3-7 jsme definovaly proměnnou $source, do které jsme načetli zdrojový kód programu, jež si přejeme obarvit. Samotný obsah pro nás tedy není důležitý, můžeme do proměnné uložit libovolný kód.

Na řádku 9 jsme proměnné $language přiřadily hodnotu „php", která určuje v jakém programu je zdrojový kód, který chceme obarvit, napsán. Podle hodnoty této proměnné bude zvolena správná definice obarvování syntaxe. Podporovaných jazyků je opravdu veliké množství a jejich seznam se stále rozrůstá. Některé z možností, které můžete přiřadit proměnné $language najdete v tomto seznamu. Pokud budete chtít zvýraznit syntaxi jazyka, který v tomto seznamu není uveden nezoufejte, prohlédněte si obsah adresáře „geshi", který obsahuje definice jednotlivých jazyků a pokud v něm najdete ten vámi požadovaný, uveďte jako hodnotu proměnné název tohoto souboru bez přípony .php.

Na řádku 11 vytváříme novou instanci objektu GeSHi, která z výše definovaných proměnných převezme potřebná data. Na posledním řádku již jen příkazem Echo vypíšeme výsledek práce, tedy zvýrazněný kód.


Ovlivňujeme vlastnosti výstupu

V určitých případech budete potřebovat ovlivnit některé vlastnosti výstupu, který GeSHi vrací v defaultním nastavení. V GeSHi je možné některé vlastnosti ovlivnit pomocí metod vytvořeného GeSHi objektu. Těmito metodami jsou například:

Příklad volání metody pro nastavení potřebných vlastností

…
$geshi = new GeSHi($source, $language);
$geshi->enable_line_numbers (GESHI_FANCY_LINE_NUMBERS);
echo $geshi->parse_code();
…

Popis jednotlivých metod a hodnoty, které jim lze předat, jsou podrobně popsány v dokumentaci, a proto se jim zde nebudu blíže věnovat.


Nastavujeme barvy

Důležitou vlastností výstupu, kterou můžeme plně ovlivnit, jsou barvy jednotlivých částí kódu. GeSHi řeší obarvování tak, že rozděluje klíčová slova, metody, proměnné, atd. do skupin. Každý kus kódu je pak ve vráceném výsledku ohraničen tagy DIV, které svému obsahu přiřazují atributem „class" třídu podle skupiny, do které konkrétní kus kódu spadá. Jedná se například o třídy KW1, KW2, KW3 pro klíčová slova, DE1, DE2, DE3 pro deklarace, a podobně. To nám umožňuje, abychom snadno jednotlivým třídám nastavili pomocí CSS jakékoliv vizuální vlastnosti. Nemusíme samozřejmě nastavovat CSS pro všechny vlastnosti hned. Můžeme je do CSS souboru doplňovat potupně až ve chvíli, kdy se nám některá barva ve výstupu nebude líbit. V takovém případě se pouze podíváme do vráceného zdrojáku stránky, kde zjistíme, jaká třída byla konkrétní části kódu přiřazena a této třídě nastavíme požadovaný styl.

V případě tohoto serveru vypadá CSS pro obarvování kódu následovně:

/* GESHI styles
**********************/
.geshi {
	line-height: 1.5em;
	font-size: 12px;
	padding: 0;
	background: #000000;
	border: 1px solid #333;
	 #fff;
	margin-top: 10px;
	margin-bottom: 20px;
	text-align: left;
}
.geshi ol {
	list-style: decimal;
	list-style-position: outside;
	padding: 0;
	margin-top: 6px;
	margin-bottom: 6px;
}
.geshi ol li {
	margin: 0 0 0 35px;
	padding:0 0 0 10px;
	 #999999;
	clear: none;
	list-style:url();
	list-style:decimal;
}

/* Line highlights */
.li1 {
	background: #000000;
}

/* comments */
.co1,
.coMULTI {
	#66666c;
	font-style:italic;	
}
/* methods */
.me1{
	#33cc00;
}
.me0 {	
	#33cc00;
}
.me2 {	
	#33cc00;
}

/* brackets */
.br0 {
	#007700;
}

/* chars */
.sy0 {
	#007700;
}

/* strings */
.st0 {
	#aaaaaa;
}
.st_h {
	#aaaaaa;
}

/* declaration */
.de0 {
	#555555;
}
.de1 {
	#555555;
}
.de2 {
	#555555;
}
.de3 {
	#555555;
}

/* keywords */
.kw1 {
	#33cc00;
	font-weight:bold;
}
.kw2 {
	#33dd00;
	font-weight:bold;
}

.kw3 {
	#33ee00;
}

/* numbers */
.nu0 {
	#d4d426;
}

/* vars */
.re0 {
	#007700;
	font-weight:bold;
}

/* head */
.head {
	background:#222222;
	#ffffff;
	padding-bottom:3px;
	padding-left:5px;
}

/* classnames */
[lang=css] .kw2,
.css .kw2 {
	#F9EE7E;
}
.re1 {
	 #96703D;
}
/* px values */
[lang=css] .re3,
.css .re3 {
	#CA7840;
}

Odlišujeme kód od okolního textu

Nyní, když umíme vytvořit PHP stránku, která nám zdrojový kód předaný v proměnné zobrazí se zvýrazněnou syntaxí, zbývá ještě vyřešit otázku, jak tuto funkčnost zakomponovat do redakčního systému. Zcela určitě totiž nebudeme chtít zapisovat PHP kód pro zvýraznění syntaxe přímo do článku ani k tomuto udělovat práva svým redaktorům. Daleko užitečnější by bylo, aby docházelo ke zpracování kódů, které jsou obsaženy uvnitř delších textů (článků), zcela automaticky.

Aby bylo něco podobného možné, budeme v textu muset kolem zdrojových kódů vkládat námi definované značky. Celý text pak před zobrazením proženeme parserem, který obsah uzavřený mezi těmito značkami předá ke zpracování GeSHi a vrácený výsledek zakomponuje do původního textu.

Když jsem hledal nějaké již hotové řešení, narazil jsem na stránky Dominika Veselého. Ten přesně takový parser, který vyhovoval našim podmínkám, vytvořil a detailně popsal ve svém článku Geshi - obarvovátko kódu, vlastní filtr. Provedl jsem tedy pouze drobné kosmetické úpravy a uložil výsledek jako soubor GeSHi_parser.php

' ) === false) {
      return $text;
    }
    $regex = "#
(.*?)#s";
    $text = preg_replace_callback ( $regex, array ('self', 'match' ), $text );
    return $text;
  }

  public function match(&$matches) {
    $args = Dominoo_GeshiFilter::parseAttribs ( $matches [1] );
    $text = $matches [2]; 
    $lang = $args ["lang"];
    $lines = $args ["lines"];
    $header = $args ["header"];

    $geshi = new GeSHi ( $text, $lang );
    $geshi->enable_line_numbers ( (($lines == "none") ? GESHI_NORMAL_LINE_NUMBERS : GESHI_FANCY_LINE_NUMBERS) );
    $geshi->enable_keyword_links ( false );
    $geshi->set_header_type(GESHI_HEADER_DIV);
    if ($header) {
      $geshi->set_header_content($header);
    }
    if($lines == "none") {
      $geshi->enable_line_numbers(GESHI_NO_LINE_NUMBERS);
    }
    $geshi->enable_classes ();
    $text = $geshi->parse_code ();
    return "
$text
"; } static function parseAttribs($attribs) { $attr = explode(" ", $attribs ); $attributes = array (); foreach ( $attr as $attribute ) { $a = explode ( "=", $attribute ); $regex = '/\"(.*)\"/'; preg_match( $regex, $a[1], $m ); $attributes[$a[0]] = $m[1]; } return $attributes; } } ?>

Kód funguje tak, že zavoláme metodu generateGeshi(), které jako parametr předáme celý obsah článku, ve kterém jsou části, jež mají být zpracovány pomocí GeSHi, obaleny tagem PRE. Tento tag současně může obsahovat atributy LANG, který definuje použitý jazyk, HEADER, který obsahuje popisek v záhlaví kódu (mezery musí být zapisovány pomocí entity &nbsp;) a LINES, který určuje, zda a jak zobrazovat čísla řádků. V případě potřeby nebude jistě problém si dopsat obsluhu i pro další atributy.

Bližší informace o činnosti uvedeného skriptu najdete v již zmíněném článku.

Nyní se ale vraťme k obarvování částí našich textů. Článek, který obsahuje úryvek zdrojového kódu, bude nutné zapisovat v tomto tvaru:

Ukázka použití GeSHi

Níže uvádím zdrojový kód mého prvního proramu v C.

<pre lang="c" header="Hello world"> #include main(){ for(;;){ printf ("Hello World!\n"); } }</pre>

Vyzkoušejte jej, stojí to za to.

Samotný jeho výpis v redakčním systému pak bude probíhat tak, že text načteme z databáze do proměnné $clanek. Tuto proměnnou předhodíme parseru a výsledek zobrazíme uživateli. Kód php stránky, která by byla zodpovědná za zobrazování článků, by pak mohl v jednoduchosti vypadat nějak takto:

generateGeshi($clanek);
?>

Rekapitulace

Pro použití GeSHi ve svém redakčním systému si: