Subversion Repositories wimsdev

Rev

Rev 17748 | Rev 17770 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

  1. #! /usr/bin/python3
  2.  
  3. """
  4. This utility is part of chemeq's source package. It is used to
  5. create a table of Mendeliev in HTML format with data coming from
  6. the file mendeleiev.cc
  7.  
  8. Chemeq is a basic standalone filter written in C language,
  9. flex and bison. It inputs strings like:
  10.  
  11. `2H2 + O2 ---> 2 H2O`
  12.  
  13. and can outputs LaTeX code and messages about the equilibrium of a
  14. chemical reaction.
  15. ----------------------------------------
  16.  
  17. Copyright (c) 2023 Georges Khaznadar
  18. License: GPL V3+
  19.  
  20.    This program is free software: you can redistribute it and/or modify
  21.    it under the terms of the GNU General Public License as published by
  22.    the Free Software Foundation, either version 3 of the License, or
  23.    (at your option) any later version.
  24.  
  25.    This program is distributed in the hope that it will be useful,
  26.    but WITHOUT ANY WARRANTY; without even the implied warranty of
  27.    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  28.    GNU General Public License for more details.
  29.  
  30.    You should have received a copy of the GNU General Public License
  31.    along with this program.  If not, see <http://www.gnu.org/licenses/>.
  32. """
  33.  
  34. import sys, re
  35. from bs4 import BeautifulSoup as BS
  36. from subprocess import call
  37. import jinja2
  38.  
  39. niveaux_electroniques = [
  40.     ["1s"],
  41.     ["2s", "2p"],
  42.     ["3s", "3p", "3d"],
  43.     ["4s", "4p", "4d", "4f"],
  44.     ["5s", "5p", "5d", "5f", "..."],
  45.     ["6s", "6p", "6d", "..."],
  46.     ["7s", "7p", "7d", "..."],
  47. ]
  48.  
  49. def regle_klechkovski():
  50.     result = []
  51.     for i in range(8):
  52.         col = i; row = 0
  53.         while col >=0:
  54.             if row < 7 and col < len(niveaux_electroniques[row]):
  55.                 result.append(niveaux_electroniques[row][col])
  56.             col -= 1; row += 1
  57.     return result
  58.  
  59. def mendel_rows():
  60.     """
  61.    Renvoie un dictionnaire qui associe un niveau à la liste des éléments qui
  62.    viennent peupler ce niveau d'électrons supplémentaires
  63.    """
  64.     capacites = {
  65.         "s": 2,
  66.         "p": 6,
  67.         "d": 10,
  68.         "f": 14,
  69.         ".": 0, # pas d'électrons quand on a marqué "..."
  70.     }
  71.     result = {}
  72.     Z = 1
  73.     row = "0"
  74.     for structure in regle_klechkovski():
  75.         if structure[0] > row:
  76.             row = structure[0]
  77.             result[row] = {}
  78.         if structure not in result[row]:
  79.             result[row][structure]=[]
  80.         for i in range(capacites[structure[1]]):
  81.             result[row][structure].append(Z)
  82.             Z += 1
  83.     return result
  84.  
  85. def layerordering(layer):
  86.     return layer.replace("s","1").replace("p","2").replace("d","3").replace("f","4")
  87.  
  88. class Element:
  89.     def __init__(self, name= None, symbol = None, Z = None, mass = None,
  90.                  elec_structure = None, mendel_row = None,
  91.                  mendel_col = None, mendel_family = None,
  92.                  parent_noble_gas = ""):
  93.         self.name = name
  94.         self.symbol= symbol
  95.         self.Z = int(Z)
  96.         self.mass = float(mass)
  97.         self.elec_structure = elec_structure
  98.         self.mendel_row = mendel_row
  99.         self.mendel_col = mendel_col
  100.         self.mendel_family = mendel_family
  101.         self.parent_noble_gas = parent_noble_gas
  102.         return
  103.  
  104.     def __str__(self):
  105.         return "{Z} {symbol} {name} {mass}, row = {mendel_row}, col = {mendel_col}, elec. struct. : {struc}".format(
  106.             struc = ("[" + self.parent_noble_gas + "] " if \
  107.                      self.parent_noble_gas else "") + \
  108.             self.elec_structure_string,
  109.             **self.__dict__
  110.         )
  111.  
  112.     def toHtml(self):
  113.         """
  114.        Crée un DIV pour une page HTML, avec les classes qui permettent un
  115.        placement correct, si on dispose de la bonne feuille de style.
  116.        """
  117.         soup = BS("""<div class="element c{mendel_col} r{mendel_row}" style="cursor: wait;"></div>""".format(**self.__dict__), features="lxml")
  118.         def addPara(theclass, text):
  119.             p = soup.new_tag("p")
  120.             p["class"] = theclass
  121.             p.string = str(text)
  122.             soup.div.append(p)
  123.             return p
  124.         addPara("symbol", self.symbol)
  125.         addPara("Z", self.Z)
  126.         addPara("mass", self.mass)
  127.         struc = addPara("struct", ("[" + self.parent_noble_gas + "] " if \
  128.                      self.parent_noble_gas else ""))
  129.         for layer in self.elec_structure:
  130.             span = soup.new_tag("span")
  131.             span["class"] = "layer"
  132.             m = re.match(r'(.*)\^\{(.*)\}', layer)
  133.             span.string = m.group(1)
  134.             struc.extend([span, " "])
  135.             sup = soup.new_tag("sup")
  136.             sup.string = m.group(2)
  137.             span.append(sup)
  138.         soup.div["title"] = str(self)
  139.         return str(soup.div)
  140.  
  141.     @property
  142.     def elec_structure_string(self):
  143.         return " ".join(sorted(self.elec_structure, key = layerordering))
  144.    
  145.     @staticmethod
  146.     def get_table():
  147.         """
  148.        Renvoie une liste d'instances d'Element, avec toutes les données
  149.        nécessaire pour dessiner une table de Mendéléiev
  150.        """
  151.         result = []
  152.         mr = mendel_rows()
  153.         data={}
  154.         elt_pattern = re.compile(r'\s+.\{\{N_\("(.*)"\),\s*"(.*)",\s*"(.*)",\s*"(.*)".*')
  155.         with open("mendeleiev.cc") as infile:
  156.             for l in infile.readlines():
  157.                 m = elt_pattern.match(l)
  158.                 if m:
  159.                     data[int(m.group(3))] = {
  160.                         "name": m.group(1),
  161.                         "symbol": m.group(2),
  162.                         "Z": m.group(3),
  163.                         "mass": m.group(4),
  164.                     }
  165.         parent_noble_gas = ""
  166.         mendel_col = 0
  167.         for row in sorted(mr.keys()):
  168.             elts = mr[row]
  169.             substructure = []
  170.             for struct, z_list in elts.items():
  171.                 for i, z in enumerate(z_list):
  172.                     # on détermine la colonne
  173.                     if z == 1: mendel_col = 1
  174.                     elif z== 2: mendel_col = 18
  175.                     elif struct[1] == "s": mendel_col = 1  + i
  176.                     elif struct[1] == "d": mendel_col = 3  + i
  177.                     elif struct[1] == "p": mendel_col = 13 + i
  178.                     elif struct[1] == "f": mendel_col = 4 + i
  179.                     # exceptions : Lu et Lr
  180.                     # après les lanthanides et les actinides, respectivement
  181.                     if z == 71 or z == 103: mendel_col = 18
  182.                     # on détermine la ligne
  183.                     mendel_row = row
  184.                     if struct[1] == "f":
  185.                         if z >= 89: # actinide
  186.                             mendel_row = 9
  187.                         elif z >= 57: # lanthanide
  188.                             mendel_row = 8
  189.                     # exceptions pour la fin des lantanides et des actinides
  190.                     if z == 71:
  191.                         mendel_row = 8 # Lu
  192.                     elif z == 103:
  193.                         mendel_row = 9 # Lr                            
  194.                     # on détermine le gaz noble parent
  195.                     if z > 86: parent_noble_gas = "Rn"
  196.                     elif z > 54: parent_noble_gas = "Xe"
  197.                     elif z > 36: parent_noble_gas = "Kr"
  198.                     elif z > 18: parent_noble_gas = "Ar"
  199.                     elif z > 10: parent_noble_gas = "Ne"
  200.                     elif z > 2: parent_noble_gas = "He"
  201.                     # on finit de construire l'instance d'Element
  202.                     structure = substructure + [struct+'^{'+str(i+1)+'}']
  203.                     elt = Element(
  204.                         elec_structure = structure,
  205.                         mendel_col = mendel_col,
  206.                         mendel_row = mendel_row,
  207.                         parent_noble_gas = parent_noble_gas,
  208.                         **data[z]
  209.                     )
  210.                     result.append(elt)
  211.                 substructure.append(struct+'^{'+str(len(z_list))+'}')
  212.         return result
  213.  
  214.     @staticmethod
  215.     def get_table_html(css=True, js=False, standalone=False, tocxx = False):
  216.         """
  217.        Crée un élément HTML prêt à insérer dans une page, ou une page
  218.        HTML complète.
  219.        @param css fournit un élément de stryle, pour le placement des
  220.                   DIVs de chaque élément (True par défaut)
  221.        @param js fournit un script javascript pour augmenter les
  222.                   facilités offertes par le tableau (False par défaut)
  223.        @param standalone fait une page HTML complète (False par défaut)
  224.        @param tocxx crée du code valide pour C++11 s'il est
  225.                   positionné à vrai. Faux par défut.
  226.        """
  227.         result=""
  228.         # éléments de style
  229.         if css:
  230.             """
  231.            création à partir d'un modèle et des constantes ci-après
  232.            """
  233.             # DIV pour chaque élément
  234.             div_width = 44
  235.             div_height = 56
  236.             # espacements verticaux
  237.             top_margin = 20
  238.             row_gap = 6
  239.             row_extra_gap = 10 # supplément pour les lanthanides et actinides
  240.             # espacements horizontaux
  241.             left_margin = 20
  242.             col_gap = 6
  243.             col_extra_gap = 6 # supplément pour les lanthanides et actinides
  244.             # caractéristiques du symbole
  245.             symbol_top = -18
  246.             symbol_font_size = 26
  247.             # caractéristiques du numéro atomique
  248.             Z_top = -16
  249.             Z_left = 2
  250.             Z_font_size = 14
  251.             # caractéristiques de la masse molaire
  252.             mass_top = 28
  253.             mass_font_size = 12
  254.             # caractéristiques de la structure électronique
  255.             struct_display = "inherit"
  256.             struct_top = -4
  257.             struct_left = 26
  258.             struct_font_size = 4
  259.             struct_line_height = 3.6
  260.             row_enum = [{"i": i+1,
  261.                          "top": top_margin + i * (row_gap + div_height) + \
  262.                          (row_extra_gap if i > 6 else 0)} \
  263.                         for i in range(9)]
  264.             col_enum = [{"i": i+1,
  265.                          "left": left_margin + i * (col_gap + div_width) + \
  266.                          (col_extra_gap if i > 1 else 0)} \
  267.                         for i in range(18)]
  268.             ################################
  269.             dict = { # pour formater le fichier modèle mendeleiev_template.css
  270.                 "div_width": div_width,
  271.                 "div_height": div_height,
  272.                 "symbol_top": symbol_top,
  273.                 "symbol_font_size": symbol_font_size,
  274.                 "Z_top": Z_top,
  275.                 "Z_left": Z_left,
  276.                 "Z_font_size": Z_font_size,
  277.                 "mass_top": mass_top,
  278.                 "mass_font_size": mass_font_size,
  279.                 "struct_display": struct_display,
  280.                 "struct_top": struct_top,
  281.                 "struct_left": struct_left,
  282.                 "struct_font_size": struct_font_size,
  283.                 "struct_line_height": struct_line_height,
  284.                 "row_enum": row_enum,
  285.                 "col_enum": col_enum,
  286.             }
  287.             ################################
  288.             tm = jinja2.Template(open("mendeleiev_template.css").read())
  289.             result += "<style>\n" + tm.render(**dict) + "</style>\n"
  290.            
  291.         # script interactif
  292.         if js:
  293.             with open("mendeleiev.js") as infile:
  294.                 result += "<script>\n" + infile.read() + "</script>\n"
  295.         # suite de DIVs, un par élément
  296.         result += "\n".join([elt.toHtml() for elt in Element.get_table()]) + "\n"
  297.         # enrobage dans un DIV commun
  298.         result = """\
  299. <div id="mendeleiev_table">{r}</div>
  300. <div id="one_element"></div>
  301. <div id="wait" style="width:500; text-align: center;
  302.     background: rgba(200,200,255,0.5); position: absolute;
  303.     top: 50px; left: 200px; border: 1px navy solid; border-radius: 3px;">
  304.        Wait a few seconds...
  305. </div>
  306. """.format(r=result)
  307.         if standalone:
  308.             result = """\
  309. <html>
  310.  <head>
  311.     <title>Mendeleiev table</title>
  312.     <meta charset="UTF-8"/>
  313.  </head>
  314.  <body>
  315.    {r}
  316.  </body>
  317. </html>
  318. """.format(r = result)
  319.         elif tocxx:
  320.             cxxcode = 'const char * html_table = '
  321.             for line in result.split("\n"):
  322.                 line = line.replace('"', r'\"') + r'\n'
  323.                 cxxcode += '"'+line+'"\n'
  324.             cxxcode =   cxxcode[:-1] + ";\n"
  325.             result = cxxcode
  326.         return result
  327.  
  328. def create_cxx_source( target = "html_table.cc"):
  329.     """
  330.    Écrit une source valide pour C++11, qui définit un élément HTML valide
  331.    contenant une table périodique des éléments, qu'on peut insérer dans une
  332.    page HTML.
  333.    """
  334.     print("Écriture dans le fichier", target)
  335.     with open(target,"w") as outfile:
  336.         outfile.write(Element.get_table_html(tocxx=True, js=True))
  337.     return
  338.  
  339. def demo():
  340.     print("succession des niveaux selon la règle de Klechkovski")
  341.     print(regle_klechkovski())
  342.     print("struture de données pour la table de classification périodique")
  343.     print(mendel_rows())
  344.     print("Écriture dans le fichier /tmp/mendeleiev.html")
  345.     with open("/tmp/mendeleiev.html","w") as outfile:
  346.         outfile.write(Element.get_table_html())
  347.     return
  348.  
  349. if __name__ == "__main__":
  350.     create_cxx_source()
  351.     # demo()
  352.