
com.github.rahulsom.nexusmonitor.CssInliner.groovy Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of NexusMonitor Show documentation
Show all versions of NexusMonitor Show documentation
Notify on new releases from Sonatype Nexus
The newest version!
package com.github.rahulsom.nexusmonitor
import groovy.transform.CompileStatic
import org.apache.commons.codec.binary.Base64
import org.apache.commons.io.IOUtils
import org.jsoup.Jsoup
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import org.jsoup.select.Elements
import java.net.URL
import java.net.URLConnection
import java.text.MessageFormat
import java.util.StringTokenizer
/**
* Inlines CSS to avoid the dreaded GMail Grimace.
*
* The magic keyword is data-inline="true"
.
*
*
* -
* When used in conjunction with the
style
tag, it inlines the stylesheet defined there into all
* html elements matching the rules.
*
* -
* When used with the
img
tag, it base64 encodes the image it found to make it visible in the
* email.
*
*
*
* @author Rahul Somasunderam
*/
@CompileStatic
class CssInliner {
public static final String CSS_STYLE = "cssstyle"
public static final String STYLE_ATTR = "style"
public static final String STYLE_TAG = "style"
public static final String IMG_TAG = "img"
public static final String IMG_SRC_ATTR = "src"
private static String concatenateProperties(String oldProp, String newProp) {
if (!oldProp.endsWith(";"))
oldProp += ";"
return oldProp.trim() + " " + newProp.trim() + ";"
}
/**
* Generates a stylesheet from an html document
*
* @param doc the html document
* @return a string representing the stylesheet.
*/
private static String fetchStyles(Document doc) {
Elements els = doc.select(STYLE_TAG)
StringBuilder styles = new StringBuilder()
for (Element e : els) {
if (e.attr("data-inline").equals("true")) {
styles.append(e.data())
e.remove()
}
}
return styles.toString()
}
/**
* Takes an input string representing an html document and processes it with the Css Inliner.
* @param input the html document
* @return the processed html document
*/
static String process(String input) {
Document doc = Jsoup.parse(input)
extractStyles(doc)
applyStyles(doc)
inlineImages(doc)
String output = doc.toString()
return output
}
/**
* Inlines images marked with data-inline="true"
*
* @param doc the html document
*/
private static void inlineImages(Document doc) {
Elements allImages = doc.getElementsByTag(IMG_TAG)
for (Element img : allImages) {
if (img.attr("data-inline").equals("true")) {
String src = img.attr(IMG_SRC_ATTR)
try {
URL url = new URL(src)
URLConnection urlConnection = url.openConnection()
urlConnection.connect()
String contentType = urlConnection.getContentType()
urlConnection.getContent()
byte[] srcContent = IOUtils.toByteArray(url.openStream())
String base64 = new Base64().encodeToString(srcContent)
img.attr(IMG_SRC_ATTR, MessageFormat.format("data:{0};base64,{1}", contentType, base64))
} catch (Exception e) {
e.printStackTrace()
}
}
}
}
/**
* Transfers styles from the cssstyle
attribute to the style
attribute.
*
* @param doc the html document
*/
private static void applyStyles(Document doc) {
Elements allStyledElements = doc.getElementsByAttribute(CSS_STYLE)
for (Element e : allStyledElements) {
String newStyle = e.attr(CSS_STYLE)
String oldStyle = e.attr(STYLE_ATTR)
e.attr(STYLE_ATTR, (newStyle + "; " + oldStyle).replace(";;", ";"))
e.removeAttr(CSS_STYLE)
}
}
/**
* Extracts styles from the stylesheet and applies them to a cssstyle
attribute. This is because the
* styles need to be applied sequentially, but before the style
defined for the element inline.
*
* @param doc the html document
*/
private static void extractStyles(Document doc) {
String stylesheet = fetchStyles(doc)
String trimmedStylesheet = stylesheet.replaceAll("\n", "").replaceAll("/\\*.*?\\*/", "").replaceAll(" +", " ")
String styleRules = trimmedStylesheet.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(CSS_STYLE)
selElem.attr(CSS_STYLE,
oldProperties.length() > 0 ? concatenateProperties(
oldProperties, properties) : properties)
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy