Back to index

lightning-sunbird  0.9+nobinonly
nsXMLContentSerializer.cpp
Go to the documentation of this file.
00001 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
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 mozilla.org 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) 1998
00020  * the Initial Developer. All Rights Reserved.
00021  *
00022  * Contributor(s):
00023  *
00024  * Alternatively, the contents of this file may be used under the terms of
00025  * either of the GNU General Public License Version 2 or later (the "GPL"),
00026  * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00027  * in which case the provisions of the GPL or the LGPL are applicable instead
00028  * of those above. If you wish to allow use of your version of this file only
00029  * under the terms of either the GPL or the LGPL, and not to allow others to
00030  * use your version of this file under the terms of the MPL, indicate your
00031  * decision by deleting the provisions above and replace them with the notice
00032  * and other provisions required by the GPL or the LGPL. If you do not delete
00033  * the provisions above, a recipient may use your version of this file under
00034  * the terms of any one of the MPL, the GPL or the LGPL.
00035  *
00036  * ***** END LICENSE BLOCK ***** */
00037 
00038 #include "nsXMLContentSerializer.h"
00039 
00040 #include "nsHTMLAtoms.h"
00041 #include "nsIDOMText.h"
00042 #include "nsIDOMCDATASection.h"
00043 #include "nsIDOMProcessingInstruction.h"
00044 #include "nsIDOMComment.h"
00045 #include "nsIDOMDocument.h"
00046 #include "nsIDOMDocumentType.h"
00047 #include "nsIDOMElement.h"
00048 #include "nsIContent.h"
00049 #include "nsIDocument.h"
00050 #include "nsINameSpaceManager.h"
00051 #include "nsITextContent.h"
00052 #include "nsTextFragment.h"
00053 #include "nsString.h"
00054 #include "prprf.h"
00055 #include "nsUnicharUtils.h"
00056 #include "nsCRT.h"
00057 #include "nsContentUtils.h"
00058 #include "nsLayoutAtoms.h"
00059 
00060 typedef struct {
00061   nsString mPrefix;
00062   nsString mURI;
00063   nsIDOMElement* mOwner;
00064 } NameSpaceDecl;
00065 
00066 nsresult NS_NewXMLContentSerializer(nsIContentSerializer** aSerializer)
00067 {
00068   nsXMLContentSerializer* it = new nsXMLContentSerializer();
00069   if (!it) {
00070     return NS_ERROR_OUT_OF_MEMORY;
00071   }
00072 
00073   return CallQueryInterface(it, aSerializer);
00074 }
00075 
00076 nsXMLContentSerializer::nsXMLContentSerializer()
00077   : mPrefixIndex(0),
00078     mInAttribute(PR_FALSE),
00079     mAddNewline(PR_FALSE)
00080 {
00081 }
00082  
00083 nsXMLContentSerializer::~nsXMLContentSerializer()
00084 {
00085 }
00086 
00087 NS_IMPL_ISUPPORTS1(nsXMLContentSerializer, nsIContentSerializer)
00088 
00089 NS_IMETHODIMP 
00090 nsXMLContentSerializer::Init(PRUint32 flags, PRUint32 aWrapColumn,
00091                              const char* aCharSet, PRBool aIsCopying)
00092 {
00093   return NS_OK;
00094 }
00095 
00096 nsresult
00097 nsXMLContentSerializer::AppendTextData(nsIDOMNode* aNode, 
00098                                        PRInt32 aStartOffset,
00099                                        PRInt32 aEndOffset,
00100                                        nsAString& aStr,
00101                                        PRBool aTranslateEntities,
00102                                        PRBool aIncrColumn)
00103 {
00104   nsCOMPtr<nsITextContent> content(do_QueryInterface(aNode));
00105   if (!content) return NS_ERROR_FAILURE;
00106 
00107   const nsTextFragment* frag = content->Text();
00108 
00109   PRInt32 endoffset = (aEndOffset == -1) ? frag->GetLength() : aEndOffset;
00110   PRInt32 length = endoffset - aStartOffset;
00111 
00112   NS_ASSERTION(aStartOffset >= 0, "Negative start offset for text fragment!");
00113   NS_ASSERTION(aStartOffset <= endoffset, "A start offset is beyond the end of the text fragment!");
00114 
00115   if (length <= 0) {
00116     // XXX Zero is a legal value, maybe non-zero values should be an
00117     // error.
00118 
00119     return NS_OK;
00120   }
00121     
00122   if (frag->Is2b()) {
00123     const PRUnichar *strStart = frag->Get2b() + aStartOffset;
00124     AppendToString(Substring(strStart, strStart + length), aStr,
00125                    aTranslateEntities, aIncrColumn);
00126   }
00127   else {
00128     AppendToString(NS_ConvertASCIItoUCS2(frag->Get1b()+aStartOffset, length),
00129                    aStr, aTranslateEntities, aIncrColumn);
00130   }
00131 
00132   return NS_OK;
00133 }
00134 
00135 NS_IMETHODIMP 
00136 nsXMLContentSerializer::AppendText(nsIDOMText* aText, 
00137                                    PRInt32 aStartOffset,
00138                                    PRInt32 aEndOffset,
00139                                    nsAString& aStr)
00140 {
00141   NS_ENSURE_ARG(aText);
00142 
00143   return AppendTextData(aText, aStartOffset, aEndOffset, aStr, PR_TRUE, PR_TRUE);
00144 }
00145 
00146 NS_IMETHODIMP 
00147 nsXMLContentSerializer::AppendCDATASection(nsIDOMCDATASection* aCDATASection,
00148                                            PRInt32 aStartOffset,
00149                                            PRInt32 aEndOffset,
00150                                            nsAString& aStr)
00151 {
00152   NS_ENSURE_ARG(aCDATASection);
00153   nsresult rv;
00154 
00155   AppendToString(NS_LITERAL_STRING("<![CDATA["), aStr);
00156   rv = AppendTextData(aCDATASection, aStartOffset, aEndOffset, aStr, PR_FALSE, PR_TRUE);
00157   if (NS_FAILED(rv)) return NS_ERROR_FAILURE;  
00158   AppendToString(NS_LITERAL_STRING("]]>"), aStr);
00159 
00160   return NS_OK;
00161 }
00162 
00163 NS_IMETHODIMP 
00164 nsXMLContentSerializer::AppendProcessingInstruction(nsIDOMProcessingInstruction* aPI,
00165                                                     PRInt32 aStartOffset,
00166                                                     PRInt32 aEndOffset,
00167                                                     nsAString& aStr)
00168 {
00169   NS_ENSURE_ARG(aPI);
00170   nsresult rv;
00171   nsAutoString target, data;
00172 
00173   MaybeAddNewline(aStr);
00174 
00175   rv = aPI->GetTarget(target);
00176   if (NS_FAILED(rv)) return NS_ERROR_FAILURE;
00177 
00178   rv = aPI->GetData(data);
00179   if (NS_FAILED(rv)) return NS_ERROR_FAILURE;
00180 
00181   AppendToString(NS_LITERAL_STRING("<?"), aStr);
00182   AppendToString(target, aStr);
00183   if (!data.IsEmpty()) {
00184     AppendToString(NS_LITERAL_STRING(" "), aStr);
00185     AppendToString(data, aStr);
00186   }
00187   AppendToString(NS_LITERAL_STRING("?>"), aStr);
00188   MaybeFlagNewline(aPI);
00189   
00190   return NS_OK;
00191 }
00192 
00193 NS_IMETHODIMP 
00194 nsXMLContentSerializer::AppendComment(nsIDOMComment* aComment,
00195                                       PRInt32 aStartOffset,
00196                                       PRInt32 aEndOffset,
00197                                       nsAString& aStr)
00198 {
00199   NS_ENSURE_ARG(aComment);
00200   nsresult rv;
00201   nsAutoString data;
00202 
00203   rv = aComment->GetData(data);
00204   if (NS_FAILED(rv)) return NS_ERROR_FAILURE;
00205 
00206   MaybeAddNewline(aStr);
00207 
00208   AppendToString(NS_LITERAL_STRING("<!--"), aStr);
00209   if (aStartOffset || (aEndOffset != -1)) {
00210     PRInt32 length = (aEndOffset == -1) ? data.Length() : aEndOffset;
00211     length -= aStartOffset;
00212 
00213     nsAutoString frag;
00214     data.Mid(frag, aStartOffset, length);
00215     AppendToString(frag, aStr);
00216   }
00217   else {
00218     AppendToString(data, aStr);
00219   }
00220   AppendToString(NS_LITERAL_STRING("-->"), aStr);
00221   MaybeFlagNewline(aComment);
00222   
00223   return NS_OK;
00224 }
00225 
00226 NS_IMETHODIMP 
00227 nsXMLContentSerializer::AppendDoctype(nsIDOMDocumentType *aDoctype,
00228                                       nsAString& aStr)
00229 {
00230   NS_ENSURE_ARG(aDoctype);
00231   nsresult rv;
00232   nsAutoString name, publicId, systemId, internalSubset;
00233 
00234   rv = aDoctype->GetName(name);
00235   if (NS_FAILED(rv)) return NS_ERROR_FAILURE;
00236   rv = aDoctype->GetPublicId(publicId);
00237   if (NS_FAILED(rv)) return NS_ERROR_FAILURE;
00238   rv = aDoctype->GetSystemId(systemId);
00239   if (NS_FAILED(rv)) return NS_ERROR_FAILURE;
00240   rv = aDoctype->GetInternalSubset(internalSubset);
00241   if (NS_FAILED(rv)) return NS_ERROR_FAILURE;
00242 
00243   MaybeAddNewline(aStr);
00244 
00245   AppendToString(NS_LITERAL_STRING("<!DOCTYPE "), aStr);
00246   AppendToString(name, aStr);
00247   PRUnichar quote;
00248   if (!publicId.IsEmpty()) {
00249     AppendToString(NS_LITERAL_STRING(" PUBLIC "), aStr);
00250     if (publicId.FindChar(PRUnichar('"')) == -1) {
00251       quote = PRUnichar('"');
00252     }
00253     else {
00254       quote = PRUnichar('\'');
00255     }
00256     AppendToString(quote, aStr);
00257     AppendToString(publicId, aStr);
00258     AppendToString(quote, aStr);
00259 
00260     if (!systemId.IsEmpty()) {
00261       AppendToString(PRUnichar(' '), aStr);
00262       if (systemId.FindChar(PRUnichar('"')) == -1) {
00263         quote = PRUnichar('"');
00264       }
00265       else {
00266         quote = PRUnichar('\'');
00267       }
00268       AppendToString(quote, aStr);
00269       AppendToString(systemId, aStr);
00270       AppendToString(quote, aStr);
00271     }
00272   }
00273   else if (!systemId.IsEmpty()) {
00274     if (systemId.FindChar(PRUnichar('"')) == -1) {
00275       quote = PRUnichar('"');
00276     }
00277     else {
00278       quote = PRUnichar('\'');
00279     }
00280     AppendToString(NS_LITERAL_STRING(" SYSTEM "), aStr);
00281     AppendToString(quote, aStr);
00282     AppendToString(systemId, aStr);
00283     AppendToString(quote, aStr);
00284   }
00285   
00286   if (!internalSubset.IsEmpty()) {
00287     AppendToString(NS_LITERAL_STRING(" ["), aStr);
00288     AppendToString(internalSubset, aStr);
00289     AppendToString(PRUnichar(']'), aStr);
00290   }
00291     
00292   AppendToString(PRUnichar('>'), aStr);
00293   MaybeFlagNewline(aDoctype);
00294 
00295   return NS_OK;
00296 }
00297 
00298 #define kXMLNS "xmlns"
00299 
00300 nsresult
00301 nsXMLContentSerializer::PushNameSpaceDecl(const nsAString& aPrefix,
00302                                           const nsAString& aURI,
00303                                           nsIDOMElement* aOwner)
00304 {
00305   NameSpaceDecl* decl = new NameSpaceDecl();
00306   if (!decl) return NS_ERROR_OUT_OF_MEMORY;
00307 
00308   decl->mPrefix.Assign(aPrefix);
00309   decl->mURI.Assign(aURI);
00310   // Don't addref - this weak reference will be removed when
00311   // we pop the stack
00312   decl->mOwner = aOwner;
00313 
00314   mNameSpaceStack.AppendElement((void*)decl);
00315   return NS_OK;
00316 }
00317 
00318 void
00319 nsXMLContentSerializer::PopNameSpaceDeclsFor(nsIDOMElement* aOwner)
00320 {
00321   PRInt32 index, count;
00322 
00323   count = mNameSpaceStack.Count();
00324   for (index = count - 1; index >= 0; index--) {
00325     NameSpaceDecl* decl = (NameSpaceDecl*)mNameSpaceStack.ElementAt(index);
00326     if (decl->mOwner != aOwner) {
00327       break;
00328     }
00329     mNameSpaceStack.RemoveElementAt(index);
00330     delete decl;
00331   }
00332 }
00333 
00334 PRBool
00335 nsXMLContentSerializer::ConfirmPrefix(nsAString& aPrefix,
00336                                       const nsAString& aURI,
00337                                       nsIDOMElement* aElement,
00338                                       PRBool aMustHavePrefix)
00339 {
00340   if (aPrefix.EqualsLiteral(kXMLNS) ||
00341       (aPrefix.EqualsLiteral("xml") &&
00342        aURI.EqualsLiteral("http://www.w3.org/XML/1998/namespace"))) {
00343     return PR_FALSE;
00344   }
00345   if (aURI.IsEmpty()) {
00346     aPrefix.Truncate();
00347     return PR_FALSE;
00348   }
00349 
00350   nsAutoString closestURIMatch;
00351   PRBool uriMatch = PR_FALSE;
00352 
00353   PRInt32 count = mNameSpaceStack.Count();
00354   PRInt32 index = count - 1;
00355   while (index >= 0) {
00356     NameSpaceDecl* decl = (NameSpaceDecl*)mNameSpaceStack.ElementAt(index);
00357     // Check if we've found a prefix match
00358     if (aPrefix.Equals(decl->mPrefix)) {
00359       
00360       // If the URI's match, we don't have to add a namespace decl
00361       if (aURI.Equals(decl->mURI)) {
00362         return PR_FALSE;
00363       }
00364 
00365       // If they don't, and either:
00366       // 1) We have a prefix (so we'd be redeclaring this prefix to point to a
00367       //    different namespace) or
00368       // 2) We're looking at an existing default namespace decl on aElement (so
00369       //    we can't create a new default namespace decl for this URI)
00370       // then generate a new prefix.  Note that we do NOT generate new prefixes
00371       // if we happen to have aPrefix == decl->mPrefix == "" and mismatching
00372       // URIs when |decl| doesn't have aElement as its owner.  In that case we
00373       // can simply push the new namespace URI as the default namespace for
00374       // aElement.
00375       if (!aPrefix.IsEmpty() ||
00376           (decl->mPrefix.IsEmpty() && decl->mOwner == aElement)) {
00377         GenerateNewPrefix(aPrefix);
00378         // Now we need to validate our new prefix/uri combination; check it
00379         // against the full namespace stack again.  Note that just restarting
00380         // the while loop is ok, since we haven't changed aURI, so the
00381         // closestURIMatch state is not affected.
00382         index = count - 1;
00383         continue;
00384       }
00385     }
00386     
00387     // If we've found a URI match, then record the first one
00388     if (!uriMatch && aURI.Equals(decl->mURI)) {
00389       // Need to check that decl->mPrefix is not declared anywhere closer to
00390       // us.  If it is, we can't use it.
00391       PRBool prefixOK = PR_TRUE;
00392       PRInt32 index2;
00393       for (index2 = count-1; index2 > index && prefixOK; --index2) {
00394         NameSpaceDecl* decl2 =
00395           (NameSpaceDecl*)mNameSpaceStack.ElementAt(index2);
00396         prefixOK = (decl2->mPrefix != decl->mPrefix);
00397       }
00398       
00399       if (prefixOK) {
00400         uriMatch = PR_TRUE;
00401         closestURIMatch.Assign(decl->mPrefix);
00402       }
00403     }
00404     
00405     --index;
00406   }
00407 
00408   // At this point the following invariants hold:
00409   // 1) There is nothing on the namespace stack that matches the pair
00410   //    (aPrefix, aURI)
00411   // 2) There is nothing on the namespace stack that has aPrefix as the prefix
00412   //    and a _different_ URI, except for the case aPrefix.IsEmpty (and
00413   //    possible default namespaces on ancestors)
00414   // 3) The prefix in closestURIMatch is mapped to aURI in our scope if
00415   //    uriMatch is set.
00416   
00417   // So if uriMatch is set it's OK to use the closestURIMatch prefix.  The one
00418   // exception is when closestURIMatch is actually empty (default namespace
00419   // decl) and we must have a prefix.
00420   if (uriMatch && (!aMustHavePrefix || !closestURIMatch.IsEmpty())) {
00421     aPrefix.Assign(closestURIMatch);
00422     return PR_FALSE;
00423   }
00424   
00425   // At this point, if aPrefix is empty (which means we never had a prefix to
00426   // start with) and we must have a prefix, just generate a new prefix and then
00427   // send it back through the namespace stack checks to make sure it's OK.
00428   if (aPrefix.IsEmpty() && aMustHavePrefix) {
00429     GenerateNewPrefix(aPrefix);
00430     return ConfirmPrefix(aPrefix, aURI, aElement, aMustHavePrefix);
00431   }
00432   // else we will just set aURI as the new default namespace URI
00433 
00434   // Indicate that we need to create a namespace decl for the
00435   // final prefix
00436   return PR_TRUE;
00437 }
00438 
00439 void
00440 nsXMLContentSerializer::GenerateNewPrefix(nsAString& aPrefix)
00441 {
00442   aPrefix.AssignLiteral("a");
00443   char buf[128];
00444   PR_snprintf(buf, sizeof(buf), "%d", mPrefixIndex++);
00445   AppendASCIItoUTF16(buf, aPrefix);
00446 }
00447 
00448 void
00449 nsXMLContentSerializer::SerializeAttr(const nsAString& aPrefix,
00450                                       const nsAString& aName,
00451                                       const nsAString& aValue,
00452                                       nsAString& aStr,
00453                                       PRBool aDoEscapeEntities)
00454 {
00455   AppendToString(PRUnichar(' '), aStr);
00456   if (!aPrefix.IsEmpty()) {
00457     AppendToString(aPrefix, aStr);
00458     AppendToString(PRUnichar(':'), aStr);
00459   }
00460   AppendToString(aName, aStr);
00461   
00462   if ( aDoEscapeEntities ) {
00463     // if problem characters are turned into character entity references
00464     // then there will be no problem with the value delimiter characters
00465     AppendToString(NS_LITERAL_STRING("=\""), aStr);
00466 
00467     mInAttribute = PR_TRUE;
00468     AppendToString(aValue, aStr, PR_TRUE);
00469     mInAttribute = PR_FALSE;
00470 
00471     AppendToString(PRUnichar('"'), aStr);
00472   }
00473   else {
00474     // Depending on whether the attribute value contains quotes or apostrophes we
00475     // need to select the delimiter character and escape characters using
00476     // character entity references, ignoring the value of aDoEscapeEntities.
00477     // See http://www.w3.org/TR/REC-html40/appendix/notes.html#h-B.3.2.2 for
00478     // the standard on character entity references in values. 
00479     PRBool bIncludesSingle = PR_FALSE;
00480     PRBool bIncludesDouble = PR_FALSE;
00481     nsAString::const_iterator iCurr, iEnd;
00482     PRUint32 uiSize, i;
00483     aValue.BeginReading(iCurr);
00484     aValue.EndReading(iEnd);
00485     for ( ; iCurr != iEnd; iCurr.advance(uiSize) ) {
00486       const PRUnichar * buf = iCurr.get();
00487       uiSize = iCurr.size_forward();
00488       for ( i = 0; i < uiSize; i++, buf++ ) {
00489         if ( *buf == PRUnichar('\'') )
00490         {
00491           bIncludesSingle = PR_TRUE;
00492           if ( bIncludesDouble ) break;
00493         }
00494         else if ( *buf == PRUnichar('"') )
00495         {
00496           bIncludesDouble = PR_TRUE;
00497           if ( bIncludesSingle ) break;
00498         }
00499       }
00500       // if both have been found we don't need to search further
00501       if ( bIncludesDouble && bIncludesSingle ) break;
00502     }
00503 
00504     // Delimiter and escaping is according to the following table
00505     //    bIncludesDouble     bIncludesSingle     Delimiter       Escape Double Quote
00506     //    FALSE               FALSE               "               FALSE
00507     //    FALSE               TRUE                "               FALSE
00508     //    TRUE                FALSE               '               FALSE
00509     //    TRUE                TRUE                "               TRUE
00510     PRUnichar cDelimiter = 
00511         (bIncludesDouble && !bIncludesSingle) ? PRUnichar('\'') : PRUnichar('"');
00512     AppendToString(PRUnichar('='), aStr);
00513     AppendToString(cDelimiter, aStr);
00514     if (bIncludesDouble && bIncludesSingle) {
00515       nsAutoString sValue(aValue);
00516       sValue.ReplaceSubstring(NS_LITERAL_STRING("\"").get(), NS_LITERAL_STRING("&quot;").get());
00517       mInAttribute = PR_TRUE;
00518       AppendToString(sValue, aStr, PR_FALSE);
00519       mInAttribute = PR_FALSE;
00520     }
00521     else {
00522       mInAttribute = PR_TRUE;
00523       AppendToString(aValue, aStr, PR_FALSE);
00524       mInAttribute = PR_FALSE;
00525     }
00526     AppendToString(cDelimiter, aStr);
00527   }
00528 }
00529 
00530 NS_IMETHODIMP 
00531 nsXMLContentSerializer::AppendElementStart(nsIDOMElement *aElement,
00532                                            PRBool aHasChildren,
00533                                            nsAString& aStr)
00534 {
00535   NS_ENSURE_ARG(aElement);
00536 
00537   nsAutoString tagPrefix, tagLocalName, tagNamespaceURI;
00538   nsAutoString xmlnsStr;
00539   xmlnsStr.AssignLiteral(kXMLNS);
00540 
00541   nsCOMPtr<nsIContent> content(do_QueryInterface(aElement));
00542   if (!content) return NS_ERROR_FAILURE;
00543 
00544   aElement->GetPrefix(tagPrefix);
00545   aElement->GetLocalName(tagLocalName);
00546   aElement->GetNamespaceURI(tagNamespaceURI);
00547 
00548   PRInt32 namespaceID;
00549     
00550   PRUint32 index, count;
00551   nsAutoString nameStr, prefixStr, uriStr, valueStr;
00552   nsCOMPtr<nsIAtom> attrName, attrPrefix;
00553 
00554   count = content->GetAttrCount();
00555 
00556   // First scan for namespace declarations, pushing each on the stack
00557   for (index = 0; index < count; index++) {
00558     
00559     content->GetAttrNameAt(index,
00560                            &namespaceID,
00561                            getter_AddRefs(attrName),
00562                            getter_AddRefs(attrPrefix));
00563     
00564     if (namespaceID == kNameSpaceID_XMLNS ||
00565         // Also push on the stack attrs named "xmlns" in the null
00566         // namespace... because once we serialize those out they'll look like
00567         // namespace decls.  :(
00568         // XXXbz what if we have both "xmlns" in the null namespace and "xmlns"
00569         // in the xmlns namespace?
00570         (namespaceID == kNameSpaceID_None &&
00571          attrName == nsLayoutAtoms::xmlnsNameSpace)) {
00572       content->GetAttr(namespaceID, attrName, uriStr);
00573 
00574       if (!attrPrefix) {
00575         // Default NS attribute does not have prefix (and the name is "xmlns")
00576         PushNameSpaceDecl(EmptyString(), uriStr, aElement);
00577       } else {
00578         attrName->ToString(nameStr);
00579         PushNameSpaceDecl(nameStr, uriStr, aElement);
00580       }
00581     }
00582   }
00583 
00584   PRBool addNSAttr;
00585     
00586   MaybeAddNewline(aStr);
00587 
00588   addNSAttr = ConfirmPrefix(tagPrefix, tagNamespaceURI, aElement, PR_FALSE);
00589   // Serialize the qualified name of the element
00590   AppendToString(NS_LITERAL_STRING("<"), aStr);
00591   if (!tagPrefix.IsEmpty()) {
00592     AppendToString(tagPrefix, aStr);
00593     AppendToString(NS_LITERAL_STRING(":"), aStr);
00594   }
00595   AppendToString(tagLocalName, aStr);
00596     
00597   // If we had to add a new namespace declaration, serialize
00598   // and push it on the namespace stack
00599   if (addNSAttr) {
00600     if (tagPrefix.IsEmpty()) {
00601       // Serialize default namespace decl
00602       SerializeAttr(EmptyString(), xmlnsStr, tagNamespaceURI, aStr, PR_TRUE);
00603     } else {
00604       // Serialize namespace decl
00605       SerializeAttr(xmlnsStr, tagPrefix, tagNamespaceURI, aStr, PR_TRUE);
00606     }
00607     PushNameSpaceDecl(tagPrefix, tagNamespaceURI, aElement);
00608   }
00609 
00610   // Now serialize each of the attributes
00611   // XXX Unfortunately we need a namespace manager to get
00612   // attribute URIs.
00613   for (index = 0; index < count; index++) {
00614     content->GetAttrNameAt(index,
00615                            &namespaceID,
00616                            getter_AddRefs(attrName),
00617                            getter_AddRefs(attrPrefix));
00618 
00619     if (attrPrefix) {
00620       attrPrefix->ToString(prefixStr);
00621     }
00622     else {
00623       prefixStr.Truncate();
00624     }
00625 
00626     addNSAttr = PR_FALSE;
00627     if (kNameSpaceID_XMLNS != namespaceID) {
00628       nsContentUtils::GetNSManagerWeakRef()->GetNameSpaceURI(namespaceID,
00629                                                              uriStr);
00630       addNSAttr = ConfirmPrefix(prefixStr, uriStr, aElement,
00631                                 namespaceID != kNameSpaceID_None);
00632     }
00633     
00634     content->GetAttr(namespaceID, attrName, valueStr);
00635     attrName->ToString(nameStr);
00636 
00637     // XXX Hack to get around the fact that MathML can add
00638     //     attributes starting with '-', which makes them
00639     //     invalid XML.
00640     if (!nameStr.IsEmpty() && nameStr.First() == '-')
00641       continue;
00642 
00643     if (namespaceID == kNameSpaceID_None) {
00644       if (content->GetNameSpaceID() == kNameSpaceID_XHTML) {
00645         if (IsShorthandAttr(attrName, content->Tag()) &&
00646             valueStr.IsEmpty()) {
00647           valueStr = nameStr;
00648         }
00649       }
00650     }
00651     SerializeAttr(prefixStr, nameStr, valueStr, aStr, PR_TRUE);
00652     
00653     if (addNSAttr) {
00654       NS_ASSERTION(!prefixStr.IsEmpty(),
00655                    "Namespaced attributes must have a prefix");
00656       SerializeAttr(xmlnsStr, prefixStr, uriStr, aStr, PR_TRUE);
00657       PushNameSpaceDecl(prefixStr, uriStr, aElement);
00658     }
00659   }
00660 
00661   // We don't output a separate end tag for empty element
00662   if (!aHasChildren) {
00663     AppendToString(NS_LITERAL_STRING("/>"), aStr);    
00664     MaybeFlagNewline(aElement);
00665   } else {
00666     AppendToString(NS_LITERAL_STRING(">"), aStr);    
00667   }
00668   
00669   return NS_OK;
00670 }
00671 
00672 NS_IMETHODIMP 
00673 nsXMLContentSerializer::AppendElementEnd(nsIDOMElement *aElement,
00674                                          nsAString& aStr)
00675 {
00676   NS_ENSURE_ARG(aElement);
00677 
00678   // We don't output a separate end tag for empty element
00679   nsCOMPtr<nsIDOMNode> node(do_QueryInterface(aElement));
00680   PRBool hasChildren;
00681   if (NS_SUCCEEDED(node->HasChildNodes(&hasChildren)) && !hasChildren) {
00682     PopNameSpaceDeclsFor(aElement);
00683   
00684     return NS_OK;
00685   }
00686   
00687   nsCOMPtr<nsIContent> content(do_QueryInterface(aElement));
00688   if (!content) return NS_ERROR_FAILURE;
00689 
00690   nsAutoString tagPrefix, tagLocalName, tagNamespaceURI;
00691   
00692   aElement->GetPrefix(tagPrefix);
00693   aElement->GetLocalName(tagLocalName);
00694   aElement->GetNamespaceURI(tagNamespaceURI);
00695 
00696 #ifdef DEBUG
00697   PRBool debugNeedToPushNamespace =
00698 #endif
00699   ConfirmPrefix(tagPrefix, tagNamespaceURI, aElement, PR_FALSE);
00700   NS_ASSERTION(!debugNeedToPushNamespace, "Can't push namespaces in closing tag!");
00701 
00702   AppendToString(NS_LITERAL_STRING("</"), aStr);
00703   if (!tagPrefix.IsEmpty()) {
00704     AppendToString(tagPrefix, aStr);
00705     AppendToString(NS_LITERAL_STRING(":"), aStr);
00706   }
00707   AppendToString(tagLocalName, aStr);
00708   AppendToString(NS_LITERAL_STRING(">"), aStr);
00709   MaybeFlagNewline(aElement);
00710   
00711   PopNameSpaceDeclsFor(aElement);
00712   
00713   return NS_OK;
00714 }
00715 
00716 void
00717 nsXMLContentSerializer::AppendToString(const PRUnichar* aStr,
00718                                        PRInt32 aLength,
00719                                        nsAString& aOutputStr)
00720 {
00721   PRInt32 length = (aLength == -1) ? nsCRT::strlen(aStr) : aLength;
00722   
00723   aOutputStr.Append(aStr, length);
00724 }
00725 
00726 void 
00727 nsXMLContentSerializer::AppendToString(const PRUnichar aChar,
00728                                        nsAString& aOutputStr)
00729 {
00730   aOutputStr.Append(aChar);
00731 }
00732 
00733 static const PRUint16 kGTVal = 62;
00734 static const char* kEntities[] = {
00735   "", "", "", "", "", "", "", "", "", "",
00736   "", "", "", "", "", "", "", "", "", "",
00737   "", "", "", "", "", "", "", "", "", "",
00738   "", "", "", "", "", "", "", "", "&amp;", "",
00739   "", "", "", "", "", "", "", "", "", "",
00740   "", "", "", "", "", "", "", "", "", "",
00741   "&lt;", "", "&gt;"
00742 };
00743 
00744 static const char* kAttrEntities[] = {
00745   "", "", "", "", "", "", "", "", "", "",
00746   "", "", "", "", "", "", "", "", "", "",
00747   "", "", "", "", "", "", "", "", "", "",
00748   "", "", "", "", "&quot;", "", "", "", "&amp;", "",
00749   "", "", "", "", "", "", "", "", "", "",
00750   "", "", "", "", "", "", "", "", "", "",
00751   "&lt;", "", "&gt;"
00752 };
00753 
00754 void
00755 nsXMLContentSerializer::AppendToString(const nsAString& aStr,
00756                                        nsAString& aOutputStr,
00757                                        PRBool aTranslateEntities,
00758                                        PRBool aIncrColumn)
00759 {
00760   if (aTranslateEntities) {
00761     nsReadingIterator<PRUnichar> done_reading;
00762     aStr.EndReading(done_reading);
00763 
00764     // for each chunk of |aString|...
00765     PRUint32 advanceLength = 0;
00766     nsReadingIterator<PRUnichar> iter;
00767 
00768     const char **entityTable = mInAttribute ? kAttrEntities : kEntities;
00769 
00770     for (aStr.BeginReading(iter); 
00771          iter != done_reading; 
00772          iter.advance(PRInt32(advanceLength))) {
00773       PRUint32 fragmentLength = iter.size_forward();
00774       const PRUnichar* c = iter.get();
00775       const PRUnichar* fragmentStart = c;
00776       const PRUnichar* fragmentEnd = c + fragmentLength;
00777       const char* entityText = nsnull;
00778 
00779       advanceLength = 0;
00780       // for each character in this chunk, check if it
00781       // needs to be replaced
00782       for (; c < fragmentEnd; c++, advanceLength++) {
00783         PRUnichar val = *c;
00784         if ((val <= kGTVal) && (entityTable[val][0] != 0)) {
00785           entityText = entityTable[val];
00786           break;
00787         }
00788       }
00789 
00790       aOutputStr.Append(fragmentStart, advanceLength);
00791       if (entityText) {
00792         AppendASCIItoUTF16(entityText, aOutputStr);
00793         advanceLength++;
00794       }
00795     }
00796 
00797     return;
00798   }
00799   
00800   aOutputStr.Append(aStr);
00801 }
00802 
00803 PRBool
00804 nsXMLContentSerializer::IsShorthandAttr(const nsIAtom* aAttrName,
00805                                         const nsIAtom* aElementName)
00806 {
00807   // checked
00808   if ((aAttrName == nsHTMLAtoms::checked) &&
00809       (aElementName == nsHTMLAtoms::input)) {
00810     return PR_TRUE;
00811   }
00812 
00813   // compact
00814   if ((aAttrName == nsHTMLAtoms::compact) &&
00815       (aElementName == nsHTMLAtoms::dir || 
00816        aElementName == nsHTMLAtoms::dl ||
00817        aElementName == nsHTMLAtoms::menu ||
00818        aElementName == nsHTMLAtoms::ol ||
00819        aElementName == nsHTMLAtoms::ul)) {
00820     return PR_TRUE;
00821   }
00822 
00823   // declare
00824   if ((aAttrName == nsHTMLAtoms::declare) &&
00825       (aElementName == nsHTMLAtoms::object)) {
00826     return PR_TRUE;
00827   }
00828 
00829   // defer
00830   if ((aAttrName == nsHTMLAtoms::defer) &&
00831       (aElementName == nsHTMLAtoms::script)) {
00832     return PR_TRUE;
00833   }
00834 
00835   // disabled
00836   if ((aAttrName == nsHTMLAtoms::disabled) &&
00837       (aElementName == nsHTMLAtoms::button ||
00838        aElementName == nsHTMLAtoms::input ||
00839        aElementName == nsHTMLAtoms::optgroup ||
00840        aElementName == nsHTMLAtoms::option ||
00841        aElementName == nsHTMLAtoms::select ||
00842        aElementName == nsHTMLAtoms::textarea)) {
00843     return PR_TRUE;
00844   }
00845 
00846   // ismap
00847   if ((aAttrName == nsHTMLAtoms::ismap) &&
00848       (aElementName == nsHTMLAtoms::img ||
00849        aElementName == nsHTMLAtoms::input)) {
00850     return PR_TRUE;
00851   }
00852 
00853   // multiple
00854   if ((aAttrName == nsHTMLAtoms::multiple) &&
00855       (aElementName == nsHTMLAtoms::select)) {
00856     return PR_TRUE;
00857   }
00858 
00859   // noresize
00860   if ((aAttrName == nsHTMLAtoms::noresize) &&
00861       (aElementName == nsHTMLAtoms::frame)) {
00862     return PR_TRUE;
00863   }
00864 
00865   // noshade
00866   if ((aAttrName == nsHTMLAtoms::noshade) &&
00867       (aElementName == nsHTMLAtoms::hr)) {
00868     return PR_TRUE;
00869   }
00870 
00871   // nowrap
00872   if ((aAttrName == nsHTMLAtoms::nowrap) &&
00873       (aElementName == nsHTMLAtoms::td ||
00874        aElementName == nsHTMLAtoms::th)) {
00875     return PR_TRUE;
00876   }
00877 
00878   // readonly
00879   if ((aAttrName == nsHTMLAtoms::readonly) &&
00880       (aElementName == nsHTMLAtoms::input ||
00881        aElementName == nsHTMLAtoms::textarea)) {
00882     return PR_TRUE;
00883   }
00884 
00885   // selected
00886   if ((aAttrName == nsHTMLAtoms::selected) &&
00887       (aElementName == nsHTMLAtoms::option)) {
00888     return PR_TRUE;
00889   }
00890 
00891   return PR_FALSE;
00892 }
00893 
00894 void
00895 nsXMLContentSerializer::MaybeAddNewline(nsAString& aStr)
00896 {
00897   if (mAddNewline) {
00898     aStr.Append((PRUnichar)'\n');
00899     mAddNewline = PR_FALSE;
00900   }
00901 }
00902 
00903 void
00904 nsXMLContentSerializer::MaybeFlagNewline(nsIDOMNode* aNode)
00905 {
00906   nsCOMPtr<nsIDOMNode> parent;
00907   aNode->GetParentNode(getter_AddRefs(parent));
00908   if (parent) {
00909     PRUint16 type;
00910     parent->GetNodeType(&type);
00911     mAddNewline = type == nsIDOMNode::DOCUMENT_NODE;
00912   }
00913 }
00914 
00915 NS_IMETHODIMP
00916 nsXMLContentSerializer::AppendDocumentStart(nsIDOMDocument *aDocument,
00917                                             nsAString& aStr)
00918 {
00919   NS_ENSURE_ARG_POINTER(aDocument);
00920 
00921   nsCOMPtr<nsIDocument> doc(do_QueryInterface(aDocument));
00922   if (!doc) {
00923     return NS_OK;
00924   }
00925 
00926   nsAutoString version, encoding, standalone;
00927   doc->GetXMLDeclaration(version, encoding, standalone);
00928 
00929   if (version.IsEmpty())
00930     return NS_OK; // A declaration must have version, or there is no decl
00931 
00932   NS_NAMED_LITERAL_STRING(endQuote, "\"");
00933 
00934   aStr += NS_LITERAL_STRING("<?xml version=\"") + version + endQuote;
00935   
00936   if (!encoding.IsEmpty()) {
00937     aStr += NS_LITERAL_STRING(" encoding=\"") + encoding + endQuote;
00938   }
00939 
00940   if (!standalone.IsEmpty()) {
00941     aStr += NS_LITERAL_STRING(" standalone=\"") + standalone + endQuote;
00942   }
00943 
00944   aStr.AppendLiteral("?>");
00945   mAddNewline = PR_TRUE;
00946 
00947   return NS_OK;
00948 }