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.ArrayDeque;
052    import java.util.Deque;
053    import java.util.Enumeration;
054    import java.awt.Color;
055    
056    /**
057     * MinimalHTMLWriter,
058     * A minimal AbstractWriter implementation for HTML.
059     *
060     * @author Sven de Marothy
061     */
062    public 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    }