001 package net.sf.jolene.dom; 002 003 import net.sf.jolene.constants.Prefs; 004 import net.sf.jolene.constants.Tags; 005 import net.sf.jolene.html.Attributes; 006 import net.sf.jolene.html.IAttributes; 007 import net.sf.jolene.html.IStyles; 008 import net.sf.jolene.html.Styles; 009 import org.apache.log4j.LogManager; 010 import org.apache.log4j.Logger; 011 012 import java.util.*; 013 014 /** 015 * Base class for all html elements. 016 * 017 * @author Dan Howard 018 * @since Oct 11, 2003 019 */ 020 public abstract class HTMLElement implements IAttributes, IStyles, Cloneable { 021 // ------------------------------ FIELDS ------------------------------ 022 023 // todo implement default attriutes for objects 024 static final Logger log = LogManager.getLogger(HTMLElement.class); 025 026 // This will contain the starting chars when streaming. e.g. 027 // INPUT TYPE = 'INPUT' 028 // SELECT = 'SELECT' 029 // LABEL = 'LABEL' 030 Tags tag; 031 032 // For swapping 033 String swapWith; 034 035 // Custom properties 036 private String afterText; 037 private String beforeText; 038 039 // These store the location in the file - internal 040 private int startPoint; 041 private int endPoint; 042 043 // Common properties. 044 private String name; 045 private String value; 046 private String content; // inner content of an element between ending of start tag and end tag. http://www.w3.org/TR/html401/interact/forms.html#h-17.5 047 private boolean renderable; 048 049 // HTML attributes - has access only with package private setters/getters. 050 private IAttributes attributes; 051 052 private IStyles styles; 053 054 protected HTMLElement() { 055 afterText = ""; 056 beforeText = ""; 057 renderable = true; 058 name = ""; 059 value = ""; 060 content = ""; 061 attributes = new Attributes(); 062 styles = new Styles(); 063 tag = Tags.input; 064 startPoint = 0; 065 endPoint = 0; 066 } 067 068 protected HTMLElement(String name) { 069 this(); 070 this.name = name; 071 } 072 073 /** 074 * Clears this element's attributes. 075 */ 076 public void clear() { 077 attributes.clear(); 078 } 079 080 /** 081 * Core clone implementation. Safely clones HTMLElement objects. Used by the DocumentFactory. 082 * 083 * @return HTMLElement 084 * @see net.sf.jolene.factories.DocumentFactory 085 */ 086 @Override 087 public HTMLElement clone() { 088 HTMLElement element; 089 boolean setValue = true; 090 091 if (this instanceof Button) { 092 element = new Button(); 093 } else if (this instanceof CheckBox) { 094 element = new CheckBox(); 095 } else if (this instanceof Grid) { 096 element = new Grid(); 097 setValue = false; 098 } else if (this instanceof Image) { 099 element = new Image(); 100 setValue = false; 101 } else if (this instanceof Input) { 102 element = new Input(); 103 } else if (this instanceof Label) { 104 element = new Label(); 105 } else if (this instanceof Radio) { 106 element = new Radio(); 107 } else if (this instanceof Select) { 108 element = new Select(); 109 } else if (this instanceof TextArea) { 110 element = new TextArea(); 111 } else if (this instanceof GridCell) { 112 element = new GridCell(); 113 } else if (this instanceof GridRow) { 114 element = new GridRow(); 115 } else if (this instanceof Header) { 116 element = new Header(); 117 } else { 118 element = new Form(); 119 setValue = false; 120 } 121 122 // Standard clone 123 Iterator<String> it; 124 String key; 125 String value; 126 it = attributes.keySet().iterator(); 127 while (it.hasNext()) { 128 key = it.next(); 129 value = getAttribute(key); 130 element.setAttribute(key, value); 131 132 // Make sure to call setValue 133 if ("value".equalsIgnoreCase(key)) { 134 element.setValue(value); 135 } 136 } 137 // Use setters instead of direct access in case we override these methods (setName is overriden for example). 138 element.setName(name); 139 element.setAfterText(afterText); 140 element.setBeforeText(beforeText); 141 element.setEndPoint(endPoint); 142 element.setRenderable(renderable); 143 element.setStartPoint(startPoint); 144 element.setTag(tag); 145 element.swapWith(swapWith); 146 147 // Set the value ONLY for objects that have a value 148 // Note that the object could have a null in the case where the object 149 // usually has a value attribute in HTML but was not specified in the 150 // actual document. example: 151 // <INPUT NAME="username" TYPE="text"> 152 //@todo why do we bother checking if element.getValue is null? It means that we always need a default for GridCell, TextArea, etc (ones which override the default get/set value methods) 153 if (setValue && this.value.trim().length() > 0) { 154 element.setValue(this.value); 155 } 156 157 return element; 158 } 159 160 /** 161 * Gets the afterText string for the object. 162 * The afterText is any string value that you want to be rendered immediatley after the object itself. 163 * 164 * @return String 165 */ 166 public String getAfterText() { 167 return afterText; 168 } 169 170 /** 171 * Return the html attribute specified by the key or null if it's not found. 172 * 173 * @param key case insensitive attribute name. 174 * @return attribute value string. 175 */ 176 public final String getAttribute(String key) { 177 return attributes.getAttribute(key); 178 } 179 180 /** 181 * Gets the beforeText string for the object. 182 * The beforeText is any string value that you want to be rendered immediatley before 183 * the object itself. 184 * 185 * @return String 186 */ 187 public String getBeforeText() { 188 return beforeText; 189 } 190 191 /** 192 * <p/> 193 * Returns the content of the HTMLElement. The content is part of the element between the end of the start tag 194 * and the beginning of the end tag. In the example below <b>Button text</b> is the content. 195 * </p> 196 * <tt> 197 * <button name="test">Button text</button> 198 * </tt> 199 * <p/> 200 * Note that some elements do not have content. For these elements this content 201 * property has no effect on the rendering of the element. 202 * </p> 203 * <p/> 204 * Elements which have a content. 205 * <ul> 206 * <li>Button</li> 207 * <li>Label</li> 208 * <li>TextArea</li> 209 * <li>Header <i>Some header elements do and some don't.</i></li> 210 * </ul> 211 * <p/> 212 * <br> 213 * Elements which do NOT have a content. 214 * <ul> 215 * <li>ChecBox</li> 216 * <li>Grid</li> 217 * <li>Image</li> 218 * <li>Input</li> 219 * <li>Radio</li> 220 * <li>Select</li> 221 * </ul> 222 * <p/> 223 * <p/> 224 * Note also that for TextArea, Text and Label the content is also the value. In this case the value and content properties are 225 * interchangable. 226 * </p> 227 * 228 * @return String content part of the HTMLElement 229 */ 230 public String getContent() { 231 return content; 232 } 233 234 /** 235 * Gets the element name. 236 * 237 * @return name of the html element. 238 */ 239 public String getName() { 240 return name; 241 } 242 243 244 /** 245 * Get the element style based on a style name. 246 * 247 * @param key case insensitive style name. 248 * @return String value for the specified style or null if not found. 249 */ 250 public String getStyle(String key) { 251 return styles.getStyle(key); 252 } 253 254 /** 255 * Get the tag for the element. 256 * 257 * @return the tag for the element. 258 */ 259 public Tags getTag() { 260 return tag; 261 } 262 263 /** 264 * Gets the current value of the element. 265 * 266 * @return String 267 */ 268 public String getValue() { 269 // Default for input type text, buttons etc.... 270 //return getAttribute("value"); 271 return value; 272 } 273 274 /** 275 * Indicates if the specified attribute exists. 276 * 277 * @param key case insensitive attribute name. 278 * @return true if the attribute exists. 279 */ 280 public boolean hasAttribute(String key) { 281 return attributes.hasAttribute(key); 282 } 283 284 /** 285 * Indicates if a style exists. 286 * 287 * @param key case instensitive style key name. 288 * @return true if the style exists in the string. 289 */ 290 public boolean hasStyle(String key) { 291 return styles.hasStyle(key); 292 } 293 294 295 /** 296 * Returns true if a Radio or CheckBox is checked. 297 * 298 * @return checked 299 */ 300 public boolean isChecked() { 301 return hasAttribute("checked"); 302 } 303 304 /** 305 * Returns true if the element is disabled 306 * 307 * @return disabled 308 */ 309 public boolean isDisabled() { 310 return hasAttribute("disabled"); 311 } 312 313 314 /** 315 * Returns true if the element is readonly 316 * 317 * @return readonly 318 */ 319 public boolean isReadonly() { 320 return hasAttribute("readonly"); 321 } 322 323 /** 324 * Returns whether this element is rendered. If this is false then the element will be rendered as an empty string. 325 * 326 * @return boolean 327 */ 328 public boolean isRenderable() { 329 return renderable; 330 } 331 332 333 /** 334 * Returns the set of attribute names. 335 * 336 * @return set of attribute names. 337 */ 338 public Set<String> keySet() { 339 return attributes.keySet(); 340 } 341 342 /** 343 * Remove the specified attribute. 344 * 345 * @param key case insensitive attribute name. 346 * @return previous value associated with specified key, or <tt>null</tt> if there was no mapping for key. 347 */ 348 public String removeAttribute(String key) { 349 return attributes.removeAttribute(key); 350 } 351 352 /** 353 * Removes a style from the element. 354 * 355 * @param key Style key name to remove. 356 * @return previous value associated with specified key, or <tt>null</tt> if there was no mapping for key. 357 */ 358 public String removeStyle(String key) { 359 return styles.removeStyle(key); 360 } 361 362 /** 363 * Sets the styles on the element based on the formatted style string, clearing all exisiting styles first. 364 * 365 * @param style string in a format like <tt>azimuth:behind;background:aliceblue;background-color:aquamarine;border-bottom-style:solid;clip:auto;border-top-width:medium;</tt> 366 */ 367 public final void resetStyles(String style) { 368 styles.resetStyles(style); 369 setAttribute("style", style); 370 } 371 372 /** 373 * Sets the styles on the element based on the Style parameter, clearing all exisiting styles first. 374 * 375 * @param style a Style object. 376 */ 377 public final void resetStyles(Styles style) { 378 resetStyles(style.toString()); 379 } 380 381 /** 382 * Sets the afterText string for the object. 383 * The afterText is any string value that you want to be rendered immediately after the object itself. 384 * <p/> 385 * Example: 386 * <pre> 387 * document.forms(0).elements("Submit").setAfterText(" Click this button to submit this form."); 388 * </pre> 389 * This would render the following back to the browser: 390 * <pre> 391 * <input type="SUBMIT" name="Submit" value="Submit"> Click this button to submit this form. 392 * </pre> 393 * 394 * @param string text after object 395 */ 396 public void setAfterText(String string) { 397 afterText = string; 398 } 399 400 /** 401 * Sets the specified attribute. 402 * 403 * @param key case insensitive attribute name. 404 * @param value value of the attribute. 405 * @return previous value associated with specified key, or <tt>null</tt> if there was no attribute for the key. 406 */ 407 public final String setAttribute(String key, String value) { 408 if (value == null) { 409 value = ""; 410 } 411 412 if ("style".equalsIgnoreCase(key)) { 413 styles.setStyles(value); 414 String style = styles.toString(); 415 attributes.setAttribute(key, style); 416 return style; 417 } 418 419 return attributes.setAttribute(key, value); 420 } 421 422 /** 423 * Sets the beforeText string for the object. 424 * The beforeText is any string value that you want to be rendered immediatley before 425 * the object itself. 426 * <p/> 427 * Example: 428 * <pre> 429 * document.forms(0).elements("Name").setBeforeText("Name: "); 430 * </pre> 431 * This would render the following back to the browser: 432 * <pre> 433 * Name: <input type="text" name="Name"> 434 * </pre> 435 * 436 * @param string of text 437 */ 438 public void setBeforeText(String string) { 439 beforeText = string; 440 } 441 442 /** 443 * Sets the element checked attribute. Used for Radios and Checkboxes. 444 * 445 * @param checked to indicate if the checkbox is checked or not. 446 */ 447 public void setChecked(boolean checked) { 448 if (checked) { 449 setAttribute("checked", "checked"); 450 } else { 451 if (hasAttribute("checked")) { 452 removeAttribute("checked"); 453 } 454 } 455 } 456 457 /** 458 * Sets the content of the HTMLElement. 459 * 460 * @param content text or value to set. 461 * @see HTMLElement#getContent() 462 */ 463 public void setContent(String content) { 464 this.content = content; 465 } 466 467 /** 468 * Set the element to be disabled. 469 * 470 * @param disabled indicator for a disabled element. 471 */ 472 public void setDisabled(boolean disabled) { 473 if (disabled) { 474 setAttribute("disabled", "disabled"); 475 } else { 476 if (hasAttribute("disabled")) { 477 removeAttribute("disabled"); 478 } 479 } 480 } 481 482 /** 483 * Set the element name. 484 * 485 * @param name name of the html element. 486 */ 487 public void setName(String name) { 488 this.name = name; 489 } 490 491 /** 492 * Set the element to be readonly. 493 * 494 * @param readonly indicator for a readonly element. 495 */ 496 public void setReadonly(boolean readonly) { 497 if (readonly) { 498 setAttribute("readonly", "readonly"); 499 } else { 500 if (hasAttribute("readonly")) { 501 removeAttribute("readonly"); 502 } 503 } 504 } 505 506 507 /** 508 * Sets whether this element is rendered. If this is false then the element will be rendered as an empty string. 509 * 510 * @param renderable true or false 511 */ 512 public void setRenderable(boolean renderable) { 513 this.renderable = renderable; 514 } 515 516 /** 517 * Set the element style. 518 * 519 * @param key Style key name. 520 * @param value Style value. 521 * @return previous value associated with specified key, or <tt>null</tt> if there was no mapping for key. 522 */ 523 public String setStyle(String key, String value) { 524 return setAttribute("style", key + ":" + value + ";"); 525 } 526 527 /** 528 * Sets the styles on the element based on a formatted style string. 529 * Expects the string to be in style format. 530 * 531 * @param style string in a format like <tt>azimuth:behind;background:aliceblue;background-color:aquamarine;border-bottom-style:solid;clip:auto;border-top-width:medium;</tt> 532 */ 533 public final void setStyles(String style) { 534 styles.setStyles(style); 535 setAttribute("style", styles.toString()); 536 } 537 538 /** 539 * Sets styles on the Style object based on the Style parameter. 540 * 541 * @param styles a Style object. 542 */ 543 public final void setStyles(Styles styles) { 544 setStyles(styles.toString()); 545 } 546 547 548 // Non-public 549 void setTag(Tags tag) { 550 this.tag = tag; 551 } 552 553 /** 554 * Sets the value of the element. 555 * 556 * @param value value of object 557 */ 558 public void setValue(String value) { 559 // Note textarea, select, label override this. 560 // Default for setValue, textarea, select, label should override. 561 // setAttribute("value", value); 562 this.value = value; 563 } 564 565 /** 566 * Replace this element's string representation with the specified string. 567 * 568 * @param swapWith any string you wish to render instead of this object. 569 */ 570 public void swapWith(String swapWith) { 571 this.swapWith = swapWith; 572 } 573 574 /** 575 * Renders the element as an html string. 576 */ 577 @Override 578 public String toString() { 579 if (swapWith != null) { 580 return swapWith; 581 } 582 583 if (!renderable && !log.isDebugEnabled()) { 584 return ""; 585 } 586 587 StringBuffer sb = new StringBuffer(""); 588 Iterator iterator = attributes.keySet().iterator(); 589 590 sb.append('<').append(tag); 591 592 List<BooleanAttributes> booleanAttributes = Arrays.asList(BooleanAttributes.values()); 593 594 while (iterator.hasNext()) { 595 String key = (String) iterator.next(); 596 String value = attributes.getAttribute(key); 597 if (value == null) { 598 log.warn("Value is null? setting to blank string"); 599 value = ""; 600 } 601 if (booleanAttributes.contains(BooleanAttributes.fromString(key.toLowerCase()))) { 602 sb.append(' ').append(key).append('=').append('\"').append(key.toLowerCase()).append('\"'); 603 } else { 604 String quote = "\""; 605 // If the key itself contains a " character 606 // use the single ' quote. 607 if (value.indexOf("\"") > -1) { 608 quote = "'"; 609 } 610 611 sb.append(' ').append(key).append('=').append(quote).append(value).append(quote); 612 } 613 } 614 615 if (iterator.hasNext()) { 616 sb.append(' '); 617 } else { 618 if ("true".equalsIgnoreCase(Prefs.XHTMLStrict.getValue())) { 619 //@todo /> is buggy! 620 sb.append(" />"); 621 } else { 622 sb.append('>'); 623 } 624 } 625 if (log.isDebugEnabled()) { 626 sb.append("Debug: "); 627 sb.append("BeforeText: ").append(beforeText).append(' '); 628 sb.append("AfterText: ").append(afterText).append(' '); 629 sb.append("Start point: ").append(startPoint).append(' '); 630 sb.append("End point: ").append(endPoint).append(' '); 631 sb.append("Render? ").append(renderable).append(' '); 632 sb.append("TAG: ").append(tag); 633 } 634 return new String(sb); 635 } 636 637 /* not used or needed we have keyset.... 638 public Collection<String> values() { 639 return attributes.values(); 640 } 641 */ 642 643 // Not public 644 IAttributes getAttributes() { 645 return attributes; 646 } 647 648 int getEndPoint() { 649 return endPoint; 650 } 651 652 int getStartPoint() { 653 return startPoint; 654 } 655 656 // Not public 657 IStyles getStyles() { 658 return styles; 659 } 660 661 // Not public 662 void setAttributes(IAttributes attributes) { 663 this.attributes = attributes; 664 } 665 666 void setEndPoint(int endPoint) { 667 this.endPoint = endPoint; 668 } 669 670 void setStartPoint(int startPoint) { 671 this.startPoint = startPoint; 672 } 673 }