Welcome, guest | Sign In | My Account | Store | Cart

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.

Python, 212 lines
  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})