//===============================================================
// package : com.tetrasix.majix.rtf
// class : com.tetrasix.majix.RtfAnalyser
//===============================================================
// The contents of this file are subject to the Mozilla Public License
// Version 1.1 (the "License"); you may not use this file except in
// compliance with the License. You may obtain a copy of the License at
// http://www.mozilla.org/MPL/
//
// Software distributed under the License is distributed on an "AS IS"
// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
// License for the specific language governing rights and limitations
// under the License.
//
// The Original Code is TetraSys code.
//
// The Initial Developer of the Original Code is TetraSys..
// Portions created by TetraSys are 
// Copyright (C) 1998-2000 TetraSys. All Rights Reserved.
//
// Contributor(s): Arne Jans, Quinscape GmbH (www.quinscape.de)
//===============================================================


/**
 *
 * @version 1.1
 */


package vkc.converter.rtf;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.List;
import java.util.Vector;

public class RtfAnalyser {

	public static final boolean OUTPUT_DELETED = false;
	public static final int LIMIT_TOLERANCE = 30;

	public static final boolean GENERATE_SECTIONBREAKS = false;
	public static final boolean GENERATE_PAGEBREAKS = false;
	public static final boolean GENERATE_LINEBREAKS = true;

	private RtfReader _reader;
	private RtfDocument _theDocument;
	private RtfInfo _theInfo = null;
	private RtfParagraph _theParagraph;
	private RtfStyleSheet _theStyles;
	private RtfParagraphProperties _theParagraphProperties;
	
	private RtfTextProperties _theTextProperties;
	private RtfTextPropertiesStack _theTextPropertiesStack;

	private RtfExternalEntities _theExternalEntities;

	// print some debug-console-outputs
	private boolean _debug = false;
	private boolean _trace = false;
	private PrintWriter _trace_stream = null;

	private int _pictureCounter = 1;
	private boolean _no_field_result;

	private RtfCompoundObjectStack _theContainerStack;
	private RtfCompoundObject _theContainer;

	// HACK, the bullettext from the group pntext for each list item.
	private String _bulletText = null;
	// HACK, the listtext from the group listtext for each list item.
	private String _listText = null;
	
	private RtfRow _theRow;
	// always contains the properties of the actual row
	private RtfRowProperties _theRowProps;
	
	private RtfCell _theCell;
	// the cellproperties-object for the vector _theCellPropertiesVector
	// always contains the properties of the actual cell
	private RtfCellProperties _theCellProperties;
	
	// the vector with all cellproperties of the actual row 
	private Vector _theCellPropertiesVector;
	private Vector _theCellLimits;

	private int _theTableIndex = -1;
	private int _theColumnIndex = -1;
	private int _theRowIndex = -1;
	
	private List _picIds;
	
/*	private int _cellVAlignment = 0;
	private int _rowBorders = 0;
	private int _cellBorders = 0;
	private boolean _clearCellBorders;
*/	
	private RtfTabDef _theTabDefault = null;
	
	private RtfTable _theTable;

	public RtfAnalyser(RtfReader reader, PrintWriter traceStream)
	{
		_reader = reader;
		_theDocument = new RtfDocument(reader.getFileName());
		_theParagraph = null;
		_theStyles = new RtfStyleSheet();
		_theExternalEntities = new RtfExternalEntities();
		_theDocument.setStyleSheet(_theStyles);
		_theDocument.setExternalEntities(_theExternalEntities);
		_theParagraphProperties = new RtfParagraphProperties();
		_theTextProperties = new RtfTextProperties();
		_theCellPropertiesVector = new Vector();
		// remains here for cellx-cellsize-measurement
		_theCellLimits = new Vector();
		
		_picIds = new ArrayList();

		_theTextPropertiesStack = new RtfTextPropertiesStack();

		_theContainerStack = new RtfCompoundObjectStack();
		_theContainer = _theDocument;

		_theRow  = null;
		_theCell = null;
		_theInfo = new RtfInfo();

		_trace_stream = traceStream;
		if (_trace_stream == null) {
			_trace = false;
		}
	}
	
	public RtfDocument parse() 	{
		RtfToken tok;

		for (;;) {
			try {
				tok = _reader.getNextToken();
				if (tok == null) {
					if (_theParagraph != null) {
						_theContainer.add(_theParagraph);
					}
					// postprocess the data for rowspans
					processRowSpanning();
					// postprocess the data for colspans
					processColSpanning();
					return _theDocument;
				}

				switch (tok.getType()) {

				case RtfToken.ASTERISK: {
					tok = _reader.getNextToken();
					if ((tok.getType() != RtfToken.CONTROLWORD)
							|| (!parseStarControlWord(tok))) {

						skipUntilEndOfGroup();
					}
				}
					break;

				case RtfToken.DATA:
					if (_theParagraph == null) {
						_theParagraph = new RtfParagraph();
					}
					String data = tok.getData();
					// only place that modifies the raw data with regard to the
					// character formatting -> caps
					if (_theTextProperties.isCapitalLetters())
						data = data.toUpperCase();
					// cloning the properties is a hack for other rtfwriters
					// like wordpad.
					// wordpad doesnt surround textblocks in group-braces, so
					// all the text
					// in the main group would get the accumulated
					// textproperties if it wouldnt be
					// cloned.
					_theParagraph.add(new RtfText(data,
							(RtfTextProperties) _theTextProperties.clone()));
					//				_theParagraph.add(new RtfText(data, _theTextProperties));
					break;

				case RtfToken.CONTROLWORD:
					parseControlWord(tok);
					break;

				case RtfToken.OPENGROUP:
					_theTextPropertiesStack.push(_theTextProperties);
					_theTextProperties = (RtfTextProperties) _theTextProperties
							.clone();
					//				last = null;
					break;

				case RtfToken.CLOSEGROUP:
					_theTextProperties = _theTextPropertiesStack.pop();
					//				last = null;
					break;
				}

				if (_trace) {
					_trace_stream.println(">>>" + tok.toString());
					String props = _theTextProperties.toString();
					String stack = _theTextPropertiesStack.toString();
					String data = props;
					if (!stack.equals("")) {
						data += " / " + stack;
					}
					if (!data.equals("")) {
						_trace_stream.println("   " + data);
					}
					if (_theParagraph != null) {
						_trace_stream.print("===");
						_theParagraph.Dump(_trace_stream);
						_trace_stream.println("");
					}
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}

	private boolean parseStarControlWord(RtfToken tok) throws IOException
	{
		String name = tok.getName();

		if (name.equals("bkmkstart")) {
			processBookmark();
			return true;
		}
		else if (name.equals("pn")) {
			processPn();
			return true;
		}
		else if (name.equals("shpinst")) {
			processShpinst();
			return true;
		}
		else if (name.equals("shppict")) {
		    return true;
		}
		else {
			return false;
		}
	}

	private void parseControlWord(RtfToken tok) throws IOException
	{
		String name = tok.getName();

		//System.out.println("\\" + name);
		if (_trace) {
			_trace_stream.println(">>\\" + name);
		}

		if (name.equals("par")) {
			if (_theParagraph == null) {
			    // empty line
				_theParagraph = new RtfParagraph();
			}
			_theParagraph.setProperties(_theParagraphProperties);
			_theContainer.add(_theParagraph);
			_theParagraph = null;
			// reset the row index and save the table
			// when coming to the first paragraph outside a table
			if (!(_theContainer instanceof RtfCell)) {
			    _theRowIndex = -1;
			    if (_theTable != null) {
			        if (_debug) {
			            System.out.println("Saving the table.");
			        }
			        _theContainer.add(_theTable);
			        _theTable = null;
			    }
			}
			
			// TODO test, because lists do not seem to be inherited between paragraphs
//			_theParagraphProperties.resetList();
		}
		else if (name.equals("sect")) {
		    if (GENERATE_SECTIONBREAKS) {
			    // insert the section-break, dont construct new paragraph.
		        insertSectionBreak();
		    } else {
				// section break is considered as paragraph break
				if (_theParagraph == null) {
					_theParagraph = new RtfParagraph();
				}
				_theParagraph.setProperties(_theParagraphProperties);
				_theContainer.add(_theParagraph);
				_theParagraph = null;
		    }
		}
		else if (name.equals("page")) {
		    if (GENERATE_PAGEBREAKS) {
			    // insert the page-break, dont construct new paragraph.
			    insertPageBreak();
		    } else {
				// page break and section break are considered as paragraph break
				if (_theParagraph == null) {
					_theParagraph = new RtfParagraph();
				}
				_theParagraphProperties.setPageBreak(true);
				_theParagraph.setProperties(_theParagraphProperties);
				_theContainer.add(_theParagraph);
				_theParagraph = null;
		    }
		}
		else if (name.equals("pard")) {
			_theParagraphProperties.reset(_theTabDefault);
			if (_listText != null) {
				// TODO remove this listtext-hack
			    // UGLY hack to determine the list type
			    char first = _listText.charAt(0);
	
			    if (Character.isUpperCase(first))
			        _theParagraphProperties.setNumStyle(RtfParagraphProperties.STYLE_NUMBERED_ALPHA_UP);
			    else if (Character.isLetter(first))
			        _theParagraphProperties.setNumStyle(RtfParagraphProperties.STYLE_NUMBERED_ALPHA);
			    else if (Character.isDigit(first))
			        _theParagraphProperties.setNumStyle(RtfParagraphProperties.STYLE_NUMBERED_DEC);
			    else {
			        _theParagraphProperties.setBullet(new Character(first).toString());
			        _theParagraphProperties.setNumStyle(RtfParagraphProperties.STYLE_BULLET);
			    }
			    // TODO hack: roman numbering or better: parsing of pnlevelsec
			    _theParagraphProperties.setBulletText(_listText);
			    // reset the listtext
			    _listText = null;
			}
		}
		else if (name.equals("pagebb")) {
			_theParagraphProperties.setPageBreak(true);
		}
		else if (name.equals("qc")) {
            _theParagraphProperties
                    .setParagraphAlignment(RtfParagraphProperties.ALIGNMENT_CENTER);
		}
		else if (name.equals("qj")) {
            _theParagraphProperties
                    .setParagraphAlignment(RtfParagraphProperties.ALIGNMENT_JUSTIFIED);
		}
		else if (name.equals("ql")) {
            _theParagraphProperties
                    .setParagraphAlignment(RtfParagraphProperties.ALIGNMENT_LEFT);
		}
		else if (name.equals("qr")) {
            _theParagraphProperties
                    .setParagraphAlignment(RtfParagraphProperties.ALIGNMENT_RIGHT);
		}
		else if (name.equals("qd")) {
            _theParagraphProperties
                    .setParagraphAlignment(RtfParagraphProperties.ALIGNMENT_DISTRIBUTED);
		}
		else if (name.equals("sb")) {
            _theParagraphProperties.setSpaceBefore(Integer.parseInt(tok.getData()));
		}
		else if (name.equals("sa")) {
            _theParagraphProperties.setSpaceAfter(Integer.parseInt(tok.getData()));
		}
		else if (name.equals("sl")) {
            _theParagraphProperties.setLineSpacing(Integer.parseInt(tok.getData()));
		}
		else if (name.equals("li")) {
            _theParagraphProperties.setLeftIndent(Integer.parseInt(tok.getData()));
		}
		else if (name.equals("ri")) {
            _theParagraphProperties.setRightIndent(Integer.parseInt(tok.getData()));
		}
		else if (name.equals("fi")) {
            _theParagraphProperties.setFirstIndent(Integer.parseInt(tok.getData()));
		}
		else if (name.equals("stylesheet")) {
			parseStyleSheet();
		}
		else if (name.equals("fonttbl")) {
			skipUntilEndOfGroup();
		}
		else if (name.equals("colortbl")) {
			skipUntilEndOfGroup();
		}
		else if (name.equals("deleted")) {
		    if (OUTPUT_DELETED) {
				String data = tok.getData();
				boolean switchoff = (data != null && data.equals("0"));
		        _theTextProperties.setDeleted(!switchoff);
		    }
		    else
		        skipUntilEndOfGroup();
		}
		else if (name.equals("pict")) {
		    processPict();
		}
		else if (name.equals("s")) {
			int code = Integer.parseInt(tok.getData());
			_theParagraphProperties.setStyle(code);
		}
		else if (name.equals("pntext")) {
		    _bulletText = getDataOfGroup().trim();
		}
		else if (name.equals("listtext")) {
		    _listText = getDataOfGroup().trim();
			// TODO remove this listtext-hack here and in "pard"-parsing
		    // UGLY hack to determine the list type
		    if (_listText.length() > 0) {
				char first = _listText.charAt(0);

				if (Character.isUpperCase(first))
					_theParagraphProperties
							.setNumStyle(RtfParagraphProperties.STYLE_NUMBERED_ALPHA_UP);
				else if (Character.isLetter(first))
					_theParagraphProperties
							.setNumStyle(RtfParagraphProperties.STYLE_NUMBERED_ALPHA);
				else if (Character.isDigit(first))
					_theParagraphProperties
							.setNumStyle(RtfParagraphProperties.STYLE_NUMBERED_DEC);
				else {
					_theParagraphProperties.setBullet(new Character(first)
							.toString());
					_theParagraphProperties
							.setNumStyle(RtfParagraphProperties.STYLE_BULLET);
				}
				// TODO hack: roman numbering or better: parsing of pnlevelsec
				_theParagraphProperties.setBulletText(_listText);
			}
		}
		else if (name.equals("b")) {
			String data = tok.getData();
			boolean switchoff = (data != null && data.equals("0"));
			_theTextProperties.setBold(!switchoff);
		}
		else if (name.equals("i")) {
			String data = tok.getData();
			boolean switchoff = (data != null && data.equals("0"));
			_theTextProperties.setItalic(!switchoff);
		}
		else if (name.equals("v")) {
			String data = tok.getData();
			boolean switchoff = (data != null && data.equals("0"));
			_theTextProperties.setHidden(!switchoff);
		}
		else if (name.equals("ul")) {
			String data = tok.getData();
			boolean switchoff = (data != null && data.equals("0"));
			if (switchoff) {
				_theTextProperties.setUnderlined(false);
				_theTextProperties.setDoubleUnderlined(false);
			} else {
				_theTextProperties.setUnderlined(true);
			}
		}
		else if (name.equals("uldb")) {
			String data = tok.getData();
			boolean switchoff = (data != null && data.equals("0"));
			_theTextProperties.setDoubleUnderlined(!switchoff);
		}
		else if (name.equals("ulnone")) {
			_theTextProperties.setUnderlined(false);
			_theTextProperties.setDoubleUnderlined(false);
		}
		else if (name.equals("cf")) {
			_theTextProperties.setColor(Integer.parseInt(tok.getData()));
		}
		else if (name.equals("super")) {
			_theTextProperties.setSuper(true);
		}
		else if (name.equals("sub")) {
			_theTextProperties.setSub(true);
		}
		else if (name.equals("expndtw")) {
		    int value = Integer.parseInt(tok.getData());
			_theTextProperties.setExpanded(value);
		}
		else if (name.equals("fs")) {
		    _theTextProperties.setFontSize(Integer.parseInt(tok.getData()));
		}
		else if (name.equals("cs")) {
			_theTextProperties.setStyle(Integer.parseInt(tok.getData()));
		}
		else if (name.equals("caps")) {
			String data = tok.getData();
			boolean switchoff = (data != null && data.equals("0"));
			_theTextProperties.setCapitalLetters(!switchoff);
		}
		else if (name.equals("plain")) {
			_theTextProperties.reset();
		}
		else if (name.equals("field")) {
			processField();
		}
		else if (name.equals("ldblquote")) {
			insertData("\"");
		}
		else if (name.equals("rdblquote")) {
			insertData("\"");
		}
		else if (name.equals("lquote")) {
			insertData("'");
		}
		else if (name.equals("rquote")) {
			insertData("'");
		}
		else if (name.equals("emdash")) {
			insertData("\u2003");
		}
		else if (name.equals("endash")) {
			insertData("\u2002");
		}
		else if (name.equals("deftab")) {
		    RtfTabDefList list = new RtfTabDefList();
			_theTabDefault = new RtfTabDef(RtfTabDef.TABALIGN_LEFT,
			        Integer.parseInt(tok.getData()),
			        RtfTabDef.TAB_TYPE_DEFAULT);
			list.setDefaultTabDef(_theTabDefault);
		    _theParagraphProperties.setTabDefList(list);
		}
		else if (name.equals("tqr")) {
			processTabDef(tok, RtfTabDef.TABALIGN_RIGHT, false);
		}
		else if (name.equals("tqc")) {
			processTabDef(tok, RtfTabDef.TABALIGN_CENTER, false);
		}
		else if (name.equals("tqdec")) {
			processTabDef(tok, RtfTabDef.TABALIGN_DECIMAL, false);
		}
		else if (name.equals("jclisttab")) {
			processTabDef(tok, RtfTabDef.TABALIGN_LEFT, true);
		}
		else if (name.equals("tx")) {
			processTabDef(tok, RtfTabDef.TABALIGN_LEFT, false);
		}
		else if (name.equals("tab")) {
			insertTab();
		}
		else if (name.equals("cell")) {
			processCell();
		    _theCellProperties = null;
		}
		else if (name.equals("row")) {
			processRow();
		    _theCellProperties = null;
		    // dont set it to null, trowd will reset it
		    // so it is just inherited
//		    _theRowProps = null;
		}
		else if (name.equals("intbl")) {
			processStartRow();
		}
		else if (name.equals("clftsWidth")) {
		    int cellwidthdeftype = Integer.parseInt(tok.getData());
		    _theCellProperties.setCellWidthDefType(cellwidthdeftype);
		}
		else if (name.equals("clwWidth")) {
		    // pay attention to the width type from
		    // the controlword clftsWidth
		    // and convert it then to twips
		    int cellwidth = Integer.parseInt(tok.getData());

		    if (_theCellProperties.getCellWidthDefType() == RtfCellProperties.CELLWIDTH_TYPE_TWIPS)
		    	_theCellProperties.setWidth(cellwidth);
		}
		else if (name.equals("clvmgf")) {
		    // this is the first cell to be vertically merged
		    _theCellProperties.setFirstVerticalMerged(true);
		    _theCellProperties.setVerticalMerged(false);
		    _theCellProperties.setRowSpan(1);
		}
		else if (name.equals("clvmrg")) {
		    // this is a cell to be vertically merged, thus not generated in xml.
		    _theCellProperties.setVerticalMerged(true);
		}
		else if (name.equals("clvertalt")) {
//			_cellVAlignment = RtfCellProperties.VERT_ALIGN_TOP;
		    _theCellProperties.setVerticalAlignment(RtfCellProperties.VERT_ALIGN_TOP);
		} else if (name.equals("clvertalc")) {
//			_cellVAlignment = RtfCellProperties.VERT_ALIGN_CENTER;
		    _theCellProperties.setVerticalAlignment(RtfCellProperties.VERT_ALIGN_CENTER);
		} else if (name.equals("clvertalb")) {
//			_cellVAlignment = RtfCellProperties.VERT_ALIGN_BOTTOM;
		    _theCellProperties.setVerticalAlignment(RtfCellProperties.VERT_ALIGN_BOTTOM);
		} else if (name.equals("trbrdrt")) {
//			_rowBorders = _rowBorders | RtfRowProperties.ROWBORDER_TOP;
		    processBorderDef(tok, RtfRowProperties.ROWBORDER_TOP, true);
		} else if (name.equals("trbrdrl")) {
//			_rowBorders = _rowBorders | RtfRowProperties.ROWBORDER_LEFT;
		    processBorderDef(tok, RtfRowProperties.ROWBORDER_LEFT, true);
		} else if (name.equals("trbrdrb")) {
//			_rowBorders = _rowBorders | RtfRowProperties.ROWBORDER_BOTTOM;
		    processBorderDef(tok, RtfRowProperties.ROWBORDER_BOTTOM, true);
		} else if (name.equals("trbrdrr")) {
//			_rowBorders = _rowBorders | RtfRowProperties.ROWBORDER_RIGHT;
		    processBorderDef(tok, RtfRowProperties.ROWBORDER_RIGHT, true);
		} else if (name.equals("trbrdrh")) {
//			_rowBorders = _rowBorders | RtfRowProperties.ROWBORDER_HORIZONTAL;
		    processBorderDef(tok, RtfRowProperties.ROWBORDER_HORIZONTAL, true);
		} else if (name.equals("trbrdrv")) {
//			_rowBorders = _rowBorders | RtfRowProperties.ROWBORDER_VERTICAL;
		    processBorderDef(tok, RtfRowProperties.ROWBORDER_VERTICAL, true);
		} else if (name.equals("clbrdrb")) {
//			_cellBorders = _cellBorders | RtfCellProperties.CELLBORDER_BOTTOM;
		    processBorderDef(tok, RtfCellProperties.CELLBORDER_BOTTOM, false);
		} else if (name.equals("clbrdrt")) {
//			_cellBorders = _cellBorders | RtfCellProperties.CELLBORDER_TOP;
		    processBorderDef(tok, RtfCellProperties.CELLBORDER_TOP, false);
		} else if (name.equals("clbrdrl")) {
//			_cellBorders = _cellBorders | RtfCellProperties.CELLBORDER_LEFT;
		    processBorderDef(tok, RtfCellProperties.CELLBORDER_LEFT, false);
		} else if (name.equals("clbrdrr")) {
//			_cellBorders = _cellBorders | RtfCellProperties.CELLBORDER_RIGHT;
		    processBorderDef(tok, RtfCellProperties.CELLBORDER_RIGHT, false);
		} else if (name.equals("brdrnil")) {
		    // already handled in processBorderDef(tok);
		} else if (name.equals("brdrnone")) {
		    // already handled in processBorderDef(tok);
		} else if (name.equals("trpaddl")) {
		    int margin = Integer.parseInt(tok.getData());
			_theRowProps.setRowPadding(RtfRowProperties.ROWBORDER_LEFT, margin);
		} else if (name.equals("trpaddr")) {
		    int margin = Integer.parseInt(tok.getData());
			_theRowProps.setRowPadding(RtfRowProperties.ROWBORDER_RIGHT, margin);
		} else if (name.equals("trpaddt")) {
		    int margin = Integer.parseInt(tok.getData());
			_theRowProps.setRowPadding(RtfRowProperties.ROWBORDER_TOP, margin);
		} else if (name.equals("trpaddb")) {
		    int margin = Integer.parseInt(tok.getData());
			_theRowProps.setRowPadding(RtfRowProperties.ROWBORDER_BOTTOM, margin);
		} else if (name.equals("clpadl")) {
		    int margin = Integer.parseInt(tok.getData());
		    _theCellProperties.setCellPadding(RtfCellProperties.CELLBORDER_LEFT, margin);
		} else if (name.equals("clpadr")) {
		    int margin = Integer.parseInt(tok.getData());
		    _theCellProperties.setCellPadding(RtfCellProperties.CELLBORDER_RIGHT, margin);
		} else if (name.equals("clpadt")) {
		    int margin = Integer.parseInt(tok.getData());
		    _theCellProperties.setCellPadding(RtfCellProperties.CELLBORDER_TOP, margin);
		} else if (name.equals("clpadb")) {
		    int margin = Integer.parseInt(tok.getData());
		    _theCellProperties.setCellPadding(RtfCellProperties.CELLBORDER_BOTTOM, margin);
		}
		else if (name.equals("info")) {
			processInfo();
		}
		else if (name.equals("line")) {
		    if (GENERATE_LINEBREAKS) {
		        insertLineBreak();
		    }
		}
		else if (name.equals("footnote")) {
			skipUntilEndOfGroup();
		}
		else if (name.equals("header")) {
			skipUntilEndOfGroup();
		}
		else if (name.equals("footer")) {
			skipUntilEndOfGroup();
		}
		else if (name.equals("listtext")) {
			skipUntilEndOfGroup();
		}
		else if (name.equals("cellx")) {
			_theCellLimits.addElement(tok.getData());
			
		    // TODO set size here if clftsWidth0 is set
		    // cellx also marks the end of the propertydefinition-block for a single cell
		    _theCellPropertiesVector.addElement(_theCellProperties.clone());
		    _theCellProperties = new RtfCellProperties();
		}
		else if (name.equals("trowd")) {
			_theCellLimits = new Vector();
		    _theCellPropertiesVector = new Vector();
		    _theCellProperties = new RtfCellProperties();
		    _theRowProps = new RtfRowProperties();
/*			_cellVAlignment = 0;
			_clearCellBorders = false;
			_cellBorders = 0;
			_rowBorders = 0;
*/		}
	}

	private void skipUntilEndOfGroup() throws IOException
	{
		int depth = 1;
		while (depth > 0) {
			RtfToken tok = _reader.getNextToken();
			if (tok == null) {
				break;
			}
			int type = tok.getType();
			switch(type) {
			case RtfToken.OPENGROUP:
				depth++;
				break;
			case RtfToken.CLOSEGROUP:
				depth--;
				break;
			case RtfToken.CONTROLWORD:
				if (_trace) {
					_trace_stream.println(">>>" + depth + " skipping " + tok.getName());
				}
				break;
			}
		}

		_theTextProperties = _theTextPropertiesStack.pop();
	}

	private String getDataOfGroup() throws IOException
	{
	    // get all data from nested groups
	    return this.getDataOfGroup(-1);
	}
	
	private String getDataOfGroup(int maxdepth) throws IOException
	{
		StringBuffer buf = new StringBuffer();
		int depth = 1;
		while (depth > 0) {
			RtfToken tok = _reader.getNextToken();
			if (tok == null) {
				break;
			}
			int type = tok.getType();
			switch(type) {
			case RtfToken.DATA:
			    if (maxdepth < 0 || depth <= maxdepth) {
			        buf.append(tok.getData());
			    }
				break;
			case RtfToken.OPENGROUP:
				depth++;
				break;
			case RtfToken.CLOSEGROUP:
				depth--;
				break;
			case RtfToken.ASTERISK:
				skipUntilEndOfGroup();
				depth--;
				break;
			}
		}

		_theTextProperties = _theTextPropertiesStack.pop();

		return buf.toString();
	}

	private void parseStyleSheet() throws IOException
	{
		for (;;) {
			RtfToken tok = _reader.getNextToken();

			if (tok == null) {
				return;
			}

			if (tok.getType() == RtfToken.OPENGROUP) {
				parseStyleDefinition();
			}
			else if (tok.getType() == RtfToken.CLOSEGROUP) {
				return;
			}
		}
	}

	private void parseStyleDefinition() throws IOException, NumberFormatException
	{
		int code = 0;
		String name = null;
		RtfTextProperties textProperties = new RtfTextProperties();
		// TODO enhance stylesheet-parsing with paragraph-properties
		//RtfParagraphProperties parProperties = new RtfParagraphProperties();
		boolean isCharStyle = false;

		for (;;) {
			RtfToken tok = _reader.getNextToken();
			
			if (tok == null) {
				break;
			}

			if (tok.getType() == RtfToken.CONTROLWORD) {
				if (tok.getName().equals("s")) {
					code = Integer.parseInt(tok.getData());					
					isCharStyle = false;
				}
				else if (tok.getName().equals("cs")) {
					code = Integer.parseInt(tok.getData());
					isCharStyle = true;
				}
				// Character Properties
				else if (tok.getName().equals("super")) {
					textProperties.setSuper(true);
				}
				else if (tok.getName().equals("sub")) {
					textProperties.setSub(true);
				}
				else if (tok.getName().equals("b")) {
					textProperties.setBold(true);
				}
				else if (tok.getName().equals("i")) {
					textProperties.setItalic(true);
				}
				else if (tok.getName().equals("v")) {
					textProperties.setHidden(true);
				}
				else if (tok.getName().equals("ul")) {
					textProperties.setUnderlined(true);
				}
				else if (tok.getName().equals("uldb")) {
				    textProperties.setDoubleUnderlined(true);
				}
				else if (tok.getName().equals("caps")) {
				    textProperties.setCapitalLetters(true);
				}
				else if (tok.getName().equals("expndtw")) {
					int value = Integer.parseInt(tok.getData());
				    textProperties.setExpanded(value);
				}
				else if (tok.getName().equals("fs")) {
				    textProperties.setFontSize(Integer.parseInt(tok.getData()));
				}
				else if (tok.getName().equals("cf")) {
					int color = Integer.parseInt(tok.getData());
					textProperties.setColor(color);
				}
				// Paragraph properties
/*				else if (tok.getName().equals("")) {
				    // TODO parse stylesheet
				}*/
			}
			else if (tok.getType() == RtfToken.DATA) {
				if (name == null) {
					name = tok.getData();
				}
				else {
					name += tok.getData();
				}
				if (name.endsWith(";")) {
					name = name.substring(0, name.length() - 1);
				}
			}
			else if (tok.getType() == RtfToken.OPENGROUP) {
				skipUntilEndOfGroup();
			}
			else if (tok.getType() == RtfToken.CLOSEGROUP) {
				break;
			}
		}

		if (isCharStyle) {
			_theStyles.defineCharacterStyle(code, name, textProperties);
		}
		else {
			_theStyles.defineParagraphStyle(code, name, textProperties);
		}
	}

	private void insertData(String data)
	{
		if (_theParagraph == null) {
			_theParagraph = new RtfParagraph();
			_theParagraph.setProperties(_theParagraphProperties);
		}
		_theParagraph.add(new RtfText(data, _theTextProperties));
	}

	private void insertTab()
	{
		if (_theParagraph == null) {
			_theParagraph = new RtfParagraph();
			_theParagraph.setProperties(_theParagraphProperties);
		}
		_theParagraph.add(new RtfTab(_theTextProperties));
	}

	private void insertPageBreak()
	{
		if (_theParagraph == null) {
			_theParagraph = new RtfParagraph();
			_theParagraph.setProperties(_theParagraphProperties);
		}
		_theParagraph.add(new RtfPageBreak());
	}

	private void insertSectionBreak()
	{
		if (_theParagraph == null) {
			_theParagraph = new RtfParagraph();
			_theParagraph.setProperties(_theParagraphProperties);
		}
		_theParagraph.add(new RtfSectionBreak());
	}

	private void insertLineBreak()
	{
		if (_theParagraph == null) {
			_theParagraph = new RtfParagraph();
			_theParagraph.setProperties(_theParagraphProperties);
		}
		_theParagraph.add(new RtfLineBreak());
	}

	void processPict() throws IOException
	{
		StringBuffer buf = new StringBuffer();
		int depth = 1;

		int pich = 0;
		int picw = 0;
		int pichg = 0;
		int picwg = 0;
		int picscalex = 100;
		int picscaley = 100;
		String pictype = "";
		String uid = null;
		
		while (depth > 0) {
			RtfToken tok = _reader.getNextToken();
			if (tok == null) {
				break;
			}
			int type = tok.getType();
			switch(type) {
			case RtfToken.DATA:
				if (depth == 1) {
					buf.append(tok.getData());
				}
				break;
			case RtfToken.OPENGROUP:
				depth++;
				break;
			case RtfToken.CLOSEGROUP:
				depth--;
				break;
			case RtfToken.CONTROLWORD:
				{
					String name = tok.getName();
					if (name.equals("picw")) {
						picw = Integer.parseInt(tok.getData());
					}
					if (name.equals("pich")) {
						pich = Integer.parseInt(tok.getData());
					}
					if (name.equals("picwgoal")) {
						picwg = Integer.parseInt(tok.getData());
					}
					if (name.equals("pichgoal")) {
						pichg = Integer.parseInt(tok.getData());
					}
					if (name.equals("picscalex")) {
						picscalex = Integer.parseInt(tok.getData());
					}
					if (name.equals("picscaley")) {
						picscaley = Integer.parseInt(tok.getData());
					}
					if (name.endsWith("blip")) {
						pictype = name.substring(0,name.indexOf("blip"));
					}
					if (name.equals("blipuid")) {
					    tok = _reader.getNextToken();
					    if (tok.getType() == RtfToken.DATA) {
					        uid = tok.getData();
					    }
					}
				}
			}
		}

		if (_theParagraph == null) {
			_theParagraph = new RtfParagraph();
			_theParagraph.setProperties(_theParagraphProperties);
		}
		if (_picIds.contains(uid)) {
		    RtfPicture oldpic = null;
		    for (int i = _theParagraph.size()-1; i > -1; i--) {
		        RtfObject ro = _theParagraph.getObject(i);
		        if (ro instanceof RtfPicture) {
		            oldpic = (RtfPicture)ro;
		            if (oldpic.getPicUid() == null ) {
		                oldpic = null;
		            } else {
		                break;
		            }
		        }
		    }
		    if (oldpic != null) {
		        if (!pictype.equals("") && oldpic.getPictype().equals("")) {
		            _theParagraph.replace(oldpic, new RtfPicture(oldpic.getCounter(), buf.toString(), picw, pich, picwg, pichg, picscalex, picscaley, pictype, uid));
		        }
		    }
		} else {
		    _picIds.add(uid);
			_theParagraph.add(new RtfPicture(_pictureCounter, buf.toString(), picw, pich, picwg, pichg, picscalex, picscaley, pictype, uid));
			_theExternalEntities.addEntity(new RtfPictureExternalEntity(_pictureCounter, ""));
			_pictureCounter++;
		}
	}

	void processField() throws IOException
	{
		RtfObject instance = null;
		int depth = 1;
loop:
		while (depth > 0) {
			RtfToken tok = _reader.getNextToken();
			if (tok == null) {
				break;
			}
			int type = tok.getType();
			switch(type) {
			case RtfToken.DATA:
				break;
			case RtfToken.OPENGROUP:
				depth++;
				break;
			case RtfToken.CLOSEGROUP:
				depth--;
				break;
			case RtfToken.CONTROLWORD:
				if (tok.getName().equals("fldinst")) {
					instance = processFieldInstance();
					if (_no_field_result) {
						skipUntilEndOfGroup();
					}
					break loop;
				}
				else if (tok.equals("fldrslt")) {
					//@@@TBC
				}
				break;
			}
		}

		if (instance != null) {
			if (_theParagraph == null) {
				_theParagraph = new RtfParagraph();
				_theParagraph.setProperties(_theParagraphProperties);
			}
			_theParagraph.add(instance);
		}
	}

	private static String getCommutators(String data, CommutatorList commutators)
	{
		char indata[] = data.trim().toCharArray();
		StringBuffer buf = new StringBuffer();
		StringBuffer bufcommutator = null;
		boolean inquotedstring = false;
		boolean incommutator = false;

		for (int ii = 0; ii < indata.length; ii++) {
			char ch = indata[ii];
			if (inquotedstring) {
				if (ch == '"') {
					inquotedstring = false;
				}
				buf.append(ch);
			}
			else if (incommutator) {
				if (Character.isSpaceChar(ch)) {
					commutators.addElement(bufcommutator.toString());
					bufcommutator = null;
					incommutator = false;
				}
				else if (ch == '"') {
					commutators.addElement(bufcommutator.toString());
					bufcommutator = null;
					inquotedstring = true;
					buf.append(ch);
					incommutator = false;
				}
				else {
					bufcommutator.append(ch);
				}
			}
			else {
				if (Character.isSpaceChar(ch)) {
					// nothing to do
				}
				else if (ch == '"') {
					inquotedstring = true;
					buf.append(ch);
				}
				else if (ch == '\\') {
					bufcommutator = new StringBuffer();
					bufcommutator.append(ch);
					incommutator = true;
				}
				else {
					// should not happen unless the user typed the
					// hyperlink target manually in the field
					buf.append(ch);
				}
			}
		}

		return buf.toString();
	}

	RtfObject processFieldInstance() throws IOException
	{
		String data = getDataOfGroup().trim();
		int pos = data.indexOf("\\*");
		if (pos >= 0) {
			data = data.substring(0, pos).trim();
		}

		_no_field_result = false;

		if (data != null) {
			if (data.startsWith("SYMBOL")) {
				return new RtfSymbol(data);
			}
			else if (data.startsWith("TOC")) {
				_no_field_result = true;
				return new RtfFieldInstance(data);
			}
			else if (data.startsWith("INCLUDEPICTURE")) {
				data = data.substring(14).trim();
				CommutatorList commutators = new CommutatorList();
				data = getCommutators(data, commutators);
				if (data.startsWith("\"")) {
					data = data.substring(1);
					int pos2 = data.indexOf('"');
					if (pos2 != -1) {
						data = data.substring(0, pos2);
					}
				}
				return new RtfPicture(data);
			}
			else if (data.startsWith("HYPERLINK")) {
				{
					CommutatorList commutators = new CommutatorList();
					data = data.substring(9).trim();
					data = getCommutators(data, commutators);
					if (data.startsWith("\"")) {
						data = data.substring(1);
					}
					if (data.endsWith("\"")) {
						data = data.substring(0, data.length() - 1);
					}
					if (commutators.isCommutatorDefined("\\l")) {
						data = "#" + data;
					}
					String result = getResultOfField();
					return  new RtfHyperLink(data, result);
				}
			}
			else {
				return new RtfFieldInstance(data);
			}
		}

		return null;
	}

	String getResultOfField() throws IOException
	{
		while (true) {
			RtfToken tok = _reader.getNextToken();
			if (tok == null) {
				return null;
			}
			int type = tok.getType();
			switch(type) {
			case RtfToken.CONTROLWORD:
				{
					String name = tok.getName();
					if (name.equals("fldrslt")) {
				        return getDataOfGroup(2).trim();
					}
				}
				break;

			case RtfToken.OPENGROUP:
				String result = getResultOfField();
				if (result != null) {
					return result;
				}
				break;

			case RtfToken.CLOSEGROUP:
				return null;

			case RtfToken.ASTERISK:
				skipUntilEndOfGroup();
				return null;
			}
		}
	}

	/**
	 * This method processes the properties of the start of a row of a table.
	 *
	 */
	void processStartRow()
	{
		if (! (_theContainer instanceof RtfCell)) {

			if (_theParagraph != null) {
				_theParagraph.setProperties(_theParagraphProperties);
				_theContainer.add(_theParagraph);
				_theParagraph = null;
			}
			
			_theContainerStack.push(_theContainer);

			_theRow = new RtfRow();
			_theRowProps.setCellWidths(_theCellLimits);
			if (_theRowProps != null) {
			    // set the row-properties
			    _theRow.setProperties(_theRowProps);
			} else {
			    // this shouldnt be the case anymore since
			    // \row doesnt set it to null anymore
			    System.err.println("RtfAnalyser.processStartRow(): Row border definition missing!");
			}
			
			// set the table-, row- and columnindex of each cellproperty
			_theRowIndex++;
			_theColumnIndex = -1;
			for (int i=0; i < _theCellPropertiesVector.size();i++) {
			    RtfCellProperties props = (RtfCellProperties) _theCellPropertiesVector.elementAt(i);
			    props.setTableIndex(_theTableIndex);
			    props.setRowIndex(_theRowIndex);
			    props.setColumnIndex(++_theColumnIndex);
			    _theCellPropertiesVector.set(i, props);
			    if (_debug) {
			        System.out.println("Table: "+_theTableIndex+", Row: "+_theRowIndex+", Column: "+_theColumnIndex+" set!");
			    }
			}
			
			_theRow.getProperties().setCellProperties(_theCellPropertiesVector);

//			_theRow.getProperties().setRowBorders(_rowBorders);
			_theCell = new RtfCell();
			RtfCellProperties firstCellProps = _theRow.getProperties().getCellProperties(0);
			// pay attention to the clftsWidth-Definition
			if (firstCellProps.getCellWidthDefType() == RtfCellProperties.CELLWIDTH_TYPE_CELLX) {
			    // use the cellx-size
			    firstCellProps.setWidth(_theRow.getProperties().getCellWidth(0));
			}
			_theCell.setProperties(firstCellProps);
/*			// set cell borders
			if (!_clearCellBorders)
			    _theCell.getProperties().setCellBorders(_cellBorders);
			else
			    _theCell.getProperties().setCellBorders(RtfCellProperties.CELLBORDER_NONE);
			// set cell vertical alignment
			_theCell.getProperties().setVerticalAlignment(this._cellVAlignment);
			_theContainerStack.push(_theContainer);
*/			_theContainer = _theCell;
		}
	}

	/**
	 * This method processes the second to the last row of the table.
	 * The first row is processed by the method <code>processStartRow()</code>.
	 *
	 */
	void processRow()
	{
		_theContainer = _theContainerStack.pop();
		if (_theRowIndex==0) {
		    if (_debug) {
		        System.out.println("New table.");
		    }
		    _theTableIndex++;
			_theTable = new RtfTable();
		}
		// save the row in the table
		if (_theTable != null) {
		    if (_debug) {
		        System.out.println("Saving the row in the table.");
		    }
		    _theTable.add(_theRow);
		}
		_theRow = null;
	}

	void processTabDef(RtfToken tok, int align, boolean listtab) throws IOException {
		
		
		int tabpos = -1;
		try{
			if (listtab) {
				RtfToken tok1 = _reader.getNextToken();
				tabpos = Integer.parseInt(tok1.getData());
			} else {
				switch (align) {
					case RtfTabDef.TABALIGN_LEFT:
						tabpos = Integer.parseInt(tok.getData());
						break;
					case RtfTabDef.TABALIGN_RIGHT:
						RtfToken tok1 = _reader.getNextToken();
						tabpos = Integer.parseInt(tok1.getData());
						break;
					case RtfTabDef.TABALIGN_CENTER:
						RtfToken tok2 = _reader.getNextToken();
						tabpos = Integer.parseInt(tok2.getData());
						break;
					case RtfTabDef.TABALIGN_DECIMAL:
						RtfToken tok3 = _reader.getNextToken();
						tabpos = Integer.parseInt(tok3.getData());
						break;
				}
			}
		} catch (NumberFormatException nfe){
		}
		
		// set the tabdef into the list
		if (tabpos != -1) {
		    // initialize tabdeflist with defaulttab
		    if (_theParagraphProperties.getTabDefList() == null) {
		        RtfTabDefList list = new RtfTabDefList();
		        if (this._theTabDefault != null)
		            list.setDefaultTabDef(this._theTabDefault);
		        else
		            list.setDefaultTabDef(new RtfTabDef(RtfTabDef.TABALIGN_LEFT,
		                    RtfTabDef.TAB_DEFAULT_WIDTH,
		                    RtfTabDef.TAB_TYPE_DEFAULT));
		        _theParagraphProperties.setTabDefList(list);
		    }
		    // set the tab for the list or paragraph
		    if (listtab) {
		        _theParagraphProperties.getTabDefList().setListTabDef(new RtfTabDef(align, tabpos, RtfTabDef.TAB_TYPE_NORMAL));
		    } else {
		        _theParagraphProperties.getTabDefList().add(new RtfTabDef(align, tabpos, RtfTabDef.TAB_TYPE_NORMAL));
		    }
		}
	}
	
	/**
	 * This method processes all tables of the document for rowspans.
	 */
	void processRowSpanning() {
        if (_debug) {
            System.out.println("RtfAnalyser.processColSpanning: Processing tables for rowspans.");
        }

        int table = -1;
        // process each table in the document
        for (int rtfobject = 0; rtfobject < this._theDocument.size(); rtfobject++) {

            RtfObject rtfObject = _theDocument.getObject(rtfobject);

            if (rtfObject instanceof RtfTable) {

                RtfTable tableObject = (RtfTable) rtfObject;
                // For the debug-output
                table++;
                
                // search the first vertically merged cell
                for (int row = 0; row < tableObject.size(); row++) {
                    RtfRow theRow = (RtfRow) tableObject.getObject(row);
                    for (int cell = 0; cell < theRow.size(); cell++) {
                        RtfCell theCell = (RtfCell) theRow.getObject(cell);
                        if (theCell.getProperties().getFirstVerticalMerged()) {
                            // found the first cell, search the column
                            int rowspan = 1;
                            // do not use the column, but the cellx-value, because of colspans
                            int searchColumnCellX = theRow.getProperties().getCellLimit(cell);
                            
                            boolean isMerged = true;
                            
                            for (int searchRow = row+1; searchRow < tableObject.size() && isMerged; searchRow++) {
                                RtfRow rowtemp = (RtfRow)tableObject.getObject(searchRow);
                                for (int celltemp = 0; celltemp < rowtemp.size(); celltemp++) {
                                    RtfCell searchCell = (RtfCell)rowtemp.getObject(celltemp);
                                    if (Math.abs(rowtemp.getProperties().getCellLimit(celltemp)-searchColumnCellX) > LIMIT_TOLERANCE)
                                        continue;
                                    isMerged = searchCell.getProperties().getVerticalMerged();
                                    // increment rowspan and continue search
                                    if (isMerged)
                                        rowspan++;
                                }
                            }
                            
                            // found last one belonging to theCell, set the rowspan-value
                            theCell.getProperties().setRowSpan(rowspan);
                        }
                    }
                }
            }
        }

	}

	/**
	 * This method processes all tables of the document for colspans.
	 */
	void processColSpanning() {
		try {
			if (_debug) {
				System.out
						.println("RtfAnalyser.processRowSpanning: Processing tables for colspans.");
			}

			int table = -1;
			// process each table in the document
			for (int rtfobject = 0; rtfobject < this._theDocument.size(); rtfobject++) {

				RtfObject rtfObject = _theDocument.getObject(rtfobject);

				if (rtfObject instanceof RtfTable) {

					RtfTable tableObject = (RtfTable) rtfObject;
					// For the debug-output
					table++;

					// First step: Sort the cell-limits and save them ascending
					// in a
					// vector without dupes
					Vector limitVector = new Vector();
					int[] cellLimitArray = null;
					for (int row = 0; row < tableObject.size(); row++) {
						RtfRow thisRow = (RtfRow) tableObject.getObject(row);
						int[] thisRowLimits = thisRow.getProperties()
								.getCellLimits();
						if (cellLimitArray == null) {
							cellLimitArray = thisRowLimits;
						} else {
							// copy the limits together into the cellLimitArray
							int[] temp = new int[cellLimitArray.length
									+ thisRowLimits.length];
							System.arraycopy(cellLimitArray, 0, temp, 0,
									cellLimitArray.length);
							System
									.arraycopy(thisRowLimits, 0, temp,
											cellLimitArray.length,
											thisRowLimits.length);
							cellLimitArray = temp;
						}
					}
					// sort the limits
					Arrays.sort(cellLimitArray);
					// fill the limitVector without dupes and "inprecise" limits
					for (int i = 0; i < cellLimitArray.length; i++) {
						if (limitVector.size() == 0
								|| Math.abs(cellLimitArray[i]
										- ((Integer) limitVector
												.get(limitVector.size() - 1))
												.intValue()) > LIMIT_TOLERANCE) {
							limitVector.add(new Integer(cellLimitArray[i]));
						}
					}

					// Second step: compare the minimal limits in the vector
					// with the limits in all rows
					// and set the colspan-values accordingly
					for (int row = 0; row < tableObject.size(); row++) {
						RtfRow thisRow = (RtfRow) tableObject.getObject(row);
						for (int cell = 0; cell < thisRow.size(); cell++) {
							int colspan = 0;
							for (int limit = cell; limit < limitVector.size(); limit++) {
								int minlimit = ((Integer) limitVector
										.get(limit)).intValue();
								int cell_limit = thisRow.getProperties()
										.getCellLimits()[cell];
								int cell_limit_previous = (cell == 0 ? 0
										: thisRow.getProperties()
												.getCellLimits()[cell - 1]);
								// when a tablecell-limit in the scope of the
								// whole
								// table is found
								// that lies between the left and the right edge
								// of
								// the current cell,
								// then increment the colspan
								if (minlimit > cell_limit_previous
										&& minlimit <= cell_limit) {
									colspan++;
								} else if (minlimit > cell_limit) {
									// break for this cell, because they are out
									// of bounds
									break;
								}
							}
							// test the colspan
							if (_debug && colspan > 1)
								System.out.println("Table: " + table
										+ ", Row: " + row + ", Cell: " + cell
										+ ", --> colspan is " + colspan);

							// set the colspan-property
							((RtfCell) thisRow.getObject(cell)).getProperties()
									.setColSpan(colspan);
							thisRow.getProperties().getCellProperties(cell)
									.setColSpan(colspan);
						}
					}
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
    }

	/**
	 * Search the documentdata for a specific cellproperty.
	 * 
	 * @param table
	 * @param row
	 * @param column
	 * @return
	 */
	RtfCellProperties getCellProperties(RtfCompoundObject data, int table, int row, int column) {
	    if (table < 0 || row < 0 || column < 0) 
	        throw new IllegalArgumentException("RtfAnalyser.getCellProperties: bad arguments!");
	    int currenttable = -1, currentrow = -1;
	    RtfCellProperties resultProps = null;
	    
	    for (int i=0; i < data.size(); i++) {
	        if (data.getObject(i) instanceof RtfRow ) {
	            currenttable = ((RtfRow)data.getObject(i)).getProperties().getCellProperties(0).getTableIndex();
	            currentrow = ((RtfRow)data.getObject(i)).getProperties().getCellProperties(0).getRowIndex();
	            if (currenttable == table && currentrow == row) {
	                RtfRow rowObject = (RtfRow)data.getObject(i);
	                for (int j=0; j < rowObject.size(); j++) {
	                    RtfCell cellObject = (RtfCell)rowObject.getObject(j);
	                    if (cellObject.getProperties().getColumnIndex() == column) {
	                        // found it
	                        resultProps = cellObject.getProperties();
	                        // break the loop
	                        break;
	                    }
	                }
	            } else if (currenttable > table) {
	                break;
	            }
	        }
	    }
	    
	    return resultProps;
	}
	
	void processBorderDef(RtfToken tok, int borderside, boolean isRow) throws IOException {
	    String name = tok.getName();
	    if (name.equals("trbrdrt")) {
	        // loop it through til the actual borderwidth-defs come
	        processBorderDef(_reader.getNextToken(),
	                RtfRowProperties.ROWBORDER_TOP, isRow);
		} else if (name.equals("trbrdrl")) {
	        processBorderDef(_reader.getNextToken(),
	                RtfRowProperties.ROWBORDER_LEFT, isRow);
		} else if (name.equals("trbrdrb")) {
	        processBorderDef(_reader.getNextToken(),
	                RtfRowProperties.ROWBORDER_BOTTOM, isRow);
		} else if (name.equals("trbrdrr")) {
	        processBorderDef(_reader.getNextToken(),
	                RtfRowProperties.ROWBORDER_RIGHT, isRow);
		} else if (name.equals("trbrdrh")) {
	        processBorderDef(_reader.getNextToken(),
	                RtfRowProperties.ROWBORDER_HORIZONTAL, isRow);
		} else if (name.equals("trbrdrv")) {
	        processBorderDef(_reader.getNextToken(),
	                RtfRowProperties.ROWBORDER_VERTICAL, isRow);
		} else if (name.equals("clbrdrb")) {
	        processBorderDef(_reader.getNextToken(),
	                RtfCellProperties.CELLBORDER_BOTTOM, isRow);
		} else if (name.equals("clbrdrt")) {
	        processBorderDef(_reader.getNextToken(),
	                RtfCellProperties.CELLBORDER_TOP, isRow);
		} else if (name.equals("clbrdrl")) {
	        processBorderDef(_reader.getNextToken(),
	                RtfCellProperties.CELLBORDER_LEFT, isRow);
		} else if (name.equals("clbrdrr")) {
	        processBorderDef(_reader.getNextToken(),
	                RtfCellProperties.CELLBORDER_RIGHT, isRow);
		} else if (name.equals("brdrw")) {
		    int width = Integer.parseInt(tok.getData());
		    // set the borderwidth of the row/cell
		    if (isRow) {
		        // row sets the defaults for the row
		        _theRowProps.setBorderWidth(borderside, width);
		    } else {
		        // cell overwrites the values set by the row
		        // but is set independently here for full information
		        _theCellProperties.setBorderWidth(borderside, width);
		    }
		} else if (name.equals("brdrnone") || name.equals("brdrnil") || name.equals("brdrtbl")) {
		    // reset the borderwidth of the row/cell
		    if (isRow) {
		        this._theRowProps.setBorderWidth(borderside, 0);
		    } else {
		        this._theCellProperties.setBorderWidth(borderside, 0);
		    }
		} else if (name.startsWith("brdr") || name.startsWith("brsp")) {
		    // ignore all control words we dont know and loop through 
	        processBorderDef(_reader.getNextToken(),
	                borderside, isRow);
		} else {
		    // back to normal
		    parseControlWord(tok);
		}
	}
	
	void processCell()
	{
		if (_theParagraph != null) {
			_theParagraph.setProperties(_theParagraphProperties);
			_theContainer.add(_theParagraph);
			_theParagraph = null;
		}

		if (! (_theContainer instanceof RtfCell)) {
			System.err.println("bad \\cell, is a " + _theContainer.toString());
		}

		_theRow.add(_theCell);
		_theCell = new RtfCell();
		RtfCellProperties thisCellProps = _theRow.getProperties().getCellProperties(_theRow.size());
		// pay attention to the clftsWidth-Definition
		if (thisCellProps.getCellWidthDefType() == RtfCellProperties.CELLWIDTH_TYPE_CELLX) {
		    // use the cellx-size
		    thisCellProps.setWidth(_theRow.getProperties().getCellWidth(_theRow.size()));
		}
		_theCell.setProperties(thisCellProps);

		_theContainer = _theCell;
	}

	void processInfo() throws IOException
	{
		_theInfo = new RtfInfo();
		_theDocument.setInfo(_theInfo);

		for (;;) {
			RtfToken tok = _reader.getNextToken();

			if (tok == null) {
				return;
			}

			if (tok.getType() == RtfToken.OPENGROUP) {
				processOneInfo();
			}
			else if (tok.getType() == RtfToken.CLOSEGROUP) {
				return;
			}
		}
	}

	void processOneInfo() throws IOException
	{
		int code = -1;
		String value = "";

		for (;;) {
			RtfToken tok = _reader.getNextToken();

			if (tok == null) {
				break;
			}

			if (tok.getType() == RtfToken.CONTROLWORD) {
				if (tok.getName().equals("title")) {
					code = RtfInfo.TITLE;
				}
				if (tok.getName().equals("subject")) {
					code = RtfInfo.SUBJECT;
				}
				if (tok.getName().equals("operator")) {
					code = RtfInfo.OPERATOR;
				}
 				if (tok.getName().equals("author")) {
					code = RtfInfo.AUTHOR;
				}
				if (tok.getName().equals("manager")) {
					code = RtfInfo.MANAGER;
				}
				if (tok.getName().equals("company")) {
					code = RtfInfo.COMPANY;
				}
                if (tok.getName().equals("category")) {
                    code = RtfInfo.CATEGORY;
                }

                if (tok.getName().equals("keywords")) {
                    code = RtfInfo.KEYWORDS;
                }

                if (tok.getName().equals("doccomm")) {
                    code = RtfInfo.DOCCOMM;
                }

			}
			else if (tok.getType() == RtfToken.DATA) {
				value += tok.getData();
			}
			else if (tok.getType() == RtfToken.CLOSEGROUP) {
				break;
			}
		}

		if (code >= 0) {
			_theInfo.defineProperty(code, value);
		}
	}

	void processPn() throws IOException
	{
	    // TODO evaluate left indents for lists?
	    // TODO evaluate changed formatting properties of lists (see rtfspec 1.7 p. 53 ff.)
	    // HACK: set the Bullet Text for every item in the list to _bulletText
	    // (this is the plaintext of the numbering for each list item)
	    _theParagraphProperties.setBulletText(this._bulletText);
	    // reset the bullettext
	    this._bulletText = null;
		int depth = 1;
		boolean orderedlist = false;
		while (depth > 0) {
//		for (;;) {
			RtfToken tok = _reader.getNextToken();

			if (tok == null) {
				return;
			}

			switch (tok.getType()) {
			case RtfToken.CONTROLWORD:
				{
					String name = tok.getName();
					if (name.equals("pnlvlblt")) {
						_theParagraphProperties.setNumStyle(RtfParagraphProperties.STYLE_BULLET);
						orderedlist = false;
					}
					else if (name.equals("pnlvlcont")) {
					    _theParagraphProperties.setDontShowNumbering(true);
					}
					else if (name.equals("pnstart")) {
					    int start = Integer.parseInt(tok.getData().trim());
						_theParagraphProperties.setOrderingStart(start);
					}
					else if (name.equals("pndec")) {
						_theParagraphProperties.setNumStyle(RtfParagraphProperties.STYLE_NUMBERED_DEC);
						orderedlist = true;
					}
					else if (name.equals("pnucltr")) {
						_theParagraphProperties.setNumStyle(RtfParagraphProperties.STYLE_NUMBERED_ALPHA_UP);
						orderedlist = true;
					}
					else if (name.equals("pnlcltr")) {
						_theParagraphProperties.setNumStyle(RtfParagraphProperties.STYLE_NUMBERED_ALPHA);
						orderedlist = true;
					}
					else if (name.equals("pnucrm")) {
						_theParagraphProperties.setNumStyle(RtfParagraphProperties.STYLE_NUMBERED_ROMAN_UP);
						orderedlist = true;
					}
					else if (name.equals("pnlcrm")) {
						_theParagraphProperties.setNumStyle(RtfParagraphProperties.STYLE_NUMBERED_ROMAN);
						orderedlist = true;
					}
					else if (name.equals("pntxtb")) {
					    String textbefore = getDataOfGroup().trim();
					    // correct the depth
					    depth--;
					    if (!orderedlist) {
					        // textbefore is the bullet if unordered
					        _theParagraphProperties.setBullet(textbefore);
//					        _theParagraphProperties.setTextBefore(null);
//					        _theParagraphProperties.setTextAfter(null);
					    } else {
					        _theParagraphProperties.setTextBefore(textbefore);
					    }
					}
					else if (name.equals("pntxta")) {
					    String textafter = getDataOfGroup().trim();
					    // correct the depth
					    depth--;
				        _theParagraphProperties.setTextAfter(textafter);
					}
				}
				break;
			
			case RtfToken.OPENGROUP:
				depth++;
				break;

			case RtfToken.CLOSEGROUP:
				depth--;
				break;
			default:
				break;
			}
//		}
		}
	}

	void processBookmark() throws IOException
	{
		String data = getDataOfGroup().trim();
		if (   ! data.equals("")
			&& ! data.startsWith("_Toc")
			&& ! data.startsWith("_H")
			) {

			if (_theParagraph == null) {
				_theParagraph = new RtfParagraph();
			}
			_theParagraph.addIdentifier(data);
		}
	}

	void processShpinst() throws IOException
	{
		int depth = 1;
		boolean done = false;

		while (depth > 0) {
			RtfToken tok = _reader.getNextToken();

			if (tok == null) {
				return;
			}

			switch (tok.getType()) {
			case RtfToken.CONTROLWORD:
				{
					String name = tok.getName();
					if (name.equals("pict") && ! done) {
						processPict();
						done = true;
					}
				}
				break;
			
			case RtfToken.OPENGROUP:
				depth++;
				break;

			case RtfToken.CLOSEGROUP:
				depth--;
				break;

			default:
				break;
			}
		}
	}

	class CommutatorList extends Vector {

		boolean isCommutatorDefined(String commutator)
		{
			for (Enumeration en = elements(); en.hasMoreElements(); ) {
				if (en.nextElement().equals(commutator)) {
					return true;
				}
			}

			return false;
		}
	}
}
