protected String strFilter(Modifiable E, List<String> ignoreStats, String defPrefix, String str, XMLTag piece, Map<String,Object> defined) throws CMException,PostProcessException
	{
		List<Varidentifier> vars=parseVariables(str);
		final boolean killArticles=str.toLowerCase().startsWith("(a(n))");
		while(vars.size()>0)
		{
			final Varidentifier V=vars.remove(vars.size()-1);
			Object val;
			if(V.isMathExpression)
			{
				final String expression=strFilter(E,ignoreStats,defPrefix,V.var,piece, defined);
				if(CMath.isMathExpression(expression))
				{
					if(expression.indexOf('.')>0)
						val=""+CMath.parseMathExpression(expression);
					else
						val=""+CMath.parseLongExpression(expression);
				}
				else
					throw new CMException("Invalid math expression '$"+expression+"' in str '"+str+"'");
			}
			else
			if(V.var.toUpperCase().startsWith("STAT:") && (E!=null))
			{
				final String[] parts=V.var.toUpperCase().split(":");
				if(E instanceof Environmental)
				{
					Environmental E2=(Environmental)E;
					for(int p=1;p<parts.length-1;p++)
					{
						final Room R=CMLib.map().roomLocation(E2);
						Environmental E3;
						if(parts[p].equals("ROOM"))
							E3=CMLib.map().roomLocation(E2);
						else
						if(parts[p].equals("AREA"))
							E3=CMLib.map().areaLocation(E2);
						else
						if(CMLib.directions().getDirectionCode(parts[p])>=0)
						{
							if(R==null)
								throw new PostProcessException("Unknown room on object "+E2.ID()+" in variable '"+V.var+"'");
							final int dir=CMLib.directions().getDirectionCode(parts[p]);
							E3=R.getRoomInDir(dir);
						}
						else
						if(parts[p].equals("ANYROOM"))
						{
							if(R==null)
								throw new PostProcessException("Unknown room on object "+E2.ID()+" in variable '"+V.var+"'");
							final List<Room> dirs=new ArrayList<Room>();
							for(int d=0;d<Directions.NUM_DIRECTIONS();d++)
							{
								if((R.getRoomInDir(d)!=null)&&(R.getRoomInDir(d).roomID().length()>0))
									dirs.add(R.getRoomInDir(d));
							}
							if(dirs.size()==0)
								throw new PostProcessException("No anyrooms on object "+E2.ID()+" in variable '"+V.var+"'");
							E3=dirs.get(CMLib.dice().roll(1, dirs.size(), -1));
						}
						else
						if(parts[p].equals("MOB"))
						{
							if(R==null)
								throw new PostProcessException("Unknown room on object "+E2.ID()+" in variable '"+V.var+"'");
							if((R.numInhabitants()==0)||((E2 instanceof MOB)&&(R.numInhabitants()==1)))
								throw new PostProcessException("No mobs in room for "+E2.ID()+" in variable '"+V.var+"'");
							E3=R.fetchInhabitant(CMLib.dice().roll(1, R.numInhabitants(), -1));
						}
						else
						if(parts[p].equals("ITEM"))
						{
							if(R==null)
								throw new PostProcessException("Unknown room on object "+E2.ID()+" in variable '"+V.var+"'");
							if((R.numItems()==0)||((E2 instanceof Item)&&(R.numItems()==1)))
								throw new PostProcessException("No items in room for "+E2.ID()+" in variable '"+V.var+"'");
							E3=R.getItem(CMLib.dice().roll(1, R.numItems(), -1));
						}
						else
						if(parts[p].equals("AREAGATE"))
						{
							if(R==null)
								throw new PostProcessException("Unknown room on object "+E2.ID()+" in variable '"+V.var+"'");
							final List<Room> dirs=new ArrayList<Room>();
							for(int d=0;d<Directions.NUM_DIRECTIONS();d++)
							{
								final Room R2=R.getRoomInDir(d);
								if((R2!=null) && (R2.roomID().length()>0) && (R.getArea()!=R2.getArea()))
									dirs.add(R.getRoomInDir(d));
							}
							if(dirs.size()==0)
							{
								if(defined.get("ROOMTAG_GATEEXITROOM") instanceof Room)
									dirs.add((Room)defined.get("ROOMTAG_GATEEXITROOM"));
								if(dirs.size()==0)
									throw new PostProcessException("No areagates on object "+E2.ID()+" in variable '"+V.var+"'");
							}
							E3=dirs.get(CMLib.dice().roll(1, dirs.size(), -1));
						}
						else
							throw new PostProcessException("Unknown stat code '"+parts[p]+"' on object "+E2.ID()+" in variable '"+V.var+"'");
						if(E3==null)
							throw new PostProcessException("Unknown '"+parts[p]+"' on object "+E2.ID()+" in variable '"+V.var+"'");
						else
							E2=E3;
					}
					E=E2;
				}
				if (E.isStat(parts[parts.length-1]))
					val=E.getStat(parts[parts.length-1]);
				else
					throw new CMException("Unknown stat code '"+parts[parts.length-1]+"' on object "+E+" in variable '"+V.var+"'");
			}
			else
				val = defined.get(V.var.toUpperCase().trim());
			if(val instanceof XMLTag)
			{
				val = findString(E,ignoreStats,defPrefix,"STRING",(XMLTag)val,defined);
			}
			if((val == null)&&(defPrefix!=null)&&(defPrefix.length()>0)&&(E!=null))
			{
				String preValue=V.var;
				if(preValue.toUpperCase().startsWith(defPrefix.toUpperCase()))
				{
					preValue=preValue.toUpperCase().substring(defPrefix.length());
					if((E.isStat(preValue))
					&&((ignoreStats==null)||(!ignoreStats.contains(preValue.toUpperCase()))))
					{
						val=fillOutStatCode(E,ignoreStats,defPrefix,preValue,piece,defined, false);
						XMLTag statPiece=piece;
						while((val == null)
						&&(statPiece.parent()!=null)
						&&(!(defPrefix.startsWith(statPiece.tag())&&(!defPrefix.startsWith(statPiece.parent().tag())))))
						{
							statPiece=statPiece.parent();
							val=fillOutStatCode(E,ignoreStats,defPrefix,preValue,statPiece,defined, false);
						}
						if((ignoreStats!=null)&&(val!=null))
							ignoreStats.add(preValue.toUpperCase());
					}
				}
			}
			if(val == null)
				throw new CMException("Unknown variable '$"+V.var+"' in str '"+str+"'",new CMException("$"+V.var));
			if(V.toUpperCase)
				val=val.toString().toUpperCase();
			if(V.toLowerCase)
				val=val.toString().toLowerCase();
			if(V.toPlural)
				val=CMLib.english().makePlural(val.toString());
			if(V.toCapitalized)
				val=CMStrings.capitalizeAndLower(val.toString());
			if(killArticles)
				val=CMLib.english().cleanArticles(val.toString());
			str=str.substring(0,V.outerStart)+val.toString()+str.substring(V.outerEnd);
			if(vars.size()==0)
				vars=parseVariables(str);
		}
		int x=str.toLowerCase().indexOf("(a(n))");
		while((x>=0)&&(x<str.length()-8))
		{
			if((Character.isWhitespace(str.charAt(x+6)))
			&&(Character.isLetter(str.charAt(x+7))))
			{
				if(CMStrings.isVowel(str.charAt(x+7)))
					str=str.substring(0,x)+"an"+str.substring(x+6);
				else
					str=str.substring(0,x)+"a"+str.substring(x+6);
			}
			else
				str=str.substring(0,x)+"a"+str.substring(x+6);
			x=str.toLowerCase().indexOf("(a(n))");
		}
		return CMLib.xml().restoreAngleBrackets(str).replace('\'','`');
	}