diff --git a/src/apps/config/config.h b/src/apps/config/config.h
index 6492680..7882fe1 100644
--- a/src/apps/config/config.h
+++ b/src/apps/config/config.h
@@ -17,20 +17,20 @@ void InitialStoreConfiguration();
*/
static char *bongo_manager_config =
-"{ version: 1, \n"
-" agents: [ \n"
-" { name: 'bongoqueue', pri: 3, enabled: true },\n"
-" { name: 'bongosmtp', pri: 3, enabled: true },\n"
-" { name: 'bongoantispam', pri: 5, enabled: false },\n"
-" { name: 'bongoavirus', pri: 5, enabled: true, config:'/config/avirus'},\n"
-" { name: 'bongocollector', pri: 7, enabled: true },\n"
-" { name: 'bongomailprox', pri: 7, enabled: true },\n"
-" { name: 'bongoconnmgr', pri: 2, enabled: true },\n"
-" { name: 'bongopluspack', pri: 5, enabled: true },\n"
-" { name: 'bongorules', pri: 2, enabled: true },\n"
-" { name: 'bongoimap', pri: 10, enabled: true },\n"
-" { name: 'bongopop3', pri: 10, enabled: true },\n"
-" { name: 'bongocalcmd', pri: 7, enabled: true },\n"
+"{ \"version\": 1, \n"
+" \"agents\": [ \n"
+" { \"name\": \"bongoqueue\", \"pri\": 3, \"enabled\": true },\n"
+" { \"name\": \"bongosmtp\", \"pri\": 3, \"enabled\": true },\n"
+" { \"name\": \"bongoantispam\", \"pri\": 5, \"enabled\": false },\n"
+" { \"name\": \"bongoavirus\", \"pri\": 5, \"enabled\": true, \"config\":\"/config/avirus\"},\n"
+" { \"name\": \"bongocollector\", \"pri\": 7, \"enabled\": true },\n"
+" { \"name\": \"bongomailprox\", \"pri\": 7, \"enabled\": true },\n"
+" { \"name\": \"bongoconnmgr\", \"pri\": 2, \"enabled\": true },\n"
+" { \"name\": \"bongopluspack\", \"pri\": 5, \"enabled\": true },\n"
+" { \"name\": \"bongorules\", \"pri\": 2, \"enabled\": true },\n"
+" { \"name\": \"bongoimap\", \"pri\": 10, \"enabled\": true },\n"
+" { \"name\": \"bongopop3\", \"pri\": 10, \"enabled\": true },\n"
+" { \"name\": \"bongocalcmd\", \"pri\": 7, \"enabled\": true }\n"
" ]\n"
"}\n";
diff --git a/src/libs/python/Bongo.rules b/src/libs/python/Bongo.rules
index 0647dec..d77f4b1 100644
--- a/src/libs/python/Bongo.rules
+++ b/src/libs/python/Bongo.rules
@@ -11,6 +11,7 @@ pkgpythonexternalemaildir = $(pythondir)/bongo/external/email
pkgpythonexternalemailmimedir = $(pythondir)/bongo/external/email/mime
pkgpythonexternaldateutilzoneinfodir = $(pythondir)/bongo/external/dateutil/zoneinfo
pkgpythonexternalsimplejsondir = $(pythondir)/bongo/external/simplejson
+pkgpythonexternalsimpletaldir = $(pythondir)/bongo/external/simpletal
pkgpythonexternalvobjectdir = $(pythondir)/bongo/external/vobject
pkgpythonnmapdir = $(pythondir)/bongo/nmap
pkgpythonstoredir = $(pythondir)/bongo/store
@@ -19,10 +20,11 @@ pkgpython_PYTHON := \
src/libs/python/bongo/__init__.py \
src/libs/python/bongo/CalCmd.py \
src/libs/python/bongo/cmdparse.py \
- src/libs/python/bongo/Console.py \
- src/libs/python/bongo/Contact.py \
+ src/libs/python/bongo/Console.py \
+ src/libs/python/bongo/Contact.py \
src/libs/python/bongo/BongoError.py \
src/libs/python/bongo/MDB.py \
+ src/libs/python/bongo/Template.py \
src/libs/python/bongo/Privs.py \
src/libs/python/bongo/StreamIO.py \
src/libs/python/bongo/table.py
@@ -110,6 +112,17 @@ pkgpythonexternalsimplejson_PYTHON := \
src/libs/python/bongo/external/simplejson/jsonfilter.py \
src/libs/python/bongo/external/simplejson/scanner.py
+pkgpythonexternalsimpletal_PYTHON := \
+ src/libs/python/bongo/external/simpletal/DummyLogger.py \
+ src/libs/python/bongo/external/simpletal/__init__.py \
+ src/libs/python/bongo/external/simpletal/simpleElementTree.py \
+ src/libs/python/bongo/external/simpletal/simpleTAL.py \
+ src/libs/python/bongo/external/simpletal/FixedHTMLParser.py \
+ src/libs/python/bongo/external/simpletal/sgmlentitynames.py \
+ src/libs/python/bongo/external/simpletal/simpleTALES.py \
+ src/libs/python/bongo/external/simpletal/simpleTALUtils.py
+
+
pkgpythonexternalvobject_PYTHON := \
src/libs/python/bongo/external/vobject/__init__.py \
src/libs/python/bongo/external/vobject/base.py \
diff --git a/src/libs/python/bongo/Template.py b/src/libs/python/bongo/Template.py
new file mode 100644
index 0000000..bd0e118
--- /dev/null
+++ b/src/libs/python/bongo/Template.py
@@ -0,0 +1,97 @@
+import os, sys, copy
+from bongo.external.simpletal import simpleTAL, simpleTALES
+
+class Template:
+ def __init__(self, depth=0):
+ self._context = simpleTALES.Context()
+ self._depth = depth
+ self._templatefile = None
+ self._preprocessor = TemplatePreprocessor()
+
+ def Run(self, req):
+ if self._templatefile == None:
+ raise RuntimeError
+
+ self._preprocessor.setTemplateFile(self._templatefile)
+ template = simpleTAL.compileHTMLTemplate(self._preprocessor)
+ template.expand(self._context, req)
+
+ def setVariable(self, name, obj):
+ self._context.addGlobal(name, obj)
+ self._preprocessor[name] = obj
+
+ def setTemplateFile(self, filename):
+ self._templatefile = filename
+
+ def setTemplatePath(self, path):
+ self._templatepath = path
+ self._preprocessor.setTemplatePath(path)
+
+ def setTemplateUriRoot(self, root):
+ self._templateuriroot = root
+ self._preprocessor.setUriRoot(root)
+
+class TemplatePreprocessor:
+ def __init__(self, dict={}):
+ self.dict = dict
+
+ def read(self):
+ return "%s" % self
+
+ def setTemplateFile(self, filename):
+ content = self._filecontent(filename)
+ if content != "":
+ self._template = content
+ else:
+ self._template = "
Error
No template %s
" % filename
+
+ def __str__(self):
+ # rely on string substitutions
+ return self._template % self
+
+ def __getitem__(self, key):
+ return self._process(key.split("|"))
+
+ def __setitem__(self, key, value):
+ self.dict[key] = value
+
+ def _process(self, args):
+ arg = args.pop(0)
+ if len(args) == 0:
+ if arg in self.dict:
+ return self.dict[arg]
+ elif hasattr(self, "call_" + arg) and callable(getattr(self, "call_" + arg)):
+ return getattr(self, "call_" + arg)()
+ else:
+ return "Bad attribute %s" % arg
+ else:
+ return getattr(self, "call_" + arg)(args)
+
+ def _filecontent(self, filename):
+ content = ""
+ try:
+ f = open (filename, "r")
+ content = f.read()
+ f.close()
+ except:
+ print "Couldn't open file %s" % filename
+ return content
+
+ def setTemplatePath(self, path):
+ self._templatepath = path
+
+ def setUriRoot(self, root):
+ self._uriroot = root
+
+ # template callable functions
+ def call_url(self, args):
+ # Pad out URLs with our web root, so the app can be moved in terms of
+ # URL and the links all still work
+ return self._uriroot + args[0]
+
+ def call_include(self, args):
+ # Include a copy of another template here. This is also processed.
+ template = self._templatepath + '/' + args[0]
+ pp = copy.copy(self)
+ pp.setTemplateFile(template)
+ return pp.read()
diff --git a/src/libs/python/bongo/external/simpletal/DummyLogger.py b/src/libs/python/bongo/external/simpletal/DummyLogger.py
new file mode 100644
index 0000000..b119750
--- /dev/null
+++ b/src/libs/python/bongo/external/simpletal/DummyLogger.py
@@ -0,0 +1,66 @@
+""" simpleTALES Implementation
+
+ Copyright (c) 2004 Colin Stewart (http://www.owlfish.com/)
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ 3. The name of the author may not be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+ If you make any bug fixes or feature enhancements please let me know!
+
+ Dummy logging module, used when logging (http://www.red-dove.com/python_logging.html)
+ is not installed.
+"""
+
+class DummyLogger:
+ def debug (self, *args):
+ pass
+
+ def info (self, *args):
+ pass
+
+ def warn (self, *args):
+ pass
+
+ def error (self, *args):
+ pass
+
+ def critical (self, *args):
+ pass
+
+def getLogger (*params):
+ return DummyLogger()
+
+def debug (*args):
+ pass
+
+def info (*args):
+ pass
+
+def warn (*args):
+ pass
+
+def error (*args):
+ pass
+
+def critical (*args):
+ pass
diff --git a/src/libs/python/bongo/external/simpletal/FixedHTMLParser.py b/src/libs/python/bongo/external/simpletal/FixedHTMLParser.py
new file mode 100644
index 0000000..edb2c1a
--- /dev/null
+++ b/src/libs/python/bongo/external/simpletal/FixedHTMLParser.py
@@ -0,0 +1,45 @@
+""" Fixed up HTMLParser
+
+ Copyright (c) 2005 Colin Stewart (http://www.owlfish.com/)
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ 3. The name of the author may not be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+ If you make any bug fixes or feature enhancements please let me know!
+
+
+ The classes in this module implement the TAL language, expanding
+ both XML and HTML templates.
+
+ Module Dependencies: logging, simpleTALES, simpleTALTemplates
+"""
+
+import bongo.external.simpletal
+import HTMLParser
+
+
+class HTMLParser (HTMLParser.HTMLParser):
+ def unescape(self, s):
+ # Just return the data - we don't partially unescaped data!
+ return s
+
diff --git a/src/libs/python/bongo/external/simpletal/__init__.py b/src/libs/python/bongo/external/simpletal/__init__.py
new file mode 100644
index 0000000..e5775fd
--- /dev/null
+++ b/src/libs/python/bongo/external/simpletal/__init__.py
@@ -0,0 +1 @@
+__version__ = "4.1"
diff --git a/src/libs/python/bongo/external/simpletal/sgmlentitynames.py b/src/libs/python/bongo/external/simpletal/sgmlentitynames.py
new file mode 100644
index 0000000..7bb051a
--- /dev/null
+++ b/src/libs/python/bongo/external/simpletal/sgmlentitynames.py
@@ -0,0 +1,284 @@
+""" simpleTAL Implementation
+
+ Copyright (c) 2004 Colin Stewart (http://www.owlfish.com/)
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ 3. The name of the author may not be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+ If you make any bug fixes or feature enhancements please let me know!
+
+ A dictionary of HTML entity names to their decimal value.
+"""
+
+htmlNameToUnicodeNumber = {"nbsp": 160
+ ,"iexcl": 161
+ ,"cent": 162
+ ,"pound": 163
+ ,"curren": 164
+ ,"yen": 165
+ ,"brvbar": 166
+ ,"sect": 167
+ ,"uml": 168
+ ,"copy": 169
+ ,"ordf": 170
+ ,"laquo": 171
+ ,"not": 172
+ ,"shy": 173
+ ,"reg": 174
+ ,"macr": 175
+ ,"deg": 176
+ ,"plusmn": 177
+ ,"sup2": 178
+ ,"sup3": 179
+ ,"acute": 180
+ ,"micro": 181
+ ,"para": 182
+ ,"middot": 183
+ ,"cedil": 184
+ ,"sup1": 185
+ ,"ordm": 186
+ ,"raquo": 187
+ ,"frac14": 188
+ ,"frac12": 189
+ ,"frac34": 190
+ ,"iquest": 191
+ ,"Agrave": 192
+ ,"Aacute": 193
+ ,"Acirc": 194
+ ,"Atilde": 195
+ ,"Auml": 196
+ ,"Aring": 197
+ ,"AElig": 198
+ ,"Ccedil": 199
+ ,"Egrave": 200
+ ,"Eacute": 201
+ ,"Ecirc": 202
+ ,"Euml": 203
+ ,"Igrave": 204
+ ,"Iacute": 205
+ ,"Icirc": 206
+ ,"Iuml": 207
+ ,"ETH": 208
+ ,"Ntilde": 209
+ ,"Ograve": 210
+ ,"Oacute": 211
+ ,"Ocirc": 212
+ ,"Otilde": 213
+ ,"Ouml": 214
+ ,"times": 215
+ ,"Oslash": 216
+ ,"Ugrave": 217
+ ,"Uacute": 218
+ ,"Ucirc": 219
+ ,"Uuml": 220
+ ,"Yacute": 221
+ ,"THORN": 222
+ ,"szlig": 223
+ ,"agrave": 224
+ ,"aacute": 225
+ ,"acirc": 226
+ ,"atilde": 227
+ ,"auml": 228
+ ,"aring": 229
+ ,"aelig": 230
+ ,"ccedil": 231
+ ,"egrave": 232
+ ,"eacute": 233
+ ,"ecirc": 234
+ ,"euml": 235
+ ,"igrave": 236
+ ,"iacute": 237
+ ,"icirc": 238
+ ,"iuml": 239
+ ,"eth": 240
+ ,"ntilde": 241
+ ,"ograve": 242
+ ,"oacute": 243
+ ,"ocirc": 244
+ ,"otilde": 245
+ ,"ouml": 246
+ ,"divide": 247
+ ,"oslash": 248
+ ,"ugrave": 249
+ ,"uacute": 250
+ ,"ucirc": 251
+ ,"uuml": 252
+ ,"yacute": 253
+ ,"thorn": 254
+ ,"yuml": 255
+ ,"fnof": 402
+ ,"Alpha": 913
+ ,"Beta": 914
+ ,"Gamma": 915
+ ,"Delta": 916
+ ,"Epsilon": 917
+ ,"Zeta": 918
+ ,"Eta": 919
+ ,"Theta": 920
+ ,"Iota": 921
+ ,"Kappa": 922
+ ,"Lambda": 923
+ ,"Mu": 924
+ ,"Nu": 925
+ ,"Xi": 926
+ ,"Omicron": 927
+ ,"Pi": 928
+ ,"Rho": 929
+ ,"Sigma": 931
+ ,"Tau": 932
+ ,"Upsilon": 933
+ ,"Phi": 934
+ ,"Chi": 935
+ ,"Psi": 936
+ ,"Omega": 937
+ ,"alpha": 945
+ ,"beta": 946
+ ,"gamma": 947
+ ,"delta": 948
+ ,"epsilon": 949
+ ,"zeta": 950
+ ,"eta": 951
+ ,"theta": 952
+ ,"iota": 953
+ ,"kappa": 954
+ ,"lambda": 955
+ ,"mu": 956
+ ,"nu": 957
+ ,"xi": 958
+ ,"omicron": 959
+ ,"pi": 960
+ ,"rho": 961
+ ,"sigmaf": 962
+ ,"sigma": 963
+ ,"tau": 964
+ ,"upsilon": 965
+ ,"phi": 966
+ ,"chi": 967
+ ,"psi": 968
+ ,"omega": 969
+ ,"thetasym": 977
+ ,"upsih": 978
+ ,"piv": 982
+ ,"bull": 8226
+ ,"hellip": 8230
+ ,"prime": 8242
+ ,"Prime": 8243
+ ,"oline": 8254
+ ,"frasl": 8260
+ ,"weierp": 8472
+ ,"image": 8465
+ ,"real": 8476
+ ,"trade": 8482
+ ,"alefsym": 8501
+ ,"larr": 8592
+ ,"uarr": 8593
+ ,"rarr": 8594
+ ,"darr": 8595
+ ,"harr": 8596
+ ,"crarr": 8629
+ ,"lArr": 8656
+ ,"uArr": 8657
+ ,"rArr": 8658
+ ,"dArr": 8659
+ ,"hArr": 8660
+ ,"forall": 8704
+ ,"part": 8706
+ ,"exist": 8707
+ ,"empty": 8709
+ ,"nabla": 8711
+ ,"isin": 8712
+ ,"notin": 8713
+ ,"ni": 8715
+ ,"prod": 8719
+ ,"sum": 8721
+ ,"minus": 8722
+ ,"lowast": 8727
+ ,"radic": 8730
+ ,"prop": 8733
+ ,"infin": 8734
+ ,"ang": 8736
+ ,"and": 8743
+ ,"or": 8744
+ ,"cap": 8745
+ ,"cup": 8746
+ ,"int": 8747
+ ,"there4": 8756
+ ,"sim": 8764
+ ,"cong": 8773
+ ,"asymp": 8776
+ ,"ne": 8800
+ ,"equiv": 8801
+ ,"le": 8804
+ ,"ge": 8805
+ ,"sub": 8834
+ ,"sup": 8835
+ ,"nsub": 8836
+ ,"sube": 8838
+ ,"supe": 8839
+ ,"oplus": 8853
+ ,"otimes": 8855
+ ,"perp": 8869
+ ,"sdot": 8901
+ ,"lceil": 8968
+ ,"rceil": 8969
+ ,"lfloor": 8970
+ ,"rfloor": 8971
+ ,"lang": 9001
+ ,"rang": 9002
+ ,"loz": 9674
+ ,"spades": 9824
+ ,"clubs": 9827
+ ,"hearts": 9829
+ ,"diams": 9830
+ ,"quot": 34
+ ,"amp": 38
+ ,"lt": 60
+ ,"gt": 62
+ ,"OElig": 338
+ ,"oelig": 339
+ ,"Scaron": 352
+ ,"scaron": 353
+ ,"Yuml": 376
+ ,"circ": 710
+ ,"tilde": 732
+ ,"ensp": 8194
+ ,"emsp": 8195
+ ,"thinsp": 8201
+ ,"zwnj": 8204
+ ,"zwj": 8205
+ ,"lrm": 8206
+ ,"rlm": 8207
+ ,"ndash": 8211
+ ,"mdash": 8212
+ ,"lsquo": 8216
+ ,"rsquo": 8217
+ ,"sbquo": 8218
+ ,"ldquo": 8220
+ ,"rdquo": 8221
+ ,"bdquo": 8222
+ ,"dagger": 8224
+ ,"Dagger": 8225
+ ,"permil": 8240
+ ,"lsaquo": 8249
+ ,"rsaquo": 8250
+ ,"euro": 8364}
diff --git a/src/libs/python/bongo/external/simpletal/simpleElementTree.py b/src/libs/python/bongo/external/simpletal/simpleElementTree.py
new file mode 100644
index 0000000..f60de3c
--- /dev/null
+++ b/src/libs/python/bongo/external/simpletal/simpleElementTree.py
@@ -0,0 +1,106 @@
+""" ElementTree integration for SimpleTAL
+
+ Copyright (c) 2004 Colin Stewart (http://www.owlfish.com/)
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ 3. The name of the author may not be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+ If you make any bug fixes or feature enhancements please let me know!
+
+ The parseFile function in this module will return a Element object that
+ implements simpleTALES.ContextVariable and makes XML documents available
+ with the following path logic:
+ - Accessing Element directly returns the Element.text value
+ - Accessing Element/find and Element/findall passes the text
+ (up to attribute accessor) to the corresponding Element function
+ - Accessing the Element@name access the attribute "name"
+ - Accessing Element/anotherElement is a short-cut for
+ Element/find/anotherElement
+
+ Module Dependencies: simpleTALES, elementtree
+"""
+
+from bongo.external.elementtree import ElementTree
+import bongo.external.simpleTALES
+
+class SimpleElementTreeVar (ElementTree._ElementInterface, simpleTALES.ContextVariable):
+ def __init__(self, tag, attrib):
+ ElementTree._ElementInterface.__init__(self, tag, attrib)
+ simpleTALES.ContextVariable.__init__(self)
+
+ def value (self, pathInfo = None):
+ if (pathInfo is not None):
+ pathIndex, paths = pathInfo
+ ourParams = paths[pathIndex:]
+ attributeName = None
+ if (len (ourParams) > 0):
+ # Look for attribute index
+ if (ourParams[-1].startswith ('@')):
+ # Attribute lookup
+ attributeName = ourParams [-1][1:]
+ ourParams = ourParams [:-1]
+ # Do we do a find?
+ activeElement = self
+ if len (ourParams) > 0:
+ # Look for a find or findall first
+ if (ourParams [0] == 'find'):
+ # Find the element if possible
+ activeElement = self.find ("/".join (ourParams [1:]))
+ elif (ourParams [0] == 'findall'):
+ # Short cut this
+ raise simpleTALES.ContextVariable (self.findall ("/".join (ourParams[1:])))
+ else:
+ # Assume that we wanted to use find
+ activeElement = self.find ("/".join (ourParams))
+ # Did we find an element and are we looking for an attribute?
+ if (attributeName is not None and activeElement is not None):
+ attrValue = activeElement.attrib.get (attributeName, None)
+ raise simpleTALES.ContextVariable (attrValue)
+
+ # Just return the element
+ if (activeElement is None):
+ # Wrap it
+ raise simpleTALES.ContextVariable (None)
+ raise activeElement
+ else:
+ return self
+
+ def __unicode__ (self):
+ return self.text
+
+ def __str__ (self):
+ return str (self.text)
+
+def parseFile (file):
+ treeBuilder = ElementTree.TreeBuilder (element_factory = SimpleElementTreeVar)
+ xmlTreeBuilder = ElementTree.XMLTreeBuilder (target=treeBuilder)
+
+ if (not hasattr (file, 'read')):
+ ourFile = open (file)
+ xmlTreeBuilder.feed (ourFile.read())
+ ourFile.close()
+ else:
+ xmlTreeBuilder.feed (file.read())
+
+ return xmlTreeBuilder.close()
+
diff --git a/src/libs/python/bongo/external/simpletal/simpleTAL.py b/src/libs/python/bongo/external/simpletal/simpleTAL.py
new file mode 100644
index 0000000..0c2690d
--- /dev/null
+++ b/src/libs/python/bongo/external/simpletal/simpleTAL.py
@@ -0,0 +1,1514 @@
+""" simpleTAL Interpreter
+
+ Copyright (c) 2005 Colin Stewart (http://www.owlfish.com/)
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ 3. The name of the author may not be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+ If you make any bug fixes or feature enhancements please let me know!
+
+
+ The classes in this module implement the TAL language, expanding
+ both XML and HTML templates.
+
+ Module Dependencies: logging, simpleTALES, simpleTALTemplates
+"""
+
+try:
+ import logging
+except:
+ import bongo.external.simpletal.DummyLogger as logging
+
+import xml.sax, cgi, StringIO, codecs, re, types, copy, sys
+
+import bongo.external.simpletal.sgmlentitynames as sgmlentitynames
+import bongo.external.simpletal as simpletal
+import bongo.external.simpletal.FixedHTMLParser as FixedHTMLParser
+
+__version__ = simpletal.__version__
+
+try:
+ # Is PyXML's LexicalHandler available?
+ from xml.sax.saxlib import LexicalHandler
+ use_lexical_handler = 1
+except ImportError:
+ use_lexical_handler = 0
+ class LexicalHandler:
+ pass
+
+try:
+ # Is PyXML's DOM2SAX available?
+ import xml.dom.ext.Dom2Sax
+ use_dom2sax = 1
+except ImportError:
+ use_dom2sax = 0
+
+import bongo.external.simpletal.simpleTALES as simpleTALES
+
+# Name-space URIs
+METAL_NAME_URI="http://xml.zope.org/namespaces/metal"
+TAL_NAME_URI="http://xml.zope.org/namespaces/tal"
+
+# All commands are of the form (opcode, args, commandList)
+# The numbers are the opcodes, and also the order of priority
+
+# Argument: [(isLocalFlag (Y/n), variableName, variablePath),...]
+TAL_DEFINE = 1
+# Argument: expression, endTagSymbol
+TAL_CONDITION = 2
+# Argument: (varname, expression, endTagSymbol)
+TAL_REPEAT = 3
+# Argument: (replaceFlag, type, expression)
+TAL_CONTENT = 4
+# Not used in byte code, only ordering.
+TAL_REPLACE = 5
+# Argument: [(attributeName, expression)]
+TAL_ATTRIBUTES = 6
+# Argument: expression
+TAL_OMITTAG = 7
+# Argument: (originalAttributeList, currentAttributeList)
+TAL_START_SCOPE = 8
+# Argument: String to output
+TAL_OUTPUT = 9
+# Argument: None
+TAL_STARTTAG = 10
+# Argument: Tag, omitTagFlag
+TAL_ENDTAG_ENDSCOPE = 11
+# Argument: None
+TAL_NOOP = 13
+
+# METAL Starts here
+# Argument: expression, slotParams, endTagSymbol
+METAL_USE_MACRO = 14
+# Argument: macroName, endTagSymbol
+METAL_DEFINE_SLOT=15
+# Only used for parsing
+METAL_FILL_SLOT=16
+METAL_DEFINE_MACRO=17
+
+METAL_NAME_REGEX = re.compile ("[a-zA-Z_][a-zA-Z0-9_]*")
+SINGLETON_XML_REGEX = re.compile ('^<[^\s/>]+(?:\s*[^=>]+="[^">]+")*\s*/>')
+ENTITY_REF_REGEX = re.compile (r'(?:&[a-zA-Z][\-\.a-zA-Z0-9]*[^\-\.a-zA-Z0-9])|(?:[xX]?[a-eA-E0-9]*[^0-9a-eA-E])')
+
+# The list of elements in HTML that can not have end tags - done as a dictionary for fast
+# lookup.
+HTML_FORBIDDEN_ENDTAG = {'AREA': 1, 'BASE': 1, 'BASEFONT': 1, 'BR': 1, 'COL': 1
+ ,'FRAME': 1, 'HR': 1, 'IMG': 1, 'INPUT': 1, 'ISINDEX': 1
+ ,'LINK': 1, 'META': 1, 'PARAM': 1}
+
+# List of element:attribute pairs that can use minimized form in HTML
+HTML_BOOLEAN_ATTS = {'AREA:NOHREF': 1, 'IMG:ISMAP': 1, 'OBJECT:DECLARE': 1
+ , 'INPUT:CHECKED': 1, 'INPUT:DISABLED': 1, 'INPUT:READONLY': 1, 'INPUT:ISMAP': 1
+ , 'SELECT:MULTIPLE': 1, 'SELECT:DISABLED': 1
+ , 'OPTGROUP:DISABLED': 1
+ , 'OPTION:SELECTED': 1, 'OPTION:DISABLED': 1
+ , 'TEXTAREA:DISABLED': 1, 'TEXTAREA:READONLY': 1
+ , 'BUTTON:DISABLED': 1, 'SCRIPT:DEFER': 1}
+
+class TemplateInterpreter:
+ def __init__ (self):
+ self.programStack = []
+ self.commandList = None
+ self.symbolTable = None
+ self.slotParameters = {}
+ self.commandHandler = {}
+ self.commandHandler [TAL_DEFINE] = self.cmdDefine
+ self.commandHandler [TAL_CONDITION] = self.cmdCondition
+ self.commandHandler [TAL_REPEAT] = self.cmdRepeat
+ self.commandHandler [TAL_CONTENT] = self.cmdContent
+ self.commandHandler [TAL_ATTRIBUTES] = self.cmdAttributes
+ self.commandHandler [TAL_OMITTAG] = self.cmdOmitTag
+ self.commandHandler [TAL_START_SCOPE] = self.cmdStartScope
+ self.commandHandler [TAL_OUTPUT] = self.cmdOutput
+ self.commandHandler [TAL_STARTTAG] = self.cmdOutputStartTag
+ self.commandHandler [TAL_ENDTAG_ENDSCOPE] = self.cmdEndTagEndScope
+ self.commandHandler [METAL_USE_MACRO] = self.cmdUseMacro
+ self.commandHandler [METAL_DEFINE_SLOT] = self.cmdDefineSlot
+ self.commandHandler [TAL_NOOP] = self.cmdNoOp
+
+ def tagAsText (self, (tag,atts), singletonFlag=0):
+ """ This returns a tag as text.
+ """
+ result = ["<"]
+ result.append (tag)
+ for attName, attValue in atts:
+ result.append (' ')
+ result.append (attName)
+ result.append ('="')
+ result.append (cgi.escape (attValue, quote=1))
+ result.append ('"')
+ if (singletonFlag):
+ result.append (" />")
+ else:
+ result.append (">")
+ return "".join (result)
+
+ def initialise (self, context, outputFile):
+ self.context = context
+ self.file = outputFile
+
+ def cleanState (self):
+ self.scopeStack = []
+ self.programCounter = 0
+ self.movePCForward = None
+ self.movePCBack = None
+ self.outputTag = 1
+ self.originalAttributes = {}
+ self.currentAttributes = []
+ # Used in repeat only.
+ self.repeatAttributesCopy = []
+ self.currentSlots = {}
+ self.repeatVariable = None
+ self.tagContent = None
+ # tagState flag as to whether there are any local variables to pop
+ self.localVarsDefined = 0
+ # Pass in the parameters
+ self.currentSlots = self.slotParameters
+
+ def popProgram (self):
+ vars, self.commandList, self.symbolTable = self.programStack.pop()
+ self.programCounter,self.scopeStack,self.slotParameters,self.currentSlots, self.movePCForward,self.movePCBack,self.outputTag,self.originalAttributes,self.currentAttributes,self.repeatVariable,self.tagContent,self.localVarsDefined = vars
+
+ def pushProgram (self):
+ vars = (self.programCounter
+ ,self.scopeStack
+ ,self.slotParameters
+ ,self.currentSlots
+ ,self.movePCForward
+ ,self.movePCBack
+ ,self.outputTag
+ ,self.originalAttributes
+ ,self.currentAttributes
+ ,self.repeatVariable
+ ,self.tagContent
+ ,self.localVarsDefined)
+ self.programStack.append ((vars,self.commandList, self.symbolTable))
+
+ def execute (self, template):
+ self.cleanState()
+ self.commandList, self.programCounter, programLength, self.symbolTable = template.getProgram()
+ cmndList = self.commandList
+ while (self.programCounter < programLength):
+ cmnd = cmndList [self.programCounter]
+ #print "PC: %s - Executing command: %s" % (str (self.programCounter), str (cmnd))
+ self.commandHandler[cmnd[0]] (cmnd[0], cmnd[1])
+
+ def cmdDefine (self, command, args):
+ """ args: [(isLocalFlag (Y/n), variableName, variablePath),...]
+ Define variables in either the local or global context
+ """
+ foundLocals = 0
+ for isLocal, varName, varPath in args:
+ result = self.context.evaluate (varPath, self.originalAttributes)
+ if (isLocal):
+ if (not foundLocals):
+ foundLocals = 1
+ self.context.pushLocals ()
+ self.context.setLocal (varName, result)
+ else:
+ self.context.addGlobal (varName, result)
+ self.localVarsDefined = foundLocals
+ self.programCounter += 1
+
+ def cmdCondition (self, command, args):
+ """ args: expression, endTagSymbol
+ Conditionally continues with execution of all content contained
+ by it.
+ """
+ result = self.context.evaluate (args[0], self.originalAttributes)
+ #~ if (result is None or (not result)):
+ conditionFalse = 0
+ if (result is None):
+ conditionFalse = 1
+ else:
+ if (not result): conditionFalse = 1
+ try:
+ temp = len (result)
+ if (temp == 0): conditionFalse = 1
+ except:
+ # Result is not a sequence.
+ pass
+ if (conditionFalse):
+ # Nothing to output - evaluated to false.
+ self.outputTag = 0
+ self.tagContent = None
+ self.programCounter = self.symbolTable[args[1]]
+ return
+ self.programCounter += 1
+
+ def cmdRepeat (self, command, args):
+ """ args: (varName, expression, endTagSymbol)
+ Repeats anything in the cmndList
+ """
+ if (self.repeatVariable is not None):
+ # We are already part way through a repeat
+ # Restore any attributes that might have been changed.
+ if (self.currentAttributes != self.repeatAttributesCopy):
+ self.currentAttributes = copy.copy (self.repeatAttributesCopy)
+ self.outputTag = 1
+ self.tagContent = None
+ self.movePCForward = None
+
+ try:
+ self.repeatVariable.increment()
+ self.context.setLocal (args[0], self.repeatVariable.getCurrentValue())
+ self.programCounter += 1
+ return
+ except IndexError, e:
+ # We have finished the repeat
+ self.repeatVariable = None
+ self.context.removeRepeat (args[0])
+ # The locals were pushed in context.addRepeat
+ self.context.popLocals()
+ self.movePCBack = None
+ # Suppress the final close tag and content
+ self.tagContent = None
+ self.outputTag = 0
+ self.programCounter = self.symbolTable [args[2]]
+ # Restore the state of repeatAttributesCopy in case we are nested.
+ self.repeatAttributesCopy = self.scopeStack.pop()
+ return
+
+ # The first time through this command
+ result = self.context.evaluate (args[1], self.originalAttributes)
+ if (result is not None and result == simpleTALES.DEFAULTVALUE):
+ # Leave everything un-touched.
+ self.programCounter += 1
+ return
+ try:
+ # We have three options, either the result is a natural sequence, an iterator., or something that can produce an iterator.
+ isSequence = len (result)
+ if (isSequence):
+ # Only setup if we have a sequence with length
+ self.repeatVariable = simpleTALES.RepeatVariable (result)
+ else:
+ # Delete the tags and their contents
+ self.outputTag = 0
+ self.programCounter = self.symbolTable [args[2]]
+ return
+ except:
+ # Not a natural sequence, can it produce an iterator?
+ if (hasattr (result, "__iter__") and callable (result.__iter__)):
+ # We can get an iterator!
+ self.repeatVariable = simpleTALES.IteratorRepeatVariable (result.__iter__())
+ elif (hasattr (result, "next") and callable (result.next)):
+ # Treat as an iterator
+ self.repeatVariable = simpleTALES.IteratorRepeatVariable (result)
+ else:
+ # Just a plain object, let's not loop
+ # Delete the tags and their contents
+ self.outputTag = 0
+ self.programCounter = self.symbolTable [args[2]]
+ return
+
+ try:
+ curValue = self.repeatVariable.getCurrentValue()
+ except IndexError, e:
+ # The iterator ran out of values before we started - treat as an empty list
+ self.outputTag = 0
+ self.repeatVariable = None
+ self.programCounter = self.symbolTable [args[2]]
+ return
+ # We really do want to repeat - so lets do it
+ self.movePCBack = self.programCounter
+ self.context.addRepeat (args[0], self.repeatVariable, curValue)
+ # We keep the old state of the repeatAttributesCopy for nested loops
+ self.scopeStack.append (self.repeatAttributesCopy)
+ # Keep a copy of the current attributes for this tag
+ self.repeatAttributesCopy = copy.copy (self.currentAttributes)
+ self.programCounter += 1
+
+ def cmdContent (self, command, args):
+ """ args: (replaceFlag, structureFlag, expression, endTagSymbol)
+ Expands content
+ """
+ result = self.context.evaluate (args[2], self.originalAttributes)
+ if (result is None):
+ if (args[0]):
+ # Only output tags if this is a content not a replace
+ self.outputTag = 0
+ # Output none of our content or the existing content, but potentially the tags
+ self.movePCForward = self.symbolTable [args[3]]
+ self.programCounter += 1
+ return
+ elif (not result == simpleTALES.DEFAULTVALUE):
+ # We have content, so let's suppress the natural content and output this!
+ if (args[0]):
+ self.outputTag = 0
+ self.tagContent = (args[1], result)
+ self.movePCForward = self.symbolTable [args[3]]
+ self.programCounter += 1
+ return
+ else:
+ # Default, let's just run through as normal
+ self.programCounter += 1
+ return
+
+ def cmdAttributes (self, command, args):
+ """ args: [(attributeName, expression)]
+ Add, leave, or remove attributes from the start tag
+ """
+ attsToRemove = {}
+ newAtts = []
+ for attName, attExpr in args:
+ resultVal = self.context.evaluate (attExpr, self.originalAttributes)
+ if (resultVal is None):
+ # Remove this attribute from the current attributes
+ attsToRemove [attName]=1
+ elif (not resultVal == simpleTALES.DEFAULTVALUE):
+ # We have a value - let's use it!
+ attsToRemove [attName]=1
+ if (isinstance (resultVal, types.UnicodeType)):
+ escapedAttVal = resultVal
+ elif (isinstance (resultVal, types.StringType)):
+ # THIS IS NOT A BUG!
+ # Use Unicode in the Context object if you are not using Ascii
+ escapedAttVal = unicode (resultVal, 'ascii')
+ else:
+ # THIS IS NOT A BUG!
+ # Use Unicode in the Context object if you are not using Ascii
+ escapedAttVal = unicode (resultVal)
+ newAtts.append ((attName, escapedAttVal))
+ # Copy over the old attributes
+ for oldAttName, oldAttValue in self.currentAttributes:
+ if (not attsToRemove.has_key (oldAttName)):
+ newAtts.append ((oldAttName, oldAttValue))
+ self.currentAttributes = newAtts
+ # Evaluate all other commands
+ self.programCounter += 1
+
+ def cmdOmitTag (self, command, args):
+ """ args: expression
+ Conditionally turn off tag output
+ """
+ result = self.context.evaluate (args, self.originalAttributes)
+ if (result is not None and result):
+ # Turn tag output off
+ self.outputTag = 0
+ self.programCounter += 1
+
+ def cmdOutputStartTag (self, command, args):
+ # Args: tagName
+ tagName, singletonTag = args
+ if (self.outputTag):
+ if (self.tagContent is None and singletonTag):
+ self.file.write (self.tagAsText ((tagName, self.currentAttributes), 1))
+ else:
+ self.file.write (self.tagAsText ((tagName, self.currentAttributes)))
+
+ if (self.movePCForward is not None):
+ self.programCounter = self.movePCForward
+ return
+ self.programCounter += 1
+ return
+
+ def cmdEndTagEndScope (self, command, args):
+ # Args: tagName, omitFlag, singletonTag
+ if (self.tagContent is not None):
+ contentType, resultVal = self.tagContent
+ if (contentType):
+ if (isinstance (resultVal, Template)):
+ # We have another template in the context, evaluate it!
+ # Save our state!
+ self.pushProgram()
+ resultVal.expandInline (self.context, self.file, self)
+ # Restore state
+ self.popProgram()
+ # End of the macro expansion (if any) so clear the parameters
+ self.slotParameters = {}
+ else:
+ if (isinstance (resultVal, types.UnicodeType)):
+ self.file.write (resultVal)
+ elif (isinstance (resultVal, types.StringType)):
+ # THIS IS NOT A BUG!
+ # Use Unicode in the Context object if you are not using Ascii
+ self.file.write (unicode (resultVal, 'ascii'))
+ else:
+ # THIS IS NOT A BUG!
+ # Use Unicode in the Context object if you are not using Ascii
+ self.file.write (unicode (resultVal))
+ else:
+ if (isinstance (resultVal, types.UnicodeType)):
+ self.file.write (cgi.escape (resultVal))
+ elif (isinstance (resultVal, types.StringType)):
+ # THIS IS NOT A BUG!
+ # Use Unicode in the Context object if you are not using Ascii
+ self.file.write (cgi.escape (unicode (resultVal, 'ascii')))
+ else:
+ # THIS IS NOT A BUG!
+ # Use Unicode in the Context object if you are not using Ascii
+ self.file.write (cgi.escape (unicode (resultVal)))
+
+ if (self.outputTag and not args[1]):
+ # Do NOT output end tag if a singleton with no content
+ if not (args[2] and self.tagContent is None):
+ self.file.write ('' + args[0] + '>')
+
+ if (self.movePCBack is not None):
+ self.programCounter = self.movePCBack
+ return
+
+ if (self.localVarsDefined):
+ self.context.popLocals()
+
+ self.movePCForward,self.movePCBack,self.outputTag,self.originalAttributes,self.currentAttributes,self.repeatVariable,self.tagContent,self.localVarsDefined = self.scopeStack.pop()
+ self.programCounter += 1
+
+ def cmdOutput (self, command, args):
+ self.file.write (args)
+ self.programCounter += 1
+
+ def cmdStartScope (self, command, args):
+ """ args: (originalAttributes, currentAttributes)
+ Pushes the current state onto the stack, and sets up the new state
+ """
+ self.scopeStack.append ((self.movePCForward
+ ,self.movePCBack
+ ,self.outputTag
+ ,self.originalAttributes
+ ,self.currentAttributes
+ ,self.repeatVariable
+ ,self.tagContent
+ ,self.localVarsDefined))
+
+ self.movePCForward = None
+ self.movePCBack = None
+ self.outputTag = 1
+ self.originalAttributes = args[0]
+ self.currentAttributes = args[1]
+ self.repeatVariable = None
+ self.tagContent = None
+ self.localVarsDefined = 0
+
+ self.programCounter += 1
+
+ def cmdNoOp (self, command, args):
+ self.programCounter += 1
+
+ def cmdUseMacro (self, command, args):
+ """ args: (macroExpression, slotParams, endTagSymbol)
+ Evaluates the expression, if it resolves to a SubTemplate it then places
+ the slotParams into currentSlots and then jumps to the end tag
+ """
+ value = self.context.evaluate (args[0], self.originalAttributes)
+ if (value is None):
+ # Don't output anything
+ self.outputTag = 0
+ # Output none of our content or the existing content
+ self.movePCForward = self.symbolTable [args[2]]
+ self.programCounter += 1
+ return
+ if (not value == simpleTALES.DEFAULTVALUE and isinstance (value, SubTemplate)):
+ # We have a macro, so let's use it
+ self.outputTag = 0
+ self.slotParameters = args[1]
+ self.tagContent = (1, value)
+ # NOTE: WE JUMP STRAIGHT TO THE END TAG, NO OTHER TAL/METAL COMMANDS ARE EVALUATED.
+ self.programCounter = self.symbolTable [args[2]]
+ return
+ else:
+ # Default, let's just run through as normal
+ self.programCounter += 1
+ return
+
+ def cmdDefineSlot (self, command, args):
+ """ args: (slotName, endTagSymbol)
+ If the slotName is filled then that is used, otherwise the original conent
+ is used.
+ """
+ if (self.currentSlots.has_key (args[0])):
+ # This slot is filled, so replace us with that content
+ self.outputTag = 0
+ self.tagContent = (1, self.currentSlots [args[0]])
+ # Output none of our content or the existing content
+ # NOTE: NO FURTHER TAL/METAL COMMANDS ARE EVALUATED
+ self.programCounter = self.symbolTable [args[1]]
+ return
+ # Slot isn't filled, so just use our own content
+ self.programCounter += 1
+ return
+
+
+class HTMLTemplateInterpreter (TemplateInterpreter):
+ def __init__ (self, minimizeBooleanAtts = 0):
+ TemplateInterpreter.__init__ (self)
+ self.minimizeBooleanAtts = minimizeBooleanAtts
+ if (minimizeBooleanAtts):
+ # Override the tagAsText method for this instance
+ self.tagAsText = self.tagAsTextMinimizeAtts
+
+ def tagAsTextMinimizeAtts (self, (tag,atts), singletonFlag=0):
+ """ This returns a tag as text.
+ """
+ result = ["<"]
+ result.append (tag)
+ upperTag = tag.upper()
+ for attName, attValue in atts:
+ if (HTML_BOOLEAN_ATTS.has_key ('%s:%s' % (upperTag, attName.upper()))):
+ # We should output a minimised boolean value
+ result.append (' ')
+ result.append (attName)
+ else:
+ result.append (' ')
+ result.append (attName)
+ result.append ('="')
+ result.append (cgi.escape (attValue, quote=1))
+ result.append ('"')
+ if (singletonFlag):
+ result.append (" />")
+ else:
+ result.append (">")
+ return "".join (result)
+
+class Template:
+ def __init__ (self, commands, macros, symbols, doctype = None):
+ self.commandList = commands
+ self.macros = macros
+ self.symbolTable = symbols
+ self.doctype = doctype
+
+ # Setup the macros
+ for macro in self.macros.values():
+ macro.setParentTemplate (self)
+
+ # Setup the slots
+ for cmnd, arg in self.commandList:
+ if (cmnd == METAL_USE_MACRO):
+ # Set the parent of each slot
+ slotMap = arg[1]
+ for slot in slotMap.values():
+ slot.setParentTemplate (self)
+
+ def expand (self, context, outputFile, outputEncoding=None, interpreter=None):
+ """ This method will write to the outputFile, using the encoding specified,
+ the expanded version of this template. The context passed in is used to resolve
+ all expressions with the template.
+ """
+ # This method must wrap outputFile if required by the encoding, and write out
+ # any template pre-amble (DTD, Encoding, etc)
+ self.expandInline (context, outputFile, interpreter)
+
+ def expandInline (self, context, outputFile, interpreter=None):
+ """ Internally used when expanding a template that is part of a context."""
+ if (interpreter is None):
+ ourInterpreter = TemplateInterpreter()
+ ourInterpreter.initialise (context, outputFile)
+ else:
+ ourInterpreter = interpreter
+ try:
+ ourInterpreter.execute (self)
+ except UnicodeError, unierror:
+ logging.error ("UnicodeError caused by placing a non-Unicode string in the Context object.")
+ raise simpleTALES.ContextContentException ("Found non-unicode string in Context!")
+
+ def getProgram (self):
+ """ Returns a tuple of (commandList, startPoint, endPoint, symbolTable) """
+ return (self.commandList, 0, len (self.commandList), self.symbolTable)
+
+ def __str__ (self):
+ result = "Commands:\n"
+ index = 0
+ for cmd in self.commandList:
+ if (cmd[0] != METAL_USE_MACRO):
+ result = result + "\n[%s] %s" % (str (index), str (cmd))
+ else:
+ result = result + "\n[%s] %s, (%s{" % (str (index), str (cmd[0]), str (cmd[1][0]))
+ for slot in cmd[1][1].keys():
+ result = result + "%s: %s" % (slot, str (cmd[1][1][slot]))
+ result = result + "}, %s)" % str (cmd[1][2])
+ index += 1
+ result = result + "\n\nSymbols:\n"
+ for symbol in self.symbolTable.keys():
+ result = result + "Symbol: " + str (symbol) + " points to: " + str (self.symbolTable[symbol]) + ", which is command: " + str (self.commandList[self.symbolTable[symbol]]) + "\n"
+
+ result = result + "\n\nMacros:\n"
+ for macro in self.macros.keys():
+ result = result + "Macro: " + str (macro) + " value of: " + str (self.macros[macro])
+ return result
+
+class SubTemplate (Template):
+ """ A SubTemplate is part of another template, and is used for the METAL implementation.
+ The two uses for this class are:
+ 1 - metal:define-macro results in a SubTemplate that is the macro
+ 2 - metal:fill-slot results in a SubTemplate that is a parameter to metal:use-macro
+ """
+ def __init__ (self, startRange, endRangeSymbol):
+ """ The parentTemplate is the template for which we are a sub-template.
+ The startRange and endRange are indexes into the parent templates command list,
+ and defines the range of commands that we can execute
+ """
+ Template.__init__ (self, [], {}, {})
+ self.startRange = startRange
+ self.endRangeSymbol = endRangeSymbol
+
+ def setParentTemplate (self, parentTemplate):
+ self.parentTemplate = parentTemplate
+ self.commandList = parentTemplate.commandList
+ self.symbolTable = parentTemplate.symbolTable
+
+ def getProgram (self):
+ """ Returns a tuple of (commandList, startPoint, endPoint, symbolTable) """
+ return (self.commandList, self.startRange, self.symbolTable[self.endRangeSymbol]+1, self.symbolTable)
+
+ def __str__ (self):
+ endRange = self.symbolTable [self.endRangeSymbol]
+ result = "SubTemplate from %s to %s\n" % (str (self.startRange), str (endRange))
+ return result
+
+class HTMLTemplate (Template):
+ """A specialised form of a template that knows how to output HTML
+ """
+ def __init__ (self, commands, macros, symbols, doctype = None, minimizeBooleanAtts = 0):
+ self.minimizeBooleanAtts = minimizeBooleanAtts
+ Template.__init__ (self, commands, macros, symbols, doctype = None)
+
+ def expand (self, context, outputFile, outputEncoding="ISO-8859-1",interpreter=None):
+ """ This method will write to the outputFile, using the encoding specified,
+ the expanded version of this template. The context passed in is used to resolve
+ all expressions with the template.
+ """
+ # This method must wrap outputFile if required by the encoding, and write out
+ # any template pre-amble (DTD, Encoding, etc)
+
+ encodingFile = codecs.lookup (outputEncoding)[3](outputFile, 'replace')
+ self.expandInline (context, encodingFile, interpreter)
+
+ def expandInline (self, context, outputFile, interpreter=None):
+ """ Ensure we use the HTMLTemplateInterpreter"""
+ if (interpreter is None):
+ ourInterpreter = HTMLTemplateInterpreter(minimizeBooleanAtts = self.minimizeBooleanAtts)
+ ourInterpreter.initialise (context, outputFile)
+ else:
+ ourInterpreter = interpreter
+ Template.expandInline (self, context, outputFile, ourInterpreter)
+
+class XMLTemplate (Template):
+ """A specialised form of a template that knows how to output XML
+ """
+
+ def __init__ (self, commands, macros, symbols, doctype = None):
+ Template.__init__ (self, commands, macros, symbols)
+ self.doctype = doctype
+
+ def expand (self, context, outputFile, outputEncoding="iso-8859-1", docType=None, suppressXMLDeclaration=0,interpreter=None):
+ """ This method will write to the outputFile, using the encoding specified,
+ the expanded version of this template. The context passed in is used to resolve
+ all expressions with the template.
+ """
+ # This method must wrap outputFile if required by the encoding, and write out
+ # any template pre-amble (DTD, Encoding, etc)
+
+ # Write out the XML prolog
+ encodingFile = codecs.lookup (outputEncoding)[3](outputFile, 'replace')
+ if (not suppressXMLDeclaration):
+ if (outputEncoding.lower() != "utf-8"):
+ encodingFile.write ('\n' % outputEncoding.lower())
+ else:
+ encodingFile.write ('\n')
+ if not docType and self.doctype:
+ docType = self.doctype
+ if docType:
+ encodingFile.write (docType)
+ encodingFile.write ('\n')
+ self.expandInline (context, encodingFile, interpreter)
+
+class TemplateCompiler:
+ def __init__ (self):
+ """ Initialise a template compiler.
+ """
+ self.commandList = []
+ self.tagStack = []
+ self.symbolLocationTable = {}
+ self.macroMap = {}
+ self.endTagSymbol = 1
+
+ self.commandHandler = {}
+ self.commandHandler [TAL_DEFINE] = self.compileCmdDefine
+ self.commandHandler [TAL_CONDITION] = self.compileCmdCondition
+ self.commandHandler [TAL_REPEAT] = self.compileCmdRepeat
+ self.commandHandler [TAL_CONTENT] = self.compileCmdContent
+ self.commandHandler [TAL_REPLACE] = self.compileCmdReplace
+ self.commandHandler [TAL_ATTRIBUTES] = self.compileCmdAttributes
+ self.commandHandler [TAL_OMITTAG] = self.compileCmdOmitTag
+
+ # Metal commands
+ self.commandHandler [METAL_USE_MACRO] = self.compileMetalUseMacro
+ self.commandHandler [METAL_DEFINE_SLOT] = self.compileMetalDefineSlot
+ self.commandHandler [METAL_FILL_SLOT] = self.compileMetalFillSlot
+ self.commandHandler [METAL_DEFINE_MACRO] = self.compileMetalDefineMacro
+
+ # Default namespaces
+ self.setTALPrefix ('tal')
+ self.tal_namespace_prefix_stack = []
+ self.metal_namespace_prefix_stack = []
+ self.tal_namespace_prefix_stack.append ('tal')
+ self.setMETALPrefix ('metal')
+ self.metal_namespace_prefix_stack.append ('metal')
+
+ self.log = logging.getLogger ("simpleTAL.TemplateCompiler")
+
+ def setTALPrefix (self, prefix):
+ self.tal_namespace_prefix = prefix
+ self.tal_namespace_omittag = '%s:omit-tag' % self.tal_namespace_prefix
+ self.tal_attribute_map = {}
+ self.tal_attribute_map ['%s:attributes'%prefix] = TAL_ATTRIBUTES
+ self.tal_attribute_map ['%s:content'%prefix]= TAL_CONTENT
+ self.tal_attribute_map ['%s:define'%prefix] = TAL_DEFINE
+ self.tal_attribute_map ['%s:replace'%prefix] = TAL_REPLACE
+ self.tal_attribute_map ['%s:omit-tag'%prefix] = TAL_OMITTAG
+ self.tal_attribute_map ['%s:condition'%prefix] = TAL_CONDITION
+ self.tal_attribute_map ['%s:repeat'%prefix] = TAL_REPEAT
+
+ def setMETALPrefix (self, prefix):
+ self.metal_namespace_prefix = prefix
+ self.metal_attribute_map = {}
+ self.metal_attribute_map ['%s:define-macro'%prefix] = METAL_DEFINE_MACRO
+ self.metal_attribute_map ['%s:use-macro'%prefix] = METAL_USE_MACRO
+ self.metal_attribute_map ['%s:define-slot'%prefix] = METAL_DEFINE_SLOT
+ self.metal_attribute_map ['%s:fill-slot'%prefix] = METAL_FILL_SLOT
+
+ def popTALNamespace (self):
+ newPrefix = self.tal_namespace_prefix_stack.pop()
+ self.setTALPrefix (newPrefix)
+
+ def popMETALNamespace (self):
+ newPrefix = self.metal_namespace_prefix_stack.pop()
+ self.setMETALPrefix (newPrefix)
+
+ def tagAsText (self, (tag,atts), singletonFlag=0):
+ """ This returns a tag as text.
+ """
+ result = ["<"]
+ result.append (tag)
+ for attName, attValue in atts:
+ result.append (' ')
+ result.append (attName)
+ result.append ('="')
+ result.append (cgi.escape (attValue, quote=1))
+ result.append ('"')
+ if (singletonFlag):
+ result.append (" />")
+ else:
+ result.append (">")
+ return "".join (result)
+
+ def getTemplate (self):
+ template = Template (self.commandList, self.macroMap, self.symbolLocationTable)
+ return template
+
+ def addCommand (self, command):
+ if (command[0] == TAL_OUTPUT and (len (self.commandList) > 0) and self.commandList[-1][0] == TAL_OUTPUT):
+ # We can combine output commands
+ self.commandList[-1] = (TAL_OUTPUT, self.commandList[-1][1] + command[1])
+ else:
+ self.commandList.append (command)
+
+ def addTag (self, tag, tagProperties={}):
+ """ Used to add a tag to the stack. Various properties can be passed in the dictionary
+ as being information required by the tag.
+ Currently supported properties are:
+ 'command' - The (command,args) tuple associated with this command
+ 'originalAtts' - The original attributes that include any metal/tal attributes
+ 'endTagSymbol' - The symbol associated with the end tag for this element
+ 'popFunctionList' - A list of functions to execute when this tag is popped
+ 'singletonTag' - A boolean to indicate that this is a singleton flag
+ """
+ # Add the tag to the tagStack (list of tuples (tag, properties, useMacroLocation))
+ self.log.debug ("Adding tag %s to stack" % tag[0])
+ command = tagProperties.get ('command',None)
+ originalAtts = tagProperties.get ('originalAtts', None)
+ singletonTag = tagProperties.get ('singletonTag', 0)
+ if (command is not None):
+ if (command[0] == METAL_USE_MACRO):
+ self.tagStack.append ((tag, tagProperties, len (self.commandList)+1))
+ else:
+ self.tagStack.append ((tag, tagProperties, None))
+ else:
+ self.tagStack.append ((tag, tagProperties, None))
+ if (command is not None):
+ # All tags that have a TAL attribute on them start with a 'start scope'
+ self.addCommand((TAL_START_SCOPE, (originalAtts, tag[1])))
+ # Now we add the TAL command
+ self.addCommand(command)
+ else:
+ # It's just a straight output, so create an output command and append it
+ self.addCommand((TAL_OUTPUT, self.tagAsText (tag, singletonTag)))
+
+ def popTag (self, tag, omitTagFlag=0):
+ """ omitTagFlag is used to control whether the end tag should be included in the
+ output or not. In HTML 4.01 there are several tags which should never have
+ end tags, this flag allows the template compiler to specify that these
+ should not be output.
+ """
+ while (len (self.tagStack) > 0):
+ oldTag, tagProperties, useMacroLocation = self.tagStack.pop()
+ endTagSymbol = tagProperties.get ('endTagSymbol', None)
+ popCommandList = tagProperties.get ('popFunctionList', [])
+ singletonTag = tagProperties.get ('singletonTag', 0)
+ for func in popCommandList:
+ apply (func, ())
+ self.log.debug ("Popped tag %s off stack" % oldTag[0])
+ if (oldTag[0] == tag[0]):
+ # We've found the right tag, now check to see if we have any TAL commands on it
+ if (endTagSymbol is not None):
+ # We have a command (it's a TAL tag)
+ # Note where the end tag symbol should point (i.e. the next command)
+ self.symbolLocationTable [endTagSymbol] = len (self.commandList)
+
+ # We need a "close scope and tag" command
+ self.addCommand((TAL_ENDTAG_ENDSCOPE, (tag[0], omitTagFlag, singletonTag)))
+ return
+ elif (omitTagFlag == 0 and singletonTag == 0):
+ # We are popping off an un-interesting tag, just add the close as text
+ self.addCommand((TAL_OUTPUT, '' + tag[0] + '>'))
+ return
+ else:
+ # We are suppressing the output of this tag, so just return
+ return
+ else:
+ # We have a different tag, which means something like
which never closes is in
+ # between us and the real tag.
+
+ # If the tag that we did pop off has a command though it means un-balanced TAL tags!
+ if (endTagSymbol is not None):
+ # ERROR
+ msg = "TAL/METAL Elements must be balanced - found close tag %s expecting %s" % (tag[0], oldTag[0])
+ self.log.error (msg)
+ raise TemplateParseException (self.tagAsText(oldTag), msg)
+ self.log.error ("Close tag %s found with no corresponding open tag." % tag[0])
+ raise TemplateParseException ("%s>" % tag[0], "Close tag encountered with no corresponding open tag.")
+
+ def parseStartTag (self, tag, attributes, singletonElement=0):
+ # Note down the tag we are handling, it will be used for error handling during
+ # compilation
+ self.currentStartTag = (tag, attributes)
+
+ # Look for tal/metal attributes
+ foundTALAtts = []
+ foundMETALAtts = []
+ foundCommandsArgs = {}
+ cleanAttributes = []
+ originalAttributes = {}
+ tagProperties = {}
+ popTagFuncList = []
+ TALElementNameSpace = 0
+ prefixToAdd = ""
+ tagProperties ['singletonTag'] = singletonElement
+
+ # Determine whether this element is in either the METAL or TAL namespace
+ if (tag.find (':') > 0):
+ # We have a namespace involved, so let's look to see if its one of ours
+ namespace = tag[0:tag.find (':')]
+ if (namespace == self.metal_namespace_prefix):
+ TALElementNameSpace = 1
+ prefixToAdd = self.metal_namespace_prefix +":"
+ elif (namespace == self.tal_namespace_prefix):
+ TALElementNameSpace = 1
+ prefixToAdd = self.tal_namespace_prefix +":"
+
+ if (TALElementNameSpace):
+ # We should treat this an implicit omit-tag
+ foundTALAtts.append (TAL_OMITTAG)
+ # Will go to default, i.e. yes
+ foundCommandsArgs [TAL_OMITTAG] = ""
+
+ for att, value in attributes:
+ originalAttributes [att] = value
+ if (TALElementNameSpace and not att.find (':') > 0):
+ # This means that the attribute name does not have a namespace, so use the prefix for this tag.
+ commandAttName = prefixToAdd + att
+ else:
+ commandAttName = att
+ self.log.debug ("Command name is now %s" % commandAttName)
+ if (att[0:5] == "xmlns"):
+ # We have a namespace declaration.
+ prefix = att[6:]
+ if (value == METAL_NAME_URI):
+ # It's a METAL namespace declaration
+ if (len (prefix) > 0):
+ self.metal_namespace_prefix_stack.append (self.metal_namespace_prefix)
+ self.setMETALPrefix (prefix)
+ # We want this function called when the scope ends
+ popTagFuncList.append (self.popMETALNamespace)
+ else:
+ # We don't allow METAL/TAL to be declared as a default
+ msg = "Can not use METAL name space by default, a prefix must be provided."
+ raise TemplateParseException (self.tagAsText (self.currentStartTag), msg)
+ elif (value == TAL_NAME_URI):
+ # TAL this time
+ if (len (prefix) > 0):
+ self.tal_namespace_prefix_stack.append (self.tal_namespace_prefix)
+ self.setTALPrefix (prefix)
+ # We want this function called when the scope ends
+ popTagFuncList.append (self.popTALNamespace)
+ else:
+ # We don't allow METAL/TAL to be declared as a default
+ msg = "Can not use TAL name space by default, a prefix must be provided."
+ raise TemplateParseException (self.tagAsText (self.currentStartTag), msg)
+ else:
+ # It's nothing special, just an ordinary namespace declaration
+ cleanAttributes.append ((att, value))
+ elif (self.tal_attribute_map.has_key (commandAttName)):
+ # It's a TAL attribute
+ cmnd = self.tal_attribute_map [commandAttName]
+ if (cmnd == TAL_OMITTAG and TALElementNameSpace):
+ self.log.warn ("Supressing omit-tag command present on TAL or METAL element")
+ else:
+ foundCommandsArgs [cmnd] = value
+ foundTALAtts.append (cmnd)
+ elif (self.metal_attribute_map.has_key (commandAttName)):
+ # It's a METAL attribute
+ cmnd = self.metal_attribute_map [commandAttName]
+ foundCommandsArgs [cmnd] = value
+ foundMETALAtts.append (cmnd)
+ else:
+ cleanAttributes.append ((att, value))
+ tagProperties ['popFunctionList'] = popTagFuncList
+
+ # This might be just content
+ if ((len (foundTALAtts) + len (foundMETALAtts)) == 0):
+ # Just content, add it to the various stacks
+ self.addTag ((tag, cleanAttributes), tagProperties)
+ return
+
+ # Create a symbol for the end of the tag - we don't know what the offset is yet
+ self.endTagSymbol += 1
+ tagProperties ['endTagSymbol'] = self.endTagSymbol
+
+ # Sort the METAL commands
+ foundMETALAtts.sort()
+ # Sort the tags by priority
+ foundTALAtts.sort()
+
+ # We handle the METAL before the TAL
+ allCommands = foundMETALAtts + foundTALAtts
+ firstTag = 1
+ for talAtt in allCommands:
+ # Parse and create a command for each
+ cmnd = self.commandHandler [talAtt](foundCommandsArgs[talAtt])
+ if (cmnd is not None):
+ if (firstTag):
+ # The first one needs to add the tag
+ firstTag = 0
+ tagProperties ['originalAtts'] = originalAttributes
+ tagProperties ['command'] = cmnd
+ self.addTag ((tag, cleanAttributes), tagProperties)
+ else:
+ # All others just append
+ self.addCommand(cmnd)
+
+ if (firstTag):
+ tagProperties ['originalAtts'] = originalAttributes
+ tagProperties ['command'] = (TAL_STARTTAG, (tag, singletonElement))
+ self.addTag ((tag, cleanAttributes), tagProperties)
+ else:
+ # Add the start tag command in as a child of the last TAL command
+ self.addCommand((TAL_STARTTAG, (tag,singletonElement)))
+
+ def parseEndTag (self, tag):
+ """ Just pop the tag and related commands off the stack. """
+ self.popTag ((tag,None))
+
+ def parseData (self, data):
+ # Just add it as an output
+ self.addCommand((TAL_OUTPUT, data))
+
+ def compileCmdDefine (self, argument):
+ # Compile a define command, resulting argument is:
+ # [(isLocalFlag (Y/n), variableName, variablePath),...]
+ # Break up the list of defines first
+ commandArgs = []
+ # We only want to match semi-colons that are not escaped
+ argumentSplitter = re.compile ('(?2 elements means a local|global flag
+ if (len (stmtBits) > 2):
+ if (stmtBits[0] == 'global'):
+ isLocal = 0
+ varName = stmtBits[1]
+ expression = ' '.join (stmtBits[2:])
+ elif (stmtBits[0] == 'local'):
+ varName = stmtBits[1]
+ expression = ' '.join (stmtBits[2:])
+ else:
+ # Must be a space in the expression that caused the >3 thing
+ varName = stmtBits[0]
+ expression = ' '.join (stmtBits[1:])
+ else:
+ # Only two bits
+ varName = stmtBits[0]
+ expression = ' '.join (stmtBits[1:])
+
+ commandArgs.append ((isLocal, varName, expression))
+ return (TAL_DEFINE, commandArgs)
+
+ def compileCmdCondition (self, argument):
+ # Compile a condition command, resulting argument is:
+ # path, endTagSymbol
+ # Sanity check
+ if (len (argument) == 0):
+ # No argument passed
+ msg = "No argument passed! condition commands must be of the form: 'path'"
+ self.log.error (msg)
+ raise TemplateParseException (self.tagAsText (self.currentStartTag), msg)
+
+ return (TAL_CONDITION, (argument, self.endTagSymbol))
+
+ def compileCmdRepeat (self, argument):
+ # Compile a repeat command, resulting argument is:
+ # (varname, expression, endTagSymbol)
+ attProps = argument.split (' ')
+ if (len (attProps) < 2):
+ # Error, badly formed repeat command
+ msg = "Badly formed repeat command '%s'. Repeat commands must be of the form: 'localVariable path'" % argument
+ self.log.error (msg)
+ raise TemplateParseException (self.tagAsText (self.currentStartTag), msg)
+
+ varName = attProps [0]
+ expression = " ".join (attProps[1:])
+ return (TAL_REPEAT, (varName, expression, self.endTagSymbol))
+
+ def compileCmdContent (self, argument, replaceFlag=0):
+ # Compile a content command, resulting argument is
+ # (replaceFlag, structureFlag, expression, endTagSymbol)
+
+ # Sanity check
+ if (len (argument) == 0):
+ # No argument passed
+ msg = "No argument passed! content/replace commands must be of the form: 'path'"
+ self.log.error (msg)
+ raise TemplateParseException (self.tagAsText (self.currentStartTag), msg)
+
+ structureFlag = 0
+ attProps = argument.split (' ')
+ if (len(attProps) > 1):
+ if (attProps[0] == "structure"):
+ structureFlag = 1
+ express = " ".join (attProps[1:])
+ elif (attProps[1] == "text"):
+ structureFlag = 0
+ express = " ".join (attProps[1:])
+ else:
+ # It's not a type selection after all - assume it's part of the path
+ express = argument
+ else:
+ express = argument
+ return (TAL_CONTENT, (replaceFlag, structureFlag, express, self.endTagSymbol))
+
+ def compileCmdReplace (self, argument):
+ return self.compileCmdContent (argument, replaceFlag=1)
+
+ def compileCmdAttributes (self, argument):
+ # Compile tal:attributes into attribute command
+ # Argument: [(attributeName, expression)]
+
+ # Break up the list of attribute settings first
+ commandArgs = []
+ # We only want to match semi-colons that are not escaped
+ argumentSplitter = re.compile ('(?")
+ else:
+ result.append (">")
+ return "".join (result)
+
+ def handle_startendtag (self, tag, attributes):
+ self.handle_starttag (tag, attributes)
+ if not (HTML_FORBIDDEN_ENDTAG.has_key (tag.upper())):
+ self.handle_endtag(tag)
+
+ def handle_starttag (self, tag, attributes):
+ self.log.debug ("Recieved Start Tag: " + tag + " Attributes: " + str (attributes))
+ atts = []
+ for att, attValue in attributes:
+ # We need to spot empty tal:omit-tags
+ if (attValue is None):
+ if (att == self.tal_namespace_omittag):
+ atts.append ((att, u""))
+ else:
+ atts.append ((att, att))
+ else:
+ # Expand any SGML entity references or char references
+ goodAttValue = []
+ last = 0
+ match = ENTITY_REF_REGEX.search (attValue)
+ while (match):
+ goodAttValue.append (attValue[last:match.start()])
+ ref = attValue[match.start():match.end()]
+ if (ref.startswith ('')):
+ # A char reference
+ if (ref[2] in ['x', 'X']):
+ # Hex
+ refValue = int (ref[3:-1], 16)
+ else:
+ refValue = int (ref[2:-1])
+ goodAttValue.append (unichr (refValue))
+ else:
+ # A named reference.
+ goodAttValue.append (unichr (sgmlentitynames.htmlNameToUnicodeNumber.get (ref[1:-1], 65533)))
+ last = match.end()
+ match = ENTITY_REF_REGEX.search (attValue, last)
+ goodAttValue.append (attValue [last:])
+ atts.append ((att, u"".join (goodAttValue)))
+
+ if (HTML_FORBIDDEN_ENDTAG.has_key (tag.upper())):
+ # This should have no end tag, so we just do the start and suppress the end
+ self.parseStartTag (tag, atts)
+ self.log.debug ("End tag forbidden, generating close tag with no output.")
+ self.popTag ((tag, None), omitTagFlag=1)
+ else:
+ self.parseStartTag (tag, atts)
+
+ def handle_endtag (self, tag):
+ self.log.debug ("Recieved End Tag: " + tag)
+ if (HTML_FORBIDDEN_ENDTAG.has_key (tag.upper())):
+ self.log.warn ("HTML 4.01 forbids end tags for the %s element" % tag)
+ else:
+ # Normal end tag
+ self.popTag ((tag, None))
+
+ def handle_data (self, data):
+ self.parseData (cgi.escape (data))
+
+ # These two methods are required so that we expand all character and entity references prior to parsing the template.
+ def handle_charref (self, ref):
+ self.log.debug ("Got Ref: %s", ref)
+ self.parseData (unichr (int (ref)))
+
+ def handle_entityref (self, ref):
+ self.log.debug ("Got Ref: %s", ref)
+ # Use handle_data so that <&> are re-encoded as required.
+ self.handle_data( unichr (sgmlentitynames.htmlNameToUnicodeNumber.get (ref, 65533)))
+
+ # Handle document type declarations
+ def handle_decl (self, data):
+ self.parseData (u'' % data)
+
+ # Pass comments through un-affected.
+ def handle_comment (self, data):
+ self.parseData (u'' % data)
+
+ def handle_pi (self, data):
+ self.log.debug ("Recieved processing instruction.")
+ self.parseData (u'%s>' % data)
+
+ def report_unbalanced (self, tag):
+ self.log.warn ("End tag %s present with no corresponding open tag.")
+
+ def getTemplate (self):
+ template = HTMLTemplate (self.commandList, self.macroMap, self.symbolLocationTable, minimizeBooleanAtts = self.minimizeBooleanAtts)
+ return template
+
+class XMLTemplateCompiler (TemplateCompiler, xml.sax.handler.ContentHandler, xml.sax.handler.DTDHandler, LexicalHandler):
+ def __init__ (self):
+ TemplateCompiler.__init__ (self)
+ xml.sax.handler.ContentHandler.__init__ (self)
+ self.doctype = None
+ self.log = logging.getLogger ("simpleTAL.XMLTemplateCompiler")
+ self.singletonElement = 0
+
+ def parseTemplate (self, file):
+ self.ourParser = xml.sax.make_parser()
+ self.log.debug ("Setting features of parser")
+ try:
+ self.ourParser.setFeature (xml.sax.handler.feature_external_ges, 0)
+ except:
+ pass
+ if use_lexical_handler:
+ self.ourParser.setProperty(xml.sax.handler.property_lexical_handler, self)
+
+ self.ourParser.setContentHandler (self)
+ self.ourParser.setDTDHandler (self)
+
+ self.ourParser.parse (file)
+
+ def parseDOM (self, dom):
+ if (not use_dom2sax):
+ self.log.critical ("PyXML is not available, DOM can not be parsed.")
+
+ self.ourParser = xml.dom.ext.Dom2Sax.Dom2SaxParser()
+ self.log.debug ("Setting features of parser")
+ if use_lexical_handler:
+ self.ourParser.setProperty(xml.sax.handler.property_lexical_handler, self)
+
+ self.ourParser.setContentHandler (self)
+ self.ourParser.setDTDHandler (self)
+
+ self.ourParser.parse (dom)
+
+ def startDTD(self, name, public_id, system_id):
+ self.log.debug ("Recieved DOCTYPE: " + name + " public_id: " + public_id + " system_id: " + system_id)
+ if public_id:
+ self.doctype = '' % (name, public_id, system_id,)
+ else:
+ self.doctype = '' % (name, system_id,)
+
+ def startElement (self, tag, attributes):
+ self.log.debug ("Recieved Real Start Tag: " + tag + " Attributes: " + str (attributes))
+ try:
+ xmlText = self.ourParser.getProperty (xml.sax.handler.property_xml_string)
+ if (SINGLETON_XML_REGEX.match (xmlText)):
+ # This is a singleton!
+ self.singletonElement=1
+ except xml.sax.SAXException, e:
+ # Parser doesn't support this property
+ pass
+ # Convert attributes into a list of tuples
+ atts = []
+ for att in attributes.getNames():
+ self.log.debug ("Attribute name %s has value %s" % (att, attributes[att]))
+ atts.append ((att, attributes [att]))
+ self.parseStartTag (tag, atts, singletonElement=self.singletonElement)
+
+ def endElement (self, tag):
+ self.log.debug ("Recieved Real End Tag: " + tag)
+ self.parseEndTag (tag)
+ self.singletonElement = 0
+
+ def characters (self, data):
+ #self.log.debug ("Recieved Real Data: " + data)
+ # Escape any data we recieve - we don't want any: <&> in there.
+ self.parseData (cgi.escape (data))
+
+ def processingInstruction (self, target, data):
+ self.log.debug ("Recieved processing instruction.")
+ self.parseData (u'%s %s?>' % (target, data))
+
+ def comment (self, data):
+ # This is only called if your XML parser supports the LexicalHandler interface.
+ self.parseData (u'' % data)
+
+ def getTemplate (self):
+ template = XMLTemplate (self.commandList, self.macroMap, self.symbolLocationTable, self.doctype)
+ return template
+
+def compileHTMLTemplate (template, inputEncoding="ISO-8859-1", minimizeBooleanAtts = 0):
+ """ Reads the templateFile and produces a compiled template.
+ To use the resulting template object call:
+ template.expand (context, outputFile)
+ """
+ if (isinstance (template, types.StringType) or isinstance (template, types.UnicodeType)):
+ # It's a string!
+ templateFile = StringIO.StringIO (template)
+ else:
+ templateFile = template
+ compiler = HTMLTemplateCompiler()
+ compiler.parseTemplate (templateFile, inputEncoding, minimizeBooleanAtts)
+ return compiler.getTemplate()
+
+def compileXMLTemplate (template):
+ """ Reads the templateFile and produces a compiled template.
+ To use the resulting template object call:
+ template.expand (context, outputFile)
+ """
+ if (isinstance (template, types.StringType)):
+ # It's a string!
+ templateFile = StringIO.StringIO (template)
+ else:
+ templateFile = template
+ compiler = XMLTemplateCompiler()
+ compiler.parseTemplate (templateFile)
+ return compiler.getTemplate()
+
+def compileDOMTemplate (template):
+ """ Traverses the DOM and produces a compiled template.
+ To use the resulting template object call:
+ template.expand (context, outputFile)
+ """
+ compiler = XMLTemplateCompiler ()
+ compiler.parseDOM (template)
+ return compiler.getTemplate()
+
diff --git a/src/libs/python/bongo/external/simpletal/simpleTALES.py b/src/libs/python/bongo/external/simpletal/simpleTALES.py
new file mode 100644
index 0000000..9a73a72
--- /dev/null
+++ b/src/libs/python/bongo/external/simpletal/simpleTALES.py
@@ -0,0 +1,651 @@
+""" simpleTALES Implementation
+
+ Copyright (c) 2005 Colin Stewart (http://www.owlfish.com/)
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ 3. The name of the author may not be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+ If you make any bug fixes or feature enhancements please let me know!
+
+ The classes in this module implement the TALES specification, used
+ by the simpleTAL module.
+
+ Module Dependencies: logging
+"""
+
+import types, sys
+
+try:
+ import logging
+except:
+ import DummyLogger as logging
+
+import bongo.external.simpletal as simpletal
+
+__version__ = simpletal.__version__
+
+DEFAULTVALUE = "This represents a Default value."
+
+class PathNotFoundException (Exception):
+ pass
+
+class ContextContentException (Exception):
+ """ This is raised when invalid content has been placed into the Context object.
+ For example using non-ascii characters instead of Unicode strings.
+ """
+ pass
+
+PATHNOTFOUNDEXCEPTION = PathNotFoundException()
+
+class ContextVariable:
+ def __init__ (self, value = None):
+ self.ourValue = value
+
+ def value (self, currentPath=None):
+ if (callable (self.ourValue)):
+ return apply (self.ourValue, ())
+ return self.ourValue
+
+ def rawValue (self):
+ return self.ourValue
+
+ def __str__ (self):
+ return repr (self.ourValue)
+
+class RepeatVariable (ContextVariable):
+ """ To be written"""
+ def __init__ (self, sequence):
+ ContextVariable.__init__ (self, 1)
+ self.sequence = sequence
+ self.position = 0
+ self.map = None
+
+ def value (self, currentPath=None):
+ if (self.map is None):
+ self.createMap()
+ return self.map
+
+ def rawValue (self):
+ return self.value()
+
+ def getCurrentValue (self):
+ return self.sequence [self.position]
+
+ def increment (self):
+ self.position += 1
+ if (self.position == len (self.sequence)):
+ raise IndexError ("Repeat Finished")
+
+ def createMap (self):
+ self.map = {}
+ self.map ['index'] = self.getIndex
+ self.map ['number'] = self.getNumber
+ self.map ['even'] = self.getEven
+ self.map ['odd'] = self.getOdd
+ self.map ['start'] = self.getStart
+ self.map ['end'] = self.getEnd
+ # TODO: first and last need to be implemented.
+ self.map ['length'] = len (self.sequence)
+ self.map ['letter'] = self.getLowerLetter
+ self.map ['Letter'] = self.getUpperLetter
+ self.map ['roman'] = self.getLowerRoman
+ self.map ['Roman'] = self.getUpperRoman
+
+ # Repeat implementation goes here
+ def getIndex (self):
+ return self.position
+
+ def getNumber (self):
+ return self.position + 1
+
+ def getEven (self):
+ if ((self.position % 2) != 0):
+ return 0
+ return 1
+
+ def getOdd (self):
+ if ((self.position % 2) == 0):
+ return 0
+ return 1
+
+ def getStart (self):
+ if (self.position == 0):
+ return 1
+ return 0
+
+ def getEnd (self):
+ if (self.position == len (self.sequence) - 1):
+ return 1
+ return 0
+
+ def getLowerLetter (self):
+ result = ""
+ nextCol = self.position
+ if (nextCol == 0):
+ return 'a'
+ while (nextCol > 0):
+ nextCol, thisCol = divmod (nextCol, 26)
+ result = chr (ord ('a') + thisCol) + result
+ return result
+
+ def getUpperLetter (self):
+ return self.getLowerLetter().upper()
+
+ def getLowerRoman (self):
+ romanNumeralList = (('m', 1000)
+ ,('cm', 900)
+ ,('d', 500)
+ ,('cd', 400)
+ ,('c', 100)
+ ,('xc', 90)
+ ,('l', 50)
+ ,('xl', 40)
+ ,('x', 10)
+ ,('ix', 9)
+ ,('v', 5)
+ ,('iv', 4)
+ ,('i', 1)
+ )
+ if (self.position > 3999):
+ # Roman numbers only supported up to 4000
+ return ' '
+ num = self.position + 1
+ result = ""
+ for roman, integer in romanNumeralList:
+ while (num >= integer):
+ result += roman
+ num -= integer
+ return result
+
+ def getUpperRoman (self):
+ return self.getLowerRoman().upper()
+
+class IteratorRepeatVariable (RepeatVariable):
+ def __init__ (self, sequence):
+ RepeatVariable.__init__ (self, sequence)
+ self.curValue = None
+ self.iterStatus = 0
+
+ def getCurrentValue (self):
+ if (self.iterStatus == 0):
+ self.iterStatus = 1
+ try:
+ self.curValue = self.sequence.next()
+ except StopIteration, e:
+ self.iterStatus = 2
+ raise IndexError ("Repeat Finished")
+ return self.curValue
+
+ def increment (self):
+ # Need this for the repeat variable functions.
+ self.position += 1
+ try:
+ self.curValue = self.sequence.next()
+ except StopIteration, e:
+ self.iterStatus = 2
+ raise IndexError ("Repeat Finished")
+
+ def createMap (self):
+ self.map = {}
+ self.map ['index'] = self.getIndex
+ self.map ['number'] = self.getNumber
+ self.map ['even'] = self.getEven
+ self.map ['odd'] = self.getOdd
+ self.map ['start'] = self.getStart
+ self.map ['end'] = self.getEnd
+ # TODO: first and last need to be implemented.
+ self.map ['length'] = sys.maxint
+ self.map ['letter'] = self.getLowerLetter
+ self.map ['Letter'] = self.getUpperLetter
+ self.map ['roman'] = self.getLowerRoman
+ self.map ['Roman'] = self.getUpperRoman
+
+ def getEnd (self):
+ if (self.iterStatus == 2):
+ return 1
+ return 0
+
+class PathFunctionVariable (ContextVariable):
+ def __init__ (self, func):
+ ContextVariable.__init__ (self, value = func)
+ self.func = func
+
+ def value (self, currentPath=None):
+ if (currentPath is not None):
+ index, paths = currentPath
+ result = ContextVariable (apply (self.func, ('/'.join (paths[index:]),)))
+ # Fast track the result
+ raise result
+
+class CachedFuncResult (ContextVariable):
+ def value (self, currentPath=None):
+ try:
+ return self.cachedValue
+ except:
+ self.cachedValue = ContextVariable.value (self)
+ return self.cachedValue
+
+ def clearCache (self):
+ try:
+ del self.cachedValue
+ except:
+ pass
+
+class PythonPathFunctions:
+ def __init__ (self, context):
+ self.context = context
+
+ def path (self, expr):
+ return self.context.evaluatePath (expr)
+
+ def string (self, expr):
+ return self.context.evaluateString (expr)
+
+ def exists (self, expr):
+ return self.context.evaluateExists (expr)
+
+ def nocall (self, expr):
+ return self.context.evaluateNoCall (expr)
+
+ def test (self, *arguments):
+ if (len (arguments) % 2):
+ # We have an odd number of arguments - which means the last one is a default
+ pairs = arguments[:-1]
+ defaultValue = arguments[-1]
+ else:
+ # No default - so use None
+ pairs = arguments
+ defaultValue = None
+
+ index = 0
+ while (index < len (pairs)):
+ test = pairs[index]
+ index += 1
+ value = pairs[index]
+ index += 1
+ if (test):
+ return value
+
+ return defaultValue
+
+class Context:
+ def __init__ (self, options=None, allowPythonPath=0):
+ self.allowPythonPath = allowPythonPath
+ self.globals = {}
+ self.locals = {}
+ self.localStack = []
+ self.repeatStack = []
+ self.populateDefaultVariables (options)
+ self.log = logging.getLogger ("simpleTALES.Context")
+ self.true = 1
+ self.false = 0
+ self.pythonPathFuncs = PythonPathFunctions (self)
+
+ def addRepeat (self, name, var, initialValue):
+ # Pop the current repeat map onto the stack
+ self.repeatStack.append (self.repeatMap)
+ self.repeatMap = self.repeatMap.copy()
+ self.repeatMap [name] = var
+ # Map this repeatMap into the global space
+ self.addGlobal ('repeat', self.repeatMap)
+
+ # Add in the locals
+ self.pushLocals()
+ self.setLocal (name, initialValue)
+
+ def removeRepeat (self, name):
+ # Bring the old repeat map back
+ self.repeatMap = self.repeatStack.pop()
+ # Map this repeatMap into the global space
+ self.addGlobal ('repeat', self.repeatMap)
+
+ def addGlobal (self, name, value):
+ self.globals[name] = value
+
+ def pushLocals (self):
+ # Push the current locals onto a stack so that we can safely over-ride them.
+ self.localStack.append (self.locals)
+ self.locals = self.locals.copy()
+
+ def setLocal (self, name, value):
+ # Override the current local if present with the new one
+ self.locals [name] = value
+
+ def popLocals (self):
+ self.locals = self.localStack.pop()
+
+ def evaluate (self, expr, originalAtts = None):
+ # Returns a ContextVariable
+ #self.log.debug ("Evaluating %s" % expr)
+ if (originalAtts is not None):
+ # Call from outside
+ self.globals['attrs'] = originalAtts
+ suppressException = 1
+ else:
+ suppressException = 0
+
+ # Supports path, exists, nocall, not, and string
+ expr = expr.strip ()
+ try:
+ if expr.startswith ('path:'):
+ return self.evaluatePath (expr[5:].lstrip ())
+ elif expr.startswith ('exists:'):
+ return self.evaluateExists (expr[7:].lstrip())
+ elif expr.startswith ('nocall:'):
+ return self.evaluateNoCall (expr[7:].lstrip())
+ elif expr.startswith ('not:'):
+ return self.evaluateNot (expr[4:].lstrip())
+ elif expr.startswith ('string:'):
+ return self.evaluateString (expr[7:].lstrip())
+ elif expr.startswith ('python:'):
+ return self.evaluatePython (expr[7:].lstrip())
+ else:
+ # Not specified - so it's a path
+ return self.evaluatePath (expr)
+ except PathNotFoundException, e:
+ if (suppressException):
+ return None
+ raise e
+
+ def evaluatePython (self, expr):
+ if (not self.allowPythonPath):
+ self.log.warn ("Parameter allowPythonPath is false. NOT Evaluating python expression %s" % expr)
+ return self.false
+ #self.log.debug ("Evaluating python expression %s" % expr)
+
+ globals={}
+ for name, value in self.globals.items():
+ if (isinstance (value, ContextVariable)): value = value.rawValue()
+ globals [name] = value
+ globals ['path'] = self.pythonPathFuncs.path
+ globals ['string'] = self.pythonPathFuncs.string
+ globals ['exists'] = self.pythonPathFuncs.exists
+ globals ['nocall'] = self.pythonPathFuncs.nocall
+ globals ['test'] = self.pythonPathFuncs.test
+
+ locals={}
+ for name, value in self.locals.items():
+ if (isinstance (value, ContextVariable)): value = value.rawValue()
+ locals [name] = value
+
+ try:
+ result = eval(expr, globals, locals)
+ if (isinstance (result, ContextVariable)):
+ return result.value()
+ return result
+ except Exception, e:
+ # An exception occured evaluating the template, return the exception as text
+ self.log.warn ("Exception occurred evaluating python path, exception: " + str (e))
+ return "Exception: %s" % str (e)
+
+ def evaluatePath (self, expr):
+ #self.log.debug ("Evaluating path expression %s" % expr)
+ allPaths = expr.split ('|')
+ if (len (allPaths) > 1):
+ for path in allPaths:
+ # Evaluate this path
+ try:
+ return self.evaluate (path.strip ())
+ except PathNotFoundException, e:
+ # Path didn't exist, try the next one
+ pass
+ # No paths evaluated - raise exception.
+ raise PATHNOTFOUNDEXCEPTION
+ else:
+ # A single path - so let's evaluate it.
+ # This *can* raise PathNotFoundException
+ return self.traversePath (allPaths[0])
+
+ def evaluateExists (self, expr):
+ #self.log.debug ("Evaluating %s to see if it exists" % expr)
+ allPaths = expr.split ('|')
+ # The first path is for us
+ # Return true if this first bit evaluates, otherwise test the rest
+ try:
+ result = self.traversePath (allPaths[0], canCall = 0)
+ return self.true
+ except PathNotFoundException, e:
+ # Look at the rest of the paths.
+ pass
+
+ for path in allPaths[1:]:
+ # Evaluate this path
+ try:
+ pathResult = self.evaluate (path.strip ())
+ # If this is part of a "exists: path1 | exists: path2" path then we need to look at the actual result.
+ if (pathResult):
+ return self.true
+ except PathNotFoundException, e:
+ pass
+ # If we get this far then there are *no* paths that exist.
+ return self.false
+
+ def evaluateNoCall (self, expr):
+ #self.log.debug ("Evaluating %s using nocall" % expr)
+ allPaths = expr.split ('|')
+ # The first path is for us
+ try:
+ return self.traversePath (allPaths[0], canCall = 0)
+ except PathNotFoundException, e:
+ # Try the rest of the paths.
+ pass
+
+ for path in allPaths[1:]:
+ # Evaluate this path
+ try:
+ return self.evaluate (path.strip ())
+ except PathNotFoundException, e:
+ pass
+ # No path evaluated - raise error
+ raise PATHNOTFOUNDEXCEPTION
+
+ def evaluateNot (self, expr):
+ #self.log.debug ("Evaluating NOT value of %s" % expr)
+
+ # Evaluate what I was passed
+ try:
+ pathResult = self.evaluate (expr)
+ except PathNotFoundException, e:
+ # In SimpleTAL the result of "not: no/such/path" should be TRUE not FALSE.
+ return self.true
+
+ if (pathResult is None):
+ # Value was Nothing
+ return self.true
+ if (pathResult == DEFAULTVALUE):
+ return self.false
+ try:
+ resultLen = len (pathResult)
+ if (resultLen > 0):
+ return self.false
+ else:
+ return self.true
+ except:
+ # Not a sequence object.
+ pass
+ if (not pathResult):
+ return self.true
+ # Everything else is true, so we return false!
+ return self.false
+
+ def evaluateString (self, expr):
+ #self.log.debug ("Evaluating String %s" % expr)
+ result = ""
+ skipCount = 0
+ for position in xrange (0,len (expr)):
+ if (skipCount > 0):
+ skipCount -= 1
+ else:
+ if (expr[position] == '$'):
+ try:
+ if (expr[position + 1] == '$'):
+ # Escaped $ sign
+ result += '$'
+ skipCount = 1
+ elif (expr[position + 1] == '{'):
+ # Looking for a path!
+ endPos = expr.find ('}', position + 1)
+ if (endPos > 0):
+ path = expr[position + 2:endPos]
+ # Evaluate the path - missing paths raise exceptions as normal.
+ try:
+ pathResult = self.evaluate (path)
+ except PathNotFoundException, e:
+ # This part of the path didn't evaluate to anything - leave blank
+ pathResult = u''
+ if (pathResult is not None):
+ if (isinstance (pathResult, types.UnicodeType)):
+ result += pathResult
+ else:
+ # THIS IS NOT A BUG!
+ # Use Unicode in Context if you aren't using Ascii!
+ result += unicode (pathResult)
+ skipCount = endPos - position
+ else:
+ # It's a variable
+ endPos = expr.find (' ', position + 1)
+ if (endPos == -1):
+ endPos = len (expr)
+ path = expr [position + 1:endPos]
+ # Evaluate the variable - missing paths raise exceptions as normal.
+ try:
+ pathResult = self.traversePath (path)
+ except PathNotFoundException, e:
+ # This part of the path didn't evaluate to anything - leave blank
+ pathResult = u''
+ if (pathResult is not None):
+ if (isinstance (pathResult, types.UnicodeType)):
+ result += pathResult
+ else:
+ # THIS IS NOT A BUG!
+ # Use Unicode in Context if you aren't using Ascii!
+ result += unicode (pathResult)
+ skipCount = endPos - position - 1
+ except IndexError, e:
+ # Trailing $ sign - just suppress it
+ self.log.warn ("Trailing $ detected")
+ pass
+ else:
+ result += expr[position]
+ return result
+
+ def traversePath (self, expr, canCall=1):
+ # canCall only applies to the *final* path destination, not points down the path.
+ # Check for and correct for trailing/leading quotes
+ if (expr.startswith ('"') or expr.startswith ("'")):
+ if (expr.endswith ('"') or expr.endswith ("'")):
+ expr = expr [1:-1]
+ else:
+ expr = expr [1:]
+ elif (expr.endswith ('"') or expr.endswith ("'")):
+ expr = expr [0:-1]
+ pathList = expr.split ('/')
+
+ path = pathList[0]
+ if path.startswith ('?'):
+ path = path[1:]
+ if self.locals.has_key(path):
+ path = self.locals[path]
+ if (isinstance (path, ContextVariable)): path = path.value()
+ elif (callable (path)):path = apply (path, ())
+
+ elif self.globals.has_key(path):
+ path = self.globals[path]
+ if (isinstance (path, ContextVariable)): path = path.value()
+ elif (callable (path)):path = apply (path, ())
+ #self.log.debug ("Dereferenced to %s" % path)
+ if self.locals.has_key(path):
+ val = self.locals[path]
+ elif self.globals.has_key(path):
+ val = self.globals[path]
+ else:
+ # If we can't find it then raise an exception
+ raise PATHNOTFOUNDEXCEPTION
+ index = 1
+ for path in pathList[1:]:
+ #self.log.debug ("Looking for path element %s" % path)
+ if path.startswith ('?'):
+ path = path[1:]
+ if self.locals.has_key(path):
+ path = self.locals[path]
+ if (isinstance (path, ContextVariable)): path = path.value()
+ elif (callable (path)):path = apply (path, ())
+ elif self.globals.has_key(path):
+ path = self.globals[path]
+ if (isinstance (path, ContextVariable)): path = path.value()
+ elif (callable (path)):path = apply (path, ())
+ #self.log.debug ("Dereferenced to %s" % path)
+ try:
+ if (isinstance (val, ContextVariable)): temp = val.value((index,pathList))
+ elif (callable (val)):temp = apply (val, ())
+ else: temp = val
+ except ContextVariable, e:
+ # Fast path for those functions that return values
+ return e.value()
+
+ if (hasattr (temp, path)):
+ val = getattr (temp, path)
+ else:
+ try:
+ try:
+ val = temp[path]
+ except TypeError:
+ val = temp[int(path)]
+ except:
+ #self.log.debug ("Not found.")
+ raise PATHNOTFOUNDEXCEPTION
+ index = index + 1
+ #self.log.debug ("Found value %s" % str (val))
+ if (canCall):
+ try:
+ if (isinstance (val, ContextVariable)): result = val.value((index,pathList))
+ elif (callable (val)):result = apply (val, ())
+ else: result = val
+ except ContextVariable, e:
+ # Fast path for those functions that return values
+ return e.value()
+ else:
+ if (isinstance (val, ContextVariable)): result = val.realValue
+ else: result = val
+ return result
+
+ def __str__ (self):
+ return "Globals: " + str (self.globals) + "Locals: " + str (self.locals)
+
+ def populateDefaultVariables (self, options):
+ vars = {}
+ self.repeatMap = {}
+ vars['nothing'] = None
+ vars['default'] = DEFAULTVALUE
+ vars['options'] = options
+ # To start with there are no repeats
+ vars['repeat'] = self.repeatMap
+ vars['attrs'] = None
+
+ # Add all of these to the global context
+ for name in vars.keys():
+ self.addGlobal (name,vars[name])
+
+ # Add also under CONTEXTS
+ self.addGlobal ('CONTEXTS', vars)
+
diff --git a/src/libs/python/bongo/external/simpletal/simpleTALUtils.py b/src/libs/python/bongo/external/simpletal/simpleTALUtils.py
new file mode 100644
index 0000000..5295098
--- /dev/null
+++ b/src/libs/python/bongo/external/simpletal/simpleTALUtils.py
@@ -0,0 +1,290 @@
+""" simpleTALUtils
+
+ Copyright (c) 2005 Colin Stewart (http://www.owlfish.com/)
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ 3. The name of the author may not be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+ If you make any bug fixes or feature enhancements please let me know!
+
+ This module is holds utilities that make using SimpleTAL easier.
+ Initially this is just the HTMLStructureCleaner class, used to clean
+ up HTML that can then be used as 'structure' content.
+
+ Module Dependencies: None
+"""
+
+import StringIO, os, stat, threading, sys, codecs, sgmllib, cgi, re, types
+import bongo.external.simpletal, bongo.external.simpleTAL
+
+__version__ = simpletal.__version__
+
+# This is used to check for already escaped attributes.
+ESCAPED_TEXT_REGEX=re.compile (r"\&\S+?;")
+
+class HTMLStructureCleaner (sgmllib.SGMLParser):
+ """ A helper class that takes HTML content and parses it, so converting
+ any stray '&', '<', or '>' symbols into their respective entity references.
+ """
+ def clean (self, content, encoding=None):
+ """ Takes the HTML content given, parses it, and converts stray markup.
+ The content can be either:
+ - A unicode string, in which case the encoding parameter is not required
+ - An ordinary string, in which case the encoding will be used
+ - A file-like object, in which case the encoding will be used if present
+
+ The method returns a unicode string which is suitable for addition to a
+ simpleTALES.Context object.
+ """
+ if (isinstance (content, types.StringType)):
+ # Not unicode, convert
+ converter = codecs.lookup (encoding)[1]
+ file = StringIO.StringIO (converter (content)[0])
+ elif (isinstance (content, types.UnicodeType)):
+ file = StringIO.StringIO (content)
+ else:
+ # Treat it as a file type object - and convert it if we have an encoding
+ if (encoding is not None):
+ converterStream = codecs.lookup (encoding)[2]
+ file = converterStream (content)
+ else:
+ file = content
+
+ self.outputFile = StringIO.StringIO (u"")
+ self.feed (file.read())
+ self.close()
+ return self.outputFile.getvalue()
+
+ def unknown_starttag (self, tag, attributes):
+ self.outputFile.write (tagAsText (tag, attributes))
+
+ def unknown_endtag (self, tag):
+ self.outputFile.write ('' + tag + '>')
+
+ def handle_data (self, data):
+ self.outputFile.write (cgi.escape (data))
+
+ def handle_charref (self, ref):
+ self.outputFile.write (u'%s;' % ref)
+
+ def handle_entityref (self, ref):
+ self.outputFile.write (u'&%s;' % ref)
+
+
+class FastStringOutput:
+ """ A very simple StringIO replacement that only provides write() and getvalue()
+ and is around 6% faster than StringIO.
+ """
+ def __init__ (self):
+ self.data = []
+
+ def write (self, data):
+ self.data.append (data)
+
+ def getvalue (self):
+ return "".join (self.data)
+
+class TemplateCache:
+ """ A TemplateCache is a multi-thread safe object that caches compiled templates.
+ This cache only works with file based templates, the ctime of the file is
+ checked on each hit, if the file has changed the template is re-compiled.
+ """
+ def __init__ (self):
+ self.templateCache = {}
+ self.cacheLock = threading.Lock()
+ self.hits = 0
+ self.misses = 0
+
+ def getTemplate (self, name, inputEncoding='ISO-8859-1'):
+ """ Name should be the path of a template file. If the path ends in 'xml' it is treated
+ as an XML Template, otherwise it's treated as an HTML Template. If the template file
+ has changed since the last cache it will be re-compiled.
+
+ inputEncoding is only used for HTML templates, and should be the encoding that the template
+ is stored in.
+ """
+ if (self.templateCache.has_key (name)):
+ template, oldctime = self.templateCache [name]
+ ctime = os.stat (name)[stat.ST_MTIME]
+ if (oldctime == ctime):
+ # Cache hit!
+ self.hits += 1
+ return template
+ # Cache miss, let's cache this template
+ return self._cacheTemplate_ (name, inputEncoding)
+
+ def getXMLTemplate (self, name):
+ """ Name should be the path of an XML template file.
+ """
+ if (self.templateCache.has_key (name)):
+ template, oldctime = self.templateCache [name]
+ ctime = os.stat (name)[stat.ST_MTIME]
+ if (oldctime == ctime):
+ # Cache hit!
+ self.hits += 1
+ return template
+ # Cache miss, let's cache this template
+ return self._cacheTemplate_ (name, None, xmlTemplate=1)
+
+ def _cacheTemplate_ (self, name, inputEncoding, xmlTemplate=0):
+ self.cacheLock.acquire ()
+ try:
+ tempFile = open (name, 'r')
+ if (xmlTemplate):
+ # We know it is XML
+ template = simpleTAL.compileXMLTemplate (tempFile)
+ else:
+ # We have to guess...
+ firstline = tempFile.readline()
+ tempFile.seek(0)
+ if (name [-3:] == "xml") or (firstline.strip ()[:5] == '"
+ return result
+
+class MacroExpansionInterpreter (simpleTAL.TemplateInterpreter):
+ def __init__ (self):
+ simpleTAL.TemplateInterpreter.__init__ (self)
+ # Override the standard interpreter way of doing things.
+ self.macroStateStack = []
+ self.commandHandler [simpleTAL.TAL_DEFINE] = self.cmdNoOp
+ self.commandHandler [simpleTAL.TAL_CONDITION] = self.cmdNoOp
+ self.commandHandler [simpleTAL.TAL_REPEAT] = self.cmdNoOp
+ self.commandHandler [simpleTAL.TAL_CONTENT] = self.cmdNoOp
+ self.commandHandler [simpleTAL.TAL_ATTRIBUTES] = self.cmdNoOp
+ self.commandHandler [simpleTAL.TAL_OMITTAG] = self.cmdNoOp
+ self.commandHandler [simpleTAL.TAL_START_SCOPE] = self.cmdStartScope
+ self.commandHandler [simpleTAL.TAL_OUTPUT] = self.cmdOutput
+ self.commandHandler [simpleTAL.TAL_STARTTAG] = self.cmdOutputStartTag
+ self.commandHandler [simpleTAL.TAL_ENDTAG_ENDSCOPE] = self.cmdEndTagEndScope
+ self.commandHandler [simpleTAL.METAL_USE_MACRO] = self.cmdUseMacro
+ self.commandHandler [simpleTAL.METAL_DEFINE_SLOT] = self.cmdDefineSlot
+ self.commandHandler [simpleTAL.TAL_NOOP] = self.cmdNoOp
+
+ self.inMacro = None
+ self.macroArg = None
+ # Original cmdOutput
+ # Original cmdEndTagEndScope
+
+ def popProgram (self):
+ self.inMacro = self.macroStateStack.pop()
+ simpleTAL.TemplateInterpreter.popProgram (self)
+
+ def pushProgram (self):
+ self.macroStateStack.append (self.inMacro)
+ simpleTAL.TemplateInterpreter.pushProgram (self)
+
+ def cmdOutputStartTag (self, command, args):
+ newAtts = []
+ for att, value in self.originalAttributes.items():
+ if (self.macroArg is not None and att == "metal:define-macro"):
+ newAtts.append (("metal:use-macro",self.macroArg))
+ elif (self.inMacro and att=="metal:define-slot"):
+ newAtts.append (("metal:fill-slot", value))
+ else:
+ newAtts.append ((att, value))
+ self.macroArg = None
+ self.currentAttributes = newAtts
+ simpleTAL.TemplateInterpreter.cmdOutputStartTag (self, command, args)
+
+ def cmdUseMacro (self, command, args):
+ simpleTAL.TemplateInterpreter.cmdUseMacro (self, command, args)
+ if (self.tagContent is not None):
+ # We have a macro, add the args to the in-macro list
+ self.inMacro = 1
+ self.macroArg = args[0]
+
+ def cmdEndTagEndScope (self, command, args):
+ # Args: tagName, omitFlag
+ if (self.tagContent is not None):
+ contentType, resultVal = self.tagContent
+ if (contentType):
+ if (isinstance (resultVal, simpleTAL.Template)):
+ # We have another template in the context, evaluate it!
+ # Save our state!
+ self.pushProgram()
+ resultVal.expandInline (self.context, self.file, self)
+ # Restore state
+ self.popProgram()
+ # End of the macro expansion (if any) so clear the parameters
+ self.slotParameters = {}
+ # End of the macro
+ self.inMacro = 0
+ else:
+ if (isinstance (resultVal, types.UnicodeType)):
+ self.file.write (resultVal)
+ elif (isinstance (resultVal, types.StringType)):
+ self.file.write (unicode (resultVal, 'ascii'))
+ else:
+ self.file.write (unicode (str (resultVal), 'ascii'))
+ else:
+ if (isinstance (resultVal, types.UnicodeType)):
+ self.file.write (cgi.escape (resultVal))
+ elif (isinstance (resultVal, types.StringType)):
+ self.file.write (cgi.escape (unicode (resultVal, 'ascii')))
+ else:
+ self.file.write (cgi.escape (unicode (str (resultVal), 'ascii')))
+
+ if (self.outputTag and not args[1]):
+ self.file.write ('' + args[0] + '>')
+
+ if (self.movePCBack is not None):
+ self.programCounter = self.movePCBack
+ return
+
+ if (self.localVarsDefined):
+ self.context.popLocals()
+
+ self.movePCForward,self.movePCBack,self.outputTag,self.originalAttributes,self.currentAttributes,self.repeatVariable,self.tagContent,self.localVarsDefined = self.scopeStack.pop()
+ self.programCounter += 1
+
+def ExpandMacros (context, template, outputEncoding="ISO-8859-1"):
+ out = StringIO.StringIO()
+ interp = MacroExpansionInterpreter()
+ interp.initialise (context, out)
+ template.expand (context, out, outputEncoding=outputEncoding, interpreter=interp)
+ # StringIO returns unicode, so we need to turn it back into native string
+ result = out.getvalue()
+ reencoder = codecs.lookup (outputEncoding)[0]
+ return reencoder (result)[0]
+
diff --git a/src/libs/python/bongo/store/StoreClient.py b/src/libs/python/bongo/store/StoreClient.py
index 30e70fa..58f1e01 100644
--- a/src/libs/python/bongo/store/StoreClient.py
+++ b/src/libs/python/bongo/store/StoreClient.py
@@ -16,6 +16,7 @@ class DocTypes:
Addressbook = 0x0004
Conversation = 0x0005
Calendar = 0x0006
+ Config = 0x0007
Folder = 0x1000
Shared = 0x2000
diff --git a/src/www/Bongo.rules b/src/www/Bongo.rules
index a265469..79e7875 100644
--- a/src/www/Bongo.rules
+++ b/src/www/Bongo.rules
@@ -11,6 +11,7 @@ htl10n_DATA := $(htl10n_jsfiles)
cssdir = $(htdocsdir)/css
dist_css_DATA := \
+ src/www/css/admin.css \
src/www/css/calendar.css \
src/www/css/composer.css \
src/www/css/contacts.css \
@@ -285,43 +286,36 @@ pkgpythonhawkeyedir = $(pkgpythondir)/hawkeye
pkgpythonhawkeye_PYTHON := \
src/www/bongo/hawkeye/__init__.py \
- src/www/bongo/hawkeye/AgentAttributes.py \
src/www/bongo/hawkeye/AgentsView.py \
src/www/bongo/hawkeye/Auth.py \
- src/www/bongo/hawkeye/Hawkeye.py \
+ src/www/bongo/hawkeye/BackupView.py \
src/www/bongo/hawkeye/HawkeyeHandler.py \
src/www/bongo/hawkeye/HawkeyePath.py \
src/www/bongo/hawkeye/RootView.py \
src/www/bongo/hawkeye/Server.py \
- src/www/bongo/hawkeye/SummaryView.py \
- src/www/bongo/hawkeye/UserAttributes.py \
+ src/www/bongo/hawkeye/ServerView.py \
+ src/www/bongo/hawkeye/SummaryView.py \
src/www/bongo/hawkeye/UsersView.py
-hawkeyepspdir = $(pkgdatadir)/hawkeye/psp
-dist_hawkeyepsp_DATA := \
- src/www/hawkeye/psp/footer.psp \
- src/www/hawkeye/psp/header.psp
+hawkeyetplrootdir = $(htdocsdir)/hawkeye/root
+dist_hawkeyetplroot_DATA := \
+ src/www/hawkeye/root/login.tpl \
+ src/www/hawkeye/root/test.tpl \
+ src/www/hawkeye/root/test-hello.tpl \
+ src/www/hawkeye/root/index.tpl
-hawkeyeagentspspdir = $(hawkeyepspdir)/agents
-dist_hawkeyeagentspsp_DATA := \
- src/www/hawkeye/psp/agents/agents.psp \
- src/www/hawkeye/psp/agents/edit.psp \
- src/www/hawkeye/psp/agents/index.psp
+hawkeyetplbackupdir = $(htdocsdir)/hawkeye/backup
+dist_hawkeyetplbackup_DATA := \
+ src/www/hawkeye/backup/index.tpl
-hawkeyeuserspspdir = $(hawkeyepspdir)/users
-dist_hawkeyeuserspsp_DATA := \
- src/www/hawkeye/psp/users/users.psp \
- src/www/hawkeye/psp/users/edit.psp \
- src/www/hawkeye/psp/users/index.psp
+hawkeyetplserverdir = $(htdocsdir)/hawkeye/server
+dist_hawkeyetplserver_DATA := \
+ src/www/hawkeye/server/index.tpl
-hawkeyesummarypspdir = $(hawkeyepspdir)/summary
-dist_hawkeyesummarypsp_DATA := \
- src/www/hawkeye/psp/summary/index.psp
-
-hawkeyerootpspdir = $(hawkeyepspdir)/root
-dist_hawkeyerootpsp_DATA := \
- src/www/hawkeye/psp/root/index.psp \
- src/www/hawkeye/psp/root/login.psp
+hawkeyetpldir = $(htdocsdir)/hawkeye
+dist_hawkeyetpl_DATA := \
+ src/www/hawkeye/footer.tpl \
+ src/www/hawkeye/header.tpl
src/www/bongo-standalone: $(top_builddir)/src/libs/python/bongo-python-wrapper
$(INSTALL) -m 755 $< $@
diff --git a/src/www/bongo-standalone.py b/src/www/bongo-standalone.py
index 5cf17e2..71d4ef6 100755
--- a/src/www/bongo-standalone.py
+++ b/src/www/bongo-standalone.py
@@ -363,17 +363,13 @@ class ThreadingSSLServer(ThreadingHTTPServer):
self.server_bind()
self.server_activate()
-# Find the htdocs dir if we're running from an svn checkout.
-# FIXME
-#binpath = os.path.abspath(os.path.dirname(sys.argv[0]))
-#
-#default_file_path = binpath
-#if binpath.startswith(Xpl.DEFAULT_LIBEXEC_DIR):
default_file_path = os.path.abspath(Xpl.DEFAULT_DATA_DIR+"/htdocs")
parser = OptionParser()
parser.add_option("-d", "--debug", dest="debug", action="store_true",
help="enable debugging output")
+parser.add_option("-D", "--devel", dest="devel", action="store_true",
+ help="development mode (implies -d)")
parser.add_option("", "--profile", dest="profile", action="store_true",
help="enable profiling output")
parser.add_option("", "--profile-memory", dest="profile_memory", action="store_true",
@@ -399,7 +395,7 @@ if __name__ == '__main__':
console.setFormatter(formatter)
logging.root.addHandler(console)
- if options.debug:
+ if options.debug or options.devel:
logging.root.setLevel(logging.DEBUG)
else:
logging.root.setLevel(logging.INFO)
@@ -415,6 +411,11 @@ if __name__ == '__main__':
% (os.path.basename(sys.argv[0]), Xpl.BONGO_USER)
sys.exit(1)
+ if options.devel:
+ default_file_path = os.path.abspath(os.path.dirname(sys.argv[0]))
+ HttpRequest.options["HawkeyeTmplRoot"] = "%s/%s" % (default_file_path, "hawkeye")
+ print "Devel: serving static files from %s" % default_file_path
+
if options.file_path:
httppath = options.file_path
diff --git a/src/www/bongo/dragonfly/Standalone.py b/src/www/bongo/dragonfly/Standalone.py
index 9983ab8..2f63f6e 100644
--- a/src/www/bongo/dragonfly/Standalone.py
+++ b/src/www/bongo/dragonfly/Standalone.py
@@ -21,6 +21,9 @@ class HttpServer:
class HttpRequest:
"""Make a SimpleHTTPRequestHandler look like a mod_python request"""
log = logging.getLogger("Dragonfly")
+ options = { "DragonflyUriRoot" : "/user",
+ "HawkeyeTmplRoot" : None, # determine this later
+ "HawkeyeUriRoot" : "/admin"}
def __init__(self, req, path, server=HttpServer()):
self.oldreq = req
@@ -56,14 +59,12 @@ class HttpRequest:
binpath = os.path.abspath(os.path.dirname(sys.argv[0]))
- tmplPath = os.path.abspath(os.path.join(Xpl.DEFAULT_DATA_DIR, "hawkeye", "psp"))
+ tmplPath = os.path.abspath(os.path.join(Xpl.DEFAULT_DATA_DIR, "htdocs/hawkeye"))
+ if self.options["HawkeyeTmplRoot"] == None:
+ self.options["HawkeyeTmplRoot"] = tmplPath
if not binpath.startswith(Xpl.DEFAULT_LIBEXEC_DIR):
- tmplPath = os.path.abspath(os.path.join(os.getcwd(), "hawkeye", "psp"))
-
- self.options = {"DragonflyUriRoot" : "/user",
- "HawkeyeTmplRoot" : tmplPath,
- "HawkeyeUriRoot" : "/admin"}
+ tmplPath = os.path.abspath(os.path.join(os.getcwd(), "hawkeye"))
self.form = None
reqtype = self.headers_in.get("Content-Type", None)
diff --git a/src/www/bongo/hawkeye/AgentAttributes.py b/src/www/bongo/hawkeye/AgentAttributes.py
deleted file mode 100644
index 4da3d2d..0000000
--- a/src/www/bongo/hawkeye/AgentAttributes.py
+++ /dev/null
@@ -1,210 +0,0 @@
-# a per-agent table of editable properties
-
-import types
-import bongo.admin.Schema as Schema
-import bongo.admin.Util as Util
-
-from bongo.bootstrap import mdb, msgapi
-
-class MultilineStringType(types.StringType):
- pass
-
-class MdbPathType(types.StringType):
- pass
-
-class Attribute:
- def __init__(self, type):
- self.type = type
- self.name = Util.PropertyArguments.get(type)
- self.prettyName = Util.PropertiesPrintable.get(type)
-
-class EditableAttribute(Attribute):
- def Validate(self, data):
- pass
-
-HawkeyeAttributes = {
- msgapi.A_BOUNCE_CC_POSTMASTER : EditableAttribute(types.BooleanType),
- msgapi.A_BOUNCE_RETURN : EditableAttribute(types.BooleanType),
- msgapi.A_CLIENT : EditableAttribute(MdbPathType),
- msgapi.A_DOMAIN : EditableAttribute(types.StringType),
- msgapi.A_MAXIMUM_ITEMS : EditableAttribute(types.IntType),
- msgapi.A_MESSAGE_LIMIT : EditableAttribute(types.IntType),
- msgapi.A_MESSAGE_STORE : EditableAttribute(MdbPathType),
- msgapi.A_MINIMUM_SPACE : EditableAttribute(types.IntType),
- msgapi.A_MONITORED_QUEUE : EditableAttribute(MdbPathType),
- msgapi.A_PORT : EditableAttribute(types.IntType),
- msgapi.A_QUEUE_INTERVAL : EditableAttribute(types.IntType),
- msgapi.A_QUEUE_SERVER : EditableAttribute(MdbPathType),
- msgapi.A_QUEUE_TIMEOUT : EditableAttribute(types.IntType),
- msgapi.A_QUOTA_MESSAGE : EditableAttribute(MultilineStringType),
- msgapi.A_ROUTING : EditableAttribute(types.BooleanType),
- msgapi.A_SCMS_DIRECTORY : EditableAttribute(types.StringType),
- msgapi.A_SCMS_SIZE_THRESHOLD : EditableAttribute(types.IntType),
- msgapi.A_SCMS_USER_THRESHOLD : EditableAttribute(types.IntType),
- msgapi.A_SMTP_ACCEPT_ETRN : EditableAttribute(types.BooleanType),
- msgapi.A_SMTP_SEND_ETRN : EditableAttribute(types.BooleanType),
- msgapi.A_SPOOL_DIRECTORY : EditableAttribute(types.StringType),
- msgapi.A_SSL_PORT : EditableAttribute(types.IntType),
- msgapi.A_STORE_SERVER : EditableAttribute(MdbPathType),
- msgapi.A_TIMEOUT : EditableAttribute(types.IntType),
- msgapi.A_TIME_INTERVAL : EditableAttribute(types.IntType),
- msgapi.A_UBE_REMOTE_AUTH_ONLY : EditableAttribute(types.BooleanType),
- msgapi.A_UBE_SMTP_AFTER_POP : EditableAttribute(types.BooleanType),
- }
-
-class HawkeyeAttributes:
- extraProps = {
- msgapi.C_ADDRESSBOOK : [ msgapi.A_MONITORED_QUEUE,
- msgapi.A_PORT ],
-
- msgapi.C_ANTISPAM : [ msgapi.A_MONITORED_QUEUE, ],
-
- msgapi.C_CONNMGR : [ msgapi.A_TIMEOUT ],
-
- msgapi.C_IMAP : [ msgapi.A_PORT,
- msgapi.A_SSL_PORT,
- msgapi.A_MONITORED_QUEUE ],
-
- msgapi.C_POP : [ msgapi.A_PORT,
- msgapi.A_SSL_PORT,
- msgapi.A_MONITORED_QUEUE ],
-
- msgapi.C_PROXY : [ msgapi.A_MAXIMUM_ITEMS,
- msgapi.A_TIME_INTERVAL,
- msgapi.A_MONITORED_QUEUE,
- msgapi.A_STORE_SERVER ],
-
- msgapi.C_QUEUE : [ msgapi.A_BOUNCE_RETURN,
- msgapi.A_BOUNCE_CC_POSTMASTER,
- msgapi.A_SPOOL_DIRECTORY,
- msgapi.A_QUOTA_MESSAGE,
- msgapi.A_QUEUE_TIMEOUT,
- msgapi.A_QUEUE_INTERVAL,
- msgapi.A_MINIMUM_SPACE,
- msgapi.A_CLIENT ],
-
- msgapi.C_SMTP : [ msgapi.A_DOMAIN,
- msgapi.A_MESSAGE_LIMIT,
- msgapi.A_MONITORED_QUEUE,
- msgapi.A_QUEUE_SERVER,
- msgapi.A_PORT,
- msgapi.A_SSL_PORT,
- msgapi.A_UBE_REMOTE_AUTH_ONLY,
- msgapi.A_ROUTING,
- msgapi.A_SMTP_ACCEPT_ETRN,
- msgapi.A_SMTP_SEND_ETRN,
- msgapi.A_UBE_SMTP_AFTER_POP ],
-
- msgapi.C_STORE : [ msgapi.A_CLIENT,
- msgapi.A_MESSAGE_STORE,
- msgapi.A_MINIMUM_SPACE,
- msgapi.A_SCMS_USER_THRESHOLD,
- msgapi.A_SCMS_SIZE_THRESHOLD,
- msgapi.A_SCMS_DIRECTORY ]
- }
-
- def __init__(self, type):
- self.editable = editable = []
- self.constant = constant = []
-
- self.editable.extend(self.extraProps.get(type, []))
-
- def GetConstant(self):
- return self.constant
-
- def GetEditable(self):
- return self.editable
-
-class AgentAttributes:
- def __init__(self, mdb, dn, values=None):
- self.schema = schema = Util.GetSchema()
-
- self.mdb = mdb
- self.dn = dn
- self.server, self.agent = dn.split("\\")
- self.newValues = None
-
- if values:
- self.newValues = self.CollapseValues(values)
-
- details = Util.GetObjectDetails(mdb, dn)
-
- self.agentType = agentType = details.get("Type")
-
- self.attrs = Util.GetAgentAttributes(mdb, dn)
- self.classAttrs = Util.GetClassAttributeObjects(agentType)
- self.typeAttrs = HawkeyeAttributes(agentType)
- self.disabled = self.attrs.get(msgapi.A_DISABLED)
-
- def CollapseValues(self, values):
- """Turn a FieldStorage object into a hash"""
- ret = {}
-
- for key in values.keys():
- ret[key] = values.getlist(key)
-
- return ret
-
- def IsEnabled(self):
- if len(self.disabled) and self.disabled[0]:
- return False
- return True
-
- def GetConstant(self):
- attrs = []
-
- constantAttrs = self.typeAttrs.GetConstant()
-
- for attr in constantAttrs:
- if not self.attrs.has_key(attr):
- continue
-
- id = attr
- name = Util.PropertiesPrintable.get(attr, attr)
- values = self.attrs.get(attr)
-
- if len(values) > 0:
- for value in values:
- attrs.append((id, name, value))
- else:
- attrs.append((id, name, ""))
-
- return attrs
-
- def GetEditable(self):
- attrs = []
-
- editableAttrs = self.typeAttrs.GetEditable()
-
- for attr in editableAttrs:
- if not self.attrs.has_key(attr):
- continue
-
- id = attr
- name = Util.PropertiesPrintable.get(attr, attr)
- values = self.attrs.get(attr)
-
- if len(values) > 0:
- for value in values:
- attrs.append((id, name, value))
- else:
- attrs.append((id, name, ""))
-
- return attrs
-
- def Validate(self):
- for key, value in self.newValues.iteritems():
- self.ValidateAttribute(key, value)
- return True
-
- def ValidateAttribute(self, attr, value):
- clsAttr = self.classAttrs.get(attr)
-
- if not clsAttr:
- raise ValueError("invalid attribute id: %s" % attr)
-
- return True
-
- def Save(self):
- Util.ModifyBongoAgent(self.mdb, self.server, self.agent, self.newValues)
- self.attrs = Util.GetAgentAttributes(self.mdb, self.dn)
diff --git a/src/www/bongo/hawkeye/Auth.py b/src/www/bongo/hawkeye/Auth.py
index 92d8416..0a00f76 100644
--- a/src/www/bongo/hawkeye/Auth.py
+++ b/src/www/bongo/hawkeye/Auth.py
@@ -1,5 +1,7 @@
import bongo.dragonfly.Auth
+from bongo.store.StoreClient import StoreClient
import bongo.dragonfly.BongoSession as BongoSession
+import time
def Login(req):
if not req.form:
@@ -12,30 +14,38 @@ def Login(req):
credUser = req.form["bongo-username"].value
credPass = req.form["bongo-password"].value
- if not bongo.dragonfly.Auth.CheckUserPass(credUser, credPass):
- req.log.debug("invalid auth credentials")
- if req.session:
+ store = None
+ try:
+ store = StoreClient(credUser, credUser, authPassword=credPass)
+ finally:
+ if store:
+ store.Quit()
+ else:
req.session.invalidate()
- return False
+ return False
req.session["credUser"] = credUser
req.session["credPass"] = credPass
- req.log.debug("updated session: %s", str(req.session))
-
+ req.session.save()
return True
def AcceptCredentials(req):
- req.log.debug("loaded session: %s", str(req.session))
-
credUser = req.session.get("credUser")
credPass = req.session.get("credPass")
-
- if not bongo.dragonfly.Auth.CheckUserPass(credUser, credPass):
- req.log.info("invalid auth credentials")
- req.session.invalidate()
+
+ if credUser==None and credPass==None:
+ # no session
return False
- return True
+ client = None
+ try:
+ client = StoreClient(credUser, credUser, authPassword=credPass)
+ finally:
+ if client:
+ client.Quit()
+ return True
+
+ return False
def authenhandler(req):
if AcceptCredentials(req):
diff --git a/src/www/bongo/hawkeye/BackupView.py b/src/www/bongo/hawkeye/BackupView.py
new file mode 100644
index 0000000..cfc38b0
--- /dev/null
+++ b/src/www/bongo/hawkeye/BackupView.py
@@ -0,0 +1,64 @@
+import bongo.dragonfly
+import bongo.dragonfly.BongoUtil
+from bongo.store.StoreClient import StoreClient, DocTypes
+import bongo.external.simplejson as simplejson
+from HawkeyeHandler import HawkeyeHandler
+from tarfile import TarInfo
+import datetime
+
+class BackupHandler(HawkeyeHandler):
+ def NeedsAuth(self, rp):
+ return True
+
+ def index_GET(self, req, rp):
+ today = datetime.date.today()
+ self.SetVariable("set", today.strftime("%y%m%d"))
+ return self.SendTemplate(req, rp, "index.tpl")
+
+ def download_GET(self, req, rp):
+ backup = ""
+ req.content_type = "application/x-tar"
+ for filename in ["manager", "avirus"]:
+ fullname = "/config/%s" % filename
+ content = self._getFile(req, fullname)
+ tf = TarInfo(fullname)
+ tf.size = len(content)
+ req.write(tf.tobuf())
+ req.write(content)
+ zerobuf = 512 - (len(content) % 512)
+ req.write("\0" * zerobuf)
+ return None
+
+ def index_POST(self, req, rp):
+ if not req.form:
+ return bongo.dragonfly.HTTP_UNAUTHORIZED
+
+ if not req.form.has_key("command"):
+ return bongo.dragonfly.HTTP_UNAUTHORIZED
+
+ config = self._getManagerFile(req)
+ if config.has_key("agents"):
+ for agent in config["agents"]:
+ if req.form.has_key(agent["name"]):
+ agent["enabled"] = True
+ else:
+ agent["enabled"] = False
+ else:
+ self.SetVariable("message", "Unable to read config")
+
+ self._setManagerFile(req, config)
+ return self.index_GET(req, rp)
+
+ def _getFile(self, req, filename):
+ store = None
+ try:
+ store = StoreClient(req.session["credUser"], req.session["credUser"], authPassword=req.session["credPass"])
+ store.Store("_system")
+ configfile = store.Read(filename)
+ store.Quit()
+ except:
+ if store:
+ store.Quit()
+ return None
+
+ return configfile
diff --git a/src/www/bongo/hawkeye/Hawkeye.py b/src/www/bongo/hawkeye/Hawkeye.py
deleted file mode 100644
index fd1961f..0000000
--- a/src/www/bongo/hawkeye/Hawkeye.py
+++ /dev/null
@@ -1,15 +0,0 @@
-# This file is imported by the Hawkeye header.psp file, so all its
-# functions can be used by any templates.
-
-import urllib
-
-def BuildAgentUrl(server, agent):
- return "/".join(("/admin/agents/edit",
- urllib.quote(server), urllib.quote(agent)))
-
-def BuildUserUrl(context, user):
- return "/".join(("/admin/users/edit",
- urllib.quote(context), urllib.quote(user)))
-
-def SplitQueryArgs(query):
- return [ urllib.unquote(s) for s in query.split("/") ]
diff --git a/src/www/bongo/hawkeye/HawkeyeHandler.py b/src/www/bongo/hawkeye/HawkeyeHandler.py
index 554c552..3dfb5e2 100644
--- a/src/www/bongo/hawkeye/HawkeyeHandler.py
+++ b/src/www/bongo/hawkeye/HawkeyeHandler.py
@@ -1,16 +1,10 @@
import os
-
-from bongo.dragonfly.BongoPSP import PSP
-from bongo.MDB import MDB
+from bongo.Template import Template
import bongo.dragonfly
class HawkeyeHandler:
def __init__(self):
- pass
-
- def GetMdb(self, req):
- session = req.session
- return MDB(session.get("credUser"), session.get("credPass"))
+ self._template = Template()
def NeedsAuth(self, rp):
return True
@@ -18,38 +12,37 @@ class HawkeyeHandler:
def ParsePath(self, rp):
pass
+ def SetVariable(self, name, value):
+ self._template.setVariable(name, value)
+
def index(self, req, rp):
return bongo.dragonfly.OK
- def SendTemplate(self, req, rp, file, vars={}):
+ def SendTemplate(self, req, rp, file):
path = os.path.join(rp.tmplPath, file)
-
- if os.path.exists(path):
- req.log.debug("sending template: %s", path)
- else:
- req.log.debug("template does not exist: %s", path)
- return bongo.dragonfly.HTTP_NOT_FOUND
-
req.content_type = "text/html"
- pspvars = {"rp" : rp}
- pspvars.update(vars)
+ if os.path.exists(path):
+ print "sending template: %s / %s" % (rp.tmplPath, file)
+ req.log.debug("sending template: %s", path)
+ else:
+ print "template does not exist: %s / %s " % (rp.tmplPath, file)
+ req.log.debug("template does not exist: %s", path)
+ req.write("Error
Template not found.
")
+ return bongo.dragonfly.HTTP_NOT_FOUND
- psp = None
+ self.SetVariable("message", "")
try:
- psp = PSP(req, path, vars=pspvars)
- psp.run()
+ t = self._template
+ t.setTemplatePath(rp.tmplRoot)
+ t.setTemplateUriRoot(rp.tmplUriRoot)
+ t.setTemplateFile(path)
+ t.Run(req)
return bongo.dragonfly.OK
except ImportError, e:
- req.log.debug("Could not import psp from mod_python", exc_info=1)
- req.write("")
- req.write("ERROR: %s
" % str(e))
- req.write("The standalone admin interface requires mod_python. Maybe it is not installed?
")
- req.write("")
-
- return bongo.dragonfly.HTTP_INTERNAL_SERVER_ERROR
- except:
- req.log.debug("Exception running psp", exc_info=1)
- if psp:
- psp.display_code()
+ req.write("ERROR
%s