########################################################################
# $Header: /var/local/cvsroot/4Suite/Ft/Server/Common/DocumentReference.py,v 1.16 2004/08/30 22:52:17 mbrown Exp $
"""
An intelligent reference to a document within or external to the repo

Copyright 2004 Fourthought, Inc. (USA).
Detailed license and copyright information: http://4suite.org/COPYRIGHT
Project home, documentation, distributions: http://4suite.org/
"""

from Ft.Lib import UriException, Uri
from Ft.Lib.Uri import BASIC_RESOLVER
from Ft.Xml import InputSource, XPath
from Ft.Xml.Domlette import NonvalidatingReader
from Ft.Xml.Xslt import StylesheetReader
from Ft.Server import FTSERVER_NAMESPACE, FTSS_URI_SCHEME
from Ft.Server.Server import FtServerServerException, Error
from Ft.Server.Common import XmlLib, ResourceTypes
from Ft.Server.Server.Drivers import FtssInputSource

import SchematronStylesheet


class DocumentReferenceType:
    INTERNAL = 1
    EXTERNAL = 2
    STRING = 3


class DocumentReference:
    """
    Abstract base class for the various types of Document References
    """
    def __init__(self, rType):
        self.referenceType = rType

    def toDom(self, baseObject):
        """
        Overidden by subclasses for implementation to convert the
        reference to a DOM node
        """
        raise FtServerServerException(Error.INTERNAL_ERROR,
            message = "Must override toDom in %s" % str(self))
    def toStylesheet(self, baseObject):
        """
        Overidden by subclasses for implementation to convert the
        reference to a stylesheet
        """
        raise FtServerServerException(Error.INTERNAL_ERROR,
            message = "Must override toStylesheet in %s" % str(self))
    def toSchematron(self, baseObject):
        """
        Overidden by subclasses for implementation to convert the
        reference to a schematron instance
        """
        raise FtServerServerException(Error.INTERNAL_ERROR,
            message = "Must override toSchematron in %s" % str(self))


class StringDocumentReference(DocumentReference):
    """
    Reference to a string array
    """
    def __init__(self, data, baseUri):
        self.data = data
        self.baseUri = baseUri
        DocumentReference.__init__(self, DocumentReferenceType.STRING)

    def toDom(self, baseObject):
        return FtssInputSource.NonvalidatingReader.parseString(self.data,
                                                               self.baseUri,
                                                               baseObject._driver)

    def toStylesheet(self, baseObject):
        isrc = FtssInputSource.FtssInputSourceFactory.fromString(self.data,
                                                                 self.baseUri,
                                                                 baseObject._driver)

        reader = StylesheetReader.StylesheetReader()
        return reader.fromSrc(isrc)

    def toSchematron(self, baseObject):
        baseUri = self.baseUri or baseObject.getPath().absolutePath
        return SchematronStylesheet.ParseSchematron(self.data, baseUri, baseObject._driver)


class InternalDocumentReference(DocumentReference):
    """
    Reference to an internal resource
    """
    def __init__(self, uri):
        self.uri = uri
        DocumentReference.__init__(self, DocumentReferenceType.INTERNAL)

    def toDom(self, baseObject):
        return baseObject.fetchResource(self.uri).asDom()

    def toStylesheet(self, baseObject):
        return baseObject.fetchResource(self.uri).asStylesheet()

    def toSchematron(self, baseObject):
        return baseObject.fetchResource(self.uri).asSchematron()


class ExternalDocumentReference(DocumentReference):
    """
    Reference to an external (non-repo) resource.
    """
    def __init__(self, uri):
        self.uri = uri
        DocumentReference.__init__(self, DocumentReferenceType.EXTERNAL)

    def toDom(self, baseObject):
        """
        Attempts to parse the external resource as XML, returning a
        Domlette document node. Parsing is done with the standard
        (repo-unaware) non-validating reader.
        """
        try:
            src = BASIC_RESOLVER.resolve(self.uri)
        except UriException:
            raise FtServerServerException(Error.RESOURCE_NOT_FOUND, uri=self.uri)

        data = src.read()
        del src # seems to be necessary to free the stream on Windows

        isrc = InputSource.DefaultFactory.fromString(data, self.uri)
        return NonvalidatingReader.parse(isrc)

    def toStylesheet(self, baseObject):
        """
        Attempts to parse the external resource as XML, returning a
        stylesheet document node. Parsing is done with the standard
        (repo-unaware) stylesheet reader. Assumes the resource really
        is XSLT.
        """
        try:
            src = BASIC_RESOLVER.resolve(self.uri)
        except UriException:
            raise FtServerServerException(Error.RESOURCE_NOT_FOUND, uri=self.uri)

        data = src.read()
        del src # seems to be necessary to free the stream on Windows

        isrc = InputSource.DefaultFactory.fromString(data, self.uri)
        reader = StylesheetReader.StylesheetReader()
        return reader.fromSrc(isrc)

    def toSchematron(self, baseObject):
        """
        Attempts to parse the external resource as XML, returning a
        stylesheet document node. Parsing is done with the standard
        (repo-unaware) stylesheet reader. The stylesheet represents
        the result of applying Rick Jelliffe's Schematron preprocessor
        to the source document, resulting in a new stylesheet that can
        be used for validation.
        """
        try:
            src = BASIC_RESOLVER.resolve(self.uri)
        except UriException:
            raise FtServerServerException(Error.RESOURCE_NOT_FOUND, uri=self.uri)

        data = src.read()
        del src # seems to be necessary to free the stream on Windows

        return SchematronStylesheet.ParseSchematron(data, self.uri,
                                                    baseObject._driver,
                                                    external=1)



def _Serialize(doc, dr):
    ref = doc.createElementNS(FTSERVER_NAMESPACE, 'ftss:DocumentReference')
    if dr.referenceType == DocumentReferenceType.STRING:
        ref.setAttributeNS(None, 'type', 'STRING')
        ref.setAttributeNS(None, 'baseUri', dr.baseUri)
        ref.appendChild(doc.createTextNode(dr.data))
    elif dr.referenceType == DocumentReferenceType.INTERNAL:
        ref.setAttributeNS(None, 'type', 'INTERNAL')
        ref.appendChild(doc.createTextNode(dr.uri))
    elif dr.referenceType == DocumentReferenceType.EXTERNAL:
        ref.setAttributeNS(None, 'type', 'EXTERNAL')
        ref.appendChild(doc.createTextNode(dr.uri))
    return ref


_documentReferenceTypeExpression = XPath.Compile('string(@type)')
_documentReferenceTextChildExpression = XPath.Compile('string(text())')
_documentReferenceStringBaseExpression = XPath.Compile('string(@baseUri)')
def _Deserialize(con):
    val_doc_type = _documentReferenceTypeExpression.evaluate(con)
    val_doc_type = val_doc_type or 'STRING'
    if val_doc_type == 'INTERNAL':
        text = _documentReferenceTextChildExpression.evaluate(con)
        docRef = InternalDocumentReference(text)
    elif val_doc_type == 'EXTERNAL':
        text = _documentReferenceTextChildExpression.evaluate(con)
        docRef = ExternalDocumentReference(text)
    else:
        stringBase = _documentReferenceStringBaseExpression.evaluate(con)
        text = _documentReferenceTextChildExpression.evaluate(con)
        docRef = StringDocumentReference(text, stringBase)
    return docRef
