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 }