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

File TocMacro.java

 

Coverage histogram

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

Code metrics

30
60
9
1
289
150
25
0.42
6.67
9
2.78

Classes

Class Line # Actions
TocMacro 59 60 0% 25 4
0.95959696%
 

Contributing tests

This file is covered by 18 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.macro.toc;
21   
22    import java.util.Arrays;
23    import java.util.Collections;
24    import java.util.List;
25   
26    import javax.inject.Inject;
27    import javax.inject.Named;
28    import javax.inject.Singleton;
29   
30    import org.xwiki.component.annotation.Component;
31    import org.xwiki.component.phase.InitializationException;
32    import org.xwiki.rendering.block.Block;
33    import org.xwiki.rendering.block.BulletedListBlock;
34    import org.xwiki.rendering.block.HeaderBlock;
35    import org.xwiki.rendering.block.LinkBlock;
36    import org.xwiki.rendering.block.ListBLock;
37    import org.xwiki.rendering.block.ListItemBlock;
38    import org.xwiki.rendering.block.NumberedListBlock;
39    import org.xwiki.rendering.block.SectionBlock;
40    import org.xwiki.rendering.block.match.ClassBlockMatcher;
41    import org.xwiki.rendering.listener.reference.DocumentResourceReference;
42    import org.xwiki.rendering.macro.AbstractMacro;
43    import org.xwiki.rendering.macro.MacroExecutionException;
44    import org.xwiki.rendering.macro.toc.TocMacroParameters;
45    import org.xwiki.rendering.macro.toc.TocMacroParameters.Scope;
46    import org.xwiki.rendering.parser.Parser;
47    import org.xwiki.rendering.renderer.reference.link.LinkLabelGenerator;
48    import org.xwiki.rendering.transformation.MacroTransformationContext;
49   
50    /**
51    * Generate a Table Of Contents based on the document sections.
52    *
53    * @version $Id: 0978501377935dafe5667d75197b20e8854c7160 $
54    * @since 1.5M2
55    */
56    @Component
57    @Named("toc")
58    @Singleton
 
59    public class TocMacro extends AbstractMacro<TocMacroParameters>
60    {
61    /**
62    * The description of the macro.
63    */
64    private static final String DESCRIPTION = "Generates a Table Of Contents.";
65   
66    /**
67    * Used to filter the {@link SectionBlock} title to generate the toc anchor.
68    */
69    private TocBlockFilter tocBlockFilter;
70   
71    /**
72    * A parser that knows how to parse plain text; this is used to transform link labels into plain text.
73    */
74    @Inject
75    @Named("plain/1.0")
76    private Parser plainTextParser;
77   
78    /**
79    * Generate link label.
80    */
81    @Inject
82    private LinkLabelGenerator linkLabelGenerator;
83   
84    /**
85    * Create and initialize the descriptor of the macro.
86    */
 
87  19 toggle public TocMacro()
88    {
89  19 super("Table Of Contents", DESCRIPTION, TocMacroParameters.class);
90   
91    // Make sure this macro is executed as one of the last macros to be executed since
92    // other macros can generate headers which need to be taken into account by the TOC
93    // macro.
94  19 setPriority(2000);
95  19 setDefaultCategory(DEFAULT_CATEGORY_NAVIGATION);
96    }
97   
 
98  19 toggle @Override
99    public void initialize() throws InitializationException
100    {
101  19 super.initialize();
102   
103  19 this.tocBlockFilter = new TocBlockFilter(this.plainTextParser, this.linkLabelGenerator);
104    }
105   
 
106  0 toggle @Override
107    public boolean supportsInlineMode()
108    {
109  0 return false;
110    }
111   
 
112  25 toggle @Override
113    public List<Block> execute(TocMacroParameters parameters, String content, MacroTransformationContext context)
114    throws MacroExecutionException
115    {
116  25 List<Block> result;
117   
118    // Example:
119    // 1 Section1
120    // 1 Section2
121    // 1.1 Section3
122    // 1 Section4
123    // 1.1.1 Section5
124   
125    // Generates:
126    // ListBlock
127    // |_ ListItemBlock (TextBlock: Section1)
128    // |_ ListItemBlock (TextBlock: Section2)
129    // ...|_ ListBlock
130    // ......|_ ListItemBlock (TextBlock: Section3)
131    // |_ ListItemBlock (TextBlock: Section4)
132    // ...|_ ListBlock
133    // ......|_ ListBlock
134    // .........|_ ListItemBlock (TextBlock: Section5)
135   
136    // Get the root block from scope parameter
137   
138  25 int start = parameters.getStart();
139  25 int depth = parameters.getDepth();
140   
141  25 Block root;
142   
143  25 if (parameters.getScope() == Scope.LOCAL) {
144  4 root = context.getCurrentMacroBlock().getParent();
145  4 if (!parameters.isCustomStart()) {
146  1 SectionBlock rootSection = context.getCurrentMacroBlock().getFirstBlock(
147    new ClassBlockMatcher(SectionBlock.class), Block.Axes.ANCESTOR);
148  1 HeaderBlock header = rootSection.getHeaderBlock();
149  1 if (header != null) {
150  1 start = header.getLevel().getAsInt() + 1;
151    }
152    }
153    } else {
154  21 root = context.getXDOM();
155    }
156   
157    // Get the list of sections in the scope
158  25 List<HeaderBlock> headers = root.getBlocks(new ClassBlockMatcher(HeaderBlock.class), Block.Axes.DESCENDANT);
159   
160    // If the root block is a section, remove it's header block for the list of header blocks
161  25 if (root instanceof SectionBlock) {
162  4 Block block = root.getChildren().get(0);
163   
164  4 if (block instanceof HeaderBlock) {
165  4 headers.remove(block);
166    }
167    }
168   
169    // Construct table of content from sections list
170  25 Block tocBlock = generateTree(headers, start, depth, parameters.isNumbered());
171  25 if (tocBlock != null) {
172  23 result = Arrays.asList(tocBlock);
173    } else {
174  2 result = Collections.emptyList();
175    }
176   
177  25 return result;
178    }
179   
180    /**
181    * Convert headers into list block tree.
182    *
183    * @param headers the headers to convert.
184    * @param start the "start" parameter value.
185    * @param depth the "depth" parameter value.
186    * @param numbered the "numbered" parameter value.
187    * @return the root block of generated block tree or null if no header was matching the specified parameters
188    */
 
189  25 toggle private Block generateTree(List<HeaderBlock> headers, int start, int depth, boolean numbered)
190    {
191  25 Block tocBlock = null;
192   
193  25 int currentLevel = start - 1;
194  25 Block currentBlock = null;
195  25 for (HeaderBlock headerBlock : headers) {
196  70 int headerLevel = headerBlock.getLevel().getAsInt();
197   
198  70 if (headerLevel >= start && headerLevel <= depth) {
199    // Move to next header in toc tree
200   
201  69 if (currentLevel < headerLevel) {
202  101 while (currentLevel < headerLevel) {
203  56 if (currentBlock instanceof ListBLock) {
204  11 currentBlock = addItemBlock(currentBlock, null);
205    }
206   
207  56 currentBlock = createChildListBlock(numbered, currentBlock);
208  56 ++currentLevel;
209    }
210    } else {
211  38 while (currentLevel > headerLevel) {
212  14 currentBlock = currentBlock.getParent().getParent();
213  14 --currentLevel;
214    }
215  24 currentBlock = currentBlock.getParent();
216    }
217   
218  69 currentBlock = addItemBlock(currentBlock, headerBlock);
219    }
220    }
221   
222  25 if (currentBlock != null) {
223  23 tocBlock = currentBlock.getRoot();
224    }
225   
226  25 return tocBlock;
227    }
228   
229    /**
230    * Add a {@link ListItemBlock} in the current toc tree block and return the new {@link ListItemBlock}.
231    *
232    * @param currentBlock the current block in the toc tree.
233    * @param headerBlock the {@link HeaderBlock} to use to generate toc anchor label.
234    * @return the new {@link ListItemBlock}.
235    */
 
236  80 toggle private Block addItemBlock(Block currentBlock, HeaderBlock headerBlock)
237    {
238  80 ListItemBlock itemBlock = headerBlock == null ? createEmptyTocEntry() : createTocEntry(headerBlock);
239   
240  80 currentBlock.addChild(itemBlock);
241   
242  80 return itemBlock;
243    }
244   
245    /**
246    * @return a new empty list item.
247    * @since 1.8RC2
248    */
 
249  11 toggle private ListItemBlock createEmptyTocEntry()
250    {
251  11 return new ListItemBlock(Collections.<Block>emptyList());
252    }
253   
254    /**
255    * Create a new toc list item based on section title.
256    *
257    * @param headerBlock the {@link HeaderBlock}.
258    * @return the new list item block.
259    */
 
260  69 toggle private ListItemBlock createTocEntry(HeaderBlock headerBlock)
261    {
262    // Create the link to target the header anchor
263  69 DocumentResourceReference reference = new DocumentResourceReference(null);
264  69 reference.setAnchor(headerBlock.getId());
265  69 LinkBlock linkBlock = new LinkBlock(this.tocBlockFilter.generateLabel(headerBlock), reference, false);
266   
267  69 return new ListItemBlock(Collections.<Block>singletonList(linkBlock));
268    }
269   
270    /**
271    * Create a new ListBlock and add it in the provided parent block.
272    *
273    * @param numbered indicate if the list has to be numbered or with bullets
274    * @param parentBlock the block where to add the new list block.
275    * @return the new list block.
276    */
 
277  56 toggle private ListBLock createChildListBlock(boolean numbered, Block parentBlock)
278    {
279  56 ListBLock childListBlock =
280  56 numbered ? new NumberedListBlock(Collections.<Block>emptyList()) : new BulletedListBlock(
281    Collections.<Block>emptyList());
282   
283  56 if (parentBlock != null) {
284  33 parentBlock.addChild(childListBlock);
285    }
286   
287  56 return childListBlock;
288    }
289    }