
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;

/**
 * <p>
 * Projekt: IDS-Suchapplet
 * </p>
 * 
 * @author Rossen Kowatschew
 * @author rossen@gmail.com
 * @version 1.3.3
 *          <p>
 *          Wird vom Applet aufgerufen. Der Inhalt des Elemnts "outputElement"
 *          mitspeichern; <br>
 *          Die XML-Datei muss eine DTD verwenden. Der gesuchte XML-Abschnitt
 *          wird in Java-Objekte abgebildet und "public class PrintAll"
 *          übergeben.
 *          </p>
 *          <p>
 *          Es wird vorausgesetzt, dass zwischen den "outputElement" und
 *          "searchedIn" eine hierarchische "parent-child" Beziehung existiert.
 *          </p>
 *          <p>
 *          Es werden also zwei Suchszenarien berücksichtigt: <br>
 *          <li>scenario=1: <br>
 *          (searchIn)...(outputElement)...(/outputElement)...(/searchIn) <br>
 *          Auch mehrere outputElement-e möglich.
 *          <li>scenario=2: <br>
 *          (outputElement)...(searchIn)...(/searchIn)...(/outputElement)
 *          </p>
 */

public class MySAXParser {
	static String parsedFile = null;

	static String searchedIn = null;

	static String outputElement = null;

	static String searchString = null;

	static ArrayList results = new ArrayList();

	/**
	 * <p>
	 * Sammelt die Ausgabe für die GUI
	 * </p>
	 */
	static StringBuffer outputBuffer = new StringBuffer();

	// ======== Flags ============
	boolean outputEl = false;

	boolean searchIn = false;

	boolean searchStr = false;

	int scenario = 0;

	/**
	 * <p>
	 * zum Verlinken der Java-Objekte, die die XML-Struktur abbilden
	 * </p>
	 */
	Stack stack = new Stack(); // 

	/**
	 * <p>
	 * zum Sammeln von DataContent
	 * </p>
	 */
	StringBuffer textBuffer;

	static String outputString = null;

	// ====================== Konstruktors =======================

	MySAXParser() {
	}

	/**
	 * Suche ohne Suchstring
	 */
	MySAXParser(String parsedF, String sIn, String outEl) throws Exception {
		parsedFile = parsedF;
		searchedIn = sIn;
		outputElement = outEl;
		start();
	}

	/**
	 * Suche mit Suchstring
	 */
	MySAXParser(String parsedF, String sIn, String outEl, String sStr)
			throws Exception {
		parsedFile = parsedF;
		searchedIn = sIn;
		outputElement = outEl;
		searchString = sStr;
		start();
	}

	/**
	 * Setting up the parser. <br>
	 * Es wird eine InputSource verwendet, um ev. Probleme mit der Auflösung der
	 * Source-Pfade (zum "parsedFile", od. zum dtd) zu vermeiden
	 */
	void start() {
		MySAXParser msp = new MySAXParser();
		try {
			SAXParserFactory factory = SAXParserFactory.newInstance();
			SAXParser p1 = factory.newSAXParser();
			XMLReader p = p1.getXMLReader();
			p.setContentHandler(msp.new Parsen());
			try {
				InputSource inputSource = new InputSource(new FileInputStream(
						new java.io.File(parsedFile)));
				inputSource.setSystemId(parsedFile);
				p.parse(parsedFile);
			} catch (FileNotFoundException fnfe) {
				ausgabe("<p> Cannot locate input file! " + fnfe.getMessage()
						+ " </p>");
			}
		} catch (ParserConfigurationException e) {
			ausgabe("<p> ParserConfigurationException in MySAXParser.start() </p>");
		} catch (IOException e) {
			ausgabe("<p> IOException in MySAXParser.start() </p>");
		} catch (SAXParseException e) {
			ausgabe("<p> SAXParseException in MySAXParser.start()"
					+ "**Parser Warnung**" + "\n" + "<br>"
					+ "  Dokumentzeile:    " + e.getLineNumber() + "\n"
					+ "<br>" + "  URI:     " + e.getSystemId() + "\n" + "<br>"
					+ "  Meldung: " + e.getMessage() + "\n" + "<br>" + "</p>");
		} catch (SAXException e) {
			ausgabe("<p> SAXException in MySAXParser.start() </p>");
		}

		//========= Ausgabe der mitgespeicherten Treffer starten ========
		if (results.size() == 0) {
			ausgabe("<p> Keine Treffer gefunden </p>");
		} else {
			outputBuffer.append("\n" + "<h4>Trefferzahl: " + results.size()
					+ "</h4>\n");
			for (int i = 0; i < results.size(); i++) {
				PrintAll print = new PrintAll((Element) results.get(i)); // RTTI
				outputBuffer.append(PrintAll.outputBuffer);
			}
			outputString = outputBuffer.toString();
			// löscht bereits ausgegebene Resultaten.
			outputBuffer = new StringBuffer();
		}
		// Werte in results löschen.
		results.clear();
	}

	/**
	 * Handling of the SAX-events: <br>
	 * Steuert das Verhalten der Anwendung, je nach dem, was für ein XML-Element
	 * geöffnet wird. Entdecken eines gesuchten Elements (Element in dem gesucht
	 * wird, Element der ausgegeben werden soll). Verlinken der Java-Objekte,
	 * die die XML-Sprach-Elemente abbilden.
	 */

	class Parsen extends DefaultHandler {
		public void startDocument() throws SAXException {
		}

		public void startElement(String nsURI, String localName, String qname,
				Attributes atts) throws SAXException {
			if (qname.equals(searchedIn)) {
				searchIn = true;
				if (outputEl) {
					scenario = 2;
				}
			}
			if (qname.equals(outputElement)) {
				outputEl = true;
				Element element = new Element(qname);
				stack.push(element);
				if (searchIn) {
					scenario = 1;
				}
			} else if (outputEl) {
				contentDataCheckStart();
				Element element = new Element(qname);
				if ((stack.top()).content == null) {
					(stack.top()).content = element;
				} else {
					Content lastNext = findLastNext((stack.top()).content);
					lastNext.next = element;
				}
				stack.push(element);
			}

			/*
			 * Bearbeiten der Attribute. <br> Das Element zu dem die Attribute
			 * gehören, ist auf dem Stack.
			 */
			if (outputEl) {
				Attribut[] array = new Attribut[atts.getLength()];
				if (atts.getLength() > 0) {
					for (int i = 0; i < atts.getLength(); i++) {
						array[i] = new Attribut(atts.getQName(i), atts
								.getValue(i));
					}
					(stack.top()).attribut = array;
				}
			}
		}

		/* Mitspeichern des Inhalts eines XML-Elements (ContentData). */
		public void characters(char[] ch, int start, int length)
				throws SAXException {
			if (outputEl) {
				String s = new String(ch, start, length);
				if (textBuffer == null) {
					textBuffer = new StringBuffer(s);
				} else {
					textBuffer.append(s);
				}
			}
		}

		/**
		 * White-space handling. Funktioniert nur bei xml-Dateien bestückt mit
		 * dtd-Angabe.
		 */
		public void ignorableWhitespace(char[] ch, int start, int length) {
		}

		//	======================= End Element ============================
		public void endElement(String namespaceURI, String localName,
				String qName) throws SAXException {
			if (outputEl) {
				contentDataCheckEnd();
				if (qName.equals(outputElement)) {
					if (searchIn) { // 1. scenario
						// 1.scenario + String
						if (searchString != null && searchStr) {
							results.add(stack.top());
							searchStr = false;
							//	1.scenario ohne String
						} else if (searchString == null) {
							results.add(stack.top());
						}
					} else if (scenario == 2) { // end of 2. scenario
						if (searchString == null
								|| (searchString != null && searchStr)) {
							results.add(stack.top());
							searchStr = false;
						}
						scenario = 0;
					}
					outputEl = false;
				}
				stack.pop();
			}
			if (qName.equals(searchedIn)) {
				if (scenario == 1) { // end 1. scenario
					scenario = 0;
				}
				searchIn = false;
			}
		}

		public void endDocument() throws SAXException {
		}

		//	======= Zusatz-Methoden ===============
		// ========================================
		/**
		 * Prüft ob es Daten (ContentData) zum Mitspeichern vor dem Element
		 * gibt. <br>
		 * Verlinkt ContentData als content vom stack.top()-Element. Das ist das
		 * Element, das vor dem gerade gestarteten Element offen war.
		 */
		protected void contentDataCheckStart() {
			String input = ("" + textBuffer);
			if (textBuffer != null) {
				if (searchString != null && searchIn) {
					input = stringMach(input);
				}
				ContentData cd = new ContentData(input);
				(stack.top()).content = cd;
				textBuffer = null;
			}
		}

		/**
		 * Prüft ob es Daten (ContentData) zum Mitspeichern vor dem Element gibt .
		 * <br>
		 * Verlinkt ContentData als last next vom stack.top()-Element. Das ist
		 * das Element, das gerade geschlossen wird.
		 */

		protected void contentDataCheckEnd() {
			String input = ("" + textBuffer);
			if (textBuffer != null) {
				if (searchString != null && searchIn) {
					input = stringMach(input);
				}
				ContentData cd = new ContentData(input);
				Content lastNext = findLastNext(stack.top());
				lastNext.next = cd;
				textBuffer = null;
			}
		}

		/**
		 * Es wird der letzte Nachbar gesucht. Zum Verlinken neuer XML-Einheiten
		 * (Elemente, Attribute etc.) nötig.
		 * 
		 * @param Content
		 *            last
		 * @return Content last
		 */
		protected Content findLastNext(Content last) {
			if (last.next != null) {
				while (last.next != null) {
					last = last.next;
				}
			}
			return last;
		}

		/*
		 * stringMach dursucht den übergebenen input-String: 1. nach ein
		 * "*"-Zeichen, das ein Sonderzeichen im "searchString" signalisiert.
		 * Sollte "*" gefunden werden, wird es für die weitere Suche mit einem
		 * "non-whitespace character" ersetzt. 2. nach den übergebenen
		 * Such-String (searchString). Jeder searchString-Treffer wird mit "
		 * <b>" + SearchString + " </b>" markiert.
		 */
		protected String stringMach(String input) {
			String sStr = searchString;
			Pattern p1, p2;
			Matcher m1, m2;
			try {
				p1 = Pattern.compile("\\*");
				m1 = p1.matcher(sStr);
				// 	Gibt es ein Sonderzeichen im gesuchten String
				if (m1.find()) {
					String replace1 = "\\\\S";
					//					System.out.println(sStr);
					sStr = m1.replaceAll(replace1);
					sStr = "\\b" + sStr + "\\b";
					//					System.out.println(sStr);
					// Gibt es Treffer im ContentData?
					p2 = Pattern.compile(sStr);
					m2 = p2.matcher(input);
					// Treffer markieren
					StringBuffer sb = new StringBuffer();
					while (m2.find()) {
						m2.appendReplacement(sb, " <b>" + m2.group() + "</b> ");
						searchStr = true;
					}
					m2.appendTail(sb);
					input = sb.toString();
					//	kein Sonderzeichen im gesuchten String
				} else {
					String temp = "\\b" + sStr + "\\b";
					p2 = Pattern.compile(temp);
					m2 = p2.matcher(input);
					if (m2.find()) {
						String replace = " <b>" + sStr + "</b> ";
						input = m2.replaceAll(replace);
						searchStr = true;
					}
				}
			} catch (PatternSyntaxException pse) {
				ausgabe("<p> There is a problem with the regular expression in MySAXParser"
						+ "/n"
						+ "<br>"
						+ "The pattern in question is: "
						+ pse.getPattern()
						+ "/n"
						+ "<br>"
						+ "The description is: "
						+ pse.getDescription()
						+ "/n"
						+ "<br>"
						+ "The message is: "
						+ "<br>"
						+ "/n"
						+ "The index is: " + pse.getIndex() + " </p>");
			}
			return input;
		}
	}

	/**
	 * @param str
	 *            für ev. Fehlermeldungen aus MySAXParser
	 */
	protected void ausgabe(String str) {
		outputString = (outputBuffer.append(str)).toString();
		// löscht bereits ausgegebene Meldungen.
		outputBuffer = new StringBuffer();
	}
}