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); } }