summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--__init__.py2
-rw-r--r--benchmark/__init__.py2
-rw-r--r--benchmark/dtml01.html1
-rw-r--r--benchmark/dtml02.html100
-rw-r--r--benchmark/dtml03.html8
-rw-r--r--benchmark/dtml04.html6
-rw-r--r--benchmark/dtml05.html10
-rw-r--r--benchmark/dtml06.html14
-rw-r--r--benchmark/dtml07.html73
-rw-r--r--benchmark/dtml08.html73
-rw-r--r--benchmark/dtml09.html10
-rw-r--r--benchmark/dtml10.html102
-rw-r--r--benchmark/dtml11.html103
-rw-r--r--benchmark/dtml12.html12
-rw-r--r--benchmark/tal01.html1
-rw-r--r--benchmark/tal02.html100
-rw-r--r--benchmark/tal03.html8
-rw-r--r--benchmark/tal04.html6
-rw-r--r--benchmark/tal05.html10
-rw-r--r--benchmark/tal06.html14
-rw-r--r--benchmark/tal07.html73
-rw-r--r--benchmark/tal08.html73
-rw-r--r--benchmark/tal09.html10
-rw-r--r--benchmark/tal10.html102
-rw-r--r--benchmark/tal11.html103
-rw-r--r--benchmark/tal12.html12
-rw-r--r--changes.txt11
-rw-r--r--driver.py197
-rw-r--r--dummyengine.py241
-rw-r--r--history.txt72
-rw-r--r--htmltalparser.py309
-rw-r--r--interfaces.py147
-rw-r--r--ndiff.py647
-rw-r--r--readme.txt97
-rw-r--r--runtest.py152
-rw-r--r--setpath.py44
-rw-r--r--taldefs.py190
-rw-r--r--talgenerator.py785
-rw-r--r--talgettext.py100
-rw-r--r--talinterpreter.py732
-rw-r--r--talparser.py145
-rw-r--r--tests/__init__.py2
-rw-r--r--tests/input/__init__.py2
-rw-r--r--tests/input/test01.html56
-rw-r--r--tests/input/test01.xml57
-rw-r--r--tests/input/test02.html118
-rw-r--r--tests/input/test02.xml119
-rw-r--r--tests/input/test03.html9
-rw-r--r--tests/input/test03.xml10
-rw-r--r--tests/input/test04.html26
-rw-r--r--tests/input/test04.xml27
-rw-r--r--tests/input/test05.html9
-rw-r--r--tests/input/test05.xml10
-rw-r--r--tests/input/test06.html6
-rw-r--r--tests/input/test06.xml7
-rw-r--r--tests/input/test07.html11
-rw-r--r--tests/input/test07.xml12
-rw-r--r--tests/input/test08.html44
-rw-r--r--tests/input/test08.xml45
-rw-r--r--tests/input/test09.html30
-rw-r--r--tests/input/test09.xml30
-rw-r--r--tests/input/test10.html48
-rw-r--r--tests/input/test11.html14
-rw-r--r--tests/input/test11.xml14
-rw-r--r--tests/input/test12.html24
-rw-r--r--tests/input/test13.html7
-rw-r--r--tests/input/test14.html10
-rw-r--r--tests/input/test14.xml15
-rw-r--r--tests/input/test15.html26
-rw-r--r--tests/input/test16.html2
-rwxr-xr-xtests/input/test16.xml7
-rw-r--r--tests/input/test17.html6
-rw-r--r--tests/input/test17.xml10
-rw-r--r--tests/input/test18.html16
-rw-r--r--tests/input/test18.xml20
-rw-r--r--tests/input/test19.html5
-rw-r--r--tests/input/test20.html1
-rw-r--r--tests/input/test21.html4
-rw-r--r--tests/input/test22.html4
-rw-r--r--tests/input/test23.html2
-rw-r--r--tests/input/test24.html3
-rw-r--r--tests/input/test25.html1
-rw-r--r--tests/input/test26.html3
-rw-r--r--tests/input/test27.html5
-rw-r--r--tests/input/test28.html5
-rw-r--r--tests/input/test29.html4
-rw-r--r--tests/input/test30.html6
-rw-r--r--tests/input/test31.html7
-rw-r--r--tests/input/test32.html4
-rw-r--r--tests/input/test_metal1.html61
-rw-r--r--tests/input/test_metal2.html7
-rw-r--r--tests/input/test_metal3.html1
-rw-r--r--tests/markbench.py138
-rw-r--r--tests/output/__init__.py2
-rw-r--r--tests/output/test01.html68
-rw-r--r--tests/output/test01.xml65
-rw-r--r--tests/output/test02.html118
-rw-r--r--tests/output/test02.xml119
-rw-r--r--tests/output/test03.html9
-rw-r--r--tests/output/test03.xml10
-rw-r--r--tests/output/test04.html38
-rw-r--r--tests/output/test04.xml39
-rw-r--r--tests/output/test05.html9
-rw-r--r--tests/output/test05.xml10
-rw-r--r--tests/output/test06.html7
-rw-r--r--tests/output/test06.xml8
-rw-r--r--tests/output/test07.html11
-rw-r--r--tests/output/test07.xml12
-rw-r--r--tests/output/test08.html47
-rw-r--r--tests/output/test08.xml48
-rw-r--r--tests/output/test09.html30
-rw-r--r--tests/output/test09.xml30
-rw-r--r--tests/output/test10.html51
-rw-r--r--tests/output/test11.html5
-rw-r--r--tests/output/test11.xml5
-rw-r--r--tests/output/test12.html24
-rw-r--r--tests/output/test13.html7
-rw-r--r--tests/output/test14.html13
-rw-r--r--tests/output/test14.xml18
-rw-r--r--tests/output/test15.html29
-rw-r--r--tests/output/test16.html1
-rwxr-xr-xtests/output/test16.xml6
-rw-r--r--tests/output/test17.html6
-rw-r--r--tests/output/test17.xml9
-rw-r--r--tests/output/test18.html16
-rw-r--r--tests/output/test18.xml19
-rw-r--r--tests/output/test19.html3
-rw-r--r--tests/output/test20.html1
-rw-r--r--tests/output/test21.html1
-rw-r--r--tests/output/test22.html1
-rw-r--r--tests/output/test23.html1
-rw-r--r--tests/output/test24.html1
-rw-r--r--tests/output/test25.html1
-rw-r--r--tests/output/test26.html1
-rw-r--r--tests/output/test27.html1
-rw-r--r--tests/output/test28.html1
-rw-r--r--tests/output/test29.html2
-rw-r--r--tests/output/test30.html1
-rw-r--r--tests/output/test31.html1
-rw-r--r--tests/output/test32.html1
-rw-r--r--tests/output/test_metal1.html79
-rw-r--r--tests/output/test_metal2.html11
-rw-r--r--tests/output/test_metal3.html1
-rw-r--r--tests/run.py42
-rw-r--r--tests/test_files.py86
-rw-r--r--tests/test_htmltalparser.py850
-rw-r--r--tests/test_sourcepos.py93
-rw-r--r--tests/test_talinterpreter.py125
-rw-r--r--tests/test_xmlparser.py261
-rw-r--r--tests/utils.py63
-rw-r--r--timer.py59
-rw-r--r--translationcontext.py41
-rw-r--r--xmlparser.py85
153 files changed, 8709 insertions, 0 deletions
diff --git a/__init__.py b/__init__.py
new file mode 100644
index 0000000..b711d36
--- /dev/null
+++ b/__init__.py
@@ -0,0 +1,2 @@
+#
+# This file is necessary to make this directory a package.
diff --git a/benchmark/__init__.py b/benchmark/__init__.py
new file mode 100644
index 0000000..b711d36
--- /dev/null
+++ b/benchmark/__init__.py
@@ -0,0 +1,2 @@
+#
+# This file is necessary to make this directory a package.
diff --git a/benchmark/dtml01.html b/benchmark/dtml01.html
new file mode 100644
index 0000000..180b47c
--- /dev/null
+++ b/benchmark/dtml01.html
@@ -0,0 +1 @@
+baseline
diff --git a/benchmark/dtml02.html b/benchmark/dtml02.html
new file mode 100644
index 0000000..33d978d
--- /dev/null
+++ b/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/benchmark/dtml03.html b/benchmark/dtml03.html
new file mode 100644
index 0000000..aea01aa
--- /dev/null
+++ b/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/benchmark/dtml04.html b/benchmark/dtml04.html
new file mode 100644
index 0000000..1a3214f
--- /dev/null
+++ b/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/benchmark/dtml05.html b/benchmark/dtml05.html
new file mode 100644
index 0000000..70b53cb
--- /dev/null
+++ b/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/benchmark/dtml06.html b/benchmark/dtml06.html
new file mode 100644
index 0000000..11e5cf2
--- /dev/null
+++ b/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/benchmark/dtml07.html b/benchmark/dtml07.html
new file mode 100644
index 0000000..48f50c7
--- /dev/null
+++ b/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/benchmark/dtml08.html b/benchmark/dtml08.html
new file mode 100644
index 0000000..48f50c7
--- /dev/null
+++ b/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/benchmark/dtml09.html b/benchmark/dtml09.html
new file mode 100644
index 0000000..ce8e43e
--- /dev/null
+++ b/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/benchmark/dtml10.html b/benchmark/dtml10.html
new file mode 100644
index 0000000..3115f7c
--- /dev/null
+++ b/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/benchmark/dtml11.html b/benchmark/dtml11.html
new file mode 100644
index 0000000..b0f71bd
--- /dev/null
+++ b/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/benchmark/dtml12.html b/benchmark/dtml12.html
new file mode 100644
index 0000000..df2dab1
--- /dev/null
+++ b/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/benchmark/tal01.html b/benchmark/tal01.html
new file mode 100644
index 0000000..180b47c
--- /dev/null
+++ b/benchmark/tal01.html
@@ -0,0 +1 @@
+baseline
diff --git a/benchmark/tal02.html b/benchmark/tal02.html
new file mode 100644
index 0000000..33d978d
--- /dev/null
+++ b/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/benchmark/tal03.html b/benchmark/tal03.html
new file mode 100644
index 0000000..b63a737
--- /dev/null
+++ b/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/benchmark/tal04.html b/benchmark/tal04.html
new file mode 100644
index 0000000..42af6e8
--- /dev/null
+++ b/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/benchmark/tal05.html b/benchmark/tal05.html
new file mode 100644
index 0000000..6e2d626
--- /dev/null
+++ b/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/benchmark/tal06.html b/benchmark/tal06.html
new file mode 100644
index 0000000..6f40872
--- /dev/null
+++ b/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/benchmark/tal07.html b/benchmark/tal07.html
new file mode 100644
index 0000000..f331f05
--- /dev/null
+++ b/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/benchmark/tal08.html b/benchmark/tal08.html
new file mode 100644
index 0000000..f577fed
--- /dev/null
+++ b/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/benchmark/tal09.html b/benchmark/tal09.html
new file mode 100644
index 0000000..ef81c58
--- /dev/null
+++ b/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/benchmark/tal10.html b/benchmark/tal10.html
new file mode 100644
index 0000000..8026df7
--- /dev/null
+++ b/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/benchmark/tal11.html b/benchmark/tal11.html
new file mode 100644
index 0000000..d4a2440
--- /dev/null
+++ b/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/benchmark/tal12.html b/benchmark/tal12.html
new file mode 100644
index 0000000..dcd2c30
--- /dev/null
+++ b/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/changes.txt b/changes.txt
new file mode 100644
index 0000000..a657fcb
--- /dev/null
+++ b/changes.txt
@@ -0,0 +1,11 @@
+TAL changes
+
+ This file contains change information for the current release.
+ Change information for previous versions can be found in the
+ file HISTORY.txt.
+
+ Version 1.5.0
+
+ Features Added
+
+ - Line and column numbers are added to more exceptions.
diff --git a/driver.py b/driver.py
new file mode 100644
index 0000000..6e43238
--- /dev/null
+++ b/driver.py
@@ -0,0 +1,197 @@
+#!/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.0 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+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.
+"""
+
+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 DummyTranslationService
+
+FILE = "tests/input/test01.xml"
+
+class TestTranslations(DummyTranslationService):
+ def translate(self, domain, msgid, mapping=None, context=None,
+ target_language=None):
+ if msgid == 'timefmt':
+ return '%(minutes)s minutes after %(hours)s %(ampm)s' % mapping
+ elif msgid == 'jobnum':
+ return '%(jobnum)s is the JOB NUMBER' % mapping
+ elif msgid == 'verify':
+ s = 'Your contact email address is recorded as %(email)s'
+ return s % mapping
+ elif msgid == 'mailto:${request/submitter}':
+ return 'mailto:bperson@dom.ain'
+ elif msgid == 'origin':
+ return '%(name)s was born in %(country)s' % mapping
+ return DummyTranslationService.translate(self, domain, msgid,
+ mapping, context,
+ target_language)
+
+class TestEngine(DummyEngine):
+ def __init__(self, macros=None):
+ DummyEngine.__init__(self, macros)
+ self.translationService = TestTranslations()
+
+ def evaluatePathOrVar(self, expr):
+ if expr == 'here/currentTime':
+ return {'hours' : 6,
+ 'minutes': 59,
+ 'ampm' : 'PM',
+ }
+ elif expr == 'context/@@object_name':
+ return '7'
+ elif expr == 'request/submitter':
+ return 'aperson@dom.ain'
+ return DummyEngine.evaluatePathOrVar(self, expr)
+
+
+# This is a disgusting hack so that we can use engines that actually know
+# something about certain object paths. TimeEngine knows about
+# here/currentTime.
+ENGINES = {'test23.html': TestEngine,
+ 'test24.html': TestEngine,
+ 'test26.html': TestEngine,
+ 'test27.html': TestEngine,
+ 'test28.html': TestEngine,
+ 'test29.html': TestEngine,
+ 'test30.html': TestEngine,
+ 'test31.html': TestEngine,
+ 'test32.html': TestEngine,
+ }
+
+def usage(code, msg=''):
+ # Python 2.1 required
+ print >> sys.stderr, __doc__
+ if msg:
+ print >> sys.stderr, msg
+ sys.exit(code)
+
+def main():
+ macros = 0
+ mode = None
+ showcode = 0
+ showtal = -1
+ strictinsert = 1
+ i18nInterpolate = 1
+ try:
+ opts, args = getopt.getopt(sys.argv[1:], "hHxlmsti",
+ ['help', 'html', 'xml'])
+ except getopt.error, msg:
+ usage(2, msg)
+ for opt, arg in opts:
+ if opt in ('-h', '--help'):
+ usage(0)
+ if opt in ('-H', '--html'):
+ if mode == 'xml':
+ usage(1, '--html and --xml are mutually exclusive')
+ mode = "html"
+ if opt == '-l':
+ strictinsert = 0
+ if opt == '-m':
+ macros = 1
+ if opt == '-n':
+ versionTest = 0
+ if opt in ('-x', '--xml'):
+ if mode == 'html':
+ usage(1, '--html and --xml are mutually exclusive')
+ mode = "xml"
+ if opt == '-s':
+ showcode = 1
+ if opt == '-t':
+ showtal = 1
+ if opt == '-i':
+ i18nInterpolate = 0
+ if args:
+ file = args[0]
+ else:
+ file = FILE
+ it = compilefile(file, mode)
+ if showcode:
+ showit(it)
+ else:
+ # See if we need a special engine for this test
+ engine = None
+ engineClass = ENGINES.get(os.path.basename(file))
+ if engineClass is not None:
+ engine = engineClass(macros)
+ interpretit(it, engine=engine,
+ tal=(not macros), showtal=showtal,
+ strictinsert=strictinsert,
+ i18nInterpolate=i18nInterpolate)
+
+def interpretit(it, engine=None, stream=None, tal=1, showtal=-1,
+ strictinsert=1, i18nInterpolate=1):
+ from 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)()
+
+def compilefile(file, mode=None):
+ assert mode in ("html", "xml", None)
+ if mode is None:
+ ext = os.path.splitext(file)[1]
+ if ext.lower() in (".html", ".htm"):
+ mode = "html"
+ else:
+ mode = "xml"
+ if mode == "html":
+ from zope.tal.htmltalparser import HTMLTALParser
+ p = HTMLTALParser()
+ else:
+ from zope.tal.talparser import TALParser
+ p = TALParser()
+ p.parseFile(file)
+ return p.getCode()
+
+def showit(it):
+ from pprint import pprint
+ pprint(it)
+
+if __name__ == "__main__":
+ main()
diff --git a/dummyengine.py b/dummyengine.py
new file mode 100644
index 0000000..67ff07b
--- /dev/null
+++ b/dummyengine.py
@@ -0,0 +1,241 @@
+##############################################################################
+#
+# 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.0 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+Dummy TALES engine so that I can test out the TAL implementation.
+"""
+
+import re
+import sys
+
+from zope.tal.taldefs import NAME_RE, TALESError, ErrorInfo
+from zope.tal.interfaces import ITALESCompiler, ITALESEngine
+from zope.interfaces.i18n import ITranslationService
+from zope.interfaces.i18n import IDomain
+
+Default = object()
+
+name_match = re.compile(r"(?s)(%s):(.*)\Z" % NAME_RE).match
+
+class CompilerError(Exception):
+ pass
+
+class DummyEngine:
+
+ position = None
+ source_file = None
+
+ __implements__ = ITALESCompiler, ITALESEngine
+
+ def __init__(self, macros=None):
+ if macros is None:
+ macros = {}
+ self.macros = macros
+ dict = {'nothing': None, 'default': Default}
+ self.locals = self.globals = dict
+ self.stack = [dict]
+ self.translationService = DummyTranslationService()
+
+ def getCompilerError(self):
+ return CompilerError
+
+ def setSourceFile(self, source_file):
+ self.source_file = source_file
+
+ def setPosition(self, position):
+ self.position = position
+
+ def compile(self, expr):
+ return "$%s$" % expr
+
+ def uncompile(self, expression):
+ assert (expression.startswith("$") and expression.endswith("$"),
+ expression)
+ return expression[1:-1]
+
+ def beginScope(self):
+ self.stack.append(self.locals)
+
+ def endScope(self):
+ assert len(self.stack) > 1, "more endScope() than beginScope() calls"
+ self.locals = self.stack.pop()
+
+ def setLocal(self, name, value):
+ if self.locals is self.stack[-1]:
+ # Unmerge this scope's locals from previous scope of first set
+ self.locals = self.locals.copy()
+ self.locals[name] = value
+
+ def setGlobal(self, name, value):
+ self.globals[name] = value
+
+ def evaluate(self, expression):
+ assert (expression.startswith("$") and expression.endswith("$"),
+ expression)
+ expression = expression[1:-1]
+ m = name_match(expression)
+ if m:
+ type, expr = m.group(1, 2)
+ else:
+ type = "path"
+ expr = expression
+ if type in ("string", "str"):
+ return expr
+ if type in ("path", "var", "global", "local"):
+ return self.evaluatePathOrVar(expr)
+ if type == "not":
+ return not self.evaluate(expr)
+ if type == "exists":
+ return self.locals.has_key(expr) or self.globals.has_key(expr)
+ if type == "python":
+ try:
+ return eval(expr, self.globals, self.locals)
+ except:
+ raise TALESError("evaluation error in %s" % `expr`)
+ if type == "position":
+ # Insert the current source file name, line number,
+ # and column offset.
+ if self.position:
+ lineno, offset = self.position
+ else:
+ lineno, offset = None, None
+ return '%s (%s,%s)' % (self.source_file, lineno, offset)
+ raise TALESError("unrecognized expression: " + `expression`)
+
+ def evaluatePathOrVar(self, expr):
+ expr = expr.strip()
+ if self.locals.has_key(expr):
+ return self.locals[expr]
+ elif self.globals.has_key(expr):
+ return self.globals[expr]
+ else:
+ raise TALESError("unknown variable: %s" % `expr`)
+
+ def evaluateValue(self, expr):
+ return self.evaluate(expr)
+
+ def evaluateBoolean(self, expr):
+ return self.evaluate(expr)
+
+ def evaluateText(self, expr):
+ text = self.evaluate(expr)
+ if text is not None and text is not Default:
+ text = str(text)
+ return text
+
+ def evaluateStructure(self, expr):
+ # XXX Should return None or a DOM tree
+ return self.evaluate(expr)
+
+ def evaluateSequence(self, expr):
+ # XXX Should return a sequence
+ return self.evaluate(expr)
+
+ def evaluateMacro(self, macroName):
+ assert (macroName.startswith("$") and macroName.endswith("$"),
+ macroName)
+ macroName = macroName[1:-1]
+ file, localName = self.findMacroFile(macroName)
+ if not file:
+ # Local macro
+ macro = self.macros[localName]
+ else:
+ # External macro
+ import driver
+ program, macros = driver.compilefile(file)
+ macro = macros.get(localName)
+ if not macro:
+ raise TALESError("macro %s not found in file %s" %
+ (localName, file))
+ return macro
+
+ def findMacroDocument(self, macroName):
+ file, localName = self.findMacroFile(macroName)
+ if not file:
+ return file, localName
+ import driver
+ doc = driver.parsefile(file)
+ return doc, localName
+
+ def findMacroFile(self, macroName):
+ if not macroName:
+ raise TALESError("empty macro name")
+ i = macroName.rfind('/')
+ if i < 0:
+ # No slash -- must be a locally defined macro
+ return None, macroName
+ else:
+ # Up to last slash is the filename
+ fileName = macroName[:i]
+ localName = macroName[i+1:]
+ return fileName, localName
+
+ def setRepeat(self, name, expr):
+ seq = self.evaluateSequence(expr)
+ return Iterator(name, seq, self)
+
+ def createErrorInfo(self, err, position):
+ return ErrorInfo(err, position)
+
+ def getDefault(self):
+ return Default
+
+ def translate(self, domain, msgid, mapping):
+ return self.translationService.translate(domain, msgid, mapping)
+
+
+class Iterator:
+
+ def __init__(self, name, seq, engine):
+ self.name = name
+ self.seq = seq
+ self.engine = engine
+ self.nextIndex = 0
+
+ def next(self):
+ i = self.nextIndex
+ try:
+ item = self.seq[i]
+ except IndexError:
+ return 0
+ self.nextIndex = i+1
+ self.engine.setLocal(self.name, item)
+ return 1
+
+class DummyDomain:
+ __implements__ = IDomain
+
+ def translate(self, msgid, mapping=None, context=None,
+ target_language=None):
+ # This is a fake translation service which simply uppercases non
+ # ${name} placeholder text in the message id.
+ #
+ # First, transform a string with ${name} placeholders into a list of
+ # substrings. Then upcase everything but the placeholders, then glue
+ # things back together.
+ def repl(m):
+ return mapping[m.group(m.lastindex).lower()]
+ cre = re.compile(r'\$(?:([_A-Z]\w*)|\{([_A-Z]\w*)\})')
+ return cre.sub(repl, msgid.upper())
+
+class DummyTranslationService:
+ __implements__ = ITranslationService
+
+ def translate(self, domain, msgid, mapping=None, context=None,
+ target_language=None):
+ # Ignore domain
+ return self.getDomain(domain).translate(msgid, mapping, context,
+ target_language)
+
+ def getDomain(self, domain):
+ return DummyDomain()
diff --git a/history.txt b/history.txt
new file mode 100644
index 0000000..48132fd
--- /dev/null
+++ b/history.txt
@@ -0,0 +1,72 @@
+TAL history
+
+ This file contains change information for previous versions.
+ Change information for the current release can be found
+ in the file CHANGES.txt.
+
+ Version 1.4.0
+
+ Features Added
+
+ - Added TAL statement: omit_tag="[<boolean expr>]" replaces
+ the statement tag with its contents if the boolean
+ expression is true or omitted.
+
+ - The TAL and METAL namespaces can be applied to tag names,
+ tags in these namespaces are removed from rendered output
+ (leaving the contents in place, as with omit_tag)
+ whenever attributes in these namespaces would be, and
+ tag attributes without explicit namespaces default to the
+ tag's namespace (per XML spec).
+
+ Version 1.3.3
+
+ Bugs Fixed
+
+ - tal:atributes was creating stray attributes in METAL
+ expansion, and there was no unit test for this behavior.
+
+ - tal:attributes parsing was not catching badly malformed
+ values, and used "print" instead of raising exceptions.
+
+ Version 1.3.2
+
+ Features Added
+
+ - Adopted Zope-style CHANGES.txt and HISTORY.txt
+ - Improved execution performance
+ - Added simple ZPT vs. TAL vs. DTML benchmarks, run by markbench.py
+
+ Version 1.3.0
+
+ Features Added
+
+ - New builtin variable 'attrs'.
+
+ Bug Fixed
+
+ - Nested macros were not working correctly.
+
+ Version 1.2.0
+
+ Features Added
+
+ - The 'if' path modifier can cancel any TAL action.
+
+ Bug Fixed
+
+ - tal:attributes inserted empty attributes into source.
+
+ Version 1.1.0
+
+ Features Added
+ - TAL does not try to parse replacement structural text.
+ - Changed tests to match TAL's omitted attributes.
+
+ Version 1.0.0
+
+ - Various minor bugs fixed
+
+ Version 1.0.0b1
+
+ - All functionality described in the Project Wiki is implemented
diff --git a/htmltalparser.py b/htmltalparser.py
new file mode 100644
index 0000000..7ddd762
--- /dev/null
+++ b/htmltalparser.py
@@ -0,0 +1,309 @@
+##############################################################################
+#
+# 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.0 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+Parse HTML and compile to TALInterpreter intermediate code.
+"""
+
+import sys
+
+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 = [
+ # List of Boolean attributes in HTML that may be given in
+ # minimized form (e.g. <img ismap> rather than <img ismap="">)
+ # From http://www.w3.org/TR/xhtml1/#guidelines (C.10)
+ "compact", "nowrap", "ismap", "declare", "noshade", "checked",
+ "disabled", "readonly", "multiple", "selected", "noresize",
+ "defer"
+ ]
+
+EMPTY_HTML_TAGS = [
+ # List of HTML tags with an empty content model; these are
+ # rendered in minimized form, e.g. <img />.
+ # From http://www.w3.org/TR/xhtml1/#dtds
+ "base", "meta", "link", "hr", "br", "param", "img", "area",
+ "input", "col", "basefont", "isindex", "frame",
+ ]
+
+PARA_LEVEL_HTML_TAGS = [
+ # List of HTML elements that close open paragraph-level elements
+ # and are themselves paragraph-level.
+ "h1", "h2", "h3", "h4", "h5", "h6", "p",
+ ]
+
+BLOCK_CLOSING_TAG_MAP = {
+ "tr": ("tr", "td", "th"),
+ "td": ("td", "th"),
+ "th": ("td", "th"),
+ "li": ("li",),
+ "dd": ("dd", "dt"),
+ "dt": ("dd", "dt"),
+ }
+
+BLOCK_LEVEL_HTML_TAGS = [
+ # List of HTML tags that denote larger sections than paragraphs.
+ "blockquote", "table", "tr", "th", "td", "thead", "tfoot", "tbody",
+ "noframe", "ul", "ol", "li", "dl", "dt", "dd", "div",
+ ]
+
+TIGHTEN_IMPLICIT_CLOSE_TAGS = (PARA_LEVEL_HTML_TAGS
+ + BLOCK_CLOSING_TAG_MAP.keys())
+
+
+class NestingError(HTMLParseError):
+ """Exception raised when elements aren't properly nested."""
+
+ def __init__(self, tagstack, endtag, position=(None, None)):
+ self.endtag = endtag
+ if tagstack:
+ if len(tagstack) == 1:
+ msg = ('Open tag <%s> does not match close tag </%s>'
+ % (tagstack[0], endtag))
+ else:
+ msg = ('Open tags <%s> do not match close tag </%s>'
+ % ('>, <'.join(tagstack), endtag))
+ else:
+ msg = 'No tags are open to match </%s>' % endtag
+ HTMLParseError.__init__(self, msg, position)
+
+class EmptyTagError(NestingError):
+ """Exception raised when empty elements have an end tag."""
+
+ def __init__(self, tag, position=(None, None)):
+ self.tag = tag
+ msg = 'Close tag </%s> should be removed' % tag
+ HTMLParseError.__init__(self, msg, position)
+
+class OpenTagError(NestingError):
+ """Exception raised when a tag is not allowed in another tag."""
+
+ def __init__(self, tagstack, tag, position=(None, None)):
+ self.tag = tag
+ msg = 'Tag <%s> is not allowed in <%s>' % (tag, tagstack[-1])
+ HTMLParseError.__init__(self, msg, position)
+
+class HTMLTALParser(HTMLParser):
+
+ # External API
+
+ def __init__(self, gen=None):
+ HTMLParser.__init__(self)
+ if gen is None:
+ gen = TALGenerator(xml=0)
+ self.gen = gen
+ self.tagstack = []
+ self.nsstack = []
+ self.nsdict = {'tal': ZOPE_TAL_NS,
+ 'metal': ZOPE_METAL_NS,
+ 'i18n': ZOPE_I18N_NS,
+ }
+
+ def parseFile(self, file):
+ f = open(file)
+ data = f.read()
+ f.close()
+ try:
+ self.parseString(data)
+ except TALError, e:
+ e.setFile(file)
+ raise
+
+ def parseString(self, data):
+ self.feed(data)
+ self.close()
+ while self.tagstack:
+ self.implied_endtag(self.tagstack[-1], 2)
+ assert self.nsstack == [], self.nsstack
+
+ def getCode(self):
+ return self.gen.getCode()
+
+ def getWarnings(self):
+ return ()
+
+ # Overriding HTMLParser methods
+
+ def handle_starttag(self, tag, attrs):
+ self.close_para_tags(tag)
+ self.scan_xmlns(attrs)
+ tag, attrlist, taldict, metaldict, i18ndict \
+ = self.process_ns(tag, attrs)
+ 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"):
+ self.gen.emitStartElement(tag, attrlist, taldict, metaldict,
+ i18ndict, self.getpos())
+ self.gen.emitEndElement(tag, implied=-1)
+ else:
+ self.gen.emitStartElement(tag, attrlist, taldict, metaldict,
+ i18ndict, self.getpos(), isend=1)
+ self.pop_xmlns()
+
+ def handle_endtag(self, tag):
+ if tag in EMPTY_HTML_TAGS:
+ # </img> etc. in the source is an error
+ raise EmptyTagError(tag, self.getpos())
+ self.close_enclosed_tags(tag)
+ self.gen.emitEndElement(tag)
+ self.pop_xmlns()
+ self.tagstack.pop()
+
+ def close_para_tags(self, tag):
+ if tag in EMPTY_HTML_TAGS:
+ return
+ close_to = -1
+ if BLOCK_CLOSING_TAG_MAP.has_key(tag):
+ blocks_to_close = BLOCK_CLOSING_TAG_MAP[tag]
+ for i in range(len(self.tagstack)):
+ t = self.tagstack[i]
+ if t in blocks_to_close:
+ if close_to == -1:
+ close_to = i
+ elif t in BLOCK_LEVEL_HTML_TAGS:
+ close_to = -1
+ elif tag in PARA_LEVEL_HTML_TAGS + BLOCK_LEVEL_HTML_TAGS:
+ i = len(self.tagstack) - 1
+ while i >= 0:
+ closetag = self.tagstack[i]
+ if closetag in BLOCK_LEVEL_HTML_TAGS:
+ break
+ if closetag in PARA_LEVEL_HTML_TAGS:
+ if closetag != "p":
+ raise OpenTagError(self.tagstack, tag, self.getpos())
+ close_to = i
+ i = i - 1
+ if close_to >= 0:
+ while len(self.tagstack) > close_to:
+ self.implied_endtag(self.tagstack[-1], 1)
+
+ def close_enclosed_tags(self, tag):
+ if tag not in self.tagstack:
+ raise NestingError(self.tagstack, tag, self.getpos())
+ while tag != self.tagstack[-1]:
+ self.implied_endtag(self.tagstack[-1], 1)
+ assert self.tagstack[-1] == tag
+
+ def implied_endtag(self, tag, implied):
+ assert tag == self.tagstack[-1]
+ assert implied in (-1, 1, 2)
+ isend = (implied < 0)
+ if tag in TIGHTEN_IMPLICIT_CLOSE_TAGS:
+ # Pick out trailing whitespace from the program, and
+ # insert the close tag before the whitespace.
+ white = self.gen.unEmitWhitespace()
+ else:
+ white = None
+ self.gen.emitEndElement(tag, isend=isend, implied=implied)
+ if white:
+ self.gen.emitRawText(white)
+ self.tagstack.pop()
+ self.pop_xmlns()
+
+ def handle_charref(self, name):
+ self.gen.emitRawText("&#%s;" % name)
+
+ def handle_entityref(self, name):
+ self.gen.emitRawText("&%s;" % name)
+
+ def handle_data(self, data):
+ self.gen.emitRawText(data)
+
+ def handle_comment(self, data):
+ self.gen.emitRawText("<!--%s-->" % data)
+
+ def handle_decl(self, data):
+ self.gen.emitRawText("<!%s>" % data)
+
+ def handle_pi(self, data):
+ self.gen.emitRawText("<?%s>" % data)
+
+ # Internal thingies
+
+ def scan_xmlns(self, attrs):
+ nsnew = {}
+ for key, value in attrs:
+ if key.startswith("xmlns:"):
+ nsnew[key[6:]] = value
+ if nsnew:
+ self.nsstack.append(self.nsdict)
+ self.nsdict = self.nsdict.copy()
+ self.nsdict.update(nsnew)
+ else:
+ self.nsstack.append(self.nsdict)
+
+ def pop_xmlns(self):
+ self.nsdict = self.nsstack.pop()
+
+ def fixname(self, name):
+ if ':' in name:
+ prefix, suffix = name.split(':', 1)
+ if prefix == 'xmlns':
+ nsuri = self.nsdict.get(suffix)
+ if nsuri in (ZOPE_TAL_NS, ZOPE_METAL_NS, ZOPE_I18N_NS):
+ return name, name, prefix
+ else:
+ nsuri = self.nsdict.get(prefix)
+ if nsuri == ZOPE_TAL_NS:
+ return name, suffix, 'tal'
+ elif nsuri == ZOPE_METAL_NS:
+ return name, suffix, 'metal'
+ elif nsuri == ZOPE_I18N_NS:
+ return name, suffix, 'i18n'
+ return name, name, 0
+
+ def process_ns(self, name, attrs):
+ attrlist = []
+ taldict = {}
+ metaldict = {}
+ i18ndict = {}
+ name, namebase, namens = self.fixname(name)
+ for item in attrs:
+ key, value = item
+ key, keybase, keyns = self.fixname(key)
+ ns = keyns or namens # default to tag namespace
+ if ns and ns != 'unknown':
+ item = (key, value, ns)
+ if ns == 'tal':
+ if taldict.has_key(keybase):
+ raise TALError("duplicate TAL attribute " +
+ `keybase`, self.getpos())
+ taldict[keybase] = value
+ elif ns == 'metal':
+ if metaldict.has_key(keybase):
+ raise METALError("duplicate METAL attribute " +
+ `keybase`, self.getpos())
+ metaldict[keybase] = value
+ elif ns == 'i18n':
+ if i18ndict.has_key(keybase):
+ raise I18NError("duplicate i18n attribute " +
+ `keybase`, self.getpos())
+ i18ndict[keybase] = value
+ attrlist.append(item)
+ if namens in ('metal', 'tal'):
+ taldict['tal tag'] = namens
+ return name, attrlist, taldict, metaldict, i18ndict
diff --git a/interfaces.py b/interfaces.py
new file mode 100644
index 0000000..04202c6
--- /dev/null
+++ b/interfaces.py
@@ -0,0 +1,147 @@
+"""Interface that a TALES engine provides to the METAL/TAL implementation."""
+
+from zope.interface import Attribute, Interface
+
+
+class ITALESCompiler(Interface):
+ """Compile-time interface provided by a TALES implementation.
+
+ The TAL compiler needs an instance of this interface to support
+ compilation of TALES expressions embedded in documents containing
+ TAL and METAL constructs.
+ """
+
+ def getCompilerError():
+ """Return the exception class raised for compilation errors.
+ """
+
+ def compile(expression):
+ """Return a compiled form of 'expression' for later evaluation.
+
+ 'expression' is the source text of the expression.
+
+ The return value may be passed to the various evaluate*()
+ methods of the ITALESEngine interface. No compatibility is
+ required for the values of the compiled expression between
+ different ITALESEngine implementations.
+ """
+
+
+class ITALESEngine(Interface):
+ """Render-time interface provided by a TALES implementation.
+
+ The TAL interpreter uses this interface to TALES to support
+ evaluation of the compiled expressions returned by
+ ITALESCompiler.compile().
+ """
+
+ def getDefault():
+ """Return the value of the 'default' TALES expression.
+
+ Checking a value for a match with 'default' should be done
+ using the 'is' operator in Python.
+ """
+
+ def setPosition((lineno, offset)):
+ """Inform the engine of the current position in the source file.
+
+ This is used to allow the evaluation engine to report
+ execution errors so that site developers can more easily
+ locate the offending expression.
+ """
+
+ def setSourceFile(filename):
+ """Inform the engine of the name of the current source file.
+
+ This is used to allow the evaluation engine to report
+ execution errors so that site developers can more easily
+ locate the offending expression.
+ """
+
+ def beginScope():
+ """Push a new scope onto the stack of open scopes.
+ """
+
+ def endScope():
+ """Pop one scope from the stack of open scopes.
+ """
+
+ def evaluate(compiled_expression):
+ """Evaluate an arbitrary expression.
+
+ No constraints are imposed on the return value.
+ """
+
+ def evaluateBoolean(compiled_expression):
+ """Evaluate an expression that must return a Boolean value.
+ """
+
+ def evaluateMacro(compiled_expression):
+ """Evaluate an expression that must return a macro program.
+ """
+
+ def evaluateStructure(compiled_expression):
+ """Evaluate an expression that must return a structured
+ document fragment.
+
+ The result of evaluating 'compiled_expression' must be a
+ string containing a parsable HTML or XML fragment. Any TAL
+ markup cnotained in the result string will be interpreted.
+ """
+
+ def evaluateText(compiled_expression):
+ """Evaluate an expression that must return text.
+
+ The returned text should be suitable for direct inclusion in
+ the output: any HTML or XML escaping or quoting is the
+ responsibility of the expression itself.
+ """
+
+ def evaluateValue(compiled_expression):
+ """Evaluate an arbitrary expression.
+
+ No constraints are imposed on the return value.
+ """
+
+ def createErrorInfo(exception, (lineno, offset)):
+ """Returns an ITALESErrorInfo object.
+
+ The returned object is used to provide information about the
+ error condition for the on-error handler.
+ """
+
+ def setGlobal(name, value):
+ """Set a global variable.
+
+ The variable will be named 'name' and have the value 'value'.
+ """
+
+ def setLocal(name, value):
+ """Set a local variable in the current scope.
+
+ The variable will be named 'name' and have the value 'value'.
+ """
+
+ def setRepeat(name, compiled_expression):
+ """
+ """
+
+ def translate(domain, msgid, mapping):
+ """
+ See ITranslationService.translate()
+ """
+
+
+class ITALESErrorInfo(Interface):
+
+ type = Attribute("type",
+ "The exception class.")
+
+ value = Attribute("value",
+ "The exception instance.")
+
+ lineno = Attribute("lineno",
+ "The line number the error occurred on in the source.")
+
+ offset = Attribute("offset",
+ "The character offset at which the error occurred.")
diff --git a/ndiff.py b/ndiff.py
new file mode 100644
index 0000000..b986911
--- /dev/null
+++ b/ndiff.py
@@ -0,0 +1,647 @@
+#! /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.0 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+
+# Module ndiff version 1.6.0
+# Released to the public domain 08-Dec-2000,
+# by Tim Peters (tim.one@home.com).
+
+# Provided as-is; use at your own risk; no warranty; no promises; enjoy!
+
+"""ndiff [-q] file1 file2
+ or
+ndiff (-r1 | -r2) < ndiff_output > file1_or_file2
+
+Print a human-friendly file difference report to stdout. Both inter-
+and intra-line differences are noted. In the second form, recreate file1
+(-r1) or file2 (-r2) on stdout, from an ndiff report on stdin.
+
+In the first form, if -q ("quiet") is not specified, the first two lines
+of output are
+
+-: file1
++: file2
+
+Each remaining line begins with a two-letter code:
+
+ "- " line unique to file1
+ "+ " line unique to file2
+ " " line common to both files
+ "? " line not present in either input file
+
+Lines beginning with "? " attempt to guide the eye to intraline
+differences, and were not present in either input file. These lines can be
+confusing if the source files contain tab characters.
+
+The first file can be recovered by retaining only lines that begin with
+" " or "- ", and deleting those 2-character prefixes; use ndiff with -r1.
+
+The second file can be recovered similarly, but by retaining only " " and
+"+ " lines; use ndiff with -r2; or, on Unix, the second file can be
+recovered by piping the output through
+
+ sed -n '/^[+ ] /s/^..//p'
+
+See module comments for details and programmatic interface.
+"""
+
+__version__ = 1, 5, 0
+
+# SequenceMatcher tries to compute a "human-friendly diff" between
+# two sequences (chiefly picturing a file as a sequence of lines,
+# and a line as a sequence of characters, here). Unlike e.g. UNIX(tm)
+# diff, the fundamental notion is the longest *contiguous* & junk-free
+# matching subsequence. That's what catches peoples' eyes. The
+# Windows(tm) windiff has another interesting notion, pairing up elements
+# that appear uniquely in each sequence. That, and the method here,
+# appear to yield more intuitive difference reports than does diff. This
+# method appears to be the least vulnerable to synching up on blocks
+# of "junk lines", though (like blank lines in ordinary text files,
+# or maybe "<P>" lines in HTML files). That may be because this is
+# the only method of the 3 that has a *concept* of "junk" <wink>.
+#
+# Note that ndiff makes no claim to produce a *minimal* diff. To the
+# contrary, minimal diffs are often counter-intuitive, because they
+# synch up anywhere possible, sometimes accidental matches 100 pages
+# apart. Restricting synch points to contiguous matches preserves some
+# notion of locality, at the occasional cost of producing a longer diff.
+#
+# With respect to junk, an earlier version of ndiff simply refused to
+# *start* a match with a junk element. The result was cases like this:
+# before: private Thread currentThread;
+# after: private volatile Thread currentThread;
+# If you consider whitespace to be junk, the longest contiguous match
+# not starting with junk is "e Thread currentThread". So ndiff reported
+# that "e volatil" was inserted between the 't' and the 'e' in "private".
+# While an accurate view, to people that's absurd. The current version
+# looks for matching blocks that are entirely junk-free, then extends the
+# longest one of those as far as possible but only with matching junk.
+# So now "currentThread" is matched, then extended to suck up the
+# preceding blank; then "private" is matched, and extended to suck up the
+# following blank; then "Thread" is matched; and finally ndiff reports
+# that "volatile " was inserted before "Thread". The only quibble
+# remaining is that perhaps it was really the case that " volatile"
+# was inserted after "private". I can live with that <wink>.
+#
+# NOTE on junk: the module-level names
+# IS_LINE_JUNK
+# IS_CHARACTER_JUNK
+# can be set to any functions you like. The first one should accept
+# a single string argument, and return true iff the string is junk.
+# The default is whether the regexp r"\s*#?\s*$" matches (i.e., a
+# line without visible characters, except for at most one splat).
+# The second should accept a string of length 1 etc. The default is
+# whether the character is a blank or tab (note: bad idea to include
+# newline in this!).
+#
+# After setting those, you can call fcompare(f1name, f2name) with the
+# names of the files you want to compare. The difference report
+# is sent to stdout. Or you can call main(args), passing what would
+# have been in sys.argv[1:] had the cmd-line form been used.
+
+TRACE = 0
+
+# define what "junk" means
+import re
+
+def IS_LINE_JUNK(line, pat=re.compile(r"\s*#?\s*$").match):
+ return pat(line) is not None
+
+def IS_CHARACTER_JUNK(ch, ws=" \t"):
+ return ch in ws
+
+del re
+
+class SequenceMatcher:
+ def __init__(self, isjunk=None, a='', b=''):
+ # Members:
+ # a
+ # first sequence
+ # b
+ # second sequence; differences are computed as "what do
+ # we need to do to 'a' to change it into 'b'?"
+ # b2j
+ # for x in b, b2j[x] is a list of the indices (into b)
+ # at which x appears; junk elements do not appear
+ # b2jhas
+ # b2j.has_key
+ # fullbcount
+ # for x in b, fullbcount[x] == the number of times x
+ # appears in b; only materialized if really needed (used
+ # only for computing quick_ratio())
+ # matching_blocks
+ # a list of (i, j, k) triples, where a[i:i+k] == b[j:j+k];
+ # ascending & non-overlapping in i and in j; terminated by
+ # a dummy (len(a), len(b), 0) sentinel
+ # opcodes
+ # a list of (tag, i1, i2, j1, j2) tuples, where tag is
+ # one of
+ # 'replace' a[i1:i2] should be replaced by b[j1:j2]
+ # 'delete' a[i1:i2] should be deleted
+ # 'insert' b[j1:j2] should be inserted
+ # 'equal' a[i1:i2] == b[j1:j2]
+ # isjunk
+ # a user-supplied function taking a sequence element and
+ # returning true iff the element is "junk" -- this has
+ # subtle but helpful effects on the algorithm, which I'll
+ # get around to writing up someday <0.9 wink>.
+ # DON'T USE! Only __chain_b uses this. Use isbjunk.
+ # isbjunk
+ # for x in b, isbjunk(x) == isjunk(x) but much faster;
+ # it's really the has_key method of a hidden dict.
+ # DOES NOT WORK for x in a!
+
+ self.isjunk = isjunk
+ self.a = self.b = None
+ self.set_seqs(a, b)
+
+ def set_seqs(self, a, b):
+ self.set_seq1(a)
+ self.set_seq2(b)
+
+ def set_seq1(self, a):
+ if a is self.a:
+ return
+ self.a = a
+ self.matching_blocks = self.opcodes = None
+
+ def set_seq2(self, b):
+ if b is self.b:
+ return
+ self.b = b
+ self.matching_blocks = self.opcodes = None
+ self.fullbcount = None
+ self.__chain_b()
+
+ # For each element x in b, set b2j[x] to a list of the indices in
+ # b where x appears; the indices are in increasing order; note that
+ # the number of times x appears in b is len(b2j[x]) ...
+ # when self.isjunk is defined, junk elements don't show up in this
+ # map at all, which stops the central find_longest_match method
+ # from starting any matching block at a junk element ...
+ # also creates the fast isbjunk function ...
+ # note that this is only called when b changes; so for cross-product
+ # kinds of matches, it's best to call set_seq2 once, then set_seq1
+ # repeatedly
+
+ def __chain_b(self):
+ # Because isjunk is a user-defined (not C) function, and we test
+ # for junk a LOT, it's important to minimize the number of calls.
+ # Before the tricks described here, __chain_b was by far the most
+ # time-consuming routine in the whole module! If anyone sees
+ # Jim Roskind, thank him again for profile.py -- I never would
+ # have guessed that.
+ # The first trick is to build b2j ignoring the possibility
+ # of junk. I.e., we don't call isjunk at all yet. Throwing
+ # out the junk later is much cheaper than building b2j "right"
+ # from the start.
+ b = self.b
+ self.b2j = b2j = {}
+ self.b2jhas = b2jhas = b2j.has_key
+ for i in xrange(len(b)):
+ elt = b[i]
+ if b2jhas(elt):
+ b2j[elt].append(i)
+ else:
+ b2j[elt] = [i]
+
+ # Now b2j.keys() contains elements uniquely, and especially when
+ # the sequence is a string, that's usually a good deal smaller
+ # than len(string). The difference is the number of isjunk calls
+ # saved.
+ isjunk, junkdict = self.isjunk, {}
+ if isjunk:
+ for elt in b2j.keys():
+ if isjunk(elt):
+ junkdict[elt] = 1 # value irrelevant; it's a set
+ del b2j[elt]
+
+ # Now for x in b, isjunk(x) == junkdict.has_key(x), but the
+ # latter is much faster. Note too that while there may be a
+ # lot of junk in the sequence, the number of *unique* junk
+ # elements is probably small. So the memory burden of keeping
+ # this dict alive is likely trivial compared to the size of b2j.
+ self.isbjunk = junkdict.has_key
+
+ def find_longest_match(self, alo, ahi, blo, bhi):
+ """Find longest matching block in a[alo:ahi] and b[blo:bhi].
+
+ If isjunk is not defined:
+
+ Return (i,j,k) such that a[i:i+k] is equal to b[j:j+k], where
+ alo <= i <= i+k <= ahi
+ blo <= j <= j+k <= bhi
+ and for all (i',j',k') meeting those conditions,
+ k >= k'
+ i <= i'
+ and if i == i', j <= j'
+ In other words, of all maximal matching blocks, return one
+ that starts earliest in a, and of all those maximal matching
+ blocks that start earliest in a, return the one that starts
+ earliest in b.
+
+ If isjunk is defined, first the longest matching block is
+ determined as above, but with the additional restriction that
+ no junk element appears in the block. Then that block is
+ extended as far as possible by matching (only) junk elements on
+ both sides. So the resulting block never matches on junk except
+ as identical junk happens to be adjacent to an "interesting"
+ match.
+
+ If no blocks match, return (alo, blo, 0).
+ """
+
+ # CAUTION: stripping common prefix or suffix would be incorrect.
+ # E.g.,
+ # ab
+ # acab
+ # Longest matching block is "ab", but if common prefix is
+ # stripped, it's "a" (tied with "b"). UNIX(tm) diff does so
+ # strip, so ends up claiming that ab is changed to acab by
+ # inserting "ca" in the middle. That's minimal but unintuitive:
+ # "it's obvious" that someone inserted "ac" at the front.
+ # Windiff ends up at the same place as diff, but by pairing up
+ # the unique 'b's and then matching the first two 'a's.
+
+ a, b, b2j, isbjunk = self.a, self.b, self.b2j, self.isbjunk
+ besti, bestj, bestsize = alo, blo, 0
+ # find longest junk-free match
+ # during an iteration of the loop, j2len[j] = length of longest
+ # junk-free match ending with a[i-1] and b[j]
+ j2len = {}
+ nothing = []
+ for i in xrange(alo, ahi):
+ # look at all instances of a[i] in b; note that because
+ # b2j has no junk keys, the loop is skipped if a[i] is junk
+ j2lenget = j2len.get
+ newj2len = {}
+ for j in b2j.get(a[i], nothing):
+ # a[i] matches b[j]
+ if j < blo:
+ continue
+ if j >= bhi:
+ break
+ k = newj2len[j] = j2lenget(j-1, 0) + 1
+ if k > bestsize:
+ besti, bestj, bestsize = i-k+1, j-k+1, k
+ j2len = newj2len
+
+ # Now that we have a wholly interesting match (albeit possibly
+ # empty!), we may as well suck up the matching junk on each
+ # side of it too. Can't think of a good reason not to, and it
+ # saves post-processing the (possibly considerable) expense of
+ # figuring out what to do with it. In the case of an empty
+ # interesting match, this is clearly the right thing to do,
+ # because no other kind of match is possible in the regions.
+ while besti > alo and bestj > blo and \
+ isbjunk(b[bestj-1]) and \
+ a[besti-1] == b[bestj-1]:
+ besti, bestj, bestsize = besti-1, bestj-1, bestsize+1
+ while besti+bestsize < ahi and bestj+bestsize < bhi and \
+ isbjunk(b[bestj+bestsize]) and \
+ a[besti+bestsize] == b[bestj+bestsize]:
+ bestsize = bestsize + 1
+
+ if TRACE:
+ print "get_matching_blocks", alo, ahi, blo, bhi
+ print " returns", besti, bestj, bestsize
+ return besti, bestj, bestsize
+
+ def get_matching_blocks(self):
+ if self.matching_blocks is not None:
+ return self.matching_blocks
+ self.matching_blocks = []
+ la, lb = len(self.a), len(self.b)
+ self.__helper(0, la, 0, lb, self.matching_blocks)
+ self.matching_blocks.append( (la, lb, 0) )
+ if TRACE:
+ print '*** matching blocks', self.matching_blocks
+ return self.matching_blocks
+
+ # builds list of matching blocks covering a[alo:ahi] and
+ # b[blo:bhi], appending them in increasing order to answer
+
+ def __helper(self, alo, ahi, blo, bhi, answer):
+ i, j, k = x = self.find_longest_match(alo, ahi, blo, bhi)
+ # a[alo:i] vs b[blo:j] unknown
+ # a[i:i+k] same as b[j:j+k]
+ # a[i+k:ahi] vs b[j+k:bhi] unknown
+ if k:
+ if alo < i and blo < j:
+ self.__helper(alo, i, blo, j, answer)
+ answer.append(x)
+ if i+k < ahi and j+k < bhi:
+ self.__helper(i+k, ahi, j+k, bhi, answer)
+
+ def ratio(self):
+ """Return a measure of the sequences' similarity (float in [0,1]).
+
+ Where T is the total number of elements in both sequences, and
+ M is the number of matches, this is 2*M / T.
+ Note that this is 1 if the sequences are identical, and 0 if
+ they have nothing in common.
+ """
+
+ matches = reduce(lambda sum, triple: sum + triple[-1],
+ self.get_matching_blocks(), 0)
+ return 2.0 * matches / (len(self.a) + len(self.b))
+
+ def quick_ratio(self):
+ """Return an upper bound on ratio() relatively quickly."""
+ # viewing a and b as multisets, set matches to the cardinality
+ # of their intersection; this counts the number of matches
+ # without regard to order, so is clearly an upper bound
+ if self.fullbcount is None:
+ self.fullbcount = fullbcount = {}
+ for elt in self.b:
+ fullbcount[elt] = fullbcount.get(elt, 0) + 1
+ fullbcount = self.fullbcount
+ # avail[x] is the number of times x appears in 'b' less the
+ # number of times we've seen it in 'a' so far ... kinda
+ avail = {}
+ availhas, matches = avail.has_key, 0
+ for elt in self.a:
+ if availhas(elt):
+ numb = avail[elt]
+ else:
+ numb = fullbcount.get(elt, 0)
+ avail[elt] = numb - 1
+ if numb > 0:
+ matches = matches + 1
+ return 2.0 * matches / (len(self.a) + len(self.b))
+
+ def real_quick_ratio(self):
+ """Return an upper bound on ratio() very quickly"""
+ la, lb = len(self.a), len(self.b)
+ # can't have more matches than the number of elements in the
+ # shorter sequence
+ return 2.0 * min(la, lb) / (la + lb)
+
+ def get_opcodes(self):
+ if self.opcodes is not None:
+ return self.opcodes
+ i = j = 0
+ self.opcodes = answer = []
+ for ai, bj, size in self.get_matching_blocks():
+ # invariant: we've pumped out correct diffs to change
+ # a[:i] into b[:j], and the next matching block is
+ # a[ai:ai+size] == b[bj:bj+size]. So we need to pump
+ # out a diff to change a[i:ai] into b[j:bj], pump out
+ # the matching block, and move (i,j) beyond the match
+ tag = ''
+ if i < ai and j < bj:
+ tag = 'replace'
+ elif i < ai:
+ tag = 'delete'
+ elif j < bj:
+ tag = 'insert'
+ if tag:
+ answer.append( (tag, i, ai, j, bj) )
+ i, j = ai+size, bj+size
+ # the list of matching blocks is terminated by a
+ # sentinel with size 0
+ if size:
+ answer.append( ('equal', ai, i, bj, j) )
+ return answer
+
+# meant for dumping lines
+def dump(tag, x, lo, hi):
+ for i in xrange(lo, hi):
+ print tag, x[i],
+
+def plain_replace(a, alo, ahi, b, blo, bhi):
+ assert alo < ahi and blo < bhi
+ # dump the shorter block first -- reduces the burden on short-term
+ # memory if the blocks are of very different sizes
+ if bhi - blo < ahi - alo:
+ dump('+', b, blo, bhi)
+ dump('-', a, alo, ahi)
+ else:
+ dump('-', a, alo, ahi)
+ dump('+', b, blo, bhi)
+
+# When replacing one block of lines with another, this guy searches
+# the blocks for *similar* lines; the best-matching pair (if any) is
+# used as a synch point, and intraline difference marking is done on
+# the similar pair. Lots of work, but often worth it.
+
+def fancy_replace(a, alo, ahi, b, blo, bhi):
+ if TRACE:
+ print '*** fancy_replace', alo, ahi, blo, bhi
+ dump('>', a, alo, ahi)
+ dump('<', b, blo, bhi)
+
+ # don't synch up unless the lines have a similarity score of at
+ # least cutoff; best_ratio tracks the best score seen so far
+ best_ratio, cutoff = 0.74, 0.75
+ cruncher = SequenceMatcher(IS_CHARACTER_JUNK)
+ eqi, eqj = None, None # 1st indices of equal lines (if any)
+
+ # search for the pair that matches best without being identical
+ # (identical lines must be junk lines, & we don't want to synch up
+ # on junk -- unless we have to)
+ for j in xrange(blo, bhi):
+ bj = b[j]
+ cruncher.set_seq2(bj)
+ for i in xrange(alo, ahi):
+ ai = a[i]
+ if ai == bj:
+ if eqi is None:
+ eqi, eqj = i, j
+ continue
+ cruncher.set_seq1(ai)
+ # computing similarity is expensive, so use the quick
+ # upper bounds first -- have seen this speed up messy
+ # compares by a factor of 3.
+ # note that ratio() is only expensive to compute the first
+ # time it's called on a sequence pair; the expensive part
+ # of the computation is cached by cruncher
+ if cruncher.real_quick_ratio() > best_ratio and \
+ cruncher.quick_ratio() > best_ratio and \
+ cruncher.ratio() > best_ratio:
+ best_ratio, best_i, best_j = cruncher.ratio(), i, j
+ if best_ratio < cutoff:
+ # no non-identical "pretty close" pair
+ if eqi is None:
+ # no identical pair either -- treat it as a straight replace
+ plain_replace(a, alo, ahi, b, blo, bhi)
+ return
+ # no close pair, but an identical pair -- synch up on that
+ best_i, best_j, best_ratio = eqi, eqj, 1.0
+ else:
+ # there's a close pair, so forget the identical pair (if any)
+ eqi = None
+
+ # a[best_i] very similar to b[best_j]; eqi is None iff they're not
+ # identical
+ if TRACE:
+ print '*** best_ratio', best_ratio, best_i, best_j
+ dump('>', a, best_i, best_i+1)
+ dump('<', b, best_j, best_j+1)
+
+ # pump out diffs from before the synch point
+ fancy_helper(a, alo, best_i, b, blo, best_j)
+
+ # do intraline marking on the synch pair
+ aelt, belt = a[best_i], b[best_j]
+ if eqi is None:
+ # pump out a '-', '?', '+', '?' quad for the synched lines
+ atags = btags = ""
+ cruncher.set_seqs(aelt, belt)
+ for tag, ai1, ai2, bj1, bj2 in cruncher.get_opcodes():
+ la, lb = ai2 - ai1, bj2 - bj1
+ if tag == 'replace':
+ atags = atags + '^' * la
+ btags = btags + '^' * lb
+ elif tag == 'delete':
+ atags = atags + '-' * la
+ elif tag == 'insert':
+ btags = btags + '+' * lb
+ elif tag == 'equal':
+ atags = atags + ' ' * la
+ btags = btags + ' ' * lb
+ else:
+ raise ValueError, 'unknown tag ' + `tag`
+ printq(aelt, belt, atags, btags)
+ else:
+ # the synch pair is identical
+ print ' ', aelt,
+
+ # pump out diffs from after the synch point
+ fancy_helper(a, best_i+1, ahi, b, best_j+1, bhi)
+
+def fancy_helper(a, alo, ahi, b, blo, bhi):
+ if alo < ahi:
+ if blo < bhi:
+ fancy_replace(a, alo, ahi, b, blo, bhi)
+ else:
+ dump('-', a, alo, ahi)
+ elif blo < bhi:
+ dump('+', b, blo, bhi)
+
+# Crap to deal with leading tabs in "?" output. Can hurt, but will
+# probably help most of the time.
+
+def printq(aline, bline, atags, btags):
+ common = min(count_leading(aline, "\t"),
+ count_leading(bline, "\t"))
+ common = min(common, count_leading(atags[:common], " "))
+ print "-", aline,
+ if count_leading(atags, " ") < len(atags):
+ print "?", "\t" * common + atags[common:]
+ print "+", bline,
+ if count_leading(btags, " ") < len(btags):
+ print "?", "\t" * common + btags[common:]
+
+def count_leading(line, ch):
+ i, n = 0, len(line)
+ while i < n and line[i] == ch:
+ i = i + 1
+ return i
+
+def fail(msg):
+ import sys
+ out = sys.stderr.write
+ out(msg + "\n\n")
+ out(__doc__)
+ return 0
+
+# open a file & return the file object; gripe and return 0 if it
+# couldn't be opened
+def fopen(fname):
+ try:
+ return open(fname, 'r')
+ except IOError, detail:
+ return fail("couldn't open " + fname + ": " + str(detail))
+
+# open two files & spray the diff to stdout; return false iff a problem
+def fcompare(f1name, f2name):
+ f1 = fopen(f1name)
+ f2 = fopen(f2name)
+ if not f1 or not f2:
+ return 0
+
+ a = f1.readlines(); f1.close()
+ b = f2.readlines(); f2.close()
+
+ cruncher = SequenceMatcher(IS_LINE_JUNK, a, b)
+ for tag, alo, ahi, blo, bhi in cruncher.get_opcodes():
+ if tag == 'replace':
+ fancy_replace(a, alo, ahi, b, blo, bhi)
+ elif tag == 'delete':
+ dump('-', a, alo, ahi)
+ elif tag == 'insert':
+ dump('+', b, blo, bhi)
+ elif tag == 'equal':
+ dump(' ', a, alo, ahi)
+ else:
+ raise ValueError, 'unknown tag ' + `tag`
+
+ return 1
+
+# crack args (sys.argv[1:] is normal) & compare;
+# return false iff a problem
+
+def main(args):
+ import getopt
+ try:
+ opts, args = getopt.getopt(args, "qr:")
+ except getopt.error, detail:
+ return fail(str(detail))
+ noisy = 1
+ qseen = rseen = 0
+ for opt, val in opts:
+ if opt == "-q":
+ qseen = 1
+ noisy = 0
+ elif opt == "-r":
+ rseen = 1
+ whichfile = val
+ if qseen and rseen:
+ return fail("can't specify both -q and -r")
+ if rseen:
+ if args:
+ return fail("no args allowed with -r option")
+ if whichfile in "12":
+ restore(whichfile)
+ return 1
+ return fail("-r value must be 1 or 2")
+ if len(args) != 2:
+ return fail("need 2 filename args")
+ f1name, f2name = args
+ if noisy:
+ print '-:', f1name
+ print '+:', f2name
+ return fcompare(f1name, f2name)
+
+def restore(which):
+ import sys
+ tag = {"1": "- ", "2": "+ "}[which]
+ prefixes = (" ", tag)
+ for line in sys.stdin.readlines():
+ if line[:2] in prefixes:
+ print line[2:],
+
+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/readme.txt b/readme.txt
new file mode 100644
index 0000000..4a28816
--- /dev/null
+++ b/readme.txt
@@ -0,0 +1,97 @@
+TAL - Template Attribute Language
+---------------------------------
+
+This is an implementation of TAL, the Zope Template Attribute
+Language. For TAL, see the Zope Presentation Templates ZWiki:
+
+ http://dev.zope.org/Wikis/DevSite/Projects/ZPT/FrontPage
+
+It is not a Zope product nor is it designed exclusively to run inside
+of Zope, but if you have a Zope checkout that includes
+Products/ParsedXML, its Expat parser will be used.
+
+Prerequisites
+-------------
+
+You need:
+
+- A recent checkout of Zope2; don't forget to run the wo_pcgi.py
+ script to compile everything. (See above -- this is now optional.)
+
+- A recent checkout of the Zope2 product ParsedXML, accessible
+ throught <Zope2>/lib/python/Products/ParsedXML; don't forget to run
+ the setup.py script to compiles Expat. (Again, optional.)
+
+- Python 1.5.2; the driver script refuses to work with other versions
+ unless you specify the -n option; this is done so that I don't
+ accidentally use Python 2.x features.
+
+- Create a .path file containing proper module search path; it should
+ point the <Zope2>/lib/python directory that you want to use.
+
+How To Play
+-----------
+
+(Don't forget to edit .path, see above!)
+
+The script driver.py takes an XML file with TAL markup as argument and
+writes the expanded version to standard output. The filename argument
+defaults to tests/input/test01.xml.
+
+Regression test
+---------------
+
+There are unit test suites in the 'tests' subdirectory; these can be
+run with tests/run.py. This should print the testcase names plus
+progress info, followed by a final line saying "OK". It requires that
+../unittest.py exists.
+
+There are a number of test files in the 'tests' subdirectory, named
+tests/input/test<number>.xml and tests/input/test<number>.html. The
+Python script ./runtest.py calls driver.main() for each test file, and
+should print "<file> OK" for each one. These tests are also run as
+part of the unit test suites, so tests/run.py is all you need.
+
+What's Here
+-----------
+
+DummyEngine.py simple-minded TALES execution engine
+TALInterpreter.py class to interpret intermediate code
+TALGenerator.py class to generate intermediate code
+XMLParser.py base class to parse XML, avoiding DOM
+TALParser.py class to parse XML with TAL into intermediate code
+HTMLTALParser.py class to parse HTML with TAL into intermediate code
+HTMLParser.py HTML-parsing base class
+driver.py script to demonstrate TAL expansion
+timer.py script to time various processing phases
+setpath.py hack to set sys.path and import ZODB
+__init__.py empty file that makes this directory a package
+runtest.py Python script to run file-comparison tests
+ndiff.py helper for runtest.py to produce diffs
+tests/ drectory with test files and output
+tests/run.py Python script to run all tests
+
+Author and License
+------------------
+
+This code is written by Guido van Rossum (project lead), Fred Drake,
+and Tim Peters. It is owned by Digital Creations and can be
+redistributed under the Zope Public License.
+
+TO DO
+-----
+
+(See also http://www.zope.org/Members/jim/ZPTIssueTracker .)
+
+- Need to remove leading whitespace and newline when omitting an
+ element (either through tal:replace with a value of nothing or
+ tal:condition with a false condition).
+
+- Empty TAL/METAL attributes are ignored: tal:replace="" is ignored
+ rather than causing an error.
+
+- HTMLTALParser.py and TALParser.py are silly names. Should be
+ HTMLTALCompiler.py and XMLTALCompiler.py (or maybe shortened,
+ without "TAL"?)
+
+- Should we preserve case of tags and attribute names in HTML?
diff --git a/runtest.py b/runtest.py
new file mode 100644
index 0000000..2c6dc34
--- /dev/null
+++ b/runtest.py
@@ -0,0 +1,152 @@
+#! /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.0 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+Driver program to run METAL and TAL regression tests.
+"""
+
+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 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/setpath.py b/setpath.py
new file mode 100644
index 0000000..e4bfade
--- /dev/null
+++ b/setpath.py
@@ -0,0 +1,44 @@
+##############################################################################
+#
+# 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.0 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+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.
+"""
+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/taldefs.py b/taldefs.py
new file mode 100644
index 0000000..dd84c2e
--- /dev/null
+++ b/taldefs.py
@@ -0,0 +1,190 @@
+##############################################################################
+#
+# 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.0 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+Common definitions used by TAL and METAL compilation an transformation.
+"""
+
+from zope.tal.interfaces import ITALESErrorInfo
+
+TAL_VERSION = "1.4"
+
+XML_NS = "http://www.w3.org/XML/1998/namespace" # URI for XML namespace
+XMLNS_NS = "http://www.w3.org/2000/xmlns/" # URI for XML NS declarations
+
+ZOPE_TAL_NS = "http://xml.zope.org/namespaces/tal"
+ZOPE_METAL_NS = "http://xml.zope.org/namespaces/metal"
+ZOPE_I18N_NS = "http://xml.zope.org/namespaces/i18n"
+
+NAME_RE = "[a-zA-Z_][a-zA-Z0-9_]*"
+
+KNOWN_METAL_ATTRIBUTES = [
+ "define-macro",
+ "use-macro",
+ "define-slot",
+ "fill-slot",
+ ]
+
+KNOWN_TAL_ATTRIBUTES = [
+ "define",
+ "condition",
+ "content",
+ "replace",
+ "repeat",
+ "attributes",
+ "on-error",
+ "omit-tag",
+ "tal tag",
+ ]
+
+KNOWN_I18N_ATTRIBUTES = [
+ "translate",
+ "domain",
+ "target",
+ "source",
+ "attributes",
+ "data",
+ "name",
+ ]
+
+class TALError(Exception):
+
+ def __init__(self, msg, position=(None, None)):
+ assert msg != ""
+ self.msg = msg
+ self.lineno = position[0]
+ self.offset = position[1]
+ self.filename = None
+
+ def setFile(self, filename):
+ self.filename = filename
+
+ def __str__(self):
+ result = self.msg
+ if self.lineno is not None:
+ result = result + ", at line %d" % self.lineno
+ if self.offset is not None:
+ result = result + ", column %d" % (self.offset + 1)
+ if self.filename is not None:
+ result = result + ', in file %s' % self.filename
+ return result
+
+class METALError(TALError):
+ pass
+
+class TALESError(TALError):
+ pass
+
+class I18NError(TALError):
+ pass
+
+
+class ErrorInfo:
+
+ __implements__ = ITALESErrorInfo
+
+ def __init__(self, err, position=(None, None)):
+ if isinstance(err, Exception):
+ self.type = err.__class__
+ self.value = err
+ else:
+ self.type = err
+ self.value = None
+ self.lineno = position[0]
+ self.offset = position[1]
+
+
+
+import re
+_attr_re = re.compile(r"\s*([^\s]+)\s+([^\s].*)\Z", re.S)
+_subst_re = re.compile(r"\s*(?:(text|structure)\s+)?(.*)\Z", re.S)
+del re
+
+def parseAttributeReplacements(arg):
+ dict = {}
+ for part in splitParts(arg):
+ m = _attr_re.match(part)
+ if not m:
+ raise TALError("Bad syntax in attributes:" + `part`)
+ name, expr = m.group(1, 2)
+ name = name.lower()
+ if dict.has_key(name):
+ raise TALError("Duplicate attribute name in attributes:" + `part`)
+ dict[name] = expr
+ return dict
+
+def parseSubstitution(arg, position=(None, None)):
+ m = _subst_re.match(arg)
+ if not m:
+ raise TALError("Bad syntax in substitution text: " + `arg`, position)
+ key, expr = m.group(1, 2)
+ if not key:
+ key = "text"
+ return key, expr
+
+def splitParts(arg):
+ # Break in pieces at undoubled semicolons and
+ # change double semicolons to singles:
+ arg = arg.replace(";;", "\0")
+ parts = arg.split(';')
+ parts = [p.replace("\0", ";") for p in parts]
+ if len(parts) > 1 and not parts[-1].strip():
+ del parts[-1] # It ended in a semicolon
+ return parts
+
+def isCurrentVersion(program):
+ version = getProgramVersion(program)
+ return version == TAL_VERSION
+
+def getProgramMode(program):
+ version = getProgramVersion(program)
+ if (version == TAL_VERSION and isinstance(program[1], 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
+
+import re
+_ent1_re = re.compile('&(?![A-Z#])', re.I)
+_entch_re = re.compile('&([A-Z][A-Z0-9]*)(?![A-Z0-9;])', re.I)
+_entn1_re = re.compile('&#(?![0-9X])', re.I)
+_entnx_re = re.compile('&(#X[A-F0-9]*)(?![A-F0-9;])', re.I)
+_entnd_re = re.compile('&(#[0-9][0-9]*)(?![0-9;])')
+del re
+
+def attrEscape(s):
+ """Replace special characters '&<>' by character entities,
+ except when '&' already begins a syntactically valid entity."""
+ s = _ent1_re.sub('&amp;', s)
+ s = _entch_re.sub(r'&amp;\1', s)
+ s = _entn1_re.sub('&amp;#', s)
+ s = _entnx_re.sub(r'&amp;\1', s)
+ s = _entnd_re.sub(r'&amp;\1', s)
+ s = s.replace('<', '&lt;')
+ s = s.replace('>', '&gt;')
+ s = s.replace('"', '&quot;')
+ return s
+
+import cgi
+def quote(s, escape=cgi.escape):
+ return '"%s"' % escape(s, 1)
+del cgi
diff --git a/talgenerator.py b/talgenerator.py
new file mode 100644
index 0000000..c67b3d4
--- /dev/null
+++ b/talgenerator.py
@@ -0,0 +1,785 @@
+##############################################################################
+#
+# 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.0 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+Code generator for TALInterpreter intermediate code.
+"""
+
+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
+
+I18N_REPLACE = 1
+I18N_CONTENT = 2
+I18N_EXPRESSION = 3
+
+
+class TALGenerator:
+
+ 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 = {}
+ self.slots = {}
+ self.slotStack = []
+ self.xml = xml
+ self.emit("version", TAL_VERSION)
+ self.emit("mode", xml and "xml" or "html")
+ if source_file is not None:
+ self.source_file = source_file
+ self.emit("setSourceFile", source_file)
+ self.i18nContext = TranslationContext()
+
+ def getCode(self):
+ assert not self.stack
+ assert not self.todoStack
+ return self.optimize(self.program), self.macros
+
+ def optimize(self, program):
+ output = []
+ collect = []
+ rawseen = cursor = 0
+ if self.xml:
+ endsep = "/>"
+ else:
+ endsep = " />"
+ for cursor in xrange(len(program)+1):
+ try:
+ item = program[cursor]
+ except IndexError:
+ item = (None, None)
+ opcode = item[0]
+ if opcode == "rawtext":
+ collect.append(item[1])
+ continue
+ if opcode == "endTag":
+ collect.append("</%s>" % item[1])
+ continue
+ if opcode == "startTag":
+ if self.optimizeStartTag(collect, item[1], item[2], ">"):
+ continue
+ if opcode == "startEndTag":
+ if self.optimizeStartTag(collect, item[1], item[2], endsep):
+ continue
+ if opcode in ("beginScope", "endScope"):
+ # Push *Scope instructions in front of any text instructions;
+ # this allows text instructions separated only by *Scope
+ # instructions to be joined together.
+ output.append(self.optimizeArgsList(item))
+ continue
+ if opcode == 'noop':
+ # This is a spacer for end tags in the face of i18n:name
+ # attributes. We can't let the optimizer collect immediately
+ # following end tags into the same rawtextOffset.
+ opcode = None
+ pass
+ text = "".join(collect)
+ if text:
+ i = text.rfind("\n")
+ if i >= 0:
+ i = len(text) - (i + 1)
+ output.append(("rawtextColumn", (text, i)))
+ else:
+ output.append(("rawtextOffset", (text, len(text))))
+ if opcode != None:
+ output.append(self.optimizeArgsList(item))
+ rawseen = cursor+1
+ 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):
+ 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, {}, [])
+ self.emitEndTag(name)
+ handler = self.popProgram()
+ self.emit("onError", block, handler)
+
+ def emitCondition(self, expr):
+ cexpr = self.compileExpression(expr)
+ program = self.popProgram()
+ self.emit("condition", cexpr, program)
+
+ def emitRepeat(self, arg):
+ m = re.match("(?s)\s*(%s)\s+(.*)\Z" % NAME_RE, arg)
+ if not m:
+ raise TALError("invalid repeat syntax: " + `arg`,
+ self.position)
+ name, expr = m.group(1, 2)
+ cexpr = self.compileExpression(expr)
+ program = self.popProgram()
+ self.emit("loop", name, cexpr, program)
+
+ def emitSubstitution(self, arg, attrDict={}):
+ key, expr = parseSubstitution(arg)
+ cexpr = self.compileExpression(expr)
+ program = self.popProgram()
+ if key == "text":
+ self.emit("insertText", cexpr, program)
+ else:
+ assert key == "structure"
+ self.emit("insertStructure", cexpr, attrDict, program)
+
+ def emitI18nVariable(self, varname, action, expression):
+ # Used for i18n:name attributes. arg is extra information describing
+ # how the contents of the variable should get filled in, and it will
+ # either be a 1-tuple or a 2-tuple. If arg[0] is None, then the
+ # i18n:name value is taken implicitly from the contents of the tag,
+ # e.g. "I live in <span i18n:name="country">the USA</span>". In this
+ # case, arg[1] is the opcode sub-program describing the contents of
+ # the tag.
+ #
+ # When arg[0] is not None, it contains the tal expression used to
+ # calculate the contents of the variable, e.g.
+ # "I live in <span i18n:name="country"
+ # tal:replace="here/countryOfOrigin" />"
+ key = cexpr = None
+ program = self.popProgram()
+ if action == I18N_REPLACE:
+ # This is a tag with an i18n:name and a tal:replace (implicit or
+ # explicit). Get rid of the first and last elements of the
+ # program, which are the start and end tag opcodes of the tag.
+ program = program[1:-1]
+ elif action == I18N_CONTENT:
+ # This is a tag with an i18n:name and a tal:content
+ # (explicit-only). Keep the first and last elements of the
+ # program, so we keep the start and end tag output.
+ pass
+ else:
+ assert action == I18N_EXPRESSION
+ key, expr = parseSubstitution(expression)
+ cexpr = self.compileExpression(expr)
+ # XXX Would key be anything but 'text' or None?
+ assert key in ('text', None)
+ self.emit('i18nVariable', varname, program, cexpr)
+
+ def emitTranslation(self, msgid, i18ndata):
+ program = self.popProgram()
+ if i18ndata is None:
+ self.emit('insertTranslation', msgid, program)
+ else:
+ key, expr = parseSubstitution(i18ndata)
+ cexpr = self.compileExpression(expr)
+ assert key == 'text'
+ self.emit('insertTranslation', msgid, program, cexpr)
+
+ def emitDefineMacro(self, macroName):
+ program = self.popProgram()
+ macroName = macroName.strip()
+ if self.macros.has_key(macroName):
+ raise METALError("duplicate macro definition: %s" % `macroName`,
+ self.position)
+ if not re.match('%s$' % NAME_RE, macroName):
+ raise METALError("invalid macro name: %s" % `macroName`,
+ self.position)
+ self.macros[macroName] = program
+ self.inMacroDef = self.inMacroDef - 1
+ self.emit("defineMacro", macroName, program)
+
+ def emitUseMacro(self, expr):
+ cexpr = self.compileExpression(expr)
+ program = self.popProgram()
+ self.inMacroUse = 0
+ self.emit("useMacro", expr, cexpr, self.popSlots(), program)
+
+ def emitDefineSlot(self, slotName):
+ program = self.popProgram()
+ slotName = slotName.strip()
+ if not re.match('%s$' % NAME_RE, slotName):
+ raise METALError("invalid slot name: %s" % `slotName`,
+ self.position)
+ self.emit("defineSlot", slotName, program)
+
+ def emitFillSlot(self, slotName):
+ program = self.popProgram()
+ slotName = slotName.strip()
+ if self.slots.has_key(slotName):
+ raise METALError("duplicate fill-slot name: %s" % `slotName`,
+ self.position)
+ if not re.match('%s$' % NAME_RE, slotName):
+ raise METALError("invalid slot name: %s" % `slotName`,
+ self.position)
+ self.slots[slotName] = program
+ self.inMacroUse = 1
+ self.emit("fillSlot", slotName, program)
+
+ def unEmitWhitespace(self):
+ collect = []
+ i = len(self.program) - 1
+ while i >= 0:
+ item = self.program[i]
+ if item[0] != "rawtext":
+ break
+ text = item[1]
+ if not re.match(r"\A\s*\Z", text):
+ break
+ collect.append(text)
+ i = i-1
+ del self.program[i+1:]
+ if i >= 0 and self.program[i][0] == "rawtext":
+ text = self.program[i][1]
+ m = re.search(r"\s+\Z", text)
+ if m:
+ self.program[i] = ("rawtext", text[:m.start()])
+ collect.append(m.group())
+ collect.reverse()
+ return "".join(collect)
+
+ def unEmitNewlineWhitespace(self):
+ collect = []
+ i = len(self.program)
+ while i > 0:
+ i = i-1
+ item = self.program[i]
+ if item[0] != "rawtext":
+ break
+ text = item[1]
+ if re.match(r"\A[ \t]*\Z", text):
+ collect.append(text)
+ continue
+ m = re.match(r"(?s)^(.*)(\n[ \t]*)\Z", text)
+ if not m:
+ break
+ text, rest = m.group(1, 2)
+ collect.reverse()
+ rest = rest + "".join(collect)
+ del self.program[i:]
+ if text:
+ self.emit("rawtext", text)
+ return rest
+ return None
+
+ def replaceAttrs(self, attrlist, repldict):
+ # Each entry in attrlist starts like (name, value).
+ # Result is (name, value, action, expr, xlat) if there is a
+ # tal:attributes entry for that attribute. Additional attrs
+ # defined only by tal:attributes are added here.
+ #
+ # (name, value, action, expr, xlat)
+ if not repldict:
+ return attrlist
+ newlist = []
+ for item in attrlist:
+ key = item[0]
+ if repldict.has_key(key):
+ expr, xlat = repldict[key]
+ item = item[:2] + ("replace", expr, xlat)
+ del repldict[key]
+ newlist.append(item)
+ # Add dynamic-only attributes
+ for key, (expr, xlat) in repldict.items():
+ newlist.append((key, None, "insert", expr, xlat))
+ return newlist
+
+ def emitStartElement(self, name, attrlist, taldict, metaldict, i18ndict,
+ position=(None, None), isend=0):
+ if not taldict and not metaldict and not i18ndict:
+ # Handle the simple, common case
+ self.emitStartTag(name, attrlist, isend)
+ self.todoPush({})
+ if isend:
+ self.emitEndElement(name, isend)
+ return
+
+ self.position = position
+ for key, value in taldict.items():
+ if key not in taldefs.KNOWN_TAL_ATTRIBUTES:
+ raise TALError("bad TAL attribute: " + `key`, position)
+ if not (value or key == 'omit-tag'):
+ raise TALError("missing value for TAL attribute: " +
+ `key`, position)
+ for key, value in metaldict.items():
+ if key not in taldefs.KNOWN_METAL_ATTRIBUTES:
+ raise METALError("bad METAL attribute: " + `key`,
+ position)
+ if not value:
+ raise TALError("missing value for METAL attribute: " +
+ `key`, position)
+ for key, value in i18ndict.items():
+ if key not in taldefs.KNOWN_I18N_ATTRIBUTES:
+ raise I18NError("bad i18n attribute: " + `key`, position)
+ if not value and key in ("attributes", "data", "id"):
+ raise I18NError("missing value for i18n attribute: " +
+ `key`, position)
+ todo = {}
+ defineMacro = metaldict.get("define-macro")
+ useMacro = metaldict.get("use-macro")
+ defineSlot = metaldict.get("define-slot")
+ fillSlot = metaldict.get("fill-slot")
+ define = taldict.get("define")
+ condition = taldict.get("condition")
+ repeat = taldict.get("repeat")
+ content = taldict.get("content")
+ replace = taldict.get("replace")
+ attrsubst = taldict.get("attributes")
+ onError = taldict.get("on-error")
+ omitTag = taldict.get("omit-tag")
+ TALtag = taldict.get("tal tag")
+ i18nattrs = i18ndict.get("attributes")
+ # Preserve empty string if implicit msgids are used. We'll generate
+ # code with the msgid='' and calculate the right implicit msgid during
+ # interpretation phase.
+ msgid = i18ndict.get("translate")
+ varname = i18ndict.get('name')
+ i18ndata = i18ndict.get('data')
+
+ if i18ndata and not msgid:
+ raise I18NError("i18n:data must be accompanied by i18n:translate",
+ position)
+
+ if len(metaldict) > 1 and (defineMacro or useMacro):
+ raise METALError("define-macro and use-macro cannot be used "
+ "together or with define-slot or fill-slot",
+ position)
+ if replace:
+ if content:
+ raise TALError(
+ "tal:content and tal:replace are mutually exclusive",
+ position)
+ if msgid is not None:
+ raise I18NError(
+ "i18n:translate and tal:replace are mutually exclusive",
+ position)
+
+ repeatWhitespace = None
+ if repeat:
+ # Hack to include preceding whitespace in the loop program
+ repeatWhitespace = self.unEmitNewlineWhitespace()
+ if position != (None, None):
+ # XXX at some point we should insist on a non-trivial position
+ self.emit("setPosition", position)
+ if self.inMacroUse:
+ if fillSlot:
+ self.pushProgram()
+ if self.source_file is not None:
+ self.emit("setSourceFile", self.source_file)
+ todo["fillSlot"] = fillSlot
+ self.inMacroUse = 0
+ else:
+ if fillSlot:
+ raise METALError("fill-slot must be within a use-macro",
+ position)
+ if not self.inMacroUse:
+ if defineMacro:
+ self.pushProgram()
+ self.emit("version", TAL_VERSION)
+ self.emit("mode", self.xml and "xml" or "html")
+ if self.source_file is not None:
+ self.emit("setSourceFile", self.source_file)
+ todo["defineMacro"] = defineMacro
+ self.inMacroDef = self.inMacroDef + 1
+ if useMacro:
+ self.pushSlots()
+ self.pushProgram()
+ todo["useMacro"] = useMacro
+ self.inMacroUse = 1
+ if defineSlot:
+ if not self.inMacroDef:
+ raise METALError(
+ "define-slot must be within a define-macro",
+ position)
+ self.pushProgram()
+ todo["defineSlot"] = defineSlot
+
+ if defineSlot or i18ndict:
+
+ domain = i18ndict.get("domain") or self.i18nContext.domain
+ source = i18ndict.get("source") or self.i18nContext.source
+ target = i18ndict.get("target") or self.i18nContext.target
+ if ( domain != DEFAULT_DOMAIN
+ or source is not None
+ or target is not None):
+ self.i18nContext = TranslationContext(self.i18nContext,
+ domain=domain,
+ source=source,
+ target=target)
+ self.emit("beginI18nContext",
+ {"domain": domain, "source": source,
+ "target": target})
+ todo["i18ncontext"] = 1
+ if taldict or i18ndict:
+ dict = {}
+ for item in attrlist:
+ key, value = item[:2]
+ dict[key] = value
+ self.emit("beginScope", dict)
+ todo["scope"] = 1
+ if onError:
+ self.pushProgram() # handler
+ self.emitStartTag(name, list(attrlist)) # Must copy attrlist!
+ 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:
+ todo["content"] = content
+ if replace:
+ # tal:replace w/ i18n:name has slightly different semantics. What
+ # we're actually replacing then is the contents of the ${name}
+ # placeholder.
+ if varname:
+ todo['i18nvar'] = (varname, replace)
+ else:
+ todo["replace"] = replace
+ self.pushProgram()
+ # i18n:name w/o tal:replace uses the content as the interpolation
+ # dictionary values
+ elif varname:
+ todo['i18nvar'] = (varname, None)
+ self.pushProgram()
+ if msgid is not None:
+ 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)
+ else:
+ repldict = {}
+ if i18nattrs:
+ i18nattrs = i18nattrs.split()
+ else:
+ i18nattrs = ()
+ # Convert repldict's name-->expr mapping to a
+ # name-->(compiled_expr, translate) mapping
+ for key, value in repldict.items():
+ repldict[key] = self.compileExpression(value), key in i18nattrs
+ for key in i18nattrs:
+ if key not in repldict:
+ repldict[key] = None, 1
+ else:
+ repldict = {}
+ if replace:
+ todo["repldict"] = repldict
+ repldict = {}
+ self.emitStartTag(name, self.replaceAttrs(attrlist, repldict), isend)
+ if optTag:
+ self.pushProgram()
+ if content:
+ self.pushProgram()
+ if msgid is not None:
+ self.pushProgram()
+ if todo and position != (None, None):
+ todo["position"] = position
+ self.todoPush(todo)
+ if isend:
+ self.emitEndElement(name, isend)
+
+ def emitEndElement(self, name, isend=0, implied=0):
+ todo = self.todoPop()
+ if not todo:
+ # Shortcut
+ if not isend:
+ self.emitEndTag(name)
+ return
+
+ self.position = position = todo.get("position", (None, None))
+ defineMacro = todo.get("defineMacro")
+ useMacro = todo.get("useMacro")
+ defineSlot = todo.get("defineSlot")
+ fillSlot = todo.get("fillSlot")
+ repeat = todo.get("repeat")
+ content = todo.get("content")
+ replace = todo.get("replace")
+ condition = todo.get("condition")
+ onError = todo.get("onError")
+ define = todo.get("define")
+ repldict = todo.get("repldict", {})
+ scope = todo.get("scope")
+ optTag = todo.get("optional tag")
+ msgid = todo.get('msgid')
+ i18ncontext = todo.get("i18ncontext")
+ varname = todo.get('i18nvar')
+ i18ndata = todo.get('i18ndata')
+
+ if implied > 0:
+ if defineMacro or useMacro or defineSlot or fillSlot:
+ exc = METALError
+ what = "METAL"
+ else:
+ exc = TALError
+ what = "TAL"
+ raise exc("%s attributes on <%s> require explicit </%s>" %
+ (what, name, name), position)
+
+ # If there's no tal:content or tal:replace in the tag with the
+ # i18n:name, tal:replace is the default.
+ i18nNameAction = I18N_REPLACE
+ if content:
+ if varname:
+ i18nNameAction = I18N_CONTENT
+ self.emitSubstitution(content, {})
+ # If we're looking at an implicit msgid, emit the insertTranslation
+ # opcode now, so that the end tag doesn't become part of the implicit
+ # msgid. If we're looking at an explicit msgid, it's better to emit
+ # the opcode after the i18nVariable opcode so we can better handle
+ # tags with both of them in them (and in the latter case, the contents
+ # would be thrown away for msgid purposes).
+ if msgid is not None and not varname:
+ self.emitTranslation(msgid, i18ndata)
+ if optTag:
+ self.emitOptTag(name, optTag, isend)
+ elif not isend:
+ # If we're processing the end tag for a tag that contained
+ # i18n:name, we need to make sure that optimize() won't collect
+ # immediately following end tags into the same rawtextOffset, so
+ # put a spacer here that the optimizer will recognize.
+ if varname:
+ self.emit('noop')
+ self.emitEndTag(name)
+ # If i18n:name appeared in the same tag as tal:replace then we're
+ # going to do the substitution a little bit differently. The results
+ # of the expression go into the i18n substitution dictionary.
+ if replace:
+ self.emitSubstitution(replace, repldict)
+ elif varname:
+ if varname[1] is not None:
+ i18nNameAction = I18N_EXPRESSION
+ # o varname[0] is the variable name
+ # o i18nNameAction is either
+ # - I18N_REPLACE for implicit tal:replace
+ # - I18N_CONTENT for tal:content
+ # - I18N_EXPRESSION for explicit tal:replace
+ # o varname[1] will be None for the first two actions and the
+ # replacement tal expression for the third action.
+ self.emitI18nVariable(varname[0], i18nNameAction, varname[1])
+ # Do not test for "msgid is not None", i.e. we only want to test for
+ # explicit msgids here. See comment above.
+ if msgid is not None and varname:
+ self.emitTranslation(msgid, i18ndata)
+ if repeat:
+ self.emitRepeat(repeat)
+ if condition:
+ self.emitCondition(condition)
+ if onError:
+ self.emitOnError(name, onError)
+ if scope:
+ self.emit("endScope")
+ if i18ncontext:
+ self.emit("endI18nContext")
+ assert self.i18nContext.parent is not None
+ self.i18nContext = self.i18nContext.parent
+ if defineSlot:
+ self.emitDefineSlot(defineSlot)
+ if fillSlot:
+ self.emitFillSlot(fillSlot)
+ if useMacro:
+ self.emitUseMacro(useMacro)
+ if defineMacro:
+ self.emitDefineMacro(defineMacro)
+
+def test():
+ t = TALGenerator()
+ t.pushProgram()
+ t.emit("bar")
+ p = t.popProgram()
+ t.emit("foo", p)
+
+if __name__ == "__main__":
+ test()
diff --git a/talgettext.py b/talgettext.py
new file mode 100644
index 0000000..be621f6
--- /dev/null
+++ b/talgettext.py
@@ -0,0 +1,100 @@
+#!/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.0 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+
+"""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.
+"""
+
+import getopt
+import os
+import sys
+
+from zope.tal.htmltalparser import HTMLTALParser
+from zope.tal.talinterpreter import TALInterpreter
+from zope.tal.dummyengine import DummyEngine
+from zope.tal.interfaces import ITALESEngine
+
+
+def usage(code, msg=''):
+ # Python 2.1 required
+ print >> sys.stderr, __doc__
+ if msg:
+ print >> sys.stderr, msg
+ sys.exit(code)
+
+
+class POEngine(DummyEngine):
+ __implements__ = ITALESEngine
+
+ catalog = {}
+
+ def evaluatePathOrVar(self, expr):
+ return 'who cares'
+
+ def translate(self, domain, msgid, mapping):
+ self.catalog[msgid] = ''
+
+
+def main():
+ try:
+ opts, args = getopt.getopt(
+ sys.argv[1:],
+ 'ho:',
+ ['help', 'output='])
+ except getopt.error, msg:
+ usage(1, msg)
+
+ outfile = None
+ for opt, arg in opts:
+ if opt in ('-h', '--help'):
+ usage(0)
+ elif opt in ('-o', '--output'):
+ outfile = arg
+
+ if not args:
+ print 'nothing to do'
+ return
+
+ # We don't care about the rendered output of the .pt file
+ class Devnull:
+ def write(self, s):
+ pass
+
+ engine = POEngine()
+ for file in args:
+ p = HTMLTALParser()
+ p.parseFile(file)
+ program, macros = p.getCode()
+ TALInterpreter(program, macros, engine, stream=Devnull())()
+
+ # Now print all the entries in the engine
+ msgids = engine.catalog.keys()
+ msgids.sort()
+ for msgid in msgids:
+ msgstr = engine.catalog[msgid]
+ print 'msgid "%s"' % msgid
+ print 'msgstr "%s"' % msgstr
+ print
+
+
+if __name__ == '__main__':
+ main()
diff --git a/talinterpreter.py b/talinterpreter.py
new file mode 100644
index 0000000..0ed5ef9
--- /dev/null
+++ b/talinterpreter.py
@@ -0,0 +1,732 @@
+##############################################################################
+#
+# 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.0 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+Interpreter for a pre-compiled TAL program.
+"""
+
+import getopt
+import re
+import sys
+
+from cgi import escape
+# Do not use cStringIO here! It's not unicode aware. :(
+from StringIO import StringIO
+
+from zope.tal.taldefs import quote, TAL_VERSION, TALError, 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
+
+BOOLEAN_HTML_ATTRS = [
+ # 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)
+ # XXX 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"
+]
+
+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 ' '.join(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:
+ apply(TALGenerator.emit, (self,) + args)
+
+ def emitStartElement(self, name, attrlist, taldict, metaldict, i18ndict,
+ position=(None, None), isend=0):
+ metaldict = {}
+ taldict = {}
+ i18ndict = {}
+ if self.enabled and self.repldict:
+ taldict["attributes"] = "x x"
+ TALGenerator.emitStartElement(self, name, attrlist,
+ taldict, metaldict, i18ndict,
+ position, isend)
+
+ def replaceAttrs(self, attrlist, repldict):
+ if self.enabled and self.repldict:
+ repldict = self.repldict
+ self.repldict = None
+ return TALGenerator.replaceAttrs(self, attrlist, repldict)
+
+
+class TALInterpreter:
+
+ def __init__(self, program, macros, engine, stream=None,
+ debug=0, wrap=60, metal=1, tal=1, showtal=-1,
+ strictinsert=1, stackLimit=100, i18nInterpolate=1):
+ self.program = program
+ self.macros = macros
+ self.engine = engine # Execution engine (aka context)
+ self.Default = engine.getDefault()
+ self._stream_stack = [stream or sys.stdout]
+ self.popStream()
+ self.debug = debug
+ self.wrap = wrap
+ self.metal = metal
+ self.tal = tal
+ if tal:
+ self.dispatch = self.bytecode_handlers_tal
+ else:
+ self.dispatch = self.bytecode_handlers
+ assert showtal in (-1, 0, 1)
+ if showtal == -1:
+ showtal = (not tal)
+ self.showtal = showtal
+ self.strictinsert = strictinsert
+ self.stackLimit = stackLimit
+ self.html = 0
+ self.endsep = "/>"
+ self.endlen = len(self.endsep)
+ self.macroStack = []
+ self.position = None, None # (lineno, offset)
+ self.col = 0
+ self.level = 0
+ self.scopeLevel = 0
+ self.sourceFile = None
+ self.i18nStack = []
+ self.i18nInterpolate = i18nInterpolate
+ self.i18nContext = TranslationContext()
+
+ def saveState(self):
+ return (self.position, self.col, self.stream, self._stream_stack,
+ self.scopeLevel, self.level, self.i18nContext)
+
+ def restoreState(self, state):
+ (self.position, self.col, self.stream,
+ self._stream_stack, scopeLevel, level, i18n) = state
+ self._stream_write = self.stream.write
+ assert self.level == level
+ while self.scopeLevel > scopeLevel:
+ self.engine.endScope()
+ self.scopeLevel = self.scopeLevel - 1
+ self.engine.setPosition(self.position)
+ self.i18nContext = i18n
+
+ def restoreOutputState(self, state):
+ (dummy, self.col, self.stream,
+ self._stream_stack, scopeLevel, level, i18n) = state
+ self._stream_write = self.stream.write
+ assert self.level == level
+ assert self.scopeLevel == scopeLevel
+
+ def pushMacro(self, macroName, slots, entering=1):
+ if len(self.macroStack) >= self.stackLimit:
+ raise METALError("macro nesting limit (%d) exceeded "
+ "by %s" % (self.stackLimit, `macroName`))
+ self.macroStack.append([macroName, slots, entering, self.i18nContext])
+
+ def popMacro(self):
+ stuff = self.macroStack.pop()
+ self.i18nContext = stuff[3]
+ return stuff
+
+ def macroContext(self, what):
+ macroStack = self.macroStack
+ i = len(macroStack)
+ while i > 0:
+ i = i-1
+ if macroStack[i][0] == what:
+ return i
+ return -1
+
+ def __call__(self):
+ assert self.level == 0
+ assert self.scopeLevel == 0
+ assert self.i18nContext.parent is None
+ self.interpret(self.program)
+ assert self.level == 0
+ assert self.scopeLevel == 0
+ assert self.i18nContext.parent is None
+ if self.col > 0:
+ self._stream_write("\n")
+ self.col = 0
+
+ def pushStream(self, newstream):
+ self._stream_stack.append(self.stream)
+ self.stream = newstream
+ self._stream_write = newstream.write
+
+ def popStream(self):
+ self.stream = self._stream_stack.pop()
+ self._stream_write = self.stream.write
+
+ def stream_write(self, s,
+ len=len):
+ self._stream_write(s)
+ i = s.rfind('\n')
+ if i < 0:
+ self.col = self.col + len(s)
+ else:
+ self.col = len(s) - (i + 1)
+
+ bytecode_handlers = {}
+
+ def interpret(self, program):
+ oldlevel = self.level
+ self.level = oldlevel + 1
+ handlers = self.dispatch
+ try:
+ if self.debug:
+ for (opcode, args) in program:
+ s = "%sdo_%s(%s)\n" % (" "*self.level, opcode,
+ repr(args))
+ if len(s) > 80:
+ s = s[:76] + "...\n"
+ sys.stderr.write(s)
+ handlers[opcode](self, args)
+ else:
+ for (opcode, args) in program:
+ handlers[opcode](self, args)
+ finally:
+ self.level = oldlevel
+
+ def do_version(self, version):
+ assert version == TAL_VERSION
+ bytecode_handlers["version"] = do_version
+
+ def do_mode(self, mode):
+ assert mode in ("html", "xml")
+ self.html = (mode == "html")
+ if self.html:
+ self.endsep = " />"
+ else:
+ self.endsep = "/>"
+ self.endlen = len(self.endsep)
+ bytecode_handlers["mode"] = do_mode
+
+ def do_setSourceFile(self, source_file):
+ self.sourceFile = source_file
+ self.engine.setSourceFile(source_file)
+ bytecode_handlers["setSourceFile"] = do_setSourceFile
+
+ def do_setPosition(self, position):
+ self.position = position
+ self.engine.setPosition(position)
+ bytecode_handlers["setPosition"] = do_setPosition
+
+ def do_startEndTag(self, stuff):
+ self.do_startTag(stuff, self.endsep, self.endlen)
+ bytecode_handlers["startEndTag"] = do_startEndTag
+
+ def do_startTag(self, (name, attrList),
+ end=">", endlen=1, _len=len):
+ # The bytecode generator does not cause calls to this method
+ # for start tags with no attributes; those are optimized down
+ # to rawtext events. Hence, there is no special "fast path"
+ # for that case.
+ _stream_write = self._stream_write
+ _stream_write("<" + name)
+ col = self.col + _len(name) + 1
+ wrap = self.wrap
+ align = col + 1
+ if align >= wrap/2:
+ align = 4 # Avoid a narrow column far to the right
+ attrAction = self.dispatch["<attrAction>"]
+ try:
+ for item in attrList:
+ if _len(item) == 2:
+ name, s = item
+ else:
+ ok, name, s = attrAction(self, item)
+ if not ok:
+ continue
+ slen = _len(s)
+ if (wrap and
+ col >= align and
+ col + 1 + slen > wrap):
+ _stream_write("\n" + " "*align)
+ col = align + slen
+ else:
+ s = " " + s
+ col = col + 1 + slen
+ _stream_write(s)
+ _stream_write(end)
+ col = col + endlen
+ finally:
+ self.col = col
+ bytecode_handlers["startTag"] = do_startTag
+
+ def attrAction(self, item):
+ name, value, action = item[:3]
+ if action == 'insert' or (action in ('metal', 'tal', 'xmlns', 'i18n')
+ and not self.showtal):
+ return 0, name, value
+ macs = self.macroStack
+ if action == 'metal' and self.metal and macs:
+ if len(macs) > 1 or not macs[-1][2]:
+ # Drop all METAL attributes at a use-depth above one.
+ return 0, name, value
+ # Clear 'entering' flag
+ macs[-1][2] = 0
+ # Convert or drop depth-one METAL attributes.
+ i = name.rfind(":") + 1
+ prefix, suffix = name[:i], name[i:]
+ if suffix == "define-macro":
+ # Convert define-macro as we enter depth one.
+ name = prefix + "use-macro"
+ value = macs[-1][0] # Macro name
+ elif suffix == "define-slot":
+ name = prefix + "fill-slot"
+ elif suffix == "fill-slot":
+ pass
+ else:
+ return 0, name, value
+
+ if value is None:
+ value = name
+ else:
+ value = "%s=%s" % (name, quote(value))
+ return 1, name, value
+
+ def attrAction_tal(self, item):
+ name, value, action = item[:3]
+ if action in ('metal', 'tal', 'xmlns', 'i18n'):
+ return self.attrAction(item)
+ ok = 1
+ expr, 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
+ else:
+ if 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 msgid:
+ value = self.i18n_attribute(value)
+ if value is None:
+ value = name
+ value = "%s=%s" % (name, quote(value))
+ return ok, name, value
+
+ def i18n_attribute(self, s):
+ # s is the value of an attribute before translation
+ # it may have been computed
+ return self.translate(s, {})
+
+
+ bytecode_handlers["<attrAction>"] = attrAction
+
+ def no_tag(self, start, program):
+ state = self.saveState()
+ self.stream = stream = 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 dumpMacroStack(self, prefix, suffix, value):
+ sys.stderr.write("+---- %s%s = %s\n" % (prefix, suffix, value))
+ for i in range(len(self.macroStack)):
+ what, macroName, slots = self.macroStack[i][:3]
+ sys.stderr.write("| %2d. %-12s %-12s %s\n" %
+ (i, what, macroName, slots and slots.keys()))
+ sys.stderr.write("+--------------------------------------\n")
+
+ 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
+ self.do_setPosition(position)
+ engine = self.engine
+ if closeprev:
+ engine.endScope()
+ engine.beginScope()
+ else:
+ engine.beginScope()
+ self.scopeLevel = self.scopeLevel + 1
+ engine.setLocal("attrs", dict)
+ bytecode_handlers["rawtextBeginScope"] = do_rawtextBeginScope
+
+ def do_beginScope(self, dict):
+ self.engine.beginScope()
+ self.scopeLevel = self.scopeLevel + 1
+
+ def do_beginScope_tal(self, dict):
+ engine = self.engine
+ engine.beginScope()
+ engine.setLocal("attrs", dict)
+ self.scopeLevel = self.scopeLevel + 1
+ bytecode_handlers["beginScope"] = do_beginScope
+
+ def do_endScope(self, notused=None):
+ self.engine.endScope()
+ self.scopeLevel = self.scopeLevel - 1
+ bytecode_handlers["endScope"] = do_endScope
+
+ def do_setLocal(self, notused):
+ pass
+
+ def do_setLocal_tal(self, (name, expr)):
+ self.engine.setLocal(name, self.engine.evaluateValue(expr))
+ bytecode_handlers["setLocal"] = do_setLocal
+
+ def do_setGlobal_tal(self, (name, expr)):
+ self.engine.setGlobal(name, self.engine.evaluateValue(expr))
+ bytecode_handlers["setGlobal"] = do_setLocal
+
+ def do_beginI18nContext(self, settings):
+ get = settings.get
+ self.i18nContext = TranslationContext(self.i18nContext,
+ domain=get("domain"),
+ source=get("source"),
+ target=get("target"))
+ bytecode_handlers["beginI18nContext"] = do_beginI18nContext
+
+ def do_endI18nContext(self, notused=None):
+ self.i18nContext = self.i18nContext.parent
+ assert self.i18nContext is not None
+ bytecode_handlers["endI18nContext"] = do_endI18nContext
+
+ def do_insertText(self, stuff):
+ self.interpret(stuff[1])
+
+ def do_insertText_tal(self, stuff):
+ text = self.engine.evaluateText(stuff[0])
+ if text is None:
+ return
+ if text is self.Default:
+ self.interpret(stuff[1])
+ return
+ s = escape(text)
+ self._stream_write(s)
+ i = s.rfind('\n')
+ if i < 0:
+ self.col = self.col + len(s)
+ else:
+ self.col = len(s) - (i + 1)
+ bytecode_handlers["insertText"] = do_insertText
+
+ def do_i18nVariable(self, stuff):
+ varname, program, expression = 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 = StringIO()
+ self.pushStream(tmpstream)
+ try:
+ self.interpret(program)
+ finally:
+ self.popStream()
+ value = normalize(tmpstream.getvalue())
+ finally:
+ self.restoreState(state)
+ else:
+ # Evaluate the value to be associated with the variable in the
+ # i18n interpolation dictionary.
+ value = self.engine.evaluate(expression)
+ # 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.
+ tmpstream = 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.
+ if msgid == '':
+ msgid = normalize(tmpstream.getvalue())
+ 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, i18ndict, obj)
+ # XXX I can't decide whether we want to cgi escape the translated
+ # string or not. OT1H 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>".
+ #s = escape(xlated_msgid)
+ s = xlated_msgid
+ # If there are i18n variables to interpolate into this string, better
+ # do it now.
+ self._stream_write(s)
+ bytecode_handlers['insertTranslation'] = do_insertTranslation
+
+ def do_insertStructure(self, stuff):
+ self.interpret(stuff[2])
+
+ def do_insertStructure_tal(self, (expr, repldict, block)):
+ structure = self.engine.evaluateStructure(expr)
+ if structure is None:
+ return
+ if structure is self.Default:
+ self.interpret(block)
+ return
+ text = str(structure)
+ if not (repldict or self.strictinsert):
+ # Take a shortcut, no error checking
+ self.stream_write(text)
+ return
+ if self.html:
+ self.insertHTMLStructure(text, repldict)
+ else:
+ self.insertXMLStructure(text, repldict)
+ bytecode_handlers["insertStructure"] = do_insertStructure
+
+ def insertHTMLStructure(self, text, repldict):
+ from 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_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, i18ndict=None, obj=None):
+ # XXX is this right?
+ if i18ndict is None:
+ i18ndict = {}
+ if obj:
+ i18ndict.update(obj)
+ # XXX need to fill this in with TranslationService calls. For now,
+ # we'll just do simple interpolation based on a $-strings to %-strings
+ # algorithm in Mailman.
+ if not self.i18nInterpolate:
+ return msgid
+ # XXX Mmmh, it seems that sometimes the msgid is None; is that really
+ # possible?
+ if msgid is None:
+ return None
+ # XXX We need to pass in one of context or target_language
+ return self.engine.translate(self.i18nContext.domain, msgid, i18ndict)
+
+ def do_rawtextColumn(self, (s, col)):
+ self._stream_write(s)
+ self.col = col
+ bytecode_handlers["rawtextColumn"] = do_rawtextColumn
+
+ def do_rawtextOffset(self, (s, offset)):
+ self._stream_write(s)
+ self.col = self.col + offset
+ bytecode_handlers["rawtextOffset"] = do_rawtextOffset
+
+ def do_condition(self, (condition, block)):
+ if not self.tal or self.engine.evaluateBoolean(condition):
+ self.interpret(block)
+ bytecode_handlers["condition"] = do_condition
+
+ def do_defineMacro(self, (macroName, macro)):
+ macs = self.macroStack
+ if len(macs) == 1:
+ entering = macs[-1][2]
+ if not entering:
+ macs.append(None)
+ self.interpret(macro)
+ assert macs[-1] is None
+ macs.pop()
+ return
+ self.interpret(macro)
+ bytecode_handlers["defineMacro"] = do_defineMacro
+
+ def do_useMacro(self, (macroName, macroExpr, compiledSlots, block)):
+ if not self.metal:
+ self.interpret(block)
+ return
+ macro = self.engine.evaluateMacro(macroExpr)
+ if macro is self.Default:
+ macro = block
+ else:
+ if not isCurrentVersion(macro):
+ raise METALError("macro %s has incompatible version %s" %
+ (`macroName`, `getProgramVersion(macro)`),
+ self.position)
+ mode = getProgramMode(macro)
+ if mode != (self.html and "html" or "xml"):
+ raise METALError("macro %s has incompatible mode %s" %
+ (`macroName`, `mode`), self.position)
+ self.pushMacro(macroName, compiledSlots)
+ prev_source = self.sourceFile
+ self.interpret(macro)
+ if self.sourceFile != prev_source:
+ self.engine.setSourceFile(prev_source)
+ self.sourceFile = prev_source
+ self.popMacro()
+ bytecode_handlers["useMacro"] = do_useMacro
+
+ def do_fillSlot(self, (slotName, block)):
+ # This is only executed if the enclosing 'use-macro' evaluates
+ # to 'default'.
+ self.interpret(block)
+ bytecode_handlers["fillSlot"] = do_fillSlot
+
+ def do_defineSlot(self, (slotName, block)):
+ if not self.metal:
+ self.interpret(block)
+ return
+ macs = self.macroStack
+ if macs and macs[-1] is not None:
+ macroName, slots = self.popMacro()[:2]
+ slot = slots.get(slotName)
+ if slot is not None:
+ prev_source = self.sourceFile
+ self.interpret(slot)
+ if self.sourceFile != prev_source:
+ self.engine.setSourceFile(prev_source)
+ self.sourceFile = prev_source
+ self.pushMacro(macroName, slots, entering=0)
+ return
+ self.pushMacro(macroName, slots)
+ # Falling out of the 'if' allows the macro to be interpreted.
+ self.interpret(block)
+ bytecode_handlers["defineSlot"] = do_defineSlot
+
+ def do_onError(self, (block, handler)):
+ self.interpret(block)
+
+ def do_onError_tal(self, (block, handler)):
+ state = self.saveState()
+ self.stream = stream = StringIO()
+ self._stream_write = stream.write
+ try:
+ self.interpret(block)
+ except:
+ exc = sys.exc_info()[1]
+ self.restoreState(state)
+ engine = self.engine
+ engine.beginScope()
+ error = engine.createErrorInfo(exc, self.position)
+ engine.setLocal('error', error)
+ try:
+ self.interpret(handler)
+ finally:
+ engine.endScope()
+ else:
+ self.restoreOutputState(state)
+ self.stream_write(stream.getvalue())
+ bytecode_handlers["onError"] = do_onError
+
+ bytecode_handlers_tal = bytecode_handlers.copy()
+ bytecode_handlers_tal["rawtextBeginScope"] = do_rawtextBeginScope_tal
+ bytecode_handlers_tal["beginScope"] = do_beginScope_tal
+ bytecode_handlers_tal["setLocal"] = do_setLocal_tal
+ bytecode_handlers_tal["setGlobal"] = do_setGlobal_tal
+ bytecode_handlers_tal["insertStructure"] = do_insertStructure_tal
+ bytecode_handlers_tal["insertText"] = do_insertText_tal
+ bytecode_handlers_tal["loop"] = do_loop_tal
+ bytecode_handlers_tal["onError"] = do_onError_tal
+ bytecode_handlers_tal["<attrAction>"] = attrAction_tal
+ bytecode_handlers_tal["optTag"] = do_optTag_tal
diff --git a/talparser.py b/talparser.py
new file mode 100644
index 0000000..12dea8d
--- /dev/null
+++ b/talparser.py
@@ -0,0 +1,145 @@
+##############################################################################
+#
+# 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.0 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+Parse XML and compile to TALInterpreter intermediate code.
+"""
+
+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): # Override
+ XMLParser.__init__(self)
+ if gen is None:
+ gen = TALGenerator()
+ self.gen = gen
+ self.nsStack = []
+ self.nsDict = {XML_NS: 'xml'}
+ self.nsNew = []
+
+ def getCode(self):
+ return self.gen.getCode()
+
+ def getWarnings(self):
+ return ()
+
+ def StartNamespaceDeclHandler(self, prefix, uri):
+ self.nsStack.append(self.nsDict.copy())
+ self.nsDict[uri] = prefix
+ self.nsNew.append((prefix, uri))
+
+ def EndNamespaceDeclHandler(self, prefix):
+ self.nsDict = self.nsStack.pop()
+
+ def StartElementHandler(self, name, attrs):
+ if self.ordered_attributes:
+ # attrs is a list of alternating names and values
+ attrlist = []
+ for i in range(0, len(attrs), 2):
+ key = attrs[i]
+ value = attrs[i+1]
+ attrlist.append((key, value))
+ else:
+ # attrs is a dict of {name: value}
+ attrlist = attrs.items()
+ attrlist.sort() # For definiteness
+ name, attrlist, taldict, metaldict, i18ndict \
+ = self.process_ns(name, attrlist)
+ attrlist = self.xmlnsattrs() + attrlist
+ self.gen.emitStartElement(name, attrlist, taldict, metaldict, i18ndict)
+
+ def process_ns(self, name, attrlist):
+ taldict = {}
+ metaldict = {}
+ i18ndict = {}
+ fixedattrlist = []
+ name, namebase, namens = self.fixname(name)
+ for key, value in attrlist:
+ key, keybase, keyns = self.fixname(key)
+ ns = keyns or namens # default to tag namespace
+ item = key.lower(), value
+ if ns == 'metal':
+ metaldict[keybase] = value
+ item = item + ("metal",)
+ elif ns == 'tal':
+ taldict[keybase] = value
+ item = item + ("tal",)
+ elif ns == 'i18n':
+ assert 0, "dealing with i18n: " + `(keybase, value)`
+ i18ndict[keybase] = value
+ item = item + ('i18n',)
+ fixedattrlist.append(item)
+ if namens in ('metal', 'tal', 'i18n'):
+ taldict['tal tag'] = namens
+ return name, fixedattrlist, taldict, metaldict, i18ndict
+
+ def xmlnsattrs(self):
+ newlist = []
+ for prefix, uri in self.nsNew:
+ if prefix:
+ key = "xmlns:" + prefix
+ else:
+ key = "xmlns"
+ if uri in (ZOPE_METAL_NS, ZOPE_TAL_NS, ZOPE_I18N_NS):
+ item = (key, uri, "xmlns")
+ else:
+ item = (key, uri)
+ newlist.append(item)
+ self.nsNew = []
+ return newlist
+
+ def fixname(self, name):
+ if ' ' in name:
+ uri, name = name.split(' ')
+ prefix = self.nsDict[uri]
+ prefixed = name
+ if prefix:
+ prefixed = "%s:%s" % (prefix, name)
+ ns = 'x'
+ if uri == ZOPE_TAL_NS:
+ ns = 'tal'
+ elif uri == ZOPE_METAL_NS:
+ ns = 'metal'
+ elif uri == ZOPE_I18N_NS:
+ ns = 'i18n'
+ return (prefixed, name, ns)
+ return (name, name, None)
+
+ def EndElementHandler(self, name):
+ name = self.fixname(name)[0]
+ self.gen.emitEndElement(name)
+
+ def DefaultHandler(self, text):
+ self.gen.emitRawText(text)
+
+def test():
+ import sys
+ p = TALParser()
+ file = "tests/input/test01.xml"
+ if sys.argv[1:]:
+ file = sys.argv[1]
+ p.parseFile(file)
+ program, macros = p.getCode()
+ from 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/tests/__init__.py b/tests/__init__.py
new file mode 100644
index 0000000..b711d36
--- /dev/null
+++ b/tests/__init__.py
@@ -0,0 +1,2 @@
+#
+# This file is necessary to make this directory a package.
diff --git a/tests/input/__init__.py b/tests/input/__init__.py
new file mode 100644
index 0000000..b711d36
--- /dev/null
+++ b/tests/input/__init__.py
@@ -0,0 +1,2 @@
+#
+# This file is necessary to make this directory a package.
diff --git a/tests/input/test01.html b/tests/input/test01.html
new file mode 100644
index 0000000..e2ae0c4
--- /dev/null
+++ b/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/tests/input/test01.xml b/tests/input/test01.xml
new file mode 100644
index 0000000..82038e9
--- /dev/null
+++ b/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/tests/input/test02.html b/tests/input/test02.html
new file mode 100644
index 0000000..df2fb18
--- /dev/null
+++ b/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/tests/input/test02.xml b/tests/input/test02.xml
new file mode 100644
index 0000000..69567ea
--- /dev/null
+++ b/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/tests/input/test03.html b/tests/input/test03.html
new file mode 100644
index 0000000..a0230e1
--- /dev/null
+++ b/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/tests/input/test03.xml b/tests/input/test03.xml
new file mode 100644
index 0000000..830149d
--- /dev/null
+++ b/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/tests/input/test04.html b/tests/input/test04.html
new file mode 100644
index 0000000..bdaad39
--- /dev/null
+++ b/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/tests/input/test04.xml b/tests/input/test04.xml
new file mode 100644
index 0000000..bde6cef
--- /dev/null
+++ b/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/tests/input/test05.html b/tests/input/test05.html
new file mode 100644
index 0000000..21f6b68
--- /dev/null
+++ b/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/tests/input/test05.xml b/tests/input/test05.xml
new file mode 100644
index 0000000..fcaaf6b
--- /dev/null
+++ b/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/tests/input/test06.html b/tests/input/test06.html
new file mode 100644
index 0000000..ac1264d
--- /dev/null
+++ b/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/tests/input/test06.xml b/tests/input/test06.xml
new file mode 100644
index 0000000..b32bd0f
--- /dev/null
+++ b/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/tests/input/test07.html b/tests/input/test07.html
new file mode 100644
index 0000000..bff98f0
--- /dev/null
+++ b/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/tests/input/test07.xml b/tests/input/test07.xml
new file mode 100644
index 0000000..e5c520a
--- /dev/null
+++ b/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/tests/input/test08.html b/tests/input/test08.html
new file mode 100644
index 0000000..1e4915b
--- /dev/null
+++ b/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/tests/input/test08.xml b/tests/input/test08.xml
new file mode 100644
index 0000000..b0360fa
--- /dev/null
+++ b/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/tests/input/test09.html b/tests/input/test09.html
new file mode 100644
index 0000000..35f481a
--- /dev/null
+++ b/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/tests/input/test09.xml b/tests/input/test09.xml
new file mode 100644
index 0000000..c3d10d7
--- /dev/null
+++ b/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/tests/input/test10.html b/tests/input/test10.html
new file mode 100644
index 0000000..6ecca4c
--- /dev/null
+++ b/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/tests/input/test11.html b/tests/input/test11.html
new file mode 100644
index 0000000..435f95c
--- /dev/null
+++ b/tests/input/test11.html
@@ -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/tests/input/test11.xml b/tests/input/test11.xml
new file mode 100644
index 0000000..435f95c
--- /dev/null
+++ b/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/tests/input/test12.html b/tests/input/test12.html
new file mode 100644
index 0000000..94d9a66
--- /dev/null
+++ b/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/tests/input/test13.html b/tests/input/test13.html
new file mode 100644
index 0000000..d68e0ce
--- /dev/null
+++ b/tests/input/test13.html
@@ -0,0 +1,7 @@
+Here's a stray greater than: >
+
+<script>
+ <!-- no comment -->
+ <notag>
+ &noentity;
+</script>
diff --git a/tests/input/test14.html b/tests/input/test14.html
new file mode 100644
index 0000000..0aaa751
--- /dev/null
+++ b/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/tests/input/test14.xml b/tests/input/test14.xml
new file mode 100644
index 0000000..c596135
--- /dev/null
+++ b/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/tests/input/test15.html b/tests/input/test15.html
new file mode 100644
index 0000000..0cd456e
--- /dev/null
+++ b/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/tests/input/test16.html b/tests/input/test16.html
new file mode 100644
index 0000000..1414f45
--- /dev/null
+++ b/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/tests/input/test16.xml b/tests/input/test16.xml
new file mode 100755
index 0000000..05860d8
--- /dev/null
+++ b/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/tests/input/test17.html b/tests/input/test17.html
new file mode 100644
index 0000000..5a5ebb3
--- /dev/null
+++ b/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/tests/input/test17.xml b/tests/input/test17.xml
new file mode 100644
index 0000000..ecb617a
--- /dev/null
+++ b/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/tests/input/test18.html b/tests/input/test18.html
new file mode 100644
index 0000000..c3a5c26
--- /dev/null
+++ b/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/tests/input/test18.xml b/tests/input/test18.xml
new file mode 100644
index 0000000..5a0cca4
--- /dev/null
+++ b/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/tests/input/test19.html b/tests/input/test19.html
new file mode 100644
index 0000000..a56632a
--- /dev/null
+++ b/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/tests/input/test20.html b/tests/input/test20.html
new file mode 100644
index 0000000..f302213
--- /dev/null
+++ b/tests/input/test20.html
@@ -0,0 +1 @@
+<span i18n:translate="">replaceable <p tal:replace="str:here">content</p></span>
diff --git a/tests/input/test21.html b/tests/input/test21.html
new file mode 100644
index 0000000..95f925e
--- /dev/null
+++ b/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/tests/input/test22.html b/tests/input/test22.html
new file mode 100644
index 0000000..26fa45a
--- /dev/null
+++ b/tests/input/test22.html
@@ -0,0 +1,4 @@
+<span i18n:translate="">
+ <span i18n:name="name"><b>Jim</b></span> was born in
+ <span i18n:name="country">the USA</span>.
+</span>
diff --git a/tests/input/test23.html b/tests/input/test23.html
new file mode 100644
index 0000000..bfe6665
--- /dev/null
+++ b/tests/input/test23.html
@@ -0,0 +1,2 @@
+<span i18n:data="here/currentTime"
+ i18n:translate="timefmt">2:32 pm</span>
diff --git a/tests/input/test24.html b/tests/input/test24.html
new file mode 100644
index 0000000..e5b7b6e
--- /dev/null
+++ b/tests/input/test24.html
@@ -0,0 +1,3 @@
+<input name="Delete"
+ tal:attributes="name string:delete_button"
+ i18n:attributes="name">
diff --git a/tests/input/test25.html b/tests/input/test25.html
new file mode 100644
index 0000000..25a99cf
--- /dev/null
+++ b/tests/input/test25.html
@@ -0,0 +1 @@
+<input name="Delete" i18n:attributes="name">
diff --git a/tests/input/test26.html b/tests/input/test26.html
new file mode 100644
index 0000000..fa5a99d
--- /dev/null
+++ b/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/tests/input/test27.html b/tests/input/test27.html
new file mode 100644
index 0000000..b9c16cb
--- /dev/null
+++ b/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/tests/input/test28.html b/tests/input/test28.html
new file mode 100644
index 0000000..14fd7d9
--- /dev/null
+++ b/tests/input/test28.html
@@ -0,0 +1,5 @@
+<p i18n:translate="verify">Your contact email address is recorded as
+ <span i18n:name="email">
+ <a href="mailto:user@example.com"
+ tal:content="request/submitter">user@host.com</a></span>
+</p>
diff --git a/tests/input/test29.html b/tests/input/test29.html
new file mode 100644
index 0000000..665b0ee
--- /dev/null
+++ b/tests/input/test29.html
@@ -0,0 +1,4 @@
+At the tone the time will be
+<span i18n:data="here/currentTime"
+ i18n:translate="timefmt"
+ i18n:name="time">2:32 pm</span>... beep!
diff --git a/tests/input/test30.html b/tests/input/test30.html
new file mode 100644
index 0000000..6f8c6ef
--- /dev/null
+++ b/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/tests/input/test31.html b/tests/input/test31.html
new file mode 100644
index 0000000..c821761
--- /dev/null
+++ b/tests/input/test31.html
@@ -0,0 +1,7 @@
+<p i18n:translate="verify">Your contact email address is recorded as
+<span 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/tests/input/test32.html b/tests/input/test32.html
new file mode 100644
index 0000000..3b09bad
--- /dev/null
+++ b/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/tests/input/test_metal1.html b/tests/input/test_metal1.html
new file mode 100644
index 0000000..a5371ce
--- /dev/null
+++ b/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/tests/input/test_metal2.html b/tests/input/test_metal2.html
new file mode 100644
index 0000000..425508a
--- /dev/null
+++ b/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/tests/input/test_metal3.html b/tests/input/test_metal3.html
new file mode 100644
index 0000000..b0af907
--- /dev/null
+++ b/tests/input/test_metal3.html
@@ -0,0 +1 @@
+<span tal:attributes="class string:foo">Should not get attr in metal</span>
diff --git a/tests/markbench.py b/tests/markbench.py
new file mode 100644
index 0000000..9791e67
--- /dev/null
+++ b/tests/markbench.py
@@ -0,0 +1,138 @@
+#! /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.0 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+'''Run benchmarks of TAL vs. DTML'''
+
+import warnings
+warnings.filterwarnings("ignore", category=DeprecationWarning)
+
+import os
+os.environ['NO_SECURITY'] = 'true'
+
+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)
+
+tal_fn = 'benchmark/tal%.2d.html'
+dtml_fn = 'benchmark/dtml%.2d.html'
+
+def compare(n, count, profiler=None):
+ 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):
+ n = 1
+ 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)
+ n = n + 1
+
+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
+ if len(sys.argv) > 1 and sys.argv[1] == "-p":
+ import profile
+ profiler = profile.Profile()
+ del sys.argv[1]
+
+ if len(sys.argv) > 1:
+ for arg in sys.argv[1:]:
+ compare(int(arg), 25, profiler)
+ else:
+ main(25, profiler)
+
+ 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/tests/output/__init__.py b/tests/output/__init__.py
new file mode 100644
index 0000000..b711d36
--- /dev/null
+++ b/tests/output/__init__.py
@@ -0,0 +1,2 @@
+#
+# This file is necessary to make this directory a package.
diff --git a/tests/output/test01.html b/tests/output/test01.html
new file mode 100644
index 0000000..7064db0
--- /dev/null
+++ b/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/tests/output/test01.xml b/tests/output/test01.xml
new file mode 100644
index 0000000..91e9851
--- /dev/null
+++ b/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/tests/output/test02.html b/tests/output/test02.html
new file mode 100644
index 0000000..8d081fc
--- /dev/null
+++ b/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/tests/output/test02.xml b/tests/output/test02.xml
new file mode 100644
index 0000000..71ff075
--- /dev/null
+++ b/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/tests/output/test03.html b/tests/output/test03.html
new file mode 100644
index 0000000..7fb5156
--- /dev/null
+++ b/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/tests/output/test03.xml b/tests/output/test03.xml
new file mode 100644
index 0000000..24be638
--- /dev/null
+++ b/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/tests/output/test04.html b/tests/output/test04.html
new file mode 100644
index 0000000..f0666da
--- /dev/null
+++ b/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/tests/output/test04.xml b/tests/output/test04.xml
new file mode 100644
index 0000000..8b73d02
--- /dev/null
+++ b/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/tests/output/test05.html b/tests/output/test05.html
new file mode 100644
index 0000000..006851a
--- /dev/null
+++ b/tests/output/test05.html
@@ -0,0 +1,9 @@
+<html>
+
+ <body>
+
+ <h1>This is the body of test5</h1>
+
+ </body>
+
+</html>
diff --git a/tests/output/test05.xml b/tests/output/test05.xml
new file mode 100644
index 0000000..0bc2691
--- /dev/null
+++ b/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/tests/output/test06.html b/tests/output/test06.html
new file mode 100644
index 0000000..d3f58d9
--- /dev/null
+++ b/tests/output/test06.html
@@ -0,0 +1,7 @@
+<html>
+ <body>
+
+ <h1>This is the body of test5</h1>
+
+ </body>
+</html>
diff --git a/tests/output/test06.xml b/tests/output/test06.xml
new file mode 100644
index 0000000..b9ad4ac
--- /dev/null
+++ b/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/tests/output/test07.html b/tests/output/test07.html
new file mode 100644
index 0000000..e0b3d88
--- /dev/null
+++ b/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/tests/output/test07.xml b/tests/output/test07.xml
new file mode 100644
index 0000000..8884d97
--- /dev/null
+++ b/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/tests/output/test08.html b/tests/output/test08.html
new file mode 100644
index 0000000..06e01b2
--- /dev/null
+++ b/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/tests/output/test08.xml b/tests/output/test08.xml
new file mode 100644
index 0000000..51a969c
--- /dev/null
+++ b/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/tests/output/test09.html b/tests/output/test09.html
new file mode 100644
index 0000000..844c1a9
--- /dev/null
+++ b/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/tests/output/test09.xml b/tests/output/test09.xml
new file mode 100644
index 0000000..c3d10d7
--- /dev/null
+++ b/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/tests/output/test10.html b/tests/output/test10.html
new file mode 100644
index 0000000..d9cc7ed
--- /dev/null
+++ b/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/tests/output/test11.html b/tests/output/test11.html
new file mode 100644
index 0000000..caba039
--- /dev/null
+++ b/tests/output/test11.html
@@ -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/tests/output/test11.xml b/tests/output/test11.xml
new file mode 100644
index 0000000..caba039
--- /dev/null
+++ b/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/tests/output/test12.html b/tests/output/test12.html
new file mode 100644
index 0000000..9533b42
--- /dev/null
+++ b/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/tests/output/test13.html b/tests/output/test13.html
new file mode 100644
index 0000000..d68e0ce
--- /dev/null
+++ b/tests/output/test13.html
@@ -0,0 +1,7 @@
+Here's a stray greater than: >
+
+<script>
+ <!-- no comment -->
+ <notag>
+ &noentity;
+</script>
diff --git a/tests/output/test14.html b/tests/output/test14.html
new file mode 100644
index 0000000..b9bf468
--- /dev/null
+++ b/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/tests/output/test14.xml b/tests/output/test14.xml
new file mode 100644
index 0000000..67c0c37
--- /dev/null
+++ b/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/tests/output/test15.html b/tests/output/test15.html
new file mode 100644
index 0000000..314fd43
--- /dev/null
+++ b/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/tests/output/test16.html b/tests/output/test16.html
new file mode 100644
index 0000000..d3ea228
--- /dev/null
+++ b/tests/output/test16.html
@@ -0,0 +1 @@
+<a href="/base/valid/link.html">blah, blah</a>
diff --git a/tests/output/test16.xml b/tests/output/test16.xml
new file mode 100755
index 0000000..b470734
--- /dev/null
+++ b/tests/output/test16.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0"?>
+<body>
+
+<img href="about:foo" alt="baz"/>
+
+</body>
diff --git a/tests/output/test17.html b/tests/output/test17.html
new file mode 100644
index 0000000..e50997d
--- /dev/null
+++ b/tests/output/test17.html
@@ -0,0 +1,6 @@
+Yes
+Yes
+Yes
+
+Yes
+Yes
diff --git a/tests/output/test17.xml b/tests/output/test17.xml
new file mode 100644
index 0000000..7a54cdb
--- /dev/null
+++ b/tests/output/test17.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0"?>
+<body>
+Yes
+Yes
+Yes
+
+Yes
+Yes
+</body>
diff --git a/tests/output/test18.html b/tests/output/test18.html
new file mode 100644
index 0000000..f49e29e
--- /dev/null
+++ b/tests/output/test18.html
@@ -0,0 +1,16 @@
+Content
+
+
+
+Content
+
+
+
+<p>Content</p>
+<p></p>
+<img>
+
+Yes
+Yes
+Yes
+Yes
diff --git a/tests/output/test18.xml b/tests/output/test18.xml
new file mode 100644
index 0000000..77eba02
--- /dev/null
+++ b/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/tests/output/test19.html b/tests/output/test19.html
new file mode 100644
index 0000000..2341a4a
--- /dev/null
+++ b/tests/output/test19.html
@@ -0,0 +1,3 @@
+<span>REPLACE THIS</span>
+<span>MSGID</span>
+<span>AND ANOTHER TRANSLATED STRING</span>
diff --git a/tests/output/test20.html b/tests/output/test20.html
new file mode 100644
index 0000000..606b989
--- /dev/null
+++ b/tests/output/test20.html
@@ -0,0 +1 @@
+<span>REPLACEABLE HERE</span>
diff --git a/tests/output/test21.html b/tests/output/test21.html
new file mode 100644
index 0000000..95b3b08
--- /dev/null
+++ b/tests/output/test21.html
@@ -0,0 +1 @@
+<span>Lomax WAS BORN IN Antarctica.</span>
diff --git a/tests/output/test22.html b/tests/output/test22.html
new file mode 100644
index 0000000..6c1b6de
--- /dev/null
+++ b/tests/output/test22.html
@@ -0,0 +1 @@
+<span><b>Jim</b> WAS BORN IN the USA.</span>
diff --git a/tests/output/test23.html b/tests/output/test23.html
new file mode 100644
index 0000000..0ea1654
--- /dev/null
+++ b/tests/output/test23.html
@@ -0,0 +1 @@
+<span>59 minutes after 6 PM</span>
diff --git a/tests/output/test24.html b/tests/output/test24.html
new file mode 100644
index 0000000..e351a6a
--- /dev/null
+++ b/tests/output/test24.html
@@ -0,0 +1 @@
+<input name="DELETE_BUTTON">
diff --git a/tests/output/test25.html b/tests/output/test25.html
new file mode 100644
index 0000000..6b80bd3
--- /dev/null
+++ b/tests/output/test25.html
@@ -0,0 +1 @@
+<input name="DELETE">
diff --git a/tests/output/test26.html b/tests/output/test26.html
new file mode 100644
index 0000000..9d179a6
--- /dev/null
+++ b/tests/output/test26.html
@@ -0,0 +1 @@
+<span>7 is the JOB NUMBER</span>
diff --git a/tests/output/test27.html b/tests/output/test27.html
new file mode 100644
index 0000000..96229e4
--- /dev/null
+++ b/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/tests/output/test28.html b/tests/output/test28.html
new file mode 100644
index 0000000..96229e4
--- /dev/null
+++ b/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/tests/output/test29.html b/tests/output/test29.html
new file mode 100644
index 0000000..019e34d
--- /dev/null
+++ b/tests/output/test29.html
@@ -0,0 +1,2 @@
+At the tone the time will be
+59 minutes after 6 PM... beep!
diff --git a/tests/output/test30.html b/tests/output/test30.html
new file mode 100644
index 0000000..964b772
--- /dev/null
+++ b/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/tests/output/test31.html b/tests/output/test31.html
new file mode 100644
index 0000000..964b772
--- /dev/null
+++ b/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/tests/output/test32.html b/tests/output/test32.html
new file mode 100644
index 0000000..f39bd97
--- /dev/null
+++ b/tests/output/test32.html
@@ -0,0 +1 @@
+<span><span>Lomax</span> was born in <span>Antarctica</span></span>
diff --git a/tests/output/test_metal1.html b/tests/output/test_metal1.html
new file mode 100644
index 0000000..671ccd0
--- /dev/null
+++ b/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 metal:fill-slot="OUTERSLOT">
+ <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 metal:fill-slot="OUTERSLOT">
+ <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 metal:fill-slot="INNERSLOT">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/tests/output/test_metal2.html b/tests/output/test_metal2.html
new file mode 100644
index 0000000..7e56c0c
--- /dev/null
+++ b/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/tests/output/test_metal3.html b/tests/output/test_metal3.html
new file mode 100644
index 0000000..b0af907
--- /dev/null
+++ b/tests/output/test_metal3.html
@@ -0,0 +1 @@
+<span tal:attributes="class string:foo">Should not get attr in metal</span>
diff --git a/tests/run.py b/tests/run.py
new file mode 100644
index 0000000..32de437
--- /dev/null
+++ b/tests/run.py
@@ -0,0 +1,42 @@
+#! /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.0 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Run all tests."""
+
+import sys
+import unittest
+
+import zope.tal.tests.utils
+import zope.tal.tests.test_htmltalparser
+import zope.tal.tests.test_talinterpreter
+import zope.tal.tests.test_files
+import zope.tal.tests.test_sourcepos
+
+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/tests/test_files.py b/tests/test_files.py
new file mode 100644
index 0000000..bb9b505
--- /dev/null
+++ b/tests/test_files.py
@@ -0,0 +1,86 @@
+#! /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.0 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Tests that run driver.py over input files comparing to output files."""
+
+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_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/tests/test_htmltalparser.py b/tests/test_htmltalparser.py
new file mode 100644
index 0000000..4257dc6
--- /dev/null
+++ b/tests/test_htmltalparser.py
@@ -0,0 +1,850 @@
+#! /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.0 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Tests for the HTMLTALParser code generator."""
+
+import pprint
+import sys
+import unittest
+
+from zope.tal import htmltalparser
+from zope.tal import 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'}),
+ ('insertText', ('$string:foo$',
+ [('startTag', ('p', [('tal:replace', 'string:foo', 'tal')])),
+ rawtext('bar</p>')])),
+ ('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'}),
+ ('insertText', ('$string:foo$',
+ [('startTag', ('p',
+ [('tal:replace', 'text string:foo', 'tal')])),
+ rawtext('bar</p>')])),
+ ('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>'}),
+ ('insertStructure', ('$string:<br>$', {},
+ [('startTag', ('p',
+ [('tal:replace', 'structure string:<br>', 'tal')])),
+ rawtext('bar</p>')])),
+ ('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'}),
+ ('insertText', ('$x$',
+ [('startTag', ('span', [('tal:replace', 'x', 'tal')])),
+ rawtext('dummy</span>')])),
+ ('endScope', ()),
+ rawtext('</p>')])),
+ ('endScope', ()),
+ ])
+
+ 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),
+ ('name', 'name="bar"'),
+ ('tal:attributes',
+ 'href string:http://www.zope.org; x string:y', 'tal'),
+ ('x', None, 'insert', '$string:y$', 0)])),
+ ('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>'}),
+ ('insertStructure',
+ ('$string:<img>$',
+ {'src': ('$string:foo.png$', 0)},
+ [('startTag', ('p',
+ [('tal:replace', 'structure string:<img>', 'tal'),
+ ('tal:attributes', 'src string:foo.png',
+ 'tal')])),
+ rawtext('duh</p>')])),
+ ('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',
+ ([('insertText', ('$notHere$',
+ [('startTag', ('p',
+ [('tal:on-error', 'string:error', 'tal'),
+ ('tal:replace', 'notHere', 'tal')])),
+ rawtext('okay</p>')]))],
+ [('startTag', ('p',
+ [('tal:on-error', 'string:error', 'tal'),
+ ('tal:replace', 'notHere', 'tal')])),
+ ('insertText', ('$string:error$', [])),
+ rawtext('</p>')])),
+ ('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'>")
+
+ 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)
+
+ #
+ # 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),
+ ('i18n:attributes', 'alt', 'i18n')])),
+ ('endScope', ()),
+ ])
+
+ 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'}),
+ ('insertText',
+ ('$str:here$',
+ [('startTag', ('p', [('tal:replace', 'str:here', 'tal')])),
+ ('rawtextOffset', ('content</p>', 11))])),
+ ('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',
+ [('startEndTag',
+ ('span',
+ [('tal:replace', 'str:Lomax', 'tal'),
+ ('i18n:name', 'name', 'i18n')]))],
+ '$str:Lomax$')),
+ ('rawtextBeginScope',
+ (' was born in\n ',
+ 2,
+ (3, 2),
+ 1,
+ {'i18n:name': 'country', 'tal:replace': 'str:Antarctica'})),
+ ('i18nVariable',
+ ('country',
+ [('startEndTag',
+ ('span',
+ [('tal:replace', 'str:Antarctica', 'tal'),
+ ('i18n:name', 'country', 'i18n')]))],
+ '$str:Antarctica$')),
+ ('endScope', ()),
+ ('rawtextColumn', ('.\n', 0))])),
+ ('endScope', ()),
+ ('rawtextColumn', ('</span>\n', 0))
+ ])
+
+ def test_i18n_name_implicit_value(self):
+ # input/test22.html
+ self._run_check('''\
+<span i18n:translate="">
+ <span i18n:name="name"><b>Jim</b></span> was born in
+ <span 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'})),
+ ('i18nVariable',
+ ('name',
+ [('rawtextOffset', ('<b>Jim</b>', 10))], None)),
+ ('rawtextBeginScope',
+ (' was born in\n ', 2, (3, 2), 1, {'i18n:name': 'country'})),
+ ('i18nVariable',
+ ('country',
+ [('rawtextOffset', ('the USA', 7))], None)),
+ ('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('''\
+At the tone the time will be
+<span i18n:data="here/currentTime"
+ i18n:translate="timefmt"
+ i18n:name="time">2:32 pm</span>... beep!
+''', [
+ ('rawtextBeginScope',
+ ('At the tone the time will be\n',
+ 0,
+ (2, 0),
+ 0,
+ {'i18n:data': 'here/currentTime',
+ 'i18n:name': 'time',
+ 'i18n:translate': 'timefmt'})),
+ ('insertTranslation',
+ ('timefmt',
+ [('startTag',
+ ('span',
+ [('i18n:data', 'here/currentTime', 'i18n'),
+ ('i18n:translate', 'timefmt', 'i18n'),
+ ('i18n:name', 'time', 'i18n')])),
+ ('i18nVariable', ('time', [], None))],
+ '$here/currentTime$')),
+ ('endScope', ()),
+ ('rawtextColumn', ('... beep!\n', 0))
+ ])
+
+ def test_i18n_explicit_msgid_with_name(self):
+ # input/test26.html
+ self._run_check('''\
+<span i18n:translate="jobnum">
+ Job #<span tal:replace="context/@@object_name"
+ i18n:name="jobnum">NN</span></span>
+''', [
+ ('setPosition', (1, 0)),
+ ('beginScope', {'i18n:translate': 'jobnum'}),
+ ('startTag', ('span', [('i18n:translate', 'jobnum', 'i18n')])),
+ ('insertTranslation',
+ ('jobnum',
+ [('rawtextBeginScope',
+ ('\n Job #',
+ 9,
+ (2, 9),
+ 0,
+ {'i18n:name': 'jobnum', 'tal:replace': 'context/@@object_name'})),
+ ('i18nVariable',
+ ('jobnum',
+ [('startTag',
+ ('span',
+ [('tal:replace', 'context/@@object_name', 'tal'),
+ ('i18n:name', 'jobnum', 'i18n')])),
+ ('rawtextOffset', ('NN', 2)),
+ ('rawtextOffset', ('</span>', 7))],
+ '$context/@@object_name$')),
+ ('endScope', ())])),
+ ('endScope', ()),
+ ('rawtextColumn', ('</span>\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 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'})),
+ ('i18nVariable',
+ ('email',
+ [('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)),
+ ('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)),
+ ('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/tests/test_sourcepos.py b/tests/test_sourcepos.py
new file mode 100644
index 0000000..8d11f27
--- /dev/null
+++ b/tests/test_sourcepos.py
@@ -0,0 +1,93 @@
+#! /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.0 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Tests for TALInterpreter."""
+
+import sys
+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/tests/test_talinterpreter.py b/tests/test_talinterpreter.py
new file mode 100644
index 0000000..81d10bf
--- /dev/null
+++ b/tests/test_talinterpreter.py
@@ -0,0 +1,125 @@
+#! /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.0 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Tests for TALInterpreter."""
+
+import sys
+import unittest
+
+from StringIO import StringIO
+
+from zope.tal.taldefs import METALError, I18NError
+from zope.tal.htmltalparser import HTMLTALParser
+from zope.tal.talinterpreter import TALInterpreter
+from zope.tal.dummyengine import DummyEngine
+from zope.tal.tests import utils
+
+
+class TestCaseBase(unittest.TestCase):
+
+ def _compile(self, source):
+ parser = HTMLTALParser()
+ 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 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")
+
+
+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_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)
+
+
+def test_suite():
+ suite = unittest.makeSuite(I18NErrorsTestCase)
+ suite.addTest(unittest.makeSuite(MacroErrorsTestCase))
+ suite.addTest(unittest.makeSuite(OutputPresentationTestCase))
+ return suite
+
+if __name__ == "__main__":
+ errs = utils.run_suite(test_suite())
+ sys.exit(errs and 1 or 0)
diff --git a/tests/test_xmlparser.py b/tests/test_xmlparser.py
new file mode 100644
index 0000000..36eebfa
--- /dev/null
+++ b/tests/test_xmlparser.py
@@ -0,0 +1,261 @@
+#! /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.0 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Tests for XMLParser.py."""
+
+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:
+ 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' c:d='v' e-f='v'/>""", [
+ ("starttag", "a", ["a.b", "v", "c:d", "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 $ >")
+
+
+# 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/tests/utils.py b/tests/utils.py
new file mode 100644
index 0000000..81d102f
--- /dev/null
+++ b/tests/utils.py
@@ -0,0 +1,63 @@
+##############################################################################
+#
+# 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.0 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Helper functions for the test suite."""
+
+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/timer.py b/timer.py
new file mode 100644
index 0000000..c8f4310
--- /dev/null
+++ b/timer.py
@@ -0,0 +1,59 @@
+#! /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.0 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+Helper program to time compilation and interpretation
+"""
+
+import getopt
+import sys
+import time
+
+from cPickle import dumps, loads
+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/translationcontext.py b/translationcontext.py
new file mode 100644
index 0000000..568ecad
--- /dev/null
+++ b/translationcontext.py
@@ -0,0 +1,41 @@
+##############################################################################
+#
+# 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.0 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""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: translationcontext.py,v 1.2 2002/12/25 14:15:29 jim Exp $
+"""
+
+DEFAULT_DOMAIN = "default"
+
+class TranslationContext:
+ """Information about the I18N settings of a TAL processor."""
+
+ def __init__(self, parent=None, domain=None, target=None, source=None):
+ if parent:
+ if not domain:
+ domain = parent.domain
+ if not target:
+ target = parent.target
+ if not source:
+ source = parent.source
+ elif domain is None:
+ domain = DEFAULT_DOMAIN
+
+ self.parent = parent
+ self.domain = domain
+ self.target = target
+ self.source = source
diff --git a/xmlparser.py b/xmlparser.py
new file mode 100644
index 0000000..03b383d
--- /dev/null
+++ b/xmlparser.py
@@ -0,0 +1,85 @@
+##############################################################################
+#
+# 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.0 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+Generic expat-based XML parser base class.
+"""
+
+import logging
+
+
+class XMLParser:
+
+ ordered_attributes = 0
+
+ handler_names = [
+ "StartElementHandler",
+ "EndElementHandler",
+ "ProcessingInstructionHandler",
+ "CharacterDataHandler",
+ "UnparsedEntityDeclHandler",
+ "NotationDeclHandler",
+ "StartNamespaceDeclHandler",
+ "EndNamespaceDeclHandler",
+ "CommentHandler",
+ "StartCdataSectionHandler",
+ "EndCdataSectionHandler",
+ "DefaultHandler",
+ "DefaultHandlerExpand",
+ "NotStandaloneHandler",
+ "ExternalEntityRefHandler",
+ "XmlDeclHandler",
+ "StartDoctypeDeclHandler",
+ "EndDoctypeDeclHandler",
+ "ElementDeclHandler",
+ "AttlistDeclHandler"
+ ]
+
+ def __init__(self, encoding=None):
+ self.parser = p = self.createParser()
+ if self.ordered_attributes:
+ try:
+ self.parser.ordered_attributes = self.ordered_attributes
+ except AttributeError:
+ 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):
+ 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)