001package com.hfg.html;
002
003import java.awt.*;
004import java.io.OutputStream;
005import java.io.PrintWriter;
006import java.util.*;
007import java.util.List;
008
009import com.hfg.css.CSS;
010import com.hfg.css.CSSDeclaration;
011import com.hfg.css.CSSProperty;
012import com.hfg.graphics.ColorSpec;
013import com.hfg.util.BooleanUtil;
014import com.hfg.util.Case;
015import com.hfg.util.StringUtil;
016import com.hfg.util.collection.CollectionUtil;
017import com.hfg.util.io.GZIP;
018import com.hfg.xml.XMLAttribute;
019import com.hfg.xml.XMLName;
020import com.hfg.xml.XMLNode;
021import com.hfg.xml.XMLTag;
022import com.hfg.util.StringBuilderPlus;
023import com.hfg.css.CssUtil;
024import com.hfg.xml.XMLizable;
025
026//------------------------------------------------------------------------------
027/**
028 * Base class for HTML elements.
029 * @author J. Alex Taylor, hairyfatguy.com
030 */
031//------------------------------------------------------------------------------
032// com.hfg XML/HTML Coding Library
033//
034// This library is free software; you can redistribute it and/or
035// modify it under the terms of the GNU Lesser General Public
036// License as published by the Free Software Foundation; either
037// version 2.1 of the License, or (at your option) any later version.
038//
039// This library is distributed in the hope that it will be useful,
040// but WITHOUT ANY WARRANTY; without even the implied warranty of
041// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
042// Lesser General Public License for more details.
043//
044// You should have received a copy of the GNU Lesser General Public
045// License along with this library; if not, write to the Free Software
046// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
047//
048// J. Alex Taylor, President, Founder, CEO, COO, CFO, OOPS hairyfatguy.com
049// jataylor@hairyfatguy.com
050//------------------------------------------------------------------------------
051
052public class HTMLTag extends XMLTag implements HTMLNode, Cloneable
053{
054
055
056   //###########################################################################
057   // CONSTRUCTORS
058   //###########################################################################
059
060   //--------------------------------------------------------------------------
061   public HTMLTag(String inName)
062   {
063      super(inName);
064   }
065
066   //--------------------------------------------------------------------------
067   public HTMLTag(String inName, Hashtable<String, String> inAttributes)
068   {
069      super(inName, inAttributes);
070   }
071
072   //--------------------------------------------------------------------------
073   public HTMLTag(String inName, Hashtable inAttributes, String inContent)
074   {
075      super(inName, inAttributes, inContent);
076   }
077
078   //--------------------------------------------------------------------------
079   public HTMLTag(XMLNode inXMLNode)
080   {
081      super(inXMLNode.getTagName());
082      initFromXMLNode(inXMLNode);
083   }
084
085   //###########################################################################
086   // PUBLIC METHODS
087   //###########################################################################
088
089   //--------------------------------------------------------------------------
090   public String toHTML()
091   {
092      return toXML();
093   }
094
095   //--------------------------------------------------------------------------
096   public void toHTML(OutputStream inStream)
097   {
098      toXML(inStream);
099   }
100
101   //--------------------------------------------------------------------------
102   public void toHTML(PrintWriter inWriter)
103   {
104      toXML(inWriter);
105   }
106
107   //--------------------------------------------------------------------------
108   public String toIndentedHTML(int inInitialIndentLevel, int inIndentSize)
109   {
110      return toIndentedXML(inInitialIndentLevel, inIndentSize);
111   }
112
113   //--------------------------------------------------------------------------
114   public void toIndentedHTML(OutputStream inOutputStream, int inInitialIndentLevel, int inIndentSize)
115   {
116      toIndentedXML(inOutputStream, inInitialIndentLevel, inIndentSize);
117   }
118
119   //--------------------------------------------------------------------------
120   public void toIndentedHTML(PrintWriter inWriter, int inInitialIndentLevel, int inIndentSize)
121   {
122      toIndentedXML(inWriter, inInitialIndentLevel, inIndentSize);
123   }
124
125
126   //--------------------------------------------------------------------------
127   /**
128    Returns output stripped of HTML tags.
129    * @return raw text string
130    */
131   public String toText()
132   {
133      StringBuilderPlus buffer = new StringBuilderPlus();
134      if (mContentAndSubtagList != null)
135      {
136         for (Object content : mContentAndSubtagList)
137         {
138            if (content instanceof String)
139            {
140               buffer.append(content.toString());
141            }
142            else if (content instanceof byte[])
143            {
144               buffer.append(GZIP.uncompressToString((byte[]) content));
145            }
146            else
147            {
148               XMLizable subtag = (XMLizable) content;
149               if (subtag instanceof HTMLTag)
150               {
151                  buffer.append(((HTMLTag)subtag).toText());
152               }
153            }
154         }
155      }
156
157      return buffer.toString();
158   }
159
160   //--------------------------------------------------------------------------
161   @Override
162   public HTMLTag clone()
163   {
164      return (HTMLTag) super.clone();
165   }
166
167   //--------------------------------------------------------------------------
168   public HTMLTag setId(String inValue)
169   {
170      setAttribute(HTML.ID, inValue);
171      return this;
172   }
173
174   //--------------------------------------------------------------------------
175   public String getId()
176   {
177      return getAttributeValue(HTML.ID);
178   }
179
180   //--------------------------------------------------------------------------
181   @Override
182   public HTMLTag setAttribute(String inName, Object inValue)
183   {
184      super.setAttribute(inName, inValue);
185      return this;
186   }
187
188   //--------------------------------------------------------------------------
189   @Override
190   public HTMLTag setAttribute(XMLName inName, Object inValue)
191   {
192      super.setAttribute(inName, inValue);
193      return this;
194   }
195
196   //--------------------------------------------------------------------------
197   public HTMLTag setClass(String inValue)
198   {
199      setAttribute(HTML.CLASS, inValue);
200      return this;
201   }
202
203   //--------------------------------------------------------------------------
204   public HTMLTag addClass(String inValue)
205   {
206      String oldValue = getAttributeValue(HTML.CLASS);
207      if (oldValue != null)
208      {
209         inValue = oldValue + " " + inValue;
210      }
211      setAttribute(HTML.CLASS, inValue);
212      return this;
213   }
214
215   //--------------------------------------------------------------------------
216   public HTMLTag removeClass(String inValue)
217   {
218      String oldValue = getAttributeValue(HTML.CLASS);
219      if (oldValue != null)
220      {
221         List<String> pieces = Arrays.asList(oldValue.split("\\s+"));
222         for (int i = 0; i < pieces.size(); i++)
223         {
224            if (pieces.get(i).equals(inValue))
225            {
226               pieces.remove(i);
227               break;
228            }
229         }
230
231         setAttribute(HTML.CLASS, StringUtil.join(pieces, " "));
232      }
233
234      return this;
235   }
236
237
238   //--------------------------------------------------------------------------
239   /**
240    Not called getClass() for obvious reasons.
241    @return the value of the 'class' attribute specifying one or more CSS classes
242    */
243   public String getClassAttribute()
244   {
245      return getAttributeValue(HTML.CLASS);
246   }
247
248   //--------------------------------------------------------------------------
249   public HTMLTag setStyle(CharSequence inValue)
250   {
251      setAttribute(HTML.STYLE, inValue);
252      return this;
253   }
254
255   //--------------------------------------------------------------------------
256   public String getStyle()
257   {
258      return getAttributeValue(HTML.STYLE);
259   }
260
261   //--------------------------------------------------------------------------
262   public HTMLTag addStyle(String inValue)
263   {
264      CssUtil.addStyle(this, inValue);
265      return this;
266   }
267
268   //--------------------------------------------------------------------------
269   public HTMLTag removeStyleProperty(CSSProperty inValue)
270   {
271      CssUtil.removeStyleProperty(this, inValue);
272      return this;
273   }
274
275   //--------------------------------------------------------------------------
276   public HTMLTag applyColorSpec(ColorSpec inValue)
277   {
278      if (inValue != null)
279      {
280         if (inValue.getForegroundColor() != null)
281         {
282            addStyle(CSS.color(inValue.getForegroundColor()));
283         }
284
285         if (inValue.getBackgroundColor() != null)
286         {
287            addStyle(CSS.bgColor(inValue.getBackgroundColor()));
288         }
289      }
290
291      return this;
292   }
293
294   //--------------------------------------------------------------------------
295   /**
296    Sets the CSS color declaration in the existing style attribute content or,
297    if no color declaration exists, one is added.
298    * @param inValue Color value to use. A value of null will cause an existing
299    *                color declaration to be removed.
300    * @return this HTMLTag object to facilitate method chaining
301    */
302   public HTMLTag setStyleColor(Color inValue)
303   {
304      String style = getStyle();
305      boolean found = false;
306      if (StringUtil.isSet(style))
307      {
308         List<CSSDeclaration> cssDeclarations = CSSDeclaration.parse(style);
309         for (CSSDeclaration cssDeclaration : cssDeclarations)
310         {
311            if (cssDeclaration.getProperty().equals(CSSProperty.color))
312            {
313               found = true;
314               if (inValue != null)
315               {
316                  cssDeclaration.setValue(CssUtil.colorToCssValue(inValue));
317               }
318               else
319               {
320                  cssDeclarations.remove(cssDeclaration);
321               }
322
323               setStyle(StringUtil.join(cssDeclarations, ";"));
324               break;
325            }
326         }
327      }
328
329      if (! found
330          && inValue != null)
331      {
332         addStyle(CSS.color(inValue));
333      }
334
335      return this;
336   }
337
338   //--------------------------------------------------------------------------
339   public HTMLTag setDraggable(boolean inValue)
340   {
341      setAttribute(HTML.DRAGGABLE, inValue);
342      return this;
343   }
344
345   //--------------------------------------------------------------------------
346   public boolean getDraggable()
347   {
348      return BooleanUtil.valueOf(getAttributeValue(HTML.DRAGGABLE));
349   }
350
351   //---------------------------------------------------------------------------
352   protected void initFromXMLNode(XMLNode inXMLNode)
353   {
354      inXMLNode.verifyTagName(getTagName(), Case.INSENSITIVE);
355
356      if (CollectionUtil.hasValues(inXMLNode.getAttributes()))
357      {
358         for (XMLAttribute attr : inXMLNode.getAttributes())
359         {
360            setAttribute(attr.clone());
361         }
362      }
363
364      if (inXMLNode instanceof XMLTag)
365      {
366         XMLTag xmlTag = (XMLTag) inXMLNode;
367         List subtagsAndContent = xmlTag.getContentPlusSubtagList();
368         if (CollectionUtil.hasValues(subtagsAndContent))
369         {
370            for (Object object : subtagsAndContent)
371            {
372               if (object instanceof XMLTag)
373               {
374                  addSubtag(HTML.constructFromXMLNode((XMLNode) object));
375               }
376               else
377               {
378                  // Process the content
379                  String content;
380                  if (object instanceof byte[])
381                  {
382                     // Byte arrays are gzip compressed text chunks
383                     content = GZIP.uncompressToString((byte[]) object);
384                  }
385                  else if (object instanceof String)
386                  {
387                     content = (String) object;
388                  }
389                  else
390                  {
391                     content = object.toString();
392                  }
393
394                  addContent(content);
395               }
396            }
397         }
398      }
399   }
400
401}