/*****************************
**********************************************
*  The following java code is based off of the mxp library which
*  was written in c++ by Thomas Mecir and as such follows the
*  same license.
 *   Copyright (C) 2010 by slothmud.org                                     *
 *   splork@slothmud.org                     
 *  If you use this code, could you please add the following link
 * somewhere on your projects page
 *  <a href="http://www.slothmud.org">Slothmud - a multiplayer free online rpg game</a>         *
 *   His library can be found at http://www.kmuddy.com/libmxp/                                                                       *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU Library General Public License as       *
 *   published by the Free Software Foundation; either version 2 of the    *
 *   License, or (at your option) any later version.                       *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU Library General Public License for more details.                  *
 ***************************************************************************/
package com.jmxp;

import java.awt.Color;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;

import com.jmxp.MXPState.mxpMode;
import com.jmxp.structures.chunk;
import com.jmxp.structures.chunk.chunkType;

public class ElementManager 
{
	  /** list of all custom elements */
	  private HashMap<String, sElement> elements = new HashMap<String, sElement>();
	  private HashMap<String, sInternalElement> ielements = new HashMap<String, sInternalElement>();

	  /** line tags associated with elements */
	  private HashMap<Integer, String> lineTags = new HashMap<Integer, String>();

	  /** aliases for internal elements */
	  private HashMap<String, String> aliases = new HashMap<String, String>();

	  /** last line tag */
	  private int lastLineTag;

	  /** state class */
	  private MXPState state;
	  /** result handler */
	  private ResultHandler results;
	  /** entity manager */
	  private EntityManager entities;
	  /** expander of parameters in custom tags */
	  private EntityManager paramexpander;
	  /** parser of custom element definitions */
	  private MXPParser parser;


	  enum tagParserState {
	    tagBegin,
	    tagName,
	    tagParam,
	    tagParamValue,
	    tagQuotedParam,
	    tagAfterQuotedParam,
	    tagBetweenParams
	  };
	  
	  enum paramParserState {
		  parNone,
		  parName,
		  parValue,
		  parQuotedValue
		};

	  
	
	private class sElementPart 
	{
		  public boolean istag;
		  public String text;
	};

	/** one external element (defined by <!element>) */
	private class sElement 
	{
	  /** is it an open element? */
	  public boolean open;
	  /** is it an element with no closing tag? */
	  public boolean empty;
	  /** tag associated with this element */
	  int tag;
	  /** flag associated with this element */
	  public String flag = "";
	  /** list of all element contents */
	  public ArrayList<sElementPart> element = new ArrayList<sElementPart>();
	  /** list of element attributes in the right order */
	  public ArrayList<String> attlist = new ArrayList<String>();
	  /** default values for attributes */
	  public HashMap<String, String> attdefault = new HashMap<String, String>();
	  /** closing sequence */
	  public ArrayList<String> closingseq = new ArrayList<String>();
	};

	/** 
	 * one internal element 
	 */
	private class sInternalElement 
	{
	  /** is it an open element? */
	  public boolean open;
	  /** is it an element with no closing tag? */
	  public boolean empty;
	  /** list of element attributes in the right order */
	  public ArrayList<String> attlist = new ArrayList<String>();
	  /** default values for attributes; if there's an empty public String (but defined), then it's a flag */
	  public HashMap<String, String> attdefault = new HashMap<String, String>();
	};

	/** one parameter in one tag :-) */
	private class sParam {
	  public boolean flag;
	  public String name = "";
	  public String value = "";
	};
	
	/** constructor */
	public ElementManager (MXPState st, ResultHandler res, EntityManager enm)
	{
		  state = st;
		  results = res;
		  entities = enm;

		  paramexpander = new EntityManager (true);
		  parser = new MXPParser(null,null,null);
		  
		  reset();
		  createInternalElements();		
	}

	public void reset()
	{
	  lastLineTag = 0;
	  removeAll();
	}
	
	public void createInternalElements()
	{
	  //the list doesn't contain information on whether an argument is required or not
	  //processor of the tag implements this functionality

	  //create all internal elements
	  sInternalElement e;

	  //!element
	  e = new sInternalElement();
	  e.empty = true;
	  e.open = false;
	  e.attlist.add("name");  //this name is not in the spec!
	  e.attlist.add("definition");  //this name is not in the spec!
	  e.attlist.add("att");
	  e.attlist.add("tag");
	  e.attlist.add("flag");
	  e.attlist.add("open");
	  e.attlist.add("delete");
	  e.attlist.add("empty");
	  e.attdefault.put("open","");  //flags
	  e.attdefault.put("delete","");
	  e.attdefault.put("empty","");
	  ielements.put("!element", e);

	  //!attlist
	  e = new sInternalElement();
	  e.empty = true;
	  e.open = false;
	  e.attlist.add("name");  //this name is not in the spec!
	  e.attlist.add("att");
	  ielements.put("!attlist", e);

	  //!entity
	  e = new sInternalElement();
	  e.empty = true;
	  e.open = false;
	  e.attlist.add("name");  //this name is not in the spec!
	  e.attlist.add("value");  //this name is not in the spec!
	  e.attlist.add("desc");
	  e.attlist.add("private");
	  e.attlist.add("publish");
	  e.attlist.add("add");
	  e.attlist.add("delete");
	  e.attlist.add("remove");
	  e.attdefault.put("private", "");  //flags
	  e.attdefault.put("publish", "");
	  e.attdefault.put("delete", "");
	  e.attdefault.put("add", "");
	  e.attdefault.put("remove", "");
	  ielements.put("!entity", e);

	  //var
	  e = new sInternalElement();
	  e.empty = false;
	  e.open = false;
	  e.attlist.add("name");  //this name is not in the spec!
	  e.attlist.add("desc");
	  e.attlist.add("private");
	  e.attlist.add("publish");
	  e.attlist.add("add");
	  e.attlist.add("delete");
	  e.attlist.add("remove");
	  e.attdefault.put("private", "");  //flags
	  e.attdefault.put("publish", "");
	  e.attdefault.put("delete", "");
	  e.attdefault.put("add", "");
	  e.attdefault.put("remove", "");
	  ielements.put("var", e);

	  //b
	  e = new sInternalElement();
	  e.empty = false;
	  e.open = true;
	  ielements.put("b", e);

	  //i
	  e = new sInternalElement();
	  e.empty = false;
	  e.open = true;
	  ielements.put("i", e);

	  //u
	  e = new sInternalElement();
	  e.empty = false;
	  e.open = true;
	  ielements.put("u", e);

	  //s
	  e = new sInternalElement();
	  e.empty = false;
	  e.open = true;
	  ielements.put("s", e);

	  //c
	  e = new sInternalElement();
	  e.empty = false;
	  e.open = true;
	  e.attlist.add("fore");
	  e.attlist.add("back");
	  ielements.put("c", e);

	  //h
	  e = new sInternalElement();
	  e.empty = false;
	  e.open = true;
	  ielements.put("h", e);

	  //font
	  e = new sInternalElement();
	  e.empty = false;
	  e.open = true;
	  e.attlist.add("face");
	  e.attlist.add("size");
	  e.attlist.add("color");
	  e.attlist.add("back");
	  ielements.put("font", e);

	  //nobr
	  e = new sInternalElement();
	  e.empty = true;
	  e.open = false;
	  ielements.put("nobr", e);

	  //p
	  e = new sInternalElement();
	  e.empty = false;
	  e.open = false;
	  ielements.put("p", e);

	  //br
	  e = new sInternalElement();
	  e.empty = true;
	  e.open = false;
	  ielements.put("br", e);

	  //sbr
	  e = new sInternalElement();
	  e.empty = true;
	  e.open = false;
	  ielements.put("sbr", e);

	  //a
	  e = new sInternalElement();
	  e.empty = false;
	  e.open = false;
	  e.attlist.add("href");
	  e.attlist.add("hint");
	  e.attlist.add("expire");
	  ielements.put("a", e);

	  //send
	  e = new sInternalElement();
	  e.empty = false;
	  e.open = false;
	  e.attlist.add("href");
	  e.attlist.add("hint");
	  e.attlist.add("prompt");
	  e.attlist.add("expire");
	  e.attdefault.put("prompt", "");  //flags
	  ielements.put("send", e);

	  //expire
	  e = new sInternalElement();
	  e.empty = true;
	  e.open = false;
	  e.attlist.add("name");  //this name is not in the spec!
	  ielements.put("expire", e);

	  //version
	  e = new sInternalElement();
	  e.empty = true;
	  e.open = false;
	  ielements.put("version", e);

	  //support
	  e = new sInternalElement();
	  e.empty = true;
	  e.open = false;
	  ielements.put("support", e);

	  //h1
	  e = new sInternalElement();
	  e.empty = false;
	  e.open = false;
	  ielements.put("h1", e);

	  //h2
	  e = new sInternalElement();
	  e.empty = false;
	  e.open = false;
	  ielements.put("h2", e);

	  //h3
	  e = new sInternalElement();
	  e.empty = false;
	  e.open = false;
	  ielements.put("h3", e);

	  //h4
	  e = new sInternalElement();
	  e.empty = false;
	  e.open = false;
	  ielements.put("h4", e);

	  //h5
	  e = new sInternalElement();
	  e.empty = false;
	  e.open = false;
	  ielements.put("h5", e);

	  //h6
	  e = new sInternalElement();
	  e.empty = false;
	  e.open = false;
	  ielements.put("h6", e);

	  //hr
	  e = new sInternalElement();
	  e.empty = true;
	  e.open = false;
	  ielements.put("hr", e);

	  //small
	  e = new sInternalElement();
	  e.empty = false;
	  e.open = false;
	  ielements.put("small", e);

	  //tt
	  e = new sInternalElement();
	  e.empty = false;
	  e.open = false;
	  ielements.put("tt", e);

	  //sound
	  e = new sInternalElement();
	  e.empty = true;
	  e.open = false;
	  e.attlist.add("fname");
	  e.attlist.add("v");
	  e.attlist.add("l");
	  e.attlist.add("p");
	  e.attlist.add("t");
	  e.attlist.add("u");
	  e.attdefault.put("v", "100");
	  e.attdefault.put("l", "1");
	  e.attdefault.put("p", "50");
	  ielements.put("sound", e);

	  //music
	  e = new sInternalElement();
	  e.empty = true;
	  e.open = false;
	  e.attlist.add("fname");
	  e.attlist.add("v");
	  e.attlist.add("l");
	  e.attlist.add("c");
	  e.attlist.add("t");
	  e.attlist.add("u");
	  e.attdefault.put("v", "100");
	  e.attdefault.put("l", "1");
	  e.attdefault.put("c", "1");
	  ielements.put("music", e);

	  //gauge
	  e = new sInternalElement();
	  e.empty = true;
	  e.open = false;
	  e.attlist.add("entity");  //this name is not in the spec!
	  e.attlist.add("max");
	  e.attlist.add("caption");
	  e.attlist.add("color");
	  ielements.put("gauge", e);

	  //stat
	  e = new sInternalElement();
	  e.empty = true;
	  e.open = false;
	  e.attlist.add("entity");  //this name is not in the spec!
	  e.attlist.add("max");
	  e.attlist.add("caption");
	  ielements.put("stat", e);

	  //frame
	  e = new sInternalElement();
	  e.empty = true;
	  e.open = false;
	  e.attlist.add("name");
	  e.attlist.add("action");
	  e.attlist.add("title");
	  e.attlist.add("internal");
	  e.attlist.add("align");
	  e.attlist.add("left");
	  e.attlist.add("top");
	  e.attlist.add("width");
	  e.attlist.add("height");
	  e.attlist.add("scrolling");
	  e.attlist.add("floating");
	  e.attdefault.put("action", "open");
	  e.attdefault.put("align", "top");
	  e.attdefault.put("left", "0");
	  e.attdefault.put("top", "0");
	  e.attdefault.put("internal", "");  //flags
	  e.attdefault.put("scrolling", "");
	  e.attdefault.put("floating", "");
	  ielements.put("frame", e);

	  //dest
	  e = new sInternalElement();
	  e.empty = false;
	  e.open = false;
	  e.attlist.add("name");  //this name is not in the spec!
	  e.attlist.add("x");
	  e.attlist.add("y");
	  e.attlist.add("eol");
	  e.attlist.add("eof");
	  e.attdefault.put("eol", "");  //flags
	  e.attdefault.put("eof", "");
	  ielements.put("dest", e);

	  //relocate
	  e = new sInternalElement();
	  e.empty = true;
	  e.open = false;
	  e.attlist.add("name");  //this name is not in the spec!
	  e.attlist.add("port");  //this name is not in the spec!
	  ielements.put("relocate", e);

	  //user
	  e = new sInternalElement();
	  e.empty = true;
	  e.open = false;
	  ielements.put("user", e);

	  //password
	  e = new sInternalElement();
	  e.empty = true;
	  e.open = false;
	  ielements.put("password", e);

	  //image
	  e = new sInternalElement();
	  e.empty = true;
	  e.open = false;
	  e.attlist.add("fname");
	  e.attlist.add("url");
	  e.attlist.add("t");
	  e.attlist.add("h");
	  e.attlist.add("w");
	  e.attlist.add("hspace");
	  e.attlist.add("vspace");
	  e.attlist.add("align");
	  e.attlist.add("ismap");
	  e.attdefault.put("align", "top");
	  e.attdefault.put("ismap", "");  //flags
	  ielements.put("image", e);

	  //filter
	  e = new sInternalElement();
	  e.empty = true;
	  e.open = false;
	  e.attlist.add("src");
	  e.attlist.add("dest");
	  e.attlist.add("name");
	  ielements.put("filter", e);

	  //finally, define some aliases for internal elements
	  aliases.put("!el", "!element");
	  aliases.put("!at", "!attlist");
	  aliases.put("!en", "!entity");
	  aliases.put("v", "var");
	  aliases.put("bold", "b");
	  aliases.put("strong", "b");
	  aliases.put("italic", "i");
	  aliases.put("em", "i");
	  aliases.put("underline", "u");
	  aliases.put("strikeout", "s");
	  aliases.put("high", "h");
	  aliases.put("color", "c");
	  aliases.put("destination", "dest");
	}
	
	private void removeAll()
	{
	  //external elements only
	  Object names [] = elements.keySet().toArray();
	  for (Object object : names) 
	  {
		removeElement((String)object);
	  }
	}

	private void removeElement (String name)
	{
	  //external elements only
	  if (elements.containsKey(name))
	  {
	    sElement e = elements.get(name);
	    e.element.clear();
	    e.attlist.clear();
	    e.attdefault.clear();
	    e.closingseq.clear();
	    if (e.tag != 0 )
	      lineTags.remove(e.tag);
	    elements.remove(name);
	  }
	}

	  /** destructor */
	  protected void finalize() throws Throwable 
	  {
		  paramexpander = null;
		  parser = null;		  
		  removeAll ();
		  //internal elements
		  Collection<sInternalElement> values = ielements.values();
		  for (sInternalElement internalElement : values) 
		  {
			  internalElement.attlist.clear();
			  internalElement.attdefault.clear();
		  }
		  ielements.clear();
		  aliases.clear();
		  super.finalize();
	  }

	  /** set pointer to cMXPState - needed due to circular dependencies */
	  public void assignMXPState (MXPState st)
	  {
		  state = st;
	  }

	  /** 
	   * is this element defined? 
	   */
	  public boolean elementDefined (String name)
	  {
		  return ((elements.containsKey(name)) || (ielements.containsKey(name)) ||
			      (aliases.containsKey(name)));		  
	  }

	  /** 
	   * is it an internal tag? 
	   */
	  public boolean internalElement (String name)
	  {
	    return ((ielements.containsKey(name)) || (aliases.containsKey(name)));
	  }

	  /** 
	   * is it a custom element? (i.e. defined via <!element>) 
	   */
	  public boolean customElement (String name)
	  {
	    return (elements.containsKey(name));
	  }

	  /** 
	   * open element? 
	   */	  
	  public boolean openElement (String name)
	  {
	    if (!elementDefined (name))
	      return false;
	    if (internalElement (name))
	    {
	      String n = name;
	      if (aliases.containsKey(name))
	        n = aliases.get(name);
	      return ielements.get(n).open;
	    }
	    else
	      return elements.get(name).open;
	  }

	  /** 
	   * empty element? i.e. does it need a closing tag? 
	   */
	  public boolean emptyElement (String name)
	  {
	    if (!elementDefined (name))
	      return false;
	    if (internalElement (name))
	    {
	      String n = name;
	      if (aliases.containsKey(name))
	        n = aliases.get(name);
	      return ielements.get(n).empty;
	    }
	    else
	      return elements.get(name).empty;
	  }

	  public void gotTag (String tag) throws Exception
	  {
	    String tagname = "";
	    ArrayList<sParam> params = new ArrayList<sParam>();
	    sParam param = new sParam();
	    param.flag = false;
	    char quote = 0;
	    tagParserState pstate = tagParserState.tagBegin;
	    for (int i = 0; i<tag.length(); i++)
	    {
	      char ch = tag.charAt(i);
	      //process character
	      switch (pstate) {
	        case tagBegin: {
	          if (ch != ' ')
	          {
	            pstate = tagParserState.tagName;
	            tagname += ch;
	          }
	          break;
	        }
	        case tagName: {
	          if (ch == ' ')
	            pstate = tagParserState.tagBetweenParams;
	          else
	            tagname += ch;
	          break;
	        }
	        case tagParam: {
	          if (ch == '=')
	            pstate = tagParserState.tagParamValue;
	          else if (ch == ' ')
	          {
	            //one parameter, value only (it could also be a flag, we'll check that later)
	            param.value = param.name;
	            param.name = "";
	            //add a new parameter :-)
	            params.add(param);
	            param = new sParam();
	            param.value = "";
	            pstate = tagParserState.tagBetweenParams;
	          }
	          else
	            param.name += ch;
	          break;
	        }
	        case tagParamValue: {
	          if (ch == ' ')
	          {
	            //add a new parameter :-)
	            params.add(param);
	            param = new sParam();
	            param.name = "";
	            param.value = "";
	            pstate = tagParserState.tagBetweenParams;
	          }
	          else if (param.value.isEmpty() && ((ch == '\'') || (ch == '"')))
	          {
	            pstate = tagParserState.tagQuotedParam;
	            quote = ch;
	          }
	          else
	            param.value += ch;
	          break;
	        }
	        case tagQuotedParam: {
	          if (ch == quote)
	          {
	            //add a new parameter :-)
	            params.add(param);
	            param = new sParam();
	            param.name = "";
	            param.value = "";
	            pstate = tagParserState.tagAfterQuotedParam;
	          }
	          else
	            param.value += ch;
	          break;
	        }
	        case tagAfterQuotedParam: {
	          if (ch == ' ')    //ignore everything up to some space...
	            pstate = tagParserState.tagBetweenParams;
	          break;
	        }
	        case tagBetweenParams: {
	          if (ch != ' ')
	          {
	            if ((ch == '\'') || (ch == '"'))
	            {
	              pstate = tagParserState.tagQuotedParam;
	              param.name = "";
	              quote = ch;
	            }
	            else
	            {
	              pstate = tagParserState.tagParam;
	              param.name += ch;
	            }
	          }
	          break;
	        }
	      };
	    }

	    //last parameter...
	    switch (pstate) 
	    {
	      case tagBegin:
	        results.addToList (results.createError ("Received a tag with no body!"));
	        break;
	      case tagParam: {
	        param.value = param.name;
	        param.name = "";
	        params.add(param);
	        param = new sParam();
	        }
	        break;
	      case tagParamValue:
	        params.add(param);
	        param = new sParam();
	        break;
	      case tagQuotedParam:
	        results.addToList (results.createError ("Received tag " + tagname +
	            " with unfinished quoted parameter!"));
	        break;
	    };

	    //nothing more to do if the tag has no contents...
	    if (pstate == tagParserState.tagBegin) return;
	    
	    //convert tag name to lowercase
	    tagname = tagname.toLowerCase();
	    
	    //handle closing tag...
	    if (tagname.charAt(0) == '/')
	    {
	      if (!params.isEmpty())
	      {
	        results.addToList (results.createError ("Received closing tag " + tagname +
	            " with parametrs!"));
	      }
	      //remove that '/'
	      tagname = tagname.substring(1);
	      //and call closing tag processing
	      handleClosingTag(tagname);
	      return;
	    }
	    
	    //convert all parameter names to lower-case
	    for (sParam param2 : params) 
	    {
	    	param2.name = param2.name.toLowerCase();
		}
	    
	    //now we check the type of the tag and act accordingly
	    if (!elementDefined (tagname))
	    {
	      params.clear();
	      results.addToList (results.createError ("Received undefined tag " + tagname + "!"));
	      return;
	    }

	    mxpMode m = state.getMXPMode ();
	    //mode can be open or secure; locked mode is not possible here (or we're in a bug)
	    if (m == mxpMode.openMode)
	      //open mode - only open tags allowed
	      if (!openElement (tagname))
	      {
	      params.clear();
	        results.addToList (results.createError ("Received secure tag " + tagname +
	            " in open mode!"));
	        return;
	      }

	    if (internalElement (tagname))
	    {
	      //if the name is an alias for another tag, change the name
	      if (aliases.containsKey(tagname))
	        tagname = aliases.get(tagname);
	      //the <support> tag has to be handled separately :(
	      if (tagname.equals("support"))
	      {
	        processSupport(params);
	        return;
	      }
	      //identify all flags in the tag
	      identifyFlags(ielements.get(tagname).attdefault, params);
	      //correctly identify all parameters (assign names where necessary)
	      handleParams (tagname, params, ielements.get(tagname).attlist,
	    		  ielements.get(tagname).attdefault);
	      //separate out all the flags (flags are only valid for internal tags)
	      ArrayList<String> flags = new ArrayList<String>();
	      for ( int i =0; i < params.size();)
	      {
	    	  sParam item = params.get(i);
	    	  if (item.flag)
	    	  {
	    		  flags.add(item.name);
	    		  params.remove(i);
	    	  }
	    	  else
	    		  i++;
	      }
	      //okay, parsing done - send the tag for further processing
	      processInternalTag(tagname, params, flags);
	    }
	    else
	    {
	      handleParams (tagname, params, elements.get(tagname).attlist,
	    		  elements.get(tagname).attdefault);
	      processCustomTag(tagname, params);
	    }
	    
	    params.clear ();
	  }

	  private void handleClosingTag (String name)
	  {
	    String n = name.toLowerCase();
	    if (!elementDefined (n))
	    {
	      results.addToList (results.createError ("Received unknown closing tag </" + n + ">!"));
	      return;
	    }
	    if (emptyElement (n))
	    {
	      results.addToList (results.createError ("Received closing tag for tag " + n +
	          ", which doesn't need a closing tag!"));
	      return;
	    }
	    if (internalElement (n))
	    {
	      //if the name is an alias for another tag, change the name
	      if (aliases.containsKey(n))
	      {
	    	  n = aliases.get(n);
	      }
	      state.gotClosingTag(n);
	    }
	    else
	    {
	      //send closing flag, if needed
	      if (!elements.get(n).flag.isEmpty())
	        state.gotFlag (false, elements.get(n).flag);
	      
	      //expand the closing tag...
	      for (String item : elements.get(n).closingseq) 
	      {
	    	  handleClosingTag(item);
	      }
	    }
	  }

	  private void processSupport (List<sParam> params) throws Exception
	  {
	    List<String> pars = new ArrayList<String>();
	    for (sParam param : params) 
	    {
			pars.add(param.value);
		}
	    state.gotSUPPORT(pars);
	  }

	  private void identifyFlags (HashMap<String, String> attdefault, List<sParam> args)
	  {
		  for (sParam param : args) 
		  {
			  if ( param.name.isEmpty())
			  {
				  String s = param.value.toLowerCase();
				  if ( attdefault.containsKey(s) && (attdefault.get(s).isEmpty()))
				  {
					param.name = s;
					param.value = "";
					param.flag = true; 
				  }
			  }
		  }
	  }
	  
	  private void handleParams (String tagname, List<sParam> args,
			    List<String> attlist, HashMap<String, String> attdefault)
			{
			  //list<string>::const_iterator cur = attlist.begin();
			  //list<sParam>::iterator it;
		  	  int cur = 0;
			  for (sParam item : args) 
			  {				  
				//flag?
			    if (item.flag)
			    {
			      //only advance parameter iterator
			      cur++;
			    }
			    //not a flag
			    else
			    {
			      //empty name?
			      if (item.name.isEmpty())
			      {
			        //set the parameter name:
			        
			        //find the correct attribute name, skipping all flags
			        while (cur < attlist.size())
			        {
			          if ((attdefault.containsKey(attlist.get(cur))) 
			        		  && (attdefault.get(attlist.get(cur)).isEmpty()))  //flag
			            cur++;
			          else
			            break;  //okay, found the correct parameter
			        }
			        if (cur == attlist.size())    //ARGH! Parameter not found :(
			        {
			          results.addToList (results.createError ("Received too many parameters for tag " +
			              tagname + "!"));
			          continue;  //continue with the next parameter...
			        }
			      }
			      //non-empty name?
			      else
			      {
			        //set "cur" to the parameter following the given one
			        
			        //to speed things up a bit, first look if the iterator is pointing at the right parameter
			        // (we won't need to traverse the whole list, if it does)
			        if ((cur == attlist.size()) || (item.name != attlist.get(cur)))
			        {
			          int cur2 = cur;  //remember old "cur" value
			          for (cur = 0; cur < attlist.size(); cur++)
			            if (item.name.equals(attlist.get(cur)))
			              break;
			          if (cur == attlist.size())    //parameter name not found
			          {
			            //restore old iterator value
			            cur = cur2;
			            results.addToList (results.createError ("Received unknown parameter " +
			                item.name + " in tag " + tagname + "!"));
			            //clear name/value to avoid confusion in later stages
			            item.name = "";
			            item.value = "";
			            //proceed with next parameter
			            continue;
			          }
			          //if cur isn't attlist.end(), it's now set to the correct value...
			        }
			      }
			      
			      //things common to all non-flag parameters...
			      
			      //set parameter name
			      item.name = attlist.get(cur);
			      //if parameter value is empty, set it to default value (if any)
			      if (item.value.isEmpty() && (attdefault.containsKey(attlist.get(cur))))
			        item.value = attdefault.get(attlist.get(cur));
			      //advance parameter iterator
			      cur++;
			    }
			  }
			  
			  //finally, we add default parameter values to the beginning of the list... these shall get
			  //overridden by given values, if any (those shall be later in the list)
			  for (Object item : attdefault.keySet().toArray()) 
			  {
				if ( !attdefault.get(item).isEmpty())
				{
					sParam s = new sParam();
				    s.flag = false;
				    s.name = (String)item;
				    s.value = attdefault.get(item);
				    args.add(0, s);					
				}
			  }
			}

	  private void processInternalTag (String name2, List<sParam> params,
			    List<String> flags)
			{
			  //list<sParam>::const_iterator it;
			  //list<string>::const_iterator it2;
			  if (name2.equals("!element"))
			  {
			    String lname = "", definition = "", att = "", flag = "";
			    int tag = 0;
			    boolean fopen = false, fdelete = false, fempty = false;
			    for (sParam item : params) 
			    {
			      String s = item.name;
			      if (s.equals("name")) lname = item.value.toLowerCase();
			      if (s.equals("definition")) definition = item.value;
			      if (s.equals("att")) att = item.value;
			      if (s.equals("flag")) flag = item.value;
			      if (s.equals("tag")) tag = Integer.parseInt(item.value);
			    }
			    for (String string : flags) 
			    {
			      if (string.equals("open")) fopen = true;
			      if (string.equals("delete")) fdelete = true;
			      if (string.equals("empty")) fempty = true;
			    }
			    
			    if (lname.isEmpty())
			    {
			      results.addToList (results.createError (
			          "Received an <!element> tag with no element name!"));
			      return;
			    }
			    //definition can be empty, that's no problem...

			    //if we want to delete the tag...
			    if (fdelete)
			    {
			      //sanity check
			      if (!elements.containsKey(lname))
			      {
			        results.addToList (results.createWarning (
			            "Received request to remove an undefined tag " + lname + "!"));
			        return;
			      }
			      removeElement (lname);
			      return;
			    }
			    
			    //parse tag definition
			    parser.simpleParse(definition);
			    ArrayList<sElementPart> tagcontents = new ArrayList<sElementPart>();
			    while (parser.hasNext())
			    {
			      chunk ch = parser.getNext();
			      if (ch.chk == chunkType.chunkError)
			        results.addToList (results.createError (ch.text));
			      else
			      {
			        //create a new element part
			        sElementPart part = new sElementPart();
			        part.text = ch.text;
			        part.istag = (ch.chk == chunkType.chunkTag) ? true : false;
			        tagcontents.add(part);
			      }
			    }
			    
			    //parse attribute list
			    ArrayList<String> attlist = new ArrayList<String>();
			    HashMap<String, String> attdefault = new HashMap<String, String>();
			    processParamList (att, attlist, attdefault);
			    
			    //and do the real work
			    addElement(lname, tagcontents, attlist, attdefault, fopen, fempty, tag, flag);
			  }
			  else if (name2.equals("!attlist"))
			  {
			    String name = "", att = "";
			    for ( Iterator it = params.iterator(); it.hasNext();)
			    {
			    	sParam item = (sParam)it.next();
			    	String s = item.name;
			    	if (s.equals("name")) name = item.value;
			    	if (s.equals("att")) att = item.value;
			    }
			    
			    if (name.isEmpty())
			    {
			      results.addToList (results.createError (
			          "Received an <!attlist> tag with no element name!"));
			      return;
			    }

			    //parse attribute list
			    ArrayList<String> attlist =  new ArrayList<String>();
			    HashMap<String, String> attdefault = new HashMap<String, String>();
			    processParamList (att, attlist, attdefault);
			    
			    //and do the real work
			    setAttList (name, attlist, attdefault);
			  }
			  else if (name2.equals("!entity"))
			  {
			    String name = "", value = "", desc;
			    boolean fpriv = false, fpub = false, fadd = false, fdel = false, frem = false;
			    for (sParam item : params) 
			    {
			      String s = item.name;
			      if (s.equals("name")) name = item.value;
			      if (s.equals("value")) value = item.value;
			      if (s.equals("desc")) desc = item.value;
			    }
			    for (String item : flags) 
			    {
			      if (item.equals( "private")) fpriv = true;
			      if (item.equals( "publish")) fpub = true;
			      if (item.equals( "delete")) fdel = true;
			      if (item.equals( "add")) fadd = true;
			      if (item.equals( "remove")) frem = true;
			    }
			    
			    if (name.isEmpty())
			    {
			      results.addToList (results.createError (
			          "Received an <!entity> tag with no variable name!"));
			      return;
			    }
			    
			    //fpub is IGNORED...
			    //fadd and frem is IGNORED...
			    if (!(fadd) && !(frem))
			    {
			      if (fdel)
			      {
			        entities.deleteEntity (name);
			        if (!fpriv) //do not announce PRIVATE entities
			          state.gotVariable (name, "", true);
			      }
			      else
			      {
			        //we now have a new variable...
			        entities.addEntity (name, value);
			        if (!fpriv) //do not announce PRIVATE entities
			          state.gotVariable (name, value, false);
			      }
			    }
			    else
			      results.addToList (results.createWarning (
			          "Ignored <!ENTITY> tag with ADD or REMOVE flag."));
			  }
			  else if (name2.equals("var"))
			  {
			    //this is very similar to the !entity handler above...
			    
			    String name = "", desc = "";
			    boolean fpriv = false, fpub = false, fadd = false, fdel = false, frem = false;
			    for ( sParam item : params) 
			    {
			      String s = item.name;
			      if (s.equals("name")) name = item.value;
			      if (s.equals("desc")) desc = item.value;
			    }
			    for ( String item : flags)
			    {  
			      if (item.equals("private")) fpriv = true;
			      if (item.equals("publish")) fpub = true;
			      if (item.equals("add")) fadd = true;
			      if (item.equals("delete")) fdel = true;
			      if (item.equals("remove")) frem = true;
			    }
			    
			    if (name.isEmpty())
			    {
			      results.addToList (results.createError (
			          "Received an <var> tag with no variable name!"));
			      return;
			    }
			    
			    //fpriv and fpub is IGNORED...
			    //fadd and fdel is IGNORED...
			    if (!(fadd) && !(fdel))
			      state.gotVAR (name);
			    else
			      results.addToList (results.createWarning ("Ignored <VAR> tag with ADD or REMOVE flag."));
			  }
			  else if (name2.equals("b"))
			    state.gotBOLD();
			  else if (name2.equals("i"))
			    state.gotITALIC();
			  else if (name2.equals("u"))
			    state.gotUNDERLINE();
			  else if (name2.equals("s"))
			    state.gotSTRIKEOUT();
			  else if (name2.equals("c"))
			  {
			    String fore = "", back = "";
			    for (sParam item : params) 
			    {
			      String s = item.name;
			      if (s.equals("fore")) fore = item.value;
			      if (s.equals("back")) back = item.value;
			    }
			    Color fg = state.fgColor();
			    Color bg = state.bgColor();
			    if (!fore.isEmpty())
			      fg = MXPColors.self().getColor(fore);
			    if (!back.isEmpty())
			      bg = MXPColors.self().getColor(back);
			    state.gotCOLOR (fg, bg);
			  }
			  else if (name2.equals("h"))
			    state.gotHIGH();
			  else if (name2.equals("font"))
			  {
			    String face = "", fore="", back="";
			    int size = 0;
			    for ( sParam item : params) 
			    {
			      String s = item.name;
			      if (s.equals("face")) face = item.value;
			      if (s.equals("size")) size = Integer.parseInt(item.value);
			      if (s.equals("color")) fore = item.value;
			      if (s.equals("back")) back = item.value;
			    }
			    if (face.isEmpty())
			      face = state.fontFace();
			    if (size == 0)
			      size = state.fontSize();
			    Color fg = state.fgColor();
			    Color bg = state.bgColor();
			    if (!fore.isEmpty())
			      fg = MXPColors.self().getColor(fore);
			    if (!back.isEmpty())
			      bg = MXPColors.self().getColor(back);
			    state.gotFONT (face, size, fg, bg);
			  }
			  else if (name2.equals("p"))
			    state.gotP();
			  else if (name2.equals("br"))
			    state.gotBR();
			  else if (name2.equals("nobr"))
			    state.gotNOBR();
			  else if (name2.equals("sbr"))
			    state.gotSBR();
			  else if (name2.equals("a"))
			  {
			    String href = "", hint = "", expire = "";
			    for ( sParam item : params) 
			    {
			      String s = item.name;
			      if (s.equals("href")) href = item.value;
			      if (s.equals("hint")) hint = item.value;
			      if (s.equals("expire")) expire = item.value;
			    }
			    state.gotA (href, hint, expire);
			  }
			  else if (name2.equals("send"))
			  {
			    String href = "", hint = "", expire = "";
			    boolean prompt = false;
			    for ( sParam item : params) 
			    {
			      String s = item.name;
			      if (s.equals("href")) href = item.value;
			      if (s.equals("hint")) hint = item.value;
			      if (s.equals("expire")) expire = item.value;
			    }
			    for ( String item : flags)
			    {  
			      if (item.equals("prompt")) prompt = true;
			    }
			    state.gotSEND (href, hint, prompt, expire);
			  }
			  else if (name2.equals("expire"))
			  {
			    String name = "";
			    for ( sParam item : params) 
			    {
			      String s = item.name;
			      if (s.equals("name")) name = item.value;
			    }
			    //name can be empty - all named links shall then expire
			    state.gotEXPIRE(name);
			  }
			  else if (name2.equals("version"))
			    state.gotVERSION();
			  else if (name2.equals("h1"))
			    state.gotHtag (1);
			  else if (name2.equals("h2"))
			    state.gotHtag (2);
			  else if (name2.equals("h3"))
			    state.gotHtag (3);
			  else if (name2.equals("h4"))
			    state.gotHtag (4);
			  else if (name2.equals("h5"))
			    state.gotHtag (5);
			  else if (name2.equals("h6"))
			    state.gotHtag (6);
			  else if (name2.equals("hr"))
			    state.gotHR();
			  else if (name2.equals("small"))
			    state.gotSMALL();
			  else if (name2.equals("tt"))
			    state.gotTT();
			  else if (name2.equals("sound"))
			  {
			    String fname = "", t = "", u = "";
			    int v = 0, l = 0, p = 0;  //shall be overridden by defaults...
			    for ( sParam item : params) 
			    {
			      String s = item.name;
			      if (s.equals("fname")) fname = item.value;
			      if (s.equals("t")) t = item.value;
			      if (s.equals("u")) u = item.value;
			      if (s.equals("v")) v = Integer.parseInt(item.value);
			      if (s.equals("l")) l = Integer.parseInt(item.value);
			      if (s.equals("p")) p = Integer.parseInt(item.value);
			    }
			    if (fname.isEmpty())
			    {
			      results.addToList (results.createError ("Received SOUND tag with no file name!"));
			      return;
			    }
			    if ((v < 0) ||  (v > 100))
			    {
			      results.addToList (results.createWarning ("Ignoring incorrect V param for SOUND tag."));
			      v = 100;  //set default value
			    }
			    if ((l < -1) || (l > 100) || (l == 0))
			    {
			      results.addToList (results.createWarning ("Ignoring incorrect L param for SOUND tag."));
			      l = 1;  //set default value
			    }
			    if ((p < 0) ||  (p > 100))
			    {
			      results.addToList (results.createWarning ("Ignoring incorrect P param for SOUND tag."));
			      p = 50;  //set default value
			    }
			    state.gotSOUND (fname, v, l, p, t, u);
			  }
			  else if (name2.equals("music"))
			  {
			    String fname = "", t = "", u = "";
			    int v = 0, l = 0, c = 0;  //shall be overridden by defaults...
			    for ( sParam item : params) 
			    {
			      String s = item.name;
			      if (s.equals("fname")) fname = item.value;
			      if (s.equals("t")) t = item.value;
			      if (s.equals("u")) u = item.value;
			      if (s.equals("v")) v = Integer.parseInt(item.value);
			      if (s.equals("l")) l = Integer.parseInt(item.value);
			      if (s.equals("c")) c = Integer.parseInt(item.value);
			    }
			    if (fname.isEmpty())
			    {
			      results.addToList (results.createError ("Received MUSIC tag with no file name!"));
			      return;
			    }
			    if ((v < 0) ||  (v > 100))
			    {
			      results.addToList (results.createWarning ("Ignoring incorrect V param for MUSIC tag."));
			      v = 100;  //set default value
			    }
			    if ((l < -1) || (l > 100) || (l == 0))
			    {
			      results.addToList (results.createWarning ("Ignoring incorrect L param for MUSIC tag."));
			      l = 1;  //set default value
			    }
			    if ((c != 0) && (c != 1))
			    {
			      results.addToList (results.createWarning ("Ignoring incorrect C param for MUSIC tag."));
			      c = 1;  //set default value
			    }
			    state.gotMUSIC (fname, v, l, (c!=0), t, u);
			  }
			  else if (name2.equals("gauge"))
			  {
			    String entity = "", max = "", caption = "", color = "";
			    for ( sParam item : params) 
			    {
			      String s = item.name;
			      if (s.equals("entity")) entity = item.value;
			      if (s.equals("max")) max = item.value;
			      if (s.equals("caption")) caption = item.value;
			      if (s.equals("color")) color = item.value;
			    }
			    if (entity.isEmpty())
			    {
			      results.addToList (results.createError ("Received GAUGE with no entity name!"));
			      return;
			    }
			    Color c;
			    if (color.isEmpty()) color = "white";
			    c = MXPColors.self().getColor(color);
			    state.gotGAUGE (entity, max, caption, c);
			  }
			  else if (name2.equals("stat"))
			  {
			    String entity = "", max = "", caption = "";
			    for ( sParam item : params) 
			    {
			      String s = item.name;
			      if (s.equals("entity")) entity = item.value;
			      if (s.equals("max")) max = item.value;
			      if (s.equals("caption")) caption = item.value;
			    }
			    if (entity.isEmpty())
			    {
			      results.addToList (results.createError ("Received STAT with no entity name!"));
			      return;
			    }
			    state.gotSTAT (entity, max, caption);
			  }
			  else if (name2.equals("frame"))
			  {
			    String name = "", action = "", title = "", align = "";
			    int left = 0, top = 0, width = 0, height = 0;
			    boolean finternal = false, fscroll = false, ffloat = false;
			    for ( sParam item : params) 
			    {
			      String s = item.name;
			      if (s.equals("name")) name = item.value;
			      if (s.equals("action")) action = item.value;
			      if (s.equals("title")) title = item.value;
			      if (s.equals("align")) align = item.value;
			      if (s.equals("left")) left = state.computeCoord (item.value, true,false);
			      if (s.equals("top")) top = state.computeCoord (item.value, false,false);
			      if (s.equals("width")) width = state.computeCoord (item.value, true,false);
			      if (s.equals("height")) height = state.computeCoord (item.value, false,false);
			    }
			    for ( String item : flags)
			    {  
			      if (item.equals("internal")) finternal = true;
			      if (item.equals("scrolling")) fscroll = true;
			      if (item.equals("floating")) ffloat = true;
			    }
			    if (name.isEmpty())
			    {
			      results.addToList (results.createError ("Received FRAME tag with no frame name!"));
			      return;
			    }
			    state.gotFRAME (name, action, title, finternal, align, left, top, width, height,
			        fscroll, ffloat);
			  }
			  else if (name2.equals("dest"))
			  {
			    String name = "";
			    int x = 0, y = 0;
			    boolean feol = false, feof = false;
			    for ( sParam item : params) 
			    {
			      String s = item.name;
			      if (s.equals("name")) name = item.value;
			      if (s.equals("x")) x = Integer.parseInt(item.value);
			      if (s.equals("y")) y = Integer.parseInt(item.value);
			    }
			    for ( String item : flags)
			    {  
			      if (item.equals("eol")) feol = true;
			      if (item.equals("eof")) feof = true;
			    }
			    if (name.isEmpty())
			    {
			      results.addToList (results.createError ("Received DEST tag with no frame name!"));
			      return;
			    }
			    state.gotDEST (name, x, y, feol, feof);
			  }
			  else if (name2.equals("relocate"))
			  {
			    String name = "";
			    int port = 0;
			    for ( sParam item : params) 
			    {
			      String s = item.name;
			      if (s.equals("name")) name = item.value;
			      if (s.equals("port")) port = Integer.parseInt(item.value);
			    }
			    if (name.isEmpty())
			    {
			      results.addToList (results.createError ("Received RELOCATE tag with no server name!"));
			      return;
			    }
			    if (port == 0)
			    {
			      results.addToList (results.createError ("Received RELOCATE tag with no server port!"));
			      return;
			    }
			    state.gotRELOCATE (name, port);
			  }
			  else if (name2.equals("user"))
			    state.gotUSER();
			  else if (name2.equals("password"))
			    state.gotPASSWORD();
			  else if (name2.equals("image"))
			  {
			    String name="", url="", t="", align="";
			    int h = 0, w = 0, hspace = 0, vspace = 0;
			    boolean fismap = false;
			    for ( sParam item : params) 
			    {
			      String s = item.name;
			      if (s.equals("fname")) name = item.value;
			      if (s.equals("url")) url = item.value;
			      if (s.equals("t")) t = item.value;
			      if (s.equals("align")) align = item.value;
			      if (s.equals("h")) h = state.computeCoord (item.value, true, true);
			      if (s.equals("w")) w = state.computeCoord (item.value, false, true);
			      if (s.equals("hspace")) hspace = Integer.parseInt(item.value);;
			      if (s.equals("vspace")) vspace = Integer.parseInt(item.value);;
			    }
			    for ( String item : flags)
			    {  
			      if (item.equals("ismap")) fismap = true;
			    }
			    if (name.isEmpty())
			    {
			      results.addToList (results.createError ("Received IMAGE tag with no image name!"));
			      return;
			    }
			    state.gotIMAGE (name, url, t, h, w, hspace, vspace, align, fismap);
			  }
			  else if (name2.equals("filter"))
			  {
			/*
			    String src, dest, name;
			    for (it = params.begin(); it != params.end(); ++it)
			    {
			      String s = (*it).name;
			      if (s.equals("src") src = (*it).value;
			      if (s.equals("dest") dest = (*it).value;
			      if (s.equals("name") name = (*it).value;
			    }
			    state.gotFILTER (src, dest, name);
			*/
			    results.addToList (results.createWarning ("Ignoring unsupported FILTER tag."));
			  }
			}

	  private void setAttList(String name, ArrayList<String> attlist,
			HashMap<String, String> attdefault) 
	  {
		  //sanity check
		  if (!elements.containsKey(name))
		  {
		    results.addToList (results.createWarning ("Received attribute list for undefined tag " +
		        name + "!"));
		    return;
		  }
		  sElement e = elements.get(name);
		  e.attlist.clear();
		  e.attdefault.clear();
		  e.attlist = attlist;
		  e.attdefault = attdefault;		 
	  }

	private void addElement(String name, List<sElementPart> contents,
			ArrayList<String> attlist, HashMap<String, String> attdefault,
			boolean open, boolean empty, int tag, String flag) 
	  {
		  //sanity checks
		  if (elementDefined (name))
		  {
		    results.addToList (results.createError ("Multiple definition of element " + name + "!"));
		    return;
		  }

		  sElement e = new sElement();
		  e.open = open;
		  e.empty = empty;
		  if ((tag >= 20) && (tag <= 99))
		  {
		    e.tag = tag;
		    if (lineTags.containsKey(tag))
		      results.addToList (results.createError ("Element " + name +
		          " uses an already assigned line tag!"));
		    lineTags.put(tag, name);
		  }
		  else
		    e.tag = 0;
		  e.flag = flag;

		  //assign element contents, generating the list of closing tags
		  e.element.clear();
		  for (sElementPart ep : contents) 
		  {
		    if (ep.istag)
		    {
		      String tag1 = ep.text.split(" ")[0].toLowerCase();
		      if (elementDefined (tag1))
		      {
		        if (open && !(openElement (tag1)))
		        {
		          ep = null;
		          results.addToList (results.createError ("Definition of open " + name +
		              " tag contains secure tag " + tag1 + "!"));
		        }
		        else if (empty && !(emptyElement (tag1)))
		        {
		        	ep = null;
		          results.addToList (results.createError ("Definition of empty " + name +
		              " tag contains non-empty tag " + tag1 + "!"));
		        }
		        else
		        {
		          e.element.add (ep);
		          if (!emptyElement(tag1)) e.closingseq.add(0,tag1);
		        }
		      }
		      else
		      {
		        //element doesn't exist yet - we must believe that it's correct
		        e.element.add(ep);
		        if (!empty) e.closingseq.add(0,tag1);
		        results.addToList (results.createWarning ("Definition of element " + name +
		            " contains undefined element " + tag1 + "!"));
		      }
		    }
		    else
		      e.element.add(ep);
		  }

		  //assign the element definition
		  elements.put(name,e);

		  //set attribute list
		  setAttList (name, attlist, attdefault);		  
	  }

	private void processParamList(String params, ArrayList<String> attlist,
			HashMap<String, String> attdefault) 
	  {
		  //this is similar to the parser in gotTag(), but it's a bit simpler...
		  if (params==null) return;
		  String name = "", value = "";
		  char quote = 0;
		  paramParserState state = paramParserState.parNone;
		  for ( int i =0; i < params.length(); i++)
		  {
		    char ch = params.charAt(i);

		    //process character
		    switch (state) {
		      case parNone: {
		        if (ch != ' ')
		        {
		          state = paramParserState.parName;
		          name += ch;
		        }
		        break;
		      }
		      case parName: {
		        if (ch == '=')
		          state = paramParserState.parValue;
		        else if (ch == ' ')
		        {
		          //new parameter, no default value
		          attlist.add(name.toLowerCase());
		          name = "";
		          state = paramParserState.parNone;
		        }
		        else
		          name += ch;
		        break;
		      }
		      case parValue: {
		        if (ch == ' ')
		        {
		          //new parameter, with default value
		          attlist.add(name.toLowerCase());
		          attdefault.put(name, value);
		          name = "";
		          value = "";
		          state = paramParserState.parNone;
		        }
		        else if (value.isEmpty() && ((ch == '\'') || (ch == '"')))
		        {
		          state = paramParserState.parQuotedValue;
		          quote = ch;
		        }
		        else
		          value += ch;
		        break;
		      }
		      case parQuotedValue: {
		        if (ch == quote)
		        {
		          //new parameter, with default value
		          attlist.add(name.toLowerCase());
		          attdefault.put(name, value);
		          name = "";
		          value = "";
		          state = paramParserState.parNone;
		        }
		        else
		          value += ch;
		        break;
		      }
		    };
		  }

		  //last parameter...
		  switch (state) {
		    case parName: {
		      //new parameter, no default value
		      attlist.add(name.toLowerCase());
		    }
		    break;
		    case parValue: {
		      //new parameter, with default value
		      attlist.add(name.toLowerCase());
		      attdefault.put(name, value);
		      break;
		    }
		    case parQuotedValue:
		      results.addToList (results.createWarning (
		          "Received tag definition with unfinished quoted default parameter value!"));
		      //new parameter, with default value (unfinished, but hey...)
		      attlist.add(name.toLowerCase());
		      attdefault.put(name, value);
		    break;
		  };
		  
		  //everything done...
		  
	  }

	private void processCustomTag(String name, List<sParam> params) throws Exception
	  {
	    //generate a mapping with all parameter values
	    paramexpander.reset(false);
	    for (sParam param : params) 
	    {
		      //assign parameter value... default values and stuff were already expanded
		      paramexpander.addEntity (param.name, "'" + param.value + "'");			
		}	    
	    //process tag contents one by one
	    for (sElementPart sep : elements.get(name).element) 
	    {
	      String part = sep.text;
	      
	      //expand tag parameters first
	      part = paramexpander.expandEntities (part, true);
	      
	      //parameters are expanded, process this part
	      if (sep.istag)
	        //this part is another tag - expand it recursively
	        gotTag(part);
	      else
	        //this part is regular text
	        state.gotText(part,true);
	    }

	    if (!elements.get(name).flag.isEmpty())
	      state.gotFlag (true, elements.get(name).flag);
	  }

	void gotLineTag (int number) throws Exception
	{
	  if ((number < 20) || (number > 99))
	  {
	    lastLineTag = 0;
	    return;
	  }
	  if (!lineTags.containsKey(number))
	  {
	    lastLineTag = 0;
	    return;
	  }
	  String tag = lineTags.get(number);
	  lastLineTag = number;
	  //behave as if we've just gotten the appropriate tag
	  gotTag(tag);
	}

	public void gotNewLine ()
	{
	  if ((lastLineTag < 20) || (lastLineTag > 99))
	  {
	    lastLineTag = 0;
	    return;
	  }
	  if (!lineTags.containsKey(lastLineTag))
	  {
	    lastLineTag = 0;
	    return;
	  }
	  String tag = lineTags.get(lastLineTag);
	  lastLineTag = 0;
	  if (emptyElement (tag))
	    //no closing tag needed
	    return;
	  //okay, send out the appropriate closing tag
	  handleClosingTag (tag);
	}
	
}