diff options
153 files changed, 8709 insertions, 0 deletions
diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..b711d36 --- /dev/null +++ b/__init__.py @@ -0,0 +1,2 @@ +# +# This file is necessary to make this directory a package. diff --git a/benchmark/__init__.py b/benchmark/__init__.py new file mode 100644 index 0000000..b711d36 --- /dev/null +++ b/benchmark/__init__.py @@ -0,0 +1,2 @@ +# +# This file is necessary to make this directory a package. diff --git a/benchmark/dtml01.html b/benchmark/dtml01.html new file mode 100644 index 0000000..180b47c --- /dev/null +++ b/benchmark/dtml01.html @@ -0,0 +1 @@ +baseline diff --git a/benchmark/dtml02.html b/benchmark/dtml02.html new file mode 100644 index 0000000..33d978d --- /dev/null +++ b/benchmark/dtml02.html @@ -0,0 +1,100 @@ + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. diff --git a/benchmark/dtml03.html b/benchmark/dtml03.html new file mode 100644 index 0000000..aea01aa --- /dev/null +++ b/benchmark/dtml03.html @@ -0,0 +1,8 @@ + <td bgcolor="white">&dtml-x0;</td> + <td bgcolor="white">&dtml-x1;</td> + <td bgcolor="white">&dtml-x2;</td> + <td bgcolor="white">&dtml-x3;</td> + <td bgcolor="white">&dtml-x4;</td> + <td bgcolor="white">&dtml-x5;</td> + <td bgcolor="white">&dtml-x6;</td> + <td bgcolor="white">&dtml-x7;</td> diff --git a/benchmark/dtml04.html b/benchmark/dtml04.html new file mode 100644 index 0000000..1a3214f --- /dev/null +++ b/benchmark/dtml04.html @@ -0,0 +1,6 @@ +<dtml-in r8> + <td bgcolor="white">&dtml-x0;</td> + <td bgcolor="white">&dtml-x1;</td> + <td bgcolor="white">&dtml-x2;</td> + <td bgcolor="white">&dtml-x3;</td> +</dtml-in> diff --git a/benchmark/dtml05.html b/benchmark/dtml05.html new file mode 100644 index 0000000..70b53cb --- /dev/null +++ b/benchmark/dtml05.html @@ -0,0 +1,10 @@ +<dtml-in r8> + <td bgcolor="white">&dtml-x0;</td> + <td bgcolor="white">&dtml-x1;</td> + <td bgcolor="white">&dtml-x2;</td> + <td bgcolor="white">&dtml-x3;</td> + <td bgcolor="white">&dtml-x4;</td> + <td bgcolor="white">&dtml-x5;</td> + <td bgcolor="white">&dtml-x6;</td> + <td bgcolor="white">&dtml-x7;</td> +</dtml-in> diff --git a/benchmark/dtml06.html b/benchmark/dtml06.html new file mode 100644 index 0000000..11e5cf2 --- /dev/null +++ b/benchmark/dtml06.html @@ -0,0 +1,14 @@ +<dtml-in r2> +<dtml-in r2> +<dtml-in r2> + <td bgcolor="white">&dtml-x0;</td> + <td bgcolor="white">&dtml-x1;</td> + <td bgcolor="white">&dtml-x2;</td> + <td bgcolor="white">&dtml-x3;</td> + <td bgcolor="white">&dtml-x4;</td> + <td bgcolor="white">&dtml-x5;</td> + <td bgcolor="white">&dtml-x6;</td> + <td bgcolor="white">&dtml-x7;</td> +</dtml-in> +</dtml-in> +</dtml-in> diff --git a/benchmark/dtml07.html b/benchmark/dtml07.html new file mode 100644 index 0000000..48f50c7 --- /dev/null +++ b/benchmark/dtml07.html @@ -0,0 +1,73 @@ + <td bgcolor="white">&dtml-x0;</td> + <td bgcolor="white">&dtml-x1;</td> + <td bgcolor="white">&dtml-x2;</td> + <td bgcolor="white">&dtml-x3;</td> + <td bgcolor="white">&dtml-x4;</td> + <td bgcolor="white">&dtml-x5;</td> + <td bgcolor="white">&dtml-x6;</td> + <td bgcolor="white">&dtml-x7;</td> + + <td bgcolor="white">&dtml-x0;</td> + <td bgcolor="white">&dtml-x1;</td> + <td bgcolor="white">&dtml-x2;</td> + <td bgcolor="white">&dtml-x3;</td> + <td bgcolor="white">&dtml-x4;</td> + <td bgcolor="white">&dtml-x5;</td> + <td bgcolor="white">&dtml-x6;</td> + <td bgcolor="white">&dtml-x7;</td> + + <td bgcolor="white">&dtml-x0;</td> + <td bgcolor="white">&dtml-x1;</td> + <td bgcolor="white">&dtml-x2;</td> + <td bgcolor="white">&dtml-x3;</td> + <td bgcolor="white">&dtml-x4;</td> + <td bgcolor="white">&dtml-x5;</td> + <td bgcolor="white">&dtml-x6;</td> + <td bgcolor="white">&dtml-x7;</td> + + <td bgcolor="white">&dtml-x0;</td> + <td bgcolor="white">&dtml-x1;</td> + <td bgcolor="white">&dtml-x2;</td> + <td bgcolor="white">&dtml-x3;</td> + <td bgcolor="white">&dtml-x4;</td> + <td bgcolor="white">&dtml-x5;</td> + <td bgcolor="white">&dtml-x6;</td> + <td bgcolor="white">&dtml-x7;</td> + + <td bgcolor="white">&dtml-x0;</td> + <td bgcolor="white">&dtml-x1;</td> + <td bgcolor="white">&dtml-x2;</td> + <td bgcolor="white">&dtml-x3;</td> + <td bgcolor="white">&dtml-x4;</td> + <td bgcolor="white">&dtml-x5;</td> + <td bgcolor="white">&dtml-x6;</td> + <td bgcolor="white">&dtml-x7;</td> + + <td bgcolor="white">&dtml-x0;</td> + <td bgcolor="white">&dtml-x1;</td> + <td bgcolor="white">&dtml-x2;</td> + <td bgcolor="white">&dtml-x3;</td> + <td bgcolor="white">&dtml-x4;</td> + <td bgcolor="white">&dtml-x5;</td> + <td bgcolor="white">&dtml-x6;</td> + <td bgcolor="white">&dtml-x7;</td> + + <td bgcolor="white">&dtml-x0;</td> + <td bgcolor="white">&dtml-x1;</td> + <td bgcolor="white">&dtml-x2;</td> + <td bgcolor="white">&dtml-x3;</td> + <td bgcolor="white">&dtml-x4;</td> + <td bgcolor="white">&dtml-x5;</td> + <td bgcolor="white">&dtml-x6;</td> + <td bgcolor="white">&dtml-x7;</td> + + <td bgcolor="white">&dtml-x0;</td> + <td bgcolor="white">&dtml-x1;</td> + <td bgcolor="white">&dtml-x2;</td> + <td bgcolor="white">&dtml-x3;</td> + <td bgcolor="white">&dtml-x4;</td> + <td bgcolor="white">&dtml-x5;</td> + <td bgcolor="white">&dtml-x6;</td> + <td bgcolor="white">&dtml-x7;</td> + + diff --git a/benchmark/dtml08.html b/benchmark/dtml08.html new file mode 100644 index 0000000..48f50c7 --- /dev/null +++ b/benchmark/dtml08.html @@ -0,0 +1,73 @@ + <td bgcolor="white">&dtml-x0;</td> + <td bgcolor="white">&dtml-x1;</td> + <td bgcolor="white">&dtml-x2;</td> + <td bgcolor="white">&dtml-x3;</td> + <td bgcolor="white">&dtml-x4;</td> + <td bgcolor="white">&dtml-x5;</td> + <td bgcolor="white">&dtml-x6;</td> + <td bgcolor="white">&dtml-x7;</td> + + <td bgcolor="white">&dtml-x0;</td> + <td bgcolor="white">&dtml-x1;</td> + <td bgcolor="white">&dtml-x2;</td> + <td bgcolor="white">&dtml-x3;</td> + <td bgcolor="white">&dtml-x4;</td> + <td bgcolor="white">&dtml-x5;</td> + <td bgcolor="white">&dtml-x6;</td> + <td bgcolor="white">&dtml-x7;</td> + + <td bgcolor="white">&dtml-x0;</td> + <td bgcolor="white">&dtml-x1;</td> + <td bgcolor="white">&dtml-x2;</td> + <td bgcolor="white">&dtml-x3;</td> + <td bgcolor="white">&dtml-x4;</td> + <td bgcolor="white">&dtml-x5;</td> + <td bgcolor="white">&dtml-x6;</td> + <td bgcolor="white">&dtml-x7;</td> + + <td bgcolor="white">&dtml-x0;</td> + <td bgcolor="white">&dtml-x1;</td> + <td bgcolor="white">&dtml-x2;</td> + <td bgcolor="white">&dtml-x3;</td> + <td bgcolor="white">&dtml-x4;</td> + <td bgcolor="white">&dtml-x5;</td> + <td bgcolor="white">&dtml-x6;</td> + <td bgcolor="white">&dtml-x7;</td> + + <td bgcolor="white">&dtml-x0;</td> + <td bgcolor="white">&dtml-x1;</td> + <td bgcolor="white">&dtml-x2;</td> + <td bgcolor="white">&dtml-x3;</td> + <td bgcolor="white">&dtml-x4;</td> + <td bgcolor="white">&dtml-x5;</td> + <td bgcolor="white">&dtml-x6;</td> + <td bgcolor="white">&dtml-x7;</td> + + <td bgcolor="white">&dtml-x0;</td> + <td bgcolor="white">&dtml-x1;</td> + <td bgcolor="white">&dtml-x2;</td> + <td bgcolor="white">&dtml-x3;</td> + <td bgcolor="white">&dtml-x4;</td> + <td bgcolor="white">&dtml-x5;</td> + <td bgcolor="white">&dtml-x6;</td> + <td bgcolor="white">&dtml-x7;</td> + + <td bgcolor="white">&dtml-x0;</td> + <td bgcolor="white">&dtml-x1;</td> + <td bgcolor="white">&dtml-x2;</td> + <td bgcolor="white">&dtml-x3;</td> + <td bgcolor="white">&dtml-x4;</td> + <td bgcolor="white">&dtml-x5;</td> + <td bgcolor="white">&dtml-x6;</td> + <td bgcolor="white">&dtml-x7;</td> + + <td bgcolor="white">&dtml-x0;</td> + <td bgcolor="white">&dtml-x1;</td> + <td bgcolor="white">&dtml-x2;</td> + <td bgcolor="white">&dtml-x3;</td> + <td bgcolor="white">&dtml-x4;</td> + <td bgcolor="white">&dtml-x5;</td> + <td bgcolor="white">&dtml-x6;</td> + <td bgcolor="white">&dtml-x7;</td> + + diff --git a/benchmark/dtml09.html b/benchmark/dtml09.html new file mode 100644 index 0000000..ce8e43e --- /dev/null +++ b/benchmark/dtml09.html @@ -0,0 +1,10 @@ +<dtml-in r64> + <td bgcolor="white">&dtml-x0;</td> + <td bgcolor="white">&dtml-x1;</td> + <td bgcolor="white">&dtml-x2;</td> + <td bgcolor="white">&dtml-x3;</td> + <td bgcolor="white">&dtml-x4;</td> + <td bgcolor="white">&dtml-x5;</td> + <td bgcolor="white">&dtml-x6;</td> + <td bgcolor="white">&dtml-x7;</td> +</dtml-in> diff --git a/benchmark/dtml10.html b/benchmark/dtml10.html new file mode 100644 index 0000000..3115f7c --- /dev/null +++ b/benchmark/dtml10.html @@ -0,0 +1,102 @@ +<dtml-in r64> + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. +</dtml-in> diff --git a/benchmark/dtml11.html b/benchmark/dtml11.html new file mode 100644 index 0000000..b0f71bd --- /dev/null +++ b/benchmark/dtml11.html @@ -0,0 +1,103 @@ +<dtml-in r64> + <td bgcolor="white">&dtml-x0;</td> + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. +</dtml-in> diff --git a/benchmark/dtml12.html b/benchmark/dtml12.html new file mode 100644 index 0000000..df2dab1 --- /dev/null +++ b/benchmark/dtml12.html @@ -0,0 +1,12 @@ +<dtml-in r8> + <dtml-let y0=x0 y1=x1 y2=x2 y3=x3 y4=x4 y5=x5 y6=x6 y7=x7> + <td bgcolor="white">&dtml-y0;</td> + <td bgcolor="white">&dtml-y1;</td> + <td bgcolor="white">&dtml-y2;</td> + <td bgcolor="white">&dtml-y3;</td> + <td bgcolor="white">&dtml-y4;</td> + <td bgcolor="white">&dtml-y5;</td> + <td bgcolor="white">&dtml-y6;</td> + <td bgcolor="white">&dtml-y7;</td> + </dtml-let> +</dtml-in> diff --git a/benchmark/tal01.html b/benchmark/tal01.html new file mode 100644 index 0000000..180b47c --- /dev/null +++ b/benchmark/tal01.html @@ -0,0 +1 @@ +baseline diff --git a/benchmark/tal02.html b/benchmark/tal02.html new file mode 100644 index 0000000..33d978d --- /dev/null +++ b/benchmark/tal02.html @@ -0,0 +1,100 @@ + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. diff --git a/benchmark/tal03.html b/benchmark/tal03.html new file mode 100644 index 0000000..b63a737 --- /dev/null +++ b/benchmark/tal03.html @@ -0,0 +1,8 @@ + <td bgcolor="white" tal:content="x0"></td> + <td bgcolor="white" tal:content="x1"></td> + <td bgcolor="white" tal:content="x2"></td> + <td bgcolor="white" tal:content="x3"></td> + <td bgcolor="white" tal:content="x4"></td> + <td bgcolor="white" tal:content="x5"></td> + <td bgcolor="white" tal:content="x6"></td> + <td bgcolor="white" tal:content="x7"></td> diff --git a/benchmark/tal04.html b/benchmark/tal04.html new file mode 100644 index 0000000..42af6e8 --- /dev/null +++ b/benchmark/tal04.html @@ -0,0 +1,6 @@ +<dtml-in tal:repeat="r r8"> + <td bgcolor="white" tal:content="x0"></td> + <td bgcolor="white" tal:content="x1"></td> + <td bgcolor="white" tal:content="x2"></td> + <td bgcolor="white" tal:content="x3"></td> +</dtml-in> diff --git a/benchmark/tal05.html b/benchmark/tal05.html new file mode 100644 index 0000000..6e2d626 --- /dev/null +++ b/benchmark/tal05.html @@ -0,0 +1,10 @@ +<dtml-in tal:repeat="r r8"> + <td bgcolor="white" tal:content="x0"></td> + <td bgcolor="white" tal:content="x1"></td> + <td bgcolor="white" tal:content="x2"></td> + <td bgcolor="white" tal:content="x3"></td> + <td bgcolor="white" tal:content="x4"></td> + <td bgcolor="white" tal:content="x5"></td> + <td bgcolor="white" tal:content="x6"></td> + <td bgcolor="white" tal:content="x7"></td> +</dtml-in> diff --git a/benchmark/tal06.html b/benchmark/tal06.html new file mode 100644 index 0000000..6f40872 --- /dev/null +++ b/benchmark/tal06.html @@ -0,0 +1,14 @@ +<dtml-in tal:repeat="r r2"> +<dtml-in tal:repeat="r r2"> +<dtml-in tal:repeat="r r2"> + <td bgcolor="white" tal:content="x0"></td> + <td bgcolor="white" tal:content="x1"></td> + <td bgcolor="white" tal:content="x2"></td> + <td bgcolor="white" tal:content="x3"></td> + <td bgcolor="white" tal:content="x4"></td> + <td bgcolor="white" tal:content="x5"></td> + <td bgcolor="white" tal:content="x6"></td> + <td bgcolor="white" tal:content="x7"></td> +</dtml-in> +</dtml-in> +</dtml-in> diff --git a/benchmark/tal07.html b/benchmark/tal07.html new file mode 100644 index 0000000..f331f05 --- /dev/null +++ b/benchmark/tal07.html @@ -0,0 +1,73 @@ + <td bgcolor="white" tal:content="x0"></td> + <td bgcolor="white" tal:content="x1"></td> + <td bgcolor="white" tal:content="x2"></td> + <td bgcolor="white" tal:content="x3"></td> + <td bgcolor="white" tal:content="x4"></td> + <td bgcolor="white" tal:content="x5"></td> + <td bgcolor="white" tal:content="x6"></td> + <td bgcolor="white" tal:content="x7"></td> + + <td bgcolor="white" tal:content="x0"></td> + <td bgcolor="white" tal:content="x1"></td> + <td bgcolor="white" tal:content="x2"></td> + <td bgcolor="white" tal:content="x3"></td> + <td bgcolor="white" tal:content="x4"></td> + <td bgcolor="white" tal:content="x5"></td> + <td bgcolor="white" tal:content="x6"></td> + <td bgcolor="white" tal:content="x7"></td> + + <td bgcolor="white" tal:content="x0"></td> + <td bgcolor="white" tal:content="x1"></td> + <td bgcolor="white" tal:content="x2"></td> + <td bgcolor="white" tal:content="x3"></td> + <td bgcolor="white" tal:content="x4"></td> + <td bgcolor="white" tal:content="x5"></td> + <td bgcolor="white" tal:content="x6"></td> + <td bgcolor="white" tal:content="x7"></td> + + <td bgcolor="white" tal:content="x0"></td> + <td bgcolor="white" tal:content="x1"></td> + <td bgcolor="white" tal:content="x2"></td> + <td bgcolor="white" tal:content="x3"></td> + <td bgcolor="white" tal:content="x4"></td> + <td bgcolor="white" tal:content="x5"></td> + <td bgcolor="white" tal:content="x6"></td> + <td bgcolor="white" tal:content="x7"></td> + + <td bgcolor="white" tal:content="x0"></td> + <td bgcolor="white" tal:content="x1"></td> + <td bgcolor="white" tal:content="x2"></td> + <td bgcolor="white" tal:content="x3"></td> + <td bgcolor="white" tal:content="x4"></td> + <td bgcolor="white" tal:content="x5"></td> + <td bgcolor="white" tal:content="x6"></td> + <td bgcolor="white" tal:content="x7"></td> + + <td bgcolor="white" tal:content="x0"></td> + <td bgcolor="white" tal:content="x1"></td> + <td bgcolor="white" tal:content="x2"></td> + <td bgcolor="white" tal:content="x3"></td> + <td bgcolor="white" tal:content="x4"></td> + <td bgcolor="white" tal:content="x5"></td> + <td bgcolor="white" tal:content="x6"></td> + <td bgcolor="white" tal:content="x7"></td> + + <td bgcolor="white" tal:content="x0"></td> + <td bgcolor="white" tal:content="x1"></td> + <td bgcolor="white" tal:content="x2"></td> + <td bgcolor="white" tal:content="x3"></td> + <td bgcolor="white" tal:content="x4"></td> + <td bgcolor="white" tal:content="x5"></td> + <td bgcolor="white" tal:content="x6"></td> + <td bgcolor="white" tal:content="x7"></td> + + <td bgcolor="white" tal:content="x0"></td> + <td bgcolor="white" tal:content="x1"></td> + <td bgcolor="white" tal:content="x2"></td> + <td bgcolor="white" tal:content="x3"></td> + <td bgcolor="white" tal:content="x4"></td> + <td bgcolor="white" tal:content="x5"></td> + <td bgcolor="white" tal:content="x6"></td> + <td bgcolor="white" tal:content="x7"></td> + + diff --git a/benchmark/tal08.html b/benchmark/tal08.html new file mode 100644 index 0000000..f577fed --- /dev/null +++ b/benchmark/tal08.html @@ -0,0 +1,73 @@ + <td bgcolor="white"><span tal:replace="x0"></span></td> + <td bgcolor="white"><span tal:replace="x1"></span></td> + <td bgcolor="white"><span tal:replace="x2"></span></td> + <td bgcolor="white"><span tal:replace="x3"></span></td> + <td bgcolor="white"><span tal:replace="x4"></span></td> + <td bgcolor="white"><span tal:replace="x5"></span></td> + <td bgcolor="white"><span tal:replace="x6"></span></td> + <td bgcolor="white"><span tal:replace="x7"></span></td> + + <td bgcolor="white"><span tal:replace="x0"></span></td> + <td bgcolor="white"><span tal:replace="x1"></span></td> + <td bgcolor="white"><span tal:replace="x2"></span></td> + <td bgcolor="white"><span tal:replace="x3"></span></td> + <td bgcolor="white"><span tal:replace="x4"></span></td> + <td bgcolor="white"><span tal:replace="x5"></span></td> + <td bgcolor="white"><span tal:replace="x6"></span></td> + <td bgcolor="white"><span tal:replace="x7"></span></td> + + <td bgcolor="white"><span tal:replace="x0"></span></td> + <td bgcolor="white"><span tal:replace="x1"></span></td> + <td bgcolor="white"><span tal:replace="x2"></span></td> + <td bgcolor="white"><span tal:replace="x3"></span></td> + <td bgcolor="white"><span tal:replace="x4"></span></td> + <td bgcolor="white"><span tal:replace="x5"></span></td> + <td bgcolor="white"><span tal:replace="x6"></span></td> + <td bgcolor="white"><span tal:replace="x7"></span></td> + + <td bgcolor="white"><span tal:replace="x0"></span></td> + <td bgcolor="white"><span tal:replace="x1"></span></td> + <td bgcolor="white"><span tal:replace="x2"></span></td> + <td bgcolor="white"><span tal:replace="x3"></span></td> + <td bgcolor="white"><span tal:replace="x4"></span></td> + <td bgcolor="white"><span tal:replace="x5"></span></td> + <td bgcolor="white"><span tal:replace="x6"></span></td> + <td bgcolor="white"><span tal:replace="x7"></span></td> + + <td bgcolor="white"><span tal:replace="x0"></span></td> + <td bgcolor="white"><span tal:replace="x1"></span></td> + <td bgcolor="white"><span tal:replace="x2"></span></td> + <td bgcolor="white"><span tal:replace="x3"></span></td> + <td bgcolor="white"><span tal:replace="x4"></span></td> + <td bgcolor="white"><span tal:replace="x5"></span></td> + <td bgcolor="white"><span tal:replace="x6"></span></td> + <td bgcolor="white"><span tal:replace="x7"></span></td> + + <td bgcolor="white"><span tal:replace="x0"></span></td> + <td bgcolor="white"><span tal:replace="x1"></span></td> + <td bgcolor="white"><span tal:replace="x2"></span></td> + <td bgcolor="white"><span tal:replace="x3"></span></td> + <td bgcolor="white"><span tal:replace="x4"></span></td> + <td bgcolor="white"><span tal:replace="x5"></span></td> + <td bgcolor="white"><span tal:replace="x6"></span></td> + <td bgcolor="white"><span tal:replace="x7"></span></td> + + <td bgcolor="white"><span tal:replace="x0"></span></td> + <td bgcolor="white"><span tal:replace="x1"></span></td> + <td bgcolor="white"><span tal:replace="x2"></span></td> + <td bgcolor="white"><span tal:replace="x3"></span></td> + <td bgcolor="white"><span tal:replace="x4"></span></td> + <td bgcolor="white"><span tal:replace="x5"></span></td> + <td bgcolor="white"><span tal:replace="x6"></span></td> + <td bgcolor="white"><span tal:replace="x7"></span></td> + + <td bgcolor="white"><span tal:replace="x0"></span></td> + <td bgcolor="white"><span tal:replace="x1"></span></td> + <td bgcolor="white"><span tal:replace="x2"></span></td> + <td bgcolor="white"><span tal:replace="x3"></span></td> + <td bgcolor="white"><span tal:replace="x4"></span></td> + <td bgcolor="white"><span tal:replace="x5"></span></td> + <td bgcolor="white"><span tal:replace="x6"></span></td> + <td bgcolor="white"><span tal:replace="x7"></span></td> + + diff --git a/benchmark/tal09.html b/benchmark/tal09.html new file mode 100644 index 0000000..ef81c58 --- /dev/null +++ b/benchmark/tal09.html @@ -0,0 +1,10 @@ +<dtml-in tal:repeat="r r64"> + <td bgcolor="white" tal:content="x0"></td> + <td bgcolor="white" tal:content="x1"></td> + <td bgcolor="white" tal:content="x2"></td> + <td bgcolor="white" tal:content="x3"></td> + <td bgcolor="white" tal:content="x4"></td> + <td bgcolor="white" tal:content="x5"></td> + <td bgcolor="white" tal:content="x6"></td> + <td bgcolor="white" tal:content="x7"></td> +</dtml-in> diff --git a/benchmark/tal10.html b/benchmark/tal10.html new file mode 100644 index 0000000..8026df7 --- /dev/null +++ b/benchmark/tal10.html @@ -0,0 +1,102 @@ +<dtml-in tal:repeat="r r64"> + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. +</dtml-in> diff --git a/benchmark/tal11.html b/benchmark/tal11.html new file mode 100644 index 0000000..d4a2440 --- /dev/null +++ b/benchmark/tal11.html @@ -0,0 +1,103 @@ +<dtml-in tal:repeat="r r64"> + <td bgcolor="white" tal:content="x0"></td> + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. + A large chunk of text to be repeated. +</dtml-in> diff --git a/benchmark/tal12.html b/benchmark/tal12.html new file mode 100644 index 0000000..dcd2c30 --- /dev/null +++ b/benchmark/tal12.html @@ -0,0 +1,12 @@ +<dtml-in tal:repeat="r r8"> + <span tal:define="y0 x0;y1 x1;y2 x2;y3 x3;y4 x4;y5 x5;y6 x6;y7 x7"> + <td bgcolor="white" tal:content="y0"></td> + <td bgcolor="white" tal:content="y1"></td> + <td bgcolor="white" tal:content="y2"></td> + <td bgcolor="white" tal:content="y3"></td> + <td bgcolor="white" tal:content="y4"></td> + <td bgcolor="white" tal:content="y5"></td> + <td bgcolor="white" tal:content="y6"></td> + <td bgcolor="white" tal:content="y7"></td> + </span> +</dtml-in> diff --git a/changes.txt b/changes.txt new file mode 100644 index 0000000..a657fcb --- /dev/null +++ b/changes.txt @@ -0,0 +1,11 @@ +TAL changes + + This file contains change information for the current release. + Change information for previous versions can be found in the + file HISTORY.txt. + + Version 1.5.0 + + Features Added + + - Line and column numbers are added to more exceptions. diff --git a/driver.py b/driver.py new file mode 100644 index 0000000..6e43238 --- /dev/null +++ b/driver.py @@ -0,0 +1,197 @@ +#!/usr/bin/env python +############################################################################## +# +# Copyright (c) 2001, 2002 Zope Corporation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +""" +Driver program to test METAL and TAL implementation. + +Usage: driver.py [options] [file] +Options: + -h / --help + Print this message and exit. + -H / --html + -x / --xml + Explicitly choose HTML or XML input. The default is to automatically + select based on the file extension. These options are mutually + exclusive. + -l + Lenient structure insertion. + -m + Macro expansion only + -s + Print intermediate opcodes only + -t + Leave TAL/METAL attributes in output + -i + Leave I18N substitution strings un-interpolated. +""" + +import os +import sys + +import getopt + +if __name__ == "__main__": + import setpath # Local hack to tweak sys.path etc. + +# Import local classes +import zope.tal.taldefs +from zope.tal.dummyengine import DummyEngine +from zope.tal.dummyengine import DummyTranslationService + +FILE = "tests/input/test01.xml" + +class TestTranslations(DummyTranslationService): + def translate(self, domain, msgid, mapping=None, context=None, + target_language=None): + if msgid == 'timefmt': + return '%(minutes)s minutes after %(hours)s %(ampm)s' % mapping + elif msgid == 'jobnum': + return '%(jobnum)s is the JOB NUMBER' % mapping + elif msgid == 'verify': + s = 'Your contact email address is recorded as %(email)s' + return s % mapping + elif msgid == 'mailto:${request/submitter}': + return 'mailto:bperson@dom.ain' + elif msgid == 'origin': + return '%(name)s was born in %(country)s' % mapping + return DummyTranslationService.translate(self, domain, msgid, + mapping, context, + target_language) + +class TestEngine(DummyEngine): + def __init__(self, macros=None): + DummyEngine.__init__(self, macros) + self.translationService = TestTranslations() + + def evaluatePathOrVar(self, expr): + if expr == 'here/currentTime': + return {'hours' : 6, + 'minutes': 59, + 'ampm' : 'PM', + } + elif expr == 'context/@@object_name': + return '7' + elif expr == 'request/submitter': + return 'aperson@dom.ain' + return DummyEngine.evaluatePathOrVar(self, expr) + + +# This is a disgusting hack so that we can use engines that actually know +# something about certain object paths. TimeEngine knows about +# here/currentTime. +ENGINES = {'test23.html': TestEngine, + 'test24.html': TestEngine, + 'test26.html': TestEngine, + 'test27.html': TestEngine, + 'test28.html': TestEngine, + 'test29.html': TestEngine, + 'test30.html': TestEngine, + 'test31.html': TestEngine, + 'test32.html': TestEngine, + } + +def usage(code, msg=''): + # Python 2.1 required + print >> sys.stderr, __doc__ + if msg: + print >> sys.stderr, msg + sys.exit(code) + +def main(): + macros = 0 + mode = None + showcode = 0 + showtal = -1 + strictinsert = 1 + i18nInterpolate = 1 + try: + opts, args = getopt.getopt(sys.argv[1:], "hHxlmsti", + ['help', 'html', 'xml']) + except getopt.error, msg: + usage(2, msg) + for opt, arg in opts: + if opt in ('-h', '--help'): + usage(0) + if opt in ('-H', '--html'): + if mode == 'xml': + usage(1, '--html and --xml are mutually exclusive') + mode = "html" + if opt == '-l': + strictinsert = 0 + if opt == '-m': + macros = 1 + if opt == '-n': + versionTest = 0 + if opt in ('-x', '--xml'): + if mode == 'html': + usage(1, '--html and --xml are mutually exclusive') + mode = "xml" + if opt == '-s': + showcode = 1 + if opt == '-t': + showtal = 1 + if opt == '-i': + i18nInterpolate = 0 + if args: + file = args[0] + else: + file = FILE + it = compilefile(file, mode) + if showcode: + showit(it) + else: + # See if we need a special engine for this test + engine = None + engineClass = ENGINES.get(os.path.basename(file)) + if engineClass is not None: + engine = engineClass(macros) + interpretit(it, engine=engine, + tal=(not macros), showtal=showtal, + strictinsert=strictinsert, + i18nInterpolate=i18nInterpolate) + +def interpretit(it, engine=None, stream=None, tal=1, showtal=-1, + strictinsert=1, i18nInterpolate=1): + from zope.tal.talinterpreter import TALInterpreter + program, macros = it + assert zope.tal.taldefs.isCurrentVersion(program) + if engine is None: + engine = DummyEngine(macros) + TALInterpreter(program, macros, engine, stream, wrap=0, + tal=tal, showtal=showtal, strictinsert=strictinsert, + i18nInterpolate=i18nInterpolate)() + +def compilefile(file, mode=None): + assert mode in ("html", "xml", None) + if mode is None: + ext = os.path.splitext(file)[1] + if ext.lower() in (".html", ".htm"): + mode = "html" + else: + mode = "xml" + if mode == "html": + from zope.tal.htmltalparser import HTMLTALParser + p = HTMLTALParser() + else: + from zope.tal.talparser import TALParser + p = TALParser() + p.parseFile(file) + return p.getCode() + +def showit(it): + from pprint import pprint + pprint(it) + +if __name__ == "__main__": + main() diff --git a/dummyengine.py b/dummyengine.py new file mode 100644 index 0000000..67ff07b --- /dev/null +++ b/dummyengine.py @@ -0,0 +1,241 @@ +############################################################################## +# +# Copyright (c) 2001, 2002 Zope Corporation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +""" +Dummy TALES engine so that I can test out the TAL implementation. +""" + +import re +import sys + +from zope.tal.taldefs import NAME_RE, TALESError, ErrorInfo +from zope.tal.interfaces import ITALESCompiler, ITALESEngine +from zope.interfaces.i18n import ITranslationService +from zope.interfaces.i18n import IDomain + +Default = object() + +name_match = re.compile(r"(?s)(%s):(.*)\Z" % NAME_RE).match + +class CompilerError(Exception): + pass + +class DummyEngine: + + position = None + source_file = None + + __implements__ = ITALESCompiler, ITALESEngine + + def __init__(self, macros=None): + if macros is None: + macros = {} + self.macros = macros + dict = {'nothing': None, 'default': Default} + self.locals = self.globals = dict + self.stack = [dict] + self.translationService = DummyTranslationService() + + def getCompilerError(self): + return CompilerError + + def setSourceFile(self, source_file): + self.source_file = source_file + + def setPosition(self, position): + self.position = position + + def compile(self, expr): + return "$%s$" % expr + + def uncompile(self, expression): + assert (expression.startswith("$") and expression.endswith("$"), + expression) + return expression[1:-1] + + def beginScope(self): + self.stack.append(self.locals) + + def endScope(self): + assert len(self.stack) > 1, "more endScope() than beginScope() calls" + self.locals = self.stack.pop() + + def setLocal(self, name, value): + if self.locals is self.stack[-1]: + # Unmerge this scope's locals from previous scope of first set + self.locals = self.locals.copy() + self.locals[name] = value + + def setGlobal(self, name, value): + self.globals[name] = value + + def evaluate(self, expression): + assert (expression.startswith("$") and expression.endswith("$"), + expression) + expression = expression[1:-1] + m = name_match(expression) + if m: + type, expr = m.group(1, 2) + else: + type = "path" + expr = expression + if type in ("string", "str"): + return expr + if type in ("path", "var", "global", "local"): + return self.evaluatePathOrVar(expr) + if type == "not": + return not self.evaluate(expr) + if type == "exists": + return self.locals.has_key(expr) or self.globals.has_key(expr) + if type == "python": + try: + return eval(expr, self.globals, self.locals) + except: + raise TALESError("evaluation error in %s" % `expr`) + if type == "position": + # Insert the current source file name, line number, + # and column offset. + if self.position: + lineno, offset = self.position + else: + lineno, offset = None, None + return '%s (%s,%s)' % (self.source_file, lineno, offset) + raise TALESError("unrecognized expression: " + `expression`) + + def evaluatePathOrVar(self, expr): + expr = expr.strip() + if self.locals.has_key(expr): + return self.locals[expr] + elif self.globals.has_key(expr): + return self.globals[expr] + else: + raise TALESError("unknown variable: %s" % `expr`) + + def evaluateValue(self, expr): + return self.evaluate(expr) + + def evaluateBoolean(self, expr): + return self.evaluate(expr) + + def evaluateText(self, expr): + text = self.evaluate(expr) + if text is not None and text is not Default: + text = str(text) + return text + + def evaluateStructure(self, expr): + # XXX Should return None or a DOM tree + return self.evaluate(expr) + + def evaluateSequence(self, expr): + # XXX Should return a sequence + return self.evaluate(expr) + + def evaluateMacro(self, macroName): + assert (macroName.startswith("$") and macroName.endswith("$"), + macroName) + macroName = macroName[1:-1] + file, localName = self.findMacroFile(macroName) + if not file: + # Local macro + macro = self.macros[localName] + else: + # External macro + import driver + program, macros = driver.compilefile(file) + macro = macros.get(localName) + if not macro: + raise TALESError("macro %s not found in file %s" % + (localName, file)) + return macro + + def findMacroDocument(self, macroName): + file, localName = self.findMacroFile(macroName) + if not file: + return file, localName + import driver + doc = driver.parsefile(file) + return doc, localName + + def findMacroFile(self, macroName): + if not macroName: + raise TALESError("empty macro name") + i = macroName.rfind('/') + if i < 0: + # No slash -- must be a locally defined macro + return None, macroName + else: + # Up to last slash is the filename + fileName = macroName[:i] + localName = macroName[i+1:] + return fileName, localName + + def setRepeat(self, name, expr): + seq = self.evaluateSequence(expr) + return Iterator(name, seq, self) + + def createErrorInfo(self, err, position): + return ErrorInfo(err, position) + + def getDefault(self): + return Default + + def translate(self, domain, msgid, mapping): + return self.translationService.translate(domain, msgid, mapping) + + +class Iterator: + + def __init__(self, name, seq, engine): + self.name = name + self.seq = seq + self.engine = engine + self.nextIndex = 0 + + def next(self): + i = self.nextIndex + try: + item = self.seq[i] + except IndexError: + return 0 + self.nextIndex = i+1 + self.engine.setLocal(self.name, item) + return 1 + +class DummyDomain: + __implements__ = IDomain + + def translate(self, msgid, mapping=None, context=None, + target_language=None): + # This is a fake translation service which simply uppercases non + # ${name} placeholder text in the message id. + # + # First, transform a string with ${name} placeholders into a list of + # substrings. Then upcase everything but the placeholders, then glue + # things back together. + def repl(m): + return mapping[m.group(m.lastindex).lower()] + cre = re.compile(r'\$(?:([_A-Z]\w*)|\{([_A-Z]\w*)\})') + return cre.sub(repl, msgid.upper()) + +class DummyTranslationService: + __implements__ = ITranslationService + + def translate(self, domain, msgid, mapping=None, context=None, + target_language=None): + # Ignore domain + return self.getDomain(domain).translate(msgid, mapping, context, + target_language) + + def getDomain(self, domain): + return DummyDomain() diff --git a/history.txt b/history.txt new file mode 100644 index 0000000..48132fd --- /dev/null +++ b/history.txt @@ -0,0 +1,72 @@ +TAL history + + This file contains change information for previous versions. + Change information for the current release can be found + in the file CHANGES.txt. + + Version 1.4.0 + + Features Added + + - Added TAL statement: omit_tag="[<boolean expr>]" replaces + the statement tag with its contents if the boolean + expression is true or omitted. + + - The TAL and METAL namespaces can be applied to tag names, + tags in these namespaces are removed from rendered output + (leaving the contents in place, as with omit_tag) + whenever attributes in these namespaces would be, and + tag attributes without explicit namespaces default to the + tag's namespace (per XML spec). + + Version 1.3.3 + + Bugs Fixed + + - tal:atributes was creating stray attributes in METAL + expansion, and there was no unit test for this behavior. + + - tal:attributes parsing was not catching badly malformed + values, and used "print" instead of raising exceptions. + + Version 1.3.2 + + Features Added + + - Adopted Zope-style CHANGES.txt and HISTORY.txt + - Improved execution performance + - Added simple ZPT vs. TAL vs. DTML benchmarks, run by markbench.py + + Version 1.3.0 + + Features Added + + - New builtin variable 'attrs'. + + Bug Fixed + + - Nested macros were not working correctly. + + Version 1.2.0 + + Features Added + + - The 'if' path modifier can cancel any TAL action. + + Bug Fixed + + - tal:attributes inserted empty attributes into source. + + Version 1.1.0 + + Features Added + - TAL does not try to parse replacement structural text. + - Changed tests to match TAL's omitted attributes. + + Version 1.0.0 + + - Various minor bugs fixed + + Version 1.0.0b1 + + - All functionality described in the Project Wiki is implemented diff --git a/htmltalparser.py b/htmltalparser.py new file mode 100644 index 0000000..7ddd762 --- /dev/null +++ b/htmltalparser.py @@ -0,0 +1,309 @@ +############################################################################## +# +# Copyright (c) 2001, 2002 Zope Corporation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +""" +Parse HTML and compile to TALInterpreter intermediate code. +""" + +import sys + +from HTMLParser import HTMLParser, HTMLParseError + +from zope.tal.taldefs import ZOPE_METAL_NS, ZOPE_TAL_NS, ZOPE_I18N_NS, \ + METALError, TALError, I18NError +from zope.tal.talgenerator import TALGenerator + + +BOOLEAN_HTML_ATTRS = [ + # List of Boolean attributes in HTML that may be given in + # minimized form (e.g. <img ismap> rather than <img ismap="">) + # From http://www.w3.org/TR/xhtml1/#guidelines (C.10) + "compact", "nowrap", "ismap", "declare", "noshade", "checked", + "disabled", "readonly", "multiple", "selected", "noresize", + "defer" + ] + +EMPTY_HTML_TAGS = [ + # List of HTML tags with an empty content model; these are + # rendered in minimized form, e.g. <img />. + # From http://www.w3.org/TR/xhtml1/#dtds + "base", "meta", "link", "hr", "br", "param", "img", "area", + "input", "col", "basefont", "isindex", "frame", + ] + +PARA_LEVEL_HTML_TAGS = [ + # List of HTML elements that close open paragraph-level elements + # and are themselves paragraph-level. + "h1", "h2", "h3", "h4", "h5", "h6", "p", + ] + +BLOCK_CLOSING_TAG_MAP = { + "tr": ("tr", "td", "th"), + "td": ("td", "th"), + "th": ("td", "th"), + "li": ("li",), + "dd": ("dd", "dt"), + "dt": ("dd", "dt"), + } + +BLOCK_LEVEL_HTML_TAGS = [ + # List of HTML tags that denote larger sections than paragraphs. + "blockquote", "table", "tr", "th", "td", "thead", "tfoot", "tbody", + "noframe", "ul", "ol", "li", "dl", "dt", "dd", "div", + ] + +TIGHTEN_IMPLICIT_CLOSE_TAGS = (PARA_LEVEL_HTML_TAGS + + BLOCK_CLOSING_TAG_MAP.keys()) + + +class NestingError(HTMLParseError): + """Exception raised when elements aren't properly nested.""" + + def __init__(self, tagstack, endtag, position=(None, None)): + self.endtag = endtag + if tagstack: + if len(tagstack) == 1: + msg = ('Open tag <%s> does not match close tag </%s>' + % (tagstack[0], endtag)) + else: + msg = ('Open tags <%s> do not match close tag </%s>' + % ('>, <'.join(tagstack), endtag)) + else: + msg = 'No tags are open to match </%s>' % endtag + HTMLParseError.__init__(self, msg, position) + +class EmptyTagError(NestingError): + """Exception raised when empty elements have an end tag.""" + + def __init__(self, tag, position=(None, None)): + self.tag = tag + msg = 'Close tag </%s> should be removed' % tag + HTMLParseError.__init__(self, msg, position) + +class OpenTagError(NestingError): + """Exception raised when a tag is not allowed in another tag.""" + + def __init__(self, tagstack, tag, position=(None, None)): + self.tag = tag + msg = 'Tag <%s> is not allowed in <%s>' % (tag, tagstack[-1]) + HTMLParseError.__init__(self, msg, position) + +class HTMLTALParser(HTMLParser): + + # External API + + def __init__(self, gen=None): + HTMLParser.__init__(self) + if gen is None: + gen = TALGenerator(xml=0) + self.gen = gen + self.tagstack = [] + self.nsstack = [] + self.nsdict = {'tal': ZOPE_TAL_NS, + 'metal': ZOPE_METAL_NS, + 'i18n': ZOPE_I18N_NS, + } + + def parseFile(self, file): + f = open(file) + data = f.read() + f.close() + try: + self.parseString(data) + except TALError, e: + e.setFile(file) + raise + + def parseString(self, data): + self.feed(data) + self.close() + while self.tagstack: + self.implied_endtag(self.tagstack[-1], 2) + assert self.nsstack == [], self.nsstack + + def getCode(self): + return self.gen.getCode() + + def getWarnings(self): + return () + + # Overriding HTMLParser methods + + def handle_starttag(self, tag, attrs): + self.close_para_tags(tag) + self.scan_xmlns(attrs) + tag, attrlist, taldict, metaldict, i18ndict \ + = self.process_ns(tag, attrs) + self.tagstack.append(tag) + self.gen.emitStartElement(tag, attrlist, taldict, metaldict, i18ndict, + self.getpos()) + if tag in EMPTY_HTML_TAGS: + self.implied_endtag(tag, -1) + + def handle_startendtag(self, tag, attrs): + self.close_para_tags(tag) + self.scan_xmlns(attrs) + tag, attrlist, taldict, metaldict, i18ndict \ + = self.process_ns(tag, attrs) + if taldict.get("content"): + self.gen.emitStartElement(tag, attrlist, taldict, metaldict, + i18ndict, self.getpos()) + self.gen.emitEndElement(tag, implied=-1) + else: + self.gen.emitStartElement(tag, attrlist, taldict, metaldict, + i18ndict, self.getpos(), isend=1) + self.pop_xmlns() + + def handle_endtag(self, tag): + if tag in EMPTY_HTML_TAGS: + # </img> etc. in the source is an error + raise EmptyTagError(tag, self.getpos()) + self.close_enclosed_tags(tag) + self.gen.emitEndElement(tag) + self.pop_xmlns() + self.tagstack.pop() + + def close_para_tags(self, tag): + if tag in EMPTY_HTML_TAGS: + return + close_to = -1 + if BLOCK_CLOSING_TAG_MAP.has_key(tag): + blocks_to_close = BLOCK_CLOSING_TAG_MAP[tag] + for i in range(len(self.tagstack)): + t = self.tagstack[i] + if t in blocks_to_close: + if close_to == -1: + close_to = i + elif t in BLOCK_LEVEL_HTML_TAGS: + close_to = -1 + elif tag in PARA_LEVEL_HTML_TAGS + BLOCK_LEVEL_HTML_TAGS: + i = len(self.tagstack) - 1 + while i >= 0: + closetag = self.tagstack[i] + if closetag in BLOCK_LEVEL_HTML_TAGS: + break + if closetag in PARA_LEVEL_HTML_TAGS: + if closetag != "p": + raise OpenTagError(self.tagstack, tag, self.getpos()) + close_to = i + i = i - 1 + if close_to >= 0: + while len(self.tagstack) > close_to: + self.implied_endtag(self.tagstack[-1], 1) + + def close_enclosed_tags(self, tag): + if tag not in self.tagstack: + raise NestingError(self.tagstack, tag, self.getpos()) + while tag != self.tagstack[-1]: + self.implied_endtag(self.tagstack[-1], 1) + assert self.tagstack[-1] == tag + + def implied_endtag(self, tag, implied): + assert tag == self.tagstack[-1] + assert implied in (-1, 1, 2) + isend = (implied < 0) + if tag in TIGHTEN_IMPLICIT_CLOSE_TAGS: + # Pick out trailing whitespace from the program, and + # insert the close tag before the whitespace. + white = self.gen.unEmitWhitespace() + else: + white = None + self.gen.emitEndElement(tag, isend=isend, implied=implied) + if white: + self.gen.emitRawText(white) + self.tagstack.pop() + self.pop_xmlns() + + def handle_charref(self, name): + self.gen.emitRawText("&#%s;" % name) + + def handle_entityref(self, name): + self.gen.emitRawText("&%s;" % name) + + def handle_data(self, data): + self.gen.emitRawText(data) + + def handle_comment(self, data): + self.gen.emitRawText("<!--%s-->" % data) + + def handle_decl(self, data): + self.gen.emitRawText("<!%s>" % data) + + def handle_pi(self, data): + self.gen.emitRawText("<?%s>" % data) + + # Internal thingies + + def scan_xmlns(self, attrs): + nsnew = {} + for key, value in attrs: + if key.startswith("xmlns:"): + nsnew[key[6:]] = value + if nsnew: + self.nsstack.append(self.nsdict) + self.nsdict = self.nsdict.copy() + self.nsdict.update(nsnew) + else: + self.nsstack.append(self.nsdict) + + def pop_xmlns(self): + self.nsdict = self.nsstack.pop() + + def fixname(self, name): + if ':' in name: + prefix, suffix = name.split(':', 1) + if prefix == 'xmlns': + nsuri = self.nsdict.get(suffix) + if nsuri in (ZOPE_TAL_NS, ZOPE_METAL_NS, ZOPE_I18N_NS): + return name, name, prefix + else: + nsuri = self.nsdict.get(prefix) + if nsuri == ZOPE_TAL_NS: + return name, suffix, 'tal' + elif nsuri == ZOPE_METAL_NS: + return name, suffix, 'metal' + elif nsuri == ZOPE_I18N_NS: + return name, suffix, 'i18n' + return name, name, 0 + + def process_ns(self, name, attrs): + attrlist = [] + taldict = {} + metaldict = {} + i18ndict = {} + name, namebase, namens = self.fixname(name) + for item in attrs: + key, value = item + key, keybase, keyns = self.fixname(key) + ns = keyns or namens # default to tag namespace + if ns and ns != 'unknown': + item = (key, value, ns) + if ns == 'tal': + if taldict.has_key(keybase): + raise TALError("duplicate TAL attribute " + + `keybase`, self.getpos()) + taldict[keybase] = value + elif ns == 'metal': + if metaldict.has_key(keybase): + raise METALError("duplicate METAL attribute " + + `keybase`, self.getpos()) + metaldict[keybase] = value + elif ns == 'i18n': + if i18ndict.has_key(keybase): + raise I18NError("duplicate i18n attribute " + + `keybase`, self.getpos()) + i18ndict[keybase] = value + attrlist.append(item) + if namens in ('metal', 'tal'): + taldict['tal tag'] = namens + return name, attrlist, taldict, metaldict, i18ndict diff --git a/interfaces.py b/interfaces.py new file mode 100644 index 0000000..04202c6 --- /dev/null +++ b/interfaces.py @@ -0,0 +1,147 @@ +"""Interface that a TALES engine provides to the METAL/TAL implementation.""" + +from zope.interface import Attribute, Interface + + +class ITALESCompiler(Interface): + """Compile-time interface provided by a TALES implementation. + + The TAL compiler needs an instance of this interface to support + compilation of TALES expressions embedded in documents containing + TAL and METAL constructs. + """ + + def getCompilerError(): + """Return the exception class raised for compilation errors. + """ + + def compile(expression): + """Return a compiled form of 'expression' for later evaluation. + + 'expression' is the source text of the expression. + + The return value may be passed to the various evaluate*() + methods of the ITALESEngine interface. No compatibility is + required for the values of the compiled expression between + different ITALESEngine implementations. + """ + + +class ITALESEngine(Interface): + """Render-time interface provided by a TALES implementation. + + The TAL interpreter uses this interface to TALES to support + evaluation of the compiled expressions returned by + ITALESCompiler.compile(). + """ + + def getDefault(): + """Return the value of the 'default' TALES expression. + + Checking a value for a match with 'default' should be done + using the 'is' operator in Python. + """ + + def setPosition((lineno, offset)): + """Inform the engine of the current position in the source file. + + This is used to allow the evaluation engine to report + execution errors so that site developers can more easily + locate the offending expression. + """ + + def setSourceFile(filename): + """Inform the engine of the name of the current source file. + + This is used to allow the evaluation engine to report + execution errors so that site developers can more easily + locate the offending expression. + """ + + def beginScope(): + """Push a new scope onto the stack of open scopes. + """ + + def endScope(): + """Pop one scope from the stack of open scopes. + """ + + def evaluate(compiled_expression): + """Evaluate an arbitrary expression. + + No constraints are imposed on the return value. + """ + + def evaluateBoolean(compiled_expression): + """Evaluate an expression that must return a Boolean value. + """ + + def evaluateMacro(compiled_expression): + """Evaluate an expression that must return a macro program. + """ + + def evaluateStructure(compiled_expression): + """Evaluate an expression that must return a structured + document fragment. + + The result of evaluating 'compiled_expression' must be a + string containing a parsable HTML or XML fragment. Any TAL + markup cnotained in the result string will be interpreted. + """ + + def evaluateText(compiled_expression): + """Evaluate an expression that must return text. + + The returned text should be suitable for direct inclusion in + the output: any HTML or XML escaping or quoting is the + responsibility of the expression itself. + """ + + def evaluateValue(compiled_expression): + """Evaluate an arbitrary expression. + + No constraints are imposed on the return value. + """ + + def createErrorInfo(exception, (lineno, offset)): + """Returns an ITALESErrorInfo object. + + The returned object is used to provide information about the + error condition for the on-error handler. + """ + + def setGlobal(name, value): + """Set a global variable. + + The variable will be named 'name' and have the value 'value'. + """ + + def setLocal(name, value): + """Set a local variable in the current scope. + + The variable will be named 'name' and have the value 'value'. + """ + + def setRepeat(name, compiled_expression): + """ + """ + + def translate(domain, msgid, mapping): + """ + See ITranslationService.translate() + """ + + +class ITALESErrorInfo(Interface): + + type = Attribute("type", + "The exception class.") + + value = Attribute("value", + "The exception instance.") + + lineno = Attribute("lineno", + "The line number the error occurred on in the source.") + + offset = Attribute("offset", + "The character offset at which the error occurred.") diff --git a/ndiff.py b/ndiff.py new file mode 100644 index 0000000..b986911 --- /dev/null +++ b/ndiff.py @@ -0,0 +1,647 @@ +#! /usr/bin/env python +############################################################################## +# +# Copyright (c) 2001, 2002 Zope Corporation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## + +# Module ndiff version 1.6.0 +# Released to the public domain 08-Dec-2000, +# by Tim Peters (tim.one@home.com). + +# Provided as-is; use at your own risk; no warranty; no promises; enjoy! + +"""ndiff [-q] file1 file2 + or +ndiff (-r1 | -r2) < ndiff_output > file1_or_file2 + +Print a human-friendly file difference report to stdout. Both inter- +and intra-line differences are noted. In the second form, recreate file1 +(-r1) or file2 (-r2) on stdout, from an ndiff report on stdin. + +In the first form, if -q ("quiet") is not specified, the first two lines +of output are + +-: file1 ++: file2 + +Each remaining line begins with a two-letter code: + + "- " line unique to file1 + "+ " line unique to file2 + " " line common to both files + "? " line not present in either input file + +Lines beginning with "? " attempt to guide the eye to intraline +differences, and were not present in either input file. These lines can be +confusing if the source files contain tab characters. + +The first file can be recovered by retaining only lines that begin with +" " or "- ", and deleting those 2-character prefixes; use ndiff with -r1. + +The second file can be recovered similarly, but by retaining only " " and +"+ " lines; use ndiff with -r2; or, on Unix, the second file can be +recovered by piping the output through + + sed -n '/^[+ ] /s/^..//p' + +See module comments for details and programmatic interface. +""" + +__version__ = 1, 5, 0 + +# SequenceMatcher tries to compute a "human-friendly diff" between +# two sequences (chiefly picturing a file as a sequence of lines, +# and a line as a sequence of characters, here). Unlike e.g. UNIX(tm) +# diff, the fundamental notion is the longest *contiguous* & junk-free +# matching subsequence. That's what catches peoples' eyes. The +# Windows(tm) windiff has another interesting notion, pairing up elements +# that appear uniquely in each sequence. That, and the method here, +# appear to yield more intuitive difference reports than does diff. This +# method appears to be the least vulnerable to synching up on blocks +# of "junk lines", though (like blank lines in ordinary text files, +# or maybe "<P>" lines in HTML files). That may be because this is +# the only method of the 3 that has a *concept* of "junk" <wink>. +# +# Note that ndiff makes no claim to produce a *minimal* diff. To the +# contrary, minimal diffs are often counter-intuitive, because they +# synch up anywhere possible, sometimes accidental matches 100 pages +# apart. Restricting synch points to contiguous matches preserves some +# notion of locality, at the occasional cost of producing a longer diff. +# +# With respect to junk, an earlier version of ndiff simply refused to +# *start* a match with a junk element. The result was cases like this: +# before: private Thread currentThread; +# after: private volatile Thread currentThread; +# If you consider whitespace to be junk, the longest contiguous match +# not starting with junk is "e Thread currentThread". So ndiff reported +# that "e volatil" was inserted between the 't' and the 'e' in "private". +# While an accurate view, to people that's absurd. The current version +# looks for matching blocks that are entirely junk-free, then extends the +# longest one of those as far as possible but only with matching junk. +# So now "currentThread" is matched, then extended to suck up the +# preceding blank; then "private" is matched, and extended to suck up the +# following blank; then "Thread" is matched; and finally ndiff reports +# that "volatile " was inserted before "Thread". The only quibble +# remaining is that perhaps it was really the case that " volatile" +# was inserted after "private". I can live with that <wink>. +# +# NOTE on junk: the module-level names +# IS_LINE_JUNK +# IS_CHARACTER_JUNK +# can be set to any functions you like. The first one should accept +# a single string argument, and return true iff the string is junk. +# The default is whether the regexp r"\s*#?\s*$" matches (i.e., a +# line without visible characters, except for at most one splat). +# The second should accept a string of length 1 etc. The default is +# whether the character is a blank or tab (note: bad idea to include +# newline in this!). +# +# After setting those, you can call fcompare(f1name, f2name) with the +# names of the files you want to compare. The difference report +# is sent to stdout. Or you can call main(args), passing what would +# have been in sys.argv[1:] had the cmd-line form been used. + +TRACE = 0 + +# define what "junk" means +import re + +def IS_LINE_JUNK(line, pat=re.compile(r"\s*#?\s*$").match): + return pat(line) is not None + +def IS_CHARACTER_JUNK(ch, ws=" \t"): + return ch in ws + +del re + +class SequenceMatcher: + def __init__(self, isjunk=None, a='', b=''): + # Members: + # a + # first sequence + # b + # second sequence; differences are computed as "what do + # we need to do to 'a' to change it into 'b'?" + # b2j + # for x in b, b2j[x] is a list of the indices (into b) + # at which x appears; junk elements do not appear + # b2jhas + # b2j.has_key + # fullbcount + # for x in b, fullbcount[x] == the number of times x + # appears in b; only materialized if really needed (used + # only for computing quick_ratio()) + # matching_blocks + # a list of (i, j, k) triples, where a[i:i+k] == b[j:j+k]; + # ascending & non-overlapping in i and in j; terminated by + # a dummy (len(a), len(b), 0) sentinel + # opcodes + # a list of (tag, i1, i2, j1, j2) tuples, where tag is + # one of + # 'replace' a[i1:i2] should be replaced by b[j1:j2] + # 'delete' a[i1:i2] should be deleted + # 'insert' b[j1:j2] should be inserted + # 'equal' a[i1:i2] == b[j1:j2] + # isjunk + # a user-supplied function taking a sequence element and + # returning true iff the element is "junk" -- this has + # subtle but helpful effects on the algorithm, which I'll + # get around to writing up someday <0.9 wink>. + # DON'T USE! Only __chain_b uses this. Use isbjunk. + # isbjunk + # for x in b, isbjunk(x) == isjunk(x) but much faster; + # it's really the has_key method of a hidden dict. + # DOES NOT WORK for x in a! + + self.isjunk = isjunk + self.a = self.b = None + self.set_seqs(a, b) + + def set_seqs(self, a, b): + self.set_seq1(a) + self.set_seq2(b) + + def set_seq1(self, a): + if a is self.a: + return + self.a = a + self.matching_blocks = self.opcodes = None + + def set_seq2(self, b): + if b is self.b: + return + self.b = b + self.matching_blocks = self.opcodes = None + self.fullbcount = None + self.__chain_b() + + # For each element x in b, set b2j[x] to a list of the indices in + # b where x appears; the indices are in increasing order; note that + # the number of times x appears in b is len(b2j[x]) ... + # when self.isjunk is defined, junk elements don't show up in this + # map at all, which stops the central find_longest_match method + # from starting any matching block at a junk element ... + # also creates the fast isbjunk function ... + # note that this is only called when b changes; so for cross-product + # kinds of matches, it's best to call set_seq2 once, then set_seq1 + # repeatedly + + def __chain_b(self): + # Because isjunk is a user-defined (not C) function, and we test + # for junk a LOT, it's important to minimize the number of calls. + # Before the tricks described here, __chain_b was by far the most + # time-consuming routine in the whole module! If anyone sees + # Jim Roskind, thank him again for profile.py -- I never would + # have guessed that. + # The first trick is to build b2j ignoring the possibility + # of junk. I.e., we don't call isjunk at all yet. Throwing + # out the junk later is much cheaper than building b2j "right" + # from the start. + b = self.b + self.b2j = b2j = {} + self.b2jhas = b2jhas = b2j.has_key + for i in xrange(len(b)): + elt = b[i] + if b2jhas(elt): + b2j[elt].append(i) + else: + b2j[elt] = [i] + + # Now b2j.keys() contains elements uniquely, and especially when + # the sequence is a string, that's usually a good deal smaller + # than len(string). The difference is the number of isjunk calls + # saved. + isjunk, junkdict = self.isjunk, {} + if isjunk: + for elt in b2j.keys(): + if isjunk(elt): + junkdict[elt] = 1 # value irrelevant; it's a set + del b2j[elt] + + # Now for x in b, isjunk(x) == junkdict.has_key(x), but the + # latter is much faster. Note too that while there may be a + # lot of junk in the sequence, the number of *unique* junk + # elements is probably small. So the memory burden of keeping + # this dict alive is likely trivial compared to the size of b2j. + self.isbjunk = junkdict.has_key + + def find_longest_match(self, alo, ahi, blo, bhi): + """Find longest matching block in a[alo:ahi] and b[blo:bhi]. + + If isjunk is not defined: + + Return (i,j,k) such that a[i:i+k] is equal to b[j:j+k], where + alo <= i <= i+k <= ahi + blo <= j <= j+k <= bhi + and for all (i',j',k') meeting those conditions, + k >= k' + i <= i' + and if i == i', j <= j' + In other words, of all maximal matching blocks, return one + that starts earliest in a, and of all those maximal matching + blocks that start earliest in a, return the one that starts + earliest in b. + + If isjunk is defined, first the longest matching block is + determined as above, but with the additional restriction that + no junk element appears in the block. Then that block is + extended as far as possible by matching (only) junk elements on + both sides. So the resulting block never matches on junk except + as identical junk happens to be adjacent to an "interesting" + match. + + If no blocks match, return (alo, blo, 0). + """ + + # CAUTION: stripping common prefix or suffix would be incorrect. + # E.g., + # ab + # acab + # Longest matching block is "ab", but if common prefix is + # stripped, it's "a" (tied with "b"). UNIX(tm) diff does so + # strip, so ends up claiming that ab is changed to acab by + # inserting "ca" in the middle. That's minimal but unintuitive: + # "it's obvious" that someone inserted "ac" at the front. + # Windiff ends up at the same place as diff, but by pairing up + # the unique 'b's and then matching the first two 'a's. + + a, b, b2j, isbjunk = self.a, self.b, self.b2j, self.isbjunk + besti, bestj, bestsize = alo, blo, 0 + # find longest junk-free match + # during an iteration of the loop, j2len[j] = length of longest + # junk-free match ending with a[i-1] and b[j] + j2len = {} + nothing = [] + for i in xrange(alo, ahi): + # look at all instances of a[i] in b; note that because + # b2j has no junk keys, the loop is skipped if a[i] is junk + j2lenget = j2len.get + newj2len = {} + for j in b2j.get(a[i], nothing): + # a[i] matches b[j] + if j < blo: + continue + if j >= bhi: + break + k = newj2len[j] = j2lenget(j-1, 0) + 1 + if k > bestsize: + besti, bestj, bestsize = i-k+1, j-k+1, k + j2len = newj2len + + # Now that we have a wholly interesting match (albeit possibly + # empty!), we may as well suck up the matching junk on each + # side of it too. Can't think of a good reason not to, and it + # saves post-processing the (possibly considerable) expense of + # figuring out what to do with it. In the case of an empty + # interesting match, this is clearly the right thing to do, + # because no other kind of match is possible in the regions. + while besti > alo and bestj > blo and \ + isbjunk(b[bestj-1]) and \ + a[besti-1] == b[bestj-1]: + besti, bestj, bestsize = besti-1, bestj-1, bestsize+1 + while besti+bestsize < ahi and bestj+bestsize < bhi and \ + isbjunk(b[bestj+bestsize]) and \ + a[besti+bestsize] == b[bestj+bestsize]: + bestsize = bestsize + 1 + + if TRACE: + print "get_matching_blocks", alo, ahi, blo, bhi + print " returns", besti, bestj, bestsize + return besti, bestj, bestsize + + def get_matching_blocks(self): + if self.matching_blocks is not None: + return self.matching_blocks + self.matching_blocks = [] + la, lb = len(self.a), len(self.b) + self.__helper(0, la, 0, lb, self.matching_blocks) + self.matching_blocks.append( (la, lb, 0) ) + if TRACE: + print '*** matching blocks', self.matching_blocks + return self.matching_blocks + + # builds list of matching blocks covering a[alo:ahi] and + # b[blo:bhi], appending them in increasing order to answer + + def __helper(self, alo, ahi, blo, bhi, answer): + i, j, k = x = self.find_longest_match(alo, ahi, blo, bhi) + # a[alo:i] vs b[blo:j] unknown + # a[i:i+k] same as b[j:j+k] + # a[i+k:ahi] vs b[j+k:bhi] unknown + if k: + if alo < i and blo < j: + self.__helper(alo, i, blo, j, answer) + answer.append(x) + if i+k < ahi and j+k < bhi: + self.__helper(i+k, ahi, j+k, bhi, answer) + + def ratio(self): + """Return a measure of the sequences' similarity (float in [0,1]). + + Where T is the total number of elements in both sequences, and + M is the number of matches, this is 2*M / T. + Note that this is 1 if the sequences are identical, and 0 if + they have nothing in common. + """ + + matches = reduce(lambda sum, triple: sum + triple[-1], + self.get_matching_blocks(), 0) + return 2.0 * matches / (len(self.a) + len(self.b)) + + def quick_ratio(self): + """Return an upper bound on ratio() relatively quickly.""" + # viewing a and b as multisets, set matches to the cardinality + # of their intersection; this counts the number of matches + # without regard to order, so is clearly an upper bound + if self.fullbcount is None: + self.fullbcount = fullbcount = {} + for elt in self.b: + fullbcount[elt] = fullbcount.get(elt, 0) + 1 + fullbcount = self.fullbcount + # avail[x] is the number of times x appears in 'b' less the + # number of times we've seen it in 'a' so far ... kinda + avail = {} + availhas, matches = avail.has_key, 0 + for elt in self.a: + if availhas(elt): + numb = avail[elt] + else: + numb = fullbcount.get(elt, 0) + avail[elt] = numb - 1 + if numb > 0: + matches = matches + 1 + return 2.0 * matches / (len(self.a) + len(self.b)) + + def real_quick_ratio(self): + """Return an upper bound on ratio() very quickly""" + la, lb = len(self.a), len(self.b) + # can't have more matches than the number of elements in the + # shorter sequence + return 2.0 * min(la, lb) / (la + lb) + + def get_opcodes(self): + if self.opcodes is not None: + return self.opcodes + i = j = 0 + self.opcodes = answer = [] + for ai, bj, size in self.get_matching_blocks(): + # invariant: we've pumped out correct diffs to change + # a[:i] into b[:j], and the next matching block is + # a[ai:ai+size] == b[bj:bj+size]. So we need to pump + # out a diff to change a[i:ai] into b[j:bj], pump out + # the matching block, and move (i,j) beyond the match + tag = '' + if i < ai and j < bj: + tag = 'replace' + elif i < ai: + tag = 'delete' + elif j < bj: + tag = 'insert' + if tag: + answer.append( (tag, i, ai, j, bj) ) + i, j = ai+size, bj+size + # the list of matching blocks is terminated by a + # sentinel with size 0 + if size: + answer.append( ('equal', ai, i, bj, j) ) + return answer + +# meant for dumping lines +def dump(tag, x, lo, hi): + for i in xrange(lo, hi): + print tag, x[i], + +def plain_replace(a, alo, ahi, b, blo, bhi): + assert alo < ahi and blo < bhi + # dump the shorter block first -- reduces the burden on short-term + # memory if the blocks are of very different sizes + if bhi - blo < ahi - alo: + dump('+', b, blo, bhi) + dump('-', a, alo, ahi) + else: + dump('-', a, alo, ahi) + dump('+', b, blo, bhi) + +# When replacing one block of lines with another, this guy searches +# the blocks for *similar* lines; the best-matching pair (if any) is +# used as a synch point, and intraline difference marking is done on +# the similar pair. Lots of work, but often worth it. + +def fancy_replace(a, alo, ahi, b, blo, bhi): + if TRACE: + print '*** fancy_replace', alo, ahi, blo, bhi + dump('>', a, alo, ahi) + dump('<', b, blo, bhi) + + # don't synch up unless the lines have a similarity score of at + # least cutoff; best_ratio tracks the best score seen so far + best_ratio, cutoff = 0.74, 0.75 + cruncher = SequenceMatcher(IS_CHARACTER_JUNK) + eqi, eqj = None, None # 1st indices of equal lines (if any) + + # search for the pair that matches best without being identical + # (identical lines must be junk lines, & we don't want to synch up + # on junk -- unless we have to) + for j in xrange(blo, bhi): + bj = b[j] + cruncher.set_seq2(bj) + for i in xrange(alo, ahi): + ai = a[i] + if ai == bj: + if eqi is None: + eqi, eqj = i, j + continue + cruncher.set_seq1(ai) + # computing similarity is expensive, so use the quick + # upper bounds first -- have seen this speed up messy + # compares by a factor of 3. + # note that ratio() is only expensive to compute the first + # time it's called on a sequence pair; the expensive part + # of the computation is cached by cruncher + if cruncher.real_quick_ratio() > best_ratio and \ + cruncher.quick_ratio() > best_ratio and \ + cruncher.ratio() > best_ratio: + best_ratio, best_i, best_j = cruncher.ratio(), i, j + if best_ratio < cutoff: + # no non-identical "pretty close" pair + if eqi is None: + # no identical pair either -- treat it as a straight replace + plain_replace(a, alo, ahi, b, blo, bhi) + return + # no close pair, but an identical pair -- synch up on that + best_i, best_j, best_ratio = eqi, eqj, 1.0 + else: + # there's a close pair, so forget the identical pair (if any) + eqi = None + + # a[best_i] very similar to b[best_j]; eqi is None iff they're not + # identical + if TRACE: + print '*** best_ratio', best_ratio, best_i, best_j + dump('>', a, best_i, best_i+1) + dump('<', b, best_j, best_j+1) + + # pump out diffs from before the synch point + fancy_helper(a, alo, best_i, b, blo, best_j) + + # do intraline marking on the synch pair + aelt, belt = a[best_i], b[best_j] + if eqi is None: + # pump out a '-', '?', '+', '?' quad for the synched lines + atags = btags = "" + cruncher.set_seqs(aelt, belt) + for tag, ai1, ai2, bj1, bj2 in cruncher.get_opcodes(): + la, lb = ai2 - ai1, bj2 - bj1 + if tag == 'replace': + atags = atags + '^' * la + btags = btags + '^' * lb + elif tag == 'delete': + atags = atags + '-' * la + elif tag == 'insert': + btags = btags + '+' * lb + elif tag == 'equal': + atags = atags + ' ' * la + btags = btags + ' ' * lb + else: + raise ValueError, 'unknown tag ' + `tag` + printq(aelt, belt, atags, btags) + else: + # the synch pair is identical + print ' ', aelt, + + # pump out diffs from after the synch point + fancy_helper(a, best_i+1, ahi, b, best_j+1, bhi) + +def fancy_helper(a, alo, ahi, b, blo, bhi): + if alo < ahi: + if blo < bhi: + fancy_replace(a, alo, ahi, b, blo, bhi) + else: + dump('-', a, alo, ahi) + elif blo < bhi: + dump('+', b, blo, bhi) + +# Crap to deal with leading tabs in "?" output. Can hurt, but will +# probably help most of the time. + +def printq(aline, bline, atags, btags): + common = min(count_leading(aline, "\t"), + count_leading(bline, "\t")) + common = min(common, count_leading(atags[:common], " ")) + print "-", aline, + if count_leading(atags, " ") < len(atags): + print "?", "\t" * common + atags[common:] + print "+", bline, + if count_leading(btags, " ") < len(btags): + print "?", "\t" * common + btags[common:] + +def count_leading(line, ch): + i, n = 0, len(line) + while i < n and line[i] == ch: + i = i + 1 + return i + +def fail(msg): + import sys + out = sys.stderr.write + out(msg + "\n\n") + out(__doc__) + return 0 + +# open a file & return the file object; gripe and return 0 if it +# couldn't be opened +def fopen(fname): + try: + return open(fname, 'r') + except IOError, detail: + return fail("couldn't open " + fname + ": " + str(detail)) + +# open two files & spray the diff to stdout; return false iff a problem +def fcompare(f1name, f2name): + f1 = fopen(f1name) + f2 = fopen(f2name) + if not f1 or not f2: + return 0 + + a = f1.readlines(); f1.close() + b = f2.readlines(); f2.close() + + cruncher = SequenceMatcher(IS_LINE_JUNK, a, b) + for tag, alo, ahi, blo, bhi in cruncher.get_opcodes(): + if tag == 'replace': + fancy_replace(a, alo, ahi, b, blo, bhi) + elif tag == 'delete': + dump('-', a, alo, ahi) + elif tag == 'insert': + dump('+', b, blo, bhi) + elif tag == 'equal': + dump(' ', a, alo, ahi) + else: + raise ValueError, 'unknown tag ' + `tag` + + return 1 + +# crack args (sys.argv[1:] is normal) & compare; +# return false iff a problem + +def main(args): + import getopt + try: + opts, args = getopt.getopt(args, "qr:") + except getopt.error, detail: + return fail(str(detail)) + noisy = 1 + qseen = rseen = 0 + for opt, val in opts: + if opt == "-q": + qseen = 1 + noisy = 0 + elif opt == "-r": + rseen = 1 + whichfile = val + if qseen and rseen: + return fail("can't specify both -q and -r") + if rseen: + if args: + return fail("no args allowed with -r option") + if whichfile in "12": + restore(whichfile) + return 1 + return fail("-r value must be 1 or 2") + if len(args) != 2: + return fail("need 2 filename args") + f1name, f2name = args + if noisy: + print '-:', f1name + print '+:', f2name + return fcompare(f1name, f2name) + +def restore(which): + import sys + tag = {"1": "- ", "2": "+ "}[which] + prefixes = (" ", tag) + for line in sys.stdin.readlines(): + if line[:2] in prefixes: + print line[2:], + +if __name__ == '__main__': + import sys + args = sys.argv[1:] + if "-profile" in args: + import profile, pstats + args.remove("-profile") + statf = "ndiff.pro" + profile.run("main(args)", statf) + stats = pstats.Stats(statf) + stats.strip_dirs().sort_stats('time').print_stats() + else: + main(args) diff --git a/readme.txt b/readme.txt new file mode 100644 index 0000000..4a28816 --- /dev/null +++ b/readme.txt @@ -0,0 +1,97 @@ +TAL - Template Attribute Language +--------------------------------- + +This is an implementation of TAL, the Zope Template Attribute +Language. For TAL, see the Zope Presentation Templates ZWiki: + + http://dev.zope.org/Wikis/DevSite/Projects/ZPT/FrontPage + +It is not a Zope product nor is it designed exclusively to run inside +of Zope, but if you have a Zope checkout that includes +Products/ParsedXML, its Expat parser will be used. + +Prerequisites +------------- + +You need: + +- A recent checkout of Zope2; don't forget to run the wo_pcgi.py + script to compile everything. (See above -- this is now optional.) + +- A recent checkout of the Zope2 product ParsedXML, accessible + throught <Zope2>/lib/python/Products/ParsedXML; don't forget to run + the setup.py script to compiles Expat. (Again, optional.) + +- Python 1.5.2; the driver script refuses to work with other versions + unless you specify the -n option; this is done so that I don't + accidentally use Python 2.x features. + +- Create a .path file containing proper module search path; it should + point the <Zope2>/lib/python directory that you want to use. + +How To Play +----------- + +(Don't forget to edit .path, see above!) + +The script driver.py takes an XML file with TAL markup as argument and +writes the expanded version to standard output. The filename argument +defaults to tests/input/test01.xml. + +Regression test +--------------- + +There are unit test suites in the 'tests' subdirectory; these can be +run with tests/run.py. This should print the testcase names plus +progress info, followed by a final line saying "OK". It requires that +../unittest.py exists. + +There are a number of test files in the 'tests' subdirectory, named +tests/input/test<number>.xml and tests/input/test<number>.html. The +Python script ./runtest.py calls driver.main() for each test file, and +should print "<file> OK" for each one. These tests are also run as +part of the unit test suites, so tests/run.py is all you need. + +What's Here +----------- + +DummyEngine.py simple-minded TALES execution engine +TALInterpreter.py class to interpret intermediate code +TALGenerator.py class to generate intermediate code +XMLParser.py base class to parse XML, avoiding DOM +TALParser.py class to parse XML with TAL into intermediate code +HTMLTALParser.py class to parse HTML with TAL into intermediate code +HTMLParser.py HTML-parsing base class +driver.py script to demonstrate TAL expansion +timer.py script to time various processing phases +setpath.py hack to set sys.path and import ZODB +__init__.py empty file that makes this directory a package +runtest.py Python script to run file-comparison tests +ndiff.py helper for runtest.py to produce diffs +tests/ drectory with test files and output +tests/run.py Python script to run all tests + +Author and License +------------------ + +This code is written by Guido van Rossum (project lead), Fred Drake, +and Tim Peters. It is owned by Digital Creations and can be +redistributed under the Zope Public License. + +TO DO +----- + +(See also http://www.zope.org/Members/jim/ZPTIssueTracker .) + +- Need to remove leading whitespace and newline when omitting an + element (either through tal:replace with a value of nothing or + tal:condition with a false condition). + +- Empty TAL/METAL attributes are ignored: tal:replace="" is ignored + rather than causing an error. + +- HTMLTALParser.py and TALParser.py are silly names. Should be + HTMLTALCompiler.py and XMLTALCompiler.py (or maybe shortened, + without "TAL"?) + +- Should we preserve case of tags and attribute names in HTML? diff --git a/runtest.py b/runtest.py new file mode 100644 index 0000000..2c6dc34 --- /dev/null +++ b/runtest.py @@ -0,0 +1,152 @@ +#! /usr/bin/env python +############################################################################## +# +# Copyright (c) 2001, 2002 Zope Corporation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +""" +Driver program to run METAL and TAL regression tests. +""" + +import glob +import os +import sys +import traceback + +from cStringIO import StringIO + +if __name__ == "__main__": + import setpath # Local hack to tweak sys.path etc. + +import zope.tal.driver +import zope.tal.tests.utils + +def showdiff(a, b): + import ndiff + cruncher = ndiff.SequenceMatcher(ndiff.IS_LINE_JUNK, a, b) + for tag, alo, ahi, blo, bhi in cruncher.get_opcodes(): + if tag == "equal": + continue + print nicerange(alo, ahi) + tag[0] + nicerange(blo, bhi) + ndiff.dump('<', a, alo, ahi) + if a and b: + print '---' + ndiff.dump('>', b, blo, bhi) + +def nicerange(lo, hi): + if hi <= lo+1: + return str(lo+1) + else: + return "%d,%d" % (lo+1, hi) + +def main(): + opts = [] + args = sys.argv[1:] + quiet = 0 + unittesting = 0 + if args and args[0] == "-q": + quiet = 1 + del args[0] + if args and args[0] == "-Q": + unittesting = 1 + del args[0] + while args and args[0].startswith('-'): + opts.append(args[0]) + del args[0] + if not args: + prefix = os.path.join("tests", "input", "test*.") + if zope.tal.tests.utils.skipxml: + xmlargs = [] + else: + xmlargs = glob.glob(prefix + "xml") + xmlargs.sort() + htmlargs = glob.glob(prefix + "html") + htmlargs.sort() + args = xmlargs + htmlargs + if not args: + sys.stderr.write("No tests found -- please supply filenames\n") + sys.exit(1) + errors = 0 + for arg in args: + locopts = [] + if arg.find("metal") >= 0 and "-m" not in opts: + locopts.append("-m") + if not unittesting: + print arg, + sys.stdout.flush() + if zope.tal.tests.utils.skipxml and arg.endswith(".xml"): + print "SKIPPED (XML parser not available)" + continue + save = sys.stdout, sys.argv + try: + try: + sys.stdout = stdout = StringIO() + sys.argv = [""] + opts + locopts + [arg] + zope.tal.driver.main() + finally: + sys.stdout, sys.argv = save + except SystemExit: + raise + except: + errors = 1 + if quiet: + print sys.exc_type + sys.stdout.flush() + else: + if unittesting: + print + else: + print "Failed:" + sys.stdout.flush() + traceback.print_exc() + continue + head, tail = os.path.split(arg) + outfile = os.path.join( + head.replace("input", "output"), + tail) + try: + f = open(outfile) + except IOError: + expected = None + print "(missing file %s)" % outfile, + else: + expected = f.readlines() + f.close() + stdout.seek(0) + if hasattr(stdout, "readlines"): + actual = stdout.readlines() + else: + actual = readlines(stdout) + if actual == expected: + if not unittesting: + print "OK" + else: + if unittesting: + print + else: + print "not OK" + errors = 1 + if not quiet and expected is not None: + showdiff(expected, actual) + if errors: + sys.exit(1) + +def readlines(f): + L = [] + while 1: + line = f.readline() + if not line: + break + L.append(line) + return L + +if __name__ == "__main__": + main() diff --git a/setpath.py b/setpath.py new file mode 100644 index 0000000..e4bfade --- /dev/null +++ b/setpath.py @@ -0,0 +1,44 @@ +############################################################################## +# +# Copyright (c) 2001, 2002 Zope Corporation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +""" +Read a module search path from .path file. + +If .path file isn't found in the directory of the setpath.py module, then try +to import ZODB. If that succeeds, we assume the path is already set up +correctly. If that import fails, an IOError is raised. +""" +import os +import sys + +dir = os.path.dirname(__file__) +path = os.path.join(dir, ".path") +try: + f = open(path) +except IOError: + try: + # If we can import ZODB, our sys.path is set up well enough already + import zodb + except ImportError: + raise IOError("Can't find ZODB package. Please edit %s to point to " + "your Zope's lib/python directory" % path) +else: + for line in f.readlines(): + line = line.strip() + if line and line[0] != '#': + for dir in line.split(os.pathsep): + dir = os.path.expanduser(os.path.expandvars(dir)) + if dir not in sys.path: + sys.path.append(dir) + # Must import this first to initialize Persistence properly + import zodb diff --git a/taldefs.py b/taldefs.py new file mode 100644 index 0000000..dd84c2e --- /dev/null +++ b/taldefs.py @@ -0,0 +1,190 @@ +############################################################################## +# +# Copyright (c) 2001, 2002 Zope Corporation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +""" +Common definitions used by TAL and METAL compilation an transformation. +""" + +from zope.tal.interfaces import ITALESErrorInfo + +TAL_VERSION = "1.4" + +XML_NS = "http://www.w3.org/XML/1998/namespace" # URI for XML namespace +XMLNS_NS = "http://www.w3.org/2000/xmlns/" # URI for XML NS declarations + +ZOPE_TAL_NS = "http://xml.zope.org/namespaces/tal" +ZOPE_METAL_NS = "http://xml.zope.org/namespaces/metal" +ZOPE_I18N_NS = "http://xml.zope.org/namespaces/i18n" + +NAME_RE = "[a-zA-Z_][a-zA-Z0-9_]*" + +KNOWN_METAL_ATTRIBUTES = [ + "define-macro", + "use-macro", + "define-slot", + "fill-slot", + ] + +KNOWN_TAL_ATTRIBUTES = [ + "define", + "condition", + "content", + "replace", + "repeat", + "attributes", + "on-error", + "omit-tag", + "tal tag", + ] + +KNOWN_I18N_ATTRIBUTES = [ + "translate", + "domain", + "target", + "source", + "attributes", + "data", + "name", + ] + +class TALError(Exception): + + def __init__(self, msg, position=(None, None)): + assert msg != "" + self.msg = msg + self.lineno = position[0] + self.offset = position[1] + self.filename = None + + def setFile(self, filename): + self.filename = filename + + def __str__(self): + result = self.msg + if self.lineno is not None: + result = result + ", at line %d" % self.lineno + if self.offset is not None: + result = result + ", column %d" % (self.offset + 1) + if self.filename is not None: + result = result + ', in file %s' % self.filename + return result + +class METALError(TALError): + pass + +class TALESError(TALError): + pass + +class I18NError(TALError): + pass + + +class ErrorInfo: + + __implements__ = ITALESErrorInfo + + def __init__(self, err, position=(None, None)): + if isinstance(err, Exception): + self.type = err.__class__ + self.value = err + else: + self.type = err + self.value = None + self.lineno = position[0] + self.offset = position[1] + + + +import re +_attr_re = re.compile(r"\s*([^\s]+)\s+([^\s].*)\Z", re.S) +_subst_re = re.compile(r"\s*(?:(text|structure)\s+)?(.*)\Z", re.S) +del re + +def parseAttributeReplacements(arg): + dict = {} + for part in splitParts(arg): + m = _attr_re.match(part) + if not m: + raise TALError("Bad syntax in attributes:" + `part`) + name, expr = m.group(1, 2) + name = name.lower() + if dict.has_key(name): + raise TALError("Duplicate attribute name in attributes:" + `part`) + dict[name] = expr + return dict + +def parseSubstitution(arg, position=(None, None)): + m = _subst_re.match(arg) + if not m: + raise TALError("Bad syntax in substitution text: " + `arg`, position) + key, expr = m.group(1, 2) + if not key: + key = "text" + return key, expr + +def splitParts(arg): + # Break in pieces at undoubled semicolons and + # change double semicolons to singles: + arg = arg.replace(";;", "\0") + parts = arg.split(';') + parts = [p.replace("\0", ";") for p in parts] + if len(parts) > 1 and not parts[-1].strip(): + del parts[-1] # It ended in a semicolon + return parts + +def isCurrentVersion(program): + version = getProgramVersion(program) + return version == TAL_VERSION + +def getProgramMode(program): + version = getProgramVersion(program) + if (version == TAL_VERSION and isinstance(program[1], tuple) and + len(program[1]) == 2): + opcode, mode = program[1] + if opcode == "mode": + return mode + return None + +def getProgramVersion(program): + if (len(program) >= 2 and + isinstance(program[0], tuple) and len(program[0]) == 2): + opcode, version = program[0] + if opcode == "version": + return version + return None + +import re +_ent1_re = re.compile('&(?![A-Z#])', re.I) +_entch_re = re.compile('&([A-Z][A-Z0-9]*)(?![A-Z0-9;])', re.I) +_entn1_re = re.compile('&#(?![0-9X])', re.I) +_entnx_re = re.compile('&(#X[A-F0-9]*)(?![A-F0-9;])', re.I) +_entnd_re = re.compile('&(#[0-9][0-9]*)(?![0-9;])') +del re + +def attrEscape(s): + """Replace special characters '&<>' by character entities, + except when '&' already begins a syntactically valid entity.""" + s = _ent1_re.sub('&', 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/talgenerator.py b/talgenerator.py new file mode 100644 index 0000000..c67b3d4 --- /dev/null +++ b/talgenerator.py @@ -0,0 +1,785 @@ +############################################################################## +# +# Copyright (c) 2001, 2002 Zope Corporation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +""" +Code generator for TALInterpreter intermediate code. +""" + +import cgi +import re + +from zope.tal import taldefs +from zope.tal.taldefs import NAME_RE, TAL_VERSION +from zope.tal.taldefs import I18NError, METALError, TALError +from zope.tal.taldefs import parseSubstitution +from zope.tal.translationcontext import TranslationContext, DEFAULT_DOMAIN + +I18N_REPLACE = 1 +I18N_CONTENT = 2 +I18N_EXPRESSION = 3 + + +class TALGenerator: + + inMacroUse = 0 + inMacroDef = 0 + source_file = None + + def __init__(self, expressionCompiler=None, xml=1, source_file=None): + if not expressionCompiler: + from zope.tal.dummyengine import DummyEngine + expressionCompiler = DummyEngine() + self.expressionCompiler = expressionCompiler + self.CompilerError = expressionCompiler.getCompilerError() + # This holds the emitted opcodes representing the input + self.program = [] + # The program stack for when we need to do some sub-evaluation for an + # intermediate result. E.g. in an i18n:name tag for which the + # contents describe the ${name} value. + self.stack = [] + # Another stack of postponed actions. Elements on this stack are a + # dictionary; key/values contain useful information that + # emitEndElement needs to finish its calculations + self.todoStack = [] + self.macros = {} + self.slots = {} + self.slotStack = [] + self.xml = xml + self.emit("version", TAL_VERSION) + self.emit("mode", xml and "xml" or "html") + if source_file is not None: + self.source_file = source_file + self.emit("setSourceFile", source_file) + self.i18nContext = TranslationContext() + + def getCode(self): + assert not self.stack + assert not self.todoStack + return self.optimize(self.program), self.macros + + def optimize(self, program): + output = [] + collect = [] + rawseen = cursor = 0 + if self.xml: + endsep = "/>" + else: + endsep = " />" + for cursor in xrange(len(program)+1): + try: + item = program[cursor] + except IndexError: + item = (None, None) + opcode = item[0] + if opcode == "rawtext": + collect.append(item[1]) + continue + if opcode == "endTag": + collect.append("</%s>" % item[1]) + continue + if opcode == "startTag": + if self.optimizeStartTag(collect, item[1], item[2], ">"): + continue + if opcode == "startEndTag": + if self.optimizeStartTag(collect, item[1], item[2], endsep): + continue + if opcode in ("beginScope", "endScope"): + # Push *Scope instructions in front of any text instructions; + # this allows text instructions separated only by *Scope + # instructions to be joined together. + output.append(self.optimizeArgsList(item)) + continue + if opcode == 'noop': + # This is a spacer for end tags in the face of i18n:name + # attributes. We can't let the optimizer collect immediately + # following end tags into the same rawtextOffset. + opcode = None + pass + text = "".join(collect) + if text: + i = text.rfind("\n") + if i >= 0: + i = len(text) - (i + 1) + output.append(("rawtextColumn", (text, i))) + else: + output.append(("rawtextOffset", (text, len(text)))) + if opcode != None: + output.append(self.optimizeArgsList(item)) + rawseen = cursor+1 + collect = [] + return self.optimizeCommonTriple(output) + + def optimizeArgsList(self, item): + if len(item) == 2: + return item + else: + return item[0], tuple(item[1:]) + + # These codes are used to indicate what sort of special actions + # are needed for each special attribute. (Simple attributes don't + # get action codes.) + # + # The special actions (which are modal) are handled by + # TALInterpreter.attrAction() and .attrAction_tal(). + # + # Each attribute is represented by a tuple: + # + # (name, value) -- a simple name/value pair, with + # no special processing + # + # (name, value, action, *extra) -- attribute with special + # processing needs, action is a + # code that indicates which + # branch to take, and *extra + # contains additional, + # action-specific information + # needed by the processing + # + def optimizeStartTag(self, collect, name, attrlist, end): + # return true if the tag can be converted to plain text + if not attrlist: + collect.append("<%s%s" % (name, end)) + return 1 + opt = 1 + new = ["<" + name] + for i in range(len(attrlist)): + item = attrlist[i] + if len(item) > 2: + opt = 0 + name, value, action = item[:3] + attrlist[i] = (name, value, action) + item[3:] + else: + if item[1] is None: + s = item[0] + else: + s = '%s="%s"' % (item[0], taldefs.attrEscape(item[1])) + attrlist[i] = item[0], s + new.append(" " + s) + # if no non-optimizable attributes were found, convert to plain text + if opt: + new.append(end) + collect.extend(new) + return opt + + def optimizeCommonTriple(self, program): + if len(program) < 3: + return program + output = program[:2] + prev2, prev1 = output + for item in program[2:]: + if ( item[0] == "beginScope" + and prev1[0] == "setPosition" + and prev2[0] == "rawtextColumn"): + position = output.pop()[1] + text, column = output.pop()[1] + prev1 = None, None + closeprev = 0 + if output and output[-1][0] == "endScope": + closeprev = 1 + output.pop() + item = ("rawtextBeginScope", + (text, column, position, closeprev, item[1])) + output.append(item) + prev2 = prev1 + prev1 = item + return output + + def todoPush(self, todo): + self.todoStack.append(todo) + + def todoPop(self): + return self.todoStack.pop() + + def compileExpression(self, expr): + try: + return self.expressionCompiler.compile(expr) + except self.CompilerError, err: + raise TALError('%s in expression %s' % (err.args[0], `expr`), + self.position) + + def pushProgram(self): + self.stack.append(self.program) + self.program = [] + + def popProgram(self): + program = self.program + self.program = self.stack.pop() + return self.optimize(program) + + def pushSlots(self): + self.slotStack.append(self.slots) + self.slots = {} + + def popSlots(self): + slots = self.slots + self.slots = self.slotStack.pop() + return slots + + def emit(self, *instruction): + self.program.append(instruction) + + def emitStartTag(self, name, attrlist, isend=0): + if isend: + opcode = "startEndTag" + else: + opcode = "startTag" + self.emit(opcode, name, attrlist) + + def emitEndTag(self, name): + if self.xml and self.program and self.program[-1][0] == "startTag": + # Minimize empty element + self.program[-1] = ("startEndTag",) + self.program[-1][1:] + else: + self.emit("endTag", name) + + def emitOptTag(self, name, optTag, isend): + program = self.popProgram() #block + start = self.popProgram() #start tag + if (isend or not program) and self.xml: + # Minimize empty element + start[-1] = ("startEndTag",) + start[-1][1:] + isend = 1 + cexpr = optTag[0] + if cexpr: + cexpr = self.compileExpression(optTag[0]) + self.emit("optTag", name, cexpr, optTag[1], isend, start, program) + + def emitRawText(self, text): + self.emit("rawtext", text) + + def emitText(self, text): + self.emitRawText(cgi.escape(text)) + + def emitDefines(self, defines): + for part in taldefs.splitParts(defines): + m = re.match( + r"(?s)\s*(?:(global|local)\s+)?(%s)\s+(.*)\Z" % NAME_RE, part) + if not m: + raise TALError("invalid define syntax: " + `part`, + self.position) + scope, name, expr = m.group(1, 2, 3) + scope = scope or "local" + cexpr = self.compileExpression(expr) + if scope == "local": + self.emit("setLocal", name, cexpr) + else: + self.emit("setGlobal", name, cexpr) + + def emitOnError(self, name, onError): + block = self.popProgram() + key, expr = parseSubstitution(onError) + cexpr = self.compileExpression(expr) + if key == "text": + self.emit("insertText", cexpr, []) + else: + assert key == "structure" + self.emit("insertStructure", cexpr, {}, []) + self.emitEndTag(name) + handler = self.popProgram() + self.emit("onError", block, handler) + + def emitCondition(self, expr): + cexpr = self.compileExpression(expr) + program = self.popProgram() + self.emit("condition", cexpr, program) + + def emitRepeat(self, arg): + m = re.match("(?s)\s*(%s)\s+(.*)\Z" % NAME_RE, arg) + if not m: + raise TALError("invalid repeat syntax: " + `arg`, + self.position) + name, expr = m.group(1, 2) + cexpr = self.compileExpression(expr) + program = self.popProgram() + self.emit("loop", name, cexpr, program) + + def emitSubstitution(self, arg, attrDict={}): + key, expr = parseSubstitution(arg) + cexpr = self.compileExpression(expr) + program = self.popProgram() + if key == "text": + self.emit("insertText", cexpr, program) + else: + assert key == "structure" + self.emit("insertStructure", cexpr, attrDict, program) + + def emitI18nVariable(self, varname, action, expression): + # Used for i18n:name attributes. arg is extra information describing + # how the contents of the variable should get filled in, and it will + # either be a 1-tuple or a 2-tuple. If arg[0] is None, then the + # i18n:name value is taken implicitly from the contents of the tag, + # e.g. "I live in <span i18n:name="country">the USA</span>". In this + # case, arg[1] is the opcode sub-program describing the contents of + # the tag. + # + # When arg[0] is not None, it contains the tal expression used to + # calculate the contents of the variable, e.g. + # "I live in <span i18n:name="country" + # tal:replace="here/countryOfOrigin" />" + key = cexpr = None + program = self.popProgram() + if action == I18N_REPLACE: + # This is a tag with an i18n:name and a tal:replace (implicit or + # explicit). Get rid of the first and last elements of the + # program, which are the start and end tag opcodes of the tag. + program = program[1:-1] + elif action == I18N_CONTENT: + # This is a tag with an i18n:name and a tal:content + # (explicit-only). Keep the first and last elements of the + # program, so we keep the start and end tag output. + pass + else: + assert action == I18N_EXPRESSION + key, expr = parseSubstitution(expression) + cexpr = self.compileExpression(expr) + # XXX Would key be anything but 'text' or None? + assert key in ('text', None) + self.emit('i18nVariable', varname, program, cexpr) + + def emitTranslation(self, msgid, i18ndata): + program = self.popProgram() + if i18ndata is None: + self.emit('insertTranslation', msgid, program) + else: + key, expr = parseSubstitution(i18ndata) + cexpr = self.compileExpression(expr) + assert key == 'text' + self.emit('insertTranslation', msgid, program, cexpr) + + def emitDefineMacro(self, macroName): + program = self.popProgram() + macroName = macroName.strip() + if self.macros.has_key(macroName): + raise METALError("duplicate macro definition: %s" % `macroName`, + self.position) + if not re.match('%s$' % NAME_RE, macroName): + raise METALError("invalid macro name: %s" % `macroName`, + self.position) + self.macros[macroName] = program + self.inMacroDef = self.inMacroDef - 1 + self.emit("defineMacro", macroName, program) + + def emitUseMacro(self, expr): + cexpr = self.compileExpression(expr) + program = self.popProgram() + self.inMacroUse = 0 + self.emit("useMacro", expr, cexpr, self.popSlots(), program) + + def emitDefineSlot(self, slotName): + program = self.popProgram() + slotName = slotName.strip() + if not re.match('%s$' % NAME_RE, slotName): + raise METALError("invalid slot name: %s" % `slotName`, + self.position) + self.emit("defineSlot", slotName, program) + + def emitFillSlot(self, slotName): + program = self.popProgram() + slotName = slotName.strip() + if self.slots.has_key(slotName): + raise METALError("duplicate fill-slot name: %s" % `slotName`, + self.position) + if not re.match('%s$' % NAME_RE, slotName): + raise METALError("invalid slot name: %s" % `slotName`, + self.position) + self.slots[slotName] = program + self.inMacroUse = 1 + self.emit("fillSlot", slotName, program) + + def unEmitWhitespace(self): + collect = [] + i = len(self.program) - 1 + while i >= 0: + item = self.program[i] + if item[0] != "rawtext": + break + text = item[1] + if not re.match(r"\A\s*\Z", text): + break + collect.append(text) + i = i-1 + del self.program[i+1:] + if i >= 0 and self.program[i][0] == "rawtext": + text = self.program[i][1] + m = re.search(r"\s+\Z", text) + if m: + self.program[i] = ("rawtext", text[:m.start()]) + collect.append(m.group()) + collect.reverse() + return "".join(collect) + + def unEmitNewlineWhitespace(self): + collect = [] + i = len(self.program) + while i > 0: + i = i-1 + item = self.program[i] + if item[0] != "rawtext": + break + text = item[1] + if re.match(r"\A[ \t]*\Z", text): + collect.append(text) + continue + m = re.match(r"(?s)^(.*)(\n[ \t]*)\Z", text) + if not m: + break + text, rest = m.group(1, 2) + collect.reverse() + rest = rest + "".join(collect) + del self.program[i:] + if text: + self.emit("rawtext", text) + return rest + return None + + def replaceAttrs(self, attrlist, repldict): + # Each entry in attrlist starts like (name, value). + # Result is (name, value, action, expr, xlat) if there is a + # tal:attributes entry for that attribute. Additional attrs + # defined only by tal:attributes are added here. + # + # (name, value, action, expr, xlat) + if not repldict: + return attrlist + newlist = [] + for item in attrlist: + key = item[0] + if repldict.has_key(key): + expr, xlat = repldict[key] + item = item[:2] + ("replace", expr, xlat) + del repldict[key] + newlist.append(item) + # Add dynamic-only attributes + for key, (expr, xlat) in repldict.items(): + newlist.append((key, None, "insert", expr, xlat)) + return newlist + + def emitStartElement(self, name, attrlist, taldict, metaldict, i18ndict, + position=(None, None), isend=0): + if not taldict and not metaldict and not i18ndict: + # Handle the simple, common case + self.emitStartTag(name, attrlist, isend) + self.todoPush({}) + if isend: + self.emitEndElement(name, isend) + return + + self.position = position + for key, value in taldict.items(): + if key not in taldefs.KNOWN_TAL_ATTRIBUTES: + raise TALError("bad TAL attribute: " + `key`, position) + if not (value or key == 'omit-tag'): + raise TALError("missing value for TAL attribute: " + + `key`, position) + for key, value in metaldict.items(): + if key not in taldefs.KNOWN_METAL_ATTRIBUTES: + raise METALError("bad METAL attribute: " + `key`, + position) + if not value: + raise TALError("missing value for METAL attribute: " + + `key`, position) + for key, value in i18ndict.items(): + if key not in taldefs.KNOWN_I18N_ATTRIBUTES: + raise I18NError("bad i18n attribute: " + `key`, position) + if not value and key in ("attributes", "data", "id"): + raise I18NError("missing value for i18n attribute: " + + `key`, position) + todo = {} + defineMacro = metaldict.get("define-macro") + useMacro = metaldict.get("use-macro") + defineSlot = metaldict.get("define-slot") + fillSlot = metaldict.get("fill-slot") + define = taldict.get("define") + condition = taldict.get("condition") + repeat = taldict.get("repeat") + content = taldict.get("content") + replace = taldict.get("replace") + attrsubst = taldict.get("attributes") + onError = taldict.get("on-error") + omitTag = taldict.get("omit-tag") + TALtag = taldict.get("tal tag") + i18nattrs = i18ndict.get("attributes") + # Preserve empty string if implicit msgids are used. We'll generate + # code with the msgid='' and calculate the right implicit msgid during + # interpretation phase. + msgid = i18ndict.get("translate") + varname = i18ndict.get('name') + i18ndata = i18ndict.get('data') + + if i18ndata and not msgid: + raise I18NError("i18n:data must be accompanied by i18n:translate", + position) + + if len(metaldict) > 1 and (defineMacro or useMacro): + raise METALError("define-macro and use-macro cannot be used " + "together or with define-slot or fill-slot", + position) + if replace: + if content: + raise TALError( + "tal:content and tal:replace are mutually exclusive", + position) + if msgid is not None: + raise I18NError( + "i18n:translate and tal:replace are mutually exclusive", + position) + + repeatWhitespace = None + if repeat: + # Hack to include preceding whitespace in the loop program + repeatWhitespace = self.unEmitNewlineWhitespace() + if position != (None, None): + # XXX at some point we should insist on a non-trivial position + self.emit("setPosition", position) + if self.inMacroUse: + if fillSlot: + self.pushProgram() + if self.source_file is not None: + self.emit("setSourceFile", self.source_file) + todo["fillSlot"] = fillSlot + self.inMacroUse = 0 + else: + if fillSlot: + raise METALError("fill-slot must be within a use-macro", + position) + if not self.inMacroUse: + if defineMacro: + self.pushProgram() + self.emit("version", TAL_VERSION) + self.emit("mode", self.xml and "xml" or "html") + if self.source_file is not None: + self.emit("setSourceFile", self.source_file) + todo["defineMacro"] = defineMacro + self.inMacroDef = self.inMacroDef + 1 + if useMacro: + self.pushSlots() + self.pushProgram() + todo["useMacro"] = useMacro + self.inMacroUse = 1 + if defineSlot: + if not self.inMacroDef: + raise METALError( + "define-slot must be within a define-macro", + position) + self.pushProgram() + todo["defineSlot"] = defineSlot + + if defineSlot or i18ndict: + + domain = i18ndict.get("domain") or self.i18nContext.domain + source = i18ndict.get("source") or self.i18nContext.source + target = i18ndict.get("target") or self.i18nContext.target + if ( domain != DEFAULT_DOMAIN + or source is not None + or target is not None): + self.i18nContext = TranslationContext(self.i18nContext, + domain=domain, + source=source, + target=target) + self.emit("beginI18nContext", + {"domain": domain, "source": source, + "target": target}) + todo["i18ncontext"] = 1 + if taldict or i18ndict: + dict = {} + for item in attrlist: + key, value = item[:2] + dict[key] = value + self.emit("beginScope", dict) + todo["scope"] = 1 + if onError: + self.pushProgram() # handler + self.emitStartTag(name, list(attrlist)) # Must copy attrlist! + self.pushProgram() # block + todo["onError"] = onError + if define: + self.emitDefines(define) + todo["define"] = define + if condition: + self.pushProgram() + todo["condition"] = condition + if repeat: + todo["repeat"] = repeat + self.pushProgram() + if repeatWhitespace: + self.emitText(repeatWhitespace) + if content: + todo["content"] = content + if replace: + # tal:replace w/ i18n:name has slightly different semantics. What + # we're actually replacing then is the contents of the ${name} + # placeholder. + if varname: + todo['i18nvar'] = (varname, replace) + else: + todo["replace"] = replace + self.pushProgram() + # i18n:name w/o tal:replace uses the content as the interpolation + # dictionary values + elif varname: + todo['i18nvar'] = (varname, None) + self.pushProgram() + if msgid is not None: + todo['msgid'] = msgid + if i18ndata: + todo['i18ndata'] = i18ndata + optTag = omitTag is not None or TALtag + if optTag: + todo["optional tag"] = omitTag, TALtag + self.pushProgram() + if attrsubst or i18nattrs: + if attrsubst: + repldict = taldefs.parseAttributeReplacements(attrsubst) + else: + repldict = {} + if i18nattrs: + i18nattrs = i18nattrs.split() + else: + i18nattrs = () + # Convert repldict's name-->expr mapping to a + # name-->(compiled_expr, translate) mapping + for key, value in repldict.items(): + repldict[key] = self.compileExpression(value), key in i18nattrs + for key in i18nattrs: + if key not in repldict: + repldict[key] = None, 1 + else: + repldict = {} + if replace: + todo["repldict"] = repldict + repldict = {} + self.emitStartTag(name, self.replaceAttrs(attrlist, repldict), isend) + if optTag: + self.pushProgram() + if content: + self.pushProgram() + if msgid is not None: + self.pushProgram() + if todo and position != (None, None): + todo["position"] = position + self.todoPush(todo) + if isend: + self.emitEndElement(name, isend) + + def emitEndElement(self, name, isend=0, implied=0): + todo = self.todoPop() + if not todo: + # Shortcut + if not isend: + self.emitEndTag(name) + return + + self.position = position = todo.get("position", (None, None)) + defineMacro = todo.get("defineMacro") + useMacro = todo.get("useMacro") + defineSlot = todo.get("defineSlot") + fillSlot = todo.get("fillSlot") + repeat = todo.get("repeat") + content = todo.get("content") + replace = todo.get("replace") + condition = todo.get("condition") + onError = todo.get("onError") + define = todo.get("define") + repldict = todo.get("repldict", {}) + scope = todo.get("scope") + optTag = todo.get("optional tag") + msgid = todo.get('msgid') + i18ncontext = todo.get("i18ncontext") + varname = todo.get('i18nvar') + i18ndata = todo.get('i18ndata') + + if implied > 0: + if defineMacro or useMacro or defineSlot or fillSlot: + exc = METALError + what = "METAL" + else: + exc = TALError + what = "TAL" + raise exc("%s attributes on <%s> require explicit </%s>" % + (what, name, name), position) + + # If there's no tal:content or tal:replace in the tag with the + # i18n:name, tal:replace is the default. + i18nNameAction = I18N_REPLACE + if content: + if varname: + i18nNameAction = I18N_CONTENT + self.emitSubstitution(content, {}) + # If we're looking at an implicit msgid, emit the insertTranslation + # opcode now, so that the end tag doesn't become part of the implicit + # msgid. If we're looking at an explicit msgid, it's better to emit + # the opcode after the i18nVariable opcode so we can better handle + # tags with both of them in them (and in the latter case, the contents + # would be thrown away for msgid purposes). + if msgid is not None and not varname: + self.emitTranslation(msgid, i18ndata) + if optTag: + self.emitOptTag(name, optTag, isend) + elif not isend: + # If we're processing the end tag for a tag that contained + # i18n:name, we need to make sure that optimize() won't collect + # immediately following end tags into the same rawtextOffset, so + # put a spacer here that the optimizer will recognize. + if varname: + self.emit('noop') + self.emitEndTag(name) + # If i18n:name appeared in the same tag as tal:replace then we're + # going to do the substitution a little bit differently. The results + # of the expression go into the i18n substitution dictionary. + if replace: + self.emitSubstitution(replace, repldict) + elif varname: + if varname[1] is not None: + i18nNameAction = I18N_EXPRESSION + # o varname[0] is the variable name + # o i18nNameAction is either + # - I18N_REPLACE for implicit tal:replace + # - I18N_CONTENT for tal:content + # - I18N_EXPRESSION for explicit tal:replace + # o varname[1] will be None for the first two actions and the + # replacement tal expression for the third action. + self.emitI18nVariable(varname[0], i18nNameAction, varname[1]) + # Do not test for "msgid is not None", i.e. we only want to test for + # explicit msgids here. See comment above. + if msgid is not None and varname: + self.emitTranslation(msgid, i18ndata) + if repeat: + self.emitRepeat(repeat) + if condition: + self.emitCondition(condition) + if onError: + self.emitOnError(name, onError) + if scope: + self.emit("endScope") + if i18ncontext: + self.emit("endI18nContext") + assert self.i18nContext.parent is not None + self.i18nContext = self.i18nContext.parent + if defineSlot: + self.emitDefineSlot(defineSlot) + if fillSlot: + self.emitFillSlot(fillSlot) + if useMacro: + self.emitUseMacro(useMacro) + if defineMacro: + self.emitDefineMacro(defineMacro) + +def test(): + t = TALGenerator() + t.pushProgram() + t.emit("bar") + p = t.popProgram() + t.emit("foo", p) + +if __name__ == "__main__": + test() diff --git a/talgettext.py b/talgettext.py new file mode 100644 index 0000000..be621f6 --- /dev/null +++ b/talgettext.py @@ -0,0 +1,100 @@ +#!/usr/bin/env python +############################################################################## +# +# Copyright (c) 2002 Zope Corporation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## + +"""Program to extract internationalization markup from Page Templates. + +Once you have marked up a Page Template file with i18n: namespace tags, use +this program to extract GNU gettext .po file entries. + +Usage: talgettext.py [options] files +Options: + -h / --help + Print this message and exit. +""" + +import getopt +import os +import sys + +from zope.tal.htmltalparser import HTMLTALParser +from zope.tal.talinterpreter import TALInterpreter +from zope.tal.dummyengine import DummyEngine +from zope.tal.interfaces import ITALESEngine + + +def usage(code, msg=''): + # Python 2.1 required + print >> sys.stderr, __doc__ + if msg: + print >> sys.stderr, msg + sys.exit(code) + + +class POEngine(DummyEngine): + __implements__ = ITALESEngine + + catalog = {} + + def evaluatePathOrVar(self, expr): + return 'who cares' + + def translate(self, domain, msgid, mapping): + self.catalog[msgid] = '' + + +def main(): + try: + opts, args = getopt.getopt( + sys.argv[1:], + 'ho:', + ['help', 'output=']) + except getopt.error, msg: + usage(1, msg) + + outfile = None + for opt, arg in opts: + if opt in ('-h', '--help'): + usage(0) + elif opt in ('-o', '--output'): + outfile = arg + + if not args: + print 'nothing to do' + return + + # We don't care about the rendered output of the .pt file + class Devnull: + def write(self, s): + pass + + engine = POEngine() + for file in args: + p = HTMLTALParser() + p.parseFile(file) + program, macros = p.getCode() + TALInterpreter(program, macros, engine, stream=Devnull())() + + # Now print all the entries in the engine + msgids = engine.catalog.keys() + msgids.sort() + for msgid in msgids: + msgstr = engine.catalog[msgid] + print 'msgid "%s"' % msgid + print 'msgstr "%s"' % msgstr + print + + +if __name__ == '__main__': + main() diff --git a/talinterpreter.py b/talinterpreter.py new file mode 100644 index 0000000..0ed5ef9 --- /dev/null +++ b/talinterpreter.py @@ -0,0 +1,732 @@ +############################################################################## +# +# Copyright (c) 2001, 2002 Zope Corporation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +""" +Interpreter for a pre-compiled TAL program. +""" + +import getopt +import re +import sys + +from cgi import escape +# Do not use cStringIO here! It's not unicode aware. :( +from StringIO import StringIO + +from zope.tal.taldefs import quote, TAL_VERSION, TALError, METALError +from zope.tal.taldefs import isCurrentVersion +from zope.tal.taldefs import getProgramVersion, getProgramMode +from zope.tal.talgenerator import TALGenerator +from zope.tal.translationcontext import TranslationContext + +BOOLEAN_HTML_ATTRS = [ + # List of Boolean attributes in HTML that should be rendered in + # minimized form (e.g. <img ismap> rather than <img ismap="">) + # From http://www.w3.org/TR/xhtml1/#guidelines (C.10) + # XXX The problem with this is that this is not valid XML and + # can't be parsed back! + "compact", "nowrap", "ismap", "declare", "noshade", "checked", + "disabled", "readonly", "multiple", "selected", "noresize", + "defer" +] + +def normalize(text): + # Now we need to normalize the whitespace in implicit message ids and + # implicit $name substitution values by stripping leading and trailing + # whitespace, and folding all internal whitespace to a single space. + return ' '.join(text.split()) + + +class AltTALGenerator(TALGenerator): + + def __init__(self, repldict, expressionCompiler=None, xml=0): + self.repldict = repldict + self.enabled = 1 + TALGenerator.__init__(self, expressionCompiler, xml) + + def enable(self, enabled): + self.enabled = enabled + + def emit(self, *args): + if self.enabled: + apply(TALGenerator.emit, (self,) + args) + + def emitStartElement(self, name, attrlist, taldict, metaldict, i18ndict, + position=(None, None), isend=0): + metaldict = {} + taldict = {} + i18ndict = {} + if self.enabled and self.repldict: + taldict["attributes"] = "x x" + TALGenerator.emitStartElement(self, name, attrlist, + taldict, metaldict, i18ndict, + position, isend) + + def replaceAttrs(self, attrlist, repldict): + if self.enabled and self.repldict: + repldict = self.repldict + self.repldict = None + return TALGenerator.replaceAttrs(self, attrlist, repldict) + + +class TALInterpreter: + + def __init__(self, program, macros, engine, stream=None, + debug=0, wrap=60, metal=1, tal=1, showtal=-1, + strictinsert=1, stackLimit=100, i18nInterpolate=1): + self.program = program + self.macros = macros + self.engine = engine # Execution engine (aka context) + self.Default = engine.getDefault() + self._stream_stack = [stream or sys.stdout] + self.popStream() + self.debug = debug + self.wrap = wrap + self.metal = metal + self.tal = tal + if tal: + self.dispatch = self.bytecode_handlers_tal + else: + self.dispatch = self.bytecode_handlers + assert showtal in (-1, 0, 1) + if showtal == -1: + showtal = (not tal) + self.showtal = showtal + self.strictinsert = strictinsert + self.stackLimit = stackLimit + self.html = 0 + self.endsep = "/>" + self.endlen = len(self.endsep) + self.macroStack = [] + self.position = None, None # (lineno, offset) + self.col = 0 + self.level = 0 + self.scopeLevel = 0 + self.sourceFile = None + self.i18nStack = [] + self.i18nInterpolate = i18nInterpolate + self.i18nContext = TranslationContext() + + def saveState(self): + return (self.position, self.col, self.stream, self._stream_stack, + self.scopeLevel, self.level, self.i18nContext) + + def restoreState(self, state): + (self.position, self.col, self.stream, + self._stream_stack, scopeLevel, level, i18n) = state + self._stream_write = self.stream.write + assert self.level == level + while self.scopeLevel > scopeLevel: + self.engine.endScope() + self.scopeLevel = self.scopeLevel - 1 + self.engine.setPosition(self.position) + self.i18nContext = i18n + + def restoreOutputState(self, state): + (dummy, self.col, self.stream, + self._stream_stack, scopeLevel, level, i18n) = state + self._stream_write = self.stream.write + assert self.level == level + assert self.scopeLevel == scopeLevel + + def pushMacro(self, macroName, slots, entering=1): + if len(self.macroStack) >= self.stackLimit: + raise METALError("macro nesting limit (%d) exceeded " + "by %s" % (self.stackLimit, `macroName`)) + self.macroStack.append([macroName, slots, entering, self.i18nContext]) + + def popMacro(self): + stuff = self.macroStack.pop() + self.i18nContext = stuff[3] + return stuff + + def macroContext(self, what): + macroStack = self.macroStack + i = len(macroStack) + while i > 0: + i = i-1 + if macroStack[i][0] == what: + return i + return -1 + + def __call__(self): + assert self.level == 0 + assert self.scopeLevel == 0 + assert self.i18nContext.parent is None + self.interpret(self.program) + assert self.level == 0 + assert self.scopeLevel == 0 + assert self.i18nContext.parent is None + if self.col > 0: + self._stream_write("\n") + self.col = 0 + + def pushStream(self, newstream): + self._stream_stack.append(self.stream) + self.stream = newstream + self._stream_write = newstream.write + + def popStream(self): + self.stream = self._stream_stack.pop() + self._stream_write = self.stream.write + + def stream_write(self, s, + len=len): + self._stream_write(s) + i = s.rfind('\n') + if i < 0: + self.col = self.col + len(s) + else: + self.col = len(s) - (i + 1) + + bytecode_handlers = {} + + def interpret(self, program): + oldlevel = self.level + self.level = oldlevel + 1 + handlers = self.dispatch + try: + if self.debug: + for (opcode, args) in program: + s = "%sdo_%s(%s)\n" % (" "*self.level, opcode, + repr(args)) + if len(s) > 80: + s = s[:76] + "...\n" + sys.stderr.write(s) + handlers[opcode](self, args) + else: + for (opcode, args) in program: + handlers[opcode](self, args) + finally: + self.level = oldlevel + + def do_version(self, version): + assert version == TAL_VERSION + bytecode_handlers["version"] = do_version + + def do_mode(self, mode): + assert mode in ("html", "xml") + self.html = (mode == "html") + if self.html: + self.endsep = " />" + else: + self.endsep = "/>" + self.endlen = len(self.endsep) + bytecode_handlers["mode"] = do_mode + + def do_setSourceFile(self, source_file): + self.sourceFile = source_file + self.engine.setSourceFile(source_file) + bytecode_handlers["setSourceFile"] = do_setSourceFile + + def do_setPosition(self, position): + self.position = position + self.engine.setPosition(position) + bytecode_handlers["setPosition"] = do_setPosition + + def do_startEndTag(self, stuff): + self.do_startTag(stuff, self.endsep, self.endlen) + bytecode_handlers["startEndTag"] = do_startEndTag + + def do_startTag(self, (name, attrList), + end=">", endlen=1, _len=len): + # The bytecode generator does not cause calls to this method + # for start tags with no attributes; those are optimized down + # to rawtext events. Hence, there is no special "fast path" + # for that case. + _stream_write = self._stream_write + _stream_write("<" + name) + col = self.col + _len(name) + 1 + wrap = self.wrap + align = col + 1 + if align >= wrap/2: + align = 4 # Avoid a narrow column far to the right + attrAction = self.dispatch["<attrAction>"] + try: + for item in attrList: + if _len(item) == 2: + name, s = item + else: + ok, name, s = attrAction(self, item) + if not ok: + continue + slen = _len(s) + if (wrap and + col >= align and + col + 1 + slen > wrap): + _stream_write("\n" + " "*align) + col = align + slen + else: + s = " " + s + col = col + 1 + slen + _stream_write(s) + _stream_write(end) + col = col + endlen + finally: + self.col = col + bytecode_handlers["startTag"] = do_startTag + + def attrAction(self, item): + name, value, action = item[:3] + if action == 'insert' or (action in ('metal', 'tal', 'xmlns', 'i18n') + and not self.showtal): + return 0, name, value + macs = self.macroStack + if action == 'metal' and self.metal and macs: + if len(macs) > 1 or not macs[-1][2]: + # Drop all METAL attributes at a use-depth above one. + return 0, name, value + # Clear 'entering' flag + macs[-1][2] = 0 + # Convert or drop depth-one METAL attributes. + i = name.rfind(":") + 1 + prefix, suffix = name[:i], name[i:] + if suffix == "define-macro": + # Convert define-macro as we enter depth one. + name = prefix + "use-macro" + value = macs[-1][0] # Macro name + elif suffix == "define-slot": + name = prefix + "fill-slot" + elif suffix == "fill-slot": + pass + else: + return 0, name, value + + if value is None: + value = name + else: + value = "%s=%s" % (name, quote(value)) + return 1, name, value + + def attrAction_tal(self, item): + name, value, action = item[:3] + if action in ('metal', 'tal', 'xmlns', 'i18n'): + return self.attrAction(item) + ok = 1 + expr, msgid = item[3:] + if self.html and name.lower() in BOOLEAN_HTML_ATTRS: + evalue = self.engine.evaluateBoolean(item[3]) + if evalue is self.Default: + if action == 'insert': # Cancelled insert + ok = 0 + elif evalue: + value = None + else: + ok = 0 + else: + if expr is not None: + evalue = self.engine.evaluateText(item[3]) + if evalue is self.Default: + if action == 'insert': # Cancelled insert + ok = 0 + else: + if evalue is None: + ok = 0 + value = evalue + if ok: + if msgid: + value = self.i18n_attribute(value) + if value is None: + value = name + value = "%s=%s" % (name, quote(value)) + return ok, name, value + + def i18n_attribute(self, s): + # s is the value of an attribute before translation + # it may have been computed + return self.translate(s, {}) + + + bytecode_handlers["<attrAction>"] = attrAction + + def no_tag(self, start, program): + state = self.saveState() + self.stream = stream = StringIO() + self._stream_write = stream.write + self.interpret(start) + self.restoreOutputState(state) + self.interpret(program) + + def do_optTag(self, (name, cexpr, tag_ns, isend, start, program), + omit=0): + if tag_ns and not self.showtal: + return self.no_tag(start, program) + + self.interpret(start) + if not isend: + self.interpret(program) + s = '</%s>' % name + self._stream_write(s) + self.col = self.col + len(s) + + def do_optTag_tal(self, stuff): + cexpr = stuff[1] + if cexpr is not None and (cexpr == '' or + self.engine.evaluateBoolean(cexpr)): + self.no_tag(stuff[-2], stuff[-1]) + else: + self.do_optTag(stuff) + bytecode_handlers["optTag"] = do_optTag + + def dumpMacroStack(self, prefix, suffix, value): + sys.stderr.write("+---- %s%s = %s\n" % (prefix, suffix, value)) + for i in range(len(self.macroStack)): + what, macroName, slots = self.macroStack[i][:3] + sys.stderr.write("| %2d. %-12s %-12s %s\n" % + (i, what, macroName, slots and slots.keys())) + sys.stderr.write("+--------------------------------------\n") + + def do_rawtextBeginScope(self, (s, col, position, closeprev, dict)): + self._stream_write(s) + self.col = col + self.do_setPosition(position) + if closeprev: + engine = self.engine + engine.endScope() + engine.beginScope() + else: + self.engine.beginScope() + self.scopeLevel = self.scopeLevel + 1 + + def do_rawtextBeginScope_tal(self, (s, col, position, closeprev, dict)): + self._stream_write(s) + self.col = col + self.do_setPosition(position) + engine = self.engine + if closeprev: + engine.endScope() + engine.beginScope() + else: + engine.beginScope() + self.scopeLevel = self.scopeLevel + 1 + engine.setLocal("attrs", dict) + bytecode_handlers["rawtextBeginScope"] = do_rawtextBeginScope + + def do_beginScope(self, dict): + self.engine.beginScope() + self.scopeLevel = self.scopeLevel + 1 + + def do_beginScope_tal(self, dict): + engine = self.engine + engine.beginScope() + engine.setLocal("attrs", dict) + self.scopeLevel = self.scopeLevel + 1 + bytecode_handlers["beginScope"] = do_beginScope + + def do_endScope(self, notused=None): + self.engine.endScope() + self.scopeLevel = self.scopeLevel - 1 + bytecode_handlers["endScope"] = do_endScope + + def do_setLocal(self, notused): + pass + + def do_setLocal_tal(self, (name, expr)): + self.engine.setLocal(name, self.engine.evaluateValue(expr)) + bytecode_handlers["setLocal"] = do_setLocal + + def do_setGlobal_tal(self, (name, expr)): + self.engine.setGlobal(name, self.engine.evaluateValue(expr)) + bytecode_handlers["setGlobal"] = do_setLocal + + def do_beginI18nContext(self, settings): + get = settings.get + self.i18nContext = TranslationContext(self.i18nContext, + domain=get("domain"), + source=get("source"), + target=get("target")) + bytecode_handlers["beginI18nContext"] = do_beginI18nContext + + def do_endI18nContext(self, notused=None): + self.i18nContext = self.i18nContext.parent + assert self.i18nContext is not None + bytecode_handlers["endI18nContext"] = do_endI18nContext + + def do_insertText(self, stuff): + self.interpret(stuff[1]) + + def do_insertText_tal(self, stuff): + text = self.engine.evaluateText(stuff[0]) + if text is None: + return + if text is self.Default: + self.interpret(stuff[1]) + return + s = escape(text) + self._stream_write(s) + i = s.rfind('\n') + if i < 0: + self.col = self.col + len(s) + else: + self.col = len(s) - (i + 1) + bytecode_handlers["insertText"] = do_insertText + + def do_i18nVariable(self, stuff): + varname, program, expression = stuff + if expression is None: + # The value is implicitly the contents of this tag, so we have to + # evaluate the mini-program to get the value of the variable. + state = self.saveState() + try: + tmpstream = StringIO() + self.pushStream(tmpstream) + try: + self.interpret(program) + finally: + self.popStream() + value = normalize(tmpstream.getvalue()) + finally: + self.restoreState(state) + else: + # Evaluate the value to be associated with the variable in the + # i18n interpolation dictionary. + value = self.engine.evaluate(expression) + # Either the i18n:name tag is nested inside an i18n:translate in which + # case the last item on the stack has the i18n dictionary and string + # representation, or the i18n:name and i18n:translate attributes are + # in the same tag, in which case the i18nStack will be empty. In that + # case we can just output the ${name} to the stream + i18ndict, srepr = self.i18nStack[-1] + i18ndict[varname] = value + placeholder = '${%s}' % varname + srepr.append(placeholder) + self._stream_write(placeholder) + bytecode_handlers['i18nVariable'] = do_i18nVariable + + def do_insertTranslation(self, stuff): + i18ndict = {} + srepr = [] + obj = None + self.i18nStack.append((i18ndict, srepr)) + msgid = stuff[0] + # We need to evaluate the content of the tag because that will give us + # several useful pieces of information. First, the contents will + # include an implicit message id, if no explicit one was given. + # Second, it will evaluate any i18nVariable definitions in the body of + # the translation (necessary for $varname substitutions). + # + # Use a temporary stream to capture the interpretation of the + # subnodes, which should /not/ go to the output stream. + tmpstream = StringIO() + self.pushStream(tmpstream) + try: + self.interpret(stuff[1]) + finally: + self.popStream() + # We only care about the evaluated contents if we need an implicit + # message id. All other useful information will be in the i18ndict on + # the top of the i18nStack. + if msgid == '': + msgid = normalize(tmpstream.getvalue()) + self.i18nStack.pop() + # See if there is was an i18n:data for msgid + if len(stuff) > 2: + obj = self.engine.evaluate(stuff[2]) + xlated_msgid = self.translate(msgid, i18ndict, obj) + # XXX I can't decide whether we want to cgi escape the translated + # string or not. OT1H not doing this could introduce a cross-site + # scripting vector by allowing translators to sneak JavaScript into + # translations. OTOH, for implicit interpolation values, we don't + # want to escape stuff like ${name} <= "<b>Timmy</b>". + #s = escape(xlated_msgid) + s = xlated_msgid + # If there are i18n variables to interpolate into this string, better + # do it now. + self._stream_write(s) + bytecode_handlers['insertTranslation'] = do_insertTranslation + + def do_insertStructure(self, stuff): + self.interpret(stuff[2]) + + def do_insertStructure_tal(self, (expr, repldict, block)): + structure = self.engine.evaluateStructure(expr) + if structure is None: + return + if structure is self.Default: + self.interpret(block) + return + text = str(structure) + if not (repldict or self.strictinsert): + # Take a shortcut, no error checking + self.stream_write(text) + return + if self.html: + self.insertHTMLStructure(text, repldict) + else: + self.insertXMLStructure(text, repldict) + bytecode_handlers["insertStructure"] = do_insertStructure + + def insertHTMLStructure(self, text, repldict): + from zope.tal.htmltalparser import HTMLTALParser + gen = AltTALGenerator(repldict, self.engine, 0) + p = HTMLTALParser(gen) # Raises an exception if text is invalid + p.parseString(text) + program, macros = p.getCode() + self.interpret(program) + + def insertXMLStructure(self, text, repldict): + from zope.tal.talparser import TALParser + gen = AltTALGenerator(repldict, self.engine, 0) + p = TALParser(gen) + gen.enable(0) + p.parseFragment('<!DOCTYPE foo PUBLIC "foo" "bar"><foo>') + gen.enable(1) + p.parseFragment(text) # Raises an exception if text is invalid + gen.enable(0) + p.parseFragment('</foo>', 1) + program, macros = gen.getCode() + self.interpret(program) + + def do_loop(self, (name, expr, block)): + self.interpret(block) + + def do_loop_tal(self, (name, expr, block)): + iterator = self.engine.setRepeat(name, expr) + while iterator.next(): + self.interpret(block) + bytecode_handlers["loop"] = do_loop + + def translate(self, msgid, i18ndict=None, obj=None): + # XXX is this right? + if i18ndict is None: + i18ndict = {} + if obj: + i18ndict.update(obj) + # XXX need to fill this in with TranslationService calls. For now, + # we'll just do simple interpolation based on a $-strings to %-strings + # algorithm in Mailman. + if not self.i18nInterpolate: + return msgid + # XXX Mmmh, it seems that sometimes the msgid is None; is that really + # possible? + if msgid is None: + return None + # XXX We need to pass in one of context or target_language + return self.engine.translate(self.i18nContext.domain, msgid, i18ndict) + + def do_rawtextColumn(self, (s, col)): + self._stream_write(s) + self.col = col + bytecode_handlers["rawtextColumn"] = do_rawtextColumn + + def do_rawtextOffset(self, (s, offset)): + self._stream_write(s) + self.col = self.col + offset + bytecode_handlers["rawtextOffset"] = do_rawtextOffset + + def do_condition(self, (condition, block)): + if not self.tal or self.engine.evaluateBoolean(condition): + self.interpret(block) + bytecode_handlers["condition"] = do_condition + + def do_defineMacro(self, (macroName, macro)): + macs = self.macroStack + if len(macs) == 1: + entering = macs[-1][2] + if not entering: + macs.append(None) + self.interpret(macro) + assert macs[-1] is None + macs.pop() + return + self.interpret(macro) + bytecode_handlers["defineMacro"] = do_defineMacro + + def do_useMacro(self, (macroName, macroExpr, compiledSlots, block)): + if not self.metal: + self.interpret(block) + return + macro = self.engine.evaluateMacro(macroExpr) + if macro is self.Default: + macro = block + else: + if not isCurrentVersion(macro): + raise METALError("macro %s has incompatible version %s" % + (`macroName`, `getProgramVersion(macro)`), + self.position) + mode = getProgramMode(macro) + if mode != (self.html and "html" or "xml"): + raise METALError("macro %s has incompatible mode %s" % + (`macroName`, `mode`), self.position) + self.pushMacro(macroName, compiledSlots) + prev_source = self.sourceFile + self.interpret(macro) + if self.sourceFile != prev_source: + self.engine.setSourceFile(prev_source) + self.sourceFile = prev_source + self.popMacro() + bytecode_handlers["useMacro"] = do_useMacro + + def do_fillSlot(self, (slotName, block)): + # This is only executed if the enclosing 'use-macro' evaluates + # to 'default'. + self.interpret(block) + bytecode_handlers["fillSlot"] = do_fillSlot + + def do_defineSlot(self, (slotName, block)): + if not self.metal: + self.interpret(block) + return + macs = self.macroStack + if macs and macs[-1] is not None: + macroName, slots = self.popMacro()[:2] + slot = slots.get(slotName) + if slot is not None: + prev_source = self.sourceFile + self.interpret(slot) + if self.sourceFile != prev_source: + self.engine.setSourceFile(prev_source) + self.sourceFile = prev_source + self.pushMacro(macroName, slots, entering=0) + return + self.pushMacro(macroName, slots) + # Falling out of the 'if' allows the macro to be interpreted. + self.interpret(block) + bytecode_handlers["defineSlot"] = do_defineSlot + + def do_onError(self, (block, handler)): + self.interpret(block) + + def do_onError_tal(self, (block, handler)): + state = self.saveState() + self.stream = stream = StringIO() + self._stream_write = stream.write + try: + self.interpret(block) + except: + exc = sys.exc_info()[1] + self.restoreState(state) + engine = self.engine + engine.beginScope() + error = engine.createErrorInfo(exc, self.position) + engine.setLocal('error', error) + try: + self.interpret(handler) + finally: + engine.endScope() + else: + self.restoreOutputState(state) + self.stream_write(stream.getvalue()) + bytecode_handlers["onError"] = do_onError + + bytecode_handlers_tal = bytecode_handlers.copy() + bytecode_handlers_tal["rawtextBeginScope"] = do_rawtextBeginScope_tal + bytecode_handlers_tal["beginScope"] = do_beginScope_tal + bytecode_handlers_tal["setLocal"] = do_setLocal_tal + bytecode_handlers_tal["setGlobal"] = do_setGlobal_tal + bytecode_handlers_tal["insertStructure"] = do_insertStructure_tal + bytecode_handlers_tal["insertText"] = do_insertText_tal + bytecode_handlers_tal["loop"] = do_loop_tal + bytecode_handlers_tal["onError"] = do_onError_tal + bytecode_handlers_tal["<attrAction>"] = attrAction_tal + bytecode_handlers_tal["optTag"] = do_optTag_tal diff --git a/talparser.py b/talparser.py new file mode 100644 index 0000000..12dea8d --- /dev/null +++ b/talparser.py @@ -0,0 +1,145 @@ +############################################################################## +# +# Copyright (c) 2001, 2002 Zope Corporation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +""" +Parse XML and compile to TALInterpreter intermediate code. +""" + +from zope.tal.taldefs import XML_NS, ZOPE_I18N_NS, ZOPE_METAL_NS, ZOPE_TAL_NS +from zope.tal.talgenerator import TALGenerator +from zope.tal.xmlparser import XMLParser + + +class TALParser(XMLParser): + + ordered_attributes = 1 + + def __init__(self, gen=None): # Override + XMLParser.__init__(self) + if gen is None: + gen = TALGenerator() + self.gen = gen + self.nsStack = [] + self.nsDict = {XML_NS: 'xml'} + self.nsNew = [] + + def getCode(self): + return self.gen.getCode() + + def getWarnings(self): + return () + + def StartNamespaceDeclHandler(self, prefix, uri): + self.nsStack.append(self.nsDict.copy()) + self.nsDict[uri] = prefix + self.nsNew.append((prefix, uri)) + + def EndNamespaceDeclHandler(self, prefix): + self.nsDict = self.nsStack.pop() + + def StartElementHandler(self, name, attrs): + if self.ordered_attributes: + # attrs is a list of alternating names and values + attrlist = [] + for i in range(0, len(attrs), 2): + key = attrs[i] + value = attrs[i+1] + attrlist.append((key, value)) + else: + # attrs is a dict of {name: value} + attrlist = attrs.items() + attrlist.sort() # For definiteness + name, attrlist, taldict, metaldict, i18ndict \ + = self.process_ns(name, attrlist) + attrlist = self.xmlnsattrs() + attrlist + self.gen.emitStartElement(name, attrlist, taldict, metaldict, i18ndict) + + def process_ns(self, name, attrlist): + taldict = {} + metaldict = {} + i18ndict = {} + fixedattrlist = [] + name, namebase, namens = self.fixname(name) + for key, value in attrlist: + key, keybase, keyns = self.fixname(key) + ns = keyns or namens # default to tag namespace + item = key.lower(), value + if ns == 'metal': + metaldict[keybase] = value + item = item + ("metal",) + elif ns == 'tal': + taldict[keybase] = value + item = item + ("tal",) + elif ns == 'i18n': + assert 0, "dealing with i18n: " + `(keybase, value)` + i18ndict[keybase] = value + item = item + ('i18n',) + fixedattrlist.append(item) + if namens in ('metal', 'tal', 'i18n'): + taldict['tal tag'] = namens + return name, fixedattrlist, taldict, metaldict, i18ndict + + def xmlnsattrs(self): + newlist = [] + for prefix, uri in self.nsNew: + if prefix: + key = "xmlns:" + prefix + else: + key = "xmlns" + if uri in (ZOPE_METAL_NS, ZOPE_TAL_NS, ZOPE_I18N_NS): + item = (key, uri, "xmlns") + else: + item = (key, uri) + newlist.append(item) + self.nsNew = [] + return newlist + + def fixname(self, name): + if ' ' in name: + uri, name = name.split(' ') + prefix = self.nsDict[uri] + prefixed = name + if prefix: + prefixed = "%s:%s" % (prefix, name) + ns = 'x' + if uri == ZOPE_TAL_NS: + ns = 'tal' + elif uri == ZOPE_METAL_NS: + ns = 'metal' + elif uri == ZOPE_I18N_NS: + ns = 'i18n' + return (prefixed, name, ns) + return (name, name, None) + + def EndElementHandler(self, name): + name = self.fixname(name)[0] + self.gen.emitEndElement(name) + + def DefaultHandler(self, text): + self.gen.emitRawText(text) + +def test(): + import sys + p = TALParser() + file = "tests/input/test01.xml" + if sys.argv[1:]: + file = sys.argv[1] + p.parseFile(file) + program, macros = p.getCode() + from zope.tal.talinterpreter import TALInterpreter + from zope.tal.dummyengine import DummyEngine + engine = DummyEngine(macros) + TALInterpreter(program, macros, engine, sys.stdout, wrap=0)() + +if __name__ == "__main__": + test() diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..b711d36 --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1,2 @@ +# +# This file is necessary to make this directory a package. diff --git a/tests/input/__init__.py b/tests/input/__init__.py new file mode 100644 index 0000000..b711d36 --- /dev/null +++ b/tests/input/__init__.py @@ -0,0 +1,2 @@ +# +# This file is necessary to make this directory a package. diff --git a/tests/input/test01.html b/tests/input/test01.html new file mode 100644 index 0000000..e2ae0c4 --- /dev/null +++ b/tests/input/test01.html @@ -0,0 +1,56 @@ +<!DOCTYPE html + PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "DTD/xhtml1-transitional.dtd"> +<html> + + <head>dadada</head> + + <body xmlns:z="http://xml.zope.org/namespaces/tal" z:define="foo python:1"> +<h1 z:condition="python:0">This title is not displayed</h1> + <h1 z:condition="python:1" z:content="str:This +Is +The +Replaced +Title">Title</h1> + + <!-- test entity references --> + &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/tests/input/test01.xml b/tests/input/test01.xml new file mode 100644 index 0000000..82038e9 --- /dev/null +++ b/tests/input/test01.xml @@ -0,0 +1,57 @@ +<?xml version="1.0" ?> +<!DOCTYPE html + PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "DTD/xhtml1-transitional.dtd"> +<html> + + <head>dadada</head> + + <body xmlns:z="http://xml.zope.org/namespaces/tal" z:define="foo python:1"> +<h1 z:condition="python:0">This title is not displayed</h1> + <h1 z:condition="python:1" z:content="str:This +Is +The +Replaced +Title">Title</h1> + + <!-- test entity references --> + &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/tests/input/test02.html b/tests/input/test02.html new file mode 100644 index 0000000..df2fb18 --- /dev/null +++ b/tests/input/test02.html @@ -0,0 +1,118 @@ +<biztalk_1 xmlns="urn:schemas-biztalk-org:biztalk:biztalk_1"> + +<foo:header xmlns:foo="whomping-willow" plain="guido" quote='"' apostrophe="'" both=""'" 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/tests/input/test02.xml b/tests/input/test02.xml new file mode 100644 index 0000000..69567ea --- /dev/null +++ b/tests/input/test02.xml @@ -0,0 +1,119 @@ +<?xml version="1.0" ?> +<biztalk_1 xmlns="urn:schemas-biztalk-org:biztalk:biztalk_1"> + +<foo:header xmlns:foo="whomping-willow" plain="guido" quote='"' apostrophe="'" both=""'" 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/tests/input/test03.html b/tests/input/test03.html new file mode 100644 index 0000000..a0230e1 --- /dev/null +++ b/tests/input/test03.html @@ -0,0 +1,9 @@ +<p xmlns:z="http://xml.zope.org/namespaces/tal"> + <span z:define="local x str:hello brave new world"> + <span z:content="text local:x">outer variable x, first appearance</span> + <span z:define="local x str:goodbye cruel world"> + <span z:content="text local:x">inner variable x</span> + </span> + <span z:content="text local:x">outer variable x, second appearance</span> + </span> +</p> diff --git a/tests/input/test03.xml b/tests/input/test03.xml new file mode 100644 index 0000000..830149d --- /dev/null +++ b/tests/input/test03.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" ?> +<p xmlns:z="http://xml.zope.org/namespaces/tal"> + <span z:define="local x str:hello brave new world"> + <span z:content="text local:x">outer variable x, first appearance</span> + <span z:define="local x str:goodbye cruel world"> + <span z:content="text local:x">inner variable x</span> + </span> + <span z:content="text local:x">outer variable x, second appearance</span> + </span> +</p> diff --git a/tests/input/test04.html b/tests/input/test04.html new file mode 100644 index 0000000..bdaad39 --- /dev/null +++ b/tests/input/test04.html @@ -0,0 +1,26 @@ +<html> + + <body xmlns:m="http://xml.zope.org/namespaces/metal" xmlns:z="http://xml.zope.org/namespaces/tal" m:define-macro="body" z:define="global count python:0"> + + <ul m:define-macro="whoops"> + <li z:repeat="item python:range(count)"> + <span z:replace="item">1</span> + <span z:replace="global:message"/> + </li> + </ul> + + <span z:define="global count python:2; global message str:hello world"/> + + <p m:use-macro="whoops">use-macro + <span m:fill-slot="whoops">fill-slot</span> + </p> + + <span z:define="global message str:goodbye cruel world"/> + + <p m:use-macro="whoops">use-macro</p> + + <p m:define-slot="whoops">define-slot</p> + + </body> + +</html> diff --git a/tests/input/test04.xml b/tests/input/test04.xml new file mode 100644 index 0000000..bde6cef --- /dev/null +++ b/tests/input/test04.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" ?> +<html> + + <body xmlns:m="http://xml.zope.org/namespaces/metal" xmlns:z="http://xml.zope.org/namespaces/tal" m:define-macro="body" z:define="global count python:0"> + + <ul m:define-macro="whoops"> + <li z:repeat="item python:range(count)"> + <span z:replace="item">1</span> + <span z:replace="global:message"/> + </li> + </ul> + + <span z:define="global count python:2; global message str:hello world"/> + + <p m:use-macro="whoops">use-macro + <span m:fill-slot="whoops">fill-slot</span> + </p> + + <span z:define="global message str:goodbye cruel world"/> + + <p m:use-macro="whoops">use-macro</p> + + <p m:define-slot="whoops">define-slot</p> + + </body> + +</html> diff --git a/tests/input/test05.html b/tests/input/test05.html new file mode 100644 index 0000000..21f6b68 --- /dev/null +++ b/tests/input/test05.html @@ -0,0 +1,9 @@ +<html> + + <body xmlns:m="http://xml.zope.org/namespaces/metal" m:define-macro="body"> + + <h1>This is the body of test5</h1> + + </body> + +</html> diff --git a/tests/input/test05.xml b/tests/input/test05.xml new file mode 100644 index 0000000..fcaaf6b --- /dev/null +++ b/tests/input/test05.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" ?> +<html> + + <body xmlns:m="http://xml.zope.org/namespaces/metal" m:define-macro="body"> + + <h1>This is the body of test5</h1> + + </body> + +</html> diff --git a/tests/input/test06.html b/tests/input/test06.html new file mode 100644 index 0000000..ac1264d --- /dev/null +++ b/tests/input/test06.html @@ -0,0 +1,6 @@ +<html> + <body xmlns:m="http://xml.zope.org/namespaces/metal" + m:use-macro="tests/input/test05.html/body"> + dummy body in test6 + </body> +</html> diff --git a/tests/input/test06.xml b/tests/input/test06.xml new file mode 100644 index 0000000..b32bd0f --- /dev/null +++ b/tests/input/test06.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" ?> +<html> + <body xmlns:m="http://xml.zope.org/namespaces/metal" + m:use-macro="tests/input/test05.xml/body"> + dummy body in test6 + </body> +</html> diff --git a/tests/input/test07.html b/tests/input/test07.html new file mode 100644 index 0000000..bff98f0 --- /dev/null +++ b/tests/input/test07.html @@ -0,0 +1,11 @@ +<table xmlns:m="http://xml.zope.org/namespaces/metal" m:define-macro="myTable"> +<!-- macro definition with slots --> + <tr> + <td>Top Left</td> + <td>Top Right</td> + </tr> + <tr> + <td>Bottom left</td> + <td><span m:define-slot="bottomRight">Bottom Right</span></td> + </tr> +</table> diff --git a/tests/input/test07.xml b/tests/input/test07.xml new file mode 100644 index 0000000..e5c520a --- /dev/null +++ b/tests/input/test07.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" ?> +<table xmlns:m="http://xml.zope.org/namespaces/metal" m:define-macro="myTable"> +<!-- macro definition with slots --> + <tr> + <td>Top Left</td> + <td>Top Right</td> + </tr> + <tr> + <td>Bottom left</td> + <td><span m:define-slot="bottomRight">Bottom Right</span></td> + </tr> +</table> diff --git a/tests/input/test08.html b/tests/input/test08.html new file mode 100644 index 0000000..1e4915b --- /dev/null +++ b/tests/input/test08.html @@ -0,0 +1,44 @@ +<table xmlns:m="http://xml.zope.org/namespaces/metal" m:use-macro="tests/input/test07.html/myTable"> +<!-- macro use with slots --> + <tr> + <td> + <span m:fill-slot="bottomRight"> + <h1>Some headline</h1> + <p>This is the real contents of the bottom right slot.</p> + <p>It is supposed to contain a lot of text. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb.</p> + <p>It is supposed to contain a lot of text. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb.</p> + <p>It is supposed to contain a lot of text. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb.</p> + </span> + </td> + </tr> +</table> diff --git a/tests/input/test08.xml b/tests/input/test08.xml new file mode 100644 index 0000000..b0360fa --- /dev/null +++ b/tests/input/test08.xml @@ -0,0 +1,45 @@ +<?xml version="1.0" ?> +<table xmlns:m="http://xml.zope.org/namespaces/metal" m:use-macro="tests/input/test07.xml/myTable"> +<!-- macro use with slots --> + <tr> + <td> + <span m:fill-slot="bottomRight"> + <h1>Some headline</h1> + <p>This is the real contents of the bottom right slot.</p> + <p>It is supposed to contain a lot of text. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb.</p> + <p>It is supposed to contain a lot of text. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb.</p> + <p>It is supposed to contain a lot of text. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb.</p> + </span> + </td> + </tr> +</table> diff --git a/tests/input/test09.html b/tests/input/test09.html new file mode 100644 index 0000000..35f481a --- /dev/null +++ b/tests/input/test09.html @@ -0,0 +1,30 @@ +<html> +<body> +<p> + Just a bunch of text. +<p>more text... +<ul> + <li>first item + <li>second item + + <ol> + <li>second list, first item + <li>second list, second item + <dl compact> + <dt>term 1 + <dt>term 2 + <dd>definition + </dl> + </ol> + + <li>Now let's have a paragraph... + <p>My Paragraph + </li> + + <li>And a table in a list item: + <table> + </table> +</ul> + +</body> +</html> diff --git a/tests/input/test09.xml b/tests/input/test09.xml new file mode 100644 index 0000000..c3d10d7 --- /dev/null +++ b/tests/input/test09.xml @@ -0,0 +1,30 @@ +<html> +<body> +<p> + Just a bunch of text.</p> +<p>more text...</p> +<ul> + <li>first item</li> + <li>second item + + <ol> + <li>second list, first item</li> + <li>second list, second item + <dl compact=""> + <dt>term 1</dt> + <dt>term 2</dt> + <dd>definition</dd> + </dl></li> + </ol></li> + + <li>Now let's have a paragraph... + <p>My Paragraph</p> + </li> + + <li>And a table in a list item: + <table> + </table></li> +</ul> + +</body> +</html> diff --git a/tests/input/test10.html b/tests/input/test10.html new file mode 100644 index 0000000..6ecca4c --- /dev/null +++ b/tests/input/test10.html @@ -0,0 +1,48 @@ +<html><body> +<table xmlns:m="http://xml.zope.org/namespaces/metal" m:use-macro="tests/input/test07.html/myTable"> +<!-- macro use with slots --> + <tr> + <td> + <span m:fill-slot="bottomRight"> + <h1>Some headline</h1> + <p>This is the real contents of the bottom right slot.</p> + <hr> + <p>It is supposed to contain a lot of text. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb.</p> + <p>It is supposed to contain a lot of text. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb.</p> + <p>It is supposed to contain a lot of text. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb.</p> + <br><br> + </span> + </td> + </tr> +</table> +</body></html> diff --git a/tests/input/test11.html b/tests/input/test11.html new file mode 100644 index 0000000..435f95c --- /dev/null +++ b/tests/input/test11.html @@ -0,0 +1,14 @@ +<html xmlns:tal="http://xml.zope.org/namespaces/tal"> + <p tal:replace="structure string:<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/tests/input/test11.xml b/tests/input/test11.xml new file mode 100644 index 0000000..435f95c --- /dev/null +++ b/tests/input/test11.xml @@ -0,0 +1,14 @@ +<html xmlns:tal="http://xml.zope.org/namespaces/tal"> + <p tal:replace="structure string:<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/tests/input/test12.html b/tests/input/test12.html new file mode 100644 index 0000000..94d9a66 --- /dev/null +++ b/tests/input/test12.html @@ -0,0 +1,24 @@ +<span tal:define="global true python:1; global false python:0" /> + +<img ismap> +<img ismap=ismap> +<img ismap="ismap"> +<img ismap="foo"> + +<img ismap tal:attributes="ismap true"> +<img ismap tal:attributes="ismap false"> +<img ismap tal:attributes="ismap nothing"> + +<img ismap="foo" tal:attributes="ismap true"> +<img ismap="foo" tal:attributes="ismap false"> +<img ismap="foo" tal:attributes="ismap nothing"> + +<img tal:attributes="ismap true"> +<img tal:attributes="ismap false"> +<img tal:attributes="ismap nothing"> + +<span tal:define="global x string:x.gif" /> + +<img src="foo"> +<img src="foo" tal:attributes="src x"> +<img src="foo" tal:attributes="src nothing"> diff --git a/tests/input/test13.html b/tests/input/test13.html new file mode 100644 index 0000000..d68e0ce --- /dev/null +++ b/tests/input/test13.html @@ -0,0 +1,7 @@ +Here's a stray greater than: > + +<script> + <!-- no comment --> + <notag> + &noentity; +</script> diff --git a/tests/input/test14.html b/tests/input/test14.html new file mode 100644 index 0000000..0aaa751 --- /dev/null +++ b/tests/input/test14.html @@ -0,0 +1,10 @@ +<table> + <tr> + <td tal:repeat="x python:['car', 'bike', 'broomstick']" tal:content="x"> + </td> + </tr> +</table> + +<p> + <span tal:repeat="x python:['Harry', 'Ron', 'Hermione']" tal:replace="x" /> +</p> diff --git a/tests/input/test14.xml b/tests/input/test14.xml new file mode 100644 index 0000000..c596135 --- /dev/null +++ b/tests/input/test14.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" ?> +<html xmlns:tal="http://xml.zope.org/namespaces/tal"> + +<table> + <tr> + <td tal:repeat="x python:['car', 'bike', 'broomstick']" tal:content="x"> + </td> + </tr> +</table> + +<p> + <span tal:repeat="x python:['Harry', 'Ron', 'Hermione']" tal:replace="x" /> +</p> + +</html> diff --git a/tests/input/test15.html b/tests/input/test15.html new file mode 100644 index 0000000..0cd456e --- /dev/null +++ b/tests/input/test15.html @@ -0,0 +1,26 @@ +<span metal:define-macro="INNER"> + <span metal:define-slot="INNERSLOT">INNERSLOT</span> +</span> + +<xxx metal:use-macro="INNER"> + <xxx metal:fill-slot="INNERSLOT">inner-argument</xxx> +</xxx> + +<div metal:define-macro="OUTER"> +<div metal:use-macro="INNER"> + <xxx metal:define-slot="OUTERSLOT" metal:fill-slot="INNERSLOT"> + OUTERSLOT + </xxx> +</div> +</div> + +<div metal:use-macro="OUTER"> +<span> + <xxx> + <div metal:fill-slot="OUTERSLOT">outer-argument</div> + </xxx> +</span> +</div> + +<div metal:use-macro="OUTER"> +</div> diff --git a/tests/input/test16.html b/tests/input/test16.html new file mode 100644 index 0000000..1414f45 --- /dev/null +++ b/tests/input/test16.html @@ -0,0 +1,2 @@ +<a href="valid/link.html" + tal:attributes="href python:'/base/' + attrs['href']">blah, blah</a> diff --git a/tests/input/test16.xml b/tests/input/test16.xml new file mode 100755 index 0000000..05860d8 --- /dev/null +++ b/tests/input/test16.xml @@ -0,0 +1,7 @@ +<?xml version="1.0"?> +<body xmlns:tal="http://xml.zope.org/namespaces/tal"> + +<img href="foo" Alt="bar" + tal:attributes="Href string:about:foo;alT string:baz" /> + +</body> diff --git a/tests/input/test17.html b/tests/input/test17.html new file mode 100644 index 0000000..5a5ebb3 --- /dev/null +++ b/tests/input/test17.html @@ -0,0 +1,6 @@ +<tal:block tal:content="string:Yes">No</tal:block> +<tal:block content="string:Yes">No</tal:block> +<tal:block>Yes</tal:block> + +<metal:block tal:content="string:Yes">No</metal:block> +<metal:block>Yes</metal:block> diff --git a/tests/input/test17.xml b/tests/input/test17.xml new file mode 100644 index 0000000..ecb617a --- /dev/null +++ b/tests/input/test17.xml @@ -0,0 +1,10 @@ +<?xml version="1.0"?> +<body xmlns:z="http://xml.zope.org/namespaces/tal" + xmlns:z2="http://xml.zope.org/namespaces/metal"> +<z:block z:content="string:Yes">No</z:block> +<z:block content="string:Yes">No</z:block> +<z:block>Yes</z:block> + +<z2:block z:content="string:Yes">No</z2:block> +<z2:block>Yes</z2:block> +</body> diff --git a/tests/input/test18.html b/tests/input/test18.html new file mode 100644 index 0000000..c3a5c26 --- /dev/null +++ b/tests/input/test18.html @@ -0,0 +1,16 @@ +<p tal:omit-tag="">Content</p> +<p tal:omit-tag=""></p> +<img tal:omit-tag=""> + +<p tal:omit-tag="string:Yes">Content</p> +<p tal:omit-tag="string:Yes"></p> +<img tal:omit-tag="string:Yes"> + +<p tal:omit-tag="nothing">Content</p> +<p tal:omit-tag="nothing"></p> +<img tal:omit-tag="nothing"> + +<p tal:define="txt string:Yes" tal:omit-tag="" tal:content="txt">No</p> +<p tal:define="txt string:Yes" tal:omit-tag="" tal:replace="txt">No</p> +<p tal:omit-tag="" tal:content="default">Yes</p> +<p tal:omit-tag="" tal:replace="default">Yes</p> diff --git a/tests/input/test18.xml b/tests/input/test18.xml new file mode 100644 index 0000000..5a0cca4 --- /dev/null +++ b/tests/input/test18.xml @@ -0,0 +1,20 @@ +<?xml version="1.0"?> +<body xmlns:tal="http://xml.zope.org/namespaces/tal" + xmlns:metal="http://xml.zope.org/namespaces/metal"> +<p tal:omit-tag="">Content</p> +<p tal:omit-tag=""></p> +<img tal:omit-tag=""/> + +<p tal:omit-tag="string:Yes">Content</p> +<p tal:omit-tag="string:Yes"></p> +<img tal:omit-tag="string:Yes"/> + +<p tal:omit-tag="nothing">Content</p> +<p tal:omit-tag="nothing"></p> +<img tal:omit-tag="nothing" /> + +<p tal:define="txt string:Yes" tal:omit-tag="" tal:content="txt">No</p> +<p tal:define="txt string:Yes" tal:omit-tag="" tal:replace="txt">No</p> +<p tal:omit-tag="" tal:content="default">Yes</p> +<p tal:omit-tag="" tal:replace="default">Yes</p> +</body> diff --git a/tests/input/test19.html b/tests/input/test19.html new file mode 100644 index 0000000..a56632a --- /dev/null +++ b/tests/input/test19.html @@ -0,0 +1,5 @@ +<span i18n:translate="">Replace this</span> +<span i18n:translate="msgid">This is a +translated string</span> +<span i18n:translate="">And another +translated string</span> diff --git a/tests/input/test20.html b/tests/input/test20.html new file mode 100644 index 0000000..f302213 --- /dev/null +++ b/tests/input/test20.html @@ -0,0 +1 @@ +<span i18n:translate="">replaceable <p tal:replace="str:here">content</p></span> diff --git a/tests/input/test21.html b/tests/input/test21.html new file mode 100644 index 0000000..95f925e --- /dev/null +++ b/tests/input/test21.html @@ -0,0 +1,4 @@ +<span i18n:translate=""> + <span tal:replace="str:Lomax" i18n:name="name" /> was born in + <span tal:replace="str:Antarctica" i18n:name="country" />. +</span> diff --git a/tests/input/test22.html b/tests/input/test22.html new file mode 100644 index 0000000..26fa45a --- /dev/null +++ b/tests/input/test22.html @@ -0,0 +1,4 @@ +<span i18n:translate=""> + <span i18n:name="name"><b>Jim</b></span> was born in + <span i18n:name="country">the USA</span>. +</span> diff --git a/tests/input/test23.html b/tests/input/test23.html new file mode 100644 index 0000000..bfe6665 --- /dev/null +++ b/tests/input/test23.html @@ -0,0 +1,2 @@ +<span i18n:data="here/currentTime" + i18n:translate="timefmt">2:32 pm</span> diff --git a/tests/input/test24.html b/tests/input/test24.html new file mode 100644 index 0000000..e5b7b6e --- /dev/null +++ b/tests/input/test24.html @@ -0,0 +1,3 @@ +<input name="Delete" + tal:attributes="name string:delete_button" + i18n:attributes="name"> diff --git a/tests/input/test25.html b/tests/input/test25.html new file mode 100644 index 0000000..25a99cf --- /dev/null +++ b/tests/input/test25.html @@ -0,0 +1 @@ +<input name="Delete" i18n:attributes="name"> diff --git a/tests/input/test26.html b/tests/input/test26.html new file mode 100644 index 0000000..fa5a99d --- /dev/null +++ b/tests/input/test26.html @@ -0,0 +1,3 @@ +<span i18n:translate="jobnum"> + Job #<span tal:replace="context/@@object_name" + i18n:name="jobnum">NN</span></span> diff --git a/tests/input/test27.html b/tests/input/test27.html new file mode 100644 index 0000000..b9c16cb --- /dev/null +++ b/tests/input/test27.html @@ -0,0 +1,5 @@ +<p i18n:translate="verify">Your contact email address is recorded as + <a href="mailto:user@example.com" + tal:content="request/submitter" + i18n:name="email">user@host.com</a> +</p> diff --git a/tests/input/test28.html b/tests/input/test28.html new file mode 100644 index 0000000..14fd7d9 --- /dev/null +++ b/tests/input/test28.html @@ -0,0 +1,5 @@ +<p i18n:translate="verify">Your contact email address is recorded as + <span i18n:name="email"> + <a href="mailto:user@example.com" + tal:content="request/submitter">user@host.com</a></span> +</p> diff --git a/tests/input/test29.html b/tests/input/test29.html new file mode 100644 index 0000000..665b0ee --- /dev/null +++ b/tests/input/test29.html @@ -0,0 +1,4 @@ +At the tone the time will be +<span i18n:data="here/currentTime" + i18n:translate="timefmt" + i18n:name="time">2:32 pm</span>... beep! diff --git a/tests/input/test30.html b/tests/input/test30.html new file mode 100644 index 0000000..6f8c6ef --- /dev/null +++ b/tests/input/test30.html @@ -0,0 +1,6 @@ +<p i18n:translate="verify">Your contact email address is recorded as +<a href="user@host.com" + tal:attributes="href string:mailto:${request/submitter}" + tal:content="request/submitter" + i18n:name="email">user@host.com</a> +</p> diff --git a/tests/input/test31.html b/tests/input/test31.html new file mode 100644 index 0000000..c821761 --- /dev/null +++ b/tests/input/test31.html @@ -0,0 +1,7 @@ +<p i18n:translate="verify">Your contact email address is recorded as +<span i18n:name="email"> +<a href="user@host.com" + tal:attributes="href string:mailto:${request/submitter}" + tal:content="request/submitter"> + user@host.com</a></span> +</p> diff --git a/tests/input/test32.html b/tests/input/test32.html new file mode 100644 index 0000000..3b09bad --- /dev/null +++ b/tests/input/test32.html @@ -0,0 +1,4 @@ +<span i18n:translate="origin"> + <span tal:content="str:Lomax" i18n:name="name" /> was born in + <span tal:content="str:Antarctica" i18n:name="country" />. +</span> diff --git a/tests/input/test_metal1.html b/tests/input/test_metal1.html new file mode 100644 index 0000000..a5371ce --- /dev/null +++ b/tests/input/test_metal1.html @@ -0,0 +1,61 @@ +<span metal:define-macro="OUTER"> + AAA + <span metal:define-macro="INNER">INNER</span> + BBB +</span> + +<xxx metal:use-macro="OUTER"> +</xxx> + +<xxx metal:use-macro="INNER"> +</xxx> + +<span metal:define-macro="OUTER2"> + AAA + <xxx metal:define-slot="OUTERSLOT"> + <span metal:define-macro="INNER2">INNER</span> + </xxx> + BBB +</span> + +<xxx metal:use-macro="OUTER2"> +</xxx> + +<xxx metal:use-macro="INNER2"> +</xxx> + +<xxx metal:use-macro="OUTER2"> + <yyy metal:fill-slot="OUTERSLOT">OUTERSLOT</yyy> +</xxx> + +<span metal:define-macro="OUTER3"> + AAA + <xxx metal:define-slot="OUTERSLOT"> + <span metal:define-macro="INNER3">INNER + <xxx metal:define-slot="INNERSLOT">INNERSLOT</xxx> + </span> + </xxx> + BBB +</span> + +<xxx metal:use-macro="OUTER3"> +</xxx> + +<xxx metal:use-macro="OUTER3"> + <yyy metal:fill-slot="OUTERSLOT">OUTERSLOT</yyy> +</xxx> + +<xxx metal:use-macro="INNER3"> +</xxx> + +<xxx metal:use-macro="INNER3"> + <yyy metal:fill-slot="INNERSLOT">INNERSLOT</yyy> +</xxx> + +<xxx metal:use-macro="INNER3"> + <yyy metal:fill-slot="INNERSLOT"> + <zzz metal:define-macro="INSLOT">INSLOT</zzz> + </yyy> +</xxx> + +<xxx metal:use-macro="INSLOT"></xxx> diff --git a/tests/input/test_metal2.html b/tests/input/test_metal2.html new file mode 100644 index 0000000..425508a --- /dev/null +++ b/tests/input/test_metal2.html @@ -0,0 +1,7 @@ +<div metal:define-macro="OUTER"> + OUTER + <span metal:define-macro="INNER">INNER</span> + OUTER +</div> + +<div metal:use-macro="OUTER"/> diff --git a/tests/input/test_metal3.html b/tests/input/test_metal3.html new file mode 100644 index 0000000..b0af907 --- /dev/null +++ b/tests/input/test_metal3.html @@ -0,0 +1 @@ +<span tal:attributes="class string:foo">Should not get attr in metal</span> diff --git a/tests/markbench.py b/tests/markbench.py new file mode 100644 index 0000000..9791e67 --- /dev/null +++ b/tests/markbench.py @@ -0,0 +1,138 @@ +#! /usr/bin/env python +############################################################################## +# +# Copyright (c) 2001, 2002 Zope Corporation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +'''Run benchmarks of TAL vs. DTML''' + +import warnings +warnings.filterwarnings("ignore", category=DeprecationWarning) + +import os +os.environ['NO_SECURITY'] = 'true' + +import sys +import time + +from cStringIO import StringIO + +#from zope.documenttemplate.dt_html import HTMLFile + +from zope.tal.htmltalparser import HTMLTALParser +from zope.tal.talinterpreter import TALInterpreter +from zope.tal.dummyengine import DummyEngine + + +def time_apply(f, args, kwargs, count): + r = [None] * count + for i in range(4): + f(*args, **kwargs) + t0 = time.clock() + for i in r: + pass + t1 = time.clock() + for i in r: + f(*args, **kwargs) + t = time.clock() - t1 - (t1 - t0) + return t / count + +def time_zpt(fn, count): + from zope.pagetemplate.pagetemplate import PageTemplate + pt = PageTemplate() + pt.write(open(fn).read()) + return time_apply(pt.pt_render, (data,), {}, count) + +def time_tal(fn, count): + p = HTMLTALParser() + p.parseFile(fn) + program, macros = p.getCode() + engine = DummyEngine(macros) + engine.globals = data + tal = TALInterpreter(program, macros, engine, StringIO(), wrap=0, + tal=1, strictinsert=0) + return time_apply(tal, (), {}, count) + +def time_dtml(fn, count): + html = HTMLFile(fn) + return time_apply(html, (), data, count) + +def profile_zpt(fn, count, profiler): + from zope.pagetemplate.pagetemplate import PageTemplate + pt = PageTemplate() + pt.write(open(fn).read()) + for i in range(4): + pt.pt_render(extra_context=data) + r = [None] * count + for i in r: + profiler.runcall(pt.pt_render, 0, data) + +def profile_tal(fn, count, profiler): + p = HTMLTALParser() + p.parseFile(fn) + program, macros = p.getCode() + engine = DummyEngine(macros) + engine.globals = data + tal = TALInterpreter(program, macros, engine, StringIO(), wrap=0, + tal=1, strictinsert=0) + for i in range(4): + tal() + r = [None] * count + for i in r: + profiler.runcall(tal) + +tal_fn = 'benchmark/tal%.2d.html' +dtml_fn = 'benchmark/dtml%.2d.html' + +def compare(n, count, profiler=None): + t1 = int(time_zpt(tal_fn % n, count) * 1000 + 0.5) + t2 = int(time_tal(tal_fn % n, count) * 1000 + 0.5) + t3 = 'n/a' # int(time_dtml(dtml_fn % n, count) * 1000 + 0.5) + print '%.2d: %10s %10s %10s' % (n, t1, t2, t3) + if profiler: + profile_tal(tal_fn % n, count, profiler) + +def main(count, profiler=None): + n = 1 + print '##: %10s %10s %10s' % ('ZPT', 'TAL', 'DTML') + while os.path.isfile(tal_fn % n) and os.path.isfile(dtml_fn % n): + compare(n, count, profiler) + n = n + 1 + +data = {'x':'X', 'r2': range(2), 'r8': range(8), 'r64': range(64)} +for i in range(10): + data['x%s' % i] = 'X%s' % i + +if __name__ == "__main__": + filename = "markbench.prof" + profiler = None + if len(sys.argv) > 1 and sys.argv[1] == "-p": + import profile + profiler = profile.Profile() + del sys.argv[1] + + if len(sys.argv) > 1: + for arg in sys.argv[1:]: + compare(int(arg), 25, profiler) + else: + main(25, profiler) + + if profiler is not None: + profiler.dump_stats(filename) + import pstats + p = pstats.Stats(filename) + p.strip_dirs() + p.sort_stats('time', 'calls') + try: + p.print_stats(20) + except IOError, e: + if e.errno != errno.EPIPE: + raise diff --git a/tests/output/__init__.py b/tests/output/__init__.py new file mode 100644 index 0000000..b711d36 --- /dev/null +++ b/tests/output/__init__.py @@ -0,0 +1,2 @@ +# +# This file is necessary to make this directory a package. diff --git a/tests/output/test01.html b/tests/output/test01.html new file mode 100644 index 0000000..7064db0 --- /dev/null +++ b/tests/output/test01.html @@ -0,0 +1,68 @@ +<!DOCTYPE html + PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "DTD/xhtml1-transitional.dtd"> +<html> + + <head>dadada</head> + + <body> + + <h1>This +Is +The +Replaced +Title</h1> + + <!-- test entity references --> + &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/tests/output/test01.xml b/tests/output/test01.xml new file mode 100644 index 0000000..91e9851 --- /dev/null +++ b/tests/output/test01.xml @@ -0,0 +1,65 @@ +<?xml version="1.0" ?> +<!DOCTYPE html + PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "DTD/xhtml1-transitional.dtd"> +<html> + + <head>dadada</head> + + <body> + + <h1>This Is The Replaced Title</h1> + + <!-- test entity references --> + &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/tests/output/test02.html b/tests/output/test02.html new file mode 100644 index 0000000..8d081fc --- /dev/null +++ b/tests/output/test02.html @@ -0,0 +1,118 @@ +<biztalk_1 xmlns="urn:schemas-biztalk-org:biztalk:biztalk_1"> + +<foo:header xmlns:foo="whomping-willow" plain="guido" quote=""" 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/tests/output/test02.xml b/tests/output/test02.xml new file mode 100644 index 0000000..71ff075 --- /dev/null +++ b/tests/output/test02.xml @@ -0,0 +1,119 @@ +<?xml version="1.0" ?> +<biztalk_1 xmlns="urn:schemas-biztalk-org:biztalk:biztalk_1"> + +<foo:header xmlns:foo="whomping-willow" plain="guido" quote=""" 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/tests/output/test03.html b/tests/output/test03.html new file mode 100644 index 0000000..7fb5156 --- /dev/null +++ b/tests/output/test03.html @@ -0,0 +1,9 @@ +<p> + <span> + <span>hello brave new world</span> + <span> + <span>goodbye cruel world</span> + </span> + <span>hello brave new world</span> + </span> +</p> diff --git a/tests/output/test03.xml b/tests/output/test03.xml new file mode 100644 index 0000000..24be638 --- /dev/null +++ b/tests/output/test03.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" ?> +<p> + <span> + <span>hello brave new world</span> + <span> + <span>goodbye cruel world</span> + </span> + <span>hello brave new world</span> + </span> +</p> diff --git a/tests/output/test04.html b/tests/output/test04.html new file mode 100644 index 0000000..f0666da --- /dev/null +++ b/tests/output/test04.html @@ -0,0 +1,38 @@ +<html> + + <body> + + <ul> + </ul> + + <span /> + + <ul> + <li> + 0 + hello world + </li> + <li> + 1 + hello world + </li> + </ul> + + <span /> + + <ul> + <li> + 0 + goodbye cruel world + </li> + <li> + 1 + goodbye cruel world + </li> + </ul> + + <p>define-slot</p> + + </body> + +</html> diff --git a/tests/output/test04.xml b/tests/output/test04.xml new file mode 100644 index 0000000..8b73d02 --- /dev/null +++ b/tests/output/test04.xml @@ -0,0 +1,39 @@ +<?xml version="1.0" ?> +<html> + + <body> + + <ul> + </ul> + + <span/> + + <ul> + <li> + 0 + hello world + </li> + <li> + 1 + hello world + </li> + </ul> + + <span/> + + <ul> + <li> + 0 + goodbye cruel world + </li> + <li> + 1 + goodbye cruel world + </li> + </ul> + + <p>define-slot</p> + + </body> + +</html> diff --git a/tests/output/test05.html b/tests/output/test05.html new file mode 100644 index 0000000..006851a --- /dev/null +++ b/tests/output/test05.html @@ -0,0 +1,9 @@ +<html> + + <body> + + <h1>This is the body of test5</h1> + + </body> + +</html> diff --git a/tests/output/test05.xml b/tests/output/test05.xml new file mode 100644 index 0000000..0bc2691 --- /dev/null +++ b/tests/output/test05.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" ?> +<html> + + <body> + + <h1>This is the body of test5</h1> + + </body> + +</html> diff --git a/tests/output/test06.html b/tests/output/test06.html new file mode 100644 index 0000000..d3f58d9 --- /dev/null +++ b/tests/output/test06.html @@ -0,0 +1,7 @@ +<html> + <body> + + <h1>This is the body of test5</h1> + + </body> +</html> diff --git a/tests/output/test06.xml b/tests/output/test06.xml new file mode 100644 index 0000000..b9ad4ac --- /dev/null +++ b/tests/output/test06.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" ?> +<html> + <body> + + <h1>This is the body of test5</h1> + + </body> +</html> diff --git a/tests/output/test07.html b/tests/output/test07.html new file mode 100644 index 0000000..e0b3d88 --- /dev/null +++ b/tests/output/test07.html @@ -0,0 +1,11 @@ +<table> +<!-- macro definition with slots --> + <tr> + <td>Top Left</td> + <td>Top Right</td> + </tr> + <tr> + <td>Bottom left</td> + <td><span>Bottom Right</span></td> + </tr> +</table> diff --git a/tests/output/test07.xml b/tests/output/test07.xml new file mode 100644 index 0000000..8884d97 --- /dev/null +++ b/tests/output/test07.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" ?> +<table> +<!-- macro definition with slots --> + <tr> + <td>Top Left</td> + <td>Top Right</td> + </tr> + <tr> + <td>Bottom left</td> + <td><span>Bottom Right</span></td> + </tr> +</table> diff --git a/tests/output/test08.html b/tests/output/test08.html new file mode 100644 index 0000000..06e01b2 --- /dev/null +++ b/tests/output/test08.html @@ -0,0 +1,47 @@ +<table> +<!-- macro definition with slots --> + <tr> + <td>Top Left</td> + <td>Top Right</td> + </tr> + <tr> + <td>Bottom left</td> + <td><span> + <h1>Some headline</h1> + <p>This is the real contents of the bottom right slot.</p> + <p>It is supposed to contain a lot of text. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb.</p> + <p>It is supposed to contain a lot of text. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb.</p> + <p>It is supposed to contain a lot of text. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb.</p> + </span></td> + </tr> +</table> diff --git a/tests/output/test08.xml b/tests/output/test08.xml new file mode 100644 index 0000000..51a969c --- /dev/null +++ b/tests/output/test08.xml @@ -0,0 +1,48 @@ +<?xml version="1.0" ?> +<table> +<!-- macro definition with slots --> + <tr> + <td>Top Left</td> + <td>Top Right</td> + </tr> + <tr> + <td>Bottom left</td> + <td><span> + <h1>Some headline</h1> + <p>This is the real contents of the bottom right slot.</p> + <p>It is supposed to contain a lot of text. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb.</p> + <p>It is supposed to contain a lot of text. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb.</p> + <p>It is supposed to contain a lot of text. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb.</p> + </span></td> + </tr> +</table> diff --git a/tests/output/test09.html b/tests/output/test09.html new file mode 100644 index 0000000..844c1a9 --- /dev/null +++ b/tests/output/test09.html @@ -0,0 +1,30 @@ +<html> +<body> +<p> + Just a bunch of text.</p> +<p>more text...</p> +<ul> + <li>first item</li> + <li>second item + + <ol> + <li>second list, first item</li> + <li>second list, second item + <dl compact> + <dt>term 1</dt> + <dt>term 2</dt> + <dd>definition</dd> + </dl></li> + </ol></li> + + <li>Now let's have a paragraph... + <p>My Paragraph</p> + </li> + + <li>And a table in a list item: + <table> + </table></li> +</ul> + +</body> +</html> diff --git a/tests/output/test09.xml b/tests/output/test09.xml new file mode 100644 index 0000000..c3d10d7 --- /dev/null +++ b/tests/output/test09.xml @@ -0,0 +1,30 @@ +<html> +<body> +<p> + Just a bunch of text.</p> +<p>more text...</p> +<ul> + <li>first item</li> + <li>second item + + <ol> + <li>second list, first item</li> + <li>second list, second item + <dl compact=""> + <dt>term 1</dt> + <dt>term 2</dt> + <dd>definition</dd> + </dl></li> + </ol></li> + + <li>Now let's have a paragraph... + <p>My Paragraph</p> + </li> + + <li>And a table in a list item: + <table> + </table></li> +</ul> + +</body> +</html> diff --git a/tests/output/test10.html b/tests/output/test10.html new file mode 100644 index 0000000..d9cc7ed --- /dev/null +++ b/tests/output/test10.html @@ -0,0 +1,51 @@ +<html><body> +<table> +<!-- macro definition with slots --> + <tr> + <td>Top Left</td> + <td>Top Right</td> + </tr> + <tr> + <td>Bottom left</td> + <td><span> + <h1>Some headline</h1> + <p>This is the real contents of the bottom right slot.</p> + <hr> + <p>It is supposed to contain a lot of text. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb.</p> + <p>It is supposed to contain a lot of text. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb.</p> + <p>It is supposed to contain a lot of text. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. + Blabber, blabber, blah. Baah, baah, barb.</p> + <br><br> + </span></td> + </tr> +</table> +</body></html> diff --git a/tests/output/test11.html b/tests/output/test11.html new file mode 100644 index 0000000..caba039 --- /dev/null +++ b/tests/output/test11.html @@ -0,0 +1,5 @@ +<html> + <a href="http://www.python.org">bar</a> + <p>bad boy!</p> + <p>x undefined</p> +</html> diff --git a/tests/output/test11.xml b/tests/output/test11.xml new file mode 100644 index 0000000..caba039 --- /dev/null +++ b/tests/output/test11.xml @@ -0,0 +1,5 @@ +<html> + <a href="http://www.python.org">bar</a> + <p>bad boy!</p> + <p>x undefined</p> +</html> diff --git a/tests/output/test12.html b/tests/output/test12.html new file mode 100644 index 0000000..9533b42 --- /dev/null +++ b/tests/output/test12.html @@ -0,0 +1,24 @@ +<span /> + +<img ismap> +<img ismap="ismap"> +<img ismap="ismap"> +<img ismap="foo"> + +<img ismap="ismap"> +<img> +<img> + +<img ismap="ismap"> +<img> +<img> + +<img ismap="ismap"> +<img> +<img> + +<span /> + +<img src="foo"> +<img src="x.gif"> +<img> diff --git a/tests/output/test13.html b/tests/output/test13.html new file mode 100644 index 0000000..d68e0ce --- /dev/null +++ b/tests/output/test13.html @@ -0,0 +1,7 @@ +Here's a stray greater than: > + +<script> + <!-- no comment --> + <notag> + &noentity; +</script> diff --git a/tests/output/test14.html b/tests/output/test14.html new file mode 100644 index 0000000..b9bf468 --- /dev/null +++ b/tests/output/test14.html @@ -0,0 +1,13 @@ +<table> + <tr> + <td>car</td> + <td>bike</td> + <td>broomstick</td> + </tr> +</table> + +<p> + Harry + Ron + Hermione +</p> diff --git a/tests/output/test14.xml b/tests/output/test14.xml new file mode 100644 index 0000000..67c0c37 --- /dev/null +++ b/tests/output/test14.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" ?> +<html> + +<table> + <tr> + <td>car</td> + <td>bike</td> + <td>broomstick</td> + </tr> +</table> + +<p> + Harry + Ron + Hermione +</p> + +</html> diff --git a/tests/output/test15.html b/tests/output/test15.html new file mode 100644 index 0000000..314fd43 --- /dev/null +++ b/tests/output/test15.html @@ -0,0 +1,29 @@ +<span> + <span>INNERSLOT</span> +</span> + +<span> + <xxx>inner-argument</xxx> +</span> + +<div> +<span> + <xxx> + OUTERSLOT + </xxx> +</span> +</div> + +<div> +<span> + <div>outer-argument</div> +</span> +</div> + +<div> +<span> + <xxx> + OUTERSLOT + </xxx> +</span> +</div> diff --git a/tests/output/test16.html b/tests/output/test16.html new file mode 100644 index 0000000..d3ea228 --- /dev/null +++ b/tests/output/test16.html @@ -0,0 +1 @@ +<a href="/base/valid/link.html">blah, blah</a> diff --git a/tests/output/test16.xml b/tests/output/test16.xml new file mode 100755 index 0000000..b470734 --- /dev/null +++ b/tests/output/test16.xml @@ -0,0 +1,6 @@ +<?xml version="1.0"?> +<body> + +<img href="about:foo" alt="baz"/> + +</body> diff --git a/tests/output/test17.html b/tests/output/test17.html new file mode 100644 index 0000000..e50997d --- /dev/null +++ b/tests/output/test17.html @@ -0,0 +1,6 @@ +Yes +Yes +Yes + +Yes +Yes diff --git a/tests/output/test17.xml b/tests/output/test17.xml new file mode 100644 index 0000000..7a54cdb --- /dev/null +++ b/tests/output/test17.xml @@ -0,0 +1,9 @@ +<?xml version="1.0"?> +<body> +Yes +Yes +Yes + +Yes +Yes +</body> diff --git a/tests/output/test18.html b/tests/output/test18.html new file mode 100644 index 0000000..f49e29e --- /dev/null +++ b/tests/output/test18.html @@ -0,0 +1,16 @@ +Content + + + +Content + + + +<p>Content</p> +<p></p> +<img> + +Yes +Yes +Yes +Yes diff --git a/tests/output/test18.xml b/tests/output/test18.xml new file mode 100644 index 0000000..77eba02 --- /dev/null +++ b/tests/output/test18.xml @@ -0,0 +1,19 @@ +<?xml version="1.0"?> +<body> +Content + + + +Content + + + +<p>Content</p> +<p/> +<img/> + +Yes +Yes +Yes +Yes +</body> diff --git a/tests/output/test19.html b/tests/output/test19.html new file mode 100644 index 0000000..2341a4a --- /dev/null +++ b/tests/output/test19.html @@ -0,0 +1,3 @@ +<span>REPLACE THIS</span> +<span>MSGID</span> +<span>AND ANOTHER TRANSLATED STRING</span> diff --git a/tests/output/test20.html b/tests/output/test20.html new file mode 100644 index 0000000..606b989 --- /dev/null +++ b/tests/output/test20.html @@ -0,0 +1 @@ +<span>REPLACEABLE HERE</span> diff --git a/tests/output/test21.html b/tests/output/test21.html new file mode 100644 index 0000000..95b3b08 --- /dev/null +++ b/tests/output/test21.html @@ -0,0 +1 @@ +<span>Lomax WAS BORN IN Antarctica.</span> diff --git a/tests/output/test22.html b/tests/output/test22.html new file mode 100644 index 0000000..6c1b6de --- /dev/null +++ b/tests/output/test22.html @@ -0,0 +1 @@ +<span><b>Jim</b> WAS BORN IN the USA.</span> diff --git a/tests/output/test23.html b/tests/output/test23.html new file mode 100644 index 0000000..0ea1654 --- /dev/null +++ b/tests/output/test23.html @@ -0,0 +1 @@ +<span>59 minutes after 6 PM</span> diff --git a/tests/output/test24.html b/tests/output/test24.html new file mode 100644 index 0000000..e351a6a --- /dev/null +++ b/tests/output/test24.html @@ -0,0 +1 @@ +<input name="DELETE_BUTTON"> diff --git a/tests/output/test25.html b/tests/output/test25.html new file mode 100644 index 0000000..6b80bd3 --- /dev/null +++ b/tests/output/test25.html @@ -0,0 +1 @@ +<input name="DELETE"> diff --git a/tests/output/test26.html b/tests/output/test26.html new file mode 100644 index 0000000..9d179a6 --- /dev/null +++ b/tests/output/test26.html @@ -0,0 +1 @@ +<span>7 is the JOB NUMBER</span> diff --git a/tests/output/test27.html b/tests/output/test27.html new file mode 100644 index 0000000..96229e4 --- /dev/null +++ b/tests/output/test27.html @@ -0,0 +1 @@ +<p>Your contact email address is recorded as <a href="mailto:user@example.com">aperson@dom.ain</a></p> diff --git a/tests/output/test28.html b/tests/output/test28.html new file mode 100644 index 0000000..96229e4 --- /dev/null +++ b/tests/output/test28.html @@ -0,0 +1 @@ +<p>Your contact email address is recorded as <a href="mailto:user@example.com">aperson@dom.ain</a></p> diff --git a/tests/output/test29.html b/tests/output/test29.html new file mode 100644 index 0000000..019e34d --- /dev/null +++ b/tests/output/test29.html @@ -0,0 +1,2 @@ +At the tone the time will be +59 minutes after 6 PM... beep! diff --git a/tests/output/test30.html b/tests/output/test30.html new file mode 100644 index 0000000..964b772 --- /dev/null +++ b/tests/output/test30.html @@ -0,0 +1 @@ +<p>Your contact email address is recorded as <a href="mailto:${request/submitter}">aperson@dom.ain</a></p> diff --git a/tests/output/test31.html b/tests/output/test31.html new file mode 100644 index 0000000..964b772 --- /dev/null +++ b/tests/output/test31.html @@ -0,0 +1 @@ +<p>Your contact email address is recorded as <a href="mailto:${request/submitter}">aperson@dom.ain</a></p> diff --git a/tests/output/test32.html b/tests/output/test32.html new file mode 100644 index 0000000..f39bd97 --- /dev/null +++ b/tests/output/test32.html @@ -0,0 +1 @@ +<span><span>Lomax</span> was born in <span>Antarctica</span></span> diff --git a/tests/output/test_metal1.html b/tests/output/test_metal1.html new file mode 100644 index 0000000..671ccd0 --- /dev/null +++ b/tests/output/test_metal1.html @@ -0,0 +1,79 @@ +<span metal:define-macro="OUTER"> + AAA + <span metal:define-macro="INNER">INNER</span> + BBB +</span> + +<span metal:use-macro="OUTER"> + AAA + <span>INNER</span> + BBB +</span> + +<span metal:use-macro="INNER">INNER</span> + +<span metal:define-macro="OUTER2"> + AAA + <xxx metal:define-slot="OUTERSLOT"> + <span metal:define-macro="INNER2">INNER</span> + </xxx> + BBB +</span> + +<span metal:use-macro="OUTER2"> + AAA + <xxx metal:fill-slot="OUTERSLOT"> + <span>INNER</span> + </xxx> + BBB +</span> + +<span metal:use-macro="INNER2">INNER</span> + +<span metal:use-macro="OUTER2"> + AAA + <yyy metal:fill-slot="OUTERSLOT">OUTERSLOT</yyy> + BBB +</span> + +<span metal:define-macro="OUTER3"> + AAA + <xxx metal:define-slot="OUTERSLOT"> + <span metal:define-macro="INNER3">INNER + <xxx metal:define-slot="INNERSLOT">INNERSLOT</xxx> + </span> + </xxx> + BBB +</span> + +<span metal:use-macro="OUTER3"> + AAA + <xxx metal:fill-slot="OUTERSLOT"> + <span>INNER + <xxx>INNERSLOT</xxx> + </span> + </xxx> + BBB +</span> + +<span metal:use-macro="OUTER3"> + AAA + <yyy metal:fill-slot="OUTERSLOT">OUTERSLOT</yyy> + BBB +</span> + +<span metal:use-macro="INNER3">INNER + <xxx metal:fill-slot="INNERSLOT">INNERSLOT</xxx> + </span> + +<span metal:use-macro="INNER3">INNER + <yyy metal:fill-slot="INNERSLOT">INNERSLOT</yyy> + </span> + +<span metal:use-macro="INNER3">INNER + <yyy metal:fill-slot="INNERSLOT"> + <zzz metal:define-macro="INSLOT">INSLOT</zzz> + </yyy> + </span> + +<zzz metal:use-macro="INSLOT">INSLOT</zzz> diff --git a/tests/output/test_metal2.html b/tests/output/test_metal2.html new file mode 100644 index 0000000..7e56c0c --- /dev/null +++ b/tests/output/test_metal2.html @@ -0,0 +1,11 @@ +<div metal:define-macro="OUTER"> + OUTER + <span metal:define-macro="INNER">INNER</span> + OUTER +</div> + +<div metal:use-macro="OUTER"> + OUTER + <span>INNER</span> + OUTER +</div> diff --git a/tests/output/test_metal3.html b/tests/output/test_metal3.html new file mode 100644 index 0000000..b0af907 --- /dev/null +++ b/tests/output/test_metal3.html @@ -0,0 +1 @@ +<span tal:attributes="class string:foo">Should not get attr in metal</span> diff --git a/tests/run.py b/tests/run.py new file mode 100644 index 0000000..32de437 --- /dev/null +++ b/tests/run.py @@ -0,0 +1,42 @@ +#! /usr/bin/env python +############################################################################## +# +# Copyright (c) 2001, 2002 Zope Corporation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +"""Run all tests.""" + +import sys +import unittest + +import zope.tal.tests.utils +import zope.tal.tests.test_htmltalparser +import zope.tal.tests.test_talinterpreter +import zope.tal.tests.test_files +import zope.tal.tests.test_sourcepos + +def test_suite(): + suite = unittest.TestSuite() + suite.addTest(test_htmltalparser.test_suite()) + if not utils.skipxml: + import test_xmlparser + suite.addTest(test_xmlparser.test_suite()) + suite.addTest(test_talinterpreter.test_suite()) + suite.addTest(test_files.test_suite()) + suite.addTest(test_sourcepos.test_suite()) + return suite + +def main(): + return utils.run_suite(test_suite()) + +if __name__ == "__main__": + errs = main() + sys.exit(errs and 1 or 0) diff --git a/tests/test_files.py b/tests/test_files.py new file mode 100644 index 0000000..bb9b505 --- /dev/null +++ b/tests/test_files.py @@ -0,0 +1,86 @@ +#! /usr/bin/env python +############################################################################## +# +# Copyright (c) 2001, 2002 Zope Corporation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +"""Tests that run driver.py over input files comparing to output files.""" + +import glob +import os +import sys +import unittest + +import zope.tal.runtest + +from zope.tal.tests import utils + + +class FileTestCase(unittest.TestCase): + + def __init__(self, file, dir): + self.__file = file + self.__dir = dir + unittest.TestCase.__init__(self) + + # For unittest. + def shortDescription(self): + path = os.path.basename(self.__file) + return '%s (%s)' % (path, self.__class__) + + def runTest(self): + basename = os.path.basename(self.__file) + #sys.stdout.write(basename + " ") + sys.stdout.flush() + if basename.startswith('test_metal'): + sys.argv = ["", "-Q", "-m", self.__file] + else: + sys.argv = ["", "-Q", self.__file] + pwd = os.getcwd() + try: + try: + os.chdir(self.__dir) + zope.tal.runtest.main() + finally: + os.chdir(pwd) + except SystemExit, what: + if what.code: + self.fail("output for %s didn't match" % self.__file) + +try: + script = __file__ +except NameError: + script = sys.argv[0] + +def test_suite(): + suite = unittest.TestSuite() + dir = os.path.dirname(script) + dir = os.path.abspath(dir) + parentdir = os.path.dirname(dir) + prefix = os.path.join(dir, "input", "test*.") + if utils.skipxml: + xmlargs = [] + else: + xmlargs = glob.glob(prefix + "xml") + xmlargs.sort() + htmlargs = glob.glob(prefix + "html") + htmlargs.sort() + args = xmlargs + htmlargs + if not args: + sys.stderr.write("Warning: no test input files found!!!\n") + for arg in args: + case = FileTestCase(arg, parentdir) + suite.addTest(case) + return suite + +if __name__ == "__main__": + errs = utils.run_suite(test_suite()) + sys.exit(errs and 1 or 0) diff --git a/tests/test_htmltalparser.py b/tests/test_htmltalparser.py new file mode 100644 index 0000000..4257dc6 --- /dev/null +++ b/tests/test_htmltalparser.py @@ -0,0 +1,850 @@ +#! /usr/bin/env python +############################################################################## +# +# Copyright (c) 2001, 2002 Zope Corporation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +"""Tests for the HTMLTALParser code generator.""" + +import pprint +import sys +import unittest + +from zope.tal import htmltalparser +from zope.tal import taldefs +from zope.tal.tests import utils + + +class TestCaseBase(unittest.TestCase): + + prologue = "" + epilogue = "" + initial_program = [('version', taldefs.TAL_VERSION), ('mode', 'html')] + final_program = [] + + def _merge(self, p1, p2): + if p1 and p2: + op1, args1 = p1[-1] + op2, args2 = p2[0] + if op1.startswith('rawtext') and op2.startswith('rawtext'): + return (p1[:-1] + + [rawtext(args1[0] + args2[0])] + + p2[1:]) + return p1+p2 + + def _run_check(self, source, program, macros={}): + parser = htmltalparser.HTMLTALParser() + parser.parseString(self.prologue + source + self.epilogue) + got_program, got_macros = parser.getCode() + program = self._merge(self.initial_program, program) + program = self._merge(program, self.final_program) + self.assert_(got_program == program, + "Program:\n" + pprint.pformat(got_program) + + "\nExpected:\n" + pprint.pformat(program)) + self.assert_(got_macros == macros, + "Macros:\n" + pprint.pformat(got_macros) + + "\nExpected:\n" + pprint.pformat(macros)) + + def _should_error(self, source, exc=taldefs.TALError): + def parse(self=self, source=source): + parser = htmltalparser.HTMLTALParser() + parser.parseString(self.prologue + source + self.epilogue) + self.assertRaises(exc, parse) + + +def rawtext(s): + """Compile raw text to the appropriate instruction.""" + if "\n" in s: + return ("rawtextColumn", (s, len(s) - (s.rfind("\n") + 1))) + else: + return ("rawtextOffset", (s, len(s))) + + +class HTMLTALParserTestCases(TestCaseBase): + + def test_code_simple_identity(self): + self._run_check("""<html a='b' b="c" c=d><title>My Title</html>""", [ + rawtext('<html a="b" b="c" c="d">' + '<title>My Title</title></html>'), + ]) + + def test_code_implied_list_closings(self): + self._run_check("""<ul><li><p><p><li></ul>""", [ + rawtext('<ul><li><p></p><p></p></li><li></li></ul>'), + ]) + self._run_check("""<dl><dt><dt><dd><dd><ol><li><li></ol></dl>""", [ + rawtext('<dl><dt></dt><dt></dt><dd></dd>' + '<dd><ol><li></li><li></li></ol></dd></dl>'), + ]) + + def test_code_implied_table_closings(self): + self._run_check("""<p>text <table><tr><th>head\t<tr><td>cell\t""" + """<table><tr><td>cell \n \t \n<tr>""", [ + rawtext('<p>text</p> <table><tr><th>head</th>' + '</tr>\t<tr><td>cell\t<table><tr><td>cell</td>' + '</tr> \n \t \n<tr></tr></table></td></tr></table>'), + ]) + self._run_check("""<table><tr><td>cell """ + """<table><tr><td>cell </table></table>""", [ + rawtext('<table><tr><td>cell <table><tr><td>cell</td></tr>' + ' </table></td></tr></table>'), + ]) + + def test_code_bad_nesting(self): + def check(self=self): + self._run_check("<a><b></a></b>", []) + self.assertRaises(htmltalparser.NestingError, check) + + def test_code_attr_syntax(self): + output = [ + rawtext('<a b="v" c="v" d="v" e></a>'), + ] + self._run_check("""<a b='v' c="v" d=v e>""", output) + self._run_check("""<a b = 'v' c = "v" d = v e>""", output) + self._run_check("""<a\nb\n=\n'v'\nc\n=\n"v"\nd\n=\nv\ne>""", output) + self._run_check("""<a\tb\t=\t'v'\tc\t=\t"v"\td\t=\tv\te>""", output) + + def test_code_attr_values(self): + self._run_check( + """<a b='xxx\n\txxx' c="yyy\t\nyyy" d='\txyz\n'>""", [ + rawtext('<a b="xxx\n\txxx" c="yyy\t\nyyy" d="\txyz\n"></a>')]) + self._run_check("""<a b='' c="">""", [ + rawtext('<a b="" c=""></a>'), + ]) + + def test_code_attr_entity_replacement(self): + # we expect entities *not* to be replaced by HTLMParser! + self._run_check("""<a b='&><"''>""", [ + 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'}), + ('insertText', ('$string:foo$', + [('startTag', ('p', [('tal:replace', 'string:foo', 'tal')])), + rawtext('bar</p>')])), + ('endScope', ()), + ]) + + def test_replace_2(self): + self._run_check("<p tal:replace='text string:foo'>bar</p>", [ + ('setPosition', (1, 0)), + ('beginScope', {'tal:replace': 'text string:foo'}), + ('insertText', ('$string:foo$', + [('startTag', ('p', + [('tal:replace', 'text string:foo', 'tal')])), + rawtext('bar</p>')])), + ('endScope', ()), + ]) + + def test_replace_3(self): + self._run_check("<p tal:replace='structure string:<br>'>bar</p>", [ + ('setPosition', (1, 0)), + ('beginScope', {'tal:replace': 'structure string:<br>'}), + ('insertStructure', ('$string:<br>$', {}, + [('startTag', ('p', + [('tal:replace', 'structure string:<br>', 'tal')])), + rawtext('bar</p>')])), + ('endScope', ()), + ]) + + def test_repeat(self): + self._run_check("<p tal:repeat='x python:(1,2,3)'>" + "<span tal:replace='x'>dummy</span></p>", [ + ('setPosition', (1, 0)), + ('beginScope', {'tal:repeat': 'x python:(1,2,3)'}), + ('loop', ('x', '$python:(1,2,3)$', + [('startTag', ('p', + [('tal:repeat', 'x python:(1,2,3)', 'tal')])), + ('setPosition', (1, 33)), + ('beginScope', {'tal:replace': 'x'}), + ('insertText', ('$x$', + [('startTag', ('span', [('tal:replace', 'x', 'tal')])), + rawtext('dummy</span>')])), + ('endScope', ()), + rawtext('</p>')])), + ('endScope', ()), + ]) + + def test_attributes_1(self): + self._run_check("<a href='foo' name='bar' tal:attributes=" + "'href string:http://www.zope.org; x string:y'>" + "link</a>", [ + ('setPosition', (1, 0)), + ('beginScope', + {'tal:attributes': 'href string:http://www.zope.org; x string:y', + 'name': 'bar', 'href': 'foo'}), + ('startTag', ('a', + [('href', 'foo', 'replace', '$string:http://www.zope.org$', 0), + ('name', 'name="bar"'), + ('tal:attributes', + 'href string:http://www.zope.org; x string:y', 'tal'), + ('x', None, 'insert', '$string:y$', 0)])), + ('endScope', ()), + rawtext('link</a>'), + ]) + + def test_attributes_2(self): + self._run_check("<p tal:replace='structure string:<img>' " + "tal:attributes='src string:foo.png'>duh</p>", [ + ('setPosition', (1, 0)), + ('beginScope', + {'tal:attributes': 'src string:foo.png', + 'tal:replace': 'structure string:<img>'}), + ('insertStructure', + ('$string:<img>$', + {'src': ('$string:foo.png$', 0)}, + [('startTag', ('p', + [('tal:replace', 'structure string:<img>', 'tal'), + ('tal:attributes', 'src string:foo.png', + 'tal')])), + rawtext('duh</p>')])), + ('endScope', ()), + ]) + + def test_on_error_1(self): + self._run_check("<p tal:on-error='string:error' " + "tal:content='notHere'>okay</p>", [ + ('setPosition', (1, 0)), + ('beginScope', + {'tal:content': 'notHere', 'tal:on-error': 'string:error'}), + ('onError', + ([('startTag', ('p', + [('tal:on-error', 'string:error', 'tal'), + ('tal:content', 'notHere', 'tal')])), + ('insertText', ('$notHere$', [rawtext('okay')])), + rawtext('</p>')], + [('startTag', ('p', + [('tal:on-error', 'string:error', 'tal'), + ('tal:content', 'notHere', 'tal')])), + ('insertText', ('$string:error$', [])), + rawtext('</p>')])), + ('endScope', ()), + ]) + + def test_on_error_2(self): + self._run_check("<p tal:on-error='string:error' " + "tal:replace='notHere'>okay</p>", [ + ('setPosition', (1, 0)), + ('beginScope', + {'tal:replace': 'notHere', 'tal:on-error': 'string:error'}), + ('onError', + ([('insertText', ('$notHere$', + [('startTag', ('p', + [('tal:on-error', 'string:error', 'tal'), + ('tal:replace', 'notHere', 'tal')])), + rawtext('okay</p>')]))], + [('startTag', ('p', + [('tal:on-error', 'string:error', 'tal'), + ('tal:replace', 'notHere', 'tal')])), + ('insertText', ('$string:error$', [])), + rawtext('</p>')])), + ('endScope', ()), + ]) + + def test_dup_attr(self): + self._should_error("<img tal:condition='x' tal:condition='x'>") + self._should_error("<img metal:define-macro='x' " + "metal:define-macro='x'>", taldefs.METALError) + + def test_tal_errors(self): + self._should_error("<p tal:define='x' />") + self._should_error("<p tal:repeat='x' />") + self._should_error("<p tal:foobar='x' />") + self._should_error("<p tal:replace='x' tal:content='x' />") + self._should_error("<p tal:replace='x'>") + + def test_metal_errors(self): + exc = taldefs.METALError + self._should_error(2*"<p metal:define-macro='x'>xxx</p>", exc) + self._should_error("<html metal:use-macro='x'>" + + 2*"<p metal:fill-slot='y' />" + "</html>", exc) + self._should_error("<p metal:foobar='x' />", exc) + self._should_error("<p metal:define-macro='x'>", exc) + + # + # I18N test cases + # + + def test_i18n_attributes(self): + self._run_check("<img alt='foo' i18n:attributes='alt'>", [ + ('setPosition', (1, 0)), + ('beginScope', {'alt': 'foo', 'i18n:attributes': 'alt'}), + ('startTag', ('img', + [('alt', 'foo', 'replace', None, 1), + ('i18n:attributes', 'alt', 'i18n')])), + ('endScope', ()), + ]) + + def test_i18n_translate(self): + # input/test19.html + self._run_check('''\ +<span i18n:translate="">Replace this</span> +<span i18n:translate="msgid">This is a +translated string</span> +<span i18n:translate="">And another +translated string</span> +''', [ + ('setPosition', (1, 0)), + ('beginScope', {'i18n:translate': ''}), + ('startTag', ('span', [('i18n:translate', '', 'i18n')])), + ('insertTranslation', ('', [('rawtextOffset', ('Replace this', 12))])), + ('rawtextBeginScope', + ('</span>\n', 0, (2, 0), 1, {'i18n:translate': 'msgid'})), + ('startTag', ('span', [('i18n:translate', 'msgid', 'i18n')])), + ('insertTranslation', + ('msgid', [('rawtextColumn', ('This is a\ntranslated string', 17))])), + ('rawtextBeginScope', ('</span>\n', 0, (4, 0), 1, {'i18n:translate': ''})), + ('startTag', ('span', [('i18n:translate', '', 'i18n')])), + ('insertTranslation', + ('', [('rawtextColumn', ('And another\ntranslated string', 17))])), + ('endScope', ()), + ('rawtextColumn', ('</span>\n', 0))]) + + def test_i18n_translate_with_nested_tal(self): + self._run_check('''\ +<span i18n:translate="">replaceable <p tal:replace="str:here">content</p></span> +''', [ + ('setPosition', (1, 0)), + ('beginScope', {'i18n:translate': ''}), + ('startTag', ('span', [('i18n:translate', '', 'i18n')])), + ('insertTranslation', + ('', + [('rawtextOffset', ('replaceable ', 12)), + ('setPosition', (1, 36)), + ('beginScope', {'tal:replace': 'str:here'}), + ('insertText', + ('$str:here$', + [('startTag', ('p', [('tal:replace', 'str:here', 'tal')])), + ('rawtextOffset', ('content</p>', 11))])), + ('endScope', ())])), + ('endScope', ()), + ('rawtextColumn', ('</span>\n', 0)) + ]) + + def test_i18n_name(self): + # input/test21.html + self._run_check('''\ +<span i18n:translate=""> + <span tal:replace="str:Lomax" i18n:name="name" /> was born in + <span tal:replace="str:Antarctica" i18n:name="country" />. +</span> +''', [ + ('setPosition', (1, 0)), + ('beginScope', {'i18n:translate': ''}), + ('startTag', ('span', [('i18n:translate', '', 'i18n')])), + ('insertTranslation', + ('', + [('rawtextBeginScope', + ('\n ', + 2, + (2, 2), + 0, + {'i18n:name': 'name', 'tal:replace': 'str:Lomax'})), + ('i18nVariable', + ('name', + [('startEndTag', + ('span', + [('tal:replace', 'str:Lomax', 'tal'), + ('i18n:name', 'name', 'i18n')]))], + '$str:Lomax$')), + ('rawtextBeginScope', + (' was born in\n ', + 2, + (3, 2), + 1, + {'i18n:name': 'country', 'tal:replace': 'str:Antarctica'})), + ('i18nVariable', + ('country', + [('startEndTag', + ('span', + [('tal:replace', 'str:Antarctica', 'tal'), + ('i18n:name', 'country', 'i18n')]))], + '$str:Antarctica$')), + ('endScope', ()), + ('rawtextColumn', ('.\n', 0))])), + ('endScope', ()), + ('rawtextColumn', ('</span>\n', 0)) + ]) + + def test_i18n_name_implicit_value(self): + # input/test22.html + self._run_check('''\ +<span i18n:translate=""> + <span i18n:name="name"><b>Jim</b></span> was born in + <span i18n:name="country">the USA</span>. +</span> +''', [ + ('setPosition', (1, 0)), + ('beginScope', {'i18n:translate': ''}), + ('startTag', ('span', [('i18n:translate', '', 'i18n')])), + ('insertTranslation', + ('', + [('rawtextBeginScope', ('\n ', 2, (2, 2), 0, {'i18n:name': 'name'})), + ('i18nVariable', + ('name', + [('rawtextOffset', ('<b>Jim</b>', 10))], None)), + ('rawtextBeginScope', + (' was born in\n ', 2, (3, 2), 1, {'i18n:name': 'country'})), + ('i18nVariable', + ('country', + [('rawtextOffset', ('the USA', 7))], None)), + ('endScope', ()), + ('rawtextColumn', ('.\n', 0))])), + ('endScope', ()), + ('rawtextColumn', ('</span>\n', 0)) + ]) + + def test_i18n_context_domain(self): + self._run_check("<span i18n:domain='mydomain'/>", [ + ('setPosition', (1, 0)), + ('beginI18nContext', {'domain': 'mydomain', + 'source': None, 'target': None}), + ('beginScope', {'i18n:domain': 'mydomain'}), + ('startEndTag', ('span', [('i18n:domain', 'mydomain', 'i18n')])), + ('endScope', ()), + ('endI18nContext', ()), + ]) + + def test_i18n_context_source(self): + self._run_check("<span i18n:source='en'/>", [ + ('setPosition', (1, 0)), + ('beginI18nContext', {'source': 'en', + 'domain': 'default', 'target': None}), + ('beginScope', {'i18n:source': 'en'}), + ('startEndTag', ('span', [('i18n:source', 'en', 'i18n')])), + ('endScope', ()), + ('endI18nContext', ()), + ]) + + def test_i18n_context_source_target(self): + self._run_check("<span i18n:source='en' i18n:target='ru'/>", [ + ('setPosition', (1, 0)), + ('beginI18nContext', {'source': 'en', 'target': 'ru', + 'domain': 'default'}), + ('beginScope', {'i18n:source': 'en', 'i18n:target': 'ru'}), + ('startEndTag', ('span', [('i18n:source', 'en', 'i18n'), + ('i18n:target', 'ru', 'i18n')])), + ('endScope', ()), + ('endI18nContext', ()), + ]) + + def test_i18n_context_in_define_slot(self): + text = ("<div metal:use-macro='M' i18n:domain='mydomain'>" + "<div metal:fill-slot='S'>spam</div>" + "</div>") + self._run_check(text, [ + ('setPosition', (1, 0)), + ('useMacro', + ('M', '$M$', + {'S': [('startTag', ('div', + [('metal:fill-slot', 'S', 'metal')])), + rawtext('spam</div>')]}, + [('beginI18nContext', {'domain': 'mydomain', + 'source': None, 'target': None}), + ('beginScope', + {'i18n:domain': 'mydomain', 'metal:use-macro': 'M'}), + ('startTag', ('div', [('metal:use-macro', 'M', 'metal'), + ('i18n:domain', 'mydomain', 'i18n')])), + ('setPosition', (1, 48)), + ('fillSlot', ('S', + [('startTag', + ('div', [('metal:fill-slot', 'S', 'metal')])), + rawtext('spam</div>')])), + ('endScope', ()), + rawtext('</div>'), + ('endI18nContext', ())])), + ]) + + def test_i18n_data(self): + # input/test23.html + self._run_check('''\ +<span i18n:data="here/currentTime" + i18n:translate="timefmt">2:32 pm</span> +''', [ + ('setPosition', (1, 0)), + ('beginScope', + {'i18n:translate': 'timefmt', 'i18n:data': 'here/currentTime'}), + ('startTag', + ('span', + [('i18n:data', 'here/currentTime', 'i18n'), + ('i18n:translate', 'timefmt', 'i18n')])), + ('insertTranslation', + ('timefmt', [('rawtextOffset', ('2:32 pm', 7))], '$here/currentTime$')), + ('endScope', ()), + ('rawtextColumn', ('</span>\n', 0)) + ]) + + def test_i18n_data_with_name(self): + # input/test29.html + self._run_check('''\ +At the tone the time will be +<span i18n:data="here/currentTime" + i18n:translate="timefmt" + i18n:name="time">2:32 pm</span>... beep! +''', [ + ('rawtextBeginScope', + ('At the tone the time will be\n', + 0, + (2, 0), + 0, + {'i18n:data': 'here/currentTime', + 'i18n:name': 'time', + 'i18n:translate': 'timefmt'})), + ('insertTranslation', + ('timefmt', + [('startTag', + ('span', + [('i18n:data', 'here/currentTime', 'i18n'), + ('i18n:translate', 'timefmt', 'i18n'), + ('i18n:name', 'time', 'i18n')])), + ('i18nVariable', ('time', [], None))], + '$here/currentTime$')), + ('endScope', ()), + ('rawtextColumn', ('... beep!\n', 0)) + ]) + + def test_i18n_explicit_msgid_with_name(self): + # input/test26.html + self._run_check('''\ +<span i18n:translate="jobnum"> + Job #<span tal:replace="context/@@object_name" + i18n:name="jobnum">NN</span></span> +''', [ + ('setPosition', (1, 0)), + ('beginScope', {'i18n:translate': 'jobnum'}), + ('startTag', ('span', [('i18n:translate', 'jobnum', 'i18n')])), + ('insertTranslation', + ('jobnum', + [('rawtextBeginScope', + ('\n Job #', + 9, + (2, 9), + 0, + {'i18n:name': 'jobnum', 'tal:replace': 'context/@@object_name'})), + ('i18nVariable', + ('jobnum', + [('startTag', + ('span', + [('tal:replace', 'context/@@object_name', 'tal'), + ('i18n:name', 'jobnum', 'i18n')])), + ('rawtextOffset', ('NN', 2)), + ('rawtextOffset', ('</span>', 7))], + '$context/@@object_name$')), + ('endScope', ())])), + ('endScope', ()), + ('rawtextColumn', ('</span>\n', 0)) + ]) + + def test_i18n_name_around_tal_content(self): + # input/test28.html + self._run_check('''\ +<p i18n:translate="verify">Your contact email address is recorded as + <span i18n:name="email"> + <a href="mailto:user@example.com" + tal:content="request/submitter">user@host.com</a></span> +</p> +''', [ + ('setPosition', (1, 0)), + ('beginScope', {'i18n:translate': 'verify'}), + ('startTag', ('p', [('i18n:translate', 'verify', 'i18n')])), + ('insertTranslation', + ('verify', + [('rawtextBeginScope', + ('Your contact email address is recorded as\n ', + 4, + (2, 4), + 0, + {'i18n:name': 'email'})), + ('i18nVariable', + ('email', + [('rawtextBeginScope', + ('\n ', + 4, + (3, 4), + 0, + {'href': 'mailto:user@example.com', + 'tal:content': 'request/submitter'})), + ('startTag', + ('a', + [('href', 'href="mailto:user@example.com"'), + ('tal:content', 'request/submitter', 'tal')])), + ('insertText', + ('$request/submitter$', + [('rawtextOffset', ('user@host.com', 13))])), + ('endScope', ()), + ('rawtextOffset', ('</a>', 4))], + None)), + ('endScope', ()), + ('rawtextColumn', ('\n', 0))])), + ('endScope', ()), + ('rawtextColumn', ('</p>\n', 0)) + ]) + + def test_i18n_name_with_tal_content(self): + # input/test27.html + self._run_check('''\ +<p i18n:translate="verify">Your contact email address is recorded as + <a href="mailto:user@example.com" + tal:content="request/submitter" + i18n:name="email">user@host.com</a> +</p> +''', [ + ('setPosition', (1, 0)), + ('beginScope', {'i18n:translate': 'verify'}), + ('startTag', ('p', [('i18n:translate', 'verify', 'i18n')])), + ('insertTranslation', + ('verify', + [('rawtextBeginScope', + ('Your contact email address is recorded as\n ', + 4, + (2, 4), + 0, + {'href': 'mailto:user@example.com', + 'i18n:name': 'email', + 'tal:content': 'request/submitter'})), + ('i18nVariable', + ('email', + [('startTag', + ('a', + [('href', 'href="mailto:user@example.com"'), + ('tal:content', 'request/submitter', 'tal'), + ('i18n:name', 'email', 'i18n')])), + ('insertText', + ('$request/submitter$', + [('rawtextOffset', ('user@host.com', 13))])), + ('rawtextOffset', ('</a>', 4))], + None)), + ('endScope', ()), + ('rawtextColumn', ('\n', 0))])), + ('endScope', ()), + ('rawtextColumn', ('</p>\n', 0)) + ]) + + +def test_suite(): + suite = unittest.makeSuite(HTMLTALParserTestCases) + suite.addTest(unittest.makeSuite(METALGeneratorTestCases)) + suite.addTest(unittest.makeSuite(TALGeneratorTestCases)) + return suite + + +if __name__ == "__main__": + errs = utils.run_suite(test_suite()) + sys.exit(errs and 1 or 0) diff --git a/tests/test_sourcepos.py b/tests/test_sourcepos.py new file mode 100644 index 0000000..8d11f27 --- /dev/null +++ b/tests/test_sourcepos.py @@ -0,0 +1,93 @@ +#! /usr/bin/env python +############################################################################## +# +# Copyright (c) 2001, 2002 Zope Corporation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +"""Tests for TALInterpreter.""" + +import sys +import unittest + +from StringIO import StringIO + +from zope.tal.htmltalparser import HTMLTALParser +from zope.tal.talinterpreter import TALInterpreter +from zope.tal.talgenerator import TALGenerator +from zope.tal.dummyengine import DummyEngine + + +page1 = '''<html metal:use-macro="main"><body> +<div metal:fill-slot="body"> +page1=<span tal:replace="position:" /> +</div> +</body></html>''' + +main_template = '''<html metal:define-macro="main"><body> +main_template=<span tal:replace="position:" /> +<div metal:define-slot="body" /> +main_template=<span tal:replace="position:" /> +<div metal:use-macro="foot" /> +main_template=<span tal:replace="position:" /> +</body></html>''' + +footer = '''<div metal:define-macro="foot"> +footer=<span tal:replace="position:" /> +</div>''' + +expected = '''<html><body> +main_template=main_template (2,14) +<div> +page1=page1 (3,6) +</div> +main_template=main_template (4,14) +<div> +footer=footer (2,7) +</div> +main_template=main_template (6,14) +</body></html>''' + + + +class SourcePosTestCase(unittest.TestCase): + + def parse(self, eng, s, fn): + gen = TALGenerator(expressionCompiler=eng, xml=0, source_file=fn) + parser = HTMLTALParser(gen) + parser.parseString(s) + program, macros = parser.getCode() + return program, macros + + def test_source_positions(self): + # Ensure source file and position are set correctly by TAL + macros = {} + eng = DummyEngine(macros) + page1_program, page1_macros = self.parse(eng, page1, 'page1') + main_template_program, main_template_macros = self.parse( + eng, main_template, 'main_template') + footer_program, footer_macros = self.parse(eng, footer, 'footer') + + macros['main'] = main_template_macros['main'] + macros['foot'] = footer_macros['foot'] + + stream = StringIO() + interp = TALInterpreter(page1_program, macros, eng, stream) + interp() + self.assertEqual(stream.getvalue().strip(), expected.strip(), + "Got result:\n%s\nExpected:\n%s" + % (stream.getvalue(), expected)) + + +def test_suite(): + return unittest.makeSuite(SourcePosTestCase) + +if __name__ == "__main__": + unittest.main(defaultTest='test_suite') diff --git a/tests/test_talinterpreter.py b/tests/test_talinterpreter.py new file mode 100644 index 0000000..81d10bf --- /dev/null +++ b/tests/test_talinterpreter.py @@ -0,0 +1,125 @@ +#! /usr/bin/env python +############################################################################## +# +# Copyright (c) 2001, 2002 Zope Corporation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +"""Tests for TALInterpreter.""" + +import sys +import unittest + +from StringIO import StringIO + +from zope.tal.taldefs import METALError, I18NError +from zope.tal.htmltalparser import HTMLTALParser +from zope.tal.talinterpreter import TALInterpreter +from zope.tal.dummyengine import DummyEngine +from zope.tal.tests import utils + + +class TestCaseBase(unittest.TestCase): + + def _compile(self, source): + parser = HTMLTALParser() + parser.parseString(source) + program, macros = parser.getCode() + return program, macros + + +class MacroErrorsTestCase(TestCaseBase): + + def setUp(self): + dummy, macros = self._compile('<p metal:define-macro="M">Booh</p>') + self.macro = macros['M'] + self.engine = DummyEngine(macros) + program, dummy = self._compile('<p metal:use-macro="M">Bah</p>') + self.interpreter = TALInterpreter(program, {}, self.engine) + + def tearDown(self): + try: + self.interpreter() + except METALError: + pass + else: + self.fail("Expected METALError") + + def test_mode_error(self): + self.macro[1] = ("mode", "duh") + + def test_version_error(self): + self.macro[0] = ("version", "duh") + + +class I18NErrorsTestCase(TestCaseBase): + + def _check(self, src, msg): + try: + self._compile(src) + except I18NError: + pass + else: + self.fail(msg) + + def test_id_with_replace(self): + self._check('<p i18n:id="foo" tal:replace="string:splat"></p>', + "expected i18n:id with tal:replace to be denied") + + def test_missing_values(self): + self._check('<p i18n:attributes=""></p>', + "missing i18n:attributes value not caught") + self._check('<p i18n:data=""></p>', + "missing i18n:data value not caught") + self._check('<p i18n:id=""></p>', + "missing i18n:id value not caught") + + +class OutputPresentationTestCase(TestCaseBase): + + def test_attribute_wrapping(self): + # To make sure the attribute-wrapping code is invoked, we have to + # include at least one TAL/METAL attribute to avoid having the start + # tag optimized into a rawtext instruction. + INPUT = r""" + <html this='element' has='a' lot='of' attributes=', so' the='output' + needs='to' be='line' wrapped='.' tal:define='foo nothing'> + </html>""" + EXPECTED = r''' + <html this="element" has="a" lot="of" + attributes=", so" the="output" needs="to" + be="line" wrapped="."> + </html>''' "\n" + self.compare(INPUT, EXPECTED) + + def test_entities(self): + INPUT = ('<img tal:define="foo nothing" ' + 'alt="&a;  
 &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) + + +def test_suite(): + suite = unittest.makeSuite(I18NErrorsTestCase) + suite.addTest(unittest.makeSuite(MacroErrorsTestCase)) + suite.addTest(unittest.makeSuite(OutputPresentationTestCase)) + return suite + +if __name__ == "__main__": + errs = utils.run_suite(test_suite()) + sys.exit(errs and 1 or 0) diff --git a/tests/test_xmlparser.py b/tests/test_xmlparser.py new file mode 100644 index 0000000..36eebfa --- /dev/null +++ b/tests/test_xmlparser.py @@ -0,0 +1,261 @@ +#! /usr/bin/env python +############################################################################## +# +# Copyright (c) 2001, 2002 Zope Corporation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +"""Tests for XMLParser.py.""" + +import sys +import unittest + +from zope.tal import xmlparser +from zope.tal.tests import utils + + +class EventCollector(xmlparser.XMLParser): + + def __init__(self): + self.events = [] + self.append = self.events.append + xmlparser.XMLParser.__init__(self) + self.parser.ordered_attributes = 1 + + def get_events(self): + # Normalize the list of events so that buffer artefacts don't + # separate runs of contiguous characters. + L = [] + prevtype = None + for event in self.events: + type = event[0] + if type == prevtype == "data": + L[-1] = ("data", L[-1][1] + event[1]) + else: + L.append(event) + prevtype = type + self.events = L + return L + + # structure markup + + def StartElementHandler(self, tag, attrs): + self.append(("starttag", tag, attrs)) + + def EndElementHandler(self, tag): + self.append(("endtag", tag)) + + # all other markup + + def CommentHandler(self, data): + self.append(("comment", data)) + + def handle_charref(self, data): + self.append(("charref", data)) + + def CharacterDataHandler(self, data): + self.append(("data", data)) + + def StartDoctypeDeclHandler(self, rootelem, publicId, systemId, subset): + self.append(("doctype", rootelem, systemId, publicId, subset)) + + def XmlDeclHandler(self, version, encoding, standalone): + self.append(("decl", version, encoding, standalone)) + + def ExternalEntityRefHandler(self, data): + self.append(("entityref", data)) + + def ProcessingInstructionHandler(self, target, data): + self.append(("pi", target, data)) + + +class EventCollectorExtra(EventCollector): + + def handle_starttag(self, tag, attrs): + EventCollector.handle_starttag(self, tag, attrs) + self.append(("starttag_text", self.get_starttag_text())) + + +class SegmentedFile: + def __init__(self, parts): + self.parts = list(parts) + + def read(self, bytes): + if self.parts: + s = self.parts.pop(0) + else: + s = '' + return s + + +class XMLParserTestCase(unittest.TestCase): + + def _run_check(self, source, events, collector=EventCollector): + parser = collector() + if isinstance(source, list): + parser.parseStream(SegmentedFile(source)) + else: + parser.parseString(source) + self.assertEquals(parser.get_events(),events) + + def _run_check_extra(self, source, events): + self._run_check(source, events, EventCollectorExtra) + + def _parse_error(self, source): + def parse(source=source): + parser = xmlparser.XMLParser() + parser.parseString(source) + self.assertRaises(xmlparser.XMLParseError, parse) + + def test_processing_instruction_plus(self): + self._run_check("<?processing instruction?><a/>", [ + ("pi", "processing", "instruction"), + ("starttag", "a", []), + ("endtag", "a"), + ]) + + def _check_simple_html(self): + self._run_check("""\ +<?xml version='1.0' encoding='iso-8859-1'?> +<!DOCTYPE html PUBLIC 'foo' 'bar'> +<html>&entity;  +<!--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' c:d='v' e-f='v'/>""", [ + ("starttag", "a", ["a.b", "v", "c:d", "v", "e-f", "v"]), + ("endtag", "a"), + ]) + + def test_starttag_end_boundary(self): + self._run_check("""<a b='<'/>""", [ + ("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 $ >") + + +# Support for the Zope regression test framework: +def test_suite(skipxml=utils.skipxml): + if skipxml: + return unittest.TestSuite() + else: + return unittest.makeSuite(XMLParserTestCase) + +if __name__ == "__main__": + errs = utils.run_suite(test_suite(skipxml=0)) + sys.exit(errs and 1 or 0) diff --git a/tests/utils.py b/tests/utils.py new file mode 100644 index 0000000..81d102f --- /dev/null +++ b/tests/utils.py @@ -0,0 +1,63 @@ +############################################################################## +# +# Copyright (c) 2001, 2002 Zope Corporation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +"""Helper functions for the test suite.""" + +import os +import sys + +mydir = os.path.abspath(os.path.dirname(__file__)) +codedir = os.path.dirname(os.path.dirname(os.path.dirname(mydir))) + +if codedir not in sys.path: + sys.path.append(codedir) + +import unittest + + +# Set skipxml to true if an XML parser could not be found. +skipxml = 0 +try: + import xml.parsers.expat +except ImportError: + skipxml = 1 + + +def run_suite(suite, outf=None, errf=None): + if outf is None: + outf = sys.stdout + runner = unittest.TextTestRunner(outf) + result = runner.run(suite) + +## print "\n\n" +## if result.errors: +## print "Errors (unexpected exceptions):" +## map(print_error, result.errors) +## print +## if result.failures: +## print "Failures (assertion failures):" +## map(print_error, result.failures) +## print + newerrs = len(result.errors) + len(result.failures) + if newerrs: + print "'Errors' indicate exceptions other than AssertionError." + print "'Failures' indicate AssertionError" + if errf is None: + errf = sys.stderr + errf.write("%d errors, %d failures\n" + % (len(result.errors), len(result.failures))) + return newerrs + + +def print_error(info): + testcase, (type, e, tb) = info diff --git a/timer.py b/timer.py new file mode 100644 index 0000000..c8f4310 --- /dev/null +++ b/timer.py @@ -0,0 +1,59 @@ +#! /usr/bin/env python +############################################################################## +# +# Copyright (c) 2001, 2002 Zope Corporation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +""" +Helper program to time compilation and interpretation +""" + +import getopt +import sys +import time + +from cPickle import dumps, loads +from cStringIO import StringIO + +from zope.tal.driver import FILE, compilefile, interpretit + + +def main(): + count = 10 + try: + opts, args = getopt.getopt(sys.argv[1:], "n:") + except getopt.error, msg: + print msg + sys.exit(2) + for o, a in opts: + if o == "-n": + count = int(a) + if not args: + args = [FILE] + for file in args: + print file + dummyfile = StringIO() + it = timefunc(count, compilefile, file) + timefunc(count, interpretit, it, None, dummyfile) + +def timefunc(count, func, *args): + sys.stderr.write("%-14s: " % func.__name__) + sys.stderr.flush() + t0 = time.clock() + for i in range(count): + result = apply(func, args) + t1 = time.clock() + sys.stderr.write("%6.3f secs for %d calls, i.e. %4.0f msecs per call\n" + % ((t1-t0), count, 1000*(t1-t0)/count)) + return result + +if __name__ == "__main__": + main() diff --git a/translationcontext.py b/translationcontext.py new file mode 100644 index 0000000..568ecad --- /dev/null +++ b/translationcontext.py @@ -0,0 +1,41 @@ +############################################################################## +# +# Copyright (c) 2001, 2002 Zope Corporation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +"""Translation context object for the TALInterpreter's I18N support. + +The translation context provides a container for the information +needed to perform translation of a marked string from a page template. + +$Id: translationcontext.py,v 1.2 2002/12/25 14:15:29 jim Exp $ +""" + +DEFAULT_DOMAIN = "default" + +class TranslationContext: + """Information about the I18N settings of a TAL processor.""" + + def __init__(self, parent=None, domain=None, target=None, source=None): + if parent: + if not domain: + domain = parent.domain + if not target: + target = parent.target + if not source: + source = parent.source + elif domain is None: + domain = DEFAULT_DOMAIN + + self.parent = parent + self.domain = domain + self.target = target + self.source = source diff --git a/xmlparser.py b/xmlparser.py new file mode 100644 index 0000000..03b383d --- /dev/null +++ b/xmlparser.py @@ -0,0 +1,85 @@ +############################################################################## +# +# Copyright (c) 2001, 2002 Zope Corporation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +""" +Generic expat-based XML parser base class. +""" + +import logging + + +class XMLParser: + + ordered_attributes = 0 + + handler_names = [ + "StartElementHandler", + "EndElementHandler", + "ProcessingInstructionHandler", + "CharacterDataHandler", + "UnparsedEntityDeclHandler", + "NotationDeclHandler", + "StartNamespaceDeclHandler", + "EndNamespaceDeclHandler", + "CommentHandler", + "StartCdataSectionHandler", + "EndCdataSectionHandler", + "DefaultHandler", + "DefaultHandlerExpand", + "NotStandaloneHandler", + "ExternalEntityRefHandler", + "XmlDeclHandler", + "StartDoctypeDeclHandler", + "EndDoctypeDeclHandler", + "ElementDeclHandler", + "AttlistDeclHandler" + ] + + def __init__(self, encoding=None): + self.parser = p = self.createParser() + if self.ordered_attributes: + try: + self.parser.ordered_attributes = self.ordered_attributes + except AttributeError: + logging.warn("TAL.XMLParser: Can't set ordered_attributes") + self.ordered_attributes = 0 + for name in self.handler_names: + method = getattr(self, name, None) + if method is not None: + try: + setattr(p, name, method) + except AttributeError: + logging.error("TAL.XMLParser: Can't set " + "expat handler %s" % name) + + def createParser(self, encoding=None): + global XMLParseError + from xml.parsers import expat + XMLParseError = expat.ExpatError + return expat.ParserCreate(encoding, ' ') + + def parseFile(self, filename): + self.parseStream(open(filename)) + + def parseString(self, s): + self.parser.Parse(s, 1) + + def parseURL(self, url): + import urllib + self.parseStream(urllib.urlopen(url)) + + def parseStream(self, stream): + self.parser.ParseFile(stream) + + def parseFragment(self, s, end=0): + self.parser.Parse(s, end) |