forked from alexo/wro4j
-
Notifications
You must be signed in to change notification settings - Fork 0
/
CssImportPreProcessor.java
173 lines (157 loc) · 5.73 KB
/
CssImportPreProcessor.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
/**
* Copyright Alex Objelean
*/
package ro.isdc.wro.model.resource.processor.impl.css;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ro.isdc.wro.model.group.Inject;
import ro.isdc.wro.model.group.processor.PreProcessorExecutor;
import ro.isdc.wro.model.resource.Resource;
import ro.isdc.wro.model.resource.ResourceType;
import ro.isdc.wro.model.resource.SupportedResourceType;
import ro.isdc.wro.model.resource.factory.UriLocatorFactory;
import ro.isdc.wro.model.resource.processor.ResourcePreProcessor;
import ro.isdc.wro.util.StringUtils;
/**
* CssImport Processor responsible for handling css <code>@import</code> statement. It is implemented as both:
* preProcessor & postProcessor. It is necessary because preProcessor is responsible for updating model with found
* imported resources, while post processor removes import occurrences.
*
* @author Alex Objelean
*/
@SupportedResourceType(ResourceType.CSS)
public class CssImportPreProcessor
implements ResourcePreProcessor {
private static final Logger LOG = LoggerFactory.getLogger(CssImportPreProcessor.class);
/**
* Contains a {@link UriLocatorFactory} reference injected externally.
*/
@Inject
private UriLocatorFactory uriLocatorFactory;
@Inject
private PreProcessorExecutor preProcessorExecutor;
/**
* List of processed resources, useful for detecting deep recursion.
*/
private final List<Resource> processed = new ArrayList<Resource>();
/** The url pattern */
///^(\s|\n)*
//@import\s*(?:url\()?["']?([^"')]+)["']?\)?;?
private static final Pattern PATTERN = Pattern.compile("@import\\s*(?:url\\()?[\"']?([^\"')]+)[\"')]?\\)?;?", Pattern.CASE_INSENSITIVE);
/**
* {@inheritDoc}
*/
public synchronized void process(final Resource resource, final Reader reader, final Writer writer)
throws IOException {
validate();
try {
final String result = parseCss(resource, reader);
writer.write(result);
processed.clear();
} finally {
reader.close();
writer.close();
}
}
/**
* Checks if required fields were injected.
*/
private void validate() {
if (uriLocatorFactory == null) {
throw new IllegalStateException("No UriLocator was injected");
}
if (preProcessorExecutor == null) {
throw new IllegalStateException("No preProcessorExecutor was injected");
}
}
/**
* @param resource {@link Resource} to process.
* @param reader Reader for processed resource.
* @return css content with all imports processed.
* @throws IOException
*/
private String parseCss(final Resource resource, final Reader reader)
throws IOException {
if (processed.contains(resource)) {
LOG.warn("Recursive import detected: " + resource);
return "";
}
processed.add(resource);
final StringBuffer sb = new StringBuffer();
final List<Resource> importsCollector = getImportedResources(resource);
// for now, minimize always
// TODO: find a way to get minimize property dynamically.
//groupExtractor.isMinimized(Context.get().getRequest())
sb.append(preProcessorExecutor.processAndMerge(importsCollector, true));
if (!importsCollector.isEmpty()) {
LOG.debug("Imported resources found : " + importsCollector.size());
}
sb.append(IOUtils.toString(reader));
LOG.debug("importsCollector: " + importsCollector);
return removeImportStatements(sb.toString());
}
/**
* Removes all @import statements for css.
*/
private String removeImportStatements(final String content) {
final Matcher m = PATTERN.matcher(content);
final StringBuffer sb = new StringBuffer();
while (m.find()) {
// add and check if already exist
m.appendReplacement(sb, "");
}
m.appendTail(sb);
return sb.toString();
}
/**
* Find a set of imported resources inside a given resource.
*/
private List<Resource> getImportedResources(final Resource resource)
throws IOException {
// it should be sorted
final List<Resource> imports = new ArrayList<Resource>();
// Check if @Scanner#findWithinHorizon can be used instead
final String css = IOUtils.toString(uriLocatorFactory.locate(resource.getUri()));
final Matcher m = PATTERN.matcher(css);
while (m.find()) {
final Resource importedResource = buildImportedResource(resource, m.group(1));
// check if already exist
if (imports.contains(importedResource)) {
LOG.warn("Duplicate imported resource: " + importedResource);
} else {
imports.add(importedResource);
}
}
return imports;
}
/**
* Build a {@link Resource} object from a found importedResource inside a given resource.
*/
private Resource buildImportedResource(final Resource resource, final String importUrl) {
final String absoluteUrl = computeAbsoluteUrl(resource, importUrl);
final Resource importResource = Resource.create(absoluteUrl, ResourceType.CSS);
return importResource;
}
/**
* Computes absolute url of the imported resource.
*
* @param relativeResource {@link Resource} where the import statement is found.
* @param importUrl found import url.
* @return absolute url of the resource to import.
*/
private String computeAbsoluteUrl(final Resource relativeResource, final String importUrl) {
final String folder = FilenameUtils.getFullPath(relativeResource.getUri());
// remove '../' & normalize the path.
final String absoluteImportUrl = StringUtils.normalizePath(folder + importUrl);
return absoluteImportUrl;
}
}