mirror of
https://github.com/M66B/FairEmail.git
synced 2025-03-03 02:05:36 +00:00
Added style sheet parser
This commit is contained in:
parent
d464d858f7
commit
2deb6e18fd
4 changed files with 119 additions and 4 deletions
|
@ -22,3 +22,5 @@ FairEmail uses:
|
|||
* [AppAuth for Android](https://github.com/openid/AppAuth-Android). Copyright 2015 The AppAuth for Android Authors. All Rights Reserved. [Apache License 2.0](https://github.com/openid/AppAuth-Android/blob/master/LICENSE).
|
||||
* [JCharset](http://www.freeutils.net/source/jcharset/). Copyright (C) 1989, 1991 Free Software Foundation, Inc. [GNU General Public License](http://www.freeutils.net/source/jcharset/#license).
|
||||
* [Material design icons](https://github.com/google/material-design-icons). Copyright ???. [Apache license version 2.0](https://github.com/google/material-design-icons#user-content-license).
|
||||
* [CSS Parser](http://cssparser.sourceforge.net/). Copyright © 1999–2019. All rights reserved. [Apache License, Version 2.0](http://cssparser.sourceforge.net/licenses.html).
|
||||
* [Java™ Architecture for XML Binding](https://github.com/eclipse-ee4j/jaxb-ri). Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. [GNU General Public License Version 2](https://github.com/eclipse-ee4j/jaxb-ri/blob/master/jaxb-ri/LICENSE.md).
|
||||
|
|
|
@ -245,6 +245,8 @@ dependencies {
|
|||
def billingclient_version = "2.2.0"
|
||||
def javamail_version = "1.6.5"
|
||||
def jsoup_version = "1.13.1"
|
||||
def css_version = "0.9.27"
|
||||
def jax_version = "2.3.0-jaxb-1.0.6"
|
||||
def dnsjava_version = "2.1.9"
|
||||
def openpgp_version = "12.0"
|
||||
def requery_version = "3.31.0"
|
||||
|
@ -341,6 +343,14 @@ dependencies {
|
|||
// https://jsoup.org/news/
|
||||
implementation "org.jsoup:jsoup:$jsoup_version"
|
||||
|
||||
// http://cssparser.sourceforge.net/
|
||||
// https://mvnrepository.com/artifact/net.sourceforge.cssparser/cssparser
|
||||
implementation "net.sourceforge.cssparser:cssparser:$css_version"
|
||||
|
||||
// https://github.com/eclipse-ee4j/jaxb-ri
|
||||
// https://mvnrepository.com/artifact/org.w3c/dom
|
||||
implementation "org.w3c:dom:$jax_version"
|
||||
|
||||
// http://www.dnsjava.org/
|
||||
// https://mvnrepository.com/artifact/dnsjava/dnsjava
|
||||
implementation "dnsjava:dnsjava:$dnsjava_version"
|
||||
|
|
|
@ -22,3 +22,5 @@ FairEmail uses:
|
|||
* [AppAuth for Android](https://github.com/openid/AppAuth-Android). Copyright 2015 The AppAuth for Android Authors. All Rights Reserved. [Apache License 2.0](https://github.com/openid/AppAuth-Android/blob/master/LICENSE).
|
||||
* [JCharset](http://www.freeutils.net/source/jcharset/). Copyright (C) 1989, 1991 Free Software Foundation, Inc. [GNU General Public License](http://www.freeutils.net/source/jcharset/#license).
|
||||
* [Material design icons](https://github.com/google/material-design-icons). Copyright ???. [Apache license version 2.0](https://github.com/google/material-design-icons#user-content-license).
|
||||
* [CSS Parser](http://cssparser.sourceforge.net/). Copyright © 1999–2019. All rights reserved. [Apache License, Version 2.0](http://cssparser.sourceforge.net/licenses.html).
|
||||
* [Java™ Architecture for XML Binding](https://github.com/eclipse-ee4j/jaxb-ri). Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. [GNU General Public License Version 2](https://github.com/eclipse-ee4j/jaxb-ri/blob/master/jaxb-ri/LICENSE.md).
|
||||
|
|
|
@ -45,6 +45,12 @@ import androidx.core.text.HtmlCompat;
|
|||
import androidx.core.util.PatternsCompat;
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
import com.steadystate.css.dom.CSSStyleRuleImpl;
|
||||
import com.steadystate.css.parser.CSSOMParser;
|
||||
import com.steadystate.css.parser.SACParserCSS3;
|
||||
import com.steadystate.css.parser.selectors.ClassConditionImpl;
|
||||
import com.steadystate.css.parser.selectors.ConditionalSelectorImpl;
|
||||
|
||||
import org.jsoup.nodes.Attribute;
|
||||
import org.jsoup.nodes.Comment;
|
||||
import org.jsoup.nodes.Document;
|
||||
|
@ -56,12 +62,21 @@ import org.jsoup.safety.Whitelist;
|
|||
import org.jsoup.select.NodeFilter;
|
||||
import org.jsoup.select.NodeTraversor;
|
||||
import org.jsoup.select.NodeVisitor;
|
||||
import org.w3c.css.sac.CSSException;
|
||||
import org.w3c.css.sac.CSSParseException;
|
||||
import org.w3c.css.sac.ErrorHandler;
|
||||
import org.w3c.css.sac.InputSource;
|
||||
import org.w3c.css.sac.Selector;
|
||||
import org.w3c.dom.css.CSSRule;
|
||||
import org.w3c.dom.css.CSSRuleList;
|
||||
import org.w3c.dom.css.CSSStyleSheet;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.StringReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
@ -74,6 +89,7 @@ import java.util.regex.Pattern;
|
|||
|
||||
import static androidx.core.text.HtmlCompat.FROM_HTML_SEPARATOR_LINE_BREAK_LIST_ITEM;
|
||||
import static androidx.core.text.HtmlCompat.TO_HTML_PARAGRAPH_LINES_CONSECUTIVE;
|
||||
import static org.w3c.css.sac.Condition.SAC_CLASS_CONDITION;
|
||||
|
||||
public class HtmlHelper {
|
||||
private static final int PREVIEW_SIZE = 500; // characters
|
||||
|
@ -356,8 +372,42 @@ public class HtmlHelper {
|
|||
.text(context.getString(R.string.title_show_full));
|
||||
}
|
||||
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/style
|
||||
CSSStyleSheet sheet = null;
|
||||
for (Element style : parsed.head().select("style")) {
|
||||
Log.i("Style=" + style.data());
|
||||
try {
|
||||
InputSource source = new InputSource(new StringReader(style.data()));
|
||||
CSSOMParser parser = new CSSOMParser(new SACParserCSS3());
|
||||
parser.setErrorHandler(new ErrorHandler() {
|
||||
@Override
|
||||
public void warning(CSSParseException ex) throws CSSException {
|
||||
Log.w(ex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void error(CSSParseException ex) throws CSSException {
|
||||
Log.e(ex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fatalError(CSSParseException ex) throws CSSException {
|
||||
Log.e(ex);
|
||||
}
|
||||
});
|
||||
|
||||
// TODO: media queries
|
||||
CSSStyleSheet s = parser.parseStyleSheet(source, null, null);
|
||||
if (s.getMedia() != null && "all".equals(s.getMedia().getMediaText()))
|
||||
sheet = s;
|
||||
} catch (Throwable ex) {
|
||||
Log.w(ex);
|
||||
}
|
||||
}
|
||||
|
||||
Whitelist whitelist = Whitelist.relaxed()
|
||||
.addTags("hr", "abbr", "big", "font", "dfn", "del", "s", "tt")
|
||||
.addAttributes(":all", "class")
|
||||
.addAttributes(":all", "style")
|
||||
.addAttributes("font", "size")
|
||||
.removeTags("col", "colgroup", "thead", "tbody")
|
||||
|
@ -412,16 +462,46 @@ public class HtmlHelper {
|
|||
|
||||
// Sanitize styles
|
||||
for (Element element : document.select("*")) {
|
||||
String clazz = element.attr("class");
|
||||
String style = element.attr("style");
|
||||
|
||||
// Process class
|
||||
if (!TextUtils.isEmpty(clazz) && sheet != null) {
|
||||
CSSRuleList rules = sheet.getCssRules();
|
||||
for (int i = 0; rules != null && i < rules.getLength(); i++) {
|
||||
CSSRule rule = rules.item(i);
|
||||
if (rule.getType() == CSSRule.STYLE_RULE) {
|
||||
CSSStyleRuleImpl srule = (CSSStyleRuleImpl) rule;
|
||||
for (int j = 0; j < srule.getSelectors().getLength(); j++) {
|
||||
Selector selector = srule.getSelectors().item(j);
|
||||
switch (selector.getSelectorType()) {
|
||||
case Selector.SAC_ANY_NODE_SELECTOR:
|
||||
style = mergeStyles(srule.getStyle().getCssText(), style);
|
||||
break;
|
||||
case Selector.SAC_CONDITIONAL_SELECTOR:
|
||||
ConditionalSelectorImpl cselector = (ConditionalSelectorImpl) selector;
|
||||
if (cselector.getCondition().getConditionType() == SAC_CLASS_CONDITION) {
|
||||
ClassConditionImpl ccondition = (ClassConditionImpl) cselector.getCondition();
|
||||
if (clazz.equals(ccondition.getValue()))
|
||||
style = mergeStyles(srule.getStyle().getCssText(), style);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Process style
|
||||
if (!TextUtils.isEmpty(style)) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
String[] params = style.split(";");
|
||||
for (String param : params) {
|
||||
int semi = param.indexOf(':');
|
||||
if (semi > 0) {
|
||||
String key = param.substring(0, semi).trim().toLowerCase(Locale.ROOT);
|
||||
String value = param.substring(semi + 1).toLowerCase(Locale.ROOT)
|
||||
int colon = param.indexOf(':');
|
||||
if (colon > 0) {
|
||||
String key = param.substring(0, colon).trim().toLowerCase(Locale.ROOT);
|
||||
String value = param.substring(colon + 1).toLowerCase(Locale.ROOT)
|
||||
.replace("!important", "")
|
||||
.trim()
|
||||
.replaceAll("\\s+", " ");
|
||||
|
@ -845,6 +925,27 @@ public class HtmlHelper {
|
|||
return document;
|
||||
}
|
||||
|
||||
private static String mergeStyles(String base, String style) {
|
||||
Map<String, String> result = new HashMap<>();
|
||||
|
||||
List<String> params = new ArrayList<>();
|
||||
if (!TextUtils.isEmpty(base))
|
||||
params.addAll(Arrays.asList(base.split(";")));
|
||||
if (!TextUtils.isEmpty(style))
|
||||
params.addAll(Arrays.asList(style.split(";")));
|
||||
|
||||
for (String param : params) {
|
||||
int colon = param.indexOf(':');
|
||||
if (colon > 0) {
|
||||
String key = param.substring(0, colon).trim().toLowerCase(Locale.ROOT);
|
||||
result.put(key, param);
|
||||
} else
|
||||
Log.w("Invalid style param=" + param);
|
||||
}
|
||||
|
||||
return TextUtils.join(";", result.values());
|
||||
}
|
||||
|
||||
private static Integer getFontWeight(String value) {
|
||||
if (TextUtils.isEmpty(value))
|
||||
return null;
|
||||
|
|
Loading…
Reference in a new issue