User friendly template class targeted towards Web-page usage and optimized for speed and efficiency.
Tags can be inserted in a template HTML file in a non-intrusive way, by using specially formatted comment strings. Therefore, the template-file can be viewed in a browser, even with prototype data embedded in it, which will later be replaced by dynamic content. Also, webdesigners can continue to work on the template and upload it without further modification.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 | # WebpageTemplate.py: User-friendly template class targeted toward Web-page usage
# and optimized for speed and efficiency.
#
# Copyright (C) 2008-2010 David Gaarenstroom <david.gaarenstroom@gmail.com>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
"""
User friendly template class targeted towards Web-page usage and optimized for
speed and efficiency.
Tags can be inserted in a template HTML file in a non-intrusive way, by using
specially formatted comment strings. Therefore the template-file can be viewed
in a browser, even with prototype data embedded in it, which will later be
replaced by dynamic content.
Also, webdesigners can continue to work on the template and upload it without
modification.
For example:
---- template.html ----
<html>
<head><title><!-- start TITLE -->This is a default title<!-- end TITLE --></title></head>
...
---- template.html ----
Load this template using:
>>> template = WebpageTemplate.WebpageTemplate("template.html")
>>> template['TITLE'] = "Try me..."
>>> print str(template)
<html>
<head><title>Try me...</title></head>
...
"""
class WebpageTemplate:
"""
Load a template file and substitute tags in it.
The syntax for the tags is:
"<!-- start " <tagname> " -->"
<an optional default string can be placed in between>
"<!-- end " <tagname> " -->"
This way a graphics designer can create a working HTML file
demonstrating what the page should look like, while the dynamic
content markers do not interfere with this. Code can go back
and forth between designer and programmer without modifications.
Templates can be copied into new instances with some tags
permanently replaced. This way, site-wide tags only have to
be substituted once, while URL or session specific tags can
be replaced per copied instance. This saves some extra work when
serving lots of pages simultaneously.
"""
import re
markup = re.compile(
r'(?P<starttag>\<!--\s*start\s+(?P<tagname>\w+)\s*--\>)'
r'(?P<enclosed>.*?)'
r'(?P<endtag>\<!--\s*end\s+(?P=tagname)\s*--\>)'
, re.DOTALL|re.IGNORECASE)
def __init__(self, filename, basetemplate = None, tags = None):
"""
filename -- filename of the template
basetemplate -- used to copy one instance to another
tags -- any predefined tags already known
"""
self.strings = basetemplate or []
self.tags = tags or {}
if filename:
filetemplate = open(filename, 'r').read()
# Iterate over all template tag matches
startoffset = 0
for match in self.markup.finditer(filetemplate):
# Everything from the last match up to this match is
# regular text
self.strings.append(\
filetemplate[startoffset:match.start('starttag')])
# Retrieve the tagname from the match
tagname = match.group('tagname')
# if there a value already defined for this tag, use it.
# (either the tag is used more than once or this instance
# was copied from another instance.) Otherwise use the
# string between starttag and endtag as default value
if not tagname in self.tags:
self.tags[tagname] = match.group('enclosed')
# Add the tagname to the strings list. Every second string
# is a tagname
self.strings.append(tagname)
startoffset = match.end('endtag')
# Add the remainder as a whole
self.strings.append(filetemplate[startoffset:])
def __contains__(self, k):
return k in self.tags
def __setitem__(self, k, v):
if not k in self.tags:
raise KeyError, k
self.tags[ k ] = v
def __getitem__(self, k):
return self.tags[ k ]
def __iter__(self):
"""
Return a strings iterator for the current template,
alternating between the static string parts and the current
tag-values.
Combined together this is the template content.
"""
stringsiter = iter(self.strings)
yield stringsiter.next()
while True:
yield self.tags[stringsiter.next()]
yield stringsiter.next()
def __str__(self):
"""
Return a string representation of the current template, using
the current tag-values as replacement text.
"""
return ''.join(self.__iter__())
def publishiter(self, mydict = None):
"""
Return an iterator usable to publish the template with its tags
replaced, and allow for some last minute (stateless) changes.
Arguments:
mydict -- optional dictionary with tags:value that
do not apply to the template state. In other
words, you can supply tag-values with higher
precedence than template instance wide
defined/implied tags.
"""
tags = dict(self.tags)
if mydict:
tags.update(mydict)
stringsiter = iter(self.strings)
yield stringsiter.next()
while True:
yield tags[stringsiter.next()]
yield stringsiter.next()
def publish(self, mydict = None):
"""
Publish the template with its tags replaced, and allow for some
last minute changes. These changes will not be added to the
instance.
Arguments:
mydict -- optional dictionary with tags:value with
higher precedence than already supplied tags.
"""
return ''.join(self.publishiter(mydict))
def copy(self, striptags = None):
"""
copy WebpageTemplate to a new instance, optionally stripping away
some tags that no longer should nor will be overwritten.
Arguments:
striptags -- optional dictionary, list or tuple containing
tags that should be stripped away.
"""
if not striptags:
return self.__class__(None, self.strings[:], dict(self.tags))
else:
stringbuf = []
tags = {}
previous_string = self.strings[0]
for index in range(1, len(self.strings), 2):
tagname = self.strings[index]
next_string = self.strings[index + 1] or ''
if tagname in striptags:
if isinstance(striptags, dict):
previous_string = previous_string + \
striptags[tagname] + next_string
else:
previous_string = previous_string + \
self.tags[tagname] + next_string
else:
stringbuf.append(previous_string)
tags[tagname] = self.tags[tagname]
stringbuf.append(tagname)
previous_string = next_string
stringbuf.append(previous_string)
return self.__class__(None, stringbuf, tags)
|
For example:
basiclayout = WebpageTemplate('./template.html')
basiclayout['Company'] = "J.Q. Public"
basiclayout['Title'] = "Welcome to J.Q. Public"
class about():
# Use a template copy as static class variable
aboutpage = basiclayout.copy({ 'TITLE': "About us" })
# These variables stay the same for every about page
aboutpage['Content'] = aboutustxt
def GET(self):
return str(self.aboutpage)
class main():
# Use a template copy as static class variable
mainpage = basiclayout.copy()
# These variables stay the same for every main page
mainpage['Content'] = maintxt
def GET(self, name):
# Some last minute changes
return self.mainpage.publish({'Counter' : get_current_counter(), 'name': name})