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.ArrayDeque;
052import java.util.Deque;
053import java.util.Enumeration;
054import java.awt.Color;
055
056/**
057 * MinimalHTMLWriter,
058 * A minimal AbstractWriter implementation for HTML.
059 *
060 * @author Sven de Marothy
061 */
062public class MinimalHTMLWriter extends AbstractWriter
063{
064  private StyledDocument doc;
065  private Deque<String> tagStack;
066  private boolean inFontTag = false;
067
068  /**
069   * Constructs a MinimalHTMLWriter.
070   * @param w - a Writer, for output.
071   * @param doc - the document
072   */
073  public MinimalHTMLWriter(Writer w, StyledDocument doc)
074  {
075    super(w, doc);
076    this.doc = doc;
077    tagStack = new ArrayDeque<String>();
078  }
079
080  /**
081   * Constructs a MinimalHTMLWriter.
082   * @param w - a Writer, for output.
083   * @param doc - the document
084   * @param pos - start position
085   * @param len - length
086   */
087  public MinimalHTMLWriter(Writer w, StyledDocument doc, int pos, int len)
088  {
089    super(w, doc, pos, len);
090    this.doc = doc;
091    tagStack = new ArrayDeque<String>();
092  }
093
094  /**
095   * Starts a span tag.
096   */
097  protected void startFontTag(String style) throws IOException
098  {
099    if( inFontTag() )
100      endOpenTags();
101    writeStartTag("<span style=\""+style+"\">");
102    inFontTag = true;
103  }
104
105  /**
106   * Returns whether the writer is within two span tags.
107   */
108  protected boolean inFontTag()
109  {
110    return inFontTag;
111  }
112
113  /**
114   * Ends a span tag.
115   */
116  protected void endFontTag() throws IOException
117  {
118    writeEndTag("</span>");
119    inFontTag = false;
120  }
121
122  /**
123   * Write the entire HTML document.
124   */
125  public synchronized void write() throws IOException, BadLocationException
126  {
127    writeStartTag("<html>");
128    writeHeader();
129    writeBody();
130    writeEndTag("</html>");
131  }
132
133  /**
134   * Write a start tag and increment the indent.
135   */
136  protected void writeStartTag(String tag) throws IOException
137  {
138    indent();
139    write(tag+NEWLINE);
140    incrIndent();
141  }
142
143  /**
144   * Write an ending tag and decrement the indent.
145   */
146  protected void writeEndTag(String endTag) throws IOException
147  {
148    decrIndent();
149    indent();
150    write(endTag+NEWLINE);
151  }
152
153  /**
154   * Write the HTML header.
155   */
156  protected void writeHeader() throws IOException
157  {
158    writeStartTag("<head>");
159    writeStartTag("<style>");
160    writeStartTag("<!--");
161    writeStyles();
162    writeEndTag("-->");
163    writeEndTag("</style>");
164    writeEndTag("</head>");
165  }
166
167  /**
168   * Write a paragraph start tag.
169   */
170  protected void writeStartParagraph(Element elem) throws IOException
171  {
172    indent();
173    write("<p class=default>"+NEWLINE); // FIXME: Class value = ?
174    incrIndent();
175  }
176
177  /**
178   * Write a paragraph end tag, closes any other open tags.
179   */
180  protected void writeEndParagraph() throws IOException
181  {
182    endOpenTags();
183    writeEndTag("</p>");
184  }
185
186  /**
187   * Writes the body of the HTML document.
188   */
189  protected void writeBody() throws IOException, BadLocationException
190  {
191    writeStartTag("<body>");
192
193    ElementIterator ei = getElementIterator();
194    Element e = ei.first();
195    boolean inParagraph = false;
196    do
197      {
198        if( e.isLeaf() )
199          {
200            boolean hasNL = (getText(e).indexOf(NEWLINE) != -1);
201            if( !inParagraph && hasText( e ) )
202              {
203                writeStartParagraph(e);
204                inParagraph = true;
205              }
206
207            if( hasText( e ) )
208              writeContent(e, true);
209
210            if( hasNL && inParagraph )
211              {
212                writeEndParagraph();
213                inParagraph = false;
214              }
215            else
216              endOpenTags();
217          }
218      }
219    while((e = ei.next()) != null);
220
221    writeEndTag("</body>");
222  }
223
224  protected void text(Element elem) throws IOException, BadLocationException
225  {
226    write( getText(elem).trim() );
227  }
228
229  /**
230   * Write bold, indent and underline tags.
231   */
232  protected void writeHTMLTags(AttributeSet attr) throws IOException
233  {
234    if(attr.getAttribute(StyleConstants.Bold) != null)
235      if(((Boolean)attr.getAttribute(StyleConstants.Bold)).booleanValue())
236        {
237          write("<b>");
238          tagStack.push("</b>");
239        }
240    if(attr.getAttribute(StyleConstants.Italic) != null)
241      if(((Boolean)attr.getAttribute(StyleConstants.Italic)).booleanValue())
242        {
243          write("<i>");
244          tagStack.push("</i>");
245        }
246    if(attr.getAttribute(StyleConstants.Underline) != null)
247      if(((Boolean)attr.getAttribute(StyleConstants.Underline)).booleanValue())
248        {
249          write("<u>");
250          tagStack.push("</u>");
251        }
252  }
253
254  /**
255   * Returns whether the element contains text or not.
256   */
257  protected boolean isText(Element elem)
258  {
259    return (elem.getEndOffset() != elem.getStartOffset());
260  }
261
262  /**
263   * Writes the content of an element.
264   */
265  protected void writeContent(Element elem, boolean needsIndenting)
266    throws IOException, BadLocationException
267  {
268    writeNonHTMLAttributes(elem.getAttributes());
269    if(needsIndenting)
270      indent();
271    writeHTMLTags(elem.getAttributes());
272    if( isText(elem) )
273      text(elem);
274    else
275      writeLeaf(elem);
276
277    endOpenTags();
278  }
279
280  /**
281   * Writes a non-text leaf element.
282   */
283  protected void writeLeaf(Element e) throws IOException
284  {
285    // NOTE: Haven't tested if this is correct.
286    if(e.getName().equals(StyleConstants.IconElementName))
287      writeImage(e);
288    else
289      writeComponent(e);
290  }
291
292  /**
293   * Write the HTML attributes which do not have tag equivalents,
294   * e.g. attributes other than bold/italic/underlined.
295   */
296  protected void writeNonHTMLAttributes(AttributeSet attr) throws IOException
297  {
298    String style = "";
299
300    // Alignment? Background?
301
302    if( StyleConstants.getForeground(attr) != null )
303      style = style + "color: " +
304        getColor(StyleConstants.getForeground(attr)) + "; ";
305
306    style = style + "font-size: "+StyleConstants.getFontSize(attr)+"pt; ";
307    style = style + "font-family: "+StyleConstants.getFontFamily(attr);
308
309    startFontTag(style);
310  }
311
312  /**
313   * Write the styles used.
314   */
315  protected void writeStyles() throws IOException
316  {
317    if(doc instanceof DefaultStyledDocument)
318      {
319        Enumeration<?> styles = ((DefaultStyledDocument)doc).getStyleNames();
320        while(styles.hasMoreElements())
321          writeStyle(doc.getStyle((String)styles.nextElement()));
322      }
323    else
324      { // What else to do here?
325        Style s = doc.getStyle("default");
326        if(s != null)
327          writeStyle( s );
328      }
329  }
330
331  /**
332   * Write a set of attributes.
333   */
334  protected void writeAttributes(AttributeSet attr) throws IOException
335  {
336    Enumeration<?> attribs = attr.getAttributeNames();
337    while(attribs.hasMoreElements())
338      {
339        Object attribName = attribs.nextElement();
340        String name = attribName.toString();
341        String output = getAttribute(name, attr.getAttribute(attribName));
342        if( output != null )
343          {
344            indent();
345            write( output + NEWLINE );
346          }
347      }
348  }
349
350  /**
351   * Deliberately unimplemented, handles component elements.
352   */
353  protected void writeComponent(Element elem) throws IOException
354  {
355  }
356
357  /**
358   * Deliberately unimplemented.
359   * Writes StyleConstants.IconElementName elements.
360   */
361  protected void writeImage(Element elem) throws IOException
362  {
363  }
364
365  // -------------------- Private methods. --------------------------------
366
367  /**
368   * Write a single style attribute
369   */
370  private String getAttribute(String name, Object a) throws IOException
371  {
372    if(name.equals("foreground"))
373      return "foreground:"+getColor((Color)a)+";";
374    if(name.equals("background"))
375      return "background:"+getColor((Color)a)+";";
376    if(name.equals("italic"))
377      return "italic:"+(((Boolean)a).booleanValue() ? "italic;" : ";");
378    if(name.equals("bold"))
379      return "bold:"+(((Boolean)a).booleanValue() ? "bold;" : "normal;");
380    if(name.equals("family"))
381      return "family:" + a + ";";
382    if(name.equals("size"))
383      {
384        int size = ((Integer)a).intValue();
385        int htmlSize;
386        if( size > 24 )
387          htmlSize = 7;
388        else if( size > 18 )
389          htmlSize = 6;
390        else if( size > 14 )
391          htmlSize = 5;
392        else if( size > 12 )
393          htmlSize = 4;
394        else if( size > 10 )
395          htmlSize = 3;
396        else if( size > 8 )
397          htmlSize = 2;
398        else
399          htmlSize = 1;
400
401        return "size:" + htmlSize + ";";
402      }
403
404    return null;
405  }
406
407  /**
408   * Stupid that Color doesn't have a method for this.
409   */
410  private String getColor(Color c)
411  {
412    String r = "00" + Integer.toHexString(c.getRed());
413    r = r.substring(r.length() - 2);
414    String g = "00" + Integer.toHexString(c.getGreen());
415    g = g.substring(g.length() - 2);
416    String b = "00" + Integer.toHexString(c.getBlue());
417    b = b.substring(b.length() - 2);
418    return "#" + r + g + b;
419  }
420
421  /**
422   * Empty the stack of open tags
423   */
424  private void endOpenTags() throws IOException
425  {
426    while(tagStack.size() > 0)
427      write(tagStack.pop());
428
429    if( inFontTag() )
430      {
431        write(""+NEWLINE);
432        endFontTag();
433      }
434  }
435
436  /**
437   * Output a single style
438   */
439  private void writeStyle(Style s) throws IOException
440  {
441    if( s == null )
442      return;
443
444    writeStartTag("p."+s.getName()+" {");
445    writeAttributes(s);
446    writeEndTag("}");
447  }
448
449  private boolean hasText(Element e) throws BadLocationException
450  {
451    return (getText(e).trim().length() > 0);
452  }
453}