# This file is part of Pantry, command-line nutrient analysis.
# 
# Version 22, released Saturday, October 13, 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.

"""
A parser filter for namespace support. Placed externally to the parser
for efficiency reasons.

$Id: namespace.py,v 1.5 2001/12/30 12:09:14 loewis Exp $
"""

import string
import xmlapp

# --- ParserFilter

class ParserFilter(xmlapp.Application):
    "A generic parser filter class."

    def __init__(self):
        xmlapp.Application.__init__(self)
        self.app=xmlapp.Application()

    def set_application(self,app):
        "Sets the application to report events to."
        self.app=app

    # --- Methods inherited from xmlapp.Application

    def set_locator(self,locator):
        xmlapp.Application.set_locator(self,locator)
        self.app.set_locator(locator)

    def doc_start(self):
        self.app.doc_start()

    def doc_end(self):
        self.app.doc_end()

    def handle_comment(self,data):
        self.app.handle_comment(data)

    def handle_start_tag(self,name,attrs):
        self.app.handle_start_tag(name,attrs)

    def handle_end_tag(self,name):
        self.app.handle_end_tag(name)

    def handle_data(self,data,start,end):
        self.app.handle_data(data,start,end)

    def handle_ignorable_data(self,data,start,end):
        self.app.handle_ignorable_data(data,start,end)

    def handle_pi(self,target,data):
        self.app.handle_pi(target,data)

    def handle_doctype(self,root,pubID,sysID):
        self.app.handle_doctype(root,pubID,sysID)

    def set_entity_info(self,xmlver,enc,sddecl):
        self.app.set_entity_info(xmlver,enc,sddecl)

# --- NamespaceFilter

class NamespaceFilter(ParserFilter):
    """An xmlproc application that processes qualified names and reports them
    as 'URI local-part' names. It reports errors through the error reporting
    mechanisms of the parser."""

    def __init__(self,parser):
        ParserFilter.__init__(self)
        self.ns_map={}       # Current prefix -> URI map
        self.ns_stack=[]     # Pushed for each element, used to maint ns_map
        self.rep_ns_attrs=0  # Report xmlns-attributes?
        self.parser=parser

    def set_report_ns_attributes(self,action):
        "Tells the filter whether to report or delete xmlns-attributes."
        self.rep_ns_attrs=action

    # --- Overridden event methods

    def handle_start_tag(self,name,attrs):
        old_ns={} # Reset ns_map to these values when we leave this element
        del_ns=[] # Delete these prefixes from ns_map when we leave element

        # attrs=attrs.copy()   Will have to do this if more filters are made

        # Find declarations, update self.ns_map and self.ns_stack
        for (a,v) in attrs.items():
            if a[:6]=="xmlns:":
                prefix=a[6:]
                if string.find(prefix,":")!=-1:
                    self.parser.report_error(1900)

                if v=="":
                    self.parser.report_error(1901)
            elif a=="xmlns":
                prefix=""
            else:
                continue

            if self.ns_map.has_key(prefix):
                old_ns[prefix]=self.ns_map[prefix]
            else:
                del_ns.append(prefix)

            if prefix=="" and v=="":
                del self.ns_map[prefix]
            else:
                self.ns_map[prefix]=v

            if not self.rep_ns_attrs:
                del attrs[a]

        self.ns_stack.append((old_ns,del_ns))

        # Process elem and attr names
        name=self.__process_name(name)

        parts=string.split(name)
        if len(parts)>1:
            ns=parts[0]
        else:
            ns=None

        for (a,v) in attrs.items():
            del attrs[a]
            aname=self.__process_name(a,ns)
            if attrs.has_key(aname):
                self.parser.report_error(1903)
            attrs[aname]=v

        # Report event
        self.app.handle_start_tag(name,attrs)

    def handle_end_tag(self,name):
        name=self.__process_name(name)

        # Clean up self.ns_map and self.ns_stack
        (old_ns,del_ns)=self.ns_stack[-1]
        del self.ns_stack[-1]

        self.ns_map.update(old_ns)
        for prefix in del_ns:
            del self.ns_map[prefix]

        self.app.handle_end_tag(name)

    # --- Internal methods

    def __process_name(self,name,default_to=None):
        n=string.split(name,":")
        if len(n)>2:
            self.parser.report_error(1900)
            return name
        elif len(n)==2:
            if n[0]=="xmlns":
                return name

            try:
                return "%s %s" % (self.ns_map[n[0]],n[1])
            except KeyError:
                self.parser.report_error(1902)
                return name
        elif default_to!=None:
            return "%s %s" % (default_to,name)
        elif self.ns_map.has_key("") and name!="xmlns":
            return "%s %s" % (self.ns_map[""],name)
        else:
            return name
