001/* MinimalHTMLWriter.java -- 
002   Copyright (C) 2006 Free Software Foundation, Inc.
003
004This file is part of GNU Classpath.
005
006GNU Classpath is free software; you can redistribute it and/or modify
007it under the terms of the GNU General Public License as published by
008the Free Software Foundation; either version 2, or (at your option)
009any later version.
010
011GNU Classpath is distributed in the hope that it will be useful, but
012WITHOUT ANY WARRANTY; without even the implied warranty of
013MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014General Public License for more details.
015
016You should have received a copy of the GNU General Public License
017along with GNU Classpath; see the file COPYING.  If not, write to the
018Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
01902110-1301 USA.
020
021Linking this library statically or dynamically with other modules is
022making a combined work based on this library.  Thus, the terms and
023conditions of the GNU General Public License cover the whole
024combination.
025
026As a special exception, the copyright holders of this library give you
027permission to link this library with independent modules to produce an
028executable, regardless of the license terms of these independent
029modules, and to copy and distribute the resulting executable under
030terms of your choice, provided that you also meet, for each linked
031independent module, the terms and conditions of the license of that
032module.  An independent module is a module which is not derived from
033or based on this library.  If you modify this library, you may extend
034this exception to your version of the library, but you are not
035obligated to do so.  If you do not wish to do so, delete this
036exception statement from your version. */
037
038package javax.swing.text.html;
039
040import javax.swing.text.AttributeSet;
041import javax.swing.text.AbstractWriter;
042import javax.swing.text.BadLocationException;
043import javax.swing.text.DefaultStyledDocument;
044import javax.swing.text.Element;
045import javax.swing.text.ElementIterator;
046import javax.swing.text.StyleConstants;
047import javax.swing.text.Style;
048import javax.swing.text.StyledDocument;
049import java.io.Writer;
050import java.io.IOException;
051import java.util.Enumeration;
052import java.util.Stack;
053import java.awt.Color;
054
055/**
056 * MinimalHTMLWriter,
057 * A minimal AbstractWriter implementation for HTML.
058 *
059 * @author Sven de Marothy
060 */
061public class MinimalHTMLWriter extends AbstractWriter 
062{
063  private StyledDocument doc;
064  private Stack tagStack;
065  private boolean inFontTag = false;
066
067  /**
068   * Constructs a MinimalHTMLWriter.
069   * @param w - a Writer, for output.
070   * @param doc - the document
071   */
072  public MinimalHTMLWriter(Writer w, StyledDocument doc)
073  {
074    super(w, doc);
075    this.doc = doc;
076    tagStack = new Stack();
077  }
078
079  /**
080   * Constructs a MinimalHTMLWriter.
081   * @param w - a Writer, for output.
082   * @param doc - the document
083   * @param pos - start position
084   * @param len - length
085   */
086  public MinimalHTMLWriter(Writer w, StyledDocument doc, int pos, int len)
087  {
088    super(w, doc, pos, len);
089    this.doc = doc;
090    tagStack = new Stack();
091  }
092
093  /**
094   * Starts a span tag.
095   */
096  protected void startFontTag(String style) throws IOException
097  {
098    if( inFontTag() )
099      endOpenTags();
100    writeStartTag("<span style=\""+style+"\">");
101    inFontTag = true;
102  }
103
104  /**
105   * Returns whether the writer is within two span tags.
106   */
107  protected boolean inFontTag()
108  {
109    return inFontTag;
110  }
111
112  /**
113   * Ends a span tag.
114   */
115  protected void endFontTag() throws IOException
116  {    
117    writeEndTag("</span>");
118    inFontTag = false;
119  }
120
121  /**
122   * Write the entire HTML document.
123   */
124  public synchronized void write() throws IOException, BadLocationException
125  {
126    writeStartTag("<html>");
127    writeHeader();
128    writeBody();
129    writeEndTag("</html>");
130  }
131
132  /**
133   * Write a start tag and increment the indent.
134   */
135  protected void writeStartTag(String tag) throws IOException
136  {
137    indent();
138    write(tag+NEWLINE);
139    incrIndent();
140  }
141
142  /**
143   * Write an ending tag and decrement the indent.
144   */
145  protected void writeEndTag(String endTag) throws IOException
146  {
147    decrIndent();
148    indent();
149    write(endTag+NEWLINE);
150  }
151
152  /**
153   * Write the HTML header.
154   */
155  protected void writeHeader() throws IOException 
156  {
157    writeStartTag("<head>");
158    writeStartTag("<style>");
159    writeStartTag("<!--");
160    writeStyles();
161    writeEndTag("-->");
162    writeEndTag("</style>");
163    writeEndTag("</head>");
164  }
165
166  /**
167   * Write a paragraph start tag.
168   */
169  protected void writeStartParagraph(Element elem) throws IOException
170  {      
171    indent();
172    write("<p class=default>"+NEWLINE); // FIXME: Class value = ?
173    incrIndent();
174  }
175
176  /**
177   * Write a paragraph end tag, closes any other open tags.
178   */
179  protected void writeEndParagraph() throws IOException
180  {
181    endOpenTags();
182    writeEndTag("</p>");
183  }
184
185  /**
186   * Writes the body of the HTML document.
187   */ 
188  protected void writeBody() throws IOException, BadLocationException
189  {
190    writeStartTag("<body>");
191
192    ElementIterator ei = getElementIterator();
193    Element e = ei.first();
194    boolean inParagraph = false;
195    do
196      {
197        if( e.isLeaf() )
198          {
199            boolean hasNL = (getText(e).indexOf(NEWLINE) != -1);
200            if( !inParagraph && hasText( e ) )
201              {
202                writeStartParagraph(e);
203                inParagraph = true;
204              }
205
206            if( hasText( e ) )
207              writeContent(e, true);
208
209            if( hasNL && inParagraph )
210              {
211                writeEndParagraph();
212                inParagraph = false;
213              }
214            else
215              endOpenTags();
216          }
217      } 
218    while((e = ei.next()) != null);
219
220    writeEndTag("</body>");
221  }
222
223  protected void text(Element elem) throws IOException, BadLocationException
224  {
225    write( getText(elem).trim() );
226  }
227
228  /**
229   * Write bold, indent and underline tags.
230   */
231  protected void writeHTMLTags(AttributeSet attr) throws IOException
232  {
233    if(attr.getAttribute(StyleConstants.Bold) != null)
234      if(((Boolean)attr.getAttribute(StyleConstants.Bold)).booleanValue())
235        {
236          write("<b>");
237          tagStack.push("</b>");
238        }
239    if(attr.getAttribute(StyleConstants.Italic) != null)
240      if(((Boolean)attr.getAttribute(StyleConstants.Italic)).booleanValue())
241        {
242          write("<i>");
243          tagStack.push("</i>");
244        }
245    if(attr.getAttribute(StyleConstants.Underline) != null)
246      if(((Boolean)attr.getAttribute(StyleConstants.Underline)).booleanValue())
247        {
248          write("<u>");
249          tagStack.push("</u>");
250        }
251  }
252
253  /**
254   * Returns whether the element contains text or not.
255   */
256  protected boolean isText(Element elem) 
257  {
258    return (elem.getEndOffset() != elem.getStartOffset());
259  }
260
261  /**
262   * Writes the content of an element.
263   */
264  protected void writeContent(Element elem, boolean needsIndenting)
265    throws IOException, BadLocationException
266  {
267    writeNonHTMLAttributes(elem.getAttributes());
268    if(needsIndenting)
269      indent();
270    writeHTMLTags(elem.getAttributes());
271    if( isText(elem) )
272      text(elem);
273    else 
274      writeLeaf(elem);
275
276    endOpenTags();
277  }
278
279  /**
280   * Writes a non-text leaf element.
281   */
282  protected void writeLeaf(Element e) throws IOException
283  {
284    // NOTE: Haven't tested if this is correct.
285    if(e.getName().equals(StyleConstants.IconElementName))
286      writeImage(e);
287    else 
288      writeComponent(e);
289  }
290
291  /**
292   * Write the HTML attributes which do not have tag equivalents, 
293   * e.g. attributes other than bold/italic/underlined.
294   */
295  protected void writeNonHTMLAttributes(AttributeSet attr) throws IOException
296  {
297    String style = "";
298
299    // Alignment? Background?
300
301    if( StyleConstants.getForeground(attr) != null )
302      style = style + "color: " + 
303        getColor(StyleConstants.getForeground(attr)) + "; ";
304
305    style = style + "font-size: "+StyleConstants.getFontSize(attr)+"pt; ";
306    style = style + "font-family: "+StyleConstants.getFontFamily(attr);
307
308    startFontTag(style);
309  }
310
311  /**
312   * Write the styles used.
313   */
314  protected void writeStyles() throws IOException
315  {
316    if(doc instanceof DefaultStyledDocument)
317      {
318        Enumeration styles = ((DefaultStyledDocument)doc).getStyleNames();
319        while(styles.hasMoreElements())
320          writeStyle(doc.getStyle((String)styles.nextElement()));
321      }
322    else
323      { // What else to do here?
324        Style s = doc.getStyle("default");
325        if(s != null)
326          writeStyle( s );
327      }
328  }
329
330  /**
331   * Write a set of attributes.
332   */
333  protected void writeAttributes(AttributeSet attr) throws IOException
334  {
335    Enumeration attribs = attr.getAttributeNames();
336    while(attribs.hasMoreElements())
337      {
338        Object attribName = attribs.nextElement();
339        String name = attribName.toString();
340        String output = getAttribute(name, attr.getAttribute(attribName));
341        if( output != null )
342          {
343            indent();
344            write( output + NEWLINE );
345          }
346      }
347  }
348
349  /**
350   * Deliberately unimplemented, handles component elements.
351   */ 
352  protected void writeComponent(Element elem) throws IOException
353  {
354  }
355
356  /**
357   * Deliberately unimplemented. 
358   * Writes StyleConstants.IconElementName elements.
359   */ 
360  protected void writeImage(Element elem) throws IOException
361  {
362  }
363
364  // -------------------- Private methods. --------------------------------
365
366  /**
367   * Write a single style attribute
368   */
369  private String getAttribute(String name, Object a) throws IOException
370  {
371    if(name.equals("foreground"))
372      return "foreground:"+getColor((Color)a)+";";
373    if(name.equals("background"))
374      return "background:"+getColor((Color)a)+";";
375    if(name.equals("italic"))
376      return "italic:"+(((Boolean)a).booleanValue() ? "italic;" : ";");
377    if(name.equals("bold"))
378      return "bold:"+(((Boolean)a).booleanValue() ? "bold;" : "normal;");
379    if(name.equals("family"))
380      return "family:" + a + ";";
381    if(name.equals("size"))
382      {
383        int size = ((Integer)a).intValue();
384        int htmlSize;
385        if( size > 24 )
386          htmlSize = 7;
387        else if( size > 18 )
388          htmlSize = 6;
389        else if( size > 14 )
390          htmlSize = 5;
391        else if( size > 12 )
392          htmlSize = 4;
393        else if( size > 10 )
394          htmlSize = 3;
395        else if( size > 8 )
396          htmlSize = 2;
397        else
398          htmlSize = 1;
399
400        return "size:" + htmlSize + ";";
401      }
402
403    return null;
404  }
405
406  /**
407   * Stupid that Color doesn't have a method for this.
408   */
409  private String getColor(Color c)
410  {
411    String r = "00" + Integer.toHexString(c.getRed());
412    r = r.substring(r.length() - 2);
413    String g = "00" + Integer.toHexString(c.getGreen());
414    g = g.substring(g.length() - 2);
415    String b = "00" + Integer.toHexString(c.getBlue());
416    b = b.substring(b.length() - 2);
417    return "#" + r + g + b;
418  }
419
420  /**
421   * Empty the stack of open tags
422   */
423  private void endOpenTags() throws IOException
424  {
425    while(!tagStack.empty())
426      write((String)tagStack.pop());
427
428    if( inFontTag() )
429      {
430        write(""+NEWLINE);
431        endFontTag();
432      }
433  }
434
435  /**
436   * Output a single style
437   */
438  private void writeStyle(Style s) throws IOException
439  {
440    if( s == null )
441      return;
442
443    writeStartTag("p."+s.getName()+" {");
444    writeAttributes(s);
445    writeEndTag("}");
446  }
447
448  private boolean hasText(Element e) throws BadLocationException
449  {
450    return (getText(e).trim().length() > 0);
451  }
452}