# Accés a fitxers XML ### Introducció. Analitzadors. XML (e**X**tensible **M**arkup **L**anguage) és un metallenguatge que ens permet emmagatzemar dades d’una forma tal que posteriorment pot ser interpretada per altres plataformes i/o aplicacions, ja que és un format d’intercanvi que conté metainformació (les etiquetes) on es descriu i estructura el contingut de qualsevol document. Un dels grans avantatges que tenen els arxius XML és que són arxius de text, sols cal que les diferents plataformes que els intercanvien coneguin la codificació dels caràcters emprats. Així, apreciarem un XML per la seva estructura característica d'informació textual continguda entre hashtags (etiquetes): **<nom_camp>"informació"</nomcamp>**, a més de l'aniuament entre diferents nivells d'etiquetes. XML a més permet incloure dins la capçalera del document la codificació que s’ha utilitzat per emmagatzemar la informació. Un exemple típic d'un XML qualsevol contingut a un arxiu podria ser aquest: ``` <?xml version="1.0" encoding="UTF-8" standalone="no"?> <CATALOG> <CD> <TITLE>Empire Burlesque</TITLE> <ARTIST>Bob Dylan</ARTIST> <COUNTRY>USA</COUNTRY> <COMPANY>Columbia</COMPANY> <PRICE>10.90</PRICE> <YEAR>1985</YEAR> </CD> <CD> <TITLE>Hide your heart</TITLE> <ARTIST>Bonnie Tyler</ARTIST> <COUNTRY>UK</COUNTRY> <COMPANY>CBS Records</COMPANY> <PRICE>9.90</PRICE> <YEAR>1988</YEAR> </CD> </CATALOG> ``` Feta aquesta breu presentació parlem dels analitzadors (*parsers*) XML, els quals permeten extreure-hi informació continguda a les etiquetes com en alguns casos també generar nous arxius XML o esmenar XML existents. Així els més utilitzats a dia d'avui són: * DOM (**D**ocument **O**bject **M**odel): * Guarden totes les dades del document XML en memòria i de forma **jeràrquica**, en arbre, amb nodes pare, fill i nodes finals (sense descedents). * Degut a això l'anàlisi amb DOM requereix molts recursos en forma de memòria, més quant més gran i complexe en nodes sigui l'XML a analitzar. * SAX (**S**imple **A**PI for **X**ML): * SAX és un analitzador seqüencial (o sintàctic): Extreu el contingut paulatinament segons es van descobrint els hashtags d’apertura (<>) i tancament (</>). * Tenen l’inconvenient que cal llegir tot el document per arribar a una part concreta d’aquest. A diferència del DOM, SAX no carrega l'arxiu XML sencer en memòria, fet que alleugera molt l'ús d'aquesta. A més, el fet que SAX processi seqüencialment (línia a línia) l'XML fa que sigui eficient i més ràpid que DOM davant arxius grans com els que s'empren a big data. Tot i que veurem ara el seu funcionament en java, val a dir que tant un com l'altre són estàndards independents del llenguatge de programació i, de fet, les seves APIs estan disponibles en molts llenguatges com per exemple python. ### Anàlisi d'XML amb DOM. En java, el DOM s’implementa fet ús de classes i interfícies contingudes al paquet **org.w3c.dom** i al paquet **javax.xml.parsers**. Per tant, una de les primeres coses que haurem de fer és importar-les per poder utilitzar-les a conveniència dins els nostres programes. En tot cas l'estructura de DOM es regeix pels següents elements, els quals veurem aparèixer a continuació: ![DOM](https://hackmd.io/_uploads/SyyVJ8AxJx.jpg) La interfície principal que utilitzarem s'anomena **Document** i representa a tot el document XML. Com tota interfície, aquesta podrà ser implementada en vàries classes. A més de Document, tenim la classe abstracta **DocumentBuilder**, la qual permet crear el DOM a partir de l’XML. A més s’especifica la classe **DocumentBuilderFactory** que ens permet fabricar els diferents DocumentBuilder. En ser DocumentBuilder abstracta no es pot instanciar directament i per això necessitem DocumentBuilderFactory (més info sobre la factoria abstracta al tutorial: [Patró de factoria abstracta](https://hackmd.io/@TTalens/Skh7oI8cp)) Així, tant si escrivim un XML nou com si el volem llegir, codificarem i instanciarem les d'aquesta forma: ``` DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); try { DocumentBuilder builder = factory.newDocumentBuilder(); //tractament diferenciat per lectura o escriptura } catch (Exception e) { e.printStackTrace(); ``` Arribats a aquest punt, cal veure com funciona DOM en el cas de generar i en el cas de llegir un XML. Comencem per l'escriptura. #### Escriptura d'un XML amb DOM A partir del builder, el que farem és fer ús de la interfície **DOMImplementation** per poder crear objectes **Document** amb el node arrel "empleats": ``` DOMImplementation implementation = builder.getDOMImplementation(); Document document = implementation.createDocument (null,"empleats", null); document.setXmlVersion("1.0"); ``` Fixeu-vos que l'objecte document té el modificador setXmlVersion per poder dir quina és la versió de XML a fer servir. Aquesta haurà de ser comuna entre els diferents components de software que exportin i importin el mateix arxiu XML. Per veure els següents passos prendrem com a referència i exemple un programa que llegeix un fitxer aleatori i genera un XML amb el seu contingut. **IMPORTANT**: no és necessari partir de fitxers aleatoris per generar XMLs amb DOM, la font d'informació podrien ser els registres obtinguts d'una base de dades o un fitxer seqüencial, per dir sols alguns exemples. En aquest cas el fitxer aleatori diposa de la següent informació registre a registre: id, nom de l'empleat (nomEmp), departament al qual pertany (dep) i el salari que percep. Disposeu del codi que genera aquest fitxer al següent repositori: [EscriureEmpleatsAleatori.java](https://github.com/atalens1/M06-UF1-DOM/blob/main/EscriureEmpleatsAleatori.java). El que farem a continuació és anar dins l'arxiu aleatori i llegir el seu contingut: ``` char nomEmp[] = new char[20], aux; for ( ; ;){ file.seek(posicio); id = file.readInt(); for (int i = 0; i < nomEmp.length ; i++) { aux =file.readChar(); nomEmp[i] = aux; } String nomEmps = new String (nomEmp); dep = file.readInt(); salari = file.readDouble(); if (id>0) { Element arrel = document.createElement ("empleat"); arrel.setAttribute("id",Integer.toString(id)); document.getDocumentElement().appendChild(arrel); CrearElement ("nomEmp",nomEmps.trim(), arrel, document); CrearElement ("dep", Integer.toString(dep), arrel, document); CrearElement ("salari", Double.toString(salari),arrel, document); } posicio = posicio + 56; if (file.getFilePointer() == file.length()) break; } ``` Analitzem ara els diferents "actors" que han aparegut: * **arrel** és un objecte element, el qual creem a partir del mètode createElement sobre l'objecte document. Especifiquem el nom que tindrà que en aquest cas és "empleat". * En aquest exemple concret hem optat per fer que la id sigui un atribut de l'element arrel, per això fem un setAttribute. * Definit ja l'arrel, l'afegim a l'XML fent getDocumentElement().appendChild(arrel) * El mètode **CrearElemento** és un mètode que rep com a paràmetres el nom de l'etiqueta (datoempleat), el seu valor (valor), el node arrel del qual penjarà (arrel) i l'objecte document (document) instanciat. Ho fem així per refactoritzar codi i evitar repetir aquestes línies dins el codi, però la seva missió és poder **generar els diferents nodes fills**. ``` public static void CrearElement (String dadaEmpleat, String valor, Element arrel, Document document) { Element elem = document.createElement (dadaEmpleat); Text text = document.createTextNode(valor); arrel.appendChild (elem); elem.appendChild (text); } ``` L'especificació DOM no defineix cap mecanisme per generar un arxiu XML a partir d'un arbre DOM. És per aquesta raó que arribats a aquest punt cal fer servir el paquet **javax.xml.transform** per poder especificar l'origen de l'XML o **source** i la seva destinació/resultat o **result**. Tant origen com resultat poden ser fitxers, streams (fluxos) o altres nodes DOM, tot i que en aquest cas veurem com escriure aquest XML al fitxer: ``` Source source = new DOMSource (document); Result result = new StreamResult (new FileWriter("empleats.xml")); Transformer transformer = TransformerFactory.newInstance().newTransformer(); transformer.setOutputProperty(OutputKeys.INDENT, "yes"); transformer.setOutputProperty("{http://xml.apache.org/xalan}indent-amount", "5"); transformer.transform (source, result); ``` Així en aquest cas l'origen/source és un DOMSource de l'objecte document i el resultat és un StreamResult sobre un writer on especifiquem el nom del fitxer .xml. Fixeu-vos que el mètode **transform** sobre l'objecte **transformer** és el responsable de generar l'arxiu xml mitjançant la transformació del document XML en un arxiu de text xml. Una altra peculiaritat és que prèviament a cridar a transform podem determinar algunes propietats per formatar l'xml, com és la seva identació o el nivell d'identació. Això ho fem emprant el mètode **setOutputProperty**. Disposeu del codi complert i ben estructurat en aquest enllaç: [CrearEmpleatDOM.java](https://github.com/atalens1/M06-UF1-DOM/blob/main/CrearEmpleatDOM.java). El resultat d'executar aquest programa és un arxiu empleats.xml amb aquest contingut: ``` <?xml version="1.0" encoding="UTF-8" standalone="no"?> <empleats> <empleat id="1"> <nomEmp>Pere Gil</nomEmp> <dep>10</dep> <salari>1200.0</salari> </empleat> <empleat id="2"> <nomEmp>Josep Lopez</nomEmp> <dep>25</dep> <salari>2300.25</salari> </empleat> <empleat id="3"> <nomEmp>Marti Garrido</nomEmp> <dep>15</dep> <salari>750.32</salari> </empleat> </empleats> ``` #### Lectura d'un XML amb DOM D'igual manera que amb l'escriptura partirem d'un builder i emprarem el mètode parse, el qual podem passar indistintament un objecte File amb el nom de l'arxiu a analitzar: ``` Document document = builder.parse(new File("empleats.xml")); ``` o bé directament el nom de l'arxiu: ``` Document document = builder.parse("empleats.xml"); ``` Amb l'objecte document instanciat, procedim a definir l'arrel i els diferents nodes fills. * Amb el mètode **getDocumentElement()** obtindrem quin és l'element arrel. * Amb el mètode **getElementsByTagName(nom_element)** obtindrem la llista de nodes fills que penjen d'un element anomenat "empleat". ``` Document document = builder.parse("empleats.xml"); Element arrel = document.getDocumentElement(); System.out.printf ("element arrel : %s %n", arrel.getNodeName()); NodeList empleats = document.getElementsByTagName("empleat"); System.out.printf ("Nodes a recòrrer: %d %n", empleats.getLength()); ``` Els printf no són necessaris en el codi que desenvolupeu, però fem una ullada als mètodes que implementen i com es podrien utilitzar en altres circumstàncies: * arrel.getNodeName() ens permet obtenir el nom del node arrel. * A partir d'un NodeList, el mètode getLength() ens permet veure quina serà la seva mida en nombre de nodes fills. Un cop ja tenim la llista de nodes la iterarem amb un bucle per tal d'obtenir les etiquetes i valors continguts: ``` for (int i = 0; i < empleats.getLength(); i++) { Node emple = empleats.item(i); if (emple.getNodeType() == Node.ELEMENT_NODE){ Element element = (Element) emple; System.out.printf("Id = %s %n",element.getAttribute("id")); System.out.printf(" * nom empleat = %s %n", element.getElementsByTagName("nomEmp").item(0).getTextContent()); System.out.printf(" * departament = %s %n", element.getElementsByTagName("dep").item(0).getTextContent()); System.out.printf(" * salari = %s %n", element.getElementsByTagName("salari").item(0).getTextContent()); } } ``` On fent `if (emple.getNodeType() == Node.ELEMENT_NODE)` validem i ens assegurem que els nodes seleccionats són del tipus element, ja que dins un fitxer XML podrien haver-hi altres tipus de node que continguessin el text que hem cercat. D'altra banda recordem que un dels elements tenia a més un atribut (`<empleat id = 1> ... </empleat>`), raó per la qual fem element.getAttribute(nom_element), en aquest cas `element.getAttribute("id")`. Per últim, item(0).getTextContent() ens permetrà recuperar el valor contingut entre les etiquetes. Disposeu del codi complet en aquest enllaç: [LlegirEmpleatDOM.java](https://github.com/atalens1/M06-UF1-DOM/blob/main/LlegirEmpleatDOM.java). El resultat d'executar aquest codi és: ``` element arrel : empleats Nodes a recòrrer: 3 Id = 1 * nom empleat = Pere Gil * departament = 10 * salari = 1200.0 Id = 2 * nom empleat = Josep Lopez * departament = 25 * salari = 2300.25 Id = 3 * nom empleat = Marti Garrido * departament = 15 * salari = 750.32 ``` ### Exercici proposat Creeu una classe anomenada Employee i feu un ArrayList d'aquesta que incorpori aquests elements: ``` Employee [ideEmp=1, name=Ana, surname=Rebollo, age=34, height=178.0, job=Enginyera Tecnica] Employee [ideEmp=2, name=Sandra, surname=Fernandez, age=55, height=165.0, job=Cap departament] Employee [ideEmp=3, name=Juan, surname=Doe, age=19, height=190.0, job=Practiques] ``` Un cop fet: a. Genereu un programa semblant a [CrearEmpleatDOM.java](https://github.com/atalens1/M06-UF1-DOM/blob/main/CrearEmpleatDOM.java) el qual escrigui un arxiu XML a partir del que hi ha contingut en aquest ArrayList. b. Genereu un programa semblant a [LlegirEmpleatDOM.java](https://github.com/atalens1/M06-UF1-DOM/blob/main/LlegirEmpleatDOM.java) el qual faci la lectura del arxiu XML generat a l'apartat a. i mostri el seu contingut per pantalla. ### Anàlisi d'XML amb SAX. SAX (Simple API for XML) és un conjunt de classes i interfícies les quals ens proveeixen d'eines per poder procesar de forma eficient un document XML. Com a punt forts de SAX destaquem el **poc consum de memòria**, degut a que no carrega tot l'XML en memòria per procesar-lo. Això permet tractar de forma eficient documents XML amb molt volum de dades (com els que es poden fer servir a Big Data). SAX fa accés seqüencial, no recorre tot el document XML per poder fer el seu procesament. Això que aparentment pot semblar un avantatge pot resultar ser un inconvenient si el que es vol és tenir una visió global de tot el document. IMPORTANT: SAX a diferència del DOM sols fa accés en mode lectura del XML. Per tant no ens permet escriure o modificar un document XML. El primer que necessitem per treballar amb SAX és fer una instància del SAXParserFactory mitjançant la qual obtindrem el parser (analitzador) SAX: ``` SAXParserFactory saxpf = SAXParserFactory.newInstance(); SAXParser parser = saxpf.newSAXParser(); XMLReader procesadorXML = parser.getXMLReader(); ``` A partir d'aquesta instància obtenim el procesador XML fent un getXMLReader() sobre el parser SAX. El fer ús d'aquestes classes i interfícies dona lloc als següents imports: ``` import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.XMLReader; ``` I a tenir present i capturar les excepcions **SAXException** i **ParserConfigurationException**, a més d'altres ja conegudes com FileNotFoundException i IOException. A partir d'aquí i un cop tenim definit el procesador XML el que fem és: ``` GestioContingut gestor = new GestioContingut(); procesadorXML.setContentHandler(gestor); InputSource fileXML = new InputSource ("empleats.xml"); procesadorXML.parse(fileXML); ``` On gestor és un objecte de la classe GestioContingut, la qual és una classe que estèn la classe DefaultHandler, la qual conté una sèrie de mètodes com: * startDocument() i endDocument(), per fer accions a l'inici i final del parseig del document. * startElement() i endElement(), per fer accions a l'inici i final del parseig de cads element contingut a l'XML. En el cas concret d'startElement() fixem-nos que rep els següents paràmetres: ``` public void startElement (String uri, String nom, String nomC, Attributes atts) ``` La uri és el que s'anomena identificador de l'espai de noms. Quan es combinen XMLs de diferents origens pot ser útil fer-ne ús de l'uri per evitar conflictes amb els noms de les seves etiquetes i elements. El paràmetre **nom** és el nom local, sense prefix de l'espai de noms i **nomC** és el que s'anomena nom qualificat, el qual incorpora el prefix d'espai de noms. Ara bé, en aquells casos com l'XML que estem emprant d'exemple, el qual no té definit cap espai de noms (namespace) veiem que el paràmetre **uri** no ve informat (és a dir ve com " ") i el paràmetre **nom** estarà doncs buit, essent el paràmetre **nomC** el que contindrà el nom complet de l'element. És per aquesta raó per la qual tota l'estona veurem que ens referim a nomC en comptes de nom. * characters() per poder tractar els diferents textElement continguts a cada element. Així la finalitat d'utilitzar GestioContingut en comptes de DefaultHandler directament està en el fet de poder afegir altres mètodes nous o adaptar el que fan els mètodes proveïts per DefaultHandler. En el nostre cas volem parsejar un document XML d'empleats com el següent: ``` <?xml version="1.0" encoding="UTF-8" standalone="no"?> <empleats> <empleat id="1"> <nomEmp>Pere Gil</nomEmp> <dep>10</dep> <salari>1200.0</salari> </empleat> <empleat id="2"> <nomEmp>Josep Lopez</nomEmp> <dep>25</dep> <salari>2300.25</salari> </empleat> <empleat id="3"> <nomEmp>Marti Garrido</nomEmp> <dep>15</dep> <salari>750.32</salari> </empleat> </empleats> ``` Les adaptacions que farem als mètodes de la classe GestioContingut són les següents, tot i que cadascú podrà treure, fer o desfer segons el problema que hi hagi de resoldre: ``` class GestioContingut extends DefaultHandler { public GestioContingut(){ super(); } //Anunciem l'inici del document public void startDocument(){ System.out.println("Inici del document XML"); } //Anunciem el final del document public void endDocument(){ System.out.println("Final del document XML"); } //Comencem a tractar els elements public void startElement (String uri, String nom, String nomC, Attributes atts) { for (int i = 0; i < atts.getLength(); i++) { // Nom de l'atribut (si l'element en té) String nomAtribut = atts.getQName(i); // Valor d'aquest atribut (si l'element en té) String valorAtribut = atts.getValue(i); System.out.printf("\t\tAtribut: %s, Valor: %s %n", nomAtribut, valorAtribut); } System.out.printf("\tPrincipi Element: %s %n", nomC); } //Final de tractament dels elements public void characters(char[] ch, int inicio, int longitud) throws SAXException { String car = new String (ch, inicio, longitud).trim(); if (!car.isEmpty()) { System.out.printf("\tCaracters: %s %n", car); } } public void endElement (String uri, String nom, String nomC){ System.out.printf("\tFinal d'element: %s %n",nomC); } } ``` Com veieu en aquest cas l'única cosa que fem és produir un output on mostrem tot el que conté l'XML, elements i contingut: ``` Inici del document XML Principi Element: empleats Atribut: id, Valor: 1 Principi Element: empleat Principi Element: nomEmp Caracters: Pere Gil Final d'element: nomEmp Principi Element: dep Caracters: 10 Final d'element: dep Principi Element: salari Caracters: 1200.0 Final d'element: salari Final d'element: empleat Atribut: id, Valor: 2 Principi Element: empleat Principi Element: nomEmp Caracters: Josep Lopez Final d'element: nomEmp Principi Element: dep Caracters: 25 Final d'element: dep Principi Element: salari Caracters: 2300.25 Final d'element: salari Final d'element: empleat Atribut: id, Valor: 3 Principi Element: empleat Principi Element: nomEmp Caracters: Marti Garrido Final d'element: nomEmp Principi Element: dep Caracters: 15 Final d'element: dep Principi Element: salari Caracters: 750.32 Final d'element: salari Final d'element: empleat Final d'element: empleats Final del document XML ``` El codi sencer corresponent a la lectura amb SAX el teniu a: [codi exemple lectura XML amb SAX](https://github.com/atalens1/M06-UF1-DOM/blob/main/LecturaSAX.java)