Estructura de l’índex en cercadors que processen en el client
Si el cercador intern es basa en un índex de paraules que no és molt extens, és possible enviar a el client dit índex i que sigui el navegador qui realitzi el procés de cerca utilitzant Javascript i maneig de DOM. Una solució consisteix a crear un arxiu de text pla amb l’índex de paraules clau, alguna cosa així com el que vam veure en el tema anterior:
...table 86tbody 86,12...
Així les paraules clau apunten els números de document on es troben. Al seu torn podem tenir un altre arxiu d’índexs amb les URL:
...12 /ijk/lmn.html86 /abc/def/ghi.html...
Això es pot enviar a client mitjançant variables de tipus string dins d’un arxiu .js, per exemple:
var indice = "...;table=86;tbody=86,12;...";var claves = "...;12=/ijk/lmn.html;...;86=/abc/def/ghi.html;...";
Després en el navegador podríem aplicar la funció split()
per convertir aquestes cadenes en els corresponents arrays i treballar sobre ells.
Però quan vaig fer la meva cercador amb Javascript vaig fer servir aquest concepte d’índex de claus sinó que vaig construir l’índex exclusivament usant les capçaleres identificats. Això és un exemple de l’HTML de l’índex ubicat al <body>
d’el document:
<div></div><div> ... <div class="item"> ... <div style="text-indent:3em"> <a href="/temas/xhtml-css/cabecera.html#h0" class="h0"> Elementos XHTML de cabecera </a> </div> ... </div> ...</div>
L’índex s’envia dins de el document, com a elements HTML, ubicat al contenidor amb id="indiceContenidos"
que inicialment té les propietats d’estil overflow: hidden; height: 0;
amb el que no serà mostrat en pantalla però el seu contingut està present. Després cada pàgina es tanca en un contenidor com id="cabecera"
, on l’identificador és el nom de el document sense l’extensió. Per tant aquest contenidor tindrà tots els vincles als encapçalats de el document cabecera.html
. Cada vincle <a>
es tanca també en un altre <div>
per dotar-lo de indentado amb estil.
En essència es tracta d’ carregar una variable global amb una col·lecció d’elements <a>
de l’índex, acció que es realitza amb la càrrega de la pàgina. Després l’usuari a l’buscar realment itera per aquesta col·lecció extraient el text interior de l’element i comprovant la coincidència amb la cadena de cerca. L’avantatge d’enviar a el client l’índex com a elements HTML és que es pot utilitzar directament com un índex de continguts, o millor dit, una taula de continguts, per a això amb Javascript canviem les propietats d’estil de l’contenidor amb id="indiceContenidos"
:
conten.style.overflow = "visible";conten.style.height = "auto";
el gran avantatge d’aquests cercadors amb Javascript és que no consumeixen recursos de servidor a l’processar les cerques. Tot es fa en el navegador. Però la mida és una limitació que condiciona aquests cercadors, de manera que per a un major volum de capçaleres ja no seria eficient enviar a el navegador d’client. Aquest cercador inclou 862 vincles a tots els encapçalats dels 16 documents que componen el glossari XHTML + CSS. El cercador es compon llavors de:
- Document busquedas.html de 152KB amb els vincles als encapçalats.
- Javascript busquedas.js de 7KB per gestionar el procés de cerques.
- Estil extern en part de l’arxiu xhtmlcss-estilo.css. Aquest ocupa 5KB però que concerneix el cercador és una quantitat menor.
En el que segueix s’exposa com funciona aquest cercador. Però partim que ja hem construït l’índex segons l’estructura assenyalada i que tornem a repetir de forma esquemàtica:
<div> ... <div> ... <div><a>...</a></div> <div><a>...</a></div> ... </div> ... <div> ... <div><a>...</a></div> <div><a>...</a></div> ... </div> ... </div>
Per construir aquest índex de forma automàtica pot consultar l’eina d’un tema següent que parla de construir els índexs. Es tracta d’una utilitat que li permetrà fabricar aquest índex partint dels documents html seleccionats, guardant-se el HTML generat en un arxiu de text. En aquesta pàgina s’explica com usar aquesta eina.
Estructura HTML-CSS de l’cercador amb Javascript
Visualment nostre cercador té aquesta aparença:
Es tracta d’un quadre de text per a la cadena de recerca, un botó per iniciar la recerca, un altre per obrir les opcions inferiors i un botó per mostrar l’índex. Les opcions de cerca permeten cercar entrades que continguin totes les paraules o només alguna paraula. A més també poden buscar-se només paraules completes i diferenciar entre majúscules i minúscules.
No convé mostrar el patró de cerca si aquest cercador es destinés a un lloc d’ús general. Però com aquí l’objectiu és mostrar tots els detalls possibles, s’inclou aquest patró de cerca. Així fins i tot l’usuari interessat pot provar altres patrons.Des del punt de vista de la seguretat hem de recordar que aquest cercador funciona en el navegador d’l’usuari, de manera que els errors per patrons indeguts només afectaran el navegador i no a servidor.
Finalment hi ha un limitador per al número màxim de resultats, ja que per a certes recerques com lletres per exemple, el nombre de resultats pot ser molt alt. Però de totes maneres la millor manera de comprendre tot això és posar-lo en execució.
L’estructura general per blocs és la següent:
<div class="vinculos"> ...Aquí van la cadena de búsqueda y botones...</div> <div > <div>OTRAS OPCIONES DE BÚSQUEDA:<br /> ...Aquí van las opciones... </div> <div> ...Aquí se ponen los resultados de la búsqueda... </div> <div> ...Aquí va el índice de vínculos... </div> </div>
L’estil CSS específic per a aquest document és:
div#indiceContenidos { overflow: hidden; height: 0; }div#indiceContenidos a { text-decoration: none; }div#vinculosIndice { display: none; }div#resultados { border: rgb(49, 99, 98) solid 1px; }div#resultados a { text-decoration: none; color: navy; }div#opcionesBusca { border: rgb(49, 99, 98) solid 1px; overflow: hidden; height: 0; }
El contenidor amb id="resultados"
està en principi buit de contingut , ja que aquí es posen de forma dinàmica els resultats de cada cerca. El contenidor amb class="vinculos"
té aquesta estructura:
<div class="vinculos"> Cadena: <input type="text" value="" size="40" onblur="patronear()" /> <input type="button" value="Buscar" onclick="buscar()" /> Encontrados: <em>0</em> <input type="button" value="Opciones" onclick="opciones()" /> <input type="button" value="Índice" onclick="indiceContenidos()" /> <div>...</div></div>
El quadre de text per a la cadena de recerca inclou un esdeveniment onblur
de tal manera que a l’sortir d’aquest element s’actualitza el patró de recerca amb la funció patronear()
de Javascript, funcions que s’inclouen en el mòdul busquedas.js. El botó per iniciar la recerca crida a la funció buscar()
. Les altres funcions són opciones()
i indiceContenidos()
que mostren el quadre d’opcions i l’índex total de continguts, ja que tots dos contenidors estan inicialment ocults tal com es va declarar en el CSS. L’índex total de continguts amb id="indiceContenidos"
s’oculta amb height:0
doncs necessitem tenir-lo carregat a la pàgina perquè anem a fer la cerca iterant pels elements <a>
, així que encara que no es visualitzen si hi són presents. Vegem ara l’estructura de el quadre d’opcions:
<div> Conjuntivas:<input type="checkbox" /> Palabra completa:<input type="checkbox" /> Diferencia mayúsculas/minúsculas:<input type="checkbox" /> Patrón:<span class="monospace">/</span> <input type="text" value="" size="40" class="monospace" /> <span class="monospace">/</span> <input type="text" size="5" value="" class="monospace" /> <input type="button" value="Actualizar patrón" onclick="patronear()" /> Resultados máximos:<input type="text" value="100" size="5" />, número de búsquedas máximas:<input type="text" value="0" size="5" /> de un total de <span>0</span><br /> </div>
Finalment el contenidor de resultats (amb id="resultados"
) està buit inicialment i de l’contenidor d’índexs (amb id="indiceContenidos"
) ja es va exposar la seva estructura en l’apartat anterior.
el Javascript per fer anar el cercador intern
l’estructura de variables globals i funcions d’aquest mòdul Javascript és la següent:
- variables globals
-
inputPatron
: una referència a l’element<input type="text">
per emmagatzemar el patró. -
inputOpcionesFlags
: una referència a l’element<input type="text">
que emmagatzema les opcions o flags de el patró. -
vinculos
: Una variable que contindrà la col·lecció d’elements<a>
de l’índex. -
hasta
: Un enter que inicialment conté el nombre d’elements d’aquest índex. -
particulas
: Una variable de tipus string que conté una llista de partícules (preposicions, adverbis, etc.) que seran eliminades de la cadena de cerca.
-
- Funcions
-
window.onload = function()
: Inicialitzar cercador amb la càrrega de la pàgina. -
function patronear()
: Preparar patró de cerca. -
function buscar()
: cerca aquest patró. -
function opciones()
: Mostra o oculta quadre opcions. -
function indiceContenidos()
: Mostra o oculta índex de continguts.
-
No vaig a exposar el codi complet d’aquest Javascript. Amb navegadors com Firefox pot descarregar aquest codi amb facilitat i consultar-lo. La funció d’inicialització amb la càrrega de la pàgina simplement s’encarrega de referenciar les variables globals i omplir l’array o col·lecció de vincles. Les funcions que mostren o oculten els contenidors d’opcions o índex no té cap complexitat. Només veurem les funcions per preparar el patró i per cercar.
Quan vam sortir de el quadre de la cadena de recerca s’executa l’esdeveniment onblur
que crida a la funció patronear()
, que és l’encarregada de preparar el patró.
function patronear() { document.getElementById("encontrados").innerHTML = 0; inputPatron.value = ""; var cadenaBusca = document.getElementById("cadBusca").value; var palabraCompleta = document.getElementById("palabraCompleta").checked; if (cadenaBusca != "") { //escapa caracteres reservados de expresiones regulares cadenaBusca = cadenaBusca.replace(/(\?\\\/\^\{\}\|])/g, "\\$1"); //elimina espacios al inicio o final cadenaBusca = cadenaBusca.replace(/^\s+|\s+$/g, ""); //suprime las preposiciones, artículos y otras palabras intermedias, es //decir rodeadas de un espacio por ambos lados. var patron = new RegExp("\\b(?:" + particulas + ")\\b", "gi"); cadenaBusca = cadenaBusca.replace(patron, " "); //Convierte más de un espacio en uno cadenaBusca = cadenaBusca.replace(/\s+/g, " "); //Quita los espacios iniciales y finales por haber partículas ahí cadenaBusca = cadenaBusca.replace(/^\s+|\s+$/g, ""); if ((cadenaBusca == "") || (cadenaBusca == " ")){ inputPatron.value = ""; } else { //reemplaza los espacios intermedios por la alternativa | o conjuntiva .*? //pero diferenciando si buscamos en palabra completa o no var conj = "\|" if (document.getElementById("conjuntiva").checked) { conj = ".*?"; } if (palabraCompleta){ cadenaBusca = cadenaBusca.replace(/\s+/g, "\\b" + conj + "\\b"); cadenaBusca = "\(?:\\b" + cadenaBusca + "\\b\)"; } else { cadenaBusca = cadenaBusca.replace(/\s+/g, conj); cadenaBusca = "\(?:" + cadenaBusca + "\)"; } inputPatron.value = cadenaBusca; var opciones = ""; var difMayusMinus = document.getElementById("caseMM").checked; if (!difMayusMinus) { opciones = opciones + "i"; } inputOpcionesFlags.value = opciones; } } }
el patró que preparem té dos camps, un amb l’expressió regular que es guardarà al <input type="text">
i que teníem referenciat en la variable global inputPatron
, i d’altra banda els flags o modificadors de l’expressió regular que emmagatzemem al <input type="text">
i que també referenciem a la variable global inputOpcionesFlags
. Aquestes variables després seran consultades a la funció buscar()
.
El patró es construeix escapant els caràcters reservats d’expressions regulars i després eliminant les partícules d’ús freqüent. Es tracta de la variable global particulas
que conté una llista separada per la barra vertical de preposicions, adverbis, articles i altres paraules d’ús freqüent en el llenguatge i que no aporten res a la recerca .Eliminem espais en l’inici o final de la cadena de recerca i convertim diversos espais en un de sol. Si a la fi la cadena conté alguna cosa més que un espai llavors construïm el patró.
En aquest moment tindrem una cadena de recerca amb paraules separades per espai. Si el <input type="checkbox" />
està desactivat és que anem a buscar alguna d’aquestes paraules. Construïm el patró separant les alternatives amb la barra vertical (una disjuntiva). En un altre cas estem buscant totes les paraules i es construeix separant-les amb .*?
que equival a una conjuntiva. Després incorporem el cas els delimitadors \b
per paraula completa. En el quadre de flags afegim l’opció i
si és que la recerca és insensible majúscules-minúscules, és a dir, no diferencia entre majúscules i minúscules.
A l’sortir de la funció patronear()
ja tenim en el quadre <input type="text">
el patró i en el quadre <input type="text">
els flags. Quan es premi el botó corresondiente executem la funció buscar()
:
function buscar() { document.getElementById("encontrados").innerHTML = 0; var divResultados = document.getElementById("resultados"); divResultados.innerHTML = ""; var cadBusca = inputPatron.value; var opciones = inputOpcionesFlags.value; var maxBusca = 1 * document.getElementById("maxBusca").value; var maxResulta = 1 * document.getElementById("maxResulta").value; var maxResultaBase = maxResulta; var item = 0; if (cadBusca != "") { var patron = new RegExp(cadBusca, opciones); document.getElementById("iterTotal").innerHTML = hasta; if (maxBusca < hasta) { hasta = maxBusca; } for (var i=0; i<hasta; i++) { var cadena = getInnerText(vinculos); var resultado = cadena.match(patron) ; if (resultado != null) { item++; if (item <= maxResulta) { divResultados.innerHTML += ' <a href="' + vinculos.href + '">' + vinculos.innerHTML + '</a><br />'; } else { var men = ""; if (maxResulta == maxResultaBase) { men = " primeros "; } else { men = " siguientes "; } document.getElementById("encontrados").innerHTML = item; var mensaje = window.confirm("Se muestran los" + men + maxResultaBase + " resultados. ¿Continuar buscando?"); if (mensaje) { maxResulta += maxResultaBase; } else { break; } } } } } document.getElementById("encontrados").innerHTML = item;}
La variable maxBusca
limita el nombre màxim d’iteracions en la col·lecció de vincles. La variable maxResulta
controla el nombre de resultats a retornar. Després vam declarar l’expressió regular amb new RegExp(cadBusca, opciones)
. En aquest cas fem servir un string en cadBusca
, el patró construït abans amb la funció patronear()
, afegint el flag. El procés de recerca consisteix en iterar per la col·lecció de vincles extraient el text interior d’aquests elements <a>
. Ho fem amb la funció getInnerText()
de la lliçó general.js amb funcions que unifiquen el comportament dels navegadors a l’extreure el text interior d’un element. Per a això és necessari vincular aquest mòdul a la capçalera de el document de cerques:
<script type="text/javascript" src="alguna_carpeta/general.js" charset="ISO-8859-1"></script>
Si no desitja incorporar aquest mòdul general.js pot situar la funció getInnerText()
directament en el mòdul busquedas.js. Sobre aquest text interior de l’element <a>
apliquem el patró de recerca, obtenint una matriu de coincidències en la variable resultado
. Controlem que el nombre de resultats sigui menor que els declarats per a presentar (variable maxResulta
), en aquest cas els anem acumulant amb innerHTML
construint dinàmicament elements vincle. Si sobrepassa maxResulta
preguntem a l’usuari si seguim iterant donant-li l’opció de trencar el bucle.
Són possibles moltes millores, però essencialment he mostrat una forma per fer un cercador intern amb Javascript. El seu gran avantatge és que s’executa en el navegador d’l’usuari, on la devolució de resultats és més ràpida i més no consumeix recursos de servidor. Els desavantatges són principalment la necessitat que l’usuari tingui activat Javascript i la mida de l’arxiu d’índexs que ha de ser transferit des del servidor. Aquest aspecte pot millorar-se si en lloc de fer un índex HTML ho féssim amb només text, però tot i així aquest cercador sempre estarà limitat per una mida màxima d’aquest arxiu índex. No sembla adequat enviar-li a el client un document incloent l’arxiu d’índex amb mida que excedeixi massa els 150KB.
En aquests moments he implementat en aquest lloc un altre cercador basat en índexs emmagatzemats com arrays serializados en arxius de text i executant-se en el servidor que s’exposa en el tema següent. Però tot i així mantinc aquest cercador amb Javascript doncs, independentment de la seva qualitat, és un bon exercici per aprendre aspectes de Javascript com maneig de DOM o expressions regulars.