/*****************************
**********************************************
* 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);
}
}