# -*- coding: ISO-8859-1 -*-
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Tests for TALInterpreter.

$Id: test_talinterpreter.py 39869 2005-11-03 14:23:04Z hdima $
"""
import os
import sys
import unittest

from StringIO import StringIO

from zope.tal.taldefs import METALError, I18NError, TAL_VERSION
from zope.tal.htmltalparser import HTMLTALParser
from zope.tal.talparser import TALParser
from zope.tal.talinterpreter import TALInterpreter
from zope.tal.dummyengine import DummyEngine
from zope.tal.dummyengine import MultipleDomainsDummyEngine
from zope.tal.dummyengine import DummyTranslationDomain
from zope.tal.tests import utils

# BBB 2005/10/10 -- MessageIDs are to be removed for Zope 3.3
import zope.deprecation
zope.deprecation.__show__.off()
from zope.i18nmessageid import MessageID, Message
zope.deprecation.__show__.on()

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 MacroFunkyErrorTest(TestCaseBase):

    def test_div_in_p_using_macro(self):
        dummy, macros = self._compile('<p metal:define-macro="M">Booh</p>')
        engine = DummyEngine(macros)
        program, dummy = self._compile(
            '<p metal:use-macro="M"><div>foo</div></p>')
        interpreter = TALInterpreter(program, {}, engine)

        output = interpreter()
        self.assertEqual(output, '<p><div>foo</div></p>')


class MacroExtendTestCase(TestCaseBase):

    def setUp(self):
        s = self._read(('input', 'pnome_template.pt'))
        self.pnome_program, pnome_macros = self._compile(s)
        s = self._read(('input', 'acme_template.pt'))
        self.acme_program, acme_macros = self._compile(s)
        s = self._read(('input', 'document_list.pt'))
        self.doclist_program, doclist_macros = self._compile(s)
        macros = {
            'pnome_macros_page': pnome_macros['page'],
            'acme_macros_page': acme_macros['page'],
            }
        self.engine = DummyEngine(macros)

    def _read(self, path):
        dir = os.path.dirname(__file__)
        fn = os.path.join(dir, *path)
        f = open(fn)
        data = f.read()
        f.close()
        return data

    def test_preview_acme_template(self):
        # An ACME designer is previewing the ACME design.  For the
        # purposes of this use case, extending a macro should act the
        # same as using a macro.
        result = StringIO()
        interpreter = TALInterpreter(
            self.acme_program, {}, self.engine, stream=result)
        interpreter()
        actual = result.getvalue().strip()
        expected = self._read(('output', 'acme_template.html')).strip()
        self.assertEqual(actual, expected)

    def test_preview_acme_template_source(self):
        # Render METAL attributes in acme_template
        result = StringIO()
        interpreter = TALInterpreter(
            self.acme_program, {}, self.engine, stream=result, tal=False)
        interpreter()
        actual = result.getvalue().strip()
        expected = self._read(('output', 'acme_template_source.html')).strip()
        self.assertEqual(actual, expected)


class I18NCornerTestCaseBase(TestCaseBase):

    def factory(self, msgid, default, mapping={}):
        raise NotImplementedError("abstract method")

    def setUp(self):
        self.engine = DummyEngine()
        # Make sure we'll translate the msgid not its unicode representation
        self.engine.setLocal('foo',
            self.factory('FoOvAlUe${empty}', 'default', {'empty': ''}))
        self.engine.setLocal('bar', 'BaRvAlUe')

    def _check(self, program, expected):
        result = StringIO()
        self.interpreter = TALInterpreter(program, {}, self.engine,
                                          stream=result)
        self.interpreter()
        self.assertEqual(expected, result.getvalue())

    def test_simple_messageid_translate(self):
        # This test is mainly here to make sure our DummyEngine works
        # correctly.
        program, macros = self._compile(
            '<span i18n:translate="" tal:content="foo"/>')
        self._check(program, '<span>FOOVALUE</span>\n')

        program, macros = self._compile(
            '<span i18n:translate="" tal:replace="foo"/>')
        self._check(program, 'FOOVALUE\n')

    def test_text_variable_translate(self):
        program, macros = self._compile(
            '<span tal:content="bar"/>')
        self._check(program, '<span>BaRvAlUe</span>\n')

        program, macros = self._compile(
            '<span i18n:translate="" tal:content="bar"/>')
        self._check(program, '<span>BARVALUE</span>\n')

        program, macros = self._compile(
            '<span i18n:translate="" tal:replace="bar"/>')
        self._check(program, 'BARVALUE\n')

    def test_text_translate(self):
        program, macros = self._compile(
            '<span tal:content="string:BaR"/>')
        self._check(program, '<span>BaR</span>\n')

        program, macros = self._compile(
            '<span i18n:translate="" tal:content="string:BaR"/>')
        self._check(program, '<span>BAR</span>\n')

        program, macros = self._compile(
            '<span i18n:translate="" tal:replace="string:BaR"/>')
        self._check(program, 'BAR\n')

    def test_structure_text_variable_translate(self):
        program, macros = self._compile(
            '<span tal:content="structure bar"/>')
        self._check(program, '<span>BaRvAlUe</span>\n')

        program, macros = self._compile(
            '<span i18n:translate="" tal:content="structure bar"/>')
        self._check(program, '<span>BARVALUE</span>\n')

        program, macros = self._compile(
            '<span i18n:translate="" tal:replace="structure bar"/>')
        self._check(program, 'BARVALUE\n')

    def test_structure_text_translate(self):
        program, macros = self._compile(
            '<span tal:content="structure string:BaR"/>')
        self._check(program, '<span>BaR</span>\n')

        program, macros = self._compile(
            '<span i18n:translate="" tal:content="structure string:BaR"/>')
        self._check(program, '<span>BAR</span>\n')

        program, macros = self._compile(
            '<span i18n:translate="" tal:replace="structure string:BaR"/>')
        self._check(program, 'BAR\n')

    def test_replace_with_messageid_and_i18nname(self):
        program, macros = self._compile(
            '<div i18n:translate="" >'
            '<span i18n:translate="" tal:replace="foo" i18n:name="foo_name"/>'
            '</div>')
        self._check(program, '<div>FOOVALUE</div>\n')

    def test_pythonexpr_replace_with_messageid_and_i18nname(self):
        program, macros = self._compile(
            '<div i18n:translate="" >'
            '<span i18n:translate="" tal:replace="python: foo"'
            '    i18n:name="foo_name"/>'
            '</div>')
        self._check(program, '<div>FOOVALUE</div>\n')

    def test_structure_replace_with_messageid_and_i18nname(self):
        program, macros = self._compile(
            '<div i18n:translate="" >'
            '<span i18n:translate="" tal:replace="structure foo"'
            '    i18n:name="foo_name"/>'
            '</div>')
        self._check(program, '<div>FOOVALUE</div>\n')

    def test_complex_replace_with_messageid_and_i18nname(self):
        program, macros = self._compile(
            '<div i18n:translate="" >'
            '<em tal:omit-tag="" i18n:name="foo_name">'
            '<span i18n:translate="" tal:replace="foo"/>'
            '</em>'
            '</div>')
        self._check(program, '<div>FOOVALUE</div>\n')

    def test_content_with_messageid_and_i18nname(self):
        program, macros = self._compile(
            '<div i18n:translate="" >'
            '<span i18n:translate="" tal:content="foo" i18n:name="foo_name"/>'
            '</div>')
        self._check(program, '<div><span>FOOVALUE</span></div>\n')

    def test_content_with_messageid_and_i18nname_and_i18ntranslate(self):
        # Let's tell the user this is incredibly silly!
        self.assertRaises(
            I18NError, self._compile,
            '<span i18n:translate="" tal:content="foo" i18n:name="foo_name"/>')

    def test_content_with_explicit_messageid(self):
        # Let's tell the user this is incredibly silly!
        self.assertRaises(
            I18NError, self._compile,
            '<span i18n:translate="ID" tal:content="foo" />')

    def test_content_with_plaintext_and_i18nname_and_i18ntranslate(self):
        # Let's tell the user this is incredibly silly!
        self.assertRaises(
            I18NError, self._compile,
            '<span i18n:translate="" i18n:name="color_name">green</span>')

    def test_translate_static_text_as_dynamic(self):
        program, macros = self._compile(
            '<div i18n:translate="">This is text for '
            '<span tal:content="bar" i18n:name="bar_name"/>.'
            '</div>')
        self._check(program,
                    '<div>THIS IS TEXT FOR <span>BaRvAlUe</span>.</div>\n')
        program, macros = self._compile(
            '<div i18n:translate="">This is text for '
            '<span i18n:translate="" tal:content="bar" i18n:name="bar_name"/>.'
            '</div>')
        self._check(program,
                    '<div>THIS IS TEXT FOR <span>BARVALUE</span>.</div>\n')

    def test_translate_static_text_as_dynamic_from_bytecode(self):
        program =  [('version', TAL_VERSION),
 ('mode', 'html'),
('setPosition', (1, 0)),
('beginScope', {'i18n:translate': ''}),
('startTag', ('div', [('i18n:translate', '', 'i18n')])),
('insertTranslation',
 ('',
  [('rawtextOffset', ('This is text for ', 17)),
   ('setPosition', (1, 40)),
   ('beginScope',
    {'tal:content': 'bar', 'i18n:name': 'bar_name', 'i18n:translate': ''}),
   ('i18nVariable',
       ('bar_name',
        [('startTag',
           ('span',
            [('i18n:translate', '', 'i18n'),
             ('tal:content', 'bar', 'tal'),
             ('i18n:name', 'bar_name', 'i18n')])),
         ('insertTranslation',
           ('',
             [('insertText', ('$bar$', []))])),
         ('rawtextOffset', ('</span>', 7))],
        None,
        0)),
   ('endScope', ()),
   ('rawtextOffset', ('.', 1))])),
('endScope', ()),
('rawtextOffset', ('</div>', 6)) 
]
        self._check(program,
                    '<div>THIS IS TEXT FOR <span>BARVALUE</span>.</div>\n')

    def test_for_correct_msgids(self):
        self.engine.translationDomain.clearMsgids()
        result = StringIO()
        #GChapelle: 
        #I have the feeling the i18n:translate with the i18n:name is wrong
        #
        #program, macros = self._compile(
        #    '<div i18n:translate="">This is text for '
        #    '<span i18n:translate="" tal:content="bar" '
        #    'i18n:name="bar_name"/>.</div>')
        program, macros = self._compile(
            '<div i18n:translate="">This is text for '
            '<span tal:content="bar" '
            'i18n:name="bar_name"/>.</div>')
        self.interpreter = TALInterpreter(program, {}, self.engine,
                                          stream=result)
        self.interpreter()
        msgids = self.engine.translationDomain.getMsgids('default')
        msgids.sort()
        self.assertEqual(1, len(msgids))
        self.assertEqual('This is text for ${bar_name}.', msgids[0][0])
        self.assertEqual({'bar_name': '<span>BaRvAlUe</span>'}, msgids[0][1])
        self.assertEqual(
            '<div>THIS IS TEXT FOR <span>BaRvAlUe</span>.</div>\n',
            result.getvalue())

    def test_for_correct_msgids_translate_name(self):
        self.engine.translationDomain.clearMsgids()
        result = StringIO()
        program, macros = self._compile(
            '<div i18n:translate="">This is text for '
            '<span i18n:translate="" tal:content="bar" '
            'i18n:name="bar_name"/>.</div>')
        self.interpreter = TALInterpreter(program, {}, self.engine,
                                          stream=result)
        self.interpreter()
        msgids = self.engine.translationDomain.getMsgids('default')
        msgids.sort()
        self.assertEqual(2, len(msgids))
        self.assertEqual('This is text for ${bar_name}.', msgids[1][0])
        self.assertEqual({'bar_name': '<span>BARVALUE</span>'}, msgids[1][1])
        self.assertEqual(
            '<div>THIS IS TEXT FOR <span>BARVALUE</span>.</div>\n',
            result.getvalue())

    def test_i18ntranslate_i18nname_and_attributes(self):
        # Test for Issue 301: Bug with i18n:name and i18n:translate
        # on the same element
        self.engine.translationDomain.clearMsgids()
        result = StringIO()
        program, macros = self._compile(
            '<p i18n:translate="">'
            'Some static text and a <a tal:attributes="href string:url"'
            ' i18n:name="link" i18n:translate="">link text</a>.</p>')
        self.interpreter = TALInterpreter(program, {}, self.engine,
                                          stream=result)
        self.interpreter()
        msgids = self.engine.translationDomain.getMsgids('default')
        msgids.sort()
        self.assertEqual(2, len(msgids))
        self.assertEqual('Some static text and a ${link}.', msgids[0][0])
        self.assertEqual({'link': '<a href="url">LINK TEXT</a>'}, msgids[0][1])
        self.assertEqual('link text', msgids[1][0])
        self.assertEqual(
            '<p>SOME STATIC TEXT AND A <a href="url">LINK TEXT</a>.</p>\n',
            result.getvalue())

    def test_for_raw_msgids(self):
        # Test for Issue 314: i18n:translate removes line breaks from
        # <pre>...</pre> contents
        # HTML mode
        self.engine.translationDomain.clearMsgids()
        result = StringIO()
        program, macros = self._compile(
            '<div i18n:translate=""> This is text\n'
            ' \tfor\n div. </div>'
            '<pre i18n:translate=""> This is text\n'
            ' <b>\tfor</b>\n pre. </pre>')
        self.interpreter = TALInterpreter(program, {}, self.engine,
                                          stream=result)
        self.interpreter()
        msgids = self.engine.translationDomain.getMsgids('default')
        msgids.sort()
        self.assertEqual(2, len(msgids))
        self.assertEqual(' This is text\n <b>\tfor</b>\n pre. ', msgids[0][0])
        self.assertEqual('This is text for div.', msgids[1][0])
        self.assertEqual(
            '<div>THIS IS TEXT FOR DIV.</div>'
            '<pre> THIS IS TEXT\n <B>\tFOR</B>\n PRE. </pre>\n',
            result.getvalue())

        # XML mode
        self.engine.translationDomain.clearMsgids()
        result = StringIO()
        parser = TALParser()
        parser.parseString(
            '<?xml version="1.0"?>\n'
            '<pre xmlns:i18n="http://xml.zope.org/namespaces/i18n"'
            ' i18n:translate=""> This is text\n'
            ' <b>\tfor</b>\n barvalue. </pre>')
        program, macros = parser.getCode()
        self.interpreter = TALInterpreter(program, {}, self.engine,
                                          stream=result)
        self.interpreter()
        msgids = self.engine.translationDomain.getMsgids('default')
        msgids.sort()
        self.assertEqual(1, len(msgids))
        self.assertEqual('This is text <b> for</b> barvalue.', msgids[0][0])
        self.assertEqual(
            '<?xml version="1.0"?>\n'
            '<pre>THIS IS TEXT <B> FOR</B> BARVALUE.</pre>\n',
            result.getvalue())

    def test_raw_msgids_and_i18ntranslate_i18nname(self):
        self.engine.translationDomain.clearMsgids()
        result = StringIO()
        program, macros = self._compile(
            '<div i18n:translate=""> This is text\n \tfor\n'
            '<pre i18n:name="bar" i18n:translate=""> \tbar\n </pre>.</div>')
        self.interpreter = TALInterpreter(program, {}, self.engine,
                                          stream=result)
        self.interpreter()
        msgids = self.engine.translationDomain.getMsgids('default')
        msgids.sort()
        self.assertEqual(2, len(msgids))
        self.assertEqual(' \tbar\n ', msgids[0][0])
        self.assertEqual('This is text for ${bar}.', msgids[1][0])
        self.assertEqual({'bar': '<pre> \tBAR\n </pre>'}, msgids[1][1])
        self.assertEqual(
            u'<div>THIS IS TEXT FOR <pre> \tBAR\n </pre>.</div>\n',
            result.getvalue())

    def test_for_handling_unicode_vars(self):
        # Make sure that non-ASCII Unicode is substituted correctly.
        # http://collector.zope.org/Zope3-dev/264
        program, macros = self._compile(
            "<div i18n:translate='' tal:define='bar python:unichr(0xC0)'>"
            "Foo <span tal:replace='bar' i18n:name='bar' /></div>")
        self._check(program, u"<div>FOO \u00C0</div>\n")

    
class I18NCornerTestCaseMessageID(I18NCornerTestCaseBase):

    def factory(self, msgid, default=None, mapping={}, domain=None):
        m = MessageID(msgid, default=default)
        m.mapping = mapping
        return m

class UnusedExplicitDomainTestCase(I18NCornerTestCaseMessageID):
    
    def factory(self, msgid, default=None, mapping={}, domain=None):
        m = MessageID(msgid, default=default, domain=domain)
        m.mapping = mapping
        return m

    
    def setUp(self):
        # MultipleDomainsDummyEngine is a Engine
        # where default domain transforms to uppercase
        self.engine = MultipleDomainsDummyEngine()
        self.engine.setLocal('foo',
            self.factory('FoOvAlUe${empty}', 'default', {'empty': ''}))
        self.engine.setLocal('bar', 'BaRvAlUe')
        self.engine.setLocal('baz',
            self.factory('BaZvAlUe', 'default', {}))
        # Message ids with different domains
        self.engine.setLocal('toupper',
            self.factory('ToUpper', 'default', {}))
        self.engine.setLocal('tolower',
            self.factory('ToLower', 'default', {}, domain='lower'))

    def test_multiple_domains(self):
        program, macros = self._compile(
            '<div i18n:translate=""'
            '     tal:content="toupper" />')
        self._check(program, '<div>TOUPPER</div>\n')
        program, macros = self._compile(
            '<div i18n:translate=""'
            '     tal:content="tolower" />')
        self._check(program, '<div>tolower</div>\n')
        program, macros = self._compile(
            '<div i18n:translate=""'
            '     tal:content="string:ToUpper" />')
        self._check(program, '<div>TOUPPER</div>\n')
        program, macros = self._compile(
            '<div i18n:translate=""'
            '     i18n:domain="lower"'
            '     tal:content="string:ToLower" />')
        self._check(program, '<div>tolower</div>\n')
        program, macros = self._compile(
            '<div i18n:translate=""'
            '     tal:define="msgid string:ToUpper"'
            '     tal:content="msgid" />')
        self._check(program, '<div>TOUPPER</div>\n')
        program, macros = self._compile(
            '<div i18n:translate=""'
            '     i18n:domain="lower"'
            '     tal:define="msgid string:ToLower"'
            '     tal:content="msgid" />')
        self._check(program, '<div>tolower</div>\n')

    def test_unused_explicit_domain(self):
        #a_very_explicit_domain_setup_by_template_developer_that_wont_be_taken_into_account_by_the_ZPT_engine 
        #is a domain that transforms to lowercase
        self.engine.setLocal('othertolower',
            self.factory('OtherToLower', 'a_very_explicit_domain_setup_by_template_developer_that_wont_be_taken_into_account_by_the_ZPT_engine', {}, domain='lower'))
        program, macros = self._compile(
            '<div i18n:translate=""'
            '     tal:content="othertolower" />')
        self._check(program, '<div>othertolower</div>\n')
        #takes domain into account for strings
        program, macros = self._compile(
            '<div i18n:translate=""'
            '     i18n:domain="a_very_explicit_domain_setup_by_template_developer_that_wont_be_taken_into_account_by_the_ZPT_engine"'
            '     tal:content="string:ToLower" />')
        self._check(program, '<div>tolower</div>\n')
        #but not for messageids
        program, macros = self._compile(
            '<div i18n:translate=""'
            '     i18n:domain="a_very_explicit_domain_setup_by_template_developer_that_wont_be_taken_into_account_by_the_ZPT_engine"'
            '     tal:content="baz" />')
        self._check(program, '<div>BAZVALUE</div>\n')

class I18NCornerTestCaseMessage(I18NCornerTestCaseBase):

    def factory(self, msgid, default=None, mapping={}):
        return Message(msgid, default=default, mapping=mapping)

class ScriptTestCase(TestCaseBase):

    def setUp(self):
        self.engine = DummyEngine()

    def _check(self, program, expected):
        result = StringIO()
        self.interpreter = TALInterpreter(program, {}, self.engine,
                                          stream=result)
        self.interpreter()
        self.assertEqual(expected, result.getvalue())

    def test_simple(self):
        program, macros = self._compile(
            '<p tal:script="text/server-python">print "hello"</p>')
        self._check(program, '<p>hello\n</p>\n')

    def test_script_and_tal_block(self):
        program, macros = self._compile(
            '<tal:block script="text/server-python">\n'
            '  global x\n'
            '  x = 1\n'
            '</tal:block>\n'
            '<span tal:replace="x" />')
        self._check(program, '\n1\n')
        self.assertEqual(self.engine.codeGlobals['x'], 1)

    def test_script_and_tal_block_having_inside_print(self):
        program, macros = self._compile(
            '<tal:block script="text/server-python">\n'
            '  print "hello"'
            '</tal:block>')
        self._check(program, 'hello\n\n')

    def test_script_and_omittag(self):
        program, macros = self._compile(
            '<p tal:omit-tag="" tal:script="text/server-python">\n'
            '  print "hello"'
            '</p>')
        self._check(program, 'hello\n\n')

    def test_script_and_inside_tags(self):
        program, macros = self._compile(
            '<p tal:omit-tag="" tal:script="text/server-python">\n'
            '  print "<b>hello</b>"'
            '</p>')
        self._check(program, '<b>hello</b>\n\n')

    def test_script_and_inside_tags_with_tal(self):
        program, macros = self._compile(
            '<p tal:omit-tag="" tal:script="text/server-python"> <!--\n'
            '  print """<b tal:replace="string:foo">hello</b>"""\n'
            '--></p>')
        self._check(program, '<b tal:replace="string:foo">hello</b>\n\n')

    def test_html_script(self):
        program, macros = self._compile(
            '<script type="text/server-python">\n'
            '  print "Hello world!"\n'
            '</script>')
        self._check(program, 'Hello world!\n')

    def test_html_script_and_javascript(self):
        program, macros = self._compile(
            '<script type="text/javascript" src="somefile.js" />\n'
            '<script type="text/server-python">\n'
            '  print "Hello world!"\n'
            '</script>')
        self._check(program,
                    '<script type="text/javascript" src="somefile.js" />\n'
                    'Hello world!\n')


class I18NErrorsTestCase(TestCaseBase):

    def _check(self, src, msg):
        try:
            self._compile(src)
        except I18NError:
            pass
        else:
            self.fail(msg)

    def test_id_with_replace(self):
        self._check('<p i18n:id="foo" tal:replace="string:splat"></p>',
                    "expected i18n:id with tal:replace to be denied")

    def test_missing_values(self):
        self._check('<p i18n:attributes=""></p>',
                    "missing i18n:attributes value not caught")
        self._check('<p i18n:data=""></p>',
                    "missing i18n:data value not caught")
        self._check('<p i18n:id=""></p>',
                    "missing i18n:id value not caught")

    def test_id_with_attributes(self):
        self._check('''<input name="Delete"
                       tal:attributes="name string:delete_button"
                       i18n:attributes="name message-id">''',
            "expected attribute being both part of tal:attributes" +
            " and having a msgid in i18n:attributes to be denied")

class OutputPresentationTestCase(TestCaseBase):

    def test_attribute_wrapping(self):
        # To make sure the attribute-wrapping code is invoked, we have to
        # include at least one TAL/METAL attribute to avoid having the start
        # tag optimized into a rawtext instruction.
        INPUT = r"""
        <html this='element' has='a' lot='of' attributes=', so' the='output'
              needs='to' be='line' wrapped='.' tal:define='foo nothing'>
        </html>"""
        EXPECTED = r'''
        <html this="element" has="a" lot="of"
              attributes=", so" the="output" needs="to"
              be="line" wrapped=".">
        </html>''' "\n"
        self.compare(INPUT, EXPECTED)

    def test_unicode_content(self):
        INPUT = """<p tal:content="python:u'déjà-vu'">para</p>"""
        EXPECTED = u"""<p>déjà-vu</p>""" "\n"
        self.compare(INPUT, EXPECTED)

    def test_unicode_structure(self):
        INPUT = """<p tal:replace="structure python:u'déjà-vu'">para</p>"""
        EXPECTED = u"""déjà-vu""" "\n"
        self.compare(INPUT, EXPECTED)

    def test_i18n_replace_number(self):
        INPUT = """
        <p i18n:translate="foo ${bar}">
        <span tal:replace="python:123" i18n:name="bar">para</span>
        </p>"""
        EXPECTED = u"""
        <p>FOO 123</p>""" "\n"
        self.compare(INPUT, EXPECTED)

    def test_entities(self):
        INPUT = ('<img tal:define="foo nothing" '
                 'alt="&a; &#1; &#x0a; &a &#45 &; &#0a; <>" />')
        EXPECTED = ('<img alt="&a; &#1; &#x0a; '
                    '&amp;a &amp;#45 &amp;; &amp;#0a; &lt;&gt;" />\n')
        self.compare(INPUT, EXPECTED)

    def compare(self, INPUT, EXPECTED):
        program, macros = self._compile(INPUT)
        sio = StringIO()
        interp = TALInterpreter(program, {}, DummyEngine(), sio, wrap=60)
        interp()
        self.assertEqual(sio.getvalue(), EXPECTED)


class TestSourceAnnotations(unittest.TestCase):

    # there are additional test files in input/ and output/ subdirs
    # (test_sa*)

    def setUp(self):
        program = []
        macros = {}
        engine = DummyEngine()
        self.interpreter = TALInterpreter(program, macros, engine)
        self.sio = self.interpreter.stream = StringIO()
        self.interpreter._pending_source_annotation = True

    def testFormatSourceAnnotation(self):
        interpreter = self.interpreter
        interpreter.sourceFile = '/path/to/source.pt'
        interpreter.position = (123, 42)
        self.assertEquals(interpreter.formatSourceAnnotation(),
            "<!--\n" +
            "=" * 78 + "\n" +
            "/path/to/source.pt (line 123)\n" +
            "=" * 78 + "\n" +
            "-->")

    def testFormatSourceAnnotation_no_position(self):
        interpreter = self.interpreter
        interpreter.sourceFile = '/path/to/source.pt'
        interpreter.position = (None, None)
        self.assertEquals(interpreter.formatSourceAnnotation(),
            "<!--\n" +
            "=" * 78 + "\n" +
            "/path/to/source.pt\n" +
            "=" * 78 + "\n" +
            "-->")

    def test_annotated_stream_write(self):
        interpreter = self.interpreter
        interpreter.formatSourceAnnotation = lambda: '@'
        test_cases = [
            '@some text',
            '\n',
            '<?xml ...?>@some text',
            ' <?xml ...?>@some text',
            '\n<?xml ...?>@some text',
            '<?xml ...',
            '<?xml ...?>@\n<!DOCTYPE ...>some text',
        ]
        for output in test_cases:
            input = output.replace('@', '')
            self.sio.seek(0)
            self.sio.truncate()
            interpreter._pending_source_annotation = True
            interpreter._annotated_stream_write(input)
            self.assertEquals(self.sio.getvalue(), output)
            if '@' in output:
                self.assert_(not interpreter._pending_source_annotation)
            else:
                self.assert_(interpreter._pending_source_annotation)


def test_suite():
    suite = unittest.makeSuite(I18NErrorsTestCase)
    suite.addTest(unittest.makeSuite(MacroErrorsTestCase))
    suite.addTest(unittest.makeSuite(MacroExtendTestCase))
    suite.addTest(unittest.makeSuite(OutputPresentationTestCase))
    suite.addTest(unittest.makeSuite(ScriptTestCase))
    suite.addTest(unittest.makeSuite(I18NCornerTestCaseMessageID))
    suite.addTest(unittest.makeSuite(I18NCornerTestCaseMessage))
    suite.addTest(unittest.makeSuite(UnusedExplicitDomainTestCase))
    suite.addTest(unittest.makeSuite(TestSourceAnnotations))

    # TODO: Deactivated test, since we have not found a solution for this and
    # it is a deep and undocumented HTML parser issue.
    # Fred is looking into this.
    #suite.addTest(unittest.makeSuite(MacroFunkyErrorTest))

    return suite

if __name__ == "__main__":
    errs = utils.run_suite(test_suite())
    sys.exit(errs and 1 or 0)
