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() |