001    /* MinimalHTMLWriter.java -- 
002       Copyright (C) 2006 Free Software Foundation, Inc.
003    
004    This file is part of GNU Classpath.
005    
006    GNU Classpath is free software; you can redistribute it and/or modify
007    it under the terms of the GNU General Public License as published by
008    the Free Software Foundation; either version 2, or (at your option)
009    any later version.
010    
011    GNU Classpath is distributed in the hope that it will be useful, but
012    WITHOUT ANY WARRANTY; without even the implied warranty of
013    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014    General Public License for more details.
015    
016    You should have received a copy of the GNU General Public License
017    along with GNU Classpath; see the file COPYING.  If not, write to the
018    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019    02110-1301 USA.
020    
021    Linking this library statically or dynamically with other modules is
022    making a combined work based on this library.  Thus, the terms and
023    conditions of the GNU General Public License cover the whole
024    combination.
025    
026    As a special exception, the copyright holders of this library give you
027    permission to link this library with independent modules to produce an
028    executable, regardless of the license terms of these independent
029    modules, and to copy and distribute the resulting executable under
030    terms of your choice, provided that you also meet, for each linked
031    independent module, the terms and conditions of the license of that
032    module.  An independent module is a module which is not derived from
033    or based on this library.  If you modify this library, you may extend
034    this exception to your version of the library, but you are not
035    obligated to do so.  If you do not wish to do so, delete this
036    exception statement from your version. */
037    
038    package javax.swing.text.html;
039    
040    import javax.swing.text.AttributeSet;
041    import javax.swing.text.AbstractWriter;
042    import javax.swing.text.BadLocationException;
043    import javax.swing.text.DefaultStyledDocument;
044    import javax.swing.text.Element;
045    import javax.swing.text.ElementIterator;
046    import javax.swing.text.StyleConstants;
047    import javax.swing.text.Style;
048    import javax.swing.text.StyledDocument;
049    import java.io.Writer;
050    import java.io.IOException;
051    import java.util.Enumeration;
052    import java.util.Stack;
053    import java.awt.Color;
054    
055    /**
056     * MinimalHTMLWriter,
057     * A minimal AbstractWriter implementation for HTML.
058     *
059     * @author Sven de Marothy
060     */
061    public 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 = (Style)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    }