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 }