diff options
Diffstat (limited to 'dom/base/nsXHTMLContentSerializer.cpp')
-rw-r--r-- | dom/base/nsXHTMLContentSerializer.cpp | 947 |
1 files changed, 947 insertions, 0 deletions
diff --git a/dom/base/nsXHTMLContentSerializer.cpp b/dom/base/nsXHTMLContentSerializer.cpp new file mode 100644 index 000000000..0dc31d7ae --- /dev/null +++ b/dom/base/nsXHTMLContentSerializer.cpp @@ -0,0 +1,947 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * nsIContentSerializer implementation that can be used with an + * nsIDocumentEncoder to convert an XHTML (not HTML!) DOM to an XHTML + * string that could be parsed into more or less the original DOM. + */ + +#include "nsXHTMLContentSerializer.h" + +#include "nsIDOMElement.h" +#include "nsIContent.h" +#include "nsIDocument.h" +#include "nsNameSpaceManager.h" +#include "nsString.h" +#include "nsUnicharUtils.h" +#include "nsXPIDLString.h" +#include "nsIServiceManager.h" +#include "nsIDocumentEncoder.h" +#include "nsGkAtoms.h" +#include "nsIURI.h" +#include "nsNetUtil.h" +#include "nsEscape.h" +#include "nsITextToSubURI.h" +#include "nsCRT.h" +#include "nsIParserService.h" +#include "nsContentUtils.h" +#include "nsLWBrkCIID.h" +#include "nsIScriptElement.h" +#include "nsStubMutationObserver.h" +#include "nsAttrName.h" +#include "nsParserConstants.h" +#include "nsComputedDOMStyle.h" +#include "mozilla/dom/Element.h" + +static const int32_t kLongLineLen = 128; + +#define kXMLNS "xmlns" + +nsresult +NS_NewXHTMLContentSerializer(nsIContentSerializer** aSerializer) +{ + RefPtr<nsXHTMLContentSerializer> it = new nsXHTMLContentSerializer(); + it.forget(aSerializer); + return NS_OK; +} + +nsXHTMLContentSerializer::nsXHTMLContentSerializer() + : mIsHTMLSerializer(false) +{ +} + +nsXHTMLContentSerializer::~nsXHTMLContentSerializer() +{ + NS_ASSERTION(mOLStateStack.IsEmpty(), "Expected OL State stack to be empty"); +} + +NS_IMETHODIMP +nsXHTMLContentSerializer::Init(uint32_t aFlags, uint32_t aWrapColumn, + const char* aCharSet, bool aIsCopying, + bool aRewriteEncodingDeclaration) +{ + // The previous version of the HTML serializer did implicit wrapping + // when there is no flags, so we keep wrapping in order to keep + // compatibility with the existing calling code + // XXXLJ perhaps should we remove this default settings later ? + if (aFlags & nsIDocumentEncoder::OutputFormatted ) { + aFlags = aFlags | nsIDocumentEncoder::OutputWrap; + } + + nsresult rv; + rv = nsXMLContentSerializer::Init(aFlags, aWrapColumn, aCharSet, aIsCopying, aRewriteEncodingDeclaration); + NS_ENSURE_SUCCESS(rv, rv); + + mRewriteEncodingDeclaration = aRewriteEncodingDeclaration; + mIsCopying = aIsCopying; + mIsFirstChildOfOL = false; + mInBody = 0; + mDisableEntityEncoding = 0; + mBodyOnly = (mFlags & nsIDocumentEncoder::OutputBodyOnly) ? true + : false; + + // set up entity converter if we are going to need it + if (mFlags & nsIDocumentEncoder::OutputEncodeW3CEntities) { + mEntityConverter = do_CreateInstance(NS_ENTITYCONVERTER_CONTRACTID); + } + return NS_OK; +} + + +// 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. +bool +nsXHTMLContentSerializer::HasLongLines(const nsString& text, int32_t& aLastNewlineOffset) +{ + uint32_t start=0; + uint32_t theLen = text.Length(); + bool rv = false; + aLastNewlineOffset = kNotFound; + for (start = 0; start < theLen; ) { + int32_t eol = text.FindChar('\n', start); + if (eol < 0) { + eol = text.Length(); + } + else { + aLastNewlineOffset = eol; + } + if (int32_t(eol - start) > kLongLineLen) + rv = true; + start = eol + 1; + } + return rv; +} + +NS_IMETHODIMP +nsXHTMLContentSerializer::AppendText(nsIContent* aText, + int32_t aStartOffset, + int32_t aEndOffset, + nsAString& aStr) +{ + NS_ENSURE_ARG(aText); + + nsAutoString data; + nsresult rv; + + rv = AppendTextData(aText, aStartOffset, aEndOffset, data, true); + if (NS_FAILED(rv)) + return NS_ERROR_FAILURE; + + if (mDoRaw || PreLevel() > 0) { + NS_ENSURE_TRUE(AppendToStringConvertLF(data, aStr), NS_ERROR_OUT_OF_MEMORY); + } + else if (mDoFormat) { + NS_ENSURE_TRUE(AppendToStringFormatedWrapped(data, aStr), NS_ERROR_OUT_OF_MEMORY); + } + else if (mDoWrap) { + NS_ENSURE_TRUE(AppendToStringWrapped(data, aStr), NS_ERROR_OUT_OF_MEMORY); + } + else { + int32_t lastNewlineOffset = kNotFound; + if (HasLongLines(data, lastNewlineOffset)) { + // We have long lines, rewrap + mDoWrap = true; + bool result = AppendToStringWrapped(data, aStr); + mDoWrap = false; + NS_ENSURE_TRUE(result, NS_ERROR_OUT_OF_MEMORY); + } + else { + NS_ENSURE_TRUE(AppendToStringConvertLF(data, aStr), NS_ERROR_OUT_OF_MEMORY); + } + } + + return NS_OK; +} + +nsresult +nsXHTMLContentSerializer::EscapeURI(nsIContent* aContent, const nsAString& aURI, nsAString& aEscapedURI) +{ + // URL escape %xx cannot be used in JS. + // No escaping if the scheme is 'javascript'. + if (IsJavaScript(aContent, nsGkAtoms::href, kNameSpaceID_None, aURI)) { + aEscapedURI = aURI; + return NS_OK; + } + + // nsITextToSubURI does charset convert plus uri escape + // This is needed to convert to a document charset which is needed to support existing browsers. + // But we eventually want to use UTF-8 instead of a document charset, then the code would be much simpler. + // See HTML 4.01 spec, "Appendix B.2.1 Non-ASCII characters in URI attribute values" + nsCOMPtr<nsITextToSubURI> textToSubURI; + nsAutoString uri(aURI); // in order to use FindCharInSet() + nsresult rv = NS_OK; + + if (!mCharset.IsEmpty() && !IsASCII(uri)) { + textToSubURI = do_GetService(NS_ITEXTTOSUBURI_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + } + + int32_t start = 0; + int32_t end; + nsAutoString part; + nsXPIDLCString escapedURI; + aEscapedURI.Truncate(0); + + // Loop and escape parts by avoiding escaping reserved characters + // (and '%', '#', as well as '[' and ']' for IPv6 address literals). + while ((end = uri.FindCharInSet("%#;/?:@&=+$,[]", start)) != -1) { + part = Substring(aURI, start, (end-start)); + if (textToSubURI && !IsASCII(part)) { + rv = textToSubURI->ConvertAndEscape(mCharset.get(), part.get(), getter_Copies(escapedURI)); + NS_ENSURE_SUCCESS(rv, rv); + } else if (NS_WARN_IF(!NS_Escape(NS_ConvertUTF16toUTF8(part), escapedURI, + url_Path))) { + return NS_ERROR_OUT_OF_MEMORY; + } + AppendASCIItoUTF16(escapedURI, aEscapedURI); + + // Append a reserved character without escaping. + part = Substring(aURI, end, 1); + aEscapedURI.Append(part); + start = end + 1; + } + + if (start < (int32_t) 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 if (NS_WARN_IF(!NS_Escape(NS_ConvertUTF16toUTF8(part), escapedURI, + url_Path))) { + return NS_ERROR_OUT_OF_MEMORY; + } + AppendASCIItoUTF16(escapedURI, aEscapedURI); + } + + return rv; +} + +bool +nsXHTMLContentSerializer::SerializeAttributes(nsIContent* aContent, + nsIContent *aOriginalElement, + nsAString& aTagPrefix, + const nsAString& aTagNamespaceURI, + nsIAtom* aTagName, + nsAString& aStr, + uint32_t aSkipAttr, + bool aAddNSAttr) +{ + nsresult rv; + uint32_t index, count; + nsAutoString prefixStr, uriStr, valueStr; + nsAutoString xmlnsStr; + xmlnsStr.AssignLiteral(kXMLNS); + + int32_t contentNamespaceID = aContent->GetNameSpaceID(); + + // this method is not called by nsHTMLContentSerializer + // so we don't have to check HTML element, just XHTML + + if (mIsCopying && kNameSpaceID_XHTML == contentNamespaceID) { + + // Need to keep track of OL and LI elements in order to get ordinal number + // for the LI. + if (aTagName == nsGkAtoms::ol) { + // We are copying and current node is an OL; + // Store its start attribute value in olState->startVal. + nsAutoString start; + int32_t startAttrVal = 0; + aContent->GetAttr(kNameSpaceID_None, nsGkAtoms::start, start); + if (!start.IsEmpty()) { + nsresult rv = NS_OK; + 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 (startAttrVal, true); + mOLStateStack.AppendElement(state); + } + else if (aTagName == nsGkAtoms::li) { + mIsFirstChildOfOL = IsFirstChildOfOL(aOriginalElement); + if (mIsFirstChildOfOL) { + // If OL is parent of this LI, serialize attributes in different manner. + NS_ENSURE_TRUE(SerializeLIValueAttribute(aContent, aStr), false); + } + } + } + + // If we had to add a new namespace declaration, serialize + // and push it on the namespace stack + if (aAddNSAttr) { + if (aTagPrefix.IsEmpty()) { + // Serialize default namespace decl + NS_ENSURE_TRUE(SerializeAttr(EmptyString(), xmlnsStr, + aTagNamespaceURI, + aStr, true), false); + } else { + // Serialize namespace decl + NS_ENSURE_TRUE(SerializeAttr(xmlnsStr, aTagPrefix, + aTagNamespaceURI, + aStr, true), false); + } + PushNameSpaceDecl(aTagPrefix, aTagNamespaceURI, aOriginalElement); + } + + NS_NAMED_LITERAL_STRING(_mozStr, "_moz"); + + count = aContent->GetAttrCount(); + + // Now serialize each of the attributes + // XXX Unfortunately we need a namespace manager to get + // attribute URIs. + for (index = 0; index < count; index++) { + + if (aSkipAttr == index) { + continue; + } + + BorrowedAttrInfo info = aContent->GetAttrInfoAt(index); + const nsAttrName* name = info.mName; + + int32_t namespaceID = name->NamespaceID(); + nsIAtom* attrName = name->LocalName(); + nsIAtom* attrPrefix = name->GetPrefix(); + + // Filter out any attribute starting with [-|_]moz + nsDependentAtomString attrNameStr(attrName); + if (StringBeginsWith(attrNameStr, NS_LITERAL_STRING("_moz")) || + StringBeginsWith(attrNameStr, NS_LITERAL_STRING("-moz"))) { + continue; + } + + if (attrPrefix) { + attrPrefix->ToString(prefixStr); + } + else { + prefixStr.Truncate(); + } + + bool addNSAttr = false; + if (kNameSpaceID_XMLNS != namespaceID) { + nsContentUtils::NameSpaceManager()->GetNameSpaceURI(namespaceID, uriStr); + addNSAttr = ConfirmPrefix(prefixStr, uriStr, aOriginalElement, true); + } + + info.mValue->ToString(valueStr); + + nsDependentAtomString nameStr(attrName); + bool isJS = false; + + if (kNameSpaceID_XHTML == contentNamespaceID) { + // + // Filter out special case of <br type="_moz"> or <br _moz*>, + // used by the editor. Bug 16988. Yuck. + // + if (namespaceID == kNameSpaceID_None && aTagName == nsGkAtoms::br && attrName == nsGkAtoms::type + && StringBeginsWith(valueStr, _mozStr)) { + continue; + } + + if (mIsCopying && mIsFirstChildOfOL && (aTagName == nsGkAtoms::li) + && (attrName == nsGkAtoms::value)) { + // This is handled separately in SerializeLIValueAttribute() + continue; + } + + isJS = IsJavaScript(aContent, attrName, namespaceID, valueStr); + + if (namespaceID == kNameSpaceID_None && + ((attrName == nsGkAtoms::href) || + (attrName == nsGkAtoms::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<nsIURI> uri = aContent->GetBaseURI(); + if (uri) { + nsAutoString absURI; + rv = NS_MakeAbsoluteURI(absURI, valueStr, uri); + if (NS_SUCCEEDED(rv)) { + valueStr = absURI; + } + } + } + // Need to escape URI. + nsAutoString tempURI(valueStr); + if (!isJS && NS_FAILED(EscapeURI(aContent, tempURI, valueStr))) + valueStr = tempURI; + } + + if (mRewriteEncodingDeclaration && aTagName == nsGkAtoms::meta && + attrName == nsGkAtoms::content) { + // If we're serializing a <meta http-equiv="content-type">, + // use the proper value, rather than what's in the document. + nsAutoString header; + aContent->GetAttr(kNameSpaceID_None, nsGkAtoms::httpEquiv, header); + if (header.LowerCaseEqualsLiteral("content-type")) { + valueStr = NS_LITERAL_STRING("text/html; charset=") + + NS_ConvertASCIItoUTF16(mCharset); + } + } + + // Expand shorthand attribute. + if (namespaceID == kNameSpaceID_None && IsShorthandAttr(attrName, aTagName) && valueStr.IsEmpty()) { + valueStr = nameStr; + } + } + else { + isJS = IsJavaScript(aContent, attrName, namespaceID, valueStr); + } + + NS_ENSURE_TRUE(SerializeAttr(prefixStr, nameStr, valueStr, aStr, !isJS), false); + + if (addNSAttr) { + NS_ASSERTION(!prefixStr.IsEmpty(), + "Namespaced attributes must have a prefix"); + NS_ENSURE_TRUE(SerializeAttr(xmlnsStr, prefixStr, uriStr, aStr, true), false); + PushNameSpaceDecl(prefixStr, uriStr, aOriginalElement); + } + } + + return true; +} + +bool +nsXHTMLContentSerializer::AfterElementStart(nsIContent* aContent, + nsIContent* aOriginalElement, + nsAString& aStr) +{ + if (mRewriteEncodingDeclaration && + aContent->IsHTMLElement(nsGkAtoms::head)) { + + // Check if there already are any content-type meta children. + // If there are, they will be modified to use the correct charset. + // If there aren't, we'll insert one here. + bool hasMeta = false; + for (nsIContent* child = aContent->GetFirstChild(); + child; + child = child->GetNextSibling()) { + if (child->IsHTMLElement(nsGkAtoms::meta) && + child->HasAttr(kNameSpaceID_None, nsGkAtoms::content)) { + nsAutoString header; + child->GetAttr(kNameSpaceID_None, nsGkAtoms::httpEquiv, header); + + if (header.LowerCaseEqualsLiteral("content-type")) { + hasMeta = true; + break; + } + } + } + + if (!hasMeta) { + NS_ENSURE_TRUE(AppendNewLineToString(aStr), false); + if (mDoFormat) { + NS_ENSURE_TRUE(AppendIndentation(aStr), false); + } + NS_ENSURE_TRUE(AppendToString(NS_LITERAL_STRING("<meta http-equiv=\"content-type\""), aStr), false); + NS_ENSURE_TRUE(AppendToString(NS_LITERAL_STRING(" content=\"text/html; charset="), aStr), false); + NS_ENSURE_TRUE(AppendToString(NS_ConvertASCIItoUTF16(mCharset), aStr), false); + if (mIsHTMLSerializer) { + NS_ENSURE_TRUE(AppendToString(NS_LITERAL_STRING("\">"), aStr), false); + } else { + NS_ENSURE_TRUE(AppendToString(NS_LITERAL_STRING("\" />"), aStr), false); + } + } + } + + return true; +} + +void +nsXHTMLContentSerializer::AfterElementEnd(nsIContent * aContent, + nsAString& aStr) +{ + NS_ASSERTION(!mIsHTMLSerializer, "nsHTMLContentSerializer shouldn't call this method !"); + + // this method is not called by nsHTMLContentSerializer + // so we don't have to check HTML element, just XHTML + if (aContent->IsHTMLElement(nsGkAtoms::body)) { + --mInBody; + } +} + + +NS_IMETHODIMP +nsXHTMLContentSerializer::AppendDocumentStart(nsIDocument *aDocument, + nsAString& aStr) +{ + if (!mBodyOnly) + return nsXMLContentSerializer::AppendDocumentStart(aDocument, aStr); + + return NS_OK; +} + +bool +nsXHTMLContentSerializer::CheckElementStart(nsIContent * aContent, + bool & aForceFormat, + nsAString& aStr, + nsresult& aResult) +{ + aResult = NS_OK; + + // 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 + aForceFormat = !(mFlags & nsIDocumentEncoder::OutputIgnoreMozDirty) && + aContent->HasAttr(kNameSpaceID_None, nsGkAtoms::mozdirty); + + if (aContent->IsHTMLElement(nsGkAtoms::br) && + (mFlags & nsIDocumentEncoder::OutputNoFormattingInPre) && + PreLevel() > 0) { + aResult = AppendNewLineToString(aStr) ? NS_OK : NS_ERROR_OUT_OF_MEMORY; + return false; + } + + if (aContent->IsHTMLElement(nsGkAtoms::body)) { + ++mInBody; + } + + return true; +} + +bool +nsXHTMLContentSerializer::CheckElementEnd(mozilla::dom::Element* aElement, + bool& aForceFormat, + nsAString& aStr) +{ + NS_ASSERTION(!mIsHTMLSerializer, "nsHTMLContentSerializer shouldn't call this method !"); + + aForceFormat = !(mFlags & nsIDocumentEncoder::OutputIgnoreMozDirty) && + aElement->HasAttr(kNameSpaceID_None, nsGkAtoms::mozdirty); + + if (mIsCopying && aElement->IsHTMLElement(nsGkAtoms::ol)) { + NS_ASSERTION((!mOLStateStack.IsEmpty()), "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.IsEmpty()) { + mOLStateStack.RemoveElementAt(mOLStateStack.Length() -1); + } + } + + bool dummyFormat; + return nsXMLContentSerializer::CheckElementEnd(aElement, dummyFormat, aStr); +} + +bool +nsXHTMLContentSerializer::AppendAndTranslateEntities(const nsAString& aStr, + nsAString& aOutputStr) +{ + if (mBodyOnly && !mInBody) { + return true; + } + + if (mDisableEntityEncoding) { + return aOutputStr.Append(aStr, mozilla::fallible); + } + + return nsXMLContentSerializer::AppendAndTranslateEntities(aStr, aOutputStr); +} + +bool +nsXHTMLContentSerializer::IsShorthandAttr(const nsIAtom* aAttrName, + const nsIAtom* aElementName) +{ + // checked + if ((aAttrName == nsGkAtoms::checked) && + (aElementName == nsGkAtoms::input)) { + return true; + } + + // compact + if ((aAttrName == nsGkAtoms::compact) && + (aElementName == nsGkAtoms::dir || + aElementName == nsGkAtoms::dl || + aElementName == nsGkAtoms::menu || + aElementName == nsGkAtoms::ol || + aElementName == nsGkAtoms::ul)) { + return true; + } + + // declare + if ((aAttrName == nsGkAtoms::declare) && + (aElementName == nsGkAtoms::object)) { + return true; + } + + // defer + if ((aAttrName == nsGkAtoms::defer) && + (aElementName == nsGkAtoms::script)) { + return true; + } + + // disabled + if ((aAttrName == nsGkAtoms::disabled) && + (aElementName == nsGkAtoms::button || + aElementName == nsGkAtoms::input || + aElementName == nsGkAtoms::optgroup || + aElementName == nsGkAtoms::option || + aElementName == nsGkAtoms::select || + aElementName == nsGkAtoms::textarea)) { + return true; + } + + // ismap + if ((aAttrName == nsGkAtoms::ismap) && + (aElementName == nsGkAtoms::img || + aElementName == nsGkAtoms::input)) { + return true; + } + + // multiple + if ((aAttrName == nsGkAtoms::multiple) && + (aElementName == nsGkAtoms::select)) { + return true; + } + + // noresize + if ((aAttrName == nsGkAtoms::noresize) && + (aElementName == nsGkAtoms::frame)) { + return true; + } + + // noshade + if ((aAttrName == nsGkAtoms::noshade) && + (aElementName == nsGkAtoms::hr)) { + return true; + } + + // nowrap + if ((aAttrName == nsGkAtoms::nowrap) && + (aElementName == nsGkAtoms::td || + aElementName == nsGkAtoms::th)) { + return true; + } + + // readonly + if ((aAttrName == nsGkAtoms::readonly) && + (aElementName == nsGkAtoms::input || + aElementName == nsGkAtoms::textarea)) { + return true; + } + + // selected + if ((aAttrName == nsGkAtoms::selected) && + (aElementName == nsGkAtoms::option)) { + return true; + } + + // autoplay and controls + if ((aElementName == nsGkAtoms::video || aElementName == nsGkAtoms::audio) && + (aAttrName == nsGkAtoms::autoplay || aAttrName == nsGkAtoms::muted || + aAttrName == nsGkAtoms::controls)) { + return true; + } + + return false; +} + +bool +nsXHTMLContentSerializer::LineBreakBeforeOpen(int32_t aNamespaceID, nsIAtom* aName) +{ + + if (aNamespaceID != kNameSpaceID_XHTML) { + return mAddSpace; + } + + if (aName == nsGkAtoms::title || + aName == nsGkAtoms::meta || + aName == nsGkAtoms::link || + aName == nsGkAtoms::style || + aName == nsGkAtoms::select || + aName == nsGkAtoms::option || + aName == nsGkAtoms::script || + aName == nsGkAtoms::html) { + return true; + } + else { + nsIParserService* parserService = nsContentUtils::GetParserService(); + + if (parserService) { + bool res; + parserService-> + IsBlock(parserService->HTMLCaseSensitiveAtomTagToId(aName), res); + return res; + } + } + + return mAddSpace; +} + +bool +nsXHTMLContentSerializer::LineBreakAfterOpen(int32_t aNamespaceID, nsIAtom* aName) +{ + + if (aNamespaceID != kNameSpaceID_XHTML) { + return false; + } + + if ((aName == nsGkAtoms::html) || + (aName == nsGkAtoms::head) || + (aName == nsGkAtoms::body) || + (aName == nsGkAtoms::ul) || + (aName == nsGkAtoms::ol) || + (aName == nsGkAtoms::dl) || + (aName == nsGkAtoms::table) || + (aName == nsGkAtoms::tbody) || + (aName == nsGkAtoms::tr) || + (aName == nsGkAtoms::br) || + (aName == nsGkAtoms::meta) || + (aName == nsGkAtoms::link) || + (aName == nsGkAtoms::script) || + (aName == nsGkAtoms::select) || + (aName == nsGkAtoms::map) || + (aName == nsGkAtoms::area) || + (aName == nsGkAtoms::style)) { + return true; + } + + return false; +} + +bool +nsXHTMLContentSerializer::LineBreakBeforeClose(int32_t aNamespaceID, nsIAtom* aName) +{ + + if (aNamespaceID != kNameSpaceID_XHTML) { + return false; + } + + if ((aName == nsGkAtoms::html) || + (aName == nsGkAtoms::head) || + (aName == nsGkAtoms::body) || + (aName == nsGkAtoms::ul) || + (aName == nsGkAtoms::ol) || + (aName == nsGkAtoms::dl) || + (aName == nsGkAtoms::select) || + (aName == nsGkAtoms::table) || + (aName == nsGkAtoms::tbody)) { + return true; + } + return false; +} + +bool +nsXHTMLContentSerializer::LineBreakAfterClose(int32_t aNamespaceID, nsIAtom* aName) +{ + + if (aNamespaceID != kNameSpaceID_XHTML) { + return false; + } + + if ((aName == nsGkAtoms::html) || + (aName == nsGkAtoms::head) || + (aName == nsGkAtoms::body) || + (aName == nsGkAtoms::tr) || + (aName == nsGkAtoms::th) || + (aName == nsGkAtoms::td) || + (aName == nsGkAtoms::pre) || + (aName == nsGkAtoms::title) || + (aName == nsGkAtoms::li) || + (aName == nsGkAtoms::dt) || + (aName == nsGkAtoms::dd) || + (aName == nsGkAtoms::blockquote) || + (aName == nsGkAtoms::select) || + (aName == nsGkAtoms::option) || + (aName == nsGkAtoms::p) || + (aName == nsGkAtoms::map) || + (aName == nsGkAtoms::div)) { + return true; + } + else { + nsIParserService* parserService = nsContentUtils::GetParserService(); + + if (parserService) { + bool res; + parserService-> + IsBlock(parserService->HTMLCaseSensitiveAtomTagToId(aName), res); + return res; + } + } + + return false; +} + + +void +nsXHTMLContentSerializer::MaybeEnterInPreContent(nsIContent* aNode) +{ + if (!ShouldMaintainPreLevel() || + !aNode->IsHTMLElement()) { + return; + } + + if (IsElementPreformatted(aNode) || + aNode->IsAnyOfHTMLElements(nsGkAtoms::script, + nsGkAtoms::style, + nsGkAtoms::noscript, + nsGkAtoms::noframes)) { + PreLevel()++; + } +} + +void +nsXHTMLContentSerializer::MaybeLeaveFromPreContent(nsIContent* aNode) +{ + if (!ShouldMaintainPreLevel() || + !aNode->IsHTMLElement()) { + return; + } + + if (IsElementPreformatted(aNode) || + aNode->IsAnyOfHTMLElements(nsGkAtoms::script, + nsGkAtoms::style, + nsGkAtoms::noscript, + nsGkAtoms::noframes)) { + --PreLevel(); + } +} + +bool +nsXHTMLContentSerializer::IsElementPreformatted(nsIContent* aNode) +{ + MOZ_ASSERT(ShouldMaintainPreLevel(), "We should not be calling this needlessly"); + + if (!aNode->IsElement()) { + return false; + } + RefPtr<nsStyleContext> styleContext = + nsComputedDOMStyle::GetStyleContextForElementNoFlush(aNode->AsElement(), + nullptr, nullptr); + if (styleContext) { + const nsStyleText* textStyle = styleContext->StyleText(); + return textStyle->WhiteSpaceOrNewlineIsSignificant(); + } + return false; +} + +bool +nsXHTMLContentSerializer::SerializeLIValueAttribute(nsIContent* 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. + bool found = false; + nsCOMPtr<nsIDOMNode> currNode = do_QueryInterface(aElement); + nsAutoString valueStr; + + olState state (0, false); + + if (!mOLStateStack.IsEmpty()) { + state = mOLStateStack[mOLStateStack.Length()-1]; + // isFirstListItem should be true only before the serialization of the + // first item in the list. + state.isFirstListItem = false; + mOLStateStack[mOLStateStack.Length()-1] = state; + } + + int32_t startVal = state.startVal; + int32_t offset = 0; + + // 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<nsIDOMElement> currElement = do_QueryInterface(currNode); + // currElement may be null if it were a text node. + if (currElement) { + nsAutoString tagName; + currElement->GetTagName(tagName); + if (tagName.LowerCaseEqualsLiteral("li")) { + currElement->GetAttribute(NS_LITERAL_STRING("value"), valueStr); + if (valueStr.IsEmpty()) + offset++; + else { + found = true; + nsresult rv = NS_OK; + startVal = valueStr.ToInteger(&rv); + } + } + } + nsCOMPtr<nsIDOMNode> tmp; + currNode->GetPreviousSibling(getter_AddRefs(tmp)); + currNode.swap(tmp); + } + // 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. + NS_ENSURE_TRUE(SerializeAttr(EmptyString(), NS_LITERAL_STRING("value"), + valueStr, aStr, false), 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); + NS_ENSURE_TRUE(SerializeAttr(EmptyString(), NS_LITERAL_STRING("value"), + valueStr, aStr, false), false); + } + + return true; +} + +bool +nsXHTMLContentSerializer::IsFirstChildOfOL(nsIContent* aElement) +{ + nsCOMPtr<nsIDOMNode> node = do_QueryInterface(aElement); + nsAutoString parentName; + + nsCOMPtr<nsIDOMNode> parentNode; + node->GetParentNode(getter_AddRefs(parentNode)); + if (parentNode) + parentNode->GetNodeName(parentName); + else + return false; + + if (parentName.LowerCaseEqualsLiteral("ol")) { + + if (!mOLStateStack.IsEmpty()) { + olState state = mOLStateStack[mOLStateStack.Length()-1]; + if (state.isFirstListItem) + return true; + } + + return false; + } + else + return false; +} + +bool +nsXHTMLContentSerializer::HasNoChildren(nsIContent * aContent) { + + for (nsIContent* child = aContent->GetFirstChild(); + child; + child = child->GetNextSibling()) { + + if (!child->IsNodeOfType(nsINode::eTEXT)) + return false; + + if (child->TextLength()) + return false; + } + + return true; +} |