# SAX-Parsing Probleme



## MArCus88 (1. Jan 2011)

Hallo zusammen,

wieder einmal bin ich auf eure geschätzte Hilfe angewiesen. Heute habe ich Probleme bei meiner Softwarehausaufgabe in Java, in der wir einen XML-Parser schreiben sollen. Soweit so gut, ich hab das XML Schema, sowie eine XML welche diese Kriterien erfüllt, erstellt. Nun ist der XML-Parser dran. Entschieden hab ich mich für den SAXParser, über den ich hier: XML and Java - Parsing XML using Java Tutorial ein Tutorial gelesen und versucht habe mich daran zu orientieren. Ich meine auch, dass ich soweit auf dem richtigen Weg bin, allerdings hab ich Probleme beim Ausführen.

Hier mal meine XML:

[XML]<?xml version="1.0" encoding="UTF-8"?>
<spieleListe xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="Users/marcusc/Documents/Eclipse Projekte/Kickernaut/bin/dialogs/xml/MatchAndPlayerImportSchema.xsd">
	<playerList>	
		<player ID="1">
			<firstName>Dieter</firstName>
			<familyName>Müller</familyName>
			<dateOfBirth>1988-05-14</dateOfBirth>
		</player>
		<player ID="2">
			<firstName>Franz</firstName>
			<familyName>Weiss</familyName>
			<dateOfBirth>1990-05-04</dateOfBirth>
		</player>
		<player ID="3">
			<firstName>Bernd</firstName>
			<familyName>Möller</familyName>
			<dateOfBirth>1963-05-06</dateOfBirth>
		</player>
		<player ID="4">
			<firstName>Ruth</firstName>
			<familyName>Schmidt</familyName>
			<dateOfBirth>1966-12-07</dateOfBirth>
		</player>
		<player ID="5">
			<firstName>Eva</firstName>
			<familyName>Kessler</familyName>
			<dateOfBirth>1977-12-12</dateOfBirth>
		</player>
	</playerList>
	<matchList>
		<match>
			<matchDate>2010-12-28</matchDate>
			<team>
				<teamPlayer playerID="1"/>
				<teamPlayer playerID="2"/>
				<goals>2</goals>
			</team>
			<team>
				<teamPlayer playerID="3"/>
				<teamPlayer playerID="4"/>
				<goals>2</goals>
			</team>
		</match>
	</matchList>
</spieleListe>[/XML]

Ich denke mal die Tags sind soweit relativ selbstsprechend. Bei der Applikation geht es um eine einfache Anwendung, mit der sich Kicker-Spiele speichern lassen. Dabei müssen je Match 2 Teams existieren und ein Matchdatum bestehen. Ein Team wiederum kann aus 1 oder 2 Spielern bestehen. Auf diese Spieler wird mittels key/keyref Bezug genommen.

Hier mal das zugehörige XML-Schema(falls es von Belang sein sollte^^):

[XML]<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">

<!-- Import-Schema für Kickernaut -->
<xsd:element name="spieleListe">
	<xsd:complexType>
		<xsd:sequence>
			<xsd:element name="playerList" maxOccurs="1" type="playerListType"/>
			<xsd:element name="matchList" maxOccurs="1" type="matchListType"/>
		</xsd:sequence>
	</xsd:complexType>

	<!-- key/keyref -->
	<xsd:key name="playerKey">
		<xsd:selector xpath="playerList/player"/>
		<xsd:field xpath="@ID"/>
	</xsd:key>
	<xsd:keyref name="playerFremdKey" refer="playerKey">
		<xsd:selector xpath="matchList/match/team/teamPlayer"/>
		<xsd:field xpath="@playerID"/>
	</xsd:keyref>

	<!-- Eindeutigkeit der Player -->
	<xsd:unique name="firstName-familyName-dateOfBirth">
		<xsd:selector xpath="playerList/player"/>
		<xsd:field xpath="firstName"/>
		<xsd:field xpath="familyName"/>
		<xsd:field xpath="dateOfBirth"/>
	</xsd:unique>
</xsd:element>

<!-- matchListType Deklaration -->
<xsd:complexType name="matchListType">
	<xsd:sequence>
		<xsd:element name="match" type="matchType" maxOccurs="unbounded"/>
	</xsd:sequence>
</xsd:complexType>

<!-- playerListType Deklaration -->
<xsd:complexType name="playerListType">
	<xsd:sequence>
		<xsd:element name="player" type="playerType" maxOccurs="unbounded"/>
	</xsd:sequence>
</xsd:complexType>

<!-- Match Deklaration -->
<xsd:complexType name="matchType">
	<xsd:sequence>
		<xsd:element name="matchDate" type="xsd:date"/>
		<xsd:element name="team" type="teamType" minOccurs="2" maxOccurs="2"/>
	</xsd:sequence>
</xsd:complexType>

<!-- teamType Deklaration -->
<xsd:complexType name="teamType">
	<xsd:sequence>
		<xsd:element name="teamPlayer" type="matchPlayerType" minOccurs="1" maxOccurs="2"/>
		<xsd:element name="goals" type="xsd:short"/>
	</xsd:sequence>
</xsd:complexType>

<!-- playerType Deklaration -->
<xsd:complexType name="playerType">
	<xsd:sequence>
		<xsd:element name="firstName" type="xsd:string"/>
		<xsd:element name="familyName" type="xsd:string"/>
		<xsd:element name="dateOfBirth" type="xsd:date"/>
	</xsd:sequence>	
	<xsd:attribute name="ID" type="xsdositiveInteger" use="required"/>
</xsd:complexType>

<!-- Relationship player-matchPlayer -->
<xsd:complexType name="matchPlayerType">
	<xsd:attribute name="playerID" type="xsdositiveInteger" use="required"/>
</xsd:complexType>
</xsd:schema>[/XML]

Nun möchte ich die XML auslesen und die Inhalte in meine dafür geschriebenen Klassen speichern. Hier mein Code für das Parsing:


```
package dialogs.xml;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.jar.Attributes;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

// die Klasse verarbeitet einen XML-Import und reicht die gewonnenen
// Daten an die Applikation weiter
public class XmlParser extends DefaultHandler {

	XMLPlayer actualPlayer;									// der Spieler der aktuell eingelesen wird
	XMLMatch actualMatch;									// das Match das aktuell eingelesen wird
	XMLTeam actualTeam;										// das Team welches aktuell eingelesen wird
	
	ArrayList<XMLPlayer> players;						 	// Liste der bisher eingelesenen Spieler
	ArrayList<XMLMatch> matches;						 	// Liste der bisher eingelesenen Matches
	String tempVal;							// speichert den aktuellen Simple-Content
	
	// Konstruktor
	public  XmlParser() {
		this.parseDocument();
	}
	
	// wird unmittelbar vom Konstruktor aufgerufen und startet das "XML-Parsing"
	public void parseDocument() {
		
		// instantiiert eine neue SAXParserFactory
		SAXParserFactory spf = SAXParserFactory.newInstance();
		try {

			// instantiiert einen neuen SAXParser
			SAXParser sp = spf.newSAXParser();

			File xmlFile = new File("bin/dialogs/xml/SpieleListe.xml");
			
			// sagt dem Parser welche XML er verarbeiten soll und registriert
			// diese Klasse als Default-Event-Handler für Callback-Events
			sp.parse(xmlFile, this);
			
			System.out.println("XML-Arbeit getan!");

		}catch(SAXException se) {
			se.printStackTrace();
		}catch(ParserConfigurationException pce) {
			pce.printStackTrace();
		}catch (IOException ie) {
			ie.printStackTrace();
		}
	}
	
	// wird aufgerufen wenn ein Start-Tag gefunden wurde
	public void startElement(String uri, 
							 String localName, 
							 String qName, 
							 Attributes attributes) throws SAXException {
		
		// den temporären String zurücksetzen
		tempVal = "";
		
		// prüfen, ob der Start-Tag eines player-Elements gefunden wurde
		if(qName.equalsIgnoreCase("player")) {
			
			// eine neue Playerinstanz erstellen
			actualPlayer = new XMLPlayer();
			
			// dem Player die mitgelieferte "ID"-Attribut übergeben
			actualPlayer.setPlayerID(Integer.parseInt(attributes.getValue("ID")));
			
		} else if(qName.equalsIgnoreCase("match")) {
			
			// ein neues Match erstellen
			actualMatch = new XMLMatch();
			
		} else if(qName.equalsIgnoreCase("team")) {
		
			// ein neues Team erstellen
			actualTeam = new XMLTeam();
		
		} else if(qName.equalsIgnoreCase("teamPlayer")) {
			
			// den gefundenen Spieler dem aktuellen Team zuführen
			actualTeam.addPlayer(Integer.parseInt(attributes.getValue("playerID")));
		} else if(qName.equalsIgnoreCase("playerList")) {
			
			// neue Spielerliste erstellen
			players = new ArrayList<XMLPlayer>();
		
		} else if(qName.equalsIgnoreCase("matchList")) {
			
			// neue Matchliste erstellen
			matches = new ArrayList<XMLMatch>();
		}
	}

	// wird aufgerufen wenn Simple-Content eingelesen wird
	public void characters(char[] ch, 
					       int start, 
					       int length) throws SAXException {
		
		tempVal = new String(ch,start,length);
	}
	
	// wird aufgerufen wenn Leerraum gefunden wurde
	public void ignorableWhitespace(char[] ch, int start, int length)
    {
    }

	// wird aufgerufen wenn ein End-Tag gefunden wurde
	public void endElement(String uri, 
						   String localName,
						   String qName) throws SAXException {
		
		// wurde ein "player"-Endtag gefunden?
		if(qName.equalsIgnoreCase("player")) {
			
			// fügt den Player in die Spielerliste hinzu
			players.add(actualPlayer);
			actualPlayer = null;
			
		} else if(qName.equalsIgnoreCase("firstName")) {
			
			// übergibt dem Spieler den Vornamen
			actualPlayer.setFirstName(tempVal);
			
		} else if(qName.equalsIgnoreCase("familyName")) {
			
			// übergibt dem Spieler den Familiennamen
			actualPlayer.setFamilyName(tempVal);
			
		} else if(qName.equalsIgnoreCase("dateOfBirth")) {
			
			// übergibt dem Spieler sein Geburtsdatum
			actualPlayer.setDateOfBirth(tempVal);
		}
		
		// wurde ein "match"-Endtag gefunden?
		if(qName.equalsIgnoreCase("match")) {
			matches.add(actualMatch);
			actualMatch = null;
		} else if(qName.equalsIgnoreCase("match")) {
			actualMatch.setMatchDate(tempVal);
		}
		
		// wurde ein "team"-Endtag gefunden?
		if(qName.equalsIgnoreCase("team")) {
			actualMatch.addTeam(actualTeam);
			actualTeam = null;
		}
	}
}
```

Beim Ausführen bekomme ich eine NullPointerException um die Ohren geworfen, drum hab ich durchs Setzen von Breakpoints versucht dem Fehler auf den Grund zu gehen. Es stellte sich heraus, dass die Methode characters(char,int,int) als erste aufgerufen wird, nur frage ich mich warum. Das erste Event was auftreten müsste, ist doch startElement(String,String,String,Attributes) oder nicht? Weil zuerst müsste ja ein Starttag gefunden werden, woraufhin Speicher für die jeweilige Klasse reserviert wird.

Danke für jegliche Hilfe vorab und noch einen schönen Abend!

Gruß
Marcus


----------



## Noctarius (1. Jan 2011)

Das erste Eventu das auftreten sollte ist ein startDocument. Welchen SAX Driver nutzt du? Hat dein Dokument eventuell einen UTF-8 BOM am Anfang und der SAX Driver unterstützt das nicht, dass er als erstes characters aufruft?


----------



## MArCus88 (1. Jan 2011)

@Noctarius:
Okay das ist mit startDocument ist richtig, da hab ich mich missverständlich ausgedrückt. In meinem Code sollte die erste Methode die startElement-Methode sein. Es ist ja nicht erforderlich die Methode startDocument zu implementieren oder?

Welchen SAX-Driver ... oh jee das kann ich garnicht sagen. In der XML-Materie bin ich noch Neuling, kann aber sagen, dass ich beides (Schema und die Beispiel-XML) im Editor der Eclipse Enterprise Edition geschrieben habe.


----------



## Noctarius (1. Jan 2011)

Also vermutlich den Apache SAX Driver (von Xerces) welcher im JRE bundled hinterlegt ist?


----------



## MArCus88 (1. Jan 2011)

Ja genau der taucht auch im Exception Stacktrace auf!


----------



## Noctarius (1. Jan 2011)

Lass dir vom Locator mal die Position ausgeben, an welcher der Fehler auftritt. Klingt spontan nach einem unsichtbaren Zeichen vor dem XML.


----------



## MArCus88 (1. Jan 2011)

Okay, kannst du mir sagen wie ich das bewerkstellige? Tut mir leid für die Anfängerfrage, aber mit Eclipse bin ich noch nicht so bewandert^^


----------



## Noctarius (2. Jan 2011)

Exceptions fangen und in der SAXException kannst du dir Zeile und Spalte holen. Dies kannst du entweder erreichen, in dem du um parser.parse einen try/catch legst oder durch überschreiben der fatalError Methode (oder so ähnlich).

Hab auch noch nicht soviel mit SAX gemacht, versuche aber gerade Lycia um einen Streaming-Parser zu erweitern


----------



## MArCus88 (2. Jan 2011)

Öhm aber warum SAXException? Ich bekomme ja eine NullPointerException


----------



## Noctarius (2. Jan 2011)

Vermutlich aus dem Constructor der String-Class weil das Array null ist? Kannst doch einfach mal schauen was Null ist und einen passenden Null-Check einbauen.


----------



## MArCus88 (2. Jan 2011)

Okay alles klar mein aktueller Spieler (actualPlayer) ist null, da das erste Tag, das gefunden wurde, das "firstName"-Tag ist. Demnach überliest er wirklich alle vorhergehenden Tags. Sprich es wird kein Speicherplatz für den aktuellen Spieler reserviert. Nur weiß ich nach wie vor nicht, woran es liegt dass er die vorhergehenden Tags überliest :-(


----------



## Noctarius (2. Jan 2011)

Die Frage ist doch sollten davor überhaupt Tags gekommen sein? Speicher dir mal den Locator (überschreiben der Methode setDocumentLocator) und lass dir in characters die Position ausgeben.


----------



## MArCus88 (2. Jan 2011)

Gut ich hab jetzt meine characters-Methode um die Zeile:


```
System.out.println("Column: "+this.documentEventsLocator.getColumnNumber()+" Line: "+this.documentEventsLocator.getLineNumber());
```

erweitert und kann mir so Line und Column ausgeben lassen. Worauf beziehen sich die Columns in meiner XML?

P.S.: documentEventsLocator ist der in der von dir beschriebenenen überschriebenene Methode gespeicherte Locator.


----------



## Noctarius (2. Jan 2011)

Zeile und Spalte innerhalb des XML eben.


----------



## MArCus88 (2. Jan 2011)

Okay die Zeile kann ich zuordnen^^ Lediglich unter einer Spalte innerhalb einer XML kann ich mir nichts vorstellen. Ich hab doch in einer XML gar keine tabellarische Ansicht oder doch?

Achja beim Debuggen ist mir aufgefallen, dass characters eine Reihe von "/n" und"/t" an meine temporäre Stringvariable übergibt. Kann das schon des Rätsels Lösung sein? Sprich dass ich mit Zeilenumbrüchen und Tabulatoren gearbeitet hab? So wollte ich meine XML nur lesbar halten.


----------



## Noctarius (2. Jan 2011)

MArCus88 hat gesagt.:


> Achja beim Debuggen ist mir aufgefallen, dass characters eine Reihe von "/n" und"/t" an meine temporäre Stringvariable übergibt. Kann das schon des Rätsels Lösung sein? Sprich dass ich mit Zeilenumbrüchen und Tabulatoren gearbeitet hab? So wollte ich meine XML nur lesbar halten.





Noctarius hat gesagt.:


> Lass dir vom Locator mal die Position ausgeben, an welcher der Fehler auftritt. Klingt spontan nach einem unsichtbaren Zeichen vor dem XML.



Schau mal hier: How to Manage White Space


----------



## MArCus88 (2. Jan 2011)

Schade ich hab mich daran gehalten was in dem Tutorial stand, leider brachte es nicht den gewünschten Effekt. Ich hab mir die dort beschriebene Filterklasse erstellt und meine characters-Methode an die characters-Methode dort weitergereicht.

Meine Parser characters-Methode schaut nun so aus:


```
// wird aufgerufen wenn Simple-Content eingelesen wird
	public void characters(char[] ch, 
					       int start, 
					       int length) throws SAXException {
		System.out.println("Column: "+this.documentEventsLocator.getColumnNumber()+" Line: "+this.documentEventsLocator.getLineNumber());
		WhitespaceFilter filter = new WhitespaceFilter();
		filter.characters(ch,start,length);
		tempVal = filter.getFilteredString();
	}
```

Die Klasse, die mir das char-Array filtert so:


```
public class WhitespaceFilter extends XMLFilterImpl {
		
	// der gefilterte String
	String filteredString;
		
	// filtert die Whitespaces aus der XML
	public void characters(char[] ch, int start, int length)  throws SAXException { 
	    if (!new String(ch, start, length).trim().equals("")) 
	       super.characters(ch, start, length); 
	    String temp = new String(ch,start,length);
	    filteredString = temp;
	}
	
	// gibt den gefilterten String zurück
	public String getFilteredString() {
		return filteredString;
	}
}
```

P.S.: Das mit den Spalten hab ich nun verstanden, Eclipse zeigt es mir ja an^^ Eine Spalte bezeichnet ein Zeichen.


----------



## Noctarius (2. Jan 2011)

Deine Whitespaces sind aber keine leeren Strings sondern Linefeeds und Tabs  Die musst du also auch rausfiltern.


----------



## MArCus88 (2. Jan 2011)

Ups ja wie dumm von mir. Nach der ganzen Zeit seh' ich den Wald vor lauter Bäumen net mehr. Die Zeile muss natürlich so ausschauen (wenn ich mich net vertan hab):


```
if (!new String(ch, start, length).trim().equals("/n") || !new String(ch, start, length).trim().equals("/t"))
```

Aber ich denke nicht dass es ein Whitespace-Problem ist. Zwischenzeitlich hatte ich nämlich schon manuell alle Whitespaces entfernt und die NullPointerException kommt trotzdem >.<


----------



## Noctarius (2. Jan 2011)

Dann kommt der eventuell noch aus einem anderen Grund. Btw \n und \t muss es heißen


----------



## MArCus88 (2. Jan 2011)

Ja dann wird es wirklich etwas anderes sein! Oh Mann ich hol mir erstmal 'nen Kaffee, nicht mal den korrekten Backslash bekomm' ich noch hin :-D


----------



## MArCus88 (2. Jan 2011)

Sorry für den dreisten Doppelpost, aber ich hab jetzt zu Testzwecken die XML eines Kommilitonen eingesetzt und komme auf denselben Fehler. Wir haben die ziemlich gleiche XML, drum hab ich nur ein wenig Code anpassen müssen. Ich schau mal ob ich alle Tags abgefragt hab, die auftreten können, eventuell liegt ja hier der Fehler!


----------



## MArCus88 (2. Jan 2011)

So ich wollte eben nochmal Bescheid geben, dass ich das Problem nun gelöst hab. Den Code hab ich etwas umgeschrieben, so dass ich nun den XmlReader als Parser benutze. Außerdem hab ich herausgefunden, dass ein weiterer Fehler wahrscheinlich in if-Bedingungen steckte, in denen ich die Start-Tags abgefragt hab. Dort habe ich noch nämlich den Parameter "qName" und nicht den "localName" abgefragt. Nun ist das geändert und siehe da, es geht


----------



## Noctarius (2. Jan 2011)

hehe


----------

