/* * See the NOTICE file distributed with this work for additional * information regarding copyright ownership. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.xwiki.wysiwyg.server.internal.converter; import java.io.StringReader; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; import org.slf4j.Logger; import org.xwiki.component.annotation.Component; import org.xwiki.component.manager.ComponentManager; import org.xwiki.gwt.wysiwyg.client.cleaner.HTMLCleaner; import org.xwiki.gwt.wysiwyg.client.converter.HTMLConverter; import org.xwiki.rendering.block.XDOM; import org.xwiki.rendering.internal.transformation.MutableRenderingContext; import org.xwiki.rendering.listener.MetaData; import org.xwiki.rendering.parser.ParseException; import org.xwiki.rendering.parser.Parser; import org.xwiki.rendering.parser.StreamParser; import org.xwiki.rendering.renderer.BlockRenderer; import org.xwiki.rendering.renderer.PrintRendererFactory; import org.xwiki.rendering.renderer.printer.DefaultWikiPrinter; import org.xwiki.rendering.renderer.printer.WikiPrinter; import org.xwiki.rendering.syntax.Syntax; import org.xwiki.rendering.syntax.SyntaxFactory; import org.xwiki.rendering.transformation.RenderingContext; import org.xwiki.rendering.transformation.Transformation; import org.xwiki.rendering.transformation.TransformationContext; import org.xwiki.rendering.transformation.TransformationException; /** * Converts HTML into/from markup syntax. * * @version $Id: 457c512b59d89beee22f1b2a2f2227633eb3b99b $ */ @Component @Singleton public class DefaultHTMLConverter implements HTMLConverter { private static final String TRANSFORMATION_ID = "wysiwygtxid"; /** * Logger. */ @Inject private Logger logger; /** * The component used to clean the HTML before the conversion. */ @Inject private HTMLCleaner htmlCleaner; /** * The component used to parse the XHTML obtained after cleaning. */ @Inject @Named("xhtml/1.0") private Parser xhtmlParser; /** * The component used to parse the XHTML obtained after cleaning, when transformations are not executed. */ @Inject @Named("xhtml/1.0") private StreamParser xhtmlStreamParser; /** * The component used to create syntax instances from syntax identifiers. */ @Inject private SyntaxFactory syntaxFactory; /** * Used to update the rendering context. */ @Inject private RenderingContext renderingContext; /** * The component used to execute the XDOM macro transformations before rendering to XHTML. * <p> * NOTE: We execute only macro transformations because they are the only transformations protected by the WYSIWYG * editor. We should use the transformation manager once generic transformation markers are implemented in the * rendering module and the WYSIWYG editor supports them. * * @see <a href="https://jira.xwiki.org/browse/XRENDERING-78">XWIKI-3260: Add markers to modified XDOM by * Transformations/Macros</a> */ @Inject @Named("macro") private Transformation macroTransformation; /** * The component used to render a XDOM to XHTML. */ @Inject @Named("annotatedxhtml/1.0") private BlockRenderer xhtmlRenderer; /** * The component manager. We need it because we have to access some components dynamically based on the input * syntax. */ @Inject @Named("context") private ComponentManager contextComponentManager; @Override public String fromHTML(String dirtyHTML, String syntaxId) { try { // Clean String html = this.htmlCleaner.clean(dirtyHTML); // Parse & Render // Note that transformations are not executed when converting XHTML to source syntax. WikiPrinter printer = new DefaultWikiPrinter(); PrintRendererFactory printRendererFactory = this.contextComponentManager.getInstance(PrintRendererFactory.class, syntaxId); this.xhtmlStreamParser.parse(new StringReader(html), printRendererFactory.createRenderer(printer)); return printer.toString(); } catch (Exception e) { this.logger.error(e.getLocalizedMessage(), e); throw new RuntimeException("Exception while parsing HTML", e); } } @Override public String toHTML(String source, String syntaxId) { try { // Parse Parser parser = this.contextComponentManager.getInstance(Parser.class, syntaxId); XDOM xdom = parser.parse(new StringReader(source)); // Execute the macro transformation executeMacroTransformation(xdom, this.syntaxFactory.createSyntaxFromIdString(syntaxId)); // Render WikiPrinter printer = new DefaultWikiPrinter(); this.xhtmlRenderer.render(xdom, printer); return printer.toString(); } catch (Exception e) { this.logger.error(e.getLocalizedMessage(), e); throw new RuntimeException("Exception while rendering HTML", e); } } @Override public String parseAndRender(String dirtyHTML, String syntaxId) { try { // Clean String html = this.htmlCleaner.clean(dirtyHTML); // Parse XDOM xdom = this.xhtmlParser.parse(new StringReader(html)); // The XHTML parser sets the "syntax" meta data property of the created XDOM to "xhtml/1.0". The syntax meta // data is used as the default syntax for macro content. We have to change this to the specified syntax // because HTML is used only to be able to edit the source syntax in the WYSIWYG editor. Syntax syntax = this.syntaxFactory.createSyntaxFromIdString(syntaxId); xdom.getMetaData().addMetaData(MetaData.SYNTAX, syntax); // Execute the macro transformation executeMacroTransformation(xdom, this.syntaxFactory.createSyntaxFromIdString(syntaxId)); // Render WikiPrinter printer = new DefaultWikiPrinter(); this.xhtmlRenderer.render(xdom, printer); return printer.toString(); } catch (Exception e) { this.logger.error(e.getLocalizedMessage(), e); throw new RuntimeException("Exception while refreshing HTML", e); } } private void executeMacroTransformation(XDOM xdom, Syntax syntax) throws TransformationException, ParseException { TransformationContext txContext = new TransformationContext(); txContext.setXDOM(xdom); txContext.setSyntax(syntax); // It's very important to set a Transformation id as otherwise if any Velocity Macro is executed it'll be // executed in isolation (and if you have, say, 2 velocity macros, the second one will not 'see' what's defined // in the first one... txContext.setId(TRANSFORMATION_ID); ((MutableRenderingContext) this.renderingContext).transformInContext(this.macroTransformation, txContext, xdom); } }