From: Patrick Canterino Date: Thu, 18 Apr 2013 22:02:29 +0000 (+0000) Subject: template.py nach template27.py kopiert X-Git-Url: https://git.p6c8.net/template-class.git/commitdiff_plain/195564a3f4963db13148e06b3282da6c49441050?ds=sidebyside template.py nach template27.py kopiert template.py wird die Python 3-Version template27.py fuer Python 2.7 --- diff --git a/template27.py b/template27.py new file mode 100644 index 0000000..05c2345 --- /dev/null +++ b/template27.py @@ -0,0 +1,840 @@ + +# +# Template (Version 2.5) +# +# Klasse zum Parsen von Templates +# +# Autor: Patrick Canterino +# Letzte Aenderung: 25.11.2011 +# +# Copyright (C) 2002-2011 Patrick Canterino +# +# Diese Datei kann unter den Bedingungen der "Artistic License 2.0" +# weitergegeben und / oder veraendert werden. +# Siehe: +# http://www.opensource.org/licenses/artistic-license-2.0 +# + +import os.path +import sys + +class Template: + + # __init__() + # + # Konstruktor + # + # Parameter: -keine- + # + # Rueckgabe: -nichts- + + def __init__(self): + self.file = '' + self.template = '' + self.original = '' + self.old_parsing = 0 + self.vars = {} + self.defined_vars = [] + self.loop_vars = {} + + # get_template() + # + # Kompletten Vorlagentext zurueckgeben + # + # Parameter: -keine- + # + # Rueckgabe: Kompletter Vorlagentext (String) + + def get_template(self): + return str(self.template) + + # set_template() + # + # Kompletten Vorlagentext aendern + # + # Parameter: Vorlagentext + # + # Rueckgabe: -nichts- (Template-Objekt wird modifiziert) + + def set_template(self,template): + self.template = str(template) + + # add_text() + # + # Vorlagentext ans Template-Objekt anhaengen + # + # Parameter: Vorlagentext + # + # Rueckgabe: -nichts- (Template-Objekt wird modifiziert) + + def add_text(self,text): + self.set_template(self.get_template()+str(text)) + + # read_file() + # + # Einlesen einer Vorlagendatei und {INCLUDE}-Anweisungen ggf. verarbeiten + # (Text wird an bereits vorhandenen Text angehaengt) + # + # Parameter: 1. Datei zum Einlesen + # 2. Status-Code (Boolean): + # true => {INCLUDE}-Anweisungen nicht verarbeiten + # false => {INCLUDE}-Anweisungen verarbeiten (Standard) + # + # Rueckgabe: -nichts- (Template-Objekt wird modifiziert) + + def read_file(self,file,not_include=0): + self.file = file + + fp = open(file,'r') + content = fp.read() + fp.close() + + self.add_text(content) + self.save_state() + + if not not_include: self.parse_includes() + + # set_var() + # + # Wert einer Variable setzen + # + # Parameter: 1. Name der Variable + # 2. Wert, den die Variable erhalten soll + # + # Rueckgabe: -nichts- (Template-Objekt wird modifiziert) + + def set_var(self,var,content): + self.vars[var] = content + + # get_var() + # + # Wert einer Variable zurueckgeben + # + # Parameter: (optional) Variablenname + # + # Rueckgabe: Wert der Variable; + # wenn die Variable nicht existiert, false; + # wenn kein Variablenname angegeben wurde, wird ein + # Array mit den Variablennamen zurueckgegeben + + def get_var(self,var=None): + if var is not None: + if self.vars.has_key(var): + return self.vars[var] + else: + return None + else: + return self.vars.keys() + + # set_vars() + # + # Komplettes Variablen-Array mit einem anderen Array ueberschreiben + # + # Parameter: Array + # + # Rueckgabe: -nichts- (Template-Objekt wird modifiziert) + + def set_vars(self,vars): + self.vars = vars + + # add_vars() + # + # Zum bestehenden Variablen-Array weitere Variablen in Form eines Arrays + # hinzufuegen + # + # Parameter: Array + # + # Rueckgabe: -nichts- (Template-Objekt wird modifiziert) + + def add_vars(self,vars): + self.vars.update(vars) + + # set_loop_data() + # + # Daten fuer eine Schleife setzen + # + # Parameter: 1. Name der Schleife + # 2. Array mit den Dictionaries mit den Variablen fuer + # die Schleifendurchgaenge + # + # Rueckgabe: -nichts- (Template-Objekt wird modifiziert) + + def set_loop_data(self,loop,data): + self.loop_vars[loop] = data + + # add_loop_data() + # + # Daten fuer einen Schleifendurchgang hinzufuegen + # + # Parameter: 1. Name der Schleife + # 2. Dictionary mit den Variablen fuer den + # Schleifendurchgang + # + # Rueckgabe: -nichts- (Template-Objekt wird modifiziert) + + def add_loop_data(self,loop,data): + if self.loop_vars.has_key(loop) and type(self.loop_vars[loop]) is list: + self.loop_vars[loop].append(data) + else: + self.loop_vars[loop] = [data] + + # parse() + # + # In der Template definierte Variablen auslesen, Variablen + # ersetzen, {IF}- und {TRIM}-Bloecke parsen + # + # Parameter: -nichts- + # + # Rueckgabe: -nichts- (Template-Objekt wird modifiziert) + + def parse(self): + if self.old_parsing: return self.parse_old() + + # Zuerst die Schleifen parsen + + if self.loop_vars and self.loop_vars.keys(): + loops = self.loop_vars.keys() + + for loop in loops: + self.parse_loop(loop) + + # In Template-Datei definierte Variablen auslesen + + self.get_defined_vars() + + # Variablen ersetzen + + vars = self.get_var() + + if vars is not None and type(vars) is list: + self.parse_if_blocks() + self.replace_vars() + + # {TRIM}-Bloecke entfernen + + self.parse_trim_blocks() + + # parse_old() + # + # In der Template definierte Variablen auslesen, Variablen + # ersetzen, {IF}- und {TRIM}-Bloecke parsen + # (alte Methode) + # + # Parameter: -nichts- + # + # Rueckgabe: -nichts- (Template-Objekt wird modifiziert) + + def parse_old(self): + # Zuerst die Schleifen parsen + + if self.loop_vars and self.loop_vars.keys(): + loops = self.loop_vars.keys() + + for loop in loops: + self.parse_loop(loop) + + # Normale Variablen durchgehen + + if self.get_var(): + vars = self.get_var() + + for var in vars: + val = self.get_var(var) + + self.parse_if_block(var,val) + + if type(val) is list: + self.fillin_array(var,val) + else: + self.fillin(var,val) + + # Jetzt dasselbe mit denen, die direkt in der Template-Datei definiert + # sind, machen. Ich weiss, dass das eine ziemlich unsaubere Loesung ist, + # aber es funktioniert + + self.get_defined_vars() + + for var in self.defined_vars: + val = self.get_var(var) + + self.parse_if_block(var,val) + self.fillin(var,val) + + # {TRIM}-Bloecke entfernen + + self.parse_trim_blocks() + + # fillin() + # + # Variablen durch Text ersetzen + # + # Parameter: 1. Variable zum Ersetzen + # 2. Text, durch den die Variable ersetzt werden soll + # + # Rueckgabe: -nichts- (Template-Objekt wird modifiziert) + + def fillin(self,var,text): + if text is None: text = '' + + template = self.get_template(); + template = template.replace('{'+str(var)+'}',str(text)); + + self.set_template(template); + + # fillin_array() + # + # Variable durch Array ersetzen + # + # Parameter: 1. Variable zum Ersetzen + # 2. Array, durch das die Variable ersetzt werden soll + # 3. Zeichenkette, mit der das Array verbunden werden soll + # (Standard: '') + # + # Rueckgabe: -nichts- (Template-Objekt wird modifiziert) + + def fillin_array(self,var,array,glue=''): + self.fillin(var,str(glue).join(array)) + + # replace_vars() + # + # Variablen eine nach der anderen ersetzen. Sollte in einer Variable eine + # andere Variable auftauchen, so wird diese nicht ersetzt. + # + # Parameter: Array mit zu parsenden Variablen (optional) + # + # Rueckgabe: -nichts- (Template-Objekt wird modifiziert) + + def replace_vars(self,valid=None): + template = self.get_template() + + if valid is None: + valid_vars = self.get_var() + else: + valid_vars = valid + + x = 0 + + while x < len(template): + if template[x] == '{': + for var in valid_vars: + # Pruefen, ob hier eine gueltige Variable beginnt + + if template[x+1:x+len(var)+2] == var + '}': + if type(self.get_var(var)) is list: + content = ''.join(self.get_var(var)) + else: + # Muss es nochmal zum String machen + # Hilft gegen den "ordinal not in range(128)"-Fehler + # Habe aber keine Ahnung, welche neuen Probleme das verursachen koennte + # Bin gespannt... + content = str(self.get_var(var)) + + if content is None: content = '' + + # Daten vor und nach der Variable + + pre = template[0:x] + post = template[len(pre)+2+len(var):] + + # Alles neu zusammensetzen + + template = pre + content + post + + # Zaehler aendern + + x = len(pre + content) - 1 + + x += 1 + + self.set_template(template) + + # to_file() + # + # Template in Datei schreiben + # + # Parameter: Datei-Handle + # + # Rueckgabe: Status-Code (Boolean) + + def to_file(self,handle): + return handle.write(self.get_template()) + + # reset() + # + # Den gesicherten Stand des Template-Textes wiederherstellen + # + # Parameter: -nichts- + # + # Rueckgabe: -nichts- (Template-Objekt wird modifiziert) + + def reset(self): + self.template = self.original + + # save_state() + # + # Aktuellen Stand des Template-Textes sichern + # (alte Sicherung wird ueberschrieben) + # + # Parameter: -nichts- + # + # Rueckgabe: -nichts- (Template-Objekt wird modifiziert) + + def save_state(self): + self.original = self.template + + # parse_loop() + # + # Eine Schleife parsen + # + # Parameter: Name der Schleife + # + # Rueckgabe: -nichts- (Template-Objekt wird modifiziert) + + def parse_loop(self,name): + template = self.get_template() + if template.find('{LOOP '+name+'}') == -1: return + + offset = 0 + name_len = len(name) + + while template.find('{LOOP '+name+'}',offset) != -1: + begin = template.find('{LOOP '+name+'}',offset) + + if template.find('{ENDLOOP}',begin+6+name_len) != -1: + end = template.find('{ENDLOOP}',begin+6+name_len) + + block = template[begin:end+9] + content = block[name_len+7:-9] + + parsed_block = '' + + x = 0 + + while x < len(self.loop_vars[name]): + loop_data = self.loop_vars[name][x] + loop_vars = loop_data.keys() + + ctpl = Template() + ctpl.set_template(content) + + for loop_var in loop_vars: + ctpl.set_var(name+'.'+loop_var,loop_data[loop_var]) + + if self.old_parsing: + ctpl.parse_old() + else: + ctpl.parse() + + parsed_block += ctpl.get_template() + + del(ctpl) + x += 1 + + template = template.replace(block,parsed_block) + offset = begin+len(parsed_block) + + else: + break + + self.set_template(template) + + # get_defined_vars() + # + # In der Template-Datei definierte Variablen auslesen + # + # Parameter: -nichts- + # + # Rueckgabe: -nichts- (Template-Objekt wird modifiziert) + + def get_defined_vars(self): + template = self.get_template() + if template.find('{DEFINE ') == -1: return + + offset = 0 + + while template.find('{DEFINE ',offset) != -1: + begin = template.find('{DEFINE ',offset)+8 + offset = begin + + name = '' + content = '' + + var_open = 0 + name_found = 0 + define_block = 0 + + x = begin + + while x < len(template): + if template[x] == '\012' or template[x] == '\015': + # Wenn in einem {DEFINE}-Block ein Zeilenumbruch gefunden wird, + # brechen wir mit dem Parsen des Blockes ab + + break + + if var_open == 1: + if template[x] == '"': + # Der Inhalt der Variable ist hier zu Ende + + var_open = 0 + + if template[x+1] == '}': + # Hier ist der Block zu Ende + + if self.get_var(name) is None: + # Die Variable wird nur gesetzt, wenn sie nicht bereits gesetzt ist + + self.set_var(name,content) + self.defined_vars.append(name) + + # {DEFINE}-Block entfernen + + pre = template[0:begin-8] + post = template[x+2:] + + template = pre+post + + # Fertig! + + offset = len(pre) + break + + elif template[x] == '\\': + # Ein Backslash wurde gefunden, er dient zum Escapen von Zeichen + + if template[x+1] == 'n': + # "\n" in Zeilenumbrueche umwandeln + + content += "\n" + else: + content += template[x+1] + + x += 1 + + else: + content += template[x] + + else: + if name_found == 1: + if var_open == 0: + if template[x] == '"': + var_open = 1 + else: + break + + else: + # Variablennamen auslesen + + if template[x] == '}' and name != '': + # Wir haben einen {DEFINE}-Block + + name_found = 1 + define_found = 1 + + # Alles ab hier sollte mit dem Teil verbunden werden, der das + # {DEFINE} in einer Zeile verarbeitet + + # Der Parser fuer {DEFINE}-Bloecke ist nicht rekursiv, was auch + # nicht noetig sein sollte + + if template.find('{ENDDEFINE}',x) != -1: + end = template.find('{ENDDEFINE}',x) + x += 1 + + content = template[x:end] + + if self.get_var(name) is None: + # Die Variable wird nur gesetzt, wenn sie nicht bereits gesetzt ist + + self.set_var(name,content) + self.defined_vars.append(name) + + pre = template[0:begin-8] + post = template[end+11:] + + template = pre + post + + # Fertig! + + offset = len(pre) + break + + else: + break + + elif template[x] != ' ': + name += template[x] + + elif template[x] != '': + name_found = 1 + + else: + break + + x += 1 + + self.set_template(template) + + # parse_if_block() + # + # IF-Bloecke verarbeiten + # + # Parameter: 1. Name des IF-Blocks (das, was nach dem IF steht) + # 2. Status-Code (true => Inhalt anzeigen + # false => Inhalt nicht anzeigen + # + # Rueckgabe: -nichts- (Template-Objekt wird modifiziert) + + def parse_if_block(self,name,state,no_negate=0): + name = str(name) + template = self.get_template() + + count = 0; + + while template.find('{IF '+name+'}') >= 0: + count += 1 + + start = template.find('{IF '+name+'}') + tpl_tmp = template[start:] + splitted = tpl_tmp.split('{ENDIF}') + + block = '' # Kompletter bedingter Block + ifs = 0 # IF-Zaehler (wird fuer jedes IF erhoeht und fuer jedes ENDIF erniedrigt) + + # {IF} + + x = 0 + + while x < len(splitted): + # Verschachtelungsfehler abfangen + if x == len(splitted)-1: raise TplClassIFNestingError(self.file,name,count) + + ifs += splitted[x].count('{IF ') # Zum Zaehler jedes Vorkommen von IF hinzuzaehlen + ifs -= 1 # Zaehler um 1 erniedrigen + block += splitted[x]+'{ENDIF}' # Daten zum Block hinzufuegen + + x += 1 + + if ifs == 0: + # Zaehler wieder 0, also haben wir das Ende des IF-Blocks gefunden :-)) + break + + if_block = block[len(name)+5:-7] # Alles zwischen {IF} und {ENDIF} + + # {ELSE} + + else_block = '' # Alles ab {ELSE} + ifs = 0 # IF-Zaehler + + splitted = if_block.split('{ELSE}'); + + x = 0 + + while x < len(splitted): + ifs += splitted[x].count('{IF ') # Zum Zaehler jedes Vorkommen von IF hinzuzaehlen + ifs -= splitted[x].count('{ENDIF}') # Vom Zaehler jedes Vorkommen von ENDIF abziehen + + x += 1 + + if ifs == 0: + # Zaehler 0, also haben wir das Ende des IF-Abschnitts gefunden + + # Aus dem Rest den ELSE-Block zusammenbauen + + y = x + + while y < len(splitted): + else_block += '{ELSE}'+splitted[y] + y += 1 + + if else_block: + if_block = if_block[0:len(if_block)-len(else_block)] + else_block = else_block[6:] + + break + + if state: + replacement = if_block + else: + replacement = else_block + + template = template.replace(block,replacement) + + self.set_template(template) + + # Evtl. verneinte Form parsen + + if not no_negate: + self.parse_if_block('!'+name,not state,1) + + # parse_if_blocks() + # + # IF-Bloecke zu allen definierten Variablen verarbeiten + # + # Parameter: Array mit zu verarbeitenden IF-Bloecken (optional) + # + # Rueckgabe: -nichts- (Template-Objekt wird modifiziert) + + def parse_if_blocks(self,valid=None): + if valid is None: + valid_vars = self.get_var() + else: + valid_vars = valid + + for valid_var in valid_vars: + self.parse_if_block(valid_var,self.get_var(valid_var)) + + # parse_trim_blocks() + # + # {TRIM}-Bloecke parsen + # + # Dieser Parser ist nicht rekursiv, was auch nicht + # noetig sein sollte. + # + # Parameter: -nichts- + # + # Rueckgabe: -nichts- (Template-Objekt wird modifiziert) + + def parse_trim_blocks(self): + template = self.get_template() + if template.find('{TRIM}') == -1: return + + offset = 0 + + while template.find('{TRIM}',offset) >= 0: + begin = template.find('{TRIM}',offset) + + if template.find('{ENDTRIM}',begin+6) >= 0: + end = template.find('{ENDTRIM}',begin+6) + + block = template[begin:end+9] + content = block[6:-9] + + trimmed = content.strip() + + template = template.replace(block,trimmed) + + offset = begin+len(trimmed) + else: + break + + self.set_template(template) + + # parse_condtag() + # + # Bedingungstags in einem Vorlagentext verarbeiten + # + # Parameter: 1. Tagname + # 2. Status-Code (true => Tag-Inhalt anzeigen + # false => Tag-Inhalt nicht anzeigen + # + # Rueckgabe: -nichts- (Template-Objekt wird modifiziert) + + def parse_condtag(self,condtag,state): + condtag = str(condtag) + template = self.get_template() + + while template.find('<'+condtag+'>') >= 0: + start = template.find('<'+condtag+'>') # Beginn des Blocks + end = template.find('')+len(condtag)+3 # Ende des Blocks + + extract = template[start:end] # Kompletten Bedingungsblock extrahieren... + + if state: + replacement = extract[len(condtag)+2:0-len(condtag)-3] + else: + replacement = '' + + template = template.replace(extract,replacement) # Block durch neue Daten ersetzen + + self.set_template(template) + + # parse_includes() + # + # {INCLUDE}-Anweisungen verarbeiten + # + # Parameter: -nichts- + # + # Rueckgabe: -nichts- (Template-Objekt wird modifiziert) + + def parse_includes(self): + template = self.get_template() + if template.find('{INCLUDE ') == -1: return + + offset = 0 + + while template.find('{INCLUDE ',offset) >= 0: + begin = template.find('{INCLUDE ',offset) + + start = begin+9 + offset = start + long = 0 + + if template[start] == '"': + long = 1 + start += 1 + + file = '' + skip = 0 + + x = start + + while x < len(template): + if template[x] == '\012' or template[x] == '\015': + skip = 1 + break + elif long == 0 and template[x] == ' ': + skip = 1 + break + elif long == 1 and template[x] == '"': + if template[x+1] != '}': skip = 1 + break + elif long == 0 and template[x] == '}': + break + else: + file += template[x] + + x += 1 + + if skip == 1: continue + + if file != '': + filepath = file + + if not os.path.isabs(file): + dir = os.path.dirname(self.file) + if not dir: dir = '.' + filepath = os.path.normpath(dir+'/'+file) + + if os.path.isfile(filepath): + inc = Template() + inc.read_file(file) + + if long == 1: end = start + len(file) + 2 + else: end = start + len(file) + 1 + + pre = template[0:begin] + post = template[end:] + + template = pre+inc.get_template()+post + offset = len(pre)+len(inc.get_template()) + + del(inc) + + self.set_template(template) + +# Klasse zum Erzeugen des Fehlers bei falsch verschachtelten +# {IF}-Bloecken + +class TplClassIFNestingError: + + def __init__(self,file,name,count): + self.file = file + self.name = name + self.count = count + + def __str__(self): + return 'Nesting error found while parsing IF block "'+self.name+'" nr. '+str(self.count)+' in template file "'+self.file+'"' + +# +### Ende ### \ No newline at end of file