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

net.sf.jasperreports.engine.util.JEditorPaneHtmlMarkupProcessor Maven / Gradle / Ivy

/*
 * JasperReports - Free Java Reporting Library.
 * Copyright (C) 2001 - 2023 Cloud Software Group, 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 .
 */
package net.sf.jasperreports.engine.util;

import java.text.AttributedCharacterIterator.Attribute;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;

import javax.swing.JEditorPane;
import javax.swing.text.AbstractDocument;
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.JRRuntimeException;
import net.sf.jasperreports.engine.base.JRBasePrintHyperlink;
import net.sf.jasperreports.engine.type.HyperlinkTypeEnum;
import net.sf.jasperreports.engine.util.JRStyledText.Run;


/**
 * @author Teodor Danciu ([email protected])
 */
public class JEditorPaneHtmlMarkupProcessor extends JEditorPaneMarkupProcessor
{
	private static final Log log = LogFactory.getLog(JEditorPaneHtmlMarkupProcessor.class);
	
	private Document document;
	private boolean bodyOccurred = false;
	private boolean isFirstContentTag = true;
	private boolean breaksFlow = false;
	private boolean suppressBreaksFlow = false;
	private int rootEndOffset;

	private Stack htmlListStack;
	private boolean insideLi;
	private boolean liStart;
	private StyledTextListInfo justClosedList;
	
	/**
	 * 
	 */
	public static JEditorPaneHtmlMarkupProcessor getInstance()
	{
		return new JEditorPaneHtmlMarkupProcessor();
	}
	
	@Override
	public String convert(String srcText)
	{
		if (srcText.indexOf('<') >= 0 || srcText.indexOf('&') >= 0)
		{
			JRStyledText styledText = new JRStyledText();
			
			htmlListStack = new Stack<>();

			JEditorPane editorPane = new JEditorPane("text/html", srcText);
			editorPane.setEditable(false);

			document = editorPane.getDocument();
			bodyOccurred = false;
			isFirstContentTag = true;

			Element root = document.getDefaultRootElement();
			if (root != null)
			{
				rootEndOffset = root.getEndOffset();
				processElement(styledText, root);
			}

			styledText.setGlobalAttributes(new HashMap<>());
			
			return JRStyledTextParser.getInstance().write(styledText);
		}
		
		return srcText;
	}
	
	
	private void processElement(JRStyledText styledText, Element parentElement)
	{
		for (int i = 0; i < parentElement.getElementCount(); i++)
		{
			Element element = parentElement.getElement(i);

			AttributeSet attrs = element.getAttributes();

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

			if (htmlTag != null) 
			{
				suppressBreaksFlow |= (htmlTag == Tag.UL || htmlTag == Tag.OL || htmlTag == Tag.LI);
				// the breaksFlow is supposed to keep the aggregated flag for the nested tags
				// that precede the current element, so in theory the current element should not be part of the
				// update of the flag here;
				// but since the breaksFLow flag is used only in BR and CONTENT tags, which are both leaf elements,
				// then it is safe to simply exclude them from the aggregation based on the element.isLeaf() flag
				// the IMPLIED tag is not a breaksFlow one for the HTML parser, but we want it to be a breaksFlow element,
				// in our algorithm, so dealing with it here
				breaksFlow |= (!element.isLeaf() && htmlTag.breaksFlow()) || htmlTag == Tag.IMPLIED; 
				
				if (htmlTag == Tag.BODY)
				{
					bodyOccurred = true;
					processElement(styledText, element);
				}
				else if (htmlTag == Tag.BR)
				{
					if (
						bodyOccurred && !isFirstContentTag && breaksFlow && !suppressBreaksFlow 
						&& i == 0 // only for first BR element in parent
						)
					{
						styledText.append("\n");
						resizeRuns(styledText.getRuns(), styledText.length(), 1);
					}

					if (element.getEndOffset() != rootEndOffset - 1) // only if the BR element is not the very last element of the html snippet 
					{
						styledText.append("\n");
						
						int startIndex = styledText.length();
						resizeRuns(styledText.getRuns(), startIndex, 1);
	
						processElement(styledText, element);
						styledText.addRun(new JRStyledText.Run(new HashMap<>(), startIndex, styledText.length()));
	
						// a second newline is added in case the 
tag was not empty, as it is usually used if (startIndex < styledText.length()) { styledText.append("\n"); resizeRuns(styledText.getRuns(), startIndex, 1); } breaksFlow = false; suppressBreaksFlow = true; } } else if (htmlTag == Tag.OL || htmlTag == Tag.UL) { Object type = attrs.getAttribute(HTML.Attribute.TYPE); Object start = attrs.getAttribute(HTML.Attribute.START); StyledTextListInfo htmlList = new StyledTextListInfo( htmlTag == Tag.OL, htmlTag == Tag.OL && type != null ? String.valueOf(type) : null, htmlTag == Tag.OL && start != null ? Integer.valueOf(start.toString()) : null, insideLi ); htmlList.setAtLiStart(liStart); htmlListStack.push(htmlList); insideLi = false; Map styleAttrs = new HashMap<>(); styleAttrs.put(JRTextAttribute.HTML_LIST, htmlListStack.toArray(new StyledTextListInfo[htmlListStack.size()])); styleAttrs.put(JRTextAttribute.HTML_LIST_ITEM, StyledTextListItemInfo.NO_LIST_ITEM_FILLER); int startIndex = styledText.length(); processElement(styledText, element); styledText.addRun(new JRStyledText.Run(styleAttrs, startIndex, styledText.length())); justClosedList = htmlListStack.pop(); } else if (htmlTag == Tag.LI) { Map styleAttrs = new HashMap<>(); StyledTextListInfo htmlList = null; boolean ulAdded = false; if (htmlListStack.size() == 0) { htmlList = new StyledTextListInfo(false, null, null, false); htmlListStack.push(htmlList); styleAttrs.put(JRTextAttribute.HTML_LIST, htmlListStack.toArray(new StyledTextListInfo[htmlListStack.size()])); styleAttrs.put(JRTextAttribute.HTML_LIST_ITEM, StyledTextListItemInfo.NO_LIST_ITEM_FILLER); ulAdded = true; } else { htmlList = htmlListStack.peek(); } htmlList.setItemCount(htmlList.getItemCount() + 1); insideLi = true; liStart = true; justClosedList = null; styleAttrs.put(JRTextAttribute.HTML_LIST_ITEM, new StyledTextListItemInfo(htmlList.getItemCount() - 1)); int startIndex = styledText.length(); processElement(styledText, element); styledText.addRun(new JRStyledText.Run(styleAttrs, startIndex, styledText.length())); insideLi = false; liStart = false; if (justClosedList != null) { justClosedList.setAtLiEnd(true); } if (ulAdded) { htmlListStack.pop(); } } else if (htmlTag == Tag.CONTENT) { String chunk = null; try { chunk = document.getText(element.getStartOffset(), element.getEndOffset() - element.getStartOffset()); } catch (BadLocationException e) { if (log.isDebugEnabled()) { log.debug("Error converting markup.", e); } } if ( chunk != null && !"\n".equals(chunk) ) { if (bodyOccurred && !isFirstContentTag && breaksFlow && !suppressBreaksFlow) { styledText.append("\n"); resizeRuns(styledText.getRuns(), styledText.length(), 1); } isFirstContentTag = false; breaksFlow = false; suppressBreaksFlow = false; liStart = false; justClosedList = null; int startIndex = styledText.length(); styledText.append(chunk); Map styleAttributes = getAttributes(element.getAttributes()); if (element instanceof RunElement) { RunElement runElement = (RunElement)element; AttributeSet attrSet = (AttributeSet)runElement.getAttribute(Tag.A); if (attrSet != null) { JRBasePrintHyperlink hyperlink = new JRBasePrintHyperlink(); hyperlink.setHyperlinkType(HyperlinkTypeEnum.REFERENCE); hyperlink.setHyperlinkReference((String)attrSet.getAttribute(HTML.Attribute.HREF)); hyperlink.setLinkTarget((String)attrSet.getAttribute(HTML.Attribute.TARGET)); styleAttributes.put(JRTextAttribute.HYPERLINK, hyperlink); } } styledText.addRun( new JRStyledText.Run(styleAttributes, startIndex, styledText.length()) ); } } else { if (bodyOccurred) { processElement(styledText, element); } } suppressBreaksFlow |= (htmlTag == Tag.UL || htmlTag == Tag.OL || htmlTag == Tag.LI); } } } /** * */ private void resizeRuns(List runs, int startIndex, int count) { for (int j = 0; j < runs.size(); j++) { JRStyledText.Run run = runs.get(j); if (run.startIndex <= startIndex && run.endIndex > startIndex - count) { run.endIndex += count; } } } /** * * @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); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy