/**
*
*/
package org.zkoss.zk.ui.select.impl;
import java.util.LinkedList;
import java.util.List;
import org.zkoss.fsm.MacroStateCtx;
import org.zkoss.fsm.StateCtx;
import org.zkoss.fsm.StateCtx.TransitionListener;
import org.zkoss.fsm.StateMachine;
import org.zkoss.fsm.StateMachine.StateMachineException;
import org.zkoss.zk.ui.select.impl.Attribute.Operator;
import org.zkoss.zk.ui.select.impl.InSeqMachine.SubState;
import org.zkoss.zk.ui.select.impl.Selector.Combinator;
import org.zkoss.zk.ui.select.impl.Token.Type;
/**
* A parser that parses selector string and generates selector objects.
* @since 6.0.0
* @author simonpai
*/
public class Parser {
private String _source;
private List<Selector> _selectorSet = new LinkedList<Selector>(); //to preserve order
private Selector _selector;
private InSeqMachine _submachine;
private StateMachine<State, CharClass, Token> _machine = new StateMachine<State, CharClass, Token>() {
protected void init() {
getState(State.PRE_SELECTOR).addReturningClasses(CharClass.WHITESPACE)
.addTransition(CharClass.SELECTOR_LITERAL, State.IN_SELECTOR);
setState(State.IN_SELECTOR,
new MacroStateCtx<State, CharClass, Token, SubState, Type>(_submachine = new InSeqMachine()))
.addReturningClasses(CharClass.SELECTOR_LITERAL)
.addTransition(CharClass.WHITESPACE, State.PRE_COMBINATOR)
.addTransition(CharClass.SELECTOR_SEPARATOR, State.PRE_SELECTOR,
new TransitionListener<Token, CharClass>() {
public void onTransit(Token input, CharClass inputClass) {
flushCurrentSelector();
}
});
getState(State.PRE_COMBINATOR).addTransition(CharClass.COMBINATOR, State.POST_COMBINATOR,
new TransitionListener<Token, CharClass>() {
public void onTransit(Token input, CharClass inputClass) {
_selector.attachCombinator(getCombinator(input));
}
}).addTransition(CharClass.SELECTOR_LITERAL, State.IN_SELECTOR).addTransition(CharClass.SELECTOR_SEPARATOR,
State.PRE_SELECTOR, new TransitionListener<Token, CharClass>() {
public void onTransit(Token input, CharClass inputClass) {
flushCurrentSelector();
}
});
getState(State.POST_COMBINATOR).addTransition(CharClass.WHITESPACE, State.PRE_SELECTOR);
}
protected CharClass getClass(Token input) {
switch (input.getType()) {
case WHITESPACE:
return CharClass.WHITESPACE;
case CBN_CHILD:
case CBN_ADJACENT_SIBLING:
case CBN_GENERAL_SIBLING:
return CharClass.COMBINATOR;
case SELECTOR_SEPARATOR:
return CharClass.SELECTOR_SEPARATOR;
default:
return CharClass.SELECTOR_LITERAL;
}
}
protected State getLandingState(Token input, CharClass inputClass) {
switch (inputClass) {
case WHITESPACE:
return State.PRE_SELECTOR;
case SELECTOR_LITERAL:
return State.IN_SELECTOR;
}
return null;
}
protected void onReset() {
_submachine.setSelector(_selector);
_submachine.setSource(_source);
}
protected void onStop(boolean endOfInput) {
// TODO: check state?
}
private void flushCurrentSelector() {
// flush current selector
_selectorSet.add(_selector = new Selector(_selectorSet.size()));
_submachine.setSelector(_selector);
}
};
public List<Selector> parse(String source) {
try {
return parse(new Tokenizer().tokenize(source), source);
} catch (StateMachineException e) {
throw new ParseException("Illegal selector string: " + source);
}
}
public List<Selector> parse(List<Token> tokens, String source) {
_source = source;
_selectorSet.clear();
_selectorSet.add(_selector = new Selector(0));
_machine.start(tokens.iterator());
return _selectorSet;
}
public void setDebugMode(boolean mode) {
_machine.setDebugMode(mode);
_submachine.setDebugMode(mode);
}
// state, char class //
private enum State {
PRE_SELECTOR, IN_SELECTOR, PRE_COMBINATOR, POST_COMBINATOR;
}
private enum CharClass {
SELECTOR_LITERAL, WHITESPACE, COMBINATOR, SELECTOR_SEPARATOR;
}
// helper //
private Combinator getCombinator(Token token) {
switch (token.getType()) {
case CBN_CHILD:
return Combinator.CHILD;
case CBN_ADJACENT_SIBLING:
return Combinator.ADJACENT_SIBLING;
case CBN_GENERAL_SIBLING:
return Combinator.GENERAL_SIBLING;
}
throw new IllegalStateException();
}
}
/*package*/ class InSeqMachine extends StateMachine<InSeqMachine.SubState, Token.Type, Token> {
private Selector _selector;
private String _source;
private SimpleSelectorSequence _seq;
public InSeqMachine setSource(String source) {
_source = source;
return this;
}
public InSeqMachine setSelector(Selector selector) {
_selector = selector;
return this;
}
protected Type getClass(Token input) {
return input.getType();
}
protected SubState getLandingState(Token input, Type inputClass) {
switch (inputClass) {
case IDENTIFIER:
case UNIVERSAL:
return SubState.MAIN;
case NTN_ID:
return SubState.ID_PRE_VALUE;
case NTN_CLASS:
return SubState.CLASS_PRE_VALUE;
case NTN_PSDOCLS:
return SubState.PSDOCLS_PRE_NAME;
case NTN_PSDOELEM:
return SubState.PSDOELEM_PRE_NAME;
case OPEN_BRACKET:
return SubState.ATTR_PRE_NAME;
default:
return null;
}
}
protected void init() {
setState(SubState.PSDOCLS_PRE_PARAM, new StateCtx<SubState, Type, Token>() {
protected void onLeave(Token input, Type inputClass, SubState dest) {
// flush pseudo class function parameter
_seq.attachPseudoClassParameter(input.source(_source));
}
});
// ID cycle
getState(SubState.MAIN).addRoute(Type.NTN_ID, SubState.ID_PRE_VALUE).addRoute(Type.IDENTIFIER, SubState.MAIN,
new TransitionListener<Token, Type>() {
public void onTransit(Token input, Type inputClass) {
// flush ID value
if (_seq.getId() != null)
throw new ParseException("Illegal selector syntax: cannot have more than 1"
+ " ID, failed at index " + input.getBeginIndex());
_seq.setId(input.source(_source));
}
});
// class cycle
getState(SubState.MAIN).addRoute(Type.NTN_CLASS, SubState.CLASS_PRE_VALUE).addRoute(Type.IDENTIFIER,
SubState.MAIN, new TransitionListener<Token, Type>() {
public void onTransit(Token input, Type inputClass) {
// flush class value
_seq.addClass(input.source(_source));
}
});
// TODO: consider quotes
// pseudo class cycle
getState(SubState.MAIN).addRoute(Type.NTN_PSDOCLS, SubState.PSDOCLS_PRE_NAME)
.addRoute(Type.IDENTIFIER, SubState.PSDOCLS_POST_NAME, new TransitionListener<Token, Type>() {
public void onTransit(Token input, Type inputClass) {
// flush pseudo class function name
_seq.addPseudoClass(input.source(_source));
}
}).addRoute(Type.OPEN_PAREN, SubState.PSDOCLS_PRE_PARAM).addReturningClasses(Type.MINOR_WHITESPACE)
.addRoute(Type.IDENTIFIER, SubState.PSDOCLS_POST_PARAM).addReturningClasses(Type.MINOR_WHITESPACE)
.addTransition(Type.PARAM_SEPARATOR, SubState.PSDOCLS_PRE_PARAM)
.addRoute(Type.CLOSE_PAREN, SubState.MAIN);
// pseudo class with no parameter
getState(SubState.PSDOCLS_POST_NAME).addTransition(Type.NTN_ID, SubState.ID_PRE_VALUE)
.addTransition(Type.NTN_CLASS, SubState.CLASS_PRE_VALUE)
.addTransition(Type.NTN_PSDOCLS, SubState.PSDOCLS_PRE_NAME)
.addTransition(Type.NTN_PSDOELEM, SubState.PSDOELEM_PRE_NAME)
.addTransition(Type.OPEN_BRACKET, SubState.ATTR_PRE_NAME);
// ZK-2944: pseudo element TODO very similar behavior as class
getState(SubState.MAIN).addRoute(Type.NTN_PSDOELEM, SubState.PSDOELEM_PRE_NAME).addRoute(Type.IDENTIFIER,
SubState.MAIN, new TransitionListener<Token, Type>() {
public void onTransit(Token input, Type inputClass) {
// flush pseudo class function name
_seq.addPseudoElement(input.source(_source));
}
});
// attribute cycle
getState(SubState.MAIN).addRoute(Type.OPEN_BRACKET, SubState.ATTR_PRE_NAME)
.addRoute(Type.IDENTIFIER, SubState.ATTR_POST_NAME, new TransitionListener<Token, Type>() {
public void onTransit(Token input, Type inputClass) {
// set attribute name
_seq.addAttribute(input.source(_source));
}
}).addRoutes(SubState.ATTR_PRE_VALUE, new TransitionListener<Token, Type>() {
public void onTransit(Token input, Type inputClass) {
// set attribute operator
_seq.attachAttributeOperator(getOperator(inputClass));
}
}, Type.OP_EQUAL, Type.OP_BEGIN_WITH, Type.OP_END_WITH, Type.OP_CONTAIN)
.addRoute(Type.IDENTIFIER, SubState.ATTR_POST_VALUE, new TransitionListener<Token, Type>() {
public void onTransit(Token input, Type inputClass) {
// set attribute value
_seq.attachAttributeValue(input.source(_source));
}
}).addRoute(Type.CLOSE_BRACKET, SubState.MAIN);
// attribute value with double quote
getState(SubState.ATTR_PRE_VALUE).addRoute(Type.DOUBLE_QUOTE, SubState.ATTR_PRE_VALUE_INDQT)
.addRoute(Type.IDENTIFIER, SubState.ATTR_POST_VALUE_INDQT, new TransitionListener<Token, Type>() {
public void onTransit(Token input, Type inputClass) {
// set attribute value
_seq.attachAttributeValue(input.source(_source), true);
}
}).addRoute(Type.DOUBLE_QUOTE, SubState.ATTR_POST_VALUE);
// attribute value with single quote
getState(SubState.ATTR_PRE_VALUE).addRoute(Type.SINGLE_QUOTE, SubState.ATTR_PRE_VALUE_INSQT)
.addRoute(Type.IDENTIFIER, SubState.ATTR_POST_VALUE_INSQT, new TransitionListener<Token, Type>() {
public void onTransit(Token input, Type inputClass) {
// set attribute value
_seq.attachAttributeValue(input.source(_source), true);
}
}).addRoute(Type.SINGLE_QUOTE, SubState.ATTR_POST_VALUE);
}
protected void onStart(Token input, Type inputClass, SubState landing) {
_selector.add(_seq = new SimpleSelectorSequence());
if (inputClass == Type.IDENTIFIER)
_seq.setType(input.source(_source));
}
protected void onStop(boolean endOfInput) {
switch (_current) {
case MAIN:
case PSDOCLS_POST_NAME:
case PSDOCLS_POST_PARAM:
case ATTR_POST_VALUE:
break;
default:
if (endOfInput)
throw new ParseException("Illegal selector syntax: unexpected end of selector string.");
}
}
protected void onDebug(String message) {
super.onDebug("\t" + message);
}
// helper //
private Operator getOperator(Type inputClass) {
switch (inputClass) {
case OP_EQUAL:
return Operator.EQUAL;
case OP_BEGIN_WITH:
return Operator.BEGIN_WITH;
case OP_END_WITH:
return Operator.END_WITH;
case OP_CONTAIN:
return Operator.CONTAIN;
default:
return null;
}
}
// state //
public enum SubState {
MAIN, ID_PRE_VALUE, CLASS_PRE_VALUE,
PSDOCLS_PRE_NAME, PSDOCLS_POST_NAME, PSDOCLS_PRE_PARAM, PSDOCLS_POST_PARAM,
PSDOELEM_PRE_NAME,
ATTR_PRE_NAME, ATTR_POST_NAME, ATTR_PRE_VALUE, ATTR_POST_VALUE, ATTR_PRE_VALUE_INSQT, ATTR_POST_VALUE_INSQT, ATTR_PRE_VALUE_INDQT, ATTR_POST_VALUE_INDQT;
}
}