package net.sf.jasperreports.engine.util;
import java.awt.Color;
import java.awt.GraphicsEnvironment;
import java.awt.font.TextAttribute;
import java.io.IOException;
import java.io.StringReader;
import java.lang.ref.SoftReference;
import java.text.AttributedCharacterIterator;
import java.text.AttributedString;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import net.sf.jasperreports.engine.JRRuntimeException;
import net.sf.jasperreports.engine.xml.JRXmlConstants;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
public class JRStyledTextParser implements ErrorHandler {
private static final String ROOT_START = "";
private static final String ROOT_END = "";
private static final String NODE_style = "style";
private static final String NODE_bold = "b";
private static final String NODE_italic = "i";
private static final String NODE_underline = "u";
private static final String NODE_sup = "sup";
private static final String NODE_sub = "sub";
private static final String NODE_font = "font";
private static final String NODE_br = "br";
private static final String NODE_li = "li";
private static final String ATTRIBUTE_fontName = "fontName";
private static final String ATTRIBUTE_fontFace = "face";
private static final String ATTRIBUTE_color = "color";
private static final String ATTRIBUTE_size = "size";
private static final String ATTRIBUTE_isBold = "isBold";
private static final String ATTRIBUTE_isItalic = "isItalic";
private static final String ATTRIBUTE_isUnderline = "isUnderline";
private static final String ATTRIBUTE_isStrikeThrough = "isStrikeThrough";
private static final String ATTRIBUTE_forecolor = "forecolor";
private static final String ATTRIBUTE_backcolor = "backcolor";
private static final String ATTRIBUTE_pdfFontName = "pdfFontName";
private static final String ATTRIBUTE_pdfEncoding = "pdfEncoding";
private static final String ATTRIBUTE_isPdfEmbedded = "isPdfEmbedded";
private static final String SPACE = " ";
private static final String EQUAL_QUOTE = "=\"";
private static final String QUOTE = "\"";
private static final String SHARP = "#";
private static final String LESS = "<";
private static final String LESS_SLASH = "";
private static final String GREATER = ">";
private static final ThreadLocal threadInstances = new ThreadLocal();
public static JRStyledTextParser getInstance() {
JRStyledTextParser instance = null;
SoftReference instanceRef = threadInstances.get();
if (instanceRef != null)
instance = instanceRef.get();
if (instance == null) {
instance = new JRStyledTextParser();
threadInstances.set(new SoftReference(instance));
}
return instance;
}
private DocumentBuilder documentBuilder = null;
public JRStyledTextParser() {
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
this.documentBuilder = factory.newDocumentBuilder();
this.documentBuilder.setErrorHandler(this);
} catch (ParserConfigurationException e) {
throw new JRRuntimeException(e);
}
}
public JRStyledText parse(Map attributes, String text) throws SAXException {
JRStyledText styledText = new JRStyledText();
Document document = null;
try {
document = this.documentBuilder.parse(new InputSource(new StringReader("" + text + "")));
} catch (IOException e) {
throw new JRRuntimeException(e);
}
parseStyle(styledText, document.getDocumentElement());
styledText.setGlobalAttributes(attributes);
return styledText;
}
public JRStyledText getStyledText(Map parentAttributes, String text, boolean isStyledText) {
JRStyledText styledText = null;
if (isStyledText)
try {
styledText = parse(parentAttributes, text);
} catch (SAXException e) {}
if (styledText == null) {
styledText = new JRStyledText();
styledText.append(text);
styledText.setGlobalAttributes(parentAttributes);
}
return styledText;
}
public String write(JRStyledText styledText) {
return write(styledText.getGlobalAttributes(), styledText.getAttributedString().getIterator(), styledText.getText());
}
public String write(Map parentAttrs, AttributedCharacterIterator iterator, String text) {
StringBuffer sbuffer = new StringBuffer();
int runLimit = 0;
while (runLimit < iterator.getEndIndex() && (runLimit = iterator.getRunLimit()) <= iterator.getEndIndex()) {
String chunk = text.substring(iterator.getIndex(), runLimit);
Map attrs = iterator.getAttributes();
StringBuffer styleBuffer = writeStyleAttributes(parentAttrs, attrs);
if (styleBuffer.length() > 0) {
sbuffer.append("<");
sbuffer.append("style");
sbuffer.append(styleBuffer.toString());
sbuffer.append(">");
writeChunk(sbuffer, parentAttrs, attrs, chunk);
sbuffer.append("");
sbuffer.append("style");
sbuffer.append(">");
} else {
writeChunk(sbuffer, parentAttrs, attrs, chunk);
}
iterator.setIndex(runLimit);
}
return sbuffer.toString();
}
public String write(JRStyledText styledText, int startIndex, int endIndex) {
AttributedCharacterIterator subIterator = (new AttributedString(styledText.getAttributedString().getIterator(), startIndex, endIndex)).getIterator();
String subText = styledText.getText().substring(startIndex, endIndex);
return write(styledText.getGlobalAttributes(), subIterator, subText);
}
public void writeChunk(StringBuffer sbuffer, Map parentAttrs, Map attrs, String chunk) {
Object value = attrs.get(TextAttribute.SUPERSCRIPT);
Object oldValue = parentAttrs.get(TextAttribute.SUPERSCRIPT);
boolean isSuper = false;
boolean isSub = false;
if (value != null && !value.equals(oldValue)) {
isSuper = TextAttribute.SUPERSCRIPT_SUPER.equals(value);
isSub = TextAttribute.SUPERSCRIPT_SUB.equals(value);
}
if (isSuper || isSub) {
String node = isSuper ? "sup" : "sub";
sbuffer.append("<");
sbuffer.append(node);
sbuffer.append(">");
sbuffer.append(JRStringUtil.xmlEncode(chunk));
sbuffer.append("");
sbuffer.append(node);
sbuffer.append(">");
} else {
sbuffer.append(JRStringUtil.xmlEncode(chunk));
}
}
private void parseStyle(JRStyledText styledText, Node parentNode) throws SAXException {
NodeList nodeList = parentNode.getChildNodes();
int i;
label116: for (i = 0; i < nodeList.getLength(); i++) {
Node node = nodeList.item(i);
if (node.getNodeType() == 3) {
styledText.append(node.getNodeValue());
} else if (node.getNodeType() == 1 && "style".equals(node.getNodeName())) {
NamedNodeMap nodeAttrs = node.getAttributes();
Map styleAttrs = new HashMap();
if (nodeAttrs.getNamedItem("fontName") != null)
styleAttrs.put(TextAttribute.FAMILY, nodeAttrs.getNamedItem("fontName").getNodeValue());
if (nodeAttrs.getNamedItem("isBold") != null)
styleAttrs.put(TextAttribute.WEIGHT, Boolean.valueOf(nodeAttrs.getNamedItem("isBold").getNodeValue()).booleanValue() ? TextAttribute.WEIGHT_BOLD : TextAttribute.WEIGHT_REGULAR);
if (nodeAttrs.getNamedItem("isItalic") != null)
styleAttrs.put(TextAttribute.POSTURE, Boolean.valueOf(nodeAttrs.getNamedItem("isItalic").getNodeValue()).booleanValue() ? TextAttribute.POSTURE_OBLIQUE : TextAttribute.POSTURE_REGULAR);
if (nodeAttrs.getNamedItem("isUnderline") != null)
styleAttrs.put(TextAttribute.UNDERLINE, Boolean.valueOf(nodeAttrs.getNamedItem("isUnderline").getNodeValue()).booleanValue() ? TextAttribute.UNDERLINE_ON : null);
if (nodeAttrs.getNamedItem("isStrikeThrough") != null)
styleAttrs.put(TextAttribute.STRIKETHROUGH, Boolean.valueOf(nodeAttrs.getNamedItem("isStrikeThrough").getNodeValue()).booleanValue() ? TextAttribute.STRIKETHROUGH_ON : null);
if (nodeAttrs.getNamedItem("size") != null)
styleAttrs.put(TextAttribute.SIZE, new Float(nodeAttrs.getNamedItem("size").getNodeValue()));
if (nodeAttrs.getNamedItem("pdfFontName") != null)
styleAttrs.put(JRTextAttribute.PDF_FONT_NAME, nodeAttrs.getNamedItem("pdfFontName").getNodeValue());
if (nodeAttrs.getNamedItem("pdfEncoding") != null)
styleAttrs.put(JRTextAttribute.PDF_ENCODING, nodeAttrs.getNamedItem("pdfEncoding").getNodeValue());
if (nodeAttrs.getNamedItem("isPdfEmbedded") != null)
styleAttrs.put(JRTextAttribute.IS_PDF_EMBEDDED, Boolean.valueOf(nodeAttrs.getNamedItem("isPdfEmbedded").getNodeValue()));
if (nodeAttrs.getNamedItem("forecolor") != null) {
Color color = JRXmlConstants.getColor(nodeAttrs.getNamedItem("forecolor").getNodeValue(), Color.black);
styleAttrs.put(TextAttribute.FOREGROUND, color);
}
if (nodeAttrs.getNamedItem("backcolor") != null) {
Color color = JRXmlConstants.getColor(nodeAttrs.getNamedItem("backcolor").getNodeValue(), Color.black);
styleAttrs.put(TextAttribute.BACKGROUND, color);
}
int startIndex = styledText.length();
parseStyle(styledText, node);
styledText.addRun(new JRStyledText.Run(styleAttrs, startIndex, styledText.length()));
} else if (node.getNodeType() == 1 && "b".equalsIgnoreCase(node.getNodeName())) {
Map styleAttrs = new HashMap();
styleAttrs.put(TextAttribute.WEIGHT, TextAttribute.WEIGHT_BOLD);
int startIndex = styledText.length();
parseStyle(styledText, node);
styledText.addRun(new JRStyledText.Run(styleAttrs, startIndex, styledText.length()));
} else if (node.getNodeType() == 1 && "i".equalsIgnoreCase(node.getNodeName())) {
Map styleAttrs = new HashMap();
styleAttrs.put(TextAttribute.POSTURE, TextAttribute.POSTURE_OBLIQUE);
int startIndex = styledText.length();
parseStyle(styledText, node);
styledText.addRun(new JRStyledText.Run(styleAttrs, startIndex, styledText.length()));
} else if (node.getNodeType() == 1 && "u".equalsIgnoreCase(node.getNodeName())) {
Map styleAttrs = new HashMap();
styleAttrs.put(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON);
int startIndex = styledText.length();
parseStyle(styledText, node);
styledText.addRun(new JRStyledText.Run(styleAttrs, startIndex, styledText.length()));
} else if (node.getNodeType() == 1 && "sup".equalsIgnoreCase(node.getNodeName())) {
Map styleAttrs = new HashMap();
styleAttrs.put(TextAttribute.SUPERSCRIPT, TextAttribute.SUPERSCRIPT_SUPER);
int startIndex = styledText.length();
parseStyle(styledText, node);
styledText.addRun(new JRStyledText.Run(styleAttrs, startIndex, styledText.length()));
} else if (node.getNodeType() == 1 && "sub".equalsIgnoreCase(node.getNodeName())) {
Map styleAttrs = new HashMap();
styleAttrs.put(TextAttribute.SUPERSCRIPT, TextAttribute.SUPERSCRIPT_SUB);
int startIndex = styledText.length();
parseStyle(styledText, node);
styledText.addRun(new JRStyledText.Run(styleAttrs, startIndex, styledText.length()));
} else if (node.getNodeType() == 1 && "font".equalsIgnoreCase(node.getNodeName())) {
NamedNodeMap nodeAttrs = node.getAttributes();
Map styleAttrs = new HashMap();
if (nodeAttrs.getNamedItem("face") != null)
styleAttrs.put(JRTextAttribute.HTML_FONT_FACE, nodeAttrs.getNamedItem("face").getNodeValue());
if (nodeAttrs.getNamedItem("size") != null)
styleAttrs.put(TextAttribute.SIZE, new Float(nodeAttrs.getNamedItem("size").getNodeValue()));
if (nodeAttrs.getNamedItem("color") != null) {
Color color = JRXmlConstants.getColor(nodeAttrs.getNamedItem("color").getNodeValue(), Color.black);
styleAttrs.put(TextAttribute.FOREGROUND, color);
}
if (nodeAttrs.getNamedItem("face") != null) {
String[] fontList = GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames();
String fontFaces = nodeAttrs.getNamedItem("face").getNodeValue();
StringTokenizer t = new StringTokenizer(fontFaces, ",");
while (t.hasMoreTokens()) {
String face = t.nextToken().trim();
for (int j = 0; j < fontList.length; j++) {
if (fontList[j].equals(face)) {
styleAttrs.put(TextAttribute.FAMILY, face);
continue label116;
}
}
}
}
int startIndex = styledText.length();
parseStyle(styledText, node);
styledText.addRun(new JRStyledText.Run(styleAttrs, startIndex, styledText.length()));
} else if (node.getNodeType() == 1 && "br".equalsIgnoreCase(node.getNodeName())) {
styledText.append("\n");
int startIndex = styledText.length();
resizeRuns(styledText.getRuns(), startIndex, 1);
parseStyle(styledText, node);
styledText.addRun(new JRStyledText.Run(new HashMap(), startIndex, styledText.length()));
if (startIndex < styledText.length()) {
styledText.append("\n");
resizeRuns(styledText.getRuns(), startIndex, 1);
}
} else if (node.getNodeType() == 1 && "li".equalsIgnoreCase(node.getNodeName())) {
String tmpText = styledText.getText();
if (tmpText.length() > 0 && !tmpText.endsWith("\n"))
styledText.append("\n");
styledText.append(" • ");
int startIndex = styledText.length();
resizeRuns(styledText.getRuns(), startIndex, 1);
parseStyle(styledText, node);
styledText.addRun(new JRStyledText.Run(new HashMap(), startIndex, styledText.length()));
Node nextNode = node.getNextSibling();
String textContent = getFirstTextOccurence(nextNode);
if (nextNode != null && (nextNode.getNodeType() != 1 || !"li".equalsIgnoreCase(nextNode.getNodeName())) && (textContent == null || !textContent.startsWith("\n"))) {
styledText.append("\n");
resizeRuns(styledText.getRuns(), startIndex, 1);
}
} else if (node.getNodeType() == 1) {
String nodeName = "<" + node.getNodeName() + ">";
throw new SAXException("Tag " + nodeName + " is not a valid styled text tag.");
}
}
}
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;
}
}
private StringBuffer writeStyleAttributes(Map parentAttrs, Map attrs) {
StringBuffer sbuffer = new StringBuffer();
Object value = attrs.get(TextAttribute.FAMILY);
Object oldValue = parentAttrs.get(TextAttribute.FAMILY);
if (value != null && !value.equals(oldValue)) {
sbuffer.append(" ");
sbuffer.append("fontName");
sbuffer.append("=\"");
sbuffer.append(value);
sbuffer.append("\"");
}
value = attrs.get(TextAttribute.WEIGHT);
oldValue = parentAttrs.get(TextAttribute.WEIGHT);
if (value != null && !value.equals(oldValue)) {
sbuffer.append(" ");
sbuffer.append("isBold");
sbuffer.append("=\"");
sbuffer.append(value.equals(TextAttribute.WEIGHT_BOLD));
sbuffer.append("\"");
}
value = attrs.get(TextAttribute.POSTURE);
oldValue = parentAttrs.get(TextAttribute.POSTURE);
if (value != null && !value.equals(oldValue)) {
sbuffer.append(" ");
sbuffer.append("isItalic");
sbuffer.append("=\"");
sbuffer.append(value.equals(TextAttribute.POSTURE_OBLIQUE));
sbuffer.append("\"");
}
value = attrs.get(TextAttribute.UNDERLINE);
oldValue = parentAttrs.get(TextAttribute.UNDERLINE);
if ((value == null && oldValue != null) || (value != null && !value.equals(oldValue))) {
sbuffer.append(" ");
sbuffer.append("isUnderline");
sbuffer.append("=\"");
sbuffer.append((value != null));
sbuffer.append("\"");
}
value = attrs.get(TextAttribute.STRIKETHROUGH);
oldValue = parentAttrs.get(TextAttribute.STRIKETHROUGH);
if ((value == null && oldValue != null) || (value != null && !value.equals(oldValue))) {
sbuffer.append(" ");
sbuffer.append("isStrikeThrough");
sbuffer.append("=\"");
sbuffer.append((value != null));
sbuffer.append("\"");
}
value = attrs.get(TextAttribute.SIZE);
oldValue = parentAttrs.get(TextAttribute.SIZE);
if (value != null && !value.equals(oldValue)) {
sbuffer.append(" ");
sbuffer.append("size");
sbuffer.append("=\"");
sbuffer.append(((Float)value).intValue());
sbuffer.append("\"");
}
value = attrs.get(JRTextAttribute.PDF_FONT_NAME);
oldValue = parentAttrs.get(JRTextAttribute.PDF_FONT_NAME);
if (value != null && !value.equals(oldValue)) {
sbuffer.append(" ");
sbuffer.append("pdfFontName");
sbuffer.append("=\"");
sbuffer.append(value);
sbuffer.append("\"");
}
value = attrs.get(JRTextAttribute.PDF_ENCODING);
oldValue = parentAttrs.get(JRTextAttribute.PDF_ENCODING);
if (value != null && !value.equals(oldValue)) {
sbuffer.append(" ");
sbuffer.append("pdfEncoding");
sbuffer.append("=\"");
sbuffer.append(value);
sbuffer.append("\"");
}
value = attrs.get(JRTextAttribute.IS_PDF_EMBEDDED);
oldValue = parentAttrs.get(JRTextAttribute.IS_PDF_EMBEDDED);
if (value != null && !value.equals(oldValue)) {
sbuffer.append(" ");
sbuffer.append("isPdfEmbedded");
sbuffer.append("=\"");
sbuffer.append(value);
sbuffer.append("\"");
}
value = attrs.get(TextAttribute.FOREGROUND);
oldValue = parentAttrs.get(TextAttribute.FOREGROUND);
if (value != null && !value.equals(oldValue)) {
sbuffer.append(" ");
sbuffer.append("forecolor");
sbuffer.append("=\"");
sbuffer.append("#");
sbuffer.append(JRColorUtil.getColorHexa((Color)value));
sbuffer.append("\"");
}
value = attrs.get(TextAttribute.BACKGROUND);
oldValue = parentAttrs.get(TextAttribute.BACKGROUND);
if (value != null && !value.equals(oldValue)) {
sbuffer.append(" ");
sbuffer.append("backcolor");
sbuffer.append("=\"");
sbuffer.append("#");
sbuffer.append(JRColorUtil.getColorHexa((Color)value));
sbuffer.append("\"");
}
return sbuffer;
}
private String getFirstTextOccurence(Node node) {
if (node != null) {
if (node.getNodeValue() != null)
return node.getNodeValue();
NodeList nodeList = node.getChildNodes();
for (int i = 0; i < nodeList.getLength(); i++) {
String firstOccurence = getFirstTextOccurence(nodeList.item(i));
if (firstOccurence != null)
return firstOccurence;
}
}
return null;
}
public void error(SAXParseException e) {}
public void fatalError(SAXParseException e) {}
public void warning(SAXParseException e) {}
}