Back to index

lightning-sunbird  0.9+nobinonly
txHTMLOutput.cpp
Go to the documentation of this file.
00001 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
00002 /* ***** BEGIN LICENSE BLOCK *****
00003  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00004  *
00005  * The contents of this file are subject to the Mozilla Public License Version
00006  * 1.1 (the "License"); you may not use this file except in compliance with
00007  * the License. You may obtain a copy of the License at
00008  * http://www.mozilla.org/MPL/
00009  *
00010  * Software distributed under the License is distributed on an "AS IS" basis,
00011  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00012  * for the specific language governing rights and limitations under the
00013  * License.
00014  *
00015  * The Original Code is TransforMiiX XSLT processor code.
00016  *
00017  * The Initial Developer of the Original Code is
00018  * Netscape Communications Corporation.
00019  * Portions created by the Initial Developer are Copyright (C) 2001
00020  * the Initial Developer. All Rights Reserved.
00021  *
00022  * Contributor(s):
00023  *   Peter Van der Beken <peterv@propagandism.org>
00024  *
00025  * Alternatively, the contents of this file may be used under the terms of
00026  * either the GNU General Public License Version 2 or later (the "GPL"), or
00027  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00028  * in which case the provisions of the GPL or the LGPL are applicable instead
00029  * of those above. If you wish to allow use of your version of this file only
00030  * under the terms of either the GPL or the LGPL, and not to allow others to
00031  * use your version of this file under the terms of the MPL, indicate your
00032  * decision by deleting the provisions above and replace them with the notice
00033  * and other provisions required by the GPL or the LGPL. If you do not delete
00034  * the provisions above, a recipient may use your version of this file under
00035  * the terms of any one of the MPL, the GPL or the LGPL.
00036  *
00037  * ***** END LICENSE BLOCK ***** */
00038 
00039 #include "txHTMLOutput.h"
00040 #include "nsCOMArray.h"
00041 #include "nsStaticNameTable.h"
00042 #include "txAtoms.h"
00043 #include "txOutputFormat.h"
00044 #include "txStringUtils.h"
00045 #include "XMLUtils.h"
00046 
00047 #define EMPTY_ELEMENTS_COUNT 13
00048 const char* const kHTMLEmptyTags[] =
00049 {
00050     "area",
00051     "base",
00052     "basefont",
00053     "br",
00054     "col",
00055     "frame",
00056     "hr",
00057     "img",
00058     "input",
00059     "isindex",
00060     "link",
00061     "meta",
00062     "param"
00063 };
00064 
00065 #define SHORTHAND_ATTR_COUNT 12
00066 const char* const kHTMLEmptyAttributes[] =
00067 {
00068     "checked",
00069     "compact",
00070     "declare",
00071     "defer",
00072     "disabled",
00073     "ismap",
00074     "multiple",
00075     "noresize",
00076     "noshade",
00077     "nowrap",
00078     "readonly",
00079     "selected"
00080 };
00081 
00082 struct txEmptyAttributesMaps
00083 {
00084     typedef nsCOMArray<nsIAtom> EmptyAttrBag;
00085     EmptyAttrBag mMaps[SHORTHAND_ATTR_COUNT];
00086 };
00087 
00088 static PRInt32 gTableRefCount;
00089 static nsStaticCaseInsensitiveNameTable* gHTMLEmptyTagsTable;
00090 static nsStaticCaseInsensitiveNameTable* gHTMLEmptyAttributesTable;
00091 static txEmptyAttributesMaps* gHTMLEmptyAttributesMaps;
00092 
00093 /* static */
00094 nsresult
00095 txHTMLOutput::init()
00096 {
00097     if (0 == gTableRefCount++) {
00098         NS_ASSERTION(!gHTMLEmptyTagsTable, "pre existing array!");
00099         gHTMLEmptyTagsTable = new nsStaticCaseInsensitiveNameTable();
00100         if (!gHTMLEmptyTagsTable) {
00101             return NS_ERROR_OUT_OF_MEMORY;
00102         }
00103 
00104         gHTMLEmptyTagsTable->Init(kHTMLEmptyTags, EMPTY_ELEMENTS_COUNT);
00105 
00106         NS_ASSERTION(!gHTMLEmptyAttributesTable, "pre existing array!");
00107         gHTMLEmptyAttributesTable = new nsStaticCaseInsensitiveNameTable();
00108         if (!gHTMLEmptyAttributesTable) {
00109             return NS_ERROR_OUT_OF_MEMORY;
00110         }
00111 
00112         gHTMLEmptyAttributesTable->Init(kHTMLEmptyAttributes,
00113                                         SHORTHAND_ATTR_COUNT);
00114 
00115         NS_ASSERTION(!gHTMLEmptyAttributesMaps, "pre existing map!");
00116         gHTMLEmptyAttributesMaps = new txEmptyAttributesMaps();
00117         if (!gHTMLEmptyAttributesMaps) {
00118             return NS_ERROR_OUT_OF_MEMORY;
00119         }
00120 
00121         // checked
00122         gHTMLEmptyAttributesMaps->mMaps[0].AppendObject(txHTMLAtoms::input);
00123 
00124         // compact
00125         gHTMLEmptyAttributesMaps->mMaps[1].AppendObject(txHTMLAtoms::dir);
00126         gHTMLEmptyAttributesMaps->mMaps[1].AppendObject(txHTMLAtoms::dl);
00127         gHTMLEmptyAttributesMaps->mMaps[1].AppendObject(txHTMLAtoms::menu);
00128         gHTMLEmptyAttributesMaps->mMaps[1].AppendObject(txHTMLAtoms::ol);
00129         gHTMLEmptyAttributesMaps->mMaps[1].AppendObject(txHTMLAtoms::ul);
00130 
00131         // declare
00132         gHTMLEmptyAttributesMaps->mMaps[2].AppendObject(txHTMLAtoms::object);
00133 
00134         // defer
00135         gHTMLEmptyAttributesMaps->mMaps[3].AppendObject(txHTMLAtoms::script);
00136 
00137         // disabled
00138         gHTMLEmptyAttributesMaps->mMaps[4].AppendObject(txHTMLAtoms::button);
00139         gHTMLEmptyAttributesMaps->mMaps[4].AppendObject(txHTMLAtoms::input);
00140         gHTMLEmptyAttributesMaps->mMaps[4].AppendObject(txHTMLAtoms::optgroup);
00141         gHTMLEmptyAttributesMaps->mMaps[4].AppendObject(txHTMLAtoms::option);
00142         gHTMLEmptyAttributesMaps->mMaps[4].AppendObject(txHTMLAtoms::select);
00143         gHTMLEmptyAttributesMaps->mMaps[4].AppendObject(txHTMLAtoms::textarea);
00144 
00145         // ismap
00146         gHTMLEmptyAttributesMaps->mMaps[5].AppendObject(txHTMLAtoms::img);
00147         gHTMLEmptyAttributesMaps->mMaps[5].AppendObject(txHTMLAtoms::input);
00148 
00149         // multiple
00150         gHTMLEmptyAttributesMaps->mMaps[6].AppendObject(txHTMLAtoms::select);
00151 
00152         // noresize
00153         gHTMLEmptyAttributesMaps->mMaps[7].AppendObject(txHTMLAtoms::frame);
00154 
00155         // noshade
00156         gHTMLEmptyAttributesMaps->mMaps[8].AppendObject(txHTMLAtoms::hr);
00157 
00158         // nowrap
00159         gHTMLEmptyAttributesMaps->mMaps[9].AppendObject(txHTMLAtoms::td);
00160         gHTMLEmptyAttributesMaps->mMaps[9].AppendObject(txHTMLAtoms::th);
00161 
00162         // readonly
00163         gHTMLEmptyAttributesMaps->mMaps[10].AppendObject(txHTMLAtoms::input);
00164         gHTMLEmptyAttributesMaps->mMaps[10].AppendObject(txHTMLAtoms::textarea);
00165 
00166         // selected
00167         gHTMLEmptyAttributesMaps->mMaps[11].AppendObject(txHTMLAtoms::option);
00168     }
00169 
00170     return NS_OK;
00171 }
00172 
00173 /* static */
00174 void
00175 txHTMLOutput::shutdown()
00176 {
00177     if (0 == --gTableRefCount) {
00178         if (gHTMLEmptyTagsTable) {
00179             delete gHTMLEmptyTagsTable;
00180             gHTMLEmptyTagsTable = nsnull;
00181         }
00182          if (gHTMLEmptyAttributesTable) {
00183             delete gHTMLEmptyAttributesTable;
00184             gHTMLEmptyAttributesTable = nsnull;
00185         }
00186         if (gHTMLEmptyAttributesMaps) {
00187             delete gHTMLEmptyAttributesMaps;
00188             gHTMLEmptyAttributesMaps = nsnull;
00189         }
00190    }
00191 }
00192 
00193 txHTMLOutput::txHTMLOutput(txOutputFormat* aFormat, ostream* aOut)
00194     : txXMLOutput(aFormat, aOut)
00195 {
00196     mUseEmptyElementShorthand = PR_FALSE;
00197 }
00198 
00199 txHTMLOutput::~txHTMLOutput()
00200 {
00201 }
00202 
00203 void txHTMLOutput::attribute(const nsAString& aName,
00204                              const PRInt32 aNsID,
00205                              const nsAString& aValue)
00206 {
00207     if (!mStartTagOpen)
00208         // XXX Signal this? (can't add attributes after element closed)
00209         return;
00210 
00211     MBool shortHand = MB_FALSE;
00212     if (aNsID == kNameSpaceID_None) {
00213         const nsAString& localPart = XMLUtils::getLocalPart(aName);
00214         shortHand = isShorthandAttribute(localPart);
00215         if (shortHand &&
00216             localPart.Equals(aValue, txCaseInsensitiveStringComparator())) {
00217             txListIterator iter(&mAttributes);
00218             txOutAttr* setAtt = 0;
00219             nsCOMPtr<nsIAtom> localName = do_GetAtom(localPart);
00220             txExpandedName att(aNsID, localName);
00221             while ((setAtt = (txOutAttr*)iter.next())) {
00222                  if (setAtt->mName == att) {
00223                      setAtt->mShorthand = MB_TRUE;
00224                      break;
00225                  }
00226             }
00227             if (!setAtt) {
00228                 setAtt = new txOutAttr(aNsID, localName, EmptyString());
00229                 setAtt->mShorthand = MB_TRUE;
00230                 mAttributes.add(setAtt);
00231             }
00232         }
00233     }
00234     if (!shortHand)
00235         txXMLOutput::attribute(aName, aNsID, aValue);
00236 }
00237 
00238 void txHTMLOutput::characters(const nsAString& aData, PRBool aDOE)
00239 {
00240     if (aDOE) {
00241         closeStartTag(MB_FALSE);
00242         printUTF8Chars(aData);
00243 
00244         return;
00245     }
00246 
00247     // Special-case script and style
00248     if (!mCurrentElements.isEmpty()) {
00249         txExpandedName* currentElement = (txExpandedName*)mCurrentElements.peek();
00250         if (currentElement->mNamespaceID == kNameSpaceID_None &&
00251             (currentElement->mLocalName == txHTMLAtoms::script ||
00252              currentElement->mLocalName == txHTMLAtoms::style)) {
00253             closeStartTag(MB_FALSE);
00254             printUTF8Chars(aData);
00255             return;
00256         }
00257     }
00258     txXMLOutput::characters(aData, aDOE);
00259 }
00260 
00261 void txHTMLOutput::endElement(const nsAString& aName,
00262                               const PRInt32 aNsID)
00263 {
00264     const nsAString& localPart = XMLUtils::getLocalPart(aName);
00265     if ((aNsID == kNameSpaceID_None) && isShorthandElement(localPart) &&
00266         mStartTagOpen) {
00267         MBool newLine = (mOutputFormat.mIndent == eTrue) &&
00268                         mAfterEndTag;
00269         closeStartTag(MB_FALSE);
00270         if (newLine)
00271             *mOut << endl;
00272         if (mOutputFormat.mIndent == eTrue)
00273             mIndentLevel -= DEFAULT_INDENT;
00274         mAfterEndTag = MB_TRUE;
00275     }
00276     else {
00277         txXMLOutput::endElement(aName, aNsID);
00278     }
00279     delete (txExpandedName*)mCurrentElements.pop();
00280 }
00281 
00282 void txHTMLOutput::processingInstruction(const nsAString& aTarget,
00283                                          const nsAString& aData)
00284 {
00285     closeStartTag(MB_FALSE);
00286     if (mOutputFormat.mIndent == eTrue) {
00287         for (PRUint32 i = 0; i < mIndentLevel; i++)
00288             *mOut << ' ';
00289     }
00290     *mOut << PI_START;
00291     printUTF8Chars(aTarget);
00292     *mOut << SPACE;
00293     printUTF8Chars(aData);
00294     *mOut << R_ANGLE_BRACKET;
00295     if (mOutputFormat.mIndent == eTrue)
00296         *mOut << endl;
00297 }
00298 
00299 void txHTMLOutput::startDocument()
00300 {
00301     // XXX Should be using mOutputFormat.getVersion
00302     *mOut << DOCTYPE_START << "html " << PUBLIC;
00303     *mOut << " \"-//W3C//DTD HTML 4.0 Transitional//EN\"";
00304     *mOut << " \"http://www.w3.org/TR/REC-html40/loose.dtd\"";
00305     *mOut << DOCTYPE_END << endl;
00306 }
00307 
00308 void txHTMLOutput::startElement(const nsAString& aName,
00309                                 const PRInt32 aNsID)
00310 {
00311     txXMLOutput::startElement(aName, aNsID);
00312 
00313     nsCOMPtr<nsIAtom> localAtom;
00314     if (aNsID == kNameSpaceID_None) {
00315         nsAutoString localName;
00316         TX_ToLowerCase(aName, localName);
00317         localAtom = do_GetAtom(localName);
00318     }
00319     else {
00320         localAtom = do_GetAtom(aName);
00321     }
00322     NS_ASSERTION(localAtom, "Can't get atom");
00323     txExpandedName* currentElement = new txExpandedName(aNsID, localAtom);
00324     NS_ASSERTION(currentElement, "Can't create currentElement");
00325     if (currentElement)
00326         mCurrentElements.push(currentElement);
00327 }
00328 
00329 void txHTMLOutput::closeStartTag(MBool aUseEmptyElementShorthand)
00330 {
00331     txExpandedName* currentElement = mCurrentElements.isEmpty() ?
00332         nsnull : (txExpandedName*)mCurrentElements.peek();
00333     if (mStartTagOpen && currentElement &&
00334         (currentElement->mNamespaceID == kNameSpaceID_None) &&
00335         (currentElement->mLocalName == txHTMLAtoms::head)) {
00336         txXMLOutput::closeStartTag(MB_FALSE);
00337         if (mOutputFormat.mIndent == eTrue) {
00338             *mOut << endl;
00339             for (PRUint32 i = 0; i < mIndentLevel; i++)
00340                 *mOut << ' ';
00341         }
00342         *mOut << LT << "meta http-equiv=" << QUOTE << "Content-Type" << QUOTE;
00343         *mOut << " content=" << QUOTE;
00344         printUTF8Chars(mOutputFormat.mMediaType);
00345         *mOut << "; charset=";
00346         printUTF8Chars(mOutputFormat.mEncoding);
00347         *mOut << QUOTE << GT;
00348     }
00349     else {
00350         txXMLOutput::closeStartTag(aUseEmptyElementShorthand);
00351     }
00352 }
00353 
00354 MBool txHTMLOutput::isShorthandElement(const nsAString& aLocalName)
00355 {
00356     return (gHTMLEmptyTagsTable->Lookup(aLocalName) !=
00357             nsStaticCaseInsensitiveNameTable::NOT_FOUND);
00358 }
00359 
00360 MBool txHTMLOutput::isShorthandAttribute(const nsAString& aLocalName)
00361 {
00362     PRInt32 index = gHTMLEmptyTagsTable->Lookup(aLocalName);
00363     if (index == nsStaticCaseInsensitiveNameTable::NOT_FOUND) {
00364         return PR_FALSE;
00365     }
00366 
00367     txExpandedName* currentElement = (txExpandedName*)mCurrentElements.peek();
00368     return (gHTMLEmptyAttributesMaps->mMaps[index].IndexOf(currentElement->mLocalName) > -1);
00369 }