|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
HTMLMacro | Line # 71 | 42 | 0% | 14 | 3 | 94.9% |
0.9491525
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||
(24) | |||
Result | |||
0.8135593
|
org.xwiki.rendering.test.integration.RenderingTest.execute org.xwiki.rendering.test.integration.RenderingTest.execute | 1 PASS | |
0.8135593
|
org.xwiki.rendering.test.integration.RenderingTest.execute org.xwiki.rendering.test.integration.RenderingTest.execute | 1 PASS | |
0.8135593
|
org.xwiki.rendering.test.integration.RenderingTest.execute org.xwiki.rendering.test.integration.RenderingTest.execute | 1 PASS | |
0.7288136
|
org.xwiki.rendering.test.integration.RenderingTest.execute org.xwiki.rendering.test.integration.RenderingTest.execute | 1 PASS | |
0.7288136
|
org.xwiki.rendering.test.integration.RenderingTest.execute org.xwiki.rendering.test.integration.RenderingTest.execute | 1 PASS | |
0.69491524
|
org.xwiki.rendering.test.integration.RenderingTest.execute org.xwiki.rendering.test.integration.RenderingTest.execute | 1 PASS | |
0.69491524
|
org.xwiki.rendering.test.integration.RenderingTest.execute org.xwiki.rendering.test.integration.RenderingTest.execute | 1 PASS | |
0.69491524
|
org.xwiki.rendering.test.integration.RenderingTest.execute org.xwiki.rendering.test.integration.RenderingTest.execute | 1 PASS | |
0.5254237
|
org.xwiki.rendering.test.integration.RenderingTest.execute org.xwiki.rendering.test.integration.RenderingTest.execute | 1 PASS | |
0.5254237
|
org.xwiki.rendering.test.integration.RenderingTest.execute org.xwiki.rendering.test.integration.RenderingTest.execute | 1 PASS | |
0.5084746
|
org.xwiki.rendering.test.integration.RenderingTest.execute org.xwiki.rendering.test.integration.RenderingTest.execute | 1 PASS | |
0.5084746
|
org.xwiki.rendering.test.integration.RenderingTest.execute org.xwiki.rendering.test.integration.RenderingTest.execute | 1 PASS | |
0.5084746
|
org.xwiki.rendering.test.integration.RenderingTest.execute org.xwiki.rendering.test.integration.RenderingTest.execute | 1 PASS | |
0.5084746
|
org.xwiki.rendering.test.integration.RenderingTest.execute org.xwiki.rendering.test.integration.RenderingTest.execute | 1 PASS | |
0.40677965
|
org.xwiki.rendering.test.integration.RenderingTest.execute org.xwiki.rendering.test.integration.RenderingTest.execute | 1 PASS | |
0.40677965
|
org.xwiki.rendering.test.integration.RenderingTest.execute org.xwiki.rendering.test.integration.RenderingTest.execute | 1 PASS | |
0.40677965
|
org.xwiki.rendering.test.integration.RenderingTest.execute org.xwiki.rendering.test.integration.RenderingTest.execute | 1 PASS | |
0.40677965
|
org.xwiki.rendering.test.integration.RenderingTest.execute org.xwiki.rendering.test.integration.RenderingTest.execute | 1 PASS | |
0.40677965
|
org.xwiki.rendering.test.integration.RenderingTest.execute org.xwiki.rendering.test.integration.RenderingTest.execute | 1 PASS | |
0.40677965
|
org.xwiki.rendering.test.integration.RenderingTest.execute org.xwiki.rendering.test.integration.RenderingTest.execute | 1 PASS | |
0.40677965
|
org.xwiki.rendering.test.integration.RenderingTest.execute org.xwiki.rendering.test.integration.RenderingTest.execute | 1 PASS | |
0.40677965
|
org.xwiki.rendering.test.integration.RenderingTest.execute org.xwiki.rendering.test.integration.RenderingTest.execute | 1 PASS | |
0.40677965
|
org.xwiki.rendering.test.integration.RenderingTest.execute org.xwiki.rendering.test.integration.RenderingTest.execute | 1 PASS | |
0.23728813
|
org.xwiki.rendering.test.integration.RenderingTest.execute org.xwiki.rendering.test.integration.RenderingTest.execute | 1 PASS | |
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 | public HTMLMacro() |
118 | { | |
119 | 26 | super("HTML", DESCRIPTION, new DefaultContentDescriptor(CONTENT_DESCRIPTION), HTMLMacroParameters.class); |
120 | 26 | setDefaultCategory(DEFAULT_CATEGORY_DEVELOPMENT); |
121 | } | |
122 | ||
123 | 8 | @Override |
124 | public boolean supportsInlineMode() | |
125 | { | |
126 | 8 | return true; |
127 | } | |
128 | ||
129 | 30 | @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 | 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 | 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 | } |
|