make sure SAX endDocument is always called as this could result in a

* parser.c: make sure SAX endDocument is always called as
  this could result in a Python memory leak otherwise (it's
  used to decrement ref-counting)
* python/generator.py python/libxml.c python/libxml.py
  python/libxml2-python-api.xml python/libxml2class.txt
  python/tests/error.py python/tests/xpath.py: implemented
  the suggestions made by Gary Benson and extended the tests
  to match it.
Daniel
diff --git a/ChangeLog b/ChangeLog
index acef54c..e0974f2 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,14 @@
+Tue Mar  5 16:33:42 CET 2002 Daniel Veillard <daniel@veillard.com>
+
+	* parser.c: make sure SAX endDocument is always called as
+	  this could result in a Python memory leak otherwise (it's
+	  used to decrement ref-counting)
+	* python/generator.py python/libxml.c python/libxml.py
+	  python/libxml2-python-api.xml python/libxml2class.txt
+	  python/tests/error.py python/tests/xpath.py: implemented
+	  the suggestions made by Gary Benson and extended the tests
+	  to match it.
+
 Tue Mar  5 10:35:24 CET 2002 Daniel Veillard <daniel@veillard.com>
 
 	* python/generator.py: applied patch fixing #73450
diff --git a/parser.c b/parser.c
index 3144c3e..b8a67f1 100644
--- a/parser.c
+++ b/parser.c
@@ -7640,8 +7640,7 @@
     /*
      * SAX: end of the document processing.
      */
-    if ((ctxt->sax) && (ctxt->sax->endDocument != NULL) &&
-	(!ctxt->disableSAX))
+    if ((ctxt->sax) && (ctxt->sax->endDocument != NULL))
         ctxt->sax->endDocument(ctxt->userData);
 
     if (! ctxt->wellFormed) {
@@ -7757,8 +7756,7 @@
     /*
      * SAX: end of the document processing.
      */
-    if ((ctxt->sax) && (ctxt->sax->endDocument != NULL) &&
-	(!ctxt->disableSAX))
+    if ((ctxt->sax) && (ctxt->sax->endDocument != NULL))
         ctxt->sax->endDocument(ctxt->userData);
 
     if (! ctxt->wellFormed) return(-1);
@@ -8245,8 +8243,7 @@
 		    xmlGenericError(xmlGenericErrorContext,
 			    "PP: entering EOF\n");
 #endif
-		    if ((ctxt->sax) && (ctxt->sax->endDocument != NULL) &&
-			(!ctxt->disableSAX))
+		    if ((ctxt->sax) && (ctxt->sax->endDocument != NULL))
 			ctxt->sax->endDocument(ctxt->userData);
 		    goto done;
 		}
@@ -8269,8 +8266,7 @@
 		    xmlGenericError(xmlGenericErrorContext,
 			    "PP: entering EOF\n");
 #endif
-		    if ((ctxt->sax) && (ctxt->sax->endDocument != NULL) &&
-			(!ctxt->disableSAX))
+		    if ((ctxt->sax) && (ctxt->sax->endDocument != NULL))
 			ctxt->sax->endDocument(ctxt->userData);
 		    goto done;
 		}
@@ -8289,8 +8285,7 @@
 		    xmlGenericError(xmlGenericErrorContext,
 			    "PP: entering EOF\n");
 #endif
-		    if ((ctxt->sax) && (ctxt->sax->endDocument != NULL) &&
-			(!ctxt->disableSAX))
+		    if ((ctxt->sax) && (ctxt->sax->endDocument != NULL))
 			ctxt->sax->endDocument(ctxt->userData);
 		    goto done;
 		}
@@ -8767,8 +8762,7 @@
 	    ctxt->disableSAX = 1;
 	} 
 	if (ctxt->instate != XML_PARSER_EOF) {
-	    if ((ctxt->sax) && (ctxt->sax->endDocument != NULL) &&
-		(!ctxt->disableSAX))
+	    if ((ctxt->sax) && (ctxt->sax->endDocument != NULL))
 		ctxt->sax->endDocument(ctxt->userData);
 	}
 	ctxt->instate = XML_PARSER_EOF;
diff --git a/python/generator.py b/python/generator.py
index 92403fb..dbfc5f7 100755
--- a/python/generator.py
+++ b/python/generator.py
@@ -583,6 +583,9 @@
     elif name[0:11] == "xmlXPathGet" and file == "python_accessor":
         func = name[11:]
         func = string.lower(func[0:1]) + func[1:]
+    elif name[0:11] == "xmlXPathSet" and file == "python_accessor":
+        func = name[8:]
+        func = string.lower(func[0:1]) + func[1:]
     elif name[0:11] == "xmlACatalog":
         func = name[11:]
         func = string.lower(func[0:1]) + func[1:]
@@ -612,6 +615,8 @@
         func = "URI" + func[3:]
     elif func[0:4] == "uTF8":
         func = "UTF8" + func[4:]
+    elif func[0:3] == 'sAX':
+        func = "SAX" + func[3:]
     return func
 
 
@@ -712,7 +717,8 @@
 		func = nameFixup(name, classe, type, file)
 		info = (0, func, name, ret, args, file)
 		function_classes[classe].append(info)
-	    elif name[0:3] == "xml" and len(args) >= 2 and args[1][1] == type:
+	    elif name[0:3] == "xml" and len(args) >= 2 and args[1][1] == type \
+	        and file != "python_accessor":
 		found = 1
 		func = nameFixup(name, classe, type, file)
 		info = (1, func, name, ret, args, file)
@@ -722,7 +728,8 @@
 		func = nameFixup(name, classe, type, file)
 		info = (0, func, name, ret, args, file)
 		function_classes[classe].append(info)
-	    elif name[0:4] == "html" and len(args) >= 2 and args[1][1] == type:
+	    elif name[0:4] == "html" and len(args) >= 2 and args[1][1] == type \
+	        and file != "python_accessor":
 		found = 1
 		func = nameFixup(name, classe, type, file)
 		info = (1, func, name, ret, args, file)
@@ -789,7 +796,25 @@
 	    classes.write(")\n");
 	    if ret[0] != "void":
 		if classes_type.has_key(ret[0]):
-		    classes.write("    if ret == None: return None\n");
+		    #
+		    # Raise an exception
+		    #
+		    if string.find(name, "URI") >= 0:
+			classes.write(
+			"    if ret == None:raise uriError('%s() failed')\n"
+			              % (name))
+		    elif string.find(name, "XPath") >= 0:
+			classes.write(
+			"    if ret == None:raise xpathError('%s() failed')\n"
+			              % (name))
+		    elif string.find(name, "Parse") >= 0:
+			classes.write(
+			"    if ret == None:raise parserError('%s() failed')\n"
+			              % (name))
+		    else:
+			classes.write(
+			"    if ret == None:raise treeError('%s() failed')\n"
+			              % (name))
 		    classes.write("    return ");
 		    classes.write(classes_type[ret[0]][1] % ("ret"));
 		    classes.write("\n");
@@ -884,12 +909,48 @@
 		classes.write(")\n");
 		if ret[0] != "void":
 		    if classes_type.has_key(ret[0]):
-			classes.write("        if ret == None: return None\n");
+			#
+			# Raise an exception
+			#
+			if string.find(name, "URI") >= 0:
+			    classes.write(
+		    "        if ret == None:raise uriError('%s() failed')\n"
+					  % (name))
+			elif string.find(name, "XPath") >= 0:
+			    classes.write(
+		    "        if ret == None:raise xpathError('%s() failed')\n"
+					  % (name))
+			elif string.find(name, "Parse") >= 0:
+			    classes.write(
+		    "        if ret == None:raise parserError('%s() failed')\n"
+					  % (name))
+			else:
+			    classes.write(
+		    "        if ret == None:raise treeError('%s() failed')\n"
+					  % (name))
 			classes.write("        return ");
 			classes.write(classes_type[ret[0]][1] % ("ret"));
 			classes.write("\n");
 		    elif converter_type.has_key(ret[0]):
-			classes.write("        if ret == None: return None\n");
+			#
+			# Raise an exception
+			#
+			if string.find(name, "URI") >= 0:
+			    classes.write(
+		    "        if ret == None:raise uriError('%s() failed')\n"
+					  % (name))
+			elif string.find(name, "XPath") >= 0:
+			    classes.write(
+		    "        if ret == None:raise xpathError('%s() failed')\n"
+					  % (name))
+			elif string.find(name, "Parse") >= 0:
+			    classes.write(
+		    "        if ret == None:raise parserError('%s() failed')\n"
+					  % (name))
+			else:
+			    classes.write(
+		    "        if ret == None:raise treeError('%s() failed')\n"
+					  % (name))
 			classes.write("        return ");
 			classes.write(converter_type[ret[0]] % ("ret"));
 			classes.write("\n");
diff --git a/python/libxml.c b/python/libxml.c
index f79af7d..8c7d295 100644
--- a/python/libxml.c
+++ b/python/libxml.c
@@ -324,7 +324,7 @@
     if (PyObject_HasAttrString(handler, "processingInstruction")) {
         result =
             PyObject_CallMethod(handler,
-                                "ignorableWhitespace", "ss", target, data);
+                                "processingInstruction", "ss", target, data);
         Py_XDECREF(result);
     }
 }
@@ -663,7 +663,7 @@
 		          &chunk, &size, &URI))
         return(NULL);
 
-#ifdef DEBUG_ERROR
+#ifdef DEBUG
     printf("libxml_xmlCreatePushParser(%p, %s, %d, %s) called\n",
 	   pyobj_SAX, chunk, size, URI);
 #endif
@@ -691,7 +691,7 @@
 		          &chunk, &size, &URI))
         return(NULL);
 
-#ifdef DEBUG_ERROR
+#ifdef DEBUG
     printf("libxml_htmlCreatePushParser(%p, %s, %d, %s) called\n",
 	   pyobj_SAX, chunk, size, URI);
 #endif
@@ -706,6 +706,60 @@
     return(pyret);
 }
 
+PyObject *
+libxml_xmlSAXParseFile(PyObject *self, PyObject *args) {
+    int recover;
+    xmlChar *URI;
+    PyObject *pyobj_SAX = NULL;
+    xmlSAXHandlerPtr SAX = NULL;
+
+    if (!PyArg_ParseTuple(args, "Osi:xmlSAXParseFile", &pyobj_SAX,
+		          &URI, &recover))
+        return(NULL);
+
+#ifdef DEBUG
+    printf("libxml_xmlSAXParseFile(%p, %s, %d) called\n",
+	   pyobj_SAX, URI, recover);
+#endif
+    if (pyobj_SAX == Py_None) {
+	Py_INCREF(Py_None);
+	return(Py_None);
+    }
+    SAX = &pythonSaxHandler;
+    Py_INCREF(pyobj_SAX);
+    /* The reference is released in pythonEndDocument() */
+    xmlSAXParseFileWithData(SAX, URI, recover, pyobj_SAX);
+    Py_INCREF(Py_None);
+    return(Py_None);
+}
+
+PyObject *
+libxml_htmlSAXParseFile(PyObject *self, PyObject *args) {
+    xmlChar *URI;
+    xmlChar *encoding;
+    PyObject *pyobj_SAX = NULL;
+    xmlSAXHandlerPtr SAX = NULL;
+
+    if (!PyArg_ParseTuple(args, "Osz:htmlSAXParseFile", &pyobj_SAX,
+		          &URI, &encoding))
+        return(NULL);
+
+#ifdef DEBUG
+    printf("libxml_htmlSAXParseFile(%p, %s, %s) called\n",
+	   pyobj_SAX, URI, encoding);
+#endif
+    if (pyobj_SAX == Py_None) {
+	Py_INCREF(Py_None);
+	return(Py_None);
+    }
+    SAX = &pythonSaxHandler;
+    Py_INCREF(pyobj_SAX);
+    /* The reference is released in pythonEndDocument() */
+    htmlSAXParseFile(URI, encoding, SAX, pyobj_SAX);
+    Py_INCREF(Py_None);
+    return(Py_None);
+}
+
 /************************************************************************
  *									*
  *			Error message callback				*
diff --git a/python/libxml.py b/python/libxml.py
index da736be..18f6840 100644
--- a/python/libxml.py
+++ b/python/libxml.py
@@ -1,6 +1,134 @@
 import libxml2mod
 
 #
+# Errors raised by the wrappers when some tree handling failed.
+#
+class treeError:
+    def __init__(self, msg):
+        self.msg = msg
+    def __str__(self):
+        return self.msg
+
+class parserError:
+    def __init__(self, msg):
+        self.msg = msg
+    def __str__(self):
+        return self.msg
+
+class uriError:
+    def __init__(self, msg):
+        self.msg = msg
+    def __str__(self):
+        return self.msg
+
+class xpathError:
+    def __init__(self, msg):
+        self.msg = msg
+    def __str__(self):
+        return self.msg
+
+#
+# Example of a class to handle SAX events
+#
+class SAXCallback:
+    """Base class for SAX handlers"""
+    def startDocument(self):
+        """called at the start of the document"""
+        pass
+
+    def endDocument(self):
+        """called at the end of the document"""
+        pass
+
+    def startElement(self, tag, attrs):
+        """called at the start of every element, tag is the name of
+	   the element, attrs is a dictionary of the element's attributes"""
+        pass
+
+    def endElement(self, tag):
+        """called at the start of every element, tag is the name of
+	   the element"""
+        pass
+
+    def characters(self, data):
+        """called when character data have been read, data is the string
+	   containing the data, multiple consecutive characters() callback
+	   are possible."""
+        pass
+
+    def cdataBlock(self, data):
+        """called when CDATA section have been read, data is the string
+	   containing the data, multiple consecutive cdataBlock() callback
+	   are possible."""
+        pass
+
+    def reference(self, name):
+        """called when an entity reference has been found"""
+        pass
+
+    def ignorableWhitespace(self, data):
+        """called when potentially ignorable white spaces have been found"""
+        pass
+
+    def processingInstruction(self, target, data):
+        """called when a PI has been found, target contains the PI name and
+	   data is the associated data in the PI"""
+        pass
+
+    def comment(self, content):
+        """called when a comment has been found, content contains the comment"""
+        pass
+
+    def externalSubset(self, name, externalID, systemID):
+        """called when a DOCTYPE declaration has been found, name is the
+	   DTD name and externalID, systemID are the DTD public and system
+	   identifier for that DTd if available"""
+        pass
+
+    def internalSubset(self, name, externalID, systemID):
+        """called when a DOCTYPE declaration has been found, name is the
+	   DTD name and externalID, systemID are the DTD public and system
+	   identifier for that DTD if available"""
+        pass
+
+    def entityDecl(self, name, type, externalID, systemID, content):
+        """called when an ENTITY declaration has been found, name is the
+	   entity name and externalID, systemID are the entity public and
+	   system identifier for that entity if available, type indicates
+	   the entity type, and content reports it's string content"""
+        pass
+
+    def notationDecl(self, name, externalID, systemID):
+        """called when an NOTATION declaration has been found, name is the
+	   notation name and externalID, systemID are the notation public and
+	   system identifier for that notation if available"""
+        pass
+
+    def attributeDecl(self, elem, name, type, defi, defaultValue, nameList):
+        """called when an ATTRIBUTE definition has been found"""
+	pass
+
+    def elementDecl(self, name, type, content):
+        """called when an ELEMENT definition has been found"""
+	pass
+
+    def entityDecl(self, name, publicId, systemID, notationName):
+        """called when an unparsed ENTITY declaration has been found,
+	   name is the entity name and publicId,, systemID are the entity
+	   public and system identifier for that entity if available,
+	   and notationName indicate the associated NOTATION"""
+        pass
+
+    def warning(self, msg):
+        print msg
+
+    def error(self, msg):
+        raise parserError(msg)
+
+    def fatalError(self, msg):
+        raise parserError(msg)
+
+#
 # This class is the ancestor of all the Node classes. It provides
 # the basic functionalities shared by all nodes (and handle
 # gracefylly the exception), like name, navigation in the tree,
diff --git a/python/libxml2-python-api.xml b/python/libxml2-python-api.xml
index 5026ee1..938d968 100644
--- a/python/libxml2-python-api.xml
+++ b/python/libxml2-python-api.xml
@@ -36,6 +36,20 @@
       <arg name='size' type='int' info='the size of the initial data'/>
       <arg name='URI' type='xmlChar *' info='The URI used for base computations'/>
     </function>
+    <function name='xmlSAXParseFile' file='python'>
+      <info>Interface to parse an XML file or resource pointed by an URI to build an event flow to the SAX object</info>
+      <return type='void'/>
+      <arg name='SAX' type='pythonObject' info='the SAX callback object or None'/>
+      <arg name='URI' type='xmlChar *' info='The URI of the resource'/>
+      <arg name='recover' type='int' info='allow recovery in case of error'/>
+    </function>
+    <function name='htmlSAXParseFile' file='python'>
+      <info>Interface to parse an HTML file or resource pointed by an URI to build an event flow to the SAX object</info>
+      <return type='void'/>
+      <arg name='SAX' type='pythonObject' info='the SAX callback object or None'/>
+      <arg name='URI' type='xmlChar *' info='The URI of the resource'/>
+      <arg name='encoding' type='const char *' info='encoding or None'/>
+    </function>
     <!-- xmlParserCtxtPtr accessors -->
     <function name='xmlParserGetDoc' file='python_accessor'>
       <info>Get the document tree from a parser context.</info>
@@ -102,7 +116,7 @@
       <return type='xmlNsPtr' info="The namespace or None"/>
       <arg name='node' type='xmlNodePtr' info='the node'/>
     </function>
-    <!-- xmlXPathParserContextPtr accessors -->
+    <!-- xmlXPathContextPtr accessors -->
     <function name='xmlXPathParserGetContext' file='python_accessor'>
       <info>Get the xpathContext from an xpathParserContext</info>
       <return type='xmlXPathContextPtr' info="The XPath context" field="context"/>
@@ -118,6 +132,18 @@
       <return type='xmlNodePtr' info="The node context" field="node"/>
       <arg name='ctxt' type='xmlXPathContextPtr' info='the XPath context'/>
     </function>
+    <function name='xmlXPathSetContextDoc' file='python_accessor'>
+      <info>Set the doc of an xpathContext</info>
+      <return type='void'/>
+      <arg name='ctxt' type='xmlXPathContextPtr' info='the XPath context'/>
+      <arg name="doc" type='xmlDocPtr' info="The doc context"/>
+    </function>
+    <function name='xmlXPathSetContextNode' file='python_accessor'>
+      <info>Set the current node of an xpathContext</info>
+      <return type='void'/>
+      <arg name='ctxt' type='xmlXPathContextPtr' info='the XPath context'/>
+      <arg name="node" type='xmlNodePtr' info="The node context"/>
+    </function>
     <function name='xmlXPathGetContextPosition' file='python_accessor'>
       <info>Get the current node from an xpathContext</info>
       <return type='int' info="The node context" field="proximityPosition"/>
diff --git a/python/libxml2class.txt b/python/libxml2class.txt
index 16cab8e..4b08b79 100644
--- a/python/libxml2class.txt
+++ b/python/libxml2class.txt
@@ -117,10 +117,12 @@
 nodePush()
 
 # functions from module python
+SAXParseFile()
 createPushParser()
 debugMemory()
 dumpMemory()
 htmlCreatePushParser()
+htmlSAXParseFile()
 newNode()
 registerErrorHandler()
 
@@ -542,6 +544,8 @@
     contextSize()
     function()
     functionURI()
+    setContextDoc()
+    setContextNode()
 
     # functions from module python
     registerXPathFunction()
diff --git a/python/tests/error.py b/python/tests/error.py
index 9337945..cc771de 100755
--- a/python/tests/error.py
+++ b/python/tests/error.py
@@ -16,8 +16,17 @@
 
      err = err + "%s %s" % (ctx, str)
 
+got_exc = 0
 libxml2.registerErrorHandler(callback, "-->")
-doc = libxml2.parseFile("missing.xml")
+try:
+    doc = libxml2.parseFile("missing.xml")
+except libxml2.parserError:
+    got_exc = 1
+
+if got_exc == 0:
+    print "Failed to get a parser exception"
+    sys.exit(1)
+
 if err != expect:
     print "error"
     print "received %s" %(err)
@@ -26,7 +35,10 @@
 
 i = 10000
 while i > 0:
-    doc = libxml2.parseFile("missing.xml")
+    try:
+	doc = libxml2.parseFile("missing.xml")
+    except libxml2.parserError:
+	got_exc = 1
     err = ""
     i = i - 1
 
diff --git a/python/tests/xpath.py b/python/tests/xpath.py
index 73ab735..2e036e1 100755
--- a/python/tests/xpath.py
+++ b/python/tests/xpath.py
@@ -22,6 +22,14 @@
 if res[0].name != "doc" or res[1].name != "foo":
     print "xpath query: wrong node set value"
     sys.exit(1)
+ctxt.setContextNode(res[0])
+res = ctxt.xpathEval("foo")
+if len(res) != 1:
+    print "xpath query: wrong node set size"
+    sys.exit(1)
+if res[0].name != "foo":
+    print "xpath query: wrong node set value"
+    sys.exit(1)
 doc.freeDoc()
 ctxt.xpathFreeContext()
 i = 1000