[ZPT] CVS: Zope3/lib/python/Zope/TAL - HTMLTALParser.py:1.30.4.3.4.1 TALDefs.py:1.24.10.4.2.1 TALGenerator.py:1.52.16.3.4.1 TALInterpreter.py:1.63.10.3.4.1 TALParser.py:1.16.16.3.4.1
Fred L. Drake, Jr.
fdrake@acm.org
Wed, 20 Mar 2002 18:36:31 -0500
Update of /cvs-repository/Zope3/lib/python/Zope/TAL
In directory cvs.zope.org:/tmp/cvs-serv20026
Modified Files:
Tag: fdrake-tal-i18n-branch
HTMLTALParser.py TALDefs.py TALGenerator.py TALInterpreter.py
TALParser.py
Log Message:
Preliminary attempts to start the I18N support for TAL.
This initial checkin is not quite working yet, but we have come a long way.
(we = Fred & Stephen Richter)
=== Zope3/lib/python/Zope/TAL/HTMLTALParser.py 1.30.4.3 => 1.30.4.3.4.1 ===
from TALGenerator import TALGenerator
-from TALDefs import ZOPE_METAL_NS, ZOPE_TAL_NS, METALError, TALError
from HTMLParser import HTMLParser, HTMLParseError
+from TALDefs import \
+ ZOPE_METAL_NS, ZOPE_TAL_NS, ZOPE_I18N_NS, METALError, TALError
BOOLEAN_HTML_ATTRS = [
# List of Boolean attributes in HTML that may be given in
@@ -106,7 +107,10 @@
self.gen = gen
self.tagstack = []
self.nsstack = []
- self.nsdict = {'tal': ZOPE_TAL_NS, 'metal': ZOPE_METAL_NS}
+ self.nsdict = {'tal': ZOPE_TAL_NS,
+ 'metal': ZOPE_METAL_NS,
+ 'i18n': ZOPE_I18N_NS,
+ }
def parseFile(self, file):
f = open(file)
@@ -132,9 +136,10 @@
def handle_starttag(self, tag, attrs):
self.close_para_tags(tag)
self.scan_xmlns(attrs)
- tag, attrlist, taldict, metaldict = self.process_ns(tag, attrs)
+ tag, attrlist, taldict, metaldict, i18ndict \
+ = self.process_ns(tag, attrs)
self.tagstack.append(tag)
- self.gen.emitStartElement(tag, attrlist, taldict, metaldict,
+ self.gen.emitStartElement(tag, attrlist, taldict, metaldict, i18ndict,
self.getpos())
if tag in EMPTY_HTML_TAGS:
self.implied_endtag(tag, -1)
@@ -142,14 +147,15 @@
def handle_startendtag(self, tag, attrs):
self.close_para_tags(tag)
self.scan_xmlns(attrs)
- tag, attrlist, taldict, metaldict = self.process_ns(tag, attrs)
+ tag, attrlist, taldict, metaldict, i18ndict \
+ = self.process_ns(tag, attrs)
if taldict.get("content"):
self.gen.emitStartElement(tag, attrlist, taldict, metaldict,
- self.getpos())
+ i18ndict, self.getpos())
self.gen.emitEndElement(tag, implied=-1)
else:
self.gen.emitStartElement(tag, attrlist, taldict, metaldict,
- self.getpos(), isend=1)
+ i18ndict, self.getpos(), isend=1)
self.pop_xmlns()
def handle_endtag(self, tag):
@@ -252,7 +258,7 @@
prefix, suffix = name.split(':', 1)
if prefix == 'xmlns':
nsuri = self.nsdict.get(suffix)
- if nsuri in (ZOPE_TAL_NS, ZOPE_METAL_NS):
+ if nsuri in (ZOPE_TAL_NS, ZOPE_METAL_NS, ZOPE_I18N_NS):
return name, name, prefix
else:
nsuri = self.nsdict.get(prefix)
@@ -260,12 +266,15 @@
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
@@ -283,7 +292,12 @@
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
+ return name, attrlist, taldict, metaldict, i18ndict
=== Zope3/lib/python/Zope/TAL/TALDefs.py 1.24.10.4 => 1.24.10.4.2.1 ===
from types import ListType, TupleType
-TAL_VERSION = "1.3.2"
+TAL_VERSION = "1.4"
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"
NAME_RE = "[a-zA-Z_][a-zA-Z0-9_]*"
@@ -46,6 +47,13 @@
"tal tag",
]
+KNOWN_I18N_ATTRIBUTES = [
+ "attributes",
+ "data",
+ "id",
+ "translate",
+ ]
+
class TALError(Exception):
def __init__(self, msg, position=(None, None)):
@@ -66,6 +74,9 @@
pass
class TALESError(TALError):
+ pass
+
+class I18NError(TALError):
pass
=== Zope3/lib/python/Zope/TAL/TALGenerator.py 1.52.16.3 => 1.52.16.3.4.1 ===
import re
import cgi
+import sys
from TALDefs import *
@@ -105,6 +106,7 @@
actionIndex = {"replace":0, "insert":1, "metal":2, "tal":3, "xmlns":4,
0: 0, 1: 1, 2: 2, 3: 3, 4: 4}
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
@@ -114,6 +116,7 @@
item = attrlist[i]
if len(item) > 2:
opt = 0
+ # XXX why are we converting action to an integer?
name, value, action = item[:3]
action = self.actionIndex[action]
attrlist[i] = (name, value, action) + item[3:]
@@ -127,6 +130,7 @@
new.append(" " + item[0])
else:
new.append(" %s=%s" % (item[0], quote(item[1])))
+ # if no non-optimizable attributes were found, convert to plain text
if opt:
new.append(end)
collect.extend(new)
@@ -272,6 +276,7 @@
self.emit("insertText", cexpr, program)
else:
assert key == "structure"
+ assert 0, 'emitSubstitution...' + `cexpr, attrDict, program`
self.emit("insertStructure", cexpr, attrDict, program)
def emitDefineMacro(self, macroName):
@@ -361,23 +366,32 @@
return None
def replaceAttrs(self, attrlist, repldict):
+ # Each entry in attrlist starts like (name, value).
+ # Result is (name, value, action, expr) 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):
- item = item[:2] + ("replace", repldict[key])
+ expr, xlat = repldict[key]
+ item = item[:2] + ("replace", expr, xlat)
del repldict[key]
+ elif len(item) == 3 and item[2] == 'i18n':
+ item = item[:2] + ('tal',)
newlist.append(item)
- for key, value in repldict.items(): # Add dynamic-only attributes
- item = (key, None, "insert", value)
- newlist.append(item)
+ # Add dynamic-only attributes
+ for key, (expr, xlat) in repldict.items():
+ newlist.append((key, None, "insert", expr, xlat))
return newlist
- def emitStartElement(self, name, attrlist, taldict, metaldict,
+ def emitStartElement(self, name, attrlist, taldict, metaldict, i18ndict,
position=(None, None), isend=0):
- if not taldict and not metaldict:
+ if not taldict and not metaldict and not i18ndict:
# Handle the simple, common case
self.emitStartTag(name, attrlist, isend)
self.todoPush({})
@@ -399,6 +413,12 @@
if not value:
raise TALError("missing value for METAL attribute: " +
`key`, position)
+ for key, value in i18ndict.items():
+ if key not in 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")
@@ -413,13 +433,24 @@
onError = taldict.get("on-error")
omitTag = taldict.get("omit-tag")
TALtag = taldict.get("tal tag")
+ i18nattrs = i18ndict.get("attributes")
+ i18nid = i18ndict.get("id")
+
+ if i18ndict.has_key("data") and not i18ndict.has_key("id"):
+ raise I18NError("i18n:data must be accompanied by i18n:id",
+ 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 content and replace:
- raise TALError("content and replace are mutually exclusive",
- position)
+ if replace:
+ if content:
+ raise TALError("content and replace are mutually exclusive",
+ position)
+ if i18nid:
+ raise I18NError("i18n:id and replace are mutually exclusive",
+ position)
repeatWhitespace = None
if repeat:
@@ -491,15 +522,29 @@
if optTag:
todo["optional tag"] = omitTag, TALtag
self.pushProgram()
- if attrsubst:
- repldict = parseAttributeReplacements(attrsubst)
+ if attrsubst or i18nattrs:
+ if attrsubst:
+ repldict = parseAttributeReplacements(attrsubst)
+ print >>sys.__stderr__, "emitStartElement", name, `repldict`
+ else:
+ repldict = {}
+ if i18nattrs:
+ i18nattrs = i18nattrs.split()
+ else:
+ i18nattrs = ()
+ # Convert repldict's name-->expr mapping to a
+ # name-->(compiled_expr, translate) mapping
for key, value in repldict.items():
- repldict[key] = self.compileExpression(value)
+ if key == 'src':
+ print >>sys.__stderr__, "src = %r" % value
+ repldict[key] = self.compileExpression(value), key in i18nattrs
+ for key in i18nattrs:
+ if key not in repldict:
+ repldict[key] = None, 1
else:
repldict = {}
if replace:
todo["repldict"] = repldict
- repldict = {}
self.emitStartTag(name, self.replaceAttrs(attrlist, repldict), isend)
if optTag:
self.pushProgram()
=== Zope3/lib/python/Zope/TAL/TALInterpreter.py 1.63.10.3 => 1.63.10.3.4.1 ===
apply(TALGenerator.emit, (self,) + args)
- def emitStartElement(self, name, attrlist, taldict, metaldict,
+ 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, position, isend)
+ taldict, metaldict, i18ndict,
+ position, isend)
def replaceAttrs(self, attrlist, repldict):
if self.enabled and self.repldict:
@@ -289,6 +291,7 @@
if action > 1:
return self.attrAction(item)
ok = 1
+ expr, xlat = item[3:]
if self.html and name.lower() in BOOLEAN_HTML_ATTRS:
evalue = self.engine.evaluateBoolean(item[3])
if evalue is self.Default:
@@ -299,19 +302,27 @@
else:
ok = 0
else:
- evalue = self.engine.evaluateText(item[3])
- if evalue is self.Default:
- if action == 1: # Cancelled insert
- ok = 0
- else:
- if evalue is None:
- ok = 0
- value = evalue
+ if expr is not None:
+ evalue = self.engine.evaluateText(item[3])
+ if evalue is self.Default:
+ if action == 1: # Cancelled insert
+ ok = 0
+ else:
+ if evalue is None:
+ ok = 0
+ value = evalue
if ok:
+ if xlat:
+ value = self.i18n_attribute(value)
if value is None:
value = name
value = "%s=%s" % (name, quote(value))
return ok, name, value
+
+ def i18n_attribute(self, s):
+ # s is the value of an attribute before translation
+ # it may have been computed
+ raise NotImplementedError("cannot translate %r" % s)
bytecode_handlers["<attrAction>"] = attrAction
=== Zope3/lib/python/Zope/TAL/TALParser.py 1.16.16.3 => 1.16.16.3.4.1 ===
attrlist = attrs.items()
attrlist.sort() # For definiteness
- name, attrlist, taldict, metaldict = self.process_ns(name, attrlist)
+ name, attrlist, taldict, metaldict, i18ndict \
+ = self.process_ns(name, attrlist)
attrlist = self.xmlnsattrs() + attrlist
- self.gen.emitStartElement(name, attrlist, taldict, metaldict)
+ 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:
@@ -77,10 +79,14 @@
elif ns == 'tal':
taldict[keybase] = value
item = item + ("tal",)
+ elif ns == 'i18n':
+ assert 0, "dealing with i18n: " + `(keybase, value)`
+ i18ndict[keybase] = value
+ item = item + ('i18n',)
fixedattrlist.append(item)
- if namens in ('metal', 'tal'):
+ if namens in ('metal', 'tal', 'i18n'):
taldict['tal tag'] = namens
- return name, fixedattrlist, taldict, metaldict
+ return name, fixedattrlist, taldict, metaldict, i18ndict
def xmlnsattrs(self):
newlist = []
@@ -89,7 +95,7 @@
key = "xmlns:" + prefix
else:
key = "xmlns"
- if uri in (ZOPE_METAL_NS, ZOPE_TAL_NS):
+ if uri in (ZOPE_METAL_NS, ZOPE_TAL_NS, ZOPE_I18N_NS):
item = (key, uri, "xmlns")
else:
item = (key, uri)
@@ -109,6 +115,8 @@
ns = 'tal'
elif uri == ZOPE_METAL_NS:
ns = 'metal'
+ elif uri == ZOPE_I18N_NS:
+ ns = 'i18n'
return (prefixed, name, ns)
return (name, name, None)