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 a Python version between 2.2 and 2.7.
For details of the PXTL language itself, please see the accompanying language documentation.
The ‘pxtl’ directory is a pure-Python package which can be dropped into any directory on the Python path.
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
Windows users for whom ‘python.exe’
is not in the command path may have to give a full pathname to python,
for example C:\Python23\python.exe setup.py install
.
The functions pxtl.processFile
, pxtl.processString
and pxtl.processURI
provide the main interface to the package.
processFile
(which was the only method available prior to pxtl 1.9) is typically
used, to load a PXTL document from the filesystem, run its code, and output the transformed file:
import os, pxtl
path= os.path.join(templateDir, 'hello.px')
pxtl.processFile(path)
The process
functions will use the optimised implementation by default.
If you want to use a specific implementation, you can call
it explicitly, using the same function names under pxtl.reference
and
pxtl.optimised
.
Note: when the optimised implementation is in use, as it is by default, compiled
bytecode will be stored in the same folder as the source template. Either the current user should have
permissions to do this, or you should pre-compile your templates after changing the (eg using
pxtl.optimised.compileAll
) to ensure the bytecode is present, otherwise
the template will have to be recompiled every time processFile is called. This is typically acceptable on a development server but
undesirable in production. If you would rather store bytecode elsewhere, see the resolver argument.
Whichever implementation you use, the arguments are the same. The first argument
is required and gives the iunput source: the path for processFile
, the uri
for processURI
or the string content (bytes or unicode; however
templates processed this way have no URI address so can't use relative import
s).
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. Use a StringIO
if you wish to capture the
output in a string.
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= True, headers= True)
dom: a DOMImplementation to use for XML work. This should normally be left unspecified, to use the embedded DOM implementation ‘pxdom’, which is the only sufficiently-compliant DOM Level 3 Core/LS implementation at the time of writing.
resolver: an object implementing the Resolver
interface, to redirect access to template resources and compiled bytecode. If omitted,
a StandardResolver
object is used.
In the processFile
method only, for
backwards compatibility, this argument may be a dictionary argument named
bytecodebase
, as a shortcut to a BytecodeResolver
.
Resolvers allow access to template files and/or bytecode files to be
redirected from the given absolute URI (as passed to processURI
or
read from an import
element) to some other resource, either
by changing the URI, or completely replacing the storage (eg to put templates in a database).
You can write your own resolvers by implementing the getResource(uri, ctype)
and setResource(uri, ctype, content)
methods; see the
code/docstrings in pxtl.resolvers.Resolver
for guidance. The
pxtl.resolvers
module contains some concrete
resolver implementations for common cases:
StandardResolver
: the normal default resolver. Fetches URIs using
urllib
, and writes bytecode only for filesystem URIs, under a filename
changed to add a ‘c’ to the extension.
ReadonlyResolver
: never stores bytecode.
BytecodeResolver
: allows the location of bytecode storage to be
changed. This resolver is passed a mapping of source URI keys to destination URI 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.
Optionally, if initialised with the isfilebased argument, operate on filepaths instead of URIs:
from os.path import join
resolver= pxtl.resolvers.BytecodeResolver({
'/www/app/templates': '/www/app/bytecode',
'/www/app/templates/tmp': '/www/app/tmpdir/bytecode',
}, isfilebased= True)
pxtl.processFile('/www/app/templates/info/index.px', resolver= resolver)
(In the above example, bytecode would be stored as /www/app/bytecode/info/index.pxc
inside the APPROOT.)
LookupResolver
: a simple in-memory dictionary of URIs to
resource content. This is good for long-lived servers with a simple set of templates
which can be compiled once and left in memory. It should be initialised with a lookup
dictionary argument mapping URIs to (content, ctype, mtime)
tuples.
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:
px:doctype
attribute, this is guessed as either ‘xml' or ‘xhtml'.
px:doctype
attribute,
None
if not specified
px_mark
PI, pxtl.write(..., 'mark')
commands and px:import
with an XML media type.
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:
Used to pre-compile the bytecode files used by the optimised implementation, so that
they can be loaded and executed quickly in the future without having to have write access
to the place where bytecode files are stored. This can be used as part of an application
install procedure in the same way as the standard library function py_compile.compile
(with doraise set). The files themselves are exactly the same format as
standard .pyc files, so are dependent on the version of Python used as well as the version
of PXTL; if either do not match the template will be recompiled at run-time.
The required arguments to pxtl.optimised.compileFile
are
path and pathc, giving the pathname of the template source file
to load and the compiled bytecode file to save, respectively.
Further optional arguments include globals and dom, which work
the same way as the processFile
arguments of the same
name. globals is only used as the scope for calculating the contents of the
doctype
attribute, the only part of a PXTL template run at
compile-time, so it too is not normally needed.
Finally there's method and encoding, which can be used to
override the output method (given in the px:doctype
value)
and the character encoding (given in the <?xml?>
declaration). These are only generally required where the imported template has a different
output method or encoding to the template that imports it (because in PXTL the importing
templateଁs doctype is always used in preference). Typically this happens when an
XHTML template to-be-imported omits px:doctype
, relying on
the importing template to set the method to xhtml.
Otherwise they can be omitted. If the output method and encoding are wrong when the template comes to be run, it will simply be recompiled, so there is not normally a problem, except when permissions don't allow the bytecode to be written, resulting in slow execution every time.
Compiles bytecode files recursively inside the given dir, analagous to the
standard library compileall
function.
Uses the optional argument resolver as in processFile
,
plus globals, dom, method and encoding from
compileFile
, and maxlevels and quiet from
the standard compileall
(so it defaults to 10 levels deep).
Having compiled a template or unmarshalled its code from a bytecode file, you can run it
using the standard Python exec
statement. However, it expects
a few internal PXTL variables to be put in its global scope before it can run, so that it knows,
for example, what stream to send the output to.
Use the prepareGlobals
function to write these internal
variables before calling exec code in mapping
. The arguments
are writer, baseuri, headers, dom
and bytecodebase.
baseuri should hold the URI of the source template, and can be
omitted if PXTL import
elements are not used, or if they are
only used with absolute-URI src
attributes. The other
arguments are the same as for processFile
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.
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.)
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= True)
Add the argument debug= True
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.
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’.
The pxtl package requires standards-compliant DOM Level 3 Core and LS functionality.
For this reason the it 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
which work the same as the Python standard
library module xml.dom.minidom.
More information on pxdom and using it in other applications.
If you have another DOM 3 implementation you would rather pxdom used, pass its DOMImplementation module into the processFile function's dom argument.
Copyright © 2005-2010, 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.