java 自动将样式表转换为内联样式

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/4521557/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-30 06:44:21  来源:igfitidea点击:

Automatically convert Style Sheets to inline style

javacsshtml-parsing

提问by Bryan Field

Don't have to worry about linked style or hover style.

不必担心链接样式或悬停样式。

I want to automatically convert files like this

我想像这样自动转换文件

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

to files like this

到这样的文件

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

I would be even more interested if there was an HTML parser that would do this.

如果有一个 HTML 解析器可以做到这一点,我会更感兴趣。

The reason I want to do this is so I can display emails that use global style sheets without their style sheets messing up the rest of my web page. I also would like to send the resulting style to web based rich text editor for reply and original message.

我想这样做的原因是,我可以显示使用全局样式表的电子邮件,而它们的样式表不会弄乱我网页的其余部分。我还想将生成的样式发送到基于 Web 的富文本编辑器以进行回复和原始消息。

回答by Grekz

Here is a solution on java, I made it with the JSoup Library: http://jsoup.org/download

这是一个关于 java 的解决方案,我用 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.
    }
}

回答by jnr

Using jsoup+ cssparser:

使用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();
}

回答by Adam Lane

After hours of trying different manual java code solutions and not being satisfied with results (responsive media query handling issues mostly), I stumbled upon https://github.com/mdedetrich/java-premailer-wrapperwhich works great as a java solution. Note that you might actually be better off running your own "premailer" server. While there is a public api to premailer, I wanted to have my own instance running that I can hit as hard as I want: https://github.com/TrackIF/premailer-server

经过数小时的尝试不同的手动 Java 代码解决方案并且对结果不满意(主要是响应式媒体查询处理问题),我偶然发现了https://github.com/mdedetrich/java-premailer-wrapper,它非常适合作为 Java 解决方案。请注意,您实际上最好运行自己的“premailer”服务器。虽然 premailer 有一个公共 api,但我想运行我自己的实例,我可以随心所欲地运行:https: //github.com/TrackIF/premailer-server

Easy to run on ec2 with just a few clicks: https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/create_deploy_Ruby_sinatra.html

只需点击几下即可轻松在 ec2 上运行: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

回答by Adam Lane

I can't yet comment but I wrote a gist that attempted to enhance the accepted answer to handle the cascading part of cascading stylesheets.

我还不能发表评论,但我写了一个要点,试图增强公认的答案来处理级联样式表的级联部分。

It doesn't work perfectly but it is almost there. https://gist.github.com/moodysalem/69e2966834a1f79492a9

它不能完美地工作,但它几乎就在那里。 https://gist.github.com/moodysalem/69e2966834a1f79492a9

回答by Abdullah Jibaly

I haven't tried this but looks like you can use something like CSS parserto get a DOM tree corresponding to your CSS. So you can do something like:

我还没有尝试过这个,但看起来你可以使用CSS 解析器之类的东西来获取与你的 CSS 相对应的 DOM 树。因此,您可以执行以下操作:

  1. Obtain cssDOM
  2. Obtain htmlDOM (JAXP)
  3. Iterate over each cssDOM element and use xpath to locate and insert the correct style in your htmlDOM.
  4. Convert htmlDOM to string.
  1. 获取cssDOM
  2. 获取 htmlDOM (JAXP)
  3. 迭代每个 cssDOM 元素并使用 xpath 在 htmlDOM 中定位和插入正确的样式。
  4. 将 htmlDOM 转换为字符串。

回答by Fabrice

You can use HtmlUnitand Jsoup. You render the html page in the browser using HtmlUnit. Then you get the computed styles going through the elements thanks to HtmlUnit. Jsoup is just here to format the html output.

您可以使用HtmlUnitJsoup。您使用 .html 在浏览器中呈现 html 页面HtmlUnit。然后,由于HtmlUnit. Jsoup 只是在这里格式化 html 输出。

You can find here a simple implementation :

你可以在这里找到一个简单的实现:

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());
   }
}

回答by radkovo

The CSSBox + jStyleParser libraries can do the job as already answered here.

CSSBox + jStyleParser 库可以完成这里已经回答的工作。

回答by Jed Watson

For a solution to this you're probably best using a battle hardened tool like the one from Mailchimp.

要解决这个问题,您可能最好使用像 Mailchimp 这样的久经沙场的工具。

They've have opened up their css inlining tool in their API, see here: http://apidocs.mailchimp.com/api/1.3/inlinecss.func.php

他们已经在他们的 API 中打开了他们的 css 内联工具,请参见此处:http: //apidocs.mailchimp.com/api/1.3/inlinecss.func.php

Much more useful than a web form.

比网络表单有用得多。

There's also an open source Ruby tool here: https://github.com/alexdunae/premailer/

这里还有一个开源 Ruby 工具:https: //github.com/alexdunae/premailer/

Premailer also exposes an API and web form, see http://premailer.dialect.ca- it's sponsored by Campaign Monitor who are one of the other big players in the email space.

Premailer 还公开了一个 API 和 Web 表单,请参阅http://premailer.dialect.ca- 它由 Campaign Monitor 赞助,他们是电子邮件领域的其他大玩家之一。

I'm guessing you could integrate Premailer into your Java app via [Jruby][1], although I have no experience with this.

我猜您可以通过 [Jruby][1] 将 Premailer 集成到您的 Java 应用程序中,尽管我没有这方面的经验。

回答by owilde1900

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

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

Use that link above. It will save hours of your time and is made especially for email templates. It's a free tool by mailchimp

使用上面的链接。它将节省您数小时的时间,并且专为电子邮件模板而设计。这是 mailchimp 的免费工具

回答by GlennG

This kind of thing is often required for e-commerce applications where the bank/whatever doesn't allow linked CSS, e.g. WorldPay.

对于银行/任何不允许链接 CSS(例如 WorldPay)的电子商务应用程序,这种事情通常是必需的。

The big challenge isn't so much the conversion as the lack of inheritance. You have to explicitly set inherited properties on all descendant tags. Testing is vital as certain browsers will cause more grief than others. You will need to add a lot more inline code than you need for a linked stylesheet, for example in a linked stylesheet all you need is p { color:red }, but inline you have to explicitly set the color on all paragraphs.

最大的挑战与其说是转换,不如说是缺乏继承。您必须在所有后代标签上显式设置继承属性。测试至关重要,因为某些浏览器会比其他浏览器造成更多的痛苦。您将需要添加比链接样式表更多的内联代码,例如,在链接样式表中,您只需要p { color:red },但内联您必须明确设置所有段落的颜色。

From my experience, it's very much a manual process that requires a light touch and a lot of tweaking and cross-browser testing to get right.

根据我的经验,这在很大程度上是一个手动过程,需要轻触、大量调整和跨浏览器测试才能正确。