# 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.
__revision__ = '$Id: readers.py,v 1.30 2002/02/20 12:05:01 alf Exp $'

from copy import copy
from xml.sax import ContentHandler
from xml.sax.xmlreader import XMLReader, AttributesNSImpl
from narval.dom import implementation,\
     ELEMENT_NODE,TEXT_NODE,DOCUMENT_NODE,DOCUMENT_FRAGMENT_NODE
from narval.lib import AL_NS, NO_NS, XSL_NS, XMLNS_NS

from xml import __version__
if __version__ == '0.7':
    from xml.dom.ext.reader.Sax2 import XmlDomGenerator as XDG
    from xml.dom.ext.reader import Sax2
    Sax2.implementation = implementation
    class XmlDomGenerator(XDG):
        def __init__(self, keepAllWs=0, implementation=None):
            XDG.__init__(self, keepAllWs)
else: # the above is fixed in CVS, so we'll get rid of this when 0.7.1 is out. 
    from xml.dom.ext.reader.Sax2 import XmlDomGenerator


EMPTY_PREFIX = None
_REQ = None

# useful functions ############################################################ 
def _get_narval_attrs(attrs, locator, name, default=None):
    """ look for attribute in valid Narval namespaces """
    # default == None <-> attribute required
    try:
        return attrs[(NO_NS, name)]
    except KeyError:
        if default is _REQ:
            message = 'No required attribute %s' % name
            if locator:
                message = '%s line %d column %d'%(message,
                                                  locator.getLineNumber(),
                                                  locator.getColumnNumber())
            raise KeyError(message)
    return default

def _get_and_apply(func, locator, attrs, attrs_desc):
    l = []
    for (key, default) in attrs_desc:
        l.append(_get_narval_attrs(attrs, locator, key, default))
    return func(*l)


#  ############################################################################
class PrototypeSaxHandler:
    """ handle prototype description for action/transform/recipe
    
    Don't need to handle namespaces here since since sax events are propagated
    here only if they are in the correct NS
    """
    
    def __init__(self, root, _ns_context, locator):
        self._locator = locator
        self._ns_context = _ns_context
        self._stack = [root]
        self.new = 0
        
    def startElementNS(self, name, qname, attrs):
        prefix, local = name
        if local in ('match', 'description'):
            self.new = 1
            e = local
        elif local == 'input':
            e = _get_and_apply(self._stack[-1].prototype.add_in_entry,
                               self._locator, attrs,(
                ( 'id', _REQ),('optional', ''),('list', ''),
                ('use', ''), ('from', '')))
        elif local == 'output':
            e = _get_and_apply(self._stack[-1].prototype.add_out_entry,
                               self._locator, attrs,(
                ( 'id', _REQ),('optional', ''),('list', ''),
                ('outdates', '')))
        self._stack.append(e)
        
    def endElementNS(self, name, qname):
        self._stack.pop()
            
    def characters(self, ch):
        if self._stack[-1] == 'match':
            if self.new:
                self._stack[-2]['match'].append(Match(ch, copy(self._ns_context)))
                self.new = 0
            else:
                self._stack[-2]['match'][-1].match += ch
        elif self._stack[-1] == 'description':
            self._stack[-2].descr += ch
            
# Default handler #############################################################
class ALDefaultHandler(XmlDomGenerator):
    def __init__(self):
        XmlDomGenerator.__init__(self, implementation=implementation)
        XmlDomGenerator.initState(self)
        self.elt = Element()
        
    def endDocument(self):
        XmlDomGenerator.endDocument(self)
        self.elt._dom = self.getRootNode().documentElement
        
#  ############################################################################
class ALRecipeSaxHandler(ContentHandler):
    def __init__(self, _ns_context, locator):
        self._ns_context = _ns_context
        self._locator = locator
        self._stack = None
        self.h = None
        
    def startElementNS(self, name, qname, attrs):
        prefix, local = name
        # root (recipe node)
        if not self._stack:
            self.elt = _get_and_apply(RecipeElement, self._locator, attrs, (
                ('name',_REQ),('group',''),('restart','no'),('decay','90'),
                ('persist','no')))
            self._stack = [self.elt]

        if self._stack[-1] == 'arguments':
            s = ''
            # namespaces handling
            qname, ns = self._get_qname(name)
            ns_l = []
            ns_l.append(ns)
            for a_name, value in attrs.items():
                a_qname, ns = self._get_qname(a_name)
                if not ns in ns_l:
                    ns_l.append(ns)
                s = '%s %s="%s"' % (s, a_qname, value)
            for ns in ns_l:
                s = '%s%s' % (s, ns)
            self._stack[-2].arguments = '%s<%s%s>' % (self._stack[-2].arguments,
                                                      qname, s)
            return
        elif prefix != AL_NS:
            print 'Unknown prefix %s in recipe, tag %s ignored' % (prefix, local)
            return

        # prototype
        if local in ('input', 'output'):
            self.h = PrototypeSaxHandler(self._stack[-1], self._ns_context,
                                         self._locator)
            
        if self.h:
            self.h.startElementNS(name, qname, attrs)
        elif local in('arguments', 'label', 'description', 'match'):
            self._stack.append(local)
        elif local == 'step':
            el = _get_and_apply(Step, self._locator, attrs, (
                ('id',_REQ),('type',_REQ),('target',_REQ),('foreach','')))
            self.elt.add_step(el)
            self._stack.append(el)
        elif local == 'transition':
            el = _get_and_apply(Transition, self._locator, attrs, (
                ('id',_REQ),('priority','0')))
            self.elt.add_transition(el)
            self._stack.append(el)
        elif local == 'in':
            _get_and_apply(self._stack[-1].add_in_step, self._locator, attrs, (
                ('idref', _REQ), ('onError', 'no')))
        elif local == 'out':
            _get_and_apply(self._stack[-1].add_out_step, self._locator, attrs, (
                ('idref', _REQ),))
        elif local == 'condition':
            cond = _get_and_apply(Condition, self._locator, attrs, (
                ('use', 'no'), ('list', 'no'), ('from', '')))
            self._stack[-1].conditions.append(cond)
            self._stack.append(cond)
        elif local == 'time':
            self._stack[-1].time_conditions.append(
                (_get_narval_attrs(attrs, self._locator, 'seconds', '*'),
                _get_narval_attrs(attrs, self._locator, 'minutes', '*'),
                _get_narval_attrs(attrs, self._locator, 'hours', '*'),
                _get_narval_attrs(attrs, self._locator, 'monthdays', '*'),
                _get_narval_attrs(attrs, self._locator, 'months', '*'),
                _get_narval_attrs(attrs, self._locator, 'weekdays', '*')))
                 
    def endElementNS(self, name, qname):
        prefix, local = name
        if self._stack[-1] == 'arguments' and not (
            prefix == AL_NS and local == 'arguments'):
            self._stack[-2].arguments += '</%s>' % (self._get_qname(name)[0])
        elif prefix != AL_NS:
            return

        if self.h:
            self.h.endElementNS(name, qname)
        elif local in ('step', 'transition', 'condition',
                       'description', 'match', 'arguments', 'label'):
            self._stack.pop()

        if local in ('input', 'output'):
            self.h = None

    def characters(self, ch):
        if self.h:
            self.h.characters(ch)
        elif self._stack[-1] == 'description':
            self._stack[-2].descr += ch
        elif self._stack[-1] == 'arguments':
            self._stack[-2].arguments += ch.replace('<', '&lt;')
        elif self._stack[-1] == 'label':
            self._stack[-2].label += ch
        elif self._stack[-1] == 'match':
            self._stack[-2]['match'].append(Match(ch, copy(self._ns_context)))

    def _get_qname(self, name):
        if not name[0]:
            return name[1], ''
        else:
            for uri, prefix in self._ns_context.items():
                if uri == name[0]:
                    return '%s:%s' % (prefix, name[1]),\
                           ' xmlns:%s="%s"' % (prefix, uri)
            else:
                raise Exception('Unknown prefix for namespace %s', name[0])
    

#  ############################################################################
class ALActionSaxHandler(ContentHandler):

    def __init__(self, _ns_context, locator):
        self._ns_context = _ns_context
        self._locator = locator
        self.h, self.descr, self.elt = None, None, None

    def startElementNS(self, name, qname, attrs):
        prefix, local = name
        if prefix != AL_NS:
            print 'Unknown prefix %s in action prototype, tag %s ignored' % (
                prefix, local)
            return
        if not self.elt:
            self.elt = _get_and_apply(ActionElement, self._locator, attrs, (
                ('name', _REQ),('group', ''),('func', _REQ),('persist','no')))
        elif local in ('input', 'output'):
            self.h = PrototypeSaxHandler(self.elt,
                                         self._ns_context, self._locator)

        if self.h:
            self.h.startElementNS(name, qname, attrs)
        elif local == 'description':
            self.descr = 1

    def endElementNS(self, name, qname):
        prefix, local = name
        if prefix != AL_NS:
            return

        if self.h:
            self.h.endElementNS(name, qname)
        elif local == 'description' and prefix == AL_NS:
            self.descr = None

        if local in ('input', 'output') and prefix == AL_NS:
            self.h = None

    def characters(self, ch):
        if self.h:
            self.h.characters(ch)
        elif self.descr:
            self.elt.descr += ch


#  ############################################################################
class ALTransformSaxHandler(ALDefaultHandler):

    def __init__(self, _ns_context, locator): 
        ALDefaultHandler.__init__(self)
        self._ns_context = _ns_context
        self._locator = locator
        self.elt = TransformElement()
        self._stack = [None]
        self.h = None
        
    def startElementNS(self, name, qname, attrs):
        prefix, local = name
        # root (action node)
        if prefix == AL_NS and local in ('input', 'output'):
            self.h = PrototypeSaxHandler(self.elt,
                                         self._ns_context, self._locator)
        if self.h:
            if prefix != AL_NS:
                print 'Unknown prefix %s in action prototype, tag %s ignored' % (
                    prefix, local)
            else:
                self.h.startElementNS(name, qname, attrs)
        elif prefix == AL_NS and local == 'description':
            self._stack.append(local)
        ALDefaultHandler.startElementNS(self, name, qname, attrs)

    def endElementNS(self, name, qname):
        prefix, local = name
        if self.h:
            if prefix == AL_NS:
                self.h.endElementNS(name, qname)
        elif prefix == AL_NS and local == 'description':
            self._stack.pop()
        ALDefaultHandler.endElementNS(self, name, qname)
        if prefix == AL_NS and local in ('input', 'output'):
            self.h = None

    def characters(self, ch):
        if self.h:
            self.h.characters(ch)
        elif self._stack[-1] == 'description':
            self.elt.descr += ch
        ALDefaultHandler.characters(self, ch)
            

# Main handler (dispatcher) ###################################################
class ALSaxHandler(ContentHandler):
    """
    Narval elements main sax handler (may produce moree than one object from a
    single xml dooument
    """
    def __init__(self, level=0, dom_handler_class=ALDefaultHandler):
        ContentHandler.__init__(self)
        self._dom_handler = dom_handler_class
        self._el_level = level
        self._level = 0
        ## results list (contains extracted elements)
        self.elements = []
        ## state handler
        self._handler = None
        ## name spaces
        self._ns_contexts = [{}] # contains uri -> prefix dicts
        self._current_context = self._ns_contexts[-1]

    def startPrefixMapping(self, prefix, uri):
        # print 'START PREFIX MAPPING', prefix, uri
        if self._handler:
            self._handler.startPrefixMapping(prefix, uri)
        self._ns_contexts.append(self._current_context.copy())
        self._current_context[uri] = prefix# or EMPTY_PREFIX

    def endPrefixMapping(self, prefix):
        # print 'END PREFIX MAPPING', prefix
        if self._handler:
            # print 'HANDLERENDPREFIX1', prefix
            self._handler.endPrefixMapping(prefix)
        self._current_context = self._ns_contexts.pop()

    def startElement(self, name, attrs):
        raise Exception('Should use sax2 interface (with NS support)')    
    def endElement(self, name):
        raise Exception('Should use sax2 interface (with NS support)')

    def startElementNS(self, name, qname, attrs):
        # print 'startNS %s (qualified: %s)' % (name, qname)
        if self._el_level == self._level:
            self._handler = self._get_handler(name)
            self._handler.setDocumentLocator(self._locator)
            self._handler.startDocument()
            ## init handler with registered prefix/uri
            for uri, prefix in self._current_context.items():
                self._handler.startPrefixMapping(prefix, uri)
        if self._handler:
            # print 'HANDLER startNS %s (qualified: %s)' % (name, qname)
            self._handler.startElementNS(name, qname, attrs)
        self._level += 1

    def endElementNS(self, name, qname):
        # print 'endNS %s (qualified: %s)' % (name, qname)
        self._level -= 1
        if self._handler:
            # print 'HANDLER endNS %s (qualified: %s)' % (name, qname)
            self._handler.endElementNS(name, qname)
        if self._el_level == self._level:
            for prefix in self._current_context.values():
                # print 'HANDLERENDPREFIX2 prefix:', prefix
                self._handler.endPrefixMapping(prefix)
            self._handler.endDocument()
            self.elements.append(self._handler.elt)
            self._handler = None
            
    def characters(self, ch):
        if self._handler:
            ch = ' '.join(ch.split())
            if len(ch) > 0:
                self._handler.characters(ch)
                
    #  Private ################################################################
    def _get_handler(self, name):
        uri, local = name
        if uri == AL_NS:
            if local == 'recipe':
                return ALRecipeSaxHandler(self._current_context, self._locator)
            if local == 'action':
                return ALActionSaxHandler(self._current_context, self._locator)
            if local == 'recipe':
                return ALRecipeSaxHandler(self._current_context, self._locator)
        elif local == 'transform' and uri == XSL_NS:
            return ALTransformSaxHandler(self._current_context, self._locator)
        return self._dom_handler()


## Narval elements reader ######################################################

class Reader:
    def __init__(self):
        from xml.sax import make_parser
        from xml.sax.handler import feature_namespaces
        from threading import Lock
        p = make_parser(["xml.sax.drivers2.drv_pyexpat"])#xmlproc"])
        p.setFeature(feature_namespaces, 1)
        self.parser = p
        p = Dom2SaxParser()
        #p.setFeature(feature_namespaces, 1)
        self.dom_parser = p
        self._s_lock = Lock()
        self._d_lock = Lock()
        
    def fromStream(self, stream, level=0):
        handler = ALSaxHandler(level)
        self._s_lock.acquire()
        try:
            self.parser.setContentHandler(handler)
            self.parser.parse(stream)
        finally:
            self._s_lock.release()
        return handler.elements

    def fromDOM(self, dom, level=0):
        handler = ALSaxHandler(level)
        self._d_lock.acquire()
        try:
            self.dom_parser.setContentHandler(handler)
            self.dom_parser.parse(dom)
        finally:
            self._d_lock.release()
        return handler.elements

    def fromString(self, str, level=0):
        from xml.dom.ext.reader import StrStream
        stream = StrStream(str)
        try:
            return self.fromStream(stream, level)
        finally:
            stream.close()

## Minimal dom reader ##########################################################
class Dom2SaxParser(XMLReader):
    """  Generate Sax events from a dom tree
    (as if feature_namespaces was enabled)
    """
    def __init__(self):
        XMLReader.__init__(self)
        
    ##  parsing ###############################################################
    def parse(self, dom):
        self._from_dom(dom)

    ## private ################################################################
    def _from_dom(self, n):
        while n:
            type = n.nodeType
            if type == ELEMENT_NODE:
                self._element_(n)
            elif type == TEXT_NODE:
                self._cont_handler.characters(n.data)
            elif type == DOCUMENT_NODE:
                self._cont_handler.startDocument()
                self._from_dom(n.firstChild)
                self._cont_handler.endDocument()
            elif type == DOCUMENT_FRAGMENT_NODE:
                for n in n.childNodes:
                    self._cont_handler.startDocument()
                    self._from_dom(n.firstChild)
                    self._cont_handler.endDocument()
            n = n.nextSibling
  
    def _element_(self, n):
        ## convert DOM namedNodeMap to SAX attributes NS
        prefix_list = []
        nnm = n.attributes
        attrs, qnames = {}, {}
        for a in nnm.values():
            a_uri = a.namespaceURI
            if a_uri == XMLNS_NS:
                prefix, val = a.localName or None, a.value or None
                self._cont_handler.startPrefixMapping(prefix, val)
                prefix_list.append(prefix)
            else:
                name = (a_uri, a.localName)
                attrs[name] = a.value
                qnames[name] = a.nodeName
        ## handle element NS
        name = (n.namespaceURI or None, n.localName or None)
        self._cont_handler.startElementNS(name, n.nodeName,
                                          AttributesNSImpl(attrs, qnames))
        self._from_dom(n.firstChild)
        self._cont_handler.endElementNS(name, n.nodeName)
        prefix_list.reverse()
        map(self._cont_handler.endPrefixMapping, prefix_list)


from ALAbstraction import Element, RecipeElement, ActionElement,\
     TransformElement, Step, Transition
from prototype import Prototype, Condition, Match


# Test ########################################################################
if __name__ == '__main__':
    import sys
    level = int(sys.argv[1])
    f = open(sys.argv[2])
    from Ft.Xml.pDomletteReader import SaxReader
    from xml.dom.ext.reader import Sax2
    from xml.dom.ext import PrettyPrint
    from xml.sax import make_parser
    from xml.sax.handler import feature_namespaces
    h = ALDefaultHandler()
    p = make_parser()
    p.setContentHandler(h)
    p.setFeature(feature_namespaces, 1)
    p.parse(f)
    d = h.document.documentElement
    #r = SaxReader()
    #r = Sax2.Reader()
    #d = r.fromStream(f).documentElement
    print d
    #go(d)
    PrettyPrint(d)
    r = Reader()
    elts = r.fromDOM(d, level)
    #elts = r.fromStream(f, level)
    f.close()
    for e in elts:
        print '-'*80
        print e.as_xml('latin-1')    
