# Copyright (c) 2000-2001 LOGILAB S.A. (Paris, FRANCE).
# http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation; either version 2 of the License, or (at your option) any later
# version.
#
# This program is distributed 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. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

"""Library of functions used by modules"""

__revision__ = "$Id: lib.py,v 1.72 2002/02/14 12:57:48 alf Exp $"

import sys, string, re, time, os, os.path
from cStringIO import StringIO
import urllib, urlparse
from xml.dom.ext import Print, PrettyPrint, StripXml
from narval.utils.BinaryStore import BinaryStore
# dom stuff from 4Thought
try:
    from Ft.Xml.XPath import Compile#, CreateContext
    from Ft.Xml.XPath.Context import Context
    from Ft.Xml.XPath.XPathParserBase import SyntaxException
except:
    from xml.xpath import Compile#, CreateContext #, Util
    from xml.xpath.Context import Context
    from xml.xpath.XPathParserBase import SyntaxException
from narval.debug import debug_msg
from narval import config
from narval.dom import DOCUMENT_NODE, DomReader

## constants ####################################################################

## These constants are to be used to pass boolean arguments to functions,
## or for return values, not for testing !
true = TRUE  = 1
false = FALSE = 0

## DEEP_COPY is used with Element.cloneNode()
DEEP_COPY = 1

## NAMESPACES
from xml.dom import XMLNS_NAMESPACE as XMLNS_NS
from Ft.Xml.Xslt import XSL_NAMESPACE as XSL_NS
NO_NS = None
## That's Narval's namespace
AL_NS = 'http://www.logilab.org/namespaces/Narval/1.2' 

## user encoding (see narvalrc)
ENCODING = 'UTF-8'
def get_encoding():
    return ENCODING
def set_encoding(e):
    global ENCODING
    ENCODING = e 

## select(s) ####################################################################

# locks
class dummy_lock:
    def release(self):
        pass
    def acquire(self):
        pass
    
#xpath_lock = threading.Lock()
xpath_lock = dummy_lock()

class SelectException(Exception) :
    """SelectException is thrown when a select_one/many fails"""
    pass

def select_many(node, str, list=None, nss={'al': AL_NS, 'xsl': XSL_NS}) :
    assert str, 'select with empty string %s'%(`str`)
    rep = []
    if list :
        for l in list :
            rep = rep + select_many(node, str%(l), nss=nss)
    else :
        try :
            try:
                xpath_lock.acquire()
                try:
                    path = Compile(str)
                except :
                    print '--------------> Syntax error in %s'%str
                    xpath_lock.release()
                    time.sleep(3)
                    xpath_lock.acquire()
                    path = Compile(str)
                    print 'second chance', path

                if not path:
                    print '--------------> Compilation of %s failed!'%str
                    xpath_lock.release()
                    time.sleep(3)
                    xpath_lock.acquire()
                    path = Compile(str)
                    print 'last chance !',path
                context = Context(node, processorNss=nss)
                rep = path.evaluate(context)
            except KeyError:
                print 'Caught KeyError in select_many()'
                rep = []
        finally :
            xpath_lock.release()
    return rep

def select_one(node, str, nss={'al': AL_NS, 'xsl': XSL_NS}) :
    """return child of requested tag. If none or more than one raise exception
    """
    child = select_many(node, str, nss=nss)
    if len(child) != 1 :
        raise SelectException("select_one: found %i element(s) instead of 1."\
                              % (len(child)))
    return child[0]


## charset conversions #########################################################
# seems to be broken. Bug submitted to pyxml. Rollback to previous version.
if (sys.hexversion >> 8) > 0x10502:
    import codecs
    def ensure_utf8str(text):
        # Assuming ISO coming in.  Is this right?
        deciso = codecs.lookup("iso-8859-1")[1]
        encutf = codecs.lookup("utf-8")[0]
        # Return UTF-8 as a string (not unicode object)
        return str(encutf(deciso(text)[0])[0])

    from types import UnicodeType
    def utf8_to_code(text, encoding):
        encoder = codecs.lookup(encoding)[0] # encode,decode,reader,writer
        if type(text) is not UnicodeType:
            text = unicode(text, "utf-8")
        return encoder(text)[0] # result,size

    def utf8_to_latin1(s):
        return utf8_to_code(s,'iso-8859-1')

    def latin1_to_utf8(s):
        deciso = codecs.lookup("iso-8859-1")[1]
        encutf = codecs.lookup("utf-8")[0]
        return encutf(deciso(s)[0])[0]

else:
    def ensure_utf8str(text):
        return text
    
    from xml.unicode.utf8_iso import utf8_to_code, code_to_utf8
    def utf8_to_latin1(s):
        buff = StringIO()
        while s:
            try:
                head,s = utf8_to_code(1,s)
            except Exception,e:
                from traceback import print_exc
                print_exc()
                head = ''
                s = s[1:]
            buff.write(head)
        ans = buff.getvalue()
        buff.close()
        return ans

    def latin1_to_utf8(s):
        buff = StringIO()
        for c in s:
            try:
                cv = code_to_utf8(1,c)
            except Exception,e:
                from traceback import print_exc
                print_exc()
                cv = ''
            buff.write(cv)
        ans = buff.getvalue()
        buff.close()
        return ans


## Extension modules loading ###################################################
def load_extension_module(module_dir,module_name):
    from imp import find_module,load_module
    NARVAL_HOME = config.get_home()
    if NARVAL_HOME not in sys.path:
        sys.path.insert(0,NARVAL_HOME)
    mp_file,mp_filename,mp_desc = find_module('extensions')
    ext_mod = load_module('extensions',mp_file,mp_filename,mp_desc)
    if module_dir:
        mp_file,mp_filename,mp_desc = find_module(module_dir, ext_mod.__path__)
        ext_mod = load_module(module_dir,mp_file,mp_filename,mp_desc)
    m_file,m_filename,m_desc = find_module(module_name, ext_mod.__path__)
    return load_module(module_name, m_file, m_filename, m_desc)


## domtree manipulation functions (creation, suppression, impression) ##########
def error(doc, type=None, msg=None):
    from xml.sax.saxutils import escape
    err = doc.createElementNS(AL_NS, 'al:error')
    if type is not None:
        err.setAttributeNS(NO_NS,'type', escape(type))
    if msg is not None:
        msg = escape(unicode(msg, ENCODING))
        err.appendChild(doc.createTextNode(msg))
    return err
        
def read_domtree_from_file(filepath,owner=None,reader=None,
                           validate=0,keepAllWs=0,catName=None):
    """ Wrapper function for the XMLReader class """
    filepath = expand_vars(filepath)
    file = open(filepath,'r')
    d = read_domtree_from_stream(file,owner,reader,
                                 validate,keepAllWs,catName)
    file.close()
    return d

def read_domtree_from_uri(uri,owner=None,reader=None,
                           validate=0,keepAllWs=0,catName=None):
    """ Wrapper function for the XMLReader class """
    reader = reader or get_reader(owner,validate,keepAllWs,catName)
    url, url_tuple = normalize_url(uri)
    if url_tuple[0] == 'file' :
	if url_tuple[2][0]=='/' and url_tuple[2][2] == ':':
            fname = url_tuple[2][1:] # XXX is that for windows paths?
        else:
            fname = url_tuple[2]
        f = open(fname)
    else:
        f = urllib.urlopen(url)
    d = read_domtree_from_stream(f,owner,reader,
                                 validate,keepAllWs,catName)
    f.close()
    return d

def read_domtree_from_stream(fp,owner=None,reader=None,
                           validate=0,keepAllWs=0,catName=None):
    """Wrapper function for the XMLReader class """
    reader = reader or get_reader(owner, validate, keepAllWs, catName)
    fragment = reader.fromStream(fp, ownerDoc=owner)
    return StripXml(fragment)

def read_domtree_from_string(string,owner=None,reader=None,
                             validate=0,keepAllWs=0,catName=None):
    reader = reader or get_reader(owner,validate,keepAllWs,catName)
    fragment = reader.fromString(string,ownerDoc=owner)
    return StripXml(fragment)


def write_domtree_to_file(tree, filepath, encoding=None):
    """ Wrapper function for the XMLWriter class """
    if not encoding:
        encoding = ENCODING
    filepath = expand_vars(filepath)
    f=open(filepath,'w')
    if tree.nodeType != DOCUMENT_NODE :
        f.write('<?xml version="1.0" encoding="%s"?>\n'%encoding)
    PrettyPrint(tree,f,encoding=encoding)


def write_domtree(tree, encoding=None) :
    """ Wrapper function for the XMLWriter class """
    if not encoding:
        encoding = ENCODING
    if tree.nodeType != DOCUMENT_NODE :
        print('<?xml version="1.0" encoding="%s"?>'%encoding)
    PrettyPrint(tree,encoding=encoding)

def domtree_to_string(element,pretty=true,encoding='UTF-8') :
    return domtrees_to_string([element],pretty,encoding)

def domtrees_to_string(elements,pretty=true,encoding='UTF-8') :
    s = StringIO()
    s.write('<?xml version="1.0" encoding="%s"?>\n'%encoding)
    if pretty:
        for element in elements:
            PrettyPrint(element,s,encoding=encoding)
    else :
        for element in elements :
            Print(element,s,encoding=encoding)
    str = s.getvalue()
    s.close()
    return str

def release_domtree(node):
    print 'WARNING: call release_dom_tree'
    pass

def get_reader(owner, validate=0, keepAllWs=0, catName=None):
    # FIXME: (cDomlette reader args and see narval.dom.__init__)
    return DomReader()#validate, keepAllWs, catName)


## expand $NARVAL_HOME and $HOME ##############################################
def expand_vars(path) :
    path = string.replace(path, '$NARVAL_HOME', config.get_home())
    home = os.getenv('HOME') or os.getenv('USERPROFILE') or ''
    if not home and sys.platform == 'win32':
        key_name = "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders"
        try:
            key = _winreg.OpenKey(_winreg.HKEY_CURRENT_USER,
                                  key_name)
            for i in range(_winreg.QueryInfoKey(key)[1]):
                (name,value,_)=_winreg.EnumValue(key,i)
                if name == 'AppData':
                    home = value.encode('mbcs')
                    break
        except:
            print 'problem accessing registry HKCU\\%s'%key_name
    if not home:
        print 'Unable to expand $HOME'
    path = string.replace(path,'$HOME', home)
    return path
    
def normalize_url(url) :
    url = expand_vars(url)
    url_tuple = urlparse.urlparse(url)
    return url, url_tuple


## dynamic load module ########################################################
def dyn_import(name, func):
    mod = __import__(name)
    components = name.split('.')
    for comp in components[1:]:
        mod = getattr(mod, comp)
    return getattr(mod, func)
    
## binary objects handler access ##############################################
binary_store = BinaryStore()

def get_binary_store():
    return binary_store



## Test #######################################################################
if __name__ == '__main__' :
    doc = read_domtree_from_file(sys.argv[1])
    print write_domtree(doc)
