Clover coverage report -
Coverage timestamp: Sun Apr 18 2004 21:32:30 EDT
file stats: LOC: 1,062   Methods: 40
NCLOC: 498   Classes: 3
 
 Source file Conditionals Statements Methods TOTAL
SHTMLWriter.java 37.3% 46.9% 55% 45.1%
coverage coverage
 1   
 /*
 2   
  * SimplyHTML, a word processor based on Java, HTML and CSS
 3   
  * Copyright (C) 2002 Ulrich Hilger
 4   
  *
 5   
  * This program is free software; you can redistribute it and/or
 6   
  * modify it under the terms of the GNU General Public License
 7   
  * as published by the Free Software Foundation; either version 2
 8   
  * of the License, or (at your option) any later version.
 9   
  *
 10   
  * This program is distributed in the hope that it will be useful,
 11   
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 12   
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 13   
  * GNU General Public License for more details.
 14   
  *
 15   
  * You should have received a copy of the GNU General Public License
 16   
  * along with this program; if not, write to the Free Software
 17   
  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 18   
  */
 19   
 
 20   
 import java.io.*;
 21   
 //import java.io.IOException;
 22   
 import javax.swing.text.*;
 23   
 //import javax.swing.text.Element;
 24   
 //import javax.swing.text.ElementIterator;
 25   
 //import javax.swing.text.AttributeSet;
 26   
 import java.util.Enumeration;
 27   
 import java.util.Vector;
 28   
 import java.util.Hashtable;
 29   
 import javax.swing.AbstractAction;
 30   
 import java.awt.event.ActionEvent;
 31   
 import javax.swing.text.html.CSS;
 32   
 import javax.swing.text.html.HTML;
 33   
 import javax.swing.text.html.HTMLDocument;
 34   
 //import javax.swing.text.StyleConstants;
 35   
 //import javax.swing.text.BadLocationException;
 36   
 //import javax.swing.text.AbstractDocument;
 37   
 
 38   
 /**
 39   
  * A writer for documents of application SimplyHTML.
 40   
  *
 41   
  * <p><code>SHTMLWriter</code> implements an own approach to
 42   
  * clean writing of HTML produced by application SimplyHTML.
 43   
  * To keep it simple, <code>SHTMLWriter</code>
 44   
  * only writes HTML and CSS content application SimplyHTML
 45   
  * 'understands'. Documents not produced by SimplyHTML might or
 46   
  * might not work with this writer.</p>
 47   
  *
 48   
  * <p>With stage 9 the mode property has been added as a workaround
 49   
  * for bug id 4765271 (see http://developer.java.sun.com/developer/bugParade/bugs/4765271.html).
 50   
  * By passing MODE_HTML, font-size attribute contents are written
 51   
  * as is, with MODE_JAVA, values of attribute font-size are
 52   
  * adjusted to (wrong) sizes expected in Java.</p>
 53   
  *
 54   
  * <p>With release 2 of stage 9 this writer is used only when the user
 55   
  * selects not to write HTML 3.2. The standard HTMLWriter with
 56   
  * a fix for writing font sizes inside content is used when HTML 3.2
 57   
  * is to be written.</p>
 58   
  *
 59   
  * @todo add text wrap for content
 60   
  *
 61   
  * @author Ulrich Hilger
 62   
  * @author Light Development
 63   
  * @author <a href="http://www.lightdev.com">http://www.lightdev.com</a>
 64   
  * @author <a href="mailto:info@lightdev.com">info@lightdev.com</a>
 65   
  * @author published under the terms and conditions of the
 66   
  *      GNU General Public License,
 67   
  *      for details see file gpl.txt in the distribution
 68   
  *      package of this software
 69   
  *
 70   
  * @version stage 11, April 27, 2003
 71   
  */
 72   
 
 73   
 public class SHTMLWriter {
 74   
 
 75   
   /** the document this writer is to generate HTML for */
 76   
   private HTMLDocument doc;
 77   
 
 78   
   /** the writer for HTML output */
 79   
   private Writer w;
 80   
 
 81   
   /**
 82   
    * constant for new line character (handled differnetly
 83   
    * on Windows, Unix/Linux and Mac)
 84   
    */
 85   
   private String newline = System.getProperty("line.separator");
 86   
 
 87   
   /** start position in document for writing HTML */
 88   
   private int startOffset;
 89   
 
 90   
   /** end position in document for writing HTML */
 91   
   private int endOffset;
 92   
 
 93   
   /** indicates if the writer currently works on an implied tag */
 94   
   private boolean inImpliedTag = false;
 95   
 
 96   
   /** indicates if the writer currently works on a link */
 97   
   private boolean inLink = false;
 98   
 
 99   
   /** indicates if the wrtier currenty is inside of content */
 100   
   private boolean inContent = false;
 101   
 
 102   
   /** indicates if the writer has already worked on the first tag inside of any content */
 103   
   private boolean firstContent = true;
 104   
 
 105   
   /**
 106   
    * table of CSS attributes which shall not be
 107   
    * written out when encountered inside a document.
 108   
    */
 109   
   private Vector unwantedCssAttributes = new Vector();
 110   
 
 111   
   /**
 112   
    * table of CSS attributes which need adjustment
 113   
    * before writing.
 114   
    *
 115   
    * <p>This is built in as a workaround for bug id 4765271
 116   
    * (see http://developer.java.sun.com/developer/bugParade/bugs/4765271.html)</p>
 117   
    */
 118   
   private Hashtable adjustCssAttributes = new Hashtable();
 119   
 
 120   
   /**
 121   
    * the mode for writing
 122   
    *
 123   
    * <p>This is built in as a workaround for bug id 4765271
 124   
    * (see http://developer.java.sun.com/developer/bugParade/bugs/4765271.html)</p>
 125   
    */
 126   
   private int mode;
 127   
 
 128   
   /**
 129   
    * indicator for writing 'normal' HTML
 130   
    *
 131   
    * <p>This is built in as a workaround for bug id 4765271
 132   
    * (see http://developer.java.sun.com/developer/bugParade/bugs/4765271.html)</p>
 133   
    */
 134   
   public static final int MODE_HTML = 1;
 135   
 
 136   
   /**
 137   
    * indicator for writing 'adjusted Java HTML'
 138   
    *
 139   
    * <p>This is built in as a workaround for bug id 4765271
 140   
    * (see http://developer.java.sun.com/developer/bugParade/bugs/4765271.html)</p>
 141   
    */
 142   
   public static final int MODE_JAVA = 2;
 143   
 
 144   
   /**
 145   
    * construct a new SHTMLWriter to generate HTML for a
 146   
    * given part of a document
 147   
    *
 148   
    * @param w  the writer for HTML output
 149   
    * @param doc  the document to generate HTML from
 150   
    * @param pos  the start position of the part of the document
 151   
    *      to generate HTML for
 152   
    * @param len  the length of the part of the document to
 153   
    *      generate HTML for
 154   
    * @param mode  the writing mode, one of MODE_HTML and MODE_JAVA
 155   
    */
 156  8
   public SHTMLWriter(Writer w, HTMLDocument doc, int pos, int len, int mode) {
 157  8
     this.doc = doc;
 158  8
     this.w = w;
 159  8
     this.startOffset = pos;
 160  8
     this.endOffset = pos + len;
 161  8
     this.mode = mode;
 162  8
     initUnwantedCssAttributes();
 163  8
     initAdjustCssAttributes();
 164   
   }
 165   
 
 166   
   /**
 167   
    * construct a new SHTMLWriter to generate HTML for a
 168   
    * given part of a document
 169   
    *
 170   
    * @param w  the writer for HTML output
 171   
    * @param doc  the document to generate HTML from
 172   
    * @param pos  the start position of the part of the document
 173   
    *      to generate HTML for
 174   
    * @param len  the length of the part of the document to
 175   
    *      generate HTML for
 176   
    */
 177  0
   public SHTMLWriter(Writer w, HTMLDocument doc, int pos, int len) {
 178  0
     this(w, doc, pos, len, MODE_JAVA);
 179   
   }
 180   
 
 181   
   /**
 182   
    * construct a new SHTMLWriter to generate HTML for a
 183   
    * given document
 184   
    *
 185   
    * @param w  the writer for HTML output
 186   
    * @param doc  the document to generate HTML from
 187   
    * @param mode  the writing mode, one of MODE_HTML and MODE_JAVA
 188   
    */
 189  0
   public SHTMLWriter(Writer w, HTMLDocument doc, int mode) {
 190  0
     this(w, doc, 0, doc.getLength(), mode);
 191   
   }
 192   
 
 193   
   /**
 194   
    * construct a new SHTMLWriter to generate HTML for a
 195   
    * given document
 196   
    *
 197   
    * @param w  the writer for HTML output
 198   
    * @param doc  the document to generate HTML from
 199   
    */
 200  2
   public SHTMLWriter(Writer w, HTMLDocument doc) {
 201  2
     this(w, doc, 0, doc.getLength(), MODE_JAVA);
 202   
   }
 203   
 
 204   
   /**
 205   
    * construct an uninitialized SHTMLWriter
 206   
    *
 207   
    * <p>This can be used for snythesizing HTML instead of
 208   
    * reading it from the element structure of a document.</p>
 209   
    *
 210   
    * <p>If a SHTMLWriter was constructed with this constructor
 211   
    * which shall be used for writing a document, the document and
 212   
    * writeSegment properties of the SHTMLWriter need to be set
 213   
    * before calling its write() methods.</p>
 214   
    *
 215   
    * @param w  the writer to write to
 216   
    */
 217  6
   public SHTMLWriter(Writer w) {
 218  6
     this(w, null, 0, 0, MODE_JAVA);
 219   
   }
 220   
 
 221   
   /**
 222   
    * initialize the table of CSS attributes which shall not be
 223   
    * written out when encountered inside a document.
 224   
    */
 225  8
   private void initUnwantedCssAttributes() {
 226  8
     unwantedCssAttributes.addElement(CSS.Attribute.BORDER_TOP_WIDTH);
 227  8
     unwantedCssAttributes.addElement(CSS.Attribute.BORDER_RIGHT_WIDTH);
 228  8
     unwantedCssAttributes.addElement(CSS.Attribute.BORDER_BOTTOM_WIDTH);
 229  8
     unwantedCssAttributes.addElement(CSS.Attribute.BORDER_LEFT_WIDTH);
 230  8
     unwantedCssAttributes.addElement(CSS.Attribute.MARGIN_TOP);
 231  8
     unwantedCssAttributes.addElement(CSS.Attribute.MARGIN_RIGHT);
 232  8
     unwantedCssAttributes.addElement(CSS.Attribute.MARGIN_BOTTOM);
 233  8
     unwantedCssAttributes.addElement(CSS.Attribute.MARGIN_LEFT);
 234  8
     unwantedCssAttributes.addElement(CSS.Attribute.PADDING_TOP);
 235  8
     unwantedCssAttributes.addElement(CSS.Attribute.PADDING_RIGHT);
 236  8
     unwantedCssAttributes.addElement(CSS.Attribute.PADDING_BOTTOM);
 237  8
     unwantedCssAttributes.addElement(CSS.Attribute.PADDING_LEFT);
 238   
   }
 239   
 
 240   
   /**
 241   
    * initialize the table of CSS attributes which need
 242   
    * adjustment before writing
 243   
    *
 244   
    * <p>This is built in as a workaround for bug id 4765271
 245   
    * (see http://developer.java.sun.com/developer/bugParade/bugs/4765271.html)</p>
 246   
    */
 247  8
   private void initAdjustCssAttributes() {
 248  8
     AdjustAction a = new AdjustAction();
 249  8
     adjustCssAttributes.put(CSS.Attribute.FONT_SIZE, a);
 250   
   }
 251   
 
 252   
   /**
 253   
    * Action to perform to adjust a given CSS attribute
 254   
    *
 255   
    * <p>This is built in as a workaround for bug id 4765271
 256   
    * (see http://developer.java.sun.com/developer/bugParade/bugs/4765271.html)</p>
 257   
    */
 258   
   private class AdjustAction extends AbstractAction {
 259   
 
 260   
     /**
 261   
      * constructor
 262   
      */
 263  8
     public AdjustAction() {
 264   
     }
 265   
 
 266   
     /**
 267   
      * actionListener implementation (unused here)
 268   
      */
 269  0
     public void actionPerformed(ActionEvent e) {
 270   
     }
 271   
 
 272   
     /**
 273   
      * get an adjusted value for a given CSS.Attribute
 274   
      *
 275   
      * @param key the CSS.Attribute this action is to adjust
 276   
      * @param val the value to adjust
 277   
      *
 278   
      * @return the adjusted value for the given CSS.Attribute
 279   
      */
 280  0
     public Object getNewValue(Object key, Object val) {
 281  0
       Object newVal = val;
 282  0
       if(mode == MODE_HTML) {
 283  0
         if(key.equals(CSS.Attribute.FONT_SIZE)) {
 284   
           //LengthValue lv = new LengthValue(val);
 285  0
           float aVal = Util.getAttrValue(val);
 286  0
           String unit = Util.getLastAttrUnit();
 287  0
           if(/*lv.getUnit().equalsIgnoreCase(LengthValue.pt)*/ unit.equalsIgnoreCase(Util.pt)) {
 288  0
             int newIntVal = (int) (aVal * 1.2f); // new Float(lv.getValue() * 1.2f).intValue();
 289  0
             newVal = new String(Integer.toString(newIntVal) + unit);
 290   
           }
 291   
         }
 292   
       }
 293  0
       return newVal;
 294   
     }
 295   
   }
 296   
 
 297   
   /**
 298   
    * set the document to write from
 299   
    *
 300   
    * @param doc the document to write from
 301   
    */
 302  0
   public void setDocument(SHTMLDocument doc) {
 303  0
     this.doc = doc;
 304   
   }
 305   
 
 306   
   /**
 307   
    * specify the text segment of the document to be written
 308   
    *
 309   
    * @param pos  the start position of the text segment
 310   
    * @param len  the length of the text segment
 311   
    */
 312  0
   public void setWriteSegment(int pos, int len) {
 313  0
     this.startOffset = pos;
 314  0
     this.endOffset = pos + len;
 315   
   }
 316   
 
 317   
   /**
 318   
    * invoke HTML creation for the document or part of document
 319   
    * given in the constructor of this SHTMLWriter
 320   
    *
 321   
    * @exception IOException on any i/o error
 322   
    * @exception BadLocationException if text retrieval via doc.getText is
 323   
    *            passed an invalid location within the document
 324   
    * @exception PropertiesMissingException if the document is not set prior
 325   
    *            to calling this method
 326   
    */
 327  0
   public void write() throws IOException, BadLocationException,
 328   
                                 PropertiesMissingException
 329   
   {
 330  0
     if((doc != null) && (endOffset > 0)) {
 331  0
       if(startOffset == 0) {
 332  0
         write(doc.getDefaultRootElement());
 333   
       }
 334   
       else {
 335  0
         write(doc.getParagraphElement(startOffset).getParentElement());
 336   
       }
 337   
     }
 338   
     else {
 339  0
       throw new PropertiesMissingException();
 340   
     }
 341   
   }
 342   
 
 343   
   /**
 344   
    * invoke HTML creation for a given element and all its children
 345   
    *
 346   
    * <p>This iterates through the element structure below the given
 347   
    * element by calling itself recursively for each branch element
 348   
    * found.</p>
 349   
    *
 350   
    * @param e  the element to generate HTML for
 351   
    *
 352   
    * @exception IOException on any i/o error
 353   
    * @exception BadLocationException if text retrieval via doc.getText is
 354   
    *            passed an invalid location within the document
 355   
    */
 356  4
   public void write(Element e) throws IOException, BadLocationException {
 357  4
     if (e.isLeaf()) {
 358  2
       inContent = true;
 359  2
       leafTag(e);
 360  2
       inContent = false;
 361   
     }
 362   
     else {
 363  2
       startTag(e);
 364  2
       int childCount = e.getElementCount();
 365  2
       int index = 0;
 366  2
       while (index < childCount) {
 367  2
         write(e.getElement(index)); // drill down in recursion
 368  2
         index++;
 369   
       }
 370  2
       endTag(e);
 371   
     }
 372   
   }
 373   
 
 374   
   /**
 375   
    * write an element and all its children. If a given element is reached,
 376   
    * writing stops with this element. If the end element is a leaf,
 377   
    * it is written as the last element, otherwise it is not written.
 378   
    *
 379   
    * @param e  the element to write including its children (if any)
 380   
    * @param end  the last leaf element to write or the branch element
 381   
    * to stop writing at (whatever applies)
 382   
    */
 383  0
   public void writeElementsUntil(Element e, Element end) throws IOException, BadLocationException
 384   
   {
 385  0
     if(e.isLeaf()) {
 386  0
       inContent = true;
 387  0
       leafTag(e);
 388  0
       inContent = false;
 389   
     }
 390   
     else {
 391  0
       if(e != end) {
 392  0
         startTag(e);
 393  0
         int childCount = e.getElementCount();
 394  0
         int index = 0;
 395  0
         while(index < childCount) {
 396  0
           writeElementsUntil(e.getElement(index), end); // drill down in recursion
 397  0
           index++;
 398   
         }
 399  0
         endTag(e);
 400   
       }
 401   
     }
 402   
   }
 403   
 
 404   
   /**
 405   
    * write elements and their children starting at a
 406   
    * given element until a given element is reached.
 407   
    * The end element is written as the last element,
 408   
    * if it is a leaf element.
 409   
    *
 410   
    * @param start  the element to start writing with
 411   
    * @param end  the last element to write
 412   
    */
 413  0
   public void write(Element start, Element end) throws IOException, BadLocationException
 414   
   {
 415  0
     Element parent = start.getParentElement();
 416  0
     int count = parent.getElementCount();
 417  0
     int i = 0;
 418  0
     Element e = parent.getElement(i);
 419  0
     while(i < count && e != start) {
 420  0
       e = parent.getElement(i++);
 421   
     }
 422  0
     while(i < count) {
 423  0
       writeElementsUntil(e, end);
 424  0
       e = parent.getElement(i++);
 425   
     }
 426   
   }
 427   
 
 428   
   /**
 429   
    * invoke HTML creation for all children of a given element.
 430   
    *
 431   
    * @param elem  the element which children are to be written as HTML
 432   
    */
 433  0
   public void writeChildElements(Element elem)
 434   
       throws IOException, BadLocationException, PropertiesMissingException
 435   
   {
 436  0
     Element para;
 437  0
     for(int i = 0; i < elem.getElementCount(); i++) {
 438  0
       para = elem.getElement(i);
 439  0
       write(para);
 440   
     }
 441   
   }
 442   
 
 443   
   /* --------- tag writing start -------------- */
 444   
 
 445   
   private char returnChar = '\r';
 446   
   private char newlineChar = '\n';
 447   
 
 448   
   /* HTML syntax constants */
 449   
   public static final char htmlTagStart = '<';
 450   
   public static final char htmlTagEnd = '>';
 451   
   public static final char htmlEndTagIndicator = '/';
 452   
   public static final char htmlAttributeSeparator = '=';
 453   
   public static final char htmlAttributeTerminator = ' ';
 454   
   public static final char htmlAttributeStartEnd = '\"';
 455   
 
 456   
   /* CSS syntax constants */
 457   
   public static final char cssAttributeTerminator = ';';
 458   
   public static final char cssAttributeSeparator = ':';
 459   
 
 460   
   /**
 461   
    * write out a start tag including tag attributes for a given element
 462   
    *
 463   
    * <p>This will not write start tags for implied elements. Indicator
 464   
    * inImpliedTag is set instead to signal subsequent methods that we are
 465   
    * inside an implied tag. Indicator inImpliedTag is cleared by method
 466   
    * endTag.</p>
 467   
    *
 468   
    * <p>CAUTION: This will not work for nested implied tags! Not sure yet,
 469   
    * if these can happen, watch out accordingly.</p>
 470   
    *
 471   
    * @param e  the element to generate a start tag for
 472   
    *
 473   
    * @exception IOException on any i/o error
 474   
    */
 475  2
   private void startTag(Element e) throws IOException {
 476  2
     AttributeSet a = e.getAttributes();
 477  2
     if(nameEquals(a, HTML.Tag.IMPLIED.toString())) {
 478  2
       inImpliedTag = true;
 479   
     }
 480   
     else {
 481  0
       startTag(e.getName(), a);
 482   
     }
 483   
   }
 484   
 
 485   
   /**
 486   
    * write out a start tag for a given tag name
 487   
    * including tag attributes (if any)
 488   
    *
 489   
    * @param name  the name of the tag to generate
 490   
    * @param a  the set of attributes to generate in tag or null if none
 491   
    *
 492   
    * @exception IOException on any i/o error
 493   
    */
 494  32
   public void startTag(String name, AttributeSet a) throws IOException {
 495  32
     if (!name.equalsIgnoreCase(HTML.Tag.A.toString())) {
 496  32
       w.write(newline);
 497  32
       indent();
 498   
     }
 499  32
     w.write(htmlTagStart);
 500  32
     w.write(name);
 501  32
     if (a != null) {
 502  26
       writeTagAttributes(a, false);
 503   
     }
 504  32
     w.write(htmlTagEnd);
 505  32
     increaseIndent();
 506   
   }
 507   
 
 508   
   /**
 509   
    * write a leaf tag
 510   
    *
 511   
    * leaf tags can have different content in the element
 512   
    * structure of a document
 513   
    *
 514   
    * 1. 'normal' leaf: the element name is CONTENT.
 515   
    *
 516   
    *    a) element has no attributes:
 517   
    *       only the text from the document is written.
 518   
    *
 519   
    *    b) element has attributes:
 520   
    *       a SPAN start tag is synthesized holding the attributes,
 521   
    *       then the text from the document is written and
 522   
    *       a SPAN end tag is written finally.
 523   
    *
 524   
    *    c) the leaf is the CONTENT element of an implied paragraph:
 525   
    *       nothing is written.
 526   
    *
 527   
    * 2. leaf holding a tag name for which no end tag is needed:
 528   
    *    the element name is other than CONTENT.
 529   
    *
 530   
    *    a start tag is generated for the element (endTag does not
 531   
    *    write an end tag for this startTag in this case).
 532   
    *
 533   
    * @exception IOException on any i/o error
 534   
    * @exception BadLocationException if text retrieval via doc.getText is
 535   
    *            passed an invalid location within the document.
 536   
    */
 537  2
   private void leafTag(Element e) throws IOException, BadLocationException {
 538   
     //System.out.println("SHTMLWriter.leafTag e= " + e.getName());
 539   
     // get the element attributes
 540  2
     AttributeSet a = e.getAttributes();
 541   
     // get value of name attribute and see if we are in content
 542  2
     if(nameEquals(a, HTML.Tag.CONTENT.toString())) {
 543   
       // we are in content but only write content, if not inside an implied tag
 544  2
       if(!inImpliedTag) {
 545   
         // get start and end position of text to write
 546  0
         int start = Math.max(startOffset, e.getStartOffset());
 547  0
         int end = Math.min(endOffset, e.getEndOffset());
 548  0
         if(end > start) {
 549   
           // element is inside the part of document to write, get the text
 550  0
           String text = doc.getText(start, end - start);
 551  0
           if(text != null) {
 552   
             // we have text, write attributes, if any
 553  0
             extractLink(a);
 554  0
             if(writeTagAttributes(a, true)) {
 555   
               // attributes written, write content text and...
 556  0
               writeContentText(text);
 557   
               // ..write SPAN end tag
 558  0
               decreaseIndent();
 559  0
               w.write(htmlTagStart);
 560  0
               w.write(htmlEndTagIndicator);
 561  0
               w.write(HTML.Tag.SPAN.toString());
 562  0
               w.write(htmlTagEnd);
 563  0
               if(inLink) {
 564   
                 // a link tag was found in attributes and written out, write end tag for it
 565  0
                 decreaseIndent();
 566  0
                 endTag(HTML.Tag.A.toString());
 567  0
                 inLink = false;
 568   
               }
 569   
             }
 570   
             else {
 571   
               // no style attributes written, just write content text
 572  0
               writeContentText(text);
 573  0
               if(inLink) {
 574   
                 // a link tag was found in attributes and written out, write end tag for it
 575  0
                 decreaseIndent();
 576  0
                 endTag(HTML.Tag.A.toString());
 577  0
                 inLink = false;
 578   
               }
 579   
             } // writeTagAttributes
 580   
           } // text != null
 581   
         } // end > start
 582   
       } // !inImpliedTag
 583   
     } // nameEquals content
 584   
     else {
 585   
       // not in content, write an implied startTag or endTag (if any)
 586  0
       if (!isEndtag(a)) {
 587  0
         extractLink(a);
 588  0
         startTag(e.getName(), a);
 589  0
         decreaseIndent();
 590   
       }
 591   
       else {
 592  0
         indent();
 593  0
         endTag(e.getName());
 594   
       }
 595   
     }
 596   
   }
 597   
 
 598   
   /**
 599   
    * test, if an attribute set contains another attribute set identifying a link
 600   
    * if yes, write a link tag with respective attributes
 601   
    *
 602   
    * @param a  the attribute set to look for a link attribute set
 603   
    *
 604   
    * @return true, when a link was written, false, if not
 605   
    */
 606  0
   private boolean extractLink(AttributeSet a) throws IOException {
 607  0
     Object key = a.getAttribute(HTML.Tag.A);
 608  0
     if(key != null) {
 609  0
       AttributeSet linkAttrs = (AttributeSet) key;
 610  0
       startTag(HTML.Tag.A.toString(), linkAttrs);
 611  0
       inLink = true;
 612   
     }
 613  0
     return inLink;
 614   
   }
 615   
 
 616   
   /**
 617   
    * determine by a given set of attributes whether or not the tag
 618   
    * the attributes belong to is an implied endtag.
 619   
    */
 620  0
   private boolean isEndtag(AttributeSet a) {
 621  0
     boolean hasEndtag = false;
 622  0
     if(a != null) {
 623  0
       Object endtag = a.getAttribute(HTML.Attribute.ENDTAG);
 624  0
       if(endtag != null) {
 625  0
         hasEndtag = true;
 626   
       }
 627   
     }
 628  0
     return hasEndtag;
 629   
   }
 630   
 
 631   
   /**
 632   
    * write out an end tag for a given element
 633   
    *
 634   
    * <p>This will not write an end tag if we are inside an implied tag.
 635   
    * Indicator inImpliedTag will be cleared in this case instead.</p>
 636   
    *
 637   
    * <p>CAUTION: This will not work for nested implied tags! Not sure yet,
 638   
    * if these can happen, watch out accordingly.</p>
 639   
    *
 640   
    * @param e  the element to generate an end tag for
 641   
    *
 642   
    * @exception IOException on any i/o error
 643   
    */
 644  2
   private void endTag(Element e) throws IOException {
 645  2
     if(inImpliedTag) {
 646  2
       inImpliedTag = false;
 647   
     }
 648   
     else {
 649  0
       decreaseIndent();
 650  0
       indent();
 651  0
       endTag(e.getName());
 652   
     }
 653   
   }
 654   
 
 655   
   /**
 656   
    * write out an end tag for a given tag name
 657   
    *
 658   
    * @param name  the name of the tag to generate an end tag for
 659   
    */
 660  31
   public void endTag(String name) throws IOException {
 661  31
     if(!inLink) {
 662  31
       w.write(newline);
 663  31
       indent();
 664   
     }
 665  31
     w.write(htmlTagStart);
 666  31
     w.write(htmlEndTagIndicator);
 667  31
     w.write(name);
 668  31
     w.write(htmlTagEnd);
 669   
   }
 670   
 
 671   
   /**
 672   
    * write attributes of a tag
 673   
    *
 674   
    * this first collects all attributes and generates HTML or
 675   
    * CSS code according to the type of attribute.
 676   
    *
 677   
    * Single members of CSS shorthand properties are
 678   
    * filtered out using method filterAttributes and Vector
 679   
    * unwantedCssAttributes. For these attributes CSS shorthand
 680   
    * properties are generated instead.
 681   
    *
 682   
    * It then writes out the attributes according to their type:
 683   
    *
 684   
    * a) if HTML attributes were generated, they are written as
 685   
    *    key/value pairs in HTML syntax following the HTML tag
 686   
    *    name.
 687   
    *
 688   
    * b) if CSS attributes were generated, they are written as
 689   
    *    key/value pairs in CSS syntax inside a STYLE attribute
 690   
    *    follwing the HTML tag name or a) above, whatever applies.
 691   
    *
 692   
    * If attributes were generated for a CONTENT tag, a) and b)
 693   
    * above are generated inside a synthesized SPAN start tag. Method
 694   
    * leafTag generates the SPAN end tag after writing the content
 695   
    * accordingly in this case.
 696   
    *
 697   
    * In stage 9 link handling is added: Links (tag A) are attributes
 698   
    * in Java instead of tags.
 699   
    *
 700   
    * @param a  set of attributes to write
 701   
    * @param inContent  true if we are in content (SPAN), false if not
 702   
    *
 703   
    * @exception IOException on any i/o error
 704   
    */
 705  26
   private boolean writeTagAttributes(AttributeSet a, boolean inContent) throws IOException {
 706  26
     boolean wroteAttributes = false;
 707  26
     a = new AttributeMapper(a).getMappedAttributes(AttributeMapper.toHTML);
 708  26
     StringBuffer style = new StringBuffer();
 709  26
     StringBuffer html = new StringBuffer();
 710  26
     splitToAttrBuffers(a, html, style);
 711  26
     writeShortHandProperty(style, CSS.Attribute.BORDER_WIDTH, a);
 712  26
     writeShortHandProperty(style, CSS.Attribute.MARGIN, a);
 713  26
     writeShortHandProperty(style, CSS.Attribute.PADDING, a);
 714  26
     if ( ( (html.length() > 0) || (style.length() > 0)) && (inContent)) {
 715  0
       if (!inLink && firstContent) {
 716  0
         w.write(newline);
 717  0
         indent();
 718  0
         firstContent = false;
 719   
       }
 720  0
       indent();
 721  0
       w.write(htmlTagStart);
 722  0
       w.write(HTML.Tag.SPAN.toString());
 723   
     }
 724  26
     if (html.length() > 0) {
 725  14
       w.write(html.toString());
 726  14
       wroteAttributes = true;
 727   
     }
 728  26
     if (style.length() > 0) {
 729  22
       w.write(htmlAttributeTerminator);
 730  22
       w.write(HTML.Attribute.STYLE.toString());
 731  22
       w.write(htmlAttributeSeparator);
 732  22
       w.write(htmlAttributeStartEnd);
 733  22
       w.write(style.toString());
 734  22
       w.write(htmlAttributeStartEnd);
 735  22
       wroteAttributes = true;
 736   
     }
 737  26
     if ( ( (html.length() > 0) || (style.length() > 0)) && (inContent)) {
 738  0
       w.write(htmlTagEnd);
 739  0
       increaseIndent();
 740   
     }
 741  26
     return wroteAttributes;
 742   
   }
 743   
 
 744   
   /**
 745   
    * split an attribute set into CSS and HTML attributes
 746   
    */
 747  26
   private void splitToAttrBuffers(AttributeSet a, StringBuffer html, StringBuffer style) throws IOException {
 748  26
     Enumeration keys = a.getAttributeNames();
 749  26
     while(keys.hasMoreElements()) {
 750  140
       Object key = keys.nextElement();
 751  140
       if(key != null) {
 752  140
         Object val = a.getAttribute(key);
 753  140
         if (val != null) {
 754  140
           if (key instanceof CSS.Attribute) {
 755  114
             filterCSSAttributes(style, key, val);
 756   
           }
 757  26
           else if (key instanceof HTML.Attribute) {
 758  17
             html.append(htmlAttributeTerminator);
 759  17
             html.append(key.toString());
 760  17
             html.append(htmlAttributeSeparator);
 761  17
             html.append(htmlAttributeStartEnd);
 762  17
             html.append(val.toString());
 763  17
             html.append(htmlAttributeStartEnd);
 764   
           }
 765   
         }
 766   
       }
 767   
     }
 768   
   }
 769   
 
 770   
   /**
 771   
    * write out a given CSS shorthand property to a given output buffer
 772   
    *
 773   
    * <p>This only writes a CSS shorthand property if the given set
 774   
    * of attributes contains members for the given CSS shorthand property</p>
 775   
    *
 776   
    * @param buf  the output buffer to write to
 777   
    * @param key  the CSS shorthand property key to write
 778   
    * @param a  the set of attributes to get the members for
 779   
    *    the given CSS shorthand property
 780   
    */
 781  78
   private void writeShortHandProperty(StringBuffer buf, Object key, AttributeSet a) {
 782  78
     CombinedAttribute ca = new CombinedAttribute(key, a, false);
 783  78
     if(!ca.isEmpty()) {
 784  22
       writeCssAttribute(buf, key, ca.getAttribute());
 785   
     }
 786   
   }
 787   
 
 788   
   /**
 789   
    * only add CSS attributes to a given output buffer if they are not
 790   
    * part of the table of CSS attributes which shall not be
 791   
    * written out when encountered inside a document.
 792   
    *
 793   
    * @param buf  the output buffer to write attributes to
 794   
    * @param key  the attribute key to filter
 795   
    * @param val  the attribute value to write
 796   
    */
 797  114
   private void filterCSSAttributes(StringBuffer buf, Object key, Object val) {
 798  114
     if(!isUnwanted(key)) {
 799  26
       if(needsAdjustment(key)) {
 800  0
         AdjustAction a = (AdjustAction) adjustCssAttributes.get(key);
 801  0
         Object newVal = a.getNewValue(key, val);
 802  0
         writeCssAttribute(buf, key, newVal);
 803   
       }
 804   
       else {
 805  26
         writeCssAttribute(buf, key, val);
 806   
       }
 807   
     }
 808   
   }
 809   
 
 810   
   /**
 811   
    * write a given CSS attribute to a given output buffer
 812   
    *
 813   
    * @param buf  the output buffer to write to
 814   
    * @param key  the CSS attribute key to write
 815   
    * @param val  the value of the attribute to write
 816   
    */
 817  48
   private void writeCssAttribute(StringBuffer buf, Object key, Object val) {
 818  48
     if(buf.length() > 0) {
 819  26
       buf.append(htmlAttributeTerminator);
 820   
     }
 821  48
     buf.append(key.toString());
 822  48
     buf.append(cssAttributeSeparator);
 823  48
     buf.append(val.toString());
 824  48
     buf.append(cssAttributeTerminator);
 825   
   }
 826   
 
 827   
   /**
 828   
    * find out whether or not a given attribute key is part of the
 829   
    * table of CSS attributes which shall not be
 830   
    * written out.
 831   
    *
 832   
    * @param key  the attribute key to check
 833   
    *
 834   
    * @return true if the attribute key is unwanted, false if not
 835   
    */
 836  114
   private boolean isUnwanted(Object key) {
 837  114
     boolean unwanted = false;
 838  114
     int i = 0;
 839  114
     int count = unwantedCssAttributes.size();
 840  114
     while(!unwanted && i < count) {
 841  676
       unwanted = key.equals(unwantedCssAttributes.elementAt(i));
 842  676
       i++;
 843   
     }
 844  114
     return unwanted;
 845   
   }
 846   
 
 847   
   /**
 848   
    * check whether or not a given CSS.Attribute needs adjustment
 849   
    *
 850   
    * <p>This is built in as a workaround for bug id 4765271
 851   
    * (see http://developer.java.sun.com/developer/bugParade/bugs/4765271.html)</p>
 852   
    *
 853   
    * @param key the CSS.Attribute to check
 854   
    */
 855  26
   private boolean needsAdjustment(Object key) {
 856  26
     boolean adjust = false;
 857  26
     if(mode == MODE_JAVA) {
 858  26
       int i = 0;
 859  26
       int count = adjustCssAttributes.size();
 860  26
       Object[] keys = adjustCssAttributes.keySet().toArray();
 861  26
       while(!adjust && i < count) {
 862  26
         adjust = key.equals(keys[i]);
 863  26
         i++;
 864   
       }
 865   
     }
 866  26
     return adjust;
 867   
   }
 868   
 
 869   
   /* -------- tag writing end ------------------- */
 870   
 
 871   
   /* -------- tag writing help methods start ------------------- */
 872   
 
 873   
   /**
 874   
    * Returns true if the StyleConstants.NameAttribute is
 875   
    * equal to name that is passed in as a parameter.
 876   
    *
 877   
    * @param attr  the set of attributes to look for a name
 878   
    * @param name  the name to look for
 879   
    *
 880   
    * @return  true if the set of attributes has a name attribute equal to
 881   
    * the name in question, false if not
 882   
    */
 883  4
   private boolean nameEquals(AttributeSet attr, String name) {
 884  4
     boolean found = false;
 885  4
     Object value = attr.getAttribute(StyleConstants.NameAttribute);
 886  4
     if(value != null) {
 887  4
       found = value.toString().equalsIgnoreCase(name);
 888   
     }
 889  4
     return found;
 890   
   }
 891   
 
 892   
   /**
 893   
    * write the text found as content and append a new line
 894   
    * if none is found in the content text.
 895   
    *
 896   
    * @param text  the content text to write
 897   
    */
 898  0
   private void writeContentText(String text) throws IOException {
 899  0
     transformSpecialChars(text.toCharArray());
 900   
     //if(!hasNewLine(text)) {
 901   
       //System.out.println("writeContentText newline");
 902   
       //w.write(newline);
 903   
     //}
 904   
   }
 905   
 
 906   
   /**
 907   
    * transform special characters in content text by replacing certain
 908   
    * characters with their HTML equivalents before actually
 909   
    * writing them out.
 910   
    *
 911   
    * @param text  the uncleaned content text to write
 912   
    *
 913   
    * @exception IOException on any i/o error
 914   
    */
 915  0
   private void transformSpecialChars(char[] text) throws IOException {
 916  0
     int last = 0;
 917  0
     int length = text.length;
 918  0
     for(int i = 0; i < text.length; i++) {
 919  0
       switch(text[i]) {
 920  0
       case '<':
 921  0
         if (i > last) {
 922  0
           w.write(text, last, i - last);
 923   
         }
 924  0
         last = i + 1;
 925  0
         w.write("&lt;");
 926  0
         break;
 927  0
       case '>':
 928  0
         if (i > last) {
 929  0
           w.write(text, last, i - last);
 930   
         }
 931  0
         last = i + 1;
 932  0
         w.write("&gt;");
 933  0
         break;
 934  0
       case '&':
 935  0
         if (i > last) {
 936  0
           w.write(text, last, i - last);
 937   
         }
 938  0
         last = i + 1;
 939  0
         w.write("&amp;");
 940  0
         break;
 941  0
       case '"':
 942  0
         if (i > last) {
 943  0
           w.write(text, last, i - last);
 944   
         }
 945  0
         last = i + 1;
 946  0
         w.write("&quot;");
 947  0
         break;
 948  0
       case '\n':
 949  0
         if (i > last) {
 950  0
           w.write(text, last, i - last);
 951   
         }
 952  0
         last = i + 1;
 953  0
         break;
 954  0
       case '\t':
 955  0
       case '\r':
 956  0
         break;
 957  0
       default:
 958  0
         if (text[i] < ' ' || text[i] > 127) {
 959  0
           if (i > last) {
 960  0
             w.write(text, last, i - last);
 961   
           }
 962  0
           last = i + 1;
 963  0
           w.write("&#");
 964  0
           w.write(String.valueOf((int) text[i]));
 965  0
           w.write(";");
 966   
         }
 967  0
         break;
 968   
       }
 969   
     }
 970  0
     if (last < length) {
 971  0
       w.write(text, last, length - last);
 972   
     }
 973   
   }
 974   
 
 975   
   /**
 976   
    * test whether or not a given string contains newline chars
 977   
    *
 978   
    * @param text  the string to look for newline chars
 979   
    *
 980   
    * @return true, if the string contains newline chars, false if not
 981   
    */
 982  0
   private boolean hasNewLine(String text) {
 983  0
     int rPos = text.indexOf(returnChar);
 984  0
     int nPos = text.indexOf(newlineChar);
 985  0
     return ((rPos > -1) || (nPos > -1));
 986   
     //return (text.indexOf(newline) > -1);
 987   
   }
 988   
 
 989   
   /* -------- tag writing help methods end ------------------- */
 990   
 
 991   
   /* -------- indent handling start ------------- */
 992   
 
 993   
   private char[] indentChars;
 994   
   private int indentLevel = 0;
 995   
   private int indentSpace = 2;
 996   
 
 997   
   /**
 998   
    * decrement the indent level.
 999   
    */
 1000  0
   private void decreaseIndent() {
 1001  0
     --indentLevel;
 1002   
   }
 1003   
 
 1004   
   /**
 1005   
    * increment the indent level.
 1006   
    */
 1007  32
   private void increaseIndent() {
 1008  32
     ++indentLevel;
 1009   
   }
 1010   
 
 1011   
   /**
 1012   
    * write spaces according to the current indentLevel.
 1013   
    *
 1014   
    * The number of spaces written is determined by the
 1015   
    * amount of spaces per indent step multiplied by the
 1016   
    * level of indentation.
 1017   
    *
 1018   
    * Indent spaces are generated in a char[] as needed.
 1019   
    *
 1020   
    * @exception IOException on any i/o error
 1021   
    */
 1022  63
   private void indent() throws IOException {
 1023  63
     if(!inContent || firstContent) {
 1024  63
       int max = indentLevel * indentSpace;
 1025  63
       if (indentChars == null || max > indentChars.length) {
 1026  39
         indentChars = new char[max];
 1027  39
         for (int i = 0; i < max; i++) {
 1028  224
           indentChars[i] = ' ';
 1029   
         }
 1030   
       }
 1031  63
       w.write(indentChars, 0, max /*Math.max(0, max)*/);
 1032   
     }
 1033   
   }
 1034   
 
 1035   
   /* -------- indent handling end --------------- */
 1036   
 
 1037   
   /**
 1038   
    * inner class for signaling that properties were missing for
 1039   
    * proper execution.
 1040   
    */
 1041   
   public class PropertiesMissingException extends Exception {
 1042   
     /**
 1043   
      * Constructs a <code>WriterPropertiesMissingException</code> with
 1044   
      * <code>null</code> as its error detail message.
 1045   
      */
 1046  0
     public PropertiesMissingException() {
 1047  0
       super();
 1048   
     }
 1049   
 
 1050   
     /**
 1051   
      * Constructs an <code>WriterPropertiesMissingException</code> with
 1052   
      * the specified detail message. The error message string <code>s</code>
 1053   
      * can later be retrieved by the <code>{@link java.lang.Throwable#getMessage}</code>
 1054   
      * method of class <code>java.lang.Throwable</code>.
 1055   
      *
 1056   
      * @param   s   the detail message.
 1057   
      */
 1058  0
     public PropertiesMissingException(String s) {
 1059  0
       super(s);
 1060   
     }
 1061   
   }
 1062   
 }