Pertanyaan Secara otomatis mengonversi Style Sheets ke gaya inline


Tidak perlu khawatir tentang gaya terkait atau gaya hover.

Saya ingin secara otomatis mengonversi file seperti ini

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

ke file seperti ini

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

Saya akan lebih tertarik jika ada parser HTML yang akan melakukan ini.

Alasan saya ingin melakukan ini adalah agar saya bisa menampilkan email yang menggunakan style sheet global tanpa style sheet mereka mengacaukan sisa halaman web saya. Saya juga ingin mengirim gaya yang dihasilkan ke editor teks kaya berbasis web untuk membalas dan pesan asli.


32
2017-12-23 18:46


asal


Jawaban:


Berikut ini solusi java, saya membuatnya dengan Pustaka 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



Menggunakan 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



Setelah berjam-jam mencoba solusi kode java manual yang berbeda dan tidak puas dengan hasil (penanganan masalah penanganan media yang responsif kebanyakan), saya tersandung https://github.com/mdedetrich/java-premailer-wrapper yang berfungsi bagus sebagai solusi java. Perhatikan bahwa Anda mungkin sebenarnya lebih baik menjalankan server "premailer" Anda sendiri. Meskipun ada api publik untuk premailer, saya ingin memiliki contoh sendiri berjalan bahwa saya dapat memukul sekeras yang saya inginkan: https://github.com/TrackIF/premailer-server

Mudah dijalankan di ec2 hanya dengan beberapa klik: 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



Saya belum mencoba ini tetapi sepertinya Anda dapat menggunakan sesuatu seperti Pengurai CSS untuk mendapatkan pohon DOM yang sesuai dengan CSS Anda. Jadi Anda dapat melakukan sesuatu seperti:

  1. Dapatkan cssDOM
  2. Dapatkan htmlDOM (JAXP)
  3. Iterate atas setiap elemen cssDOM dan gunakan xpath untuk mencari dan memasukkan gaya yang benar di htmlDOM Anda.
  4. Ubah htmlDOM menjadi string.

2
2017-12-23 21:20



Saya belum bisa berkomentar tetapi saya menulis sebuah intisari yang berusaha untuk meningkatkan jawaban yang diterima untuk menangani bagian Cascading dari stylesheet mengalir.

Itu tidak bekerja dengan sempurna tetapi hampir di sana. https://gist.github.com/moodysalem/69e2966834a1f79492a9


2
2017-10-18 08:51



Untuk solusi untuk ini Anda mungkin terbaik menggunakan alat perang yang diperkeras seperti yang dari Mailchimp.

Mereka telah membuka alat css inlining mereka di API mereka, lihat di sini: http://apidocs.mailchimp.com/api/1.3/inlinecss.func.php

Jauh lebih bermanfaat daripada formulir web.

Ada juga alat Ruby open source di sini: https://github.com/alexdunae/premailer/

Premailer juga memaparkan API dan formulir web, lihat http://premailer.dialect.ca - Ini disponsori oleh Campaign Monitor yang merupakan salah satu pemain besar lainnya di ruang email.

Saya kira Anda bisa mengintegrasikan Premailer ke dalam aplikasi Java Anda melalui [Jruby] [1], meskipun saya tidak memiliki pengalaman dengan ini.


1
2017-10-22 14:12



Kamu dapat memakai HtmlUnit dan Jsoup. Anda membuat halaman html di browser menggunakan HtmlUnit. Kemudian Anda mendapatkan gaya yang dihitung melalui elemen berkat HtmlUnit. Jsoup hanya di sini untuk memformat output html.

Anda dapat menemukan di sini implementasi sederhana:

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



http://www.mailchimp.com/labs/inlinecss.php

Gunakan tautan di atas. Ini akan menghemat jam waktu Anda dan dibuat khusus untuk template email. Ini alat gratis melalui mailchimp


0
2017-12-25 04:18



Hal semacam ini sering diperlukan untuk aplikasi e-commerce di mana bank / apa pun tidak mengizinkan CSS yang ditautkan, mis. WorldPay.

Tantangan besar tidak begitu banyak konversi sebagai kurangnya warisan. Anda harus secara eksplisit mengatur properti yang diwariskan pada semua tag keturunan. Pengujian sangat penting karena peramban tertentu akan menyebabkan lebih banyak kesedihan daripada yang lain. Anda perlu menambahkan lebih banyak kode sebaris daripada yang Anda butuhkan untuk stylesheet yang ditautkan, misalnya dalam stylesheet yang ditautkan yang Anda butuhkan adalah p { color:red }, tetapi sebaris Anda harus secara eksplisit mengatur warna pada semua paragraf.

Dari pengalaman saya, ini sangat proses manual yang membutuhkan sentuhan ringan dan banyak tweaking dan pengujian lintas-browser untuk mendapatkan yang benar.


0
2017-12-25 11:56