1. Project Clover database Sat Feb 2 2019 06:45:20 CET
  2. Package org.xwiki.rendering.wikimodel.xhtml.impl

File TagStack.java

 

Coverage histogram

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

Code metrics

42
168
41
1
491
349
110
0.65
4.1
41
2.68

Classes

Class Line # Actions
TagStack 46 168 0% 110 22
0.912350691.2%
 

Contributing tests

This file is covered by 351 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.wikimodel.xhtml.impl;
21   
22    import java.util.ArrayDeque;
23    import java.util.Deque;
24    import java.util.HashMap;
25    import java.util.Iterator;
26    import java.util.LinkedList;
27    import java.util.Map;
28    import java.util.Queue;
29   
30    import org.apache.commons.lang3.StringUtils;
31    import org.xwiki.rendering.wikimodel.WikiPageUtil;
32    import org.xwiki.rendering.wikimodel.WikiParameters;
33    import org.xwiki.rendering.wikimodel.impl.WikiScannerContext;
34    import org.xwiki.rendering.wikimodel.xhtml.XhtmlCharacter;
35    import org.xwiki.rendering.wikimodel.xhtml.XhtmlCharacterType;
36    import org.xwiki.rendering.wikimodel.xhtml.handler.AbstractFormatTagHandler;
37    import org.xwiki.rendering.wikimodel.xhtml.handler.CommentHandler;
38    import org.xwiki.rendering.wikimodel.xhtml.handler.TagHandler;
39   
40    /**
41    * Provides context for the parsing.
42    *
43    * @version $Id: f90dc2bfc76fc8f46f4ffedb7170e3adbcb30529 $
44    * @since 7.0RC1
45    */
 
46    public class TagStack
47    {
48    private static final String QUOTE_DEPTH = "quoteDepth";
49   
50    private static final String INSIDE_BLOCK_ELEMENT = "insideBlockElement";
51   
52    private static final String LIST_STYLES = "listStyles";
53   
54    private static final String DOCUMENT_PARENT = "documentParent";
55   
56    /**
57    * Represents a default {@link IgnoreElementRule} that ignore all elements and cannot be switched off.
58    *
59    * @since 10.10RC1
60    */
61    public static final IgnoreElementRule IGNORE_ALL = new IgnoreElementRule(f -> false, true);
62   
63    private final Map<String, TagHandler> fMap;
64   
65    private final CommentHandler fCommentHandler;
66   
67    private TagContext fPeek;
68   
69    private final Deque<WikiScannerContext> fScannerContext = new ArrayDeque<WikiScannerContext>();
70   
71    private final Deque<Map<String, Object>> fStackParameters = new ArrayDeque<Map<String, Object>>();
72   
73    /**
74    * A stack of rules to ignore elements
75    */
76    private Deque<IgnoreElementRule> fignoreElementRuleStack = new ArrayDeque<>();
77   
78    private int fEmptyLineCount;
79   
80    private XhtmlCharacterType fPreviousCharType = null;
81   
82    private String characters;
83   
 
84  0 toggle public TagStack(WikiScannerContext context, Map<String, TagHandler> handlers)
85    {
86  0 this(context, handlers, new CommentHandler());
87    }
88   
 
89  433 toggle public TagStack(WikiScannerContext context, Map<String, TagHandler> handlers, CommentHandler commentHandler)
90    {
91  433 fMap = handlers;
92  433 fScannerContext.push(context);
93  433 fCommentHandler = commentHandler;
94   
95    // init stack paramaters
96  433 pushStackParameters();
97    }
98   
 
99  2773 toggle public void beginElement(String name, WikiParameters params)
100    {
101  2773 flushCharacters(false);
102   
103  2773 fPeek = new TagContext(fPeek, name, params, this);
104  2773 name = fPeek.getName();
105  2773 TagHandler handler = fMap.get(name);
106   
107    // check if the ignore rule should be activated
108  2773 this.switchIgnoreRule(fPeek);
109  2773 if (!this.shouldIgnoreElements()) {
110  2657 if (!(handler instanceof AbstractFormatTagHandler)) {
111  2352 fPreviousCharType = null;
112    }
113   
114  2657 fPeek.beginElement(handler);
115    }
116    }
117   
 
118  2773 toggle public void endElement()
119    {
120  2773 flushCharacters(true);
121   
122  2773 if (!this.shouldIgnoreElements()) {
123  2657 fPeek.endElement();
124    }
125   
126    // check if the ignore rule should be deactivated
127  2773 this.switchIgnoreRule(fPeek);
128  2773 fPeek = fPeek.getParentContext();
129    }
130   
 
131  7192 toggle private XhtmlCharacterType getCharacterType(char ch, XhtmlCharacterType prevCharType, boolean beforeEnd)
132    {
133  7192 XhtmlCharacterType type = XhtmlCharacterType.CHARACTER;
134  7192 switch (ch) {
135  8 case '!':
136  3 case '\'':
137  6 case '#':
138  2 case '$':
139  2 case '%':
140  1 case '&':
141  5 case '(':
142  5 case ')':
143  11 case '*':
144  2 case '+':
145  6 case ',':
146  18 case '-':
147  24 case '.':
148  20 case '/':
149  22 case ':':
150  4 case ';':
151  5 case '<':
152  16 case '=':
153  11 case '>':
154  2 case '?':
155  2 case '@':
156  10 case '[':
157  2 case '\\':
158  5 case ']':
159  6 case '^':
160  6 case '_':
161  2 case '`':
162  23 case '{':
163  9 case '|':
164  20 case '}':
165  3 case '~':
166  3 case '\"':
167  264 type = XhtmlCharacterType.SPECIAL_SYMBOL;
168  264 break;
169  602 case ' ':
170  0 case '\t':
171  602 type = XhtmlCharacterType.SPACE;
172  602 break;
173  101 case 160:
174    // This is a &nbsp;
175    // If previous character was a space as well, send it as a space, it will be rendered back in XHTML as a
176    // nbsp anyway. If it's in the middle of a word, it's a regular word character.
177  101 if (prevCharType == XhtmlCharacterType.SPACE || prevCharType == null || beforeEnd) {
178  61 type = XhtmlCharacterType.SPACE;
179    } else {
180  40 type = XhtmlCharacterType.CHARACTER;
181    }
182  101 break;
183  17 case '\n':
184  0 case '\r':
185  17 type = XhtmlCharacterType.NEW_LINE;
186  17 break;
187  6208 default:
188  6208 break;
189    }
190  7192 return type;
191    }
192   
 
193  5996 toggle public WikiScannerContext getScannerContext()
194    {
195  5996 return fScannerContext.isEmpty() ? null : fScannerContext.peek();
196    }
197   
 
198  0 toggle public void setScannerContext(WikiScannerContext context)
199    {
200  0 if (!fScannerContext.isEmpty()) {
201  0 fScannerContext.pop();
202    }
203  0 fScannerContext.push(context);
204    }
205   
 
206  113 toggle public void pushScannerContext(WikiScannerContext context)
207    {
208  113 fScannerContext.push(context);
209    }
210   
 
211  113 toggle public WikiScannerContext popScannerContext()
212    {
213  113 return fScannerContext.pop();
214    }
215   
 
216  966 toggle private void flushStack(Queue<XhtmlCharacter> stack)
217    {
218  3245 while (!stack.isEmpty()) {
219  2279 XhtmlCharacter character = stack.poll();
220  2279 switch (character.getType()) {
221  0 case ESCAPED:
222  0 getScannerContext().onEscape("" + character.getCharacter());
223  0 break;
224  264 case SPECIAL_SYMBOL:
225  264 getScannerContext().onSpecialSymbol("" + character.getCharacter());
226  264 break;
227  17 case NEW_LINE:
228  17 getScannerContext().onLineBreak();
229  17 break;
230  627 case SPACE:
231  627 StringBuilder spaceBuffer = new StringBuilder(" ");
232  663 while (!stack.isEmpty() && (stack.element().getType() == XhtmlCharacterType.SPACE)) {
233  36 stack.poll();
234  36 spaceBuffer.append(' ');
235    }
236  627 getScannerContext().onSpace(spaceBuffer.toString());
237  627 break;
238  1371 default:
239  1371 StringBuilder charBuffer = new StringBuilder();
240  1371 charBuffer.append(character.getCharacter());
241  6248 while (!stack.isEmpty() && (stack.element().getType() == XhtmlCharacterType.CHARACTER)) {
242  4877 charBuffer.append(stack.poll().getCharacter());
243    }
244  1371 getScannerContext().onWord(WikiPageUtil.escapeXmlString(charBuffer.toString()));
245    }
246    }
247    }
248   
 
249  1118 toggle public void onCharacters(String content)
250    {
251  1118 if (!fPeek.isContentContainer() || shouldIgnoreElements()) {
252  109 return;
253    }
254   
255  1009 if (!fPeek.appendContent(content)) {
256  966 flushCharacters(false);
257   
258  966 this.characters = content;
259   
260    // Don't wait if the content does not contain any non breaking space
261  966 if (!StringUtils.contains(content, 160)) {
262  896 flushCharacters(false);
263    }
264    }
265    }
266   
 
267  7711 toggle private void flushCharacters(boolean beforeEnd)
268    {
269  7711 if (this.characters != null) {
270  966 Queue<XhtmlCharacter> stack = new ArrayDeque<>();
271  8158 for (int i = 0; i < this.characters.length(); i++) {
272  7192 char c = this.characters.charAt(i);
273  7192 fPreviousCharType =
274    getCharacterType(c, fPreviousCharType, (this.characters.length() == i + 1) && beforeEnd);
275  7192 stack.offer(new XhtmlCharacter(c, fPreviousCharType));
276    }
277   
278    // Now send the events.
279  966 flushStack(stack);
280   
281  966 this.characters = null;
282    }
283    }
284   
 
285  303 toggle public void onComment(char[] array, int start, int length)
286    {
287  303 flushCharacters(false);
288   
289  303 fCommentHandler.onComment(new String(array, start, length), this);
290    }
291   
 
292  528 toggle public void pushStackParameters()
293    {
294  528 fStackParameters.push(new HashMap<String, Object>());
295   
296    // Pre-initialize stack parameters for performance reason
297    // (so that we don't have to check all the time if they're
298    // initialized or not)
299  528 setStackParameter(LIST_STYLES, new StringBuffer());
300  528 setQuoteDepth(0);
301  528 getStackParameters().put(INSIDE_BLOCK_ELEMENT, false);
302   
303    // Allow each handler to have some initialization
304  528 for (TagHandler tagElementHandler : fMap.values()) {
305  25872 tagElementHandler.initialize(this);
306    }
307    }
308   
 
309  95 toggle public void popStackParameters()
310    {
311  95 fStackParameters.pop();
312    }
313   
 
314  21660 toggle private Map<String, Object> getStackParameters()
315    {
316  21660 return fStackParameters.peek();
317    }
318   
 
319  3662 toggle public void setStackParameter(String name, Object data)
320    {
321  3662 Deque<Object> set = (Deque<Object>) getStackParameters().get(name);
322  3662 if (set != null && !set.isEmpty()) {
323  230 set.pop();
324    }
325  3662 pushStackParameter(name, data);
326    }
327   
 
328  4016 toggle public Object getStackParameter(String name)
329    {
330  4016 Deque<Object> set = (Deque<Object>) getStackParameters().get(name);
331  4016 return (set == null) ? null : set.peek();
332    }
333   
 
334  0 toggle @Deprecated
335    public Object getStackParameter(String name, int index)
336    {
337  0 Deque<Object> set = (Deque<Object>) getStackParameters().get(name);
338  0 if (set == null || set.size() <= index) {
339  0 return null;
340    }
341  0 return set.toArray()[set.size() - index - 1];
342    }
343   
 
344  11 toggle public Iterator<Object> getStackParameterIterator(String name)
345    {
346  11 Deque<Object> set = (Deque<Object>) getStackParameters().get(name);
347  11 return (set == null) ? null : set.descendingIterator();
348    }
349   
 
350  4733 toggle public void pushStackParameter(String name, Object data)
351    {
352  4733 Deque<Object> set = (Deque<Object>) getStackParameters().get(name);
353  4733 if (set == null) {
354  4038 getStackParameters().put(name, set = new LinkedList<Object>());
355    }
356   
357  4733 set.push(data);
358    }
359   
 
360  1067 toggle public Object popStackParameter(String name)
361    {
362  1067 return ((Deque<Object>) getStackParameters().get(name)).pop();
363    }
364   
 
365  586 toggle public void setQuoteDepth(int depth)
366    {
367  586 setStackParameter(QUOTE_DEPTH, depth);
368    }
369   
 
370  1679 toggle public int getQuoteDepth()
371    {
372  1679 return (int) getStackParameter(QUOTE_DEPTH);
373    }
374   
 
375  667 toggle public boolean isInsideBlockElement()
376    {
377  667 return (boolean) getStackParameters().get(INSIDE_BLOCK_ELEMENT);
378    }
379   
 
380  601 toggle public void setInsideBlockElement()
381    {
382  601 getStackParameters().put(INSIDE_BLOCK_ELEMENT, true);
383    }
384   
 
385  675 toggle public void unsetInsideBlockElement()
386    {
387  675 getStackParameters().put(INSIDE_BLOCK_ELEMENT, false);
388    }
389   
 
390  21 toggle public void setDocumentParent()
391    {
392  21 getStackParameters().put(DOCUMENT_PARENT, fPeek.getParent());
393    }
394   
 
395  1641 toggle public TagContext getDocumentParent()
396    {
397  1641 return (TagContext) getStackParameters().get(DOCUMENT_PARENT);
398    }
399   
 
400  175 toggle public String pushListStyle(char style)
401    {
402  175 StringBuffer listStyles = (StringBuffer) getStackParameter(LIST_STYLES);
403  175 listStyles.append(style);
404  175 return listStyles.toString();
405    }
406   
 
407  175 toggle public void popListStyle()
408    {
409    // We should always have a length greater than 0 but we handle
410    // the case where the user has entered some badly formed HTML
411  175 StringBuffer listStyles = (StringBuffer) getStackParameter(LIST_STYLES);
412  175 if (listStyles.length() > 0) {
413  175 listStyles.setLength(listStyles.length() - 1);
414    }
415    }
416   
 
417  111 toggle public boolean isEndOfList()
418    {
419  111 return ((StringBuffer) getStackParameter(LIST_STYLES)).length() == 0;
420    }
421   
 
422  15 toggle public void resetEmptyLinesCount()
423    {
424  15 fEmptyLineCount = 0;
425    }
426   
 
427  19 toggle public void incrementEmptyLinesCount()
428    {
429  19 fEmptyLineCount += 1;
430    }
431   
 
432  1275 toggle public int getEmptyLinesCount()
433    {
434  1275 return fEmptyLineCount;
435    }
436   
 
437  6960 toggle public boolean shouldIgnoreElements()
438    {
439    // we ignore elements if there is at least one ignore rule and it is active
440  6960 return !this.fignoreElementRuleStack.isEmpty() && this.fignoreElementRuleStack.peek().isActive();
441    }
442   
 
443  7 toggle public void setIgnoreElements()
444    {
445    // if we want to ignore all elements, we use an ignore rule that cannot be deactivated
446  7 this.pushIgnoreElementRule(IGNORE_ALL);
447    }
448   
 
449  6 toggle public void unsetIgnoreElements()
450    {
451  6 this.popIgnoreElementRule();
452    }
453   
454    /**
455    * Push a new rule to ignore elements.
456    *
457    * @param ignoreElementRule the rule to be used now.
458    *
459    * @since 10.10RC1
460    */
 
461  61 toggle public void pushIgnoreElementRule(IgnoreElementRule ignoreElementRule)
462    {
463  61 this.fignoreElementRuleStack.push(ignoreElementRule);
464    }
465   
466    /**
467    * Retrieve the last rule to ignore element.
468    *
469    * @return the last rule that was in the stack.
470    *
471    * @since 10.10RC1
472    */
 
473  61 toggle public IgnoreElementRule popIgnoreElementRule()
474    {
475  61 return this.fignoreElementRuleStack.pop();
476    }
477   
478    /**
479    * Check if an ignore rule should be (de)activated based on the given tag context.
480    *
481    * @param fPeek the tag context to match with a rule for activating it.
482    *
483    * @since 10.10RC1
484    */
 
485  5546 toggle private void switchIgnoreRule(TagContext fPeek)
486    {
487  5546 if (!this.fignoreElementRuleStack.isEmpty()) {
488  304 this.fignoreElementRuleStack.peek().switchRule(fPeek);
489    }
490    }
491    }