001 package edu.nrao.sss.html; 002 003 import java.io.IOException; 004 import java.io.Writer; 005 import java.util.ArrayList; 006 import java.util.Collection; 007 import java.util.HashMap; 008 import java.util.Map; 009 010 import javax.swing.text.html.HTML; 011 012 /** 013 * Parent to other HTML elements. 014 * <p> 015 * <b>Version Info:</b> 016 * <table style="margin-left:2em"> 017 * <tr><td>$Revision: 501 $</td></tr> 018 * <tr><td>$Date: 2007-04-04 09:13:28 -0600 (Wed, 04 Apr 2007) $</td></tr> 019 * <tr><td>$Author: dharland $</td></tr> 020 * </table></p> 021 * 022 * @author David M. Harland 023 * @since 2007-03-16 024 */ 025 public abstract class HtmlElement 026 { 027 /** HTML representation of a non-breaking space. */ 028 public static final String NBSP_HTML = " "; 029 030 /** 031 * The unicode text into which java's HTML parsers convert 032 * {@link #NBSP_HTML}. When writing HTML elements to 033 * HTML text, this is the value used for a non-breaking space. 034 * This representation is ASCII 0xA0, or unicode 00A0. 035 */ 036 public static final String NBSP_UNICODE = "\u00A0"; 037 038 /** 039 * Plain text representation of {@link #NBSP_HTML}. When writing HTML 040 * elements to plain (non-HTML) text, this is the value used for 041 * a non-breaking space. This representation is the normal space 042 * character, ASCII 32 (0x20). 043 */ 044 public static final String NBSP_TEXT = " "; 045 046 HTML.Tag tag; //Not directly publicly mutable; can be reset by subclasses 047 048 Map<HTML.Attribute, HtmlAttribute> attributes; //Not directly pub'ly mutable 049 050 /** 051 * Helps create a new instance. 052 * 053 * @param htmlTag the kind of element this is. 054 */ 055 HtmlElement(HTML.Tag htmlTag) 056 { 057 if (htmlTag == null) throw 058 new IllegalArgumentException("May not send null tag to constructor."); 059 060 tag = htmlTag; 061 attributes = new HashMap<HTML.Attribute, HtmlAttribute>(); 062 } 063 064 /** 065 * Returns the HTML tag for this element. 066 * @return the HTML tag for this element. 067 */ 068 public HTML.Tag getTag() 069 { 070 return tag; 071 } 072 073 /** 074 * Returns <i>true</i> if this is a simple element. 075 * A simple element is one that may not have content, such as the line break 076 * (<br/>) element. 077 * 078 * @return <i>true</i> if this is a simple element. 079 */ 080 public abstract boolean isSimple(); 081 082 //============================================================================ 083 // ATTRIBUTES 084 //============================================================================ 085 086 /** 087 * Adds {@code newAttribute} to this element. If this element currently holds 088 * an attribute of the same type as {@code newAttribute}, it is replaced by 089 * {@code newAttribute}. 090 * 091 * @param newAttribute a new attribute for this element. 092 * If this value is <i>null</i> this method does nothing. 093 * 094 * @return an attribute of the same type as {@code newAttribute}, or 095 * <i>null</i> if this element had no attribute of that type. 096 */ 097 public HtmlAttribute addAttribute(HtmlAttribute newAttribute) 098 { 099 return (newAttribute == null) ? null 100 : attributes.put(newAttribute.getType(), 101 newAttribute); 102 } 103 104 /** 105 * Removes from this element its attribute of the given type. 106 * 107 * @param unwantedType the type of attribute to remove from this element. 108 * 109 * @return the attribute of the given type held by this element, or 110 * <i>null</i> if it held no such attribute. 111 */ 112 public HtmlAttribute removeAttribute(HTML.Attribute unwantedType) 113 { 114 return (unwantedType == null) ? null : attributes.remove(unwantedType); 115 } 116 117 /** 118 * Removes all attributes from this element. 119 * @return the number of attributes of this element prior to their removal. 120 */ 121 public int removeAllAttributes() 122 { 123 int oldCount = attributes.size(); 124 125 if (oldCount > 0) 126 attributes.clear(); 127 128 return oldCount; 129 } 130 131 /** 132 * Returns the attribute of the given type held by this element, if any. 133 * If this element holds no such element, <i>null</i> is returned. 134 * 135 * @param attributeType the type of attribute desired. 136 * 137 * @return the attribute of the given type held by this element, if any. 138 * If this element holds no such element, <i>null</i> is returned. 139 */ 140 public HtmlAttribute getAttribute(HTML.Attribute attributeType) 141 { 142 return (attributeType == null) ? null : attributes.get(attributeType); 143 } 144 145 /** 146 * Returns the attributes of this element. 147 * <p> 148 * The returned collection is not held by this element, so 149 * any changes made to it will be <i>not</i> reflected in this object. 150 * The attributes in the collection, however, are those held by this 151 * element.</p> 152 * 153 * @return the attributes of this element. 154 * The collection is guaranteed to be non-null, but could be empty. 155 */ 156 public Collection<HtmlAttribute> getAttributes() 157 { 158 return new ArrayList<HtmlAttribute>(attributes.values()); 159 } 160 161 /** 162 * Returns the value of the given attribute, or the empty string 163 * (<tt>""</tt>) if this element has no such attribute. 164 * 165 * @param attributeType the name of an attribute of this element 166 * for which a value is sought. 167 * 168 * @return the value of the given attribute. 169 */ 170 public String getAttributeValue(HTML.Attribute attributeType) 171 { 172 HtmlAttribute attribute = attributes.get(attributeType); 173 174 return attribute == null ? "" : attribute.getValue(); 175 } 176 177 /** 178 * Sets the attributes of this element based on the given text. 179 * <p> 180 * The expected form of {@code attributeText} is a series of 181 * <tt>name="value"</tt> pairs, where each pair in the series is 182 * separated by whitespace. It is expected that the opening and 183 * closing tag markers, along with the tag name itself, are 184 * <i>not</i> present in the text. Leading and trailing whitespace 185 * is permitted and will be stripped during parsing. The <tt>value</tt> 186 * of each pair should be in either single (') or double (") quotes.</p> 187 * <p> 188 * If anything goes wrong during parsing, an {@code IllegalArgumentException} 189 * is thrown. All successfully parsed pairs up to the point of the exception 190 * will be maintained by this instance.</p> 191 * 192 * @param attributeText a series of <tt>name="value"</tt> pairs, where 193 * each pair in the series is separated by whitespace. 194 */ 195 void parseAttributes(String attributeText) 196 { 197 int equalsPosition = attributeText.indexOf('='); 198 199 while (equalsPosition > -1) 200 { 201 //Will let HtmlAttribute.parse worry about situation of no quotes. 202 //Here we just try to determine which kind comes first. 203 int singleQuotePos = attributeText.indexOf('\''); 204 int doubleQuotePos = attributeText.indexOf('"'); 205 int startQuotePos; 206 207 char quoteChar; 208 209 if ((singleQuotePos > -1) && (singleQuotePos < doubleQuotePos)) 210 { 211 quoteChar = '\''; 212 startQuotePos = singleQuotePos; 213 } 214 else 215 { 216 quoteChar = '"'; 217 startQuotePos = doubleQuotePos; 218 } 219 220 //Send just one name="value" pair to parse method 221 int endQuotePos = attributeText.indexOf(quoteChar, startQuotePos+1); 222 String parseText = attributeText.substring(0, endQuotePos+1); 223 224 HtmlAttribute attribute = HtmlAttribute.parse(parseText); 225 attributes.put(attribute.getType(), attribute); 226 227 //Trim text to part not yet parsed 228 attributeText = attributeText.substring(parseText.length()); 229 230 equalsPosition = attributeText.indexOf('='); 231 } 232 } 233 234 /** 235 * Copies the attributes of the other element into this one. 236 * @param other a provider of HTML attributes. 237 */ 238 protected void copyAttributesOf(HtmlElement other) 239 { 240 for (HtmlAttribute oAttr : other.getAttributes()) 241 addAttribute(new HtmlAttribute(oAttr.getType(), oAttr.getValue())); 242 } 243 244 //============================================================================ 245 // WRITING 246 //============================================================================ 247 248 /** 249 * Writes this element to the given device. 250 * 251 * @param device the destination of the HTML. 252 * 253 * @param padding the number of spaces to write before writing this element. 254 * 255 * @param newLine if this value is <i>true</i>, a new-line character is 256 * written after this element. 257 * 258 * @throws IOException if anything goes wrong while writing. 259 */ 260 public void writeHtmlTo(Writer device, int padding, boolean newLine) 261 throws IOException 262 { 263 writeHtmlTo(device, padding, 1, newLine); 264 } 265 266 /** 267 * Non-public method that handles the writing. This method also 268 * keeps track of the nesting of elements so that each nested 269 * element is padded by {@code padding}, relative to its container 270 * element. 271 */ 272 void writeHtmlTo(Writer device, int padding, int depth, boolean newLine) 273 throws IOException 274 { 275 //Padding 276 device.write(getPadding(padding, depth)); 277 278 //Opening tag 279 device.write('<'); 280 device.write(tag.toString()); 281 282 //Attributes 283 for (HtmlAttribute attr : attributes.values()) 284 { 285 device.write(' '); 286 device.write(attr.toString()); 287 } 288 289 //Contents and/or closing tag 290 if (isSimple()) 291 { 292 device.write("/>"); 293 } 294 else 295 { 296 device.write('>'); 297 writeContentsAsHtml(device, padding, depth); 298 device.write("</"); 299 device.write(tag.toString()); 300 device.write('>'); 301 } 302 303 //New line 304 if (newLine) 305 device.write(System.getProperty("line.separator")); 306 } 307 308 /** 309 * Writes the contents of this element to the device. 310 * The contents of an element is the information between the close of the 311 * start tag and the open of end tag. 312 */ 313 abstract void writeContentsAsHtml(Writer device, int padding, int depth) 314 throws IOException; 315 316 /** Creates string of space characters. Length = depth * spacesPerLevel. */ 317 String getPadding(int spacesPerLevel, int depth) 318 { 319 StringBuilder buff = new StringBuilder(); 320 321 int spaceCount = spacesPerLevel * depth; 322 323 for (int s=1; s <= spaceCount; s++) 324 buff.append(' '); 325 326 return buff.toString(); 327 } 328 329 /** Replaces \u00A0 with replacement. */ 330 void replaceUnicodeNbspWith(StringBuilder buff, String replacement) 331 { 332 int nbspPos = buff.indexOf(NBSP_UNICODE); 333 334 while (nbspPos >= 0) 335 { 336 buff.deleteCharAt(nbspPos); 337 buff.insert(nbspPos, replacement); 338 nbspPos = buff.indexOf(NBSP_UNICODE); 339 } 340 } 341 }