import sys, cStringIO, os, urllib, types
from xml.xslt import Processor
from xml.xslt import StylesheetReader
from Ft.Lib import pDomlette
from Ft.Lib import cDomlette
from xml.dom.ext.reader import Sax
from xml.dom.ext.reader import Sax2
from xml.dom.ext import ReleaseNode
from xml.dom import minidom
from Ft.Lib import Uri
from Ft.Lib import TreeCompare
from Ft.Lib import Cyclops

def CycleFilter(cycles):
    # Python 2.0+ has cycles in its exceptions
    if type(cycles[0][0]) is types.ClassType:
        return not issubclass(cycles[0][0], Exception)
    return 0

def CheckXInclude(source):
    if source == 'default' and Processor.g_domModule == pDomlette:
        return 1
    return source == 'pDomlette'

def GetReaderInfo(source):
    if source == 'cDomlette':
        reader = cDomlette.RawExpatReader()
        uri_func = reader.fromUri
        str_func = reader.fromString
        release = reader.releaseNode
    elif source == 'pDomlette':
        reader = pDomlette.PyExpatReader()
        uri_func = reader.fromUri
        str_func = reader.fromString
        release = reader.releaseNode
    elif source == '4DOM':
        reader = Sax2.Reader()
        uri_func = reader.fromUri
        str_func = reader.fromString
        release = reader.releaseNode
    elif source == 'minidom':
        uri_func = minidom.parse
        str_func = minidom.parseString
        release = lambda node: node.unlink()
    elif source == 'default':
        uri_func = str_func = release = None
    return uri_func, str_func, release


class FileInfo:
    def __init__(self, uri=None, string=None, validate=0, stripElements=None,
                 baseUri='', nsAware=1, xinclude=0):
        self.uri = uri
        self.string = string
        self.validate = validate
        self.stripElements = stripElements or []
        self.baseUri = baseUri
        self.nsAware = nsAware
        self.xinclude = xinclude


class XsltTester:
    """The tester will do many things to each file  all end in a XSLT process
    For Source documents
        1.  Read from URI (if possible)
        2.  Read from Stream
        3.  read from string
        4.  Read using cDomlette, pDomlette, and 4DOM (Sax,Sax2,possible HTML)
        5.  Possible use validation
    For Stylesheets
        1.  Read from URI
        2.  Read from Stream
        3.  Read from string
        4.  Use StylesheetReader and FromDocument (from cDomlette, pDomlette, and 4DOM)
        
        """

    def __init__(self,
                 source,
                 stylesheets,
                 expected,
                 cDomletteExpected=None,
                 ignorePis = 1,
                 baseUri = '',
                 topLevelParams = None,
                 writer=None,
                 extensionModules = None,
                 expectedException = None,
                 exceptionValues = None,
                 stylesheetReader = None,
                 compareFunc = TreeCompare.TreeCompare
                 ):
        self.expected = expected
        self.source = source
        self.stylesheets = stylesheets
        self.cDomletteExpected = cDomletteExpected
        self.ignorePis = ignorePis
        self.baseUri = baseUri
        self.topLevelParams = topLevelParams
        self.writer = writer
        self.extensionModules = extensionModules or []

        self.expectedException = expectedException != None
        self.exception = expectedException or Exception
        self.exceptionValues = exceptionValues or {}

        self.stylesheetReader = stylesheetReader

        self.compareFunc = compareFunc


    def test(self, tester, stream=sys.stdout):
        if tester.test_data.get('cyclops'):
            z = Cyclops.CycleFinder()
            z.install_cycle_filter(CycleFilter)
            z.run(self._test, (tester, stream))
            tester.startTest("Check for Leaks")
            z.find_cycles(1)
            try:
                tester.compare(0, len(z.cycles))
            except:
                z.show_cycles()
                tester.error('Cycles found')
            tester.testDone()
        else:
            self._test(tester, stream)
        return

    def _test(self, tester, stream):

        if not (self.source.uri or self.source.string):
            raise ValueError('Source needs either string or uri')

        title = 'Source %(source)s, Stylesheet %(stylesheet)s' % (tester.test_data)
        tester.startTest(title)

        if self.source.xinclude and not CheckXInclude(tester.test_data['source']):
            tester.warning('%s reader does not support XInclude' % 
                           tester.test_data['source'])
            tester.testDone()
            return

        for stylesheet in self.stylesheets:
            if stylesheet.xinclude and not CheckXInclude(tester.test_data['stylesheet']):
                tester.warning('%s reader does not support XInclude' % 
                               tester.test_data['stylesheet'])
                tester.testDone()
                return
        
        processor = Processor.Processor()
        processor.registerExtensionModules(self.extensionModules)

        if self.stylesheetReader:
            processor.setStylesheetReader(self.stylesheetReader)

        uri_func, str_func, release = GetReaderInfo(tester.test_data['source'])
        forceStripElements = 0
        if self.source.uri:
            if uri_func:
                source = uri_func(self.source.uri)
                run = processor.runNode
                forceStripElements = 1
            else:
                source = self.source.uri
                run = processor.runUri

        else:
            if str_func:
                source = str_func(self.source.string)
                run = processor.runNode
                forceStripElements = 1
            else:
                source = self.source.string
                run = processor.runString
        try:
            self.addStylesheets(tester, processor)
            if forceStripElements:
                result = run(source,
                             ignorePis=self.ignorePis,
                             topLevelParams=self.topLevelParams,
                             forceStripElements=1,
                             writer=self.writer)
            else:
                result = run(source,
                             ignorePis=self.ignorePis,
                             topLevelParams=self.topLevelParams,
                             writer=self.writer)
            processor.reclaim()
        except self.exception, exc:
            if not self.expectedException:
                import traceback
                traceback.print_exc(exc)
                raise
                tester.error('Unexpected exception thrown')
            for (name, value) in self.exceptionValues.items():
                # Must be two separate checks because 'value' might be None
                if not (hasattr(exc, name) and getattr(exc, name) == value):
                    import traceback
                    traceback.print_exc(exc)
                    tester.error("Expected '%s' to be '%s' on exception %s" % (name, value, exc))
        else:
            if self.expectedException:
                tester.error("Expected Exception %s not raised" % self.exception)
            tester.compare(self.expected, result, diff=1, func=self.compareFunc)

        # If the source has a release function, use it
        release and release(source)

        tester.testDone()
        return

    def addStylesheets(self, tester, processor):
        source = tester.test_data['stylesheet']
        uri_func, str_func, release = GetReaderInfo(source)

        for stylesheet in self.stylesheets:
            if stylesheet.uri:
                if uri_func:
                    node = uri_func(stylesheet.uri)
                    try:
                        processor.appendStylesheetNode(node)
                    finally:
                        release(node)
                else:
                    processor.appendStylesheetUri(stylesheet.uri)
            else:
                if str_func:
                    node = str_func(stylesheet.string)
                    try:
                        processor.appendStylesheetNode(node)
                    finally:
                        release(node)
                else:
                    processor.appendStylesheetString(stylesheet.string)
        return
