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/. */
8 * nsIContentSerializer implementation that can be used with an
9 * nsIDocumentEncoder to convert an XML DOM to an XML string that
10 * could be parsed into more or less the original DOM.
13 #include "nsXMLContentSerializer.h"
15 #include "nsGkAtoms.h"
16 #include "nsIContent.h"
17 #include "nsIContentInlines.h"
18 #include "mozilla/dom/Document.h"
19 #include "nsIDocumentEncoder.h"
20 #include "nsElementTable.h"
21 #include "nsNameSpaceManager.h"
22 #include "nsTextFragment.h"
24 #include "mozilla/Sprintf.h"
25 #include "nsUnicharUtils.h"
27 #include "nsContentUtils.h"
28 #include "nsAttrName.h"
29 #include "mozilla/dom/Comment.h"
30 #include "mozilla/dom/CustomElementRegistry.h"
31 #include "mozilla/dom/DocumentType.h"
32 #include "mozilla/dom/Element.h"
33 #include "mozilla/dom/ProcessingInstruction.h"
34 #include "mozilla/intl/Segmenter.h"
35 #include "nsParserConstants.h"
36 #include "mozilla/Encoding.h"
38 using namespace mozilla
;
39 using namespace mozilla::dom
;
41 #define kXMLNS "xmlns"
43 // to be readable, we assume that an indented line contains
44 // at least this number of characters (arbitrary value here).
45 // This is a limit for the indentation.
46 #define MIN_INDENTED_LINE_LENGTH 15
48 // the string used to indent.
49 #define INDENT_STRING " "
50 #define INDENT_STRING_LENGTH 2
52 nsresult
NS_NewXMLContentSerializer(nsIContentSerializer
** aSerializer
) {
53 RefPtr
<nsXMLContentSerializer
> it
= new nsXMLContentSerializer();
54 it
.forget(aSerializer
);
58 nsXMLContentSerializer::nsXMLContentSerializer()
62 mIsIndentationAddedOnCurrentLine(false),
64 mAddNewlineForRootNode(false),
66 mMayIgnoreLineBreakSequence(false),
70 nsXMLContentSerializer::~nsXMLContentSerializer() = default;
72 NS_IMPL_ISUPPORTS(nsXMLContentSerializer
, nsIContentSerializer
)
75 nsXMLContentSerializer::Init(uint32_t aFlags
, uint32_t aWrapColumn
,
76 const Encoding
* aEncoding
, bool aIsCopying
,
77 bool aRewriteEncodingDeclaration
,
78 bool* aNeedsPreformatScanning
,
80 *aNeedsPreformatScanning
= false;
84 mIsIndentationAddedOnCurrentLine
= false;
86 mAddNewlineForRootNode
= false;
88 mMayIgnoreLineBreakSequence
= false;
93 aEncoding
->Name(mCharset
);
97 // Set the line break character:
98 if ((mFlags
& nsIDocumentEncoder::OutputCRLineBreak
) &&
99 (mFlags
& nsIDocumentEncoder::OutputLFLineBreak
)) { // Windows
100 mLineBreak
.AssignLiteral("\r\n");
101 } else if (mFlags
& nsIDocumentEncoder::OutputCRLineBreak
) { // Mac
102 mLineBreak
.Assign('\r');
103 } else if (mFlags
& nsIDocumentEncoder::OutputLFLineBreak
) { // Unix/DOM
104 mLineBreak
.Assign('\n');
106 mLineBreak
.AssignLiteral(NS_LINEBREAK
); // Platform/default
109 mDoRaw
= !!(mFlags
& nsIDocumentEncoder::OutputRaw
);
111 mDoFormat
= (mFlags
& nsIDocumentEncoder::OutputFormatted
&& !mDoRaw
);
113 mDoWrap
= (mFlags
& nsIDocumentEncoder::OutputWrap
&& !mDoRaw
);
116 !(mFlags
& nsIDocumentEncoder::OutputDisallowLineBreaking
);
121 mMaxColumn
= aWrapColumn
;
126 mIsIndentationAddedOnCurrentLine
= false;
130 nsresult
nsXMLContentSerializer::AppendTextData(nsIContent
* aNode
,
131 int32_t aStartOffset
,
134 bool aTranslateEntities
) {
135 nsIContent
* content
= aNode
;
136 const nsTextFragment
* frag
;
137 if (!content
|| !(frag
= content
->GetText())) {
138 return NS_ERROR_FAILURE
;
141 int32_t fragLength
= frag
->GetLength();
143 (aEndOffset
== -1) ? fragLength
: std::min(aEndOffset
, fragLength
);
144 int32_t length
= endoffset
- aStartOffset
;
146 NS_ASSERTION(aStartOffset
>= 0, "Negative start offset for text fragment!");
147 NS_ASSERTION(aStartOffset
<= endoffset
,
148 "A start offset is beyond the end of the text fragment!");
151 // XXX Zero is a legal value, maybe non-zero values should be an
157 const char16_t
* strStart
= frag
->Get2b() + aStartOffset
;
158 if (aTranslateEntities
) {
159 NS_ENSURE_TRUE(AppendAndTranslateEntities(
160 Substring(strStart
, strStart
+ length
), aStr
),
161 NS_ERROR_OUT_OF_MEMORY
);
163 NS_ENSURE_TRUE(aStr
.Append(Substring(strStart
, strStart
+ length
),
165 NS_ERROR_OUT_OF_MEMORY
);
169 if (!CopyASCIItoUTF16(Span(frag
->Get1b() + aStartOffset
, length
), utf16
,
170 mozilla::fallible_t())) {
171 return NS_ERROR_OUT_OF_MEMORY
;
173 if (aTranslateEntities
) {
174 NS_ENSURE_TRUE(AppendAndTranslateEntities(utf16
, aStr
),
175 NS_ERROR_OUT_OF_MEMORY
);
177 NS_ENSURE_TRUE(aStr
.Append(utf16
, mozilla::fallible
),
178 NS_ERROR_OUT_OF_MEMORY
);
186 nsXMLContentSerializer::AppendText(nsIContent
* aText
, int32_t aStartOffset
,
187 int32_t aEndOffset
) {
188 NS_ENSURE_ARG(aText
);
189 NS_ENSURE_STATE(mOutput
);
194 rv
= AppendTextData(aText
, aStartOffset
, aEndOffset
, data
, true);
195 if (NS_FAILED(rv
)) return NS_ERROR_FAILURE
;
197 if (mDoRaw
|| PreLevel() > 0) {
198 NS_ENSURE_TRUE(AppendToStringConvertLF(data
, *mOutput
),
199 NS_ERROR_OUT_OF_MEMORY
);
200 } else if (mDoFormat
) {
201 NS_ENSURE_TRUE(AppendToStringFormatedWrapped(data
, *mOutput
),
202 NS_ERROR_OUT_OF_MEMORY
);
203 } else if (mDoWrap
) {
204 NS_ENSURE_TRUE(AppendToStringWrapped(data
, *mOutput
),
205 NS_ERROR_OUT_OF_MEMORY
);
207 NS_ENSURE_TRUE(AppendToStringConvertLF(data
, *mOutput
),
208 NS_ERROR_OUT_OF_MEMORY
);
215 nsXMLContentSerializer::AppendCDATASection(nsIContent
* aCDATASection
,
216 int32_t aStartOffset
,
217 int32_t aEndOffset
) {
218 NS_ENSURE_ARG(aCDATASection
);
219 NS_ENSURE_STATE(mOutput
);
223 constexpr auto cdata
= u
"<![CDATA["_ns
;
225 if (mDoRaw
|| PreLevel() > 0) {
226 NS_ENSURE_TRUE(AppendToString(cdata
, *mOutput
), NS_ERROR_OUT_OF_MEMORY
);
227 } else if (mDoFormat
) {
228 NS_ENSURE_TRUE(AppendToStringFormatedWrapped(cdata
, *mOutput
),
229 NS_ERROR_OUT_OF_MEMORY
);
230 } else if (mDoWrap
) {
231 NS_ENSURE_TRUE(AppendToStringWrapped(cdata
, *mOutput
),
232 NS_ERROR_OUT_OF_MEMORY
);
234 NS_ENSURE_TRUE(AppendToString(cdata
, *mOutput
), NS_ERROR_OUT_OF_MEMORY
);
238 rv
= AppendTextData(aCDATASection
, aStartOffset
, aEndOffset
, data
, false);
239 if (NS_FAILED(rv
)) return NS_ERROR_FAILURE
;
241 NS_ENSURE_TRUE(AppendToStringConvertLF(data
, *mOutput
),
242 NS_ERROR_OUT_OF_MEMORY
);
244 NS_ENSURE_TRUE(AppendToString(u
"]]>"_ns
, *mOutput
), NS_ERROR_OUT_OF_MEMORY
);
250 nsXMLContentSerializer::AppendProcessingInstruction(ProcessingInstruction
* aPI
,
251 int32_t aStartOffset
,
252 int32_t aEndOffset
) {
253 NS_ENSURE_STATE(mOutput
);
255 nsAutoString target
, data
, start
;
257 NS_ENSURE_TRUE(MaybeAddNewlineForRootNode(*mOutput
), NS_ERROR_OUT_OF_MEMORY
);
259 aPI
->GetTarget(target
);
263 NS_ENSURE_TRUE(start
.AppendLiteral("<?", mozilla::fallible
),
264 NS_ERROR_OUT_OF_MEMORY
);
265 NS_ENSURE_TRUE(start
.Append(target
, mozilla::fallible
),
266 NS_ERROR_OUT_OF_MEMORY
);
268 if (mDoRaw
|| PreLevel() > 0) {
269 NS_ENSURE_TRUE(AppendToString(start
, *mOutput
), NS_ERROR_OUT_OF_MEMORY
);
270 } else if (mDoFormat
) {
272 NS_ENSURE_TRUE(AppendNewLineToString(*mOutput
), NS_ERROR_OUT_OF_MEMORY
);
274 NS_ENSURE_TRUE(AppendToStringFormatedWrapped(start
, *mOutput
),
275 NS_ERROR_OUT_OF_MEMORY
);
276 } else if (mDoWrap
) {
277 NS_ENSURE_TRUE(AppendToStringWrapped(start
, *mOutput
),
278 NS_ERROR_OUT_OF_MEMORY
);
280 NS_ENSURE_TRUE(AppendToString(start
, *mOutput
), NS_ERROR_OUT_OF_MEMORY
);
283 if (!data
.IsEmpty()) {
284 NS_ENSURE_TRUE(AppendToString(char16_t(' '), *mOutput
),
285 NS_ERROR_OUT_OF_MEMORY
);
286 NS_ENSURE_TRUE(AppendToStringConvertLF(data
, *mOutput
),
287 NS_ERROR_OUT_OF_MEMORY
);
289 NS_ENSURE_TRUE(AppendToString(u
"?>"_ns
, *mOutput
), NS_ERROR_OUT_OF_MEMORY
);
291 MaybeFlagNewlineForRootNode(aPI
);
297 nsXMLContentSerializer::AppendComment(Comment
* aComment
, int32_t aStartOffset
,
298 int32_t aEndOffset
) {
299 NS_ENSURE_STATE(mOutput
);
302 aComment
->GetData(data
);
304 int32_t dataLength
= data
.Length();
305 if (aStartOffset
|| (aEndOffset
!= -1 && aEndOffset
< dataLength
)) {
307 (aEndOffset
== -1) ? dataLength
: std::min(aEndOffset
, dataLength
);
308 length
-= aStartOffset
;
312 data
.Mid(frag
, aStartOffset
, length
);
317 NS_ENSURE_TRUE(MaybeAddNewlineForRootNode(*mOutput
), NS_ERROR_OUT_OF_MEMORY
);
319 constexpr auto startComment
= u
"<!--"_ns
;
321 if (mDoRaw
|| PreLevel() > 0) {
322 NS_ENSURE_TRUE(AppendToString(startComment
, *mOutput
),
323 NS_ERROR_OUT_OF_MEMORY
);
324 } else if (mDoFormat
) {
326 NS_ENSURE_TRUE(AppendNewLineToString(*mOutput
), NS_ERROR_OUT_OF_MEMORY
);
328 NS_ENSURE_TRUE(AppendToStringFormatedWrapped(startComment
, *mOutput
),
329 NS_ERROR_OUT_OF_MEMORY
);
330 } else if (mDoWrap
) {
331 NS_ENSURE_TRUE(AppendToStringWrapped(startComment
, *mOutput
),
332 NS_ERROR_OUT_OF_MEMORY
);
334 NS_ENSURE_TRUE(AppendToString(startComment
, *mOutput
),
335 NS_ERROR_OUT_OF_MEMORY
);
338 // Even if mDoformat, we don't format the content because it
339 // could have been preformated by the author
340 NS_ENSURE_TRUE(AppendToStringConvertLF(data
, *mOutput
),
341 NS_ERROR_OUT_OF_MEMORY
);
342 NS_ENSURE_TRUE(AppendToString(u
"-->"_ns
, *mOutput
), NS_ERROR_OUT_OF_MEMORY
);
344 MaybeFlagNewlineForRootNode(aComment
);
350 nsXMLContentSerializer::AppendDoctype(DocumentType
* aDocType
) {
351 NS_ENSURE_STATE(mOutput
);
353 nsAutoString name
, publicId
, systemId
;
354 aDocType
->GetName(name
);
355 aDocType
->GetPublicId(publicId
);
356 aDocType
->GetSystemId(systemId
);
358 NS_ENSURE_TRUE(MaybeAddNewlineForRootNode(*mOutput
), NS_ERROR_OUT_OF_MEMORY
);
360 NS_ENSURE_TRUE(AppendToString(u
"<!DOCTYPE "_ns
, *mOutput
),
361 NS_ERROR_OUT_OF_MEMORY
);
362 NS_ENSURE_TRUE(AppendToString(name
, *mOutput
), NS_ERROR_OUT_OF_MEMORY
);
365 if (!publicId
.IsEmpty()) {
366 NS_ENSURE_TRUE(AppendToString(u
" PUBLIC "_ns
, *mOutput
),
367 NS_ERROR_OUT_OF_MEMORY
);
368 if (publicId
.FindChar(char16_t('"')) == -1) {
369 quote
= char16_t('"');
371 quote
= char16_t('\'');
373 NS_ENSURE_TRUE(AppendToString(quote
, *mOutput
), NS_ERROR_OUT_OF_MEMORY
);
374 NS_ENSURE_TRUE(AppendToString(publicId
, *mOutput
), NS_ERROR_OUT_OF_MEMORY
);
375 NS_ENSURE_TRUE(AppendToString(quote
, *mOutput
), NS_ERROR_OUT_OF_MEMORY
);
377 if (!systemId
.IsEmpty()) {
378 NS_ENSURE_TRUE(AppendToString(char16_t(' '), *mOutput
),
379 NS_ERROR_OUT_OF_MEMORY
);
380 if (systemId
.FindChar(char16_t('"')) == -1) {
381 quote
= char16_t('"');
383 quote
= char16_t('\'');
385 NS_ENSURE_TRUE(AppendToString(quote
, *mOutput
), NS_ERROR_OUT_OF_MEMORY
);
386 NS_ENSURE_TRUE(AppendToString(systemId
, *mOutput
),
387 NS_ERROR_OUT_OF_MEMORY
);
388 NS_ENSURE_TRUE(AppendToString(quote
, *mOutput
), NS_ERROR_OUT_OF_MEMORY
);
390 } else if (!systemId
.IsEmpty()) {
391 if (systemId
.FindChar(char16_t('"')) == -1) {
392 quote
= char16_t('"');
394 quote
= char16_t('\'');
396 NS_ENSURE_TRUE(AppendToString(u
" SYSTEM "_ns
, *mOutput
),
397 NS_ERROR_OUT_OF_MEMORY
);
398 NS_ENSURE_TRUE(AppendToString(quote
, *mOutput
), NS_ERROR_OUT_OF_MEMORY
);
399 NS_ENSURE_TRUE(AppendToString(systemId
, *mOutput
), NS_ERROR_OUT_OF_MEMORY
);
400 NS_ENSURE_TRUE(AppendToString(quote
, *mOutput
), NS_ERROR_OUT_OF_MEMORY
);
403 NS_ENSURE_TRUE(AppendToString(kGreaterThan
, *mOutput
),
404 NS_ERROR_OUT_OF_MEMORY
);
405 MaybeFlagNewlineForRootNode(aDocType
);
410 nsresult
nsXMLContentSerializer::PushNameSpaceDecl(const nsAString
& aPrefix
,
411 const nsAString
& aURI
,
412 nsIContent
* aOwner
) {
413 NameSpaceDecl
* decl
= mNameSpaceStack
.AppendElement();
414 if (!decl
) return NS_ERROR_OUT_OF_MEMORY
;
416 decl
->mPrefix
.Assign(aPrefix
);
417 decl
->mURI
.Assign(aURI
);
418 // Don't addref - this weak reference will be removed when
420 decl
->mOwner
= aOwner
;
424 void nsXMLContentSerializer::PopNameSpaceDeclsFor(nsIContent
* aOwner
) {
425 int32_t index
, count
;
427 count
= mNameSpaceStack
.Length();
428 for (index
= count
- 1; index
>= 0; index
--) {
429 if (mNameSpaceStack
[index
].mOwner
!= aOwner
) {
432 mNameSpaceStack
.RemoveLastElement();
436 bool nsXMLContentSerializer::ConfirmPrefix(nsAString
& aPrefix
,
437 const nsAString
& aURI
,
438 nsIContent
* aElement
,
440 if (aPrefix
.EqualsLiteral(kXMLNS
)) {
444 if (aURI
.EqualsLiteral("http://www.w3.org/XML/1998/namespace")) {
445 // The prefix must be xml for this namespace. We don't need to declare it,
446 // so always just set the prefix to xml.
447 aPrefix
.AssignLiteral("xml");
454 if (aURI
.IsEmpty()) {
455 // Attribute in the null namespace. This just shouldn't have a prefix.
456 // And there's no need to push any namespace decls
461 // Attribute not in the null namespace -- must have a prefix
462 mustHavePrefix
= true;
464 // Not an attribute, so doesn't _have_ to have a prefix
465 mustHavePrefix
= false;
468 // Keep track of the closest prefix that's bound to aURI and whether we've
469 // found such a thing. closestURIMatch holds the prefix, and uriMatch
470 // indicates whether we actually have one.
471 nsAutoString closestURIMatch
;
472 bool uriMatch
= false;
474 // Also keep track of whether we've seen aPrefix already. If we have, that
475 // means that it's already bound to a URI different from aURI, so even if we
476 // later (so in a more outer scope) see it bound to aURI we can't reuse it.
477 bool haveSeenOurPrefix
= false;
479 int32_t count
= mNameSpaceStack
.Length();
480 int32_t index
= count
- 1;
482 NameSpaceDecl
& decl
= mNameSpaceStack
.ElementAt(index
);
483 // Check if we've found a prefix match
484 if (aPrefix
.Equals(decl
.mPrefix
)) {
485 // If the URIs match and aPrefix is not bound to any other URI, we can
487 if (!haveSeenOurPrefix
&& aURI
.Equals(decl
.mURI
)) {
488 // Just use our uriMatch stuff. That will deal with an empty aPrefix
489 // the right way. We can break out of the loop now, though.
491 closestURIMatch
= aPrefix
;
495 haveSeenOurPrefix
= true;
497 // If they don't, and either:
498 // 1) We have a prefix (so we'd be redeclaring this prefix to point to a
499 // different namespace) or
500 // 2) We're looking at an existing default namespace decl on aElement (so
501 // we can't create a new default namespace decl for this URI)
502 // then generate a new prefix. Note that we do NOT generate new prefixes
503 // if we happen to have aPrefix == decl->mPrefix == "" and mismatching
504 // URIs when |decl| doesn't have aElement as its owner. In that case we
505 // can simply push the new namespace URI as the default namespace for
507 if (!aPrefix
.IsEmpty() || decl
.mOwner
== aElement
) {
508 NS_ASSERTION(!aURI
.IsEmpty(),
509 "Not allowed to add a xmlns attribute with an empty "
510 "namespace name unless it declares the default "
513 GenerateNewPrefix(aPrefix
);
514 // Now we need to validate our new prefix/uri combination; check it
515 // against the full namespace stack again. Note that just restarting
516 // the while loop is ok, since we haven't changed aURI, so the
517 // closestURIMatch and uriMatch state is not affected.
519 haveSeenOurPrefix
= false;
524 // If we've found a URI match, then record the first one
525 if (!uriMatch
&& aURI
.Equals(decl
.mURI
)) {
526 // Need to check that decl->mPrefix is not declared anywhere closer to
527 // us. If it is, we can't use it.
528 bool prefixOK
= true;
530 for (index2
= count
- 1; index2
> index
&& prefixOK
; --index2
) {
531 prefixOK
= (mNameSpaceStack
[index2
].mPrefix
!= decl
.mPrefix
);
536 closestURIMatch
.Assign(decl
.mPrefix
);
543 // At this point the following invariants hold:
544 // 1) The prefix in closestURIMatch is mapped to aURI in our scope if
546 // 2) There is nothing on the namespace stack that has aPrefix as the prefix
547 // and a _different_ URI, except for the case aPrefix.IsEmpty (and
548 // possible default namespaces on ancestors)
550 // So if uriMatch is set it's OK to use the closestURIMatch prefix. The one
551 // exception is when closestURIMatch is actually empty (default namespace
552 // decl) and we must have a prefix.
553 if (uriMatch
&& (!mustHavePrefix
|| !closestURIMatch
.IsEmpty())) {
554 aPrefix
.Assign(closestURIMatch
);
558 if (aPrefix
.IsEmpty()) {
559 // At this point, aPrefix is empty (which means we never had a prefix to
560 // start with). If we must have a prefix, just generate a new prefix and
561 // then send it back through the namespace stack checks to make sure it's
563 if (mustHavePrefix
) {
564 GenerateNewPrefix(aPrefix
);
565 return ConfirmPrefix(aPrefix
, aURI
, aElement
, aIsAttribute
);
568 // One final special case. If aPrefix is empty and we never saw an empty
569 // prefix (default namespace decl) on the namespace stack and we're in the
570 // null namespace there is no reason to output an |xmlns=""| here. It just
571 // makes the output less readable.
572 if (!haveSeenOurPrefix
&& aURI
.IsEmpty()) {
577 // Now just set aURI as the new default namespace URI. Indicate that we need
578 // to create a namespace decl for the final prefix
582 void nsXMLContentSerializer::GenerateNewPrefix(nsAString
& aPrefix
) {
584 aPrefix
.AppendInt(mPrefixIndex
++);
587 bool nsXMLContentSerializer::SerializeAttr(const nsAString
& aPrefix
,
588 const nsAString
& aName
,
589 const nsAString
& aValue
,
591 bool aDoEscapeEntities
) {
592 // Because this method can short-circuit AppendToString for raw output, we
593 // need to make sure that we're not inappropriately serializing attributes
594 // from outside the body
595 if (mBodyOnly
&& !mInBody
) {
599 nsAutoString attrString_
;
600 // For innerHTML we can do faster appending without
601 // temporary strings.
602 bool rawAppend
= mDoRaw
&& aDoEscapeEntities
;
603 nsAString
& attrString
= (rawAppend
) ? aStr
: attrString_
;
605 NS_ENSURE_TRUE(attrString
.Append(char16_t(' '), mozilla::fallible
), false);
606 if (!aPrefix
.IsEmpty()) {
607 NS_ENSURE_TRUE(attrString
.Append(aPrefix
, mozilla::fallible
), false);
608 NS_ENSURE_TRUE(attrString
.Append(char16_t(':'), mozilla::fallible
), false);
610 NS_ENSURE_TRUE(attrString
.Append(aName
, mozilla::fallible
), false);
612 if (aDoEscapeEntities
) {
613 // if problem characters are turned into character entity references
614 // then there will be no problem with the value delimiter characters
615 NS_ENSURE_TRUE(attrString
.AppendLiteral("=\"", mozilla::fallible
), false);
618 bool result
= AppendAndTranslateEntities(aValue
, attrString
);
619 mInAttribute
= false;
620 NS_ENSURE_TRUE(result
, false);
622 NS_ENSURE_TRUE(attrString
.Append(char16_t('"'), mozilla::fallible
), false);
627 // Depending on whether the attribute value contains quotes or apostrophes
628 // we need to select the delimiter character and escape characters using
629 // character entity references, ignoring the value of aDoEscapeEntities.
630 // See http://www.w3.org/TR/REC-html40/appendix/notes.html#h-B.3.2.2 for
631 // the standard on character entity references in values. We also have to
632 // make sure to escape any '&' characters.
634 bool bIncludesSingle
= false;
635 bool bIncludesDouble
= false;
636 nsAString::const_iterator iCurr
, iEnd
;
637 aValue
.BeginReading(iCurr
);
638 aValue
.EndReading(iEnd
);
639 for (; iCurr
!= iEnd
; ++iCurr
) {
640 if (*iCurr
== char16_t('\'')) {
641 bIncludesSingle
= true;
642 if (bIncludesDouble
) {
645 } else if (*iCurr
== char16_t('"')) {
646 bIncludesDouble
= true;
647 if (bIncludesSingle
) {
653 // Delimiter and escaping is according to the following table
654 // bIncludesDouble bIncludesSingle Delimiter Escape Double Quote
655 // FALSE FALSE " FALSE
656 // FALSE TRUE " FALSE
657 // TRUE FALSE ' FALSE
659 char16_t cDelimiter
=
660 (bIncludesDouble
&& !bIncludesSingle
) ? char16_t('\'') : char16_t('"');
661 NS_ENSURE_TRUE(attrString
.Append(char16_t('='), mozilla::fallible
), false);
662 NS_ENSURE_TRUE(attrString
.Append(cDelimiter
, mozilla::fallible
), false);
663 nsAutoString
sValue(aValue
);
665 sValue
.ReplaceSubstring(u
"&"_ns
, u
"&"_ns
, mozilla::fallible
),
667 if (bIncludesDouble
&& bIncludesSingle
) {
669 sValue
.ReplaceSubstring(u
"\""_ns
, u
"""_ns
, mozilla::fallible
),
672 NS_ENSURE_TRUE(attrString
.Append(sValue
, mozilla::fallible
), false);
673 NS_ENSURE_TRUE(attrString
.Append(cDelimiter
, mozilla::fallible
), false);
676 if (mDoWrap
&& mColPos
+ attrString
.Length() > mMaxColumn
) {
677 // Attr would cause us to overrun the max width, so begin a new line.
678 NS_ENSURE_TRUE(AppendNewLineToString(aStr
), false);
680 // Chomp the leading space.
681 nsDependentSubstring
chomped(attrString
, 1);
682 if (mDoFormat
&& mIndent
.Length() + chomped
.Length() <= mMaxColumn
) {
683 NS_ENSURE_TRUE(AppendIndentation(aStr
), false);
685 NS_ENSURE_TRUE(AppendToStringConvertLF(chomped
, aStr
), false);
687 NS_ENSURE_TRUE(AppendToStringConvertLF(attrString
, aStr
), false);
693 uint32_t nsXMLContentSerializer::ScanNamespaceDeclarations(
694 Element
* aElement
, Element
* aOriginalElement
,
695 const nsAString
& aTagNamespaceURI
) {
696 uint32_t index
, count
;
697 nsAutoString uriStr
, valueStr
;
699 count
= aElement
->GetAttrCount();
701 // First scan for namespace declarations, pushing each on the stack
702 uint32_t skipAttr
= count
;
703 for (index
= 0; index
< count
; index
++) {
704 const BorrowedAttrInfo info
= aElement
->GetAttrInfoAt(index
);
705 const nsAttrName
* name
= info
.mName
;
707 int32_t namespaceID
= name
->NamespaceID();
708 nsAtom
* attrName
= name
->LocalName();
710 if (namespaceID
== kNameSpaceID_XMLNS
||
711 // Also push on the stack attrs named "xmlns" in the null
712 // namespace... because once we serialize those out they'll look like
713 // namespace decls. :(
714 // XXXbz what if we have both "xmlns" in the null namespace and "xmlns"
715 // in the xmlns namespace?
716 (namespaceID
== kNameSpaceID_None
&& attrName
== nsGkAtoms::xmlns
)) {
717 info
.mValue
->ToString(uriStr
);
719 if (!name
->GetPrefix()) {
720 if (aTagNamespaceURI
.IsEmpty() && !uriStr
.IsEmpty()) {
721 // If the element is in no namespace we need to add a xmlns
722 // attribute to declare that. That xmlns attribute must not have a
723 // prefix (see http://www.w3.org/TR/REC-xml-names/#dt-prefix), ie it
724 // must declare the default namespace. We just found an xmlns
725 // attribute that declares the default namespace to something
726 // non-empty. We're going to ignore this attribute, for children we
727 // will detect that we need to add it again and attributes aren't
728 // affected by the default namespace.
731 // Default NS attribute does not have prefix (and the name is "xmlns")
732 PushNameSpaceDecl(u
""_ns
, uriStr
, aOriginalElement
);
735 PushNameSpaceDecl(nsDependentAtomString(attrName
), uriStr
,
743 bool nsXMLContentSerializer::IsJavaScript(nsIContent
* aContent
,
744 nsAtom
* aAttrNameAtom
,
745 int32_t aAttrNamespaceID
,
746 const nsAString
& aValueString
) {
747 bool isHtml
= aContent
->IsHTMLElement();
748 bool isXul
= aContent
->IsXULElement();
749 bool isSvg
= aContent
->IsSVGElement();
751 if (aAttrNamespaceID
== kNameSpaceID_None
&& (isHtml
|| isXul
|| isSvg
) &&
752 (aAttrNameAtom
== nsGkAtoms::href
|| aAttrNameAtom
== nsGkAtoms::src
)) {
753 static const char kJavaScript
[] = "javascript";
754 int32_t pos
= aValueString
.FindChar(':');
755 if (pos
< (int32_t)(sizeof kJavaScript
- 1)) return false;
756 nsAutoString
scheme(Substring(aValueString
, 0, pos
));
757 scheme
.StripWhitespace();
758 if ((scheme
.Length() == (sizeof kJavaScript
- 1)) &&
759 scheme
.EqualsIgnoreCase(kJavaScript
))
765 return aContent
->IsEventAttributeName(aAttrNameAtom
);
768 bool nsXMLContentSerializer::SerializeAttributes(
769 Element
* aElement
, Element
* aOriginalElement
, nsAString
& aTagPrefix
,
770 const nsAString
& aTagNamespaceURI
, nsAtom
* aTagName
, nsAString
& aStr
,
771 uint32_t aSkipAttr
, bool aAddNSAttr
) {
772 nsAutoString prefixStr
, uriStr
, valueStr
;
773 nsAutoString xmlnsStr
;
774 xmlnsStr
.AssignLiteral(kXMLNS
);
775 uint32_t index
, count
;
777 MaybeSerializeIsValue(aElement
, aStr
);
779 // If we had to add a new namespace declaration, serialize
780 // and push it on the namespace stack
782 if (aTagPrefix
.IsEmpty()) {
783 // Serialize default namespace decl
785 SerializeAttr(u
""_ns
, xmlnsStr
, aTagNamespaceURI
, aStr
, true), false);
787 // Serialize namespace decl
789 SerializeAttr(xmlnsStr
, aTagPrefix
, aTagNamespaceURI
, aStr
, true),
792 PushNameSpaceDecl(aTagPrefix
, aTagNamespaceURI
, aOriginalElement
);
795 count
= aElement
->GetAttrCount();
797 // Now serialize each of the attributes
798 // XXX Unfortunately we need a namespace manager to get
800 for (index
= 0; index
< count
; index
++) {
801 if (aSkipAttr
== index
) {
805 const nsAttrName
* name
= aElement
->GetAttrNameAt(index
);
806 int32_t namespaceID
= name
->NamespaceID();
807 nsAtom
* attrName
= name
->LocalName();
808 nsAtom
* attrPrefix
= name
->GetPrefix();
810 // Filter out any attribute starting with [-|_]moz
811 nsDependentAtomString
attrNameStr(attrName
);
812 if (StringBeginsWith(attrNameStr
, u
"_moz"_ns
) ||
813 StringBeginsWith(attrNameStr
, u
"-moz"_ns
)) {
818 attrPrefix
->ToString(prefixStr
);
820 prefixStr
.Truncate();
823 bool addNSAttr
= false;
824 if (kNameSpaceID_XMLNS
!= namespaceID
) {
825 nsNameSpaceManager::GetInstance()->GetNameSpaceURI(namespaceID
, uriStr
);
826 addNSAttr
= ConfirmPrefix(prefixStr
, uriStr
, aOriginalElement
, true);
829 aElement
->GetAttr(namespaceID
, attrName
, valueStr
);
831 nsDependentAtomString
nameStr(attrName
);
832 bool isJS
= IsJavaScript(aElement
, attrName
, namespaceID
, valueStr
);
834 NS_ENSURE_TRUE(SerializeAttr(prefixStr
, nameStr
, valueStr
, aStr
, !isJS
),
838 NS_ASSERTION(!prefixStr
.IsEmpty(),
839 "Namespaced attributes must have a prefix");
840 NS_ENSURE_TRUE(SerializeAttr(xmlnsStr
, prefixStr
, uriStr
, aStr
, true),
842 PushNameSpaceDecl(prefixStr
, uriStr
, aOriginalElement
);
850 nsXMLContentSerializer::AppendElementStart(Element
* aElement
,
851 Element
* aOriginalElement
) {
852 NS_ENSURE_ARG(aElement
);
853 NS_ENSURE_STATE(mOutput
);
855 bool forceFormat
= false;
857 if (!CheckElementStart(aElement
, forceFormat
, *mOutput
, rv
)) {
858 // When we go to AppendElementEnd for this element, we're going to
859 // MaybeLeaveFromPreContent(). So make sure to MaybeEnterInPreContent()
860 // now, so our PreLevel() doesn't get confused.
861 MaybeEnterInPreContent(aElement
);
865 NS_ENSURE_SUCCESS(rv
, rv
);
867 nsAutoString tagPrefix
, tagLocalName
, tagNamespaceURI
;
868 aElement
->NodeInfo()->GetPrefix(tagPrefix
);
869 aElement
->NodeInfo()->GetName(tagLocalName
);
870 aElement
->NodeInfo()->GetNamespaceURI(tagNamespaceURI
);
873 ScanNamespaceDeclarations(aElement
, aOriginalElement
, tagNamespaceURI
);
875 nsAtom
* name
= aElement
->NodeInfo()->NameAtom();
876 bool lineBreakBeforeOpen
=
877 LineBreakBeforeOpen(aElement
->GetNameSpaceID(), name
);
879 if ((mDoFormat
|| forceFormat
) && !mDoRaw
&& !PreLevel()) {
880 if (mColPos
&& lineBreakBeforeOpen
) {
881 NS_ENSURE_TRUE(AppendNewLineToString(*mOutput
), NS_ERROR_OUT_OF_MEMORY
);
883 NS_ENSURE_TRUE(MaybeAddNewlineForRootNode(*mOutput
),
884 NS_ERROR_OUT_OF_MEMORY
);
887 NS_ENSURE_TRUE(AppendIndentation(*mOutput
), NS_ERROR_OUT_OF_MEMORY
);
888 } else if (mAddSpace
) {
889 NS_ENSURE_TRUE(AppendToString(char16_t(' '), *mOutput
),
890 NS_ERROR_OUT_OF_MEMORY
);
893 } else if (mAddSpace
) {
894 NS_ENSURE_TRUE(AppendToString(char16_t(' '), *mOutput
),
895 NS_ERROR_OUT_OF_MEMORY
);
898 NS_ENSURE_TRUE(MaybeAddNewlineForRootNode(*mOutput
),
899 NS_ERROR_OUT_OF_MEMORY
);
902 // Always reset to avoid false newlines in case MaybeAddNewlineForRootNode
904 mAddNewlineForRootNode
= false;
908 ConfirmPrefix(tagPrefix
, tagNamespaceURI
, aOriginalElement
, false);
910 // Serialize the qualified name of the element
911 NS_ENSURE_TRUE(AppendToString(kLessThan
, *mOutput
), NS_ERROR_OUT_OF_MEMORY
);
912 if (!tagPrefix
.IsEmpty()) {
913 NS_ENSURE_TRUE(AppendToString(tagPrefix
, *mOutput
), NS_ERROR_OUT_OF_MEMORY
);
914 NS_ENSURE_TRUE(AppendToString(u
":"_ns
, *mOutput
), NS_ERROR_OUT_OF_MEMORY
);
916 NS_ENSURE_TRUE(AppendToString(tagLocalName
, *mOutput
),
917 NS_ERROR_OUT_OF_MEMORY
);
919 MaybeEnterInPreContent(aElement
);
921 if ((mDoFormat
|| forceFormat
) && !mDoRaw
&& !PreLevel()) {
922 NS_ENSURE_TRUE(IncrIndentation(name
), NS_ERROR_OUT_OF_MEMORY
);
926 SerializeAttributes(aElement
, aOriginalElement
, tagPrefix
,
927 tagNamespaceURI
, name
, *mOutput
, skipAttr
, addNSAttr
),
928 NS_ERROR_OUT_OF_MEMORY
);
930 NS_ENSURE_TRUE(AppendEndOfElementStart(aElement
, aOriginalElement
, *mOutput
),
931 NS_ERROR_OUT_OF_MEMORY
);
933 if ((mDoFormat
|| forceFormat
) && !mDoRaw
&& !PreLevel() &&
934 LineBreakAfterOpen(aElement
->GetNameSpaceID(), name
)) {
935 NS_ENSURE_TRUE(AppendNewLineToString(*mOutput
), NS_ERROR_OUT_OF_MEMORY
);
938 NS_ENSURE_TRUE(AfterElementStart(aElement
, aOriginalElement
, *mOutput
),
939 NS_ERROR_OUT_OF_MEMORY
);
944 // aElement is the actual element we're outputting. aOriginalElement is the one
945 // in the original DOM, which is the one we have to test for kids.
946 static bool ElementNeedsSeparateEndTag(Element
* aElement
,
947 Element
* aOriginalElement
) {
948 if (aOriginalElement
->GetChildCount()) {
949 // We have kids, so we need a separate end tag. This needs to be checked on
950 // aOriginalElement because that's the one that's actually in the DOM and
955 if (!aElement
->IsHTMLElement()) {
956 // Empty non-HTML elements can just skip a separate end tag.
960 // HTML container tags should have a separate end tag even if empty, per spec.
962 // https://w3c.github.io/DOM-Parsing/#dfn-concept-xml-serialization-algorithm
963 nsAtom
* localName
= aElement
->NodeInfo()->NameAtom();
964 bool isHTMLContainer
= nsHTMLElement::IsContainer(
965 nsHTMLTags::CaseSensitiveAtomTagToId(localName
));
966 return isHTMLContainer
;
969 bool nsXMLContentSerializer::AppendEndOfElementStart(Element
* aElement
,
970 Element
* aOriginalElement
,
972 if (ElementNeedsSeparateEndTag(aElement
, aOriginalElement
)) {
973 return AppendToString(kGreaterThan
, aStr
);
976 // We don't need a separate end tag. For HTML elements (which at this point
977 // must be non-containers), append a space before the '/', per spec. See
978 // https://w3c.github.io/DOM-Parsing/#dfn-concept-xml-serialization-algorithm
979 if (aOriginalElement
->IsHTMLElement()) {
980 if (!AppendToString(kSpace
, aStr
)) {
985 return AppendToString(u
"/>"_ns
, aStr
);
989 nsXMLContentSerializer::AppendElementEnd(Element
* aElement
,
990 Element
* aOriginalElement
) {
991 NS_ENSURE_ARG(aElement
);
992 NS_ENSURE_STATE(mOutput
);
994 nsIContent
* content
= aElement
;
996 bool forceFormat
= false, outputElementEnd
;
998 CheckElementEnd(aElement
, aOriginalElement
, forceFormat
, *mOutput
);
1000 nsAtom
* name
= content
->NodeInfo()->NameAtom();
1002 if ((mDoFormat
|| forceFormat
) && !mDoRaw
&& !PreLevel()) {
1003 DecrIndentation(name
);
1006 if (!outputElementEnd
) {
1007 // Keep this in sync with the cleanup at the end of this method.
1008 PopNameSpaceDeclsFor(aElement
);
1009 MaybeLeaveFromPreContent(content
);
1010 MaybeFlagNewlineForRootNode(aElement
);
1011 AfterElementEnd(content
, *mOutput
);
1015 nsAutoString tagPrefix
, tagLocalName
, tagNamespaceURI
;
1017 aElement
->NodeInfo()->GetPrefix(tagPrefix
);
1018 aElement
->NodeInfo()->GetName(tagLocalName
);
1019 aElement
->NodeInfo()->GetNamespaceURI(tagNamespaceURI
);
1022 bool debugNeedToPushNamespace
=
1024 ConfirmPrefix(tagPrefix
, tagNamespaceURI
, aElement
, false);
1025 NS_ASSERTION(!debugNeedToPushNamespace
,
1026 "Can't push namespaces in closing tag!");
1028 if ((mDoFormat
|| forceFormat
) && !mDoRaw
&& !PreLevel()) {
1029 bool lineBreakBeforeClose
=
1030 LineBreakBeforeClose(content
->GetNameSpaceID(), name
);
1032 if (mColPos
&& lineBreakBeforeClose
) {
1033 NS_ENSURE_TRUE(AppendNewLineToString(*mOutput
), NS_ERROR_OUT_OF_MEMORY
);
1036 NS_ENSURE_TRUE(AppendIndentation(*mOutput
), NS_ERROR_OUT_OF_MEMORY
);
1037 } else if (mAddSpace
) {
1038 NS_ENSURE_TRUE(AppendToString(char16_t(' '), *mOutput
),
1039 NS_ERROR_OUT_OF_MEMORY
);
1042 } else if (mAddSpace
) {
1043 NS_ENSURE_TRUE(AppendToString(char16_t(' '), *mOutput
),
1044 NS_ERROR_OUT_OF_MEMORY
);
1048 NS_ENSURE_TRUE(AppendToString(kEndTag
, *mOutput
), NS_ERROR_OUT_OF_MEMORY
);
1049 if (!tagPrefix
.IsEmpty()) {
1050 NS_ENSURE_TRUE(AppendToString(tagPrefix
, *mOutput
), NS_ERROR_OUT_OF_MEMORY
);
1051 NS_ENSURE_TRUE(AppendToString(u
":"_ns
, *mOutput
), NS_ERROR_OUT_OF_MEMORY
);
1053 NS_ENSURE_TRUE(AppendToString(tagLocalName
, *mOutput
),
1054 NS_ERROR_OUT_OF_MEMORY
);
1055 NS_ENSURE_TRUE(AppendToString(kGreaterThan
, *mOutput
),
1056 NS_ERROR_OUT_OF_MEMORY
);
1058 // Keep what follows in sync with the cleanup in the !outputElementEnd case.
1059 PopNameSpaceDeclsFor(aElement
);
1061 MaybeLeaveFromPreContent(content
);
1063 if ((mDoFormat
|| forceFormat
) && !mDoRaw
&& !PreLevel() &&
1064 LineBreakAfterClose(content
->GetNameSpaceID(), name
)) {
1065 NS_ENSURE_TRUE(AppendNewLineToString(*mOutput
), NS_ERROR_OUT_OF_MEMORY
);
1067 MaybeFlagNewlineForRootNode(aElement
);
1070 AfterElementEnd(content
, *mOutput
);
1076 nsXMLContentSerializer::Finish() {
1077 NS_ENSURE_STATE(mOutput
);
1085 nsXMLContentSerializer::GetOutputLength(uint32_t& aLength
) const {
1086 NS_ENSURE_STATE(mOutput
);
1088 aLength
= mOutput
->Length();
1094 nsXMLContentSerializer::AppendDocumentStart(Document
* aDocument
) {
1095 NS_ENSURE_ARG_POINTER(aDocument
);
1096 NS_ENSURE_STATE(mOutput
);
1098 nsAutoString version
, encoding
, standalone
;
1099 aDocument
->GetXMLDeclaration(version
, encoding
, standalone
);
1101 if (version
.IsEmpty())
1102 return NS_OK
; // A declaration must have version, or there is no decl
1104 constexpr auto endQuote
= u
"\""_ns
;
1106 *mOutput
+= u
"<?xml version=\""_ns
+ version
+ endQuote
;
1108 if (!mCharset
.IsEmpty()) {
1110 u
" encoding=\""_ns
+ NS_ConvertASCIItoUTF16(mCharset
) + endQuote
;
1112 // Otherwise just don't output an encoding attr. Not that we expect
1113 // mCharset to ever be empty.
1116 NS_WARNING("Empty mCharset? How come?");
1120 if (!standalone
.IsEmpty()) {
1121 *mOutput
+= u
" standalone=\""_ns
+ standalone
+ endQuote
;
1124 NS_ENSURE_TRUE(mOutput
->AppendLiteral("?>", mozilla::fallible
),
1125 NS_ERROR_OUT_OF_MEMORY
);
1126 mAddNewlineForRootNode
= true;
1131 bool nsXMLContentSerializer::CheckElementStart(Element
*, bool& aForceFormat
,
1133 nsresult
& aResult
) {
1135 aForceFormat
= false;
1139 bool nsXMLContentSerializer::CheckElementEnd(Element
* aElement
,
1140 Element
* aOriginalElement
,
1143 // We don't output a separate end tag for empty element
1144 aForceFormat
= false;
1145 return ElementNeedsSeparateEndTag(aElement
, aOriginalElement
);
1148 bool nsXMLContentSerializer::AppendToString(const char16_t aChar
,
1149 nsAString
& aOutputStr
) {
1150 if (mBodyOnly
&& !mInBody
) {
1154 return aOutputStr
.Append(aChar
, mozilla::fallible
);
1157 bool nsXMLContentSerializer::AppendToString(const nsAString
& aStr
,
1158 nsAString
& aOutputStr
) {
1159 if (mBodyOnly
&& !mInBody
) {
1162 mColPos
+= aStr
.Length();
1163 return aOutputStr
.Append(aStr
, mozilla::fallible
);
1168 // This table indexes into kEntityStrings[].
1169 const uint8_t nsXMLContentSerializer::kEntities
[] = {
1171 _
, _
, _
, _
, _
, _
, _
, _
, _
, _
,
1172 _
, _
, _
, _
, _
, _
, _
, _
, _
, _
,
1173 _
, _
, _
, _
, _
, _
, _
, _
, _
, _
,
1174 _
, _
, _
, _
, _
, _
, _
, _
, 2, _
,
1175 _
, _
, _
, _
, _
, _
, _
, _
, _
, _
,
1176 _
, _
, _
, _
, _
, _
, _
, _
, _
, _
,
1181 // This table indexes into kEntityStrings[].
1182 const uint8_t nsXMLContentSerializer::kAttrEntities
[] = {
1184 _
, _
, _
, _
, _
, _
, _
, _
, _
, 5,
1185 6, _
, _
, 7, _
, _
, _
, _
, _
, _
,
1186 _
, _
, _
, _
, _
, _
, _
, _
, _
, _
,
1187 _
, _
, _
, _
, 1, _
, _
, _
, 2, _
,
1188 _
, _
, _
, _
, _
, _
, _
, _
, _
, _
,
1189 _
, _
, _
, _
, _
, _
, _
, _
, _
, _
,
1196 const char* const nsXMLContentSerializer::kEntityStrings
[] = {
1207 bool nsXMLContentSerializer::AppendAndTranslateEntities(const nsAString
& aStr
,
1208 nsAString
& aOutputStr
) {
1210 return AppendAndTranslateEntities
<kGTVal
>(aStr
, aOutputStr
, kAttrEntities
,
1214 return AppendAndTranslateEntities
<kGTVal
>(aStr
, aOutputStr
, kEntities
,
1219 bool nsXMLContentSerializer::AppendAndTranslateEntities(
1220 const nsAString
& aStr
, nsAString
& aOutputStr
, const uint8_t aEntityTable
[],
1221 uint16_t aMaxTableIndex
, const char* const aStringTable
[]) {
1222 nsReadingIterator
<char16_t
> done_reading
;
1223 aStr
.EndReading(done_reading
);
1225 // for each chunk of |aString|...
1226 uint32_t advanceLength
= 0;
1227 nsReadingIterator
<char16_t
> iter
;
1229 for (aStr
.BeginReading(iter
); iter
!= done_reading
;
1230 iter
.advance(int32_t(advanceLength
))) {
1231 uint32_t fragmentLength
= done_reading
- iter
;
1232 const char16_t
* c
= iter
.get();
1233 const char16_t
* fragmentStart
= c
;
1234 const char16_t
* fragmentEnd
= c
+ fragmentLength
;
1235 const char* entityText
= nullptr;
1238 // for each character in this chunk, check if it
1239 // needs to be replaced
1240 for (; c
< fragmentEnd
; c
++, advanceLength
++) {
1242 if ((val
<= aMaxTableIndex
) && aEntityTable
[val
]) {
1243 entityText
= aStringTable
[aEntityTable
[val
]];
1249 aOutputStr
.Append(fragmentStart
, advanceLength
, mozilla::fallible
),
1252 NS_ENSURE_TRUE(AppendASCIItoUTF16(mozilla::MakeStringSpan(entityText
),
1253 aOutputStr
, mozilla::fallible
),
1262 bool nsXMLContentSerializer::MaybeAddNewlineForRootNode(nsAString
& aStr
) {
1263 if (mAddNewlineForRootNode
) {
1264 return AppendNewLineToString(aStr
);
1270 void nsXMLContentSerializer::MaybeFlagNewlineForRootNode(nsINode
* aNode
) {
1271 nsINode
* parent
= aNode
->GetParentNode();
1273 mAddNewlineForRootNode
= parent
->IsDocument();
1277 void nsXMLContentSerializer::MaybeEnterInPreContent(nsIContent
* aNode
) {
1278 // support of the xml:space attribute
1280 if (ShouldMaintainPreLevel() && aNode
->IsElement() &&
1281 aNode
->AsElement()->GetAttr(kNameSpaceID_XML
, nsGkAtoms::space
, space
) &&
1282 space
.EqualsLiteral("preserve")) {
1287 void nsXMLContentSerializer::MaybeLeaveFromPreContent(nsIContent
* aNode
) {
1288 // support of the xml:space attribute
1290 if (ShouldMaintainPreLevel() && aNode
->IsElement() &&
1291 aNode
->AsElement()->GetAttr(kNameSpaceID_XML
, nsGkAtoms::space
, space
) &&
1292 space
.EqualsLiteral("preserve")) {
1297 bool nsXMLContentSerializer::AppendNewLineToString(nsAString
& aStr
) {
1298 bool result
= AppendToString(mLineBreak
, aStr
);
1299 mMayIgnoreLineBreakSequence
= true;
1302 mIsIndentationAddedOnCurrentLine
= false;
1306 bool nsXMLContentSerializer::AppendIndentation(nsAString
& aStr
) {
1307 mIsIndentationAddedOnCurrentLine
= true;
1308 bool result
= AppendToString(mIndent
, aStr
);
1310 mMayIgnoreLineBreakSequence
= false;
1314 bool nsXMLContentSerializer::IncrIndentation(nsAtom
* aName
) {
1315 // we want to keep the source readable
1317 mIndent
.Length() >= uint32_t(mMaxColumn
) - MIN_INDENTED_LINE_LENGTH
) {
1320 return mIndent
.AppendLiteral(INDENT_STRING
, mozilla::fallible
);
1326 void nsXMLContentSerializer::DecrIndentation(nsAtom
* aName
) {
1327 if (mIndentOverflow
)
1330 mIndent
.Cut(0, INDENT_STRING_LENGTH
);
1333 bool nsXMLContentSerializer::LineBreakBeforeOpen(int32_t aNamespaceID
,
1338 bool nsXMLContentSerializer::LineBreakAfterOpen(int32_t aNamespaceID
,
1343 bool nsXMLContentSerializer::LineBreakBeforeClose(int32_t aNamespaceID
,
1348 bool nsXMLContentSerializer::LineBreakAfterClose(int32_t aNamespaceID
,
1353 bool nsXMLContentSerializer::AppendToStringConvertLF(const nsAString
& aStr
,
1354 nsAString
& aOutputStr
) {
1355 if (mBodyOnly
&& !mInBody
) {
1360 NS_ENSURE_TRUE(AppendToString(aStr
, aOutputStr
), false);
1362 // Convert line-endings to mLineBreak
1364 uint32_t theLen
= aStr
.Length();
1365 while (start
< theLen
) {
1366 int32_t eol
= aStr
.FindChar('\n', start
);
1367 if (eol
== kNotFound
) {
1368 nsDependentSubstring
dataSubstring(aStr
, start
, theLen
- start
);
1369 NS_ENSURE_TRUE(AppendToString(dataSubstring
, aOutputStr
), false);
1371 // if there was a line break before this substring
1372 // AppendNewLineToString was called, so we should reverse
1374 mMayIgnoreLineBreakSequence
= false;
1376 nsDependentSubstring
dataSubstring(aStr
, start
, eol
- start
);
1377 NS_ENSURE_TRUE(AppendToString(dataSubstring
, aOutputStr
), false);
1378 NS_ENSURE_TRUE(AppendNewLineToString(aOutputStr
), false);
1387 bool nsXMLContentSerializer::AppendFormatedWrapped_WhitespaceSequence(
1388 nsAString::const_char_iterator
& aPos
,
1389 const nsAString::const_char_iterator aEnd
,
1390 const nsAString::const_char_iterator aSequenceStart
,
1391 bool& aMayIgnoreStartOfLineWhitespaceSequence
, nsAString
& aOutputStr
) {
1392 // Handle the complete sequence of whitespace.
1393 // Continue to iterate until we find the first non-whitespace char.
1394 // Updates "aPos" to point to the first unhandled char.
1395 // Also updates the aMayIgnoreStartOfLineWhitespaceSequence flag,
1396 // as well as the other "global" state flags.
1398 bool sawBlankOrTab
= false;
1399 bool leaveLoop
= false;
1405 sawBlankOrTab
= true;
1409 // do not increase mColPos,
1410 // because we will reduce the whitespace to a single char
1416 } while (!leaveLoop
&& aPos
< aEnd
);
1419 // if we had previously been asked to add space,
1420 // our situation has not changed
1421 } else if (!sawBlankOrTab
&& mMayIgnoreLineBreakSequence
) {
1422 // nothing to do in the case where line breaks have already been added
1423 // before the call of AppendToStringWrapped
1424 // and only if we found line break in the sequence
1425 mMayIgnoreLineBreakSequence
= false;
1426 } else if (aMayIgnoreStartOfLineWhitespaceSequence
) {
1428 aMayIgnoreStartOfLineWhitespaceSequence
= false;
1430 if (sawBlankOrTab
) {
1431 if (mDoWrap
&& mColPos
+ 1 >= mMaxColumn
) {
1432 // no much sense in delaying, we only have one slot left,
1433 // let's write a break now
1434 bool result
= aOutputStr
.Append(mLineBreak
, mozilla::fallible
);
1436 mIsIndentationAddedOnCurrentLine
= false;
1437 mMayIgnoreLineBreakSequence
= true;
1438 NS_ENSURE_TRUE(result
, false);
1440 // do not write out yet, we may write out either a space or a linebreak
1441 // let's delay writing it out until we know more
1443 ++mColPos
; // eat a slot of available space
1446 // Asian text usually does not contain spaces, therefore we should not
1447 // transform a linebreak into a space.
1448 // Since we only saw linebreaks, but no spaces or tabs,
1449 // let's write a linebreak now.
1450 NS_ENSURE_TRUE(AppendNewLineToString(aOutputStr
), false);
1457 bool nsXMLContentSerializer::AppendWrapped_NonWhitespaceSequence(
1458 nsAString::const_char_iterator
& aPos
,
1459 const nsAString::const_char_iterator aEnd
,
1460 const nsAString::const_char_iterator aSequenceStart
,
1461 bool& aMayIgnoreStartOfLineWhitespaceSequence
,
1462 bool& aSequenceStartAfterAWhiteSpace
, nsAString
& aOutputStr
) {
1463 mMayIgnoreLineBreakSequence
= false;
1464 aMayIgnoreStartOfLineWhitespaceSequence
= false;
1466 // Handle the complete sequence of non-whitespace in this block
1467 // Iterate until we find the first whitespace char or an aEnd condition
1468 // Updates "aPos" to point to the first unhandled char.
1469 // Also updates the aMayIgnoreStartOfLineWhitespaceSequence flag,
1470 // as well as the other "global" state flags.
1472 bool thisSequenceStartsAtBeginningOfLine
= !mColPos
;
1473 bool onceAgainBecauseWeAddedBreakInFront
= false;
1474 bool foundWhitespaceInLoop
;
1475 uint32_t length
, colPos
;
1481 if (mDoFormat
&& !mDoRaw
&& !PreLevel() &&
1482 !onceAgainBecauseWeAddedBreakInFront
) {
1483 colPos
= mIndent
.Length();
1487 foundWhitespaceInLoop
= false;
1489 // we iterate until the next whitespace character
1490 // or until we reach the maximum of character per line
1491 // or until the end of the string to add.
1493 if (*aPos
== ' ' || *aPos
== '\t' || *aPos
== '\n') {
1494 foundWhitespaceInLoop
= true;
1500 } while ((!mDoWrap
|| colPos
+ length
< mMaxColumn
) && aPos
< aEnd
);
1502 // in the case we don't reached the end of the string, but we reached the
1503 // maxcolumn, we see if there is a whitespace after the maxcolumn if yes,
1504 // then we can append directly the string instead of appending a new line
1506 if (*aPos
== ' ' || *aPos
== '\t' || *aPos
== '\n') {
1507 foundWhitespaceInLoop
= true;
1510 if (aPos
== aEnd
|| foundWhitespaceInLoop
) {
1511 // there is enough room for the complete block we found
1512 if (mDoFormat
&& !mColPos
) {
1513 NS_ENSURE_TRUE(AppendIndentation(aOutputStr
), false);
1514 } else if (mAddSpace
) {
1515 bool result
= aOutputStr
.Append(char16_t(' '), mozilla::fallible
);
1517 NS_ENSURE_TRUE(result
, false);
1521 NS_ENSURE_TRUE(aOutputStr
.Append(aSequenceStart
, aPos
- aSequenceStart
,
1525 // We have not yet reached the max column, we will continue to
1526 // fill the current line in the next outer loop iteration
1527 // (this one in AppendToStringWrapped)
1528 // make sure we return in this outer loop
1529 onceAgainBecauseWeAddedBreakInFront
= false;
1530 } else { // we reach the max column
1531 if (!thisSequenceStartsAtBeginningOfLine
&&
1532 (mAddSpace
|| (!mDoFormat
&& aSequenceStartAfterAWhiteSpace
))) {
1533 // when !mDoFormat, mAddSpace is not used, mAddSpace is always false
1534 // so, in the case where mDoWrap && !mDoFormat, if we want to enter in
1535 // this condition...
1537 // We can avoid to wrap. We try to add the whole block
1538 // in an empty new line
1540 NS_ENSURE_TRUE(AppendNewLineToString(aOutputStr
), false);
1541 aPos
= aSequenceStart
;
1542 thisSequenceStartsAtBeginningOfLine
= true;
1543 onceAgainBecauseWeAddedBreakInFront
= true;
1546 onceAgainBecauseWeAddedBreakInFront
= false;
1547 Maybe
<uint32_t> wrapPosition
;
1549 if (mAllowLineBreaking
) {
1550 MOZ_ASSERT(aPos
< aEnd
,
1551 "We shouldn't be here if aPos reaches the end of text!");
1553 // Search forward from aSequenceStart until we find the largest
1554 // wrap position less than or equal to aPos.
1555 Maybe
<uint32_t> nextWrapPosition
;
1556 Span
<const char16_t
> subSeq(aSequenceStart
, aEnd
);
1557 intl::LineBreakIteratorUtf16
lineBreakIter(subSeq
);
1559 nextWrapPosition
= lineBreakIter
.Next();
1560 MOZ_ASSERT(nextWrapPosition
.isSome(),
1561 "We should've exited the loop when reaching the end of "
1562 "text in the previous iteration!");
1564 // Trim space at the tail. UAX#14 doesn't have break opportunity
1565 // for ASCII space at the tail.
1566 const Maybe
<uint32_t> originalNextWrapPosition
= nextWrapPosition
;
1567 while (*nextWrapPosition
> 0 &&
1568 subSeq
.at(*nextWrapPosition
- 1) == 0x20) {
1569 nextWrapPosition
= Some(*nextWrapPosition
- 1);
1571 if (*nextWrapPosition
== 0) {
1572 // Restore the original nextWrapPosition.
1573 nextWrapPosition
= originalNextWrapPosition
;
1576 if (aSequenceStart
+ *nextWrapPosition
> aPos
) {
1579 wrapPosition
= nextWrapPosition
;
1582 if (!wrapPosition
) {
1583 // The wrap position found in the first iteration of the above loop
1584 // already exceeds aPos. We accept it as valid a wrap position only
1585 // if it is not end-of-text. If the line-breaker returned
1586 // end-of-text, we don't know that it is actually a good wrap
1587 // position, so ignore it and continue to use the fallback code
1589 if (*nextWrapPosition
< subSeq
.Length()) {
1590 wrapPosition
= nextWrapPosition
;
1596 if (!mColPos
&& mDoFormat
) {
1597 NS_ENSURE_TRUE(AppendIndentation(aOutputStr
), false);
1598 } else if (mAddSpace
) {
1599 bool result
= aOutputStr
.Append(char16_t(' '), mozilla::fallible
);
1601 NS_ENSURE_TRUE(result
, false);
1603 NS_ENSURE_TRUE(aOutputStr
.Append(aSequenceStart
, *wrapPosition
,
1607 NS_ENSURE_TRUE(AppendNewLineToString(aOutputStr
), false);
1608 aPos
= aSequenceStart
+ *wrapPosition
;
1609 aMayIgnoreStartOfLineWhitespaceSequence
= true;
1611 // try some simple fallback logic
1612 // go forward up to the next whitespace position,
1613 // in the worst case this will be all the rest of the data
1615 // XXX(jfkthame) Should we (conditionally) output indentation here?
1616 // It makes for tidier-looking formatted output, at the cost of
1617 // exceeding the target width by a greater amount on such lines.
1618 // if (!mColPos && mDoFormat) {
1619 // NS_ENSURE_TRUE(AppendIndentation(aOutputStr), false);
1620 // mAddSpace = false;
1623 // we update the mColPos variable with the length of
1624 // the part already parsed.
1627 // now try to find the next whitespace
1629 if (*aPos
== ' ' || *aPos
== '\t' || *aPos
== '\n') {
1635 } while (aPos
< aEnd
);
1638 bool result
= aOutputStr
.Append(char16_t(' '), mozilla::fallible
);
1640 NS_ENSURE_TRUE(result
, false);
1643 aOutputStr
.Append(aSequenceStart
, aPos
- aSequenceStart
,
1648 aSequenceStartAfterAWhiteSpace
= false;
1650 } while (onceAgainBecauseWeAddedBreakInFront
);
1655 bool nsXMLContentSerializer::AppendToStringFormatedWrapped(
1656 const nsAString
& aStr
, nsAString
& aOutputStr
) {
1657 if (mBodyOnly
&& !mInBody
) {
1661 nsAString::const_char_iterator pos
, end
, sequenceStart
;
1663 aStr
.BeginReading(pos
);
1664 aStr
.EndReading(end
);
1666 bool sequenceStartAfterAWhitespace
= false;
1668 nsAString::const_char_iterator end2
;
1669 aOutputStr
.EndReading(end2
);
1671 if (*end2
== ' ' || *end2
== '\n' || *end2
== '\t') {
1672 sequenceStartAfterAWhitespace
= true;
1676 // if the current line already has text on it, such as a tag,
1677 // leading whitespace is significant
1678 bool mayIgnoreStartOfLineWhitespaceSequence
=
1680 (mIsIndentationAddedOnCurrentLine
&& sequenceStartAfterAWhitespace
&&
1681 uint32_t(mColPos
) == mIndent
.Length()));
1684 sequenceStart
= pos
;
1686 // if beginning of a whitespace sequence
1687 if (*pos
== ' ' || *pos
== '\n' || *pos
== '\t') {
1688 NS_ENSURE_TRUE(AppendFormatedWrapped_WhitespaceSequence(
1689 pos
, end
, sequenceStart
,
1690 mayIgnoreStartOfLineWhitespaceSequence
, aOutputStr
),
1692 } else { // any other non-whitespace char
1694 AppendWrapped_NonWhitespaceSequence(
1695 pos
, end
, sequenceStart
, mayIgnoreStartOfLineWhitespaceSequence
,
1696 sequenceStartAfterAWhitespace
, aOutputStr
),
1704 bool nsXMLContentSerializer::AppendWrapped_WhitespaceSequence(
1705 nsAString::const_char_iterator
& aPos
,
1706 const nsAString::const_char_iterator aEnd
,
1707 const nsAString::const_char_iterator aSequenceStart
,
1708 nsAString
& aOutputStr
) {
1709 // Handle the complete sequence of whitespace.
1710 // Continue to iterate until we find the first non-whitespace char.
1711 // Updates "aPos" to point to the first unhandled char.
1713 mIsIndentationAddedOnCurrentLine
= false;
1715 bool leaveLoop
= false;
1716 nsAString::const_char_iterator lastPos
= aPos
;
1722 // if there are too many spaces on a line, we wrap
1723 if (mColPos
>= mMaxColumn
) {
1724 if (lastPos
!= aPos
) {
1726 aOutputStr
.Append(lastPos
, aPos
- lastPos
, mozilla::fallible
),
1729 NS_ENSURE_TRUE(AppendToString(mLineBreak
, aOutputStr
), false);
1738 if (lastPos
!= aPos
) {
1740 aOutputStr
.Append(lastPos
, aPos
- lastPos
, mozilla::fallible
),
1743 NS_ENSURE_TRUE(AppendToString(mLineBreak
, aOutputStr
), false);
1752 } while (!leaveLoop
&& aPos
< aEnd
);
1754 if (lastPos
!= aPos
) {
1756 aOutputStr
.Append(lastPos
, aPos
- lastPos
, mozilla::fallible
), false);
1762 bool nsXMLContentSerializer::AppendToStringWrapped(const nsAString
& aStr
,
1763 nsAString
& aOutputStr
) {
1764 if (mBodyOnly
&& !mInBody
) {
1768 nsAString::const_char_iterator pos
, end
, sequenceStart
;
1770 aStr
.BeginReading(pos
);
1771 aStr
.EndReading(end
);
1773 // not used in this case, but needed by AppendWrapped_NonWhitespaceSequence
1774 bool mayIgnoreStartOfLineWhitespaceSequence
= false;
1775 mMayIgnoreLineBreakSequence
= false;
1777 bool sequenceStartAfterAWhitespace
= false;
1778 if (pos
< end
&& !aOutputStr
.IsEmpty()) {
1779 nsAString::const_char_iterator end2
;
1780 aOutputStr
.EndReading(end2
);
1782 if (*end2
== ' ' || *end2
== '\n' || *end2
== '\t') {
1783 sequenceStartAfterAWhitespace
= true;
1788 sequenceStart
= pos
;
1790 // if beginning of a whitespace sequence
1791 if (*pos
== ' ' || *pos
== '\n' || *pos
== '\t') {
1792 sequenceStartAfterAWhitespace
= true;
1794 AppendWrapped_WhitespaceSequence(pos
, end
, sequenceStart
, aOutputStr
),
1796 } else { // any other non-whitespace char
1798 AppendWrapped_NonWhitespaceSequence(
1799 pos
, end
, sequenceStart
, mayIgnoreStartOfLineWhitespaceSequence
,
1800 sequenceStartAfterAWhitespace
, aOutputStr
),
1808 bool nsXMLContentSerializer::ShouldMaintainPreLevel() const {
1809 // Only attempt to maintain the pre level for consumers who care about it.
1810 return !mDoRaw
|| (mFlags
& nsIDocumentEncoder::OutputNoFormattingInPre
);
1813 bool nsXMLContentSerializer::MaybeSerializeIsValue(Element
* aElement
,
1815 CustomElementData
* ceData
= aElement
->GetCustomElementData();
1817 nsAtom
* isAttr
= ceData
->GetIs(aElement
);
1818 if (isAttr
&& !aElement
->HasAttr(nsGkAtoms::is
)) {
1819 NS_ENSURE_TRUE(aStr
.AppendLiteral(" is=\"", mozilla::fallible
), false);
1821 aStr
.Append(nsDependentAtomString(isAttr
), mozilla::fallible
), false);
1822 NS_ENSURE_TRUE(aStr
.AppendLiteral("\"", mozilla::fallible
), false);