Blob Blame History Raw
# -*- coding: utf-8 -*-

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

import io
import sys
import copy
import gzip
import os.path
import unittest
import contextlib
from textwrap import dedent
from tempfile import NamedTemporaryFile

this_dir = os.path.dirname(__file__)
if this_dir not in sys.path:
    sys.path.insert(0, this_dir) # needed for Py3

is_python3 = sys.version_info[0] >= 3

try:
    unicode
except NameError: # Python 3
    unicode = str

try:
    basestring
except NameError: # Python 3
    basestring = str

from .common_imports import etree, BytesIO, HelperTestCase, fileInTestDir
from .common_imports import doctest, _bytes, _str, make_doctest, skipif

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(_bytes('<a><b>\\uF8D2</b><c>\\uF8D2</c></a>'
                                 ).decode("unicode_escape"))
        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 = _bytes(dedent(expected).strip()).decode("unicode_escape").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] = unicode(bytes(res[0]), 'UTF-8')
            assert 'UTF-8' in res[0]

    def test_xslt_encoding(self):
        with self._xslt_setup() as res:
            res[0] = unicode(bytes(res[0]), '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')
            if is_python3:
                output = str(f.getvalue(), 'UTF-16')
            else:
                output = unicode(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(object):
            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 io.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 contextlib.closing(gzip.GzipFile(f.name)) as f:
                    res[0] = f.read().decode("UTF-16")
            finally:
                os.unlink(f.name)

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

    def test_xslt_unicode_standalone(self):
        tree = self.parse(_bytes('<a><b>\\uF8D2</b><c>\\uF8D2</c></a>'
        ).decode("unicode_escape"))
        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 = _bytes('''\
<?xml version="1.0" standalone="no"?>
<foo>\\uF8D2</foo>
''').decode("unicode_escape")
        self.assertEqual(expected,
                         unicode(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.apply, 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(
            _bytes('<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),
                          _bytes('<a><b>B</b><c>C</c></a>'))
        result = tree.xslt(style)
        self.assertEqual(self._rootstring(tree),
                          _bytes('<a><b>B</b><c>C</c></a>'))
        self.assertEqual(self._rootstring(result),
                          _bytes('<A><B>B</B><C>C</C></A>'))

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

        c_tree = etree.ElementTree(tree.getroot()[1])
        self.assertEqual(self._rootstring(c_tree),
                          _bytes('<c>C</c>'))
        result = c_tree.xslt(style)
        self.assertEqual(self._rootstring(tree),
                          _bytes('<a><b>B</b><c>C</c></a>'))
        self.assertEqual(self._rootstring(result),
                          _bytes('<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(_bytes("""\
<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 = _bytes("""\
<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(_bytes('''\
        <transform>
          <widget displayType="fieldset"/>
        </transform>'''))

        xslt = etree.XSLT(etree.XML(_bytes('''\
        <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(_bytes("""\
<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(_bytes('<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(_bytes('<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(_bytes('<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(_bytes("""\
<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(_bytes('<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(_bytes("""\
<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(_bytes('<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(_bytes('<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(_bytes("""\
<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(_bytes('<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(_bytes("""\
<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(_bytes('<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),
                          _bytes('<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),
                          _bytes('<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),
                         _bytes('<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),
                         _bytes('<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),
                         _bytes('<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),
                          _bytes('<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),
                          _bytes('<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, basestring):
                            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),
                          _bytes('<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, basestring)
                        output_parent.append(result)

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

        result = tree.xslt(style, extensions=extensions)
        self.assertEqual(self._rootstring(result),
                          _bytes('<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, basestring):
                            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),
                          _bytes('<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),
                          _bytes('<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),
                          _bytes('<?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),
                          _bytes('<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),
                          _bytes('<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),
                          _bytes('<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))


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

    pytestmark = skipif('sys.version_info < (3,0)')

    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(_bytes('''\
<?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(_bytes('''\
<?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(_bytes('''\
<?xml version="1.0"?>
<foo>B</foo>
'''),
                          bytes(memoryview(res)))


def test_suite():
    suite = unittest.TestSuite()
    suite.addTests([unittest.makeSuite(ETreeXSLTTestCase)])
    suite.addTests([unittest.makeSuite(ETreeEXSLTTestCase)])
    suite.addTests([unittest.makeSuite(ETreeXSLTExtFuncTestCase)])
    suite.addTests([unittest.makeSuite(ETreeXSLTExtElementTestCase)])
    if is_python3:
        suite.addTests([unittest.makeSuite(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__)