1. Project Clover database Tue Dec 20 2016 21:24:09 CET
  2. Package org.xwiki.rendering.internal.macro.velocity.filter

File HTMLVelocityMacroFilter.java

 

Coverage histogram

../../../../../../../img/srcFileCovDistChart9.png
38% of files have more coverage

Code metrics

24
65
12
2
334
156
28
0.43
5.42
6
2.33

Classes

Class Line # Actions
HTMLVelocityMacroFilter 50 60 0% 23 4
0.9560439695.6%
HTMLVelocityMacroFilter.FilterContext 276 5 0% 5 10
0.00%
 

Contributing tests

This file is covered by 10 tests. .

Source view

1    /*
2    * See the NOTICE file distributed with this work for additional
3    * information regarding copyright ownership.
4    *
5    * This is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU Lesser General Public License as
7    * published by the Free Software Foundation; either version 2.1 of
8    * the License, or (at your option) any later version.
9    *
10    * This software is distributed in the hope that it will be useful,
11    * but WITHOUT ANY WARRANTY; without even the implied warranty of
12    * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13    * Lesser General Public License for more details.
14    *
15    * You should have received a copy of the GNU Lesser General Public
16    * License along with this software; if not, write to the Free
17    * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
18    * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
19    */
20    package org.xwiki.rendering.internal.macro.velocity.filter;
21   
22    import java.util.regex.Pattern;
23   
24    import javax.inject.Inject;
25    import javax.inject.Named;
26    import javax.inject.Singleton;
27   
28    import org.apache.velocity.VelocityContext;
29    import org.slf4j.Logger;
30    import org.xwiki.component.annotation.Component;
31    import org.xwiki.component.phase.Initializable;
32    import org.xwiki.component.phase.InitializationException;
33    import org.xwiki.rendering.macro.velocity.filter.VelocityMacroFilter;
34    import org.xwiki.velocity.internal.util.InvalidVelocityException;
35    import org.xwiki.velocity.internal.util.VelocityParser;
36    import org.xwiki.velocity.internal.util.VelocityParserContext;
37    import org.xwiki.velocity.internal.util.VelocityBlock.VelocityType;
38   
39    /**
40    * Replace each white space/new lines group by a space and inject $nl and $sp bindings in {@link VelocityContext} which
41    * are used to respectively force a new line or a space before executing the velocity script. The bindings are removed
42    * after script execution.
43    *
44    * @version $Id: 023c47db2061b938d2c24cf9f94609379824faba $
45    * @since 2.0M1
46    */
47    @Component
48    @Named("html")
49    @Singleton
 
50    public class HTMLVelocityMacroFilter implements VelocityMacroFilter, Initializable
51    {
52    /**
53    * The name of the new line binding.
54    */
55    private static final String BINDING_NEWLINE = "nl";
56   
57    /**
58    * The value of the $nl binding.
59    */
60    private static final String NEWLINE = "\n";
61   
62    /**
63    * The name of the space binding.
64    */
65    private static final String BINDING_SPACE = "sp";
66   
67    /**
68    * The value of the $sp binding.
69    */
70    private static final String SPACE = " ";
71   
72    /**
73    * Match not UNIX new lines to replace them.
74    */
75    private static final Pattern MSNEWLINE_PATTERN = Pattern.compile("\\r\\n|\\r");
76   
77    /**
78    * The logger to use for logging.
79    */
80    @Inject
81    private Logger logger;
82   
83    /**
84    * Used to parser content to clean and match system directives and $nl variables.
85    */
86    private VelocityParser velocityParser;
87   
 
88  10 toggle @Override
89    public void initialize() throws InitializationException
90    {
91  10 this.velocityParser = new VelocityParser();
92    }
93   
 
94  46 toggle @Override
95    public String before(String content, VelocityContext velocityContext)
96    {
97    // Add bindings
98  46 velocityContext.put(BINDING_NEWLINE, NEWLINE);
99  46 velocityContext.put(BINDING_SPACE, SPACE);
100   
101  46 return clean(content);
102    }
103   
104    /**
105    * Clean whites spaces in the velocity macro content.
106    * <p>
107    * Here a the rules:
108    * <ul>
109    * <li>any group of white spaces is replaced by a space</li>
110    * <li>all white spaces after a velocity directive consuming following newline (#if, #set, etc.) are removed (no
111    * replacement)</li>
112    * <li>all white spaces before or after $nl are removed (no replacement)</li>
113    * <li>all white spaces at the beginning and at the end of the content are removed (no replacement)</li>
114    * <li>all velocity comments are removed</li>
115    * </ul>
116    *
117    * @param content the content to clean
118    * @return the cleaned content
119    */
 
120  46 toggle public String clean(String content)
121    {
122  46 StringBuffer contentBuffer = new StringBuffer();
123   
124  46 char[] array = MSNEWLINE_PATTERN.matcher(content).replaceAll(NEWLINE).toCharArray();
125   
126  46 VelocityParserContext context = new VelocityParserContext();
127  46 FilterContext filterContext = new FilterContext();
128   
129  46 int i = 0;
130  286 while (i < array.length) {
131  240 try {
132  240 if (array[i] == '#') {
133  40 i = cleanKeyWord(contentBuffer, array, i, context, filterContext);
134   
135  38 continue;
136  200 } else if (array[i] == '$') {
137  20 i = cleanVar(contentBuffer, array, i, context, filterContext);
138   
139  15 continue;
140  180 } else if (Character.isWhitespace(array[i])) {
141  94 if (!filterContext.removeWhiteSpaces && contentBuffer.length() > 0) {
142  55 filterContext.foundWhiteSpace = true;
143    }
144   
145  94 ++i;
146   
147  94 continue;
148    }
149    } catch (InvalidVelocityException e) {
150  7 this.logger.debug("Not a valid velocity keyword at char [" + i + "]", e);
151    }
152   
153  93 flushWhiteSpaces(contentBuffer, filterContext, false);
154   
155  93 contentBuffer.append(array[i]);
156   
157  93 ++i;
158    }
159   
160  46 flushWhiteSpaces(contentBuffer, filterContext, true);
161   
162  46 return contentBuffer.toString();
163    }
164   
165    /**
166    * Handle velocity comments and directive.
167    *
168    * @param contentBuffer the final result buffer
169    * @param array the source
170    * @param currentIndex the current index in the source
171    * @param context the velocity parser context
172    * @param filterContext the filter context
173    * @return the index after the comment or directive
174    * @throws InvalidVelocityException not velocity
175    */
 
176  40 toggle private int cleanKeyWord(StringBuffer contentBuffer, char[] array, int currentIndex, VelocityParserContext context,
177    FilterContext filterContext) throws InvalidVelocityException
178    {
179  40 int i = this.velocityParser.getKeyWord(array, currentIndex, null, context);
180   
181  38 if (context.getType() != VelocityType.COMMENT) {
182  35 if (context.getType() == VelocityType.DIRECTIVE) {
183  31 if (filterContext.wsGroup.length() == 0) {
184  23 flushWhiteSpaces(filterContext.wsGroup, filterContext, false);
185    }
186   
187  31 filterContext.wsGroup.append(array, currentIndex, i - currentIndex);
188   
189  31 filterContext.removeWhiteSpaces = true;
190    } else {
191  4 flushWhiteSpaces(contentBuffer, filterContext, false);
192   
193  4 contentBuffer.append(array, currentIndex, i - currentIndex);
194    }
195    }
196   
197  38 return i;
198    }
199   
200    /**
201    * Handle velocity variables.
202    *
203    * @param contentBuffer the final result buffer
204    * @param array the source
205    * @param currentIndex the current index in the source
206    * @param context the velocity parser context
207    * @param filterContext the filter context
208    * @return the index after the variable
209    * @throws InvalidVelocityException not velocity
210    */
 
211  20 toggle private int cleanVar(StringBuffer contentBuffer, char[] array, int currentIndex, VelocityParserContext context,
212    FilterContext filterContext) throws InvalidVelocityException
213    {
214  20 StringBuffer varName = new StringBuffer();
215  20 int i = this.velocityParser.getVar(array, currentIndex, varName, null, context);
216   
217  15 if (varName.toString().equals(BINDING_NEWLINE)) {
218  8 flushWhiteSpaces(contentBuffer, filterContext, true);
219   
220  8 contentBuffer.append("${nl}");
221  8 filterContext.removeWhiteSpaces = true;
222    } else {
223  7 flushWhiteSpaces(contentBuffer, filterContext, false);
224   
225  7 contentBuffer.append(array, currentIndex, i - currentIndex);
226    }
227   
228  15 return i;
229    }
230   
231    /**
232    * Flush stored velocity directive which does not produce output (like #if, set, etc.). I also append a space if
233    * there was a space in the group source. This is not make sure the whole no output velocity content is taken into
234    * account when cleaning white spaces to be sure to have only one space at the end between two really generated
235    * text.
236    *
237    * @param contentBuffer the final result buffer
238    * @param filterContext the filter context
239    * @param forceNoSpace if true no space is printed (only {@link FilterContext#wsGroup})
240    */
 
241  181 toggle private void flushWhiteSpaces(StringBuffer contentBuffer, FilterContext filterContext, boolean forceNoSpace)
242    {
243  181 if (filterContext.wsGroup.length() > 0) {
244  23 boolean space = filterContext.wsGroup.charAt(0) == ' ';
245   
246  23 if (forceNoSpace && space) {
247  2 contentBuffer.append(filterContext.wsGroup, 1, filterContext.wsGroup.length());
248    } else {
249  21 contentBuffer.append(filterContext.wsGroup);
250    }
251  23 filterContext.wsGroup.setLength(0);
252    }
253   
254  181 if (filterContext.foundWhiteSpace && !forceNoSpace) {
255  24 contentBuffer.append(' ');
256    }
257   
258  181 filterContext.foundWhiteSpace = false;
259  181 filterContext.removeWhiteSpaces = false;
260    }
261   
 
262  0 toggle @Override
263    public String after(String content, VelocityContext velocityContext)
264    {
265  0 velocityContext.remove(BINDING_NEWLINE);
266  0 velocityContext.remove(BINDING_SPACE);
267   
268  0 return content;
269    }
270   
271    /**
272    * Used to return store and retrieve some information during the filtering process.
273    *
274    * @version $Id: 023c47db2061b938d2c24cf9f94609379824faba $
275    */
 
276    static class FilterContext
277    {
278    /**
279    * Indicate if at least one white space has been found.
280    */
281    private boolean foundWhiteSpace;
282   
283    /**
284    * Indicate if whites space has to be removed instead of replaced by a unique space.
285    */
286    private boolean removeWhiteSpaces;
287   
288    /**
289    * @see #getWsGroup()
290    */
291    private StringBuffer wsGroup = new StringBuffer();
292   
293    /**
294    * @return Indicate if at least one white space has been found.
295    */
 
296  0 toggle public boolean isFoundWhiteSpace()
297    {
298  0 return this.foundWhiteSpace;
299    }
300   
301    /**
302    * @param foundWhiteSpace Indicate if at least one white space has been found.
303    */
 
304  0 toggle public void setFoundWhiteSpace(boolean foundWhiteSpace)
305    {
306  0 this.foundWhiteSpace = foundWhiteSpace;
307    }
308   
309    /**
310    * @return Indicate if whites space has to be removed instead of replaced by a unique space.
311    */
 
312  0 toggle public boolean isRemoveWhiteSpaces()
313    {
314  0 return this.removeWhiteSpaces;
315    }
316   
317    /**
318    * @param removeWhiteSpaces Indicate if whites space has to be removed instead of replaced by a unique space.
319    */
 
320  0 toggle public void setRemoveWhiteSpaces(boolean removeWhiteSpaces)
321    {
322  0 this.removeWhiteSpaces = removeWhiteSpaces;
323    }
324   
325    /**
326    * @return Used to store velocity directive whish does not generate output until something else is found. It's
327    * used to match a whole group of white space including no output velocity code.
328    */
 
329  0 toggle public StringBuffer getWsGroup()
330    {
331  0 return this.wsGroup;
332    }
333    }
334    }