不必担心链接样式或悬停样式。
我想自动转换这样的文件
<html>
<body>
<style>
body{background:#FFC}
p{background:red}
body, p{font-weight:bold}
</style>
<p>...</p>
</body>
</html>
这样的文件
<html>
<body style="background:red;font-weight:bold">
<p style="background:#FFC;font-weight:bold">...</p>
</body>
</html>
如果有一个可以执行此操作的HTML解析器,我会更感兴趣。
我想这样做的原因是我可以显示使用全局样式表的电子邮件,而不会让他们的样式表弄乱我的其余网页。我还想将结果样式发送到基于Web的富文本编辑器以进行回复和原始消息。
答案 0 :(得分:29)
这是java的解决方案,我使用JSoup Library: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.
}
}
答案 1 :(得分:9)
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();
}
答案 2 :(得分:3)
经过几个小时尝试不同的手动java代码解决方案并且对结果不满意(主要是响应式媒体查询处理问题),我偶然发现https://github.com/mdedetrich/java-premailer-wrapper作为java解决方案很有用。请注意,运行自己的“预编译器”服务器实际上可能会更好。虽然premailer有一个公共api,但我想让我自己的实例运行,我可以按照我想要的方式点击: https://github.com/TrackIF/premailer-server
只需点击几下即可轻松在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
答案 3 :(得分:2)
我没试过这个,但看起来你可以使用像CSS parser这样的东西来获得与你的CSS相对应的DOM树。所以你可以这样做:
答案 4 :(得分:2)
我还不能发表评论,但我写了一篇小说,试图提高处理层叠样式表级联部分的接受答案。
它不能很好地工作,但几乎就在那里。 https://gist.github.com/moodysalem/69e2966834a1f79492a9
答案 5 :(得分:2)
您可以使用HtmlUnit
和Jsoup
。您使用HtmlUnit
在浏览器中呈现html页面。然后,由于HtmlUnit
,您将获得通过元素的计算样式。 Jsoup就是在这里格式化html输出。
您可以在这里找到一个简单的实现:
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());
}
}
答案 6 :(得分:1)
对于解决方案,你可能最好使用像Mailchimp那样的经过加固的强化工具。
他们已经在他们的API中打开了他们的css内联工具,请参见此处:http://apidocs.mailchimp.com/api/1.3/inlinecss.func.php
比网络表单更有用。
这里还有一个开源Ruby工具:https://github.com/alexdunae/premailer/
Premailer还公开了API和网络表单,请参阅http://premailer.dialect.ca - 它由Campaign Monitor赞助,他们是电子邮件领域的其他重要参与者之一。
我猜你可以通过[Jruby] [1]将Premailer集成到你的Java应用程序中,虽然我没有这方面的经验。
答案 7 :(得分:1)
CSSBox + jStyleParser库可以按已回答here的方式完成工作。
答案 8 :(得分:0)
http://www.mailchimp.com/labs/inlinecss.php
使用上面的链接。它可以节省您的时间,特别适用于电子邮件模板。这是mailchimp的免费工具
答案 9 :(得分:0)
电子商务应用程序通常需要这种东西,其中银行/任何不允许链接的CSS,例如, WorldPay工作。
最大的挑战不是转换,而是缺乏继承。您必须在所有后代标记上显式设置继承属性。测试至关重要,因为某些浏览器会比其他浏览器更加悲伤。您需要添加比链接样式表所需的内联代码更多的内联代码,例如在链接的样式表中,您只需要p { color:red }
,但是内联您必须在所有段落上明确设置颜色。
根据我的经验,这是一个非常简单的手动过程,需要轻微的调整和大量的调整和跨浏览器测试才能正确。
答案 10 :(得分:0)
我采用前两个答案并采用它们来利用CSS解析器库的功能:
public String inline(String html, String styles) throws IOException {
Document document = Jsoup.parse(html);
CSSRuleList ruleList = getCssRules(styles);
for (int i = 0; i < ruleList.getLength(); i++) {
CSSRule rule = ruleList.item(i);
if (rule instanceof CSSStyleRule) {
CSSStyleRule styleRule = (CSSStyleRule) rule;
String selector = styleRule.getSelectorText();
Elements elements = document.select(selector);
for (final Element element : elements) {
applyRuleToElement(element, styleRule);
}
}
}
removeClasses(document);
return document.html();
}
private CSSRuleList getCssRules(String styles) throws IOException {
CSSOMParser parser = new CSSOMParser(new SACParserCSS3());
CSSStyleSheet styleSheet = parser.parseStyleSheet(new InputSource(new StringReader(styles)), null, null);
CSSRuleList list = styleSheet.getCssRules();
return list;
}
private void applyRuleToElement(Element element, CSSStyleRule rule){
String elementStyleString = element.attr("style");
CSSStyleDeclarationImpl elementStyleDeclaration = new CSSStyleDeclarationImpl();
elementStyleDeclaration.setCssText(elementStyleString);
CSSStyleDeclarationImpl ruleStyleDeclaration = (CSSStyleDeclarationImpl)rule.getStyle();
for(Property p : ruleStyleDeclaration.getProperties()){
elementStyleDeclaration.addProperty(p);
}
String cssText = elementStyleDeclaration.getCssText();
element.attr("style", cssText);
}
private void removeClasses(Document document){
Elements elements = document.getElementsByAttribute("class");
elements.removeAttr("class");
}
也许可以通过使用像https://github.com/phax/ph-css这样的CSS解析器来进一步改进它?