net.sf.jasperreports.engine.util.JEditorPaneHtmlMarkupProcessor Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jasperreports Show documentation
Show all versions of jasperreports Show documentation
Free Java Reporting Library
/*
* 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);
}
}