1. Project Clover database Sat Feb 2 2019 06:45:20 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
329
167
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: 4726539d0b9dcf4bec57c7c6b057a9f6310b6517 $
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, String.valueOf(ESCAPE_CHAR) + ESCAPE_CHAR };
84   
85    /**
86    * Escapes to remove the interwiki content.
87    */
88    private static final String[] ESCAPE_INTERWIKI =
89    new String[] { String.valueOf(ESCAPE_CHAR) + ESCAPE_CHAR, String.valueOf(ESCAPE_CHAR) };
90   
91    /**
92    * Replacement chars for the escapes to be removed from the reference part.
93    */
94    private static final String[] ESCAPE_REPLACEMENTS_REFERENCE = new String[] { SEPARATOR_QUERYSTRING,
95    SEPARATOR_INTERWIKI, SEPARATOR_ANCHOR };
96   
97    /**
98    * Replacement chars for the escapes to be removed from the query string, anchor and interwiki parts.
99    */
100    private static final String[] ESCAPE_REPLACEMENTS_EXTRA = new String[] { SEPARATOR_QUERYSTRING,
101    SEPARATOR_INTERWIKI, SEPARATOR_ANCHOR, String.valueOf(ESCAPE_CHAR) };
102   
103    /**
104    * Replacements chars for the escapes to be removed from the interwiki content.
105    */
106    private static final String[] ESCAPE_REPLACEMENTS_INTERWIKI = new String[] { String.valueOf(ESCAPE_CHAR), "" };
107   
108    /**
109    * The list of recognized URL prefixes.
110    */
111    private static final List<String> URI_PREFIXES = Arrays.asList("mailto");
112   
113    /**
114    * Parser to parse link references pointing to URLs.
115    */
116    @Inject
117    @Named("url")
118    private ResourceReferenceTypeParser urlResourceReferenceTypeParser;
119   
120    @Inject
121    @Named("link/untyped")
122    private ResourceReferenceParser untypedLinkReferenceParser;
123   
124    /**
125    * @return the list of URI prefixes the link parser recognizes
126    */
 
127  1 toggle protected List<String> getAllowedURIPrefixes()
128    {
129  1 return URI_PREFIXES;
130    }
131   
 
132  1889 toggle @Override
133    public ResourceReference parse(String rawReference)
134    {
135    // 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.
136  1889 ResourceReference resourceReference = parseURILinks(rawReference);
137  1889 if (resourceReference != null) {
138  518 return resourceReference;
139    }
140   
141    // Step 2: Look for an InterWiki link
142  1371 StringBuilder content = new StringBuilder(rawReference);
143  1371 resourceReference = parseInterWikiLinks(content);
144  1371 if (resourceReference != null) {
145  11 return resourceReference;
146    }
147   
148    // Step 3: If we're in non wiki mode, we consider the reference to be a URL.
149  1360 if (!isInWikiMode()) {
150  35 resourceReference = new ResourceReference(rawReference, ResourceType.URL);
151  35 resourceReference.setTyped(false);
152  35 return resourceReference;
153    }
154   
155    // Step 4: Consider that we have a reference to a document
156  1325 return parseDocumentLink(content);
157    }
158   
159    /**
160    * Construct a Document Link reference out of the passed content.
161    *
162    * @param content the string containing the Document link reference
163    * @return the parsed Link Object corresponding to the Document link reference
164    */
 
165  1325 toggle private ResourceReference parseDocumentLink(StringBuilder content)
166    {
167    // Extract any query string.
168  1325 String queryString = null;
169  1325 String text = parseElementAfterString(content, SEPARATOR_QUERYSTRING);
170  1325 if (text != null) {
171  484 queryString = removeEscapesFromExtraParts(text);
172    }
173   
174    // Extract any anchor.
175  1325 String anchor = null;
176  1325 text = parseElementAfterString(content, SEPARATOR_ANCHOR);
177  1325 if (text != null) {
178  14 anchor = removeEscapesFromExtraParts(text);
179    }
180   
181    // Make sure to unescape the remaining reference string.
182  1325 String unescapedReferenceString = removeEscapesFromReferencePart(content.toString());
183    // Parse the string as an untyped link reference.
184  1325 ResourceReference reference = untypedLinkReferenceParser.parse(unescapedReferenceString);
185  1325 reference.setTyped(false);
186   
187    // Set any previously extracted parameters.
188  1325 if (StringUtils.isNotBlank(queryString)) {
189  484 reference.setParameter(DocumentResourceReference.QUERY_STRING, queryString);
190    }
191  1325 if (StringUtils.isNotBlank(anchor)) {
192  14 reference.setParameter(DocumentResourceReference.ANCHOR, anchor);
193    }
194   
195  1325 return reference;
196    }
197   
198    /**
199    * Check if the passed link references is an URI link reference.
200    *
201    * @param rawLink the original reference to parse
202    * @return the parsed Link object or null if the passed reference is not an URI link reference or if no URI type
203    * parser was found for the passed URI scheme
204    */
 
205  1889 toggle private ResourceReference parseURILinks(String rawLink)
206    {
207  1889 ResourceReference result = null;
208  1889 int uriSchemeDelimiterPos = rawLink.indexOf(':');
209  1889 if (uriSchemeDelimiterPos > -1) {
210  548 String scheme = rawLink.substring(0, uriSchemeDelimiterPos);
211  548 String reference = rawLink.substring(uriSchemeDelimiterPos + 1);
212  548 if (getAllowedURIPrefixes().contains(scheme)) {
213  33 try {
214  33 ResourceReferenceTypeParser parser =
215    this.componentManagerProvider.get().getInstance(ResourceReferenceTypeParser.class, scheme);
216  33 ResourceReference resourceReference = parser.parse(reference);
217  33 if (resourceReference != null) {
218  33 result = resourceReference;
219    }
220    } catch (ComponentLookupException e) {
221    // Failed to lookup component, this shouldn't happen but ignore it.
222    }
223    } else {
224    // Check if it's a URL
225  515 ResourceReference resourceReference = this.urlResourceReferenceTypeParser.parse(rawLink);
226  515 if (resourceReference != null) {
227  485 resourceReference.setTyped(false);
228  485 result = resourceReference;
229    }
230    }
231    }
232  1889 return result;
233    }
234   
235    /**
236    * Check if the passed link references is an interwiki link reference.
237    *
238    * @param content the original content to parse
239    * @return the parsed Link object or null if the passed reference is not an interwiki link reference
240    */
 
241  1371 toggle private ResourceReference parseInterWikiLinks(StringBuilder content)
242    {
243  1371 ResourceReference result = null;
244  1371 String interWikiAlias = parseElementAfterString(content, SEPARATOR_INTERWIKI);
245  1371 if (interWikiAlias != null) {
246  11 InterWikiResourceReference link = new InterWikiResourceReference(removeEscapes(content.toString()));
247  11 link.setInterWikiAlias(removeEscapes(interWikiAlias));
248  11 result = link;
249    }
250  1371 return result;
251    }
252   
253    /**
254    * Find out the element located to the right of the passed separator.
255    *
256    * @param content the string to parse. This parameter will be modified by the method to remove the parsed content.
257    * @param separator the separator string to locate the element
258    * @return the parsed element or null if the separator string wasn't found
259    */
 
260  4021 toggle protected String parseElementAfterString(StringBuilder content, String separator)
261    {
262  4021 String element = null;
263   
264    // Find the first non escaped separator (starting from the end of the content buffer).
265  4021 int index = content.lastIndexOf(separator);
266  4033 while (index != -1) {
267    // Check if the element is found and it's not escaped.
268  521 if (!shouldEscape(content, index)) {
269  509 element = content.substring(index + separator.length()).trim();
270  509 content.delete(index, content.length());
271  509 break;
272    }
273   
274  12 if (index > 0) {
275  12 index = content.lastIndexOf(separator, index - 1);
276    } else {
277  0 break;
278    }
279    }
280   
281  4021 return element;
282    }
283   
284    /**
285    * Count the number of escape chars before a given character and if that number is odd then that character should be
286    * escaped.
287    *
288    * @param content the content in which to check for escapes
289    * @param charPosition the position of the char for which to decide if it should be escaped or not
290    * @return true if the character should be escaped
291    */
 
292  521 toggle private boolean shouldEscape(StringBuilder content, int charPosition)
293    {
294  521 int counter = 0;
295  521 int pos = charPosition - 1;
296  553 while (pos > -1 && content.charAt(pos) == ESCAPE_CHAR) {
297  32 counter++;
298  32 pos--;
299    }
300  521 return counter % 2 != 0;
301    }
302   
303    /**
304    * @param text the reference from which to remove unneeded escapes
305    * @return the cleaned text
306    */
 
307  1325 toggle private String removeEscapesFromReferencePart(String text)
308    {
309  1325 return StringUtils.replaceEach(text, ESCAPES_REFERENCE, ESCAPE_REPLACEMENTS_REFERENCE);
310    }
311   
312    /**
313    * @param text the reference from which to remove unneeded escapes
314    * @return the cleaned text
315    */
 
316  498 toggle private String removeEscapesFromExtraParts(String text)
317    {
318  498 return StringUtils.replaceEach(text, ESCAPES_EXTRA, ESCAPE_REPLACEMENTS_EXTRA);
319    }
320   
321    /**
322    * @param text the reference from which to remove unneeded escapes
323    * @return the cleaned text
324    */
 
325  22 toggle private String removeEscapes(String text)
326    {
327  22 return StringUtils.replaceEach(text, ESCAPE_INTERWIKI, ESCAPE_REPLACEMENTS_INTERWIKI);
328    }
329    }