Clover Coverage Report - XWiki Rendering - Parent POM 4.0-SNAPSHOT (Aggregated)
Coverage timestamp: Mon Mar 12 2012 18:03:13 CET
../../../../../../img/srcFileCovDistChart10.png 0% of files have more coverage
42   274   14   8.4
12   132   0.33   5
5     2.8  
1    
 
  HTMLMacro       Line # 71 42 0% 14 3 94.9% 0.9491525
 
  (24)
 
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.html;
21   
22    import java.io.StringReader;
23    import java.util.Arrays;
24    import java.util.Collections;
25    import java.util.List;
26   
27    import javax.inject.Inject;
28    import javax.inject.Named;
29    import javax.inject.Singleton;
30   
31    import org.apache.commons.lang3.StringUtils;
32    import org.w3c.dom.Document;
33    import org.w3c.dom.Element;
34    import org.w3c.dom.Node;
35    import org.xwiki.component.annotation.Component;
36    import org.xwiki.rendering.block.Block;
37    import org.xwiki.rendering.block.Block.Axes;
38    import org.xwiki.rendering.block.MacroBlock;
39    import org.xwiki.rendering.block.MacroMarkerBlock;
40    import org.xwiki.rendering.block.RawBlock;
41    import org.xwiki.rendering.block.XDOM;
42    import org.xwiki.rendering.block.match.ClassBlockMatcher;
43    import org.xwiki.rendering.macro.AbstractMacro;
44    import org.xwiki.rendering.macro.MacroContentParser;
45    import org.xwiki.rendering.macro.MacroExecutionException;
46    import org.xwiki.rendering.macro.descriptor.DefaultContentDescriptor;
47    import org.xwiki.rendering.macro.html.HTMLMacroParameters;
48    import org.xwiki.rendering.renderer.PrintRenderer;
49    import org.xwiki.rendering.renderer.PrintRendererFactory;
50    import org.xwiki.rendering.renderer.printer.DefaultWikiPrinter;
51    import org.xwiki.rendering.renderer.printer.WikiPrinter;
52    import org.xwiki.rendering.syntax.Syntax;
53    import org.xwiki.rendering.syntax.SyntaxType;
54    import org.xwiki.rendering.transformation.MacroTransformationContext;
55    import org.xwiki.rendering.transformation.Transformation;
56    import org.xwiki.xml.html.HTMLCleaner;
57    import org.xwiki.xml.html.HTMLConstants;
58    import org.xwiki.xml.html.HTMLUtils;
59   
60    /**
61    * Allows inserting HTML and XHTML in wiki pages. This macro also accepts wiki syntax alongside (X)HTML elements (it's
62    * also possible to disable this feature using a macro parameter). When wiki syntax is used inside XML elements, the
63    * leading and trailing spaces and newlines are stripped.
64    *
65    * @version $Id: d303fc4c57bb99c5999dbf8b677fe0dc826a9b8e $
66    * @since 1.6M1
67    */
68    @Component
69    @Named("html")
70    @Singleton
 
71    public class HTMLMacro extends AbstractMacro<HTMLMacroParameters>
72    {
73    /**
74    * The description of the macro.
75    */
76    private static final String DESCRIPTION = "Inserts HTML or XHTML code into the page.";
77   
78    /**
79    * The description of the macro content.
80    */
81    private static final String CONTENT_DESCRIPTION = "The HTML content to insert in the page.";
82   
83    /**
84    * The syntax representing the output of this macro (used for the RawBlock).
85    */
86    private static final Syntax XHTML_SYNTAX = new Syntax(SyntaxType.XHTML, "1.0");
87   
88    /**
89    * Used to search for inner macros.
90    */
91    private static final ClassBlockMatcher MACROBLOCKMATCHER = new ClassBlockMatcher(MacroBlock.class);
92   
93    /**
94    * To clean the passed HTML so that it's valid XHTML (this is required since we use an XML parser to parse it).
95    */
96    @Inject
97    private HTMLCleaner htmlCleaner;
98   
99    /**
100    * Factory to create special XHTML renderer for the HTML Macro. We override the default XHTML renderer since we want
101    * special behaviors, for example to not escape special symbols (since we don't want to escape HTML tags for
102    * example).
103    */
104    @Inject
105    @Named("xhtmlmacro/1.0")
106    private PrintRendererFactory xhtmlRendererFactory;
107   
108    /**
109    * The parser used to parse macro content.
110    */
111    @Inject
112    private MacroContentParser contentParser;
113   
114    /**
115    * Create and initialize the descriptor of the macro.
116    */
 
117  26 toggle public HTMLMacro()
118    {
119  26 super("HTML", DESCRIPTION, new DefaultContentDescriptor(CONTENT_DESCRIPTION), HTMLMacroParameters.class);
120  26 setDefaultCategory(DEFAULT_CATEGORY_DEVELOPMENT);
121    }
122   
 
123  8 toggle @Override
124    public boolean supportsInlineMode()
125    {
126  8 return true;
127    }
128   
 
129  30 toggle @Override
130    public List<Block> execute(HTMLMacroParameters parameters, String content, MacroTransformationContext context)
131    throws MacroExecutionException
132    {
133  30 List<Block> blocks;
134   
135  30 if (!StringUtils.isEmpty(content)) {
136   
137  30 String normalizedContent = content;
138   
139    // If the user has mentioned that there's wiki syntax in the macro then we parse the content using
140    // a wiki syntax parser and render it back using a special renderer to print the XDOM blocks into
141    // a text representing the resulting XHTML content.
142  30 if (parameters.getWiki()) {
143  11 normalizedContent = renderWikiSyntax(normalizedContent, context.getTransformation(), context);
144    }
145   
146    // Clean the HTML into valid XHTML if the user has asked (it's the default).
147  30 if (parameters.getClean()) {
148  22 normalizedContent = cleanHTML(normalizedContent, context.isInline());
149    }
150   
151  29 blocks = Arrays.asList((Block) new RawBlock(normalizedContent, XHTML_SYNTAX));
152    } else {
153  0 blocks = Collections.emptyList();
154    }
155   
156  29 return blocks;
157    }
158   
159    /**
160    * Clean the HTML entered by the user, transforming it into valid XHTML.
161    *
162    * @param content the content to clean
163    * @param isInline true if the content is inline and thus if we need to remove the top level paragraph element
164    * created by the cleaner
165    * @return the cleaned HTML as a string representing valid XHTML
166    * @throws MacroExecutionException if the macro is inline and the content is not inline HTML
167    */
 
168  22 toggle private String cleanHTML(String content, boolean isInline) throws MacroExecutionException
169    {
170  22 String cleanedContent = content;
171   
172    // Note that we trim the content since we want to be lenient with the user in case he has entered
173    // some spaces/newlines before a XML declaration (prolog). Otherwise the XML parser would fail to parse.
174  22 Document document = this.htmlCleaner.clean(new StringReader(cleanedContent));
175   
176    // Since XML can only have a single root node and since we want to allow users to put
177    // content such as the following, we need to wrap the content in a root node:
178    // <tag1>
179    // ..
180    // </tag1>
181    // <tag2>
182    // </tag2>
183    // In addition we also need to ensure the XHTML DTD is defined so that valid XHTML entities can be
184    // specified.
185   
186    // Remove the HTML envelope since this macro is only a fragment of a page which will already have an
187    // HTML envelope when rendered. We remove it so that the HTML <head> tag isn't output.
188  22 HTMLUtils.stripHTMLEnvelope(document);
189   
190    // If in inline mode verify we have inline HTML content and remove the top level paragraph if there's one
191  22 if (isInline) {
192    // TODO: Improve this since when're inside a table cell or a list item we can allow non inline items too
193  5 Element root = document.getDocumentElement();
194  5 if (root.getChildNodes().getLength() == 1 && root.getFirstChild().getNodeType() == Node.ELEMENT_NODE
195    && root.getFirstChild().getNodeName().equalsIgnoreCase("p")) {
196  4 HTMLUtils.stripFirstElementInside(document, HTMLConstants.TAG_HTML, HTMLConstants.TAG_P);
197    } else {
198  1 throw new MacroExecutionException(
199    "When using the HTML macro inline, you can only use inline HTML content."
200    + " Block HTML content (such as tables) cannot be displayed."
201    + " Try leaving an empty line before and after the HTML macro.");
202    }
203    }
204   
205    // Don't print the XML declaration nor the XHTML DocType.
206  21 cleanedContent = HTMLUtils.toString(document, true, true);
207   
208    // Don't print the top level html element (which is always present and at the same location
209    // since it's been normalized by the HTML cleaner)
210    // Note: we trim the first 7 characters since they correspond to a leading new line (generated by
211    // XMLUtils.toString() since the doctype is printed on a line by itself followed by a new line) +
212    // the 6 chars from "<html>".
213  21 cleanedContent = cleanedContent.substring(7, cleanedContent.length() - 8);
214   
215  21 return cleanedContent;
216    }
217   
218    /**
219    * Parse the passed context using a wiki syntax parser and render the result as an XHTML string.
220    *
221    * @param content the content to parse
222    * @param transformation the macro transformation to execute macros when wiki is set to true
223    * @param context the context of the macros transformation process
224    * @return the output XHTML as a string containing the XWiki Syntax resolved as XHTML
225    * @throws MacroExecutionException in case there's a parsing problem
226    */
 
227  11 toggle private String renderWikiSyntax(String content, Transformation transformation, MacroTransformationContext context)
228    throws MacroExecutionException
229    {
230  11 String xhtml;
231   
232  11 try {
233    // Parse the wiki syntax
234  11 XDOM xdom = this.contentParser.parse(content, context, false, false);
235   
236    // Force clean=false for sub HTML macro:
237    // - at this point we don't know the context of the macro, it can be some <div> directly followed by the
238    // html macro, it this case the macro will be parsed as inline block
239    // - by forcing clean=false, we also make the html macro merge the whole html before cleaning so the cleaner
240    // have the chole context and can clean better
241  11 List<MacroBlock> macros = xdom.getBlocks(MACROBLOCKMATCHER, Axes.DESCENDANT);
242  11 for (MacroBlock macro : macros) {
243  6 if (macro.getId().equals("html")) {
244  4 macro.setParameter("clean", "false");
245    }
246    }
247   
248  11 MacroBlock htmlMacroBlock = context.getCurrentMacroBlock();
249   
250  11 MacroMarkerBlock htmlMacroMarker =
251    new MacroMarkerBlock(htmlMacroBlock.getId(), htmlMacroBlock.getParameters(),
252    htmlMacroBlock.getContent(), xdom.getChildren(), htmlMacroBlock.isInline());
253    // otherwise the HTML block will not be able to access the parent DOM
254  11 htmlMacroMarker.setParent(htmlMacroBlock.getParent());
255   
256    // Execute the Macro transformation
257  11 transformation.transform(htmlMacroMarker, context.getTransformationContext());
258   
259    // Render the whole parsed content as a XHTML string
260  11 WikiPrinter printer = new DefaultWikiPrinter();
261  11 PrintRenderer renderer = this.xhtmlRendererFactory.createRenderer(printer);
262  11 for (Block block : htmlMacroMarker.getChildren()) {
263  14 block.traverse(renderer);
264    }
265   
266  11 xhtml = printer.toString();
267   
268    } catch (Exception e) {
269  0 throw new MacroExecutionException("Failed to parse content [" + content + "].", e);
270    }
271   
272  11 return xhtml;
273    }
274    }