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