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 }