Pregunta Convierte automáticamente hojas de estilo al estilo en línea


No tiene que preocuparse por el estilo vinculado o el estilo de desplazamiento.

Quiero convertir automáticamente archivos como este

<html>
<body>
<style>
body{background:#FFC}
p{background:red}
body, p{font-weight:bold}
</style>
<p>...</p>
</body>
</html>

a archivos como este

<html>
<body style="background:red;font-weight:bold">
<p style="background:#FFC;font-weight:bold">...</p>
</body>
</html>

Estaría aún más interesado si hubiera un analizador de HTML que haría esto.

La razón por la que quiero hacer esto es para poder mostrar mensajes de correo electrónico que usan hojas de estilo globales sin que sus hojas de estilo arruinen el resto de mi página web. También me gustaría enviar el estilo resultante al editor de texto enriquecido basado en la web para la respuesta y el mensaje original.


32
2017-12-23 18:46


origen


Respuestas:


Aquí hay una solución sobre Java, lo hice con la Biblioteca JSoup: http://jsoup.org/download

import java.io.IOException;
import java.util.StringTokenizer;

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

public class AutomaticCssInliner {
    /**
     * Hecho por Grekz, http://grekz.wordpress.com
     */
    public static void main(String[] args) throws IOException {
        final String style = "style";
        final String html = "<html>" + "<body> <style>"
                + "body{background:#FFC} \n p{background:red}"
                + "body, p{font-weight:bold} </style>"
                + "<p>...</p> </body> </html>";
        // Document doc = Jsoup.connect("http://mypage.com/inlineme.php").get();
        Document doc = Jsoup.parse(html);
        Elements els = doc.select(style);// to get all the style elements
        for (Element e : els) {
            String styleRules = e.getAllElements().get(0).data().replaceAll(
                    "\n", "").trim(), delims = "{}";
            StringTokenizer st = new StringTokenizer(styleRules, delims);
            while (st.countTokens() > 1) {
                String selector = st.nextToken(), properties = st.nextToken();
                Elements selectedElements = doc.select(selector);
                for (Element selElem : selectedElements) {
                    String oldProperties = selElem.attr(style);
                    selElem.attr(style,
                            oldProperties.length() > 0 ? concatenateProperties(
                                    oldProperties, properties) : properties);
                }
            }
            e.remove();
        }
        System.out.println(doc);// now we have the result html without the
        // styles tags, and the inline css in each
        // element
    }

    private static String concatenateProperties(String oldProp, String newProp) {
        oldProp = oldProp.trim();
        if (!newProp.endsWith(";"))
           newProp += ";";
        return newProp + oldProp; // The existing (old) properties should take precedence.
    }
}

28
2017-12-23 19:14



Utilizando jsoup + cssparser:

private static final String STYLE_ATTR = "style";
private static final String CLASS_ATTR = "class";

public String inlineStyles(String html, File cssFile, boolean removeClasses) throws IOException {
    Document document = Jsoup.parse(html);
    CSSOMParser parser = new CSSOMParser(new SACParserCSS3());
    InputSource source = new InputSource(new FileReader(cssFile));
    CSSStyleSheet stylesheet = parser.parseStyleSheet(source, null, null);

    CSSRuleList ruleList = stylesheet.getCssRules();
    Map<Element, Map<String, String>> allElementsStyles = new HashMap<>();
    for (int ruleIndex = 0; ruleIndex < ruleList.getLength(); ruleIndex++) {
        CSSRule item = ruleList.item(ruleIndex);
        if (item instanceof CSSStyleRule) {
            CSSStyleRule styleRule = (CSSStyleRule) item;
            String cssSelector = styleRule.getSelectorText();
            Elements elements = document.select(cssSelector);
            for (Element element : elements) {
                Map<String, String> elementStyles = allElementsStyles.computeIfAbsent(element, k -> new LinkedHashMap<>());
                CSSStyleDeclaration style = styleRule.getStyle();
                for (int propertyIndex = 0; propertyIndex < style.getLength(); propertyIndex++) {
                    String propertyName = style.item(propertyIndex);
                    String propertyValue = style.getPropertyValue(propertyName);
                    elementStyles.put(propertyName, propertyValue);
                }
            }
        }
    }

    for (Map.Entry<Element, Map<String, String>> elementEntry : allElementsStyles.entrySet()) {
        Element element = elementEntry.getKey();
        StringBuilder builder = new StringBuilder();
        for (Map.Entry<String, String> styleEntry : elementEntry.getValue().entrySet()) {
            builder.append(styleEntry.getKey()).append(":").append(styleEntry.getValue()).append(";");
        }
        builder.append(element.attr(STYLE_ATTR));
        element.attr(STYLE_ATTR, builder.toString());
        if (removeClasses) {
            element.removeAttr(CLASS_ATTR);
        }
    }

    return document.html();
}

9
2018-02-13 15:07



Después de horas de probar diferentes soluciones de código Java manual y no estar satisfecho con los resultados (problemas de manejo de consultas de medios de comunicación en su mayoría), me encontré con https://github.com/mdedetrich/java-premailer-wrapper que funciona muy bien como una solución java. Tenga en cuenta que quizás sea mejor que ejecute su propio servidor "premailer". Si bien hay una API pública para el premailer, quería tener mi propia instancia ejecutándose para poder pegar tan fuerte como quisiera: https://github.com/TrackIF/premailer-server

Fácil de ejecutar en ec2 con solo unos pocos clics: https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/create_deploy_Ruby_sinatra.html

git clone https://github.com/Enalmada/premailer-server
cd premailer-server
eb init  (choose latest ruby)
eb create premailer-server
eb deploy
curl --data "html=<your html>" http://your.eb.url

4
2017-08-19 08:59



No lo he intentado pero parece que puedes usar algo como Analizador de CSS para obtener un árbol DOM correspondiente a su CSS. Entonces puedes hacer algo como:

  1. Obtener cssDOM
  2. Obtener htmlDOM (JAXP)
  3. Itere sobre cada elemento cssDOM y use xpath para ubicar e insertar el estilo correcto en su htmlDOM.
  4. Convierta htmlDOM en cadena.

2
2017-12-23 21:20



Todavía no puedo comentar, pero escribí una idea que intentó mejorar la respuesta aceptada para manejar la parte en cascada de las hojas de estilo en cascada.

No funciona a la perfección, pero casi está ahí. https://gist.github.com/moodysalem/69e2966834a1f79492a9


2
2017-10-18 08:51



Para encontrar una solución a esto, probablemente sea mejor utilizar una herramienta de batalla endurecida como la de Mailchimp.

Han abierto su herramienta CSS inline en su API, mira aquí: http://apidocs.mailchimp.com/api/1.3/inlinecss.func.php

Mucho más útil que un formulario web.

También hay una herramienta Ruby de código abierto aquí: https://github.com/alexdunae/premailer/

Premailer también expone una API y un formulario web, ver http://premailer.dialect.ca - está patrocinado por Campaign Monitor, que es uno de los otros grandes jugadores en el espacio de correo electrónico.

Supongo que podrías integrar Premailer en tu aplicación Java a través de [Jruby] [1], aunque no tengo experiencia en esto.


1
2017-10-22 14:12



Puedes usar HtmlUnit y Jsoup. Usted renderiza la página html en el navegador utilizando HtmlUnit. Luego obtienes los estilos computados pasando por los elementos gracias a HtmlUnit. Jsoup solo está aquí para formatear la salida html.

Puede encontrar aquí una implementación simple:

public final class CssInliner {
   private static final Logger log = Logger.getLogger(CssInliner.class);

   private CssInliner() {
   }

   public static CssInliner make() {
      return new CssInliner();
   }

   /**
    * Main method
    *
    * @param html html to inline
    *
    * @return inlined html
    */
   public String inline(String html) throws IOException {

      try (WebClient webClient = new WebClient()) {

         HtmlPage htmlPage = getHtmlPage(webClient, html);
         Window window = webClient.getCurrentWindow().getScriptableObject();

         for (HtmlElement htmlElement : htmlPage.getHtmlElementDescendants()) {
            applyComputedStyle(window, htmlElement);
         }

         return outputCleanHtml(htmlPage);
      }
   }

   /**
    * Output the HtmlUnit page to a clean html. Remove the old global style tag
    * that we do not need anymore. This in order to simplify of the tests of the
    * output.
    *
    * @param htmlPage
    *
    * @return
    */
   private String outputCleanHtml(HtmlPage htmlPage) {
      Document doc = Jsoup.parse(htmlPage.getDocumentElement().asXml());
      Element globalStyleTag = doc.selectFirst("html style");
      if (globalStyleTag != null) {
         globalStyleTag.remove();
      }
      doc.outputSettings().syntax(Syntax.html);
      return doc.html();
   }

   /**
    * Modify the html elements by adding an style attribute to each element
    *
    * @param window
    * @param htmlElement
    */
   private void applyComputedStyle(Window window, HtmlElement htmlElement) {

      HTMLElement pj = htmlElement.getScriptableObject();
      ComputedCSSStyleDeclaration cssStyleDeclaration = window.getComputedStyle(pj, null);

      Map<String, StyleElement> map = getStringStyleElementMap(cssStyleDeclaration);
      // apply style element to html
      if (!map.isEmpty()) {
         htmlElement.writeStyleToElement(map);
      }
   }

   private Map<String, StyleElement> getStringStyleElementMap(ComputedCSSStyleDeclaration cssStyleDeclaration) {
      Map<String, StyleElement> map = new HashMap<>();
      for (Definition definition : Definition.values()) {
         String style = cssStyleDeclaration.getStyleAttribute(definition, false);

         if (StringUtils.isNotBlank(style)) {
            map.put(definition.getAttributeName(),
                    new StyleElement(definition.getAttributeName(),
                                     style,
                                     "",
                                     SelectorSpecificity.DEFAULT_STYLE_ATTRIBUTE));
         }

      }
      return map;
   }

   private HtmlPage getHtmlPage(WebClient webClient, String html) throws IOException {
      URL url = new URL("http://tinubuinliner/" + Math.random());
      StringWebResponse stringWebResponse = new StringWebResponse(html, url);

      return HTMLParser.parseHtml(stringWebResponse, webClient.getCurrentWindow());
   }
}

1
2018-05-24 16:37