Zona HTML Zona Java Zona PHP Zona ASP Zona Bases de datos
-Tutoriales

El API JAXP


Construir un JTree Amigable desde un DOM

Ahora que sabemos el aspecto que tiene un DOM internamente, estaremos mejor preparados para modificar o construir un DOM desde cero. Antes de ir a ese punto, esta sección presenta algunas modificaciones para el JTreeModel que nos permitan producir una versión más amigable del JTree para usarlo en un GUI.

. Comprimir la vista del árbol

Mostrar el DOM en forma de árbol está muy bien para experimentar y aprender como funciona un DOM. Pero no es la clase de imagen amigable en la que muchos usuarios quieren ver un JTree. Sin embargo, se necesitan muy pocas modificaciones en el adaptador TreeModel para convertirlo en algo más presentable. En esta sección haremos estos cambios.

Nota:

El código descrito en esta sección está en DomEcho03.java. El fichero sobre el que opera es slideSample01.xml.

. Hacer Seleccionable la Operación

Cuando modifiquemos el analizador, vamos a comprimir la vista del DOM, eliminando todo excepto los nodos que realmente queremos mostrar. Empezamos definiendo una variable booleana que controla si queremos o no comprimir la vista del DOM.

public class DomEcho extends JPanel
{
    static Document document; 

    boolean compress = true;

    static final int windowHeight = 460;
    ...

. Identificar los Nodos del Árbol

El siguiente paso es identificar los nodos que queremos mostrar en el árbol. Para hacer esto, vamos al área donde definimos los nombres de todos los tipos de elementos (en el array typeName), y añadimos el código en negrita de abajo:

public class DomEcho extends JPanel
{
    ...
    public static void makeFrame() {
        ...
    }

    // An array of names for DOM node-types
    static String[] typeName = {
        ...
    };
    final int ELEMENT_TYPE =   1;

    // The list of elements to display in the tree
    static String[] treeElementNames = {
        "slideshow",
        "slide",
        "title",         // For slideshow #1
        "slide-title",   // For slideshow #10
        "item",
    };
    boolean treeElement(String elementName) {
      for (int i=0; i<treeElementNames.length; i++) {
        if ( elementName.equals(treeElementNames[i]) ) return true;
      }
      return false;
    } 

Con este código, configuramos una constante que podemos usar para identificar el tipo de nodo ELEMENT, declarado los nombres de los elementos que queremos en el árbol, y creado un método que dice el nombre de un elemento dato es o no un "elemento árbol".

Nota:

El mecanismo que hemos creado aquí depende del echo de que las estructuras de nodos como slideshow y slide nunca contienen texto, mientras que el texto normalmente aparece en contenidos de nodos como item.

. Controlar la Visibilidad de los Nodos

El siguiente paso es modificar la función childCount del AdapterNode para que sólo cuente los nodos "tree element" -- nodos que se han designado como mostrables en el JTree. Hacemos las modificaciones en negrita de abajo para hacer esto:

public class DomEcho extends JPanel
{
    ...
    public class AdapterNode 
    { 
      ...
      public AdapterNode child(int searchIndex) {
        ...
      }

      public int childCount() {
        if (!compress) {
          // Indent this
          return domNode.getChildNodes().getLength();  
        } 
        int count = 0;
        for (int i=0; i<domNode.getChildNodes().getLength(); i++) {
           org.w3c.dom.Node node = domNode.getChildNodes().item(i); 
           if (node.getNodeType() == ELEMENT_TYPE
           && treeElement( node.getNodeName() )) 
           {
             ++count;
           }
        }
        return count;
      }
    } // AdapterNode

La única parte delicada de este código es aseguranos de que el nodo es un nodo elemento antes de compararlo. El nodo DocType es necesario porque tiene el mismo nombre "slideshow", que el elemento slideshow.

. Controlar el Acceso a los Hijos

Finalmente, necesitamos modificar la función child del AdapterNode para devolver el número de ítems en la lista de nodos mostrables, en vez del número de ítems en la lista de todos los nodos. Añadimos el código en negrita de abajo para hacer esto:

 
public class DomEcho extends JPanel
{
    ...
    public class AdapterNode 
    { 
      ...
      public int index(AdapterNode child) {
        ...
      }

      public AdapterNode child(int searchIndex) {
        //Note: JTree index is zero-based.
        org.w3c.dom.Node node = domNode.getChildNodes().item(searchIndex);
        if (compress) {
          // Return Nth displayable node
          int elementNodeIndex = 0;
          for (int i=0; i<domNode.getChildNodes().getLength(); i++) {
            node = domNode.getChildNodes().item(i);
            if (node.getNodeType() == ELEMENT_TYPE 
            && treeElement( node.getNodeName() )
            && elementNodeIndex++ == searchIndex) {
               break; 
            }
          }
        }
        return new AdapterNode(node); 
      } // child

  }  // AdapterNode

No hay nada especial aquí. Es un versión ligeramente modificada de la misma lógica que usamos para devolver el contador de hijos.

. Comprobar los Resultados

Cuando compilemos y ejecutemos esta aplicación sobre slideSample01.xml, y expandamos los nodos del árbol, veremos el resultado de la figura 1. Los únicos nodos que quedan en el árbol son los nodos de la estructura de alto nivel.

Nota:

Todas las imágenes de esta pagina se han reducido para que quepan en ella, si las quieres ver a su tamaño natural, pulsa sobre ellas.

. Crédito Extra

En la forma en que la aplicación está ahora, la información que le dice a la aplicación cómo comprimir el árbol para mostrarlo está codificada en duro. Aquí tenemos algunas formas en las que podríamos considerar extender la aplicación.

Usar un Argumento de la Línea de Comandos
Si comprimimos o no el árbol podría estar determinado por un argumento de la línea de ocmandos, en vez de una variable booleanda codificada.

Leer las lista treeElement desde un fichero
Si leemos la lista de elementos a incluir en el árbol desde un fichero externo, podríamos construir una aplicación totalmente gobernada por comandos.

. Actuar sobre las Selecciones del Árbol

Ahora que el árbol se muestra de la forma apropiada, el siguiente paso es concatenar subárboles bajo nodos seleccionados para mostrarlos en el htmlPane. Cuando lo tengamos, usaremos el texto concatenado para poner de vuelta la información de indentificación del nodo en el JTree.

Nota:

El código de está sección es DomEcho04.java.

. Identificar los Tipos de Nodos

Cuando concatenamos los sub-nodos bajo un elemento, el procesamiento que estamos haciendo depende del tipo del nodo. Por eso lo primero es definir las constantes para el resto de los tipos de nodos. Añadimos el código en negrita de abajo para hacer esto.

public class DomEcho extends JPanel
{
    ...
    // An array of names for DOM node-types
    static String[] typeName = {
        ...
    };
    static final int ELEMENT_TYPE =   1;
    static final int ATTR_TYPE =      2;
    static final int TEXT_TYPE =      3;
    static final int CDATA_TYPE =     4;
    static final int ENTITYREF_TYPE = 5;
    static final int ENTITY_TYPE =    6;
    static final int PROCINSTR_TYPE = 7;
    static final int COMMENT_TYPE =   8;
    static final int DOCUMENT_TYPE =  9;
    static final int DOCTYPE_TYPE =  10;
    static final int DOCFRAG_TYPE =  11;
    static final int NOTATION_TYPE = 12; 

. Concatenar Subnodos para Definir Contenidos de Elementos

Luego, necesitamos definir el método que concatena el texto y los subnodos para un elemento y devuelve el contenido de este elemento. Para definir el método content, necesitaremos añadir el código en negrita de abajo:

public class DomEcho extends JPanel
{
    ...
    public class AdapterNode 
    { 
      ...
      public String toString() {
        ...
      }

      public String content() {
        String s = "";
        org.w3c.dom.NodeList nodeList = domNode.getChildNodes();
        for (int i=0; i<nodeList.getLength(); i++) {
          org.w3c.dom.Node node = nodeList.item(i);
          int type = node.getNodeType();
          AdapterNode adpNode = new AdapterNode(node);
          if (type == ELEMENT_TYPE) {   
            if ( treeElement(node.getNodeName()) ) continue;
            s += "<" + node.getNodeName() + ">";
            s += adpNode.content();
            s += "</" + node.getNodeName() + ">";
          } else if (type == TEXT_TYPE) {
            s += node.getNodeValue();
          } else if (type == ENTITYREF_TYPE) {
            // The content is in the TEXT node under it
            s += adpNode.content();
          } else if (type == CDATA_TYPE) {          
            StringBuffer sb = new StringBuffer( node.getNodeValue() );
            for (int j=0; j<sb.length(); j++) {
              if (sb.charAt(j) == '<') {
                sb.setCharAt(j, '&');
                sb.insert(j+1, "lt;");
                j += 3;
              } else if (sb.charAt(j) == '&') {
                sb.setCharAt(j, '&');
                sb.insert(j+1, "amp;");
                j += 4;
              }
            }
            s += "<pre>" + sb + "\n</pre>";
          }
        }
        return s;
      }
      ...
    } // AdapterNode

Este no es el código más eficiente que se pueda escribir, pero funciona y nos va bién para nuestros propósitos. En este código, hemos reconocido y tratado con los siguientes tipos de datos.

Element
Para elementos con nombres como el nodo "em" de XHTML, devolvemos el contenido del nodo entre las etiquetas <em> y </em> apropiadas. Sin embargo, cuando procesamos el contenido de un elemento slideshow, por ejemplo, no incluimos las etiquetas para los elementos slide que contiene, cuando devolvemos el contenido de un nodo, nos saltamos cualquier elemento que sea propiamente mostrado en el árbol.

Text
No hay sorpresa. Para un nodo texto, sólo devolvemos el valor del nodo.

Entity Reference
Al contrario que los nodos CDATA, las entidades de referencia pueden contener múltiples subelementos. Por eso la estrategia es devolver la concatenación de dichos subelementos.

CDATA
Al igual que en un nodo texto, devolvemos el valor del nodo. Sin embargo, como el texto en este caso podría contener ángulos y ampersands, necesitamos convertirlos a una forma en que se muestren de la forma adecuada en el panel HTML.

Por otro lado, hay unos pocos tipos de nodos que no hemos precesado en el código anterior. Merece la pena gastar un momento para examinarlos y entender porqué.

Attribute
Estos nodos no aparecen en el DOM, pero se pueden obtener llamando a getAttributes sobre los nodos elementos

Entity
Estos nodos tampoco aparecen en el DOM. Se obtienen llamando a getEntities sobre nodos DocType

Processing Instruction
Estos nodos no contienen datos mostrables.

Comment
Nada que queremos mostrar.

Document
Este es el nodo raíz del DOM. No hay datos que mostrar de él.

DocType
El nodo DocType contiene la especificación DTD, con o sin punteros externos. Sólo aparece bajo nodo raíz, no tiene nada que mostrar en el árbol.

Document Fragment
Este nodo es equivalente a un nodo documento. Es un nodo raíz que la especificación DOM entiende para contener resultados intermedios durante operaciones de cortar/pegar, por ejemplo.

Notation
Podemos ignorarlos. Estos nodos se usan para incluir datos binarios en el DOM.

. Mostrar el Contenido en el JTree

Con la concatenación de contenidos creada, sólo nos quedan unos pequeños pasos de programación. El primero es modificar toString para que use el contenido del nodo como información identificactiva. Añadimos el código en negrita de abajo para hacer esto:

public class DomEcho extends JPanel
{
    ...
    public class AdapterNode 
    { 
      ...
      public String toString() {
        ...
        if (! nodeName.startsWith("#")) {
           s += ": " + nodeName;
        }
        if (compress) {
           String t = content().trim();
           int x = t.indexOf("\n");
           if (x >= 0) t = t.substring(0, x);
           s += " " + t;
           return s;
        }
        if (domNode.getNodeValue() != null) {
           ...
        }
        return s;
      } 

. Conectar el JTree con el JEditorPane

Volviendo al constructor de la aplicación, creamos el oyente de treeSelection y los usamos para conectar el JTree con el JEditorPane:

public class DomEcho extends JPanel
{
    ...
    public DomEcho()
    {
       ...

       // Build right-side view
       JEditorPane htmlPane = new JEditorPane("text/html","");
       htmlPane.setEditable(false);
       JScrollPane htmlView = new JScrollPane(htmlPane);
       htmlView.setPreferredSize( 
           new Dimension( rightWidth, windowHeight ));

       tree.addTreeSelectionListener(
         new TreeSelectionListener() {
           public void valueChanged(TreeSelectionEvent e) {
             TreePath p = e.getNewLeadSelectionPath();
             if (p != null) {
               AdapterNode adpNode = 
                  (AdapterNode) p.getLastPathComponent();
               htmlPane.setText(adpNode.content());
             }
           }
         }
       ); 

Ahora, cuando se selecciona un nodo en el JTree, su contenido es enviado al htmlPane.

Nota:

El TreeSelectionListener de este ejemplo se crea usando una adaptador de clase interna anónimo. Si estámos programando en la versión 1.1 de la plataforma Java, necesitaremos definir una clase externa para este propósito.

Si compilamos esta versión de la aplicación, descubriremos inmediatament que el htmlPane necesita ser especificado como final para ser referenciado en la clase interna, por eso añadimos la palabra clave en negrita de abajo:

public DomEcho04()
{
   ...

   // Build right-side view
   final JEditorPane htmlPane = new JEditorPane("text/html","");
   htmlPane.setEditable(false);
   JScrollPane htmlView = new JScrollPane(htmlPane);
   htmlView.setPreferredSize( 
       new Dimension( rightWidth, windowHeight ));

. Ejecutar la Aplicación

Cuando compilamos la aplicación y la ejecutamos sobre slideSample10.xml, obtenemos algo parecido a la figura 2. Si expandimos el árbol el JTree incluye ahora textos identificativos para todos los nodos que sea posible.

Seleccionar un ítem que incluye subelementos XHTML produce una salida como la de la figura 3.

Seleccionar un nodo que contiene una referencia de entidad hace que el texto de la entidad sea incluido, como se ve en la figura 4.

Finalmente, seleccionado un nodo que incluye una sección CDATA produce como resultado la figura 5.

. Finalizando

Ahora entendemos mucho mejor todo lo que hay sobre la estructura de un DOM, y sabemos como adaptar un DOM para crear una pantalla amigable en un JTree. Nos ha costado un poco de codificación, pero en recompensa hemos obtenido una herramienta valiosa para exponer la estructura de un DOM y una plantilla para aplicaciones GUI. En la siguiente página, haremos un par de pequeñas modificaciones al código para convertir la aplicación en un vehículo de experimentación, y luego experimentar constuyendo y manipulando un DOM.

 
Patrocinados
 

Copyright © 1999-2010 Programación en castellano. Todos los derechos reservados.
Formulario de Contacto - Datos legales - Publicidad

diseño y desarrollo web por Color Vivo Internet. Un proyecto de los Hermanos Carrero