Subversion Repositories wimsdev

Rev

Rev 17770 | 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):
17770 georgesk 105
        return "{Z} {symbol} {name} {mass}, row = {mendel_row}, col = {mendel_col}, elec. struct. {struc}".format(
17748 georgesk 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)
17770 georgesk 127
        struc = addPara("str0", ("[" + self.parent_noble_gas + "] " if \
17748 georgesk 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
17770 georgesk 215
    def get_table_html(css=True, js=False, standalone=False, tocxx = False,
216
                       compress = True):
17748 georgesk 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
17770 georgesk 226
                   positionné à vrai. Faux par défaut.
227
        @param compress minifiera le code CSS ; (True par défaut)
17748 georgesk 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)]
17773 georgesk 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
17748 georgesk 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())
17770 georgesk 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"
17748 georgesk 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 = """\
17773 georgesk 309
<div id="mendeleiev_table" style="position: relative; width: {w}px; height: {h}px;">{r}</div>
17748 georgesk 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>
17773 georgesk 316
""".format(r=result, w=total_width, h=total_height)
17748 georgesk 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:
17752 georgesk 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
17748 georgesk 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()