| package inliner |
| |
| import ( |
| "sort" |
| |
| "github.com/PuerkitoBio/goquery" |
| |
| "github.com/chris-ramon/douceur/css" |
| "github.com/chris-ramon/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 |
| } |
| } |
| } |
| } |