Itt jársz most: Kezdőlap > Alkalmazásfejlesztés > Szintaxis kiemelés reguláris kifejezésekkel

Szűrő megjelenítése

Szintaxis kiemelés reguláris kifejezésekkel

Mai cikkemben egy egyszerű módszert szeretnék bemutatni, hogyan írjunk szintaxis kiemelőt PHP-ban. Először is tisztázzuk, milyen elemekből áll általában egy programozási nyelv forrásszövege. Elsődleges elemei a fenntartott vagy foglalt szavak. Ezekbe általában beletartoznak a típusszavak is, de ha mégsem, az egyszerűsítés kedvéért egy kalap alá vehetjük őket az előbbi csoporttal. Aztán vannak a numerikus konstansok: egész és valós számok. Vannak még a literálok, illetve egyes nyelvekben (pl. a PHP-ban) a változók is egyértelműen megkülönböztethetőek. Ezek mellett még találhatunk a fordítónak szóló direktívákat, illetve kommenteket. Ennél ténylegesen persze minden nyelv többféle elemből áll össze, ezek csak a szöveg egyértelműen elkülöníthető részei, illetve azon részei, amiket érdemes elkülöníteni.

Lényegre térve, legtöbb bajunk a fent felsorolt elemek közül a fenntartott szavakkal lehet. Ezekkel ugyanis csak egy dolgot tehetünk: tároljuk őket valahol. Ehhez persze ismernünk kell az adott nyelv fenntartott szavait. Szerencsére ez nem olyan nagy bravúr, mint elsőre hangzik, nem kell elkezdenünk egyenként összeszedni azokat, mivel a nyelvek dokumentációiban találhatunk ezekről teljes listákat. Ezeket egy tömbben letárolva már kezdhetjük is velük a munkát. A többi elemhez nem kell ilyen tömböket készítenünk, mivel azok egyértelműen elkülöníthető részei a forrásszövegnek. Amennyiben több nyelv fenntartott szavait is tárolni szeretnénk, érdemes létrehozni egy asszociatív tömböt, amelynek azonosítói kiterjesztések, az azonosítókhoz rendelt értékek pedig egy-egy tömb, melyekben az adott nyelv fenntartott szavait tároljuk.

Amint azt a mellékelt forrásban majd láthatjátok, egy osztályt készítettem, mely így könnyen integrálható bármilyen rendszerbe. A teljes feldolgozó két fájlból áll, az egyik az osztályt tartalmazó php állomány, a másik pedig egy CSS, mely a kategóriák formázását határozza meg. Vegyük sorra a feldolgozó-osztályunk elemeit:

  • Szükségünk lesz három darab változóra, melyekben sorra az alábbiakat tároljuk: feldolgozandó fájlunk tartalmát, a logikai állományt, és a típust. Ezeket deklarálhatjuk privát típusként is, ugyanis kívülről ezen változók tartalmához nem érdemes hozzányúlni, elég ha az osztály maga kezeli őket.

  • Itt deklaráljuk a fenntartott szavakat tartalmazó asszociatív tömböt is. Ezt azonban nem szeretnénk, ha példányosítaná a rendszer, így ezt jelöljük meg osztályváltozóként a static kulcsszóval. Szintén legyen a láthatósága privát.

  • Szükségünk lesz egy publikus függvényre, mely a kiemelést végzi, itt helyezzük el a reguláris kifejezéseket. Ezekről később még külön ejtek néhány szót. A függvény visszatérését végül érdemes a <pre>[visszatérés]</pre>tagek közé konkatenálni, így megjelenítéskor a szöveg megtartja formázottságát.

  • A példányosításkor lefutó konstruktorunk fogja megnyitni olvasásra a megadott állományt és beállítani a fentebb ismertetett három változót, így itt az fopen függvény segítségével kell olvasásra nyitnunk a forrásszöveget tartalmazó szöveges állományt, majd az fgets függvénnyel soronként kiolvasva tudjuk tárolni a tartalmát. A beolvasás végén még hívjuk meg a változóra a htmlspecialchars függvényt, így kiszűrve a forrásban található speciális karaktereket. Végül a fájlt a destruktor zárja be.

  • Szükségünk lehet még egy speciális, publikus láthatóságú, statikus függvényre, mellyel lekérhetjük az asszociatív tömbben tárolt nyelvek kiterjesztéseit. Mivel ezek a tömb indexei (kulcsai), az értékeket az array_keys függvénnyel csíphetjük el. Ez arra lehet jó, ha a feldolgozást kezdeményező oldalon vizsgálni szeretnénk, egyáltalán elkezdhető-e a feldolgozás, le vannak-e tárolva az adott nyelvhez a fenntartott szavak.

Ennyi eszközre van mindössze szükségünk, végül a feldolgozást kezdeményező oldalon annyit kell tennünk, hogy példányosítjuk a feldolgozó-osztályt, konstruktorát felparaméterezve a fájlnévvel és a forrásszöveg kiterjesztésével (ezt a két paramétert esetleg egyben is elküldhetjük és a feldolgozóoldalon is megállapíthatjuk egy egyszerű explode függvénnyel), meghívjuk a feldolgozást végző függvényt, majd a függvény visszatérését ki kell íratnunk és meg is vagyunk. Ezután persze szüntessük meg az objektumot, ne foglalja feleslegesen a memóriát.

De visszatérve még kicsit a reguláris kifejezésekre. Aki már találkozott velük, biztosan tisztában van vele, mennyire meg tudja dolgoztatni az ember agyát egy precíz mintázat összehozása. Szerencsére jelen esetben nem kell túl bonyolult kifejezésekre gondolnunk, bár bevallom, a majd a forrásban látható kifejezések némi finomításra szorulnak. Működési elvük nagyon egyszerű különben, ha megtalál egy adott mintázatot, kicseréli egy adott [találat] sztringre. Ezért van szükség a CSS fájlra, abban tároljuk a stílusosztályokat. A kiemelő függvény a fenntartott szavak esetén nagyon egyszerűen jár el, egyenként végighalad a szavakon és megnézi, van-e olyan a forrásban. Ha van, cseréli. A többi elemnél csak mintázat alapján történő csere történik. Figyeljünk arra, hogy a speciális karaktereket a htmlspecialchars kicseréli azok HTML entitásaira. Ezeket a mintázatban így az entitásokkal kell keresnünk, például stringeknél biztosan bele fogunk futni ebbe a „hibába”. Visszatérve a kifejezésekre, ha van rá ötletetek, hogyan lehet őket finomhangolni némileg, szívesen fogadom azokat kommentekben!

<?php
	class syntaxHL
	{
		private $content = null;
		private $handle = null;
		private $type = null;
		
		private static $keyWords = array(
			'c'		=> array('auto', 'break', 'case', 'char', 'const', 'continue', 'default', 'do', 'double', 'else', 'enum', 'extern', 'float', 'for', 'goto', 'if', 'int', 'long', 'register', 'return', 'short', 'signed', 'sizeof', 'static', 'struct', 'switch', 'typedef', 'union', 'unsigned', 'void', 'volatile', 'while'),				
			'java'	=> array('class', 'abstract', 'assert', 'boolean', 'break', 'byte', 'case', 'catch', 'char', 'const*', 'continue', 'default', 'double', 'do', 'else', 'enum', 'extends', 'false', 'final', 'finally', 'float', 'for', 'goto*', 'if', 'implements', 'import', 'instanceof', 'int', 'interface', 'long', 'native', 'new', 'null', 'package', 'private', 'protected', 'public', 'return', 'short', 'static', 'strictfp', 'super', 'switch', 'synchronized', 'this', 'throw', 'throws', 'transient', 'true', 'try', 'void', 'volatile', 'while'),
			'php'	=> array('class', '__halt_compiler', 'abstract', 'and', 'array', 'as', 'break', 'callable', 'case', 'catch', 'clone', 'const', 'continue', 'declare', 'default', 'die', 'do', 'echo', 'else', 'elseif', 'empty', 'enddeclare', 'endfor', 'endforeach', 'endif', 'endswitch', 'endwhile', 'eval', 'exit', 'extends', 'false', 'final', 'for', 'foreach', 'function', 'global', 'goto', 'if', 'implements', 'include', 'include_once', 'instanceof', 'insteadof', 'interface', 'isset', 'list', 'namespace', 'new', 'null', 'or', 'print', 'private', 'protected', 'public', 'require', 'require_once', 'return', 'static', 'switch', 'throw', 'trait', 'true', 'try', 'unset', 'use', 'var', 'while', 'xor'),
			'py'	=> array('class', 'and', 'as', 'assert', 'break', 'continue', 'def', 'del', 'elif', 'else', 'except', 'exec', 'finally', 'for', 'from', 'global', 'if', 'import', 'in','is', 'lambda', 'not', 'or', 'pass', 'print', 'raise', 'return', 'try', 'while', 'with', 'yield')
		);

		public function highlight()
		{	
			// reserved words
			foreach(self::$keyWords[$this->type] as $word)
				$this->content = preg_replace("/\b$word\b/i", '<span class="shlResW">'.$word.'</span>', $this->content);
				
			// numbers
			$this->content = preg_replace("/\b([0-9]+)\b/", '<span class="shlNum">\\1</span>', $this->content);
			
			// variables
			$this->content = preg_replace("/([$][_]?[a-z][a-z0-9_]+)\b/i", '<span class="shlVar">\\1</span>', $this->content);
			
			// strings
			$this->content = preg_replace('/"(.*?)"/', '<span class="shlStr">"\\1"</span>', $this->content);
			$this->content = preg_replace('/\'(.*?)\'/', '<span class="shlStr">\'\\1\'</span>', $this->content);
			
			// multiline comments
			$this->content = preg_replace("/\/\*(.*?)\*\//s", '<span class="shlCom">/*\\1*/</span>', $this->content);
			
			// one-line comments
			$this->content = preg_replace("/\/\/(.*?)\n/", '<span class="shlCom">//\\1</span>', $this->content);
			
			// compiler directives
			$this->content = preg_replace("/\#(.*?)\n/", '<span class="shlCom">#\\1</span>', $this->content);
			
			return '<pre>'.$this->content.'</pre>';
		}
		
		public static function extensions()
		{
			return array_keys(self::$keyWords);
		}
		
		function __construct($filename, $type)
		{
			$this->type = $type;
			
			$this->handle = fopen($filename, 'r');
			if($this->handle)
			{
				while(!feof($this->handle))
				{
					$this->content .= fgets($this->handle);
				}
			}
			$this->content = htmlspecialchars($this->content);
		}
		
		function __destruct()
		{
			fclose($this->handle);
		}
	}
?>
Kommentek

Komment írásához jelentkezz be
Bejelentkezés

Még senki nem szólt hozzá ehhez a bejegyzéshez.