1. Project Clover database Tue Dec 20 2016 21:24:09 CET
  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

This file is covered by 3 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.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  34 toggle @Override
98    public void initialize() throws InitializationException
99    {
100  34 this.mappingTree = new XDOM(Collections.<Block>emptyList());
101   
102    // Transform mappings into Blocks
103  34 for (Map.Entry<Object, Object> entry : this.configuration.getMappings().entrySet()) {
104  578 try {
105  578 XDOM xdom = this.plainTextParser.parse(new StringReader((String) entry.getKey()));
106    // Remove top level paragraph
107  578 this.parserUtils.removeTopLevelParagraph(xdom.getChildren());
108  578 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  7172 toggle @Override
117    public void transform(Block source, TransformationContext context) throws TransformationException
118    {
119  7172 List<Block> filteredBlocks = this.filter.filter(source.getChildren());
120  7171 if (!this.mappingTree.getChildren().isEmpty() && !filteredBlocks.isEmpty()) {
121  6881 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  578 toggle private Block convertToDeepTree(Block sourceTree, String iconName)
134    {
135  578 XDOM targetTree = new XDOM(Collections.<Block>emptyList());
136  578 Block pointer = targetTree;
137  578 for (Block block : sourceTree.getChildren()) {
138  1564 pointer.addChild(block);
139  1564 pointer = block;
140    }
141    // Add an image block as the last block
142  578 pointer.addChild(new ImageBlock(new ResourceReference(iconName, ResourceType.ICON), true));
143  578 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  1054 toggle private void mergeTree(Block targetTree, Block sourceTree)
153    {
154  1054 for (Block block : sourceTree.getChildren()) {
155    // Check if the current block exists in the target tree at the same place in the tree
156  1054 int pos = indexOf(targetTree.getChildren(), block);
157  1054 if (pos > -1) {
158  476 Block foundBlock = targetTree.getChildren().get(pos);
159  476 mergeTree(foundBlock, block);
160    } else {
161  578 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  1054 toggle private int indexOf(List<Block> targetBlocks, Block block)
174    {
175  1054 int pos = 0;
176  1054 for (Block targetBlock : targetBlocks) {
177    // Test a non deep equality
178  3400 if (blockEquals(targetBlock, block)) {
179  476 return pos;
180    }
181  2924 pos++;
182    }
183  578 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  2370146 toggle private boolean blockEquals(Block target, Block source)
194    {
195  2370146 boolean found = false;
196  2370146 if (source instanceof SpecialSymbolBlock && target instanceof SpecialSymbolBlock) {
197  820722 if (((SpecialSymbolBlock) target).getSymbol() == ((SpecialSymbolBlock) source).getSymbol()) {
198  13595 found = true;
199    }
200  1549424 } else if (source instanceof WordBlock && target instanceof WordBlock) {
201  6106 if (((WordBlock) target).getWord().equals(((WordBlock) source).getWord())) {
202  17 found = true;
203    }
204  1543318 } else if (source.equals(target)) {
205  0 found = true;
206    }
207  2370147 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  40384 toggle private void parseTree(List<Block> sourceBlocks)
216    {
217  40384 Block matchStartBlock = null;
218  40384 int count = 0;
219  40384 Block mappingCursor = this.mappingTree.getChildren().get(0);
220  40384 Block sourceBlock = sourceBlocks.get(0);
221  842980 while (sourceBlock != null) {
222  3156228 while (mappingCursor != null) {
223  2366746 if (blockEquals(sourceBlock, mappingCursor)) {
224  13136 if (matchStartBlock == null) {
225  13089 matchStartBlock = sourceBlock;
226    }
227  13136 count++;
228  13136 mappingCursor = mappingCursor.getChildren().get(0);
229    // If we reach the Image Block then we've found a match!
230  13136 if (mappingCursor instanceof ImageBlock) {
231    // Replace the first source block with the image block and remove all other blocks...
232  67 for (int i = 0; i < count - 1; i++) {
233  43 matchStartBlock.getParent().removeBlock(matchStartBlock.getNextSibling());
234    }
235  24 sourceBlock = mappingCursor.clone();
236  24 matchStartBlock.getParent().replaceChild(sourceBlock, matchStartBlock);
237  24 mappingCursor = null;
238  24 matchStartBlock = null;
239  24 count = 0;
240    } else {
241    // Look for next block match
242  13112 break;
243    }
244    } else {
245  2353611 mappingCursor = mappingCursor.getNextSibling();
246    }
247    }
248    // Look for a match in children of the source block
249  802595 List<Block> filteredSourceBlocks = this.filter.filter(sourceBlock.getChildren());
250  802595 if (filteredSourceBlocks.size() > 0) {
251  33503 parseTree(filteredSourceBlocks);
252  769092 } else if (mappingCursor == null) {
253    // No match has been found, reset state variables
254  755980 mappingCursor = this.mappingTree.getChildren().get(0);
255  755980 count = 0;
256  755980 matchStartBlock = null;
257    }
258  802596 sourceBlock = this.filter.getNextSibling(sourceBlock);
259    }
260    }
261    }