Commit 82471624 authored by Philipp von Weitershausen's avatar Philipp von Weitershausen

Merge ajung-zpt-end-game branch.

parents 997aef4e d8a6bbd0
......@@ -18,6 +18,18 @@ Zope Changes
Restructuring
- Products.PageTemplates now uses the Zope 3 ZPT implementation
in zope.pagetemplate.
- The TAL package has been deprecated in favour of the TAL
engine from zope.tal.
- Products.PageTemplates.TALES has been deprecated in favour of
the TALES engine from zope.tales.
- ZTUtils.Iterator has been deprecated in favour of the TALES
iterator implementation in zope.tales.tales.
- ZCatalog: removed manage_deleteIndex(), manage_delColumns()
which were deprecated since Zope 2.4
......
......@@ -10,32 +10,16 @@
# FOR A PARTICULAR PURPOSE
#
##############################################################################
"""Defer and Lazy expression handler
"""Lazy expression handler
defer expressions can be usesd for a design pattern called deferred evaluation.
Example:
<div tal:define="xis defer:string:x is $x">
<p tal:repeat="x python:range(3)"
tal:content="xis"></p>
</div>
Output:
<div>
<p>x is 0</p>
<p>x is 1</p>
<p>x is 2</p>
</div>
A lazy expressions is implemented in a similar way but has a different result. While
a defer expression is evaluated every time it is used according to its context a lazy
expression is evaluted only the first time it is used. Lazy expression are known
under the name lazy initialization of variables, too.
A common use case for a lazy expression is a lazy binding of a costly expression.
While one could call an expression only when it's required it makes sense to define
it only one time when it could be used multiple times.
A lazy expressions is implemented similarly to the defer expression
but has a different result. While a defer expression is evaluated
every time it is used according to its context a lazy expression is
evaluted only the first time it is used. Lazy expression are known
under the name lazy initialization of variables, too. A common use
case for a lazy expression is a lazy binding of a costly expression.
While one could call an expression only when it's required it makes
sense to define it only one time when it could be used multiple times.
Example
......@@ -45,38 +29,11 @@ Example
<div tal:condition"python: not (foo or bar)">...</div>
</div>
"""
from zope.tales.expressions import DeferWrapper, DeferExpr
_marker = object()
# defer expression
class DeferWrapper:
"""Wrapper for defer: expression
"""
def __init__(self, expr, econtext):
self._expr = expr
self._econtext = econtext
def __str__(self):
return str(self())
def __call__(self):
return self._expr(self._econtext)
class DeferExpr:
"""defer: expression handler for deferred evaluation of the context
"""
def __init__(self, name, expr, compiler):
self._s = expr = expr.lstrip()
self._c = compiler.compile(expr)
def __call__(self, econtext):
return DeferWrapper(self._c, econtext)
def __repr__(self):
return 'defer:%s' % `self._s`
# lazy expression
# TODO These should really be integrated into the Zope 3 ZPT
# implementation (zope.tales)
class LazyWrapper(DeferWrapper):
"""Wrapper for lazy: expression
......@@ -99,4 +56,3 @@ class LazyExpr(DeferExpr):
def __repr__(self):
return 'lazy:%s' % `self._s`
......@@ -10,73 +10,81 @@
# FOR A PARTICULAR PURPOSE
#
##############################################################################
"""Page Template Expression Engine
Page Template-specific implementation of TALES, with handlers
for Python expressions, string literals, and paths.
"""
__version__='$Revision: 1.45 $'[11:-2]
import re, sys
from TALES import Engine
from TALES import CompilerError
from TALES import _valid_name
from TALES import NAME_RE
from TALES import Undefined
from TALES import Default
from TALES import _parse_expr
from Acquisition import aq_base, aq_inner, aq_parent
from DeferExpr import LazyWrapper
from DeferExpr import LazyExpr
from DeferExpr import DeferWrapper
from DeferExpr import DeferExpr
_engine = None
def getEngine():
global _engine
if _engine is None:
from PathIterator import Iterator
_engine = Engine(Iterator)
installHandlers(_engine)
return _engine
def installHandlers(engine):
reg = engine.registerType
pe = PathExpr
for pt in ('standard', 'path', 'exists', 'nocall'):
reg(pt, pe)
reg('string', StringExpr)
reg('python', PythonExpr)
reg('not', NotExpr)
reg('defer', DeferExpr)
reg('lazy', LazyExpr)
import AccessControl
import AccessControl.cAccessControl
acquisition_security_filter = AccessControl.cAccessControl.aq_validate
from AccessControl import getSecurityManager
from AccessControl.ZopeGuards import guarded_getattr
from AccessControl import Unauthorized
from ZRPythonExpr import PythonExpr
from ZRPythonExpr import _SecureModuleImporter
from ZRPythonExpr import call_with_ns
SecureModuleImporter = _SecureModuleImporter()
Undefs = (Undefined, AttributeError, KeyError,
TypeError, IndexError, Unauthorized)
$Id$
"""
from zope.interface import implements
from zope.tales.tales import Context, Iterator
from zope.tales.expressions import PathExpr, StringExpr, NotExpr
from zope.tales.expressions import DeferExpr, SubPathExpr, Undefs
from zope.tales.pythonexpr import PythonExpr
from zope.traversing.interfaces import ITraversable
from zope.traversing.adapters import traversePathElement
from zope.contentprovider.tales import TALESProviderExpression
from zope.proxy import removeAllProxies
import zope.app.pagetemplate.engine
import OFS.interfaces
from Acquisition import aq_base
from zExceptions import NotFound, Unauthorized
from Products.PageTemplates import ZRPythonExpr
from Products.PageTemplates.DeferExpr import LazyExpr
from Products.PageTemplates.GlobalTranslationService import getGlobalTranslationService
SecureModuleImporter = ZRPythonExpr._SecureModuleImporter()
# BBB 2005/05/01 -- remove after 12 months
import zope.deprecation
from zope.deprecation import deprecate
zope.deprecation.deprecated(
("StringExpr", "NotExpr", "PathExpr", "SubPathExpr", "Undefs"),
"Zope 2 uses the Zope 3 ZPT engine now. Expression types can be "
"imported from zope.tales.expressions."
)
# In Zope 2 traversal semantics, NotFound or Unauthorized (the Zope 2
# versions) indicate that traversal has failed. By default, Zope 3's
# TALES engine doesn't recognize them as such which is why we extend
# Zope 3's list here and make sure our implementation of the TALES
# Path Expression uses them
ZopeUndefs = Undefs + (NotFound, Unauthorized)
def boboAwareZopeTraverse(object, path_items, econtext):
"""Traverses a sequence of names, first trying attributes then items.
This uses Zope 3 path traversal where possible and interacts
correctly with objects providing OFS.interface.ITraversable when
necessary (bobo-awareness).
"""
request = getattr(econtext, 'request', None)
path_items = list(path_items)
path_items.reverse()
while path_items:
name = path_items.pop()
if OFS.interfaces.ITraversable.providedBy(object):
object = object.restrictedTraverse(name)
else:
object = traversePathElement(object, name, path_items,
request=request)
return object
def render(ob, ns):
"""
Calls the object, possibly a document template, or just returns it if
not callable. (From DT_Util.py)
"""Calls the object, possibly a document template, or just returns
it if not callable. (From DT_Util.py)
"""
if hasattr(ob, '__render_with_namespace__'):
ob = call_with_ns(ob.__render_with_namespace__, ns)
ob = ZRPythonExpr.call_with_ns(ob.__render_with_namespace__, ns)
else:
# items might be acquisition wrapped
base = aq_base(ob)
# item might be proxied (e.g. modules might have a deprecation
# proxy)
base = removeAllProxies(base)
if callable(base):
try:
if getattr(base, 'isDocTemp', 0):
......@@ -88,278 +96,172 @@ def render(ob, ns):
raise
return ob
class SubPathExpr:
def __init__(self, path):
self._path = path = path.strip().split('/')
self._base = base = path.pop(0)
if base and not _valid_name(base):
raise CompilerError, 'Invalid variable name "%s"' % base
# Parse path
self._dp = dp = []
for i in range(len(path)):
e = path[i]
if e[:1] == '?' and _valid_name(e[1:]):
dp.append((i, e[1:]))
dp.reverse()
def _eval(self, econtext,
list=list, isinstance=isinstance, StringType=type('')):
vars = econtext.vars
path = self._path
if self._dp:
path = list(path) # Copy!
for i, varname in self._dp:
val = vars[varname]
if isinstance(val, StringType):
path[i] = val
else:
# If the value isn't a string, assume it's a sequence
# of path names.
path[i:i+1] = list(val)
__traceback_info__ = base = self._base
if base == 'CONTEXTS' or not base:
ob = econtext.contexts
else:
ob = vars[base]
if isinstance(ob, DeferWrapper):
ob = ob()
if path:
ob = restrictedTraverse(ob, path, getSecurityManager())
return ob
class PathExpr:
def __init__(self, name, expr, engine):
self._s = expr
self._name = name
self._hybrid = 0
paths = expr.split('|')
self._subexprs = []
add = self._subexprs.append
for i in range(len(paths)):
path = paths[i].lstrip()
if _parse_expr(path):
# This part is the start of another expression type,
# so glue it back together and compile it.
add(engine.compile(('|'.join(paths[i:]).lstrip())))
self._hybrid = 1
break
add(SubPathExpr(path)._eval)
class ZopePathExpr(PathExpr):
def _exists(self, econtext):
for expr in self._subexprs:
try:
expr(econtext)
except Undefs:
pass
else:
return 1
return 0
def __init__(self, name, expr, engine):
super(ZopePathExpr, self).__init__(name, expr, engine,
boboAwareZopeTraverse)
def _eval(self, econtext,
isinstance=isinstance,
BasicTypes=(str, unicode, dict, list, tuple, bool),
render=render):
# override this to support different call metrics (see bottom of
# method) and Zope 2's traversal exceptions (ZopeUndefs instead of
# Undefs)
def _eval(self, econtext):
for expr in self._subexprs[:-1]:
# Try all but the last subexpression, skipping undefined ones.
try:
ob = expr(econtext)
except Undefs:
except ZopeUndefs: # use Zope 2 expression types
pass
else:
break
else:
# On the last subexpression allow exceptions through, and
# don't autocall if the expression was not a subpath.
# On the last subexpression allow exceptions through.
ob = self._subexprs[-1](econtext)
if self._hybrid:
return ob
if self._name == 'nocall' or isinstance(ob, BasicTypes):
if self._name == 'nocall':
return ob
# Return the rendered object
return render(ob, econtext.vars)
def __call__(self, econtext):
if self._name == 'exists':
return self._exists(econtext)
return self._eval(econtext)
def __str__(self):
return '%s expression %s' % (self._name, `self._s`)
def __repr__(self):
return '%s:%s' % (self._name, `self._s`)
_interp = re.compile(r'\$(%(n)s)|\${(%(n)s(?:/[^}]*)*)}' % {'n': NAME_RE})
# this is where we are different from our super class:
return render(ob, econtext.vars)
class StringExpr:
def __init__(self, name, expr, engine):
self._s = expr
if '%' in expr:
expr = expr.replace('%', '%%')
self._vars = vars = []
if '$' in expr:
parts = []
for exp in expr.split('$$'):
if parts: parts.append('$')
m = _interp.search(exp)
while m is not None:
parts.append(exp[:m.start()])
parts.append('%s')
vars.append(PathExpr('path', m.group(1) or m.group(2),
engine))
exp = exp[m.end():]
m = _interp.search(exp)
if '$' in exp:
raise CompilerError, (
'$ must be doubled or followed by a simple path')
parts.append(exp)
expr = ''.join(parts)
self._expr = expr
def __call__(self, econtext):
vvals = []
for var in self._vars:
v = var(econtext)
# I hope this isn't in use anymore.
## if isinstance(v, Exception):
## raise v
vvals.append(v)
return self._expr % tuple(vvals)
def __str__(self):
return 'string expression %s' % `self._s`
def __repr__(self):
return 'string:%s' % `self._s`
class NotExpr:
def __init__(self, name, expr, compiler):
self._s = expr = expr.lstrip()
self._c = compiler.compile(expr)
def __call__(self, econtext):
# We use the (not x) and 1 or 0 formulation to avoid changing
# the representation of the result in Python 2.3, where the
# result of "not" becomes an instance of bool.
return (not econtext.evaluateBoolean(self._c)) and 1 or 0
def __repr__(self):
return 'not:%s' % `self._s`
from zope.interface import Interface, implements
from zope.component import queryMultiAdapter
from zope.traversing.interfaces import TraversalError
from zope.traversing.namespace import nsParse, namespaceLookup
from zope.publisher.interfaces.browser import IBrowserRequest
from zope.publisher.browser import setDefaultSkin
class FakeRequest(dict):
implements(IBrowserRequest)
def getURL(self):
return "http://codespeak.net/z3/five"
def restrictedTraverse(object, path, securityManager,
get=getattr, has=hasattr, N=None, M=[],
TupleType=type(()) ):
REQUEST = FakeRequest()
REQUEST['path'] = path
REQUEST['TraversalRequestNameStack'] = path = path[:] # Copy!
setDefaultSkin(REQUEST)
path.reverse()
validate = securityManager.validate
__traceback_info__ = REQUEST
while path:
name = path.pop()
if isinstance(name, TupleType):
object = object(*name)
continue
if not name or name[0] == '_':
# Skip directly to item access
o = object[name]
# Check access to the item.
if not validate(object, object, None, o):
raise Unauthorized, name
object = o
continue
if name=='..':
o = get(object, 'aq_parent', M)
if o is not M:
if not validate(object, object, name, o):
raise Unauthorized, name
object=o
continue
t=get(object, '__bobo_traverse__', N)
if name and name[:1] in '@+':
# Process URI segment parameters.
ns, nm = nsParse(name)
if ns:
try:
o = namespaceLookup(ns, nm, object,
REQUEST).__of__(object)
if not validate(object, object, name, o):
raise Unauthorized, name
except TraversalError:
raise AttributeError(name)
elif t is not N:
o=t(REQUEST, name)
container = None
if aq_base(o) is not o:
# The object is wrapped, so the acquisition
# context determines the container.
container = aq_parent(aq_inner(o))
elif has(o, 'im_self'):
container = o.im_self
elif (has(aq_base(object), name) and get(object, name) == o):
container = object
if not validate(object, container, name, o):
raise Unauthorized, name
else:
# Try an attribute.
o = guarded_getattr(object, str(name), M) # failed on u'aq_parent'
if o is M:
# Try an item.
try:
# XXX maybe in Python 2.2 we can just check whether
# the object has the attribute "__getitem__"
# instead of blindly catching exceptions.
try:
o = object[name]
except (AttributeError, KeyError):
# Try to look for a view
o = queryMultiAdapter((object, REQUEST),
Interface, name)
if o is None:
# Didn't find one, reraise the error:
raise
o = o.__of__(object)
except AttributeError, exc:
if str(exc).find('__getitem__') >= 0:
# The object does not support the item interface.
# Try to re-raise the original attribute error.
# XXX I think this only happens with
# ExtensionClass instances.
guarded_getattr(object, name)
raise
except TypeError, exc:
if str(exc).find('unsubscriptable') >= 0:
# The object does not support the item interface.
# Try to re-raise the original attribute error.
# XXX This is sooooo ugly.
guarded_getattr(object, name)
raise
else:
# Check access to the item.
if not validate(object, object, None, o):
raise Unauthorized, name
object = o
# override this to support Zope 2's traversal exceptions
# (ZopeUndefs instead of Undefs)
def _exists(self, econtext):
for expr in self._subexprs:
try:
expr(econtext)
except ZopeUndefs: # use Zope 2 expression types
pass
else:
return 1
return 0
return object
class ZopeContext(Context):
def translate(self, msgid, domain=None, mapping=None, default=None):
context = self.contexts.get('context')
return getGlobalTranslationService().translate(
domain, msgid, mapping=mapping,
context=context, default=default)
class ZopeEngine(zope.app.pagetemplate.engine.ZopeEngine):
_create_context = ZopeContext
class ZopeIterator(Iterator):
# The things below used to be attributes in
# ZTUtils.Iterator.Iterator, however in zope.tales.tales.Iterator
# they're methods. We need BBB on the Python level so we redefine
# them as properties here. Eventually, we would like to get rid
# of them, though, so that we won't have to maintain yet another
# iterator class somewhere.
@property
def index(self):
return super(ZopeIterator, self).index()
@property
def start(self):
return super(ZopeIterator, self).start()
@property
def end(self):
return super(ZopeIterator, self).end()
@property
def item(self):
return super(ZopeIterator, self).item()
# This method was on the old ZTUtils.Iterator.Iterator class but
# isn't part of the spec. We'll support it for a short
# deprecation period.
# BBB 2005/05/01 -- to be removed after 12 months
@property
@deprecate("The 'nextIndex' method has been deprecated and will disappear "
"in Zope 2.12. Use 'iterator.index+1' instead.")
def nextIndex(self):
return self.index + 1
# 'first' and 'last' are Zope 2 enhancements to the TALES iterator
# spec. See help/tal-repeat.stx for more info
def first(self, name=None):
if self.start:
return True
return not self.same_part(name, self._last_item, self.item)
def last(self, name=None):
if self.end:
return True
return not self.same_part(name, self.item, self._next)
def same_part(self, name, ob1, ob2):
if name is None:
return ob1 == ob2
no = object()
return getattr(ob1, name, no) == getattr(ob2, name, no) is not no
# 'first' needs to have access to the last item in the loop
def next(self):
if self._nextIndex > 0:
self._last_item = self.item
return super(ZopeIterator, self).next()
class PathIterator(ZopeIterator):
"""A TALES Iterator with the ability to use first() and last() on
subpaths of elements."""
# we want to control our own traversal so that we can deal with
# 'first' and 'last' when they appear in path expressions
implements(ITraversable)
def traverse(self, name, furtherPath):
if name in ('first', 'last'):
method = getattr(self, name)
# it's important that 'name' becomes a copy because we'll
# clear out 'furtherPath'
name = furtherPath[:]
if not name:
name = None
# make sure that traversal ends here with us
furtherPath[:] = []
return method(name)
return getattr(self, name)
def same_part(self, name, ob1, ob2):
if name is None:
return ob1 == ob2
if isinstance(name, basestring):
name = name.split('/')
try:
ob1 = boboAwareZopeTraverse(ob1, name, None)
ob2 = boboAwareZopeTraverse(ob2, name, None)
except ZopeUndefs:
return False
return ob1 == ob2
def createZopeEngine():
e = ZopeEngine()
e.iteratorFactory = PathIterator
for pt in ZopePathExpr._default_type_names:
e.registerType(pt, ZopePathExpr)
e.registerType('string', StringExpr)
e.registerType('python', ZRPythonExpr.PythonExpr)
e.registerType('not', NotExpr)
e.registerType('defer', DeferExpr)
e.registerType('lazy', LazyExpr)
e.registerType('provider', TALESProviderExpression)
e.registerBaseName('modules', SecureModuleImporter)
return e
def createTrustedZopeEngine():
# same as createZopeEngine, but use non-restricted Python
# expression evaluator
e = createZopeEngine()
e.types['python'] = PythonExpr
return e
_engine = createZopeEngine()
def getEngine():
return _engine
......@@ -15,12 +15,11 @@
$Id$
"""
import re
import Products.Five.i18n
from DocumentTemplate.DT_Util import ustr
from TAL.TALDefs import NAME_RE
from zope.tal.taldefs import NAME_RE
class DummyTranslationService:
"""Translation service that doesn't know anything about translation."""
......@@ -30,13 +29,15 @@ class DummyTranslationService:
return ustr(mapping[m.group(m.lastindex)])
cre = re.compile(r'\$(?:(%s)|\{(%s)\})' % (NAME_RE, NAME_RE))
return cre.sub(repl, default or msgid)
# XXX Not all of Zope2.I18n.ITranslationService is implemented.
#
# As of Five 1.1, we're by default using Zope 3 Message Catalogs for
# translation, but we allow fallback translation services such as PTS
# and Localizer
#
# TODO We should really deprecate Zope2-style translation service and
# only support Zope3-style i18n in the future.
#
Products.Five.i18n._fallback_translation_service = DummyTranslationService()
fiveTranslationService = Products.Five.i18n.FiveTranslationService()
......
......@@ -12,65 +12,47 @@
##############################################################################
"""Page Template module
HTML- and XML-based template objects using TAL, TALES, and METAL.
$Id$
"""
import sys
import ExtensionClass
import zope.pagetemplate.pagetemplate
from zope.pagetemplate.pagetemplate import _error_start, PTRuntimeError
from zope.pagetemplate.pagetemplate import PageTemplateTracebackSupplement
from zope.tales.expressions import SimpleModuleImporter
from Products.PageTemplates.Expressions import getEngine
__version__='$Revision: 1.31 $'[11:-2]
import sys, types
from TAL.TALParser import TALParser
from TAL.HTMLTALParser import HTMLTALParser
from TAL.TALGenerator import TALGenerator
# Do not use cStringIO here! It's not unicode aware. :(
from TAL.TALInterpreter import TALInterpreter, FasterStringIO
from Expressions import getEngine
from ExtensionClass import Base
from ComputedAttribute import ComputedAttribute
class PageTemplate(Base):
"Page Templates using TAL, TALES, and METAL"
content_type = 'text/html'
expand = 0
_v_errors = ()
_v_warnings = ()
_v_program = None
_v_macros = None
_v_cooked = 0
id = '(unknown)'
_text = ''
_error_start = '<!-- Page Template Diagnostics'
def StringIO(self):
# Third-party products wishing to provide a full Unicode-aware
# StringIO can do so by monkey-patching this method.
return FasterStringIO()
##############################################################################
# BBB 2005/05/01 -- to be removed after 12 months
_ModuleImporter = SimpleModuleImporter
ModuleImporter = SimpleModuleImporter()
import zope.deprecation
zope.deprecation.deprecated(
('ModuleImporter', '_ModuleImporter'),
"Zope 2 uses the Zope 3 ZPT engine now. ModuleImporter has moved "
"to zope.pagetemplate.pagetemplate.SimpleModuleImporter (this is a "
"class, not an instance)."
)
zope.deprecation.deprecated(
('PTRuntimeError', 'PageTemplateTracebackSupplement'),
"Zope 2 uses the Zope 3 ZPT engine now. The object you're importing "
"has moved to zope.pagetemplate.pagetemplate. This reference will "
"be gone in Zope 2.12.",
)
##############################################################################
def macros(self):
return self.pt_macros()
macros = ComputedAttribute(macros, 1)
class PageTemplate(ExtensionClass.Base,
zope.pagetemplate.pagetemplate.PageTemplate):
def pt_edit(self, text, content_type):
if content_type:
self.content_type = str(content_type)
if hasattr(text, 'read'):
text = text.read()
charset = getattr(self, 'management_page_charset', None)
if charset and type(text) == types.StringType:
try:
unicode(text,'us-ascii')
except UnicodeDecodeError:
text = unicode(text, charset)
self.write(text)
def pt_getEngine(self):
return getEngine()
def pt_getContext(self):
c = {'template': self,
'options': {},
'nothing': None,
'request': None,
'modules': ModuleImporter,
'modules': SimpleModuleImporter(),
}
parent = getattr(self, 'aq_parent', None)
if parent is not None:
......@@ -83,73 +65,43 @@ class PageTemplate(Base):
c['root'] = self
return c
def pt_render(self, source=0, extra_context={}):
"""Render this Page Template"""
if not self._v_cooked:
self._cook()
__traceback_supplement__ = (PageTemplateTracebackSupplement, self)
@property
def macros(self):
return self.pt_macros()
# sub classes may override this to do additional stuff for macro access
def pt_macros(self):
self._cook_check()
if self._v_errors:
e = str(self._v_errors)
__traceback_supplement__ = (PageTemplateTracebackSupplement, self, {})
raise PTRuntimeError, (
'Page Template %s has errors: %s' % (self.id, e))
output = self.StringIO()
c = self.pt_getContext()
c.update(extra_context)
'Page Template %s has errors: %s' % (
self.id, self._v_errors
))
return self._v_macros
TALInterpreter(self._v_program, self._v_macros,
getEngine().getContext(c),
output,
tal=not source, strictinsert=0)()
return output.getvalue()
# these methods are reimplemented or duplicated here because of
# different call signatures in the Zope 2 world
def __call__(self, *args, **kwargs):
if not kwargs.has_key('args'):
kwargs['args'] = args
return self.pt_render(extra_context={'options': kwargs})
def pt_render(self, source=False, extra_context={}):
c = self.pt_getContext()
c.update(extra_context)
return super(PageTemplate, self).pt_render(c, source=source)
def pt_errors(self):
if not self._v_cooked:
self._cook()
def pt_errors(self, namespace={}):
self._cook_check()
err = self._v_errors
if err:
return err
if not self.expand: return
try:
self.pt_render(source=1)
self.pt_render(source=True, extra_context=namespace)
except:
return ('Macro expansion failed', '%s: %s' % sys.exc_info()[:2])
def pt_warnings(self):
if not self._v_cooked:
self._cook()
return self._v_warnings
def pt_macros(self):
if not self._v_cooked:
self._cook()
if self._v_errors:
__traceback_supplement__ = (PageTemplateTracebackSupplement, self)
raise PTRuntimeError, (
'Page Template %s has errors: %s' % (
self.id,
self._v_errors
))
return self._v_macros
def pt_source_file(self):
return None # Unknown.
def write(self, text):
assert type(text) in types.StringTypes
if text[:len(self._error_start)] == self._error_start:
errend = text.find('-->')
if errend >= 0:
text = text[errend + 4:]
if self._text != text:
self._text = text
self._cook()
def __call__(self, *args, **kwargs):
if not kwargs.has_key('args'):
kwargs['args'] = args
return self.pt_render(extra_context={'options': kwargs})
def read(self):
self._cook_check()
......@@ -157,71 +109,21 @@ class PageTemplate(Base):
if not self.expand:
return self._text
try:
return self.pt_render(source=1)
return self.pt_render(source=True)
except:
return ('%s\n Macro expansion failed\n %s\n-->\n%s' %
(self._error_start, "%s: %s" % sys.exc_info()[:2],
(_error_start, "%s: %s" % sys.exc_info()[:2],
self._text) )
return ('%s\n %s\n-->\n%s' % (self._error_start,
return ('%s\n %s\n-->\n%s' % (_error_start,
'\n '.join(self._v_errors),
self._text))
def _cook_check(self):
if not self._v_cooked:
self._cook()
def _cook(self):
"""Compile the TAL and METAL statments.
Cooking must not fail due to compilation errors in templates.
"""
source_file = self.pt_source_file()
if self.html():
gen = TALGenerator(getEngine(), xml=0, source_file=source_file)
parser = HTMLTALParser(gen)
else:
gen = TALGenerator(getEngine(), source_file=source_file)
parser = TALParser(gen)
self._v_errors = ()
try:
parser.parseString(self._text)
self._v_program, self._v_macros = parser.getCode()
except:
self._v_errors = ["Compilation failed",
"%s: %s" % sys.exc_info()[:2]]
self._v_warnings = parser.getWarnings()
self._v_cooked = 1
# convenience method for the ZMI which allows to explicitly
# specify the HTMLness of a template. The old Zope 2
# implementation had this as well, but arguably on the wrong class
# (this should be a ZopePageTemplate thing if at all)
def html(self):
if not hasattr(getattr(self, 'aq_base', self), 'is_html'):
return self.content_type == 'text/html'
return self.is_html
class _ModuleImporter:
def __getitem__(self, module):
mod = __import__(module)
path = module.split('.')
for name in path[1:]:
mod = getattr(mod, name)
return mod
ModuleImporter = _ModuleImporter()
class PTRuntimeError(RuntimeError):
'''The Page Template has template errors that prevent it from rendering.'''
pass
class PageTemplateTracebackSupplement:
#__implements__ = ITracebackSupplement
def __init__(self, pt):
self.object = pt
w = pt.pt_warnings()
e = pt.pt_errors()
if e:
w = list(w) + list(e)
self.warnings = w
......@@ -10,32 +10,35 @@
# FOR A PARTICULAR PURPOSE
#
##############################################################################
"""Filesystem Page Template module
Zope object encapsulating a Page Template from the filesystem.
"""
__version__ = '$Revision: 1.30 $'[11:-2]
import os, AccessControl
import os
from logging import getLogger
from Globals import package_home, DevelopmentMode
import AccessControl
from Globals import package_home, InitializeClass, DevelopmentMode
from App.config import getConfiguration
from Acquisition import aq_parent, aq_inner
from ComputedAttribute import ComputedAttribute
from OFS.SimpleItem import SimpleItem
from OFS.Traversable import Traversable
from Shared.DC.Scripts.Script import Script
from Shared.DC.Scripts.Signature import FuncCode
from AccessControl import getSecurityManager
from OFS.Traversable import Traversable
from PageTemplate import PageTemplate
from Expressions import SecureModuleImporter
from ComputedAttribute import ComputedAttribute
from Acquisition import aq_parent, aq_inner
from App.config import getConfiguration
from OFS.SimpleItem import Item_w__name__
from Products.PageTemplates.Expressions import SecureModuleImporter
from Products.PageTemplates.PageTemplate import PageTemplate
from zope.contenttype import guess_content_type
from zope.pagetemplate.pagetemplatefile import sniff_type
LOG = getLogger('PageTemplateFile')
class PageTemplateFile(Item_w__name__, Script, PageTemplate, Traversable):
"Zope wrapper for filesystem Page Template using TAL, TALES, and METAL"
def guess_type(filename, text):
content_type, dummy = guess_content_type(filename, text)
if content_type in ('text/html', 'text/xml'):
return content_type
return sniff_type(text) or 'text/html'
class PageTemplateFile(SimpleItem, Script, PageTemplate, Traversable):
"""Zope 2 implementation of a PageTemplate loaded from a file."""
meta_type = 'Page Template (File)'
......@@ -53,29 +56,35 @@ class PageTemplateFile(Item_w__name__, Script, PageTemplate, Traversable):
security.declareProtected('View management screens',
'read', 'document_src')
_default_bindings = {'name_subpath': 'traverse_subpath'}
def __init__(self, filename, _prefix=None, **kw):
self.ZBindings_edit(self._default_bindings)
if _prefix is None:
_prefix = getConfiguration().softwarehome
elif not isinstance(_prefix, str):
_prefix = package_home(_prefix)
name = kw.get('__name__')
name = None
if kw.has_key('__name__'):
name = kw['__name__']
del kw['__name__']
basepath, ext = os.path.splitext(filename)
if name:
self._need__name__ = 0
self.__name__ = name
self.id = self.__name__ = name
else:
self.__name__ = os.path.basename(basepath)
self.id = self.__name__ = os.path.basename(basepath)
if _prefix:
if isinstance(_prefix, str):
filename = os.path.join(_prefix, filename)
else:
filename = os.path.join(package_home(_prefix), filename)
if not ext:
# XXX This is pretty bogus, but can't be removed since
# it's been released this way.
filename = filename + '.zpt'
self.filename = os.path.join(_prefix, filename)
def getId(self):
"""return the ID of this object"""
return self.__name__
self.filename = filename
content = open(filename).read()
self.pt_edit( content, guess_type(filename, content))
def pt_getContext(self):
root = self.getPhysicalRoot()
context = self._getContext()
......@@ -106,10 +115,13 @@ class PageTemplateFile(Item_w__name__, Script, PageTemplate, Traversable):
pass
# Execute the template in a new security context.
security = getSecurityManager()
security = AccessControl.getSecurityManager()
bound_names['user'] = security.getUser()
security.addContext(self)
try:
context = self.pt_getContext()
context.update(bound_names)
return self.pt_render(extra_context=bound_names)
finally:
security.removeContext(self)
......@@ -187,6 +199,7 @@ class PageTemplateFile(Item_w__name__, Script, PageTemplate, Traversable):
raise StorageError, ("Instance of AntiPersistent class %s "
"cannot be stored." % self.__class__.__name__)
InitializeClass(PageTemplateFile)
XML_PREFIXES = [
"<?xml", # ascii, utf-8
......
......@@ -10,37 +10,16 @@
# FOR A PARTICULAR PURPOSE
#
##############################################################################
"""Path Iterator
A TALES Iterator with the ability to use first() and last() on
subpaths of elements.
"""
__version__='$Revision: 1.4 $'[11:-2]
BBB 2005/05/01 -- to be removed after 12 months
import TALES
from Expressions import restrictedTraverse, Undefs, getSecurityManager
class Iterator(TALES.Iterator):
def __bobo_traverse__(self, REQUEST, name):
if name in ('first', 'last'):
path = REQUEST['TraversalRequestNameStack']
names = list(path)
names.reverse()
path[:] = [tuple(names)]
return getattr(self, name)
def same_part(self, name, ob1, ob2):
if name is None:
return ob1 == ob2
if isinstance(name, type('')):
name = name.split('/')
name = filter(None, name)
securityManager = getSecurityManager()
try:
ob1 = restrictedTraverse(ob1, name, securityManager)
ob2 = restrictedTraverse(ob2, name, securityManager)
except Undefs:
return 0
return ob1 == ob2
$Id$
"""
import zope.deferredimport
zope.deferredimport.deprecated(
"It has been renamed to PathIterator and moved to the "
"Products.PageTemplates.Expressions module. This reference will be "
"gone in Zope 2.12.",
PathIterator = "Products.PageTemplates.Expressions:PathIterator"
)
......@@ -10,77 +10,10 @@
# FOR A PARTICULAR PURPOSE
#
##############################################################################
"""Generic Python Expression Handler
"""
__version__='$Revision: 1.13 $'[11:-2]
from TALES import CompilerError
from sys import exc_info
from DeferExpr import DeferWrapper
class getSecurityManager:
'''Null security manager'''
def validate(self, *args, **kwargs):
return 1
addContext = removeContext = validate
class PythonExpr:
def __init__(self, name, expr, engine):
self.expr = expr = expr.strip().replace('\n', ' ')
try:
d = {}
exec 'def f():\n return %s\n' % expr.strip() in d
self._f = d['f']
except:
raise CompilerError, ('Python expression error:\n'
'%s: %s') % exc_info()[:2]
self._get_used_names()
def _get_used_names(self):
self._f_varnames = vnames = []
for vname in self._f.func_code.co_names:
if vname[0] not in '$_':
vnames.append(vname)
def _bind_used_names(self, econtext, _marker=[]):
# Bind template variables
names = {'CONTEXTS': econtext.contexts}
vars = econtext.vars
getType = econtext.getCompiler().getTypes().get
for vname in self._f_varnames:
val = vars.get(vname, _marker)
if val is _marker:
has = val = getType(vname)
if has:
val = ExprTypeProxy(vname, val, econtext)
names[vname] = val
else:
names[vname] = val
for key, val in names.items():
if isinstance(val, DeferWrapper):
names[key] = val()
return names
def __call__(self, econtext):
__traceback_info__ = self.expr
f = self._f
f.func_globals.update(self._bind_used_names(econtext))
return f()
def __str__(self):
return 'Python expression "%s"' % self.expr
def __repr__(self):
return '<PythonExpr %s>' % self.expr
class ExprTypeProxy:
'''Class that proxies access to an expression type handler'''
def __init__(self, name, handler, econtext):
self._name = name
self._handler = handler
self._econtext = econtext
def __call__(self, text):
return self._handler(self._name, text,
self._econtext.getCompiler())(self._econtext)
$Id$
"""
# BBB 2005/05/01 -- remove after 12 months
import zope.deprecation
zope.deprecation.moved("zope.tales.pythonexpr", "2.12")
See <a href="http://dev.zope.org/Wikis/DevSite/Projects/ZPT">the
ZPT project Wiki</a> for more information about Page Templates, or
<a href="http://www.zope.org/Members/4am/ZPT">the download page</a>
for installation instructions and the most recent version of the software.
This Product requires the TAL and ZTUtils packages to be installed in
your Python path (not Products). See the links above for more information.
......@@ -12,40 +12,15 @@
##############################################################################
"""TALES
An implementation of a generic TALES engine
"""
BBB 2005/05/01 -- to be removed after 12 months
__version__='$Revision: 1.39 $'[11:-2]
$Id$
"""
from zope.tales.tests.simpleexpr import SimpleExpr
from zope.tales.tales import ExpressionEngine as Engine
from zope.tales.tales import _default as Default
import re, sys, ZTUtils
from weakref import ref
from MultiMapping import MultiMapping
from DocumentTemplate.DT_Util import ustr
from GlobalTranslationService import getGlobalTranslationService
from zExceptions import Unauthorized
StringType = type('')
NAME_RE = r"[a-zA-Z][a-zA-Z0-9_]*"
_parse_expr = re.compile(r"(%s):" % NAME_RE).match
_valid_name = re.compile('%s$' % NAME_RE).match
class TALESError(Exception):
"""Error during TALES expression evaluation"""
class Undefined(TALESError):
'''Exception raised on traversal of an undefined path'''
class RegistrationError(Exception):
'''TALES Type Registration Error'''
class CompilerError(Exception):
'''TALES Compiler Error'''
class Default:
'''Retain Default'''
Default = Default()
class SafeMapping(MultiMapping):
'''Mapping with security declarations and limited method exposure.
......@@ -60,241 +35,5 @@ class SafeMapping(MultiMapping):
_push = MultiMapping.push
_pop = MultiMapping.pop
class Iterator(ZTUtils.Iterator):
def __init__(self, name, seq, context):
ZTUtils.Iterator.__init__(self, seq)
self.name = name
self._context_ref = ref(context)
def next(self):
if ZTUtils.Iterator.next(self):
context = self._context_ref()
if context is not None:
context.setLocal(self.name, self.item)
return 1
return 0
class ErrorInfo:
"""Information about an exception passed to an on-error handler."""
__allow_access_to_unprotected_subobjects__ = 1
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]
class Engine:
'''Expression Engine
An instance of this class keeps a mutable collection of expression
type handlers. It can compile expression strings by delegating to
these handlers. It can provide an expression Context, which is
capable of holding state and evaluating compiled expressions.
'''
Iterator = Iterator
def __init__(self, Iterator=None):
self.types = {}
if Iterator is not None:
self.Iterator = Iterator
def registerType(self, name, handler):
if not _valid_name(name):
raise RegistrationError, 'Invalid Expression type "%s".' % name
types = self.types
if types.has_key(name):
raise RegistrationError, (
'Multiple registrations for Expression type "%s".' %
name)
types[name] = handler
def getTypes(self):
return self.types
def compile(self, expression):
m = _parse_expr(expression)
if m:
type = m.group(1)
expr = expression[m.end():]
else:
type = "standard"
expr = expression
try:
handler = self.types[type]
except KeyError:
raise CompilerError, (
'Unrecognized expression type "%s".' % type)
return handler(type, expr, self)
def getContext(self, contexts=None, **kwcontexts):
if contexts is not None:
if kwcontexts:
kwcontexts.update(contexts)
else:
kwcontexts = contexts
return Context(self, kwcontexts)
def getCompilerError(self):
return CompilerError
class Context:
'''Expression Context
An instance of this class holds context information that it can
use to evaluate compiled expressions.
'''
_context_class = SafeMapping
position = (None, None)
source_file = None
def __init__(self, compiler, contexts):
self._compiler = compiler
self.contexts = contexts
contexts['nothing'] = None
contexts['default'] = Default
self.repeat_vars = rv = {}
# Wrap this, as it is visible to restricted code
contexts['repeat'] = rep = self._context_class(rv)
contexts['loop'] = rep # alias
self.global_vars = gv = contexts.copy()
self.local_vars = lv = {}
self.vars = self._context_class(gv, lv)
# Keep track of what needs to be popped as each scope ends.
self._scope_stack = []
def getCompiler(self):
return self._compiler
def beginScope(self):
self._scope_stack.append([self.local_vars.copy()])
def endScope(self):
scope = self._scope_stack.pop()
self.local_vars = lv = scope[0]
v = self.vars
v._pop()
v._push(lv)
# Pop repeat variables, if any
i = len(scope) - 1
while i:
name, value = scope[i]
if value is None:
del self.repeat_vars[name]
else:
self.repeat_vars[name] = value
i = i - 1
def setLocal(self, name, value):
self.local_vars[name] = value
def setGlobal(self, name, value):
self.global_vars[name] = value
def setRepeat(self, name, expr):
expr = self.evaluate(expr)
if not expr:
return self._compiler.Iterator(name, (), self)
it = self._compiler.Iterator(name, expr, self)
old_value = self.repeat_vars.get(name)
self._scope_stack[-1].append((name, old_value))
self.repeat_vars[name] = it
return it
def evaluate(self, expression,
isinstance=isinstance, StringType=StringType):
if isinstance(expression, StringType):
expression = self._compiler.compile(expression)
__traceback_supplement__ = (
TALESTracebackSupplement, self, expression)
return expression(self)
evaluateValue = evaluate
evaluateBoolean = evaluate
def evaluateText(self, expr):
text = self.evaluate(expr)
if text is Default or text is None:
return text
return ustr(text)
def evaluateStructure(self, expr):
return self.evaluate(expr)
evaluateStructure = evaluate
def evaluateMacro(self, expr):
# XXX Should return None or a macro definition
return self.evaluate(expr)
evaluateMacro = evaluate
def createErrorInfo(self, err, position):
return ErrorInfo(err, position)
def getDefault(self):
return Default
def setSourceFile(self, source_file):
self.source_file = source_file
def setPosition(self, position):
self.position = position
def translate(self, domain, msgid, mapping=None,
context=None, target_language=None, default=None):
if context is None:
context = self.contexts.get('here')
return getGlobalTranslationService().translate(
domain, msgid, mapping=mapping,
context=context,
default=default,
target_language=target_language)
def getValue(self, name, default=None):
return self.vars.get(name, default)
class TALESTracebackSupplement:
"""Implementation of ITracebackSupplement"""
def __init__(self, context, expression):
self.context = context
self.source_url = context.source_file
self.line = context.position[0]
self.column = context.position[1]
self.expression = repr(expression)
def getInfo(self, as_html=0):
import pprint
from cgi import escape
data = self.context.contexts.copy()
try:
s = pprint.pformat(data)
except Unauthorized, e:
s = ' - %s: %s' % (getattr(e, '__class__', type(e)), e)
if as_html:
s = escape(s)
return s
if not as_html:
return ' - Names:\n %s' % s.replace('\n', '\n ')
else:
return '<b>Names:</b><pre>%s</pre>' % (escape(s))
class SimpleExpr:
'''Simple example of an expression type handler'''
def __init__(self, name, expr, engine):
self._name = name
self._expr = expr
def __call__(self, econtext):
return self._name, self._expr
def __repr__(self):
return '<SimpleExpr %s %s>' % (self._name, `self._expr`)
import zope.deprecation
zope.deprecation.moved("zope.tales.tales", "2.12")
##############################################################################
#
# 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
#
##############################################################################
"""Old Zope-specific Python Expression Handler
Handler for Python expressions, using the pre-Python 2.1 restriction
machinery from PythonScripts.
"""
__version__='$Revision: 1.8 $'[11:-2]
from AccessControl import getSecurityManager
from Products.PythonScripts.Guarded import _marker, \
GuardedBlock, theGuard, safebin, WriteGuard, ReadGuard, UntupleFunction
from TALES import CompilerError
from PythonExpr import PythonExpr
class PythonExpr(PythonExpr):
def __init__(self, name, expr, engine):
self.expr = expr = expr.strip().replace('\n', ' ')
blk = GuardedBlock('def f():\n return \\\n %s\n' % expr)
if blk.errors:
raise CompilerError, ('Python expression error:\n%s' %
'\n'.join(blk.errors) )
guards = {'$guard': theGuard, '$write_guard': WriteGuard,
'$read_guard': ReadGuard, '__debug__': __debug__}
self._f = UntupleFunction(blk.t, guards, __builtins__=safebin)
self._get_used_names()
class _SecureModuleImporter:
__allow_access_to_unprotected_subobjects__ = 1
def __getitem__(self, module):
mod = safebin['__import__'](module)
path = module.split('.')
for name in path[1:]:
mod = getattr(mod, name)
return mod
from DocumentTemplate.DT_Util import TemplateDict, InstanceDict
def validate(accessed, container, name, value, dummy):
return getSecurityManager().validate(accessed, container, name, value)
def call_with_ns(f, ns, arg=1):
td = TemplateDict()
td.validate = validate
td.this = ns['here']
td._push(ns['request'])
td._push(InstanceDict(td.this, td))
td._push(ns)
try:
if arg==2:
return f(None, td)
else:
return f(td)
finally:
td._pop(3)
......@@ -10,20 +10,17 @@
# FOR A PARTICULAR PURPOSE
#
##############################################################################
"""Zope-specific Python Expression Handler
Handler for Python expressions that uses the RestrictedPython package.
"""
__version__='$Revision: 1.11 $'[11:-2]
$Id$
"""
from AccessControl import safe_builtins
from AccessControl.ZopeGuards import guarded_getattr, get_safe_globals
from RestrictedPython import compile_restricted_eval
from TALES import CompilerError
from PythonExpr import PythonExpr
from zope.tales.tales import CompilerError
from zope.tales.pythonexpr import PythonExpr
class PythonExpr(PythonExpr):
_globals = get_safe_globals()
......@@ -31,23 +28,23 @@ class PythonExpr(PythonExpr):
_globals['__debug__' ] = __debug__
def __init__(self, name, expr, engine):
self.expr = expr = expr.strip().replace('\n', ' ')
code, err, warn, use = compile_restricted_eval(expr, str(self))
self.text = text = expr.strip().replace('\n', ' ')
code, err, warn, use = compile_restricted_eval(text, str(self))
if err:
raise CompilerError, ('Python expression error:\n%s' %
'\n'.join(err) )
self._f_varnames = use.keys()
raise engine.getCompilerError()('Python expression error:\n%s' %
'\n'.join(err))
self._varnames = use.keys()
self._code = code
def __call__(self, econtext):
__traceback_info__ = self.expr
code = self._code
g = self._bind_used_names(econtext)
g.update(self._globals)
return eval(code, g, {})
__traceback_info__ = self.text
vars = self._bind_used_names(econtext, {})
vars.update(self._globals)
return eval(self._code, vars, {})
class _SecureModuleImporter:
__allow_access_to_unprotected_subobjects__ = 1
__allow_access_to_unprotected_subobjects__ = True
def __getitem__(self, module):
mod = safe_builtins['__import__'](module)
path = module.split('.')
......
......@@ -10,58 +10,56 @@
# FOR A PARTICULAR PURPOSE
#
##############################################################################
"""Zope Page Template module (wrapper for the Zope 3 ZPT implementation)
""" Zope Page Template module (wrapper for the Zope 3 ZPT implementation) """
__version__='$Revision: 1.48 $'[11:-2]
$Id$
"""
import re
import os
import Acquisition
from Globals import ImageFile, package_home, InitializeClass
from OFS.SimpleItem import SimpleItem
from zope.contenttype import guess_content_type
from DateTime.DateTime import DateTime
from Shared.DC.Scripts.Script import Script
from Shared.DC.Scripts.Signature import FuncCode
from OFS.SimpleItem import SimpleItem
from OFS.History import Historical, html_diff
from OFS.Cache import Cacheable
from OFS.Traversable import Traversable
from OFS.PropertyManager import PropertyManager
from AccessControl import getSecurityManager, safe_builtins, ClassSecurityInfo
from AccessControl.Permissions import view, ftp_access, change_page_templates, view_management_screens
from AccessControl.Permissions import view, ftp_access, change_page_templates
from AccessControl.Permissions import view_management_screens
from webdav.Lockable import ResourceLockedError
from webdav.WriteLockInterface import WriteLockInterface
from zope.pagetemplate.pagetemplate import PageTemplate
from zope.pagetemplate.pagetemplatefile import sniff_type
from Products.PageTemplates.Expressions import getEngine
from Products.PageTemplates.PageTemplate import PageTemplate
from Products.PageTemplates.PageTemplateFile import PageTemplateFile
from Products.PageTemplates.PageTemplateFile import guess_type
from Products.PageTemplates.Expressions import SecureModuleImporter
# regular expression to extract the encoding from the XML preamble
encoding_reg= re.compile('<\?xml.*?encoding="(.*?)".*?\?>', re.M)
encoding_reg = re.compile('<\?xml.*?encoding="(.*?)".*?\?>', re.M)
preferred_encodings = ['utf-8', 'iso-8859-15']
if os.environ.has_key('ZPT_PREFERRED_ENCODING'):
preferred_encodings.insert(0, os.environ['ZPT_PREFERRED_ENCODING'])
class SecureModuleImporter:
__allow_access_to_unprotected_subobjects__ = 1
def __getitem__(self, module):
mod = safe_builtins['__import__'](module)
path = module.split('.')
for name in path[1:]:
mod = getattr(mod, name)
return mod
def sniffEncoding(text, default_encoding='utf-8'):
"""Try to determine the encoding from html or xml"""
if text.startswith('<?xml'):
mo = encoding_reg.search(text)
if mo:
return mo.group(1)
return default_encoding
class Src(Acquisition.Explicit):
""" I am scary code """
index_html = None
PUT = document_src = Acquisition.Acquired
index_html = None
def __before_publishing_traverse__(self, ob, request):
if getattr(request, '_hacked_path', 0):
......@@ -71,32 +69,9 @@ class Src(Acquisition.Explicit):
" "
return self.document_src(REQUEST)
def sniffEncoding(text, default_encoding='utf-8'):
""" try to determine the encoding from html or xml """
if text.startswith('<?xml'):
mo = encoding_reg.search(text)
if mo:
return mo.group(1)
return default_encoding
def guess_type(filename, text):
content_type, dummy = guess_content_type(filename, text)
if content_type in ('text/html', 'text/xml'):
return content_type
return sniff_type(text) or 'text/html'
_default_content_fn = os.path.join(package_home(globals()), 'pt', 'default.html')
class ZopePageTemplate(Script, PageTemplate, Historical, Cacheable,
Traversable, PropertyManager):
""" Z2 wrapper class for Zope 3 page templates """
"Zope wrapper for Page Template using TAL, TALES, and METAL"
__implements__ = (WriteLockInterface,)
......@@ -106,7 +81,8 @@ class ZopePageTemplate(Script, PageTemplate, Historical, Cacheable,
func_code = FuncCode((), 0)
_default_bindings = {'name_subpath': 'traverse_subpath'}
_default_content_fn = os.path.join(package_home(globals()), 'www', 'default.html')
_default_content_fn = os.path.join(package_home(globals()),
'www', 'default.html')
manage_options = (
{'label':'Edit', 'action':'pt_editForm',
......@@ -125,35 +101,39 @@ class ZopePageTemplate(Script, PageTemplate, Historical, Cacheable,
security = ClassSecurityInfo()
security.declareObjectProtected(view)
# protect methods from base class(es)
security.declareProtected(view, '__call__')
security.declareProtected(view_management_screens,
'read', 'ZScriptHTML_tryForm')
def __init__(self, id, text=None, content_type=None, encoding='utf-8', strict=False):
def __init__(self, id, text=None, content_type=None, encoding='utf-8',
strict=False):
self.id = id
self.expand = 0
self.strict = strict
self.ZBindings_edit(self._default_bindings)
if not text:
text = open(self._default_content_fn).read()
encoding = 'utf-8'
content_type = 'text/html'
self.pt_edit(text, content_type, encoding)
def pt_render(self, namespace, source=False, sourceAnnotations=False,
showtal=False):
if namespace is None:
namespace = self.pt_getContext()
return super(ZopePageTemplate, self).pt_render(namespace, source, sourceAnnotations,
showtal)
def pt_getEngine(self):
return getEngine()
security.declareProtected(change_page_templates, 'pt_edit')
def pt_edit(self, text, content_type, encoding='utf-8'):
text = text.strip()
if self.strict and not isinstance(text, unicode):
text = unicode(text, encoding)
self.ZCacheable_invalidate()
PageTemplate.pt_edit(self, text, content_type)
super(ZopePageTemplate, self).pt_edit(text, content_type)
pt_editForm = PageTemplateFile('www/ptEdit', globals(),
__name__='pt_editForm')
pt_editForm._owner = None
manage = manage_main = pt_editForm
source_dot_xml = Src()
security.declareProtected(change_page_templates, 'pt_editAction')
def pt_editAction(self, REQUEST, title, text, content_type, encoding, expand):
......@@ -174,20 +154,17 @@ class ZopePageTemplate(Script, PageTemplate, Historical, Cacheable,
% '<br>'.join(self._v_warnings))
return self.pt_editForm(manage_tabs_message=message)
security.declareProtected(change_page_templates, 'pt_setTitle')
def pt_setTitle(self, title, encoding='utf-8'):
if self.strict and not isinstance(title, unicode):
title = unicode(title, encoding)
self._setPropValue('title', title)
def _setPropValue(self, id, value):
""" set a property and invalidate the cache """
PropertyManager._setPropValue(self, id, value)
self.ZCacheable_invalidate()
security.declareProtected(change_page_templates, 'pt_upload')
def pt_upload(self, REQUEST, file='', encoding='utf-8'):
"""Replace the document with the text in file."""
......@@ -239,12 +216,12 @@ class ZopePageTemplate(Script, PageTemplate, Historical, Cacheable,
"""Parameters to test the script with."""
return []
# def manage_historyCompare(self, rev1, rev2, REQUEST,
# historyComparisonResults=''):
# return ZopePageTemplate.inheritedAttribute(
# 'manage_historyCompare')(
# self, rev1, rev2, REQUEST,
# historyComparisonResults=html_diff(rev1._text, rev2._text) )
def manage_historyCompare(self, rev1, rev2, REQUEST,
historyComparisonResults=''):
return ZopePageTemplate.inheritedAttribute(
'manage_historyCompare')(
self, rev1, rev2, REQUEST,
historyComparisonResults=html_diff(rev1._text, rev2._text) )
def pt_getContext(self, *args, **kw):
root = self.getPhysicalRoot()
......@@ -257,12 +234,13 @@ class ZopePageTemplate(Script, PageTemplate, Historical, Cacheable,
'options': {},
'root': root,
'request': getattr(root, 'REQUEST', None),
'modules': SecureModuleImporter(),
'modules': SecureModuleImporter,
}
return c
security.declareProtected(view_management_screens, 'read',
'ZScriptHTML_tryForm')
def write(self, text):
self.ZCacheable_invalidate()
ZopePageTemplate.inheritedAttribute('write')(self, text)
def _exec(self, bound_names, args, kw):
"""Call a Page Template"""
......@@ -295,11 +273,7 @@ class ZopePageTemplate(Script, PageTemplate, Historical, Cacheable,
security.addContext(self)
try:
# XXX: check the parameters for pt_render()! (aj)
result = self.pt_render(self.pt_getContext())
# result = self.pt_render(extra_context=bound_names)
result = self.pt_render(extra_context=bound_names)
if keyset is not None:
# Store the result in the cache.
self.ZCacheable_set(result, keywords=keyset)
......@@ -316,7 +290,7 @@ class ZopePageTemplate(Script, PageTemplate, Historical, Cacheable,
""" Handle HTTP PUT requests """
self.dav__init(REQUEST, RESPONSE)
self.dav__simpleifhandler(REQUEST, RESPONSE, refresh=1)
## XXX:this should be unicode or we must pass an encoding
## XXX this should be unicode or we must pass an encoding
self.pt_edit(REQUEST.get('BODY', ''))
RESPONSE.setStatus(204)
return RESPONSE
......@@ -325,7 +299,6 @@ class ZopePageTemplate(Script, PageTemplate, Historical, Cacheable,
manage_FTPput = PUT
security.declareProtected(ftp_access, 'manage_FTPstat','manage_FTPlist')
security.declareProtected(ftp_access, 'manage_FTPget')
def manage_FTPget(self):
"Get source for FTP download"
......@@ -369,7 +342,6 @@ class ZopePageTemplate(Script, PageTemplate, Historical, Cacheable,
'title': 'This template has an error'},)
return icons
security.declareProtected(view, 'pt_source_file')
def pt_source_file(self):
"""Returns a file name to be compiled into the TAL code."""
......@@ -383,24 +355,17 @@ class ZopePageTemplate(Script, PageTemplate, Historical, Cacheable,
def wl_isLocked(self):
return 0
security.declareProtected(view, 'strictUnicode')
def strictUnicode(self):
""" Return True if the ZPT enforces the use of unicode,
False otherwise.
"""
return self.strict
def manage_convertUnicode(self, preferred_encodings=preferred_encodings, RESPONSE=None):
""" convert non-unicode templates to unicode """
def manage_convertUnicode(self, preferred_encodings=preferred_encodings,
RESPONSE=None):
"""Convert non-unicode templates to unicode"""
if not isinstance(self._text, unicode):
for encoding in preferred_encodings:
try:
self._text = unicode(self._text, encoding)
if RESPONSE:
return RESPONSE.redirect(self.absolute_url() + '/pt_editForm?manage_tabs_message=ZPT+successfully+converted')
return RESPONSE.redirect(self.absolute_url() +
'/pt_editForm?manage_tabs_message='
'ZPT+successfully+converted')
else:
return
except UnicodeDecodeError:
......@@ -410,49 +375,23 @@ class ZopePageTemplate(Script, PageTemplate, Historical, Cacheable,
else:
if RESPONSE:
return RESPONSE.redirect(self.absolute_url() + '/pt_editForm?manage_tabs_message=ZPT+already+converted')
return RESPONSE.redirect(self.absolute_url() +
'/pt_editForm?manage_tabs_message='
'ZPT+already+converted')
else:
return
security.declareProtected(view_management_screens, 'getSource')
getSource = Src()
source_dot_xml = Src()
InitializeClass(ZopePageTemplate)
setattr(ZopePageTemplate, 'source.xml', ZopePageTemplate.source_dot_xml)
setattr(ZopePageTemplate, 'source.html', ZopePageTemplate.source_dot_xml)
# Product registration and Add support
manage_addPageTemplateForm = PageTemplateFile(
'www/ptAdd', globals(), __name__='manage_addPageTemplateForm')
def _newZPT(id, filename):
""" factory to generate ZPT instances from the file-system
based templates (basically for internal purposes)
"""
zpt = ZopePageTemplate(id, open(filename).read(), 'text/html')
zpt.__name__= id
return zpt
class FSZPT(ZopePageTemplate):
""" factory to generate ZPT instances from the file-system
based templates (basically for internal purposes)
"""
def __init__(self, id, filename):
ZopePageTemplate.__init__(self, id, open(filename).read(), 'text/html')
self.__name__= id
InitializeClass(FSZPT)
ZopePageTemplate.pt_editForm = FSZPT('pt_editForm', os.path.join(package_home(globals()),'pt', 'ptEdit.pt'))
# this is scary, do we need this?
ZopePageTemplate.manage = ZopePageTemplate.pt_editForm
manage_addPageTemplateForm= FSZPT('manage_addPageTemplateForm', os.path.join(package_home(globals()), 'pt', 'ptAdd.pt'))
def manage_addPageTemplate(self, id, title='', text='', encoding='utf-8', submit=None, REQUEST=None, RESPONSE=None):
def manage_addPageTemplate(self, id, title='', text='', encoding='utf-8',
submit=None, REQUEST=None, RESPONSE=None):
"Add a Page Template with optional file content."
filename = ''
......@@ -480,11 +419,6 @@ def manage_addPageTemplate(self, id, title='', text='', encoding='utf-8', submit
content_type = guess_type(filename, text)
encoding = sniffEncoding(text, encoding)
if not text:
text = open(_default_content_fn).read()
encoding = 'utf-8'
content_type = 'text/html'
zpt = ZopePageTemplate(id, text, content_type, encoding)
zpt.pt_setTitle(title, encoding)
self._setObject(id, zpt)
......@@ -499,7 +433,7 @@ def manage_addPageTemplate(self, id, title='', text='', encoding='utf-8', submit
return zpt
from Products.PageTemplates import misc_
misc_['exclamation.gif'] = ImageFile('pt/exclamation.gif', globals())
misc_['exclamation.gif'] = ImageFile('www/exclamation.gif', globals())
def initialize(context):
context.registerClass(
......@@ -507,7 +441,7 @@ def initialize(context):
permission='Add Page Templates',
constructors=(manage_addPageTemplateForm,
manage_addPageTemplate),
icon='pt/zpt.gif',
icon='www/zpt.gif',
)
context.registerHelp()
context.registerHelpTitle('Zope Help')
......
......@@ -10,14 +10,13 @@
# FOR A PARTICULAR PURPOSE
#
##############################################################################
__doc__='''Package wrapper for Page Templates
"""Package wrapper for Page Templates
This wrapper allows the Page Template modules to be segregated in a
separate package.
$Id$'''
__version__='$$'[11:-2]
$Id$
"""
# Placeholder for Zope Product data
misc_ = {}
......
<html>
<head>
<title tal:content="template/title">The title</title>
</head>
<body>
<h2><span tal:replace="here/title_or_id">content title or id</span>
<span tal:condition="template/title"
tal:replace="template/title">optional template title</span></h2>
This is Page Template <em tal:content="template/id">template id</em>.
</body>
</html>
<h1 tal:replace="structure here/manage_page_header">Header</h1>
<h2 tal:define="form_title string:Add Page Template"
tal:replace="structure here/manage_form_title">Form Title</h2>
<p class="form-help">
Page Templates allow you to use simple HTML or XML attributes to
create dynamic templates. You may choose to upload the template text
from a local file by typing the file name or using the <em>browse</em>
button.
</p>
<form action="manage_addPageTemplate" method="post"
enctype="multipart/form-data">
<table cellspacing="0" cellpadding="2" border="0">
<tr>
<td align="left" valign="top">
<div class="form-label">
Id
</div>
</td>
<td align="left" valign="top">
<input type="text" name="id" size="40" />
</td>
</tr>
<tr>
<td align="left" valign="top">
<div class="form-optional">
File
</div>
</td>
<td align="left" valign="top">
<input type="file" name="file" size="25" value="" />
</td>
</tr>
<tr>
<td align="left" valign="top">
<div class="form-optional">
Encoding
</div>
</td>
<td align="left" valign="top">
<input type="text" name="encoding" size="25" value="utf-8" />
<em>(only used for non-XML and non-HTML content)</em>
</td>
</tr>
<tr>
<td align="left" valign="top">
</td>
<td align="left" valign="top">
<div class="form-element">
<input class="form-element" type="submit" name="submit"
value=" Add " />
<input class="form-element" type="submit" name="submit"
value=" Add and Edit " />
</div>
</td>
</tr>
</table>
</form>
<h1 tal:replace="structure here/manage_page_footer">Footer</h1>
<h1 tal:replace="structure python: context.manage_page_header(management_page_charset='utf-8')">Header</h1>
<h2 tal:define="manage_tabs_message options/manage_tabs_message | nothing"
tal:replace="structure context/manage_tabs">Tabs</h2>
<tal:block define="global body request/other/text | request/form/text
| context/read" />
<form action="" method="post" tal:attributes="action request/URL1">
<input type="hidden" name=":default_method" value="pt_changePrefs">
<input type="hidden" name="encoding" value="utf-8"/>
<table width="100%" cellspacing="0" cellpadding="2" border="0">
<tr>
<td align="left" valign="middle">
<div class="form-optional">
Title
</div>
</td>
<td align="left" valign="middle">
<input type="text" name="title" size="40"
tal:attributes="value request/title | context/title" />
</td>
<td align="left" valign="middle">
<div class="form-label"> Last Modified </div>
</td>
<td align="left" valign="middle">
<div class="form-text"
tal:content="python:context.bobobase_modification_time().strftime('%Y-%m-%d %I:%M %p')">1/1/2000
</div>
</td>
</tr>
<tr>
<td align="left" valign="middle">
<div class="form-label">
Content-Type
</div>
</td>
<td align="left" valign="middle">
<select name="content_type" size="1" tal:define="ct context/content_type">
<option value="text/html" tal:attributes="SELECTED python: ct == 'text/html'">text/html</option>
<option value="text/xml" tal:attributes="SELECTED python: ct == 'text/xml'">text/xml</option>
</select>
</td>
<td align="left" valign="top" colspan=2>
<a href="source.html" tal:condition="context/html">Browse HTML source</a>
<a href="source.xml" tal:condition="not:context/html">Browse XML source</a>
<br>
<input type="hidden" name="expand:int:default" value="0">
<input type="checkbox" value="1" name="expand:int"
tal:attributes="checked request/expand | context/expand">
Expand macros when editing
</td>
</tr>
<!-- XXX: check if 'None' is a proper argument for 'namespace' -->
<tr tal:define="errors python: context.pt_errors(None)" tal:condition="errors">
<tal:block define="global body python:context.document_src({'raw':1})"/>
<td align="left" valign="middle" class="form-label">Errors</td>
<td align="left" valign="middle" style="background-color: #FFDDDD"
colspan="3">
<pre tal:content="python: '\n'.join(errors)">errors</pre>
</td>
</tr>
<tr tal:define="warnings context/pt_warnings" tal:condition="warnings">
<td align="left" valign="middle" class="form-label">Warnings</td>
<td align="left" valign="middle" style="background-color: #FFEEDD"
colspan="3">
<pre tal:content="python: '\n'.join(warnings)">errors</pre>
</td>
</tr>
<tr>
<td align="left" valign="top" colspan="4"
tal:define="width request/dtpref_cols | string:100%;
relative_width python:str(width).endswith('%')">
<textarea name="text:text" wrap="off" style="width: 100%;" rows="20"
tal:condition="relative_width"
tal:attributes="style string:width: $width;;;
rows request/dtpref_rows | default"
tal:content="body">Template Body</textarea>
<textarea name="text:text" wrap="off" rows="20" cols="50"
tal:condition="not:relative_width"
tal:attributes="cols width; rows request/dtpref_rows | default"
tal:content="body">Template Body</textarea>
</td>
</tr>
<tr>
<td align="left" valign="top" colspan="4">
<div class="form-element">
<em tal:condition="context/wl_isLocked">Locked by WebDAV</em>
<input tal:condition="not:context/wl_isLocked"
class="form-element" type="submit"
name="pt_editAction:method" value="Save Changes">
&nbsp;&nbsp;
<input class="form-element" type="submit" name="height" value="Taller">
<input class="form-element" type="submit" name="height" value="Shorter">
<input class="form-element" type="submit" name="width" value="Wider">
<input class="form-element" type="submit" name="width" value="Narrower">
</div>
</td>
</tr>
</table>
</form>
<p class="form-help">
You can upload the text for <span tal:replace="context/title_and_id" />
using the following form.
Choose an existing HTML or XML file from your local computer by clicking
<em>browse</em>. You can also <a href="document_src">click context</a>
to view or download the current text.
</p>
<form action="pt_upload" method="post"
enctype="multipart/form-data">
<table cellpadding="2" cellspacing="0" border="0">
<tr>
<td align="left" valign="top">
<div class="form-label">
File &nbsp;
</div>
</td>
<td align="left" valign="top">
<input type="file" name="file" size="40" value="">
</td>
</tr>
<tr>
<td align="left" valign="top">
<div class="form-label">
Encoding &nbsp;
</div>
</td>
<td align="left" valign="top" colspan="2">
<input name="encoding" value="utf-8"/>
</td>
<td align="left" valign="top" colspan="1">
<em>(only used for non-XML and non-XHTML content)</em>
</td>
</tr>
<tr>
<td></td>
<td align="left" valign="top">
<div class="form-element">
<em tal:condition="context/wl_isLocked">Locked by WebDAV</em>
<input tal:condition="not:context/wl_isLocked"
class="form-element" type="submit" value="Upload File">
</div>
</td>
</tr>
</table>
</form>
<h1 tal:replace="structure context/manage_page_footer">Footer</h1>
def all():
import testTALES
return testTALES.test_suite()
class harness1:
def __init__(self):
self.__callstack = []
def _assert_(self, name, *args, **kwargs):
self.__callstack.append((name, args, kwargs))
def _complete_(self):
assert len(self.__callstack) == 0, "Harness methods called"
def __getattr__(self, name):
cs = self.__callstack
assert len(cs), 'Unexpected harness method call "%s".' % name
assert cs[0][0] == name, (
'Harness method name "%s" called, "%s" expected.' %
(name, cs[0][0]) )
return self._method_
def _method_(self, *args, **kwargs):
name, aargs, akwargs = self.__callstack.pop(0)
assert aargs == args, "Harness method arguments"
assert akwargs == kwargs, "Harness method keyword args"
class harness2(harness1):
def _assert_(self, name, result, *args, **kwargs):
self.__callstack.append((name, result, args, kwargs))
def _method_(self, *args, **kwargs):
name, result, aargs, akwargs = self.__callstack.pop(0)
assert aargs == args, "Harness method arguments"
assert akwargs == kwargs, "Harness method keyword args"
return result
# make this directory a package
<p tal:define="now modules/DateTime/DateTime" tal:content="now/isCurrentYear" />
......@@ -4,7 +4,7 @@
<p>[foo](bar/{})</p>
<a href="foo" alt="[default](alttext/{})">link</a>
<p>[dom](${name} was born in ${country}./{'country':'Antarctica','name':'Lomax'})</p>
<p>[default](hmm/{'age':'25'})</p>
<p>[default](hmm/{'age':u'25'})</p>
</head>
</body>
</html>
......@@ -11,8 +11,10 @@
#
##############################################################################
import os, sys, unittest
import unittest
import zope.component.testing
from zope.traversing.adapters import DefaultTraversable
from Products.PageTemplates.tests import util
from Products.PageTemplates.PageTemplate import PageTemplate
from Acquisition import Implicit
......@@ -43,16 +45,20 @@ class UnitTestSecurityPolicy:
def checkPermission( self, permission, object, context) :
return 1
class DTMLTests(unittest.TestCase):
class DTMLTests(zope.component.testing.PlacelessSetup, unittest.TestCase):
def setUp(self):
self.t=(AqPageTemplate())
super(DTMLTests, self).setUp()
zope.component.provideAdapter(DefaultTraversable, (None,))
self.t = AqPageTemplate()
self.policy = UnitTestSecurityPolicy()
self.oldPolicy = SecurityManager.setSecurityPolicy( self.policy )
self.oldPolicy = SecurityManager.setSecurityPolicy(self.policy)
noSecurityManager() # Use the new policy.
def tearDown(self):
SecurityManager.setSecurityPolicy( self.oldPolicy )
super(DTMLTests, self).tearDown()
SecurityManager.setSecurityPolicy(self.oldPolicy)
noSecurityManager() # Reset to old policy.
def check1(self):
......
import os, sys, unittest
import unittest
import zope.component.testing
from zope.traversing.adapters import DefaultTraversable
from Products.PageTemplates import Expressions
from Products.PageTemplates.DeferExpr import LazyWrapper
......@@ -9,9 +12,12 @@ class Dummy:
def __call__(self):
return 'dummy'
class ExpressionTests(unittest.TestCase):
class ExpressionTests(zope.component.testing.PlacelessSetup, unittest.TestCase):
def setUp(self):
super(ExpressionTests, self).setUp()
zope.component.provideAdapter(DefaultTraversable, (None,))
self.e = e = Expressions.getEngine()
self.ec = e.getContext(
one = 1,
......@@ -20,9 +26,6 @@ class ExpressionTests(unittest.TestCase):
dummy = Dummy()
)
def tearDown(self):
del self.e, self.ec
def testCompile(self):
'''Test expression compilation'''
e = self.e
......@@ -50,9 +53,11 @@ class ExpressionTests(unittest.TestCase):
'''Test advanced expression evaluation 1'''
ec = self.ec
assert ec.evaluate('x | nothing') is None
assert ec.evaluate('d/') == 'blank'
# empty path elements aren't supported anymore, for the lack
# of a use case
#assert ec.evaluate('d/') == 'blank'
assert ec.evaluate('d/_') == 'under'
assert ec.evaluate('d/ | nothing') == 'blank'
#assert ec.evaluate('d/ | nothing') == 'blank'
assert ec.evaluate('d/?blank') == 'blank'
def testHybrid(self):
......@@ -63,7 +68,7 @@ class ExpressionTests(unittest.TestCase):
assert ec.evaluate('x | string:x') == 'x'
assert ec.evaluate('x | string:$one') == '1'
assert ec.evaluate('x | not:exists:x')
def testWrappers(self):
"""Test if defer and lazy are returning their wrappers
"""
......
......@@ -11,8 +11,10 @@
#
##############################################################################
import os, sys, unittest
import unittest
import zope.component.testing
from zope.traversing.adapters import DefaultTraversable
from Products.PageTemplates.tests import util
from Products.PageTemplates.PageTemplate import PageTemplate
from Products.PageTemplates.GlobalTranslationService import \
......@@ -59,9 +61,12 @@ class UnitTestSecurityPolicy:
def checkPermission( self, permission, object, context) :
return 1
class HTMLTests(unittest.TestCase):
class HTMLTests(zope.component.testing.PlacelessSetup, unittest.TestCase):
def setUp(self):
super(HTMLTests, self).setUp()
zope.component.provideAdapter(DefaultTraversable, (None,))
self.folder = f = Folder()
f.laf = AqPageTemplate()
f.t = AqPageTemplate()
......@@ -70,6 +75,7 @@ class HTMLTests(unittest.TestCase):
noSecurityManager() # Use the new policy.
def tearDown(self):
super(HTMLTests, self).tearDown()
SecurityManager.setSecurityPolicy( self.oldPolicy )
noSecurityManager() # Reset to old policy.
......@@ -156,6 +162,9 @@ class HTMLTests(unittest.TestCase):
self.assert_expected(self.folder.t, 'CheckI18nTranslateHooked.html')
setGlobalTranslationService(old_ts)
def checkImportOldStyleClass(self):
self.assert_expected(self.folder.t, 'CheckImportOldStyleClass.html')
def test_suite():
return unittest.makeSuite(HTMLTests, 'check')
......
import os, sys, unittest
import unittest
# BBB 2005/05/01 -- to be changed after 12 months
# ignore deprecation warnings on import for now
import warnings
showwarning = warnings.showwarning
warnings.showwarning = lambda *a, **k: None
# this old import should remain here until the TALES.py module is
# completely removed, so that API backward compatibility is properly
# tested
from Products.PageTemplates import TALES
from Products.PageTemplates.tests import harness1
import string
# restore warning machinery
warnings.showwarning = showwarning
from zope.tales.tests.test_tales import Harness
class DummyUnicodeExpr:
'''Dummy expression type handler returning unicode'''
......@@ -18,14 +28,14 @@ class TALESTests(unittest.TestCase):
def testIterator0(self):
'''Test sample Iterator class'''
context = harness1()
context = Harness(self)
it = TALES.Iterator('name', (), context)
assert not it.next(), "Empty iterator"
context._complete_()
def testIterator1(self):
'''Test sample Iterator class'''
context = harness1()
context = Harness(self)
it = TALES.Iterator('name', (1,), context)
context._assert_('setLocal', 'name', 1)
assert it.next() and not it.next(), "Single-element iterator"
......@@ -33,7 +43,7 @@ class TALESTests(unittest.TestCase):
def testIterator2(self):
'''Test sample Iterator class'''
context = harness1()
context = Harness(self)
it = TALES.Iterator('text', 'text', context)
for c in 'text':
context._assert_('setLocal', 'text', c)
......@@ -104,23 +114,25 @@ class TALESTests(unittest.TestCase):
def testVariables(self):
'''Test variables'''
ctxt = self.getContext()
c = ctxt.vars
ctxt.beginScope()
ctxt.setLocal('v1', 1)
ctxt.setLocal('v2', 2)
c = ctxt.vars
assert c['v1'] == 1, 'Variable "v1"'
ctxt.beginScope()
ctxt.setLocal('v1', 3)
ctxt.setGlobal('g', 1)
c = ctxt.vars
assert c['v1'] == 3, 'Inner scope'
assert c['v2'] == 2, 'Outer scope'
assert c['g'] == 1, 'Global'
ctxt.endScope()
c = ctxt.vars
assert c['v1'] == 1, "Uncovered local"
assert c['g'] == 1, "Global from inner scope"
......
......@@ -10,9 +10,9 @@ Note: Tests require Zope >= 2.7
import unittest
import Zope2
import transaction
import zope.component.testing
from zope.traversing.adapters import DefaultTraversable
from Testing.makerequest import makerequest
from Products.PageTemplates.ZopePageTemplate import _default_content_fn
class ZPTRegressions(unittest.TestCase):
......@@ -35,7 +35,7 @@ class ZPTRegressions(unittest.TestCase):
def testAddWithoutParams(self):
pt = self._addPT('pt1')
default_text = open(_default_content_fn).read()
default_text = open(pt._default_content_fn).read()
self.assertEqual(pt.title, '')
self.assertEqual(pt.document_src().strip(), default_text.strip())
......@@ -58,8 +58,12 @@ class ZPTRegressions(unittest.TestCase):
pt = self.app.pt1
self.assertEqual(pt.document_src(), self.text)
class ZPTMacros(unittest.TestCase):
class ZPTMacros(zope.component.testing.PlacelessSetup, unittest.TestCase):
def setUp(self):
super(ZPTMacros, self).setUp()
zope.component.provideAdapter(DefaultTraversable, (None,))
transaction.begin()
self.app = makerequest(Zope2.app())
f = self.app.manage_addProduct['PageTemplates'].manage_addPageTemplate
......@@ -86,7 +90,13 @@ class ZPTMacros(unittest.TestCase):
This is in the slot
</p>
</div>
"""
"""
def tearDown(self):
super(ZPTMacros, self).tearDown()
transaction.abort()
self.app._p_jar.close()
def testMacroExpansion(self):
request = self.app.REQUEST
......@@ -98,9 +108,9 @@ class ZPTMacros(unittest.TestCase):
request = self.app.REQUEST
self._addPT('pt1', text=self.text, REQUEST=request)
pt = self.app.pt1
pt.pt_render(None, source=1)
self.assertEqual(pt.pt_errors(None), None)
pt.pt_render(source=True)
self.assertEqual(pt.pt_errors(), None)
class DummyFileUpload:
def __init__(self, data='', filename='', content_type=''):
......
<h1 tal:replace="structure here/manage_page_header">Header</h1>
<h2 tal:define="form_title string:Add Page Template"
tal:replace="structure here/manage_form_title">Form Title</h2>
<p class="form-help">
Page Templates allow you to use simple HTML or XML attributes to
create dynamic templates. You may choose to upload the template text
from a local file by typing the file name or using the <em>browse</em>
button.
</p>
<p class="form-help">Page Templates allow you to use simple HTML or
XML attributes to create dynamic templates. You may choose to upload
the template text from a local file by typing the file name or using
the <em>browse</em> button.</p>
<form action="manage_addPageTemplate" method="post"
enctype="multipart/form-data">
enctype="multipart/form-data">
<table cellspacing="0" cellpadding="2" border="0">
<tr>
<td align="left" valign="top">
<div class="form-label">
Id
</div>
<div class="form-label">Id</div>
</td>
<td align="left" valign="top">
<input type="text" name="id" size="40" />
<input type="text" name="id" size="40" />
</td>
</tr>
<tr>
<td align="left" valign="top">
<div class="form-optional">
File
</div>
<div class="form-optional">File</div>
</td>
<td align="left" valign="top">
<input type="file" name="file" size="25" value="" />
<input type="file" name="file" size="25" value="" />
</td>
</tr>
<tr>
<td align="left" valign="top">
<div class="form-optional">Encoding</div>
</td>
<td align="left" valign="top">
<input type="text" name="encoding" size="25" value="utf-8" />
<em>(only used for non-XML and non-HTML content)</em>
</td>
</tr>
<tr>
<td align="left" valign="top"></td>
<td align="left" valign="top">
<div class="form-element">
<input class="form-element" type="submit" name="submit"
value=" Add " />
<input class="form-element" type="submit" name="submit"
value=" Add and Edit " />
</div>
<div class="form-element">
<input class="form-element" type="submit" name="submit"
value=" Add " />
<input class="form-element" type="submit" name="submit"
value=" Add and Edit " />
</div>
</td>
</tr>
</table>
......
<h1 tal:replace="structure here/manage_page_header">Header</h1>
<h1 tal:replace="structure python:context.manage_page_header(management_page_charset='utf-8')">Header</h1>
<h2 tal:define="manage_tabs_message options/manage_tabs_message | nothing"
tal:replace="structure here/manage_tabs">Tabs</h2>
tal:replace="structure context/manage_tabs">Tabs</h2>
<tal:block define="global body request/other/text | request/form/text
| here/read" />
| context/read" />
<form action="" method="post" tal:attributes="action request/URL1">
<input type="hidden" name=":default_method" value="pt_changePrefs">
<input type="hidden" name=":default_method" value="pt_changePrefs" />
<input type="hidden" name="encoding" value="utf-8" />
<table width="100%" cellspacing="0" cellpadding="2" border="0">
<tr>
<td align="left" valign="middle">
<div class="form-optional">
Title
</div>
<div class="form-optional">Title</div>
</td>
<td align="left" valign="middle">
<input type="text" name="title" size="40"
tal:attributes="value request/title | here/title" />
<input type="text" name="title" size="40"
tal:attributes="value request/title | context/title" />
</td>
<td align="left" valign="middle">
<div class="form-optional">
Content-Type
</div>
<div class="form-label">Content-Type</div>
</td>
<td align="left" valign="middle">
<input type="text" name="content_type" size="14"
tal:attributes="value request/content_type | here/content_type" />
<input type="text" name="content_type" size="14"
tal:attributes="value request/content_type | context/content_type" />
</td>
</tr>
<tr>
<td align="left" valign="middle">
<div class="form-label">
Last Modified
</div>
<div class="form-label">Last Modified</div>
</td>
<td align="left" valign="middle">
<div class="form-text"
tal:content="python:here.bobobase_modification_time().strftime('%Y-%m-%d %I:%M %p')">1/1/2000
</div>
<div class="form-text"
tal:content="python:context.bobobase_modification_time().strftime('%Y-%m-%d %I:%M %p')">1/1/2000
</div>
</td>
<td align="left" valign="top" colspan=2>
<a href="source.html" tal:condition="here/html">Browse HTML source</a>
<a href="source.xml" tal:condition="not:here/html">Browse XML source</a>
<br>
<input type="hidden" name="expand:int:default" value="0">
<td align="left" valign="top" colspan="2">
<a href="source.html" tal:condition="context/html">Browse HTML source</a>
<a href="source.xml" tal:condition="not:context/html">Browse XML source</a>
<br />
<input type="hidden" name="expand:int:default" value="0" />
<input type="checkbox" value="1" name="expand:int"
tal:attributes="checked request/expand | here/expand">
tal:attributes="checked request/expand | context/expand" />
Expand macros when editing
</td>
</tr>
<tr tal:define="errors here/pt_errors" tal:condition="errors">
<tal:block define="global body python:here.document_src({'raw':1})"/>
<tr tal:define="errors context/pt_errors" tal:condition="errors">
<tal:block define="global body python:context.document_src({'raw':1})" />
<td align="left" valign="middle" class="form-label">Errors</td>
<td align="left" valign="middle" style="background-color: #FFDDDD"
colspan="3">
<pre tal:content="python:modules['string'].join(errors, '\n')">errors</pre>
colspan="3">
<pre tal:content="python:'\n'.join(errors)">errors</pre>
</td>
</tr>
<tr tal:define="warnings here/pt_warnings" tal:condition="warnings">
<tr tal:define="warnings context/pt_warnings" tal:condition="warnings">
<td align="left" valign="middle" class="form-label">Warnings</td>
<td align="left" valign="middle" style="background-color: #FFEEDD"
colspan="3">
<pre tal:content="python:modules['string'].join(warnings, '\n')">errors</pre>
colspan="3">
<pre tal:content="python:'\n'.join(warnings)">errors</pre>
</td>
</tr>
<tr>
<td align="left" valign="top" colspan="4"
tal:define="width request/dtpref_cols | string:100%;
relative_width python:str(width).endswith('%')">
tal:define="width request/dtpref_cols | string:100%;
relative_width python:str(width).endswith('%')">
<textarea name="text:text" wrap="off" style="width: 100%;" rows="20"
tal:condition="relative_width"
tal:attributes="style string:width: $width;;;
rows request/dtpref_rows | default"
tal:content="body">Template Body</textarea>
tal:condition="relative_width"
tal:attributes="style string:width: $width;;;
rows request/dtpref_rows | default"
tal:content="body">Template Body</textarea>
<textarea name="text:text" wrap="off" rows="20" cols="50"
tal:condition="not:relative_width"
tal:attributes="cols width; rows request/dtpref_rows | default"
tal:content="body">Template Body</textarea>
tal:condition="not:relative_width"
tal:attributes="cols width; rows request/dtpref_rows | default"
tal:content="body">Template Body</textarea>
</td>
</tr>
<tr>
<td align="left" valign="top" colspan="4">
<div class="form-element">
<em tal:condition="here/wl_isLocked">Locked by WebDAV</em>
<input tal:condition="not:here/wl_isLocked"
class="form-element" type="submit"
name="pt_editAction:method" value="Save Changes">
&nbsp;&nbsp;
<input class="form-element" type="submit" name="height" value="Taller">
<input class="form-element" type="submit" name="height" value="Shorter">
<input class="form-element" type="submit" name="width" value="Wider">
<input class="form-element" type="submit" name="width" value="Narrower">
<em tal:condition="context/wl_isLocked">Locked by WebDAV</em>
<input tal:condition="not:context/wl_isLocked"
class="form-element" type="submit"
name="pt_editAction:method" value="Save Changes">
&nbsp;&nbsp;
<input class="form-element" type="submit" name="height" value="Taller" />
<input class="form-element" type="submit" name="height" value="Shorter" />
<input class="form-element" type="submit" name="width" value="Wider" />
<input class="form-element" type="submit" name="width" value="Narrower" />
</div>
</td>
</tr>
</table>
</form>
<p class="form-help">
You can upload the text for <span tal:replace="here/title_and_id" />
using the following form.
Choose an existing HTML or XML file from your local computer by clicking
<em>browse</em>. You can also <a href="document_src">click here</a>
to view or download the current text.
</p>
<p class="form-help">You can upload the text for
<span tal:replace="context/title_and_id" /> using the following form.
Choose an existing HTML or XML file from your local computer by
clicking <em>browse</em>. You can also <a href="document_src">click
context</a> to view or download the current text.</p>
<form action="pt_upload" method="post"
enctype="multipart/form-data">
<form action="pt_upload" method="post" enctype="multipart/form-data">
<table cellpadding="2" cellspacing="0" border="0">
<tr>
<td align="left" valign="top">
<div class="form-label">
File &nbsp;
</div>
<div class="form-label">File &nbsp;</div>
</td>
<td align="left" valign="top">
<input type="file" name="file" size="25" value="">
<input type="file" name="file" size="40" value="" />
</td>
</tr>
<tr tal:condition="context/management_page_charset|nothing">
<tr>
<td align="left" valign="top">
<div class="form-label">
Encoding &nbsp;
</div>
<div class="form-label">Encoding &nbsp;</div>
</td>
<td align="left" valign="top">
<input name="charset" value=""
tal:attributes="value here/management_page_charset|default" />
<td align="left" valign="top" colspan="2">
<input name="encoding" value="utf-8" />
</td>
<td align="left" valign="top" colspan="1">
<em>(only used for non-XML and non-XHTML content)</em>
</td>
</tr>
<tr>
<td></td>
<td align="left" valign="top">
<div class="form-element">
<em tal:condition="here/wl_isLocked">Locked by WebDAV</em>
<input tal:condition="not:here/wl_isLocked"
class="form-element" type="submit" value="Upload File">
<em tal:condition="context/wl_isLocked">Locked by WebDAV</em>
<input tal:condition="not:context/wl_isLocked"
class="form-element" type="submit" value="Upload File" />
</div>
</td>
</tr>
</table>
</form>
<h1 tal:replace="structure here/manage_page_footer">Footer</h1>
<h1 tal:replace="structure context/manage_page_footer">Footer</h1>
......@@ -13,238 +13,13 @@
##############################################################################
"""
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()
BBB 2005/05/01 -- to be removed after 12 months
"""
import zope.deprecation
zope.deprecation.moved('zope.tal.dummyengine', '2.12')
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)
from zope.tal.dummyengine import DummyTranslationDomain as DummyDomain
class DummyTranslationService:
......
......@@ -13,303 +13,8 @@
##############################################################################
"""
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
BBB 2005/05/01 -- to be removed after 12 months
"""
import zope.deprecation
zope.deprecation.moved('zope.tal.htmltalparser', '2.12')
"""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
import zope.deferredimport
zope.deferredimport.deprecatedFrom(
"The TAL implementation has moved to zope.tal. Import expression "
"interfaces from zope.tal.interfaces. The old references will be "
"gone in Zope 2.12.",
'zope.tal.interfaces'
'ITALExpressionCompiler', 'ITALExpressionEngine', 'ITALExpressionErrorInfo'
)
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.")
......@@ -13,179 +13,16 @@
##############################################################################
"""
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
BBB 2005/05/01 -- to be removed after 12 months
"""
import zope.deprecation
zope.deprecation.moved('zope.tal.taldefs', '2.12')
import zope.deferredimport
zope.deferredimport.deprecated(
"TALESError has been renamed TALExpressionError and should be "
"imported from zope.tal.taldefs. This reference will be gone in "
"Zope 2.12.",
TALESError = 'zope.tal.taldefs.TALExpressionError'
)
......@@ -13,880 +13,8 @@
##############################################################################
"""
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()
BBB 2005/05/01 -- to be removed after 12 months
"""
import zope.deprecation
zope.deprecation.moved('zope.tal.talgenerator', '2.12')
......@@ -13,828 +13,16 @@
##############################################################################
"""Interpreter for a pre-compiled TAL program.
BBB 2005/05/01 -- to be removed after 12 months
$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
from zope.i18nmessageid import Message
from TALDefs import attrEscape, TAL_VERSION, METALError
from TALDefs import isCurrentVersion
from TALDefs import getProgramVersion, getProgramMode
from TALGenerator import TALGenerator
from TranslationContext import TranslationContext
I18nMessageTypes = (Message,)
# 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
try:
self.interpret(slot)
finally:
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"
import zope.deprecation
zope.deprecation.moved('zope.tal.talinterpreter', '2.12')
import zope.deferredimport
zope.deferredimport.deprecated(
"'interpolate' has moved to zope.i18n. This reference will be gone "
"in Zope 2.12.",
interpolate = 'zope.i18n:interpolate'
)
......@@ -13,131 +13,8 @@
##############################################################################
"""
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()
BBB 2005/05/01 -- to be removed after 12 months
"""
import zope.deprecation
zope.deprecation.moved('zope.tal.talparser', '2.12')
......@@ -13,29 +13,9 @@
##############################################################################
"""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.
BBB 2005/05/01 -- to be removed after 12 months
$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
import zope.deprecation
zope.deprecation.moved('zope.tal.translationcontext', '2.12')
......@@ -13,75 +13,11 @@
##############################################################################
"""
Generic expat-based XML parser base class.
BBB 2005/05/01 -- to be removed after 12 months
"""
import zope.deprecation
zope.deprecation.moved('zope.tal.xmlparser', '2.12')
import xml.parsers.expat
from logging import getLogger
LOG = getLogger('TAL')
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:
LOG.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:
LOG.error("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)
......@@ -34,165 +34,11 @@ Options:
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)
BBB 2005/05/01 -- to be removed after 12 months
"""
import zope.deprecation
zope.deprecation.moved('zope.tal.driver', '2.12')
if __name__ == "__main__":
main()
#! /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:],
# BBB 2005/05/01 -- to be removed after 12 months
import zope.deprecation
zope.deprecation.moved('zope.tal.ndiff', '2.12')
if __name__ == '__main__':
import sys
......
......@@ -14,138 +14,11 @@
##############################################################################
"""
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
BBB 2005/05/01 -- to be removed after 12 months
"""
import zope.deprecation
zope.deprecation.moved('zope.tal.runtest', '2.12')
if __name__ == "__main__":
main()
#!/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')
# BBB 2005/05/01 -- to be removed after 12 months
import zope.deprecation
zope.deprecation.moved('zope.tal.talgettext', '2.12')
if __name__ == '__main__':
main()
<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>
i18n:name="time"
tal:omit-tag="">2:32 pm</span>... beep!</div>
......@@ -2,5 +2,5 @@
<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 i18n:name="name3" tal:omit-tag=""><b>some</b> <i>text</i></span>
</span>
<div>AT THE TONE THE TIME WILL BE 59 MINUTES AFTER 6 PM... BEEP!</div>
<div>AT THE TONE THE TIME WILL BE 59 minutes after 6 PM... BEEP!</div>
......@@ -22,7 +22,7 @@
<span metal:use-macro="OUTER2">
AAA
<xxx metal:fill-slot="OUTERSLOT">
<xxx>
<span>INNER</span>
</xxx>
BBB
......@@ -48,7 +48,7 @@
<span metal:use-macro="OUTER3">
AAA
<xxx metal:fill-slot="OUTERSLOT">
<xxx>
<span>INNER
<xxx>INNERSLOT</xxx>
</span>
......@@ -63,7 +63,7 @@
</span>
<span metal:use-macro="INNER3">INNER
<xxx metal:fill-slot="INNERSLOT">INNERSLOT</xxx>
<xxx>INNERSLOT</xxx>
</span>
<span metal:use-macro="INNER3">INNER
......
......@@ -2,5 +2,5 @@
<x metal:define-slot="title" />
</html>
<html metal:use-macro="page" i18n:domain="zope">
<x metal:fill-slot="title" />
<x />
</html>
......@@ -8,7 +8,7 @@ import glob
from TAL.tests import utils
import unittest
from TAL import runtest
from zope.tal import runtest
class FileTestCase(unittest.TestCase):
......
......@@ -21,8 +21,18 @@ from TAL.tests import utils
import unittest
# BBB 2005/05/01 -- to be changed after 12 months
# ignore deprecation warnings on import for now
import warnings
showwarning = warnings.showwarning
warnings.showwarning = lambda *a, **k: None
# this old import should remain here until the TAL package is
# completely removed, so that API backward compatibility is properly
# tested
from TAL import HTMLTALParser
from TAL.TALDefs import TAL_VERSION, TALError, METALError
# restore warning machinery
warnings.showwarning = showwarning
class TestCaseBase(unittest.TestCase):
......@@ -345,9 +355,13 @@ class TALGeneratorTestCases(TestCaseBase):
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>')])),
('optTag',
('p',
'',
None,
0,
[('startTag', ('p', [('tal:replace', 'string:foo', 'tal')]))],
[('insertText', ('$string:foo$', [rawtext('bar')]))])),
('endScope', ()),
])
......@@ -355,10 +369,13 @@ class TALGeneratorTestCases(TestCaseBase):
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>')])),
('optTag',
('p',
'',
None,
0,
[('startTag', ('p', [('tal:replace', 'text string:foo', 'tal')]))],
[('insertText', ('$string:foo$', [('rawtextOffset', ('bar', 3))]))])),
('endScope', ()),
])
......@@ -366,10 +383,14 @@ class TALGeneratorTestCases(TestCaseBase):
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>')])),
('optTag',
('p',
'',
None,
0,
[('startTag', ('p', [('tal:replace', 'structure string:<br>', 'tal')]))],
[('insertStructure',
('$string:<br>$', {}, [('rawtextOffset', ('bar', 3))]))])),
('endScope', ()),
])
......@@ -383,9 +404,13 @@ class TALGeneratorTestCases(TestCaseBase):
[('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>')])),
('optTag',
('span',
'',
None,
0,
[('startTag', ('span', [('tal:replace', 'x', 'tal')]))],
[('insertText', ('$x$', [rawtext('dummy')]))])),
('endScope', ()),
rawtext('</p>')])),
('endScope', ()),
......@@ -416,14 +441,19 @@ class TALGeneratorTestCases(TestCaseBase):
('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>')])),
('optTag',
('p',
'',
None,
0,
[('startTag',
('p',
[('tal:replace', 'structure string:<img>', 'tal'),
('tal:attributes', 'src string:foo.png', 'tal')]))],
[('insertStructure',
('$string:<img>$',
{'src': ('$string:foo.png$', False, None)},
[rawtext('duh')]))])),
('endScope', ()),
])
......@@ -454,11 +484,16 @@ class TALGeneratorTestCases(TestCaseBase):
('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>')]))],
([('optTag',
('p',
'',
None,
0,
[('startTag',
('p',
[('tal:on-error', 'string:error', 'tal'),
('tal:replace', 'notHere', 'tal')]))],
[('insertText', ('$notHere$', [('rawtextOffset', ('okay', 4))]))]))],
[('startTag', ('p',
[('tal:on-error', 'string:error', 'tal'),
('tal:replace', 'notHere', 'tal')])),
......@@ -560,10 +595,14 @@ translated string</span>
[('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))])),
('optTag',
('p',
'',
None,
0,
[('startTag', ('p', [('tal:replace', 'str:here', 'tal')]))],
[('insertText',
('$str:here$', [rawtext('content')]))])),
('endScope', ())])),
('endScope', ()),
('rawtextColumn', ('</span>\n', 0))
......@@ -590,12 +629,18 @@ translated string</span>
{'i18n:name': 'name', 'tal:replace': 'str:Lomax'})),
('i18nVariable',
('name',
[('startEndTag',
[('optTag',
('span',
[('tal:replace', 'str:Lomax', 'tal'),
('i18n:name', 'name', 'i18n')]))],
'$str:Lomax$',
0)),
'',
None,
1,
[('startEndTag',
('span',
[('tal:replace', 'str:Lomax', 'tal'),
('i18n:name', 'name', 'i18n')]))],
[('insertText', ('$str:Lomax$', []))]))],
None,
False)),
('rawtextBeginScope',
(' was born in\n ',
2,
......@@ -604,12 +649,18 @@ translated string</span>
{'i18n:name': 'country', 'tal:replace': 'str:Antarctica'})),
('i18nVariable',
('country',
[('startEndTag',
[('optTag',
('span',
[('tal:replace', 'str:Antarctica', 'tal'),
('i18n:name', 'country', 'i18n')]))],
'$str:Antarctica$',
0)),
'',
None,
1,
[('startEndTag',
('span',
[('tal:replace', 'str:Antarctica', 'tal'),
('i18n:name', 'country', 'i18n')]))],
[('insertText', ('$str:Antarctica$', []))]))],
None,
False)),
('endScope', ()),
('rawtextColumn', ('.\n', 0))])),
('endScope', ()),
......@@ -664,13 +715,21 @@ translated string</span>
('',
[('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)),
('name',
[('startTag', ('span', [('i18n:name', 'name', 'i18n')])),
('rawtextOffset', ('<b>Jim</b>', 10)),
('rawtextOffset', ('</span>', 7))],
None,
False)),
('rawtextBeginScope',
(' was born in\n ', 2, (3, 2), 1, {'i18n:name': 'country'})),
('i18nVariable',
('country',
[('startTag', ('span', [('i18n:name', 'country', 'i18n')])),
('rawtextOffset', ('the USA', 7)),
('rawtextOffset', ('</span>', 7))],
None,
False)),
('endScope', ()),
('rawtextColumn', ('.\n', 0))])),
('endScope', ()),
......@@ -778,15 +837,20 @@ translated string</span>
{'i18n:data': 'here/currentTime',
'i18n:name': 'time',
'i18n:translate': 'timefmt'})),
('insertTranslation',
('timefmt',
('i18nVariable',
('time',
[('startTag',
('span',
[('i18n:data', 'here/currentTime', 'i18n'),
('i18n:translate', 'timefmt', 'i18n'),
('i18n:name', 'time', 'i18n')])),
('i18nVariable', ('time', [], None, 0))],
'$here/currentTime$')),
('insertTranslation',
('timefmt',
[('rawtextOffset', ('2:32 pm', 7))],
'$here/currentTime$')),
('rawtextOffset', ('</span>', 7))],
None,
False)),
('endScope', ()),
('rawtextOffset', ('... beep!', 9))])),
('endScope', ()),
......@@ -813,14 +877,20 @@ translated string</span>
{'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)),
[('optTag',
('span',
'',
None,
0,
[('startTag',
('span',
[('tal:replace', 'context/@@object_name', 'tal'),
('i18n:name', 'jobnum', 'i18n')]))],
[('insertText',
('$context/@@object_name$',
[('rawtextOffset', ('NN', 2))]))]))],
None,
False)),
('endScope', ())])),
('endScope', ()),
('rawtextColumn', ('</span>\n', 0))
......@@ -848,7 +918,8 @@ translated string</span>
{'i18n:name': 'email'})),
('i18nVariable',
('email',
[('rawtextBeginScope',
[('startTag', ('span', [('i18n:name', 'email', 'i18n')])),
('rawtextBeginScope',
('\n ',
4,
(3, 4),
......@@ -863,14 +934,14 @@ translated string</span>
('$request/submitter$',
[('rawtextOffset', ('user@host.com', 13))])),
('endScope', ()),
('rawtextOffset', ('</a>', 4))],
('rawtextOffset', ('</a>', 4)),
('rawtextOffset', ('</span>', 7))],
None,
0)),
False)),
('endScope', ()),
('rawtextColumn', ('\n', 0))])),
('endScope', ()),
('rawtextColumn', ('</p>\n', 0))
])
('rawtextColumn', ('</p>\n', 0))])
def check_i18n_name_with_tal_content(self):
# input/test27.html
......
......@@ -6,10 +6,20 @@ import unittest
from StringIO import StringIO
# BBB 2005/05/01 -- to be changed after 12 months
# ignore deprecation warnings on import for now
import warnings
showwarning = warnings.showwarning
warnings.showwarning = lambda *a, **k: None
# this old import should remain here until the TAL package is
# completely removed, so that API backward compatibility is properly
# tested
from TAL.HTMLTALParser import HTMLTALParser
from TAL.TALInterpreter import TALInterpreter
from TAL.TALGenerator import TALGenerator
from TAL.DummyEngine import DummyEngine
# restore warning machinery
warnings.showwarning = showwarning
page1 = '''<html metal:use-macro="main"><body>
......
......@@ -21,12 +21,24 @@ import unittest
from StringIO import StringIO
# BBB 2005/05/01 -- to be changed after 12 months
# ignore deprecation warnings on import for now
import warnings
showwarning = warnings.showwarning
warnings.showwarning = lambda *a, **k: None
# this old import should remain here until the TAL package is
# completely removed, so that API backward compatibility is properly
# tested
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
# restore warning machinery
warnings.showwarning = showwarning
from TAL.tests import utils
from zope.i18nmessageid import Message
......@@ -105,14 +117,15 @@ class I18NCornerTestCase(TestCaseBase):
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"/>'
'<span tal:replace="structure foo" i18n:name="foo_name"'
' i18n:translate=""/>'
'</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">'
'<em i18n:name="foo_name" tal:omit-tag="">'
'<span tal:replace="foo"/>'
'</em>'
'</div>')
......@@ -146,7 +159,7 @@ class I18NCornerTestCase(TestCaseBase):
'<div>THIS IS TEXT FOR <span>BARVALUE</span>.</div>\n')
def test_translate_static_text_as_dynamic_from_bytecode(self):
program = [('version', '1.5'),
program = [('version', '1.6'),
('mode', 'html'),
('setPosition', (1, 0)),
('beginScope', {'i18n:translate': ''}),
......@@ -178,23 +191,8 @@ class I18NCornerTestCase(TestCaseBase):
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()
self.engine.translationDomain.clearMsgids()
result = StringIO()
program, macros = self._compile(
'<div i18n:translate="">This is text for '
......@@ -203,7 +201,7 @@ class I18NCornerTestCase(TestCaseBase):
self.interpreter = TALInterpreter(program, {}, self.engine,
stream=result)
self.interpreter()
msgids = list(xlatdmn.data)
msgids = self.engine.translationDomain.getMsgids('default')
msgids.sort()
self.assertEqual(2, len(msgids))
self.assertEqual('BaRvAlUe', msgids[0][0])
......@@ -217,7 +215,7 @@ class I18NCornerTestCase(TestCaseBase):
# Test for Issue 314: i18n:translate removes line breaks from
# <pre>...</pre> contents
# HTML mode
xlatdmn = self._getCollectingTranslationDomain()
self.engine.translationDomain.clearMsgids()
result = StringIO()
program, macros = self._compile(
'<div i18n:translate=""> This is text\n'
......@@ -227,7 +225,7 @@ class I18NCornerTestCase(TestCaseBase):
self.interpreter = TALInterpreter(program, {}, self.engine,
stream=result)
self.interpreter()
msgids = list(xlatdmn.data)
msgids = self.engine.translationDomain.getMsgids('default')
msgids.sort()
self.assertEqual(2, len(msgids))
self.assertEqual(' This is text\n <b>\tfor</b>\n pre. ', msgids[0][0])
......@@ -238,7 +236,7 @@ class I18NCornerTestCase(TestCaseBase):
result.getvalue())
# XML mode
xlatdmn = self._getCollectingTranslationDomain()
self.engine.translationDomain.clearMsgids()
result = StringIO()
parser = TALParser()
parser.parseString(
......@@ -250,7 +248,7 @@ class I18NCornerTestCase(TestCaseBase):
self.interpreter = TALInterpreter(program, {}, self.engine,
stream=result)
self.interpreter()
msgids = list(xlatdmn.data)
msgids = self.engine.translationDomain.getMsgids('default')
msgids.sort()
self.assertEqual(1, len(msgids))
self.assertEqual('This is text <b> for</b> barvalue.', msgids[0][0])
......@@ -260,7 +258,7 @@ class I18NCornerTestCase(TestCaseBase):
result.getvalue())
def test_raw_msgids_and_i18ntranslate_i18nname(self):
xlatdmn = self._getCollectingTranslationDomain()
self.engine.translationDomain.clearMsgids()
result = StringIO()
program, macros = self._compile(
'<div i18n:translate=""> This is text\n \tfor\n'
......@@ -269,7 +267,7 @@ class I18NCornerTestCase(TestCaseBase):
self.interpreter = TALInterpreter(program, {}, self.engine,
stream=result)
self.interpreter()
msgids = list(xlatdmn.data)
msgids = self.engine.translationDomain.getMsgids('default')
msgids.sort()
self.assertEqual(2, len(msgids))
self.assertEqual(' \tRaW\n ', msgids[0][0])
......@@ -416,12 +414,13 @@ class InterpolateTestCase(TestCaseBase):
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)
# this test just tests sick behaviour, we'll disable it
#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)
......
......@@ -7,7 +7,17 @@ import sys
from TAL.tests import utils
import unittest
# BBB 2005/05/01 -- to be changed after 12 months
# ignore deprecation warnings on import for now
import warnings
showwarning = warnings.showwarning
warnings.showwarning = lambda *a, **k: None
# this old import should remain here until the TAL package is
# completely removed, so that API backward compatibility is properly
# tested
from TAL import XMLParser
# restore warning machinery
warnings.showwarning = showwarning
class EventCollector(XMLParser.XMLParser):
......
......@@ -14,44 +14,11 @@
##############################################################################
"""
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
BBB 2005/05/01 -- to be removed after 12 months
"""
import zope.deprecation
zope.deprecation.moved('zope.tal.timer', '2.12')
if __name__ == "__main__":
main()
......@@ -12,14 +12,17 @@
##############################################################################
"""Iterator class
Unlike the builtin iterators of Python 2.2+, these classes are
designed to maintain information about the state of an iteration.
The Iterator() function accepts either a sequence or a Python
iterator. The next() method fetches the next item, and returns
true if it succeeds.
BBB 2005/05/01 -- to be removed after 12 months
$Id$
"""
import zope.deprecation
zope.deprecation.deprecated(
'Iterator',
'Iterator has been deprecated and will be removed in Zope 2.12. '
'PageTemplates now use the Zope 3 implementation. Use ZopeIterator '
'from Products.PageTemplates.Expressions instead.'
)
class Iterator:
'''Simple Iterator class'''
......
......@@ -14,8 +14,12 @@
$Id$
"""
# BBB 2005/05/01 -- to be removed after 12 months
import zope.deferredimport
zope.deferredimport.define(
Iterator = 'ZTUtils.Iterator:Iterator'
)
from Iterator import Iterator
from Tree import encodeExpansion, decodeExpansion, a2b, b2a
from SimpleTree import SimpleTreeMaker
......
from __future__ import generators
import os, sys, unittest
import zope.deprecation
zope.deprecation.__show__.off()
from ZTUtils import Iterator
zope.deprecation.__show__.on()
try:
iter
......
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