#!/usr/bin/python2.3
# -*- coding: utf-8 -*-
"""
$Id: validator_class.py,v 1.54 2007/05/30 14:05:10 dom Exp $

License
-------
Copyright (c) 2006 World Wide Web Consortium, (Massachusetts
Institute of Technology, European Research Consortium for Informatics
and Mathematics, Keio University). All Rights Reserved. This work is
distributed under the W3C Software License [1] in the hope that it
will be useful, but WITHOUT ANY WARRANTY; without even the implied
warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

[1] http://www.w3.org/Consortium/Legal/copyright-software

"""

from validator import testcase
from xml.sax.handler  import ContentHandler
import socket

class SaxDistributor(ContentHandler):
    """ Distributes sax events to a list of objects """
    def __init__(self,objectsList,uri,profile):
        ContentHandler.__init__(self)
        from validator.testcase import XMLBasedTestCase
        self.uri = uri
        self._objectsList = []
        self._observations = testcase.TestResults()
        for i in objectsList:
            # for descendant of XMLBasedTestCase, we ensure
            # that we parse the document only once
            try:
                if isinstance(i,XMLBasedTestCase):
                    i.setup(uri)
                    self._objectsList.append(i)
                else:
                    self._observations.extend(i.run(uri,profile))
            except Exception, msg:
                self._observations.append(testcase.Observation("EX1",testcase.Location(self.uri),{"msg":msg,"bp":i.BpId}))
                    

    def setDocumentLocator(self, locator):
        for i in self._objectsList:
            i.setDocumentLocator(locator)

    def startDocument(self):
        for i in self._objectsList:
            i.startDocument()

    def startElement(self,name,attrs):
        for i in self._objectsList:
            try:
                i.startElement(name,attrs)
            except Exception, msg:
                self._observations.append(testcase.Observation("EX1",testcase.Location(self.uri),{"msg":msg,"bp":i.BpId}))
                

    def characters(self,content):
        for i in self._objectsList:
            i.characters(content)

    def endElement(self,name):
        for i in self._objectsList:
            i.endElement(name)

    def endDocument(self):
        for i in self._objectsList:
            try:
                i.endDocument()
            except Exception, msg:
                self._observations.append(testcase.Observation("EX1",testcase.Location(self.uri),{"msg":msg,"bp":i.BpId}))
            self._observations.extend(i.getObservations())

    def getObservations(self):
        return self._observations

class Validator:
    def __init__(self,xmlprofile):
        from validator.utils import Profile
        self._testcases = []
        self._headers = None
        self._content = None
        self._sortedResults = {"pass":[],"fail":[],"na":[],"info":[],"warning":[],"none":[],"error":[]}
        self._profile = Profile(xmlprofile)

        # list of modules imported in the validator
        # the order matters
        modulesList = {
            "access_key":"AccessKeysTestCase",
            "characterencoding":"CharacterEncodingTestCase",
            "contentformat":"ContentFormatTestCase",
            "external_resources":"ExternalResourcesTestCase",
            "form_controls":"FormControlsTestCase",            
            "image_map":"ImageMapTestCase",
            "images":"ImagesTestCase",
            "link_target":"LinkTargetTestCase",
            "measures":"MeasuresTestCase",
            "minimizemarkup":"MinimizeMarkupTestCase",
            "non_text":"NonTextTestCase",
            "object_scripts":"ObjectScriptTestCase",
            "page_size":"PageSizeTestCase",
            "page_title":"PageTitleTestCase",
            "pop_up":"PopUpTestCase",
            "refresh_redirect":"RefreshRedirectTestCase",
            "scrolling":"ScrollingTestCase",
            "tables":"TablesTestCase",
            "stylesheets":"StyleSheetsTestCase",
            "structure":"StructureTestCase",
            "caching":"CachingTestCase"
            }
#        modulesList = {"characterencoding":"CharacterEncodingTestCase"}
        for modname,classname in modulesList.iteritems():
            mod = __import__(modname)
            self._testcases.append(getattr(mod,classname)())
        self._observations=testcase.TestResults()

    def _sendAuthChallenge(self,www_authenticate):
        import sys
        print "Status: 401 proxy authorization required"
        print "WWW-Authenticate: %s" % www_authenticate
        print
        sys.exit()


    def run(self,uri,output="xhtml"):
        from validator.utils import HTTPRequest, HttpError
        import socket
        self.uri=uri
        h = HTTPRequest
        self._interrupted = False
        credentials = None
        if os.environ.has_key('HTTP_AUTHORIZATION') and os.environ['HTTP_AUTHORIZATION'] and os.environ['HTTP_AUTHORIZATION'].split(" ")[1]:
            credentials = {}
            import base64
            cred_tmp = base64.decodestring(os.environ['HTTP_AUTHORIZATION'].split(" ")[1]).split(":")
            credentials["login"] = cred_tmp[0]
            credentials["password"] = cred_tmp[1]
        try:
            self._headers,self._content = h.http_request(uri,"GET",None,credentials)
        except socket.error, msg:
            self._observations.append(testcase.Observation("HT1",testcase.Location(uri),{"msg":msg,"method":"GET","uri":uri}))
        except Exception, msg:
            self._observations.append(testcase.Observation("HT1",testcase.Location(uri),{"msg":msg,"method":"GET","uri":uri}))
        if not self._headers:
            self._interrupted = True
        elif not self._headers.status in [200,304]:
            self._interrupted = True
            # if headers[0].status==401 and not credentials and headers[0].has_key("www-authenticate"):
            # self._sendAuthChallenge(headers[0]["www-authenticate"])
            # else:
            self._observations.append(testcase.Observation("HT3",testcase.Location(uri),{"code":self._headers.status,"method":"GET","uri":uri}))
            

        # if we didn't get a proper result from our HTTP request,
        # we may as well stop now
        if not "HT1" in self._observations and not "HT3" in self._observations:
            from cStringIO import StringIO
            fp = StringIO(self._content)
            lines = fp.readlines()
            fp.reset()

            # We first check if the document is well-formed
            # otherwise, we won't be able to do the other tests
            
            from validity import ValidityTestCase
            validity = ValidityTestCase()
            self._observations.extend(validity.run(uri))
            if "VA2" in self._observations:
                import tidy
                options=dict(output_xhtml=1,tidy_mark=0)
                # for some reasons, calling str() directly on the result of
                # tidy.parseString raises an error; thus calling __str__
                self._content = tidy.parseString(self._content,**options).__str__()
                fp = StringIO(self._content)
                lines = fp.readlines()
                fp.reset()

            self._interrupted = False
            from xml.sax import make_parser
            import xml.sax.handler
            p = make_parser()
            p.setFeature(xml.sax.handler.feature_validation,0)
            p.setFeature(xml.sax.handler.feature_external_ges,0)
            p.setFeature(xml.sax.handler.feature_external_pes,0)
            s = SaxDistributor(self._testcases,uri,self._profile)
            p.setContentHandler(s)
            try:
                p.parse(fp)
            except Exception, msg:
                self._observations.append(testcase.Observation("EX3",testcase.Location(self.uri),{"msg":msg}))
                self._interrupted = True
            self._observations.extend(s.getObservations())
        self._prepareReport()
        self.displayResults(output)

            
    def _prepareReport(self):
        from validator.messages import messages
        from validator.utils import Profile
        from cStringIO import StringIO
        lines = []
        if self._content:
            fp = StringIO(self._content)
            lines = fp.readlines()
        p = Profile("ddc-mok-profile.xml")
        for o in self._observations:
            if messages.has_key(o.id):
                info = o.additionalInfo
                info["location"] = str(o.location)
                if isinstance(o.location,testcase.LineColumnLocation):
                    info["link_or_page"] = "resource linked at %s" % (str(o.location))
                else:
                    info["link_or_page"] = "page"
                info["profilename"]=self._profile.data.name.PCDATA
                
                bplist = []
                if o.source:
                    for bp in o.source:
                        bpname = bp
                        bpwords = bp.split("_")
                        bpwiki = "CategoryBp"
                        for w in bpwords:
                            bpwiki = bpwiki + w[0] + w[1:].lower()
                        bplist.append({"bpwiki":bpwiki,"bpname":bpname})
                if hasattr(self._profile.data,"observations") and hasattr(self._profile.data.observations,o.id):
                    data = {"msg":messages[o.id] % info,"location":o.location}
                    # extracting HTML source code for display
                    source = None
                    if isinstance(o.location,testcase.LineColumnLocation):
                        try:
                           source = lines[int(o.location.line)-1][max(0,int(o.location.column)-20):min(len(lines[int(o.location.line)-1])-1,int(o.location.column)+40)].encode('utf-8')
                           if len(lines[int(o.location.line)-1]) - 1 < int(o.location.column) + 40:
                               source = source + " " + lines[int(o.location.line)][0:int(o.location.column)+40 - len(lines[int(o.location.line)-1])].encode('utf-8')
                        except Exception, inst:
                            pass
                        if source:
                            source = "…" + source.strip() + "…"
                    if getattr(self._profile.data.observations,o.id).level in ("fail","warning","error"):
                        data["Bp_list"]=bplist
                        if source:
                            data["source"]=source
                    try:
                        self._sortedResults[getattr(self._profile.data.observations,o.id).level].append(data)

                    except:
                        print "Error while retrieving message %s" % (o.id)

                else:
                    print "Unknown level for message %s" % (o.id)
            else:
                print o.id + " has no associated message"

    def displayResults(self,output="xhtml"):
        import os
        from htmltmpl import TemplateManager, TemplateProcessor
        tproc = TemplateProcessor()
        tproc.set("uri",self.uri)
        if output=="unicorn":
            template = TemplateManager().prepare("templates/results-unicorn.tmpl")
        else:
            template = TemplateManager().prepare("templates/results.tmpl")
            if output=="xhtml":
                tproc.set("xhtml",1)
            else:
                tproc.set("xhtml",0)
        if "PT1" in self._observations:
            title = self._observations.getObservationById("PT1").additionalInfo["title"]
            tproc.set("testedpage_title",title.encode('utf-8'))
        if self._interrupted:
            tproc.set("interrupted",1)
        # sorting the results on the location of the observation
        try:
            for level in ["error","fail","warning","info","pass","na"]:
                # could use operator.attrgetter() if python2.4
                self._sortedResults[level].sort(lambda x, y: cmp(x["location"], y["location"]))
        except:
            pass
        tproc.set("have_error",len(self._sortedResults["error"]))
        tproc.set("have_failed",len(self._sortedResults["fail"]))
        tproc.set("have_warning",len(self._sortedResults["warning"]))
        tproc.set("have_info",len(self._sortedResults["info"]))
        tproc.set("have_pass",len(self._sortedResults["pass"]))
        tproc.set("have_na",len(self._sortedResults["na"]))
        tproc.set("Failed_list",self._sortedResults["fail"])
        tproc.set("Error_list",self._sortedResults["error"])
        tproc.set("Warning_list",self._sortedResults["warning"])
        tproc.set("Info_list",self._sortedResults["info"])
        tproc.set("Na_list",self._sortedResults["na"])
        tproc.set("Pass_list",self._sortedResults["pass"])
        print tproc.process(template)
            

import unittest

class Tests(unittest.TestCase):
    def testValidator(self):
        v = Validator("ddc-profile.xml")
        v.run("http://w3.org/mobile")


def serveRequest():
    import cgi
    fields = cgi.FieldStorage()
    acceptHeaders = "application/xhtml+xml"
    UA = "Foo"
    if os.environ.has_key("HTTP_ACCEPT"):
        acceptHeaders = os.environ["HTTP_ACCEPT"]
    if os.environ.has_key("HTTP_USER_AGENT"):
        UA = os.environ["HTTP_USER_AGENT"]
    from validator.utils import Conneg
    c = Conneg(acceptHeaders)
    xhtml = (c.preferred(["application/xhtml+xml","text/html"])=="application/xhtml+xml" and not "MSIE" in UA)

    if not fields.has_key('uri'):


        from htmltmpl import TemplateManager, TemplateProcessor
        template = TemplateManager().prepare("templates/ui.tmpl")
        tproc = TemplateProcessor()
        if xhtml:
            tproc.set("xhtml",1)
        else:
            tproc.set("xhtml",0)        
        
        tproc.set("page_title","W3C Mobile Web Best Practices checker")
        print tproc.process(template)
    else:
        # User agent string should be taken from a place shared with utils.py
        if os.environ.has_key("HTTP_USER_AGENT") and os.environ["HTTP_USER_AGENT"]=="W3C-mobileOK/DDC-1.0 (see http://www.w3.org/2006/07/mobileok-ddc)":
            print "403 Forbidden"
            print "Content-Type: text/plain"
            print
            print "Checker won't respond on requests from itself to avoid loops"
        else:
            v = Validator("ddc-mok-profile.xml")
            uri = fields["uri"].value
            # if the scheme is omitted, we assume http://
            if not ":" in uri:
                uri = "http://%s" % uri
            v.run(uri,xhtml)    


def _test():
    unittest.main()

if __name__ == '__main__':
    import os
    if os.environ.has_key('SCRIPT_NAME'):
        serveRequest()
    else:
        _test()
    #import hotshot
    #prof = hotshot.Profile("hotshot_stats")
    #prof.runcall(_test)
    #prof.close()

