Subversion Repositories wimsdev

Rev

Rev 17770 | 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("str0", ("[" + 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.                        compress = True):
  217.         """
  218.        Crée un élément HTML prêt à insérer dans une page, ou une page
  219.        HTML complète.
  220.        @param css fournit un élément de stryle, pour le placement des
  221.                   DIVs de chaque élément (True par défaut)
  222.        @param js fournit un script javascript pour augmenter les
  223.                   facilités offertes par le tableau (False par défaut)
  224.        @param standalone fait une page HTML complète (False par défaut)
  225.        @param tocxx crée du code valide pour C++11 s'il est
  226.                   positionné à vrai. Faux par défaut.
  227.        @param compress minifiera le code CSS ; (True par défaut)
  228.        """
  229.         result=""
  230.         # éléments de style
  231.         if css:
  232.             """
  233.            création à partir d'un modèle et des constantes ci-après
  234.            """
  235.             # DIV pour chaque élément
  236.             div_width = 44
  237.             div_height = 56
  238.             # espacements verticaux
  239.             top_margin = 20
  240.             row_gap = 6
  241.             row_extra_gap = 10 # supplément pour les lanthanides et actinides
  242.             # espacements horizontaux
  243.             left_margin = 20
  244.             col_gap = 6
  245.             col_extra_gap = 6 # supplément pour les lanthanides et actinides
  246.             # caractéristiques du symbole
  247.             symbol_top = -18
  248.             symbol_font_size = 26
  249.             # caractéristiques du numéro atomique
  250.             Z_top = -16
  251.             Z_left = 2
  252.             Z_font_size = 14
  253.             # caractéristiques de la masse molaire
  254.             mass_top = 28
  255.             mass_font_size = 12
  256.             # caractéristiques de la structure électronique
  257.             struct_display = "inherit"
  258.             struct_top = -4
  259.             struct_left = 26
  260.             struct_font_size = 4
  261.             struct_line_height = 3.6
  262.             row_enum = [{"i": i+1,
  263.                          "top": top_margin + i * (row_gap + div_height) + \
  264.                          (row_extra_gap if i > 6 else 0)} \
  265.                         for i in range(9)]
  266.             col_enum = [{"i": i+1,
  267.                          "left": left_margin + i * (col_gap + div_width) + \
  268.                          (col_extra_gap if i > 1 else 0)} \
  269.                         for i in range(18)]
  270.             total_width = 18 * div_width + 17 * col_gap + col_extra_gap + \
  271.                 2 * left_margin
  272.             total_height = 9 * div_height + 8 * row_gap + row_extra_gap + \
  273.                 2 * top_margin
  274.             ################################
  275.             dict = { # pour formater le fichier modèle mendeleiev_template.css
  276.                 "div_width": div_width,
  277.                 "div_height": div_height,
  278.                 "symbol_top": symbol_top,
  279.                 "symbol_font_size": symbol_font_size,
  280.                 "Z_top": Z_top,
  281.                 "Z_left": Z_left,
  282.                 "Z_font_size": Z_font_size,
  283.                 "mass_top": mass_top,
  284.                 "mass_font_size": mass_font_size,
  285.                 "struct_display": struct_display,
  286.                 "struct_top": struct_top,
  287.                 "struct_left": struct_left,
  288.                 "struct_font_size": struct_font_size,
  289.                 "struct_line_height": struct_line_height,
  290.                 "row_enum": row_enum,
  291.                 "col_enum": col_enum,
  292.             }
  293.             ################################
  294.             tm = jinja2.Template(open("mendeleiev_template.css").read())
  295.             css_code = tm.render(**dict)
  296.             if compress:
  297.                 from cssmin import cssmin
  298.                 css_code = cssmin(css_code, wrap = 72)
  299.             result += "<style>\n" + css_code + "</style>\n"
  300.            
  301.         # script interactif
  302.         if js:
  303.             with open("mendeleiev.js") as infile:
  304.                 result += "<script>\n" + infile.read() + "</script>\n"
  305.         # suite de DIVs, un par élément
  306.         result += "\n".join([elt.toHtml() for elt in Element.get_table()]) + "\n"
  307.         # enrobage dans un DIV commun
  308.         result = """\
  309. <div id="mendeleiev_table" style="position: relative; width: {w}px; height: {h}px;">{r}</div>
  310. <div id="one_element"></div>
  311. <div id="wait" style="width:500; text-align: center;
  312.     background: rgba(200,200,255,0.5); position: absolute;
  313.     top: 50px; left: 200px; border: 1px navy solid; border-radius: 3px;">
  314.        Wait a few seconds...
  315. </div>
  316. """.format(r=result, w=total_width, h=total_height)
  317.         if standalone:
  318.             result = """\
  319. <html>
  320.  <head>
  321.     <title>Mendeleiev table</title>
  322.     <meta charset="UTF-8"/>
  323.  </head>
  324.  <body>
  325.    {r}
  326.  </body>
  327. </html>
  328. """.format(r = result)
  329.         elif tocxx:
  330.             cxxcode = 'const char * html_table = '
  331.             for line in result.split("\n"):
  332.                 line = line.replace('"', r'\"') + r'\n'
  333.                 cxxcode += '"'+line+'"\n'
  334.             cxxcode =   cxxcode[:-1] + ";\n"
  335.             result = cxxcode
  336.         return result
  337.  
  338. def create_cxx_source( target = "html_table.cc"):
  339.     """
  340.    Écrit une source valide pour C++11, qui définit un élément HTML valide
  341.    contenant une table périodique des éléments, qu'on peut insérer dans une
  342.    page HTML.
  343.    """
  344.     print("Écriture dans le fichier", target)
  345.     with open(target,"w") as outfile:
  346.         outfile.write(Element.get_table_html(tocxx=True, js=True))
  347.     return
  348.  
  349. def demo():
  350.     print("succession des niveaux selon la règle de Klechkovski")
  351.     print(regle_klechkovski())
  352.     print("struture de données pour la table de classification périodique")
  353.     print(mendel_rows())
  354.     print("Écriture dans le fichier /tmp/mendeleiev.html")
  355.     with open("/tmp/mendeleiev.html","w") as outfile:
  356.         outfile.write(Element.get_table_html())
  357.     return
  358.  
  359. if __name__ == "__main__":
  360.     create_cxx_source()
  361.     # demo()
  362.