Commit e052fb6b authored by Andreas Jung's avatar Andreas Jung

you're time has come, no longer needed for Z3 ZPTs

parent 1c0af142
TAL changes
This file contains change information for the current release.
Change information for previous versions can be found in the
file HISTORY.txt.
Version 1.5.0
Features Added
- Line and column numbers are added to more exceptions.
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""
Dummy TALES engine so that I can test out the TAL implementation.
"""
import re
import sys
from TALDefs import NAME_RE, TALESError, ErrorInfo
from ITALES import ITALESCompiler, ITALESEngine
from DocumentTemplate.DT_Util import ustr
class _Default:
pass
Default = _Default()
name_match = re.compile(r"(?s)(%s):(.*)\Z" % NAME_RE).match
class CompilerError(Exception):
pass
class DummyEngine:
position = None
source_file = None
__implements__ = ITALESCompiler, ITALESEngine
def __init__(self, macros=None):
if macros is None:
macros = {}
self.macros = macros
dict = {'nothing': None, 'default': Default}
self.locals = self.globals = dict
self.stack = [dict]
self.translationService = DummyTranslationService()
def getCompilerError(self):
return CompilerError
def getCompiler(self):
return self
def setSourceFile(self, source_file):
self.source_file = source_file
def setPosition(self, position):
self.position = position
def compile(self, expr):
return "$%s$" % expr
def uncompile(self, expression):
assert (expression.startswith("$") and expression.endswith("$"),
expression)
return expression[1:-1]
def beginScope(self):
self.stack.append(self.locals)
def endScope(self):
assert len(self.stack) > 1, "more endScope() than beginScope() calls"
self.locals = self.stack.pop()
def setLocal(self, name, value):
if self.locals is self.stack[-1]:
# Unmerge this scope's locals from previous scope of first set
self.locals = self.locals.copy()
self.locals[name] = value
def setGlobal(self, name, value):
self.globals[name] = value
def evaluate(self, expression):
assert (expression.startswith("$") and expression.endswith("$"),
expression)
expression = expression[1:-1]
m = name_match(expression)
if m:
type, expr = m.group(1, 2)
else:
type = "path"
expr = expression
if type in ("string", "str"):
return expr
if type in ("path", "var", "global", "local"):
return self.evaluatePathOrVar(expr)
if type == "not":
return not self.evaluate(expr)
if type == "exists":
return self.locals.has_key(expr) or self.globals.has_key(expr)
if type == "python":
try:
return eval(expr, self.globals, self.locals)
except:
raise TALESError("evaluation error in %s" % `expr`)
if type == "position":
# Insert the current source file name, line number,
# and column offset.
if self.position:
lineno, offset = self.position
else:
lineno, offset = None, None
return '%s (%s,%s)' % (self.source_file, lineno, offset)
raise TALESError("unrecognized expression: " + `expression`)
def evaluatePathOrVar(self, expr):
expr = expr.strip()
if self.locals.has_key(expr):
return self.locals[expr]
elif self.globals.has_key(expr):
return self.globals[expr]
else:
raise TALESError("unknown variable: %s" % `expr`)
def evaluateValue(self, expr):
return self.evaluate(expr)
def evaluateBoolean(self, expr):
return self.evaluate(expr)
def evaluateText(self, expr):
text = self.evaluate(expr)
if text is not None and text is not Default:
text = ustr(text)
return text
def evaluateStructure(self, expr):
# XXX Should return None or a DOM tree
return self.evaluate(expr)
def evaluateSequence(self, expr):
# XXX Should return a sequence
return self.evaluate(expr)
def evaluateMacro(self, macroName):
assert (macroName.startswith("$") and macroName.endswith("$"),
macroName)
macroName = macroName[1:-1]
file, localName = self.findMacroFile(macroName)
if not file:
# Local macro
macro = self.macros[localName]
else:
# External macro
import driver
program, macros = driver.compilefile(file)
macro = macros.get(localName)
if not macro:
raise TALESError("macro %s not found in file %s" %
(localName, file))
return macro
def findMacroDocument(self, macroName):
file, localName = self.findMacroFile(macroName)
if not file:
return file, localName
import driver
doc = driver.parsefile(file)
return doc, localName
def findMacroFile(self, macroName):
if not macroName:
raise TALESError("empty macro name")
i = macroName.rfind('/')
if i < 0:
# No slash -- must be a locally defined macro
return None, macroName
else:
# Up to last slash is the filename
fileName = macroName[:i]
localName = macroName[i+1:]
return fileName, localName
def setRepeat(self, name, expr):
seq = self.evaluateSequence(expr)
return Iterator(name, seq, self)
def createErrorInfo(self, err, position):
return ErrorInfo(err, position)
def getDefault(self):
return Default
def translate(self, domain, msgid, mapping, default=None):
return self.translationService.translate(domain, msgid, mapping,
default=default)
class Iterator:
# This is not an implementation of a Python iterator. The next()
# method returns true or false to indicate whether another item is
# available; if there is another item, the iterator instance calls
# setLocal() on the evaluation engine passed to the constructor.
def __init__(self, name, seq, engine):
self.name = name
self.seq = seq
self.engine = engine
self.nextIndex = 0
def next(self):
i = self.nextIndex
try:
item = self.seq[i]
except IndexError:
return 0
self.nextIndex = i+1
self.engine.setLocal(self.name, item)
return 1
class DummyDomain:
def translate(self, msgid, mapping=None, context=None,
target_language=None, default=None):
# This is a fake translation service which simply uppercases non
# ${name} placeholder text in the message id.
#
# First, transform a string with ${name} placeholders into a list of
# substrings. Then upcase everything but the placeholders, then glue
# things back together.
# simulate an unknown msgid by returning None
text = msgid
if msgid == "don't translate me":
if default is not None:
text = default
else:
text = msgid.upper()
def repl(m, mapping=mapping):
return ustr(mapping[m.group(m.lastindex).lower()])
cre = re.compile(r'\$(?:(%s)|\{(%s)\})' % (NAME_RE, NAME_RE))
return cre.sub(repl, text)
class DummyTranslationService:
def translate(self, domain, msgid, mapping=None, context=None,
target_language=None, default=None):
return self.getDomain(domain).translate(msgid, mapping, context,
target_language,
default=default)
def getDomain(self, domain):
return DummyDomain()
TAL history
This file contains change information for previous versions.
Change information for the current release can be found
in the file CHANGES.txt.
Version 1.4.0
Features Added
- Added TAL statement: omit_tag="[<boolean expr>]" replaces
the statement tag with its contents if the boolean
expression is true or omitted.
- The TAL and METAL namespaces can be applied to tag names,
tags in these namespaces are removed from rendered output
(leaving the contents in place, as with omit_tag)
whenever attributes in these namespaces would be, and
tag attributes without explicit namespaces default to the
tag's namespace (per XML spec).
Version 1.3.3
Bugs Fixed
- tal:atributes was creating stray attributes in METAL
expansion, and there was no unit test for this behavior.
- tal:attributes parsing was not catching badly malformed
values, and used "print" instead of raising exceptions.
Version 1.3.2
Features Added
- Adopted Zope-style CHANGES.txt and HISTORY.txt
- Improved execution performance
- Added simple ZPT vs. TAL vs. DTML benchmarks, run by markbench.py
Version 1.3.0
Features Added
- New builtin variable 'attrs'.
Bug Fixed
- Nested macros were not working correctly.
Version 1.2.0
Features Added
- The 'if' path modifier can cancel any TAL action.
Bug Fixed
- tal:attributes inserted empty attributes into source.
Version 1.1.0
Features Added
- TAL does not try to parse replacement structural text.
- Changed tests to match TAL's omitted attributes.
Version 1.0.0
- Various minor bugs fixed
Version 1.0.0b1
- All functionality described in the Project Wiki is implemented
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""
Parse HTML and compile to TALInterpreter intermediate code.
"""
import sys
from TALGenerator import TALGenerator
from HTMLParser import HTMLParser, HTMLParseError
from TALDefs import \
ZOPE_METAL_NS, ZOPE_TAL_NS, ZOPE_I18N_NS, METALError, TALError, I18NError
BOOLEAN_HTML_ATTRS = [
# List of Boolean attributes in HTML that may be given in
# minimized form (e.g. <img ismap> rather than <img ismap="">)
# From http://www.w3.org/TR/xhtml1/#guidelines (C.10)
"compact", "nowrap", "ismap", "declare", "noshade", "checked",
"disabled", "readonly", "multiple", "selected", "noresize",
"defer"
]
EMPTY_HTML_TAGS = [
# List of HTML tags with an empty content model; these are
# rendered in minimized form, e.g. <img />.
# From http://www.w3.org/TR/xhtml1/#dtds
"base", "meta", "link", "hr", "br", "param", "img", "area",
"input", "col", "basefont", "isindex", "frame",
]
PARA_LEVEL_HTML_TAGS = [
# List of HTML elements that close open paragraph-level elements
# and are themselves paragraph-level.
"h1", "h2", "h3", "h4", "h5", "h6", "p",
]
BLOCK_CLOSING_TAG_MAP = {
"tr": ("tr", "td", "th"),
"td": ("td", "th"),
"th": ("td", "th"),
"li": ("li",),
"dd": ("dd", "dt"),
"dt": ("dd", "dt"),
}
BLOCK_LEVEL_HTML_TAGS = [
# List of HTML tags that denote larger sections than paragraphs.
"blockquote", "table", "tr", "th", "td", "thead", "tfoot", "tbody",
"noframe", "ul", "ol", "li", "dl", "dt", "dd", "div",
]
TIGHTEN_IMPLICIT_CLOSE_TAGS = (PARA_LEVEL_HTML_TAGS
+ BLOCK_CLOSING_TAG_MAP.keys())
class NestingError(HTMLParseError):
"""Exception raised when elements aren't properly nested."""
def __init__(self, tagstack, endtag, position=(None, None)):
self.endtag = endtag
if tagstack:
if len(tagstack) == 1:
msg = ('Open tag <%s> does not match close tag </%s>'
% (tagstack[0], endtag))
else:
msg = ('Open tags <%s> do not match close tag </%s>'
% ('>, <'.join(tagstack), endtag))
else:
msg = 'No tags are open to match </%s>' % endtag
HTMLParseError.__init__(self, msg, position)
class EmptyTagError(NestingError):
"""Exception raised when empty elements have an end tag."""
def __init__(self, tag, position=(None, None)):
self.tag = tag
msg = 'Close tag </%s> should be removed' % tag
HTMLParseError.__init__(self, msg, position)
class OpenTagError(NestingError):
"""Exception raised when a tag is not allowed in another tag."""
def __init__(self, tagstack, tag, position=(None, None)):
self.tag = tag
msg = 'Tag <%s> is not allowed in <%s>' % (tag, tagstack[-1])
HTMLParseError.__init__(self, msg, position)
class HTMLTALParser(HTMLParser):
# External API
def __init__(self, gen=None):
HTMLParser.__init__(self)
if gen is None:
gen = TALGenerator(xml=0)
self.gen = gen
self.tagstack = []
self.nsstack = []
self.nsdict = {'tal': ZOPE_TAL_NS,
'metal': ZOPE_METAL_NS,
'i18n': ZOPE_I18N_NS,
}
def parseFile(self, file):
f = open(file)
data = f.read()
f.close()
try:
self.parseString(data)
except TALError, e:
e.setFile(file)
raise
def parseString(self, data):
self.feed(data)
self.close()
while self.tagstack:
self.implied_endtag(self.tagstack[-1], 2)
assert self.nsstack == [], self.nsstack
def getCode(self):
return self.gen.getCode()
def getWarnings(self):
return ()
# Overriding HTMLParser methods
def handle_starttag(self, tag, attrs):
self.close_para_tags(tag)
self.scan_xmlns(attrs)
tag, attrlist, taldict, metaldict, i18ndict \
= self.process_ns(tag, attrs)
if tag in EMPTY_HTML_TAGS and taldict.get("content"):
raise TALError(
"empty HTML tags cannot use tal:content: %s" % `tag`,
self.getpos())
self.tagstack.append(tag)
self.gen.emitStartElement(tag, attrlist, taldict, metaldict, i18ndict,
self.getpos())
if tag in EMPTY_HTML_TAGS:
self.implied_endtag(tag, -1)
def handle_startendtag(self, tag, attrs):
self.close_para_tags(tag)
self.scan_xmlns(attrs)
tag, attrlist, taldict, metaldict, i18ndict \
= self.process_ns(tag, attrs)
if taldict.get("content"):
if tag in EMPTY_HTML_TAGS:
raise TALError(
"empty HTML tags cannot use tal:content: %s" % `tag`,
self.getpos())
self.gen.emitStartElement(tag, attrlist, taldict, metaldict,
i18ndict, self.getpos())
self.gen.emitEndElement(tag, implied=-1)
else:
self.gen.emitStartElement(tag, attrlist, taldict, metaldict,
i18ndict, self.getpos(), isend=1)
self.pop_xmlns()
def handle_endtag(self, tag):
if tag in EMPTY_HTML_TAGS:
# </img> etc. in the source is an error
raise EmptyTagError(tag, self.getpos())
self.close_enclosed_tags(tag)
self.gen.emitEndElement(tag)
self.pop_xmlns()
self.tagstack.pop()
def close_para_tags(self, tag):
if tag in EMPTY_HTML_TAGS:
return
close_to = -1
if BLOCK_CLOSING_TAG_MAP.has_key(tag):
blocks_to_close = BLOCK_CLOSING_TAG_MAP[tag]
for i in range(len(self.tagstack)):
t = self.tagstack[i]
if t in blocks_to_close:
if close_to == -1:
close_to = i
elif t in BLOCK_LEVEL_HTML_TAGS:
close_to = -1
elif tag in PARA_LEVEL_HTML_TAGS + BLOCK_LEVEL_HTML_TAGS:
i = len(self.tagstack) - 1
while i >= 0:
closetag = self.tagstack[i]
if closetag in BLOCK_LEVEL_HTML_TAGS:
break
if closetag in PARA_LEVEL_HTML_TAGS:
if closetag != "p":
raise OpenTagError(self.tagstack, tag, self.getpos())
close_to = i
i = i - 1
if close_to >= 0:
while len(self.tagstack) > close_to:
self.implied_endtag(self.tagstack[-1], 1)
def close_enclosed_tags(self, tag):
if tag not in self.tagstack:
raise NestingError(self.tagstack, tag, self.getpos())
while tag != self.tagstack[-1]:
self.implied_endtag(self.tagstack[-1], 1)
assert self.tagstack[-1] == tag
def implied_endtag(self, tag, implied):
assert tag == self.tagstack[-1]
assert implied in (-1, 1, 2)
isend = (implied < 0)
if tag in TIGHTEN_IMPLICIT_CLOSE_TAGS:
# Pick out trailing whitespace from the program, and
# insert the close tag before the whitespace.
white = self.gen.unEmitWhitespace()
else:
white = None
self.gen.emitEndElement(tag, isend=isend, implied=implied)
if white:
self.gen.emitRawText(white)
self.tagstack.pop()
self.pop_xmlns()
def handle_charref(self, name):
self.gen.emitRawText("&#%s;" % name)
def handle_entityref(self, name):
self.gen.emitRawText("&%s;" % name)
def handle_data(self, data):
self.gen.emitRawText(data)
def handle_comment(self, data):
self.gen.emitRawText("<!--%s-->" % data)
def handle_decl(self, data):
self.gen.emitRawText("<!%s>" % data)
def handle_pi(self, data):
self.gen.emitRawText("<?%s>" % data)
# Internal thingies
def scan_xmlns(self, attrs):
nsnew = {}
for key, value in attrs:
if key.startswith("xmlns:"):
nsnew[key[6:]] = value
if nsnew:
self.nsstack.append(self.nsdict)
self.nsdict = self.nsdict.copy()
self.nsdict.update(nsnew)
else:
self.nsstack.append(self.nsdict)
def pop_xmlns(self):
self.nsdict = self.nsstack.pop()
def fixname(self, name):
if ':' in name:
prefix, suffix = name.split(':', 1)
if prefix == 'xmlns':
nsuri = self.nsdict.get(suffix)
if nsuri in (ZOPE_TAL_NS, ZOPE_METAL_NS, ZOPE_I18N_NS):
return name, name, prefix
else:
nsuri = self.nsdict.get(prefix)
if nsuri == ZOPE_TAL_NS:
return name, suffix, 'tal'
elif nsuri == ZOPE_METAL_NS:
return name, suffix, 'metal'
elif nsuri == ZOPE_I18N_NS:
return name, suffix, 'i18n'
return name, name, 0
def process_ns(self, name, attrs):
attrlist = []
taldict = {}
metaldict = {}
i18ndict = {}
name, namebase, namens = self.fixname(name)
for item in attrs:
key, value = item
key, keybase, keyns = self.fixname(key)
ns = keyns or namens # default to tag namespace
if ns and ns != 'unknown':
item = (key, value, ns)
if ns == 'tal':
if taldict.has_key(keybase):
raise TALError("duplicate TAL attribute " +
`keybase`, self.getpos())
taldict[keybase] = value
elif ns == 'metal':
if metaldict.has_key(keybase):
raise METALError("duplicate METAL attribute " +
`keybase`, self.getpos())
metaldict[keybase] = value
elif ns == 'i18n':
if i18ndict.has_key(keybase):
raise I18NError("duplicate i18n attribute " +
`keybase`, self.getpos())
i18ndict[keybase] = value
attrlist.append(item)
if namens in ('metal', 'tal'):
taldict['tal tag'] = namens
return name, attrlist, taldict, metaldict, i18ndict
"""Interface that a TALES engine provides to the METAL/TAL implementation."""
try:
from Interface import Interface
from Interface.Attribute import Attribute
except:
# Before 2.7
class Interface: pass
def Attribute(*args): pass
class ITALESCompiler(Interface):
"""Compile-time interface provided by a TALES implementation.
The TAL compiler needs an instance of this interface to support
compilation of TALES expressions embedded in documents containing
TAL and METAL constructs.
"""
def getCompilerError():
"""Return the exception class raised for compilation errors.
"""
def compile(expression):
"""Return a compiled form of 'expression' for later evaluation.
'expression' is the source text of the expression.
The return value may be passed to the various evaluate*()
methods of the ITALESEngine interface. No compatibility is
required for the values of the compiled expression between
different ITALESEngine implementations.
"""
class ITALESEngine(Interface):
"""Render-time interface provided by a TALES implementation.
The TAL interpreter uses this interface to TALES to support
evaluation of the compiled expressions returned by
ITALESCompiler.compile().
"""
def getCompiler():
"""Return an object that supports ITALESCompiler."""
def getDefault():
"""Return the value of the 'default' TALES expression.
Checking a value for a match with 'default' should be done
using the 'is' operator in Python.
"""
def setPosition((lineno, offset)):
"""Inform the engine of the current position in the source file.
This is used to allow the evaluation engine to report
execution errors so that site developers can more easily
locate the offending expression.
"""
def setSourceFile(filename):
"""Inform the engine of the name of the current source file.
This is used to allow the evaluation engine to report
execution errors so that site developers can more easily
locate the offending expression.
"""
def beginScope():
"""Push a new scope onto the stack of open scopes.
"""
def endScope():
"""Pop one scope from the stack of open scopes.
"""
def evaluate(compiled_expression):
"""Evaluate an arbitrary expression.
No constraints are imposed on the return value.
"""
def evaluateBoolean(compiled_expression):
"""Evaluate an expression that must return a Boolean value.
"""
def evaluateMacro(compiled_expression):
"""Evaluate an expression that must return a macro program.
"""
def evaluateStructure(compiled_expression):
"""Evaluate an expression that must return a structured
document fragment.
The result of evaluating 'compiled_expression' must be a
string containing a parsable HTML or XML fragment. Any TAL
markup cnotained in the result string will be interpreted.
"""
def evaluateText(compiled_expression):
"""Evaluate an expression that must return text.
The returned text should be suitable for direct inclusion in
the output: any HTML or XML escaping or quoting is the
responsibility of the expression itself.
"""
def evaluateValue(compiled_expression):
"""Evaluate an arbitrary expression.
No constraints are imposed on the return value.
"""
def createErrorInfo(exception, (lineno, offset)):
"""Returns an ITALESErrorInfo object.
The returned object is used to provide information about the
error condition for the on-error handler.
"""
def setGlobal(name, value):
"""Set a global variable.
The variable will be named 'name' and have the value 'value'.
"""
def setLocal(name, value):
"""Set a local variable in the current scope.
The variable will be named 'name' and have the value 'value'.
"""
def setRepeat(name, compiled_expression):
"""
"""
def translate(domain, msgid, mapping, default=None):
"""
See ITranslationService.translate()
"""
class ITALESErrorInfo(Interface):
type = Attribute("type",
"The exception class.")
value = Attribute("value",
"The exception instance.")
lineno = Attribute("lineno",
"The line number the error occurred on in the source.")
offset = Attribute("offset",
"The character offset at which the error occurred.")
TAL - Template Attribute Language
---------------------------------
This is an implementation of TAL, the Zope Template Attribute
Language. For TAL, see the Zope Presentation Templates ZWiki:
http://dev.zope.org/Wikis/DevSite/Projects/ZPT/FrontPage
It is not a Zope product nor is it designed exclusively to run inside
of Zope, but if you have a Zope checkout that includes
Products/ParsedXML, its Expat parser will be used.
Prerequisites
-------------
You need:
- A recent checkout of Zope2; don't forget to run the wo_pcgi.py
script to compile everything. (See above -- this is now optional.)
- A recent checkout of the Zope2 product ParsedXML, accessible
throught <Zope2>/lib/python/Products/ParsedXML; don't forget to run
the setup.py script to compiles Expat. (Again, optional.)
- Python 1.5.2; the driver script refuses to work with other versions
unless you specify the -n option; this is done so that I don't
accidentally use Python 2.x features.
- Create a .path file containing proper module search path; it should
point the <Zope2>/lib/python directory that you want to use.
How To Play
-----------
(Don't forget to edit .path, see above!)
The script driver.py takes an XML file with TAL markup as argument and
writes the expanded version to standard output. The filename argument
defaults to tests/input/test01.xml.
Regression test
---------------
There are unit test suites in the 'tests' subdirectory; these can be
run with tests/run.py. This should print the testcase names plus
progress info, followed by a final line saying "OK". It requires that
../unittest.py exists.
There are a number of test files in the 'tests' subdirectory, named
tests/input/test<number>.xml and tests/input/test<number>.html. The
Python script ./runtest.py calls driver.main() for each test file, and
should print "<file> OK" for each one. These tests are also run as
part of the unit test suites, so tests/run.py is all you need.
What's Here
-----------
DummyEngine.py simple-minded TALES execution engine
TALInterpreter.py class to interpret intermediate code
TALGenerator.py class to generate intermediate code
XMLParser.py base class to parse XML, avoiding DOM
TALParser.py class to parse XML with TAL into intermediate code
HTMLTALParser.py class to parse HTML with TAL into intermediate code
HTMLParser.py HTML-parsing base class
driver.py script to demonstrate TAL expansion
timer.py script to time various processing phases
setpath.py hack to set sys.path and import ZODB
__init__.py empty file that makes this directory a package
runtest.py Python script to run file-comparison tests
ndiff.py helper for runtest.py to produce diffs
tests/ drectory with test files and output
tests/run.py Python script to run all tests
Author and License
------------------
This code is written by Guido van Rossum (project lead), Fred Drake,
and Tim Peters. It is owned by Digital Creations and can be
redistributed under the Zope Public License.
TO DO
-----
(See also http://www.zope.org/Members/jim/ZPTIssueTracker .)
- Need to remove leading whitespace and newline when omitting an
element (either through tal:replace with a value of nothing or
tal:condition with a false condition).
- Empty TAL/METAL attributes are ignored: tal:replace="" is ignored
rather than causing an error.
- HTMLTALParser.py and TALParser.py are silly names. Should be
HTMLTALCompiler.py and XMLTALCompiler.py (or maybe shortened,
without "TAL"?)
- Should we preserve case of tags and attribute names in HTML?
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""
Common definitions used by TAL and METAL compilation an transformation.
"""
from types import ListType, TupleType
from ITALES import ITALESErrorInfo
TAL_VERSION = "1.5"
XML_NS = "http://www.w3.org/XML/1998/namespace" # URI for XML namespace
XMLNS_NS = "http://www.w3.org/2000/xmlns/" # URI for XML NS declarations
ZOPE_TAL_NS = "http://xml.zope.org/namespaces/tal"
ZOPE_METAL_NS = "http://xml.zope.org/namespaces/metal"
ZOPE_I18N_NS = "http://xml.zope.org/namespaces/i18n"
# This RE must exactly match the expression of the same name in the
# zope.i18n.simpletranslationservice module:
NAME_RE = "[a-zA-Z_][-a-zA-Z0-9_]*"
KNOWN_METAL_ATTRIBUTES = [
"define-macro",
"use-macro",
"define-slot",
"fill-slot",
"slot",
]
KNOWN_TAL_ATTRIBUTES = [
"define",
"condition",
"content",
"replace",
"repeat",
"attributes",
"on-error",
"omit-tag",
"tal tag",
]
KNOWN_I18N_ATTRIBUTES = [
"translate",
"domain",
"target",
"source",
"attributes",
"data",
"name",
]
class TALError(Exception):
def __init__(self, msg, position=(None, None)):
assert msg != ""
self.msg = msg
self.lineno = position[0]
self.offset = position[1]
self.filename = None
def setFile(self, filename):
self.filename = filename
def __str__(self):
result = self.msg
if self.lineno is not None:
result = result + ", at line %d" % self.lineno
if self.offset is not None:
result = result + ", column %d" % (self.offset + 1)
if self.filename is not None:
result = result + ', in file %s' % self.filename
return result
class METALError(TALError):
pass
class TALESError(TALError):
pass
class I18NError(TALError):
pass
class ErrorInfo:
__implements__ = ITALESErrorInfo
def __init__(self, err, position=(None, None)):
if isinstance(err, Exception):
self.type = err.__class__
self.value = err
else:
self.type = err
self.value = None
self.lineno = position[0]
self.offset = position[1]
import re
_attr_re = re.compile(r"\s*([^\s]+)\s+([^\s].*)\Z", re.S)
_subst_re = re.compile(r"\s*(?:(text|structure)\s+)?(.*)\Z", re.S)
del re
def parseAttributeReplacements(arg, xml):
dict = {}
for part in splitParts(arg):
m = _attr_re.match(part)
if not m:
raise TALError("Bad syntax in attributes: " + `part`)
name, expr = m.group(1, 2)
if not xml:
name = name.lower()
if dict.has_key(name):
raise TALError("Duplicate attribute name in attributes: " + `part`)
dict[name] = expr
return dict
def parseSubstitution(arg, position=(None, None)):
m = _subst_re.match(arg)
if not m:
raise TALError("Bad syntax in substitution text: " + `arg`, position)
key, expr = m.group(1, 2)
if not key:
key = "text"
return key, expr
def splitParts(arg):
# Break in pieces at undoubled semicolons and
# change double semicolons to singles:
arg = arg.replace(";;", "\0")
parts = arg.split(';')
parts = [p.replace("\0", ";") for p in parts]
if len(parts) > 1 and not parts[-1].strip():
del parts[-1] # It ended in a semicolon
return parts
def isCurrentVersion(program):
version = getProgramVersion(program)
return version == TAL_VERSION
def getProgramMode(program):
version = getProgramVersion(program)
if (version == TAL_VERSION and isinstance(program[1], TupleType) and
len(program[1]) == 2):
opcode, mode = program[1]
if opcode == "mode":
return mode
return None
def getProgramVersion(program):
if (len(program) >= 2 and
isinstance(program[0], TupleType) and len(program[0]) == 2):
opcode, version = program[0]
if opcode == "version":
return version
return None
import re
_ent1_re = re.compile('&(?![A-Z#])', re.I)
_entch_re = re.compile('&([A-Z][A-Z0-9]*)(?![A-Z0-9;])', re.I)
_entn1_re = re.compile('&#(?![0-9X])', re.I)
_entnx_re = re.compile('&(#X[A-F0-9]*)(?![A-F0-9;])', re.I)
_entnd_re = re.compile('&(#[0-9][0-9]*)(?![0-9;])')
del re
def attrEscape(s):
"""Replace special characters '&<>' by character entities,
except when '&' already begins a syntactically valid entity."""
s = _ent1_re.sub('&amp;', s)
s = _entch_re.sub(r'&amp;\1', s)
s = _entn1_re.sub('&amp;#', s)
s = _entnx_re.sub(r'&amp;\1', s)
s = _entnd_re.sub(r'&amp;\1', s)
s = s.replace('<', '&lt;')
s = s.replace('>', '&gt;')
s = s.replace('"', '&quot;')
return s
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""
Code generator for TALInterpreter intermediate code.
"""
import re
import cgi
import TALDefs
from TALDefs import NAME_RE, TAL_VERSION
from TALDefs import I18NError, METALError, TALError
from TALDefs import parseSubstitution
from TranslationContext import TranslationContext, DEFAULT_DOMAIN
I18N_REPLACE = 1
I18N_CONTENT = 2
I18N_EXPRESSION = 3
_name_rx = re.compile(NAME_RE)
class TALGenerator:
inMacroUse = 0
inMacroDef = 0
source_file = None
def __init__(self, expressionCompiler=None, xml=1, source_file=None):
if not expressionCompiler:
from DummyEngine import DummyEngine
expressionCompiler = DummyEngine()
self.expressionCompiler = expressionCompiler
self.CompilerError = expressionCompiler.getCompilerError()
# This holds the emitted opcodes representing the input
self.program = []
# The program stack for when we need to do some sub-evaluation for an
# intermediate result. E.g. in an i18n:name tag for which the
# contents describe the ${name} value.
self.stack = []
# Another stack of postponed actions. Elements on this stack are a
# dictionary; key/values contain useful information that
# emitEndElement needs to finish its calculations
self.todoStack = []
self.macros = {}
self.slots = {}
self.slotStack = []
self.xml = xml
self.emit("version", TAL_VERSION)
self.emit("mode", xml and "xml" or "html")
if source_file is not None:
self.source_file = source_file
self.emit("setSourceFile", source_file)
self.i18nContext = TranslationContext()
self.i18nLevel = 0
def getCode(self):
assert not self.stack
assert not self.todoStack
return self.optimize(self.program), self.macros
def optimize(self, program):
output = []
collect = []
cursor = 0
if self.xml:
endsep = "/>"
else:
endsep = " />"
for cursor in xrange(len(program)+1):
try:
item = program[cursor]
except IndexError:
item = (None, None)
opcode = item[0]
if opcode == "rawtext":
collect.append(item[1])
continue
if opcode == "endTag":
collect.append("</%s>" % item[1])
continue
if opcode == "startTag":
if self.optimizeStartTag(collect, item[1], item[2], ">"):
continue
if opcode == "startEndTag":
if self.optimizeStartTag(collect, item[1], item[2], endsep):
continue
if opcode in ("beginScope", "endScope"):
# Push *Scope instructions in front of any text instructions;
# this allows text instructions separated only by *Scope
# instructions to be joined together.
output.append(self.optimizeArgsList(item))
continue
if opcode == 'noop':
# This is a spacer for end tags in the face of i18n:name
# attributes. We can't let the optimizer collect immediately
# following end tags into the same rawtextOffset.
opcode = None
pass
text = "".join(collect)
if text:
i = text.rfind("\n")
if i >= 0:
i = len(text) - (i + 1)
output.append(("rawtextColumn", (text, i)))
else:
output.append(("rawtextOffset", (text, len(text))))
if opcode != None:
output.append(self.optimizeArgsList(item))
collect = []
return self.optimizeCommonTriple(output)
def optimizeArgsList(self, item):
if len(item) == 2:
return item
else:
return item[0], tuple(item[1:])
# These codes are used to indicate what sort of special actions
# are needed for each special attribute. (Simple attributes don't
# get action codes.)
#
# The special actions (which are modal) are handled by
# TALInterpreter.attrAction() and .attrAction_tal().
#
# Each attribute is represented by a tuple:
#
# (name, value) -- a simple name/value pair, with
# no special processing
#
# (name, value, action, *extra) -- attribute with special
# processing needs, action is a
# code that indicates which
# branch to take, and *extra
# contains additional,
# action-specific information
# needed by the processing
#
def optimizeStartTag(self, collect, name, attrlist, end):
# return true if the tag can be converted to plain text
if not attrlist:
collect.append("<%s%s" % (name, end))
return 1
opt = 1
new = ["<" + name]
for i in range(len(attrlist)):
item = attrlist[i]
if len(item) > 2:
opt = 0
name, value, action = item[:3]
attrlist[i] = (name, value, action) + item[3:]
else:
if item[1] is None:
s = item[0]
else:
s = '%s="%s"' % (item[0], TALDefs.attrEscape(item[1]))
attrlist[i] = item[0], s
new.append(" " + s)
# if no non-optimizable attributes were found, convert to plain text
if opt:
new.append(end)
collect.extend(new)
return opt
def optimizeCommonTriple(self, program):
if len(program) < 3:
return program
output = program[:2]
prev2, prev1 = output
for item in program[2:]:
if ( item[0] == "beginScope"
and prev1[0] == "setPosition"
and prev2[0] == "rawtextColumn"):
position = output.pop()[1]
text, column = output.pop()[1]
prev1 = None, None
closeprev = 0
if output and output[-1][0] == "endScope":
closeprev = 1
output.pop()
item = ("rawtextBeginScope",
(text, column, position, closeprev, item[1]))
output.append(item)
prev2 = prev1
prev1 = item
return output
def todoPush(self, todo):
self.todoStack.append(todo)
def todoPop(self):
return self.todoStack.pop()
def compileExpression(self, expr):
try:
return self.expressionCompiler.compile(expr)
except self.CompilerError, err:
raise TALError('%s in expression %s' % (err.args[0], `expr`),
self.position)
def pushProgram(self):
self.stack.append(self.program)
self.program = []
def popProgram(self):
program = self.program
self.program = self.stack.pop()
return self.optimize(program)
def pushSlots(self):
self.slotStack.append(self.slots)
self.slots = {}
def popSlots(self):
slots = self.slots
self.slots = self.slotStack.pop()
return slots
def emit(self, *instruction):
self.program.append(instruction)
def emitStartTag(self, name, attrlist, isend=0):
if isend:
opcode = "startEndTag"
else:
opcode = "startTag"
self.emit(opcode, name, attrlist)
def emitEndTag(self, name):
if self.xml and self.program and self.program[-1][0] == "startTag":
# Minimize empty element
self.program[-1] = ("startEndTag",) + self.program[-1][1:]
else:
self.emit("endTag", name)
def emitOptTag(self, name, optTag, isend):
program = self.popProgram() #block
start = self.popProgram() #start tag
if (isend or not program) and self.xml:
# Minimize empty element
start[-1] = ("startEndTag",) + start[-1][1:]
isend = 1
cexpr = optTag[0]
if cexpr:
cexpr = self.compileExpression(optTag[0])
self.emit("optTag", name, cexpr, optTag[1], isend, start, program)
def emitRawText(self, text):
self.emit("rawtext", text)
def emitText(self, text):
self.emitRawText(cgi.escape(text))
def emitDefines(self, defines):
for part in TALDefs.splitParts(defines):
m = re.match(
r"(?s)\s*(?:(global|local)\s+)?(%s)\s+(.*)\Z" % NAME_RE, part)
if not m:
raise TALError("invalid define syntax: " + `part`,
self.position)
scope, name, expr = m.group(1, 2, 3)
scope = scope or "local"
cexpr = self.compileExpression(expr)
if scope == "local":
self.emit("setLocal", name, cexpr)
else:
self.emit("setGlobal", name, cexpr)
def emitOnError(self, name, onError, TALtag, isend):
block = self.popProgram()
key, expr = parseSubstitution(onError)
cexpr = self.compileExpression(expr)
if key == "text":
self.emit("insertText", cexpr, [])
else:
assert key == "structure"
self.emit("insertStructure", cexpr, {}, [])
if TALtag:
self.emitOptTag(name, (None, 1), isend)
else:
self.emitEndTag(name)
handler = self.popProgram()
self.emit("onError", block, handler)
def emitCondition(self, expr):
cexpr = self.compileExpression(expr)
program = self.popProgram()
self.emit("condition", cexpr, program)
def emitRepeat(self, arg):
m = re.match("(?s)\s*(%s)\s+(.*)\Z" % NAME_RE, arg)
if not m:
raise TALError("invalid repeat syntax: " + `arg`,
self.position)
name, expr = m.group(1, 2)
cexpr = self.compileExpression(expr)
program = self.popProgram()
self.emit("loop", name, cexpr, program)
def emitSubstitution(self, arg, attrDict={}):
key, expr = parseSubstitution(arg)
cexpr = self.compileExpression(expr)
program = self.popProgram()
if key == "text":
self.emit("insertText", cexpr, program)
else:
assert key == "structure"
self.emit("insertStructure", cexpr, attrDict, program)
def emitI18nVariable(self, stuff):
# Used for i18n:name attributes. arg is extra information describing
# how the contents of the variable should get filled in, and it will
# either be a 1-tuple or a 2-tuple. If arg[0] is None, then the
# i18n:name value is taken implicitly from the contents of the tag,
# e.g. "I live in <span i18n:name="country">the USA</span>". In this
# case, arg[1] is the opcode sub-program describing the contents of
# the tag.
#
# When arg[0] is not None, it contains the tal expression used to
# calculate the contents of the variable, e.g.
# "I live in <span i18n:name="country"
# tal:replace="here/countryOfOrigin" />"
varname, action, expression = stuff
m = _name_rx.match(varname)
if m is None or m.group() != varname:
raise TALError("illegal i18n:name: %r" % varname, self.position)
key = cexpr = None
program = self.popProgram()
if action == I18N_REPLACE:
# This is a tag with an i18n:name and a tal:replace (implicit or
# explicit). Get rid of the first and last elements of the
# program, which are the start and end tag opcodes of the tag.
program = program[1:-1]
elif action == I18N_CONTENT:
# This is a tag with an i18n:name and a tal:content
# (explicit-only). Keep the first and last elements of the
# program, so we keep the start and end tag output.
pass
else:
assert action == I18N_EXPRESSION
key, expr = parseSubstitution(expression)
cexpr = self.compileExpression(expr)
self.emit('i18nVariable',
varname, program, cexpr, int(key == "structure"))
def emitTranslation(self, msgid, i18ndata):
program = self.popProgram()
if i18ndata is None:
self.emit('insertTranslation', msgid, program)
else:
key, expr = parseSubstitution(i18ndata)
cexpr = self.compileExpression(expr)
assert key == 'text'
self.emit('insertTranslation', msgid, program, cexpr)
def emitDefineMacro(self, macroName):
program = self.popProgram()
macroName = macroName.strip()
if self.macros.has_key(macroName):
raise METALError("duplicate macro definition: %s" % `macroName`,
self.position)
if not re.match('%s$' % NAME_RE, macroName):
raise METALError("invalid macro name: %s" % `macroName`,
self.position)
self.macros[macroName] = program
self.inMacroDef = self.inMacroDef - 1
self.emit("defineMacro", macroName, program)
def emitUseMacro(self, expr):
cexpr = self.compileExpression(expr)
program = self.popProgram()
self.inMacroUse = 0
self.emit("useMacro", expr, cexpr, self.popSlots(), program)
def emitDefineSlot(self, slotName):
program = self.popProgram()
slotName = slotName.strip()
if not re.match('%s$' % NAME_RE, slotName):
raise METALError("invalid slot name: %s" % `slotName`,
self.position)
self.emit("defineSlot", slotName, program)
def emitFillSlot(self, slotName):
program = self.popProgram()
slotName = slotName.strip()
if self.slots.has_key(slotName):
raise METALError("duplicate fill-slot name: %s" % `slotName`,
self.position)
if not re.match('%s$' % NAME_RE, slotName):
raise METALError("invalid slot name: %s" % `slotName`,
self.position)
self.slots[slotName] = program
self.inMacroUse = 1
self.emit("fillSlot", slotName, program)
def unEmitWhitespace(self):
collect = []
i = len(self.program) - 1
while i >= 0:
item = self.program[i]
if item[0] != "rawtext":
break
text = item[1]
if not re.match(r"\A\s*\Z", text):
break
collect.append(text)
i = i-1
del self.program[i+1:]
if i >= 0 and self.program[i][0] == "rawtext":
text = self.program[i][1]
m = re.search(r"\s+\Z", text)
if m:
self.program[i] = ("rawtext", text[:m.start()])
collect.append(m.group())
collect.reverse()
return "".join(collect)
def unEmitNewlineWhitespace(self):
collect = []
i = len(self.program)
while i > 0:
i = i-1
item = self.program[i]
if item[0] != "rawtext":
break
text = item[1]
if re.match(r"\A[ \t]*\Z", text):
collect.append(text)
continue
m = re.match(r"(?s)^(.*)(\n[ \t]*)\Z", text)
if not m:
break
text, rest = m.group(1, 2)
collect.reverse()
rest = rest + "".join(collect)
del self.program[i:]
if text:
self.emit("rawtext", text)
return rest
return None
def replaceAttrs(self, attrlist, repldict):
# Each entry in attrlist starts like (name, value).
# Result is (name, value, action, expr, xlat) if there is a
# tal:attributes entry for that attribute. Additional attrs
# defined only by tal:attributes are added here.
#
# (name, value, action, expr, xlat)
if not repldict:
return attrlist
newlist = []
for item in attrlist:
key = item[0]
if repldict.has_key(key):
expr, xlat, msgid = repldict[key]
item = item[:2] + ("replace", expr, xlat, msgid)
del repldict[key]
newlist.append(item)
# Add dynamic-only attributes
for key, (expr, xlat, msgid) in repldict.items():
newlist.append((key, None, "insert", expr, xlat, msgid))
return newlist
def emitStartElement(self, name, attrlist, taldict, metaldict, i18ndict,
position=(None, None), isend=0):
if not taldict and not metaldict and not i18ndict:
# Handle the simple, common case
self.emitStartTag(name, attrlist, isend)
self.todoPush({})
if isend:
self.emitEndElement(name, isend)
return
self.position = position
for key, value in taldict.items():
if key not in TALDefs.KNOWN_TAL_ATTRIBUTES:
raise TALError("bad TAL attribute: " + `key`, position)
if not (value or key == 'omit-tag'):
raise TALError("missing value for TAL attribute: " +
`key`, position)
for key, value in metaldict.items():
if key not in TALDefs.KNOWN_METAL_ATTRIBUTES:
raise METALError("bad METAL attribute: " + `key`,
position)
if not value:
raise TALError("missing value for METAL attribute: " +
`key`, position)
for key, value in i18ndict.items():
if key not in TALDefs.KNOWN_I18N_ATTRIBUTES:
raise I18NError("bad i18n attribute: " + `key`, position)
if not value and key in ("attributes", "data", "id"):
raise I18NError("missing value for i18n attribute: " +
`key`, position)
todo = {}
defineMacro = metaldict.get("define-macro")
useMacro = metaldict.get("use-macro")
defineSlot = metaldict.get("define-slot")
fillSlot = metaldict.get("fill-slot")
define = taldict.get("define")
condition = taldict.get("condition")
repeat = taldict.get("repeat")
content = taldict.get("content")
replace = taldict.get("replace")
attrsubst = taldict.get("attributes")
onError = taldict.get("on-error")
omitTag = taldict.get("omit-tag")
TALtag = taldict.get("tal tag")
i18nattrs = i18ndict.get("attributes")
# Preserve empty string if implicit msgids are used. We'll generate
# code with the msgid='' and calculate the right implicit msgid during
# interpretation phase.
msgid = i18ndict.get("translate")
varname = i18ndict.get('name')
i18ndata = i18ndict.get('data')
if varname and not self.i18nLevel:
raise I18NError(
"i18n:name can only occur inside a translation unit",
position)
if i18ndata and not msgid:
raise I18NError("i18n:data must be accompanied by i18n:translate",
position)
if len(metaldict) > 1 and (defineMacro or useMacro):
raise METALError("define-macro and use-macro cannot be used "
"together or with define-slot or fill-slot",
position)
if replace:
if content:
raise TALError(
"tal:content and tal:replace are mutually exclusive",
position)
if msgid is not None:
raise I18NError(
"i18n:translate and tal:replace are mutually exclusive",
position)
repeatWhitespace = None
if repeat:
# Hack to include preceding whitespace in the loop program
repeatWhitespace = self.unEmitNewlineWhitespace()
if position != (None, None):
# XXX at some point we should insist on a non-trivial position
self.emit("setPosition", position)
if self.inMacroUse:
if fillSlot:
self.pushProgram()
if self.source_file is not None:
self.emit("setSourceFile", self.source_file)
todo["fillSlot"] = fillSlot
self.inMacroUse = 0
else:
if fillSlot:
raise METALError("fill-slot must be within a use-macro",
position)
if not self.inMacroUse:
if defineMacro:
self.pushProgram()
self.emit("version", TAL_VERSION)
self.emit("mode", self.xml and "xml" or "html")
if self.source_file is not None:
self.emit("setSourceFile", self.source_file)
todo["defineMacro"] = defineMacro
self.inMacroDef = self.inMacroDef + 1
if useMacro:
self.pushSlots()
self.pushProgram()
todo["useMacro"] = useMacro
self.inMacroUse = 1
if defineSlot:
if not self.inMacroDef:
raise METALError(
"define-slot must be within a define-macro",
position)
self.pushProgram()
todo["defineSlot"] = defineSlot
if defineSlot or i18ndict:
domain = i18ndict.get("domain") or self.i18nContext.domain
source = i18ndict.get("source") or self.i18nContext.source
target = i18ndict.get("target") or self.i18nContext.target
if ( domain != DEFAULT_DOMAIN
or source is not None
or target is not None):
self.i18nContext = TranslationContext(self.i18nContext,
domain=domain,
source=source,
target=target)
self.emit("beginI18nContext",
{"domain": domain, "source": source,
"target": target})
todo["i18ncontext"] = 1
if taldict or i18ndict:
dict = {}
for item in attrlist:
key, value = item[:2]
dict[key] = value
self.emit("beginScope", dict)
todo["scope"] = 1
if onError:
self.pushProgram() # handler
if TALtag:
self.pushProgram() # start
self.emitStartTag(name, list(attrlist)) # Must copy attrlist!
if TALtag:
self.pushProgram() # start
self.pushProgram() # block
todo["onError"] = onError
if define:
self.emitDefines(define)
todo["define"] = define
if condition:
self.pushProgram()
todo["condition"] = condition
if repeat:
todo["repeat"] = repeat
self.pushProgram()
if repeatWhitespace:
self.emitText(repeatWhitespace)
if content:
if varname:
todo['i18nvar'] = (varname, I18N_CONTENT, None)
todo["content"] = content
self.pushProgram()
else:
todo["content"] = content
elif replace:
# tal:replace w/ i18n:name has slightly different semantics. What
# we're actually replacing then is the contents of the ${name}
# placeholder.
if varname:
todo['i18nvar'] = (varname, I18N_EXPRESSION, replace)
else:
todo["replace"] = replace
self.pushProgram()
# i18n:name w/o tal:replace uses the content as the interpolation
# dictionary values
elif varname:
todo['i18nvar'] = (varname, I18N_REPLACE, None)
self.pushProgram()
if msgid is not None:
self.i18nLevel += 1
todo['msgid'] = msgid
if i18ndata:
todo['i18ndata'] = i18ndata
optTag = omitTag is not None or TALtag
if optTag:
todo["optional tag"] = omitTag, TALtag
self.pushProgram()
if attrsubst or i18nattrs:
if attrsubst:
repldict = TALDefs.parseAttributeReplacements(attrsubst,
self.xml)
else:
repldict = {}
if i18nattrs:
i18nattrs = _parseI18nAttributes(i18nattrs, attrlist, repldict,
self.position, self.xml,
self.source_file)
else:
i18nattrs = {}
# Convert repldict's name-->expr mapping to a
# name-->(compiled_expr, translate) mapping
for key, value in repldict.items():
if i18nattrs.get(key, None):
raise I18NError(
("attribute [%s] cannot both be part of tal:attributes" +
" and have a msgid in i18n:attributes") % key,
position)
ce = self.compileExpression(value)
repldict[key] = ce, key in i18nattrs, i18nattrs.get(key)
for key in i18nattrs:
if not repldict.has_key(key):
repldict[key] = None, 1, i18nattrs.get(key)
else:
repldict = {}
if replace:
todo["repldict"] = repldict
repldict = {}
self.emitStartTag(name, self.replaceAttrs(attrlist, repldict), isend)
if optTag:
self.pushProgram()
if content and not varname:
self.pushProgram()
if msgid is not None:
self.pushProgram()
if content and varname:
self.pushProgram()
if todo and position != (None, None):
todo["position"] = position
self.todoPush(todo)
if isend:
self.emitEndElement(name, isend)
def emitEndElement(self, name, isend=0, implied=0):
todo = self.todoPop()
if not todo:
# Shortcut
if not isend:
self.emitEndTag(name)
return
self.position = position = todo.get("position", (None, None))
defineMacro = todo.get("defineMacro")
useMacro = todo.get("useMacro")
defineSlot = todo.get("defineSlot")
fillSlot = todo.get("fillSlot")
repeat = todo.get("repeat")
content = todo.get("content")
replace = todo.get("replace")
condition = todo.get("condition")
onError = todo.get("onError")
repldict = todo.get("repldict", {})
scope = todo.get("scope")
optTag = todo.get("optional tag")
msgid = todo.get('msgid')
i18ncontext = todo.get("i18ncontext")
varname = todo.get('i18nvar')
i18ndata = todo.get('i18ndata')
if implied > 0:
if defineMacro or useMacro or defineSlot or fillSlot:
exc = METALError
what = "METAL"
else:
exc = TALError
what = "TAL"
raise exc("%s attributes on <%s> require explicit </%s>" %
(what, name, name), position)
# If there's no tal:content or tal:replace in the tag with the
# i18n:name, tal:replace is the default.
if content:
self.emitSubstitution(content, {})
# If we're looking at an implicit msgid, emit the insertTranslation
# opcode now, so that the end tag doesn't become part of the implicit
# msgid. If we're looking at an explicit msgid, it's better to emit
# the opcode after the i18nVariable opcode so we can better handle
# tags with both of them in them (and in the latter case, the contents
# would be thrown away for msgid purposes).
#
# Still, we should emit insertTranslation opcode before i18nVariable
# in case tal:content, i18n:translate and i18n:name in the same tag
if msgid is not None:
if (not varname) or (
varname and (varname[1] == I18N_CONTENT)):
self.emitTranslation(msgid, i18ndata)
self.i18nLevel -= 1
if optTag:
self.emitOptTag(name, optTag, isend)
elif not isend:
# If we're processing the end tag for a tag that contained
# i18n:name, we need to make sure that optimize() won't collect
# immediately following end tags into the same rawtextOffset, so
# put a spacer here that the optimizer will recognize.
if varname:
self.emit('noop')
self.emitEndTag(name)
# If i18n:name appeared in the same tag as tal:replace then we're
# going to do the substitution a little bit differently. The results
# of the expression go into the i18n substitution dictionary.
if replace:
self.emitSubstitution(replace, repldict)
elif varname:
# o varname[0] is the variable name
# o varname[1] is either
# - I18N_REPLACE for implicit tal:replace
# - I18N_CONTENT for tal:content
# - I18N_EXPRESSION for explicit tal:replace
# o varname[2] will be None for the first two actions and the
# replacement tal expression for the third action. This
# can include a 'text' or 'structure' indicator.
assert (varname[1]
in [I18N_REPLACE, I18N_CONTENT, I18N_EXPRESSION])
self.emitI18nVariable(varname)
# Do not test for "msgid is not None", i.e. we only want to test for
# explicit msgids here. See comment above.
if msgid is not None:
# in case tal:content, i18n:translate and i18n:name in the
# same tag insertTranslation opcode has already been
# emitted
if varname and (varname[1] <> I18N_CONTENT):
self.emitTranslation(msgid, i18ndata)
if repeat:
self.emitRepeat(repeat)
if condition:
self.emitCondition(condition)
if onError:
self.emitOnError(name, onError, optTag and optTag[1], isend)
if scope:
self.emit("endScope")
if i18ncontext:
self.emit("endI18nContext")
assert self.i18nContext.parent is not None
self.i18nContext = self.i18nContext.parent
if defineSlot:
self.emitDefineSlot(defineSlot)
if fillSlot:
self.emitFillSlot(fillSlot)
if useMacro:
self.emitUseMacro(useMacro)
if defineMacro:
self.emitDefineMacro(defineMacro)
def _parseI18nAttributes(i18nattrs, attrlist, repldict, position,
xml, source_file):
def addAttribute(dic, attr, msgid, position, xml):
if not xml:
attr = attr.lower()
if attr in dic:
raise TALError(
"attribute may only be specified once in i18n:attributes: "
+ attr,
position)
dic[attr] = msgid
d = {}
if ';' in i18nattrs:
i18nattrlist = i18nattrs.split(';')
i18nattrlist = [attr.strip().split()
for attr in i18nattrlist if attr.strip()]
for parts in i18nattrlist:
if len(parts) > 2:
raise TALError("illegal i18n:attributes specification: %r"
% parts, position)
if len(parts) == 2:
attr, msgid = parts
else:
# len(parts) == 1
attr = parts[0]
msgid = None
addAttribute(d, attr, msgid, position, xml)
else:
i18nattrlist = i18nattrs.split()
if len(i18nattrlist) == 1:
addAttribute(d, i18nattrlist[0], None, position, xml)
elif len(i18nattrlist) == 2:
staticattrs = [attr[0] for attr in attrlist if len(attr) == 2]
if (not i18nattrlist[1] in staticattrs) and (
not i18nattrlist[1] in repldict):
attr, msgid = i18nattrlist
addAttribute(d, attr, msgid, position, xml)
else:
import warnings
warnings.warn(I18N_ATTRIBUTES_WARNING
% (source_file, str(position), i18nattrs)
, DeprecationWarning)
msgid = None
for attr in i18nattrlist:
addAttribute(d, attr, msgid, position, xml)
else:
import warnings
warnings.warn(I18N_ATTRIBUTES_WARNING
% (source_file, str(position), i18nattrs)
, DeprecationWarning)
msgid = None
for attr in i18nattrlist:
addAttribute(d, attr, msgid, position, xml)
return d
I18N_ATTRIBUTES_WARNING = (
'Space separated attributes in i18n:attributes'
' are deprecated (i18n:attributes="value title"). Please use'
' semicolon to separate attributes'
' (i18n:attributes="value; title").'
'\nFile %s at row, column %s\nAttributes %s')
def test():
t = TALGenerator()
t.pushProgram()
t.emit("bar")
p = t.popProgram()
t.emit("foo", p)
if __name__ == "__main__":
test()
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Interpreter for a pre-compiled TAL program.
$Id$
"""
import cgi
import sys
import re
# Do not use cStringIO here! It's not unicode aware. :(
from StringIO import StringIO
from DocumentTemplate.DT_Util import ustr
from ZODB.POSException import ConflictError
# BBB 2005/10/10 -- MessageIDs are to be removed for Zope 3.3
import zope.deprecation
zope.deprecation.__show__.off()
from zope.i18nmessageid import Message, MessageID
zope.deprecation.__show__.on()
from TALDefs import attrEscape, TAL_VERSION, METALError
from TALDefs import isCurrentVersion
from TALDefs import getProgramVersion, getProgramMode
from TALGenerator import TALGenerator
from TranslationContext import TranslationContext
# This will become (MessageID, Message) when we use Zope 3.1 as a base:
I18nMessageTypes = Message, MessageID
# TODO: In Python 2.4 we can use frozenset() instead of dict.fromkeys()
BOOLEAN_HTML_ATTRS = dict.fromkeys([
# List of Boolean attributes in HTML that should be rendered in
# minimized form (e.g. <img ismap> rather than <img ismap="">)
# From http://www.w3.org/TR/xhtml1/#guidelines (C.10)
# TODO: The problem with this is that this is not valid XML and
# can't be parsed back!
"compact", "nowrap", "ismap", "declare", "noshade", "checked",
"disabled", "readonly", "multiple", "selected", "noresize",
"defer"
])
_nulljoin = ''.join
_spacejoin = ' '.join
def normalize(text):
# Now we need to normalize the whitespace in implicit message ids and
# implicit $name substitution values by stripping leading and trailing
# whitespace, and folding all internal whitespace to a single space.
return _spacejoin(text.split())
NAME_RE = r"[a-zA-Z][a-zA-Z0-9_]*"
_interp_regex = re.compile(r'(?<!\$)(\$(?:%(n)s|{%(n)s}))' %({'n': NAME_RE}))
_get_var_regex = re.compile(r'%(n)s' %({'n': NAME_RE}))
def interpolate(text, mapping):
"""Interpolate ${keyword} substitutions.
This is called when no translation is provided by the translation
service.
"""
if not mapping:
return text
# Find all the spots we want to substitute.
to_replace = _interp_regex.findall(text)
# Now substitute with the variables in mapping.
for string in to_replace:
var = _get_var_regex.findall(string)[0]
if mapping.has_key(var):
# Call ustr because we may have an integer for instance.
subst = ustr(mapping[var])
try:
text = text.replace(string, subst)
except UnicodeError:
# subst contains high-bit chars...
# As we have no way of knowing the correct encoding,
# substitue something instead of raising an exception.
subst = `subst`[1:-1]
text = text.replace(string, subst)
return text
class AltTALGenerator(TALGenerator):
def __init__(self, repldict, expressionCompiler=None, xml=0):
self.repldict = repldict
self.enabled = 1
TALGenerator.__init__(self, expressionCompiler, xml)
def enable(self, enabled):
self.enabled = enabled
def emit(self, *args):
if self.enabled:
TALGenerator.emit(self, *args)
def emitStartElement(self, name, attrlist, taldict, metaldict, i18ndict,
position=(None, None), isend=0):
metaldict = {}
taldict = {}
i18ndict = {}
if self.enabled and self.repldict:
taldict["attributes"] = "x x"
TALGenerator.emitStartElement(self, name, attrlist,
taldict, metaldict, i18ndict,
position, isend)
def replaceAttrs(self, attrlist, repldict):
if self.enabled and self.repldict:
repldict = self.repldict
self.repldict = None
return TALGenerator.replaceAttrs(self, attrlist, repldict)
class TALInterpreter:
"""TAL interpreter.
"""
def __init__(self, program, macros, engine, stream=None,
debug=0, wrap=60, metal=1, tal=1, showtal=-1,
strictinsert=1, stackLimit=100, i18nInterpolate=1):
"""Create a TAL interpreter.
Optional arguments:
stream -- output stream (defaults to sys.stdout).
debug -- enable debugging output to sys.stderr (off by default).
wrap -- try to wrap attributes on opening tags to this number of
column (default: 60).
metal -- enable METAL macro processing (on by default).
tal -- enable TAL processing (on by default).
showtal -- do not strip away TAL directives. A special value of
-1 (which is the default setting) enables showtal when TAL
processing is disabled, and disables showtal when TAL processing is
enabled. Note that you must use 0, 1, or -1; true boolean values
are not supported (TODO: why?).
strictinsert -- enable TAL processing and stricter HTML/XML
checking on text produced by structure inserts (on by default).
Note that Zope turns this value off by default.
stackLimit -- set macro nesting limit (default: 100).
i18nInterpolate -- enable i18n translations (default: on).
"""
self.program = program
self.macros = macros
self.engine = engine # Execution engine (aka context)
self.Default = engine.getDefault()
self._currentTag = ""
self._stream_stack = [stream or sys.stdout]
self.popStream()
self.debug = debug
self.wrap = wrap
self.metal = metal
self.tal = tal
if tal:
self.dispatch = self.bytecode_handlers_tal
else:
self.dispatch = self.bytecode_handlers
assert showtal in (-1, 0, 1)
if showtal == -1:
showtal = (not tal)
self.showtal = showtal
self.strictinsert = strictinsert
self.stackLimit = stackLimit
self.html = 0
self.endsep = "/>"
self.endlen = len(self.endsep)
self.macroStack = []
self.position = None, None # (lineno, offset)
self.col = 0
self.level = 0
self.scopeLevel = 0
self.sourceFile = None
self.i18nStack = []
self.i18nInterpolate = i18nInterpolate
self.i18nContext = TranslationContext()
def StringIO(self):
# Third-party products wishing to provide a full Unicode-aware
# StringIO can do so by monkey-patching this method.
return FasterStringIO()
def saveState(self):
return (self.position, self.col, self.stream, self._stream_stack,
self.scopeLevel, self.level, self.i18nContext)
def restoreState(self, state):
(self.position, self.col, self.stream,
self._stream_stack, scopeLevel, level, i18n) = state
self._stream_write = self.stream.write
assert self.level == level
while self.scopeLevel > scopeLevel:
self.engine.endScope()
self.scopeLevel = self.scopeLevel - 1
self.engine.setPosition(self.position)
self.i18nContext = i18n
def restoreOutputState(self, state):
(dummy, self.col, self.stream,
self._stream_stack, scopeLevel, level, i18n) = state
self._stream_write = self.stream.write
assert self.level == level
assert self.scopeLevel == scopeLevel
def pushMacro(self, macroName, slots, entering=1):
if len(self.macroStack) >= self.stackLimit:
raise METALError("macro nesting limit (%d) exceeded "
"by %s" % (self.stackLimit, `macroName`))
self.macroStack.append([macroName, slots, entering, self.i18nContext])
def popMacro(self):
return self.macroStack.pop()
def __call__(self):
assert self.level == 0
assert self.scopeLevel == 0
assert self.i18nContext.parent is None
self.interpret(self.program)
assert self.level == 0
assert self.scopeLevel == 0
assert self.i18nContext.parent is None
if self.col > 0:
self._stream_write("\n")
self.col = 0
def pushStream(self, newstream):
self._stream_stack.append(self.stream)
self.stream = newstream
self._stream_write = self.stream.write
def popStream(self):
self.stream = self._stream_stack.pop()
self._stream_write = self.stream.write
def stream_write(self, s,
len=len):
self._stream_write(s)
i = s.rfind('\n')
if i < 0:
self.col = self.col + len(s)
else:
self.col = len(s) - (i + 1)
bytecode_handlers = {}
def interpret(self, program):
oldlevel = self.level
self.level = oldlevel + 1
handlers = self.dispatch
try:
if self.debug:
for (opcode, args) in program:
s = "%sdo_%s(%s)\n" % (" "*self.level, opcode,
repr(args))
if len(s) > 80:
s = s[:76] + "...\n"
sys.stderr.write(s)
handlers[opcode](self, args)
else:
for (opcode, args) in program:
handlers[opcode](self, args)
finally:
self.level = oldlevel
def do_version(self, version):
assert version == TAL_VERSION
bytecode_handlers["version"] = do_version
def do_mode(self, mode):
assert mode in ("html", "xml")
self.html = (mode == "html")
if self.html:
self.endsep = " />"
else:
self.endsep = "/>"
self.endlen = len(self.endsep)
bytecode_handlers["mode"] = do_mode
def do_setSourceFile(self, source_file):
self.sourceFile = source_file
self.engine.setSourceFile(source_file)
bytecode_handlers["setSourceFile"] = do_setSourceFile
def do_setPosition(self, position):
self.position = position
self.engine.setPosition(position)
bytecode_handlers["setPosition"] = do_setPosition
def do_startEndTag(self, stuff):
self.do_startTag(stuff, self.endsep, self.endlen)
bytecode_handlers["startEndTag"] = do_startEndTag
def do_startTag(self, (name, attrList),
end=">", endlen=1, _len=len):
# The bytecode generator does not cause calls to this method
# for start tags with no attributes; those are optimized down
# to rawtext events. Hence, there is no special "fast path"
# for that case.
self._currentTag = name
L = ["<", name]
append = L.append
col = self.col + _len(name) + 1
wrap = self.wrap
align = col + 1
if align >= wrap/2:
align = 4 # Avoid a narrow column far to the right
attrAction = self.dispatch["<attrAction>"]
try:
for item in attrList:
if _len(item) == 2:
name, s = item
else:
# item[2] is the 'action' field:
if item[2] in ('metal', 'tal', 'xmlns', 'i18n'):
if not self.showtal:
continue
ok, name, s = self.attrAction(item)
else:
ok, name, s = attrAction(self, item)
if not ok:
continue
slen = _len(s)
if (wrap and
col >= align and
col + 1 + slen > wrap):
append("\n")
append(" "*align)
col = align + slen
else:
append(" ")
col = col + 1 + slen
append(s)
append(end)
col = col + endlen
finally:
self._stream_write(_nulljoin(L))
self.col = col
bytecode_handlers["startTag"] = do_startTag
def attrAction(self, item):
name, value, action = item[:3]
if action == 'insert':
return 0, name, value
macs = self.macroStack
if action == 'metal' and self.metal and macs:
if len(macs) > 1 or not macs[-1][2]:
# Drop all METAL attributes at a use-depth above one.
return 0, name, value
# Clear 'entering' flag
macs[-1][2] = 0
# Convert or drop depth-one METAL attributes.
i = name.rfind(":") + 1
prefix, suffix = name[:i], name[i:]
if suffix == "define-macro":
# Convert define-macro as we enter depth one.
name = prefix + "use-macro"
value = macs[-1][0] # Macro name
elif suffix == "define-slot":
name = prefix + "fill-slot"
elif suffix == "fill-slot":
pass
else:
return 0, name, value
if value is None:
value = name
else:
value = '%s="%s"' % (name, attrEscape(value))
return 1, name, value
def attrAction_tal(self, item):
name, value, action = item[:3]
ok = 1
expr, xlat, msgid = item[3:]
if self.html and name.lower() in BOOLEAN_HTML_ATTRS:
evalue = self.engine.evaluateBoolean(item[3])
if evalue is self.Default:
if action == 'insert': # Cancelled insert
ok = 0
elif evalue:
value = None
else:
ok = 0
elif expr is not None:
evalue = self.engine.evaluateText(item[3])
if evalue is self.Default:
if action == 'insert': # Cancelled insert
ok = 0
else:
if evalue is None:
ok = 0
value = evalue
else:
evalue = None
if ok:
if xlat:
translated = self.translate(msgid or value, value, {})
if translated is not None:
value = translated
if value is None:
value = name
elif evalue is self.Default:
value = attrEscape(value)
else:
value = cgi.escape(value, quote=1)
value = '%s="%s"' % (name, value)
return ok, name, value
bytecode_handlers["<attrAction>"] = attrAction
def no_tag(self, start, program):
state = self.saveState()
self.stream = stream = self.StringIO()
self._stream_write = stream.write
self.interpret(start)
self.restoreOutputState(state)
self.interpret(program)
def do_optTag(self, (name, cexpr, tag_ns, isend, start, program),
omit=0):
if tag_ns and not self.showtal:
return self.no_tag(start, program)
self.interpret(start)
if not isend:
self.interpret(program)
s = '</%s>' % name
self._stream_write(s)
self.col = self.col + len(s)
def do_optTag_tal(self, stuff):
cexpr = stuff[1]
if cexpr is not None and (cexpr == '' or
self.engine.evaluateBoolean(cexpr)):
self.no_tag(stuff[-2], stuff[-1])
else:
self.do_optTag(stuff)
bytecode_handlers["optTag"] = do_optTag
def do_rawtextBeginScope(self, (s, col, position, closeprev, dict)):
self._stream_write(s)
self.col = col
self.do_setPosition(position)
if closeprev:
engine = self.engine
engine.endScope()
engine.beginScope()
else:
self.engine.beginScope()
self.scopeLevel = self.scopeLevel + 1
def do_rawtextBeginScope_tal(self, (s, col, position, closeprev, dict)):
self._stream_write(s)
self.col = col
engine = self.engine
self.position = position
engine.setPosition(position)
if closeprev:
engine.endScope()
engine.beginScope()
else:
engine.beginScope()
self.scopeLevel = self.scopeLevel + 1
engine.setLocal("attrs", dict)
bytecode_handlers["rawtextBeginScope"] = do_rawtextBeginScope
def do_beginScope(self, dict):
self.engine.beginScope()
self.scopeLevel = self.scopeLevel + 1
def do_beginScope_tal(self, dict):
engine = self.engine
engine.beginScope()
engine.setLocal("attrs", dict)
self.scopeLevel = self.scopeLevel + 1
bytecode_handlers["beginScope"] = do_beginScope
def do_endScope(self, notused=None):
self.engine.endScope()
self.scopeLevel = self.scopeLevel - 1
bytecode_handlers["endScope"] = do_endScope
def do_setLocal(self, notused):
pass
def do_setLocal_tal(self, (name, expr)):
self.engine.setLocal(name, self.engine.evaluateValue(expr))
bytecode_handlers["setLocal"] = do_setLocal
def do_setGlobal_tal(self, (name, expr)):
self.engine.setGlobal(name, self.engine.evaluateValue(expr))
bytecode_handlers["setGlobal"] = do_setLocal
def do_beginI18nContext(self, settings):
get = settings.get
self.i18nContext = TranslationContext(self.i18nContext,
domain=get("domain"),
source=get("source"),
target=get("target"))
bytecode_handlers["beginI18nContext"] = do_beginI18nContext
def do_endI18nContext(self, notused=None):
self.i18nContext = self.i18nContext.parent
assert self.i18nContext is not None
bytecode_handlers["endI18nContext"] = do_endI18nContext
def do_insertText(self, stuff):
self.interpret(stuff[1])
def do_insertText_tal(self, stuff):
text = self.engine.evaluateText(stuff[0])
if text is None:
return
if text is self.Default:
self.interpret(stuff[1])
return
if isinstance(text, I18nMessageTypes):
# Translate this now.
text = self.engine.translate(text.domain, text,
text.mapping, default=text.default)
s = cgi.escape(text)
self._stream_write(s)
i = s.rfind('\n')
if i < 0:
self.col = self.col + len(s)
else:
self.col = len(s) - (i + 1)
bytecode_handlers["insertText"] = do_insertText
def do_i18nVariable(self, stuff):
varname, program, expression, structure = stuff
if expression is None:
# The value is implicitly the contents of this tag, so we have to
# evaluate the mini-program to get the value of the variable.
state = self.saveState()
try:
tmpstream = self.StringIO()
self.pushStream(tmpstream)
try:
self.interpret(program)
finally:
self.popStream()
if self.html and self._currentTag == "pre":
value = tmpstream.getvalue()
else:
value = normalize(tmpstream.getvalue())
finally:
self.restoreState(state)
else:
# Evaluate the value to be associated with the variable in the
# i18n interpolation dictionary.
if structure:
value = self.engine.evaluateStructure(expression)
else:
value = self.engine.evaluate(expression)
# evaluate() does not do any I18n, so we do it here.
if isinstance(value, I18nMessageTypes):
# Translate this now.
# XXX
value = self.engine.translate(value.domain, value,
value.mapping, value.default)
if not structure:
value = cgi.escape(ustr(value))
# Either the i18n:name tag is nested inside an i18n:translate in which
# case the last item on the stack has the i18n dictionary and string
# representation, or the i18n:name and i18n:translate attributes are
# in the same tag, in which case the i18nStack will be empty. In that
# case we can just output the ${name} to the stream
i18ndict, srepr = self.i18nStack[-1]
i18ndict[varname] = value
placeholder = '${%s}' % varname
srepr.append(placeholder)
self._stream_write(placeholder)
bytecode_handlers['i18nVariable'] = do_i18nVariable
def do_insertTranslation(self, stuff):
i18ndict = {}
srepr = []
obj = None
self.i18nStack.append((i18ndict, srepr))
msgid = stuff[0]
# We need to evaluate the content of the tag because that will give us
# several useful pieces of information. First, the contents will
# include an implicit message id, if no explicit one was given.
# Second, it will evaluate any i18nVariable definitions in the body of
# the translation (necessary for $varname substitutions).
#
# Use a temporary stream to capture the interpretation of the
# subnodes, which should /not/ go to the output stream.
currentTag = self._currentTag
tmpstream = self.StringIO()
self.pushStream(tmpstream)
try:
self.interpret(stuff[1])
finally:
self.popStream()
# We only care about the evaluated contents if we need an implicit
# message id. All other useful information will be in the i18ndict on
# the top of the i18nStack.
default = tmpstream.getvalue()
if not msgid:
if self.html and currentTag == "pre":
msgid = default
else:
msgid = normalize(default)
self.i18nStack.pop()
# See if there is was an i18n:data for msgid
if len(stuff) > 2:
obj = self.engine.evaluate(stuff[2])
xlated_msgid = self.translate(msgid, default, i18ndict, obj)
# TODO: I can't decide whether we want to cgi escape the translated
# string or not. OTOH not doing this could introduce a cross-site
# scripting vector by allowing translators to sneak JavaScript into
# translations. OTOH, for implicit interpolation values, we don't
# want to escape stuff like ${name} <= "<b>Timmy</b>".
assert xlated_msgid is not None
self._stream_write(xlated_msgid)
bytecode_handlers['insertTranslation'] = do_insertTranslation
def do_insertStructure(self, stuff):
self.interpret(stuff[2])
def do_insertStructure_tal(self, (expr, repldict, block)):
structure = self.engine.evaluateStructure(expr)
if structure is None:
return
if structure is self.Default:
self.interpret(block)
return
text = ustr(structure)
if not (repldict or self.strictinsert):
# Take a shortcut, no error checking
self.stream_write(text)
return
if self.html:
self.insertHTMLStructure(text, repldict)
else:
self.insertXMLStructure(text, repldict)
bytecode_handlers["insertStructure"] = do_insertStructure
def insertHTMLStructure(self, text, repldict):
from HTMLTALParser import HTMLTALParser
gen = AltTALGenerator(repldict, self.engine.getCompiler(), 0)
p = HTMLTALParser(gen) # Raises an exception if text is invalid
p.parseString(text)
program, macros = p.getCode()
self.interpret(program)
def insertXMLStructure(self, text, repldict):
from TALParser import TALParser
gen = AltTALGenerator(repldict, self.engine.getCompiler(), 0)
p = TALParser(gen)
gen.enable(0)
p.parseFragment('<!DOCTYPE foo PUBLIC "foo" "bar"><foo>')
gen.enable(1)
p.parseFragment(text) # Raises an exception if text is invalid
gen.enable(0)
p.parseFragment('</foo>', 1)
program, macros = gen.getCode()
self.interpret(program)
def do_loop(self, (name, expr, block)):
self.interpret(block)
def do_loop_tal(self, (name, expr, block)):
iterator = self.engine.setRepeat(name, expr)
while iterator.next():
self.interpret(block)
bytecode_handlers["loop"] = do_loop
def translate(self, msgid, default, i18ndict, obj=None):
if obj:
i18ndict.update(obj)
if not self.i18nInterpolate:
return msgid
# TODO: We need to pass in one of context or target_language
return self.engine.translate(self.i18nContext.domain,
msgid, i18ndict, default=default)
def do_rawtextColumn(self, (s, col)):
self._stream_write(s)
self.col = col
bytecode_handlers["rawtextColumn"] = do_rawtextColumn
def do_rawtextOffset(self, (s, offset)):
self._stream_write(s)
self.col = self.col + offset
bytecode_handlers["rawtextOffset"] = do_rawtextOffset
def do_condition(self, (condition, block)):
if not self.tal or self.engine.evaluateBoolean(condition):
self.interpret(block)
bytecode_handlers["condition"] = do_condition
def do_defineMacro(self, (macroName, macro)):
macs = self.macroStack
if len(macs) == 1:
entering = macs[-1][2]
if not entering:
macs.append(None)
self.interpret(macro)
assert macs[-1] is None
macs.pop()
return
self.interpret(macro)
bytecode_handlers["defineMacro"] = do_defineMacro
def do_useMacro(self, (macroName, macroExpr, compiledSlots, block)):
if not self.metal:
self.interpret(block)
return
macro = self.engine.evaluateMacro(macroExpr)
if macro is self.Default:
macro = block
else:
if not isCurrentVersion(macro):
raise METALError("macro %s has incompatible version %s" %
(`macroName`, `getProgramVersion(macro)`),
self.position)
mode = getProgramMode(macro)
if mode != (self.html and "html" or "xml"):
raise METALError("macro %s has incompatible mode %s" %
(`macroName`, `mode`), self.position)
self.pushMacro(macroName, compiledSlots)
prev_source = self.sourceFile
self.interpret(macro)
if self.sourceFile != prev_source:
self.engine.setSourceFile(prev_source)
self.sourceFile = prev_source
self.popMacro()
bytecode_handlers["useMacro"] = do_useMacro
def do_fillSlot(self, (slotName, block)):
# This is only executed if the enclosing 'use-macro' evaluates
# to 'default'.
self.interpret(block)
bytecode_handlers["fillSlot"] = do_fillSlot
def do_defineSlot(self, (slotName, block)):
if not self.metal:
self.interpret(block)
return
macs = self.macroStack
if macs and macs[-1] is not None:
macroName, slots = self.popMacro()[:2]
slot = slots.get(slotName)
if slot is not None:
prev_source = self.sourceFile
self.interpret(slot)
if self.sourceFile != prev_source:
self.engine.setSourceFile(prev_source)
self.sourceFile = prev_source
self.pushMacro(macroName, slots, entering=0)
return
self.pushMacro(macroName, slots)
# Falling out of the 'if' allows the macro to be interpreted.
self.interpret(block)
bytecode_handlers["defineSlot"] = do_defineSlot
def do_onError(self, (block, handler)):
self.interpret(block)
def do_onError_tal(self, (block, handler)):
state = self.saveState()
self.stream = stream = self.StringIO()
self._stream_write = stream.write
try:
self.interpret(block)
except ConflictError:
raise
except:
exc = sys.exc_info()[1]
self.restoreState(state)
engine = self.engine
engine.beginScope()
error = engine.createErrorInfo(exc, self.position)
engine.setLocal('error', error)
try:
self.interpret(handler)
finally:
engine.endScope()
else:
self.restoreOutputState(state)
self.stream_write(stream.getvalue())
bytecode_handlers["onError"] = do_onError
bytecode_handlers_tal = bytecode_handlers.copy()
bytecode_handlers_tal["rawtextBeginScope"] = do_rawtextBeginScope_tal
bytecode_handlers_tal["beginScope"] = do_beginScope_tal
bytecode_handlers_tal["setLocal"] = do_setLocal_tal
bytecode_handlers_tal["setGlobal"] = do_setGlobal_tal
bytecode_handlers_tal["insertStructure"] = do_insertStructure_tal
bytecode_handlers_tal["insertText"] = do_insertText_tal
bytecode_handlers_tal["loop"] = do_loop_tal
bytecode_handlers_tal["onError"] = do_onError_tal
bytecode_handlers_tal["<attrAction>"] = attrAction_tal
bytecode_handlers_tal["optTag"] = do_optTag_tal
class FasterStringIO(StringIO):
"""Append-only version of StringIO.
This let's us have a much faster write() method.
"""
def close(self):
if not self.closed:
self.write = _write_ValueError
StringIO.close(self)
def seek(self, pos, mode=0):
raise RuntimeError("FasterStringIO.seek() not allowed")
def write(self, s):
#assert self.pos == self.len
self.buflist.append(s)
self.len = self.pos = self.pos + len(s)
def _write_ValueError(s):
raise ValueError, "I/O operation on closed file"
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""
Parse XML and compile to TALInterpreter intermediate code.
"""
from XMLParser import XMLParser
from TALDefs import XML_NS, ZOPE_I18N_NS, ZOPE_METAL_NS, ZOPE_TAL_NS
from TALGenerator import TALGenerator
class TALParser(XMLParser):
ordered_attributes = 1
def __init__(self, gen=None): # Override
XMLParser.__init__(self)
if gen is None:
gen = TALGenerator()
self.gen = gen
self.nsStack = []
self.nsDict = {XML_NS: 'xml'}
self.nsNew = []
def getCode(self):
return self.gen.getCode()
def getWarnings(self):
return ()
def StartNamespaceDeclHandler(self, prefix, uri):
self.nsStack.append(self.nsDict.copy())
self.nsDict[uri] = prefix
self.nsNew.append((prefix, uri))
def EndNamespaceDeclHandler(self, prefix):
self.nsDict = self.nsStack.pop()
def StartElementHandler(self, name, attrs):
if self.ordered_attributes:
# attrs is a list of alternating names and values
attrlist = []
for i in range(0, len(attrs), 2):
key = attrs[i]
value = attrs[i+1]
attrlist.append((key, value))
else:
# attrs is a dict of {name: value}
attrlist = attrs.items()
attrlist.sort() # For definiteness
name, attrlist, taldict, metaldict, i18ndict \
= self.process_ns(name, attrlist)
attrlist = self.xmlnsattrs() + attrlist
self.gen.emitStartElement(name, attrlist, taldict, metaldict, i18ndict)
def process_ns(self, name, attrlist):
taldict = {}
metaldict = {}
i18ndict = {}
fixedattrlist = []
name, namebase, namens = self.fixname(name)
for key, value in attrlist:
key, keybase, keyns = self.fixname(key)
ns = keyns or namens # default to tag namespace
item = key, value
if ns == 'metal':
metaldict[keybase] = value
item = item + ("metal",)
elif ns == 'tal':
taldict[keybase] = value
item = item + ("tal",)
elif ns == 'i18n':
i18ndict[keybase] = value
item = item + ('i18n',)
fixedattrlist.append(item)
if namens in ('metal', 'tal', 'i18n'):
taldict['tal tag'] = namens
return name, fixedattrlist, taldict, metaldict, i18ndict
def xmlnsattrs(self):
newlist = []
for prefix, uri in self.nsNew:
if prefix:
key = "xmlns:" + prefix
else:
key = "xmlns"
if uri in (ZOPE_METAL_NS, ZOPE_TAL_NS, ZOPE_I18N_NS):
item = (key, uri, "xmlns")
else:
item = (key, uri)
newlist.append(item)
self.nsNew = []
return newlist
def fixname(self, name):
if ' ' in name:
uri, name = name.split(' ')
prefix = self.nsDict[uri]
prefixed = name
if prefix:
prefixed = "%s:%s" % (prefix, name)
ns = 'x'
if uri == ZOPE_TAL_NS:
ns = 'tal'
elif uri == ZOPE_METAL_NS:
ns = 'metal'
elif uri == ZOPE_I18N_NS:
ns = 'i18n'
return (prefixed, name, ns)
return (name, name, None)
def EndElementHandler(self, name):
name = self.fixname(name)[0]
self.gen.emitEndElement(name)
def DefaultHandler(self, text):
self.gen.emitRawText(text)
def test():
import sys
p = TALParser()
file = "tests/input/test01.xml"
if sys.argv[1:]:
file = sys.argv[1]
p.parseFile(file)
program, macros = p.getCode()
from TALInterpreter import TALInterpreter
from DummyEngine import DummyEngine
engine = DummyEngine(macros)
TALInterpreter(program, macros, engine, sys.stdout, wrap=0)()
if __name__ == "__main__":
test()
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Translation context object for the TALInterpreter's I18N support.
The translation context provides a container for the information
needed to perform translation of a marked string from a page template.
$Id$
"""
DEFAULT_DOMAIN = "default"
class TranslationContext:
"""Information about the I18N settings of a TAL processor."""
def __init__(self, parent=None, domain=None, target=None, source=None):
if parent:
if not domain:
domain = parent.domain
if not target:
target = parent.target
if not source:
source = parent.source
elif domain is None:
domain = DEFAULT_DOMAIN
self.parent = parent
self.domain = domain
self.target = target
self.source = source
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
#
##############################################################################
"""
Generic expat-based XML parser base class.
"""
import xml.parsers.expat
import zLOG
XMLParseError = xml.parsers.expat.ExpatError
class XMLParser:
ordered_attributes = 0
handler_names = [
"StartElementHandler",
"EndElementHandler",
"ProcessingInstructionHandler",
"CharacterDataHandler",
"UnparsedEntityDeclHandler",
"NotationDeclHandler",
"StartNamespaceDeclHandler",
"EndNamespaceDeclHandler",
"CommentHandler",
"StartCdataSectionHandler",
"EndCdataSectionHandler",
"DefaultHandler",
"DefaultHandlerExpand",
"NotStandaloneHandler",
"ExternalEntityRefHandler",
"XmlDeclHandler",
"StartDoctypeDeclHandler",
"EndDoctypeDeclHandler",
"ElementDeclHandler",
"AttlistDeclHandler"
]
def __init__(self, encoding=None):
self.parser = p = self.createParser()
if self.ordered_attributes:
try:
self.parser.ordered_attributes = self.ordered_attributes
except AttributeError:
zLOG.LOG("TAL.XMLParser", zLOG.INFO,
"Can't set ordered_attributes")
self.ordered_attributes = 0
for name in self.handler_names:
method = getattr(self, name, None)
if method is not None:
try:
setattr(p, name, method)
except AttributeError:
zLOG.LOG("TAL.XMLParser", zLOG.PROBLEM,
"Can't set expat handler %s" % name)
def createParser(self, encoding=None):
return xml.parsers.expat.ParserCreate(encoding, ' ')
def parseFile(self, filename):
self.parseStream(open(filename))
def parseString(self, s):
self.parser.Parse(s, 1)
def parseURL(self, url):
import urllib
self.parseStream(urllib.urlopen(url))
def parseStream(self, stream):
self.parser.ParseFile(stream)
def parseFragment(self, s, end=0):
self.parser.Parse(s, end)
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
#
##############################################################################
""" Template Attribute Language package """
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
<td bgcolor="white">&dtml-x0;</td>
<td bgcolor="white">&dtml-x1;</td>
<td bgcolor="white">&dtml-x2;</td>
<td bgcolor="white">&dtml-x3;</td>
<td bgcolor="white">&dtml-x4;</td>
<td bgcolor="white">&dtml-x5;</td>
<td bgcolor="white">&dtml-x6;</td>
<td bgcolor="white">&dtml-x7;</td>
<dtml-in r8>
<td bgcolor="white">&dtml-x0;</td>
<td bgcolor="white">&dtml-x1;</td>
<td bgcolor="white">&dtml-x2;</td>
<td bgcolor="white">&dtml-x3;</td>
</dtml-in>
<dtml-in r8>
<td bgcolor="white">&dtml-x0;</td>
<td bgcolor="white">&dtml-x1;</td>
<td bgcolor="white">&dtml-x2;</td>
<td bgcolor="white">&dtml-x3;</td>
<td bgcolor="white">&dtml-x4;</td>
<td bgcolor="white">&dtml-x5;</td>
<td bgcolor="white">&dtml-x6;</td>
<td bgcolor="white">&dtml-x7;</td>
</dtml-in>
<dtml-in r2>
<dtml-in r2>
<dtml-in r2>
<td bgcolor="white">&dtml-x0;</td>
<td bgcolor="white">&dtml-x1;</td>
<td bgcolor="white">&dtml-x2;</td>
<td bgcolor="white">&dtml-x3;</td>
<td bgcolor="white">&dtml-x4;</td>
<td bgcolor="white">&dtml-x5;</td>
<td bgcolor="white">&dtml-x6;</td>
<td bgcolor="white">&dtml-x7;</td>
</dtml-in>
</dtml-in>
</dtml-in>
<td bgcolor="white">&dtml-x0;</td>
<td bgcolor="white">&dtml-x1;</td>
<td bgcolor="white">&dtml-x2;</td>
<td bgcolor="white">&dtml-x3;</td>
<td bgcolor="white">&dtml-x4;</td>
<td bgcolor="white">&dtml-x5;</td>
<td bgcolor="white">&dtml-x6;</td>
<td bgcolor="white">&dtml-x7;</td>
<td bgcolor="white">&dtml-x0;</td>
<td bgcolor="white">&dtml-x1;</td>
<td bgcolor="white">&dtml-x2;</td>
<td bgcolor="white">&dtml-x3;</td>
<td bgcolor="white">&dtml-x4;</td>
<td bgcolor="white">&dtml-x5;</td>
<td bgcolor="white">&dtml-x6;</td>
<td bgcolor="white">&dtml-x7;</td>
<td bgcolor="white">&dtml-x0;</td>
<td bgcolor="white">&dtml-x1;</td>
<td bgcolor="white">&dtml-x2;</td>
<td bgcolor="white">&dtml-x3;</td>
<td bgcolor="white">&dtml-x4;</td>
<td bgcolor="white">&dtml-x5;</td>
<td bgcolor="white">&dtml-x6;</td>
<td bgcolor="white">&dtml-x7;</td>
<td bgcolor="white">&dtml-x0;</td>
<td bgcolor="white">&dtml-x1;</td>
<td bgcolor="white">&dtml-x2;</td>
<td bgcolor="white">&dtml-x3;</td>
<td bgcolor="white">&dtml-x4;</td>
<td bgcolor="white">&dtml-x5;</td>
<td bgcolor="white">&dtml-x6;</td>
<td bgcolor="white">&dtml-x7;</td>
<td bgcolor="white">&dtml-x0;</td>
<td bgcolor="white">&dtml-x1;</td>
<td bgcolor="white">&dtml-x2;</td>
<td bgcolor="white">&dtml-x3;</td>
<td bgcolor="white">&dtml-x4;</td>
<td bgcolor="white">&dtml-x5;</td>
<td bgcolor="white">&dtml-x6;</td>
<td bgcolor="white">&dtml-x7;</td>
<td bgcolor="white">&dtml-x0;</td>
<td bgcolor="white">&dtml-x1;</td>
<td bgcolor="white">&dtml-x2;</td>
<td bgcolor="white">&dtml-x3;</td>
<td bgcolor="white">&dtml-x4;</td>
<td bgcolor="white">&dtml-x5;</td>
<td bgcolor="white">&dtml-x6;</td>
<td bgcolor="white">&dtml-x7;</td>
<td bgcolor="white">&dtml-x0;</td>
<td bgcolor="white">&dtml-x1;</td>
<td bgcolor="white">&dtml-x2;</td>
<td bgcolor="white">&dtml-x3;</td>
<td bgcolor="white">&dtml-x4;</td>
<td bgcolor="white">&dtml-x5;</td>
<td bgcolor="white">&dtml-x6;</td>
<td bgcolor="white">&dtml-x7;</td>
<td bgcolor="white">&dtml-x0;</td>
<td bgcolor="white">&dtml-x1;</td>
<td bgcolor="white">&dtml-x2;</td>
<td bgcolor="white">&dtml-x3;</td>
<td bgcolor="white">&dtml-x4;</td>
<td bgcolor="white">&dtml-x5;</td>
<td bgcolor="white">&dtml-x6;</td>
<td bgcolor="white">&dtml-x7;</td>
<td bgcolor="white">&dtml-x0;</td>
<td bgcolor="white">&dtml-x1;</td>
<td bgcolor="white">&dtml-x2;</td>
<td bgcolor="white">&dtml-x3;</td>
<td bgcolor="white">&dtml-x4;</td>
<td bgcolor="white">&dtml-x5;</td>
<td bgcolor="white">&dtml-x6;</td>
<td bgcolor="white">&dtml-x7;</td>
<td bgcolor="white">&dtml-x0;</td>
<td bgcolor="white">&dtml-x1;</td>
<td bgcolor="white">&dtml-x2;</td>
<td bgcolor="white">&dtml-x3;</td>
<td bgcolor="white">&dtml-x4;</td>
<td bgcolor="white">&dtml-x5;</td>
<td bgcolor="white">&dtml-x6;</td>
<td bgcolor="white">&dtml-x7;</td>
<td bgcolor="white">&dtml-x0;</td>
<td bgcolor="white">&dtml-x1;</td>
<td bgcolor="white">&dtml-x2;</td>
<td bgcolor="white">&dtml-x3;</td>
<td bgcolor="white">&dtml-x4;</td>
<td bgcolor="white">&dtml-x5;</td>
<td bgcolor="white">&dtml-x6;</td>
<td bgcolor="white">&dtml-x7;</td>
<td bgcolor="white">&dtml-x0;</td>
<td bgcolor="white">&dtml-x1;</td>
<td bgcolor="white">&dtml-x2;</td>
<td bgcolor="white">&dtml-x3;</td>
<td bgcolor="white">&dtml-x4;</td>
<td bgcolor="white">&dtml-x5;</td>
<td bgcolor="white">&dtml-x6;</td>
<td bgcolor="white">&dtml-x7;</td>
<td bgcolor="white">&dtml-x0;</td>
<td bgcolor="white">&dtml-x1;</td>
<td bgcolor="white">&dtml-x2;</td>
<td bgcolor="white">&dtml-x3;</td>
<td bgcolor="white">&dtml-x4;</td>
<td bgcolor="white">&dtml-x5;</td>
<td bgcolor="white">&dtml-x6;</td>
<td bgcolor="white">&dtml-x7;</td>
<td bgcolor="white">&dtml-x0;</td>
<td bgcolor="white">&dtml-x1;</td>
<td bgcolor="white">&dtml-x2;</td>
<td bgcolor="white">&dtml-x3;</td>
<td bgcolor="white">&dtml-x4;</td>
<td bgcolor="white">&dtml-x5;</td>
<td bgcolor="white">&dtml-x6;</td>
<td bgcolor="white">&dtml-x7;</td>
<td bgcolor="white">&dtml-x0;</td>
<td bgcolor="white">&dtml-x1;</td>
<td bgcolor="white">&dtml-x2;</td>
<td bgcolor="white">&dtml-x3;</td>
<td bgcolor="white">&dtml-x4;</td>
<td bgcolor="white">&dtml-x5;</td>
<td bgcolor="white">&dtml-x6;</td>
<td bgcolor="white">&dtml-x7;</td>
<td bgcolor="white">&dtml-x0;</td>
<td bgcolor="white">&dtml-x1;</td>
<td bgcolor="white">&dtml-x2;</td>
<td bgcolor="white">&dtml-x3;</td>
<td bgcolor="white">&dtml-x4;</td>
<td bgcolor="white">&dtml-x5;</td>
<td bgcolor="white">&dtml-x6;</td>
<td bgcolor="white">&dtml-x7;</td>
<dtml-in r64>
<td bgcolor="white">&dtml-x0;</td>
<td bgcolor="white">&dtml-x1;</td>
<td bgcolor="white">&dtml-x2;</td>
<td bgcolor="white">&dtml-x3;</td>
<td bgcolor="white">&dtml-x4;</td>
<td bgcolor="white">&dtml-x5;</td>
<td bgcolor="white">&dtml-x6;</td>
<td bgcolor="white">&dtml-x7;</td>
</dtml-in>
<dtml-in r64>
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
</dtml-in>
<dtml-in r64>
<td bgcolor="white">&dtml-x0;</td>
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
</dtml-in>
<dtml-in r8>
<dtml-let y0=x0 y1=x1 y2=x2 y3=x3 y4=x4 y5=x5 y6=x6 y7=x7>
<td bgcolor="white">&dtml-y0;</td>
<td bgcolor="white">&dtml-y1;</td>
<td bgcolor="white">&dtml-y2;</td>
<td bgcolor="white">&dtml-y3;</td>
<td bgcolor="white">&dtml-y4;</td>
<td bgcolor="white">&dtml-y5;</td>
<td bgcolor="white">&dtml-y6;</td>
<td bgcolor="white">&dtml-y7;</td>
</dtml-let>
</dtml-in>
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
<td bgcolor="white" tal:content="x0"></td>
<td bgcolor="white" tal:content="x1"></td>
<td bgcolor="white" tal:content="x2"></td>
<td bgcolor="white" tal:content="x3"></td>
<td bgcolor="white" tal:content="x4"></td>
<td bgcolor="white" tal:content="x5"></td>
<td bgcolor="white" tal:content="x6"></td>
<td bgcolor="white" tal:content="x7"></td>
<dtml-in tal:repeat="r r8">
<td bgcolor="white" tal:content="x0"></td>
<td bgcolor="white" tal:content="x1"></td>
<td bgcolor="white" tal:content="x2"></td>
<td bgcolor="white" tal:content="x3"></td>
</dtml-in>
<dtml-in tal:repeat="r r8">
<td bgcolor="white" tal:content="x0"></td>
<td bgcolor="white" tal:content="x1"></td>
<td bgcolor="white" tal:content="x2"></td>
<td bgcolor="white" tal:content="x3"></td>
<td bgcolor="white" tal:content="x4"></td>
<td bgcolor="white" tal:content="x5"></td>
<td bgcolor="white" tal:content="x6"></td>
<td bgcolor="white" tal:content="x7"></td>
</dtml-in>
<dtml-in tal:repeat="r r2">
<dtml-in tal:repeat="r r2">
<dtml-in tal:repeat="r r2">
<td bgcolor="white" tal:content="x0"></td>
<td bgcolor="white" tal:content="x1"></td>
<td bgcolor="white" tal:content="x2"></td>
<td bgcolor="white" tal:content="x3"></td>
<td bgcolor="white" tal:content="x4"></td>
<td bgcolor="white" tal:content="x5"></td>
<td bgcolor="white" tal:content="x6"></td>
<td bgcolor="white" tal:content="x7"></td>
</dtml-in>
</dtml-in>
</dtml-in>
<td bgcolor="white" tal:content="x0"></td>
<td bgcolor="white" tal:content="x1"></td>
<td bgcolor="white" tal:content="x2"></td>
<td bgcolor="white" tal:content="x3"></td>
<td bgcolor="white" tal:content="x4"></td>
<td bgcolor="white" tal:content="x5"></td>
<td bgcolor="white" tal:content="x6"></td>
<td bgcolor="white" tal:content="x7"></td>
<td bgcolor="white" tal:content="x0"></td>
<td bgcolor="white" tal:content="x1"></td>
<td bgcolor="white" tal:content="x2"></td>
<td bgcolor="white" tal:content="x3"></td>
<td bgcolor="white" tal:content="x4"></td>
<td bgcolor="white" tal:content="x5"></td>
<td bgcolor="white" tal:content="x6"></td>
<td bgcolor="white" tal:content="x7"></td>
<td bgcolor="white" tal:content="x0"></td>
<td bgcolor="white" tal:content="x1"></td>
<td bgcolor="white" tal:content="x2"></td>
<td bgcolor="white" tal:content="x3"></td>
<td bgcolor="white" tal:content="x4"></td>
<td bgcolor="white" tal:content="x5"></td>
<td bgcolor="white" tal:content="x6"></td>
<td bgcolor="white" tal:content="x7"></td>
<td bgcolor="white" tal:content="x0"></td>
<td bgcolor="white" tal:content="x1"></td>
<td bgcolor="white" tal:content="x2"></td>
<td bgcolor="white" tal:content="x3"></td>
<td bgcolor="white" tal:content="x4"></td>
<td bgcolor="white" tal:content="x5"></td>
<td bgcolor="white" tal:content="x6"></td>
<td bgcolor="white" tal:content="x7"></td>
<td bgcolor="white" tal:content="x0"></td>
<td bgcolor="white" tal:content="x1"></td>
<td bgcolor="white" tal:content="x2"></td>
<td bgcolor="white" tal:content="x3"></td>
<td bgcolor="white" tal:content="x4"></td>
<td bgcolor="white" tal:content="x5"></td>
<td bgcolor="white" tal:content="x6"></td>
<td bgcolor="white" tal:content="x7"></td>
<td bgcolor="white" tal:content="x0"></td>
<td bgcolor="white" tal:content="x1"></td>
<td bgcolor="white" tal:content="x2"></td>
<td bgcolor="white" tal:content="x3"></td>
<td bgcolor="white" tal:content="x4"></td>
<td bgcolor="white" tal:content="x5"></td>
<td bgcolor="white" tal:content="x6"></td>
<td bgcolor="white" tal:content="x7"></td>
<td bgcolor="white" tal:content="x0"></td>
<td bgcolor="white" tal:content="x1"></td>
<td bgcolor="white" tal:content="x2"></td>
<td bgcolor="white" tal:content="x3"></td>
<td bgcolor="white" tal:content="x4"></td>
<td bgcolor="white" tal:content="x5"></td>
<td bgcolor="white" tal:content="x6"></td>
<td bgcolor="white" tal:content="x7"></td>
<td bgcolor="white" tal:content="x0"></td>
<td bgcolor="white" tal:content="x1"></td>
<td bgcolor="white" tal:content="x2"></td>
<td bgcolor="white" tal:content="x3"></td>
<td bgcolor="white" tal:content="x4"></td>
<td bgcolor="white" tal:content="x5"></td>
<td bgcolor="white" tal:content="x6"></td>
<td bgcolor="white" tal:content="x7"></td>
<td bgcolor="white"><span tal:replace="x0"></span></td>
<td bgcolor="white"><span tal:replace="x1"></span></td>
<td bgcolor="white"><span tal:replace="x2"></span></td>
<td bgcolor="white"><span tal:replace="x3"></span></td>
<td bgcolor="white"><span tal:replace="x4"></span></td>
<td bgcolor="white"><span tal:replace="x5"></span></td>
<td bgcolor="white"><span tal:replace="x6"></span></td>
<td bgcolor="white"><span tal:replace="x7"></span></td>
<td bgcolor="white"><span tal:replace="x0"></span></td>
<td bgcolor="white"><span tal:replace="x1"></span></td>
<td bgcolor="white"><span tal:replace="x2"></span></td>
<td bgcolor="white"><span tal:replace="x3"></span></td>
<td bgcolor="white"><span tal:replace="x4"></span></td>
<td bgcolor="white"><span tal:replace="x5"></span></td>
<td bgcolor="white"><span tal:replace="x6"></span></td>
<td bgcolor="white"><span tal:replace="x7"></span></td>
<td bgcolor="white"><span tal:replace="x0"></span></td>
<td bgcolor="white"><span tal:replace="x1"></span></td>
<td bgcolor="white"><span tal:replace="x2"></span></td>
<td bgcolor="white"><span tal:replace="x3"></span></td>
<td bgcolor="white"><span tal:replace="x4"></span></td>
<td bgcolor="white"><span tal:replace="x5"></span></td>
<td bgcolor="white"><span tal:replace="x6"></span></td>
<td bgcolor="white"><span tal:replace="x7"></span></td>
<td bgcolor="white"><span tal:replace="x0"></span></td>
<td bgcolor="white"><span tal:replace="x1"></span></td>
<td bgcolor="white"><span tal:replace="x2"></span></td>
<td bgcolor="white"><span tal:replace="x3"></span></td>
<td bgcolor="white"><span tal:replace="x4"></span></td>
<td bgcolor="white"><span tal:replace="x5"></span></td>
<td bgcolor="white"><span tal:replace="x6"></span></td>
<td bgcolor="white"><span tal:replace="x7"></span></td>
<td bgcolor="white"><span tal:replace="x0"></span></td>
<td bgcolor="white"><span tal:replace="x1"></span></td>
<td bgcolor="white"><span tal:replace="x2"></span></td>
<td bgcolor="white"><span tal:replace="x3"></span></td>
<td bgcolor="white"><span tal:replace="x4"></span></td>
<td bgcolor="white"><span tal:replace="x5"></span></td>
<td bgcolor="white"><span tal:replace="x6"></span></td>
<td bgcolor="white"><span tal:replace="x7"></span></td>
<td bgcolor="white"><span tal:replace="x0"></span></td>
<td bgcolor="white"><span tal:replace="x1"></span></td>
<td bgcolor="white"><span tal:replace="x2"></span></td>
<td bgcolor="white"><span tal:replace="x3"></span></td>
<td bgcolor="white"><span tal:replace="x4"></span></td>
<td bgcolor="white"><span tal:replace="x5"></span></td>
<td bgcolor="white"><span tal:replace="x6"></span></td>
<td bgcolor="white"><span tal:replace="x7"></span></td>
<td bgcolor="white"><span tal:replace="x0"></span></td>
<td bgcolor="white"><span tal:replace="x1"></span></td>
<td bgcolor="white"><span tal:replace="x2"></span></td>
<td bgcolor="white"><span tal:replace="x3"></span></td>
<td bgcolor="white"><span tal:replace="x4"></span></td>
<td bgcolor="white"><span tal:replace="x5"></span></td>
<td bgcolor="white"><span tal:replace="x6"></span></td>
<td bgcolor="white"><span tal:replace="x7"></span></td>
<td bgcolor="white"><span tal:replace="x0"></span></td>
<td bgcolor="white"><span tal:replace="x1"></span></td>
<td bgcolor="white"><span tal:replace="x2"></span></td>
<td bgcolor="white"><span tal:replace="x3"></span></td>
<td bgcolor="white"><span tal:replace="x4"></span></td>
<td bgcolor="white"><span tal:replace="x5"></span></td>
<td bgcolor="white"><span tal:replace="x6"></span></td>
<td bgcolor="white"><span tal:replace="x7"></span></td>
<dtml-in tal:repeat="r r64">
<td bgcolor="white" tal:content="x0"></td>
<td bgcolor="white" tal:content="x1"></td>
<td bgcolor="white" tal:content="x2"></td>
<td bgcolor="white" tal:content="x3"></td>
<td bgcolor="white" tal:content="x4"></td>
<td bgcolor="white" tal:content="x5"></td>
<td bgcolor="white" tal:content="x6"></td>
<td bgcolor="white" tal:content="x7"></td>
</dtml-in>
<dtml-in tal:repeat="r r64">
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
</dtml-in>
<dtml-in tal:repeat="r r64">
<td bgcolor="white" tal:content="x0"></td>
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
A large chunk of text to be repeated.
</dtml-in>
<dtml-in tal:repeat="r r8">
<span tal:define="y0 x0;y1 x1;y2 x2;y3 x3;y4 x4;y5 x5;y6 x6;y7 x7">
<td bgcolor="white" tal:content="y0"></td>
<td bgcolor="white" tal:content="y1"></td>
<td bgcolor="white" tal:content="y2"></td>
<td bgcolor="white" tal:content="y3"></td>
<td bgcolor="white" tal:content="y4"></td>
<td bgcolor="white" tal:content="y5"></td>
<td bgcolor="white" tal:content="y6"></td>
<td bgcolor="white" tal:content="y7"></td>
</span>
</dtml-in>
#!/usr/bin/env python
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""
Driver program to test METAL and TAL implementation.
Usage: driver.py [options] [file]
Options:
-h / --help
Print this message and exit.
-H / --html
-x / --xml
Explicitly choose HTML or XML input. The default is to automatically
select based on the file extension. These options are mutually
exclusive.
-l
Lenient structure insertion.
-m
Macro expansion only
-s
Print intermediate opcodes only
-t
Leave TAL/METAL attributes in output
-i
Leave I18N substitution strings un-interpolated.
"""
import os
import sys
import getopt
if __name__ == "__main__":
import setpath # Local hack to tweak sys.path etc.
# Import local classes
import TALDefs
from DummyEngine import DummyEngine
from DummyEngine import DummyTranslationService
FILE = "tests/input/test01.xml"
class TestTranslations(DummyTranslationService):
def translate(self, domain, msgid, mapping=None, context=None,
target_language=None, default=None):
if msgid == 'timefmt':
return '%(minutes)s minutes after %(hours)s %(ampm)s' % mapping
elif msgid == 'jobnum':
return '%(jobnum)s is the JOB NUMBER' % mapping
elif msgid == 'verify':
s = 'Your contact email address is recorded as %(email)s'
return s % mapping
elif msgid == 'mailto:${request/submitter}':
return 'mailto:bperson@dom.ain'
elif msgid == 'origin':
return '%(name)s was born in %(country)s' % mapping
return DummyTranslationService.translate(self, domain, msgid,
mapping, context,
target_language,
default=default)
class TestEngine(DummyEngine):
def __init__(self, macros=None):
DummyEngine.__init__(self, macros)
self.translationService = TestTranslations()
def evaluatePathOrVar(self, expr):
if expr == 'here/currentTime':
return {'hours' : 6,
'minutes': 59,
'ampm' : 'PM',
}
elif expr == 'context/@@object_name':
return '7'
elif expr == 'request/submitter':
return 'aperson@dom.ain'
return DummyEngine.evaluatePathOrVar(self, expr)
# This is a disgusting hack so that we can use engines that actually know
# something about certain object paths. TimeEngine knows about
# here/currentTime.
ENGINES = {'test23.html': TestEngine,
'test24.html': TestEngine,
'test26.html': TestEngine,
'test27.html': TestEngine,
'test28.html': TestEngine,
'test29.html': TestEngine,
'test30.html': TestEngine,
'test31.html': TestEngine,
'test32.html': TestEngine,
}
def usage(code, msg=''):
# Python 2.1 required
print >> sys.stderr, __doc__
if msg:
print >> sys.stderr, msg
sys.exit(code)
def main():
macros = 0
mode = None
showcode = 0
showtal = -1
strictinsert = 1
i18nInterpolate = 1
try:
opts, args = getopt.getopt(sys.argv[1:], "hHxlmsti",
['help', 'html', 'xml'])
except getopt.error, msg:
usage(2, msg)
for opt, arg in opts:
if opt in ('-h', '--help'):
usage(0)
if opt in ('-H', '--html'):
if mode == 'xml':
usage(1, '--html and --xml are mutually exclusive')
mode = "html"
if opt == '-l':
strictinsert = 0
if opt == '-m':
macros = 1
if opt == '-n':
versionTest = 0
if opt in ('-x', '--xml'):
if mode == 'html':
usage(1, '--html and --xml are mutually exclusive')
mode = "xml"
if opt == '-s':
showcode = 1
if opt == '-t':
showtal = 1
if opt == '-i':
i18nInterpolate = 0
if args:
file = args[0]
else:
file = FILE
it = compilefile(file, mode)
if showcode:
showit(it)
else:
# See if we need a special engine for this test
engine = None
engineClass = ENGINES.get(os.path.basename(file))
if engineClass is not None:
engine = engineClass(macros)
interpretit(it, engine=engine,
tal=(not macros), showtal=showtal,
strictinsert=strictinsert,
i18nInterpolate=i18nInterpolate)
def interpretit(it, engine=None, stream=None, tal=1, showtal=-1,
strictinsert=1, i18nInterpolate=1):
from TALInterpreter import TALInterpreter
program, macros = it
assert TALDefs.isCurrentVersion(program)
if engine is None:
engine = DummyEngine(macros)
TALInterpreter(program, macros, engine, stream, wrap=0,
tal=tal, showtal=showtal, strictinsert=strictinsert,
i18nInterpolate=i18nInterpolate)()
def compilefile(file, mode=None):
assert mode in ("html", "xml", None)
if mode is None:
ext = os.path.splitext(file)[1]
if ext.lower() in (".html", ".htm"):
mode = "html"
else:
mode = "xml"
if mode == "html":
from HTMLTALParser import HTMLTALParser
p = HTMLTALParser()
else:
from TALParser import TALParser
p = TALParser()
p.parseFile(file)
return p.getCode()
def showit(it):
from pprint import pprint
pprint(it)
if __name__ == "__main__":
main()
#! /usr/bin/env python
# This software is subject to the provisions of the Zope Public License,
# Version 1.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
'''Run benchmarks of TAL vs. DTML'''
try:
import warnings
except ImportError:
pass
else:
warnings.filterwarnings("ignore", category=DeprecationWarning)
import os
os.environ['NO_SECURITY'] = 'true'
import sys, time
if __name__ == "__main__":
import setpath
from DocumentTemplate.DT_HTML import HTMLFile
from HTMLTALParser import HTMLTALParser
from TALInterpreter import TALInterpreter
from DummyEngine import DummyEngine
from cStringIO import StringIO
def time_apply(f, args, kwargs, count):
for i in range(4):
f(*args, **kwargs)
r = [None] * count
t0 = time.clock()
for i in r:
pass
t1 = time.clock()
for i in r:
f(*args, **kwargs)
t = time.clock() - t1 - (t1 - t0)
return t / count
def time_zpt(fn, count):
from Products.PageTemplates.PageTemplate import PageTemplate
pt = PageTemplate()
pt.write(open(fn).read())
return time_apply(pt.pt_render, (), {'extra_context': data}, count)
def time_tal(fn, count):
p = HTMLTALParser()
p.parseFile(fn)
program, macros = p.getCode()
engine = DummyEngine(macros)
engine.globals = data
tal = TALInterpreter(program, macros, engine, StringIO(), wrap=0,
tal=1, strictinsert=0)
return time_apply(tal, (), {}, count)
def time_dtml(fn, count):
html = HTMLFile(fn)
return time_apply(html, (), data, count)
def profile_zpt(fn, count, profiler):
from Products.PageTemplates.PageTemplate import PageTemplate
pt = PageTemplate()
pt.write(open(fn).read())
for i in range(4):
pt.pt_render(extra_context=data)
r = [None] * count
for i in r:
profiler.runcall(pt.pt_render, 0, data)
def profile_tal(fn, count, profiler):
p = HTMLTALParser()
p.parseFile(fn)
program, macros = p.getCode()
engine = DummyEngine(macros)
engine.globals = data
tal = TALInterpreter(program, macros, engine, StringIO(), wrap=0,
tal=1, strictinsert=0)
for i in range(4):
tal()
r = [None] * count
for i in r:
profiler.runcall(tal)
tal_fn = 'benchmark/tal%.2d.html'
dtml_fn = 'benchmark/dtml%.2d.html'
def compare(n, count, profiler=None):
t1 = int(time_zpt(tal_fn % n, count) * 1000 + 0.5)
t2 = int(time_tal(tal_fn % n, count) * 1000 + 0.5)
t3 = int(time_dtml(dtml_fn % n, count) * 1000 + 0.5)
print '%.2d: %10s %10s %10s' % (n, t1, t2, t3)
if profiler:
profile_tal(tal_fn % n, count, profiler)
def main(count, profiler=None):
n = 1
print '##: %10s %10s %10s' % ('ZPT', 'TAL', 'DTML')
while os.path.isfile(tal_fn % n) and os.path.isfile(dtml_fn % n):
compare(n, count, profiler)
n = n + 1
data = {'x':'X', 'r2': range(2), 'r8': range(8), 'r64': range(64)}
for i in range(10):
data['x%s' % i] = 'X%s' % i
if __name__ == "__main__":
filename = "markbench.prof"
profiler = None
if len(sys.argv) > 1 and sys.argv[1] == "-p":
import profile
profiler = profile.Profile()
del sys.argv[1]
if len(sys.argv) > 1:
for arg in sys.argv[1:]:
compare(int(arg), 25, profiler)
else:
main(25, profiler)
if profiler is not None:
profiler.dump_stats(filename)
import pstats
p = pstats.Stats(filename)
p.strip_dirs()
p.sort_stats('time', 'calls')
try:
p.print_stats(20)
except IOError, e:
if e.errno != errno.EPIPE:
raise
"""Shared support for scanning document type declarations in HTML and XHTML."""
import re, string
_declname_match = re.compile(r'[a-zA-Z][-_.a-zA-Z0-9]*\s*').match
_declstringlit_match = re.compile(r'(\'[^\']*\'|"[^"]*")\s*').match
del re
class ParserBase:
"""Parser base class which provides some common support methods used
by the SGML/HTML and XHTML parsers."""
def reset(self):
self.lineno = 1
self.offset = 0
def getpos(self):
"""Return current line number and offset."""
return self.lineno, self.offset
def error(self, message):
"""Return an error, showing current line number and offset.
Concrete subclasses *must* override this method.
"""
raise NotImplementedError
# Internal -- update line number and offset. This should be
# called for each piece of data exactly once, in order -- in other
# words the concatenation of all the input strings to this
# function should be exactly the entire input.
def updatepos(self, i, j):
if i >= j:
return j
rawdata = self.rawdata
nlines = rawdata.count("\n", i, j)
if nlines:
self.lineno = self.lineno + nlines
pos = rawdata.rindex("\n", i, j) # Should not fail
self.offset = j-(pos+1)
else:
self.offset = self.offset + j-i
return j
_decl_otherchars = ''
# Internal -- parse declaration (for use by subclasses).
def parse_declaration(self, i):
# This is some sort of declaration; in "HTML as
# deployed," this should only be the document type
# declaration ("<!DOCTYPE html...>").
rawdata = self.rawdata
import sys
j = i + 2
assert rawdata[i:j] == "<!", "unexpected call to parse_declaration"
if rawdata[j:j+1] in ("-", ""):
# Start of comment followed by buffer boundary,
# or just a buffer boundary.
return -1
# in practice, this should look like: ((name|stringlit) S*)+ '>'
n = len(rawdata)
decltype, j = self._scan_name(j, i)
if j < 0:
return j
if decltype == "doctype":
self._decl_otherchars = ''
while j < n:
c = rawdata[j]
if c == ">":
# end of declaration syntax
data = rawdata[i+2:j]
if decltype == "doctype":
self.handle_decl(data)
else:
self.unknown_decl(data)
return j + 1
if c in "\"'":
m = _declstringlit_match(rawdata, j)
if not m:
return -1 # incomplete
j = m.end()
elif c in "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ":
name, j = self._scan_name(j, i)
elif c in self._decl_otherchars:
j = j + 1
elif c == "[":
if decltype == "doctype":
j = self._parse_doctype_subset(j + 1, i)
else:
self.error("unexpected '[' char in declaration")
else:
self.error(
"unexpected %s char in declaration" % `rawdata[j]`)
if j < 0:
return j
return -1 # incomplete
# Internal -- scan past the internal subset in a <!DOCTYPE declaration,
# returning the index just past any whitespace following the trailing ']'.
def _parse_doctype_subset(self, i, declstartpos):
rawdata = self.rawdata
n = len(rawdata)
j = i
while j < n:
c = rawdata[j]
if c == "<":
s = rawdata[j:j+2]
if s == "<":
# end of buffer; incomplete
return -1
if s != "<!":
self.updatepos(declstartpos, j + 1)
self.error("unexpected char in internal subset (in %s)"
% `s`)
if (j + 2) == n:
# end of buffer; incomplete
return -1
if (j + 4) > n:
# end of buffer; incomplete
return -1
if rawdata[j:j+4] == "<!--":
j = self.parse_comment(j, report=0)
if j < 0:
return j
continue
name, j = self._scan_name(j + 2, declstartpos)
if j == -1:
return -1
if name not in ("attlist", "element", "entity", "notation"):
self.updatepos(declstartpos, j + 2)
self.error(
"unknown declaration %s in internal subset" % `name`)
# handle the individual names
meth = getattr(self, "_parse_doctype_" + name)
j = meth(j, declstartpos)
if j < 0:
return j
elif c == "%":
# parameter entity reference
if (j + 1) == n:
# end of buffer; incomplete
return -1
s, j = self._scan_name(j + 1, declstartpos)
if j < 0:
return j
if rawdata[j] == ";":
j = j + 1
elif c == "]":
j = j + 1
while j < n and rawdata[j] in string.whitespace:
j = j + 1
if j < n:
if rawdata[j] == ">":
return j
self.updatepos(declstartpos, j)
self.error("unexpected char after internal subset")
else:
return -1
elif c in string.whitespace:
j = j + 1
else:
self.updatepos(declstartpos, j)
self.error("unexpected char %s in internal subset" % `c`)
# end of buffer reached
return -1
# Internal -- scan past <!ELEMENT declarations
def _parse_doctype_element(self, i, declstartpos):
rawdata = self.rawdata
n = len(rawdata)
name, j = self._scan_name(i, declstartpos)
if j == -1:
return -1
# style content model; just skip until '>'
if '>' in rawdata[j:]:
return rawdata.find(">", j) + 1
return -1
# Internal -- scan past <!ATTLIST declarations
def _parse_doctype_attlist(self, i, declstartpos):
rawdata = self.rawdata
name, j = self._scan_name(i, declstartpos)
c = rawdata[j:j+1]
if c == "":
return -1
if c == ">":
return j + 1
while 1:
# scan a series of attribute descriptions; simplified:
# name type [value] [#constraint]
name, j = self._scan_name(j, declstartpos)
if j < 0:
return j
c = rawdata[j:j+1]
if c == "":
return -1
if c == "(":
# an enumerated type; look for ')'
if ")" in rawdata[j:]:
j = rawdata.find(")", j) + 1
else:
return -1
while rawdata[j:j+1].isspace():
j = j + 1
if not rawdata[j:]:
# end of buffer, incomplete
return -1
else:
name, j = self._scan_name(j, declstartpos)
c = rawdata[j:j+1]
if not c:
return -1
if c in "'\"":
m = _declstringlit_match(rawdata, j)
if m:
j = m.end()
else:
return -1
c = rawdata[j:j+1]
if not c:
return -1
if c == "#":
if rawdata[j:] == "#":
# end of buffer
return -1
name, j = self._scan_name(j + 1, declstartpos)
if j < 0:
return j
c = rawdata[j:j+1]
if not c:
return -1
if c == '>':
# all done
return j + 1
# Internal -- scan past <!NOTATION declarations
def _parse_doctype_notation(self, i, declstartpos):
name, j = self._scan_name(i, declstartpos)
if j < 0:
return j
rawdata = self.rawdata
while 1:
c = rawdata[j:j+1]
if not c:
# end of buffer; incomplete
return -1
if c == '>':
return j + 1
if c in "'\"":
m = _declstringlit_match(rawdata, j)
if not m:
return -1
j = m.end()
else:
name, j = self._scan_name(j, declstartpos)
if j < 0:
return j
# Internal -- scan past <!ENTITY declarations
def _parse_doctype_entity(self, i, declstartpos):
rawdata = self.rawdata
if rawdata[i:i+1] == "%":
j = i + 1
while 1:
c = rawdata[j:j+1]
if not c:
return -1
if c in string.whitespace:
j = j + 1
else:
break
else:
j = i
name, j = self._scan_name(j, declstartpos)
if j < 0:
return j
while 1:
c = self.rawdata[j:j+1]
if not c:
return -1
if c in "'\"":
m = _declstringlit_match(rawdata, j)
if m:
j = m.end()
else:
return -1 # incomplete
elif c == ">":
return j + 1
else:
name, j = self._scan_name(j, declstartpos)
if j < 0:
return j
# Internal -- scan a name token and the new position and the token, or
# return -1 if we've reached the end of the buffer.
def _scan_name(self, i, declstartpos):
rawdata = self.rawdata
n = len(rawdata)
if i == n:
return None, -1
m = _declname_match(rawdata, i)
if m:
s = m.group()
name = s.strip()
if (i + len(s)) == n:
return None, -1 # end of buffer
return name.lower(), m.end()
else:
self.updatepos(declstartpos, i)
self.error("expected name token")
#! /usr/bin/env python
# Module ndiff version 1.6.0
# Released to the public domain 08-Dec-2000,
# by Tim Peters (tim.one@home.com).
# Provided as-is; use at your own risk; no warranty; no promises; enjoy!
"""ndiff [-q] file1 file2
or
ndiff (-r1 | -r2) < ndiff_output > file1_or_file2
Print a human-friendly file difference report to stdout. Both inter-
and intra-line differences are noted. In the second form, recreate file1
(-r1) or file2 (-r2) on stdout, from an ndiff report on stdin.
In the first form, if -q ("quiet") is not specified, the first two lines
of output are
-: file1
+: file2
Each remaining line begins with a two-letter code:
"- " line unique to file1
"+ " line unique to file2
" " line common to both files
"? " line not present in either input file
Lines beginning with "? " attempt to guide the eye to intraline
differences, and were not present in either input file. These lines can be
confusing if the source files contain tab characters.
The first file can be recovered by retaining only lines that begin with
" " or "- ", and deleting those 2-character prefixes; use ndiff with -r1.
The second file can be recovered similarly, but by retaining only " " and
"+ " lines; use ndiff with -r2; or, on Unix, the second file can be
recovered by piping the output through
sed -n '/^[+ ] /s/^..//p'
See module comments for details and programmatic interface.
"""
__version__ = 1, 5, 0
# SequenceMatcher tries to compute a "human-friendly diff" between
# two sequences (chiefly picturing a file as a sequence of lines,
# and a line as a sequence of characters, here). Unlike e.g. UNIX(tm)
# diff, the fundamental notion is the longest *contiguous* & junk-free
# matching subsequence. That's what catches peoples' eyes. The
# Windows(tm) windiff has another interesting notion, pairing up elements
# that appear uniquely in each sequence. That, and the method here,
# appear to yield more intuitive difference reports than does diff. This
# method appears to be the least vulnerable to synching up on blocks
# of "junk lines", though (like blank lines in ordinary text files,
# or maybe "<P>" lines in HTML files). That may be because this is
# the only method of the 3 that has a *concept* of "junk" <wink>.
#
# Note that ndiff makes no claim to produce a *minimal* diff. To the
# contrary, minimal diffs are often counter-intuitive, because they
# synch up anywhere possible, sometimes accidental matches 100 pages
# apart. Restricting synch points to contiguous matches preserves some
# notion of locality, at the occasional cost of producing a longer diff.
#
# With respect to junk, an earlier version of ndiff simply refused to
# *start* a match with a junk element. The result was cases like this:
# before: private Thread currentThread;
# after: private volatile Thread currentThread;
# If you consider whitespace to be junk, the longest contiguous match
# not starting with junk is "e Thread currentThread". So ndiff reported
# that "e volatil" was inserted between the 't' and the 'e' in "private".
# While an accurate view, to people that's absurd. The current version
# looks for matching blocks that are entirely junk-free, then extends the
# longest one of those as far as possible but only with matching junk.
# So now "currentThread" is matched, then extended to suck up the
# preceding blank; then "private" is matched, and extended to suck up the
# following blank; then "Thread" is matched; and finally ndiff reports
# that "volatile " was inserted before "Thread". The only quibble
# remaining is that perhaps it was really the case that " volatile"
# was inserted after "private". I can live with that <wink>.
#
# NOTE on junk: the module-level names
# IS_LINE_JUNK
# IS_CHARACTER_JUNK
# can be set to any functions you like. The first one should accept
# a single string argument, and return true iff the string is junk.
# The default is whether the regexp r"\s*#?\s*$" matches (i.e., a
# line without visible characters, except for at most one splat).
# The second should accept a string of length 1 etc. The default is
# whether the character is a blank or tab (note: bad idea to include
# newline in this!).
#
# After setting those, you can call fcompare(f1name, f2name) with the
# names of the files you want to compare. The difference report
# is sent to stdout. Or you can call main(args), passing what would
# have been in sys.argv[1:] had the cmd-line form been used.
TRACE = 0
# define what "junk" means
import re
def IS_LINE_JUNK(line, pat=re.compile(r"\s*#?\s*$").match):
return pat(line) is not None
def IS_CHARACTER_JUNK(ch, ws=" \t"):
return ch in ws
del re
class SequenceMatcher:
def __init__(self, isjunk=None, a='', b=''):
# Members:
# a
# first sequence
# b
# second sequence; differences are computed as "what do
# we need to do to 'a' to change it into 'b'?"
# b2j
# for x in b, b2j[x] is a list of the indices (into b)
# at which x appears; junk elements do not appear
# b2jhas
# b2j.has_key
# fullbcount
# for x in b, fullbcount[x] == the number of times x
# appears in b; only materialized if really needed (used
# only for computing quick_ratio())
# matching_blocks
# a list of (i, j, k) triples, where a[i:i+k] == b[j:j+k];
# ascending & non-overlapping in i and in j; terminated by
# a dummy (len(a), len(b), 0) sentinel
# opcodes
# a list of (tag, i1, i2, j1, j2) tuples, where tag is
# one of
# 'replace' a[i1:i2] should be replaced by b[j1:j2]
# 'delete' a[i1:i2] should be deleted
# 'insert' b[j1:j2] should be inserted
# 'equal' a[i1:i2] == b[j1:j2]
# isjunk
# a user-supplied function taking a sequence element and
# returning true iff the element is "junk" -- this has
# subtle but helpful effects on the algorithm, which I'll
# get around to writing up someday <0.9 wink>.
# DON'T USE! Only __chain_b uses this. Use isbjunk.
# isbjunk
# for x in b, isbjunk(x) == isjunk(x) but much faster;
# it's really the has_key method of a hidden dict.
# DOES NOT WORK for x in a!
self.isjunk = isjunk
self.a = self.b = None
self.set_seqs(a, b)
def set_seqs(self, a, b):
self.set_seq1(a)
self.set_seq2(b)
def set_seq1(self, a):
if a is self.a:
return
self.a = a
self.matching_blocks = self.opcodes = None
def set_seq2(self, b):
if b is self.b:
return
self.b = b
self.matching_blocks = self.opcodes = None
self.fullbcount = None
self.__chain_b()
# For each element x in b, set b2j[x] to a list of the indices in
# b where x appears; the indices are in increasing order; note that
# the number of times x appears in b is len(b2j[x]) ...
# when self.isjunk is defined, junk elements don't show up in this
# map at all, which stops the central find_longest_match method
# from starting any matching block at a junk element ...
# also creates the fast isbjunk function ...
# note that this is only called when b changes; so for cross-product
# kinds of matches, it's best to call set_seq2 once, then set_seq1
# repeatedly
def __chain_b(self):
# Because isjunk is a user-defined (not C) function, and we test
# for junk a LOT, it's important to minimize the number of calls.
# Before the tricks described here, __chain_b was by far the most
# time-consuming routine in the whole module! If anyone sees
# Jim Roskind, thank him again for profile.py -- I never would
# have guessed that.
# The first trick is to build b2j ignoring the possibility
# of junk. I.e., we don't call isjunk at all yet. Throwing
# out the junk later is much cheaper than building b2j "right"
# from the start.
b = self.b
self.b2j = b2j = {}
self.b2jhas = b2jhas = b2j.has_key
for i in xrange(len(b)):
elt = b[i]
if b2jhas(elt):
b2j[elt].append(i)
else:
b2j[elt] = [i]
# Now b2j.keys() contains elements uniquely, and especially when
# the sequence is a string, that's usually a good deal smaller
# than len(string). The difference is the number of isjunk calls
# saved.
isjunk, junkdict = self.isjunk, {}
if isjunk:
for elt in b2j.keys():
if isjunk(elt):
junkdict[elt] = 1 # value irrelevant; it's a set
del b2j[elt]
# Now for x in b, isjunk(x) == junkdict.has_key(x), but the
# latter is much faster. Note too that while there may be a
# lot of junk in the sequence, the number of *unique* junk
# elements is probably small. So the memory burden of keeping
# this dict alive is likely trivial compared to the size of b2j.
self.isbjunk = junkdict.has_key
def find_longest_match(self, alo, ahi, blo, bhi):
"""Find longest matching block in a[alo:ahi] and b[blo:bhi].
If isjunk is not defined:
Return (i,j,k) such that a[i:i+k] is equal to b[j:j+k], where
alo <= i <= i+k <= ahi
blo <= j <= j+k <= bhi
and for all (i',j',k') meeting those conditions,
k >= k'
i <= i'
and if i == i', j <= j'
In other words, of all maximal matching blocks, return one
that starts earliest in a, and of all those maximal matching
blocks that start earliest in a, return the one that starts
earliest in b.
If isjunk is defined, first the longest matching block is
determined as above, but with the additional restriction that
no junk element appears in the block. Then that block is
extended as far as possible by matching (only) junk elements on
both sides. So the resulting block never matches on junk except
as identical junk happens to be adjacent to an "interesting"
match.
If no blocks match, return (alo, blo, 0).
"""
# CAUTION: stripping common prefix or suffix would be incorrect.
# E.g.,
# ab
# acab
# Longest matching block is "ab", but if common prefix is
# stripped, it's "a" (tied with "b"). UNIX(tm) diff does so
# strip, so ends up claiming that ab is changed to acab by
# inserting "ca" in the middle. That's minimal but unintuitive:
# "it's obvious" that someone inserted "ac" at the front.
# Windiff ends up at the same place as diff, but by pairing up
# the unique 'b's and then matching the first two 'a's.
a, b, b2j, isbjunk = self.a, self.b, self.b2j, self.isbjunk
besti, bestj, bestsize = alo, blo, 0
# find longest junk-free match
# during an iteration of the loop, j2len[j] = length of longest
# junk-free match ending with a[i-1] and b[j]
j2len = {}
nothing = []
for i in xrange(alo, ahi):
# look at all instances of a[i] in b; note that because
# b2j has no junk keys, the loop is skipped if a[i] is junk
j2lenget = j2len.get
newj2len = {}
for j in b2j.get(a[i], nothing):
# a[i] matches b[j]
if j < blo:
continue
if j >= bhi:
break
k = newj2len[j] = j2lenget(j-1, 0) + 1
if k > bestsize:
besti, bestj, bestsize = i-k+1, j-k+1, k
j2len = newj2len
# Now that we have a wholly interesting match (albeit possibly
# empty!), we may as well suck up the matching junk on each
# side of it too. Can't think of a good reason not to, and it
# saves post-processing the (possibly considerable) expense of
# figuring out what to do with it. In the case of an empty
# interesting match, this is clearly the right thing to do,
# because no other kind of match is possible in the regions.
while besti > alo and bestj > blo and \
isbjunk(b[bestj-1]) and \
a[besti-1] == b[bestj-1]:
besti, bestj, bestsize = besti-1, bestj-1, bestsize+1
while besti+bestsize < ahi and bestj+bestsize < bhi and \
isbjunk(b[bestj+bestsize]) and \
a[besti+bestsize] == b[bestj+bestsize]:
bestsize = bestsize + 1
if TRACE:
print "get_matching_blocks", alo, ahi, blo, bhi
print " returns", besti, bestj, bestsize
return besti, bestj, bestsize
def get_matching_blocks(self):
if self.matching_blocks is not None:
return self.matching_blocks
self.matching_blocks = []
la, lb = len(self.a), len(self.b)
self.__helper(0, la, 0, lb, self.matching_blocks)
self.matching_blocks.append( (la, lb, 0) )
if TRACE:
print '*** matching blocks', self.matching_blocks
return self.matching_blocks
# builds list of matching blocks covering a[alo:ahi] and
# b[blo:bhi], appending them in increasing order to answer
def __helper(self, alo, ahi, blo, bhi, answer):
i, j, k = x = self.find_longest_match(alo, ahi, blo, bhi)
# a[alo:i] vs b[blo:j] unknown
# a[i:i+k] same as b[j:j+k]
# a[i+k:ahi] vs b[j+k:bhi] unknown
if k:
if alo < i and blo < j:
self.__helper(alo, i, blo, j, answer)
answer.append(x)
if i+k < ahi and j+k < bhi:
self.__helper(i+k, ahi, j+k, bhi, answer)
def ratio(self):
"""Return a measure of the sequences' similarity (float in [0,1]).
Where T is the total number of elements in both sequences, and
M is the number of matches, this is 2*M / T.
Note that this is 1 if the sequences are identical, and 0 if
they have nothing in common.
"""
matches = reduce(lambda sum, triple: sum + triple[-1],
self.get_matching_blocks(), 0)
return 2.0 * matches / (len(self.a) + len(self.b))
def quick_ratio(self):
"""Return an upper bound on ratio() relatively quickly."""
# viewing a and b as multisets, set matches to the cardinality
# of their intersection; this counts the number of matches
# without regard to order, so is clearly an upper bound
if self.fullbcount is None:
self.fullbcount = fullbcount = {}
for elt in self.b:
fullbcount[elt] = fullbcount.get(elt, 0) + 1
fullbcount = self.fullbcount
# avail[x] is the number of times x appears in 'b' less the
# number of times we've seen it in 'a' so far ... kinda
avail = {}
availhas, matches = avail.has_key, 0
for elt in self.a:
if availhas(elt):
numb = avail[elt]
else:
numb = fullbcount.get(elt, 0)
avail[elt] = numb - 1
if numb > 0:
matches = matches + 1
return 2.0 * matches / (len(self.a) + len(self.b))
def real_quick_ratio(self):
"""Return an upper bound on ratio() very quickly"""
la, lb = len(self.a), len(self.b)
# can't have more matches than the number of elements in the
# shorter sequence
return 2.0 * min(la, lb) / (la + lb)
def get_opcodes(self):
if self.opcodes is not None:
return self.opcodes
i = j = 0
self.opcodes = answer = []
for ai, bj, size in self.get_matching_blocks():
# invariant: we've pumped out correct diffs to change
# a[:i] into b[:j], and the next matching block is
# a[ai:ai+size] == b[bj:bj+size]. So we need to pump
# out a diff to change a[i:ai] into b[j:bj], pump out
# the matching block, and move (i,j) beyond the match
tag = ''
if i < ai and j < bj:
tag = 'replace'
elif i < ai:
tag = 'delete'
elif j < bj:
tag = 'insert'
if tag:
answer.append( (tag, i, ai, j, bj) )
i, j = ai+size, bj+size
# the list of matching blocks is terminated by a
# sentinel with size 0
if size:
answer.append( ('equal', ai, i, bj, j) )
return answer
# meant for dumping lines
def dump(tag, x, lo, hi):
for i in xrange(lo, hi):
print tag, x[i],
def plain_replace(a, alo, ahi, b, blo, bhi):
assert alo < ahi and blo < bhi
# dump the shorter block first -- reduces the burden on short-term
# memory if the blocks are of very different sizes
if bhi - blo < ahi - alo:
dump('+', b, blo, bhi)
dump('-', a, alo, ahi)
else:
dump('-', a, alo, ahi)
dump('+', b, blo, bhi)
# When replacing one block of lines with another, this guy searches
# the blocks for *similar* lines; the best-matching pair (if any) is
# used as a synch point, and intraline difference marking is done on
# the similar pair. Lots of work, but often worth it.
def fancy_replace(a, alo, ahi, b, blo, bhi):
if TRACE:
print '*** fancy_replace', alo, ahi, blo, bhi
dump('>', a, alo, ahi)
dump('<', b, blo, bhi)
# don't synch up unless the lines have a similarity score of at
# least cutoff; best_ratio tracks the best score seen so far
best_ratio, cutoff = 0.74, 0.75
cruncher = SequenceMatcher(IS_CHARACTER_JUNK)
eqi, eqj = None, None # 1st indices of equal lines (if any)
# search for the pair that matches best without being identical
# (identical lines must be junk lines, & we don't want to synch up
# on junk -- unless we have to)
for j in xrange(blo, bhi):
bj = b[j]
cruncher.set_seq2(bj)
for i in xrange(alo, ahi):
ai = a[i]
if ai == bj:
if eqi is None:
eqi, eqj = i, j
continue
cruncher.set_seq1(ai)
# computing similarity is expensive, so use the quick
# upper bounds first -- have seen this speed up messy
# compares by a factor of 3.
# note that ratio() is only expensive to compute the first
# time it's called on a sequence pair; the expensive part
# of the computation is cached by cruncher
if cruncher.real_quick_ratio() > best_ratio and \
cruncher.quick_ratio() > best_ratio and \
cruncher.ratio() > best_ratio:
best_ratio, best_i, best_j = cruncher.ratio(), i, j
if best_ratio < cutoff:
# no non-identical "pretty close" pair
if eqi is None:
# no identical pair either -- treat it as a straight replace
plain_replace(a, alo, ahi, b, blo, bhi)
return
# no close pair, but an identical pair -- synch up on that
best_i, best_j, best_ratio = eqi, eqj, 1.0
else:
# there's a close pair, so forget the identical pair (if any)
eqi = None
# a[best_i] very similar to b[best_j]; eqi is None iff they're not
# identical
if TRACE:
print '*** best_ratio', best_ratio, best_i, best_j
dump('>', a, best_i, best_i+1)
dump('<', b, best_j, best_j+1)
# pump out diffs from before the synch point
fancy_helper(a, alo, best_i, b, blo, best_j)
# do intraline marking on the synch pair
aelt, belt = a[best_i], b[best_j]
if eqi is None:
# pump out a '-', '?', '+', '?' quad for the synched lines
atags = btags = ""
cruncher.set_seqs(aelt, belt)
for tag, ai1, ai2, bj1, bj2 in cruncher.get_opcodes():
la, lb = ai2 - ai1, bj2 - bj1
if tag == 'replace':
atags = atags + '^' * la
btags = btags + '^' * lb
elif tag == 'delete':
atags = atags + '-' * la
elif tag == 'insert':
btags = btags + '+' * lb
elif tag == 'equal':
atags = atags + ' ' * la
btags = btags + ' ' * lb
else:
raise ValueError, 'unknown tag ' + `tag`
printq(aelt, belt, atags, btags)
else:
# the synch pair is identical
print ' ', aelt,
# pump out diffs from after the synch point
fancy_helper(a, best_i+1, ahi, b, best_j+1, bhi)
def fancy_helper(a, alo, ahi, b, blo, bhi):
if alo < ahi:
if blo < bhi:
fancy_replace(a, alo, ahi, b, blo, bhi)
else:
dump('-', a, alo, ahi)
elif blo < bhi:
dump('+', b, blo, bhi)
# Crap to deal with leading tabs in "?" output. Can hurt, but will
# probably help most of the time.
def printq(aline, bline, atags, btags):
common = min(count_leading(aline, "\t"),
count_leading(bline, "\t"))
common = min(common, count_leading(atags[:common], " "))
print "-", aline,
if count_leading(atags, " ") < len(atags):
print "?", "\t" * common + atags[common:]
print "+", bline,
if count_leading(btags, " ") < len(btags):
print "?", "\t" * common + btags[common:]
def count_leading(line, ch):
i, n = 0, len(line)
while i < n and line[i] == ch:
i = i + 1
return i
def fail(msg):
import sys
out = sys.stderr.write
out(msg + "\n\n")
out(__doc__)
return 0
# open a file & return the file object; gripe and return 0 if it
# couldn't be opened
def fopen(fname):
try:
return open(fname, 'r')
except IOError, detail:
return fail("couldn't open " + fname + ": " + str(detail))
# open two files & spray the diff to stdout; return false iff a problem
def fcompare(f1name, f2name):
f1 = fopen(f1name)
f2 = fopen(f2name)
if not f1 or not f2:
return 0
a = f1.readlines(); f1.close()
b = f2.readlines(); f2.close()
cruncher = SequenceMatcher(IS_LINE_JUNK, a, b)
for tag, alo, ahi, blo, bhi in cruncher.get_opcodes():
if tag == 'replace':
fancy_replace(a, alo, ahi, b, blo, bhi)
elif tag == 'delete':
dump('-', a, alo, ahi)
elif tag == 'insert':
dump('+', b, blo, bhi)
elif tag == 'equal':
dump(' ', a, alo, ahi)
else:
raise ValueError, 'unknown tag ' + `tag`
return 1
# crack args (sys.argv[1:] is normal) & compare;
# return false iff a problem
def main(args):
import getopt
try:
opts, args = getopt.getopt(args, "qr:")
except getopt.error, detail:
return fail(str(detail))
noisy = 1
qseen = rseen = 0
for opt, val in opts:
if opt == "-q":
qseen = 1
noisy = 0
elif opt == "-r":
rseen = 1
whichfile = val
if qseen and rseen:
return fail("can't specify both -q and -r")
if rseen:
if args:
return fail("no args allowed with -r option")
if whichfile in "12":
restore(whichfile)
return 1
return fail("-r value must be 1 or 2")
if len(args) != 2:
return fail("need 2 filename args")
f1name, f2name = args
if noisy:
print '-:', f1name
print '+:', f2name
return fcompare(f1name, f2name)
def restore(which):
import sys
tag = {"1": "- ", "2": "+ "}[which]
prefixes = (" ", tag)
for line in sys.stdin.readlines():
if line[:2] in prefixes:
print line[2:],
if __name__ == '__main__':
import sys
args = sys.argv[1:]
if "-profile" in args:
import profile, pstats
args.remove("-profile")
statf = "ndiff.pro"
profile.run("main(args)", statf)
stats = pstats.Stats(statf)
stats.strip_dirs().sort_stats('time').print_stats()
else:
main(args)
#! /usr/bin/env python
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
#
##############################################################################
"""
Driver program to run METAL and TAL regression tests.
"""
import sys
import os
from cStringIO import StringIO
import glob
import traceback
if __name__ == "__main__":
import setpath # Local hack to tweak sys.path etc.
import driver
import tests.utils
def showdiff(a, b):
import ndiff
cruncher = ndiff.SequenceMatcher(ndiff.IS_LINE_JUNK, a, b)
for tag, alo, ahi, blo, bhi in cruncher.get_opcodes():
if tag == "equal":
continue
print nicerange(alo, ahi) + tag[0] + nicerange(blo, bhi)
ndiff.dump('<', a, alo, ahi)
if a and b:
print '---'
ndiff.dump('>', b, blo, bhi)
def nicerange(lo, hi):
if hi <= lo+1:
return str(lo+1)
else:
return "%d,%d" % (lo+1, hi)
def main():
opts = []
args = sys.argv[1:]
quiet = 0
unittesting = 0
if args and args[0] == "-q":
quiet = 1
del args[0]
if args and args[0] == "-Q":
unittesting = 1
del args[0]
while args and args[0].startswith('-'):
opts.append(args[0])
del args[0]
if not args:
prefix = os.path.join("tests", "input", "test*.")
if tests.utils.skipxml:
xmlargs = []
else:
xmlargs = glob.glob(prefix + "xml")
xmlargs.sort()
htmlargs = glob.glob(prefix + "html")
htmlargs.sort()
args = xmlargs + htmlargs
if not args:
sys.stderr.write("No tests found -- please supply filenames\n")
sys.exit(1)
errors = 0
for arg in args:
locopts = []
if arg.find("metal") >= 0 and "-m" not in opts:
locopts.append("-m")
if not unittesting:
print arg,
sys.stdout.flush()
if tests.utils.skipxml and arg.endswith(".xml"):
print "SKIPPED (XML parser not available)"
continue
save = sys.stdout, sys.argv
try:
try:
sys.stdout = stdout = StringIO()
sys.argv = [""] + opts + locopts + [arg]
driver.main()
finally:
sys.stdout, sys.argv = save
except SystemExit:
raise
except:
errors = 1
if quiet:
print sys.exc_type
sys.stdout.flush()
else:
if unittesting:
print
else:
print "Failed:"
sys.stdout.flush()
traceback.print_exc()
continue
head, tail = os.path.split(arg)
outfile = os.path.join(
head.replace("input", "output"),
tail)
try:
f = open(outfile)
except IOError:
expected = None
print "(missing file %s)" % outfile,
else:
expected = f.readlines()
f.close()
stdout.seek(0)
if hasattr(stdout, "readlines"):
actual = stdout.readlines()
else:
actual = readlines(stdout)
if actual == expected:
if not unittesting:
print "OK"
else:
if unittesting:
print
else:
print "not OK"
errors = 1
if not quiet and expected is not None:
showdiff(expected, actual)
if errors:
sys.exit(1)
def readlines(f):
L = []
while 1:
line = f.readline()
if not line:
break
L.append(line)
return L
if __name__ == "__main__":
main()
# This software is subject to the provisions of the Zope Public License,
# Version 1.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
"""
Read a module search path from .path.
"""
import os
import sys
dir = os.path.dirname(__file__)
path = os.path.join(dir, ".path")
try:
f = open(path)
except IOError:
raise IOError, "Please edit .path to point to <Zope2/lib/python>"
else:
for line in f.readlines():
line = line.strip()
if line and line[0] != '#':
for dir in line.split(os.pathsep):
dir = os.path.expanduser(os.path.expandvars(dir))
if dir not in sys.path:
sys.path.append(dir)
import ZODB # Must import this first to initialize Persistence properly
#!/usr/bin/env python
##############################################################################
#
# Copyright (c) 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Program to extract internationalization markup from Page Templates.
Once you have marked up a Page Template file with i18n: namespace tags, use
this program to extract GNU gettext .po file entries.
Usage: talgettext.py [options] files
Options:
-h / --help
Print this message and exit.
-o / --output <file>
Output the translation .po file to <file>.
-u / --update <file>
Update the existing translation <file> with any new translation strings
found.
"""
import sys
import time
import getopt
import traceback
from TAL.HTMLTALParser import HTMLTALParser
from TAL.TALInterpreter import TALInterpreter
from TAL.DummyEngine import DummyEngine
from ITALES import ITALESEngine
from TAL.TALDefs import TALESError
__version__ = '$Revision: 1.1.2.1 $'
pot_header = '''\
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR ORGANIZATION
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\\n"
"POT-Creation-Date: %(time)s\\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\\n"
"Language-Team: LANGUAGE <LL@li.org>\\n"
"MIME-Version: 1.0\\n"
"Content-Type: text/plain; charset=CHARSET\\n"
"Content-Transfer-Encoding: ENCODING\\n"
"Generated-By: talgettext.py %(version)s\\n"
'''
NLSTR = '"\n"'
try:
True
except NameError:
True=1
False=0
def usage(code, msg=''):
# Python 2.1 required
print >> sys.stderr, __doc__
if msg:
print >> sys.stderr, msg
sys.exit(code)
class POTALInterpreter(TALInterpreter):
def translate(self, msgid, default, i18ndict=None, obj=None):
# XXX is this right?
if i18ndict is None:
i18ndict = {}
if obj:
i18ndict.update(obj)
# XXX Mmmh, it seems that sometimes the msgid is None; is that really
# possible?
if msgid is None:
return None
# XXX We need to pass in one of context or target_language
return self.engine.translate(msgid, self.i18nContext.domain, i18ndict,
position=self.position, default=default)
class POEngine(DummyEngine):
__implements__ = ITALESEngine
def __init__(self, macros=None):
self.catalog = {}
DummyEngine.__init__(self, macros)
def evaluate(*args):
return '' # who cares
def evaluatePathOrVar(*args):
return '' # who cares
def evaluateSequence(self, expr):
return (0,) # dummy
def evaluateBoolean(self, expr):
return True # dummy
def translate(self, msgid, domain=None, mapping=None, default=None,
# XXX position is not part of the ITALESEngine
# interface
position=None):
if domain not in self.catalog:
self.catalog[domain] = {}
domain = self.catalog[domain]
# ---------------------------------------------
# only non-empty msgids are added to dictionary
# (changed by heinrichbernd - 2004/09/07)
# ---------------------------------------------
if msgid:
if msgid not in domain:
domain[msgid] = []
domain[msgid].append((self.file, position))
return 'x'
class UpdatePOEngine(POEngine):
"""A slightly-less braindead POEngine which supports loading an existing
.po file first."""
def __init__ (self, macros=None, filename=None):
POEngine.__init__(self, macros)
self._filename = filename
self._loadFile()
self.base = self.catalog
self.catalog = {}
def __add(self, id, s, fuzzy):
"Add a non-fuzzy translation to the dictionary."
if not fuzzy and str:
# check for multi-line values and munge them appropriately
if '\n' in s:
lines = s.rstrip().split('\n')
s = NLSTR.join(lines)
self.catalog[id] = s
def _loadFile(self):
# shamelessly cribbed from Python's Tools/i18n/msgfmt.py
# 25-Mar-2003 Nathan R. Yergler (nathan@zope.org)
# 14-Apr-2003 Hacked by Barry Warsaw (barry@zope.com)
ID = 1
STR = 2
try:
lines = open(self._filename).readlines()
except IOError, msg:
print >> sys.stderr, msg
sys.exit(1)
section = None
fuzzy = False
# Parse the catalog
lno = 0
for l in lines:
lno += True
# If we get a comment line after a msgstr, this is a new entry
if l[0] == '#' and section == STR:
self.__add(msgid, msgstr, fuzzy)
section = None
fuzzy = False
# Record a fuzzy mark
if l[:2] == '#,' and l.find('fuzzy'):
fuzzy = True
# Skip comments
if l[0] == '#':
continue
# Now we are in a msgid section, output previous section
if l.startswith('msgid'):
if section == STR:
self.__add(msgid, msgstr, fuzzy)
section = ID
l = l[5:]
msgid = msgstr = ''
# Now we are in a msgstr section
elif l.startswith('msgstr'):
section = STR
l = l[6:]
# Skip empty lines
if not l.strip():
continue
# XXX: Does this always follow Python escape semantics?
l = eval(l)
if section == ID:
msgid += l
elif section == STR:
msgstr += '%s\n' % l
else:
print >> sys.stderr, 'Syntax error on %s:%d' % (infile, lno), \
'before:'
print >> sys.stderr, l
sys.exit(1)
# Add last entry
if section == STR:
self.__add(msgid, msgstr, fuzzy)
def evaluate(self, expression):
try:
return POEngine.evaluate(self, expression)
except TALESError:
pass
def evaluatePathOrVar(self, expr):
return 'who cares'
def translate(self, msgid, domain=None, mapping=None, default=None,
position=None):
if msgid not in self.base:
POEngine.translate(self, msgid, domain, mapping, default, position)
return 'x'
def main():
try:
opts, args = getopt.getopt(
sys.argv[1:],
'ho:u:',
['help', 'output=', 'update='])
except getopt.error, msg:
usage(1, msg)
outfile = None
engine = None
update_mode = False
for opt, arg in opts:
if opt in ('-h', '--help'):
usage(0)
elif opt in ('-o', '--output'):
outfile = arg
elif opt in ('-u', '--update'):
update_mode = True
if outfile is None:
outfile = arg
engine = UpdatePOEngine(filename=arg)
if not args:
print 'nothing to do'
return
# We don't care about the rendered output of the .pt file
class Devnull:
def write(self, s):
pass
# check if we've already instantiated an engine;
# if not, use the stupidest one available
if not engine:
engine = POEngine()
# process each file specified
for filename in args:
try:
engine.file = filename
p = HTMLTALParser()
p.parseFile(filename)
program, macros = p.getCode()
POTALInterpreter(program, macros, engine, stream=Devnull(),
metal=False)()
except: # Hee hee, I love bare excepts!
print 'There was an error processing', filename
traceback.print_exc()
# Now output the keys in the engine. Write them to a file if --output or
# --update was specified; otherwise use standard out.
if (outfile is None):
outfile = sys.stdout
else:
outfile = file(outfile, update_mode and "a" or "w")
catalog = {}
for domain in engine.catalog.keys():
catalog.update(engine.catalog[domain])
messages = catalog.copy()
try:
messages.update(engine.base)
except AttributeError:
pass
if '' not in messages:
print >> outfile, pot_header % {'time': time.ctime(),
'version': __version__}
msgids = catalog.keys()
# XXX: You should not sort by msgid, but by filename and position. (SR)
msgids.sort()
for msgid in msgids:
positions = catalog[msgid]
for filename, position in positions:
outfile.write('#: %s:%s\n' % (filename, position[0]))
outfile.write('msgid "%s"\n' % msgid)
outfile.write('msgstr ""\n')
outfile.write('\n')
if __name__ == '__main__':
main()
"""Empty file to make this directory a Python package."""
<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"DTD/xhtml1-transitional.dtd">
<html>
<head>dadada</head>
<body xmlns:z="http://xml.zope.org/namespaces/tal" z:define="foo python:1">
<h1 z:condition="python:0">This title is not displayed</h1>
<h1 z:condition="python:1" z:content="str:This
Is
The
Replaced
Title">Title</h1>
<!-- test entity references -->
&nbsp;&HarryPotter;
<!-- examples adapted from TemplateAttributeLanguageSyntax -->
<span z:content="str:here/id"/>
<p z:define="x str:template/title; global five python:2+3;" z:content="text var:five"/>
<p z:repeat="car python:['honda', 'subaru', 'acura']">
<span z:replace="var:car"/>
</p>
<p xml:foo="bar">foo bar</p>
<!-- more examples -->
<ul>
<span z:repeat="car python:['honda', 'subaru', 'acura']">
<li z:content="var:car">Car Name</li>
</span>
</ul>
<!-- test attribute expansion -->
<a href="foo" z:attributes="href python:'http://python.org' ">python</a>
<a z:attributes="href python:'http://python.org' ">python</a>
<!-- test insert/replace structure -->
<span z:content="structure python:None" />
<span z:replace="structure python:None" />
<span z:define="global x str:&lt;h3&gt;Header Level 3&lt;/h3&gt;" />
<span z:define="global x python:'&amp;' + 'nbsp;;' + x" />
<span z:replace="structure x" />
<span z:content="structure x" />
</body>
</html>
<?xml version="1.0" ?>
<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"DTD/xhtml1-transitional.dtd">
<html>
<head>dadada</head>
<body xmlns:z="http://xml.zope.org/namespaces/tal" z:define="foo python:1">
<h1 z:condition="python:0">This title is not displayed</h1>
<h1 z:condition="python:1" z:content="str:This
Is
The
Replaced
Title">Title</h1>
<!-- test entity references -->
&nbsp;&HarryPotter;
<!-- examples adapted from TemplateAttributeLanguageSyntax -->
<span z:content="str:here/id"/>
<p z:define="x str:template/title; global five python:2+3;" z:content="text var:five"/>
<p z:repeat="car python:['honda', 'subaru', 'acura']">
<span z:replace="var:car"/>
</p>
<p xml:foo="bar">foo bar</p>
<!-- more examples -->
<ul>
<span z:repeat="car python:['honda', 'subaru', 'acura']">
<li z:content="var:car">Car Name</li>
</span>
</ul>
<!-- test attribute expansion -->
<a href="foo" z:attributes="href python:'http://python.org' ">python</a>
<a z:attributes="href python:'http://python.org' ">python</a>
<!-- test insert/replace structure -->
<span z:content="structure python:None" />
<span z:replace="structure python:None" />
<span z:define="global x str:&lt;h3&gt;Header Level 3&lt;/h3&gt;" />
<span z:define="global x python:'&amp;' + 'nbsp;;' + x" />
<span z:replace="structure x" />
<span z:content="structure x" />
</body>
</html>
<biztalk_1 xmlns="urn:schemas-biztalk-org:biztalk:biztalk_1">
<foo:header xmlns:foo="whomping-willow" plain="guido" quote='"' apostrophe="'" both="&quot;'" lt="&lt;" gt="&gt;" amp="&amp;" foo="">
<manifest>
<document>
<name>sample1</name>
<description>a simple invoice</description>
</document>
</manifest>
</foo:header>
<body>
<!-- sample1.xml is an example of a simple invoice for a small restaurant supplies order -->
<Invoice xmlns="urn:http://schemas.biztalk.org/united_rest_com/yw7sg15x.xml">
<Header>
<InvoiceNumber>01786</InvoiceNumber>
<InvoiceDate>2000-03-17</InvoiceDate> <!-- March 17th, 2000 -->
<OrderNo>55377</OrderNo>
<OrderDate>2000-03-15</OrderDate> <!-- March 15th, 2000 -->
<CustomerPO>GJ03405</CustomerPO>
<ShipMethod>DAVE 1</ShipMethod>
<ShipDate>2000-03-17</ShipDate> <!-- March 17th, 2000 -->
<CustomerID>K5211(34)</CustomerID>
<SalesPersonCode>23</SalesPersonCode>
<TaxID>23</TaxID>
</Header>
<InvoiceTo>
<Name>SHIPWRIGHT RESTAURANTS LIMITED</Name>
<AddressLine>125 NORTH SERVICE ROAD W</AddressLine>
<AddressLine>WESTLAKE ACCESS</AddressLine>
<City>NORTH BAY</City>
<PostCode>L8B1O5</PostCode>
<State>ONTARIO</State>
<Country>CANADA</Country>
</InvoiceTo>
<ShipTo>
<Name/>
<AddressLine>ATTN: PAULINE DEGRASSI</AddressLine>
<City/>
<PostCode/>
<State/>
<Country/>
</ShipTo>
<DetailLines>
<DetailLine>
<QuantityShipped>1</QuantityShipped>
<UnitOfMeasure>CS</UnitOfMeasure>
<PartNumber>DM 5309</PartNumber>
<PartDescription>#1013 12 OZ.MUNICH STEIN</PartDescription>
<UnitPrice>37.72</UnitPrice>
<LineTotal>37.72</LineTotal>
</DetailLine>
<DetailLine>
<QuantityShipped>6</QuantityShipped>
<UnitOfMeasure>DZ</UnitOfMeasure>
<PartNumber>ON 6420</PartNumber>
<PartDescription>PROVINCIAL DINNER FORK</PartDescription>
<UnitPrice>17.98</UnitPrice>
<LineTotal>107.88</LineTotal>
</DetailLine>
<DetailLine>
<QuantityShipped>72</QuantityShipped>
<UnitOfMeasure>EA</UnitOfMeasure>
<PartNumber>JR20643</PartNumber>
<PartDescription>PLASTIC HANDLED STEAK KNIFE</PartDescription>
<UnitPrice>.81</UnitPrice>
<LineTotal>58.32</LineTotal>
</DetailLine>
<DetailLine>
<QuantityShipped>6</QuantityShipped>
<UnitOfMeasure>DZ</UnitOfMeasure>
<PartNumber>ON 6410</PartNumber>
<PartDescription>PROVINCIAL TEASPOONS</PartDescription>
<UnitPrice>12.16</UnitPrice>
<LineTotal>72.96</LineTotal>
</DetailLine>
<DetailLine>
<QuantityShipped>0</QuantityShipped>
<UnitOfMeasure>DZ</UnitOfMeasure>
<PartNumber>ON 6411</PartNumber>
<PartDescription>PROVINCIAL RD BOWL SPOON</PartDescription>
<QuantityBackOrdered>6</QuantityBackOrdered>
<UnitPrice>17.98</UnitPrice>
<LineTotal>0.00</LineTotal>
</DetailLine>
<DetailLine>
<QuantityShipped>1</QuantityShipped>
<UnitOfMeasure>EA</UnitOfMeasure>
<PartNumber>DO 3218</PartNumber>
<PartDescription>34 OZ DUAL DIAL SCALE AM3218</PartDescription>
<UnitPrice>70.00</UnitPrice>
<DiscountPercentage>5.0</DiscountPercentage>
<LineTotal>66.50</LineTotal>
</DetailLine>
<DetailLine>
<QuantityShipped>1</QuantityShipped>
<UnitOfMeasure>CS</UnitOfMeasure>
<PartNumber>DM 195</PartNumber>
<PartDescription>20 OZ.BEER PUB GLASS</PartDescription>
<UnitPrice>55.90</UnitPrice>
<LineTotal>55.90</LineTotal>
</DetailLine>
</DetailLines>
<Totals>
<SubTotal>399.28</SubTotal>
<DiscountTotal>3.50</DiscountTotal>
<FreightTotal>23.75</FreightTotal>
<GSTTotal>29.61</GSTTotal>
<ProvTaxTotal>33.84</ProvTaxTotal>
<OtherTotal>33.84</OtherTotal>
<InvoiceTotal>486.48</InvoiceTotal>
</Totals>
</Invoice>
</body>
</biztalk_1>
<?xml version="1.0" ?>
<biztalk_1 xmlns="urn:schemas-biztalk-org:biztalk:biztalk_1">
<foo:header xmlns:foo="whomping-willow" plain="guido" quote='"' apostrophe="'" both="&quot;'" lt="&lt;" gt="&gt;" amp="&amp;" foo="">
<manifest>
<document>
<name>sample1</name>
<description>a simple invoice</description>
</document>
</manifest>
</foo:header>
<body>
<!-- sample1.xml is an example of a simple invoice for a small restaurant supplies order -->
<Invoice xmlns="urn:http://schemas.biztalk.org/united_rest_com/yw7sg15x.xml">
<Header>
<InvoiceNumber>01786</InvoiceNumber>
<InvoiceDate>2000-03-17</InvoiceDate> <!-- March 17th, 2000 -->
<OrderNo>55377</OrderNo>
<OrderDate>2000-03-15</OrderDate> <!-- March 15th, 2000 -->
<CustomerPO>GJ03405</CustomerPO>
<ShipMethod>DAVE 1</ShipMethod>
<ShipDate>2000-03-17</ShipDate> <!-- March 17th, 2000 -->
<CustomerID>K5211(34)</CustomerID>
<SalesPersonCode>23</SalesPersonCode>
<TaxID>23</TaxID>
</Header>
<InvoiceTo>
<Name>SHIPWRIGHT RESTAURANTS LIMITED</Name>
<AddressLine>125 NORTH SERVICE ROAD W</AddressLine>
<AddressLine>WESTLAKE ACCESS</AddressLine>
<City>NORTH BAY</City>
<PostCode>L8B1O5</PostCode>
<State>ONTARIO</State>
<Country>CANADA</Country>
</InvoiceTo>
<ShipTo>
<Name/>
<AddressLine>ATTN: PAULINE DEGRASSI</AddressLine>
<City/>
<PostCode/>
<State/>
<Country/>
</ShipTo>
<DetailLines>
<DetailLine>
<QuantityShipped>1</QuantityShipped>
<UnitOfMeasure>CS</UnitOfMeasure>
<PartNumber>DM 5309</PartNumber>
<PartDescription>#1013 12 OZ.MUNICH STEIN</PartDescription>
<UnitPrice>37.72</UnitPrice>
<LineTotal>37.72</LineTotal>
</DetailLine>
<DetailLine>
<QuantityShipped>6</QuantityShipped>
<UnitOfMeasure>DZ</UnitOfMeasure>
<PartNumber>ON 6420</PartNumber>
<PartDescription>PROVINCIAL DINNER FORK</PartDescription>
<UnitPrice>17.98</UnitPrice>
<LineTotal>107.88</LineTotal>
</DetailLine>
<DetailLine>
<QuantityShipped>72</QuantityShipped>
<UnitOfMeasure>EA</UnitOfMeasure>
<PartNumber>JR20643</PartNumber>
<PartDescription>PLASTIC HANDLED STEAK KNIFE</PartDescription>
<UnitPrice>.81</UnitPrice>
<LineTotal>58.32</LineTotal>
</DetailLine>
<DetailLine>
<QuantityShipped>6</QuantityShipped>
<UnitOfMeasure>DZ</UnitOfMeasure>
<PartNumber>ON 6410</PartNumber>
<PartDescription>PROVINCIAL TEASPOONS</PartDescription>
<UnitPrice>12.16</UnitPrice>
<LineTotal>72.96</LineTotal>
</DetailLine>
<DetailLine>
<QuantityShipped>0</QuantityShipped>
<UnitOfMeasure>DZ</UnitOfMeasure>
<PartNumber>ON 6411</PartNumber>
<PartDescription>PROVINCIAL RD BOWL SPOON</PartDescription>
<QuantityBackOrdered>6</QuantityBackOrdered>
<UnitPrice>17.98</UnitPrice>
<LineTotal>0.00</LineTotal>
</DetailLine>
<DetailLine>
<QuantityShipped>1</QuantityShipped>
<UnitOfMeasure>EA</UnitOfMeasure>
<PartNumber>DO 3218</PartNumber>
<PartDescription>34 OZ DUAL DIAL SCALE AM3218</PartDescription>
<UnitPrice>70.00</UnitPrice>
<DiscountPercentage>5.0</DiscountPercentage>
<LineTotal>66.50</LineTotal>
</DetailLine>
<DetailLine>
<QuantityShipped>1</QuantityShipped>
<UnitOfMeasure>CS</UnitOfMeasure>
<PartNumber>DM 195</PartNumber>
<PartDescription>20 OZ.BEER PUB GLASS</PartDescription>
<UnitPrice>55.90</UnitPrice>
<LineTotal>55.90</LineTotal>
</DetailLine>
</DetailLines>
<Totals>
<SubTotal>399.28</SubTotal>
<DiscountTotal>3.50</DiscountTotal>
<FreightTotal>23.75</FreightTotal>
<GSTTotal>29.61</GSTTotal>
<ProvTaxTotal>33.84</ProvTaxTotal>
<OtherTotal>33.84</OtherTotal>
<InvoiceTotal>486.48</InvoiceTotal>
</Totals>
</Invoice>
</body>
</biztalk_1>
<p xmlns:z="http://xml.zope.org/namespaces/tal">
<span z:define="local x str:hello brave new world">
<span z:content="text local:x">outer variable x, first appearance</span>
<span z:define="local x str:goodbye cruel world">
<span z:content="text local:x">inner variable x</span>
</span>
<span z:content="text local:x">outer variable x, second appearance</span>
</span>
</p>
<?xml version="1.0" ?>
<p xmlns:z="http://xml.zope.org/namespaces/tal">
<span z:define="local x str:hello brave new world">
<span z:content="text local:x">outer variable x, first appearance</span>
<span z:define="local x str:goodbye cruel world">
<span z:content="text local:x">inner variable x</span>
</span>
<span z:content="text local:x">outer variable x, second appearance</span>
</span>
</p>
<html>
<body xmlns:m="http://xml.zope.org/namespaces/metal" xmlns:z="http://xml.zope.org/namespaces/tal" m:define-macro="body" z:define="global count python:0">
<ul m:define-macro="whoops">
<li z:repeat="item python:range(count)">
<span z:replace="item">1</span>
<span z:replace="global:message"/>
</li>
</ul>
<span z:define="global count python:2; global message str:hello world"/>
<p m:use-macro="whoops">use-macro
<span m:fill-slot="whoops">fill-slot</span>
</p>
<span z:define="global message str:goodbye cruel world"/>
<p m:use-macro="whoops">use-macro</p>
<p m:define-slot="whoops">define-slot</p>
</body>
</html>
<?xml version="1.0" ?>
<html>
<body xmlns:m="http://xml.zope.org/namespaces/metal" xmlns:z="http://xml.zope.org/namespaces/tal" m:define-macro="body" z:define="global count python:0">
<ul m:define-macro="whoops">
<li z:repeat="item python:range(count)">
<span z:replace="item">1</span>
<span z:replace="global:message"/>
</li>
</ul>
<span z:define="global count python:2; global message str:hello world"/>
<p m:use-macro="whoops">use-macro
<span m:fill-slot="whoops">fill-slot</span>
</p>
<span z:define="global message str:goodbye cruel world"/>
<p m:use-macro="whoops">use-macro</p>
<p m:define-slot="whoops">define-slot</p>
</body>
</html>
<html>
<body xmlns:m="http://xml.zope.org/namespaces/metal" m:define-macro="body">
<h1>This is the body of test5</h1>
</body>
</html>
<?xml version="1.0" ?>
<html>
<body xmlns:m="http://xml.zope.org/namespaces/metal" m:define-macro="body">
<h1>This is the body of test5</h1>
</body>
</html>
<html>
<body xmlns:m="http://xml.zope.org/namespaces/metal"
m:use-macro="tests/input/test05.html/body">
dummy body in test6
</body>
</html>
<?xml version="1.0" ?>
<html>
<body xmlns:m="http://xml.zope.org/namespaces/metal"
m:use-macro="tests/input/test05.xml/body">
dummy body in test6
</body>
</html>
<table xmlns:m="http://xml.zope.org/namespaces/metal" m:define-macro="myTable">
<!-- macro definition with slots -->
<tr>
<td>Top Left</td>
<td>Top Right</td>
</tr>
<tr>
<td>Bottom left</td>
<td><span m:define-slot="bottomRight">Bottom Right</span></td>
</tr>
</table>
<?xml version="1.0" ?>
<table xmlns:m="http://xml.zope.org/namespaces/metal" m:define-macro="myTable">
<!-- macro definition with slots -->
<tr>
<td>Top Left</td>
<td>Top Right</td>
</tr>
<tr>
<td>Bottom left</td>
<td><span m:define-slot="bottomRight">Bottom Right</span></td>
</tr>
</table>
<table xmlns:m="http://xml.zope.org/namespaces/metal" m:use-macro="tests/input/test07.html/myTable">
<!-- macro use with slots -->
<tr>
<td>
<span m:fill-slot="bottomRight">
<h1>Some headline</h1>
<p>This is the real contents of the bottom right slot.</p>
<p>It is supposed to contain a lot of text. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb.</p>
<p>It is supposed to contain a lot of text. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb.</p>
<p>It is supposed to contain a lot of text. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb.</p>
</span>
</td>
</tr>
</table>
<?xml version="1.0" ?>
<table xmlns:m="http://xml.zope.org/namespaces/metal" m:use-macro="tests/input/test07.xml/myTable">
<!-- macro use with slots -->
<tr>
<td>
<span m:fill-slot="bottomRight">
<h1>Some headline</h1>
<p>This is the real contents of the bottom right slot.</p>
<p>It is supposed to contain a lot of text. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb.</p>
<p>It is supposed to contain a lot of text. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb.</p>
<p>It is supposed to contain a lot of text. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb.</p>
</span>
</td>
</tr>
</table>
<html>
<body>
<p>
Just a bunch of text.
<p>more text...
<ul>
<li>first item
<li>second item
<ol>
<li>second list, first item
<li>second list, second item
<dl compact>
<dt>term 1
<dt>term 2
<dd>definition
</dl>
</ol>
<li>Now let's have a paragraph...
<p>My Paragraph
</li>
<li>And a table in a list item:
<table>
</table>
</ul>
</body>
</html>
<html>
<body>
<p>
Just a bunch of text.</p>
<p>more text...</p>
<ul>
<li>first item</li>
<li>second item
<ol>
<li>second list, first item</li>
<li>second list, second item
<dl compact="">
<dt>term 1</dt>
<dt>term 2</dt>
<dd>definition</dd>
</dl></li>
</ol></li>
<li>Now let's have a paragraph...
<p>My Paragraph</p>
</li>
<li>And a table in a list item:
<table>
</table></li>
</ul>
</body>
</html>
<html><body>
<table xmlns:m="http://xml.zope.org/namespaces/metal" m:use-macro="tests/input/test07.html/myTable">
<!-- macro use with slots -->
<tr>
<td>
<span m:fill-slot="bottomRight">
<h1>Some headline</h1>
<p>This is the real contents of the bottom right slot.</p>
<hr>
<p>It is supposed to contain a lot of text. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb.</p>
<p>It is supposed to contain a lot of text. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb.</p>
<p>It is supposed to contain a lot of text. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb.</p>
<br><br>
</span>
</td>
</tr>
</table>
</body></html>
<html xmlns:tal="http://xml.zope.org/namespaces/tal">
<p tal:replace="structure string:&lt;a&gt;bar&lt;/a&gt;"
tal:attributes="href string:http://www.python.org">dummy text</p>
<p tal:define="x python:1" tal:on-error="string:bad boy!">
<span tal:define="x python:2">
<span tal:define="x python:3">
<span tal:content="python:1/0"/>
</span>
</span>
</p>
<p tal:on-error="string:x undefined">
<span tal:content="x"/>
</p>
</html>
<html xmlns:tal="http://xml.zope.org/namespaces/tal">
<p tal:replace="structure string:&lt;a&gt;bar&lt;/a&gt;"
tal:attributes="href string:http://www.python.org">dummy text</p>
<p tal:define="x python:1" tal:on-error="string:bad boy!">
<span tal:define="x python:2">
<span tal:define="x python:3">
<span tal:content="python:1/0"/>
</span>
</span>
</p>
<p tal:on-error="string:x undefined">
<span tal:content="x"/>
</p>
</html>
<span tal:define="global true python:1; global false python:0" />
<img ismap>
<img ismap=ismap>
<img ismap="ismap">
<img ismap="foo">
<img ismap tal:attributes="ismap true">
<img ismap tal:attributes="ismap false">
<img ismap tal:attributes="ismap nothing">
<img ismap tal:attributes="ismap default">
<img ismap="foo" tal:attributes="ismap true">
<img ismap="foo" tal:attributes="ismap false">
<img ismap="foo" tal:attributes="ismap nothing">
<img ismap="foo" tal:attributes="ismap default">
<img tal:attributes="ismap true">
<img tal:attributes="ismap false">
<img tal:attributes="ismap nothing">
<img tal:attributes="ismap default">
<span tal:define="global x string:x.gif" />
<img src="foo">
<img src="foo" tal:attributes="src x">
<img src="foo" tal:attributes="src nothing">
<img src="foo" tal:attributes="src default">
<img tal:attributes="src default">
Here's a stray greater than: >
<script>
<!-- no comment -->
<notag>
&noentity;
</script>
<table>
<tr>
<td tal:repeat="x python:['car', 'bike', 'broomstick']" tal:content="x">
</td>
</tr>
</table>
<p>
<span tal:repeat="x python:['Harry', 'Ron', 'Hermione']" tal:replace="x" />
</p>
<?xml version="1.0" ?>
<html xmlns:tal="http://xml.zope.org/namespaces/tal">
<table>
<tr>
<td tal:repeat="x python:['car', 'bike', 'broomstick']" tal:content="x">
</td>
</tr>
</table>
<p>
<span tal:repeat="x python:['Harry', 'Ron', 'Hermione']" tal:replace="x" />
</p>
</html>
<span metal:define-macro="INNER">
<span metal:define-slot="INNERSLOT">INNERSLOT</span>
</span>
<xxx metal:use-macro="INNER">
<xxx metal:fill-slot="INNERSLOT">inner-argument</xxx>
</xxx>
<div metal:define-macro="OUTER">
<div metal:use-macro="INNER">
<xxx metal:define-slot="OUTERSLOT" metal:fill-slot="INNERSLOT">
OUTERSLOT
</xxx>
</div>
</div>
<div metal:use-macro="OUTER">
<span>
<xxx>
<div metal:fill-slot="OUTERSLOT">outer-argument</div>
</xxx>
</span>
</div>
<div metal:use-macro="OUTER">
</div>
<a href="valid/link.html"
tal:attributes="href python:'/base/' + attrs['href']">blah, blah</a>
<input tal:attributes="value string:a &quot;laser&quot;" />
<?xml version="1.0"?>
<body xmlns:tal="http://xml.zope.org/namespaces/tal">
<ImG href="foo" Alt="bar"
tal:attributes="Href string:about:foo;alT string:baz" />
</body>
<tal:block tal:content="string:Yes">No</tal:block>
<tal:block content="string:Yes">No</tal:block>
<tal:block>Yes</tal:block>
<metal:block tal:content="string:Yes">No</metal:block>
<metal:block>Yes</metal:block>
<?xml version="1.0"?>
<body xmlns:z="http://xml.zope.org/namespaces/tal"
xmlns:z2="http://xml.zope.org/namespaces/metal">
<z:block z:content="string:Yes">No</z:block>
<z:block content="string:Yes">No</z:block>
<z:block>Yes</z:block>
<z2:block z:content="string:Yes">No</z2:block>
<z2:block>Yes</z2:block>
</body>
<p tal:omit-tag="">Content</p>
<p tal:omit-tag=""></p>
<img tal:omit-tag="">
<p tal:omit-tag="string:Yes">Content</p>
<p tal:omit-tag="string:Yes"></p>
<img tal:omit-tag="string:Yes">
<p tal:omit-tag="nothing">Content</p>
<p tal:omit-tag="nothing"></p>
<img tal:omit-tag="nothing">
<p tal:define="txt string:Yes" tal:omit-tag="" tal:content="txt">No</p>
<p tal:define="txt string:Yes" tal:omit-tag="" tal:replace="txt">No</p>
<p tal:omit-tag="" tal:content="default">Yes</p>
<p tal:omit-tag="" tal:replace="default">Yes</p>
<?xml version="1.0"?>
<body xmlns:tal="http://xml.zope.org/namespaces/tal"
xmlns:metal="http://xml.zope.org/namespaces/metal">
<p tal:omit-tag="">Content</p>
<p tal:omit-tag=""></p>
<img tal:omit-tag=""/>
<p tal:omit-tag="string:Yes">Content</p>
<p tal:omit-tag="string:Yes"></p>
<img tal:omit-tag="string:Yes"/>
<p tal:omit-tag="nothing">Content</p>
<p tal:omit-tag="nothing"></p>
<img tal:omit-tag="nothing" />
<p tal:define="txt string:Yes" tal:omit-tag="" tal:content="txt">No</p>
<p tal:define="txt string:Yes" tal:omit-tag="" tal:replace="txt">No</p>
<p tal:omit-tag="" tal:content="default">Yes</p>
<p tal:omit-tag="" tal:replace="default">Yes</p>
</body>
<input name="Delete"
tal:attributes="name string:delete_button"
i18n:attributes="name">
<input name="Delete"
i18n:attributes="name message-id">
<input i18n:attributes=" name message-id;
attr input-attr ">
<input name="Delete"
i18n:attributes="name message-id;">
<input name="Delete" title="titletext"
i18n:attributes="name; title">
<input name="Delete" tal:attributes="title string:titletaltext"
i18n:attributes="name; title">
<input name="Delete" tal:attributes="title string:titletaltext"
i18n:attributes="name message-id">
<input name="Delete" i18n:attributes="name">
<div i18n:translate="">At the tone the time will be
<span i18n:data="here/currentTime"
i18n:translate="timefmt"
i18n:name="time">2:32 pm</span>... beep!</div>
<span i18n:translate="don't translate me">
stuff
<span tal:replace="string:foobar" i18n:name="longname" />
more stuff
</span>
<span i18n:translate="">
stuff
<span tal:replace="string:foobar" i18n:name="longname" />
more stuff
</span>
<span tal:replace="string:<foo>" />
<span i18n:translate="">
<span tal:replace="string:<foo>" i18n:name="name1" />
<span tal:replace="structure string:<bar />" i18n:name="name2" />
<span i18n:name="name3"><b>some</b> <i>text</i></span>
</span>
<input value="don't translate me"
i18n:attributes="value">
<span metal:define-macro="OUTER">
AAA
<span metal:define-macro="INNER">INNER</span>
BBB
</span>
<xxx metal:use-macro="OUTER">
</xxx>
<xxx metal:use-macro="INNER">
</xxx>
<span metal:define-macro="OUTER2">
AAA
<xxx metal:define-slot="OUTERSLOT">
<span metal:define-macro="INNER2">INNER</span>
</xxx>
BBB
</span>
<xxx metal:use-macro="OUTER2">
</xxx>
<xxx metal:use-macro="INNER2">
</xxx>
<xxx metal:use-macro="OUTER2">
<yyy metal:fill-slot="OUTERSLOT">OUTERSLOT</yyy>
</xxx>
<span metal:define-macro="OUTER3">
AAA
<xxx metal:define-slot="OUTERSLOT">
<span metal:define-macro="INNER3">INNER
<xxx metal:define-slot="INNERSLOT">INNERSLOT</xxx>
</span>
</xxx>
BBB
</span>
<xxx metal:use-macro="OUTER3">
</xxx>
<xxx metal:use-macro="OUTER3">
<yyy metal:fill-slot="OUTERSLOT">OUTERSLOT</yyy>
</xxx>
<xxx metal:use-macro="INNER3">
</xxx>
<xxx metal:use-macro="INNER3">
<yyy metal:fill-slot="INNERSLOT">INNERSLOT</yyy>
</xxx>
<xxx metal:use-macro="INNER3">
<yyy metal:fill-slot="INNERSLOT">
<zzz metal:define-macro="INSLOT">INSLOT</zzz>
</yyy>
</xxx>
<xxx metal:use-macro="INSLOT"></xxx>
<div metal:define-macro="OUTER">
OUTER
<span metal:define-macro="INNER">INNER</span>
OUTER
</div>
<div metal:use-macro="OUTER"/>
<span tal:attributes="class string:foo">Should not get attr in metal</span>
<!-- the outer element *must* be tal:something or metal:something -->
<metal:block define-macro="page" i18n:domain="zope">
<title metal:define-slot="title">Z3 UI</title>
</metal:block>
<!-- the outer element *must* include tal:omit-tag='' -->
<x tal:omit-tag="" metal:define-macro="page" i18n:domain="zope">
<title metal:define-slot="title">Z3 UI</title>
</x>
<metal:block define-macro="page">
<html i18:domain="zope">
<metal:block define-slot="title">Z3 UI</metal:block>
</html>
</metal:block>
<html metal:define-macro="page" i18n:domain="zope">
<x metal:define-slot="title" />
</html>
<html metal:use-macro="page" />
\ No newline at end of file
<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"DTD/xhtml1-transitional.dtd">
<html>
<head>dadada</head>
<body>
<h1>This
Is
The
Replaced
Title</h1>
<!-- test entity references -->
&nbsp;&HarryPotter;
<!-- examples adapted from TemplateAttributeLanguageSyntax -->
<span>here/id</span>
<p>5</p>
<p>
honda
</p>
<p>
subaru
</p>
<p>
acura
</p>
<p xml:foo="bar">foo bar</p>
<!-- more examples -->
<ul>
<span>
<li>honda</li>
</span>
<span>
<li>subaru</li>
</span>
<span>
<li>acura</li>
</span>
</ul>
<!-- test attribute expansion -->
<a href="http://python.org">python</a>
<a href="http://python.org">python</a>
<!-- test insert/replace structure -->
<span></span>
<span />
<span />
&nbsp;<h3>Header Level 3</h3>
<span>&nbsp;<h3>Header Level 3</h3></span>
</body>
</html>
<?xml version="1.0" ?>
<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"DTD/xhtml1-transitional.dtd">
<html>
<head>dadada</head>
<body>
<h1>This Is The Replaced Title</h1>
<!-- test entity references -->
&nbsp;&HarryPotter;
<!-- examples adapted from TemplateAttributeLanguageSyntax -->
<span>here/id</span>
<p>5</p>
<p>
honda
</p>
<p>
subaru
</p>
<p>
acura
</p>
<p xml:foo="bar">foo bar</p>
<!-- more examples -->
<ul>
<span>
<li>honda</li>
</span>
<span>
<li>subaru</li>
</span>
<span>
<li>acura</li>
</span>
</ul>
<!-- test attribute expansion -->
<a href="http://python.org">python</a>
<a href="http://python.org">python</a>
<!-- test insert/replace structure -->
<span></span>
<span/>
<span/>
&nbsp;<h3>Header Level 3</h3>
<span>&nbsp;<h3>Header Level 3</h3></span>
</body>
</html>
<biztalk_1 xmlns="urn:schemas-biztalk-org:biztalk:biztalk_1">
<foo:header xmlns:foo="whomping-willow" plain="guido" quote="&quot;" apostrophe="'" both="&quot;'" lt="&lt;" gt="&gt;" amp="&amp;" foo="">
<manifest>
<document>
<name>sample1</name>
<description>a simple invoice</description>
</document>
</manifest>
</foo:header>
<body>
<!-- sample1.xml is an example of a simple invoice for a small restaurant supplies order -->
<invoice xmlns="urn:http://schemas.biztalk.org/united_rest_com/yw7sg15x.xml">
<header>
<invoicenumber>01786</invoicenumber>
<invoicedate>2000-03-17</invoicedate> <!-- March 17th, 2000 -->
<orderno>55377</orderno>
<orderdate>2000-03-15</orderdate> <!-- March 15th, 2000 -->
<customerpo>GJ03405</customerpo>
<shipmethod>DAVE 1</shipmethod>
<shipdate>2000-03-17</shipdate> <!-- March 17th, 2000 -->
<customerid>K5211(34)</customerid>
<salespersoncode>23</salespersoncode>
<taxid>23</taxid>
</header>
<invoiceto>
<name>SHIPWRIGHT RESTAURANTS LIMITED</name>
<addressline>125 NORTH SERVICE ROAD W</addressline>
<addressline>WESTLAKE ACCESS</addressline>
<city>NORTH BAY</city>
<postcode>L8B1O5</postcode>
<state>ONTARIO</state>
<country>CANADA</country>
</invoiceto>
<shipto>
<name />
<addressline>ATTN: PAULINE DEGRASSI</addressline>
<city />
<postcode />
<state />
<country />
</shipto>
<detaillines>
<detailline>
<quantityshipped>1</quantityshipped>
<unitofmeasure>CS</unitofmeasure>
<partnumber>DM 5309</partnumber>
<partdescription>#1013 12 OZ.MUNICH STEIN</partdescription>
<unitprice>37.72</unitprice>
<linetotal>37.72</linetotal>
</detailline>
<detailline>
<quantityshipped>6</quantityshipped>
<unitofmeasure>DZ</unitofmeasure>
<partnumber>ON 6420</partnumber>
<partdescription>PROVINCIAL DINNER FORK</partdescription>
<unitprice>17.98</unitprice>
<linetotal>107.88</linetotal>
</detailline>
<detailline>
<quantityshipped>72</quantityshipped>
<unitofmeasure>EA</unitofmeasure>
<partnumber>JR20643</partnumber>
<partdescription>PLASTIC HANDLED STEAK KNIFE</partdescription>
<unitprice>.81</unitprice>
<linetotal>58.32</linetotal>
</detailline>
<detailline>
<quantityshipped>6</quantityshipped>
<unitofmeasure>DZ</unitofmeasure>
<partnumber>ON 6410</partnumber>
<partdescription>PROVINCIAL TEASPOONS</partdescription>
<unitprice>12.16</unitprice>
<linetotal>72.96</linetotal>
</detailline>
<detailline>
<quantityshipped>0</quantityshipped>
<unitofmeasure>DZ</unitofmeasure>
<partnumber>ON 6411</partnumber>
<partdescription>PROVINCIAL RD BOWL SPOON</partdescription>
<quantitybackordered>6</quantitybackordered>
<unitprice>17.98</unitprice>
<linetotal>0.00</linetotal>
</detailline>
<detailline>
<quantityshipped>1</quantityshipped>
<unitofmeasure>EA</unitofmeasure>
<partnumber>DO 3218</partnumber>
<partdescription>34 OZ DUAL DIAL SCALE AM3218</partdescription>
<unitprice>70.00</unitprice>
<discountpercentage>5.0</discountpercentage>
<linetotal>66.50</linetotal>
</detailline>
<detailline>
<quantityshipped>1</quantityshipped>
<unitofmeasure>CS</unitofmeasure>
<partnumber>DM 195</partnumber>
<partdescription>20 OZ.BEER PUB GLASS</partdescription>
<unitprice>55.90</unitprice>
<linetotal>55.90</linetotal>
</detailline>
</detaillines>
<totals>
<subtotal>399.28</subtotal>
<discounttotal>3.50</discounttotal>
<freighttotal>23.75</freighttotal>
<gsttotal>29.61</gsttotal>
<provtaxtotal>33.84</provtaxtotal>
<othertotal>33.84</othertotal>
<invoicetotal>486.48</invoicetotal>
</totals>
</invoice>
</body>
</biztalk_1>
<?xml version="1.0" ?>
<biztalk_1 xmlns="urn:schemas-biztalk-org:biztalk:biztalk_1">
<foo:header xmlns:foo="whomping-willow" plain="guido" quote="&quot;" apostrophe="'" both="&quot;'" lt="&lt;" gt="&gt;" amp="&amp;" foo="">
<manifest>
<document>
<name>sample1</name>
<description>a simple invoice</description>
</document>
</manifest>
</foo:header>
<body>
<!-- sample1.xml is an example of a simple invoice for a small restaurant supplies order -->
<Invoice xmlns="urn:http://schemas.biztalk.org/united_rest_com/yw7sg15x.xml">
<Header>
<InvoiceNumber>01786</InvoiceNumber>
<InvoiceDate>2000-03-17</InvoiceDate> <!-- March 17th, 2000 -->
<OrderNo>55377</OrderNo>
<OrderDate>2000-03-15</OrderDate> <!-- March 15th, 2000 -->
<CustomerPO>GJ03405</CustomerPO>
<ShipMethod>DAVE 1</ShipMethod>
<ShipDate>2000-03-17</ShipDate> <!-- March 17th, 2000 -->
<CustomerID>K5211(34)</CustomerID>
<SalesPersonCode>23</SalesPersonCode>
<TaxID>23</TaxID>
</Header>
<InvoiceTo>
<Name>SHIPWRIGHT RESTAURANTS LIMITED</Name>
<AddressLine>125 NORTH SERVICE ROAD W</AddressLine>
<AddressLine>WESTLAKE ACCESS</AddressLine>
<City>NORTH BAY</City>
<PostCode>L8B1O5</PostCode>
<State>ONTARIO</State>
<Country>CANADA</Country>
</InvoiceTo>
<ShipTo>
<Name/>
<AddressLine>ATTN: PAULINE DEGRASSI</AddressLine>
<City/>
<PostCode/>
<State/>
<Country/>
</ShipTo>
<DetailLines>
<DetailLine>
<QuantityShipped>1</QuantityShipped>
<UnitOfMeasure>CS</UnitOfMeasure>
<PartNumber>DM 5309</PartNumber>
<PartDescription>#1013 12 OZ.MUNICH STEIN</PartDescription>
<UnitPrice>37.72</UnitPrice>
<LineTotal>37.72</LineTotal>
</DetailLine>
<DetailLine>
<QuantityShipped>6</QuantityShipped>
<UnitOfMeasure>DZ</UnitOfMeasure>
<PartNumber>ON 6420</PartNumber>
<PartDescription>PROVINCIAL DINNER FORK</PartDescription>
<UnitPrice>17.98</UnitPrice>
<LineTotal>107.88</LineTotal>
</DetailLine>
<DetailLine>
<QuantityShipped>72</QuantityShipped>
<UnitOfMeasure>EA</UnitOfMeasure>
<PartNumber>JR20643</PartNumber>
<PartDescription>PLASTIC HANDLED STEAK KNIFE</PartDescription>
<UnitPrice>.81</UnitPrice>
<LineTotal>58.32</LineTotal>
</DetailLine>
<DetailLine>
<QuantityShipped>6</QuantityShipped>
<UnitOfMeasure>DZ</UnitOfMeasure>
<PartNumber>ON 6410</PartNumber>
<PartDescription>PROVINCIAL TEASPOONS</PartDescription>
<UnitPrice>12.16</UnitPrice>
<LineTotal>72.96</LineTotal>
</DetailLine>
<DetailLine>
<QuantityShipped>0</QuantityShipped>
<UnitOfMeasure>DZ</UnitOfMeasure>
<PartNumber>ON 6411</PartNumber>
<PartDescription>PROVINCIAL RD BOWL SPOON</PartDescription>
<QuantityBackOrdered>6</QuantityBackOrdered>
<UnitPrice>17.98</UnitPrice>
<LineTotal>0.00</LineTotal>
</DetailLine>
<DetailLine>
<QuantityShipped>1</QuantityShipped>
<UnitOfMeasure>EA</UnitOfMeasure>
<PartNumber>DO 3218</PartNumber>
<PartDescription>34 OZ DUAL DIAL SCALE AM3218</PartDescription>
<UnitPrice>70.00</UnitPrice>
<DiscountPercentage>5.0</DiscountPercentage>
<LineTotal>66.50</LineTotal>
</DetailLine>
<DetailLine>
<QuantityShipped>1</QuantityShipped>
<UnitOfMeasure>CS</UnitOfMeasure>
<PartNumber>DM 195</PartNumber>
<PartDescription>20 OZ.BEER PUB GLASS</PartDescription>
<UnitPrice>55.90</UnitPrice>
<LineTotal>55.90</LineTotal>
</DetailLine>
</DetailLines>
<Totals>
<SubTotal>399.28</SubTotal>
<DiscountTotal>3.50</DiscountTotal>
<FreightTotal>23.75</FreightTotal>
<GSTTotal>29.61</GSTTotal>
<ProvTaxTotal>33.84</ProvTaxTotal>
<OtherTotal>33.84</OtherTotal>
<InvoiceTotal>486.48</InvoiceTotal>
</Totals>
</Invoice>
</body>
</biztalk_1>
<p>
<span>
<span>hello brave new world</span>
<span>
<span>goodbye cruel world</span>
</span>
<span>hello brave new world</span>
</span>
</p>
<?xml version="1.0" ?>
<p>
<span>
<span>hello brave new world</span>
<span>
<span>goodbye cruel world</span>
</span>
<span>hello brave new world</span>
</span>
</p>
<html>
<body>
<ul>
</ul>
<span />
<ul>
<li>
0
hello world
</li>
<li>
1
hello world
</li>
</ul>
<span />
<ul>
<li>
0
goodbye cruel world
</li>
<li>
1
goodbye cruel world
</li>
</ul>
<p>define-slot</p>
</body>
</html>
<?xml version="1.0" ?>
<html>
<body>
<ul>
</ul>
<span/>
<ul>
<li>
0
hello world
</li>
<li>
1
hello world
</li>
</ul>
<span/>
<ul>
<li>
0
goodbye cruel world
</li>
<li>
1
goodbye cruel world
</li>
</ul>
<p>define-slot</p>
</body>
</html>
<html>
<body>
<h1>This is the body of test5</h1>
</body>
</html>
<?xml version="1.0" ?>
<html>
<body>
<h1>This is the body of test5</h1>
</body>
</html>
<html>
<body>
<h1>This is the body of test5</h1>
</body>
</html>
<?xml version="1.0" ?>
<html>
<body>
<h1>This is the body of test5</h1>
</body>
</html>
<table>
<!-- macro definition with slots -->
<tr>
<td>Top Left</td>
<td>Top Right</td>
</tr>
<tr>
<td>Bottom left</td>
<td><span>Bottom Right</span></td>
</tr>
</table>
<?xml version="1.0" ?>
<table>
<!-- macro definition with slots -->
<tr>
<td>Top Left</td>
<td>Top Right</td>
</tr>
<tr>
<td>Bottom left</td>
<td><span>Bottom Right</span></td>
</tr>
</table>
<table>
<!-- macro definition with slots -->
<tr>
<td>Top Left</td>
<td>Top Right</td>
</tr>
<tr>
<td>Bottom left</td>
<td><span>
<h1>Some headline</h1>
<p>This is the real contents of the bottom right slot.</p>
<p>It is supposed to contain a lot of text. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb.</p>
<p>It is supposed to contain a lot of text. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb.</p>
<p>It is supposed to contain a lot of text. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb.</p>
</span></td>
</tr>
</table>
<?xml version="1.0" ?>
<table>
<!-- macro definition with slots -->
<tr>
<td>Top Left</td>
<td>Top Right</td>
</tr>
<tr>
<td>Bottom left</td>
<td><span>
<h1>Some headline</h1>
<p>This is the real contents of the bottom right slot.</p>
<p>It is supposed to contain a lot of text. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb.</p>
<p>It is supposed to contain a lot of text. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb.</p>
<p>It is supposed to contain a lot of text. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb.</p>
</span></td>
</tr>
</table>
<html>
<body>
<p>
Just a bunch of text.</p>
<p>more text...</p>
<ul>
<li>first item</li>
<li>second item
<ol>
<li>second list, first item</li>
<li>second list, second item
<dl compact>
<dt>term 1</dt>
<dt>term 2</dt>
<dd>definition</dd>
</dl></li>
</ol></li>
<li>Now let's have a paragraph...
<p>My Paragraph</p>
</li>
<li>And a table in a list item:
<table>
</table></li>
</ul>
</body>
</html>
<html>
<body>
<p>
Just a bunch of text.</p>
<p>more text...</p>
<ul>
<li>first item</li>
<li>second item
<ol>
<li>second list, first item</li>
<li>second list, second item
<dl compact="">
<dt>term 1</dt>
<dt>term 2</dt>
<dd>definition</dd>
</dl></li>
</ol></li>
<li>Now let's have a paragraph...
<p>My Paragraph</p>
</li>
<li>And a table in a list item:
<table>
</table></li>
</ul>
</body>
</html>
<html><body>
<table>
<!-- macro definition with slots -->
<tr>
<td>Top Left</td>
<td>Top Right</td>
</tr>
<tr>
<td>Bottom left</td>
<td><span>
<h1>Some headline</h1>
<p>This is the real contents of the bottom right slot.</p>
<hr>
<p>It is supposed to contain a lot of text. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb.</p>
<p>It is supposed to contain a lot of text. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb.</p>
<p>It is supposed to contain a lot of text. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
Blabber, blabber, blah. Baah, baah, barb.</p>
<br><br>
</span></td>
</tr>
</table>
</body></html>
<html>
<a href="http://www.python.org">bar</a>
<p>bad boy!</p>
<p>x undefined</p>
</html>
<html>
<a href="http://www.python.org">bar</a>
<p>bad boy!</p>
<p>x undefined</p>
</html>
<span />
<img ismap>
<img ismap="ismap">
<img ismap="ismap">
<img ismap="foo">
<img ismap="ismap">
<img>
<img>
<img ismap="ismap">
<img ismap="ismap">
<img>
<img>
<img ismap="foo">
<img ismap="ismap">
<img>
<img>
<img>
<span />
<img src="foo">
<img src="x.gif">
<img>
<img src="foo">
<img>
Here's a stray greater than: >
<script>
<!-- no comment -->
<notag>
&noentity;
</script>
<table>
<tr>
<td>car</td>
<td>bike</td>
<td>broomstick</td>
</tr>
</table>
<p>
Harry
Ron
Hermione
</p>
<?xml version="1.0" ?>
<html>
<table>
<tr>
<td>car</td>
<td>bike</td>
<td>broomstick</td>
</tr>
</table>
<p>
Harry
Ron
Hermione
</p>
</html>
<span>
<span>INNERSLOT</span>
</span>
<span>
<xxx>inner-argument</xxx>
</span>
<div>
<span>
<xxx>
OUTERSLOT
</xxx>
</span>
</div>
<div>
<span>
<div>outer-argument</div>
</span>
</div>
<div>
<span>
<xxx>
OUTERSLOT
</xxx>
</span>
</div>
<a href="/base/valid/link.html">blah, blah</a>
<input value="a &quot;laser&quot;" />
<?xml version="1.0"?>
<body>
<ImG href="foo" Alt="bar" alT="baz" Href="about:foo"/>
</body>
<?xml version="1.0"?>
<body>
Yes
Yes
Yes
Yes
Yes
</body>
Content
Content
<p>Content</p>
<p></p>
<img>
Yes
Yes
Yes
Yes
<?xml version="1.0"?>
<body>
Content
Content
<p>Content</p>
<p/>
<img/>
Yes
Yes
Yes
Yes
</body>
<input name="DELETE_BUTTON">
<input name="MESSAGE-ID">
<input name="MESSAGE-ID" attr="INPUT-ATTR">
<input name="MESSAGE-ID">
<input name="DELETE" title="TITLETEXT">
<input name="DELETE" title="TITLETALTEXT">
<input name="MESSAGE-ID" title="titletaltext">
<div>AT THE TONE THE TIME WILL BE 59 MINUTES AFTER 6 PM... BEEP!</div>
<span>
stuff
foobar
more stuff
</span>
<span>STUFF foobar MORE STUFF</span>
&lt;foo&gt;
<span>&lt;foo&gt; <bar /> <b>some</b> <i>text</i></span>
<span metal:define-macro="OUTER">
AAA
<span metal:define-macro="INNER">INNER</span>
BBB
</span>
<span metal:use-macro="OUTER">
AAA
<span>INNER</span>
BBB
</span>
<span metal:use-macro="INNER">INNER</span>
<span metal:define-macro="OUTER2">
AAA
<xxx metal:define-slot="OUTERSLOT">
<span metal:define-macro="INNER2">INNER</span>
</xxx>
BBB
</span>
<span metal:use-macro="OUTER2">
AAA
<xxx metal:fill-slot="OUTERSLOT">
<span>INNER</span>
</xxx>
BBB
</span>
<span metal:use-macro="INNER2">INNER</span>
<span metal:use-macro="OUTER2">
AAA
<yyy metal:fill-slot="OUTERSLOT">OUTERSLOT</yyy>
BBB
</span>
<span metal:define-macro="OUTER3">
AAA
<xxx metal:define-slot="OUTERSLOT">
<span metal:define-macro="INNER3">INNER
<xxx metal:define-slot="INNERSLOT">INNERSLOT</xxx>
</span>
</xxx>
BBB
</span>
<span metal:use-macro="OUTER3">
AAA
<xxx metal:fill-slot="OUTERSLOT">
<span>INNER
<xxx>INNERSLOT</xxx>
</span>
</xxx>
BBB
</span>
<span metal:use-macro="OUTER3">
AAA
<yyy metal:fill-slot="OUTERSLOT">OUTERSLOT</yyy>
BBB
</span>
<span metal:use-macro="INNER3">INNER
<xxx metal:fill-slot="INNERSLOT">INNERSLOT</xxx>
</span>
<span metal:use-macro="INNER3">INNER
<yyy metal:fill-slot="INNERSLOT">INNERSLOT</yyy>
</span>
<span metal:use-macro="INNER3">INNER
<yyy metal:fill-slot="INNERSLOT">
<zzz metal:define-macro="INSLOT">INSLOT</zzz>
</yyy>
</span>
<zzz metal:use-macro="INSLOT">INSLOT</zzz>
<div metal:define-macro="OUTER">
OUTER
<span metal:define-macro="INNER">INNER</span>
OUTER
</div>
<div metal:use-macro="OUTER">
OUTER
<span>INNER</span>
OUTER
</div>
<span tal:attributes="class string:foo">Should not get attr in metal</span>
<!-- the outer element *must* be tal:something or metal:something -->
<metal:block define-macro="page" i18n:domain="zope">
<title metal:define-slot="title">Z3 UI</title>
</metal:block>
<!-- the outer element *must* include tal:omit-tag='' -->
<x tal:omit-tag="" metal:define-macro="page" i18n:domain="zope">
<title metal:define-slot="title">Z3 UI</title>
</x>
<metal:block define-macro="page">
<html i18:domain="zope">
<metal:block define-slot="title">Z3 UI</metal:block>
</html>
</metal:block>
<html metal:define-macro="page" i18n:domain="zope">
<x metal:define-slot="title" />
</html>
<html metal:use-macro="page" i18n:domain="zope">
<x metal:fill-slot="title" />
</html>
#! /usr/bin/env python1.5
"""Run all tests."""
import sys
import utils
import unittest
import test_htmlparser
import test_htmltalparser
import test_talinterpreter
import test_files
import test_sourcepos
def test_suite():
suite = unittest.TestSuite()
suite.addTest(test_htmlparser.test_suite())
suite.addTest(test_htmltalparser.test_suite())
if not utils.skipxml:
import test_xmlparser
suite.addTest(test_xmlparser.test_suite())
suite.addTest(test_talinterpreter.test_suite())
suite.addTest(test_files.test_suite())
suite.addTest(test_sourcepos.test_suite())
return suite
def main():
return utils.run_suite(test_suite())
if __name__ == "__main__":
errs = main()
sys.exit(errs and 1 or 0)
#! /usr/bin/env python1.5
"""Tests that run driver.py over input files comparing to output files."""
import os
import sys
import glob
from TAL.tests import utils
import unittest
from TAL import runtest
class FileTestCase(unittest.TestCase):
def __init__(self, file, dir):
self.__file = file
self.__dir = dir
unittest.TestCase.__init__(self)
def shortDescription(self):
return os.path.join("...", "TAL", "tests", "input",
os.path.basename(self.__file))
def runTest(self):
basename = os.path.basename(self.__file)
#sys.stdout.write(basename + " ")
sys.stdout.flush()
if basename[:10] == 'test_metal':
sys.argv = ["", "-Q", "-m", self.__file]
else:
sys.argv = ["", "-Q", self.__file]
pwd = os.getcwd()
try:
try:
os.chdir(self.__dir)
runtest.main()
finally:
os.chdir(pwd)
except SystemExit, what:
if what.code:
self.fail("output for %s didn't match" % self.__file)
try:
script = __file__
except NameError:
script = sys.argv[0]
def test_suite():
suite = unittest.TestSuite()
dir = os.path.dirname(script)
dir = os.path.abspath(dir)
parentdir = os.path.dirname(dir)
prefix = os.path.join(dir, "input", "test*.")
if utils.skipxml:
xmlargs = []
else:
xmlargs = glob.glob(prefix + "xml")
xmlargs.sort()
htmlargs = glob.glob(prefix + "html")
htmlargs.sort()
args = xmlargs + htmlargs
if not args:
sys.stderr.write("Warning: no test input files found!!!\n")
for arg in args:
case = FileTestCase(arg, parentdir)
suite.addTest(case)
return suite
if __name__ == "__main__":
errs = utils.run_suite(test_suite())
sys.exit(errs and 1 or 0)
#! /usr/bin/env python
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Tests for the HTMLTALParser code generator."""
import pprint
import sys
from TAL.tests import utils
import unittest
from TAL import HTMLTALParser
from TAL.TALDefs import TAL_VERSION, TALError, METALError
class TestCaseBase(unittest.TestCase):
prologue = ""
epilogue = ""
initial_program = [('version', TAL_VERSION), ('mode', 'html')]
final_program = []
def _merge(self, p1, p2):
if p1 and p2:
op1, args1 = p1[-1]
op2, args2 = p2[0]
if op1.startswith('rawtext') and op2.startswith('rawtext'):
return (p1[:-1]
+ [rawtext(args1[0] + args2[0])]
+ p2[1:])
return p1+p2
def _run_check(self, source, program, macros={}):
parser = HTMLTALParser.HTMLTALParser()
parser.parseString(self.prologue + source + self.epilogue)
got_program, got_macros = parser.getCode()
program = self._merge(self.initial_program, program)
program = self._merge(program, self.final_program)
self.assert_(got_program == program,
"Program:\n" + pprint.pformat(got_program)
+ "\nExpected:\n" + pprint.pformat(program))
self.assert_(got_macros == macros,
"Macros:\n" + pprint.pformat(got_macros)
+ "\nExpected:\n" + pprint.pformat(macros))
def _get_check(self, source, program=[], macros={}):
parser = HTMLTALParser.HTMLTALParser()
parser.parseString(source)
got_program, got_macros = parser.getCode()
pprint.pprint(got_program)
pprint.pprint(got_macros)
def _should_error(self, source, exc=TALError):
def parse(self=self, source=source):
parser = HTMLTALParser.HTMLTALParser()
parser.parseString(self.prologue + source + self.epilogue)
self.assertRaises(exc, parse)
def rawtext(s):
"""Compile raw text to the appropriate instruction."""
if "\n" in s:
return ("rawtextColumn", (s, len(s) - (s.rfind("\n") + 1)))
else:
return ("rawtextOffset", (s, len(s)))
class HTMLTALParserTestCases(TestCaseBase):
def check_code_simple_identity(self):
self._run_check("""<html a='b' b="c" c=d><title>My Title</html>""", [
rawtext('<html a="b" b="c" c="d">'
'<title>My Title</title></html>'),
])
def check_code_implied_list_closings(self):
self._run_check("""<ul><li><p><p><li></ul>""", [
rawtext('<ul><li><p></p><p></p></li><li></li></ul>'),
])
self._run_check("""<dl><dt><dt><dd><dd><ol><li><li></ol></dl>""", [
rawtext('<dl><dt></dt><dt></dt><dd></dd>'
'<dd><ol><li></li><li></li></ol></dd></dl>'),
])
def check_code_implied_table_closings(self):
self._run_check("""<p>text <table><tr><th>head\t<tr><td>cell\t"""
"""<table><tr><td>cell \n \t \n<tr>""", [
rawtext('<p>text</p> <table><tr><th>head</th>'
'</tr>\t<tr><td>cell\t<table><tr><td>cell</td>'
'</tr> \n \t \n<tr></tr></table></td></tr></table>'),
])
self._run_check("""<table><tr><td>cell """
"""<table><tr><td>cell </table></table>""", [
rawtext('<table><tr><td>cell <table><tr><td>cell</td></tr>'
' </table></td></tr></table>'),
])
def check_code_bad_nesting(self):
def check(self=self):
self._run_check("<a><b></a></b>", [])
self.assertRaises(HTMLTALParser.NestingError, check)
def check_code_attr_syntax(self):
output = [
rawtext('<a b="v" c="v" d="v" e></a>'),
]
self._run_check("""<a b='v' c="v" d=v e>""", output)
self._run_check("""<a b = 'v' c = "v" d = v e>""", output)
self._run_check("""<a\nb\n=\n'v'\nc\n=\n"v"\nd\n=\nv\ne>""", output)
self._run_check("""<a\tb\t=\t'v'\tc\t=\t"v"\td\t=\tv\te>""", output)
def check_code_attr_values(self):
self._run_check(
"""<a b='xxx\n\txxx' c="yyy\t\nyyy" d='\txyz\n'>""", [
rawtext('<a b="xxx\n\txxx" c="yyy\t\nyyy" d="\txyz\n"></a>')])
self._run_check("""<a b='' c="">""", [
rawtext('<a b="" c=""></a>'),
])
def check_code_attr_entity_replacement(self):
# we expect entities *not* to be replaced by HTLMParser!
self._run_check("""<a b='&amp;&gt;&lt;&quot;&apos;'>""", [
rawtext('<a b="&amp;&gt;&lt;&quot;\'"></a>'),
])
self._run_check("""<a b='\"'>""", [
rawtext('<a b="&quot;"></a>'),
])
self._run_check("""<a b='&'>""", [
rawtext('<a b="&amp;"></a>'),
])
self._run_check("""<a b='<'>""", [
rawtext('<a b="&lt;"></a>'),
])
def check_code_attr_funky_names(self):
self._run_check("""<a a.b='v' c:d=v e-f=v>""", [
rawtext('<a a.b="v" c:d="v" e-f="v"></a>'),
])
def check_code_pcdata_entityref(self):
self._run_check("""&nbsp;""", [
rawtext('&nbsp;'),
])
def check_code_short_endtags(self):
self._run_check("""<html><img/></html>""", [
rawtext('<html><img /></html>'),
])
class METALGeneratorTestCases(TestCaseBase):
def check_null(self):
self._run_check("", [])
def check_define_macro(self):
macro = self.initial_program + [
('startTag', ('p', [('metal:define-macro', 'M', 'metal')])),
rawtext('booh</p>'),
]
program = [
('setPosition', (1, 0)),
('defineMacro', ('M', macro)),
]
macros = {'M': macro}
self._run_check('<p metal:define-macro="M">booh</p>', program, macros)
def check_use_macro(self):
self._run_check('<p metal:use-macro="M">booh</p>', [
('setPosition', (1, 0)),
('useMacro',
('M', '$M$', {},
[('startTag', ('p', [('metal:use-macro', 'M', 'metal')])),
rawtext('booh</p>')])),
])
def check_define_slot(self):
macro = self.initial_program + [
('startTag', ('p', [('metal:define-macro', 'M', 'metal')])),
rawtext('foo'),
('setPosition', (1, 29)),
('defineSlot', ('S',
[('startTag', ('span', [('metal:define-slot', 'S', 'metal')])),
rawtext('spam</span>')])),
rawtext('bar</p>'),
]
program = [('setPosition', (1, 0)),
('defineMacro', ('M', macro))]
macros = {'M': macro}
self._run_check('<p metal:define-macro="M">foo'
'<span metal:define-slot="S">spam</span>bar</p>',
program, macros)
def check_fill_slot(self):
self._run_check('<p metal:use-macro="M">foo'
'<span metal:fill-slot="S">spam</span>bar</p>', [
('setPosition', (1, 0)),
('useMacro',
('M', '$M$',
{'S': [('startTag', ('span',
[('metal:fill-slot', 'S', 'metal')])),
rawtext('spam</span>')]},
[('startTag', ('p', [('metal:use-macro', 'M', 'metal')])),
rawtext('foo'),
('setPosition', (1, 26)),
('fillSlot', ('S',
[('startTag', ('span', [('metal:fill-slot', 'S', 'metal')])),
rawtext('spam</span>')])),
rawtext('bar</p>')])),
])
class TALGeneratorTestCases(TestCaseBase):
def check_null(self):
self._run_check("", [])
def check_define_1(self):
self._run_check("<p tal:define='xyzzy string:spam'></p>", [
('setPosition', (1, 0)),
('beginScope', {'tal:define': 'xyzzy string:spam'}),
('setLocal', ('xyzzy', '$string:spam$')),
('startTag', ('p', [('tal:define', 'xyzzy string:spam', 'tal')])),
('endScope', ()),
rawtext('</p>'),
])
def check_define_2(self):
self._run_check("<p tal:define='local xyzzy string:spam'></p>", [
('setPosition', (1, 0)),
('beginScope', {'tal:define': 'local xyzzy string:spam'}),
('setLocal', ('xyzzy', '$string:spam$')),
('startTag', ('p',
[('tal:define', 'local xyzzy string:spam', 'tal')])),
('endScope', ()),
rawtext('</p>'),
])
def check_define_3(self):
self._run_check("<p tal:define='global xyzzy string:spam'></p>", [
('setPosition', (1, 0)),
('beginScope', {'tal:define': 'global xyzzy string:spam'}),
('setGlobal', ('xyzzy', '$string:spam$')),
('startTag', ('p',
[('tal:define', 'global xyzzy string:spam', 'tal')])),
('endScope', ()),
rawtext('</p>'),
])
def check_define_4(self):
self._run_check("<p tal:define='x string:spam; y x'></p>", [
('setPosition', (1, 0)),
('beginScope', {'tal:define': 'x string:spam; y x'}),
('setLocal', ('x', '$string:spam$')),
('setLocal', ('y', '$x$')),
('startTag', ('p', [('tal:define', 'x string:spam; y x', 'tal')])),
('endScope', ()),
rawtext('</p>'),
])
def check_define_5(self):
self._run_check("<p tal:define='x string:;;;;; y x'></p>", [
('setPosition', (1, 0)),
('beginScope', {'tal:define': 'x string:;;;;; y x'}),
('setLocal', ('x', '$string:;;$')),
('setLocal', ('y', '$x$')),
('startTag', ('p', [('tal:define', 'x string:;;;;; y x', 'tal')])),
('endScope', ()),
rawtext('</p>'),
])
def check_define_6(self):
self._run_check(
"<p tal:define='x string:spam; global y x; local z y'></p>", [
('setPosition', (1, 0)),
('beginScope',
{'tal:define': 'x string:spam; global y x; local z y'}),
('setLocal', ('x', '$string:spam$')),
('setGlobal', ('y', '$x$')),
('setLocal', ('z', '$y$')),
('startTag', ('p',
[('tal:define', 'x string:spam; global y x; local z y', 'tal')])),
('endScope', ()),
rawtext('</p>'),
])
def check_condition(self):
self._run_check(
"<p><span tal:condition='python:1'><b>foo</b></span></p>", [
rawtext('<p>'),
('setPosition', (1, 3)),
('beginScope', {'tal:condition': 'python:1'}),
('condition', ('$python:1$',
[('startTag', ('span', [('tal:condition', 'python:1', 'tal')])),
rawtext('<b>foo</b></span>')])),
('endScope', ()),
rawtext('</p>'),
])
def check_content_1(self):
self._run_check("<p tal:content='string:foo'>bar</p>", [
('setPosition', (1, 0)),
('beginScope', {'tal:content': 'string:foo'}),
('startTag', ('p', [('tal:content', 'string:foo', 'tal')])),
('insertText', ('$string:foo$', [rawtext('bar')])),
('endScope', ()),
rawtext('</p>'),
])
def check_content_2(self):
self._run_check("<p tal:content='text string:foo'>bar</p>", [
('setPosition', (1, 0)),
('beginScope', {'tal:content': 'text string:foo'}),
('startTag', ('p', [('tal:content', 'text string:foo', 'tal')])),
('insertText', ('$string:foo$', [rawtext('bar')])),
('endScope', ()),
rawtext('</p>'),
])
def check_content_3(self):
self._run_check("<p tal:content='structure string:<br>'>bar</p>", [
('setPosition', (1, 0)),
('beginScope', {'tal:content': 'structure string:<br>'}),
('startTag', ('p',
[('tal:content', 'structure string:<br>', 'tal')])),
('insertStructure',
('$string:<br>$', {}, [rawtext('bar')])),
('endScope', ()),
rawtext('</p>'),
])
def check_replace_1(self):
self._run_check("<p tal:replace='string:foo'>bar</p>", [
('setPosition', (1, 0)),
('beginScope', {'tal:replace': 'string:foo'}),
('insertText', ('$string:foo$',
[('startTag', ('p', [('tal:replace', 'string:foo', 'tal')])),
rawtext('bar</p>')])),
('endScope', ()),
])
def check_replace_2(self):
self._run_check("<p tal:replace='text string:foo'>bar</p>", [
('setPosition', (1, 0)),
('beginScope', {'tal:replace': 'text string:foo'}),
('insertText', ('$string:foo$',
[('startTag', ('p',
[('tal:replace', 'text string:foo', 'tal')])),
rawtext('bar</p>')])),
('endScope', ()),
])
def check_replace_3(self):
self._run_check("<p tal:replace='structure string:<br>'>bar</p>", [
('setPosition', (1, 0)),
('beginScope', {'tal:replace': 'structure string:<br>'}),
('insertStructure', ('$string:<br>$', {},
[('startTag', ('p',
[('tal:replace', 'structure string:<br>', 'tal')])),
rawtext('bar</p>')])),
('endScope', ()),
])
def check_repeat(self):
self._run_check("<p tal:repeat='x python:(1,2,3)'>"
"<span tal:replace='x'>dummy</span></p>", [
('setPosition', (1, 0)),
('beginScope', {'tal:repeat': 'x python:(1,2,3)'}),
('loop', ('x', '$python:(1,2,3)$',
[('startTag', ('p',
[('tal:repeat', 'x python:(1,2,3)', 'tal')])),
('setPosition', (1, 33)),
('beginScope', {'tal:replace': 'x'}),
('insertText', ('$x$',
[('startTag', ('span', [('tal:replace', 'x', 'tal')])),
rawtext('dummy</span>')])),
('endScope', ()),
rawtext('</p>')])),
('endScope', ()),
])
def check_attributes_1(self):
self._run_check("<a href='foo' name='bar' tal:attributes="
"'href string:http://www.zope.org; x string:y'>"
"link</a>", [
('setPosition', (1, 0)),
('beginScope',
{'tal:attributes': 'href string:http://www.zope.org; x string:y',
'name': 'bar', 'href': 'foo'}),
('startTag', ('a',
[('href', 'foo', 'replace', '$string:http://www.zope.org$', 0, None),
('name', 'name="bar"'),
('tal:attributes',
'href string:http://www.zope.org; x string:y', 'tal'),
('x', None, 'insert', '$string:y$', 0, None)])),
('endScope', ()),
rawtext('link</a>'),
])
def check_attributes_2(self):
self._run_check("<p tal:replace='structure string:<img>' "
"tal:attributes='src string:foo.png'>duh</p>", [
('setPosition', (1, 0)),
('beginScope',
{'tal:attributes': 'src string:foo.png',
'tal:replace': 'structure string:<img>'}),
('insertStructure',
('$string:<img>$',
{'src': ('$string:foo.png$', 0, None)},
[('startTag', ('p',
[('tal:replace', 'structure string:<img>', 'tal'),
('tal:attributes', 'src string:foo.png',
'tal')])),
rawtext('duh</p>')])),
('endScope', ()),
])
def check_on_error_1(self):
self._run_check("<p tal:on-error='string:error' "
"tal:content='notHere'>okay</p>", [
('setPosition', (1, 0)),
('beginScope',
{'tal:content': 'notHere', 'tal:on-error': 'string:error'}),
('onError',
([('startTag', ('p',
[('tal:on-error', 'string:error', 'tal'),
('tal:content', 'notHere', 'tal')])),
('insertText', ('$notHere$', [rawtext('okay')])),
rawtext('</p>')],
[('startTag', ('p',
[('tal:on-error', 'string:error', 'tal'),
('tal:content', 'notHere', 'tal')])),
('insertText', ('$string:error$', [])),
rawtext('</p>')])),
('endScope', ()),
])
def check_on_error_2(self):
self._run_check("<p tal:on-error='string:error' "
"tal:replace='notHere'>okay</p>", [
('setPosition', (1, 0)),
('beginScope',
{'tal:replace': 'notHere', 'tal:on-error': 'string:error'}),
('onError',
([('insertText', ('$notHere$',
[('startTag', ('p',
[('tal:on-error', 'string:error', 'tal'),
('tal:replace', 'notHere', 'tal')])),
rawtext('okay</p>')]))],
[('startTag', ('p',
[('tal:on-error', 'string:error', 'tal'),
('tal:replace', 'notHere', 'tal')])),
('insertText', ('$string:error$', [])),
rawtext('</p>')])),
('endScope', ()),
])
def check_dup_attr(self):
self._should_error("<img tal:condition='x' tal:condition='x'>")
self._should_error("<img metal:define-macro='x' "
"metal:define-macro='x'>", METALError)
def check_tal_errors(self):
self._should_error("<p tal:define='x' />")
self._should_error("<p tal:repeat='x' />")
self._should_error("<p tal:foobar='x' />")
self._should_error("<p tal:replace='x' tal:content='x' />")
self._should_error("<p tal:replace='x'>")
for tag in HTMLTALParser.EMPTY_HTML_TAGS:
self._should_error("<%s tal:content='string:foo'>" % tag)
def check_metal_errors(self):
exc = METALError
self._should_error(2*"<p metal:define-macro='x'>xxx</p>", exc)
self._should_error("<html metal:use-macro='x'>" +
2*"<p metal:fill-slot='y' />" + "</html>", exc)
self._should_error("<p metal:foobar='x' />", exc)
self._should_error("<p metal:define-macro='x'>", exc)
#
# I18N test cases
#
def check_i18n_attributes(self):
self._run_check("<img alt='foo' i18n:attributes='alt'>", [
('setPosition', (1, 0)),
('beginScope', {'alt': 'foo', 'i18n:attributes': 'alt'}),
('startTag', ('img',
[('alt', 'foo', 'replace', None, 1, None),
('i18n:attributes', 'alt', 'i18n')])),
('endScope', ()),
])
self._run_check("<img alt='foo' i18n:attributes='alt foo ; bar'>", [
('setPosition', (1, 0)),
('beginScope', {'alt': 'foo', 'i18n:attributes': 'alt foo ; bar'}),
('startTag', ('img',
[('alt', 'foo', 'replace', None, 1, 'foo'),
('i18n:attributes', 'alt foo ; bar', 'i18n'),
('bar', None, 'insert', None, 1, None)])),
('endScope', ()),
])
self._should_error('''<input name="Delete"
tal:attributes="name string:delete_button"
i18n:attributes="name message-id">''')
def test_i18n_name_bad_name(self):
self._should_error("<span i18n:name='not a valid name' />")
self._should_error("<span i18n:name='-bad-name' />")
def test_i18n_attributes_repeated_attr(self):
self._should_error("<a i18n:attributes='href; href' />")
self._should_error("<a i18n:attributes='href; HREF' />")
def check_i18n_translate(self):
# input/test19.html
self._run_check('''\
<span i18n:translate="">Replace this</span>
<span i18n:translate="msgid">This is a
translated string</span>
<span i18n:translate="">And another
translated string</span>
''', [
('setPosition', (1, 0)),
('beginScope', {'i18n:translate': ''}),
('startTag', ('span', [('i18n:translate', '', 'i18n')])),
('insertTranslation', ('', [('rawtextOffset', ('Replace this', 12))])),
('rawtextBeginScope',
('</span>\n', 0, (2, 0), 1, {'i18n:translate': 'msgid'})),
('startTag', ('span', [('i18n:translate', 'msgid', 'i18n')])),
('insertTranslation',
('msgid', [('rawtextColumn', ('This is a\ntranslated string', 17))])),
('rawtextBeginScope', ('</span>\n', 0, (4, 0), 1, {'i18n:translate': ''})),
('startTag', ('span', [('i18n:translate', '', 'i18n')])),
('insertTranslation',
('', [('rawtextColumn', ('And another\ntranslated string', 17))])),
('endScope', ()),
('rawtextColumn', ('</span>\n', 0))])
def check_i18n_translate_with_nested_tal(self):
self._run_check('''\
<span i18n:translate="">replaceable <p tal:replace="str:here">content</p></span>
''', [
('setPosition', (1, 0)),
('beginScope', {'i18n:translate': ''}),
('startTag', ('span', [('i18n:translate', '', 'i18n')])),
('insertTranslation',
('',
[('rawtextOffset', ('replaceable ', 12)),
('setPosition', (1, 36)),
('beginScope', {'tal:replace': 'str:here'}),
('insertText',
('$str:here$',
[('startTag', ('p', [('tal:replace', 'str:here', 'tal')])),
('rawtextOffset', ('content</p>', 11))])),
('endScope', ())])),
('endScope', ()),
('rawtextColumn', ('</span>\n', 0))
])
def check_i18n_name(self):
# input/test21.html
self._run_check('''\
<span i18n:translate="">
<span tal:replace="str:Lomax" i18n:name="name" /> was born in
<span tal:replace="str:Antarctica" i18n:name="country" />.
</span>
''', [
('setPosition', (1, 0)),
('beginScope', {'i18n:translate': ''}),
('startTag', ('span', [('i18n:translate', '', 'i18n')])),
('insertTranslation',
('',
[('rawtextBeginScope',
('\n ',
2,
(2, 2),
0,
{'i18n:name': 'name', 'tal:replace': 'str:Lomax'})),
('i18nVariable',
('name',
[('startEndTag',
('span',
[('tal:replace', 'str:Lomax', 'tal'),
('i18n:name', 'name', 'i18n')]))],
'$str:Lomax$',
0)),
('rawtextBeginScope',
(' was born in\n ',
2,
(3, 2),
1,
{'i18n:name': 'country', 'tal:replace': 'str:Antarctica'})),
('i18nVariable',
('country',
[('startEndTag',
('span',
[('tal:replace', 'str:Antarctica', 'tal'),
('i18n:name', 'country', 'i18n')]))],
'$str:Antarctica$',
0)),
('endScope', ()),
('rawtextColumn', ('.\n', 0))])),
('endScope', ()),
('rawtextColumn', ('</span>\n', 0))
])
def test_i18n_name_with_content(self):
self._run_check('<div i18n:translate="">This is text for '
'<span i18n:translate="" tal:content="bar" i18n:name="bar_name"/>.'
'</div>', [
('setPosition', (1, 0)),
('beginScope', {'i18n:translate': ''}),
('startTag', ('div', [('i18n:translate', '', 'i18n')])),
('insertTranslation',
('',
[('rawtextOffset', ('This is text for ', 17)),
('setPosition', (1, 40)),
('beginScope',
{'tal:content': 'bar', 'i18n:name': 'bar_name', 'i18n:translate': ''}),
('i18nVariable',
('bar_name',
[('startTag',
('span',
[('i18n:translate', '', 'i18n'),
('tal:content', 'bar', 'tal'),
('i18n:name', 'bar_name', 'i18n')])),
('insertTranslation',
('',
[('insertText', ('$bar$', []))])),
('rawtextOffset', ('</span>', 7))],
None,
0)),
('endScope', ()),
('rawtextOffset', ('.', 1))])),
('endScope', ()),
('rawtextOffset', ('</div>', 6))
])
def check_i18n_name_implicit_value(self):
# input/test22.html
self._run_check('''\
<span i18n:translate="">
<span i18n:name="name"><b>Jim</b></span> was born in
<span i18n:name="country">the USA</span>.
</span>
''', [
('setPosition', (1, 0)),
('beginScope', {'i18n:translate': ''}),
('startTag', ('span', [('i18n:translate', '', 'i18n')])),
('insertTranslation',
('',
[('rawtextBeginScope', ('\n ', 2, (2, 2), 0, {'i18n:name': 'name'})),
('i18nVariable',
('name',
[('rawtextOffset', ('<b>Jim</b>', 10))], None, 0)),
('rawtextBeginScope',
(' was born in\n ', 2, (3, 2), 1, {'i18n:name': 'country'})),
('i18nVariable',
('country',
[('rawtextOffset', ('the USA', 7))], None, 0)),
('endScope', ()),
('rawtextColumn', ('.\n', 0))])),
('endScope', ()),
('rawtextColumn', ('</span>\n', 0))
])
def check_i18n_context_domain(self):
self._run_check("<span i18n:domain='mydomain'/>", [
('setPosition', (1, 0)),
('beginI18nContext', {'domain': 'mydomain',
'source': None, 'target': None}),
('beginScope', {'i18n:domain': 'mydomain'}),
('startEndTag', ('span', [('i18n:domain', 'mydomain', 'i18n')])),
('endScope', ()),
('endI18nContext', ()),
])
def check_i18n_context_source(self):
self._run_check("<span i18n:source='en'/>", [
('setPosition', (1, 0)),
('beginI18nContext', {'source': 'en',
'domain': 'default', 'target': None}),
('beginScope', {'i18n:source': 'en'}),
('startEndTag', ('span', [('i18n:source', 'en', 'i18n')])),
('endScope', ()),
('endI18nContext', ()),
])
def check_i18n_context_source_target(self):
self._run_check("<span i18n:source='en' i18n:target='ru'/>", [
('setPosition', (1, 0)),
('beginI18nContext', {'source': 'en', 'target': 'ru',
'domain': 'default'}),
('beginScope', {'i18n:source': 'en', 'i18n:target': 'ru'}),
('startEndTag', ('span', [('i18n:source', 'en', 'i18n'),
('i18n:target', 'ru', 'i18n')])),
('endScope', ()),
('endI18nContext', ()),
])
def check_i18n_context_in_define_slot(self):
text = ("<div metal:use-macro='M' i18n:domain='mydomain'>"
"<div metal:fill-slot='S'>spam</div>"
"</div>")
self._run_check(text, [
('setPosition', (1, 0)),
('useMacro',
('M', '$M$',
{'S': [('startTag', ('div',
[('metal:fill-slot', 'S', 'metal')])),
rawtext('spam</div>')]},
[('beginI18nContext', {'domain': 'mydomain',
'source': None, 'target': None}),
('beginScope',
{'i18n:domain': 'mydomain', 'metal:use-macro': 'M'}),
('startTag', ('div', [('metal:use-macro', 'M', 'metal'),
('i18n:domain', 'mydomain', 'i18n')])),
('setPosition', (1, 48)),
('fillSlot', ('S',
[('startTag',
('div', [('metal:fill-slot', 'S', 'metal')])),
rawtext('spam</div>')])),
('endScope', ()),
rawtext('</div>'),
('endI18nContext', ())])),
])
def check_i18n_data(self):
# input/test23.html
self._run_check('''\
<span i18n:data="here/currentTime"
i18n:translate="timefmt">2:32 pm</span>
''', [
('setPosition', (1, 0)),
('beginScope',
{'i18n:translate': 'timefmt', 'i18n:data': 'here/currentTime'}),
('startTag',
('span',
[('i18n:data', 'here/currentTime', 'i18n'),
('i18n:translate', 'timefmt', 'i18n')])),
('insertTranslation',
('timefmt', [('rawtextOffset', ('2:32 pm', 7))], '$here/currentTime$')),
('endScope', ()),
('rawtextColumn', ('</span>\n', 0))
])
def check_i18n_data_with_name(self):
# input/test29.html
self._run_check('''\
<div i18n:translate="">At the tone the time will be
<span i18n:data="here/currentTime"
i18n:translate="timefmt"
i18n:name="time">2:32 pm</span>... beep!</div>
''',
[('setPosition', (1, 0)),
('beginScope', {'i18n:translate': ''}),
('startTag', ('div', [('i18n:translate', '', 'i18n')])),
('insertTranslation',
('',
[('rawtextBeginScope',
('At the tone the time will be\n',
0,
(2, 0),
0,
{'i18n:data': 'here/currentTime',
'i18n:name': 'time',
'i18n:translate': 'timefmt'})),
('insertTranslation',
('timefmt',
[('startTag',
('span',
[('i18n:data', 'here/currentTime', 'i18n'),
('i18n:translate', 'timefmt', 'i18n'),
('i18n:name', 'time', 'i18n')])),
('i18nVariable', ('time', [], None, 0))],
'$here/currentTime$')),
('endScope', ()),
('rawtextOffset', ('... beep!', 9))])),
('endScope', ()),
('rawtextColumn', ('</div>\n', 0))]
)
def check_i18n_explicit_msgid_with_name(self):
# input/test26.html
self._run_check('''\
<span i18n:translate="jobnum">
Job #<span tal:replace="context/@@object_name"
i18n:name="jobnum">NN</span></span>
''', [
('setPosition', (1, 0)),
('beginScope', {'i18n:translate': 'jobnum'}),
('startTag', ('span', [('i18n:translate', 'jobnum', 'i18n')])),
('insertTranslation',
('jobnum',
[('rawtextBeginScope',
('\n Job #',
9,
(2, 9),
0,
{'i18n:name': 'jobnum', 'tal:replace': 'context/@@object_name'})),
('i18nVariable',
('jobnum',
[('startTag',
('span',
[('tal:replace', 'context/@@object_name', 'tal'),
('i18n:name', 'jobnum', 'i18n')])),
('rawtextOffset', ('NN', 2)),
('rawtextOffset', ('</span>', 7))],
'$context/@@object_name$',
0)),
('endScope', ())])),
('endScope', ()),
('rawtextColumn', ('</span>\n', 0))
])
def check_i18n_name_around_tal_content(self):
# input/test28.html
self._run_check('''\
<p i18n:translate="verify">Your contact email address is recorded as
<span i18n:name="email">
<a href="mailto:user@example.com"
tal:content="request/submitter">user@host.com</a></span>
</p>
''', [
('setPosition', (1, 0)),
('beginScope', {'i18n:translate': 'verify'}),
('startTag', ('p', [('i18n:translate', 'verify', 'i18n')])),
('insertTranslation',
('verify',
[('rawtextBeginScope',
('Your contact email address is recorded as\n ',
4,
(2, 4),
0,
{'i18n:name': 'email'})),
('i18nVariable',
('email',
[('rawtextBeginScope',
('\n ',
4,
(3, 4),
0,
{'href': 'mailto:user@example.com',
'tal:content': 'request/submitter'})),
('startTag',
('a',
[('href', 'href="mailto:user@example.com"'),
('tal:content', 'request/submitter', 'tal')])),
('insertText',
('$request/submitter$',
[('rawtextOffset', ('user@host.com', 13))])),
('endScope', ()),
('rawtextOffset', ('</a>', 4))],
None,
0)),
('endScope', ()),
('rawtextColumn', ('\n', 0))])),
('endScope', ()),
('rawtextColumn', ('</p>\n', 0))
])
def check_i18n_name_with_tal_content(self):
# input/test27.html
self._run_check('''\
<p i18n:translate="verify">Your contact email address is recorded as
<a href="mailto:user@example.com"
tal:content="request/submitter"
i18n:name="email">user@host.com</a>
</p>
''', [
('setPosition', (1, 0)),
('beginScope', {'i18n:translate': 'verify'}),
('startTag', ('p', [('i18n:translate', 'verify', 'i18n')])),
('insertTranslation',
('verify',
[('rawtextBeginScope',
('Your contact email address is recorded as\n ',
4,
(2, 4),
0,
{'href': 'mailto:user@example.com',
'i18n:name': 'email',
'tal:content': 'request/submitter'})),
('i18nVariable',
('email',
[('startTag',
('a',
[('href', 'href="mailto:user@example.com"'),
('tal:content', 'request/submitter', 'tal'),
('i18n:name', 'email', 'i18n')])),
('insertText',
('$request/submitter$',
[('rawtextOffset', ('user@host.com', 13))])),
('rawtextOffset', ('</a>', 4))],
None,
0)),
('endScope', ()),
('rawtextColumn', ('\n', 0))])),
('endScope', ()),
('rawtextColumn', ('</p>\n', 0))
])
def test_suite():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(HTMLTALParserTestCases, "check_"))
suite.addTest(unittest.makeSuite(METALGeneratorTestCases, "check_"))
suite.addTest(unittest.makeSuite(TALGeneratorTestCases, "check_"))
return suite
if __name__ == "__main__":
errs = utils.run_suite(test_suite())
sys.exit(errs and 1 or 0)
#! /usr/bin/env python
"""Tests for TALInterpreter."""
import sys
import unittest
from StringIO import StringIO
from TAL.HTMLTALParser import HTMLTALParser
from TAL.TALInterpreter import TALInterpreter
from TAL.TALGenerator import TALGenerator
from TAL.DummyEngine import DummyEngine
page1 = '''<html metal:use-macro="main"><body>
<div metal:fill-slot="body">
page1=<span tal:replace="position:" />
</div>
</body></html>'''
main_template = '''<html metal:define-macro="main"><body>
main_template=<span tal:replace="position:" />
<div metal:define-slot="body" />
main_template=<span tal:replace="position:" />
<div metal:use-macro="foot" />
main_template=<span tal:replace="position:" />
</body></html>'''
footer = '''<div metal:define-macro="foot">
footer=<span tal:replace="position:" />
</div>'''
expected = '''<html><body>
main_template=main_template (2,14)
<div>
page1=page1 (3,6)
</div>
main_template=main_template (4,14)
<div>
footer=footer (2,7)
</div>
main_template=main_template (6,14)
</body></html>'''
class Tests(unittest.TestCase):
def parse(self, eng, s, fn):
gen = TALGenerator(expressionCompiler=eng, xml=0, source_file=fn)
parser = HTMLTALParser(gen)
parser.parseString(s)
program, macros = parser.getCode()
return program, macros
def testSourcePositions(self):
"""Ensure source file and position are set correctly by TAL"""
macros = {}
eng = DummyEngine(macros)
page1_program, page1_macros = self.parse(eng, page1, 'page1')
main_template_program, main_template_macros = self.parse(
eng, main_template, 'main_template')
footer_program, footer_macros = self.parse(eng, footer, 'footer')
macros['main'] = main_template_macros['main']
macros['foot'] = footer_macros['foot']
stream = StringIO()
interp = TALInterpreter(page1_program, macros, eng, stream)
interp()
self.assertEqual(stream.getvalue().strip(), expected.strip(),
stream.getvalue())
def test_suite():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(Tests))
return suite
if __name__ == "__main__":
unittest.main()
# -*- coding: ISO-8859-1 -*-
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Tests for TALInterpreter.
$Id$
"""
import sys
import unittest
from StringIO import StringIO
from TAL.TALDefs import METALError, I18NError
from TAL.HTMLTALParser import HTMLTALParser
from TAL.TALParser import TALParser
from TAL.TALInterpreter import TALInterpreter
from TAL.DummyEngine import DummyEngine, DummyTranslationService
from TAL.TALInterpreter import interpolate
from TAL.tests import utils
from zope.i18nmessageid import Message
class TestCaseBase(unittest.TestCase):
def _compile(self, source):
parser = HTMLTALParser()
parser.parseString(source)
program, macros = parser.getCode()
return program, macros
class MacroErrorsTestCase(TestCaseBase):
def setUp(self):
dummy, macros = self._compile('<p metal:define-macro="M">Booh</p>')
self.macro = macros['M']
self.engine = DummyEngine(macros)
program, dummy = self._compile('<p metal:use-macro="M">Bah</p>')
self.interpreter = TALInterpreter(program, {}, self.engine)
def tearDown(self):
try:
self.interpreter()
except METALError:
pass
else:
self.fail("Expected METALError")
def test_mode_error(self):
self.macro[1] = ("mode", "duh")
def test_version_error(self):
self.macro[0] = ("version", "duh")
class I18NCornerTestCase(TestCaseBase):
def setUp(self):
self.engine = DummyEngine()
self.engine.setLocal('foo', Message('FoOvAlUe', 'default'))
self.engine.setLocal('bar', 'BaRvAlUe')
self.engine.setLocal('raw', ' \tRaW\n ')
self.engine.setLocal('noxlt', Message("don't translate me"))
def _check(self, program, expected):
result = StringIO()
self.interpreter = TALInterpreter(program, {}, self.engine,
stream=result)
self.interpreter()
self.assertEqual(expected, result.getvalue())
def test_simple_messageid_translate(self):
# This test is mainly here to make sure our DummyEngine works
# correctly.
program, macros = self._compile('<span tal:content="foo"/>')
self._check(program, '<span>FOOVALUE</span>\n')
program, macros = self._compile('<span tal:replace="foo"/>')
self._check(program, 'FOOVALUE\n')
def test_replace_with_messageid_and_i18nname(self):
program, macros = self._compile(
'<div i18n:translate="" >'
'<span tal:replace="foo" i18n:name="foo_name"/>'
'</div>')
self._check(program, '<div>FOOVALUE</div>\n')
def test_pythonexpr_replace_with_messageid_and_i18nname(self):
program, macros = self._compile(
'<div i18n:translate="" >'
'<span tal:replace="python: foo" i18n:name="foo_name"/>'
'</div>')
self._check(program, '<div>FOOVALUE</div>\n')
def test_structure_replace_with_messageid_and_i18nname(self):
program, macros = self._compile(
'<div i18n:translate="" >'
'<span tal:replace="structure foo" i18n:name="foo_name"/>'
'</div>')
self._check(program, '<div>FOOVALUE</div>\n')
def test_complex_replace_with_messageid_and_i18nname(self):
program, macros = self._compile(
'<div i18n:translate="" >'
'<em i18n:name="foo_name">'
'<span tal:replace="foo"/>'
'</em>'
'</div>')
self._check(program, '<div>FOOVALUE</div>\n')
def test_content_with_messageid_and_i18nname(self):
program, macros = self._compile(
'<div i18n:translate="" >'
'<span tal:content="foo" i18n:name="foo_name"/>'
'</div>')
self._check(program, '<div><span>FOOVALUE</span></div>\n')
def test_content_with_messageid_and_i18nname_and_i18ntranslate(self):
# Let's tell the user this is incredibly silly!
self.assertRaises(
I18NError, self._compile,
'<span i18n:translate="" tal:content="foo" i18n:name="foo_name"/>')
def test_content_with_plaintext_and_i18nname_and_i18ntranslate(self):
# Let's tell the user this is incredibly silly!
self.assertRaises(
I18NError, self._compile,
'<span i18n:translate="" i18n:name="color_name">green</span>')
def test_translate_static_text_as_dynamic(self):
program, macros = self._compile(
'<div i18n:translate="">This is text for '
'<span i18n:translate="" tal:content="bar" i18n:name="bar_name"/>.'
'</div>')
self._check(program,
'<div>THIS IS TEXT FOR <span>BARVALUE</span>.</div>\n')
def test_translate_static_text_as_dynamic_from_bytecode(self):
program = [('version', '1.5'),
('mode', 'html'),
('setPosition', (1, 0)),
('beginScope', {'i18n:translate': ''}),
('startTag', ('div', [('i18n:translate', '', 'i18n')])),
('insertTranslation',
('',
[('rawtextOffset', ('This is text for ', 17)),
('setPosition', (1, 40)),
('beginScope',
{'tal:content': 'bar', 'i18n:name': 'bar_name', 'i18n:translate': ''}),
('i18nVariable',
('bar_name',
[('startTag',
('span',
[('i18n:translate', '', 'i18n'),
('tal:content', 'bar', 'tal'),
('i18n:name', 'bar_name', 'i18n')])),
('insertTranslation',
('',
[('insertText', ('$bar$', []))])),
('rawtextOffset', ('</span>', 7))],
None,
0)),
('endScope', ()),
('rawtextOffset', ('.', 1))])),
('endScope', ()),
('rawtextOffset', ('</div>', 6))
]
self._check(program,
'<div>THIS IS TEXT FOR <span>BARVALUE</span>.</div>\n')
def _getCollectingTranslationDomain(self):
class CollectingTranslationService(DummyTranslationService):
data = []
def translate(self, domain, msgid, mapping=None,
context=None, target_language=None, default=None):
self.data.append((msgid, mapping))
return DummyTranslationService.translate(
self,
domain, msgid, mapping, context, target_language, default)
xlatsvc = CollectingTranslationService()
self.engine.translationService = xlatsvc
return xlatsvc
def test_for_correct_msgids(self):
xlatdmn = self._getCollectingTranslationDomain()
result = StringIO()
program, macros = self._compile(
'<div i18n:translate="">This is text for '
'<span i18n:translate="" tal:content="bar" '
'i18n:name="bar_name"/>.</div>')
self.interpreter = TALInterpreter(program, {}, self.engine,
stream=result)
self.interpreter()
msgids = list(xlatdmn.data)
msgids.sort()
self.assertEqual(2, len(msgids))
self.assertEqual('BaRvAlUe', msgids[0][0])
self.assertEqual('This is text for ${bar_name}.', msgids[1][0])
self.assertEqual({'bar_name': '<span>BARVALUE</span>'}, msgids[1][1])
self.assertEqual(
'<div>THIS IS TEXT FOR <span>BARVALUE</span>.</div>\n',
result.getvalue())
def test_for_raw_msgids(self):
# Test for Issue 314: i18n:translate removes line breaks from
# <pre>...</pre> contents
# HTML mode
xlatdmn = self._getCollectingTranslationDomain()
result = StringIO()
program, macros = self._compile(
'<div i18n:translate=""> This is text\n'
' \tfor\n div. </div>'
'<pre i18n:translate=""> This is text\n'
' <b>\tfor</b>\n pre. </pre>')
self.interpreter = TALInterpreter(program, {}, self.engine,
stream=result)
self.interpreter()
msgids = list(xlatdmn.data)
msgids.sort()
self.assertEqual(2, len(msgids))
self.assertEqual(' This is text\n <b>\tfor</b>\n pre. ', msgids[0][0])
self.assertEqual('This is text for div.', msgids[1][0])
self.assertEqual(
'<div>THIS IS TEXT FOR DIV.</div>'
'<pre> THIS IS TEXT\n <B>\tFOR</B>\n PRE. </pre>\n',
result.getvalue())
# XML mode
xlatdmn = self._getCollectingTranslationDomain()
result = StringIO()
parser = TALParser()
parser.parseString(
'<?xml version="1.0"?>\n'
'<pre xmlns:i18n="http://xml.zope.org/namespaces/i18n"'
' i18n:translate=""> This is text\n'
' <b>\tfor</b>\n barvalue. </pre>')
program, macros = parser.getCode()
self.interpreter = TALInterpreter(program, {}, self.engine,
stream=result)
self.interpreter()
msgids = list(xlatdmn.data)
msgids.sort()
self.assertEqual(1, len(msgids))
self.assertEqual('This is text <b> for</b> barvalue.', msgids[0][0])
self.assertEqual(
'<?xml version="1.0"?>\n'
'<pre>THIS IS TEXT <B> FOR</B> BARVALUE.</pre>\n',
result.getvalue())
def test_raw_msgids_and_i18ntranslate_i18nname(self):
xlatdmn = self._getCollectingTranslationDomain()
result = StringIO()
program, macros = self._compile(
'<div i18n:translate=""> This is text\n \tfor\n'
'<pre tal:content="raw" i18n:name="raw"'
' i18n:translate=""></pre>.</div>')
self.interpreter = TALInterpreter(program, {}, self.engine,
stream=result)
self.interpreter()
msgids = list(xlatdmn.data)
msgids.sort()
self.assertEqual(2, len(msgids))
self.assertEqual(' \tRaW\n ', msgids[0][0])
self.assertEqual('This is text for ${raw}.', msgids[1][0])
self.assertEqual({'raw': '<pre> \tRAW\n </pre>'}, msgids[1][1])
self.assertEqual(
u'<div>THIS IS TEXT FOR <pre> \tRAW\n </pre>.</div>\n',
result.getvalue())
def test_for_handling_unicode_vars(self):
# Make sure that non-ASCII Unicode is substituted correctly.
# http://collector.zope.org/Zope3-dev/264
program, macros = self._compile(
"<div i18n:translate='' tal:define='bar python:unichr(0xC0)'>"
"Foo <span tal:replace='bar' i18n:name='bar' /></div>")
self._check(program, u"<div>FOO \u00C0</div>\n")
def test_for_untranslated_messageid_simple(self):
program, macros = self._compile('<span tal:content="noxlt"/>')
self._check(program, "<span>don't translate me</span>\n")
def test_for_untranslated_messageid_i18nname(self):
program, macros = self._compile(
'<div i18n:translate="" >'
'<span tal:replace="python: noxlt" i18n:name="foo_name"/>'
'</div>')
self._check(program, "<div>don't translate me</div>\n")
class I18NErrorsTestCase(TestCaseBase):
def _check(self, src, msg):
try:
self._compile(src)
except I18NError:
pass
else:
self.fail(msg)
def test_id_with_replace(self):
self._check('<p i18n:id="foo" tal:replace="string:splat"></p>',
"expected i18n:id with tal:replace to be denied")
def test_missing_values(self):
self._check('<p i18n:attributes=""></p>',
"missing i18n:attributes value not caught")
self._check('<p i18n:data=""></p>',
"missing i18n:data value not caught")
self._check('<p i18n:id=""></p>',
"missing i18n:id value not caught")
def test_id_with_attributes(self):
self._check('''<input name="Delete"
tal:attributes="name string:delete_button"
i18n:attributes="name message-id">''',
"expected attribute being both part of tal:attributes" +
" and having a msgid in i18n:attributes to be denied")
class OutputPresentationTestCase(TestCaseBase):
def test_attribute_wrapping(self):
# To make sure the attribute-wrapping code is invoked, we have to
# include at least one TAL/METAL attribute to avoid having the start
# tag optimized into a rawtext instruction.
INPUT = r"""
<html this='element' has='a' lot='of' attributes=', so' the='output'
needs='to' be='line' wrapped='.' tal:define='foo nothing'>
</html>"""
EXPECTED = r'''
<html this="element" has="a" lot="of"
attributes=", so" the="output" needs="to"
be="line" wrapped=".">
</html>''' "\n"
self.compare(INPUT, EXPECTED)
def test_unicode_content(self):
INPUT = """<p tal:content="python:u'dj-vu'">para</p>"""
EXPECTED = u"""<p>dj-vu</p>""" "\n"
self.compare(INPUT, EXPECTED)
def test_unicode_structure(self):
INPUT = """<p tal:replace="structure python:u'dj-vu'">para</p>"""
EXPECTED = u"""dj-vu""" "\n"
self.compare(INPUT, EXPECTED)
def test_i18n_replace_number(self):
INPUT = """
<p i18n:translate="foo ${bar}">
<span tal:replace="python:123" i18n:name="bar">para</span>
</p>"""
EXPECTED = u"""
<p>FOO 123</p>""" "\n"
self.compare(INPUT, EXPECTED)
def test_entities(self):
INPUT = ('<img tal:define="foo nothing" '
'alt="&a; &#1; &#x0a; &a &#45 &; &#0a; <>" />')
EXPECTED = ('<img alt="&a; &#1; &#x0a; '
'&amp;a &amp;#45 &amp;; &amp;#0a; &lt;&gt;" />\n')
self.compare(INPUT, EXPECTED)
def compare(self, INPUT, EXPECTED):
program, macros = self._compile(INPUT)
sio = StringIO()
interp = TALInterpreter(program, {}, DummyEngine(), sio, wrap=60)
interp()
self.assertEqual(sio.getvalue(), EXPECTED)
class InterpolateTestCase(TestCaseBase):
def test_syntax_ok(self):
text = "foo ${bar_0MAN} $baz_zz bee"
mapping = {'bar_0MAN': 'fish', 'baz_zz': 'moo'}
expected = "foo fish moo bee"
self.assertEqual(interpolate(text, mapping), expected)
def test_syntax_bad(self):
text = "foo $_bar_man} $ ${baz bee"
mapping = {'_bar_man': 'fish', 'baz': 'moo'}
expected = text
self.assertEqual(interpolate(text, mapping), expected)
def test_missing(self):
text = "foo ${bar} ${baz}"
mapping = {'bar': 'fish'}
expected = "foo fish ${baz}"
self.assertEqual(interpolate(text, mapping), expected)
def test_redundant(self):
text = "foo ${bar}"
mapping = {'bar': 'fish', 'baz': 'moo'}
expected = "foo fish"
self.assertEqual(interpolate(text, mapping), expected)
def test_numeric(self):
text = "foo ${bar}"
mapping = {'bar': 123}
expected = "foo 123"
self.assertEqual(interpolate(text, mapping), expected)
def test_unicode(self):
text = u"foo ${bar}"
mapping = {u'bar': u'baz'}
expected = u"foo baz"
self.assertEqual(interpolate(text, mapping), expected)
def test_unicode_mixed_unknown_encoding(self):
# This test assumes that sys.getdefaultencoding is ascii...
text = u"foo ${bar}"
mapping = {u'bar': 'd\xe9j\xe0'}
expected = u"foo d\\xe9j\\xe0"
self.assertEqual(interpolate(text, mapping), expected)
def test_suite():
suite = unittest.makeSuite(I18NErrorsTestCase)
suite.addTest(unittest.makeSuite(MacroErrorsTestCase))
suite.addTest(unittest.makeSuite(OutputPresentationTestCase))
suite.addTest(unittest.makeSuite(I18NCornerTestCase))
suite.addTest(unittest.makeSuite(InterpolateTestCase))
return suite
if __name__ == "__main__":
errs = utils.run_suite(test_suite())
sys.exit(errs and 1 or 0)
#! /usr/bin/env python1.5
"""Tests for XMLParser.py."""
import string
import sys
from TAL.tests import utils
import unittest
from TAL import XMLParser
class EventCollector(XMLParser.XMLParser):
def __init__(self):
self.events = []
self.append = self.events.append
XMLParser.XMLParser.__init__(self)
self.parser.ordered_attributes = 1
def get_events(self):
# Normalize the list of events so that buffer artefacts don't
# separate runs of contiguous characters.
L = []
prevtype = None
for event in self.events:
type = event[0]
if type == prevtype == "data":
L[-1] = ("data", L[-1][1] + event[1])
else:
L.append(event)
prevtype = type
self.events = L
return L
# structure markup
def StartElementHandler(self, tag, attrs):
self.append(("starttag", tag, attrs))
def EndElementHandler(self, tag):
self.append(("endtag", tag))
# all other markup
def CommentHandler(self, data):
self.append(("comment", data))
def handle_charref(self, data):
self.append(("charref", data))
def CharacterDataHandler(self, data):
self.append(("data", data))
def StartDoctypeDeclHandler(self, rootelem, publicId, systemId, subset):
self.append(("doctype", rootelem, systemId, publicId, subset))
def XmlDeclHandler(self, version, encoding, standalone):
self.append(("decl", version, encoding, standalone))
def ExternalEntityRefHandler(self, data):
self.append(("entityref", data))
def ProcessingInstructionHandler(self, target, data):
self.append(("pi", target, data))
class EventCollectorExtra(EventCollector):
def handle_starttag(self, tag, attrs):
EventCollector.handle_starttag(self, tag, attrs)
self.append(("starttag_text", self.get_starttag_text()))
class SegmentedFile:
def __init__(self, parts):
self.parts = list(parts)
def read(self, bytes):
if self.parts:
s = self.parts.pop(0)
else:
s = ''
return s
class XMLParserTestCase(unittest.TestCase):
def _run_check(self, source, events, collector=EventCollector):
parser = collector()
if isinstance(source, type([])):
parser.parseStream(SegmentedFile(source))
else:
parser.parseString(source)
if utils.oldexpat:
while events[0][0] in ('decl', 'doctype'):
del events[0]
self.assertEquals(parser.get_events(), events)
def _run_check_extra(self, source, events):
self._run_check(source, events, EventCollectorExtra)
def _parse_error(self, source):
def parse(source=source):
parser = XMLParser.XMLParser()
parser.parseString(source)
self.assertRaises(XMLParser.XMLParseError, parse)
def check_processing_instruction_plus(self):
self._run_check("<?processing instruction?><a/>", [
("pi", "processing", "instruction"),
("starttag", "a", []),
("endtag", "a"),
])
def _check_simple_html(self):
self._run_check("""\
<?xml version='1.0' encoding='iso-8859-1'?>
<!DOCTYPE html PUBLIC 'foo' 'bar'>
<html>&entity;&#32;
<!--comment1a
-></foo><bar>&lt;<?pi?></foo<bar
comment1b-->
<img src='Bar' ismap=''/>sample
text
<!--comment2a- -comment2b-->
</html>
""", [
("decl", "1.0", "iso-8859-1", -1),
("doctype", "html", "foo", "bar", 0),
("starttag", "html", []),
# ("entityref", "entity"),
("data", " \n"),
("comment", "comment1a\n-></foo><bar>&lt;<?pi?></foo<bar\ncomment1b"),
("data", "\n"),
("starttag", "img", ["src", "Bar", "ismap", ""]),
("endtag", "img"),
("data", "sample\ntext\n"),
("comment", "comment2a- -comment2b"),
("data", "\n"),
("endtag", "html"),
])
def check_bad_nesting(self):
try:
self._run_check("<a><b></a></b>", [
("starttag", "a", []),
("starttag", "b", []),
("endtag", "a"),
("endtag", "b"),
])
except:
e = sys.exc_info()[1]
self.assert_(e.lineno == 1,
"did not receive correct position information")
else:
self.fail("expected parse error: bad nesting")
def check_attr_syntax(self):
output = [
("starttag", "a", ["b", "v", "c", "v"]),
("endtag", "a"),
]
self._run_check("""<a b='v' c="v"/>""", output)
self._run_check("""<a b = 'v' c = "v"/>""", output)
self._run_check("""<a\nb\n=\n'v'\nc\n=\n"v"\n/>""", output)
self._run_check("""<a\tb\t=\t'v'\tc\t=\t"v"\t/>""", output)
def check_attr_values(self):
self._run_check("""<a b='xxx\n\txxx' c="yyy\t\nyyy" d='\txyz\n'/>""",
[("starttag", "a", ["b", "xxx xxx",
"c", "yyy yyy",
"d", " xyz "]),
("endtag", "a"),
])
self._run_check("""<a b='' c="" d=''/>""", [
("starttag", "a", ["b", "", "c", "", "d", ""]),
("endtag", "a"),
])
def check_attr_entity_replacement(self):
self._run_check("""<a b='&amp;&gt;&lt;&quot;&apos;'/>""", [
("starttag", "a", ["b", "&><\"'"]),
("endtag", "a"),
])
def check_attr_funky_names(self):
self._run_check("""<a a.b='v' e-f='v'/>""", [
("starttag", "a", ["a.b", "v", "e-f", "v"]),
("endtag", "a"),
])
def check_starttag_end_boundary(self):
self._run_check("""<a b='&lt;'/>""", [
("starttag", "a", ["b", "<"]),
("endtag", "a"),
])
self._run_check("""<a b='&gt;'/>""", [
("starttag", "a", ["b", ">"]),
("endtag", "a"),
])
def check_buffer_artefacts(self):
output = [("starttag", "a", ["b", "<"]), ("endtag", "a")]
self._run_check(["<a b='&lt;'/>"], output)
self._run_check(["<a ", "b='&lt;'/>"], output)
self._run_check(["<a b", "='&lt;'/>"], output)
self._run_check(["<a b=", "'&lt;'/>"], output)
self._run_check(["<a b='&lt;", "'/>"], output)
self._run_check(["<a b='&lt;'", "/>"], output)
output = [("starttag", "a", ["b", ">"]), ("endtag", "a")]
self._run_check(["<a b='&gt;'/>"], output)
self._run_check(["<a ", "b='&gt;'/>"], output)
self._run_check(["<a b", "='&gt;'/>"], output)
self._run_check(["<a b=", "'&gt;'/>"], output)
self._run_check(["<a b='&gt;", "'/>"], output)
self._run_check(["<a b='&gt;'", "/>"], output)
def check_starttag_junk_chars(self):
self._parse_error("<")
self._parse_error("<>")
self._parse_error("</>")
self._parse_error("</$>")
self._parse_error("</")
self._parse_error("</a")
self._parse_error("</a")
self._parse_error("<a<a>")
self._parse_error("</a<a>")
self._parse_error("<$")
self._parse_error("<$>")
self._parse_error("<!")
self._parse_error("<a $>")
self._parse_error("<a")
self._parse_error("<a foo='bar'")
self._parse_error("<a foo='bar")
self._parse_error("<a foo='>'")
self._parse_error("<a foo='>")
def check_declaration_junk_chars(self):
self._parse_error("<!DOCTYPE foo $ >")
# Support for the Zope regression test framework:
def test_suite(skipxml=utils.skipxml):
suite = unittest.TestSuite()
if not skipxml:
suite.addTest(unittest.makeSuite(XMLParserTestCase, "check_"))
return suite
if __name__ == "__main__":
errs = utils.run_suite(test_suite(skipxml=0))
sys.exit(errs and 1 or 0)
"""Helper functions for the test suite."""
import os
import sys
mydir = os.path.abspath(os.path.dirname(__file__))
codedir = os.path.dirname(os.path.dirname(mydir))
if codedir not in sys.path:
sys.path.append(codedir)
import unittest
import xml.parsers.expat
# Set skipxml to true if an XML parser could not be found.
# (But Python always includes one now.)
skipxml = False
# Set oldexpat if the StartDoctypeDeclHandler and XmlDeclHandler are
# not supported. The tests need to know whether the events reported
# by those handlers should be expected, but need to make sure the
# right thing is returned if they are.
#
oldexpat = False
p = xml.parsers.expat.ParserCreate()
#
# Can't use hasattr() since pyexpat supports the handler
# attributes in a broken way.
try:
p.StartDoctypeDeclHandler = None
except AttributeError:
oldexpat = True
def run_suite(suite, outf=None, errf=None):
if outf is None:
outf = sys.stdout
runner = unittest.TextTestRunner(outf)
result = runner.run(suite)
## print "\n\n"
## if result.errors:
## print "Errors (unexpected exceptions):"
## map(print_error, result.errors)
## print
## if result.failures:
## print "Failures (assertion failures):"
## map(print_error, result.failures)
## print
newerrs = len(result.errors) + len(result.failures)
if newerrs:
print "'Errors' indicate exceptions other than AssertionError."
print "'Failures' indicate AssertionError"
if errf is None:
errf = sys.stderr
errf.write("%d errors, %d failures\n"
% (len(result.errors), len(result.failures)))
return newerrs
def print_error(info):
testcase, (type, e, tb) = info
#! /usr/bin/env python
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
#
##############################################################################
"""
Helper program to time compilation and interpretation
"""
import sys
import time
import getopt
from cPickle import dumps, loads
from cStringIO import StringIO
from driver import FILE, compilefile, interpretit
def main():
count = 10
try:
opts, args = getopt.getopt(sys.argv[1:], "n:")
except getopt.error, msg:
print msg
sys.exit(2)
for o, a in opts:
if o == "-n":
count = int(a)
if not args:
args = [FILE]
for file in args:
print file
dummyfile = StringIO()
it = timefunc(count, compilefile, file)
timefunc(count, interpretit, it, None, dummyfile)
def timefunc(count, func, *args):
sys.stderr.write("%-14s: " % func.__name__)
sys.stderr.flush()
t0 = time.clock()
for i in range(count):
result = func(*args)
t1 = time.clock()
sys.stderr.write("%6.3f secs for %d calls, i.e. %4.0f msecs per call\n"
% ((t1-t0), count, 1000*(t1-t0)/count))
return result
if __name__ == "__main__":
main()
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment