[Update] Az IMDB lapok forrása megváltozott de egyenlőre vannak olyan rendesek, hogy a php kérésekre a régi forrást adják vissza ezért a minta alkalmazás még működik. Ez azzal is jár, hogy az oldal jelenlegi struktúrájának elemzésekor nem lesznek megtalálhatók a lent említett HTML elemek.
A segédletben használt Transformers film oldalának forrása innen letölthető. [/Update]

[Update2] Sajnos már a php kérésére is az új források jönnek vissza, így az alkalmazás csak a nem változó elemeket, a címet és az évet fogja felismerni. A segédlet alapján, így már akinek van kedve kísérletezhet az új elemző elkészítésével, legyen ez a gyakorló feladat :) [/Update2]

Mivel regex segédlet az interneten számos helyen van most nem ennek elsajátításán lesz a fő hangsúly, hanem hogyan lehet egyszerűen flash-ben használni és adatokat kiszűrni a segítségével.
Az illesztési minták elkészítéséhez nagy segítséget nyújthat Grant Skinner RegExr alkalmazása amit itt találhatunk meg: http://gskinner.com/RegExr/.  Számos előnyeinek egyike, hogy flash-be bemásolható formában is kiírja a mintákat lehet tesztelni működésüket vagy akár flash-ből bemásolva is tudja használni őket.
Hasznos lehet továbbá FireBug is aminek segítségével egyszerűen elemezhetjük egy weblap felépítést: http://getfirebug.com/

AS3-ban a reguláris kifejezéseket lehetőség van speciális módon, a kódban definiálni hasonlóan az XML-hez, így nincs szükség az amúgy stringekben speciális jelentéssel bíró karaktereket mint pl az escape karakter “\” duplázására.

private var pattern1:RegExp = new RegExp(" hello \\s world ","gimsx");
private var pattern2:RegExp = / hello \s world /gimsx;

private function test():void
{
	var foo:String = "Hey and Hello World how are you?"

	if(pattern2.test(foo)){
		trace("Benne van")

		pattern2.lastIndex = 0
		var result:Array = pattern2.exec(foo)
		trace("index: "+result.index)
		trace("input: "+result.input)
		trace(result[0])

	}else{
		trace("Nincs benne")
	}
}

eredmény

Benne van
index: 8
input: Hey and Hello World how are you?
Hello World

Az 1. sorban a hagyományos módon hoztam létre a RegExp-et a 2. sorban az új AS3-as szintaxis segítségével. A továbbiakban ezt fogom mindenhol használni az egyszerűsége és átláthatósága miatt. Ahogy a kódból is látszik / jelek közé kerül a kifejezés, a végére pedig a különböző beállítások amik a következőket jelentik:

Flag Tulajdonság Leírás
g global Több találatot ad vissza nem csak addig keres amíg 1-et nem talál
i ignoreCase Nagy és kis betők között nem tesz  különbséget de fontos hogy ez csak az angol A-Z a-z karakterekre igaz.
m multiline Az $ és a ^ karakter ebben az estben több sor esetén egy sor elejét és végét is jelölheti.
s dotall A . (pont) karakter az új sor karakterre is illeszkedik, fontos ha több soros szövegben keresünk.
x extended A kifejezésbe lehetőség van szóközöket rakni nem lesznek figyelembe véve így lehetőség van formázni és átláthatóvá tenni azt.

A test segítségével tesztelhetem hogy van-e egyezés, majd az exec segítségével lekértem az eredményt ami tartalmaz pár speciális tulajdonságot. Az ne zavarjon meg senkit hogy a dokumentációban Object a visszatérési értéke, tulajdonképpen egy Array-t ad vissza amin speciális tulajdonságok is elérhetőek. A mintában csak tesztelés után hívtam meg az exec-et hogy szemléltessem a 2 metódus működését, erre amúgy nincs szükség, ha nincs egyezés exec null értékkel tér vissza. A pattern2.lastIndex = 0 sorra azért van szükség, mivel ha újra akarunk használni egy példányt minden illesztés után a pattern elmenti önmagán az utolsó pozíciót, így több találat estén onnan folytatja a keresést, de mivel csak egy találtunk volt a következő illesztésnél már nem találna semmit attól a pozíciótól kezdve.

Most hogy tudjuk hogyan kell definiálni a reguláris kifejezéseket írjuk meg az IMDB oldalához tartozókat. A forrás elemzéséhez válasszunk egy népszerű filmet amin minden információ megtalálható, legyen az mondjuk a Transformers.
Kezdjük a film címével és az évvel amikor megjelent. Ez az információ mind megtalálható a<title> elemek között estünkben ez így néz ki:

html:

<title>Transformers (2007)</title>

regex:

/<title>(?P<title>.+) \s*  \(  (?P<year>\d{4}) \)  .* <\/title>/ixs

leírás:

/<title>{egy vagy több karakter}  {0 vagy több szóköz}  \(  {4 karakteres szám}  \)  .* <\/title>/gixsm

Lehetőség van névvel ellátott csoportok definiálásra amit a (?P<név> … ) formátumban hozhatunk létre majd később a csoport által illesztett szöveget a név segítségével tudjuk visszakérni. Most már csak annyi a dolgunk, hogy megírjuk a szükséges függvényeket.

private static function getTitleInfo(source:String):TitleInfo
{
	titleRegex.lastIndex = 0
	var result:Array = titleRegex.exec(source)
	var info:TitleInfo
	if(result){
		info = new TitleInfo()
		info.title = unescapeEntity(result.title)
		info.year = parseInt(result.year)
	}

	return info
}

private static function unescapeEntity(str:String):String
{
	if(str){
		return new XMLDocument(str).firstChild.nodeValue;
	}else{
		return str
	}
}

A getTitleInfo függvényem megkapja a forrást és ráilleszti a regex-et ami ha sikeres a névvel ellátott csoportokat a title és a year tulajdoonságokkal lekérhetem. Ezeket egy TitleInfo osztályba mentem ami csak ezt a 2 tulajdonságot definiálja. Fontos odafigyelni hogy a forrás tartalmazhat HTML karakter entitás referenciákat is ami azt jelenti hogy például a Gálvölgyi Show forrásban így néz ki:
<title>&#x22;G&#xE1;lv&#xF6;lgyi Show&#x22; (1998)</title>
Ezeket egy trükkel tudjuk visszakonvertálni amit az unescapeEntity függvény implementál.

Lépjünk tovább a következő információ amire szükségünk van a szavazatok és az érékelés amikre a ez kifejezés illeszkedik:

html:

 <div class="starbar-meta">
        <b>7.3/10</b>
           &nbsp;&nbsp;<a href="ratings" class="tn15more">190,003 votes</a>&nbsp;&raquo;

regex:

/<div\sclass="starbar-meta"> .*? <b> (?P<score>( \d{1,2}) | (\d\.\d)) \/ 10 <\/b> .*?>  (?P<votes>.+?)  \s votes<\/a>/ixs

leírás:

/<div class="starbar-meta"> .*? <b> ({1 vagy 2 szám} VAGY {szám . szám}) / 10 </b> .*? > {több karakter} {space} votes<\/a>/ixs

Ebben a forrásban használtam egy úgynevezett lusta kvantort a mi a fenti kódban .*? ként látható. Ha egy kérdő jelet rakok a * mögé az azt fogja jelenteni hogy a lehető legkevesebb köztes karaktert keresse meg az előtte és utána lévő kifejezések között. Ez nagyon fontos mivel különben nem a következőre hanem akár jóval messzebb található egyezésre fog illeszkedni, ami már nekünk nem megfelelő hiszen bizonyos sorrendben egymás után megtalálható html elemeket keresek. Most lássuk a kódot:

private static function getScoreInfo(source:String):ScoreInfo
{
	var floatRegex:RegExp = /(\d+) (,|\.) (\d+)/x

	ratingRegex.lastIndex = 0
	var result:Array = ratingRegex.exec(source)
	var info:ScoreInfo
	if(result){
		info = new ScoreInfo()
		info.score = parseFloat(result.score)
		info.votes = parseInt(
			String(result.votes).replace(floatRegex, "$1$3")
		)
	}

	return info
}

A fenti kódban a lekérés mellett a String.replace metódusát is használtam ami nem csak String hanem RegExp típusú paramétert is képes fogadni. Ebben az esetben a reguláris kifejezés által megadott, egy vagy több szövegrészt fogja cserélni a 2. paraméterben megadott szöveggel, vagy akár egy megadott függvény visszatérési értékével.
Ha az első paraméter RegExp és a második paraméter String, akkor a String tartalmazhat bizonyos speciális karaktereket amiket a csere után helyettesít, ezeket a függvény leírásánál meg lehet nézni. Én a függvényben a $n formátumot adtam meg ahol az n a csoport sorszáma amiből 3-at definiáltam a reguláris kifejezésben. Ezzel azt értem el hogy a . vagy , előtti és utáni szöveget a . vagy , kihagyásával illesztem össze így IMDB-ben a vesszővel elválasztott szavazat számot vessző nélkül számmá tudom konvertálni.

Következő feladatunk a műfaj (Genre) információk lekérése amiből több is van, de a leírás kedvéért nehezítsük a dolgunkat és mi csak azokra vagyunk kíváncsiak amik a “Genre:” utáni részben vannak. Ehhez a legegyszerűbb ha először megkapjuk annak a résznek a forrását, majd abban keressük meg a műfaj neveket.

html:

<div class="info">
<h5>Genre:</h5>
<div class="info-content">

<a href="/Sections/Genres/Action/">Action</a> | <a href="/Sections/Genres/Sci-Fi/">Sci-Fi</a> | <a href="/Sections/Genres/Thriller/">Thriller</a> <a class="tn15more inline" href="/title/tt0418279/keywords" onClick="(new Image()).src='/rg/title-tease/keywords/images/b.gif?link=/title/tt0418279/keywords';">See more</a>&nbsp;&raquo;
</div>
</div>

műfaj div regex:

/<h5>Genre:<\/h5> .*? <div.*?> (?P<div>.*?) <\/div>/gixsm

műfaj regex:

/<a \s href= "\/Sections\/Genres\/.*?" > (?P<genre>.*?) <\/a>/gixsm

kód:

private static function getGenres(source:String):Array
{
	genresDivRegex.lastIndex = 0
	var result:Array = genresDivRegex.exec(source)
	var genres:Array = new Array()

	if(result){
		source = result.div
		genreRegex.lastIndex = 0
		while(result = genreRegex.exec(source)){
			genres.push(result.genre)
		}
	}

	return genres
}

A getGenres függvényem először lekéri a div forrását ahol a műfajok fel vannak sorolva, majd egy while ciklus addig kéri le őket amíg null-t nem kapunk. Ebben az esetben a cikluson belül nem szabad nullázni az indexet mivel szükségünk van rá, hogy mindig a következőt kapjuk és nem akarunk végleten ciklusba sem kerülni.

Most hogy sikeresen megírtuk a főbb információkat lekérő elemeket, lássuk hogyan néz ki az egész összerakva. Készítsünk egy statikus osztályt ami visszaadja a forrásból kiszűrt információkat, és egyet amiből az információk könnyen elérhetőek.

IMDBSourceInfo.as

package
{
	public class IMDBSourceInfo
	{
		public var hasRating:Boolean
		public var title:String
		public var year:int
		public var genres:Array
		public var score:Number
		public var votes:Number
		public var posterUrl:String
	}
}

IMDBSourceParser.as

package
{
	import flash.xml.XMLDocument;

	public class IMDBSourceParser
	{
		private static const titleRegex:RegExp =
			/<title>(?P<title>.+)  \s*  \(  (?P<year>\d{4}) \)  \s* <\/title>/gixsm

		private static const genresDivRegex:RegExp =
			/<h5>Genre:<\/h5> .*?<div.*?> (?P<div>.*?) <\/div>/gixsm

		private static const genreRegex:RegExp =
			/<a \s href= "\/Sections\/Genres\/.*?" > (?P<genre>.*?) <\/a>/gixsm

		private static const posterRegex:RegExp =
			/<a\s*?name=\"poster\" .*? src=\"(?P<url>.*?)\" \s*? \/>/ixs

		private static const ratingRegex:RegExp =
			/<div\sclass="starbar-meta"> .*? <b> (?P<score>( \d{1,2}) | (\d\.\d)) \/ 10 <\/b> .*?>  (?P<votes>.+?)  \s votes<\/a>/ixs

		//--------------------------------------------------------------------------
		//
		//  Public Static Methods
		//
		//--------------------------------------------------------------------------

		public static function parse(source:String):IMDBSourceInfo
		{
			var data:IMDBSourceInfo = new IMDBSourceInfo()
			var titleInfo:TitleInfo = getTitleInfo(source)
			var scoreInfo:ScoreInfo = getScoreInfo(source)

			if(titleInfo){
				data.title = titleInfo.title
				data.year = titleInfo.year
			}
			if(scoreInfo){
				data.score = scoreInfo.score
				data.votes = scoreInfo.votes
				data.hasRating = true
			}
			data.genres = getGenres(source)
			data.posterUrl = getPoster(source)

			return data
		}

		//--------------------------------------------------------------------------
		//
		//  Private Static Methods
		//
		//--------------------------------------------------------------------------

		private static function getPoster(source:String):String
		{
			posterRegex.lastIndex = 0
			var result:Array = posterRegex.exec(source)
			if(result){
				return result.url
			}

			return null
		}

		private static function getScoreInfo(source:String):ScoreInfo
		{
			var floatRegex:RegExp = /(\d+) (,|\.) (\d+)/x

			ratingRegex.lastIndex = 0
			var result:Array = ratingRegex.exec(source)
			var info:ScoreInfo
			if(result){
				info = new ScoreInfo()
				info.score = parseFloat(result.score)
				info.votes = parseInt(
					String(result.votes).replace(floatRegex, "$1$3")
				)
			}

			return info
		}

		private static function getGenres(source:String):Array
		{
			genresDivRegex.lastIndex = 0
			var result:Array = genresDivRegex.exec(source)
			var genres:Array = new Array()

			if(result){
				source = result.div
				genreRegex.lastIndex = 0
				while(result = genreRegex.exec(source)){
					genres.push(result.genre)
				}
			}

			return genres
		}

		private static function getTitleInfo(source:String):TitleInfo
		{
			titleRegex.lastIndex = 0
			var result:Array = titleRegex.exec(source)
			var info:TitleInfo
			if(result){
				info = new TitleInfo()
				info.title = unescapeEntity(result.title)
				info.year = parseInt(result.year)
			}

			return info
		}

		private static function unescapeEntity(str:String):String
		{
			if(str){
				return new XMLDocument(str).firstChild.nodeValue;
			}else{
				return str
			}
		}
	}
}

class ScoreInfo
{
	public var votes:Number
	public var score:Number
}

class TitleInfo
{
	public var title:String
	public var year:Number
}

Most hogy minden szükséges osztály megvan meg kell írnunk a forrás betöltését. A forrásokat az URLLoader segítségével tudjuk betölteni, de viszont ha a webhely ahonnan a forrásokat töltenénk nem engedélyezi a hozzáférést nekünk egy crossdomain.xml-ben akkor php-val kell betöltenünk a forrást majd azt átadni flash-nek. IMDB-n noha van ilyen xml file http://www.imdb.com/crossdomain.xml nekünk nem ad engedélyt.
Amennyiben a betöltést fejlesztő környezetben teszteltjük nem lesz szükségünk ilyen php-ra mivel ezekre az swf-ekre nem vonatkoznak a biztonsági megkötések, viszont amint kikerülnek internetre, nem fognak működni.
Szükségünk van tehát egy php-ra ami letölti a kívánt adatot majd átadja nekünk.

getsource.php

<?php
    header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
    header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
    header("Cache-Control: no-store, no-cache, must-revalidate");
    header("Cache-Control: post-check=0, pre-check=0", false);
    header("Pragma: no-cache");

    $url = $_GET["url"];
    $page = file_get_contents($url);
    echo $page;
?>

A php egyszerűen lekéri a GET metódussal megkapott elérési útvonalon található weblap forrását és ezt fogjuk majd betölteni. Beállítunk továbbá pár fejlécet ami azt fogja megakadályozni hogy a böngésző a cache-ből töltse be az adatokat. Éles körülmények között nem árthat levédeni a php-t hogy csak a kívánt feladatokat lássa el.
Most hogy minden szükséges komponens a rendelkezésünkre áll, töltsük be és értelmezzük az adatokat.

private var urlLoader:URLLoader

private function init():void
{
	urlLoader = new URLLoader()
	urlLoader.addEventListener(Event.COMPLETE, onLoadComplete)
	urlLoader.addEventListener(IOErrorEvent.IO_ERROR, onLoadFailed)
	urlLoader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onLoadFailed)
}

private function loadSource(url:String):void
{
	var req:URLRequest = new URLRequest(
		"http://localhost/getsource.php?url="+escape(url)
	)

	urlLoader.load(req)
}

private function onLoadComplete(event:Event):void
{
	var info:IMDBSourceInfo =
		IMDBSourceParser.parse(urlLoader.data)

	trace("Cím:", info.title)
	trace("Év:", info.year)
	trace("Műfaj:", info.genres)
	trace("Szavazatok:", info.votes)
	trace("Értékelés:", info.score)
}

private function onLoadFailed(event:ErrorEvent):void
{
	trace("Hiba", event.text)
}

Az init és a loadSource meghívása után sikeres betöltés esetén meg kell kapnunk a kívánt információkat.

Források: IMDBTutorial.zip
Alkalmazás forrás nézete: srcview/SourceView.html