mirror of
https://github.com/crazy-max/diun.git
synced 2025-01-08 17:53:09 +00:00
164 lines
4.6 KiB
Go
164 lines
4.6 KiB
Go
package css
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"strings"
|
|
|
|
"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
|
|
NowSelectorText string
|
|
NowRuleType RuleType
|
|
CurrentRule *CSSRule
|
|
CurrentNestedRule *CSSRule
|
|
}
|
|
|
|
func resetContextStyleRule(context *parserContext) {
|
|
context.CurrentRule = nil
|
|
context.NowSelectorText = ""
|
|
context.NowRuleType = STYLE_RULE
|
|
context.State = STATE_NONE
|
|
}
|
|
|
|
func parseRule(context *parserContext, s *scanner.Scanner, css *CSSStyleSheet) {
|
|
context.CurrentRule = NewRule(context.NowRuleType)
|
|
context.NowSelectorText += parseSelector(s)
|
|
context.CurrentRule.Style.SelectorText = strings.TrimSpace(context.NowSelectorText)
|
|
context.CurrentRule.Style.Styles = parseBlock(s)
|
|
if context.CurrentNestedRule != nil {
|
|
context.CurrentNestedRule.Rules = append(context.CurrentNestedRule.Rules, context.CurrentRule)
|
|
} else {
|
|
css.CssRuleList = append(css.CssRuleList, context.CurrentRule)
|
|
}
|
|
}
|
|
|
|
// 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,
|
|
NowSelectorText: "",
|
|
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)
|
|
resetContextStyleRule(context)
|
|
case "@import":
|
|
// No validation
|
|
// https://developer.mozilla.org/en-US/docs/Web/CSS/@import
|
|
rule := parseImport(s)
|
|
if rule != nil {
|
|
css.CssRuleList = append(css.CssRuleList, rule)
|
|
}
|
|
resetContextStyleRule(context)
|
|
case "@charset":
|
|
// No validation
|
|
// https://developer.mozilla.org/en-US/docs/Web/CSS/@charset
|
|
rule := parseCharset(s)
|
|
if rule != nil {
|
|
css.CssRuleList = append(css.CssRuleList, rule)
|
|
}
|
|
resetContextStyleRule(context)
|
|
|
|
case "@page":
|
|
context.NowRuleType = PAGE_RULE
|
|
parseRule(context, s, css)
|
|
resetContextStyleRule(context)
|
|
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)
|
|
resetContextStyleRule(context)
|
|
default:
|
|
log.Println(fmt.Printf("Skip unsupported atrule: %s", token.Value))
|
|
skipRules(s)
|
|
resetContextStyleRule(context)
|
|
}
|
|
default:
|
|
if context.State == STATE_NONE {
|
|
if token.Value == "}" && context.CurrentNestedRule != nil {
|
|
// close media 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)
|
|
context.CurrentNestedRule.Style.SelectorText = strings.TrimSpace(token.Value + parseSelector(s))
|
|
resetContextStyleRule(context)
|
|
break
|
|
} else {
|
|
context.NowSelectorText += token.Value
|
|
parseRule(context, s, css)
|
|
resetContextStyleRule(context)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
return css
|
|
}
|