org.xwiki.rendering.internal.parser.markdown.AbstractTextPegdownVisitor.java Source code

Java tutorial

Introduction

Here is the source code for org.xwiki.rendering.internal.parser.markdown.AbstractTextPegdownVisitor.java

Source

/*
 * 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.parser.markdown;

import java.io.StringReader;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.lang3.StringUtils;
import org.pegdown.ast.AbbreviationNode;
import org.pegdown.ast.BlockQuoteNode;
import org.pegdown.ast.CodeNode;
import org.pegdown.ast.ParaNode;
import org.pegdown.ast.QuotedNode;
import org.pegdown.ast.SimpleNode;
import org.pegdown.ast.SpecialTextNode;
import org.pegdown.ast.TextNode;
import org.pegdown.ast.VerbatimNode;
import org.xwiki.rendering.listener.InlineFilterListener;
import org.xwiki.rendering.listener.WrappingListener;
import org.xwiki.rendering.parser.ParseException;
import org.xwiki.rendering.syntax.Syntax;

/**
 * Implements Pegdown Visitor's text events.
 *
 * @version $Id: c9e976921833fda25d5631043707fe355dabfaa1 $
 * @since 4.5M1
 */
public abstract class AbstractTextPegdownVisitor extends AbstractPegdownVisitor {
    /**
     * Id of the code macro.
     */
    private static final String CODE_MACRO_ID = "code";

    /**
     * Regex to recognize an HTML entity.
     */
    private static final Pattern HTML_ENTITY_PATTERN = Pattern.compile("&[^\\s]*;");

    /**
     * Abbreviation definitions.
     */
    private Map<String, AbbreviationNode> abbreviations = new HashMap<String, AbbreviationNode>();

    /**
     * Whether we're currently in the code that handles abbreviations or not. This is to prevent recursive abbreviation
     * handling.
     */
    private boolean isHandlingAbbreviations;

    @Override
    public void visit(ParaNode paraNode) {
        getListener().beginParagraph(Collections.EMPTY_MAP);
        visitChildren(paraNode);
        getListener().endParagraph(Collections.EMPTY_MAP);
    }

    @Override
    public void visit(TextNode textNode) {
        boolean foundAbbreviation = false;
        if (!this.isHandlingAbbreviations) {
            for (Map.Entry<String, AbbreviationNode> abbreviationEntry : this.abbreviations.entrySet()) {
                int pos = textNode.getText().indexOf(abbreviationEntry.getKey());
                if (pos > -1) {
                    visit(new TextNode(textNode.getText().substring(0, pos)));

                    this.isHandlingAbbreviations = true;
                    String abbreviationDefinition = extractText(abbreviationEntry.getValue().getExpansion());
                    this.isHandlingAbbreviations = false;

                    String html;
                    if (StringUtils.isNotEmpty(abbreviationDefinition)) {
                        html = String.format("<abbr title=\"%s\">%s</abbr>", abbreviationDefinition,
                                abbreviationEntry.getKey());
                    } else {
                        html = String.format("<abbr>%s</abbr>", abbreviationEntry.getKey());
                    }
                    getListener().onRawText(html, Syntax.HTML_4_01);

                    visit(new TextNode(textNode.getText().substring(pos + abbreviationEntry.getKey().length())));
                    foundAbbreviation = true;
                    break;
                }
            }
        }

        if (!foundAbbreviation) {
            // Mardkown supports embedding HTML entities directly in the content. Thus we need to find them and replace
            // them with a RawBlock
            Matcher matcher = HTML_ENTITY_PATTERN.matcher(textNode.getText());
            if (matcher.find()) {
                if (matcher.start() > 0) {
                    visit(textNode.getText().substring(0, matcher.start()));
                }
                getListener().onRawText(matcher.group(), Syntax.HTML_4_01);
                if (matcher.end() < textNode.getText().length()) {
                    visit(new TextNode(textNode.getText().substring(matcher.end())));
                }
            } else {
                visit(textNode.getText());
            }
        }
    }

    /**
     * @param text the text to parse and for which to return XWiki events
     */
    protected void visit(String text) {
        try {
            WrappingListener inlineListener = new InlineFilterListener();
            inlineListener.setWrappedListener(getListener());
            this.plainTextStreamParser.parse(new StringReader(text), inlineListener);
        } catch (ParseException e) {
            throw new RuntimeException(String.format("Error parsing content [%s]", text), e);
        }
    }

    @Override
    public void visit(SpecialTextNode specialTextNode) {
        visit(specialTextNode.getText());
    }

    @Override
    public void visit(CodeNode codeNode) {
        // Since XWiki doesn't have a Code Block we generate a Code Macro Block
        getListener().onMacro(CODE_MACRO_ID, Collections.EMPTY_MAP, codeNode.getText(), true);
    }

    @Override
    public void visit(VerbatimNode verbatimNode) {
        String text = StringUtils.removeEnd(verbatimNode.getText(), "\n");

        Map<String, String> parameters;
        if (verbatimNode.getType().length() > 0) {
            parameters = Collections.singletonMap("language", verbatimNode.getType());
        } else {
            parameters = Collections.EMPTY_MAP;
        }

        getListener().onMacro(CODE_MACRO_ID, parameters, text, false);
    }

    @Override
    public void visit(BlockQuoteNode blockQuoteNode) {
        getListener().beginQuotation(Collections.EMPTY_MAP);

        // XWiki only supports paragraph in quotations, see http://jira.xwiki.org/browse/XRENDERING-259.
        // We replace Paragraph events with QuotationLine events.
        QuoteListener quoteListener = new QuoteListener();
        quoteListener.setWrappedListener(getListener());
        this.listeners.push(quoteListener);
        visitChildren(blockQuoteNode);
        this.listeners.pop();
        quoteListener.closeOpenedQuotationLines();

        getListener().endQuotation(Collections.EMPTY_MAP);
    }

    @Override
    public void visit(QuotedNode quotedNode) {
        // XWiki doesn't have a notion of Quote block and thus we can't store the quote in a manner independent of the
        // rendering... Thus in order to get the same kind of output as users would expect from Markdown we generate
        // "beautified" quotes in our AST. Ideally those should be get beautified by the renderer only.
        switch (quotedNode.getType()) {
        case DoubleAngle:
            getListener().onSpecialSymbol('\u201C');
            visitChildren(quotedNode);
            getListener().onSpecialSymbol('\u201D');
            break;
        case Single:
            getListener().onSpecialSymbol('\'');
            visitChildren(quotedNode);
            getListener().onSpecialSymbol('\'');
            break;
        case Double:
        default:
            getListener().onSpecialSymbol('\"');
            visitChildren(quotedNode);
            getListener().onSpecialSymbol('\"');
            break;
        }
    }

    @Override
    public void visit(SimpleNode simpleNode) {
        switch (simpleNode.getType()) {
        case Linebreak:
            getListener().onNewLine();
            break;
        case Apostrophe:
            getListener().onSpecialSymbol('\'');
            break;
        case HRule:
            getListener().onHorizontalLine(Collections.EMPTY_MAP);
            break;
        case Endash:
            getListener().onSpecialSymbol('\u2013');
            break;
        case Emdash:
            getListener().onSpecialSymbol('\u2014');
            break;
        case Ellipsis:
            getListener().onSpecialSymbol('\u2026');
            break;
        case Nbsp:
        default:
            getListener().onSpace();
            break;
        }
    }

    @Override
    public void visit(AbbreviationNode abbreviationNode) {
        // Since XWiki doesn't support abbreviations, we store abbreviation definitions in memory and when an
        // abbrevitation is used in the text we add the <abbr> HTML element around it.
        this.isHandlingAbbreviations = true;
        this.abbreviations.put(extractText(abbreviationNode), abbreviationNode);
        this.isHandlingAbbreviations = false;
    }
}