Share |

martedì 1 giugno 2010

Java: Creare la mappa XPath di files XML

In questo articolo Java verrà mostrato come scorrere un intero documento XML, in modo ricorsivo, senza che lo si conosca a priori, e salvando l'intera mappatura interna dei sui path in modo tale da poterne conoscere la struttura ed avere già a disposizione le chiavi di accesso ai dati, utilizzabili per esempio come argomento di predicati_XPath.

Caso d'uso

Tempo fa, mi fu chiesto di creare una classe Java da utilizzare all'interno del DB Oracle 10g per ovviare alle scarse performances del DB nel trattare dati in formato XML. Questa classe avrebbe dovuto scorrere un documento XML contenuto in ogni record di una tabella e restituire un array di stringhe rappresentanti le chiavi di accesso ai dati pronte per essere usate da interpreti xPath per estrarre ogni foglia del documento XML.

Per foglie sono da intendere tutti quei nodi che sono terminatori di un ramo dell'albero gerarchico XML e che quindi non hanno alcun nodo figlio.

Quindi da un documento XML così fatto:

<pippo>
   <ciao>
      <tag/>
   </ciao>
   <dona>56</dona>
   <beppe>
      <mirko/>
   </beppe>
   <beppe>ii</beppe>
   <nano>1</nano>
</pippo>

La classe avrebbe dovuto restituire questi path XPath:

  • /pippo/ciao/tag
  • /pippo/dona
  • /pippo/beppe/mirko
  • /pippo/beppe
  • /pippo/nano

Come si può notare i predicati XPath restituiti estrarrebbero solo i nodi foglia.

Osservazioni

Analizzando il problema, si può affermare quindi che la classe Java, per ogni nodo che incontrerà scorrendo ricorsivamente il documento XML, dovrà:

  • verificare che il nodo in esame non abbia nodi figlio;
  • ricreare tutti i gradi di parentela dei nodi esaminati;
  • salvare i path creati in formato utile a xPath;

Poiché Oracle 10g utilizza le librerie del SDK1.4 di Java, per la rappresentazione del documento XML sono stati utilizzati packages standard forniti nel suddetto jdk.

E' ora di vedere il codice sorgente della classe e di analizzarlo.

La classe XmlReader

Come mio solito preferisco presentare il codice sorgente della classe commentanto abbondantemente nei punti più importanti:

import java.io.IOException;
import java.io.InputStream;
import java.io.StringBufferInputStream;
import java.util.ArrayList;
import java.util.List;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

/**
 * Scorre ricorsivamente un documento XML e 
 * salva le chiavi di accesso ad ogni nodo di testo
 * in formato utile ad interpreti XPath.
 *
 * @author  Agrati Mirko
 * @version 1.0.0
 */
public class XmlReader {
  /** Lista contenente gli xPath generati */
  public static List xPathArray ;

  /** 
   * Restituisce un Array di stringhe XPath 
   * 
   * @param  xml  XML in formato String da parsare;
   * @return      Un array di oggetti;
   */
  public static Object[] getXPath(String xml){
    InputStream is =  new StringBufferInputStream(xml);
    xPathArray = new ArrayList();

    try {
      DocumentBuilder db = 
DocumentBuilderFactory.newInstance().newDocumentBuilder();

      //Parse della stringa XML e creazione di un DOM Document
      Document document = db.parse(new InputSource(is));

      //Mi posiziono sulla Root del documento 
      Element root = document.getDocumentElement();

      //Inizio a scorrere il documento
      XmlReader.walkTree(root);
    } 
    catch (SAXException e) { e.printStackTrace(); }
    catch (ParserConfigurationException e){ 
      e.printStackTrace(); 
    }
    catch (IOException ee) { ee.printStackTrace(); }

    return xPathArray.toArray();
  }

  /**
   * Ricorsivamente ricostruisce i gradi di parentela 
   * del nodo ricevuto in input aggiornando la stringa 
   * ricevuta come parametro, per poi stoccarla nella 
   * lista contenente i path.
   *
   * @param nd  Nodo da esaminare 
   * @param str Path dei gradi di parentela
   */
  private static void findParents(Element nd,String str){
    String t = new String(str);
    Element e = nd;
    t = e.getParentNode().getNodeName() + "/" + t;

    if(e.getParentNode() != null 
        && e.getParentNode().getNodeType() == 1){
      findParents((Element)e.getParentNode(),t);
    }
    else{
      if(!xPathArray.contains(t.replaceFirst("#document","")))
        xPathArray.add(t.replaceFirst("#document",""));
    } 
  }


  /**
   * Scorre tutto l'albero XML.
   * Quando trova le foglie le passa al metodo 
   * {@link #findParents} per ricostruire l'albero parentale.
   */
  private static void walkTree(Element node){
    String t = new String(node.getNodeName());

    //Se il nodo ha nodi figliio di tipo TEXT
    if(node.getParentNode() != null && 
        (!node.hasChildNodes() || 
          node.getFirstChild().getNodeType() == 3) ){
      findParents((Element)node,t);
    }

    if(node.hasChildNodes()){
      for(int i=0; i<node.getChildNodes().getLength();i++){
        if(node.getChildNodes().item(i).getNodeType() == 1)
          walkTree((Element)node.getChildNodes().item(i));
      }
    }
  }

  public static void main(String[] args){
    String xmlFile = 
      "<pippo>"
      + "<ciao><tag/></ciao>"
      + "<dona>56</dona>"
      + "<beppe><mirko/></beppe>"
      + "<beppe>ii</beppe>"
      + "<nano>1</nano>"
      + "</pippo>";
      
    Object[] arr = XmlReader.getXPath(xmlFile);

    for(int i=0; i<arr.length;i++){
      System.out.println(arr[i]);
    }
  }
}

Il funzionamento della classe è abbastanza chiaro e trasparente, e tutto sommato anche semplice, ma quel che conta di più è che compie il suo sporco lavoro.

Alla prossima,
MA

0 commenti:

Posta un commento

Non ti è chiaro qualcosa?
No problem, posta il tuo dubbio ;)

..... e ricordati di firmarlo!