/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* ***** BEGIN LICENSE BLOCK ***** * Version: NPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Netscape Public License * Version 1.1 (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * http://www.mozilla.org/NPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is mozilla.org code. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 1998 * the Initial Developer. All Rights Reserved. * * Contributor(s): * * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the NPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the NPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include "nsHTMLContentSerializer.h" #include "nsIDOMElement.h" #include "nsIDOMDocumentType.h" #include "nsIDOMText.h" #include "nsIContent.h" #include "nsIDocument.h" #include "nsIDOMDocument.h" #include "nsINameSpaceManager.h" #include "nsString.h" #include "nsUnicharUtils.h" #include "nsXPIDLString.h" #include "nsIServiceManager.h" #include "nsIDocumentEncoder.h" #include "nsLayoutAtoms.h" #include "nsHTMLAtoms.h" #include "nsIURI.h" #include "nsNetUtil.h" #include "nsEscape.h" #include "nsITextToSubURI.h" #include "nsCRT.h" #include "nsIParserService.h" #include "nsContentUtils.h" #include "nsILineBreakerFactory.h" #include "nsLWBrkCIID.h" #include "nsIPrefBranch.h" #include "nsIPrefService.h" #include "nsIServiceManager.h" #define PREF_NOESCAPING "editor.encode.noEscaping" #define kIndentStr NS_LITERAL_STRING(" ") #define kLessThan NS_LITERAL_STRING("<") #define kGreaterThan NS_LITERAL_STRING(">") #define kEndTag NS_LITERAL_STRING("") #define kTagSpanEnd NS_LITERAL_STRING("") #define kSpanEnd NS_LITERAL_STRING("") #define kSelectionStart NS_LITERAL_STRING("") #define kSelectionEnd NS_LITERAL_STRING("") #define kEscapedLessThan NS_LITERAL_STRING("<") #define kEscapedGreaterThan NS_LITERAL_STRING(">") static const char kMozStr[] = "moz"; static NS_DEFINE_CID(kLWBrkCID, NS_LWBRK_CID); static const PRInt32 kLongLineLen = 128; nsresult NS_NewHTMLContentSerializer(nsIContentSerializer** aSerializer) { nsHTMLContentSerializer* it = new nsHTMLContentSerializer(); if (!it) { return NS_ERROR_OUT_OF_MEMORY; } return CallQueryInterface(it, aSerializer); } nsHTMLContentSerializer::nsHTMLContentSerializer() : mIndent(0), mColPos(0), mInBody(PR_FALSE), mAddSpace(PR_FALSE), mMayIgnoreLineBreakSequence(PR_FALSE), mInCDATA(PR_FALSE), mNeedLineBreaker(PR_TRUE), mDisableEscaping(PR_FALSE) { } nsHTMLContentSerializer::~nsHTMLContentSerializer() { NS_ASSERTION(mOLStateStack.Count() == 0, "Expected OL State stack to be empty"); if (mOLStateStack.Count() > 0){ for (PRInt32 i = 0; i < mOLStateStack.Count(); i++){ olState* state = (olState*)mOLStateStack[i]; delete state; mOLStateStack.RemoveElementAt(i); } } } NS_IMETHODIMP nsHTMLContentSerializer::Init(PRUint32 aFlags, PRUint32 aWrapColumn, const char* aCharSet, PRBool aIsCopying) { mFlags = aFlags; if (!aWrapColumn) { mMaxColumn = 72; } else { mMaxColumn = aWrapColumn; } mIsCopying = aIsCopying; mIsFirstChildOfOL = PR_FALSE; mDoFormat = (mFlags & nsIDocumentEncoder::OutputFormatted) ? PR_TRUE : PR_FALSE; mDoWrap = (mFlags & nsIDocumentEncoder::OutputWrap) ? PR_TRUE : PR_FALSE; mBodyOnly = (mFlags & nsIDocumentEncoder::OutputBodyOnly) ? PR_TRUE : PR_FALSE; // Set the line break character: if (mFlags & nsIDocumentEncoder::OutputForColoredSourceView) { // override in the case of a colored source view mLineBreak.Assign(NS_LITERAL_STRING("
")); } else if ((mFlags & nsIDocumentEncoder::OutputCRLineBreak) && (mFlags & nsIDocumentEncoder::OutputLFLineBreak)) { // Windows mLineBreak.Assign(NS_LITERAL_STRING("\r\n")); } else if (mFlags & nsIDocumentEncoder::OutputCRLineBreak) { // Mac mLineBreak.Assign(NS_LITERAL_STRING("\r")); } else if (mFlags & nsIDocumentEncoder::OutputLFLineBreak) { // Unix/DOM mLineBreak.Assign(NS_LITERAL_STRING("\n")); } else { mLineBreak.AssignWithConversion(NS_LINEBREAK); // Platform/default } mPreLevel = 0; mCharSet = aCharSet; // set up entity converter if we are going to need it if (mFlags & nsIDocumentEncoder::OutputEncodeW3CEntities) { mEntityConverter = do_CreateInstance(NS_ENTITYCONVERTER_CONTRACTID); } nsCOMPtr prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID)); if (prefBranch) { PRBool isEscapingDisabled; if (NS_SUCCEEDED(prefBranch->GetBoolPref(PREF_NOESCAPING, &isEscapingDisabled))) mDisableEscaping = isEscapingDisabled; } return NS_OK; } NS_IMETHODIMP nsHTMLContentSerializer::AppendText(nsIDOMText* aText, PRInt32 aStartOffset, PRInt32 aEndOffset, nsAString& aStr) { NS_ENSURE_ARG(aText); if (mNeedLineBreaker) { mNeedLineBreaker = PR_FALSE; nsCOMPtr domDoc; aText->GetOwnerDocument(getter_AddRefs(domDoc)); nsCOMPtr document = do_QueryInterface(domDoc); if (document) { mLineBreaker = document->GetLineBreaker(); } if (!mLineBreaker) { nsresult rv; nsCOMPtr lf(do_GetService(kLWBrkCID, &rv)); if (NS_SUCCEEDED(rv)) { rv = lf->GetBreaker(nsString(), getter_AddRefs(mLineBreaker)); // Ignore result value. // If we are unable to obtain a line breaker, // we will use our simple fallback logic. } } } nsAutoString data; nsresult rv; if ((mFlags & nsIDocumentEncoder::OutputForColoredSourceView) && aText == mStartSelectionContainer && aStartOffset < mStartSelectionOffset) { rv = AppendTextData((nsIDOMNode*)aText, aStartOffset, mStartSelectionOffset, data, PR_TRUE, PR_FALSE); if (NS_FAILED(rv)) return NS_ERROR_FAILURE; AppendTextInternal(data, aStr); data.Truncate(); AppendToString(kSelectionStart, aStr, PR_FALSE, PR_FALSE); aStartOffset = mStartSelectionOffset; } if ((mFlags & nsIDocumentEncoder::OutputForColoredSourceView) && aText == mEndSelectionContainer && (aEndOffset == -1 || aEndOffset > mEndSelectionOffset)) { rv = AppendTextData((nsIDOMNode*)aText, aStartOffset, mEndSelectionOffset, data, PR_TRUE, PR_FALSE); if (NS_FAILED(rv)) return NS_ERROR_FAILURE; AppendTextInternal(data, aStr); data.Truncate(); AppendToString(kSelectionEnd, aStr, PR_FALSE, PR_FALSE); rv = AppendTextData((nsIDOMNode*)aText, mEndSelectionOffset, aEndOffset, data, PR_TRUE, PR_FALSE); } else { rv = AppendTextData((nsIDOMNode*)aText, aStartOffset, aEndOffset, data, PR_TRUE, PR_FALSE); if (NS_FAILED(rv)) return NS_ERROR_FAILURE; } AppendTextInternal(data, aStr); return NS_OK; } void nsHTMLContentSerializer::AppendTextInternal(const nsString& aData, nsAString& aStr) { if (mPreLevel > 0) { AppendToStringConvertLF(aData, aStr); } else if (!mDoWrap) { PRInt32 lastNewlineOffset = kNotFound; PRBool hasLongLines = HasLongLines(aData, lastNewlineOffset); if (hasLongLines) { // We have long lines, rewrap AppendToStringWrapped(aData, aStr, PR_FALSE); if (lastNewlineOffset != kNotFound) mColPos = aData.Length() - lastNewlineOffset; } else { AppendToStringConvertLF(aData, aStr); } } else if (mFlags & nsIDocumentEncoder::OutputRaw) { PRInt32 lastNewlineOffset = aData.RFindChar('\n'); AppendToString(aData, aStr); if (lastNewlineOffset != kNotFound) mColPos = aData.Length() - lastNewlineOffset; } else { AppendToStringWrapped(aData, aStr, PR_FALSE); } } void nsHTMLContentSerializer::AppendWrapped_WhitespaceSequence( nsASingleFragmentString::const_char_iterator &aPos, const nsASingleFragmentString::const_char_iterator aEnd, const nsASingleFragmentString::const_char_iterator aSequenceStart, PRBool &aMayIgnoreStartOfLineWhitespaceSequence, nsAString &aOutputStr) { // Handle the complete sequence of whitespace. // Continue to iterate until we find the first non-whitespace char. // Updates "aPos" to point to the first unhandled char. // Also updates the aMayIgnoreStartOfLineWhitespaceSequence flag, // as well as the other "global" state flags. PRBool sawBlankOrTab = PR_FALSE; PRBool leaveLoop = PR_FALSE; do { switch (*aPos) { case ' ': case '\t': sawBlankOrTab = PR_TRUE; // no break case '\n': ++aPos; // do not increase mColPos, // because we will reduce the whitespace to a single char break; default: leaveLoop = PR_TRUE; break; } } while (!leaveLoop && aPos < aEnd); if (mAddSpace) { // if we had previously been asked to add space, // our situation has not changed } else if (!sawBlankOrTab && mMayIgnoreLineBreakSequence) { // nothing to do mMayIgnoreLineBreakSequence = PR_FALSE; } else if (aMayIgnoreStartOfLineWhitespaceSequence) { // nothing to do aMayIgnoreStartOfLineWhitespaceSequence = PR_FALSE; } else { if (sawBlankOrTab) { if (mColPos + 1 >= mMaxColumn) { // no much sense in delaying, we only have one slot left, // let's write a break now aOutputStr.Append(mLineBreak); mColPos = 0; } else { // do not write out yet, we may write out either a space or a linebreak // let's delay writing it out until we know more mAddSpace = PR_TRUE; ++mColPos; // eat a slot of available space } } else { // Asian text usually does not contain spaces, therefore we should not // transform a linebreak into a space. // Since we only saw linebreaks, but no spaces or tabs, // let's write a linebreak now. aOutputStr.Append(mLineBreak); mMayIgnoreLineBreakSequence = PR_TRUE; mColPos = 0; } } } void nsHTMLContentSerializer::AppendWrapped_NonWhitespaceSequence( nsASingleFragmentString::const_char_iterator &aPos, const nsASingleFragmentString::const_char_iterator aEnd, const nsASingleFragmentString::const_char_iterator aSequenceStart, PRBool &aMayIgnoreStartOfLineWhitespaceSequence, nsAString& aOutputStr) { mMayIgnoreLineBreakSequence = PR_FALSE; aMayIgnoreStartOfLineWhitespaceSequence = PR_FALSE; // Handle the complete sequence of non-whitespace in this block // Iterate until we find the first whitespace char or an aEnd condition // Updates "aPos" to point to the first unhandled char. // Also updates the aMayIgnoreStartOfLineWhitespaceSequence flag, // as well as the other "global" state flags. PRBool thisSequenceStartsAtBeginningOfLine = !mColPos; PRBool onceAgainBecauseWeAddedBreakInFront; PRBool foundWhitespaceInLoop; do { onceAgainBecauseWeAddedBreakInFront = PR_FALSE; foundWhitespaceInLoop = PR_FALSE; do { if (*aPos == ' ' || *aPos == '\t' || *aPos == '\n') { foundWhitespaceInLoop = PR_TRUE; break; } ++aPos; ++mColPos; } while (mColPos < mMaxColumn && aPos < aEnd); if (aPos == aEnd || foundWhitespaceInLoop) { // there is enough room for the complete block we found if (mAddSpace) { aOutputStr.Append(PRUnichar(' ')); mAddSpace = PR_FALSE; } aOutputStr.Append(aSequenceStart, aPos - aSequenceStart); // We have not yet reached the max column, we will continue to // fill the current line in the next outer loop iteration. } else { // mColPos == mMaxColumn if (!thisSequenceStartsAtBeginningOfLine && mAddSpace) { // We can avoid to wrap. aOutputStr.Append(mLineBreak); mAddSpace = PR_FALSE; aPos = aSequenceStart; mColPos = 0; thisSequenceStartsAtBeginningOfLine = PR_TRUE; onceAgainBecauseWeAddedBreakInFront = PR_TRUE; } else { // we must wrap PRBool foundWrapPosition = PR_FALSE; if (mLineBreaker) { // we have a line breaker helper object PRUint32 wrapPosition; PRBool needMoreText; nsresult rv; rv = mLineBreaker->Prev(aSequenceStart, (aEnd - aSequenceStart), (aPos - aSequenceStart) + 1, &wrapPosition, &needMoreText); if (NS_SUCCEEDED(rv) && !needMoreText && wrapPosition > 0) { foundWrapPosition = PR_TRUE; } else { rv = mLineBreaker->Next(aSequenceStart, (aEnd - aSequenceStart), (aPos - aSequenceStart), &wrapPosition, &needMoreText); if (NS_SUCCEEDED(rv) && !needMoreText && wrapPosition > 0) { foundWrapPosition = PR_TRUE; } } if (foundWrapPosition) { if (mAddSpace) { aOutputStr.Append(PRUnichar(' ')); mAddSpace = PR_FALSE; } aOutputStr.Append(aSequenceStart, wrapPosition); aOutputStr.Append(mLineBreak); aPos = aSequenceStart + wrapPosition; mColPos = 0; aMayIgnoreStartOfLineWhitespaceSequence = PR_TRUE; mMayIgnoreLineBreakSequence = PR_TRUE; } } if (!mLineBreaker || !foundWrapPosition) { // try some simple fallback logic // go forward up to the next whitespace position, // in the worst case this will be all the rest of the data do { if (*aPos == ' ' || *aPos == '\t' || *aPos == '\n') { break; } ++aPos; ++mColPos; } while (aPos < aEnd); if (mAddSpace) { aOutputStr.Append(PRUnichar(' ')); mAddSpace = PR_FALSE; } aOutputStr.Append(aSequenceStart, aPos - aSequenceStart); } } } } while (onceAgainBecauseWeAddedBreakInFront); } void nsHTMLContentSerializer::AppendToStringWrapped(const nsASingleFragmentString& aStr, nsAString& aOutputStr, PRBool aTranslateEntities) { nsASingleFragmentString::const_char_iterator pos, end, sequenceStart; aStr.BeginReading(pos); aStr.EndReading(end); // if the current line already has text on it, such as a tag, // leading whitespace is significant PRBool mayIgnoreStartOfLineWhitespaceSequence = !mColPos; while (pos < end) { sequenceStart = pos; // if beginning of a whitespace sequence if (*pos == ' ' || *pos == '\n' || *pos == '\t') { AppendWrapped_WhitespaceSequence(pos, end, sequenceStart, mayIgnoreStartOfLineWhitespaceSequence, aOutputStr); } else { // any other non-whitespace char AppendWrapped_NonWhitespaceSequence(pos, end, sequenceStart, mayIgnoreStartOfLineWhitespaceSequence, aOutputStr); } } } NS_IMETHODIMP nsHTMLContentSerializer::AppendDocumentStart(nsIDOMDocument *aDocument, nsAString& aStr) { return NS_OK; } PRBool nsHTMLContentSerializer::ContainsPHPChunk(const nsAString& aValueString) { nsAString::const_iterator start, end; aValueString.BeginReading(start); aValueString.EndReading(end); NS_NAMED_LITERAL_STRING(phpDelim, " textToSubURI; nsAutoString uri(aURI); // in order to use FindCharInSet(), IsASCII() nsresult rv = NS_OK; if (!mCharSet.IsEmpty() && !uri.IsASCII()) { textToSubURI = do_GetService(NS_ITEXTTOSUBURI_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv, rv); } PRInt32 start = 0; PRInt32 end; nsAutoString part; nsXPIDLCString escapedURI; aEscapedURI.Truncate(0); // Loop and escape parts by avoiding escaping reserved characters (and '%', '#' ). while ((end = uri.FindCharInSet("%#;/?:@&=+$,", start)) != -1) { part = Substring(aURI, start, (end-start)); if (textToSubURI && !part.IsASCII()) { rv = textToSubURI->ConvertAndEscape(mCharSet.get(), part.get(), getter_Copies(escapedURI)); NS_ENSURE_SUCCESS(rv, rv); } else { escapedURI.Adopt(nsEscape(NS_ConvertUCS2toUTF8(part).get(), url_Path)); } AppendASCIItoUTF16(escapedURI, aEscapedURI); // Append a reserved character without escaping. part = Substring(aURI, end, 1); aEscapedURI.Append(part); start = end + 1; } if (start < (PRInt32) aURI.Length()) { // Escape the remaining part. part = Substring(aURI, start, aURI.Length()-start); if (textToSubURI) { rv = textToSubURI->ConvertAndEscape(mCharSet.get(), part.get(), getter_Copies(escapedURI)); NS_ENSURE_SUCCESS(rv, rv); } else { escapedURI.Adopt(nsEscape(NS_ConvertUCS2toUTF8(part).get(), url_Path)); } AppendASCIItoUTF16(escapedURI, aEscapedURI); } return rv; } void nsHTMLContentSerializer::SerializeAttributes(nsIContent* aContent, nsIAtom* aTagName, nsAString& aStr) { nsresult rv; PRUint32 index, count; nsAutoString nameStr, valueStr; PRInt32 namespaceID; nsCOMPtr attrName, attrPrefix; count = aContent->GetAttrCount(); NS_NAMED_LITERAL_STRING(_mozStr, "_moz"); // Loop backward over the attributes, since the order they are stored in is // the opposite of the order they were parsed in (see bug 213347 for reason). // index is unsigned, hence index >= 0 is always true. for (index = count; index > 0; ) { --index; aContent->GetAttrNameAt(index, &namespaceID, getter_AddRefs(attrName), getter_AddRefs(attrPrefix)); // Filter out any attribute starting with [-|_]moz const char* sharedName; attrName->GetUTF8String(&sharedName); if ((('_' == *sharedName) || ('-' == *sharedName)) && !nsCRT::strncmp(sharedName+1, kMozStr, PRUint32(sizeof(kMozStr)-1))) { continue; } aContent->GetAttr(namespaceID, attrName, valueStr); // // Filter out special case of
or
, // used by the editor. Bug 16988. Yuck. // if (aTagName == nsHTMLAtoms::br && attrName == nsHTMLAtoms::type && StringBeginsWith(valueStr, _mozStr)) { continue; } // XXX: This special cased textarea code should be // removed when bug #17003 is fixed. if ((aTagName == nsHTMLAtoms::textarea) && ((attrName == nsHTMLAtoms::value) || (attrName == nsHTMLAtoms::defaultvalue))){ continue; } if (mIsCopying && mIsFirstChildOfOL && (aTagName == nsHTMLAtoms::li) && (attrName == nsHTMLAtoms::value)){ // This is handled separately in SerializeLIValueAttribute() continue; } PRBool isJS = IsJavaScript(attrName, valueStr); PRBool disableEscaping = mDisableEscaping || ContainsPHPChunk(valueStr); if (((attrName == nsHTMLAtoms::href) || (attrName == nsHTMLAtoms::src))) { // Make all links absolute when converting only the selection: if (mFlags & nsIDocumentEncoder::OutputAbsoluteLinks) { // Would be nice to handle OBJECT and APPLET tags, // but that gets more complicated since we have to // search the tag list for CODEBASE as well. // For now, just leave them relative. nsCOMPtr uri = aContent->GetBaseURI(); if (uri) { // don't do that if there's login/password information provided // in the base URI nsCAutoString username, password; uri->GetUsername(username); uri->GetPassword(password); if (username.IsEmpty() && password.IsEmpty()) { nsAutoString absURI; rv = NS_MakeAbsoluteURI(absURI, valueStr, uri); if (NS_SUCCEEDED(rv)) { valueStr = absURI; } } } } // Need to escape URI. nsAutoString tempURI(valueStr); if (!isJS && !disableEscaping && NS_FAILED(EscapeURI(tempURI, valueStr))) valueStr = tempURI; } attrName->ToString(nameStr); /*If we already crossed the MaxColumn limit or * if this attr name-value pair(including a space,=,opening and closing quotes) is greater than MaxColumn limit * then start the attribute from a new line. */ PRInt32 foo1 = nameStr.Length(); PRInt32 foo2 = valueStr.Length(); if (mDoWrap && (mColPos >= mMaxColumn || ((PRInt32)(mColPos + nameStr.Length() + valueStr.Length() + 4) > mMaxColumn))) { aStr.Append(mLineBreak); mColPos = 0; } // Expand shorthand attribute. if (IsShorthandAttr(attrName, aTagName) && valueStr.IsEmpty()) { valueStr = nameStr; } SerializeAttr(nsAutoString(), nameStr, valueStr, aStr, !isJS && !disableEscaping); } } PRBool nsHTMLContentSerializer::IsNotMinimizable(nsIAtom * aName) { // because MSIE chokes on minimizing other empty elements return (aName != nsHTMLAtoms::area && aName != nsHTMLAtoms::base && aName != nsHTMLAtoms::br && aName != nsHTMLAtoms::col && aName != nsHTMLAtoms::frame && aName != nsHTMLAtoms::hr && aName != nsHTMLAtoms::img && aName != nsHTMLAtoms::input && aName != nsHTMLAtoms::isindex && aName != nsHTMLAtoms::link && aName != nsHTMLAtoms::meta && aName != nsHTMLAtoms::param); } NS_IMETHODIMP nsHTMLContentSerializer::AppendElementStart(nsIDOMElement *aElement, PRBool aHasChildren, nsAString& aStr) { NS_ENSURE_ARG(aElement); nsCOMPtr content = do_QueryInterface(aElement); if (!content) return NS_ERROR_FAILURE; // The _moz_dirty attribute is emitted by the editor to // indicate that this element should be pretty printed // even if we're not in pretty printing mode PRBool hasDirtyAttr = HasDirtyAttr(content); nsIAtom *name = content->Tag(); if (name == nsHTMLAtoms::br) { // hey, could we finally get rid of painful extra
s ???-) nsAutoString typeValue; aElement->GetAttribute(NS_LITERAL_STRING("type"), typeValue); if (typeValue.Equals(NS_LITERAL_STRING("_moz"))) { if (mFlags & nsIDocumentEncoder::OutputForColoredSourceView) { if (aElement == mStartSelectionContainer) AppendToString(kSelectionStart, aStr, PR_FALSE, PR_FALSE); if (aElement == mStartSelectionContainer) AppendToString(kSelectionStart, aStr, PR_FALSE, PR_FALSE); } return NS_OK; } } if (name == nsHTMLAtoms::br && mPreLevel > 0 && (mFlags & nsIDocumentEncoder::OutputNoFormattingInPre)) { AppendToString(mLineBreak, aStr); mMayIgnoreLineBreakSequence = PR_TRUE; mColPos = 0; return NS_OK; } if (name == nsHTMLAtoms::body) { mInBody = PR_TRUE; } if (LineBreakBeforeOpen(name, hasDirtyAttr)) { AppendToString(mLineBreak, aStr); mMayIgnoreLineBreakSequence = PR_TRUE; mColPos = 0; mAddSpace = PR_FALSE; } else if (mAddSpace) { AppendToString(PRUnichar(' '), aStr); mAddSpace = PR_FALSE; } else { MaybeAddNewline(aStr); } // Always reset to avoid false newlines in case MaybeAddNewline wasn't // called mAddNewline = PR_FALSE; StartIndentation(name, hasDirtyAttr, aStr); if (name == nsHTMLAtoms::pre || name == nsHTMLAtoms::script || name == nsHTMLAtoms::style) { mPreLevel++; } if (mFlags & nsIDocumentEncoder::OutputForColoredSourceView) { if (aElement == mStartSelectionContainer) { AppendToString(kSelectionStart, aStr, PR_FALSE, PR_FALSE); } AppendToString(kTagSpanStart, aStr, PR_FALSE, PR_FALSE); AppendToString(kEscapedLessThan, aStr); } else AppendToString(kLessThan, aStr); nsAutoString nameStr; name->ToString(nameStr); AppendToString(nameStr.get(), -1, aStr); // Need to keep track of OL and LI elements in order to get ordinal number // for the LI. if (mIsCopying && name == nsHTMLAtoms::ol){ // We are copying and current node is an OL; // Store it's start attribute value in olState->startVal. nsAutoString start; PRInt32 startAttrVal = 0; aElement->GetAttribute(NS_LITERAL_STRING("start"), start); if (!start.IsEmpty()){ PRInt32 rv = 0; startAttrVal = start.ToInteger(&rv); //If OL has "start" attribute, first LI element has to start with that value //Therefore subtracting 1 as all the LI elements are incrementing it before using it; //In failure of ToInteger(), default StartAttrValue to 0. if (NS_SUCCEEDED(rv)) startAttrVal--; else startAttrVal = 0; } olState* state = new olState(startAttrVal, PR_TRUE); if (state) mOLStateStack.AppendElement(state); } if (mIsCopying && name == nsHTMLAtoms::li) { mIsFirstChildOfOL = IsFirstChildOfOL(aElement); if (mIsFirstChildOfOL){ // If OL is parent of this LI, serialize attributes in different manner. SerializeLIValueAttribute(aElement, aStr); } } // Even LI passed above have to go through this // for serializing attributes other than "value". SerializeAttributes(content, name, aStr); PRBool isXhtml = PR_FALSE; nsCOMPtr domDoc; aElement->GetOwnerDocument(getter_AddRefs(domDoc)); nsCOMPtr docType; nsresult rv = domDoc->GetDoctype(getter_AddRefs(docType)); if (NS_SUCCEEDED(rv) && docType) { nsAutoString publicId; rv = docType->GetPublicId(publicId); if (NS_SUCCEEDED(rv) && (publicId.Equals(NS_LITERAL_STRING("-//W3C//DTD XHTML 1.0 Transitional//EN")) || publicId.Equals(NS_LITERAL_STRING("-//W3C//DTD XHTML 1.0 Strict//EN")))) isXhtml = PR_TRUE; } PRBool notMinimizable = IsNotMinimizable(name); if (mFlags & nsIDocumentEncoder::OutputForColoredSourceView) { if (isXhtml && !aHasChildren && !notMinimizable) AppendToString(NS_LITERAL_STRING(" />"), aStr); else AppendToString(kEscapedGreaterThan, aStr); AppendToString(kSpanEnd, aStr, PR_FALSE, PR_FALSE); } else { if (isXhtml && !aHasChildren && !notMinimizable) AppendToString(NS_LITERAL_STRING(" />"), aStr); else AppendToString(kGreaterThan, aStr); } if (LineBreakAfterOpen(name, hasDirtyAttr)) { AppendToString(mLineBreak, aStr); mMayIgnoreLineBreakSequence = PR_TRUE; mColPos = 0; } // XXX: This special cased textarea code should be // removed when bug #17003 is fixed. if (name == nsHTMLAtoms::textarea) { nsAutoString valueStr; content->GetAttr(kNameSpaceID_None, nsHTMLAtoms::value, valueStr); AppendToString(valueStr, aStr); } if (name == nsHTMLAtoms::script || name == nsHTMLAtoms::style || name == nsHTMLAtoms::noscript || name == nsHTMLAtoms::noframes) { mInCDATA = PR_TRUE; } return NS_OK; } NS_IMETHODIMP nsHTMLContentSerializer::AppendElementEnd(nsIDOMElement *aElement, nsAString& aStr) { NS_ENSURE_ARG(aElement); nsCOMPtr content = do_QueryInterface(aElement); if (!content) return NS_ERROR_FAILURE; PRBool hasDirtyAttr = HasDirtyAttr(content); nsIAtom *name = content->Tag(); if (name == nsHTMLAtoms::pre || name == nsHTMLAtoms::script || name == nsHTMLAtoms::style) { mPreLevel--; } if (mIsCopying && (name == nsHTMLAtoms::ol)){ NS_ASSERTION((mOLStateStack.Count() > 0), "Cannot have an empty OL Stack"); /* Though at this point we must always have an state to be deleted as all the OL opening tags are supposed to push an olState object to the stack*/ if (mOLStateStack.Count() > 0) { olState* state = (olState*)mOLStateStack.ElementAt(mOLStateStack.Count() -1); mOLStateStack.RemoveElementAt(mOLStateStack.Count() -1); delete state; } } nsIParserService* parserService = nsContentUtils::GetParserServiceWeakRef(); if (parserService && (name != nsHTMLAtoms::style)) { PRBool isContainer; PRInt32 id; parserService->HTMLAtomTagToId(name, &id); parserService->IsContainer(id, isContainer); if (!isContainer) return NS_OK; } nsCOMPtr node(do_QueryInterface(aElement)); PRBool hasChildren; nsresult rv = node->HasChildNodes(&hasChildren); if (NS_FAILED(rv)) return rv; PRBool isXhtml = PR_FALSE; nsCOMPtr domDoc; aElement->GetOwnerDocument(getter_AddRefs(domDoc)); nsCOMPtr docType; rv = domDoc->GetDoctype(getter_AddRefs(docType)); if (NS_SUCCEEDED(rv) && docType) { nsAutoString publicId; rv = docType->GetPublicId(publicId); if (NS_SUCCEEDED(rv) && (publicId.Equals(NS_LITERAL_STRING("-//W3C//DTD XHTML 1.0 Transitional//EN")) || publicId.Equals(NS_LITERAL_STRING("-//W3C//DTD XHTML 1.0 Strict//EN")))) isXhtml = PR_TRUE; } PRBool notMinimizable = IsNotMinimizable(name); if (hasChildren || !isXhtml || notMinimizable ) { if (LineBreakBeforeClose(name, hasDirtyAttr)) { AppendToString(mLineBreak, aStr); mMayIgnoreLineBreakSequence = PR_TRUE; mColPos = 0; mAddSpace = PR_FALSE; } else if (mAddSpace) { AppendToString(PRUnichar(' '), aStr); mAddSpace = PR_FALSE; } EndIndentation(name, hasDirtyAttr, aStr); nsAutoString nameStr; name->ToString(nameStr); if (mFlags & nsIDocumentEncoder::OutputForColoredSourceView) { AppendToString(kTagSpanEnd, aStr, PR_FALSE, PR_FALSE); AppendToString(NS_LITERAL_STRING("</"), aStr); } else AppendToString(kEndTag, aStr); AppendToString(nameStr.get(), -1, aStr); if (mFlags & nsIDocumentEncoder::OutputForColoredSourceView) { AppendToString(kEscapedGreaterThan, aStr); AppendToString(kSpanEnd, aStr, PR_FALSE, PR_FALSE); } else AppendToString(kGreaterThan, aStr); } if ((mFlags & nsIDocumentEncoder::OutputForColoredSourceView) && aElement == mEndSelectionContainer) AppendToString(kSelectionEnd, aStr, PR_FALSE, PR_FALSE); if (LineBreakAfterClose(name, hasDirtyAttr)) { AppendToString(mLineBreak, aStr); mMayIgnoreLineBreakSequence = PR_TRUE; mColPos = 0; } else { MaybeFlagNewline(aElement); } mInCDATA = PR_FALSE; return NS_OK; } void nsHTMLContentSerializer::AppendToString(const PRUnichar* aStr, PRInt32 aLength, nsAString& aOutputStr) { if (mBodyOnly && !mInBody) { return; } PRInt32 length = (aLength == -1) ? nsCRT::strlen(aStr) : aLength; mColPos += length; aOutputStr.Append(aStr, length); } void nsHTMLContentSerializer::AppendToString(const PRUnichar aChar, nsAString& aOutputStr) { if (mBodyOnly && !mInBody) { return; } mColPos += 1; aOutputStr.Append(aChar); } static const PRUint16 kValNBSP = 160; static const char kEntityNBSP[] = "nbsp"; static const PRUint16 kGTVal = 62; static const char* kEntities[] = { "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "amp", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "lt", "", "gt" }; static const char* kAttrEntities[] = { "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "quot", "", "", "", "amp", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "lt", "", "gt" }; void nsHTMLContentSerializer::AppendToString(const nsAString& aStr, nsAString& aOutputStr, PRBool aTranslateEntities, PRBool aIncrColumn, PRBool aAsInCDATA) { if (mBodyOnly && !mInBody) { return; } if (aIncrColumn) { mColPos += aStr.Length(); } if (aTranslateEntities && (!mInCDATA || (mFlags & nsIDocumentEncoder::OutputForColoredSourceView))) { if (mFlags & (nsIDocumentEncoder::OutputEncodeBasicEntities | nsIDocumentEncoder::OutputEncodeLatin1Entities | nsIDocumentEncoder::OutputEncodeHTMLEntities | nsIDocumentEncoder::OutputEncodeW3CEntities | nsIDocumentEncoder::OutputEncodeCharacterEntities)) { nsIParserService* parserService = nsContentUtils::GetParserServiceWeakRef(); if (!parserService) { NS_ERROR("Can't get parser service"); return; } nsReadingIterator done_reading; aStr.EndReading(done_reading); // for each chunk of |aString|... PRUint32 advanceLength = 0; nsReadingIterator iter; const char **entityTable = mInAttribute ? kAttrEntities : kEntities; for (aStr.BeginReading(iter); iter != done_reading; iter.advance(PRInt32(advanceLength))) { PRUint32 fragmentLength = iter.size_forward(); const PRUnichar* c = iter.get(); const PRUnichar* fragmentStart = c; const PRUnichar* fragmentEnd = c + fragmentLength; const char* entityText = nsnull; nsCAutoString entityReplacement; char* fullEntityText = nsnull; advanceLength = 0; // for each character in this chunk, check if it // needs to be replaced for (; c < fragmentEnd; c++, advanceLength++) { PRUnichar val = *c; PRBool dontEncodeGT = PRBool(mFlags & nsIDocumentEncoder::DontEncodeGreatherThan) && !mInAttribute; if ((mFlags & nsIDocumentEncoder::OutputEncodeCharacterEntities) && (val == kValNBSP || val > 127 || ((!dontEncodeGT && val <= kGTVal) || (dontEncodeGT && val < kGTVal)) && (entityTable[val][0] != 0))) { nsAutoString entityValue(PRUnichar('#')); entityValue.AppendInt(val); entityText = ToNewCString(entityValue); break; } else if (val == kValNBSP) { entityText = kEntityNBSP; break; } else if (((!dontEncodeGT && val <= kGTVal) || (dontEncodeGT && val < kGTVal)) && (entityTable[val][0] != 0)) { entityText = entityTable[val]; break; } else if (val > 127 && ((val < 256 && mFlags & nsIDocumentEncoder::OutputEncodeLatin1Entities) || mFlags & nsIDocumentEncoder::OutputEncodeHTMLEntities)) { parserService->HTMLConvertUnicodeToEntity(val, entityReplacement); if (!entityReplacement.IsEmpty()) { entityText = entityReplacement.get(); break; } } else if (val > 127 && mFlags & nsIDocumentEncoder::OutputEncodeW3CEntities && mEntityConverter && NS_SUCCEEDED(mEntityConverter->ConvertToEntity(val, nsIEntityConverter::entityW3C, &fullEntityText))) { break; } } aOutputStr.Append(fragmentStart, advanceLength); if (entityText) { if (!mInCDATA && !aAsInCDATA && (mFlags & nsIDocumentEncoder::OutputForColoredSourceView)) aOutputStr.Append(NS_LITERAL_STRING("&")); else aOutputStr.Append(PRUnichar('&')); AppendASCIItoUTF16(entityText, aOutputStr); aOutputStr.Append(PRUnichar(';')); advanceLength++; } // if it comes from nsIEntityConverter, it already has '&' and ';' else if (fullEntityText) { nsAutoString fetStr; fetStr.AssignWithConversion(fullEntityText); if (!mInCDATA && !aAsInCDATA && (mFlags & nsIDocumentEncoder::OutputForColoredSourceView)) fetStr.ReplaceSubstring(NS_LITERAL_STRING("&").get(), NS_LITERAL_STRING("&").get()); aOutputStr.Append(fetStr); nsMemory::Free(fullEntityText); advanceLength++; } } } else { nsXMLContentSerializer::AppendToString(aStr, aOutputStr, aTranslateEntities, aIncrColumn); } return; } aOutputStr.Append(aStr); } void nsHTMLContentSerializer::AppendToStringConvertLF(const nsAString& aStr, nsAString& aOutputStr) { // Convert line-endings to mLineBreak PRUint32 start = 0; PRUint32 theLen = aStr.Length(); while (start < theLen) { PRInt32 eol = aStr.FindChar('\n', start); if (eol == kNotFound) { nsDependentSubstring dataSubstring(aStr, start, theLen - start); AppendToString(dataSubstring, aOutputStr); start = theLen; } else { nsDependentSubstring dataSubstring(aStr, start, eol - start); AppendToString(dataSubstring, aOutputStr); AppendToString(mLineBreak, aOutputStr); start = eol + 1; if (start == theLen) mColPos = 0; } } } PRBool nsHTMLContentSerializer::HasDirtyAttr(nsIContent* aContent) { return PR_FALSE; /* nsAutoString val; if (NS_CONTENT_ATTR_NOT_THERE != aContent->GetAttr(kNameSpaceID_None, nsLayoutAtoms::mozdirty, val)) { return PR_TRUE; } else { return PR_FALSE; } */ } PRBool nsHTMLContentSerializer::LineBreakBeforeOpen(nsIAtom* aName, PRBool aHasDirtyAttr) { if ((!mDoFormat && !aHasDirtyAttr) || mPreLevel || !mColPos || (mFlags & nsIDocumentEncoder::OutputRaw)) { return PR_FALSE; } if (aName == nsHTMLAtoms::title || aName == nsHTMLAtoms::meta || aName == nsHTMLAtoms::link || aName == nsHTMLAtoms::style || aName == nsHTMLAtoms::select || aName == nsHTMLAtoms::option || aName == nsHTMLAtoms::script || aName == nsHTMLAtoms::html) { return PR_TRUE; } else { nsIParserService* parserService = nsContentUtils::GetParserServiceWeakRef(); if (parserService) { PRBool res; PRInt32 id; parserService->HTMLAtomTagToId(aName, &id); parserService->IsBlock(id, res); return res; } } return PR_FALSE; } PRBool nsHTMLContentSerializer::LineBreakAfterOpen(nsIAtom* aName, PRBool aHasDirtyAttr) { if ((!mDoFormat && !aHasDirtyAttr) || mPreLevel || (mFlags & nsIDocumentEncoder::OutputRaw)) { return PR_FALSE; } if ((aName == nsHTMLAtoms::html) || (aName == nsHTMLAtoms::head) || (aName == nsHTMLAtoms::body) || (aName == nsHTMLAtoms::ul) || (aName == nsHTMLAtoms::ol) || (aName == nsHTMLAtoms::dl) || (aName == nsHTMLAtoms::table) || (aName == nsHTMLAtoms::tbody) || (aName == nsHTMLAtoms::tr) || (aName == nsHTMLAtoms::br) || (aName == nsHTMLAtoms::meta) || (aName == nsHTMLAtoms::link) || (aName == nsHTMLAtoms::script) || (aName == nsHTMLAtoms::select) || (aName == nsHTMLAtoms::map) || (aName == nsHTMLAtoms::area) || (aName == nsHTMLAtoms::style)) { return PR_TRUE; } return PR_FALSE; } PRBool nsHTMLContentSerializer::LineBreakBeforeClose(nsIAtom* aName, PRBool aHasDirtyAttr) { if ((!mDoFormat && !aHasDirtyAttr) || mPreLevel || !mColPos || (mFlags & nsIDocumentEncoder::OutputRaw)) { return PR_FALSE; } if ((aName == nsHTMLAtoms::html) || (aName == nsHTMLAtoms::head) || (aName == nsHTMLAtoms::body) || (aName == nsHTMLAtoms::ul) || (aName == nsHTMLAtoms::ol) || (aName == nsHTMLAtoms::dl) || (aName == nsHTMLAtoms::select) || (aName == nsHTMLAtoms::table) || (aName == nsHTMLAtoms::tbody)) { return PR_TRUE; } return PR_FALSE; } PRBool nsHTMLContentSerializer::LineBreakAfterClose(nsIAtom* aName, PRBool aHasDirtyAttr) { if ((!mDoFormat && !aHasDirtyAttr) || mPreLevel || (mFlags & nsIDocumentEncoder::OutputRaw)) { return PR_FALSE; } if ((aName == nsHTMLAtoms::html) || (aName == nsHTMLAtoms::head) || (aName == nsHTMLAtoms::body) || (aName == nsHTMLAtoms::tr) || (aName == nsHTMLAtoms::th) || (aName == nsHTMLAtoms::td) || (aName == nsHTMLAtoms::pre) || (aName == nsHTMLAtoms::title) || (aName == nsHTMLAtoms::li) || (aName == nsHTMLAtoms::dt) || (aName == nsHTMLAtoms::dd) || (aName == nsHTMLAtoms::blockquote) || (aName == nsHTMLAtoms::select) || (aName == nsHTMLAtoms::option) || (aName == nsHTMLAtoms::p) || (aName == nsHTMLAtoms::map) || (aName == nsHTMLAtoms::div)) { return PR_TRUE; } else { nsIParserService* parserService = nsContentUtils::GetParserServiceWeakRef(); if (parserService) { PRBool res; PRInt32 id; parserService->HTMLAtomTagToId(aName, &id); parserService->IsBlock(id, res); return res; } } return PR_FALSE; } void nsHTMLContentSerializer::StartIndentation(nsIAtom* aName, PRBool aHasDirtyAttr, nsAString& aStr) { if ((mDoFormat || aHasDirtyAttr) && !mPreLevel && !mColPos) { for (PRInt32 i = mIndent; --i >= 0; ) { AppendToString(kIndentStr, aStr); } } if ((aName == nsHTMLAtoms::head) || (aName == nsHTMLAtoms::table) || (aName == nsHTMLAtoms::tr) || (aName == nsHTMLAtoms::ul) || (aName == nsHTMLAtoms::ol) || (aName == nsHTMLAtoms::dl) || (aName == nsHTMLAtoms::tbody) || (aName == nsHTMLAtoms::form) || (aName == nsHTMLAtoms::frameset) || (aName == nsHTMLAtoms::blockquote) || (aName == nsHTMLAtoms::li) || (aName == nsHTMLAtoms::dt) || (aName == nsHTMLAtoms::dd)) { mIndent++; } } void nsHTMLContentSerializer::EndIndentation(nsIAtom* aName, PRBool aHasDirtyAttr, nsAString& aStr) { if ((aName == nsHTMLAtoms::head) || (aName == nsHTMLAtoms::table) || (aName == nsHTMLAtoms::tr) || (aName == nsHTMLAtoms::ul) || (aName == nsHTMLAtoms::ol) || (aName == nsHTMLAtoms::dl) || (aName == nsHTMLAtoms::li) || (aName == nsHTMLAtoms::tbody) || (aName == nsHTMLAtoms::form) || (aName == nsHTMLAtoms::blockquote) || (aName == nsHTMLAtoms::dt) || (aName == nsHTMLAtoms::dd) || (aName == nsHTMLAtoms::frameset)) { mIndent--; } if ((mDoFormat || aHasDirtyAttr) && !mPreLevel && !mColPos) { for (PRInt32 i = mIndent; --i >= 0; ) { AppendToString(kIndentStr, aStr); } } } // See if the string has any lines longer than longLineLen: // if so, we presume formatting is wonky (e.g. the node has been edited) // and we'd better rewrap the whole text node. PRBool nsHTMLContentSerializer::HasLongLines(const nsString& text, PRInt32& aLastNewlineOffset) { PRUint32 start=0; PRUint32 theLen=text.Length(); PRBool rv = PR_FALSE; aLastNewlineOffset = kNotFound; for (start = 0; start < theLen; ) { PRInt32 eol = text.FindChar('\n', start); if (eol < 0) { eol = text.Length(); } else { aLastNewlineOffset = eol; } if (PRInt32(eol - start) > kLongLineLen) rv = PR_TRUE; start = eol+1; } return rv; } void nsHTMLContentSerializer::SerializeLIValueAttribute(nsIDOMElement* aElement, nsAString& aStr) { // We are copying and we are at the "first" LI node of OL in selected range. // It may not be the first LI child of OL but it's first in the selected range. // Note that we get into this condition only once per a OL. nsCOMPtr node = do_QueryInterface(aElement); PRBool found = PR_FALSE; nsIDOMNode* currNode = node; nsAutoString valueStr; PRInt32 offset = 0; olState defaultOLState(0, PR_FALSE); olState* state = nsnull; if (mOLStateStack.Count() > 0) state = (olState*)mOLStateStack.ElementAt(mOLStateStack.Count()-1); /* Though we should never reach to a "state" as null or mOLStateStack.Count() == 0 at this point as all LI are supposed to be inside some OL and OL tag should have pushed a state to the olStateStack.*/ if (!state || mOLStateStack.Count() == 0) state = &defaultOLState; PRInt32 startVal = state->startVal; state->isFirstListItem = PR_FALSE; // Traverse previous siblings until we find one with "value" attribute. // offset keeps track of how many previous siblings we had tocurrNode traverse. while (currNode && !found) { nsCOMPtr currElement = do_QueryInterface(currNode); // currElement may be null if it were a text node. if (currElement) { nsAutoString tagName; currElement->GetTagName(tagName); if (tagName.EqualsIgnoreCase("LI")) { currElement->GetAttribute(NS_LITERAL_STRING("value"), valueStr); if (valueStr.IsEmpty()) offset++; else { found = PR_TRUE; PRInt32 rv = 0; startVal = valueStr.ToInteger(&rv); } } } currNode->GetPreviousSibling(&currNode); } // If LI was not having "value", Set the "value" attribute for it. // Note that We are at the first LI in the selected range of OL. if (offset == 0 && found) { // offset = 0 => LI itself has the value attribute and we did not need to traverse back. // Just serialize value attribute like other tags. SerializeAttr(nsAutoString(), NS_LITERAL_STRING("value"), valueStr, aStr, PR_FALSE); } else if (offset == 1 && !found) { /*(offset = 1 && !found) means either LI is the first child node of OL and LI is not having "value" attribute. In that case we would not like to set "value" attribute to reduce the changes. */ //do nothing... } else if (offset > 0) { // Set value attribute. nsAutoString valueStr; //As serializer needs to use this valueAttr we are creating here, valueStr.AppendInt(startVal + offset); SerializeAttr(nsAutoString(), NS_LITERAL_STRING("value"), valueStr, aStr, PR_FALSE); } } PRBool nsHTMLContentSerializer::IsFirstChildOfOL(nsIDOMElement* aElement){ nsCOMPtr node = do_QueryInterface(aElement); nsIDOMNode* parentNode; nsAutoString parentName; node->GetParentNode(&parentNode); if (parentNode) parentNode->GetNodeName(parentName); else return PR_FALSE; if (parentName.EqualsIgnoreCase("OL")) { olState defaultOLState(0, PR_FALSE); olState* state = nsnull; if (mOLStateStack.Count() > 0) state = (olState*)mOLStateStack.ElementAt(mOLStateStack.Count()-1); /* Though we should never reach to a "state" as null at this point as all LI are supposed to be inside some OL and OL tag should have pushed a state to the mOLStateStack.*/ if (!state) state = &defaultOLState; if (state->isFirstListItem) return PR_TRUE; return PR_FALSE; } else return PR_FALSE; }