# This file is part of Pantry, command-line nutrient analysis.
# 
# Version 23, released Friday, November 09, 2007.
# 
# Copyright 2007 Omari Norman.
# 
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated
# documentation files (the "Software"), to deal in the
# Software without restriction, including without limitation
# the rights to use, copy, modify, merge, publish, distribute,
# sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
# 
# The above copyright notice and this permission notice shall
# be included in all copies or substantial portions of the
# Software.
# 
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
# PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
# OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
# 
# Pantry's "built-in" XML validator is a modified version of
# xmlproc.  xmlproc is Copyright 1998-2000 by Lars Marius
# Garshol, Oslo, Norway.
# 
# All Rights Reserved
# 
# Permission to use, copy, modify, and distribute this
# software and its documentation for any purpose and without
# fee is hereby granted, provided that the above copyright
# notice appear in all copies and that modified copies are
# clearly marked as such.
# 
# LARS MARIUS GARSHOL DISCLAIMS ALL WARRANTIES WITH REGARD TO
# THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL LARS MARIUS
# GARSHOL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL
# DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF
# USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

from __future__ import division
_BUILTIN = 1
_PYXML = 2

def xmlCheck(data, dtdStr):
    """ Check data (a string) for XML validity. """

    import subprocess
    import os
    import errno

    try:
        p = subprocess.Popen(['xmllint', '--version'],
                stderr = subprocess.PIPE, stdout = subprocess.PIPE)
    except OSError, e:
        if e.errno != errno.ENOENT:
            raise
        try:
            from xml.parsers import xmlproc
        except ImportError:
            _pyvalid(data, dtdStr, _BUILTIN)
        else:
            _pyvalid(data, dtdStr, _PYXML)
    else:
        _xmllint(data, dtdStr)

def _pyvalid(data, dtdStr, whichType):
    """ Validate a filename using dtdStr and xmlproc.

    if whichType is _BUILTIN, uses builtin validator.
    If whichType is _PYXML, uses PyXML. """

    if whichType == _BUILTIN:
        from pantry.lib.xmlproc import xmlproc
        from pantry.lib.xmlproc import xmlval
        from pantry.lib.xmlproc import xmldtd
        from pantry.lib.xmlproc import xmlapp

    elif whichType == _PYXML:
        from xml.parsers.xmlproc import xmlproc
        from xml.parsers.xmlproc import xmlval
        from xml.parsers.xmlproc import xmldtd
        from xml.parsers.xmlproc import xmlapp
    else:
        raise Exception

    class CustomError(xmlapp.ErrorHandler):
        def fatal(self, msg):
            if self.locator == None:
                raise BadXMLError('error: %s' % msg)
            else:
                errorText = ('Line: %s column: %s error: %s' %
                    (self.locator.get_line(), self.locator.get_column(), msg))
                raise BadXMLError(errorText)

    dtd = xmldtd.load_dtd_string(dtdStr)
    parser = xmlproc.XMLProcessor()
    parser.set_application(xmlval.ValidatingApp(dtd, parser))
    parser.dtd = dtd
    parser.ent = dtd
    parser.set_error_handler(CustomError(parser))
    parser.parse_string(data)

def _xmllint(data, dtdStr):
    """ Validate filename using dtdStr and xmllint. """

    import tempfile
    import subprocess
    import os

    dtdFile = tempfile.NamedTemporaryFile()
    dtdFile.write(dtdStr)
    dtdFile.flush()
    cmd = [ 'xmllint', '--noout', '--dtdvalid', dtdFile.name, '-' ]
    sp = subprocess.Popen(cmd, stderr = subprocess.PIPE, 
            stdin = subprocess.PIPE)
    sp.stdin.write(data)
    sp.stdin.close()
    output = sp.stderr.read()
    if output:
        # trim last line of xmllint output, which is not helpful
        output = ''.join(output.splitlines()[:-1]) 
        raise BadXMLError(output)

def getRootElement(filename):
    """ Returns unicode of the xml document's root element. """

    from xml.sax import make_parser
    from xml.sax.handler import ContentHandler
    from xml.sax._exceptions import SAXParseException

    class Envoy(object):
        pass

    class AllDone(Exception):
        pass

    class FileHandler(ContentHandler):
        def __init__(self, envoy):
            self.envoy = envoy

        def startElement(self, name, attrs):
            self.envoy.rootName = name
            raise AllDone

    envoy = Envoy()
    parser = make_parser()
    parser.setContentHandler(FileHandler(envoy))
    try:
        parser.parse(filename)
    except AllDone:
        pass
    except SAXParseException:
        raise NoRootElementError
    except ValueError:
        raise FileNotFoundError

    return envoy.rootName

class Error(Exception):
    pass

class BadXMLError(Error):
    """ XML validator aborted with error output. """
    def __init__(self, validatorOutput):
        self.msg = [u'Validator output:']
        self.msg.append(validatorOutput)

class NoRootElementError(Error):
    """ Could not determine document root element. """
    def __init__(self):
        self.msg = [u'Could not determine document root element.']

class FileNotFoundError(Error):
    """ XML file not found. """
    def __init__(self):
        self.msg = [u'File not found']
