All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.zkoss.zk.ui.select.impl.Parser Maven / Gradle / Ivy

There is a newer version: 10.0.0-jakarta
Show newest version
/**
 * 
 */
package org.zkoss.zk.ui.select.impl;

import java.util.ArrayList;
import java.util.List;

import org.zkoss.fsm.*;
import org.zkoss.fsm.StateCtx.TransitionListener;
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 _selectorSet = new ArrayList();
	private Selector _selector;
	
	private InSeqMachine _submachine;
	
	private StateMachine _machine = 
		new StateMachine(){
		
		protected void init() {
			
			getState(State.PRE_SELECTOR)
				.addReturningClasses(CharClass.WHITESPACE)
				.addTransition(CharClass.SELECTOR_LITERAL, State.IN_SELECTOR);
			
			setState(State.IN_SELECTOR,
					new MacroStateCtx(
							_submachine = new InSeqMachine()))
				.addReturningClasses(CharClass.SELECTOR_LITERAL)
				.addTransition(CharClass.WHITESPACE, State.PRE_COMBINATOR)
				.addTransition(CharClass.SELECTOR_SEPARATOR, State.PRE_SELECTOR, 
						new TransitionListener(){
					public void onTransit(Token input, CharClass inputClass) {
						flushCurrentSelector();
					}
				});
			
			getState(State.PRE_COMBINATOR)
				.addTransition(CharClass.COMBINATOR, State.POST_COMBINATOR, 
						new TransitionListener(){
					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(){
					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 parse(String source) {
		try {
			return parse(new Tokenizer().tokenize(source), source);
		} catch (StateMachineException e) {
			throw new ParseException("Illegal selector string: " + source);
		}
	}
	
	public List parse(List 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 {
	
	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 OPEN_BRACKET:
			return SubState.ATTR_PRE_NAME;
		default:
			return null;
		}
	}
	
	protected void init() {
		
		setState(SubState.PSDOCLS_PRE_PARAM, new StateCtx(){
			
			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(){
				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(){
				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(){
				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.OPEN_BRACKET, SubState.ATTR_PRE_NAME);
		
		// attribute cycle
		getState(SubState.MAIN)
			.addRoute(Type.OPEN_BRACKET, SubState.ATTR_PRE_NAME)
			.addRoute(Type.IDENTIFIER, SubState.ATTR_POST_NAME, 
					new TransitionListener(){
				public void onTransit(Token input, Type inputClass) {
					// set attribute name
					_seq.addAttribute(input.source(_source));
				}
			})
			.addRoutes(SubState.ATTR_PRE_VALUE, 
					new TransitionListener(){
				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(){
				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(){
				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(){
				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,
		
		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;
	}
	
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy