Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 /*
7 * nsIContentSerializer implementation that can be used with an
8 * nsIDocumentEncoder to convert an XML DOM to an XML string that
9 * could be parsed into more or less the original DOM.
10 */
12 #include "nsXMLContentSerializer.h"
14 #include "nsGkAtoms.h"
15 #include "nsIDOMProcessingInstruction.h"
16 #include "nsIDOMComment.h"
17 #include "nsIDOMDocumentType.h"
18 #include "nsIContent.h"
19 #include "nsIDocument.h"
20 #include "nsIDocumentEncoder.h"
21 #include "nsNameSpaceManager.h"
22 #include "nsTextFragment.h"
23 #include "nsString.h"
24 #include "prprf.h"
25 #include "nsUnicharUtils.h"
26 #include "nsCRT.h"
27 #include "nsContentUtils.h"
28 #include "nsAttrName.h"
29 #include "nsILineBreaker.h"
30 #include "mozilla/dom/Element.h"
31 #include "nsParserConstants.h"
33 using namespace mozilla::dom;
35 #define kXMLNS "xmlns"
37 // to be readable, we assume that an indented line contains
38 // at least this number of characters (arbitrary value here).
39 // This is a limit for the indentation.
40 #define MIN_INDENTED_LINE_LENGTH 15
42 // the string used to indent.
43 #define INDENT_STRING " "
44 #define INDENT_STRING_LENGTH 2
46 nsresult NS_NewXMLContentSerializer(nsIContentSerializer** aSerializer)
47 {
48 nsXMLContentSerializer* it = new nsXMLContentSerializer();
49 if (!it) {
50 return NS_ERROR_OUT_OF_MEMORY;
51 }
53 return CallQueryInterface(it, aSerializer);
54 }
56 nsXMLContentSerializer::nsXMLContentSerializer()
57 : mPrefixIndex(0),
58 mColPos(0),
59 mIndentOverflow(0),
60 mIsIndentationAddedOnCurrentLine(false),
61 mInAttribute(false),
62 mAddNewlineForRootNode(false),
63 mAddSpace(false),
64 mMayIgnoreLineBreakSequence(false),
65 mBodyOnly(false),
66 mInBody(0)
67 {
68 }
70 nsXMLContentSerializer::~nsXMLContentSerializer()
71 {
72 }
74 NS_IMPL_ISUPPORTS(nsXMLContentSerializer, nsIContentSerializer)
76 NS_IMETHODIMP
77 nsXMLContentSerializer::Init(uint32_t aFlags, uint32_t aWrapColumn,
78 const char* aCharSet, bool aIsCopying,
79 bool aRewriteEncodingDeclaration)
80 {
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 mCharset = aCharSet;
93 mFlags = aFlags;
95 // Set the line break character:
96 if ((mFlags & nsIDocumentEncoder::OutputCRLineBreak)
97 && (mFlags & nsIDocumentEncoder::OutputLFLineBreak)) { // Windows
98 mLineBreak.AssignLiteral("\r\n");
99 }
100 else if (mFlags & nsIDocumentEncoder::OutputCRLineBreak) { // Mac
101 mLineBreak.AssignLiteral("\r");
102 }
103 else if (mFlags & nsIDocumentEncoder::OutputLFLineBreak) { // Unix/DOM
104 mLineBreak.AssignLiteral("\n");
105 }
106 else {
107 mLineBreak.AssignLiteral(NS_LINEBREAK); // Platform/default
108 }
110 mDoRaw = !!(mFlags & nsIDocumentEncoder::OutputRaw);
112 mDoFormat = (mFlags & nsIDocumentEncoder::OutputFormatted && !mDoRaw);
114 mDoWrap = (mFlags & nsIDocumentEncoder::OutputWrap && !mDoRaw);
116 if (!aWrapColumn) {
117 mMaxColumn = 72;
118 }
119 else {
120 mMaxColumn = aWrapColumn;
121 }
123 mPreLevel = 0;
124 mIsIndentationAddedOnCurrentLine = false;
125 return NS_OK;
126 }
128 nsresult
129 nsXMLContentSerializer::AppendTextData(nsIContent* aNode,
130 int32_t aStartOffset,
131 int32_t aEndOffset,
132 nsAString& aStr,
133 bool aTranslateEntities)
134 {
135 nsIContent* content = aNode;
136 const nsTextFragment* frag;
137 if (!content || !(frag = content->GetText())) {
138 return NS_ERROR_FAILURE;
139 }
141 int32_t fragLength = frag->GetLength();
142 int32_t endoffset = (aEndOffset == -1) ? fragLength : std::min(aEndOffset, fragLength);
143 int32_t length = endoffset - aStartOffset;
145 NS_ASSERTION(aStartOffset >= 0, "Negative start offset for text fragment!");
146 NS_ASSERTION(aStartOffset <= endoffset, "A start offset is beyond the end of the text fragment!");
148 if (length <= 0) {
149 // XXX Zero is a legal value, maybe non-zero values should be an
150 // error.
151 return NS_OK;
152 }
154 if (frag->Is2b()) {
155 const char16_t *strStart = frag->Get2b() + aStartOffset;
156 if (aTranslateEntities) {
157 AppendAndTranslateEntities(Substring(strStart, strStart + length), aStr);
158 }
159 else {
160 aStr.Append(Substring(strStart, strStart + length));
161 }
162 }
163 else {
164 if (aTranslateEntities) {
165 AppendAndTranslateEntities(NS_ConvertASCIItoUTF16(frag->Get1b()+aStartOffset, length), aStr);
166 }
167 else {
168 aStr.Append(NS_ConvertASCIItoUTF16(frag->Get1b()+aStartOffset, length));
169 }
170 }
172 return NS_OK;
173 }
175 NS_IMETHODIMP
176 nsXMLContentSerializer::AppendText(nsIContent* aText,
177 int32_t aStartOffset,
178 int32_t aEndOffset,
179 nsAString& aStr)
180 {
181 NS_ENSURE_ARG(aText);
183 nsAutoString data;
184 nsresult rv;
186 rv = AppendTextData(aText, aStartOffset, aEndOffset, data, true);
187 if (NS_FAILED(rv))
188 return NS_ERROR_FAILURE;
190 if (mPreLevel > 0 || mDoRaw) {
191 AppendToStringConvertLF(data, aStr);
192 }
193 else if (mDoFormat) {
194 AppendToStringFormatedWrapped(data, aStr);
195 }
196 else if (mDoWrap) {
197 AppendToStringWrapped(data, aStr);
198 }
199 else {
200 AppendToStringConvertLF(data, aStr);
201 }
203 return NS_OK;
204 }
206 NS_IMETHODIMP
207 nsXMLContentSerializer::AppendCDATASection(nsIContent* aCDATASection,
208 int32_t aStartOffset,
209 int32_t aEndOffset,
210 nsAString& aStr)
211 {
212 NS_ENSURE_ARG(aCDATASection);
213 nsresult rv;
215 NS_NAMED_LITERAL_STRING(cdata , "<![CDATA[");
217 if (mPreLevel > 0 || mDoRaw) {
218 AppendToString(cdata, aStr);
219 }
220 else if (mDoFormat) {
221 AppendToStringFormatedWrapped(cdata, aStr);
222 }
223 else if (mDoWrap) {
224 AppendToStringWrapped(cdata, aStr);
225 }
226 else {
227 AppendToString(cdata, aStr);
228 }
230 nsAutoString data;
231 rv = AppendTextData(aCDATASection, aStartOffset, aEndOffset, data, false);
232 if (NS_FAILED(rv)) return NS_ERROR_FAILURE;
234 AppendToStringConvertLF(data, aStr);
236 AppendToString(NS_LITERAL_STRING("]]>"), aStr);
238 return NS_OK;
239 }
241 NS_IMETHODIMP
242 nsXMLContentSerializer::AppendProcessingInstruction(nsIContent* aPI,
243 int32_t aStartOffset,
244 int32_t aEndOffset,
245 nsAString& aStr)
246 {
247 nsCOMPtr<nsIDOMProcessingInstruction> pi = do_QueryInterface(aPI);
248 NS_ENSURE_ARG(pi);
249 nsresult rv;
250 nsAutoString target, data, start;
252 MaybeAddNewlineForRootNode(aStr);
254 rv = pi->GetTarget(target);
255 if (NS_FAILED(rv)) return NS_ERROR_FAILURE;
257 rv = pi->GetData(data);
258 if (NS_FAILED(rv)) return NS_ERROR_FAILURE;
260 start.AppendLiteral("<?");
261 start.Append(target);
263 if (mPreLevel > 0 || mDoRaw) {
264 AppendToString(start, aStr);
265 }
266 else if (mDoFormat) {
267 if (mAddSpace) {
268 AppendNewLineToString(aStr);
269 }
270 AppendToStringFormatedWrapped(start, aStr);
271 }
272 else if (mDoWrap) {
273 AppendToStringWrapped(start, aStr);
274 }
275 else {
276 AppendToString(start, aStr);
277 }
279 if (!data.IsEmpty()) {
280 AppendToString(char16_t(' '), aStr);
281 AppendToStringConvertLF(data, aStr);
282 }
283 AppendToString(NS_LITERAL_STRING("?>"), aStr);
285 MaybeFlagNewlineForRootNode(aPI);
287 return NS_OK;
288 }
290 NS_IMETHODIMP
291 nsXMLContentSerializer::AppendComment(nsIContent* aComment,
292 int32_t aStartOffset,
293 int32_t aEndOffset,
294 nsAString& aStr)
295 {
296 nsCOMPtr<nsIDOMComment> comment = do_QueryInterface(aComment);
297 NS_ENSURE_ARG(comment);
298 nsresult rv;
299 nsAutoString data;
301 rv = comment->GetData(data);
302 if (NS_FAILED(rv)) return NS_ERROR_FAILURE;
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);
313 }
314 data.Assign(frag);
315 }
317 MaybeAddNewlineForRootNode(aStr);
319 NS_NAMED_LITERAL_STRING(startComment, "<!--");
321 if (mPreLevel > 0 || mDoRaw) {
322 AppendToString(startComment, aStr);
323 }
324 else if (mDoFormat) {
325 if (mAddSpace) {
326 AppendNewLineToString(aStr);
327 }
328 AppendToStringFormatedWrapped(startComment, aStr);
329 }
330 else if (mDoWrap) {
331 AppendToStringWrapped(startComment, aStr);
332 }
333 else {
334 AppendToString(startComment, aStr);
335 }
337 // Even if mDoformat, we don't format the content because it
338 // could have been preformated by the author
339 AppendToStringConvertLF(data, aStr);
340 AppendToString(NS_LITERAL_STRING("-->"), aStr);
342 MaybeFlagNewlineForRootNode(aComment);
344 return NS_OK;
345 }
347 NS_IMETHODIMP
348 nsXMLContentSerializer::AppendDoctype(nsIContent* aDocType,
349 nsAString& aStr)
350 {
351 nsCOMPtr<nsIDOMDocumentType> docType = do_QueryInterface(aDocType);
352 NS_ENSURE_ARG(docType);
353 nsresult rv;
354 nsAutoString name, publicId, systemId, internalSubset;
356 rv = docType->GetName(name);
357 if (NS_FAILED(rv)) return NS_ERROR_FAILURE;
358 rv = docType->GetPublicId(publicId);
359 if (NS_FAILED(rv)) return NS_ERROR_FAILURE;
360 rv = docType->GetSystemId(systemId);
361 if (NS_FAILED(rv)) return NS_ERROR_FAILURE;
362 rv = docType->GetInternalSubset(internalSubset);
363 if (NS_FAILED(rv)) return NS_ERROR_FAILURE;
365 MaybeAddNewlineForRootNode(aStr);
367 AppendToString(NS_LITERAL_STRING("<!DOCTYPE "), aStr);
368 AppendToString(name, aStr);
370 char16_t quote;
371 if (!publicId.IsEmpty()) {
372 AppendToString(NS_LITERAL_STRING(" PUBLIC "), aStr);
373 if (publicId.FindChar(char16_t('"')) == -1) {
374 quote = char16_t('"');
375 }
376 else {
377 quote = char16_t('\'');
378 }
379 AppendToString(quote, aStr);
380 AppendToString(publicId, aStr);
381 AppendToString(quote, aStr);
383 if (!systemId.IsEmpty()) {
384 AppendToString(char16_t(' '), aStr);
385 if (systemId.FindChar(char16_t('"')) == -1) {
386 quote = char16_t('"');
387 }
388 else {
389 quote = char16_t('\'');
390 }
391 AppendToString(quote, aStr);
392 AppendToString(systemId, aStr);
393 AppendToString(quote, aStr);
394 }
395 }
396 else if (!systemId.IsEmpty()) {
397 if (systemId.FindChar(char16_t('"')) == -1) {
398 quote = char16_t('"');
399 }
400 else {
401 quote = char16_t('\'');
402 }
403 AppendToString(NS_LITERAL_STRING(" SYSTEM "), aStr);
404 AppendToString(quote, aStr);
405 AppendToString(systemId, aStr);
406 AppendToString(quote, aStr);
407 }
409 if (!internalSubset.IsEmpty()) {
410 AppendToString(NS_LITERAL_STRING(" ["), aStr);
411 AppendToString(internalSubset, aStr);
412 AppendToString(char16_t(']'), aStr);
413 }
415 AppendToString(kGreaterThan, aStr);
416 MaybeFlagNewlineForRootNode(aDocType);
418 return NS_OK;
419 }
421 nsresult
422 nsXMLContentSerializer::PushNameSpaceDecl(const nsAString& aPrefix,
423 const nsAString& aURI,
424 nsIContent* aOwner)
425 {
426 NameSpaceDecl* decl = mNameSpaceStack.AppendElement();
427 if (!decl) return NS_ERROR_OUT_OF_MEMORY;
429 decl->mPrefix.Assign(aPrefix);
430 decl->mURI.Assign(aURI);
431 // Don't addref - this weak reference will be removed when
432 // we pop the stack
433 decl->mOwner = aOwner;
434 return NS_OK;
435 }
437 void
438 nsXMLContentSerializer::PopNameSpaceDeclsFor(nsIContent* aOwner)
439 {
440 int32_t index, count;
442 count = mNameSpaceStack.Length();
443 for (index = count - 1; index >= 0; index--) {
444 if (mNameSpaceStack[index].mOwner != aOwner) {
445 break;
446 }
447 mNameSpaceStack.RemoveElementAt(index);
448 }
449 }
451 bool
452 nsXMLContentSerializer::ConfirmPrefix(nsAString& aPrefix,
453 const nsAString& aURI,
454 nsIContent* aElement,
455 bool aIsAttribute)
456 {
457 if (aPrefix.EqualsLiteral(kXMLNS)) {
458 return false;
459 }
461 if (aURI.EqualsLiteral("http://www.w3.org/XML/1998/namespace")) {
462 // The prefix must be xml for this namespace. We don't need to declare it,
463 // so always just set the prefix to xml.
464 aPrefix.AssignLiteral("xml");
466 return false;
467 }
469 bool mustHavePrefix;
470 if (aIsAttribute) {
471 if (aURI.IsEmpty()) {
472 // Attribute in the null namespace. This just shouldn't have a prefix.
473 // And there's no need to push any namespace decls
474 aPrefix.Truncate();
475 return false;
476 }
478 // Attribute not in the null namespace -- must have a prefix
479 mustHavePrefix = true;
480 } else {
481 // Not an attribute, so doesn't _have_ to have a prefix
482 mustHavePrefix = false;
483 }
485 // Keep track of the closest prefix that's bound to aURI and whether we've
486 // found such a thing. closestURIMatch holds the prefix, and uriMatch
487 // indicates whether we actually have one.
488 nsAutoString closestURIMatch;
489 bool uriMatch = false;
491 // Also keep track of whether we've seen aPrefix already. If we have, that
492 // means that it's already bound to a URI different from aURI, so even if we
493 // later (so in a more outer scope) see it bound to aURI we can't reuse it.
494 bool haveSeenOurPrefix = false;
496 int32_t count = mNameSpaceStack.Length();
497 int32_t index = count - 1;
498 while (index >= 0) {
499 NameSpaceDecl& decl = mNameSpaceStack.ElementAt(index);
500 // Check if we've found a prefix match
501 if (aPrefix.Equals(decl.mPrefix)) {
503 // If the URIs match and aPrefix is not bound to any other URI, we can
504 // use aPrefix
505 if (!haveSeenOurPrefix && aURI.Equals(decl.mURI)) {
506 // Just use our uriMatch stuff. That will deal with an empty aPrefix
507 // the right way. We can break out of the loop now, though.
508 uriMatch = true;
509 closestURIMatch = aPrefix;
510 break;
511 }
513 haveSeenOurPrefix = true;
515 // If they don't, and either:
516 // 1) We have a prefix (so we'd be redeclaring this prefix to point to a
517 // different namespace) or
518 // 2) We're looking at an existing default namespace decl on aElement (so
519 // we can't create a new default namespace decl for this URI)
520 // then generate a new prefix. Note that we do NOT generate new prefixes
521 // if we happen to have aPrefix == decl->mPrefix == "" and mismatching
522 // URIs when |decl| doesn't have aElement as its owner. In that case we
523 // can simply push the new namespace URI as the default namespace for
524 // aElement.
525 if (!aPrefix.IsEmpty() || decl.mOwner == aElement) {
526 NS_ASSERTION(!aURI.IsEmpty(),
527 "Not allowed to add a xmlns attribute with an empty "
528 "namespace name unless it declares the default "
529 "namespace.");
531 GenerateNewPrefix(aPrefix);
532 // Now we need to validate our new prefix/uri combination; check it
533 // against the full namespace stack again. Note that just restarting
534 // the while loop is ok, since we haven't changed aURI, so the
535 // closestURIMatch and uriMatch state is not affected.
536 index = count - 1;
537 haveSeenOurPrefix = false;
538 continue;
539 }
540 }
542 // If we've found a URI match, then record the first one
543 if (!uriMatch && aURI.Equals(decl.mURI)) {
544 // Need to check that decl->mPrefix is not declared anywhere closer to
545 // us. If it is, we can't use it.
546 bool prefixOK = true;
547 int32_t index2;
548 for (index2 = count-1; index2 > index && prefixOK; --index2) {
549 prefixOK = (mNameSpaceStack[index2].mPrefix != decl.mPrefix);
550 }
552 if (prefixOK) {
553 uriMatch = true;
554 closestURIMatch.Assign(decl.mPrefix);
555 }
556 }
558 --index;
559 }
561 // At this point the following invariants hold:
562 // 1) The prefix in closestURIMatch is mapped to aURI in our scope if
563 // uriMatch is set.
564 // 2) There is nothing on the namespace stack that has aPrefix as the prefix
565 // and a _different_ URI, except for the case aPrefix.IsEmpty (and
566 // possible default namespaces on ancestors)
568 // So if uriMatch is set it's OK to use the closestURIMatch prefix. The one
569 // exception is when closestURIMatch is actually empty (default namespace
570 // decl) and we must have a prefix.
571 if (uriMatch && (!mustHavePrefix || !closestURIMatch.IsEmpty())) {
572 aPrefix.Assign(closestURIMatch);
573 return false;
574 }
576 if (aPrefix.IsEmpty()) {
577 // At this point, aPrefix is empty (which means we never had a prefix to
578 // start with). If we must have a prefix, just generate a new prefix and
579 // then send it back through the namespace stack checks to make sure it's
580 // OK.
581 if (mustHavePrefix) {
582 GenerateNewPrefix(aPrefix);
583 return ConfirmPrefix(aPrefix, aURI, aElement, aIsAttribute);
584 }
586 // One final special case. If aPrefix is empty and we never saw an empty
587 // prefix (default namespace decl) on the namespace stack and we're in the
588 // null namespace there is no reason to output an |xmlns=""| here. It just
589 // makes the output less readable.
590 if (!haveSeenOurPrefix && aURI.IsEmpty()) {
591 return false;
592 }
593 }
595 // Now just set aURI as the new default namespace URI. Indicate that we need
596 // to create a namespace decl for the final prefix
597 return true;
598 }
600 void
601 nsXMLContentSerializer::GenerateNewPrefix(nsAString& aPrefix)
602 {
603 aPrefix.AssignLiteral("a");
604 char buf[128];
605 PR_snprintf(buf, sizeof(buf), "%d", mPrefixIndex++);
606 AppendASCIItoUTF16(buf, aPrefix);
607 }
609 void
610 nsXMLContentSerializer::SerializeAttr(const nsAString& aPrefix,
611 const nsAString& aName,
612 const nsAString& aValue,
613 nsAString& aStr,
614 bool aDoEscapeEntities)
615 {
616 nsAutoString attrString_;
617 // For innerHTML we can do faster appending without
618 // temporary strings.
619 bool rawAppend = mDoRaw && aDoEscapeEntities;
620 nsAString& attrString = (rawAppend) ? aStr : attrString_;
622 attrString.Append(char16_t(' '));
623 if (!aPrefix.IsEmpty()) {
624 attrString.Append(aPrefix);
625 attrString.Append(char16_t(':'));
626 }
627 attrString.Append(aName);
629 if (aDoEscapeEntities) {
630 // if problem characters are turned into character entity references
631 // then there will be no problem with the value delimiter characters
632 attrString.AppendLiteral("=\"");
634 mInAttribute = true;
635 AppendAndTranslateEntities(aValue, attrString);
636 mInAttribute = false;
638 attrString.Append(char16_t('"'));
639 if (rawAppend) {
640 return;
641 }
642 }
643 else {
644 // Depending on whether the attribute value contains quotes or apostrophes we
645 // need to select the delimiter character and escape characters using
646 // character entity references, ignoring the value of aDoEscapeEntities.
647 // See http://www.w3.org/TR/REC-html40/appendix/notes.html#h-B.3.2.2 for
648 // the standard on character entity references in values. We also have to
649 // make sure to escape any '&' characters.
651 bool bIncludesSingle = false;
652 bool bIncludesDouble = false;
653 nsAString::const_iterator iCurr, iEnd;
654 uint32_t uiSize, i;
655 aValue.BeginReading(iCurr);
656 aValue.EndReading(iEnd);
657 for ( ; iCurr != iEnd; iCurr.advance(uiSize) ) {
658 const char16_t * buf = iCurr.get();
659 uiSize = iCurr.size_forward();
660 for ( i = 0; i < uiSize; i++, buf++ ) {
661 if ( *buf == char16_t('\'') )
662 {
663 bIncludesSingle = true;
664 if ( bIncludesDouble ) break;
665 }
666 else if ( *buf == char16_t('"') )
667 {
668 bIncludesDouble = true;
669 if ( bIncludesSingle ) break;
670 }
671 }
672 // if both have been found we don't need to search further
673 if ( bIncludesDouble && bIncludesSingle ) break;
674 }
676 // Delimiter and escaping is according to the following table
677 // bIncludesDouble bIncludesSingle Delimiter Escape Double Quote
678 // FALSE FALSE " FALSE
679 // FALSE TRUE " FALSE
680 // TRUE FALSE ' FALSE
681 // TRUE TRUE " TRUE
682 char16_t cDelimiter =
683 (bIncludesDouble && !bIncludesSingle) ? char16_t('\'') : char16_t('"');
684 attrString.Append(char16_t('='));
685 attrString.Append(cDelimiter);
686 nsAutoString sValue(aValue);
687 sValue.ReplaceSubstring(NS_LITERAL_STRING("&"),
688 NS_LITERAL_STRING("&"));
689 if (bIncludesDouble && bIncludesSingle) {
690 sValue.ReplaceSubstring(NS_LITERAL_STRING("\""),
691 NS_LITERAL_STRING("""));
692 }
693 attrString.Append(sValue);
694 attrString.Append(cDelimiter);
695 }
696 if (mPreLevel > 0 || mDoRaw) {
697 AppendToStringConvertLF(attrString, aStr);
698 }
699 else if (mDoFormat) {
700 AppendToStringFormatedWrapped(attrString, aStr);
701 }
702 else if (mDoWrap) {
703 AppendToStringWrapped(attrString, aStr);
704 }
705 else {
706 AppendToStringConvertLF(attrString, aStr);
707 }
708 }
710 uint32_t
711 nsXMLContentSerializer::ScanNamespaceDeclarations(nsIContent* aContent,
712 nsIContent *aOriginalElement,
713 const nsAString& aTagNamespaceURI)
714 {
715 uint32_t index, count;
716 nsAutoString uriStr, valueStr;
718 count = aContent->GetAttrCount();
720 // First scan for namespace declarations, pushing each on the stack
721 uint32_t skipAttr = count;
722 for (index = 0; index < count; index++) {
724 const nsAttrName* name = aContent->GetAttrNameAt(index);
725 int32_t namespaceID = name->NamespaceID();
726 nsIAtom *attrName = name->LocalName();
728 if (namespaceID == kNameSpaceID_XMLNS ||
729 // Also push on the stack attrs named "xmlns" in the null
730 // namespace... because once we serialize those out they'll look like
731 // namespace decls. :(
732 // XXXbz what if we have both "xmlns" in the null namespace and "xmlns"
733 // in the xmlns namespace?
734 (namespaceID == kNameSpaceID_None &&
735 attrName == nsGkAtoms::xmlns)) {
736 aContent->GetAttr(namespaceID, attrName, uriStr);
738 if (!name->GetPrefix()) {
739 if (aTagNamespaceURI.IsEmpty() && !uriStr.IsEmpty()) {
740 // If the element is in no namespace we need to add a xmlns
741 // attribute to declare that. That xmlns attribute must not have a
742 // prefix (see http://www.w3.org/TR/REC-xml-names/#dt-prefix), ie it
743 // must declare the default namespace. We just found an xmlns
744 // attribute that declares the default namespace to something
745 // non-empty. We're going to ignore this attribute, for children we
746 // will detect that we need to add it again and attributes aren't
747 // affected by the default namespace.
748 skipAttr = index;
749 }
750 else {
751 // Default NS attribute does not have prefix (and the name is "xmlns")
752 PushNameSpaceDecl(EmptyString(), uriStr, aOriginalElement);
753 }
754 }
755 else {
756 PushNameSpaceDecl(nsDependentAtomString(attrName), uriStr,
757 aOriginalElement);
758 }
759 }
760 }
761 return skipAttr;
762 }
765 bool
766 nsXMLContentSerializer::IsJavaScript(nsIContent * aContent, nsIAtom* aAttrNameAtom,
767 int32_t aAttrNamespaceID, const nsAString& aValueString)
768 {
769 bool isHtml = aContent->IsHTML();
770 bool isXul = aContent->IsXUL();
771 bool isSvg = aContent->IsSVG();
773 if (aAttrNamespaceID == kNameSpaceID_None &&
774 (isHtml || isXul || isSvg) &&
775 (aAttrNameAtom == nsGkAtoms::href ||
776 aAttrNameAtom == nsGkAtoms::src)) {
778 static const char kJavaScript[] = "javascript";
779 int32_t pos = aValueString.FindChar(':');
780 if (pos < (int32_t)(sizeof kJavaScript - 1))
781 return false;
782 nsAutoString scheme(Substring(aValueString, 0, pos));
783 scheme.StripWhitespace();
784 if ((scheme.Length() == (sizeof kJavaScript - 1)) &&
785 scheme.EqualsIgnoreCase(kJavaScript))
786 return true;
787 else
788 return false;
789 }
791 return aContent->IsEventAttributeName(aAttrNameAtom);
792 }
795 void
796 nsXMLContentSerializer::SerializeAttributes(nsIContent* aContent,
797 nsIContent *aOriginalElement,
798 nsAString& aTagPrefix,
799 const nsAString& aTagNamespaceURI,
800 nsIAtom* aTagName,
801 nsAString& aStr,
802 uint32_t aSkipAttr,
803 bool aAddNSAttr)
804 {
806 nsAutoString prefixStr, uriStr, valueStr;
807 nsAutoString xmlnsStr;
808 xmlnsStr.AssignLiteral(kXMLNS);
809 uint32_t index, count;
811 // If we had to add a new namespace declaration, serialize
812 // and push it on the namespace stack
813 if (aAddNSAttr) {
814 if (aTagPrefix.IsEmpty()) {
815 // Serialize default namespace decl
816 SerializeAttr(EmptyString(), xmlnsStr, aTagNamespaceURI, aStr, true);
817 }
818 else {
819 // Serialize namespace decl
820 SerializeAttr(xmlnsStr, aTagPrefix, aTagNamespaceURI, aStr, true);
821 }
822 PushNameSpaceDecl(aTagPrefix, aTagNamespaceURI, aOriginalElement);
823 }
825 count = aContent->GetAttrCount();
827 // Now serialize each of the attributes
828 // XXX Unfortunately we need a namespace manager to get
829 // attribute URIs.
830 for (index = 0; index < count; index++) {
831 if (aSkipAttr == index) {
832 continue;
833 }
835 const nsAttrName* name = aContent->GetAttrNameAt(index);
836 int32_t namespaceID = name->NamespaceID();
837 nsIAtom* attrName = name->LocalName();
838 nsIAtom* attrPrefix = name->GetPrefix();
840 // Filter out any attribute starting with [-|_]moz
841 nsDependentAtomString attrNameStr(attrName);
842 if (StringBeginsWith(attrNameStr, NS_LITERAL_STRING("_moz")) ||
843 StringBeginsWith(attrNameStr, NS_LITERAL_STRING("-moz"))) {
844 continue;
845 }
847 if (attrPrefix) {
848 attrPrefix->ToString(prefixStr);
849 }
850 else {
851 prefixStr.Truncate();
852 }
854 bool addNSAttr = false;
855 if (kNameSpaceID_XMLNS != namespaceID) {
856 nsContentUtils::NameSpaceManager()->GetNameSpaceURI(namespaceID, uriStr);
857 addNSAttr = ConfirmPrefix(prefixStr, uriStr, aOriginalElement, true);
858 }
860 aContent->GetAttr(namespaceID, attrName, valueStr);
862 nsDependentAtomString nameStr(attrName);
863 bool isJS = IsJavaScript(aContent, attrName, namespaceID, valueStr);
865 SerializeAttr(prefixStr, nameStr, valueStr, aStr, !isJS);
867 if (addNSAttr) {
868 NS_ASSERTION(!prefixStr.IsEmpty(),
869 "Namespaced attributes must have a prefix");
870 SerializeAttr(xmlnsStr, prefixStr, uriStr, aStr, true);
871 PushNameSpaceDecl(prefixStr, uriStr, aOriginalElement);
872 }
873 }
874 }
876 NS_IMETHODIMP
877 nsXMLContentSerializer::AppendElementStart(Element* aElement,
878 Element* aOriginalElement,
879 nsAString& aStr)
880 {
881 NS_ENSURE_ARG(aElement);
883 nsIContent* content = aElement;
885 bool forceFormat = false;
886 if (!CheckElementStart(content, forceFormat, aStr)) {
887 return NS_OK;
888 }
890 nsAutoString tagPrefix, tagLocalName, tagNamespaceURI;
891 aElement->NodeInfo()->GetPrefix(tagPrefix);
892 aElement->NodeInfo()->GetName(tagLocalName);
893 aElement->NodeInfo()->GetNamespaceURI(tagNamespaceURI);
895 uint32_t skipAttr = ScanNamespaceDeclarations(content,
896 aOriginalElement, tagNamespaceURI);
898 nsIAtom *name = content->Tag();
899 bool lineBreakBeforeOpen = LineBreakBeforeOpen(content->GetNameSpaceID(), name);
901 if ((mDoFormat || forceFormat) && !mPreLevel && !mDoRaw) {
902 if (mColPos && lineBreakBeforeOpen) {
903 AppendNewLineToString(aStr);
904 }
905 else {
906 MaybeAddNewlineForRootNode(aStr);
907 }
908 if (!mColPos) {
909 AppendIndentation(aStr);
910 }
911 else if (mAddSpace) {
912 AppendToString(char16_t(' '), aStr);
913 mAddSpace = false;
914 }
915 }
916 else if (mAddSpace) {
917 AppendToString(char16_t(' '), aStr);
918 mAddSpace = false;
919 }
920 else {
921 MaybeAddNewlineForRootNode(aStr);
922 }
924 // Always reset to avoid false newlines in case MaybeAddNewlineForRootNode wasn't
925 // called
926 mAddNewlineForRootNode = false;
928 bool addNSAttr;
929 addNSAttr = ConfirmPrefix(tagPrefix, tagNamespaceURI, aOriginalElement,
930 false);
932 // Serialize the qualified name of the element
933 AppendToString(kLessThan, aStr);
934 if (!tagPrefix.IsEmpty()) {
935 AppendToString(tagPrefix, aStr);
936 AppendToString(NS_LITERAL_STRING(":"), aStr);
937 }
938 AppendToString(tagLocalName, aStr);
940 MaybeEnterInPreContent(content);
942 if ((mDoFormat || forceFormat) && !mPreLevel && !mDoRaw) {
943 IncrIndentation(name);
944 }
946 SerializeAttributes(content, aOriginalElement, tagPrefix, tagNamespaceURI,
947 name, aStr, skipAttr, addNSAttr);
949 AppendEndOfElementStart(aOriginalElement, name, content->GetNameSpaceID(),
950 aStr);
952 if ((mDoFormat || forceFormat) && !mPreLevel
953 && !mDoRaw && LineBreakAfterOpen(content->GetNameSpaceID(), name)) {
954 AppendNewLineToString(aStr);
955 }
957 AfterElementStart(content, aOriginalElement, aStr);
959 return NS_OK;
960 }
962 void
963 nsXMLContentSerializer::AppendEndOfElementStart(nsIContent *aOriginalElement,
964 nsIAtom * aName,
965 int32_t aNamespaceID,
966 nsAString& aStr)
967 {
968 // We don't output a separate end tag for empty elements
969 if (!aOriginalElement->GetChildCount()) {
970 AppendToString(NS_LITERAL_STRING("/>"), aStr);
971 }
972 else {
973 AppendToString(kGreaterThan, aStr);
974 }
975 }
977 NS_IMETHODIMP
978 nsXMLContentSerializer::AppendElementEnd(Element* aElement,
979 nsAString& aStr)
980 {
981 NS_ENSURE_ARG(aElement);
983 nsIContent* content = aElement;
985 bool forceFormat = false, outputElementEnd;
986 outputElementEnd = CheckElementEnd(content, forceFormat, aStr);
988 nsIAtom *name = content->Tag();
990 if ((mDoFormat || forceFormat) && !mPreLevel && !mDoRaw) {
991 DecrIndentation(name);
992 }
994 if (!outputElementEnd) {
995 PopNameSpaceDeclsFor(aElement);
996 MaybeFlagNewlineForRootNode(aElement);
997 return NS_OK;
998 }
1000 nsAutoString tagPrefix, tagLocalName, tagNamespaceURI;
1002 aElement->NodeInfo()->GetPrefix(tagPrefix);
1003 aElement->NodeInfo()->GetName(tagLocalName);
1004 aElement->NodeInfo()->GetNamespaceURI(tagNamespaceURI);
1006 #ifdef DEBUG
1007 bool debugNeedToPushNamespace =
1008 #endif
1009 ConfirmPrefix(tagPrefix, tagNamespaceURI, aElement, false);
1010 NS_ASSERTION(!debugNeedToPushNamespace, "Can't push namespaces in closing tag!");
1012 if ((mDoFormat || forceFormat) && !mPreLevel && !mDoRaw) {
1014 bool lineBreakBeforeClose = LineBreakBeforeClose(content->GetNameSpaceID(), name);
1016 if (mColPos && lineBreakBeforeClose) {
1017 AppendNewLineToString(aStr);
1018 }
1019 if (!mColPos) {
1020 AppendIndentation(aStr);
1021 }
1022 else if (mAddSpace) {
1023 AppendToString(char16_t(' '), aStr);
1024 mAddSpace = false;
1025 }
1026 }
1027 else if (mAddSpace) {
1028 AppendToString(char16_t(' '), aStr);
1029 mAddSpace = false;
1030 }
1032 AppendToString(kEndTag, aStr);
1033 if (!tagPrefix.IsEmpty()) {
1034 AppendToString(tagPrefix, aStr);
1035 AppendToString(NS_LITERAL_STRING(":"), aStr);
1036 }
1037 AppendToString(tagLocalName, aStr);
1038 AppendToString(kGreaterThan, aStr);
1040 PopNameSpaceDeclsFor(aElement);
1042 MaybeLeaveFromPreContent(content);
1044 if ((mDoFormat || forceFormat) && !mPreLevel
1045 && !mDoRaw && LineBreakAfterClose(content->GetNameSpaceID(), name)) {
1046 AppendNewLineToString(aStr);
1047 }
1048 else {
1049 MaybeFlagNewlineForRootNode(aElement);
1050 }
1052 AfterElementEnd(content, aStr);
1054 return NS_OK;
1055 }
1057 NS_IMETHODIMP
1058 nsXMLContentSerializer::AppendDocumentStart(nsIDocument *aDocument,
1059 nsAString& aStr)
1060 {
1061 NS_ENSURE_ARG_POINTER(aDocument);
1063 nsAutoString version, encoding, standalone;
1064 aDocument->GetXMLDeclaration(version, encoding, standalone);
1066 if (version.IsEmpty())
1067 return NS_OK; // A declaration must have version, or there is no decl
1069 NS_NAMED_LITERAL_STRING(endQuote, "\"");
1071 aStr += NS_LITERAL_STRING("<?xml version=\"") + version + endQuote;
1073 if (!mCharset.IsEmpty()) {
1074 aStr += NS_LITERAL_STRING(" encoding=\"") +
1075 NS_ConvertASCIItoUTF16(mCharset) + endQuote;
1076 }
1077 // Otherwise just don't output an encoding attr. Not that we expect
1078 // mCharset to ever be empty.
1079 #ifdef DEBUG
1080 else {
1081 NS_WARNING("Empty mCharset? How come?");
1082 }
1083 #endif
1085 if (!standalone.IsEmpty()) {
1086 aStr += NS_LITERAL_STRING(" standalone=\"") + standalone + endQuote;
1087 }
1089 aStr.AppendLiteral("?>");
1090 mAddNewlineForRootNode = true;
1092 return NS_OK;
1093 }
1095 bool
1096 nsXMLContentSerializer::CheckElementStart(nsIContent * aContent,
1097 bool & aForceFormat,
1098 nsAString& aStr)
1099 {
1100 aForceFormat = false;
1101 return true;
1102 }
1104 bool
1105 nsXMLContentSerializer::CheckElementEnd(nsIContent * aContent,
1106 bool & aForceFormat,
1107 nsAString& aStr)
1108 {
1109 // We don't output a separate end tag for empty element
1110 aForceFormat = false;
1111 return aContent->GetChildCount() > 0;
1112 }
1114 void
1115 nsXMLContentSerializer::AppendToString(const char16_t aChar,
1116 nsAString& aOutputStr)
1117 {
1118 if (mBodyOnly && !mInBody) {
1119 return;
1120 }
1121 mColPos += 1;
1122 aOutputStr.Append(aChar);
1123 }
1125 void
1126 nsXMLContentSerializer::AppendToString(const nsAString& aStr,
1127 nsAString& aOutputStr)
1128 {
1129 if (mBodyOnly && !mInBody) {
1130 return;
1131 }
1132 mColPos += aStr.Length();
1133 aOutputStr.Append(aStr);
1134 }
1137 static const uint16_t kGTVal = 62;
1138 static const char* kEntities[] = {
1139 "", "", "", "", "", "", "", "", "", "",
1140 "", "", "", "", "", "", "", "", "", "",
1141 "", "", "", "", "", "", "", "", "", "",
1142 "", "", "", "", "", "", "", "", "&", "",
1143 "", "", "", "", "", "", "", "", "", "",
1144 "", "", "", "", "", "", "", "", "", "",
1145 "<", "", ">"
1146 };
1148 static const char* kAttrEntities[] = {
1149 "", "", "", "", "", "", "", "", "", "",
1150 "", "", "", "", "", "", "", "", "", "",
1151 "", "", "", "", "", "", "", "", "", "",
1152 "", "", "", "", """, "", "", "", "&", "",
1153 "", "", "", "", "", "", "", "", "", "",
1154 "", "", "", "", "", "", "", "", "", "",
1155 "<", "", ">"
1156 };
1158 void
1159 nsXMLContentSerializer::AppendAndTranslateEntities(const nsAString& aStr,
1160 nsAString& aOutputStr)
1161 {
1162 nsReadingIterator<char16_t> done_reading;
1163 aStr.EndReading(done_reading);
1165 // for each chunk of |aString|...
1166 uint32_t advanceLength = 0;
1167 nsReadingIterator<char16_t> iter;
1169 const char **entityTable = mInAttribute ? kAttrEntities : kEntities;
1171 for (aStr.BeginReading(iter);
1172 iter != done_reading;
1173 iter.advance(int32_t(advanceLength))) {
1174 uint32_t fragmentLength = iter.size_forward();
1175 const char16_t* c = iter.get();
1176 const char16_t* fragmentStart = c;
1177 const char16_t* fragmentEnd = c + fragmentLength;
1178 const char* entityText = nullptr;
1180 advanceLength = 0;
1181 // for each character in this chunk, check if it
1182 // needs to be replaced
1183 for (; c < fragmentEnd; c++, advanceLength++) {
1184 char16_t val = *c;
1185 if ((val <= kGTVal) && (entityTable[val][0] != 0)) {
1186 entityText = entityTable[val];
1187 break;
1188 }
1189 }
1191 aOutputStr.Append(fragmentStart, advanceLength);
1192 if (entityText) {
1193 AppendASCIItoUTF16(entityText, aOutputStr);
1194 advanceLength++;
1195 }
1196 }
1197 }
1199 void
1200 nsXMLContentSerializer::MaybeAddNewlineForRootNode(nsAString& aStr)
1201 {
1202 if (mAddNewlineForRootNode) {
1203 AppendNewLineToString(aStr);
1204 }
1205 }
1207 void
1208 nsXMLContentSerializer::MaybeFlagNewlineForRootNode(nsINode* aNode)
1209 {
1210 nsINode* parent = aNode->GetParentNode();
1211 if (parent) {
1212 mAddNewlineForRootNode = parent->IsNodeOfType(nsINode::eDOCUMENT);
1213 }
1214 }
1216 void
1217 nsXMLContentSerializer::MaybeEnterInPreContent(nsIContent* aNode)
1218 {
1219 // support of the xml:space attribute
1220 if (aNode->HasAttr(kNameSpaceID_XML, nsGkAtoms::space)) {
1221 nsAutoString space;
1222 aNode->GetAttr(kNameSpaceID_XML, nsGkAtoms::space, space);
1223 if (space.EqualsLiteral("preserve"))
1224 ++mPreLevel;
1225 }
1226 }
1228 void
1229 nsXMLContentSerializer::MaybeLeaveFromPreContent(nsIContent* aNode)
1230 {
1231 // support of the xml:space attribute
1232 if (aNode->HasAttr(kNameSpaceID_XML, nsGkAtoms::space)) {
1233 nsAutoString space;
1234 aNode->GetAttr(kNameSpaceID_XML, nsGkAtoms::space, space);
1235 if (space.EqualsLiteral("preserve"))
1236 --mPreLevel;
1237 }
1238 }
1240 void
1241 nsXMLContentSerializer::AppendNewLineToString(nsAString& aStr)
1242 {
1243 AppendToString(mLineBreak, aStr);
1244 mMayIgnoreLineBreakSequence = true;
1245 mColPos = 0;
1246 mAddSpace = false;
1247 mIsIndentationAddedOnCurrentLine = false;
1248 }
1250 void
1251 nsXMLContentSerializer::AppendIndentation(nsAString& aStr)
1252 {
1253 mIsIndentationAddedOnCurrentLine = true;
1254 AppendToString(mIndent, aStr);
1255 mAddSpace = false;
1256 mMayIgnoreLineBreakSequence = false;
1257 }
1259 void
1260 nsXMLContentSerializer::IncrIndentation(nsIAtom* aName)
1261 {
1262 // we want to keep the source readable
1263 if (mDoWrap &&
1264 mIndent.Length() >= uint32_t(mMaxColumn) - MIN_INDENTED_LINE_LENGTH) {
1265 ++mIndentOverflow;
1266 }
1267 else {
1268 mIndent.AppendLiteral(INDENT_STRING);
1269 }
1270 }
1272 void
1273 nsXMLContentSerializer::DecrIndentation(nsIAtom* aName)
1274 {
1275 if(mIndentOverflow)
1276 --mIndentOverflow;
1277 else
1278 mIndent.Cut(0, INDENT_STRING_LENGTH);
1279 }
1281 bool
1282 nsXMLContentSerializer::LineBreakBeforeOpen(int32_t aNamespaceID, nsIAtom* aName)
1283 {
1284 return mAddSpace;
1285 }
1287 bool
1288 nsXMLContentSerializer::LineBreakAfterOpen(int32_t aNamespaceID, nsIAtom* aName)
1289 {
1290 return false;
1291 }
1293 bool
1294 nsXMLContentSerializer::LineBreakBeforeClose(int32_t aNamespaceID, nsIAtom* aName)
1295 {
1296 return mAddSpace;
1297 }
1299 bool
1300 nsXMLContentSerializer::LineBreakAfterClose(int32_t aNamespaceID, nsIAtom* aName)
1301 {
1302 return false;
1303 }
1305 void
1306 nsXMLContentSerializer::AppendToStringConvertLF(const nsAString& aStr,
1307 nsAString& aOutputStr)
1308 {
1309 if (mBodyOnly && !mInBody) {
1310 return;
1311 }
1313 if (mDoRaw) {
1314 AppendToString(aStr, aOutputStr);
1315 }
1316 else {
1317 // Convert line-endings to mLineBreak
1318 uint32_t start = 0;
1319 uint32_t theLen = aStr.Length();
1320 while (start < theLen) {
1321 int32_t eol = aStr.FindChar('\n', start);
1322 if (eol == kNotFound) {
1323 nsDependentSubstring dataSubstring(aStr, start, theLen - start);
1324 AppendToString(dataSubstring, aOutputStr);
1325 start = theLen;
1326 // if there was a line break before this substring
1327 // AppendNewLineToString was called, so we should reverse
1328 // this flag
1329 mMayIgnoreLineBreakSequence = false;
1330 }
1331 else {
1332 nsDependentSubstring dataSubstring(aStr, start, eol - start);
1333 AppendToString(dataSubstring, aOutputStr);
1334 AppendNewLineToString(aOutputStr);
1335 start = eol + 1;
1336 }
1337 }
1338 }
1339 }
1341 void
1342 nsXMLContentSerializer::AppendFormatedWrapped_WhitespaceSequence(
1343 nsASingleFragmentString::const_char_iterator &aPos,
1344 const nsASingleFragmentString::const_char_iterator aEnd,
1345 const nsASingleFragmentString::const_char_iterator aSequenceStart,
1346 bool &aMayIgnoreStartOfLineWhitespaceSequence,
1347 nsAString &aOutputStr)
1348 {
1349 // Handle the complete sequence of whitespace.
1350 // Continue to iterate until we find the first non-whitespace char.
1351 // Updates "aPos" to point to the first unhandled char.
1352 // Also updates the aMayIgnoreStartOfLineWhitespaceSequence flag,
1353 // as well as the other "global" state flags.
1355 bool sawBlankOrTab = false;
1356 bool leaveLoop = false;
1358 do {
1359 switch (*aPos) {
1360 case ' ':
1361 case '\t':
1362 sawBlankOrTab = true;
1363 // no break
1364 case '\n':
1365 ++aPos;
1366 // do not increase mColPos,
1367 // because we will reduce the whitespace to a single char
1368 break;
1369 default:
1370 leaveLoop = true;
1371 break;
1372 }
1373 } while (!leaveLoop && aPos < aEnd);
1375 if (mAddSpace) {
1376 // if we had previously been asked to add space,
1377 // our situation has not changed
1378 }
1379 else if (!sawBlankOrTab && mMayIgnoreLineBreakSequence) {
1380 // nothing to do in the case where line breaks have already been added
1381 // before the call of AppendToStringWrapped
1382 // and only if we found line break in the sequence
1383 mMayIgnoreLineBreakSequence = false;
1384 }
1385 else if (aMayIgnoreStartOfLineWhitespaceSequence) {
1386 // nothing to do
1387 aMayIgnoreStartOfLineWhitespaceSequence = false;
1388 }
1389 else {
1390 if (sawBlankOrTab) {
1391 if (mDoWrap && mColPos + 1 >= mMaxColumn) {
1392 // no much sense in delaying, we only have one slot left,
1393 // let's write a break now
1394 aOutputStr.Append(mLineBreak);
1395 mColPos = 0;
1396 mIsIndentationAddedOnCurrentLine = false;
1397 mMayIgnoreLineBreakSequence = true;
1398 }
1399 else {
1400 // do not write out yet, we may write out either a space or a linebreak
1401 // let's delay writing it out until we know more
1402 mAddSpace = true;
1403 ++mColPos; // eat a slot of available space
1404 }
1405 }
1406 else {
1407 // Asian text usually does not contain spaces, therefore we should not
1408 // transform a linebreak into a space.
1409 // Since we only saw linebreaks, but no spaces or tabs,
1410 // let's write a linebreak now.
1411 AppendNewLineToString(aOutputStr);
1412 }
1413 }
1414 }
1416 void
1417 nsXMLContentSerializer::AppendWrapped_NonWhitespaceSequence(
1418 nsASingleFragmentString::const_char_iterator &aPos,
1419 const nsASingleFragmentString::const_char_iterator aEnd,
1420 const nsASingleFragmentString::const_char_iterator aSequenceStart,
1421 bool &aMayIgnoreStartOfLineWhitespaceSequence,
1422 bool &aSequenceStartAfterAWhiteSpace,
1423 nsAString& aOutputStr)
1424 {
1425 mMayIgnoreLineBreakSequence = false;
1426 aMayIgnoreStartOfLineWhitespaceSequence = false;
1428 // Handle the complete sequence of non-whitespace in this block
1429 // Iterate until we find the first whitespace char or an aEnd condition
1430 // Updates "aPos" to point to the first unhandled char.
1431 // Also updates the aMayIgnoreStartOfLineWhitespaceSequence flag,
1432 // as well as the other "global" state flags.
1434 bool thisSequenceStartsAtBeginningOfLine = !mColPos;
1435 bool onceAgainBecauseWeAddedBreakInFront = false;
1436 bool foundWhitespaceInLoop;
1437 uint32_t length, colPos;
1439 do {
1441 if (mColPos) {
1442 colPos = mColPos;
1443 }
1444 else {
1445 if (mDoFormat && !mPreLevel && !onceAgainBecauseWeAddedBreakInFront) {
1446 colPos = mIndent.Length();
1447 }
1448 else
1449 colPos = 0;
1450 }
1451 foundWhitespaceInLoop = false;
1452 length = 0;
1453 // we iterate until the next whitespace character
1454 // or until we reach the maximum of character per line
1455 // or until the end of the string to add.
1456 do {
1457 if (*aPos == ' ' || *aPos == '\t' || *aPos == '\n') {
1458 foundWhitespaceInLoop = true;
1459 break;
1460 }
1462 ++aPos;
1463 ++length;
1464 } while ( (!mDoWrap || colPos + length < mMaxColumn) && aPos < aEnd);
1466 // in the case we don't reached the end of the string, but we reached the maxcolumn,
1467 // we see if there is a whitespace after the maxcolumn
1468 // if yes, then we can append directly the string instead of
1469 // appending a new line etc.
1470 if (*aPos == ' ' || *aPos == '\t' || *aPos == '\n') {
1471 foundWhitespaceInLoop = true;
1472 }
1474 if (aPos == aEnd || foundWhitespaceInLoop) {
1475 // there is enough room for the complete block we found
1476 if (mDoFormat && !mColPos) {
1477 AppendIndentation(aOutputStr);
1478 }
1479 else if (mAddSpace) {
1480 aOutputStr.Append(char16_t(' '));
1481 mAddSpace = false;
1482 }
1484 mColPos += length;
1485 aOutputStr.Append(aSequenceStart, aPos - aSequenceStart);
1487 // We have not yet reached the max column, we will continue to
1488 // fill the current line in the next outer loop iteration
1489 // (this one in AppendToStringWrapped)
1490 // make sure we return in this outer loop
1491 onceAgainBecauseWeAddedBreakInFront = false;
1492 }
1493 else { // we reach the max column
1494 if (!thisSequenceStartsAtBeginningOfLine &&
1495 (mAddSpace || (!mDoFormat && aSequenceStartAfterAWhiteSpace))) {
1496 // when !mDoFormat, mAddSpace is not used, mAddSpace is always false
1497 // so, in the case where mDoWrap && !mDoFormat, if we want to enter in this condition...
1499 // We can avoid to wrap. We try to add the whole block
1500 // in an empty new line
1502 AppendNewLineToString(aOutputStr);
1503 aPos = aSequenceStart;
1504 thisSequenceStartsAtBeginningOfLine = true;
1505 onceAgainBecauseWeAddedBreakInFront = true;
1506 }
1507 else {
1508 // we must wrap
1509 onceAgainBecauseWeAddedBreakInFront = false;
1510 bool foundWrapPosition = false;
1511 int32_t wrapPosition;
1513 nsILineBreaker *lineBreaker = nsContentUtils::LineBreaker();
1515 wrapPosition = lineBreaker->Prev(aSequenceStart,
1516 (aEnd - aSequenceStart),
1517 (aPos - aSequenceStart) + 1);
1518 if (wrapPosition != NS_LINEBREAKER_NEED_MORE_TEXT) {
1519 foundWrapPosition = true;
1520 }
1521 else {
1522 wrapPosition = lineBreaker->Next(aSequenceStart,
1523 (aEnd - aSequenceStart),
1524 (aPos - aSequenceStart));
1525 if (wrapPosition != NS_LINEBREAKER_NEED_MORE_TEXT) {
1526 foundWrapPosition = true;
1527 }
1528 }
1530 if (foundWrapPosition) {
1531 if (!mColPos && mDoFormat) {
1532 AppendIndentation(aOutputStr);
1533 }
1534 else if (mAddSpace) {
1535 aOutputStr.Append(char16_t(' '));
1536 mAddSpace = false;
1537 }
1538 aOutputStr.Append(aSequenceStart, wrapPosition);
1540 AppendNewLineToString(aOutputStr);
1541 aPos = aSequenceStart + wrapPosition;
1542 aMayIgnoreStartOfLineWhitespaceSequence = true;
1543 }
1544 else {
1545 // try some simple fallback logic
1546 // go forward up to the next whitespace position,
1547 // in the worst case this will be all the rest of the data
1549 // we update the mColPos variable with the length of
1550 // the part already parsed.
1551 mColPos += length;
1553 // now try to find the next whitespace
1554 do {
1555 if (*aPos == ' ' || *aPos == '\t' || *aPos == '\n') {
1556 break;
1557 }
1559 ++aPos;
1560 ++mColPos;
1561 } while (aPos < aEnd);
1563 if (mAddSpace) {
1564 aOutputStr.Append(char16_t(' '));
1565 mAddSpace = false;
1566 }
1567 aOutputStr.Append(aSequenceStart, aPos - aSequenceStart);
1568 }
1569 }
1570 aSequenceStartAfterAWhiteSpace = false;
1571 }
1572 } while (onceAgainBecauseWeAddedBreakInFront);
1573 }
1575 void
1576 nsXMLContentSerializer::AppendToStringFormatedWrapped(const nsASingleFragmentString& aStr,
1577 nsAString& aOutputStr)
1578 {
1579 if (mBodyOnly && !mInBody) {
1580 return;
1581 }
1583 nsASingleFragmentString::const_char_iterator pos, end, sequenceStart;
1585 aStr.BeginReading(pos);
1586 aStr.EndReading(end);
1588 bool sequenceStartAfterAWhitespace = false;
1589 if (pos < end) {
1590 nsAString::const_char_iterator end2;
1591 aOutputStr.EndReading(end2);
1592 --end2;
1593 if (*end2 == ' ' || *end2 == '\n' || *end2 == '\t') {
1594 sequenceStartAfterAWhitespace = true;
1595 }
1596 }
1598 // if the current line already has text on it, such as a tag,
1599 // leading whitespace is significant
1600 bool mayIgnoreStartOfLineWhitespaceSequence =
1601 (!mColPos || (mIsIndentationAddedOnCurrentLine &&
1602 sequenceStartAfterAWhitespace &&
1603 uint32_t(mColPos) == mIndent.Length()));
1605 while (pos < end) {
1606 sequenceStart = pos;
1608 // if beginning of a whitespace sequence
1609 if (*pos == ' ' || *pos == '\n' || *pos == '\t') {
1610 AppendFormatedWrapped_WhitespaceSequence(pos, end, sequenceStart,
1611 mayIgnoreStartOfLineWhitespaceSequence, aOutputStr);
1612 }
1613 else { // any other non-whitespace char
1614 AppendWrapped_NonWhitespaceSequence(pos, end, sequenceStart,
1615 mayIgnoreStartOfLineWhitespaceSequence, sequenceStartAfterAWhitespace, aOutputStr);
1616 }
1617 }
1618 }
1620 void
1621 nsXMLContentSerializer::AppendWrapped_WhitespaceSequence(
1622 nsASingleFragmentString::const_char_iterator &aPos,
1623 const nsASingleFragmentString::const_char_iterator aEnd,
1624 const nsASingleFragmentString::const_char_iterator aSequenceStart,
1625 nsAString &aOutputStr)
1626 {
1627 // Handle the complete sequence of whitespace.
1628 // Continue to iterate until we find the first non-whitespace char.
1629 // Updates "aPos" to point to the first unhandled char.
1630 mAddSpace = false;
1631 mIsIndentationAddedOnCurrentLine = false;
1633 bool leaveLoop = false;
1634 nsASingleFragmentString::const_char_iterator lastPos = aPos;
1636 do {
1637 switch (*aPos) {
1638 case ' ':
1639 case '\t':
1640 // if there are too many spaces on a line, we wrap
1641 if (mColPos >= mMaxColumn) {
1642 if (lastPos != aPos) {
1643 aOutputStr.Append(lastPos, aPos - lastPos);
1644 }
1645 AppendToString(mLineBreak, aOutputStr);
1646 mColPos = 0;
1647 lastPos = aPos;
1648 }
1650 ++mColPos;
1651 ++aPos;
1652 break;
1653 case '\n':
1654 if (lastPos != aPos) {
1655 aOutputStr.Append(lastPos, aPos - lastPos);
1656 }
1657 AppendToString(mLineBreak, aOutputStr);
1658 mColPos = 0;
1659 ++aPos;
1660 lastPos = aPos;
1661 break;
1662 default:
1663 leaveLoop = true;
1664 break;
1665 }
1666 } while (!leaveLoop && aPos < aEnd);
1668 if (lastPos != aPos) {
1669 aOutputStr.Append(lastPos, aPos - lastPos);
1670 }
1671 }
1673 void
1674 nsXMLContentSerializer::AppendToStringWrapped(const nsASingleFragmentString& aStr,
1675 nsAString& aOutputStr)
1676 {
1677 if (mBodyOnly && !mInBody) {
1678 return;
1679 }
1681 nsASingleFragmentString::const_char_iterator pos, end, sequenceStart;
1683 aStr.BeginReading(pos);
1684 aStr.EndReading(end);
1686 // not used in this case, but needed by AppendWrapped_NonWhitespaceSequence
1687 bool mayIgnoreStartOfLineWhitespaceSequence = false;
1688 mMayIgnoreLineBreakSequence = false;
1690 bool sequenceStartAfterAWhitespace = false;
1691 if (pos < end && !aOutputStr.IsEmpty()) {
1692 nsAString::const_char_iterator end2;
1693 aOutputStr.EndReading(end2);
1694 --end2;
1695 if (*end2 == ' ' || *end2 == '\n' || *end2 == '\t') {
1696 sequenceStartAfterAWhitespace = true;
1697 }
1698 }
1700 while (pos < end) {
1701 sequenceStart = pos;
1703 // if beginning of a whitespace sequence
1704 if (*pos == ' ' || *pos == '\n' || *pos == '\t') {
1705 sequenceStartAfterAWhitespace = true;
1706 AppendWrapped_WhitespaceSequence(pos, end, sequenceStart, aOutputStr);
1707 }
1708 else { // any other non-whitespace char
1709 AppendWrapped_NonWhitespaceSequence(pos, end, sequenceStart,
1710 mayIgnoreStartOfLineWhitespaceSequence, sequenceStartAfterAWhitespace, aOutputStr);
1711 }
1712 }
1713 }