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 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(""); } 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(""); } 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) {} }