diff --git a/.classpath b/.classpath deleted file mode 100644 index 8e189f0..0000000 --- a/.classpath +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/.project b/.project deleted file mode 100644 index 9f35eb7..0000000 --- a/.project +++ /dev/null @@ -1,36 +0,0 @@ - - - CSSInliner - - - - - - org.eclipse.wst.common.project.facet.core.builder - - - - - org.eclipse.jdt.core.javabuilder - - - - - org.eclipse.wst.validation.validationbuilder - - - - - org.eclipse.m2e.core.maven2Builder - - - - - - org.eclipse.jem.workbench.JavaEMFNature - org.eclipse.wst.common.modulecore.ModuleCoreNature - org.eclipse.m2e.core.maven2Nature - org.eclipse.jdt.core.javanature - org.eclipse.wst.common.project.facet.core.nature - - diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs deleted file mode 100644 index 107056a..0000000 --- a/.settings/org.eclipse.jdt.core.prefs +++ /dev/null @@ -1,12 +0,0 @@ -eclipse.preferences.version=1 -org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled -org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 -org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve -org.eclipse.jdt.core.compiler.compliance=1.6 -org.eclipse.jdt.core.compiler.debug.lineNumber=generate -org.eclipse.jdt.core.compiler.debug.localVariable=generate -org.eclipse.jdt.core.compiler.debug.sourceFile=generate -org.eclipse.jdt.core.compiler.problem.assertIdentifier=error -org.eclipse.jdt.core.compiler.problem.enumIdentifier=error -org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning -org.eclipse.jdt.core.compiler.source=1.6 diff --git a/.settings/org.eclipse.m2e.core.prefs b/.settings/org.eclipse.m2e.core.prefs deleted file mode 100644 index 84d23c8..0000000 --- a/.settings/org.eclipse.m2e.core.prefs +++ /dev/null @@ -1,4 +0,0 @@ -activeProfiles=nexus -eclipse.preferences.version=1 -resolveWorkspaceProjects=true -version=1 diff --git a/.settings/org.eclipse.wst.common.component b/.settings/org.eclipse.wst.common.component deleted file mode 100644 index 5439c8a..0000000 --- a/.settings/org.eclipse.wst.common.component +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/.settings/org.eclipse.wst.common.project.facet.core.xml b/.settings/org.eclipse.wst.common.project.facet.core.xml deleted file mode 100644 index 5c9bd75..0000000 --- a/.settings/org.eclipse.wst.common.project.facet.core.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/.settings/org.jboss.ide.eclipse.as.core.prefs b/.settings/org.jboss.ide.eclipse.as.core.prefs deleted file mode 100644 index cf3aa3a..0000000 --- a/.settings/org.jboss.ide.eclipse.as.core.prefs +++ /dev/null @@ -1,2 +0,0 @@ -eclipse.preferences.version=1 -org.jboss.ide.eclipse.as.core.singledeployable.deployableList= diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 6b0b127..0000000 --- a/LICENSE +++ /dev/null @@ -1,203 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - diff --git a/NOTICE b/NOTICE deleted file mode 100644 index 5fe1301..0000000 --- a/NOTICE +++ /dev/null @@ -1,24 +0,0 @@ -CSSInliner -Copyright 2012 Leonty Belskiy, leonti@eleonti.com. - - - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. - - - -This project includes PMD, Copyright (c) 2002-2009, InfoEther, Inc. -All rights reserved. http://pmd.sourceforge.net/ - -This product includes software developed in part by support from the Defense -Advanced Research Project Agency (DARPA) diff --git a/README.md b/README.md index 579440c..642018d 100644 --- a/README.md +++ b/README.md @@ -12,11 +12,17 @@ If used for email, 'class' attributes can be removed: ##Build -The dependency com.osbcp.cssparser:cssparser:1.4 is not available -in maven central. Deploy the jar from lib/osbcp-css-parser-1.4.jar -to your own maven repository so that it fits the dependency -declaration in the pom. - +The dependency com.osbcp:cssparser:1.5 is now available +in maven central. + +##Other similar libraries +- CSS parsers + - [https://github.com/phax/ph-css](https://github.com/phax/ph-css) + - [http://stackoverflow.com/questions/1513587/looking-for-a-css-parser-in-java](http://stackoverflow.com/questions/1513587/looking-for-a-css-parser-in-java) + - [http://sourceforge.net/p/cssparser/](http://sourceforge.net/p/cssparser/) +- Other resources + - [inline vs internal vs external css](https://vineetgupta22.wordpress.com/2011/07/09/inline-vs-internal-vs-external-css/) + ##Changes v1.0 2012-02-05 Leonti Bielski @@ -30,4 +36,3 @@ Copyright 2012 Leonti Bielski, leonti.me Licensed under the Apache License, Version 2.0 - diff --git a/lib/osbcp-css-parser-1.4.jar b/lib/osbcp-css-parser-1.4.jar deleted file mode 100644 index 72ca813..0000000 Binary files a/lib/osbcp-css-parser-1.4.jar and /dev/null differ diff --git a/pom.xml b/pom.xml index 2987b2f..1327883 100644 --- a/pom.xml +++ b/pom.xml @@ -1,22 +1,57 @@ - + 4.0.0 com.leonty CSSInliner - 1.0 - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.0 - - 1.7 - 1.7 - - - - + 1.5-SNAPSHOT + Java CSS Inliner + Utility to inline css styles in html document to be used in emails. + ${github.url} + + + The Apache License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + + + + + Leonti + https://github.com/Leonti + + + Tocco AG + https://www.tocco.ch + + + Costin Grigore + raisercostin@gmail.com + brainlight + http://raisercostin.org + + + + 1.7 + 1.7 + UTF-8 + raisercostin + CSSInliner + https://github.com/${github.user}/${github.repo} + raisercostin + maven + CSSInliner + + + + scm:git:${github.url}.git + scm:git:${github.url}.git + ${github.url} + HEAD + + + + bintray + https://api.bintray.com/maven/${bintray.user}/${bintray.repo}/${bintray.package} + + org.jsoup @@ -24,14 +59,95 @@ 1.8.1 - com.osbcp.cssparser + com.osbcp cssparser - 1.4 + 1.7 + + + ch.qos.logback + logback-classic + 1.1.3 org.testng testng 6.8.8 + test + + + junit + junit + 4.8.2 + test + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.3 + + ${source.java.version} + ${target.java.version} + ${project.build.sourceEncoding} + + + + maven-release-plugin + 2.5.2 + + false + release + true + + + + + + + release + + + + maven-source-plugin + + + attach-sources + + jar + + + + + + maven-javadoc-plugin + + + attach-javadocs + + jar + + + + + + + + + + + jcenter-bintray + Bintray JCenter Maven Repository + default + https://jcenter.bintray.com/ + + true + + + false + + + \ No newline at end of file diff --git a/src/main/java/com/leonty/CSSInliner.java b/src/main/java/com/leonty/CSSInliner.java index 3cbcb45..6fec2c5 100644 --- a/src/main/java/com/leonty/CSSInliner.java +++ b/src/main/java/com/leonty/CSSInliner.java @@ -1,5 +1,10 @@ package com.leonty; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -7,37 +12,70 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.jsoup.select.Elements; +import org.jsoup.select.Selector.SelectorParseException; +import org.slf4j.LoggerFactory; import com.osbcp.cssparser.CSSParser; import com.osbcp.cssparser.PropertyValue; import com.osbcp.cssparser.Rule; public class CSSInliner { + private static final org.slf4j.Logger LOG = LoggerFactory + .getLogger(CSSInliner.class); public static String inlineCss(String html, String css) throws Exception { return inlineCss(html, css, false); } - - public static String inlineCss(String html, String css, Boolean removeAttributes) throws Exception { + + /** Returns the cleaned html that will be used for inlining css. */ + public static String cleanup(String html) { + Document doc = Jsoup.parse(html); + return doc.html(); + } + + public static String inlineCss(String html, String css, + Boolean removeAttributes) throws Exception { + Document doc = Jsoup.parse(html); + HashMap> selected = new HashMap>(); + css += getInternalStyles(doc); + extractSelectors(css, doc, selected); + mergeCss(removeAttributes, selected); + return doc.html(); + } + + public static String inlineCss(String html, Boolean removeAttributes) + throws Exception { Document doc = Jsoup.parse(html); HashMap> selected = new HashMap>(); - - css += getDocumentStyles(doc); - extractSelectors(css, doc, selected); - - mergeCss(removeAttributes, selected); - + String css = readExternalStyles(doc); + remvoeExternalStyles(doc); + css += getInternalStyles(doc); + extractSelectors(css, doc, selected); + mergeCss(removeAttributes, selected); + return doc.html(); + } + + public static String internalCss(String html) throws IOException { + Document doc = Jsoup.parse(html); + String css = readExternalStyles(doc); + css += getInternalStyles(doc); + remvoeExternalStyles(doc); + Element head = doc.head(); + head.append(""); return doc.html(); } private static void mergeCss(Boolean removeAttributes, HashMap> selected) { - Iterator>> it = selected.entrySet().iterator(); + Iterator>> it = selected.entrySet() + .iterator(); while (it.hasNext()) { Map.Entry> pair = it.next(); @@ -52,66 +90,179 @@ private static void mergeCss(Boolean removeAttributes, if (initialStyle != null && !initialStyle.isEmpty()) { style += initialStyle; } - + pair.getKey().attr("style", style); - + // if this is used for email we don't need id and style attributes // as all of our styles are already inline if (removeAttributes) { - pair.getKey().removeAttr("class"); + pair.getKey().removeAttr("class"); } } } private static void extractSelectors(String css, Document doc, HashMap> selected) throws Exception { - List rules = CSSParser.parse(css); + extractSelectors(css, doc, selected, false); + } + private static void extractSelectors(String css, Document doc, + HashMap> selected, boolean debug) + throws Exception { + LOG.trace("extract selectors from [{}]", css); + List rules = CSSParser.parse(css); + ArrayList ignored = new ArrayList(); for (Rule rule : rules) { - for (com.osbcp.cssparser.Selector selector : rule.getSelectors()) { - - Elements elements = doc.select(selector.toString()); - - for (Element element : elements) { - - // list of selectors to apply to this element - ArrayList selectors = new ArrayList(); - - // this element already has selectors attached to it - get reference to the list of existing selectors - if (selected.containsKey(element)) { - selectors = selected.get(element); + Try validSelector = select(doc, selector.toString()); + if (validSelector.isDefined()) { + Elements elements = validSelector.get(); + for (Element element : elements) { + + // list of selectors to apply to this element + ArrayList selectors = new ArrayList(); + + // this element already has selectors attached to it + // - + // get + // reference to the list of existing selectors + if (selected.containsKey(element)) { + selectors = selected.get(element); + } + + // add new selector containing styles to this dom + // element + selectors.add(new Selector(selector.toString(), + getStyleString(rule))); + + selected.put(element, selectors); } - - // add new selector containing styles to this dom element - selectors.add(new Selector(selector.toString(), getStyleString(rule))); - - selected.put(element, selectors); + } else { + ignored.add(selector); + if (debug) + LOG.warn("Couldn't select with [" + selector.toString() + + "]", validSelector.failure()); } } } + LOG.warn( + "{} selectors where ignored while {} where returned. Switch to trace to get a list.", + ignored.size(), selected.size()); + LOG.trace("The following selectors where ignored {}", ignored); + } + + private static Try select(Document doc, String selector) { + try { + return Try.success(doc.select(selector)); + } catch (SelectorParseException e) { + return Try.failure(e); + } + } + + private static String readExternalStyles(Document doc) throws IOException { + Elements elements = selectExternalStyle(doc); + StringBuilder styles = new StringBuilder(); + for (Element style : elements) { + String url = style.attr("href"); + LOG.info("Download external css from [{}]", url); + String cssContent = Jsoup.connect(url).execute().body(); + cssContent = replaceRelativeUrls(cssContent, url); + styles.append("/*external css from [").append(url).append("]*/\n") + .append(cssContent).append("\n"); + } + return styles.toString(); + } + private static final Pattern url1 = Pattern.compile("url\\([ ]*'([^']+)'[ ]*\\)"); + private static final Pattern url2 = Pattern.compile("url\\([ ]*\"([^\"]+)\"[ ]*\\)"); + + private static String replaceRelativeUrls(String cssContent, String url) { + String result = cssContent; + result = replaceUrlWithSeparator(result, url, "'", url1); + result = replaceUrlWithSeparator(result, url, "\"", url2); + return result; + } + + private static String replaceUrlWithSeparator(String cssContent, + String url, String separator, Pattern urlMatcher) { + try { + String result = cssContent; + Matcher m = urlMatcher.matcher(result); + StringBuffer sb = new StringBuffer(); + URI fullURL = new URI(url); + URI parent = fullURL.getPath().endsWith("/") ? fullURL.resolve("..") : fullURL.resolve("."); + while (m.find()) { + String foundUrl = m.group(1); + //System.out.println(foundUrl + "->" + isAbsoluteURL(foundUrl)); + String replacement = ""; + if (isAbsoluteURL(foundUrl)) + replacement = foundUrl; + else + replacement = parent.toString() + foundUrl; + m.appendReplacement(sb, "url("+separator+replacement+separator+")"); + } + m.appendTail(sb); + result = sb.toString(); + result = url2.matcher(result).replaceAll("url(\"$1\")"); + return result; + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } } - - public static String getDocumentStyles(Document doc) { + + /*see http://stackoverflow.com/questions/10159186/how-to-get-parent-url-in-java */ + public static boolean isAbsoluteURL(String url) { + try { + final URL baseHTTP = new URL("http://example.com"); + final URL baseFILE = new URL("file:///"); + URL frelative = new URL(baseFILE, url); + URL hrelative = new URL(baseHTTP, url); + // System.err.println("DEBUG: file URL: " + frelative.toString()); + // System.err.println("DEBUG: http URL: " + hrelative.toString()); + return frelative.equals(hrelative); + } catch (MalformedURLException e) { + throw new RuntimeException(e); + } + } + + public static boolean isAbsoluteURL2(String url) { + try { + URI u = new URI(url); + return u.isAbsolute(); + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } + } + + private static void remvoeExternalStyles(Document doc) { + Elements elements = selectExternalStyle(doc); + elements.remove(); + } + + private static Elements selectExternalStyle(Document doc) { + return doc.select("link[rel=stylesheet],link[type=text/css]"); + } + + public static String getInternalStyles(Document doc) throws IOException { Elements elements = doc.select("style"); - + String styles = ""; for (Element style : elements) { styles += style.html(); } - + // we don't need style elements anymore elements.remove(); return styles; } - + public static String getStyleString(Rule rule) { String style = ""; - + for (PropertyValue propertyValue : rule.getPropertyValues()) { - style += propertyValue.getProperty() + ": " + propertyValue.getValue() + "; "; + style += propertyValue.getProperty() + ": " + + propertyValue.getValue() + "; "; } - + return style; } } diff --git a/src/main/java/com/leonty/Try.java b/src/main/java/com/leonty/Try.java new file mode 100644 index 0000000..3a0b9a3 --- /dev/null +++ b/src/main/java/com/leonty/Try.java @@ -0,0 +1,75 @@ +package com.leonty; + +abstract class Try { + + public abstract T get(); + + public abstract Throwable failure(); + + public abstract boolean isDefined(); + + public static Try success(T value) { + return new Success(value); + } + + public static Try failure(Throwable e) { + return new Failure(e); + } + + private static class Success extends Try { + private final T value; + + public Success(T value) { + this.value = value; + } + + @Override + public T get() { + return value; + } + + @Override + public boolean isDefined() { + return true; + } + + @Override + public String toString() { + return "Success(" + value + ")"; + } + + @Override + public Throwable failure() { + throw new RuntimeException( + "The result is success. Cannot get a failure."); + } + } + + private static class Failure extends Try { + public final Throwable error; + + public Failure(Throwable error) { + this.error = error; + } + + @Override + public T get() { + throw new RuntimeException("Try thrown an exception.", error); + } + + @Override + public boolean isDefined() { + return false; + } + + @Override + public String toString() { + return "Failure(" + error + ")"; + } + + @Override + public Throwable failure() { + return error; + } + } +} \ No newline at end of file diff --git a/src/test/java/com/leonty/test/CSSInlinerTest.java b/src/test/java/com/leonty/test/CSSInlinerTest.java index 76e2f12..7c8cb57 100644 --- a/src/test/java/com/leonty/test/CSSInlinerTest.java +++ b/src/test/java/com/leonty/test/CSSInlinerTest.java @@ -12,10 +12,11 @@ import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; - -import static org.testng.Assert.assertEquals; +import static org.junit.Assert.*; +import org.junit.Test; +//import org.testng.annotations.Test; +//import static org.testng.AssertJUnit.assertEquals; public class CSSInlinerTest { @Test @@ -25,10 +26,37 @@ public void test() throws Exception { String inlinedHtml = readFile("inlined.html"); - assertEquals(CSSInliner.inlineCss(html, css, true), inlinedHtml); + //assertEquals(CSSInliner.inlineCss(html, css, true), inlinedHtml); + assertEquals(normalized(inlinedHtml), normalized(CSSInliner.inlineCss(html, css, true))); + } + + @Test + public void test2() throws Exception { + String html = readFile("test2.html"); + //String css = readFile("test.css"); + + String inlinedHtml = readFile("test2-inlined.html"); + + //assertEquals(CSSInliner.inlineCss(html, css, true), inlinedHtml); + assertEquals(normalized(CSSInliner.cleanup(inlinedHtml)), normalized(CSSInliner.inlineCss(html, true))); + } + + @Test + public void testInternalStyle() throws Exception { + String html = readFile("test2.html"); + //String css = readFile("test.css"); + + String internalHtml = readFile("test2-internal.html"); + + //assertEquals(CSSInliner.inlineCss(html, css, true), inlinedHtml); + assertEquals(normalized(CSSInliner.cleanup(internalHtml)), normalized(CSSInliner.internalCss(html))); + } + + private String normalized(String text) { + return text.replaceAll("\\r\\n|\\n|\\r", "\n").replaceAll("\\t"," ").replaceAll("\\s+\\n","\n"); } - @Test(dataProvider = "selectorPrecedenceData") + @org.testng.annotations.Test(dataProvider = "selectorPrecedenceData") public void testSelectorPrecedence(String css, String initialStyleAttribute, String expectedStyleAttribute) throws Exception { String html = "\n" + diff --git a/src/test/java/com/leonty/test/SelectorTest.java b/src/test/java/com/leonty/test/SelectorTest.java index d51b5ff..3407df5 100644 --- a/src/test/java/com/leonty/test/SelectorTest.java +++ b/src/test/java/com/leonty/test/SelectorTest.java @@ -1,9 +1,12 @@ package com.leonty.test; import com.leonty.Selector; -import org.testng.annotations.Test; -import static org.testng.AssertJUnit.assertEquals; +import static org.junit.Assert.*; +import org.junit.Test; + +//import org.testng.annotations.Test; +//import static org.testng.AssertJUnit.assertEquals; public class SelectorTest { diff --git a/src/test/resources/com/leonty/test/test2-inlined.html b/src/test/resources/com/leonty/test/test2-inlined.html new file mode 100644 index 0000000..c3f8da8 --- /dev/null +++ b/src/test/resources/com/leonty/test/test2-inlined.html @@ -0,0 +1,88 @@ + + + + Cauta - + + + + + +
+
+
+
+
+ Sandale Dama Sandale Dama Quiet Beige GMIP 752 Ilpasso.ro - Ilpasso.ro +
+
+ +
+
+
+ Sandale Dama Sandale Dama Quiet Beige GMIP 752 Sandale de dama din piele naturala cu diverse finisaje care confera versatilitate oricarei tinute de... +
+
+
+
+
+
Pret: + 399 RON + +
+
+
+
+
+
+
+ +
+ +
+ + \ No newline at end of file diff --git a/src/test/resources/com/leonty/test/test2-internal.html b/src/test/resources/com/leonty/test/test2-internal.html new file mode 100644 index 0000000..7acfeff --- /dev/null +++ b/src/test/resources/com/leonty/test/test2-internal.html @@ -0,0 +1,7974 @@ + + + + Cauta - + + + + + + +
+
+
+
+
+ Sandale Dama Sandale Dama Quiet Beige GMIP 752 Ilpasso.ro - Ilpasso.ro +
+
+ +
+
+
+ Sandale Dama Sandale Dama Quiet Beige GMIP 752 Sandale de dama din piele naturala cu diverse finisaje care confera versatilitate oricarei tinute de... +
+
+
+
+
+
Pret: + 399 RON + +
+
+
+
+
+
+
+ +
+ +
+ + \ No newline at end of file diff --git a/src/test/resources/com/leonty/test/test2.html b/src/test/resources/com/leonty/test/test2.html new file mode 100644 index 0000000..83316f2 --- /dev/null +++ b/src/test/resources/com/leonty/test/test2.html @@ -0,0 +1,150 @@ + + + + + Cauta - + + + + + + + + + + + + + + + + +
+ +
+
+
+
+ + Sandale Dama Sandale Dama Quiet Beige GMIP 752 Ilpasso.ro - Ilpasso.ro + +
+
+ + +
+
+
+ Sandale Dama Sandale Dama Quiet Beige GMIP 752 Sandale de dama din piele naturala cu diverse finisaje care confera versatilitate oricarei tinute de... +
+
+
+
+
+
+ Pret: + 399 RON + + +
+
+ +
+
+
+
+ +
+ + + + + + + +