Visualizacion de codigo HTML como un grafo

1
18619

Visualización de código
HTML como un
grafo

En Autentia nos gusta
compartir los conocimientos con
vosotros.. ¡y con nosotros mismos! Cuando se hace
I+D
solemos comentar y publicar los resultados; nos lo pasamos bien. 

Hace unos días encontré un applet que
permite visualizar la
estructura de componentes HTML de una página web en manera
de
grafo. Los resultados son
curiosos; acerquémonos a: Websites
as Graphs.

Introducción a Websites as Graphs

Frente a una visualización estructurada en forma de
árbol de
etiquetas HTML, con el applet disponible en www.aharef.info/static/htmlgraph/
podemos, de un vistazo, ver la complejidad de componentes y anidaciones
jerárquicas de los mismos. Simplemente hay que introducir la
URL hasta la página deseada y comenzará una
animación que irá formando el grafo
acíclico. Veamos un
ejemplo de cada uno del
index.php
nuestra web de adictos:


Árbol
de componentes HTML (usando firebug)


Grafo de
componentes HTML (usando Websites
as Graphs)

Ambas perspectivas muestran la misma información
estructural aunque tienen
propósitos distintos. La vista en árbol permite
profundizar en niveles y
obtener el detalle de todos los elementos del código:
etiquetas y atributos HTML, URIs, URLs, scripts, etc. El grafo
sólo muestra información estática y no
etiquetada de un número limitado de etiquetas
estándar HTML, que son:

  • blue:
    for links (the A tag)
  • red:
    for tables (TABLE, TR and TD tags)
  • green:
    for the DIV tag
  • violet:
    for images (the IMG tag)
  • yellow:
    for forms (FORM, INPUT, TEXTAREA, SELECT and OPTION tags)
  • orange:
    for linebreaks and blockquotes (BR, P, and BLOCKQUOTE tags)
  • black:
    the HTML tag, the root node
  • gray:
    all other tags

Conforme a esta leyenda el grafo queda
desglosado de
la siguiente manera:

Algoritmo
de generación del grafo

El autor de la web pone a disposición el
código fuente del
applet, que copio a
continuación. Vamos a manejarlo:

////////////////////////////////////////////////////////////
//
// A word of caution: This code can be optimized - I made it in quite a hurry. 
// A large chunk is from an example from Traer Physics.
//
// Feel free to use / modify this code however you wish. 
// I would be happy if you would make a reference to the www.aharef.info site!
//
// Oh, and yes, don't forget to check out my alter ego art project, www.onethousandpaintings.com
//
////////////////////////////////////////////////////////////

import traer.physics.*;
import traer.animation.*;
import java.util.Iterator;
import java.util.ArrayList;
import java.util.HashMap;
import processing.net.*; 
import org.htmlparser.*;
import org.htmlparser.util.*;
import org.htmlparser.filters.*;
import org.htmlparser.nodes.*;

int totalNumberOfNodes = 0;
ArrayList elements = new ArrayList();
ArrayList parents = new ArrayList();
int nodesAdded = 0; 
int maxDegree = 0;
HashMap particleNodeLookup = new HashMap();
HashMap particleInfoLookup = new HashMap();
ParticleSystem physics;
Smoother3D centroid;

// MAKE SURE YOU CHANGE THIS! I might change this site in the future.
// Simply point to a site on your own server that gets the html from any other site.
private String urlPath = "http://www.aharef.info/static/htmlgraph/getDataFromURL.php?URL=";
private String content;

float NODE_SIZE = 30;
float EDGE_LENGTH = 50;
float EDGE_STRENGTH = 0.2;
float SPACER_STRENGTH = 2000;
  
final String GRAY = "155,155,155";
final String BLUE = "0,0,155";
final String ORANGE = "255,155,51";
final String YELLOW = "255,255,51";
final String RED = "255,0,0";
final String GREEN = "0,155,0";
final String VIOLET = "204,0,255";
final String BLACK = "0,0,0";



void setup() {
  size(750, 750);
  // if you want to run it locally from within processing, comment the two following lines, and define urlPath as http://www.whateveryoursite.com
  String urlFromForm = param("urlFromForm"); 
  urlPath += urlFromForm;
  smooth();
  framerate(24);
  strokeWeight(2);
  ellipseMode(CENTER);
  physics = new ParticleSystem( 0, 0.25 );
  centroid = new Smoother3D( 0.0, 0.0, 1.0,0.8 );
  initialize();
  this.getDataFromClient();
}

void getDataFromClient() {
  try {
    org.htmlparser.Parser ps = new org.htmlparser.Parser ();
    ps.setURL(urlPath);
    OrFilter orf = new OrFilter();
    NodeFilter[] nfls = new NodeFilter[1];
    nfls[0] = new TagNameFilter("html");
    orf.setPredicates(nfls);
    NodeList nList  = ps.parse(orf);
    Node node = nList.elementAt (0);
    this.parseTree(node);
    EDGE_STRENGTH = (1.0 / maxDegree) * 5;
    if (EDGE_STRENGTH > 0.2) EDGE_STRENGTH = 0.2;
  }
  catch (Exception e) {
     e.printStackTrace();
  }
}

void initialize() {
  physics.clear();
}

void parseTree(Node node) {
  int degree = 0;
  if (node == null) return;
  String nodeText = node.getText();
  if (node instanceof TagNode && !((TagNode)node).isEndTag())  {   
      //println(((TagNode)node).getTagName());
      totalNumberOfNodes++;
      elements.add(node);
      parents.add(((TagNode)node).getParent());
   }
  NodeList children = node.getChildren();
  if (children == null) return;
  SimpleNodeIterator iter = children.elements();
  while(iter.hasMoreNodes()) {
    Node child = iter.nextNode();
    if (child instanceof TagNode && !((TagNode)child).isEndTag()) degree++;
    parseTree(child);
  }
  if (degree > maxDegree) maxDegree = degree;
}


Particle addNode(Particle q) {
  Particle p = physics.makeParticle();
  if (q == null) return p;
  addSpacersToNode( p, q );
  makeEdgeBetween( p, q );
  float randomX =  (float)((Math.random() * 0.5) + 0.5);
  if (Math.random()  1) {
    updateCentroid();
  }
  centroid.tick();
  background(255);
  translate(width/2, height/2);
  scale(centroid.z());
  translate( -centroid.x(), -centroid.y() );
  drawNetwork();
  //uncomment this if you want your network saved as pdfs
  //endRecord();
}

void addNodesToGraph() {
  Particle newParticle;
  TagNode tagNodeToAdd = (TagNode)elements.get(nodesAdded);
  Node parentNode = (Node)parents.get(nodesAdded);
  if (parentNode == null) {
    // this is the html element
    newParticle = addNode(null);
  }
  else {
    Particle parentParticle = (Particle)particleNodeLookup.get(parentNode);
    newParticle = addNode(parentParticle);
  }
  particleNodeLookup.put(tagNodeToAdd,newParticle);
  String nodeColor = GRAY;
  if (tagNodeToAdd.getTagName().equalsIgnoreCase("a")) nodeColor = BLUE;
  else if (tagNodeToAdd.getTagName().equalsIgnoreCase("div")) nodeColor = GREEN;
  else if (tagNodeToAdd.getTagName().equalsIgnoreCase("html")) nodeColor = BLACK;
  else if (tagNodeToAdd.getTagName().equalsIgnoreCase("tr")) nodeColor = RED;
  else if (tagNodeToAdd.getTagName().equalsIgnoreCase("td")) nodeColor = RED;
  else if (tagNodeToAdd.getTagName().equalsIgnoreCase("table")) nodeColor =  RED;
  else if (tagNodeToAdd.getTagName().equalsIgnoreCase("br")) nodeColor =  ORANGE;
  else if (tagNodeToAdd.getTagName().equalsIgnoreCase("p")) nodeColor =  ORANGE;
  else if (tagNodeToAdd.getTagName().equalsIgnoreCase("blockquote")) nodeColor =  ORANGE;
  else if (tagNodeToAdd.getTagName().equalsIgnoreCase("img")) nodeColor =  VIOLET;
  else if (tagNodeToAdd.getTagName().equalsIgnoreCase("form")) nodeColor =  YELLOW;
  else if (tagNodeToAdd.getTagName().equalsIgnoreCase("input")) nodeColor =  YELLOW;
  else if (tagNodeToAdd.getTagName().equalsIgnoreCase("textarea")) nodeColor =  YELLOW;
  else if (tagNodeToAdd.getTagName().equalsIgnoreCase("select")) nodeColor =  YELLOW;
  else if (tagNodeToAdd.getTagName().equalsIgnoreCase("option")) nodeColor =  YELLOW;
  particleInfoLookup.put(newParticle,nodeColor);
  nodesAdded++;  
  //println(nodesAdded + " of " + totalNumberOfNodes + " (" + tagNodeToAdd.getTagName() + ")");
}

void drawNetwork() {
  // draw edges
  stroke( 0 );
  beginShape( LINES );
  for ( int i = 0; i  deltaX ) {
    centroid.setTarget(xMin + 0.5*deltaX, yMin + 0.5*deltaY, height/(deltaY+50));
  }
  else {
    centroid.setTarget(xMin + 0.5*deltaX, yMin + 0.5*deltaY, width/(deltaX+50));
  }
}

void addSpacersToNode( Particle p, Particle r ) {
  for ( int i = 0; i 

A primera vista parece código java puro, pero no lo es (no hay clase). Se trata de un lenguaje opensource basado en java llamado Processing con propósitos de simplificar la programación de imágenes, animaciones o interacciones. Basándose en ello existen librerías que simulan partículas flotantes, choches elásticos, efectos gravitatorios, fluidos, gases... muy completo e interesante para simulaciones físicas por ejemplo. Como queremos modificar y ejecutar el código en nuestro pc, vamos a preparar el sistema. Descargamos:

Descomprimimos
la descarga de Processing. Copiamos las librerías en la
carpeta
libraries (así se incluyen en el classpath
automáticamente), de manera que queden así:

  • processing/libraries/physics/library/physics.jar
  • processing/libraries/animation/library/animation.jar
  • processing/libraries/htmlparser/library/
    (jars incluidos en htmlparser)

Iniciamos
Processing, copiamos el código en un nuevo editor y
cambiamos
las líneas 58 y 59 como nos indican, por lo siguiente:

//String urlFromForm = param("urlFromForm");
//urlPath += urlFromForm;
urlPath="http://www.adictosaltrabajo.com";

A
partir de la release 0116 el método framerate() se cambia
por
frameRate(). Por lo tanto la línea 61 la cambiaremos por

frameRate(24);

Ejecutamos
(Sketch | run) y se abrirá la ventana donde se
renderizará el grafo con una animación muy
divertida
(blowing popcorn).

El
grafo puede guardarse en PDF. Simplemente descomentamos las
líneas 131 y 149 y generará en disco archivos PDF
con
capturas periódicas (en función del frameRate
indicado)
con el patrón frameimage-####.pdf:
frameimage-0000.pdf,
frameimage-0001.pdf,
etc.

El autor del script, Marcel
Salathé, también comenta en la web
que puede exportarse a una imagen, y que si hacemos una
donación
nos envía un pdf y una imagen de 1500 x 1500
píxeles.
Aquí vamos a generar algo similar, una imagen de un
tamaño suficientemente grande para imprimir un
póster.
Dudo que Marcel viva de las donaciones por PayPal, pero para no hacerle
una faena solo mostraremos cómo guardar en un archivo de
imagen
y no a redimensionarla (es sencillo).

Leemos en la API de Processing
cómo usar el método save().
Insertamos la sentencia save(«c:/grafoWebAdictos.jpg»); al
final del método draw():

void draw() {
  //uncomment this if you want your network saved as pdfs
  //beginRecord(PDF, "frameimage-####.pdf");
  if (nodesAdded  1) {
    updateCentroid();
  }
  centroid.tick();
  background(255);
  translate(width/2, height/2);
  scale(centroid.z());
  translate( -centroid.x(), -centroid.y() );
  drawNetwork();
  save("c:/grafoWebAdictos.jpg");
  //uncomment this if you want your network saved as pdfs
  endRecord();
}

y
ejecutamos
nuevamente. El fichero se está reescribiendo con los frames
que
vemos en la pantalla. Cuando queramos ver el resultado, primero cerramos la
animación y luego
acudimos al fichero que estará en la URI indicada.

Finalmente
damos el toque mágico del cambio de tamaño de la
imagen,
dejamos el ordenador procesando en el rato de café, y he aqui
la imagen, y el póster en la oficina 🙂

Conclusión

En
un blog puede leerse: This
is simply the most beautiful translation of HTML into another medium,
ever
.
Cierto es que la información que presenta
es limitada y que no permite extraer muchas más conclusiones
más allá de la complejidad o la accesibilidad; no
sabemos si la página será usable, será
conforme con los estándares W3C, es XHTML o HTML. Pero la
iniciativa es original y merece un tutorial. Sobre todo para dar las
gracias a M. Salathé por compartir su original trabajo 🙂

PD:  Proyectos relacionados:


‘ADN’ de
componentes HTML (usando WEB2DNAArt Project)


Estructura
del site
completo de autentia.com (usando Validation Graphs)

1 COMENTARIO

DEJA UNA RESPUESTA

Por favor ingrese su comentario!

He leído y acepto la política de privacidad

Por favor ingrese su nombre aquí

Información básica acerca de la protección de datos

  • Responsable:
  • Finalidad:
  • Legitimación:
  • Destinatarios:
  • Derechos:
  • Más información: Puedes ampliar información acerca de la protección de datos en el siguiente enlace:política de privacidad