import java.io.FileOutputStream;
import java.io.*;
import java.lang.System;
import java.util.*;
/*
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.*;
import org.apache.poi.xssf.usermodel.*;
*/
import org.apache.poi.xwpf.usermodel.*;
// import org.apache.poi.xwpf.extractor.XWPFWordExtractor;

public class docx2html {  

		public static String indent(int il) {
			StringBuffer res = new StringBuffer("");
			for (int i=0; i<il; i++) {
				res.append("  ");
			}
			return res.toString();
		}			

		private enum ListType { NUMLIST, BULLIST };
		
		private enum TransAct { MEMLCSTAGE, MEMQOBJECT, MEMZWECK, ADDZWECK, MEMBESCH, ADDBESCH, MEMKRIT, ADDKRIT, 
								MEMEINORD, ADDEINORD, MEMPRUEF, ADDPRUEF, MEMERF, ADDERF, MEMGUID, ADDGUID, MEMABH, ADDABH,
								ERREXIT };
		
		private enum State { SCAN, LCSLIST, QOLIST, QOATTRIB, QOAZWECK, QOABESCH, QOAKRIT, QOAEINORD, QOAPRUEF, QOAERF, QOAGUID, QOAABH };
		
		private final String zuordnungsNamen[] = { "Funktionale Eignung", "Effizienz", "Kompatibilität", "Benutzbarkeit", "Zuverlässigkeit", "Sicherheit", "Wartbarkeit", "Portabilität", "Sonstiges"};
		
		private static class StateTransition {
			public State oldstate;
			public BodyElementType evtBodyElementType;
			public String evtStyle;
			public String evtText;
			public State newstate;
			public TransAct sttr;
			
			public StateTransition( State oldst, BodyElementType evtBet, String evtSty, String evtTxt, State newst, TransAct tr) {
				this.oldstate = oldst;
				this.evtBodyElementType = evtBet;
				this.evtStyle = evtSty;
				this.evtText = evtTxt;
				this.newstate = newst;
				this.sttr = tr;
			}
			public static boolean matchstr(String str1, String str2) {
				if (str1==null) return true;
				if (str2==null) return false;
				return str2.matches(str1);
			}
			public boolean match( State oldst, BodyElementType evtBet, String evtSty, String evtTxt) {
				if (((this.oldstate==null) || (this.oldstate==oldst)) && 
					((this.evtBodyElementType==null) || (this.evtBodyElementType==evtBet)) &&
					matchstr(this.evtStyle,evtSty) && 
					matchstr(this.evtText,evtTxt)) {
					return true;
				} else {					
					return false;
				}
			}
		}
		
		private class StateMachine {
			private StateTransition stategraph[];
			private State enginestate = State.SCAN;
			public StateMachine(StateTransition gr[]) {
				stategraph = gr;
			}
			public StateTransition findSttr(State oldst, BodyElementType evtBet, String theStyle, String theText) {
				// System.err.printf("FIND_STTR.OLDST=%s.BODYELEMENTTYPE=%s.STYLE=%s.TEXT=%s\n",oldst,evtBet.toString(),theStyle,theText);
				for (int i=0; i<stategraph.length; i++) {
					// System.err.printf("FIND_STTR.INDEX=%d\n",i);
					if (stategraph[i].match(oldst,evtBet, theStyle, theText)) {
						return stategraph[i];
					}
				}
				return null;
			}
		}
		
		private enum DocPartType {PARA, TABLE, LIST, DOC}
		
		private class TextRun {
			String text;
			public TextRun(String theText) {
				this.text = theText;
			}
			public TextRun(XWPFRun r) {
				this.text = r.getText(0);	
			}
			public String toString() {
				return "<TextRun>"+((this.text==null)?"":this.text.replace("\n","<br/>"))+"</TextRun>";
			}
			public String toJSON() {
				return "{\"objecttype\": \"TextRun\", \"text\": \""+((this.text==null)?"":this.text.replace("\n","\\n").replace("\t"," "))+"\"}";
			}
				
		}
		
		private class DocPart {
			String style;
			DocPartType type;
			public DocPart() {
				this.style = null;
				this.type = null;
			}
			public DocPart(String theStyle, DocPartType theType) {
				this.style = theStyle;
				this.type = theType;
			}
			public void debugDump(PrintStream ps, int il) {
				ps.printf(indent(il)+"<DocPart type=\"%s\">\n",this.type.toString());
				ps.print(this.toString());
				ps.println(indent(il)+"</DocPart>");
			}
			public String toJSON() {
				return "{\"error\": \"toJSON() of base class DocPart should not be invoked\"}";
			}
		}
		
		private class DocPartParagraph extends DocPart {
			ArrayList<TextRun> runs;
			public DocPartParagraph(String theStyle, DocPartType theType, XWPFParagraph p) {
				super(theStyle,theType);
				this.runs = new ArrayList<TextRun>();
				java.util.List<XWPFRun> rl = p.getRuns();
				ListIterator<XWPFRun> rli = rl.listIterator();
				while (rli.hasNext()) {
					this.addRun(new TextRun(rli.next()));
				}
			}
			public DocPartParagraph(String doctxt) {
				super("UnStyled",DocPartType.PARA);
				this.runs = new ArrayList<TextRun>();
				this.addRun(new TextRun(doctxt));
			}
			public void addRun(TextRun theRun) {
				this.runs.add(theRun);
			}
			public String toString() {
				ListIterator<TextRun> tri = runs.listIterator();
				StringBuffer res = new StringBuffer();
				while (tri.hasNext()) {
					res.append(tri.next().toString());
				}
				return res.toString();
			}
			public String toJSON() {
				ListIterator<TextRun> tri = runs.listIterator();
				StringBuffer res = new StringBuffer("{\"objecttype\": \"DocPartParagraph\", \"runs\": [");
				boolean first = true;
				while (tri.hasNext()) {
					if (!first) {
						res.append(", ");
					} else {
						first = false;
					}
					res.append(tri.next().toJSON());
				}
				res.append("]}");
				return res.toString();
			}
		}
		
		private class DocTabRow {
			ArrayList<Document> columns;
			public DocTabRow(XWPFTableRow tr) {
				XWPFTableCell tc;
				this.columns = new ArrayList<Document>();
				java.util.List<XWPFTableCell> tcl = tr.getTableCells();
				ListIterator<XWPFTableCell> tcli = tcl.listIterator();
				while (tcli.hasNext()) {
					tc = tcli.next();
					columns.add(new Document(tc.getBodyElements()));
				}
			}
			public String toString() {
				StringBuffer res = new StringBuffer("<TableRow>\n");
				ListIterator<Document> ci = columns.listIterator();
				while (ci.hasNext()) {
					res.append("<TableColumn>"+ci.next().toString()+"<TableColumn>\n");
				}
				res.append("</TableRow>");
				return res.toString();
			}
			public String toJSON() {
				StringBuffer res = new StringBuffer("{\"objecttype\": \"TableRow>\", \"columns\": [");
				ListIterator<Document> ci = columns.listIterator();
				while (ci.hasNext()) {
					res.append("{\"objecttype\": \"DocTableColumn\", \"Document\": "+ci.next().toJSON()+"}\n");
				}
				res.append("]}");
				return res.toString();
			}
		}
		
		private class DocPartTable extends DocPart {
			ArrayList<DocTabRow> rows;
			public DocPartTable(String theStyle, DocPartType theType) {
				super(theStyle,theType);
			}
			public DocPartTable(String theStyle, DocPartType theType, XWPFTable t) {
				java.util.List<XWPFTableRow> trl = t.getRows();
				ListIterator<XWPFTableRow> trli = trl.listIterator();
				this.rows = new ArrayList<DocTabRow>();
				XWPFTableRow tr;
				while (trli.hasNext()) {
					tr = trli.next();
					this.rows.add(new DocTabRow(tr));
				}
			}
			public String toString() {
				StringBuffer res = new StringBuffer("<Table>\n");
				ListIterator<DocTabRow> tri = rows.listIterator();
				while (tri.hasNext()) {
					res.append(tri.next().toString()+"\n");
				}
				res.append("</Table>");
				return res.toString();
			}
			public String toJSON() {
				StringBuffer res = new StringBuffer("{\"objecttype\": \"DocPartTable\", \"rows\": [");
				ListIterator<DocTabRow> tri = rows.listIterator();
				boolean first = true;
				while (tri.hasNext()) {
					if (!first) {
						res.append(", ");
					} else {
						first = false;
					}
					res.append(tri.next().toJSON());
				}
				res.append("]}");
				return res.toString();
			}
		}
		
		private class DocPartList extends DocPart {
			ArrayList<DocPart> items;
			public DocPartList(String theStyle, DocPartType theType) {
				super(theStyle,theType);
			}
			public String toString() {
				StringBuffer res = new StringBuffer("<DocPartList>");
				ListIterator<DocPart> dpli = items.listIterator();
				while (dpli.hasNext()) {
					res.append("<ListItem>"+dpli.toString()+"</ListItem>");
				}
				res.append("</DocPartList>\n");
				return res.toString();
			}
			public String toJSON() {
				StringBuffer res = new StringBuffer("{\"objecttype\": \"DocPartList\", listitems: [");
				ListIterator<DocPart> dpli = items.listIterator();
				boolean first = true;
				while (dpli.hasNext()) {
					if (!first) {
						res.append(", ");
						first = false;
					} else {
						first = false;
					}
					res.append("{\"objecttype\": \"DocPartListItem\", "+dpli.next().toJSON()+"}");
				}
				res.append("]}");
				return res.toString();
			}
		}
		
		private class DocPartDoc extends DocPart {
			Document doc;
			public DocPartDoc(String theStyle, DocPartType theType, Document theDoc) {
				super(theStyle,theType);
				this.doc = theDoc;
			}
			public String toString() {
				return this.doc.toString();
			}
			public String toJSON() {
				return this.doc.toJSON();
			}
		}
		
		private class Document {
			ArrayList<DocPart> parts;
			public Document() {
				this.parts = new ArrayList<DocPart>();
			}
			public Document(String doctxt) {
				this.parts = new ArrayList<DocPart>();
				this.addPart(new DocPartParagraph(doctxt));
			}
			public void addBodyElement(IBodyElement be) {
				BodyElementType bet = be.getElementType();
				switch (bet) {
					case PARAGRAPH:
						XWPFParagraph p = (XWPFParagraph)be;
						String pstyle = p.getStyle();
						parts.add(new DocPartParagraph(pstyle,DocPartType.PARA,(XWPFParagraph)be));
						break;
					case TABLE:
						XWPFTable t = (XWPFTable)be;
						String tstyle = t.getStyleID();
						parts.add(new DocPartTable(tstyle,DocPartType.TABLE,t));
						break;
					default:
						Integer ubetid = unknownStyles.get(bet);
						if (ubetid==null) {
							System.err.println("Unknown BodyElementType \""+bet.toString()+"\"");
							unknownBeTypenames.put(bet.toString(),unknownBeTypeNumber++);
						}
				}
			}
			public Document(java.util.List<IBodyElement> bel) {
				this.parts = new ArrayList<DocPart>();
				ListIterator<IBodyElement> beli = bel.listIterator();
				IBodyElement be;
				BodyElementType bet;
				Integer ubetid;
				while (beli.hasNext()) {
					be = beli.next();
					this.addBodyElement(be);
				}
				
			}
			public void addPart(DocPart thePart) {
				parts.add(thePart);
			}
			public String toString() {
				StringBuffer res = new StringBuffer("<Document>");
				ListIterator<DocPart> dpi = parts.listIterator();
				while (dpi.hasNext()) {
					res.append(dpi.next().toString());
				}
				res.append("</Document>");
				return res.toString();
			}
			public String toJSON() {
				StringBuffer res = new StringBuffer("{\"objecttype\": \"Document\", \"DocParts\": [");
				ListIterator<DocPart> dpi = parts.listIterator();
				boolean first = true;
				while (dpi.hasNext()) {
					if (!first) {
						res.append(", ");
					} else {
						first = false;
					}
					res.append(dpi.next().toJSON());
				}
				res.append("]}");
				return res.toString();
			}
			public void debugDump(PrintStream ps, int il) {
				ps.println(indent(il)+this.toString());
			}
		}

		private class QCriterion {
			public String index;
			public String description;
			public QCriterion(String ix, String dsc) {
				this.index = ix;
				this.description = dsc;
			}
			public void debugDump(PrintStream ps, int il) {
				ps.println(indent(il)+"<QCriterion><Index>"+this.index+"</Index><Description>"+this.description+"</Description><QCriterion>");
			}
			public String toJSON() {
				return "{\"objecttype\": \"QCriterion\", \"Index\": \""+this.index+"\", \"Description\": \""+this.description+"\"}";
			}
		}
		
		private class QCriteria {
			private ArrayList<QCriterion> criteria;
			private int maxindex = 0;
			public QCriteria() {
				criteria = new ArrayList<QCriterion>();
			}
			public void addCriterion(String idx, String desc) {
				// System.err.println("QCriteria.add.IDX=\""+idx+"\".DESC=\""+desc+"\"");
				criteria.add(new QCriterion(idx,desc));
			}
			public void addBodyElement(IBodyElement be) {
				BodyElementType bet = be.getElementType();
				if (bet==BodyElementType.PARAGRAPH) {
					XWPFParagraph p = (XWPFParagraph)be;
					// System.err.printf("Qcriteria: Style: %s\n",p.getStyle());
					String ids = Character.toString((char)(maxindex+Character.codePointAt("A",0)));
					maxindex++;
					String dsc = p.getText();
					this.addCriterion(ids,dsc);
				} else {
					System.err.printf("QCriteria: body element type %s ignored\n",bet.toString());
				}
			}
			public void debugDump(PrintStream ps, int il) {
				ps.println(indent(il)+"<QCriteria>");
				ListIterator<QCriterion> qci = this.criteria.listIterator();
				while (qci.hasNext()) {
					qci.next().debugDump(ps,il+1);
				}
				ps.println(indent(il)+"</QCriteria>");
			}
			public String toJSON() {
				StringBuffer res = new StringBuffer("{\"objecttype\": \"QCriteria\", \"criteria\": [");
				ListIterator<QCriterion> qci = this.criteria.listIterator();
				boolean first = true;
				while (qci.hasNext()) {
					if (!first) {
						res.append(", ");
					} else {
						first = false;
					}
					res.append(qci.next().toJSON()+"\n");
				}
				res.append("]}\n");
				return res.toString();
			}
		}
		
		private class QPruefart {
			public String pruefart;
			public QPruefart(String theArt) {
				this.pruefart = theArt.replace("\t"," ").replace("\n","\\n");
			}
			public void debugDump(PrintStream ps, int il) {
				ps.println(indent(il)+"  "+pruefart);
			}
			public String toJSON() {
				return "{\"objecttype\": \"QPruefart\", \"pruefart\": \""+pruefart+"\"}";
			}
		}
		
		private class QPruefarten {
			ArrayList<QPruefart> arten;
			public QPruefarten() {
				arten = new ArrayList<QPruefart>();
			}
			public void addPruefart(QPruefart theArt) {
				arten.add(theArt);
			}
			public void addBodyElement(IBodyElement be) {
				BodyElementType bet = be.getElementType();
				if (bet==BodyElementType.PARAGRAPH) {
					XWPFParagraph p = (XWPFParagraph)be;
					if (p!=null) {
						String pt = p.getText();
						if (pt!=null) {
							this.addPruefart(new QPruefart(p.getText()));
						}
					}
				} else {
					System.err.printf("QPruefarten: body element type %s ignored\n",bet.toString());
				}
			}
			public void debugDump(PrintStream ps, int il) {
				ListIterator<QPruefart> pali = arten.listIterator();
				while (pali.hasNext()) {
					ps.println(indent(il)+"<Pruefart>");
					pali.next().debugDump(ps,il+1);
					ps.println(indent(il)+"</Pruefart>");
				}
			}
			public String toJSON() {
				ListIterator<QPruefart> pali = arten.listIterator();
				StringBuffer res = new StringBuffer("{\"objecttype\": \"QPruefarten\", \"pruefarten\": [");
				boolean first = true;
				while (pali.hasNext()) {
					if (!first) {
						res.append(", ");
					} else {
						first = false;
					}
					res.append(pali.next().toJSON()+"\n");
				}
				res.append("]}\n");
				return res.toString();
			}
		}
		
		private class Erfuellungsbedingung {
			String critname;
			Document bedingung;
			public Erfuellungsbedingung(String theCritName, Document theBedingung) {
				this.critname = theCritName;
				this.bedingung = theBedingung;
			}
			public void addBodyElement(IBodyElement be) {
				BodyElementType bet = be.getElementType();
				if (bet==BodyElementType.PARAGRAPH) {
					XWPFParagraph p = (XWPFParagraph)be;
					System.err.printf("Erfuellungsbedingung: addBodyElement() of type PARAGRAPH not yet implemented, text=\"%s\"\n",p.getText());
				} else {
					System.err.printf("Erfuellungsbedingung: body element type %s ignored\n",bet.toString());
				}
			}
			public void debugDump(PrintStream ps, int il) {
				ps.println(indent(il)+"<Erfuellungsbedingung><Kriterium>"+this.critname+"</Kriterium><Bedingung>"+bedingung.toString()+"</Bedingung></Erfuellungsbedingung>");
			}
			public String toJSON() {
				return "{\"objecttype\": \"Erfuellungsbedingung\", \"Kriterium\": \""+this.critname+"\", \"Bedingung\": "+bedingung.toJSON()+"}";
			}
		}
		
		private class Erfuellungsbedingungen {
			ArrayList<Erfuellungsbedingung> bedingungen;
			public Erfuellungsbedingungen() {
				bedingungen = new ArrayList<Erfuellungsbedingung>();
			}
			public void addBedingung(Erfuellungsbedingung theBedingung) {
				bedingungen.add(theBedingung);
			}
			public void addBodyElement(IBodyElement be) {
				BodyElementType bet = be.getElementType();
				if (bet==BodyElementType.PARAGRAPH) {
					XWPFParagraph p = (XWPFParagraph)be;
					String ptxt = p.getText();
					if (ptxt!=null) {
						int colpos = ptxt.indexOf(":");
						if (colpos>=0) {
							String critnam = ptxt.substring(0,colpos);
							String critval = ptxt.substring(colpos+1);
							Erfuellungsbedingung crit = new Erfuellungsbedingung(critnam,new Document(critval));
							this.addBedingung(crit);
						} else {
							System.err.printf("Erfuellungsbedingungen: paragraph has no colon to separate Criterion from condition: \"%s\"\n",ptxt);
						}
					}
				} else {
					System.err.printf("Erfuellungsbedingungen: body element type %s ignored\n",bet.toString());
				}
			}
			public void debugDump(PrintStream ps,int il) {
				ListIterator<Erfuellungsbedingung> li = bedingungen.listIterator();
				ps.println(indent(il)+"<Erfuellungsbedingungen>");
				while (li.hasNext()) {
					li.next().debugDump(ps,il+1);
				}
				ps.println(indent(il)+"</Erfuellungsbedingungen>");
			}
			public String toJSON() {
				ListIterator<Erfuellungsbedingung> li = bedingungen.listIterator();
				StringBuffer res = new StringBuffer("{\"objecttype\": \"Erfuellungsbedingungen\", \"bedingungen\": [");
				boolean first = true;
				while (li.hasNext()) {
					if (!first) {
						res.append(", ");
					} else {
						first = false;
					}
					res.append(li.next().toJSON());
				}
				res.append("]}\n");
				return res.toString();
			}
		}
				
		private class ISO25010Einordnung {
			/*
				Funktionale Eignung
				Effizienz
				Kompatibilität
				Benutzbarkeit
				Zuverlässigkeit
				Sicherheit
				Wartbarkeit
				Portierbarkeit
				Sonstiges
			*/
			public final static int FUNC 	= 0;
			public final static int EFFIC 	= 1;
			public final static int COMPAT 	= 2;
			public final static int USAB 	= 3;
			public final static int RELIAB 	= 4;
			public final static int SEC 	= 5;
			public final static int MAINT 	= 6;
			public final static int PORTAB 	= 7;
			public final static int OTHER 	= 8;
			public boolean isZugeordnet[] = { false, false, false, false, false, false, false, false, false };
			public void ISO25010Einordnung() {
			}
			public void setEinordnung(int theIndex, boolean theEinordnung) {
				this.isZugeordnet[theIndex] = theEinordnung;
			}
			public void addBodyElement(IBodyElement be) {
				BodyElementType bet = be.getElementType();
				if (bet==BodyElementType.TABLE) {
					XWPFTable t = (XWPFTable)be;
					java.util.List<XWPFTableRow> trl = t.getRows();
					ListIterator<XWPFTableRow> trli = trl.listIterator();
					XWPFTableRow tr;
					String nam = null;
					String val = null;
					while (trli.hasNext()) {												// iterate over all rows
						tr = trli.next();
						XWPFTableCell tc;
						int colnum = 0;
						java.util.List<XWPFTableCell> tcl = tr.getTableCells();
						ListIterator<XWPFTableCell> tcli = tcl.listIterator();
						while (tcli.hasNext()) {											// iteate over all columns, 1st is Zuordnungsbereich, 2nd is checkmark
							tc = tcli.next();
							java.util.List<IBodyElement> tcbel = tc.getBodyElements();
							ListIterator<IBodyElement> tcbeli = tcbel.listIterator();
							if (tcbeli.hasNext()) {											// cell paragraph has at least one body element
								IBodyElement cbe = tcbeli.next();
								BodyElementType cbet = cbe.getElementType();
								if (cbet==BodyElementType.PARAGRAPH) {
									XWPFParagraph cbep = (XWPFParagraph)cbe;
									if (colnum==0) {										// 1st column is Zuordnungsbereich
										nam = cbep.getText();
									} else if (colnum==1) {									// 2nd column is checkmark, not empty means "TRUE"
										val = cbep.getText();
										if (val==null) val="";
									}
								}
							}
							colnum++;
						}
						if (nam!=null) {
							if (val==null) val = "";
							for (int zi=0; zi<zuordnungsNamen.length; zi++) {
								if (nam.equals(zuordnungsNamen[zi])) {
									if (!(val.equals(""))) {
										this.isZugeordnet[zi] = true;
									}
								}
							}
						}
					}
				} else {
					System.err.printf("ISO25010Einordnung: body element type %s ignored\n",bet.toString());
				}
			}
			public void debugDump(PrintStream ps, int il) {
				ps.println(indent(il)+"<ISO25010Einordnungen>");
				for (int i=0; i<this.isZugeordnet.length; i++) {
					ps.printf(indent(il+1)+"<ISO25010Einordnung><Bereich>%s</Bereich><Zugeordnet>%s</Zugeordnet><ISO25010Einordnung>\n",zuordnungsNamen[i],this.isZugeordnet[i]?"TRUE":"FALSE");
				}
				ps.println(indent(il)+"</ISO25010Einordnungen>");
			}
			public String toJSON() {
				StringBuffer res = new StringBuffer("{\"objecttype\": \"ISO25010Einordnungen\", \"einordnungen\": [");
				boolean first = true;
				for (int i=0; i<this.isZugeordnet.length; i++) {
					if (!first) {
						res.append(", ");
					} else {
						first = false;
					}
					res.append("{\"Bereich\": \""+zuordnungsNamen[i]+"\", \"Zugeordnet\": \""+((this.isZugeordnet[i])?"TRUE":"FALSE")+"\"}");
				}
				res.append("]}");
				return res.toString();
			}
		}
		
		private class QObject {
			public String objectname;
			public Document zweck;
			public Document beschreibung;
			public QCriteria criteria;
			public ISO25010Einordnung einordnung;
			public QPruefarten pruefarten;
			public Erfuellungsbedingungen erfuellungsbedingungen;
			public Document guidance;
			public Document abhaengigkeiten;
			public QObject(String theName) {
				this.objectname = theName;
				this.zweck = new Document();
				this.beschreibung = new Document();
				this.criteria = new QCriteria();
				this.einordnung = new ISO25010Einordnung();
				this.pruefarten = new QPruefarten();
				this.erfuellungsbedingungen = new Erfuellungsbedingungen();
				this.guidance = new Document();
				this.abhaengigkeiten = new Document();
			}
			public String getName() {
				return objectname;
			}
			public void debugDump(PrintStream ps, int il) {
				ps.println(indent(il)+"<QObject name=\""+this.objectname+"\">");
				ps.println(indent(il+1)+"<Zweck>");					this.zweck.debugDump(ps,il+2); 					ps.println(indent(il+1)+"</Zweck>");
				ps.println(indent(il+1)+"<BeschreibungThema>"); 	this.beschreibung.debugDump(ps,il+2);			ps.println(indent(il+1)+"</BeschreibungThema>");
																	this.criteria.debugDump(ps,il+1);
																	this.pruefarten.debugDump(ps,il+1);
																	this.einordnung.debugDump(ps,il+1);
																	this.erfuellungsbedingungen.debugDump(ps,il+1);
				ps.println(indent(il+1)+"<Guidance>");				this.guidance.debugDump(ps,il+2);				ps.println(indent(il+1)+"</Guidance>");
				ps.println(indent(il+1)+"<Abhaengigkeiten>");		this.abhaengigkeiten.debugDump(ps,il+2);		ps.println(indent(il+1)+"</Abhaengigkeiten>");
				ps.println(indent(il)+"</QObject>");
			}
			public String toJSON() {
				return 	"{\"objecttype\": \"QObject\", \"name\": \""+this.objectname+"\", \n"+
						"\"Zweck\": "+this.zweck.toJSON()+", \n"+
						"\"BeschreibungThema\": "+this.beschreibung.toJSON()+", \n"+
						"\"QCriteria\": "+this.criteria.toJSON()+", \n"+
						"\"Pruefarten\": "+this.pruefarten.toJSON()+", \n"+
						"\"ISO25010Einordnung\": "+this.einordnung.toJSON()+", \n"+
						"\"Erfuellungsbedingungen\": "+this.erfuellungsbedingungen.toJSON()+", \n"+
						"\"Guidance\": "+this.guidance.toJSON()+", \n"+
						"\"Abhaengigkeiten\": "+this.abhaengigkeiten.toJSON()+"}";
			}
		}
							
		private class LifeCycleStage {
			public String stagename;
			public ArrayList<QObject> qobjects;
			
			public LifeCycleStage(String name) {
				this.stagename = name;
				this.qobjects = new ArrayList<QObject>();
			}
			public void addQObject(QObject theObject) {
				this.qobjects.add(theObject);
			}
			public String getName() {
				return stagename;
			}
			public void debugDump(PrintStream ps, int il) {
				ps.println(indent(il)+"<LifeCycleStage name=\""+this.stagename+"\">");
				ListIterator<QObject> qoi = this.qobjects.listIterator();
				while (qoi.hasNext()) {
					qoi.next().debugDump(ps,il+1);
				}
				ps.println(indent(il)+"</LifeCycleStage>");
			}
			public String toJSON() {
				StringBuffer res = new StringBuffer("{\"objecttype\": \"LifeCycleStage\", \"name\": \""+this.stagename+"\", \"QObjects\": [");
				ListIterator<QObject> qoi = this.qobjects.listIterator();
				boolean first = true;
				while (qoi.hasNext()) {
					if (!first) {
						res.append(", ");
					} else {
						first = false;
					}
					res.append(qoi.next().toJSON()+"\n");
				}
				res.append("]}\n");
				return res.toString();
			}
		}
		
		private class QHB {
			public StateMachine machine;
			private State 		enginestate = State.SCAN;
			private ArrayList<LifeCycleStage> lifecycles = new ArrayList<LifeCycleStage>();		
			private LifeCycleStage	currentlcstage = null;
			private QObject			currentqobject = null;		
			public QHB() {
				StateTransition gr[] = {
					new StateTransition(State.SCAN,		BodyElementType.PARAGRAPH,	"berschrift1",	"Qualitätsanforderungen.*",State.LCSLIST,null), 
					new StateTransition(State.LCSLIST,	BodyElementType.PARAGRAPH,	"berschrift2",	null,State.QOLIST, TransAct.MEMLCSTAGE),
					new StateTransition(State.QOLIST,	BodyElementType.PARAGRAPH,	"berschrift3",	null,State.QOATTRIB, TransAct.MEMQOBJECT),
					new StateTransition(State.QOATTRIB,	BodyElementType.PARAGRAPH,	"berschrift4",	"Zweck und Nutzen.*",State.QOAZWECK, TransAct.MEMZWECK),
					new StateTransition(State.QOAZWECK,	BodyElementType.PARAGRAPH,	"berschrift4",	"Beschreibung des Themas.*",State.QOABESCH, TransAct.MEMBESCH),
					new StateTransition(State.QOAZWECK,	BodyElementType.PARAGRAPH,	"berschrift3",	null,State.QOATTRIB, TransAct.MEMQOBJECT),
					new StateTransition(State.QOAZWECK,	BodyElementType.PARAGRAPH,	"berschrift2",	null,State.QOLIST, TransAct.MEMLCSTAGE),
					new StateTransition(State.QOAZWECK,	BodyElementType.PARAGRAPH,	"berschrift1",	null,State.SCAN,null),
					new StateTransition(State.QOAZWECK,	null,						null,			null,null,TransAct.ADDZWECK),
					new StateTransition(State.QOABESCH,	BodyElementType.PARAGRAPH,	"berschrift4",	"Beschreibung des Q-Krit.*",State.QOAKRIT, TransAct.MEMKRIT),
					new StateTransition(State.QOABESCH,	null,						null,			null,null,TransAct.ADDBESCH),
					new StateTransition(State.QOAKRIT,	BodyElementType.PARAGRAPH,	"berschrift4",	"Einordnung nach ISO.*",State.QOAEINORD, TransAct.MEMEINORD),
					new StateTransition(State.QOAKRIT,	null,						null,			null,null,TransAct.ADDKRIT),
					new StateTransition(State.QOAEINORD,BodyElementType.PARAGRAPH,	"berschrift4",	"Arten der Überprüfung.*",State.QOAPRUEF, TransAct.MEMPRUEF),
					new StateTransition(State.QOAEINORD,null,						null,			null,null,TransAct.ADDEINORD),
					new StateTransition(State.QOAPRUEF,	BodyElementType.PARAGRAPH,	"berschrift4",	"Bedingungen, wann das Q-Kriterium erfüllt.*",State.QOAERF, TransAct.MEMERF),
					new StateTransition(State.QOAPRUEF,	null,						null,			null,null,TransAct.ADDPRUEF),
					new StateTransition(State.QOAERF,	BodyElementType.PARAGRAPH,	"berschrift4",	"Guid.*",State.QOAGUID, TransAct.MEMGUID),
					new StateTransition(State.QOAERF,	null,						null,			null,null,TransAct.ADDERF),
					new StateTransition(State.QOAGUID,	BodyElementType.PARAGRAPH,	"berschrift4",	"Abhängigkeit.*",State.QOAABH, TransAct.MEMABH),
					new StateTransition(State.QOAGUID,	BodyElementType.PARAGRAPH,	"berschrift4",	"Guid.*",State.QOAGUID, TransAct.MEMGUID),
					new StateTransition(State.QOAGUID,	BodyElementType.PARAGRAPH,	"berschrift4",	"Bedingungen, wann das Q-Kriterium erfüllt.*",State.QOAERF, TransAct.MEMERF),
					new StateTransition(State.QOAGUID,	BodyElementType.PARAGRAPH,	"berschrift3",	null,State.QOATTRIB, TransAct.MEMQOBJECT),
					new StateTransition(State.QOAGUID,	BodyElementType.PARAGRAPH,	"berschrift2",	null,State.QOLIST, TransAct.MEMLCSTAGE),
					new StateTransition(State.QOAGUID,	BodyElementType.PARAGRAPH,	"berschrift1",	null,State.SCAN,null),
					new StateTransition(State.QOAGUID,	null,						null,			null,null,TransAct.ADDGUID),
					new StateTransition(State.QOAABH,	BodyElementType.PARAGRAPH,	"berschrift4",	"Guid.*",State.QOAGUID, TransAct.MEMGUID),
					new StateTransition(State.QOAABH,	BodyElementType.PARAGRAPH,	"berschrift3",	null,State.QOATTRIB, TransAct.MEMQOBJECT),
					new StateTransition(State.QOAABH,	BodyElementType.PARAGRAPH,	"berschrift2",	null,State.QOLIST, TransAct.MEMLCSTAGE),
					new StateTransition(State.QOAABH,	BodyElementType.PARAGRAPH,	"berschrift1",	null,State.SCAN,null),
					new StateTransition(State.QOAABH,	null,						null,			null,null,TransAct.ADDABH)
				};
				this.machine = new StateMachine(gr);
			}
			private void memorizeLCstage(XWPFParagraph p) {
				currentlcstage = new LifeCycleStage(p.getText());
				lifecycles.add(currentlcstage);
			}
			private void memorizeQObject(XWPFParagraph p) {
				if (currentlcstage==null) {
					currentlcstage = new LifeCycleStage("Unbekannter Lifecycle-Name");
					lifecycles.add(currentlcstage);
				}
				currentqobject = new QObject(p.getText());
				currentlcstage.addQObject(currentqobject);
			}
			private void executeTransAction(TransAct sttr, IBodyElement be) {
				if (sttr!=null) {
					switch (sttr) {
						case MEMLCSTAGE:	this.memorizeLCstage((XWPFParagraph)be); 									break;
						case MEMQOBJECT:	this.memorizeQObject((XWPFParagraph)be); 									break;
						case MEMZWECK:		this.currentqobject.zweck = new Document();									break;
						case ADDZWECK:		this.currentqobject.zweck.addBodyElement(be);								break;
						case MEMBESCH:		this.currentqobject.beschreibung = new Document();							break;
						case ADDBESCH:		this.currentqobject.beschreibung.addBodyElement(be);						break;
						case MEMKRIT:		this.currentqobject.criteria = new QCriteria();								break;
						case ADDKRIT:		this.currentqobject.criteria.addBodyElement(be);							break;
						case MEMPRUEF:		this.currentqobject.pruefarten = new QPruefarten();							break;
						case ADDPRUEF:		this.currentqobject.pruefarten.addBodyElement(be);							break;
						case MEMEINORD:		this.currentqobject.einordnung = new ISO25010Einordnung();					break;
						case ADDEINORD:		this.currentqobject.einordnung.addBodyElement(be);							break;
						case MEMERF:		this.currentqobject.erfuellungsbedingungen = new Erfuellungsbedingungen();	break;
						case ADDERF:		this.currentqobject.erfuellungsbedingungen.addBodyElement(be);				break;
						case MEMGUID:		this.currentqobject.guidance = new Document();								break;
						case ADDGUID:		this.currentqobject.guidance.addBodyElement(be);							break;
						case MEMABH:		this.currentqobject.abhaengigkeiten = new Document();						break;
						case ADDABH:		this.currentqobject.abhaengigkeiten.addBodyElement(be);						break;
						case ERREXIT:		System.err.println("STTR_Error_Exit"); System.exit(-1);						break;
						default:
							System.err.println("Illegal StateTransition Code "+sttr);
					}
				}
			}
			public void processBodyElement(IBodyElement be) {
				BodyElementType bet;
				bet = be.getElementType();
				StateTransition sttr;
				State tempstate;
				String ptext = (bet==BodyElementType.PARAGRAPH?((XWPFParagraph)be).getText():"");
				String stylename =(bet==BodyElementType.PARAGRAPH?((XWPFParagraph)be).getStyle():(bet==BodyElementType.TABLE?((XWPFTable)be).getStyleID():""));
				tempstate = this.enginestate;
				if ((sttr=this.machine.findSttr(this.enginestate,bet,stylename,ptext))!=null) {
					if (sttr.newstate!=null) {
						this.enginestate = sttr.newstate;
					}
					executeTransAction(sttr.sttr,be);
				}
				System.err.printf("PROCESS_BODY_ELEMENT.ENGINESTATE=%s.BODYELEMENTTYPE=%s.STYLE=%s.NEWSTATE=%s.TRANSACT=%s.TEXT=%s\n",
				                  tempstate.toString(),bet.toString(),stylename,enginestate.toString(),(sttr==null)?"NONE":(sttr.sttr==null)?"NULL":sttr.sttr.toString(),
								  (ptext.length()>40)?ptext.substring(0,40)+"...":ptext);
			}
			public void debugDump(PrintStream ps, int il) {
				ps.println("<QHB>");
				ListIterator<LifeCycleStage> lci = this.lifecycles.listIterator();
				while (lci.hasNext()) {
					lci.next().debugDump(ps,il+1);
				}
				ps.println("</QHB>");
			}
			public String toJSON() {
				StringBuffer res = new StringBuffer("{\"objecttype\": \"QHB\", \"LifeCycleStages\": [");
				ListIterator<LifeCycleStage> lci = this.lifecycles.listIterator();
				boolean first = true;
				while (lci.hasNext()) {
					if (!first) {
						res.append(", ");
					} else {
						first = false;
					}
					res.append(lci.next().toJSON());
				}
				res.append("]}\n");
				return res.toString();
			}
		}
		
		private static Hashtable<String, Integer> unknownStyles = new Hashtable<String, Integer>();
		private static Integer unknownStyleNumber = new Integer(0);
		private static Hashtable<String, Integer> unknownBeTypenames = new Hashtable<String, Integer>();
		private static Integer unknownBeTypeNumber = new Integer(0);
		private static boolean listActive = false;
		private static ListType activeListType = ListType.NUMLIST;
		private static int strucLevels[] = {0,0,0,0,0,0};
		private static int TOCLevels[] = {0,0,0,0,0,0};
		
		private static String curLevelPath(int lvls[], Integer level) {
			int cl = 0;
			String path = "";
			while (cl<level) {
				if (cl>0) {
					path += ".";
				}
				path += lvls[cl];
				cl++;
			}
			return path;
		}
		
		private static void incLevel(int lvls[], Integer level) {
			lvls[level-1]++;
			for (int cl = level; cl<lvls.length; cl++) {
				lvls[cl] = 0;
			}
		}
		
		private static void printMarkup(String txt) {
			System.out.println(txt);
		}
		
		private static void printContent(String txt) {
			System.out.println(txt.replace("<", "&lt;").replace("&", "&amp;"));
		}
		
		private static void printComment(String txt) {
			printMarkup("<!-- "+txt+" -->");
		}
		
		private static void printElement(String tag, String txt) {
			printMarkup("<"+tag+">");
			printContent(txt);
			printMarkup("</"+tag+">");
		}
		
		private static void closeList(ListType listtype) {
			if (listActive) {
				printMarkup((listtype==ListType.NUMLIST)?"</ol>":"</ul>");
				listActive = false;
			}
		}
		
		private static void closeList() {
			closeList(activeListType);
		}
		
		private static void openList(ListType listtype) {
			if (listActive) {
				closeList();
			}
			printMarkup((listtype==ListType.NUMLIST)?"<ol>":"<ul>");
			activeListType = listtype;
			listActive = true;
		}
				
		private static void printListItem(XWPFParagraph p, ListType listtype) {
			if (!listActive) {
				openList(listtype);
			}
			if (activeListType!=listtype) {
				closeList();
				openList(listtype);
			}
			printComment("List Item Number Format: "+p.getNumFmt());
			printComment("List Item Number Level: "+p.getNumIlvl());
			printElement("li",p.getText()); 
		}
		
		private static void printHeader(Integer level, XWPFParagraph p) {
			closeList();
			printComment("Header Level "+level+" Number Format: "+p.getNumFmt());
			printComment("Header Level "+level+" Number Level: "+p.getNumIlvl());
			incLevel(strucLevels, level);
			printMarkup("<a name=\""+curLevelPath(strucLevels,level)+"\">");
			printElement("h"+level,curLevelPath(strucLevels,level)+" "+p.getText());
			printMarkup("</a>");
		}
		
		private static void printTOCentry(Integer level, XWPFParagraph p) {
			closeList();
			printComment("TOC entry Level "+level+" Number Format: "+p.getNumFmt());
			printComment("TOC entry Level "+level+" Number Level: "+p.getNumIlvl());
			incLevel(TOCLevels,level);;
			printMarkup("<a href=\"#"+curLevelPath(TOCLevels,level)+"\">");
			printElement("p",curLevelPath(TOCLevels,level)+" - "+p.getText());
			printMarkup("</a>");
		}
			
		private static void printParagraph(XWPFParagraph p) {
			Integer usid;
			String sn = p.getStyle();
			String sid = p.getStyleID();
			if (sid==null) sid = "Standard";
			switch (sid) {
				case "berschrift1":		printHeader(1,p); break;
				case "berschrift2":		printHeader(2,p); break;
				case "berschrift3":		printHeader(3,p); break;
				case "berschrift4":		printHeader(4,p); break;
				case "berschrift5":		printHeader(5,p); break;
				case "berschrift6":		printHeader(6,p); break;
				case "Verzeichnis1":	printTOCentry(1,p); break;
				case "Verzeichnis2":	printTOCentry(2,p); break;
				case "Verzeichnis3":	printTOCentry(3,p); break;
				case "Verzeichnis4":	printTOCentry(4,p); break;
				case "Verzeichnis5":	printTOCentry(5,p); break;
				case "Verzeichnis6":	printTOCentry(6,p); break;
				case "Listenabsatz":	printListItem(p,ListType.BULLIST); break;
				case "Listennummer":	printListItem(p,ListType.NUMLIST); break;
				default:
					usid = unknownStyles.get(sid);
					if (usid==null) {
						System.err.println("Unknown Style ID \""+sid+"\"");
						unknownStyles.put(sid,unknownStyleNumber++);
					}
					closeList();
					printComment("Unknown Style: \""+sid+"\"");
					printElement("p",p.getText());
					break;
			}
		}	
		
		private static void printTable(IBodyElement be) {
			XWPFTable t = (XWPFTable) be;
			printMarkup("<table border=\"1\">");
			printMarkup("<!-- Table Style-ID: "+t.getStyleID()+" -->");
			java.util.List<XWPFTableRow> trl = t.getRows();
			ListIterator<XWPFTableRow> trli = trl.listIterator();
			XWPFTableRow tr;
			while (trli.hasNext()) {
				printMarkup("<tr>");
				tr = trli.next();
				XWPFTableCell tc;
				java.util.List<XWPFTableCell> tcl = tr.getTableCells();
				ListIterator<XWPFTableCell> tcli = tcl.listIterator();
				while (tcli.hasNext()) {
					tc = tcli.next();
					printMarkup("<td>");
					printBodyElements(tc.getBodyElements(),null);
					printMarkup("</td>");
				}
				printMarkup("<tr>");
			}
			printMarkup("</table>");
		}
		
		private static void printBodyElements(java.util.List<IBodyElement> bel,QHB qhb) {
			ListIterator<IBodyElement> beli = bel.listIterator();
			IBodyElement be;
			BodyElementType bet;
			Integer ubetid;
			while (beli.hasNext()) {
				be = beli.next();
				bet = be.getElementType();
				switch (bet) {
					case PARAGRAPH:
						printParagraph((XWPFParagraph)be);
						break;
					case TABLE:
						printTable(be);
						break;
					default:
						ubetid = unknownBeTypenames.get(bet);
						if (ubetid==null) {
							System.err.println("Unknown BodyElementType \""+bet.toString()+"\"");
							unknownBeTypenames.put(bet.toString(),unknownBeTypeNumber++);
						}
				}
				if (qhb!=null) {
					qhb.processBodyElement(be);
				}
			}
			closeList();
		}
		
		public static void debugDumpQHB(QHB qhb) {
			System.err.println("debugDumpQHB:");
			qhb.debugDump(System.err,0);
		}
		
		private static QHB qhb;
		
        public static void main(String[] args) throws Exception{
				if (args.length<1) {
					System.err.println("docx2html: "+args.length+" arguments");
					System.err.println("Usage: docx2html <docx-file-name>");
					System.exit(-1);
				}					
                /* Read the input file that contains the document */
                FileInputStream input_document = new FileInputStream(new File(args[0]));    
                /* Create a POI XWFPDocument Object from the input file */
                XWPFDocument doc = new XWPFDocument(input_document); 
				System.err.println("document "+args[0]+" opened");
				docx2html me = new docx2html();
				qhb = me.new QHB();
				printMarkup("<!doctype html>");
				printMarkup("<html class=\"no-js\" lang=\"\">");
				printMarkup("<head>");
				printMarkup("  <meta charset=\"utf-8\">");
				printMarkup("  <meta http-equiv=\"x-ua-compatible\" content=\"ie=edge\">");
				printMarkup("</head>");
				printMarkup("<body>");
				printBodyElements(doc.getBodyElements(),qhb); 
				printMarkup("</body>");				
                input_document.close(); 
				System.err.println("input document closed");
				debugDumpQHB(qhb);
				System.err.println("---- JSON QHB:");
				System.err.println(qhb.toJSON());
				System.err.println("---- end of JSON QHB");
        }
}