Subversion Repositories wimsdev

Rev

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

Rev Author Line No. Line
17748 georgesk 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:
17752 georgesk 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
17748 georgesk 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()