upgrade to most recent bluemonday (#11007)
* upgrade to most recent bluemonday * make vendor * update tests for bluemonday * update tests for bluemonday * update tests for bluemondaymj
parent
4c54477bb5
commit
d00ebf445b
@ -0,0 +1,22 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2015 Aymerick JEHANNE
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
@ -0,0 +1,60 @@
|
|||||||
|
package css
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// Declaration represents a parsed style property
|
||||||
|
type Declaration struct {
|
||||||
|
Property string
|
||||||
|
Value string
|
||||||
|
Important bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDeclaration instanciates a new Declaration
|
||||||
|
func NewDeclaration() *Declaration {
|
||||||
|
return &Declaration{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns string representation of the Declaration
|
||||||
|
func (decl *Declaration) String() string {
|
||||||
|
return decl.StringWithImportant(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// StringWithImportant returns string representation with optional !important part
|
||||||
|
func (decl *Declaration) StringWithImportant(option bool) string {
|
||||||
|
result := fmt.Sprintf("%s: %s", decl.Property, decl.Value)
|
||||||
|
|
||||||
|
if option && decl.Important {
|
||||||
|
result += " !important"
|
||||||
|
}
|
||||||
|
|
||||||
|
result += ";"
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equal returns true if both Declarations are equals
|
||||||
|
func (decl *Declaration) Equal(other *Declaration) bool {
|
||||||
|
return (decl.Property == other.Property) && (decl.Value == other.Value) && (decl.Important == other.Important)
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// DeclarationsByProperty
|
||||||
|
//
|
||||||
|
|
||||||
|
// DeclarationsByProperty represents sortable style declarations
|
||||||
|
type DeclarationsByProperty []*Declaration
|
||||||
|
|
||||||
|
// Implements sort.Interface
|
||||||
|
func (declarations DeclarationsByProperty) Len() int {
|
||||||
|
return len(declarations)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implements sort.Interface
|
||||||
|
func (declarations DeclarationsByProperty) Swap(i, j int) {
|
||||||
|
declarations[i], declarations[j] = declarations[j], declarations[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implements sort.Interface
|
||||||
|
func (declarations DeclarationsByProperty) Less(i, j int) bool {
|
||||||
|
return declarations[i].Property < declarations[j].Property
|
||||||
|
}
|
@ -0,0 +1,230 @@
|
|||||||
|
package css
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
indentSpace = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
// RuleKind represents a Rule kind
|
||||||
|
type RuleKind int
|
||||||
|
|
||||||
|
// Rule kinds
|
||||||
|
const (
|
||||||
|
QualifiedRule RuleKind = iota
|
||||||
|
AtRule
|
||||||
|
)
|
||||||
|
|
||||||
|
// At Rules than have Rules inside their block instead of Declarations
|
||||||
|
var atRulesWithRulesBlock = []string{
|
||||||
|
"@document", "@font-feature-values", "@keyframes", "@media", "@supports",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rule represents a parsed CSS rule
|
||||||
|
type Rule struct {
|
||||||
|
Kind RuleKind
|
||||||
|
|
||||||
|
// At Rule name (eg: "@media")
|
||||||
|
Name string
|
||||||
|
|
||||||
|
// Raw prelude
|
||||||
|
Prelude string
|
||||||
|
|
||||||
|
// Qualified Rule selectors parsed from prelude
|
||||||
|
Selectors []string
|
||||||
|
|
||||||
|
// Style properties
|
||||||
|
Declarations []*Declaration
|
||||||
|
|
||||||
|
// At Rule embedded rules
|
||||||
|
Rules []*Rule
|
||||||
|
|
||||||
|
// Current rule embedding level
|
||||||
|
EmbedLevel int
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRule instanciates a new Rule
|
||||||
|
func NewRule(kind RuleKind) *Rule {
|
||||||
|
return &Rule{
|
||||||
|
Kind: kind,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns string representation of rule kind
|
||||||
|
func (kind RuleKind) String() string {
|
||||||
|
switch kind {
|
||||||
|
case QualifiedRule:
|
||||||
|
return "Qualified Rule"
|
||||||
|
case AtRule:
|
||||||
|
return "At Rule"
|
||||||
|
default:
|
||||||
|
return "WAT"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// EmbedsRules returns true if this rule embeds another rules
|
||||||
|
func (rule *Rule) EmbedsRules() bool {
|
||||||
|
if rule.Kind == AtRule {
|
||||||
|
for _, atRuleName := range atRulesWithRulesBlock {
|
||||||
|
if rule.Name == atRuleName {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equal returns true if both rules are equals
|
||||||
|
func (rule *Rule) Equal(other *Rule) bool {
|
||||||
|
if (rule.Kind != other.Kind) ||
|
||||||
|
(rule.Prelude != other.Prelude) ||
|
||||||
|
(rule.Name != other.Name) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (len(rule.Selectors) != len(other.Selectors)) ||
|
||||||
|
(len(rule.Declarations) != len(other.Declarations)) ||
|
||||||
|
(len(rule.Rules) != len(other.Rules)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, sel := range rule.Selectors {
|
||||||
|
if sel != other.Selectors[i] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, decl := range rule.Declarations {
|
||||||
|
if !decl.Equal(other.Declarations[i]) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, rule := range rule.Rules {
|
||||||
|
if !rule.Equal(other.Rules[i]) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Diff returns a string representation of rules differences
|
||||||
|
func (rule *Rule) Diff(other *Rule) []string {
|
||||||
|
result := []string{}
|
||||||
|
|
||||||
|
if rule.Kind != other.Kind {
|
||||||
|
result = append(result, fmt.Sprintf("Kind: %s | %s", rule.Kind.String(), other.Kind.String()))
|
||||||
|
}
|
||||||
|
|
||||||
|
if rule.Prelude != other.Prelude {
|
||||||
|
result = append(result, fmt.Sprintf("Prelude: \"%s\" | \"%s\"", rule.Prelude, other.Prelude))
|
||||||
|
}
|
||||||
|
|
||||||
|
if rule.Name != other.Name {
|
||||||
|
result = append(result, fmt.Sprintf("Name: \"%s\" | \"%s\"", rule.Name, other.Name))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(rule.Selectors) != len(other.Selectors) {
|
||||||
|
result = append(result, fmt.Sprintf("Selectors: %v | %v", strings.Join(rule.Selectors, ", "), strings.Join(other.Selectors, ", ")))
|
||||||
|
} else {
|
||||||
|
for i, sel := range rule.Selectors {
|
||||||
|
if sel != other.Selectors[i] {
|
||||||
|
result = append(result, fmt.Sprintf("Selector: \"%s\" | \"%s\"", sel, other.Selectors[i]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(rule.Declarations) != len(other.Declarations) {
|
||||||
|
result = append(result, fmt.Sprintf("Declarations Nb: %d | %d", len(rule.Declarations), len(other.Declarations)))
|
||||||
|
} else {
|
||||||
|
for i, decl := range rule.Declarations {
|
||||||
|
if !decl.Equal(other.Declarations[i]) {
|
||||||
|
result = append(result, fmt.Sprintf("Declaration: \"%s\" | \"%s\"", decl.String(), other.Declarations[i].String()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(rule.Rules) != len(other.Rules) {
|
||||||
|
result = append(result, fmt.Sprintf("Rules Nb: %d | %d", len(rule.Rules), len(other.Rules)))
|
||||||
|
} else {
|
||||||
|
|
||||||
|
for i, rule := range rule.Rules {
|
||||||
|
if !rule.Equal(other.Rules[i]) {
|
||||||
|
result = append(result, fmt.Sprintf("Rule: \"%s\" | \"%s\"", rule.String(), other.Rules[i].String()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the string representation of a rule
|
||||||
|
func (rule *Rule) String() string {
|
||||||
|
result := ""
|
||||||
|
|
||||||
|
if rule.Kind == QualifiedRule {
|
||||||
|
for i, sel := range rule.Selectors {
|
||||||
|
if i != 0 {
|
||||||
|
result += ", "
|
||||||
|
}
|
||||||
|
result += sel
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// AtRule
|
||||||
|
result += fmt.Sprintf("%s", rule.Name)
|
||||||
|
|
||||||
|
if rule.Prelude != "" {
|
||||||
|
if result != "" {
|
||||||
|
result += " "
|
||||||
|
}
|
||||||
|
result += fmt.Sprintf("%s", rule.Prelude)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (len(rule.Declarations) == 0) && (len(rule.Rules) == 0) {
|
||||||
|
result += ";"
|
||||||
|
} else {
|
||||||
|
result += " {\n"
|
||||||
|
|
||||||
|
if rule.EmbedsRules() {
|
||||||
|
for _, subRule := range rule.Rules {
|
||||||
|
result += fmt.Sprintf("%s%s\n", rule.indent(), subRule.String())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for _, decl := range rule.Declarations {
|
||||||
|
result += fmt.Sprintf("%s%s\n", rule.indent(), decl.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result += fmt.Sprintf("%s}", rule.indentEndBlock())
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns identation spaces for declarations and rules
|
||||||
|
func (rule *Rule) indent() string {
|
||||||
|
result := ""
|
||||||
|
|
||||||
|
for i := 0; i < ((rule.EmbedLevel + 1) * indentSpace); i++ {
|
||||||
|
result += " "
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns identation spaces for end of block character
|
||||||
|
func (rule *Rule) indentEndBlock() string {
|
||||||
|
result := ""
|
||||||
|
|
||||||
|
for i := 0; i < (rule.EmbedLevel * indentSpace); i++ {
|
||||||
|
result += " "
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
package css
|
||||||
|
|
||||||
|
// Stylesheet represents a parsed stylesheet
|
||||||
|
type Stylesheet struct {
|
||||||
|
Rules []*Rule
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewStylesheet instanciate a new Stylesheet
|
||||||
|
func NewStylesheet() *Stylesheet {
|
||||||
|
return &Stylesheet{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns string representation of the Stylesheet
|
||||||
|
func (sheet *Stylesheet) String() string {
|
||||||
|
result := ""
|
||||||
|
|
||||||
|
for _, rule := range sheet.Rules {
|
||||||
|
if result != "" {
|
||||||
|
result += "\n"
|
||||||
|
}
|
||||||
|
result += rule.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2015 Aymerick JEHANNE
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
@ -0,0 +1,409 @@
|
|||||||
|
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("{")
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
Copyright (c) 2013, Gorilla web toolkit
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without modification,
|
||||||
|
are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
Redistributions of source code must retain the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
Redistributions in binary form must reproduce the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer in the documentation and/or
|
||||||
|
other materials provided with the distribution.
|
||||||
|
|
||||||
|
Neither the name of the {organization} nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||||
|
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
@ -0,0 +1,33 @@
|
|||||||
|
// Copyright 2012 The Gorilla Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
/*
|
||||||
|
Package gorilla/css/scanner generates tokens for a CSS3 input.
|
||||||
|
|
||||||
|
It follows the CSS3 specification located at:
|
||||||
|
|
||||||
|
http://www.w3.org/TR/css3-syntax/
|
||||||
|
|
||||||
|
To use it, create a new scanner for a given CSS string and call Next() until
|
||||||
|
the token returned has type TokenEOF or TokenError:
|
||||||
|
|
||||||
|
s := scanner.New(myCSS)
|
||||||
|
for {
|
||||||
|
token := s.Next()
|
||||||
|
if token.Type == scanner.TokenEOF || token.Type == scanner.TokenError {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// Do something with the token...
|
||||||
|
}
|
||||||
|
|
||||||
|
Following the CSS3 specification, an error can only occur when the scanner
|
||||||
|
finds an unclosed quote or unclosed comment. In these cases the text becomes
|
||||||
|
"untokenizable". Everything else is tokenizable and it is up to a parser
|
||||||
|
to make sense of the token stream (or ignore nonsensical token sequences).
|
||||||
|
|
||||||
|
Note: the scanner doesn't perform lexical analysis or, in other words, it
|
||||||
|
doesn't care about the token context. It is intended to be used by a
|
||||||
|
lexer or parser.
|
||||||
|
*/
|
||||||
|
package scanner
|
@ -0,0 +1,356 @@
|
|||||||
|
// Copyright 2012 The Gorilla Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package scanner
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
// tokenType identifies the type of lexical tokens.
|
||||||
|
type tokenType int
|
||||||
|
|
||||||
|
// String returns a string representation of the token type.
|
||||||
|
func (t tokenType) String() string {
|
||||||
|
return tokenNames[t]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Token represents a token and the corresponding string.
|
||||||
|
type Token struct {
|
||||||
|
Type tokenType
|
||||||
|
Value string
|
||||||
|
Line int
|
||||||
|
Column int
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns a string representation of the token.
|
||||||
|
func (t *Token) String() string {
|
||||||
|
if len(t.Value) > 10 {
|
||||||
|
return fmt.Sprintf("%s (line: %d, column: %d): %.10q...",
|
||||||
|
t.Type, t.Line, t.Column, t.Value)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s (line: %d, column: %d): %q",
|
||||||
|
t.Type, t.Line, t.Column, t.Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// All tokens -----------------------------------------------------------------
|
||||||
|
|
||||||
|
// The complete list of tokens in CSS3.
|
||||||
|
const (
|
||||||
|
// Scanner flags.
|
||||||
|
TokenError tokenType = iota
|
||||||
|
TokenEOF
|
||||||
|
// From now on, only tokens from the CSS specification.
|
||||||
|
TokenIdent
|
||||||
|
TokenAtKeyword
|
||||||
|
TokenString
|
||||||
|
TokenHash
|
||||||
|
TokenNumber
|
||||||
|
TokenPercentage
|
||||||
|
TokenDimension
|
||||||
|
TokenURI
|
||||||
|
TokenUnicodeRange
|
||||||
|
TokenCDO
|
||||||
|
TokenCDC
|
||||||
|
TokenS
|
||||||
|
TokenComment
|
||||||
|
TokenFunction
|
||||||
|
TokenIncludes
|
||||||
|
TokenDashMatch
|
||||||
|
TokenPrefixMatch
|
||||||
|
TokenSuffixMatch
|
||||||
|
TokenSubstringMatch
|
||||||
|
TokenChar
|
||||||
|
TokenBOM
|
||||||
|
)
|
||||||
|
|
||||||
|
// tokenNames maps tokenType's to their names. Used for conversion to string.
|
||||||
|
var tokenNames = map[tokenType]string{
|
||||||
|
TokenError: "error",
|
||||||
|
TokenEOF: "EOF",
|
||||||
|
TokenIdent: "IDENT",
|
||||||
|
TokenAtKeyword: "ATKEYWORD",
|
||||||
|
TokenString: "STRING",
|
||||||
|
TokenHash: "HASH",
|
||||||
|
TokenNumber: "NUMBER",
|
||||||
|
TokenPercentage: "PERCENTAGE",
|
||||||
|
TokenDimension: "DIMENSION",
|
||||||
|
TokenURI: "URI",
|
||||||
|
TokenUnicodeRange: "UNICODE-RANGE",
|
||||||
|
TokenCDO: "CDO",
|
||||||
|
TokenCDC: "CDC",
|
||||||
|
TokenS: "S",
|
||||||
|
TokenComment: "COMMENT",
|
||||||
|
TokenFunction: "FUNCTION",
|
||||||
|
TokenIncludes: "INCLUDES",
|
||||||
|
TokenDashMatch: "DASHMATCH",
|
||||||
|
TokenPrefixMatch: "PREFIXMATCH",
|
||||||
|
TokenSuffixMatch: "SUFFIXMATCH",
|
||||||
|
TokenSubstringMatch: "SUBSTRINGMATCH",
|
||||||
|
TokenChar: "CHAR",
|
||||||
|
TokenBOM: "BOM",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Macros and productions -----------------------------------------------------
|
||||||
|
// http://www.w3.org/TR/css3-syntax/#tokenization
|
||||||
|
|
||||||
|
var macroRegexp = regexp.MustCompile(`\{[a-z]+\}`)
|
||||||
|
|
||||||
|
// macros maps macro names to patterns to be expanded.
|
||||||
|
var macros = map[string]string{
|
||||||
|
// must be escaped: `\.+*?()|[]{}^$`
|
||||||
|
"ident": `-?{nmstart}{nmchar}*`,
|
||||||
|
"name": `{nmchar}+`,
|
||||||
|
"nmstart": `[a-zA-Z_]|{nonascii}|{escape}`,
|
||||||
|
"nonascii": "[\u0080-\uD7FF\uE000-\uFFFD\U00010000-\U0010FFFF]",
|
||||||
|
"unicode": `\\[0-9a-fA-F]{1,6}{wc}?`,
|
||||||
|
"escape": "{unicode}|\\\\[\u0020-\u007E\u0080-\uD7FF\uE000-\uFFFD\U00010000-\U0010FFFF]",
|
||||||
|
"nmchar": `[a-zA-Z0-9_-]|{nonascii}|{escape}`,
|
||||||
|
"num": `[0-9]*\.[0-9]+|[0-9]+`,
|
||||||
|
"string": `"(?:{stringchar}|')*"|'(?:{stringchar}|")*'`,
|
||||||
|
"stringchar": `{urlchar}|[ ]|\\{nl}`,
|
||||||
|
"nl": `[\n\r\f]|\r\n`,
|
||||||
|
"w": `{wc}*`,
|
||||||
|
"wc": `[\t\n\f\r ]`,
|
||||||
|
|
||||||
|
// urlchar should accept [(ascii characters minus those that need escaping)|{nonascii}|{escape}]
|
||||||
|
// ASCII characters range = `[\u0020-\u007e]`
|
||||||
|
// Skip space \u0020 = `[\u0021-\u007e]`
|
||||||
|
// Skip quotation mark \0022 = `[\u0021\u0023-\u007e]`
|
||||||
|
// Skip apostrophe \u0027 = `[\u0021\u0023-\u0026\u0028-\u007e]`
|
||||||
|
// Skip reverse solidus \u005c = `[\u0021\u0023-\u0026\u0028-\u005b\u005d\u007e]`
|
||||||
|
// Finally, the left square bracket (\u005b) and right (\u005d) needs escaping themselves
|
||||||
|
"urlchar": "[\u0021\u0023-\u0026\u0028-\\\u005b\\\u005d-\u007E]|{nonascii}|{escape}",
|
||||||
|
}
|
||||||
|
|
||||||
|
// productions maps the list of tokens to patterns to be expanded.
|
||||||
|
var productions = map[tokenType]string{
|
||||||
|
// Unused regexps (matched using other methods) are commented out.
|
||||||
|
TokenIdent: `{ident}`,
|
||||||
|
TokenAtKeyword: `@{ident}`,
|
||||||
|
TokenString: `{string}`,
|
||||||
|
TokenHash: `#{name}`,
|
||||||
|
TokenNumber: `{num}`,
|
||||||
|
TokenPercentage: `{num}%`,
|
||||||
|
TokenDimension: `{num}{ident}`,
|
||||||
|
TokenURI: `url\({w}(?:{string}|{urlchar}*?){w}\)`,
|
||||||
|
TokenUnicodeRange: `U\+[0-9A-F\?]{1,6}(?:-[0-9A-F]{1,6})?`,
|
||||||
|
//TokenCDO: `<!--`,
|
||||||
|
TokenCDC: `-->`,
|
||||||
|
TokenS: `{wc}+`,
|
||||||
|
TokenComment: `/\*[^\*]*[\*]+(?:[^/][^\*]*[\*]+)*/`,
|
||||||
|
TokenFunction: `{ident}\(`,
|
||||||
|
//TokenIncludes: `~=`,
|
||||||
|
//TokenDashMatch: `\|=`,
|
||||||
|
//TokenPrefixMatch: `\^=`,
|
||||||
|
//TokenSuffixMatch: `\$=`,
|
||||||
|
//TokenSubstringMatch: `\*=`,
|
||||||
|
//TokenChar: `[^"']`,
|
||||||
|
//TokenBOM: "\uFEFF",
|
||||||
|
}
|
||||||
|
|
||||||
|
// matchers maps the list of tokens to compiled regular expressions.
|
||||||
|
//
|
||||||
|
// The map is filled on init() using the macros and productions defined in
|
||||||
|
// the CSS specification.
|
||||||
|
var matchers = map[tokenType]*regexp.Regexp{}
|
||||||
|
|
||||||
|
// matchOrder is the order to test regexps when first-char shortcuts
|
||||||
|
// can't be used.
|
||||||
|
var matchOrder = []tokenType{
|
||||||
|
TokenURI,
|
||||||
|
TokenFunction,
|
||||||
|
TokenUnicodeRange,
|
||||||
|
TokenIdent,
|
||||||
|
TokenDimension,
|
||||||
|
TokenPercentage,
|
||||||
|
TokenNumber,
|
||||||
|
TokenCDC,
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// replace macros and compile regexps for productions.
|
||||||
|
replaceMacro := func(s string) string {
|
||||||
|
return "(?:" + macros[s[1:len(s)-1]] + ")"
|
||||||
|
}
|
||||||
|
for t, s := range productions {
|
||||||
|
for macroRegexp.MatchString(s) {
|
||||||
|
s = macroRegexp.ReplaceAllStringFunc(s, replaceMacro)
|
||||||
|
}
|
||||||
|
matchers[t] = regexp.MustCompile("^(?:" + s + ")")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scanner --------------------------------------------------------------------
|
||||||
|
|
||||||
|
// New returns a new CSS scanner for the given input.
|
||||||
|
func New(input string) *Scanner {
|
||||||
|
// Normalize newlines.
|
||||||
|
input = strings.Replace(input, "\r\n", "\n", -1)
|
||||||
|
return &Scanner{
|
||||||
|
input: input,
|
||||||
|
row: 1,
|
||||||
|
col: 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scanner scans an input and emits tokens following the CSS3 specification.
|
||||||
|
type Scanner struct {
|
||||||
|
input string
|
||||||
|
pos int
|
||||||
|
row int
|
||||||
|
col int
|
||||||
|
err *Token
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next returns the next token from the input.
|
||||||
|
//
|
||||||
|
// At the end of the input the token type is TokenEOF.
|
||||||
|
//
|
||||||
|
// If the input can't be tokenized the token type is TokenError. This occurs
|
||||||
|
// in case of unclosed quotation marks or comments.
|
||||||
|
func (s *Scanner) Next() *Token {
|
||||||
|
if s.err != nil {
|
||||||
|
return s.err
|
||||||
|
}
|
||||||
|
if s.pos >= len(s.input) {
|
||||||
|
s.err = &Token{TokenEOF, "", s.row, s.col}
|
||||||
|
return s.err
|
||||||
|
}
|
||||||
|
if s.pos == 0 {
|
||||||
|
// Test BOM only once, at the beginning of the file.
|
||||||
|
if strings.HasPrefix(s.input, "\uFEFF") {
|
||||||
|
return s.emitSimple(TokenBOM, "\uFEFF")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// There's a lot we can guess based on the first byte so we'll take a
|
||||||
|
// shortcut before testing multiple regexps.
|
||||||
|
input := s.input[s.pos:]
|
||||||
|
switch input[0] {
|
||||||
|
case '\t', '\n', '\f', '\r', ' ':
|
||||||
|
// Whitespace.
|
||||||
|
return s.emitToken(TokenS, matchers[TokenS].FindString(input))
|
||||||
|
case '.':
|
||||||
|
// Dot is too common to not have a quick check.
|
||||||
|
// We'll test if this is a Char; if it is followed by a number it is a
|
||||||
|
// dimension/percentage/number, and this will be matched later.
|
||||||
|
if len(input) > 1 && !unicode.IsDigit(rune(input[1])) {
|
||||||
|
return s.emitSimple(TokenChar, ".")
|
||||||
|
}
|
||||||
|
case '#':
|
||||||
|
// Another common one: Hash or Char.
|
||||||
|
if match := matchers[TokenHash].FindString(input); match != "" {
|
||||||
|
return s.emitToken(TokenHash, match)
|
||||||
|
}
|
||||||
|
return s.emitSimple(TokenChar, "#")
|
||||||
|
case '@':
|
||||||
|
// Another common one: AtKeyword or Char.
|
||||||
|
if match := matchers[TokenAtKeyword].FindString(input); match != "" {
|
||||||
|
return s.emitSimple(TokenAtKeyword, match)
|
||||||
|
}
|
||||||
|
return s.emitSimple(TokenChar, "@")
|
||||||
|
case ':', ',', ';', '%', '&', '+', '=', '>', '(', ')', '[', ']', '{', '}':
|
||||||
|
// More common chars.
|
||||||
|
return s.emitSimple(TokenChar, string(input[0]))
|
||||||
|
case '"', '\'':
|
||||||
|
// String or error.
|
||||||
|
match := matchers[TokenString].FindString(input)
|
||||||
|
if match != "" {
|
||||||
|
return s.emitToken(TokenString, match)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.err = &Token{TokenError, "unclosed quotation mark", s.row, s.col}
|
||||||
|
return s.err
|
||||||
|
case '/':
|
||||||
|
// Comment, error or Char.
|
||||||
|
if len(input) > 1 && input[1] == '*' {
|
||||||
|
match := matchers[TokenComment].FindString(input)
|
||||||
|
if match != "" {
|
||||||
|
return s.emitToken(TokenComment, match)
|
||||||
|
} else {
|
||||||
|
s.err = &Token{TokenError, "unclosed comment", s.row, s.col}
|
||||||
|
return s.err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s.emitSimple(TokenChar, "/")
|
||||||
|
case '~':
|
||||||
|
// Includes or Char.
|
||||||
|
return s.emitPrefixOrChar(TokenIncludes, "~=")
|
||||||
|
case '|':
|
||||||
|
// DashMatch or Char.
|
||||||
|
return s.emitPrefixOrChar(TokenDashMatch, "|=")
|
||||||
|
case '^':
|
||||||
|
// PrefixMatch or Char.
|
||||||
|
return s.emitPrefixOrChar(TokenPrefixMatch, "^=")
|
||||||
|
case '$':
|
||||||
|
// SuffixMatch or Char.
|
||||||
|
return s.emitPrefixOrChar(TokenSuffixMatch, "$=")
|
||||||
|
case '*':
|
||||||
|
// SubstringMatch or Char.
|
||||||
|
return s.emitPrefixOrChar(TokenSubstringMatch, "*=")
|
||||||
|
case '<':
|
||||||
|
// CDO or Char.
|
||||||
|
return s.emitPrefixOrChar(TokenCDO, "<!--")
|
||||||
|
}
|
||||||
|
// Test all regexps, in order.
|
||||||
|
for _, token := range matchOrder {
|
||||||
|
if match := matchers[token].FindString(input); match != "" {
|
||||||
|
return s.emitToken(token, match)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// We already handled unclosed quotation marks and comments,
|
||||||
|
// so this can only be a Char.
|
||||||
|
r, width := utf8.DecodeRuneInString(input)
|
||||||
|
token := &Token{TokenChar, string(r), s.row, s.col}
|
||||||
|
s.col += width
|
||||||
|
s.pos += width
|
||||||
|
return token
|
||||||
|
}
|
||||||
|
|
||||||
|
// updatePosition updates input coordinates based on the consumed text.
|
||||||
|
func (s *Scanner) updatePosition(text string) {
|
||||||
|
width := utf8.RuneCountInString(text)
|
||||||
|
lines := strings.Count(text, "\n")
|
||||||
|
s.row += lines
|
||||||
|
if lines == 0 {
|
||||||
|
s.col += width
|
||||||
|
} else {
|
||||||
|
s.col = utf8.RuneCountInString(text[strings.LastIndex(text, "\n"):])
|
||||||
|
}
|
||||||
|
s.pos += len(text) // while col is a rune index, pos is a byte index
|
||||||
|
}
|
||||||
|
|
||||||
|
// emitToken returns a Token for the string v and updates the scanner position.
|
||||||
|
func (s *Scanner) emitToken(t tokenType, v string) *Token {
|
||||||
|
token := &Token{t, v, s.row, s.col}
|
||||||
|
s.updatePosition(v)
|
||||||
|
return token
|
||||||
|
}
|
||||||
|
|
||||||
|
// emitSimple returns a Token for the string v and updates the scanner
|
||||||
|
// position in a simplified manner.
|
||||||
|
//
|
||||||
|
// The string is known to have only ASCII characters and to not have a newline.
|
||||||
|
func (s *Scanner) emitSimple(t tokenType, v string) *Token {
|
||||||
|
token := &Token{t, v, s.row, s.col}
|
||||||
|
s.col += len(v)
|
||||||
|
s.pos += len(v)
|
||||||
|
return token
|
||||||
|
}
|
||||||
|
|
||||||
|
// emitPrefixOrChar returns a Token for type t if the current position
|
||||||
|
// matches the given prefix. Otherwise it returns a Char token using the
|
||||||
|
// first character from the prefix.
|
||||||
|
//
|
||||||
|
// The prefix is known to have only ASCII characters and to not have a newline.
|
||||||
|
func (s *Scanner) emitPrefixOrChar(t tokenType, prefix string) *Token {
|
||||||
|
if strings.HasPrefix(s.input[s.pos:], prefix) {
|
||||||
|
return s.emitSimple(t, prefix)
|
||||||
|
}
|
||||||
|
return s.emitSimple(TokenChar, string(prefix[0]))
|
||||||
|
}
|
@ -1,22 +1,15 @@
|
|||||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
# Binaries for programs and plugins
|
||||||
*.o
|
*.exe
|
||||||
*.a
|
*.exe~
|
||||||
|
*.dll
|
||||||
*.so
|
*.so
|
||||||
|
*.dylib
|
||||||
|
|
||||||
# Folders
|
# Test binary, built with `go test -c`
|
||||||
_obj
|
*.test
|
||||||
_test
|
|
||||||
|
|
||||||
# Architecture specific extensions/prefixes
|
|
||||||
*.[568vq]
|
|
||||||
[568vq].out
|
|
||||||
|
|
||||||
*.cgo1.go
|
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||||
*.cgo2.c
|
*.out
|
||||||
_cgo_defun.c
|
|
||||||
_cgo_gotypes.go
|
|
||||||
_cgo_export.*
|
|
||||||
|
|
||||||
_testmain.go
|
# goland idea folder
|
||||||
|
*.idea
|
||||||
*.exe
|
|
@ -1,18 +1,22 @@
|
|||||||
language: go
|
language: go
|
||||||
go:
|
go:
|
||||||
- 1.1
|
- 1.2.x
|
||||||
- 1.2
|
- 1.3.x
|
||||||
- 1.3
|
- 1.4.x
|
||||||
- 1.4
|
- 1.5.x
|
||||||
- 1.5
|
- 1.6.x
|
||||||
- 1.6
|
- 1.7.x
|
||||||
- 1.7
|
- 1.8.x
|
||||||
|
- 1.9.x
|
||||||
|
- 1.10.x
|
||||||
|
- 1.11.x
|
||||||
|
- 1.12.x
|
||||||
- tip
|
- tip
|
||||||
matrix:
|
matrix:
|
||||||
allow_failures:
|
allow_failures:
|
||||||
- go: tip
|
- go: tip
|
||||||
fast_finish: true
|
fast_finish: true
|
||||||
install:
|
install:
|
||||||
- go get golang.org/x/net/html
|
- go get .
|
||||||
script:
|
script:
|
||||||
- go test -v ./...
|
- go test -v ./...
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
|
|
||||||
1. John Graham-Cumming http://jgc.org/
|
1. John Graham-Cumming http://jgc.org/
|
||||||
|
1. Mohammad Gufran https://github.com/Gufran
|
||||||
|
1. Steven Gutzwiller https://github.com/StevenGutzwiller
|
||||||
|
1. Andrew Krasichkov @buglloc https://github.com/buglloc
|
||||||
1. Mike Samuel mikesamuel@gmail.com
|
1. Mike Samuel mikesamuel@gmail.com
|
||||||
1. Dmitri Shuralyov shurcooL@gmail.com
|
1. Dmitri Shuralyov shurcooL@gmail.com
|
||||||
1. https://github.com/opennota
|
1. https://github.com/opennota
|
||||||
1. https://github.com/Gufran
|
|
@ -0,0 +1,10 @@
|
|||||||
|
module github.com/microcosm-cc/bluemonday
|
||||||
|
|
||||||
|
go 1.9
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/aymerick/douceur v0.2.0 // indirect
|
||||||
|
github.com/chris-ramon/douceur v0.2.0
|
||||||
|
github.com/gorilla/css v1.0.0 // indirect
|
||||||
|
golang.org/x/net v0.0.0-20181220203305-927f97764cc3
|
||||||
|
)
|
@ -0,0 +1,8 @@
|
|||||||
|
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
|
||||||
|
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
|
||||||
|
github.com/chris-ramon/douceur v0.2.0 h1:IDMEdxlEUUBYBKE4z/mJnFyVXox+MjuEVDJNN27glkU=
|
||||||
|
github.com/chris-ramon/douceur v0.2.0/go.mod h1:wDW5xjJdeoMm1mRt4sD4c/LbF/mWdEpRXQKjTR8nIBE=
|
||||||
|
github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
|
||||||
|
github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
|
||||||
|
golang.org/x/net v0.0.0-20181220203305-927f97764cc3 h1:eH6Eip3UpmR+yM/qI9Ijluzb1bNv/cAU/n+6l8tRSis=
|
||||||
|
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,17 @@
|
|||||||
|
// Copyright 2020 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Constants that were deprecated or moved to enums in the FreeBSD headers. Keep
|
||||||
|
// them here for backwards compatibility.
|
||||||
|
|
||||||
|
package unix
|
||||||
|
|
||||||
|
const (
|
||||||
|
DLT_HHDLC = 0x79
|
||||||
|
IPV6_MIN_MEMBERSHIPS = 0x1f
|
||||||
|
IP_MAX_SOURCE_FILTER = 0x400
|
||||||
|
IP_MIN_MEMBERSHIPS = 0x1f
|
||||||
|
RT_CACHING_CONTEXT = 0x1
|
||||||
|
RT_NORTREF = 0x2
|
||||||
|
)
|
@ -0,0 +1,57 @@
|
|||||||
|
// Copyright 2009 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// illumos system calls not present on Solaris.
|
||||||
|
|
||||||
|
// +build amd64,illumos
|
||||||
|
|
||||||
|
package unix
|
||||||
|
|
||||||
|
import "unsafe"
|
||||||
|
|
||||||
|
func bytes2iovec(bs [][]byte) []Iovec {
|
||||||
|
iovecs := make([]Iovec, len(bs))
|
||||||
|
for i, b := range bs {
|
||||||
|
iovecs[i].SetLen(len(b))
|
||||||
|
if len(b) > 0 {
|
||||||
|
// somehow Iovec.Base on illumos is (*int8), not (*byte)
|
||||||
|
iovecs[i].Base = (*int8)(unsafe.Pointer(&b[0]))
|
||||||
|
} else {
|
||||||
|
iovecs[i].Base = (*int8)(unsafe.Pointer(&_zero))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return iovecs
|
||||||
|
}
|
||||||
|
|
||||||
|
//sys readv(fd int, iovs []Iovec) (n int, err error)
|
||||||
|
|
||||||
|
func Readv(fd int, iovs [][]byte) (n int, err error) {
|
||||||
|
iovecs := bytes2iovec(iovs)
|
||||||
|
n, err = readv(fd, iovecs)
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
//sys preadv(fd int, iovs []Iovec, off int64) (n int, err error)
|
||||||
|
|
||||||
|
func Preadv(fd int, iovs [][]byte, off int64) (n int, err error) {
|
||||||
|
iovecs := bytes2iovec(iovs)
|
||||||
|
n, err = preadv(fd, iovecs, off)
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
//sys writev(fd int, iovs []Iovec) (n int, err error)
|
||||||
|
|
||||||
|
func Writev(fd int, iovs [][]byte) (n int, err error) {
|
||||||
|
iovecs := bytes2iovec(iovs)
|
||||||
|
n, err = writev(fd, iovecs)
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
//sys pwritev(fd int, iovs []Iovec, off int64) (n int, err error)
|
||||||
|
|
||||||
|
func Pwritev(fd int, iovs [][]byte, off int64) (n int, err error) {
|
||||||
|
iovecs := bytes2iovec(iovs)
|
||||||
|
n, err = pwritev(fd, iovecs, off)
|
||||||
|
return n, err
|
||||||
|
}
|
@ -0,0 +1,87 @@
|
|||||||
|
// go run mksyscall_solaris.go -illumos -tags illumos,amd64 syscall_illumos.go
|
||||||
|
// Code generated by the command above; see README.md. DO NOT EDIT.
|
||||||
|
|
||||||
|
// +build illumos,amd64
|
||||||
|
|
||||||
|
package unix
|
||||||
|
|
||||||
|
import (
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:cgo_import_dynamic libc_readv readv "libc.so"
|
||||||
|
//go:cgo_import_dynamic libc_preadv preadv "libc.so"
|
||||||
|
//go:cgo_import_dynamic libc_writev writev "libc.so"
|
||||||
|
//go:cgo_import_dynamic libc_pwritev pwritev "libc.so"
|
||||||
|
|
||||||
|
//go:linkname procreadv libc_readv
|
||||||
|
//go:linkname procpreadv libc_preadv
|
||||||
|
//go:linkname procwritev libc_writev
|
||||||
|
//go:linkname procpwritev libc_pwritev
|
||||||
|
|
||||||
|
var (
|
||||||
|
procreadv,
|
||||||
|
procpreadv,
|
||||||
|
procwritev,
|
||||||
|
procpwritev syscallFunc
|
||||||
|
)
|
||||||
|
|
||||||
|
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||||
|
|
||||||
|
func readv(fd int, iovs []Iovec) (n int, err error) {
|
||||||
|
var _p0 *Iovec
|
||||||
|
if len(iovs) > 0 {
|
||||||
|
_p0 = &iovs[0]
|
||||||
|
}
|
||||||
|
r0, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procreadv)), 3, uintptr(fd), uintptr(unsafe.Pointer(_p0)), uintptr(len(iovs)), 0, 0, 0)
|
||||||
|
n = int(r0)
|
||||||
|
if e1 != 0 {
|
||||||
|
err = e1
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||||
|
|
||||||
|
func preadv(fd int, iovs []Iovec, off int64) (n int, err error) {
|
||||||
|
var _p0 *Iovec
|
||||||
|
if len(iovs) > 0 {
|
||||||
|
_p0 = &iovs[0]
|
||||||
|
}
|
||||||
|
r0, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procpreadv)), 4, uintptr(fd), uintptr(unsafe.Pointer(_p0)), uintptr(len(iovs)), uintptr(off), 0, 0)
|
||||||
|
n = int(r0)
|
||||||
|
if e1 != 0 {
|
||||||
|
err = e1
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||||
|
|
||||||
|
func writev(fd int, iovs []Iovec) (n int, err error) {
|
||||||
|
var _p0 *Iovec
|
||||||
|
if len(iovs) > 0 {
|
||||||
|
_p0 = &iovs[0]
|
||||||
|
}
|
||||||
|
r0, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procwritev)), 3, uintptr(fd), uintptr(unsafe.Pointer(_p0)), uintptr(len(iovs)), 0, 0, 0)
|
||||||
|
n = int(r0)
|
||||||
|
if e1 != 0 {
|
||||||
|
err = e1
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||||
|
|
||||||
|
func pwritev(fd int, iovs []Iovec, off int64) (n int, err error) {
|
||||||
|
var _p0 *Iovec
|
||||||
|
if len(iovs) > 0 {
|
||||||
|
_p0 = &iovs[0]
|
||||||
|
}
|
||||||
|
r0, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procpwritev)), 4, uintptr(fd), uintptr(unsafe.Pointer(_p0)), uintptr(len(iovs)), uintptr(off), 0, 0)
|
||||||
|
n = int(r0)
|
||||||
|
if e1 != 0 {
|
||||||
|
err = e1
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
Loading…
Reference in new issue