diff options
Diffstat (limited to 'src/zope')
206 files changed, 11065 insertions, 0 deletions
diff --git a/src/zope/tal/DEPENDENCIES.cfg b/src/zope/tal/DEPENDENCIES.cfg new file mode 100644 index 0000000..2886de2 --- /dev/null +++ b/src/zope/tal/DEPENDENCIES.cfg @@ -0,0 +1,4 @@ +zope.i18n +zope.i18nmessageid +zope.interface +zope.deprecation diff --git a/src/zope/tal/__init__.py b/src/zope/tal/__init__.py new file mode 100644 index 0000000..b711d36 --- /dev/null +++ b/src/zope/tal/__init__.py @@ -0,0 +1,2 @@ +# +# This file is necessary to make this directory a package. diff --git a/src/zope/tal/benchmark/__init__.py b/src/zope/tal/benchmark/__init__.py new file mode 100644 index 0000000..b711d36 --- /dev/null +++ b/src/zope/tal/benchmark/__init__.py @@ -0,0 +1,2 @@ +# +# This file is necessary to make this directory a package. diff --git a/src/zope/tal/benchmark/dtml01.html b/src/zope/tal/benchmark/dtml01.html new file mode 100644 index 0000000..180b47c --- /dev/null +++ b/src/zope/tal/benchmark/dtml01.html @@ -0,0 +1 @@ +baseline diff --git a/src/zope/tal/benchmark/dtml02.html b/src/zope/tal/benchmark/dtml02.html new file mode 100644 index 0000000..33d978d --- /dev/null +++ b/src/zope/tal/benchmark/dtml02.html @@ -0,0 +1,100 @@ + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. diff --git a/src/zope/tal/benchmark/dtml03.html b/src/zope/tal/benchmark/dtml03.html new file mode 100644 index 0000000..aea01aa --- /dev/null +++ b/src/zope/tal/benchmark/dtml03.html @@ -0,0 +1,8 @@ + <td bgcolor="white">&dtml-x0;</td> + <td bgcolor="white">&dtml-x1;</td> + <td bgcolor="white">&dtml-x2;</td> + <td bgcolor="white">&dtml-x3;</td> + <td bgcolor="white">&dtml-x4;</td> + <td bgcolor="white">&dtml-x5;</td> + <td bgcolor="white">&dtml-x6;</td> + <td bgcolor="white">&dtml-x7;</td> diff --git a/src/zope/tal/benchmark/dtml04.html b/src/zope/tal/benchmark/dtml04.html new file mode 100644 index 0000000..1a3214f --- /dev/null +++ b/src/zope/tal/benchmark/dtml04.html @@ -0,0 +1,6 @@ +<dtml-in r8> + <td bgcolor="white">&dtml-x0;</td> + <td bgcolor="white">&dtml-x1;</td> + <td bgcolor="white">&dtml-x2;</td> + <td bgcolor="white">&dtml-x3;</td> +</dtml-in> diff --git a/src/zope/tal/benchmark/dtml05.html b/src/zope/tal/benchmark/dtml05.html new file mode 100644 index 0000000..70b53cb --- /dev/null +++ b/src/zope/tal/benchmark/dtml05.html @@ -0,0 +1,10 @@ +<dtml-in r8> + <td bgcolor="white">&dtml-x0;</td> + <td bgcolor="white">&dtml-x1;</td> + <td bgcolor="white">&dtml-x2;</td> + <td bgcolor="white">&dtml-x3;</td> + <td bgcolor="white">&dtml-x4;</td> + <td bgcolor="white">&dtml-x5;</td> + <td bgcolor="white">&dtml-x6;</td> + <td bgcolor="white">&dtml-x7;</td> +</dtml-in> diff --git a/src/zope/tal/benchmark/dtml06.html b/src/zope/tal/benchmark/dtml06.html new file mode 100644 index 0000000..11e5cf2 --- /dev/null +++ b/src/zope/tal/benchmark/dtml06.html @@ -0,0 +1,14 @@ +<dtml-in r2> +<dtml-in r2> +<dtml-in r2> + <td bgcolor="white">&dtml-x0;</td> + <td bgcolor="white">&dtml-x1;</td> + <td bgcolor="white">&dtml-x2;</td> + <td bgcolor="white">&dtml-x3;</td> + <td bgcolor="white">&dtml-x4;</td> + <td bgcolor="white">&dtml-x5;</td> + <td bgcolor="white">&dtml-x6;</td> + <td bgcolor="white">&dtml-x7;</td> +</dtml-in> +</dtml-in> +</dtml-in> diff --git a/src/zope/tal/benchmark/dtml07.html b/src/zope/tal/benchmark/dtml07.html new file mode 100644 index 0000000..48f50c7 --- /dev/null +++ b/src/zope/tal/benchmark/dtml07.html @@ -0,0 +1,73 @@ + <td bgcolor="white">&dtml-x0;</td> + <td bgcolor="white">&dtml-x1;</td> + <td bgcolor="white">&dtml-x2;</td> + <td bgcolor="white">&dtml-x3;</td> + <td bgcolor="white">&dtml-x4;</td> + <td bgcolor="white">&dtml-x5;</td> + <td bgcolor="white">&dtml-x6;</td> + <td bgcolor="white">&dtml-x7;</td> + + <td bgcolor="white">&dtml-x0;</td> + <td bgcolor="white">&dtml-x1;</td> + <td bgcolor="white">&dtml-x2;</td> + <td bgcolor="white">&dtml-x3;</td> + <td bgcolor="white">&dtml-x4;</td> + <td bgcolor="white">&dtml-x5;</td> + <td bgcolor="white">&dtml-x6;</td> + <td bgcolor="white">&dtml-x7;</td> + + <td bgcolor="white">&dtml-x0;</td> + <td bgcolor="white">&dtml-x1;</td> + <td bgcolor="white">&dtml-x2;</td> + <td bgcolor="white">&dtml-x3;</td> + <td bgcolor="white">&dtml-x4;</td> + <td bgcolor="white">&dtml-x5;</td> + <td bgcolor="white">&dtml-x6;</td> + <td bgcolor="white">&dtml-x7;</td> + + <td bgcolor="white">&dtml-x0;</td> + <td bgcolor="white">&dtml-x1;</td> + <td bgcolor="white">&dtml-x2;</td> + <td bgcolor="white">&dtml-x3;</td> + <td bgcolor="white">&dtml-x4;</td> + <td bgcolor="white">&dtml-x5;</td> + <td bgcolor="white">&dtml-x6;</td> + <td bgcolor="white">&dtml-x7;</td> + + <td bgcolor="white">&dtml-x0;</td> + <td bgcolor="white">&dtml-x1;</td> + <td bgcolor="white">&dtml-x2;</td> + <td bgcolor="white">&dtml-x3;</td> + <td bgcolor="white">&dtml-x4;</td> + <td bgcolor="white">&dtml-x5;</td> + <td bgcolor="white">&dtml-x6;</td> + <td bgcolor="white">&dtml-x7;</td> + + <td bgcolor="white">&dtml-x0;</td> + <td bgcolor="white">&dtml-x1;</td> + <td bgcolor="white">&dtml-x2;</td> + <td bgcolor="white">&dtml-x3;</td> + <td bgcolor="white">&dtml-x4;</td> + <td bgcolor="white">&dtml-x5;</td> + <td bgcolor="white">&dtml-x6;</td> + <td bgcolor="white">&dtml-x7;</td> + + <td bgcolor="white">&dtml-x0;</td> + <td bgcolor="white">&dtml-x1;</td> + <td bgcolor="white">&dtml-x2;</td> + <td bgcolor="white">&dtml-x3;</td> + <td bgcolor="white">&dtml-x4;</td> + <td bgcolor="white">&dtml-x5;</td> + <td bgcolor="white">&dtml-x6;</td> + <td bgcolor="white">&dtml-x7;</td> + + <td bgcolor="white">&dtml-x0;</td> + <td bgcolor="white">&dtml-x1;</td> + <td bgcolor="white">&dtml-x2;</td> + <td bgcolor="white">&dtml-x3;</td> + <td bgcolor="white">&dtml-x4;</td> + <td bgcolor="white">&dtml-x5;</td> + <td bgcolor="white">&dtml-x6;</td> + <td bgcolor="white">&dtml-x7;</td> + + diff --git a/src/zope/tal/benchmark/dtml08.html b/src/zope/tal/benchmark/dtml08.html new file mode 100644 index 0000000..48f50c7 --- /dev/null +++ b/src/zope/tal/benchmark/dtml08.html @@ -0,0 +1,73 @@ + <td bgcolor="white">&dtml-x0;</td> + <td bgcolor="white">&dtml-x1;</td> + <td bgcolor="white">&dtml-x2;</td> + <td bgcolor="white">&dtml-x3;</td> + <td bgcolor="white">&dtml-x4;</td> + <td bgcolor="white">&dtml-x5;</td> + <td bgcolor="white">&dtml-x6;</td> + <td bgcolor="white">&dtml-x7;</td> + + <td bgcolor="white">&dtml-x0;</td> + <td bgcolor="white">&dtml-x1;</td> + <td bgcolor="white">&dtml-x2;</td> + <td bgcolor="white">&dtml-x3;</td> + <td bgcolor="white">&dtml-x4;</td> + <td bgcolor="white">&dtml-x5;</td> + <td bgcolor="white">&dtml-x6;</td> + <td bgcolor="white">&dtml-x7;</td> + + <td bgcolor="white">&dtml-x0;</td> + <td bgcolor="white">&dtml-x1;</td> + <td bgcolor="white">&dtml-x2;</td> + <td bgcolor="white">&dtml-x3;</td> + <td bgcolor="white">&dtml-x4;</td> + <td bgcolor="white">&dtml-x5;</td> + <td bgcolor="white">&dtml-x6;</td> + <td bgcolor="white">&dtml-x7;</td> + + <td bgcolor="white">&dtml-x0;</td> + <td bgcolor="white">&dtml-x1;</td> + <td bgcolor="white">&dtml-x2;</td> + <td bgcolor="white">&dtml-x3;</td> + <td bgcolor="white">&dtml-x4;</td> + <td bgcolor="white">&dtml-x5;</td> + <td bgcolor="white">&dtml-x6;</td> + <td bgcolor="white">&dtml-x7;</td> + + <td bgcolor="white">&dtml-x0;</td> + <td bgcolor="white">&dtml-x1;</td> + <td bgcolor="white">&dtml-x2;</td> + <td bgcolor="white">&dtml-x3;</td> + <td bgcolor="white">&dtml-x4;</td> + <td bgcolor="white">&dtml-x5;</td> + <td bgcolor="white">&dtml-x6;</td> + <td bgcolor="white">&dtml-x7;</td> + + <td bgcolor="white">&dtml-x0;</td> + <td bgcolor="white">&dtml-x1;</td> + <td bgcolor="white">&dtml-x2;</td> + <td bgcolor="white">&dtml-x3;</td> + <td bgcolor="white">&dtml-x4;</td> + <td bgcolor="white">&dtml-x5;</td> + <td bgcolor="white">&dtml-x6;</td> + <td bgcolor="white">&dtml-x7;</td> + + <td bgcolor="white">&dtml-x0;</td> + <td bgcolor="white">&dtml-x1;</td> + <td bgcolor="white">&dtml-x2;</td> + <td bgcolor="white">&dtml-x3;</td> + <td bgcolor="white">&dtml-x4;</td> + <td bgcolor="white">&dtml-x5;</td> + <td bgcolor="white">&dtml-x6;</td> + <td bgcolor="white">&dtml-x7;</td> + + <td bgcolor="white">&dtml-x0;</td> + <td bgcolor="white">&dtml-x1;</td> + <td bgcolor="white">&dtml-x2;</td> + <td bgcolor="white">&dtml-x3;</td> + <td bgcolor="white">&dtml-x4;</td> + <td bgcolor="white">&dtml-x5;</td> + <td bgcolor="white">&dtml-x6;</td> + <td bgcolor="white">&dtml-x7;</td> + + diff --git a/src/zope/tal/benchmark/dtml09.html b/src/zope/tal/benchmark/dtml09.html new file mode 100644 index 0000000..ce8e43e --- /dev/null +++ b/src/zope/tal/benchmark/dtml09.html @@ -0,0 +1,10 @@ +<dtml-in r64> + <td bgcolor="white">&dtml-x0;</td> + <td bgcolor="white">&dtml-x1;</td> + <td bgcolor="white">&dtml-x2;</td> + <td bgcolor="white">&dtml-x3;</td> + <td bgcolor="white">&dtml-x4;</td> + <td bgcolor="white">&dtml-x5;</td> + <td bgcolor="white">&dtml-x6;</td> + <td bgcolor="white">&dtml-x7;</td> +</dtml-in> diff --git a/src/zope/tal/benchmark/dtml10.html b/src/zope/tal/benchmark/dtml10.html new file mode 100644 index 0000000..3115f7c --- /dev/null +++ b/src/zope/tal/benchmark/dtml10.html @@ -0,0 +1,102 @@ +<dtml-in r64> + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. +</dtml-in> diff --git a/src/zope/tal/benchmark/dtml11.html b/src/zope/tal/benchmark/dtml11.html new file mode 100644 index 0000000..b0f71bd --- /dev/null +++ b/src/zope/tal/benchmark/dtml11.html @@ -0,0 +1,103 @@ +<dtml-in r64> + <td bgcolor="white">&dtml-x0;</td> + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. +</dtml-in> diff --git a/src/zope/tal/benchmark/dtml12.html b/src/zope/tal/benchmark/dtml12.html new file mode 100644 index 0000000..df2dab1 --- /dev/null +++ b/src/zope/tal/benchmark/dtml12.html @@ -0,0 +1,12 @@ +<dtml-in r8> + <dtml-let y0=x0 y1=x1 y2=x2 y3=x3 y4=x4 y5=x5 y6=x6 y7=x7> + <td bgcolor="white">&dtml-y0;</td> + <td bgcolor="white">&dtml-y1;</td> + <td bgcolor="white">&dtml-y2;</td> + <td bgcolor="white">&dtml-y3;</td> + <td bgcolor="white">&dtml-y4;</td> + <td bgcolor="white">&dtml-y5;</td> + <td bgcolor="white">&dtml-y6;</td> + <td bgcolor="white">&dtml-y7;</td> + </dtml-let> +</dtml-in> diff --git a/src/zope/tal/benchmark/tal01.html b/src/zope/tal/benchmark/tal01.html new file mode 100644 index 0000000..180b47c --- /dev/null +++ b/src/zope/tal/benchmark/tal01.html @@ -0,0 +1 @@ +baseline diff --git a/src/zope/tal/benchmark/tal02.html b/src/zope/tal/benchmark/tal02.html new file mode 100644 index 0000000..33d978d --- /dev/null +++ b/src/zope/tal/benchmark/tal02.html @@ -0,0 +1,100 @@ + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. diff --git a/src/zope/tal/benchmark/tal03.html b/src/zope/tal/benchmark/tal03.html new file mode 100644 index 0000000..b63a737 --- /dev/null +++ b/src/zope/tal/benchmark/tal03.html @@ -0,0 +1,8 @@ + <td bgcolor="white" tal:content="x0"></td> + <td bgcolor="white" tal:content="x1"></td> + <td bgcolor="white" tal:content="x2"></td> + <td bgcolor="white" tal:content="x3"></td> + <td bgcolor="white" tal:content="x4"></td> + <td bgcolor="white" tal:content="x5"></td> + <td bgcolor="white" tal:content="x6"></td> + <td bgcolor="white" tal:content="x7"></td> diff --git a/src/zope/tal/benchmark/tal04.html b/src/zope/tal/benchmark/tal04.html new file mode 100644 index 0000000..42af6e8 --- /dev/null +++ b/src/zope/tal/benchmark/tal04.html @@ -0,0 +1,6 @@ +<dtml-in tal:repeat="r r8"> + <td bgcolor="white" tal:content="x0"></td> + <td bgcolor="white" tal:content="x1"></td> + <td bgcolor="white" tal:content="x2"></td> + <td bgcolor="white" tal:content="x3"></td> +</dtml-in> diff --git a/src/zope/tal/benchmark/tal05.html b/src/zope/tal/benchmark/tal05.html new file mode 100644 index 0000000..6e2d626 --- /dev/null +++ b/src/zope/tal/benchmark/tal05.html @@ -0,0 +1,10 @@ +<dtml-in tal:repeat="r r8"> + <td bgcolor="white" tal:content="x0"></td> + <td bgcolor="white" tal:content="x1"></td> + <td bgcolor="white" tal:content="x2"></td> + <td bgcolor="white" tal:content="x3"></td> + <td bgcolor="white" tal:content="x4"></td> + <td bgcolor="white" tal:content="x5"></td> + <td bgcolor="white" tal:content="x6"></td> + <td bgcolor="white" tal:content="x7"></td> +</dtml-in> diff --git a/src/zope/tal/benchmark/tal06.html b/src/zope/tal/benchmark/tal06.html new file mode 100644 index 0000000..6f40872 --- /dev/null +++ b/src/zope/tal/benchmark/tal06.html @@ -0,0 +1,14 @@ +<dtml-in tal:repeat="r r2"> +<dtml-in tal:repeat="r r2"> +<dtml-in tal:repeat="r r2"> + <td bgcolor="white" tal:content="x0"></td> + <td bgcolor="white" tal:content="x1"></td> + <td bgcolor="white" tal:content="x2"></td> + <td bgcolor="white" tal:content="x3"></td> + <td bgcolor="white" tal:content="x4"></td> + <td bgcolor="white" tal:content="x5"></td> + <td bgcolor="white" tal:content="x6"></td> + <td bgcolor="white" tal:content="x7"></td> +</dtml-in> +</dtml-in> +</dtml-in> diff --git a/src/zope/tal/benchmark/tal07.html b/src/zope/tal/benchmark/tal07.html new file mode 100644 index 0000000..f331f05 --- /dev/null +++ b/src/zope/tal/benchmark/tal07.html @@ -0,0 +1,73 @@ + <td bgcolor="white" tal:content="x0"></td> + <td bgcolor="white" tal:content="x1"></td> + <td bgcolor="white" tal:content="x2"></td> + <td bgcolor="white" tal:content="x3"></td> + <td bgcolor="white" tal:content="x4"></td> + <td bgcolor="white" tal:content="x5"></td> + <td bgcolor="white" tal:content="x6"></td> + <td bgcolor="white" tal:content="x7"></td> + + <td bgcolor="white" tal:content="x0"></td> + <td bgcolor="white" tal:content="x1"></td> + <td bgcolor="white" tal:content="x2"></td> + <td bgcolor="white" tal:content="x3"></td> + <td bgcolor="white" tal:content="x4"></td> + <td bgcolor="white" tal:content="x5"></td> + <td bgcolor="white" tal:content="x6"></td> + <td bgcolor="white" tal:content="x7"></td> + + <td bgcolor="white" tal:content="x0"></td> + <td bgcolor="white" tal:content="x1"></td> + <td bgcolor="white" tal:content="x2"></td> + <td bgcolor="white" tal:content="x3"></td> + <td bgcolor="white" tal:content="x4"></td> + <td bgcolor="white" tal:content="x5"></td> + <td bgcolor="white" tal:content="x6"></td> + <td bgcolor="white" tal:content="x7"></td> + + <td bgcolor="white" tal:content="x0"></td> + <td bgcolor="white" tal:content="x1"></td> + <td bgcolor="white" tal:content="x2"></td> + <td bgcolor="white" tal:content="x3"></td> + <td bgcolor="white" tal:content="x4"></td> + <td bgcolor="white" tal:content="x5"></td> + <td bgcolor="white" tal:content="x6"></td> + <td bgcolor="white" tal:content="x7"></td> + + <td bgcolor="white" tal:content="x0"></td> + <td bgcolor="white" tal:content="x1"></td> + <td bgcolor="white" tal:content="x2"></td> + <td bgcolor="white" tal:content="x3"></td> + <td bgcolor="white" tal:content="x4"></td> + <td bgcolor="white" tal:content="x5"></td> + <td bgcolor="white" tal:content="x6"></td> + <td bgcolor="white" tal:content="x7"></td> + + <td bgcolor="white" tal:content="x0"></td> + <td bgcolor="white" tal:content="x1"></td> + <td bgcolor="white" tal:content="x2"></td> + <td bgcolor="white" tal:content="x3"></td> + <td bgcolor="white" tal:content="x4"></td> + <td bgcolor="white" tal:content="x5"></td> + <td bgcolor="white" tal:content="x6"></td> + <td bgcolor="white" tal:content="x7"></td> + + <td bgcolor="white" tal:content="x0"></td> + <td bgcolor="white" tal:content="x1"></td> + <td bgcolor="white" tal:content="x2"></td> + <td bgcolor="white" tal:content="x3"></td> + <td bgcolor="white" tal:content="x4"></td> + <td bgcolor="white" tal:content="x5"></td> + <td bgcolor="white" tal:content="x6"></td> + <td bgcolor="white" tal:content="x7"></td> + + <td bgcolor="white" tal:content="x0"></td> + <td bgcolor="white" tal:content="x1"></td> + <td bgcolor="white" tal:content="x2"></td> + <td bgcolor="white" tal:content="x3"></td> + <td bgcolor="white" tal:content="x4"></td> + <td bgcolor="white" tal:content="x5"></td> + <td bgcolor="white" tal:content="x6"></td> + <td bgcolor="white" tal:content="x7"></td> + + diff --git a/src/zope/tal/benchmark/tal08.html b/src/zope/tal/benchmark/tal08.html new file mode 100644 index 0000000..f577fed --- /dev/null +++ b/src/zope/tal/benchmark/tal08.html @@ -0,0 +1,73 @@ + <td bgcolor="white"><span tal:replace="x0"></span></td> + <td bgcolor="white"><span tal:replace="x1"></span></td> + <td bgcolor="white"><span tal:replace="x2"></span></td> + <td bgcolor="white"><span tal:replace="x3"></span></td> + <td bgcolor="white"><span tal:replace="x4"></span></td> + <td bgcolor="white"><span tal:replace="x5"></span></td> + <td bgcolor="white"><span tal:replace="x6"></span></td> + <td bgcolor="white"><span tal:replace="x7"></span></td> + + <td bgcolor="white"><span tal:replace="x0"></span></td> + <td bgcolor="white"><span tal:replace="x1"></span></td> + <td bgcolor="white"><span tal:replace="x2"></span></td> + <td bgcolor="white"><span tal:replace="x3"></span></td> + <td bgcolor="white"><span tal:replace="x4"></span></td> + <td bgcolor="white"><span tal:replace="x5"></span></td> + <td bgcolor="white"><span tal:replace="x6"></span></td> + <td bgcolor="white"><span tal:replace="x7"></span></td> + + <td bgcolor="white"><span tal:replace="x0"></span></td> + <td bgcolor="white"><span tal:replace="x1"></span></td> + <td bgcolor="white"><span tal:replace="x2"></span></td> + <td bgcolor="white"><span tal:replace="x3"></span></td> + <td bgcolor="white"><span tal:replace="x4"></span></td> + <td bgcolor="white"><span tal:replace="x5"></span></td> + <td bgcolor="white"><span tal:replace="x6"></span></td> + <td bgcolor="white"><span tal:replace="x7"></span></td> + + <td bgcolor="white"><span tal:replace="x0"></span></td> + <td bgcolor="white"><span tal:replace="x1"></span></td> + <td bgcolor="white"><span tal:replace="x2"></span></td> + <td bgcolor="white"><span tal:replace="x3"></span></td> + <td bgcolor="white"><span tal:replace="x4"></span></td> + <td bgcolor="white"><span tal:replace="x5"></span></td> + <td bgcolor="white"><span tal:replace="x6"></span></td> + <td bgcolor="white"><span tal:replace="x7"></span></td> + + <td bgcolor="white"><span tal:replace="x0"></span></td> + <td bgcolor="white"><span tal:replace="x1"></span></td> + <td bgcolor="white"><span tal:replace="x2"></span></td> + <td bgcolor="white"><span tal:replace="x3"></span></td> + <td bgcolor="white"><span tal:replace="x4"></span></td> + <td bgcolor="white"><span tal:replace="x5"></span></td> + <td bgcolor="white"><span tal:replace="x6"></span></td> + <td bgcolor="white"><span tal:replace="x7"></span></td> + + <td bgcolor="white"><span tal:replace="x0"></span></td> + <td bgcolor="white"><span tal:replace="x1"></span></td> + <td bgcolor="white"><span tal:replace="x2"></span></td> + <td bgcolor="white"><span tal:replace="x3"></span></td> + <td bgcolor="white"><span tal:replace="x4"></span></td> + <td bgcolor="white"><span tal:replace="x5"></span></td> + <td bgcolor="white"><span tal:replace="x6"></span></td> + <td bgcolor="white"><span tal:replace="x7"></span></td> + + <td bgcolor="white"><span tal:replace="x0"></span></td> + <td bgcolor="white"><span tal:replace="x1"></span></td> + <td bgcolor="white"><span tal:replace="x2"></span></td> + <td bgcolor="white"><span tal:replace="x3"></span></td> + <td bgcolor="white"><span tal:replace="x4"></span></td> + <td bgcolor="white"><span tal:replace="x5"></span></td> + <td bgcolor="white"><span tal:replace="x6"></span></td> + <td bgcolor="white"><span tal:replace="x7"></span></td> + + <td bgcolor="white"><span tal:replace="x0"></span></td> + <td bgcolor="white"><span tal:replace="x1"></span></td> + <td bgcolor="white"><span tal:replace="x2"></span></td> + <td bgcolor="white"><span tal:replace="x3"></span></td> + <td bgcolor="white"><span tal:replace="x4"></span></td> + <td bgcolor="white"><span tal:replace="x5"></span></td> + <td bgcolor="white"><span tal:replace="x6"></span></td> + <td bgcolor="white"><span tal:replace="x7"></span></td> + + diff --git a/src/zope/tal/benchmark/tal09.html b/src/zope/tal/benchmark/tal09.html new file mode 100644 index 0000000..ef81c58 --- /dev/null +++ b/src/zope/tal/benchmark/tal09.html @@ -0,0 +1,10 @@ +<dtml-in tal:repeat="r r64"> + <td bgcolor="white" tal:content="x0"></td> + <td bgcolor="white" tal:content="x1"></td> + <td bgcolor="white" tal:content="x2"></td> + <td bgcolor="white" tal:content="x3"></td> + <td bgcolor="white" tal:content="x4"></td> + <td bgcolor="white" tal:content="x5"></td> + <td bgcolor="white" tal:content="x6"></td> + <td bgcolor="white" tal:content="x7"></td> +</dtml-in> diff --git a/src/zope/tal/benchmark/tal10.html b/src/zope/tal/benchmark/tal10.html new file mode 100644 index 0000000..8026df7 --- /dev/null +++ b/src/zope/tal/benchmark/tal10.html @@ -0,0 +1,102 @@ +<dtml-in tal:repeat="r r64"> + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. +</dtml-in> diff --git a/src/zope/tal/benchmark/tal11.html b/src/zope/tal/benchmark/tal11.html new file mode 100644 index 0000000..d4a2440 --- /dev/null +++ b/src/zope/tal/benchmark/tal11.html @@ -0,0 +1,103 @@ +<dtml-in tal:repeat="r r64"> + <td bgcolor="white" tal:content="x0"></td> + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. +</dtml-in> diff --git a/src/zope/tal/benchmark/tal12.html b/src/zope/tal/benchmark/tal12.html new file mode 100644 index 0000000..dcd2c30 --- /dev/null +++ b/src/zope/tal/benchmark/tal12.html @@ -0,0 +1,12 @@ +<dtml-in tal:repeat="r r8"> + <span tal:define="y0 x0;y1 x1;y2 x2;y3 x3;y4 x4;y5 x5;y6 x6;y7 x7"> + <td bgcolor="white" tal:content="y0"></td> + <td bgcolor="white" tal:content="y1"></td> + <td bgcolor="white" tal:content="y2"></td> + <td bgcolor="white" tal:content="y3"></td> + <td bgcolor="white" tal:content="y4"></td> + <td bgcolor="white" tal:content="y5"></td> + <td bgcolor="white" tal:content="y6"></td> + <td bgcolor="white" tal:content="y7"></td> + </span> +</dtml-in> diff --git a/src/zope/tal/driver.py b/src/zope/tal/driver.py new file mode 100644 index 0000000..033e28a --- /dev/null +++ b/src/zope/tal/driver.py @@ -0,0 +1,209 @@ +#!/usr/bin/env python +############################################################################## +# +# Copyright (c) 2001, 2002 Zope Corporation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +"""Driver program to test METAL and TAL implementation. + +Usage: driver.py [options] [file] +Options: + -h / --help + Print this message and exit. + -H / --html + -x / --xml + Explicitly choose HTML or XML input. The default is to automatically + select based on the file extension. These options are mutually + exclusive. + -l + Lenient structure insertion. + -m + Macro expansion only + -s + Print intermediate opcodes only + -t + Leave TAL/METAL attributes in output + -i + Leave I18N substitution strings un-interpolated. + -a + Enable source annotations + +$Id$ +""" + +import os +import sys + +import getopt + +if __name__ == "__main__": + import setpath # Local hack to tweak sys.path etc. + +# Import local classes +import zope.tal.taldefs +from zope.tal.dummyengine import DummyEngine +from zope.tal.dummyengine import DummyTranslationDomain + +FILE = "tests/input/test01.xml" + +class TestTranslations(DummyTranslationDomain): + def translate(self, msgid, mapping=None, context=None, + target_language=None, default=None): + if msgid == 'timefmt': + return '%(minutes)s minutes after %(hours)s %(ampm)s' % mapping + elif msgid == 'jobnum': + return '%(jobnum)s is the JOB NUMBER' % mapping + elif msgid == 'verify': + s = 'Your contact email address is recorded as %(email)s' + return s % mapping + elif msgid == 'mailto:${request/submitter}': + return 'mailto:bperson@dom.ain' + elif msgid == 'origin': + return '%(name)s was born in %(country)s' % mapping + return DummyTranslationDomain.translate( + self, msgid, mapping, context, + target_language, default=default) + + +class TestEngine(DummyEngine): + def __init__(self, macros=None): + DummyEngine.__init__(self, macros) + self.translationDomain = TestTranslations() + + def evaluatePathOrVar(self, expr): + if expr == 'here/currentTime': + return {'hours' : 6, + 'minutes': 59, + 'ampm' : 'PM', + } + elif expr == 'context/@@object_name': + return '7' + elif expr == 'request/submitter': + return 'aperson@dom.ain' + return DummyEngine.evaluatePathOrVar(self, expr) + + +# This is a disgusting hack so that we can use engines that actually know +# something about certain object paths. TimeEngine knows about +# here/currentTime. +ENGINES = {'test23.html': TestEngine, + 'test24.html': TestEngine, + 'test26.html': TestEngine, + 'test27.html': TestEngine, + 'test28.html': TestEngine, + 'test29.html': TestEngine, + 'test30.html': TestEngine, + 'test31.html': TestEngine, + 'test32.html': TestEngine, + } + +def usage(code, msg=''): + print >> sys.stderr, __doc__ + if msg: + print >> sys.stderr, msg + sys.exit(code) + +def main(): + macros = 0 + mode = None + showcode = 0 + showtal = -1 + sourceAnnotations = 0 + strictinsert = 1 + i18nInterpolate = 1 + try: + opts, args = getopt.getopt(sys.argv[1:], "hHxlmstia", + ['help', 'html', 'xml']) + except getopt.error, msg: + usage(2, msg) + for opt, arg in opts: + if opt in ('-h', '--help'): + usage(0) + if opt in ('-H', '--html'): + if mode == 'xml': + usage(1, '--html and --xml are mutually exclusive') + mode = "html" + if opt == '-l': + strictinsert = 0 + if opt == '-m': + macros = 1 + if opt in ('-x', '--xml'): + if mode == 'html': + usage(1, '--html and --xml are mutually exclusive') + mode = "xml" + if opt == '-s': + showcode = 1 + if opt == '-t': + showtal = 1 + if opt == '-i': + i18nInterpolate = 0 + if opt == '-a': + sourceAnnotations = 1 + if args: + file = args[0] + else: + file = FILE + it = compilefile(file, mode) + if showcode: + showit(it) + else: + # See if we need a special engine for this test + engine = None + engineClass = ENGINES.get(os.path.basename(file)) + if engineClass is not None: + engine = engineClass(macros) + interpretit(it, engine=engine, + tal=(not macros), showtal=showtal, + strictinsert=strictinsert, + i18nInterpolate=i18nInterpolate, + sourceAnnotations=sourceAnnotations) + +def interpretit(it, engine=None, stream=None, tal=1, showtal=-1, + strictinsert=1, i18nInterpolate=1, sourceAnnotations=0): + from zope.tal.talinterpreter import TALInterpreter + program, macros = it + assert zope.tal.taldefs.isCurrentVersion(program) + if engine is None: + engine = DummyEngine(macros) + TALInterpreter(program, macros, engine, stream, wrap=0, + tal=tal, showtal=showtal, strictinsert=strictinsert, + i18nInterpolate=i18nInterpolate, + sourceAnnotations=sourceAnnotations)() + +def compilefile(file, mode=None): + assert mode in ("html", "xml", None) + if mode is None: + ext = os.path.splitext(file)[1] + if ext.lower() in (".html", ".htm"): + mode = "html" + else: + mode = "xml" + from zope.tal.talgenerator import TALGenerator + filename = os.path.abspath(file) + prefix = os.path.dirname(os.path.abspath(__file__)) + os.path.sep + if filename.startswith(prefix): + filename = filename[len(prefix):] + filename = filename.replace(os.sep, '/') # test files expect slashes + if mode == "html": + from zope.tal.htmltalparser import HTMLTALParser + p = HTMLTALParser(gen=TALGenerator(source_file=filename, xml=0)) + else: + from zope.tal.talparser import TALParser + p = TALParser(gen=TALGenerator(source_file=filename)) + p.parseFile(file) + return p.getCode() + +def showit(it): + from pprint import pprint + pprint(it) + +if __name__ == "__main__": + main() diff --git a/src/zope/tal/dummyengine.py b/src/zope/tal/dummyengine.py new file mode 100644 index 0000000..2a35d84 --- /dev/null +++ b/src/zope/tal/dummyengine.py @@ -0,0 +1,332 @@ +############################################################################## +# +# Copyright (c) 2001, 2002, 2003 Zope Corporation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +"""Dummy TAL expression engine so that I can test out the TAL implementation. + +$Id$ +""" +import re + +from zope.interface import implements +from zope.tal.taldefs import NAME_RE, TALExpressionError, ErrorInfo +from zope.tal.interfaces import ITALExpressionCompiler, ITALExpressionEngine +from zope.i18nmessageid import Message +from zope.i18n.interfaces import ITranslationDomain + +Default = object() + +name_match = re.compile(r"(?s)(%s):(.*)\Z" % NAME_RE).match + +class CompilerError(Exception): + pass + +class DummyEngine(object): + + position = None + source_file = None + + implements(ITALExpressionCompiler, ITALExpressionEngine) + + def __init__(self, macros=None): + if macros is None: + macros = {} + self.macros = macros + dict = {'nothing': None, 'default': Default} + self.locals = self.globals = dict + self.stack = [dict] + self.translationDomain = DummyTranslationDomain() + self.useEngineAttrDicts = False + + # zope.tal.interfaces.ITALExpressionCompiler + + def getCompilerError(self): + return CompilerError + + def compile(self, expr): + return "$%s$" % expr + + # zope.tal.interfaces.ITALExpressionEngine + + def setSourceFile(self, source_file): + self.source_file = source_file + + def setPosition(self, position): + self.position = position + + def beginScope(self): + self.stack.append(self.locals) + + def endScope(self): + assert len(self.stack) > 1, "more endScope() than beginScope() calls" + self.locals = self.stack.pop() + + def setLocal(self, name, value): + if self.locals is self.stack[-1]: + # Unmerge this scope's locals from previous scope of first set + self.locals = self.locals.copy() + self.locals[name] = value + + def setGlobal(self, name, value): + self.globals[name] = value + + def getValue(self, name, default=None): + value = self.globals.get(name, default) + if value is default: + value = self.locals.get(name, default) + return value + + def evaluate(self, expression): + assert (expression.startswith("$") and expression.endswith("$"), + expression) + expression = expression[1:-1] + m = name_match(expression) + if m: + type, expr = m.group(1, 2) + else: + type = "path" + expr = expression + + if type in ("string", "str"): + return expr + if type in ("path", "var", "global", "local"): + return self.evaluatePathOrVar(expr) + if type == "not": + return not self.evaluate(expr) + if type == "exists": + return self.locals.has_key(expr) or self.globals.has_key(expr) + if type == "python": + try: + return eval(expr, self.globals, self.locals) + except: + raise TALExpressionError("evaluation error in %s" % `expr`) + if type == "position": + # Insert the current source file name, line number, + # and column offset. + if self.position: + lineno, offset = self.position + else: + lineno, offset = None, None + return '%s (%s,%s)' % (self.source_file, lineno, offset) + raise TALExpressionError("unrecognized expression: " + `expression`) + + # implementation; can be overridden + def evaluatePathOrVar(self, expr): + expr = expr.strip() + if self.locals.has_key(expr): + return self.locals[expr] + elif self.globals.has_key(expr): + return self.globals[expr] + else: + raise TALExpressionError("unknown variable: %s" % `expr`) + + def evaluateValue(self, expr): + return self.evaluate(expr) + + def evaluateBoolean(self, expr): + return self.evaluate(expr) + + def evaluateText(self, expr): + text = self.evaluate(expr) + if isinstance(text, (str, unicode, Message)): + return text + if text is not None and text is not Default: + text = str(text) + return text + + def evaluateStructure(self, expr): + # TODO Should return None or a DOM tree + return self.evaluate(expr) + + # implementation; can be overridden + def evaluateSequence(self, expr): + # TODO: Should return a sequence + return self.evaluate(expr) + + def evaluateMacro(self, macroName): + assert (macroName.startswith("$") and macroName.endswith("$"), + macroName) + macroName = macroName[1:-1] + file, localName = self.findMacroFile(macroName) + if not file: + # Local macro + macro = self.macros[localName] + else: + # External macro + import driver + program, macros = driver.compilefile(file) + macro = macros.get(localName) + if not macro: + raise TALExpressionError("macro %s not found in file %s" % + (localName, file)) + return macro + + # internal + def findMacroFile(self, macroName): + if not macroName: + raise TALExpressionError("empty macro name") + i = macroName.rfind('/') + if i < 0: + # No slash -- must be a locally defined macro + return None, macroName + else: + # Up to last slash is the filename + fileName = macroName[:i] + localName = macroName[i+1:] + return fileName, localName + + def setRepeat(self, name, expr): + seq = self.evaluateSequence(expr) + return Iterator(name, seq, self) + + def createErrorInfo(self, err, position): + return ErrorInfo(err, position) + + def getDefault(self): + return Default + + def translate(self, msgid, domain=None, mapping=None, default=None): + self.translationDomain.domain = domain + return self.translationDomain.translate( + msgid, mapping, default=default) + + def evaluateCode(self, lang, code): + # We probably implement too much, but I use the dummy engine to test + # some of the issues that we will have. + + # For testing purposes only + locals = {} + globals = {} + if self.useEngineAttrDicts: + globals = self.globals.copy() + locals = self.locals.copy() + + assert lang == 'text/server-python' + import sys, StringIO + + # Removing probable comments + if code.strip().startswith('<!--') and code.strip().endswith('-->'): + code = code.strip()[4:-3] + + # Prepare code. + lines = code.split('\n') + lines = filter(lambda l: l.strip() != '', lines) + code = '\n'.join(lines) + # This saves us from all indentation issues :) + if code.startswith(' ') or code.startswith('\t'): + code = 'if 1 == 1:\n' + code + '\n' + tmp = sys.stdout + sys.stdout = StringIO.StringIO() + try: + exec code in globals, locals + finally: + result = sys.stdout + sys.stdout = tmp + + # For testing purposes only + self.codeLocals = locals + self.codeGlobals = globals + + self.locals.update(locals) + self.globals.update(globals) + + return result.getvalue() + +class Iterator(object): + + def __init__(self, name, seq, engine): + self.name = name + self.seq = seq + self.engine = engine + self.nextIndex = 0 + + def next(self): + i = self.nextIndex + try: + item = self.seq[i] + except IndexError: + return 0 + self.nextIndex = i+1 + self.engine.setLocal(self.name, item) + return 1 + + +class DummyTranslationDomain(object): + implements(ITranslationDomain) + + domain = '' + + msgids = {} + + def appendMsgid(self, domain, data): + if not self.msgids.has_key(domain): + self.msgids[domain] = [] + self.msgids[domain].append(data) + + def getMsgids(self, domain): + return self.msgids[domain] + + def clearMsgids(self): + self.msgids = {} + + def translate(self, msgid, mapping=None, context=None, + target_language=None, default=None): + + domain = self.domain + # This is a fake translation service which simply uppercases non + # ${name} placeholder text in the message id. + # + # First, transform a string with ${name} placeholders into a list of + # substrings. Then upcase everything but the placeholders, then glue + # things back together. + + # If the domain is a string method, then transform the string + # by calling that method. + + # MessageID attributes override arguments + if isinstance(msgid, Message): + domain = msgid.domain + mapping = msgid.mapping + default = msgid.default + if default is None: # Message doesn't substitute itself for + default = msgid # missing default + + # simulate an unknown msgid by returning None + if msgid == "don't translate me": + text = default + elif domain and hasattr('', domain): + text = getattr(msgid, domain)() + else: + domain = 'default' + text = msgid.upper() + + self.appendMsgid(domain, (msgid, mapping)) + + def repl(m): + return unicode(mapping[m.group(m.lastindex).lower()]) + cre = re.compile(r'\$(?:([_A-Za-z][-\w]*)|\{([_A-Za-z][-\w]*)\})') + return cre.sub(repl, text) + +class MultipleDomainsDummyEngine(DummyEngine): + + def translate(self, msgid, domain=None, mapping=None, default=None): + + if isinstance(msgid, Message): + domain = msgid.domain + + if domain == 'a_very_explicit_domain_setup_by_template_developer_that_wont_be_taken_into_account_by_the_ZPT_engine': + domain = 'lower' + + self.translationDomain.domain = domain + return self.translationDomain.translate( + msgid, mapping, default=default) + diff --git a/src/zope/tal/htmltalparser.py b/src/zope/tal/htmltalparser.py new file mode 100644 index 0000000..cae554e --- /dev/null +++ b/src/zope/tal/htmltalparser.py @@ -0,0 +1,321 @@ +############################################################################## +# +# Copyright (c) 2001, 2002 Zope Corporation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +"""Parse HTML and compile to TALInterpreter intermediate code. + +$Id$ +""" + +from HTMLParser import HTMLParser, HTMLParseError + +from zope.tal.taldefs import ZOPE_METAL_NS, ZOPE_TAL_NS, ZOPE_I18N_NS, \ + METALError, TALError, I18NError +from zope.tal.talgenerator import TALGenerator + + +BOOLEAN_HTML_ATTRS = frozenset([ + # List of Boolean attributes in HTML that may be given in + # minimized form (e.g. <img ismap> rather than <img ismap="">) + # From http://www.w3.org/TR/xhtml1/#guidelines (C.10) + "compact", "nowrap", "ismap", "declare", "noshade", "checked", + "disabled", "readonly", "multiple", "selected", "noresize", + "defer" + ]) + +EMPTY_HTML_TAGS = frozenset([ + # List of HTML tags with an empty content model; these are + # rendered in minimized form, e.g. <img />. + # From http://www.w3.org/TR/xhtml1/#dtds + "base", "meta", "link", "hr", "br", "param", "img", "area", + "input", "col", "basefont", "isindex", "frame", + ]) + +PARA_LEVEL_HTML_TAGS = frozenset([ + # List of HTML elements that close open paragraph-level elements + # and are themselves paragraph-level. + "h1", "h2", "h3", "h4", "h5", "h6", "p", + ]) + +BLOCK_CLOSING_TAG_MAP = { + "tr": ("tr", "td", "th"), + "td": ("td", "th"), + "th": ("td", "th"), + "li": ("li",), + "dd": ("dd", "dt"), + "dt": ("dd", "dt"), + } + +BLOCK_LEVEL_HTML_TAGS = frozenset([ + # List of HTML tags that denote larger sections than paragraphs. + "blockquote", "table", "tr", "th", "td", "thead", "tfoot", "tbody", + "noframe", "ul", "ol", "li", "dl", "dt", "dd", "div", + ]) + +SECTION_LEVEL_HTML_TAGS = PARA_LEVEL_HTML_TAGS.union(BLOCK_LEVEL_HTML_TAGS) + +TIGHTEN_IMPLICIT_CLOSE_TAGS = PARA_LEVEL_HTML_TAGS.union(BLOCK_CLOSING_TAG_MAP) + + +class NestingError(HTMLParseError): + """Exception raised when elements aren't properly nested.""" + + def __init__(self, tagstack, endtag, position=(None, None)): + self.endtag = endtag + if tagstack: + if len(tagstack) == 1: + msg = ('Open tag <%s> does not match close tag </%s>' + % (tagstack[0], endtag)) + else: + msg = ('Open tags <%s> do not match close tag </%s>' + % ('>, <'.join(tagstack), endtag)) + else: + msg = 'No tags are open to match </%s>' % endtag + HTMLParseError.__init__(self, msg, position) + +class EmptyTagError(NestingError): + """Exception raised when empty elements have an end tag.""" + + def __init__(self, tag, position=(None, None)): + self.tag = tag + msg = 'Close tag </%s> should be removed' % tag + HTMLParseError.__init__(self, msg, position) + +class OpenTagError(NestingError): + """Exception raised when a tag is not allowed in another tag.""" + + def __init__(self, tagstack, tag, position=(None, None)): + self.tag = tag + msg = 'Tag <%s> is not allowed in <%s>' % (tag, tagstack[-1]) + HTMLParseError.__init__(self, msg, position) + +class HTMLTALParser(HTMLParser): + + # External API + + def __init__(self, gen=None): + HTMLParser.__init__(self) + if gen is None: + gen = TALGenerator(xml=0) + self.gen = gen + self.tagstack = [] + self.nsstack = [] + self.nsdict = {'tal': ZOPE_TAL_NS, + 'metal': ZOPE_METAL_NS, + 'i18n': ZOPE_I18N_NS, + } + + def parseFile(self, file): + f = open(file) + data = f.read() + f.close() + try: + self.parseString(data) + except TALError, e: + e.setFile(file) + raise + + def parseString(self, data): + self.feed(data) + self.close() + while self.tagstack: + self.implied_endtag(self.tagstack[-1], 2) + assert self.nsstack == [], self.nsstack + + def getCode(self): + return self.gen.getCode() + + # Overriding HTMLParser methods + + def handle_starttag(self, tag, attrs): + self.close_para_tags(tag) + self.scan_xmlns(attrs) + tag, attrlist, taldict, metaldict, i18ndict \ + = self.process_ns(tag, attrs) + if tag in EMPTY_HTML_TAGS and "content" in taldict: + raise TALError( + "empty HTML tags cannot use tal:content: %s" % `tag`, + self.getpos()) + # Support for inline Python code. + if tag == 'script': + type_attr = filter(lambda a: a[0] == 'type', attrlist) + if type_attr and type_attr[0][1].startswith('text/server-'): + attrlist.remove(type_attr[0]) + taldict = {'script': type_attr[0][1], 'omit-tag': ''} + self.tagstack.append(tag) + self.gen.emitStartElement(tag, attrlist, taldict, metaldict, i18ndict, + self.getpos()) + if tag in EMPTY_HTML_TAGS: + self.implied_endtag(tag, -1) + + def handle_startendtag(self, tag, attrs): + self.close_para_tags(tag) + self.scan_xmlns(attrs) + tag, attrlist, taldict, metaldict, i18ndict \ + = self.process_ns(tag, attrs) + if taldict.get("content"): + if tag in EMPTY_HTML_TAGS: + raise TALError( + "empty HTML tags cannot use tal:content: %s" % `tag`, + self.getpos()) + self.gen.emitStartElement(tag, attrlist, taldict, metaldict, + i18ndict, self.getpos()) + self.gen.emitEndElement(tag, implied=-1, position=self.getpos()) + else: + self.gen.emitStartElement(tag, attrlist, taldict, metaldict, + i18ndict, self.getpos(), isend=1) + self.pop_xmlns() + + def handle_endtag(self, tag): + if tag in EMPTY_HTML_TAGS: + # </img> etc. in the source is an error + raise EmptyTagError(tag, self.getpos()) + self.close_enclosed_tags(tag) + self.gen.emitEndElement(tag, position=self.getpos()) + self.pop_xmlns() + self.tagstack.pop() + + def close_para_tags(self, tag): + if tag in EMPTY_HTML_TAGS: + return + close_to = -1 + if BLOCK_CLOSING_TAG_MAP.has_key(tag): + blocks_to_close = BLOCK_CLOSING_TAG_MAP[tag] + for i in range(len(self.tagstack)): + t = self.tagstack[i] + if t in blocks_to_close: + if close_to == -1: + close_to = i + elif t in BLOCK_LEVEL_HTML_TAGS: + close_to = -1 + elif tag in SECTION_LEVEL_HTML_TAGS: + i = len(self.tagstack) - 1 + while i >= 0: + closetag = self.tagstack[i] + if closetag in BLOCK_LEVEL_HTML_TAGS: + break + if closetag in PARA_LEVEL_HTML_TAGS: + if closetag != "p": + raise OpenTagError(self.tagstack, tag, self.getpos()) + close_to = i + i = i - 1 + if close_to >= 0: + while len(self.tagstack) > close_to: + self.implied_endtag(self.tagstack[-1], 1) + + def close_enclosed_tags(self, tag): + if tag not in self.tagstack: + raise NestingError(self.tagstack, tag, self.getpos()) + while tag != self.tagstack[-1]: + self.implied_endtag(self.tagstack[-1], 1) + assert self.tagstack[-1] == tag + + def implied_endtag(self, tag, implied): + assert tag == self.tagstack[-1] + assert implied in (-1, 1, 2) + isend = (implied < 0) + if tag in TIGHTEN_IMPLICIT_CLOSE_TAGS: + # Pick out trailing whitespace from the program, and + # insert the close tag before the whitespace. + white = self.gen.unEmitWhitespace() + else: + white = None + self.gen.emitEndElement(tag, isend=isend, implied=implied, + position=self.getpos()) + if white: + self.gen.emitRawText(white) + self.tagstack.pop() + self.pop_xmlns() + + def handle_charref(self, name): + self.gen.emitRawText("&#%s;" % name) + + def handle_entityref(self, name): + self.gen.emitRawText("&%s;" % name) + + def handle_data(self, data): + self.gen.emitRawText(data) + + def handle_comment(self, data): + self.gen.emitRawText("<!--%s-->" % data) + + def handle_decl(self, data): + self.gen.emitRawText("<!%s>" % data) + + def handle_pi(self, data): + self.gen.emitRawText("<?%s>" % data) + + # Internal thingies + + def scan_xmlns(self, attrs): + nsnew = {} + for key, value in attrs: + if key.startswith("xmlns:"): + nsnew[key[6:]] = value + if nsnew: + self.nsstack.append(self.nsdict) + self.nsdict = self.nsdict.copy() + self.nsdict.update(nsnew) + else: + self.nsstack.append(self.nsdict) + + def pop_xmlns(self): + self.nsdict = self.nsstack.pop() + + def fixname(self, name): + if ':' in name: + prefix, suffix = name.split(':', 1) + if prefix == 'xmlns': + nsuri = self.nsdict.get(suffix) + if nsuri in (ZOPE_TAL_NS, ZOPE_METAL_NS, ZOPE_I18N_NS): + return name, name, prefix + else: + nsuri = self.nsdict.get(prefix) + if nsuri == ZOPE_TAL_NS: + return name, suffix, 'tal' + elif nsuri == ZOPE_METAL_NS: + return name, suffix, 'metal' + elif nsuri == ZOPE_I18N_NS: + return name, suffix, 'i18n' + return name, name, 0 + + def process_ns(self, name, attrs): + attrlist = [] + taldict = {} + metaldict = {} + i18ndict = {} + name, namebase, namens = self.fixname(name) + for item in attrs: + key, value = item + key, keybase, keyns = self.fixname(key) + ns = keyns or namens # default to tag namespace + if ns and ns != 'unknown': + item = (key, value, ns) + if ns == 'tal': + if taldict.has_key(keybase): + raise TALError("duplicate TAL attribute " + + `keybase`, self.getpos()) + taldict[keybase] = value + elif ns == 'metal': + if metaldict.has_key(keybase): + raise METALError("duplicate METAL attribute " + + `keybase`, self.getpos()) + metaldict[keybase] = value + elif ns == 'i18n': + if i18ndict.has_key(keybase): + raise I18NError("duplicate i18n attribute " + + `keybase`, self.getpos()) + i18ndict[keybase] = value + attrlist.append(item) + if namens in ('metal', 'tal'): + taldict['tal tag'] = namens + return name, attrlist, taldict, metaldict, i18ndict diff --git a/src/zope/tal/interfaces.py b/src/zope/tal/interfaces.py new file mode 100644 index 0000000..8b8b7f0 --- /dev/null +++ b/src/zope/tal/interfaces.py @@ -0,0 +1,207 @@ +############################################################################## +# +# Copyright (c) 2003 Zope Corporation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +"""Interface that a TAL expression implementation provides to the METAL/TAL +implementation. + +$Id$ +""" +from zope.interface import Attribute, Interface + + +class ITALExpressionCompiler(Interface): + """Compile-time interface provided by a TAL expression implementation. + + The TAL compiler needs an instance of this interface to support + compilation of TAL expressions embedded in documents containing + TAL and METAL constructs. + """ + + def getCompilerError(): + """Return the exception class raised for compilation errors. + """ + + def compile(expression): + """Return a compiled form of 'expression' for later evaluation. + + 'expression' is the source text of the expression. + + The return value may be passed to the various evaluate*() + methods of the ITALExpressionEngine interface. No compatibility is + required for the values of the compiled expression between + different ITALExpressionEngine implementations. + """ + + def getContext(namespace): + """Create an expression execution context + + The given namespace provides the initial top-level names. + """ + +class ITALExpressionEngine(Interface): + """Render-time interface provided by a TAL expression implementation. + + The TAL interpreter uses this interface to TAL expression to support + evaluation of the compiled expressions returned by + ITALExpressionCompiler.compile(). + """ + + def getDefault(): + """Return the value of the 'default' TAL expression. + + Checking a value for a match with 'default' should be done + using the 'is' operator in Python. + """ + + def setPosition((lineno, offset)): + """Inform the engine of the current position in the source file. + + This is used to allow the evaluation engine to report + execution errors so that site developers can more easily + locate the offending expression. + """ + + def setSourceFile(filename): + """Inform the engine of the name of the current source file. + + This is used to allow the evaluation engine to report + execution errors so that site developers can more easily + locate the offending expression. + """ + + def beginScope(): + """Push a new scope onto the stack of open scopes. + """ + + def endScope(): + """Pop one scope from the stack of open scopes. + """ + + def evaluate(compiled_expression): + """Evaluate an arbitrary expression. + + No constraints are imposed on the return value. + """ + + def evaluateBoolean(compiled_expression): + """Evaluate an expression that must return a Boolean value. + """ + + def evaluateMacro(compiled_expression): + """Evaluate an expression that must return a macro program. + """ + + def evaluateStructure(compiled_expression): + """Evaluate an expression that must return a structured + document fragment. + + The result of evaluating 'compiled_expression' must be a + string containing a parsable HTML or XML fragment. Any TAL + markup contained in the result string will be interpreted. + """ + + def evaluateText(compiled_expression): + """Evaluate an expression that must return text. + + The returned text should be suitable for direct inclusion in + the output: any HTML or XML escaping or quoting is the + responsibility of the expression itself. + + If the expression evaluates to None, then that is returned. It + represents 'nothing' in TALES. + If the expression evaluates to what getDefault() of this interface + returns, by comparison using 'is', then that is returned. It + represents 'default' in TALES. + """ + + def evaluateValue(compiled_expression): + """Evaluate an arbitrary expression. + + No constraints are imposed on the return value. + """ + + def createErrorInfo(exception, (lineno, offset)): + """Returns an ITALExpressionErrorInfo object. + + The returned object is used to provide information about the + error condition for the on-error handler. + """ + + def setGlobal(name, value): + """Set a global variable. + + The variable will be named 'name' and have the value 'value'. + """ + + def setLocal(name, value): + """Set a local variable in the current scope. + + The variable will be named 'name' and have the value 'value'. + """ + + def getValue(name, default=None): + """Get a variable by name. + + If the variable does not exist, return default. + """ + + def setRepeat(name, compiled_expression): + """Start a repetition, returning an ITALIterator. + + The engine is expected to add the a value (typically the + returned iterator) for the name to the variable namespace. + """ + + def translate(msgid, domain=None, mapping=None, default=None): + """See zope.i18n.interfaces.ITranslationDomain.translate""" + + # NB: This differs from the Zope 2 equivalent in the order of + # the arguments. This will be a (hopefully minor) issue when + # creating a unified TAL implementation. + + def evaluateCode(lang, code): + """Evaluates code of the given language. + + Returns whatever the code outputs. This can be defined on a + per-language basis. In Python this usually everything the print + statement will return. + """ + + +class ITALIterator(Interface): + """A TAL iterator + + Not to be confused with a Python iterator. + """ + + def next(): + """Advance to the next value in the iteration, if possible + + Return a true value if it was possible to advance and return + a false value otherwise. + """ + + +class ITALExpressionErrorInfo(Interface): + + type = Attribute("type", + "The exception class.") + + value = Attribute("value", + "The exception instance.") + + lineno = Attribute("lineno", + "The line number the error occurred on in the source.") + + offset = Attribute("offset", + "The character offset at which the error occurred.") diff --git a/src/zope/tal/ndiff.py b/src/zope/tal/ndiff.py new file mode 100644 index 0000000..7873abc --- /dev/null +++ b/src/zope/tal/ndiff.py @@ -0,0 +1,649 @@ +#! /usr/bin/env python +############################################################################## +# +# Copyright (c) 2001, 2002 Zope Corporation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## + +# Module ndiff version 1.6.0 +# Released to the public domain 08-Dec-2000, +# by Tim Peters (tim.one@home.com). + +# Provided as-is; use at your own risk; no warranty; no promises; enjoy! + +"""ndiff [-q] file1 file2 + or +ndiff (-r1 | -r2) < ndiff_output > file1_or_file2 + +Print a human-friendly file difference report to stdout. Both inter- +and intra-line differences are noted. In the second form, recreate file1 +(-r1) or file2 (-r2) on stdout, from an ndiff report on stdin. + +In the first form, if -q ("quiet") is not specified, the first two lines +of output are + +-: file1 ++: file2 + +Each remaining line begins with a two-letter code: + + "- " line unique to file1 + "+ " line unique to file2 + " " line common to both files + "? " line not present in either input file + +Lines beginning with "? " attempt to guide the eye to intraline +differences, and were not present in either input file. These lines can be +confusing if the source files contain tab characters. + +The first file can be recovered by retaining only lines that begin with +" " or "- ", and deleting those 2-character prefixes; use ndiff with -r1. + +The second file can be recovered similarly, but by retaining only " " and +"+ " lines; use ndiff with -r2; or, on Unix, the second file can be +recovered by piping the output through + + sed -n '/^[+ ] /s/^..//p' + +See module comments for details and programmatic interface. + +$Id$ +""" + +__version__ = 1, 5, 0 + +# SequenceMatcher tries to compute a "human-friendly diff" between +# two sequences (chiefly picturing a file as a sequence of lines, +# and a line as a sequence of characters, here). Unlike e.g. UNIX(tm) +# diff, the fundamental notion is the longest *contiguous* & junk-free +# matching subsequence. That's what catches peoples' eyes. The +# Windows(tm) windiff has another interesting notion, pairing up elements +# that appear uniquely in each sequence. That, and the method here, +# appear to yield more intuitive difference reports than does diff. This +# method appears to be the least vulnerable to synching up on blocks +# of "junk lines", though (like blank lines in ordinary text files, +# or maybe "<P>" lines in HTML files). That may be because this is +# the only method of the 3 that has a *concept* of "junk" <wink>. +# +# Note that ndiff makes no claim to produce a *minimal* diff. To the +# contrary, minimal diffs are often counter-intuitive, because they +# synch up anywhere possible, sometimes accidental matches 100 pages +# apart. Restricting synch points to contiguous matches preserves some +# notion of locality, at the occasional cost of producing a longer diff. +# +# With respect to junk, an earlier version of ndiff simply refused to +# *start* a match with a junk element. The result was cases like this: +# before: private Thread currentThread; +# after: private volatile Thread currentThread; +# If you consider whitespace to be junk, the longest contiguous match +# not starting with junk is "e Thread currentThread". So ndiff reported +# that "e volatil" was inserted between the 't' and the 'e' in "private". +# While an accurate view, to people that's absurd. The current version +# looks for matching blocks that are entirely junk-free, then extends the +# longest one of those as far as possible but only with matching junk. +# So now "currentThread" is matched, then extended to suck up the +# preceding blank; then "private" is matched, and extended to suck up the +# following blank; then "Thread" is matched; and finally ndiff reports +# that "volatile " was inserted before "Thread". The only quibble +# remaining is that perhaps it was really the case that " volatile" +# was inserted after "private". I can live with that <wink>. +# +# NOTE on junk: the module-level names +# IS_LINE_JUNK +# IS_CHARACTER_JUNK +# can be set to any functions you like. The first one should accept +# a single string argument, and return true iff the string is junk. +# The default is whether the regexp r"\s*#?\s*$" matches (i.e., a +# line without visible characters, except for at most one splat). +# The second should accept a string of length 1 etc. The default is +# whether the character is a blank or tab (note: bad idea to include +# newline in this!). +# +# After setting those, you can call fcompare(f1name, f2name) with the +# names of the files you want to compare. The difference report +# is sent to stdout. Or you can call main(args), passing what would +# have been in sys.argv[1:] had the cmd-line form been used. + +TRACE = 0 + +# define what "junk" means +import re + +def IS_LINE_JUNK(line, pat=re.compile(r"\s*#?\s*$").match): + return pat(line) is not None + +def IS_CHARACTER_JUNK(ch, ws=" \t"): + return ch in ws + +del re + +class SequenceMatcher(object): + def __init__(self, isjunk=None, a='', b=''): + # Members: + # a + # first sequence + # b + # second sequence; differences are computed as "what do + # we need to do to 'a' to change it into 'b'?" + # b2j + # for x in b, b2j[x] is a list of the indices (into b) + # at which x appears; junk elements do not appear + # b2jhas + # b2j.has_key + # fullbcount + # for x in b, fullbcount[x] == the number of times x + # appears in b; only materialized if really needed (used + # only for computing quick_ratio()) + # matching_blocks + # a list of (i, j, k) triples, where a[i:i+k] == b[j:j+k]; + # ascending & non-overlapping in i and in j; terminated by + # a dummy (len(a), len(b), 0) sentinel + # opcodes + # a list of (tag, i1, i2, j1, j2) tuples, where tag is + # one of + # 'replace' a[i1:i2] should be replaced by b[j1:j2] + # 'delete' a[i1:i2] should be deleted + # 'insert' b[j1:j2] should be inserted + # 'equal' a[i1:i2] == b[j1:j2] + # isjunk + # a user-supplied function taking a sequence element and + # returning true iff the element is "junk" -- this has + # subtle but helpful effects on the algorithm, which I'll + # get around to writing up someday <0.9 wink>. + # DON'T USE! Only __chain_b uses this. Use isbjunk. + # isbjunk + # for x in b, isbjunk(x) == isjunk(x) but much faster; + # it's really the has_key method of a hidden dict. + # DOES NOT WORK for x in a! + + self.isjunk = isjunk + self.a = self.b = None + self.set_seqs(a, b) + + def set_seqs(self, a, b): + self.set_seq1(a) + self.set_seq2(b) + + def set_seq1(self, a): + if a is self.a: + return + self.a = a + self.matching_blocks = self.opcodes = None + + def set_seq2(self, b): + if b is self.b: + return + self.b = b + self.matching_blocks = self.opcodes = None + self.fullbcount = None + self.__chain_b() + + # For each element x in b, set b2j[x] to a list of the indices in + # b where x appears; the indices are in increasing order; note that + # the number of times x appears in b is len(b2j[x]) ... + # when self.isjunk is defined, junk elements don't show up in this + # map at all, which stops the central find_longest_match method + # from starting any matching block at a junk element ... + # also creates the fast isbjunk function ... + # note that this is only called when b changes; so for cross-product + # kinds of matches, it's best to call set_seq2 once, then set_seq1 + # repeatedly + + def __chain_b(self): + # Because isjunk is a user-defined (not C) function, and we test + # for junk a LOT, it's important to minimize the number of calls. + # Before the tricks described here, __chain_b was by far the most + # time-consuming routine in the whole module! If anyone sees + # Jim Roskind, thank him again for profile.py -- I never would + # have guessed that. + # The first trick is to build b2j ignoring the possibility + # of junk. I.e., we don't call isjunk at all yet. Throwing + # out the junk later is much cheaper than building b2j "right" + # from the start. + b = self.b + self.b2j = b2j = {} + self.b2jhas = b2jhas = b2j.has_key + for i in xrange(len(b)): + elt = b[i] + if b2jhas(elt): + b2j[elt].append(i) + else: + b2j[elt] = [i] + + # Now b2j.keys() contains elements uniquely, and especially when + # the sequence is a string, that's usually a good deal smaller + # than len(string). The difference is the number of isjunk calls + # saved. + isjunk, junkdict = self.isjunk, {} + if isjunk: + for elt in b2j.keys(): + if isjunk(elt): + junkdict[elt] = 1 # value irrelevant; it's a set + del b2j[elt] + + # Now for x in b, isjunk(x) == junkdict.has_key(x), but the + # latter is much faster. Note too that while there may be a + # lot of junk in the sequence, the number of *unique* junk + # elements is probably small. So the memory burden of keeping + # this dict alive is likely trivial compared to the size of b2j. + self.isbjunk = junkdict.has_key + + def find_longest_match(self, alo, ahi, blo, bhi): + """Find longest matching block in a[alo:ahi] and b[blo:bhi]. + + If isjunk is not defined: + + Return (i,j,k) such that a[i:i+k] is equal to b[j:j+k], where + alo <= i <= i+k <= ahi + blo <= j <= j+k <= bhi + and for all (i',j',k') meeting those conditions, + k >= k' + i <= i' + and if i == i', j <= j' + In other words, of all maximal matching blocks, return one + that starts earliest in a, and of all those maximal matching + blocks that start earliest in a, return the one that starts + earliest in b. + + If isjunk is defined, first the longest matching block is + determined as above, but with the additional restriction that + no junk element appears in the block. Then that block is + extended as far as possible by matching (only) junk elements on + both sides. So the resulting block never matches on junk except + as identical junk happens to be adjacent to an "interesting" + match. + + If no blocks match, return (alo, blo, 0). + """ + + # CAUTION: stripping common prefix or suffix would be incorrect. + # E.g., + # ab + # acab + # Longest matching block is "ab", but if common prefix is + # stripped, it's "a" (tied with "b"). UNIX(tm) diff does so + # strip, so ends up claiming that ab is changed to acab by + # inserting "ca" in the middle. That's minimal but unintuitive: + # "it's obvious" that someone inserted "ac" at the front. + # Windiff ends up at the same place as diff, but by pairing up + # the unique 'b's and then matching the first two 'a's. + + a, b, b2j, isbjunk = self.a, self.b, self.b2j, self.isbjunk + besti, bestj, bestsize = alo, blo, 0 + # find longest junk-free match + # during an iteration of the loop, j2len[j] = length of longest + # junk-free match ending with a[i-1] and b[j] + j2len = {} + nothing = [] + for i in xrange(alo, ahi): + # look at all instances of a[i] in b; note that because + # b2j has no junk keys, the loop is skipped if a[i] is junk + j2lenget = j2len.get + newj2len = {} + for j in b2j.get(a[i], nothing): + # a[i] matches b[j] + if j < blo: + continue + if j >= bhi: + break + k = newj2len[j] = j2lenget(j-1, 0) + 1 + if k > bestsize: + besti, bestj, bestsize = i-k+1, j-k+1, k + j2len = newj2len + + # Now that we have a wholly interesting match (albeit possibly + # empty!), we may as well suck up the matching junk on each + # side of it too. Can't think of a good reason not to, and it + # saves post-processing the (possibly considerable) expense of + # figuring out what to do with it. In the case of an empty + # interesting match, this is clearly the right thing to do, + # because no other kind of match is possible in the regions. + while besti > alo and bestj > blo and \ + isbjunk(b[bestj-1]) and \ + a[besti-1] == b[bestj-1]: + besti, bestj, bestsize = besti-1, bestj-1, bestsize+1 + while besti+bestsize < ahi and bestj+bestsize < bhi and \ + isbjunk(b[bestj+bestsize]) and \ + a[besti+bestsize] == b[bestj+bestsize]: + bestsize = bestsize + 1 + + if TRACE: + print "get_matching_blocks", alo, ahi, blo, bhi + print " returns", besti, bestj, bestsize + return besti, bestj, bestsize + + def get_matching_blocks(self): + if self.matching_blocks is not None: + return self.matching_blocks + self.matching_blocks = [] + la, lb = len(self.a), len(self.b) + self.__helper(0, la, 0, lb, self.matching_blocks) + self.matching_blocks.append((la, lb, 0)) + if TRACE: + print '*** matching blocks', self.matching_blocks + return self.matching_blocks + + # builds list of matching blocks covering a[alo:ahi] and + # b[blo:bhi], appending them in increasing order to answer + + def __helper(self, alo, ahi, blo, bhi, answer): + i, j, k = x = self.find_longest_match(alo, ahi, blo, bhi) + # a[alo:i] vs b[blo:j] unknown + # a[i:i+k] same as b[j:j+k] + # a[i+k:ahi] vs b[j+k:bhi] unknown + if k: + if alo < i and blo < j: + self.__helper(alo, i, blo, j, answer) + answer.append(x) + if i+k < ahi and j+k < bhi: + self.__helper(i+k, ahi, j+k, bhi, answer) + + def ratio(self): + """Return a measure of the sequences' similarity (float in [0,1]). + + Where T is the total number of elements in both sequences, and + M is the number of matches, this is 2*M / T. + Note that this is 1 if the sequences are identical, and 0 if + they have nothing in common. + """ + + matches = reduce(lambda sum, triple: sum + triple[-1], + self.get_matching_blocks(), 0) + return 2.0 * matches / (len(self.a) + len(self.b)) + + def quick_ratio(self): + """Return an upper bound on ratio() relatively quickly.""" + # viewing a and b as multisets, set matches to the cardinality + # of their intersection; this counts the number of matches + # without regard to order, so is clearly an upper bound + if self.fullbcount is None: + self.fullbcount = fullbcount = {} + for elt in self.b: + fullbcount[elt] = fullbcount.get(elt, 0) + 1 + fullbcount = self.fullbcount + # avail[x] is the number of times x appears in 'b' less the + # number of times we've seen it in 'a' so far ... kinda + avail = {} + availhas, matches = avail.has_key, 0 + for elt in self.a: + if availhas(elt): + numb = avail[elt] + else: + numb = fullbcount.get(elt, 0) + avail[elt] = numb - 1 + if numb > 0: + matches = matches + 1 + return 2.0 * matches / (len(self.a) + len(self.b)) + + def real_quick_ratio(self): + """Return an upper bound on ratio() very quickly""" + la, lb = len(self.a), len(self.b) + # can't have more matches than the number of elements in the + # shorter sequence + return 2.0 * min(la, lb) / (la + lb) + + def get_opcodes(self): + if self.opcodes is not None: + return self.opcodes + i = j = 0 + self.opcodes = answer = [] + for ai, bj, size in self.get_matching_blocks(): + # invariant: we've pumped out correct diffs to change + # a[:i] into b[:j], and the next matching block is + # a[ai:ai+size] == b[bj:bj+size]. So we need to pump + # out a diff to change a[i:ai] into b[j:bj], pump out + # the matching block, and move (i,j) beyond the match + tag = '' + if i < ai and j < bj: + tag = 'replace' + elif i < ai: + tag = 'delete' + elif j < bj: + tag = 'insert' + if tag: + answer.append((tag, i, ai, j, bj)) + i, j = ai+size, bj+size + # the list of matching blocks is terminated by a + # sentinel with size 0 + if size: + answer.append(('equal', ai, i, bj, j)) + return answer + +# meant for dumping lines +def dump(tag, x, lo, hi): + for i in xrange(lo, hi): + print tag, x[i], + +def plain_replace(a, alo, ahi, b, blo, bhi): + assert alo < ahi and blo < bhi + # dump the shorter block first -- reduces the burden on short-term + # memory if the blocks are of very different sizes + if bhi - blo < ahi - alo: + dump('+', b, blo, bhi) + dump('-', a, alo, ahi) + else: + dump('-', a, alo, ahi) + dump('+', b, blo, bhi) + +# When replacing one block of lines with another, this guy searches +# the blocks for *similar* lines; the best-matching pair (if any) is +# used as a synch point, and intraline difference marking is done on +# the similar pair. Lots of work, but often worth it. + +def fancy_replace(a, alo, ahi, b, blo, bhi): + if TRACE: + print '*** fancy_replace', alo, ahi, blo, bhi + dump('>', a, alo, ahi) + dump('<', b, blo, bhi) + + # don't synch up unless the lines have a similarity score of at + # least cutoff; best_ratio tracks the best score seen so far + best_ratio, cutoff = 0.74, 0.75 + cruncher = SequenceMatcher(IS_CHARACTER_JUNK) + eqi, eqj = None, None # 1st indices of equal lines (if any) + + # search for the pair that matches best without being identical + # (identical lines must be junk lines, & we don't want to synch up + # on junk -- unless we have to) + for j in xrange(blo, bhi): + bj = b[j] + cruncher.set_seq2(bj) + for i in xrange(alo, ahi): + ai = a[i] + if ai == bj: + if eqi is None: + eqi, eqj = i, j + continue + cruncher.set_seq1(ai) + # computing similarity is expensive, so use the quick + # upper bounds first -- have seen this speed up messy + # compares by a factor of 3. + # note that ratio() is only expensive to compute the first + # time it's called on a sequence pair; the expensive part + # of the computation is cached by cruncher + if cruncher.real_quick_ratio() > best_ratio and \ + cruncher.quick_ratio() > best_ratio and \ + cruncher.ratio() > best_ratio: + best_ratio, best_i, best_j = cruncher.ratio(), i, j + if best_ratio < cutoff: + # no non-identical "pretty close" pair + if eqi is None: + # no identical pair either -- treat it as a straight replace + plain_replace(a, alo, ahi, b, blo, bhi) + return + # no close pair, but an identical pair -- synch up on that + best_i, best_j, best_ratio = eqi, eqj, 1.0 + else: + # there's a close pair, so forget the identical pair (if any) + eqi = None + + # a[best_i] very similar to b[best_j]; eqi is None iff they're not + # identical + if TRACE: + print '*** best_ratio', best_ratio, best_i, best_j + dump('>', a, best_i, best_i+1) + dump('<', b, best_j, best_j+1) + + # pump out diffs from before the synch point + fancy_helper(a, alo, best_i, b, blo, best_j) + + # do intraline marking on the synch pair + aelt, belt = a[best_i], b[best_j] + if eqi is None: + # pump out a '-', '?', '+', '?' quad for the synched lines + atags = btags = "" + cruncher.set_seqs(aelt, belt) + for tag, ai1, ai2, bj1, bj2 in cruncher.get_opcodes(): + la, lb = ai2 - ai1, bj2 - bj1 + if tag == 'replace': + atags = atags + '^' * la + btags = btags + '^' * lb + elif tag == 'delete': + atags = atags + '-' * la + elif tag == 'insert': + btags = btags + '+' * lb + elif tag == 'equal': + atags = atags + ' ' * la + btags = btags + ' ' * lb + else: + raise ValueError('unknown tag ' + `tag`) + printq(aelt, belt, atags, btags) + else: + # the synch pair is identical + print ' ', aelt, + + # pump out diffs from after the synch point + fancy_helper(a, best_i+1, ahi, b, best_j+1, bhi) + +def fancy_helper(a, alo, ahi, b, blo, bhi): + if alo < ahi: + if blo < bhi: + fancy_replace(a, alo, ahi, b, blo, bhi) + else: + dump('-', a, alo, ahi) + elif blo < bhi: + dump('+', b, blo, bhi) + +# Crap to deal with leading tabs in "?" output. Can hurt, but will +# probably help most of the time. + +def printq(aline, bline, atags, btags): + common = min(count_leading(aline, "\t"), + count_leading(bline, "\t")) + common = min(common, count_leading(atags[:common], " ")) + print "-", aline, + if count_leading(atags, " ") < len(atags): + print "?", "\t" * common + atags[common:] + print "+", bline, + if count_leading(btags, " ") < len(btags): + print "?", "\t" * common + btags[common:] + +def count_leading(line, ch): + i, n = 0, len(line) + while i < n and line[i] == ch: + i = i + 1 + return i + +def fail(msg): + import sys + out = sys.stderr.write + out(msg + "\n\n") + out(__doc__) + return 0 + +# open a file & return the file object; gripe and return 0 if it +# couldn't be opened +def fopen(fname): + try: + return open(fname, 'r') + except IOError, detail: + return fail("couldn't open " + fname + ": " + str(detail)) + +# open two files & spray the diff to stdout; return false iff a problem +def fcompare(f1name, f2name): + f1 = fopen(f1name) + f2 = fopen(f2name) + if not f1 or not f2: + return 0 + + a = f1.readlines(); f1.close() + b = f2.readlines(); f2.close() + + cruncher = SequenceMatcher(IS_LINE_JUNK, a, b) + for tag, alo, ahi, blo, bhi in cruncher.get_opcodes(): + if tag == 'replace': + fancy_replace(a, alo, ahi, b, blo, bhi) + elif tag == 'delete': + dump('-', a, alo, ahi) + elif tag == 'insert': + dump('+', b, blo, bhi) + elif tag == 'equal': + dump(' ', a, alo, ahi) + else: + raise ValueError('unknown tag ' + `tag`) + + return 1 + +# crack args (sys.argv[1:] is normal) & compare; +# return false iff a problem + +def main(args): + import getopt + try: + opts, args = getopt.getopt(args, "qr:") + except getopt.error, detail: + return fail(str(detail)) + noisy = 1 + qseen = rseen = 0 + for opt, val in opts: + if opt == "-q": + qseen = 1 + noisy = 0 + elif opt == "-r": + rseen = 1 + whichfile = val + if qseen and rseen: + return fail("can't specify both -q and -r") + if rseen: + if args: + return fail("no args allowed with -r option") + if whichfile in "12": + restore(whichfile) + return 1 + return fail("-r value must be 1 or 2") + if len(args) != 2: + return fail("need 2 filename args") + f1name, f2name = args + if noisy: + print '-:', f1name + print '+:', f2name + return fcompare(f1name, f2name) + +def restore(which): + import sys + tag = {"1": "- ", "2": "+ "}[which] + prefixes = (" ", tag) + for line in sys.stdin.readlines(): + if line[:2] in prefixes: + print line[2:], + +if __name__ == '__main__': + import sys + args = sys.argv[1:] + if "-profile" in args: + import profile, pstats + args.remove("-profile") + statf = "ndiff.pro" + profile.run("main(args)", statf) + stats = pstats.Stats(statf) + stats.strip_dirs().sort_stats('time').print_stats() + else: + main(args) diff --git a/src/zope/tal/runtest.py b/src/zope/tal/runtest.py new file mode 100644 index 0000000..1cb75c8 --- /dev/null +++ b/src/zope/tal/runtest.py @@ -0,0 +1,154 @@ +#! /usr/bin/env python +############################################################################## +# +# Copyright (c) 2001, 2002 Zope Corporation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +"""Driver program to run METAL and TAL regression tests. + +$Id$ +""" +import glob +import os +import sys +import traceback + +from cStringIO import StringIO + +if __name__ == "__main__": + import setpath # Local hack to tweak sys.path etc. + +import zope.tal.driver +import zope.tal.tests.utils + +def showdiff(a, b): + import ndiff + cruncher = ndiff.SequenceMatcher(ndiff.IS_LINE_JUNK, a, b) + for tag, alo, ahi, blo, bhi in cruncher.get_opcodes(): + if tag == "equal": + continue + print nicerange(alo, ahi) + tag[0] + nicerange(blo, bhi) + ndiff.dump('<', a, alo, ahi) + if a and b: + print '---' + ndiff.dump('>', b, blo, bhi) + +def nicerange(lo, hi): + if hi <= lo+1: + return str(lo+1) + else: + return "%d,%d" % (lo+1, hi) + +def main(): + opts = [] + args = sys.argv[1:] + quiet = 0 + unittesting = 0 + if args and args[0] == "-q": + quiet = 1 + del args[0] + if args and args[0] == "-Q": + unittesting = 1 + del args[0] + while args and args[0].startswith('-'): + opts.append(args[0]) + del args[0] + if not args: + prefix = os.path.join("tests", "input", "test*.") + if zope.tal.tests.utils.skipxml: + xmlargs = [] + else: + xmlargs = glob.glob(prefix + "xml") + xmlargs.sort() + htmlargs = glob.glob(prefix + "html") + htmlargs.sort() + args = xmlargs + htmlargs + if not args: + sys.stderr.write("No tests found -- please supply filenames\n") + sys.exit(1) + errors = 0 + for arg in args: + locopts = [] + if arg.find("metal") >= 0 and "-m" not in opts: + locopts.append("-m") + if arg.find("_sa") >= 0 and "-a" not in opts: + locopts.append("-a") + if not unittesting: + print arg, + sys.stdout.flush() + if zope.tal.tests.utils.skipxml and arg.endswith(".xml"): + print "SKIPPED (XML parser not available)" + continue + save = sys.stdout, sys.argv + try: + try: + sys.stdout = stdout = StringIO() + sys.argv = [""] + opts + locopts + [arg] + zope.tal.driver.main() + finally: + sys.stdout, sys.argv = save + except SystemExit: + raise + except: + errors = 1 + if quiet: + print sys.exc_type + sys.stdout.flush() + else: + if unittesting: + print + else: + print "Failed:" + sys.stdout.flush() + traceback.print_exc() + continue + head, tail = os.path.split(arg) + outfile = os.path.join( + head.replace("input", "output"), + tail) + try: + f = open(outfile) + except IOError: + expected = None + print "(missing file %s)" % outfile, + else: + expected = f.readlines() + f.close() + stdout.seek(0) + if hasattr(stdout, "readlines"): + actual = stdout.readlines() + else: + actual = readlines(stdout) + if actual == expected: + if not unittesting: + print "OK" + else: + if unittesting: + print + else: + print "not OK" + errors = 1 + if not quiet and expected is not None: + showdiff(expected, actual) + if errors: + sys.exit(1) + +def readlines(f): + L = [] + while 1: + line = f.readline() + if not line: + break + L.append(line) + return L + +if __name__ == "__main__": + main() diff --git a/src/zope/tal/setpath.py b/src/zope/tal/setpath.py new file mode 100644 index 0000000..18eced9 --- /dev/null +++ b/src/zope/tal/setpath.py @@ -0,0 +1,48 @@ +############################################################################## +# +# Copyright (c) 2001, 2002 Zope Corporation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +"""Read a module search path from .path file. + +If .path file isn't found in the directory of the setpath.py module, then try +to import ZODB. If that succeeds, we assume the path is already set up +correctly. If that import fails, an IOError is raised. + +$Id$ +""" + +# TODO: Why does this want to find ZODB ??? + +import os +import sys + +dir = os.path.dirname(__file__) +path = os.path.join(dir, ".path") +try: + f = open(path) +except IOError: + try: + # If we can import ZODB, our sys.path is set up well enough already + import ZODB + except ImportError: + raise IOError("Can't find ZODB package. Please edit %s to point to " + "your Zope's lib/python directory" % path) +else: + for line in f.readlines(): + line = line.strip() + if line and line[0] != '#': + for dir in line.split(os.pathsep): + dir = os.path.expanduser(os.path.expandvars(dir)) + if dir not in sys.path: + sys.path.append(dir) + # Must import this first to initialize Persistence properly + import ZODB diff --git a/src/zope/tal/taldefs.py b/src/zope/tal/taldefs.py new file mode 100644 index 0000000..554a4fe --- /dev/null +++ b/src/zope/tal/taldefs.py @@ -0,0 +1,201 @@ +############################################################################## +# +# Copyright (c) 2001, 2002 Zope Corporation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +"""Common definitions used by TAL and METAL compilation and transformation. + +$Id$ +""" +import re +from zope.tal.interfaces import ITALExpressionErrorInfo +from zope.interface import implements + + +TAL_VERSION = "1.6" + +XML_NS = "http://www.w3.org/XML/1998/namespace" # URI for XML namespace +XMLNS_NS = "http://www.w3.org/2000/xmlns/" # URI for XML NS declarations + +ZOPE_TAL_NS = "http://xml.zope.org/namespaces/tal" +ZOPE_METAL_NS = "http://xml.zope.org/namespaces/metal" +ZOPE_I18N_NS = "http://xml.zope.org/namespaces/i18n" + +# This RE must exactly match the expression of the same name in the +# zope.i18n.simpletranslationservice module: +NAME_RE = "[a-zA-Z_][-a-zA-Z0-9_]*" + +KNOWN_METAL_ATTRIBUTES = frozenset([ + "define-macro", + "extend-macro", + "use-macro", + "define-slot", + "fill-slot", + ]) + +KNOWN_TAL_ATTRIBUTES = frozenset([ + "define", + "condition", + "content", + "replace", + "repeat", + "attributes", + "on-error", + "omit-tag", + "script", + "tal tag", # a pseudo attribute that holds the namespace of elements + # like <tal:x>, <metal:y>, <i18n:z> + ]) + +KNOWN_I18N_ATTRIBUTES = frozenset([ + "translate", + "domain", + "target", + "source", + "attributes", + "data", + "name", + ]) + +class TALError(Exception): + + def __init__(self, msg, position=(None, None)): + assert msg != "" + self.msg = msg + self.lineno = position[0] + self.offset = position[1] + self.filename = None + + def setFile(self, filename): + self.filename = filename + + def __str__(self): + result = self.msg + if self.lineno is not None: + result = result + ", at line %d" % self.lineno + if self.offset is not None: + result = result + ", column %d" % (self.offset + 1) + if self.filename is not None: + result = result + ', in file %s' % self.filename + return result + +class METALError(TALError): + pass + +class TALExpressionError(TALError): + pass + +class I18NError(TALError): + pass + + +class ErrorInfo(object): + implements(ITALExpressionErrorInfo) + + def __init__(self, err, position=(None, None)): + if isinstance(err, Exception): + self.type = err.__class__ + self.value = err + else: + self.type = err + self.value = None + self.lineno = position[0] + self.offset = position[1] + + +_attr_re = re.compile(r"\s*([^\s]+)\s+([^\s].*)\Z", re.S) +_subst_re = re.compile(r"\s*(?:(text|structure)\s+)?(.*)\Z", re.S) + +def parseAttributeReplacements(arg, xml): + dict = {} + for part in splitParts(arg): + m = _attr_re.match(part) + if not m: + raise TALError("Bad syntax in attributes: %r" % part) + name, expr = m.groups() + if not xml: + name = name.lower() + if name in dict: + raise TALError("Duplicate attribute name in attributes: %r" % part) + dict[name] = expr + return dict + +def parseSubstitution(arg, position=(None, None)): + m = _subst_re.match(arg) + if not m: + raise TALError("Bad syntax in substitution text: %r" % arg, position) + key, expr = m.groups() + if not key: + key = "text" + return key, expr + +def splitParts(arg): + # Break in pieces at undoubled semicolons and + # change double semicolons to singles: + arg = arg.replace(";;", "\0") + parts = arg.split(';') + parts = [p.replace("\0", ";") for p in parts] + if len(parts) > 1 and not parts[-1].strip(): + del parts[-1] # It ended in a semicolon + return parts + +def isCurrentVersion(program): + version = getProgramVersion(program) + return version == TAL_VERSION + +def isinstance_(ob, type): + # Proxy-friendly and faster isinstance_ check for new-style objects + try: + return type in ob.__class__.__mro__ + except AttributeError: + return False + + +def getProgramMode(program): + version = getProgramVersion(program) + if (version == TAL_VERSION and isinstance_(program[1], tuple) and + len(program[1]) == 2): + opcode, mode = program[1] + if opcode == "mode": + return mode + return None + +def getProgramVersion(program): + if (len(program) >= 2 and + isinstance_(program[0], tuple) and len(program[0]) == 2): + opcode, version = program[0] + if opcode == "version": + return version + return None + +_ent1_re = re.compile('&(?![A-Z#])', re.I) +_entch_re = re.compile('&([A-Z][A-Z0-9]*)(?![A-Z0-9;])', re.I) +_entn1_re = re.compile('&#(?![0-9X])', re.I) +_entnx_re = re.compile('&(#X[A-F0-9]*)(?![A-F0-9;])', re.I) +_entnd_re = re.compile('&(#[0-9][0-9]*)(?![0-9;])') + +def attrEscape(s): + """Replace special characters '&<>' by character entities, + except when '&' already begins a syntactically valid entity.""" + s = _ent1_re.sub('&', s) + s = _entch_re.sub(r'&\1', s) + s = _entn1_re.sub('&#', s) + s = _entnx_re.sub(r'&\1', s) + s = _entnd_re.sub(r'&\1', s) + s = s.replace('<', '<') + s = s.replace('>', '>') + s = s.replace('"', '"') + return s + +import cgi +def quote(s, escape=cgi.escape): + return '"%s"' % escape(s, 1) +del cgi diff --git a/src/zope/tal/talgenerator.py b/src/zope/tal/talgenerator.py new file mode 100644 index 0000000..e01bd50 --- /dev/null +++ b/src/zope/tal/talgenerator.py @@ -0,0 +1,856 @@ +############################################################################## +# +# Copyright (c) 2001, 2002 Zope Corporation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +"""Code generator for TALInterpreter intermediate code. + +$Id$ +""" +import cgi +import re + +from zope.tal import taldefs +from zope.tal.taldefs import NAME_RE, TAL_VERSION +from zope.tal.taldefs import I18NError, METALError, TALError +from zope.tal.taldefs import parseSubstitution +from zope.tal.translationcontext import TranslationContext, DEFAULT_DOMAIN + + +_name_rx = re.compile(NAME_RE) + +class TALGenerator(object): + + inMacroUse = 0 + inMacroDef = 0 + source_file = None + + def __init__(self, expressionCompiler=None, xml=1, source_file=None): + if not expressionCompiler: + from zope.tal.dummyengine import DummyEngine + expressionCompiler = DummyEngine() + self.expressionCompiler = expressionCompiler + self.CompilerError = expressionCompiler.getCompilerError() + # This holds the emitted opcodes representing the input + self.program = [] + # The program stack for when we need to do some sub-evaluation for an + # intermediate result. E.g. in an i18n:name tag for which the + # contents describe the ${name} value. + self.stack = [] + # Another stack of postponed actions. Elements on this stack are a + # dictionary; key/values contain useful information that + # emitEndElement needs to finish its calculations + self.todoStack = [] + self.macros = {} + # {slot-name --> default content program} + self.slots = {} + self.slotStack = [] + self.xml = xml # true --> XML, false --> HTML + self.emit("version", TAL_VERSION) + self.emit("mode", xml and "xml" or "html") + if source_file is not None: + self.source_file = source_file + self.emit("setSourceFile", source_file) + self.i18nContext = TranslationContext() + self.i18nLevel = 0 + + def getCode(self): + assert not self.stack + assert not self.todoStack + return self.optimize(self.program), self.macros + + def optimize(self, program): + output = [] + collect = [] + cursor = 0 + for cursor in xrange(len(program)+1): + try: + item = program[cursor] + except IndexError: + item = (None, None) + opcode = item[0] + if opcode == "rawtext": + collect.append(item[1]) + continue + if opcode == "endTag": + collect.append("</%s>" % item[1]) + continue + if opcode == "startTag": + if self.optimizeStartTag(collect, item[1], item[2], ">"): + continue + if opcode == "startEndTag": + endsep = self.xml and "/>" or " />" + if self.optimizeStartTag(collect, item[1], item[2], endsep): + continue + if opcode in ("beginScope", "endScope"): + # Push *Scope instructions in front of any text instructions; + # this allows text instructions separated only by *Scope + # instructions to be joined together. + output.append(self.optimizeArgsList(item)) + continue + if opcode == 'noop': + # This is a spacer for end tags in the face of i18n:name + # attributes. We can't let the optimizer collect immediately + # following end tags into the same rawtextOffset. + opcode = None + pass + text = "".join(collect) + if text: + i = text.rfind("\n") + if i >= 0: + i = len(text) - (i + 1) + output.append(("rawtextColumn", (text, i))) + else: + output.append(("rawtextOffset", (text, len(text)))) + if opcode != None: + output.append(self.optimizeArgsList(item)) + collect = [] + return self.optimizeCommonTriple(output) + + def optimizeArgsList(self, item): + if len(item) == 2: + return item + else: + return item[0], tuple(item[1:]) + + # These codes are used to indicate what sort of special actions + # are needed for each special attribute. (Simple attributes don't + # get action codes.) + # + # The special actions (which are modal) are handled by + # TALInterpreter.attrAction() and .attrAction_tal(). + # + # Each attribute is represented by a tuple: + # + # (name, value) -- a simple name/value pair, with + # no special processing + # + # (name, value, action, *extra) -- attribute with special + # processing needs, action is a + # code that indicates which + # branch to take, and *extra + # contains additional, + # action-specific information + # needed by the processing + # + def optimizeStartTag(self, collect, name, attrlist, end): + # return true if the tag can be converted to plain text + if not attrlist: + collect.append("<%s%s" % (name, end)) + return 1 + opt = 1 + new = ["<" + name] + for i in range(len(attrlist)): + item = attrlist[i] + if len(item) > 2: + opt = 0 + name, value, action = item[:3] + attrlist[i] = (name, value, action) + item[3:] + else: + if item[1] is None: + s = item[0] + else: + s = '%s="%s"' % (item[0], taldefs.attrEscape(item[1])) + attrlist[i] = item[0], s + new.append(" " + s) + # if no non-optimizable attributes were found, convert to plain text + if opt: + new.append(end) + collect.extend(new) + return opt + + def optimizeCommonTriple(self, program): + if len(program) < 3: + return program + output = program[:2] + prev2, prev1 = output + for item in program[2:]: + if ( item[0] == "beginScope" + and prev1[0] == "setPosition" + and prev2[0] == "rawtextColumn"): + position = output.pop()[1] + text, column = output.pop()[1] + prev1 = None, None + closeprev = 0 + if output and output[-1][0] == "endScope": + closeprev = 1 + output.pop() + item = ("rawtextBeginScope", + (text, column, position, closeprev, item[1])) + output.append(item) + prev2 = prev1 + prev1 = item + return output + + def todoPush(self, todo): + self.todoStack.append(todo) + + def todoPop(self): + return self.todoStack.pop() + + def compileExpression(self, expr): + try: + return self.expressionCompiler.compile(expr) + except self.CompilerError, err: + raise TALError('%s in expression %s' % (err.args[0], `expr`), + self.position) + + def pushProgram(self): + self.stack.append(self.program) + self.program = [] + + def popProgram(self): + program = self.program + self.program = self.stack.pop() + return self.optimize(program) + + def pushSlots(self): + self.slotStack.append(self.slots) + self.slots = {} + + def popSlots(self): + slots = self.slots + self.slots = self.slotStack.pop() + return slots + + def emit(self, *instruction): + self.program.append(instruction) + + def emitStartTag(self, name, attrlist, isend=0): + if isend: + opcode = "startEndTag" + else: + opcode = "startTag" + self.emit(opcode, name, attrlist) + + def emitEndTag(self, name): + if self.xml and self.program and self.program[-1][0] == "startTag": + # Minimize empty element + self.program[-1] = ("startEndTag",) + self.program[-1][1:] + else: + self.emit("endTag", name) + + def emitOptTag(self, name, optTag, isend): + program = self.popProgram() #block + start = self.popProgram() #start tag + if (isend or not program) and self.xml: + # Minimize empty element + start[-1] = ("startEndTag",) + start[-1][1:] + isend = 1 + cexpr = optTag[0] + if cexpr: + cexpr = self.compileExpression(optTag[0]) + self.emit("optTag", name, cexpr, optTag[1], isend, start, program) + + def emitRawText(self, text): + self.emit("rawtext", text) + + def emitText(self, text): + self.emitRawText(cgi.escape(text)) + + def emitDefines(self, defines): + for part in taldefs.splitParts(defines): + m = re.match( + r"(?s)\s*(?:(global|local)\s+)?(%s)\s+(.*)\Z" % NAME_RE, part) + if not m: + raise TALError("invalid define syntax: " + `part`, + self.position) + scope, name, expr = m.group(1, 2, 3) + scope = scope or "local" + cexpr = self.compileExpression(expr) + if scope == "local": + self.emit("setLocal", name, cexpr) + else: + self.emit("setGlobal", name, cexpr) + + def emitOnError(self, name, onError, TALtag, isend): + block = self.popProgram() + key, expr = parseSubstitution(onError) + cexpr = self.compileExpression(expr) + if key == "text": + self.emit("insertText", cexpr, []) + else: + assert key == "structure" + self.emit("insertStructure", cexpr, {}, []) + if TALtag: + self.emitOptTag(name, (None, 1), isend) + else: + self.emitEndTag(name) + handler = self.popProgram() + self.emit("onError", block, handler) + + def emitCondition(self, expr): + cexpr = self.compileExpression(expr) + program = self.popProgram() + self.emit("condition", cexpr, program) + + def emitRepeat(self, arg): + m = re.match("(?s)\s*(%s)\s+(.*)\Z" % NAME_RE, arg) + if not m: + raise TALError("invalid repeat syntax: " + `arg`, + self.position) + name, expr = m.group(1, 2) + cexpr = self.compileExpression(expr) + program = self.popProgram() + self.emit("loop", name, cexpr, program) + + def emitSubstitution(self, arg, attrDict={}): + key, expr = parseSubstitution(arg) + cexpr = self.compileExpression(expr) + program = self.popProgram() + if key == "text": + self.emit("insertText", cexpr, program) + else: + assert key == "structure" + self.emit("insertStructure", cexpr, attrDict, program) + + def emitI18nSubstitution(self, arg, attrDict={}): + # TODO: Code duplication is BAD, we need to fix it later + key, expr = parseSubstitution(arg) + cexpr = self.compileExpression(expr) + program = self.popProgram() + if key == "text": + self.emit("insertI18nText", cexpr, program) + else: + assert key == "structure" + self.emit("insertI18nStructure", cexpr, attrDict, program) + + def emitEvaluateCode(self, lang): + program = self.popProgram() + self.emit('evaluateCode', lang, program) + + def emitI18nVariable(self, varname): + # Used for i18n:name attributes. + m = _name_rx.match(varname) + if m is None or m.group() != varname: + raise TALError("illegal i18n:name: %r" % varname, self.position) + program = self.popProgram() + self.emit('i18nVariable', varname, program, None, False) + + def emitTranslation(self, msgid, i18ndata): + program = self.popProgram() + if i18ndata is None: + self.emit('insertTranslation', msgid, program) + else: + key, expr = parseSubstitution(i18ndata) + cexpr = self.compileExpression(expr) + assert key == 'text' + self.emit('insertTranslation', msgid, program, cexpr) + + def emitDefineMacro(self, macroName): + program = self.popProgram() + macroName = macroName.strip() + if self.macros.has_key(macroName): + raise METALError("duplicate macro definition: %s" % `macroName`, + self.position) + if not re.match('%s$' % NAME_RE, macroName): + raise METALError("invalid macro name: %s" % `macroName`, + self.position) + self.macros[macroName] = program + self.inMacroDef = self.inMacroDef - 1 + self.emit("defineMacro", macroName, program) + + def emitUseMacro(self, expr): + cexpr = self.compileExpression(expr) + program = self.popProgram() + self.inMacroUse = 0 + self.emit("useMacro", expr, cexpr, self.popSlots(), program) + + def emitExtendMacro(self, defineName, useExpr): + cexpr = self.compileExpression(useExpr) + program = self.popProgram() + self.inMacroUse = 0 + self.emit("extendMacro", useExpr, cexpr, self.popSlots(), program, + defineName) + self.emitDefineMacro(defineName) + + def emitDefineSlot(self, slotName): + program = self.popProgram() + slotName = slotName.strip() + if not re.match('%s$' % NAME_RE, slotName): + raise METALError("invalid slot name: %s" % `slotName`, + self.position) + self.emit("defineSlot", slotName, program) + + def emitFillSlot(self, slotName): + program = self.popProgram() + slotName = slotName.strip() + if self.slots.has_key(slotName): + raise METALError("duplicate fill-slot name: %s" % `slotName`, + self.position) + if not re.match('%s$' % NAME_RE, slotName): + raise METALError("invalid slot name: %s" % `slotName`, + self.position) + self.slots[slotName] = program + self.inMacroUse = 1 + self.emit("fillSlot", slotName, program) + + def unEmitWhitespace(self): + collect = [] + i = len(self.program) - 1 + while i >= 0: + item = self.program[i] + if item[0] != "rawtext": + break + text = item[1] + if not re.match(r"\A\s*\Z", text): + break + collect.append(text) + i = i-1 + del self.program[i+1:] + if i >= 0 and self.program[i][0] == "rawtext": + text = self.program[i][1] + m = re.search(r"\s+\Z", text) + if m: + self.program[i] = ("rawtext", text[:m.start()]) + collect.append(m.group()) + collect.reverse() + return "".join(collect) + + def unEmitNewlineWhitespace(self): + collect = [] + i = len(self.program) + while i > 0: + i = i-1 + item = self.program[i] + if item[0] != "rawtext": + break + text = item[1] + if re.match(r"\A[ \t]*\Z", text): + collect.append(text) + continue + m = re.match(r"(?s)^(.*)(\n[ \t]*)\Z", text) + if not m: + break + text, rest = m.group(1, 2) + collect.reverse() + rest = rest + "".join(collect) + del self.program[i:] + if text: + self.emit("rawtext", text) + return rest + return None + + def replaceAttrs(self, attrlist, repldict): + # Each entry in attrlist starts like (name, value). Result is + # (name, value, action, expr, xlat, msgid) if there is a + # tal:attributes entry for that attribute. Additional attrs + # defined only by tal:attributes are added here. + # + # (name, value, action, expr, xlat, msgid) + if not repldict: + return attrlist + newlist = [] + for item in attrlist: + key = item[0] + if repldict.has_key(key): + expr, xlat, msgid = repldict[key] + item = item[:2] + ("replace", expr, xlat, msgid) + del repldict[key] + newlist.append(item) + # Add dynamic-only attributes + for key, (expr, xlat, msgid) in repldict.items(): + newlist.append((key, None, "insert", expr, xlat, msgid)) + return newlist + + def emitStartElement(self, name, attrlist, taldict, metaldict, i18ndict, + position=(None, None), isend=0): + if not taldict and not metaldict and not i18ndict: + # Handle the simple, common case + self.emitStartTag(name, attrlist, isend) + self.todoPush({}) + if isend: + self.emitEndElement(name, isend) + return + self.position = position + + # TODO: Ugly hack to work around tal:replace and i18n:translate issue. + # I (DV) need to cleanup the code later. + replaced = False + if "replace" in taldict: + if "content" in taldict: + raise TALError( + "tal:content and tal:replace are mutually exclusive", + position) + taldict["omit-tag"] = taldict.get("omit-tag", "") + taldict["content"] = taldict.pop("replace") + replaced = True + + for key, value in taldict.items(): + if key not in taldefs.KNOWN_TAL_ATTRIBUTES: + raise TALError("bad TAL attribute: " + `key`, position) + if not (value or key == 'omit-tag'): + raise TALError("missing value for TAL attribute: " + + `key`, position) + for key, value in metaldict.items(): + if key not in taldefs.KNOWN_METAL_ATTRIBUTES: + raise METALError("bad METAL attribute: " + `key`, + position) + if not value: + raise TALError("missing value for METAL attribute: " + + `key`, position) + for key, value in i18ndict.items(): + if key not in taldefs.KNOWN_I18N_ATTRIBUTES: + raise I18NError("bad i18n attribute: " + `key`, position) + if not value and key in ("attributes", "data", "id"): + raise I18NError("missing value for i18n attribute: " + + `key`, position) + + todo = {} + defineMacro = metaldict.get("define-macro") + extendMacro = metaldict.get("extend-macro") + useMacro = metaldict.get("use-macro") + defineSlot = metaldict.get("define-slot") + fillSlot = metaldict.get("fill-slot") + define = taldict.get("define") + condition = taldict.get("condition") + repeat = taldict.get("repeat") + content = taldict.get("content") + script = taldict.get("script") + attrsubst = taldict.get("attributes") + onError = taldict.get("on-error") + omitTag = taldict.get("omit-tag") + TALtag = taldict.get("tal tag") + i18nattrs = i18ndict.get("attributes") + # Preserve empty string if implicit msgids are used. We'll generate + # code with the msgid='' and calculate the right implicit msgid during + # interpretation phase. + msgid = i18ndict.get("translate") + varname = i18ndict.get('name') + i18ndata = i18ndict.get('data') + + if varname and not self.i18nLevel: + raise I18NError( + "i18n:name can only occur inside a translation unit", + position) + + if i18ndata and not msgid: + raise I18NError("i18n:data must be accompanied by i18n:translate", + position) + + if extendMacro: + if useMacro: + raise METALError( + "extend-macro cannot be used with use-macro", position) + if not defineMacro: + raise METALError( + "extend-macro must be used with define-macro", position) + + if defineMacro or extendMacro or useMacro: + if fillSlot or defineSlot: + raise METALError( + "define-slot and fill-slot cannot be used with " + "define-macro, extend-macro, or use-macro", position) + if defineMacro and useMacro: + raise METALError( + "define-macro may not be used with use-macro", position) + + useMacro = useMacro or extendMacro + + if content and msgid: + raise I18NError( + "explicit message id and tal:content can't be used together", + position) + + repeatWhitespace = None + if repeat: + # Hack to include preceding whitespace in the loop program + repeatWhitespace = self.unEmitNewlineWhitespace() + if position != (None, None): + # TODO: at some point we should insist on a non-trivial position + self.emit("setPosition", position) + if self.inMacroUse: + if fillSlot: + self.pushProgram() + # generate a source annotation at the beginning of fill-slot + if self.source_file is not None: + if position != (None, None): + self.emit("setPosition", position) + self.emit("setSourceFile", self.source_file) + todo["fillSlot"] = fillSlot + self.inMacroUse = 0 + else: + if fillSlot: + raise METALError("fill-slot must be within a use-macro", + position) + if not self.inMacroUse: + if defineMacro: + self.pushProgram() + self.emit("version", TAL_VERSION) + self.emit("mode", self.xml and "xml" or "html") + # generate a source annotation at the beginning of the macro + if self.source_file is not None: + if position != (None, None): + self.emit("setPosition", position) + self.emit("setSourceFile", self.source_file) + todo["defineMacro"] = defineMacro + self.inMacroDef = self.inMacroDef + 1 + if useMacro: + self.pushSlots() + self.pushProgram() + todo["useMacro"] = useMacro + self.inMacroUse = 1 + if defineSlot: + if not self.inMacroDef: + raise METALError( + "define-slot must be within a define-macro", + position) + self.pushProgram() + todo["defineSlot"] = defineSlot + + if defineSlot or i18ndict: + + domain = i18ndict.get("domain") or self.i18nContext.domain + source = i18ndict.get("source") or self.i18nContext.source + target = i18ndict.get("target") or self.i18nContext.target + if ( domain != DEFAULT_DOMAIN + or source is not None + or target is not None): + self.i18nContext = TranslationContext(self.i18nContext, + domain=domain, + source=source, + target=target) + self.emit("beginI18nContext", + {"domain": domain, "source": source, + "target": target}) + todo["i18ncontext"] = 1 + if taldict or i18ndict: + dict = {} + for item in attrlist: + key, value = item[:2] + dict[key] = value + self.emit("beginScope", dict) + todo["scope"] = 1 + if onError: + self.pushProgram() # handler + if TALtag: + self.pushProgram() # start + self.emitStartTag(name, list(attrlist)) # Must copy attrlist! + if TALtag: + self.pushProgram() # start + self.pushProgram() # block + todo["onError"] = onError + if define: + self.emitDefines(define) + todo["define"] = define + if condition: + self.pushProgram() + todo["condition"] = condition + if repeat: + todo["repeat"] = repeat + self.pushProgram() + if repeatWhitespace: + self.emitText(repeatWhitespace) + if content: + if varname: + todo['i18nvar'] = varname + todo["content"] = content + self.pushProgram() + else: + todo["content"] = content + # i18n:name w/o tal:replace uses the content as the interpolation + # dictionary values + elif varname: + todo['i18nvar'] = varname + self.pushProgram() + if msgid is not None: + self.i18nLevel += 1 + todo['msgid'] = msgid + if i18ndata: + todo['i18ndata'] = i18ndata + optTag = omitTag is not None or TALtag + if optTag: + todo["optional tag"] = omitTag, TALtag + self.pushProgram() + if attrsubst or i18nattrs: + if attrsubst: + repldict = taldefs.parseAttributeReplacements(attrsubst, + self.xml) + else: + repldict = {} + if i18nattrs: + i18nattrs = _parseI18nAttributes(i18nattrs, self.position, + self.xml) + else: + i18nattrs = {} + # Convert repldict's name-->expr mapping to a + # name-->(compiled_expr, translate) mapping + for key, value in repldict.items(): + if i18nattrs.get(key, None): + raise I18NError( + "attribute [%s] cannot both be part of tal:attributes" + " and have a msgid in i18n:attributes" % key, + position) + ce = self.compileExpression(value) + repldict[key] = ce, key in i18nattrs, i18nattrs.get(key) + for key in i18nattrs: + if key not in repldict: + repldict[key] = None, 1, i18nattrs.get(key) + else: + repldict = {} + if replaced: + todo["repldict"] = repldict + repldict = {} + if script: + todo["script"] = script + self.emitStartTag(name, self.replaceAttrs(attrlist, repldict), isend) + if optTag: + self.pushProgram() + if content and not varname: + self.pushProgram() + if not content and msgid is not None: + self.pushProgram() + if content and varname: + self.pushProgram() + if script: + self.pushProgram() + if todo and position != (None, None): + todo["position"] = position + self.todoPush(todo) + if isend: + self.emitEndElement(name, isend, position=position) + + def emitEndElement(self, name, isend=0, implied=0, position=(None, None)): + todo = self.todoPop() + if not todo: + # Shortcut + if not isend: + self.emitEndTag(name) + return + + self.position = todo.get("position", (None, None)) + defineMacro = todo.get("defineMacro") + useMacro = todo.get("useMacro") + defineSlot = todo.get("defineSlot") + fillSlot = todo.get("fillSlot") + repeat = todo.get("repeat") + content = todo.get("content") + script = todo.get("script") + condition = todo.get("condition") + onError = todo.get("onError") + repldict = todo.get("repldict", {}) + scope = todo.get("scope") + optTag = todo.get("optional tag") + msgid = todo.get('msgid') + i18ncontext = todo.get("i18ncontext") + varname = todo.get('i18nvar') + i18ndata = todo.get('i18ndata') + + if implied > 0: + if defineMacro or useMacro or defineSlot or fillSlot: + exc = METALError + what = "METAL" + else: + exc = TALError + what = "TAL" + raise exc("%s attributes on <%s> require explicit </%s>" % + (what, name, name), self.position) + + if script: + self.emitEvaluateCode(script) + # If there's no tal:content or tal:replace in the tag with the + # i18n:name, tal:replace is the default. + if content: + if msgid is not None: + self.emitI18nSubstitution(content, repldict) + else: + self.emitSubstitution(content, repldict) + # If we're looking at an implicit msgid, emit the insertTranslation + # opcode now, so that the end tag doesn't become part of the implicit + # msgid. If we're looking at an explicit msgid, it's better to emit + # the opcode after the i18nVariable opcode so we can better handle + # tags with both of them in them (and in the latter case, the contents + # would be thrown away for msgid purposes). + # + # Still, we should emit insertTranslation opcode before i18nVariable + # in case tal:content, i18n:translate and i18n:name in the same tag + if not content and msgid is not None: + self.emitTranslation(msgid, i18ndata) + self.i18nLevel -= 1 + if optTag: + self.emitOptTag(name, optTag, isend) + elif not isend: + # If we're processing the end tag for a tag that contained + # i18n:name, we need to make sure that optimize() won't collect + # immediately following end tags into the same rawtextOffset, so + # put a spacer here that the optimizer will recognize. + if varname: + self.emit('noop') + self.emitEndTag(name) + if varname: + self.emitI18nVariable(varname) + if repeat: + self.emitRepeat(repeat) + if condition: + self.emitCondition(condition) + if onError: + self.emitOnError(name, onError, optTag and optTag[1], isend) + if scope: + self.emit("endScope") + if i18ncontext: + self.emit("endI18nContext") + assert self.i18nContext.parent is not None + self.i18nContext = self.i18nContext.parent + if defineSlot: + self.emitDefineSlot(defineSlot) + if fillSlot: + self.emitFillSlot(fillSlot) + if useMacro or defineMacro: + if useMacro and defineMacro: + self.emitExtendMacro(defineMacro, useMacro) + elif useMacro: + self.emitUseMacro(useMacro) + elif defineMacro: + self.emitDefineMacro(defineMacro) + if useMacro or defineSlot: + # generate a source annotation after define-slot or use-macro + # because the source file might have changed + if self.source_file is not None: + if position != (None, None): + self.emit("setPosition", position) + self.emit("setSourceFile", self.source_file) + + +def _parseI18nAttributes(i18nattrs, position, xml): + d = {} + # Filter out empty items, eg: + # i18n:attributes="value msgid; name msgid2;" + # would result in 3 items where the last one is empty + attrs = [spec for spec in i18nattrs.split(";") if spec] + for spec in attrs: + parts = spec.split() + if len(parts) == 2: + attr, msgid = parts + elif len(parts) == 1: + attr = parts[0] + msgid = None + else: + raise TALError("illegal i18n:attributes specification: %r" % spec, + position) + if not xml: + attr = attr.lower() + if attr in d: + raise TALError( + "attribute may only be specified once in i18n:attributes: %r" + % attr, + position) + d[attr] = msgid + return d + +def test(): + t = TALGenerator() + t.pushProgram() + t.emit("bar") + p = t.popProgram() + t.emit("foo", p) + +if __name__ == "__main__": + test() diff --git a/src/zope/tal/talgettext.py b/src/zope/tal/talgettext.py new file mode 100644 index 0000000..2a0d794 --- /dev/null +++ b/src/zope/tal/talgettext.py @@ -0,0 +1,316 @@ +#!/usr/bin/env python +############################################################################## +# +# Copyright (c) 2002 Zope Corporation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +"""Program to extract internationalization markup from Page Templates. + +Once you have marked up a Page Template file with i18n: namespace tags, use +this program to extract GNU gettext .po file entries. + +Usage: talgettext.py [options] files +Options: + -h / --help + Print this message and exit. + -o / --output <file> + Output the translation .po file to <file>. + -u / --update <file> + Update the existing translation <file> with any new translation strings + found. + +$Id$ +""" +import sys +import time +import getopt +import traceback + +from zope.interface import implements +from zope.tal.htmltalparser import HTMLTALParser +from zope.tal.talinterpreter import TALInterpreter, normalize +from zope.tal.dummyengine import DummyEngine +from zope.tal.interfaces import ITALExpressionEngine +from zope.tal.taldefs import TALExpressionError +from zope.i18nmessageid import Message + +pot_header = '''\ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR ORGANIZATION +# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\\n" +"POT-Creation-Date: %(time)s\\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\\n" +"Last-Translator: FULL NAME <EMAIL@ADDRESS>\\n" +"Language-Team: LANGUAGE <LL@li.org>\\n" +"MIME-Version: 1.0\\n" +"Content-Type: text/plain; charset=CHARSET\\n" +"Content-Transfer-Encoding: ENCODING\\n" +"Generated-By: talgettext.py %(version)s\\n" +''' + +NLSTR = '"\n"' + +def usage(code, msg=''): + # Python 2.1 required + print >> sys.stderr, __doc__ + if msg: + print >> sys.stderr, msg + sys.exit(code) + + +class POTALInterpreter(TALInterpreter): + def translate(self, msgid, default=None, i18ndict=None, obj=None): + if default is None: + default = getattr(msgid, 'default', unicode(msgid)) + # If no i18n dict exists yet, create one. + if i18ndict is None: + i18ndict = {} + if obj: + i18ndict.update(obj) + # Mmmh, it seems that sometimes the msgid is None; is that really + # possible? + if msgid is None: + return None + # TODO: We need to pass in one of context or target_language + return self.engine.translate(msgid, self.i18nContext.domain, i18ndict, + default=default, position=self.position) + + +class POEngine(DummyEngine): + implements(ITALExpressionEngine) + + def __init__(self, macros=None): + self.catalog = {} + DummyEngine.__init__(self, macros) + + def evaluate(*args): + # If the result of evaluate ever gets into a message ID, we want + # to notice the fact in the .pot file. + return '${DYNAMIC_CONTENT}' + + def evaluatePathOrVar(*args): + # Actually this method is never called. + return 'XXX' + + def evaluateSequence(self, expr): + return (0,) # dummy + + def evaluateBoolean(self, expr): + return True # dummy + + def translate(self, msgid, domain=None, mapping=None, default=None, + # Position is not part of the ITALExpressionEngine + # interface + position=None): + + # Make the message is a Message object, if the default differs + # from the value, so that the POT generator can put the default + # text into a comment. + if default is not None and normalize(default) != msgid: + msgid = Message(msgid, default=default) + + if domain not in self.catalog: + self.catalog[domain] = {} + domain = self.catalog[domain] + + if msgid not in domain: + domain[msgid] = [] + domain[msgid].append((self.file, position)) + return 'x' + + +class UpdatePOEngine(POEngine): + """A slightly-less braindead POEngine which supports loading an existing + .po file first.""" + + def __init__ (self, macros=None, filename=None): + POEngine.__init__(self, macros) + + self._filename = filename + self._loadFile() + self.base = self.catalog + self.catalog = {} + + def __add(self, id, s, fuzzy): + "Add a non-fuzzy translation to the dictionary." + if not fuzzy and str: + # check for multi-line values and munge them appropriately + if '\n' in s: + lines = s.rstrip().split('\n') + s = NLSTR.join(lines) + self.catalog[id] = s + + def _loadFile(self): + # shamelessly cribbed from Python's Tools/i18n/msgfmt.py + # 25-Mar-2003 Nathan R. Yergler (nathan@zope.org) + # 14-Apr-2003 Hacked by Barry Warsaw (barry@zope.com) + + ID = 1 + STR = 2 + + try: + lines = open(self._filename).readlines() + except IOError, msg: + print >> sys.stderr, msg + sys.exit(1) + + section = None + fuzzy = False + + # Parse the catalog + lno = 0 + for l in lines: + lno += True + # If we get a comment line after a msgstr, this is a new entry + if l[0] == '#' and section == STR: + self.__add(msgid, msgstr, fuzzy) + section = None + fuzzy = False + # Record a fuzzy mark + if l[:2] == '#,' and l.find('fuzzy'): + fuzzy = True + # Skip comments + if l[0] == '#': + continue + # Now we are in a msgid section, output previous section + if l.startswith('msgid'): + if section == STR: + self.__add(msgid, msgstr, fuzzy) + section = ID + l = l[5:] + msgid = msgstr = '' + # Now we are in a msgstr section + elif l.startswith('msgstr'): + section = STR + l = l[6:] + # Skip empty lines + if not l.strip(): + continue + # TODO: Does this always follow Python escape semantics? + l = eval(l) + if section == ID: + msgid += l + elif section == STR: + msgstr += '%s\n' % l + else: + print >> sys.stderr, 'Syntax error on %s:%d' % (infile, lno), \ + 'before:' + print >> sys.stderr, l + sys.exit(1) + # Add last entry + if section == STR: + self.__add(msgid, msgstr, fuzzy) + + def evaluate(self, expression): + try: + return POEngine.evaluate(self, expression) + except TALExpressionError: + pass + + def evaluatePathOrVar(self, expr): + return 'who cares' + + def translate(self, msgid, domain=None, mapping=None, default=None, + position=None): + if msgid not in self.base: + POEngine.translate(self, msgid, domain, mapping, default, position) + return 'x' + + +def main(): + try: + opts, args = getopt.getopt( + sys.argv[1:], + 'ho:u:', + ['help', 'output=', 'update=']) + except getopt.error, msg: + usage(1, msg) + + outfile = None + engine = None + update_mode = False + for opt, arg in opts: + if opt in ('-h', '--help'): + usage(0) + elif opt in ('-o', '--output'): + outfile = arg + elif opt in ('-u', '--update'): + update_mode = True + if outfile is None: + outfile = arg + engine = UpdatePOEngine(filename=arg) + + if not args: + print 'nothing to do' + return + + # We don't care about the rendered output of the .pt file + class Devnull(object): + def write(self, s): + pass + + # check if we've already instantiated an engine; + # if not, use the stupidest one available + if not engine: + engine = POEngine() + + # process each file specified + for filename in args: + try: + engine.file = filename + p = HTMLTALParser() + p.parseFile(filename) + program, macros = p.getCode() + POTALInterpreter(program, macros, engine, stream=Devnull(), + metal=False)() + except: # Hee hee, I love bare excepts! + print 'There was an error processing', filename + traceback.print_exc() + + # Now output the keys in the engine. Write them to a file if --output or + # --update was specified; otherwise use standard out. + if (outfile is None): + outfile = sys.stdout + else: + outfile = file(outfile, update_mode and "a" or "w") + + catalog = {} + for domain in engine.catalog.keys(): + catalog.update(engine.catalog[domain]) + + messages = catalog.copy() + try: + messages.update(engine.base) + except AttributeError: + pass + if '' not in messages: + print >> outfile, pot_header % {'time': time.ctime(), + 'version': __version__} + + msgids = catalog.keys() + # TODO: You should not sort by msgid, but by filename and position. (SR) + msgids.sort() + for msgid in msgids: + positions = engine.catalog[msgid] + for filename, position in positions: + outfile.write('#: %s:%s\n' % (filename, position[0])) + + outfile.write('msgid "%s"\n' % msgid) + outfile.write('msgstr ""\n') + outfile.write('\n') + + +if __name__ == '__main__': + main() diff --git a/src/zope/tal/talinterpreter.py b/src/zope/tal/talinterpreter.py new file mode 100644 index 0000000..9c801bf --- /dev/null +++ b/src/zope/tal/talinterpreter.py @@ -0,0 +1,1024 @@ +############################################################################## +# +# Copyright (c) 2001, 2002 Zope Corporation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +"""Interpreter for a pre-compiled TAL program. + +$Id$ +""" +import cgi +import operator +import sys +import warnings + +# Do not use cStringIO here! It's not unicode aware. :( +from StringIO import StringIO + +from zope.i18nmessageid import Message +from zope.tal.taldefs import quote, TAL_VERSION, METALError +from zope.tal.taldefs import isCurrentVersion +from zope.tal.taldefs import getProgramVersion, getProgramMode +from zope.tal.talgenerator import TALGenerator +from zope.tal.translationcontext import TranslationContext + + +# Avoid constructing this tuple over and over +I18nMessageTypes = (Message,) + +TypesToTranslate = I18nMessageTypes + (str, unicode) + +BOOLEAN_HTML_ATTRS = frozenset([ + # List of Boolean attributes in HTML that should be rendered in + # minimized form (e.g. <img ismap> rather than <img ismap="">) + # From http://www.w3.org/TR/xhtml1/#guidelines (C.10) + # TODO: The problem with this is that this is not valid XML and + # can't be parsed back! + "compact", "nowrap", "ismap", "declare", "noshade", "checked", + "disabled", "readonly", "multiple", "selected", "noresize", + "defer" +]) + +_nulljoin = ''.join +_spacejoin = ' '.join + +def normalize(text): + # Now we need to normalize the whitespace in implicit message ids and + # implicit $name substitution values by stripping leading and trailing + # whitespace, and folding all internal whitespace to a single space. + return _spacejoin(text.split()) + + +class AltTALGenerator(TALGenerator): + + def __init__(self, repldict, expressionCompiler=None, xml=0): + self.repldict = repldict + self.enabled = 1 + TALGenerator.__init__(self, expressionCompiler, xml) + + def enable(self, enabled): + self.enabled = enabled + + def emit(self, *args): + if self.enabled: + TALGenerator.emit(self, *args) + + def emitStartElement(self, name, attrlist, taldict, metaldict, i18ndict, + position=(None, None), isend=0): + metaldict = {} + taldict = {} + i18ndict = {} + if self.enabled and self.repldict: + taldict["attributes"] = "x x" + TALGenerator.emitStartElement(self, name, attrlist, + taldict, metaldict, i18ndict, + position, isend) + + def replaceAttrs(self, attrlist, repldict): + if self.enabled and self.repldict: + repldict = self.repldict + self.repldict = None + return TALGenerator.replaceAttrs(self, attrlist, repldict) + + + +class MacroStackItem(list): + # This is a `list` subclass for backward compability. + """Stack entry for the TALInterpreter.macroStack. + + This offers convenience attributes for more readable access. + + """ + __slots__ = () + + # These would be nicer using @syntax, but that would require + # Python 2.4.x; this will do for now. + + macroName = property(lambda self: self[0]) + slots = property(lambda self: self[1]) + definingName = property(lambda self: self[2]) + extending = property(lambda self: self[3]) + entering = property(lambda self: self[4], + lambda self, value: operator.setitem(self, 4, value)) + i18nContext = property(lambda self: self[5]) + + +class TALInterpreter(object): + """TAL interpreter. + + Some notes on source annotations. They are HTML/XML comments added to the + output whenever sourceFile is changed by a setSourceFile bytecode. Source + annotations are disabled by default, but you can turn them on by passing a + sourceAnnotations argument to the constructor. You can change the format + of the annotations by overriding formatSourceAnnotation in a subclass. + + The output of the annotation is delayed until some actual text is output + for two reasons: + + 1. setPosition bytecode follows setSourceFile, and we need position + information to output the line number. + 2. Comments are not allowed in XML documents before the <?xml?> + declaration. + + For performance reasons (TODO: premature optimization?) instead of checking + the value of _pending_source_annotation on every write to the output + stream, the _stream_write attribute is changed to point to + _annotated_stream_write method whenever _pending_source_annotation is + set to True, and to _stream.write when it is False. The following + invariant always holds: + + if self._pending_source_annotation: + assert self._stream_write is self._annotated_stream_write + else: + assert self._stream_write is self.stream.write + + """ + + def __init__(self, program, macros, engine, stream=None, + debug=0, wrap=60, metal=1, tal=1, showtal=-1, + strictinsert=1, stackLimit=100, i18nInterpolate=1, + sourceAnnotations=0): + """Create a TAL interpreter. + + Optional arguments: + + stream -- output stream (defaults to sys.stdout). + + debug -- enable debugging output to sys.stderr (off by default). + + wrap -- try to wrap attributes on opening tags to this number of + column (default: 60). + + metal -- enable METAL macro processing (on by default). + + tal -- enable TAL processing (on by default). + + showtal -- do not strip away TAL directives. A special value of + -1 (which is the default setting) enables showtal when TAL + processing is disabled, and disables showtal when TAL processing is + enabled. Note that you must use 0, 1, or -1; true boolean values + are not supported (TODO: why?). + + strictinsert -- enable TAL processing and stricter HTML/XML + checking on text produced by structure inserts (on by default). + Note that Zope turns this value off by default. + + stackLimit -- set macro nesting limit (default: 100). + + i18nInterpolate -- enable i18n translations (default: on). + + sourceAnnotations -- enable source annotations with HTML comments + (default: off). + + """ + self.program = program + self.macros = macros + self.engine = engine # Execution engine (aka context) + self.Default = engine.getDefault() + self._pending_source_annotation = False + self._currentTag = "" + self._stream_stack = [stream or sys.stdout] + self.popStream() + self.debug = debug + self.wrap = wrap + self.metal = metal + self.tal = tal + if tal: + self.dispatch = self.bytecode_handlers_tal + else: + self.dispatch = self.bytecode_handlers + assert showtal in (-1, 0, 1) + if showtal == -1: + showtal = (not tal) + self.showtal = showtal + self.strictinsert = strictinsert + self.stackLimit = stackLimit + self.html = 0 + self.endsep = "/>" + self.endlen = len(self.endsep) + # macroStack entries are MacroStackItem instances; + # the entries are mutated while on the stack + self.macroStack = [] + # `inUseDirective` is set iff we're handling either a + # metal:use-macro or a metal:extend-macro + self.inUseDirective = False + self.position = None, None # (lineno, offset) + self.col = 0 + self.level = 0 + self.scopeLevel = 0 + self.sourceFile = None + self.i18nStack = [] + self.i18nInterpolate = i18nInterpolate + self.i18nContext = TranslationContext() + self.sourceAnnotations = sourceAnnotations + + def StringIO(self): + # Third-party products wishing to provide a full Unicode-aware + # StringIO can do so by monkey-patching this method. + return FasterStringIO() + + def saveState(self): + return (self.position, self.col, self.stream, self._stream_stack, + self.scopeLevel, self.level, self.i18nContext) + + def restoreState(self, state): + (self.position, self.col, self.stream, + self._stream_stack, scopeLevel, level, i18n) = state + if self._pending_source_annotation: + self._stream_write = self._annotated_stream_write + else: + self._stream_write = self.stream.write + assert self.level == level + while self.scopeLevel > scopeLevel: + self.engine.endScope() + self.scopeLevel = self.scopeLevel - 1 + self.engine.setPosition(self.position) + self.i18nContext = i18n + + def restoreOutputState(self, state): + (dummy, self.col, self.stream, + self._stream_stack, scopeLevel, level, i18n) = state + if self._pending_source_annotation: + self._stream_write = self._annotated_stream_write + else: + self._stream_write = self.stream.write + assert self.level == level + assert self.scopeLevel == scopeLevel + + def pushMacro(self, macroName, slots, definingName, extending): + if len(self.macroStack) >= self.stackLimit: + raise METALError("macro nesting limit (%d) exceeded " + "by %s" % (self.stackLimit, `macroName`)) + self.macroStack.append( + MacroStackItem((macroName, slots, definingName, extending, + True, self.i18nContext))) + + def popMacro(self): + return self.macroStack.pop() + + def __call__(self): + assert self.level == 0 + assert self.scopeLevel == 0 + assert self.i18nContext.parent is None + self.interpret(self.program) + assert self.level == 0 + assert self.scopeLevel == 0 + assert self.i18nContext.parent is None + if self.col > 0: + self._stream_write("\n") + self.col = 0 + + def pushStream(self, newstream): + self._stream_stack.append(self.stream) + self.stream = newstream + if self._pending_source_annotation: + self._stream_write = self._annotated_stream_write + else: + self._stream_write = self.stream.write + + def popStream(self): + self.stream = self._stream_stack.pop() + if self._pending_source_annotation: + self._stream_write = self._annotated_stream_write + else: + self._stream_write = self.stream.write + + def _annotated_stream_write(self, s): + idx = s.find('<?xml') + if idx >= 0 or s.isspace(): + # Do not preprend comments in front of the <?xml?> declaration. + end_of_doctype = s.find('?>', idx) + if end_of_doctype > idx: + self.stream.write(s[:end_of_doctype+2]) + s = s[end_of_doctype+2:] + # continue + else: + self.stream.write(s) + return + self._pending_source_annotation = False + self._stream_write = self.stream.write + self._stream_write(self.formatSourceAnnotation()) + self._stream_write(s) + + def formatSourceAnnotation(self): + lineno = self.position[0] + if lineno is None: + location = self.sourceFile + else: + location = '%s (line %s)' % (self.sourceFile, lineno) + sep = '=' * 78 + return '<!--\n%s\n%s\n%s\n-->' % (sep, location, sep) + + def stream_write(self, s, + len=len): + self._stream_write(s) + i = s.rfind('\n') + if i < 0: + self.col = self.col + len(s) + else: + self.col = len(s) - (i + 1) + + bytecode_handlers = {} + + def interpret(self, program): + oldlevel = self.level + self.level = oldlevel + 1 + handlers = self.dispatch + try: + if self.debug: + for (opcode, args) in program: + s = "%sdo_%s(%s)\n" % (" "*self.level, opcode, + repr(args)) + if len(s) > 80: + s = s[:76] + "...\n" + sys.stderr.write(s) + handlers[opcode](self, args) + else: + for (opcode, args) in program: + handlers[opcode](self, args) + finally: + self.level = oldlevel + + def do_version(self, version): + assert version == TAL_VERSION + bytecode_handlers["version"] = do_version + + def do_mode(self, mode): + assert mode in ("html", "xml") + self.html = (mode == "html") + if self.html: + self.endsep = " />" + else: + self.endsep = "/>" + self.endlen = len(self.endsep) + bytecode_handlers["mode"] = do_mode + + def do_setSourceFile(self, source_file): + self.sourceFile = source_file + self.engine.setSourceFile(source_file) + if self.sourceAnnotations: + self._pending_source_annotation = True + self._stream_write = self._annotated_stream_write + + bytecode_handlers["setSourceFile"] = do_setSourceFile + + def do_setPosition(self, position): + self.position = position + self.engine.setPosition(position) + bytecode_handlers["setPosition"] = do_setPosition + + def do_startEndTag(self, stuff): + self.do_startTag(stuff, self.endsep, self.endlen) + bytecode_handlers["startEndTag"] = do_startEndTag + + def do_startTag(self, (name, attrList), + end=">", endlen=1, _len=len): + # The bytecode generator does not cause calls to this method + # for start tags with no attributes; those are optimized down + # to rawtext events. Hence, there is no special "fast path" + # for that case. + self._currentTag = name + L = ["<", name] + append = L.append + col = self.col + _len(name) + 1 + wrap = self.wrap + align = col + 1 + if align >= wrap/2: + align = 4 # Avoid a narrow column far to the right + attrAction = self.dispatch["<attrAction>"] + try: + for item in attrList: + if _len(item) == 2: + rendered = item[1:] + else: + # item[2] is the 'action' field: + if item[2] in ('metal', 'tal', 'xmlns', 'i18n'): + if not self.showtal: + continue + rendered = self.attrAction(item) + else: + rendered = attrAction(self, item) + if not rendered: + continue + for s in rendered: + slen = _len(s) + if (wrap and + col >= align and + col + 1 + slen > wrap): + append("\n") + append(" "*align) + col = align + slen + else: + append(" ") + col = col + 1 + slen + append(s) + append(end) + col = col + endlen + finally: + self._stream_write(_nulljoin(L)) + self.col = col + bytecode_handlers["startTag"] = do_startTag + + def attrAction(self, item): + name, value, action = item[:3] + if action == 'insert': + return () + macs = self.macroStack + if action == 'metal' and self.metal and macs: + # Drop all METAL attributes at a use-depth beyond the first + # use-macro and its extensions + if len(macs) > 1: + for macro in macs[1:]: + if not macro.extending: + return () + if not macs[-1].entering: + return () + macs[-1].entering = False + # Convert or drop depth-one METAL attributes. + i = name.rfind(":") + 1 + prefix, suffix = name[:i], name[i:] + if suffix == "define-macro": + # Convert define-macro as we enter depth one. + useName = macs[0].macroName + defName = macs[0].definingName + res = [] + if defName: + res.append('%sdefine-macro=%s' % (prefix, quote(defName))) + if useName: + res.append('%suse-macro=%s' % (prefix, quote(useName))) + return res + elif suffix == "define-slot": + name = prefix + "fill-slot" + elif suffix == "fill-slot": + pass + else: + return () + + if value is None: + value = name + else: + value = "%s=%s" % (name, quote(value)) + return [value] + + def attrAction_tal(self, item): + name, value, action = item[:3] + ok = 1 + expr, xlat, msgid = item[3:] + if self.html and name.lower() in BOOLEAN_HTML_ATTRS: + evalue = self.engine.evaluateBoolean(item[3]) + if evalue is self.Default: + if action == 'insert': # Cancelled insert + ok = 0 + elif evalue: + value = None + else: + ok = 0 + elif expr is not None: + evalue = self.engine.evaluateText(item[3]) + if evalue is self.Default: + if action == 'insert': # Cancelled insert + ok = 0 + else: + if evalue is None: + ok = 0 + value = evalue + + if ok: + if xlat: + translated = self.translate(msgid or value, value) + if translated is not None: + value = translated + elif isinstance(value, I18nMessageTypes): + translated = self.translate(value) + if translated is not None: + value = translated + if value is None: + value = name + return ["%s=%s" % (name, quote(value))] + else: + return () + bytecode_handlers["<attrAction>"] = attrAction + + def no_tag(self, start, program): + state = self.saveState() + self.stream = stream = self.StringIO() + self._stream_write = stream.write + self.interpret(start) + self.restoreOutputState(state) + self.interpret(program) + + def do_optTag(self, (name, cexpr, tag_ns, isend, start, program), + omit=0): + if tag_ns and not self.showtal: + return self.no_tag(start, program) + + self.interpret(start) + if not isend: + self.interpret(program) + s = '</%s>' % name + self._stream_write(s) + self.col = self.col + len(s) + + def do_optTag_tal(self, stuff): + cexpr = stuff[1] + if cexpr is not None and (cexpr == '' or + self.engine.evaluateBoolean(cexpr)): + self.no_tag(stuff[-2], stuff[-1]) + else: + self.do_optTag(stuff) + bytecode_handlers["optTag"] = do_optTag + + def do_rawtextBeginScope(self, (s, col, position, closeprev, dict)): + self._stream_write(s) + self.col = col + self.do_setPosition(position) + if closeprev: + engine = self.engine + engine.endScope() + engine.beginScope() + else: + self.engine.beginScope() + self.scopeLevel = self.scopeLevel + 1 + + def do_rawtextBeginScope_tal(self, (s, col, position, closeprev, dict)): + self._stream_write(s) + self.col = col + engine = self.engine + self.position = position + engine.setPosition(position) + if closeprev: + engine.endScope() + engine.beginScope() + else: + engine.beginScope() + self.scopeLevel = self.scopeLevel + 1 + engine.setLocal("attrs", dict) + bytecode_handlers["rawtextBeginScope"] = do_rawtextBeginScope + + def do_beginScope(self, dict): + self.engine.beginScope() + self.scopeLevel = self.scopeLevel + 1 + + def do_beginScope_tal(self, dict): + engine = self.engine + engine.beginScope() + engine.setLocal("attrs", dict) + self.scopeLevel = self.scopeLevel + 1 + bytecode_handlers["beginScope"] = do_beginScope + + def do_endScope(self, notused=None): + self.engine.endScope() + self.scopeLevel = self.scopeLevel - 1 + bytecode_handlers["endScope"] = do_endScope + + def do_setLocal(self, notused): + pass + + def do_setLocal_tal(self, (name, expr)): + self.engine.setLocal(name, self.engine.evaluateValue(expr)) + bytecode_handlers["setLocal"] = do_setLocal + + def do_setGlobal_tal(self, (name, expr)): + self.engine.setGlobal(name, self.engine.evaluateValue(expr)) + bytecode_handlers["setGlobal"] = do_setLocal + + def do_beginI18nContext(self, settings): + get = settings.get + self.i18nContext = TranslationContext(self.i18nContext, + domain=get("domain"), + source=get("source"), + target=get("target")) + bytecode_handlers["beginI18nContext"] = do_beginI18nContext + + def do_endI18nContext(self, notused=None): + self.i18nContext = self.i18nContext.parent + assert self.i18nContext is not None + bytecode_handlers["endI18nContext"] = do_endI18nContext + + def do_insertText(self, stuff): + self.interpret(stuff[1]) + bytecode_handlers["insertText"] = do_insertText + bytecode_handlers["insertI18nText"] = do_insertText + + def _writeText(self, text): + # '&' must be done first! + s = text.replace( + "&", "&").replace("<", "<").replace(">", ">") + self._stream_write(s) + i = s.rfind('\n') + if i < 0: + self.col += len(s) + else: + self.col = len(s) - (i + 1) + + def do_insertText_tal(self, stuff): + text = self.engine.evaluateText(stuff[0]) + if text is None: + return + if text is self.Default: + self.interpret(stuff[1]) + return + if isinstance(text, I18nMessageTypes): + # Translate this now. + text = self.translate(text) + self._writeText(text) + + def do_insertI18nText_tal(self, stuff): + # TODO: Code duplication is BAD, we need to fix it later + text = self.engine.evaluateText(stuff[0]) + if text is not None: + if text is self.Default: + self.interpret(stuff[1]) + else: + if isinstance(text, TypesToTranslate): + text = self.translate(text) + self._writeText(text) + + def do_i18nVariable(self, stuff): + varname, program, expression, structure = stuff + if expression is None: + # The value is implicitly the contents of this tag, so we have to + # evaluate the mini-program to get the value of the variable. + state = self.saveState() + try: + tmpstream = self.StringIO() + self.pushStream(tmpstream) + try: + self.interpret(program) + finally: + self.popStream() + if self.html and self._currentTag == "pre": + value = tmpstream.getvalue() + else: + value = normalize(tmpstream.getvalue()) + finally: + self.restoreState(state) + else: + # TODO: Seems like this branch not used anymore, we + # need to remove it + + # Evaluate the value to be associated with the variable in the + # i18n interpolation dictionary. + if structure: + value = self.engine.evaluateStructure(expression) + else: + value = self.engine.evaluate(expression) + + # evaluate() does not do any I18n, so we do it here. + if isinstance(value, I18nMessageTypes): + # Translate this now. + value = self.translate(value) + + if not structure: + value = cgi.escape(unicode(value)) + + # Either the i18n:name tag is nested inside an i18n:translate in which + # case the last item on the stack has the i18n dictionary and string + # representation, or the i18n:name and i18n:translate attributes are + # in the same tag, in which case the i18nStack will be empty. In that + # case we can just output the ${name} to the stream + i18ndict, srepr = self.i18nStack[-1] + i18ndict[varname] = value + placeholder = '${%s}' % varname + srepr.append(placeholder) + self._stream_write(placeholder) + bytecode_handlers['i18nVariable'] = do_i18nVariable + + def do_insertTranslation(self, stuff): + i18ndict = {} + srepr = [] + obj = None + self.i18nStack.append((i18ndict, srepr)) + msgid = stuff[0] + # We need to evaluate the content of the tag because that will give us + # several useful pieces of information. First, the contents will + # include an implicit message id, if no explicit one was given. + # Second, it will evaluate any i18nVariable definitions in the body of + # the translation (necessary for $varname substitutions). + # + # Use a temporary stream to capture the interpretation of the + # subnodes, which should /not/ go to the output stream. + currentTag = self._currentTag + tmpstream = self.StringIO() + self.pushStream(tmpstream) + try: + self.interpret(stuff[1]) + finally: + self.popStream() + # We only care about the evaluated contents if we need an implicit + # message id. All other useful information will be in the i18ndict on + # the top of the i18nStack. + default = tmpstream.getvalue() + if not msgid: + if self.html and currentTag == "pre": + msgid = default + else: + msgid = normalize(default) + self.i18nStack.pop() + # See if there is was an i18n:data for msgid + if len(stuff) > 2: + obj = self.engine.evaluate(stuff[2]) + xlated_msgid = self.translate(msgid, default, i18ndict, obj) + # TODO: I can't decide whether we want to cgi escape the translated + # string or not. OTOH not doing this could introduce a cross-site + # scripting vector by allowing translators to sneak JavaScript into + # translations. OTOH, for implicit interpolation values, we don't + # want to escape stuff like ${name} <= "<b>Timmy</b>". + assert xlated_msgid is not None + self._stream_write(xlated_msgid) + bytecode_handlers['insertTranslation'] = do_insertTranslation + + def do_insertStructure(self, stuff): + self.interpret(stuff[2]) + bytecode_handlers["insertStructure"] = do_insertStructure + bytecode_handlers["insertI18nStructure"] = do_insertStructure + + def do_insertStructure_tal(self, (expr, repldict, block)): + structure = self.engine.evaluateStructure(expr) + if structure is None: + return + if structure is self.Default: + self.interpret(block) + return + if isinstance(structure, I18nMessageTypes): + text = self.translate(structure) + else: + text = unicode(structure) + if not (repldict or self.strictinsert): + # Take a shortcut, no error checking + self.stream_write(text) + return + if self.html: + self.insertHTMLStructure(text, repldict) + else: + self.insertXMLStructure(text, repldict) + + def do_insertI18nStructure_tal(self, (expr, repldict, block)): + # TODO: Code duplication is BAD, we need to fix it later + structure = self.engine.evaluateStructure(expr) + if structure is not None: + if structure is self.Default: + self.interpret(block) + else: + if not isinstance(structure, TypesToTranslate): + structure = unicode(structure) + text = self.translate(structure) + if not (repldict or self.strictinsert): + # Take a shortcut, no error checking + self.stream_write(text) + elif self.html: + self.insertHTMLStructure(text, repldict) + else: + self.insertXMLStructure(text, repldict) + + def insertHTMLStructure(self, text, repldict): + from zope.tal.htmltalparser import HTMLTALParser + gen = AltTALGenerator(repldict, self.engine, 0) + p = HTMLTALParser(gen) # Raises an exception if text is invalid + p.parseString(text) + program, macros = p.getCode() + self.interpret(program) + + def insertXMLStructure(self, text, repldict): + from zope.tal.talparser import TALParser + gen = AltTALGenerator(repldict, self.engine, 0) + p = TALParser(gen) + gen.enable(0) + p.parseFragment('<!DOCTYPE foo PUBLIC "foo" "bar"><foo>') + gen.enable(1) + p.parseFragment(text) # Raises an exception if text is invalid + gen.enable(0) + p.parseFragment('</foo>', 1) + program, macros = gen.getCode() + self.interpret(program) + + def do_evaluateCode(self, stuff): + lang, program = stuff + # Use a temporary stream to capture the interpretation of the + # subnodes, which should /not/ go to the output stream. + tmpstream = self.StringIO() + self.pushStream(tmpstream) + try: + self.interpret(program) + finally: + self.popStream() + code = tmpstream.getvalue() + output = self.engine.evaluateCode(lang, code) + self._stream_write(output) + bytecode_handlers["evaluateCode"] = do_evaluateCode + + def do_loop(self, (name, expr, block)): + self.interpret(block) + + def do_loop_tal(self, (name, expr, block)): + iterator = self.engine.setRepeat(name, expr) + while iterator.next(): + self.interpret(block) + bytecode_handlers["loop"] = do_loop + + def translate(self, msgid, default=None, i18ndict=None, + obj=None, domain=None): + if default is None: + default = getattr(msgid, 'default', unicode(msgid)) + if i18ndict is None: + i18ndict = {} + if domain is None: + domain = getattr(msgid, 'domain', self.i18nContext.domain) + if obj: + i18ndict.update(obj) + if not self.i18nInterpolate: + return msgid + # TODO: We need to pass in one of context or target_language + return self.engine.translate(msgid, self.i18nContext.domain, + i18ndict, default=default) + + def do_rawtextColumn(self, (s, col)): + self._stream_write(s) + self.col = col + bytecode_handlers["rawtextColumn"] = do_rawtextColumn + + def do_rawtextOffset(self, (s, offset)): + self._stream_write(s) + self.col = self.col + offset + bytecode_handlers["rawtextOffset"] = do_rawtextOffset + + def do_condition(self, (condition, block)): + if not self.tal or self.engine.evaluateBoolean(condition): + self.interpret(block) + bytecode_handlers["condition"] = do_condition + + def do_defineMacro(self, (macroName, macro)): + wasInUse = self.inUseDirective + self.inUseDirective = False + self.interpret(macro) + self.inUseDirective = wasInUse + bytecode_handlers["defineMacro"] = do_defineMacro + + def do_useMacro(self, (macroName, macroExpr, compiledSlots, block), + definingName=None, extending=False): + if not self.metal: + self.interpret(block) + return + macro = self.engine.evaluateMacro(macroExpr) + if macro is self.Default: + macro = block + else: + if not isCurrentVersion(macro): + raise METALError("macro %s has incompatible version %s" % + (`macroName`, `getProgramVersion(macro)`), + self.position) + mode = getProgramMode(macro) + if mode != (self.html and "html" or "xml"): + raise METALError("macro %s has incompatible mode %s" % + (`macroName`, `mode`), self.position) + self.pushMacro(macroName, compiledSlots, definingName, extending) + + # We want 'macroname' name to be always available as a variable + outer = self.engine.getValue('macroname') + self.engine.setLocal('macroname', macroName.rsplit('/', 1)[-1]) + + prev_source = self.sourceFile + wasInUse = self.inUseDirective + self.inUseDirective = True + self.interpret(macro) + self.inUseDirective = wasInUse + + if self.sourceFile != prev_source: + self.engine.setSourceFile(prev_source) + self.sourceFile = prev_source + self.popMacro() + # Push the outer macroname again. + self.engine.setLocal('macroname', outer) + bytecode_handlers["useMacro"] = do_useMacro + + def do_extendMacro(self, (macroName, macroExpr, compiledSlots, block, + definingName)): + # extendMacro results from a combination of define-macro and + # use-macro. definingName has the value of the + # metal:define-macro attribute. + extending = self.metal and self.inUseDirective + self.do_useMacro((macroName, macroExpr, compiledSlots, block), + definingName, extending) + bytecode_handlers["extendMacro"] = do_extendMacro + + def do_fillSlot(self, (slotName, block)): + # This is only executed if the enclosing 'use-macro' evaluates + # to 'default'. + self.interpret(block) + bytecode_handlers["fillSlot"] = do_fillSlot + + def do_defineSlot(self, (slotName, block)): + if not self.metal: + self.interpret(block) + return + macs = self.macroStack + if macs: + len_macs = len(macs) + # Measure the extension depth of this use-macro + depth = 1 + while depth < len_macs: + if macs[-depth].extending: + depth += 1 + else: + break + # Search for a slot filler from the most specific to the + # most general macro. The most general is at the top of + # the stack. + slot = None + i = len_macs - 1 + while i >= (len_macs - depth): + slot = macs[i].slots.get(slotName) + if slot is not None: + break + i -= 1 + if slot is not None: + # Found a slot filler. Temporarily chop the macro + # stack starting at the macro that filled the slot and + # render the slot filler. + chopped = macs[i:] + del macs[i:] + try: + self.interpret(slot) + finally: + # Restore the stack entries. + for mac in chopped: + mac.entering = False # Not entering + macs.extend(chopped) + return + # Falling out of the 'if' allows the macro to be interpreted. + self.interpret(block) + bytecode_handlers["defineSlot"] = do_defineSlot + + def do_onError(self, (block, handler)): + self.interpret(block) + + def do_onError_tal(self, (block, handler)): + state = self.saveState() + self.stream = stream = self.StringIO() + self._stream_write = stream.write + try: + self.interpret(block) + # TODO: this should not catch ZODB.POSException.ConflictError. + # The ITALExpressionEngine interface should provide a way of + # getting the set of exception types that should not be + # handled. + except: + exc = sys.exc_info()[1] + self.restoreState(state) + engine = self.engine + engine.beginScope() + error = engine.createErrorInfo(exc, self.position) + engine.setLocal('error', error) + try: + self.interpret(handler) + finally: + engine.endScope() + else: + self.restoreOutputState(state) + self.stream_write(stream.getvalue()) + bytecode_handlers["onError"] = do_onError + + bytecode_handlers_tal = bytecode_handlers.copy() + bytecode_handlers_tal["rawtextBeginScope"] = do_rawtextBeginScope_tal + bytecode_handlers_tal["beginScope"] = do_beginScope_tal + bytecode_handlers_tal["setLocal"] = do_setLocal_tal + bytecode_handlers_tal["setGlobal"] = do_setGlobal_tal + bytecode_handlers_tal["insertStructure"] = do_insertStructure_tal + bytecode_handlers_tal["insertI18nStructure"] = do_insertI18nStructure_tal + bytecode_handlers_tal["insertText"] = do_insertText_tal + bytecode_handlers_tal["insertI18nText"] = do_insertI18nText_tal + bytecode_handlers_tal["loop"] = do_loop_tal + bytecode_handlers_tal["onError"] = do_onError_tal + bytecode_handlers_tal["<attrAction>"] = attrAction_tal + bytecode_handlers_tal["optTag"] = do_optTag_tal + + +class FasterStringIO(StringIO): + """Append-only version of StringIO. + + This let's us have a much faster write() method. + """ + def close(self): + if not self.closed: + self.write = _write_ValueError + StringIO.close(self) + + def seek(self, pos, mode=0): + raise RuntimeError("FasterStringIO.seek() not allowed") + + def write(self, s): + #assert self.pos == self.len + self.buflist.append(s) + self.len = self.pos = self.pos + len(s) + + +def _write_ValueError(s): + raise ValueError("I/O operation on closed file") diff --git a/src/zope/tal/talparser.py b/src/zope/tal/talparser.py new file mode 100644 index 0000000..9335ed0 --- /dev/null +++ b/src/zope/tal/talparser.py @@ -0,0 +1,142 @@ +############################################################################## +# +# Copyright (c) 2001, 2002 Zope Corporation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +"""Parse XML and compile to TALInterpreter intermediate code. + +$Id$ +""" +from zope.tal.taldefs import XML_NS, ZOPE_I18N_NS, ZOPE_METAL_NS, ZOPE_TAL_NS +from zope.tal.talgenerator import TALGenerator +from zope.tal.xmlparser import XMLParser + + +class TALParser(XMLParser): + + ordered_attributes = 1 + + def __init__(self, gen=None, encoding=None): # Override + XMLParser.__init__(self, encoding) + if gen is None: + gen = TALGenerator() + self.gen = gen + self.nsStack = [] + self.nsDict = {XML_NS: 'xml'} + self.nsNew = [] + + def getCode(self): + return self.gen.getCode() + + def StartNamespaceDeclHandler(self, prefix, uri): + self.nsStack.append(self.nsDict.copy()) + self.nsDict[uri] = prefix + self.nsNew.append((prefix, uri)) + + def EndNamespaceDeclHandler(self, prefix): + self.nsDict = self.nsStack.pop() + + def StartElementHandler(self, name, attrs): + if self.ordered_attributes: + # attrs is a list of alternating names and values + attrlist = [] + for i in range(0, len(attrs), 2): + key = attrs[i] + value = attrs[i+1] + attrlist.append((key, value)) + else: + # attrs is a dict of {name: value} + attrlist = attrs.items() + attrlist.sort() # For definiteness + name, attrlist, taldict, metaldict, i18ndict \ + = self.process_ns(name, attrlist) + attrlist = self.xmlnsattrs() + attrlist + self.gen.emitStartElement(name, attrlist, taldict, metaldict, i18ndict, + self.getpos()) + + def process_ns(self, name, attrlist): + taldict = {} + metaldict = {} + i18ndict = {} + fixedattrlist = [] + name, namebase, namens = self.fixname(name) + for key, value in attrlist: + key, keybase, keyns = self.fixname(key) + ns = keyns or namens # default to tag namespace + item = key, value + if ns == 'metal': + metaldict[keybase] = value + item = item + ("metal",) + elif ns == 'tal': + taldict[keybase] = value + item = item + ("tal",) + elif ns == 'i18n': + i18ndict[keybase] = value + item = item + ('i18n',) + fixedattrlist.append(item) + if namens in ('metal', 'tal', 'i18n'): + taldict['tal tag'] = namens + return name, fixedattrlist, taldict, metaldict, i18ndict + + def xmlnsattrs(self): + newlist = [] + for prefix, uri in self.nsNew: + if prefix: + key = "xmlns:" + prefix + else: + key = "xmlns" + if uri in (ZOPE_METAL_NS, ZOPE_TAL_NS, ZOPE_I18N_NS): + item = (key, uri, "xmlns") + else: + item = (key, uri) + newlist.append(item) + self.nsNew = [] + return newlist + + def fixname(self, name): + if ' ' in name: + uri, name = name.split(' ') + prefix = self.nsDict[uri] + prefixed = name + if prefix: + prefixed = "%s:%s" % (prefix, name) + ns = 'x' + if uri == ZOPE_TAL_NS: + ns = 'tal' + elif uri == ZOPE_METAL_NS: + ns = 'metal' + elif uri == ZOPE_I18N_NS: + ns = 'i18n' + return (prefixed, name, ns) + return (name, name, None) + + def EndElementHandler(self, name): + name = self.fixname(name)[0] + self.gen.emitEndElement(name, position=self.getpos()) + + def DefaultHandler(self, text): + self.gen.emitRawText(text) + +def test(): + import sys + p = TALParser() + file = "tests/input/test01.xml" + if sys.argv[1:]: + file = sys.argv[1] + p.parseFile(file) + program, macros = p.getCode() + from zope.tal.talinterpreter import TALInterpreter + from zope.tal.dummyengine import DummyEngine + engine = DummyEngine(macros) + TALInterpreter(program, macros, engine, sys.stdout, wrap=0)() + +if __name__ == "__main__": + test() diff --git a/src/zope/tal/tests/__init__.py b/src/zope/tal/tests/__init__.py new file mode 100644 index 0000000..b711d36 --- /dev/null +++ b/src/zope/tal/tests/__init__.py @@ -0,0 +1,2 @@ +# +# This file is necessary to make this directory a package. diff --git a/src/zope/tal/tests/input/__init__.py b/src/zope/tal/tests/input/__init__.py new file mode 100644 index 0000000..b711d36 --- /dev/null +++ b/src/zope/tal/tests/input/__init__.py @@ -0,0 +1,2 @@ +# +# This file is necessary to make this directory a package. diff --git a/src/zope/tal/tests/input/acme_template.pt b/src/zope/tal/tests/input/acme_template.pt new file mode 100644 index 0000000..0af01ba --- /dev/null +++ b/src/zope/tal/tests/input/acme_template.pt @@ -0,0 +1,15 @@ +<!-- This is ACME's generic look and feel, which is based on +PNOME's look and feel. --> +<html metal:extend-macro="pnome_macros_page" metal:define-macro="page"> +<head> +<title metal:fill-slot="title">ACME Look and Feel</title> +</head> +<body> +<div metal:fill-slot="page-footer"> +Copyright 2004 Acme Inc. +<div metal:define-slot="disclaimer"> +Standard disclaimers apply. +</div> +</div> +</body> +</html> diff --git a/src/zope/tal/tests/input/document_list.pt b/src/zope/tal/tests/input/document_list.pt new file mode 100644 index 0000000..8226be1 --- /dev/null +++ b/src/zope/tal/tests/input/document_list.pt @@ -0,0 +1,21 @@ +<!-- ACME's document_list uses the ACME look and feel --> +<html metal:use-macro="acme_macros_page"> +<head> +<title metal:fill-slot="title">Acme Document List</title> +<style metal:fill-slot="local-styles" type="text/css"> + body { background-color: white; } +</style> +</head> +<body> +<div metal:fill-slot="content"> +<h1>Documents</h1> +<ul> +<li>Rocket Science for Dummies</li> +<li>Birds for the Gourmet Chef</li> +</ul> +</div> +<div metal:fill-slot="disclaimer"> +This document list is classified. +</div> +</body> +</html> diff --git a/src/zope/tal/tests/input/pnome_template.pt b/src/zope/tal/tests/input/pnome_template.pt new file mode 100644 index 0000000..f4d1c66 --- /dev/null +++ b/src/zope/tal/tests/input/pnome_template.pt @@ -0,0 +1,23 @@ +<!-- fakeplone is a fictional user interface created by a large, +well-focused team of graphics designers --> +<html metal:define-macro="page"> +<head> +<title metal:define-slot="title">Title here</title> +<metal:block define-slot="local-styles"> +</metal:block> +</head> +<body> +<div> + <div metal:define-slot="annoying-quote"> + "The early bird gets the worm, but the second mouse gets the cheese." + </div> + <a href="#">Preferences...</a> +</div> +<div metal:define-slot="content"> + Content here +</div> +<div metal:define-slot="page-footer"> + page footer +</div> +</body> +</html> diff --git a/src/zope/tal/tests/input/test01.html b/src/zope/tal/tests/input/test01.html new file mode 100644 index 0000000..e2ae0c4 --- /dev/null +++ b/src/zope/tal/tests/input/test01.html @@ -0,0 +1,56 @@ +<!DOCTYPE html + PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "DTD/xhtml1-transitional.dtd"> +<html> + + <head>dadada</head> + + <body xmlns:z="http://xml.zope.org/namespaces/tal" z:define="foo python:1"> +<h1 z:condition="python:0">This title is not displayed</h1> + <h1 z:condition="python:1" z:content="str:This +Is +The +Replaced +Title">Title</h1> + + <!-- test entity references --> + &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:<h3>Header Level 3</h3>" /> + <span z:define="global x python:'&' + 'nbsp;;' + x" /> + + <span z:replace="structure x" /> + <span z:content="structure x" /> + + </body> + +</html> diff --git a/src/zope/tal/tests/input/test01.xml b/src/zope/tal/tests/input/test01.xml new file mode 100644 index 0000000..82038e9 --- /dev/null +++ b/src/zope/tal/tests/input/test01.xml @@ -0,0 +1,57 @@ +<?xml version="1.0" ?> +<!DOCTYPE html + PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "DTD/xhtml1-transitional.dtd"> +<html> + + <head>dadada</head> + + <body xmlns:z="http://xml.zope.org/namespaces/tal" z:define="foo python:1"> +<h1 z:condition="python:0">This title is not displayed</h1> + <h1 z:condition="python:1" z:content="str:This +Is +The +Replaced +Title">Title</h1> + + <!-- test entity references --> + &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:<h3>Header Level 3</h3>" /> + <span z:define="global x python:'&' + 'nbsp;;' + x" /> + + <span z:replace="structure x" /> + <span z:content="structure x" /> + + </body> + +</html> diff --git a/src/zope/tal/tests/input/test02.html b/src/zope/tal/tests/input/test02.html new file mode 100644 index 0000000..df2fb18 --- /dev/null +++ b/src/zope/tal/tests/input/test02.html @@ -0,0 +1,118 @@ +<biztalk_1 xmlns="urn:schemas-biztalk-org:biztalk:biztalk_1"> + +<foo:header xmlns:foo="whomping-willow" plain="guido" quote='"' apostrophe="'" both=""'" lt="<" gt=">" amp="&" foo=""> + <manifest> + <document> + <name>sample1</name> + <description>a simple invoice</description> + </document> + </manifest> +</foo:header> + +<body> + +<!-- sample1.xml is an example of a simple invoice for a small restaurant supplies order --> + +<Invoice xmlns="urn:http://schemas.biztalk.org/united_rest_com/yw7sg15x.xml"> + <Header> + <InvoiceNumber>01786</InvoiceNumber> + <InvoiceDate>2000-03-17</InvoiceDate> <!-- March 17th, 2000 --> + <OrderNo>55377</OrderNo> + <OrderDate>2000-03-15</OrderDate> <!-- March 15th, 2000 --> + <CustomerPO>GJ03405</CustomerPO> + <ShipMethod>DAVE 1</ShipMethod> + <ShipDate>2000-03-17</ShipDate> <!-- March 17th, 2000 --> + <CustomerID>K5211(34)</CustomerID> + <SalesPersonCode>23</SalesPersonCode> + <TaxID>23</TaxID> + </Header> + <InvoiceTo> + <Name>SHIPWRIGHT RESTAURANTS LIMITED</Name> + <AddressLine>125 NORTH SERVICE ROAD W</AddressLine> + <AddressLine>WESTLAKE ACCESS</AddressLine> + <City>NORTH BAY</City> + <PostCode>L8B1O5</PostCode> + <State>ONTARIO</State> + <Country>CANADA</Country> + </InvoiceTo> + <ShipTo> + <Name/> + <AddressLine>ATTN: PAULINE DEGRASSI</AddressLine> + <City/> + <PostCode/> + <State/> + <Country/> + </ShipTo> + <DetailLines> + <DetailLine> + <QuantityShipped>1</QuantityShipped> + <UnitOfMeasure>CS</UnitOfMeasure> + <PartNumber>DM 5309</PartNumber> + <PartDescription>#1013 12 OZ.MUNICH STEIN</PartDescription> + <UnitPrice>37.72</UnitPrice> + <LineTotal>37.72</LineTotal> + </DetailLine> + <DetailLine> + <QuantityShipped>6</QuantityShipped> + <UnitOfMeasure>DZ</UnitOfMeasure> + <PartNumber>ON 6420</PartNumber> + <PartDescription>PROVINCIAL DINNER FORK</PartDescription> + <UnitPrice>17.98</UnitPrice> + <LineTotal>107.88</LineTotal> + </DetailLine> + <DetailLine> + <QuantityShipped>72</QuantityShipped> + <UnitOfMeasure>EA</UnitOfMeasure> + <PartNumber>JR20643</PartNumber> + <PartDescription>PLASTIC HANDLED STEAK KNIFE</PartDescription> + <UnitPrice>.81</UnitPrice> + <LineTotal>58.32</LineTotal> + </DetailLine> + <DetailLine> + <QuantityShipped>6</QuantityShipped> + <UnitOfMeasure>DZ</UnitOfMeasure> + <PartNumber>ON 6410</PartNumber> + <PartDescription>PROVINCIAL TEASPOONS</PartDescription> + <UnitPrice>12.16</UnitPrice> + <LineTotal>72.96</LineTotal> + </DetailLine> + <DetailLine> + <QuantityShipped>0</QuantityShipped> + <UnitOfMeasure>DZ</UnitOfMeasure> + <PartNumber>ON 6411</PartNumber> + <PartDescription>PROVINCIAL RD BOWL SPOON</PartDescription> + <QuantityBackOrdered>6</QuantityBackOrdered> + <UnitPrice>17.98</UnitPrice> + <LineTotal>0.00</LineTotal> + </DetailLine> + <DetailLine> + <QuantityShipped>1</QuantityShipped> + <UnitOfMeasure>EA</UnitOfMeasure> + <PartNumber>DO 3218</PartNumber> + <PartDescription>34 OZ DUAL DIAL SCALE AM3218</PartDescription> + <UnitPrice>70.00</UnitPrice> + <DiscountPercentage>5.0</DiscountPercentage> + <LineTotal>66.50</LineTotal> + </DetailLine> + <DetailLine> + <QuantityShipped>1</QuantityShipped> + <UnitOfMeasure>CS</UnitOfMeasure> + <PartNumber>DM 195</PartNumber> + <PartDescription>20 OZ.BEER PUB GLASS</PartDescription> + <UnitPrice>55.90</UnitPrice> + <LineTotal>55.90</LineTotal> + </DetailLine> + </DetailLines> + <Totals> + <SubTotal>399.28</SubTotal> + <DiscountTotal>3.50</DiscountTotal> + <FreightTotal>23.75</FreightTotal> + <GSTTotal>29.61</GSTTotal> + <ProvTaxTotal>33.84</ProvTaxTotal> + <OtherTotal>33.84</OtherTotal> + <InvoiceTotal>486.48</InvoiceTotal> + </Totals> +</Invoice> + +</body> +</biztalk_1> diff --git a/src/zope/tal/tests/input/test02.xml b/src/zope/tal/tests/input/test02.xml new file mode 100644 index 0000000..69567ea --- /dev/null +++ b/src/zope/tal/tests/input/test02.xml @@ -0,0 +1,119 @@ +<?xml version="1.0" ?> +<biztalk_1 xmlns="urn:schemas-biztalk-org:biztalk:biztalk_1"> + +<foo:header xmlns:foo="whomping-willow" plain="guido" quote='"' apostrophe="'" both=""'" lt="<" gt=">" amp="&" foo=""> + <manifest> + <document> + <name>sample1</name> + <description>a simple invoice</description> + </document> + </manifest> +</foo:header> + +<body> + +<!-- sample1.xml is an example of a simple invoice for a small restaurant supplies order --> + +<Invoice xmlns="urn:http://schemas.biztalk.org/united_rest_com/yw7sg15x.xml"> + <Header> + <InvoiceNumber>01786</InvoiceNumber> + <InvoiceDate>2000-03-17</InvoiceDate> <!-- March 17th, 2000 --> + <OrderNo>55377</OrderNo> + <OrderDate>2000-03-15</OrderDate> <!-- March 15th, 2000 --> + <CustomerPO>GJ03405</CustomerPO> + <ShipMethod>DAVE 1</ShipMethod> + <ShipDate>2000-03-17</ShipDate> <!-- March 17th, 2000 --> + <CustomerID>K5211(34)</CustomerID> + <SalesPersonCode>23</SalesPersonCode> + <TaxID>23</TaxID> + </Header> + <InvoiceTo> + <Name>SHIPWRIGHT RESTAURANTS LIMITED</Name> + <AddressLine>125 NORTH SERVICE ROAD W</AddressLine> + <AddressLine>WESTLAKE ACCESS</AddressLine> + <City>NORTH BAY</City> + <PostCode>L8B1O5</PostCode> + <State>ONTARIO</State> + <Country>CANADA</Country> + </InvoiceTo> + <ShipTo> + <Name/> + <AddressLine>ATTN: PAULINE DEGRASSI</AddressLine> + <City/> + <PostCode/> + <State/> + <Country/> + </ShipTo> + <DetailLines> + <DetailLine> + <QuantityShipped>1</QuantityShipped> + <UnitOfMeasure>CS</UnitOfMeasure> + <PartNumber>DM 5309</PartNumber> + <PartDescription>#1013 12 OZ.MUNICH STEIN</PartDescription> + <UnitPrice>37.72</UnitPrice> + <LineTotal>37.72</LineTotal> + </DetailLine> + <DetailLine> + <QuantityShipped>6</QuantityShipped> + <UnitOfMeasure>DZ</UnitOfMeasure> + <PartNumber>ON 6420</PartNumber> + <PartDescription>PROVINCIAL DINNER FORK</PartDescription> + <UnitPrice>17.98</UnitPrice> + <LineTotal>107.88</LineTotal> + </DetailLine> + <DetailLine> + <QuantityShipped>72</QuantityShipped> + <UnitOfMeasure>EA</UnitOfMeasure> + <PartNumber>JR20643</PartNumber> + <PartDescription>PLASTIC HANDLED STEAK KNIFE</PartDescription> + <UnitPrice>.81</UnitPrice> + <LineTotal>58.32</LineTotal> + </DetailLine> + <DetailLine> + <QuantityShipped>6</QuantityShipped> + <UnitOfMeasure>DZ</UnitOfMeasure> + <PartNumber>ON 6410</PartNumber> + <PartDescription>PROVINCIAL TEASPOONS</PartDescription> + <UnitPrice>12.16</UnitPrice> + <LineTotal>72.96</LineTotal> + </DetailLine> + <DetailLine> + <QuantityShipped>0</QuantityShipped> + <UnitOfMeasure>DZ</UnitOfMeasure> + <PartNumber>ON 6411</PartNumber> + <PartDescription>PROVINCIAL RD BOWL SPOON</PartDescription> + <QuantityBackOrdered>6</QuantityBackOrdered> + <UnitPrice>17.98</UnitPrice> + <LineTotal>0.00</LineTotal> + </DetailLine> + <DetailLine> + <QuantityShipped>1</QuantityShipped> + <UnitOfMeasure>EA</UnitOfMeasure> + <PartNumber>DO 3218</PartNumber> + <PartDescription>34 OZ DUAL DIAL SCALE AM3218</PartDescription> + <UnitPrice>70.00</UnitPrice> + <DiscountPercentage>5.0</DiscountPercentage> + <LineTotal>66.50</LineTotal> + </DetailLine> + <DetailLine> + <QuantityShipped>1</QuantityShipped> + <UnitOfMeasure>CS</UnitOfMeasure> + <PartNumber>DM 195</PartNumber> + <PartDescription>20 OZ.BEER PUB GLASS</PartDescription> + <UnitPrice>55.90</UnitPrice> + <LineTotal>55.90</LineTotal> + </DetailLine> + </DetailLines> + <Totals> + <SubTotal>399.28</SubTotal> + <DiscountTotal>3.50</DiscountTotal> + <FreightTotal>23.75</FreightTotal> + <GSTTotal>29.61</GSTTotal> + <ProvTaxTotal>33.84</ProvTaxTotal> + <OtherTotal>33.84</OtherTotal> + <InvoiceTotal>486.48</InvoiceTotal> + </Totals> +</Invoice> + +</body> +</biztalk_1> diff --git a/src/zope/tal/tests/input/test03.html b/src/zope/tal/tests/input/test03.html new file mode 100644 index 0000000..a0230e1 --- /dev/null +++ b/src/zope/tal/tests/input/test03.html @@ -0,0 +1,9 @@ +<p xmlns:z="http://xml.zope.org/namespaces/tal"> + <span z:define="local x str:hello brave new world"> + <span z:content="text local:x">outer variable x, first appearance</span> + <span z:define="local x str:goodbye cruel world"> + <span z:content="text local:x">inner variable x</span> + </span> + <span z:content="text local:x">outer variable x, second appearance</span> + </span> +</p> diff --git a/src/zope/tal/tests/input/test03.xml b/src/zope/tal/tests/input/test03.xml new file mode 100644 index 0000000..830149d --- /dev/null +++ b/src/zope/tal/tests/input/test03.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" ?> +<p xmlns:z="http://xml.zope.org/namespaces/tal"> + <span z:define="local x str:hello brave new world"> + <span z:content="text local:x">outer variable x, first appearance</span> + <span z:define="local x str:goodbye cruel world"> + <span z:content="text local:x">inner variable x</span> + </span> + <span z:content="text local:x">outer variable x, second appearance</span> + </span> +</p> diff --git a/src/zope/tal/tests/input/test04.html b/src/zope/tal/tests/input/test04.html new file mode 100644 index 0000000..bdaad39 --- /dev/null +++ b/src/zope/tal/tests/input/test04.html @@ -0,0 +1,26 @@ +<html> + + <body xmlns:m="http://xml.zope.org/namespaces/metal" xmlns:z="http://xml.zope.org/namespaces/tal" m:define-macro="body" z:define="global count python:0"> + + <ul m:define-macro="whoops"> + <li z:repeat="item python:range(count)"> + <span z:replace="item">1</span> + <span z:replace="global:message"/> + </li> + </ul> + + <span z:define="global count python:2; global message str:hello world"/> + + <p m:use-macro="whoops">use-macro + <span m:fill-slot="whoops">fill-slot</span> + </p> + + <span z:define="global message str:goodbye cruel world"/> + + <p m:use-macro="whoops">use-macro</p> + + <p m:define-slot="whoops">define-slot</p> + + </body> + +</html> diff --git a/src/zope/tal/tests/input/test04.xml b/src/zope/tal/tests/input/test04.xml new file mode 100644 index 0000000..bde6cef --- /dev/null +++ b/src/zope/tal/tests/input/test04.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" ?> +<html> + + <body xmlns:m="http://xml.zope.org/namespaces/metal" xmlns:z="http://xml.zope.org/namespaces/tal" m:define-macro="body" z:define="global count python:0"> + + <ul m:define-macro="whoops"> + <li z:repeat="item python:range(count)"> + <span z:replace="item">1</span> + <span z:replace="global:message"/> + </li> + </ul> + + <span z:define="global count python:2; global message str:hello world"/> + + <p m:use-macro="whoops">use-macro + <span m:fill-slot="whoops">fill-slot</span> + </p> + + <span z:define="global message str:goodbye cruel world"/> + + <p m:use-macro="whoops">use-macro</p> + + <p m:define-slot="whoops">define-slot</p> + + </body> + +</html> diff --git a/src/zope/tal/tests/input/test05.html b/src/zope/tal/tests/input/test05.html new file mode 100644 index 0000000..21f6b68 --- /dev/null +++ b/src/zope/tal/tests/input/test05.html @@ -0,0 +1,9 @@ +<html> + + <body xmlns:m="http://xml.zope.org/namespaces/metal" m:define-macro="body"> + + <h1>This is the body of test5</h1> + + </body> + +</html> diff --git a/src/zope/tal/tests/input/test05.xml b/src/zope/tal/tests/input/test05.xml new file mode 100644 index 0000000..fcaaf6b --- /dev/null +++ b/src/zope/tal/tests/input/test05.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" ?> +<html> + + <body xmlns:m="http://xml.zope.org/namespaces/metal" m:define-macro="body"> + + <h1>This is the body of test5</h1> + + </body> + +</html> diff --git a/src/zope/tal/tests/input/test06.html b/src/zope/tal/tests/input/test06.html new file mode 100644 index 0000000..ac1264d --- /dev/null +++ b/src/zope/tal/tests/input/test06.html @@ -0,0 +1,6 @@ +<html> + <body xmlns:m="http://xml.zope.org/namespaces/metal" + m:use-macro="tests/input/test05.html/body"> + dummy body in test6 + </body> +</html> diff --git a/src/zope/tal/tests/input/test06.xml b/src/zope/tal/tests/input/test06.xml new file mode 100644 index 0000000..b32bd0f --- /dev/null +++ b/src/zope/tal/tests/input/test06.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" ?> +<html> + <body xmlns:m="http://xml.zope.org/namespaces/metal" + m:use-macro="tests/input/test05.xml/body"> + dummy body in test6 + </body> +</html> diff --git a/src/zope/tal/tests/input/test07.html b/src/zope/tal/tests/input/test07.html new file mode 100644 index 0000000..bff98f0 --- /dev/null +++ b/src/zope/tal/tests/input/test07.html @@ -0,0 +1,11 @@ +<table xmlns:m="http://xml.zope.org/namespaces/metal" m:define-macro="myTable"> +<!-- macro definition with slots --> + <tr> + <td>Top Left</td> + <td>Top Right</td> + </tr> + <tr> + <td>Bottom left</td> + <td><span m:define-slot="bottomRight">Bottom Right</span></td> + </tr> +</table> diff --git a/src/zope/tal/tests/input/test07.xml b/src/zope/tal/tests/input/test07.xml new file mode 100644 index 0000000..e5c520a --- /dev/null +++ b/src/zope/tal/tests/input/test07.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" ?> +<table xmlns:m="http://xml.zope.org/namespaces/metal" m:define-macro="myTable"> +<!-- macro definition with slots --> + <tr> + <td>Top Left</td> + <td>Top Right</td> + </tr> + <tr> + <td>Bottom left</td> + <td><span m:define-slot="bottomRight">Bottom Right</span></td> + </tr> +</table> diff --git a/src/zope/tal/tests/input/test08.html b/src/zope/tal/tests/input/test08.html new file mode 100644 index 0000000..1e4915b --- /dev/null +++ b/src/zope/tal/tests/input/test08.html @@ -0,0 +1,44 @@ +<table xmlns:m="http://xml.zope.org/namespaces/metal" m:use-macro="tests/input/test07.html/myTable"> +<!-- macro use with slots --> + <tr> + <td> + <span m:fill-slot="bottomRight"> + <h1>Some headline</h1> + <p>This is the real contents of the bottom right slot.</p> + <p>It is supposed to contain a lot of text. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb.</p> + <p>It is supposed to contain a lot of text. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb.</p> + <p>It is supposed to contain a lot of text. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb.</p> + </span> + </td> + </tr> +</table> diff --git a/src/zope/tal/tests/input/test08.xml b/src/zope/tal/tests/input/test08.xml new file mode 100644 index 0000000..b0360fa --- /dev/null +++ b/src/zope/tal/tests/input/test08.xml @@ -0,0 +1,45 @@ +<?xml version="1.0" ?> +<table xmlns:m="http://xml.zope.org/namespaces/metal" m:use-macro="tests/input/test07.xml/myTable"> +<!-- macro use with slots --> + <tr> + <td> + <span m:fill-slot="bottomRight"> + <h1>Some headline</h1> + <p>This is the real contents of the bottom right slot.</p> + <p>It is supposed to contain a lot of text. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb.</p> + <p>It is supposed to contain a lot of text. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb.</p> + <p>It is supposed to contain a lot of text. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb.</p> + </span> + </td> + </tr> +</table> diff --git a/src/zope/tal/tests/input/test09.html b/src/zope/tal/tests/input/test09.html new file mode 100644 index 0000000..35f481a --- /dev/null +++ b/src/zope/tal/tests/input/test09.html @@ -0,0 +1,30 @@ +<html> +<body> +<p> + Just a bunch of text. +<p>more text... +<ul> + <li>first item + <li>second item + + <ol> + <li>second list, first item + <li>second list, second item + <dl compact> + <dt>term 1 + <dt>term 2 + <dd>definition + </dl> + </ol> + + <li>Now let's have a paragraph... + <p>My Paragraph + </li> + + <li>And a table in a list item: + <table> + </table> +</ul> + +</body> +</html> diff --git a/src/zope/tal/tests/input/test09.xml b/src/zope/tal/tests/input/test09.xml new file mode 100644 index 0000000..c3d10d7 --- /dev/null +++ b/src/zope/tal/tests/input/test09.xml @@ -0,0 +1,30 @@ +<html> +<body> +<p> + Just a bunch of text.</p> +<p>more text...</p> +<ul> + <li>first item</li> + <li>second item + + <ol> + <li>second list, first item</li> + <li>second list, second item + <dl compact=""> + <dt>term 1</dt> + <dt>term 2</dt> + <dd>definition</dd> + </dl></li> + </ol></li> + + <li>Now let's have a paragraph... + <p>My Paragraph</p> + </li> + + <li>And a table in a list item: + <table> + </table></li> +</ul> + +</body> +</html> diff --git a/src/zope/tal/tests/input/test10.html b/src/zope/tal/tests/input/test10.html new file mode 100644 index 0000000..6ecca4c --- /dev/null +++ b/src/zope/tal/tests/input/test10.html @@ -0,0 +1,48 @@ +<html><body> +<table xmlns:m="http://xml.zope.org/namespaces/metal" m:use-macro="tests/input/test07.html/myTable"> +<!-- macro use with slots --> + <tr> + <td> + <span m:fill-slot="bottomRight"> + <h1>Some headline</h1> + <p>This is the real contents of the bottom right slot.</p> + <hr> + <p>It is supposed to contain a lot of text. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb.</p> + <p>It is supposed to contain a lot of text. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb.</p> + <p>It is supposed to contain a lot of text. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb.</p> + <br><br> + </span> + </td> + </tr> +</table> +</body></html> diff --git a/src/zope/tal/tests/input/test11.html b/src/zope/tal/tests/input/test11.html new file mode 100644 index 0000000..89f7563 --- /dev/null +++ b/src/zope/tal/tests/input/test11.html @@ -0,0 +1,19 @@ +<html xmlns:tal="http://xml.zope.org/namespaces/tal"> + <p tal:replace="structure string:<a>bar</a>" + tal:attributes="href string:http://www.python.org">dummy text</p> + <p tal:define="x python:1" tal:on-error="string:bad boy!"> + <span tal:define="x python:2"> + <span tal:define="x python:3"> + <span tal:content="python:1/0"/> + </span> + </span> + </p> + <p tal:on-error="string:x undefined"> + <span tal:content="x"/> + </p> + <tal:block on-error="string:x undefined" replace="x" /> + <tal:block on-error="string:x undefined"> + <p tal:content="x">p</p> + </tal:block> + <div tal:replace="structure string:<hr />">rule</div> +</html> diff --git a/src/zope/tal/tests/input/test11.xml b/src/zope/tal/tests/input/test11.xml new file mode 100644 index 0000000..435f95c --- /dev/null +++ b/src/zope/tal/tests/input/test11.xml @@ -0,0 +1,14 @@ +<html xmlns:tal="http://xml.zope.org/namespaces/tal"> + <p tal:replace="structure string:<a>bar</a>" + tal:attributes="href string:http://www.python.org">dummy text</p> + <p tal:define="x python:1" tal:on-error="string:bad boy!"> + <span tal:define="x python:2"> + <span tal:define="x python:3"> + <span tal:content="python:1/0"/> + </span> + </span> + </p> + <p tal:on-error="string:x undefined"> + <span tal:content="x"/> + </p> +</html> diff --git a/src/zope/tal/tests/input/test12.html b/src/zope/tal/tests/input/test12.html new file mode 100644 index 0000000..94d9a66 --- /dev/null +++ b/src/zope/tal/tests/input/test12.html @@ -0,0 +1,24 @@ +<span tal:define="global true python:1; global false python:0" /> + +<img ismap> +<img ismap=ismap> +<img ismap="ismap"> +<img ismap="foo"> + +<img ismap tal:attributes="ismap true"> +<img ismap tal:attributes="ismap false"> +<img ismap tal:attributes="ismap nothing"> + +<img ismap="foo" tal:attributes="ismap true"> +<img ismap="foo" tal:attributes="ismap false"> +<img ismap="foo" tal:attributes="ismap nothing"> + +<img tal:attributes="ismap true"> +<img tal:attributes="ismap false"> +<img tal:attributes="ismap nothing"> + +<span tal:define="global x string:x.gif" /> + +<img src="foo"> +<img src="foo" tal:attributes="src x"> +<img src="foo" tal:attributes="src nothing"> diff --git a/src/zope/tal/tests/input/test13.html b/src/zope/tal/tests/input/test13.html new file mode 100644 index 0000000..d68e0ce --- /dev/null +++ b/src/zope/tal/tests/input/test13.html @@ -0,0 +1,7 @@ +Here's a stray greater than: > + +<script> + <!-- no comment --> + <notag> + &noentity; +</script> diff --git a/src/zope/tal/tests/input/test14.html b/src/zope/tal/tests/input/test14.html new file mode 100644 index 0000000..0aaa751 --- /dev/null +++ b/src/zope/tal/tests/input/test14.html @@ -0,0 +1,10 @@ +<table> + <tr> + <td tal:repeat="x python:['car', 'bike', 'broomstick']" tal:content="x"> + </td> + </tr> +</table> + +<p> + <span tal:repeat="x python:['Harry', 'Ron', 'Hermione']" tal:replace="x" /> +</p> diff --git a/src/zope/tal/tests/input/test14.xml b/src/zope/tal/tests/input/test14.xml new file mode 100644 index 0000000..c596135 --- /dev/null +++ b/src/zope/tal/tests/input/test14.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" ?> +<html xmlns:tal="http://xml.zope.org/namespaces/tal"> + +<table> + <tr> + <td tal:repeat="x python:['car', 'bike', 'broomstick']" tal:content="x"> + </td> + </tr> +</table> + +<p> + <span tal:repeat="x python:['Harry', 'Ron', 'Hermione']" tal:replace="x" /> +</p> + +</html> diff --git a/src/zope/tal/tests/input/test15.html b/src/zope/tal/tests/input/test15.html new file mode 100644 index 0000000..0cd456e --- /dev/null +++ b/src/zope/tal/tests/input/test15.html @@ -0,0 +1,26 @@ +<span metal:define-macro="INNER"> + <span metal:define-slot="INNERSLOT">INNERSLOT</span> +</span> + +<xxx metal:use-macro="INNER"> + <xxx metal:fill-slot="INNERSLOT">inner-argument</xxx> +</xxx> + +<div metal:define-macro="OUTER"> +<div metal:use-macro="INNER"> + <xxx metal:define-slot="OUTERSLOT" metal:fill-slot="INNERSLOT"> + OUTERSLOT + </xxx> +</div> +</div> + +<div metal:use-macro="OUTER"> +<span> + <xxx> + <div metal:fill-slot="OUTERSLOT">outer-argument</div> + </xxx> +</span> +</div> + +<div metal:use-macro="OUTER"> +</div> diff --git a/src/zope/tal/tests/input/test16.html b/src/zope/tal/tests/input/test16.html new file mode 100644 index 0000000..1414f45 --- /dev/null +++ b/src/zope/tal/tests/input/test16.html @@ -0,0 +1,2 @@ +<a href="valid/link.html" + tal:attributes="href python:'/base/' + attrs['href']">blah, blah</a> diff --git a/src/zope/tal/tests/input/test16.xml b/src/zope/tal/tests/input/test16.xml new file mode 100644 index 0000000..2efb2ab --- /dev/null +++ b/src/zope/tal/tests/input/test16.xml @@ -0,0 +1,7 @@ +<?xml version="1.0"?> +<body xmlns:tal="http://xml.zope.org/namespaces/tal"> + +<ImG href="foo" Alt="bar" + tal:attributes="Href string:about:foo;alT string:baz" /> + +</body> diff --git a/src/zope/tal/tests/input/test17.html b/src/zope/tal/tests/input/test17.html new file mode 100644 index 0000000..5a5ebb3 --- /dev/null +++ b/src/zope/tal/tests/input/test17.html @@ -0,0 +1,6 @@ +<tal:block tal:content="string:Yes">No</tal:block> +<tal:block content="string:Yes">No</tal:block> +<tal:block>Yes</tal:block> + +<metal:block tal:content="string:Yes">No</metal:block> +<metal:block>Yes</metal:block> diff --git a/src/zope/tal/tests/input/test17.xml b/src/zope/tal/tests/input/test17.xml new file mode 100644 index 0000000..ecb617a --- /dev/null +++ b/src/zope/tal/tests/input/test17.xml @@ -0,0 +1,10 @@ +<?xml version="1.0"?> +<body xmlns:z="http://xml.zope.org/namespaces/tal" + xmlns:z2="http://xml.zope.org/namespaces/metal"> +<z:block z:content="string:Yes">No</z:block> +<z:block content="string:Yes">No</z:block> +<z:block>Yes</z:block> + +<z2:block z:content="string:Yes">No</z2:block> +<z2:block>Yes</z2:block> +</body> diff --git a/src/zope/tal/tests/input/test18.html b/src/zope/tal/tests/input/test18.html new file mode 100644 index 0000000..c3a5c26 --- /dev/null +++ b/src/zope/tal/tests/input/test18.html @@ -0,0 +1,16 @@ +<p tal:omit-tag="">Content</p> +<p tal:omit-tag=""></p> +<img tal:omit-tag=""> + +<p tal:omit-tag="string:Yes">Content</p> +<p tal:omit-tag="string:Yes"></p> +<img tal:omit-tag="string:Yes"> + +<p tal:omit-tag="nothing">Content</p> +<p tal:omit-tag="nothing"></p> +<img tal:omit-tag="nothing"> + +<p tal:define="txt string:Yes" tal:omit-tag="" tal:content="txt">No</p> +<p tal:define="txt string:Yes" tal:omit-tag="" tal:replace="txt">No</p> +<p tal:omit-tag="" tal:content="default">Yes</p> +<p tal:omit-tag="" tal:replace="default">Yes</p> diff --git a/src/zope/tal/tests/input/test18.xml b/src/zope/tal/tests/input/test18.xml new file mode 100644 index 0000000..5a0cca4 --- /dev/null +++ b/src/zope/tal/tests/input/test18.xml @@ -0,0 +1,20 @@ +<?xml version="1.0"?> +<body xmlns:tal="http://xml.zope.org/namespaces/tal" + xmlns:metal="http://xml.zope.org/namespaces/metal"> +<p tal:omit-tag="">Content</p> +<p tal:omit-tag=""></p> +<img tal:omit-tag=""/> + +<p tal:omit-tag="string:Yes">Content</p> +<p tal:omit-tag="string:Yes"></p> +<img tal:omit-tag="string:Yes"/> + +<p tal:omit-tag="nothing">Content</p> +<p tal:omit-tag="nothing"></p> +<img tal:omit-tag="nothing" /> + +<p tal:define="txt string:Yes" tal:omit-tag="" tal:content="txt">No</p> +<p tal:define="txt string:Yes" tal:omit-tag="" tal:replace="txt">No</p> +<p tal:omit-tag="" tal:content="default">Yes</p> +<p tal:omit-tag="" tal:replace="default">Yes</p> +</body> diff --git a/src/zope/tal/tests/input/test19.html b/src/zope/tal/tests/input/test19.html new file mode 100644 index 0000000..a56632a --- /dev/null +++ b/src/zope/tal/tests/input/test19.html @@ -0,0 +1,5 @@ +<span i18n:translate="">Replace this</span> +<span i18n:translate="msgid">This is a +translated string</span> +<span i18n:translate="">And another +translated string</span> diff --git a/src/zope/tal/tests/input/test19.xml b/src/zope/tal/tests/input/test19.xml new file mode 100644 index 0000000..fe4bf79 --- /dev/null +++ b/src/zope/tal/tests/input/test19.xml @@ -0,0 +1,8 @@ +<?xml version="1.0"?> +<body xmlns:i18n="http://xml.zope.org/namespaces/i18n"> +<span i18n:translate="">Replace this</span> +<span i18n:translate="msgid">This is a +translated string</span> +<span i18n:translate="">And another +translated string</span> +</body> diff --git a/src/zope/tal/tests/input/test20.html b/src/zope/tal/tests/input/test20.html new file mode 100644 index 0000000..f302213 --- /dev/null +++ b/src/zope/tal/tests/input/test20.html @@ -0,0 +1 @@ +<span i18n:translate="">replaceable <p tal:replace="str:here">content</p></span> diff --git a/src/zope/tal/tests/input/test20.xml b/src/zope/tal/tests/input/test20.xml new file mode 100644 index 0000000..5050883 --- /dev/null +++ b/src/zope/tal/tests/input/test20.xml @@ -0,0 +1,6 @@ +<?xml version="1.0"?> +<body xmlns:tal="http://xml.zope.org/namespaces/tal" + xmlns:metal="http://xml.zope.org/namespaces/metal" + xmlns:i18n="http://xml.zope.org/namespaces/i18n"> +<span i18n:translate="">replaceable <p tal:replace="str:here">content</p></span> +</body> diff --git a/src/zope/tal/tests/input/test21.html b/src/zope/tal/tests/input/test21.html new file mode 100644 index 0000000..95f925e --- /dev/null +++ b/src/zope/tal/tests/input/test21.html @@ -0,0 +1,4 @@ +<span i18n:translate=""> + <span tal:replace="str:Lomax" i18n:name="name" /> was born in + <span tal:replace="str:Antarctica" i18n:name="country" />. +</span> diff --git a/src/zope/tal/tests/input/test21.xml b/src/zope/tal/tests/input/test21.xml new file mode 100644 index 0000000..eea370b --- /dev/null +++ b/src/zope/tal/tests/input/test21.xml @@ -0,0 +1,9 @@ +<?xml version="1.0"?> +<body xmlns:tal="http://xml.zope.org/namespaces/tal" + xmlns:metal="http://xml.zope.org/namespaces/metal" + xmlns:i18n="http://xml.zope.org/namespaces/i18n"> +<span i18n:translate=""> + <span tal:replace="str:Lomax" i18n:name="name" /> was born in + <span tal:replace="str:Antarctica" i18n:name="country" />. +</span> +</body> diff --git a/src/zope/tal/tests/input/test22.html b/src/zope/tal/tests/input/test22.html new file mode 100644 index 0000000..a4a7e93 --- /dev/null +++ b/src/zope/tal/tests/input/test22.html @@ -0,0 +1,4 @@ +<span i18n:translate=""> + <span tal:omit-tag="" i18n:name="name"><b>Jim</b></span> was born in + <span tal:omit-tag="" i18n:name="country">the USA</span>. +</span> diff --git a/src/zope/tal/tests/input/test22.xml b/src/zope/tal/tests/input/test22.xml new file mode 100644 index 0000000..54b57d8 --- /dev/null +++ b/src/zope/tal/tests/input/test22.xml @@ -0,0 +1,6 @@ +<?xml version="1.0"?> +<body xmlns:tal="http://xml.zope.org/namespaces/tal"> + <span tal:content="default">content</span> + <span tal:omit-tag="" tal:content="default">omit</span> + <span tal:replace="default">replace</span> +</body> diff --git a/src/zope/tal/tests/input/test23.html b/src/zope/tal/tests/input/test23.html new file mode 100644 index 0000000..bfe6665 --- /dev/null +++ b/src/zope/tal/tests/input/test23.html @@ -0,0 +1,2 @@ +<span i18n:data="here/currentTime" + i18n:translate="timefmt">2:32 pm</span> diff --git a/src/zope/tal/tests/input/test24.html b/src/zope/tal/tests/input/test24.html new file mode 100644 index 0000000..6d53984 --- /dev/null +++ b/src/zope/tal/tests/input/test24.html @@ -0,0 +1,12 @@ +<input name="Delete" + tal:attributes="name string:delete_button" + i18n:attributes="name"> + +<input name="Delete" + i18n:attributes="name message-id"> + +<input i18n:attributes=" name message-id; + attr input-attr "> + +<input i18n:attributes=" name message-id; + attr input-attr;"> diff --git a/src/zope/tal/tests/input/test25.html b/src/zope/tal/tests/input/test25.html new file mode 100644 index 0000000..25a99cf --- /dev/null +++ b/src/zope/tal/tests/input/test25.html @@ -0,0 +1 @@ +<input name="Delete" i18n:attributes="name"> diff --git a/src/zope/tal/tests/input/test26.html b/src/zope/tal/tests/input/test26.html new file mode 100644 index 0000000..fa5a99d --- /dev/null +++ b/src/zope/tal/tests/input/test26.html @@ -0,0 +1,3 @@ +<span i18n:translate="jobnum"> + Job #<span tal:replace="context/@@object_name" + i18n:name="jobnum">NN</span></span> diff --git a/src/zope/tal/tests/input/test27.html b/src/zope/tal/tests/input/test27.html new file mode 100644 index 0000000..b9c16cb --- /dev/null +++ b/src/zope/tal/tests/input/test27.html @@ -0,0 +1,5 @@ +<p i18n:translate="verify">Your contact email address is recorded as + <a href="mailto:user@example.com" + tal:content="request/submitter" + i18n:name="email">user@host.com</a> +</p> diff --git a/src/zope/tal/tests/input/test28.html b/src/zope/tal/tests/input/test28.html new file mode 100644 index 0000000..0364663 --- /dev/null +++ b/src/zope/tal/tests/input/test28.html @@ -0,0 +1,5 @@ +<p i18n:translate="verify">Your contact email address is recorded as + <span tal:omit-tag="" i18n:name="email"> + <a href="mailto:user@example.com" + tal:content="request/submitter">user@host.com</a></span> +</p> diff --git a/src/zope/tal/tests/input/test29.html b/src/zope/tal/tests/input/test29.html new file mode 100644 index 0000000..e2f1e82 --- /dev/null +++ b/src/zope/tal/tests/input/test29.html @@ -0,0 +1,4 @@ +<div i18n:translate="">At the tone the time will be +<span i18n:data="here/currentTime" + i18n:translate="timefmt" + i18n:name="time">2:32 pm</span>... beep!</div> diff --git a/src/zope/tal/tests/input/test30.html b/src/zope/tal/tests/input/test30.html new file mode 100644 index 0000000..6f8c6ef --- /dev/null +++ b/src/zope/tal/tests/input/test30.html @@ -0,0 +1,6 @@ +<p i18n:translate="verify">Your contact email address is recorded as +<a href="user@host.com" + tal:attributes="href string:mailto:${request/submitter}" + tal:content="request/submitter" + i18n:name="email">user@host.com</a> +</p> diff --git a/src/zope/tal/tests/input/test31.html b/src/zope/tal/tests/input/test31.html new file mode 100644 index 0000000..c927f42 --- /dev/null +++ b/src/zope/tal/tests/input/test31.html @@ -0,0 +1,7 @@ +<p i18n:translate="verify">Your contact email address is recorded as +<span tal:omit-tag="" i18n:name="email"> +<a href="user@host.com" + tal:attributes="href string:mailto:${request/submitter}" + tal:content="request/submitter"> + user@host.com</a></span> +</p> diff --git a/src/zope/tal/tests/input/test32.html b/src/zope/tal/tests/input/test32.html new file mode 100644 index 0000000..3b09bad --- /dev/null +++ b/src/zope/tal/tests/input/test32.html @@ -0,0 +1,4 @@ +<span i18n:translate="origin"> + <span tal:content="str:Lomax" i18n:name="name" /> was born in + <span tal:content="str:Antarctica" i18n:name="country" />. +</span> diff --git a/src/zope/tal/tests/input/test33.html b/src/zope/tal/tests/input/test33.html new file mode 100644 index 0000000..f5dcf58 --- /dev/null +++ b/src/zope/tal/tests/input/test33.html @@ -0,0 +1 @@ +<span i18n:translate="">don't translate me</span> diff --git a/src/zope/tal/tests/input/test34.html b/src/zope/tal/tests/input/test34.html new file mode 100644 index 0000000..4cd6ff0 --- /dev/null +++ b/src/zope/tal/tests/input/test34.html @@ -0,0 +1,11 @@ +<span i18n:translate="don't translate me"> + stuff + <span tal:replace="string:foobar" i18n:name="longname" /> + more stuff +</span> + +<span i18n:translate=""> + stuff + <span tal:replace="string:foobar" i18n:name="longname" /> + more stuff +</span> diff --git a/src/zope/tal/tests/input/test35.html b/src/zope/tal/tests/input/test35.html new file mode 100644 index 0000000..7964e9f --- /dev/null +++ b/src/zope/tal/tests/input/test35.html @@ -0,0 +1,7 @@ +<span metal:define-macro="page" tal:omit-tag=""> + <h1 metal:define-slot="name" tal:omit-tag="" /> +</span> + +<span metal:use-macro="page"> + <h1 metal:fill-slot="name" tal:content="macroname">name</h1> +</span>
\ No newline at end of file diff --git a/src/zope/tal/tests/input/test36.html b/src/zope/tal/tests/input/test36.html new file mode 100644 index 0000000..bf4932a --- /dev/null +++ b/src/zope/tal/tests/input/test36.html @@ -0,0 +1,6 @@ +<span tal:replace="string:<foo>" /> +<span i18n:translate=""> + <span tal:replace="string:<foo>" i18n:name="name1" /> + <span tal:replace="structure string:<bar />" i18n:name="name2" /> + <span tal:omit-tag="" i18n:name="name3"><b>some</b> <i>text</i></span> +</span> diff --git a/src/zope/tal/tests/input/test_domain.html b/src/zope/tal/tests/input/test_domain.html new file mode 100644 index 0000000..95d40a2 --- /dev/null +++ b/src/zope/tal/tests/input/test_domain.html @@ -0,0 +1,7 @@ +<div i18n:domain="lower"> +<span i18n:translate="">Replace this</span> +<span i18n:translate="msgid">This is a +translated string</span> +<span i18n:translate="">And another +translated string</span> +</div> diff --git a/src/zope/tal/tests/input/test_failed_attr_translation.html b/src/zope/tal/tests/input/test_failed_attr_translation.html new file mode 100644 index 0000000..1c395c7 --- /dev/null +++ b/src/zope/tal/tests/input/test_failed_attr_translation.html @@ -0,0 +1,2 @@ +<input value="don't translate me" + i18n:attributes="value"> diff --git a/src/zope/tal/tests/input/test_metal1.html b/src/zope/tal/tests/input/test_metal1.html new file mode 100644 index 0000000..a5371ce --- /dev/null +++ b/src/zope/tal/tests/input/test_metal1.html @@ -0,0 +1,61 @@ +<span metal:define-macro="OUTER"> + AAA + <span metal:define-macro="INNER">INNER</span> + BBB +</span> + +<xxx metal:use-macro="OUTER"> +</xxx> + +<xxx metal:use-macro="INNER"> +</xxx> + +<span metal:define-macro="OUTER2"> + AAA + <xxx metal:define-slot="OUTERSLOT"> + <span metal:define-macro="INNER2">INNER</span> + </xxx> + BBB +</span> + +<xxx metal:use-macro="OUTER2"> +</xxx> + +<xxx metal:use-macro="INNER2"> +</xxx> + +<xxx metal:use-macro="OUTER2"> + <yyy metal:fill-slot="OUTERSLOT">OUTERSLOT</yyy> +</xxx> + +<span metal:define-macro="OUTER3"> + AAA + <xxx metal:define-slot="OUTERSLOT"> + <span metal:define-macro="INNER3">INNER + <xxx metal:define-slot="INNERSLOT">INNERSLOT</xxx> + </span> + </xxx> + BBB +</span> + +<xxx metal:use-macro="OUTER3"> +</xxx> + +<xxx metal:use-macro="OUTER3"> + <yyy metal:fill-slot="OUTERSLOT">OUTERSLOT</yyy> +</xxx> + +<xxx metal:use-macro="INNER3"> +</xxx> + +<xxx metal:use-macro="INNER3"> + <yyy metal:fill-slot="INNERSLOT">INNERSLOT</yyy> +</xxx> + +<xxx metal:use-macro="INNER3"> + <yyy metal:fill-slot="INNERSLOT"> + <zzz metal:define-macro="INSLOT">INSLOT</zzz> + </yyy> +</xxx> + +<xxx metal:use-macro="INSLOT"></xxx> diff --git a/src/zope/tal/tests/input/test_metal2.html b/src/zope/tal/tests/input/test_metal2.html new file mode 100644 index 0000000..425508a --- /dev/null +++ b/src/zope/tal/tests/input/test_metal2.html @@ -0,0 +1,7 @@ +<div metal:define-macro="OUTER"> + OUTER + <span metal:define-macro="INNER">INNER</span> + OUTER +</div> + +<div metal:use-macro="OUTER"/> diff --git a/src/zope/tal/tests/input/test_metal3.html b/src/zope/tal/tests/input/test_metal3.html new file mode 100644 index 0000000..b0af907 --- /dev/null +++ b/src/zope/tal/tests/input/test_metal3.html @@ -0,0 +1 @@ +<span tal:attributes="class string:foo">Should not get attr in metal</span> diff --git a/src/zope/tal/tests/input/test_metal4.html b/src/zope/tal/tests/input/test_metal4.html new file mode 100644 index 0000000..dc774d3 --- /dev/null +++ b/src/zope/tal/tests/input/test_metal4.html @@ -0,0 +1,4 @@ +<!-- the outer element *must* be tal:something or metal:something --> +<metal:block define-macro="page" i18n:domain="zope"> + <title metal:define-slot="title">Z3 UI</title> +</metal:block> diff --git a/src/zope/tal/tests/input/test_metal5.html b/src/zope/tal/tests/input/test_metal5.html new file mode 100644 index 0000000..8bae3d8 --- /dev/null +++ b/src/zope/tal/tests/input/test_metal5.html @@ -0,0 +1,4 @@ +<!-- the outer element *must* include tal:omit-tag='' --> +<x tal:omit-tag="" metal:define-macro="page" i18n:domain="zope"> + <title metal:define-slot="title">Z3 UI</title> +</x> diff --git a/src/zope/tal/tests/input/test_metal6.html b/src/zope/tal/tests/input/test_metal6.html new file mode 100644 index 0000000..ce243f2 --- /dev/null +++ b/src/zope/tal/tests/input/test_metal6.html @@ -0,0 +1,5 @@ +<metal:block define-macro="page"> + <html i18:domain="zope"> + <metal:block define-slot="title">Z3 UI</metal:block> + </html> +</metal:block> diff --git a/src/zope/tal/tests/input/test_metal7.html b/src/zope/tal/tests/input/test_metal7.html new file mode 100644 index 0000000..75ec511 --- /dev/null +++ b/src/zope/tal/tests/input/test_metal7.html @@ -0,0 +1,6 @@ +<html metal:define-macro="page" i18n:domain="zope"> + <x metal:define-slot="title" /> +</html> +<html metal:use-macro="page"> + <x metal:fill-slot="title" /> +</html> diff --git a/src/zope/tal/tests/input/test_metal8.html b/src/zope/tal/tests/input/test_metal8.html new file mode 100644 index 0000000..40d8a43 --- /dev/null +++ b/src/zope/tal/tests/input/test_metal8.html @@ -0,0 +1,15 @@ +<html metal:define-macro="page" i18n:domain="zope"> +<body> +<div metal:define-macro="workspace"> +<div metal:define-slot="body"> +Default body +</div> +</div> +</body> +</html> + +<html metal:use-macro="page"> +<div metal:fill-slot="body"> +Filled-in body +</div> +</html> diff --git a/src/zope/tal/tests/input/test_metal9.html b/src/zope/tal/tests/input/test_metal9.html new file mode 100644 index 0000000..46b1b45 --- /dev/null +++ b/src/zope/tal/tests/input/test_metal9.html @@ -0,0 +1,23 @@ +<div metal:define-macro="macro1" i18n:domain="zope"> +<span metal:define-slot="slot1"> +Default for macro1 +</span> +</div> + +<div metal:define-macro="macro2" metal:extend-macro="macro1" i18n:domain="zope"> +<span metal:fill-slot="slot1"> +Macro 2's slot 1 decoration +<span metal:define-slot="slot1"> +Default for macro2 +</span> +</span> +</div> + +<div metal:use-macro="macro2"> +</div> + +<div metal:use-macro="macro2"> +<span metal:fill-slot="slot1"> +Custom slot1 +</span> +</div> diff --git a/src/zope/tal/tests/input/test_sa1.html b/src/zope/tal/tests/input/test_sa1.html new file mode 100644 index 0000000..8879865 --- /dev/null +++ b/src/zope/tal/tests/input/test_sa1.html @@ -0,0 +1,6 @@ +<html> +<title>Simple test of source annotations</title> +<body> +<p>Foo!</p> +</body> +</html> diff --git a/src/zope/tal/tests/input/test_sa1.xml b/src/zope/tal/tests/input/test_sa1.xml new file mode 100644 index 0000000..d00a46d --- /dev/null +++ b/src/zope/tal/tests/input/test_sa1.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" ?> +<html> +<title>Simple test of source annotations</title> +<body> +<p>Foo!</p> +</body> +</html> diff --git a/src/zope/tal/tests/input/test_sa2.html b/src/zope/tal/tests/input/test_sa2.html new file mode 100644 index 0000000..1c4e06b --- /dev/null +++ b/src/zope/tal/tests/input/test_sa2.html @@ -0,0 +1,9 @@ +<!DOCTYPE html + PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "DTD/xhtml1-transitional.dtd"> +<html> +<title>Simple test of source annotations</title> +<body> +<p>Foo!</p> +</body> +</html> diff --git a/src/zope/tal/tests/input/test_sa2.xml b/src/zope/tal/tests/input/test_sa2.xml new file mode 100644 index 0000000..b54d6a1 --- /dev/null +++ b/src/zope/tal/tests/input/test_sa2.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" ?> +<!DOCTYPE html + PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "DTD/xhtml1-transitional.dtd"> +<html> +<title>Simple test of source annotations</title> +<body> +<p>Foo!</p> +</body> +</html> diff --git a/src/zope/tal/tests/input/test_sa3.html b/src/zope/tal/tests/input/test_sa3.html new file mode 100644 index 0000000..675805d --- /dev/null +++ b/src/zope/tal/tests/input/test_sa3.html @@ -0,0 +1,15 @@ +<html> +<body> + <div metal:define-macro="macro1">This is macro1 on sa3 line 3. + <span metal:define-slot="slot1">This is slot1 on sa3 line 4.</span> + This is the end of macro1 on sa3 line 5. + </div> + <p>Some text on sa3 line 7.</p> + <p metal:use-macro="macro1"> + This text on sa3 line 9 will disappear. + <b metal:fill-slot="slot1">Text from sa3 line 10 is filled into slot1.</b> + This text on sa3 line 11 will disappear. + </p> + <p>This is some text on sa3 line 13.</p> +</body> +</html> diff --git a/src/zope/tal/tests/input/test_sa3.xml b/src/zope/tal/tests/input/test_sa3.xml new file mode 100644 index 0000000..79e3251 --- /dev/null +++ b/src/zope/tal/tests/input/test_sa3.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" ?> +<html> +<body xmlns:metal="http://xml.zope.org/namespaces/metal"> + <div metal:define-macro="macro1">This is macro1 on sa3 line 4. + <span metal:define-slot="slot1">This is slot1 on sa3 line 5.</span> + This is the end of macro1 on sa3 line 6. + </div> + <p>Some text on sa3 line 8.</p> + <p metal:use-macro="macro1"> + This text on sa3 line 10 will disappear. + <b metal:fill-slot="slot1">Text from sa3 line 11 is filled into slot1.</b> + This text on sa3 line 12 will disappear. + </p> + <p>This is some text on sa3 line 14.</p> +</body> +</html> diff --git a/src/zope/tal/tests/input/test_sa4.html b/src/zope/tal/tests/input/test_sa4.html new file mode 100644 index 0000000..97596f6 --- /dev/null +++ b/src/zope/tal/tests/input/test_sa4.html @@ -0,0 +1,11 @@ +<html> +<body> + <p>Some text on sa4 line 3.</p> + <p metal:use-macro="tests/input/test_sa3.html/macro1"> + This text on sa4 line 5 will disappear. + <b metal:fill-slot="slot1">Text from sa4 line 6 is filled into slot1.</b> + This text on sa4 line 7 will disappear. + </p> + <p>This is some text on sa4 line 9.</p> +</body> +</html> diff --git a/src/zope/tal/tests/markbench.py b/src/zope/tal/tests/markbench.py new file mode 100644 index 0000000..f08f9e2 --- /dev/null +++ b/src/zope/tal/tests/markbench.py @@ -0,0 +1,187 @@ +#! /usr/bin/env python +############################################################################## +# +# Copyright (c) 2001, 2002 Zope Corporation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +"""Run benchmarks of TAL vs. DTML + +$Id$ +""" + +import warnings +warnings.filterwarnings("ignore", category=DeprecationWarning) + +import os +os.environ['NO_SECURITY'] = 'true' + +import getopt +import sys +import time + +from cStringIO import StringIO + +#from zope.documenttemplate.dt_html import HTMLFile + +from zope.tal.htmltalparser import HTMLTALParser +from zope.tal.talinterpreter import TALInterpreter +from zope.tal.dummyengine import DummyEngine + + +def time_apply(f, args, kwargs, count): + r = [None] * count + for i in range(4): + f(*args, **kwargs) + t0 = time.clock() + for i in r: + pass + t1 = time.clock() + for i in r: + f(*args, **kwargs) + t = time.clock() - t1 - (t1 - t0) + return t / count + +def time_zpt(fn, count): + from zope.pagetemplate.pagetemplate import PageTemplate + pt = PageTemplate() + pt.write(open(fn).read()) + return time_apply(pt.pt_render, (data,), {}, count) + +def time_tal(fn, count): + p = HTMLTALParser() + p.parseFile(fn) + program, macros = p.getCode() + engine = DummyEngine(macros) + engine.globals = data + tal = TALInterpreter(program, macros, engine, StringIO(), wrap=0, + tal=1, strictinsert=0) + return time_apply(tal, (), {}, count) + +def time_dtml(fn, count): + html = HTMLFile(fn) + return time_apply(html, (), data, count) + +def profile_zpt(fn, count, profiler): + from zope.pagetemplate.pagetemplate import PageTemplate + pt = PageTemplate() + pt.write(open(fn).read()) + for i in range(4): + pt.pt_render(extra_context=data) + r = [None] * count + for i in r: + profiler.runcall(pt.pt_render, 0, data) + +def profile_tal(fn, count, profiler): + p = HTMLTALParser() + p.parseFile(fn) + program, macros = p.getCode() + engine = DummyEngine(macros) + engine.globals = data + tal = TALInterpreter(program, macros, engine, StringIO(), wrap=0, + tal=1, strictinsert=0) + for i in range(4): + tal() + r = [None] * count + for i in r: + profiler.runcall(tal) + +# Figure out where the benchmark files are: +try: + fname = __file__ +except NameError: + fname = sys.argv[0] +taldir = os.path.dirname(os.path.dirname(os.path.abspath(fname))) +benchdir = os.path.join(taldir, 'benchmark') + +# Construct templates for the filenames: +tal_fn = os.path.join(benchdir, 'tal%.2d.html') +dtml_fn = os.path.join(benchdir, 'dtml%.2d.html') + +def compare(n, count, profiler=None, verbose=1): + if verbose: + t1 = int(time_zpt(tal_fn % n, count) * 1000 + 0.5) + t2 = int(time_tal(tal_fn % n, count) * 1000 + 0.5) + t3 = 'n/a' # int(time_dtml(dtml_fn % n, count) * 1000 + 0.5) + print '%.2d: %10s %10s %10s' % (n, t1, t2, t3) + if profiler: + profile_tal(tal_fn % n, count, profiler) + +def main(count, profiler=None, verbose=1): + n = 1 + if verbose: + print '##: %10s %10s %10s' % ('ZPT', 'TAL', 'DTML') + while os.path.isfile(tal_fn % n) and os.path.isfile(dtml_fn % n): + compare(n, count, profiler, verbose) + n = n + 1 + +def get_signal_name(sig): + import signal + for name in dir(signal): + if getattr(signal, name) == sig: + return name + return None + +data = {'x':'X', 'r2': range(2), 'r8': range(8), 'r64': range(64)} +for i in range(10): + data['x%s' % i] = 'X%s' % i + +if __name__ == "__main__": + filename = "markbench.prof" + profiler = None + runtests = False + verbose = True + + opts, args = getopt.getopt(sys.argv[1:], "pqt") + for opt, arg in opts: + if opt == "-p": + import profile + profiler = profile.Profile() + elif opt == "-q": + verbose = False + elif opt == "-t": + runtests = True + + if runtests: + srcdir = os.path.dirname(os.path.dirname(taldir)) + topdir = os.path.dirname(srcdir) + pwd = os.getcwd() + os.chdir(topdir) + rc = os.spawnl(os.P_WAIT, sys.executable, + sys.executable, "test.py", "zope.tal.tests") + if rc > 0: + # TODO: Failing tests don't cause test.py to report an + # error; not sure why. ;-( + sys.exit(rc) + elif rc < 0: + sig = -rc + print >>sys.stderr, ( + "Process exited, signal %d (%s)." + % (sig, get_signal_name(sig) or "<unknown signal>")) + sys.exit(1) + os.chdir(pwd) + + if len(args) >= 1: + for arg in args: + compare(int(arg), 25, profiler, verbose) + else: + main(25, profiler, verbose) + + if profiler is not None: + profiler.dump_stats(filename) + import pstats + p = pstats.Stats(filename) + p.strip_dirs() + p.sort_stats('time', 'calls') + try: + p.print_stats(20) + except IOError, e: + if e.errno != errno.EPIPE: + raise diff --git a/src/zope/tal/tests/output/__init__.py b/src/zope/tal/tests/output/__init__.py new file mode 100644 index 0000000..b711d36 --- /dev/null +++ b/src/zope/tal/tests/output/__init__.py @@ -0,0 +1,2 @@ +# +# This file is necessary to make this directory a package. diff --git a/src/zope/tal/tests/output/acme_template.html b/src/zope/tal/tests/output/acme_template.html new file mode 100644 index 0000000..3d37355 --- /dev/null +++ b/src/zope/tal/tests/output/acme_template.html @@ -0,0 +1,26 @@ +<!-- This is ACME's generic look and feel, which is based on +PNOME's look and feel. --> +<html> +<head> +<title>ACME Look and Feel</title> + + +</head> +<body> +<div> + <div> + "The early bird gets the worm, but the second mouse gets the cheese." + </div> + <a href="#">Preferences...</a> +</div> +<div> + Content here +</div> +<div> +Copyright 2004 Acme Inc. +<div> +Standard disclaimers apply. +</div> +</div> +</body> +</html> diff --git a/src/zope/tal/tests/output/acme_template_source.html b/src/zope/tal/tests/output/acme_template_source.html new file mode 100644 index 0000000..11f19d3 --- /dev/null +++ b/src/zope/tal/tests/output/acme_template_source.html @@ -0,0 +1,27 @@ +<!-- This is ACME's generic look and feel, which is based on +PNOME's look and feel. --> +<html metal:define-macro="page" + metal:use-macro="pnome_macros_page"> +<head> +<title metal:fill-slot="title">ACME Look and Feel</title> +<metal:block> +</metal:block> +</head> +<body> +<div> + <div> + "The early bird gets the worm, but the second mouse gets the cheese." + </div> + <a href="#">Preferences...</a> +</div> +<div> + Content here +</div> +<div metal:fill-slot="page-footer"> +Copyright 2004 Acme Inc. +<div metal:define-slot="disclaimer"> +Standard disclaimers apply. +</div> +</div> +</body> +</html> diff --git a/src/zope/tal/tests/output/document_list.html b/src/zope/tal/tests/output/document_list.html new file mode 100644 index 0000000..9e0ea10 --- /dev/null +++ b/src/zope/tal/tests/output/document_list.html @@ -0,0 +1,30 @@ +<!-- ACME's document_list uses the ACME look and feel --> +<html> +<head> +<title>Acme Document List</title> +<style type="text/css"> + body { background-color: white; } +</style> +</head> +<body> +<div> + <div> + "The early bird gets the worm, but the second mouse gets the cheese." + </div> + <a href="#">Preferences...</a> +</div> +<div> +<h1>Documents</h1> +<ul> +<li>Rocket Science for Dummies</li> +<li>Birds for the Gourmet Chef</li> +</ul> +</div> +<div> +Copyright 2004 Acme Inc. +<div> +This document list is classified. +</div> +</div> +</body> +</html> diff --git a/src/zope/tal/tests/output/document_list_source.html b/src/zope/tal/tests/output/document_list_source.html new file mode 100644 index 0000000..69600e0 --- /dev/null +++ b/src/zope/tal/tests/output/document_list_source.html @@ -0,0 +1,30 @@ +<!-- ACME's document_list uses the ACME look and feel --> +<html metal:use-macro="acme_macros_page"> +<head> +<title metal:fill-slot="title">Acme Document List</title> +<style metal:fill-slot="local-styles" type="text/css"> + body { background-color: white; } +</style> +</head> +<body> +<div> + <div> + "The early bird gets the worm, but the second mouse gets the cheese." + </div> + <a href="#">Preferences...</a> +</div> +<div metal:fill-slot="content"> +<h1>Documents</h1> +<ul> +<li>Rocket Science for Dummies</li> +<li>Birds for the Gourmet Chef</li> +</ul> +</div> +<div> +Copyright 2004 Acme Inc. +<div metal:fill-slot="disclaimer"> +This document list is classified. +</div> +</div> +</body> +</html> diff --git a/src/zope/tal/tests/output/test01.html b/src/zope/tal/tests/output/test01.html new file mode 100644 index 0000000..7064db0 --- /dev/null +++ b/src/zope/tal/tests/output/test01.html @@ -0,0 +1,68 @@ +<!DOCTYPE html + PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "DTD/xhtml1-transitional.dtd"> +<html> + + <head>dadada</head> + + <body> + + <h1>This +Is +The +Replaced +Title</h1> + + <!-- test entity references --> + &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 /> + + <h3>Header Level 3</h3> + <span> <h3>Header Level 3</h3></span> + + </body> + +</html> diff --git a/src/zope/tal/tests/output/test01.xml b/src/zope/tal/tests/output/test01.xml new file mode 100644 index 0000000..91e9851 --- /dev/null +++ b/src/zope/tal/tests/output/test01.xml @@ -0,0 +1,65 @@ +<?xml version="1.0" ?> +<!DOCTYPE html + PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "DTD/xhtml1-transitional.dtd"> +<html> + + <head>dadada</head> + + <body> + + <h1>This Is The Replaced Title</h1> + + <!-- test entity references --> + &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/> + + <h3>Header Level 3</h3> + <span> <h3>Header Level 3</h3></span> + + </body> + +</html> diff --git a/src/zope/tal/tests/output/test02.html b/src/zope/tal/tests/output/test02.html new file mode 100644 index 0000000..8d081fc --- /dev/null +++ b/src/zope/tal/tests/output/test02.html @@ -0,0 +1,118 @@ +<biztalk_1 xmlns="urn:schemas-biztalk-org:biztalk:biztalk_1"> + +<foo:header xmlns:foo="whomping-willow" plain="guido" quote=""" apostrophe="'" both=""'" lt="<" gt=">" amp="&" foo=""> + <manifest> + <document> + <name>sample1</name> + <description>a simple invoice</description> + </document> + </manifest> +</foo:header> + +<body> + +<!-- sample1.xml is an example of a simple invoice for a small restaurant supplies order --> + +<invoice xmlns="urn:http://schemas.biztalk.org/united_rest_com/yw7sg15x.xml"> + <header> + <invoicenumber>01786</invoicenumber> + <invoicedate>2000-03-17</invoicedate> <!-- March 17th, 2000 --> + <orderno>55377</orderno> + <orderdate>2000-03-15</orderdate> <!-- March 15th, 2000 --> + <customerpo>GJ03405</customerpo> + <shipmethod>DAVE 1</shipmethod> + <shipdate>2000-03-17</shipdate> <!-- March 17th, 2000 --> + <customerid>K5211(34)</customerid> + <salespersoncode>23</salespersoncode> + <taxid>23</taxid> + </header> + <invoiceto> + <name>SHIPWRIGHT RESTAURANTS LIMITED</name> + <addressline>125 NORTH SERVICE ROAD W</addressline> + <addressline>WESTLAKE ACCESS</addressline> + <city>NORTH BAY</city> + <postcode>L8B1O5</postcode> + <state>ONTARIO</state> + <country>CANADA</country> + </invoiceto> + <shipto> + <name /> + <addressline>ATTN: PAULINE DEGRASSI</addressline> + <city /> + <postcode /> + <state /> + <country /> + </shipto> + <detaillines> + <detailline> + <quantityshipped>1</quantityshipped> + <unitofmeasure>CS</unitofmeasure> + <partnumber>DM 5309</partnumber> + <partdescription>#1013 12 OZ.MUNICH STEIN</partdescription> + <unitprice>37.72</unitprice> + <linetotal>37.72</linetotal> + </detailline> + <detailline> + <quantityshipped>6</quantityshipped> + <unitofmeasure>DZ</unitofmeasure> + <partnumber>ON 6420</partnumber> + <partdescription>PROVINCIAL DINNER FORK</partdescription> + <unitprice>17.98</unitprice> + <linetotal>107.88</linetotal> + </detailline> + <detailline> + <quantityshipped>72</quantityshipped> + <unitofmeasure>EA</unitofmeasure> + <partnumber>JR20643</partnumber> + <partdescription>PLASTIC HANDLED STEAK KNIFE</partdescription> + <unitprice>.81</unitprice> + <linetotal>58.32</linetotal> + </detailline> + <detailline> + <quantityshipped>6</quantityshipped> + <unitofmeasure>DZ</unitofmeasure> + <partnumber>ON 6410</partnumber> + <partdescription>PROVINCIAL TEASPOONS</partdescription> + <unitprice>12.16</unitprice> + <linetotal>72.96</linetotal> + </detailline> + <detailline> + <quantityshipped>0</quantityshipped> + <unitofmeasure>DZ</unitofmeasure> + <partnumber>ON 6411</partnumber> + <partdescription>PROVINCIAL RD BOWL SPOON</partdescription> + <quantitybackordered>6</quantitybackordered> + <unitprice>17.98</unitprice> + <linetotal>0.00</linetotal> + </detailline> + <detailline> + <quantityshipped>1</quantityshipped> + <unitofmeasure>EA</unitofmeasure> + <partnumber>DO 3218</partnumber> + <partdescription>34 OZ DUAL DIAL SCALE AM3218</partdescription> + <unitprice>70.00</unitprice> + <discountpercentage>5.0</discountpercentage> + <linetotal>66.50</linetotal> + </detailline> + <detailline> + <quantityshipped>1</quantityshipped> + <unitofmeasure>CS</unitofmeasure> + <partnumber>DM 195</partnumber> + <partdescription>20 OZ.BEER PUB GLASS</partdescription> + <unitprice>55.90</unitprice> + <linetotal>55.90</linetotal> + </detailline> + </detaillines> + <totals> + <subtotal>399.28</subtotal> + <discounttotal>3.50</discounttotal> + <freighttotal>23.75</freighttotal> + <gsttotal>29.61</gsttotal> + <provtaxtotal>33.84</provtaxtotal> + <othertotal>33.84</othertotal> + <invoicetotal>486.48</invoicetotal> + </totals> +</invoice> + +</body> +</biztalk_1> diff --git a/src/zope/tal/tests/output/test02.xml b/src/zope/tal/tests/output/test02.xml new file mode 100644 index 0000000..71ff075 --- /dev/null +++ b/src/zope/tal/tests/output/test02.xml @@ -0,0 +1,119 @@ +<?xml version="1.0" ?> +<biztalk_1 xmlns="urn:schemas-biztalk-org:biztalk:biztalk_1"> + +<foo:header xmlns:foo="whomping-willow" plain="guido" quote=""" apostrophe="'" both=""'" lt="<" gt=">" amp="&" foo=""> + <manifest> + <document> + <name>sample1</name> + <description>a simple invoice</description> + </document> + </manifest> +</foo:header> + +<body> + +<!-- sample1.xml is an example of a simple invoice for a small restaurant supplies order --> + +<Invoice xmlns="urn:http://schemas.biztalk.org/united_rest_com/yw7sg15x.xml"> + <Header> + <InvoiceNumber>01786</InvoiceNumber> + <InvoiceDate>2000-03-17</InvoiceDate> <!-- March 17th, 2000 --> + <OrderNo>55377</OrderNo> + <OrderDate>2000-03-15</OrderDate> <!-- March 15th, 2000 --> + <CustomerPO>GJ03405</CustomerPO> + <ShipMethod>DAVE 1</ShipMethod> + <ShipDate>2000-03-17</ShipDate> <!-- March 17th, 2000 --> + <CustomerID>K5211(34)</CustomerID> + <SalesPersonCode>23</SalesPersonCode> + <TaxID>23</TaxID> + </Header> + <InvoiceTo> + <Name>SHIPWRIGHT RESTAURANTS LIMITED</Name> + <AddressLine>125 NORTH SERVICE ROAD W</AddressLine> + <AddressLine>WESTLAKE ACCESS</AddressLine> + <City>NORTH BAY</City> + <PostCode>L8B1O5</PostCode> + <State>ONTARIO</State> + <Country>CANADA</Country> + </InvoiceTo> + <ShipTo> + <Name/> + <AddressLine>ATTN: PAULINE DEGRASSI</AddressLine> + <City/> + <PostCode/> + <State/> + <Country/> + </ShipTo> + <DetailLines> + <DetailLine> + <QuantityShipped>1</QuantityShipped> + <UnitOfMeasure>CS</UnitOfMeasure> + <PartNumber>DM 5309</PartNumber> + <PartDescription>#1013 12 OZ.MUNICH STEIN</PartDescription> + <UnitPrice>37.72</UnitPrice> + <LineTotal>37.72</LineTotal> + </DetailLine> + <DetailLine> + <QuantityShipped>6</QuantityShipped> + <UnitOfMeasure>DZ</UnitOfMeasure> + <PartNumber>ON 6420</PartNumber> + <PartDescription>PROVINCIAL DINNER FORK</PartDescription> + <UnitPrice>17.98</UnitPrice> + <LineTotal>107.88</LineTotal> + </DetailLine> + <DetailLine> + <QuantityShipped>72</QuantityShipped> + <UnitOfMeasure>EA</UnitOfMeasure> + <PartNumber>JR20643</PartNumber> + <PartDescription>PLASTIC HANDLED STEAK KNIFE</PartDescription> + <UnitPrice>.81</UnitPrice> + <LineTotal>58.32</LineTotal> + </DetailLine> + <DetailLine> + <QuantityShipped>6</QuantityShipped> + <UnitOfMeasure>DZ</UnitOfMeasure> + <PartNumber>ON 6410</PartNumber> + <PartDescription>PROVINCIAL TEASPOONS</PartDescription> + <UnitPrice>12.16</UnitPrice> + <LineTotal>72.96</LineTotal> + </DetailLine> + <DetailLine> + <QuantityShipped>0</QuantityShipped> + <UnitOfMeasure>DZ</UnitOfMeasure> + <PartNumber>ON 6411</PartNumber> + <PartDescription>PROVINCIAL RD BOWL SPOON</PartDescription> + <QuantityBackOrdered>6</QuantityBackOrdered> + <UnitPrice>17.98</UnitPrice> + <LineTotal>0.00</LineTotal> + </DetailLine> + <DetailLine> + <QuantityShipped>1</QuantityShipped> + <UnitOfMeasure>EA</UnitOfMeasure> + <PartNumber>DO 3218</PartNumber> + <PartDescription>34 OZ DUAL DIAL SCALE AM3218</PartDescription> + <UnitPrice>70.00</UnitPrice> + <DiscountPercentage>5.0</DiscountPercentage> + <LineTotal>66.50</LineTotal> + </DetailLine> + <DetailLine> + <QuantityShipped>1</QuantityShipped> + <UnitOfMeasure>CS</UnitOfMeasure> + <PartNumber>DM 195</PartNumber> + <PartDescription>20 OZ.BEER PUB GLASS</PartDescription> + <UnitPrice>55.90</UnitPrice> + <LineTotal>55.90</LineTotal> + </DetailLine> + </DetailLines> + <Totals> + <SubTotal>399.28</SubTotal> + <DiscountTotal>3.50</DiscountTotal> + <FreightTotal>23.75</FreightTotal> + <GSTTotal>29.61</GSTTotal> + <ProvTaxTotal>33.84</ProvTaxTotal> + <OtherTotal>33.84</OtherTotal> + <InvoiceTotal>486.48</InvoiceTotal> + </Totals> +</Invoice> + +</body> +</biztalk_1> diff --git a/src/zope/tal/tests/output/test03.html b/src/zope/tal/tests/output/test03.html new file mode 100644 index 0000000..7fb5156 --- /dev/null +++ b/src/zope/tal/tests/output/test03.html @@ -0,0 +1,9 @@ +<p> + <span> + <span>hello brave new world</span> + <span> + <span>goodbye cruel world</span> + </span> + <span>hello brave new world</span> + </span> +</p> diff --git a/src/zope/tal/tests/output/test03.xml b/src/zope/tal/tests/output/test03.xml new file mode 100644 index 0000000..24be638 --- /dev/null +++ b/src/zope/tal/tests/output/test03.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" ?> +<p> + <span> + <span>hello brave new world</span> + <span> + <span>goodbye cruel world</span> + </span> + <span>hello brave new world</span> + </span> +</p> diff --git a/src/zope/tal/tests/output/test04.html b/src/zope/tal/tests/output/test04.html new file mode 100644 index 0000000..f0666da --- /dev/null +++ b/src/zope/tal/tests/output/test04.html @@ -0,0 +1,38 @@ +<html> + + <body> + + <ul> + </ul> + + <span /> + + <ul> + <li> + 0 + hello world + </li> + <li> + 1 + hello world + </li> + </ul> + + <span /> + + <ul> + <li> + 0 + goodbye cruel world + </li> + <li> + 1 + goodbye cruel world + </li> + </ul> + + <p>define-slot</p> + + </body> + +</html> diff --git a/src/zope/tal/tests/output/test04.xml b/src/zope/tal/tests/output/test04.xml new file mode 100644 index 0000000..8b73d02 --- /dev/null +++ b/src/zope/tal/tests/output/test04.xml @@ -0,0 +1,39 @@ +<?xml version="1.0" ?> +<html> + + <body> + + <ul> + </ul> + + <span/> + + <ul> + <li> + 0 + hello world + </li> + <li> + 1 + hello world + </li> + </ul> + + <span/> + + <ul> + <li> + 0 + goodbye cruel world + </li> + <li> + 1 + goodbye cruel world + </li> + </ul> + + <p>define-slot</p> + + </body> + +</html> diff --git a/src/zope/tal/tests/output/test05.html b/src/zope/tal/tests/output/test05.html new file mode 100644 index 0000000..006851a --- /dev/null +++ b/src/zope/tal/tests/output/test05.html @@ -0,0 +1,9 @@ +<html> + + <body> + + <h1>This is the body of test5</h1> + + </body> + +</html> diff --git a/src/zope/tal/tests/output/test05.xml b/src/zope/tal/tests/output/test05.xml new file mode 100644 index 0000000..0bc2691 --- /dev/null +++ b/src/zope/tal/tests/output/test05.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" ?> +<html> + + <body> + + <h1>This is the body of test5</h1> + + </body> + +</html> diff --git a/src/zope/tal/tests/output/test06.html b/src/zope/tal/tests/output/test06.html new file mode 100644 index 0000000..d3f58d9 --- /dev/null +++ b/src/zope/tal/tests/output/test06.html @@ -0,0 +1,7 @@ +<html> + <body> + + <h1>This is the body of test5</h1> + + </body> +</html> diff --git a/src/zope/tal/tests/output/test06.xml b/src/zope/tal/tests/output/test06.xml new file mode 100644 index 0000000..b9ad4ac --- /dev/null +++ b/src/zope/tal/tests/output/test06.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" ?> +<html> + <body> + + <h1>This is the body of test5</h1> + + </body> +</html> diff --git a/src/zope/tal/tests/output/test07.html b/src/zope/tal/tests/output/test07.html new file mode 100644 index 0000000..e0b3d88 --- /dev/null +++ b/src/zope/tal/tests/output/test07.html @@ -0,0 +1,11 @@ +<table> +<!-- macro definition with slots --> + <tr> + <td>Top Left</td> + <td>Top Right</td> + </tr> + <tr> + <td>Bottom left</td> + <td><span>Bottom Right</span></td> + </tr> +</table> diff --git a/src/zope/tal/tests/output/test07.xml b/src/zope/tal/tests/output/test07.xml new file mode 100644 index 0000000..8884d97 --- /dev/null +++ b/src/zope/tal/tests/output/test07.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" ?> +<table> +<!-- macro definition with slots --> + <tr> + <td>Top Left</td> + <td>Top Right</td> + </tr> + <tr> + <td>Bottom left</td> + <td><span>Bottom Right</span></td> + </tr> +</table> diff --git a/src/zope/tal/tests/output/test08.html b/src/zope/tal/tests/output/test08.html new file mode 100644 index 0000000..06e01b2 --- /dev/null +++ b/src/zope/tal/tests/output/test08.html @@ -0,0 +1,47 @@ +<table> +<!-- macro definition with slots --> + <tr> + <td>Top Left</td> + <td>Top Right</td> + </tr> + <tr> + <td>Bottom left</td> + <td><span> + <h1>Some headline</h1> + <p>This is the real contents of the bottom right slot.</p> + <p>It is supposed to contain a lot of text. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb.</p> + <p>It is supposed to contain a lot of text. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb.</p> + <p>It is supposed to contain a lot of text. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb.</p> + </span></td> + </tr> +</table> diff --git a/src/zope/tal/tests/output/test08.xml b/src/zope/tal/tests/output/test08.xml new file mode 100644 index 0000000..51a969c --- /dev/null +++ b/src/zope/tal/tests/output/test08.xml @@ -0,0 +1,48 @@ +<?xml version="1.0" ?> +<table> +<!-- macro definition with slots --> + <tr> + <td>Top Left</td> + <td>Top Right</td> + </tr> + <tr> + <td>Bottom left</td> + <td><span> + <h1>Some headline</h1> + <p>This is the real contents of the bottom right slot.</p> + <p>It is supposed to contain a lot of text. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb.</p> + <p>It is supposed to contain a lot of text. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb.</p> + <p>It is supposed to contain a lot of text. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb.</p> + </span></td> + </tr> +</table> diff --git a/src/zope/tal/tests/output/test09.html b/src/zope/tal/tests/output/test09.html new file mode 100644 index 0000000..844c1a9 --- /dev/null +++ b/src/zope/tal/tests/output/test09.html @@ -0,0 +1,30 @@ +<html> +<body> +<p> + Just a bunch of text.</p> +<p>more text...</p> +<ul> + <li>first item</li> + <li>second item + + <ol> + <li>second list, first item</li> + <li>second list, second item + <dl compact> + <dt>term 1</dt> + <dt>term 2</dt> + <dd>definition</dd> + </dl></li> + </ol></li> + + <li>Now let's have a paragraph... + <p>My Paragraph</p> + </li> + + <li>And a table in a list item: + <table> + </table></li> +</ul> + +</body> +</html> diff --git a/src/zope/tal/tests/output/test09.xml b/src/zope/tal/tests/output/test09.xml new file mode 100644 index 0000000..c3d10d7 --- /dev/null +++ b/src/zope/tal/tests/output/test09.xml @@ -0,0 +1,30 @@ +<html> +<body> +<p> + Just a bunch of text.</p> +<p>more text...</p> +<ul> + <li>first item</li> + <li>second item + + <ol> + <li>second list, first item</li> + <li>second list, second item + <dl compact=""> + <dt>term 1</dt> + <dt>term 2</dt> + <dd>definition</dd> + </dl></li> + </ol></li> + + <li>Now let's have a paragraph... + <p>My Paragraph</p> + </li> + + <li>And a table in a list item: + <table> + </table></li> +</ul> + +</body> +</html> diff --git a/src/zope/tal/tests/output/test10.html b/src/zope/tal/tests/output/test10.html new file mode 100644 index 0000000..d9cc7ed --- /dev/null +++ b/src/zope/tal/tests/output/test10.html @@ -0,0 +1,51 @@ +<html><body> +<table> +<!-- macro definition with slots --> + <tr> + <td>Top Left</td> + <td>Top Right</td> + </tr> + <tr> + <td>Bottom left</td> + <td><span> + <h1>Some headline</h1> + <p>This is the real contents of the bottom right slot.</p> + <hr> + <p>It is supposed to contain a lot of text. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb.</p> + <p>It is supposed to contain a lot of text. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb.</p> + <p>It is supposed to contain a lot of text. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb.</p> + <br><br> + </span></td> + </tr> +</table> +</body></html> diff --git a/src/zope/tal/tests/output/test11.html b/src/zope/tal/tests/output/test11.html new file mode 100644 index 0000000..9e2223c --- /dev/null +++ b/src/zope/tal/tests/output/test11.html @@ -0,0 +1,8 @@ +<html> + <a href="http://www.python.org">bar</a> + <p>bad boy!</p> + <p>x undefined</p> + x undefined + x undefined + <hr /> +</html> diff --git a/src/zope/tal/tests/output/test11.xml b/src/zope/tal/tests/output/test11.xml new file mode 100644 index 0000000..caba039 --- /dev/null +++ b/src/zope/tal/tests/output/test11.xml @@ -0,0 +1,5 @@ +<html> + <a href="http://www.python.org">bar</a> + <p>bad boy!</p> + <p>x undefined</p> +</html> diff --git a/src/zope/tal/tests/output/test12.html b/src/zope/tal/tests/output/test12.html new file mode 100644 index 0000000..9533b42 --- /dev/null +++ b/src/zope/tal/tests/output/test12.html @@ -0,0 +1,24 @@ +<span /> + +<img ismap> +<img ismap="ismap"> +<img ismap="ismap"> +<img ismap="foo"> + +<img ismap="ismap"> +<img> +<img> + +<img ismap="ismap"> +<img> +<img> + +<img ismap="ismap"> +<img> +<img> + +<span /> + +<img src="foo"> +<img src="x.gif"> +<img> diff --git a/src/zope/tal/tests/output/test13.html b/src/zope/tal/tests/output/test13.html new file mode 100644 index 0000000..d68e0ce --- /dev/null +++ b/src/zope/tal/tests/output/test13.html @@ -0,0 +1,7 @@ +Here's a stray greater than: > + +<script> + <!-- no comment --> + <notag> + &noentity; +</script> diff --git a/src/zope/tal/tests/output/test14.html b/src/zope/tal/tests/output/test14.html new file mode 100644 index 0000000..b9bf468 --- /dev/null +++ b/src/zope/tal/tests/output/test14.html @@ -0,0 +1,13 @@ +<table> + <tr> + <td>car</td> + <td>bike</td> + <td>broomstick</td> + </tr> +</table> + +<p> + Harry + Ron + Hermione +</p> diff --git a/src/zope/tal/tests/output/test14.xml b/src/zope/tal/tests/output/test14.xml new file mode 100644 index 0000000..67c0c37 --- /dev/null +++ b/src/zope/tal/tests/output/test14.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" ?> +<html> + +<table> + <tr> + <td>car</td> + <td>bike</td> + <td>broomstick</td> + </tr> +</table> + +<p> + Harry + Ron + Hermione +</p> + +</html> diff --git a/src/zope/tal/tests/output/test15.html b/src/zope/tal/tests/output/test15.html new file mode 100644 index 0000000..314fd43 --- /dev/null +++ b/src/zope/tal/tests/output/test15.html @@ -0,0 +1,29 @@ +<span> + <span>INNERSLOT</span> +</span> + +<span> + <xxx>inner-argument</xxx> +</span> + +<div> +<span> + <xxx> + OUTERSLOT + </xxx> +</span> +</div> + +<div> +<span> + <div>outer-argument</div> +</span> +</div> + +<div> +<span> + <xxx> + OUTERSLOT + </xxx> +</span> +</div> diff --git a/src/zope/tal/tests/output/test16.html b/src/zope/tal/tests/output/test16.html new file mode 100644 index 0000000..d3ea228 --- /dev/null +++ b/src/zope/tal/tests/output/test16.html @@ -0,0 +1 @@ +<a href="/base/valid/link.html">blah, blah</a> diff --git a/src/zope/tal/tests/output/test16.xml b/src/zope/tal/tests/output/test16.xml new file mode 100644 index 0000000..77e9069 --- /dev/null +++ b/src/zope/tal/tests/output/test16.xml @@ -0,0 +1,6 @@ +<?xml version="1.0"?> +<body> + +<ImG href="foo" Alt="bar" alT="baz" Href="about:foo"/> + +</body> diff --git a/src/zope/tal/tests/output/test17.html b/src/zope/tal/tests/output/test17.html new file mode 100644 index 0000000..e50997d --- /dev/null +++ b/src/zope/tal/tests/output/test17.html @@ -0,0 +1,6 @@ +Yes +Yes +Yes + +Yes +Yes diff --git a/src/zope/tal/tests/output/test17.xml b/src/zope/tal/tests/output/test17.xml new file mode 100644 index 0000000..7a54cdb --- /dev/null +++ b/src/zope/tal/tests/output/test17.xml @@ -0,0 +1,9 @@ +<?xml version="1.0"?> +<body> +Yes +Yes +Yes + +Yes +Yes +</body> diff --git a/src/zope/tal/tests/output/test18.html b/src/zope/tal/tests/output/test18.html new file mode 100644 index 0000000..f49e29e --- /dev/null +++ b/src/zope/tal/tests/output/test18.html @@ -0,0 +1,16 @@ +Content + + + +Content + + + +<p>Content</p> +<p></p> +<img> + +Yes +Yes +Yes +Yes diff --git a/src/zope/tal/tests/output/test18.xml b/src/zope/tal/tests/output/test18.xml new file mode 100644 index 0000000..77eba02 --- /dev/null +++ b/src/zope/tal/tests/output/test18.xml @@ -0,0 +1,19 @@ +<?xml version="1.0"?> +<body> +Content + + + +Content + + + +<p>Content</p> +<p/> +<img/> + +Yes +Yes +Yes +Yes +</body> diff --git a/src/zope/tal/tests/output/test19.html b/src/zope/tal/tests/output/test19.html new file mode 100644 index 0000000..2341a4a --- /dev/null +++ b/src/zope/tal/tests/output/test19.html @@ -0,0 +1,3 @@ +<span>REPLACE THIS</span> +<span>MSGID</span> +<span>AND ANOTHER TRANSLATED STRING</span> diff --git a/src/zope/tal/tests/output/test19.xml b/src/zope/tal/tests/output/test19.xml new file mode 100644 index 0000000..4460acd --- /dev/null +++ b/src/zope/tal/tests/output/test19.xml @@ -0,0 +1,6 @@ +<?xml version="1.0"?> +<body> +<span>REPLACE THIS</span> +<span>MSGID</span> +<span>AND ANOTHER TRANSLATED STRING</span> +</body> diff --git a/src/zope/tal/tests/output/test20.html b/src/zope/tal/tests/output/test20.html new file mode 100644 index 0000000..606b989 --- /dev/null +++ b/src/zope/tal/tests/output/test20.html @@ -0,0 +1 @@ +<span>REPLACEABLE HERE</span> diff --git a/src/zope/tal/tests/output/test20.xml b/src/zope/tal/tests/output/test20.xml new file mode 100644 index 0000000..ed1f9fe --- /dev/null +++ b/src/zope/tal/tests/output/test20.xml @@ -0,0 +1,4 @@ +<?xml version="1.0"?> +<body> +<span>REPLACEABLE HERE</span> +</body> diff --git a/src/zope/tal/tests/output/test21.html b/src/zope/tal/tests/output/test21.html new file mode 100644 index 0000000..95b3b08 --- /dev/null +++ b/src/zope/tal/tests/output/test21.html @@ -0,0 +1 @@ +<span>Lomax WAS BORN IN Antarctica.</span> diff --git a/src/zope/tal/tests/output/test21.xml b/src/zope/tal/tests/output/test21.xml new file mode 100644 index 0000000..c373d52 --- /dev/null +++ b/src/zope/tal/tests/output/test21.xml @@ -0,0 +1,4 @@ +<?xml version="1.0"?> +<body> +<span>Lomax WAS BORN IN Antarctica.</span> +</body> diff --git a/src/zope/tal/tests/output/test22.html b/src/zope/tal/tests/output/test22.html new file mode 100644 index 0000000..6c1b6de --- /dev/null +++ b/src/zope/tal/tests/output/test22.html @@ -0,0 +1 @@ +<span><b>Jim</b> WAS BORN IN the USA.</span> diff --git a/src/zope/tal/tests/output/test22.xml b/src/zope/tal/tests/output/test22.xml new file mode 100644 index 0000000..c2e79c5 --- /dev/null +++ b/src/zope/tal/tests/output/test22.xml @@ -0,0 +1,6 @@ +<?xml version="1.0"?> +<body> + <span>content</span> + omit + replace +</body> diff --git a/src/zope/tal/tests/output/test23.html b/src/zope/tal/tests/output/test23.html new file mode 100644 index 0000000..0ea1654 --- /dev/null +++ b/src/zope/tal/tests/output/test23.html @@ -0,0 +1 @@ +<span>59 minutes after 6 PM</span> diff --git a/src/zope/tal/tests/output/test24.html b/src/zope/tal/tests/output/test24.html new file mode 100644 index 0000000..8dbfba3 --- /dev/null +++ b/src/zope/tal/tests/output/test24.html @@ -0,0 +1,7 @@ +<input name="DELETE_BUTTON"> + +<input name="MESSAGE-ID"> + +<input name="MESSAGE-ID" attr="INPUT-ATTR"> + +<input name="MESSAGE-ID" attr="INPUT-ATTR"> diff --git a/src/zope/tal/tests/output/test25.html b/src/zope/tal/tests/output/test25.html new file mode 100644 index 0000000..6b80bd3 --- /dev/null +++ b/src/zope/tal/tests/output/test25.html @@ -0,0 +1 @@ +<input name="DELETE"> diff --git a/src/zope/tal/tests/output/test26.html b/src/zope/tal/tests/output/test26.html new file mode 100644 index 0000000..9d179a6 --- /dev/null +++ b/src/zope/tal/tests/output/test26.html @@ -0,0 +1 @@ +<span>7 is the JOB NUMBER</span> diff --git a/src/zope/tal/tests/output/test27.html b/src/zope/tal/tests/output/test27.html new file mode 100644 index 0000000..96229e4 --- /dev/null +++ b/src/zope/tal/tests/output/test27.html @@ -0,0 +1 @@ +<p>Your contact email address is recorded as <a href="mailto:user@example.com">aperson@dom.ain</a></p> diff --git a/src/zope/tal/tests/output/test28.html b/src/zope/tal/tests/output/test28.html new file mode 100644 index 0000000..96229e4 --- /dev/null +++ b/src/zope/tal/tests/output/test28.html @@ -0,0 +1 @@ +<p>Your contact email address is recorded as <a href="mailto:user@example.com">aperson@dom.ain</a></p> diff --git a/src/zope/tal/tests/output/test29.html b/src/zope/tal/tests/output/test29.html new file mode 100644 index 0000000..886137e --- /dev/null +++ b/src/zope/tal/tests/output/test29.html @@ -0,0 +1 @@ +<div>AT THE TONE THE TIME WILL BE <span>59 minutes after 6 PM</span>... BEEP!</div> diff --git a/src/zope/tal/tests/output/test30.html b/src/zope/tal/tests/output/test30.html new file mode 100644 index 0000000..964b772 --- /dev/null +++ b/src/zope/tal/tests/output/test30.html @@ -0,0 +1 @@ +<p>Your contact email address is recorded as <a href="mailto:${request/submitter}">aperson@dom.ain</a></p> diff --git a/src/zope/tal/tests/output/test31.html b/src/zope/tal/tests/output/test31.html new file mode 100644 index 0000000..964b772 --- /dev/null +++ b/src/zope/tal/tests/output/test31.html @@ -0,0 +1 @@ +<p>Your contact email address is recorded as <a href="mailto:${request/submitter}">aperson@dom.ain</a></p> diff --git a/src/zope/tal/tests/output/test32.html b/src/zope/tal/tests/output/test32.html new file mode 100644 index 0000000..f39bd97 --- /dev/null +++ b/src/zope/tal/tests/output/test32.html @@ -0,0 +1 @@ +<span><span>Lomax</span> was born in <span>Antarctica</span></span> diff --git a/src/zope/tal/tests/output/test33.html b/src/zope/tal/tests/output/test33.html new file mode 100644 index 0000000..4472f21 --- /dev/null +++ b/src/zope/tal/tests/output/test33.html @@ -0,0 +1 @@ +<span>don't translate me</span> diff --git a/src/zope/tal/tests/output/test34.html b/src/zope/tal/tests/output/test34.html new file mode 100644 index 0000000..1d7b5f2 --- /dev/null +++ b/src/zope/tal/tests/output/test34.html @@ -0,0 +1,7 @@ +<span> + stuff + foobar + more stuff +</span> + +<span>STUFF foobar MORE STUFF</span> diff --git a/src/zope/tal/tests/output/test35.html b/src/zope/tal/tests/output/test35.html new file mode 100644 index 0000000..b1a9d2e --- /dev/null +++ b/src/zope/tal/tests/output/test35.html @@ -0,0 +1,6 @@ + + + + + + <h1>page</h1> diff --git a/src/zope/tal/tests/output/test36.html b/src/zope/tal/tests/output/test36.html new file mode 100644 index 0000000..2a563c1 --- /dev/null +++ b/src/zope/tal/tests/output/test36.html @@ -0,0 +1,2 @@ +<foo> +<span><foo> <bar /> <b>some</b> <i>text</i></span> diff --git a/src/zope/tal/tests/output/test_domain.html b/src/zope/tal/tests/output/test_domain.html new file mode 100644 index 0000000..6a282ac --- /dev/null +++ b/src/zope/tal/tests/output/test_domain.html @@ -0,0 +1,5 @@ +<div> +<span>replace this</span> +<span>msgid</span> +<span>and another translated string</span> +</div> diff --git a/src/zope/tal/tests/output/test_failed_attr_translation.html b/src/zope/tal/tests/output/test_failed_attr_translation.html new file mode 100644 index 0000000..cd34b1f --- /dev/null +++ b/src/zope/tal/tests/output/test_failed_attr_translation.html @@ -0,0 +1 @@ +<input value="don't translate me"> diff --git a/src/zope/tal/tests/output/test_metal1.html b/src/zope/tal/tests/output/test_metal1.html new file mode 100644 index 0000000..c8cc346 --- /dev/null +++ b/src/zope/tal/tests/output/test_metal1.html @@ -0,0 +1,79 @@ +<span metal:define-macro="OUTER"> + AAA + <span metal:define-macro="INNER">INNER</span> + BBB +</span> + +<span metal:use-macro="OUTER"> + AAA + <span>INNER</span> + BBB +</span> + +<span metal:use-macro="INNER">INNER</span> + +<span metal:define-macro="OUTER2"> + AAA + <xxx metal:define-slot="OUTERSLOT"> + <span metal:define-macro="INNER2">INNER</span> + </xxx> + BBB +</span> + +<span metal:use-macro="OUTER2"> + AAA + <xxx> + <span>INNER</span> + </xxx> + BBB +</span> + +<span metal:use-macro="INNER2">INNER</span> + +<span metal:use-macro="OUTER2"> + AAA + <yyy metal:fill-slot="OUTERSLOT">OUTERSLOT</yyy> + BBB +</span> + +<span metal:define-macro="OUTER3"> + AAA + <xxx metal:define-slot="OUTERSLOT"> + <span metal:define-macro="INNER3">INNER + <xxx metal:define-slot="INNERSLOT">INNERSLOT</xxx> + </span> + </xxx> + BBB +</span> + +<span metal:use-macro="OUTER3"> + AAA + <xxx> + <span>INNER + <xxx>INNERSLOT</xxx> + </span> + </xxx> + BBB +</span> + +<span metal:use-macro="OUTER3"> + AAA + <yyy metal:fill-slot="OUTERSLOT">OUTERSLOT</yyy> + BBB +</span> + +<span metal:use-macro="INNER3">INNER + <xxx>INNERSLOT</xxx> + </span> + +<span metal:use-macro="INNER3">INNER + <yyy metal:fill-slot="INNERSLOT">INNERSLOT</yyy> + </span> + +<span metal:use-macro="INNER3">INNER + <yyy metal:fill-slot="INNERSLOT"> + <zzz metal:define-macro="INSLOT">INSLOT</zzz> + </yyy> + </span> + +<zzz metal:use-macro="INSLOT">INSLOT</zzz> diff --git a/src/zope/tal/tests/output/test_metal2.html b/src/zope/tal/tests/output/test_metal2.html new file mode 100644 index 0000000..7e56c0c --- /dev/null +++ b/src/zope/tal/tests/output/test_metal2.html @@ -0,0 +1,11 @@ +<div metal:define-macro="OUTER"> + OUTER + <span metal:define-macro="INNER">INNER</span> + OUTER +</div> + +<div metal:use-macro="OUTER"> + OUTER + <span>INNER</span> + OUTER +</div> diff --git a/src/zope/tal/tests/output/test_metal3.html b/src/zope/tal/tests/output/test_metal3.html new file mode 100644 index 0000000..b0af907 --- /dev/null +++ b/src/zope/tal/tests/output/test_metal3.html @@ -0,0 +1 @@ +<span tal:attributes="class string:foo">Should not get attr in metal</span> diff --git a/src/zope/tal/tests/output/test_metal4.html b/src/zope/tal/tests/output/test_metal4.html new file mode 100644 index 0000000..dc774d3 --- /dev/null +++ b/src/zope/tal/tests/output/test_metal4.html @@ -0,0 +1,4 @@ +<!-- the outer element *must* be tal:something or metal:something --> +<metal:block define-macro="page" i18n:domain="zope"> + <title metal:define-slot="title">Z3 UI</title> +</metal:block> diff --git a/src/zope/tal/tests/output/test_metal5.html b/src/zope/tal/tests/output/test_metal5.html new file mode 100644 index 0000000..8bae3d8 --- /dev/null +++ b/src/zope/tal/tests/output/test_metal5.html @@ -0,0 +1,4 @@ +<!-- the outer element *must* include tal:omit-tag='' --> +<x tal:omit-tag="" metal:define-macro="page" i18n:domain="zope"> + <title metal:define-slot="title">Z3 UI</title> +</x> diff --git a/src/zope/tal/tests/output/test_metal6.html b/src/zope/tal/tests/output/test_metal6.html new file mode 100644 index 0000000..ce243f2 --- /dev/null +++ b/src/zope/tal/tests/output/test_metal6.html @@ -0,0 +1,5 @@ +<metal:block define-macro="page"> + <html i18:domain="zope"> + <metal:block define-slot="title">Z3 UI</metal:block> + </html> +</metal:block> diff --git a/src/zope/tal/tests/output/test_metal7.html b/src/zope/tal/tests/output/test_metal7.html new file mode 100644 index 0000000..cc449ed --- /dev/null +++ b/src/zope/tal/tests/output/test_metal7.html @@ -0,0 +1,6 @@ +<html metal:define-macro="page" i18n:domain="zope"> + <x metal:define-slot="title" /> +</html> +<html metal:use-macro="page" i18n:domain="zope"> + <x metal:fill-slot="title" /> +</html> diff --git a/src/zope/tal/tests/output/test_metal8.html b/src/zope/tal/tests/output/test_metal8.html new file mode 100644 index 0000000..d56adab --- /dev/null +++ b/src/zope/tal/tests/output/test_metal8.html @@ -0,0 +1,19 @@ +<html metal:define-macro="page" i18n:domain="zope"> +<body> +<div metal:define-macro="workspace"> +<div metal:define-slot="body"> +Default body +</div> +</div> +</body> +</html> + +<html metal:use-macro="page" i18n:domain="zope"> +<body> +<div> +<div metal:fill-slot="body"> +Filled-in body +</div> +</div> +</body> +</html> diff --git a/src/zope/tal/tests/output/test_metal9.html b/src/zope/tal/tests/output/test_metal9.html new file mode 100644 index 0000000..4cbc637 --- /dev/null +++ b/src/zope/tal/tests/output/test_metal9.html @@ -0,0 +1,32 @@ +<div metal:define-macro="macro1" i18n:domain="zope"> +<span metal:define-slot="slot1"> +Default for macro1 +</span> +</div> + +<div metal:define-macro="macro2" metal:use-macro="macro1" i18n:domain="zope"> +<span metal:fill-slot="slot1"> +Macro 2's slot 1 decoration +<span metal:define-slot="slot1"> +Default for macro2 +</span> +</span> +</div> + +<div metal:use-macro="macro2" i18n:domain="zope"> +<span metal:fill-slot="slot1"> +Macro 2's slot 1 decoration +<span> +Default for macro2 +</span> +</span> +</div> + +<div metal:use-macro="macro2" i18n:domain="zope"> +<span metal:fill-slot="slot1"> +Macro 2's slot 1 decoration +<span metal:fill-slot="slot1"> +Custom slot1 +</span> +</span> +</div> diff --git a/src/zope/tal/tests/output/test_sa1.html b/src/zope/tal/tests/output/test_sa1.html new file mode 100644 index 0000000..a37b9e9 --- /dev/null +++ b/src/zope/tal/tests/output/test_sa1.html @@ -0,0 +1,10 @@ +<!-- +============================================================================== +tests/input/test_sa1.html +============================================================================== +--><html> +<title>Simple test of source annotations</title> +<body> +<p>Foo!</p> +</body> +</html> diff --git a/src/zope/tal/tests/output/test_sa1.xml b/src/zope/tal/tests/output/test_sa1.xml new file mode 100644 index 0000000..8e1f4cc --- /dev/null +++ b/src/zope/tal/tests/output/test_sa1.xml @@ -0,0 +1,11 @@ +<?xml version="1.0" ?><!-- +============================================================================== +tests/input/test_sa1.xml +============================================================================== +--> +<html> +<title>Simple test of source annotations</title> +<body> +<p>Foo!</p> +</body> +</html> diff --git a/src/zope/tal/tests/output/test_sa2.html b/src/zope/tal/tests/output/test_sa2.html new file mode 100644 index 0000000..4709b49 --- /dev/null +++ b/src/zope/tal/tests/output/test_sa2.html @@ -0,0 +1,13 @@ +<!-- +============================================================================== +tests/input/test_sa2.html +============================================================================== +--><!DOCTYPE html + PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "DTD/xhtml1-transitional.dtd"> +<html> +<title>Simple test of source annotations</title> +<body> +<p>Foo!</p> +</body> +</html> diff --git a/src/zope/tal/tests/output/test_sa2.xml b/src/zope/tal/tests/output/test_sa2.xml new file mode 100644 index 0000000..30b5699 --- /dev/null +++ b/src/zope/tal/tests/output/test_sa2.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" ?><!-- +============================================================================== +tests/input/test_sa2.xml +============================================================================== +--> +<!DOCTYPE html + PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "DTD/xhtml1-transitional.dtd"> +<html> +<title>Simple test of source annotations</title> +<body> +<p>Foo!</p> +</body> +</html> diff --git a/src/zope/tal/tests/output/test_sa3.html b/src/zope/tal/tests/output/test_sa3.html new file mode 100644 index 0000000..8431438 --- /dev/null +++ b/src/zope/tal/tests/output/test_sa3.html @@ -0,0 +1,42 @@ +<!-- +============================================================================== +tests/input/test_sa3.html +============================================================================== +--><html> +<body> + <!-- +============================================================================== +tests/input/test_sa3.html (line 3) +============================================================================== +--><div>This is macro1 on sa3 line 3. + <span>This is slot1 on sa3 line 4.</span><!-- +============================================================================== +tests/input/test_sa3.html (line 4) +============================================================================== +--> + This is the end of macro1 on sa3 line 5. + </div> + <p>Some text on sa3 line 7.</p> + <!-- +============================================================================== +tests/input/test_sa3.html (line 3) +============================================================================== +--><div>This is macro1 on sa3 line 3. + <!-- +============================================================================== +tests/input/test_sa3.html (line 10) +============================================================================== +--><b>Text from sa3 line 10 is filled into slot1.</b><!-- +============================================================================== +tests/input/test_sa3.html (line 4) +============================================================================== +--> + This is the end of macro1 on sa3 line 5. + </div><!-- +============================================================================== +tests/input/test_sa3.html (line 12) +============================================================================== +--> + <p>This is some text on sa3 line 13.</p> +</body> +</html> diff --git a/src/zope/tal/tests/output/test_sa3.xml b/src/zope/tal/tests/output/test_sa3.xml new file mode 100644 index 0000000..bd20f83 --- /dev/null +++ b/src/zope/tal/tests/output/test_sa3.xml @@ -0,0 +1,43 @@ +<?xml version="1.0" ?><!-- +============================================================================== +tests/input/test_sa3.xml +============================================================================== +--> +<html> +<body> + <!-- +============================================================================== +tests/input/test_sa3.xml (line 4) +============================================================================== +--><div>This is macro1 on sa3 line 4. + <span>This is slot1 on sa3 line 5.</span><!-- +============================================================================== +tests/input/test_sa3.xml (line 5) +============================================================================== +--> + This is the end of macro1 on sa3 line 6. + </div> + <p>Some text on sa3 line 8.</p> + <!-- +============================================================================== +tests/input/test_sa3.xml (line 4) +============================================================================== +--><div>This is macro1 on sa3 line 4. + <!-- +============================================================================== +tests/input/test_sa3.xml (line 11) +============================================================================== +--><b>Text from sa3 line 11 is filled into slot1.</b><!-- +============================================================================== +tests/input/test_sa3.xml (line 5) +============================================================================== +--> + This is the end of macro1 on sa3 line 6. + </div><!-- +============================================================================== +tests/input/test_sa3.xml (line 13) +============================================================================== +--> + <p>This is some text on sa3 line 14.</p> +</body> +</html> diff --git a/src/zope/tal/tests/output/test_sa4.html b/src/zope/tal/tests/output/test_sa4.html new file mode 100644 index 0000000..4aca908 --- /dev/null +++ b/src/zope/tal/tests/output/test_sa4.html @@ -0,0 +1,30 @@ +<!-- +============================================================================== +tests/input/test_sa4.html +============================================================================== +--><html> +<body> + <p>Some text on sa4 line 3.</p> + <!-- +============================================================================== +tests/input/test_sa3.html (line 3) +============================================================================== +--><div>This is macro1 on sa3 line 3. + <!-- +============================================================================== +tests/input/test_sa4.html (line 6) +============================================================================== +--><b>Text from sa4 line 6 is filled into slot1.</b><!-- +============================================================================== +tests/input/test_sa3.html (line 4) +============================================================================== +--> + This is the end of macro1 on sa3 line 5. + </div><!-- +============================================================================== +tests/input/test_sa4.html (line 8) +============================================================================== +--> + <p>This is some text on sa4 line 9.</p> +</body> +</html> diff --git a/src/zope/tal/tests/run.py b/src/zope/tal/tests/run.py new file mode 100644 index 0000000..b4dab8c --- /dev/null +++ b/src/zope/tal/tests/run.py @@ -0,0 +1,45 @@ +#! /usr/bin/env python +############################################################################## +# +# Copyright (c) 2001, 2002 Zope Corporation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +"""Run all tests. + +$Id$ +""" +import sys +import unittest + +from zope.tal.tests import utils +from zope.tal.tests import test_htmltalparser +from zope.tal.tests import test_talinterpreter +from zope.tal.tests import test_files +from zope.tal.tests import test_sourcepos + +# TODO this code isn't picked up by the Zope 3 test framework.. +def test_suite(): + suite = unittest.TestSuite() + suite.addTest(test_htmltalparser.test_suite()) + if not utils.skipxml: + import test_xmlparser + suite.addTest(test_xmlparser.test_suite()) + suite.addTest(test_talinterpreter.test_suite()) + suite.addTest(test_files.test_suite()) + suite.addTest(test_sourcepos.test_suite()) + return suite + +def main(): + return utils.run_suite(test_suite()) + +if __name__ == "__main__": + errs = main() + sys.exit(errs and 1 or 0) diff --git a/src/zope/tal/tests/test_files.py b/src/zope/tal/tests/test_files.py new file mode 100644 index 0000000..e26f00a --- /dev/null +++ b/src/zope/tal/tests/test_files.py @@ -0,0 +1,90 @@ +############################################################################## +# +# Copyright (c) 2001, 2002 Zope Corporation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +"""Tests that run driver.py over input files comparing to output files. + +$Id$ +""" + +import glob +import os +import sys +import unittest + +import zope.tal.runtest + +from zope.tal.tests import utils + + +class FileTestCase(unittest.TestCase): + + def __init__(self, file, dir): + self.__file = file + self.__dir = dir + unittest.TestCase.__init__(self) + + # For unittest. + def shortDescription(self): + path = os.path.basename(self.__file) + return '%s (%s)' % (path, self.__class__) + + def runTest(self): + basename = os.path.basename(self.__file) + #sys.stdout.write(basename + " ") + sys.stdout.flush() + if basename.startswith('test_sa'): + sys.argv = ["", "-Q", "-a", self.__file] + elif basename.startswith('test_metal'): + sys.argv = ["", "-Q", "-m", self.__file] + else: + sys.argv = ["", "-Q", self.__file] + pwd = os.getcwd() + try: + try: + os.chdir(self.__dir) + zope.tal.runtest.main() + finally: + os.chdir(pwd) + except SystemExit, what: + if what.code: + self.fail("output for %s didn't match" % self.__file) + +try: + script = __file__ +except NameError: + script = sys.argv[0] + +def test_suite(): + suite = unittest.TestSuite() + dir = os.path.dirname(script) + dir = os.path.abspath(dir) + parentdir = os.path.dirname(dir) + prefix = os.path.join(dir, "input", "test*.") + if utils.skipxml: + xmlargs = [] + else: + xmlargs = glob.glob(prefix + "xml") + xmlargs.sort() + htmlargs = glob.glob(prefix + "html") + htmlargs.sort() + args = xmlargs + htmlargs + if not args: + sys.stderr.write("Warning: no test input files found!!!\n") + for arg in args: + case = FileTestCase(arg, parentdir) + suite.addTest(case) + return suite + +if __name__ == "__main__": + errs = utils.run_suite(test_suite()) + sys.exit(errs and 1 or 0) diff --git a/src/zope/tal/tests/test_htmltalparser.py b/src/zope/tal/tests/test_htmltalparser.py new file mode 100644 index 0000000..eb53f51 --- /dev/null +++ b/src/zope/tal/tests/test_htmltalparser.py @@ -0,0 +1,1021 @@ +############################################################################## +# +# Copyright (c) 2001, 2002 Zope Corporation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +"""Tests for the HTMLTALParser code generator. + +$Id$ +""" +import pprint +import sys +import unittest + +from zope.tal import htmltalparser, taldefs +from zope.tal.tests import utils + + +class TestCaseBase(unittest.TestCase): + + prologue = "" + epilogue = "" + initial_program = [('version', taldefs.TAL_VERSION), ('mode', 'html')] + final_program = [] + + def _merge(self, p1, p2): + if p1 and p2: + op1, args1 = p1[-1] + op2, args2 = p2[0] + if op1.startswith('rawtext') and op2.startswith('rawtext'): + return (p1[:-1] + + [rawtext(args1[0] + args2[0])] + + p2[1:]) + return p1+p2 + + def _run_check(self, source, program, macros={}): + parser = htmltalparser.HTMLTALParser() + parser.parseString(self.prologue + source + self.epilogue) + got_program, got_macros = parser.getCode() + program = self._merge(self.initial_program, program) + program = self._merge(program, self.final_program) + self.assert_(got_program == program, + "Program:\n" + pprint.pformat(got_program) + + "\nExpected:\n" + pprint.pformat(program)) + self.assert_(got_macros == macros, + "Macros:\n" + pprint.pformat(got_macros) + + "\nExpected:\n" + pprint.pformat(macros)) + + def _should_error(self, source, exc=taldefs.TALError): + def parse(self=self, source=source): + parser = htmltalparser.HTMLTALParser() + parser.parseString(self.prologue + source + self.epilogue) + self.assertRaises(exc, parse) + + +def rawtext(s): + """Compile raw text to the appropriate instruction.""" + if "\n" in s: + return ("rawtextColumn", (s, len(s) - (s.rfind("\n") + 1))) + else: + return ("rawtextOffset", (s, len(s))) + + +class HTMLTALParserTestCases(TestCaseBase): + + def test_code_simple_identity(self): + self._run_check("""<html a='b' b="c" c=d><title>My Title</html>""", [ + rawtext('<html a="b" b="c" c="d">' + '<title>My Title</title></html>'), + ]) + + def test_code_implied_list_closings(self): + self._run_check("""<ul><li><p><p><li></ul>""", [ + rawtext('<ul><li><p></p><p></p></li><li></li></ul>'), + ]) + self._run_check("""<dl><dt><dt><dd><dd><ol><li><li></ol></dl>""", [ + rawtext('<dl><dt></dt><dt></dt><dd></dd>' + '<dd><ol><li></li><li></li></ol></dd></dl>'), + ]) + + def test_code_implied_table_closings(self): + self._run_check("""<p>text <table><tr><th>head\t<tr><td>cell\t""" + """<table><tr><td>cell \n \t \n<tr>""", [ + rawtext('<p>text</p> <table><tr><th>head</th>' + '</tr>\t<tr><td>cell\t<table><tr><td>cell</td>' + '</tr> \n \t \n<tr></tr></table></td></tr></table>'), + ]) + self._run_check("""<table><tr><td>cell """ + """<table><tr><td>cell </table></table>""", [ + rawtext('<table><tr><td>cell <table><tr><td>cell</td></tr>' + ' </table></td></tr></table>'), + ]) + + def test_code_bad_nesting(self): + def check(self=self): + self._run_check("<a><b></a></b>", []) + self.assertRaises(htmltalparser.NestingError, check) + + def test_code_attr_syntax(self): + output = [ + rawtext('<a b="v" c="v" d="v" e></a>'), + ] + self._run_check("""<a b='v' c="v" d=v e>""", output) + self._run_check("""<a b = 'v' c = "v" d = v e>""", output) + self._run_check("""<a\nb\n=\n'v'\nc\n=\n"v"\nd\n=\nv\ne>""", output) + self._run_check("""<a\tb\t=\t'v'\tc\t=\t"v"\td\t=\tv\te>""", output) + + def test_code_attr_values(self): + self._run_check( + """<a b='xxx\n\txxx' c="yyy\t\nyyy" d='\txyz\n'>""", [ + rawtext('<a b="xxx\n\txxx" c="yyy\t\nyyy" d="\txyz\n"></a>')]) + self._run_check("""<a b='' c="">""", [ + rawtext('<a b="" c=""></a>'), + ]) + + def test_code_attr_entity_replacement(self): + # we expect entities *not* to be replaced by HTLMParser! + self._run_check("""<a b='&><"''>""", [ + rawtext('<a b="&><"\'"></a>'), + ]) + self._run_check("""<a b='\"'>""", [ + rawtext('<a b="""></a>'), + ]) + self._run_check("""<a b='&'>""", [ + rawtext('<a b="&"></a>'), + ]) + self._run_check("""<a b='<'>""", [ + rawtext('<a b="<"></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(""" """, [ + rawtext(' '), + ]) + + def test_code_short_endtags(self): + self._run_check("""<html><img/></html>""", [ + rawtext('<html><img /></html>'), + ]) + + +class METALGeneratorTestCases(TestCaseBase): + + def test_null(self): + self._run_check("", []) + + def test_define_macro(self): + macro = self.initial_program + [ + ('startTag', ('p', [('metal:define-macro', 'M', 'metal')])), + rawtext('booh</p>'), + ] + program = [ + ('setPosition', (1, 0)), + ('defineMacro', ('M', macro)), + ] + macros = {'M': macro} + self._run_check('<p metal:define-macro="M">booh</p>', program, macros) + + def test_use_macro(self): + self._run_check('<p metal:use-macro="M">booh</p>', [ + ('setPosition', (1, 0)), + ('useMacro', + ('M', '$M$', {}, + [('startTag', ('p', [('metal:use-macro', 'M', 'metal')])), + rawtext('booh</p>')])), + ]) + + def test_define_slot(self): + macro = self.initial_program + [ + ('startTag', ('p', [('metal:define-macro', 'M', 'metal')])), + rawtext('foo'), + ('setPosition', (1, 29)), + ('defineSlot', ('S', + [('startTag', ('span', [('metal:define-slot', 'S', 'metal')])), + rawtext('spam</span>')])), + rawtext('bar</p>'), + ] + program = [('setPosition', (1, 0)), + ('defineMacro', ('M', macro))] + macros = {'M': macro} + self._run_check('<p metal:define-macro="M">foo' + '<span metal:define-slot="S">spam</span>bar</p>', + program, macros) + + def test_fill_slot(self): + self._run_check('<p metal:use-macro="M">foo' + '<span metal:fill-slot="S">spam</span>bar</p>', [ + ('setPosition', (1, 0)), + ('useMacro', + ('M', '$M$', + {'S': [('startTag', ('span', + [('metal:fill-slot', 'S', 'metal')])), + rawtext('spam</span>')]}, + [('startTag', ('p', [('metal:use-macro', 'M', 'metal')])), + rawtext('foo'), + ('setPosition', (1, 26)), + ('fillSlot', ('S', + [('startTag', ('span', [('metal:fill-slot', 'S', 'metal')])), + rawtext('spam</span>')])), + rawtext('bar</p>')])), + ]) + + +class TALGeneratorTestCases(TestCaseBase): + + def test_null(self): + self._run_check("", []) + + def test_define_1(self): + self._run_check("<p tal:define='xyzzy string:spam'></p>", [ + ('setPosition', (1, 0)), + ('beginScope', {'tal:define': 'xyzzy string:spam'}), + ('setLocal', ('xyzzy', '$string:spam$')), + ('startTag', ('p', [('tal:define', 'xyzzy string:spam', 'tal')])), + ('endScope', ()), + rawtext('</p>'), + ]) + + def test_define_2(self): + self._run_check("<p tal:define='local xyzzy string:spam'></p>", [ + ('setPosition', (1, 0)), + ('beginScope', {'tal:define': 'local xyzzy string:spam'}), + ('setLocal', ('xyzzy', '$string:spam$')), + ('startTag', ('p', + [('tal:define', 'local xyzzy string:spam', 'tal')])), + ('endScope', ()), + rawtext('</p>'), + ]) + + def test_define_3(self): + self._run_check("<p tal:define='global xyzzy string:spam'></p>", [ + ('setPosition', (1, 0)), + ('beginScope', {'tal:define': 'global xyzzy string:spam'}), + ('setGlobal', ('xyzzy', '$string:spam$')), + ('startTag', ('p', + [('tal:define', 'global xyzzy string:spam', 'tal')])), + ('endScope', ()), + rawtext('</p>'), + ]) + + def test_define_4(self): + self._run_check("<p tal:define='x string:spam; y x'></p>", [ + ('setPosition', (1, 0)), + ('beginScope', {'tal:define': 'x string:spam; y x'}), + ('setLocal', ('x', '$string:spam$')), + ('setLocal', ('y', '$x$')), + ('startTag', ('p', [('tal:define', 'x string:spam; y x', 'tal')])), + ('endScope', ()), + rawtext('</p>'), + ]) + + def test_define_5(self): + self._run_check("<p tal:define='x string:;;;;; y x'></p>", [ + ('setPosition', (1, 0)), + ('beginScope', {'tal:define': 'x string:;;;;; y x'}), + ('setLocal', ('x', '$string:;;$')), + ('setLocal', ('y', '$x$')), + ('startTag', ('p', [('tal:define', 'x string:;;;;; y x', 'tal')])), + ('endScope', ()), + rawtext('</p>'), + ]) + + def test_define_6(self): + self._run_check( + "<p tal:define='x string:spam; global y x; local z y'></p>", [ + ('setPosition', (1, 0)), + ('beginScope', + {'tal:define': 'x string:spam; global y x; local z y'}), + ('setLocal', ('x', '$string:spam$')), + ('setGlobal', ('y', '$x$')), + ('setLocal', ('z', '$y$')), + ('startTag', ('p', + [('tal:define', 'x string:spam; global y x; local z y', 'tal')])), + ('endScope', ()), + rawtext('</p>'), + ]) + + def test_condition(self): + self._run_check( + "<p><span tal:condition='python:1'><b>foo</b></span></p>", [ + rawtext('<p>'), + ('setPosition', (1, 3)), + ('beginScope', {'tal:condition': 'python:1'}), + ('condition', ('$python:1$', + [('startTag', ('span', [('tal:condition', 'python:1', 'tal')])), + rawtext('<b>foo</b></span>')])), + ('endScope', ()), + rawtext('</p>'), + ]) + + def test_content_1(self): + self._run_check("<p tal:content='string:foo'>bar</p>", [ + ('setPosition', (1, 0)), + ('beginScope', {'tal:content': 'string:foo'}), + ('startTag', ('p', [('tal:content', 'string:foo', 'tal')])), + ('insertText', ('$string:foo$', [rawtext('bar')])), + ('endScope', ()), + rawtext('</p>'), + ]) + + def test_content_2(self): + self._run_check("<p tal:content='text string:foo'>bar</p>", [ + ('setPosition', (1, 0)), + ('beginScope', {'tal:content': 'text string:foo'}), + ('startTag', ('p', [('tal:content', 'text string:foo', 'tal')])), + ('insertText', ('$string:foo$', [rawtext('bar')])), + ('endScope', ()), + rawtext('</p>'), + ]) + + def test_content_3(self): + self._run_check("<p tal:content='structure string:<br>'>bar</p>", [ + ('setPosition', (1, 0)), + ('beginScope', {'tal:content': 'structure string:<br>'}), + ('startTag', ('p', + [('tal:content', 'structure string:<br>', 'tal')])), + ('insertStructure', + ('$string:<br>$', {}, [rawtext('bar')])), + ('endScope', ()), + rawtext('</p>'), + ]) + + def test_replace_1(self): + self._run_check("<p tal:replace='string:foo'>bar</p>", [ + ('setPosition', (1, 0)), + ('beginScope', {'tal:replace': 'string:foo'}), + ('optTag', + ('p', + '', + None, + 0, + [('startTag', ('p', [('tal:replace', 'string:foo', 'tal')]))], + [('insertText', ('$string:foo$', [('rawtextOffset', ('bar', 3))]))])), + ('endScope', ()), + ]) + + def test_replace_2(self): + self._run_check("<p tal:replace='text string:foo'>bar</p>", [ + ('setPosition', (1, 0)), + ('beginScope', {'tal:replace': 'text string:foo'}), + ('optTag', + ('p', + '', + None, + 0, + [('startTag', ('p', [('tal:replace', 'text string:foo', 'tal')]))], + [('insertText', ('$string:foo$', [('rawtextOffset', ('bar', 3))]))])), + ('endScope', ()), + ]) + + def test_replace_3(self): + self._run_check("<p tal:replace='structure string:<br>'>bar</p>", [ + ('setPosition', (1, 0)), + ('beginScope', {'tal:replace': 'structure string:<br>'}), + ('optTag', + ('p', + '', + None, + 0, + [('startTag', ('p', [('tal:replace', 'structure string:<br>', 'tal')]))], + [('insertStructure', + ('$string:<br>$', {}, [('rawtextOffset', ('bar', 3))]))])), + ('endScope', ()), + ]) + + def test_repeat(self): + self._run_check("<p tal:repeat='x python:(1,2,3)'>" + "<span tal:replace='x'>dummy</span></p>", [ + ('setPosition', (1, 0)), + ('beginScope', {'tal:repeat': 'x python:(1,2,3)'}), + ('loop', ('x', '$python:(1,2,3)$', + [('startTag', ('p', + [('tal:repeat', 'x python:(1,2,3)', 'tal')])), + ('setPosition', (1, 33)), + ('beginScope', {'tal:replace': 'x'}), + ('optTag', + ('span', + '', + None, + 0, + [('startTag', ('span', [('tal:replace', 'x', 'tal')]))], + [('insertText', ('$x$', [('rawtextOffset', ('dummy', 5))]))])), + ('endScope', ()), + rawtext('</p>')])), + ('endScope', ()), + ]) + + def test_script_1(self): + self._run_check('<p tal:script="text/server-python">code</p>', [ + ('setPosition', (1, 0)), + ('beginScope', {'tal:script': 'text/server-python'}), + ('startTag', ('p', + [('tal:script', 'text/server-python', 'tal')])), + ('evaluateCode', ('text/server-python', + [('rawtextOffset', ('code', 4))])), + ('endScope', ()), + rawtext('</p>'), + ]) + + def test_script_2(self): + self._run_check('<tal:block script="text/server-python">' + 'code' + '</tal:block>', [ + ('setPosition', (1, 0)), + ('beginScope', {'script': 'text/server-python'}), + ('optTag', + ('tal:block', + None, + 'tal', + 0, + [('startTag', ('tal:block', + [('script', 'text/server-python', 'tal')]))], + [('evaluateCode', + ('text/server-python', + [('rawtextOffset', ('code', 4))]))])), + ('endScope', ()) + ]) + + def test_script_3(self): + self._run_check('<script type="text/server-python">code</script>', [ + ('setPosition', (1, 0)), + ('beginScope', {}), + ('optTag', + ('script', + '', + None, + 0, + [('rawtextOffset', ('<script>', 8))], + [('evaluateCode', + ('text/server-python', [('rawtextOffset', ('code', 4))]))])), + ('endScope', ()) + ]) + + def test_script_4(self): + self._run_check('<script type="text/javascript">code</script>', [ + ('rawtextOffset', + ('<script type="text/javascript">code</script>', 44)) + ]) + + def test_attributes_1(self): + self._run_check("<a href='foo' name='bar' tal:attributes=" + "'href string:http://www.zope.org; x string:y'>" + "link</a>", [ + ('setPosition', (1, 0)), + ('beginScope', + {'tal:attributes': 'href string:http://www.zope.org; x string:y', + 'name': 'bar', 'href': 'foo'}), + ('startTag', ('a', + [('href', 'foo', 'replace', '$string:http://www.zope.org$', 0, None), + ('name', 'name="bar"'), + ('tal:attributes', + 'href string:http://www.zope.org; x string:y', 'tal'), + ('x', None, 'insert', '$string:y$', 0, None)])), + ('endScope', ()), + rawtext('link</a>'), + ]) + + def test_attributes_2(self): + self._run_check("<p tal:replace='structure string:<img>' " + "tal:attributes='src string:foo.png'>duh</p>", [ + ('setPosition', (1, 0)), + ('beginScope', + {'tal:attributes': 'src string:foo.png', + 'tal:replace': 'structure string:<img>'}), + ('optTag', + ('p', + '', + None, + 0, + [('startTag', + ('p', + [('tal:replace', 'structure string:<img>', 'tal'), + ('tal:attributes', 'src string:foo.png', 'tal')]))], + [('insertStructure', + ('$string:<img>$', + {'src': ('$string:foo.png$', False, None)}, + [('rawtextOffset', ('duh', 3))]))])), + ('endScope', ())]) + + def test_on_error_1(self): + self._run_check("<p tal:on-error='string:error' " + "tal:content='notHere'>okay</p>", [ + ('setPosition', (1, 0)), + ('beginScope', + {'tal:content': 'notHere', 'tal:on-error': 'string:error'}), + ('onError', + ([('startTag', ('p', + [('tal:on-error', 'string:error', 'tal'), + ('tal:content', 'notHere', 'tal')])), + ('insertText', ('$notHere$', [rawtext('okay')])), + rawtext('</p>')], + [('startTag', ('p', + [('tal:on-error', 'string:error', 'tal'), + ('tal:content', 'notHere', 'tal')])), + ('insertText', ('$string:error$', [])), + rawtext('</p>')])), + ('endScope', ()), + ]) + + def test_on_error_2(self): + self._run_check("<p tal:on-error='string:error' " + "tal:replace='notHere'>okay</p>", [ + ('setPosition', (1, 0)), + ('beginScope', + {'tal:replace': 'notHere', 'tal:on-error': 'string:error'}), + ('onError', + ([('optTag', + ('p', + '', + None, + 0, + [('startTag', + ('p', + [('tal:on-error', 'string:error', 'tal'), + ('tal:replace', 'notHere', 'tal')]))], + [('insertText', ('$notHere$', [('rawtextOffset', ('okay', 4))]))]))], + [('startTag', + ('p', + [('tal:on-error', 'string:error', 'tal'), + ('tal:replace', 'notHere', 'tal')])), + ('insertText', ('$string:error$', [])), + ('rawtextOffset', ('</p>', 4))])), + ('endScope', ()), + ]) + + def test_dup_attr(self): + self._should_error("<img tal:condition='x' tal:condition='x'>") + self._should_error("<img metal:define-macro='x' " + "metal:define-macro='x'>", taldefs.METALError) + + def test_tal_errors(self): + self._should_error("<p tal:define='x' />") + self._should_error("<p tal:repeat='x' />") + self._should_error("<p tal:foobar='x' />") + self._should_error("<p tal:replace='x' tal:content='x' />") + self._should_error("<p tal:replace='x'>") + for tag in htmltalparser.EMPTY_HTML_TAGS: + self._should_error("<%s tal:content='string:foo'>" % tag) + + def test_metal_errors(self): + exc = taldefs.METALError + self._should_error(2*"<p metal:define-macro='x'>xxx</p>", exc) + self._should_error("<html metal:use-macro='x'>" + + 2*"<p metal:fill-slot='y' />" + "</html>", exc) + self._should_error("<p metal:foobar='x' />", exc) + self._should_error("<p metal:define-macro='x'>", exc) + + def test_extend_macro_errors(self): + exc = taldefs.METALError + # extend-macro requires define-macro: + self._should_error("<p metal:extend-macro='x'>xxx</p>", exc) + # extend-macro prevents use-macro: + self._should_error("<p metal:extend-macro='x'" + " metal:use-macro='x'" + " metal:define-macro='y'>xxx</p>", exc) + # use-macro doesn't co-exist with define-macro: + self._should_error("<p metal:use-macro='x'" + " metal:define-macro='y'>xxx</p>", exc) + + # + # I18N test cases + # + + def test_i18n_attributes(self): + self._run_check("<img alt='foo' i18n:attributes='alt'>", [ + ('setPosition', (1, 0)), + ('beginScope', {'alt': 'foo', 'i18n:attributes': 'alt'}), + ('startTag', ('img', + [('alt', 'foo', 'replace', None, 1, None), + ('i18n:attributes', 'alt', 'i18n')])), + ('endScope', ()), + ]) + self._run_check("<img alt='foo' i18n:attributes='alt foo ; bar'>", [ + ('setPosition', (1, 0)), + ('beginScope', {'alt': 'foo', 'i18n:attributes': 'alt foo ; bar'}), + ('startTag', ('img', + [('alt', 'foo', 'replace', None, 1, 'foo'), + ('i18n:attributes', 'alt foo ; bar', 'i18n'), + ('bar', None, 'insert', None, 1, None)])), + ('endScope', ()), + ]) + + def test_i18n_name_bad_name(self): + self._should_error("<span i18n:name='not a valid name' />") + self._should_error("<span i18n:name='-bad-name' />") + + def test_i18n_attributes_repeated_attr(self): + self._should_error("<a i18n:attributes='href; href' />") + self._should_error("<a i18n:attributes='href; HREF' />") + + def test_i18n_translate(self): + # input/test19.html + self._run_check('''\ +<span i18n:translate="">Replace this</span> +<span i18n:translate="msgid">This is a +translated string</span> +<span i18n:translate="">And another +translated string</span> +''', [ + ('setPosition', (1, 0)), + ('beginScope', {'i18n:translate': ''}), + ('startTag', ('span', [('i18n:translate', '', 'i18n')])), + ('insertTranslation', ('', [('rawtextOffset', ('Replace this', 12))])), + ('rawtextBeginScope', + ('</span>\n', 0, (2, 0), 1, {'i18n:translate': 'msgid'})), + ('startTag', ('span', [('i18n:translate', 'msgid', 'i18n')])), + ('insertTranslation', + ('msgid', [('rawtextColumn', ('This is a\ntranslated string', 17))])), + ('rawtextBeginScope', ('</span>\n', 0, (4, 0), 1, {'i18n:translate': ''})), + ('startTag', ('span', [('i18n:translate', '', 'i18n')])), + ('insertTranslation', + ('', [('rawtextColumn', ('And another\ntranslated string', 17))])), + ('endScope', ()), + ('rawtextColumn', ('</span>\n', 0))]) + + def test_i18n_translate_with_nested_tal(self): + self._run_check('''\ +<span i18n:translate="">replaceable <p tal:replace="str:here">content</p></span> +''', [ + ('setPosition', (1, 0)), + ('beginScope', {'i18n:translate': ''}), + ('startTag', ('span', [('i18n:translate', '', 'i18n')])), + ('insertTranslation', + ('', + [('rawtextOffset', ('replaceable ', 12)), + ('setPosition', (1, 36)), + ('beginScope', {'tal:replace': 'str:here'}), + ('optTag', + ('p', + '', + None, + 0, + [('startTag', ('p', [('tal:replace', 'str:here', 'tal')]))], + [('insertText', + ('$str:here$', [('rawtextOffset', ('content', 7))]))])), + ('endScope', ())])), + ('endScope', ()), + ('rawtextColumn', ('</span>\n', 0)) + ]) + + def test_i18n_name(self): + # input/test21.html + self._run_check('''\ +<span i18n:translate=""> + <span tal:replace="str:Lomax" i18n:name="name" /> was born in + <span tal:replace="str:Antarctica" i18n:name="country" />. +</span> +''', [ + ('setPosition', (1, 0)), + ('beginScope', {'i18n:translate': ''}), + ('startTag', ('span', [('i18n:translate', '', 'i18n')])), + ('insertTranslation', + ('', + [('rawtextBeginScope', + ('\n ', + 2, + (2, 2), + 0, + {'i18n:name': 'name', 'tal:replace': 'str:Lomax'})), + ('i18nVariable', + ('name', + [('optTag', + ('span', + '', + None, + 1, + [('startEndTag', + ('span', + [('tal:replace', 'str:Lomax', 'tal'), + ('i18n:name', 'name', 'i18n')]))], + [('insertText', ('$str:Lomax$', []))]))], + None, + 0)), + ('rawtextBeginScope', + (' was born in\n ', + 2, + (3, 2), + 1, + {'i18n:name': 'country', 'tal:replace': 'str:Antarctica'})), + ('i18nVariable', + ('country', + [('optTag', + ('span', + '', + None, + 1, + [('startEndTag', + ('span', + [('tal:replace', 'str:Antarctica', 'tal'), + ('i18n:name', 'country', 'i18n')]))], + [('insertText', ('$str:Antarctica$', []))]))], + None, + 0)), + ('endScope', ()), + ('rawtextColumn', ('.\n', 0))])), + ('endScope', ()), + ('rawtextColumn', ('</span>\n', 0)) + ]) + + def test_i18n_name_with_content(self): + self._run_check('<div i18n:translate="">This is text for ' + '<span i18n:translate="" tal:content="bar" i18n:name="bar_name"/>.' + '</div>', [ +('setPosition', (1, 0)), +('beginScope', {'i18n:translate': ''}), +('startTag', ('div', [('i18n:translate', '', 'i18n')])), +('insertTranslation', + ('', + [('rawtextOffset', ('This is text for ', 17)), + ('setPosition', (1, 40)), + ('beginScope', + {'tal:content': 'bar', 'i18n:name': 'bar_name', 'i18n:translate': ''}), + ('i18nVariable', + ('bar_name', + [('startTag', + ('span', + [('i18n:translate', '', 'i18n'), + ('tal:content', 'bar', 'tal'), + ('i18n:name', 'bar_name', 'i18n')])), + ('insertI18nText', ('$bar$', [])), + ('rawtextOffset', ('</span>', 7))], + None, + 0)), + ('endScope', ()), + ('rawtextOffset', ('.', 1))])), +('endScope', ()), +('rawtextOffset', ('</div>', 6)) + ]) + + def test_i18n_name_implicit_value(self): + # input/test22.html + self._run_check('''\ +<span i18n:translate=""> + <span tal:omit-tag="" i18n:name="name"><b>Jim</b></span> was born in + <span tal:omit-tag="" i18n:name="country">the USA</span>. +</span> +''', [('setPosition', (1, 0)), + ('beginScope', {'i18n:translate': ''}), + ('startTag', ('span', [('i18n:translate', '', 'i18n')])), + ('insertTranslation', + ('', + [('rawtextBeginScope', + ('\n ', 2, (2, 2), 0, {'i18n:name': 'name', 'tal:omit-tag': ''})), + ('i18nVariable', + ('name', + [('optTag', + ('span', + '', + None, + 0, + [('startTag', + ('span', + [('tal:omit-tag', '', 'tal'), + ('i18n:name', 'name', 'i18n')]))], + [('rawtextOffset', ('<b>Jim</b>', 10))]))], + None, + 0)), + ('rawtextBeginScope', + (' was born in\n ', + 2, + (3, 2), + 1, + {'i18n:name': 'country', 'tal:omit-tag': ''})), + ('i18nVariable', + ('country', + [('optTag', + ('span', + '', + None, + 0, + [('startTag', + ('span', + [('tal:omit-tag', '', 'tal'), + ('i18n:name', 'country', 'i18n')]))], + [('rawtextOffset', ('the USA', 7))]))], + None, + 0)), + ('endScope', ()), + ('rawtextColumn', ('.\n', 0))])), + ('endScope', ()), + ('rawtextColumn', ('</span>\n', 0)) + ]) + + def test_i18n_context_domain(self): + self._run_check("<span i18n:domain='mydomain'/>", [ + ('setPosition', (1, 0)), + ('beginI18nContext', {'domain': 'mydomain', + 'source': None, 'target': None}), + ('beginScope', {'i18n:domain': 'mydomain'}), + ('startEndTag', ('span', [('i18n:domain', 'mydomain', 'i18n')])), + ('endScope', ()), + ('endI18nContext', ()), + ]) + + def test_i18n_context_source(self): + self._run_check("<span i18n:source='en'/>", [ + ('setPosition', (1, 0)), + ('beginI18nContext', {'source': 'en', + 'domain': 'default', 'target': None}), + ('beginScope', {'i18n:source': 'en'}), + ('startEndTag', ('span', [('i18n:source', 'en', 'i18n')])), + ('endScope', ()), + ('endI18nContext', ()), + ]) + + def test_i18n_context_source_target(self): + self._run_check("<span i18n:source='en' i18n:target='ru'/>", [ + ('setPosition', (1, 0)), + ('beginI18nContext', {'source': 'en', 'target': 'ru', + 'domain': 'default'}), + ('beginScope', {'i18n:source': 'en', 'i18n:target': 'ru'}), + ('startEndTag', ('span', [('i18n:source', 'en', 'i18n'), + ('i18n:target', 'ru', 'i18n')])), + ('endScope', ()), + ('endI18nContext', ()), + ]) + + def test_i18n_context_in_define_slot(self): + text = ("<div metal:use-macro='M' i18n:domain='mydomain'>" + "<div metal:fill-slot='S'>spam</div>" + "</div>") + self._run_check(text, [ + ('setPosition', (1, 0)), + ('useMacro', + ('M', '$M$', + {'S': [('startTag', ('div', + [('metal:fill-slot', 'S', 'metal')])), + rawtext('spam</div>')]}, + [('beginI18nContext', {'domain': 'mydomain', + 'source': None, 'target': None}), + ('beginScope', + {'i18n:domain': 'mydomain', 'metal:use-macro': 'M'}), + ('startTag', ('div', [('metal:use-macro', 'M', 'metal'), + ('i18n:domain', 'mydomain', 'i18n')])), + ('setPosition', (1, 48)), + ('fillSlot', ('S', + [('startTag', + ('div', [('metal:fill-slot', 'S', 'metal')])), + rawtext('spam</div>')])), + ('endScope', ()), + rawtext('</div>'), + ('endI18nContext', ())])), + ]) + + def test_i18n_data(self): + # input/test23.html + self._run_check('''\ +<span i18n:data="here/currentTime" + i18n:translate="timefmt">2:32 pm</span> +''', [ + ('setPosition', (1, 0)), + ('beginScope', + {'i18n:translate': 'timefmt', 'i18n:data': 'here/currentTime'}), + ('startTag', + ('span', + [('i18n:data', 'here/currentTime', 'i18n'), + ('i18n:translate', 'timefmt', 'i18n')])), + ('insertTranslation', + ('timefmt', [('rawtextOffset', ('2:32 pm', 7))], '$here/currentTime$')), + ('endScope', ()), + ('rawtextColumn', ('</span>\n', 0)) + ]) + + def test_i18n_data_with_name(self): + # input/test29.html + self._run_check('''\ +<div i18n:translate="">At the tone the time will be +<span i18n:data="here/currentTime" + i18n:translate="timefmt" + i18n:name="time">2:32 pm</span>... beep!</div> +''', [('setPosition', (1, 0)), + ('beginScope', {'i18n:translate': ''}), + ('startTag', ('div', [('i18n:translate', '', 'i18n')])), + ('insertTranslation', + ('', + [('rawtextBeginScope', + ('At the tone the time will be\n', + 0, + (2, 0), + 0, + {'i18n:data': 'here/currentTime', + 'i18n:name': 'time', + 'i18n:translate': 'timefmt'})), + ('i18nVariable', + ('time', + [('startTag', + ('span', + [('i18n:data', 'here/currentTime', 'i18n'), + ('i18n:translate', 'timefmt', 'i18n'), + ('i18n:name', 'time', 'i18n')])), + ('insertTranslation', + ('timefmt', + [('rawtextOffset', ('2:32 pm', 7))], + '$here/currentTime$')), + ('rawtextOffset', ('</span>', 7))], + None, + 0)), + ('endScope', ()), + ('rawtextOffset', ('... beep!', 9))])), + ('endScope', ()), + ('rawtextColumn', ('</div>\n', 0)) + ]) + + def test_i18n_name_around_tal_content(self): + # input/test28.html + self._run_check('''\ +<p i18n:translate="verify">Your contact email address is recorded as + <span tal:omit-tag="" i18n:name="email"> + <a href="mailto:user@example.com" + tal:content="request/submitter">user@host.com</a></span> +</p> +''', [('setPosition', (1, 0)), + ('beginScope', {'i18n:translate': 'verify'}), + ('startTag', ('p', [('i18n:translate', 'verify', 'i18n')])), + ('insertTranslation', + ('verify', + [('rawtextBeginScope', + ('Your contact email address is recorded as\n ', + 4, + (2, 4), + 0, + {'i18n:name': 'email', 'tal:omit-tag': ''})), + ('i18nVariable', + ('email', + [('optTag', + ('span', + '', + None, + 0, + [('startTag', + ('span', + [('tal:omit-tag', '', 'tal'), + ('i18n:name', 'email', 'i18n')]))], + [('rawtextBeginScope', + ('\n ', + 4, + (3, 4), + 0, + {'href': 'mailto:user@example.com', + 'tal:content': 'request/submitter'})), + ('startTag', + ('a', + [('href', 'href="mailto:user@example.com"'), + ('tal:content', 'request/submitter', 'tal')])), + ('insertText', + ('$request/submitter$', + [('rawtextOffset', ('user@host.com', 13))])), + ('endScope', ()), + ('rawtextOffset', ('</a>', 4))]))], + None, + 0)), + ('endScope', ()), + ('rawtextColumn', ('\n', 0))])), + ('endScope', ()), + ('rawtextColumn', ('</p>\n', 0)) + ]) + + def test_i18n_name_with_tal_content(self): + # input/test27.html + self._run_check('''\ +<p i18n:translate="verify">Your contact email address is recorded as + <a href="mailto:user@example.com" + tal:content="request/submitter" + i18n:name="email">user@host.com</a> +</p> +''', [ + ('setPosition', (1, 0)), + ('beginScope', {'i18n:translate': 'verify'}), + ('startTag', ('p', [('i18n:translate', 'verify', 'i18n')])), + ('insertTranslation', + ('verify', + [('rawtextBeginScope', + ('Your contact email address is recorded as\n ', + 4, + (2, 4), + 0, + {'href': 'mailto:user@example.com', + 'i18n:name': 'email', + 'tal:content': 'request/submitter'})), + ('i18nVariable', + ('email', + [('startTag', + ('a', + [('href', 'href="mailto:user@example.com"'), + ('tal:content', 'request/submitter', 'tal'), + ('i18n:name', 'email', 'i18n')])), + ('insertText', + ('$request/submitter$', + [('rawtextOffset', ('user@host.com', 13))])), + ('rawtextOffset', ('</a>', 4))], + None, + 0)), + ('endScope', ()), + ('rawtextColumn', ('\n', 0))])), + ('endScope', ()), + ('rawtextColumn', ('</p>\n', 0)) + ]) + + +def test_suite(): + suite = unittest.makeSuite(HTMLTALParserTestCases) + suite.addTest(unittest.makeSuite(METALGeneratorTestCases)) + suite.addTest(unittest.makeSuite(TALGeneratorTestCases)) + return suite + + +if __name__ == "__main__": + errs = utils.run_suite(test_suite()) + sys.exit(errs and 1 or 0) diff --git a/src/zope/tal/tests/test_sourcepos.py b/src/zope/tal/tests/test_sourcepos.py new file mode 100644 index 0000000..4034b21 --- /dev/null +++ b/src/zope/tal/tests/test_sourcepos.py @@ -0,0 +1,93 @@ +############################################################################## +# +# Copyright (c) 2001, 2002 Zope Corporation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +"""Tests for TALInterpreter. + +$Id$ +""" +import unittest + +from StringIO import StringIO + +from zope.tal.htmltalparser import HTMLTALParser +from zope.tal.talinterpreter import TALInterpreter +from zope.tal.talgenerator import TALGenerator +from zope.tal.dummyengine import DummyEngine + + +page1 = '''<html metal:use-macro="main"><body> +<div metal:fill-slot="body"> +page1=<span tal:replace="position:" /> +</div> +</body></html>''' + +main_template = '''<html metal:define-macro="main"><body> +main_template=<span tal:replace="position:" /> +<div metal:define-slot="body" /> +main_template=<span tal:replace="position:" /> +<div metal:use-macro="foot" /> +main_template=<span tal:replace="position:" /> +</body></html>''' + +footer = '''<div metal:define-macro="foot"> +footer=<span tal:replace="position:" /> +</div>''' + +expected = '''<html><body> +main_template=main_template (2,14) +<div> +page1=page1 (3,6) +</div> +main_template=main_template (4,14) +<div> +footer=footer (2,7) +</div> +main_template=main_template (6,14) +</body></html>''' + + + +class SourcePosTestCase(unittest.TestCase): + + def parse(self, eng, s, fn): + gen = TALGenerator(expressionCompiler=eng, xml=0, source_file=fn) + parser = HTMLTALParser(gen) + parser.parseString(s) + program, macros = parser.getCode() + return program, macros + + def test_source_positions(self): + # Ensure source file and position are set correctly by TAL + macros = {} + eng = DummyEngine(macros) + page1_program, page1_macros = self.parse(eng, page1, 'page1') + main_template_program, main_template_macros = self.parse( + eng, main_template, 'main_template') + footer_program, footer_macros = self.parse(eng, footer, 'footer') + + macros['main'] = main_template_macros['main'] + macros['foot'] = footer_macros['foot'] + + stream = StringIO() + interp = TALInterpreter(page1_program, macros, eng, stream) + interp() + self.assertEqual(stream.getvalue().strip(), expected.strip(), + "Got result:\n%s\nExpected:\n%s" + % (stream.getvalue(), expected)) + + +def test_suite(): + return unittest.makeSuite(SourcePosTestCase) + +if __name__ == "__main__": + unittest.main(defaultTest='test_suite') diff --git a/src/zope/tal/tests/test_talgettext.py b/src/zope/tal/tests/test_talgettext.py new file mode 100644 index 0000000..bdfbfd8 --- /dev/null +++ b/src/zope/tal/tests/test_talgettext.py @@ -0,0 +1,78 @@ +############################################################################## +# +# Copyright (c) 2001, 2002 Zope Corporation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +"""Tests for the talgettext utility. + +$Id$ +""" +import sys +import unittest +from StringIO import StringIO + +from zope.tal.htmltalparser import HTMLTALParser +from zope.tal.talgettext import POTALInterpreter +from zope.tal.talgettext import POEngine +from zope.tal.tests import utils + +class test_POEngine(unittest.TestCase): + """Test the PO engine functionality, which simply adds items to a catalog + as .translate is called + """ + + def test_translate(self): + test_keys = ['foo', 'bar', 'blarf', 'washington'] + + engine = POEngine() + engine.file = 'foo.pt' + for key in test_keys: + engine.translate(key, 'domain') + + for key in test_keys: + self.failIf(key not in engine.catalog['domain'], + "POEngine catalog does not properly store message ids" + ) + + def test_dynamic_msgids(self): + sample_source = """ + <p i18n:translate=""> + Some + <span tal:replace="string:strange">dynamic</span> + text. + </p> + <p i18n:translate=""> + A <a tal:attributes="href path:dynamic">link</a>. + </p> + """ + p = HTMLTALParser() + p.parseString(sample_source) + program, macros = p.getCode() + engine = POEngine() + engine.file = 'sample_source' + POTALInterpreter(program, macros, engine, stream=StringIO(), + metal=False)() + msgids = [] + for domain in engine.catalog.values(): + msgids += domain.keys() + msgids.sort() + self.assertEquals(msgids, + ['A <a href="${DYNAMIC_CONTENT}">link</a>.', + 'Some ${DYNAMIC_CONTENT} text.']) + + +def test_suite(): + suite = unittest.makeSuite(test_POEngine) + return suite + +if __name__ == "__main__": + errs = utils.run_suite(test_suite()) + sys.exit(errs and 1 or 0) diff --git a/src/zope/tal/tests/test_talinterpreter.py b/src/zope/tal/tests/test_talinterpreter.py new file mode 100644 index 0000000..c9e8ed7 --- /dev/null +++ b/src/zope/tal/tests/test_talinterpreter.py @@ -0,0 +1,853 @@ +# -*- coding: ISO-8859-1 -*- +############################################################################## +# +# Copyright (c) 2001, 2002 Zope Corporation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +"""Tests for TALInterpreter. + +$Id$ +""" +import os +import sys +import unittest + +from StringIO import StringIO + +from zope.tal.taldefs import METALError, I18NError, TAL_VERSION +from zope.tal.taldefs import TALExpressionError +from zope.tal.htmltalparser import HTMLTALParser +from zope.tal.talparser import TALParser +from zope.tal.talinterpreter import TALInterpreter +from zope.tal.talgenerator import TALGenerator +from zope.tal.dummyengine import DummyEngine +from zope.tal.dummyengine import MultipleDomainsDummyEngine +from zope.tal.dummyengine import DummyTranslationDomain +from zope.tal.tests import utils +from zope.i18nmessageid import Message + + +class TestCaseBase(unittest.TestCase): + + def _compile(self, source, source_file=None): + generator = TALGenerator(xml=0, source_file=source_file) + parser = HTMLTALParser(generator) + parser.parseString(source) + program, macros = parser.getCode() + return program, macros + + +class MacroErrorsTestCase(TestCaseBase): + + def setUp(self): + dummy, macros = self._compile('<p metal:define-macro="M">Booh</p>') + self.macro = macros['M'] + self.engine = DummyEngine(macros) + program, dummy = self._compile('<p metal:use-macro="M">Bah</p>') + self.interpreter = TALInterpreter(program, {}, self.engine) + + def tearDown(self): + try: + self.interpreter() + except METALError: + pass + else: + self.fail("Expected METALError") + + def test_mode_error(self): + self.macro[1] = ("mode", "duh") + + def test_version_error(self): + self.macro[0] = ("version", "duh") + + +class MacroFunkyErrorTest(TestCaseBase): + + def test_div_in_p_using_macro(self): + dummy, macros = self._compile('<p metal:define-macro="M">Booh</p>') + engine = DummyEngine(macros) + program, dummy = self._compile( + '<p metal:use-macro="M"><div>foo</div></p>') + interpreter = TALInterpreter(program, {}, engine) + + output = interpreter() + self.assertEqual(output, '<p><div>foo</div></p>') + + +class MacroExtendTestCase(TestCaseBase): + + def setUp(self): + s = self._read(('input', 'pnome_template.pt')) + self.pnome_program, pnome_macros = self._compile(s) + s = self._read(('input', 'acme_template.pt')) + self.acme_program, acme_macros = self._compile(s) + s = self._read(('input', 'document_list.pt')) + self.doclist_program, doclist_macros = self._compile(s) + macros = { + 'pnome_macros_page': pnome_macros['page'], + 'acme_macros_page': acme_macros['page'], + } + self.engine = DummyEngine(macros) + + def _read(self, path): + dir = os.path.dirname(__file__) + fn = os.path.join(dir, *path) + f = open(fn) + data = f.read() + f.close() + return data + + def test_preview_acme_template(self): + # An ACME designer is previewing the ACME design. For the + # purposes of this use case, extending a macro should act the + # same as using a macro. + result = StringIO() + interpreter = TALInterpreter( + self.acme_program, {}, self.engine, stream=result) + interpreter() + actual = result.getvalue().strip() + expected = self._read(('output', 'acme_template.html')).strip() + self.assertEqual(actual, expected) + + def test_preview_acme_template_source(self): + # Render METAL attributes in acme_template + result = StringIO() + interpreter = TALInterpreter( + self.acme_program, {}, self.engine, stream=result, tal=False) + interpreter() + actual = result.getvalue().strip() + expected = self._read(('output', 'acme_template_source.html')).strip() + self.assertEqual(actual, expected) + + +class I18NCornerTestCaseBase(TestCaseBase): + + def factory(self, msgid, default, mapping={}): + raise NotImplementedError("abstract method") + + def setUp(self): + self.engine = DummyEngine() + # Make sure we'll translate the msgid not its unicode representation + self.engine.setLocal('foo', + self.factory('FoOvAlUe${empty}', 'default', {'empty': ''})) + self.engine.setLocal('bar', 'BaRvAlUe') + + def _check(self, program, expected): + result = StringIO() + self.interpreter = TALInterpreter(program, {}, self.engine, + stream=result) + self.interpreter() + self.assertEqual(expected, result.getvalue()) + + def test_simple_messageid_translate(self): + # This test is mainly here to make sure our DummyEngine works + # correctly. + program, macros = self._compile( + '<span i18n:translate="" tal:content="foo"/>') + self._check(program, '<span>FOOVALUE</span>\n') + + program, macros = self._compile( + '<span i18n:translate="" tal:replace="foo"/>') + self._check(program, 'FOOVALUE\n') + + # i18n messages defined in Python are translated automatically + # (no i18n:translate necessary) + program, macros = self._compile( + '<span tal:content="foo" />') + self._check(program, '<span>FOOVALUE</span>\n') + + program, macros = self._compile( + '<span tal:replace="foo" />') + self._check(program, 'FOOVALUE\n') + + def test_attributes_translation(self): + program, macros = self._compile( + '<span tal:attributes="test bar"/>') + self._check(program, '<span test="BaRvAlUe" />\n') + + program, macros = self._compile( + '<span test="bar" i18n:attributes="test"/>') + self._check(program, '<span test="BAR" />\n') + + program, macros = self._compile( + '<span tal:attributes="test bar" i18n:attributes="test"/>') + self._check(program, '<span test="BARVALUE" />\n') + + # i18n messages defined in Python are translated automatically + # (no i18n:attributes necessary) + program, macros = self._compile( + '<span tal:attributes="test foo"/>') + self._check(program, '<span test="FOOVALUE" />\n') + + def test_text_variable_translate(self): + program, macros = self._compile( + '<span tal:content="bar"/>') + self._check(program, '<span>BaRvAlUe</span>\n') + + program, macros = self._compile( + '<span i18n:translate="" tal:content="bar"/>') + self._check(program, '<span>BARVALUE</span>\n') + + program, macros = self._compile( + '<span i18n:translate="" tal:replace="bar"/>') + self._check(program, 'BARVALUE\n') + + def test_text_translate(self): + program, macros = self._compile( + '<span tal:content="string:BaR"/>') + self._check(program, '<span>BaR</span>\n') + + program, macros = self._compile( + '<span i18n:translate="" tal:content="string:BaR"/>') + self._check(program, '<span>BAR</span>\n') + + program, macros = self._compile( + '<span i18n:translate="" tal:replace="string:BaR"/>') + self._check(program, 'BAR\n') + + def test_structure_text_variable_translate(self): + program, macros = self._compile( + '<span tal:content="structure bar"/>') + self._check(program, '<span>BaRvAlUe</span>\n') + + program, macros = self._compile( + '<span i18n:translate="" tal:content="structure bar"/>') + self._check(program, '<span>BARVALUE</span>\n') + + program, macros = self._compile( + '<span i18n:translate="" tal:replace="structure bar"/>') + self._check(program, 'BARVALUE\n') + + # i18n messages defined in Python are translated automatically + # (no i18n:translate necessary) + program, macros = self._compile( + '<span tal:content="structure foo"/>') + self._check(program, '<span>FOOVALUE</span>\n') + + program, macros = self._compile( + '<span tal:replace="structure foo"/>') + self._check(program, 'FOOVALUE\n') + + def test_structure_text_translate(self): + program, macros = self._compile( + '<span tal:content="structure string:BaR"/>') + self._check(program, '<span>BaR</span>\n') + + program, macros = self._compile( + '<span i18n:translate="" tal:content="structure string:BaR"/>') + self._check(program, '<span>BAR</span>\n') + + program, macros = self._compile( + '<span i18n:translate="" tal:replace="structure string:BaR"/>') + self._check(program, 'BAR\n') + + def test_replace_with_messageid_and_i18nname(self): + program, macros = self._compile( + '<div i18n:translate="" >' + '<span i18n:translate="" tal:replace="foo" i18n:name="foo_name"/>' + '</div>') + self._check(program, '<div>FOOVALUE</div>\n') + + def test_pythonexpr_replace_with_messageid_and_i18nname(self): + program, macros = self._compile( + '<div i18n:translate="" >' + '<span i18n:translate="" tal:replace="python: foo"' + ' i18n:name="foo_name"/>' + '</div>') + self._check(program, '<div>FOOVALUE</div>\n') + + def test_structure_replace_with_messageid_and_i18nname(self): + program, macros = self._compile( + '<div i18n:translate="" >' + '<span i18n:translate="" tal:replace="structure foo"' + ' i18n:name="foo_name"/>' + '</div>') + self._check(program, '<div>FOOVALUE</div>\n') + + def test_complex_replace_with_messageid_and_i18nname(self): + program, macros = self._compile( + '<div i18n:translate="" >' + '<em tal:omit-tag="" i18n:name="foo_name">' + '<span i18n:translate="" tal:replace="foo"/>' + '</em>' + '</div>') + self._check(program, '<div>FOOVALUE</div>\n') + + def test_content_with_messageid_and_i18nname(self): + program, macros = self._compile( + '<div i18n:translate="" >' + '<span i18n:translate="" tal:content="foo" i18n:name="foo_name"/>' + '</div>') + self._check(program, '<div><span>FOOVALUE</span></div>\n') + + def test_content_with_messageid_and_i18nname_and_i18ntranslate(self): + # Let's tell the user this is incredibly silly! + self.assertRaises( + I18NError, self._compile, + '<span i18n:translate="" tal:content="foo" i18n:name="foo_name"/>') + + def test_content_with_explicit_messageid(self): + # Let's tell the user this is incredibly silly! + self.assertRaises( + I18NError, self._compile, + '<span i18n:translate="ID" tal:content="foo" />') + + def test_content_with_plaintext_and_i18nname_and_i18ntranslate(self): + # Let's tell the user this is incredibly silly! + self.assertRaises( + I18NError, self._compile, + '<span i18n:translate="" i18n:name="color_name">green</span>') + + def test_translate_static_text_as_dynamic(self): + program, macros = self._compile( + '<div i18n:translate="">This is text for ' + '<span tal:content="bar" i18n:name="bar_name"/>.' + '</div>') + self._check(program, + '<div>THIS IS TEXT FOR <span>BaRvAlUe</span>.</div>\n') + program, macros = self._compile( + '<div i18n:translate="">This is text for ' + '<span i18n:translate="" tal:content="bar" i18n:name="bar_name"/>.' + '</div>') + self._check(program, + '<div>THIS IS TEXT FOR <span>BARVALUE</span>.</div>\n') + + def test_translate_static_text_as_dynamic_from_bytecode(self): + program = [('version', TAL_VERSION), + ('mode', 'html'), +('setPosition', (1, 0)), +('beginScope', {'i18n:translate': ''}), +('startTag', ('div', [('i18n:translate', '', 'i18n')])), +('insertTranslation', + ('', + [('rawtextOffset', ('This is text for ', 17)), + ('setPosition', (1, 40)), + ('beginScope', + {'tal:content': 'bar', 'i18n:name': 'bar_name', 'i18n:translate': ''}), + ('i18nVariable', + ('bar_name', + [('startTag', + ('span', + [('i18n:translate', '', 'i18n'), + ('tal:content', 'bar', 'tal'), + ('i18n:name', 'bar_name', 'i18n')])), + ('insertTranslation', + ('', + [('insertText', ('$bar$', []))])), + ('rawtextOffset', ('</span>', 7))], + None, + 0)), + ('endScope', ()), + ('rawtextOffset', ('.', 1))])), +('endScope', ()), +('rawtextOffset', ('</div>', 6)) +] + self._check(program, + '<div>THIS IS TEXT FOR <span>BARVALUE</span>.</div>\n') + + def test_for_correct_msgids(self): + self.engine.translationDomain.clearMsgids() + result = StringIO() + #GChapelle: + #I have the feeling the i18n:translate with the i18n:name is wrong + # + #program, macros = self._compile( + # '<div i18n:translate="">This is text for ' + # '<span i18n:translate="" tal:content="bar" ' + # 'i18n:name="bar_name"/>.</div>') + program, macros = self._compile( + '<div i18n:translate="">This is text for ' + '<span tal:content="bar" ' + 'i18n:name="bar_name"/>.</div>') + self.interpreter = TALInterpreter(program, {}, self.engine, + stream=result) + self.interpreter() + msgids = self.engine.translationDomain.getMsgids('default') + msgids.sort() + self.assertEqual(1, len(msgids)) + self.assertEqual('This is text for ${bar_name}.', msgids[0][0]) + self.assertEqual({'bar_name': '<span>BaRvAlUe</span>'}, msgids[0][1]) + self.assertEqual( + '<div>THIS IS TEXT FOR <span>BaRvAlUe</span>.</div>\n', + result.getvalue()) + + def test_for_correct_msgids_translate_name(self): + self.engine.translationDomain.clearMsgids() + result = StringIO() + program, macros = self._compile( + '<div i18n:translate="">This is text for ' + '<span i18n:translate="" tal:content="bar" ' + 'i18n:name="bar_name"/>.</div>') + self.interpreter = TALInterpreter(program, {}, self.engine, + stream=result) + self.interpreter() + msgids = self.engine.translationDomain.getMsgids('default') + msgids.sort() + self.assertEqual(2, len(msgids)) + self.assertEqual('This is text for ${bar_name}.', msgids[1][0]) + self.assertEqual({'bar_name': '<span>BARVALUE</span>'}, msgids[1][1]) + self.assertEqual( + '<div>THIS IS TEXT FOR <span>BARVALUE</span>.</div>\n', + result.getvalue()) + + def test_i18ntranslate_i18nname_and_attributes(self): + # Test for Issue 301: Bug with i18n:name and i18n:translate + # on the same element + self.engine.translationDomain.clearMsgids() + result = StringIO() + program, macros = self._compile( + '<p i18n:translate="">' + 'Some static text and a <a tal:attributes="href string:url"' + ' i18n:name="link" i18n:translate="">link text</a>.</p>') + self.interpreter = TALInterpreter(program, {}, self.engine, + stream=result) + self.interpreter() + msgids = self.engine.translationDomain.getMsgids('default') + msgids.sort() + self.assertEqual(2, len(msgids)) + self.assertEqual('Some static text and a ${link}.', msgids[0][0]) + self.assertEqual({'link': '<a href="url">LINK TEXT</a>'}, msgids[0][1]) + self.assertEqual('link text', msgids[1][0]) + self.assertEqual( + '<p>SOME STATIC TEXT AND A <a href="url">LINK TEXT</a>.</p>\n', + result.getvalue()) + + def test_for_raw_msgids(self): + # Test for Issue 314: i18n:translate removes line breaks from + # <pre>...</pre> contents + # HTML mode + self.engine.translationDomain.clearMsgids() + result = StringIO() + program, macros = self._compile( + '<div i18n:translate=""> This is text\n' + ' \tfor\n div. </div>' + '<pre i18n:translate=""> This is text\n' + ' <b>\tfor</b>\n pre. </pre>') + self.interpreter = TALInterpreter(program, {}, self.engine, + stream=result) + self.interpreter() + msgids = self.engine.translationDomain.getMsgids('default') + msgids.sort() + self.assertEqual(2, len(msgids)) + self.assertEqual(' This is text\n <b>\tfor</b>\n pre. ', msgids[0][0]) + self.assertEqual('This is text for div.', msgids[1][0]) + self.assertEqual( + '<div>THIS IS TEXT FOR DIV.</div>' + '<pre> THIS IS TEXT\n <B>\tFOR</B>\n PRE. </pre>\n', + result.getvalue()) + + # XML mode + self.engine.translationDomain.clearMsgids() + result = StringIO() + parser = TALParser() + parser.parseString( + '<?xml version="1.0"?>\n' + '<pre xmlns:i18n="http://xml.zope.org/namespaces/i18n"' + ' i18n:translate=""> This is text\n' + ' <b>\tfor</b>\n barvalue. </pre>') + program, macros = parser.getCode() + self.interpreter = TALInterpreter(program, {}, self.engine, + stream=result) + self.interpreter() + msgids = self.engine.translationDomain.getMsgids('default') + msgids.sort() + self.assertEqual(1, len(msgids)) + self.assertEqual('This is text <b> for</b> barvalue.', msgids[0][0]) + self.assertEqual( + '<?xml version="1.0"?>\n' + '<pre>THIS IS TEXT <B> FOR</B> BARVALUE.</pre>\n', + result.getvalue()) + + def test_raw_msgids_and_i18ntranslate_i18nname(self): + self.engine.translationDomain.clearMsgids() + result = StringIO() + program, macros = self._compile( + '<div i18n:translate=""> This is text\n \tfor\n' + '<pre i18n:name="bar" i18n:translate=""> \tbar\n </pre>.</div>') + self.interpreter = TALInterpreter(program, {}, self.engine, + stream=result) + self.interpreter() + msgids = self.engine.translationDomain.getMsgids('default') + msgids.sort() + self.assertEqual(2, len(msgids)) + self.assertEqual(' \tbar\n ', msgids[0][0]) + self.assertEqual('This is text for ${bar}.', msgids[1][0]) + self.assertEqual({'bar': '<pre> \tBAR\n </pre>'}, msgids[1][1]) + self.assertEqual( + u'<div>THIS IS TEXT FOR <pre> \tBAR\n </pre>.</div>\n', + result.getvalue()) + + def test_for_handling_unicode_vars(self): + # Make sure that non-ASCII Unicode is substituted correctly. + # http://collector.zope.org/Zope3-dev/264 + program, macros = self._compile( + "<div i18n:translate='' tal:define='bar python:unichr(0xC0)'>" + "Foo <span tal:replace='bar' i18n:name='bar' /></div>") + self._check(program, u"<div>FOO \u00C0</div>\n") + +class I18NCornerTestCaseMessage(I18NCornerTestCaseBase): + + def factory(self, msgid, default=None, mapping={}, domain=None): + return Message(msgid, domain=domain, default=default, mapping=mapping) + +class UnusedExplicitDomainTestCase(I18NCornerTestCaseMessage): + + def setUp(self): + # MultipleDomainsDummyEngine is a Engine + # where default domain transforms to uppercase + self.engine = MultipleDomainsDummyEngine() + self.engine.setLocal('foo', + self.factory('FoOvAlUe${empty}', 'default', {'empty': ''})) + self.engine.setLocal('bar', 'BaRvAlUe') + self.engine.setLocal('baz', + self.factory('BaZvAlUe', 'default', {})) + # Message ids with different domains + self.engine.setLocal('toupper', + self.factory('ToUpper', 'default', {})) + self.engine.setLocal('tolower', + self.factory('ToLower', 'default', {}, domain='lower')) + + def test_multiple_domains(self): + program, macros = self._compile( + '<div i18n:translate=""' + ' tal:content="toupper" />') + self._check(program, '<div>TOUPPER</div>\n') + program, macros = self._compile( + '<div i18n:translate=""' + ' tal:content="tolower" />') + self._check(program, '<div>tolower</div>\n') + program, macros = self._compile( + '<div i18n:translate=""' + ' tal:content="string:ToUpper" />') + self._check(program, '<div>TOUPPER</div>\n') + program, macros = self._compile( + '<div i18n:translate=""' + ' i18n:domain="lower"' + ' tal:content="string:ToLower" />') + self._check(program, '<div>tolower</div>\n') + program, macros = self._compile( + '<div i18n:translate=""' + ' tal:define="msgid string:ToUpper"' + ' tal:content="msgid" />') + self._check(program, '<div>TOUPPER</div>\n') + program, macros = self._compile( + '<div i18n:translate=""' + ' i18n:domain="lower"' + ' tal:define="msgid string:ToLower"' + ' tal:content="msgid" />') + self._check(program, '<div>tolower</div>\n') + + def test_unused_explicit_domain(self): + #a_very_explicit_domain_setup_by_template_developer_that_wont_be_taken_into_account_by_the_ZPT_engine + #is a domain that transforms to lowercase + self.engine.setLocal('othertolower', + self.factory('OtherToLower', 'a_very_explicit_domain_setup_by_template_developer_that_wont_be_taken_into_account_by_the_ZPT_engine', {}, domain='lower')) + program, macros = self._compile( + '<div i18n:translate=""' + ' tal:content="othertolower" />') + self._check(program, '<div>othertolower</div>\n') + #takes domain into account for strings + program, macros = self._compile( + '<div i18n:translate=""' + ' i18n:domain="a_very_explicit_domain_setup_by_template_developer_that_wont_be_taken_into_account_by_the_ZPT_engine"' + ' tal:content="string:ToLower" />') + self._check(program, '<div>tolower</div>\n') + #but not for messageids + program, macros = self._compile( + '<div i18n:translate=""' + ' i18n:domain="a_very_explicit_domain_setup_by_template_developer_that_wont_be_taken_into_account_by_the_ZPT_engine"' + ' tal:content="baz" />') + self._check(program, '<div>BAZVALUE</div>\n') + +class ScriptTestCase(TestCaseBase): + + def setUp(self): + self.engine = DummyEngine() + + def _check(self, program, expected): + result = StringIO() + self.interpreter = TALInterpreter(program, {}, self.engine, + stream=result) + self.interpreter() + self.assertEqual(expected, result.getvalue()) + + def test_simple(self): + program, macros = self._compile( + '<p tal:script="text/server-python">print "hello"</p>') + self._check(program, '<p>hello\n</p>\n') + + def test_script_and_tal_block(self): + program, macros = self._compile( + '<tal:block script="text/server-python">\n' + ' global x\n' + ' x = 1\n' + '</tal:block>\n' + '<span tal:replace="x" />') + self._check(program, '\n1\n') + self.assertEqual(self.engine.codeGlobals['x'], 1) + + def test_script_and_tal_block_having_inside_print(self): + program, macros = self._compile( + '<tal:block script="text/server-python">\n' + ' print "hello"' + '</tal:block>') + self._check(program, 'hello\n\n') + + def test_script_and_omittag(self): + program, macros = self._compile( + '<p tal:omit-tag="" tal:script="text/server-python">\n' + ' print "hello"' + '</p>') + self._check(program, 'hello\n\n') + + def test_script_and_inside_tags(self): + program, macros = self._compile( + '<p tal:omit-tag="" tal:script="text/server-python">\n' + ' print "<b>hello</b>"' + '</p>') + self._check(program, '<b>hello</b>\n\n') + + def test_script_and_inside_tags_with_tal(self): + program, macros = self._compile( + '<p tal:omit-tag="" tal:script="text/server-python"> <!--\n' + ' print """<b tal:replace="string:foo">hello</b>"""\n' + '--></p>') + self._check(program, '<b tal:replace="string:foo">hello</b>\n\n') + + def test_html_script(self): + program, macros = self._compile( + '<script type="text/server-python">\n' + ' print "Hello world!"\n' + '</script>') + self._check(program, 'Hello world!\n') + + def test_html_script_and_javascript(self): + program, macros = self._compile( + '<script type="text/javascript" src="somefile.js" />\n' + '<script type="text/server-python">\n' + ' print "Hello world!"\n' + '</script>') + self._check(program, + '<script type="text/javascript" src="somefile.js" />\n' + 'Hello world!\n') + + +class I18NErrorsTestCase(TestCaseBase): + + def _check(self, src, msg): + try: + self._compile(src) + except I18NError: + pass + else: + self.fail(msg) + + def test_id_with_replace(self): + self._check('<p i18n:id="foo" tal:replace="string:splat"></p>', + "expected i18n:id with tal:replace to be denied") + + def test_missing_values(self): + self._check('<p i18n:attributes=""></p>', + "missing i18n:attributes value not caught") + self._check('<p i18n:data=""></p>', + "missing i18n:data value not caught") + self._check('<p i18n:id=""></p>', + "missing i18n:id value not caught") + + def test_id_with_attributes(self): + self._check('''<input name="Delete" + tal:attributes="name string:delete_button" + i18n:attributes="name message-id">''', + "expected attribute being both part of tal:attributes" + + " and having a msgid in i18n:attributes to be denied") + +class OutputPresentationTestCase(TestCaseBase): + + def test_attribute_wrapping(self): + # To make sure the attribute-wrapping code is invoked, we have to + # include at least one TAL/METAL attribute to avoid having the start + # tag optimized into a rawtext instruction. + INPUT = r""" + <html this='element' has='a' lot='of' attributes=', so' the='output' + needs='to' be='line' wrapped='.' tal:define='foo nothing'> + </html>""" + EXPECTED = r''' + <html this="element" has="a" lot="of" + attributes=", so" the="output" needs="to" + be="line" wrapped="."> + </html>''' "\n" + self.compare(INPUT, EXPECTED) + + def test_unicode_content(self): + INPUT = """<p tal:content="python:u'déjà-vu'">para</p>""" + EXPECTED = u"""<p>déjà-vu</p>""" "\n" + self.compare(INPUT, EXPECTED) + + def test_unicode_structure(self): + INPUT = """<p tal:replace="structure python:u'déjà-vu'">para</p>""" + EXPECTED = u"""déjà-vu""" "\n" + self.compare(INPUT, EXPECTED) + + def test_i18n_replace_number(self): + INPUT = """ + <p i18n:translate="foo ${bar}"> + <span tal:replace="python:123" i18n:name="bar">para</span> + </p>""" + EXPECTED = u""" + <p>FOO 123</p>""" "\n" + self.compare(INPUT, EXPECTED) + + def test_entities(self): + INPUT = ('<img tal:define="foo nothing" ' + 'alt="&a;  
 &a - &; �a; <>" />') + EXPECTED = ('<img alt="&a;  
 ' + '&a &#45 &; &#0a; <>" />\n') + self.compare(INPUT, EXPECTED) + + def compare(self, INPUT, EXPECTED): + program, macros = self._compile(INPUT) + sio = StringIO() + interp = TALInterpreter(program, {}, DummyEngine(), sio, wrap=60) + interp() + self.assertEqual(sio.getvalue(), EXPECTED) + + +class TestSourceAnnotations(unittest.TestCase): + + # there are additional test files in input/ and output/ subdirs + # (test_sa*) + + def setUp(self): + program = [] + macros = {} + engine = DummyEngine() + self.interpreter = TALInterpreter(program, macros, engine) + self.sio = self.interpreter.stream = StringIO() + self.interpreter._pending_source_annotation = True + + def testFormatSourceAnnotation(self): + interpreter = self.interpreter + interpreter.sourceFile = '/path/to/source.pt' + interpreter.position = (123, 42) + self.assertEquals(interpreter.formatSourceAnnotation(), + "<!--\n" + + "=" * 78 + "\n" + + "/path/to/source.pt (line 123)\n" + + "=" * 78 + "\n" + + "-->") + + def testFormatSourceAnnotation_no_position(self): + interpreter = self.interpreter + interpreter.sourceFile = '/path/to/source.pt' + interpreter.position = (None, None) + self.assertEquals(interpreter.formatSourceAnnotation(), + "<!--\n" + + "=" * 78 + "\n" + + "/path/to/source.pt\n" + + "=" * 78 + "\n" + + "-->") + + def test_annotated_stream_write(self): + interpreter = self.interpreter + interpreter.formatSourceAnnotation = lambda: '@' + test_cases = [ + '@some text', + '\n', + '<?xml ...?>@some text', + ' <?xml ...?>@some text', + '\n<?xml ...?>@some text', + '<?xml ...', + '<?xml ...?>@\n<!DOCTYPE ...>some text', + ] + for output in test_cases: + input = output.replace('@', '') + self.sio.seek(0) + self.sio.truncate() + interpreter._pending_source_annotation = True + interpreter._annotated_stream_write(input) + self.assertEquals(self.sio.getvalue(), output) + if '@' in output: + self.assert_(not interpreter._pending_source_annotation) + else: + self.assert_(interpreter._pending_source_annotation) + + +class TestErrorTracebacks(TestCaseBase): + + # Regression test for http://www.zope.org/Collectors/Zope3-dev/697 + + def test_define_slot_does_not_clobber_source_file_on_exception(self): + m_program, m_macros = self._compile(""" + <div metal:define-macro="amacro"> + <div metal:define-slot="aslot"> + </div> + </div> + """, source_file='macros.pt') + p_program, p_macros = self._compile(""" + <div metal:use-macro="amacro"> + <div metal:fill-slot="aslot"> + <tal:x replace="no_such_thing" /> + </div> + </div> + """, source_file='page.pt') + engine = DummyEngine(macros=m_macros) + interp = TALInterpreter(p_program, {}, engine, StringIO()) + # Expect TALExpressionError: unknown variable: 'no_such_thing' + self.assertRaises(TALExpressionError, interp) + # Now the engine should know where the error occurred + self.assertEquals(engine.source_file, 'page.pt') + self.assertEquals(engine.position, (4, 16)) + + def test_define_slot_restores_source_file_if_no_exception(self): + m_program, m_macros = self._compile(""" + <div metal:define-macro="amacro"> + <div metal:define-slot="aslot"> + </div> + <tal:x replace="no_such_thing" /> + </div> + """, source_file='macros.pt') + p_program, p_macros = self._compile(""" + <div metal:use-macro="amacro"> + <div metal:fill-slot="aslot"> + </div> + </div> + """, source_file='page.pt') + engine = DummyEngine(macros=m_macros) + interp = TALInterpreter(p_program, {}, engine, StringIO()) + # Expect TALExpressionError: unknown variable: 'no_such_thing' + self.assertRaises(TALExpressionError, interp) + # Now the engine should know where the error occurred + self.assertEquals(engine.source_file, 'macros.pt') + self.assertEquals(engine.position, (5, 14)) + + + +def test_suite(): + suite = unittest.makeSuite(I18NErrorsTestCase) + suite.addTest(unittest.makeSuite(MacroErrorsTestCase)) + suite.addTest(unittest.makeSuite(MacroExtendTestCase)) + suite.addTest(unittest.makeSuite(OutputPresentationTestCase)) + suite.addTest(unittest.makeSuite(ScriptTestCase)) + suite.addTest(unittest.makeSuite(I18NCornerTestCaseMessage)) + suite.addTest(unittest.makeSuite(UnusedExplicitDomainTestCase)) + suite.addTest(unittest.makeSuite(TestSourceAnnotations)) + suite.addTest(unittest.makeSuite(TestErrorTracebacks)) + + # TODO: Deactivated test, since we have not found a solution for this and + # it is a deep and undocumented HTML parser issue. + # Fred is looking into this. + #suite.addTest(unittest.makeSuite(MacroFunkyErrorTest)) + + return suite + +if __name__ == "__main__": + errs = utils.run_suite(test_suite()) + sys.exit(errs and 1 or 0) diff --git a/src/zope/tal/tests/test_talparser.py b/src/zope/tal/tests/test_talparser.py new file mode 100644 index 0000000..f159cc1 --- /dev/null +++ b/src/zope/tal/tests/test_talparser.py @@ -0,0 +1,39 @@ +############################################################################## +# +# Copyright (c) 2004 Zope Corporation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +"""Tests for zope.tal.talparser. + +$Id$ +""" +import unittest + +from zope.tal import talparser + + +class TALParserTestCase(unittest.TestCase): + + def test_parser_returns_macros(self): + parser = talparser.TALParser() + parser.parseString( + "<?xml version='1.0'?>\n" + "<doc xmlns:metal='http://xml.zope.org/namespaces/metal'>\n" + " <m metal:define-macro='MACRO'>\n" + " <para>some text</para>\n" + " </m>\n" + "</doc>") + bytecode, macros = parser.getCode() + self.assertEqual(macros.keys(), ["MACRO"]) + + +def test_suite(): + return unittest.makeSuite(TALParserTestCase) diff --git a/src/zope/tal/tests/test_xmlparser.py b/src/zope/tal/tests/test_xmlparser.py new file mode 100644 index 0000000..02d5848 --- /dev/null +++ b/src/zope/tal/tests/test_xmlparser.py @@ -0,0 +1,268 @@ +############################################################################## +# +# Copyright (c) 2001, 2002 Zope Corporation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +"""Tests for XMLParser.py. + +$Id$ +""" +import sys +import unittest + +from zope.tal import xmlparser +from zope.tal.tests import utils + + +class EventCollector(xmlparser.XMLParser): + + def __init__(self): + self.events = [] + self.append = self.events.append + xmlparser.XMLParser.__init__(self) + self.parser.ordered_attributes = 1 + + def get_events(self): + # Normalize the list of events so that buffer artefacts don't + # separate runs of contiguous characters. + L = [] + prevtype = None + for event in self.events: + type = event[0] + if type == prevtype == "data": + L[-1] = ("data", L[-1][1] + event[1]) + else: + L.append(event) + prevtype = type + self.events = L + return L + + # structure markup + + def StartElementHandler(self, tag, attrs): + self.append(("starttag", tag, attrs)) + + def EndElementHandler(self, tag): + self.append(("endtag", tag)) + + # all other markup + + def CommentHandler(self, data): + self.append(("comment", data)) + + def handle_charref(self, data): + self.append(("charref", data)) + + def CharacterDataHandler(self, data): + self.append(("data", data)) + + def StartDoctypeDeclHandler(self, rootelem, publicId, systemId, subset): + self.append(("doctype", rootelem, systemId, publicId, subset)) + + def XmlDeclHandler(self, version, encoding, standalone): + self.append(("decl", version, encoding, standalone)) + + def ExternalEntityRefHandler(self, data): + self.append(("entityref", data)) + + def ProcessingInstructionHandler(self, target, data): + self.append(("pi", target, data)) + + +class EventCollectorExtra(EventCollector): + + def handle_starttag(self, tag, attrs): + EventCollector.handle_starttag(self, tag, attrs) + self.append(("starttag_text", self.get_starttag_text())) + + +class SegmentedFile(object): + def __init__(self, parts): + self.parts = list(parts) + + def read(self, bytes): + if self.parts: + s = self.parts.pop(0) + else: + s = '' + return s + + +class XMLParserTestCase(unittest.TestCase): + + def _run_check(self, source, events, collector=EventCollector): + parser = collector() + if isinstance(source, list): + parser.parseStream(SegmentedFile(source)) + else: + parser.parseString(source) + self.assertEquals(parser.get_events(),events) + + def _run_check_extra(self, source, events): + self._run_check(source, events, EventCollectorExtra) + + def _parse_error(self, source): + def parse(source=source): + parser = xmlparser.XMLParser() + parser.parseString(source) + self.assertRaises(xmlparser.XMLParseError, parse) + + def test_processing_instruction_plus(self): + self._run_check("<?processing instruction?><a/>", [ + ("pi", "processing", "instruction"), + ("starttag", "a", []), + ("endtag", "a"), + ]) + + def _check_simple_html(self): + self._run_check("""\ +<?xml version='1.0' encoding='iso-8859-1'?> +<!DOCTYPE html PUBLIC 'foo' 'bar'> +<html>&entity;  +<!--comment1a +-></foo><bar><<?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><<?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='&><"''/>""", [ + ("starttag", "a", ["b", "&><\"'"]), + ("endtag", "a"), + ]) + + def test_attr_funky_names(self): + self._run_check("""<a a.b='v' e-f='v'/>""", [ + ("starttag", "a", ["a.b", "v", "e-f", "v"]), + ("endtag", "a"), + ]) + + def test_starttag_end_boundary(self): + self._run_check("""<a b='<'/>""", [ + ("starttag", "a", ["b", "<"]), + ("endtag", "a"), + ]) + self._run_check("""<a b='>'/>""", [ + ("starttag", "a", ["b", ">"]), + ("endtag", "a"), + ]) + + def test_buffer_artefacts(self): + output = [("starttag", "a", ["b", "<"]), ("endtag", "a")] + self._run_check(["<a b='<'/>"], output) + self._run_check(["<a ", "b='<'/>"], output) + self._run_check(["<a b", "='<'/>"], output) + self._run_check(["<a b=", "'<'/>"], output) + self._run_check(["<a b='<", "'/>"], output) + self._run_check(["<a b='<'", "/>"], output) + + output = [("starttag", "a", ["b", ">"]), ("endtag", "a")] + self._run_check(["<a b='>'/>"], output) + self._run_check(["<a ", "b='>'/>"], output) + self._run_check(["<a b", "='>'/>"], output) + self._run_check(["<a b=", "'>'/>"], output) + self._run_check(["<a b='>", "'/>"], output) + self._run_check(["<a b='>'", "/>"], output) + + def test_starttag_junk_chars(self): + self._parse_error("<") + self._parse_error("<>") + self._parse_error("</>") + self._parse_error("</$>") + self._parse_error("</") + self._parse_error("</a") + self._parse_error("</a") + self._parse_error("<a<a>") + self._parse_error("</a<a>") + self._parse_error("<$") + self._parse_error("<$>") + self._parse_error("<!") + self._parse_error("<a $>") + self._parse_error("<a") + self._parse_error("<a foo='bar'") + self._parse_error("<a foo='bar") + self._parse_error("<a foo='>'") + self._parse_error("<a foo='>") + + def test_declaration_junk_chars(self): + self._parse_error("<!DOCTYPE foo $ >") + + def test_unicode_string(self): + output = [('starttag', u'p', []), + ('data', u'\xe4\xf6\xfc\xdf'), + ('endtag', u'p')] + self._run_check(u'<p>\xe4\xf6\xfc\xdf</p>', output) + + +# Support for the Zope regression test framework: +def test_suite(skipxml=utils.skipxml): + if skipxml: + return unittest.TestSuite() + else: + return unittest.makeSuite(XMLParserTestCase) + +if __name__ == "__main__": + errs = utils.run_suite(test_suite(skipxml=0)) + sys.exit(errs and 1 or 0) diff --git a/src/zope/tal/tests/utils.py b/src/zope/tal/tests/utils.py new file mode 100644 index 0000000..03eba52 --- /dev/null +++ b/src/zope/tal/tests/utils.py @@ -0,0 +1,65 @@ +############################################################################## +# +# Copyright (c) 2001, 2002 Zope Corporation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +"""Helper functions for the test suite. + +$Id$ +""" +import os +import sys + +mydir = os.path.abspath(os.path.dirname(__file__)) +codedir = os.path.dirname(os.path.dirname(os.path.dirname(mydir))) + +if codedir not in sys.path: + sys.path.append(codedir) + +import unittest + + +# Set skipxml to true if an XML parser could not be found. +skipxml = 0 +try: + import xml.parsers.expat +except ImportError: + skipxml = 1 + + +def run_suite(suite, outf=None, errf=None): + if outf is None: + outf = sys.stdout + runner = unittest.TextTestRunner(outf) + result = runner.run(suite) + +## print "\n\n" +## if result.errors: +## print "Errors (unexpected exceptions):" +## map(print_error, result.errors) +## print +## if result.failures: +## print "Failures (assertion failures):" +## map(print_error, result.failures) +## print + newerrs = len(result.errors) + len(result.failures) + if newerrs: + print "'Errors' indicate exceptions other than AssertionError." + print "'Failures' indicate AssertionError" + if errf is None: + errf = sys.stderr + errf.write("%d errors, %d failures\n" + % (len(result.errors), len(result.failures))) + return newerrs + + +def print_error(info): + testcase, (type, e, tb) = info diff --git a/src/zope/tal/timer.py b/src/zope/tal/timer.py new file mode 100644 index 0000000..916a2e2 --- /dev/null +++ b/src/zope/tal/timer.py @@ -0,0 +1,58 @@ +#! /usr/bin/env python +############################################################################## +# +# Copyright (c) 2001, 2002 Zope Corporation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +"""Helper program to time compilation and interpretation + +$Id$ +""" +import getopt +import sys +import time + +from cStringIO import StringIO + +from zope.tal.driver import FILE, compilefile, interpretit + + +def main(): + count = 10 + try: + opts, args = getopt.getopt(sys.argv[1:], "n:") + except getopt.error, msg: + print msg + sys.exit(2) + for o, a in opts: + if o == "-n": + count = int(a) + if not args: + args = [FILE] + for file in args: + print file + dummyfile = StringIO() + it = timefunc(count, compilefile, file) + timefunc(count, interpretit, it, None, dummyfile) + +def timefunc(count, func, *args): + sys.stderr.write("%-14s: " % func.__name__) + sys.stderr.flush() + t0 = time.clock() + for i in range(count): + result = apply(func, args) + t1 = time.clock() + sys.stderr.write("%6.3f secs for %d calls, i.e. %4.0f msecs per call\n" + % ((t1-t0), count, 1000*(t1-t0)/count)) + return result + +if __name__ == "__main__": + main() diff --git a/src/zope/tal/translationcontext.py b/src/zope/tal/translationcontext.py new file mode 100644 index 0000000..3d870b9 --- /dev/null +++ b/src/zope/tal/translationcontext.py @@ -0,0 +1,40 @@ +############################################################################## +# +# Copyright (c) 2001, 2002 Zope Corporation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +"""Translation context object for the TALInterpreter's I18N support. + +The translation context provides a container for the information +needed to perform translation of a marked string from a page template. + +$Id$ +""" +DEFAULT_DOMAIN = "default" + +class TranslationContext(object): + """Information about the I18N settings of a TAL processor.""" + + def __init__(self, parent=None, domain=None, target=None, source=None): + if parent: + if not domain: + domain = parent.domain + if not target: + target = parent.target + if not source: + source = parent.source + elif domain is None: + domain = DEFAULT_DOMAIN + + self.parent = parent + self.domain = domain + self.target = target + self.source = source diff --git a/src/zope/tal/xmlparser.py b/src/zope/tal/xmlparser.py new file mode 100644 index 0000000..aafa693 --- /dev/null +++ b/src/zope/tal/xmlparser.py @@ -0,0 +1,105 @@ +############################################################################## +# +# Copyright (c) 2001, 2002 Zope Corporation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +"""Generic Expat-based XML parser base class. + +This creates a parser with namespace processing enabled. + +$Id$ +""" +import logging + + +class XMLParser(object): + + ordered_attributes = 0 + + handler_names = [ + "StartElementHandler", + "EndElementHandler", + "ProcessingInstructionHandler", + "CharacterDataHandler", + "UnparsedEntityDeclHandler", + "NotationDeclHandler", + "StartNamespaceDeclHandler", + "EndNamespaceDeclHandler", + "CommentHandler", + "StartCdataSectionHandler", + "EndCdataSectionHandler", + "DefaultHandler", + "DefaultHandlerExpand", + "NotStandaloneHandler", + "ExternalEntityRefHandler", + "XmlDeclHandler", + "StartDoctypeDeclHandler", + "EndDoctypeDeclHandler", + "ElementDeclHandler", + "AttlistDeclHandler" + ] + + def __init__(self, encoding=None): + self.parser = p = self.createParser(encoding) + if self.ordered_attributes: + try: + self.parser.ordered_attributes = self.ordered_attributes + except AttributeError: + logging.warn("TAL.XMLParser: Can't set ordered_attributes") + self.ordered_attributes = 0 + for name in self.handler_names: + method = getattr(self, name, None) + if method is not None: + try: + setattr(p, name, method) + except AttributeError: + logging.error("TAL.XMLParser: Can't set " + "expat handler %s" % name) + + def createParser(self, encoding=None): + global XMLParseError + from xml.parsers import expat + XMLParseError = expat.ExpatError + return expat.ParserCreate(encoding, ' ') + + def parseFile(self, filename): + self.parseStream(open(filename)) + + def parseString(self, s): + if isinstance(s, unicode): + # Expat cannot deal with unicode strings, only with + # encoded ones. Also, its range of encodings is rather + # limited, UTF-8 is the safest bet here. + s = s.encode('utf-8') + self.parser.Parse(s, 1) + + def parseURL(self, url): + import urllib + self.parseStream(urllib.urlopen(url)) + + def parseStream(self, stream): + self.parser.ParseFile(stream) + + def parseFragment(self, s, end=0): + self.parser.Parse(s, end) + + def getpos(self): + # Apparently ErrorLineNumber and ErrorLineNumber contain the current + # position even when there was no error. This contradicts the official + # documentation[1], but expat.h[2] contains the following definition: + # + # /* For backwards compatibility with previous versions. */ + # #define XML_GetErrorLineNumber XML_GetCurrentLineNumber + # + # [1] http://python.org/doc/current/lib/xmlparser-objects.html + # [2] http://cvs.sourceforge.net/viewcvs.py/expat/expat/lib/expat.h + return (self.parser.ErrorLineNumber, self.parser.ErrorColumnNumber) + |