Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Z
Zope
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Kirill Smelkov
Zope
Commits
211c2854
Commit
211c2854
authored
May 28, 2006
by
Philipp von Weitershausen
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Deprecate TAL package and make it a facade of zope.tal.
parent
7544aa4b
Changes
14
Show whitespace changes
Inline
Side-by-side
Showing
14 changed files
with
73 additions
and
4035 deletions
+73
-4035
lib/python/TAL/DummyEngine.py
lib/python/TAL/DummyEngine.py
+5
-230
lib/python/TAL/HTMLTALParser.py
lib/python/TAL/HTMLTALParser.py
+4
-299
lib/python/TAL/ITALES.py
lib/python/TAL/ITALES.py
+8
-153
lib/python/TAL/TALDefs.py
lib/python/TAL/TALDefs.py
+12
-175
lib/python/TAL/TALGenerator.py
lib/python/TAL/TALGenerator.py
+4
-876
lib/python/TAL/TALInterpreter.py
lib/python/TAL/TALInterpreter.py
+11
-823
lib/python/TAL/TALParser.py
lib/python/TAL/TALParser.py
+4
-127
lib/python/TAL/TranslationContext.py
lib/python/TAL/TranslationContext.py
+3
-23
lib/python/TAL/XMLParser.py
lib/python/TAL/XMLParser.py
+4
-68
lib/python/TAL/driver.py
lib/python/TAL/driver.py
+4
-158
lib/python/TAL/ndiff.py
lib/python/TAL/ndiff.py
+3
-620
lib/python/TAL/runtest.py
lib/python/TAL/runtest.py
+4
-131
lib/python/TAL/talgettext.py
lib/python/TAL/talgettext.py
+3
-315
lib/python/TAL/timer.py
lib/python/TAL/timer.py
+4
-37
No files found.
lib/python/TAL/DummyEngine.py
View file @
211c2854
...
...
@@ -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
:
...
...
lib/python/TAL/HTMLTALParser.py
View file @
211c2854
...
...
@@ -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'
)
lib/python/TAL/ITALES.py
View file @
211c2854
"""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."
)
lib/python/TAL/TALDefs.py
View file @
211c2854
...
...
@@ -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('&', s)
s = _entch_re.sub(r'&
\
1
', s)
s = _entn1_re.sub('&#', s)
s = _entnx_re.sub(r'&
\
1
', s)
s = _entnd_re.sub(r'&
\
1
', s)
s = s.replace('<', '<')
s = s.replace('>', '>')
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'
)
lib/python/TAL/TALGenerator.py
View file @
211c2854
...
...
@@ -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*(?:(glo
bal|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
").'
'
\
n
File %s at row, column %s
\
n
Attributes %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'
)
lib/python/TAL/TALInterpreter.py
View file @
211c2854
...
...
@@ -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'
)
lib/python/TAL/TALParser.py
View file @
211c2854
...
...
@@ -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'
)
lib/python/TAL/TranslationContext.py
View file @
211c2854
...
...
@@ -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'
)
lib/python/TAL/XMLParser.py
View file @
211c2854
...
...
@@ -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
)
lib/python/TAL/driver.py
View file @
211c2854
...
...
@@ -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
()
lib/python/TAL/ndiff.py
View file @
211c2854
#! /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
...
...
lib/python/TAL/runtest.py
View file @
211c2854
...
...
@@ -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
()
lib/python/TAL/talgettext.py
View file @
211c2854
#!/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
()
lib/python/TAL/timer.py
View file @
211c2854
...
...
@@ -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
()
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment