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         * &lt;button name="test"&gt;Button text&lt;/button&gt;
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         * &lt;input type="SUBMIT" name="Submit" value="Submit"&gt; 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: &lt;input type="text" name="Name"&gt;
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    }