Bug 1861467 - [wpt-sync] Update web-platform-tests to eedf737ce39c512d0ca3471f988972e...
[gecko.git] / dom / serializers / nsXMLContentSerializer.cpp
blobab0fcdf4135be82bbc0056937801a0b9a174f242
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/. */
7 /*
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"
23 #include "nsString.h"
24 #include "mozilla/Sprintf.h"
25 #include "nsUnicharUtils.h"
26 #include "nsCRT.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);
55 return NS_OK;
58 nsXMLContentSerializer::nsXMLContentSerializer()
59 : mPrefixIndex(0),
60 mColPos(0),
61 mIndentOverflow(0),
62 mIsIndentationAddedOnCurrentLine(false),
63 mInAttribute(false),
64 mAddNewlineForRootNode(false),
65 mAddSpace(false),
66 mMayIgnoreLineBreakSequence(false),
67 mBodyOnly(false),
68 mInBody(0) {}
70 nsXMLContentSerializer::~nsXMLContentSerializer() = default;
72 NS_IMPL_ISUPPORTS(nsXMLContentSerializer, nsIContentSerializer)
74 NS_IMETHODIMP
75 nsXMLContentSerializer::Init(uint32_t aFlags, uint32_t aWrapColumn,
76 const Encoding* aEncoding, bool aIsCopying,
77 bool aRewriteEncodingDeclaration,
78 bool* aNeedsPreformatScanning,
79 nsAString& aOutput) {
80 *aNeedsPreformatScanning = false;
81 mPrefixIndex = 0;
82 mColPos = 0;
83 mIndentOverflow = 0;
84 mIsIndentationAddedOnCurrentLine = false;
85 mInAttribute = false;
86 mAddNewlineForRootNode = false;
87 mAddSpace = false;
88 mMayIgnoreLineBreakSequence = false;
89 mBodyOnly = false;
90 mInBody = 0;
92 if (aEncoding) {
93 aEncoding->Name(mCharset);
95 mFlags = aFlags;
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');
105 } else {
106 mLineBreak.AssignLiteral(NS_LINEBREAK); // Platform/default
109 mDoRaw = !!(mFlags & nsIDocumentEncoder::OutputRaw);
111 mDoFormat = (mFlags & nsIDocumentEncoder::OutputFormatted && !mDoRaw);
113 mDoWrap = (mFlags & nsIDocumentEncoder::OutputWrap && !mDoRaw);
115 mAllowLineBreaking =
116 !(mFlags & nsIDocumentEncoder::OutputDisallowLineBreaking);
118 if (!aWrapColumn) {
119 mMaxColumn = 72;
120 } else {
121 mMaxColumn = aWrapColumn;
124 mOutput = &aOutput;
125 mPreLevel = 0;
126 mIsIndentationAddedOnCurrentLine = false;
127 return NS_OK;
130 nsresult nsXMLContentSerializer::AppendTextData(nsIContent* aNode,
131 int32_t aStartOffset,
132 int32_t aEndOffset,
133 nsAString& aStr,
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();
142 int32_t endoffset =
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!");
150 if (length <= 0) {
151 // XXX Zero is a legal value, maybe non-zero values should be an
152 // error.
153 return NS_OK;
156 if (frag->Is2b()) {
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);
162 } else {
163 NS_ENSURE_TRUE(aStr.Append(Substring(strStart, strStart + length),
164 mozilla::fallible),
165 NS_ERROR_OUT_OF_MEMORY);
167 } else {
168 nsAutoString utf16;
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);
176 } else {
177 NS_ENSURE_TRUE(aStr.Append(utf16, mozilla::fallible),
178 NS_ERROR_OUT_OF_MEMORY);
182 return NS_OK;
185 NS_IMETHODIMP
186 nsXMLContentSerializer::AppendText(nsIContent* aText, int32_t aStartOffset,
187 int32_t aEndOffset) {
188 NS_ENSURE_ARG(aText);
189 NS_ENSURE_STATE(mOutput);
191 nsAutoString data;
192 nsresult rv;
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);
206 } else {
207 NS_ENSURE_TRUE(AppendToStringConvertLF(data, *mOutput),
208 NS_ERROR_OUT_OF_MEMORY);
211 return NS_OK;
214 NS_IMETHODIMP
215 nsXMLContentSerializer::AppendCDATASection(nsIContent* aCDATASection,
216 int32_t aStartOffset,
217 int32_t aEndOffset) {
218 NS_ENSURE_ARG(aCDATASection);
219 NS_ENSURE_STATE(mOutput);
221 nsresult rv;
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);
233 } else {
234 NS_ENSURE_TRUE(AppendToString(cdata, *mOutput), NS_ERROR_OUT_OF_MEMORY);
237 nsAutoString data;
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);
246 return NS_OK;
249 NS_IMETHODIMP
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);
261 aPI->GetData(data);
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) {
271 if (mAddSpace) {
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);
279 } else {
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);
293 return NS_OK;
296 NS_IMETHODIMP
297 nsXMLContentSerializer::AppendComment(Comment* aComment, int32_t aStartOffset,
298 int32_t aEndOffset) {
299 NS_ENSURE_STATE(mOutput);
301 nsAutoString data;
302 aComment->GetData(data);
304 int32_t dataLength = data.Length();
305 if (aStartOffset || (aEndOffset != -1 && aEndOffset < dataLength)) {
306 int32_t length =
307 (aEndOffset == -1) ? dataLength : std::min(aEndOffset, dataLength);
308 length -= aStartOffset;
310 nsAutoString frag;
311 if (length > 0) {
312 data.Mid(frag, aStartOffset, length);
314 data.Assign(frag);
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) {
325 if (mAddSpace) {
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);
333 } else {
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);
346 return NS_OK;
349 NS_IMETHODIMP
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);
364 char16_t quote;
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('"');
370 } else {
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('"');
382 } else {
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('"');
393 } else {
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);
407 return NS_OK;
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
419 // we pop the stack
420 decl->mOwner = aOwner;
421 return NS_OK;
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) {
430 break;
432 mNameSpaceStack.RemoveLastElement();
436 bool nsXMLContentSerializer::ConfirmPrefix(nsAString& aPrefix,
437 const nsAString& aURI,
438 nsIContent* aElement,
439 bool aIsAttribute) {
440 if (aPrefix.EqualsLiteral(kXMLNS)) {
441 return false;
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");
449 return false;
452 bool mustHavePrefix;
453 if (aIsAttribute) {
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
457 aPrefix.Truncate();
458 return false;
461 // Attribute not in the null namespace -- must have a prefix
462 mustHavePrefix = true;
463 } else {
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;
481 while (index >= 0) {
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
486 // use aPrefix
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.
490 uriMatch = true;
491 closestURIMatch = aPrefix;
492 break;
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
506 // aElement.
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 "
511 "namespace.");
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.
518 index = count - 1;
519 haveSeenOurPrefix = false;
520 continue;
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;
529 int32_t index2;
530 for (index2 = count - 1; index2 > index && prefixOK; --index2) {
531 prefixOK = (mNameSpaceStack[index2].mPrefix != decl.mPrefix);
534 if (prefixOK) {
535 uriMatch = true;
536 closestURIMatch.Assign(decl.mPrefix);
540 --index;
543 // At this point the following invariants hold:
544 // 1) The prefix in closestURIMatch is mapped to aURI in our scope if
545 // uriMatch is set.
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);
555 return false;
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
562 // OK.
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()) {
573 return false;
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
579 return true;
582 void nsXMLContentSerializer::GenerateNewPrefix(nsAString& aPrefix) {
583 aPrefix.Assign('a');
584 aPrefix.AppendInt(mPrefixIndex++);
587 bool nsXMLContentSerializer::SerializeAttr(const nsAString& aPrefix,
588 const nsAString& aName,
589 const nsAString& aValue,
590 nsAString& aStr,
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) {
596 return true;
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);
617 mInAttribute = true;
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);
623 if (rawAppend) {
624 return true;
626 } else {
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) {
643 break;
645 } else if (*iCurr == char16_t('"')) {
646 bIncludesDouble = true;
647 if (bIncludesSingle) {
648 break;
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
658 // TRUE TRUE " TRUE
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);
664 NS_ENSURE_TRUE(
665 sValue.ReplaceSubstring(u"&"_ns, u"&amp;"_ns, mozilla::fallible),
666 false);
667 if (bIncludesDouble && bIncludesSingle) {
668 NS_ENSURE_TRUE(
669 sValue.ReplaceSubstring(u"\""_ns, u"&quot;"_ns, mozilla::fallible),
670 false);
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);
686 } else {
687 NS_ENSURE_TRUE(AppendToStringConvertLF(attrString, aStr), false);
690 return true;
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.
729 skipAttr = index;
730 } else {
731 // Default NS attribute does not have prefix (and the name is "xmlns")
732 PushNameSpaceDecl(u""_ns, uriStr, aOriginalElement);
734 } else {
735 PushNameSpaceDecl(nsDependentAtomString(attrName), uriStr,
736 aOriginalElement);
740 return skipAttr;
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))
760 return true;
761 else
762 return false;
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
781 if (aAddNSAttr) {
782 if (aTagPrefix.IsEmpty()) {
783 // Serialize default namespace decl
784 NS_ENSURE_TRUE(
785 SerializeAttr(u""_ns, xmlnsStr, aTagNamespaceURI, aStr, true), false);
786 } else {
787 // Serialize namespace decl
788 NS_ENSURE_TRUE(
789 SerializeAttr(xmlnsStr, aTagPrefix, aTagNamespaceURI, aStr, true),
790 false);
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
799 // attribute URIs.
800 for (index = 0; index < count; index++) {
801 if (aSkipAttr == index) {
802 continue;
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)) {
814 continue;
817 if (attrPrefix) {
818 attrPrefix->ToString(prefixStr);
819 } else {
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),
835 false);
837 if (addNSAttr) {
838 NS_ASSERTION(!prefixStr.IsEmpty(),
839 "Namespaced attributes must have a prefix");
840 NS_ENSURE_TRUE(SerializeAttr(xmlnsStr, prefixStr, uriStr, aStr, true),
841 false);
842 PushNameSpaceDecl(prefixStr, uriStr, aOriginalElement);
846 return true;
849 NS_IMETHODIMP
850 nsXMLContentSerializer::AppendElementStart(Element* aElement,
851 Element* aOriginalElement) {
852 NS_ENSURE_ARG(aElement);
853 NS_ENSURE_STATE(mOutput);
855 bool forceFormat = false;
856 nsresult rv = NS_OK;
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);
862 return rv;
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);
872 uint32_t skipAttr =
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);
882 } else {
883 NS_ENSURE_TRUE(MaybeAddNewlineForRootNode(*mOutput),
884 NS_ERROR_OUT_OF_MEMORY);
886 if (!mColPos) {
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);
891 mAddSpace = false;
893 } else if (mAddSpace) {
894 NS_ENSURE_TRUE(AppendToString(char16_t(' '), *mOutput),
895 NS_ERROR_OUT_OF_MEMORY);
896 mAddSpace = false;
897 } else {
898 NS_ENSURE_TRUE(MaybeAddNewlineForRootNode(*mOutput),
899 NS_ERROR_OUT_OF_MEMORY);
902 // Always reset to avoid false newlines in case MaybeAddNewlineForRootNode
903 // wasn't called
904 mAddNewlineForRootNode = false;
906 bool addNSAttr;
907 addNSAttr =
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);
925 NS_ENSURE_TRUE(
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);
941 return NS_OK;
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
951 // might have kids.
952 return true;
955 if (!aElement->IsHTMLElement()) {
956 // Empty non-HTML elements can just skip a separate end tag.
957 return false;
960 // HTML container tags should have a separate end tag even if empty, per spec.
961 // See
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,
971 nsAString& aStr) {
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)) {
981 return false;
985 return AppendToString(u"/>"_ns, aStr);
988 NS_IMETHODIMP
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;
997 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);
1012 return NS_OK;
1015 nsAutoString tagPrefix, tagLocalName, tagNamespaceURI;
1017 aElement->NodeInfo()->GetPrefix(tagPrefix);
1018 aElement->NodeInfo()->GetName(tagLocalName);
1019 aElement->NodeInfo()->GetNamespaceURI(tagNamespaceURI);
1021 #ifdef DEBUG
1022 bool debugNeedToPushNamespace =
1023 #endif
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);
1035 if (!mColPos) {
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);
1040 mAddSpace = false;
1042 } else if (mAddSpace) {
1043 NS_ENSURE_TRUE(AppendToString(char16_t(' '), *mOutput),
1044 NS_ERROR_OUT_OF_MEMORY);
1045 mAddSpace = false;
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);
1066 } else {
1067 MaybeFlagNewlineForRootNode(aElement);
1070 AfterElementEnd(content, *mOutput);
1072 return NS_OK;
1075 NS_IMETHODIMP
1076 nsXMLContentSerializer::Finish() {
1077 NS_ENSURE_STATE(mOutput);
1079 mOutput = nullptr;
1081 return NS_OK;
1084 NS_IMETHODIMP
1085 nsXMLContentSerializer::GetOutputLength(uint32_t& aLength) const {
1086 NS_ENSURE_STATE(mOutput);
1088 aLength = mOutput->Length();
1090 return NS_OK;
1093 NS_IMETHODIMP
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()) {
1109 *mOutput +=
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.
1114 #ifdef DEBUG
1115 else {
1116 NS_WARNING("Empty mCharset? How come?");
1118 #endif
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;
1128 return NS_OK;
1131 bool nsXMLContentSerializer::CheckElementStart(Element*, bool& aForceFormat,
1132 nsAString& aStr,
1133 nsresult& aResult) {
1134 aResult = NS_OK;
1135 aForceFormat = false;
1136 return true;
1139 bool nsXMLContentSerializer::CheckElementEnd(Element* aElement,
1140 Element* aOriginalElement,
1141 bool& aForceFormat,
1142 nsAString& aStr) {
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) {
1151 return true;
1153 mColPos += 1;
1154 return aOutputStr.Append(aChar, mozilla::fallible);
1157 bool nsXMLContentSerializer::AppendToString(const nsAString& aStr,
1158 nsAString& aOutputStr) {
1159 if (mBodyOnly && !mInBody) {
1160 return true;
1162 mColPos += aStr.Length();
1163 return aOutputStr.Append(aStr, mozilla::fallible);
1166 #define _ 0
1168 // This table indexes into kEntityStrings[].
1169 const uint8_t nsXMLContentSerializer::kEntities[] = {
1170 // clang-format off
1171 _, _, _, _, _, _, _, _, _, _,
1172 _, _, _, _, _, _, _, _, _, _,
1173 _, _, _, _, _, _, _, _, _, _,
1174 _, _, _, _, _, _, _, _, 2, _,
1175 _, _, _, _, _, _, _, _, _, _,
1176 _, _, _, _, _, _, _, _, _, _,
1177 3, _, 4
1178 // clang-format on
1181 // This table indexes into kEntityStrings[].
1182 const uint8_t nsXMLContentSerializer::kAttrEntities[] = {
1183 // clang-format off
1184 _, _, _, _, _, _, _, _, _, 5,
1185 6, _, _, 7, _, _, _, _, _, _,
1186 _, _, _, _, _, _, _, _, _, _,
1187 _, _, _, _, 1, _, _, _, 2, _,
1188 _, _, _, _, _, _, _, _, _, _,
1189 _, _, _, _, _, _, _, _, _, _,
1190 3, _, 4
1191 // clang-format on
1194 #undef _
1196 const char* const nsXMLContentSerializer::kEntityStrings[] = {
1197 /* 0 */ nullptr,
1198 /* 1 */ "&quot;",
1199 /* 2 */ "&amp;",
1200 /* 3 */ "&lt;",
1201 /* 4 */ "&gt;",
1202 /* 5 */ "&#9;",
1203 /* 6 */ "&#xA;",
1204 /* 7 */ "&#xD;",
1207 bool nsXMLContentSerializer::AppendAndTranslateEntities(const nsAString& aStr,
1208 nsAString& aOutputStr) {
1209 if (mInAttribute) {
1210 return AppendAndTranslateEntities<kGTVal>(aStr, aOutputStr, kAttrEntities,
1211 kEntityStrings);
1214 return AppendAndTranslateEntities<kGTVal>(aStr, aOutputStr, kEntities,
1215 kEntityStrings);
1218 /* static */
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;
1237 advanceLength = 0;
1238 // for each character in this chunk, check if it
1239 // needs to be replaced
1240 for (; c < fragmentEnd; c++, advanceLength++) {
1241 char16_t val = *c;
1242 if ((val <= aMaxTableIndex) && aEntityTable[val]) {
1243 entityText = aStringTable[aEntityTable[val]];
1244 break;
1248 NS_ENSURE_TRUE(
1249 aOutputStr.Append(fragmentStart, advanceLength, mozilla::fallible),
1250 false);
1251 if (entityText) {
1252 NS_ENSURE_TRUE(AppendASCIItoUTF16(mozilla::MakeStringSpan(entityText),
1253 aOutputStr, mozilla::fallible),
1254 false);
1255 advanceLength++;
1259 return true;
1262 bool nsXMLContentSerializer::MaybeAddNewlineForRootNode(nsAString& aStr) {
1263 if (mAddNewlineForRootNode) {
1264 return AppendNewLineToString(aStr);
1267 return true;
1270 void nsXMLContentSerializer::MaybeFlagNewlineForRootNode(nsINode* aNode) {
1271 nsINode* parent = aNode->GetParentNode();
1272 if (parent) {
1273 mAddNewlineForRootNode = parent->IsDocument();
1277 void nsXMLContentSerializer::MaybeEnterInPreContent(nsIContent* aNode) {
1278 // support of the xml:space attribute
1279 nsAutoString space;
1280 if (ShouldMaintainPreLevel() && aNode->IsElement() &&
1281 aNode->AsElement()->GetAttr(kNameSpaceID_XML, nsGkAtoms::space, space) &&
1282 space.EqualsLiteral("preserve")) {
1283 ++PreLevel();
1287 void nsXMLContentSerializer::MaybeLeaveFromPreContent(nsIContent* aNode) {
1288 // support of the xml:space attribute
1289 nsAutoString space;
1290 if (ShouldMaintainPreLevel() && aNode->IsElement() &&
1291 aNode->AsElement()->GetAttr(kNameSpaceID_XML, nsGkAtoms::space, space) &&
1292 space.EqualsLiteral("preserve")) {
1293 --PreLevel();
1297 bool nsXMLContentSerializer::AppendNewLineToString(nsAString& aStr) {
1298 bool result = AppendToString(mLineBreak, aStr);
1299 mMayIgnoreLineBreakSequence = true;
1300 mColPos = 0;
1301 mAddSpace = false;
1302 mIsIndentationAddedOnCurrentLine = false;
1303 return result;
1306 bool nsXMLContentSerializer::AppendIndentation(nsAString& aStr) {
1307 mIsIndentationAddedOnCurrentLine = true;
1308 bool result = AppendToString(mIndent, aStr);
1309 mAddSpace = false;
1310 mMayIgnoreLineBreakSequence = false;
1311 return result;
1314 bool nsXMLContentSerializer::IncrIndentation(nsAtom* aName) {
1315 // we want to keep the source readable
1316 if (mDoWrap &&
1317 mIndent.Length() >= uint32_t(mMaxColumn) - MIN_INDENTED_LINE_LENGTH) {
1318 ++mIndentOverflow;
1319 } else {
1320 return mIndent.AppendLiteral(INDENT_STRING, mozilla::fallible);
1323 return true;
1326 void nsXMLContentSerializer::DecrIndentation(nsAtom* aName) {
1327 if (mIndentOverflow)
1328 --mIndentOverflow;
1329 else
1330 mIndent.Cut(0, INDENT_STRING_LENGTH);
1333 bool nsXMLContentSerializer::LineBreakBeforeOpen(int32_t aNamespaceID,
1334 nsAtom* aName) {
1335 return mAddSpace;
1338 bool nsXMLContentSerializer::LineBreakAfterOpen(int32_t aNamespaceID,
1339 nsAtom* aName) {
1340 return false;
1343 bool nsXMLContentSerializer::LineBreakBeforeClose(int32_t aNamespaceID,
1344 nsAtom* aName) {
1345 return mAddSpace;
1348 bool nsXMLContentSerializer::LineBreakAfterClose(int32_t aNamespaceID,
1349 nsAtom* aName) {
1350 return false;
1353 bool nsXMLContentSerializer::AppendToStringConvertLF(const nsAString& aStr,
1354 nsAString& aOutputStr) {
1355 if (mBodyOnly && !mInBody) {
1356 return true;
1359 if (mDoRaw) {
1360 NS_ENSURE_TRUE(AppendToString(aStr, aOutputStr), false);
1361 } else {
1362 // Convert line-endings to mLineBreak
1363 uint32_t start = 0;
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);
1370 start = theLen;
1371 // if there was a line break before this substring
1372 // AppendNewLineToString was called, so we should reverse
1373 // this flag
1374 mMayIgnoreLineBreakSequence = false;
1375 } else {
1376 nsDependentSubstring dataSubstring(aStr, start, eol - start);
1377 NS_ENSURE_TRUE(AppendToString(dataSubstring, aOutputStr), false);
1378 NS_ENSURE_TRUE(AppendNewLineToString(aOutputStr), false);
1379 start = eol + 1;
1384 return true;
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;
1401 do {
1402 switch (*aPos) {
1403 case ' ':
1404 case '\t':
1405 sawBlankOrTab = true;
1406 [[fallthrough]];
1407 case '\n':
1408 ++aPos;
1409 // do not increase mColPos,
1410 // because we will reduce the whitespace to a single char
1411 break;
1412 default:
1413 leaveLoop = true;
1414 break;
1416 } while (!leaveLoop && aPos < aEnd);
1418 if (mAddSpace) {
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) {
1427 // nothing to do
1428 aMayIgnoreStartOfLineWhitespaceSequence = false;
1429 } else {
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);
1435 mColPos = 0;
1436 mIsIndentationAddedOnCurrentLine = false;
1437 mMayIgnoreLineBreakSequence = true;
1438 NS_ENSURE_TRUE(result, false);
1439 } else {
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
1442 mAddSpace = true;
1443 ++mColPos; // eat a slot of available space
1445 } else {
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);
1454 return true;
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;
1477 do {
1478 if (mColPos) {
1479 colPos = mColPos;
1480 } else {
1481 if (mDoFormat && !mDoRaw && !PreLevel() &&
1482 !onceAgainBecauseWeAddedBreakInFront) {
1483 colPos = mIndent.Length();
1484 } else
1485 colPos = 0;
1487 foundWhitespaceInLoop = false;
1488 length = 0;
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.
1492 do {
1493 if (*aPos == ' ' || *aPos == '\t' || *aPos == '\n') {
1494 foundWhitespaceInLoop = true;
1495 break;
1498 ++aPos;
1499 ++length;
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
1505 // etc.
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);
1516 mAddSpace = false;
1517 NS_ENSURE_TRUE(result, false);
1520 mColPos += length;
1521 NS_ENSURE_TRUE(aOutputStr.Append(aSequenceStart, aPos - aSequenceStart,
1522 mozilla::fallible),
1523 false);
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;
1544 } else {
1545 // we must wrap
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);
1558 while (true) {
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) {
1577 break;
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
1588 // below.
1589 if (*nextWrapPosition < subSeq.Length()) {
1590 wrapPosition = nextWrapPosition;
1595 if (wrapPosition) {
1596 if (!mColPos && mDoFormat) {
1597 NS_ENSURE_TRUE(AppendIndentation(aOutputStr), false);
1598 } else if (mAddSpace) {
1599 bool result = aOutputStr.Append(char16_t(' '), mozilla::fallible);
1600 mAddSpace = false;
1601 NS_ENSURE_TRUE(result, false);
1603 NS_ENSURE_TRUE(aOutputStr.Append(aSequenceStart, *wrapPosition,
1604 mozilla::fallible),
1605 false);
1607 NS_ENSURE_TRUE(AppendNewLineToString(aOutputStr), false);
1608 aPos = aSequenceStart + *wrapPosition;
1609 aMayIgnoreStartOfLineWhitespaceSequence = true;
1610 } else {
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;
1621 // }
1623 // we update the mColPos variable with the length of
1624 // the part already parsed.
1625 mColPos += length;
1627 // now try to find the next whitespace
1628 do {
1629 if (*aPos == ' ' || *aPos == '\t' || *aPos == '\n') {
1630 break;
1633 ++aPos;
1634 ++mColPos;
1635 } while (aPos < aEnd);
1637 if (mAddSpace) {
1638 bool result = aOutputStr.Append(char16_t(' '), mozilla::fallible);
1639 mAddSpace = false;
1640 NS_ENSURE_TRUE(result, false);
1642 NS_ENSURE_TRUE(
1643 aOutputStr.Append(aSequenceStart, aPos - aSequenceStart,
1644 mozilla::fallible),
1645 false);
1648 aSequenceStartAfterAWhiteSpace = false;
1650 } while (onceAgainBecauseWeAddedBreakInFront);
1652 return true;
1655 bool nsXMLContentSerializer::AppendToStringFormatedWrapped(
1656 const nsAString& aStr, nsAString& aOutputStr) {
1657 if (mBodyOnly && !mInBody) {
1658 return true;
1661 nsAString::const_char_iterator pos, end, sequenceStart;
1663 aStr.BeginReading(pos);
1664 aStr.EndReading(end);
1666 bool sequenceStartAfterAWhitespace = false;
1667 if (pos < end) {
1668 nsAString::const_char_iterator end2;
1669 aOutputStr.EndReading(end2);
1670 --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 =
1679 (!mColPos ||
1680 (mIsIndentationAddedOnCurrentLine && sequenceStartAfterAWhitespace &&
1681 uint32_t(mColPos) == mIndent.Length()));
1683 while (pos < end) {
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),
1691 false);
1692 } else { // any other non-whitespace char
1693 NS_ENSURE_TRUE(
1694 AppendWrapped_NonWhitespaceSequence(
1695 pos, end, sequenceStart, mayIgnoreStartOfLineWhitespaceSequence,
1696 sequenceStartAfterAWhitespace, aOutputStr),
1697 false);
1701 return true;
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.
1712 mAddSpace = false;
1713 mIsIndentationAddedOnCurrentLine = false;
1715 bool leaveLoop = false;
1716 nsAString::const_char_iterator lastPos = aPos;
1718 do {
1719 switch (*aPos) {
1720 case ' ':
1721 case '\t':
1722 // if there are too many spaces on a line, we wrap
1723 if (mColPos >= mMaxColumn) {
1724 if (lastPos != aPos) {
1725 NS_ENSURE_TRUE(
1726 aOutputStr.Append(lastPos, aPos - lastPos, mozilla::fallible),
1727 false);
1729 NS_ENSURE_TRUE(AppendToString(mLineBreak, aOutputStr), false);
1730 mColPos = 0;
1731 lastPos = aPos;
1734 ++mColPos;
1735 ++aPos;
1736 break;
1737 case '\n':
1738 if (lastPos != aPos) {
1739 NS_ENSURE_TRUE(
1740 aOutputStr.Append(lastPos, aPos - lastPos, mozilla::fallible),
1741 false);
1743 NS_ENSURE_TRUE(AppendToString(mLineBreak, aOutputStr), false);
1744 mColPos = 0;
1745 ++aPos;
1746 lastPos = aPos;
1747 break;
1748 default:
1749 leaveLoop = true;
1750 break;
1752 } while (!leaveLoop && aPos < aEnd);
1754 if (lastPos != aPos) {
1755 NS_ENSURE_TRUE(
1756 aOutputStr.Append(lastPos, aPos - lastPos, mozilla::fallible), false);
1759 return true;
1762 bool nsXMLContentSerializer::AppendToStringWrapped(const nsAString& aStr,
1763 nsAString& aOutputStr) {
1764 if (mBodyOnly && !mInBody) {
1765 return true;
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);
1781 --end2;
1782 if (*end2 == ' ' || *end2 == '\n' || *end2 == '\t') {
1783 sequenceStartAfterAWhitespace = true;
1787 while (pos < end) {
1788 sequenceStart = pos;
1790 // if beginning of a whitespace sequence
1791 if (*pos == ' ' || *pos == '\n' || *pos == '\t') {
1792 sequenceStartAfterAWhitespace = true;
1793 NS_ENSURE_TRUE(
1794 AppendWrapped_WhitespaceSequence(pos, end, sequenceStart, aOutputStr),
1795 false);
1796 } else { // any other non-whitespace char
1797 NS_ENSURE_TRUE(
1798 AppendWrapped_NonWhitespaceSequence(
1799 pos, end, sequenceStart, mayIgnoreStartOfLineWhitespaceSequence,
1800 sequenceStartAfterAWhitespace, aOutputStr),
1801 false);
1805 return true;
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,
1814 nsAString& aStr) {
1815 CustomElementData* ceData = aElement->GetCustomElementData();
1816 if (ceData) {
1817 nsAtom* isAttr = ceData->GetIs(aElement);
1818 if (isAttr && !aElement->HasAttr(nsGkAtoms::is)) {
1819 NS_ENSURE_TRUE(aStr.AppendLiteral(" is=\"", mozilla::fallible), false);
1820 NS_ENSURE_TRUE(
1821 aStr.Append(nsDependentAtomString(isAttr), mozilla::fallible), false);
1822 NS_ENSURE_TRUE(aStr.AppendLiteral("\"", mozilla::fallible), false);
1826 return true;