0001"""Pythonic, XML Templating
0002
0003Kid is a simple, Python-based template language for generating and
0004transforming XML vocabularies. Kid was spawned as a result of a kinky love
0005triangle between XSLT, TAL, and PHP. We believe many of the best features
0006of these languages live on in Kid with much of the limitations and
0007complexity stamped out (well, eventually :).
0008
0009"""
0010
0011__revision__ = "$Rev: 332 $"
0012__date__ = "$Date: 2006-05-20 22:26:35 +0000 (Sat, 20 May 2006) $"
0013
0014import release
0015__version__ = release.version
0016__author__ = release.author
0017__email__ = release.email
0018__copyright__ = release.copyright
0019__license__ = release.license
0020
0021
0022import sys
0023import os
0024
0025from kid.util import xml_sniff, QuickTextReader
0026from kid.namespace import Namespace
0027from kid.pull import ElementStream, Element, SubElement, Fragment, XML, document, _coalesce
0029from kid.et import ElementTree, Comment, ProcessingInstruction
0030from kid.parser import KID_XMLNS
0031from kid.serialization import Serializer, XMLSerializer, HTMLSerializer, PlainSerializer, XHTMLSerializer
0032
0033assume_encoding = sys.getdefaultencoding()
0034
0035def enable_import(suffixes=None):
0036 """Enable the kid module loader and import hooks.
0037
0038 This function must be called before importing kid templates if templates
0039 are not pre-compiled.
0040
0041 Note that if your application uses ZODB, you will need to import ZODB
0042 before calling this function as ZODB's import hooks have some issues if
0043 installed after the kid import hooks.
0044
0045 """
0046 import kid.importer
0047 kid.importer.install(suffixes)
0048
0049
0050
0051
0052if os.environ.get('KID_IMPORT', None) is not None:
0053 enable_import()
0054
0055def import_template(name):
0056 """Import template by name.
0057
0058 This is identical to calling `enable_import` followed by an import
0059 statement. For example, importing a template named foo using the normal
0060 import mechanism looks like this::
0061
0062 import kid
0063 kid.enable_import()
0064 import foo
0065
0066 This function can be used to achieve the same result as follows::
0067
0068 import kid
0069 foo = kid.import_template('foo')
0070
0071 This is sometimes useful when the name of the template is available only
0072 as a string.
0073 """
0074 enable_import()
0075 mod = __import__(name)
0076 components = name.split('.')
0077 for comp in components[1:]:
0078 mod = getattr(mod, comp)
0079 return mod
0080
0081def load_template(file, name='', cache=1, encoding=None, ns={}):
0082 """Bypass import machinery and load a template module directly.
0083
0084 This can be used as an alternative to accessing templates using the
0085 native python import mechanisms.
0086
0087 file
0088 Can be a filename, a kid template string, or an open file object.
0089 name
0090 Optionally specifies the module name to use for this template. This
0091 is a hack to enable relative imports in templates.
0092 cache
0093 Whether to look for a byte-compiled version of the template. If
0094 no byte-compiled version is found, an attempt is made to dump a
0095 byte-compiled version after compiling. This argument is ignored if
0096 file is not a filename.
0097 """
0098 if isinstance(file, basestring):
0099 if xml_sniff(file):
0100 fo = QuickTextReader(file)
0101 filename = '<string>'
0102 else:
0103 fo = None
0104 filename = file
0105 else:
0106 fo = file
0107 filename = '<string>'
0108 import kid.importer as importer
0109 if filename != '<string>':
0110 abs_filename = path.find(filename)
0111 if not abs_filename:
0112 raise Exception, "Template not found: %s (in %s)" % (
0113 filename, ', '.join(path.paths))
0114 filename = abs_filename
0115 name = importer.get_template_name(name, filename)
0116 if sys.modules.has_key(name):
0117 return sys.modules.get(name)
0118 import kid.compiler as compiler
0119 if filename == '<string>':
0120 code = compiler.compile(fo, filename, encoding)
0121 else:
0122 template = compiler.KidFile(filename, 0, encoding)
0123 code = template.compile(dump_code=cache, dump_source=os.environ.get('KID_OUTPUT_PY'))
0124
0125 mod = importer._create_module(code, name, filename, store=cache, ns=ns)
0126 return mod
0127
0128
0129output_methods = {
0130 'xml' : XMLSerializer(decl=1),
0131 'xhtml' : XHTMLSerializer(decl=0, doctype='xhtml'),
0132 'xhtml-strict' : XHTMLSerializer(decl=0, doctype='xhtml-strict'),
0133 'html' : HTMLSerializer(doctype='html'),
0134 'html-strict' : HTMLSerializer(doctype='html-strict'),
0135 'plain': PlainSerializer()}
0136
0137def Template(file=None, source=None, name=None, **kw):
0138 """Get a Template class quickly given a module name, file, or string.
0139
0140 This is a convenience function for getting a template in a variety of
0141 ways. One and only one of the arguments name or file must be specified.
0142
0143 file:string
0144 The template module is loaded by calling
0145 ``load_template(file, name='', cache=1)``
0146 name:string
0147 The kid import hook is enabled and the template module is located
0148 using the normal Python import mechanisms.
0149 source:string
0150 string containing the templates source.
0151
0152 Once the template module is obtained, a new instance of the module's
0153 Template class is created with the keyword arguments passed to this
0154 function.
0155 """
0156 if name:
0157 mod = import_template(name)
0158 elif file is not None:
0159 mod = load_template(file)
0160 elif source is not None:
0161 mod = load_template(QuickTextReader(source), hex(id(source)))
0162 else:
0163 raise Exception("Must specify one of name, file, or source.")
0164 mod.Template.module = mod
0165 return mod.Template(**kw)
0166
0167from kid.filter import transform_filter
0168
0169class BaseTemplate(object):
0170
0171 """Base class for compiled Templates.
0172
0173 All kid template modules expose a class named ``Template`` that
0174 extends from this class making the methods defined here available on
0175 all Template subclasses.
0176
0177 This class should not be instantiated directly.
0178 """
0179
0180
0181 serializer = output_methods['xml']
0182 _filters = [transform_filter]
0183
0184 def __init__(self, *args, **kw):
0185 """
0186 Initialize a template with instance attributes specified by
0187 keyword arguments.
0188
0189 Keyword arguments are available to the template using self.var
0190 notation.
0191 """
0192 self.__dict__.update(kw)
0193 self._layout_classes = []
0194
0195 def write(self, file, encoding=None, fragment=0, output=None):
0196 """
0197 Execute template and write output to file.
0198
0199 file:file
0200 A filename or a file like object (must support write()).
0201 encoding:string
0202 The output encoding. Default: utf-8.
0203 fragment:bool
0204 Controls whether prologue information (such as <?xml?>
0205 declaration and DOCTYPE should be written). Set to 1
0206 when generating fragments meant to be inserted into
0207 existing XML documents.
0208 output:string,`Serializer`
0209 A string specifying an output method ('xml', 'html',
0210 'xhtml') or a Serializer object.
0211 """
0212 serializer = self._get_serializer(output)
0213 return serializer.write(self, file, encoding, fragment)
0214
0215 def serialize(self, encoding=None, fragment=0, output=None):
0216 """
0217 Execute a template and return a single string.
0218
0219 encoding
0220 The output encoding. Default: utf-8.
0221 fragment
0222 Controls whether prologue information (such as <?xml?>
0223 declaration and DOCTYPE should be written). Set to 1
0224 when generating fragments meant to be inserted into
0225 existing XML documents.
0226 output
0227 A string specifying an output method ('xml', 'html',
0228 'xhtml') or a Serializer object.
0229
0230 This is a convienence method, roughly equivalent to::
0231
0232 ''.join([x for x in obj.generate(encoding, fragment, output)]
0233
0234 """
0235 serializer = self._get_serializer(output)
0236 return serializer.serialize(self, encoding, fragment)
0237
0238 def generate(self, encoding=None, fragment=0, output=None):
0239 """
0240 Execute template and generate serialized output incrementally.
0241
0242 This method returns an iterator that yields an encoded string
0243 for each iteration. The iteration ends when the template is done
0244 executing.
0245
0246 encoding
0247 The output encoding. Default: utf-8.
0248 fragment
0249 Controls whether prologue information (such as <?xml?>
0250 declaration and DOCTYPE should be written). Set to 1
0251 when generating fragments meant to be inserted into
0252 existing XML documents.
0253 output
0254 A string specifying an output method ('xml', 'html',
0255 'xhtml') or a Serializer object.
0256 """
0257 serializer = self._get_serializer(output)
0258 return serializer.generate(self, encoding, fragment)
0259
0260 def __iter__(self):
0261 return iter(self.transform())
0262
0263 def __str__(self):
0264 return self.serialize()
0265
0266 def __unicode__(self):
0267 return unicode(self.serialize(encoding='utf-16'), 'utf-16')
0268
0269 def initialize(self):
0270 pass
0271
0272 def pull(self):
0273 """Returns an iterator over the items in this template."""
0274
0275 self.initialize()
0276 stream = ElementStream(_coalesce(self.content(), self._get_assume_encoding()))
0277 return stream
0278
0279 def _pull(self):
0280 """Generate events for this template.
0281
0282 Compiled templates implement this method.
0283 """
0284 return []
0285
0286 def content(self):
0287 from inspect import getmro
0288 visited = self._layout_classes
0289 mro = list(getmro(self.__class__))
0290 mro.reverse()
0291 for c in mro:
0292 if c.__dict__.has_key('layout') and c not in visited:
0293 visited.insert(0, c)
0294 return c.__dict__['layout'](self)
0295 return self._pull()
0296
0297 def transform(self, stream=None, filters=[]):
0298 """
0299 Execute the template and apply any match transformations.
0300
0301 If stream is specified, it must be one of the following:
0302
0303 Element
0304 An ElementTree Element.
0305 ElementStream
0306 An `pull.ElementStream` instance or other iterator that yields
0307 stream events.
0308 string
0309 A file or URL unless the string starts with
0310 '<' in which case it is considered an XML document
0311 and processed as if it had been an Element.
0312
0313 By default, the `pull` method is called to obtain the stream.
0314 """
0315 if stream is None:
0316 stream = self.pull()
0317 elif isinstance(stream, basestring):
0318 if xml_sniff(stream):
0319 stream = XML(stream, fragment=0)
0320 else:
0321 stream = document(stream)
0322 elif hasattr(stream, 'tag'):
0323 stream = ElementStream(stream)
0324 else:
0325 stream = ElementStream.ensure(stream)
0326 for f in filters + self._filters:
0327 stream = f(stream, self)
0328 return stream
0329
0330 def _get_match_templates(self):
0331
0332 try:
0333 rslt = self._match_templates_cached
0334 except AttributeError:
0335 rslt = []
0336 mro = self.__class__.__mro__
0337 for C in mro:
0338 try:
0339 templates = C._match_templates
0340 except AttributeError:
0341 continue
0342 rslt += templates
0343 self._match_templates_cached = rslt
0344 return rslt
0345
0346 def _get_serializer(self, serializer):
0347 if serializer is None:
0348 return self.serializer
0349 elif isinstance(serializer, basestring):
0350 return output_methods[serializer]
0351 else:
0352 return serializer
0353
0354 def _get_assume_encoding(self):
0355 global assume_encoding
0356
0357 if hasattr(self, "assume_encoding"):
0358 return self.assume_encoding
0359 else:
0360 return assume_encoding
0361
0362 def defined(self, name):
0363 return hasattr(self, name)
0364
0365 def value_of(self, name, default=None):
0366 return getattr(self, name, default)
0367
0368class TemplatePath(object):
0369 def __init__(self, paths=None):
0370 if isinstance(paths, basestring):
0371 paths = [paths]
0372 elif paths is None:
0373 paths = []
0374 paths += [os.getcwd(), '/']
0375 self.paths = [self._cleanse_path(p) for p in paths]
0376
0377 def _cleanse_path(self, path):
0378 from os.path import normpath, expanduser, abspath
0379 return abspath(normpath(expanduser(path)))
0380
0381 def insert(self, path, pos=0):
0382 self.paths.insert(pos, self._cleanse_path(path))
0383
0384 def append(self, path):
0385 self.paths.append(self._cleanse_path(path))
0386
0387 def find(self, path, rel="/"):
0388 from os.path import normpath, join, exists, dirname
0389 path = normpath(path)
0390 for p in self.paths + [dirname(rel)]:
0391 p = join(p, path)
0392 if exists(p):
0393 return p
0394
0395path = TemplatePath()
0396
0397import kid.properties
0398properties = kid.properties
0399
0400__all__ = ['KID_XMLNS', 'BaseTemplate', 'Template',
0401 'enable_import', 'import_template', 'load_template',
0402 'Element', 'SubElement', 'XML', 'document', 'Namespace',
0403 'Serializer', 'XMLSerializer', 'HTMLSerializer', 'XHTMLSerializer', 'output_methods',
0404 'filter', 'namespace', 'serialization', 'util', 'properties']