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