/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
*/
package org.apache.wicket.resource;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.wicket.WicketRuntimeException;
import org.apache.wicket.css.ICssCompressor;
import org.apache.wicket.request.Url;
import org.apache.wicket.request.cycle.RequestCycle;
import org.apache.wicket.request.resource.PackageResourceReference;
import org.apache.wicket.util.image.ImageUtil;
/**
* This compressor is used to replace URLs within CSS files with URLs created from
* PackageResourceReferences that belongs to their corresponding resources (e.g images).The scope of
* the CSS file is used to create the PackageResourceReferences. The compress method is not
* compressing any content, but replacing the URLs with Wicket representatives.<br>
* <br>
* Usage:
*
* <pre>
* this.getResourceSettings().setCssCompressor(new CssUrlReplacer());
* </pre>
*
* @since 6.20.0
* @author Tobias Soloschenko
*/
public class CssUrlReplacer implements IScopeAwareTextResourceProcessor, ICssCompressor
{
// The pattern to find URLs in CSS resources
private static final Pattern URL_PATTERN = Pattern
.compile("url\\([ ]*['|\"]?([^ ]*?)['|\"]?[ ]*\\)");
/**
* Used to be append to CSS URLs (background-image: url('Beer.gif?embedBase64');). The
* CssUrlReplacer embeds the base64 content instead of using an URL.
*/
public static final String EMBED_BASE64 = "embedBase64";
private final Set<String> excludes = new LinkedHashSet<>();
/**
* Creates a css url replacer
*/
public CssUrlReplacer()
{
}
/**
* Creates a css url replacer
*
* @param excludes
* css file names to be excluded
*/
public CssUrlReplacer(Collection<String> excludes)
{
this.excludes.addAll(excludes);
}
/**
* Replaces the URLs of CSS resources with Wicket representatives.
*/
@Override
public String process(String input, Class<?> scope, String name)
{
// filter out the excluded css files
for (String excludeName : excludes)
{
if(name.endsWith(excludeName)){
return input;
}
}
RequestCycle cycle = RequestCycle.get();
Url cssUrl = Url.parse(name);
Matcher matcher = URL_PATTERN.matcher(input);
StringBuffer output = new StringBuffer();
while (matcher.find())
{
Url imageCandidateUrl = Url.parse(matcher.group(1));
CharSequence processedUrl;
boolean embedded = false;
if (imageCandidateUrl.isFull())
{
processedUrl = imageCandidateUrl.toString(Url.StringMode.FULL);
}
else if (imageCandidateUrl.isContextAbsolute())
{
processedUrl = imageCandidateUrl.toString();
}
else if (imageCandidateUrl.isDataUrl())
{
embedded = true;
processedUrl = imageCandidateUrl.toString();
}
else
{
// relativize against the url for the containing CSS file
Url cssUrlCopy = new Url(cssUrl);
cssUrlCopy.resolveRelative(imageCandidateUrl);
// if the image should be processed as URL or base64 embedded
if (cssUrlCopy.getQueryString() != null
&& cssUrlCopy.getQueryString().contains(EMBED_BASE64))
{
embedded = true;
PackageResourceReference imageReference = new PackageResourceReference(scope,
cssUrlCopy.toString().replace("?" + EMBED_BASE64, ""));
try
{
processedUrl = ImageUtil.createBase64EncodedImage(imageReference, true);
}
catch (Exception e)
{
throw new WicketRuntimeException(
"Error while embedding an image into the css: " + imageReference, e);
}
}
else
{
PackageResourceReference imageReference = new PackageResourceReference(scope,
cssUrlCopy.toString());
processedUrl = cycle.urlFor(imageReference, null);
}
}
// embedded data urls don't need single quotes, but regular urls do:
matcher.appendReplacement(output,
embedded ? "url(" + processedUrl + ")" : "url('" + processedUrl + "')");
}
matcher.appendTail(output);
return output.toString();
}
@Override
public String compress(String original)
{
throw new UnsupportedOperationException(
CssUrlReplacer.class.getSimpleName() + ".process() should be used instead!");
}
/**
* Gets excluded css file names
*
* @return a list with css file names to be excluded
*/
public Collection<String> getExcludes()
{
return excludes;
}
/**
* Sets a list of css file names to be excluded
*
* @param excludes
* a list with css file names to be excluded
*/
public void setExcludes(Collection<String> excludes)
{
this.excludes.clear();
this.excludes.addAll(excludes);
}
}