<?php
/**
 * Cette classe permet d'analyser une page web
 *
 * Ces deux attributs de la classe sont statiques
 * @param array  Parse       Expressions régulières utilisées pour analyser la page
 * @param array  Tags        Balises dont on souhaite mémoriser le contenu
 *
 * Les attributs qui suivent dépendent de l'objet
 * @param string Url         Adresse de la page
 * @param string Base        Adresse de base de la page
 * @param array  FileContent Contenu du fichier (brut ou découpé)
 * @param array  Memorized   Contenu du fichier retenu pour l'indexation
 * @param array  Meta        Métadonnées de la page
 * @param int    Chars       Nombre de caractères mémorisés
 * @param int    Words       Nombre de mots mémorisés
 * @param array  Images      Adresses des images et leur contenu alternatif disponible
 * @param array  Links       Liens hypertextes contenus dans la page
*/

class Spider
{
    private static 
$Parse = array(
        
'comments' => '`<!--(?:(?:\s|.)*?)-->`im',
        
'head'     => '`<head(?: [^>]*)?>((?:\s|.)*?)</head>`im',
        
'body'     => '`<body(?: [^>]*)?>((?:\s|.)*?)</body>`im',
        
'meta'     => '`<meta (?:http-equiv|name)="(.+?)"+ content="(.+?)"(?: /)?>`im',
        
'tag'      => '`<%1$s(?: [^>]*)?>((?:\s|.)*?)?</%1$s>`im',
        
'image'    => '`<img(?:[^>]+?)src="(.+?)"(?:[^>]*?)(?: alt="(.*?)" )(?:.*?)(?: /)?>`im',
        
'link'     => '`<a(?:[^>]+?)?href="(.+?)"(?:[^>]*?)>((?:\s|.)*?)</a>`im'
    
);

    private static 
$Tags = array('p''h[1-6]''strong''em');

    
/* ~ object properties ~ */
    
public $Url '';
    public 
$Base '';
    public 
$FileContent = array('file' => '''head' => '''body' => '');
    public 
$Memorized = array();
    public 
$Meta = array();
    public 
$Chars 0;
    public 
$Words 0;
    public 
$Images = array();
    public 
$Links  = array();

    
/**
     * Ouvre une connexion et télécharge le fichier
     * à analyser.
     *
     * @param string url URL du fichier à analyser
     */
    
public function __construct($url) {
        if(empty(
$url))
            throw new 
Exception('Vous devez préciser une adresse à visiter');
        if(!(
$this->FileContent['file'] = @file_get_contents($url)))
            throw new 
Exception('Impossible de récuperer la page à analyser');

        
$this->Url $url;
        
$this->Parse();
    }

    
/**
     * Analyse le fichier
     *
     * @return void
     */
    
protected function Parse() {
        
# Vérifions si cette page est une page HTML
        
if(!preg_match('`<html(.*?)>`i'$this->FileContent['file']))
            throw new 
Exception('Ce type de fichier n\'est pas supporté par ce spider');

        
# On supprime les commentaires inutiles, puis on
        # découpe le fichier selon l'en-tête et le corps de page
        
$temp_content preg_replace(self::$Parse['comments'], ''$this->FileContent['file']);

        
preg_match(self::$Parse['head'], $temp_content$this->FileContent['head']);
        
$this->FileContent['head'] = $this->FileContent['head'][1];
        
preg_match(self::$Parse['body'], $temp_content$this->FileContent['body']);
        
$this->FileContent['body'] = $this->FileContent['body'][1];

        
# analyse des métatags
        
$meta = array();
        
preg_match_all(self::$Parse['meta'], $this->FileContent['head'], $metaPREG_SET_ORDER);
        for(
$i 0$i sizeof($meta); $i++) {
            
$this->Meta[strtolower($meta[$i][1])] = $meta[$i][2];
        }

        
# On vérifie une deuxième fois si le fichier est bien
        # une page (X)HTML
        
if(    isset($this->Meta['content-type']) &&
            
strpos($this->Meta['content-type'], 'text/html') === false &&
            
strpos($this->Meta['content-type'], 'text/xhtml+xml') === false)
        {
            throw new 
Exception('Ce type de fichier n\'est pas supporté par ce spider');
        }

        
# analyse du contenu
        
foreach(self::$Tags as $tag) {
            
$cur = array();
            
preg_match_all(sprintf(self::$Parse['tag'], $tag), $this->FileContent['body'], $cur);
            
$cur $cur[1];
            for(
$i 0$i sizeof($cur); $i++) {
                
$cur[$i] = strip_tags($cur[$i]);
                
$this->Words += sizeof(explode(' '$cur[$i]));
                
$this->Chars += strlen($cur[$i]);
                
$this->Memorized[$tag][] = $cur[$i];
            }
        }

        
# Compter les images
        
$imgs = array();
        
preg_match_all(self::$Parse['image'], $this->FileContent['body'], $imgs);
        for(
$i 0$i sizeof($imgs[1]); $i++) {
            
$this->Images[] = array($imgs[1][$i], $imgs[2][$i]);
        }

        
# Compter les liens
        
$links = array();

        
preg_match_all(self::$Parse['link'], $this->FileContent['body'], $links);
        for(
$i 0$i sizeof($links[1]); $i++) {
            if(
$links[1][$i] == '#')
                continue;
            
$this->Links[] = array($this->Link($links[1][$i]), $links[2][$i]);
        }
    }

    
/**
     * Adapate le lien
     *
     * @param string href Lien a éditer
     *
     * @return string
     */
    
public function Link($href) {
        
# Deviner l'adresse absolue de base :
        
$root $this->Root();
        if(!
preg_match('`^(http|ftp|mailto)://`'$href)) {
            return 
$this->Root().$href;
        }
        return 
preg_replace('`^\./`'$url$href);
    }

    
/**
     * Recherche l'adresse absolue de base
     *
     * @return string
     */
    
public function Root() {
        
# Si on a déjà cherché cette adresse, on la renvoie
        # directement
        
if(!empty($this->Base))
            return 
$this->Base;

        
# On cherche une balise <base> éventuelle
        # sinon on utilise l'adresse de la page
        
$base = array();
        if(
preg_match('`<base href="([^"]+?)"(?: /)?>`i'$this->FileContent['head'], $base)) {
            
$this->Base $base[1];
        } else {
            
$this->Base dirname($this->Url);
        }

        
# Ajoute un / à la fin si absent
        
if($this->Base{strlen($this->Base)-1} != '/')
            
$this->Base .= '/';

          return 
$this->Base;
    }

    
/**
     * Fonction permettant de lancer le crawling
     * et l'indexation des pages.
     *
     * @param string url    Adresse du point de départ
     * @param int     row   Nombre de passages (profondeur maximum)
     * @param array   pages Tableau d'objets Spider contenant le resultat de l'analyse
     *
     * @return Spider
     */
    
public static function Analyse($url, &$row, &$pages) {
        
$row--;
        if(!
is_null($url))
            
$p = new Spider($url);
        if(!
is_null($p)) {
            if(
$row 0) {
                foreach(
$p->Links as $lk) {
                    
self::Analyse($lk[0], $row$pages);
                }
            }
            
$pages[] = $p;
        }

        return 
$p;
    }
}

# Lancer le crawling
$profondeur 1;
$pages = array();
Spider::Analyse('http://exemple.com'$profondeur$pages);