|
|
|
@ -22,9 +22,10 @@ import java.nio.charset.Charset; |
|
|
|
import java.nio.charset.StandardCharsets; |
|
|
|
import java.nio.charset.StandardCharsets; |
|
|
|
import java.util.ArrayList; |
|
|
|
import java.util.ArrayList; |
|
|
|
import java.util.Collections; |
|
|
|
import java.util.Collections; |
|
|
|
import java.util.HashSet; |
|
|
|
|
|
|
|
import java.util.List; |
|
|
|
import java.util.List; |
|
|
|
import java.util.Set; |
|
|
|
import java.util.Set; |
|
|
|
|
|
|
|
import java.util.SortedSet; |
|
|
|
|
|
|
|
import java.util.TreeSet; |
|
|
|
|
|
|
|
|
|
|
|
import org.apache.commons.logging.Log; |
|
|
|
import org.apache.commons.logging.Log; |
|
|
|
import org.apache.commons.logging.LogFactory; |
|
|
|
import org.apache.commons.logging.LogFactory; |
|
|
|
@ -83,27 +84,27 @@ public class CssLinkResourceTransformer extends ResourceTransformerSupport { |
|
|
|
logger.trace("Transforming resource: " + newResource); |
|
|
|
logger.trace("Transforming resource: " + newResource); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
byte[] bytes = new byte[0]; |
|
|
|
byte[] bytes; |
|
|
|
try { |
|
|
|
try { |
|
|
|
bytes = FileCopyUtils.copyToByteArray(newResource.getInputStream()); |
|
|
|
bytes = FileCopyUtils.copyToByteArray(newResource.getInputStream()); |
|
|
|
} |
|
|
|
} |
|
|
|
catch (IOException ex) { |
|
|
|
catch (IOException ex) { |
|
|
|
return Mono.error(Exceptions.propagate(ex)); |
|
|
|
return Mono.error(Exceptions.propagate(ex)); |
|
|
|
} |
|
|
|
} |
|
|
|
String fullContent = new String(bytes, DEFAULT_CHARSET); |
|
|
|
String cssContent = new String(bytes, DEFAULT_CHARSET); |
|
|
|
List<Segment> segments = parseContent(fullContent); |
|
|
|
List<ContentChunkInfo> contentChunkInfos = parseContent(cssContent); |
|
|
|
|
|
|
|
|
|
|
|
if (segments.isEmpty()) { |
|
|
|
if (contentChunkInfos.isEmpty()) { |
|
|
|
if (logger.isTraceEnabled()) { |
|
|
|
if (logger.isTraceEnabled()) { |
|
|
|
logger.trace("No links found."); |
|
|
|
logger.trace("No links found."); |
|
|
|
} |
|
|
|
} |
|
|
|
return Mono.just(newResource); |
|
|
|
return Mono.just(newResource); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return Flux.fromIterable(segments) |
|
|
|
return Flux.fromIterable(contentChunkInfos) |
|
|
|
.concatMap(segment -> { |
|
|
|
.concatMap(contentChunkInfo -> { |
|
|
|
String segmentContent = segment.getContent(fullContent); |
|
|
|
String segmentContent = contentChunkInfo.getContent(cssContent); |
|
|
|
if (segment.isLink() && !hasScheme(segmentContent)) { |
|
|
|
if (contentChunkInfo.isLink() && !hasScheme(segmentContent)) { |
|
|
|
String link = toAbsolutePath(segmentContent, exchange); |
|
|
|
String link = toAbsolutePath(segmentContent, exchange); |
|
|
|
return resolveUrlPath(link, exchange, newResource, transformerChain) |
|
|
|
return resolveUrlPath(link, exchange, newResource, transformerChain) |
|
|
|
.defaultIfEmpty(segmentContent); |
|
|
|
.defaultIfEmpty(segmentContent); |
|
|
|
@ -116,39 +117,30 @@ public class CssLinkResourceTransformer extends ResourceTransformerSupport { |
|
|
|
writer.write(chunk); |
|
|
|
writer.write(chunk); |
|
|
|
return writer; |
|
|
|
return writer; |
|
|
|
}) |
|
|
|
}) |
|
|
|
.flatMap(writer -> { |
|
|
|
.map(writer -> { |
|
|
|
byte[] newContent = writer.toString().getBytes(DEFAULT_CHARSET); |
|
|
|
byte[] newContent = writer.toString().getBytes(DEFAULT_CHARSET); |
|
|
|
return Mono.just(new TransformedResource(resource, newContent)); |
|
|
|
return new TransformedResource(resource, newContent); |
|
|
|
}); |
|
|
|
}); |
|
|
|
}); |
|
|
|
}); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private List<Segment> parseContent(String fullContent) { |
|
|
|
private List<ContentChunkInfo> parseContent(String cssContent) { |
|
|
|
|
|
|
|
SortedSet<ContentChunkInfo> links = new TreeSet<>(); |
|
|
|
List<Segment> links = new ArrayList<>(); |
|
|
|
this.linkParsers.forEach(parser -> parser.parse(cssContent, links)); |
|
|
|
for (LinkParser parser : this.linkParsers) { |
|
|
|
|
|
|
|
links.addAll(parser.parseLinks(fullContent)); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (links.isEmpty()) { |
|
|
|
if (links.isEmpty()) { |
|
|
|
return Collections.emptyList(); |
|
|
|
return Collections.emptyList(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
Collections.sort(links); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int index = 0; |
|
|
|
int index = 0; |
|
|
|
List<Segment> allSegments = new ArrayList<>(links); |
|
|
|
List<ContentChunkInfo> result = new ArrayList<>(); |
|
|
|
for (Segment link : links) { |
|
|
|
for (ContentChunkInfo link : links) { |
|
|
|
allSegments.add(new Segment(index, link.getStart(), false)); |
|
|
|
result.add(new ContentChunkInfo(index, link.getStart(), false)); |
|
|
|
|
|
|
|
result.add(link); |
|
|
|
index = link.getEnd(); |
|
|
|
index = link.getEnd(); |
|
|
|
} |
|
|
|
} |
|
|
|
if (index < fullContent.length()) { |
|
|
|
if (index < cssContent.length()) { |
|
|
|
allSegments.add(new Segment(index, fullContent.length(), false)); |
|
|
|
result.add(new ContentChunkInfo(index, cssContent.length(), false)); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
return result; |
|
|
|
Collections.sort(allSegments); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return allSegments; |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private boolean hasScheme(String link) { |
|
|
|
private boolean hasScheme(String link) { |
|
|
|
@ -157,66 +149,60 @@ public class CssLinkResourceTransformer extends ResourceTransformerSupport { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
|
|
* Extract content chunks that represent links. |
|
|
|
|
|
|
|
*/ |
|
|
|
@FunctionalInterface |
|
|
|
@FunctionalInterface |
|
|
|
protected interface LinkParser { |
|
|
|
protected interface LinkParser { |
|
|
|
|
|
|
|
|
|
|
|
Set<Segment> parseLinks(String fullContent); |
|
|
|
void parse(String cssContent, SortedSet<ContentChunkInfo> result); |
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
protected static abstract class AbstractLinkParser implements LinkParser { |
|
|
|
protected static abstract class AbstractLinkParser implements LinkParser { |
|
|
|
|
|
|
|
|
|
|
|
/** Return the keyword to use to search for links. */ |
|
|
|
/** Return the keyword to use to search for links, e.g. "@import", "url(" */ |
|
|
|
protected abstract String getKeyword(); |
|
|
|
protected abstract String getKeyword(); |
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
@Override |
|
|
|
public Set<Segment> parseLinks(String fullContent) { |
|
|
|
public void parse(String content, SortedSet<ContentChunkInfo> result) { |
|
|
|
Set<Segment> linksToAdd = new HashSet<>(8); |
|
|
|
int position = 0; |
|
|
|
int index = 0; |
|
|
|
while (true) { |
|
|
|
do { |
|
|
|
position = content.indexOf(getKeyword(), position); |
|
|
|
index = fullContent.indexOf(getKeyword(), index); |
|
|
|
if (position == -1) { |
|
|
|
if (index == -1) { |
|
|
|
return; |
|
|
|
break; |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
index = skipWhitespace(fullContent, index + getKeyword().length()); |
|
|
|
position += getKeyword().length(); |
|
|
|
if (fullContent.charAt(index) == '\'') { |
|
|
|
while (Character.isWhitespace(content.charAt(position))) { |
|
|
|
index = addLink(index, "'", fullContent, linksToAdd); |
|
|
|
position++; |
|
|
|
} |
|
|
|
} |
|
|
|
else if (fullContent.charAt(index) == '"') { |
|
|
|
if (content.charAt(position) == '\'') { |
|
|
|
index = addLink(index, "\"", fullContent, linksToAdd); |
|
|
|
position = extractLink(position, '\'', content, result); |
|
|
|
} |
|
|
|
} |
|
|
|
else { |
|
|
|
else if (content.charAt(position) == '"') { |
|
|
|
index = extractLink(index, fullContent, linksToAdd); |
|
|
|
position = extractLink(position, '"', content, result); |
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
else { |
|
|
|
while (true); |
|
|
|
position = extractUnquotedLink(position, content, result); |
|
|
|
return linksToAdd; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private int skipWhitespace(String content, int index) { |
|
|
|
|
|
|
|
while (true) { |
|
|
|
|
|
|
|
if (Character.isWhitespace(content.charAt(index))) { |
|
|
|
|
|
|
|
index++; |
|
|
|
|
|
|
|
continue; |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
return index; |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
protected int addLink(int index, String endKey, String content, Set<Segment> linksToAdd) { |
|
|
|
protected int extractLink(int index, char endChar, String content, Set<ContentChunkInfo> result) { |
|
|
|
int start = index + 1; |
|
|
|
int start = index + 1; |
|
|
|
int end = content.indexOf(endKey, start); |
|
|
|
int end = content.indexOf(endChar, start); |
|
|
|
linksToAdd.add(new Segment(start, end, true)); |
|
|
|
result.add(new ContentChunkInfo(start, end, true)); |
|
|
|
return end + endKey.length(); |
|
|
|
return end + 1; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
* Invoked after a keyword match, after whitespaces removed, and when |
|
|
|
* Invoked after a keyword match, after whitespaces removed, and when |
|
|
|
* the next char is neither a single nor double quote. |
|
|
|
* the next char is neither a single nor double quote. |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
protected abstract int extractLink(int index, String content, Set<Segment> linksToAdd); |
|
|
|
protected abstract int extractUnquotedLink(int position, String content, |
|
|
|
|
|
|
|
Set<ContentChunkInfo> linksToAdd); |
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@ -229,14 +215,14 @@ public class CssLinkResourceTransformer extends ResourceTransformerSupport { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
@Override |
|
|
|
protected int extractLink(int index, String content, Set<Segment> linksToAdd) { |
|
|
|
protected int extractUnquotedLink(int position, String content, Set<ContentChunkInfo> result) { |
|
|
|
if (content.substring(index, index + 4).equals("url(")) { |
|
|
|
if (content.substring(position, position + 4).equals("url(")) { |
|
|
|
// Ignore, UrlLinkParser will take care
|
|
|
|
// Ignore, UrlFunctionContentParser will take care
|
|
|
|
} |
|
|
|
} |
|
|
|
else if (logger.isErrorEnabled()) { |
|
|
|
else if (logger.isErrorEnabled()) { |
|
|
|
logger.error("Unexpected syntax for @import link at index " + index); |
|
|
|
logger.error("Unexpected syntax for @import link at index " + position); |
|
|
|
} |
|
|
|
} |
|
|
|
return index; |
|
|
|
return position; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@ -249,26 +235,26 @@ public class CssLinkResourceTransformer extends ResourceTransformerSupport { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
@Override |
|
|
|
protected int extractLink(int index, String content, Set<Segment> linksToAdd) { |
|
|
|
protected int extractUnquotedLink(int position, String content, Set<ContentChunkInfo> result) { |
|
|
|
// A url() function without unquoted
|
|
|
|
// A url() function without unquoted
|
|
|
|
return addLink(index - 1, ")", content, linksToAdd); |
|
|
|
return extractLink(position - 1, ')', content, result); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private static class Segment implements Comparable<Segment> { |
|
|
|
private static class ContentChunkInfo implements Comparable<ContentChunkInfo> { |
|
|
|
|
|
|
|
|
|
|
|
private final int start; |
|
|
|
private final int start; |
|
|
|
|
|
|
|
|
|
|
|
private final int end; |
|
|
|
private final int end; |
|
|
|
|
|
|
|
|
|
|
|
private final boolean link; |
|
|
|
private final boolean isLink; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public Segment(int start, int end, boolean isLink) { |
|
|
|
ContentChunkInfo(int start, int end, boolean isLink) { |
|
|
|
this.start = start; |
|
|
|
this.start = start; |
|
|
|
this.end = end; |
|
|
|
this.end = end; |
|
|
|
this.link = isLink; |
|
|
|
this.isLink = isLink; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -281,7 +267,7 @@ public class CssLinkResourceTransformer extends ResourceTransformerSupport { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
public boolean isLink() { |
|
|
|
public boolean isLink() { |
|
|
|
return this.link; |
|
|
|
return this.isLink; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
public String getContent(String fullContent) { |
|
|
|
public String getContent(String fullContent) { |
|
|
|
@ -289,7 +275,7 @@ public class CssLinkResourceTransformer extends ResourceTransformerSupport { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
@Override |
|
|
|
public int compareTo(Segment other) { |
|
|
|
public int compareTo(ContentChunkInfo other) { |
|
|
|
return (this.start < other.start ? -1 : (this.start == other.start ? 0 : 1)); |
|
|
|
return (this.start < other.start ? -1 : (this.start == other.start ? 0 : 1)); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@ -298,8 +284,8 @@ public class CssLinkResourceTransformer extends ResourceTransformerSupport { |
|
|
|
if (this == obj) { |
|
|
|
if (this == obj) { |
|
|
|
return true; |
|
|
|
return true; |
|
|
|
} |
|
|
|
} |
|
|
|
if (obj != null && obj instanceof Segment) { |
|
|
|
if (obj != null && obj instanceof ContentChunkInfo) { |
|
|
|
Segment other = (Segment) obj; |
|
|
|
ContentChunkInfo other = (ContentChunkInfo) obj; |
|
|
|
return (this.start == other.start && this.end == other.end); |
|
|
|
return (this.start == other.start && this.end == other.end); |
|
|
|
} |
|
|
|
} |
|
|
|
return false; |
|
|
|
return false; |
|
|
|
|