/*
 * $Id: XmlExtractor.java,v 1.16 2006/06/06 20:36:04 marbobo Exp $
 *
 * Copyright (c) Siemens AG 2008 
 *           (c) Universität Paderborn 2008 
 * Licensed under EUPL 1.1. 
 *
 * $Log: XmlExtractor.java,v $
 * Revision 1.16  2006/06/06 20:36:04  marbobo
 * + Mögliche NPE abgefangen
 *
 * Revision 1.15  2006/03/09 12:56:02  lawern
 * Anpassung an JDK 1.5
 *
 * Revision 1.14  2005/06/30 13:51:20  marbobo
 * + layout angepasst
 *
 * Revision 1.13  2005/04/01 00:12:11  marbobo
 * + UTF-8 Parsing vervollständigt und korrigiert
 *
 * Revision 1.12  2005/03/24 12:57:56  lawern
 * Umstellung des TDF-Formats auf Version 1.1 in UTF-8-Kodierung.
 *
 * Revision 1.11  2005/01/27 13:54:58  lawern
 * Neuer Absatz, wenn Abstand groesser als 1.55-facher Zeilenabstand.
 *
 * Revision 1.10  2004/11/30 11:51:47  lawern
 * Wegen Fehler in pdftohtml keine Texte mit FontSize < 0 akzeptieren.
 *
 * Revision 1.9  2004/11/10 15:02:43  koppy
 * Projektweite Überarbeitung von Error-Logs, Stacktraces,..
 *
 * Revision 1.8  2004/11/02 10:14:41  lawern
 * "java.lang.InternalError: fillbuf" abgefangen
 *
 * Revision 1.7  2004/06/27 17:12:47  marbobo
 * + Quellcode maintenance
 *
 * Revision 1.6  2003/12/24 10:12:01  bigmac
 * Projektweite Import-Optimierung
 *
 * Revision 1.5  2003/09/24 14:34:24  lawern
 * Metadaten werden jetzt aus PDF-Dokument übernommen.
 *
 * Revision 1.4  2003/09/22 07:35:42  lawern
 * Codierung von < und > in TDF-Dokumenten erweitert.
 *
 * Revision 1.3  2003/09/12 12:14:26  lawern
 * Links werden jetzt extrahiert.
 *
 * Revision 1.2  2003/07/24 12:45:54  lawern
 * LogManager wird jetzt auch verwendet.
 *
 * Revision 1.1  2003/07/24 11:50:58  lawern
 * initiale Version
 *
 */
package vkc.converter;

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

import vkc.analyse.TextStyle;
import vkc.analyse.TextStyles;

import vkc.misc.VKCLogManager;

import vkc.util.VKCUtils;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;

import java.net.URL;

import java.util.ArrayList;

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

/**
 * Diese Klasse extrahiert aus einem RTF-Dokument den Text und gewichtet diesen
 * auf Basis der verwendeten typographischen Styles (Schriftgröße,
 * Kursivschrift usw.). Es wirkt sich sicherlich zeitsparend aus, wenn man
 * Objekte dieses Typs mehrfach verwendet.
 *
 * @author lawern
 */
public final class XmlExtractor {
	public static final boolean debug = false;

	public static final int UNDEFINED = 0;

	public static final int TEXT = 1;

	public static final int TITLE = 2;

	public static final int SUBJECT = 3;

	public static final int KEYWORDS = 4;

	private boolean rendered;

	private ArrayList links = new ArrayList();

	private TextStyles elements;

	private String docTitle;

	private String docSubject;

	private String docKeywords;

	public XmlExtractor() {
		//Link-Liste löschen
		links.clear();

		//Style-Statistik neu anlegen
		elements = new TextStyles();
	}

	public void analyse(String file) {
		SAXParserFactory factory;
		SAXParser parser;

		// Abruf eines Factory-Exemplars
		factory = SAXParserFactory.newInstance();

		//factory.setValidating(false);
		//factory.setNamespaceAware(false);
		try {
			// Erzeugung des Parsers über diese Factory
			parser = factory.newSAXParser();

			// parsen der XML-Datei
			parser.parse(file, new NestingHandler());
		} catch (SAXException e) {
			VKCLogManager.errorLog(this, "1", "Can't parse xml file!", e);
		} catch (IOException e) {
			VKCLogManager.errorLog(this, "2", "Can't parse xml file!", e);
		} catch (ParserConfigurationException e) {
			VKCLogManager.errorLog(this, "3", "Can't parse xml file!", e);
		} catch (java.lang.InternalError ie) {
			VKCLogManager.errorLog(this, "4", "Can't parse xml file!", ie);
		}

		rendered = true;
	}

	/**
	 * Liefert eine Liste mit Objekten vom Typ TextStyle zurück, welche das
	 * gerenderte RTF-Dokument beschreiben.
	 *
	 * @return Liste mit Objekten vom Typ TextStyle
	 */
	public TextStyles getElements() {
		if (rendered) {
			return elements;
		}

		return null;
	}

	/**
	 * Gibt eine Liste zurueck, der alle HREFs einer Seite enthaelt.
	 *
	 * @return Link-Vektor
	 */
	public ArrayList getLinks() {
		if (rendered) {
			return links;
		}

		return null;
	}

	/**
	 * Schreibt eine Datei mit Gewichtungsmarken in ein byte[]
	 *
	 * @return Array, welches die Datei enthält
	 */
	public byte[] getTdfArray() {
		ByteArrayOutputStream array = new ByteArrayOutputStream();
		PrintStream out;

		try {
			out = new PrintStream(array, false, "UTF-8");
		} catch (UnsupportedEncodingException e) {
			return null;
		}

		if (rendered) {
			TextStyle actStyle = null;

			out.println("<V:1.1>");

			String text;

			for (int i = 0; i < elements.size(); i++) {
				actStyle = (TextStyle) elements.get(i);

				//neue Zeile?
				if (actStyle.newLine) {
					out.println("");

					//neuer Absatz?
					if (actStyle.separated) {
						out.println("<P>");
					}
				}

				text = VKCUtils.tdfEncode(actStyle.text);
				out.print("<S:" + actStyle.weight + ":\u200E" + text + "\u200E>");
			}
		}

		return array.toByteArray();
	}

	public static void main(String[] args) {
		VKCLogManager.init("c:/temp/test_", ".log");
		VKCLogManager.setEnableLogging(true);
		VKCLogManager.setLogToConsole(true);

		XmlExtractor extractor = new XmlExtractor();
		extractor.analyse(args[0]);

		System.out.println(new String(extractor.getTdfArray()));
		System.exit(0);
	}

	public String getDocKeywords() {
		return docKeywords;
	}

	public String getDocSubject() {
		return docSubject;
	}

	public String getDocTitle() {
		return docTitle;
	}

	class NestingHandler extends DefaultHandler {
		private int lastRowHeight = 0;

		private int height;

		private int lastRowPosition;

		private int y;

		private int x;

		private boolean italic;

		private boolean bold;

		private int fontSize;

		private int currentElement;

		int[] fonts = new int[10];

		public void startElement(String uri, String localName, String qName, Attributes attributes) {
			int fontId;

			switch (qName.charAt(0)) {
				// fontspec-Tag auswerten
				case 'f':
					fontId = new Integer(attributes.getValue("id")).intValue();
					fontSize = new Integer(attributes.getValue("size")).intValue();
	
					//testen ob das Fonts-Array ausreicht
					if (fontId > (fonts.length - 1)) {
						//doppelt so großes Array erzeugen und umkopieren
						int[] tmpFonts = new int[fonts.length * 2];
						System.arraycopy(fonts, 0, tmpFonts, 0, fonts.length);
						fonts = tmpFonts;
					}
	
					fonts[fontId] = fontSize;
	
					break;
	
				// text-oder title-Tag auswerten
				case 't':
					if (qName.charAt(1) == 'e') {
						//text-Tag
						currentElement = TEXT;
	
						//Schriftgröße bestimmen
						fontId = new Integer(attributes.getValue("font")).intValue();
						fontSize = fonts[fontId];
	
						//Koordinaten der Zeile bestimmen
						x = new Integer(attributes.getValue("left")).intValue();
						y = new Integer(attributes.getValue("top")).intValue() + fontSize;
						height = new Integer(attributes.getValue("height")).intValue();
					} else {
						//title-Tag
						currentElement = TITLE;
					}
	
					break;
	
				// a-Tag auswerten
				case 'a':
					String link = attributes.getValue("href");
	
					try {
						URL url = new URL(link);
						links.add(url);
	
						//System.out.println("href: " + link);
					} catch (java.net.MalformedURLException mue) {
					}
	
					break;
	
				// bold-Tag auswerten
				case 'b':
					bold = true;
	
					break;
	
				// italic-Tag auswerten
				case 'i':
					italic = true;
	
					break;
	
				default:
					break;
			}
		}

		public void endElement(String uri, String localName, String qName) {
			switch (qName.charAt(0)) {
				// text-, title-, subject- oder keywords-Tag auswerten
				case 't':
				case 's':
				case 'k':
					currentElement = UNDEFINED;
	
					break;
	
				// bold-Tag auswerten
				case 'b':
					bold = false;
	
					break;
	
				// italic-Tag auswerten
				case 'i':
					italic = false;
	
					break;
	
				default:
					break;
			}
		}

		public void characters(char[] ch, int start, int length) throws SAXException {
			if (currentElement != UNDEFINED) {
				boolean newLine;

				String text = new String(ch, start, length).trim();

				if (text.length() > 0) {
					switch (currentElement) {
						case TITLE:
							docTitle = text;
	
							break;
	
						case SUBJECT:
							docSubject = text;
	
							break;
	
						case KEYWORDS:
							docKeywords = text;
	
							break;
	
						case TEXT:
							boolean separated;
	
							// neue Zeile?
							if (y != lastRowPosition) {
								newLine = true;
	
								// neuer Absatz? Wenn Abstand groesser als 1.55-facher Zeilenabstand!
								if ((lastRowPosition + (lastRowHeight * 1.55)) < y) {
									separated = true;
								} else {
									separated = false;
								}
	
								lastRowPosition = y;
								lastRowHeight = height;
							} else {
								newLine = false;
								separated = false;
							}
	
							if (fontSize >= 0) {
								elements.add(new TextStyle(fontSize, bold, italic,
										false, text, x, y, height, newLine,
										separated));
	
								//                    System.out.println(text + " newLine=" + newLine +
								//                        " separated=" + separated + " x=" + x + " y=" + y +
								//                        " fontSize=" + fontSize + " bold=" + bold + " italic=" +
								//                        italic);
							}
					}
				}
			}
		}

		//verhindert, dass die pdf2xml.dtd geladen wird
		public InputSource resolveEntity(String publicId, String systemId) throws SAXException {
			if (systemId.indexOf("pdf2xml.dtd") > 0) {
				// this deactivates the open office DTD
				return new InputSource(new ByteArrayInputStream("<?xml version='1.0' encoding='UTF-8'?>".getBytes()));
			}

			try {
				//Die folgende Zeile wird benötigt, damit diese Klasse mit JDKs < 1.5 übersetzt werden kann!
				if (false)
					throw new IOException();
				;

				return super.resolveEntity(publicId, systemId);
			} catch (IOException ioe) {
				throw new SAXException(ioe);
			}
		}
	}
}