michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /* michael@0: * nsIContentSerializer implementation that can be used with an michael@0: * nsIDocumentEncoder to convert an XML DOM to an XML string that michael@0: * could be parsed into more or less the original DOM. michael@0: */ michael@0: michael@0: #include "nsXMLContentSerializer.h" michael@0: michael@0: #include "nsGkAtoms.h" michael@0: #include "nsIDOMProcessingInstruction.h" michael@0: #include "nsIDOMComment.h" michael@0: #include "nsIDOMDocumentType.h" michael@0: #include "nsIContent.h" michael@0: #include "nsIDocument.h" michael@0: #include "nsIDocumentEncoder.h" michael@0: #include "nsNameSpaceManager.h" michael@0: #include "nsTextFragment.h" michael@0: #include "nsString.h" michael@0: #include "prprf.h" michael@0: #include "nsUnicharUtils.h" michael@0: #include "nsCRT.h" michael@0: #include "nsContentUtils.h" michael@0: #include "nsAttrName.h" michael@0: #include "nsILineBreaker.h" michael@0: #include "mozilla/dom/Element.h" michael@0: #include "nsParserConstants.h" michael@0: michael@0: using namespace mozilla::dom; michael@0: michael@0: #define kXMLNS "xmlns" michael@0: michael@0: // to be readable, we assume that an indented line contains michael@0: // at least this number of characters (arbitrary value here). michael@0: // This is a limit for the indentation. michael@0: #define MIN_INDENTED_LINE_LENGTH 15 michael@0: michael@0: // the string used to indent. michael@0: #define INDENT_STRING " " michael@0: #define INDENT_STRING_LENGTH 2 michael@0: michael@0: nsresult NS_NewXMLContentSerializer(nsIContentSerializer** aSerializer) michael@0: { michael@0: nsXMLContentSerializer* it = new nsXMLContentSerializer(); michael@0: if (!it) { michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: michael@0: return CallQueryInterface(it, aSerializer); michael@0: } michael@0: michael@0: nsXMLContentSerializer::nsXMLContentSerializer() michael@0: : mPrefixIndex(0), michael@0: mColPos(0), michael@0: mIndentOverflow(0), michael@0: mIsIndentationAddedOnCurrentLine(false), michael@0: mInAttribute(false), michael@0: mAddNewlineForRootNode(false), michael@0: mAddSpace(false), michael@0: mMayIgnoreLineBreakSequence(false), michael@0: mBodyOnly(false), michael@0: mInBody(0) michael@0: { michael@0: } michael@0: michael@0: nsXMLContentSerializer::~nsXMLContentSerializer() michael@0: { michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS(nsXMLContentSerializer, nsIContentSerializer) michael@0: michael@0: NS_IMETHODIMP michael@0: nsXMLContentSerializer::Init(uint32_t aFlags, uint32_t aWrapColumn, michael@0: const char* aCharSet, bool aIsCopying, michael@0: bool aRewriteEncodingDeclaration) michael@0: { michael@0: mPrefixIndex = 0; michael@0: mColPos = 0; michael@0: mIndentOverflow = 0; michael@0: mIsIndentationAddedOnCurrentLine = false; michael@0: mInAttribute = false; michael@0: mAddNewlineForRootNode = false; michael@0: mAddSpace = false; michael@0: mMayIgnoreLineBreakSequence = false; michael@0: mBodyOnly = false; michael@0: mInBody = 0; michael@0: michael@0: mCharset = aCharSet; michael@0: mFlags = aFlags; michael@0: michael@0: // Set the line break character: michael@0: if ((mFlags & nsIDocumentEncoder::OutputCRLineBreak) michael@0: && (mFlags & nsIDocumentEncoder::OutputLFLineBreak)) { // Windows michael@0: mLineBreak.AssignLiteral("\r\n"); michael@0: } michael@0: else if (mFlags & nsIDocumentEncoder::OutputCRLineBreak) { // Mac michael@0: mLineBreak.AssignLiteral("\r"); michael@0: } michael@0: else if (mFlags & nsIDocumentEncoder::OutputLFLineBreak) { // Unix/DOM michael@0: mLineBreak.AssignLiteral("\n"); michael@0: } michael@0: else { michael@0: mLineBreak.AssignLiteral(NS_LINEBREAK); // Platform/default michael@0: } michael@0: michael@0: mDoRaw = !!(mFlags & nsIDocumentEncoder::OutputRaw); michael@0: michael@0: mDoFormat = (mFlags & nsIDocumentEncoder::OutputFormatted && !mDoRaw); michael@0: michael@0: mDoWrap = (mFlags & nsIDocumentEncoder::OutputWrap && !mDoRaw); michael@0: michael@0: if (!aWrapColumn) { michael@0: mMaxColumn = 72; michael@0: } michael@0: else { michael@0: mMaxColumn = aWrapColumn; michael@0: } michael@0: michael@0: mPreLevel = 0; michael@0: mIsIndentationAddedOnCurrentLine = false; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsXMLContentSerializer::AppendTextData(nsIContent* aNode, michael@0: int32_t aStartOffset, michael@0: int32_t aEndOffset, michael@0: nsAString& aStr, michael@0: bool aTranslateEntities) michael@0: { michael@0: nsIContent* content = aNode; michael@0: const nsTextFragment* frag; michael@0: if (!content || !(frag = content->GetText())) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: int32_t fragLength = frag->GetLength(); michael@0: int32_t endoffset = (aEndOffset == -1) ? fragLength : std::min(aEndOffset, fragLength); michael@0: int32_t length = endoffset - aStartOffset; michael@0: michael@0: NS_ASSERTION(aStartOffset >= 0, "Negative start offset for text fragment!"); michael@0: NS_ASSERTION(aStartOffset <= endoffset, "A start offset is beyond the end of the text fragment!"); michael@0: michael@0: if (length <= 0) { michael@0: // XXX Zero is a legal value, maybe non-zero values should be an michael@0: // error. michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (frag->Is2b()) { michael@0: const char16_t *strStart = frag->Get2b() + aStartOffset; michael@0: if (aTranslateEntities) { michael@0: AppendAndTranslateEntities(Substring(strStart, strStart + length), aStr); michael@0: } michael@0: else { michael@0: aStr.Append(Substring(strStart, strStart + length)); michael@0: } michael@0: } michael@0: else { michael@0: if (aTranslateEntities) { michael@0: AppendAndTranslateEntities(NS_ConvertASCIItoUTF16(frag->Get1b()+aStartOffset, length), aStr); michael@0: } michael@0: else { michael@0: aStr.Append(NS_ConvertASCIItoUTF16(frag->Get1b()+aStartOffset, length)); michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsXMLContentSerializer::AppendText(nsIContent* aText, michael@0: int32_t aStartOffset, michael@0: int32_t aEndOffset, michael@0: nsAString& aStr) michael@0: { michael@0: NS_ENSURE_ARG(aText); michael@0: michael@0: nsAutoString data; michael@0: nsresult rv; michael@0: michael@0: rv = AppendTextData(aText, aStartOffset, aEndOffset, data, true); michael@0: if (NS_FAILED(rv)) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: if (mPreLevel > 0 || mDoRaw) { michael@0: AppendToStringConvertLF(data, aStr); michael@0: } michael@0: else if (mDoFormat) { michael@0: AppendToStringFormatedWrapped(data, aStr); michael@0: } michael@0: else if (mDoWrap) { michael@0: AppendToStringWrapped(data, aStr); michael@0: } michael@0: else { michael@0: AppendToStringConvertLF(data, aStr); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsXMLContentSerializer::AppendCDATASection(nsIContent* aCDATASection, michael@0: int32_t aStartOffset, michael@0: int32_t aEndOffset, michael@0: nsAString& aStr) michael@0: { michael@0: NS_ENSURE_ARG(aCDATASection); michael@0: nsresult rv; michael@0: michael@0: NS_NAMED_LITERAL_STRING(cdata , " 0 || mDoRaw) { michael@0: AppendToString(cdata, aStr); michael@0: } michael@0: else if (mDoFormat) { michael@0: AppendToStringFormatedWrapped(cdata, aStr); michael@0: } michael@0: else if (mDoWrap) { michael@0: AppendToStringWrapped(cdata, aStr); michael@0: } michael@0: else { michael@0: AppendToString(cdata, aStr); michael@0: } michael@0: michael@0: nsAutoString data; michael@0: rv = AppendTextData(aCDATASection, aStartOffset, aEndOffset, data, false); michael@0: if (NS_FAILED(rv)) return NS_ERROR_FAILURE; michael@0: michael@0: AppendToStringConvertLF(data, aStr); michael@0: michael@0: AppendToString(NS_LITERAL_STRING("]]>"), aStr); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsXMLContentSerializer::AppendProcessingInstruction(nsIContent* aPI, michael@0: int32_t aStartOffset, michael@0: int32_t aEndOffset, michael@0: nsAString& aStr) michael@0: { michael@0: nsCOMPtr pi = do_QueryInterface(aPI); michael@0: NS_ENSURE_ARG(pi); michael@0: nsresult rv; michael@0: nsAutoString target, data, start; michael@0: michael@0: MaybeAddNewlineForRootNode(aStr); michael@0: michael@0: rv = pi->GetTarget(target); michael@0: if (NS_FAILED(rv)) return NS_ERROR_FAILURE; michael@0: michael@0: rv = pi->GetData(data); michael@0: if (NS_FAILED(rv)) return NS_ERROR_FAILURE; michael@0: michael@0: start.AppendLiteral(" 0 || mDoRaw) { michael@0: AppendToString(start, aStr); michael@0: } michael@0: else if (mDoFormat) { michael@0: if (mAddSpace) { michael@0: AppendNewLineToString(aStr); michael@0: } michael@0: AppendToStringFormatedWrapped(start, aStr); michael@0: } michael@0: else if (mDoWrap) { michael@0: AppendToStringWrapped(start, aStr); michael@0: } michael@0: else { michael@0: AppendToString(start, aStr); michael@0: } michael@0: michael@0: if (!data.IsEmpty()) { michael@0: AppendToString(char16_t(' '), aStr); michael@0: AppendToStringConvertLF(data, aStr); michael@0: } michael@0: AppendToString(NS_LITERAL_STRING("?>"), aStr); michael@0: michael@0: MaybeFlagNewlineForRootNode(aPI); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsXMLContentSerializer::AppendComment(nsIContent* aComment, michael@0: int32_t aStartOffset, michael@0: int32_t aEndOffset, michael@0: nsAString& aStr) michael@0: { michael@0: nsCOMPtr comment = do_QueryInterface(aComment); michael@0: NS_ENSURE_ARG(comment); michael@0: nsresult rv; michael@0: nsAutoString data; michael@0: michael@0: rv = comment->GetData(data); michael@0: if (NS_FAILED(rv)) return NS_ERROR_FAILURE; michael@0: michael@0: int32_t dataLength = data.Length(); michael@0: if (aStartOffset || (aEndOffset != -1 && aEndOffset < dataLength)) { michael@0: int32_t length = michael@0: (aEndOffset == -1) ? dataLength : std::min(aEndOffset, dataLength); michael@0: length -= aStartOffset; michael@0: michael@0: nsAutoString frag; michael@0: if (length > 0) { michael@0: data.Mid(frag, aStartOffset, length); michael@0: } michael@0: data.Assign(frag); michael@0: } michael@0: michael@0: MaybeAddNewlineForRootNode(aStr); michael@0: michael@0: NS_NAMED_LITERAL_STRING(startComment, ""), aStr); michael@0: michael@0: MaybeFlagNewlineForRootNode(aComment); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsXMLContentSerializer::AppendDoctype(nsIContent* aDocType, michael@0: nsAString& aStr) michael@0: { michael@0: nsCOMPtr docType = do_QueryInterface(aDocType); michael@0: NS_ENSURE_ARG(docType); michael@0: nsresult rv; michael@0: nsAutoString name, publicId, systemId, internalSubset; michael@0: michael@0: rv = docType->GetName(name); michael@0: if (NS_FAILED(rv)) return NS_ERROR_FAILURE; michael@0: rv = docType->GetPublicId(publicId); michael@0: if (NS_FAILED(rv)) return NS_ERROR_FAILURE; michael@0: rv = docType->GetSystemId(systemId); michael@0: if (NS_FAILED(rv)) return NS_ERROR_FAILURE; michael@0: rv = docType->GetInternalSubset(internalSubset); michael@0: if (NS_FAILED(rv)) return NS_ERROR_FAILURE; michael@0: michael@0: MaybeAddNewlineForRootNode(aStr); michael@0: michael@0: AppendToString(NS_LITERAL_STRING("mPrefix.Assign(aPrefix); michael@0: decl->mURI.Assign(aURI); michael@0: // Don't addref - this weak reference will be removed when michael@0: // we pop the stack michael@0: decl->mOwner = aOwner; michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsXMLContentSerializer::PopNameSpaceDeclsFor(nsIContent* aOwner) michael@0: { michael@0: int32_t index, count; michael@0: michael@0: count = mNameSpaceStack.Length(); michael@0: for (index = count - 1; index >= 0; index--) { michael@0: if (mNameSpaceStack[index].mOwner != aOwner) { michael@0: break; michael@0: } michael@0: mNameSpaceStack.RemoveElementAt(index); michael@0: } michael@0: } michael@0: michael@0: bool michael@0: nsXMLContentSerializer::ConfirmPrefix(nsAString& aPrefix, michael@0: const nsAString& aURI, michael@0: nsIContent* aElement, michael@0: bool aIsAttribute) michael@0: { michael@0: if (aPrefix.EqualsLiteral(kXMLNS)) { michael@0: return false; michael@0: } michael@0: michael@0: if (aURI.EqualsLiteral("http://www.w3.org/XML/1998/namespace")) { michael@0: // The prefix must be xml for this namespace. We don't need to declare it, michael@0: // so always just set the prefix to xml. michael@0: aPrefix.AssignLiteral("xml"); michael@0: michael@0: return false; michael@0: } michael@0: michael@0: bool mustHavePrefix; michael@0: if (aIsAttribute) { michael@0: if (aURI.IsEmpty()) { michael@0: // Attribute in the null namespace. This just shouldn't have a prefix. michael@0: // And there's no need to push any namespace decls michael@0: aPrefix.Truncate(); michael@0: return false; michael@0: } michael@0: michael@0: // Attribute not in the null namespace -- must have a prefix michael@0: mustHavePrefix = true; michael@0: } else { michael@0: // Not an attribute, so doesn't _have_ to have a prefix michael@0: mustHavePrefix = false; michael@0: } michael@0: michael@0: // Keep track of the closest prefix that's bound to aURI and whether we've michael@0: // found such a thing. closestURIMatch holds the prefix, and uriMatch michael@0: // indicates whether we actually have one. michael@0: nsAutoString closestURIMatch; michael@0: bool uriMatch = false; michael@0: michael@0: // Also keep track of whether we've seen aPrefix already. If we have, that michael@0: // means that it's already bound to a URI different from aURI, so even if we michael@0: // later (so in a more outer scope) see it bound to aURI we can't reuse it. michael@0: bool haveSeenOurPrefix = false; michael@0: michael@0: int32_t count = mNameSpaceStack.Length(); michael@0: int32_t index = count - 1; michael@0: while (index >= 0) { michael@0: NameSpaceDecl& decl = mNameSpaceStack.ElementAt(index); michael@0: // Check if we've found a prefix match michael@0: if (aPrefix.Equals(decl.mPrefix)) { michael@0: michael@0: // If the URIs match and aPrefix is not bound to any other URI, we can michael@0: // use aPrefix michael@0: if (!haveSeenOurPrefix && aURI.Equals(decl.mURI)) { michael@0: // Just use our uriMatch stuff. That will deal with an empty aPrefix michael@0: // the right way. We can break out of the loop now, though. michael@0: uriMatch = true; michael@0: closestURIMatch = aPrefix; michael@0: break; michael@0: } michael@0: michael@0: haveSeenOurPrefix = true; michael@0: michael@0: // If they don't, and either: michael@0: // 1) We have a prefix (so we'd be redeclaring this prefix to point to a michael@0: // different namespace) or michael@0: // 2) We're looking at an existing default namespace decl on aElement (so michael@0: // we can't create a new default namespace decl for this URI) michael@0: // then generate a new prefix. Note that we do NOT generate new prefixes michael@0: // if we happen to have aPrefix == decl->mPrefix == "" and mismatching michael@0: // URIs when |decl| doesn't have aElement as its owner. In that case we michael@0: // can simply push the new namespace URI as the default namespace for michael@0: // aElement. michael@0: if (!aPrefix.IsEmpty() || decl.mOwner == aElement) { michael@0: NS_ASSERTION(!aURI.IsEmpty(), michael@0: "Not allowed to add a xmlns attribute with an empty " michael@0: "namespace name unless it declares the default " michael@0: "namespace."); michael@0: michael@0: GenerateNewPrefix(aPrefix); michael@0: // Now we need to validate our new prefix/uri combination; check it michael@0: // against the full namespace stack again. Note that just restarting michael@0: // the while loop is ok, since we haven't changed aURI, so the michael@0: // closestURIMatch and uriMatch state is not affected. michael@0: index = count - 1; michael@0: haveSeenOurPrefix = false; michael@0: continue; michael@0: } michael@0: } michael@0: michael@0: // If we've found a URI match, then record the first one michael@0: if (!uriMatch && aURI.Equals(decl.mURI)) { michael@0: // Need to check that decl->mPrefix is not declared anywhere closer to michael@0: // us. If it is, we can't use it. michael@0: bool prefixOK = true; michael@0: int32_t index2; michael@0: for (index2 = count-1; index2 > index && prefixOK; --index2) { michael@0: prefixOK = (mNameSpaceStack[index2].mPrefix != decl.mPrefix); michael@0: } michael@0: michael@0: if (prefixOK) { michael@0: uriMatch = true; michael@0: closestURIMatch.Assign(decl.mPrefix); michael@0: } michael@0: } michael@0: michael@0: --index; michael@0: } michael@0: michael@0: // At this point the following invariants hold: michael@0: // 1) The prefix in closestURIMatch is mapped to aURI in our scope if michael@0: // uriMatch is set. michael@0: // 2) There is nothing on the namespace stack that has aPrefix as the prefix michael@0: // and a _different_ URI, except for the case aPrefix.IsEmpty (and michael@0: // possible default namespaces on ancestors) michael@0: michael@0: // So if uriMatch is set it's OK to use the closestURIMatch prefix. The one michael@0: // exception is when closestURIMatch is actually empty (default namespace michael@0: // decl) and we must have a prefix. michael@0: if (uriMatch && (!mustHavePrefix || !closestURIMatch.IsEmpty())) { michael@0: aPrefix.Assign(closestURIMatch); michael@0: return false; michael@0: } michael@0: michael@0: if (aPrefix.IsEmpty()) { michael@0: // At this point, aPrefix is empty (which means we never had a prefix to michael@0: // start with). If we must have a prefix, just generate a new prefix and michael@0: // then send it back through the namespace stack checks to make sure it's michael@0: // OK. michael@0: if (mustHavePrefix) { michael@0: GenerateNewPrefix(aPrefix); michael@0: return ConfirmPrefix(aPrefix, aURI, aElement, aIsAttribute); michael@0: } michael@0: michael@0: // One final special case. If aPrefix is empty and we never saw an empty michael@0: // prefix (default namespace decl) on the namespace stack and we're in the michael@0: // null namespace there is no reason to output an |xmlns=""| here. It just michael@0: // makes the output less readable. michael@0: if (!haveSeenOurPrefix && aURI.IsEmpty()) { michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: // Now just set aURI as the new default namespace URI. Indicate that we need michael@0: // to create a namespace decl for the final prefix michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: nsXMLContentSerializer::GenerateNewPrefix(nsAString& aPrefix) michael@0: { michael@0: aPrefix.AssignLiteral("a"); michael@0: char buf[128]; michael@0: PR_snprintf(buf, sizeof(buf), "%d", mPrefixIndex++); michael@0: AppendASCIItoUTF16(buf, aPrefix); michael@0: } michael@0: michael@0: void michael@0: nsXMLContentSerializer::SerializeAttr(const nsAString& aPrefix, michael@0: const nsAString& aName, michael@0: const nsAString& aValue, michael@0: nsAString& aStr, michael@0: bool aDoEscapeEntities) michael@0: { michael@0: nsAutoString attrString_; michael@0: // For innerHTML we can do faster appending without michael@0: // temporary strings. michael@0: bool rawAppend = mDoRaw && aDoEscapeEntities; michael@0: nsAString& attrString = (rawAppend) ? aStr : attrString_; michael@0: michael@0: attrString.Append(char16_t(' ')); michael@0: if (!aPrefix.IsEmpty()) { michael@0: attrString.Append(aPrefix); michael@0: attrString.Append(char16_t(':')); michael@0: } michael@0: attrString.Append(aName); michael@0: michael@0: if (aDoEscapeEntities) { michael@0: // if problem characters are turned into character entity references michael@0: // then there will be no problem with the value delimiter characters michael@0: attrString.AppendLiteral("=\""); michael@0: michael@0: mInAttribute = true; michael@0: AppendAndTranslateEntities(aValue, attrString); michael@0: mInAttribute = false; michael@0: michael@0: attrString.Append(char16_t('"')); michael@0: if (rawAppend) { michael@0: return; michael@0: } michael@0: } michael@0: else { michael@0: // Depending on whether the attribute value contains quotes or apostrophes we michael@0: // need to select the delimiter character and escape characters using michael@0: // character entity references, ignoring the value of aDoEscapeEntities. michael@0: // See http://www.w3.org/TR/REC-html40/appendix/notes.html#h-B.3.2.2 for michael@0: // the standard on character entity references in values. We also have to michael@0: // make sure to escape any '&' characters. michael@0: michael@0: bool bIncludesSingle = false; michael@0: bool bIncludesDouble = false; michael@0: nsAString::const_iterator iCurr, iEnd; michael@0: uint32_t uiSize, i; michael@0: aValue.BeginReading(iCurr); michael@0: aValue.EndReading(iEnd); michael@0: for ( ; iCurr != iEnd; iCurr.advance(uiSize) ) { michael@0: const char16_t * buf = iCurr.get(); michael@0: uiSize = iCurr.size_forward(); michael@0: for ( i = 0; i < uiSize; i++, buf++ ) { michael@0: if ( *buf == char16_t('\'') ) michael@0: { michael@0: bIncludesSingle = true; michael@0: if ( bIncludesDouble ) break; michael@0: } michael@0: else if ( *buf == char16_t('"') ) michael@0: { michael@0: bIncludesDouble = true; michael@0: if ( bIncludesSingle ) break; michael@0: } michael@0: } michael@0: // if both have been found we don't need to search further michael@0: if ( bIncludesDouble && bIncludesSingle ) break; michael@0: } michael@0: michael@0: // Delimiter and escaping is according to the following table michael@0: // bIncludesDouble bIncludesSingle Delimiter Escape Double Quote michael@0: // FALSE FALSE " FALSE michael@0: // FALSE TRUE " FALSE michael@0: // TRUE FALSE ' FALSE michael@0: // TRUE TRUE " TRUE michael@0: char16_t cDelimiter = michael@0: (bIncludesDouble && !bIncludesSingle) ? char16_t('\'') : char16_t('"'); michael@0: attrString.Append(char16_t('=')); michael@0: attrString.Append(cDelimiter); michael@0: nsAutoString sValue(aValue); michael@0: sValue.ReplaceSubstring(NS_LITERAL_STRING("&"), michael@0: NS_LITERAL_STRING("&")); michael@0: if (bIncludesDouble && bIncludesSingle) { michael@0: sValue.ReplaceSubstring(NS_LITERAL_STRING("\""), michael@0: NS_LITERAL_STRING(""")); michael@0: } michael@0: attrString.Append(sValue); michael@0: attrString.Append(cDelimiter); michael@0: } michael@0: if (mPreLevel > 0 || mDoRaw) { michael@0: AppendToStringConvertLF(attrString, aStr); michael@0: } michael@0: else if (mDoFormat) { michael@0: AppendToStringFormatedWrapped(attrString, aStr); michael@0: } michael@0: else if (mDoWrap) { michael@0: AppendToStringWrapped(attrString, aStr); michael@0: } michael@0: else { michael@0: AppendToStringConvertLF(attrString, aStr); michael@0: } michael@0: } michael@0: michael@0: uint32_t michael@0: nsXMLContentSerializer::ScanNamespaceDeclarations(nsIContent* aContent, michael@0: nsIContent *aOriginalElement, michael@0: const nsAString& aTagNamespaceURI) michael@0: { michael@0: uint32_t index, count; michael@0: nsAutoString uriStr, valueStr; michael@0: michael@0: count = aContent->GetAttrCount(); michael@0: michael@0: // First scan for namespace declarations, pushing each on the stack michael@0: uint32_t skipAttr = count; michael@0: for (index = 0; index < count; index++) { michael@0: michael@0: const nsAttrName* name = aContent->GetAttrNameAt(index); michael@0: int32_t namespaceID = name->NamespaceID(); michael@0: nsIAtom *attrName = name->LocalName(); michael@0: michael@0: if (namespaceID == kNameSpaceID_XMLNS || michael@0: // Also push on the stack attrs named "xmlns" in the null michael@0: // namespace... because once we serialize those out they'll look like michael@0: // namespace decls. :( michael@0: // XXXbz what if we have both "xmlns" in the null namespace and "xmlns" michael@0: // in the xmlns namespace? michael@0: (namespaceID == kNameSpaceID_None && michael@0: attrName == nsGkAtoms::xmlns)) { michael@0: aContent->GetAttr(namespaceID, attrName, uriStr); michael@0: michael@0: if (!name->GetPrefix()) { michael@0: if (aTagNamespaceURI.IsEmpty() && !uriStr.IsEmpty()) { michael@0: // If the element is in no namespace we need to add a xmlns michael@0: // attribute to declare that. That xmlns attribute must not have a michael@0: // prefix (see http://www.w3.org/TR/REC-xml-names/#dt-prefix), ie it michael@0: // must declare the default namespace. We just found an xmlns michael@0: // attribute that declares the default namespace to something michael@0: // non-empty. We're going to ignore this attribute, for children we michael@0: // will detect that we need to add it again and attributes aren't michael@0: // affected by the default namespace. michael@0: skipAttr = index; michael@0: } michael@0: else { michael@0: // Default NS attribute does not have prefix (and the name is "xmlns") michael@0: PushNameSpaceDecl(EmptyString(), uriStr, aOriginalElement); michael@0: } michael@0: } michael@0: else { michael@0: PushNameSpaceDecl(nsDependentAtomString(attrName), uriStr, michael@0: aOriginalElement); michael@0: } michael@0: } michael@0: } michael@0: return skipAttr; michael@0: } michael@0: michael@0: michael@0: bool michael@0: nsXMLContentSerializer::IsJavaScript(nsIContent * aContent, nsIAtom* aAttrNameAtom, michael@0: int32_t aAttrNamespaceID, const nsAString& aValueString) michael@0: { michael@0: bool isHtml = aContent->IsHTML(); michael@0: bool isXul = aContent->IsXUL(); michael@0: bool isSvg = aContent->IsSVG(); michael@0: michael@0: if (aAttrNamespaceID == kNameSpaceID_None && michael@0: (isHtml || isXul || isSvg) && michael@0: (aAttrNameAtom == nsGkAtoms::href || michael@0: aAttrNameAtom == nsGkAtoms::src)) { michael@0: michael@0: static const char kJavaScript[] = "javascript"; michael@0: int32_t pos = aValueString.FindChar(':'); michael@0: if (pos < (int32_t)(sizeof kJavaScript - 1)) michael@0: return false; michael@0: nsAutoString scheme(Substring(aValueString, 0, pos)); michael@0: scheme.StripWhitespace(); michael@0: if ((scheme.Length() == (sizeof kJavaScript - 1)) && michael@0: scheme.EqualsIgnoreCase(kJavaScript)) michael@0: return true; michael@0: else michael@0: return false; michael@0: } michael@0: michael@0: return aContent->IsEventAttributeName(aAttrNameAtom); michael@0: } michael@0: michael@0: michael@0: void michael@0: nsXMLContentSerializer::SerializeAttributes(nsIContent* aContent, michael@0: nsIContent *aOriginalElement, michael@0: nsAString& aTagPrefix, michael@0: const nsAString& aTagNamespaceURI, michael@0: nsIAtom* aTagName, michael@0: nsAString& aStr, michael@0: uint32_t aSkipAttr, michael@0: bool aAddNSAttr) michael@0: { michael@0: michael@0: nsAutoString prefixStr, uriStr, valueStr; michael@0: nsAutoString xmlnsStr; michael@0: xmlnsStr.AssignLiteral(kXMLNS); michael@0: uint32_t index, count; michael@0: michael@0: // If we had to add a new namespace declaration, serialize michael@0: // and push it on the namespace stack michael@0: if (aAddNSAttr) { michael@0: if (aTagPrefix.IsEmpty()) { michael@0: // Serialize default namespace decl michael@0: SerializeAttr(EmptyString(), xmlnsStr, aTagNamespaceURI, aStr, true); michael@0: } michael@0: else { michael@0: // Serialize namespace decl michael@0: SerializeAttr(xmlnsStr, aTagPrefix, aTagNamespaceURI, aStr, true); michael@0: } michael@0: PushNameSpaceDecl(aTagPrefix, aTagNamespaceURI, aOriginalElement); michael@0: } michael@0: michael@0: count = aContent->GetAttrCount(); michael@0: michael@0: // Now serialize each of the attributes michael@0: // XXX Unfortunately we need a namespace manager to get michael@0: // attribute URIs. michael@0: for (index = 0; index < count; index++) { michael@0: if (aSkipAttr == index) { michael@0: continue; michael@0: } michael@0: michael@0: const nsAttrName* name = aContent->GetAttrNameAt(index); michael@0: int32_t namespaceID = name->NamespaceID(); michael@0: nsIAtom* attrName = name->LocalName(); michael@0: nsIAtom* attrPrefix = name->GetPrefix(); michael@0: michael@0: // Filter out any attribute starting with [-|_]moz michael@0: nsDependentAtomString attrNameStr(attrName); michael@0: if (StringBeginsWith(attrNameStr, NS_LITERAL_STRING("_moz")) || michael@0: StringBeginsWith(attrNameStr, NS_LITERAL_STRING("-moz"))) { michael@0: continue; michael@0: } michael@0: michael@0: if (attrPrefix) { michael@0: attrPrefix->ToString(prefixStr); michael@0: } michael@0: else { michael@0: prefixStr.Truncate(); michael@0: } michael@0: michael@0: bool addNSAttr = false; michael@0: if (kNameSpaceID_XMLNS != namespaceID) { michael@0: nsContentUtils::NameSpaceManager()->GetNameSpaceURI(namespaceID, uriStr); michael@0: addNSAttr = ConfirmPrefix(prefixStr, uriStr, aOriginalElement, true); michael@0: } michael@0: michael@0: aContent->GetAttr(namespaceID, attrName, valueStr); michael@0: michael@0: nsDependentAtomString nameStr(attrName); michael@0: bool isJS = IsJavaScript(aContent, attrName, namespaceID, valueStr); michael@0: michael@0: SerializeAttr(prefixStr, nameStr, valueStr, aStr, !isJS); michael@0: michael@0: if (addNSAttr) { michael@0: NS_ASSERTION(!prefixStr.IsEmpty(), michael@0: "Namespaced attributes must have a prefix"); michael@0: SerializeAttr(xmlnsStr, prefixStr, uriStr, aStr, true); michael@0: PushNameSpaceDecl(prefixStr, uriStr, aOriginalElement); michael@0: } michael@0: } michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsXMLContentSerializer::AppendElementStart(Element* aElement, michael@0: Element* aOriginalElement, michael@0: nsAString& aStr) michael@0: { michael@0: NS_ENSURE_ARG(aElement); michael@0: michael@0: nsIContent* content = aElement; michael@0: michael@0: bool forceFormat = false; michael@0: if (!CheckElementStart(content, forceFormat, aStr)) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsAutoString tagPrefix, tagLocalName, tagNamespaceURI; michael@0: aElement->NodeInfo()->GetPrefix(tagPrefix); michael@0: aElement->NodeInfo()->GetName(tagLocalName); michael@0: aElement->NodeInfo()->GetNamespaceURI(tagNamespaceURI); michael@0: michael@0: uint32_t skipAttr = ScanNamespaceDeclarations(content, michael@0: aOriginalElement, tagNamespaceURI); michael@0: michael@0: nsIAtom *name = content->Tag(); michael@0: bool lineBreakBeforeOpen = LineBreakBeforeOpen(content->GetNameSpaceID(), name); michael@0: michael@0: if ((mDoFormat || forceFormat) && !mPreLevel && !mDoRaw) { michael@0: if (mColPos && lineBreakBeforeOpen) { michael@0: AppendNewLineToString(aStr); michael@0: } michael@0: else { michael@0: MaybeAddNewlineForRootNode(aStr); michael@0: } michael@0: if (!mColPos) { michael@0: AppendIndentation(aStr); michael@0: } michael@0: else if (mAddSpace) { michael@0: AppendToString(char16_t(' '), aStr); michael@0: mAddSpace = false; michael@0: } michael@0: } michael@0: else if (mAddSpace) { michael@0: AppendToString(char16_t(' '), aStr); michael@0: mAddSpace = false; michael@0: } michael@0: else { michael@0: MaybeAddNewlineForRootNode(aStr); michael@0: } michael@0: michael@0: // Always reset to avoid false newlines in case MaybeAddNewlineForRootNode wasn't michael@0: // called michael@0: mAddNewlineForRootNode = false; michael@0: michael@0: bool addNSAttr; michael@0: addNSAttr = ConfirmPrefix(tagPrefix, tagNamespaceURI, aOriginalElement, michael@0: false); michael@0: michael@0: // Serialize the qualified name of the element michael@0: AppendToString(kLessThan, aStr); michael@0: if (!tagPrefix.IsEmpty()) { michael@0: AppendToString(tagPrefix, aStr); michael@0: AppendToString(NS_LITERAL_STRING(":"), aStr); michael@0: } michael@0: AppendToString(tagLocalName, aStr); michael@0: michael@0: MaybeEnterInPreContent(content); michael@0: michael@0: if ((mDoFormat || forceFormat) && !mPreLevel && !mDoRaw) { michael@0: IncrIndentation(name); michael@0: } michael@0: michael@0: SerializeAttributes(content, aOriginalElement, tagPrefix, tagNamespaceURI, michael@0: name, aStr, skipAttr, addNSAttr); michael@0: michael@0: AppendEndOfElementStart(aOriginalElement, name, content->GetNameSpaceID(), michael@0: aStr); michael@0: michael@0: if ((mDoFormat || forceFormat) && !mPreLevel michael@0: && !mDoRaw && LineBreakAfterOpen(content->GetNameSpaceID(), name)) { michael@0: AppendNewLineToString(aStr); michael@0: } michael@0: michael@0: AfterElementStart(content, aOriginalElement, aStr); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsXMLContentSerializer::AppendEndOfElementStart(nsIContent *aOriginalElement, michael@0: nsIAtom * aName, michael@0: int32_t aNamespaceID, michael@0: nsAString& aStr) michael@0: { michael@0: // We don't output a separate end tag for empty elements michael@0: if (!aOriginalElement->GetChildCount()) { michael@0: AppendToString(NS_LITERAL_STRING("/>"), aStr); michael@0: } michael@0: else { michael@0: AppendToString(kGreaterThan, aStr); michael@0: } michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsXMLContentSerializer::AppendElementEnd(Element* aElement, michael@0: nsAString& aStr) michael@0: { michael@0: NS_ENSURE_ARG(aElement); michael@0: michael@0: nsIContent* content = aElement; michael@0: michael@0: bool forceFormat = false, outputElementEnd; michael@0: outputElementEnd = CheckElementEnd(content, forceFormat, aStr); michael@0: michael@0: nsIAtom *name = content->Tag(); michael@0: michael@0: if ((mDoFormat || forceFormat) && !mPreLevel && !mDoRaw) { michael@0: DecrIndentation(name); michael@0: } michael@0: michael@0: if (!outputElementEnd) { michael@0: PopNameSpaceDeclsFor(aElement); michael@0: MaybeFlagNewlineForRootNode(aElement); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsAutoString tagPrefix, tagLocalName, tagNamespaceURI; michael@0: michael@0: aElement->NodeInfo()->GetPrefix(tagPrefix); michael@0: aElement->NodeInfo()->GetName(tagLocalName); michael@0: aElement->NodeInfo()->GetNamespaceURI(tagNamespaceURI); michael@0: michael@0: #ifdef DEBUG michael@0: bool debugNeedToPushNamespace = michael@0: #endif michael@0: ConfirmPrefix(tagPrefix, tagNamespaceURI, aElement, false); michael@0: NS_ASSERTION(!debugNeedToPushNamespace, "Can't push namespaces in closing tag!"); michael@0: michael@0: if ((mDoFormat || forceFormat) && !mPreLevel && !mDoRaw) { michael@0: michael@0: bool lineBreakBeforeClose = LineBreakBeforeClose(content->GetNameSpaceID(), name); michael@0: michael@0: if (mColPos && lineBreakBeforeClose) { michael@0: AppendNewLineToString(aStr); michael@0: } michael@0: if (!mColPos) { michael@0: AppendIndentation(aStr); michael@0: } michael@0: else if (mAddSpace) { michael@0: AppendToString(char16_t(' '), aStr); michael@0: mAddSpace = false; michael@0: } michael@0: } michael@0: else if (mAddSpace) { michael@0: AppendToString(char16_t(' '), aStr); michael@0: mAddSpace = false; michael@0: } michael@0: michael@0: AppendToString(kEndTag, aStr); michael@0: if (!tagPrefix.IsEmpty()) { michael@0: AppendToString(tagPrefix, aStr); michael@0: AppendToString(NS_LITERAL_STRING(":"), aStr); michael@0: } michael@0: AppendToString(tagLocalName, aStr); michael@0: AppendToString(kGreaterThan, aStr); michael@0: michael@0: PopNameSpaceDeclsFor(aElement); michael@0: michael@0: MaybeLeaveFromPreContent(content); michael@0: michael@0: if ((mDoFormat || forceFormat) && !mPreLevel michael@0: && !mDoRaw && LineBreakAfterClose(content->GetNameSpaceID(), name)) { michael@0: AppendNewLineToString(aStr); michael@0: } michael@0: else { michael@0: MaybeFlagNewlineForRootNode(aElement); michael@0: } michael@0: michael@0: AfterElementEnd(content, aStr); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsXMLContentSerializer::AppendDocumentStart(nsIDocument *aDocument, michael@0: nsAString& aStr) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aDocument); michael@0: michael@0: nsAutoString version, encoding, standalone; michael@0: aDocument->GetXMLDeclaration(version, encoding, standalone); michael@0: michael@0: if (version.IsEmpty()) michael@0: return NS_OK; // A declaration must have version, or there is no decl michael@0: michael@0: NS_NAMED_LITERAL_STRING(endQuote, "\""); michael@0: michael@0: aStr += NS_LITERAL_STRING(""); michael@0: mAddNewlineForRootNode = true; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool michael@0: nsXMLContentSerializer::CheckElementStart(nsIContent * aContent, michael@0: bool & aForceFormat, michael@0: nsAString& aStr) michael@0: { michael@0: aForceFormat = false; michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: nsXMLContentSerializer::CheckElementEnd(nsIContent * aContent, michael@0: bool & aForceFormat, michael@0: nsAString& aStr) michael@0: { michael@0: // We don't output a separate end tag for empty element michael@0: aForceFormat = false; michael@0: return aContent->GetChildCount() > 0; michael@0: } michael@0: michael@0: void michael@0: nsXMLContentSerializer::AppendToString(const char16_t aChar, michael@0: nsAString& aOutputStr) michael@0: { michael@0: if (mBodyOnly && !mInBody) { michael@0: return; michael@0: } michael@0: mColPos += 1; michael@0: aOutputStr.Append(aChar); michael@0: } michael@0: michael@0: void michael@0: nsXMLContentSerializer::AppendToString(const nsAString& aStr, michael@0: nsAString& aOutputStr) michael@0: { michael@0: if (mBodyOnly && !mInBody) { michael@0: return; michael@0: } michael@0: mColPos += aStr.Length(); michael@0: aOutputStr.Append(aStr); michael@0: } michael@0: michael@0: michael@0: static const uint16_t kGTVal = 62; michael@0: static const char* kEntities[] = { michael@0: "", "", "", "", "", "", "", "", "", "", michael@0: "", "", "", "", "", "", "", "", "", "", michael@0: "", "", "", "", "", "", "", "", "", "", michael@0: "", "", "", "", "", "", "", "", "&", "", michael@0: "", "", "", "", "", "", "", "", "", "", michael@0: "", "", "", "", "", "", "", "", "", "", michael@0: "<", "", ">" michael@0: }; michael@0: michael@0: static const char* kAttrEntities[] = { michael@0: "", "", "", "", "", "", "", "", "", "", michael@0: "", "", "", "", "", "", "", "", "", "", michael@0: "", "", "", "", "", "", "", "", "", "", michael@0: "", "", "", "", """, "", "", "", "&", "", michael@0: "", "", "", "", "", "", "", "", "", "", michael@0: "", "", "", "", "", "", "", "", "", "", michael@0: "<", "", ">" michael@0: }; michael@0: michael@0: void michael@0: nsXMLContentSerializer::AppendAndTranslateEntities(const nsAString& aStr, michael@0: nsAString& aOutputStr) michael@0: { michael@0: nsReadingIterator done_reading; michael@0: aStr.EndReading(done_reading); michael@0: michael@0: // for each chunk of |aString|... michael@0: uint32_t advanceLength = 0; michael@0: nsReadingIterator iter; michael@0: michael@0: const char **entityTable = mInAttribute ? kAttrEntities : kEntities; michael@0: michael@0: for (aStr.BeginReading(iter); michael@0: iter != done_reading; michael@0: iter.advance(int32_t(advanceLength))) { michael@0: uint32_t fragmentLength = iter.size_forward(); michael@0: const char16_t* c = iter.get(); michael@0: const char16_t* fragmentStart = c; michael@0: const char16_t* fragmentEnd = c + fragmentLength; michael@0: const char* entityText = nullptr; michael@0: michael@0: advanceLength = 0; michael@0: // for each character in this chunk, check if it michael@0: // needs to be replaced michael@0: for (; c < fragmentEnd; c++, advanceLength++) { michael@0: char16_t val = *c; michael@0: if ((val <= kGTVal) && (entityTable[val][0] != 0)) { michael@0: entityText = entityTable[val]; michael@0: break; michael@0: } michael@0: } michael@0: michael@0: aOutputStr.Append(fragmentStart, advanceLength); michael@0: if (entityText) { michael@0: AppendASCIItoUTF16(entityText, aOutputStr); michael@0: advanceLength++; michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsXMLContentSerializer::MaybeAddNewlineForRootNode(nsAString& aStr) michael@0: { michael@0: if (mAddNewlineForRootNode) { michael@0: AppendNewLineToString(aStr); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsXMLContentSerializer::MaybeFlagNewlineForRootNode(nsINode* aNode) michael@0: { michael@0: nsINode* parent = aNode->GetParentNode(); michael@0: if (parent) { michael@0: mAddNewlineForRootNode = parent->IsNodeOfType(nsINode::eDOCUMENT); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsXMLContentSerializer::MaybeEnterInPreContent(nsIContent* aNode) michael@0: { michael@0: // support of the xml:space attribute michael@0: if (aNode->HasAttr(kNameSpaceID_XML, nsGkAtoms::space)) { michael@0: nsAutoString space; michael@0: aNode->GetAttr(kNameSpaceID_XML, nsGkAtoms::space, space); michael@0: if (space.EqualsLiteral("preserve")) michael@0: ++mPreLevel; michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsXMLContentSerializer::MaybeLeaveFromPreContent(nsIContent* aNode) michael@0: { michael@0: // support of the xml:space attribute michael@0: if (aNode->HasAttr(kNameSpaceID_XML, nsGkAtoms::space)) { michael@0: nsAutoString space; michael@0: aNode->GetAttr(kNameSpaceID_XML, nsGkAtoms::space, space); michael@0: if (space.EqualsLiteral("preserve")) michael@0: --mPreLevel; michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsXMLContentSerializer::AppendNewLineToString(nsAString& aStr) michael@0: { michael@0: AppendToString(mLineBreak, aStr); michael@0: mMayIgnoreLineBreakSequence = true; michael@0: mColPos = 0; michael@0: mAddSpace = false; michael@0: mIsIndentationAddedOnCurrentLine = false; michael@0: } michael@0: michael@0: void michael@0: nsXMLContentSerializer::AppendIndentation(nsAString& aStr) michael@0: { michael@0: mIsIndentationAddedOnCurrentLine = true; michael@0: AppendToString(mIndent, aStr); michael@0: mAddSpace = false; michael@0: mMayIgnoreLineBreakSequence = false; michael@0: } michael@0: michael@0: void michael@0: nsXMLContentSerializer::IncrIndentation(nsIAtom* aName) michael@0: { michael@0: // we want to keep the source readable michael@0: if (mDoWrap && michael@0: mIndent.Length() >= uint32_t(mMaxColumn) - MIN_INDENTED_LINE_LENGTH) { michael@0: ++mIndentOverflow; michael@0: } michael@0: else { michael@0: mIndent.AppendLiteral(INDENT_STRING); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsXMLContentSerializer::DecrIndentation(nsIAtom* aName) michael@0: { michael@0: if(mIndentOverflow) michael@0: --mIndentOverflow; michael@0: else michael@0: mIndent.Cut(0, INDENT_STRING_LENGTH); michael@0: } michael@0: michael@0: bool michael@0: nsXMLContentSerializer::LineBreakBeforeOpen(int32_t aNamespaceID, nsIAtom* aName) michael@0: { michael@0: return mAddSpace; michael@0: } michael@0: michael@0: bool michael@0: nsXMLContentSerializer::LineBreakAfterOpen(int32_t aNamespaceID, nsIAtom* aName) michael@0: { michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: nsXMLContentSerializer::LineBreakBeforeClose(int32_t aNamespaceID, nsIAtom* aName) michael@0: { michael@0: return mAddSpace; michael@0: } michael@0: michael@0: bool michael@0: nsXMLContentSerializer::LineBreakAfterClose(int32_t aNamespaceID, nsIAtom* aName) michael@0: { michael@0: return false; michael@0: } michael@0: michael@0: void michael@0: nsXMLContentSerializer::AppendToStringConvertLF(const nsAString& aStr, michael@0: nsAString& aOutputStr) michael@0: { michael@0: if (mBodyOnly && !mInBody) { michael@0: return; michael@0: } michael@0: michael@0: if (mDoRaw) { michael@0: AppendToString(aStr, aOutputStr); michael@0: } michael@0: else { michael@0: // Convert line-endings to mLineBreak michael@0: uint32_t start = 0; michael@0: uint32_t theLen = aStr.Length(); michael@0: while (start < theLen) { michael@0: int32_t eol = aStr.FindChar('\n', start); michael@0: if (eol == kNotFound) { michael@0: nsDependentSubstring dataSubstring(aStr, start, theLen - start); michael@0: AppendToString(dataSubstring, aOutputStr); michael@0: start = theLen; michael@0: // if there was a line break before this substring michael@0: // AppendNewLineToString was called, so we should reverse michael@0: // this flag michael@0: mMayIgnoreLineBreakSequence = false; michael@0: } michael@0: else { michael@0: nsDependentSubstring dataSubstring(aStr, start, eol - start); michael@0: AppendToString(dataSubstring, aOutputStr); michael@0: AppendNewLineToString(aOutputStr); michael@0: start = eol + 1; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsXMLContentSerializer::AppendFormatedWrapped_WhitespaceSequence( michael@0: nsASingleFragmentString::const_char_iterator &aPos, michael@0: const nsASingleFragmentString::const_char_iterator aEnd, michael@0: const nsASingleFragmentString::const_char_iterator aSequenceStart, michael@0: bool &aMayIgnoreStartOfLineWhitespaceSequence, michael@0: nsAString &aOutputStr) michael@0: { michael@0: // Handle the complete sequence of whitespace. michael@0: // Continue to iterate until we find the first non-whitespace char. michael@0: // Updates "aPos" to point to the first unhandled char. michael@0: // Also updates the aMayIgnoreStartOfLineWhitespaceSequence flag, michael@0: // as well as the other "global" state flags. michael@0: michael@0: bool sawBlankOrTab = false; michael@0: bool leaveLoop = false; michael@0: michael@0: do { michael@0: switch (*aPos) { michael@0: case ' ': michael@0: case '\t': michael@0: sawBlankOrTab = true; michael@0: // no break michael@0: case '\n': michael@0: ++aPos; michael@0: // do not increase mColPos, michael@0: // because we will reduce the whitespace to a single char michael@0: break; michael@0: default: michael@0: leaveLoop = true; michael@0: break; michael@0: } michael@0: } while (!leaveLoop && aPos < aEnd); michael@0: michael@0: if (mAddSpace) { michael@0: // if we had previously been asked to add space, michael@0: // our situation has not changed michael@0: } michael@0: else if (!sawBlankOrTab && mMayIgnoreLineBreakSequence) { michael@0: // nothing to do in the case where line breaks have already been added michael@0: // before the call of AppendToStringWrapped michael@0: // and only if we found line break in the sequence michael@0: mMayIgnoreLineBreakSequence = false; michael@0: } michael@0: else if (aMayIgnoreStartOfLineWhitespaceSequence) { michael@0: // nothing to do michael@0: aMayIgnoreStartOfLineWhitespaceSequence = false; michael@0: } michael@0: else { michael@0: if (sawBlankOrTab) { michael@0: if (mDoWrap && mColPos + 1 >= mMaxColumn) { michael@0: // no much sense in delaying, we only have one slot left, michael@0: // let's write a break now michael@0: aOutputStr.Append(mLineBreak); michael@0: mColPos = 0; michael@0: mIsIndentationAddedOnCurrentLine = false; michael@0: mMayIgnoreLineBreakSequence = true; michael@0: } michael@0: else { michael@0: // do not write out yet, we may write out either a space or a linebreak michael@0: // let's delay writing it out until we know more michael@0: mAddSpace = true; michael@0: ++mColPos; // eat a slot of available space michael@0: } michael@0: } michael@0: else { michael@0: // Asian text usually does not contain spaces, therefore we should not michael@0: // transform a linebreak into a space. michael@0: // Since we only saw linebreaks, but no spaces or tabs, michael@0: // let's write a linebreak now. michael@0: AppendNewLineToString(aOutputStr); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsXMLContentSerializer::AppendWrapped_NonWhitespaceSequence( michael@0: nsASingleFragmentString::const_char_iterator &aPos, michael@0: const nsASingleFragmentString::const_char_iterator aEnd, michael@0: const nsASingleFragmentString::const_char_iterator aSequenceStart, michael@0: bool &aMayIgnoreStartOfLineWhitespaceSequence, michael@0: bool &aSequenceStartAfterAWhiteSpace, michael@0: nsAString& aOutputStr) michael@0: { michael@0: mMayIgnoreLineBreakSequence = false; michael@0: aMayIgnoreStartOfLineWhitespaceSequence = false; michael@0: michael@0: // Handle the complete sequence of non-whitespace in this block michael@0: // Iterate until we find the first whitespace char or an aEnd condition michael@0: // Updates "aPos" to point to the first unhandled char. michael@0: // Also updates the aMayIgnoreStartOfLineWhitespaceSequence flag, michael@0: // as well as the other "global" state flags. michael@0: michael@0: bool thisSequenceStartsAtBeginningOfLine = !mColPos; michael@0: bool onceAgainBecauseWeAddedBreakInFront = false; michael@0: bool foundWhitespaceInLoop; michael@0: uint32_t length, colPos; michael@0: michael@0: do { michael@0: michael@0: if (mColPos) { michael@0: colPos = mColPos; michael@0: } michael@0: else { michael@0: if (mDoFormat && !mPreLevel && !onceAgainBecauseWeAddedBreakInFront) { michael@0: colPos = mIndent.Length(); michael@0: } michael@0: else michael@0: colPos = 0; michael@0: } michael@0: foundWhitespaceInLoop = false; michael@0: length = 0; michael@0: // we iterate until the next whitespace character michael@0: // or until we reach the maximum of character per line michael@0: // or until the end of the string to add. michael@0: do { michael@0: if (*aPos == ' ' || *aPos == '\t' || *aPos == '\n') { michael@0: foundWhitespaceInLoop = true; michael@0: break; michael@0: } michael@0: michael@0: ++aPos; michael@0: ++length; michael@0: } while ( (!mDoWrap || colPos + length < mMaxColumn) && aPos < aEnd); michael@0: michael@0: // in the case we don't reached the end of the string, but we reached the maxcolumn, michael@0: // we see if there is a whitespace after the maxcolumn michael@0: // if yes, then we can append directly the string instead of michael@0: // appending a new line etc. michael@0: if (*aPos == ' ' || *aPos == '\t' || *aPos == '\n') { michael@0: foundWhitespaceInLoop = true; michael@0: } michael@0: michael@0: if (aPos == aEnd || foundWhitespaceInLoop) { michael@0: // there is enough room for the complete block we found michael@0: if (mDoFormat && !mColPos) { michael@0: AppendIndentation(aOutputStr); michael@0: } michael@0: else if (mAddSpace) { michael@0: aOutputStr.Append(char16_t(' ')); michael@0: mAddSpace = false; michael@0: } michael@0: michael@0: mColPos += length; michael@0: aOutputStr.Append(aSequenceStart, aPos - aSequenceStart); michael@0: michael@0: // We have not yet reached the max column, we will continue to michael@0: // fill the current line in the next outer loop iteration michael@0: // (this one in AppendToStringWrapped) michael@0: // make sure we return in this outer loop michael@0: onceAgainBecauseWeAddedBreakInFront = false; michael@0: } michael@0: else { // we reach the max column michael@0: if (!thisSequenceStartsAtBeginningOfLine && michael@0: (mAddSpace || (!mDoFormat && aSequenceStartAfterAWhiteSpace))) { michael@0: // when !mDoFormat, mAddSpace is not used, mAddSpace is always false michael@0: // so, in the case where mDoWrap && !mDoFormat, if we want to enter in this condition... michael@0: michael@0: // We can avoid to wrap. We try to add the whole block michael@0: // in an empty new line michael@0: michael@0: AppendNewLineToString(aOutputStr); michael@0: aPos = aSequenceStart; michael@0: thisSequenceStartsAtBeginningOfLine = true; michael@0: onceAgainBecauseWeAddedBreakInFront = true; michael@0: } michael@0: else { michael@0: // we must wrap michael@0: onceAgainBecauseWeAddedBreakInFront = false; michael@0: bool foundWrapPosition = false; michael@0: int32_t wrapPosition; michael@0: michael@0: nsILineBreaker *lineBreaker = nsContentUtils::LineBreaker(); michael@0: michael@0: wrapPosition = lineBreaker->Prev(aSequenceStart, michael@0: (aEnd - aSequenceStart), michael@0: (aPos - aSequenceStart) + 1); michael@0: if (wrapPosition != NS_LINEBREAKER_NEED_MORE_TEXT) { michael@0: foundWrapPosition = true; michael@0: } michael@0: else { michael@0: wrapPosition = lineBreaker->Next(aSequenceStart, michael@0: (aEnd - aSequenceStart), michael@0: (aPos - aSequenceStart)); michael@0: if (wrapPosition != NS_LINEBREAKER_NEED_MORE_TEXT) { michael@0: foundWrapPosition = true; michael@0: } michael@0: } michael@0: michael@0: if (foundWrapPosition) { michael@0: if (!mColPos && mDoFormat) { michael@0: AppendIndentation(aOutputStr); michael@0: } michael@0: else if (mAddSpace) { michael@0: aOutputStr.Append(char16_t(' ')); michael@0: mAddSpace = false; michael@0: } michael@0: aOutputStr.Append(aSequenceStart, wrapPosition); michael@0: michael@0: AppendNewLineToString(aOutputStr); michael@0: aPos = aSequenceStart + wrapPosition; michael@0: aMayIgnoreStartOfLineWhitespaceSequence = true; michael@0: } michael@0: else { michael@0: // try some simple fallback logic michael@0: // go forward up to the next whitespace position, michael@0: // in the worst case this will be all the rest of the data michael@0: michael@0: // we update the mColPos variable with the length of michael@0: // the part already parsed. michael@0: mColPos += length; michael@0: michael@0: // now try to find the next whitespace michael@0: do { michael@0: if (*aPos == ' ' || *aPos == '\t' || *aPos == '\n') { michael@0: break; michael@0: } michael@0: michael@0: ++aPos; michael@0: ++mColPos; michael@0: } while (aPos < aEnd); michael@0: michael@0: if (mAddSpace) { michael@0: aOutputStr.Append(char16_t(' ')); michael@0: mAddSpace = false; michael@0: } michael@0: aOutputStr.Append(aSequenceStart, aPos - aSequenceStart); michael@0: } michael@0: } michael@0: aSequenceStartAfterAWhiteSpace = false; michael@0: } michael@0: } while (onceAgainBecauseWeAddedBreakInFront); michael@0: } michael@0: michael@0: void michael@0: nsXMLContentSerializer::AppendToStringFormatedWrapped(const nsASingleFragmentString& aStr, michael@0: nsAString& aOutputStr) michael@0: { michael@0: if (mBodyOnly && !mInBody) { michael@0: return; michael@0: } michael@0: michael@0: nsASingleFragmentString::const_char_iterator pos, end, sequenceStart; michael@0: michael@0: aStr.BeginReading(pos); michael@0: aStr.EndReading(end); michael@0: michael@0: bool sequenceStartAfterAWhitespace = false; michael@0: if (pos < end) { michael@0: nsAString::const_char_iterator end2; michael@0: aOutputStr.EndReading(end2); michael@0: --end2; michael@0: if (*end2 == ' ' || *end2 == '\n' || *end2 == '\t') { michael@0: sequenceStartAfterAWhitespace = true; michael@0: } michael@0: } michael@0: michael@0: // if the current line already has text on it, such as a tag, michael@0: // leading whitespace is significant michael@0: bool mayIgnoreStartOfLineWhitespaceSequence = michael@0: (!mColPos || (mIsIndentationAddedOnCurrentLine && michael@0: sequenceStartAfterAWhitespace && michael@0: uint32_t(mColPos) == mIndent.Length())); michael@0: michael@0: while (pos < end) { michael@0: sequenceStart = pos; michael@0: michael@0: // if beginning of a whitespace sequence michael@0: if (*pos == ' ' || *pos == '\n' || *pos == '\t') { michael@0: AppendFormatedWrapped_WhitespaceSequence(pos, end, sequenceStart, michael@0: mayIgnoreStartOfLineWhitespaceSequence, aOutputStr); michael@0: } michael@0: else { // any other non-whitespace char michael@0: AppendWrapped_NonWhitespaceSequence(pos, end, sequenceStart, michael@0: mayIgnoreStartOfLineWhitespaceSequence, sequenceStartAfterAWhitespace, aOutputStr); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsXMLContentSerializer::AppendWrapped_WhitespaceSequence( michael@0: nsASingleFragmentString::const_char_iterator &aPos, michael@0: const nsASingleFragmentString::const_char_iterator aEnd, michael@0: const nsASingleFragmentString::const_char_iterator aSequenceStart, michael@0: nsAString &aOutputStr) michael@0: { michael@0: // Handle the complete sequence of whitespace. michael@0: // Continue to iterate until we find the first non-whitespace char. michael@0: // Updates "aPos" to point to the first unhandled char. michael@0: mAddSpace = false; michael@0: mIsIndentationAddedOnCurrentLine = false; michael@0: michael@0: bool leaveLoop = false; michael@0: nsASingleFragmentString::const_char_iterator lastPos = aPos; michael@0: michael@0: do { michael@0: switch (*aPos) { michael@0: case ' ': michael@0: case '\t': michael@0: // if there are too many spaces on a line, we wrap michael@0: if (mColPos >= mMaxColumn) { michael@0: if (lastPos != aPos) { michael@0: aOutputStr.Append(lastPos, aPos - lastPos); michael@0: } michael@0: AppendToString(mLineBreak, aOutputStr); michael@0: mColPos = 0; michael@0: lastPos = aPos; michael@0: } michael@0: michael@0: ++mColPos; michael@0: ++aPos; michael@0: break; michael@0: case '\n': michael@0: if (lastPos != aPos) { michael@0: aOutputStr.Append(lastPos, aPos - lastPos); michael@0: } michael@0: AppendToString(mLineBreak, aOutputStr); michael@0: mColPos = 0; michael@0: ++aPos; michael@0: lastPos = aPos; michael@0: break; michael@0: default: michael@0: leaveLoop = true; michael@0: break; michael@0: } michael@0: } while (!leaveLoop && aPos < aEnd); michael@0: michael@0: if (lastPos != aPos) { michael@0: aOutputStr.Append(lastPos, aPos - lastPos); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsXMLContentSerializer::AppendToStringWrapped(const nsASingleFragmentString& aStr, michael@0: nsAString& aOutputStr) michael@0: { michael@0: if (mBodyOnly && !mInBody) { michael@0: return; michael@0: } michael@0: michael@0: nsASingleFragmentString::const_char_iterator pos, end, sequenceStart; michael@0: michael@0: aStr.BeginReading(pos); michael@0: aStr.EndReading(end); michael@0: michael@0: // not used in this case, but needed by AppendWrapped_NonWhitespaceSequence michael@0: bool mayIgnoreStartOfLineWhitespaceSequence = false; michael@0: mMayIgnoreLineBreakSequence = false; michael@0: michael@0: bool sequenceStartAfterAWhitespace = false; michael@0: if (pos < end && !aOutputStr.IsEmpty()) { michael@0: nsAString::const_char_iterator end2; michael@0: aOutputStr.EndReading(end2); michael@0: --end2; michael@0: if (*end2 == ' ' || *end2 == '\n' || *end2 == '\t') { michael@0: sequenceStartAfterAWhitespace = true; michael@0: } michael@0: } michael@0: michael@0: while (pos < end) { michael@0: sequenceStart = pos; michael@0: michael@0: // if beginning of a whitespace sequence michael@0: if (*pos == ' ' || *pos == '\n' || *pos == '\t') { michael@0: sequenceStartAfterAWhitespace = true; michael@0: AppendWrapped_WhitespaceSequence(pos, end, sequenceStart, aOutputStr); michael@0: } michael@0: else { // any other non-whitespace char michael@0: AppendWrapped_NonWhitespaceSequence(pos, end, sequenceStart, michael@0: mayIgnoreStartOfLineWhitespaceSequence, sequenceStartAfterAWhitespace, aOutputStr); michael@0: } michael@0: } michael@0: }