1 /*
2  * JasperReports - Free Java Reporting Library.
3  * Copyright (C) 2001 - 2019 TIBCO Software Inc. All rights reserved.
4  * http://www.jaspersoft.com
5  *
6  * Unless you have purchased a commercial license agreement from Jaspersoft,
7  * the following license terms apply:
8  *
9  * This program is part of JasperReports.
10  *
11  * JasperReports is free software: you can redistribute it and/or modify
12  * it under the terms of the GNU Lesser General Public License as published by
13  * the Free Software Foundation, either version 3 of the License, or
14  * (at your option) any later version.
15  *
16  * JasperReports is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19  * GNU Lesser General Public License for more details.
20  *
21  * You should have received a copy of the GNU Lesser General Public License
22  * along with JasperReports. If not, see <http://www.gnu.org/licenses/>.
23  */

24 package net.sf.jasperreports.engine.util;
25
26 import java.awt.Color;
27 import java.awt.GraphicsEnvironment;
28 import java.awt.font.TextAttribute;
29 import java.io.IOException;
30 import java.io.StringReader;
31 import java.lang.ref.SoftReference;
32 import java.text.AttributedCharacterIterator;
33 import java.text.AttributedCharacterIterator.Attribute;
34 import java.text.AttributedString;
35 import java.util.Arrays;
36 import java.util.HashMap;
37 import java.util.HashSet;
38 import java.util.Iterator;
39 import java.util.List;
40 import java.util.Locale;
41 import java.util.Map;
42 import java.util.Set;
43 import java.util.StringTokenizer;
44
45 import javax.xml.parsers.DocumentBuilder;
46 import javax.xml.parsers.DocumentBuilderFactory;
47 import javax.xml.parsers.ParserConfigurationException;
48
49 import org.apache.commons.logging.Log;
50 import org.apache.commons.logging.LogFactory;
51 import org.w3c.dom.Document;
52 import org.w3c.dom.NamedNodeMap;
53 import org.w3c.dom.Node;
54 import org.w3c.dom.NodeList;
55 import org.xml.sax.ErrorHandler;
56 import org.xml.sax.InputSource;
57 import org.xml.sax.SAXException;
58 import org.xml.sax.SAXParseException;
59
60 import net.sf.jasperreports.engine.JRPrintHyperlink;
61 import net.sf.jasperreports.engine.JRPrintHyperlinkParameter;
62 import net.sf.jasperreports.engine.JRPrintHyperlinkParameters;
63 import net.sf.jasperreports.engine.JRRuntimeException;
64 import net.sf.jasperreports.engine.base.JRBasePrintHyperlink;
65 import net.sf.jasperreports.engine.fonts.FontFamily;
66 import net.sf.jasperreports.engine.type.HyperlinkTypeEnum;
67 import net.sf.jasperreports.engine.util.JRStyledText.Run;
68 import net.sf.jasperreports.extensions.ExtensionsEnvironment;
69
70
71 /**
72  * @author Teodor Danciu (teodord@users.sourceforge.net)
73  */

74 public class JRStyledTextParser implements ErrorHandler
75 {
76     private static final Log log = LogFactory.getLog(JRStyledTextParser.class);
77
78     private static final Set<String> AVAILABLE_FONT_FACE_NAMES = new HashSet<String>();
79     static
80     {
81         //FIXME doing this in a static block obscures exceptions, move it to some other place
82         try
83         {
84             //FIXMEFONT do some cache
85             //FIXME these should be taken from the current JasperReportsContext
86             List<FontFamily> families = ExtensionsEnvironment.getExtensionsRegistry().getExtensions(FontFamily.class);
87             for (Iterator<FontFamily> itf = families.iterator(); itf.hasNext();)
88             {
89                 FontFamily family =itf.next();
90                 AVAILABLE_FONT_FACE_NAMES.add(family.getName());
91             }
92             
93             //FIXME use JRGraphEnvInitializer
94             AVAILABLE_FONT_FACE_NAMES.addAll(
95                 Arrays.asList(GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames())
96                 );
97         }
98         catch (Exception e)
99         {
100             log.error("Error while loading available fonts", e);
101             throw e;
102         }
103     }
104
105     /**
106      *
107      */

108     private static final String ROOT_START = "<st>";
109     private static final String ROOT_END = "</st>";
110     private static final String NODE_style = "style";
111     private static final String NODE_bold = "b";
112     private static final String NODE_italic = "i";
113     private static final String NODE_underline = "u";
114     private static final String NODE_sup = "sup";
115     private static final String NODE_sub = "sub";
116     private static final String NODE_font = "font";
117     private static final String NODE_br = "br";
118     private static final String NODE_li = "li";
119     private static final String NODE_a = "a";
120     private static final String NODE_param = "param";
121     private static final String ATTRIBUTE_fontName = "fontName";
122     private static final String ATTRIBUTE_fontFace = "face";
123     private static final String ATTRIBUTE_color = "color";
124     private static final String ATTRIBUTE_size = "size";
125     private static final String ATTRIBUTE_isBold = "isBold";
126     private static final String ATTRIBUTE_isItalic = "isItalic";
127     private static final String ATTRIBUTE_isUnderline = "isUnderline";
128     private static final String ATTRIBUTE_isStrikeThrough = "isStrikeThrough";
129     private static final String ATTRIBUTE_forecolor = "forecolor";
130     private static final String ATTRIBUTE_backcolor = "backcolor";
131     private static final String ATTRIBUTE_pdfFontName = "pdfFontName";
132     private static final String ATTRIBUTE_pdfEncoding = "pdfEncoding";
133     private static final String ATTRIBUTE_isPdfEmbedded = "isPdfEmbedded";
134     private static final String ATTRIBUTE_type = "type";
135     private static final String ATTRIBUTE_href = "href";
136     private static final String ATTRIBUTE_target = "target";
137     private static final String ATTRIBUTE_name = "name";
138     private static final String ATTRIBUTE_valueClass = "valueClass";
139
140     private static final String SPACE = " ";
141     private static final String EQUAL_QUOTE = "=\"";
142     private static final String QUOTE = "\"";
143     private static final String LESS = "<";
144     private static final String LESS_SLASH = "</";
145     private static final String GREATER = ">";
146     
147     /**
148      * Thread local soft cache of instances.
149      */

150     private static final ThreadLocal<SoftReference<JRStyledTextParser>> threadInstances = new ThreadLocal<SoftReference<JRStyledTextParser>>();
151     
152     /**
153      * 
154      */

155     private static final ThreadLocal<Locale> threadLocale = new ThreadLocal<Locale>();
156     
157     /**
158      * Return a cached instance.
159      * 
160      * @return a cached instance
161      */

162     public static JRStyledTextParser getInstance()
163     {
164         JRStyledTextParser instance = null;
165         SoftReference<JRStyledTextParser> instanceRef = threadInstances.get();
166         if (instanceRef != null)
167         {
168             instance =  instanceRef.get();
169         }
170         if (instance == null)
171         {
172             instance = new JRStyledTextParser();
173             threadInstances.set(new SoftReference<JRStyledTextParser>(instance));
174         }
175         return instance;
176     }
177     
178
179     /**
180      * 
181      */

182     public static void setLocale(Locale locale)
183     {
184         threadLocale.set(locale);
185     }
186     
187     /**
188      * 
189      */

190     public static Locale getLocale()
191     {
192         return threadLocale.get();
193     }
194     
195     /**
196      *
197      */

198     private DocumentBuilder documentBuilder;
199     
200     /**
201      *
202      */

203     private JRBasePrintHyperlink hyperlink;
204
205
206     /**
207      *
208      */

209     private JRStyledTextParser()
210     {
211         try
212         {
213             DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
214             factory.setFeature(JRXmlUtils.FEATURE_DISALLOW_DOCTYPE, true);
215             
216             documentBuilder = factory.newDocumentBuilder();
217             documentBuilder.setErrorHandler(this);
218         }
219         catch (ParserConfigurationException e)
220         {
221             throw new JRRuntimeException(e);
222         }
223     }
224
225
226     /**
227      *
228      */

229     public JRStyledText parse(Map<Attribute,Object> attributes, String text, Locale locale) throws SAXException
230     {
231         JRStyledText styledText = new JRStyledText(locale);
232         
233         Document document = null;
234
235         try
236         {
237             document = documentBuilder.parse(new InputSource(new StringReader(ROOT_START + text + ROOT_END)));
238         }
239         catch (IOException e)
240         {
241             throw new JRRuntimeException(e);
242         }
243         
244         hyperlink = null;
245         
246         parseStyle(styledText, document.getDocumentElement());
247         
248         styledText.setGlobalAttributes(attributes);
249         
250         return styledText;
251     }
252
253     /**
254      * Creates a styled text object by either parsing a styled text String or
255      * by wrapping an unstyled String.
256      * 
257      * @param parentAttributes the element-level styled text attributes
258      * @param text the (either styled or unstyled) text
259      * @param isStyledText flag indicating that the text is styled
260      * @param locale the locale for the text
261      * @return a styled text object
262      */

263     public JRStyledText getStyledText(Map<Attribute,Object> parentAttributes, String text, boolean isStyledText, Locale locale)
264     {
265         JRStyledText styledText = null;
266         if (isStyledText)
267         {
268             try
269             {
270                 styledText = parse(parentAttributes, text, locale);
271             }
272             catch (SAXException e)
273             {
274                 //ignore if invalid styled text and treat like normal text
275             }
276         }
277     
278         if (styledText == null)
279         {
280             // using the original String object instead without creating a buffer and a String copy
281             styledText = new JRStyledText(locale, text, parentAttributes);
282         }
283         
284         return styledText;
285     }
286     
287     /**
288      * Outputs a styled text String given a styled text instance.
289      * 
290      * @param styledText the styled text object
291      * @return the String styled text representation
292      */

293     public String write(JRStyledText styledText)
294     {
295         return write(styledText.getGlobalAttributes(), 
296                 styledText.getAttributedString().getIterator(), 
297                 styledText.getText());
298     }
299     
300     /**
301      * Outputs a styled text String given a set of element-level styled text
302      * attributes and a styled text in the form of a String text and an iterator
303      * of style attributes.
304      * 
305      * @param parentAttrs the element-level styled text attributes
306      * @param iterator iterator of styled text attributes
307      * @param text the text
308      * @return the String styled text representation
309      */

310     public String write(Map<Attribute,Object> parentAttrs, AttributedCharacterIterator iterator, String text)
311     {
312         StringBuilder sb = new StringBuilder();
313         
314         int runLimit = 0;
315
316         while(runLimit < iterator.getEndIndex() && (runLimit = iterator.getRunLimit()) <= iterator.getEndIndex())
317         {
318             String chunk = text.substring(iterator.getIndex(), runLimit);
319             Map<Attribute,Object> attrs = iterator.getAttributes();
320             
321             StringBuilder styleBuilder = writeStyleAttributes(parentAttrs, attrs);
322             if (styleBuilder.length() > 0)
323             {
324                 sb.append(LESS);
325                 sb.append(NODE_style);
326                 sb.append(styleBuilder.toString());
327                 sb.append(GREATER);
328                 writeChunk(sb, parentAttrs, attrs, chunk);
329                 sb.append(LESS_SLASH);
330                 sb.append(NODE_style);
331                 sb.append(GREATER);
332             }
333             else
334             {
335                 writeChunk(sb, parentAttrs, attrs, chunk);
336             }
337
338             iterator.setIndex(runLimit);
339         }
340         
341         return sb.toString();
342     }
343
344     /**
345      * Outputs the String representation of a styled text chunk.
346      * 
347      * @param styledText the styled text
348      * @param startIndex the start index
349      * @param endIndex the end index
350      * @return the String styled text representation of the chunk delimited by
351      * the start index and the end index
352      * @see #write(Map, AttributedCharacterIterator, String)
353      */

354     public String write(JRStyledText styledText, 
355             int startIndex, int endIndex)
356     {
357         AttributedCharacterIterator subIterator = new AttributedString(
358                 styledText.getAttributedString().getIterator(), 
359                 startIndex, endIndex).getIterator();
360         String subText = styledText.getText().substring(startIndex, endIndex);
361         return write(styledText.getGlobalAttributes(), subIterator, subText);
362     }
363
364     /**
365      *
366      */

367     public void writeChunk(StringBuilder sb, Map<Attribute,Object> parentAttrs, Map<Attribute,Object> attrs, String chunk)
368     {
369         Object value = attrs.get(TextAttribute.SUPERSCRIPT);
370         Object oldValue = parentAttrs.get(TextAttribute.SUPERSCRIPT);
371
372         boolean isSuper = false;
373         boolean isSub = false;
374         
375         if (value != null && !value.equals(oldValue))
376         {
377             isSuper=TextAttribute.SUPERSCRIPT_SUPER.equals(value);
378             isSub=TextAttribute.SUPERSCRIPT_SUB.equals(value);
379         }
380
381         String scriptNode = isSuper?NODE_sup:NODE_sub;
382
383         if (isSuper || isSub)
384         {
385             sb.append(LESS);
386             sb.append(scriptNode);
387             sb.append(GREATER);
388         }
389
390         JRPrintHyperlink hlink = (JRPrintHyperlink)attrs.get(JRTextAttribute.HYPERLINK);
391         if (hlink != null)
392         {
393             sb.append(LESS);
394             sb.append(NODE_a);
395
396             String href = hlink.getHyperlinkReference();
397             if (href != null && href.trim().length() > 0)
398             {
399                 sb.append(SPACE);
400                 sb.append(ATTRIBUTE_href);
401                 sb.append(EQUAL_QUOTE);
402                 sb.append(JRStringUtil.htmlEncode(href));
403                 sb.append(QUOTE);
404             }
405             
406             String type = hlink.getLinkType();
407             if (type != null && type.trim().length() > 0)
408             {
409                 sb.append(SPACE);
410                 sb.append(ATTRIBUTE_type);
411                 sb.append(EQUAL_QUOTE);
412                 sb.append(type);
413                 sb.append(QUOTE);
414             }
415             
416             String target = hlink.getLinkTarget();
417             if (target != null && target.trim().length() > 0)
418             {
419                 sb.append(SPACE);
420                 sb.append(ATTRIBUTE_target);
421                 sb.append(EQUAL_QUOTE);
422                 sb.append(target);
423                 sb.append(QUOTE);
424             }
425             
426             sb.append(GREATER);
427             
428             JRPrintHyperlinkParameters parameters = hlink.getHyperlinkParameters();
429             if (parameters != null && parameters.getParameters() != null)
430             {
431                 for (JRPrintHyperlinkParameter parameter : parameters.getParameters())
432                 {
433                     sb.append(LESS);
434                     sb.append(NODE_param);
435                     sb.append(SPACE);
436                     sb.append(ATTRIBUTE_name);
437                     sb.append(EQUAL_QUOTE);
438                     sb.append(parameter.getName());
439                     sb.append(QUOTE);
440                     sb.append(GREATER);
441                     
442                     if (parameter.getValue() != null)
443                     {
444                         String strValue = JRValueStringUtils.serialize(parameter.getValueClass(), parameter.getValue());
445                         sb.append(JRStringUtil.xmlEncode(strValue));
446                     }
447
448                     sb.append(LESS_SLASH);
449                     sb.append(NODE_param);
450                     sb.append(GREATER);
451                 }
452             }
453         }
454
455         sb.append(JRStringUtil.xmlEncode(chunk));
456
457         if (hlink != null)
458         {
459             sb.append(LESS_SLASH);
460             sb.append(NODE_a);
461             sb.append(GREATER);
462         }
463
464         if (isSuper || isSub)
465         {
466             sb.append(LESS_SLASH);
467             sb.append(scriptNode);
468             sb.append(GREATER);
469         }
470     }
471
472     /**
473      *
474      */

475     private void parseStyle(JRStyledText styledText, Node parentNode) throws SAXException
476     {
477         NodeList nodeList = parentNode.getChildNodes();
478         for(int i = 0; i < nodeList.getLength(); i++)
479         {
480             Node node = nodeList.item(i);
481             if (node.getNodeType() == Node.TEXT_NODE)
482             {
483                 styledText.append(node.getNodeValue());
484             }
485             else if (
486                 node.getNodeType() == Node.ELEMENT_NODE
487                 && NODE_style.equals(node.getNodeName())
488                 )
489             {
490                 NamedNodeMap nodeAttrs = node.getAttributes();
491
492                 Map<Attribute,Object> styleAttrs = new HashMap<Attribute,Object>();
493
494                 if (nodeAttrs.getNamedItem(ATTRIBUTE_fontName) != null)
495                 {
496                     styleAttrs.put(
497                         TextAttribute.FAMILY,
498                         nodeAttrs.getNamedItem(ATTRIBUTE_fontName).getNodeValue()
499                         );
500                 }
501
502                 if (nodeAttrs.getNamedItem(ATTRIBUTE_isBold) != null)
503                 {
504                     styleAttrs.put(
505                         TextAttribute.WEIGHT,
506                         Boolean.valueOf(nodeAttrs.getNamedItem(ATTRIBUTE_isBold).getNodeValue())
507                         ? TextAttribute.WEIGHT_BOLD : TextAttribute.WEIGHT_REGULAR
508                         );
509                 }
510
511                 if (nodeAttrs.getNamedItem(ATTRIBUTE_isItalic) != null)
512                 {
513                     styleAttrs.put(
514                         TextAttribute.POSTURE,
515                         Boolean.valueOf(nodeAttrs.getNamedItem(ATTRIBUTE_isItalic).getNodeValue())
516                         ? TextAttribute.POSTURE_OBLIQUE : TextAttribute.POSTURE_REGULAR
517                         );
518                 }
519
520                 if (nodeAttrs.getNamedItem(ATTRIBUTE_isUnderline) != null)
521                 {
522                     styleAttrs.put(
523                         TextAttribute.UNDERLINE,
524                         Boolean.valueOf(nodeAttrs.getNamedItem(ATTRIBUTE_isUnderline).getNodeValue())
525                         ? TextAttribute.UNDERLINE_ON : null
526                         );
527                 }
528
529                 if (nodeAttrs.getNamedItem(ATTRIBUTE_isStrikeThrough) != null)
530                 {
531                     styleAttrs.put(
532                         TextAttribute.STRIKETHROUGH,
533                         Boolean.valueOf(nodeAttrs.getNamedItem(ATTRIBUTE_isStrikeThrough).getNodeValue())
534                         ? TextAttribute.STRIKETHROUGH_ON : null
535                         );
536                 }
537
538                 if (nodeAttrs.getNamedItem(ATTRIBUTE_size) != null)
539                 {
540                     styleAttrs.put(
541                         TextAttribute.SIZE,
542                         Float.valueOf(nodeAttrs.getNamedItem(ATTRIBUTE_size).getNodeValue())
543                         );
544                 }
545
546                 if (nodeAttrs.getNamedItem(ATTRIBUTE_pdfFontName) != null)
547                 {
548                     styleAttrs.put(
549                         JRTextAttribute.PDF_FONT_NAME,
550                         nodeAttrs.getNamedItem(ATTRIBUTE_pdfFontName).getNodeValue()
551                         );
552                 }
553
554                 if (nodeAttrs.getNamedItem(ATTRIBUTE_pdfEncoding) != null)
555                 {
556                     styleAttrs.put(
557                         JRTextAttribute.PDF_ENCODING,
558                         nodeAttrs.getNamedItem(ATTRIBUTE_pdfEncoding).getNodeValue()
559                         );
560                 }
561
562                 if (nodeAttrs.getNamedItem(ATTRIBUTE_isPdfEmbedded) != null)
563                 {
564                     styleAttrs.put(
565                         JRTextAttribute.IS_PDF_EMBEDDED,
566                         Boolean.valueOf(nodeAttrs.getNamedItem(ATTRIBUTE_isPdfEmbedded).getNodeValue())
567                         );
568                 }
569
570                 if (nodeAttrs.getNamedItem(ATTRIBUTE_forecolor) != null)
571                 {
572                     Color color = 
573                         JRColorUtil.getColor(
574                             nodeAttrs.getNamedItem(ATTRIBUTE_forecolor).getNodeValue(),
575                             Color.black
576                             );
577                     styleAttrs.put(
578                         TextAttribute.FOREGROUND,
579                         color
580                         );
581                 }
582
583                 if (nodeAttrs.getNamedItem(ATTRIBUTE_backcolor) != null)
584                 {
585                     Color color = 
586                         JRColorUtil.getColor(
587                             nodeAttrs.getNamedItem(ATTRIBUTE_backcolor).getNodeValue(),
588                             Color.black
589                             );
590                     styleAttrs.put(
591                         TextAttribute.BACKGROUND,
592                         color
593                         );
594                 }
595
596                 int startIndex = styledText.length();
597
598                 parseStyle(styledText, node);
599
600                 styledText.addRun(new JRStyledText.Run(styleAttrs, startIndex, styledText.length()));
601             }
602             else if (node.getNodeType() == Node.ELEMENT_NODE && NODE_bold.equalsIgnoreCase(node.getNodeName()))
603             {
604                 Map<Attribute,Object> styleAttrs = new HashMap<Attribute,Object>();
605                 styleAttrs.put(TextAttribute.WEIGHT, TextAttribute.WEIGHT_BOLD);
606
607                 int startIndex = styledText.length();
608
609                 parseStyle(styledText, node);
610
611                 styledText.addRun(new JRStyledText.Run(styleAttrs, startIndex, styledText.length()));
612             }
613             else if (node.getNodeType() == Node.ELEMENT_NODE && NODE_italic.equalsIgnoreCase(node.getNodeName()))
614             {
615                 Map<Attribute,Object> styleAttrs = new HashMap<Attribute,Object>();
616                 styleAttrs.put(TextAttribute.POSTURE, TextAttribute.POSTURE_OBLIQUE);
617
618                 int startIndex = styledText.length();
619
620                 parseStyle(styledText, node);
621
622                 styledText.addRun(new JRStyledText.Run(styleAttrs, startIndex, styledText.length()));
623             }
624             else if (node.getNodeType() == Node.ELEMENT_NODE && NODE_underline.equalsIgnoreCase(node.getNodeName()))
625             {
626                 Map<Attribute,Object> styleAttrs = new HashMap<Attribute,Object>();
627                 styleAttrs.put(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON);
628
629                 int startIndex = styledText.length();
630
631                 parseStyle(styledText, node);
632
633                 styledText.addRun(new JRStyledText.Run(styleAttrs, startIndex, styledText.length()));
634             }
635             else if (node.getNodeType() == Node.ELEMENT_NODE && NODE_sup.equalsIgnoreCase(node.getNodeName()))
636             {
637                 Map<Attribute,Object> styleAttrs = new HashMap<Attribute,Object>();
638                 styleAttrs.put(TextAttribute.SUPERSCRIPT, TextAttribute.SUPERSCRIPT_SUPER);
639
640                 int startIndex = styledText.length();
641
642                 parseStyle(styledText, node);
643
644                 styledText.addRun(new JRStyledText.Run(styleAttrs, startIndex, styledText.length()));
645             }
646             else if (node.getNodeType() == Node.ELEMENT_NODE && NODE_sub.equalsIgnoreCase(node.getNodeName()))
647             {
648                 Map<Attribute,Object> styleAttrs = new HashMap<Attribute,Object>();
649                 styleAttrs.put(TextAttribute.SUPERSCRIPT, TextAttribute.SUPERSCRIPT_SUB);
650
651                 int startIndex = styledText.length();
652
653                 parseStyle(styledText, node);
654
655                 styledText.addRun(new JRStyledText.Run(styleAttrs, startIndex, styledText.length()));
656             }
657             else if (node.getNodeType() == Node.ELEMENT_NODE && NODE_font.equalsIgnoreCase(node.getNodeName()))
658             {
659                 NamedNodeMap nodeAttrs = node.getAttributes();
660
661                 Map<Attribute,Object> styleAttrs = new HashMap<Attribute,Object>();
662
663                 if (nodeAttrs.getNamedItem(ATTRIBUTE_size) != null)
664                 {
665                     styleAttrs.put(
666                         TextAttribute.SIZE,
667                         Float.valueOf(nodeAttrs.getNamedItem(ATTRIBUTE_size).getNodeValue())
668                         );
669                 }
670
671                 if (nodeAttrs.getNamedItem(ATTRIBUTE_color) != null)
672                 {
673                     Color color = 
674                         JRColorUtil.getColor(
675                             nodeAttrs.getNamedItem(ATTRIBUTE_color).getNodeValue(),
676                             Color.black
677                             );
678                     styleAttrs.put(
679                         TextAttribute.FOREGROUND,
680                         color
681                         );
682                 }
683
684                 if (nodeAttrs.getNamedItem(ATTRIBUTE_fontFace) != null
685                 {
686                     String fontFaces = nodeAttrs.getNamedItem(ATTRIBUTE_fontFace).getNodeValue();
687
688                     StringTokenizer t = new StringTokenizer(fontFaces, ",");
689                     while (t.hasMoreTokens()) 
690                     {
691                         String face = t.nextToken().trim();
692                         if (AVAILABLE_FONT_FACE_NAMES.contains(face)) 
693                         {
694                             styleAttrs.put(TextAttribute.FAMILY, face);
695                             break;
696                         }
697                     }
698                 }
699                 
700                 int startIndex = styledText.length();
701
702                 parseStyle(styledText, node);
703
704                 styledText.addRun(new JRStyledText.Run(styleAttrs, startIndex, styledText.length()));
705
706             }
707             else if (node.getNodeType() == Node.ELEMENT_NODE && NODE_br.equalsIgnoreCase(node.getNodeName()))
708             {
709                 styledText.append("\n");
710
711                 int startIndex = styledText.length();
712                 resizeRuns(styledText.getRuns(), startIndex, 1);
713
714                 parseStyle(styledText, node);
715                 styledText.addRun(new JRStyledText.Run(new HashMap<Attribute,Object>(), startIndex, styledText.length()));
716
717                 if (startIndex < styledText.length()) {
718                     styledText.append("\n");
719                     resizeRuns(styledText.getRuns(), startIndex, 1);
720                 }
721             }
722             else if (node.getNodeType() == Node.ELEMENT_NODE && NODE_li.equalsIgnoreCase(node.getNodeName()))
723             {
724                 String tmpText = styledText.getText();
725                 if(tmpText.length() > 0 && !tmpText.endsWith("\n"))
726                 {
727                     styledText.append("\n");
728                 }
729                 styledText.append(" \u2022 ");
730
731                 int startIndex = styledText.length();
732                 resizeRuns(styledText.getRuns(), startIndex, 1);
733                 parseStyle(styledText, node);
734                 styledText.addRun(new JRStyledText.Run(new HashMap<Attribute,Object>(), startIndex, styledText.length()));
735                 
736                 // if the text in the next node does not start with a '\n', or 
737                 // if the next node is not a <li /> one, we have to append a new line
738                 Node nextNode = node.getNextSibling();
739                 String textContent = getFirstTextOccurence(nextNode);
740                 if(nextNode != null && 
741                         !((nextNode.getNodeType() == Node.ELEMENT_NODE &&
742                                 NODE_li.equalsIgnoreCase(nextNode.getNodeName()) ||
743                         (textContent != null && textContent.startsWith("\n")))
744                         ))
745                 {
746                     styledText.append("\n");
747                     resizeRuns(styledText.getRuns(), startIndex, 1);
748                 }
749             }
750             else if (node.getNodeType() == Node.ELEMENT_NODE && NODE_a.equalsIgnoreCase(node.getNodeName()))
751             {
752                 if (hyperlink == null)
753                 {
754                     NamedNodeMap nodeAttrs = node.getAttributes();
755
756                     Map<Attribute,Object> styleAttrs = new HashMap<Attribute,Object>();
757
758                     hyperlink = new JRBasePrintHyperlink();
759                     hyperlink.setHyperlinkType(HyperlinkTypeEnum.REFERENCE);
760                     styleAttrs.put(JRTextAttribute.HYPERLINK, hyperlink);
761                     
762                     if (nodeAttrs.getNamedItem(ATTRIBUTE_href) != null)
763                     {
764                         hyperlink.setHyperlinkReference( nodeAttrs.getNamedItem(ATTRIBUTE_href).getNodeValue());
765                     }
766
767                     if (nodeAttrs.getNamedItem(ATTRIBUTE_type) != null)
768                     {
769                         hyperlink.setLinkType(nodeAttrs.getNamedItem(ATTRIBUTE_type).getNodeValue());
770                     }
771
772                     if (nodeAttrs.getNamedItem(ATTRIBUTE_target) != null)
773                     {
774                         hyperlink.setLinkTarget(nodeAttrs.getNamedItem(ATTRIBUTE_target).getNodeValue());
775                     }
776
777                     int startIndex = styledText.length();
778
779                     parseStyle(styledText, node);
780
781                     styledText.addRun(new JRStyledText.Run(styleAttrs, startIndex, styledText.length()));
782                     
783                     hyperlink = null;
784                 }
785                 else
786                 {
787                     throw new SAXException("Hyperlink <a> tags cannot be nested.");
788                 }
789             }
790             else if (node.getNodeType() == Node.ELEMENT_NODE && NODE_param.equalsIgnoreCase(node.getNodeName()))
791             {
792                 if (hyperlink == null)
793                 {
794                     throw new SAXException("Hyperlink <param> tags must appear inside an <a> tag only.");
795                 }
796                 else
797                 {
798                     NamedNodeMap nodeAttrs = node.getAttributes();
799
800                     JRPrintHyperlinkParameter parameter = new JRPrintHyperlinkParameter();
801                     
802                     if (nodeAttrs.getNamedItem(ATTRIBUTE_name) != null)
803                     {
804                         parameter.setName(nodeAttrs.getNamedItem(ATTRIBUTE_name).getNodeValue());
805                     }
806
807                     if (nodeAttrs.getNamedItem(ATTRIBUTE_valueClass) != null)
808                     {
809                         parameter.setValueClass(nodeAttrs.getNamedItem(ATTRIBUTE_valueClass).getNodeValue());
810                     }
811
812                     String strValue = node.getTextContent();
813                     if (strValue != null)
814                     {
815                         Object value = JRValueStringUtils.deserialize(parameter.getValueClass(), strValue);
816                         parameter.setValue(value);
817                     }
818                         
819                     hyperlink.addHyperlinkParameter(parameter);
820                 }
821             }
822             else if (node.getNodeType() == Node.ELEMENT_NODE)
823             {
824                 String nodeName = "<" + node.getNodeName() + ">";
825                 throw new SAXException("Tag " + nodeName + " is not a valid styled text tag.");
826             }
827         }
828     }
829
830     /**
831      *
832      */

833     private void resizeRuns(List<Run> runs, int startIndex, int count)
834     {
835         for (int j = 0; j < runs.size(); j++)
836         {
837             JRStyledText.Run run = runs.get(j);
838             if (run.startIndex <= startIndex && run.endIndex > startIndex - count)
839             {
840                 run.endIndex += count;
841             }
842         }
843     }
844
845
846     /**
847      *
848      */

849     private StringBuilder writeStyleAttributes(Map<Attribute,Object> parentAttrs,  Map<Attribute,Object> attrs)
850     {
851         StringBuilder sb = new StringBuilder();
852         
853         Object value = attrs.get(TextAttribute.FAMILY);
854         Object oldValue = parentAttrs.get(TextAttribute.FAMILY);
855         
856         if (value != null && !value.equals(oldValue))
857         {
858             sb.append(SPACE);
859             sb.append(ATTRIBUTE_fontName);
860             sb.append(EQUAL_QUOTE);
861             sb.append(value);
862             sb.append(QUOTE);
863         }
864
865         value = attrs.get(TextAttribute.WEIGHT);
866         oldValue = parentAttrs.get(TextAttribute.WEIGHT);
867
868         if (value != null && !value.equals(oldValue))
869         {
870             sb.append(SPACE);
871             sb.append(ATTRIBUTE_isBold);
872             sb.append(EQUAL_QUOTE);
873             sb.append(value.equals(TextAttribute.WEIGHT_BOLD));
874             sb.append(QUOTE);
875         }
876
877         value = attrs.get(TextAttribute.POSTURE);
878         oldValue = parentAttrs.get(TextAttribute.POSTURE);
879
880         if (value != null && !value.equals(oldValue))
881         {
882             sb.append(SPACE);
883             sb.append(ATTRIBUTE_isItalic);
884             sb.append(EQUAL_QUOTE);
885             sb.append(value.equals(TextAttribute.POSTURE_OBLIQUE));
886             sb.append(QUOTE);
887         }
888
889         value = attrs.get(TextAttribute.UNDERLINE);
890         oldValue = parentAttrs.get(TextAttribute.UNDERLINE);
891
892         if (
893             (value == null && oldValue != null)
894             || (value != null && !value.equals(oldValue))
895             )
896         {
897             sb.append(SPACE);
898             sb.append(ATTRIBUTE_isUnderline);
899             sb.append(EQUAL_QUOTE);
900             sb.append(value != null);
901             sb.append(QUOTE);
902         }
903
904         value = attrs.get(TextAttribute.STRIKETHROUGH);
905         oldValue = parentAttrs.get(TextAttribute.STRIKETHROUGH);
906
907         if (
908             (value == null && oldValue != null)
909             || (value != null && !value.equals(oldValue))
910             )
911         {
912             sb.append(SPACE);
913             sb.append(ATTRIBUTE_isStrikeThrough);
914             sb.append(EQUAL_QUOTE);
915             sb.append(value != null);
916             sb.append(QUOTE);
917         }
918
919         value = attrs.get(TextAttribute.SIZE);
920         oldValue = parentAttrs.get(TextAttribute.SIZE);
921
922         if (value != null && !value.equals(oldValue))
923         {
924             sb.append(SPACE);
925             sb.append(ATTRIBUTE_size);
926             sb.append(EQUAL_QUOTE);
927             sb.append(value);
928             sb.append(QUOTE);
929         }
930
931         value = attrs.get(JRTextAttribute.PDF_FONT_NAME);
932         oldValue = parentAttrs.get(JRTextAttribute.PDF_FONT_NAME);
933
934         if (value != null && !value.equals(oldValue))
935         {
936             sb.append(SPACE);
937             sb.append(ATTRIBUTE_pdfFontName);
938             sb.append(EQUAL_QUOTE);
939             sb.append(value);
940             sb.append(QUOTE);
941         }
942
943         value = attrs.get(JRTextAttribute.PDF_ENCODING);
944         oldValue = parentAttrs.get(JRTextAttribute.PDF_ENCODING);
945
946         if (value != null && !value.equals(oldValue))
947         {
948             sb.append(SPACE);
949             sb.append(ATTRIBUTE_pdfEncoding);
950             sb.append(EQUAL_QUOTE);
951             sb.append(value);
952             sb.append(QUOTE);
953         }
954
955         value = attrs.get(JRTextAttribute.IS_PDF_EMBEDDED);
956         oldValue = parentAttrs.get(JRTextAttribute.IS_PDF_EMBEDDED);
957
958         if (value != null && !value.equals(oldValue))
959         {
960             sb.append(SPACE);
961             sb.append(ATTRIBUTE_isPdfEmbedded);
962             sb.append(EQUAL_QUOTE);
963             sb.append(value);
964             sb.append(QUOTE);
965         }
966
967         value = attrs.get(TextAttribute.FOREGROUND);
968         oldValue = parentAttrs.get(TextAttribute.FOREGROUND);
969
970         if (value != null && !value.equals(oldValue))
971         {
972             sb.append(SPACE);
973             sb.append(ATTRIBUTE_forecolor);
974             sb.append(EQUAL_QUOTE);
975             sb.append(JRColorUtil.getCssColor((Color)value));
976             sb.append(QUOTE);
977         }
978
979         value = attrs.get(TextAttribute.BACKGROUND);
980         oldValue = parentAttrs.get(TextAttribute.BACKGROUND);
981
982         if (value != null && !value.equals(oldValue))
983         {
984             sb.append(SPACE);
985             sb.append(ATTRIBUTE_backcolor);
986             sb.append(EQUAL_QUOTE);
987             sb.append(JRColorUtil.getCssColor((Color)value));
988             sb.append(QUOTE);
989         }
990         
991         return sb;
992     }
993     
994     /**
995      * The method returns the first text occurrence in a given node element
996      * @param node
997      * @return String
998      */

999     private String getFirstTextOccurence(Node node){
1000         if(node != null)
1001         {
1002             if(node.getNodeValue() != null)
1003             {
1004                 return node.getNodeValue();
1005             }
1006             NodeList nodeList = node.getChildNodes();
1007             for (int i=0; i< nodeList.getLength(); i++)
1008             {
1009                 String firstOccurence = getFirstTextOccurence(nodeList.item(i));
1010                 if(firstOccurence != null)
1011                 {
1012                     return firstOccurence;
1013                 }
1014             }
1015         }
1016         return null;
1017     }
1018
1019     @Override
1020     public void error(SAXParseException e) {
1021         if(log.isErrorEnabled())
1022         {
1023             log.error("Error parsing styled text.", e);
1024         }
1025     }
1026
1027     @Override
1028     public void fatalError(SAXParseException e) {
1029         if(log.isFatalEnabled())
1030         {
1031             log.fatal("Error parsing styled text.", e);
1032         }
1033     }
1034
1035     @Override
1036     public void warning(SAXParseException e) {
1037         if(log.isWarnEnabled())
1038         {
1039             log.warn("Error parsing styled text.", e);
1040         }
1041     }
1042
1043 }
1044