123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158 |
- package css
- import (
- "log"
- "github.com/gorilla/css/scanner"
- )
- /*
- stylesheet : [ CDO | CDC | S | statement ]*;
- statement : ruleset | at-rule;
- at-rule : ATKEYWORD S* any* [ block | ';' S* ];
- block : '{' S* [ any | block | ATKEYWORD S* | ';' S* ]* '}' S*;
- ruleset : selector? '{' S* declaration? [ ';' S* declaration? ]* '}' S*;
- selector : any+;
- declaration : property S* ':' S* value;
- property : IDENT;
- value : [ any | block | ATKEYWORD S* ]+;
- any : [ IDENT | NUMBER | PERCENTAGE | DIMENSION | STRING
- | DELIM | URI | HASH | UNICODE-RANGE | INCLUDES
- | DASHMATCH | ':' | FUNCTION S* [any|unused]* ')'
- | '(' S* [any|unused]* ')' | '[' S* [any|unused]* ']'
- ] S*;
- unused : block | ATKEYWORD S* | ';' S* | CDO S* | CDC S*;
- */
- type State int
- const (
- STATE_NONE State = iota
- STATE_SELECTOR
- STATE_PROPERTY
- STATE_VALUE
- )
- type parserContext struct {
- State State
- NowSelector []*scanner.Token
- NowRuleType RuleType
- CurrentNestedRule *CSSRule
- }
- func (context *parserContext) resetContextStyleRule() {
- context.NowSelector = make([]*scanner.Token, 0)
- context.NowRuleType = STYLE_RULE
- context.State = STATE_NONE
- }
- func parseRule(context *parserContext, s *scanner.Scanner, css *CSSStyleSheet) {
- rule := NewRule(context.NowRuleType)
- selector := append(context.NowSelector, parseSelector(s)...)
- rule.Style.Selector = &CSSValue{Tokens: selector}
- rule.Style.Styles = parseBlock(s)
- if context.CurrentNestedRule != nil {
- context.CurrentNestedRule.Rules = append(context.CurrentNestedRule.Rules, rule)
- } else {
- css.CssRuleList = append(css.CssRuleList, rule)
- }
- context.resetContextStyleRule()
- }
- // Parse takes a string of valid css rules, stylesheet,
- // and parses it. Be aware this function has poor error handling
- // so you should have valid syntax in your css
- func Parse(csstext string) *CSSStyleSheet {
- context := &parserContext{
- State: STATE_NONE,
- NowSelector: make([]*scanner.Token, 0),
- NowRuleType: STYLE_RULE,
- CurrentNestedRule: nil,
- }
- css := &CSSStyleSheet{}
- css.CssRuleList = make([]*CSSRule, 0)
- s := scanner.New(csstext)
- for {
- token := s.Next()
- if token.Type == scanner.TokenEOF || token.Type == scanner.TokenError {
- break
- }
- switch token.Type {
- case scanner.TokenCDO:
- break
- case scanner.TokenCDC:
- break
- case scanner.TokenComment:
- break
- case scanner.TokenS:
- break
- case scanner.TokenAtKeyword:
- switch token.Value {
- case "@media":
- context.NowRuleType = MEDIA_RULE
- case "@font-face":
- // Parse as normal rule, would be nice to parse according to syntax
- // https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face
- context.NowRuleType = FONT_FACE_RULE
- parseRule(context, s, css)
- case "@import":
- // No validation
- // https://developer.mozilla.org/en-US/docs/Web/CSS/@import
- rule := parseAtNoBody(s, IMPORT_RULE)
- if rule != nil {
- css.CssRuleList = append(css.CssRuleList, rule)
- }
- context.resetContextStyleRule()
- case "@charset":
- // No validation
- // https://developer.mozilla.org/en-US/docs/Web/CSS/@charset
- rule := parseAtNoBody(s, CHARSET_RULE)
- if rule != nil {
- css.CssRuleList = append(css.CssRuleList, rule)
- }
- context.resetContextStyleRule()
- case "@page":
- context.NowRuleType = PAGE_RULE
- parseRule(context, s, css)
- case "@keyframes":
- context.NowRuleType = KEYFRAMES_RULE
- case "@-webkit-keyframes":
- context.NowRuleType = WEBKIT_KEYFRAMES_RULE
- case "@counter-style":
- context.NowRuleType = COUNTER_STYLE_RULE
- parseRule(context, s, css)
- default:
- log.Printf("Skip unsupported atrule: %s", token.Value)
- skipRules(s)
- context.resetContextStyleRule()
- }
- default:
- if context.State == STATE_NONE {
- if token.Value == "}" && context.CurrentNestedRule != nil {
- // close media/keyframe/… rule
- css.CssRuleList = append(css.CssRuleList, context.CurrentNestedRule)
- context.CurrentNestedRule = nil
- break
- }
- }
- if context.NowRuleType == MEDIA_RULE || context.NowRuleType == KEYFRAMES_RULE || context.NowRuleType == WEBKIT_KEYFRAMES_RULE {
- context.CurrentNestedRule = NewRule(context.NowRuleType)
- sel := append([]*scanner.Token{token}, parseSelector(s)...)
- context.CurrentNestedRule.Style.Selector = &CSSValue{Tokens: sel}
- context.resetContextStyleRule()
- break
- } else {
- context.NowSelector = append(context.NowSelector, token)
- parseRule(context, s, css)
- break
- }
- }
- }
- return css
- }
|