nsXHTMLContentSerializer.cpp (24568B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 /* 8 * nsIContentSerializer implementation that can be used with an 9 * nsIDocumentEncoder to convert an XHTML (not HTML!) DOM to an XHTML 10 * string that could be parsed into more or less the original DOM. 11 */ 12 13 #include "nsXHTMLContentSerializer.h" 14 15 #include "mozilla/dom/Document.h" 16 #include "mozilla/dom/Element.h" 17 #include "nsAttrName.h" 18 #include "nsCRT.h" 19 #include "nsComputedDOMStyle.h" 20 #include "nsContentUtils.h" 21 #include "nsElementTable.h" 22 #include "nsEscape.h" 23 #include "nsGkAtoms.h" 24 #include "nsIContent.h" 25 #include "nsIDocumentEncoder.h" 26 #include "nsIScriptElement.h" 27 #include "nsIURI.h" 28 #include "nsNameSpaceManager.h" 29 #include "nsNetUtil.h" 30 #include "nsString.h" 31 #include "nsStubMutationObserver.h" 32 #include "nsUnicharUtils.h" 33 34 using namespace mozilla; 35 using namespace mozilla::dom; 36 37 static const int32_t kLongLineLen = 128; 38 39 #define kXMLNS "xmlns" 40 41 nsresult NS_NewXHTMLContentSerializer(nsIContentSerializer** aSerializer) { 42 RefPtr<nsXHTMLContentSerializer> it = new nsXHTMLContentSerializer(); 43 it.forget(aSerializer); 44 return NS_OK; 45 } 46 47 nsXHTMLContentSerializer::nsXHTMLContentSerializer() 48 : mIsHTMLSerializer(false), 49 mIsCopying(false), 50 mDisableEntityEncoding(0), 51 mRewriteEncodingDeclaration(false), 52 mIsFirstChildOfOL(false) {} 53 54 nsXHTMLContentSerializer::~nsXHTMLContentSerializer() { 55 NS_ASSERTION(mOLStateStack.IsEmpty(), "Expected OL State stack to be empty"); 56 } 57 58 NS_IMETHODIMP 59 nsXHTMLContentSerializer::Init(uint32_t aFlags, uint32_t aWrapColumn, 60 const Encoding* aEncoding, bool aIsCopying, 61 bool aRewriteEncodingDeclaration, 62 bool* aNeedsPreformatScanning, 63 nsAString& aOutput) { 64 // The previous version of the HTML serializer did implicit wrapping 65 // when there is no flags, so we keep wrapping in order to keep 66 // compatibility with the existing calling code 67 // XXXLJ perhaps should we remove this default settings later ? 68 if (aFlags & nsIDocumentEncoder::OutputFormatted) { 69 aFlags = aFlags | nsIDocumentEncoder::OutputWrap; 70 } 71 72 nsresult rv; 73 rv = nsXMLContentSerializer::Init(aFlags, aWrapColumn, aEncoding, aIsCopying, 74 aRewriteEncodingDeclaration, 75 aNeedsPreformatScanning, aOutput); 76 NS_ENSURE_SUCCESS(rv, rv); 77 78 mRewriteEncodingDeclaration = aRewriteEncodingDeclaration; 79 mIsCopying = aIsCopying; 80 mIsFirstChildOfOL = false; 81 mInBody = 0; 82 mDisableEntityEncoding = 0; 83 mBodyOnly = (mFlags & nsIDocumentEncoder::OutputBodyOnly); 84 85 return NS_OK; 86 } 87 88 // See if the string has any lines longer than longLineLen: 89 // if so, we presume formatting is wonky (e.g. the node has been edited) 90 // and we'd better rewrap the whole text node. 91 bool nsXHTMLContentSerializer::HasLongLines(const nsString& text, 92 int32_t& aLastNewlineOffset) { 93 uint32_t start = 0; 94 uint32_t theLen = text.Length(); 95 bool rv = false; 96 aLastNewlineOffset = kNotFound; 97 for (start = 0; start < theLen;) { 98 int32_t eol = text.FindChar('\n', start); 99 if (eol < 0) { 100 eol = text.Length(); 101 } else { 102 aLastNewlineOffset = eol; 103 } 104 if (int32_t(eol - start) > kLongLineLen) rv = true; 105 start = eol + 1; 106 } 107 return rv; 108 } 109 110 NS_IMETHODIMP 111 nsXHTMLContentSerializer::AppendText(Text* aText, int32_t aStartOffset, 112 int32_t aEndOffset) { 113 NS_ENSURE_ARG(aText); 114 NS_ENSURE_STATE(mOutput); 115 116 nsAutoString data; 117 nsresult rv; 118 119 rv = AppendTextData(aText, aStartOffset, aEndOffset, data, true); 120 if (NS_FAILED(rv)) return NS_ERROR_FAILURE; 121 122 if (mDoRaw || PreLevel() > 0) { 123 NS_ENSURE_TRUE(AppendToStringConvertLF(data, *mOutput), 124 NS_ERROR_OUT_OF_MEMORY); 125 } else if (mDoFormat) { 126 NS_ENSURE_TRUE(AppendToStringFormatedWrapped(data, *mOutput), 127 NS_ERROR_OUT_OF_MEMORY); 128 } else if (mDoWrap) { 129 NS_ENSURE_TRUE(AppendToStringWrapped(data, *mOutput), 130 NS_ERROR_OUT_OF_MEMORY); 131 } else { 132 int32_t lastNewlineOffset = kNotFound; 133 if (HasLongLines(data, lastNewlineOffset)) { 134 // We have long lines, rewrap 135 mDoWrap = true; 136 bool result = AppendToStringWrapped(data, *mOutput); 137 mDoWrap = false; 138 NS_ENSURE_TRUE(result, NS_ERROR_OUT_OF_MEMORY); 139 } else { 140 NS_ENSURE_TRUE(AppendToStringConvertLF(data, *mOutput), 141 NS_ERROR_OUT_OF_MEMORY); 142 } 143 } 144 145 return NS_OK; 146 } 147 148 bool nsXHTMLContentSerializer::SerializeAttributes( 149 Element* aElement, Element* aOriginalElement, nsAString& aTagPrefix, 150 const nsAString& aTagNamespaceURI, nsAtom* aTagName, nsAString& aStr, 151 uint32_t aSkipAttr, bool aAddNSAttr) { 152 nsresult rv; 153 uint32_t index, count; 154 nsAutoString prefixStr, uriStr, valueStr; 155 nsAutoString xmlnsStr; 156 xmlnsStr.AssignLiteral(kXMLNS); 157 158 int32_t contentNamespaceID = aElement->GetNameSpaceID(); 159 160 MaybeSerializeIsValue(aElement, aStr); 161 162 // this method is not called by nsHTMLContentSerializer 163 // so we don't have to check HTML element, just XHTML 164 165 if (mIsCopying && kNameSpaceID_XHTML == contentNamespaceID) { 166 // Need to keep track of OL and LI elements in order to get ordinal number 167 // for the LI. 168 if (aTagName == nsGkAtoms::ol) { 169 // We are copying and current node is an OL; 170 // Store its start attribute value in olState->startVal. 171 nsAutoString start; 172 int32_t startAttrVal = 0; 173 aElement->GetAttr(nsGkAtoms::start, start); 174 if (!start.IsEmpty()) { 175 nsresult rv = NS_OK; 176 startAttrVal = start.ToInteger(&rv); 177 // If OL has "start" attribute, first LI element has to start with that 178 // value Therefore subtracting 1 as all the LI elements are incrementing 179 // it before using it; In failure of ToInteger(), default StartAttrValue 180 // to 0. 181 if (NS_SUCCEEDED(rv)) 182 --startAttrVal; 183 else 184 startAttrVal = 0; 185 } 186 olState state(startAttrVal, true); 187 mOLStateStack.AppendElement(state); 188 } else if (aTagName == nsGkAtoms::li) { 189 mIsFirstChildOfOL = IsFirstChildOfOL(aOriginalElement); 190 if (mIsFirstChildOfOL) { 191 // If OL is parent of this LI, serialize attributes in different manner. 192 NS_ENSURE_TRUE(SerializeLIValueAttribute(aElement, aStr), false); 193 } 194 } 195 } 196 197 // If we had to add a new namespace declaration, serialize 198 // and push it on the namespace stack 199 if (aAddNSAttr) { 200 if (aTagPrefix.IsEmpty()) { 201 // Serialize default namespace decl 202 NS_ENSURE_TRUE( 203 SerializeAttr(u""_ns, xmlnsStr, aTagNamespaceURI, aStr, true), false); 204 } else { 205 // Serialize namespace decl 206 NS_ENSURE_TRUE( 207 SerializeAttr(xmlnsStr, aTagPrefix, aTagNamespaceURI, aStr, true), 208 false); 209 } 210 PushNameSpaceDecl(aTagPrefix, aTagNamespaceURI, aOriginalElement); 211 } 212 213 count = aElement->GetAttrCount(); 214 215 // Now serialize each of the attributes 216 // XXX Unfortunately we need a namespace manager to get 217 // attribute URIs. 218 for (index = 0; index < count; index++) { 219 if (aSkipAttr == index) { 220 continue; 221 } 222 223 dom::BorrowedAttrInfo info = aElement->GetAttrInfoAt(index); 224 const nsAttrName* name = info.mName; 225 226 int32_t namespaceID = name->NamespaceID(); 227 nsAtom* attrName = name->LocalName(); 228 nsAtom* attrPrefix = name->GetPrefix(); 229 230 // Filter out any attribute starting with [-|_]moz 231 nsDependentAtomString attrNameStr(attrName); 232 if (StringBeginsWith(attrNameStr, u"_moz"_ns) || 233 StringBeginsWith(attrNameStr, u"-moz"_ns)) { 234 continue; 235 } 236 237 if (attrPrefix) { 238 attrPrefix->ToString(prefixStr); 239 } else { 240 prefixStr.Truncate(); 241 } 242 243 bool addNSAttr = false; 244 if (kNameSpaceID_XMLNS != namespaceID) { 245 nsNameSpaceManager::GetInstance()->GetNameSpaceURI(namespaceID, uriStr); 246 addNSAttr = ConfirmPrefix(prefixStr, uriStr, aOriginalElement, true); 247 } 248 249 info.mValue->ToString(valueStr); 250 251 nsDependentAtomString nameStr(attrName); 252 bool isJS = false; 253 254 if (kNameSpaceID_XHTML == contentNamespaceID) { 255 if (mIsCopying && mIsFirstChildOfOL && (aTagName == nsGkAtoms::li) && 256 (attrName == nsGkAtoms::value)) { 257 // This is handled separately in SerializeLIValueAttribute() 258 continue; 259 } 260 261 isJS = IsJavaScript(aElement, attrName, namespaceID, valueStr); 262 263 if (namespaceID == kNameSpaceID_None && 264 ((attrName == nsGkAtoms::href) || (attrName == nsGkAtoms::src))) { 265 // Make all links absolute when converting only the selection: 266 if (mFlags & nsIDocumentEncoder::OutputAbsoluteLinks) { 267 // Would be nice to handle OBJECT tags, 268 // but that gets more complicated since we have to 269 // search the tag list for CODEBASE as well. 270 // For now, just leave them relative. 271 nsIURI* uri = aElement->GetBaseURI(); 272 if (uri) { 273 nsAutoString absURI; 274 rv = NS_MakeAbsoluteURI(absURI, valueStr, uri); 275 if (NS_SUCCEEDED(rv)) { 276 valueStr = absURI; 277 } 278 } 279 } 280 } 281 282 if (mRewriteEncodingDeclaration && aTagName == nsGkAtoms::meta && 283 attrName == nsGkAtoms::content) { 284 // If we're serializing a <meta http-equiv="content-type">, 285 // use the proper value, rather than what's in the document. 286 nsAutoString header; 287 aElement->GetAttr(nsGkAtoms::httpEquiv, header); 288 if (header.LowerCaseEqualsLiteral("content-type")) { 289 valueStr = 290 u"text/html; charset="_ns + NS_ConvertASCIItoUTF16(mCharset); 291 } 292 } 293 294 // Expand shorthand attribute. 295 if (namespaceID == kNameSpaceID_None && 296 IsShorthandAttr(attrName, aTagName) && valueStr.IsEmpty()) { 297 valueStr = nameStr; 298 } 299 } else { 300 isJS = IsJavaScript(aElement, attrName, namespaceID, valueStr); 301 } 302 303 NS_ENSURE_TRUE(SerializeAttr(prefixStr, nameStr, valueStr, aStr, !isJS), 304 false); 305 306 if (addNSAttr) { 307 NS_ASSERTION(!prefixStr.IsEmpty(), 308 "Namespaced attributes must have a prefix"); 309 NS_ENSURE_TRUE(SerializeAttr(xmlnsStr, prefixStr, uriStr, aStr, true), 310 false); 311 PushNameSpaceDecl(prefixStr, uriStr, aOriginalElement); 312 } 313 } 314 315 return true; 316 } 317 318 bool nsXHTMLContentSerializer::AfterElementStart(nsIContent* aContent, 319 nsIContent* aOriginalElement, 320 nsAString& aStr) { 321 if (mRewriteEncodingDeclaration && aContent->IsHTMLElement(nsGkAtoms::head)) { 322 // Check if there already are any content-type meta children. 323 // If there are, they will be modified to use the correct charset. 324 // If there aren't, we'll insert one here. 325 bool hasMeta = false; 326 for (nsIContent* child = aContent->GetFirstChild(); child; 327 child = child->GetNextSibling()) { 328 if (child->IsHTMLElement(nsGkAtoms::meta) && 329 child->AsElement()->HasAttr(nsGkAtoms::content)) { 330 nsAutoString header; 331 child->AsElement()->GetAttr(nsGkAtoms::httpEquiv, header); 332 333 if (header.LowerCaseEqualsLiteral("content-type")) { 334 hasMeta = true; 335 break; 336 } 337 } 338 } 339 340 if (!hasMeta) { 341 NS_ENSURE_TRUE(AppendNewLineToString(aStr), false); 342 if (mDoFormat) { 343 NS_ENSURE_TRUE(AppendIndentation(aStr), false); 344 } 345 NS_ENSURE_TRUE( 346 AppendToString(u"<meta http-equiv=\"content-type\""_ns, aStr), false); 347 NS_ENSURE_TRUE(AppendToString(u" content=\"text/html; charset="_ns, aStr), 348 false); 349 NS_ENSURE_TRUE(AppendToString(NS_ConvertASCIItoUTF16(mCharset), aStr), 350 false); 351 if (mIsHTMLSerializer) { 352 NS_ENSURE_TRUE(AppendToString(u"\">"_ns, aStr), false); 353 } else { 354 NS_ENSURE_TRUE(AppendToString(u"\" />"_ns, aStr), false); 355 } 356 } 357 } 358 359 return true; 360 } 361 362 void nsXHTMLContentSerializer::AfterElementEnd(nsIContent* aContent, 363 nsAString& aStr) { 364 NS_ASSERTION(!mIsHTMLSerializer, 365 "nsHTMLContentSerializer shouldn't call this method !"); 366 367 // this method is not called by nsHTMLContentSerializer 368 // so we don't have to check HTML element, just XHTML 369 if (aContent->IsHTMLElement(nsGkAtoms::body)) { 370 --mInBody; 371 } 372 } 373 374 NS_IMETHODIMP 375 nsXHTMLContentSerializer::AppendDocumentStart(Document* aDocument) { 376 if (!mBodyOnly) { 377 return nsXMLContentSerializer::AppendDocumentStart(aDocument); 378 } 379 380 return NS_OK; 381 } 382 383 bool nsXHTMLContentSerializer::CheckElementStart(Element* aElement, 384 bool& aForceFormat, 385 nsAString& aStr, 386 nsresult& aResult) { 387 aResult = NS_OK; 388 389 // The _moz_dirty attribute is emitted by the editor to 390 // indicate that this element should be pretty printed 391 // even if we're not in pretty printing mode 392 aForceFormat = !(mFlags & nsIDocumentEncoder::OutputIgnoreMozDirty) && 393 aElement->HasAttr(nsGkAtoms::mozdirty); 394 395 if (aElement->IsHTMLElement(nsGkAtoms::br) && 396 (mFlags & nsIDocumentEncoder::OutputNoFormattingInPre) && 397 PreLevel() > 0) { 398 aResult = AppendNewLineToString(aStr) ? NS_OK : NS_ERROR_OUT_OF_MEMORY; 399 return false; 400 } 401 402 if (aElement->IsHTMLElement(nsGkAtoms::body)) { 403 ++mInBody; 404 } 405 406 return true; 407 } 408 409 bool nsXHTMLContentSerializer::CheckElementEnd(Element* aElement, 410 Element* aOriginalElement, 411 bool& aForceFormat, 412 nsAString& aStr) { 413 NS_ASSERTION(!mIsHTMLSerializer, 414 "nsHTMLContentSerializer shouldn't call this method !"); 415 416 aForceFormat = !(mFlags & nsIDocumentEncoder::OutputIgnoreMozDirty) && 417 aElement->HasAttr(nsGkAtoms::mozdirty); 418 419 if (mIsCopying && aElement->IsHTMLElement(nsGkAtoms::ol)) { 420 NS_ASSERTION((!mOLStateStack.IsEmpty()), "Cannot have an empty OL Stack"); 421 /* Though at this point we must always have an state to be deleted as all 422 the OL opening tags are supposed to push an olState object to the stack*/ 423 if (!mOLStateStack.IsEmpty()) { 424 mOLStateStack.RemoveLastElement(); 425 } 426 } 427 428 bool dummyFormat; 429 return nsXMLContentSerializer::CheckElementEnd(aElement, aOriginalElement, 430 dummyFormat, aStr); 431 } 432 433 bool nsXHTMLContentSerializer::AppendAndTranslateEntities( 434 const nsAString& aStr, nsAString& aOutputStr) { 435 if (mBodyOnly && !mInBody) { 436 return true; 437 } 438 439 if (mDisableEntityEncoding) { 440 return aOutputStr.Append(aStr, fallible); 441 } 442 443 return nsXMLContentSerializer::AppendAndTranslateEntities(aStr, aOutputStr); 444 } 445 446 bool nsXHTMLContentSerializer::IsShorthandAttr(const nsAtom* aAttrName, 447 const nsAtom* aElementName) { 448 // checked 449 if ((aAttrName == nsGkAtoms::checked) && (aElementName == nsGkAtoms::input)) { 450 return true; 451 } 452 453 // compact 454 if ((aAttrName == nsGkAtoms::compact) && 455 (aElementName == nsGkAtoms::dir || aElementName == nsGkAtoms::dl || 456 aElementName == nsGkAtoms::menu || aElementName == nsGkAtoms::ol || 457 aElementName == nsGkAtoms::ul)) { 458 return true; 459 } 460 461 // declare 462 if ((aAttrName == nsGkAtoms::declare) && 463 (aElementName == nsGkAtoms::object)) { 464 return true; 465 } 466 467 // defer 468 if ((aAttrName == nsGkAtoms::defer) && (aElementName == nsGkAtoms::script)) { 469 return true; 470 } 471 472 // disabled 473 if ((aAttrName == nsGkAtoms::disabled) && 474 (aElementName == nsGkAtoms::button || aElementName == nsGkAtoms::input || 475 aElementName == nsGkAtoms::optgroup || 476 aElementName == nsGkAtoms::option || aElementName == nsGkAtoms::select || 477 aElementName == nsGkAtoms::textarea)) { 478 return true; 479 } 480 481 // ismap 482 if ((aAttrName == nsGkAtoms::ismap) && 483 (aElementName == nsGkAtoms::img || aElementName == nsGkAtoms::input)) { 484 return true; 485 } 486 487 // multiple 488 if ((aAttrName == nsGkAtoms::multiple) && 489 (aElementName == nsGkAtoms::select)) { 490 return true; 491 } 492 493 // noresize 494 if ((aAttrName == nsGkAtoms::noresize) && 495 (aElementName == nsGkAtoms::frame)) { 496 return true; 497 } 498 499 // noshade 500 if ((aAttrName == nsGkAtoms::noshade) && (aElementName == nsGkAtoms::hr)) { 501 return true; 502 } 503 504 // nowrap 505 if ((aAttrName == nsGkAtoms::nowrap) && 506 (aElementName == nsGkAtoms::td || aElementName == nsGkAtoms::th)) { 507 return true; 508 } 509 510 // readonly 511 if ((aAttrName == nsGkAtoms::readonly) && 512 (aElementName == nsGkAtoms::input || 513 aElementName == nsGkAtoms::textarea)) { 514 return true; 515 } 516 517 // selected 518 if ((aAttrName == nsGkAtoms::selected) && 519 (aElementName == nsGkAtoms::option)) { 520 return true; 521 } 522 523 // autoplay and controls 524 if ((aElementName == nsGkAtoms::video || aElementName == nsGkAtoms::audio) && 525 (aAttrName == nsGkAtoms::autoplay || aAttrName == nsGkAtoms::muted || 526 aAttrName == nsGkAtoms::controls)) { 527 return true; 528 } 529 530 return false; 531 } 532 533 bool nsXHTMLContentSerializer::LineBreakBeforeOpen(int32_t aNamespaceID, 534 nsAtom* aName) { 535 if (aNamespaceID != kNameSpaceID_XHTML) { 536 return mAddSpace; 537 } 538 539 if (aName == nsGkAtoms::title || aName == nsGkAtoms::meta || 540 aName == nsGkAtoms::link || aName == nsGkAtoms::style || 541 aName == nsGkAtoms::select || aName == nsGkAtoms::option || 542 aName == nsGkAtoms::script || aName == nsGkAtoms::html) { 543 return true; 544 } 545 546 return nsHTMLElement::IsBlock(nsHTMLTags::CaseSensitiveAtomTagToId(aName)); 547 } 548 549 bool nsXHTMLContentSerializer::LineBreakAfterOpen(int32_t aNamespaceID, 550 nsAtom* aName) { 551 if (aNamespaceID != kNameSpaceID_XHTML) { 552 return false; 553 } 554 555 if ((aName == nsGkAtoms::html) || (aName == nsGkAtoms::head) || 556 (aName == nsGkAtoms::body) || (aName == nsGkAtoms::ul) || 557 (aName == nsGkAtoms::ol) || (aName == nsGkAtoms::dl) || 558 (aName == nsGkAtoms::table) || (aName == nsGkAtoms::tbody) || 559 (aName == nsGkAtoms::tr) || (aName == nsGkAtoms::br) || 560 (aName == nsGkAtoms::meta) || (aName == nsGkAtoms::link) || 561 (aName == nsGkAtoms::script) || (aName == nsGkAtoms::select) || 562 (aName == nsGkAtoms::map) || (aName == nsGkAtoms::area) || 563 (aName == nsGkAtoms::style)) { 564 return true; 565 } 566 567 return false; 568 } 569 570 bool nsXHTMLContentSerializer::LineBreakBeforeClose(int32_t aNamespaceID, 571 nsAtom* aName) { 572 if (aNamespaceID != kNameSpaceID_XHTML) { 573 return false; 574 } 575 576 if ((aName == nsGkAtoms::html) || (aName == nsGkAtoms::head) || 577 (aName == nsGkAtoms::body) || (aName == nsGkAtoms::ul) || 578 (aName == nsGkAtoms::ol) || (aName == nsGkAtoms::dl) || 579 (aName == nsGkAtoms::select) || (aName == nsGkAtoms::table) || 580 (aName == nsGkAtoms::tbody)) { 581 return true; 582 } 583 return false; 584 } 585 586 bool nsXHTMLContentSerializer::LineBreakAfterClose(int32_t aNamespaceID, 587 nsAtom* aName) { 588 if (aNamespaceID != kNameSpaceID_XHTML) { 589 return false; 590 } 591 592 if ((aName == nsGkAtoms::html) || (aName == nsGkAtoms::head) || 593 (aName == nsGkAtoms::body) || (aName == nsGkAtoms::tr) || 594 (aName == nsGkAtoms::th) || (aName == nsGkAtoms::td) || 595 (aName == nsGkAtoms::title) || (aName == nsGkAtoms::dt) || 596 (aName == nsGkAtoms::dd) || (aName == nsGkAtoms::select) || 597 (aName == nsGkAtoms::option) || (aName == nsGkAtoms::map)) { 598 return true; 599 } 600 601 return nsHTMLElement::IsBlock(nsHTMLTags::CaseSensitiveAtomTagToId(aName)); 602 } 603 604 void nsXHTMLContentSerializer::MaybeEnterInPreContent(nsIContent* aNode) { 605 if (!ShouldMaintainPreLevel() || !aNode->IsHTMLElement()) { 606 return; 607 } 608 609 if (IsElementPreformatted(aNode) || 610 aNode->IsAnyOfHTMLElements(nsGkAtoms::script, nsGkAtoms::style, 611 nsGkAtoms::noscript, nsGkAtoms::noframes)) { 612 PreLevel()++; 613 } 614 } 615 616 void nsXHTMLContentSerializer::MaybeLeaveFromPreContent(nsIContent* aNode) { 617 if (!ShouldMaintainPreLevel() || !aNode->IsHTMLElement()) { 618 return; 619 } 620 621 if (IsElementPreformatted(aNode) || 622 aNode->IsAnyOfHTMLElements(nsGkAtoms::script, nsGkAtoms::style, 623 nsGkAtoms::noscript, nsGkAtoms::noframes)) { 624 --PreLevel(); 625 } 626 } 627 628 bool nsXHTMLContentSerializer::IsElementPreformatted(nsIContent* aNode) { 629 MOZ_ASSERT(ShouldMaintainPreLevel(), 630 "We should not be calling this needlessly"); 631 632 if (!aNode->IsElement()) { 633 return false; 634 } 635 RefPtr<const ComputedStyle> computedStyle = 636 nsComputedDOMStyle::GetComputedStyleNoFlush(aNode->AsElement()); 637 if (computedStyle) { 638 const nsStyleText* textStyle = computedStyle->StyleText(); 639 return textStyle->WhiteSpaceOrNewlineIsSignificant(); 640 } 641 return false; 642 } 643 644 bool nsXHTMLContentSerializer::SerializeLIValueAttribute(nsIContent* aElement, 645 nsAString& aStr) { 646 // We are copying and we are at the "first" LI node of OL in selected range. 647 // It may not be the first LI child of OL but it's first in the selected 648 // range. Note that we get into this condition only once per a OL. 649 bool found = false; 650 nsAutoString valueStr; 651 652 olState state(0, false); 653 654 if (!mOLStateStack.IsEmpty()) { 655 state = mOLStateStack[mOLStateStack.Length() - 1]; 656 // isFirstListItem should be true only before the serialization of the 657 // first item in the list. 658 state.isFirstListItem = false; 659 mOLStateStack[mOLStateStack.Length() - 1] = state; 660 } 661 662 int32_t startVal = state.startVal; 663 int32_t offset = 0; 664 665 // Traverse previous siblings until we find one with "value" attribute. 666 // offset keeps track of how many previous siblings we had to traverse. 667 nsIContent* currNode = aElement; 668 while (currNode && !found) { 669 if (currNode->IsHTMLElement(nsGkAtoms::li)) { 670 currNode->AsElement()->GetAttr(nsGkAtoms::value, valueStr); 671 if (valueStr.IsEmpty()) { 672 offset++; 673 } else { 674 found = true; 675 nsresult rv = NS_OK; 676 startVal = valueStr.ToInteger(&rv); 677 } 678 } 679 currNode = currNode->GetPreviousSibling(); 680 } 681 // If LI was not having "value", Set the "value" attribute for it. 682 // Note that We are at the first LI in the selected range of OL. 683 if (offset == 0 && found) { 684 // offset = 0 => LI itself has the value attribute and we did not need to 685 // traverse back. Just serialize value attribute like other tags. 686 NS_ENSURE_TRUE(SerializeAttr(u""_ns, u"value"_ns, valueStr, aStr, false), 687 false); 688 } else if (offset == 1 && !found) { 689 /*(offset = 1 && !found) means either LI is the first child node of OL 690 and LI is not having "value" attribute. 691 In that case we would not like to set "value" attribute to reduce the 692 changes. 693 */ 694 // do nothing... 695 } else if (offset > 0) { 696 // Set value attribute. 697 nsAutoString valueStr; 698 699 // As serializer needs to use this valueAttr we are creating here, 700 valueStr.AppendInt(startVal + offset); 701 NS_ENSURE_TRUE(SerializeAttr(u""_ns, u"value"_ns, valueStr, aStr, false), 702 false); 703 } 704 705 return true; 706 } 707 708 bool nsXHTMLContentSerializer::IsFirstChildOfOL(nsIContent* aElement) { 709 nsIContent* parent = aElement->GetParent(); 710 if (parent && parent->NodeName().LowerCaseEqualsLiteral("ol")) { 711 if (!mOLStateStack.IsEmpty()) { 712 olState state = mOLStateStack[mOLStateStack.Length() - 1]; 713 if (state.isFirstListItem) return true; 714 } 715 } 716 717 return false; 718 } 719 720 bool nsXHTMLContentSerializer::HasNoChildren(nsIContent* aContent) { 721 for (nsIContent* child = aContent->GetFirstChild(); child; 722 child = child->GetNextSibling()) { 723 if (!child->IsText()) return false; 724 725 if (child->TextLength()) return false; 726 } 727 728 return true; 729 }