1    package com.instantbank.lettertemplate.editor.web;
2    
3    import java.io.File;
4    import java.io.PrintWriter;
5    import java.io.FileWriter;
6    import java.io.IOException;
7    import java.io.BufferedOutputStream;
8    import java.io.FileOutputStream;
9    import java.io.FileNotFoundException;
10   import java.io.Reader;
11   import java.io.BufferedReader;
12   import java.io.StringReader;
13   import java.io.StringWriter;
14   import java.io.ByteArrayInputStream;
15   import java.io.ByteArrayOutputStream;
16   
17   import java.util.ArrayList;
18   import java.util.HashSet;
19   import java.util.Set;
20   import java.util.Hashtable;
21   import java.util.Iterator;
22   import java.util.Set;
23   import java.util.Enumeration;
24   import java.util.Locale;
25   import java.util.Calendar;
26   import java.util.Date;
27   import java.text.SimpleDateFormat;
28   
29   import javax.xml.transform.Templates;
30   import javax.xml.transform.TransformerConfigurationException;
31   import javax.xml.transform.TransformerFactory;
32   import javax.xml.transform.Result;
33   
34   import javax.xml.transform.sax.SAXSource;
35   import javax.xml.transform.sax.SAXTransformerFactory;
36   import javax.xml.transform.sax.TransformerHandler;
37   import javax.xml.transform.sax.SAXResult;
38   import javax.xml.transform.sax.TemplatesHandler;
39   
40   import javax.xml.transform.stream.StreamResult;
41   
42   import org.apache.fop.apps.Driver;
43   import org.apache.fop.apps.FOPException;
44   
45   import org.apache.log.LogTarget;
46   import org.apache.log.Hierarchy;
47   
48   import org.apache.log.format.PatternFormatter;
49   
50   import org.apache.log.output.io.StreamTarget;
51   
52   import org.apache.log4j.Category;
53   
54   import org.xml.sax.SAXException;
55   import org.xml.sax.XMLReader;
56   import org.xml.sax.InputSource;
57   
58   import org.xml.sax.helpers.XMLReaderFactory;
59   
60   import com.instantbank.common.utilcomponents.Debug;
61   import com.instantbank.common.utilcomponents.CommonUtil;
62   import com.instantbank.common.utilcomponents.JNDINames;
63   import com.instantbank.common.utilcomponents.LetterTemplateExceptionMessage;
64   import com.instantbank.common.utilcomponents.LetterTemplateGlobals;
65   import com.instantbank.common.utilcomponents.DAOException;
66   
67   import com.instantbank.component.lettertemplate.util.VariablesFormat;
68   import com.instantbank.component.lettertemplate.ejb.LetterTemplate;
69   import com.instantbank.component.job.util.Field;
70   
71   import com.instantbank.lettertemplate.control.LetterTemplateEventException;
72   import com.instantbank.lettertemplate.editor.util.LetterOp;
73   
74   import ru.novosoft.rtf2fo.RTFReader;
75   import ru.novosoft.rtf2fo.FOBuilder;
76   
77   /**
78    * Transforms a Template --first-- to FO format and --after-- either to PDF
79    * format --if the template has "laser" print type-- or to TXT format --if the
80    * template has "typewriter" print type--.
81    *
82    * @author InstantBank (Rodrigo Lopez)
83    * @created September 2002
84    */
85   public class TemplateTransformer {
86   
87     /**
88      * Rtf auxiliary mark indicating the occurrence of an image.
89      */
90     public static final String IMGMARK = "ef\'\\";
91   
92     /**
93      * Rtf "auxiliary" mark indicating the occurrence of a variable. This text
94      * must be replaced by a "true" rtf variable occurrence. See {@link
95      * #openRtfVarTag}, {@link #closeRtfVarTag}.
96      */
97     public static final String VARMARK = "ff\'\\";
98   
99     /**
100     * Auxiliary mark indicating the occurrence of an image in a fo file.
101     */
102    public static final char CHARIMGMARK = '\u00FE';
103  
104    /**
105     * Opening rtf text for a true Rtf variable occurrence.
106     */
107    public static final String openRtfVarTag
108       = "{\\field{\\*\\fldinst { DOCVARIABLE ";
109  
110    /**
111     * Closing text for a true Rtf variable occurrence.
112     */
113    public static final String closeRtfVarTag
114       = " \\\\* MERGEFORMAT }}{\\fldrslt {none}}}";
115  
116    /**
117     * Extension for the temporary file that contains a fo template just before
118     * variables are substituted by values.
119     */
120    public static final String FO_EXT = ".fo";
121  
122    /**
123     * Extension for the directory that contains the images files referenced by a
124     * fo template.
125     */
126    public static final String IMAGES_DIR = ".images";
127  
128    /**
129     * Rendering mode for laser templates.
130     */
131    public static final int RENDER_PDF = Driver.RENDER_PDF;
132  
133    /**
134     * Rendering mode for typewritter templates.
135     */
136    public static final int RENDER_TXT = Driver.RENDER_TXT;
137  
138    /**
139     * Fo template which is the base of the transformations.
140     */
141    String foTemplate;
142  
143    /**
144     * Base name shared by the foTemplate file and the directory of images.
145     */
146    private String foFileName;
147  
148    /**
149     * working directory where temporary files are generated.
150     */
151    protected String workDir;
152  
153    /**
154     * Rendering mode for the final document. Default value is RENDER_PDF.
155     */
156    private int renderMode = RENDER_PDF;
157  
158    /**
159     * Debugger objet for this class.
160     */
161    private static Debug debug;
162  
163    //------- Common Jaxp stuff ------------
164  
165    /**
166     * Sax parser for the construction of the (xsl)templates and for the
167     * actual substitution of variables in the (letter)template.
168     */
169    private XMLReader reader;
170  
171    /**
172     * Common Xsl templates constructed from LetterTemplateGlobals.fillSheet.
173     * The actual Xsl transformer is built starting from these templates.
174     */
175    private Templates xslTemplates;
176  
177    /**
178     * Sax factory required for xsl transformer building.
179     */
180    private SAXTransformerFactory saxFactory;
181  
182    /**
183     * Formatter for values of variables as taken from the data base.
184     */
185    private VariablesFormat varFormatter;
186  
187    /**
188     * The sequence of images in ascending order of position
189     * <pos,varCode,formatCode>
190     */
191    private ArrayList[] theImages;
192  
193    /**
194     * The set of vars as {[varCode,[format1, format2, ..]]}
195     */
196    private ArrayList[] theVars;
197  
198    /**
199     * Offset type and name for variables in {@link #theVars}
200     */
201    private String[] varsOffsetType;
202  
203    /**
204     * Full information about company fields
205     */
206    private Hashtable hashFields;
207  
208    /**
209     * Company's Calendars.
210     */
211    private Hashtable calendars;
212  
213    /**
214     * Private reference to the letter template ejb.
215     */
216    private LetterTemplate letejb;
217  
218    /**
219     * Path for rtf2fo's configuration dir.
220     */
221    private static String rtf2foconfdir;
222    /**
223     * The rtf2fo logger
224     */
225    private static Category cat;
226  
227    /**
228     * Parser for dates comming from the data base
229     */
230    private static SimpleDateFormat dateFormater;
231  
232    /**
233     * The Fop logger.
234     */
235    org.apache.log.Logger foplog;
236  
237    static {
238      debug = new Debug();
239      debug.setDebugginOn(true);
240      debug.setPreMessage("** TemplateTransformer: ");
241  
242      dateFormater =
243        new SimpleDateFormat(
244        LetterTemplateGlobals.DATE_PARSING_PATTERN, Locale.US);
245  
246      rtf2foconfdir =
247        CommonUtil.getApplicationProperty(JNDINames.RTF2FO_CONFIG_PATH);
248  
249      System.setProperty("rtf2fo.home", "file:/" + rtf2foconfdir);
250      System.setProperty("log4j.configuration",
251        "file:/" + rtf2foconfdir + "/conf/defaultLog.properties");
252  
253      cat = Category.getInstance(TemplateTransformer.class.getName());
254  
255    }
256  
257  
258    /**
259     * TemplateTransformer constructor.
260     *
261     * @param tmpDir Directory where the foTemplate will be created.
262     * @param foTemplate The foTemplate --without images references-- as a String.
263     * @param theImages Images in ascending order --by position--.
264     * @param theVars Variables' information (unordered). Each variable is paired
265     *                   with all formats and date offsets applied to it in the template.
266     * @param foFileName Chosen name for the fo template file.
267     * @param letejb Session ejb providing letter template services.
268     * @throws LetterTemplateEventException Description of the Exception
269     */
270    public TemplateTransformer(String tmpDir, String foTemplate,
271                               ArrayList[] theImages, ArrayList[] theVars,
272                               String foFileName, LetterTemplate letejb)
273       throws LetterTemplateEventException {
274  
275      debug.println("Rtf2FoConfdir = " + rtf2foconfdir);
276      workDir = tmpDir;
277  
278      varFormatter = new VariablesFormat();
279  
280      this.foTemplate = foTemplate;
281      this.foFileName = foFileName;
282      this.theImages = theImages;
283      this.theVars = theVars;
284      this.letejb = letejb;
285  
286      initFopLogger();
287  
288      initfiller();
289  
290      //Stores the foTemplate in a file with external references to images
291      this.toFoFile();
292      try {
293        this.hashFields = letejb.loadFields();
294        initOffsetType(theVars, hashFields);
295      }
296      catch(Exception ex) {
297        debug.println(CommonUtil.stackTraceToString(ex));
298        throw new LetterTemplateEventException
299          ("Field information can not be loaded");
300      }
301  
302      try {
303        this.calendars = letejb.loadCalendars();
304      }
305      catch(Exception ex) {
306        debug.println(CommonUtil.stackTraceToString(ex));
307        throw new LetterTemplateEventException
308          ("Calendar information can not be loaded");
309      }
310    }
311  
312  
313    /**
314     * Light version of a TemplateTransformer. When instantiated through
315     * this constructor, it behaves correctly only for checking date offset
316     * inconsistencies.
317     *
318     * @param theVars Variables' information (unordered). Each variable is paired
319     *                   with all formats and date offsets applied to it in the
320     *                   template.
321     * @param hashFields Table with company field's information.
322     * @throws LetterTemplateEventException Description of the Exception
323     */
324    public TemplateTransformer(ArrayList[] theVars, Hashtable hashFields)
325       throws LetterTemplateEventException {
326  
327      this.theVars = theVars;
328      this.hashFields = hashFields;
329      initOffsetType(theVars, hashFields);
330  
331    }
332  
333  
334    /**
335     * Inits the logger for the FOP api
336     */
337    private void initFopLogger() {
338      Hierarchy hierarchy = Hierarchy.getDefaultHierarchy();
339  
340      PatternFormatter formatter = new PatternFormatter(
341        "[%{priority}]: %{message}\n%{throwable}");
342      LogTarget target = null;
343      try {
344        target = new StreamTarget(
345          new FileOutputStream(workDir + "/fop.log"), formatter);
346      }
347      catch(Exception ex) {
348        debug.println("fop logger file couln't be opened");
349      }
350      hierarchy.setDefaultLogTarget(target);
351      foplog = hierarchy.getLoggerFor("fop");
352      foplog.setPriority(org.apache.log.Priority.DEBUG);
353      debug.println("Everything was right initiating FopLogger");
354    }
355  
356  
357    /**
358     * Calculates --in {@link #varsOffsetType}--the offset types and names
359     * of a list of variables.
360     *
361     * @param theVars The list of variables with the format:
362     *        [varCode, [(fmt1, offset1), (fmt2, offset2), ...]]
363     * <DT><B>Side effects:</B><DD>
364     *     Sets the varsOffsetType attribute.
365     * @param hashFields Description of the Parameter
366     */
367    private void initOffsetType(ArrayList[] theVars, Hashtable hashFields) {
368      varsOffsetType = new String[theVars.length];
369      for(int i = 0; i < theVars.length; i++) {
370        Long varCode = (Long)theVars[i].get(0);
371        Field field = (Field)hashFields.get(varCode);
372        varsOffsetType[i] = field.getOffsetType();
373      }
374    }
375  
376  
377    /**
378     * Checks if date offsets in {@link #theVars} are consistent with the type of
379     * offset precalculated in {@link #varsOffsetType} by
380     * {@link #initOffsetType(ArrayList[], Hashtable)}.
381     *
382     * @return A set with codes of the inconsistent variables (if there are
383     * inconsistencies).
384     */
385    public Set offsetConsistency() {
386      Set res = new HashSet();
387      for(int i = 0; i < theVars.length; i++) {
388        if(varsOffsetType[i].equals(LetterTemplateGlobals.NO_OFFSET)
389          && nonullOffset((ArrayList)theVars[i].get(1))) {
390          res.add(theVars[i].get(0));
391        }
392      }
393      return res;
394    }
395  
396  
397    /**
398     * Test if there is a non zero date offset in a list of [varFormat, dateOffset]
399     *
400     * @param fmtDelta A list of [varFormat, dateOffset]
401     * @return True if there is, at least, one non zero offset. False otherwise
402     */
403    private static boolean nonullOffset(ArrayList fmtDelta) {
404      Iterator it = fmtDelta.iterator();
405      while(it.hasNext()) {
406        Long[] fd = (Long[])it.next();
407        if(fd[1].longValue() != 0) {
408          return true;
409        }
410      }
411      return false;
412    }
413  
414  
415    /**
416     * Getter method for hashFields
417     *
418     * @return The hashFields value
419     */
420    public Hashtable getHashFields() {
421      return hashFields;
422    }
423  
424  
425    /**
426     * Inits the parser(reader) and the (xsl)templates.
427     */
428    private void initfiller() {
429      try {
430        TransformerFactory transformerFactory = TransformerFactory.newInstance();
431        if(transformerFactory.getFeature(SAXSource.FEATURE)
432          && transformerFactory.getFeature(SAXResult.FEATURE)) {
433          saxFactory =
434            ((SAXTransformerFactory)transformerFactory);
435  
436          TemplatesHandler templatesHandler = saxFactory.newTemplatesHandler();
437  
438          reader = XMLReaderFactory.createXMLReader();
439          reader.setContentHandler(templatesHandler);
440  
441          //Loads --parsing-- the fill sheet
442          reader.parse(
443            new InputSource(
444            new ByteArrayInputStream(LetterTemplateGlobals.fillSheet.getBytes())));
445  
446          //Gets the associated (xslt) templates
447          xslTemplates = templatesHandler.getTemplates();
448        }
449      }
450      catch(TransformerConfigurationException tex) {
451        cat.debug(tex.getMessage());
452      }
453      catch(SAXException sex) {
454        cat.debug(CommonUtil.stackTraceToString(sex));
455      }
456      catch(IOException iox) {
457        cat.debug(CommonUtil.stackTraceToString(iox));
458      }
459    }
460  
461  
462    /**
463     * Creates a String containing the FO version of a rtf text using the "rtf2fo"
464     * api. The resulting text includes "fo variable tags" that must be filled
465     * afterwards in order to obtain an actual letter. Based on the rtf2fo API.
466     *
467     * @param data Rtf text to be transformed. Includes "rtf variable tags" and
468     *      "rtf image marks".
469     * @return The resulting fo template (as a String) or "null" if any
470     *      trouble arises.
471     */
472    public static String rtftoFoTemplate(String data) {
473  
474      ru.novosoft.rtf2fo.Options opt = new ru.novosoft.rtf2fo.Options();
475  
476      //generate a "fo template" with variable tags
477      opt.generateFields = true;
478  
479      //options compatible with fop-0.20.3
480      opt.noTextBoxes = true;
481      opt.noUseContentTabs = true;
482      opt.useMasterName = false;
483      opt.generateSimpleFontFamily = true;
484  
485      try {
486        StringWriter writer = new StringWriter();
487        Reader reader = new BufferedReader(new StringReader(data));
488        RTFReader rtfReader = new RTFReader(reader, null, null);
489        FOBuilder builder = new FOBuilder(rtfReader, writer, opt);
490  
491        rtfReader.setControlHandler(builder);
492        rtfReader.setCharacterHandler(builder);
493        rtfReader.process();
494  
495        writer.flush();
496        writer.close();
497        return writer.toString();
498      }
499      catch(IOException iox) {
500        debug.println("Problems in toFoTemplate");
501        debug.println(CommonUtil.stackTraceToString(iox));
502        return null;
503      }
504    }
505  
506  
507    /**
508     * Substitutes every occurrence of a {@link #VARMARK} --in the rtf text of this
509     * template-- by the "\docvar" rtf convention (see {@link
510     * #rtfVariableTag(String)}).  All
511     * the processing is made in "reverse order" in order to ease the String
512     * searches. The names of the generated tags are constructed by
513     * {@link #buildVarName(ArrayList) buildVarName}.
514     *
515     * @param rtf Description of the Parameter
516     * @param theVariables Description of the Parameter
517     * @return The resulting rtf with the
518     *      substituted text.
519     * @exception LetterTemplateEventException
520     */
521    public static String substituteVariableTags(String rtf, ArrayList[] theVariables)
522       throws LetterTemplateEventException {
523  
524      StringBuffer buff = new StringBuffer(rtf);
525      buff.reverse();
526      String search = buff.toString();
527  
528      ArrayList var;
529      int iSch = search.indexOf(VARMARK);
530      int gap = 0;
531      int pos = iSch + gap;
532  
533      String varTag;
534  
535      String varName;
536      Long varCode;
537      Long fmtCode;
538  
539      int varIdx = theVariables.length - 1;
540      while(iSch >= 0) {
541        pos = iSch + gap;
542        if(iSch + 4 < search.length() && search.charAt(iSch + 4) == '\\') {
543          iSch += 5;
544        }
545        else {
546          if(varIdx < 0) {
547            throw
548              new LetterTemplateEventException(
549              LetterTemplateExceptionMessage.EXTRA_VAR_POSITION);
550          }
551          var = theVariables[varIdx--];
552          varName = buildVarName(var);
553          varTag = rtfVariableTag(varName);
554          buff.replace(pos, pos + 4, varTag);
555          iSch += 4;
556          gap += varTag.length() - 4;
557        }
558        iSch = search.indexOf(VARMARK, iSch);
559  
560      }
561  
562      if(varIdx >= 0) {
563        var = theVariables[varIdx--];
564        varCode = (Long)var.get(1);
565        debug.println("Missing pos for var = " + varCode);
566        throw
567          new LetterTemplateEventException(
568          LetterTemplateExceptionMessage.VAR_POSITION_MISSING);
569      }
570  
571      buff.reverse();
572      return buff.toString();
573    }
574  
575  
576    private static String buildVarName(ArrayList var) {
577      return buildVarName((Long)var.get(1), (Long)var.get(2), (Long)var.get(3));
578    }
579  
580  
581    private static String buildVarName(Long varCode, Long fmtCode, Long delta) {
582      String offset = "";
583      long off = delta.longValue();
584      if(off > 0) {
585        offset = "_p" + off;
586      }
587      else if(off < 0) {
588        offset = "_m" + (-off);
589      }
590  
591      return "f" + fmtCode + offset + "_v" + varCode;
592    }
593  
594  
595    /**
596     * Delivers a rtf variable tag after the name of the variable.
597     *
598     * @param varName Variable's name.
599     * @return The rtf variable tag "in reverse order". See {@link
600     *      #substituteVariableTags(String,ArrayList[])}.
601     */
602    private static String rtfVariableTag(String varName) {
603      String res = openRtfVarTag + varName + closeRtfVarTag;
604      StringBuffer buff = new StringBuffer(res);
605      buff.reverse();
606      return buff.toString();
607    }
608  
609  
610    /**
611     * Delivers a font table in rtf notation with actual family names and font
612     * names.
613     *
614     * @param fontTable Font Table with \fnil family names and font names as
615     *      presented by the template editor.
616     * @return The corrected font table or the original one if anything
617     *      goes wrong.
618     */
619    public static String actualFontTable(String fontTable) {
620      StringBuffer buff = new StringBuffer(fontTable);
621      debug.println("fonttable = " + fontTable);
622      int fnilPos;
623      int semiPos;
624      int gap;
625      int bufPos;
626      int width;
627      try {
628        for(fnilPos = fontTable.indexOf("\\fnil"), gap = 0;
629          fnilPos >= 0;
630          fnilPos = fontTable.indexOf("\\fnil", semiPos)) {
631  
632          bufPos = fnilPos + gap;
633          semiPos = fontTable.indexOf(";", fnilPos);
634          width = semiPos - fnilPos;
635          int whitePos = fontTable.indexOf(' ', fnilPos);
636          String family = actualFamily(fontTable.substring(whitePos + 1, semiPos));
637          debug.println("newFamily = " + family);
638          buff.replace(bufPos, bufPos + width, family);
639          debug.println("table buffer = " + buff.toString());
640          gap = gap + family.length() - width;
641        }
642        return buff.toString();
643      }
644      catch(Throwable tex) {
645        debug.println("Problems converting fontable: " + fontTable);
646        debug.println(CommonUtil.stackTraceToString(tex));
647      }
648  
649      return fontTable;
650    }
651  
652  
653    /**
654     * Delivers actual font family and family name for a font name managed by
655     * template editor.
656     *
657     * @param fontName Font name managed by the editor.
658     * @return Actual font family and family name for "fontName"
659     */
660    public static String actualFamily(String fontName) {
661      int i;
662      for(i = 0; i < LetterOp.FONTNAME.length; i++) {
663        if(LetterOp.FONTNAME[i].equals(fontName)) {
664          break;
665        }
666      }
667  
668      if(i >= LetterOp.FONTNAME.length) {
669        return "\\fnil " + fontName;
670      }
671  
672      return LetterOp.FONTFAMILY[i] + " " + LetterOp.FONTALTNAME[i];
673    }
674  
675  
676    /**
677     *  Calculates the set --without repetitions-- of codes of variables mentioned
678     *  in an ArrayList with information of variables. Each variable will be paired with
679     *  a list of formats and date offsets. The resulting array is in ascending order by varCode.
680     *
681     * @param allVars The new ofVariables value
682     * @return Array of [varCode, [(format1, delta1), (format2, delta) ..]].
683     *      If there is no
684     *      variables, returns an array with 0 entries. The  array is in
685     *      ascending order by varCode.
686     */
687    public static ArrayList[] setOfVariables(ArrayList allVars) {
688  
689      if(allVars.size() == 0) {
690        return new ArrayList[0];
691      }
692  
693      //Calculates a set of tuples { <varCode, varFormat, delta> }
694      Set set = new HashSet();
695      Iterator it = allVars.iterator();
696      while(it.hasNext()) {
697        ArrayList slot = (ArrayList)it.next();
698        ArrayList setSlot = new ArrayList(3);
699        setSlot.add((Long)slot.get(1));
700        setSlot.add((Long)slot.get(2));
701        setSlot.add((Long)slot.get(3));
702        set.add(setSlot);
703      }
704  
705      //debug code: allows to see if a true set has been calculated
706      //printVarSet(set);
707  
708  
709      //Calculates a HashTable with
710      // { (varCode, < [fmt1,delta1], [fmt2,delta2], ..>), ... }
711      Hashtable ht = new Hashtable();
712      it = set.iterator();
713      while(it.hasNext()) {
714        ArrayList setSlot = (ArrayList)it.next();
715        Long varCode = (Long)setSlot.get(0);
716        Long[] fmtDelta = new Long[]{(Long)setSlot.get(1), (Long)setSlot.get(2)};
717        ArrayList formats = (ArrayList)ht.get(varCode);
718        if(formats == null) {
719          formats = new ArrayList(1);
720          formats.add(fmtDelta);
721          ht.put(varCode, formats);
722        }
723        else {
724          formats.add(fmtDelta);
725        }
726      }
727  
728      //Calculates an array of (varCode, < [fmt1,delta1], [fmt2,delta2], ..>)
729      ArrayList tmp = new ArrayList();
730      Enumeration e = ht.keys();
731      int k = 0;
732      while(e.hasMoreElements()) {
733        ArrayList slot = new ArrayList(2);
734        Long varCode = (Long)e.nextElement();
735        slot.add(0, varCode);
736        slot.add(1, ht.get(varCode));
737  
738        //insert in ascending order
739        int m = 0;
740        while(m < tmp.size()) {
741          long mCode = ((Long)((ArrayList)tmp.get(m)).get(0)).longValue();
742          if(varCode.longValue() < mCode) {
743            tmp.add(m, slot);
744            break;
745          }
746          m++;
747        }
748        if(m == tmp.size()) {
749          tmp.add(slot);
750        }
751      }
752  
753      //Copies the ordered ArrayList slots in a new array
754      ArrayList res[] = new ArrayList[tmp.size()];
755      for(int l = 0; l < res.length; l++) {
756        res[l] = (ArrayList)tmp.get(l);
757      }
758      return res;
759    }
760  
761  
762    /**
763     * Debug method that allows to "see" the set generated by setOfVariables.
764     *
765     * @param set Vars in format [(code, {[fmt1,offset],[fmt2,offset]})]
766     */
767    public static void printSetOfVars(ArrayList[] set) {
768      debug.println("SetOfVars:");
769      StringBuffer sb;
770      for(int i = 0; i < set.length; i++) {
771        sb = new StringBuffer();
772        ArrayList setSlot = (ArrayList)set[i];
773        Long code = (Long)setSlot.get(0);
774        ArrayList fmtDelta = (ArrayList)setSlot.get(1);
775        Iterator ik = fmtDelta.iterator();
776        sb.append(code.toString() + " => [");
777        while(ik.hasNext()) {
778          Long[] fmtOff = (Long[])ik.next();
779          sb.append("(" + fmtOff[0] + "," + fmtOff[1] + ")");
780        }
781        sb.append(" ]");
782        debug.println(sb.toString());
783      }
784    }
785  
786  
787    /**
788     * Debug method that allows to print variables' information as
789     * extracted from the data base.
790     *
791     * @param allVars Description of the Parameter
792     */
793    public static void printAllVars(ArrayList allVars) {
794      if(allVars == null) {
795        return;
796      }
797      StringBuffer sb = new StringBuffer(25);
798      for(int i = 0; i < allVars.size(); i++) {
799        ArrayList slot = (ArrayList)allVars.get(i);
800        sb.append("AllVars = (");
801        sb.append((Long)slot.get(0));
802        sb.append(", ");
803        sb.append((Long)slot.get(1));
804        sb.append(", ");
805        sb.append((Long)slot.get(2));
806        sb.append(", ");
807        sb.append((Long)slot.get(3));
808        sb.append(')');
809        debug.println(sb.toString());
810      }
811    }
812  
813  
814    /**
815     * Debug method that allows to print raw variable values as
816     * extracted from the data base.
817     *
818     * @param varCodes Description of the Parameter
819     * @param varValues Description of the Parameter
820     */
821    public static void printRawVars(Long[] varCodes, String[] varValues) {
822      debug.println("Raw Var Values:");
823      int length = varCodes.length;
824      if(length > varValues.length) {
825        length = varValues.length;
826        debug.println("varCodes longer that varValues!!");
827      }
828      else if(length < varValues.length) {
829        debug.println("varCodes shorter that varValues!!");
830      }
831  
832      for(int i = 0; i < length; i++) {
833        debug.println("var(" + varCodes[i] + ") = " + varValues[i]);
834      }
835    }
836  
837  
838    /**
839     * Extracts the codes of variables from a set of variables as calculated
840     * by {@link #setOfVariables(ArrayList)}.
841     *
842     * @param set The original set.
843     * @return An array of codes of variables in ascending order.
844     */
845    public static Long[] varCodesFromSet(ArrayList[] set) {
846  
847      Long res[] = new Long[set.length];
848      for(int i = 0; i < set.length; i++) {
849        res[i] = (Long)set[i].get(0);
850      }
851  
852      return res;
853    }
854  
855  
856    /**
857     * Transform a LetterTemplate in a "fo-template" file with external references to
858     * image files. The fo-template is stored in a file and the images
859     * are stored
860     * in a subdirectory.
861     *
862     * @return Name of the file containing the fo-template.
863     * @exception LetterTemplateEventException
864     */
865    public String toFoFile() throws LetterTemplateEventException {
866  
867      String unfilledFo = foTemplate;
868      //Substitutes image marks by references to image files and creates
869      //image files.
870      debug.println("before: template.hasImages() ?");
871      if(theImages.length > 0) {
872        debug.println("template has images");
873        unfilledFo = substituteImages(workDir, unfilledFo);
874        if(unfilledFo == null) {
875          throw
876            new LetterTemplateEventException(
877            LetterTemplateExceptionMessage.PROBLEMS_SUBSTITUTING_IMAGES);
878        }
879      }
880  
881      try {
882        FileOutputStream out =
883          new FileOutputStream(new File(workDir, foFileName + FO_EXT));
884        out.write(unfilledFo.getBytes());
885        out.flush();
886        out.close();
887        return foFileName + FO_EXT;
888      }
889      catch(IOException ex) {
890        debug.println("Problems recording a fo-template");
891        return null;
892      }
893  
894    }
895  
896  
897    /**
898     * Substitutes images marks in a Fo template by an actual fo-reference to
899     * images files. The images files are constructed after the raw image bytes
900     * stored for each image in the {@link #theImages} array. They are
901     * created in a subdirectory whose name is composed from the basePath
902     * parameter, the {@link #foFileName} and the ".images" extension. The
903     * files themselves are named pict1.jpeg, pict2.jpeg, etc.
904     *
905     * @param basePath Directory where the images subdirectory should be located.
906     * @param unfilledFo Name of the fo template file.
907     * @return The fo template (as a String) with external images references
908     *          or null if the
909     *          images subdirectory can not be created or any other trouble arises.
910     * @exception LetterTemplateEventException
911     */
912    public String substituteImages(String basePath, String unfilledFo)
913       throws LetterTemplateEventException {
914      File imageDir;
915  
916      StringWriter writer = new StringWriter();
917  
918      imageDir = new File(basePath, foFileName + IMAGES_DIR);
919      if(!imageDir.mkdir()) {
920        return null;
921      }
922  
923      try {
924        int i = 0;
925        int c;
926  
927        int imgIdx = 0;
928        int numpict = 0;
929        ArrayList slot;
930        byte[] rawImage;
931        while(i < unfilledFo.length()) {
932          c = unfilledFo.charAt(i);
933          if(c != CHARIMGMARK) {
934            writer.write(c);
935          }
936          else {
937            if(imgIdx >= theImages.length) {
938              throw new
939                LetterTemplateEventException(
940                LetterTemplateExceptionMessage.EXTRA_IMAGE_POSITION);
941            }
942            slot = theImages[imgIdx++];
943            rawImage = (byte[])slot.get(1);
944            numpict++;
945            String imgName = "pict" + numpict + ".jpeg";
946            File fimg = new File(imageDir, imgName);
947            BufferedOutputStream outimg =
948              new BufferedOutputStream(new FileOutputStream(fimg));
949            outimg.write(rawImage);
950            outimg.flush();
951            outimg.close();
952  
953            String imgRef = imageReference(foFileName + IMAGES_DIR, imgName);
954            writer.write(imgRef);
955          }
956          i++;
957        }
958        if(imgIdx < theImages.length) {
959          debug.println("Mmmm ... seems like a malformed template.");
960          throw
961            new LetterTemplateEventException(LetterTemplateExceptionMessage.IMAGE_POSITION_MISSING);
962        }
963  
964        writer.flush();
965        writer.close();
966        return writer.toString();
967      }
968      catch(Exception ex) {
969        debug.println("In substituteImages");
970        debug.println(CommonUtil.stackTraceToString(ex));
971        return null;
972      }
973    }
974  
975  
976    /**
977     * Constructs a fo:inline reference to an image file
978     *
979     * @param imageDir Directory where the image file is located.
980     * @param imageFile Name of the image file.
981     * @return A reference of the form <pre>
982     *   <fo:inline><fo:external-graphic src="file:///..."/></fo:inline>
983     *      </pre>
984     */
985    private String imageReference(String imageDir, String imageFile) {
986      String open = "<fo:inline><fo:external-graphic src=";
987      String close = "/></fo:inline>";
988      String fileUrl = "file:///" + workDir + "/" + imageDir + "/" + imageFile;
989      String ref = "\"" + fileUrl + "\"";
990      return open + ref + close;
991    }
992  
993  
994  
995  
996    /**
997     * Debug method allowing to write a string of characters in a file. The file
998     * is created in the {@link #workDir} directory.
999     *
1000    * @param fileName File's name.
1001    * @param data The string to be written.
1002    */
1003   public void toFile(String fileName, String data) {
1004     try {
1005       File dir = new File(workDir);
1006       PrintWriter fout =
1007         new PrintWriter(
1008         new FileWriter(new File(dir, fileName)));
1009 
1010       fout.println(data);
1011       fout.close();
1012     }
1013     catch(IOException ex) {
1014       debug.println("in toFile");
1015       debug.println(CommonUtil.stackTraceToString(ex));
1016     }
1017   }
1018 
1019 
1020   /**
1021    * Produces an actual letter
1022    *
1023    * @param varValues The values of variables in the template.
1024    * @return the letter.
1025    * @throws LetterTemplateEventException Description of the Exception
1026    */
1027   public ByteArrayOutputStream transform(String[] varValues)
1028      throws LetterTemplateEventException {
1029     String xmlVarValues = buildXmlData(theVars, varsOffsetType, varValues);
1030     String unfilledFoBaseURI = "file:///" + workDir + "/";
1031     String foFilled =
1032       fillVarValues(unfilledFoBaseURI, foFileName + FO_EXT, xmlVarValues);
1033 
1034     if(foFilled == null) {
1035       throw
1036         new LetterTemplateEventException(
1037         LetterTemplateExceptionMessage.PROBLEMS_FILLING_VARIABLES);
1038     }
1039     return toLetter(foFilled);
1040   }
1041 
1042 
1043   /**
1044    * Builds the xml format of "variables' values" expected by "rtf2fo" in order
1045    * to fill the fo template produced by {@link #toFoFile()}. The names of
1046    * the variables are generated with
1047    * {@link #buildVarName(ArrayList) buildVarName}.
1048    *
1049    * @param setOfVars Set of variables in the template.
1050    * @param varsOffType Date Offset type for variables in setOfVars
1051    * @param varValues Array containing  the values of the variables
1052    *      in the same order as in setOfVars.
1053    * @return A xml string with the syntax: <pre>
1054    *   <varvalues>
1055    *     <var-name-1> value-of-this-var </var-name-1>
1056    *     <var-name-2> value-of-this-var </var-name-2>
1057    *                            ::::
1058    *     <var-name-n> value-of-this-var </var-name-n>
1059    *   </varvalues>
1060    * </pre>
1061    */
1062 
1063   public String buildXmlData(ArrayList[] setOfVars, String[] varsOffType,
1064                              String[] varValues) {
1065     String open = "<varvalues>";
1066     String close = "</varvalues>";
1067     StringBuffer res = new StringBuffer(open);
1068     for(int i = 0; i < setOfVars.length; i++) {
1069       Long varCode = (Long)setOfVars[i].get(0);
1070       String offType = varsOffType[i];
1071       ArrayList slot = (ArrayList)setOfVars[i].get(1);
1072       String varValue = varValues[i];
1073       for(int k = 0; k < slot.size(); k++) {
1074         Long fmtCode = ((Long[])slot.get(k))[0];
1075         Long delta = ((Long[])slot.get(k))[1];
1076 
1077         //Fix the name and value of a variable according to delta
1078         String varName = buildVarName(varCode, fmtCode, delta);
1079         String auxVarValue = varValue;
1080         if(delta.longValue() != 0) {
1081           auxVarValue = applyDelta(varValue, delta, offType);
1082         }
1083 
1084         res.append('<');
1085         res.append(varName);
1086         res.append('>');
1087         String value = varFormatter.format(auxVarValue, fmtCode);
1088         res.append(value);
1089         res.append('<');
1090         res.append('/');
1091         res.append(varName);
1092         res.append('>');
1093         res.append('\n');
1094       }
1095     }
1096     res.append(close);
1097     return res.toString();
1098   }
1099 
1100 
1101   /**
1102    * Fills a fo template with variable values.
1103    *
1104    * @param baseUri Base of the Uri address of the file containing the fo
1105    *                 template.
1106    * @param foName Name of the fo template file.
1107    * @param xmlVarData Xml structure containing the
1108    *      variables values. It has the syntax: <pre>
1109    *   <varvalues>
1110    *     <var-name-1> value-of-this-var </var-name-1>
1111    *     <var-name-2> value-of-this-var </var-name-2>
1112    *                            ::::
1113    *     <var-name-n> value-of-this-var </var-name-n>
1114    *   </varvalues>
1115    * </pre>
1116    * @return A String containing a FO document with variables substituted by
1117    * actual values.
1118    */
1119 
1120   public String fillVarValues(String baseUri, String foName, String xmlVarData) {
1121     try {
1122       TransformerHandler handler = saxFactory.newTransformerHandler(xslTemplates);
1123 
1124       handler.getTransformer().setParameter("template", foName);
1125       handler.getTransformer().setParameter("base", baseUri);
1126 
1127       //Only during debugging
1128       //handler.getTransformer().setErrorListener(new XsltErrorListener());
1129 
1130       StringWriter writer = new StringWriter();
1131       Result result = new StreamResult(writer);
1132       handler.setResult(result);
1133 
1134       reader.setContentHandler(handler);
1135       reader.parse(
1136         new InputSource(new ByteArrayInputStream(xmlVarData.getBytes())));
1137 
1138       writer.flush();
1139       writer.close();
1140 
1141       return writer.toString();
1142     }
1143     catch(Exception ex) {
1144       return null;
1145     }
1146 
1147   }
1148 
1149 
1150   /**
1151    * Erases the fo-template file and image files.
1152    */
1153   public void cleanfiles() {
1154     File foFile = new File(workDir, foFileName + FO_EXT);
1155     if(foFile.delete()) {
1156       debug.println(foFileName + FO_EXT + " deleted");
1157     }
1158 
1159     if(theImages.length == 0) {
1160       return;
1161     }
1162 
1163     File imagDir = new File(workDir, foFileName + IMAGES_DIR);
1164 
1165     String[] imgFiles = imagDir.list();
1166 
1167     for(int i = 0; i < imgFiles.length; i++) {
1168       debug.println("image = " + imgFiles[i]);
1169       File image = new File(imagDir, imgFiles[i]);
1170 
1171       if(image.delete()) {
1172         debug.println(imgFiles[i] + " erased");
1173       }
1174       else {
1175         debug.println(imgFiles[i] + " not erased");
1176       }
1177     }
1178 
1179     if(imagDir.delete()) {
1180       cat.info(foFileName + IMAGES_DIR + " erased");
1181     }
1182   }
1183 
1184 
1185   /**
1186    * Sets the renderMode attribute.
1187    * Valid values: RENDER_PDF, RENDER_TXT. Default value is RENDER_PDF.
1188    *
1189    * @param mode The new renderMode value
1190    */
1191   public void setRenderMode(int mode) {
1192     this.renderMode = mode;
1193   }
1194 
1195 
1196   /**
1197    * Constructs the final version of the document. Before using it,
1198    * {@link #renderMode} must be set in order to produce a TXT or
1199    * a PDF document.
1200    *
1201    * @param filledTemplate The fo template (as a String) filled with
1202    *      variable values and external references to images.
1203    * @return The final version of the document.
1204    */
1205   public ByteArrayOutputStream toLetter(String filledTemplate) {
1206 
1207     try {
1208       InputSource in = new InputSource(new StringReader(filledTemplate));
1209       ByteArrayOutputStream bout = new ByteArrayOutputStream();
1210 
1211       Driver driver = new Driver(in, bout);
1212       driver.setLogger(foplog);
1213       driver.setRenderer(renderMode);
1214       driver.run();
1215       bout.close();
1216       return bout;
1217     }
1218     catch(FileNotFoundException fex) {
1219       cat.debug(CommonUtil.stackTraceToString(fex));
1220     }
1221     catch(IOException iox) {
1222       cat.debug(CommonUtil.stackTraceToString(iox));
1223     }
1224     catch(FOPException fopx) {
1225       cat.debug(CommonUtil.stackTraceToString(fopx));
1226     }
1227     return null;
1228   }
1229 
1230 
1231   /**
1232    * Applies a days offset to a date.
1233    *
1234    * @param strDate The date as a string in the format specified by
1235    * LetterTemplateGlobals.DATE_PARSING_PATTERN
1236    * @param delta the offset. Can be positive, negative or zero.
1237    * @param offType Description of the Parameter
1238    * @return The fixed date, again as a String in the same format as the
1239    * original date.
1240    */
1241   public String applyDelta(String strDate, Long delta, String offType) {
1242     try {
1243       Date d = dateFormater.parse(strDate);
1244       if(offType.equals(LetterTemplateGlobals.CHRONO_DAYS_OFFSET)) {
1245         Calendar cal = Calendar.getInstance(Locale.US);
1246         cal.setTime(d);
1247         cal.add(Calendar.DAY_OF_MONTH, delta.intValue());
1248         return dateFormater.format(cal.getTime());
1249       }
1250       else {  // Workable date offset
1251         Date lbDate = CommonUtil.workableOffset(d, delta.intValue(), calendars);
1252         if(lbDate != null) {
1253           return dateFormater.format(lbDate);
1254         }
1255         else {
1256           throw new
1257             Exception("There were no calendar to calculate the workable offset");
1258         }
1259       }
1260     }
1261     catch(Exception ex) {
1262       return LetterTemplateGlobals.BAD_FORMATED_DATE;
1263     }
1264   }
1265 
1266 }
1267 
1268