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

File HTMLMacro.java

 

Coverage histogram

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

Code metrics

16
54
6
1
314
159
17
0.31
9
6
2.83

Classes

Class Line # Actions
HTMLMacro 76 54 0% 17 3
0.960526396.1%
 

Contributing tests

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