Clover Coverage Report - XWiki Rendering - Parent POM 4.0-SNAPSHOT (Aggregated)
Coverage timestamp: Mon Mar 12 2012 18:03:13 CET
../../../../../../img/srcFileCovDistChart10.png 0% of files have more coverage
73   337   28   6.64
28   172   0.38   11
11     2.55  
1    
 
  GenericLinkReferenceParser       Line # 51 73 0% 28 3 97.3% 0.97321427
 
  (116)
 
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.parser.reference;
21   
22    import java.util.Arrays;
23    import java.util.List;
24   
25    import javax.inject.Inject;
26    import javax.inject.Named;
27    import javax.inject.Singleton;
28   
29    import org.apache.commons.lang3.StringUtils;
30    import org.xwiki.component.annotation.Component;
31    import org.xwiki.component.manager.ComponentLookupException;
32    import org.xwiki.component.manager.ComponentManager;
33    import org.xwiki.rendering.listener.reference.DocumentResourceReference;
34    import org.xwiki.rendering.listener.reference.InterWikiResourceReference;
35    import org.xwiki.rendering.listener.reference.ResourceReference;
36    import org.xwiki.rendering.listener.reference.ResourceType;
37    import org.xwiki.rendering.parser.ResourceReferenceParser;
38    import org.xwiki.rendering.parser.ResourceReferenceTypeParser;
39    import org.xwiki.rendering.wiki.WikiModel;
40   
41    /**
42    * Each syntax should have its own resource reference parser. However while we wait for syntax specific parser to be
43    * implemented this generic parser should provide a good approximation.
44    *
45    * @version $Id: 502324efc93d8fb0153ce00dfd6a14b93e46ddd9 $
46    * @since 2.5RC1
47    */
48    @Component
49    @Named("default/link")
50    @Singleton
 
51    public class GenericLinkReferenceParser implements ResourceReferenceParser
52    {
53    /**
54    * Interwiki separator.
55    */
56    public static final String SEPARATOR_INTERWIKI = "@";
57   
58    /**
59    * Query String separator.
60    */
61    public static final String SEPARATOR_QUERYSTRING = "?";
62   
63    /**
64    * Anchor separator.
65    */
66    public static final String SEPARATOR_ANCHOR = "#";
67   
68    /**
69    * Escape character to allow "#", "@" and "?" characters in a reference's name.
70    */
71    public static final char ESCAPE_CHAR = '\\';
72   
73    /**
74    * Escapes to remove from the document reference part when parsing the raw reference (i.e. excluding query string,
75    * anchor and interwiki parts). Note that we don't remove the escaped escape char since this is how an escape char
76    * is represented in an Entity Reference.
77    */
78    private static final String[] ESCAPES_REFERENCE = new String[] {ESCAPE_CHAR + SEPARATOR_QUERYSTRING,
79    ESCAPE_CHAR + SEPARATOR_INTERWIKI, ESCAPE_CHAR + SEPARATOR_ANCHOR};
80   
81    /**
82    * Escapes to remove from the query string, anchor and interwiki parts when parsing the raw reference.
83    */
84    private static final String[] ESCAPES_EXTRA = new String[] {ESCAPE_CHAR + SEPARATOR_QUERYSTRING,
85    ESCAPE_CHAR + SEPARATOR_INTERWIKI, ESCAPE_CHAR + SEPARATOR_ANCHOR, "" + ESCAPE_CHAR + ESCAPE_CHAR};
86   
87    /**
88    * Escapes to remove the interwiki content.
89    */
90    private static final String[] ESCAPE_INTERWIKI = new String[] {"" + ESCAPE_CHAR + ESCAPE_CHAR, "" + ESCAPE_CHAR};
91   
92    /**
93    * Replacement chars for the escapes to be removed from the reference part.
94    */
95    private static final String[] ESCAPE_REPLACEMENTS_REFERENCE = new String[] {SEPARATOR_QUERYSTRING,
96    SEPARATOR_INTERWIKI, SEPARATOR_ANCHOR};
97   
98    /**
99    * Replacement chars for the escapes to be removed from the query string, anchor and interwiki parts.
100    */
101    private static final String[] ESCAPE_REPLACEMENTS_EXTRA = new String[] {SEPARATOR_QUERYSTRING, SEPARATOR_INTERWIKI,
102    SEPARATOR_ANCHOR, "" + ESCAPE_CHAR};
103   
104    /**
105    * Replacements chars for the escapes to be removed from the interwiki content.
106    */
107    private static final String[] ESCAPE_REPLACEMENTS_INTERWIKI = new String[] {"" + ESCAPE_CHAR, ""};
108   
109    /**
110    * The list of recognized URL prefixes.
111    */
112    private static final List<String> URI_PREFIXES = Arrays.asList("mailto");
113   
114    /**
115    * Parser to parse link references pointing to URLs.
116    */
117    @Inject
118    @Named("url")
119    private ResourceReferenceTypeParser urlResourceReferenceTypeParser;
120   
121    /**
122    * Used to verify if we're in wiki mode or not by looking up an implementation of
123    * {@link org.xwiki.rendering.wiki.WikiModel}.
124    */
125    @Inject
126    private ComponentManager componentManager;
127   
128    /**
129    * @return the list of URI prefixes the link parser recognizes
130    */
 
131  1 toggle protected List<String> getAllowedURIPrefixes()
132    {
133  1 return URI_PREFIXES;
134    }
135   
 
136  212 toggle @Override
137    public ResourceReference parse(String rawReference)
138    {
139    // Step 1: If we're not in wiki mode then all links are URL links.
140  212 if (!isInWikiMode()) {
141  56 ResourceReference resourceReference = new ResourceReference(rawReference, ResourceType.URL);
142  56 resourceReference.setTyped(false);
143  56 return resourceReference;
144    }
145   
146    // Step 2: Check if it's a known URI by looking for one of the known URI schemes. If not, check if it's a URL.
147  156 ResourceReference resourceReference = parseURILinks(rawReference);
148  156 if (resourceReference != null) {
149  60 return resourceReference;
150    }
151   
152    // Step 3: Look for an InterWiki link
153  96 StringBuffer content = new StringBuffer(rawReference);
154  96 resourceReference = parseInterWikiLinks(content);
155  96 if (resourceReference != null) {
156  11 return resourceReference;
157    }
158   
159    // Step 4: Consider that we have a reference to a document.
160  85 return parseDocumentLink(content);
161    }
162   
163    /**
164    * Construct a Document Link reference out of the passed content.
165    *
166    * @param content the string containing the Document link reference
167    * @return the parsed Link Object corresponding to the Document link reference
168    */
 
169  85 toggle private ResourceReference parseDocumentLink(StringBuffer content)
170    {
171  85 String queryString = null;
172  85 String text = parseElementAfterString(content, SEPARATOR_QUERYSTRING);
173  85 if (text != null) {
174  8 queryString = removeEscapesFromExtraParts(text);
175    }
176   
177  85 String anchor = null;
178  85 text = parseElementAfterString(content, SEPARATOR_ANCHOR);
179  85 if (text != null) {
180  13 anchor = removeEscapesFromExtraParts(text);
181    }
182   
183  85 DocumentResourceReference reference =
184    new DocumentResourceReference(removeEscapesFromReferencePart(content.toString()));
185  85 reference.setTyped(false);
186  85 reference.setQueryString(queryString);
187  85 reference.setAnchor(anchor);
188   
189  85 return reference;
190    }
191   
192    /**
193    * Check if the passed link references is an URI link reference.
194    *
195    * @param rawLink the original reference to parse
196    * @return the parsed Link object or null if the passed reference is not an URI link reference or if no URI type
197    * parser was found for the passed URI scheme
198    */
 
199  156 toggle private ResourceReference parseURILinks(String rawLink)
200    {
201  156 ResourceReference result = null;
202  156 int uriSchemeDelimiterPos = rawLink.indexOf(":");
203  156 if (uriSchemeDelimiterPos > -1) {
204  63 String scheme = rawLink.substring(0, uriSchemeDelimiterPos);
205  63 String reference = rawLink.substring(uriSchemeDelimiterPos + 1);
206  63 if (getAllowedURIPrefixes().contains(scheme)) {
207  29 try {
208  29 ResourceReference resourceReference =
209    this.componentManager.lookup(ResourceReferenceTypeParser.class, scheme).parse(reference);
210  29 if (resourceReference != null) {
211  29 result = resourceReference;
212    }
213    } catch (ComponentLookupException e) {
214    // Failed to lookup component, this shouldn't happen but ignore it.
215    }
216    } else {
217    // Check if it's a URL
218  34 ResourceReference resourceReference = this.urlResourceReferenceTypeParser.parse(rawLink);
219  34 if (resourceReference != null) {
220  31 resourceReference.setTyped(false);
221  31 result = resourceReference;
222    }
223    }
224    }
225  156 return result;
226    }
227   
228    /**
229    * Check if the passed link references is an interwiki link reference.
230    *
231    * @param content the original content to parse
232    * @return the parsed Link object or null if the passed reference is not an interwiki link reference
233    */
 
234  96 toggle private ResourceReference parseInterWikiLinks(StringBuffer content)
235    {
236  96 ResourceReference result = null;
237  96 String interWikiAlias = parseElementAfterString(content, SEPARATOR_INTERWIKI);
238  96 if (interWikiAlias != null) {
239  11 InterWikiResourceReference link = new InterWikiResourceReference(removeEscapes(content.toString()));
240  11 link.setInterWikiAlias(removeEscapes(interWikiAlias));
241  11 result = link;
242    }
243  96 return result;
244    }
245   
246    /**
247    * Find out the element located to the right of the passed separator.
248    *
249    * @param content the string to parse. This parameter will be modified by the method to remove the parsed content.
250    * @param separator the separator string to locate the element
251    * @return the parsed element or null if the separator string wasn't found
252    */
 
253  266 toggle protected String parseElementAfterString(StringBuffer content, String separator)
254    {
255  266 String element = null;
256   
257    // Find the first non escaped separator (starting from the end of the content buffer).
258  266 int index = content.lastIndexOf(separator);
259  278 while (index != -1) {
260    // Check if the element is found and it's not escaped.
261  44 if (!shouldEscape(content, index)) {
262  32 element = content.substring(index + separator.length()).trim();
263  32 content.delete(index, content.length());
264  32 break;
265    }
266   
267  12 if (index > 0) {
268  12 index = content.lastIndexOf(separator, index - 1);
269    } else {
270  0 break;
271    }
272    }
273   
274  266 return element;
275    }
276   
277    /**
278    * @return true if we're in wiki mode (ie there's no implementing class for
279    * {@link org.xwiki.rendering.wiki.WikiModel})
280    */
 
281  212 toggle private boolean isInWikiMode()
282    {
283  212 boolean result = true;
284  212 try {
285  212 this.componentManager.lookup(WikiModel.class);
286    } catch (ComponentLookupException e) {
287  56 result = false;
288    }
289  212 return result;
290    }
291   
292    /**
293    * Count the number of escape chars before a given character and if that number is odd then that character should be
294    * escaped.
295    *
296    * @param content the content in which to check for escapes
297    * @param charPosition the position of the char for which to decide if it should be escaped or not
298    * @return true if the character should be escaped
299    */
 
300  44 toggle private boolean shouldEscape(StringBuffer content, int charPosition)
301    {
302  44 int counter = 0;
303  44 int pos = charPosition - 1;
304  76 while (pos > -1 && content.charAt(pos) == ESCAPE_CHAR) {
305  32 counter++;
306  32 pos--;
307    }
308  44 return (counter % 2 != 0);
309    }
310   
311    /**
312    * @param text the reference from which to remove unneeded escapes
313    * @return the cleaned text
314    */
 
315  85 toggle private String removeEscapesFromReferencePart(String text)
316    {
317  85 return StringUtils.replaceEach(text, ESCAPES_REFERENCE, ESCAPE_REPLACEMENTS_REFERENCE);
318    }
319   
320    /**
321    * @param text the reference from which to remove unneeded escapes
322    * @return the cleaned text
323    */
 
324  21 toggle private String removeEscapesFromExtraParts(String text)
325    {
326  21 return StringUtils.replaceEach(text, ESCAPES_EXTRA, ESCAPE_REPLACEMENTS_EXTRA);
327    }
328   
329    /**
330    * @param text the reference from which to remove unneeded escapes
331    * @return the cleaned text
332    */
 
333  22 toggle private String removeEscapes(String text)
334    {
335  22 return StringUtils.replaceEach(text, ESCAPE_INTERWIKI, ESCAPE_REPLACEMENTS_INTERWIKI);
336    }
337    }