001package com.hfg.xml;
002
003
004import java.util.Map;
005import java.util.HashMap;
006import java.util.regex.Pattern;
007import java.util.regex.Matcher;
008
009import org.w3c.dom.NamedNodeMap;
010
011import com.hfg.util.CompareUtil;
012import com.hfg.util.StringBuilderPlus;
013import com.hfg.util.mime.MimeType;
014
015//------------------------------------------------------------------------------
016/**
017 * Enumeration of common DOCTYPE tags.
018 *
019 * @see <a href='http://www.w3.org/QA/2002/04/valid-dtd-list.html'>W3C's recommended DTDs to use in your Web document.</a>
020 *
021 * @author J. Alex Taylor, hairyfatguy.com
022 */
023//------------------------------------------------------------------------------
024// com.hfg XML/HTML Coding Library
025//
026// This library is free software; you can redistribute it and/or
027// modify it under the terms of the GNU Lesser General Public
028// License as published by the Free Software Foundation; either
029// version 2.1 of the License, or (at your option) any later version.
030//
031// This library is distributed in the hope that it will be useful,
032// but WITHOUT ANY WARRANTY; without even the implied warranty of
033// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
034// Lesser General Public License for more details.
035//
036// You should have received a copy of the GNU Lesser General Public
037// License along with this library; if not, write to the Free Software
038// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
039//
040// J. Alex Taylor, President, Founder, CEO, COO, CFO, OOPS hairyfatguy.com
041// jataylor@hairyfatguy.com
042//------------------------------------------------------------------------------
043
044//   <!DOCTYPE
045//             HTML                                          // name
046//             PUBLIC
047//             "-//W3C//DTD HTML 4.01//EN"                   // publicId
048//             "http://www.w3.org/TR/html4/strict.dtd"       // systemId
049//              [                                            // internal subset in square brackets
050//               <!ENTITY Zizek "&Zcaron;i&zcaron;ek">
051//               <!ENTITY Mocnik "Mo&ccaron;nik">
052//              ]>
053
054                                      // DocumentType,
055public class Doctype implements Comparable<Doctype>
056{
057
058   //###########################################################################
059   // PRIVATE FIELDS
060   //###########################################################################
061
062   private String   mString;
063   private String   mName;
064   private String   mPublicId;
065   private String   mSystemId;
066   private MimeType mMimeType;
067
068   private static Map<String, Doctype> sUniqueMap = new HashMap<String, Doctype>(10);
069
070   private static final String  NL = System.getProperty("line.separator");
071
072   //###########################################################################
073   // PUBLIC FIELDS
074   //###########################################################################
075
076   public static final Doctype HTML_4_01_STRICT
077         = new Doctype("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" "
078         + "\"http://www.w3.org/TR/html4/strict.dtd\">", MimeType.TEXT_HTML);
079
080   public static final Doctype HTML_4_01_TRANSITIONAL
081         = new Doctype("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" "
082         + "\"http://www.w3.org/TR/html4/loose.dtd\">", MimeType.TEXT_HTML);
083
084   /** A workaround for IE6 */
085   public static final Doctype HTML_4_01_TRANSITIONAL_NO_URL
086         = new Doctype("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">", MimeType.TEXT_HTML);
087
088   public static final Doctype HTML_4_01_FRAMESET
089         = new Doctype("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Frameset//EN\" "
090         + "\"http://www.w3.org/TR/html4/frameset.dtd\">", MimeType.TEXT_HTML);
091
092   public static final Doctype HTML_4_01_FRAMESET_NO_URL
093         = new Doctype("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Frameset//EN\">", MimeType.TEXT_HTML);
094
095   public static final Doctype HTML_5
096         = new Doctype("<!DOCTYPE html>", MimeType.TEXT_HTML);
097
098   public static final Doctype XHTML_1_0_STRICT
099         = new Doctype("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" "
100         + "\"http://www.w3.org/TR/xhtml1/xhtml1-strict.dtd\">", MimeType.APPLICATION_XHTML);
101
102   public static final Doctype XHTML_1_0_TRANSITIONAL
103         = new Doctype("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" "
104         + "\"http://www.w3.org/TR/xhtml1/xhtml1-transitional.dtd\">", MimeType.APPLICATION_XHTML);
105
106   public static final Doctype XHTML_1_0_TRANSITIONAL_NO_URL
107         = new Doctype("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\">", MimeType.APPLICATION_XHTML);
108
109   public static final Doctype XHTML_1_0_FRAMESET
110         = new Doctype("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Frameset//EN\" "
111         + "\"http://www.w3.org/TR/xhtml1/xhtml1-frameset.dtd\">", MimeType.APPLICATION_XHTML);
112
113   public static final Doctype XHTML_1_1
114         = new Doctype("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\">", MimeType.APPLICATION_XHTML);
115
116   public static final Doctype SVG_1_1
117         = new Doctype("<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">", MimeType.IMAGE_SVG_XML);
118
119   //###########################################################################
120   // CONSTRUCTORS
121   //###########################################################################
122
123   //--------------------------------------------------------------------------
124   public Doctype()
125   {
126
127   }
128
129
130   //--------------------------------------------------------------------------
131   private Doctype(String inValue, MimeType inMimeType)
132   {
133      mString = inValue;
134
135      if (sUniqueMap.containsKey(inValue))
136      {
137         throw new RuntimeException("'" + inValue + "' is the value of an existing Doctype!");
138      }
139
140      Pattern pattern = Pattern.compile("^<\\!DOCTYPE\\s+(\\S+)(?:\\s+PUBLIC\\s+([\"'])(.+?)\\2)?(?:\\s+([\"'])(.+?)\\4)?\\s*>", Pattern.CASE_INSENSITIVE);
141      Matcher m = pattern.matcher(inValue);
142      if (m.find())
143      {
144         mName     = m.group(1);
145         mPublicId = m.group(3);
146         mSystemId = m.group(5);
147      }
148      else
149      {
150         throw new XMLException(inValue + " is not a well-formed DOCTYPE!");
151      }
152
153      sUniqueMap.put(mString, this);
154
155      setMimeType(inMimeType);
156   }
157
158   //###########################################################################
159   // PUBLIC METHODS
160   //###########################################################################
161
162   //--------------------------------------------------------------------------
163   public static Doctype valueOf(String inValue)
164   {
165      Doctype doctype = sUniqueMap.get(inValue);
166      if (null == doctype)
167      {
168         doctype = new Doctype(inValue, null);
169      }
170
171      return doctype;
172   }
173
174   //--------------------------------------------------------------------------
175   public Doctype setMimeType(MimeType inValue)
176   {
177      mMimeType = inValue;
178      return this;
179   }
180
181   //--------------------------------------------------------------------------
182   public MimeType getMimeType()
183   {
184      return mMimeType;
185   }
186
187   //--------------------------------------------------------------------------
188   public Doctype setName(String inValue)
189   {
190      mName = inValue;
191      return this;
192   }
193
194   //--------------------------------------------------------------------------
195   public Doctype setPublicId(String inValue)
196   {
197      mPublicId = inValue;
198      return this;
199   }
200
201   //--------------------------------------------------------------------------
202   public Doctype setSystemId(String inValue)
203   {
204      mSystemId = inValue;
205      return this;
206   }
207
208   //--------------------------------------------------------------------------
209   @Override
210   public final String toString()
211   {
212      StringBuilderPlus buffer = new StringBuilderPlus("<!DOCTYPE").setDelimiter(" ");
213      buffer.delimitedAppend(mName);
214      if (mPublicId != null) buffer.delimitedAppend("PUBLIC \"" + mPublicId + "\"");
215      if (mSystemId != null) buffer.delimitedAppend("\"" + mSystemId + "\"");
216      buffer.append(">");
217
218      return buffer.toString();
219   }
220
221   //--------------------------------------------------------------------------
222   @Override
223   public final int hashCode()
224   {
225      int value = 0;
226      if (name() != null)
227      {
228         value += 31 * name().hashCode();
229      }
230
231      if (getPublicId() != null)
232      {
233         value += 31 * getPublicId().hashCode();
234      }
235
236      if (getSystemId() != null)
237      {
238         value += 31 * getSystemId().hashCode();
239      }
240
241      return value;
242   }
243
244   //--------------------------------------------------------------------------
245   @Override
246   public final boolean equals(Object inObj)
247   {
248      return (inObj != null
249              && inObj instanceof Doctype
250              && 0 == compareTo((Doctype) inObj));
251   }
252
253   //--------------------------------------------------------------------------
254   public int compareTo(Doctype inObj2)
255   {
256      int result = -1;
257      if (inObj2 != null)
258      {
259         result = CompareUtil.compare(name(), inObj2.name());
260
261         if (0 == result)
262         {
263            result = CompareUtil.compare(getPublicId(), inObj2.getPublicId());
264         }
265
266         if (0 == result)
267         {
268            result = CompareUtil.compare(getSystemId(), inObj2.getSystemId());
269         }
270      }
271
272      return result;
273   }
274
275   // Methods to fulfill the DocumentType interface
276
277   //--------------------------------------------------------------------------
278   /**
279    The name of DTD; the name immediately following the DOCTYPE keyword.
280    */
281   public String name()
282   {
283      return mName;
284   }
285
286   //--------------------------------------------------------------------------
287   public NamedNodeMap getEntities()
288   {
289      return null;  // TODO
290   }
291
292   //--------------------------------------------------------------------------
293   public NamedNodeMap getNotations()
294   {
295      return null;  // TODO
296   }
297
298   //--------------------------------------------------------------------------
299   /**
300    The public identifier of the external subset.
301    */
302   public String getPublicId()
303   {
304      return mPublicId;
305   }
306
307   //--------------------------------------------------------------------------
308   /**
309    The system identifier of the external subset.
310    */
311   public String getSystemId()
312   {
313      return mSystemId;
314   }
315
316   //--------------------------------------------------------------------------
317   /**
318    The internal subset as a string, or null if there is none.
319    This is does not contain the delimiting square brackets.
320    The actual content returned depends on how much information is available to the implementation.
321    This may vary depending on various parameters, including the XML processor used to build the document.
322    */
323   public String getInternalSubset()
324   {
325      return null;  // TODO
326   }
327}