summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Theune <ct@gocept.com>2007-05-03 22:34:23 +0000
committerChristian Theune <ct@gocept.com>2007-05-03 22:34:23 +0000
commit18627546bad8adef9fbd4f2a18ecededcfdb9352 (patch)
tree04d593b2bcc8afe058840f4a1667cdb23f7b8f46
parent32cc0013e39049938adcf919a34f13c6d5f45ab0 (diff)
parentae8fd91d9ee289f31a30cc7a435fd077dface481 (diff)
downloadzope-tal-18627546bad8adef9fbd4f2a18ecededcfdb9352.tar.gz
Moving code to satellite.
-rw-r--r--src/zope/tal/DEPENDENCIES.cfg4
-rw-r--r--src/zope/tal/__init__.py2
-rw-r--r--src/zope/tal/benchmark/__init__.py2
-rw-r--r--src/zope/tal/benchmark/dtml01.html1
-rw-r--r--src/zope/tal/benchmark/dtml02.html100
-rw-r--r--src/zope/tal/benchmark/dtml03.html8
-rw-r--r--src/zope/tal/benchmark/dtml04.html6
-rw-r--r--src/zope/tal/benchmark/dtml05.html10
-rw-r--r--src/zope/tal/benchmark/dtml06.html14
-rw-r--r--src/zope/tal/benchmark/dtml07.html73
-rw-r--r--src/zope/tal/benchmark/dtml08.html73
-rw-r--r--src/zope/tal/benchmark/dtml09.html10
-rw-r--r--src/zope/tal/benchmark/dtml10.html102
-rw-r--r--src/zope/tal/benchmark/dtml11.html103
-rw-r--r--src/zope/tal/benchmark/dtml12.html12
-rw-r--r--src/zope/tal/benchmark/tal01.html1
-rw-r--r--src/zope/tal/benchmark/tal02.html100
-rw-r--r--src/zope/tal/benchmark/tal03.html8
-rw-r--r--src/zope/tal/benchmark/tal04.html6
-rw-r--r--src/zope/tal/benchmark/tal05.html10
-rw-r--r--src/zope/tal/benchmark/tal06.html14
-rw-r--r--src/zope/tal/benchmark/tal07.html73
-rw-r--r--src/zope/tal/benchmark/tal08.html73
-rw-r--r--src/zope/tal/benchmark/tal09.html10
-rw-r--r--src/zope/tal/benchmark/tal10.html102
-rw-r--r--src/zope/tal/benchmark/tal11.html103
-rw-r--r--src/zope/tal/benchmark/tal12.html12
-rw-r--r--src/zope/tal/driver.py209
-rw-r--r--src/zope/tal/dummyengine.py332
-rw-r--r--src/zope/tal/htmltalparser.py321
-rw-r--r--src/zope/tal/interfaces.py207
-rw-r--r--src/zope/tal/ndiff.py649
-rw-r--r--src/zope/tal/runtest.py154
-rw-r--r--src/zope/tal/setpath.py48
-rw-r--r--src/zope/tal/taldefs.py201
-rw-r--r--src/zope/tal/talgenerator.py856
-rw-r--r--src/zope/tal/talgettext.py316
-rw-r--r--src/zope/tal/talinterpreter.py1024
-rw-r--r--src/zope/tal/talparser.py142
-rw-r--r--src/zope/tal/tests/__init__.py2
-rw-r--r--src/zope/tal/tests/input/__init__.py2
-rw-r--r--src/zope/tal/tests/input/acme_template.pt15
-rw-r--r--src/zope/tal/tests/input/document_list.pt21
-rw-r--r--src/zope/tal/tests/input/pnome_template.pt23
-rw-r--r--src/zope/tal/tests/input/test01.html56
-rw-r--r--src/zope/tal/tests/input/test01.xml57
-rw-r--r--src/zope/tal/tests/input/test02.html118
-rw-r--r--src/zope/tal/tests/input/test02.xml119
-rw-r--r--src/zope/tal/tests/input/test03.html9
-rw-r--r--src/zope/tal/tests/input/test03.xml10
-rw-r--r--src/zope/tal/tests/input/test04.html26
-rw-r--r--src/zope/tal/tests/input/test04.xml27
-rw-r--r--src/zope/tal/tests/input/test05.html9
-rw-r--r--src/zope/tal/tests/input/test05.xml10
-rw-r--r--src/zope/tal/tests/input/test06.html6
-rw-r--r--src/zope/tal/tests/input/test06.xml7
-rw-r--r--src/zope/tal/tests/input/test07.html11
-rw-r--r--src/zope/tal/tests/input/test07.xml12
-rw-r--r--src/zope/tal/tests/input/test08.html44
-rw-r--r--src/zope/tal/tests/input/test08.xml45
-rw-r--r--src/zope/tal/tests/input/test09.html30
-rw-r--r--src/zope/tal/tests/input/test09.xml30
-rw-r--r--src/zope/tal/tests/input/test10.html48
-rw-r--r--src/zope/tal/tests/input/test11.html19
-rw-r--r--src/zope/tal/tests/input/test11.xml14
-rw-r--r--src/zope/tal/tests/input/test12.html24
-rw-r--r--src/zope/tal/tests/input/test13.html7
-rw-r--r--src/zope/tal/tests/input/test14.html10
-rw-r--r--src/zope/tal/tests/input/test14.xml15
-rw-r--r--src/zope/tal/tests/input/test15.html26
-rw-r--r--src/zope/tal/tests/input/test16.html2
-rw-r--r--src/zope/tal/tests/input/test16.xml7
-rw-r--r--src/zope/tal/tests/input/test17.html6
-rw-r--r--src/zope/tal/tests/input/test17.xml10
-rw-r--r--src/zope/tal/tests/input/test18.html16
-rw-r--r--src/zope/tal/tests/input/test18.xml20
-rw-r--r--src/zope/tal/tests/input/test19.html5
-rw-r--r--src/zope/tal/tests/input/test19.xml8
-rw-r--r--src/zope/tal/tests/input/test20.html1
-rw-r--r--src/zope/tal/tests/input/test20.xml6
-rw-r--r--src/zope/tal/tests/input/test21.html4
-rw-r--r--src/zope/tal/tests/input/test21.xml9
-rw-r--r--src/zope/tal/tests/input/test22.html4
-rw-r--r--src/zope/tal/tests/input/test22.xml6
-rw-r--r--src/zope/tal/tests/input/test23.html2
-rw-r--r--src/zope/tal/tests/input/test24.html12
-rw-r--r--src/zope/tal/tests/input/test25.html1
-rw-r--r--src/zope/tal/tests/input/test26.html3
-rw-r--r--src/zope/tal/tests/input/test27.html5
-rw-r--r--src/zope/tal/tests/input/test28.html5
-rw-r--r--src/zope/tal/tests/input/test29.html4
-rw-r--r--src/zope/tal/tests/input/test30.html6
-rw-r--r--src/zope/tal/tests/input/test31.html7
-rw-r--r--src/zope/tal/tests/input/test32.html4
-rw-r--r--src/zope/tal/tests/input/test33.html1
-rw-r--r--src/zope/tal/tests/input/test34.html11
-rw-r--r--src/zope/tal/tests/input/test35.html7
-rw-r--r--src/zope/tal/tests/input/test36.html6
-rw-r--r--src/zope/tal/tests/input/test_domain.html7
-rw-r--r--src/zope/tal/tests/input/test_failed_attr_translation.html2
-rw-r--r--src/zope/tal/tests/input/test_metal1.html61
-rw-r--r--src/zope/tal/tests/input/test_metal2.html7
-rw-r--r--src/zope/tal/tests/input/test_metal3.html1
-rw-r--r--src/zope/tal/tests/input/test_metal4.html4
-rw-r--r--src/zope/tal/tests/input/test_metal5.html4
-rw-r--r--src/zope/tal/tests/input/test_metal6.html5
-rw-r--r--src/zope/tal/tests/input/test_metal7.html6
-rw-r--r--src/zope/tal/tests/input/test_metal8.html15
-rw-r--r--src/zope/tal/tests/input/test_metal9.html23
-rw-r--r--src/zope/tal/tests/input/test_sa1.html6
-rw-r--r--src/zope/tal/tests/input/test_sa1.xml7
-rw-r--r--src/zope/tal/tests/input/test_sa2.html9
-rw-r--r--src/zope/tal/tests/input/test_sa2.xml10
-rw-r--r--src/zope/tal/tests/input/test_sa3.html15
-rw-r--r--src/zope/tal/tests/input/test_sa3.xml16
-rw-r--r--src/zope/tal/tests/input/test_sa4.html11
-rw-r--r--src/zope/tal/tests/markbench.py187
-rw-r--r--src/zope/tal/tests/output/__init__.py2
-rw-r--r--src/zope/tal/tests/output/acme_template.html26
-rw-r--r--src/zope/tal/tests/output/acme_template_source.html27
-rw-r--r--src/zope/tal/tests/output/document_list.html30
-rw-r--r--src/zope/tal/tests/output/document_list_source.html30
-rw-r--r--src/zope/tal/tests/output/test01.html68
-rw-r--r--src/zope/tal/tests/output/test01.xml65
-rw-r--r--src/zope/tal/tests/output/test02.html118
-rw-r--r--src/zope/tal/tests/output/test02.xml119
-rw-r--r--src/zope/tal/tests/output/test03.html9
-rw-r--r--src/zope/tal/tests/output/test03.xml10
-rw-r--r--src/zope/tal/tests/output/test04.html38
-rw-r--r--src/zope/tal/tests/output/test04.xml39
-rw-r--r--src/zope/tal/tests/output/test05.html9
-rw-r--r--src/zope/tal/tests/output/test05.xml10
-rw-r--r--src/zope/tal/tests/output/test06.html7
-rw-r--r--src/zope/tal/tests/output/test06.xml8
-rw-r--r--src/zope/tal/tests/output/test07.html11
-rw-r--r--src/zope/tal/tests/output/test07.xml12
-rw-r--r--src/zope/tal/tests/output/test08.html47
-rw-r--r--src/zope/tal/tests/output/test08.xml48
-rw-r--r--src/zope/tal/tests/output/test09.html30
-rw-r--r--src/zope/tal/tests/output/test09.xml30
-rw-r--r--src/zope/tal/tests/output/test10.html51
-rw-r--r--src/zope/tal/tests/output/test11.html8
-rw-r--r--src/zope/tal/tests/output/test11.xml5
-rw-r--r--src/zope/tal/tests/output/test12.html24
-rw-r--r--src/zope/tal/tests/output/test13.html7
-rw-r--r--src/zope/tal/tests/output/test14.html13
-rw-r--r--src/zope/tal/tests/output/test14.xml18
-rw-r--r--src/zope/tal/tests/output/test15.html29
-rw-r--r--src/zope/tal/tests/output/test16.html1
-rw-r--r--src/zope/tal/tests/output/test16.xml6
-rw-r--r--src/zope/tal/tests/output/test17.html6
-rw-r--r--src/zope/tal/tests/output/test17.xml9
-rw-r--r--src/zope/tal/tests/output/test18.html16
-rw-r--r--src/zope/tal/tests/output/test18.xml19
-rw-r--r--src/zope/tal/tests/output/test19.html3
-rw-r--r--src/zope/tal/tests/output/test19.xml6
-rw-r--r--src/zope/tal/tests/output/test20.html1
-rw-r--r--src/zope/tal/tests/output/test20.xml4
-rw-r--r--src/zope/tal/tests/output/test21.html1
-rw-r--r--src/zope/tal/tests/output/test21.xml4
-rw-r--r--src/zope/tal/tests/output/test22.html1
-rw-r--r--src/zope/tal/tests/output/test22.xml6
-rw-r--r--src/zope/tal/tests/output/test23.html1
-rw-r--r--src/zope/tal/tests/output/test24.html7
-rw-r--r--src/zope/tal/tests/output/test25.html1
-rw-r--r--src/zope/tal/tests/output/test26.html1
-rw-r--r--src/zope/tal/tests/output/test27.html1
-rw-r--r--src/zope/tal/tests/output/test28.html1
-rw-r--r--src/zope/tal/tests/output/test29.html1
-rw-r--r--src/zope/tal/tests/output/test30.html1
-rw-r--r--src/zope/tal/tests/output/test31.html1
-rw-r--r--src/zope/tal/tests/output/test32.html1
-rw-r--r--src/zope/tal/tests/output/test33.html1
-rw-r--r--src/zope/tal/tests/output/test34.html7
-rw-r--r--src/zope/tal/tests/output/test35.html6
-rw-r--r--src/zope/tal/tests/output/test36.html2
-rw-r--r--src/zope/tal/tests/output/test_domain.html5
-rw-r--r--src/zope/tal/tests/output/test_failed_attr_translation.html1
-rw-r--r--src/zope/tal/tests/output/test_metal1.html79
-rw-r--r--src/zope/tal/tests/output/test_metal2.html11
-rw-r--r--src/zope/tal/tests/output/test_metal3.html1
-rw-r--r--src/zope/tal/tests/output/test_metal4.html4
-rw-r--r--src/zope/tal/tests/output/test_metal5.html4
-rw-r--r--src/zope/tal/tests/output/test_metal6.html5
-rw-r--r--src/zope/tal/tests/output/test_metal7.html6
-rw-r--r--src/zope/tal/tests/output/test_metal8.html19
-rw-r--r--src/zope/tal/tests/output/test_metal9.html32
-rw-r--r--src/zope/tal/tests/output/test_sa1.html10
-rw-r--r--src/zope/tal/tests/output/test_sa1.xml11
-rw-r--r--src/zope/tal/tests/output/test_sa2.html13
-rw-r--r--src/zope/tal/tests/output/test_sa2.xml14
-rw-r--r--src/zope/tal/tests/output/test_sa3.html42
-rw-r--r--src/zope/tal/tests/output/test_sa3.xml43
-rw-r--r--src/zope/tal/tests/output/test_sa4.html30
-rw-r--r--src/zope/tal/tests/run.py45
-rw-r--r--src/zope/tal/tests/test_files.py90
-rw-r--r--src/zope/tal/tests/test_htmltalparser.py1021
-rw-r--r--src/zope/tal/tests/test_sourcepos.py93
-rw-r--r--src/zope/tal/tests/test_talgettext.py78
-rw-r--r--src/zope/tal/tests/test_talinterpreter.py853
-rw-r--r--src/zope/tal/tests/test_talparser.py39
-rw-r--r--src/zope/tal/tests/test_xmlparser.py268
-rw-r--r--src/zope/tal/tests/utils.py65
-rw-r--r--src/zope/tal/timer.py58
-rw-r--r--src/zope/tal/translationcontext.py40
-rw-r--r--src/zope/tal/xmlparser.py105
206 files changed, 11065 insertions, 0 deletions
diff --git a/src/zope/tal/DEPENDENCIES.cfg b/src/zope/tal/DEPENDENCIES.cfg
new file mode 100644
index 0000000..2886de2
--- /dev/null
+++ b/src/zope/tal/DEPENDENCIES.cfg
@@ -0,0 +1,4 @@
+zope.i18n
+zope.i18nmessageid
+zope.interface
+zope.deprecation
diff --git a/src/zope/tal/__init__.py b/src/zope/tal/__init__.py
new file mode 100644
index 0000000..b711d36
--- /dev/null
+++ b/src/zope/tal/__init__.py
@@ -0,0 +1,2 @@
+#
+# This file is necessary to make this directory a package.
diff --git a/src/zope/tal/benchmark/__init__.py b/src/zope/tal/benchmark/__init__.py
new file mode 100644
index 0000000..b711d36
--- /dev/null
+++ b/src/zope/tal/benchmark/__init__.py
@@ -0,0 +1,2 @@
+#
+# This file is necessary to make this directory a package.
diff --git a/src/zope/tal/benchmark/dtml01.html b/src/zope/tal/benchmark/dtml01.html
new file mode 100644
index 0000000..180b47c
--- /dev/null
+++ b/src/zope/tal/benchmark/dtml01.html
@@ -0,0 +1 @@
+baseline
diff --git a/src/zope/tal/benchmark/dtml02.html b/src/zope/tal/benchmark/dtml02.html
new file mode 100644
index 0000000..33d978d
--- /dev/null
+++ b/src/zope/tal/benchmark/dtml02.html
@@ -0,0 +1,100 @@
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
diff --git a/src/zope/tal/benchmark/dtml03.html b/src/zope/tal/benchmark/dtml03.html
new file mode 100644
index 0000000..aea01aa
--- /dev/null
+++ b/src/zope/tal/benchmark/dtml03.html
@@ -0,0 +1,8 @@
+ <td bgcolor="white">&dtml-x0;</td>
+ <td bgcolor="white">&dtml-x1;</td>
+ <td bgcolor="white">&dtml-x2;</td>
+ <td bgcolor="white">&dtml-x3;</td>
+ <td bgcolor="white">&dtml-x4;</td>
+ <td bgcolor="white">&dtml-x5;</td>
+ <td bgcolor="white">&dtml-x6;</td>
+ <td bgcolor="white">&dtml-x7;</td>
diff --git a/src/zope/tal/benchmark/dtml04.html b/src/zope/tal/benchmark/dtml04.html
new file mode 100644
index 0000000..1a3214f
--- /dev/null
+++ b/src/zope/tal/benchmark/dtml04.html
@@ -0,0 +1,6 @@
+<dtml-in r8>
+ <td bgcolor="white">&dtml-x0;</td>
+ <td bgcolor="white">&dtml-x1;</td>
+ <td bgcolor="white">&dtml-x2;</td>
+ <td bgcolor="white">&dtml-x3;</td>
+</dtml-in>
diff --git a/src/zope/tal/benchmark/dtml05.html b/src/zope/tal/benchmark/dtml05.html
new file mode 100644
index 0000000..70b53cb
--- /dev/null
+++ b/src/zope/tal/benchmark/dtml05.html
@@ -0,0 +1,10 @@
+<dtml-in r8>
+ <td bgcolor="white">&dtml-x0;</td>
+ <td bgcolor="white">&dtml-x1;</td>
+ <td bgcolor="white">&dtml-x2;</td>
+ <td bgcolor="white">&dtml-x3;</td>
+ <td bgcolor="white">&dtml-x4;</td>
+ <td bgcolor="white">&dtml-x5;</td>
+ <td bgcolor="white">&dtml-x6;</td>
+ <td bgcolor="white">&dtml-x7;</td>
+</dtml-in>
diff --git a/src/zope/tal/benchmark/dtml06.html b/src/zope/tal/benchmark/dtml06.html
new file mode 100644
index 0000000..11e5cf2
--- /dev/null
+++ b/src/zope/tal/benchmark/dtml06.html
@@ -0,0 +1,14 @@
+<dtml-in r2>
+<dtml-in r2>
+<dtml-in r2>
+ <td bgcolor="white">&dtml-x0;</td>
+ <td bgcolor="white">&dtml-x1;</td>
+ <td bgcolor="white">&dtml-x2;</td>
+ <td bgcolor="white">&dtml-x3;</td>
+ <td bgcolor="white">&dtml-x4;</td>
+ <td bgcolor="white">&dtml-x5;</td>
+ <td bgcolor="white">&dtml-x6;</td>
+ <td bgcolor="white">&dtml-x7;</td>
+</dtml-in>
+</dtml-in>
+</dtml-in>
diff --git a/src/zope/tal/benchmark/dtml07.html b/src/zope/tal/benchmark/dtml07.html
new file mode 100644
index 0000000..48f50c7
--- /dev/null
+++ b/src/zope/tal/benchmark/dtml07.html
@@ -0,0 +1,73 @@
+ <td bgcolor="white">&dtml-x0;</td>
+ <td bgcolor="white">&dtml-x1;</td>
+ <td bgcolor="white">&dtml-x2;</td>
+ <td bgcolor="white">&dtml-x3;</td>
+ <td bgcolor="white">&dtml-x4;</td>
+ <td bgcolor="white">&dtml-x5;</td>
+ <td bgcolor="white">&dtml-x6;</td>
+ <td bgcolor="white">&dtml-x7;</td>
+
+ <td bgcolor="white">&dtml-x0;</td>
+ <td bgcolor="white">&dtml-x1;</td>
+ <td bgcolor="white">&dtml-x2;</td>
+ <td bgcolor="white">&dtml-x3;</td>
+ <td bgcolor="white">&dtml-x4;</td>
+ <td bgcolor="white">&dtml-x5;</td>
+ <td bgcolor="white">&dtml-x6;</td>
+ <td bgcolor="white">&dtml-x7;</td>
+
+ <td bgcolor="white">&dtml-x0;</td>
+ <td bgcolor="white">&dtml-x1;</td>
+ <td bgcolor="white">&dtml-x2;</td>
+ <td bgcolor="white">&dtml-x3;</td>
+ <td bgcolor="white">&dtml-x4;</td>
+ <td bgcolor="white">&dtml-x5;</td>
+ <td bgcolor="white">&dtml-x6;</td>
+ <td bgcolor="white">&dtml-x7;</td>
+
+ <td bgcolor="white">&dtml-x0;</td>
+ <td bgcolor="white">&dtml-x1;</td>
+ <td bgcolor="white">&dtml-x2;</td>
+ <td bgcolor="white">&dtml-x3;</td>
+ <td bgcolor="white">&dtml-x4;</td>
+ <td bgcolor="white">&dtml-x5;</td>
+ <td bgcolor="white">&dtml-x6;</td>
+ <td bgcolor="white">&dtml-x7;</td>
+
+ <td bgcolor="white">&dtml-x0;</td>
+ <td bgcolor="white">&dtml-x1;</td>
+ <td bgcolor="white">&dtml-x2;</td>
+ <td bgcolor="white">&dtml-x3;</td>
+ <td bgcolor="white">&dtml-x4;</td>
+ <td bgcolor="white">&dtml-x5;</td>
+ <td bgcolor="white">&dtml-x6;</td>
+ <td bgcolor="white">&dtml-x7;</td>
+
+ <td bgcolor="white">&dtml-x0;</td>
+ <td bgcolor="white">&dtml-x1;</td>
+ <td bgcolor="white">&dtml-x2;</td>
+ <td bgcolor="white">&dtml-x3;</td>
+ <td bgcolor="white">&dtml-x4;</td>
+ <td bgcolor="white">&dtml-x5;</td>
+ <td bgcolor="white">&dtml-x6;</td>
+ <td bgcolor="white">&dtml-x7;</td>
+
+ <td bgcolor="white">&dtml-x0;</td>
+ <td bgcolor="white">&dtml-x1;</td>
+ <td bgcolor="white">&dtml-x2;</td>
+ <td bgcolor="white">&dtml-x3;</td>
+ <td bgcolor="white">&dtml-x4;</td>
+ <td bgcolor="white">&dtml-x5;</td>
+ <td bgcolor="white">&dtml-x6;</td>
+ <td bgcolor="white">&dtml-x7;</td>
+
+ <td bgcolor="white">&dtml-x0;</td>
+ <td bgcolor="white">&dtml-x1;</td>
+ <td bgcolor="white">&dtml-x2;</td>
+ <td bgcolor="white">&dtml-x3;</td>
+ <td bgcolor="white">&dtml-x4;</td>
+ <td bgcolor="white">&dtml-x5;</td>
+ <td bgcolor="white">&dtml-x6;</td>
+ <td bgcolor="white">&dtml-x7;</td>
+
+
diff --git a/src/zope/tal/benchmark/dtml08.html b/src/zope/tal/benchmark/dtml08.html
new file mode 100644
index 0000000..48f50c7
--- /dev/null
+++ b/src/zope/tal/benchmark/dtml08.html
@@ -0,0 +1,73 @@
+ <td bgcolor="white">&dtml-x0;</td>
+ <td bgcolor="white">&dtml-x1;</td>
+ <td bgcolor="white">&dtml-x2;</td>
+ <td bgcolor="white">&dtml-x3;</td>
+ <td bgcolor="white">&dtml-x4;</td>
+ <td bgcolor="white">&dtml-x5;</td>
+ <td bgcolor="white">&dtml-x6;</td>
+ <td bgcolor="white">&dtml-x7;</td>
+
+ <td bgcolor="white">&dtml-x0;</td>
+ <td bgcolor="white">&dtml-x1;</td>
+ <td bgcolor="white">&dtml-x2;</td>
+ <td bgcolor="white">&dtml-x3;</td>
+ <td bgcolor="white">&dtml-x4;</td>
+ <td bgcolor="white">&dtml-x5;</td>
+ <td bgcolor="white">&dtml-x6;</td>
+ <td bgcolor="white">&dtml-x7;</td>
+
+ <td bgcolor="white">&dtml-x0;</td>
+ <td bgcolor="white">&dtml-x1;</td>
+ <td bgcolor="white">&dtml-x2;</td>
+ <td bgcolor="white">&dtml-x3;</td>
+ <td bgcolor="white">&dtml-x4;</td>
+ <td bgcolor="white">&dtml-x5;</td>
+ <td bgcolor="white">&dtml-x6;</td>
+ <td bgcolor="white">&dtml-x7;</td>
+
+ <td bgcolor="white">&dtml-x0;</td>
+ <td bgcolor="white">&dtml-x1;</td>
+ <td bgcolor="white">&dtml-x2;</td>
+ <td bgcolor="white">&dtml-x3;</td>
+ <td bgcolor="white">&dtml-x4;</td>
+ <td bgcolor="white">&dtml-x5;</td>
+ <td bgcolor="white">&dtml-x6;</td>
+ <td bgcolor="white">&dtml-x7;</td>
+
+ <td bgcolor="white">&dtml-x0;</td>
+ <td bgcolor="white">&dtml-x1;</td>
+ <td bgcolor="white">&dtml-x2;</td>
+ <td bgcolor="white">&dtml-x3;</td>
+ <td bgcolor="white">&dtml-x4;</td>
+ <td bgcolor="white">&dtml-x5;</td>
+ <td bgcolor="white">&dtml-x6;</td>
+ <td bgcolor="white">&dtml-x7;</td>
+
+ <td bgcolor="white">&dtml-x0;</td>
+ <td bgcolor="white">&dtml-x1;</td>
+ <td bgcolor="white">&dtml-x2;</td>
+ <td bgcolor="white">&dtml-x3;</td>
+ <td bgcolor="white">&dtml-x4;</td>
+ <td bgcolor="white">&dtml-x5;</td>
+ <td bgcolor="white">&dtml-x6;</td>
+ <td bgcolor="white">&dtml-x7;</td>
+
+ <td bgcolor="white">&dtml-x0;</td>
+ <td bgcolor="white">&dtml-x1;</td>
+ <td bgcolor="white">&dtml-x2;</td>
+ <td bgcolor="white">&dtml-x3;</td>
+ <td bgcolor="white">&dtml-x4;</td>
+ <td bgcolor="white">&dtml-x5;</td>
+ <td bgcolor="white">&dtml-x6;</td>
+ <td bgcolor="white">&dtml-x7;</td>
+
+ <td bgcolor="white">&dtml-x0;</td>
+ <td bgcolor="white">&dtml-x1;</td>
+ <td bgcolor="white">&dtml-x2;</td>
+ <td bgcolor="white">&dtml-x3;</td>
+ <td bgcolor="white">&dtml-x4;</td>
+ <td bgcolor="white">&dtml-x5;</td>
+ <td bgcolor="white">&dtml-x6;</td>
+ <td bgcolor="white">&dtml-x7;</td>
+
+
diff --git a/src/zope/tal/benchmark/dtml09.html b/src/zope/tal/benchmark/dtml09.html
new file mode 100644
index 0000000..ce8e43e
--- /dev/null
+++ b/src/zope/tal/benchmark/dtml09.html
@@ -0,0 +1,10 @@
+<dtml-in r64>
+ <td bgcolor="white">&dtml-x0;</td>
+ <td bgcolor="white">&dtml-x1;</td>
+ <td bgcolor="white">&dtml-x2;</td>
+ <td bgcolor="white">&dtml-x3;</td>
+ <td bgcolor="white">&dtml-x4;</td>
+ <td bgcolor="white">&dtml-x5;</td>
+ <td bgcolor="white">&dtml-x6;</td>
+ <td bgcolor="white">&dtml-x7;</td>
+</dtml-in>
diff --git a/src/zope/tal/benchmark/dtml10.html b/src/zope/tal/benchmark/dtml10.html
new file mode 100644
index 0000000..3115f7c
--- /dev/null
+++ b/src/zope/tal/benchmark/dtml10.html
@@ -0,0 +1,102 @@
+<dtml-in r64>
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+</dtml-in>
diff --git a/src/zope/tal/benchmark/dtml11.html b/src/zope/tal/benchmark/dtml11.html
new file mode 100644
index 0000000..b0f71bd
--- /dev/null
+++ b/src/zope/tal/benchmark/dtml11.html
@@ -0,0 +1,103 @@
+<dtml-in r64>
+ <td bgcolor="white">&dtml-x0;</td>
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+</dtml-in>
diff --git a/src/zope/tal/benchmark/dtml12.html b/src/zope/tal/benchmark/dtml12.html
new file mode 100644
index 0000000..df2dab1
--- /dev/null
+++ b/src/zope/tal/benchmark/dtml12.html
@@ -0,0 +1,12 @@
+<dtml-in r8>
+ <dtml-let y0=x0 y1=x1 y2=x2 y3=x3 y4=x4 y5=x5 y6=x6 y7=x7>
+ <td bgcolor="white">&dtml-y0;</td>
+ <td bgcolor="white">&dtml-y1;</td>
+ <td bgcolor="white">&dtml-y2;</td>
+ <td bgcolor="white">&dtml-y3;</td>
+ <td bgcolor="white">&dtml-y4;</td>
+ <td bgcolor="white">&dtml-y5;</td>
+ <td bgcolor="white">&dtml-y6;</td>
+ <td bgcolor="white">&dtml-y7;</td>
+ </dtml-let>
+</dtml-in>
diff --git a/src/zope/tal/benchmark/tal01.html b/src/zope/tal/benchmark/tal01.html
new file mode 100644
index 0000000..180b47c
--- /dev/null
+++ b/src/zope/tal/benchmark/tal01.html
@@ -0,0 +1 @@
+baseline
diff --git a/src/zope/tal/benchmark/tal02.html b/src/zope/tal/benchmark/tal02.html
new file mode 100644
index 0000000..33d978d
--- /dev/null
+++ b/src/zope/tal/benchmark/tal02.html
@@ -0,0 +1,100 @@
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
diff --git a/src/zope/tal/benchmark/tal03.html b/src/zope/tal/benchmark/tal03.html
new file mode 100644
index 0000000..b63a737
--- /dev/null
+++ b/src/zope/tal/benchmark/tal03.html
@@ -0,0 +1,8 @@
+ <td bgcolor="white" tal:content="x0"></td>
+ <td bgcolor="white" tal:content="x1"></td>
+ <td bgcolor="white" tal:content="x2"></td>
+ <td bgcolor="white" tal:content="x3"></td>
+ <td bgcolor="white" tal:content="x4"></td>
+ <td bgcolor="white" tal:content="x5"></td>
+ <td bgcolor="white" tal:content="x6"></td>
+ <td bgcolor="white" tal:content="x7"></td>
diff --git a/src/zope/tal/benchmark/tal04.html b/src/zope/tal/benchmark/tal04.html
new file mode 100644
index 0000000..42af6e8
--- /dev/null
+++ b/src/zope/tal/benchmark/tal04.html
@@ -0,0 +1,6 @@
+<dtml-in tal:repeat="r r8">
+ <td bgcolor="white" tal:content="x0"></td>
+ <td bgcolor="white" tal:content="x1"></td>
+ <td bgcolor="white" tal:content="x2"></td>
+ <td bgcolor="white" tal:content="x3"></td>
+</dtml-in>
diff --git a/src/zope/tal/benchmark/tal05.html b/src/zope/tal/benchmark/tal05.html
new file mode 100644
index 0000000..6e2d626
--- /dev/null
+++ b/src/zope/tal/benchmark/tal05.html
@@ -0,0 +1,10 @@
+<dtml-in tal:repeat="r r8">
+ <td bgcolor="white" tal:content="x0"></td>
+ <td bgcolor="white" tal:content="x1"></td>
+ <td bgcolor="white" tal:content="x2"></td>
+ <td bgcolor="white" tal:content="x3"></td>
+ <td bgcolor="white" tal:content="x4"></td>
+ <td bgcolor="white" tal:content="x5"></td>
+ <td bgcolor="white" tal:content="x6"></td>
+ <td bgcolor="white" tal:content="x7"></td>
+</dtml-in>
diff --git a/src/zope/tal/benchmark/tal06.html b/src/zope/tal/benchmark/tal06.html
new file mode 100644
index 0000000..6f40872
--- /dev/null
+++ b/src/zope/tal/benchmark/tal06.html
@@ -0,0 +1,14 @@
+<dtml-in tal:repeat="r r2">
+<dtml-in tal:repeat="r r2">
+<dtml-in tal:repeat="r r2">
+ <td bgcolor="white" tal:content="x0"></td>
+ <td bgcolor="white" tal:content="x1"></td>
+ <td bgcolor="white" tal:content="x2"></td>
+ <td bgcolor="white" tal:content="x3"></td>
+ <td bgcolor="white" tal:content="x4"></td>
+ <td bgcolor="white" tal:content="x5"></td>
+ <td bgcolor="white" tal:content="x6"></td>
+ <td bgcolor="white" tal:content="x7"></td>
+</dtml-in>
+</dtml-in>
+</dtml-in>
diff --git a/src/zope/tal/benchmark/tal07.html b/src/zope/tal/benchmark/tal07.html
new file mode 100644
index 0000000..f331f05
--- /dev/null
+++ b/src/zope/tal/benchmark/tal07.html
@@ -0,0 +1,73 @@
+ <td bgcolor="white" tal:content="x0"></td>
+ <td bgcolor="white" tal:content="x1"></td>
+ <td bgcolor="white" tal:content="x2"></td>
+ <td bgcolor="white" tal:content="x3"></td>
+ <td bgcolor="white" tal:content="x4"></td>
+ <td bgcolor="white" tal:content="x5"></td>
+ <td bgcolor="white" tal:content="x6"></td>
+ <td bgcolor="white" tal:content="x7"></td>
+
+ <td bgcolor="white" tal:content="x0"></td>
+ <td bgcolor="white" tal:content="x1"></td>
+ <td bgcolor="white" tal:content="x2"></td>
+ <td bgcolor="white" tal:content="x3"></td>
+ <td bgcolor="white" tal:content="x4"></td>
+ <td bgcolor="white" tal:content="x5"></td>
+ <td bgcolor="white" tal:content="x6"></td>
+ <td bgcolor="white" tal:content="x7"></td>
+
+ <td bgcolor="white" tal:content="x0"></td>
+ <td bgcolor="white" tal:content="x1"></td>
+ <td bgcolor="white" tal:content="x2"></td>
+ <td bgcolor="white" tal:content="x3"></td>
+ <td bgcolor="white" tal:content="x4"></td>
+ <td bgcolor="white" tal:content="x5"></td>
+ <td bgcolor="white" tal:content="x6"></td>
+ <td bgcolor="white" tal:content="x7"></td>
+
+ <td bgcolor="white" tal:content="x0"></td>
+ <td bgcolor="white" tal:content="x1"></td>
+ <td bgcolor="white" tal:content="x2"></td>
+ <td bgcolor="white" tal:content="x3"></td>
+ <td bgcolor="white" tal:content="x4"></td>
+ <td bgcolor="white" tal:content="x5"></td>
+ <td bgcolor="white" tal:content="x6"></td>
+ <td bgcolor="white" tal:content="x7"></td>
+
+ <td bgcolor="white" tal:content="x0"></td>
+ <td bgcolor="white" tal:content="x1"></td>
+ <td bgcolor="white" tal:content="x2"></td>
+ <td bgcolor="white" tal:content="x3"></td>
+ <td bgcolor="white" tal:content="x4"></td>
+ <td bgcolor="white" tal:content="x5"></td>
+ <td bgcolor="white" tal:content="x6"></td>
+ <td bgcolor="white" tal:content="x7"></td>
+
+ <td bgcolor="white" tal:content="x0"></td>
+ <td bgcolor="white" tal:content="x1"></td>
+ <td bgcolor="white" tal:content="x2"></td>
+ <td bgcolor="white" tal:content="x3"></td>
+ <td bgcolor="white" tal:content="x4"></td>
+ <td bgcolor="white" tal:content="x5"></td>
+ <td bgcolor="white" tal:content="x6"></td>
+ <td bgcolor="white" tal:content="x7"></td>
+
+ <td bgcolor="white" tal:content="x0"></td>
+ <td bgcolor="white" tal:content="x1"></td>
+ <td bgcolor="white" tal:content="x2"></td>
+ <td bgcolor="white" tal:content="x3"></td>
+ <td bgcolor="white" tal:content="x4"></td>
+ <td bgcolor="white" tal:content="x5"></td>
+ <td bgcolor="white" tal:content="x6"></td>
+ <td bgcolor="white" tal:content="x7"></td>
+
+ <td bgcolor="white" tal:content="x0"></td>
+ <td bgcolor="white" tal:content="x1"></td>
+ <td bgcolor="white" tal:content="x2"></td>
+ <td bgcolor="white" tal:content="x3"></td>
+ <td bgcolor="white" tal:content="x4"></td>
+ <td bgcolor="white" tal:content="x5"></td>
+ <td bgcolor="white" tal:content="x6"></td>
+ <td bgcolor="white" tal:content="x7"></td>
+
+
diff --git a/src/zope/tal/benchmark/tal08.html b/src/zope/tal/benchmark/tal08.html
new file mode 100644
index 0000000..f577fed
--- /dev/null
+++ b/src/zope/tal/benchmark/tal08.html
@@ -0,0 +1,73 @@
+ <td bgcolor="white"><span tal:replace="x0"></span></td>
+ <td bgcolor="white"><span tal:replace="x1"></span></td>
+ <td bgcolor="white"><span tal:replace="x2"></span></td>
+ <td bgcolor="white"><span tal:replace="x3"></span></td>
+ <td bgcolor="white"><span tal:replace="x4"></span></td>
+ <td bgcolor="white"><span tal:replace="x5"></span></td>
+ <td bgcolor="white"><span tal:replace="x6"></span></td>
+ <td bgcolor="white"><span tal:replace="x7"></span></td>
+
+ <td bgcolor="white"><span tal:replace="x0"></span></td>
+ <td bgcolor="white"><span tal:replace="x1"></span></td>
+ <td bgcolor="white"><span tal:replace="x2"></span></td>
+ <td bgcolor="white"><span tal:replace="x3"></span></td>
+ <td bgcolor="white"><span tal:replace="x4"></span></td>
+ <td bgcolor="white"><span tal:replace="x5"></span></td>
+ <td bgcolor="white"><span tal:replace="x6"></span></td>
+ <td bgcolor="white"><span tal:replace="x7"></span></td>
+
+ <td bgcolor="white"><span tal:replace="x0"></span></td>
+ <td bgcolor="white"><span tal:replace="x1"></span></td>
+ <td bgcolor="white"><span tal:replace="x2"></span></td>
+ <td bgcolor="white"><span tal:replace="x3"></span></td>
+ <td bgcolor="white"><span tal:replace="x4"></span></td>
+ <td bgcolor="white"><span tal:replace="x5"></span></td>
+ <td bgcolor="white"><span tal:replace="x6"></span></td>
+ <td bgcolor="white"><span tal:replace="x7"></span></td>
+
+ <td bgcolor="white"><span tal:replace="x0"></span></td>
+ <td bgcolor="white"><span tal:replace="x1"></span></td>
+ <td bgcolor="white"><span tal:replace="x2"></span></td>
+ <td bgcolor="white"><span tal:replace="x3"></span></td>
+ <td bgcolor="white"><span tal:replace="x4"></span></td>
+ <td bgcolor="white"><span tal:replace="x5"></span></td>
+ <td bgcolor="white"><span tal:replace="x6"></span></td>
+ <td bgcolor="white"><span tal:replace="x7"></span></td>
+
+ <td bgcolor="white"><span tal:replace="x0"></span></td>
+ <td bgcolor="white"><span tal:replace="x1"></span></td>
+ <td bgcolor="white"><span tal:replace="x2"></span></td>
+ <td bgcolor="white"><span tal:replace="x3"></span></td>
+ <td bgcolor="white"><span tal:replace="x4"></span></td>
+ <td bgcolor="white"><span tal:replace="x5"></span></td>
+ <td bgcolor="white"><span tal:replace="x6"></span></td>
+ <td bgcolor="white"><span tal:replace="x7"></span></td>
+
+ <td bgcolor="white"><span tal:replace="x0"></span></td>
+ <td bgcolor="white"><span tal:replace="x1"></span></td>
+ <td bgcolor="white"><span tal:replace="x2"></span></td>
+ <td bgcolor="white"><span tal:replace="x3"></span></td>
+ <td bgcolor="white"><span tal:replace="x4"></span></td>
+ <td bgcolor="white"><span tal:replace="x5"></span></td>
+ <td bgcolor="white"><span tal:replace="x6"></span></td>
+ <td bgcolor="white"><span tal:replace="x7"></span></td>
+
+ <td bgcolor="white"><span tal:replace="x0"></span></td>
+ <td bgcolor="white"><span tal:replace="x1"></span></td>
+ <td bgcolor="white"><span tal:replace="x2"></span></td>
+ <td bgcolor="white"><span tal:replace="x3"></span></td>
+ <td bgcolor="white"><span tal:replace="x4"></span></td>
+ <td bgcolor="white"><span tal:replace="x5"></span></td>
+ <td bgcolor="white"><span tal:replace="x6"></span></td>
+ <td bgcolor="white"><span tal:replace="x7"></span></td>
+
+ <td bgcolor="white"><span tal:replace="x0"></span></td>
+ <td bgcolor="white"><span tal:replace="x1"></span></td>
+ <td bgcolor="white"><span tal:replace="x2"></span></td>
+ <td bgcolor="white"><span tal:replace="x3"></span></td>
+ <td bgcolor="white"><span tal:replace="x4"></span></td>
+ <td bgcolor="white"><span tal:replace="x5"></span></td>
+ <td bgcolor="white"><span tal:replace="x6"></span></td>
+ <td bgcolor="white"><span tal:replace="x7"></span></td>
+
+
diff --git a/src/zope/tal/benchmark/tal09.html b/src/zope/tal/benchmark/tal09.html
new file mode 100644
index 0000000..ef81c58
--- /dev/null
+++ b/src/zope/tal/benchmark/tal09.html
@@ -0,0 +1,10 @@
+<dtml-in tal:repeat="r r64">
+ <td bgcolor="white" tal:content="x0"></td>
+ <td bgcolor="white" tal:content="x1"></td>
+ <td bgcolor="white" tal:content="x2"></td>
+ <td bgcolor="white" tal:content="x3"></td>
+ <td bgcolor="white" tal:content="x4"></td>
+ <td bgcolor="white" tal:content="x5"></td>
+ <td bgcolor="white" tal:content="x6"></td>
+ <td bgcolor="white" tal:content="x7"></td>
+</dtml-in>
diff --git a/src/zope/tal/benchmark/tal10.html b/src/zope/tal/benchmark/tal10.html
new file mode 100644
index 0000000..8026df7
--- /dev/null
+++ b/src/zope/tal/benchmark/tal10.html
@@ -0,0 +1,102 @@
+<dtml-in tal:repeat="r r64">
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+</dtml-in>
diff --git a/src/zope/tal/benchmark/tal11.html b/src/zope/tal/benchmark/tal11.html
new file mode 100644
index 0000000..d4a2440
--- /dev/null
+++ b/src/zope/tal/benchmark/tal11.html
@@ -0,0 +1,103 @@
+<dtml-in tal:repeat="r r64">
+ <td bgcolor="white" tal:content="x0"></td>
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+ A large chunk of text to be repeated.
+</dtml-in>
diff --git a/src/zope/tal/benchmark/tal12.html b/src/zope/tal/benchmark/tal12.html
new file mode 100644
index 0000000..dcd2c30
--- /dev/null
+++ b/src/zope/tal/benchmark/tal12.html
@@ -0,0 +1,12 @@
+<dtml-in tal:repeat="r r8">
+ <span tal:define="y0 x0;y1 x1;y2 x2;y3 x3;y4 x4;y5 x5;y6 x6;y7 x7">
+ <td bgcolor="white" tal:content="y0"></td>
+ <td bgcolor="white" tal:content="y1"></td>
+ <td bgcolor="white" tal:content="y2"></td>
+ <td bgcolor="white" tal:content="y3"></td>
+ <td bgcolor="white" tal:content="y4"></td>
+ <td bgcolor="white" tal:content="y5"></td>
+ <td bgcolor="white" tal:content="y6"></td>
+ <td bgcolor="white" tal:content="y7"></td>
+ </span>
+</dtml-in>
diff --git a/src/zope/tal/driver.py b/src/zope/tal/driver.py
new file mode 100644
index 0000000..033e28a
--- /dev/null
+++ b/src/zope/tal/driver.py
@@ -0,0 +1,209 @@
+#!/usr/bin/env python
+##############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Driver program to test METAL and TAL implementation.
+
+Usage: driver.py [options] [file]
+Options:
+ -h / --help
+ Print this message and exit.
+ -H / --html
+ -x / --xml
+ Explicitly choose HTML or XML input. The default is to automatically
+ select based on the file extension. These options are mutually
+ exclusive.
+ -l
+ Lenient structure insertion.
+ -m
+ Macro expansion only
+ -s
+ Print intermediate opcodes only
+ -t
+ Leave TAL/METAL attributes in output
+ -i
+ Leave I18N substitution strings un-interpolated.
+ -a
+ Enable source annotations
+
+$Id$
+"""
+
+import os
+import sys
+
+import getopt
+
+if __name__ == "__main__":
+ import setpath # Local hack to tweak sys.path etc.
+
+# Import local classes
+import zope.tal.taldefs
+from zope.tal.dummyengine import DummyEngine
+from zope.tal.dummyengine import DummyTranslationDomain
+
+FILE = "tests/input/test01.xml"
+
+class TestTranslations(DummyTranslationDomain):
+ def translate(self, 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 DummyTranslationDomain.translate(
+ self, msgid, mapping, context,
+ target_language, default=default)
+
+
+class TestEngine(DummyEngine):
+ def __init__(self, macros=None):
+ DummyEngine.__init__(self, macros)
+ self.translationDomain = 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=''):
+ print >> sys.stderr, __doc__
+ if msg:
+ print >> sys.stderr, msg
+ sys.exit(code)
+
+def main():
+ macros = 0
+ mode = None
+ showcode = 0
+ showtal = -1
+ sourceAnnotations = 0
+ strictinsert = 1
+ i18nInterpolate = 1
+ try:
+ opts, args = getopt.getopt(sys.argv[1:], "hHxlmstia",
+ ['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 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 opt == '-a':
+ sourceAnnotations = 1
+ 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,
+ sourceAnnotations=sourceAnnotations)
+
+def interpretit(it, engine=None, stream=None, tal=1, showtal=-1,
+ strictinsert=1, i18nInterpolate=1, sourceAnnotations=0):
+ from zope.tal.talinterpreter import TALInterpreter
+ program, macros = it
+ assert zope.tal.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,
+ sourceAnnotations=sourceAnnotations)()
+
+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"
+ from zope.tal.talgenerator import TALGenerator
+ filename = os.path.abspath(file)
+ prefix = os.path.dirname(os.path.abspath(__file__)) + os.path.sep
+ if filename.startswith(prefix):
+ filename = filename[len(prefix):]
+ filename = filename.replace(os.sep, '/') # test files expect slashes
+ if mode == "html":
+ from zope.tal.htmltalparser import HTMLTALParser
+ p = HTMLTALParser(gen=TALGenerator(source_file=filename, xml=0))
+ else:
+ from zope.tal.talparser import TALParser
+ p = TALParser(gen=TALGenerator(source_file=filename))
+ p.parseFile(file)
+ return p.getCode()
+
+def showit(it):
+ from pprint import pprint
+ pprint(it)
+
+if __name__ == "__main__":
+ main()
diff --git a/src/zope/tal/dummyengine.py b/src/zope/tal/dummyengine.py
new file mode 100644
index 0000000..2a35d84
--- /dev/null
+++ b/src/zope/tal/dummyengine.py
@@ -0,0 +1,332 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2002, 2003 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Dummy TAL expression engine so that I can test out the TAL implementation.
+
+$Id$
+"""
+import re
+
+from zope.interface import implements
+from zope.tal.taldefs import NAME_RE, TALExpressionError, ErrorInfo
+from zope.tal.interfaces import ITALExpressionCompiler, ITALExpressionEngine
+from zope.i18nmessageid import Message
+from zope.i18n.interfaces import ITranslationDomain
+
+Default = object()
+
+name_match = re.compile(r"(?s)(%s):(.*)\Z" % NAME_RE).match
+
+class CompilerError(Exception):
+ pass
+
+class DummyEngine(object):
+
+ position = None
+ source_file = None
+
+ implements(ITALExpressionCompiler, ITALExpressionEngine)
+
+ 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.translationDomain = DummyTranslationDomain()
+ self.useEngineAttrDicts = False
+
+ # zope.tal.interfaces.ITALExpressionCompiler
+
+ def getCompilerError(self):
+ return CompilerError
+
+ def compile(self, expr):
+ return "$%s$" % expr
+
+ # zope.tal.interfaces.ITALExpressionEngine
+
+ def setSourceFile(self, source_file):
+ self.source_file = source_file
+
+ def setPosition(self, position):
+ self.position = position
+
+ 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 getValue(self, name, default=None):
+ value = self.globals.get(name, default)
+ if value is default:
+ value = self.locals.get(name, default)
+ return 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 TALExpressionError("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 TALExpressionError("unrecognized expression: " + `expression`)
+
+ # implementation; can be overridden
+ 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 TALExpressionError("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 isinstance(text, (str, unicode, Message)):
+ return text
+ if text is not None and text is not Default:
+ text = str(text)
+ return text
+
+ def evaluateStructure(self, expr):
+ # TODO Should return None or a DOM tree
+ return self.evaluate(expr)
+
+ # implementation; can be overridden
+ def evaluateSequence(self, expr):
+ # TODO: 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 TALExpressionError("macro %s not found in file %s" %
+ (localName, file))
+ return macro
+
+ # internal
+ def findMacroFile(self, macroName):
+ if not macroName:
+ raise TALExpressionError("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, msgid, domain=None, mapping=None, default=None):
+ self.translationDomain.domain = domain
+ return self.translationDomain.translate(
+ msgid, mapping, default=default)
+
+ def evaluateCode(self, lang, code):
+ # We probably implement too much, but I use the dummy engine to test
+ # some of the issues that we will have.
+
+ # For testing purposes only
+ locals = {}
+ globals = {}
+ if self.useEngineAttrDicts:
+ globals = self.globals.copy()
+ locals = self.locals.copy()
+
+ assert lang == 'text/server-python'
+ import sys, StringIO
+
+ # Removing probable comments
+ if code.strip().startswith('<!--') and code.strip().endswith('-->'):
+ code = code.strip()[4:-3]
+
+ # Prepare code.
+ lines = code.split('\n')
+ lines = filter(lambda l: l.strip() != '', lines)
+ code = '\n'.join(lines)
+ # This saves us from all indentation issues :)
+ if code.startswith(' ') or code.startswith('\t'):
+ code = 'if 1 == 1:\n' + code + '\n'
+ tmp = sys.stdout
+ sys.stdout = StringIO.StringIO()
+ try:
+ exec code in globals, locals
+ finally:
+ result = sys.stdout
+ sys.stdout = tmp
+
+ # For testing purposes only
+ self.codeLocals = locals
+ self.codeGlobals = globals
+
+ self.locals.update(locals)
+ self.globals.update(globals)
+
+ return result.getvalue()
+
+class Iterator(object):
+
+ 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 DummyTranslationDomain(object):
+ implements(ITranslationDomain)
+
+ domain = ''
+
+ msgids = {}
+
+ def appendMsgid(self, domain, data):
+ if not self.msgids.has_key(domain):
+ self.msgids[domain] = []
+ self.msgids[domain].append(data)
+
+ def getMsgids(self, domain):
+ return self.msgids[domain]
+
+ def clearMsgids(self):
+ self.msgids = {}
+
+ def translate(self, msgid, mapping=None, context=None,
+ target_language=None, default=None):
+
+ domain = self.domain
+ # 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.
+
+ # If the domain is a string method, then transform the string
+ # by calling that method.
+
+ # MessageID attributes override arguments
+ if isinstance(msgid, Message):
+ domain = msgid.domain
+ mapping = msgid.mapping
+ default = msgid.default
+ if default is None: # Message doesn't substitute itself for
+ default = msgid # missing default
+
+ # simulate an unknown msgid by returning None
+ if msgid == "don't translate me":
+ text = default
+ elif domain and hasattr('', domain):
+ text = getattr(msgid, domain)()
+ else:
+ domain = 'default'
+ text = msgid.upper()
+
+ self.appendMsgid(domain, (msgid, mapping))
+
+ def repl(m):
+ return unicode(mapping[m.group(m.lastindex).lower()])
+ cre = re.compile(r'\$(?:([_A-Za-z][-\w]*)|\{([_A-Za-z][-\w]*)\})')
+ return cre.sub(repl, text)
+
+class MultipleDomainsDummyEngine(DummyEngine):
+
+ def translate(self, msgid, domain=None, mapping=None, default=None):
+
+ if isinstance(msgid, Message):
+ domain = msgid.domain
+
+ if domain == 'a_very_explicit_domain_setup_by_template_developer_that_wont_be_taken_into_account_by_the_ZPT_engine':
+ domain = 'lower'
+
+ self.translationDomain.domain = domain
+ return self.translationDomain.translate(
+ msgid, mapping, default=default)
+
diff --git a/src/zope/tal/htmltalparser.py b/src/zope/tal/htmltalparser.py
new file mode 100644
index 0000000..cae554e
--- /dev/null
+++ b/src/zope/tal/htmltalparser.py
@@ -0,0 +1,321 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Parse HTML and compile to TALInterpreter intermediate code.
+
+$Id$
+"""
+
+from HTMLParser import HTMLParser, HTMLParseError
+
+from zope.tal.taldefs import ZOPE_METAL_NS, ZOPE_TAL_NS, ZOPE_I18N_NS, \
+ METALError, TALError, I18NError
+from zope.tal.talgenerator import TALGenerator
+
+
+BOOLEAN_HTML_ATTRS = frozenset([
+ # 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 = frozenset([
+ # 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 = frozenset([
+ # 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 = frozenset([
+ # 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",
+ ])
+
+SECTION_LEVEL_HTML_TAGS = PARA_LEVEL_HTML_TAGS.union(BLOCK_LEVEL_HTML_TAGS)
+
+TIGHTEN_IMPLICIT_CLOSE_TAGS = PARA_LEVEL_HTML_TAGS.union(BLOCK_CLOSING_TAG_MAP)
+
+
+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()
+
+ # 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 "content" in taldict:
+ raise TALError(
+ "empty HTML tags cannot use tal:content: %s" % `tag`,
+ self.getpos())
+ # Support for inline Python code.
+ if tag == 'script':
+ type_attr = filter(lambda a: a[0] == 'type', attrlist)
+ if type_attr and type_attr[0][1].startswith('text/server-'):
+ attrlist.remove(type_attr[0])
+ taldict = {'script': type_attr[0][1], 'omit-tag': ''}
+ 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, position=self.getpos())
+ 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, position=self.getpos())
+ 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 SECTION_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,
+ position=self.getpos())
+ 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
diff --git a/src/zope/tal/interfaces.py b/src/zope/tal/interfaces.py
new file mode 100644
index 0000000..8b8b7f0
--- /dev/null
+++ b/src/zope/tal/interfaces.py
@@ -0,0 +1,207 @@
+##############################################################################
+#
+# Copyright (c) 2003 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.
+#
+##############################################################################
+"""Interface that a TAL expression implementation provides to the METAL/TAL
+implementation.
+
+$Id$
+"""
+from zope.interface import Attribute, Interface
+
+
+class ITALExpressionCompiler(Interface):
+ """Compile-time interface provided by a TAL expression implementation.
+
+ The TAL compiler needs an instance of this interface to support
+ compilation of TAL 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 ITALExpressionEngine interface. No compatibility is
+ required for the values of the compiled expression between
+ different ITALExpressionEngine implementations.
+ """
+
+ def getContext(namespace):
+ """Create an expression execution context
+
+ The given namespace provides the initial top-level names.
+ """
+
+class ITALExpressionEngine(Interface):
+ """Render-time interface provided by a TAL expression implementation.
+
+ The TAL interpreter uses this interface to TAL expression to support
+ evaluation of the compiled expressions returned by
+ ITALExpressionCompiler.compile().
+ """
+
+ def getDefault():
+ """Return the value of the 'default' TAL 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 contained 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.
+
+ If the expression evaluates to None, then that is returned. It
+ represents 'nothing' in TALES.
+ If the expression evaluates to what getDefault() of this interface
+ returns, by comparison using 'is', then that is returned. It
+ represents 'default' in TALES.
+ """
+
+ def evaluateValue(compiled_expression):
+ """Evaluate an arbitrary expression.
+
+ No constraints are imposed on the return value.
+ """
+
+ def createErrorInfo(exception, (lineno, offset)):
+ """Returns an ITALExpressionErrorInfo 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 getValue(name, default=None):
+ """Get a variable by name.
+
+ If the variable does not exist, return default.
+ """
+
+ def setRepeat(name, compiled_expression):
+ """Start a repetition, returning an ITALIterator.
+
+ The engine is expected to add the a value (typically the
+ returned iterator) for the name to the variable namespace.
+ """
+
+ def translate(msgid, domain=None, mapping=None, default=None):
+ """See zope.i18n.interfaces.ITranslationDomain.translate"""
+
+ # NB: This differs from the Zope 2 equivalent in the order of
+ # the arguments. This will be a (hopefully minor) issue when
+ # creating a unified TAL implementation.
+
+ def evaluateCode(lang, code):
+ """Evaluates code of the given language.
+
+ Returns whatever the code outputs. This can be defined on a
+ per-language basis. In Python this usually everything the print
+ statement will return.
+ """
+
+
+class ITALIterator(Interface):
+ """A TAL iterator
+
+ Not to be confused with a Python iterator.
+ """
+
+ def next():
+ """Advance to the next value in the iteration, if possible
+
+ Return a true value if it was possible to advance and return
+ a false value otherwise.
+ """
+
+
+class ITALExpressionErrorInfo(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.")
diff --git a/src/zope/tal/ndiff.py b/src/zope/tal/ndiff.py
new file mode 100644
index 0000000..7873abc
--- /dev/null
+++ b/src/zope/tal/ndiff.py
@@ -0,0 +1,649 @@
+#! /usr/bin/env python
+##############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+
+# 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.
+
+$Id$
+"""
+
+__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(object):
+ def __init__(self, isjunk=None, a='', b=''):
+ # Members:
+ # a
+ # first sequence
+ # b
+ # second sequence; differences are computed as "what do
+ # we need to do to 'a' to change it into 'b'?"
+ # b2j
+ # for x in b, b2j[x] is a list of the indices (into b)
+ # at which x appears; junk elements do not appear
+ # b2jhas
+ # b2j.has_key
+ # fullbcount
+ # for x in b, fullbcount[x] == the number of times x
+ # appears in b; only materialized if really needed (used
+ # only for computing quick_ratio())
+ # matching_blocks
+ # a list of (i, j, k) triples, where a[i:i+k] == b[j:j+k];
+ # ascending & non-overlapping in i and in j; terminated by
+ # a dummy (len(a), len(b), 0) sentinel
+ # opcodes
+ # a list of (tag, i1, i2, j1, j2) tuples, where tag is
+ # one of
+ # 'replace' a[i1:i2] should be replaced by b[j1:j2]
+ # 'delete' a[i1:i2] should be deleted
+ # 'insert' b[j1:j2] should be inserted
+ # 'equal' a[i1:i2] == b[j1:j2]
+ # isjunk
+ # a user-supplied function taking a sequence element and
+ # returning true iff the element is "junk" -- this has
+ # subtle but helpful effects on the algorithm, which I'll
+ # get around to writing up someday <0.9 wink>.
+ # DON'T USE! Only __chain_b uses this. Use isbjunk.
+ # isbjunk
+ # for x in b, isbjunk(x) == isjunk(x) but much faster;
+ # it's really the has_key method of a hidden dict.
+ # DOES NOT WORK for x in a!
+
+ self.isjunk = isjunk
+ self.a = self.b = None
+ self.set_seqs(a, b)
+
+ def set_seqs(self, a, b):
+ self.set_seq1(a)
+ self.set_seq2(b)
+
+ def set_seq1(self, a):
+ if a is self.a:
+ return
+ self.a = a
+ self.matching_blocks = self.opcodes = None
+
+ def set_seq2(self, b):
+ if b is self.b:
+ return
+ self.b = b
+ self.matching_blocks = self.opcodes = None
+ self.fullbcount = None
+ self.__chain_b()
+
+ # For each element x in b, set b2j[x] to a list of the indices in
+ # b where x appears; the indices are in increasing order; note that
+ # the number of times x appears in b is len(b2j[x]) ...
+ # when self.isjunk is defined, junk elements don't show up in this
+ # map at all, which stops the central find_longest_match method
+ # from starting any matching block at a junk element ...
+ # also creates the fast isbjunk function ...
+ # note that this is only called when b changes; so for cross-product
+ # kinds of matches, it's best to call set_seq2 once, then set_seq1
+ # repeatedly
+
+ def __chain_b(self):
+ # Because isjunk is a user-defined (not C) function, and we test
+ # for junk a LOT, it's important to minimize the number of calls.
+ # Before the tricks described here, __chain_b was by far the most
+ # time-consuming routine in the whole module! If anyone sees
+ # Jim Roskind, thank him again for profile.py -- I never would
+ # have guessed that.
+ # The first trick is to build b2j ignoring the possibility
+ # of junk. I.e., we don't call isjunk at all yet. Throwing
+ # out the junk later is much cheaper than building b2j "right"
+ # from the start.
+ b = self.b
+ self.b2j = b2j = {}
+ self.b2jhas = b2jhas = b2j.has_key
+ for i in xrange(len(b)):
+ elt = b[i]
+ if b2jhas(elt):
+ b2j[elt].append(i)
+ else:
+ b2j[elt] = [i]
+
+ # Now b2j.keys() contains elements uniquely, and especially when
+ # the sequence is a string, that's usually a good deal smaller
+ # than len(string). The difference is the number of isjunk calls
+ # saved.
+ isjunk, junkdict = self.isjunk, {}
+ if isjunk:
+ for elt in b2j.keys():
+ if isjunk(elt):
+ junkdict[elt] = 1 # value irrelevant; it's a set
+ del b2j[elt]
+
+ # Now for x in b, isjunk(x) == junkdict.has_key(x), but the
+ # latter is much faster. Note too that while there may be a
+ # lot of junk in the sequence, the number of *unique* junk
+ # elements is probably small. So the memory burden of keeping
+ # this dict alive is likely trivial compared to the size of b2j.
+ self.isbjunk = junkdict.has_key
+
+ def find_longest_match(self, alo, ahi, blo, bhi):
+ """Find longest matching block in a[alo:ahi] and b[blo:bhi].
+
+ If isjunk is not defined:
+
+ Return (i,j,k) such that a[i:i+k] is equal to b[j:j+k], where
+ alo <= i <= i+k <= ahi
+ blo <= j <= j+k <= bhi
+ and for all (i',j',k') meeting those conditions,
+ k >= k'
+ i <= i'
+ and if i == i', j <= j'
+ In other words, of all maximal matching blocks, return one
+ that starts earliest in a, and of all those maximal matching
+ blocks that start earliest in a, return the one that starts
+ earliest in b.
+
+ If isjunk is defined, first the longest matching block is
+ determined as above, but with the additional restriction that
+ no junk element appears in the block. Then that block is
+ extended as far as possible by matching (only) junk elements on
+ both sides. So the resulting block never matches on junk except
+ as identical junk happens to be adjacent to an "interesting"
+ match.
+
+ If no blocks match, return (alo, blo, 0).
+ """
+
+ # CAUTION: stripping common prefix or suffix would be incorrect.
+ # E.g.,
+ # ab
+ # acab
+ # Longest matching block is "ab", but if common prefix is
+ # stripped, it's "a" (tied with "b"). UNIX(tm) diff does so
+ # strip, so ends up claiming that ab is changed to acab by
+ # inserting "ca" in the middle. That's minimal but unintuitive:
+ # "it's obvious" that someone inserted "ac" at the front.
+ # Windiff ends up at the same place as diff, but by pairing up
+ # the unique 'b's and then matching the first two 'a's.
+
+ a, b, b2j, isbjunk = self.a, self.b, self.b2j, self.isbjunk
+ besti, bestj, bestsize = alo, blo, 0
+ # find longest junk-free match
+ # during an iteration of the loop, j2len[j] = length of longest
+ # junk-free match ending with a[i-1] and b[j]
+ j2len = {}
+ nothing = []
+ for i in xrange(alo, ahi):
+ # look at all instances of a[i] in b; note that because
+ # b2j has no junk keys, the loop is skipped if a[i] is junk
+ j2lenget = j2len.get
+ newj2len = {}
+ for j in b2j.get(a[i], nothing):
+ # a[i] matches b[j]
+ if j < blo:
+ continue
+ if j >= bhi:
+ break
+ k = newj2len[j] = j2lenget(j-1, 0) + 1
+ if k > bestsize:
+ besti, bestj, bestsize = i-k+1, j-k+1, k
+ j2len = newj2len
+
+ # Now that we have a wholly interesting match (albeit possibly
+ # empty!), we may as well suck up the matching junk on each
+ # side of it too. Can't think of a good reason not to, and it
+ # saves post-processing the (possibly considerable) expense of
+ # figuring out what to do with it. In the case of an empty
+ # interesting match, this is clearly the right thing to do,
+ # because no other kind of match is possible in the regions.
+ while besti > alo and bestj > blo and \
+ isbjunk(b[bestj-1]) and \
+ a[besti-1] == b[bestj-1]:
+ besti, bestj, bestsize = besti-1, bestj-1, bestsize+1
+ while besti+bestsize < ahi and bestj+bestsize < bhi and \
+ isbjunk(b[bestj+bestsize]) and \
+ a[besti+bestsize] == b[bestj+bestsize]:
+ bestsize = bestsize + 1
+
+ if TRACE:
+ print "get_matching_blocks", alo, ahi, blo, bhi
+ print " returns", besti, bestj, bestsize
+ return besti, bestj, bestsize
+
+ def get_matching_blocks(self):
+ if self.matching_blocks is not None:
+ return self.matching_blocks
+ self.matching_blocks = []
+ la, lb = len(self.a), len(self.b)
+ self.__helper(0, la, 0, lb, self.matching_blocks)
+ self.matching_blocks.append((la, lb, 0))
+ if TRACE:
+ print '*** matching blocks', self.matching_blocks
+ return self.matching_blocks
+
+ # builds list of matching blocks covering a[alo:ahi] and
+ # b[blo:bhi], appending them in increasing order to answer
+
+ def __helper(self, alo, ahi, blo, bhi, answer):
+ i, j, k = x = self.find_longest_match(alo, ahi, blo, bhi)
+ # a[alo:i] vs b[blo:j] unknown
+ # a[i:i+k] same as b[j:j+k]
+ # a[i+k:ahi] vs b[j+k:bhi] unknown
+ if k:
+ if alo < i and blo < j:
+ self.__helper(alo, i, blo, j, answer)
+ answer.append(x)
+ if i+k < ahi and j+k < bhi:
+ self.__helper(i+k, ahi, j+k, bhi, answer)
+
+ def ratio(self):
+ """Return a measure of the sequences' similarity (float in [0,1]).
+
+ Where T is the total number of elements in both sequences, and
+ M is the number of matches, this is 2*M / T.
+ Note that this is 1 if the sequences are identical, and 0 if
+ they have nothing in common.
+ """
+
+ matches = reduce(lambda sum, triple: sum + triple[-1],
+ self.get_matching_blocks(), 0)
+ return 2.0 * matches / (len(self.a) + len(self.b))
+
+ def quick_ratio(self):
+ """Return an upper bound on ratio() relatively quickly."""
+ # viewing a and b as multisets, set matches to the cardinality
+ # of their intersection; this counts the number of matches
+ # without regard to order, so is clearly an upper bound
+ if self.fullbcount is None:
+ self.fullbcount = fullbcount = {}
+ for elt in self.b:
+ fullbcount[elt] = fullbcount.get(elt, 0) + 1
+ fullbcount = self.fullbcount
+ # avail[x] is the number of times x appears in 'b' less the
+ # number of times we've seen it in 'a' so far ... kinda
+ avail = {}
+ availhas, matches = avail.has_key, 0
+ for elt in self.a:
+ if availhas(elt):
+ numb = avail[elt]
+ else:
+ numb = fullbcount.get(elt, 0)
+ avail[elt] = numb - 1
+ if numb > 0:
+ matches = matches + 1
+ return 2.0 * matches / (len(self.a) + len(self.b))
+
+ def real_quick_ratio(self):
+ """Return an upper bound on ratio() very quickly"""
+ la, lb = len(self.a), len(self.b)
+ # can't have more matches than the number of elements in the
+ # shorter sequence
+ return 2.0 * min(la, lb) / (la + lb)
+
+ def get_opcodes(self):
+ if self.opcodes is not None:
+ return self.opcodes
+ i = j = 0
+ self.opcodes = answer = []
+ for ai, bj, size in self.get_matching_blocks():
+ # invariant: we've pumped out correct diffs to change
+ # a[:i] into b[:j], and the next matching block is
+ # a[ai:ai+size] == b[bj:bj+size]. So we need to pump
+ # out a diff to change a[i:ai] into b[j:bj], pump out
+ # the matching block, and move (i,j) beyond the match
+ tag = ''
+ if i < ai and j < bj:
+ tag = 'replace'
+ elif i < ai:
+ tag = 'delete'
+ elif j < bj:
+ tag = 'insert'
+ if tag:
+ answer.append((tag, i, ai, j, bj))
+ i, j = ai+size, bj+size
+ # the list of matching blocks is terminated by a
+ # sentinel with size 0
+ if size:
+ answer.append(('equal', ai, i, bj, j))
+ return answer
+
+# meant for dumping lines
+def dump(tag, x, lo, hi):
+ for i in xrange(lo, hi):
+ print tag, x[i],
+
+def plain_replace(a, alo, ahi, b, blo, bhi):
+ assert alo < ahi and blo < bhi
+ # dump the shorter block first -- reduces the burden on short-term
+ # memory if the blocks are of very different sizes
+ if bhi - blo < ahi - alo:
+ dump('+', b, blo, bhi)
+ dump('-', a, alo, ahi)
+ else:
+ dump('-', a, alo, ahi)
+ dump('+', b, blo, bhi)
+
+# When replacing one block of lines with another, this guy searches
+# the blocks for *similar* lines; the best-matching pair (if any) is
+# used as a synch point, and intraline difference marking is done on
+# the similar pair. Lots of work, but often worth it.
+
+def fancy_replace(a, alo, ahi, b, blo, bhi):
+ if TRACE:
+ print '*** fancy_replace', alo, ahi, blo, bhi
+ dump('>', a, alo, ahi)
+ dump('<', b, blo, bhi)
+
+ # don't synch up unless the lines have a similarity score of at
+ # least cutoff; best_ratio tracks the best score seen so far
+ best_ratio, cutoff = 0.74, 0.75
+ cruncher = SequenceMatcher(IS_CHARACTER_JUNK)
+ eqi, eqj = None, None # 1st indices of equal lines (if any)
+
+ # search for the pair that matches best without being identical
+ # (identical lines must be junk lines, & we don't want to synch up
+ # on junk -- unless we have to)
+ for j in xrange(blo, bhi):
+ bj = b[j]
+ cruncher.set_seq2(bj)
+ for i in xrange(alo, ahi):
+ ai = a[i]
+ if ai == bj:
+ if eqi is None:
+ eqi, eqj = i, j
+ continue
+ cruncher.set_seq1(ai)
+ # computing similarity is expensive, so use the quick
+ # upper bounds first -- have seen this speed up messy
+ # compares by a factor of 3.
+ # note that ratio() is only expensive to compute the first
+ # time it's called on a sequence pair; the expensive part
+ # of the computation is cached by cruncher
+ if cruncher.real_quick_ratio() > best_ratio and \
+ cruncher.quick_ratio() > best_ratio and \
+ cruncher.ratio() > best_ratio:
+ best_ratio, best_i, best_j = cruncher.ratio(), i, j
+ if best_ratio < cutoff:
+ # no non-identical "pretty close" pair
+ if eqi is None:
+ # no identical pair either -- treat it as a straight replace
+ plain_replace(a, alo, ahi, b, blo, bhi)
+ return
+ # no close pair, but an identical pair -- synch up on that
+ best_i, best_j, best_ratio = eqi, eqj, 1.0
+ else:
+ # there's a close pair, so forget the identical pair (if any)
+ eqi = None
+
+ # a[best_i] very similar to b[best_j]; eqi is None iff they're not
+ # identical
+ if TRACE:
+ print '*** best_ratio', best_ratio, best_i, best_j
+ dump('>', a, best_i, best_i+1)
+ dump('<', b, best_j, best_j+1)
+
+ # pump out diffs from before the synch point
+ fancy_helper(a, alo, best_i, b, blo, best_j)
+
+ # do intraline marking on the synch pair
+ aelt, belt = a[best_i], b[best_j]
+ if eqi is None:
+ # pump out a '-', '?', '+', '?' quad for the synched lines
+ atags = btags = ""
+ cruncher.set_seqs(aelt, belt)
+ for tag, ai1, ai2, bj1, bj2 in cruncher.get_opcodes():
+ la, lb = ai2 - ai1, bj2 - bj1
+ if tag == 'replace':
+ atags = atags + '^' * la
+ btags = btags + '^' * lb
+ elif tag == 'delete':
+ atags = atags + '-' * la
+ elif tag == 'insert':
+ btags = btags + '+' * lb
+ elif tag == 'equal':
+ atags = atags + ' ' * la
+ btags = btags + ' ' * lb
+ else:
+ raise ValueError('unknown tag ' + `tag`)
+ printq(aelt, belt, atags, btags)
+ else:
+ # the synch pair is identical
+ print ' ', aelt,
+
+ # pump out diffs from after the synch point
+ fancy_helper(a, best_i+1, ahi, b, best_j+1, bhi)
+
+def fancy_helper(a, alo, ahi, b, blo, bhi):
+ if alo < ahi:
+ if blo < bhi:
+ fancy_replace(a, alo, ahi, b, blo, bhi)
+ else:
+ dump('-', a, alo, ahi)
+ elif blo < bhi:
+ dump('+', b, blo, bhi)
+
+# Crap to deal with leading tabs in "?" output. Can hurt, but will
+# probably help most of the time.
+
+def printq(aline, bline, atags, btags):
+ common = min(count_leading(aline, "\t"),
+ count_leading(bline, "\t"))
+ common = min(common, count_leading(atags[:common], " "))
+ print "-", aline,
+ if count_leading(atags, " ") < len(atags):
+ print "?", "\t" * common + atags[common:]
+ print "+", bline,
+ if count_leading(btags, " ") < len(btags):
+ print "?", "\t" * common + btags[common:]
+
+def count_leading(line, ch):
+ i, n = 0, len(line)
+ while i < n and line[i] == ch:
+ i = i + 1
+ return i
+
+def fail(msg):
+ import sys
+ out = sys.stderr.write
+ out(msg + "\n\n")
+ out(__doc__)
+ return 0
+
+# open a file & return the file object; gripe and return 0 if it
+# couldn't be opened
+def fopen(fname):
+ try:
+ return open(fname, 'r')
+ except IOError, detail:
+ return fail("couldn't open " + fname + ": " + str(detail))
+
+# open two files & spray the diff to stdout; return false iff a problem
+def fcompare(f1name, f2name):
+ f1 = fopen(f1name)
+ f2 = fopen(f2name)
+ if not f1 or not f2:
+ return 0
+
+ a = f1.readlines(); f1.close()
+ b = f2.readlines(); f2.close()
+
+ cruncher = SequenceMatcher(IS_LINE_JUNK, a, b)
+ for tag, alo, ahi, blo, bhi in cruncher.get_opcodes():
+ if tag == 'replace':
+ fancy_replace(a, alo, ahi, b, blo, bhi)
+ elif tag == 'delete':
+ dump('-', a, alo, ahi)
+ elif tag == 'insert':
+ dump('+', b, blo, bhi)
+ elif tag == 'equal':
+ dump(' ', a, alo, ahi)
+ else:
+ raise ValueError('unknown tag ' + `tag`)
+
+ return 1
+
+# crack args (sys.argv[1:] is normal) & compare;
+# return false iff a problem
+
+def main(args):
+ import getopt
+ try:
+ opts, args = getopt.getopt(args, "qr:")
+ except getopt.error, detail:
+ return fail(str(detail))
+ noisy = 1
+ qseen = rseen = 0
+ for opt, val in opts:
+ if opt == "-q":
+ qseen = 1
+ noisy = 0
+ elif opt == "-r":
+ rseen = 1
+ whichfile = val
+ if qseen and rseen:
+ return fail("can't specify both -q and -r")
+ if rseen:
+ if args:
+ return fail("no args allowed with -r option")
+ if whichfile in "12":
+ restore(whichfile)
+ return 1
+ return fail("-r value must be 1 or 2")
+ if len(args) != 2:
+ return fail("need 2 filename args")
+ f1name, f2name = args
+ if noisy:
+ print '-:', f1name
+ print '+:', f2name
+ return fcompare(f1name, f2name)
+
+def restore(which):
+ import sys
+ tag = {"1": "- ", "2": "+ "}[which]
+ prefixes = (" ", tag)
+ for line in sys.stdin.readlines():
+ if line[:2] in prefixes:
+ print line[2:],
+
+if __name__ == '__main__':
+ import sys
+ args = sys.argv[1:]
+ if "-profile" in args:
+ import profile, pstats
+ args.remove("-profile")
+ statf = "ndiff.pro"
+ profile.run("main(args)", statf)
+ stats = pstats.Stats(statf)
+ stats.strip_dirs().sort_stats('time').print_stats()
+ else:
+ main(args)
diff --git a/src/zope/tal/runtest.py b/src/zope/tal/runtest.py
new file mode 100644
index 0000000..1cb75c8
--- /dev/null
+++ b/src/zope/tal/runtest.py
@@ -0,0 +1,154 @@
+#! /usr/bin/env python
+##############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Driver program to run METAL and TAL regression tests.
+
+$Id$
+"""
+import glob
+import os
+import sys
+import traceback
+
+from cStringIO import StringIO
+
+if __name__ == "__main__":
+ import setpath # Local hack to tweak sys.path etc.
+
+import zope.tal.driver
+import zope.tal.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 zope.tal.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 arg.find("_sa") >= 0 and "-a" not in opts:
+ locopts.append("-a")
+ if not unittesting:
+ print arg,
+ sys.stdout.flush()
+ if zope.tal.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]
+ zope.tal.driver.main()
+ finally:
+ sys.stdout, sys.argv = save
+ except SystemExit:
+ raise
+ except:
+ errors = 1
+ if quiet:
+ print sys.exc_type
+ sys.stdout.flush()
+ else:
+ if unittesting:
+ print
+ else:
+ print "Failed:"
+ sys.stdout.flush()
+ traceback.print_exc()
+ continue
+ head, tail = os.path.split(arg)
+ outfile = os.path.join(
+ head.replace("input", "output"),
+ tail)
+ try:
+ f = open(outfile)
+ except IOError:
+ expected = None
+ print "(missing file %s)" % outfile,
+ else:
+ expected = f.readlines()
+ f.close()
+ stdout.seek(0)
+ if hasattr(stdout, "readlines"):
+ actual = stdout.readlines()
+ else:
+ actual = readlines(stdout)
+ if actual == expected:
+ if not unittesting:
+ print "OK"
+ else:
+ if unittesting:
+ print
+ else:
+ print "not OK"
+ errors = 1
+ if not quiet and expected is not None:
+ showdiff(expected, actual)
+ if errors:
+ sys.exit(1)
+
+def readlines(f):
+ L = []
+ while 1:
+ line = f.readline()
+ if not line:
+ break
+ L.append(line)
+ return L
+
+if __name__ == "__main__":
+ main()
diff --git a/src/zope/tal/setpath.py b/src/zope/tal/setpath.py
new file mode 100644
index 0000000..18eced9
--- /dev/null
+++ b/src/zope/tal/setpath.py
@@ -0,0 +1,48 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Read a module search path from .path file.
+
+If .path file isn't found in the directory of the setpath.py module, then try
+to import ZODB. If that succeeds, we assume the path is already set up
+correctly. If that import fails, an IOError is raised.
+
+$Id$
+"""
+
+# TODO: Why does this want to find ZODB ???
+
+import os
+import sys
+
+dir = os.path.dirname(__file__)
+path = os.path.join(dir, ".path")
+try:
+ f = open(path)
+except IOError:
+ try:
+ # If we can import ZODB, our sys.path is set up well enough already
+ import ZODB
+ except ImportError:
+ raise IOError("Can't find ZODB package. Please edit %s to point to "
+ "your Zope's lib/python directory" % path)
+else:
+ for line in f.readlines():
+ line = line.strip()
+ if line and line[0] != '#':
+ for dir in line.split(os.pathsep):
+ dir = os.path.expanduser(os.path.expandvars(dir))
+ if dir not in sys.path:
+ sys.path.append(dir)
+ # Must import this first to initialize Persistence properly
+ import ZODB
diff --git a/src/zope/tal/taldefs.py b/src/zope/tal/taldefs.py
new file mode 100644
index 0000000..554a4fe
--- /dev/null
+++ b/src/zope/tal/taldefs.py
@@ -0,0 +1,201 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Common definitions used by TAL and METAL compilation and transformation.
+
+$Id$
+"""
+import re
+from zope.tal.interfaces import ITALExpressionErrorInfo
+from zope.interface import implements
+
+
+TAL_VERSION = "1.6"
+
+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 = frozenset([
+ "define-macro",
+ "extend-macro",
+ "use-macro",
+ "define-slot",
+ "fill-slot",
+ ])
+
+KNOWN_TAL_ATTRIBUTES = frozenset([
+ "define",
+ "condition",
+ "content",
+ "replace",
+ "repeat",
+ "attributes",
+ "on-error",
+ "omit-tag",
+ "script",
+ "tal tag", # a pseudo attribute that holds the namespace of elements
+ # like <tal:x>, <metal:y>, <i18n:z>
+ ])
+
+KNOWN_I18N_ATTRIBUTES = frozenset([
+ "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 TALExpressionError(TALError):
+ pass
+
+class I18NError(TALError):
+ pass
+
+
+class ErrorInfo(object):
+ implements(ITALExpressionErrorInfo)
+
+ 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]
+
+
+_attr_re = re.compile(r"\s*([^\s]+)\s+([^\s].*)\Z", re.S)
+_subst_re = re.compile(r"\s*(?:(text|structure)\s+)?(.*)\Z", re.S)
+
+def parseAttributeReplacements(arg, xml):
+ dict = {}
+ for part in splitParts(arg):
+ m = _attr_re.match(part)
+ if not m:
+ raise TALError("Bad syntax in attributes: %r" % part)
+ name, expr = m.groups()
+ if not xml:
+ name = name.lower()
+ if name in dict:
+ raise TALError("Duplicate attribute name in attributes: %r" % 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: %r" % arg, position)
+ key, expr = m.groups()
+ 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 isinstance_(ob, type):
+ # Proxy-friendly and faster isinstance_ check for new-style objects
+ try:
+ return type in ob.__class__.__mro__
+ except AttributeError:
+ return False
+
+
+def getProgramMode(program):
+ version = getProgramVersion(program)
+ if (version == TAL_VERSION and isinstance_(program[1], tuple) 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], tuple) and len(program[0]) == 2):
+ opcode, version = program[0]
+ if opcode == "version":
+ return version
+ return None
+
+_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;])')
+
+def attrEscape(s):
+ """Replace special characters '&<>' by character entities,
+ except when '&' already begins a syntactically valid entity."""
+ s = _ent1_re.sub('&amp;', s)
+ s = _entch_re.sub(r'&amp;\1', s)
+ s = _entn1_re.sub('&amp;#', s)
+ s = _entnx_re.sub(r'&amp;\1', s)
+ s = _entnd_re.sub(r'&amp;\1', s)
+ s = s.replace('<', '&lt;')
+ s = s.replace('>', '&gt;')
+ s = s.replace('"', '&quot;')
+ return s
+
+import cgi
+def quote(s, escape=cgi.escape):
+ return '"%s"' % escape(s, 1)
+del cgi
diff --git a/src/zope/tal/talgenerator.py b/src/zope/tal/talgenerator.py
new file mode 100644
index 0000000..e01bd50
--- /dev/null
+++ b/src/zope/tal/talgenerator.py
@@ -0,0 +1,856 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Code generator for TALInterpreter intermediate code.
+
+$Id$
+"""
+import cgi
+import re
+
+from zope.tal import taldefs
+from zope.tal.taldefs import NAME_RE, TAL_VERSION
+from zope.tal.taldefs import I18NError, METALError, TALError
+from zope.tal.taldefs import parseSubstitution
+from zope.tal.translationcontext import TranslationContext, DEFAULT_DOMAIN
+
+
+_name_rx = re.compile(NAME_RE)
+
+class TALGenerator(object):
+
+ inMacroUse = 0
+ inMacroDef = 0
+ source_file = None
+
+ def __init__(self, expressionCompiler=None, xml=1, source_file=None):
+ if not expressionCompiler:
+ from zope.tal.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 = {}
+ # {slot-name --> default content program}
+ self.slots = {}
+ self.slotStack = []
+ self.xml = xml # true --> XML, false --> HTML
+ 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
+ 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":
+ endsep = self.xml and "/>" or " />"
+ if self.optimizeStartTag(collect, item[1], item[2], endsep):
+ continue
+ if opcode in ("beginScope", "endScope"):
+ # Push *Scope instructions in front of any text instructions;
+ # this allows text instructions separated only by *Scope
+ # instructions to be joined together.
+ output.append(self.optimizeArgsList(item))
+ continue
+ if opcode == 'noop':
+ # This is a spacer for end tags in the face of i18n:name
+ # attributes. We can't let the optimizer collect immediately
+ # following end tags into the same rawtextOffset.
+ opcode = None
+ pass
+ text = "".join(collect)
+ if text:
+ i = text.rfind("\n")
+ if i >= 0:
+ i = len(text) - (i + 1)
+ output.append(("rawtextColumn", (text, i)))
+ else:
+ output.append(("rawtextOffset", (text, len(text))))
+ if opcode != None:
+ output.append(self.optimizeArgsList(item))
+ collect = []
+ return self.optimizeCommonTriple(output)
+
+ def optimizeArgsList(self, item):
+ if len(item) == 2:
+ return item
+ else:
+ return item[0], tuple(item[1:])
+
+ # These codes are used to indicate what sort of special actions
+ # are needed for each special attribute. (Simple attributes don't
+ # get action codes.)
+ #
+ # The special actions (which are modal) are handled by
+ # TALInterpreter.attrAction() and .attrAction_tal().
+ #
+ # Each attribute is represented by a tuple:
+ #
+ # (name, value) -- a simple name/value pair, with
+ # no special processing
+ #
+ # (name, value, action, *extra) -- attribute with special
+ # processing needs, action is a
+ # code that indicates which
+ # branch to take, and *extra
+ # contains additional,
+ # action-specific information
+ # needed by the processing
+ #
+ def optimizeStartTag(self, collect, name, attrlist, end):
+ # return true if the tag can be converted to plain text
+ if not attrlist:
+ collect.append("<%s%s" % (name, end))
+ return 1
+ opt = 1
+ new = ["<" + name]
+ for i in range(len(attrlist)):
+ item = attrlist[i]
+ if len(item) > 2:
+ opt = 0
+ name, value, action = item[:3]
+ attrlist[i] = (name, value, action) + item[3:]
+ else:
+ if item[1] is None:
+ s = item[0]
+ else:
+ s = '%s="%s"' % (item[0], taldefs.attrEscape(item[1]))
+ attrlist[i] = item[0], s
+ new.append(" " + s)
+ # if no non-optimizable attributes were found, convert to plain text
+ if opt:
+ new.append(end)
+ collect.extend(new)
+ return opt
+
+ def optimizeCommonTriple(self, program):
+ if len(program) < 3:
+ return program
+ output = program[:2]
+ prev2, prev1 = output
+ for item in program[2:]:
+ if ( item[0] == "beginScope"
+ and prev1[0] == "setPosition"
+ and prev2[0] == "rawtextColumn"):
+ position = output.pop()[1]
+ text, column = output.pop()[1]
+ prev1 = None, None
+ closeprev = 0
+ if output and output[-1][0] == "endScope":
+ closeprev = 1
+ output.pop()
+ item = ("rawtextBeginScope",
+ (text, column, position, closeprev, item[1]))
+ output.append(item)
+ prev2 = prev1
+ prev1 = item
+ return output
+
+ def todoPush(self, todo):
+ self.todoStack.append(todo)
+
+ def todoPop(self):
+ return self.todoStack.pop()
+
+ def compileExpression(self, expr):
+ try:
+ return self.expressionCompiler.compile(expr)
+ except self.CompilerError, err:
+ raise TALError('%s in expression %s' % (err.args[0], `expr`),
+ self.position)
+
+ def pushProgram(self):
+ self.stack.append(self.program)
+ self.program = []
+
+ def popProgram(self):
+ program = self.program
+ self.program = self.stack.pop()
+ return self.optimize(program)
+
+ def pushSlots(self):
+ self.slotStack.append(self.slots)
+ self.slots = {}
+
+ def popSlots(self):
+ slots = self.slots
+ self.slots = self.slotStack.pop()
+ return slots
+
+ def emit(self, *instruction):
+ self.program.append(instruction)
+
+ def emitStartTag(self, name, attrlist, isend=0):
+ if isend:
+ opcode = "startEndTag"
+ else:
+ opcode = "startTag"
+ self.emit(opcode, name, attrlist)
+
+ def emitEndTag(self, name):
+ if self.xml and self.program and self.program[-1][0] == "startTag":
+ # Minimize empty element
+ self.program[-1] = ("startEndTag",) + self.program[-1][1:]
+ else:
+ self.emit("endTag", name)
+
+ def emitOptTag(self, name, optTag, isend):
+ program = self.popProgram() #block
+ start = self.popProgram() #start tag
+ if (isend or not program) and self.xml:
+ # Minimize empty element
+ start[-1] = ("startEndTag",) + start[-1][1:]
+ isend = 1
+ cexpr = optTag[0]
+ if cexpr:
+ cexpr = self.compileExpression(optTag[0])
+ self.emit("optTag", name, cexpr, optTag[1], isend, start, program)
+
+ def emitRawText(self, text):
+ self.emit("rawtext", text)
+
+ def emitText(self, text):
+ self.emitRawText(cgi.escape(text))
+
+ def emitDefines(self, defines):
+ for part in taldefs.splitParts(defines):
+ m = re.match(
+ r"(?s)\s*(?:(global|local)\s+)?(%s)\s+(.*)\Z" % NAME_RE, part)
+ if not m:
+ raise TALError("invalid define syntax: " + `part`,
+ self.position)
+ scope, name, expr = m.group(1, 2, 3)
+ scope = scope or "local"
+ cexpr = self.compileExpression(expr)
+ if scope == "local":
+ self.emit("setLocal", name, cexpr)
+ else:
+ self.emit("setGlobal", name, cexpr)
+
+ def emitOnError(self, name, onError, TALtag, isend):
+ block = self.popProgram()
+ key, expr = parseSubstitution(onError)
+ cexpr = self.compileExpression(expr)
+ if key == "text":
+ self.emit("insertText", cexpr, [])
+ else:
+ assert key == "structure"
+ self.emit("insertStructure", cexpr, {}, [])
+ if TALtag:
+ self.emitOptTag(name, (None, 1), isend)
+ else:
+ self.emitEndTag(name)
+ handler = self.popProgram()
+ self.emit("onError", block, handler)
+
+ def emitCondition(self, expr):
+ cexpr = self.compileExpression(expr)
+ program = self.popProgram()
+ self.emit("condition", cexpr, program)
+
+ def emitRepeat(self, arg):
+ m = re.match("(?s)\s*(%s)\s+(.*)\Z" % NAME_RE, arg)
+ if not m:
+ raise TALError("invalid repeat syntax: " + `arg`,
+ self.position)
+ name, expr = m.group(1, 2)
+ cexpr = self.compileExpression(expr)
+ program = self.popProgram()
+ self.emit("loop", name, cexpr, program)
+
+ def emitSubstitution(self, arg, attrDict={}):
+ key, expr = parseSubstitution(arg)
+ cexpr = self.compileExpression(expr)
+ program = self.popProgram()
+ if key == "text":
+ self.emit("insertText", cexpr, program)
+ else:
+ assert key == "structure"
+ self.emit("insertStructure", cexpr, attrDict, program)
+
+ def emitI18nSubstitution(self, arg, attrDict={}):
+ # TODO: Code duplication is BAD, we need to fix it later
+ key, expr = parseSubstitution(arg)
+ cexpr = self.compileExpression(expr)
+ program = self.popProgram()
+ if key == "text":
+ self.emit("insertI18nText", cexpr, program)
+ else:
+ assert key == "structure"
+ self.emit("insertI18nStructure", cexpr, attrDict, program)
+
+ def emitEvaluateCode(self, lang):
+ program = self.popProgram()
+ self.emit('evaluateCode', lang, program)
+
+ def emitI18nVariable(self, varname):
+ # Used for i18n:name attributes.
+ m = _name_rx.match(varname)
+ if m is None or m.group() != varname:
+ raise TALError("illegal i18n:name: %r" % varname, self.position)
+ program = self.popProgram()
+ self.emit('i18nVariable', varname, program, None, False)
+
+ 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 emitExtendMacro(self, defineName, useExpr):
+ cexpr = self.compileExpression(useExpr)
+ program = self.popProgram()
+ self.inMacroUse = 0
+ self.emit("extendMacro", useExpr, cexpr, self.popSlots(), program,
+ defineName)
+ self.emitDefineMacro(defineName)
+
+ 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, msgid) 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, msgid)
+ 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
+
+ # TODO: Ugly hack to work around tal:replace and i18n:translate issue.
+ # I (DV) need to cleanup the code later.
+ replaced = False
+ if "replace" in taldict:
+ if "content" in taldict:
+ raise TALError(
+ "tal:content and tal:replace are mutually exclusive",
+ position)
+ taldict["omit-tag"] = taldict.get("omit-tag", "")
+ taldict["content"] = taldict.pop("replace")
+ replaced = True
+
+ 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")
+ extendMacro = metaldict.get("extend-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")
+ script = taldict.get("script")
+ 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 extendMacro:
+ if useMacro:
+ raise METALError(
+ "extend-macro cannot be used with use-macro", position)
+ if not defineMacro:
+ raise METALError(
+ "extend-macro must be used with define-macro", position)
+
+ if defineMacro or extendMacro or useMacro:
+ if fillSlot or defineSlot:
+ raise METALError(
+ "define-slot and fill-slot cannot be used with "
+ "define-macro, extend-macro, or use-macro", position)
+ if defineMacro and useMacro:
+ raise METALError(
+ "define-macro may not be used with use-macro", position)
+
+ useMacro = useMacro or extendMacro
+
+ if content and msgid:
+ raise I18NError(
+ "explicit message id and tal:content can't be used together",
+ position)
+
+ repeatWhitespace = None
+ if repeat:
+ # Hack to include preceding whitespace in the loop program
+ repeatWhitespace = self.unEmitNewlineWhitespace()
+ if position != (None, None):
+ # TODO: at some point we should insist on a non-trivial position
+ self.emit("setPosition", position)
+ if self.inMacroUse:
+ if fillSlot:
+ self.pushProgram()
+ # generate a source annotation at the beginning of fill-slot
+ if self.source_file is not None:
+ if position != (None, None):
+ self.emit("setPosition", position)
+ 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")
+ # generate a source annotation at the beginning of the macro
+ if self.source_file is not None:
+ if position != (None, None):
+ self.emit("setPosition", position)
+ 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
+ todo["content"] = content
+ self.pushProgram()
+ else:
+ todo["content"] = content
+ # i18n:name w/o tal:replace uses the content as the interpolation
+ # dictionary values
+ elif varname:
+ todo['i18nvar'] = varname
+ 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, self.position,
+ self.xml)
+ 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 key not in repldict:
+ repldict[key] = None, 1, i18nattrs.get(key)
+ else:
+ repldict = {}
+ if replaced:
+ todo["repldict"] = repldict
+ repldict = {}
+ if script:
+ todo["script"] = script
+ self.emitStartTag(name, self.replaceAttrs(attrlist, repldict), isend)
+ if optTag:
+ self.pushProgram()
+ if content and not varname:
+ self.pushProgram()
+ if not content and msgid is not None:
+ self.pushProgram()
+ if content and varname:
+ self.pushProgram()
+ if script:
+ self.pushProgram()
+ if todo and position != (None, None):
+ todo["position"] = position
+ self.todoPush(todo)
+ if isend:
+ self.emitEndElement(name, isend, position=position)
+
+ def emitEndElement(self, name, isend=0, implied=0, position=(None, None)):
+ todo = self.todoPop()
+ if not todo:
+ # Shortcut
+ if not isend:
+ self.emitEndTag(name)
+ return
+
+ self.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")
+ script = todo.get("script")
+ 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), self.position)
+
+ if script:
+ self.emitEvaluateCode(script)
+ # If there's no tal:content or tal:replace in the tag with the
+ # i18n:name, tal:replace is the default.
+ if content:
+ if msgid is not None:
+ self.emitI18nSubstitution(content, repldict)
+ else:
+ self.emitSubstitution(content, repldict)
+ # 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 not content and msgid is not None:
+ 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 varname:
+ self.emitI18nVariable(varname)
+ 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 or defineMacro:
+ if useMacro and defineMacro:
+ self.emitExtendMacro(defineMacro, useMacro)
+ elif useMacro:
+ self.emitUseMacro(useMacro)
+ elif defineMacro:
+ self.emitDefineMacro(defineMacro)
+ if useMacro or defineSlot:
+ # generate a source annotation after define-slot or use-macro
+ # because the source file might have changed
+ if self.source_file is not None:
+ if position != (None, None):
+ self.emit("setPosition", position)
+ self.emit("setSourceFile", self.source_file)
+
+
+def _parseI18nAttributes(i18nattrs, position, xml):
+ d = {}
+ # Filter out empty items, eg:
+ # i18n:attributes="value msgid; name msgid2;"
+ # would result in 3 items where the last one is empty
+ attrs = [spec for spec in i18nattrs.split(";") if spec]
+ for spec in attrs:
+ parts = spec.split()
+ if len(parts) == 2:
+ attr, msgid = parts
+ elif len(parts) == 1:
+ attr = parts[0]
+ msgid = None
+ else:
+ raise TALError("illegal i18n:attributes specification: %r" % spec,
+ position)
+ if not xml:
+ attr = attr.lower()
+ if attr in d:
+ raise TALError(
+ "attribute may only be specified once in i18n:attributes: %r"
+ % attr,
+ position)
+ d[attr] = msgid
+ return d
+
+def test():
+ t = TALGenerator()
+ t.pushProgram()
+ t.emit("bar")
+ p = t.popProgram()
+ t.emit("foo", p)
+
+if __name__ == "__main__":
+ test()
diff --git a/src/zope/tal/talgettext.py b/src/zope/tal/talgettext.py
new file mode 100644
index 0000000..2a0d794
--- /dev/null
+++ b/src/zope/tal/talgettext.py
@@ -0,0 +1,316 @@
+#!/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.
+
+$Id$
+"""
+import sys
+import time
+import getopt
+import traceback
+
+from zope.interface import implements
+from zope.tal.htmltalparser import HTMLTALParser
+from zope.tal.talinterpreter import TALInterpreter, normalize
+from zope.tal.dummyengine import DummyEngine
+from zope.tal.interfaces import ITALExpressionEngine
+from zope.tal.taldefs import TALExpressionError
+from zope.i18nmessageid import Message
+
+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"'
+
+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=None, i18ndict=None, obj=None):
+ if default is None:
+ default = getattr(msgid, 'default', unicode(msgid))
+ # If no i18n dict exists yet, create one.
+ if i18ndict is None:
+ i18ndict = {}
+ if obj:
+ i18ndict.update(obj)
+ # Mmmh, it seems that sometimes the msgid is None; is that really
+ # possible?
+ if msgid is None:
+ return None
+ # TODO: We need to pass in one of context or target_language
+ return self.engine.translate(msgid, self.i18nContext.domain, i18ndict,
+ default=default, position=self.position)
+
+
+class POEngine(DummyEngine):
+ implements(ITALExpressionEngine)
+
+ def __init__(self, macros=None):
+ self.catalog = {}
+ DummyEngine.__init__(self, macros)
+
+ def evaluate(*args):
+ # If the result of evaluate ever gets into a message ID, we want
+ # to notice the fact in the .pot file.
+ return '${DYNAMIC_CONTENT}'
+
+ def evaluatePathOrVar(*args):
+ # Actually this method is never called.
+ return 'XXX'
+
+ def evaluateSequence(self, expr):
+ return (0,) # dummy
+
+ def evaluateBoolean(self, expr):
+ return True # dummy
+
+ def translate(self, msgid, domain=None, mapping=None, default=None,
+ # Position is not part of the ITALExpressionEngine
+ # interface
+ position=None):
+
+ # Make the message is a Message object, if the default differs
+ # from the value, so that the POT generator can put the default
+ # text into a comment.
+ if default is not None and normalize(default) != msgid:
+ msgid = Message(msgid, default=default)
+
+ if domain not in self.catalog:
+ self.catalog[domain] = {}
+ domain = self.catalog[domain]
+
+ 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
+ # TODO: 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 TALExpressionError:
+ 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(object):
+ 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()
+ # TODO: You should not sort by msgid, but by filename and position. (SR)
+ msgids.sort()
+ for msgid in msgids:
+ positions = engine.catalog[msgid]
+ for filename, position in positions:
+ outfile.write('#: %s:%s\n' % (filename, position[0]))
+
+ outfile.write('msgid "%s"\n' % msgid)
+ outfile.write('msgstr ""\n')
+ outfile.write('\n')
+
+
+if __name__ == '__main__':
+ main()
diff --git a/src/zope/tal/talinterpreter.py b/src/zope/tal/talinterpreter.py
new file mode 100644
index 0000000..9c801bf
--- /dev/null
+++ b/src/zope/tal/talinterpreter.py
@@ -0,0 +1,1024 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Interpreter for a pre-compiled TAL program.
+
+$Id$
+"""
+import cgi
+import operator
+import sys
+import warnings
+
+# Do not use cStringIO here! It's not unicode aware. :(
+from StringIO import StringIO
+
+from zope.i18nmessageid import Message
+from zope.tal.taldefs import quote, TAL_VERSION, METALError
+from zope.tal.taldefs import isCurrentVersion
+from zope.tal.taldefs import getProgramVersion, getProgramMode
+from zope.tal.talgenerator import TALGenerator
+from zope.tal.translationcontext import TranslationContext
+
+
+# Avoid constructing this tuple over and over
+I18nMessageTypes = (Message,)
+
+TypesToTranslate = I18nMessageTypes + (str, unicode)
+
+BOOLEAN_HTML_ATTRS = frozenset([
+ # 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())
+
+
+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 MacroStackItem(list):
+ # This is a `list` subclass for backward compability.
+ """Stack entry for the TALInterpreter.macroStack.
+
+ This offers convenience attributes for more readable access.
+
+ """
+ __slots__ = ()
+
+ # These would be nicer using @syntax, but that would require
+ # Python 2.4.x; this will do for now.
+
+ macroName = property(lambda self: self[0])
+ slots = property(lambda self: self[1])
+ definingName = property(lambda self: self[2])
+ extending = property(lambda self: self[3])
+ entering = property(lambda self: self[4],
+ lambda self, value: operator.setitem(self, 4, value))
+ i18nContext = property(lambda self: self[5])
+
+
+class TALInterpreter(object):
+ """TAL interpreter.
+
+ Some notes on source annotations. They are HTML/XML comments added to the
+ output whenever sourceFile is changed by a setSourceFile bytecode. Source
+ annotations are disabled by default, but you can turn them on by passing a
+ sourceAnnotations argument to the constructor. You can change the format
+ of the annotations by overriding formatSourceAnnotation in a subclass.
+
+ The output of the annotation is delayed until some actual text is output
+ for two reasons:
+
+ 1. setPosition bytecode follows setSourceFile, and we need position
+ information to output the line number.
+ 2. Comments are not allowed in XML documents before the <?xml?>
+ declaration.
+
+ For performance reasons (TODO: premature optimization?) instead of checking
+ the value of _pending_source_annotation on every write to the output
+ stream, the _stream_write attribute is changed to point to
+ _annotated_stream_write method whenever _pending_source_annotation is
+ set to True, and to _stream.write when it is False. The following
+ invariant always holds:
+
+ if self._pending_source_annotation:
+ assert self._stream_write is self._annotated_stream_write
+ else:
+ assert self._stream_write is self.stream.write
+
+ """
+
+ def __init__(self, program, macros, engine, stream=None,
+ debug=0, wrap=60, metal=1, tal=1, showtal=-1,
+ strictinsert=1, stackLimit=100, i18nInterpolate=1,
+ sourceAnnotations=0):
+ """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).
+
+ sourceAnnotations -- enable source annotations with HTML comments
+ (default: off).
+
+ """
+ self.program = program
+ self.macros = macros
+ self.engine = engine # Execution engine (aka context)
+ self.Default = engine.getDefault()
+ self._pending_source_annotation = False
+ 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)
+ # macroStack entries are MacroStackItem instances;
+ # the entries are mutated while on the stack
+ self.macroStack = []
+ # `inUseDirective` is set iff we're handling either a
+ # metal:use-macro or a metal:extend-macro
+ self.inUseDirective = False
+ 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()
+ self.sourceAnnotations = sourceAnnotations
+
+ 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
+ if self._pending_source_annotation:
+ self._stream_write = self._annotated_stream_write
+ else:
+ 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
+ if self._pending_source_annotation:
+ self._stream_write = self._annotated_stream_write
+ else:
+ self._stream_write = self.stream.write
+ assert self.level == level
+ assert self.scopeLevel == scopeLevel
+
+ def pushMacro(self, macroName, slots, definingName, extending):
+ if len(self.macroStack) >= self.stackLimit:
+ raise METALError("macro nesting limit (%d) exceeded "
+ "by %s" % (self.stackLimit, `macroName`))
+ self.macroStack.append(
+ MacroStackItem((macroName, slots, definingName, extending,
+ True, 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
+ if self._pending_source_annotation:
+ self._stream_write = self._annotated_stream_write
+ else:
+ self._stream_write = self.stream.write
+
+ def popStream(self):
+ self.stream = self._stream_stack.pop()
+ if self._pending_source_annotation:
+ self._stream_write = self._annotated_stream_write
+ else:
+ self._stream_write = self.stream.write
+
+ def _annotated_stream_write(self, s):
+ idx = s.find('<?xml')
+ if idx >= 0 or s.isspace():
+ # Do not preprend comments in front of the <?xml?> declaration.
+ end_of_doctype = s.find('?>', idx)
+ if end_of_doctype > idx:
+ self.stream.write(s[:end_of_doctype+2])
+ s = s[end_of_doctype+2:]
+ # continue
+ else:
+ self.stream.write(s)
+ return
+ self._pending_source_annotation = False
+ self._stream_write = self.stream.write
+ self._stream_write(self.formatSourceAnnotation())
+ self._stream_write(s)
+
+ def formatSourceAnnotation(self):
+ lineno = self.position[0]
+ if lineno is None:
+ location = self.sourceFile
+ else:
+ location = '%s (line %s)' % (self.sourceFile, lineno)
+ sep = '=' * 78
+ return '<!--\n%s\n%s\n%s\n-->' % (sep, location, sep)
+
+ 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)
+ if self.sourceAnnotations:
+ self._pending_source_annotation = True
+ self._stream_write = self._annotated_stream_write
+
+ 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:
+ rendered = item[1:]
+ else:
+ # item[2] is the 'action' field:
+ if item[2] in ('metal', 'tal', 'xmlns', 'i18n'):
+ if not self.showtal:
+ continue
+ rendered = self.attrAction(item)
+ else:
+ rendered = attrAction(self, item)
+ if not rendered:
+ continue
+ for s in rendered:
+ 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 ()
+ macs = self.macroStack
+ if action == 'metal' and self.metal and macs:
+ # Drop all METAL attributes at a use-depth beyond the first
+ # use-macro and its extensions
+ if len(macs) > 1:
+ for macro in macs[1:]:
+ if not macro.extending:
+ return ()
+ if not macs[-1].entering:
+ return ()
+ macs[-1].entering = False
+ # 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.
+ useName = macs[0].macroName
+ defName = macs[0].definingName
+ res = []
+ if defName:
+ res.append('%sdefine-macro=%s' % (prefix, quote(defName)))
+ if useName:
+ res.append('%suse-macro=%s' % (prefix, quote(useName)))
+ return res
+ elif suffix == "define-slot":
+ name = prefix + "fill-slot"
+ elif suffix == "fill-slot":
+ pass
+ else:
+ return ()
+
+ if value is None:
+ value = name
+ else:
+ value = "%s=%s" % (name, quote(value))
+ return [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
+
+ if ok:
+ if xlat:
+ translated = self.translate(msgid or value, value)
+ if translated is not None:
+ value = translated
+ elif isinstance(value, I18nMessageTypes):
+ translated = self.translate(value)
+ if translated is not None:
+ value = translated
+ if value is None:
+ value = name
+ return ["%s=%s" % (name, quote(value))]
+ else:
+ return ()
+ 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])
+ bytecode_handlers["insertText"] = do_insertText
+ bytecode_handlers["insertI18nText"] = do_insertText
+
+ def _writeText(self, text):
+ # '&' must be done first!
+ s = text.replace(
+ "&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")
+ self._stream_write(s)
+ i = s.rfind('\n')
+ if i < 0:
+ self.col += len(s)
+ else:
+ self.col = len(s) - (i + 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.translate(text)
+ self._writeText(text)
+
+ def do_insertI18nText_tal(self, stuff):
+ # TODO: Code duplication is BAD, we need to fix it later
+ text = self.engine.evaluateText(stuff[0])
+ if text is not None:
+ if text is self.Default:
+ self.interpret(stuff[1])
+ else:
+ if isinstance(text, TypesToTranslate):
+ text = self.translate(text)
+ self._writeText(text)
+
+ 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:
+ # TODO: Seems like this branch not used anymore, we
+ # need to remove it
+
+ # 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.
+ value = self.translate(value)
+
+ if not structure:
+ value = cgi.escape(unicode(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])
+ bytecode_handlers["insertStructure"] = do_insertStructure
+ bytecode_handlers["insertI18nStructure"] = do_insertStructure
+
+ 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
+ if isinstance(structure, I18nMessageTypes):
+ text = self.translate(structure)
+ else:
+ text = unicode(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)
+
+ def do_insertI18nStructure_tal(self, (expr, repldict, block)):
+ # TODO: Code duplication is BAD, we need to fix it later
+ structure = self.engine.evaluateStructure(expr)
+ if structure is not None:
+ if structure is self.Default:
+ self.interpret(block)
+ else:
+ if not isinstance(structure, TypesToTranslate):
+ structure = unicode(structure)
+ text = self.translate(structure)
+ if not (repldict or self.strictinsert):
+ # Take a shortcut, no error checking
+ self.stream_write(text)
+ elif self.html:
+ self.insertHTMLStructure(text, repldict)
+ else:
+ self.insertXMLStructure(text, repldict)
+
+ def insertHTMLStructure(self, text, repldict):
+ from zope.tal.htmltalparser import HTMLTALParser
+ gen = AltTALGenerator(repldict, self.engine, 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 zope.tal.talparser import TALParser
+ gen = AltTALGenerator(repldict, self.engine, 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_evaluateCode(self, stuff):
+ lang, program = stuff
+ # Use a temporary stream to capture the interpretation of the
+ # subnodes, which should /not/ go to the output stream.
+ tmpstream = self.StringIO()
+ self.pushStream(tmpstream)
+ try:
+ self.interpret(program)
+ finally:
+ self.popStream()
+ code = tmpstream.getvalue()
+ output = self.engine.evaluateCode(lang, code)
+ self._stream_write(output)
+ bytecode_handlers["evaluateCode"] = do_evaluateCode
+
+ 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=None, i18ndict=None,
+ obj=None, domain=None):
+ if default is None:
+ default = getattr(msgid, 'default', unicode(msgid))
+ if i18ndict is None:
+ i18ndict = {}
+ if domain is None:
+ domain = getattr(msgid, 'domain', self.i18nContext.domain)
+ 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(msgid, self.i18nContext.domain,
+ 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)):
+ wasInUse = self.inUseDirective
+ self.inUseDirective = False
+ self.interpret(macro)
+ self.inUseDirective = wasInUse
+ bytecode_handlers["defineMacro"] = do_defineMacro
+
+ def do_useMacro(self, (macroName, macroExpr, compiledSlots, block),
+ definingName=None, extending=False):
+ 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, definingName, extending)
+
+ # We want 'macroname' name to be always available as a variable
+ outer = self.engine.getValue('macroname')
+ self.engine.setLocal('macroname', macroName.rsplit('/', 1)[-1])
+
+ prev_source = self.sourceFile
+ wasInUse = self.inUseDirective
+ self.inUseDirective = True
+ self.interpret(macro)
+ self.inUseDirective = wasInUse
+
+ if self.sourceFile != prev_source:
+ self.engine.setSourceFile(prev_source)
+ self.sourceFile = prev_source
+ self.popMacro()
+ # Push the outer macroname again.
+ self.engine.setLocal('macroname', outer)
+ bytecode_handlers["useMacro"] = do_useMacro
+
+ def do_extendMacro(self, (macroName, macroExpr, compiledSlots, block,
+ definingName)):
+ # extendMacro results from a combination of define-macro and
+ # use-macro. definingName has the value of the
+ # metal:define-macro attribute.
+ extending = self.metal and self.inUseDirective
+ self.do_useMacro((macroName, macroExpr, compiledSlots, block),
+ definingName, extending)
+ bytecode_handlers["extendMacro"] = do_extendMacro
+
+ 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:
+ len_macs = len(macs)
+ # Measure the extension depth of this use-macro
+ depth = 1
+ while depth < len_macs:
+ if macs[-depth].extending:
+ depth += 1
+ else:
+ break
+ # Search for a slot filler from the most specific to the
+ # most general macro. The most general is at the top of
+ # the stack.
+ slot = None
+ i = len_macs - 1
+ while i >= (len_macs - depth):
+ slot = macs[i].slots.get(slotName)
+ if slot is not None:
+ break
+ i -= 1
+ if slot is not None:
+ # Found a slot filler. Temporarily chop the macro
+ # stack starting at the macro that filled the slot and
+ # render the slot filler.
+ chopped = macs[i:]
+ del macs[i:]
+ try:
+ self.interpret(slot)
+ finally:
+ # Restore the stack entries.
+ for mac in chopped:
+ mac.entering = False # Not entering
+ macs.extend(chopped)
+ return
+ # 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)
+ # TODO: this should not catch ZODB.POSException.ConflictError.
+ # The ITALExpressionEngine interface should provide a way of
+ # getting the set of exception types that should not be
+ # handled.
+ 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["insertI18nStructure"] = do_insertI18nStructure_tal
+ bytecode_handlers_tal["insertText"] = do_insertText_tal
+ bytecode_handlers_tal["insertI18nText"] = do_insertI18nText_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")
diff --git a/src/zope/tal/talparser.py b/src/zope/tal/talparser.py
new file mode 100644
index 0000000..9335ed0
--- /dev/null
+++ b/src/zope/tal/talparser.py
@@ -0,0 +1,142 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Parse XML and compile to TALInterpreter intermediate code.
+
+$Id$
+"""
+from zope.tal.taldefs import XML_NS, ZOPE_I18N_NS, ZOPE_METAL_NS, ZOPE_TAL_NS
+from zope.tal.talgenerator import TALGenerator
+from zope.tal.xmlparser import XMLParser
+
+
+class TALParser(XMLParser):
+
+ ordered_attributes = 1
+
+ def __init__(self, gen=None, encoding=None): # Override
+ XMLParser.__init__(self, encoding)
+ 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 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,
+ self.getpos())
+
+ 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, position=self.getpos())
+
+ 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 zope.tal.talinterpreter import TALInterpreter
+ from zope.tal.dummyengine import DummyEngine
+ engine = DummyEngine(macros)
+ TALInterpreter(program, macros, engine, sys.stdout, wrap=0)()
+
+if __name__ == "__main__":
+ test()
diff --git a/src/zope/tal/tests/__init__.py b/src/zope/tal/tests/__init__.py
new file mode 100644
index 0000000..b711d36
--- /dev/null
+++ b/src/zope/tal/tests/__init__.py
@@ -0,0 +1,2 @@
+#
+# This file is necessary to make this directory a package.
diff --git a/src/zope/tal/tests/input/__init__.py b/src/zope/tal/tests/input/__init__.py
new file mode 100644
index 0000000..b711d36
--- /dev/null
+++ b/src/zope/tal/tests/input/__init__.py
@@ -0,0 +1,2 @@
+#
+# This file is necessary to make this directory a package.
diff --git a/src/zope/tal/tests/input/acme_template.pt b/src/zope/tal/tests/input/acme_template.pt
new file mode 100644
index 0000000..0af01ba
--- /dev/null
+++ b/src/zope/tal/tests/input/acme_template.pt
@@ -0,0 +1,15 @@
+<!-- This is ACME's generic look and feel, which is based on
+PNOME's look and feel. -->
+<html metal:extend-macro="pnome_macros_page" metal:define-macro="page">
+<head>
+<title metal:fill-slot="title">ACME Look and Feel</title>
+</head>
+<body>
+<div metal:fill-slot="page-footer">
+Copyright 2004 Acme Inc.
+<div metal:define-slot="disclaimer">
+Standard disclaimers apply.
+</div>
+</div>
+</body>
+</html>
diff --git a/src/zope/tal/tests/input/document_list.pt b/src/zope/tal/tests/input/document_list.pt
new file mode 100644
index 0000000..8226be1
--- /dev/null
+++ b/src/zope/tal/tests/input/document_list.pt
@@ -0,0 +1,21 @@
+<!-- ACME's document_list uses the ACME look and feel -->
+<html metal:use-macro="acme_macros_page">
+<head>
+<title metal:fill-slot="title">Acme Document List</title>
+<style metal:fill-slot="local-styles" type="text/css">
+ body { background-color: white; }
+</style>
+</head>
+<body>
+<div metal:fill-slot="content">
+<h1>Documents</h1>
+<ul>
+<li>Rocket Science for Dummies</li>
+<li>Birds for the Gourmet Chef</li>
+</ul>
+</div>
+<div metal:fill-slot="disclaimer">
+This document list is classified.
+</div>
+</body>
+</html>
diff --git a/src/zope/tal/tests/input/pnome_template.pt b/src/zope/tal/tests/input/pnome_template.pt
new file mode 100644
index 0000000..f4d1c66
--- /dev/null
+++ b/src/zope/tal/tests/input/pnome_template.pt
@@ -0,0 +1,23 @@
+<!-- fakeplone is a fictional user interface created by a large,
+well-focused team of graphics designers -->
+<html metal:define-macro="page">
+<head>
+<title metal:define-slot="title">Title here</title>
+<metal:block define-slot="local-styles">
+</metal:block>
+</head>
+<body>
+<div>
+ <div metal:define-slot="annoying-quote">
+ "The early bird gets the worm, but the second mouse gets the cheese."
+ </div>
+ <a href="#">Preferences...</a>
+</div>
+<div metal:define-slot="content">
+ Content here
+</div>
+<div metal:define-slot="page-footer">
+ page footer
+</div>
+</body>
+</html>
diff --git a/src/zope/tal/tests/input/test01.html b/src/zope/tal/tests/input/test01.html
new file mode 100644
index 0000000..e2ae0c4
--- /dev/null
+++ b/src/zope/tal/tests/input/test01.html
@@ -0,0 +1,56 @@
+<!DOCTYPE html
+ PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "DTD/xhtml1-transitional.dtd">
+<html>
+
+ <head>dadada</head>
+
+ <body xmlns:z="http://xml.zope.org/namespaces/tal" z:define="foo python:1">
+<h1 z:condition="python:0">This title is not displayed</h1>
+ <h1 z:condition="python:1" z:content="str:This
+Is
+The
+Replaced
+Title">Title</h1>
+
+ <!-- test entity references -->
+ &nbsp;&HarryPotter;
+
+ <!-- examples adapted from TemplateAttributeLanguageSyntax -->
+
+ <span z:content="str:here/id"/>
+
+ <p z:define="x str:template/title; global five python:2+3;" z:content="text var:five"/>
+
+ <p z:repeat="car python:['honda', 'subaru', 'acura']">
+ <span z:replace="var:car"/>
+ </p>
+
+ <p xml:foo="bar">foo bar</p>
+
+ <!-- more examples -->
+
+ <ul>
+ <span z:repeat="car python:['honda', 'subaru', 'acura']">
+ <li z:content="var:car">Car Name</li>
+ </span>
+ </ul>
+
+ <!-- test attribute expansion -->
+
+ <a href="foo" z:attributes="href python:'http://python.org' ">python</a>
+ <a z:attributes="href python:'http://python.org' ">python</a>
+
+ <!-- test insert/replace structure -->
+ <span z:content="structure python:None" />
+ <span z:replace="structure python:None" />
+
+ <span z:define="global x str:&lt;h3&gt;Header Level 3&lt;/h3&gt;" />
+ <span z:define="global x python:'&amp;' + 'nbsp;;' + x" />
+
+ <span z:replace="structure x" />
+ <span z:content="structure x" />
+
+ </body>
+
+</html>
diff --git a/src/zope/tal/tests/input/test01.xml b/src/zope/tal/tests/input/test01.xml
new file mode 100644
index 0000000..82038e9
--- /dev/null
+++ b/src/zope/tal/tests/input/test01.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" ?>
+<!DOCTYPE html
+ PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "DTD/xhtml1-transitional.dtd">
+<html>
+
+ <head>dadada</head>
+
+ <body xmlns:z="http://xml.zope.org/namespaces/tal" z:define="foo python:1">
+<h1 z:condition="python:0">This title is not displayed</h1>
+ <h1 z:condition="python:1" z:content="str:This
+Is
+The
+Replaced
+Title">Title</h1>
+
+ <!-- test entity references -->
+ &nbsp;&HarryPotter;
+
+ <!-- examples adapted from TemplateAttributeLanguageSyntax -->
+
+ <span z:content="str:here/id"/>
+
+ <p z:define="x str:template/title; global five python:2+3;" z:content="text var:five"/>
+
+ <p z:repeat="car python:['honda', 'subaru', 'acura']">
+ <span z:replace="var:car"/>
+ </p>
+
+ <p xml:foo="bar">foo bar</p>
+
+ <!-- more examples -->
+
+ <ul>
+ <span z:repeat="car python:['honda', 'subaru', 'acura']">
+ <li z:content="var:car">Car Name</li>
+ </span>
+ </ul>
+
+ <!-- test attribute expansion -->
+
+ <a href="foo" z:attributes="href python:'http://python.org' ">python</a>
+ <a z:attributes="href python:'http://python.org' ">python</a>
+
+ <!-- test insert/replace structure -->
+ <span z:content="structure python:None" />
+ <span z:replace="structure python:None" />
+
+ <span z:define="global x str:&lt;h3&gt;Header Level 3&lt;/h3&gt;" />
+ <span z:define="global x python:'&amp;' + 'nbsp;;' + x" />
+
+ <span z:replace="structure x" />
+ <span z:content="structure x" />
+
+ </body>
+
+</html>
diff --git a/src/zope/tal/tests/input/test02.html b/src/zope/tal/tests/input/test02.html
new file mode 100644
index 0000000..df2fb18
--- /dev/null
+++ b/src/zope/tal/tests/input/test02.html
@@ -0,0 +1,118 @@
+<biztalk_1 xmlns="urn:schemas-biztalk-org:biztalk:biztalk_1">
+
+<foo:header xmlns:foo="whomping-willow" plain="guido" quote='"' apostrophe="'" both="&quot;'" lt="&lt;" gt="&gt;" amp="&amp;" foo="">
+ <manifest>
+ <document>
+ <name>sample1</name>
+ <description>a simple invoice</description>
+ </document>
+ </manifest>
+</foo:header>
+
+<body>
+
+<!-- sample1.xml is an example of a simple invoice for a small restaurant supplies order -->
+
+<Invoice xmlns="urn:http://schemas.biztalk.org/united_rest_com/yw7sg15x.xml">
+ <Header>
+ <InvoiceNumber>01786</InvoiceNumber>
+ <InvoiceDate>2000-03-17</InvoiceDate> <!-- March 17th, 2000 -->
+ <OrderNo>55377</OrderNo>
+ <OrderDate>2000-03-15</OrderDate> <!-- March 15th, 2000 -->
+ <CustomerPO>GJ03405</CustomerPO>
+ <ShipMethod>DAVE 1</ShipMethod>
+ <ShipDate>2000-03-17</ShipDate> <!-- March 17th, 2000 -->
+ <CustomerID>K5211(34)</CustomerID>
+ <SalesPersonCode>23</SalesPersonCode>
+ <TaxID>23</TaxID>
+ </Header>
+ <InvoiceTo>
+ <Name>SHIPWRIGHT RESTAURANTS LIMITED</Name>
+ <AddressLine>125 NORTH SERVICE ROAD W</AddressLine>
+ <AddressLine>WESTLAKE ACCESS</AddressLine>
+ <City>NORTH BAY</City>
+ <PostCode>L8B1O5</PostCode>
+ <State>ONTARIO</State>
+ <Country>CANADA</Country>
+ </InvoiceTo>
+ <ShipTo>
+ <Name/>
+ <AddressLine>ATTN: PAULINE DEGRASSI</AddressLine>
+ <City/>
+ <PostCode/>
+ <State/>
+ <Country/>
+ </ShipTo>
+ <DetailLines>
+ <DetailLine>
+ <QuantityShipped>1</QuantityShipped>
+ <UnitOfMeasure>CS</UnitOfMeasure>
+ <PartNumber>DM 5309</PartNumber>
+ <PartDescription>#1013 12 OZ.MUNICH STEIN</PartDescription>
+ <UnitPrice>37.72</UnitPrice>
+ <LineTotal>37.72</LineTotal>
+ </DetailLine>
+ <DetailLine>
+ <QuantityShipped>6</QuantityShipped>
+ <UnitOfMeasure>DZ</UnitOfMeasure>
+ <PartNumber>ON 6420</PartNumber>
+ <PartDescription>PROVINCIAL DINNER FORK</PartDescription>
+ <UnitPrice>17.98</UnitPrice>
+ <LineTotal>107.88</LineTotal>
+ </DetailLine>
+ <DetailLine>
+ <QuantityShipped>72</QuantityShipped>
+ <UnitOfMeasure>EA</UnitOfMeasure>
+ <PartNumber>JR20643</PartNumber>
+ <PartDescription>PLASTIC HANDLED STEAK KNIFE</PartDescription>
+ <UnitPrice>.81</UnitPrice>
+ <LineTotal>58.32</LineTotal>
+ </DetailLine>
+ <DetailLine>
+ <QuantityShipped>6</QuantityShipped>
+ <UnitOfMeasure>DZ</UnitOfMeasure>
+ <PartNumber>ON 6410</PartNumber>
+ <PartDescription>PROVINCIAL TEASPOONS</PartDescription>
+ <UnitPrice>12.16</UnitPrice>
+ <LineTotal>72.96</LineTotal>
+ </DetailLine>
+ <DetailLine>
+ <QuantityShipped>0</QuantityShipped>
+ <UnitOfMeasure>DZ</UnitOfMeasure>
+ <PartNumber>ON 6411</PartNumber>
+ <PartDescription>PROVINCIAL RD BOWL SPOON</PartDescription>
+ <QuantityBackOrdered>6</QuantityBackOrdered>
+ <UnitPrice>17.98</UnitPrice>
+ <LineTotal>0.00</LineTotal>
+ </DetailLine>
+ <DetailLine>
+ <QuantityShipped>1</QuantityShipped>
+ <UnitOfMeasure>EA</UnitOfMeasure>
+ <PartNumber>DO 3218</PartNumber>
+ <PartDescription>34 OZ DUAL DIAL SCALE AM3218</PartDescription>
+ <UnitPrice>70.00</UnitPrice>
+ <DiscountPercentage>5.0</DiscountPercentage>
+ <LineTotal>66.50</LineTotal>
+ </DetailLine>
+ <DetailLine>
+ <QuantityShipped>1</QuantityShipped>
+ <UnitOfMeasure>CS</UnitOfMeasure>
+ <PartNumber>DM 195</PartNumber>
+ <PartDescription>20 OZ.BEER PUB GLASS</PartDescription>
+ <UnitPrice>55.90</UnitPrice>
+ <LineTotal>55.90</LineTotal>
+ </DetailLine>
+ </DetailLines>
+ <Totals>
+ <SubTotal>399.28</SubTotal>
+ <DiscountTotal>3.50</DiscountTotal>
+ <FreightTotal>23.75</FreightTotal>
+ <GSTTotal>29.61</GSTTotal>
+ <ProvTaxTotal>33.84</ProvTaxTotal>
+ <OtherTotal>33.84</OtherTotal>
+ <InvoiceTotal>486.48</InvoiceTotal>
+ </Totals>
+</Invoice>
+
+</body>
+</biztalk_1>
diff --git a/src/zope/tal/tests/input/test02.xml b/src/zope/tal/tests/input/test02.xml
new file mode 100644
index 0000000..69567ea
--- /dev/null
+++ b/src/zope/tal/tests/input/test02.xml
@@ -0,0 +1,119 @@
+<?xml version="1.0" ?>
+<biztalk_1 xmlns="urn:schemas-biztalk-org:biztalk:biztalk_1">
+
+<foo:header xmlns:foo="whomping-willow" plain="guido" quote='"' apostrophe="'" both="&quot;'" lt="&lt;" gt="&gt;" amp="&amp;" foo="">
+ <manifest>
+ <document>
+ <name>sample1</name>
+ <description>a simple invoice</description>
+ </document>
+ </manifest>
+</foo:header>
+
+<body>
+
+<!-- sample1.xml is an example of a simple invoice for a small restaurant supplies order -->
+
+<Invoice xmlns="urn:http://schemas.biztalk.org/united_rest_com/yw7sg15x.xml">
+ <Header>
+ <InvoiceNumber>01786</InvoiceNumber>
+ <InvoiceDate>2000-03-17</InvoiceDate> <!-- March 17th, 2000 -->
+ <OrderNo>55377</OrderNo>
+ <OrderDate>2000-03-15</OrderDate> <!-- March 15th, 2000 -->
+ <CustomerPO>GJ03405</CustomerPO>
+ <ShipMethod>DAVE 1</ShipMethod>
+ <ShipDate>2000-03-17</ShipDate> <!-- March 17th, 2000 -->
+ <CustomerID>K5211(34)</CustomerID>
+ <SalesPersonCode>23</SalesPersonCode>
+ <TaxID>23</TaxID>
+ </Header>
+ <InvoiceTo>
+ <Name>SHIPWRIGHT RESTAURANTS LIMITED</Name>
+ <AddressLine>125 NORTH SERVICE ROAD W</AddressLine>
+ <AddressLine>WESTLAKE ACCESS</AddressLine>
+ <City>NORTH BAY</City>
+ <PostCode>L8B1O5</PostCode>
+ <State>ONTARIO</State>
+ <Country>CANADA</Country>
+ </InvoiceTo>
+ <ShipTo>
+ <Name/>
+ <AddressLine>ATTN: PAULINE DEGRASSI</AddressLine>
+ <City/>
+ <PostCode/>
+ <State/>
+ <Country/>
+ </ShipTo>
+ <DetailLines>
+ <DetailLine>
+ <QuantityShipped>1</QuantityShipped>
+ <UnitOfMeasure>CS</UnitOfMeasure>
+ <PartNumber>DM 5309</PartNumber>
+ <PartDescription>#1013 12 OZ.MUNICH STEIN</PartDescription>
+ <UnitPrice>37.72</UnitPrice>
+ <LineTotal>37.72</LineTotal>
+ </DetailLine>
+ <DetailLine>
+ <QuantityShipped>6</QuantityShipped>
+ <UnitOfMeasure>DZ</UnitOfMeasure>
+ <PartNumber>ON 6420</PartNumber>
+ <PartDescription>PROVINCIAL DINNER FORK</PartDescription>
+ <UnitPrice>17.98</UnitPrice>
+ <LineTotal>107.88</LineTotal>
+ </DetailLine>
+ <DetailLine>
+ <QuantityShipped>72</QuantityShipped>
+ <UnitOfMeasure>EA</UnitOfMeasure>
+ <PartNumber>JR20643</PartNumber>
+ <PartDescription>PLASTIC HANDLED STEAK KNIFE</PartDescription>
+ <UnitPrice>.81</UnitPrice>
+ <LineTotal>58.32</LineTotal>
+ </DetailLine>
+ <DetailLine>
+ <QuantityShipped>6</QuantityShipped>
+ <UnitOfMeasure>DZ</UnitOfMeasure>
+ <PartNumber>ON 6410</PartNumber>
+ <PartDescription>PROVINCIAL TEASPOONS</PartDescription>
+ <UnitPrice>12.16</UnitPrice>
+ <LineTotal>72.96</LineTotal>
+ </DetailLine>
+ <DetailLine>
+ <QuantityShipped>0</QuantityShipped>
+ <UnitOfMeasure>DZ</UnitOfMeasure>
+ <PartNumber>ON 6411</PartNumber>
+ <PartDescription>PROVINCIAL RD BOWL SPOON</PartDescription>
+ <QuantityBackOrdered>6</QuantityBackOrdered>
+ <UnitPrice>17.98</UnitPrice>
+ <LineTotal>0.00</LineTotal>
+ </DetailLine>
+ <DetailLine>
+ <QuantityShipped>1</QuantityShipped>
+ <UnitOfMeasure>EA</UnitOfMeasure>
+ <PartNumber>DO 3218</PartNumber>
+ <PartDescription>34 OZ DUAL DIAL SCALE AM3218</PartDescription>
+ <UnitPrice>70.00</UnitPrice>
+ <DiscountPercentage>5.0</DiscountPercentage>
+ <LineTotal>66.50</LineTotal>
+ </DetailLine>
+ <DetailLine>
+ <QuantityShipped>1</QuantityShipped>
+ <UnitOfMeasure>CS</UnitOfMeasure>
+ <PartNumber>DM 195</PartNumber>
+ <PartDescription>20 OZ.BEER PUB GLASS</PartDescription>
+ <UnitPrice>55.90</UnitPrice>
+ <LineTotal>55.90</LineTotal>
+ </DetailLine>
+ </DetailLines>
+ <Totals>
+ <SubTotal>399.28</SubTotal>
+ <DiscountTotal>3.50</DiscountTotal>
+ <FreightTotal>23.75</FreightTotal>
+ <GSTTotal>29.61</GSTTotal>
+ <ProvTaxTotal>33.84</ProvTaxTotal>
+ <OtherTotal>33.84</OtherTotal>
+ <InvoiceTotal>486.48</InvoiceTotal>
+ </Totals>
+</Invoice>
+
+</body>
+</biztalk_1>
diff --git a/src/zope/tal/tests/input/test03.html b/src/zope/tal/tests/input/test03.html
new file mode 100644
index 0000000..a0230e1
--- /dev/null
+++ b/src/zope/tal/tests/input/test03.html
@@ -0,0 +1,9 @@
+<p xmlns:z="http://xml.zope.org/namespaces/tal">
+ <span z:define="local x str:hello brave new world">
+ <span z:content="text local:x">outer variable x, first appearance</span>
+ <span z:define="local x str:goodbye cruel world">
+ <span z:content="text local:x">inner variable x</span>
+ </span>
+ <span z:content="text local:x">outer variable x, second appearance</span>
+ </span>
+</p>
diff --git a/src/zope/tal/tests/input/test03.xml b/src/zope/tal/tests/input/test03.xml
new file mode 100644
index 0000000..830149d
--- /dev/null
+++ b/src/zope/tal/tests/input/test03.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" ?>
+<p xmlns:z="http://xml.zope.org/namespaces/tal">
+ <span z:define="local x str:hello brave new world">
+ <span z:content="text local:x">outer variable x, first appearance</span>
+ <span z:define="local x str:goodbye cruel world">
+ <span z:content="text local:x">inner variable x</span>
+ </span>
+ <span z:content="text local:x">outer variable x, second appearance</span>
+ </span>
+</p>
diff --git a/src/zope/tal/tests/input/test04.html b/src/zope/tal/tests/input/test04.html
new file mode 100644
index 0000000..bdaad39
--- /dev/null
+++ b/src/zope/tal/tests/input/test04.html
@@ -0,0 +1,26 @@
+<html>
+
+ <body xmlns:m="http://xml.zope.org/namespaces/metal" xmlns:z="http://xml.zope.org/namespaces/tal" m:define-macro="body" z:define="global count python:0">
+
+ <ul m:define-macro="whoops">
+ <li z:repeat="item python:range(count)">
+ <span z:replace="item">1</span>
+ <span z:replace="global:message"/>
+ </li>
+ </ul>
+
+ <span z:define="global count python:2; global message str:hello world"/>
+
+ <p m:use-macro="whoops">use-macro
+ <span m:fill-slot="whoops">fill-slot</span>
+ </p>
+
+ <span z:define="global message str:goodbye cruel world"/>
+
+ <p m:use-macro="whoops">use-macro</p>
+
+ <p m:define-slot="whoops">define-slot</p>
+
+ </body>
+
+</html>
diff --git a/src/zope/tal/tests/input/test04.xml b/src/zope/tal/tests/input/test04.xml
new file mode 100644
index 0000000..bde6cef
--- /dev/null
+++ b/src/zope/tal/tests/input/test04.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" ?>
+<html>
+
+ <body xmlns:m="http://xml.zope.org/namespaces/metal" xmlns:z="http://xml.zope.org/namespaces/tal" m:define-macro="body" z:define="global count python:0">
+
+ <ul m:define-macro="whoops">
+ <li z:repeat="item python:range(count)">
+ <span z:replace="item">1</span>
+ <span z:replace="global:message"/>
+ </li>
+ </ul>
+
+ <span z:define="global count python:2; global message str:hello world"/>
+
+ <p m:use-macro="whoops">use-macro
+ <span m:fill-slot="whoops">fill-slot</span>
+ </p>
+
+ <span z:define="global message str:goodbye cruel world"/>
+
+ <p m:use-macro="whoops">use-macro</p>
+
+ <p m:define-slot="whoops">define-slot</p>
+
+ </body>
+
+</html>
diff --git a/src/zope/tal/tests/input/test05.html b/src/zope/tal/tests/input/test05.html
new file mode 100644
index 0000000..21f6b68
--- /dev/null
+++ b/src/zope/tal/tests/input/test05.html
@@ -0,0 +1,9 @@
+<html>
+
+ <body xmlns:m="http://xml.zope.org/namespaces/metal" m:define-macro="body">
+
+ <h1>This is the body of test5</h1>
+
+ </body>
+
+</html>
diff --git a/src/zope/tal/tests/input/test05.xml b/src/zope/tal/tests/input/test05.xml
new file mode 100644
index 0000000..fcaaf6b
--- /dev/null
+++ b/src/zope/tal/tests/input/test05.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" ?>
+<html>
+
+ <body xmlns:m="http://xml.zope.org/namespaces/metal" m:define-macro="body">
+
+ <h1>This is the body of test5</h1>
+
+ </body>
+
+</html>
diff --git a/src/zope/tal/tests/input/test06.html b/src/zope/tal/tests/input/test06.html
new file mode 100644
index 0000000..ac1264d
--- /dev/null
+++ b/src/zope/tal/tests/input/test06.html
@@ -0,0 +1,6 @@
+<html>
+ <body xmlns:m="http://xml.zope.org/namespaces/metal"
+ m:use-macro="tests/input/test05.html/body">
+ dummy body in test6
+ </body>
+</html>
diff --git a/src/zope/tal/tests/input/test06.xml b/src/zope/tal/tests/input/test06.xml
new file mode 100644
index 0000000..b32bd0f
--- /dev/null
+++ b/src/zope/tal/tests/input/test06.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" ?>
+<html>
+ <body xmlns:m="http://xml.zope.org/namespaces/metal"
+ m:use-macro="tests/input/test05.xml/body">
+ dummy body in test6
+ </body>
+</html>
diff --git a/src/zope/tal/tests/input/test07.html b/src/zope/tal/tests/input/test07.html
new file mode 100644
index 0000000..bff98f0
--- /dev/null
+++ b/src/zope/tal/tests/input/test07.html
@@ -0,0 +1,11 @@
+<table xmlns:m="http://xml.zope.org/namespaces/metal" m:define-macro="myTable">
+<!-- macro definition with slots -->
+ <tr>
+ <td>Top Left</td>
+ <td>Top Right</td>
+ </tr>
+ <tr>
+ <td>Bottom left</td>
+ <td><span m:define-slot="bottomRight">Bottom Right</span></td>
+ </tr>
+</table>
diff --git a/src/zope/tal/tests/input/test07.xml b/src/zope/tal/tests/input/test07.xml
new file mode 100644
index 0000000..e5c520a
--- /dev/null
+++ b/src/zope/tal/tests/input/test07.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" ?>
+<table xmlns:m="http://xml.zope.org/namespaces/metal" m:define-macro="myTable">
+<!-- macro definition with slots -->
+ <tr>
+ <td>Top Left</td>
+ <td>Top Right</td>
+ </tr>
+ <tr>
+ <td>Bottom left</td>
+ <td><span m:define-slot="bottomRight">Bottom Right</span></td>
+ </tr>
+</table>
diff --git a/src/zope/tal/tests/input/test08.html b/src/zope/tal/tests/input/test08.html
new file mode 100644
index 0000000..1e4915b
--- /dev/null
+++ b/src/zope/tal/tests/input/test08.html
@@ -0,0 +1,44 @@
+<table xmlns:m="http://xml.zope.org/namespaces/metal" m:use-macro="tests/input/test07.html/myTable">
+<!-- macro use with slots -->
+ <tr>
+ <td>
+ <span m:fill-slot="bottomRight">
+ <h1>Some headline</h1>
+ <p>This is the real contents of the bottom right slot.</p>
+ <p>It is supposed to contain a lot of text. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb.</p>
+ <p>It is supposed to contain a lot of text. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb.</p>
+ <p>It is supposed to contain a lot of text. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb.</p>
+ </span>
+ </td>
+ </tr>
+</table>
diff --git a/src/zope/tal/tests/input/test08.xml b/src/zope/tal/tests/input/test08.xml
new file mode 100644
index 0000000..b0360fa
--- /dev/null
+++ b/src/zope/tal/tests/input/test08.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" ?>
+<table xmlns:m="http://xml.zope.org/namespaces/metal" m:use-macro="tests/input/test07.xml/myTable">
+<!-- macro use with slots -->
+ <tr>
+ <td>
+ <span m:fill-slot="bottomRight">
+ <h1>Some headline</h1>
+ <p>This is the real contents of the bottom right slot.</p>
+ <p>It is supposed to contain a lot of text. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb.</p>
+ <p>It is supposed to contain a lot of text. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb.</p>
+ <p>It is supposed to contain a lot of text. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb.</p>
+ </span>
+ </td>
+ </tr>
+</table>
diff --git a/src/zope/tal/tests/input/test09.html b/src/zope/tal/tests/input/test09.html
new file mode 100644
index 0000000..35f481a
--- /dev/null
+++ b/src/zope/tal/tests/input/test09.html
@@ -0,0 +1,30 @@
+<html>
+<body>
+<p>
+ Just a bunch of text.
+<p>more text...
+<ul>
+ <li>first item
+ <li>second item
+
+ <ol>
+ <li>second list, first item
+ <li>second list, second item
+ <dl compact>
+ <dt>term 1
+ <dt>term 2
+ <dd>definition
+ </dl>
+ </ol>
+
+ <li>Now let's have a paragraph...
+ <p>My Paragraph
+ </li>
+
+ <li>And a table in a list item:
+ <table>
+ </table>
+</ul>
+
+</body>
+</html>
diff --git a/src/zope/tal/tests/input/test09.xml b/src/zope/tal/tests/input/test09.xml
new file mode 100644
index 0000000..c3d10d7
--- /dev/null
+++ b/src/zope/tal/tests/input/test09.xml
@@ -0,0 +1,30 @@
+<html>
+<body>
+<p>
+ Just a bunch of text.</p>
+<p>more text...</p>
+<ul>
+ <li>first item</li>
+ <li>second item
+
+ <ol>
+ <li>second list, first item</li>
+ <li>second list, second item
+ <dl compact="">
+ <dt>term 1</dt>
+ <dt>term 2</dt>
+ <dd>definition</dd>
+ </dl></li>
+ </ol></li>
+
+ <li>Now let's have a paragraph...
+ <p>My Paragraph</p>
+ </li>
+
+ <li>And a table in a list item:
+ <table>
+ </table></li>
+</ul>
+
+</body>
+</html>
diff --git a/src/zope/tal/tests/input/test10.html b/src/zope/tal/tests/input/test10.html
new file mode 100644
index 0000000..6ecca4c
--- /dev/null
+++ b/src/zope/tal/tests/input/test10.html
@@ -0,0 +1,48 @@
+<html><body>
+<table xmlns:m="http://xml.zope.org/namespaces/metal" m:use-macro="tests/input/test07.html/myTable">
+<!-- macro use with slots -->
+ <tr>
+ <td>
+ <span m:fill-slot="bottomRight">
+ <h1>Some headline</h1>
+ <p>This is the real contents of the bottom right slot.</p>
+ <hr>
+ <p>It is supposed to contain a lot of text. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb.</p>
+ <p>It is supposed to contain a lot of text. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb.</p>
+ <p>It is supposed to contain a lot of text. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb.</p>
+ <br><br>
+ </span>
+ </td>
+ </tr>
+</table>
+</body></html>
diff --git a/src/zope/tal/tests/input/test11.html b/src/zope/tal/tests/input/test11.html
new file mode 100644
index 0000000..89f7563
--- /dev/null
+++ b/src/zope/tal/tests/input/test11.html
@@ -0,0 +1,19 @@
+<html xmlns:tal="http://xml.zope.org/namespaces/tal">
+ <p tal:replace="structure string:&lt;a&gt;bar&lt;/a&gt;"
+ tal:attributes="href string:http://www.python.org">dummy text</p>
+ <p tal:define="x python:1" tal:on-error="string:bad boy!">
+ <span tal:define="x python:2">
+ <span tal:define="x python:3">
+ <span tal:content="python:1/0"/>
+ </span>
+ </span>
+ </p>
+ <p tal:on-error="string:x undefined">
+ <span tal:content="x"/>
+ </p>
+ <tal:block on-error="string:x undefined" replace="x" />
+ <tal:block on-error="string:x undefined">
+ <p tal:content="x">p</p>
+ </tal:block>
+ <div tal:replace="structure string:&lt;hr /&gt;">rule</div>
+</html>
diff --git a/src/zope/tal/tests/input/test11.xml b/src/zope/tal/tests/input/test11.xml
new file mode 100644
index 0000000..435f95c
--- /dev/null
+++ b/src/zope/tal/tests/input/test11.xml
@@ -0,0 +1,14 @@
+<html xmlns:tal="http://xml.zope.org/namespaces/tal">
+ <p tal:replace="structure string:&lt;a&gt;bar&lt;/a&gt;"
+ tal:attributes="href string:http://www.python.org">dummy text</p>
+ <p tal:define="x python:1" tal:on-error="string:bad boy!">
+ <span tal:define="x python:2">
+ <span tal:define="x python:3">
+ <span tal:content="python:1/0"/>
+ </span>
+ </span>
+ </p>
+ <p tal:on-error="string:x undefined">
+ <span tal:content="x"/>
+ </p>
+</html>
diff --git a/src/zope/tal/tests/input/test12.html b/src/zope/tal/tests/input/test12.html
new file mode 100644
index 0000000..94d9a66
--- /dev/null
+++ b/src/zope/tal/tests/input/test12.html
@@ -0,0 +1,24 @@
+<span tal:define="global true python:1; global false python:0" />
+
+<img ismap>
+<img ismap=ismap>
+<img ismap="ismap">
+<img ismap="foo">
+
+<img ismap tal:attributes="ismap true">
+<img ismap tal:attributes="ismap false">
+<img ismap tal:attributes="ismap nothing">
+
+<img ismap="foo" tal:attributes="ismap true">
+<img ismap="foo" tal:attributes="ismap false">
+<img ismap="foo" tal:attributes="ismap nothing">
+
+<img tal:attributes="ismap true">
+<img tal:attributes="ismap false">
+<img tal:attributes="ismap nothing">
+
+<span tal:define="global x string:x.gif" />
+
+<img src="foo">
+<img src="foo" tal:attributes="src x">
+<img src="foo" tal:attributes="src nothing">
diff --git a/src/zope/tal/tests/input/test13.html b/src/zope/tal/tests/input/test13.html
new file mode 100644
index 0000000..d68e0ce
--- /dev/null
+++ b/src/zope/tal/tests/input/test13.html
@@ -0,0 +1,7 @@
+Here's a stray greater than: >
+
+<script>
+ <!-- no comment -->
+ <notag>
+ &noentity;
+</script>
diff --git a/src/zope/tal/tests/input/test14.html b/src/zope/tal/tests/input/test14.html
new file mode 100644
index 0000000..0aaa751
--- /dev/null
+++ b/src/zope/tal/tests/input/test14.html
@@ -0,0 +1,10 @@
+<table>
+ <tr>
+ <td tal:repeat="x python:['car', 'bike', 'broomstick']" tal:content="x">
+ </td>
+ </tr>
+</table>
+
+<p>
+ <span tal:repeat="x python:['Harry', 'Ron', 'Hermione']" tal:replace="x" />
+</p>
diff --git a/src/zope/tal/tests/input/test14.xml b/src/zope/tal/tests/input/test14.xml
new file mode 100644
index 0000000..c596135
--- /dev/null
+++ b/src/zope/tal/tests/input/test14.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" ?>
+<html xmlns:tal="http://xml.zope.org/namespaces/tal">
+
+<table>
+ <tr>
+ <td tal:repeat="x python:['car', 'bike', 'broomstick']" tal:content="x">
+ </td>
+ </tr>
+</table>
+
+<p>
+ <span tal:repeat="x python:['Harry', 'Ron', 'Hermione']" tal:replace="x" />
+</p>
+
+</html>
diff --git a/src/zope/tal/tests/input/test15.html b/src/zope/tal/tests/input/test15.html
new file mode 100644
index 0000000..0cd456e
--- /dev/null
+++ b/src/zope/tal/tests/input/test15.html
@@ -0,0 +1,26 @@
+<span metal:define-macro="INNER">
+ <span metal:define-slot="INNERSLOT">INNERSLOT</span>
+</span>
+
+<xxx metal:use-macro="INNER">
+ <xxx metal:fill-slot="INNERSLOT">inner-argument</xxx>
+</xxx>
+
+<div metal:define-macro="OUTER">
+<div metal:use-macro="INNER">
+ <xxx metal:define-slot="OUTERSLOT" metal:fill-slot="INNERSLOT">
+ OUTERSLOT
+ </xxx>
+</div>
+</div>
+
+<div metal:use-macro="OUTER">
+<span>
+ <xxx>
+ <div metal:fill-slot="OUTERSLOT">outer-argument</div>
+ </xxx>
+</span>
+</div>
+
+<div metal:use-macro="OUTER">
+</div>
diff --git a/src/zope/tal/tests/input/test16.html b/src/zope/tal/tests/input/test16.html
new file mode 100644
index 0000000..1414f45
--- /dev/null
+++ b/src/zope/tal/tests/input/test16.html
@@ -0,0 +1,2 @@
+<a href="valid/link.html"
+ tal:attributes="href python:'/base/' + attrs['href']">blah, blah</a>
diff --git a/src/zope/tal/tests/input/test16.xml b/src/zope/tal/tests/input/test16.xml
new file mode 100644
index 0000000..2efb2ab
--- /dev/null
+++ b/src/zope/tal/tests/input/test16.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0"?>
+<body xmlns:tal="http://xml.zope.org/namespaces/tal">
+
+<ImG href="foo" Alt="bar"
+ tal:attributes="Href string:about:foo;alT string:baz" />
+
+</body>
diff --git a/src/zope/tal/tests/input/test17.html b/src/zope/tal/tests/input/test17.html
new file mode 100644
index 0000000..5a5ebb3
--- /dev/null
+++ b/src/zope/tal/tests/input/test17.html
@@ -0,0 +1,6 @@
+<tal:block tal:content="string:Yes">No</tal:block>
+<tal:block content="string:Yes">No</tal:block>
+<tal:block>Yes</tal:block>
+
+<metal:block tal:content="string:Yes">No</metal:block>
+<metal:block>Yes</metal:block>
diff --git a/src/zope/tal/tests/input/test17.xml b/src/zope/tal/tests/input/test17.xml
new file mode 100644
index 0000000..ecb617a
--- /dev/null
+++ b/src/zope/tal/tests/input/test17.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0"?>
+<body xmlns:z="http://xml.zope.org/namespaces/tal"
+ xmlns:z2="http://xml.zope.org/namespaces/metal">
+<z:block z:content="string:Yes">No</z:block>
+<z:block content="string:Yes">No</z:block>
+<z:block>Yes</z:block>
+
+<z2:block z:content="string:Yes">No</z2:block>
+<z2:block>Yes</z2:block>
+</body>
diff --git a/src/zope/tal/tests/input/test18.html b/src/zope/tal/tests/input/test18.html
new file mode 100644
index 0000000..c3a5c26
--- /dev/null
+++ b/src/zope/tal/tests/input/test18.html
@@ -0,0 +1,16 @@
+<p tal:omit-tag="">Content</p>
+<p tal:omit-tag=""></p>
+<img tal:omit-tag="">
+
+<p tal:omit-tag="string:Yes">Content</p>
+<p tal:omit-tag="string:Yes"></p>
+<img tal:omit-tag="string:Yes">
+
+<p tal:omit-tag="nothing">Content</p>
+<p tal:omit-tag="nothing"></p>
+<img tal:omit-tag="nothing">
+
+<p tal:define="txt string:Yes" tal:omit-tag="" tal:content="txt">No</p>
+<p tal:define="txt string:Yes" tal:omit-tag="" tal:replace="txt">No</p>
+<p tal:omit-tag="" tal:content="default">Yes</p>
+<p tal:omit-tag="" tal:replace="default">Yes</p>
diff --git a/src/zope/tal/tests/input/test18.xml b/src/zope/tal/tests/input/test18.xml
new file mode 100644
index 0000000..5a0cca4
--- /dev/null
+++ b/src/zope/tal/tests/input/test18.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0"?>
+<body xmlns:tal="http://xml.zope.org/namespaces/tal"
+ xmlns:metal="http://xml.zope.org/namespaces/metal">
+<p tal:omit-tag="">Content</p>
+<p tal:omit-tag=""></p>
+<img tal:omit-tag=""/>
+
+<p tal:omit-tag="string:Yes">Content</p>
+<p tal:omit-tag="string:Yes"></p>
+<img tal:omit-tag="string:Yes"/>
+
+<p tal:omit-tag="nothing">Content</p>
+<p tal:omit-tag="nothing"></p>
+<img tal:omit-tag="nothing" />
+
+<p tal:define="txt string:Yes" tal:omit-tag="" tal:content="txt">No</p>
+<p tal:define="txt string:Yes" tal:omit-tag="" tal:replace="txt">No</p>
+<p tal:omit-tag="" tal:content="default">Yes</p>
+<p tal:omit-tag="" tal:replace="default">Yes</p>
+</body>
diff --git a/src/zope/tal/tests/input/test19.html b/src/zope/tal/tests/input/test19.html
new file mode 100644
index 0000000..a56632a
--- /dev/null
+++ b/src/zope/tal/tests/input/test19.html
@@ -0,0 +1,5 @@
+<span i18n:translate="">Replace this</span>
+<span i18n:translate="msgid">This is a
+translated string</span>
+<span i18n:translate="">And another
+translated string</span>
diff --git a/src/zope/tal/tests/input/test19.xml b/src/zope/tal/tests/input/test19.xml
new file mode 100644
index 0000000..fe4bf79
--- /dev/null
+++ b/src/zope/tal/tests/input/test19.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0"?>
+<body xmlns:i18n="http://xml.zope.org/namespaces/i18n">
+<span i18n:translate="">Replace this</span>
+<span i18n:translate="msgid">This is a
+translated string</span>
+<span i18n:translate="">And another
+translated string</span>
+</body>
diff --git a/src/zope/tal/tests/input/test20.html b/src/zope/tal/tests/input/test20.html
new file mode 100644
index 0000000..f302213
--- /dev/null
+++ b/src/zope/tal/tests/input/test20.html
@@ -0,0 +1 @@
+<span i18n:translate="">replaceable <p tal:replace="str:here">content</p></span>
diff --git a/src/zope/tal/tests/input/test20.xml b/src/zope/tal/tests/input/test20.xml
new file mode 100644
index 0000000..5050883
--- /dev/null
+++ b/src/zope/tal/tests/input/test20.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0"?>
+<body xmlns:tal="http://xml.zope.org/namespaces/tal"
+ xmlns:metal="http://xml.zope.org/namespaces/metal"
+ xmlns:i18n="http://xml.zope.org/namespaces/i18n">
+<span i18n:translate="">replaceable <p tal:replace="str:here">content</p></span>
+</body>
diff --git a/src/zope/tal/tests/input/test21.html b/src/zope/tal/tests/input/test21.html
new file mode 100644
index 0000000..95f925e
--- /dev/null
+++ b/src/zope/tal/tests/input/test21.html
@@ -0,0 +1,4 @@
+<span i18n:translate="">
+ <span tal:replace="str:Lomax" i18n:name="name" /> was born in
+ <span tal:replace="str:Antarctica" i18n:name="country" />.
+</span>
diff --git a/src/zope/tal/tests/input/test21.xml b/src/zope/tal/tests/input/test21.xml
new file mode 100644
index 0000000..eea370b
--- /dev/null
+++ b/src/zope/tal/tests/input/test21.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0"?>
+<body xmlns:tal="http://xml.zope.org/namespaces/tal"
+ xmlns:metal="http://xml.zope.org/namespaces/metal"
+ xmlns:i18n="http://xml.zope.org/namespaces/i18n">
+<span i18n:translate="">
+ <span tal:replace="str:Lomax" i18n:name="name" /> was born in
+ <span tal:replace="str:Antarctica" i18n:name="country" />.
+</span>
+</body>
diff --git a/src/zope/tal/tests/input/test22.html b/src/zope/tal/tests/input/test22.html
new file mode 100644
index 0000000..a4a7e93
--- /dev/null
+++ b/src/zope/tal/tests/input/test22.html
@@ -0,0 +1,4 @@
+<span i18n:translate="">
+ <span tal:omit-tag="" i18n:name="name"><b>Jim</b></span> was born in
+ <span tal:omit-tag="" i18n:name="country">the USA</span>.
+</span>
diff --git a/src/zope/tal/tests/input/test22.xml b/src/zope/tal/tests/input/test22.xml
new file mode 100644
index 0000000..54b57d8
--- /dev/null
+++ b/src/zope/tal/tests/input/test22.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0"?>
+<body xmlns:tal="http://xml.zope.org/namespaces/tal">
+ <span tal:content="default">content</span>
+ <span tal:omit-tag="" tal:content="default">omit</span>
+ <span tal:replace="default">replace</span>
+</body>
diff --git a/src/zope/tal/tests/input/test23.html b/src/zope/tal/tests/input/test23.html
new file mode 100644
index 0000000..bfe6665
--- /dev/null
+++ b/src/zope/tal/tests/input/test23.html
@@ -0,0 +1,2 @@
+<span i18n:data="here/currentTime"
+ i18n:translate="timefmt">2:32 pm</span>
diff --git a/src/zope/tal/tests/input/test24.html b/src/zope/tal/tests/input/test24.html
new file mode 100644
index 0000000..6d53984
--- /dev/null
+++ b/src/zope/tal/tests/input/test24.html
@@ -0,0 +1,12 @@
+<input name="Delete"
+ tal:attributes="name string:delete_button"
+ i18n:attributes="name">
+
+<input name="Delete"
+ i18n:attributes="name message-id">
+
+<input i18n:attributes=" name message-id;
+ attr input-attr ">
+
+<input i18n:attributes=" name message-id;
+ attr input-attr;">
diff --git a/src/zope/tal/tests/input/test25.html b/src/zope/tal/tests/input/test25.html
new file mode 100644
index 0000000..25a99cf
--- /dev/null
+++ b/src/zope/tal/tests/input/test25.html
@@ -0,0 +1 @@
+<input name="Delete" i18n:attributes="name">
diff --git a/src/zope/tal/tests/input/test26.html b/src/zope/tal/tests/input/test26.html
new file mode 100644
index 0000000..fa5a99d
--- /dev/null
+++ b/src/zope/tal/tests/input/test26.html
@@ -0,0 +1,3 @@
+<span i18n:translate="jobnum">
+ Job #<span tal:replace="context/@@object_name"
+ i18n:name="jobnum">NN</span></span>
diff --git a/src/zope/tal/tests/input/test27.html b/src/zope/tal/tests/input/test27.html
new file mode 100644
index 0000000..b9c16cb
--- /dev/null
+++ b/src/zope/tal/tests/input/test27.html
@@ -0,0 +1,5 @@
+<p i18n:translate="verify">Your contact email address is recorded as
+ <a href="mailto:user@example.com"
+ tal:content="request/submitter"
+ i18n:name="email">user@host.com</a>
+</p>
diff --git a/src/zope/tal/tests/input/test28.html b/src/zope/tal/tests/input/test28.html
new file mode 100644
index 0000000..0364663
--- /dev/null
+++ b/src/zope/tal/tests/input/test28.html
@@ -0,0 +1,5 @@
+<p i18n:translate="verify">Your contact email address is recorded as
+ <span tal:omit-tag="" i18n:name="email">
+ <a href="mailto:user@example.com"
+ tal:content="request/submitter">user@host.com</a></span>
+</p>
diff --git a/src/zope/tal/tests/input/test29.html b/src/zope/tal/tests/input/test29.html
new file mode 100644
index 0000000..e2f1e82
--- /dev/null
+++ b/src/zope/tal/tests/input/test29.html
@@ -0,0 +1,4 @@
+<div i18n:translate="">At the tone the time will be
+<span i18n:data="here/currentTime"
+ i18n:translate="timefmt"
+ i18n:name="time">2:32 pm</span>... beep!</div>
diff --git a/src/zope/tal/tests/input/test30.html b/src/zope/tal/tests/input/test30.html
new file mode 100644
index 0000000..6f8c6ef
--- /dev/null
+++ b/src/zope/tal/tests/input/test30.html
@@ -0,0 +1,6 @@
+<p i18n:translate="verify">Your contact email address is recorded as
+<a href="user@host.com"
+ tal:attributes="href string:mailto:${request/submitter}"
+ tal:content="request/submitter"
+ i18n:name="email">user@host.com</a>
+</p>
diff --git a/src/zope/tal/tests/input/test31.html b/src/zope/tal/tests/input/test31.html
new file mode 100644
index 0000000..c927f42
--- /dev/null
+++ b/src/zope/tal/tests/input/test31.html
@@ -0,0 +1,7 @@
+<p i18n:translate="verify">Your contact email address is recorded as
+<span tal:omit-tag="" i18n:name="email">
+<a href="user@host.com"
+ tal:attributes="href string:mailto:${request/submitter}"
+ tal:content="request/submitter">
+ user@host.com</a></span>
+</p>
diff --git a/src/zope/tal/tests/input/test32.html b/src/zope/tal/tests/input/test32.html
new file mode 100644
index 0000000..3b09bad
--- /dev/null
+++ b/src/zope/tal/tests/input/test32.html
@@ -0,0 +1,4 @@
+<span i18n:translate="origin">
+ <span tal:content="str:Lomax" i18n:name="name" /> was born in
+ <span tal:content="str:Antarctica" i18n:name="country" />.
+</span>
diff --git a/src/zope/tal/tests/input/test33.html b/src/zope/tal/tests/input/test33.html
new file mode 100644
index 0000000..f5dcf58
--- /dev/null
+++ b/src/zope/tal/tests/input/test33.html
@@ -0,0 +1 @@
+<span i18n:translate="">don't translate me</span>
diff --git a/src/zope/tal/tests/input/test34.html b/src/zope/tal/tests/input/test34.html
new file mode 100644
index 0000000..4cd6ff0
--- /dev/null
+++ b/src/zope/tal/tests/input/test34.html
@@ -0,0 +1,11 @@
+<span i18n:translate="don't translate me">
+ stuff
+ <span tal:replace="string:foobar" i18n:name="longname" />
+ more stuff
+</span>
+
+<span i18n:translate="">
+ stuff
+ <span tal:replace="string:foobar" i18n:name="longname" />
+ more stuff
+</span>
diff --git a/src/zope/tal/tests/input/test35.html b/src/zope/tal/tests/input/test35.html
new file mode 100644
index 0000000..7964e9f
--- /dev/null
+++ b/src/zope/tal/tests/input/test35.html
@@ -0,0 +1,7 @@
+<span metal:define-macro="page" tal:omit-tag="">
+ <h1 metal:define-slot="name" tal:omit-tag="" />
+</span>
+
+<span metal:use-macro="page">
+ <h1 metal:fill-slot="name" tal:content="macroname">name</h1>
+</span> \ No newline at end of file
diff --git a/src/zope/tal/tests/input/test36.html b/src/zope/tal/tests/input/test36.html
new file mode 100644
index 0000000..bf4932a
--- /dev/null
+++ b/src/zope/tal/tests/input/test36.html
@@ -0,0 +1,6 @@
+<span tal:replace="string:<foo>" />
+<span i18n:translate="">
+ <span tal:replace="string:<foo>" i18n:name="name1" />
+ <span tal:replace="structure string:<bar />" i18n:name="name2" />
+ <span tal:omit-tag="" i18n:name="name3"><b>some</b> <i>text</i></span>
+</span>
diff --git a/src/zope/tal/tests/input/test_domain.html b/src/zope/tal/tests/input/test_domain.html
new file mode 100644
index 0000000..95d40a2
--- /dev/null
+++ b/src/zope/tal/tests/input/test_domain.html
@@ -0,0 +1,7 @@
+<div i18n:domain="lower">
+<span i18n:translate="">Replace this</span>
+<span i18n:translate="msgid">This is a
+translated string</span>
+<span i18n:translate="">And another
+translated string</span>
+</div>
diff --git a/src/zope/tal/tests/input/test_failed_attr_translation.html b/src/zope/tal/tests/input/test_failed_attr_translation.html
new file mode 100644
index 0000000..1c395c7
--- /dev/null
+++ b/src/zope/tal/tests/input/test_failed_attr_translation.html
@@ -0,0 +1,2 @@
+<input value="don't translate me"
+ i18n:attributes="value">
diff --git a/src/zope/tal/tests/input/test_metal1.html b/src/zope/tal/tests/input/test_metal1.html
new file mode 100644
index 0000000..a5371ce
--- /dev/null
+++ b/src/zope/tal/tests/input/test_metal1.html
@@ -0,0 +1,61 @@
+<span metal:define-macro="OUTER">
+ AAA
+ <span metal:define-macro="INNER">INNER</span>
+ BBB
+</span>
+
+<xxx metal:use-macro="OUTER">
+</xxx>
+
+<xxx metal:use-macro="INNER">
+</xxx>
+
+<span metal:define-macro="OUTER2">
+ AAA
+ <xxx metal:define-slot="OUTERSLOT">
+ <span metal:define-macro="INNER2">INNER</span>
+ </xxx>
+ BBB
+</span>
+
+<xxx metal:use-macro="OUTER2">
+</xxx>
+
+<xxx metal:use-macro="INNER2">
+</xxx>
+
+<xxx metal:use-macro="OUTER2">
+ <yyy metal:fill-slot="OUTERSLOT">OUTERSLOT</yyy>
+</xxx>
+
+<span metal:define-macro="OUTER3">
+ AAA
+ <xxx metal:define-slot="OUTERSLOT">
+ <span metal:define-macro="INNER3">INNER
+ <xxx metal:define-slot="INNERSLOT">INNERSLOT</xxx>
+ </span>
+ </xxx>
+ BBB
+</span>
+
+<xxx metal:use-macro="OUTER3">
+</xxx>
+
+<xxx metal:use-macro="OUTER3">
+ <yyy metal:fill-slot="OUTERSLOT">OUTERSLOT</yyy>
+</xxx>
+
+<xxx metal:use-macro="INNER3">
+</xxx>
+
+<xxx metal:use-macro="INNER3">
+ <yyy metal:fill-slot="INNERSLOT">INNERSLOT</yyy>
+</xxx>
+
+<xxx metal:use-macro="INNER3">
+ <yyy metal:fill-slot="INNERSLOT">
+ <zzz metal:define-macro="INSLOT">INSLOT</zzz>
+ </yyy>
+</xxx>
+
+<xxx metal:use-macro="INSLOT"></xxx>
diff --git a/src/zope/tal/tests/input/test_metal2.html b/src/zope/tal/tests/input/test_metal2.html
new file mode 100644
index 0000000..425508a
--- /dev/null
+++ b/src/zope/tal/tests/input/test_metal2.html
@@ -0,0 +1,7 @@
+<div metal:define-macro="OUTER">
+ OUTER
+ <span metal:define-macro="INNER">INNER</span>
+ OUTER
+</div>
+
+<div metal:use-macro="OUTER"/>
diff --git a/src/zope/tal/tests/input/test_metal3.html b/src/zope/tal/tests/input/test_metal3.html
new file mode 100644
index 0000000..b0af907
--- /dev/null
+++ b/src/zope/tal/tests/input/test_metal3.html
@@ -0,0 +1 @@
+<span tal:attributes="class string:foo">Should not get attr in metal</span>
diff --git a/src/zope/tal/tests/input/test_metal4.html b/src/zope/tal/tests/input/test_metal4.html
new file mode 100644
index 0000000..dc774d3
--- /dev/null
+++ b/src/zope/tal/tests/input/test_metal4.html
@@ -0,0 +1,4 @@
+<!-- the outer element *must* be tal:something or metal:something -->
+<metal:block define-macro="page" i18n:domain="zope">
+ <title metal:define-slot="title">Z3 UI</title>
+</metal:block>
diff --git a/src/zope/tal/tests/input/test_metal5.html b/src/zope/tal/tests/input/test_metal5.html
new file mode 100644
index 0000000..8bae3d8
--- /dev/null
+++ b/src/zope/tal/tests/input/test_metal5.html
@@ -0,0 +1,4 @@
+<!-- the outer element *must* include tal:omit-tag='' -->
+<x tal:omit-tag="" metal:define-macro="page" i18n:domain="zope">
+ <title metal:define-slot="title">Z3 UI</title>
+</x>
diff --git a/src/zope/tal/tests/input/test_metal6.html b/src/zope/tal/tests/input/test_metal6.html
new file mode 100644
index 0000000..ce243f2
--- /dev/null
+++ b/src/zope/tal/tests/input/test_metal6.html
@@ -0,0 +1,5 @@
+<metal:block define-macro="page">
+ <html i18:domain="zope">
+ <metal:block define-slot="title">Z3 UI</metal:block>
+ </html>
+</metal:block>
diff --git a/src/zope/tal/tests/input/test_metal7.html b/src/zope/tal/tests/input/test_metal7.html
new file mode 100644
index 0000000..75ec511
--- /dev/null
+++ b/src/zope/tal/tests/input/test_metal7.html
@@ -0,0 +1,6 @@
+<html metal:define-macro="page" i18n:domain="zope">
+ <x metal:define-slot="title" />
+</html>
+<html metal:use-macro="page">
+ <x metal:fill-slot="title" />
+</html>
diff --git a/src/zope/tal/tests/input/test_metal8.html b/src/zope/tal/tests/input/test_metal8.html
new file mode 100644
index 0000000..40d8a43
--- /dev/null
+++ b/src/zope/tal/tests/input/test_metal8.html
@@ -0,0 +1,15 @@
+<html metal:define-macro="page" i18n:domain="zope">
+<body>
+<div metal:define-macro="workspace">
+<div metal:define-slot="body">
+Default body
+</div>
+</div>
+</body>
+</html>
+
+<html metal:use-macro="page">
+<div metal:fill-slot="body">
+Filled-in body
+</div>
+</html>
diff --git a/src/zope/tal/tests/input/test_metal9.html b/src/zope/tal/tests/input/test_metal9.html
new file mode 100644
index 0000000..46b1b45
--- /dev/null
+++ b/src/zope/tal/tests/input/test_metal9.html
@@ -0,0 +1,23 @@
+<div metal:define-macro="macro1" i18n:domain="zope">
+<span metal:define-slot="slot1">
+Default for macro1
+</span>
+</div>
+
+<div metal:define-macro="macro2" metal:extend-macro="macro1" i18n:domain="zope">
+<span metal:fill-slot="slot1">
+Macro 2's slot 1 decoration
+<span metal:define-slot="slot1">
+Default for macro2
+</span>
+</span>
+</div>
+
+<div metal:use-macro="macro2">
+</div>
+
+<div metal:use-macro="macro2">
+<span metal:fill-slot="slot1">
+Custom slot1
+</span>
+</div>
diff --git a/src/zope/tal/tests/input/test_sa1.html b/src/zope/tal/tests/input/test_sa1.html
new file mode 100644
index 0000000..8879865
--- /dev/null
+++ b/src/zope/tal/tests/input/test_sa1.html
@@ -0,0 +1,6 @@
+<html>
+<title>Simple test of source annotations</title>
+<body>
+<p>Foo!</p>
+</body>
+</html>
diff --git a/src/zope/tal/tests/input/test_sa1.xml b/src/zope/tal/tests/input/test_sa1.xml
new file mode 100644
index 0000000..d00a46d
--- /dev/null
+++ b/src/zope/tal/tests/input/test_sa1.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" ?>
+<html>
+<title>Simple test of source annotations</title>
+<body>
+<p>Foo!</p>
+</body>
+</html>
diff --git a/src/zope/tal/tests/input/test_sa2.html b/src/zope/tal/tests/input/test_sa2.html
new file mode 100644
index 0000000..1c4e06b
--- /dev/null
+++ b/src/zope/tal/tests/input/test_sa2.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html
+ PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "DTD/xhtml1-transitional.dtd">
+<html>
+<title>Simple test of source annotations</title>
+<body>
+<p>Foo!</p>
+</body>
+</html>
diff --git a/src/zope/tal/tests/input/test_sa2.xml b/src/zope/tal/tests/input/test_sa2.xml
new file mode 100644
index 0000000..b54d6a1
--- /dev/null
+++ b/src/zope/tal/tests/input/test_sa2.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" ?>
+<!DOCTYPE html
+ PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "DTD/xhtml1-transitional.dtd">
+<html>
+<title>Simple test of source annotations</title>
+<body>
+<p>Foo!</p>
+</body>
+</html>
diff --git a/src/zope/tal/tests/input/test_sa3.html b/src/zope/tal/tests/input/test_sa3.html
new file mode 100644
index 0000000..675805d
--- /dev/null
+++ b/src/zope/tal/tests/input/test_sa3.html
@@ -0,0 +1,15 @@
+<html>
+<body>
+ <div metal:define-macro="macro1">This is macro1 on sa3 line 3.
+ <span metal:define-slot="slot1">This is slot1 on sa3 line 4.</span>
+ This is the end of macro1 on sa3 line 5.
+ </div>
+ <p>Some text on sa3 line 7.</p>
+ <p metal:use-macro="macro1">
+ This text on sa3 line 9 will disappear.
+ <b metal:fill-slot="slot1">Text from sa3 line 10 is filled into slot1.</b>
+ This text on sa3 line 11 will disappear.
+ </p>
+ <p>This is some text on sa3 line 13.</p>
+</body>
+</html>
diff --git a/src/zope/tal/tests/input/test_sa3.xml b/src/zope/tal/tests/input/test_sa3.xml
new file mode 100644
index 0000000..79e3251
--- /dev/null
+++ b/src/zope/tal/tests/input/test_sa3.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" ?>
+<html>
+<body xmlns:metal="http://xml.zope.org/namespaces/metal">
+ <div metal:define-macro="macro1">This is macro1 on sa3 line 4.
+ <span metal:define-slot="slot1">This is slot1 on sa3 line 5.</span>
+ This is the end of macro1 on sa3 line 6.
+ </div>
+ <p>Some text on sa3 line 8.</p>
+ <p metal:use-macro="macro1">
+ This text on sa3 line 10 will disappear.
+ <b metal:fill-slot="slot1">Text from sa3 line 11 is filled into slot1.</b>
+ This text on sa3 line 12 will disappear.
+ </p>
+ <p>This is some text on sa3 line 14.</p>
+</body>
+</html>
diff --git a/src/zope/tal/tests/input/test_sa4.html b/src/zope/tal/tests/input/test_sa4.html
new file mode 100644
index 0000000..97596f6
--- /dev/null
+++ b/src/zope/tal/tests/input/test_sa4.html
@@ -0,0 +1,11 @@
+<html>
+<body>
+ <p>Some text on sa4 line 3.</p>
+ <p metal:use-macro="tests/input/test_sa3.html/macro1">
+ This text on sa4 line 5 will disappear.
+ <b metal:fill-slot="slot1">Text from sa4 line 6 is filled into slot1.</b>
+ This text on sa4 line 7 will disappear.
+ </p>
+ <p>This is some text on sa4 line 9.</p>
+</body>
+</html>
diff --git a/src/zope/tal/tests/markbench.py b/src/zope/tal/tests/markbench.py
new file mode 100644
index 0000000..f08f9e2
--- /dev/null
+++ b/src/zope/tal/tests/markbench.py
@@ -0,0 +1,187 @@
+#! /usr/bin/env python
+##############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Run benchmarks of TAL vs. DTML
+
+$Id$
+"""
+
+import warnings
+warnings.filterwarnings("ignore", category=DeprecationWarning)
+
+import os
+os.environ['NO_SECURITY'] = 'true'
+
+import getopt
+import sys
+import time
+
+from cStringIO import StringIO
+
+#from zope.documenttemplate.dt_html import HTMLFile
+
+from zope.tal.htmltalparser import HTMLTALParser
+from zope.tal.talinterpreter import TALInterpreter
+from zope.tal.dummyengine import DummyEngine
+
+
+def time_apply(f, args, kwargs, count):
+ r = [None] * count
+ for i in range(4):
+ f(*args, **kwargs)
+ t0 = time.clock()
+ for i in r:
+ pass
+ t1 = time.clock()
+ for i in r:
+ f(*args, **kwargs)
+ t = time.clock() - t1 - (t1 - t0)
+ return t / count
+
+def time_zpt(fn, count):
+ from zope.pagetemplate.pagetemplate import PageTemplate
+ pt = PageTemplate()
+ pt.write(open(fn).read())
+ return time_apply(pt.pt_render, (data,), {}, count)
+
+def time_tal(fn, count):
+ p = HTMLTALParser()
+ p.parseFile(fn)
+ program, macros = p.getCode()
+ engine = DummyEngine(macros)
+ engine.globals = data
+ tal = TALInterpreter(program, macros, engine, StringIO(), wrap=0,
+ tal=1, strictinsert=0)
+ return time_apply(tal, (), {}, count)
+
+def time_dtml(fn, count):
+ html = HTMLFile(fn)
+ return time_apply(html, (), data, count)
+
+def profile_zpt(fn, count, profiler):
+ from zope.pagetemplate.pagetemplate import PageTemplate
+ pt = PageTemplate()
+ pt.write(open(fn).read())
+ for i in range(4):
+ pt.pt_render(extra_context=data)
+ r = [None] * count
+ for i in r:
+ profiler.runcall(pt.pt_render, 0, data)
+
+def profile_tal(fn, count, profiler):
+ p = HTMLTALParser()
+ p.parseFile(fn)
+ program, macros = p.getCode()
+ engine = DummyEngine(macros)
+ engine.globals = data
+ tal = TALInterpreter(program, macros, engine, StringIO(), wrap=0,
+ tal=1, strictinsert=0)
+ for i in range(4):
+ tal()
+ r = [None] * count
+ for i in r:
+ profiler.runcall(tal)
+
+# Figure out where the benchmark files are:
+try:
+ fname = __file__
+except NameError:
+ fname = sys.argv[0]
+taldir = os.path.dirname(os.path.dirname(os.path.abspath(fname)))
+benchdir = os.path.join(taldir, 'benchmark')
+
+# Construct templates for the filenames:
+tal_fn = os.path.join(benchdir, 'tal%.2d.html')
+dtml_fn = os.path.join(benchdir, 'dtml%.2d.html')
+
+def compare(n, count, profiler=None, verbose=1):
+ if verbose:
+ t1 = int(time_zpt(tal_fn % n, count) * 1000 + 0.5)
+ t2 = int(time_tal(tal_fn % n, count) * 1000 + 0.5)
+ t3 = 'n/a' # int(time_dtml(dtml_fn % n, count) * 1000 + 0.5)
+ print '%.2d: %10s %10s %10s' % (n, t1, t2, t3)
+ if profiler:
+ profile_tal(tal_fn % n, count, profiler)
+
+def main(count, profiler=None, verbose=1):
+ n = 1
+ if verbose:
+ print '##: %10s %10s %10s' % ('ZPT', 'TAL', 'DTML')
+ while os.path.isfile(tal_fn % n) and os.path.isfile(dtml_fn % n):
+ compare(n, count, profiler, verbose)
+ n = n + 1
+
+def get_signal_name(sig):
+ import signal
+ for name in dir(signal):
+ if getattr(signal, name) == sig:
+ return name
+ return None
+
+data = {'x':'X', 'r2': range(2), 'r8': range(8), 'r64': range(64)}
+for i in range(10):
+ data['x%s' % i] = 'X%s' % i
+
+if __name__ == "__main__":
+ filename = "markbench.prof"
+ profiler = None
+ runtests = False
+ verbose = True
+
+ opts, args = getopt.getopt(sys.argv[1:], "pqt")
+ for opt, arg in opts:
+ if opt == "-p":
+ import profile
+ profiler = profile.Profile()
+ elif opt == "-q":
+ verbose = False
+ elif opt == "-t":
+ runtests = True
+
+ if runtests:
+ srcdir = os.path.dirname(os.path.dirname(taldir))
+ topdir = os.path.dirname(srcdir)
+ pwd = os.getcwd()
+ os.chdir(topdir)
+ rc = os.spawnl(os.P_WAIT, sys.executable,
+ sys.executable, "test.py", "zope.tal.tests")
+ if rc > 0:
+ # TODO: Failing tests don't cause test.py to report an
+ # error; not sure why. ;-(
+ sys.exit(rc)
+ elif rc < 0:
+ sig = -rc
+ print >>sys.stderr, (
+ "Process exited, signal %d (%s)."
+ % (sig, get_signal_name(sig) or "<unknown signal>"))
+ sys.exit(1)
+ os.chdir(pwd)
+
+ if len(args) >= 1:
+ for arg in args:
+ compare(int(arg), 25, profiler, verbose)
+ else:
+ main(25, profiler, verbose)
+
+ if profiler is not None:
+ profiler.dump_stats(filename)
+ import pstats
+ p = pstats.Stats(filename)
+ p.strip_dirs()
+ p.sort_stats('time', 'calls')
+ try:
+ p.print_stats(20)
+ except IOError, e:
+ if e.errno != errno.EPIPE:
+ raise
diff --git a/src/zope/tal/tests/output/__init__.py b/src/zope/tal/tests/output/__init__.py
new file mode 100644
index 0000000..b711d36
--- /dev/null
+++ b/src/zope/tal/tests/output/__init__.py
@@ -0,0 +1,2 @@
+#
+# This file is necessary to make this directory a package.
diff --git a/src/zope/tal/tests/output/acme_template.html b/src/zope/tal/tests/output/acme_template.html
new file mode 100644
index 0000000..3d37355
--- /dev/null
+++ b/src/zope/tal/tests/output/acme_template.html
@@ -0,0 +1,26 @@
+<!-- This is ACME's generic look and feel, which is based on
+PNOME's look and feel. -->
+<html>
+<head>
+<title>ACME Look and Feel</title>
+
+
+</head>
+<body>
+<div>
+ <div>
+ "The early bird gets the worm, but the second mouse gets the cheese."
+ </div>
+ <a href="#">Preferences...</a>
+</div>
+<div>
+ Content here
+</div>
+<div>
+Copyright 2004 Acme Inc.
+<div>
+Standard disclaimers apply.
+</div>
+</div>
+</body>
+</html>
diff --git a/src/zope/tal/tests/output/acme_template_source.html b/src/zope/tal/tests/output/acme_template_source.html
new file mode 100644
index 0000000..11f19d3
--- /dev/null
+++ b/src/zope/tal/tests/output/acme_template_source.html
@@ -0,0 +1,27 @@
+<!-- This is ACME's generic look and feel, which is based on
+PNOME's look and feel. -->
+<html metal:define-macro="page"
+ metal:use-macro="pnome_macros_page">
+<head>
+<title metal:fill-slot="title">ACME Look and Feel</title>
+<metal:block>
+</metal:block>
+</head>
+<body>
+<div>
+ <div>
+ "The early bird gets the worm, but the second mouse gets the cheese."
+ </div>
+ <a href="#">Preferences...</a>
+</div>
+<div>
+ Content here
+</div>
+<div metal:fill-slot="page-footer">
+Copyright 2004 Acme Inc.
+<div metal:define-slot="disclaimer">
+Standard disclaimers apply.
+</div>
+</div>
+</body>
+</html>
diff --git a/src/zope/tal/tests/output/document_list.html b/src/zope/tal/tests/output/document_list.html
new file mode 100644
index 0000000..9e0ea10
--- /dev/null
+++ b/src/zope/tal/tests/output/document_list.html
@@ -0,0 +1,30 @@
+<!-- ACME's document_list uses the ACME look and feel -->
+<html>
+<head>
+<title>Acme Document List</title>
+<style type="text/css">
+ body { background-color: white; }
+</style>
+</head>
+<body>
+<div>
+ <div>
+ "The early bird gets the worm, but the second mouse gets the cheese."
+ </div>
+ <a href="#">Preferences...</a>
+</div>
+<div>
+<h1>Documents</h1>
+<ul>
+<li>Rocket Science for Dummies</li>
+<li>Birds for the Gourmet Chef</li>
+</ul>
+</div>
+<div>
+Copyright 2004 Acme Inc.
+<div>
+This document list is classified.
+</div>
+</div>
+</body>
+</html>
diff --git a/src/zope/tal/tests/output/document_list_source.html b/src/zope/tal/tests/output/document_list_source.html
new file mode 100644
index 0000000..69600e0
--- /dev/null
+++ b/src/zope/tal/tests/output/document_list_source.html
@@ -0,0 +1,30 @@
+<!-- ACME's document_list uses the ACME look and feel -->
+<html metal:use-macro="acme_macros_page">
+<head>
+<title metal:fill-slot="title">Acme Document List</title>
+<style metal:fill-slot="local-styles" type="text/css">
+ body { background-color: white; }
+</style>
+</head>
+<body>
+<div>
+ <div>
+ "The early bird gets the worm, but the second mouse gets the cheese."
+ </div>
+ <a href="#">Preferences...</a>
+</div>
+<div metal:fill-slot="content">
+<h1>Documents</h1>
+<ul>
+<li>Rocket Science for Dummies</li>
+<li>Birds for the Gourmet Chef</li>
+</ul>
+</div>
+<div>
+Copyright 2004 Acme Inc.
+<div metal:fill-slot="disclaimer">
+This document list is classified.
+</div>
+</div>
+</body>
+</html>
diff --git a/src/zope/tal/tests/output/test01.html b/src/zope/tal/tests/output/test01.html
new file mode 100644
index 0000000..7064db0
--- /dev/null
+++ b/src/zope/tal/tests/output/test01.html
@@ -0,0 +1,68 @@
+<!DOCTYPE html
+ PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "DTD/xhtml1-transitional.dtd">
+<html>
+
+ <head>dadada</head>
+
+ <body>
+
+ <h1>This
+Is
+The
+Replaced
+Title</h1>
+
+ <!-- test entity references -->
+ &nbsp;&HarryPotter;
+
+ <!-- examples adapted from TemplateAttributeLanguageSyntax -->
+
+ <span>here/id</span>
+
+ <p>5</p>
+
+ <p>
+ honda
+ </p>
+ <p>
+ subaru
+ </p>
+ <p>
+ acura
+ </p>
+
+ <p xml:foo="bar">foo bar</p>
+
+ <!-- more examples -->
+
+ <ul>
+ <span>
+ <li>honda</li>
+ </span>
+ <span>
+ <li>subaru</li>
+ </span>
+ <span>
+ <li>acura</li>
+ </span>
+ </ul>
+
+ <!-- test attribute expansion -->
+
+ <a href="http://python.org">python</a>
+ <a href="http://python.org">python</a>
+
+ <!-- test insert/replace structure -->
+ <span></span>
+
+
+ <span />
+ <span />
+
+ &nbsp;<h3>Header Level 3</h3>
+ <span>&nbsp;<h3>Header Level 3</h3></span>
+
+ </body>
+
+</html>
diff --git a/src/zope/tal/tests/output/test01.xml b/src/zope/tal/tests/output/test01.xml
new file mode 100644
index 0000000..91e9851
--- /dev/null
+++ b/src/zope/tal/tests/output/test01.xml
@@ -0,0 +1,65 @@
+<?xml version="1.0" ?>
+<!DOCTYPE html
+ PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "DTD/xhtml1-transitional.dtd">
+<html>
+
+ <head>dadada</head>
+
+ <body>
+
+ <h1>This Is The Replaced Title</h1>
+
+ <!-- test entity references -->
+ &nbsp;&HarryPotter;
+
+ <!-- examples adapted from TemplateAttributeLanguageSyntax -->
+
+ <span>here/id</span>
+
+ <p>5</p>
+
+ <p>
+ honda
+ </p>
+ <p>
+ subaru
+ </p>
+ <p>
+ acura
+ </p>
+
+ <p xml:foo="bar">foo bar</p>
+
+ <!-- more examples -->
+
+ <ul>
+ <span>
+ <li>honda</li>
+ </span>
+ <span>
+ <li>subaru</li>
+ </span>
+ <span>
+ <li>acura</li>
+ </span>
+ </ul>
+
+ <!-- test attribute expansion -->
+
+ <a href="http://python.org">python</a>
+ <a href="http://python.org">python</a>
+
+ <!-- test insert/replace structure -->
+ <span></span>
+
+
+ <span/>
+ <span/>
+
+ &nbsp;<h3>Header Level 3</h3>
+ <span>&nbsp;<h3>Header Level 3</h3></span>
+
+ </body>
+
+</html>
diff --git a/src/zope/tal/tests/output/test02.html b/src/zope/tal/tests/output/test02.html
new file mode 100644
index 0000000..8d081fc
--- /dev/null
+++ b/src/zope/tal/tests/output/test02.html
@@ -0,0 +1,118 @@
+<biztalk_1 xmlns="urn:schemas-biztalk-org:biztalk:biztalk_1">
+
+<foo:header xmlns:foo="whomping-willow" plain="guido" quote="&quot;" apostrophe="'" both="&quot;'" lt="&lt;" gt="&gt;" amp="&amp;" foo="">
+ <manifest>
+ <document>
+ <name>sample1</name>
+ <description>a simple invoice</description>
+ </document>
+ </manifest>
+</foo:header>
+
+<body>
+
+<!-- sample1.xml is an example of a simple invoice for a small restaurant supplies order -->
+
+<invoice xmlns="urn:http://schemas.biztalk.org/united_rest_com/yw7sg15x.xml">
+ <header>
+ <invoicenumber>01786</invoicenumber>
+ <invoicedate>2000-03-17</invoicedate> <!-- March 17th, 2000 -->
+ <orderno>55377</orderno>
+ <orderdate>2000-03-15</orderdate> <!-- March 15th, 2000 -->
+ <customerpo>GJ03405</customerpo>
+ <shipmethod>DAVE 1</shipmethod>
+ <shipdate>2000-03-17</shipdate> <!-- March 17th, 2000 -->
+ <customerid>K5211(34)</customerid>
+ <salespersoncode>23</salespersoncode>
+ <taxid>23</taxid>
+ </header>
+ <invoiceto>
+ <name>SHIPWRIGHT RESTAURANTS LIMITED</name>
+ <addressline>125 NORTH SERVICE ROAD W</addressline>
+ <addressline>WESTLAKE ACCESS</addressline>
+ <city>NORTH BAY</city>
+ <postcode>L8B1O5</postcode>
+ <state>ONTARIO</state>
+ <country>CANADA</country>
+ </invoiceto>
+ <shipto>
+ <name />
+ <addressline>ATTN: PAULINE DEGRASSI</addressline>
+ <city />
+ <postcode />
+ <state />
+ <country />
+ </shipto>
+ <detaillines>
+ <detailline>
+ <quantityshipped>1</quantityshipped>
+ <unitofmeasure>CS</unitofmeasure>
+ <partnumber>DM 5309</partnumber>
+ <partdescription>#1013 12 OZ.MUNICH STEIN</partdescription>
+ <unitprice>37.72</unitprice>
+ <linetotal>37.72</linetotal>
+ </detailline>
+ <detailline>
+ <quantityshipped>6</quantityshipped>
+ <unitofmeasure>DZ</unitofmeasure>
+ <partnumber>ON 6420</partnumber>
+ <partdescription>PROVINCIAL DINNER FORK</partdescription>
+ <unitprice>17.98</unitprice>
+ <linetotal>107.88</linetotal>
+ </detailline>
+ <detailline>
+ <quantityshipped>72</quantityshipped>
+ <unitofmeasure>EA</unitofmeasure>
+ <partnumber>JR20643</partnumber>
+ <partdescription>PLASTIC HANDLED STEAK KNIFE</partdescription>
+ <unitprice>.81</unitprice>
+ <linetotal>58.32</linetotal>
+ </detailline>
+ <detailline>
+ <quantityshipped>6</quantityshipped>
+ <unitofmeasure>DZ</unitofmeasure>
+ <partnumber>ON 6410</partnumber>
+ <partdescription>PROVINCIAL TEASPOONS</partdescription>
+ <unitprice>12.16</unitprice>
+ <linetotal>72.96</linetotal>
+ </detailline>
+ <detailline>
+ <quantityshipped>0</quantityshipped>
+ <unitofmeasure>DZ</unitofmeasure>
+ <partnumber>ON 6411</partnumber>
+ <partdescription>PROVINCIAL RD BOWL SPOON</partdescription>
+ <quantitybackordered>6</quantitybackordered>
+ <unitprice>17.98</unitprice>
+ <linetotal>0.00</linetotal>
+ </detailline>
+ <detailline>
+ <quantityshipped>1</quantityshipped>
+ <unitofmeasure>EA</unitofmeasure>
+ <partnumber>DO 3218</partnumber>
+ <partdescription>34 OZ DUAL DIAL SCALE AM3218</partdescription>
+ <unitprice>70.00</unitprice>
+ <discountpercentage>5.0</discountpercentage>
+ <linetotal>66.50</linetotal>
+ </detailline>
+ <detailline>
+ <quantityshipped>1</quantityshipped>
+ <unitofmeasure>CS</unitofmeasure>
+ <partnumber>DM 195</partnumber>
+ <partdescription>20 OZ.BEER PUB GLASS</partdescription>
+ <unitprice>55.90</unitprice>
+ <linetotal>55.90</linetotal>
+ </detailline>
+ </detaillines>
+ <totals>
+ <subtotal>399.28</subtotal>
+ <discounttotal>3.50</discounttotal>
+ <freighttotal>23.75</freighttotal>
+ <gsttotal>29.61</gsttotal>
+ <provtaxtotal>33.84</provtaxtotal>
+ <othertotal>33.84</othertotal>
+ <invoicetotal>486.48</invoicetotal>
+ </totals>
+</invoice>
+
+</body>
+</biztalk_1>
diff --git a/src/zope/tal/tests/output/test02.xml b/src/zope/tal/tests/output/test02.xml
new file mode 100644
index 0000000..71ff075
--- /dev/null
+++ b/src/zope/tal/tests/output/test02.xml
@@ -0,0 +1,119 @@
+<?xml version="1.0" ?>
+<biztalk_1 xmlns="urn:schemas-biztalk-org:biztalk:biztalk_1">
+
+<foo:header xmlns:foo="whomping-willow" plain="guido" quote="&quot;" apostrophe="'" both="&quot;'" lt="&lt;" gt="&gt;" amp="&amp;" foo="">
+ <manifest>
+ <document>
+ <name>sample1</name>
+ <description>a simple invoice</description>
+ </document>
+ </manifest>
+</foo:header>
+
+<body>
+
+<!-- sample1.xml is an example of a simple invoice for a small restaurant supplies order -->
+
+<Invoice xmlns="urn:http://schemas.biztalk.org/united_rest_com/yw7sg15x.xml">
+ <Header>
+ <InvoiceNumber>01786</InvoiceNumber>
+ <InvoiceDate>2000-03-17</InvoiceDate> <!-- March 17th, 2000 -->
+ <OrderNo>55377</OrderNo>
+ <OrderDate>2000-03-15</OrderDate> <!-- March 15th, 2000 -->
+ <CustomerPO>GJ03405</CustomerPO>
+ <ShipMethod>DAVE 1</ShipMethod>
+ <ShipDate>2000-03-17</ShipDate> <!-- March 17th, 2000 -->
+ <CustomerID>K5211(34)</CustomerID>
+ <SalesPersonCode>23</SalesPersonCode>
+ <TaxID>23</TaxID>
+ </Header>
+ <InvoiceTo>
+ <Name>SHIPWRIGHT RESTAURANTS LIMITED</Name>
+ <AddressLine>125 NORTH SERVICE ROAD W</AddressLine>
+ <AddressLine>WESTLAKE ACCESS</AddressLine>
+ <City>NORTH BAY</City>
+ <PostCode>L8B1O5</PostCode>
+ <State>ONTARIO</State>
+ <Country>CANADA</Country>
+ </InvoiceTo>
+ <ShipTo>
+ <Name/>
+ <AddressLine>ATTN: PAULINE DEGRASSI</AddressLine>
+ <City/>
+ <PostCode/>
+ <State/>
+ <Country/>
+ </ShipTo>
+ <DetailLines>
+ <DetailLine>
+ <QuantityShipped>1</QuantityShipped>
+ <UnitOfMeasure>CS</UnitOfMeasure>
+ <PartNumber>DM 5309</PartNumber>
+ <PartDescription>#1013 12 OZ.MUNICH STEIN</PartDescription>
+ <UnitPrice>37.72</UnitPrice>
+ <LineTotal>37.72</LineTotal>
+ </DetailLine>
+ <DetailLine>
+ <QuantityShipped>6</QuantityShipped>
+ <UnitOfMeasure>DZ</UnitOfMeasure>
+ <PartNumber>ON 6420</PartNumber>
+ <PartDescription>PROVINCIAL DINNER FORK</PartDescription>
+ <UnitPrice>17.98</UnitPrice>
+ <LineTotal>107.88</LineTotal>
+ </DetailLine>
+ <DetailLine>
+ <QuantityShipped>72</QuantityShipped>
+ <UnitOfMeasure>EA</UnitOfMeasure>
+ <PartNumber>JR20643</PartNumber>
+ <PartDescription>PLASTIC HANDLED STEAK KNIFE</PartDescription>
+ <UnitPrice>.81</UnitPrice>
+ <LineTotal>58.32</LineTotal>
+ </DetailLine>
+ <DetailLine>
+ <QuantityShipped>6</QuantityShipped>
+ <UnitOfMeasure>DZ</UnitOfMeasure>
+ <PartNumber>ON 6410</PartNumber>
+ <PartDescription>PROVINCIAL TEASPOONS</PartDescription>
+ <UnitPrice>12.16</UnitPrice>
+ <LineTotal>72.96</LineTotal>
+ </DetailLine>
+ <DetailLine>
+ <QuantityShipped>0</QuantityShipped>
+ <UnitOfMeasure>DZ</UnitOfMeasure>
+ <PartNumber>ON 6411</PartNumber>
+ <PartDescription>PROVINCIAL RD BOWL SPOON</PartDescription>
+ <QuantityBackOrdered>6</QuantityBackOrdered>
+ <UnitPrice>17.98</UnitPrice>
+ <LineTotal>0.00</LineTotal>
+ </DetailLine>
+ <DetailLine>
+ <QuantityShipped>1</QuantityShipped>
+ <UnitOfMeasure>EA</UnitOfMeasure>
+ <PartNumber>DO 3218</PartNumber>
+ <PartDescription>34 OZ DUAL DIAL SCALE AM3218</PartDescription>
+ <UnitPrice>70.00</UnitPrice>
+ <DiscountPercentage>5.0</DiscountPercentage>
+ <LineTotal>66.50</LineTotal>
+ </DetailLine>
+ <DetailLine>
+ <QuantityShipped>1</QuantityShipped>
+ <UnitOfMeasure>CS</UnitOfMeasure>
+ <PartNumber>DM 195</PartNumber>
+ <PartDescription>20 OZ.BEER PUB GLASS</PartDescription>
+ <UnitPrice>55.90</UnitPrice>
+ <LineTotal>55.90</LineTotal>
+ </DetailLine>
+ </DetailLines>
+ <Totals>
+ <SubTotal>399.28</SubTotal>
+ <DiscountTotal>3.50</DiscountTotal>
+ <FreightTotal>23.75</FreightTotal>
+ <GSTTotal>29.61</GSTTotal>
+ <ProvTaxTotal>33.84</ProvTaxTotal>
+ <OtherTotal>33.84</OtherTotal>
+ <InvoiceTotal>486.48</InvoiceTotal>
+ </Totals>
+</Invoice>
+
+</body>
+</biztalk_1>
diff --git a/src/zope/tal/tests/output/test03.html b/src/zope/tal/tests/output/test03.html
new file mode 100644
index 0000000..7fb5156
--- /dev/null
+++ b/src/zope/tal/tests/output/test03.html
@@ -0,0 +1,9 @@
+<p>
+ <span>
+ <span>hello brave new world</span>
+ <span>
+ <span>goodbye cruel world</span>
+ </span>
+ <span>hello brave new world</span>
+ </span>
+</p>
diff --git a/src/zope/tal/tests/output/test03.xml b/src/zope/tal/tests/output/test03.xml
new file mode 100644
index 0000000..24be638
--- /dev/null
+++ b/src/zope/tal/tests/output/test03.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" ?>
+<p>
+ <span>
+ <span>hello brave new world</span>
+ <span>
+ <span>goodbye cruel world</span>
+ </span>
+ <span>hello brave new world</span>
+ </span>
+</p>
diff --git a/src/zope/tal/tests/output/test04.html b/src/zope/tal/tests/output/test04.html
new file mode 100644
index 0000000..f0666da
--- /dev/null
+++ b/src/zope/tal/tests/output/test04.html
@@ -0,0 +1,38 @@
+<html>
+
+ <body>
+
+ <ul>
+ </ul>
+
+ <span />
+
+ <ul>
+ <li>
+ 0
+ hello world
+ </li>
+ <li>
+ 1
+ hello world
+ </li>
+ </ul>
+
+ <span />
+
+ <ul>
+ <li>
+ 0
+ goodbye cruel world
+ </li>
+ <li>
+ 1
+ goodbye cruel world
+ </li>
+ </ul>
+
+ <p>define-slot</p>
+
+ </body>
+
+</html>
diff --git a/src/zope/tal/tests/output/test04.xml b/src/zope/tal/tests/output/test04.xml
new file mode 100644
index 0000000..8b73d02
--- /dev/null
+++ b/src/zope/tal/tests/output/test04.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" ?>
+<html>
+
+ <body>
+
+ <ul>
+ </ul>
+
+ <span/>
+
+ <ul>
+ <li>
+ 0
+ hello world
+ </li>
+ <li>
+ 1
+ hello world
+ </li>
+ </ul>
+
+ <span/>
+
+ <ul>
+ <li>
+ 0
+ goodbye cruel world
+ </li>
+ <li>
+ 1
+ goodbye cruel world
+ </li>
+ </ul>
+
+ <p>define-slot</p>
+
+ </body>
+
+</html>
diff --git a/src/zope/tal/tests/output/test05.html b/src/zope/tal/tests/output/test05.html
new file mode 100644
index 0000000..006851a
--- /dev/null
+++ b/src/zope/tal/tests/output/test05.html
@@ -0,0 +1,9 @@
+<html>
+
+ <body>
+
+ <h1>This is the body of test5</h1>
+
+ </body>
+
+</html>
diff --git a/src/zope/tal/tests/output/test05.xml b/src/zope/tal/tests/output/test05.xml
new file mode 100644
index 0000000..0bc2691
--- /dev/null
+++ b/src/zope/tal/tests/output/test05.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" ?>
+<html>
+
+ <body>
+
+ <h1>This is the body of test5</h1>
+
+ </body>
+
+</html>
diff --git a/src/zope/tal/tests/output/test06.html b/src/zope/tal/tests/output/test06.html
new file mode 100644
index 0000000..d3f58d9
--- /dev/null
+++ b/src/zope/tal/tests/output/test06.html
@@ -0,0 +1,7 @@
+<html>
+ <body>
+
+ <h1>This is the body of test5</h1>
+
+ </body>
+</html>
diff --git a/src/zope/tal/tests/output/test06.xml b/src/zope/tal/tests/output/test06.xml
new file mode 100644
index 0000000..b9ad4ac
--- /dev/null
+++ b/src/zope/tal/tests/output/test06.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" ?>
+<html>
+ <body>
+
+ <h1>This is the body of test5</h1>
+
+ </body>
+</html>
diff --git a/src/zope/tal/tests/output/test07.html b/src/zope/tal/tests/output/test07.html
new file mode 100644
index 0000000..e0b3d88
--- /dev/null
+++ b/src/zope/tal/tests/output/test07.html
@@ -0,0 +1,11 @@
+<table>
+<!-- macro definition with slots -->
+ <tr>
+ <td>Top Left</td>
+ <td>Top Right</td>
+ </tr>
+ <tr>
+ <td>Bottom left</td>
+ <td><span>Bottom Right</span></td>
+ </tr>
+</table>
diff --git a/src/zope/tal/tests/output/test07.xml b/src/zope/tal/tests/output/test07.xml
new file mode 100644
index 0000000..8884d97
--- /dev/null
+++ b/src/zope/tal/tests/output/test07.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" ?>
+<table>
+<!-- macro definition with slots -->
+ <tr>
+ <td>Top Left</td>
+ <td>Top Right</td>
+ </tr>
+ <tr>
+ <td>Bottom left</td>
+ <td><span>Bottom Right</span></td>
+ </tr>
+</table>
diff --git a/src/zope/tal/tests/output/test08.html b/src/zope/tal/tests/output/test08.html
new file mode 100644
index 0000000..06e01b2
--- /dev/null
+++ b/src/zope/tal/tests/output/test08.html
@@ -0,0 +1,47 @@
+<table>
+<!-- macro definition with slots -->
+ <tr>
+ <td>Top Left</td>
+ <td>Top Right</td>
+ </tr>
+ <tr>
+ <td>Bottom left</td>
+ <td><span>
+ <h1>Some headline</h1>
+ <p>This is the real contents of the bottom right slot.</p>
+ <p>It is supposed to contain a lot of text. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb.</p>
+ <p>It is supposed to contain a lot of text. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb.</p>
+ <p>It is supposed to contain a lot of text. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb.</p>
+ </span></td>
+ </tr>
+</table>
diff --git a/src/zope/tal/tests/output/test08.xml b/src/zope/tal/tests/output/test08.xml
new file mode 100644
index 0000000..51a969c
--- /dev/null
+++ b/src/zope/tal/tests/output/test08.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" ?>
+<table>
+<!-- macro definition with slots -->
+ <tr>
+ <td>Top Left</td>
+ <td>Top Right</td>
+ </tr>
+ <tr>
+ <td>Bottom left</td>
+ <td><span>
+ <h1>Some headline</h1>
+ <p>This is the real contents of the bottom right slot.</p>
+ <p>It is supposed to contain a lot of text. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb.</p>
+ <p>It is supposed to contain a lot of text. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb.</p>
+ <p>It is supposed to contain a lot of text. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb.</p>
+ </span></td>
+ </tr>
+</table>
diff --git a/src/zope/tal/tests/output/test09.html b/src/zope/tal/tests/output/test09.html
new file mode 100644
index 0000000..844c1a9
--- /dev/null
+++ b/src/zope/tal/tests/output/test09.html
@@ -0,0 +1,30 @@
+<html>
+<body>
+<p>
+ Just a bunch of text.</p>
+<p>more text...</p>
+<ul>
+ <li>first item</li>
+ <li>second item
+
+ <ol>
+ <li>second list, first item</li>
+ <li>second list, second item
+ <dl compact>
+ <dt>term 1</dt>
+ <dt>term 2</dt>
+ <dd>definition</dd>
+ </dl></li>
+ </ol></li>
+
+ <li>Now let's have a paragraph...
+ <p>My Paragraph</p>
+ </li>
+
+ <li>And a table in a list item:
+ <table>
+ </table></li>
+</ul>
+
+</body>
+</html>
diff --git a/src/zope/tal/tests/output/test09.xml b/src/zope/tal/tests/output/test09.xml
new file mode 100644
index 0000000..c3d10d7
--- /dev/null
+++ b/src/zope/tal/tests/output/test09.xml
@@ -0,0 +1,30 @@
+<html>
+<body>
+<p>
+ Just a bunch of text.</p>
+<p>more text...</p>
+<ul>
+ <li>first item</li>
+ <li>second item
+
+ <ol>
+ <li>second list, first item</li>
+ <li>second list, second item
+ <dl compact="">
+ <dt>term 1</dt>
+ <dt>term 2</dt>
+ <dd>definition</dd>
+ </dl></li>
+ </ol></li>
+
+ <li>Now let's have a paragraph...
+ <p>My Paragraph</p>
+ </li>
+
+ <li>And a table in a list item:
+ <table>
+ </table></li>
+</ul>
+
+</body>
+</html>
diff --git a/src/zope/tal/tests/output/test10.html b/src/zope/tal/tests/output/test10.html
new file mode 100644
index 0000000..d9cc7ed
--- /dev/null
+++ b/src/zope/tal/tests/output/test10.html
@@ -0,0 +1,51 @@
+<html><body>
+<table>
+<!-- macro definition with slots -->
+ <tr>
+ <td>Top Left</td>
+ <td>Top Right</td>
+ </tr>
+ <tr>
+ <td>Bottom left</td>
+ <td><span>
+ <h1>Some headline</h1>
+ <p>This is the real contents of the bottom right slot.</p>
+ <hr>
+ <p>It is supposed to contain a lot of text. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb.</p>
+ <p>It is supposed to contain a lot of text. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb.</p>
+ <p>It is supposed to contain a lot of text. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab.
+ Blabber, blabber, blah. Baah, baah, barb.</p>
+ <br><br>
+ </span></td>
+ </tr>
+</table>
+</body></html>
diff --git a/src/zope/tal/tests/output/test11.html b/src/zope/tal/tests/output/test11.html
new file mode 100644
index 0000000..9e2223c
--- /dev/null
+++ b/src/zope/tal/tests/output/test11.html
@@ -0,0 +1,8 @@
+<html>
+ <a href="http://www.python.org">bar</a>
+ <p>bad boy!</p>
+ <p>x undefined</p>
+ x undefined
+ x undefined
+ <hr />
+</html>
diff --git a/src/zope/tal/tests/output/test11.xml b/src/zope/tal/tests/output/test11.xml
new file mode 100644
index 0000000..caba039
--- /dev/null
+++ b/src/zope/tal/tests/output/test11.xml
@@ -0,0 +1,5 @@
+<html>
+ <a href="http://www.python.org">bar</a>
+ <p>bad boy!</p>
+ <p>x undefined</p>
+</html>
diff --git a/src/zope/tal/tests/output/test12.html b/src/zope/tal/tests/output/test12.html
new file mode 100644
index 0000000..9533b42
--- /dev/null
+++ b/src/zope/tal/tests/output/test12.html
@@ -0,0 +1,24 @@
+<span />
+
+<img ismap>
+<img ismap="ismap">
+<img ismap="ismap">
+<img ismap="foo">
+
+<img ismap="ismap">
+<img>
+<img>
+
+<img ismap="ismap">
+<img>
+<img>
+
+<img ismap="ismap">
+<img>
+<img>
+
+<span />
+
+<img src="foo">
+<img src="x.gif">
+<img>
diff --git a/src/zope/tal/tests/output/test13.html b/src/zope/tal/tests/output/test13.html
new file mode 100644
index 0000000..d68e0ce
--- /dev/null
+++ b/src/zope/tal/tests/output/test13.html
@@ -0,0 +1,7 @@
+Here's a stray greater than: >
+
+<script>
+ <!-- no comment -->
+ <notag>
+ &noentity;
+</script>
diff --git a/src/zope/tal/tests/output/test14.html b/src/zope/tal/tests/output/test14.html
new file mode 100644
index 0000000..b9bf468
--- /dev/null
+++ b/src/zope/tal/tests/output/test14.html
@@ -0,0 +1,13 @@
+<table>
+ <tr>
+ <td>car</td>
+ <td>bike</td>
+ <td>broomstick</td>
+ </tr>
+</table>
+
+<p>
+ Harry
+ Ron
+ Hermione
+</p>
diff --git a/src/zope/tal/tests/output/test14.xml b/src/zope/tal/tests/output/test14.xml
new file mode 100644
index 0000000..67c0c37
--- /dev/null
+++ b/src/zope/tal/tests/output/test14.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" ?>
+<html>
+
+<table>
+ <tr>
+ <td>car</td>
+ <td>bike</td>
+ <td>broomstick</td>
+ </tr>
+</table>
+
+<p>
+ Harry
+ Ron
+ Hermione
+</p>
+
+</html>
diff --git a/src/zope/tal/tests/output/test15.html b/src/zope/tal/tests/output/test15.html
new file mode 100644
index 0000000..314fd43
--- /dev/null
+++ b/src/zope/tal/tests/output/test15.html
@@ -0,0 +1,29 @@
+<span>
+ <span>INNERSLOT</span>
+</span>
+
+<span>
+ <xxx>inner-argument</xxx>
+</span>
+
+<div>
+<span>
+ <xxx>
+ OUTERSLOT
+ </xxx>
+</span>
+</div>
+
+<div>
+<span>
+ <div>outer-argument</div>
+</span>
+</div>
+
+<div>
+<span>
+ <xxx>
+ OUTERSLOT
+ </xxx>
+</span>
+</div>
diff --git a/src/zope/tal/tests/output/test16.html b/src/zope/tal/tests/output/test16.html
new file mode 100644
index 0000000..d3ea228
--- /dev/null
+++ b/src/zope/tal/tests/output/test16.html
@@ -0,0 +1 @@
+<a href="/base/valid/link.html">blah, blah</a>
diff --git a/src/zope/tal/tests/output/test16.xml b/src/zope/tal/tests/output/test16.xml
new file mode 100644
index 0000000..77e9069
--- /dev/null
+++ b/src/zope/tal/tests/output/test16.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0"?>
+<body>
+
+<ImG href="foo" Alt="bar" alT="baz" Href="about:foo"/>
+
+</body>
diff --git a/src/zope/tal/tests/output/test17.html b/src/zope/tal/tests/output/test17.html
new file mode 100644
index 0000000..e50997d
--- /dev/null
+++ b/src/zope/tal/tests/output/test17.html
@@ -0,0 +1,6 @@
+Yes
+Yes
+Yes
+
+Yes
+Yes
diff --git a/src/zope/tal/tests/output/test17.xml b/src/zope/tal/tests/output/test17.xml
new file mode 100644
index 0000000..7a54cdb
--- /dev/null
+++ b/src/zope/tal/tests/output/test17.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0"?>
+<body>
+Yes
+Yes
+Yes
+
+Yes
+Yes
+</body>
diff --git a/src/zope/tal/tests/output/test18.html b/src/zope/tal/tests/output/test18.html
new file mode 100644
index 0000000..f49e29e
--- /dev/null
+++ b/src/zope/tal/tests/output/test18.html
@@ -0,0 +1,16 @@
+Content
+
+
+
+Content
+
+
+
+<p>Content</p>
+<p></p>
+<img>
+
+Yes
+Yes
+Yes
+Yes
diff --git a/src/zope/tal/tests/output/test18.xml b/src/zope/tal/tests/output/test18.xml
new file mode 100644
index 0000000..77eba02
--- /dev/null
+++ b/src/zope/tal/tests/output/test18.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0"?>
+<body>
+Content
+
+
+
+Content
+
+
+
+<p>Content</p>
+<p/>
+<img/>
+
+Yes
+Yes
+Yes
+Yes
+</body>
diff --git a/src/zope/tal/tests/output/test19.html b/src/zope/tal/tests/output/test19.html
new file mode 100644
index 0000000..2341a4a
--- /dev/null
+++ b/src/zope/tal/tests/output/test19.html
@@ -0,0 +1,3 @@
+<span>REPLACE THIS</span>
+<span>MSGID</span>
+<span>AND ANOTHER TRANSLATED STRING</span>
diff --git a/src/zope/tal/tests/output/test19.xml b/src/zope/tal/tests/output/test19.xml
new file mode 100644
index 0000000..4460acd
--- /dev/null
+++ b/src/zope/tal/tests/output/test19.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0"?>
+<body>
+<span>REPLACE THIS</span>
+<span>MSGID</span>
+<span>AND ANOTHER TRANSLATED STRING</span>
+</body>
diff --git a/src/zope/tal/tests/output/test20.html b/src/zope/tal/tests/output/test20.html
new file mode 100644
index 0000000..606b989
--- /dev/null
+++ b/src/zope/tal/tests/output/test20.html
@@ -0,0 +1 @@
+<span>REPLACEABLE HERE</span>
diff --git a/src/zope/tal/tests/output/test20.xml b/src/zope/tal/tests/output/test20.xml
new file mode 100644
index 0000000..ed1f9fe
--- /dev/null
+++ b/src/zope/tal/tests/output/test20.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0"?>
+<body>
+<span>REPLACEABLE HERE</span>
+</body>
diff --git a/src/zope/tal/tests/output/test21.html b/src/zope/tal/tests/output/test21.html
new file mode 100644
index 0000000..95b3b08
--- /dev/null
+++ b/src/zope/tal/tests/output/test21.html
@@ -0,0 +1 @@
+<span>Lomax WAS BORN IN Antarctica.</span>
diff --git a/src/zope/tal/tests/output/test21.xml b/src/zope/tal/tests/output/test21.xml
new file mode 100644
index 0000000..c373d52
--- /dev/null
+++ b/src/zope/tal/tests/output/test21.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0"?>
+<body>
+<span>Lomax WAS BORN IN Antarctica.</span>
+</body>
diff --git a/src/zope/tal/tests/output/test22.html b/src/zope/tal/tests/output/test22.html
new file mode 100644
index 0000000..6c1b6de
--- /dev/null
+++ b/src/zope/tal/tests/output/test22.html
@@ -0,0 +1 @@
+<span><b>Jim</b> WAS BORN IN the USA.</span>
diff --git a/src/zope/tal/tests/output/test22.xml b/src/zope/tal/tests/output/test22.xml
new file mode 100644
index 0000000..c2e79c5
--- /dev/null
+++ b/src/zope/tal/tests/output/test22.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0"?>
+<body>
+ <span>content</span>
+ omit
+ replace
+</body>
diff --git a/src/zope/tal/tests/output/test23.html b/src/zope/tal/tests/output/test23.html
new file mode 100644
index 0000000..0ea1654
--- /dev/null
+++ b/src/zope/tal/tests/output/test23.html
@@ -0,0 +1 @@
+<span>59 minutes after 6 PM</span>
diff --git a/src/zope/tal/tests/output/test24.html b/src/zope/tal/tests/output/test24.html
new file mode 100644
index 0000000..8dbfba3
--- /dev/null
+++ b/src/zope/tal/tests/output/test24.html
@@ -0,0 +1,7 @@
+<input name="DELETE_BUTTON">
+
+<input name="MESSAGE-ID">
+
+<input name="MESSAGE-ID" attr="INPUT-ATTR">
+
+<input name="MESSAGE-ID" attr="INPUT-ATTR">
diff --git a/src/zope/tal/tests/output/test25.html b/src/zope/tal/tests/output/test25.html
new file mode 100644
index 0000000..6b80bd3
--- /dev/null
+++ b/src/zope/tal/tests/output/test25.html
@@ -0,0 +1 @@
+<input name="DELETE">
diff --git a/src/zope/tal/tests/output/test26.html b/src/zope/tal/tests/output/test26.html
new file mode 100644
index 0000000..9d179a6
--- /dev/null
+++ b/src/zope/tal/tests/output/test26.html
@@ -0,0 +1 @@
+<span>7 is the JOB NUMBER</span>
diff --git a/src/zope/tal/tests/output/test27.html b/src/zope/tal/tests/output/test27.html
new file mode 100644
index 0000000..96229e4
--- /dev/null
+++ b/src/zope/tal/tests/output/test27.html
@@ -0,0 +1 @@
+<p>Your contact email address is recorded as <a href="mailto:user@example.com">aperson@dom.ain</a></p>
diff --git a/src/zope/tal/tests/output/test28.html b/src/zope/tal/tests/output/test28.html
new file mode 100644
index 0000000..96229e4
--- /dev/null
+++ b/src/zope/tal/tests/output/test28.html
@@ -0,0 +1 @@
+<p>Your contact email address is recorded as <a href="mailto:user@example.com">aperson@dom.ain</a></p>
diff --git a/src/zope/tal/tests/output/test29.html b/src/zope/tal/tests/output/test29.html
new file mode 100644
index 0000000..886137e
--- /dev/null
+++ b/src/zope/tal/tests/output/test29.html
@@ -0,0 +1 @@
+<div>AT THE TONE THE TIME WILL BE <span>59 minutes after 6 PM</span>... BEEP!</div>
diff --git a/src/zope/tal/tests/output/test30.html b/src/zope/tal/tests/output/test30.html
new file mode 100644
index 0000000..964b772
--- /dev/null
+++ b/src/zope/tal/tests/output/test30.html
@@ -0,0 +1 @@
+<p>Your contact email address is recorded as <a href="mailto:${request/submitter}">aperson@dom.ain</a></p>
diff --git a/src/zope/tal/tests/output/test31.html b/src/zope/tal/tests/output/test31.html
new file mode 100644
index 0000000..964b772
--- /dev/null
+++ b/src/zope/tal/tests/output/test31.html
@@ -0,0 +1 @@
+<p>Your contact email address is recorded as <a href="mailto:${request/submitter}">aperson@dom.ain</a></p>
diff --git a/src/zope/tal/tests/output/test32.html b/src/zope/tal/tests/output/test32.html
new file mode 100644
index 0000000..f39bd97
--- /dev/null
+++ b/src/zope/tal/tests/output/test32.html
@@ -0,0 +1 @@
+<span><span>Lomax</span> was born in <span>Antarctica</span></span>
diff --git a/src/zope/tal/tests/output/test33.html b/src/zope/tal/tests/output/test33.html
new file mode 100644
index 0000000..4472f21
--- /dev/null
+++ b/src/zope/tal/tests/output/test33.html
@@ -0,0 +1 @@
+<span>don't translate me</span>
diff --git a/src/zope/tal/tests/output/test34.html b/src/zope/tal/tests/output/test34.html
new file mode 100644
index 0000000..1d7b5f2
--- /dev/null
+++ b/src/zope/tal/tests/output/test34.html
@@ -0,0 +1,7 @@
+<span>
+ stuff
+ foobar
+ more stuff
+</span>
+
+<span>STUFF foobar MORE STUFF</span>
diff --git a/src/zope/tal/tests/output/test35.html b/src/zope/tal/tests/output/test35.html
new file mode 100644
index 0000000..b1a9d2e
--- /dev/null
+++ b/src/zope/tal/tests/output/test35.html
@@ -0,0 +1,6 @@
+
+
+
+
+
+ <h1>page</h1>
diff --git a/src/zope/tal/tests/output/test36.html b/src/zope/tal/tests/output/test36.html
new file mode 100644
index 0000000..2a563c1
--- /dev/null
+++ b/src/zope/tal/tests/output/test36.html
@@ -0,0 +1,2 @@
+&lt;foo&gt;
+<span>&lt;foo&gt; <bar /> <b>some</b> <i>text</i></span>
diff --git a/src/zope/tal/tests/output/test_domain.html b/src/zope/tal/tests/output/test_domain.html
new file mode 100644
index 0000000..6a282ac
--- /dev/null
+++ b/src/zope/tal/tests/output/test_domain.html
@@ -0,0 +1,5 @@
+<div>
+<span>replace this</span>
+<span>msgid</span>
+<span>and another translated string</span>
+</div>
diff --git a/src/zope/tal/tests/output/test_failed_attr_translation.html b/src/zope/tal/tests/output/test_failed_attr_translation.html
new file mode 100644
index 0000000..cd34b1f
--- /dev/null
+++ b/src/zope/tal/tests/output/test_failed_attr_translation.html
@@ -0,0 +1 @@
+<input value="don't translate me">
diff --git a/src/zope/tal/tests/output/test_metal1.html b/src/zope/tal/tests/output/test_metal1.html
new file mode 100644
index 0000000..c8cc346
--- /dev/null
+++ b/src/zope/tal/tests/output/test_metal1.html
@@ -0,0 +1,79 @@
+<span metal:define-macro="OUTER">
+ AAA
+ <span metal:define-macro="INNER">INNER</span>
+ BBB
+</span>
+
+<span metal:use-macro="OUTER">
+ AAA
+ <span>INNER</span>
+ BBB
+</span>
+
+<span metal:use-macro="INNER">INNER</span>
+
+<span metal:define-macro="OUTER2">
+ AAA
+ <xxx metal:define-slot="OUTERSLOT">
+ <span metal:define-macro="INNER2">INNER</span>
+ </xxx>
+ BBB
+</span>
+
+<span metal:use-macro="OUTER2">
+ AAA
+ <xxx>
+ <span>INNER</span>
+ </xxx>
+ BBB
+</span>
+
+<span metal:use-macro="INNER2">INNER</span>
+
+<span metal:use-macro="OUTER2">
+ AAA
+ <yyy metal:fill-slot="OUTERSLOT">OUTERSLOT</yyy>
+ BBB
+</span>
+
+<span metal:define-macro="OUTER3">
+ AAA
+ <xxx metal:define-slot="OUTERSLOT">
+ <span metal:define-macro="INNER3">INNER
+ <xxx metal:define-slot="INNERSLOT">INNERSLOT</xxx>
+ </span>
+ </xxx>
+ BBB
+</span>
+
+<span metal:use-macro="OUTER3">
+ AAA
+ <xxx>
+ <span>INNER
+ <xxx>INNERSLOT</xxx>
+ </span>
+ </xxx>
+ BBB
+</span>
+
+<span metal:use-macro="OUTER3">
+ AAA
+ <yyy metal:fill-slot="OUTERSLOT">OUTERSLOT</yyy>
+ BBB
+</span>
+
+<span metal:use-macro="INNER3">INNER
+ <xxx>INNERSLOT</xxx>
+ </span>
+
+<span metal:use-macro="INNER3">INNER
+ <yyy metal:fill-slot="INNERSLOT">INNERSLOT</yyy>
+ </span>
+
+<span metal:use-macro="INNER3">INNER
+ <yyy metal:fill-slot="INNERSLOT">
+ <zzz metal:define-macro="INSLOT">INSLOT</zzz>
+ </yyy>
+ </span>
+
+<zzz metal:use-macro="INSLOT">INSLOT</zzz>
diff --git a/src/zope/tal/tests/output/test_metal2.html b/src/zope/tal/tests/output/test_metal2.html
new file mode 100644
index 0000000..7e56c0c
--- /dev/null
+++ b/src/zope/tal/tests/output/test_metal2.html
@@ -0,0 +1,11 @@
+<div metal:define-macro="OUTER">
+ OUTER
+ <span metal:define-macro="INNER">INNER</span>
+ OUTER
+</div>
+
+<div metal:use-macro="OUTER">
+ OUTER
+ <span>INNER</span>
+ OUTER
+</div>
diff --git a/src/zope/tal/tests/output/test_metal3.html b/src/zope/tal/tests/output/test_metal3.html
new file mode 100644
index 0000000..b0af907
--- /dev/null
+++ b/src/zope/tal/tests/output/test_metal3.html
@@ -0,0 +1 @@
+<span tal:attributes="class string:foo">Should not get attr in metal</span>
diff --git a/src/zope/tal/tests/output/test_metal4.html b/src/zope/tal/tests/output/test_metal4.html
new file mode 100644
index 0000000..dc774d3
--- /dev/null
+++ b/src/zope/tal/tests/output/test_metal4.html
@@ -0,0 +1,4 @@
+<!-- the outer element *must* be tal:something or metal:something -->
+<metal:block define-macro="page" i18n:domain="zope">
+ <title metal:define-slot="title">Z3 UI</title>
+</metal:block>
diff --git a/src/zope/tal/tests/output/test_metal5.html b/src/zope/tal/tests/output/test_metal5.html
new file mode 100644
index 0000000..8bae3d8
--- /dev/null
+++ b/src/zope/tal/tests/output/test_metal5.html
@@ -0,0 +1,4 @@
+<!-- the outer element *must* include tal:omit-tag='' -->
+<x tal:omit-tag="" metal:define-macro="page" i18n:domain="zope">
+ <title metal:define-slot="title">Z3 UI</title>
+</x>
diff --git a/src/zope/tal/tests/output/test_metal6.html b/src/zope/tal/tests/output/test_metal6.html
new file mode 100644
index 0000000..ce243f2
--- /dev/null
+++ b/src/zope/tal/tests/output/test_metal6.html
@@ -0,0 +1,5 @@
+<metal:block define-macro="page">
+ <html i18:domain="zope">
+ <metal:block define-slot="title">Z3 UI</metal:block>
+ </html>
+</metal:block>
diff --git a/src/zope/tal/tests/output/test_metal7.html b/src/zope/tal/tests/output/test_metal7.html
new file mode 100644
index 0000000..cc449ed
--- /dev/null
+++ b/src/zope/tal/tests/output/test_metal7.html
@@ -0,0 +1,6 @@
+<html metal:define-macro="page" i18n:domain="zope">
+ <x metal:define-slot="title" />
+</html>
+<html metal:use-macro="page" i18n:domain="zope">
+ <x metal:fill-slot="title" />
+</html>
diff --git a/src/zope/tal/tests/output/test_metal8.html b/src/zope/tal/tests/output/test_metal8.html
new file mode 100644
index 0000000..d56adab
--- /dev/null
+++ b/src/zope/tal/tests/output/test_metal8.html
@@ -0,0 +1,19 @@
+<html metal:define-macro="page" i18n:domain="zope">
+<body>
+<div metal:define-macro="workspace">
+<div metal:define-slot="body">
+Default body
+</div>
+</div>
+</body>
+</html>
+
+<html metal:use-macro="page" i18n:domain="zope">
+<body>
+<div>
+<div metal:fill-slot="body">
+Filled-in body
+</div>
+</div>
+</body>
+</html>
diff --git a/src/zope/tal/tests/output/test_metal9.html b/src/zope/tal/tests/output/test_metal9.html
new file mode 100644
index 0000000..4cbc637
--- /dev/null
+++ b/src/zope/tal/tests/output/test_metal9.html
@@ -0,0 +1,32 @@
+<div metal:define-macro="macro1" i18n:domain="zope">
+<span metal:define-slot="slot1">
+Default for macro1
+</span>
+</div>
+
+<div metal:define-macro="macro2" metal:use-macro="macro1" i18n:domain="zope">
+<span metal:fill-slot="slot1">
+Macro 2's slot 1 decoration
+<span metal:define-slot="slot1">
+Default for macro2
+</span>
+</span>
+</div>
+
+<div metal:use-macro="macro2" i18n:domain="zope">
+<span metal:fill-slot="slot1">
+Macro 2's slot 1 decoration
+<span>
+Default for macro2
+</span>
+</span>
+</div>
+
+<div metal:use-macro="macro2" i18n:domain="zope">
+<span metal:fill-slot="slot1">
+Macro 2's slot 1 decoration
+<span metal:fill-slot="slot1">
+Custom slot1
+</span>
+</span>
+</div>
diff --git a/src/zope/tal/tests/output/test_sa1.html b/src/zope/tal/tests/output/test_sa1.html
new file mode 100644
index 0000000..a37b9e9
--- /dev/null
+++ b/src/zope/tal/tests/output/test_sa1.html
@@ -0,0 +1,10 @@
+<!--
+==============================================================================
+tests/input/test_sa1.html
+==============================================================================
+--><html>
+<title>Simple test of source annotations</title>
+<body>
+<p>Foo!</p>
+</body>
+</html>
diff --git a/src/zope/tal/tests/output/test_sa1.xml b/src/zope/tal/tests/output/test_sa1.xml
new file mode 100644
index 0000000..8e1f4cc
--- /dev/null
+++ b/src/zope/tal/tests/output/test_sa1.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" ?><!--
+==============================================================================
+tests/input/test_sa1.xml
+==============================================================================
+-->
+<html>
+<title>Simple test of source annotations</title>
+<body>
+<p>Foo!</p>
+</body>
+</html>
diff --git a/src/zope/tal/tests/output/test_sa2.html b/src/zope/tal/tests/output/test_sa2.html
new file mode 100644
index 0000000..4709b49
--- /dev/null
+++ b/src/zope/tal/tests/output/test_sa2.html
@@ -0,0 +1,13 @@
+<!--
+==============================================================================
+tests/input/test_sa2.html
+==============================================================================
+--><!DOCTYPE html
+ PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "DTD/xhtml1-transitional.dtd">
+<html>
+<title>Simple test of source annotations</title>
+<body>
+<p>Foo!</p>
+</body>
+</html>
diff --git a/src/zope/tal/tests/output/test_sa2.xml b/src/zope/tal/tests/output/test_sa2.xml
new file mode 100644
index 0000000..30b5699
--- /dev/null
+++ b/src/zope/tal/tests/output/test_sa2.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" ?><!--
+==============================================================================
+tests/input/test_sa2.xml
+==============================================================================
+-->
+<!DOCTYPE html
+ PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "DTD/xhtml1-transitional.dtd">
+<html>
+<title>Simple test of source annotations</title>
+<body>
+<p>Foo!</p>
+</body>
+</html>
diff --git a/src/zope/tal/tests/output/test_sa3.html b/src/zope/tal/tests/output/test_sa3.html
new file mode 100644
index 0000000..8431438
--- /dev/null
+++ b/src/zope/tal/tests/output/test_sa3.html
@@ -0,0 +1,42 @@
+<!--
+==============================================================================
+tests/input/test_sa3.html
+==============================================================================
+--><html>
+<body>
+ <!--
+==============================================================================
+tests/input/test_sa3.html (line 3)
+==============================================================================
+--><div>This is macro1 on sa3 line 3.
+ <span>This is slot1 on sa3 line 4.</span><!--
+==============================================================================
+tests/input/test_sa3.html (line 4)
+==============================================================================
+-->
+ This is the end of macro1 on sa3 line 5.
+ </div>
+ <p>Some text on sa3 line 7.</p>
+ <!--
+==============================================================================
+tests/input/test_sa3.html (line 3)
+==============================================================================
+--><div>This is macro1 on sa3 line 3.
+ <!--
+==============================================================================
+tests/input/test_sa3.html (line 10)
+==============================================================================
+--><b>Text from sa3 line 10 is filled into slot1.</b><!--
+==============================================================================
+tests/input/test_sa3.html (line 4)
+==============================================================================
+-->
+ This is the end of macro1 on sa3 line 5.
+ </div><!--
+==============================================================================
+tests/input/test_sa3.html (line 12)
+==============================================================================
+-->
+ <p>This is some text on sa3 line 13.</p>
+</body>
+</html>
diff --git a/src/zope/tal/tests/output/test_sa3.xml b/src/zope/tal/tests/output/test_sa3.xml
new file mode 100644
index 0000000..bd20f83
--- /dev/null
+++ b/src/zope/tal/tests/output/test_sa3.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" ?><!--
+==============================================================================
+tests/input/test_sa3.xml
+==============================================================================
+-->
+<html>
+<body>
+ <!--
+==============================================================================
+tests/input/test_sa3.xml (line 4)
+==============================================================================
+--><div>This is macro1 on sa3 line 4.
+ <span>This is slot1 on sa3 line 5.</span><!--
+==============================================================================
+tests/input/test_sa3.xml (line 5)
+==============================================================================
+-->
+ This is the end of macro1 on sa3 line 6.
+ </div>
+ <p>Some text on sa3 line 8.</p>
+ <!--
+==============================================================================
+tests/input/test_sa3.xml (line 4)
+==============================================================================
+--><div>This is macro1 on sa3 line 4.
+ <!--
+==============================================================================
+tests/input/test_sa3.xml (line 11)
+==============================================================================
+--><b>Text from sa3 line 11 is filled into slot1.</b><!--
+==============================================================================
+tests/input/test_sa3.xml (line 5)
+==============================================================================
+-->
+ This is the end of macro1 on sa3 line 6.
+ </div><!--
+==============================================================================
+tests/input/test_sa3.xml (line 13)
+==============================================================================
+-->
+ <p>This is some text on sa3 line 14.</p>
+</body>
+</html>
diff --git a/src/zope/tal/tests/output/test_sa4.html b/src/zope/tal/tests/output/test_sa4.html
new file mode 100644
index 0000000..4aca908
--- /dev/null
+++ b/src/zope/tal/tests/output/test_sa4.html
@@ -0,0 +1,30 @@
+<!--
+==============================================================================
+tests/input/test_sa4.html
+==============================================================================
+--><html>
+<body>
+ <p>Some text on sa4 line 3.</p>
+ <!--
+==============================================================================
+tests/input/test_sa3.html (line 3)
+==============================================================================
+--><div>This is macro1 on sa3 line 3.
+ <!--
+==============================================================================
+tests/input/test_sa4.html (line 6)
+==============================================================================
+--><b>Text from sa4 line 6 is filled into slot1.</b><!--
+==============================================================================
+tests/input/test_sa3.html (line 4)
+==============================================================================
+-->
+ This is the end of macro1 on sa3 line 5.
+ </div><!--
+==============================================================================
+tests/input/test_sa4.html (line 8)
+==============================================================================
+-->
+ <p>This is some text on sa4 line 9.</p>
+</body>
+</html>
diff --git a/src/zope/tal/tests/run.py b/src/zope/tal/tests/run.py
new file mode 100644
index 0000000..b4dab8c
--- /dev/null
+++ b/src/zope/tal/tests/run.py
@@ -0,0 +1,45 @@
+#! /usr/bin/env python
+##############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Run all tests.
+
+$Id$
+"""
+import sys
+import unittest
+
+from zope.tal.tests import utils
+from zope.tal.tests import test_htmltalparser
+from zope.tal.tests import test_talinterpreter
+from zope.tal.tests import test_files
+from zope.tal.tests import test_sourcepos
+
+# TODO this code isn't picked up by the Zope 3 test framework..
+def test_suite():
+ suite = unittest.TestSuite()
+ suite.addTest(test_htmltalparser.test_suite())
+ if not utils.skipxml:
+ import test_xmlparser
+ suite.addTest(test_xmlparser.test_suite())
+ suite.addTest(test_talinterpreter.test_suite())
+ suite.addTest(test_files.test_suite())
+ suite.addTest(test_sourcepos.test_suite())
+ return suite
+
+def main():
+ return utils.run_suite(test_suite())
+
+if __name__ == "__main__":
+ errs = main()
+ sys.exit(errs and 1 or 0)
diff --git a/src/zope/tal/tests/test_files.py b/src/zope/tal/tests/test_files.py
new file mode 100644
index 0000000..e26f00a
--- /dev/null
+++ b/src/zope/tal/tests/test_files.py
@@ -0,0 +1,90 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Tests that run driver.py over input files comparing to output files.
+
+$Id$
+"""
+
+import glob
+import os
+import sys
+import unittest
+
+import zope.tal.runtest
+
+from zope.tal.tests import utils
+
+
+class FileTestCase(unittest.TestCase):
+
+ def __init__(self, file, dir):
+ self.__file = file
+ self.__dir = dir
+ unittest.TestCase.__init__(self)
+
+ # For unittest.
+ def shortDescription(self):
+ path = os.path.basename(self.__file)
+ return '%s (%s)' % (path, self.__class__)
+
+ def runTest(self):
+ basename = os.path.basename(self.__file)
+ #sys.stdout.write(basename + " ")
+ sys.stdout.flush()
+ if basename.startswith('test_sa'):
+ sys.argv = ["", "-Q", "-a", self.__file]
+ elif basename.startswith('test_metal'):
+ sys.argv = ["", "-Q", "-m", self.__file]
+ else:
+ sys.argv = ["", "-Q", self.__file]
+ pwd = os.getcwd()
+ try:
+ try:
+ os.chdir(self.__dir)
+ zope.tal.runtest.main()
+ finally:
+ os.chdir(pwd)
+ except SystemExit, what:
+ if what.code:
+ self.fail("output for %s didn't match" % self.__file)
+
+try:
+ script = __file__
+except NameError:
+ script = sys.argv[0]
+
+def test_suite():
+ suite = unittest.TestSuite()
+ dir = os.path.dirname(script)
+ dir = os.path.abspath(dir)
+ parentdir = os.path.dirname(dir)
+ prefix = os.path.join(dir, "input", "test*.")
+ if utils.skipxml:
+ xmlargs = []
+ else:
+ xmlargs = glob.glob(prefix + "xml")
+ xmlargs.sort()
+ htmlargs = glob.glob(prefix + "html")
+ htmlargs.sort()
+ args = xmlargs + htmlargs
+ if not args:
+ sys.stderr.write("Warning: no test input files found!!!\n")
+ for arg in args:
+ case = FileTestCase(arg, parentdir)
+ suite.addTest(case)
+ return suite
+
+if __name__ == "__main__":
+ errs = utils.run_suite(test_suite())
+ sys.exit(errs and 1 or 0)
diff --git a/src/zope/tal/tests/test_htmltalparser.py b/src/zope/tal/tests/test_htmltalparser.py
new file mode 100644
index 0000000..eb53f51
--- /dev/null
+++ b/src/zope/tal/tests/test_htmltalparser.py
@@ -0,0 +1,1021 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Tests for the HTMLTALParser code generator.
+
+$Id$
+"""
+import pprint
+import sys
+import unittest
+
+from zope.tal import htmltalparser, taldefs
+from zope.tal.tests import utils
+
+
+class TestCaseBase(unittest.TestCase):
+
+ prologue = ""
+ epilogue = ""
+ initial_program = [('version', taldefs.TAL_VERSION), ('mode', 'html')]
+ final_program = []
+
+ def _merge(self, p1, p2):
+ if p1 and p2:
+ op1, args1 = p1[-1]
+ op2, args2 = p2[0]
+ if op1.startswith('rawtext') and op2.startswith('rawtext'):
+ return (p1[:-1]
+ + [rawtext(args1[0] + args2[0])]
+ + p2[1:])
+ return p1+p2
+
+ def _run_check(self, source, program, macros={}):
+ parser = htmltalparser.HTMLTALParser()
+ parser.parseString(self.prologue + source + self.epilogue)
+ got_program, got_macros = parser.getCode()
+ program = self._merge(self.initial_program, program)
+ program = self._merge(program, self.final_program)
+ self.assert_(got_program == program,
+ "Program:\n" + pprint.pformat(got_program)
+ + "\nExpected:\n" + pprint.pformat(program))
+ self.assert_(got_macros == macros,
+ "Macros:\n" + pprint.pformat(got_macros)
+ + "\nExpected:\n" + pprint.pformat(macros))
+
+ def _should_error(self, source, exc=taldefs.TALError):
+ def parse(self=self, source=source):
+ parser = htmltalparser.HTMLTALParser()
+ parser.parseString(self.prologue + source + self.epilogue)
+ self.assertRaises(exc, parse)
+
+
+def rawtext(s):
+ """Compile raw text to the appropriate instruction."""
+ if "\n" in s:
+ return ("rawtextColumn", (s, len(s) - (s.rfind("\n") + 1)))
+ else:
+ return ("rawtextOffset", (s, len(s)))
+
+
+class HTMLTALParserTestCases(TestCaseBase):
+
+ def test_code_simple_identity(self):
+ self._run_check("""<html a='b' b="c" c=d><title>My Title</html>""", [
+ rawtext('<html a="b" b="c" c="d">'
+ '<title>My Title</title></html>'),
+ ])
+
+ def test_code_implied_list_closings(self):
+ self._run_check("""<ul><li><p><p><li></ul>""", [
+ rawtext('<ul><li><p></p><p></p></li><li></li></ul>'),
+ ])
+ self._run_check("""<dl><dt><dt><dd><dd><ol><li><li></ol></dl>""", [
+ rawtext('<dl><dt></dt><dt></dt><dd></dd>'
+ '<dd><ol><li></li><li></li></ol></dd></dl>'),
+ ])
+
+ def test_code_implied_table_closings(self):
+ self._run_check("""<p>text <table><tr><th>head\t<tr><td>cell\t"""
+ """<table><tr><td>cell \n \t \n<tr>""", [
+ rawtext('<p>text</p> <table><tr><th>head</th>'
+ '</tr>\t<tr><td>cell\t<table><tr><td>cell</td>'
+ '</tr> \n \t \n<tr></tr></table></td></tr></table>'),
+ ])
+ self._run_check("""<table><tr><td>cell """
+ """<table><tr><td>cell </table></table>""", [
+ rawtext('<table><tr><td>cell <table><tr><td>cell</td></tr>'
+ ' </table></td></tr></table>'),
+ ])
+
+ def test_code_bad_nesting(self):
+ def check(self=self):
+ self._run_check("<a><b></a></b>", [])
+ self.assertRaises(htmltalparser.NestingError, check)
+
+ def test_code_attr_syntax(self):
+ output = [
+ rawtext('<a b="v" c="v" d="v" e></a>'),
+ ]
+ self._run_check("""<a b='v' c="v" d=v e>""", output)
+ self._run_check("""<a b = 'v' c = "v" d = v e>""", output)
+ self._run_check("""<a\nb\n=\n'v'\nc\n=\n"v"\nd\n=\nv\ne>""", output)
+ self._run_check("""<a\tb\t=\t'v'\tc\t=\t"v"\td\t=\tv\te>""", output)
+
+ def test_code_attr_values(self):
+ self._run_check(
+ """<a b='xxx\n\txxx' c="yyy\t\nyyy" d='\txyz\n'>""", [
+ rawtext('<a b="xxx\n\txxx" c="yyy\t\nyyy" d="\txyz\n"></a>')])
+ self._run_check("""<a b='' c="">""", [
+ rawtext('<a b="" c=""></a>'),
+ ])
+
+ def test_code_attr_entity_replacement(self):
+ # we expect entities *not* to be replaced by HTLMParser!
+ self._run_check("""<a b='&amp;&gt;&lt;&quot;&apos;'>""", [
+ rawtext('<a b="&amp;&gt;&lt;&quot;\'"></a>'),
+ ])
+ self._run_check("""<a b='\"'>""", [
+ rawtext('<a b="&quot;"></a>'),
+ ])
+ self._run_check("""<a b='&'>""", [
+ rawtext('<a b="&amp;"></a>'),
+ ])
+ self._run_check("""<a b='<'>""", [
+ rawtext('<a b="&lt;"></a>'),
+ ])
+
+ def test_code_attr_funky_names(self):
+ self._run_check("""<a a.b='v' c:d=v e-f=v>""", [
+ rawtext('<a a.b="v" c:d="v" e-f="v"></a>'),
+ ])
+
+ def test_code_pcdata_entityref(self):
+ self._run_check("""&nbsp;""", [
+ rawtext('&nbsp;'),
+ ])
+
+ def test_code_short_endtags(self):
+ self._run_check("""<html><img/></html>""", [
+ rawtext('<html><img /></html>'),
+ ])
+
+
+class METALGeneratorTestCases(TestCaseBase):
+
+ def test_null(self):
+ self._run_check("", [])
+
+ def test_define_macro(self):
+ macro = self.initial_program + [
+ ('startTag', ('p', [('metal:define-macro', 'M', 'metal')])),
+ rawtext('booh</p>'),
+ ]
+ program = [
+ ('setPosition', (1, 0)),
+ ('defineMacro', ('M', macro)),
+ ]
+ macros = {'M': macro}
+ self._run_check('<p metal:define-macro="M">booh</p>', program, macros)
+
+ def test_use_macro(self):
+ self._run_check('<p metal:use-macro="M">booh</p>', [
+ ('setPosition', (1, 0)),
+ ('useMacro',
+ ('M', '$M$', {},
+ [('startTag', ('p', [('metal:use-macro', 'M', 'metal')])),
+ rawtext('booh</p>')])),
+ ])
+
+ def test_define_slot(self):
+ macro = self.initial_program + [
+ ('startTag', ('p', [('metal:define-macro', 'M', 'metal')])),
+ rawtext('foo'),
+ ('setPosition', (1, 29)),
+ ('defineSlot', ('S',
+ [('startTag', ('span', [('metal:define-slot', 'S', 'metal')])),
+ rawtext('spam</span>')])),
+ rawtext('bar</p>'),
+ ]
+ program = [('setPosition', (1, 0)),
+ ('defineMacro', ('M', macro))]
+ macros = {'M': macro}
+ self._run_check('<p metal:define-macro="M">foo'
+ '<span metal:define-slot="S">spam</span>bar</p>',
+ program, macros)
+
+ def test_fill_slot(self):
+ self._run_check('<p metal:use-macro="M">foo'
+ '<span metal:fill-slot="S">spam</span>bar</p>', [
+ ('setPosition', (1, 0)),
+ ('useMacro',
+ ('M', '$M$',
+ {'S': [('startTag', ('span',
+ [('metal:fill-slot', 'S', 'metal')])),
+ rawtext('spam</span>')]},
+ [('startTag', ('p', [('metal:use-macro', 'M', 'metal')])),
+ rawtext('foo'),
+ ('setPosition', (1, 26)),
+ ('fillSlot', ('S',
+ [('startTag', ('span', [('metal:fill-slot', 'S', 'metal')])),
+ rawtext('spam</span>')])),
+ rawtext('bar</p>')])),
+ ])
+
+
+class TALGeneratorTestCases(TestCaseBase):
+
+ def test_null(self):
+ self._run_check("", [])
+
+ def test_define_1(self):
+ self._run_check("<p tal:define='xyzzy string:spam'></p>", [
+ ('setPosition', (1, 0)),
+ ('beginScope', {'tal:define': 'xyzzy string:spam'}),
+ ('setLocal', ('xyzzy', '$string:spam$')),
+ ('startTag', ('p', [('tal:define', 'xyzzy string:spam', 'tal')])),
+ ('endScope', ()),
+ rawtext('</p>'),
+ ])
+
+ def test_define_2(self):
+ self._run_check("<p tal:define='local xyzzy string:spam'></p>", [
+ ('setPosition', (1, 0)),
+ ('beginScope', {'tal:define': 'local xyzzy string:spam'}),
+ ('setLocal', ('xyzzy', '$string:spam$')),
+ ('startTag', ('p',
+ [('tal:define', 'local xyzzy string:spam', 'tal')])),
+ ('endScope', ()),
+ rawtext('</p>'),
+ ])
+
+ def test_define_3(self):
+ self._run_check("<p tal:define='global xyzzy string:spam'></p>", [
+ ('setPosition', (1, 0)),
+ ('beginScope', {'tal:define': 'global xyzzy string:spam'}),
+ ('setGlobal', ('xyzzy', '$string:spam$')),
+ ('startTag', ('p',
+ [('tal:define', 'global xyzzy string:spam', 'tal')])),
+ ('endScope', ()),
+ rawtext('</p>'),
+ ])
+
+ def test_define_4(self):
+ self._run_check("<p tal:define='x string:spam; y x'></p>", [
+ ('setPosition', (1, 0)),
+ ('beginScope', {'tal:define': 'x string:spam; y x'}),
+ ('setLocal', ('x', '$string:spam$')),
+ ('setLocal', ('y', '$x$')),
+ ('startTag', ('p', [('tal:define', 'x string:spam; y x', 'tal')])),
+ ('endScope', ()),
+ rawtext('</p>'),
+ ])
+
+ def test_define_5(self):
+ self._run_check("<p tal:define='x string:;;;;; y x'></p>", [
+ ('setPosition', (1, 0)),
+ ('beginScope', {'tal:define': 'x string:;;;;; y x'}),
+ ('setLocal', ('x', '$string:;;$')),
+ ('setLocal', ('y', '$x$')),
+ ('startTag', ('p', [('tal:define', 'x string:;;;;; y x', 'tal')])),
+ ('endScope', ()),
+ rawtext('</p>'),
+ ])
+
+ def test_define_6(self):
+ self._run_check(
+ "<p tal:define='x string:spam; global y x; local z y'></p>", [
+ ('setPosition', (1, 0)),
+ ('beginScope',
+ {'tal:define': 'x string:spam; global y x; local z y'}),
+ ('setLocal', ('x', '$string:spam$')),
+ ('setGlobal', ('y', '$x$')),
+ ('setLocal', ('z', '$y$')),
+ ('startTag', ('p',
+ [('tal:define', 'x string:spam; global y x; local z y', 'tal')])),
+ ('endScope', ()),
+ rawtext('</p>'),
+ ])
+
+ def test_condition(self):
+ self._run_check(
+ "<p><span tal:condition='python:1'><b>foo</b></span></p>", [
+ rawtext('<p>'),
+ ('setPosition', (1, 3)),
+ ('beginScope', {'tal:condition': 'python:1'}),
+ ('condition', ('$python:1$',
+ [('startTag', ('span', [('tal:condition', 'python:1', 'tal')])),
+ rawtext('<b>foo</b></span>')])),
+ ('endScope', ()),
+ rawtext('</p>'),
+ ])
+
+ def test_content_1(self):
+ self._run_check("<p tal:content='string:foo'>bar</p>", [
+ ('setPosition', (1, 0)),
+ ('beginScope', {'tal:content': 'string:foo'}),
+ ('startTag', ('p', [('tal:content', 'string:foo', 'tal')])),
+ ('insertText', ('$string:foo$', [rawtext('bar')])),
+ ('endScope', ()),
+ rawtext('</p>'),
+ ])
+
+ def test_content_2(self):
+ self._run_check("<p tal:content='text string:foo'>bar</p>", [
+ ('setPosition', (1, 0)),
+ ('beginScope', {'tal:content': 'text string:foo'}),
+ ('startTag', ('p', [('tal:content', 'text string:foo', 'tal')])),
+ ('insertText', ('$string:foo$', [rawtext('bar')])),
+ ('endScope', ()),
+ rawtext('</p>'),
+ ])
+
+ def test_content_3(self):
+ self._run_check("<p tal:content='structure string:<br>'>bar</p>", [
+ ('setPosition', (1, 0)),
+ ('beginScope', {'tal:content': 'structure string:<br>'}),
+ ('startTag', ('p',
+ [('tal:content', 'structure string:<br>', 'tal')])),
+ ('insertStructure',
+ ('$string:<br>$', {}, [rawtext('bar')])),
+ ('endScope', ()),
+ rawtext('</p>'),
+ ])
+
+ def test_replace_1(self):
+ self._run_check("<p tal:replace='string:foo'>bar</p>", [
+ ('setPosition', (1, 0)),
+ ('beginScope', {'tal:replace': 'string:foo'}),
+ ('optTag',
+ ('p',
+ '',
+ None,
+ 0,
+ [('startTag', ('p', [('tal:replace', 'string:foo', 'tal')]))],
+ [('insertText', ('$string:foo$', [('rawtextOffset', ('bar', 3))]))])),
+ ('endScope', ()),
+ ])
+
+ def test_replace_2(self):
+ self._run_check("<p tal:replace='text string:foo'>bar</p>", [
+ ('setPosition', (1, 0)),
+ ('beginScope', {'tal:replace': 'text string:foo'}),
+ ('optTag',
+ ('p',
+ '',
+ None,
+ 0,
+ [('startTag', ('p', [('tal:replace', 'text string:foo', 'tal')]))],
+ [('insertText', ('$string:foo$', [('rawtextOffset', ('bar', 3))]))])),
+ ('endScope', ()),
+ ])
+
+ def test_replace_3(self):
+ self._run_check("<p tal:replace='structure string:<br>'>bar</p>", [
+ ('setPosition', (1, 0)),
+ ('beginScope', {'tal:replace': 'structure string:<br>'}),
+ ('optTag',
+ ('p',
+ '',
+ None,
+ 0,
+ [('startTag', ('p', [('tal:replace', 'structure string:<br>', 'tal')]))],
+ [('insertStructure',
+ ('$string:<br>$', {}, [('rawtextOffset', ('bar', 3))]))])),
+ ('endScope', ()),
+ ])
+
+ def test_repeat(self):
+ self._run_check("<p tal:repeat='x python:(1,2,3)'>"
+ "<span tal:replace='x'>dummy</span></p>", [
+ ('setPosition', (1, 0)),
+ ('beginScope', {'tal:repeat': 'x python:(1,2,3)'}),
+ ('loop', ('x', '$python:(1,2,3)$',
+ [('startTag', ('p',
+ [('tal:repeat', 'x python:(1,2,3)', 'tal')])),
+ ('setPosition', (1, 33)),
+ ('beginScope', {'tal:replace': 'x'}),
+ ('optTag',
+ ('span',
+ '',
+ None,
+ 0,
+ [('startTag', ('span', [('tal:replace', 'x', 'tal')]))],
+ [('insertText', ('$x$', [('rawtextOffset', ('dummy', 5))]))])),
+ ('endScope', ()),
+ rawtext('</p>')])),
+ ('endScope', ()),
+ ])
+
+ def test_script_1(self):
+ self._run_check('<p tal:script="text/server-python">code</p>', [
+ ('setPosition', (1, 0)),
+ ('beginScope', {'tal:script': 'text/server-python'}),
+ ('startTag', ('p',
+ [('tal:script', 'text/server-python', 'tal')])),
+ ('evaluateCode', ('text/server-python',
+ [('rawtextOffset', ('code', 4))])),
+ ('endScope', ()),
+ rawtext('</p>'),
+ ])
+
+ def test_script_2(self):
+ self._run_check('<tal:block script="text/server-python">'
+ 'code'
+ '</tal:block>', [
+ ('setPosition', (1, 0)),
+ ('beginScope', {'script': 'text/server-python'}),
+ ('optTag',
+ ('tal:block',
+ None,
+ 'tal',
+ 0,
+ [('startTag', ('tal:block',
+ [('script', 'text/server-python', 'tal')]))],
+ [('evaluateCode',
+ ('text/server-python',
+ [('rawtextOffset', ('code', 4))]))])),
+ ('endScope', ())
+ ])
+
+ def test_script_3(self):
+ self._run_check('<script type="text/server-python">code</script>', [
+ ('setPosition', (1, 0)),
+ ('beginScope', {}),
+ ('optTag',
+ ('script',
+ '',
+ None,
+ 0,
+ [('rawtextOffset', ('<script>', 8))],
+ [('evaluateCode',
+ ('text/server-python', [('rawtextOffset', ('code', 4))]))])),
+ ('endScope', ())
+ ])
+
+ def test_script_4(self):
+ self._run_check('<script type="text/javascript">code</script>', [
+ ('rawtextOffset',
+ ('<script type="text/javascript">code</script>', 44))
+ ])
+
+ def test_attributes_1(self):
+ self._run_check("<a href='foo' name='bar' tal:attributes="
+ "'href string:http://www.zope.org; x string:y'>"
+ "link</a>", [
+ ('setPosition', (1, 0)),
+ ('beginScope',
+ {'tal:attributes': 'href string:http://www.zope.org; x string:y',
+ 'name': 'bar', 'href': 'foo'}),
+ ('startTag', ('a',
+ [('href', 'foo', 'replace', '$string:http://www.zope.org$', 0, None),
+ ('name', 'name="bar"'),
+ ('tal:attributes',
+ 'href string:http://www.zope.org; x string:y', 'tal'),
+ ('x', None, 'insert', '$string:y$', 0, None)])),
+ ('endScope', ()),
+ rawtext('link</a>'),
+ ])
+
+ def test_attributes_2(self):
+ self._run_check("<p tal:replace='structure string:<img>' "
+ "tal:attributes='src string:foo.png'>duh</p>", [
+ ('setPosition', (1, 0)),
+ ('beginScope',
+ {'tal:attributes': 'src string:foo.png',
+ 'tal:replace': 'structure string:<img>'}),
+ ('optTag',
+ ('p',
+ '',
+ None,
+ 0,
+ [('startTag',
+ ('p',
+ [('tal:replace', 'structure string:<img>', 'tal'),
+ ('tal:attributes', 'src string:foo.png', 'tal')]))],
+ [('insertStructure',
+ ('$string:<img>$',
+ {'src': ('$string:foo.png$', False, None)},
+ [('rawtextOffset', ('duh', 3))]))])),
+ ('endScope', ())])
+
+ def test_on_error_1(self):
+ self._run_check("<p tal:on-error='string:error' "
+ "tal:content='notHere'>okay</p>", [
+ ('setPosition', (1, 0)),
+ ('beginScope',
+ {'tal:content': 'notHere', 'tal:on-error': 'string:error'}),
+ ('onError',
+ ([('startTag', ('p',
+ [('tal:on-error', 'string:error', 'tal'),
+ ('tal:content', 'notHere', 'tal')])),
+ ('insertText', ('$notHere$', [rawtext('okay')])),
+ rawtext('</p>')],
+ [('startTag', ('p',
+ [('tal:on-error', 'string:error', 'tal'),
+ ('tal:content', 'notHere', 'tal')])),
+ ('insertText', ('$string:error$', [])),
+ rawtext('</p>')])),
+ ('endScope', ()),
+ ])
+
+ def test_on_error_2(self):
+ self._run_check("<p tal:on-error='string:error' "
+ "tal:replace='notHere'>okay</p>", [
+ ('setPosition', (1, 0)),
+ ('beginScope',
+ {'tal:replace': 'notHere', 'tal:on-error': 'string:error'}),
+ ('onError',
+ ([('optTag',
+ ('p',
+ '',
+ None,
+ 0,
+ [('startTag',
+ ('p',
+ [('tal:on-error', 'string:error', 'tal'),
+ ('tal:replace', 'notHere', 'tal')]))],
+ [('insertText', ('$notHere$', [('rawtextOffset', ('okay', 4))]))]))],
+ [('startTag',
+ ('p',
+ [('tal:on-error', 'string:error', 'tal'),
+ ('tal:replace', 'notHere', 'tal')])),
+ ('insertText', ('$string:error$', [])),
+ ('rawtextOffset', ('</p>', 4))])),
+ ('endScope', ()),
+ ])
+
+ def test_dup_attr(self):
+ self._should_error("<img tal:condition='x' tal:condition='x'>")
+ self._should_error("<img metal:define-macro='x' "
+ "metal:define-macro='x'>", taldefs.METALError)
+
+ def test_tal_errors(self):
+ self._should_error("<p tal:define='x' />")
+ self._should_error("<p tal:repeat='x' />")
+ self._should_error("<p tal:foobar='x' />")
+ self._should_error("<p tal:replace='x' tal:content='x' />")
+ self._should_error("<p tal:replace='x'>")
+ for tag in htmltalparser.EMPTY_HTML_TAGS:
+ self._should_error("<%s tal:content='string:foo'>" % tag)
+
+ def test_metal_errors(self):
+ exc = taldefs.METALError
+ self._should_error(2*"<p metal:define-macro='x'>xxx</p>", exc)
+ self._should_error("<html metal:use-macro='x'>" +
+ 2*"<p metal:fill-slot='y' />" + "</html>", exc)
+ self._should_error("<p metal:foobar='x' />", exc)
+ self._should_error("<p metal:define-macro='x'>", exc)
+
+ def test_extend_macro_errors(self):
+ exc = taldefs.METALError
+ # extend-macro requires define-macro:
+ self._should_error("<p metal:extend-macro='x'>xxx</p>", exc)
+ # extend-macro prevents use-macro:
+ self._should_error("<p metal:extend-macro='x'"
+ " metal:use-macro='x'"
+ " metal:define-macro='y'>xxx</p>", exc)
+ # use-macro doesn't co-exist with define-macro:
+ self._should_error("<p metal:use-macro='x'"
+ " metal:define-macro='y'>xxx</p>", exc)
+
+ #
+ # I18N test cases
+ #
+
+ def test_i18n_attributes(self):
+ self._run_check("<img alt='foo' i18n:attributes='alt'>", [
+ ('setPosition', (1, 0)),
+ ('beginScope', {'alt': 'foo', 'i18n:attributes': 'alt'}),
+ ('startTag', ('img',
+ [('alt', 'foo', 'replace', None, 1, None),
+ ('i18n:attributes', 'alt', 'i18n')])),
+ ('endScope', ()),
+ ])
+ self._run_check("<img alt='foo' i18n:attributes='alt foo ; bar'>", [
+ ('setPosition', (1, 0)),
+ ('beginScope', {'alt': 'foo', 'i18n:attributes': 'alt foo ; bar'}),
+ ('startTag', ('img',
+ [('alt', 'foo', 'replace', None, 1, 'foo'),
+ ('i18n:attributes', 'alt foo ; bar', 'i18n'),
+ ('bar', None, 'insert', None, 1, None)])),
+ ('endScope', ()),
+ ])
+
+ def test_i18n_name_bad_name(self):
+ self._should_error("<span i18n:name='not a valid name' />")
+ self._should_error("<span i18n:name='-bad-name' />")
+
+ def test_i18n_attributes_repeated_attr(self):
+ self._should_error("<a i18n:attributes='href; href' />")
+ self._should_error("<a i18n:attributes='href; HREF' />")
+
+ def test_i18n_translate(self):
+ # input/test19.html
+ self._run_check('''\
+<span i18n:translate="">Replace this</span>
+<span i18n:translate="msgid">This is a
+translated string</span>
+<span i18n:translate="">And another
+translated string</span>
+''', [
+ ('setPosition', (1, 0)),
+ ('beginScope', {'i18n:translate': ''}),
+ ('startTag', ('span', [('i18n:translate', '', 'i18n')])),
+ ('insertTranslation', ('', [('rawtextOffset', ('Replace this', 12))])),
+ ('rawtextBeginScope',
+ ('</span>\n', 0, (2, 0), 1, {'i18n:translate': 'msgid'})),
+ ('startTag', ('span', [('i18n:translate', 'msgid', 'i18n')])),
+ ('insertTranslation',
+ ('msgid', [('rawtextColumn', ('This is a\ntranslated string', 17))])),
+ ('rawtextBeginScope', ('</span>\n', 0, (4, 0), 1, {'i18n:translate': ''})),
+ ('startTag', ('span', [('i18n:translate', '', 'i18n')])),
+ ('insertTranslation',
+ ('', [('rawtextColumn', ('And another\ntranslated string', 17))])),
+ ('endScope', ()),
+ ('rawtextColumn', ('</span>\n', 0))])
+
+ def test_i18n_translate_with_nested_tal(self):
+ self._run_check('''\
+<span i18n:translate="">replaceable <p tal:replace="str:here">content</p></span>
+''', [
+ ('setPosition', (1, 0)),
+ ('beginScope', {'i18n:translate': ''}),
+ ('startTag', ('span', [('i18n:translate', '', 'i18n')])),
+ ('insertTranslation',
+ ('',
+ [('rawtextOffset', ('replaceable ', 12)),
+ ('setPosition', (1, 36)),
+ ('beginScope', {'tal:replace': 'str:here'}),
+ ('optTag',
+ ('p',
+ '',
+ None,
+ 0,
+ [('startTag', ('p', [('tal:replace', 'str:here', 'tal')]))],
+ [('insertText',
+ ('$str:here$', [('rawtextOffset', ('content', 7))]))])),
+ ('endScope', ())])),
+ ('endScope', ()),
+ ('rawtextColumn', ('</span>\n', 0))
+ ])
+
+ def test_i18n_name(self):
+ # input/test21.html
+ self._run_check('''\
+<span i18n:translate="">
+ <span tal:replace="str:Lomax" i18n:name="name" /> was born in
+ <span tal:replace="str:Antarctica" i18n:name="country" />.
+</span>
+''', [
+ ('setPosition', (1, 0)),
+ ('beginScope', {'i18n:translate': ''}),
+ ('startTag', ('span', [('i18n:translate', '', 'i18n')])),
+ ('insertTranslation',
+ ('',
+ [('rawtextBeginScope',
+ ('\n ',
+ 2,
+ (2, 2),
+ 0,
+ {'i18n:name': 'name', 'tal:replace': 'str:Lomax'})),
+ ('i18nVariable',
+ ('name',
+ [('optTag',
+ ('span',
+ '',
+ None,
+ 1,
+ [('startEndTag',
+ ('span',
+ [('tal:replace', 'str:Lomax', 'tal'),
+ ('i18n:name', 'name', 'i18n')]))],
+ [('insertText', ('$str:Lomax$', []))]))],
+ None,
+ 0)),
+ ('rawtextBeginScope',
+ (' was born in\n ',
+ 2,
+ (3, 2),
+ 1,
+ {'i18n:name': 'country', 'tal:replace': 'str:Antarctica'})),
+ ('i18nVariable',
+ ('country',
+ [('optTag',
+ ('span',
+ '',
+ None,
+ 1,
+ [('startEndTag',
+ ('span',
+ [('tal:replace', 'str:Antarctica', 'tal'),
+ ('i18n:name', 'country', 'i18n')]))],
+ [('insertText', ('$str:Antarctica$', []))]))],
+ None,
+ 0)),
+ ('endScope', ()),
+ ('rawtextColumn', ('.\n', 0))])),
+ ('endScope', ()),
+ ('rawtextColumn', ('</span>\n', 0))
+ ])
+
+ def test_i18n_name_with_content(self):
+ self._run_check('<div i18n:translate="">This is text for '
+ '<span i18n:translate="" tal:content="bar" i18n:name="bar_name"/>.'
+ '</div>', [
+('setPosition', (1, 0)),
+('beginScope', {'i18n:translate': ''}),
+('startTag', ('div', [('i18n:translate', '', 'i18n')])),
+('insertTranslation',
+ ('',
+ [('rawtextOffset', ('This is text for ', 17)),
+ ('setPosition', (1, 40)),
+ ('beginScope',
+ {'tal:content': 'bar', 'i18n:name': 'bar_name', 'i18n:translate': ''}),
+ ('i18nVariable',
+ ('bar_name',
+ [('startTag',
+ ('span',
+ [('i18n:translate', '', 'i18n'),
+ ('tal:content', 'bar', 'tal'),
+ ('i18n:name', 'bar_name', 'i18n')])),
+ ('insertI18nText', ('$bar$', [])),
+ ('rawtextOffset', ('</span>', 7))],
+ None,
+ 0)),
+ ('endScope', ()),
+ ('rawtextOffset', ('.', 1))])),
+('endScope', ()),
+('rawtextOffset', ('</div>', 6))
+ ])
+
+ def test_i18n_name_implicit_value(self):
+ # input/test22.html
+ self._run_check('''\
+<span i18n:translate="">
+ <span tal:omit-tag="" i18n:name="name"><b>Jim</b></span> was born in
+ <span tal:omit-tag="" i18n:name="country">the USA</span>.
+</span>
+''', [('setPosition', (1, 0)),
+ ('beginScope', {'i18n:translate': ''}),
+ ('startTag', ('span', [('i18n:translate', '', 'i18n')])),
+ ('insertTranslation',
+ ('',
+ [('rawtextBeginScope',
+ ('\n ', 2, (2, 2), 0, {'i18n:name': 'name', 'tal:omit-tag': ''})),
+ ('i18nVariable',
+ ('name',
+ [('optTag',
+ ('span',
+ '',
+ None,
+ 0,
+ [('startTag',
+ ('span',
+ [('tal:omit-tag', '', 'tal'),
+ ('i18n:name', 'name', 'i18n')]))],
+ [('rawtextOffset', ('<b>Jim</b>', 10))]))],
+ None,
+ 0)),
+ ('rawtextBeginScope',
+ (' was born in\n ',
+ 2,
+ (3, 2),
+ 1,
+ {'i18n:name': 'country', 'tal:omit-tag': ''})),
+ ('i18nVariable',
+ ('country',
+ [('optTag',
+ ('span',
+ '',
+ None,
+ 0,
+ [('startTag',
+ ('span',
+ [('tal:omit-tag', '', 'tal'),
+ ('i18n:name', 'country', 'i18n')]))],
+ [('rawtextOffset', ('the USA', 7))]))],
+ None,
+ 0)),
+ ('endScope', ()),
+ ('rawtextColumn', ('.\n', 0))])),
+ ('endScope', ()),
+ ('rawtextColumn', ('</span>\n', 0))
+ ])
+
+ def test_i18n_context_domain(self):
+ self._run_check("<span i18n:domain='mydomain'/>", [
+ ('setPosition', (1, 0)),
+ ('beginI18nContext', {'domain': 'mydomain',
+ 'source': None, 'target': None}),
+ ('beginScope', {'i18n:domain': 'mydomain'}),
+ ('startEndTag', ('span', [('i18n:domain', 'mydomain', 'i18n')])),
+ ('endScope', ()),
+ ('endI18nContext', ()),
+ ])
+
+ def test_i18n_context_source(self):
+ self._run_check("<span i18n:source='en'/>", [
+ ('setPosition', (1, 0)),
+ ('beginI18nContext', {'source': 'en',
+ 'domain': 'default', 'target': None}),
+ ('beginScope', {'i18n:source': 'en'}),
+ ('startEndTag', ('span', [('i18n:source', 'en', 'i18n')])),
+ ('endScope', ()),
+ ('endI18nContext', ()),
+ ])
+
+ def test_i18n_context_source_target(self):
+ self._run_check("<span i18n:source='en' i18n:target='ru'/>", [
+ ('setPosition', (1, 0)),
+ ('beginI18nContext', {'source': 'en', 'target': 'ru',
+ 'domain': 'default'}),
+ ('beginScope', {'i18n:source': 'en', 'i18n:target': 'ru'}),
+ ('startEndTag', ('span', [('i18n:source', 'en', 'i18n'),
+ ('i18n:target', 'ru', 'i18n')])),
+ ('endScope', ()),
+ ('endI18nContext', ()),
+ ])
+
+ def test_i18n_context_in_define_slot(self):
+ text = ("<div metal:use-macro='M' i18n:domain='mydomain'>"
+ "<div metal:fill-slot='S'>spam</div>"
+ "</div>")
+ self._run_check(text, [
+ ('setPosition', (1, 0)),
+ ('useMacro',
+ ('M', '$M$',
+ {'S': [('startTag', ('div',
+ [('metal:fill-slot', 'S', 'metal')])),
+ rawtext('spam</div>')]},
+ [('beginI18nContext', {'domain': 'mydomain',
+ 'source': None, 'target': None}),
+ ('beginScope',
+ {'i18n:domain': 'mydomain', 'metal:use-macro': 'M'}),
+ ('startTag', ('div', [('metal:use-macro', 'M', 'metal'),
+ ('i18n:domain', 'mydomain', 'i18n')])),
+ ('setPosition', (1, 48)),
+ ('fillSlot', ('S',
+ [('startTag',
+ ('div', [('metal:fill-slot', 'S', 'metal')])),
+ rawtext('spam</div>')])),
+ ('endScope', ()),
+ rawtext('</div>'),
+ ('endI18nContext', ())])),
+ ])
+
+ def test_i18n_data(self):
+ # input/test23.html
+ self._run_check('''\
+<span i18n:data="here/currentTime"
+ i18n:translate="timefmt">2:32 pm</span>
+''', [
+ ('setPosition', (1, 0)),
+ ('beginScope',
+ {'i18n:translate': 'timefmt', 'i18n:data': 'here/currentTime'}),
+ ('startTag',
+ ('span',
+ [('i18n:data', 'here/currentTime', 'i18n'),
+ ('i18n:translate', 'timefmt', 'i18n')])),
+ ('insertTranslation',
+ ('timefmt', [('rawtextOffset', ('2:32 pm', 7))], '$here/currentTime$')),
+ ('endScope', ()),
+ ('rawtextColumn', ('</span>\n', 0))
+ ])
+
+ def test_i18n_data_with_name(self):
+ # input/test29.html
+ self._run_check('''\
+<div i18n:translate="">At the tone the time will be
+<span i18n:data="here/currentTime"
+ i18n:translate="timefmt"
+ i18n:name="time">2:32 pm</span>... beep!</div>
+''', [('setPosition', (1, 0)),
+ ('beginScope', {'i18n:translate': ''}),
+ ('startTag', ('div', [('i18n:translate', '', 'i18n')])),
+ ('insertTranslation',
+ ('',
+ [('rawtextBeginScope',
+ ('At the tone the time will be\n',
+ 0,
+ (2, 0),
+ 0,
+ {'i18n:data': 'here/currentTime',
+ 'i18n:name': 'time',
+ 'i18n:translate': 'timefmt'})),
+ ('i18nVariable',
+ ('time',
+ [('startTag',
+ ('span',
+ [('i18n:data', 'here/currentTime', 'i18n'),
+ ('i18n:translate', 'timefmt', 'i18n'),
+ ('i18n:name', 'time', 'i18n')])),
+ ('insertTranslation',
+ ('timefmt',
+ [('rawtextOffset', ('2:32 pm', 7))],
+ '$here/currentTime$')),
+ ('rawtextOffset', ('</span>', 7))],
+ None,
+ 0)),
+ ('endScope', ()),
+ ('rawtextOffset', ('... beep!', 9))])),
+ ('endScope', ()),
+ ('rawtextColumn', ('</div>\n', 0))
+ ])
+
+ def test_i18n_name_around_tal_content(self):
+ # input/test28.html
+ self._run_check('''\
+<p i18n:translate="verify">Your contact email address is recorded as
+ <span tal:omit-tag="" i18n:name="email">
+ <a href="mailto:user@example.com"
+ tal:content="request/submitter">user@host.com</a></span>
+</p>
+''', [('setPosition', (1, 0)),
+ ('beginScope', {'i18n:translate': 'verify'}),
+ ('startTag', ('p', [('i18n:translate', 'verify', 'i18n')])),
+ ('insertTranslation',
+ ('verify',
+ [('rawtextBeginScope',
+ ('Your contact email address is recorded as\n ',
+ 4,
+ (2, 4),
+ 0,
+ {'i18n:name': 'email', 'tal:omit-tag': ''})),
+ ('i18nVariable',
+ ('email',
+ [('optTag',
+ ('span',
+ '',
+ None,
+ 0,
+ [('startTag',
+ ('span',
+ [('tal:omit-tag', '', 'tal'),
+ ('i18n:name', 'email', 'i18n')]))],
+ [('rawtextBeginScope',
+ ('\n ',
+ 4,
+ (3, 4),
+ 0,
+ {'href': 'mailto:user@example.com',
+ 'tal:content': 'request/submitter'})),
+ ('startTag',
+ ('a',
+ [('href', 'href="mailto:user@example.com"'),
+ ('tal:content', 'request/submitter', 'tal')])),
+ ('insertText',
+ ('$request/submitter$',
+ [('rawtextOffset', ('user@host.com', 13))])),
+ ('endScope', ()),
+ ('rawtextOffset', ('</a>', 4))]))],
+ None,
+ 0)),
+ ('endScope', ()),
+ ('rawtextColumn', ('\n', 0))])),
+ ('endScope', ()),
+ ('rawtextColumn', ('</p>\n', 0))
+ ])
+
+ def test_i18n_name_with_tal_content(self):
+ # input/test27.html
+ self._run_check('''\
+<p i18n:translate="verify">Your contact email address is recorded as
+ <a href="mailto:user@example.com"
+ tal:content="request/submitter"
+ i18n:name="email">user@host.com</a>
+</p>
+''', [
+ ('setPosition', (1, 0)),
+ ('beginScope', {'i18n:translate': 'verify'}),
+ ('startTag', ('p', [('i18n:translate', 'verify', 'i18n')])),
+ ('insertTranslation',
+ ('verify',
+ [('rawtextBeginScope',
+ ('Your contact email address is recorded as\n ',
+ 4,
+ (2, 4),
+ 0,
+ {'href': 'mailto:user@example.com',
+ 'i18n:name': 'email',
+ 'tal:content': 'request/submitter'})),
+ ('i18nVariable',
+ ('email',
+ [('startTag',
+ ('a',
+ [('href', 'href="mailto:user@example.com"'),
+ ('tal:content', 'request/submitter', 'tal'),
+ ('i18n:name', 'email', 'i18n')])),
+ ('insertText',
+ ('$request/submitter$',
+ [('rawtextOffset', ('user@host.com', 13))])),
+ ('rawtextOffset', ('</a>', 4))],
+ None,
+ 0)),
+ ('endScope', ()),
+ ('rawtextColumn', ('\n', 0))])),
+ ('endScope', ()),
+ ('rawtextColumn', ('</p>\n', 0))
+ ])
+
+
+def test_suite():
+ suite = unittest.makeSuite(HTMLTALParserTestCases)
+ suite.addTest(unittest.makeSuite(METALGeneratorTestCases))
+ suite.addTest(unittest.makeSuite(TALGeneratorTestCases))
+ return suite
+
+
+if __name__ == "__main__":
+ errs = utils.run_suite(test_suite())
+ sys.exit(errs and 1 or 0)
diff --git a/src/zope/tal/tests/test_sourcepos.py b/src/zope/tal/tests/test_sourcepos.py
new file mode 100644
index 0000000..4034b21
--- /dev/null
+++ b/src/zope/tal/tests/test_sourcepos.py
@@ -0,0 +1,93 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Tests for TALInterpreter.
+
+$Id$
+"""
+import unittest
+
+from StringIO import StringIO
+
+from zope.tal.htmltalparser import HTMLTALParser
+from zope.tal.talinterpreter import TALInterpreter
+from zope.tal.talgenerator import TALGenerator
+from zope.tal.dummyengine import DummyEngine
+
+
+page1 = '''<html metal:use-macro="main"><body>
+<div metal:fill-slot="body">
+page1=<span tal:replace="position:" />
+</div>
+</body></html>'''
+
+main_template = '''<html metal:define-macro="main"><body>
+main_template=<span tal:replace="position:" />
+<div metal:define-slot="body" />
+main_template=<span tal:replace="position:" />
+<div metal:use-macro="foot" />
+main_template=<span tal:replace="position:" />
+</body></html>'''
+
+footer = '''<div metal:define-macro="foot">
+footer=<span tal:replace="position:" />
+</div>'''
+
+expected = '''<html><body>
+main_template=main_template (2,14)
+<div>
+page1=page1 (3,6)
+</div>
+main_template=main_template (4,14)
+<div>
+footer=footer (2,7)
+</div>
+main_template=main_template (6,14)
+</body></html>'''
+
+
+
+class SourcePosTestCase(unittest.TestCase):
+
+ def parse(self, eng, s, fn):
+ gen = TALGenerator(expressionCompiler=eng, xml=0, source_file=fn)
+ parser = HTMLTALParser(gen)
+ parser.parseString(s)
+ program, macros = parser.getCode()
+ return program, macros
+
+ def test_source_positions(self):
+ # Ensure source file and position are set correctly by TAL
+ macros = {}
+ eng = DummyEngine(macros)
+ page1_program, page1_macros = self.parse(eng, page1, 'page1')
+ main_template_program, main_template_macros = self.parse(
+ eng, main_template, 'main_template')
+ footer_program, footer_macros = self.parse(eng, footer, 'footer')
+
+ macros['main'] = main_template_macros['main']
+ macros['foot'] = footer_macros['foot']
+
+ stream = StringIO()
+ interp = TALInterpreter(page1_program, macros, eng, stream)
+ interp()
+ self.assertEqual(stream.getvalue().strip(), expected.strip(),
+ "Got result:\n%s\nExpected:\n%s"
+ % (stream.getvalue(), expected))
+
+
+def test_suite():
+ return unittest.makeSuite(SourcePosTestCase)
+
+if __name__ == "__main__":
+ unittest.main(defaultTest='test_suite')
diff --git a/src/zope/tal/tests/test_talgettext.py b/src/zope/tal/tests/test_talgettext.py
new file mode 100644
index 0000000..bdfbfd8
--- /dev/null
+++ b/src/zope/tal/tests/test_talgettext.py
@@ -0,0 +1,78 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Tests for the talgettext utility.
+
+$Id$
+"""
+import sys
+import unittest
+from StringIO import StringIO
+
+from zope.tal.htmltalparser import HTMLTALParser
+from zope.tal.talgettext import POTALInterpreter
+from zope.tal.talgettext import POEngine
+from zope.tal.tests import utils
+
+class test_POEngine(unittest.TestCase):
+ """Test the PO engine functionality, which simply adds items to a catalog
+ as .translate is called
+ """
+
+ def test_translate(self):
+ test_keys = ['foo', 'bar', 'blarf', 'washington']
+
+ engine = POEngine()
+ engine.file = 'foo.pt'
+ for key in test_keys:
+ engine.translate(key, 'domain')
+
+ for key in test_keys:
+ self.failIf(key not in engine.catalog['domain'],
+ "POEngine catalog does not properly store message ids"
+ )
+
+ def test_dynamic_msgids(self):
+ sample_source = """
+ <p i18n:translate="">
+ Some
+ <span tal:replace="string:strange">dynamic</span>
+ text.
+ </p>
+ <p i18n:translate="">
+ A <a tal:attributes="href path:dynamic">link</a>.
+ </p>
+ """
+ p = HTMLTALParser()
+ p.parseString(sample_source)
+ program, macros = p.getCode()
+ engine = POEngine()
+ engine.file = 'sample_source'
+ POTALInterpreter(program, macros, engine, stream=StringIO(),
+ metal=False)()
+ msgids = []
+ for domain in engine.catalog.values():
+ msgids += domain.keys()
+ msgids.sort()
+ self.assertEquals(msgids,
+ ['A <a href="${DYNAMIC_CONTENT}">link</a>.',
+ 'Some ${DYNAMIC_CONTENT} text.'])
+
+
+def test_suite():
+ suite = unittest.makeSuite(test_POEngine)
+ return suite
+
+if __name__ == "__main__":
+ errs = utils.run_suite(test_suite())
+ sys.exit(errs and 1 or 0)
diff --git a/src/zope/tal/tests/test_talinterpreter.py b/src/zope/tal/tests/test_talinterpreter.py
new file mode 100644
index 0000000..c9e8ed7
--- /dev/null
+++ b/src/zope/tal/tests/test_talinterpreter.py
@@ -0,0 +1,853 @@
+# -*- coding: ISO-8859-1 -*-
+##############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Tests for TALInterpreter.
+
+$Id$
+"""
+import os
+import sys
+import unittest
+
+from StringIO import StringIO
+
+from zope.tal.taldefs import METALError, I18NError, TAL_VERSION
+from zope.tal.taldefs import TALExpressionError
+from zope.tal.htmltalparser import HTMLTALParser
+from zope.tal.talparser import TALParser
+from zope.tal.talinterpreter import TALInterpreter
+from zope.tal.talgenerator import TALGenerator
+from zope.tal.dummyengine import DummyEngine
+from zope.tal.dummyengine import MultipleDomainsDummyEngine
+from zope.tal.dummyengine import DummyTranslationDomain
+from zope.tal.tests import utils
+from zope.i18nmessageid import Message
+
+
+class TestCaseBase(unittest.TestCase):
+
+ def _compile(self, source, source_file=None):
+ generator = TALGenerator(xml=0, source_file=source_file)
+ parser = HTMLTALParser(generator)
+ parser.parseString(source)
+ program, macros = parser.getCode()
+ return program, macros
+
+
+class MacroErrorsTestCase(TestCaseBase):
+
+ def setUp(self):
+ dummy, macros = self._compile('<p metal:define-macro="M">Booh</p>')
+ self.macro = macros['M']
+ self.engine = DummyEngine(macros)
+ program, dummy = self._compile('<p metal:use-macro="M">Bah</p>')
+ self.interpreter = TALInterpreter(program, {}, self.engine)
+
+ def tearDown(self):
+ try:
+ self.interpreter()
+ except METALError:
+ pass
+ else:
+ self.fail("Expected METALError")
+
+ def test_mode_error(self):
+ self.macro[1] = ("mode", "duh")
+
+ def test_version_error(self):
+ self.macro[0] = ("version", "duh")
+
+
+class MacroFunkyErrorTest(TestCaseBase):
+
+ def test_div_in_p_using_macro(self):
+ dummy, macros = self._compile('<p metal:define-macro="M">Booh</p>')
+ engine = DummyEngine(macros)
+ program, dummy = self._compile(
+ '<p metal:use-macro="M"><div>foo</div></p>')
+ interpreter = TALInterpreter(program, {}, engine)
+
+ output = interpreter()
+ self.assertEqual(output, '<p><div>foo</div></p>')
+
+
+class MacroExtendTestCase(TestCaseBase):
+
+ def setUp(self):
+ s = self._read(('input', 'pnome_template.pt'))
+ self.pnome_program, pnome_macros = self._compile(s)
+ s = self._read(('input', 'acme_template.pt'))
+ self.acme_program, acme_macros = self._compile(s)
+ s = self._read(('input', 'document_list.pt'))
+ self.doclist_program, doclist_macros = self._compile(s)
+ macros = {
+ 'pnome_macros_page': pnome_macros['page'],
+ 'acme_macros_page': acme_macros['page'],
+ }
+ self.engine = DummyEngine(macros)
+
+ def _read(self, path):
+ dir = os.path.dirname(__file__)
+ fn = os.path.join(dir, *path)
+ f = open(fn)
+ data = f.read()
+ f.close()
+ return data
+
+ def test_preview_acme_template(self):
+ # An ACME designer is previewing the ACME design. For the
+ # purposes of this use case, extending a macro should act the
+ # same as using a macro.
+ result = StringIO()
+ interpreter = TALInterpreter(
+ self.acme_program, {}, self.engine, stream=result)
+ interpreter()
+ actual = result.getvalue().strip()
+ expected = self._read(('output', 'acme_template.html')).strip()
+ self.assertEqual(actual, expected)
+
+ def test_preview_acme_template_source(self):
+ # Render METAL attributes in acme_template
+ result = StringIO()
+ interpreter = TALInterpreter(
+ self.acme_program, {}, self.engine, stream=result, tal=False)
+ interpreter()
+ actual = result.getvalue().strip()
+ expected = self._read(('output', 'acme_template_source.html')).strip()
+ self.assertEqual(actual, expected)
+
+
+class I18NCornerTestCaseBase(TestCaseBase):
+
+ def factory(self, msgid, default, mapping={}):
+ raise NotImplementedError("abstract method")
+
+ def setUp(self):
+ self.engine = DummyEngine()
+ # Make sure we'll translate the msgid not its unicode representation
+ self.engine.setLocal('foo',
+ self.factory('FoOvAlUe${empty}', 'default', {'empty': ''}))
+ self.engine.setLocal('bar', 'BaRvAlUe')
+
+ def _check(self, program, expected):
+ result = StringIO()
+ self.interpreter = TALInterpreter(program, {}, self.engine,
+ stream=result)
+ self.interpreter()
+ self.assertEqual(expected, result.getvalue())
+
+ def test_simple_messageid_translate(self):
+ # This test is mainly here to make sure our DummyEngine works
+ # correctly.
+ program, macros = self._compile(
+ '<span i18n:translate="" tal:content="foo"/>')
+ self._check(program, '<span>FOOVALUE</span>\n')
+
+ program, macros = self._compile(
+ '<span i18n:translate="" tal:replace="foo"/>')
+ self._check(program, 'FOOVALUE\n')
+
+ # i18n messages defined in Python are translated automatically
+ # (no i18n:translate necessary)
+ program, macros = self._compile(
+ '<span tal:content="foo" />')
+ self._check(program, '<span>FOOVALUE</span>\n')
+
+ program, macros = self._compile(
+ '<span tal:replace="foo" />')
+ self._check(program, 'FOOVALUE\n')
+
+ def test_attributes_translation(self):
+ program, macros = self._compile(
+ '<span tal:attributes="test bar"/>')
+ self._check(program, '<span test="BaRvAlUe" />\n')
+
+ program, macros = self._compile(
+ '<span test="bar" i18n:attributes="test"/>')
+ self._check(program, '<span test="BAR" />\n')
+
+ program, macros = self._compile(
+ '<span tal:attributes="test bar" i18n:attributes="test"/>')
+ self._check(program, '<span test="BARVALUE" />\n')
+
+ # i18n messages defined in Python are translated automatically
+ # (no i18n:attributes necessary)
+ program, macros = self._compile(
+ '<span tal:attributes="test foo"/>')
+ self._check(program, '<span test="FOOVALUE" />\n')
+
+ def test_text_variable_translate(self):
+ program, macros = self._compile(
+ '<span tal:content="bar"/>')
+ self._check(program, '<span>BaRvAlUe</span>\n')
+
+ program, macros = self._compile(
+ '<span i18n:translate="" tal:content="bar"/>')
+ self._check(program, '<span>BARVALUE</span>\n')
+
+ program, macros = self._compile(
+ '<span i18n:translate="" tal:replace="bar"/>')
+ self._check(program, 'BARVALUE\n')
+
+ def test_text_translate(self):
+ program, macros = self._compile(
+ '<span tal:content="string:BaR"/>')
+ self._check(program, '<span>BaR</span>\n')
+
+ program, macros = self._compile(
+ '<span i18n:translate="" tal:content="string:BaR"/>')
+ self._check(program, '<span>BAR</span>\n')
+
+ program, macros = self._compile(
+ '<span i18n:translate="" tal:replace="string:BaR"/>')
+ self._check(program, 'BAR\n')
+
+ def test_structure_text_variable_translate(self):
+ program, macros = self._compile(
+ '<span tal:content="structure bar"/>')
+ self._check(program, '<span>BaRvAlUe</span>\n')
+
+ program, macros = self._compile(
+ '<span i18n:translate="" tal:content="structure bar"/>')
+ self._check(program, '<span>BARVALUE</span>\n')
+
+ program, macros = self._compile(
+ '<span i18n:translate="" tal:replace="structure bar"/>')
+ self._check(program, 'BARVALUE\n')
+
+ # i18n messages defined in Python are translated automatically
+ # (no i18n:translate necessary)
+ program, macros = self._compile(
+ '<span tal:content="structure foo"/>')
+ self._check(program, '<span>FOOVALUE</span>\n')
+
+ program, macros = self._compile(
+ '<span tal:replace="structure foo"/>')
+ self._check(program, 'FOOVALUE\n')
+
+ def test_structure_text_translate(self):
+ program, macros = self._compile(
+ '<span tal:content="structure string:BaR"/>')
+ self._check(program, '<span>BaR</span>\n')
+
+ program, macros = self._compile(
+ '<span i18n:translate="" tal:content="structure string:BaR"/>')
+ self._check(program, '<span>BAR</span>\n')
+
+ program, macros = self._compile(
+ '<span i18n:translate="" tal:replace="structure string:BaR"/>')
+ self._check(program, 'BAR\n')
+
+ def test_replace_with_messageid_and_i18nname(self):
+ program, macros = self._compile(
+ '<div i18n:translate="" >'
+ '<span i18n:translate="" tal:replace="foo" i18n:name="foo_name"/>'
+ '</div>')
+ self._check(program, '<div>FOOVALUE</div>\n')
+
+ def test_pythonexpr_replace_with_messageid_and_i18nname(self):
+ program, macros = self._compile(
+ '<div i18n:translate="" >'
+ '<span i18n:translate="" tal:replace="python: foo"'
+ ' i18n:name="foo_name"/>'
+ '</div>')
+ self._check(program, '<div>FOOVALUE</div>\n')
+
+ def test_structure_replace_with_messageid_and_i18nname(self):
+ program, macros = self._compile(
+ '<div i18n:translate="" >'
+ '<span i18n:translate="" tal:replace="structure foo"'
+ ' i18n:name="foo_name"/>'
+ '</div>')
+ self._check(program, '<div>FOOVALUE</div>\n')
+
+ def test_complex_replace_with_messageid_and_i18nname(self):
+ program, macros = self._compile(
+ '<div i18n:translate="" >'
+ '<em tal:omit-tag="" i18n:name="foo_name">'
+ '<span i18n:translate="" tal:replace="foo"/>'
+ '</em>'
+ '</div>')
+ self._check(program, '<div>FOOVALUE</div>\n')
+
+ def test_content_with_messageid_and_i18nname(self):
+ program, macros = self._compile(
+ '<div i18n:translate="" >'
+ '<span i18n:translate="" tal:content="foo" i18n:name="foo_name"/>'
+ '</div>')
+ self._check(program, '<div><span>FOOVALUE</span></div>\n')
+
+ def test_content_with_messageid_and_i18nname_and_i18ntranslate(self):
+ # Let's tell the user this is incredibly silly!
+ self.assertRaises(
+ I18NError, self._compile,
+ '<span i18n:translate="" tal:content="foo" i18n:name="foo_name"/>')
+
+ def test_content_with_explicit_messageid(self):
+ # Let's tell the user this is incredibly silly!
+ self.assertRaises(
+ I18NError, self._compile,
+ '<span i18n:translate="ID" tal:content="foo" />')
+
+ def test_content_with_plaintext_and_i18nname_and_i18ntranslate(self):
+ # Let's tell the user this is incredibly silly!
+ self.assertRaises(
+ I18NError, self._compile,
+ '<span i18n:translate="" i18n:name="color_name">green</span>')
+
+ def test_translate_static_text_as_dynamic(self):
+ program, macros = self._compile(
+ '<div i18n:translate="">This is text for '
+ '<span tal:content="bar" i18n:name="bar_name"/>.'
+ '</div>')
+ self._check(program,
+ '<div>THIS IS TEXT FOR <span>BaRvAlUe</span>.</div>\n')
+ program, macros = self._compile(
+ '<div i18n:translate="">This is text for '
+ '<span i18n:translate="" tal:content="bar" i18n:name="bar_name"/>.'
+ '</div>')
+ self._check(program,
+ '<div>THIS IS TEXT FOR <span>BARVALUE</span>.</div>\n')
+
+ def test_translate_static_text_as_dynamic_from_bytecode(self):
+ program = [('version', TAL_VERSION),
+ ('mode', 'html'),
+('setPosition', (1, 0)),
+('beginScope', {'i18n:translate': ''}),
+('startTag', ('div', [('i18n:translate', '', 'i18n')])),
+('insertTranslation',
+ ('',
+ [('rawtextOffset', ('This is text for ', 17)),
+ ('setPosition', (1, 40)),
+ ('beginScope',
+ {'tal:content': 'bar', 'i18n:name': 'bar_name', 'i18n:translate': ''}),
+ ('i18nVariable',
+ ('bar_name',
+ [('startTag',
+ ('span',
+ [('i18n:translate', '', 'i18n'),
+ ('tal:content', 'bar', 'tal'),
+ ('i18n:name', 'bar_name', 'i18n')])),
+ ('insertTranslation',
+ ('',
+ [('insertText', ('$bar$', []))])),
+ ('rawtextOffset', ('</span>', 7))],
+ None,
+ 0)),
+ ('endScope', ()),
+ ('rawtextOffset', ('.', 1))])),
+('endScope', ()),
+('rawtextOffset', ('</div>', 6))
+]
+ self._check(program,
+ '<div>THIS IS TEXT FOR <span>BARVALUE</span>.</div>\n')
+
+ def test_for_correct_msgids(self):
+ self.engine.translationDomain.clearMsgids()
+ result = StringIO()
+ #GChapelle:
+ #I have the feeling the i18n:translate with the i18n:name is wrong
+ #
+ #program, macros = self._compile(
+ # '<div i18n:translate="">This is text for '
+ # '<span i18n:translate="" tal:content="bar" '
+ # 'i18n:name="bar_name"/>.</div>')
+ program, macros = self._compile(
+ '<div i18n:translate="">This is text for '
+ '<span tal:content="bar" '
+ 'i18n:name="bar_name"/>.</div>')
+ self.interpreter = TALInterpreter(program, {}, self.engine,
+ stream=result)
+ self.interpreter()
+ msgids = self.engine.translationDomain.getMsgids('default')
+ msgids.sort()
+ self.assertEqual(1, len(msgids))
+ self.assertEqual('This is text for ${bar_name}.', msgids[0][0])
+ self.assertEqual({'bar_name': '<span>BaRvAlUe</span>'}, msgids[0][1])
+ self.assertEqual(
+ '<div>THIS IS TEXT FOR <span>BaRvAlUe</span>.</div>\n',
+ result.getvalue())
+
+ def test_for_correct_msgids_translate_name(self):
+ self.engine.translationDomain.clearMsgids()
+ result = StringIO()
+ program, macros = self._compile(
+ '<div i18n:translate="">This is text for '
+ '<span i18n:translate="" tal:content="bar" '
+ 'i18n:name="bar_name"/>.</div>')
+ self.interpreter = TALInterpreter(program, {}, self.engine,
+ stream=result)
+ self.interpreter()
+ msgids = self.engine.translationDomain.getMsgids('default')
+ msgids.sort()
+ self.assertEqual(2, len(msgids))
+ self.assertEqual('This is text for ${bar_name}.', msgids[1][0])
+ self.assertEqual({'bar_name': '<span>BARVALUE</span>'}, msgids[1][1])
+ self.assertEqual(
+ '<div>THIS IS TEXT FOR <span>BARVALUE</span>.</div>\n',
+ result.getvalue())
+
+ def test_i18ntranslate_i18nname_and_attributes(self):
+ # Test for Issue 301: Bug with i18n:name and i18n:translate
+ # on the same element
+ self.engine.translationDomain.clearMsgids()
+ result = StringIO()
+ program, macros = self._compile(
+ '<p i18n:translate="">'
+ 'Some static text and a <a tal:attributes="href string:url"'
+ ' i18n:name="link" i18n:translate="">link text</a>.</p>')
+ self.interpreter = TALInterpreter(program, {}, self.engine,
+ stream=result)
+ self.interpreter()
+ msgids = self.engine.translationDomain.getMsgids('default')
+ msgids.sort()
+ self.assertEqual(2, len(msgids))
+ self.assertEqual('Some static text and a ${link}.', msgids[0][0])
+ self.assertEqual({'link': '<a href="url">LINK TEXT</a>'}, msgids[0][1])
+ self.assertEqual('link text', msgids[1][0])
+ self.assertEqual(
+ '<p>SOME STATIC TEXT AND A <a href="url">LINK TEXT</a>.</p>\n',
+ result.getvalue())
+
+ def test_for_raw_msgids(self):
+ # Test for Issue 314: i18n:translate removes line breaks from
+ # <pre>...</pre> contents
+ # HTML mode
+ self.engine.translationDomain.clearMsgids()
+ result = StringIO()
+ program, macros = self._compile(
+ '<div i18n:translate=""> This is text\n'
+ ' \tfor\n div. </div>'
+ '<pre i18n:translate=""> This is text\n'
+ ' <b>\tfor</b>\n pre. </pre>')
+ self.interpreter = TALInterpreter(program, {}, self.engine,
+ stream=result)
+ self.interpreter()
+ msgids = self.engine.translationDomain.getMsgids('default')
+ msgids.sort()
+ self.assertEqual(2, len(msgids))
+ self.assertEqual(' This is text\n <b>\tfor</b>\n pre. ', msgids[0][0])
+ self.assertEqual('This is text for div.', msgids[1][0])
+ self.assertEqual(
+ '<div>THIS IS TEXT FOR DIV.</div>'
+ '<pre> THIS IS TEXT\n <B>\tFOR</B>\n PRE. </pre>\n',
+ result.getvalue())
+
+ # XML mode
+ self.engine.translationDomain.clearMsgids()
+ result = StringIO()
+ parser = TALParser()
+ parser.parseString(
+ '<?xml version="1.0"?>\n'
+ '<pre xmlns:i18n="http://xml.zope.org/namespaces/i18n"'
+ ' i18n:translate=""> This is text\n'
+ ' <b>\tfor</b>\n barvalue. </pre>')
+ program, macros = parser.getCode()
+ self.interpreter = TALInterpreter(program, {}, self.engine,
+ stream=result)
+ self.interpreter()
+ msgids = self.engine.translationDomain.getMsgids('default')
+ msgids.sort()
+ self.assertEqual(1, len(msgids))
+ self.assertEqual('This is text <b> for</b> barvalue.', msgids[0][0])
+ self.assertEqual(
+ '<?xml version="1.0"?>\n'
+ '<pre>THIS IS TEXT <B> FOR</B> BARVALUE.</pre>\n',
+ result.getvalue())
+
+ def test_raw_msgids_and_i18ntranslate_i18nname(self):
+ self.engine.translationDomain.clearMsgids()
+ result = StringIO()
+ program, macros = self._compile(
+ '<div i18n:translate=""> This is text\n \tfor\n'
+ '<pre i18n:name="bar" i18n:translate=""> \tbar\n </pre>.</div>')
+ self.interpreter = TALInterpreter(program, {}, self.engine,
+ stream=result)
+ self.interpreter()
+ msgids = self.engine.translationDomain.getMsgids('default')
+ msgids.sort()
+ self.assertEqual(2, len(msgids))
+ self.assertEqual(' \tbar\n ', msgids[0][0])
+ self.assertEqual('This is text for ${bar}.', msgids[1][0])
+ self.assertEqual({'bar': '<pre> \tBAR\n </pre>'}, msgids[1][1])
+ self.assertEqual(
+ u'<div>THIS IS TEXT FOR <pre> \tBAR\n </pre>.</div>\n',
+ result.getvalue())
+
+ def test_for_handling_unicode_vars(self):
+ # Make sure that non-ASCII Unicode is substituted correctly.
+ # http://collector.zope.org/Zope3-dev/264
+ program, macros = self._compile(
+ "<div i18n:translate='' tal:define='bar python:unichr(0xC0)'>"
+ "Foo <span tal:replace='bar' i18n:name='bar' /></div>")
+ self._check(program, u"<div>FOO \u00C0</div>\n")
+
+class I18NCornerTestCaseMessage(I18NCornerTestCaseBase):
+
+ def factory(self, msgid, default=None, mapping={}, domain=None):
+ return Message(msgid, domain=domain, default=default, mapping=mapping)
+
+class UnusedExplicitDomainTestCase(I18NCornerTestCaseMessage):
+
+ def setUp(self):
+ # MultipleDomainsDummyEngine is a Engine
+ # where default domain transforms to uppercase
+ self.engine = MultipleDomainsDummyEngine()
+ self.engine.setLocal('foo',
+ self.factory('FoOvAlUe${empty}', 'default', {'empty': ''}))
+ self.engine.setLocal('bar', 'BaRvAlUe')
+ self.engine.setLocal('baz',
+ self.factory('BaZvAlUe', 'default', {}))
+ # Message ids with different domains
+ self.engine.setLocal('toupper',
+ self.factory('ToUpper', 'default', {}))
+ self.engine.setLocal('tolower',
+ self.factory('ToLower', 'default', {}, domain='lower'))
+
+ def test_multiple_domains(self):
+ program, macros = self._compile(
+ '<div i18n:translate=""'
+ ' tal:content="toupper" />')
+ self._check(program, '<div>TOUPPER</div>\n')
+ program, macros = self._compile(
+ '<div i18n:translate=""'
+ ' tal:content="tolower" />')
+ self._check(program, '<div>tolower</div>\n')
+ program, macros = self._compile(
+ '<div i18n:translate=""'
+ ' tal:content="string:ToUpper" />')
+ self._check(program, '<div>TOUPPER</div>\n')
+ program, macros = self._compile(
+ '<div i18n:translate=""'
+ ' i18n:domain="lower"'
+ ' tal:content="string:ToLower" />')
+ self._check(program, '<div>tolower</div>\n')
+ program, macros = self._compile(
+ '<div i18n:translate=""'
+ ' tal:define="msgid string:ToUpper"'
+ ' tal:content="msgid" />')
+ self._check(program, '<div>TOUPPER</div>\n')
+ program, macros = self._compile(
+ '<div i18n:translate=""'
+ ' i18n:domain="lower"'
+ ' tal:define="msgid string:ToLower"'
+ ' tal:content="msgid" />')
+ self._check(program, '<div>tolower</div>\n')
+
+ def test_unused_explicit_domain(self):
+ #a_very_explicit_domain_setup_by_template_developer_that_wont_be_taken_into_account_by_the_ZPT_engine
+ #is a domain that transforms to lowercase
+ self.engine.setLocal('othertolower',
+ self.factory('OtherToLower', 'a_very_explicit_domain_setup_by_template_developer_that_wont_be_taken_into_account_by_the_ZPT_engine', {}, domain='lower'))
+ program, macros = self._compile(
+ '<div i18n:translate=""'
+ ' tal:content="othertolower" />')
+ self._check(program, '<div>othertolower</div>\n')
+ #takes domain into account for strings
+ program, macros = self._compile(
+ '<div i18n:translate=""'
+ ' i18n:domain="a_very_explicit_domain_setup_by_template_developer_that_wont_be_taken_into_account_by_the_ZPT_engine"'
+ ' tal:content="string:ToLower" />')
+ self._check(program, '<div>tolower</div>\n')
+ #but not for messageids
+ program, macros = self._compile(
+ '<div i18n:translate=""'
+ ' i18n:domain="a_very_explicit_domain_setup_by_template_developer_that_wont_be_taken_into_account_by_the_ZPT_engine"'
+ ' tal:content="baz" />')
+ self._check(program, '<div>BAZVALUE</div>\n')
+
+class ScriptTestCase(TestCaseBase):
+
+ def setUp(self):
+ self.engine = DummyEngine()
+
+ def _check(self, program, expected):
+ result = StringIO()
+ self.interpreter = TALInterpreter(program, {}, self.engine,
+ stream=result)
+ self.interpreter()
+ self.assertEqual(expected, result.getvalue())
+
+ def test_simple(self):
+ program, macros = self._compile(
+ '<p tal:script="text/server-python">print "hello"</p>')
+ self._check(program, '<p>hello\n</p>\n')
+
+ def test_script_and_tal_block(self):
+ program, macros = self._compile(
+ '<tal:block script="text/server-python">\n'
+ ' global x\n'
+ ' x = 1\n'
+ '</tal:block>\n'
+ '<span tal:replace="x" />')
+ self._check(program, '\n1\n')
+ self.assertEqual(self.engine.codeGlobals['x'], 1)
+
+ def test_script_and_tal_block_having_inside_print(self):
+ program, macros = self._compile(
+ '<tal:block script="text/server-python">\n'
+ ' print "hello"'
+ '</tal:block>')
+ self._check(program, 'hello\n\n')
+
+ def test_script_and_omittag(self):
+ program, macros = self._compile(
+ '<p tal:omit-tag="" tal:script="text/server-python">\n'
+ ' print "hello"'
+ '</p>')
+ self._check(program, 'hello\n\n')
+
+ def test_script_and_inside_tags(self):
+ program, macros = self._compile(
+ '<p tal:omit-tag="" tal:script="text/server-python">\n'
+ ' print "<b>hello</b>"'
+ '</p>')
+ self._check(program, '<b>hello</b>\n\n')
+
+ def test_script_and_inside_tags_with_tal(self):
+ program, macros = self._compile(
+ '<p tal:omit-tag="" tal:script="text/server-python"> <!--\n'
+ ' print """<b tal:replace="string:foo">hello</b>"""\n'
+ '--></p>')
+ self._check(program, '<b tal:replace="string:foo">hello</b>\n\n')
+
+ def test_html_script(self):
+ program, macros = self._compile(
+ '<script type="text/server-python">\n'
+ ' print "Hello world!"\n'
+ '</script>')
+ self._check(program, 'Hello world!\n')
+
+ def test_html_script_and_javascript(self):
+ program, macros = self._compile(
+ '<script type="text/javascript" src="somefile.js" />\n'
+ '<script type="text/server-python">\n'
+ ' print "Hello world!"\n'
+ '</script>')
+ self._check(program,
+ '<script type="text/javascript" src="somefile.js" />\n'
+ 'Hello world!\n')
+
+
+class I18NErrorsTestCase(TestCaseBase):
+
+ def _check(self, src, msg):
+ try:
+ self._compile(src)
+ except I18NError:
+ pass
+ else:
+ self.fail(msg)
+
+ def test_id_with_replace(self):
+ self._check('<p i18n:id="foo" tal:replace="string:splat"></p>',
+ "expected i18n:id with tal:replace to be denied")
+
+ def test_missing_values(self):
+ self._check('<p i18n:attributes=""></p>',
+ "missing i18n:attributes value not caught")
+ self._check('<p i18n:data=""></p>',
+ "missing i18n:data value not caught")
+ self._check('<p i18n:id=""></p>',
+ "missing i18n:id value not caught")
+
+ def test_id_with_attributes(self):
+ self._check('''<input name="Delete"
+ tal:attributes="name string:delete_button"
+ i18n:attributes="name message-id">''',
+ "expected attribute being both part of tal:attributes" +
+ " and having a msgid in i18n:attributes to be denied")
+
+class OutputPresentationTestCase(TestCaseBase):
+
+ def test_attribute_wrapping(self):
+ # To make sure the attribute-wrapping code is invoked, we have to
+ # include at least one TAL/METAL attribute to avoid having the start
+ # tag optimized into a rawtext instruction.
+ INPUT = r"""
+ <html this='element' has='a' lot='of' attributes=', so' the='output'
+ needs='to' be='line' wrapped='.' tal:define='foo nothing'>
+ </html>"""
+ EXPECTED = r'''
+ <html this="element" has="a" lot="of"
+ attributes=", so" the="output" needs="to"
+ be="line" wrapped=".">
+ </html>''' "\n"
+ self.compare(INPUT, EXPECTED)
+
+ def test_unicode_content(self):
+ INPUT = """<p tal:content="python:u'déjà-vu'">para</p>"""
+ EXPECTED = u"""<p>déjà-vu</p>""" "\n"
+ self.compare(INPUT, EXPECTED)
+
+ def test_unicode_structure(self):
+ INPUT = """<p tal:replace="structure python:u'déjà-vu'">para</p>"""
+ EXPECTED = u"""déjà-vu""" "\n"
+ self.compare(INPUT, EXPECTED)
+
+ def test_i18n_replace_number(self):
+ INPUT = """
+ <p i18n:translate="foo ${bar}">
+ <span tal:replace="python:123" i18n:name="bar">para</span>
+ </p>"""
+ EXPECTED = u"""
+ <p>FOO 123</p>""" "\n"
+ self.compare(INPUT, EXPECTED)
+
+ def test_entities(self):
+ INPUT = ('<img tal:define="foo nothing" '
+ 'alt="&a; &#1; &#x0a; &a &#45 &; &#0a; <>" />')
+ EXPECTED = ('<img alt="&a; &#1; &#x0a; '
+ '&amp;a &amp;#45 &amp;; &amp;#0a; &lt;&gt;" />\n')
+ self.compare(INPUT, EXPECTED)
+
+ def compare(self, INPUT, EXPECTED):
+ program, macros = self._compile(INPUT)
+ sio = StringIO()
+ interp = TALInterpreter(program, {}, DummyEngine(), sio, wrap=60)
+ interp()
+ self.assertEqual(sio.getvalue(), EXPECTED)
+
+
+class TestSourceAnnotations(unittest.TestCase):
+
+ # there are additional test files in input/ and output/ subdirs
+ # (test_sa*)
+
+ def setUp(self):
+ program = []
+ macros = {}
+ engine = DummyEngine()
+ self.interpreter = TALInterpreter(program, macros, engine)
+ self.sio = self.interpreter.stream = StringIO()
+ self.interpreter._pending_source_annotation = True
+
+ def testFormatSourceAnnotation(self):
+ interpreter = self.interpreter
+ interpreter.sourceFile = '/path/to/source.pt'
+ interpreter.position = (123, 42)
+ self.assertEquals(interpreter.formatSourceAnnotation(),
+ "<!--\n" +
+ "=" * 78 + "\n" +
+ "/path/to/source.pt (line 123)\n" +
+ "=" * 78 + "\n" +
+ "-->")
+
+ def testFormatSourceAnnotation_no_position(self):
+ interpreter = self.interpreter
+ interpreter.sourceFile = '/path/to/source.pt'
+ interpreter.position = (None, None)
+ self.assertEquals(interpreter.formatSourceAnnotation(),
+ "<!--\n" +
+ "=" * 78 + "\n" +
+ "/path/to/source.pt\n" +
+ "=" * 78 + "\n" +
+ "-->")
+
+ def test_annotated_stream_write(self):
+ interpreter = self.interpreter
+ interpreter.formatSourceAnnotation = lambda: '@'
+ test_cases = [
+ '@some text',
+ '\n',
+ '<?xml ...?>@some text',
+ ' <?xml ...?>@some text',
+ '\n<?xml ...?>@some text',
+ '<?xml ...',
+ '<?xml ...?>@\n<!DOCTYPE ...>some text',
+ ]
+ for output in test_cases:
+ input = output.replace('@', '')
+ self.sio.seek(0)
+ self.sio.truncate()
+ interpreter._pending_source_annotation = True
+ interpreter._annotated_stream_write(input)
+ self.assertEquals(self.sio.getvalue(), output)
+ if '@' in output:
+ self.assert_(not interpreter._pending_source_annotation)
+ else:
+ self.assert_(interpreter._pending_source_annotation)
+
+
+class TestErrorTracebacks(TestCaseBase):
+
+ # Regression test for http://www.zope.org/Collectors/Zope3-dev/697
+
+ def test_define_slot_does_not_clobber_source_file_on_exception(self):
+ m_program, m_macros = self._compile("""
+ <div metal:define-macro="amacro">
+ <div metal:define-slot="aslot">
+ </div>
+ </div>
+ """, source_file='macros.pt')
+ p_program, p_macros = self._compile("""
+ <div metal:use-macro="amacro">
+ <div metal:fill-slot="aslot">
+ <tal:x replace="no_such_thing" />
+ </div>
+ </div>
+ """, source_file='page.pt')
+ engine = DummyEngine(macros=m_macros)
+ interp = TALInterpreter(p_program, {}, engine, StringIO())
+ # Expect TALExpressionError: unknown variable: 'no_such_thing'
+ self.assertRaises(TALExpressionError, interp)
+ # Now the engine should know where the error occurred
+ self.assertEquals(engine.source_file, 'page.pt')
+ self.assertEquals(engine.position, (4, 16))
+
+ def test_define_slot_restores_source_file_if_no_exception(self):
+ m_program, m_macros = self._compile("""
+ <div metal:define-macro="amacro">
+ <div metal:define-slot="aslot">
+ </div>
+ <tal:x replace="no_such_thing" />
+ </div>
+ """, source_file='macros.pt')
+ p_program, p_macros = self._compile("""
+ <div metal:use-macro="amacro">
+ <div metal:fill-slot="aslot">
+ </div>
+ </div>
+ """, source_file='page.pt')
+ engine = DummyEngine(macros=m_macros)
+ interp = TALInterpreter(p_program, {}, engine, StringIO())
+ # Expect TALExpressionError: unknown variable: 'no_such_thing'
+ self.assertRaises(TALExpressionError, interp)
+ # Now the engine should know where the error occurred
+ self.assertEquals(engine.source_file, 'macros.pt')
+ self.assertEquals(engine.position, (5, 14))
+
+
+
+def test_suite():
+ suite = unittest.makeSuite(I18NErrorsTestCase)
+ suite.addTest(unittest.makeSuite(MacroErrorsTestCase))
+ suite.addTest(unittest.makeSuite(MacroExtendTestCase))
+ suite.addTest(unittest.makeSuite(OutputPresentationTestCase))
+ suite.addTest(unittest.makeSuite(ScriptTestCase))
+ suite.addTest(unittest.makeSuite(I18NCornerTestCaseMessage))
+ suite.addTest(unittest.makeSuite(UnusedExplicitDomainTestCase))
+ suite.addTest(unittest.makeSuite(TestSourceAnnotations))
+ suite.addTest(unittest.makeSuite(TestErrorTracebacks))
+
+ # TODO: Deactivated test, since we have not found a solution for this and
+ # it is a deep and undocumented HTML parser issue.
+ # Fred is looking into this.
+ #suite.addTest(unittest.makeSuite(MacroFunkyErrorTest))
+
+ return suite
+
+if __name__ == "__main__":
+ errs = utils.run_suite(test_suite())
+ sys.exit(errs and 1 or 0)
diff --git a/src/zope/tal/tests/test_talparser.py b/src/zope/tal/tests/test_talparser.py
new file mode 100644
index 0000000..f159cc1
--- /dev/null
+++ b/src/zope/tal/tests/test_talparser.py
@@ -0,0 +1,39 @@
+##############################################################################
+#
+# Copyright (c) 2004 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Tests for zope.tal.talparser.
+
+$Id$
+"""
+import unittest
+
+from zope.tal import talparser
+
+
+class TALParserTestCase(unittest.TestCase):
+
+ def test_parser_returns_macros(self):
+ parser = talparser.TALParser()
+ parser.parseString(
+ "<?xml version='1.0'?>\n"
+ "<doc xmlns:metal='http://xml.zope.org/namespaces/metal'>\n"
+ " <m metal:define-macro='MACRO'>\n"
+ " <para>some text</para>\n"
+ " </m>\n"
+ "</doc>")
+ bytecode, macros = parser.getCode()
+ self.assertEqual(macros.keys(), ["MACRO"])
+
+
+def test_suite():
+ return unittest.makeSuite(TALParserTestCase)
diff --git a/src/zope/tal/tests/test_xmlparser.py b/src/zope/tal/tests/test_xmlparser.py
new file mode 100644
index 0000000..02d5848
--- /dev/null
+++ b/src/zope/tal/tests/test_xmlparser.py
@@ -0,0 +1,268 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Tests for XMLParser.py.
+
+$Id$
+"""
+import sys
+import unittest
+
+from zope.tal import xmlparser
+from zope.tal.tests import utils
+
+
+class EventCollector(xmlparser.XMLParser):
+
+ def __init__(self):
+ self.events = []
+ self.append = self.events.append
+ xmlparser.XMLParser.__init__(self)
+ self.parser.ordered_attributes = 1
+
+ def get_events(self):
+ # Normalize the list of events so that buffer artefacts don't
+ # separate runs of contiguous characters.
+ L = []
+ prevtype = None
+ for event in self.events:
+ type = event[0]
+ if type == prevtype == "data":
+ L[-1] = ("data", L[-1][1] + event[1])
+ else:
+ L.append(event)
+ prevtype = type
+ self.events = L
+ return L
+
+ # structure markup
+
+ def StartElementHandler(self, tag, attrs):
+ self.append(("starttag", tag, attrs))
+
+ def EndElementHandler(self, tag):
+ self.append(("endtag", tag))
+
+ # all other markup
+
+ def CommentHandler(self, data):
+ self.append(("comment", data))
+
+ def handle_charref(self, data):
+ self.append(("charref", data))
+
+ def CharacterDataHandler(self, data):
+ self.append(("data", data))
+
+ def StartDoctypeDeclHandler(self, rootelem, publicId, systemId, subset):
+ self.append(("doctype", rootelem, systemId, publicId, subset))
+
+ def XmlDeclHandler(self, version, encoding, standalone):
+ self.append(("decl", version, encoding, standalone))
+
+ def ExternalEntityRefHandler(self, data):
+ self.append(("entityref", data))
+
+ def ProcessingInstructionHandler(self, target, data):
+ self.append(("pi", target, data))
+
+
+class EventCollectorExtra(EventCollector):
+
+ def handle_starttag(self, tag, attrs):
+ EventCollector.handle_starttag(self, tag, attrs)
+ self.append(("starttag_text", self.get_starttag_text()))
+
+
+class SegmentedFile(object):
+ def __init__(self, parts):
+ self.parts = list(parts)
+
+ def read(self, bytes):
+ if self.parts:
+ s = self.parts.pop(0)
+ else:
+ s = ''
+ return s
+
+
+class XMLParserTestCase(unittest.TestCase):
+
+ def _run_check(self, source, events, collector=EventCollector):
+ parser = collector()
+ if isinstance(source, list):
+ parser.parseStream(SegmentedFile(source))
+ else:
+ parser.parseString(source)
+ self.assertEquals(parser.get_events(),events)
+
+ def _run_check_extra(self, source, events):
+ self._run_check(source, events, EventCollectorExtra)
+
+ def _parse_error(self, source):
+ def parse(source=source):
+ parser = xmlparser.XMLParser()
+ parser.parseString(source)
+ self.assertRaises(xmlparser.XMLParseError, parse)
+
+ def test_processing_instruction_plus(self):
+ self._run_check("<?processing instruction?><a/>", [
+ ("pi", "processing", "instruction"),
+ ("starttag", "a", []),
+ ("endtag", "a"),
+ ])
+
+ def _check_simple_html(self):
+ self._run_check("""\
+<?xml version='1.0' encoding='iso-8859-1'?>
+<!DOCTYPE html PUBLIC 'foo' 'bar'>
+<html>&entity;&#32;
+<!--comment1a
+-></foo><bar>&lt;<?pi?></foo<bar
+comment1b-->
+<img src='Bar' ismap=''/>sample
+text
+<!--comment2a- -comment2b-->
+</html>
+""", [
+ ("decl", "1.0", "iso-8859-1", -1),
+ ("doctype", "html", "foo", "bar", 0),
+ ("starttag", "html", []),
+# ("entityref", "entity"),
+ ("data", " \n"),
+ ("comment", "comment1a\n-></foo><bar>&lt;<?pi?></foo<bar\ncomment1b"),
+ ("data", "\n"),
+ ("starttag", "img", ["src", "Bar", "ismap", ""]),
+ ("endtag", "img"),
+ ("data", "sample\ntext\n"),
+ ("comment", "comment2a- -comment2b"),
+ ("data", "\n"),
+ ("endtag", "html"),
+ ])
+
+ def test_bad_nesting(self):
+ try:
+ self._run_check("<a><b></a></b>", [
+ ("starttag", "a", []),
+ ("starttag", "b", []),
+ ("endtag", "a"),
+ ("endtag", "b"),
+ ])
+ except:
+ e = sys.exc_info()[1]
+ self.assert_(e.lineno == 1,
+ "did not receive correct position information")
+ else:
+ self.fail("expected parse error: bad nesting")
+
+ def test_attr_syntax(self):
+ output = [
+ ("starttag", "a", ["b", "v", "c", "v"]),
+ ("endtag", "a"),
+ ]
+ self._run_check("""<a b='v' c="v"/>""", output)
+ self._run_check("""<a b = 'v' c = "v"/>""", output)
+ self._run_check("""<a\nb\n=\n'v'\nc\n=\n"v"\n/>""", output)
+ self._run_check("""<a\tb\t=\t'v'\tc\t=\t"v"\t/>""", output)
+
+ def test_attr_values(self):
+ self._run_check("""<a b='xxx\n\txxx' c="yyy\t\nyyy" d='\txyz\n'/>""",
+ [("starttag", "a", ["b", "xxx xxx",
+ "c", "yyy yyy",
+ "d", " xyz "]),
+ ("endtag", "a"),
+ ])
+ self._run_check("""<a b='' c="" d=''/>""", [
+ ("starttag", "a", ["b", "", "c", "", "d", ""]),
+ ("endtag", "a"),
+ ])
+
+ def test_attr_entity_replacement(self):
+ self._run_check("""<a b='&amp;&gt;&lt;&quot;&apos;'/>""", [
+ ("starttag", "a", ["b", "&><\"'"]),
+ ("endtag", "a"),
+ ])
+
+ def test_attr_funky_names(self):
+ self._run_check("""<a a.b='v' e-f='v'/>""", [
+ ("starttag", "a", ["a.b", "v", "e-f", "v"]),
+ ("endtag", "a"),
+ ])
+
+ def test_starttag_end_boundary(self):
+ self._run_check("""<a b='&lt;'/>""", [
+ ("starttag", "a", ["b", "<"]),
+ ("endtag", "a"),
+ ])
+ self._run_check("""<a b='&gt;'/>""", [
+ ("starttag", "a", ["b", ">"]),
+ ("endtag", "a"),
+ ])
+
+ def test_buffer_artefacts(self):
+ output = [("starttag", "a", ["b", "<"]), ("endtag", "a")]
+ self._run_check(["<a b='&lt;'/>"], output)
+ self._run_check(["<a ", "b='&lt;'/>"], output)
+ self._run_check(["<a b", "='&lt;'/>"], output)
+ self._run_check(["<a b=", "'&lt;'/>"], output)
+ self._run_check(["<a b='&lt;", "'/>"], output)
+ self._run_check(["<a b='&lt;'", "/>"], output)
+
+ output = [("starttag", "a", ["b", ">"]), ("endtag", "a")]
+ self._run_check(["<a b='&gt;'/>"], output)
+ self._run_check(["<a ", "b='&gt;'/>"], output)
+ self._run_check(["<a b", "='&gt;'/>"], output)
+ self._run_check(["<a b=", "'&gt;'/>"], output)
+ self._run_check(["<a b='&gt;", "'/>"], output)
+ self._run_check(["<a b='&gt;'", "/>"], output)
+
+ def test_starttag_junk_chars(self):
+ self._parse_error("<")
+ self._parse_error("<>")
+ self._parse_error("</>")
+ self._parse_error("</$>")
+ self._parse_error("</")
+ self._parse_error("</a")
+ self._parse_error("</a")
+ self._parse_error("<a<a>")
+ self._parse_error("</a<a>")
+ self._parse_error("<$")
+ self._parse_error("<$>")
+ self._parse_error("<!")
+ self._parse_error("<a $>")
+ self._parse_error("<a")
+ self._parse_error("<a foo='bar'")
+ self._parse_error("<a foo='bar")
+ self._parse_error("<a foo='>'")
+ self._parse_error("<a foo='>")
+
+ def test_declaration_junk_chars(self):
+ self._parse_error("<!DOCTYPE foo $ >")
+
+ def test_unicode_string(self):
+ output = [('starttag', u'p', []),
+ ('data', u'\xe4\xf6\xfc\xdf'),
+ ('endtag', u'p')]
+ self._run_check(u'<p>\xe4\xf6\xfc\xdf</p>', output)
+
+
+# Support for the Zope regression test framework:
+def test_suite(skipxml=utils.skipxml):
+ if skipxml:
+ return unittest.TestSuite()
+ else:
+ return unittest.makeSuite(XMLParserTestCase)
+
+if __name__ == "__main__":
+ errs = utils.run_suite(test_suite(skipxml=0))
+ sys.exit(errs and 1 or 0)
diff --git a/src/zope/tal/tests/utils.py b/src/zope/tal/tests/utils.py
new file mode 100644
index 0000000..03eba52
--- /dev/null
+++ b/src/zope/tal/tests/utils.py
@@ -0,0 +1,65 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Helper functions for the test suite.
+
+$Id$
+"""
+import os
+import sys
+
+mydir = os.path.abspath(os.path.dirname(__file__))
+codedir = os.path.dirname(os.path.dirname(os.path.dirname(mydir)))
+
+if codedir not in sys.path:
+ sys.path.append(codedir)
+
+import unittest
+
+
+# Set skipxml to true if an XML parser could not be found.
+skipxml = 0
+try:
+ import xml.parsers.expat
+except ImportError:
+ skipxml = 1
+
+
+def run_suite(suite, outf=None, errf=None):
+ if outf is None:
+ outf = sys.stdout
+ runner = unittest.TextTestRunner(outf)
+ result = runner.run(suite)
+
+## print "\n\n"
+## if result.errors:
+## print "Errors (unexpected exceptions):"
+## map(print_error, result.errors)
+## print
+## if result.failures:
+## print "Failures (assertion failures):"
+## map(print_error, result.failures)
+## print
+ newerrs = len(result.errors) + len(result.failures)
+ if newerrs:
+ print "'Errors' indicate exceptions other than AssertionError."
+ print "'Failures' indicate AssertionError"
+ if errf is None:
+ errf = sys.stderr
+ errf.write("%d errors, %d failures\n"
+ % (len(result.errors), len(result.failures)))
+ return newerrs
+
+
+def print_error(info):
+ testcase, (type, e, tb) = info
diff --git a/src/zope/tal/timer.py b/src/zope/tal/timer.py
new file mode 100644
index 0000000..916a2e2
--- /dev/null
+++ b/src/zope/tal/timer.py
@@ -0,0 +1,58 @@
+#! /usr/bin/env python
+##############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Helper program to time compilation and interpretation
+
+$Id$
+"""
+import getopt
+import sys
+import time
+
+from cStringIO import StringIO
+
+from zope.tal.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 = apply(func, args)
+ t1 = time.clock()
+ sys.stderr.write("%6.3f secs for %d calls, i.e. %4.0f msecs per call\n"
+ % ((t1-t0), count, 1000*(t1-t0)/count))
+ return result
+
+if __name__ == "__main__":
+ main()
diff --git a/src/zope/tal/translationcontext.py b/src/zope/tal/translationcontext.py
new file mode 100644
index 0000000..3d870b9
--- /dev/null
+++ b/src/zope/tal/translationcontext.py
@@ -0,0 +1,40 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Translation context object for the TALInterpreter's I18N support.
+
+The translation context provides a container for the information
+needed to perform translation of a marked string from a page template.
+
+$Id$
+"""
+DEFAULT_DOMAIN = "default"
+
+class TranslationContext(object):
+ """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
diff --git a/src/zope/tal/xmlparser.py b/src/zope/tal/xmlparser.py
new file mode 100644
index 0000000..aafa693
--- /dev/null
+++ b/src/zope/tal/xmlparser.py
@@ -0,0 +1,105 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Generic Expat-based XML parser base class.
+
+This creates a parser with namespace processing enabled.
+
+$Id$
+"""
+import logging
+
+
+class XMLParser(object):
+
+ 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(encoding)
+ if self.ordered_attributes:
+ try:
+ self.parser.ordered_attributes = self.ordered_attributes
+ except AttributeError:
+ logging.warn("TAL.XMLParser: 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:
+ logging.error("TAL.XMLParser: Can't set "
+ "expat handler %s" % name)
+
+ def createParser(self, encoding=None):
+ global XMLParseError
+ from xml.parsers import expat
+ XMLParseError = expat.ExpatError
+ return expat.ParserCreate(encoding, ' ')
+
+ def parseFile(self, filename):
+ self.parseStream(open(filename))
+
+ def parseString(self, s):
+ if isinstance(s, unicode):
+ # Expat cannot deal with unicode strings, only with
+ # encoded ones. Also, its range of encodings is rather
+ # limited, UTF-8 is the safest bet here.
+ s = s.encode('utf-8')
+ 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)
+
+ def getpos(self):
+ # Apparently ErrorLineNumber and ErrorLineNumber contain the current
+ # position even when there was no error. This contradicts the official
+ # documentation[1], but expat.h[2] contains the following definition:
+ #
+ # /* For backwards compatibility with previous versions. */
+ # #define XML_GetErrorLineNumber XML_GetCurrentLineNumber
+ #
+ # [1] http://python.org/doc/current/lib/xmlparser-objects.html
+ # [2] http://cvs.sourceforge.net/viewcvs.py/expat/expat/lib/expat.h
+ return (self.parser.ErrorLineNumber, self.parser.ErrorColumnNumber)
+