vendor dependencies with dep
This commit is contained in:
parent
93d8310491
commit
1384296a47
2712 changed files with 965742 additions and 0 deletions
30
vendor/github.com/vanng822/go-premailer/premailer/data/markup_test.html
generated
vendored
Normal file
30
vendor/github.com/vanng822/go-premailer/premailer/data/markup_test.html
generated
vendored
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>Title</title>
|
||||
<style type="text/css">
|
||||
h1 {
|
||||
width: 50px;
|
||||
color:red;
|
||||
}
|
||||
h2 {
|
||||
vertical-align: top;
|
||||
}
|
||||
h3 {
|
||||
text-align: right;
|
||||
}
|
||||
strong {
|
||||
text-decoration:none
|
||||
}
|
||||
div {
|
||||
background-color: green
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Hi!</h1>
|
||||
<h2>There</h2>
|
||||
<h3>Hello</h3>
|
||||
<p><strong>Yes!</strong></p>
|
||||
<div>Green color</div>
|
||||
</body>
|
||||
</html>
|
||||
45
vendor/github.com/vanng822/go-premailer/premailer/doc.go
generated
vendored
Normal file
45
vendor/github.com/vanng822/go-premailer/premailer/doc.go
generated
vendored
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
// Package premailer is for inline styling.
|
||||
//
|
||||
// import (
|
||||
// "fmt"
|
||||
// "github.com/vanng822/go-premailer/premailer"
|
||||
// "log"
|
||||
// )
|
||||
//
|
||||
// func main() {
|
||||
// prem := premailer.NewPremailerFromFile(inputFile, premailer.NewOptions())
|
||||
// html, err := prem.Transform()
|
||||
// if err != nil {
|
||||
// log.Fatal(err)
|
||||
// }
|
||||
//
|
||||
// fmt.Println(html)
|
||||
// }
|
||||
// // Input
|
||||
//
|
||||
// <html>
|
||||
// <head>
|
||||
// <title>Title</title>
|
||||
// <style type="text/css">
|
||||
// h1 { width: 300px; color:red; }
|
||||
// strong { text-decoration:none; }
|
||||
// </style>
|
||||
// </head>
|
||||
// <body>
|
||||
// <h1>Hi!</h1>
|
||||
// <p><strong>Yes!</strong></p>
|
||||
// </body>
|
||||
// </html>
|
||||
//
|
||||
// // Output
|
||||
//
|
||||
// <html>
|
||||
// <head>
|
||||
// <title>Title</title>
|
||||
// </head>
|
||||
// <body>
|
||||
// <h1 style="color:red;width:300px" width="300">Hi!</h1>
|
||||
// <p><strong style="text-decoration:none">Yes!</strong></p>
|
||||
// </body>
|
||||
// </html>
|
||||
package premailer
|
||||
70
vendor/github.com/vanng822/go-premailer/premailer/element.go
generated
vendored
Normal file
70
vendor/github.com/vanng822/go-premailer/premailer/element.go
generated
vendored
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
package premailer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/PuerkitoBio/goquery"
|
||||
"github.com/vanng822/css"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type elementRules struct {
|
||||
element *goquery.Selection
|
||||
rules []*styleRule
|
||||
cssToAttributes bool
|
||||
}
|
||||
|
||||
func (er *elementRules) inline() {
|
||||
inline, _ := er.element.Attr("style")
|
||||
|
||||
var inlineStyles map[string]*css.CSSStyleDeclaration
|
||||
if inline != "" {
|
||||
inlineStyles = css.ParseBlock(inline)
|
||||
}
|
||||
|
||||
styles := make(map[string]string)
|
||||
for _, rule := range er.rules {
|
||||
for prop, s := range rule.styles {
|
||||
styles[prop] = s.Value
|
||||
}
|
||||
}
|
||||
|
||||
if len(inlineStyles) > 0 {
|
||||
for prop, s := range inlineStyles {
|
||||
styles[prop] = s.Value
|
||||
}
|
||||
}
|
||||
|
||||
final := make([]string, 0, len(styles))
|
||||
for p, v := range styles {
|
||||
final = append(final, fmt.Sprintf("%s:%s", p, v))
|
||||
if er.cssToAttributes {
|
||||
er.style_to_basic_html_attribute(p, v)
|
||||
}
|
||||
}
|
||||
|
||||
sort.Strings(final)
|
||||
style := strings.Join(final, ";")
|
||||
if style != "" {
|
||||
er.element.SetAttr("style", style)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (er *elementRules) style_to_basic_html_attribute(prop, value string) {
|
||||
switch prop {
|
||||
case "text-align":
|
||||
er.element.SetAttr("align", value)
|
||||
case "vertical-align":
|
||||
er.element.SetAttr("valign", value)
|
||||
case "background-color":
|
||||
er.element.SetAttr("bgcolor", value)
|
||||
case "width":
|
||||
fallthrough
|
||||
case "height":
|
||||
if strings.HasSuffix(value, "px") {
|
||||
value = value[:len(value)-2]
|
||||
}
|
||||
er.element.SetAttr(prop, value)
|
||||
}
|
||||
}
|
||||
16
vendor/github.com/vanng822/go-premailer/premailer/main_test.go
generated
vendored
Normal file
16
vendor/github.com/vanng822/go-premailer/premailer/main_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
package premailer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
fmt.Println("Test starting")
|
||||
args := os.Args[:]
|
||||
retCode := m.Run()
|
||||
os.Args = args
|
||||
fmt.Println("Test ending")
|
||||
os.Exit(retCode)
|
||||
}
|
||||
20
vendor/github.com/vanng822/go-premailer/premailer/options.go
generated
vendored
Normal file
20
vendor/github.com/vanng822/go-premailer/premailer/options.go
generated
vendored
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
package premailer
|
||||
|
||||
import ()
|
||||
|
||||
// Options for controlling behaviour
|
||||
type Options struct {
|
||||
// Remove class attribute from element
|
||||
// Default false
|
||||
RemoveClasses bool
|
||||
// Copy related CSS properties into HTML attributes (e.g. background-color to bgcolor)
|
||||
// Default true
|
||||
CssToAttributes bool
|
||||
}
|
||||
|
||||
// NewOptions return an Options instance with default value
|
||||
func NewOptions() *Options {
|
||||
options := &Options{}
|
||||
options.CssToAttributes = true
|
||||
return options
|
||||
}
|
||||
208
vendor/github.com/vanng822/go-premailer/premailer/premailer.go
generated
vendored
Normal file
208
vendor/github.com/vanng822/go-premailer/premailer/premailer.go
generated
vendored
Normal file
|
|
@ -0,0 +1,208 @@
|
|||
package premailer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/PuerkitoBio/goquery"
|
||||
"github.com/vanng822/css"
|
||||
"golang.org/x/net/html"
|
||||
)
|
||||
|
||||
// Inteface of Premailer
|
||||
type Premailer interface {
|
||||
// Transform process and inlining css
|
||||
// It start to collect the rules in the document style tags
|
||||
// Calculate specificity and sort the rules based on that
|
||||
// It then collects the affected elements
|
||||
// And applies the rules on those
|
||||
// The leftover rules will put back into a style element
|
||||
Transform() (string, error)
|
||||
}
|
||||
|
||||
var unmergableSelector = regexp.MustCompile("(?i)\\:{1,2}(visited|active|hover|focus|link|root|in-range|invalid|valid|after|before|selection|target|first\\-(line|letter))|^\\@")
|
||||
var notSupportedSelector = regexp.MustCompile("(?i)\\:(checked|disabled|enabled|lang)")
|
||||
|
||||
type premailer struct {
|
||||
doc *goquery.Document
|
||||
elIdAttr string
|
||||
elements map[string]*elementRules
|
||||
rules []*styleRule
|
||||
leftover []*css.CSSRule
|
||||
allRules [][]*css.CSSRule
|
||||
elementId int
|
||||
processed bool
|
||||
options *Options
|
||||
}
|
||||
|
||||
// NewPremailer return a new instance of Premailer
|
||||
// It take a Document as argument and it shouldn't be nil
|
||||
func NewPremailer(doc *goquery.Document, options *Options) Premailer {
|
||||
pr := premailer{}
|
||||
pr.doc = doc
|
||||
pr.rules = make([]*styleRule, 0)
|
||||
pr.allRules = make([][]*css.CSSRule, 0)
|
||||
pr.leftover = make([]*css.CSSRule, 0)
|
||||
pr.elements = make(map[string]*elementRules)
|
||||
pr.elIdAttr = "pr-el-id"
|
||||
if options == nil {
|
||||
options = NewOptions()
|
||||
}
|
||||
pr.options = options
|
||||
return &pr
|
||||
}
|
||||
|
||||
func (pr *premailer) sortRules() {
|
||||
ruleIndex := 0
|
||||
for ruleSetIndex, rules := range pr.allRules {
|
||||
if rules == nil {
|
||||
continue
|
||||
}
|
||||
for _, rule := range rules {
|
||||
if rule.Type != css.STYLE_RULE {
|
||||
pr.leftover = append(pr.leftover, rule)
|
||||
continue
|
||||
}
|
||||
normalStyles := make(map[string]*css.CSSStyleDeclaration)
|
||||
importantStyles := make(map[string]*css.CSSStyleDeclaration)
|
||||
|
||||
for prop, s := range rule.Style.Styles {
|
||||
if s.Important == 1 {
|
||||
importantStyles[prop] = s
|
||||
} else {
|
||||
normalStyles[prop] = s
|
||||
}
|
||||
}
|
||||
|
||||
selectors := strings.Split(rule.Style.SelectorText, ",")
|
||||
for _, selector := range selectors {
|
||||
if unmergableSelector.MatchString(selector) || notSupportedSelector.MatchString(selector) {
|
||||
// cause longer css
|
||||
pr.leftover = append(pr.leftover, copyRule(selector, rule))
|
||||
continue
|
||||
}
|
||||
if strings.Contains(selector, "*") {
|
||||
// keep this?
|
||||
pr.leftover = append(pr.leftover, copyRule(selector, rule))
|
||||
continue
|
||||
}
|
||||
if len(normalStyles) > 0 {
|
||||
pr.rules = append(pr.rules, &styleRule{makeSpecificity(0, ruleSetIndex, ruleIndex, selector), selector, normalStyles})
|
||||
ruleIndex += 1
|
||||
}
|
||||
if len(importantStyles) > 0 {
|
||||
pr.rules = append(pr.rules, &styleRule{makeSpecificity(1, ruleSetIndex, ruleIndex, selector), selector, importantStyles})
|
||||
ruleIndex += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
sort.Sort(bySpecificity(pr.rules))
|
||||
}
|
||||
|
||||
func (pr *premailer) collectRules() {
|
||||
var wg sync.WaitGroup
|
||||
pr.doc.Find("style:not([data-premailer='ignore'])").Each(func(_ int, s *goquery.Selection) {
|
||||
if _, exist := s.Attr("media"); exist {
|
||||
return
|
||||
}
|
||||
wg.Add(1)
|
||||
pr.allRules = append(pr.allRules, nil)
|
||||
go func(ruleSetIndex int) {
|
||||
defer func() {
|
||||
wg.Done()
|
||||
if r := recover(); r != nil {
|
||||
pr.allRules[ruleSetIndex] = nil
|
||||
log.Println("Got error when passing css")
|
||||
log.Println(r)
|
||||
}
|
||||
}()
|
||||
ss := css.Parse(s.Text())
|
||||
pr.allRules[ruleSetIndex] = ss.GetCSSRuleList()
|
||||
s.ReplaceWithHtml("")
|
||||
}(len(pr.allRules) - 1)
|
||||
})
|
||||
wg.Wait()
|
||||
|
||||
}
|
||||
|
||||
func (pr *premailer) collectElements() {
|
||||
for _, rule := range pr.rules {
|
||||
pr.doc.Find(rule.selector).Each(func(_ int, s *goquery.Selection) {
|
||||
if id, exist := s.Attr(pr.elIdAttr); exist {
|
||||
pr.elements[id].rules = append(pr.elements[id].rules, rule)
|
||||
} else {
|
||||
id := strconv.Itoa(pr.elementId)
|
||||
s.SetAttr(pr.elIdAttr, id)
|
||||
rules := make([]*styleRule, 0)
|
||||
rules = append(rules, rule)
|
||||
pr.elements[id] = &elementRules{element: s, rules: rules, cssToAttributes: pr.options.CssToAttributes}
|
||||
pr.elementId += 1
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func (pr *premailer) applyInline() {
|
||||
for _, element := range pr.elements {
|
||||
element.inline()
|
||||
element.element.RemoveAttr(pr.elIdAttr)
|
||||
if pr.options.RemoveClasses {
|
||||
element.element.RemoveAttr("class")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (pr *premailer) addLeftover() {
|
||||
if len(pr.leftover) > 0 {
|
||||
headNode := pr.doc.Find("head")
|
||||
|
||||
styleNode := &html.Node{}
|
||||
styleNode.Type = html.ElementNode
|
||||
styleNode.Data = "style"
|
||||
styleNode.Attr = []html.Attribute{html.Attribute{Key: "type", Val: "text/css"}}
|
||||
cssNode := &html.Node{}
|
||||
cssData := make([]string, 0, len(pr.leftover))
|
||||
for _, rule := range pr.leftover {
|
||||
if rule.Type == css.MEDIA_RULE {
|
||||
mcssData := make([]string, 0, len(rule.Rules))
|
||||
for _, mrule := range rule.Rules {
|
||||
mcssData = append(mcssData, makeRuleImportant(mrule))
|
||||
}
|
||||
cssData = append(cssData, fmt.Sprintf("%s %s{\n%s\n}\n",
|
||||
rule.Type.Text(),
|
||||
rule.Style.SelectorText,
|
||||
strings.Join(mcssData, "\n")))
|
||||
} else {
|
||||
cssData = append(cssData, makeRuleImportant(rule))
|
||||
}
|
||||
}
|
||||
cssNode.Data = strings.Join(cssData, "")
|
||||
cssNode.Type = html.TextNode
|
||||
styleNode.AppendChild(cssNode)
|
||||
headNode.AppendNodes(styleNode)
|
||||
}
|
||||
}
|
||||
|
||||
// Transform process and inlining css
|
||||
// It start to collect the rules in the document style tags
|
||||
// Calculate specificity and sort the rules based on that
|
||||
// It then collects the affected elements
|
||||
// And applies the rules on those
|
||||
// The leftover rules will put back into a style element
|
||||
func (pr *premailer) Transform() (string, error) {
|
||||
if !pr.processed {
|
||||
pr.collectRules()
|
||||
pr.sortRules()
|
||||
pr.collectElements()
|
||||
pr.applyInline()
|
||||
pr.addLeftover()
|
||||
}
|
||||
return pr.doc.Html()
|
||||
}
|
||||
24
vendor/github.com/vanng822/go-premailer/premailer/premailer_from_file.go
generated
vendored
Normal file
24
vendor/github.com/vanng822/go-premailer/premailer/premailer_from_file.go
generated
vendored
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
package premailer
|
||||
|
||||
import (
|
||||
"github.com/PuerkitoBio/goquery"
|
||||
"os"
|
||||
)
|
||||
|
||||
// NewPremailerFromFile take an filename
|
||||
// Read the content of this file
|
||||
// and create a goquery.Document
|
||||
// and then create and Premailer instance.
|
||||
// It will panic if any error happens
|
||||
func NewPremailerFromFile(filename string, options *Options) Premailer {
|
||||
fd, err := os.Open(filename)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer fd.Close()
|
||||
d, err := goquery.NewDocumentFromReader(fd)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return NewPremailer(d, options)
|
||||
}
|
||||
24
vendor/github.com/vanng822/go-premailer/premailer/premailer_from_file_test.go
generated
vendored
Normal file
24
vendor/github.com/vanng822/go-premailer/premailer/premailer_from_file_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
package premailer
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestBasicHTMLFromFile(t *testing.T) {
|
||||
p := NewPremailerFromFile("data/markup_test.html", nil)
|
||||
result_html, err := p.Transform()
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Contains(t, result_html, "<h1 style=\"color:red;width:50px\" width=\"50\">Hi!</h1>")
|
||||
assert.Contains(t, result_html, "<h2 style=\"vertical-align:top\" valign=\"top\">There</h2>")
|
||||
assert.Contains(t, result_html, "<h3 style=\"text-align:right\" align=\"right\">Hello</h3>")
|
||||
assert.Contains(t, result_html, "<p><strong style=\"text-decoration:none\">Yes!</strong></p>")
|
||||
assert.Contains(t, result_html, "<div style=\"background-color:green\" bgcolor=\"green\">Green color</div>")
|
||||
}
|
||||
|
||||
func TestFromFilePanic(t *testing.T) {
|
||||
assert.Panics(t, func() {
|
||||
NewPremailerFromFile("data/blablabla.html", nil)
|
||||
})
|
||||
}
|
||||
19
vendor/github.com/vanng822/go-premailer/premailer/premailer_from_string.go
generated
vendored
Normal file
19
vendor/github.com/vanng822/go-premailer/premailer/premailer_from_string.go
generated
vendored
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
package premailer
|
||||
|
||||
import (
|
||||
"github.com/PuerkitoBio/goquery"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// NewPremailerFromString take in a document in string format
|
||||
// and create a goquery.Document
|
||||
// and then create and Premailer instance.
|
||||
// It will panic if any error happens
|
||||
func NewPremailerFromString(doc string, options *Options) Premailer {
|
||||
read := strings.NewReader(doc)
|
||||
d, err := goquery.NewDocumentFromReader(read)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return NewPremailer(d, options)
|
||||
}
|
||||
406
vendor/github.com/vanng822/go-premailer/premailer/premailer_test.go
generated
vendored
Normal file
406
vendor/github.com/vanng822/go-premailer/premailer/premailer_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,406 @@
|
|||
package premailer
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestBasicHTML(t *testing.T) {
|
||||
html := `<html>
|
||||
<head>
|
||||
<title>Title</title>
|
||||
<style type="text/css">
|
||||
h1 {
|
||||
width: 50px;
|
||||
color:red;
|
||||
}
|
||||
h2 {
|
||||
vertical-align: top;
|
||||
}
|
||||
h3 {
|
||||
text-align: right;
|
||||
}
|
||||
strong {
|
||||
text-decoration:none
|
||||
}
|
||||
div {
|
||||
background-color: green
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Hi!</h1>
|
||||
<h2>There</h2>
|
||||
<h3>Hello</h3>
|
||||
<p><strong>Yes!</strong></p>
|
||||
<div>Green color</div>
|
||||
</body>
|
||||
</html>`
|
||||
|
||||
p := NewPremailerFromString(html, nil)
|
||||
result_html, err := p.Transform()
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Contains(t, result_html, "<h1 style=\"color:red;width:50px\" width=\"50\">Hi!</h1>")
|
||||
assert.Contains(t, result_html, "<h2 style=\"vertical-align:top\" valign=\"top\">There</h2>")
|
||||
assert.Contains(t, result_html, "<h3 style=\"text-align:right\" align=\"right\">Hello</h3>")
|
||||
assert.Contains(t, result_html, "<p><strong style=\"text-decoration:none\">Yes!</strong></p>")
|
||||
assert.Contains(t, result_html, "<div style=\"background-color:green\" bgcolor=\"green\">Green color</div>")
|
||||
}
|
||||
|
||||
func TestDataPremailerIgnore(t *testing.T) {
|
||||
html := `<html>
|
||||
<head>
|
||||
<title>Title</title>
|
||||
<style type="text/css" data-premailer="ignore">
|
||||
h1, h2 {
|
||||
color:red;
|
||||
}
|
||||
strong {
|
||||
text-decoration:none
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Hi!</h1>
|
||||
<p><strong>Yes!</strong></p>
|
||||
</body>
|
||||
</html>`
|
||||
|
||||
p := NewPremailerFromString(html, nil)
|
||||
result_html, err := p.Transform()
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Contains(t, result_html, "<h1>Hi!</h1>")
|
||||
assert.Contains(t, result_html, "<p><strong>Yes!</strong></p>")
|
||||
}
|
||||
|
||||
func TestWithInline(t *testing.T) {
|
||||
html := `<html>
|
||||
<head>
|
||||
<title>Title</title>
|
||||
<style type="text/css">
|
||||
h1, h2 {
|
||||
width: 50px;
|
||||
color:red;
|
||||
}
|
||||
strong {
|
||||
text-decoration:none
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1 style="width: 100%;">Hi!</h1>
|
||||
<p><strong>Yes!</strong></p>
|
||||
</body>
|
||||
</html>`
|
||||
|
||||
p := NewPremailerFromString(html, nil)
|
||||
result_html, err := p.Transform()
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Contains(t, result_html, "<h1 style=\"color:red;width:100%\" width=\"100%\">Hi!</h1>")
|
||||
assert.Contains(t, result_html, "<p><strong style=\"text-decoration:none\">Yes!</strong></p>")
|
||||
assert.NotContains(t, result_html, "<style type=\"text/css\">")
|
||||
}
|
||||
|
||||
func TestPseudoSelectors(t *testing.T) {
|
||||
html := `<html>
|
||||
<head>
|
||||
<title>Title</title>
|
||||
<style type="text/css">
|
||||
a:active {
|
||||
color: red;
|
||||
font-size: 12px;
|
||||
}
|
||||
a:first-child {
|
||||
color: green;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Hi!</h1>
|
||||
<p>
|
||||
<a href="/home">Yes!</a>
|
||||
<a href="/away">No!</a>
|
||||
</p>
|
||||
</body>
|
||||
</html>`
|
||||
|
||||
p := NewPremailerFromString(html, nil)
|
||||
result_html, err := p.Transform()
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Contains(t, result_html, "<a href=\"/home\" style=\"color:green\">Yes!</a>")
|
||||
assert.Contains(t, result_html, "<style type=\"text/css\">")
|
||||
}
|
||||
|
||||
func TestRemoveClass(t *testing.T) {
|
||||
html := `<html>
|
||||
<head>
|
||||
<title>Title</title>
|
||||
<style type="text/css">
|
||||
h1, h2 {
|
||||
color:red;
|
||||
}
|
||||
.big {
|
||||
font-size: 40px;
|
||||
width: 150px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1 class="big">Hi!</h1>
|
||||
<p><strong>Yes!</strong></p>
|
||||
</body>
|
||||
</html>`
|
||||
|
||||
options := &Options{}
|
||||
options.RemoveClasses = true
|
||||
p := NewPremailerFromString(html, options)
|
||||
result_html, err := p.Transform()
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Contains(t, result_html, "<h1 style=\"color:red;font-size:40px;width:150px\">Hi!</h1>")
|
||||
assert.Contains(t, result_html, "<p><strong>Yes!</strong></p>")
|
||||
}
|
||||
|
||||
func TestCssToAttributesFalse(t *testing.T) {
|
||||
html := `<html>
|
||||
<head>
|
||||
<title>Title</title>
|
||||
<style type="text/css">
|
||||
h1, h2 {
|
||||
color:red;
|
||||
}
|
||||
.wide {
|
||||
width: 1000px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1 class="wide">Hi!</h1>
|
||||
<p><strong>Yes!</strong></p>
|
||||
</body>
|
||||
</html>`
|
||||
|
||||
options := &Options{}
|
||||
options.CssToAttributes = false
|
||||
p := NewPremailerFromString(html, options)
|
||||
result_html, err := p.Transform()
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Contains(t, result_html, "<h1 class=\"wide\" style=\"color:red;width:1000px\">Hi!</h1>")
|
||||
assert.Contains(t, result_html, "<p><strong>Yes!</strong></p>")
|
||||
}
|
||||
|
||||
func TestWithImportant(t *testing.T) {
|
||||
html := `<html>
|
||||
<head>
|
||||
<title>Title</title>
|
||||
<style type="text/css">
|
||||
h1, h2 {
|
||||
color:red;
|
||||
}
|
||||
p {
|
||||
width: 100px !important;
|
||||
color: blue
|
||||
}
|
||||
.wide {
|
||||
width: 1000px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Hi!</h1>
|
||||
<p class="wide"><strong>Yes!</strong></p>
|
||||
</body>
|
||||
</html>`
|
||||
|
||||
p := NewPremailerFromString(html, NewOptions())
|
||||
result_html, err := p.Transform()
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Contains(t, result_html, "<h1 style=\"color:red\">Hi!</h1>")
|
||||
assert.Contains(t, result_html, "<p class=\"wide\" style=\"color:blue;width:100px\" width=\"100\"><strong>Yes!</strong></p>")
|
||||
}
|
||||
|
||||
func TestWithMediaRule(t *testing.T) {
|
||||
html := `<html>
|
||||
<head>
|
||||
<title>Title</title>
|
||||
<style type="text/css">
|
||||
h1, h2 {
|
||||
color:red;
|
||||
}
|
||||
p {
|
||||
width: 100px !important;
|
||||
color: blue
|
||||
}
|
||||
.wide {
|
||||
width: 1000px;
|
||||
}
|
||||
@media all and (min-width: 62em) {
|
||||
h1 {
|
||||
font-size: 55px;
|
||||
line-height: 60px;
|
||||
padding-top: 0;
|
||||
padding-bottom: 5px
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Hi!</h1>
|
||||
<p class="wide"><strong>Yes!</strong></p>
|
||||
</body>
|
||||
</html>`
|
||||
|
||||
p := NewPremailerFromString(html, NewOptions())
|
||||
result_html, err := p.Transform()
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Contains(t, result_html, "<h1 style=\"color:red\">Hi!</h1>")
|
||||
assert.Contains(t, result_html, "<p class=\"wide\" style=\"color:blue;width:100px\" width=\"100\"><strong>Yes!</strong></p>")
|
||||
|
||||
assert.Contains(t, result_html, "@media all and (min-width: 62em){")
|
||||
assert.Contains(t, result_html, "font-size: 55px !important;")
|
||||
assert.Contains(t, result_html, "line-height: 60px !important;")
|
||||
assert.Contains(t, result_html, "padding-bottom: 5px !important;")
|
||||
assert.Contains(t, result_html, "padding-top: 0 !important")
|
||||
}
|
||||
|
||||
func TestWithMediaAttribute(t *testing.T) {
|
||||
html := `<html>
|
||||
<head>
|
||||
<title>Title</title>
|
||||
<style type="text/css">
|
||||
h1, h2 {
|
||||
color:red;
|
||||
}
|
||||
p {
|
||||
width: 100px !important;
|
||||
color: blue
|
||||
}
|
||||
.wide {
|
||||
width: 1000px;
|
||||
}
|
||||
</style>
|
||||
<style type="text/css" media="all and (min-width: 62em)">
|
||||
h1 {
|
||||
font-size: 55px;
|
||||
line-height: 60px;
|
||||
padding-top: 0;
|
||||
padding-bottom: 5px
|
||||
}
|
||||
</style>
|
||||
<style>
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Hi!</h1>
|
||||
<p class="wide"><strong>Yes!</strong></p>
|
||||
</body>
|
||||
</html>`
|
||||
|
||||
p := NewPremailerFromString(html, NewOptions())
|
||||
result_html, err := p.Transform()
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Contains(t, result_html, "<h1 style=\"color:red\">Hi!</h1>")
|
||||
assert.Contains(t, result_html, "<p class=\"wide\" style=\"color:blue;width:100px\" width=\"100\"><strong>Yes!</strong></p>")
|
||||
|
||||
assert.Contains(t, result_html, "<style type=\"text/css\" media=\"all and (min-width: 62em)\">")
|
||||
assert.Contains(t, result_html, "font-size: 55px;")
|
||||
assert.Contains(t, result_html, "line-height: 60px;")
|
||||
assert.Contains(t, result_html, "padding-top: 0;")
|
||||
assert.Contains(t, result_html, "padding-bottom: 5px")
|
||||
}
|
||||
|
||||
func TestIndexOutOfRange(t *testing.T) {
|
||||
html := `<html>
|
||||
<head>
|
||||
<title>Title</title>
|
||||
<style type="text/css">
|
||||
h1, h2 {
|
||||
color:red;
|
||||
}
|
||||
p {
|
||||
width: 100px !important;
|
||||
color: blue
|
||||
}
|
||||
.wide {
|
||||
width: 1000px;
|
||||
}
|
||||
</style>
|
||||
<style type="text/css" media="all and (min-width: 62em)">
|
||||
h1 {
|
||||
font-size: 55px;
|
||||
line-height: 60px;
|
||||
padding-top: 0;
|
||||
padding-bottom: 5px
|
||||
}
|
||||
</style>
|
||||
<style>
|
||||
.some {
|
||||
color: red;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Hi!</h1>
|
||||
<p class="wide"><strong>Yes!</strong></p>
|
||||
</body>
|
||||
</html>`
|
||||
|
||||
p := NewPremailerFromString(html, NewOptions())
|
||||
result_html, err := p.Transform()
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Contains(t, result_html, "<h1 style=\"color:red\">Hi!</h1>")
|
||||
assert.Contains(t, result_html, "<p class=\"wide\" style=\"color:blue;width:100px\" width=\"100\"><strong>Yes!</strong></p>")
|
||||
|
||||
assert.Contains(t, result_html, "<style type=\"text/css\" media=\"all and (min-width: 62em)\">")
|
||||
assert.Contains(t, result_html, "font-size: 55px;")
|
||||
assert.Contains(t, result_html, "line-height: 60px;")
|
||||
assert.Contains(t, result_html, "padding-top: 0;")
|
||||
assert.Contains(t, result_html, "padding-bottom: 5px")
|
||||
}
|
||||
|
||||
func TestSpecificity(t *testing.T) {
|
||||
html := `<html>
|
||||
<head>
|
||||
<title>Title</title>
|
||||
<style type="text/css">
|
||||
table.bar-chart td.bar-area {
|
||||
padding: 10px;
|
||||
}
|
||||
table { width: 91%; }
|
||||
table { width: 92%; }
|
||||
table { width: 93%; }
|
||||
table { width: 94%; }
|
||||
table { width: 95%; }
|
||||
table { width: 96%; }
|
||||
table { width: 97%; }
|
||||
table.bar-chart td {
|
||||
padding: 5px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<table class="bar-chart">
|
||||
<tr><td>1</td></tr>
|
||||
<tr><td class="bar-area">2</td></tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>`
|
||||
|
||||
p := NewPremailerFromString(html, NewOptions())
|
||||
result_html, err := p.Transform()
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Contains(t, result_html, `<tr><td style="padding:5px">1</td></tr>`)
|
||||
assert.Contains(t, result_html, `<tr><td class="bar-area" style="padding:10px">2</td></tr>`)
|
||||
}
|
||||
112
vendor/github.com/vanng822/go-premailer/premailer/selector_compatible_test.go
generated
vendored
Normal file
112
vendor/github.com/vanng822/go-premailer/premailer/selector_compatible_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
package premailer
|
||||
|
||||
import (
|
||||
"github.com/PuerkitoBio/goquery"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSupportedSelectors(t *testing.T) {
|
||||
selectors := []string{
|
||||
".footer__content_wrapper--last",
|
||||
"table[class=\"body\"] .footer__content td",
|
||||
"table[class=\"body\"] td.footer__link_wrapper--first",
|
||||
".header + .content",
|
||||
"#firstname",
|
||||
"p ~ ul",
|
||||
"div > p",
|
||||
"div > p",
|
||||
"div p",
|
||||
"div, p",
|
||||
"[target]",
|
||||
"[target=_blank]",
|
||||
"[title~=flower]",
|
||||
"[lang|=en]",
|
||||
"a[href^=\"https\"]",
|
||||
"a[href$=\".pdf\"]",
|
||||
"a[href*=\"css\"]",
|
||||
"p:empty",
|
||||
"p:first-child",
|
||||
"p:first-of-type",
|
||||
"p:last-child",
|
||||
"p:last-of-type",
|
||||
":not(p)",
|
||||
"p:nth-child(2)",
|
||||
"p:nth-last-child(2)",
|
||||
"p:nth-of-type(2)",
|
||||
"p:only-child",
|
||||
"p:nth-last-of-type(2)",
|
||||
"div:not(:nth-child(1))",
|
||||
"div:not(:not(:first-child))",
|
||||
}
|
||||
|
||||
html := `<html>
|
||||
<head>
|
||||
<title>Title</title>
|
||||
<style type="text/css" data-premailer="ignore">
|
||||
h1, h2 {
|
||||
color:red;
|
||||
}
|
||||
strong {
|
||||
text-decoration:none
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Hi!</h1>
|
||||
<p><strong>Yes!</strong></p>
|
||||
</body>
|
||||
</html>`
|
||||
|
||||
read := strings.NewReader(html)
|
||||
doc, _ := goquery.NewDocumentFromReader(read)
|
||||
|
||||
pr := premailer{}
|
||||
pr.doc = doc
|
||||
for _, selector := range selectors {
|
||||
assert.NotPanics(t, func() {
|
||||
pr.doc.Find(selector)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNotSupportedSelectors(t *testing.T) {
|
||||
|
||||
html := `<html>
|
||||
<head>
|
||||
<title>Title</title>
|
||||
<style type="text/css" data-premailer="ignore">
|
||||
h1, h2 {
|
||||
color:red;
|
||||
}
|
||||
strong {
|
||||
text-decoration:none
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Hi!</h1>
|
||||
<p><strong>Yes!</strong></p>
|
||||
</body>
|
||||
</html>`
|
||||
|
||||
read := strings.NewReader(html)
|
||||
doc, _ := goquery.NewDocumentFromReader(read)
|
||||
|
||||
pr := premailer{}
|
||||
pr.doc = doc
|
||||
|
||||
notSupportedSelectors := []string{
|
||||
"input:checked",
|
||||
"input:disabled",
|
||||
"input:enabled",
|
||||
"input:optional",
|
||||
"input:read-only",
|
||||
"p:lang(it)",
|
||||
}
|
||||
|
||||
for _, selector := range notSupportedSelectors {
|
||||
assert.Equal(t, 0, pr.doc.Find(selector).Length())
|
||||
}
|
||||
}
|
||||
68
vendor/github.com/vanng822/go-premailer/premailer/specificity.go
generated
vendored
Normal file
68
vendor/github.com/vanng822/go-premailer/premailer/specificity.go
generated
vendored
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
package premailer
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// https://developer.mozilla.org/en-US/docs/Web/CSS/Specificity
|
||||
// https://developer.mozilla.org/en-US/docs/Web/CSS/Reference#Selectors
|
||||
|
||||
type specificity struct {
|
||||
important int
|
||||
idCount int
|
||||
classCount int
|
||||
typeCount int
|
||||
attrCount int
|
||||
ruleSetIndex int
|
||||
ruleIndex int
|
||||
}
|
||||
|
||||
func (s *specificity) importantOrders() []int {
|
||||
return []int{s.important, s.idCount,
|
||||
s.classCount, s.attrCount,
|
||||
s.typeCount, s.ruleSetIndex,
|
||||
s.ruleIndex}
|
||||
}
|
||||
|
||||
var _type_selector_regex = regexp.MustCompile("(^|\\s)\\w")
|
||||
|
||||
func makeSpecificity(important, ruleSetIndex, ruleIndex int, selector string) *specificity {
|
||||
spec := specificity{}
|
||||
// determine values for priority
|
||||
if important > 0 {
|
||||
spec.important = 1
|
||||
} else {
|
||||
spec.important = 0
|
||||
}
|
||||
spec.idCount = strings.Count(selector, "#")
|
||||
spec.classCount = strings.Count(selector, ".")
|
||||
spec.attrCount = strings.Count(selector, "[")
|
||||
spec.typeCount = len(_type_selector_regex.FindAllString(selector, -1))
|
||||
spec.ruleSetIndex = ruleSetIndex
|
||||
spec.ruleIndex = ruleIndex
|
||||
return &spec
|
||||
}
|
||||
|
||||
type bySpecificity []*styleRule
|
||||
|
||||
func (bs bySpecificity) Len() int {
|
||||
return len(bs)
|
||||
}
|
||||
func (bs bySpecificity) Swap(i, j int) {
|
||||
bs[i], bs[j] = bs[j], bs[i]
|
||||
}
|
||||
|
||||
func (bs bySpecificity) Less(i, j int) bool {
|
||||
iorders := bs[i].specificity.importantOrders()
|
||||
jorders := bs[j].specificity.importantOrders()
|
||||
for n, v := range iorders {
|
||||
if v < jorders[n] {
|
||||
return true
|
||||
}
|
||||
if v > jorders[n] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
118
vendor/github.com/vanng822/go-premailer/premailer/specificity_test.go
generated
vendored
Normal file
118
vendor/github.com/vanng822/go-premailer/premailer/specificity_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
package premailer
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestSpecificitySelectorType(t *testing.T) {
|
||||
spec := makeSpecificity(1, 2, 100, "table")
|
||||
expected := []int{1, 0, 0, 0, 1, 2, 100}
|
||||
assert.Equal(t, expected, spec.importantOrders())
|
||||
}
|
||||
|
||||
func TestSpecificitySelectorClass(t *testing.T) {
|
||||
// class
|
||||
spec := makeSpecificity(1, 2, 102, "table.red")
|
||||
expected := []int{1, 0, 1, 0, 1, 2, 102}
|
||||
assert.Equal(t, expected, spec.importantOrders())
|
||||
}
|
||||
|
||||
func TestSpecificitySelectorAttr(t *testing.T) {
|
||||
// Attribute
|
||||
spec := makeSpecificity(1, 3, 103, "span[lang~=\"en-us\"]")
|
||||
expected := []int{1, 0, 0, 1, 1, 3, 103}
|
||||
assert.Equal(t, expected, spec.importantOrders())
|
||||
}
|
||||
|
||||
func TestSpecificitySelectorId(t *testing.T) {
|
||||
// id
|
||||
spec := makeSpecificity(0, 3, 104, "#example")
|
||||
expected := []int{0, 1, 0, 0, 0, 3, 104}
|
||||
assert.Equal(t, expected, spec.importantOrders())
|
||||
}
|
||||
|
||||
func TestSpecificitySort(t *testing.T) {
|
||||
undertest := make([]*styleRule, 4)
|
||||
for i := 0; i < 4; i++ {
|
||||
undertest[i] = &styleRule{}
|
||||
}
|
||||
specificity0 := makeSpecificity(1, 2, 100, "table")
|
||||
undertest[0].specificity = specificity0
|
||||
specificity1 := makeSpecificity(1, 2, 102, "table.red")
|
||||
undertest[1].specificity = specificity1
|
||||
specificity2 := makeSpecificity(1, 3, 103, "span[lang~=\"en-us\"]")
|
||||
undertest[2].specificity = specificity2
|
||||
specificity3 := makeSpecificity(0, 3, 104, "#example")
|
||||
undertest[3].specificity = specificity3
|
||||
|
||||
// expected order
|
||||
/*
|
||||
expected3 := []int{0, 1, 0, 0, 0, 3, 104}
|
||||
expected0 := []int{1, 0, 0, 0, 1, 2, 100}
|
||||
expected2 := []int{1, 0, 0, 1, 1, 3, 103}
|
||||
expected1 := []int{1, 0, 1, 0, 1, 2, 102}
|
||||
*/
|
||||
sort.Sort(bySpecificity(undertest))
|
||||
|
||||
assert.Equal(t, specificity3, undertest[0].specificity)
|
||||
assert.Equal(t, specificity0, undertest[1].specificity)
|
||||
assert.Equal(t, specificity2, undertest[2].specificity)
|
||||
assert.Equal(t, specificity1, undertest[3].specificity)
|
||||
}
|
||||
|
||||
func TestSpecificitySortRuleSetIndex(t *testing.T) {
|
||||
undertest := make([]*styleRule, 2)
|
||||
for i := 0; i < 2; i++ {
|
||||
undertest[i] = &styleRule{}
|
||||
}
|
||||
specificity0 := makeSpecificity(1, 2, 102, "table")
|
||||
undertest[0].specificity = specificity0
|
||||
specificity1 := makeSpecificity(1, 1, 102, "table")
|
||||
undertest[1].specificity = specificity1
|
||||
|
||||
sort.Sort(bySpecificity(undertest))
|
||||
|
||||
assert.Equal(t, specificity1, undertest[0].specificity)
|
||||
assert.Equal(t, specificity0, undertest[1].specificity)
|
||||
}
|
||||
|
||||
func TestSpecificitySortRuleIndex(t *testing.T) {
|
||||
undertest := make([]*styleRule, 2)
|
||||
for i := 0; i < 2; i++ {
|
||||
undertest[i] = &styleRule{}
|
||||
}
|
||||
specificity0 := makeSpecificity(1, 1, 102, "table")
|
||||
undertest[0].specificity = specificity0
|
||||
specificity1 := makeSpecificity(1, 1, 100, "table")
|
||||
undertest[1].specificity = specificity1
|
||||
|
||||
sort.Sort(bySpecificity(undertest))
|
||||
|
||||
assert.Equal(t, specificity1, undertest[0].specificity)
|
||||
assert.Equal(t, specificity0, undertest[1].specificity)
|
||||
}
|
||||
|
||||
func TestSpecificitySortLongArray(t *testing.T) {
|
||||
// It has to be longer than 6 due to internal implementation of sort.Sort(),
|
||||
rules := []*styleRule{
|
||||
&styleRule{specificity: makeSpecificity(0, 0, 1, "table.padded")},
|
||||
&styleRule{specificity: makeSpecificity(0, 0, 2, "table.padded")},
|
||||
&styleRule{specificity: makeSpecificity(0, 0, 3, "table.padded")},
|
||||
&styleRule{specificity: makeSpecificity(0, 0, 4, "table.padded")},
|
||||
&styleRule{specificity: makeSpecificity(0, 0, 5, "table.padded")},
|
||||
&styleRule{specificity: makeSpecificity(0, 0, 6, "table.padded")},
|
||||
&styleRule{specificity: makeSpecificity(0, 0, 11, "table")},
|
||||
}
|
||||
|
||||
sort.Sort(bySpecificity(rules))
|
||||
|
||||
ruleIndices := make([]int, len(rules))
|
||||
for i := range rules {
|
||||
ruleIndices[i] = rules[i].specificity.ruleIndex
|
||||
}
|
||||
expectedRuleIndices := []int{11, 1, 2, 3, 4, 5, 6}
|
||||
assert.Equal(t, expectedRuleIndices, ruleIndices)
|
||||
}
|
||||
11
vendor/github.com/vanng822/go-premailer/premailer/style_rule.go
generated
vendored
Normal file
11
vendor/github.com/vanng822/go-premailer/premailer/style_rule.go
generated
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
package premailer
|
||||
|
||||
import (
|
||||
"github.com/vanng822/css"
|
||||
)
|
||||
|
||||
type styleRule struct {
|
||||
specificity *specificity
|
||||
selector string
|
||||
styles map[string]*css.CSSStyleDeclaration
|
||||
}
|
||||
24
vendor/github.com/vanng822/go-premailer/premailer/util.go
generated
vendored
Normal file
24
vendor/github.com/vanng822/go-premailer/premailer/util.go
generated
vendored
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
package premailer
|
||||
|
||||
import (
|
||||
"github.com/vanng822/css"
|
||||
)
|
||||
|
||||
func copyRule(selector string, rule *css.CSSRule) *css.CSSRule {
|
||||
// copy rule for each selector
|
||||
styles := make(map[string]*css.CSSStyleDeclaration)
|
||||
for prop, s := range rule.Style.Styles {
|
||||
styles[prop] = css.NewCSSStyleDeclaration(s.Property, s.Value, s.Important)
|
||||
}
|
||||
copiedStyle := css.CSSStyleRule{SelectorText: selector, Styles: styles}
|
||||
copiedRule := &css.CSSRule{Type: rule.Type, Style: copiedStyle}
|
||||
return copiedRule
|
||||
}
|
||||
|
||||
func makeRuleImportant(rule *css.CSSRule) string {
|
||||
// this for using Text() which has nice sorted props
|
||||
for _, s := range rule.Style.Styles {
|
||||
s.Important = 1
|
||||
}
|
||||
return rule.Style.Text()
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue