---
# System prepended metadata

title: M06 Accés a dades. XML SAX.
tags: [M6, python, DAM]

---

---
title: M06 Accés a dades. XML SAX.
tags: DAM, Python, M6
---

<div style="width: 30%; margin-left: auto;">
    
![](https://hackmd.io/_uploads/HJiR4eGJT.png)    
    
</div>


<a rel="license" href="http://creativecommons.org/licenses/by-sa/4.0/"><img alt="Llicència de Creative Commons" style="border-width:0" src="https://i.creativecommons.org/l/by-sa/4.0/88x31.png" /></a><br />Aquesta obra està subjecta a una llicència de <a rel="license" href="http://creativecommons.org/licenses/by-sa/4.0/">Reconeixement-CompartirIgual 4.0 Internacional de Creative Commons</a>

[Link en MarkDown](https://hackmd.io/@JdaXaviQ/rkDNzWpZa)

# M06 Accés a dades. UF01. XML SAX.
## Mètodes de '_parsejar_' documents XML:
1. [SAX](https://docs.python.org/es/3/library/xml.sax.html) (**S**imple **A**pi for **X**ML): SAX consisteix en una API sequencial basada en events que permet processar tot el document XML sense haver de carregar tot el document a memòria. Especialment útil quan hem de manegar fitxers molt grans.

    * Els elements són processats en el mateix ordre que apareixen al document. 
    * El desenvolupador ha de definir *[callbacks](https://ca.wikipedia.org/wiki/Callback_(programaci%C3%B3))* que s'encarreguen de processar nodes específics cada cop que es troben al document.
    * SAX només permet processar el document com a lectura.
    * És molt eficient en termes d'ús de memòria i de temps de processat però afegeix dificultat si el comparem amb DOM.

2. [DOM](https://www.w3.org/DOM/DOMTR) (**D**ocument **O**bject **M**odel): Amb DOM es carrega a memòria una representació abstracta de tot el document XML oferint accés aleatori a qualsevol node de l'arbre.

    * DOM necessita parsejar tot el document abans de poder accedir a la informació desada, cosa que el pot fer lent en algunes ocasions.
    * Tota la informació s'ha de carregar sencera a memòria, en documents extensos pot suposar un greu problema.
    * Permet modificar el document.

## Treballant amb SAX.
La biblioteca de python que utilitzarem en aquest curs per parsejar de manera sequencial documents XML serà [xml.sax](https://docs.python.org/es/3/library/xml.sax.html).
La forma habitual de començar a treballar és crear-nos la nostra pròpia classe que hereti de xml.sax.ContentHandler i sobrescriure els mètodes startElement(self, name, attrs), endElement(self, name) i characters(self, content)
Com a exemple utilitzarem el següent contingut XML i el desarem al fitxer smiley.svg
```xml=
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
  "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
    <!ENTITY custom_entity "Hello">
]>
<svg xmlns="http://www.w3.org/2000/svg"
  xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
  viewBox="-105 -100 210 270" width="210" height="270">
  <inkscape:custom x="42" inkscape:z="555">Some value</inkscape:custom>
  <defs>
    <linearGradient id="skin" x1="0" x2="0" y1="0" y2="1">
      <stop offset="0%" stop-color="yellow" stop-opacity="1.0"/>
      <stop offset="75%" stop-color="gold" stop-opacity="1.0"/>
      <stop offset="100%" stop-color="orange" stop-opacity="1"/>
    </linearGradient>
  </defs>
  <g id="smiley" inkscape:groupmode="layer" inkscape:label="Smiley">
    <!-- Head -->
    <circle cx="0" cy="0" r="50"
      fill="url(#skin)" stroke="orange" stroke-width="2"/>
    <!-- Eyes -->
    <ellipse cx="-20" cy="-10" rx="6" ry="8" fill="black" stroke="none"/>
    <ellipse cx="20" cy="-10" rx="6" ry="8" fill="black" stroke="none"/>
    <!-- Mouth -->
    <path d="M-20 20 A25 25 0 0 0 20 20"
      fill="white" stroke="black" stroke-width="3"/>
  </g>
  <text x="-40" y="75">&custom_entity; &lt;svg&gt;!</text>
  <script>
    <![CDATA[
      console.log("CDATA disables XML parsing: <svg>")
      const smiley = document.getElementById("smiley")
      const eyes = document.querySelectorAll("ellipse")
      const setRadius = r => e => eyes.forEach(x => x.setAttribute("ry", r))
      smiley.addEventListener("mouseenter", setRadius(2))
      smiley.addEventListener("mouseleave", setRadius(8))
    ]]>
  </script>
</svg>
```
Podem observar el contingut del fitxer obrint-lo directament amb el nostre navegador i també amb qualsevol programari d'edició o visualització de fitxers d'imatges vectorials.
A continuació trobeu un exemple de com extreure la informació d'aquest document utilitzant python xml.sax:
```python=
#!/usr/bin/python3
# -*- coding: utf-8 -*-

"""
svg_handler.py
Exemple d'utilització de la biblioteca xml.sax
"""
import xml.sax


def main():
    controlador_svg = SVGHandler()
    xml.sax.parse("./smiley.svg", controlador_svg)


class SVGHandler(xml.sax.ContentHandler):

    def startElement(self, name, attrs):
        print(f"BEGIN: <{name}>, {attrs.keys()}")
        for key in attrs.keys():
            print(f"\t{key}: {attrs[key]}")

    def endElement(self, name):
        print(f"END: </{name}>")

    def characters(self, content):
        if content.strip() != "":
            print("CONTENT:", repr(content))


if __name__ == "__main__":
    main()
```
I ara un altre exemple d'utilització de la mateixa biblioteca:
```python=
#!/usr/bin/python3
# -*- coding: utf-8 -*-

import xml.sax

class AlumnesHandler(xml.sax.ContentHandler):
    def __init__(self):
        self.llistat_alumnes = []
        self.llistat_etiquetes = []
        self.etiqueta_actual = ""
        self.nom = ""
        self.cognom = ""
        self.grup = ""

    
    def startElement(self, name, attrs):
        self.llistat_etiquetes.append(name)
        self.etiqueta_actual = name
        if name == "alumne":
            print("[v] Acabem d'entrar a un alumne nou.")
            self.nom = ""
            self.cognom = ""
            self.grup = ""

    def endElement(self, name):
        sortim_de = self.llistat_etiquetes.pop()
        if len(self.llistat_etiquetes) > 0:
            self.etiqueta_actual = self.llistat_etiquetes[-1]
        else:
            self.etiqueta_actual = ""
        if name == "alumne":
            print("[v] Acabem de sortir de l'alumne: ", self.nom, self.cognom)
            self.llistat_alumnes.append({
                "nom": self.nom,
                "cognom": self.cognom,
                "grup": self.grup
            })

    def characters(self, content):
        if self.etiqueta_actual == "nom":
            self.nom = content
        elif self.etiqueta_actual == "cognom":
            self.cognom = content
        elif self.etiqueta_actual == "grup":
            self.grup = content
        

def main():
    content = """<class>
    <alumne>
      <nom>Xavi</nom>
      <cognom>Quesada</cognom>
      <grup>DAM 2</grup>
    </alumne>
    <alumne>
      <nom>Pere</nom>
      <cognom>Llull</cognom>
      <grup>DAM 2</grup>
    </alumne>
    <alumne>
      <nom>Alicia</nom>
      <cognom>Galan</cognom>
      <grup>DAM 2</grup>
    </alumne>
</class>
    """
    with open("./class_dam2.xml","w") as dam2:
        dam2.write(content)
    

if __name__ == "__main__":
    main()
    parser = xml.sax.make_parser()

    handler = AlumnesHandler()
    parser.setContentHandler(handler)
    parser.parse("./class_dam2.xml")
    print("Hem acabat de processar el fitxer XML.")
    print(handler.llistat_alumnes)
```
Amb la seva sortida a consola:
```=
[v] Acabem d'entrar a un alumne nou.
[v] Acabem de sortir de l'alumne:  Xavi Quesada
[v] Acabem d'entrar a un alumne nou.
[v] Acabem de sortir de l'alumne:  Pere Llull
[v] Acabem d'entrar a un alumne nou.
[v] Acabem de sortir de l'alumne:  Alicia Galan
Hem acabat de processar el fitxer XML.
[{'nom': 'Xavi', 'cognom': 'Quesada', 'grup': 'DAM 2'}, {'nom': 'Pere', 'cognom': 'Llull', 'grup': 'DAM 2'}, {'nom': 'Alicia', 'cognom': 'Galan', 'grup': 'DAM 2'}]
```