package org.squirrelframework.foundation.fsm.cssparser;
import java.util.List;
import org.squirrelframework.foundation.fsm.AnonymousAction;
import org.squirrelframework.foundation.fsm.Condition;
import org.squirrelframework.foundation.fsm.HistoryType;
import org.squirrelframework.foundation.fsm.StateMachineBuilder;
import org.squirrelframework.foundation.fsm.StateMachineBuilderFactory;
import org.squirrelframework.foundation.fsm.cssparser.SimpleCssParser.ParserContext;
import org.squirrelframework.foundation.fsm.cssparser.SimpleCssParser.ParserState;
import org.squirrelframework.foundation.fsm.impl.AbstractStateMachine;
import com.google.common.collect.Lists;
/**
* This is an example on how to use state machine to build a simple CSS parser. The state machine was defined in fluent API.
*
* @author Henry.He
*
*/
public class SimpleCssParser extends AbstractStateMachine<SimpleCssParser, ParserState, Character, ParserContext>{
enum ParserState {
RULE, SELECTOR, PROPERTY, PROPERTY_NAME, PROPERTY_VALUE, COMMENT
}
static class ParserContext {
private ParserContext() {}
Character currChar;
Character nextChar;
List<CssRule> rules;
}
private static final Character STAR = '*';
private static final Character SLASH = '/';
private static final Character BRACKET_BEG = '{';
private static final Character BRACKET_END = '}';
private static final Character COLON = ':';
private static final Character SEMI_COLON = ';';
private StringBuilder buffer;
private CssRule currRule;
private CssProperty currProperty;
private static final StateMachineBuilder<SimpleCssParser, ParserState, Character, ParserContext> builder;
static {
builder = StateMachineBuilderFactory.create(
SimpleCssParser.class, ParserState.class, Character.class, ParserContext.class);
builder.externalTransition().from(ParserState.RULE).to(ParserState.COMMENT).on(SLASH).when(
new Condition<ParserContext>() {
@Override
public boolean isSatisfied(ParserContext context) {
return context.nextChar!=null && context.nextChar.equals(STAR);
}
@Override
public String name() {
return "Cond1";
}
});
builder.externalTransition().from(ParserState.COMMENT).to(ParserState.RULE).on(STAR).when(
new Condition<ParserContext>() {
@Override
public boolean isSatisfied(ParserContext context) {
return context.nextChar!=null && context.nextChar.equals(SLASH);
}
@Override
public String name() {
return "Cond2";
}
});
builder.externalTransition().from(ParserState.SELECTOR).to(ParserState.PROPERTY).on(BRACKET_BEG);
builder.externalTransition().from(ParserState.PROPERTY_NAME).to(ParserState.PROPERTY_VALUE).on(COLON);
builder.externalTransition().from(ParserState.PROPERTY_VALUE).to(ParserState.PROPERTY_NAME).on(SEMI_COLON);
builder.externalTransition().from(ParserState.PROPERTY).to(ParserState.SELECTOR).on(BRACKET_END).perform(
new AnonymousAction<SimpleCssParser, ParserState, Character, ParserContext>() {
@Override
public void execute(ParserState from, ParserState to, Character event,
ParserContext context, SimpleCssParser stateMachine) {
stateMachine.setCurrRule(null);
stateMachine.setCurrProperty(null);
}
});
builder.defineSequentialStatesOn(ParserState.RULE, HistoryType.DEEP,
ParserState.SELECTOR, ParserState.PROPERTY);
builder.defineSequentialStatesOn(ParserState.PROPERTY, HistoryType.DEEP,
ParserState.PROPERTY_NAME, ParserState.PROPERTY_VALUE);
builder.onExit(ParserState.SELECTOR).perform(new AnonymousAction<SimpleCssParser, ParserState, Character, ParserContext>() {
@Override
public void execute(ParserState from, ParserState to, Character event,
ParserContext context, SimpleCssParser stateMachine) {
if(event.equals(SLASH)) return;
CssRule rule = stateMachine.getCurrRule();
if(rule==null) {
rule = new CssRule();
stateMachine.setCurrRule(rule);
context.rules.add(rule);
}
rule.setSelector(stateMachine.getBufferValue());
}
});
builder.onExit(ParserState.PROPERTY_NAME).perform(new AnonymousAction<SimpleCssParser, ParserState, Character, ParserContext>() {
@Override
public void execute(ParserState from, ParserState to, Character event,
ParserContext context, SimpleCssParser stateMachine) {
if(event.equals(COLON)) {
CssProperty newProperty = new CssProperty();
newProperty.setName(stateMachine.getBufferValue());
stateMachine.getCurrRule().addProperty(newProperty);
stateMachine.setCurrProperty(newProperty);
}
}
});
builder.onExit(ParserState.PROPERTY_VALUE).perform(new AnonymousAction<SimpleCssParser, ParserState, Character, ParserContext>() {
@Override
public void execute(ParserState from, ParserState to, Character event,
ParserContext context, SimpleCssParser stateMachine) {
if(stateMachine.getCurrProperty()!=null && !event.equals(SLASH))
stateMachine.getCurrProperty().setValue(stateMachine.getBufferValue());
}
});
}
public String getBufferValue() {
if(buffer==null) return "";
String value = buffer.toString().trim();
buffer = null;
return value;
}
public CssRule getCurrRule() {
return currRule;
}
public void setCurrRule(CssRule currRule) {
this.currRule = currRule;
}
public CssProperty getCurrProperty() {
return currProperty;
}
public void setCurrProperty(CssProperty currProperty) {
this.currProperty = currProperty;
}
List<CssRule> parse(final String css) {
List<CssRule> result = Lists.newArrayList();
if(css==null || css.trim().isEmpty())
return result;
for (int i = 0; i < css.length(); i++) {
Character c = css.charAt(i);
ParserContext context = new ParserContext();
context.rules = result;
context.currChar = c;
context.nextChar = (i<css.length()-1) ? css.charAt(i+1) : null;
boolean isComment = getCurrentState()==ParserState.COMMENT;
fire(c, context);
if( (!isComment && getCurrentState()==ParserState.COMMENT) ||
(isComment && getCurrentState()!=ParserState.COMMENT) ) {
i++;
}
}
return result;
}
@Override
public void afterTransitionDeclined(ParserState sourceState, Character event, ParserContext context) {
// record string values except comments
if(sourceState!=ParserState.COMMENT) {
if(buffer==null)
buffer=new StringBuilder();
buffer.append(event);
}
}
public static SimpleCssParser newParser() {
return builder.newStateMachine(ParserState.RULE);
}
}