logoApuntes agrupados: Ficheros xml


Ficheros xml (xml): Se encontraron 3 apuntes:

  1. Ajax, con X de XML
  2. Este diario: sitemap.xml
  3. XML para guardar tablas


Titulo: Ajax, con X de XML


emoticón de Caricatos Publicado el día 08 de mayo de 2012
id=85

Mundo Hace algunos días me preguntaron en los foros del web si tenía un ejemplo simple de lectura de datos estructurados XML con Ajax...

... después de revisar mis códigos, he encontrado muchas rutinas con esa tecnología, pero de una complejidad que no creí correcto referenciar, así que se me ocurrió hacer algo nuevo que pueda usar en esta página. La conclusión ha sido crear un sistema que despliegue la lista de apuntes del sector "Archivos", a la derecha de este diario.

Preliminares

Viendo el código fuente de la página, descubrimos que se trata de una lista desordenada "ul" (unordered list) y en cada item "li" (list item) el enlace del tipo "?archivos=mayo+2012", y al pinchar nos lleva a la lista correspondiente, pero podríamos simplemente mostrar otra lista de los títulos asociados desplegándola debajo.

Para que sea XML hemos pensado que podría tener una estructura como los ficheros RSS y de esa manera conseguir que sea visible fácilmente usando la hoja XSL que ya teníamos hecha para nuestro sistema de sindicación.

Vamos a dejar de lado tantas siglas y enlazamos al fichero resultante de este mes. Aunque todos nuestros códigos están accesibles en nuestros listados, a continuación mostramos el que hemos usado para nuestro propósito:

$aaaa = $_GET["a"];
$mm = $_GET["m"];
header("Content-type: text/xml");
	ob_start();
	echo <<< cabecera
<?xml version="1.0" encoding="ISO-8859-1" ?>
<?xml-stylesheet type="text/xsl" href="diario.rss.xsl" ?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<atom:link href="$diario_ruta/diario.rss.xml" rel="self" type="application/rss+xml" />
<title>$diario_titulo: extras</title>
<link>{$_SERVER[PHP_SELF]}?extra=extras_xml</link>
<description>$diario_titulo</description>

cabecera;
$cabeza = ob_get_clean();

$sql = "select titulo, apunte from $tabla_apuntes where activo > 0 and fecha like '$aaaa-$mm%' order by fecha desc";
$listado = array();
$res = @mysql_query($sql);
$cuenta = mysql_num_rows($res);
while ($dato = mysql_fetch_array($res))	{
	$titulo = $dato["titulo"];
	$link = $diario."?titulo=".urlencode($titulo);
	preg_match('/\[rss\](.*?)\[\/rss\]/is', $dato["apunte"], $des);
	array_push($listado, "\n<item>\n<title>$titulo</title>\n<link>$link</link>\n<description>{$des[1]}</description>\n</item>\n");
}
$lista = implode("\n", $listado);

echo $cabeza.$lista."\n</channel>\n</rss>";

Para que sea más fácil nuestro propósito con el lenguaje javascript hemos modificado los enlaces añadiendo un atributo "id" del tipo "archivos_aaaa_mm", como por ejemplo: id="archivos_2012_05" para el enlace del mes de mayo de este año 2012.

Código no intrusivo

Para que sea inapreciable el arreglo hemos añadido el código después de cargarse la página. No obstante los enlaces seguirán siendo funcionales, así que mientras no se hayan cargado todos los elementos de la página serán los de siempre, y cuando se cargue el código, los enlaces desplegarán una lista con las referencias asociadas.

var lista_archivos = new Object();
function enlace_archivo_ajax(e)	{
	archivos = this;
	archivos_id = archivos.id;
	desglose_id = archivos.id.split("_");
	mes = desglose_id.pop();
	año = desglose_id.pop();
	id_archivos = "desglose_" + mes + "_" + año;
	if (lista_archivos[id_archivos] == undefined)	{
		lista_archivos[id_archivos] = [];
		lista_archivos[id_archivos]["visible"] = true;
		nueva_espera = document.createElement("img");
		nueva_espera.src = "diario/dibujos/espera.gif";
		tag(archivos_id).parentNode.appendChild(nueva_espera);
		url = "diario.jocker.php?extra=archivos&a=" + año + "&m=" + mes;
		Ajax = objetoAjax();
		Ajax.open("get", url, true);
		Ajax.onreadystatechange = function()	{
			if	(Ajax.readyState == 4 && Ajax.status == 200) {
				nueva_capa = document.createElement("ul");
				nueva_capa.id = id_archivos;
				nueva_capa.className = "enlaces";
				tag(archivos_id).parentNode.appendChild(nueva_capa);
				respuesta = Ajax.responseXML.documentElement;
				items = respuesta.getElementsByTagName("item");
				for (i = 0, total = items.length; i < total; i++)	{
					el_enlace = items[i].getElementsByTagName("link")[0].firstChild.nodeValue;
					el_titulo = items[i].getElementsByTagName("title")[0].firstChild.nodeValue;
					la_desc = (items[i].getElementsByTagName("description")[0].hasChildNodes()) ?
						items[i].getElementsByTagName("description")[0].firstChild.nodeValue: "sin descripción";
					nuevo_li = document.createElement("li");
					nuevo_enlace = document.createElement("a");
					nuevo_enlace.href = el_enlace;
					nuevo_enlace.setAttribute("title", la_desc);
					nuevo_enlace.appendChild(document.createTextNode(el_titulo));
					nuevo_li.appendChild(nuevo_enlace);
					nueva_capa.appendChild(nuevo_li);
				};
				tag(archivos_id).parentNode.removeChild(nueva_espera);
			}
		}
		Ajax.send();
	}
	else	{
		tag(id_archivos).style.display = (lista_archivos[id_archivos]["visible"]) ? "none":"block";
		lista_archivos[id_archivos]["visible"] = !lista_archivos[id_archivos]["visible"];
	}
	cancelar_evento(e);
}

Aunque pueda parecer mucho código, la parte "Ajax" es bastante sencilla: se lee el fichero XML/RSS correspondiente al enlace pinchado y se genera la lista desordenada con sus enlaces asociados. Los mismos quedan resumimos en la siguiente lista:

Por cada item de la lista se obtienen los datos: "title", "link" y "description" que usaremos al desplegar la lista (title es el texto que se muestra, link el enlace y description el atributo title).

Evitando redundancias

Otra parte fundamental del código y tal vez la razón de su extensión, es la que evita realizar más de una vez una misma consulta. Cuando se despliega una lista, se puede plegar pinchando sobre el mismo enlace, pero si volvemos a querer desplegarla simplemente se muestra lo que anteriormente se había ocultado; considerando que es casi imposible que se realizaran cambios en los enlaces de cada lista, no tienen ningún sentido volver a leer los datos ya leídos en la misma sesión (un vicio bastante común).






Titulo: Este diario: sitemap.xml


emoticón de Caricatos Publicado el día 17 de octubre de 2012
id=98

Hemos tenido un pequeño problema con la generación de nuestro "sitemap.xml" debido a que ha crecido bastante el contenido de esta página, por lo que hemos decidido revisarlo minuciosamente.

En concreto, tan solo obteníamos unas pocas más de cien entradas, así que lo estamos re-implementando.

Los enlaces

Estamos interesados en que nuestras enlaces se generen correctamente así que hemos revisado que no exista ningún error en su localización.

Hemos descubierto algunos enlaces, sobre todo de imágenes que estaban mal referenciadas, así que decidimos hacer una búsqueda generalizada de enlaces, sobre todo descubrir los enlaces internos y eliminar la ruta absoluta de los mismos. Para ello hemos acudido a nuestro buscador interno (sección "Extras"), seleccionamos todas las tablas y en el campo de búsqueda insertando el nombre de nuestro dominio, obteniendo el listado de páginas que en su contenido se encuentra: pulse para ver ese resultado.

Revisando las páginas obtenidas en nuestra búsqueda, descubrimos que son casos con recortes de imágenes, y nuestro sistema necesita esa ruta completa para su funcionamiento, así que decidimos realizar las búsquedas algo más complejas, añadiendo al principio de la cadena a buscar los atributos donde va el nombre del dominio (href=", src="); chequeando también las mismas cadenas pero con comillas simples.

Las imágenes

La referencia de imágenes en el sitemap puede hacerse simplemente con su URL (ubicación), o adjuntando más información como un título o una descripción: Sitemaps de imágenes.

Revisando nuestro Listado de imágenes se nos ha ocurrido que podríamos aportar un título (nombre -> image:title) y una descripción (Resúmen -> image:caption). Para obtener esos datos desde la tabla de imágenes implementamos este código:

// Declaramos el array images para guardar información de nuestra tabla.
$images = array();

$sql = "select id, titulo, descripcion from $tabla_imagenes";
$res = @mysql_query($sql);
while ($dato = mysql_fetch_array($res))    {
	$id = $dato["id"];
	$titulo = $dato["titulo"];
	preg_match('/\[rss\](.*?)\[\/rss\]/is', $dato["descripcion"], $des);
	$info = preg_replace(array('/\<(.*?)\>/','/\"/'), '', $des[1]);
	$images[$id] = array("titulo" => $titulo, "info" => $info);
}

Ahora debemos buscar en los contenidos de las páginas que referenciamos en nuestro sitemap las imágenes y en el caso de que sean de nuestra tabla de imágenes, rescatar esa información adicional que hemos reseñado. Al tratarse de un mismo código para todos los textos, hemos implementado una función:

function sacaImagenes($texto)	{
	global $images, $diario_ruta;
	preg_match_all('/\<img src=\"(.*?)\"/is', $texto, $_imagen);
	$imgs = array();
	for ($i = 0, $total=count($_imagen[1]); $i < $total; $i++)
	if (substr($_imagen[1][$i], 0, 7) != "http://")	{
		$_src = $_imagen[1][$i];
		$_ids = substr($_src, 0, 17);
		$_id = false;
		if ($_ids == "diario.imagen.php")	{
			preg_match("/id=(\d*)/is", $_src, $__id);
			if (array_key_exists($__id[1], $images))	$_id = $__id[1];
		};
		if ($_id != false)	{
			$_titulo = $images[$_id]["titulo"];
			$_info = $images[$_id]["info"];
			array_push($imgs, array("loc" => "$diario_ruta/$_src", "title" => $_titulo, "caption" => $_info));
		}
		else	{
			array_push($imgs, array("loc" => "$diario_ruta/$_src"));
		}
	}
	return $imgs;
}

Con esa función obtenemos un array con los datos de las imágenes encontradas y que pertenecen al dominio por tener una ruta relativa.

Ahora nos queda mostrar las imágenes:

//$item es el objeto con los datos de una entrada del sitemap
//$item["images"] es el array de imágenes asociado al "$item"
while (count($item["images"]) > 0)	{
	$imagenes = array_pop($item["images"]);
	$lista .= "\n\t<image:image>";
	foreach($imagenes as $itemImage => $itemsito)
		$lista .= "\n\t\t<image:$itemImage><![CDATA[".$itemsito."]]></image:$itemImage>";
	$lista .= "\n\t</image:image>";
}

La experiencia de adaptar nuestro sitemap para referenciar nuestras imágenes nos ha servido para plantearnos futuros retos que en breve iremos comentando.

Prioridades

A pesar de que el valor "priority" de las entradas del sitemap no es relevante, pensamos que si podemos ofrecer mayor información de nuestro sitio será mejor valorado por los buscadores, así que nos atrevimos a implementar un sistema que genere un valor coherente para ese atributo.

Teniendo como base que los valores admitidos están entre el cero (0) y el uno (1), creemos oportuno darle valor "1.0" a la página principal. Las demás páginas tendrán como mínimo el valor "0.5", y dependerá de las visitas que hay registradas; por último dependiendo del tipo de página le daremos una prioridad mayor o menor. Con el código que mostramos a continuación apreciaremos esa discriminación:

$datosPrioridades = array(
	"apunte" => array("tope" => 4, "min" => false, "max" => false),
	"archivos" => array("tope" => 2, "min" => false, "max" => false),
	"imagen" => array("tope" => 4, "min" => false, "max" => false),
	"categoria" => array("tope" => 3, "min" => false, "max" => false),
	"etiqueta" => array("tope" => 3, "min" => false, "max" => false),
	"extra" => array("tope" => 3, "min" => false, "max" => false),
	"pagina" => array("tope" => 4.5, "min" => false, "max" => false)
);

Los tipos de páginas que tenemos en el sitemap son como puede apreciarse: apuntes, archivos, imágenes, categorías, etiquetas, extras y páginas. El valor que etiquetamos como tope es el máximo que sumaremos al "0.5" mínimo que ya hemos comentado, y ese tope coincidirá con el número máximo de visitas correspondientes a cada tipo (dividido por diez)... ¡Sí, sí!, todo un galimatías, por eso nos fiaremos que se entienda con el código:

function prioridad($tipo, $cual)	{
	global $datosPrioridades, $contadores;
	$yo = $contadores["$tipo.$cual"];
	$tope = $datosPrioridades[$tipo]["tope"];
	$min = $datosPrioridades[$tipo]["min"];
	$max = $datosPrioridades[$tipo]["max"];
	return number_format(0.5 + ($tope * (($yo * $min) / ($max * $min)) / 10), 2);
}

Este código nos devuelve un número entre "0.5" y un valor máximo igual al tope dividido 10... o sea entre 0.5 y 0.9 en los apuntes; entre 0.5 y 0.95 en el tipo "página", etc.

Nos falta explicar de donde sale esa variable "$yo" de nuestro código, que evidentemente es un valor numérico obtenido desde un array, en concreto el número de veces que fue visitada la página:

$contadores = array();
$sql = "select pagina, cuenta from $tabla_contadores order by pagina";
$res = @mysql_query($sql);
while ($dato = mysql_fetch_array($res))	{
	$pag = $dato["pagina"];
	$cuenta = $dato["cuenta"];
	$contadores[$pag] = $cuenta;
	list($tipo) = explode(".", $pag);
	if (!$datosPrioridades[$tipo]["min"])	{
		$datosPrioridades[$tipo]["min"] = $cuenta;
	}
	elseif ($cuenta < $datosPrioridades[$tipo]["min"])	{
		$datosPrioridades[$tipo]["min"] = $cuenta;
	}
	if (!$datosPrioridades[$tipo]["max"])	{
		$datosPrioridades[$tipo]["max"] = $cuenta;
	}
	elseif ($cuenta > $datosPrioridades[$tipo]["max"])	{
		$datosPrioridades[$tipo]["max"] = $cuenta;
	}
}

El código también nos muestra como obtenemos el valor máximo y mínimo de cada tipo de página.

Generando nuestro sitemap.xml

Empezaremos con la cabecera XML:

// La cabecera header se ha usado durante las pruebas...
//header("Content-type: text/xml");
$cabeza =<<< cabecera
<?xml version="1.0" encoding="ISO-8859-1" ?>
<?xml-stylesheet type="text/xsl" href="sitemap.xsl" ?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
	xmlns:image="http://www.google.com/schemas/sitemap-image/1.1">

cabecera;
$ahora = date("Y-m-d"); 

$lista = "";
$listados = array();

$listados["index"] = array("link" => $diario, "fecha" => $ahora, "images" => array(array("loc" => "$diario_ruta/caricatos.ico", "title" => "icono de $diario_titulo", "caption" => "Icono de mi caricatura")), "pri" => "1.0");

Vemos que a la vez que generamos la cabecera, también abrimos la etiqueta "urlset", dejando el contenido en la variable "$cabeza". También tenemos la variable "$ahora" con la fecha actual en formato válido para sitemaps. Un array llamado "$listados" con un elemento "index" que será la primera url del sitemap, y que representará la raíz del mismo. Antes de detenernos en el sentido de la variable "$lista" veremos el tratamiento de un tipo de página: "los apuntes".

$sql = "select id, titulo, apunte, fecha from $tabla_apuntes where activo > 0 order by fecha desc"; 
$res = @mysql_query($sql); 

while ($dato = mysql_fetch_array($res))    {
	$id = $dato["id"];
	$titulo = $dato["titulo"];
	$link = $diario."?titulo=".urlencode($titulo);
	$fecha = substr($dato["fecha"], 0, 10);
	$imgs = sacaImagenes($dato["apunte"]);
	$listados["apunte.$id"] = array("link" => $link, "fecha" => $fecha, "images" => $imgs, "pri" => prioridad("apunte", $id));
	list($aaaa, $mm) = explode("-", $fecha);
	$cadena_archivos = $mes[$mm]." $aaaa";
	if (!isset($archivos[$cadena_archivos]))	$archivos[$cadena_archivos] = array();
}

Omitiremos explicar las tres últimas líneas del código ya que solo sirven para obtener los datos de las secciones "archivos", pero puede notarse que se van generando elementos del array "$listados" con los datos que usaremos en cada etiqueta "url" de nuestro sitemap.

El siguiente código es concretamente el que genera todas las "url"s:

foreach ($listados as $dato => $item)	{
	$lista .= "<url>\n\t";
	$lista .= "<loc>".$item["link"]."</loc>";
	if (isset($item["fecha"]))
		$lista .= "\n\t<lastmod>".$item["fecha"]."</lastmod>";
	$lista .= "\n\t<changefreq>never</changefreq>";
	$lista .= "\n\t<priority>".$item["pri"]."</priority>";
	while (count($item["images"]) > 0)	{
		$imagenes = array_pop($item["images"]);
		$lista .= "\n\t<image:image>";
		foreach($imagenes as $itemImagen => $imagen)
			$lista .= "\n\t\t<image:$itemImagen>$imagen</image:$itemImagen>";
		$lista .= "\n\t</image:image>";
	}
	$lista .= "\n</url>\n";
}

Para terminar cerramos esa etiqueta "urlset" y volcamos el sitemap:

$sitemap = "$cabeza\n$lista\n</urlset>\n";
file_put_contents("sitemap.xml", $sitemap);

Nos falta...

La prueba de fuego

Es hora de generar el sitemap y probarlo en las herramientas para webmaster de google...

He de reconocer que el primer intento fue un fracaso por los parámetros que llevaban algunas imágenes, no obstante se ha solucionado sencillamente ya que solo debía cambiar los elementos "&" por su entidad "&amp;". Con el siguiente código:

$_src = preg_replace('/(\&amp;|\&)/', '&amp;', $_imagen[1][$i]);
//$_src = str_replace("&amp;amp;", "&amp;", str_replace("&", "&amp;", $_imagen[1][$i]));

Tenemos más para contar, pero lo dejaremos para más adelante...






Titulo: XML para guardar tablas


emoticón de Caricatos Publicado el día 04 de noviembre de 2012
id=100

webmasterEn ocasiones tenemos que realizar operaciones que implican cierto riesgo de alteración de datos y se nos ocurre que podría ser buena idea volcarlos a un fichero que pueda servirnos para recuperarlos si algo falla. También podríamos utilizar esos mismos datos para transferir esa información a otro soporte (algún disco local, por ejemplo); entonces el formato que se nos antoja adecuado es una estructura "XML".

Nuestro caso: Los apuntes

Para nuestro caso concreto, vamos a volcar la tabla de apuntes, pero solo nos interesan los título con sus respectivos contenidos, descartando la fecha y demás campos que para nuestro propósito son innecesarios, aunque viendo el código puede apreciarse con que facilidad podríamos insertar más campos.

Para la creación del nuevo documento usaremos código PHP:

$cabeza =<<< cabecera
<?xml version="1.0" encoding="ISO-8859-1" ?>
<?xml-stylesheet type="text/xsl" href="diario.jocker.php?extra=apuntes_xsl" ?>
<apuntes>

cabecera;

$lista = "";

$sql = "select id, titulo, apunte from $tabla_apuntes order by fecha desc"; 
$res = @mysql_query($sql); 

while ($dato = mysql_fetch_array($res))    {
	$id = $dato["id"];
	$titulo = $dato["titulo"];
	$apunte = $dato["apunte"];
	$lista .= "<apunte>\n\t";
	$lista .= "<id>$id</id>";
	$lista .= "<titulo><![CDATA[$titulo]]></titulo>";
	$lista .= "<texto><![CDATA[$apunte]]></texto>";
	$lista .= "\n</apunte>\n";
}

$apuntes = "$cabeza\n$lista\n</apuntes>\n";
file_put_contents("apuntes.xml", $apuntes);

Para poder comprobar que el resultado es correcto, hemos asociado una hoja de estilos XSL y ya podemos enlazarlo (posiblemente no actualizaremos este fichero constantemente).

Ajax al rescate

Ya hemos hablado de la "x" de Ajax: Ajax, con X de XML, y en este caso no es necesario leer un documento creado dinámicamente. Nuestra página leerá al iniciarse el fichero mencionado, volcándolo en una capa que hemos identificado como "listado" (id="listado").

window.onload = function() {
	url = "apuntes.xml";
	Ajax = objetoAjax();
	Ajax.open("get", url, true);
	Ajax.onreadystatechange = function()	{
		if	(Ajax.readyState == 4 && Ajax.status == 200) {
			nueva_capa = document.createElement("ul");
			respuesta = Ajax.responseXML.documentElement;
			apuntes = respuesta.getElementsByTagName("apunte");
			for (i = 0, total = apuntes.length; i < total; i++)	{
				titulo = apuntes[i].getElementsByTagName("titulo")[0].firstChild.nodeValue;
				apunte = apuntes[i].getElementsByTagName("texto")[0].firstChild.nodeValue;
				id = apuntes[i].getElementsByTagName("id")[0].firstChild.nodeValue;
				capas[id] = {"estado": false, "apunte": apunte};
				f = "<div><input type='checkbox' id='c_" + id + "' name='ap' value='ap_" + id + "'/> ";
				f += "<span style='cursor: pointer' onclick='plegar(" + id + ")'>" + titulo + "</span>";
				f += "<form action='apuntar.php' method='post' target='ventana'";
				f += " id='f_" + id + "'>";
				f += "<input type='hidden' name='id' value='" + id + "'/>";
				f += "<input type='hidden' name='titulo' value='" + titulo + "'/>";
				f += "<fieldset><legend> " + titulo + " </legend>";
				f += "<textarea name='apunte' style='width:100%; height: 200px'></textarea>";
				f += "<button type='submit' style='text-align: center; width: 100%;'> actualizar </button>";
				f += "</fieldset>";
				f += "<div id='v_" + id + "' class='prever'>";
				f += "<button type='button' onclick='prever(" + id + ")'>prever";
				f += "</button></div>";
				f += "</form>";
				tag("listado").innerHTML += f;
			};
			tag("cargando").src = "si.gif";//	cargando => cargado
		}
	}
	Ajax.send();
}

Si analizamos este código podemos encontrar cosas de difícil comprensión, que trataremos de explicar a continuación.

Volcado incompleto y objeto "capas"

Si bien generamos un elemento "textarea" con el nombre "apunte" (name="apunte"), y una capa "div" con la clase "prever" (class="prever"), podemos ver que no lleva contenido alguno en el primero de los casos, y que parece tratarse del valor asociado a la etiqueta apunte del fichero leído; y en el segundo caso, tan solo hay un botón con la leyenda "prever", y que tiene como parámetro el identificador "id" del apunte.

Las razones son: para el primero de los casos que realmente no se muestra el contenido ya que el formulario está oculto y que se despliega pulsando sobre su título. De todos modos en el objeto "capas" guardamos cada apunte asociado a su id, así que a la vez que desplegamos el apunte asignamos ese valor al elemento textarea:

function plegar(f)	{
	if (document.forms["f_" + f].apunte.value == "")
		document.forms["f_" + f].apunte.value = capas[f].apunte;
	tag("f_" + f).style.display = (capas[f].estado) ? "none":"block";
	capas[f].estado = !capas[f].estado;
}

Si hubiéramos intentado incluir los contenidos al obtenerlos mediante Ajax, podríamos tener problemas por las etiquetas que tienen los apuntes, porque se podrían cerrar algunas existentes o añadidas como los "div" y "textarea". Y si incluyésemos los contenidos se deberían cargar muchos elementos que ralentizarían demasiado la carga de la página.

Así es la función prever:

function prever(f){
	tag("v_" + tag("id_" + f).value).innerHTML = document.forms["f_" + f].apunte.value;
}

Evidentemente los cambios solo serán posibles en el modo administrativo. Aquí el resultado (nótese que cada apunte puede considerarse un probador de código modificando el contenido y pulsando el botón "prever").



Zona de comentarios

Esto grupo aún no tiene comentarios.

Evaluación

Valoración de esta página: (grupo.xml) valor

Valoración evaluar evaluar evaluar evaluar evaluar evaluar evaluar evaluar evaluar evaluar

Respuesta: Zona de mensajes (proceso de evaluación)

Copyright © 2002-2018 www.pepemolina.com
RSS rss | Ver Mapa del sitio