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

File GenericLinkReferenceParser.java

 

Coverage histogram

../../../../../../img/srcFileCovDistChart10.png
0% of files have more coverage

Code metrics

32
72
10
1
328
166
28
0.39
7.2
10
2.8

Classes

Class Line # Actions
GenericLinkReferenceParser 49 72 0% 28 3
0.973684297.4%
 

Contributing tests

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