PXTL

Python XML Templating Language
Implementation package ‘pxtl’ 1.4 [beta]

This package contains two implementations of PXTL: the interpreter-based reference implementation, transforming XML as DOM trees, and the compiler-based optimised implementation, for faster output of serialised XML in performance-critical applications such as web templating.

The pxtl package requires Python 1.5.2 or later, and is known to work up to at least Python 2.3. Python 1.6 or later is required if Unicode characters are to be handled correctly.

For details of the PXTL language itself, please see the accompanying language documentation.

Installation

The ‘pxtl’ directory is a pure-Python package which can be dropped into any directory on the Python path.

Alternatively, to get it installed and pre-compiled in one go, open a command line (shell), cd into the directory containing setup.py, and enter:

python setup.py install

Installing this way requires Python 1.6 or later (or the distutils package to be otherwise installed). Windows users for whom ‘python.exe’ is not in the command path may have to give a full pathname to python, eg. ‘C:\Python23\python.exe setup.py install’.

Usage

processFile

Use the function pxtl.processFile to load a PXTL document, run its code, and output the transformed file.

import os, pxtl
path= os.path.join(templateDir, 'hello.px')
pxtl.processFile(path)

The first argument path is required; it holds the local pathname of the template to process. There are several further optional arguments:

writer: a stream-like object to which to send the results. If this is not given, standard output (sys.stdout) is used.

globals: a dictionary to be used as ‘global scope’ for Python code in the template. Any values in the dictionary can be accessed by template code as a variable with the same name as the dictionary item’s key; any globals written by template code will be stored in the directory and may be read by the caller after the template has run. If this argument is not given, a new, empty scope is created.

pxtl.processFile('~/pxtl/film.px', globals= {'stars': 4})
<px:for range="stars">
  <img src="/img/star.gif" alt="*"/>
</px:for>

debug: if set to a true value, any exceptions that occur in running template code will be caught, and a debugging HTML page output instead of raising an error. This option is highly recommended for developing web pages (because it is much more descriptive than a plain traceback or web server error page), but should not be used on production servers (as showing script source and variables to users might leak security-sensitive information; also there is a marginal speed penalty).

headers: if set to a true value, the output will be prefixed with an appropriate HTTP ‘Content-Type’ header for the document. This may save CGI script authors a little effort.

pxtl.processFile('/web/pxtl/page.px', debug= 1, headers= 1)

dom: a DOM implementation to use for XML work. This must provide a DOMImplementation interface supporting the W3C DOM Level 3 Core and LS specifications. If this argument is not given, PXTL uses its own embedded DOM implementation ‘pxdom’.

bytecodebase: allows the location of bytecode cache files (.pxc) generated by the optimised implementation to be changed. This argument, if given, should be a mapping of source path keys to destination path values. Given multiple keys, the one that is the nearest ancestor to the template file is chosen. If no key is an ancestor, the path is not changed and the bytecode is stored in the same directory as the template as usual.

from os.path import join
bcb= {
  join(APPROOT, 'templates'): join(APPROOT, 'bytecode'),
  join(APPROOT, 'templates', 'tmp'): TEMPDIR,
}
path= join(APPROOT, 'templates', 'info', 'index.px')
pxtl.processFile(path, bytecodebase= bcb)

In the above example, bytecode would be stored as bytecode/info/index.pxc inside the APPROOT.

If the bytecodebase argument is set to None, no attempt will be made to store bytecode files.

pxtl.processFile normally uses the optimised implementation for speed, but in some cases (currently, when bytecodebase is set to None), it will use the reference implementation instead. If you want to call either implementation directly (eg. for testing), import the relevant module from the package and use pxtl.reference.processFile or pxtl.optimised.processFile with the same arguments.

Additional ‘reference’ functions

If you want pxtl to transform DOM trees directly, rather than dealing with the serialised XML documents, you should use the pxtl.reference.Transformer class.

import pxtl.reference
t= pxtl.reference.Transformer()
transformedDocument= t.transformDocument(document)

The object passed to transformDocument must implement the Document interface from the DOM Level 2 Core standard. If the document uses <px:import> elements, it must also support DOM Level 3 Core and LS.

The document object is generally transformed in-place. However in certain cases (particularly, changing a doctype with non-pxdom implementations) a copy may be made. In this case transformDocument will return the new copy instead of the original document.

After transformation, the Transformer object has the following readable properties:

Additional ‘optimised’ functions

If you wish to control the compilation of PXTL templates manually, you can do it using the pxtl.optimised.Compiler class. This takes a DOM tree and returns the text of a Python program to run it.

import pxtl.optimised
c= pxtl.optimised.Compiler()
program= c.compileDocument(document)
codeobject= compile(program, '<template>', 'exec')

The document will not be altered by compileDocument. Other arguments that may be passed are globals, an optional scope for the few code parts of PXTL templates that are evaluated at compile-time, and method, which if given will override any output method given in a doctype attribute or guessed automatically.

After compilation, the Transformer object has the following readable property:

Other functions

The pxtl module contains two other functions that applications may occasionally want to use.

encodeText(text, coding): takes a string text, encodes it and returns it. The coding argument must be a string name of one of the PXTL coding formats, as used in PIs, for example 'upar'.

decodeName(name): takes a string name as encoded by the px_name PI/pseudo-PI and returns the original, decoded string. This may be needed if such a value is submitted in a form.

PXTL ‘server pages’

Normally it is expected that a PXTL page will be invoked and processed by a CGI script or other component of a web application framework.

It is, however, also possible to have a web server call a PXTL template directly. The template can contain code to process form submissions, take actions and output a page. This is the ‘server pages’ model employed by frameworks such as PHP, ASP and JSP.

This is not always considered an ideal approach to web application architecture, but it can certainly be useful for rapid prototyping. If you want PXTL to work this way you must configure your web server to associate .px files with PXTL.

(Note: in any case, if you are putting .px files in your web root, you should either use a bytecodebase outside the web root, or configure the server to block access to .pxc files. Otherwise users may be able to see your scripts’ cached bytecode, which could be a security issue.)

Apache

To enable PXTL server pages in Apache, create a .cgi file executable by Apache (eg. in the cgi-bin directory, with read and execute permission for all), containing:

#!/usr/bin/env python
import os, pxtl
pxtl.processFile(os.environ['PATH_TRANSLATED'], headers= 1)

Add the argument debug= 1 to the last line if you want PXTL to output a debugging page in case of errors.

Finally, add the following configuration directives to Apache‘s httpd.conf (or .htaccess):

AddHandler pxtl-template .px
Action pxtl-template /cgi-bin/pxtl.cgi

Change the Action path if your CGI script is elsewhere.

IIS

To enable PXTL server pages in IIS, open Internet Services Manager (from Administrative Tools) and get the ‘Properties’ of the web site you wish to configure. Go to the ‘Home Directory’ tab, choose ‘Configuration...’ and add an application mapping for extension ‘.px’ with the value:

"PYPATH\python.exe" -u "PXPATH\pxtl\__init__.py" "%s"

If you want to use PXTL’s debugging page if an error occurs in the template code, add a -d switch:

"PYPATH\python.exe" -u "PXPATH\pxtl\__init__.py" -d "%s"

‘PYPATH’ must be substituted with the place where your Python executable is stored, for example ‘C:\Python23’; ‘PXPATH’ must be substituted with the place where your pxtl package is stored, for example ‘C:\Python23\Lib\site-packages’.

DOM implementations

pxdom

The pxtl package includes a complete stand-alone pure-Python DOM 3 Core and LS implementation, which it uses by default. If you want to use it in your script, for example to create a DOM tree to pass to a Transformer or Compiler, use the module pxtl.pxdom as a DOMImplementationSource:

import pxtl.pxdom
implementation= pxtl.pxdom.getDOMImplementation('')

Then use DOM 3 methods such as createLSParser. Alternatively, the pxdom module offers the same convenience methods parse and parseString as minidom does.

More information on pxdom and using it in other applications.

Other DOMs

Any DOM implementation supporting Level 3 Core and the parsing interfaces from Level 3 LS can be used by the pxtl package, by passing its DOMImplementation object in the dom argument to processFile, or one of its Document objects to Transformer.transformDocument or Compiler.compileDocument.

Unfortunately at the time of writing the commonly-used implementations ‘minidom’ (part of the standard library, and updated by the PyXML package) and ‘4DOM’ (part of PyXML) do not support DOM Level 3. There are also some known compliance issues; further, some older versions are quite badly bugged.

To aid interoperability with applications that use these implementations, the pxtl package includes a module that provides wrappers to them, which support just enough DOM Level 3 for pxtl to work. Some of the bugs in older versions that affect pxtl are also dynamically patched. There are still issues (for example comments, CDATA sections and entity references may go missing with minidom), but generally pxtl should run.

Use the function pxtl.fixdom.getImplementation to access these wrapper implementations. The function takes one argument, name, which should be either ‘4DOM’ or ‘minidom’. The relevant DOMImplementation object is returned. To create a document from it, use the method createLSParser on it, then call parseURI on the LSParser object. Other DOM 3 Load methods are not supported.

It should be noted that the techniques used to patch the implementations are unreliable, and the end results might not be completely stable. It is possible future versions of minidom and 4DOM will break the fixdom patches to them. It is also possible that at some point in the future they may support DOM Level 3 well enough to be used directly without the fixdom patches.

Changelog

Updates from 1.3 to 1.4

Updates from 1.2 to 1.3

Updates from 1.1 to 1.2

Updates from 1.0 to 1.1

Updates from 0.9 to 1.0

Licence (new-BSD-style)

Copyright © 2004, Andrew Clover. All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

This software is provided by the copyright holder and contributors “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 copyright owner or contributors 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.