f128c947ab
* Refactor plugin to be compatible with Drone 0.5 * Add vendor files * Re-add logo.svg, make loading environment from .env file optional, and use drone-go/template * Fix README * Fix issue with date formatting, update the DOCS, and improve types * Add working directory and volume mount to README example
182 lines
4.1 KiB
Go
182 lines
4.1 KiB
Go
package inliner
|
|
|
|
import (
|
|
"sort"
|
|
|
|
"github.com/PuerkitoBio/goquery"
|
|
|
|
"github.com/aymerick/douceur/css"
|
|
"github.com/aymerick/douceur/parser"
|
|
)
|
|
|
|
// Element represents a HTML element with matching CSS rules
|
|
type Element struct {
|
|
// The goquery handler
|
|
elt *goquery.Selection
|
|
|
|
// The style rules to apply on that element
|
|
styleRules []*StyleRule
|
|
}
|
|
|
|
// ElementAttr represents a HTML element attribute
|
|
type ElementAttr struct {
|
|
attr string
|
|
elements []string
|
|
}
|
|
|
|
// Index is style property name
|
|
var styleToAttr map[string]*ElementAttr
|
|
|
|
func init() {
|
|
// Borrowed from premailer:
|
|
// https://github.com/premailer/premailer/blob/master/lib/premailer/premailer.rb
|
|
styleToAttr = map[string]*ElementAttr{
|
|
"text-align": {
|
|
"align",
|
|
[]string{"h1", "h2", "h3", "h4", "h5", "h6", "p", "div", "blockquote", "tr", "th", "td"},
|
|
},
|
|
"background-color": {
|
|
"bgcolor",
|
|
[]string{"body", "table", "tr", "th", "td"},
|
|
},
|
|
"background-image": {
|
|
"background",
|
|
[]string{"table"},
|
|
},
|
|
"vertical-align": {
|
|
"valign",
|
|
[]string{"th", "td"},
|
|
},
|
|
"float": {
|
|
"align",
|
|
[]string{"img"},
|
|
},
|
|
// @todo width and height ?
|
|
}
|
|
}
|
|
|
|
// NewElement instanciates a new element
|
|
func NewElement(elt *goquery.Selection) *Element {
|
|
return &Element{
|
|
elt: elt,
|
|
}
|
|
}
|
|
|
|
// Add a Style Rule to Element
|
|
func (element *Element) addStyleRule(styleRule *StyleRule) {
|
|
element.styleRules = append(element.styleRules, styleRule)
|
|
}
|
|
|
|
// Inline styles on element
|
|
func (element *Element) inline() error {
|
|
// compute declarations
|
|
declarations, err := element.computeDeclarations()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// set style attribute
|
|
styleValue := computeStyleValue(declarations)
|
|
if styleValue != "" {
|
|
element.elt.SetAttr("style", styleValue)
|
|
}
|
|
|
|
// set additionnal attributes
|
|
element.setAttributesFromStyle(declarations)
|
|
|
|
return nil
|
|
}
|
|
|
|
// Compute css declarations
|
|
func (element *Element) computeDeclarations() ([]*css.Declaration, error) {
|
|
result := []*css.Declaration{}
|
|
|
|
styles := make(map[string]*StyleDeclaration)
|
|
|
|
// First: parsed stylesheets rules
|
|
mergeStyleDeclarations(element.styleRules, styles)
|
|
|
|
// Then: inline rules
|
|
inlineRules, err := element.parseInlineStyle()
|
|
if err != nil {
|
|
return result, err
|
|
}
|
|
|
|
mergeStyleDeclarations(inlineRules, styles)
|
|
|
|
// map to array
|
|
for _, styleDecl := range styles {
|
|
result = append(result, styleDecl.Declaration)
|
|
}
|
|
|
|
// sort declarations by property name
|
|
sort.Sort(css.DeclarationsByProperty(result))
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// Parse inline style rules
|
|
func (element *Element) parseInlineStyle() ([]*StyleRule, error) {
|
|
result := []*StyleRule{}
|
|
|
|
styleValue, exists := element.elt.Attr("style")
|
|
if (styleValue == "") || !exists {
|
|
return result, nil
|
|
}
|
|
|
|
declarations, err := parser.ParseDeclarations(styleValue)
|
|
if err != nil {
|
|
return result, err
|
|
}
|
|
|
|
result = append(result, NewStyleRule(inlineFakeSelector, declarations))
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// Set additional attributes from style declarations
|
|
func (element *Element) setAttributesFromStyle(declarations []*css.Declaration) {
|
|
// for each style declarations
|
|
for _, declaration := range declarations {
|
|
if eltAttr := styleToAttr[declaration.Property]; eltAttr != nil {
|
|
// check if element is allowed for that attribute
|
|
for _, eltAllowed := range eltAttr.elements {
|
|
if element.elt.Nodes[0].Data == eltAllowed {
|
|
element.elt.SetAttr(eltAttr.attr, declaration.Value)
|
|
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// helper
|
|
func computeStyleValue(declarations []*css.Declaration) string {
|
|
result := ""
|
|
|
|
// set style attribute value
|
|
for _, declaration := range declarations {
|
|
if result != "" {
|
|
result += " "
|
|
}
|
|
|
|
result += declaration.StringWithImportant(false)
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
// helper
|
|
func mergeStyleDeclarations(styleRules []*StyleRule, output map[string]*StyleDeclaration) {
|
|
for _, styleRule := range styleRules {
|
|
for _, declaration := range styleRule.Declarations {
|
|
styleDecl := NewStyleDeclaration(styleRule, declaration)
|
|
|
|
if (output[declaration.Property] == nil) || (styleDecl.Specificity() >= output[declaration.Property].Specificity()) {
|
|
output[declaration.Property] = styleDecl
|
|
}
|
|
}
|
|
}
|
|
}
|