net.sf.jasperreports.engine.util.JEditorPaneHtmlMarkupProcessor.java Source code

Java tutorial

Introduction

Here is the source code for net.sf.jasperreports.engine.util.JEditorPaneHtmlMarkupProcessor.java

Source

/*
 * JasperReports - Free Java Reporting Library.
 * Copyright (C) 2001 - 2019 TIBCO Software Inc. All rights reserved.
 * http://www.jaspersoft.com
 *
 * Unless you have purchased a commercial license agreement from Jaspersoft,
 * the following license terms apply:
 *
 * This program is part of JasperReports.
 *
 * JasperReports 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 3 of the License, or
 * (at your option) any later version.
 *
 * JasperReports 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 JasperReports. If not, see <http://www.gnu.org/licenses/>.
 */
package net.sf.jasperreports.engine.util;

import java.awt.font.TextAttribute;
import java.text.AttributedCharacterIterator.Attribute;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;

import javax.swing.JEditorPane;
import javax.swing.text.AbstractDocument;
import javax.swing.text.AbstractDocument.LeafElement;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.Element;
import javax.swing.text.StyleConstants;
import javax.swing.text.html.HTML;
import javax.swing.text.html.HTML.Tag;
import javax.swing.text.html.HTMLDocument.RunElement;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import net.sf.jasperreports.engine.JRPrintHyperlink;
import net.sf.jasperreports.engine.JRRuntimeException;
import net.sf.jasperreports.engine.base.JRBasePrintHyperlink;
import net.sf.jasperreports.engine.type.HyperlinkTypeEnum;

/**
 * @author Teodor Danciu (teodord@users.sourceforge.net)
 */
public class JEditorPaneHtmlMarkupProcessor extends JEditorPaneMarkupProcessor {
    private static final Log log = LogFactory.getLog(JEditorPaneHtmlMarkupProcessor.class);

    public static final String DEFAULT_BULLET_CHARACTER = "\u2022";
    public static final String DEFAULT_BULLET_SEPARATOR = ".";

    private static JEditorPaneHtmlMarkupProcessor instance;

    /**
     * 
     */
    public static JEditorPaneHtmlMarkupProcessor getInstance() {
        if (instance == null) {
            instance = new JEditorPaneHtmlMarkupProcessor();
        }
        return instance;
    }

    @Override
    public String convert(String srcText) {
        JEditorPane editorPane = new JEditorPane("text/html", srcText);
        editorPane.setEditable(false);

        List<Element> elements = new ArrayList<Element>();

        Document document = editorPane.getDocument();

        Element root = document.getDefaultRootElement();
        if (root != null) {
            addElements(elements, root);
        }

        int startOffset = 0;
        int endOffset = 0;
        int crtOffset = 0;
        String chunk = null;
        JRPrintHyperlink hyperlink = null;
        Element element = null;
        Element parent = null;
        boolean bodyOccurred = false;
        int[] orderedListIndex = new int[elements.size()];
        String whitespace = "    ";
        String[] whitespaces = new String[elements.size()];
        for (int i = 0; i < elements.size(); i++) {
            whitespaces[i] = "";
        }

        StringBuilder text = new StringBuilder();
        List<JRStyledText.Run> styleRuns = new ArrayList<>();

        for (int i = 0; i < elements.size(); i++) {
            if (bodyOccurred && chunk != null) {
                text.append(chunk);
                Map<Attribute, Object> styleAttributes = getAttributes(element.getAttributes());
                if (hyperlink != null) {
                    styleAttributes.put(JRTextAttribute.HYPERLINK, hyperlink);
                    hyperlink = null;
                }
                if (!styleAttributes.isEmpty()) {
                    styleRuns.add(
                            new JRStyledText.Run(styleAttributes, startOffset + crtOffset, endOffset + crtOffset));
                }
            }

            chunk = null;
            element = elements.get(i);
            parent = element.getParentElement();
            startOffset = element.getStartOffset();
            endOffset = element.getEndOffset();
            AttributeSet attrs = element.getAttributes();

            Object elementName = attrs.getAttribute(AbstractDocument.ElementNameAttribute);
            Object object = (elementName != null) ? null : attrs.getAttribute(StyleConstants.NameAttribute);
            if (object instanceof HTML.Tag) {

                HTML.Tag htmlTag = (HTML.Tag) object;
                if (htmlTag == Tag.BODY) {
                    bodyOccurred = true;
                    crtOffset = -startOffset;
                } else if (htmlTag == Tag.BR) {
                    chunk = "\n";
                } else if (htmlTag == Tag.OL) {
                    orderedListIndex[i] = 0;
                    String parentName = parent.getName().toLowerCase();
                    whitespaces[i] = whitespaces[elements.indexOf(parent)] + whitespace;
                    if (parentName.equals("li")) {
                        chunk = "";
                    } else {
                        chunk = "\n";
                        ++crtOffset;
                    }
                } else if (htmlTag == Tag.UL) {
                    whitespaces[i] = whitespaces[elements.indexOf(parent)] + whitespace;

                    String parentName = parent.getName().toLowerCase();
                    if (parentName.equals("li")) {
                        chunk = "";
                    } else {
                        chunk = "\n";
                        ++crtOffset;
                    }

                } else if (htmlTag == Tag.LI) {

                    whitespaces[i] = whitespaces[elements.indexOf(parent)];
                    if (element.getElement(0) != null && (element.getElement(0).getName().toLowerCase().equals("ol")
                            || element.getElement(0).getName().toLowerCase().equals("ul"))) {
                        chunk = "";
                    } else if (parent.getName().equals("ol")) {
                        int index = elements.indexOf(parent);
                        Object type = parent.getAttributes().getAttribute(HTML.Attribute.TYPE);
                        Object startObject = parent.getAttributes().getAttribute(HTML.Attribute.START);
                        int start = startObject == null ? 0
                                : Math.max(0, Integer.valueOf(startObject.toString()) - 1);
                        String suffix = "";

                        ++orderedListIndex[index];

                        if (type != null) {
                            switch (((String) type).charAt(0)) {
                            case 'A':
                                suffix = getOLBulletChars(orderedListIndex[index] + start, true);
                                break;
                            case 'a':
                                suffix = getOLBulletChars(orderedListIndex[index] + start, false);
                                break;
                            case 'I':
                                suffix = JRStringUtil.getRomanNumeral(orderedListIndex[index] + start, true);
                                break;
                            case 'i':
                                suffix = JRStringUtil.getRomanNumeral(orderedListIndex[index] + start, false);
                                break;
                            case '1':
                            default:
                                suffix = String.valueOf(orderedListIndex[index] + start);
                                break;
                            }
                        } else {
                            suffix += orderedListIndex[index] + start;
                        }
                        chunk = whitespaces[index] + suffix + DEFAULT_BULLET_SEPARATOR + "  ";

                    } else {
                        chunk = whitespaces[elements.indexOf(parent)] + DEFAULT_BULLET_CHARACTER + "  ";
                    }
                    crtOffset += chunk.length();
                } else if (element instanceof LeafElement) {
                    if (element instanceof RunElement) {
                        RunElement runElement = (RunElement) element;
                        AttributeSet attrSet = (AttributeSet) runElement.getAttribute(Tag.A);
                        if (attrSet != null) {
                            hyperlink = new JRBasePrintHyperlink();
                            hyperlink.setHyperlinkType(HyperlinkTypeEnum.REFERENCE);
                            hyperlink.setHyperlinkReference((String) attrSet.getAttribute(HTML.Attribute.HREF));
                            hyperlink.setLinkTarget((String) attrSet.getAttribute(HTML.Attribute.TARGET));
                        }
                    }
                    try {
                        chunk = document.getText(startOffset, endOffset - startOffset);
                    } catch (BadLocationException e) {
                        if (log.isDebugEnabled()) {
                            log.debug("Error converting markup.", e);
                        }
                    }
                }
            }
        }

        if (chunk != null) {
            if (!"\n".equals(chunk)) {
                text.append(chunk);
                Map<Attribute, Object> styleAttributes = getAttributes(element.getAttributes());
                if (hyperlink != null) {
                    styleAttributes.put(JRTextAttribute.HYPERLINK, hyperlink);
                    hyperlink = null;
                }
                if (!styleAttributes.isEmpty()) {
                    styleRuns.add(
                            new JRStyledText.Run(styleAttributes, startOffset + crtOffset, endOffset + crtOffset));
                }
            } else {
                //final newline, not appending
                //check if there's any style run that would have covered it, that can happen if there's a <li> tag with style
                int length = text.length();
                for (ListIterator<JRStyledText.Run> it = styleRuns.listIterator(); it.hasNext();) {
                    JRStyledText.Run run = it.next();
                    //only looking at runs that end at the position where the newline should have been
                    //we don't want to hide bugs in which runs that span after the text length are created
                    if (run.endIndex == length + 1) {
                        if (run.startIndex < run.endIndex - 1) {
                            it.set(new JRStyledText.Run(run.attributes, run.startIndex, run.endIndex - 1));
                        } else {
                            it.remove();
                        }
                    }
                }
            }
        }

        JRStyledText styledText = new JRStyledText(null, text.toString());
        for (JRStyledText.Run run : styleRuns) {
            styledText.addRun(run);
        }
        styledText.setGlobalAttributes(new HashMap<Attribute, Object>());

        return JRStyledTextParser.getInstance().write(styledText);
    }

    /**
     * 
     */
    protected void addElements(List<Element> elements, Element element) {
        //if(element instanceof LeafElement)
        {
            elements.add(element);
        }
        for (int i = 0; i < element.getElementCount(); i++) {
            Element child = element.getElement(i);
            addElements(elements, child);
        }
    }

    @Override
    protected Map<Attribute, Object> getAttributes(AttributeSet attrSet) {
        Map<Attribute, Object> attrMap = new HashMap<Attribute, Object>();
        if (attrSet.isDefined(StyleConstants.FontFamily)) {
            attrMap.put(TextAttribute.FAMILY, StyleConstants.getFontFamily(attrSet));
        }

        if (attrSet.isDefined(StyleConstants.Bold)) {
            attrMap.put(TextAttribute.WEIGHT,
                    StyleConstants.isBold(attrSet) ? TextAttribute.WEIGHT_BOLD : TextAttribute.WEIGHT_REGULAR);
        }

        if (attrSet.isDefined(StyleConstants.Italic)) {
            attrMap.put(TextAttribute.POSTURE, StyleConstants.isItalic(attrSet) ? TextAttribute.POSTURE_OBLIQUE
                    : TextAttribute.POSTURE_REGULAR);
        }

        if (attrSet.isDefined(StyleConstants.Underline)) {
            attrMap.put(TextAttribute.UNDERLINE,
                    StyleConstants.isUnderline(attrSet) ? TextAttribute.UNDERLINE_ON : null);
        }

        if (attrSet.isDefined(StyleConstants.StrikeThrough)) {
            attrMap.put(TextAttribute.STRIKETHROUGH,
                    StyleConstants.isStrikeThrough(attrSet) ? TextAttribute.STRIKETHROUGH_ON : null);
        }

        if (attrSet.isDefined(StyleConstants.FontSize)) {
            attrMap.put(TextAttribute.SIZE, StyleConstants.getFontSize(attrSet));
        }

        if (attrSet.isDefined(StyleConstants.Foreground)) {
            attrMap.put(TextAttribute.FOREGROUND, StyleConstants.getForeground(attrSet));
        }

        if (attrSet.isDefined(StyleConstants.Background)) {
            attrMap.put(TextAttribute.BACKGROUND, StyleConstants.getBackground(attrSet));
        }

        //FIXME: why StyleConstants.isSuperscript(attrSet) does return false
        if (attrSet.isDefined(StyleConstants.Superscript) && !StyleConstants.isSubscript(attrSet)) {
            attrMap.put(TextAttribute.SUPERSCRIPT, TextAttribute.SUPERSCRIPT_SUPER);
        }

        if (attrSet.isDefined(StyleConstants.Subscript) && StyleConstants.isSubscript(attrSet)) {
            attrMap.put(TextAttribute.SUPERSCRIPT, TextAttribute.SUPERSCRIPT_SUB);
        }

        return attrMap;
    }

    /**
     * 
     * @param index the current index between 0 and 18277
     * @param isUpperCase specifies whether the result should be made of upper case characters
     * @return a character representation of the numeric index in an ordered bullet list, that contains up to 3 chars
     */
    protected static String getOLBulletChars(int index, boolean isUpperCase) {
        // max 3-letter index is 18277
        if (index < 0 || index > 18277) {
            throw new JRRuntimeException(JRStringUtil.EXCEPTION_MESSAGE_KEY_NUMBER_OUTSIDE_BOUNDS,
                    new Object[] { index });
        }

        return JRStringUtil.getLetterNumeral(index, isUpperCase);
    }
}