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

patrick-canterino.de