All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.xwiki.rendering.internal.renderer.xhtml.XHTMLChainingRenderer Maven / Gradle / Ivy

The newest version!
/*
 * 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.rendering.internal.renderer.xhtml;

import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.xwiki.rendering.internal.renderer.xhtml.image.XHTMLImageRenderer;
import org.xwiki.rendering.internal.renderer.xhtml.link.XHTMLLinkRenderer;
import org.xwiki.rendering.listener.Format;
import org.xwiki.rendering.listener.HeaderLevel;
import org.xwiki.rendering.listener.ListType;
import org.xwiki.rendering.listener.MetaData;
import org.xwiki.rendering.listener.chaining.BlockStateChainingListener;
import org.xwiki.rendering.listener.chaining.BlockStateChainingListener.Event;
import org.xwiki.rendering.listener.chaining.EmptyBlockChainingListener;
import org.xwiki.rendering.listener.chaining.ListenerChain;
import org.xwiki.rendering.listener.chaining.MetaDataStateChainingListener;
import org.xwiki.rendering.listener.reference.ResourceReference;
import org.xwiki.rendering.renderer.AbstractChainingPrintRenderer;
import org.xwiki.rendering.renderer.printer.WikiPrinter;
import org.xwiki.rendering.renderer.printer.XHTMLWikiPrinter;
import org.xwiki.rendering.syntax.Syntax;
import org.xwiki.rendering.syntax.SyntaxType;
import org.xwiki.xml.html.HTMLConstants;
import org.xwiki.xml.html.HTMLElementSanitizer;

/**
 * Convert listener events to XHTML.
 *
 * @version $Id: 58b61861e09ee495557cff07fa3327683ea1477d $
 * @since 1.8RC1
 */
public class XHTMLChainingRenderer extends AbstractChainingPrintRenderer
{
    /**
     * Class attribute value that indicates if the header was generated by a macro.
     */
    public static final String GENERATEDHEADERCLASS = "wikigeneratedheader";

    /**
     * Class attribute value that indicates if the header id attribute was generated automatically or if it was the user
     * who specified an id.
     */
    public static final String GENERATEDIDCLASS = "wikigeneratedid";

    private XHTMLLinkRenderer linkRenderer;

    private XHTMLImageRenderer imageRenderer;

    private final HTMLElementSanitizer htmlElementSanitizer;

    private XHTMLWikiPrinter xhtmlWikiPrinter;

    /**
     * @param linkRenderer the object to render link events into XHTML. This is done so that it's pluggable because
     *     link rendering depends on how the underlying system wants to handle it. For example for XWiki we check if the
     *     document exists, we get the document URL, etc.
     * @param imageRenderer the object to render image events into XHTML. This is done so that it's pluggable
     *     because image rendering depends on how the underlying system wants to handle it. For example for XWiki we
     *     check if the image exists as a document attachments, we get its URL, etc.
     * @param htmlElementSanitizer the sanitizer for XHTML elements
     * @param listenerChain the chain of listener filters used to compute various states
     */
    public XHTMLChainingRenderer(XHTMLLinkRenderer linkRenderer, XHTMLImageRenderer imageRenderer,
        HTMLElementSanitizer htmlElementSanitizer, ListenerChain listenerChain)
    {
        setListenerChain(listenerChain);

        this.linkRenderer = linkRenderer;
        this.imageRenderer = imageRenderer;
        this.htmlElementSanitizer = htmlElementSanitizer;
    }

    // State

    protected BlockStateChainingListener getBlockState()
    {
        return (BlockStateChainingListener) getListenerChain().getListener(BlockStateChainingListener.class);
    }

    protected EmptyBlockChainingListener getEmptyBlockState()
    {
        return (EmptyBlockChainingListener) getListenerChain().getListener(EmptyBlockChainingListener.class);
    }

    protected MetaDataStateChainingListener getMetaDataState()
    {
        return (MetaDataStateChainingListener) getListenerChain().getListener(MetaDataStateChainingListener.class);
    }

    // Printer

    @Override
    protected void pushPrinter(WikiPrinter wikiPrinter)
    {
        super.pushPrinter(wikiPrinter);
        getXHTMLWikiPrinter().setWikiPrinter(getPrinter());
    }

    @Override
    protected void popPrinter()
    {
        super.popPrinter();
        getXHTMLWikiPrinter().setWikiPrinter(getPrinter());
    }

    protected XHTMLWikiPrinter getXHTMLWikiPrinter()
    {
        if (this.xhtmlWikiPrinter == null) {
            this.xhtmlWikiPrinter = new XHTMLWikiPrinter(getPrinter(), getHtmlElementSanitizer());
        }
        return this.xhtmlWikiPrinter;
    }

    protected HTMLElementSanitizer getHtmlElementSanitizer()
    {
        return this.htmlElementSanitizer;
    }

    // Events

    @Override
    public void beginGroup(Map parameters)
    {
        Map clonedParameters = new LinkedHashMap();
        clonedParameters.putAll(parameters);
        getXHTMLWikiPrinter().setStandalone();
        getXHTMLWikiPrinter().printXMLStartElement("div", clonedParameters);
    }

    @Override
    public void endGroup(Map parameters)
    {
        getXHTMLWikiPrinter().printXMLEndElement("div");
    }

    @Override
    public void beginFormat(Format format, Map parameters)
    {
        switch (format) {
            case BOLD:
                getXHTMLWikiPrinter().printXMLStartElement("strong");
                break;
            case ITALIC:
                getXHTMLWikiPrinter().printXMLStartElement("em");
                break;
            case STRIKEDOUT:
                getXHTMLWikiPrinter().printXMLStartElement("del");
                break;
            case UNDERLINED:
                getXHTMLWikiPrinter().printXMLStartElement("ins");
                break;
            case SUPERSCRIPT:
                getXHTMLWikiPrinter().printXMLStartElement("sup");
                break;
            case SUBSCRIPT:
                getXHTMLWikiPrinter().printXMLStartElement("sub");
                break;
            case MONOSPACE:
                getXHTMLWikiPrinter().printXMLStartElement("tt");
                break;
            case NONE:
                break;
            // Unsupported format
            default:
                break;
        }
        if (!parameters.isEmpty()) {
            getXHTMLWikiPrinter().printXMLStartElement("span", parameters);
        }
    }

    @Override
    public void endFormat(Format format, Map parameters)
    {
        if (!parameters.isEmpty()) {
            getXHTMLWikiPrinter().printXMLEndElement("span");
        }
        switch (format) {
            case BOLD:
                getXHTMLWikiPrinter().printXMLEndElement("strong");
                break;
            case ITALIC:
                getXHTMLWikiPrinter().printXMLEndElement("em");
                break;
            case STRIKEDOUT:
                getXHTMLWikiPrinter().printXMLEndElement("del");
                break;
            case UNDERLINED:
                getXHTMLWikiPrinter().printXMLEndElement("ins");
                break;
            case SUPERSCRIPT:
                getXHTMLWikiPrinter().printXMLEndElement("sup");
                break;
            case SUBSCRIPT:
                getXHTMLWikiPrinter().printXMLEndElement("sub");
                break;
            case MONOSPACE:
                getXHTMLWikiPrinter().printXMLEndElement("tt");
                break;
            case NONE:
                break;
            // Unsupported format
            default:
                break;
        }
    }

    @Override
    public void beginParagraph(Map parameters)
    {
        getXHTMLWikiPrinter().setStandalone();
        getXHTMLWikiPrinter().printXMLStartElement("p", parameters);
    }

    @Override
    public void endParagraph(Map parameters)
    {
        getXHTMLWikiPrinter().printXMLEndElement("p");
    }

    @Override
    public void onNewLine()
    {
        getXHTMLWikiPrinter().printXMLElement("br");
    }

    @Override
    public void beginLink(ResourceReference reference, boolean freestanding, Map parameters)
    {
        // Ensure the link renderer is using the latest printer since the original printer used could have been
        // superseded by another one in the printer stack.
        this.linkRenderer.setXHTMLWikiPrinter(getXHTMLWikiPrinter());

        // If the ResourceReference doesn't have a base reference specified, then look for one in previously sent
        // events (it's sent in begin/endMetaData events).
        List baseReferences = reference.getBaseReferences();
        if (baseReferences.isEmpty()) {
            reference.addBaseReferences(getMetaDataState().getAllMetaData(MetaData.BASE));
        }

        this.linkRenderer.beginLink(reference, freestanding, parameters);
    }

    @Override
    public void endLink(ResourceReference reference, boolean freestanding, Map parameters)
    {
        this.linkRenderer.setHasLabel(!getEmptyBlockState().isCurrentContainerBlockEmpty());
        this.linkRenderer.endLink(reference, freestanding, parameters);
    }

    @Override
    public void beginHeader(HeaderLevel level, String id, Map parameters)
    {
        Map attributes = new LinkedHashMap();

        attributes.put("id", id);

        // Indicate that the id is generated. This is to differentiate from ids added as parameters.
        // Note that we add this only if the user hasn't specified an id as an override in a parameter.
        if (!parameters.containsKey("id")) {
            addClassValue("class", GENERATEDIDCLASS, attributes);
        }

        attributes.putAll(parameters);

        // Section editing feature:
        // In order for the UI side to be able to add a section edit button we need to provide some information to it
        // and especially we need to tell it if the header was a header generated by a macro or not. The reason is
        // that macro-generated headers should not be editable by the user.
        // TODO: In the future it's possible that we'll want this kind of behavior implemented using a Transformation.
        // If we decide this then remove this code.
        if (getBlockState().isInMacro()) {
            addClassValue("class", GENERATEDHEADERCLASS, attributes);
        }

        getXHTMLWikiPrinter().setStandalone();
        getXHTMLWikiPrinter().printXMLStartElement("h" + level.getAsInt(), attributes);
        // We generate a span so that CSS rules have a hook to perform some magic that wouldn't work on just a H
        // element. Like some IE6 magic and others.
        getXHTMLWikiPrinter().printXMLStartElement("span");
    }

    @Override
    public void endHeader(HeaderLevel level, String id, Map parameters)
    {
        getXHTMLWikiPrinter().printXMLEndElement("span");
        getXHTMLWikiPrinter().printXMLEndElement("h" + level.getAsInt());
    }

    @Override
    public void onWord(String word)
    {
        getXHTMLWikiPrinter().printXML(word);
    }

    @Override
    public void onSpace()
    {
        // The XHTML printer will decide whether to print a normal space or a  
        getXHTMLWikiPrinter().printSpace();
    }

    @Override
    public void onSpecialSymbol(char symbol)
    {
        getXHTMLWikiPrinter().printXML(String.valueOf(symbol));
    }

    @Override
    public void beginList(ListType type, Map parameters)
    {
        if (type == ListType.BULLETED) {
            getXHTMLWikiPrinter().printXMLStartElement("ul", parameters);
        } else {
            getXHTMLWikiPrinter().printXMLStartElement("ol", parameters);
        }
    }

    @Override
    public void beginListItem()
    {
        getXHTMLWikiPrinter().setStandalone();
        getXHTMLWikiPrinter().printXMLStartElement("li");
    }

    @Override
    public void beginListItem(Map parameters)
    {
        getXHTMLWikiPrinter().printXMLStartElement("li", parameters);
    }

    @Override
    public void endList(ListType type, Map parameters)
    {
        if (type == ListType.BULLETED) {
            getXHTMLWikiPrinter().printXMLEndElement("ul");
        } else {
            getXHTMLWikiPrinter().printXMLEndElement("ol");
        }
    }

    @Override
    public void endListItem()
    {
        getXHTMLWikiPrinter().printXMLEndElement("li");
    }

    @Override
    public void endListItem(Map parameters)
    {
        endListItem();
    }

    @Override
    public void onId(String name)
    {
        // Don't use the "name" attribute (see http://www.w3.org/TR/html4/struct/links.html#h-12.2.3).
        // If the id s in a paragraph use  and if in a standalone block then use
        // 
. if (getBlockState().isInLine()) { // Note: We're using and not since some browsers do not support the // syntax (FF3) when the content type is set to HTML instead of XHTML. getXHTMLWikiPrinter().printXMLStartElement("span", new String[][] { { "id", name } }); getXHTMLWikiPrinter().printXMLEndElement("span"); } else { getXHTMLWikiPrinter().printXMLStartElement("div", new String[][] { { "id", name } }); getXHTMLWikiPrinter().printXMLEndElement("div"); } } @Override public void onHorizontalLine(Map parameters) { getXHTMLWikiPrinter().printXMLElement("hr", parameters); } @Override public void onVerbatim(String content, boolean inline, Map parameters) { if (inline) { // Note: We generate a tt element rather than a pre element since pre elements cannot be located inside // paragraphs for example. There also no tag in XHTML that has a semantic of preserving inline content so // tt is the closest to pre for inline. // The class is what is expected by wikimodel to understand the tt as meaning a verbatim and not a Monospace // element. getXHTMLWikiPrinter().printXMLStartElement("tt", new String[][] { { "class", "wikimodel-verbatim" } }); getXHTMLWikiPrinter().printXML(content); getXHTMLWikiPrinter().printXMLEndElement("tt"); } else { getXHTMLWikiPrinter().printXMLStartElement("pre", parameters); getXHTMLWikiPrinter().printXML(content); getXHTMLWikiPrinter().printXMLEndElement("pre"); } } @Override public void onEmptyLines(int count) { // We need to use a special tag for empty lines since in XHTML the BR tag cannot be used outside of content // tags. // Note: We're using
and not
since some browsers do not support the
syntax (FF3) // when the content type is set to HTML instead of XHTML. for (int i = 0; i < count; ++i) { getXHTMLWikiPrinter().printXMLStartElement("div", new String[][] { { "class", "wikimodel-emptyline" } }); getXHTMLWikiPrinter().printXMLEndElement("div"); } } /** * {@inheritDoc} * * @since 2.0RC1 */ @Override public void beginDefinitionList(Map parameters) { getXHTMLWikiPrinter().printXMLStartElement("dl", parameters); } /** * {@inheritDoc} * * @since 2.0RC1 */ @Override public void endDefinitionList(Map parameters) { getXHTMLWikiPrinter().printXMLEndElement("dl"); } @Override public void beginDefinitionTerm() { getXHTMLWikiPrinter().printXMLStartElement("dt"); } @Override public void beginDefinitionDescription() { getXHTMLWikiPrinter().printXMLStartElement("dd"); } @Override public void endDefinitionTerm() { getXHTMLWikiPrinter().printXMLEndElement("dt"); } @Override public void endDefinitionDescription() { getXHTMLWikiPrinter().printXMLEndElement("dd"); } @Override public void beginQuotation(Map parameters) { if (getBlockState().isInQuotationLine()) { getXHTMLWikiPrinter().printXMLEndElement("p"); } getXHTMLWikiPrinter().printXMLStartElement("blockquote", parameters); getXHTMLWikiPrinter().setStandalone(); getXHTMLWikiPrinter().printXMLStartElement("p"); } @Override public void endQuotation(Map parameters) { getXHTMLWikiPrinter().printXMLEndElement("p"); getXHTMLWikiPrinter().printXMLEndElement("blockquote"); if (getBlockState().isInQuotationLine()) { getXHTMLWikiPrinter().printXMLStartElement("p"); } } @Override public void beginQuotationLine() { // Send a new line if the previous event was endQuotationLine since we need to separate each quotation line // or they'll printed next to each other and not on a new line each. if (getBlockState().isInQuotation() && getBlockState().getPreviousEvent() == Event.QUOTATION_LINE) { onNewLine(); } } @Override public void beginTable(Map parameters) { getXHTMLWikiPrinter().printXMLStartElement("table", parameters); } @Override public void beginTableRow(Map parameters) { getXHTMLWikiPrinter().printXMLStartElement("tr", parameters); } @Override public void beginTableCell(Map parameters) { getXHTMLWikiPrinter().setStandalone(); getXHTMLWikiPrinter().printXMLStartElement("td", parameters); } @Override public void beginTableHeadCell(Map parameters) { getXHTMLWikiPrinter().setStandalone(); // Find proper scope attribute value Map parametersWithScope; if (!parameters.containsKey("scope")) { parametersWithScope = new LinkedHashMap(parameters); if (getBlockState().getCellRow() == 0 || getBlockState().getCellCol() > 0) { parametersWithScope.put("scope", "col"); } else { parametersWithScope.put("scope", "row"); } } else { parametersWithScope = parameters; } // Write th element getXHTMLWikiPrinter().printXMLStartElement("th", parametersWithScope); } @Override public void endTable(Map parameters) { getXHTMLWikiPrinter().printXMLEndElement("table"); } @Override public void endTableRow(Map parameters) { getXHTMLWikiPrinter().printXMLEndElement("tr"); } @Override public void endTableCell(Map parameters) { getXHTMLWikiPrinter().printXMLEndElement("td"); } @Override public void endTableHeadCell(Map parameters) { getXHTMLWikiPrinter().printXMLEndElement("th"); } /** * {@inheritDoc} * * @since 2.5RC1 */ @Override public void onImage(ResourceReference reference, boolean freestanding, Map parameters) { onImage(reference, freestanding, null, parameters); } @Override public void onImage(ResourceReference reference, boolean freestanding, String id, Map parameters) { // Ensure the image renderer is using the latest printer since the original printer used could have been // superseded by another one in the printer stack. this.imageRenderer.setXHTMLWikiPrinter(getXHTMLWikiPrinter()); // If the ResourceReference doesn't have a base reference specified, then look for one in previously sent // events (it's sent in begin/endMetaData events). List baseReferences = reference.getBaseReferences(); if (baseReferences.isEmpty()) { reference.addBaseReferences(getMetaDataState().getAllMetaData(MetaData.BASE)); } this.imageRenderer.onImage(reference, freestanding, id, parameters); } @Override public void onRawText(String text, Syntax syntax) { // Directly inject the HTML content in the wiki printer (bypassing the XHTML printer) if (SyntaxType.HTML_FAMILY_TYPES.contains(syntax.getType())) { getXHTMLWikiPrinter().printRaw(text); } } @Override public void beginFigureCaption(Map parameters) { // We add a div to have some nice fallback (since
/
tags are not supported in XHTML 1.0). Map extendedParameters = new LinkedHashMap<>(parameters); addClassValue(HTMLConstants.ATTRIBUTE_CLASS, "figcaption", extendedParameters); getXHTMLWikiPrinter().printXMLStartElement(HTMLConstants.TAG_DIV, extendedParameters); } @Override public void endFigureCaption(Map parameters) { // See beginFigureCaption() getXHTMLWikiPrinter().printXMLEndElement(HTMLConstants.TAG_DIV); } private void addClassValue(String classAttributeName, String newClassValue, Map attributes) { String classValue = attributes.get(classAttributeName); if (classValue == null) { classValue = newClassValue; } else { classValue = classValue.trim() + " " + newClassValue; } attributes.put(classAttributeName, classValue); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy