# Probleme beim SAX parsing



## DerUnwissende (28. Mai 2007)

Hallo zusammen, ich habe folgendes Problem:

Ich entwickle gerade zum ersten mal eine Client-Server Anwendung und möchte für die Kommunikation XML verwenden. Beim Parsing des InputStreams des Sockets mit SAXParser und einer von DefaultHandler abgeleiteten Klasse wird nun jedoch nicht die endDocument Methode aufgerufen, der Parser scheint einfach weiterhin zu glauben, dass das Dokument noch nicht zu Ende ist. Deshalb kann ich natürlich keine weiteren Dokumente schicken, ohne eine SAXException zu bekommen.
Woran liegt das? Wie kann ich das umgehen, ohne auf SAX zu verzichten, oder die Verbindung nach jeder Datei neu aufbauen zu müssen?

Hier erstmal der der das Dokument sendet:

```
TransformerFactory tFactory= TransformerFactory.newInstance();
Transformer t= tFactory.newTransformer();
DOMSource source = new DOMSource(doc);

synchronized(s){      //s ist ein Socket
	StreamResult result = new StreamResult(s.getOutputStream());
	t.transform(source, result);
}
```

Hier ein Teil des empfangenden Threads der den Parser in einer Schleife aufruft:

```
while(!close){
		     try{
				Debug.out("start of parsing loop");
				DefaultHandler handler=new ServerHandler(this, connection);
				SAXParserFactory factory = SAXParserFactory.newInstance();
			        SAXParser saxParser = factory.newSAXParser();
		    	        saxParser.parse( connSocket.getInputStream(), handler ); 
//der Thread blockiert hier und wartet auf Anfragen vom Client.

		    }catch(ParserConfigurationException pce){
		    	Debug.out(pce.getMessage());
		    	pce.printStackTrace();
		    }catch(IOException ioe){
		    	Debug.out("IOException while parsing (maybe interrupted): "+ioe.getMessage());
		    	
		    }catch(SAXException saxe){
		    	Debug.out("SAXExceptin while parsing (maybe interrupted): "+saxe.getMessage());
		    }finally{
		    	if (connSocket.isClosed()){
		    		//TODO wrong!!!
		    		if(close)
		    			Debug.out("Connection closed Client Sided!");
		    		else{
		    			close=true;
		    			Debug.out("Connection closed Server Sided!");
		    		}
		    	}
		    }
		}
```
Beispiel XML Datei die ich getestet habe

```
<?xml version="1.0" encoding="UTF-8"?><TEST_1><TEST_2><TEST_3/></TEST_2><TEST_4/></TEST_1>
```
Ich möchte den ersten Thread jetzt mal nicht weiter mit evt. unnötigem Quelltext überladen, wenn ihr mehr infos braucht einfach schreiben

Vielen Dank schonmal im Voraus


----------



## kleiner_held (28. Mai 2007)

Hast du schon mal versucht, am Socket-OutputStream des Clients nach dem Senden ein flush() aufzurufen?


----------



## DerUnwissende (29. Mai 2007)

flush hab ich probiert. macht keinen Unterschied


----------



## kleiner_held (29. Mai 2007)

Ah ja, ich entsinne mich dunkel dieses Problem auch mal gehabt zu haben. 
Die Ursache ist, dass der Parser (getestet mit Xerces) kein endDocument aufruft bis der InputStream ein -1 beim read zurueckgibt. Unangenehm ist ausserdem, dass der Parser, wenn er fertig ist, ein close() am InputStream aufruft und dir damit die Verbindung zumacht.

Das stand auch irgendwo versteckt in der Java API zu org.xml.sax.InputSource


			
				Java API hat gesagt.:
			
		

> An InputSource object belongs to the application: the SAX parser shall never modify it in any way (it may modify a copy if necessary). However, standard processing of both byte and character streams is to close them on as part of end-of-parse cleanup, so applications should not attempt to re-use such streams after they have been handed to a parser.



Die saubere Lösung wäre, alle zu sendenen Dokumente in einem neuen XML Dokoment zusammenzufassen, also 
mehrere Anfragen in einem neuen Root-Tag zu kapseln.
Statt

```
<root>Inhalt .. </root>  <root>Inhalt 2 .. </root>
```
die XML Struktur erweitern zu:

```
<neuerRoot><root>Inhalt .. </root>  <root>Inhalt 2 .. </root></neuerRoot>
```
Man arbeitet dann die ganze Verbindungszeit mit einem Parser und der verwendete Handler muss durch endElement das Ende der jeweiligen Abschnitte erkennen.

Wenn man eine vorgegebene XSD oder DTD hat und nebenbei eventuell validieren will oder bei bidirektionaler Kommunikation ist das manchmal ein wenig blöd.

Ich hatte mir damals auch einen "Hack" einfallen lassen und dem Sax-Parser quasi ein Ende des Inputs "untergeschoben". Der Client hat nach jedem vollstaendigen Senden eines XML Dokuments ein ASCII Steuerzeichen (SUB 0x1A) in den Ausgabestrom eingefuegt.
Auf serverseite wird der Eingabestrom mit einem speziellen FilterInputStream gekapselt, der beim Erhalt des Steuerzeichens ein StreamEnde signalisiert (return -1) bis er mit setInterrupted(false) wieder freigeschaltet wird. Ausserdem verschluckt er alle close() Aufrufe. Ist aber wie gesagt ein ziemlich haesslicher Hack, aber eine bessere loesung ist mir damals net eingefallen.

Bei dir waere der Aufruf dann so:

```
InterruptableInputStream interruptableInputStream = new InterruptableInputStream(connSocket.getInputStream());
      while(!close){
         try{
            Debug.out("start of parsing loop");
            DefaultHandler handler=new ServerHandler(this, connection);
            SAXParserFactory factory = SAXParserFactory.newInstance();
            SAXParser saxParser = factory.newSAXParser();
                  saxParser.parse(interruptableInputStream, handler ); 
            // nach dem Lesen wieder freischalten
            interruptableInputStream.setInterrrupted(false);
	...
```


```
public class InterruptableInputStream extends PushbackInputStream
    {
        private boolean interrrupted = false;
        private byte stopChar = 0x1A;
        
        public InterruptableInputStream(InputStream in)
        {
            super(in, 2048);
        }
        
        public int read() throws IOException
        {
            if (interrrupted)
            {
                return -1;
            }
            int read = super.read();
            if (read == stopChar)
            {
            	setInterrrupted(true);
            	return -1;
            }
            return read;
        }

        public int read(byte[] b, int off, int len) throws IOException
        {
            if (interrrupted)
            {
                return -1;
            }
            // if there are unread bytes in the buffer, we do first empty the buffer
            // otherwise we have to ensure to read max one more byte as buffer space is available
            // (one more byte is possible, because we do only push back if there is a stopChar 
            // found and we never push back the stopChar)
            int read = super.read(b, off, Math.min(len, // never more than the caller allows
            		super.pos == super.buf.length ?     // buffer empty ?  
            		super.pos + 1 :                     // yes -> one byte more than buffer spaces is available
            		super.buf.length - super.pos));     // no  -> the buffer content
            
            // check the read bytes for the stopChar, if it was found then push back all bytes behind the
            // stopChar and return only the bytes until there
            if (read > 0)
            {
            	for (int i = 0; i < read; i++)
            	{
            		if (b[i+off] == stopChar)
            		{
            			unread(b, off+i+1, read-i-1);
            			setInterrrupted(true);
                		return i;
            		}
            	}
            }
            return read;
        }

        public boolean isInterrrupted()
        {
            return interrrupted;
        }

        public void setInterrrupted(boolean interrrupted)
        {
            this.interrrupted = interrrupted;
        }
        
        public void close() throws IOException
        {
        	// ignore the close
        }
    }
```


----------



## DerUnwissende (29. Mai 2007)

OK, der Hinweis hilft mir auf jeden Fall schonmal weiter. Ich denke ich werde es nun so handhaben, alles in ein File zu schreiben, bis das Steuersignal kommt, und dann das File zu parsen. Mal schauen.

Vielen Dank auf jeden Fall für die Hilfe


----------

