
licence="""
    file acidebase.py: part of the package pyacidobasic version %s:

    Copyright (C) 2010 Georges Khaznadar <georgesk@ofset.org>

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
"""

from PyQt5.QtCore import *
from xml.dom import minidom
import numpy as np
import re

class abHTMLparser:
    """
    un analyseur de code HTML capable de construire une liste d'acides
    et de bases
    """
    def __init__(self,infile):
        self.infile=infile
        try:
            self.document=minidom.parse(open(infile))
        except Exception as e:
            print("Erreur dans %s : %s" %(infile, e))
        return

    def parseTableToList(self):
        t=self.document.getElementsByTagName("table")[0]
        lignes=t.getElementsByTagName("tr")[1:]
        ab=[acideBase.fromTableRow(l) for l in lignes]
        return ab

noTD = re.compile(r'<td[^>]*>\s*(.*)\s*</td>', re.MULTILINE)
def untagTD(s):
    m=noTD.match(s)
    if m:
        return m.group(1)
    else:
        return s
        
class acideBase:
    def __init__(self, formes=["AH","A<sup>-</sup>"], formeIndex=0, nom="acide générique", pK=[3.5], c=1.0, charge=0, v=0.0, other=None):
        """
        objet représentant un couple acido-basique
        @param formes la liste des formes acido-basiques conjuguées
        @param formeIndex la forme de base
        @param nom désignation de la forme de base
        @param pK liste des constantes d'acidité
        @param c concentration initiale en mol/L
        @param charge charge électrique de la forme acide en unité élémentaire
        @param v volume en mL
        @param other une instance d'acideBase à recopier
        """
        if other !=None:
            self.formes=other.formes
            self.formeIndex=other.formeIndex
            self.nom=other.nom
            self.pK=other.pK
            self.c=other.c
            self.charge=other.charge
            self.v=other.v
        else:
            self.formes=formes
            self.formeIndex=formeIndex
            self.nom=nom
            self.pK=pK
            self.c=c
            self.charge=charge
            self.v=v

    @staticmethod
    def fromTableRow(tr):
        data=tr.getElementsByTagName("td")
        formes=re.split(r",\s*",untagTD(data[0].toxml()))
        formeIndex=int(untagTD(data[1].toxml()))
        nom=untagTD(data[2].toxml())
        pK=re.split(r",\s*",untagTD(data[3].toxml()))
        pK=[float(i) for i in pK]
        c=float(untagTD(data[4].toxml()))
        charge=int(untagTD(data[5].toxml()))
        result=acideBase(formes,formeIndex,nom,pK,c,charge)
        return result
    

    def copie(self):
        return acideBase(self.formes, self.formeIndex, self.nom, self.pK,  self.c, self.charge, self.v)

    def __str__(self):
        return "%s;%s;%s;%s;%s;%s;%s" %(self.formes,
                                         self.formeIndex,
                                         self.nom,
                                         self.pK,
                                         self.c,
                                         self.charge,
                                         self.v)

    def concentrationParPH(self, espece, pHData, vTotal, vBurette=None):
        """
        @param espece un index dans self.formes
        @param pHData un array (numpy) de données de pH
        @param vTotal un vecteur de volume total de la solution en unité mL
        @param vBurette un vecteur de volumes versés en unité mL, ou None si on s'intéresse au bécher
        @return un array de données pour la concentration de l'espèce en fonction du pH
        """
        result=np.ones(len(pHData))
        for i in range(len(pHData)):
            if vBurette!=None:
                verse=vBurette[i]
            else:
                verse=None
            result[i]=self.ni(pHData[i],
                              vBurette=verse)[espece]*1e3/vTotal[i]
        return result
    
    def chargeNette(self,pH):
        """
        @param pH : le pH
        @return la charge électrique en Faraday en fonction du pH
        """
        ni=self.ni(pH)
        ch=self.charge # on considère la charge de l'espèce acide
        result=0.0
        # on calcule d'abord la concentration de charges
        for n in ni:
            result+=n*ch # on cumule la charge de l'espèce acide
            ch-=1        # et on passe à la base conjuguée
        return result

    def ni(self,pH, vBurette=None):
        """
        renvoie la liste des quantités des formes acido-basiques
        @param pH : le pH
        @param vBurette volume versé, ou None si ce n'est pas de la burette que vient le produit
        @return la liste des quantités de formes acido-basiques
        """
        n=np.ones(1+len(self.pK)) # liste des quantités des espèces
        total=1.0                 # concentration totale
        for i in range(len(self.pK)):
            c=n[i]*10**(pH-self.pK[i])
            n[1+i]=c
            total+=c
        # On normaliste pour que la quantité totale soit self.c*self.v*1e-3
        if vBurette!=None:
            n *= self.c*vBurette*1e-3/total
        else:
            n *= self.c*self.v*1e-3/total
        return n
    
def fromString(s):
    s=s.split(";")
    ab=acideBase()
    formes=s[0].split(',')
    ab.formes=[]
    for f in formes:
        f=unicode(f,"utf-8")
        ab.formes.append(QString(f))
    ab.formeIndex=   int(s[1])
    ab.nom=          QString(unicode(s[2],"utf-8"))
    pKs=s[3].split(",")
    ab.pK=[]
    for pK in pKs:
        ab.pK.append(float(pK))
    ab.c= float(s[4])
    ab.charge= int(s[5])
    return ab

def listFromString(s):
    liste=[]
    for ligne in s.split("\n"):
        try:
            acideOuBase=fromString(ligne)
            liste.append(acideOuBase)
        except:
            pass
    return liste

def listFromHTML(s):
    """
    remplit une liste d'acides/bases à partir de code HTML
    """
    liste=[]
    p=abHTMLparser(liste)
    p.feed(s)
    return liste

class abDOZparser:
    """
    classe pour un analyseur de fichier .equ de dozzaqueux
    """
    def __init__(self, liste):
        """
        Le constructeur
        @ param liste référence d'une liste que l'analyseur va allonger
        """
        self.liste=liste
        self.doc=None     # racine du document
        self.el=[]        # liste d'éléments de la base de donnée

    def feed(self, s):
        """
        digère le contenu d'un fichier .qu de dozzaqueux
        """
        if s[:5]!='<Doz>':
            s=xmlizeDoz(s)
        self.doc=parseString(s)
        self.el=self.doc.getElementsByTagName("Element")
        self.lesAcides()
        
    def lesAcides(self):
        """
        extrait les composés de la base qui ont un comportement acido-basique
        """
        for e in self.el:
            lk=e.getElementsByTagName("Log_K")
            if lk:
                lk=lk[0].firstChild.nodeValue.strip().split("\n")
                i=e.getElementsByTagName("Identifiant")[0]
                nom=i.firstChild.nodeValue.strip().encode("utf-8")
                attributs=e.getElementsByTagName("Composition")[0].firstChild.nodeValue.strip().split("\n")
    

def xmlizeDoz(s):
    """
    change un fichier dozzaqueux de type .equ pour en faire un fichier
    xml valide. Testé avec la version 119 du fichier base.equ
    """
    s="<Doz>\n"+s+"</Doz>\n" # établit une racine unique
    s=s.replace("Representant de l'element", "Representant")
    s=s.replace("Atomes constitutifs","Atomes_constitutifsq")
    s=s.replace("Log K","Log_K")
    return s

def listFromDozzaqueux(s):
    """
    remplit une liste d'acides/bases à partir de code Dozzaqueux
    """
    liste=[]
    p=abDOZparser(liste)
    p.feed(s)
    return liste

def listFromFile(fname):
    p=fname.find("html")
    if p>0:
        return listFromHTML(open(fname,"r").read(2000000))
    p=fname.find("equ")
    if p>0:
        return listFromDozzaqueux(open(fname,"r").read(2000000))
    return listFromString(open(fname,"r").read(2000000))

if __name__=="__main__":
    p=abHTMLparser("lang/db-fr_FR.html")
    for a in p.parseTableToList():
        print(str(a))
    
