1. Project Clover database Wed Oct 21 2015 17:09:14 CEST
  2. Package org.xwiki.rendering.internal.transformation.icon

File IconTransformation.java

 

Coverage histogram

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

Code metrics

32
68
7
1
261
157
27
0.4
9.71
7
3.86

Classes

Class Line # Actions
IconTransformation 61 68 0% 27 3
0.9719626397.2%
 

Contributing tests

No tests hitting this source file were found.

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.transformation.icon;
21   
22    import java.io.StringReader;
23    import java.util.Collections;
24    import java.util.List;
25    import java.util.Map;
26   
27    import javax.inject.Inject;
28    import javax.inject.Named;
29    import javax.inject.Singleton;
30   
31    import org.slf4j.Logger;
32    import org.xwiki.component.annotation.Component;
33    import org.xwiki.component.phase.Initializable;
34    import org.xwiki.component.phase.InitializationException;
35    import org.xwiki.rendering.block.Block;
36    import org.xwiki.rendering.block.ImageBlock;
37    import org.xwiki.rendering.block.SpecialSymbolBlock;
38    import org.xwiki.rendering.block.WordBlock;
39    import org.xwiki.rendering.block.XDOM;
40    import org.xwiki.rendering.internal.block.ProtectedBlockFilter;
41    import org.xwiki.rendering.listener.reference.ResourceReference;
42    import org.xwiki.rendering.listener.reference.ResourceType;
43    import org.xwiki.rendering.parser.ParseException;
44    import org.xwiki.rendering.parser.Parser;
45    import org.xwiki.rendering.transformation.AbstractTransformation;
46    import org.xwiki.rendering.transformation.TransformationContext;
47    import org.xwiki.rendering.transformation.TransformationException;
48    import org.xwiki.rendering.transformation.icon.IconTransformationConfiguration;
49    import org.xwiki.rendering.util.ParserUtils;
50   
51    /**
52    * Transforms some special characters representing icons into images. For example transforms {@code :)} characters into
53    * a smiley.
54    *
55    * @version $Id: 3cceb1a313e00a7084a39a231782c3ffb8f51c57 $
56    * @since 2.6RC1
57    */
58    @Component
59    @Named("icon")
60    @Singleton
 
61    public class IconTransformation extends AbstractTransformation implements Initializable
62    {
63    /**
64    * Used to get the icon mapping information (suite of characters mapped to an icon name).
65    */
66    @Inject
67    private IconTransformationConfiguration configuration;
68   
69    /**
70    * Used to parse the mapping suite of characters into a XDOM tree for fast matching.
71    */
72    @Inject
73    @Named("plain/1.0")
74    private Parser plainTextParser;
75   
76    /**
77    * The logger to log.
78    */
79    @Inject
80    private Logger logger;
81   
82    /**
83    * Used to remove the top level paragraph since we don't currently have an inline parser.
84    */
85    private ParserUtils parserUtils = new ParserUtils();
86   
87    /**
88    * The computed tree used to perform the fast mapping.
89    */
90    private XDOM mappingTree;
91   
92    /**
93    * Used to filter protected blocks (code macro marker block, etc).
94    */
95    private ProtectedBlockFilter filter = new ProtectedBlockFilter();
96   
 
97  3 toggle @Override
98    public void initialize() throws InitializationException
99    {
100  3 this.mappingTree = new XDOM(Collections.<Block>emptyList());
101   
102    // Transform mappings into Blocks
103  3 for (Map.Entry<Object, Object> entry : this.configuration.getMappings().entrySet()) {
104  51 try {
105  51 XDOM xdom = this.plainTextParser.parse(new StringReader((String) entry.getKey()));
106    // Remove top level paragraph
107  51 this.parserUtils.removeTopLevelParagraph(xdom.getChildren());
108  51 mergeTree(this.mappingTree, convertToDeepTree(xdom, (String) entry.getValue()));
109    } catch (ParseException e) {
110  0 this.logger.warn("Failed to parse icon symbols [" + entry.getKey() + "]. Reason = ["
111    + e.getMessage() + "]");
112    }
113    }
114    }
115   
 
116  3 toggle @Override
117    public void transform(Block source, TransformationContext context) throws TransformationException
118    {
119  3 List<Block> filteredBlocks = this.filter.filter(source.getChildren());
120  3 if (!this.mappingTree.getChildren().isEmpty() && !filteredBlocks.isEmpty()) {
121  2 parseTree(filteredBlocks);
122    }
123    }
124   
125    /**
126    * Converts a standard XDOM tree into a deep tree: sibling are transformed into parent/child relationships and the
127    * leaf node is an Image node referencing the passed icon name.
128    *
129    * @param sourceTree the source tree to modify
130    * @param iconName the name of the icon to display when a match is found
131    * @return the modified tree
132    */
 
133  51 toggle private Block convertToDeepTree(Block sourceTree, String iconName)
134    {
135  51 XDOM targetTree = new XDOM(Collections.<Block>emptyList());
136  51 Block pointer = targetTree;
137  51 for (Block block : sourceTree.getChildren()) {
138  138 pointer.addChild(block);
139  138 pointer = block;
140    }
141    // Add an image block as the last block
142  51 pointer.addChild(new ImageBlock(new ResourceReference(iconName, ResourceType.ICON), true));
143  51 return targetTree;
144    }
145   
146    /**
147    * Merged two XDOM trees.
148    *
149    * @param targetTree the tree to merge into
150    * @param sourceTree the tree to merge
151    */
 
152  93 toggle private void mergeTree(Block targetTree, Block sourceTree)
153    {
154  93 for (Block block : sourceTree.getChildren()) {
155    // Check if the current block exists in the target tree at the same place in the tree
156  93 int pos = indexOf(targetTree.getChildren(), block);
157  93 if (pos > -1) {
158  42 Block foundBlock = targetTree.getChildren().get(pos);
159  42 mergeTree(foundBlock, block);
160    } else {
161  51 targetTree.addChild(block);
162    }
163    }
164    }
165   
166    /**
167    * Shallow indexOf implementation that only compares nodes based on their data and not their children data.
168    *
169    * @param targetBlocks the list of blocks to look into
170    * @param block the block to look for in the list of passed blocks
171    * @return the position of the block in the list of target blocks and -1 if not found
172    */
 
173  93 toggle private int indexOf(List<Block> targetBlocks, Block block)
174    {
175  93 int pos = 0;
176  93 for (Block targetBlock : targetBlocks) {
177    // Test a non deep equality
178  300 if (blockEquals(targetBlock, block)) {
179  42 return pos;
180    }
181  258 pos++;
182    }
183  51 return -1;
184    }
185   
186    /**
187    * Compares two nodes in a shallow manner (children data are not compared).
188    *
189    * @param target the target node to compare
190    * @param source the source node to compare
191    * @return true if the two blocks are equals in a shallow manner (children data are not compared)
192    */
 
193  471 toggle private boolean blockEquals(Block target, Block source)
194    {
195  471 boolean found = false;
196  471 if (source instanceof SpecialSymbolBlock && target instanceof SpecialSymbolBlock) {
197  205 if (((SpecialSymbolBlock) target).getSymbol() == ((SpecialSymbolBlock) source).getSymbol()) {
198  83 found = true;
199    }
200  266 } else if (source instanceof WordBlock && target instanceof WordBlock) {
201  74 if (((WordBlock) target).getWord().equals(((WordBlock) source).getWord())) {
202  9 found = true;
203    }
204  192 } else if (source.equals(target)) {
205  0 found = true;
206    }
207  471 return found;
208    }
209   
210    /**
211    * Parse a list of blocks and replace suite of Blocks matching the icon mapping definitions by image blocks.
212    *
213    * @param sourceBlocks the blocks to parse
214    */
 
215  4 toggle private void parseTree(List<Block> sourceBlocks)
216    {
217  4 Block matchStartBlock = null;
218  4 int count = 0;
219  4 Block mappingCursor = this.mappingTree.getChildren().get(0);
220  4 Block sourceBlock = sourceBlocks.get(0);
221  61 while (sourceBlock != null) {
222  196 while (mappingCursor != null) {
223  171 if (blockEquals(sourceBlock, mappingCursor)) {
224  50 if (matchStartBlock == null) {
225  19 matchStartBlock = sourceBlock;
226    }
227  50 count++;
228  50 mappingCursor = mappingCursor.getChildren().get(0);
229    // If we reach the Image Block then we've found a match!
230  50 if (mappingCursor instanceof ImageBlock) {
231    // Replace the first source block with the image block and remove all other blocks...
232  49 for (int i = 0; i < count - 1; i++) {
233  31 matchStartBlock.getParent().removeBlock(matchStartBlock.getNextSibling());
234    }
235  18 sourceBlock = mappingCursor.clone();
236  18 matchStartBlock.getParent().replaceChild(sourceBlock, matchStartBlock);
237  18 mappingCursor = null;
238  18 matchStartBlock = null;
239  18 count = 0;
240    } else {
241    // Look for next block match
242  32 break;
243    }
244    } else {
245  121 mappingCursor = mappingCursor.getNextSibling();
246    }
247    }
248    // Look for a match in children of the source block
249  57 List<Block> filteredSourceBlocks = this.filter.filter(sourceBlock.getChildren());
250  57 if (filteredSourceBlocks.size() > 0) {
251  2 parseTree(filteredSourceBlocks);
252  55 } else if (mappingCursor == null) {
253    // No match has been found, reset state variables
254  23 mappingCursor = this.mappingTree.getChildren().get(0);
255  23 count = 0;
256  23 matchStartBlock = null;
257    }
258  57 sourceBlock = this.filter.getNextSibling(sourceBlock);
259    }
260    }
261    }