/*
* Copyright (C) 2003-2009 eXo Platform SAS.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Affero General Public License
* as published by the Free Software Foundation; either version 3
* of the License, or (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see<http://www.gnu.org/licenses/>.
*/
package org.exoplatform.wiki.rendering.impl;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.exoplatform.services.log.ExoLogger;
import org.exoplatform.services.log.Log;
import org.exoplatform.wiki.rendering.RenderingService;
import org.exoplatform.wiki.rendering.converter.BlockConverter;
import org.picocontainer.Startable;
import org.w3c.dom.Document;
import org.xwiki.component.embed.EmbeddableComponentManager;
import org.xwiki.component.manager.ComponentLookupException;
import org.xwiki.component.manager.ComponentRepositoryException;
import org.xwiki.context.Execution;
import org.xwiki.rendering.block.Block;
import org.xwiki.rendering.block.FormatBlock;
import org.xwiki.rendering.block.GroupBlock;
import org.xwiki.rendering.block.HeaderBlock;
import org.xwiki.rendering.block.LinkBlock;
import org.xwiki.rendering.block.SectionBlock;
import org.xwiki.rendering.block.XDOM;
import org.xwiki.rendering.converter.ConversionException;
import org.xwiki.rendering.listener.Format;
import org.xwiki.rendering.listener.reference.ResourceReference;
import org.xwiki.rendering.listener.reference.ResourceType;
import org.xwiki.rendering.parser.ParseException;
import org.xwiki.rendering.parser.Parser;
import org.xwiki.rendering.renderer.BlockRenderer;
import org.xwiki.rendering.renderer.printer.DefaultWikiPrinter;
import org.xwiki.rendering.renderer.printer.WikiPrinter;
import org.xwiki.rendering.syntax.Syntax;
import org.xwiki.rendering.transformation.TransformationException;
import org.xwiki.rendering.transformation.TransformationManager;
import org.xwiki.xml.html.HTMLCleaner;
import org.xwiki.xml.html.HTMLCleanerConfiguration;
import org.xwiki.xml.html.HTMLUtils;
/**
* Created by The eXo Platform SAS Author : eXoPlatform exo@exoplatform.com Nov
* 5, 2009
*/
public class RenderingServiceImpl implements RenderingService, Startable {
private String cssURL;
private Log LOG = ExoLogger.getExoLogger(RenderingServiceImpl.class);
EmbeddableComponentManager componentManager = null;
public Execution getExecution() throws ComponentLookupException, ComponentRepositoryException{
return componentManager.lookup(Execution.class);
}
public <T> T getComponent(Class<T> clazz) {
return getComponent(clazz, "default");
}
/*
* (non-Javadoc)
* @see org.exoplatform.wiki.rendering.RenderingService#render(java.lang.String, java.lang.String, java.lang.String)
*/
public String render(String markup, String sourceSyntax, String targetSyntax, boolean supportSectionEdit) throws Exception {
XDOM xdom = parse(markup, sourceSyntax);
Syntax sSyntax = (sourceSyntax == null) ? Syntax.XWIKI_2_0 : getSyntax(sourceSyntax);
Syntax tSyntax = (targetSyntax == null) ? Syntax.XHTML_1_0 : getSyntax(targetSyntax);
try {
BlockConverter refiner = componentManager.lookup(BlockConverter.class, sSyntax.toIdString());
refiner.convert(xdom);
} catch (ComponentLookupException e) {
if (LOG.isDebugEnabled()) {
LOG.debug(String.format("Syntax %s doesn't have any refiner", sSyntax));
}
} catch (ConversionException e) {
throw new ConversionException("Failed to refine input source", e);
}
if (supportSectionEdit) {
List<HeaderBlock> filteredHeaders = getFilteredHeaders(xdom);
int sectionIndex = 1;
for (HeaderBlock block : filteredHeaders) {
SectionBlock section = block.getSection();
Block parentBlock = section.getParent();
ResourceReference link = new ResourceReference( "section=" + sectionIndex, ResourceType.URL);
sectionIndex++;
List<Block> emtyList = Collections.emptyList();
Map<String, String> linkParameters = new LinkedHashMap<String, String>();
linkParameters.put("title", "Edit section: " + renderXDOM(new XDOM(block.getChildren()), sSyntax));
LinkBlock linkBlock = new LinkBlock(emtyList, link, true, linkParameters);
Map<String, String> spanParameters = new LinkedHashMap<String, String>();
spanParameters.put("class", "EditSection");
FormatBlock spanBlock = new FormatBlock(Collections.singletonList((Block) linkBlock), Format.NONE, spanParameters);
Map<String, String> params = new HashMap<String, String>();
params.put("class", "header-container");
Block headerContainer = new GroupBlock(params);
headerContainer.addChild(block);
headerContainer.addChild(spanBlock);
section.replaceChild(headerContainer, block);
params.put("class", "section-container");
Block sectionContainer = new GroupBlock(params);
sectionContainer.addChild(section);
parentBlock.replaceChild(sectionContainer, section);
}
}
WikiPrinter printer = convert(xdom, sSyntax, tSyntax);
return printer.toString();
}
public String getContentOfSection(String markup, String sourceSyntax, String sectionIndex) throws Exception {
XDOM xdom = parse(markup, sourceSyntax);
Syntax sSyntax = (sourceSyntax == null) ? Syntax.XWIKI_2_0 : getSyntax(sourceSyntax);
List<HeaderBlock> headers = getFilteredHeaders(xdom);
int index = Integer.parseInt(sectionIndex);
String content = null;
if (headers.size() >= index) {
SectionBlock section = headers.get(index - 1).getSection();
content = renderXDOM(new XDOM(Collections.<Block> singletonList(section)), sSyntax);
}
return content;
}
public String updateContentOfSection(String markup, String sourceSyntax, String sectionIndex, String newSectionContent) throws Exception {
XDOM xdom = parse(markup, sourceSyntax);
Syntax sSyntax = (sourceSyntax == null) ? Syntax.XWIKI_2_0 : getSyntax(sourceSyntax);
List<HeaderBlock> headers = getFilteredHeaders(xdom);
int index = Integer.parseInt(sectionIndex);
String content = null;
if (headers.size() >= index) {
HeaderBlock header = headers.get(index - 1);
List<Block> blocks = parse(newSectionContent, sourceSyntax).getChildren();
int sectionLevel = header.getLevel().getAsInt();
for (int level = 1; level < sectionLevel && blocks.size() == 1
&& blocks.get(0) instanceof SectionBlock; ++level) {
blocks = blocks.get(0).getChildren();
}
// replace old current SectionBlock with new Blocks
Block section = header.getSection();
section.getParent().replaceChild(blocks, section);
// render back XDOM to document's content syntax
content = renderXDOM(xdom, sSyntax);
}
return content;
}
private String clean(String dirtyHTML)
{
HTMLCleaner cleaner = getComponent(HTMLCleaner.class);
HTMLCleanerConfiguration config = cleaner.getDefaultConfiguration();
Document document = cleaner.clean(new StringReader(dirtyHTML), config);
return HTMLUtils.toString(document);
}
@Override
public void start() {
componentManager = new EmbeddableComponentManager();
componentManager.initialize(this.getClass().getClassLoader());
}
@Override
public void stop() {
}
private <T> T getComponent(Class<T> clazz, String hint) {
T component = null;
if (componentManager != null) {
try {
component = componentManager.lookup(clazz, hint);
} catch (ComponentLookupException e) {
throw new RuntimeException("Failed to load component [" + clazz.getName() + "] for hint [" + hint + "]", e);
}
} else {
throw new RuntimeException("Component manager has not been initialized before lookup for [" + clazz.getName() + "] for hint [" + hint + "]");
}
return component;
}
private void outputTree(Block parent, int level) {
StringBuffer buf = new StringBuffer();
int i = 0;
while (i++ < level) {
buf.append(" ");
}
buf.append(parent.getClass().getSimpleName());
if(LOG.isDebugEnabled()){
LOG.debug(buf.toString());
}
List<Block> children = parent.getChildren();
for (Block block : children) {
outputTree(block, level + 1);
}
}
/*
* private XDOM traverseTo(Block parent, int level) { StringBuffer buf = new
* StringBuffer(); int i = 0; while(i++<level) { buf.append(" "); }
* buf.append(parent.getClass().getSimpleName());
* System.out.println(buf.toString()); List<Block> children =
* parent.getChildren(); for (Block block : children) { outputTree(block,
* level+1); } }
*/
private WikiPrinter convert(XDOM xdom, Syntax sourceSyntax, Syntax targetSyntax) throws Exception {
// Step 2: Run transformations
try {
TransformationManager transformationManager = componentManager.lookup(TransformationManager.class);
transformationManager.performTransformations(xdom, sourceSyntax);
} catch (TransformationException e) {
throw new ConversionException("Failed to execute some transformations", e);
}
// Step 3: Locate the Renderer and render the content in the passed printer
WikiPrinter printer = new DefaultWikiPrinter();
BlockRenderer renderer;
try {
renderer = componentManager.lookup(BlockRenderer.class, targetSyntax.toIdString());
} catch (ComponentLookupException e) {
throw new ConversionException("Failed to locate Renderer for syntax [" + targetSyntax + "]",
e);
}
renderer.render(xdom, printer);
return printer;
}
public XDOM parse(String markup, String sourceSyntax) throws Exception {
XDOM xdom;
Syntax sSyntax = (sourceSyntax == null) ? Syntax.XWIKI_2_0 : getSyntax(sourceSyntax);
if (sSyntax == Syntax.XHTML_1_0 || sSyntax == Syntax.ANNOTATED_XHTML_1_0) {
markup = clean(markup);
}
try {
Parser parser = componentManager.lookup(Parser.class, sSyntax.toIdString());
xdom = parser.parse(new StringReader(markup));
} catch (ComponentLookupException e) {
throw new ConversionException("Failed to locate Parser for syntax [" + sSyntax + "]", e);
} catch (ParseException e) {
throw new ConversionException("Failed to parse input source", e);
}
if (LOG.isDebugEnabled()) {
outputTree(xdom, 0);
}
return xdom;
}
private String renderXDOM(Block content, Syntax targetSyntax) throws Exception {
try {
BlockRenderer renderer = componentManager.lookup(BlockRenderer.class, targetSyntax.toIdString());
WikiPrinter printer = new DefaultWikiPrinter();
renderer.render(content, printer);
return printer.toString();
} catch (Exception e) {
throw new ConversionException("Failed to render document to syntax [" + targetSyntax + "]", e);
}
}
private List<HeaderBlock> getFilteredHeaders(XDOM xdom) {
List<HeaderBlock> filteredHeaders = new ArrayList<HeaderBlock>();
// get the headers
List<HeaderBlock> headers = xdom.getChildrenByType(HeaderBlock.class, true);
// get the maximum header level
int sectionDepth = 3;
// filter the headers
for (HeaderBlock header : headers) {
if (header.getLevel().getAsInt() <= sectionDepth) {
filteredHeaders.add(header);
}
}
return filteredHeaders;
}
private Syntax getSyntax(String syntaxId) {
Syntax syntax = Syntax.XWIKI_2_0;
if (Syntax.XWIKI_2_0.toIdString().equals(syntaxId)) {
syntax = Syntax.XWIKI_2_0;
} else if (Syntax.CREOLE_1_0.toIdString().equals(syntaxId)) {
syntax = Syntax.CREOLE_1_0;
} else if (Syntax.CONFLUENCE_1_0.toIdString().equals(syntaxId)) {
syntax = Syntax.CONFLUENCE_1_0;
} else if (Syntax.XHTML_1_0.toIdString().equals(syntaxId)) {
syntax = Syntax.XHTML_1_0;
} else if (Syntax.ANNOTATED_XHTML_1_0.toIdString().equals(syntaxId)) {
syntax = Syntax.ANNOTATED_XHTML_1_0;
} else if (Syntax.MEDIAWIKI_1_0.toIdString().equals(syntaxId)) {
syntax = Syntax.MEDIAWIKI_1_0;
} else if (Syntax.XWIKI_1_0.toIdString().equals(syntaxId)) {
syntax = Syntax.XWIKI_1_0;
} else if (Syntax.JSPWIKI_1_0.toIdString().equals(syntaxId)) {
syntax = Syntax.JSPWIKI_1_0;
} else if (Syntax.TWIKI_1_0.toIdString().equals(syntaxId)) {
syntax = Syntax.TWIKI_1_0;
} else if (Syntax.HTML_4_01.toIdString().equals(syntaxId)) {
syntax = Syntax.HTML_4_01;
} else if (Syntax.PLAIN_1_0.toIdString().equals(syntaxId)) {
syntax = Syntax.PLAIN_1_0;
} else if (Syntax.TEX_1_0.toIdString().equals(syntaxId)) {
syntax = Syntax.TEX_1_0;
} else if (Syntax.EVENT_1_0.toIdString().equals(syntaxId)) {
syntax = Syntax.EVENT_1_0;
}
return syntax;
}
public String getCssURL() {
return cssURL;
}
public void setCssURL(String cssURL) {
this.cssURL = cssURL;
}
}