########################################################################
#
# File Name:            XInclude.py
#
# Documentation:        http://docs.ftsuite.com/Lib/XInclude.py.html
#
"""
A Utility to expand XInclude
WWW: http://4suite.org/XInclude         e-mail: support@4suite.org

Implements http://www.w3.org/TR/2001/WD-xinclude-20010516/
Copyright (c) 2000-2001 Fourthought Inc, USA.   All Rights Reserved.
See  http://4suite.org/COPYRIGHT  for license and copyright information
"""
#'
import os,urllib
import xml.dom
from Ft import XPointer
from Ft.Lib import Uri
from xml import xpath
from xml.xpath import Context
g_includeExpr = xpath.Compile("//xi:include")

XINCLUDE_NAMESPACE = 'http://www.w3.org/2001/XInclude'

NONNORMATIVE_SCHEMA_FOR_XINCLUDE_ELEMENT = """
<xs:schema
  xmlns:xs="http://www.w3.org/2001/XMLSchema"
  xmlns:xi="http://www.w3.org/2001/XInclude"
  targetNamespace="http://www.w3.org/2001/XInclude">

  <xs:element name="include">
    <xs:complexType mixed="true">
      <xs:attribute name="href" type="xs:anyURI" use="required"/>
      <xs:attribute name="parse">
        <xs:simpleType>
          <xs:restriction base="xs:string">
            <xs:enumeration value="xml"/>
            <xs:enumeration value="text"/>
          </xs:restriction>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="encoding" type="xs:string"/>
      <xs:anyAttribute />
    </xs:complexType>
  </xs:element>

</xs:schema>
"""

class XIncludeException(Exception):

    CIRCULAR_INCLUDE_ERROR = 1
    RESOURCE_ERROR = 2

    def __init__(self, code, extra=None):
        # args ::= (code, object?)?
        msg = g_errorMessages[code]
        if extra:
            msg = msg + '\n' + str(extra)
        Exception.__init__(self, msg)
        self.code = code
        self.msg = msg

g_errorMessages = {
    XIncludeException.CIRCULAR_INCLUDE_ERROR: "Circular Inclusion",
    XIncludeException.RESOURCE_ERROR: "Invalid resource, or not well-formed XML",
    }


def ProcessIncludesFromUri(uri, reader=None, visited=None, baseUri='',
                           uriResolver=None):
    reader = reader or PyExpatReader()
    if uriResolver:
        reader.uriResolver = uriResolver
    node = reader.fromUri(uri, baseUri)
    return ProcessIncludesFromNode(node, reader, visited, baseUri)


def ProcessIncludesFromString(st, reader=None, visited=None, baseUri=''):
    reader = reader or PyExpatReader()
    node = reader.fromString(st, baseUri)
    return ProcessIncludesFromNode(node, reader, visited, baseUri)


def ProcessIncludesFromNode(node, reader=None, visited=None, baseUri=''):
    nss={'xi': XINCLUDE_NAMESPACE}
    reader = reader or PyExpatReader()
    con = Context.Context(node, processorNss=nss)
    nodes = g_includeExpr.evaluate(con)
    visited = visited or []
    for n in nodes:
        _ProcessNode(n, visited, reader, baseUri)
    return node

def _ProcessNode(node, visited, reader, baseUri):
    if (node.namespaceURI, node.localName) != (XINCLUDE_NAMESPACE, 'include'):
        return
    href = node.getAttributeNS('', 'href')
    if not href:
        raise XIncludeException(XIncludeException.XINCLUDE_MISSING_HREF)
    if href in visited:
        raise XIncludeException(XIncludeException.CIRCULAR_INCLUDE_ERROR)
    parse = node.getAttributeNS('', 'parse')
    if not parse or parse=='xml':
        #Use XPointer to load the new
        #FIXME catch parse exception
        replaceNode = XPointer.SelectUri(href, reader=reader, base=baseUri)
        replaceNode = ProcessIncludesFromNode(replaceNode, visited=visited,
                                              reader=reader)
        if replaceNode.nodeType == xml.dom.Node.DOCUMENT_NODE:
            replaceNode = replaceNode.documentElement
        visited.append(href)
    else:
        if os.path.exists(href):
            st = open(href)
        else:
            st = urllib.open(href)
        replaceNode = node.ownerDocument.createTextNode(st.read())
        st.close()

    node.parentNode.replaceChild(replaceNode,node)
    return

    
from Ft.Lib.pDomlette import PyExpatReader
