]> git.p6c8.net - template-class.git/blob - template.py
- Leerzeichen bei einigen Kommentaren korrigiert
[template-class.git] / template.py
1
2 #
3 # Template (Version 2.5)
4 # (Python 3)
5 #
6 # Klasse zum Parsen von Templates
7 #
8 # Autor: Patrick Canterino <patrick@patshaping.de>
9 # Letzte Aenderung: 18.4.2013
10 #
11 # Copyright (C) 2002-2013 Patrick Canterino
12 #
13 # Diese Datei kann unter den Bedingungen der "Artistic License 2.0"
14 # weitergegeben und / oder veraendert werden.
15 # Siehe:
16 # http://www.opensource.org/licenses/artistic-license-2.0
17 #
18
19 import os.path
20 import sys
21
22 class Template:
23
24 # __init__()
25 #
26 # Konstruktor
27 #
28 # Parameter: -keine-
29 #
30 # Rueckgabe: -nichts-
31
32 def __init__(self):
33 self.file = ''
34 self.template = ''
35 self.original = ''
36 self.old_parsing = 0
37 self.vars = {}
38 self.defined_vars = []
39 self.loop_vars = {}
40
41 # get_template()
42 #
43 # Kompletten Vorlagentext zurueckgeben
44 #
45 # Parameter: -keine-
46 #
47 # Rueckgabe: Kompletter Vorlagentext (String)
48
49 def get_template(self):
50 return str(self.template)
51
52 # set_template()
53 #
54 # Kompletten Vorlagentext aendern
55 #
56 # Parameter: Vorlagentext
57 #
58 # Rueckgabe: -nichts- (Template-Objekt wird modifiziert)
59
60 def set_template(self,template):
61 self.template = str(template)
62
63 # add_text()
64 #
65 # Vorlagentext ans Template-Objekt anhaengen
66 #
67 # Parameter: Vorlagentext
68 #
69 # Rueckgabe: -nichts- (Template-Objekt wird modifiziert)
70
71 def add_text(self,text):
72 self.set_template(self.get_template()+str(text))
73
74 # read_file()
75 #
76 # Einlesen einer Vorlagendatei und {INCLUDE}-Anweisungen ggf. verarbeiten
77 # (Text wird an bereits vorhandenen Text angehaengt)
78 #
79 # Parameter: 1. Datei zum Einlesen
80 # 2. Status-Code (Boolean):
81 # true => {INCLUDE}-Anweisungen nicht verarbeiten
82 # false => {INCLUDE}-Anweisungen verarbeiten (Standard)
83 #
84 # Rueckgabe: -nichts- (Template-Objekt wird modifiziert)
85
86 def read_file(self,file,not_include=0):
87 self.file = file
88
89 fp = open(file,'r')
90 content = fp.read()
91 fp.close()
92
93 self.add_text(content)
94 self.save_state()
95
96 if not not_include: self.parse_includes()
97
98 # set_var()
99 #
100 # Wert einer Variable setzen
101 #
102 # Parameter: 1. Name der Variable
103 # 2. Wert, den die Variable erhalten soll
104 #
105 # Rueckgabe: -nichts- (Template-Objekt wird modifiziert)
106
107 def set_var(self,var,content):
108 if content is None: content = ''
109 self.vars[var] = content
110
111 # get_var()
112 #
113 # Wert einer Variable zurueckgeben
114 #
115 # Parameter: (optional) Variablenname
116 #
117 # Rueckgabe: Wert der Variable;
118 # wenn die Variable nicht existiert, false;
119 # wenn kein Variablenname angegeben wurde, wird ein
120 # Array mit den Variablennamen zurueckgegeben
121
122 def get_var(self,var=None):
123 if var is not None:
124 if var in self.vars:
125 return self.vars[var]
126 else:
127 return None
128 else:
129 return self.vars.keys()
130
131 # set_vars()
132 #
133 # Komplettes Variablen-Array mit einem anderen Array ueberschreiben
134 #
135 # Parameter: Array
136 #
137 # Rueckgabe: -nichts- (Template-Objekt wird modifiziert)
138
139 def set_vars(self,vars):
140 self.vars = vars
141
142 # add_vars()
143 #
144 # Zum bestehenden Variablen-Array weitere Variablen in Form eines Arrays
145 # hinzufuegen
146 #
147 # Parameter: Array
148 #
149 # Rueckgabe: -nichts- (Template-Objekt wird modifiziert)
150
151 def add_vars(self,vars):
152 self.vars.update(vars)
153
154 # set_loop_data()
155 #
156 # Daten fuer eine Schleife setzen
157 #
158 # Parameter: 1. Name der Schleife
159 # 2. Array mit den Dictionaries mit den Variablen fuer
160 # die Schleifendurchgaenge
161 #
162 # Rueckgabe: -nichts- (Template-Objekt wird modifiziert)
163
164 def set_loop_data(self,loop,data):
165 self.loop_vars[loop] = data
166
167 # add_loop_data()
168 #
169 # Daten fuer einen Schleifendurchgang hinzufuegen
170 #
171 # Parameter: 1. Name der Schleife
172 # 2. Dictionary mit den Variablen fuer den
173 # Schleifendurchgang
174 #
175 # Rueckgabe: -nichts- (Template-Objekt wird modifiziert)
176
177 def add_loop_data(self,loop,data):
178 if loop in self.loop_vars and type(self.loop_vars[loop]) is list:
179 self.loop_vars[loop].append(data)
180 else:
181 self.loop_vars[loop] = [data]
182
183 # parse()
184 #
185 # In der Template definierte Variablen auslesen, Variablen
186 # ersetzen, {IF}- und {TRIM}-Bloecke parsen
187 #
188 # Parameter: -nichts-
189 #
190 # Rueckgabe: -nichts- (Template-Objekt wird modifiziert)
191
192 def parse(self):
193 if self.old_parsing: return self.parse_old()
194
195 # Zuerst die Schleifen parsen
196
197 if self.loop_vars and self.loop_vars.keys():
198 loops = self.loop_vars.keys()
199
200 for loop in loops:
201 self.parse_loop(loop)
202
203 # In Template-Datei definierte Variablen auslesen
204
205 self.get_defined_vars()
206
207 # Variablen ersetzen
208
209 vars = self.get_var()
210
211 if vars is not None and len(vars) > 0:
212 self.parse_if_blocks()
213 self.replace_vars()
214
215 # {TRIM}-Bloecke entfernen
216
217 self.parse_trim_blocks()
218
219 # parse_old()
220 #
221 # In der Template definierte Variablen auslesen, Variablen
222 # ersetzen, {IF}- und {TRIM}-Bloecke parsen
223 # (alte Methode)
224 #
225 # Parameter: -nichts-
226 #
227 # Rueckgabe: -nichts- (Template-Objekt wird modifiziert)
228
229 def parse_old(self):
230 # Zuerst die Schleifen parsen
231
232 if self.loop_vars and self.loop_vars.keys():
233 loops = self.loop_vars.keys()
234
235 for loop in loops:
236 self.parse_loop(loop)
237
238 # Normale Variablen durchgehen
239
240 if self.get_var():
241 vars = self.get_var()
242
243 for var in vars:
244 val = self.get_var(var)
245
246 self.parse_if_block(var,val)
247
248 if type(val) is list:
249 self.fillin_array(var,val)
250 else:
251 self.fillin(var,val)
252
253 # Jetzt dasselbe mit denen, die direkt in der Template-Datei definiert
254 # sind, machen. Ich weiss, dass das eine ziemlich unsaubere Loesung ist,
255 # aber es funktioniert
256
257 self.get_defined_vars()
258
259 for var in self.defined_vars:
260 val = self.get_var(var)
261
262 self.parse_if_block(var,val)
263 self.fillin(var,val)
264
265 # {TRIM}-Bloecke entfernen
266
267 self.parse_trim_blocks()
268
269 # fillin()
270 #
271 # Variablen durch Text ersetzen
272 #
273 # Parameter: 1. Variable zum Ersetzen
274 # 2. Text, durch den die Variable ersetzt werden soll
275 #
276 # Rueckgabe: -nichts- (Template-Objekt wird modifiziert)
277
278 def fillin(self,var,text):
279 if text is None: text = ''
280
281 template = self.get_template();
282 template = template.replace('{'+str(var)+'}',str(text));
283
284 self.set_template(template);
285
286 # fillin_array()
287 #
288 # Variable durch Array ersetzen
289 #
290 # Parameter: 1. Variable zum Ersetzen
291 # 2. Array, durch das die Variable ersetzt werden soll
292 # 3. Zeichenkette, mit der das Array verbunden werden soll
293 # (Standard: '')
294 #
295 # Rueckgabe: -nichts- (Template-Objekt wird modifiziert)
296
297 def fillin_array(self,var,array,glue=''):
298 self.fillin(var,str(glue).join(array))
299
300 # replace_vars()
301 #
302 # Variablen eine nach der anderen ersetzen. Sollte in einer Variable eine
303 # andere Variable auftauchen, so wird diese nicht ersetzt.
304 #
305 # Parameter: Array mit zu parsenden Variablen (optional)
306 #
307 # Rueckgabe: -nichts- (Template-Objekt wird modifiziert)
308
309 def replace_vars(self,valid=None):
310 template = self.get_template()
311
312 if valid is None:
313 valid_vars = self.get_var()
314 else:
315 valid_vars = valid
316
317 x = 0
318
319 while x < len(template):
320 if template[x] == '{':
321 for var in valid_vars:
322 # Pruefen, ob hier eine gueltige Variable beginnt
323
324 if template[x+1:x+len(var)+2] == var + '}':
325 if type(self.get_var(var)) is list:
326 content = ''.join(self.get_var(var))
327 else:
328 # Muss es nochmal zum String machen
329 # Hilft gegen den "ordinal not in range(128)"-Fehler
330 # Habe aber keine Ahnung, welche neuen Probleme das verursachen koennte
331 # Bin gespannt...
332 content = str(self.get_var(var))
333
334 if content is None: content = ''
335
336 # Daten vor und nach der Variable
337
338 pre = template[0:x]
339 post = template[len(pre)+2+len(var):]
340
341 # Alles neu zusammensetzen
342
343 template = pre + content + post
344
345 # Zaehler aendern
346
347 x = len(pre + content) - 1
348
349 x += 1
350
351 self.set_template(template)
352
353 # to_file()
354 #
355 # Template in Datei schreiben
356 #
357 # Parameter: Datei-Handle
358 #
359 # Rueckgabe: Status-Code (Boolean)
360
361 def to_file(self,handle):
362 return handle.write(self.get_template())
363
364 # reset()
365 #
366 # Den gesicherten Stand des Template-Textes wiederherstellen
367 #
368 # Parameter: -nichts-
369 #
370 # Rueckgabe: -nichts- (Template-Objekt wird modifiziert)
371
372 def reset(self):
373 self.template = self.original
374
375 # save_state()
376 #
377 # Aktuellen Stand des Template-Textes sichern
378 # (alte Sicherung wird ueberschrieben)
379 #
380 # Parameter: -nichts-
381 #
382 # Rueckgabe: -nichts- (Template-Objekt wird modifiziert)
383
384 def save_state(self):
385 self.original = self.template
386
387 # parse_loop()
388 #
389 # Eine Schleife parsen
390 #
391 # Parameter: Name der Schleife
392 #
393 # Rueckgabe: -nichts- (Template-Objekt wird modifiziert)
394
395 def parse_loop(self,name):
396 template = self.get_template()
397 if template.find('{LOOP '+name+'}') == -1: return
398
399 offset = 0
400 name_len = len(name)
401
402 while template.find('{LOOP '+name+'}',offset) != -1:
403 begin = template.find('{LOOP '+name+'}',offset)
404
405 if template.find('{ENDLOOP}',begin+6+name_len) != -1:
406 end = template.find('{ENDLOOP}',begin+6+name_len)
407
408 block = template[begin:end+9]
409 content = block[name_len+7:-9]
410
411 parsed_block = ''
412
413 x = 0
414
415 while x < len(self.loop_vars[name]):
416 loop_data = self.loop_vars[name][x]
417 loop_vars = loop_data.keys()
418
419 ctpl = Template()
420 ctpl.set_template(content)
421
422 for loop_var in loop_vars:
423 ctpl.set_var(name+'.'+loop_var,loop_data[loop_var])
424
425 if self.old_parsing:
426 ctpl.parse_old()
427 else:
428 ctpl.parse()
429
430 parsed_block += ctpl.get_template()
431
432 del(ctpl)
433 x += 1
434
435 template = template.replace(block,parsed_block)
436 offset = begin+len(parsed_block)
437
438 else:
439 break
440
441 self.set_template(template)
442
443 # get_defined_vars()
444 #
445 # In der Template-Datei definierte Variablen auslesen
446 #
447 # Parameter: -nichts-
448 #
449 # Rueckgabe: -nichts- (Template-Objekt wird modifiziert)
450
451 def get_defined_vars(self):
452 template = self.get_template()
453 if template.find('{DEFINE ') == -1: return
454
455 offset = 0
456
457 while template.find('{DEFINE ',offset) != -1:
458 begin = template.find('{DEFINE ',offset)+8
459 offset = begin
460
461 name = ''
462 content = ''
463
464 var_open = 0
465 name_found = 0
466 define_block = 0
467
468 x = begin
469
470 while x < len(template):
471 if template[x] == '\012' or template[x] == '\015':
472 # Wenn in einem {DEFINE}-Block ein Zeilenumbruch gefunden wird,
473 # brechen wir mit dem Parsen des Blockes ab
474
475 break
476
477 if var_open == 1:
478 if template[x] == '"':
479 # Der Inhalt der Variable ist hier zu Ende
480
481 var_open = 0
482
483 if template[x+1] == '}':
484 # Hier ist der Block zu Ende
485
486 if self.get_var(name) is None:
487 # Die Variable wird nur gesetzt, wenn sie nicht bereits gesetzt ist
488
489 self.set_var(name,content)
490 self.defined_vars.append(name)
491
492 # {DEFINE}-Block entfernen
493
494 pre = template[0:begin-8]
495 post = template[x+2:]
496
497 template = pre+post
498
499 # Fertig!
500
501 offset = len(pre)
502 break
503
504 elif template[x] == '\\':
505 # Ein Backslash wurde gefunden, er dient zum Escapen von Zeichen
506
507 if template[x+1] == 'n':
508 # "\n" in Zeilenumbrueche umwandeln
509
510 content += "\n"
511 else:
512 content += template[x+1]
513
514 x += 1
515
516 else:
517 content += template[x]
518
519 else:
520 if name_found == 1:
521 if var_open == 0:
522 if template[x] == '"':
523 var_open = 1
524 else:
525 break
526
527 else:
528 # Variablennamen auslesen
529
530 if template[x] == '}' and name != '':
531 # Wir haben einen {DEFINE}-Block
532
533 name_found = 1
534 define_found = 1
535
536 # Alles ab hier sollte mit dem Teil verbunden werden, der das
537 # {DEFINE} in einer Zeile verarbeitet
538
539 # Der Parser fuer {DEFINE}-Bloecke ist nicht rekursiv, was auch
540 # nicht noetig sein sollte
541
542 if template.find('{ENDDEFINE}',x) != -1:
543 end = template.find('{ENDDEFINE}',x)
544 x += 1
545
546 content = template[x:end]
547
548 if self.get_var(name) is None:
549 # Die Variable wird nur gesetzt, wenn sie nicht bereits gesetzt ist
550
551 self.set_var(name,content)
552 self.defined_vars.append(name)
553
554 pre = template[0:begin-8]
555 post = template[end+11:]
556
557 template = pre + post
558
559 # Fertig!
560
561 offset = len(pre)
562 break
563
564 else:
565 break
566
567 elif template[x] != ' ':
568 name += template[x]
569
570 elif template[x] != '':
571 name_found = 1
572
573 else:
574 break
575
576 x += 1
577
578 self.set_template(template)
579
580 # parse_if_block()
581 #
582 # IF-Bloecke verarbeiten
583 #
584 # Parameter: 1. Name des IF-Blocks (das, was nach dem IF steht)
585 # 2. Status-Code (true => Inhalt anzeigen
586 # false => Inhalt nicht anzeigen
587 #
588 # Rueckgabe: -nichts- (Template-Objekt wird modifiziert)
589
590 def parse_if_block(self,name,state,no_negate=0):
591 name = str(name)
592 template = self.get_template()
593
594 count = 0;
595
596 while template.find('{IF '+name+'}') >= 0:
597 count += 1
598
599 start = template.find('{IF '+name+'}')
600 tpl_tmp = template[start:]
601 splitted = tpl_tmp.split('{ENDIF}')
602
603 block = '' # Kompletter bedingter Block
604 ifs = 0 # IF-Zaehler (wird fuer jedes IF erhoeht und fuer jedes ENDIF erniedrigt)
605
606 # {IF}
607
608 x = 0
609
610 while x < len(splitted):
611 # Verschachtelungsfehler abfangen
612 if x == len(splitted)-1: raise TplClassIFNestingError(self.file,name,count)
613
614 ifs += splitted[x].count('{IF ') # Zum Zaehler jedes Vorkommen von IF hinzuzaehlen
615 ifs -= 1 # Zaehler um 1 erniedrigen
616 block += splitted[x]+'{ENDIF}' # Daten zum Block hinzufuegen
617
618 x += 1
619
620 if ifs == 0:
621 # Zaehler wieder 0, also haben wir das Ende des IF-Blocks gefunden :-))
622 break
623
624 if_block = block[len(name)+5:-7] # Alles zwischen {IF} und {ENDIF}
625
626 # {ELSE}
627
628 else_block = '' # Alles ab {ELSE}
629 ifs = 0 # IF-Zaehler
630
631 splitted = if_block.split('{ELSE}');
632
633 x = 0
634
635 while x < len(splitted):
636 ifs += splitted[x].count('{IF ') # Zum Zaehler jedes Vorkommen von IF hinzuzaehlen
637 ifs -= splitted[x].count('{ENDIF}') # Vom Zaehler jedes Vorkommen von ENDIF abziehen
638
639 x += 1
640
641 if ifs == 0:
642 # Zaehler 0, also haben wir das Ende des IF-Abschnitts gefunden
643
644 # Aus dem Rest den ELSE-Block zusammenbauen
645
646 y = x
647
648 while y < len(splitted):
649 else_block += '{ELSE}'+splitted[y]
650 y += 1
651
652 if else_block:
653 if_block = if_block[0:len(if_block)-len(else_block)]
654 else_block = else_block[6:]
655
656 break
657
658 if state:
659 replacement = if_block
660 else:
661 replacement = else_block
662
663 template = template.replace(block,replacement)
664
665 self.set_template(template)
666
667 # Evtl. verneinte Form parsen
668
669 if not no_negate:
670 self.parse_if_block('!'+name,not state,1)
671
672 # parse_if_blocks()
673 #
674 # IF-Bloecke zu allen definierten Variablen verarbeiten
675 #
676 # Parameter: Array mit zu verarbeitenden IF-Bloecken (optional)
677 #
678 # Rueckgabe: -nichts- (Template-Objekt wird modifiziert)
679
680 def parse_if_blocks(self,valid=None):
681 if valid is None:
682 valid_vars = self.get_var()
683 else:
684 valid_vars = valid
685
686 for valid_var in valid_vars:
687 self.parse_if_block(valid_var,self.get_var(valid_var))
688
689 # parse_trim_blocks()
690 #
691 # {TRIM}-Bloecke parsen
692 #
693 # Dieser Parser ist nicht rekursiv, was auch nicht
694 # noetig sein sollte.
695 #
696 # Parameter: -nichts-
697 #
698 # Rueckgabe: -nichts- (Template-Objekt wird modifiziert)
699
700 def parse_trim_blocks(self):
701 template = self.get_template()
702 if template.find('{TRIM}') == -1: return
703
704 offset = 0
705
706 while template.find('{TRIM}',offset) >= 0:
707 begin = template.find('{TRIM}',offset)
708
709 if template.find('{ENDTRIM}',begin+6) >= 0:
710 end = template.find('{ENDTRIM}',begin+6)
711
712 block = template[begin:end+9]
713 content = block[6:-9]
714
715 trimmed = content.strip()
716
717 template = template.replace(block,trimmed)
718
719 offset = begin+len(trimmed)
720 else:
721 break
722
723 self.set_template(template)
724
725 # parse_condtag()
726 #
727 # Bedingungstags in einem Vorlagentext verarbeiten
728 #
729 # Parameter: 1. Tagname
730 # 2. Status-Code (true => Tag-Inhalt anzeigen
731 # false => Tag-Inhalt nicht anzeigen
732 #
733 # Rueckgabe: -nichts- (Template-Objekt wird modifiziert)
734
735 def parse_condtag(self,condtag,state):
736 condtag = str(condtag)
737 template = self.get_template()
738
739 while template.find('<'+condtag+'>') >= 0:
740 start = template.find('<'+condtag+'>') # Beginn des Blocks
741 end = template.find('</'+condtag+'>')+len(condtag)+3 # Ende des Blocks
742
743 extract = template[start:end] # Kompletten Bedingungsblock extrahieren...
744
745 if state:
746 replacement = extract[len(condtag)+2:0-len(condtag)-3]
747 else:
748 replacement = ''
749
750 template = template.replace(extract,replacement) # Block durch neue Daten ersetzen
751
752 self.set_template(template)
753
754 # parse_includes()
755 #
756 # {INCLUDE}-Anweisungen verarbeiten
757 #
758 # Parameter: -nichts-
759 #
760 # Rueckgabe: -nichts- (Template-Objekt wird modifiziert)
761
762 def parse_includes(self):
763 template = self.get_template()
764 if template.find('{INCLUDE ') == -1: return
765
766 offset = 0
767
768 while template.find('{INCLUDE ',offset) >= 0:
769 begin = template.find('{INCLUDE ',offset)
770
771 start = begin+9
772 offset = start
773 long = 0
774
775 if template[start] == '"':
776 long = 1
777 start += 1
778
779 file = ''
780 skip = 0
781
782 x = start
783
784 while x < len(template):
785 if template[x] == '\012' or template[x] == '\015':
786 skip = 1
787 break
788 elif long == 0 and template[x] == ' ':
789 skip = 1
790 break
791 elif long == 1 and template[x] == '"':
792 if template[x+1] != '}': skip = 1
793 break
794 elif long == 0 and template[x] == '}':
795 break
796 else:
797 file += template[x]
798
799 x += 1
800
801 if skip == 1: continue
802
803 if file != '':
804 filepath = file
805
806 if not os.path.isabs(file):
807 dir = os.path.dirname(self.file)
808 if not dir: dir = '.'
809 filepath = os.path.normpath(dir+'/'+file)
810
811 if os.path.isfile(filepath):
812 inc = Template()
813 inc.read_file(file)
814
815 if long == 1: end = start + len(file) + 2
816 else: end = start + len(file) + 1
817
818 pre = template[0:begin]
819 post = template[end:]
820
821 template = pre+inc.get_template()+post
822 offset = len(pre)+len(inc.get_template())
823
824 del(inc)
825
826 self.set_template(template)
827
828 # Klasse zum Erzeugen des Fehlers bei falsch verschachtelten
829 # {IF}-Bloecken
830
831 class TplClassIFNestingError:
832
833 def __init__(self,file,name,count):
834 self.file = file
835 self.name = name
836 self.count = count
837
838 def __str__(self):
839 return 'Nesting error found while parsing IF block "'+self.name+'" nr. '+str(self.count)+' in template file "'+self.file+'"'
840
841 #
842 ### Ende ###

patrick-canterino.de