1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 /*
8 * nsIContentSerializer implementation that can be used with an
9 * nsIDocumentEncoder to convert an XML DOM to an XML string that
10 * could be parsed into more or less the original DOM.
11 */
12
13 #include "nsXMLContentSerializer.h"
14
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/LineBreaker.h"
35 #include "nsParserConstants.h"
36 #include "mozilla/Encoding.h"
37
38 using namespace mozilla;
39 using namespace mozilla::dom;
40
41 #define kXMLNS "xmlns"
42
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
47
48 // the string used to indent.
49 #define INDENT_STRING " "
50 #define INDENT_STRING_LENGTH 2
51
NS_NewXMLContentSerializer(nsIContentSerializer ** aSerializer)52 nsresult NS_NewXMLContentSerializer(nsIContentSerializer** aSerializer) {
53 RefPtr<nsXMLContentSerializer> it = new nsXMLContentSerializer();
54 it.forget(aSerializer);
55 return NS_OK;
56 }
57
nsXMLContentSerializer()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) {}
69
70 nsXMLContentSerializer::~nsXMLContentSerializer() = default;
71
NS_IMPL_ISUPPORTS(nsXMLContentSerializer,nsIContentSerializer)72 NS_IMPL_ISUPPORTS(nsXMLContentSerializer, nsIContentSerializer)
73
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;
91
92 if (aEncoding) {
93 aEncoding->Name(mCharset);
94 }
95 mFlags = aFlags;
96
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
107 }
108
109 mDoRaw = !!(mFlags & nsIDocumentEncoder::OutputRaw);
110
111 mDoFormat = (mFlags & nsIDocumentEncoder::OutputFormatted && !mDoRaw);
112
113 mDoWrap = (mFlags & nsIDocumentEncoder::OutputWrap && !mDoRaw);
114
115 mAllowLineBreaking =
116 !(mFlags & nsIDocumentEncoder::OutputDisallowLineBreaking);
117
118 if (!aWrapColumn) {
119 mMaxColumn = 72;
120 } else {
121 mMaxColumn = aWrapColumn;
122 }
123
124 mOutput = &aOutput;
125 mPreLevel = 0;
126 mIsIndentationAddedOnCurrentLine = false;
127 return NS_OK;
128 }
129
AppendTextData(nsIContent * aNode,int32_t aStartOffset,int32_t aEndOffset,nsAString & aStr,bool aTranslateEntities)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;
139 }
140
141 int32_t fragLength = frag->GetLength();
142 int32_t endoffset =
143 (aEndOffset == -1) ? fragLength : std::min(aEndOffset, fragLength);
144 int32_t length = endoffset - aStartOffset;
145
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!");
149
150 if (length <= 0) {
151 // XXX Zero is a legal value, maybe non-zero values should be an
152 // error.
153 return NS_OK;
154 }
155
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);
166 }
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;
172 }
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);
179 }
180 }
181
182 return NS_OK;
183 }
184
185 NS_IMETHODIMP
AppendText(nsIContent * aText,int32_t aStartOffset,int32_t aEndOffset)186 nsXMLContentSerializer::AppendText(nsIContent* aText, int32_t aStartOffset,
187 int32_t aEndOffset) {
188 NS_ENSURE_ARG(aText);
189 NS_ENSURE_STATE(mOutput);
190
191 nsAutoString data;
192 nsresult rv;
193
194 rv = AppendTextData(aText, aStartOffset, aEndOffset, data, true);
195 if (NS_FAILED(rv)) return NS_ERROR_FAILURE;
196
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);
209 }
210
211 return NS_OK;
212 }
213
214 NS_IMETHODIMP
AppendCDATASection(nsIContent * aCDATASection,int32_t aStartOffset,int32_t aEndOffset)215 nsXMLContentSerializer::AppendCDATASection(nsIContent* aCDATASection,
216 int32_t aStartOffset,
217 int32_t aEndOffset) {
218 NS_ENSURE_ARG(aCDATASection);
219 NS_ENSURE_STATE(mOutput);
220
221 nsresult rv;
222
223 constexpr auto cdata = u"<![CDATA["_ns;
224
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);
235 }
236
237 nsAutoString data;
238 rv = AppendTextData(aCDATASection, aStartOffset, aEndOffset, data, false);
239 if (NS_FAILED(rv)) return NS_ERROR_FAILURE;
240
241 NS_ENSURE_TRUE(AppendToStringConvertLF(data, *mOutput),
242 NS_ERROR_OUT_OF_MEMORY);
243
244 NS_ENSURE_TRUE(AppendToString(u"]]>"_ns, *mOutput), NS_ERROR_OUT_OF_MEMORY);
245
246 return NS_OK;
247 }
248
249 NS_IMETHODIMP
AppendProcessingInstruction(ProcessingInstruction * aPI,int32_t aStartOffset,int32_t aEndOffset)250 nsXMLContentSerializer::AppendProcessingInstruction(ProcessingInstruction* aPI,
251 int32_t aStartOffset,
252 int32_t aEndOffset) {
253 NS_ENSURE_STATE(mOutput);
254
255 nsAutoString target, data, start;
256
257 NS_ENSURE_TRUE(MaybeAddNewlineForRootNode(*mOutput), NS_ERROR_OUT_OF_MEMORY);
258
259 aPI->GetTarget(target);
260
261 aPI->GetData(data);
262
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);
267
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);
273 }
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);
281 }
282
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);
288 }
289 NS_ENSURE_TRUE(AppendToString(u"?>"_ns, *mOutput), NS_ERROR_OUT_OF_MEMORY);
290
291 MaybeFlagNewlineForRootNode(aPI);
292
293 return NS_OK;
294 }
295
296 NS_IMETHODIMP
AppendComment(Comment * aComment,int32_t aStartOffset,int32_t aEndOffset)297 nsXMLContentSerializer::AppendComment(Comment* aComment, int32_t aStartOffset,
298 int32_t aEndOffset) {
299 NS_ENSURE_STATE(mOutput);
300
301 nsAutoString data;
302 aComment->GetData(data);
303
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;
309
310 nsAutoString frag;
311 if (length > 0) {
312 data.Mid(frag, aStartOffset, length);
313 }
314 data.Assign(frag);
315 }
316
317 NS_ENSURE_TRUE(MaybeAddNewlineForRootNode(*mOutput), NS_ERROR_OUT_OF_MEMORY);
318
319 constexpr auto startComment = u"<!--"_ns;
320
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);
327 }
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);
336 }
337
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);
343
344 MaybeFlagNewlineForRootNode(aComment);
345
346 return NS_OK;
347 }
348
349 NS_IMETHODIMP
AppendDoctype(DocumentType * aDocType)350 nsXMLContentSerializer::AppendDoctype(DocumentType* aDocType) {
351 NS_ENSURE_STATE(mOutput);
352
353 nsAutoString name, publicId, systemId;
354 aDocType->GetName(name);
355 aDocType->GetPublicId(publicId);
356 aDocType->GetSystemId(systemId);
357
358 NS_ENSURE_TRUE(MaybeAddNewlineForRootNode(*mOutput), NS_ERROR_OUT_OF_MEMORY);
359
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);
363
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('\'');
372 }
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);
376
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('\'');
384 }
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);
389 }
390 } else if (!systemId.IsEmpty()) {
391 if (systemId.FindChar(char16_t('"')) == -1) {
392 quote = char16_t('"');
393 } else {
394 quote = char16_t('\'');
395 }
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);
401 }
402
403 NS_ENSURE_TRUE(AppendToString(kGreaterThan, *mOutput),
404 NS_ERROR_OUT_OF_MEMORY);
405 MaybeFlagNewlineForRootNode(aDocType);
406
407 return NS_OK;
408 }
409
PushNameSpaceDecl(const nsAString & aPrefix,const nsAString & aURI,nsIContent * aOwner)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;
415
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;
422 }
423
PopNameSpaceDeclsFor(nsIContent * aOwner)424 void nsXMLContentSerializer::PopNameSpaceDeclsFor(nsIContent* aOwner) {
425 int32_t index, count;
426
427 count = mNameSpaceStack.Length();
428 for (index = count - 1; index >= 0; index--) {
429 if (mNameSpaceStack[index].mOwner != aOwner) {
430 break;
431 }
432 mNameSpaceStack.RemoveLastElement();
433 }
434 }
435
ConfirmPrefix(nsAString & aPrefix,const nsAString & aURI,nsIContent * aElement,bool aIsAttribute)436 bool nsXMLContentSerializer::ConfirmPrefix(nsAString& aPrefix,
437 const nsAString& aURI,
438 nsIContent* aElement,
439 bool aIsAttribute) {
440 if (aPrefix.EqualsLiteral(kXMLNS)) {
441 return false;
442 }
443
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");
448
449 return false;
450 }
451
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;
459 }
460
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;
466 }
467
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;
473
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;
478
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;
493 }
494
495 haveSeenOurPrefix = true;
496
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.");
512
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;
521 }
522 }
523
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);
532 }
533
534 if (prefixOK) {
535 uriMatch = true;
536 closestURIMatch.Assign(decl.mPrefix);
537 }
538 }
539
540 --index;
541 }
542
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)
549
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;
556 }
557
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);
566 }
567
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;
574 }
575 }
576
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;
580 }
581
GenerateNewPrefix(nsAString & aPrefix)582 void nsXMLContentSerializer::GenerateNewPrefix(nsAString& aPrefix) {
583 aPrefix.Assign('a');
584 aPrefix.AppendInt(mPrefixIndex++);
585 }
586
SerializeAttr(const nsAString & aPrefix,const nsAString & aName,const nsAString & aValue,nsAString & aStr,bool aDoEscapeEntities)587 bool nsXMLContentSerializer::SerializeAttr(const nsAString& aPrefix,
588 const nsAString& aName,
589 const nsAString& aValue,
590 nsAString& aStr,
591 bool aDoEscapeEntities) {
592 nsAutoString attrString_;
593 // For innerHTML we can do faster appending without
594 // temporary strings.
595 bool rawAppend = mDoRaw && aDoEscapeEntities;
596 nsAString& attrString = (rawAppend) ? aStr : attrString_;
597
598 NS_ENSURE_TRUE(attrString.Append(char16_t(' '), mozilla::fallible), false);
599 if (!aPrefix.IsEmpty()) {
600 NS_ENSURE_TRUE(attrString.Append(aPrefix, mozilla::fallible), false);
601 NS_ENSURE_TRUE(attrString.Append(char16_t(':'), mozilla::fallible), false);
602 }
603 NS_ENSURE_TRUE(attrString.Append(aName, mozilla::fallible), false);
604
605 if (aDoEscapeEntities) {
606 // if problem characters are turned into character entity references
607 // then there will be no problem with the value delimiter characters
608 NS_ENSURE_TRUE(attrString.AppendLiteral("=\"", mozilla::fallible), false);
609
610 mInAttribute = true;
611 bool result = AppendAndTranslateEntities(aValue, attrString);
612 mInAttribute = false;
613 NS_ENSURE_TRUE(result, false);
614
615 NS_ENSURE_TRUE(attrString.Append(char16_t('"'), mozilla::fallible), false);
616 if (rawAppend) {
617 return true;
618 }
619 } else {
620 // Depending on whether the attribute value contains quotes or apostrophes
621 // we need to select the delimiter character and escape characters using
622 // character entity references, ignoring the value of aDoEscapeEntities.
623 // See http://www.w3.org/TR/REC-html40/appendix/notes.html#h-B.3.2.2 for
624 // the standard on character entity references in values. We also have to
625 // make sure to escape any '&' characters.
626
627 bool bIncludesSingle = false;
628 bool bIncludesDouble = false;
629 nsAString::const_iterator iCurr, iEnd;
630 aValue.BeginReading(iCurr);
631 aValue.EndReading(iEnd);
632 for (; iCurr != iEnd; ++iCurr) {
633 if (*iCurr == char16_t('\'')) {
634 bIncludesSingle = true;
635 if (bIncludesDouble) {
636 break;
637 }
638 } else if (*iCurr == char16_t('"')) {
639 bIncludesDouble = true;
640 if (bIncludesSingle) {
641 break;
642 }
643 }
644 }
645
646 // Delimiter and escaping is according to the following table
647 // bIncludesDouble bIncludesSingle Delimiter Escape Double Quote
648 // FALSE FALSE " FALSE
649 // FALSE TRUE " FALSE
650 // TRUE FALSE ' FALSE
651 // TRUE TRUE " TRUE
652 char16_t cDelimiter =
653 (bIncludesDouble && !bIncludesSingle) ? char16_t('\'') : char16_t('"');
654 NS_ENSURE_TRUE(attrString.Append(char16_t('='), mozilla::fallible), false);
655 NS_ENSURE_TRUE(attrString.Append(cDelimiter, mozilla::fallible), false);
656 nsAutoString sValue(aValue);
657 NS_ENSURE_TRUE(
658 sValue.ReplaceSubstring(u"&"_ns, u"&"_ns, mozilla::fallible),
659 false);
660 if (bIncludesDouble && bIncludesSingle) {
661 NS_ENSURE_TRUE(
662 sValue.ReplaceSubstring(u"\""_ns, u"""_ns, mozilla::fallible),
663 false);
664 }
665 NS_ENSURE_TRUE(attrString.Append(sValue, mozilla::fallible), false);
666 NS_ENSURE_TRUE(attrString.Append(cDelimiter, mozilla::fallible), false);
667 }
668 if (mDoRaw || PreLevel() > 0) {
669 NS_ENSURE_TRUE(AppendToStringConvertLF(attrString, aStr), false);
670 } else if (mDoFormat) {
671 NS_ENSURE_TRUE(AppendToStringFormatedWrapped(attrString, aStr), false);
672 } else if (mDoWrap) {
673 NS_ENSURE_TRUE(AppendToStringWrapped(attrString, aStr), false);
674 } else {
675 NS_ENSURE_TRUE(AppendToStringConvertLF(attrString, aStr), false);
676 }
677
678 return true;
679 }
680
ScanNamespaceDeclarations(Element * aElement,Element * aOriginalElement,const nsAString & aTagNamespaceURI)681 uint32_t nsXMLContentSerializer::ScanNamespaceDeclarations(
682 Element* aElement, Element* aOriginalElement,
683 const nsAString& aTagNamespaceURI) {
684 uint32_t index, count;
685 nsAutoString uriStr, valueStr;
686
687 count = aElement->GetAttrCount();
688
689 // First scan for namespace declarations, pushing each on the stack
690 uint32_t skipAttr = count;
691 for (index = 0; index < count; index++) {
692 const BorrowedAttrInfo info = aElement->GetAttrInfoAt(index);
693 const nsAttrName* name = info.mName;
694
695 int32_t namespaceID = name->NamespaceID();
696 nsAtom* attrName = name->LocalName();
697
698 if (namespaceID == kNameSpaceID_XMLNS ||
699 // Also push on the stack attrs named "xmlns" in the null
700 // namespace... because once we serialize those out they'll look like
701 // namespace decls. :(
702 // XXXbz what if we have both "xmlns" in the null namespace and "xmlns"
703 // in the xmlns namespace?
704 (namespaceID == kNameSpaceID_None && attrName == nsGkAtoms::xmlns)) {
705 info.mValue->ToString(uriStr);
706
707 if (!name->GetPrefix()) {
708 if (aTagNamespaceURI.IsEmpty() && !uriStr.IsEmpty()) {
709 // If the element is in no namespace we need to add a xmlns
710 // attribute to declare that. That xmlns attribute must not have a
711 // prefix (see http://www.w3.org/TR/REC-xml-names/#dt-prefix), ie it
712 // must declare the default namespace. We just found an xmlns
713 // attribute that declares the default namespace to something
714 // non-empty. We're going to ignore this attribute, for children we
715 // will detect that we need to add it again and attributes aren't
716 // affected by the default namespace.
717 skipAttr = index;
718 } else {
719 // Default NS attribute does not have prefix (and the name is "xmlns")
720 PushNameSpaceDecl(u""_ns, uriStr, aOriginalElement);
721 }
722 } else {
723 PushNameSpaceDecl(nsDependentAtomString(attrName), uriStr,
724 aOriginalElement);
725 }
726 }
727 }
728 return skipAttr;
729 }
730
IsJavaScript(nsIContent * aContent,nsAtom * aAttrNameAtom,int32_t aAttrNamespaceID,const nsAString & aValueString)731 bool nsXMLContentSerializer::IsJavaScript(nsIContent* aContent,
732 nsAtom* aAttrNameAtom,
733 int32_t aAttrNamespaceID,
734 const nsAString& aValueString) {
735 bool isHtml = aContent->IsHTMLElement();
736 bool isXul = aContent->IsXULElement();
737 bool isSvg = aContent->IsSVGElement();
738
739 if (aAttrNamespaceID == kNameSpaceID_None && (isHtml || isXul || isSvg) &&
740 (aAttrNameAtom == nsGkAtoms::href || aAttrNameAtom == nsGkAtoms::src)) {
741 static const char kJavaScript[] = "javascript";
742 int32_t pos = aValueString.FindChar(':');
743 if (pos < (int32_t)(sizeof kJavaScript - 1)) return false;
744 nsAutoString scheme(Substring(aValueString, 0, pos));
745 scheme.StripWhitespace();
746 if ((scheme.Length() == (sizeof kJavaScript - 1)) &&
747 scheme.EqualsIgnoreCase(kJavaScript))
748 return true;
749 else
750 return false;
751 }
752
753 return aContent->IsEventAttributeName(aAttrNameAtom);
754 }
755
SerializeAttributes(Element * aElement,Element * aOriginalElement,nsAString & aTagPrefix,const nsAString & aTagNamespaceURI,nsAtom * aTagName,nsAString & aStr,uint32_t aSkipAttr,bool aAddNSAttr)756 bool nsXMLContentSerializer::SerializeAttributes(
757 Element* aElement, Element* aOriginalElement, nsAString& aTagPrefix,
758 const nsAString& aTagNamespaceURI, nsAtom* aTagName, nsAString& aStr,
759 uint32_t aSkipAttr, bool aAddNSAttr) {
760 nsAutoString prefixStr, uriStr, valueStr;
761 nsAutoString xmlnsStr;
762 xmlnsStr.AssignLiteral(kXMLNS);
763 uint32_t index, count;
764
765 MaybeSerializeIsValue(aElement, aStr);
766
767 // If we had to add a new namespace declaration, serialize
768 // and push it on the namespace stack
769 if (aAddNSAttr) {
770 if (aTagPrefix.IsEmpty()) {
771 // Serialize default namespace decl
772 NS_ENSURE_TRUE(
773 SerializeAttr(u""_ns, xmlnsStr, aTagNamespaceURI, aStr, true), false);
774 } else {
775 // Serialize namespace decl
776 NS_ENSURE_TRUE(
777 SerializeAttr(xmlnsStr, aTagPrefix, aTagNamespaceURI, aStr, true),
778 false);
779 }
780 PushNameSpaceDecl(aTagPrefix, aTagNamespaceURI, aOriginalElement);
781 }
782
783 count = aElement->GetAttrCount();
784
785 // Now serialize each of the attributes
786 // XXX Unfortunately we need a namespace manager to get
787 // attribute URIs.
788 for (index = 0; index < count; index++) {
789 if (aSkipAttr == index) {
790 continue;
791 }
792
793 const nsAttrName* name = aElement->GetAttrNameAt(index);
794 int32_t namespaceID = name->NamespaceID();
795 nsAtom* attrName = name->LocalName();
796 nsAtom* attrPrefix = name->GetPrefix();
797
798 // Filter out any attribute starting with [-|_]moz
799 nsDependentAtomString attrNameStr(attrName);
800 if (StringBeginsWith(attrNameStr, u"_moz"_ns) ||
801 StringBeginsWith(attrNameStr, u"-moz"_ns)) {
802 continue;
803 }
804
805 if (attrPrefix) {
806 attrPrefix->ToString(prefixStr);
807 } else {
808 prefixStr.Truncate();
809 }
810
811 bool addNSAttr = false;
812 if (kNameSpaceID_XMLNS != namespaceID) {
813 nsContentUtils::NameSpaceManager()->GetNameSpaceURI(namespaceID, uriStr);
814 addNSAttr = ConfirmPrefix(prefixStr, uriStr, aOriginalElement, true);
815 }
816
817 aElement->GetAttr(namespaceID, attrName, valueStr);
818
819 nsDependentAtomString nameStr(attrName);
820 bool isJS = IsJavaScript(aElement, attrName, namespaceID, valueStr);
821
822 NS_ENSURE_TRUE(SerializeAttr(prefixStr, nameStr, valueStr, aStr, !isJS),
823 false);
824
825 if (addNSAttr) {
826 NS_ASSERTION(!prefixStr.IsEmpty(),
827 "Namespaced attributes must have a prefix");
828 NS_ENSURE_TRUE(SerializeAttr(xmlnsStr, prefixStr, uriStr, aStr, true),
829 false);
830 PushNameSpaceDecl(prefixStr, uriStr, aOriginalElement);
831 }
832 }
833
834 return true;
835 }
836
837 NS_IMETHODIMP
AppendElementStart(Element * aElement,Element * aOriginalElement)838 nsXMLContentSerializer::AppendElementStart(Element* aElement,
839 Element* aOriginalElement) {
840 NS_ENSURE_ARG(aElement);
841 NS_ENSURE_STATE(mOutput);
842
843 bool forceFormat = false;
844 nsresult rv = NS_OK;
845 if (!CheckElementStart(aElement, forceFormat, *mOutput, rv)) {
846 // When we go to AppendElementEnd for this element, we're going to
847 // MaybeLeaveFromPreContent(). So make sure to MaybeEnterInPreContent()
848 // now, so our PreLevel() doesn't get confused.
849 MaybeEnterInPreContent(aElement);
850 return rv;
851 }
852
853 NS_ENSURE_SUCCESS(rv, rv);
854
855 nsAutoString tagPrefix, tagLocalName, tagNamespaceURI;
856 aElement->NodeInfo()->GetPrefix(tagPrefix);
857 aElement->NodeInfo()->GetName(tagLocalName);
858 aElement->NodeInfo()->GetNamespaceURI(tagNamespaceURI);
859
860 uint32_t skipAttr =
861 ScanNamespaceDeclarations(aElement, aOriginalElement, tagNamespaceURI);
862
863 nsAtom* name = aElement->NodeInfo()->NameAtom();
864 bool lineBreakBeforeOpen =
865 LineBreakBeforeOpen(aElement->GetNameSpaceID(), name);
866
867 if ((mDoFormat || forceFormat) && !mDoRaw && !PreLevel()) {
868 if (mColPos && lineBreakBeforeOpen) {
869 NS_ENSURE_TRUE(AppendNewLineToString(*mOutput), NS_ERROR_OUT_OF_MEMORY);
870 } else {
871 NS_ENSURE_TRUE(MaybeAddNewlineForRootNode(*mOutput),
872 NS_ERROR_OUT_OF_MEMORY);
873 }
874 if (!mColPos) {
875 NS_ENSURE_TRUE(AppendIndentation(*mOutput), NS_ERROR_OUT_OF_MEMORY);
876 } else if (mAddSpace) {
877 NS_ENSURE_TRUE(AppendToString(char16_t(' '), *mOutput),
878 NS_ERROR_OUT_OF_MEMORY);
879 mAddSpace = false;
880 }
881 } else if (mAddSpace) {
882 NS_ENSURE_TRUE(AppendToString(char16_t(' '), *mOutput),
883 NS_ERROR_OUT_OF_MEMORY);
884 mAddSpace = false;
885 } else {
886 NS_ENSURE_TRUE(MaybeAddNewlineForRootNode(*mOutput),
887 NS_ERROR_OUT_OF_MEMORY);
888 }
889
890 // Always reset to avoid false newlines in case MaybeAddNewlineForRootNode
891 // wasn't called
892 mAddNewlineForRootNode = false;
893
894 bool addNSAttr;
895 addNSAttr =
896 ConfirmPrefix(tagPrefix, tagNamespaceURI, aOriginalElement, false);
897
898 // Serialize the qualified name of the element
899 NS_ENSURE_TRUE(AppendToString(kLessThan, *mOutput), NS_ERROR_OUT_OF_MEMORY);
900 if (!tagPrefix.IsEmpty()) {
901 NS_ENSURE_TRUE(AppendToString(tagPrefix, *mOutput), NS_ERROR_OUT_OF_MEMORY);
902 NS_ENSURE_TRUE(AppendToString(u":"_ns, *mOutput), NS_ERROR_OUT_OF_MEMORY);
903 }
904 NS_ENSURE_TRUE(AppendToString(tagLocalName, *mOutput),
905 NS_ERROR_OUT_OF_MEMORY);
906
907 MaybeEnterInPreContent(aElement);
908
909 if ((mDoFormat || forceFormat) && !mDoRaw && !PreLevel()) {
910 NS_ENSURE_TRUE(IncrIndentation(name), NS_ERROR_OUT_OF_MEMORY);
911 }
912
913 NS_ENSURE_TRUE(
914 SerializeAttributes(aElement, aOriginalElement, tagPrefix,
915 tagNamespaceURI, name, *mOutput, skipAttr, addNSAttr),
916 NS_ERROR_OUT_OF_MEMORY);
917
918 NS_ENSURE_TRUE(AppendEndOfElementStart(aElement, aOriginalElement, *mOutput),
919 NS_ERROR_OUT_OF_MEMORY);
920
921 if ((mDoFormat || forceFormat) && !mDoRaw && !PreLevel() &&
922 LineBreakAfterOpen(aElement->GetNameSpaceID(), name)) {
923 NS_ENSURE_TRUE(AppendNewLineToString(*mOutput), NS_ERROR_OUT_OF_MEMORY);
924 }
925
926 NS_ENSURE_TRUE(AfterElementStart(aElement, aOriginalElement, *mOutput),
927 NS_ERROR_OUT_OF_MEMORY);
928
929 return NS_OK;
930 }
931
932 // aElement is the actual element we're outputting. aOriginalElement is the one
933 // in the original DOM, which is the one we have to test for kids.
ElementNeedsSeparateEndTag(Element * aElement,Element * aOriginalElement)934 static bool ElementNeedsSeparateEndTag(Element* aElement,
935 Element* aOriginalElement) {
936 if (aOriginalElement->GetChildCount()) {
937 // We have kids, so we need a separate end tag. This needs to be checked on
938 // aOriginalElement because that's the one that's actually in the DOM and
939 // might have kids.
940 return true;
941 }
942
943 if (!aElement->IsHTMLElement()) {
944 // Empty non-HTML elements can just skip a separate end tag.
945 return false;
946 }
947
948 // HTML container tags should have a separate end tag even if empty, per spec.
949 // See
950 // https://w3c.github.io/DOM-Parsing/#dfn-concept-xml-serialization-algorithm
951 nsAtom* localName = aElement->NodeInfo()->NameAtom();
952 bool isHTMLContainer = nsHTMLElement::IsContainer(
953 nsHTMLTags::CaseSensitiveAtomTagToId(localName));
954 return isHTMLContainer;
955 }
956
AppendEndOfElementStart(Element * aElement,Element * aOriginalElement,nsAString & aStr)957 bool nsXMLContentSerializer::AppendEndOfElementStart(Element* aElement,
958 Element* aOriginalElement,
959 nsAString& aStr) {
960 if (ElementNeedsSeparateEndTag(aElement, aOriginalElement)) {
961 return AppendToString(kGreaterThan, aStr);
962 }
963
964 // We don't need a separate end tag. For HTML elements (which at this point
965 // must be non-containers), append a space before the '/', per spec. See
966 // https://w3c.github.io/DOM-Parsing/#dfn-concept-xml-serialization-algorithm
967 if (aOriginalElement->IsHTMLElement()) {
968 if (!AppendToString(kSpace, aStr)) {
969 return false;
970 }
971 }
972
973 return AppendToString(u"/>"_ns, aStr);
974 }
975
976 NS_IMETHODIMP
AppendElementEnd(Element * aElement,Element * aOriginalElement)977 nsXMLContentSerializer::AppendElementEnd(Element* aElement,
978 Element* aOriginalElement) {
979 NS_ENSURE_ARG(aElement);
980 NS_ENSURE_STATE(mOutput);
981
982 nsIContent* content = aElement;
983
984 bool forceFormat = false, outputElementEnd;
985 outputElementEnd =
986 CheckElementEnd(aElement, aOriginalElement, forceFormat, *mOutput);
987
988 nsAtom* name = content->NodeInfo()->NameAtom();
989
990 if ((mDoFormat || forceFormat) && !mDoRaw && !PreLevel()) {
991 DecrIndentation(name);
992 }
993
994 if (!outputElementEnd) {
995 // Keep this in sync with the cleanup at the end of this method.
996 PopNameSpaceDeclsFor(aElement);
997 MaybeLeaveFromPreContent(content);
998 MaybeFlagNewlineForRootNode(aElement);
999 AfterElementEnd(content, *mOutput);
1000 return NS_OK;
1001 }
1002
1003 nsAutoString tagPrefix, tagLocalName, tagNamespaceURI;
1004
1005 aElement->NodeInfo()->GetPrefix(tagPrefix);
1006 aElement->NodeInfo()->GetName(tagLocalName);
1007 aElement->NodeInfo()->GetNamespaceURI(tagNamespaceURI);
1008
1009 #ifdef DEBUG
1010 bool debugNeedToPushNamespace =
1011 #endif
1012 ConfirmPrefix(tagPrefix, tagNamespaceURI, aElement, false);
1013 NS_ASSERTION(!debugNeedToPushNamespace,
1014 "Can't push namespaces in closing tag!");
1015
1016 if ((mDoFormat || forceFormat) && !mDoRaw && !PreLevel()) {
1017 bool lineBreakBeforeClose =
1018 LineBreakBeforeClose(content->GetNameSpaceID(), name);
1019
1020 if (mColPos && lineBreakBeforeClose) {
1021 NS_ENSURE_TRUE(AppendNewLineToString(*mOutput), NS_ERROR_OUT_OF_MEMORY);
1022 }
1023 if (!mColPos) {
1024 NS_ENSURE_TRUE(AppendIndentation(*mOutput), NS_ERROR_OUT_OF_MEMORY);
1025 } else if (mAddSpace) {
1026 NS_ENSURE_TRUE(AppendToString(char16_t(' '), *mOutput),
1027 NS_ERROR_OUT_OF_MEMORY);
1028 mAddSpace = false;
1029 }
1030 } else if (mAddSpace) {
1031 NS_ENSURE_TRUE(AppendToString(char16_t(' '), *mOutput),
1032 NS_ERROR_OUT_OF_MEMORY);
1033 mAddSpace = false;
1034 }
1035
1036 NS_ENSURE_TRUE(AppendToString(kEndTag, *mOutput), NS_ERROR_OUT_OF_MEMORY);
1037 if (!tagPrefix.IsEmpty()) {
1038 NS_ENSURE_TRUE(AppendToString(tagPrefix, *mOutput), NS_ERROR_OUT_OF_MEMORY);
1039 NS_ENSURE_TRUE(AppendToString(u":"_ns, *mOutput), NS_ERROR_OUT_OF_MEMORY);
1040 }
1041 NS_ENSURE_TRUE(AppendToString(tagLocalName, *mOutput),
1042 NS_ERROR_OUT_OF_MEMORY);
1043 NS_ENSURE_TRUE(AppendToString(kGreaterThan, *mOutput),
1044 NS_ERROR_OUT_OF_MEMORY);
1045
1046 // Keep what follows in sync with the cleanup in the !outputElementEnd case.
1047 PopNameSpaceDeclsFor(aElement);
1048
1049 MaybeLeaveFromPreContent(content);
1050
1051 if ((mDoFormat || forceFormat) && !mDoRaw && !PreLevel() &&
1052 LineBreakAfterClose(content->GetNameSpaceID(), name)) {
1053 NS_ENSURE_TRUE(AppendNewLineToString(*mOutput), NS_ERROR_OUT_OF_MEMORY);
1054 } else {
1055 MaybeFlagNewlineForRootNode(aElement);
1056 }
1057
1058 AfterElementEnd(content, *mOutput);
1059
1060 return NS_OK;
1061 }
1062
1063 NS_IMETHODIMP
Finish()1064 nsXMLContentSerializer::Finish() {
1065 NS_ENSURE_STATE(mOutput);
1066
1067 mOutput = nullptr;
1068
1069 return NS_OK;
1070 }
1071
1072 NS_IMETHODIMP
GetOutputLength(uint32_t & aLength) const1073 nsXMLContentSerializer::GetOutputLength(uint32_t& aLength) const {
1074 NS_ENSURE_STATE(mOutput);
1075
1076 aLength = mOutput->Length();
1077
1078 return NS_OK;
1079 }
1080
1081 NS_IMETHODIMP
AppendDocumentStart(Document * aDocument)1082 nsXMLContentSerializer::AppendDocumentStart(Document* aDocument) {
1083 NS_ENSURE_ARG_POINTER(aDocument);
1084 NS_ENSURE_STATE(mOutput);
1085
1086 nsAutoString version, encoding, standalone;
1087 aDocument->GetXMLDeclaration(version, encoding, standalone);
1088
1089 if (version.IsEmpty())
1090 return NS_OK; // A declaration must have version, or there is no decl
1091
1092 constexpr auto endQuote = u"\""_ns;
1093
1094 *mOutput += u"<?xml version=\""_ns + version + endQuote;
1095
1096 if (!mCharset.IsEmpty()) {
1097 *mOutput +=
1098 u" encoding=\""_ns + NS_ConvertASCIItoUTF16(mCharset) + endQuote;
1099 }
1100 // Otherwise just don't output an encoding attr. Not that we expect
1101 // mCharset to ever be empty.
1102 #ifdef DEBUG
1103 else {
1104 NS_WARNING("Empty mCharset? How come?");
1105 }
1106 #endif
1107
1108 if (!standalone.IsEmpty()) {
1109 *mOutput += u" standalone=\""_ns + standalone + endQuote;
1110 }
1111
1112 NS_ENSURE_TRUE(mOutput->AppendLiteral("?>", mozilla::fallible),
1113 NS_ERROR_OUT_OF_MEMORY);
1114 mAddNewlineForRootNode = true;
1115
1116 return NS_OK;
1117 }
1118
CheckElementStart(Element *,bool & aForceFormat,nsAString & aStr,nsresult & aResult)1119 bool nsXMLContentSerializer::CheckElementStart(Element*, bool& aForceFormat,
1120 nsAString& aStr,
1121 nsresult& aResult) {
1122 aResult = NS_OK;
1123 aForceFormat = false;
1124 return true;
1125 }
1126
CheckElementEnd(Element * aElement,Element * aOriginalElement,bool & aForceFormat,nsAString & aStr)1127 bool nsXMLContentSerializer::CheckElementEnd(Element* aElement,
1128 Element* aOriginalElement,
1129 bool& aForceFormat,
1130 nsAString& aStr) {
1131 // We don't output a separate end tag for empty element
1132 aForceFormat = false;
1133 return ElementNeedsSeparateEndTag(aElement, aOriginalElement);
1134 }
1135
AppendToString(const char16_t aChar,nsAString & aOutputStr)1136 bool nsXMLContentSerializer::AppendToString(const char16_t aChar,
1137 nsAString& aOutputStr) {
1138 if (mBodyOnly && !mInBody) {
1139 return true;
1140 }
1141 mColPos += 1;
1142 return aOutputStr.Append(aChar, mozilla::fallible);
1143 }
1144
AppendToString(const nsAString & aStr,nsAString & aOutputStr)1145 bool nsXMLContentSerializer::AppendToString(const nsAString& aStr,
1146 nsAString& aOutputStr) {
1147 if (mBodyOnly && !mInBody) {
1148 return true;
1149 }
1150 mColPos += aStr.Length();
1151 return aOutputStr.Append(aStr, mozilla::fallible);
1152 }
1153
1154 #define _ 0
1155
1156 // This table indexes into kEntityStrings[].
1157 const uint8_t nsXMLContentSerializer::kEntities[] = {
1158 // clang-format off
1159 _, _, _, _, _, _, _, _, _, _,
1160 _, _, _, _, _, _, _, _, _, _,
1161 _, _, _, _, _, _, _, _, _, _,
1162 _, _, _, _, _, _, _, _, 2, _,
1163 _, _, _, _, _, _, _, _, _, _,
1164 _, _, _, _, _, _, _, _, _, _,
1165 3, _, 4
1166 // clang-format on
1167 };
1168
1169 // This table indexes into kEntityStrings[].
1170 const uint8_t nsXMLContentSerializer::kAttrEntities[] = {
1171 // clang-format off
1172 _, _, _, _, _, _, _, _, _, 5,
1173 6, _, _, 7, _, _, _, _, _, _,
1174 _, _, _, _, _, _, _, _, _, _,
1175 _, _, _, _, 1, _, _, _, 2, _,
1176 _, _, _, _, _, _, _, _, _, _,
1177 _, _, _, _, _, _, _, _, _, _,
1178 3, _, 4
1179 // clang-format on
1180 };
1181
1182 #undef _
1183
1184 const char* const nsXMLContentSerializer::kEntityStrings[] = {
1185 /* 0 */ nullptr,
1186 /* 1 */ """,
1187 /* 2 */ "&",
1188 /* 3 */ "<",
1189 /* 4 */ ">",
1190 /* 5 */ "	",
1191 /* 6 */ "
",
1192 /* 7 */ "
",
1193 };
1194
AppendAndTranslateEntities(const nsAString & aStr,nsAString & aOutputStr)1195 bool nsXMLContentSerializer::AppendAndTranslateEntities(const nsAString& aStr,
1196 nsAString& aOutputStr) {
1197 if (mInAttribute) {
1198 return AppendAndTranslateEntities<kGTVal>(aStr, aOutputStr, kAttrEntities,
1199 kEntityStrings);
1200 }
1201
1202 return AppendAndTranslateEntities<kGTVal>(aStr, aOutputStr, kEntities,
1203 kEntityStrings);
1204 }
1205
1206 /* static */
AppendAndTranslateEntities(const nsAString & aStr,nsAString & aOutputStr,const uint8_t aEntityTable[],uint16_t aMaxTableIndex,const char * const aStringTable[])1207 bool nsXMLContentSerializer::AppendAndTranslateEntities(
1208 const nsAString& aStr, nsAString& aOutputStr, const uint8_t aEntityTable[],
1209 uint16_t aMaxTableIndex, const char* const aStringTable[]) {
1210 nsReadingIterator<char16_t> done_reading;
1211 aStr.EndReading(done_reading);
1212
1213 // for each chunk of |aString|...
1214 uint32_t advanceLength = 0;
1215 nsReadingIterator<char16_t> iter;
1216
1217 for (aStr.BeginReading(iter); iter != done_reading;
1218 iter.advance(int32_t(advanceLength))) {
1219 uint32_t fragmentLength = done_reading - iter;
1220 const char16_t* c = iter.get();
1221 const char16_t* fragmentStart = c;
1222 const char16_t* fragmentEnd = c + fragmentLength;
1223 const char* entityText = nullptr;
1224
1225 advanceLength = 0;
1226 // for each character in this chunk, check if it
1227 // needs to be replaced
1228 for (; c < fragmentEnd; c++, advanceLength++) {
1229 char16_t val = *c;
1230 if ((val <= aMaxTableIndex) && aEntityTable[val]) {
1231 entityText = aStringTable[aEntityTable[val]];
1232 break;
1233 }
1234 }
1235
1236 NS_ENSURE_TRUE(
1237 aOutputStr.Append(fragmentStart, advanceLength, mozilla::fallible),
1238 false);
1239 if (entityText) {
1240 NS_ENSURE_TRUE(AppendASCIItoUTF16(mozilla::MakeStringSpan(entityText),
1241 aOutputStr, mozilla::fallible),
1242 false);
1243 advanceLength++;
1244 }
1245 }
1246
1247 return true;
1248 }
1249
MaybeAddNewlineForRootNode(nsAString & aStr)1250 bool nsXMLContentSerializer::MaybeAddNewlineForRootNode(nsAString& aStr) {
1251 if (mAddNewlineForRootNode) {
1252 return AppendNewLineToString(aStr);
1253 }
1254
1255 return true;
1256 }
1257
MaybeFlagNewlineForRootNode(nsINode * aNode)1258 void nsXMLContentSerializer::MaybeFlagNewlineForRootNode(nsINode* aNode) {
1259 nsINode* parent = aNode->GetParentNode();
1260 if (parent) {
1261 mAddNewlineForRootNode = parent->IsDocument();
1262 }
1263 }
1264
MaybeEnterInPreContent(nsIContent * aNode)1265 void nsXMLContentSerializer::MaybeEnterInPreContent(nsIContent* aNode) {
1266 // support of the xml:space attribute
1267 nsAutoString space;
1268 if (ShouldMaintainPreLevel() && aNode->IsElement() &&
1269 aNode->AsElement()->GetAttr(kNameSpaceID_XML, nsGkAtoms::space, space) &&
1270 space.EqualsLiteral("preserve")) {
1271 ++PreLevel();
1272 }
1273 }
1274
MaybeLeaveFromPreContent(nsIContent * aNode)1275 void nsXMLContentSerializer::MaybeLeaveFromPreContent(nsIContent* aNode) {
1276 // support of the xml:space attribute
1277 nsAutoString space;
1278 if (ShouldMaintainPreLevel() && aNode->IsElement() &&
1279 aNode->AsElement()->GetAttr(kNameSpaceID_XML, nsGkAtoms::space, space) &&
1280 space.EqualsLiteral("preserve")) {
1281 --PreLevel();
1282 }
1283 }
1284
AppendNewLineToString(nsAString & aStr)1285 bool nsXMLContentSerializer::AppendNewLineToString(nsAString& aStr) {
1286 bool result = AppendToString(mLineBreak, aStr);
1287 mMayIgnoreLineBreakSequence = true;
1288 mColPos = 0;
1289 mAddSpace = false;
1290 mIsIndentationAddedOnCurrentLine = false;
1291 return result;
1292 }
1293
AppendIndentation(nsAString & aStr)1294 bool nsXMLContentSerializer::AppendIndentation(nsAString& aStr) {
1295 mIsIndentationAddedOnCurrentLine = true;
1296 bool result = AppendToString(mIndent, aStr);
1297 mAddSpace = false;
1298 mMayIgnoreLineBreakSequence = false;
1299 return result;
1300 }
1301
IncrIndentation(nsAtom * aName)1302 bool nsXMLContentSerializer::IncrIndentation(nsAtom* aName) {
1303 // we want to keep the source readable
1304 if (mDoWrap &&
1305 mIndent.Length() >= uint32_t(mMaxColumn) - MIN_INDENTED_LINE_LENGTH) {
1306 ++mIndentOverflow;
1307 } else {
1308 return mIndent.AppendLiteral(INDENT_STRING, mozilla::fallible);
1309 }
1310
1311 return true;
1312 }
1313
DecrIndentation(nsAtom * aName)1314 void nsXMLContentSerializer::DecrIndentation(nsAtom* aName) {
1315 if (mIndentOverflow)
1316 --mIndentOverflow;
1317 else
1318 mIndent.Cut(0, INDENT_STRING_LENGTH);
1319 }
1320
LineBreakBeforeOpen(int32_t aNamespaceID,nsAtom * aName)1321 bool nsXMLContentSerializer::LineBreakBeforeOpen(int32_t aNamespaceID,
1322 nsAtom* aName) {
1323 return mAddSpace;
1324 }
1325
LineBreakAfterOpen(int32_t aNamespaceID,nsAtom * aName)1326 bool nsXMLContentSerializer::LineBreakAfterOpen(int32_t aNamespaceID,
1327 nsAtom* aName) {
1328 return false;
1329 }
1330
LineBreakBeforeClose(int32_t aNamespaceID,nsAtom * aName)1331 bool nsXMLContentSerializer::LineBreakBeforeClose(int32_t aNamespaceID,
1332 nsAtom* aName) {
1333 return mAddSpace;
1334 }
1335
LineBreakAfterClose(int32_t aNamespaceID,nsAtom * aName)1336 bool nsXMLContentSerializer::LineBreakAfterClose(int32_t aNamespaceID,
1337 nsAtom* aName) {
1338 return false;
1339 }
1340
AppendToStringConvertLF(const nsAString & aStr,nsAString & aOutputStr)1341 bool nsXMLContentSerializer::AppendToStringConvertLF(const nsAString& aStr,
1342 nsAString& aOutputStr) {
1343 if (mBodyOnly && !mInBody) {
1344 return true;
1345 }
1346
1347 if (mDoRaw) {
1348 NS_ENSURE_TRUE(AppendToString(aStr, aOutputStr), false);
1349 } else {
1350 // Convert line-endings to mLineBreak
1351 uint32_t start = 0;
1352 uint32_t theLen = aStr.Length();
1353 while (start < theLen) {
1354 int32_t eol = aStr.FindChar('\n', start);
1355 if (eol == kNotFound) {
1356 nsDependentSubstring dataSubstring(aStr, start, theLen - start);
1357 NS_ENSURE_TRUE(AppendToString(dataSubstring, aOutputStr), false);
1358 start = theLen;
1359 // if there was a line break before this substring
1360 // AppendNewLineToString was called, so we should reverse
1361 // this flag
1362 mMayIgnoreLineBreakSequence = false;
1363 } else {
1364 nsDependentSubstring dataSubstring(aStr, start, eol - start);
1365 NS_ENSURE_TRUE(AppendToString(dataSubstring, aOutputStr), false);
1366 NS_ENSURE_TRUE(AppendNewLineToString(aOutputStr), false);
1367 start = eol + 1;
1368 }
1369 }
1370 }
1371
1372 return true;
1373 }
1374
AppendFormatedWrapped_WhitespaceSequence(nsAString::const_char_iterator & aPos,const nsAString::const_char_iterator aEnd,const nsAString::const_char_iterator aSequenceStart,bool & aMayIgnoreStartOfLineWhitespaceSequence,nsAString & aOutputStr)1375 bool nsXMLContentSerializer::AppendFormatedWrapped_WhitespaceSequence(
1376 nsAString::const_char_iterator& aPos,
1377 const nsAString::const_char_iterator aEnd,
1378 const nsAString::const_char_iterator aSequenceStart,
1379 bool& aMayIgnoreStartOfLineWhitespaceSequence, nsAString& aOutputStr) {
1380 // Handle the complete sequence of whitespace.
1381 // Continue to iterate until we find the first non-whitespace char.
1382 // Updates "aPos" to point to the first unhandled char.
1383 // Also updates the aMayIgnoreStartOfLineWhitespaceSequence flag,
1384 // as well as the other "global" state flags.
1385
1386 bool sawBlankOrTab = false;
1387 bool leaveLoop = false;
1388
1389 do {
1390 switch (*aPos) {
1391 case ' ':
1392 case '\t':
1393 sawBlankOrTab = true;
1394 [[fallthrough]];
1395 case '\n':
1396 ++aPos;
1397 // do not increase mColPos,
1398 // because we will reduce the whitespace to a single char
1399 break;
1400 default:
1401 leaveLoop = true;
1402 break;
1403 }
1404 } while (!leaveLoop && aPos < aEnd);
1405
1406 if (mAddSpace) {
1407 // if we had previously been asked to add space,
1408 // our situation has not changed
1409 } else if (!sawBlankOrTab && mMayIgnoreLineBreakSequence) {
1410 // nothing to do in the case where line breaks have already been added
1411 // before the call of AppendToStringWrapped
1412 // and only if we found line break in the sequence
1413 mMayIgnoreLineBreakSequence = false;
1414 } else if (aMayIgnoreStartOfLineWhitespaceSequence) {
1415 // nothing to do
1416 aMayIgnoreStartOfLineWhitespaceSequence = false;
1417 } else {
1418 if (sawBlankOrTab) {
1419 if (mDoWrap && mColPos + 1 >= mMaxColumn) {
1420 // no much sense in delaying, we only have one slot left,
1421 // let's write a break now
1422 bool result = aOutputStr.Append(mLineBreak, mozilla::fallible);
1423 mColPos = 0;
1424 mIsIndentationAddedOnCurrentLine = false;
1425 mMayIgnoreLineBreakSequence = true;
1426 NS_ENSURE_TRUE(result, false);
1427 } else {
1428 // do not write out yet, we may write out either a space or a linebreak
1429 // let's delay writing it out until we know more
1430 mAddSpace = true;
1431 ++mColPos; // eat a slot of available space
1432 }
1433 } else {
1434 // Asian text usually does not contain spaces, therefore we should not
1435 // transform a linebreak into a space.
1436 // Since we only saw linebreaks, but no spaces or tabs,
1437 // let's write a linebreak now.
1438 NS_ENSURE_TRUE(AppendNewLineToString(aOutputStr), false);
1439 }
1440 }
1441
1442 return true;
1443 }
1444
AppendWrapped_NonWhitespaceSequence(nsAString::const_char_iterator & aPos,const nsAString::const_char_iterator aEnd,const nsAString::const_char_iterator aSequenceStart,bool & aMayIgnoreStartOfLineWhitespaceSequence,bool & aSequenceStartAfterAWhiteSpace,nsAString & aOutputStr)1445 bool nsXMLContentSerializer::AppendWrapped_NonWhitespaceSequence(
1446 nsAString::const_char_iterator& aPos,
1447 const nsAString::const_char_iterator aEnd,
1448 const nsAString::const_char_iterator aSequenceStart,
1449 bool& aMayIgnoreStartOfLineWhitespaceSequence,
1450 bool& aSequenceStartAfterAWhiteSpace, nsAString& aOutputStr) {
1451 mMayIgnoreLineBreakSequence = false;
1452 aMayIgnoreStartOfLineWhitespaceSequence = false;
1453
1454 // Handle the complete sequence of non-whitespace in this block
1455 // Iterate until we find the first whitespace char or an aEnd condition
1456 // Updates "aPos" to point to the first unhandled char.
1457 // Also updates the aMayIgnoreStartOfLineWhitespaceSequence flag,
1458 // as well as the other "global" state flags.
1459
1460 bool thisSequenceStartsAtBeginningOfLine = !mColPos;
1461 bool onceAgainBecauseWeAddedBreakInFront = false;
1462 bool foundWhitespaceInLoop;
1463 uint32_t length, colPos;
1464
1465 do {
1466 if (mColPos) {
1467 colPos = mColPos;
1468 } else {
1469 if (mDoFormat && !mDoRaw && !PreLevel() &&
1470 !onceAgainBecauseWeAddedBreakInFront) {
1471 colPos = mIndent.Length();
1472 } else
1473 colPos = 0;
1474 }
1475 foundWhitespaceInLoop = false;
1476 length = 0;
1477 // we iterate until the next whitespace character
1478 // or until we reach the maximum of character per line
1479 // or until the end of the string to add.
1480 do {
1481 if (*aPos == ' ' || *aPos == '\t' || *aPos == '\n') {
1482 foundWhitespaceInLoop = true;
1483 break;
1484 }
1485
1486 ++aPos;
1487 ++length;
1488 } while ((!mDoWrap || colPos + length < mMaxColumn) && aPos < aEnd);
1489
1490 // in the case we don't reached the end of the string, but we reached the
1491 // maxcolumn, we see if there is a whitespace after the maxcolumn if yes,
1492 // then we can append directly the string instead of appending a new line
1493 // etc.
1494 if (*aPos == ' ' || *aPos == '\t' || *aPos == '\n') {
1495 foundWhitespaceInLoop = true;
1496 }
1497
1498 if (aPos == aEnd || foundWhitespaceInLoop) {
1499 // there is enough room for the complete block we found
1500 if (mDoFormat && !mColPos) {
1501 NS_ENSURE_TRUE(AppendIndentation(aOutputStr), false);
1502 } else if (mAddSpace) {
1503 bool result = aOutputStr.Append(char16_t(' '), mozilla::fallible);
1504 mAddSpace = false;
1505 NS_ENSURE_TRUE(result, false);
1506 }
1507
1508 mColPos += length;
1509 NS_ENSURE_TRUE(aOutputStr.Append(aSequenceStart, aPos - aSequenceStart,
1510 mozilla::fallible),
1511 false);
1512
1513 // We have not yet reached the max column, we will continue to
1514 // fill the current line in the next outer loop iteration
1515 // (this one in AppendToStringWrapped)
1516 // make sure we return in this outer loop
1517 onceAgainBecauseWeAddedBreakInFront = false;
1518 } else { // we reach the max column
1519 if (!thisSequenceStartsAtBeginningOfLine &&
1520 (mAddSpace || (!mDoFormat && aSequenceStartAfterAWhiteSpace))) {
1521 // when !mDoFormat, mAddSpace is not used, mAddSpace is always false
1522 // so, in the case where mDoWrap && !mDoFormat, if we want to enter in
1523 // this condition...
1524
1525 // We can avoid to wrap. We try to add the whole block
1526 // in an empty new line
1527
1528 NS_ENSURE_TRUE(AppendNewLineToString(aOutputStr), false);
1529 aPos = aSequenceStart;
1530 thisSequenceStartsAtBeginningOfLine = true;
1531 onceAgainBecauseWeAddedBreakInFront = true;
1532 } else {
1533 // we must wrap
1534 onceAgainBecauseWeAddedBreakInFront = false;
1535 bool foundWrapPosition = false;
1536 int32_t wrapPosition = 0;
1537
1538 if (mAllowLineBreaking) {
1539 mozilla::intl::LineBreaker* lineBreaker =
1540 nsContentUtils::LineBreaker();
1541
1542 wrapPosition =
1543 lineBreaker->Prev(aSequenceStart, (aEnd - aSequenceStart),
1544 (aPos - aSequenceStart) + 1);
1545 if (wrapPosition != NS_LINEBREAKER_NEED_MORE_TEXT) {
1546 foundWrapPosition = true;
1547 } else {
1548 wrapPosition =
1549 lineBreaker->Next(aSequenceStart, (aEnd - aSequenceStart),
1550 (aPos - aSequenceStart));
1551 if (wrapPosition != NS_LINEBREAKER_NEED_MORE_TEXT) {
1552 foundWrapPosition = true;
1553 }
1554 }
1555 }
1556
1557 if (foundWrapPosition) {
1558 if (!mColPos && mDoFormat) {
1559 NS_ENSURE_TRUE(AppendIndentation(aOutputStr), false);
1560 } else if (mAddSpace) {
1561 bool result = aOutputStr.Append(char16_t(' '), mozilla::fallible);
1562 mAddSpace = false;
1563 NS_ENSURE_TRUE(result, false);
1564 }
1565 NS_ENSURE_TRUE(aOutputStr.Append(aSequenceStart, wrapPosition,
1566 mozilla::fallible),
1567 false);
1568
1569 NS_ENSURE_TRUE(AppendNewLineToString(aOutputStr), false);
1570 aPos = aSequenceStart + wrapPosition;
1571 aMayIgnoreStartOfLineWhitespaceSequence = true;
1572 } else {
1573 // try some simple fallback logic
1574 // go forward up to the next whitespace position,
1575 // in the worst case this will be all the rest of the data
1576
1577 // we update the mColPos variable with the length of
1578 // the part already parsed.
1579 mColPos += length;
1580
1581 // now try to find the next whitespace
1582 do {
1583 if (*aPos == ' ' || *aPos == '\t' || *aPos == '\n') {
1584 break;
1585 }
1586
1587 ++aPos;
1588 ++mColPos;
1589 } while (aPos < aEnd);
1590
1591 if (mAddSpace) {
1592 bool result = aOutputStr.Append(char16_t(' '), mozilla::fallible);
1593 mAddSpace = false;
1594 NS_ENSURE_TRUE(result, false);
1595 }
1596 NS_ENSURE_TRUE(
1597 aOutputStr.Append(aSequenceStart, aPos - aSequenceStart,
1598 mozilla::fallible),
1599 false);
1600 }
1601 }
1602 aSequenceStartAfterAWhiteSpace = false;
1603 }
1604 } while (onceAgainBecauseWeAddedBreakInFront);
1605
1606 return true;
1607 }
1608
AppendToStringFormatedWrapped(const nsAString & aStr,nsAString & aOutputStr)1609 bool nsXMLContentSerializer::AppendToStringFormatedWrapped(
1610 const nsAString& aStr, nsAString& aOutputStr) {
1611 if (mBodyOnly && !mInBody) {
1612 return true;
1613 }
1614
1615 nsAString::const_char_iterator pos, end, sequenceStart;
1616
1617 aStr.BeginReading(pos);
1618 aStr.EndReading(end);
1619
1620 bool sequenceStartAfterAWhitespace = false;
1621 if (pos < end) {
1622 nsAString::const_char_iterator end2;
1623 aOutputStr.EndReading(end2);
1624 --end2;
1625 if (*end2 == ' ' || *end2 == '\n' || *end2 == '\t') {
1626 sequenceStartAfterAWhitespace = true;
1627 }
1628 }
1629
1630 // if the current line already has text on it, such as a tag,
1631 // leading whitespace is significant
1632 bool mayIgnoreStartOfLineWhitespaceSequence =
1633 (!mColPos ||
1634 (mIsIndentationAddedOnCurrentLine && sequenceStartAfterAWhitespace &&
1635 uint32_t(mColPos) == mIndent.Length()));
1636
1637 while (pos < end) {
1638 sequenceStart = pos;
1639
1640 // if beginning of a whitespace sequence
1641 if (*pos == ' ' || *pos == '\n' || *pos == '\t') {
1642 NS_ENSURE_TRUE(AppendFormatedWrapped_WhitespaceSequence(
1643 pos, end, sequenceStart,
1644 mayIgnoreStartOfLineWhitespaceSequence, aOutputStr),
1645 false);
1646 } else { // any other non-whitespace char
1647 NS_ENSURE_TRUE(
1648 AppendWrapped_NonWhitespaceSequence(
1649 pos, end, sequenceStart, mayIgnoreStartOfLineWhitespaceSequence,
1650 sequenceStartAfterAWhitespace, aOutputStr),
1651 false);
1652 }
1653 }
1654
1655 return true;
1656 }
1657
AppendWrapped_WhitespaceSequence(nsAString::const_char_iterator & aPos,const nsAString::const_char_iterator aEnd,const nsAString::const_char_iterator aSequenceStart,nsAString & aOutputStr)1658 bool nsXMLContentSerializer::AppendWrapped_WhitespaceSequence(
1659 nsAString::const_char_iterator& aPos,
1660 const nsAString::const_char_iterator aEnd,
1661 const nsAString::const_char_iterator aSequenceStart,
1662 nsAString& aOutputStr) {
1663 // Handle the complete sequence of whitespace.
1664 // Continue to iterate until we find the first non-whitespace char.
1665 // Updates "aPos" to point to the first unhandled char.
1666 mAddSpace = false;
1667 mIsIndentationAddedOnCurrentLine = false;
1668
1669 bool leaveLoop = false;
1670 nsAString::const_char_iterator lastPos = aPos;
1671
1672 do {
1673 switch (*aPos) {
1674 case ' ':
1675 case '\t':
1676 // if there are too many spaces on a line, we wrap
1677 if (mColPos >= mMaxColumn) {
1678 if (lastPos != aPos) {
1679 NS_ENSURE_TRUE(
1680 aOutputStr.Append(lastPos, aPos - lastPos, mozilla::fallible),
1681 false);
1682 }
1683 NS_ENSURE_TRUE(AppendToString(mLineBreak, aOutputStr), false);
1684 mColPos = 0;
1685 lastPos = aPos;
1686 }
1687
1688 ++mColPos;
1689 ++aPos;
1690 break;
1691 case '\n':
1692 if (lastPos != aPos) {
1693 NS_ENSURE_TRUE(
1694 aOutputStr.Append(lastPos, aPos - lastPos, mozilla::fallible),
1695 false);
1696 }
1697 NS_ENSURE_TRUE(AppendToString(mLineBreak, aOutputStr), false);
1698 mColPos = 0;
1699 ++aPos;
1700 lastPos = aPos;
1701 break;
1702 default:
1703 leaveLoop = true;
1704 break;
1705 }
1706 } while (!leaveLoop && aPos < aEnd);
1707
1708 if (lastPos != aPos) {
1709 NS_ENSURE_TRUE(
1710 aOutputStr.Append(lastPos, aPos - lastPos, mozilla::fallible), false);
1711 }
1712
1713 return true;
1714 }
1715
AppendToStringWrapped(const nsAString & aStr,nsAString & aOutputStr)1716 bool nsXMLContentSerializer::AppendToStringWrapped(const nsAString& aStr,
1717 nsAString& aOutputStr) {
1718 if (mBodyOnly && !mInBody) {
1719 return true;
1720 }
1721
1722 nsAString::const_char_iterator pos, end, sequenceStart;
1723
1724 aStr.BeginReading(pos);
1725 aStr.EndReading(end);
1726
1727 // not used in this case, but needed by AppendWrapped_NonWhitespaceSequence
1728 bool mayIgnoreStartOfLineWhitespaceSequence = false;
1729 mMayIgnoreLineBreakSequence = false;
1730
1731 bool sequenceStartAfterAWhitespace = false;
1732 if (pos < end && !aOutputStr.IsEmpty()) {
1733 nsAString::const_char_iterator end2;
1734 aOutputStr.EndReading(end2);
1735 --end2;
1736 if (*end2 == ' ' || *end2 == '\n' || *end2 == '\t') {
1737 sequenceStartAfterAWhitespace = true;
1738 }
1739 }
1740
1741 while (pos < end) {
1742 sequenceStart = pos;
1743
1744 // if beginning of a whitespace sequence
1745 if (*pos == ' ' || *pos == '\n' || *pos == '\t') {
1746 sequenceStartAfterAWhitespace = true;
1747 NS_ENSURE_TRUE(
1748 AppendWrapped_WhitespaceSequence(pos, end, sequenceStart, aOutputStr),
1749 false);
1750 } else { // any other non-whitespace char
1751 NS_ENSURE_TRUE(
1752 AppendWrapped_NonWhitespaceSequence(
1753 pos, end, sequenceStart, mayIgnoreStartOfLineWhitespaceSequence,
1754 sequenceStartAfterAWhitespace, aOutputStr),
1755 false);
1756 }
1757 }
1758
1759 return true;
1760 }
1761
ShouldMaintainPreLevel() const1762 bool nsXMLContentSerializer::ShouldMaintainPreLevel() const {
1763 // Only attempt to maintain the pre level for consumers who care about it.
1764 return !mDoRaw || (mFlags & nsIDocumentEncoder::OutputNoFormattingInPre);
1765 }
1766
MaybeSerializeIsValue(Element * aElement,nsAString & aStr)1767 bool nsXMLContentSerializer::MaybeSerializeIsValue(Element* aElement,
1768 nsAString& aStr) {
1769 CustomElementData* ceData = aElement->GetCustomElementData();
1770 if (ceData) {
1771 nsAtom* isAttr = ceData->GetIs(aElement);
1772 if (isAttr && !aElement->HasAttr(kNameSpaceID_None, nsGkAtoms::is)) {
1773 NS_ENSURE_TRUE(aStr.AppendLiteral(" is=\"", mozilla::fallible), false);
1774 NS_ENSURE_TRUE(
1775 aStr.Append(nsDependentAtomString(isAttr), mozilla::fallible), false);
1776 NS_ENSURE_TRUE(aStr.AppendLiteral("\"", mozilla::fallible), false);
1777 }
1778 }
1779
1780 return true;
1781 }
1782