/*
* XModeHandler.java - XML handler for mode files
* :tabSize=4:indentSize=4:noTabs=false:
* :folding=explicit:collapseFolds=1:
*
* Copyright (C) 1999 mike dillon
* Portions copyright (C) 2000, 2001 Slava Pestov
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
package org.gjt.sp.jedit.syntax;
//{{{ Imports
import java.util.*;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.helpers.DefaultHandler;
import org.gjt.sp.jedit.Mode;
import org.gjt.sp.util.Log;
import org.gjt.sp.util.XMLUtilities;
//}}}
/**
* XML handler for mode definition files.
* @version $Id: XModeHandler.java 21831 2012-06-18 22:54:17Z ezust $
*/
public abstract class XModeHandler extends DefaultHandler
{
//{{{ XModeHandler constructor
public XModeHandler (String modeName)
{
this.modeName = modeName;
marker = new TokenMarker();
marker.addRuleSet(new ParserRuleSet(modeName,"MAIN"));
stateStack = new Stack<TagDecl>();
} //}}}
//{{{ resolveEntity() method
public InputSource resolveEntity(String publicId, String systemId)
{
return XMLUtilities.findEntity(systemId, "xmode.dtd", XModeHandler.class);
} //}}}
//{{{ characters() method
public void characters(char[] c, int off, int len)
{
peekElement().setText(c, off, len);
} //}}}
//{{{ startElement() method
public void startElement(String uri, String localName,
String qName, Attributes attrs)
{
TagDecl tag = pushElement(qName, attrs);
if (qName.equals("WHITESPACE"))
{
Log.log(Log.WARNING,this,modeName + ": WHITESPACE rule "
+ "no longer needed");
}
else if (qName.equals("KEYWORDS"))
{
keywords = new KeywordMap(rules.getIgnoreCase());
}
else if (qName.equals("RULES"))
{
if(tag.lastSetName == null)
tag.lastSetName = "MAIN";
rules = marker.getRuleSet(tag.lastSetName);
if(rules == null)
{
rules = new ParserRuleSet(modeName,tag.lastSetName);
marker.addRuleSet(rules);
}
rules.setIgnoreCase(tag.lastIgnoreCase);
rules.setHighlightDigits(tag.lastHighlightDigits);
if(tag.lastDigitRE != null)
{
try
{
rules.setDigitRegexp(Pattern.compile(tag.lastDigitRE,
tag.lastIgnoreCase
? Pattern.CASE_INSENSITIVE : 0));
}
catch(PatternSyntaxException e)
{
error("regexp",e);
}
}
if(tag.lastEscape != null)
rules.setEscapeRule(ParserRule.createEscapeRule(tag.lastEscape));
rules.setDefault(tag.lastDefaultID);
rules.setNoWordSep(tag.lastNoWordSep);
}
} //}}}
//{{{ endElement() method
public void endElement(String uri, String localName, String name)
{
TagDecl tag = popElement();
if (name.equals(tag.tagName))
{
if(tag.lastDelegateSet != null
&& ! tag.tagName.equals("IMPORT")
&& ! tag.lastDelegateSet.getModeName().equals(modeName))
{
Mode mode = ModeProvider.instance.getMode(tag.lastDelegateSet.getModeName());
if( ! reloadModes.contains(mode) )
{
reloadModes.add(mode);
}
}
//{{{ PROPERTY
if (tag.tagName.equals("PROPERTY"))
{
props.put(propName,propValue);
} //}}}
//{{{ PROPS
else if (tag.tagName.equals("PROPS"))
{
if(peekElement().tagName.equals("RULES"))
rules.setProperties(props);
else
modeProps = props;
props = new Hashtable<String, String>();
} //}}}
//{{{ RULES
else if (tag.tagName.equals("RULES"))
{
rules.setKeywords(keywords);
keywords = null;
rules = null;
} //}}}
//{{{ IMPORT
else if (tag.tagName.equals("IMPORT"))
{
// prevent lockups
if (!rules.equals(tag.lastDelegateSet))
{
rules.addRuleSet(tag.lastDelegateSet);
}
} //}}}
//{{{ TERMINATE
else if (tag.tagName.equals("TERMINATE"))
{
rules.setTerminateChar(tag.termChar);
} //}}}
//{{{ SEQ
else if (tag.tagName.equals("SEQ"))
{
if(tag.lastStart == null)
{
error("empty-tag","SEQ");
return;
}
rules.addRule(ParserRule.createSequenceRule(
tag.lastStartPosMatch,tag.lastStart.toString(),
tag.lastDelegateSet,tag.lastTokenID));
} //}}}
//{{{ SEQ_REGEXP
else if (tag.tagName.equals("SEQ_REGEXP"))
{
if(tag.lastStart == null)
{
error("empty-tag","SEQ_REGEXP");
return;
}
try
{
if (null != tag.lastHashChars)
{
rules.addRule(ParserRule.createRegexpSequenceRule(
tag.lastStartPosMatch,tag.lastHashChars.toCharArray(),
tag.lastStart.toString(),tag.lastDelegateSet,
tag.lastTokenID,findParent("RULES").lastIgnoreCase));
}
else
{
rules.addRule(ParserRule.createRegexpSequenceRule(
tag.lastHashChar,tag.lastStartPosMatch,
tag.lastStart.toString(),tag.lastDelegateSet,
tag.lastTokenID,findParent("RULES").lastIgnoreCase));
}
}
catch(PatternSyntaxException re)
{
error("regexp",re);
}
} //}}}
//{{{ SPAN
else if (tag.tagName.equals("SPAN"))
{
if(tag.lastStart == null)
{
error("empty-tag","BEGIN");
return;
}
if(tag.lastEnd == null)
{
error("empty-tag","END");
return;
}
rules.addRule(ParserRule
.createSpanRule(
tag.lastStartPosMatch,tag.lastStart.toString(),
tag.lastEndPosMatch,tag.lastEnd.toString(),
tag.lastDelegateSet,
tag.lastTokenID,tag.lastMatchType,
tag.lastNoLineBreak,
tag.lastNoWordBreak,
tag.lastEscape));
} //}}}
//{{{ SPAN_REGEXP
else if (tag.tagName.equals("SPAN_REGEXP"))
{
if(tag.lastStart == null)
{
error("empty-tag","BEGIN");
return;
}
if(tag.lastEnd == null)
{
error("empty-tag","END");
return;
}
try
{
if (null != tag.lastHashChars)
{
rules.addRule(ParserRule
.createRegexpSpanRule(
tag.lastStartPosMatch,tag.lastHashChars.toCharArray(),
tag.lastStart.toString(),
tag.lastEndPosMatch,tag.lastEnd.toString(),
tag.lastDelegateSet,
tag.lastTokenID,
tag.lastMatchType,
tag.lastNoLineBreak,
tag.lastNoWordBreak,
findParent("RULES").lastIgnoreCase,
tag.lastEscape,
tag.lastEndRegexp));
}
else
{
rules.addRule(ParserRule
.createRegexpSpanRule(
tag.lastHashChar,
tag.lastStartPosMatch,tag.lastStart.toString(),
tag.lastEndPosMatch,tag.lastEnd.toString(),
tag.lastDelegateSet,
tag.lastTokenID,
tag.lastMatchType,
tag.lastNoLineBreak,
tag.lastNoWordBreak,
findParent("RULES").lastIgnoreCase,
tag.lastEscape,
tag.lastEndRegexp));
}
}
catch(PatternSyntaxException re)
{
error("regexp",re);
}
} //}}}
//{{{ EOL_SPAN
else if (tag.tagName.equals("EOL_SPAN"))
{
if(tag.lastStart == null)
{
error("empty-tag","EOL_SPAN");
return;
}
rules.addRule(ParserRule.createEOLSpanRule(
tag.lastStartPosMatch,tag.lastStart.toString(),
tag.lastDelegateSet,tag.lastTokenID,
tag.lastMatchType));
} //}}}
//{{{ EOL_SPAN_REGEXP
else if (tag.tagName.equals("EOL_SPAN_REGEXP"))
{
if(tag.lastStart == null)
{
error("empty-tag","EOL_SPAN_REGEXP");
return;
}
try
{
if (null != tag.lastHashChars)
{
rules.addRule(ParserRule.createRegexpEOLSpanRule(
tag.lastStartPosMatch,tag.lastHashChars.toCharArray(),
tag.lastStart.toString(),tag.lastDelegateSet,
tag.lastTokenID,tag.lastMatchType,
findParent("RULES").lastIgnoreCase));
}
else
{
rules.addRule(ParserRule.createRegexpEOLSpanRule(
tag.lastHashChar,tag.lastStartPosMatch,
tag.lastStart.toString(),tag.lastDelegateSet,
tag.lastTokenID,tag.lastMatchType,
findParent("RULES").lastIgnoreCase));
}
}
catch(PatternSyntaxException re)
{
error("regexp",re);
}
} //}}}
//{{{ MARK_FOLLOWING
else if (tag.tagName.equals("MARK_FOLLOWING"))
{
if(tag.lastStart == null)
{
error("empty-tag","MARK_FOLLOWING");
return;
}
rules.addRule(ParserRule
.createMarkFollowingRule(
tag.lastStartPosMatch,tag.lastStart.toString(),
tag.lastTokenID,tag.lastMatchType));
} //}}}
//{{{ MARK_PREVIOUS
else if (tag.tagName.equals("MARK_PREVIOUS"))
{
if(tag.lastStart == null)
{
error("empty-tag","MARK_PREVIOUS");
return;
}
rules.addRule(ParserRule
.createMarkPreviousRule(
tag.lastStartPosMatch,tag.lastStart.toString(),
tag.lastTokenID,tag.lastMatchType));
} //}}}
//{{{ Keywords
else if (
!tag.tagName.equals("END")
&& !tag.tagName.equals("BEGIN")
&& !tag.tagName.equals("KEYWORDS")
&& !tag.tagName.equals("MODE"))
{
byte token = Token.stringToToken(tag.tagName);
if(token != -1)
{
if (tag.lastKeyword == null
|| tag.lastKeyword.length() == 0)
{
error("empty-keyword", null);
}
else
{
addKeyword(tag.lastKeyword.toString(),token);
}
}
} //}}}
}
else
{
// can't happen
throw new InternalError();
}
} //}}}
//{{{ startDocument() method
public void startDocument()
{
props = new Hashtable<String, String>();
pushElement(null, null);
reloadModes = new Vector<Mode>();
} //}}}
//{{{ endDocument() method
public void endDocument()
{
ParserRuleSet[] rulesets = marker.getRuleSets();
for(int i = 0; i < rulesets.length; i++)
{
rulesets[i].resolveImports();
}
for(Mode mode : reloadModes)
{
mode.setTokenMarker(null);
mode.loadIfNecessary();
}
} //}}}
//{{{ getTokenMarker() method
/**
* Returns the TokenMarker.
*
* @return a TokenMarker it cannot be null
*/
public TokenMarker getTokenMarker()
{
return marker;
} //}}}
//{{{ getModeProperties() method
public Hashtable<String, String> getModeProperties()
{
return modeProps;
} //}}}
//{{{ Protected members
//{{{ error() method
/**
* Reports an error.
* You must override this method so that the mode loader can do error
* reporting.
* @param msg The error type
* @param subst A <code>String</code> or a <code>Throwable</code>
* containing specific information
* @since jEdit 4.2pre1
*/
protected abstract void error(String msg, Object subst);
//}}}
//{{{ getTokenMarker() method
/**
* Returns the token marker for the given mode.
* You must override this method so that the mode loader can resolve
* delegate targets.
* @param mode The mode name
* @since jEdit 4.2pre1
*/
protected abstract TokenMarker getTokenMarker(String mode);
//}}}
//}}}
//{{{ Private members
//{{{ Instance variables
private String modeName;
/** The token marker cannot be null. */
private final TokenMarker marker;
private KeywordMap keywords;
/** this stack can contains null elements. */
private Stack<TagDecl> stateStack;
private String propName;
private String propValue;
private Hashtable<String, String> props;
private Hashtable<String, String> modeProps;
private ParserRuleSet rules;
/**
* A list of modes to be reloaded at the end, loaded through DELEGATEs
* @see http://sourceforge.net/tracker/index.php?func=detail&aid=1742250&group_id=588&atid=100588
*/
private Vector<Mode> reloadModes;
//}}}
//{{{ addKeyword() method
private void addKeyword(String k, byte id)
{
if (keywords == null) return;
keywords.add(k,id);
} //}}}
//{{{ pushElement() method
private TagDecl pushElement(String name, Attributes attrs)
{
if (name != null)
{
TagDecl tag = new TagDecl(name, attrs);
stateStack.push(tag);
return tag;
}
else
{
stateStack.push(null);
return null;
}
} //}}}
//{{{ peekElement() method
private TagDecl peekElement()
{
return stateStack.peek();
} //}}}
//{{{ popElement() method
private TagDecl popElement()
{
return stateStack.pop();
} //}}}
//{{{ findParent() method
/**
* Finds the first element whose tag matches 'tagName',
* searching backwards in the stack.
*/
private TagDecl findParent(String tagName)
{
for (int idx = stateStack.size() - 1; idx >= 0; idx--)
{
TagDecl tag = stateStack.get(idx);
if (tag.tagName.equals(tagName))
return tag;
}
return null;
} //}}}
//}}}
/**
* Hold info about what tag was read and what attributes were
* set in the XML file, to be kept by the handler in a stack
* (similar to the way tag names were kept in a stack before).
*/
private class TagDecl
{
public TagDecl(String tagName, Attributes attrs)
{
this.tagName = tagName;
String tmp;
propName = attrs.getValue("NAME");
propValue = attrs.getValue("VALUE");
tmp = attrs.getValue("TYPE");
if (tmp != null)
{
lastTokenID = Token.stringToToken(tmp);
if(lastTokenID == -1)
error("token-invalid",tmp);
}
lastMatchType = ParserRule.MATCH_TYPE_RULE;
// check for the deprecated "EXCLUDE_MATCH" and
// warn if found.
tmp = attrs.getValue("EXCLUDE_MATCH");
if (tmp != null)
{
Log.log(Log.WARNING, this, modeName + ": EXCLUDE_MATCH is deprecated");
if ("TRUE".equalsIgnoreCase(tmp))
{
lastMatchType = ParserRule.MATCH_TYPE_CONTEXT;
}
}
// override with the newer MATCH_TYPE if present
tmp = attrs.getValue("MATCH_TYPE");
if (tmp != null)
{
if ("CONTEXT".equals(tmp))
{
lastMatchType = ParserRule.MATCH_TYPE_CONTEXT;
}
else if ("RULE".equals(tmp))
{
lastMatchType = ParserRule.MATCH_TYPE_RULE;
}
else
{
lastMatchType = Token.stringToToken(tmp);
if(lastMatchType == -1)
error("token-invalid",tmp);
}
}
lastAtLineStart = "TRUE".equals(attrs.getValue("AT_LINE_START"));
lastAtWhitespaceEnd = "TRUE".equals(attrs.getValue("AT_WHITESPACE_END"));
lastAtWordStart = "TRUE".equals(attrs.getValue("AT_WORD_START"));
lastNoLineBreak = "TRUE".equals(attrs.getValue("NO_LINE_BREAK"));
lastNoWordBreak = "TRUE".equals(attrs.getValue("NO_WORD_BREAK"));
lastIgnoreCase = (attrs.getValue("IGNORE_CASE") == null ||
"TRUE".equals(attrs.getValue("IGNORE_CASE")));
lastHighlightDigits = "TRUE".equals(attrs.getValue("HIGHLIGHT_DIGITS"));
lastRegexp = "TRUE".equals(attrs.getValue("REGEXP"));
lastDigitRE = attrs.getValue("DIGIT_RE");
tmp = attrs.getValue("NO_WORD_SEP");
if (tmp != null)
lastNoWordSep = tmp;
tmp = attrs.getValue("AT_CHAR");
if (tmp != null)
{
try
{
termChar = Integer.parseInt(tmp);
}
catch (NumberFormatException e)
{
error("termchar-invalid",tmp);
termChar = -1;
}
}
lastEscape = attrs.getValue("ESCAPE");
lastSetName = attrs.getValue("SET");
tmp = attrs.getValue("DELEGATE");
if (tmp != null)
{
String delegateMode, delegateSetName;
int index = tmp.indexOf("::");
if(index != -1)
{
delegateMode = tmp.substring(0,index);
delegateSetName = tmp.substring(index + 2);
}
else
{
delegateMode = modeName;
delegateSetName = tmp;
}
TokenMarker delegateMarker = getTokenMarker(delegateMode);
if(delegateMarker == null)
error("delegate-invalid",tmp);
else
{
lastDelegateSet = delegateMarker
.getRuleSet(delegateSetName);
if(delegateMarker == marker
&& lastDelegateSet == null)
{
// stupid hack to handle referencing
// a rule set that is defined later!
lastDelegateSet = new ParserRuleSet(
delegateMode,
delegateSetName);
lastDelegateSet.setDefault(Token.INVALID);
marker.addRuleSet(lastDelegateSet);
}
else if(lastDelegateSet == null)
error("delegate-invalid",tmp);
}
}
tmp = attrs.getValue("DEFAULT");
if (tmp != null)
{
lastDefaultID = Token.stringToToken(tmp);
if(lastDefaultID == -1)
{
error("token-invalid",tmp);
lastDefaultID = Token.NULL;
}
}
lastHashChar = attrs.getValue("HASH_CHAR");
lastHashChars = attrs.getValue("HASH_CHARS");
if ((null != lastHashChar) && (null != lastHashChars))
{
error("hash-char-and-hash-chars-mutually-exclusive",null);
lastHashChars = null;
}
}
public void setText(char[] c, int off, int len)
{
if (tagName.equals("EOL_SPAN") ||
tagName.equals("EOL_SPAN_REGEXP") ||
tagName.equals("MARK_PREVIOUS") ||
tagName.equals("MARK_FOLLOWING") ||
tagName.equals("SEQ") ||
tagName.equals("SEQ_REGEXP") ||
tagName.equals("BEGIN")
)
{
TagDecl target = this;
if (tagName.equals("BEGIN"))
target = stateStack.get(stateStack.size() - 2);
if (target.lastStart == null)
{
target.lastStart = new StringBuffer();
target.lastStart.append(c, off, len);
target.lastStartPosMatch = ((target.lastAtLineStart ? ParserRule.AT_LINE_START : 0)
| (target.lastAtWhitespaceEnd ? ParserRule.AT_WHITESPACE_END : 0)
| (target.lastAtWordStart ? ParserRule.AT_WORD_START : 0));
target.lastAtLineStart = false;
target.lastAtWordStart = false;
target.lastAtWhitespaceEnd = false;
}
else
{
target.lastStart.append(c, off, len);
}
}
else if (tagName.equals("END"))
{
TagDecl target = stateStack.get(stateStack.size() - 2);
if (target.lastEnd == null)
{
target.lastEnd = new StringBuffer();
target.lastEnd.append(c, off, len);
target.lastEndPosMatch = ((this.lastAtLineStart ? ParserRule.AT_LINE_START : 0)
| (this.lastAtWhitespaceEnd ? ParserRule.AT_WHITESPACE_END : 0)
| (this.lastAtWordStart ? ParserRule.AT_WORD_START : 0));
target.lastEndRegexp = this.lastRegexp;
target.lastAtLineStart = false;
target.lastAtWordStart = false;
target.lastAtWhitespaceEnd = false;
}
else
{
target.lastEnd.append(c, off, len);
}
}
else
{
if (lastKeyword == null)
lastKeyword = new StringBuffer();
lastKeyword.append(c, off, len);
}
}
public String tagName;
public StringBuffer lastStart;
public StringBuffer lastEnd;
public StringBuffer lastKeyword;
public String lastSetName;
public String lastEscape;
public ParserRuleSet lastDelegateSet;
public String lastNoWordSep = "_";
public ParserRuleSet rules;
public byte lastDefaultID = Token.NULL;
public byte lastTokenID;
public byte lastMatchType;
public int termChar = -1;
public boolean lastNoLineBreak;
public boolean lastNoWordBreak;
public boolean lastIgnoreCase = true;
public boolean lastHighlightDigits;
public boolean lastAtLineStart;
public boolean lastAtWhitespaceEnd;
public boolean lastAtWordStart;
public boolean lastRegexp;
public boolean lastEndRegexp;
public int lastStartPosMatch;
public int lastEndPosMatch;
public String lastDigitRE;
public String lastHashChar;
public String lastHashChars;
}
}