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

File XWikiSyntaxEscapeHandler.java

 

Coverage histogram

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

Code metrics

22
51
8
1
205
108
21
0.41
6.38
8
2.62

Classes

Class Line # Actions
XWikiSyntaxEscapeHandler 34 51 0% 21 2
0.9753086697.5%
 

Contributing tests

This file is covered by 270 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.renderer.xwiki20;
21   
22    import java.util.regex.Matcher;
23    import java.util.regex.Pattern;
24   
25    import org.apache.commons.lang3.StringUtils;
26    import org.xwiki.rendering.listener.chaining.BlockStateChainingListener;
27   
28    /**
29    * Escape characters that would be confused for XWiki wiki syntax if they were not escaped.
30    *
31    * @version $Id: 67fa179feb6db9210bb973d2ce739da5c6617e64 $
32    * @since 2.0M3
33    */
 
34    public class XWikiSyntaxEscapeHandler
35    {
36    public static final Pattern STARLISTEND_PATTERN = Pattern.compile("(\\**([:;]*|1+\\.)?\\p{Blank})");
37   
38    private static final Pattern LIST_PATTERN = Pattern
39    .compile("\\p{Blank}*((\\*+[:;]*)|([1*]+\\.[:;]*)|([:;]+))\\p{Blank}+");
40   
41    private static final Pattern QUOTE_PATTERN = Pattern.compile("(\\>+)");
42   
43    private static final Pattern HEADER_PATTERN = Pattern.compile("\\p{Blank}*(=+)");
44   
45    private static final Pattern TABLE_PATTERN = Pattern.compile("\\p{Blank}*(\\||!!)");
46   
47    /**
48    * Note that we take care to not match if the first character is preceded by an escape (i.e. '~).
49    */
50    private static final Pattern DOUBLE_CHARS_PATTERN = Pattern.compile(
51    "(?<!~)\\/\\/|(?<!~)\\*\\*|(?<!~)__|(?<!~)--|(?<!~)\\^\\^|(?<!~),,|(?<!~)##|(?<!~)\\\\\\\\");
52   
53    public static final String ESCAPE_CHAR = "~";
54   
55    private boolean beforeLink = false;
56   
57    private boolean onNewLine = true;
58   
 
59  2536 toggle public void setOnNewLine(boolean onNewLine)
60    {
61  2536 this.onNewLine = onNewLine;
62    }
63   
 
64  712 toggle public boolean isOnNewLine()
65    {
66  712 return this.onNewLine;
67    }
68   
 
69  680 toggle public void escape(StringBuffer accumulatedBuffer, XWikiSyntaxListenerChain listenerChain, boolean escapeLastChar,
70    Pattern escapeFirstIfMatching)
71    {
72  680 BlockStateChainingListener blockStateListener = listenerChain.getBlockStateChainingListener();
73   
74    // Escape tilde symbol (i.e. the escape character).
75    // Note: This needs to be the first replacement since other replacements below also use the tilde symbol
76  680 replaceAll(accumulatedBuffer, ESCAPE_CHAR, ESCAPE_CHAR + ESCAPE_CHAR);
77   
78    // Escape anything that looks like starting of custom parameters
79  680 replaceAll(accumulatedBuffer, "(%", ESCAPE_CHAR + "(%");
80   
81    // When in a paragraph we need to escape symbols that are at beginning of lines and that could be confused
82    // with list items, headers or tables.
83  680 if (blockStateListener.isInLine() && isOnNewLine()) {
84   
85    // Look for list pattern at beginning of line and escape the first character only (it's enough)
86  228 escapeFirstMatchedCharacter(LIST_PATTERN, accumulatedBuffer);
87   
88    // Look for header pattern at beginning of line and escape the first character only (it's enough)
89  228 escapeFirstMatchedCharacter(HEADER_PATTERN, accumulatedBuffer);
90   
91    // Look for table character patterns at beginning of line and escape the first character only (it's enough)
92  228 escapeFirstMatchedCharacter(TABLE_PATTERN, accumulatedBuffer);
93   
94    // Look for quote pattern at beginning of line and escape the first character only (it's enough)
95  228 escapeFirstMatchedCharacter(QUOTE_PATTERN, accumulatedBuffer);
96    }
97   
98    // Escape table characters
99  680 if (blockStateListener.isInTable()) {
100  67 replaceAll(accumulatedBuffer, "|", ESCAPE_CHAR + "|");
101  67 replaceAll(accumulatedBuffer, "!!", ESCAPE_CHAR + "!!");
102    }
103   
104  680 if (escapeFirstIfMatching != null) {
105  3 escapeFirstMatchedCharacter(escapeFirstIfMatching, accumulatedBuffer);
106    }
107   
108    // When in a header we need to escape "=" symbols since otherwise they would be confused for end of section
109    // characters.
110  680 if (blockStateListener.isInHeader()) {
111  62 replaceAll(accumulatedBuffer, "=", ESCAPE_CHAR + "=");
112    }
113   
114    // Escape verbatim "{{{"
115  680 replaceAll(accumulatedBuffer, "{{{", ESCAPE_CHAR + "{" + ESCAPE_CHAR + "{" + ESCAPE_CHAR + "{");
116   
117    // Escape "{{"
118  680 replaceAll(accumulatedBuffer, "{{", ESCAPE_CHAR + "{" + ESCAPE_CHAR + "{");
119   
120    // Escape groups
121  680 replaceAll(accumulatedBuffer, "(((", ESCAPE_CHAR + "(" + ESCAPE_CHAR + "(" + ESCAPE_CHAR + "(");
122  680 replaceAll(accumulatedBuffer, ")))", ESCAPE_CHAR + ")" + ESCAPE_CHAR + ")" + ESCAPE_CHAR + ")");
123   
124    // Escape reserved keywords
125  680 Matcher matcher = DOUBLE_CHARS_PATTERN.matcher(accumulatedBuffer.toString());
126  701 for (int i = 0; matcher.find(); i = i + 2) {
127  21 accumulatedBuffer.replace(matcher.start() + i, matcher.end() + i, ESCAPE_CHAR + matcher.group().charAt(0)
128    + ESCAPE_CHAR + matcher.group().charAt(1));
129    }
130   
131    // Escape ":" in "image:something", "attach:something" and "mailto:something"
132    // Note: even though there are some restriction in the URI specification as to what character is valid after
133    // the ":" character following the scheme we only check for characters greater than the space symbol for
134    // simplicity.
135  680 escapeURI(accumulatedBuffer, "image:");
136  680 escapeURI(accumulatedBuffer, "attach:");
137  680 escapeURI(accumulatedBuffer, "mailto:");
138   
139    // Escape last character if we're told to do so. This is to handle cases such as:
140    // - onWord("hello:") followed by onFormat(ITALIC) which would lead to "hello://" if the ":" wasn't escaped
141    // - onWord("{") followed by onMacro() which would lead to "{{{" if the "{" wasn't escaped
142  680 if (escapeLastChar) {
143  4 accumulatedBuffer.insert(accumulatedBuffer.length() - 1, '~');
144    }
145   
146    // Escape begin link
147  680 replaceAll(accumulatedBuffer, "[[", ESCAPE_CHAR + "[" + ESCAPE_CHAR + "[");
148   
149    // Escape link label
150  680 int linkLevel = getLinkLevel(listenerChain);
151   
152  680 if (linkLevel > 0) {
153    // This need to be done after anything else because link label add another level of escaping (escaped as
154    // link label and then escaped as wiki content).
155  25 String escape = StringUtils.repeat(ESCAPE_CHAR, linkLevel);
156  25 replaceAll(accumulatedBuffer, ESCAPE_CHAR, escape + ESCAPE_CHAR);
157  25 replaceAll(accumulatedBuffer, "]]", escape + "]" + escape + "]");
158  25 replaceAll(accumulatedBuffer, ">>", escape + ">" + escape + ">");
159  25 replaceAll(accumulatedBuffer, "||", escape + "|" + escape + "|");
160    }
161    }
162   
 
163  680 toggle private int getLinkLevel(XWikiSyntaxListenerChain listenerChain)
164    {
165  680 int linkDepth = listenerChain.getBlockStateChainingListener().getLinkDepth();
166   
167  680 if (this.beforeLink) {
168  12 --linkDepth;
169    }
170   
171  680 return linkDepth;
172    }
173   
 
174  186 toggle public void setBeforeLink(boolean beforeLink)
175    {
176  186 this.beforeLink = beforeLink;
177    }
178   
 
179  2040 toggle private void escapeURI(StringBuffer accumulatedBuffer, String match)
180    {
181  2040 int pos = accumulatedBuffer.indexOf(match);
182  2040 if (pos > -1) {
183    // Escape the ":" symbol
184  6 accumulatedBuffer.insert(pos + match.length() - 1, '~');
185    }
186    }
187   
 
188  5056 toggle private void replaceAll(StringBuffer accumulatedBuffer, String match, String replacement)
189    {
190  5056 int pos = -replacement.length();
191  ? while ((pos + replacement.length() < accumulatedBuffer.length())
192    && ((pos = accumulatedBuffer.indexOf(match, pos + replacement.length())) != -1)) {
193  31 accumulatedBuffer.replace(pos, pos + match.length(), replacement);
194    }
195    }
196   
 
197  915 toggle private void escapeFirstMatchedCharacter(Pattern pattern, StringBuffer accumulatedBuffer)
198    {
199  915 Matcher matcher = pattern.matcher(accumulatedBuffer);
200  915 if (matcher.lookingAt()) {
201    // Escape the first character
202  11 accumulatedBuffer.replace(matcher.start(1), matcher.start(1) + 1, ESCAPE_CHAR + matcher.group(1).charAt(0));
203    }
204    }
205    }