Commit 56e64abe authored by Evan Simpson's avatar Evan Simpson

Refactor Path expressions, make them support other expression types as the...

Refactor Path expressions, make them support other expression types as the final alternate, and support Iterator.first() and last().
parent 26e2a24a
...@@ -17,11 +17,11 @@ Page Template-specific implementation of TALES, with handlers ...@@ -17,11 +17,11 @@ Page Template-specific implementation of TALES, with handlers
for Python expressions, string literals, and paths. for Python expressions, string literals, and paths.
""" """
__version__='$Revision: 1.29 $'[11:-2] __version__='$Revision: 1.30 $'[11:-2]
import re, sys import re, sys
from TALES import Engine, CompilerError, _valid_name, NAME_RE, \ from TALES import Engine, CompilerError, _valid_name, NAME_RE, \
TALESError, Undefined, Default TALESError, Undefined, Default, _parse_expr
from string import strip, split, join, replace, lstrip from string import strip, split, join, replace, lstrip
from Acquisition import aq_base, aq_inner, aq_parent from Acquisition import aq_base, aq_inner, aq_parent
...@@ -30,7 +30,8 @@ _engine = None ...@@ -30,7 +30,8 @@ _engine = None
def getEngine(): def getEngine():
global _engine global _engine
if _engine is None: if _engine is None:
_engine = Engine() from PathIterator import Iterator
_engine = Engine(Iterator)
installHandlers(_engine) installHandlers(_engine)
_engine._nocatch = (TALESError, 'Redirect') _engine._nocatch = (TALESError, 'Redirect')
return _engine return _engine
...@@ -71,6 +72,9 @@ else: ...@@ -71,6 +72,9 @@ else:
else: else:
return f(ns) return f(ns)
Undefs = (Undefined, AttributeError, KeyError,
TypeError, IndexError, Unauthorized)
def render(ob, ns): def render(ob, ns):
""" """
Calls the object, possibly a document template, or just returns it if Calls the object, possibly a document template, or just returns it if
...@@ -91,38 +95,27 @@ def render(ob, ns): ...@@ -91,38 +95,27 @@ def render(ob, ns):
raise raise
return ob return ob
class PathExpr: class SubPathExpr:
def __init__(self, name, expr, engine): def __init__(self, path):
self._s = expr self._path = path = split(strip(path), '/')
self._name = name self._base = base = path.pop(0)
self._paths = map(self._prepPath, split(expr, '|'))
def _prepPath(self, path):
path = split(strip(path), '/')
base = path.pop(0)
if not _valid_name(base): if not _valid_name(base):
raise CompilerError, 'Invalid variable name "%s"' % base raise CompilerError, 'Invalid variable name "%s"' % base
# Parse path # Parse path
dp = [] self._dp = dp = []
for i in range(len(path)): for i in range(len(path)):
e = path[i] e = path[i]
if e[:1] == '?' and _valid_name(e[1:]): if e[:1] == '?' and _valid_name(e[1:]):
dp.append((i, e[1:])) dp.append((i, e[1:]))
dp.reverse() dp.reverse()
return base, path, dp
def _eval(self, econtext, securityManager, def _eval(self, econtext,
list=list, isinstance=isinstance, StringType=type(''), list=list, isinstance=isinstance, StringType=type('')):
render=render):
vars = econtext.vars vars = econtext.vars
exists = 0 path = self._path
more_paths = len(self._paths) if self._dp:
for base, path, dp in self._paths:
more_paths = more_paths - 1
# Expand dynamic path parts from right to left.
if dp:
path = list(path) # Copy! path = list(path) # Copy!
for i, varname in dp: for i, varname in self._dp:
val = vars[varname] val = vars[varname]
if isinstance(val, StringType): if isinstance(val, StringType):
path[i] = val path[i] = val
...@@ -130,8 +123,7 @@ class PathExpr: ...@@ -130,8 +123,7 @@ class PathExpr:
# If the value isn't a string, assume it's a sequence # If the value isn't a string, assume it's a sequence
# of path names. # of path names.
path[i:i+1] = list(val) path[i:i+1] = list(val)
try: __traceback_info__ = base = self._base
__traceback_info__ = base
if base == 'CONTEXTS': if base == 'CONTEXTS':
ob = econtext.contexts ob = econtext.contexts
else: else:
...@@ -139,27 +131,62 @@ class PathExpr: ...@@ -139,27 +131,62 @@ class PathExpr:
if isinstance(ob, DeferWrapper): if isinstance(ob, DeferWrapper):
ob = ob() ob = ob()
if path: if path:
ob = restrictedTraverse(ob, path, securityManager) ob = restrictedTraverse(ob, path, getSecurityManager())
exists = 1 return ob
class PathExpr:
def __init__(self, name, expr, engine):
self._s = expr
self._name = name
paths = split(expr, '|')
self._subexprs = []
add = self._subexprs.append
for i in range(len(paths)):
path = lstrip(paths[i])
if _parse_expr(path):
# This part is the start of another expression type,
# so glue it back together and compile it.
add(engine.compile(lstrip(join(paths[i:], '|'))))
break break
except Undefined: add(SubPathExpr(path)._eval)
if self._name != 'exists' and not more_paths:
raise def _exists(self, econtext):
except (AttributeError, KeyError, TypeError, IndexError, for expr in self._subexprs:
Unauthorized), e: try:
if self._name != 'exists' and not more_paths: expr(econtext)
except Undefs:
pass
else:
return 1
return 0
def _eval(self, econtext,
isinstance=isinstance, StringType=type(''), render=render):
for expr in self._subexprs[:-1]:
# Try all but the last subexpression, skipping undefined ones
try:
ob = expr(econtext)
except Undefs:
pass
else:
break
else:
# On the last subexpression allow exceptions through, but
# wrap ones that indicate that the subexpression was undefined
try:
ob = self._subexprs[-1](econtext)
except Undefs[1:]:
raise Undefined(self._s, sys.exc_info()) raise Undefined(self._s, sys.exc_info())
if self._name == 'exists':
# All we wanted to know is whether one of the paths exist.
return exists
if self._name == 'nocall' or isinstance(ob, StringType): if self._name == 'nocall' or isinstance(ob, StringType):
return ob return ob
# Return the rendered object # Return the rendered object
return render(ob, vars) return render(ob, econtext.vars)
def __call__(self, econtext): def __call__(self, econtext):
return self._eval(econtext, getSecurityManager()) if self._name == 'exists':
return self._exists(econtext)
return self._eval(econtext)
def __str__(self): def __str__(self):
return '%s expression %s' % (self._name, `self._s`) return '%s expression %s' % (self._name, `self._s`)
...@@ -247,21 +274,21 @@ class DeferExpr: ...@@ -247,21 +274,21 @@ class DeferExpr:
def restrictedTraverse(self, path, securityManager, def restrictedTraverse(self, path, securityManager,
get=getattr, has=hasattr, N=None, M=[]): get=getattr, has=hasattr, N=None, M=[]):
i = 0 REQUEST = {'path': path}
REQUEST['TraversalRequestNameStack'] = path = path[:] # Copy!
if not path[0]: if not path[0]:
# If the path starts with an empty string, go to the root first. # If the path starts with an empty string, go to the root first.
self = self.getPhysicalRoot() self = self.getPhysicalRoot()
if not securityManager.validateValue(self): if not securityManager.validateValue(self):
raise Unauthorized, name raise Unauthorized, name
i = 1 path.pop(0)
REQUEST={'TraversalRequestNameStack': path} path.reverse()
validate = securityManager.validate validate = securityManager.validate
object = self object = self
while i < len(path): while path:
__traceback_info__ = (path, i) __traceback_info__ = REQUEST
name = path[i] name = path.pop()
i = i + 1
if name[0] == '_': if name[0] == '_':
# Never allowed in a URL. # Never allowed in a URL.
......
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
HTML- and XML-based template objects using TAL, TALES, and METAL. HTML- and XML-based template objects using TAL, TALES, and METAL.
""" """
__version__='$Revision: 1.19 $'[11:-2] __version__='$Revision: 1.20 $'[11:-2]
import os, sys, traceback, pprint import os, sys, traceback, pprint
from TAL.TALParser import TALParser from TAL.TALParser import TALParser
...@@ -40,6 +40,7 @@ class PageTemplate(Base): ...@@ -40,6 +40,7 @@ class PageTemplate(Base):
expand = 1 expand = 1
_v_errors = () _v_errors = ()
_v_warnings = () _v_warnings = ()
id = '(unknown)'
_text = '' _text = ''
_error_start = '<!-- Page Template Diagnostics' _error_start = '<!-- Page Template Diagnostics'
......
##############################################################################
#
# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (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
#
##############################################################################
"""Path Iterator
A TALES Iterator with the ability to use first() and last() on
subpaths of elements.
"""
__version__='$Revision: 1.1 $'[11:-2]
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)
del path[:]
names.reverse()
return getattr(self, name)(names)
return getattr(self, name)
def same_part(self, name, ob1, ob2):
if name is None:
return ob1 == ob2
if isinstance(name, type('')):
name = split(name, '/')
name = filter(None, name)
securityManager = getSecurityManager()
try:
ob1 = restrictedTraverse(ob1, name, securityManager)
ob2 = restrictedTraverse(ob2, name, securityManager)
except Undefs:
return 0
return ob1 == ob2
<html>
<body tal:define="ztu modules/ZTUtils;b python:ztu.Batch(range(10), 5)">
<p tal:repeat="n b">
Batch 1: item=<span tal:replace="n">n</span>
</p>
<p tal:repeat="n b/next">
Batch 2: item=<span tal:replace="n">n</span>
</p>
</body>
</html>
...@@ -5,10 +5,14 @@ ...@@ -5,10 +5,14 @@
<p tal:content="x | nil">2</p> <p tal:content="x | nil">2</p>
<p tal:content="python:nil or x">3</p> <p tal:content="python:nil or x">3</p>
<p tal:content="y/z | x">4</p> <p tal:content="y/z | x">4</p>
<p tal:content="y/z | string:X">4</p>
<p tal:content="y/z | python:'|AXE|'[2]">4</p>
<p tal:content="y/z | x | nil">5</p> <p tal:content="y/z | x | nil">5</p>
<p tal:attributes="name nil">Z</p> <p tal:attributes="name nil">Z</p>
<p tal:attributes="name y/z | nil">Z</p> <p tal:attributes="name y/z | nil">Z</p>
<p tal:attributes="name y/z | string:">Z</p>
<p tal:attributes="name y/z | python:'||'[:0]">Z</p>
<p tal:attributes="name y/z | nothing">Z</p> <p tal:attributes="name y/z | nothing">Z</p>
<p tal:on-error="python:str(error.value)" tal:content="a/b | c/d">Z</p> <p tal:on-error="python:str(error.value)" tal:content="a/b | c/d">Z</p>
......
<html>
<body tal:define="objects python:[
{'name': 'fred', 'legs': 2},
{'name': 'wilma', 'legs': 2},
{'name': 'dino', 'legs': 4},
]">
<tal:block repeat="ob objects">
<h4 tal:condition="repeat/ob/first/legs">Legs:
<span tal:replace="ob/legs">1</span></h4>
<p tal:content="ob/name">Name</p>
<hr tal:condition="repeat/ob/last/legs" />
</tal:block>
</body>
</html>
<html>
<body>
<p>
Batch 1: item=0
</p>
<p>
Batch 1: item=1
</p>
<p>
Batch 1: item=2
</p>
<p>
Batch 1: item=3
</p>
<p>
Batch 1: item=4
</p>
<p>
Batch 2: item=5
</p>
<p>
Batch 2: item=6
</p>
<p>
Batch 2: item=7
</p>
<p>
Batch 2: item=8
</p>
<p>
Batch 2: item=9
</p>
</body>
</html>
...@@ -6,7 +6,11 @@ ...@@ -6,7 +6,11 @@
<p>X</p> <p>X</p>
<p>X</p> <p>X</p>
<p>X</p> <p>X</p>
<p>X</p>
<p>X</p>
<p name="">Z</p>
<p name="">Z</p>
<p name="">Z</p> <p name="">Z</p>
<p name="">Z</p> <p name="">Z</p>
<p>Z</p> <p>Z</p>
......
<html>
<body>
<h4>Legs:
2</h4>
<p>fred</p>
<p>wilma</p>
<hr >
<h4>Legs:
4</h4>
<p>dino</p>
<hr >
</body>
</html>
...@@ -58,6 +58,13 @@ class HTMLTests(unittest.TestCase): ...@@ -58,6 +58,13 @@ class HTMLTests(unittest.TestCase):
def tearDown(self): def tearDown(self):
SecurityManager.setSecurityPolicy( self.oldPolicy ) SecurityManager.setSecurityPolicy( self.oldPolicy )
def assert_expected(self, t, fname, *args, **kwargs):
t.write(util.read_input(fname))
assert not t._v_errors, 'Template errors: %s' % t._v_errors
expect = util.read_output(fname)
out = apply(t, args, kwargs)
util.check_html(expect, out)
def getProducts(self): def getProducts(self):
return [ return [
{'description': 'This is the tee for those who LOVE Zope. ' {'description': 'This is the tee for those who LOVE Zope. '
...@@ -71,86 +78,49 @@ class HTMLTests(unittest.TestCase): ...@@ -71,86 +78,49 @@ class HTMLTests(unittest.TestCase):
] ]
def check1(self): def check1(self):
laf = self.folder.laf self.assert_expected(self.folder.laf, 'TeeShopLAF.html')
laf.write(util.read_input('TeeShopLAF.html'))
expect = util.read_output('TeeShopLAF.html')
util.check_html(expect, laf())
def check2(self): def check2(self):
self.folder.laf.write(util.read_input('TeeShopLAF.html')) self.folder.laf.write(util.read_input('TeeShopLAF.html'))
t = self.folder.t self.assert_expected(self.folder.t, 'TeeShop2.html',
t.write(util.read_input('TeeShop2.html')) getProducts=self.getProducts)
expect = util.read_output('TeeShop2.html')
out = t(getProducts=self.getProducts)
util.check_html(expect, out)
def check3(self): def check3(self):
self.folder.laf.write(util.read_input('TeeShopLAF.html')) self.folder.laf.write(util.read_input('TeeShopLAF.html'))
t = self.folder.t self.assert_expected(self.folder.t, 'TeeShop1.html',
t.write(util.read_input('TeeShop1.html')) getProducts=self.getProducts)
expect = util.read_output('TeeShop1.html')
out = t(getProducts=self.getProducts)
util.check_html(expect, out)
def checkSimpleLoop(self): def checkSimpleLoop(self):
t = self.folder.t self.assert_expected(self.folder.t, 'Loop1.html')
t.write(util.read_input('Loop1.html'))
expect = util.read_output('Loop1.html') def checkFancyLoop(self):
out = t() self.assert_expected(self.folder.t, 'Loop2.html')
util.check_html(expect, out)
def checkGlobalsShadowLocals(self): def checkGlobalsShadowLocals(self):
t = self.folder.t self.assert_expected(self.folder.t, 'GlobalsShadowLocals.html')
t.write(util.read_input('GlobalsShadowLocals.html'))
expect = util.read_output('GlobalsShadowLocals.html')
out = t()
util.check_html(expect, out)
def checkStringExpressions(self): def checkStringExpressions(self):
t = self.folder.t self.assert_expected(self.folder.t, 'StringExpression.html')
t.write(util.read_input('StringExpression.html'))
expect = util.read_output('StringExpression.html')
out = t()
util.check_html(expect, out)
def checkReplaceWithNothing(self): def checkReplaceWithNothing(self):
t = self.folder.t self.assert_expected(self.folder.t, 'CheckNothing.html')
t.write(util.read_input('CheckNothing.html'))
expect = util.read_output('CheckNothing.html')
out = t()
util.check_html(expect, out)
def checkWithXMLHeader(self): def checkWithXMLHeader(self):
t = self.folder.t self.assert_expected(self.folder.t, 'CheckWithXMLHeader.html')
t.write(util.read_input('CheckWithXMLHeader.html'))
expect = util.read_output('CheckWithXMLHeader.html')
out = t()
util.check_html(expect, out)
def checkNotExpression(self): def checkNotExpression(self):
t = self.folder.t self.assert_expected(self.folder.t, 'CheckNotExpression.html')
t.write(util.read_input('CheckNotExpression.html'))
expect = util.read_output('CheckNotExpression.html')
out = t()
util.check_html(expect, out)
def checkPathNothing(self): def checkPathNothing(self):
t = self.folder.t self.assert_expected(self.folder.t, 'CheckPathNothing.html')
t.write(util.read_input('CheckPathNothing.html'))
expect = util.read_output('CheckPathNothing.html')
out = t()
util.check_html(expect, out)
def checkPathAlt(self): def checkPathAlt(self):
t = self.folder.t self.assert_expected(self.folder.t, 'CheckPathAlt.html')
t.write(util.read_input('CheckPathAlt.html'))
expect = util.read_output('CheckPathAlt.html')
out = t()
util.check_html(expect, out)
def checkBatchIteration(self):
self.assert_expected(self.folder.t, 'CheckBatchIteration.html')
def test_suite(): def test_suite():
return unittest.makeSuite(HTMLTests, 'check') return unittest.makeSuite(HTMLTests, 'check')
......
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