2011-01-28 13 views
57

Sto seguendo questo tutorial.Come analizzare XML usando il parser SAX

Funziona alla grande, ma mi piacerebbe che restituisse una matrice con tutte le stringhe anziché una singola stringa con l'ultimo elemento.

Qualche idea su come procedere?

+0

La prego di inviare una rappresentazione astratta della vostra struttura XML? –

+0

http://dearfriends.se/category/blog/feed/rss/ -> visualizza sorgente – Johan

risposta

178

Quindi si desidera creare un parser XML per analizzare un feed RSS come questo.

<rss version="0.92"> 
<channel> 
    <title>MyTitle</title> 
    <link>http://myurl.com</link> 
    <description>MyDescription</description> 
    <lastBuildDate>SomeDate</lastBuildDate> 
    <docs>http://someurl.com</docs> 
    <language>SomeLanguage</language> 

    <item> 
     <title>TitleOne</title> 
     <description><![CDATA[Some text.]]></description> 
     <link>http://linktoarticle.com</link> 
    </item> 

    <item> 
     <title>TitleTwo</title> 
     <description><![CDATA[Some other text.]]></description> 
     <link>http://linktoanotherarticle.com</link> 
    </item> 

</channel> 
</rss> 

Ora hai due implementazioni SAX con cui puoi lavorare. O si utilizza l'implementazione org.xml.sax o android.sax. Spiegherò i pro ei contro di entrambi dopo aver postato un breve esempio di ancore.

android.sax Attuazione

Cominciamo con il android.sax implementazione.

È necessario definire la struttura XML utilizzando gli oggetti RootElement e Element.

In ogni caso, vorrei lavorare con POJO (Plain Old Java Objects) che terrebbero i vostri dati. Qui sarebbero necessari i POJO.

Channel.java

public class Channel implements Serializable { 

    private Items items; 
    private String title; 
    private String link; 
    private String description; 
    private String lastBuildDate; 
    private String docs; 
    private String language; 

    public Channel() { 
     setItems(null); 
     setTitle(null); 
     // set every field to null in the constructor 
    } 

    public void setItems(Items items) { 
     this.items = items; 
    } 

    public Items getItems() { 
     return items; 
    } 

    public void setTitle(String title) { 
     this.title = title; 
    } 

    public String getTitle() { 
     return title; 
    } 
    // rest of the class looks similar so just setters and getters 
} 

Questa classe implementa l'interfaccia Serializable modo da poter mettere in una Bundle e fare qualcosa con esso.

Ora abbiamo bisogno di una classe per contenere i nostri articoli. In questo caso ho intenzione di estendere la classe ArrayList.

Items.java

public class Items extends ArrayList<Item> { 

    public Items() { 
     super(); 
    } 

} 

Quello è per il nostro contenitore degli elementi. Ora abbiamo bisogno di una classe per contenere i dati di ogni singolo elemento.

Item.java

public class Item implements Serializable { 

    private String title; 
    private String description; 
    private String link; 

    public Item() { 
     setTitle(null); 
     setDescription(null); 
     setLink(null); 
    } 

    public void setTitle(String title) { 
     this.title = title; 
    } 

    public String getTitle() { 
     return title; 
    } 

    // same as above. 

} 

Esempio:

public class Example extends DefaultHandler { 

    private Channel channel; 
    private Items items; 
    private Item item; 

    public Example() { 
     items = new Items(); 
    } 

    public Channel parse(InputStream is) { 
     RootElement root = new RootElement("rss"); 
     Element chanElement = root.getChild("channel"); 
     Element chanTitle = chanElement.getChild("title"); 
     Element chanLink = chanElement.getChild("link"); 
     Element chanDescription = chanElement.getChild("description"); 
     Element chanLastBuildDate = chanElement.getChild("lastBuildDate"); 
     Element chanDocs = chanElement.getChild("docs"); 
     Element chanLanguage = chanElement.getChild("language"); 

     Element chanItem = chanElement.getChild("item"); 
     Element itemTitle = chanItem.getChild("title"); 
     Element itemDescription = chanItem.getChild("description"); 
     Element itemLink = chanItem.getChild("link"); 

     chanElement.setStartElementListener(new StartElementListener() { 
      public void start(Attributes attributes) { 
       channel = new Channel(); 
      } 
     }); 

     // Listen for the end of a text element and set the text as our 
     // channel's title. 
     chanTitle.setEndTextElementListener(new EndTextElementListener() { 
      public void end(String body) { 
       channel.setTitle(body); 
      } 
     }); 

     // Same thing happens for the other elements of channel ex. 

     // On every <item> tag occurrence we create a new Item object. 
     chanItem.setStartElementListener(new StartElementListener() { 
      public void start(Attributes attributes) { 
       item = new Item(); 
      } 
     }); 

     // On every </item> tag occurrence we add the current Item object 
     // to the Items container. 
     chanItem.setEndElementListener(new EndElementListener() { 
      public void end() { 
       items.add(item); 
      } 
     }); 

     itemTitle.setEndTextElementListener(new EndTextElementListener() { 
      public void end(String body) { 
       item.setTitle(body); 
      } 
     }); 

     // and so on 

     // here we actually parse the InputStream and return the resulting 
     // Channel object. 
     try { 
      Xml.parse(is, Xml.Encoding.UTF_8, root.getContentHandler()); 
      return channel; 
     } catch (SAXException e) { 
      // handle the exception 
     } catch (IOException e) { 
      // handle the exception 
     } 

     return null; 
    } 

} 

Ora che è stato un esempio molto veloce come si può vedere. Il vantaggio principale dell'utilizzo dell'implementazione SAX è che è possibile definire la struttura dell'XML che si deve analizzare e quindi aggiungere semplicemente un listener di eventi agli elementi appropriati. Lo svantaggio è che il codice diventa piuttosto ripetitivo e gonfio.

org.xml.sax Attuazione

L'implementazione del gestore org.xml.sax SAX è un po 'diverso.

Qui non si specifica o si dichiara la struttura XML ma si ascolta solo gli eventi.Quelli più utilizzati sono i seguenti eventi:

  • documento di avvio
  • Documento finale
  • elemento di Inizio
  • elemento terminale
  • personaggi tra l'elemento di avvio e elemento terminale

Un esempio l'implementazione del gestore utilizzando l'oggetto Canale sopra appare come questa.

Esempio

public class ExampleHandler extends DefaultHandler { 

    private Channel channel; 
    private Items items; 
    private Item item; 
    private boolean inItem = false; 

    private StringBuilder content; 

    public ExampleHandler() { 
     items = new Items(); 
     content = new StringBuilder(); 
    } 

    public void startElement(String uri, String localName, String qName, 
      Attributes atts) throws SAXException { 
     content = new StringBuilder(); 
     if(localName.equalsIgnoreCase("channel")) { 
      channel = new Channel(); 
     } else if(localName.equalsIgnoreCase("item")) { 
      inItem = true; 
      item = new Item(); 
     } 
    } 

    public void endElement(String uri, String localName, String qName) 
      throws SAXException { 
     if(localName.equalsIgnoreCase("title")) { 
      if(inItem) { 
       item.setTitle(content.toString()); 
      } else { 
       channel.setTitle(content.toString()); 
      } 
     } else if(localName.equalsIgnoreCase("link")) { 
      if(inItem) { 
       item.setLink(content.toString()); 
      } else { 
       channel.setLink(content.toString()); 
      } 
     } else if(localName.equalsIgnoreCase("description")) { 
      if(inItem) { 
       item.setDescription(content.toString()); 
      } else { 
       channel.setDescription(content.toString()); 
      } 
     } else if(localName.equalsIgnoreCase("lastBuildDate")) { 
      channel.setLastBuildDate(content.toString()); 
     } else if(localName.equalsIgnoreCase("docs")) { 
      channel.setDocs(content.toString()); 
     } else if(localName.equalsIgnoreCase("language")) { 
      channel.setLanguage(content.toString()); 
     } else if(localName.equalsIgnoreCase("item")) { 
      inItem = false; 
      items.add(item); 
     } else if(localName.equalsIgnoreCase("channel")) { 
      channel.setItems(items); 
     } 
    } 

    public void characters(char[] ch, int start, int length) 
      throws SAXException { 
     content.append(ch, start, length); 
    } 

    public void endDocument() throws SAXException { 
     // you can do something here for example send 
     // the Channel object somewhere or whatever. 
    } 

} 

Ora per essere onesti non posso davvero dire un reale vantaggio di questa implementazione gestore sul android.sax uno. Posso comunque dirvi lo svantaggio che dovrebbe essere abbastanza ovvio ora. Dai un'occhiata alla dichiarazione else if nel metodo startElement. Dato che abbiamo i tag <title>, link e description, dobbiamo tracciarci nella struttura XML che siamo al momento. Cioè se incontriamo un tag iniziale <item> impostiamo il flag inItem su true per garantire che mappiamo i dati corretti sull'oggetto corretto e nel metodo endElement impostiamo tale flag su false se incontriamo un tag </item>. Per segnalare che abbiamo finito con quel tag item.

In questo esempio è abbastanza facile da gestire, ma è necessario ingannare una struttura più complessa con tag ripetuti in livelli diversi. Dovresti utilizzare Enums per esempio per impostare lo stato corrente e un sacco di statemiet di switch/case per verificare dove sei o una soluzione più elegante sarebbe una sorta di tag tracker che utilizza uno stack di tag.

+0

@Adinia nota che non è un problema utilizzare entrambe le implementazioni insieme. Non c'è problema nel farlo finché sai perché lo fai. –

+0

@ octavian-damiean È vero che il mio codice funzionava, ma non sapevo davvero perché scrivessi ogni riga; Cerco di ripulirlo un po 'ora, perché ho capito come funziona ognuno di loro. Quindi grazie per la nota che va bene usare entrambi insieme. – Adinia

+0

@Adinia vedo. Prego. Se hai altre domande a riguardo puoi anche unirti alla bella [chat room Android] (http://chat.stackoverflow.com/rooms/15/android). –

2

In molti problemi è necessario utilizzare diversi tipi di file xml per scopi diversi. Non cercherò di cogliere l'immensità e di raccontare dalla mia esperienza ciò di cui avevo bisogno.

Java, forse, il mio linguaggio di programmazione preferito. Inoltre, questo amore è rafforzato dal fatto che è possibile risolvere qualsiasi problema e trovare una bicicletta non è necessario.

Quindi, mi ci è voluto creare un gruppo di client-server che eseguono un database che consenta al client di creare remotamente le voci nel server del database. Inutile controllare i dati di input, ecc. E simili, ma non si tratta di questo.

Come principio di lavoro, io, senza esitazione, ho scelto la trasmissione di informazioni sotto forma di file xml. Dei seguenti tipi:

<? xml version = "1.0" encoding = "UTF-8" standalone = "no"?> 
<doc> 
<id> 3 </ id> 
<fam> Ivanov </ fam> 
<name> Ivan </ name> 
<otc> I. </ otc> 
<dateb> 10-03-2005 </ dateb> 
<datep> 10-03-2005 </ datep> 
<datev> 10-03-2005 </ datev> 
<datebegin> 09-06-2009 </ datebegin> 
<dateend> 10-03-2005 </ dateend> 
<vdolid> 1 </ vdolid> 
<specid> 1 </ specid> 
<klavid> 1 </ klavid> 
<stav> 2.0 </ stav> 
<progid> 1 </ progid> 
</ doc> 

Semplificare la lettura oltre, ad eccezione di dire che sono le informazioni sulle istituzioni mediche. Cognome, nome, ID univoco e così via. In generale, le serie di dati. Questo file è salito in modo sicuro sul lato server e quindi inizia l'analisi del file.

Delle due opzioni parsing (SAX vs DOM) ho scelto vista SAX del fatto che funziona in modo più luminoso, e fu il primo che è caduto nelle mani :)

So. Come sapete, per lavorare con successo con il parser, abbiamo bisogno di sovrascrivere i metodi necessari di DefaultHandler. Per iniziare, collega i pacchetti richiesti.

import org.xml.sax.helpers.DefaultHandler; 
import org.xml.sax. *; 

Ora possiamo iniziare a scrivere il nostro parser

public class SAXPars extends DefaultHandler { 
   ... 
} 

Cominciamo con il metodo startDocument(). Lui, come suggerisce il nome, reagisce all'inizio di un evento del documento. Qui si può appendere una varietà di azioni come l'allocazione della memoria, o per ripristinare i valori, ma il nostro esempio è abbastanza semplice, quindi basta segnare l'inizio di lavoro di un messaggio appropriato:

Override 
public void startDocument() throws SAXException { 
   System.out.println ("Start parse XML ..."); 
} 

Avanti. Il parser passa attraverso il documento incontra l'elemento della sua struttura. Avvia il metodo startElement(). E infatti, il suo aspetto è questo: startElement (String namespaceURI, String localName, String qName, Attributes atts). Qui namespaceURI - lo spazio dei nomi, localName - il nome locale dell'elemento, qName- una combinazione di nome locale con uno spazio dei nomi (separato da due punti) e atts - gli attributi di questo elemento. In questo caso, tutto semplice. È sufficiente usare qName'om e gettarlo in qualche linea di servizio thisElement. Quindi segniamo in cui l'elemento al momento siamo.

@Override 
public void startElement (String namespaceURI, String localName, String qName, Attributes atts) throws SAXException { 
   thisElement = qName; 
} 

Successivamente, elemento di riunione si arriva al suo significato. Qui includono i metodi characters(). Ha la forma: caratteri (char [] ch, int start, int length). Bene, qui tutto è chiaro. ch - un file contenente la stessa importanza della stringa all'interno di questo elemento. inizio e lunghezza: il numero di servizio che indica il punto di partenza nella linea e nella lunghezza.

@Override 
public void characters (char [] ch, int start, int length) throws SAXException { 
   if (thisElement.equals ("id")) { 
      doc.setId (new Integer (new String (ch, start, length))); 
   } 
   if (thisElement.equals ("fam")) { 
      doc.setFam (new String (ch, start, length)); 
   } 
   if (thisElement.equals ("name")) { 
      doc.setName (new String (ch, start, length)); 
   } 
   if (thisElement.equals ("otc")) { 
      doc.setOtc (new String (ch, start, length)); 
   } 
   if (thisElement.equals ("dateb")) { 
      doc.setDateb (new String (ch, start, length)); 
   } 
   if (thisElement.equals ("datep")) { 
      doc.setDatep (new String (ch, start, length)); 
   } 
   if (thisElement.equals ("datev")) { 
      doc.setDatev (new String (ch, start, length)); 
   } 
   if (thisElement.equals ("datebegin")) { 
      doc.setDatebegin (new String (ch, start, length)); 
   } 
   if (thisElement.equals ("dateend")) { 
      doc.setDateend (new String (ch, start, length)); 
   } 
   if (thisElement.equals ("vdolid")) { 
      doc.setVdolid (new Integer (new String (ch, start, length))); 
   } 
   if (thisElement.equals ("specid")) { 
      doc.setSpecid (new Integer (new String (ch, start, length))); 
   } 
   if (thisElement.equals ("klavid")) { 
      doc.setKlavid (new Integer (new String (ch, start, length))); 
   } 
   if (thisElement.equals ("stav")) { 
      doc.setStav (new Float (new String (ch, start, length))); 
   } 
   if (thisElement.equals ("progid")) { 
      doc.setProgid (new Integer (new String (ch, start, length))); 
   } 
} 

Ah, sì. Ho quasi dimenticato. Poiché l'obiettivo di cui si tratterà di piegare i dati di naparsennye si riferisce al tipo di dottori. Questa classe è definita e ha tutti i setter necessari.

Il prossimo elemento ovvio termina ed è seguito dal successivo. Responsabile della conclusione di endElement(). Ci segnala che l'oggetto è finito e puoi fare qualsiasi cosa in questo momento. Procederà. Pulisci elemento.

@Override 
public void endElement (String namespaceURI, String localName, String qName) throws SAXException { 
   thisElement = ""; 
} 

Arrivando così all'intero documento, arriviamo alla fine del file. Work endDocument(). In esso, possiamo liberare memoria, fare una diagnostica di stampa, ecc. Nel nostro caso, basta scrivere su cosa termina l'analisi.

@Override 
public void endDocument() { 
   System.out.println ("Stop parse XML ..."); 
} 

Così abbiamo ottenuto una classe per analizzare xml il nostro formato. Ecco il testo completo:

import org.xml.sax.helpers.DefaultHandler; 
import org.xml.sax. *; 
  
public class SAXPars extends DefaultHandler { 
  
Doctors doc = new Doctors(); 
String thisElement = ""; 
  
public Doctors getResult() { 
   return doc; 
} 
  
@Override 
public void startDocument() throws SAXException { 
   System.out.println ("Start parse XML ..."); 
} 
  
@Override 
public void startElement (String namespaceURI, String localName, String qName, Attributes atts) throws SAXException { 
   thisElement = qName; 
} 
  
@Override 
public void endElement (String namespaceURI, String localName, String qName) throws SAXException { 
   thisElement = ""; 
} 
  
@Override 
public void characters (char [] ch, int start, int length) throws SAXException { 
   if (thisElement.equals ("id")) { 
      doc.setId (new Integer (new String (ch, start, length))); 
   } 
   if (thisElement.equals ("fam")) { 
      doc.setFam (new String (ch, start, length)); 
   } 
   if (thisElement.equals ("name")) { 
      doc.setName (new String (ch, start, length)); 
   } 
   if (thisElement.equals ("otc")) { 
      doc.setOtc (new String (ch, start, length)); 
   } 
   if (thisElement.equals ("dateb")) { 
      doc.setDateb (new String (ch, start, length)); 
   } 
   if (thisElement.equals ("datep")) { 
      doc.setDatep (new String (ch, start, length)); 
   } 
   if (thisElement.equals ("datev")) { 
      doc.setDatev (new String (ch, start, length)); 
   } 
   if (thisElement.equals ("datebegin")) { 
      doc.setDatebegin (new String (ch, start, length)); 
   } 
   if (thisElement.equals ("dateend")) { 
      doc.setDateend (new String (ch, start, length)); 
   } 
   if (thisElement.equals ("vdolid")) { 
      doc.setVdolid (new Integer (new String (ch, start, length))); 
   } 
   if (thisElement.equals ("specid")) { 
      doc.setSpecid (new Integer (new String (ch, start, length))); 
   } 
   if (thisElement.equals ("klavid")) { 
      doc.setKlavid (new Integer (new String (ch, start, length))); 
   } 
   if (thisElement.equals ("stav")) { 
      doc.setStav (new Float (new String (ch, start, length))); 
   } 
   if (thisElement.equals ("progid")) { 
      doc.setProgid (new Integer (new String (ch, start, length))); 
   } 
} 
  
@Override 
public void endDocument() { 
   System.out.println ("Stop parse XML ..."); 
} 
} 

Spero che l'argomento abbia contribuito a presentare facilmente l'essenza del parser SAX.

Non giudicare rigorosamente il primo articolo :) Spero che almeno qualcuno sia stato utile.

UPD: Per eseguire questo parser, è possibile utilizzare questo codice:

SAXParserFactory factory = SAXParserFactory.newInstance(); 
SAXParser parser = factory.newSAXParser(); 
SAXPars saxp = new SAXPars(); 
  
parser.parse (new File ("..."), saxp); 

Problemi correlati