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

patrick-canterino.de