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 = 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 }