In seguito all’apprezzamento della pillola “costruiamo un vero motore di ricerca con MATCH e AGAINST”
http://forum.html.it/forum/showthrea...+match+against
alcuni utenti mi hanno chiesto come fare per creare un motore di ricerca non orientato al contenuto di un database ma a delle semplici pagine html. In poche parole come implementare un motore di ricerca in un sito html. L’idea è semplice: Apriamo i file html, gli togliamo tutto tranne il contenuto e poi lo analizziamo.
Diciamo che i file html sono per comodità nella stessa cartella dello script di ricerca. Come prima cosa dovremo leggere il contenuto della cartella prendendo in considerazione i soli file html.
Codice PHP:
$folder = "./"
$dir = opendir($folder);
while ($file = readdir($dir))
{
if(eregi(".htm|.html", $file))
{
$fp = fopen($folder . $file, "r");
$string = fread($fp, filesize($folder . $file));
Fino a questo punto è semplice. Avvio un ciclo che mi permette di leggere uno per uno i file contenuti nella cartella. In questo ciclo pongo una condizione: Se i file hanno estensione .htm o .html allora li apro e metto il contenuto nella variabile $string. Ora dobbiamo ripulire $string da quello che non ci serve.
La prima idea che potrebbe venire è quella di usare la funzione strip_tags per eliminare tutti i tag. Sì, ma non è ancora il momento; dobbiamo prima considerare delle eccezioni. Ad esempio se la nostra pagina html contiene del javascript, strip_tags eliminerà i tags <script> ma non il loro contenuto:
Codice PHP:
testo</p>
<script language=”JavaScript”>
alert("io non mi cancello")
</script>
Altro testo</p>
//questo codice passato per strip_tags darà questo risultato
Testo
alert("io non mi cancello")
Altro testo
Lo stesso discorso vale per un eventuale CSS inline. Verranno eliminati i tag <style> ma non il contenuto. Penso sia tutto, ma se dovessero esserci altre eccezioni che mi sono sfuggite, fate sapere.
Allora come risolviamo il problema? Naturalmente con le espressioni regolari.
Codice PHP:
$string = preg_replace("'<script[^>]*?>.*?</script>'si", '', $string);
$string = preg_replace("'<style[^>]*?>.*?</style>'si", '', $string);
In questo modo abbiamo eliminato l’eventuale javascript e css inline da $string, quindi finalmente passeremo per srtip_tags.
Codice PHP:
$string = strip_tags($string);
A questo punto abbiamo una stringa pulita con unicamente il contenuto testuale della pagina pronto per essere analizzato. Ricordate che siamo ancora all’interno di un ciclo quindi queste operazioni sono eseguite per ogni file html.
Adesso prendiamo la nostra chiave di ricerca (diciamo $key che contiene “cane gatto topo”) e la esplodiamo.
Codice PHP:
$keys = explode(" ", $key);
Dobbiamo ora avviare un nuovo ciclo per verificare se in $string è contenuto cane o gatto o topo. Bisogna fare tante verifiche quante sono le chiavi di ricerca.
Codice PHP:
$score = 0;
for($i = 0; $i < count($keys); $i++)
{
if (eregi($keys[$i], $string))
{
$score = $score + 1;
}
}
if ($score > 0)
{
$ResultConteiner[] = array($score, $file);
}
Qui si controlla per ogni chiave, se questa chiave è contenuta in $string. Se è il caso, incrementiamo $score di 1 (in pratica $score ci da la pertinenza; se in $string ci sarà sia cane che gatto che topo, $score varrà 3). In seguito, se score è maggiore di 0 vuole dire che è stata trovata almeno un’occorrenza dunque viene registrato nell’array multidimensionale $ResultConteiner sia lo score che il nome della pagina; e qui il ciclo riprende con la prossima pagina. Una volta analizzate tutte le pagine e terminato il ciclo, la prossima operazione che faremo sarà:
Codice PHP:
rsort($ResultConteiner);
In questo modo ordiniamo i risultati per pertinenza.
Riepiloghiamo il codice scritto fin’ora
Codice PHP:
$folder = "./"
$dir = opendir($folder);
while ($file = readdir($dir))
{
if(eregi(".htm|.html", $file))
{
$fp = fopen($folder . $file, "r");
$string = fread($fp, filesize($folder . $file));
$string = preg_replace("'<script[^>]*?>.*?</script>'si", '', $string);
$string = preg_replace("'<style[^>]*?>.*?</style>'si", '', $string);
$string = strip_tags($string);
fclose($fp);
$score = 0;
$keys = explode(" ", $key);
for($i = 0; $i < count($keys); $i++)
{
if (eregi($keys[$i], $string))
{
$score = $score + 1;
}
}
if ($score > 0)
{
$ResultConteiner[] = array($score, $file);
}
}
}
rsort($ResultConteiner);
A questo punto abbiamo l’array multidimensionale $ResultConteiner da utilizzare per il nostro output.
Questo array è strutturato come segue:
$ResultConteiner[chiave1][chiave2]
La chiave 2 può avere valore 0 o 1. 0 è lo score mentre in 1 è contenuto il nome della pagina. La chiave 1 può avere tanti valori quante sono le pagine nelle quali è stata trovata almeno un’occorrenza.
Quindi possiamo estrarre il risultato della ricerca in questo modo:
Codice PHP:
$max = count($ResultConteiner);
if ($max == 0)
{
echo "Nessun risultato";
}
else
{
for ($i = 0; $i < $max; $i++)
{
echo '[url="' . $folder . $ResultConteiner[$i][1] . '"]' . $ResultConteiner[$i][1] . "[/url]
";
}
}
Possiamo anche stampare lo score nel formato ad esempio 4/5 (4 occorrenze trovate su 5 chiavi analizzate) modificando l’echo come segue:
Codice PHP:
echo '[url="' . $folder . $ResultConteiner[$i][1] . '"]' . $ResultConteiner[$i][1] . "[/url]” . $ResultConteiner[$i][0] . " / " . $keys . "
”;
Un’ultima considerazione va fatta: Dobbiamo minimamente filtrare il criterio di ricerca. Pensiamo ad esempio che l’utente inserisca come chiave “vacanze a Parigi”. Probabilmente in tutte le pagine sarà presente una “a”, dunque il nostro risultato sarà un tantino forviante. Direi che possono essere scartate tutte le parole fino a 3 lettere, che se ci pensate sono per la stragrande maggioranza delle congiunzioni, articoli o pronomi del tutto inutili ai fini di una ricerca (da notare che la ricerca fulltext di mysql esegue automaticamente una procedura del genere, ma molto più raffinata). Come fare? Penso che la soluzione più elegante sia di scrivere una funzione javascript da invocare OnSubmit che verifichi i nostri criteri. Personalmente non sono in grado di scriverla, ma se qualche volenteroso lettore lo sa fare, la posti pure. Io intanto farò questa verifica in php.
Codice PHP:
$keys = explode(' ', $_POST['key']); //la chiave che ci è inviata dal form
for ($i = 0; $i < count($keys); $i++)
{
if (strlen($keys[$i]) <= 3)
{
header ("Location: pagina_con_il_form.php?alert=1");
die;
}
}
Mentre la pagina con il form sarà più o meno così:
Codice PHP:
if ($_GET['alert'] == 1)
{
echo '<script type="text/javascript">
alert("Non sono ammesse parole con meno di 4 lettere");
</script>';
}
echo '<form action="pagina_della_ricerca.php" method="POST">
<input type="text" name="key" /> <input type="submit" value="cerca" />
</form>';
Ecco. Se siete arrivati fino a qui siete degli eroi, e vi ricompenserò con una classe pronta per l’uso che riassume tutto quello che abbiamo trattato fino ad ora.
Codice PHP:
class HtmlSearch
{
var $HtmlFolder = "./";
var $PrintScore = true;
var $ResultConteiner = array();
var $NumKey;
function ParsingFiles ($key)
{
$dir = opendir($this->HtmlFolder);
while ($file = readdir($dir))
{
if(eregi(".htm|.html", $file))
{
$fp = fopen($this->HtmlFolder . $file, "a+");
$string = fread($fp, filesize($this->HtmlFolder . $file));
$string = preg_replace("'<script[^>]*?>.*?</script>'si", '', $string);
$string = preg_replace("'<style[^>]*?>.*?</style>'si", '', $string);
$string = strip_tags($string);
fclose($fp);
$score = 0;
$keys = explode(" ", $key);
$this->NumKey = count($keys);
for($i = 0; $i < $this->NumKey; $i++)
{
if (eregi($keys[$i], $string))
{
$score = $score + 1;
}
}
if ($score > 0)
{
$this->ResultConteiner[] = array($score, $file);
}
}
}
}
function SearchResult ($key)
{
$this->ParsingFiles($key);
rsort($this->ResultConteiner);
$max = count($this->ResultConteiner);
if ($max == 0)
{
echo "Nessun risultato";
}
else
{
for ($i = 0; $i < $max; $i++)
{
echo '[url="' . $this->HtmlFolder . $this->ResultConteiner[$i][1] . '"]' . $this->ResultConteiner[$i][1] . "[/url] ";
if ($this->PrintScore)
{
echo $this->ResultConteiner[$i][0] . " / " . $this->NumKey . "
";
}
else
{
echo '
';
}
}
}
}
function Form ()
{
if ($_GET['alert'] == 1)
{
echo '<script type="text/javascript">
alert("Non sono ammesse parole con meno di 4 lettere");
</script>';
}
echo '<form action="' . basename($_SERVER['PHP_SELF']) . '" method="POST">
<input type="text" name="key" /> <input type="submit" value="cerca" />
</form>';
}
function ValidateKeys ()
{
$keys = explode(' ', $_POST['key']);
for ($i = 0; $i < count($keys); $i++)
{
if (strlen($keys[$i]) <= 3)
{
header ("Location: " . basename($_SERVER['PHP_SELF']) . "?alert=1");
die;
}
}
$this->SearchResult($_POST['key']);
}
function PageSwitch ()
{
if (!isset($_POST['key']))
{
$this->Form();
}
else
{
$this->ValidateKeys();
}
}
}
Per utilizzarla vi basterà istanziare la classe ed invocare il metodo PageSwitch.
Codice PHP:
$search = new HtmlSearch();
$search->PageSwitch();
PageSwitch controlla che key sia settata. Se non lo è invia al form, altrimenti valida la chiave. Se la chiave contiene parole con meno di 4 lettere, rimanda al form attivando l’alert. Se tutto è ok inizia la ricerca e stampa i risultati.
Ovviamente il layout può essere adattato a tutte le esigenze; in particolare i metodi Form e SearchResult.
Utilizzate questa classe con prudenza. Un sito con molte pagine rischia di produrre una ricerca lenta (ricordate che abbiamo due cicli nidificati).