#!/usr/bin/python
# -*- coding: UTF-8 -*-


"""
    Code pour la manipulation des fichiers au format XML, avec SAX.
"""


###############################################################################
# Informations de versions
###############################################################################
__project__   = 'Formation Python'
__author__    = 'Simon CHOLLET (s.chollet@llr.in2p3.fr)'
__modifiers__ = ''
__date__      = '08/09/2011'
__version__   = '1.0'


###############################################################################
# Importations
###############################################################################
# Importation du module de parseurs
import xml.parsers.expat as xmlparser

# Importation de l'objet permettant de stocker la configuration
import config_xml as xmlcfg


###############################################################################
# Definitions specifiques
###############################################################################
# Mode pour afficher des messages
XML_DEBUG = False

# Chemin des fichiers XML
XML_SIMPLE_CFG_INPUT_PATH = "../input/simple.xml"
XML_FULL_CFG_INPUT_PATH   = "../input/config.xml"

# Le mode de parsage de chaine de caracteres
XML_PARSE_STRING = 1
# Le mode de parsage de fichier
XML_PARSE_FILE   = 2


###############################################################################
# Declaration des classes / fonctions
###############################################################################
class SaxCfgParser():
    """
        Classe permettant de parser le fichier XML de configuration avec SAX.
        -----------------------------------------------------------------------
        Attributs :
            - m_xml_path : Chemin du fichier XML a lire.
            - m_simple   : True si configuration Simple,
                           False si configuration Complete.
            - m_parser   : Parseur.
            - m_type     : Type de parsage souhaite.
            - m_config   : Configuration inclue dans le fichier XML.
            - m_pile     : Pile contenant les balises dans l'ordre d'analyse.
    """

    # Attributs avec initialisation par defaut
    m_xml_path = ""
    m_simple   = True
    m_parser   = None
    m_type     = XML_PARSE_STRING
    m_config   = None
    m_pile     = []


    def __init__(self, xml_path="", simple=True):
        """
            Constructeur
            -------------------------------------------------------------------
            Arguments :
                - xml_path : Chemin du fichier (optionnel, vide par defaut).
                - simple   : True si utilisation configuration simple,
                             False si configuration complete.
            -------------------------------------------------------------------
            Retour : Aucun.
        """
        # Mise a jour des attributs
        self.m_xml_path = xml_path
        self.m_simple   = simple

        # Creation du parseur
        self.m_parser = xmlparser.ParserCreate()

        # La methode d'analyse de debut de parsage
        self.m_parser.StartElementHandler = self.StartElt

        # La methode d'analyse des donnees de parsage
        self.m_parser.CharacterDataHandler = self.DataElt

        # La methode d'analyse de fin de parsage
        self.m_parser.EndElementHandler = self.EndElt

        # Initialisation des autres attributs
        m_type = XML_PARSE_STRING
        if simple:
            self.m_config = xmlcfg.SimpleCfg()
        else:
            self.m_config = xmlcfg.FullCfg()
        self.m_pile = []


    def StartElt(self, name, attr):
        """
            Cette methode est appelee lorsque le parseur rencontre une balise,
            lors du parsage.
            -------------------------------------------------------------------
            Arguments :
                - name : Nom de la balise.
                - attr : Attributs de la balise.
            -------------------------------------------------------------------
            Retour : Aucun.
        """
        # Ajout de la balise dans la pile
        self.m_pile.append((name, attr))
        if XML_DEBUG:
            print "Name = %s, Attr = %s" % (name, attr)

        # Parsage par la configuration elle-meme
        self.m_config.Parse(self.m_pile)


    def DataElt(self, data):
        """
            Cette methode est appelee pour analyser les donnees d'une balise,
            lors du parsage.
            -------------------------------------------------------------------
            Arguments :
                - data : Les donnees de la balise.
            -------------------------------------------------------------------
            Retour : Aucun.
        """
        if XML_DEBUG:
            print "Data = %s (len = %d)" % (data, len(data.strip(("\t \n"))))

        # Parsage par la configuration elle-meme
        if len(data.strip(("\t \n"))):
            self.m_config.Parse(self.m_pile, data)


    def EndElt(self, name):
        """
            Cette methode est appelee lorsque le parseur rencontre une balise,
            lors du parsage.
            -------------------------------------------------------------------
            Arguments :
                - name : Nom de la balise.
            -------------------------------------------------------------------
            Retour : Aucun.
        """
        # Retrait de la balise dans la pile
        balise = self.m_pile.pop()
        if XML_DEBUG:
            print "End name = %s" % (name)

        # Si erreur sur l'ordre d'apparition des balises :
        # Il faut toujours '<tag>...</tag>'.
        if balise[0] != name:
            return False


    def Parse(self, data, mode=XML_PARSE_STRING):
        """
            Cette methode est appelee lorsque le parseur rencontre une balise,
            lors du parsage.
            -------------------------------------------------------------------
            Arguments :
                - data : Chaine de caracteres, ou handle fichier.
                - mode : Mode d'utilisation :
                    * XML_PARSE_STRING : Parsage de chaine de caracteres.
                    * XML_PARSE_FILE   : Parsage de fichier.
            -------------------------------------------------------------------
            Retour :
                True si OK, False si erreur.
        """
        # Analyse des donnees
        self.m_mode = mode
        if self.m_mode == XML_PARSE_STRING:
            self.m_parser.Parse(data, 0)
        elif self.m_mode == XML_PARSE_FILE:
            # Ouverture du fichier XML
            xmlfile = open(data, 'r')

            # Parsage du fichier
            self.m_parser.ParseFile(xmlfile)

            # Fermeture du fichier
            xmlfile.close()
        else:
            # Mode inconnu !
            return False

        # Tout va bien
        return True


    def Close(self):
        """
            Cette methode permet de fermer proprement le parseur.
            -------------------------------------------------------------------
            Arguments : Aucun.
            -------------------------------------------------------------------
            Retour :
                True si OK, False si erreur.
        """
        # Pour la fin de vie du parseur
        if self.m_mode == XML_PARSE_STRING:
            self.m_parser.Parse("", 1)

        # Pour le parseur lui-meme
        del self.m_parser

        # Tout va bien
        return True


#
#
# Fonction appelee lors de la demande
# d'execution de la classe/module
if __name__ == "__main__":

    ###########################################################################
    # Creation du parseur
    parser = xmlparser.ParserCreate()

    ###########################################################################
    # Appel du parseur sur une chaine de caracteres
    parser.Parse("""<?xml version="1.0" encoding="utf-8" ?>
        <config>
            <author>Simon CHOLLET</author>
            <email>s.chollet@llr.in2p3.fr</email>
            <institut nom="LLR" address="Ecole Polytechnique - 91128 PALAISEAU" />
            <date>09/09/2011</date>
            <version>1.0</version>
        </config>
        """, 0)
    parser.Parse("", 1)

    # Creation du parseur de fichier
    parser = xmlparser.ParserCreate()

    # Ouverture du fichier XML
    xmlfile = open(XML_SIMPLE_CFG_INPUT_PATH, 'r')

    ###########################################################################
    # Appel du parseur sur un fichier
    parser.ParseFile(xmlfile)

    # Fermeture du fichier XML
    xmlfile.close()

    ###########################################################################
    # Utilisation de la classe SaxCfgParser
    # Nouveau parseur sur configuration simple
    parser = SaxCfgParser(simple=True)

    # Parse le fichier
    parser.Parse(XML_SIMPLE_CFG_INPUT_PATH, XML_PARSE_FILE)

    # Affiche le contenu de la configuration lue
    # Affiche :
    #   >>> SimpleCfg.__repr__() <<<
    #    * Auteur   : Simon CHOLLET
    #    * Email    : s.chollet@llr.in2p3.fr
    #    * Institut : LLR, Ecole Polytechnique - 91128 PALAISEAU
    #    * Date     : 09/09/2011
    #    * Version  : 1.0
    print parser.m_config

    # Ferme le parseur
    parser.Close()


    ###########################################################################
    # Nouveau parseur sur configuration complete
    parser = SaxCfgParser(simple=False)

    # Parse le fichier
    parser.Parse(XML_FULL_CFG_INPUT_PATH, XML_PARSE_FILE)

    # Affiche le contenu de la configuration lue
    # Affiche :
    #   >>> FullCfg.__repr__() <<<
    #    * Auteur   : Simon CHOLLET
    #    * Email    : s.chollet@llr.in2p3.fr
    #    * Institut : LLR, Ecole Polytechnique - 91128 PALAISEAU
    #    * Date     : 09/09/2011
    #    * Version  : 1.0
    #    * Descrip. : Ce fichier de configuration permet de décrire une ...
    #   --------------------------------------------------------
    #      Systeme (3 zones) :
    #       - Zone 1 : Source (5 devices)
    #          - Device 1 (4 canaux) :
    #             - Descr. : Lecroy 104 MX
    #             - Marque, Ref. : Lecroy, dev_1_1
    #             - Comm. : 192.168.0.9, 1000
    #             - Canal 1 : Impulsion Entrée
    #             - Canal 2 : Trigger
    #             - Canal 3 : Horloge
    #             - Canal 4 : Impulsion Sortie
    #          - Device 2 (2 canaux) :
    #   ...
    #       - Zone 2 : Amplificateur (5 devices)
    #   ...
    print parser.m_config

    # Ferme le parseur
    parser.Close()
