"""
Test cases related to XSLT processing
"""


import io
import copy
import gzip
import os.path
import unittest
import contextlib

from io import BytesIO
from textwrap import dedent
from tempfile import NamedTemporaryFile, mkdtemp

from .common_imports import (
    etree, HelperTestCase, fileInTestDir, make_doctest, SimpleFSPath
)


class ETreeXSLTTestCase(HelperTestCase):
    """XSLT tests etree"""
        
    def test_xslt(self):
        tree = self.parse('<a><b>B</b><c>C</c></a>')
        style = self.parse('''\
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:template match="*" />
  <xsl:template match="/">
    <foo><xsl:value-of select="/a/b/text()" /></foo>
  </xsl:template>
</xsl:stylesheet>''')

        st = etree.XSLT(style)
        res = st(tree)
        self.assertEqual('''\
<?xml version="1.0"?>
<foo>B</foo>
''',
                          str(res))

    def test_xslt_elementtree_error(self):
        self.assertRaises(ValueError, etree.XSLT, etree.ElementTree())

    def test_xslt_input_none(self):
        self.assertRaises(TypeError, etree.XSLT, None)

    def test_xslt_invalid_stylesheet(self):
        style = self.parse('''\
<xsl:stylesheet version="1.0"
        xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:stylesheet />
</xsl:stylesheet>''')

        self.assertRaises(
            etree.XSLTParseError, etree.XSLT, style)

    def test_xslt_copy(self):
        tree = self.parse('<a><b>B</b><c>C</c></a>')
        style = self.parse('''\
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:template match="*" />
  <xsl:template match="/">
    <foo><xsl:value-of select="/a/b/text()" /></foo>
  </xsl:template>
</xsl:stylesheet>''')

        transform = etree.XSLT(style)
        res = transform(tree)
        self.assertEqual('''\
<?xml version="1.0"?>
<foo>B</foo>
''',
                          str(res))

        transform_copy = copy.deepcopy(transform)
        res = transform_copy(tree)
        self.assertEqual('''\
<?xml version="1.0"?>
<foo>B</foo>
''',
                          str(res))

        transform = etree.XSLT(style)
        res = transform(tree)
        self.assertEqual('''\
<?xml version="1.0"?>
<foo>B</foo>
''',
                          str(res))

    @contextlib.contextmanager
    def _xslt_setup(
            self, encoding='UTF-16', expected_encoding=None,
            expected='<?xml version="1.0" encoding="%(ENCODING)s"?><foo>\uF8D2</foo>'):
        tree = self.parse('<a><b>\uF8D2</b><c>\uF8D2</c></a>')
        style = self.parse('''\
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output encoding="%(ENCODING)s"/>
  <xsl:template match="/">
    <foo><xsl:value-of select="/a/b/text()" /></foo>
  </xsl:template>
</xsl:stylesheet>''' % {'ENCODING': encoding})

        st = etree.XSLT(style)
        res = st(tree)
        expected = dedent(expected).strip().replace('\n', '') % {
            'ENCODING': expected_encoding or encoding,
        }

        data = [res]
        yield data
        self.assertEqual(expected, data[0].replace('\n', ''))

    def test_xslt_utf8(self):
        with self._xslt_setup(encoding='UTF-8') as res:
            res[0] = bytes(res[0]).decode('UTF-8')
            assert 'UTF-8' in res[0]

    def test_xslt_encoding(self):
        with self._xslt_setup() as res:
            res[0] = bytes(res[0]).decode('UTF-16')
            assert 'UTF-16' in res[0]

    def test_xslt_encoding_override(self):
        with self._xslt_setup(encoding='UTF-8', expected_encoding='UTF-16') as res:
            f = BytesIO()
            res[0].write(f, encoding='UTF-16')
            output = str(f.getvalue(), 'UTF-16')
            res[0] = output.replace("'", '"')

    def test_xslt_write_output_bytesio(self):
        with self._xslt_setup() as res:
            f = BytesIO()
            res[0].write_output(f)
            res[0] = f.getvalue().decode('UTF-16')

    def test_xslt_write_output_failure(self):
        class Writer:
            def write(self, data):
                raise ValueError("FAILED!")

        try:
            with self._xslt_setup() as res:
                res[0].write_output(Writer())
        except ValueError as exc:
            self.assertTrue("FAILED!" in str(exc), exc)
        else:
            self.assertTrue(False, "exception not raised")

    def test_xslt_write_output_file(self):
        with self._xslt_setup() as res:
            f = NamedTemporaryFile(delete=False)
            try:
                try:
                    res[0].write_output(f)
                finally:
                    f.close()
                with open(f.name, encoding='UTF-16') as f:
                    res[0] = f.read()
            finally:
                os.unlink(f.name)

    def test_xslt_write_output_file_path(self):
        with self._xslt_setup() as res:
            f = NamedTemporaryFile(delete=False)
            try:
                try:
                    res[0].write_output(f.name, compression=9)
                finally:
                    f.close()
                with gzip.GzipFile(f.name) as f:
                    res[0] = f.read().decode("UTF-16")
            finally:
                os.unlink(f.name)
    
    def test_xslt_write_output_file_pathlike(self):
        with self._xslt_setup() as res:
            f = NamedTemporaryFile(delete=False)
            try:
                try:
                    res[0].write_output(SimpleFSPath(f.name), compression=9)
                finally:
                    f.close()
                with gzip.GzipFile(f.name) as f:
                    res[0] = f.read().decode("UTF-16")
            finally:
                os.unlink(f.name)

    def test_xslt_write_output_file_path_urlescaped(self):
        # libxml2 should not unescape file paths.
        with self._xslt_setup() as res:
            f = NamedTemporaryFile(prefix='tmp%2e', suffix='.xml.gz', delete=False)
            try:
                try:
                    res[0].write_output(f.name, compression=3)
                finally:
                    f.close()
                with gzip.GzipFile(f.name) as f:
                    res[0] = f.read().decode("UTF-16")
            finally:
                os.unlink(f.name)

    def test_xslt_write_output_file_path_urlescaped_plus(self):
        with self._xslt_setup() as res:
            f = NamedTemporaryFile(prefix='p+%2e', suffix='.xml.gz', delete=False)
            try:
                try:
                    res[0].write_output(f.name, compression=1)
                finally:
                    f.close()
                with gzip.GzipFile(f.name) as f:
                    res[0] = f.read().decode("UTF-16")
            finally:
                os.unlink(f.name)

    def test_xslt_write_output_file_oserror(self):
        with self._xslt_setup(expected='') as res:
            tempdir = mkdtemp()
            try:
                res[0].write_output(os.path.join(tempdir, 'missing_subdir', 'out.xml'))
            except OSError:
                res[0] = ''
            else:
                self.fail("IOError not raised")
            finally:
                os.rmdir(tempdir)

    def test_xslt_unicode(self):
        expected = '''
            <?xml version="1.0"?>
            <foo>\uF8D2</foo>
        '''
        with self._xslt_setup(expected=expected) as res:
            res[0] = str(res[0])

    def test_xslt_unicode_standalone(self):
        tree = self.parse('<a><b>\uF8D2</b><c>\uF8D2</c></a>')
        style = self.parse('''\
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output encoding="UTF-16" standalone="no"/>
  <xsl:template match="/">
    <foo><xsl:value-of select="/a/b/text()" /></foo>
  </xsl:template>
</xsl:stylesheet>''')

        st = etree.XSLT(style)
        res = st(tree)
        expected = '''\
<?xml version="1.0" standalone="no"?>
<foo>\uF8D2</foo>
'''
        self.assertEqual(expected,
                         str(res))

    def test_xslt_input(self):
        style = self.parse('''\
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:template match="*" />
  <xsl:template match="/">
    <foo><xsl:value-of select="/a/b/text()" /></foo>
  </xsl:template>
</xsl:stylesheet>''')

        st = etree.XSLT(style)
        st = etree.XSLT(style.getroot())

    def test_xslt_input_partial_doc(self):
        style = self.parse('''\
<otherroot>
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:template match="*" />
  <xsl:template match="/">
    <foo><xsl:value-of select="/a/b/text()" /></foo>
  </xsl:template>
</xsl:stylesheet>
</otherroot>''')

        self.assertRaises(etree.XSLTParseError, etree.XSLT, style)
        root_node = style.getroot()
        self.assertRaises(etree.XSLTParseError, etree.XSLT, root_node)
        st = etree.XSLT(root_node[0])

    def test_xslt_broken(self):
        style = self.parse('''\
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:foo />
</xsl:stylesheet>''')
        self.assertRaises(etree.XSLTParseError,
                          etree.XSLT, style)

    def test_xslt_parsing_error_log(self):
        tree = self.parse('<a/>')
        style = self.parse('''\
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:foo />
</xsl:stylesheet>''')
        self.assertRaises(etree.XSLTParseError,
                          etree.XSLT, style)
        exc = None
        try:
            etree.XSLT(style)
        except etree.XSLTParseError as e:
            exc = e
        else:
            self.assertFalse(True, "XSLT processing should have failed but didn't")
        self.assertTrue(exc is not None)
        self.assertTrue(len(exc.error_log))
        for error in exc.error_log:
            self.assertTrue(':ERROR:XSLT:' in str(error))

    def test_xslt_apply_error_log(self):
        tree = self.parse('<a/>')
        style = self.parse('''\
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:template match="a">
        <xsl:copy>
            <xsl:message terminate="yes">FAIL</xsl:message>
        </xsl:copy>
    </xsl:template>
</xsl:stylesheet>''')
        self.assertRaises(etree.XSLTApplyError,
                          etree.XSLT(style), tree)

        transform = etree.XSLT(style)
        exc = None
        try:
            transform(tree)
        except etree.XSLTApplyError as e:
            exc = e
        else:
            self.assertFalse(True, "XSLT processing should have failed but didn't")

        self.assertTrue(exc is not None)
        self.assertTrue(len(exc.error_log))
        self.assertEqual(len(transform.error_log), len(exc.error_log))
        for error in exc.error_log:
            self.assertTrue(':ERROR:XSLT:' in str(error))
        for error in transform.error_log:
            self.assertTrue(':ERROR:XSLT:' in str(error))

    def test_xslt_parameters(self):
        tree = self.parse('<a><b>B</b><c>C</c></a>')
        style = self.parse('''\
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:template match="/">
    <foo><xsl:value-of select="$bar" /></foo>
  </xsl:template>
</xsl:stylesheet>''')

        st = etree.XSLT(style)
        res = st(tree, bar="'Bar'")
        self.assertEqual('''\
<?xml version="1.0"?>
<foo>Bar</foo>
''',
                          str(res))

    def test_xslt_string_parameters(self):
        tree = self.parse('<a><b>B</b><c>C</c></a>')
        style = self.parse('''\
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:template match="/">
    <foo><xsl:value-of select="$bar" /></foo>
  </xsl:template>
</xsl:stylesheet>''')

        st = etree.XSLT(style)
        res = st(tree, bar=etree.XSLT.strparam('''it's me, "Bar"'''))
        self.assertEqual('''\
<?xml version="1.0"?>
<foo>it's me, "Bar"</foo>
''',
                          str(res))

    def test_xslt_parameter_invalid(self):
        tree = self.parse('<a><b>B</b><c>C</c></a>')
        style = self.parse('''\
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:param name="bar"/>
  <xsl:template match="/">
    <foo><xsl:value-of select="$bar" /></foo>
  </xsl:template>
</xsl:stylesheet>''')

        st = etree.XSLT(style)
        res = self.assertRaises(etree.XSLTApplyError,
                                st, tree, bar="<test/>")
        res = self.assertRaises(etree.XSLTApplyError,
                                st, tree, bar="....")

    def test_xslt_parameter_missing(self):
        # apply() without needed parameter will lead to XSLTApplyError
        tree = self.parse('<a><b>B</b><c>C</c></a>')
        style = self.parse('''\
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:template match="/">
    <foo><xsl:value-of select="$bar" /></foo>
  </xsl:template>
</xsl:stylesheet>''')

        st = etree.XSLT(style)
        # at least libxslt 1.1.28 produces this error, earlier ones (e.g. 1.1.18) might not ...
        self.assertRaises(etree.XSLTApplyError, st, tree)

    def test_xslt_multiple_parameters(self):
        tree = self.parse('<a><b>B</b><c>C</c></a>')
        style = self.parse('''\
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:template match="*" />
  <xsl:template match="/">
    <foo><xsl:value-of select="$bar" /></foo>
    <foo><xsl:value-of select="$baz" /></foo>
  </xsl:template>
</xsl:stylesheet>''')

        st = etree.XSLT(style)
        res = st(tree, bar="'Bar'", baz="'Baz'")
        self.assertEqual('''\
<?xml version="1.0"?>
<foo>Bar</foo><foo>Baz</foo>
''',
                          str(res))
        
    def test_xslt_parameter_xpath(self):
        tree = self.parse('<a><b>B</b><c>C</c></a>')
        style = self.parse('''\
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:template match="*" />
  <xsl:template match="/">
    <foo><xsl:value-of select="$bar" /></foo>
  </xsl:template>
</xsl:stylesheet>''')

        st = etree.XSLT(style)
        res = st(tree, bar="/a/b/text()")
        self.assertEqual('''\
<?xml version="1.0"?>
<foo>B</foo>
''',
                          str(res))

    def test_xslt_parameter_xpath_object(self):
        tree = self.parse('<a><b>B</b><c>C</c></a>')
        style = self.parse('''\
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:template match="*" />
  <xsl:template match="/">
    <foo><xsl:value-of select="$bar" /></foo>
  </xsl:template>
</xsl:stylesheet>''')

        st = etree.XSLT(style)
        res = st(tree, bar=etree.XPath("/a/b/text()"))
        self.assertEqual('''\
<?xml version="1.0"?>
<foo>B</foo>
''',
                          str(res))
        
    def test_xslt_default_parameters(self):
        tree = self.parse('<a><b>B</b><c>C</c></a>')
        style = self.parse('''\
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:param name="bar" select="'Default'" />
  <xsl:template match="*" />
  <xsl:template match="/">
    <foo><xsl:value-of select="$bar" /></foo>
  </xsl:template>
</xsl:stylesheet>''')

        st = etree.XSLT(style)
        res = st(tree, bar="'Bar'")
        self.assertEqual('''\
<?xml version="1.0"?>
<foo>Bar</foo>
''',
                          str(res))
        res = st(tree)
        self.assertEqual('''\
<?xml version="1.0"?>
<foo>Default</foo>
''',
                          str(res))
        
    def test_xslt_html_output(self):
        tree = self.parse('<a><b>B</b><c>C</c></a>')
        style = self.parse('''\
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="html"/>
  <xsl:strip-space elements="*"/>
  <xsl:template match="/">
    <html><body><xsl:value-of select="/a/b/text()" /></body></html>
  </xsl:template>
</xsl:stylesheet>''')

        st = etree.XSLT(style)
        res = st(tree)
        self.assertEqual('<html><body>B</body></html>',
                          str(res).strip())

    def test_xslt_include(self):
        tree = etree.parse(fileInTestDir('test1.xslt'))
        st = etree.XSLT(tree)

    def test_xslt_include_from_filelike(self):
        f = open(fileInTestDir('test1.xslt'), 'rb')
        tree = etree.parse(f)
        f.close()
        st = etree.XSLT(tree)

    def test_xslt_multiple_transforms(self):
        xml = '<a/>'
        xslt = '''\
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
    <xsl:template match="/">
        <response>Some text</response>
    </xsl:template>
</xsl:stylesheet>
'''
        source = self.parse(xml)
        styledoc = self.parse(xslt)
        style = etree.XSLT(styledoc)
        result = style(source)

        etree.tostring(result.getroot())
        
        source = self.parse(xml)
        styledoc = self.parse(xslt)
        style = etree.XSLT(styledoc)
        result = style(source)
        
        etree.tostring(result.getroot())

    def test_xslt_repeat_transform(self):
        xml = '<a/>'
        xslt = '''\
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
    <xsl:template match="/">
        <response>Some text</response>
    </xsl:template>
</xsl:stylesheet>
'''
        source = self.parse(xml)
        styledoc = self.parse(xslt)
        transform = etree.XSLT(styledoc)
        result = transform(source)
        result = transform(source)
        etree.tostring(result.getroot())
        result = transform(source)
        etree.tostring(result.getroot())
        str(result)

        result1 = transform(source)
        result2 = transform(source)
        self.assertEqual(str(result1), str(result2))
        result = transform(source)
        str(result)

    def test_xslt_empty(self):
        # could segfault if result contains "empty document"
        xml = '<blah/>'
        xslt = '''
        <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
          <xsl:template match="/" />
        </xsl:stylesheet>
        '''

        source = self.parse(xml)
        styledoc = self.parse(xslt)
        style = etree.XSLT(styledoc)
        result = style(source)
        self.assertEqual('', str(result))

    def test_xslt_message(self):
        xml = '<blah/>'
        xslt = '''
        <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
          <xsl:template match="/">
            <xsl:message>TEST TEST TEST</xsl:message>
          </xsl:template>
        </xsl:stylesheet>
        '''

        source = self.parse(xml)
        styledoc = self.parse(xslt)
        style = etree.XSLT(styledoc)
        result = style(source)
        self.assertEqual('', str(result))
        self.assertTrue("TEST TEST TEST" in [entry.message
                                          for entry in style.error_log])

    def test_xslt_message_terminate(self):
        xml = '<blah/>'
        xslt = '''
        <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
          <xsl:template match="/">
            <xsl:message terminate="yes">TEST TEST TEST</xsl:message>
          </xsl:template>
        </xsl:stylesheet>
        '''

        source = self.parse(xml)
        styledoc = self.parse(xslt)
        style = etree.XSLT(styledoc)

        self.assertRaises(etree.XSLTApplyError, style, source)
        self.assertTrue("TEST TEST TEST" in [entry.message
                                          for entry in style.error_log])

    def test_xslt_shortcut(self):
        tree = self.parse('<a><b>B</b><c>C</c></a>')
        style = self.parse('''\
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:template match="*" />
  <xsl:template match="/">
    <doc>
    <foo><xsl:value-of select="$bar" /></foo>
    <foo><xsl:value-of select="$baz" /></foo>
    </doc>
  </xsl:template>
</xsl:stylesheet>''')

        result = tree.xslt(style, bar="'Bar'", baz="'Baz'")
        self.assertEqual(
            b'<doc><foo>Bar</foo><foo>Baz</foo></doc>',
            etree.tostring(result.getroot()))
        
    def test_multiple_elementrees(self):
        tree = self.parse('<a><b>B</b><c>C</c></a>')
        style = self.parse('''\
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:template match="a"><A><xsl:apply-templates/></A></xsl:template>
  <xsl:template match="b"><B><xsl:apply-templates/></B></xsl:template>
  <xsl:template match="c"><C><xsl:apply-templates/></C></xsl:template>
</xsl:stylesheet>''')

        self.assertEqual(self._rootstring(tree),
                          b'<a><b>B</b><c>C</c></a>')
        result = tree.xslt(style)
        self.assertEqual(self._rootstring(tree),
                          b'<a><b>B</b><c>C</c></a>')
        self.assertEqual(self._rootstring(result),
                          b'<A><B>B</B><C>C</C></A>')

        b_tree = etree.ElementTree(tree.getroot()[0])
        self.assertEqual(self._rootstring(b_tree),
                          b'<b>B</b>')
        result = b_tree.xslt(style)
        self.assertEqual(self._rootstring(tree),
                          b'<a><b>B</b><c>C</c></a>')
        self.assertEqual(self._rootstring(result),
                          b'<B>B</B>')

        c_tree = etree.ElementTree(tree.getroot()[1])
        self.assertEqual(self._rootstring(c_tree),
                          b'<c>C</c>')
        result = c_tree.xslt(style)
        self.assertEqual(self._rootstring(tree),
                          b'<a><b>B</b><c>C</c></a>')
        self.assertEqual(self._rootstring(result),
                          b'<C>C</C>')

    def test_xslt_document_XML(self):
        # make sure document('') works from parsed strings
        xslt = etree.XSLT(etree.XML("""\
<xsl:stylesheet version="1.0"
   xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:template match="/">
    <test>TEXT<xsl:copy-of select="document('')//test"/></test>
  </xsl:template>
</xsl:stylesheet>
"""))
        result = xslt(etree.XML('<a/>'))
        root = result.getroot()
        self.assertEqual(root.tag,
                          'test')
        self.assertEqual(root[0].tag,
                          'test')
        self.assertEqual(root[0].text,
                          'TEXT')
        self.assertEqual(root[0][0].tag,
                          '{http://www.w3.org/1999/XSL/Transform}copy-of')

    def test_xslt_document_parse(self):
        # make sure document('') works from loaded files
        xslt = etree.XSLT(etree.parse(fileInTestDir("test-document.xslt")))
        result = xslt(etree.XML('<a/>'))
        root = result.getroot()
        self.assertEqual(root.tag,
                          'test')
        self.assertEqual(root[0].tag,
                          '{http://www.w3.org/1999/XSL/Transform}stylesheet')

    def test_xslt_document_elementtree(self):
        # make sure document('') works from loaded files
        xslt = etree.XSLT(etree.ElementTree(file=fileInTestDir("test-document.xslt")))
        result = xslt(etree.XML('<a/>'))
        root = result.getroot()
        self.assertEqual(root.tag,
                          'test')
        self.assertEqual(root[0].tag,
                          '{http://www.w3.org/1999/XSL/Transform}stylesheet')

    def test_xslt_document_error(self):
        xslt = etree.XSLT(etree.XML("""\
<xsl:stylesheet version="1.0"
   xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:template match="/">
    <test>TEXT<xsl:copy-of select="document('uri:__junkfood__is__evil__')//test"/></test>
  </xsl:template>
</xsl:stylesheet>
"""))

        errors = None
        try:
            xslt(etree.XML('<a/>'))
        except etree.XSLTApplyError as exc:
            errors = exc.error_log
        else:
            self.assertFalse(True, "XSLT processing should have failed but didn't")

        self.assertTrue(len(errors))
        for error in errors:
            if ':ERROR:XSLT:' in str(error):
                break
        else:
            self.assertFalse(True, 'No XSLT errors found in error log:\n%s' % errors)

    def test_xslt_document_XML_resolver(self):
        # make sure document('') works when custom resolvers are in use
        assertEqual = self.assertEqual
        called = {'count' : 0}
        class TestResolver(etree.Resolver):
            def resolve(self, url, id, context):
                assertEqual(url, 'file://ANYTHING')
                called['count'] += 1
                return self.resolve_string('<CALLED/>', context)

        parser = etree.XMLParser()
        parser.resolvers.add(TestResolver())

        xslt = etree.XSLT(etree.XML(b"""\
<xsl:stylesheet version="1.0"
   xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
   xmlns:l="local">
  <xsl:template match="/">
    <test>
      <xsl:for-each select="document('')//l:data/l:entry">
        <xsl:copy-of select="document('file://ANYTHING')"/>
        <xsl:copy>
          <xsl:attribute name="value">
            <xsl:value-of select="."/>
          </xsl:attribute>
        </xsl:copy>
      </xsl:for-each>
    </test>
  </xsl:template>
  <l:data>
    <l:entry>A</l:entry>
    <l:entry>B</l:entry>
  </l:data>
</xsl:stylesheet>
""", parser))

        self.assertEqual(called['count'], 0)
        result = xslt(etree.XML('<a/>'))
        self.assertEqual(called['count'], 1)

        root = result.getroot()
        self.assertEqual(root.tag,
                          'test')
        self.assertEqual(len(root), 4)

        self.assertEqual(root[0].tag,
                          'CALLED')
        self.assertEqual(root[1].tag,
                          '{local}entry')
        self.assertEqual(root[1].text,
                          None)
        self.assertEqual(root[1].get("value"),
                          'A')
        self.assertEqual(root[2].tag,
                          'CALLED')
        self.assertEqual(root[3].tag,
                          '{local}entry')
        self.assertEqual(root[3].text,
                          None)
        self.assertEqual(root[3].get("value"),
                          'B')

    def test_xslt_resolver_url_building(self):
        assertEqual = self.assertEqual
        called = {'count' : 0}
        expected_url = None
        class TestResolver(etree.Resolver):
            def resolve(self, url, id, context):
                assertEqual(url, expected_url)
                called['count'] += 1
                return self.resolve_string('<CALLED/>', context)

        stylesheet_xml = b"""\
<xsl:stylesheet version="1.0"
   xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
   xmlns:l="local">
  <xsl:template match="/">
    <xsl:copy-of select="document('test.xml')"/>
  </xsl:template>
</xsl:stylesheet>
"""

        parser = etree.XMLParser()
        parser.resolvers.add(TestResolver())

        # test without base_url => relative path only
        expected_url = 'test.xml'
        xslt = etree.XSLT(etree.XML(stylesheet_xml, parser))

        self.assertEqual(called['count'], 0)
        result = xslt(etree.XML('<a/>'))
        self.assertEqual(called['count'], 1)

        # now the same thing with a stylesheet base URL on the filesystem
        called['count'] = 0
        expected_url = 'MY/BASE/test.xml'  # seems to be the same on Windows
        xslt = etree.XSLT(etree.XML(
            stylesheet_xml, parser,
            base_url=os.path.join('MY', 'BASE', 'FILE')))

        self.assertEqual(called['count'], 0)
        result = xslt(etree.XML('<a/>'))
        self.assertEqual(called['count'], 1)

        # now the same thing with a stylesheet base URL
        called['count'] = 0
        expected_url = 'http://server.com/BASE/DIR/test.xml'
        xslt = etree.XSLT(etree.XML(
            stylesheet_xml, parser,
            base_url='http://server.com/BASE/DIR/FILE'))

        self.assertEqual(called['count'], 0)
        result = xslt(etree.XML('<a/>'))
        self.assertEqual(called['count'], 1)

        # now the same thing with a stylesheet base file:// URL
        called['count'] = 0
        expected_url = 'file://BASE/DIR/test.xml'
        xslt = etree.XSLT(etree.XML(
            stylesheet_xml, parser,
            base_url='file://BASE/DIR/FILE'))

        self.assertEqual(called['count'], 0)
        result = xslt(etree.XML('<a/>'))
        self.assertEqual(called['count'], 1)

    def test_xslt_document_parse_allow(self):
        access_control = etree.XSLTAccessControl(read_file=True)
        xslt = etree.XSLT(etree.parse(fileInTestDir("test-document.xslt")),
                          access_control=access_control)
        result = xslt(etree.XML('<a/>'))
        root = result.getroot()
        self.assertEqual(root.tag,
                         'test')
        self.assertEqual(root[0].tag,
                         '{http://www.w3.org/1999/XSL/Transform}stylesheet')

    def test_xslt_document_parse_deny(self):
        access_control = etree.XSLTAccessControl(read_file=False)
        xslt = etree.XSLT(etree.parse(fileInTestDir("test-document.xslt")),
                          access_control=access_control)
        self.assertRaises(etree.XSLTApplyError, xslt, etree.XML('<a/>'))

    def test_xslt_document_parse_deny_all(self):
        access_control = etree.XSLTAccessControl.DENY_ALL
        xslt = etree.XSLT(etree.parse(fileInTestDir("test-document.xslt")),
                          access_control=access_control)
        self.assertRaises(etree.XSLTApplyError, xslt, etree.XML('<a/>'))

    def test_xslt_access_control_repr(self):
        access_control = etree.XSLTAccessControl.DENY_ALL
        self.assertTrue(repr(access_control).startswith(type(access_control).__name__))
        self.assertEqual(repr(access_control), repr(access_control))
        self.assertNotEqual(repr(etree.XSLTAccessControl.DENY_ALL),
                            repr(etree.XSLTAccessControl.DENY_WRITE))
        self.assertNotEqual(repr(etree.XSLTAccessControl.DENY_ALL),
                            repr(etree.XSLTAccessControl()))

    def test_xslt_move_result(self):
        root = etree.XML(b'''\
        <transform>
          <widget displayType="fieldset"/>
        </transform>''')

        xslt = etree.XSLT(etree.XML(b'''\
        <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
          <xsl:output method="html" indent="no"/>
          <xsl:template match="/">
            <html>
              <xsl:apply-templates/>
            </html>
          </xsl:template>

          <xsl:template match="widget">
            <xsl:element name="{@displayType}"/>
          </xsl:template>

        </xsl:stylesheet>'''))

        result = xslt(root[0])
        root[:] = result.getroot()[:]
        del root # segfaulted before
        
    def test_xslt_pi(self):
        tree = self.parse('''\
<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="%s"?>
<a>
  <b>B</b>
  <c>C</c>
</a>''' % fileInTestDir("test1.xslt"))

        style_root = tree.getroot().getprevious().parseXSL().getroot()
        self.assertEqual("{http://www.w3.org/1999/XSL/Transform}stylesheet",
                          style_root.tag)

    def test_xslt_pi_embedded_xmlid(self):
        # test xml:id dictionary lookup mechanism
        tree = self.parse('''\
<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="#style"?>
<a>
  <b>B</b>
  <c>C</c>
  <xsl:stylesheet version="1.0" xml:id="style"
      xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:template match="*" />
    <xsl:template match="/">
      <foo><xsl:value-of select="/a/b/text()" /></foo>
    </xsl:template>
  </xsl:stylesheet>
</a>''')

        style_root = tree.getroot().getprevious().parseXSL().getroot()
        self.assertEqual("{http://www.w3.org/1999/XSL/Transform}stylesheet",
                          style_root.tag)

        st = etree.XSLT(style_root)
        res = st(tree)
        self.assertEqual('''\
<?xml version="1.0"?>
<foo>B</foo>
''',
                          str(res))

    def test_xslt_pi_embedded_id(self):
        # test XPath lookup mechanism
        tree = self.parse('''\
<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="#style"?>
<a>
  <b>B</b>
  <c>C</c>
</a>''')

        style = self.parse('''\
<xsl:stylesheet version="1.0" xml:id="style"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:template match="*" />
  <xsl:template match="/">
    <foo><xsl:value-of select="/a/b/text()" /></foo>
  </xsl:template>
</xsl:stylesheet>
''')

        tree.getroot().append(style.getroot())

        style_root = tree.getroot().getprevious().parseXSL().getroot()
        self.assertEqual("{http://www.w3.org/1999/XSL/Transform}stylesheet",
                          style_root.tag)

        st = etree.XSLT(style_root)
        res = st(tree)
        self.assertEqual('''\
<?xml version="1.0"?>
<foo>B</foo>
''',
                          str(res))

    def test_xslt_pi_get(self):
        tree = self.parse('''\
<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="TEST"?>
<a>
  <b>B</b>
  <c>C</c>
</a>''')

        pi = tree.getroot().getprevious()
        self.assertEqual("TEST", pi.get("href"))

    def test_xslt_pi_get_all(self):
        tree = self.parse('''\
<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="TEST"?>
<a>
  <b>B</b>
  <c>C</c>
</a>''')

        pi = tree.getroot().getprevious()
        self.assertEqual("TEST", pi.get("href"))
        self.assertEqual("text/xsl", pi.get("type"))
        self.assertEqual(None, pi.get("motz"))

    def test_xslt_pi_get_all_reversed(self):
        tree = self.parse('''\
<?xml version="1.0"?>
<?xml-stylesheet href="TEST" type="text/xsl"?>
<a>
  <b>B</b>
  <c>C</c>
</a>''')

        pi = tree.getroot().getprevious()
        self.assertEqual("TEST", pi.get("href"))
        self.assertEqual("text/xsl", pi.get("type"))
        self.assertEqual(None, pi.get("motz"))

    def test_xslt_pi_get_unknown(self):
        tree = self.parse('''\
<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="TEST"?>
<a>
  <b>B</b>
  <c>C</c>
</a>''')

        pi = tree.getroot().getprevious()
        self.assertEqual(None, pi.get("unknownattribute"))

    def test_xslt_pi_set_replace(self):
        tree = self.parse('''\
<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="TEST"?>
<a>
  <b>B</b>
  <c>C</c>
</a>''')

        pi = tree.getroot().getprevious()
        self.assertEqual("TEST", pi.get("href"))

        pi.set("href", "TEST123")
        self.assertEqual("TEST123", pi.get("href"))

    def test_xslt_pi_set_new(self):
        tree = self.parse('''\
<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl"?>
<a>
  <b>B</b>
  <c>C</c>
</a>''')

        pi = tree.getroot().getprevious()
        self.assertEqual(None, pi.get("href"))

        pi.set("href", "TEST")
        self.assertEqual("TEST", pi.get("href"))

class ETreeEXSLTTestCase(HelperTestCase):
    """EXSLT tests"""

    def test_exslt_str(self):
        tree = self.parse('<a><b>B</b><c>C</c></a>')
        style = self.parse('''\
<xsl:stylesheet version="1.0"
    xmlns:str="http://exslt.org/strings"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    exclude-result-prefixes="str xsl">
  <xsl:template match="text()">
    <xsl:value-of select="str:align(string(.), '***', 'center')" />
  </xsl:template>
  <xsl:template match="*">
    <xsl:copy>
      <xsl:apply-templates/>
    </xsl:copy>
  </xsl:template>
</xsl:stylesheet>''')

        st = etree.XSLT(style)
        res = st(tree)
        self.assertEqual('''\
<?xml version="1.0"?>
<a><b>*B*</b><c>*C*</c></a>
''',
                          str(res))

    def test_exslt_str_attribute_replace(self):
        tree = self.parse('<a><b>B</b><c>C</c></a>')
        style = self.parse('''\
          <xsl:stylesheet version = "1.0"
              xmlns:xsl='http://www.w3.org/1999/XSL/Transform'
              xmlns:str="http://exslt.org/strings"
              extension-element-prefixes="str">

              <xsl:template match="/">
                <h1 class="{str:replace('abc', 'b', 'x')}">test</h1>
              </xsl:template>

          </xsl:stylesheet>''')

        st = etree.XSLT(style)
        res = st(tree)
        self.assertEqual(str(res), '''\
<?xml version="1.0"?>
<h1 class="axc">test</h1>
''')

    def test_exslt_math(self):
        tree = self.parse('<a><b>B</b><c>C</c></a>')
        style = self.parse('''\
<xsl:stylesheet version="1.0"
    xmlns:math="http://exslt.org/math"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    exclude-result-prefixes="math xsl">
  <xsl:template match="*">
    <xsl:copy>
      <xsl:attribute name="pi">
        <xsl:value-of select="math:constant('PI', count(*)+2)"/>
      </xsl:attribute>
      <xsl:apply-templates/>
    </xsl:copy>
  </xsl:template>
</xsl:stylesheet>''')

        st = etree.XSLT(style)
        res = st(tree)
        self.assertEqual('''\
<?xml version="1.0"?>
<a pi="3.14"><b pi="3">B</b><c pi="3">C</c></a>
''',
                          str(res))

    def test_exslt_regexp_test(self):
        xslt = etree.XSLT(etree.XML(b"""\
<xsl:stylesheet version="1.0"
   xmlns:regexp="http://exslt.org/regular-expressions"
   xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:template match="*">
    <test><xsl:copy-of select="*[regexp:test(string(.), '8.')]"/></test>
  </xsl:template>
</xsl:stylesheet>
"""))
        result = xslt(etree.XML(b'<a><b>123</b><b>098</b><b>987</b></a>'))
        root = result.getroot()
        self.assertEqual(root.tag,
                          'test')
        self.assertEqual(len(root), 1)
        self.assertEqual(root[0].tag,
                          'b')
        self.assertEqual(root[0].text,
                          '987')

    def test_exslt_regexp_replace(self):
        xslt = etree.XSLT(etree.XML("""\
<xsl:stylesheet version="1.0"
   xmlns:regexp="http://exslt.org/regular-expressions"
   xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:template match="*">
    <test>
      <xsl:copy-of select="regexp:replace(string(.), 'd.', '',   'XX')"/>
      <xsl:text>-</xsl:text>
      <xsl:copy-of select="regexp:replace(string(.), 'd.', 'gi', 'XX')"/>
    </test>
  </xsl:template>
</xsl:stylesheet>
"""))
        result = xslt(etree.XML(b'<a>abdCdEeDed</a>'))
        root = result.getroot()
        self.assertEqual(root.tag,
                          'test')
        self.assertEqual(len(root), 0)
        self.assertEqual(root.text, 'abXXdEeDed-abXXXXeXXd')

    def test_exslt_regexp_match(self):
        xslt = etree.XSLT(etree.XML("""\
<xsl:stylesheet version="1.0"
   xmlns:regexp="http://exslt.org/regular-expressions"
   xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:template match="*">
    <test>
      <test1><xsl:copy-of  select="regexp:match(string(.), 'd.')"/></test1>
      <test2><xsl:copy-of  select="regexp:match(string(.), 'd.', 'g')"/></test2>
      <test2i><xsl:copy-of select="regexp:match(string(.), 'd.', 'gi')"/></test2i>
    </test>
  </xsl:template>
</xsl:stylesheet>
"""))
        result = xslt(etree.XML(b'<a>abdCdEeDed</a>'))
        root = result.getroot()
        self.assertEqual(root.tag,  'test')
        self.assertEqual(len(root), 3)

        self.assertEqual(len(root[0]), 1)
        self.assertEqual(root[0][0].tag, 'match')
        self.assertEqual(root[0][0].text, 'dC')

        self.assertEqual(len(root[1]), 2)
        self.assertEqual(root[1][0].tag, 'match')
        self.assertEqual(root[1][0].text, 'dC')
        self.assertEqual(root[1][1].tag, 'match')
        self.assertEqual(root[1][1].text, 'dE')

        self.assertEqual(len(root[2]), 3)
        self.assertEqual(root[2][0].tag, 'match')
        self.assertEqual(root[2][0].text, 'dC')
        self.assertEqual(root[2][1].tag, 'match')
        self.assertEqual(root[2][1].text, 'dE')
        self.assertEqual(root[2][2].tag, 'match')
        self.assertEqual(root[2][2].text, 'De')

    def test_exslt_regexp_match_groups(self):
        xslt = etree.XSLT(etree.XML(b"""\
<xsl:stylesheet version="1.0"
   xmlns:regexp="http://exslt.org/regular-expressions"
   xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:template match="/">
    <test>
      <xsl:for-each select="regexp:match(
            '123abc567', '([0-9]+)([a-z]+)([0-9]+)' )">
        <test1><xsl:value-of select="."/></test1>
      </xsl:for-each>
    </test>
  </xsl:template>
</xsl:stylesheet>
"""))
        result = xslt(etree.XML(b'<a/>'))
        root = result.getroot()
        self.assertEqual(root.tag,  'test')
        self.assertEqual(len(root), 4)

        self.assertEqual(root[0].text, "123abc567")
        self.assertEqual(root[1].text, "123")
        self.assertEqual(root[2].text, "abc")
        self.assertEqual(root[3].text, "567")

    def test_exslt_regexp_match1(self):
        # taken from http://www.exslt.org/regexp/functions/match/index.html
        xslt = etree.XSLT(etree.XML(br"""
<xsl:stylesheet version="1.0"
   xmlns:regexp="http://exslt.org/regular-expressions"
   xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:template match="/">
    <test>
      <xsl:for-each select="regexp:match(
            'http://www.bayes.co.uk/xml/index.xml?/xml/utils/rechecker.xml',
            '(\w+):\/\/([^/:]+)(:\d*)?([^# ]*)')">
        <test1><xsl:value-of select="."/></test1>
      </xsl:for-each>
    </test>
  </xsl:template>
</xsl:stylesheet>
"""))
        result = xslt(etree.XML(b'<a/>'))
        root = result.getroot()
        self.assertEqual(root.tag,  'test')
        self.assertEqual(len(root), 5)

        self.assertEqual(
            root[0].text,
            "http://www.bayes.co.uk/xml/index.xml?/xml/utils/rechecker.xml")
        self.assertEqual(
            root[1].text,
            "http")
        self.assertEqual(
            root[2].text,
            "www.bayes.co.uk")
        self.assertFalse(root[3].text)
        self.assertEqual(
            root[4].text,
            "/xml/index.xml?/xml/utils/rechecker.xml")

    def test_exslt_regexp_match2(self):
        # taken from http://www.exslt.org/regexp/functions/match/index.html
        xslt = etree.XSLT(self.parse("""\
<xsl:stylesheet version="1.0"
   xmlns:regexp="http://exslt.org/regular-expressions"
   xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:template match="/">
    <test>
      <xsl:for-each select="regexp:match(
            'This is a test string', '(\\w+)', 'g')">
        <test1><xsl:value-of select="."/></test1>
      </xsl:for-each>
    </test>
  </xsl:template>
</xsl:stylesheet>
"""))
        result = xslt(etree.XML(b'<a/>'))
        root = result.getroot()
        self.assertEqual(root.tag,  'test')
        self.assertEqual(len(root), 5)

        self.assertEqual(root[0].text, "This")
        self.assertEqual(root[1].text, "is")
        self.assertEqual(root[2].text, "a")
        self.assertEqual(root[3].text, "test")
        self.assertEqual(root[4].text, "string")

    def _test_exslt_regexp_match3(self):
        # taken from http://www.exslt.org/regexp/functions/match/index.html
        # THIS IS NOT SUPPORTED!
        xslt = etree.XSLT(etree.XML(b"""\
<xsl:stylesheet version="1.0"
   xmlns:regexp="http://exslt.org/regular-expressions"
   xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:template match="/">
    <test>
      <xsl:for-each select="regexp:match(
            'This is a test string', '([a-z])+ ', 'g')">
        <test1><xsl:value-of select="."/></test1>
      </xsl:for-each>
    </test>
  </xsl:template>
</xsl:stylesheet>
"""))
        result = xslt(etree.XML(b'<a/>'))
        root = result.getroot()
        self.assertEqual(root.tag,  'test')
        self.assertEqual(len(root), 4)

        self.assertEqual(root[0].text, "his")
        self.assertEqual(root[1].text, "is")
        self.assertEqual(root[2].text, "a")
        self.assertEqual(root[3].text, "test")

    def _test_exslt_regexp_match4(self):
        # taken from http://www.exslt.org/regexp/functions/match/index.html
        # THIS IS NOT SUPPORTED!
        xslt = etree.XSLT(etree.XML(b"""\
<xsl:stylesheet version="1.0"
   xmlns:regexp="http://exslt.org/regular-expressions"
   xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:template match="/">
    <test>
      <xsl:for-each select="regexp:match(
            'This is a test string', '([a-z])+ ', 'gi')">
        <test1><xsl:value-of select="."/></test1>
      </xsl:for-each>
    </test>
  </xsl:template>
</xsl:stylesheet>
"""))
        result = xslt(etree.XML(b'<a/>'))
        root = result.getroot()
        self.assertEqual(root.tag,  'test')
        self.assertEqual(len(root), 4)

        self.assertEqual(root[0].text, "This")
        self.assertEqual(root[1].text, "is")
        self.assertEqual(root[2].text, "a")
        self.assertEqual(root[3].text, "test")


class ETreeXSLTExtFuncTestCase(HelperTestCase):
    """Tests for XPath extension functions in XSLT."""

    def test_extensions1(self):
        tree = self.parse('<a><b>B</b></a>')
        style = self.parse('''\
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:myns="testns"
    exclude-result-prefixes="myns">
  <xsl:template match="a"><A><xsl:value-of select="myns:mytext(b)"/></A></xsl:template>
</xsl:stylesheet>''')

        def mytext(ctxt, values):
            return 'X' * len(values)

        result = tree.xslt(style, {('testns', 'mytext') : mytext})
        self.assertEqual(self._rootstring(result),
                          b'<A>X</A>')

    def test_extensions2(self):
        tree = self.parse('<a><b>B</b></a>')
        style = self.parse('''\
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:myns="testns"
    exclude-result-prefixes="myns">
  <xsl:template match="a"><A><xsl:value-of select="myns:mytext(b)"/></A></xsl:template>
</xsl:stylesheet>''')

        def mytext(ctxt, values):
            return 'X' * len(values)

        namespace = etree.FunctionNamespace('testns')
        namespace['mytext'] = mytext

        result = tree.xslt(style)
        self.assertEqual(self._rootstring(result),
                          b'<A>X</A>')

    def test_variable_result_tree_fragment(self):
        tree = self.parse('<a><b>B</b><b/></a>')
        style = self.parse('''\
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:myns="testns"
    exclude-result-prefixes="myns">
  <xsl:template match="a">
    <xsl:variable name="content">
       <xsl:apply-templates/>
    </xsl:variable>
    <A><xsl:value-of select="myns:mytext($content)"/></A>
  </xsl:template>
  <xsl:template match="b"><xsl:copy>BBB</xsl:copy></xsl:template>
</xsl:stylesheet>''')

        def mytext(ctxt, values):
            for value in values:
                self.assertTrue(hasattr(value, 'tag'),
                             "%s is not an Element" % type(value))
                self.assertEqual(value.tag, 'b')
                self.assertEqual(value.text, 'BBB')
            return 'X'.join([el.tag for el in values])

        namespace = etree.FunctionNamespace('testns')
        namespace['mytext'] = mytext

        result = tree.xslt(style)
        self.assertEqual(self._rootstring(result),
                         b'<A>bXb</A>')

    def test_xpath_on_context_node(self):
        tree = self.parse('<a><b>B<c/>C</b><b/></a>')
        style = self.parse('''\
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:myns="testns"
    exclude-result-prefixes="myns">
  <xsl:template match="b">
    <A><xsl:value-of select="myns:myext()"/></A>
  </xsl:template>
</xsl:stylesheet>''')

        def extfunc(ctxt):
            text_content = ctxt.context_node.xpath('text()')
            return 'x'.join(text_content)

        namespace = etree.FunctionNamespace('testns')
        namespace['myext'] = extfunc

        result = tree.xslt(style)
        self.assertEqual(self._rootstring(result),
                         b'<A>BxC</A>')

    def test_xpath_on_foreign_context_node(self):
        # LP ticket 1354652
        class Resolver(etree.Resolver):
            def resolve(self, system_url, public_id, context):
                assert system_url == 'extdoc.xml'
                return self.resolve_string(b'<a><b>B<c/>C</b><b/></a>', context)

        parser = etree.XMLParser()
        parser.resolvers.add(Resolver())

        tree = self.parse(b'<a><b/><b/></a>')
        transform = etree.XSLT(self.parse(b'''\
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:mypre="testns"
    exclude-result-prefixes="mypre">
  <xsl:template match="b">
    <B><xsl:value-of select="mypre:myext()"/></B>
  </xsl:template>
  <xsl:template match="a">
    <A><xsl:apply-templates select="document('extdoc.xml')//b" /></A>
  </xsl:template>
</xsl:stylesheet>''', parser=parser))

        def extfunc(ctxt):
            text_content = ctxt.context_node.xpath('text()')
            return 'x'.join(text_content)

        namespace = etree.FunctionNamespace('testns')
        namespace['myext'] = extfunc

        result = transform(tree)
        self.assertEqual(self._rootstring(result),
                         b'<A><B>BxC</B><B/></A>')


class ETreeXSLTExtElementTestCase(HelperTestCase):
    """Tests for extension elements in XSLT."""

    def test_extension_element(self):
        tree = self.parse('<a><b>B</b></a>')
        style = self.parse('''\
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:myns="testns"
    extension-element-prefixes="myns"
    exclude-result-prefixes="myns">
  <xsl:template match="a">
    <A><myns:myext>b</myns:myext></A>
  </xsl:template>
</xsl:stylesheet>''')

        class MyExt(etree.XSLTExtension):
            def execute(self, context, self_node, input_node, output_parent):
                child = etree.Element(self_node.text)
                child.text = 'X'
                output_parent.append(child)

        extensions = { ('testns', 'myext') : MyExt() }

        result = tree.xslt(style, extensions=extensions)
        self.assertEqual(self._rootstring(result),
                          b'<A><b>X</b></A>')

    def test_extension_element_doc_context(self):
        tree = self.parse('<a><b>B</b></a>')
        style = self.parse('''\
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:myns="testns"
    extension-element-prefixes="myns"
    exclude-result-prefixes="myns">
  <xsl:template match="/">
    <A><myns:myext>b</myns:myext></A>
  </xsl:template>
</xsl:stylesheet>''')

        tags = []

        class MyExt(etree.XSLTExtension):
            def execute(self, context, self_node, input_node, output_parent):
                tags.append(input_node.tag)

        extensions = { ('testns', 'myext') : MyExt() }

        result = tree.xslt(style, extensions=extensions)
        self.assertEqual(tags, ['a'])

    def test_extension_element_comment_pi_context(self):
        tree = self.parse('<?test toast?><a><!--a comment--><?another pi?></a>')
        style = self.parse('''\
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:myns="testns"
    extension-element-prefixes="myns"
    exclude-result-prefixes="myns">
  <xsl:template match="/">
    <ROOT><xsl:apply-templates /></ROOT>
  </xsl:template>
  <xsl:template match="comment()">
    <A><myns:myext>b</myns:myext></A>
  </xsl:template>
  <xsl:template match="processing-instruction()">
    <A><myns:myext>b</myns:myext></A>
  </xsl:template>
</xsl:stylesheet>''')

        text = []

        class MyExt(etree.XSLTExtension):
            def execute(self, context, self_node, input_node, output_parent):
                text.append(input_node.text)

        extensions = { ('testns', 'myext') : MyExt() }

        result = tree.xslt(style, extensions=extensions)
        self.assertEqual(text, ['toast', 'a comment', 'pi'])

    def _test_extension_element_attribute_context(self):
        # currently not supported
        tree = self.parse('<a test="A"><b attr="B"/></a>')
        style = self.parse('''\
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:myns="testns"
    extension-element-prefixes="myns"
    exclude-result-prefixes="myns">
  <xsl:template match="@test">
    <A><myns:myext>b</myns:myext></A>
  </xsl:template>
  <xsl:template match="@attr">
    <A><myns:myext>b</myns:myext></A>
  </xsl:template>
</xsl:stylesheet>''')

        text = []

        class MyExt(etree.XSLTExtension):
            def execute(self, context, self_node, attr_value, output_parent):
                text.append(attr_value)

        extensions = { ('testns', 'myext') : MyExt() }

        result = tree.xslt(style, extensions=extensions)
        self.assertEqual(text, ['A', 'B'])

    def test_extension_element_content(self):
        tree = self.parse('<a><b>B</b></a>')
        style = self.parse('''\
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:myns="testns"
    extension-element-prefixes="myns">
  <xsl:template match="a">
    <A><myns:myext><x>X</x><y>Y</y><z/></myns:myext></A>
  </xsl:template>
</xsl:stylesheet>''')

        class MyExt(etree.XSLTExtension):
            def execute(self, context, self_node, input_node, output_parent):
                output_parent.extend(list(self_node)[1:])

        extensions = { ('testns', 'myext') : MyExt() }

        result = tree.xslt(style, extensions=extensions)
        self.assertEqual(self._rootstring(result),
                          b'<A><y>Y</y><z/></A>')

    def test_extension_element_apply_templates(self):
        tree = self.parse('<a><b>B</b></a>')
        style = self.parse('''\
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:myns="testns"
    extension-element-prefixes="myns">
  <xsl:template match="a">
    <A><myns:myext><x>X</x><y>Y</y><z/></myns:myext></A>
  </xsl:template>
  <xsl:template match="x" />
  <xsl:template match="z">XYZ</xsl:template>
</xsl:stylesheet>''')

        class MyExt(etree.XSLTExtension):
            def execute(self, context, self_node, input_node, output_parent):
                for child in self_node:
                    for result in self.apply_templates(context, child):
                        if isinstance(result, str):
                            el = etree.Element("T")
                            el.text = result
                        else:
                            el = result
                        output_parent.append(el)

        extensions = { ('testns', 'myext') : MyExt() }

        result = tree.xslt(style, extensions=extensions)
        self.assertEqual(self._rootstring(result),
                          b'<A><T>Y</T><T>XYZ</T></A>')

    def test_extension_element_apply_templates_elements_only(self):
        tree = self.parse('<a><b>B</b></a>')
        style = self.parse('''\
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:myns="testns"
    extension-element-prefixes="myns">
  <xsl:template match="a">
    <A><myns:myext><x>X</x><y>Y</y><z/></myns:myext></A>
  </xsl:template>
  <xsl:template match="x"><X/></xsl:template>
  <xsl:template match="z">XYZ</xsl:template>
</xsl:stylesheet>''')

        class MyExt(etree.XSLTExtension):
            def execute(self, context, self_node, input_node, output_parent):
                for child in self_node:
                    for result in self.apply_templates(context, child,
                                                       elements_only=True):
                        assert not isinstance(result, str)
                        output_parent.append(result)

        extensions = { ('testns', 'myext') : MyExt() }

        result = tree.xslt(style, extensions=extensions)
        self.assertEqual(self._rootstring(result),
                          b'<A><X/></A>')

    def test_extension_element_apply_templates_remove_blank_text(self):
        tree = self.parse('<a><b>B</b></a>')
        style = self.parse('''\
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:myns="testns"
    extension-element-prefixes="myns">
  <xsl:template match="a">
    <A><myns:myext><x>X</x><y>Y</y><z/></myns:myext></A>
  </xsl:template>
  <xsl:template match="x"><X/></xsl:template>
  <xsl:template match="y"><xsl:text>   </xsl:text></xsl:template>
  <xsl:template match="z">XYZ</xsl:template>
</xsl:stylesheet>''')

        class MyExt(etree.XSLTExtension):
            def execute(self, context, self_node, input_node, output_parent):
                for child in self_node:
                    for result in self.apply_templates(context, child,
                                                       remove_blank_text=True):
                        if isinstance(result, str):
                            assert result.strip()
                            el = etree.Element("T")
                            el.text = result
                        else:
                            el = result
                        output_parent.append(el)

        extensions = { ('testns', 'myext') : MyExt() }

        result = tree.xslt(style, extensions=extensions)
        self.assertEqual(self._rootstring(result),
                          b'<A><X/><T>XYZ</T></A>')

    def test_extension_element_apply_templates_target_node(self):
        tree = self.parse('<a><b>B</b></a>')
        style = self.parse('''\
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:myns="testns"
    extension-element-prefixes="myns">
  <xsl:template match="a">
    <A><myns:myext><x>X</x><y>Y</y><z/></myns:myext></A>
  </xsl:template>
  <xsl:template match="x" />
  <xsl:template match="z">XYZ</xsl:template>
</xsl:stylesheet>''')

        class MyExt(etree.XSLTExtension):
            def execute(self, context, self_node, input_node, output_parent):
                for child in self_node:
                    self.apply_templates(context, child, output_parent)

        extensions = { ('testns', 'myext') : MyExt() }

        result = tree.xslt(style, extensions=extensions)
        self.assertEqual(self._rootstring(result),
                          b'<A>YXYZ</A>')

    def test_extension_element_apply_templates_target_node_doc(self):
        tree = self.parse('<a><b>B</b></a>')
        style = self.parse('''\
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:myns="testns"
    extension-element-prefixes="myns">
  <xsl:template match="a">
    <myns:myext><x>X</x><y>Y</y><z/></myns:myext>
  </xsl:template>
  <xsl:template match="x"><xsl:processing-instruction name="test">TEST</xsl:processing-instruction></xsl:template>
  <xsl:template match="y"><Y>XYZ</Y></xsl:template>
  <xsl:template match="z"><xsl:comment>TEST</xsl:comment></xsl:template>
</xsl:stylesheet>''')

        class MyExt(etree.XSLTExtension):
            def execute(self, context, self_node, input_node, output_parent):
                for child in self_node:
                    self.apply_templates(context, child, output_parent)

        extensions = { ('testns', 'myext') : MyExt() }

        result = tree.xslt(style, extensions=extensions)
        self.assertEqual(etree.tostring(result),
                          b'<?test TEST?><Y>XYZ</Y><!--TEST-->')

    def test_extension_element_process_children(self):
        tree = self.parse('<a><b>E</b></a>')
        style = self.parse('''\
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:myns="testns"
    extension-element-prefixes="myns">
  <xsl:template match="a">
    <xsl:variable name="testvar">yo</xsl:variable>
    <A>
      <myns:myext>
        <xsl:attribute name="attr">
          <xsl:value-of select="$testvar" />
        </xsl:attribute>
        <B>
          <xsl:choose>
            <xsl:when test="1 = 2"><C/></xsl:when>
            <xsl:otherwise><D><xsl:value-of select="b/text()" /></D></xsl:otherwise>
          </xsl:choose>
        </B>
      </myns:myext>
    </A>
  </xsl:template>
</xsl:stylesheet>''')

        class MyExt(etree.XSLTExtension):
            def execute(self, context, self_node, input_node, output_parent):
                el = etree.Element('MY')
                self.process_children(context, el)
                output_parent.append(el)

        extensions = { ('testns', 'myext') : MyExt() }

        result = tree.xslt(style, extensions=extensions)
        self.assertEqual(self._rootstring(result),
                          b'<A><MYattr="yo"><B><D>E</D></B></MY></A>')

    def test_extension_element_process_children_to_append_only(self):
        tree = self.parse('<a/>')
        style = self.parse('''\
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:myns="testns"
    extension-element-prefixes="myns">
  <xsl:template match="a">
    <myns:myext>
      <A/>
    </myns:myext>
  </xsl:template>
</xsl:stylesheet>''')

        class MyExt(etree.XSLTExtension):
            def execute(self, context, self_node, input_node, output_parent):
                self.process_children(context, output_parent)

        extensions = { ('testns', 'myext') : MyExt() }

        result = tree.xslt(style, extensions=extensions)
        self.assertEqual(self._rootstring(result),
                          b'<A/>')

    def test_extension_element_process_children_to_read_only_raise(self):
        tree = self.parse('<a/>')
        style = self.parse('''\
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:myns="testns"
    extension-element-prefixes="myns">
  <xsl:template match="a">
    <myns:myext>
      <A/>
    </myns:myext>
  </xsl:template>
</xsl:stylesheet>''')

        class MyExt(etree.XSLTExtension):
            def execute(self, context, self_node, input_node, output_parent):
                self.process_children(context, self_node)

        extensions = { ('testns', 'myext') : MyExt() }

        self.assertRaises(TypeError, tree.xslt, style, extensions=extensions)

    def test_extension_element_process_children_with_subextension_element(self):
        tree = self.parse('<a/>')
        style = self.parse('''\
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:myns="testns"
    extension-element-prefixes="myns">
  <xsl:template match="a">
    <myns:myext>
      <A><myns:myext><B/></myns:myext></A>
    </myns:myext>
  </xsl:template>
</xsl:stylesheet>''')

        class MyExt(etree.XSLTExtension):
            callback_call_counter = 0
            def execute(self, context, self_node, input_node, output_parent):
                self.callback_call_counter += 1
                el = etree.Element('MY', n=str(self.callback_call_counter))
                self.process_children(context, el)
                output_parent.append(el)

        extensions = { ('testns', 'myext') : MyExt() }

        result = tree.xslt(style, extensions=extensions)
        self.assertEqual(self._rootstring(result),
                          b'<MYn="1"><A><MYn="2"><B/></MY></A></MY>')

    def test_extension_element_raise(self):
        tree = self.parse('<a><b>B</b></a>')
        style = self.parse('''\
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:myns="testns"
    extension-element-prefixes="myns"
    exclude-result-prefixes="myns">
  <xsl:template match="a">
    <A><myns:myext>b</myns:myext></A>
  </xsl:template>
</xsl:stylesheet>''')

        class MyError(Exception):
            pass

        class MyExt(etree.XSLTExtension):
            def execute(self, context, self_node, input_node, output_parent):
                raise MyError("expected!")

        extensions = { ('testns', 'myext') : MyExt() }
        self.assertRaises(MyError, tree.xslt, style, extensions=extensions)

    # FIXME: DISABLED - implementation seems to be broken
    # if someone cares enough about this feature, I take pull requests that fix it.
    def _test_multiple_extension_elements_with_output_parent(self):
        tree = self.parse("""\
<text>
  <par>This is <format>arbitrary</format> text in a paragraph</par>
</text>""")
        style = self.parse("""\
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:my="my" extension-element-prefixes="my" version="1.0">
  <xsl:template match="par">
    <my:par><xsl:apply-templates /></my:par>
  </xsl:template>
  <xsl:template match="format">
    <my:format><xsl:apply-templates /></my:format>
  </xsl:template>
</xsl:stylesheet>
""")
        test = self
        calls = []

        class ExtMyPar(etree.XSLTExtension):
            def execute(self, context, self_node, input_node, output_parent):
                calls.append('par')
                p = etree.Element("p")
                p.attrib["style"] = "color:red"
                self.process_children(context, p)
                output_parent.append(p)

        class ExtMyFormat(etree.XSLTExtension):
            def execute(self, context, self_node, input_node, output_parent):
                calls.append('format')
                content = self.process_children(context)
                test.assertEqual(1, len(content))
                test.assertEqual('arbitrary', content[0])
                test.assertEqual('This is ', output_parent.text)
                output_parent.text += '*-%s-*' % content[0]

        extensions = {("my", "par"): ExtMyPar(), ("my", "format"): ExtMyFormat()}
        transform = etree.XSLT(style, extensions=extensions)
        result = transform(tree)
        self.assertEqual(['par', 'format'], calls)
        self.assertEqual(
            b'<p style="color:red">This is *-arbitrary-* text in a paragraph</p>\n',
            etree.tostring(result))

    def test_extensions_nsmap(self):
        tree = self.parse("""\
<root>
  <inner xmlns:sha256="http://www.w3.org/2001/04/xmlenc#sha256">
    <data>test</data>
  </inner>
</root>
""")
        style = self.parse("""\
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:my="extns" extension-element-prefixes="my" version="1.0">
  <xsl:template match="node()|@*">
    <xsl:copy>
      <xsl:apply-templates select="node()|@*"/>
    </xsl:copy>
  </xsl:template>

  <xsl:template match="data">
    <my:show-nsmap/>
  </xsl:template>
</xsl:stylesheet>
""")
        class MyExt(etree.XSLTExtension):
            def execute(self, context, self_node, input_node, output_parent):
                output_parent.text = str(input_node.nsmap)

        extensions = {('extns', 'show-nsmap'): MyExt()}

        result = tree.xslt(style, extensions=extensions)
        self.assertEqual(etree.tostring(result, pretty_print=True), b"""\
<root>
  <inner xmlns:sha256="http://www.w3.org/2001/04/xmlenc#sha256">{'sha256': 'http://www.w3.org/2001/04/xmlenc#sha256'}
  </inner>
</root>
""")



class Py3XSLTTestCase(HelperTestCase):
    """XSLT tests for etree under Python 3"""

    def test_xslt_result_bytes(self):
        tree = self.parse('<a><b>B</b><c>C</c></a>')
        style = self.parse('''\
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:template match="*" />
  <xsl:template match="/">
    <foo><xsl:value-of select="/a/b/text()" /></foo>
  </xsl:template>
</xsl:stylesheet>''')

        st = etree.XSLT(style)
        res = st(tree)
        self.assertEqual(b'''\
<?xml version="1.0"?>
<foo>B</foo>
''',
                          bytes(res))

    def test_xslt_result_bytearray(self):
        tree = self.parse('<a><b>B</b><c>C</c></a>')
        style = self.parse('''\
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:template match="*" />
  <xsl:template match="/">
    <foo><xsl:value-of select="/a/b/text()" /></foo>
  </xsl:template>
</xsl:stylesheet>''')

        st = etree.XSLT(style)
        res = st(tree)
        self.assertEqual(b'''\
<?xml version="1.0"?>
<foo>B</foo>
''',
                          bytearray(res))

    def test_xslt_result_memoryview(self):
        tree = self.parse('<a><b>B</b><c>C</c></a>')
        style = self.parse('''\
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:template match="*" />
  <xsl:template match="/">
    <foo><xsl:value-of select="/a/b/text()" /></foo>
  </xsl:template>
</xsl:stylesheet>''')

        st = etree.XSLT(style)
        res = st(tree)
        self.assertEqual(b'''\
<?xml version="1.0"?>
<foo>B</foo>
''',
                          bytes(memoryview(res)))


def test_suite():
    suite = unittest.TestSuite()
    suite.addTests([unittest.defaultTestLoader.loadTestsFromTestCase(ETreeXSLTTestCase)])
    suite.addTests([unittest.defaultTestLoader.loadTestsFromTestCase(ETreeEXSLTTestCase)])
    suite.addTests([unittest.defaultTestLoader.loadTestsFromTestCase(ETreeXSLTExtFuncTestCase)])
    suite.addTests([unittest.defaultTestLoader.loadTestsFromTestCase(ETreeXSLTExtElementTestCase)])
    suite.addTests([unittest.defaultTestLoader.loadTestsFromTestCase(Py3XSLTTestCase)])
    suite.addTests(
        [make_doctest('../../../doc/extensions.txt')])
    suite.addTests(
        [make_doctest('../../../doc/xpathxslt.txt')])
    return suite

if __name__ == '__main__':
    print('to test use test.py %s' % __file__)
