22
22
import java .nio .charset .StandardCharsets ;
23
23
import java .util .ArrayList ;
24
24
import java .util .Collections ;
25
- import java .util .HashSet ;
26
25
import java .util .List ;
27
26
import java .util .Set ;
27
+ import java .util .SortedSet ;
28
+ import java .util .TreeSet ;
28
29
29
30
import org .apache .commons .logging .Log ;
30
31
import org .apache .commons .logging .LogFactory ;
@@ -83,27 +84,27 @@ public Mono<Resource> transform(ServerWebExchange exchange, Resource resource,
83
84
logger .trace ("Transforming resource: " + newResource );
84
85
}
85
86
86
- byte [] bytes = new byte [ 0 ] ;
87
+ byte [] bytes ;
87
88
try {
88
89
bytes = FileCopyUtils .copyToByteArray (newResource .getInputStream ());
89
90
}
90
91
catch (IOException ex ) {
91
92
return Mono .error (Exceptions .propagate (ex ));
92
93
}
93
- String fullContent = new String (bytes , DEFAULT_CHARSET );
94
- List <Segment > segments = parseContent (fullContent );
94
+ String cssContent = new String (bytes , DEFAULT_CHARSET );
95
+ List <ContentChunkInfo > contentChunkInfos = parseContent (cssContent );
95
96
96
- if (segments .isEmpty ()) {
97
+ if (contentChunkInfos .isEmpty ()) {
97
98
if (logger .isTraceEnabled ()) {
98
99
logger .trace ("No links found." );
99
100
}
100
101
return Mono .just (newResource );
101
102
}
102
103
103
- return Flux .fromIterable (segments )
104
- .concatMap (segment -> {
105
- String segmentContent = segment .getContent (fullContent );
106
- if (segment .isLink () && !hasScheme (segmentContent )) {
104
+ return Flux .fromIterable (contentChunkInfos )
105
+ .concatMap (contentChunkInfo -> {
106
+ String segmentContent = contentChunkInfo .getContent (cssContent );
107
+ if (contentChunkInfo .isLink () && !hasScheme (segmentContent )) {
107
108
String link = toAbsolutePath (segmentContent , exchange );
108
109
return resolveUrlPath (link , exchange , newResource , transformerChain )
109
110
.defaultIfEmpty (segmentContent );
@@ -116,39 +117,30 @@ public Mono<Resource> transform(ServerWebExchange exchange, Resource resource,
116
117
writer .write (chunk );
117
118
return writer ;
118
119
})
119
- .flatMap (writer -> {
120
+ .map (writer -> {
120
121
byte [] newContent = writer .toString ().getBytes (DEFAULT_CHARSET );
121
- return Mono . just ( new TransformedResource (resource , newContent ) );
122
+ return new TransformedResource (resource , newContent );
122
123
});
123
124
});
124
125
}
125
126
126
- private List <Segment > parseContent (String fullContent ) {
127
-
128
- List <Segment > links = new ArrayList <>();
129
- for (LinkParser parser : this .linkParsers ) {
130
- links .addAll (parser .parseLinks (fullContent ));
131
- }
132
-
127
+ private List <ContentChunkInfo > parseContent (String cssContent ) {
128
+ SortedSet <ContentChunkInfo > links = new TreeSet <>();
129
+ this .linkParsers .forEach (parser -> parser .parse (cssContent , links ));
133
130
if (links .isEmpty ()) {
134
131
return Collections .emptyList ();
135
132
}
136
-
137
- Collections .sort (links );
138
-
139
133
int index = 0 ;
140
- List <Segment > allSegments = new ArrayList <>(links );
141
- for (Segment link : links ) {
142
- allSegments .add (new Segment (index , link .getStart (), false ));
134
+ List <ContentChunkInfo > result = new ArrayList <>();
135
+ for (ContentChunkInfo link : links ) {
136
+ result .add (new ContentChunkInfo (index , link .getStart (), false ));
137
+ result .add (link );
143
138
index = link .getEnd ();
144
139
}
145
- if (index < fullContent .length ()) {
146
- allSegments .add (new Segment (index , fullContent .length (), false ));
140
+ if (index < cssContent .length ()) {
141
+ result .add (new ContentChunkInfo (index , cssContent .length (), false ));
147
142
}
148
-
149
- Collections .sort (allSegments );
150
-
151
- return allSegments ;
143
+ return result ;
152
144
}
153
145
154
146
private boolean hasScheme (String link ) {
@@ -157,66 +149,60 @@ private boolean hasScheme(String link) {
157
149
}
158
150
159
151
152
+ /**
153
+ * Extract content chunks that represent links.
154
+ */
160
155
@ FunctionalInterface
161
156
protected interface LinkParser {
162
157
163
- Set < Segment > parseLinks (String fullContent );
158
+ void parse (String cssContent , SortedSet < ContentChunkInfo > result );
164
159
165
160
}
166
161
167
162
168
163
protected static abstract class AbstractLinkParser implements LinkParser {
169
164
170
- /** Return the keyword to use to search for links. */
165
+ /** Return the keyword to use to search for links, e.g. "@import", "url(" */
171
166
protected abstract String getKeyword ();
172
167
173
168
@ Override
174
- public Set <Segment > parseLinks (String fullContent ) {
175
- Set <Segment > linksToAdd = new HashSet <>(8 );
176
- int index = 0 ;
177
- do {
178
- index = fullContent .indexOf (getKeyword (), index );
179
- if (index == -1 ) {
180
- break ;
169
+ public void parse (String content , SortedSet <ContentChunkInfo > result ) {
170
+ int position = 0 ;
171
+ while (true ) {
172
+ position = content .indexOf (getKeyword (), position );
173
+ if (position == -1 ) {
174
+ return ;
181
175
}
182
- index = skipWhitespace ( fullContent , index + getKeyword ().length () );
183
- if ( fullContent . charAt (index ) == '\'' ) {
184
- index = addLink ( index , "'" , fullContent , linksToAdd ) ;
176
+ position += getKeyword ().length ();
177
+ while ( Character . isWhitespace ( content . charAt (position )) ) {
178
+ position ++ ;
185
179
}
186
- else if (fullContent .charAt (index ) == '" ' ) {
187
- index = addLink ( index , " \" " , fullContent , linksToAdd );
180
+ if (content .charAt (position ) == '\' ' ) {
181
+ position = extractLink ( position , '\'' , content , result );
188
182
}
189
- else {
190
- index = extractLink (index , fullContent , linksToAdd );
191
-
183
+ else if (content .charAt (position ) == '"' ) {
184
+ position = extractLink (position , '"' , content , result );
192
185
}
193
- }
194
- while (true );
195
- return linksToAdd ;
196
- }
186
+ else {
187
+ position = extractUnquotedLink (position , content , result );
197
188
198
- private int skipWhitespace (String content , int index ) {
199
- while (true ) {
200
- if (Character .isWhitespace (content .charAt (index ))) {
201
- index ++;
202
- continue ;
203
189
}
204
- return index ;
205
190
}
206
191
}
207
192
208
- protected int addLink (int index , String endKey , String content , Set <Segment > linksToAdd ) {
193
+ protected int extractLink (int index , char endChar , String content , Set <ContentChunkInfo > result ) {
209
194
int start = index + 1 ;
210
- int end = content .indexOf (endKey , start );
211
- linksToAdd .add (new Segment (start , end , true ));
212
- return end + endKey . length () ;
195
+ int end = content .indexOf (endChar , start );
196
+ result .add (new ContentChunkInfo (start , end , true ));
197
+ return end + 1 ;
213
198
}
214
199
215
200
/**
216
201
* Invoked after a keyword match, after whitespaces removed, and when
217
202
* the next char is neither a single nor double quote.
218
203
*/
219
- protected abstract int extractLink (int index , String content , Set <Segment > linksToAdd );
204
+ protected abstract int extractUnquotedLink (int position , String content ,
205
+ Set <ContentChunkInfo > linksToAdd );
220
206
221
207
}
222
208
@@ -229,14 +215,14 @@ protected String getKeyword() {
229
215
}
230
216
231
217
@ Override
232
- protected int extractLink (int index , String content , Set <Segment > linksToAdd ) {
233
- if (content .substring (index , index + 4 ).equals ("url(" )) {
234
- // Ignore, UrlLinkParser will take care
218
+ protected int extractUnquotedLink (int position , String content , Set <ContentChunkInfo > result ) {
219
+ if (content .substring (position , position + 4 ).equals ("url(" )) {
220
+ // Ignore, UrlFunctionContentParser will take care
235
221
}
236
222
else if (logger .isErrorEnabled ()) {
237
- logger .error ("Unexpected syntax for @import link at index " + index );
223
+ logger .error ("Unexpected syntax for @import link at index " + position );
238
224
}
239
- return index ;
225
+ return position ;
240
226
}
241
227
}
242
228
@@ -249,26 +235,26 @@ protected String getKeyword() {
249
235
}
250
236
251
237
@ Override
252
- protected int extractLink (int index , String content , Set <Segment > linksToAdd ) {
238
+ protected int extractUnquotedLink (int position , String content , Set <ContentChunkInfo > result ) {
253
239
// A url() function without unquoted
254
- return addLink ( index - 1 , ")" , content , linksToAdd );
240
+ return extractLink ( position - 1 , ')' , content , result );
255
241
}
256
242
}
257
243
258
244
259
- private static class Segment implements Comparable <Segment > {
245
+ private static class ContentChunkInfo implements Comparable <ContentChunkInfo > {
260
246
261
247
private final int start ;
262
248
263
249
private final int end ;
264
250
265
- private final boolean link ;
251
+ private final boolean isLink ;
266
252
267
253
268
- public Segment (int start , int end , boolean isLink ) {
254
+ ContentChunkInfo (int start , int end , boolean isLink ) {
269
255
this .start = start ;
270
256
this .end = end ;
271
- this .link = isLink ;
257
+ this .isLink = isLink ;
272
258
}
273
259
274
260
@@ -281,15 +267,15 @@ public int getEnd() {
281
267
}
282
268
283
269
public boolean isLink () {
284
- return this .link ;
270
+ return this .isLink ;
285
271
}
286
272
287
273
public String getContent (String fullContent ) {
288
274
return fullContent .substring (this .start , this .end );
289
275
}
290
276
291
277
@ Override
292
- public int compareTo (Segment other ) {
278
+ public int compareTo (ContentChunkInfo other ) {
293
279
return (this .start < other .start ? -1 : (this .start == other .start ? 0 : 1 ));
294
280
}
295
281
@@ -298,8 +284,8 @@ public boolean equals(@Nullable Object obj) {
298
284
if (this == obj ) {
299
285
return true ;
300
286
}
301
- if (obj != null && obj instanceof Segment ) {
302
- Segment other = (Segment ) obj ;
287
+ if (obj != null && obj instanceof ContentChunkInfo ) {
288
+ ContentChunkInfo other = (ContentChunkInfo ) obj ;
303
289
return (this .start == other .start && this .end == other .end );
304
290
}
305
291
return false ;
0 commit comments