123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409 |
- package parser
- import (
- "errors"
- "fmt"
- "regexp"
- "strings"
- "github.com/gorilla/css/scanner"
- "github.com/aymerick/douceur/css"
- )
- const (
- importantSuffixRegexp = `(?i)\s*!important\s*$`
- )
- var (
- importantRegexp *regexp.Regexp
- )
- // Parser represents a CSS parser
- type Parser struct {
- scan *scanner.Scanner // Tokenizer
- // Tokens parsed but not consumed yet
- tokens []*scanner.Token
- // Rule embedding level
- embedLevel int
- }
- func init() {
- importantRegexp = regexp.MustCompile(importantSuffixRegexp)
- }
- // NewParser instanciates a new parser
- func NewParser(txt string) *Parser {
- return &Parser{
- scan: scanner.New(txt),
- }
- }
- // Parse parses a whole stylesheet
- func Parse(text string) (*css.Stylesheet, error) {
- result, err := NewParser(text).ParseStylesheet()
- if err != nil {
- return nil, err
- }
- return result, nil
- }
- // ParseDeclarations parses CSS declarations
- func ParseDeclarations(text string) ([]*css.Declaration, error) {
- result, err := NewParser(text).ParseDeclarations()
- if err != nil {
- return nil, err
- }
- return result, nil
- }
- // ParseStylesheet parses a stylesheet
- func (parser *Parser) ParseStylesheet() (*css.Stylesheet, error) {
- result := css.NewStylesheet()
- // Parse BOM
- if _, err := parser.parseBOM(); err != nil {
- return result, err
- }
- // Parse list of rules
- rules, err := parser.ParseRules()
- if err != nil {
- return result, err
- }
- result.Rules = rules
- return result, nil
- }
- // ParseRules parses a list of rules
- func (parser *Parser) ParseRules() ([]*css.Rule, error) {
- result := []*css.Rule{}
- inBlock := false
- if parser.tokenChar("{") {
- // parsing a block of rules
- inBlock = true
- parser.embedLevel++
- parser.shiftToken()
- }
- for parser.tokenParsable() {
- if parser.tokenIgnorable() {
- parser.shiftToken()
- } else if parser.tokenChar("}") {
- if !inBlock {
- errMsg := fmt.Sprintf("Unexpected } character: %s", parser.nextToken().String())
- return result, errors.New(errMsg)
- }
- parser.shiftToken()
- parser.embedLevel--
- // finished
- break
- } else {
- rule, err := parser.ParseRule()
- if err != nil {
- return result, err
- }
- rule.EmbedLevel = parser.embedLevel
- result = append(result, rule)
- }
- }
- return result, parser.err()
- }
- // ParseRule parses a rule
- func (parser *Parser) ParseRule() (*css.Rule, error) {
- if parser.tokenAtKeyword() {
- return parser.parseAtRule()
- }
- return parser.parseQualifiedRule()
- }
- // ParseDeclarations parses a list of declarations
- func (parser *Parser) ParseDeclarations() ([]*css.Declaration, error) {
- result := []*css.Declaration{}
- if parser.tokenChar("{") {
- parser.shiftToken()
- }
- for parser.tokenParsable() {
- if parser.tokenIgnorable() {
- parser.shiftToken()
- } else if parser.tokenChar("}") {
- // end of block
- parser.shiftToken()
- break
- } else {
- declaration, err := parser.ParseDeclaration()
- if err != nil {
- return result, err
- }
- result = append(result, declaration)
- }
- }
- return result, parser.err()
- }
- // ParseDeclaration parses a declaration
- func (parser *Parser) ParseDeclaration() (*css.Declaration, error) {
- result := css.NewDeclaration()
- curValue := ""
- for parser.tokenParsable() {
- if parser.tokenChar(":") {
- result.Property = strings.TrimSpace(curValue)
- curValue = ""
- parser.shiftToken()
- } else if parser.tokenChar(";") || parser.tokenChar("}") {
- if result.Property == "" {
- errMsg := fmt.Sprintf("Unexpected ; character: %s", parser.nextToken().String())
- return result, errors.New(errMsg)
- }
- if importantRegexp.MatchString(curValue) {
- result.Important = true
- curValue = importantRegexp.ReplaceAllString(curValue, "")
- }
- result.Value = strings.TrimSpace(curValue)
- if parser.tokenChar(";") {
- parser.shiftToken()
- }
- // finished
- break
- } else {
- token := parser.shiftToken()
- curValue += token.Value
- }
- }
- // log.Printf("[parsed] Declaration: %s", result.String())
- return result, parser.err()
- }
- // Parse an At Rule
- func (parser *Parser) parseAtRule() (*css.Rule, error) {
- // parse rule name (eg: "@import")
- token := parser.shiftToken()
- result := css.NewRule(css.AtRule)
- result.Name = token.Value
- for parser.tokenParsable() {
- if parser.tokenChar(";") {
- parser.shiftToken()
- // finished
- break
- } else if parser.tokenChar("{") {
- if result.EmbedsRules() {
- // parse rules block
- rules, err := parser.ParseRules()
- if err != nil {
- return result, err
- }
- result.Rules = rules
- } else {
- // parse declarations block
- declarations, err := parser.ParseDeclarations()
- if err != nil {
- return result, err
- }
- result.Declarations = declarations
- }
- // finished
- break
- } else {
- // parse prelude
- prelude, err := parser.parsePrelude()
- if err != nil {
- return result, err
- }
- result.Prelude = prelude
- }
- }
- // log.Printf("[parsed] Rule: %s", result.String())
- return result, parser.err()
- }
- // Parse a Qualified Rule
- func (parser *Parser) parseQualifiedRule() (*css.Rule, error) {
- result := css.NewRule(css.QualifiedRule)
- for parser.tokenParsable() {
- if parser.tokenChar("{") {
- if result.Prelude == "" {
- errMsg := fmt.Sprintf("Unexpected { character: %s", parser.nextToken().String())
- return result, errors.New(errMsg)
- }
- // parse declarations block
- declarations, err := parser.ParseDeclarations()
- if err != nil {
- return result, err
- }
- result.Declarations = declarations
- // finished
- break
- } else {
- // parse prelude
- prelude, err := parser.parsePrelude()
- if err != nil {
- return result, err
- }
- result.Prelude = prelude
- }
- }
- result.Selectors = strings.Split(result.Prelude, ",")
- for i, sel := range result.Selectors {
- result.Selectors[i] = strings.TrimSpace(sel)
- }
- // log.Printf("[parsed] Rule: %s", result.String())
- return result, parser.err()
- }
- // Parse Rule prelude
- func (parser *Parser) parsePrelude() (string, error) {
- result := ""
- for parser.tokenParsable() && !parser.tokenEndOfPrelude() {
- token := parser.shiftToken()
- result += token.Value
- }
- result = strings.TrimSpace(result)
- // log.Printf("[parsed] prelude: %s", result)
- return result, parser.err()
- }
- // Parse BOM
- func (parser *Parser) parseBOM() (bool, error) {
- if parser.nextToken().Type == scanner.TokenBOM {
- parser.shiftToken()
- return true, nil
- }
- return false, parser.err()
- }
- // Returns next token without removing it from tokens buffer
- func (parser *Parser) nextToken() *scanner.Token {
- if len(parser.tokens) == 0 {
- // fetch next token
- nextToken := parser.scan.Next()
- // log.Printf("[token] %s => %v", nextToken.Type.String(), nextToken.Value)
- // queue it
- parser.tokens = append(parser.tokens, nextToken)
- }
- return parser.tokens[0]
- }
- // Returns next token and remove it from the tokens buffer
- func (parser *Parser) shiftToken() *scanner.Token {
- var result *scanner.Token
- result, parser.tokens = parser.tokens[0], parser.tokens[1:]
- return result
- }
- // Returns tokenizer error, or nil if no error
- func (parser *Parser) err() error {
- if parser.tokenError() {
- token := parser.nextToken()
- return fmt.Errorf("Tokenizer error: %s", token.String())
- }
- return nil
- }
- // Returns true if next token is Error
- func (parser *Parser) tokenError() bool {
- return parser.nextToken().Type == scanner.TokenError
- }
- // Returns true if next token is EOF
- func (parser *Parser) tokenEOF() bool {
- return parser.nextToken().Type == scanner.TokenEOF
- }
- // Returns true if next token is a whitespace
- func (parser *Parser) tokenWS() bool {
- return parser.nextToken().Type == scanner.TokenS
- }
- // Returns true if next token is a comment
- func (parser *Parser) tokenComment() bool {
- return parser.nextToken().Type == scanner.TokenComment
- }
- // Returns true if next token is a CDO or a CDC
- func (parser *Parser) tokenCDOorCDC() bool {
- switch parser.nextToken().Type {
- case scanner.TokenCDO, scanner.TokenCDC:
- return true
- default:
- return false
- }
- }
- // Returns true if next token is ignorable
- func (parser *Parser) tokenIgnorable() bool {
- return parser.tokenWS() || parser.tokenComment() || parser.tokenCDOorCDC()
- }
- // Returns true if next token is parsable
- func (parser *Parser) tokenParsable() bool {
- return !parser.tokenEOF() && !parser.tokenError()
- }
- // Returns true if next token is an At Rule keyword
- func (parser *Parser) tokenAtKeyword() bool {
- return parser.nextToken().Type == scanner.TokenAtKeyword
- }
- // Returns true if next token is given character
- func (parser *Parser) tokenChar(value string) bool {
- token := parser.nextToken()
- return (token.Type == scanner.TokenChar) && (token.Value == value)
- }
- // Returns true if next token marks the end of a prelude
- func (parser *Parser) tokenEndOfPrelude() bool {
- return parser.tokenChar(";") || parser.tokenChar("{")
- }
|