Bumping manifests a=b2g-bump
[gecko.git] / dom / base / nsXMLContentSerializer.cpp
blob77d8ab1b65a407ad8d4ed74bcec6b0104f55f54e
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.
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)
48 nsXMLContentSerializer* it = new nsXMLContentSerializer();
49 if (!it) {
50 return NS_ERROR_OUT_OF_MEMORY;
53 return CallQueryInterface(it, aSerializer);
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)
70 nsXMLContentSerializer::~nsXMLContentSerializer()
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)
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");
100 else if (mFlags & nsIDocumentEncoder::OutputCRLineBreak) { // Mac
101 mLineBreak.Assign('\r');
103 else if (mFlags & nsIDocumentEncoder::OutputLFLineBreak) { // Unix/DOM
104 mLineBreak.Assign('\n');
106 else {
107 mLineBreak.AssignLiteral(NS_LINEBREAK); // Platform/default
110 mDoRaw = !!(mFlags & nsIDocumentEncoder::OutputRaw);
112 mDoFormat = (mFlags & nsIDocumentEncoder::OutputFormatted && !mDoRaw);
114 mDoWrap = (mFlags & nsIDocumentEncoder::OutputWrap && !mDoRaw);
116 if (!aWrapColumn) {
117 mMaxColumn = 72;
119 else {
120 mMaxColumn = aWrapColumn;
123 mPreLevel = 0;
124 mIsIndentationAddedOnCurrentLine = false;
125 return NS_OK;
128 nsresult
129 nsXMLContentSerializer::AppendTextData(nsIContent* aNode,
130 int32_t aStartOffset,
131 int32_t aEndOffset,
132 nsAString& aStr,
133 bool aTranslateEntities)
135 nsIContent* content = aNode;
136 const nsTextFragment* frag;
137 if (!content || !(frag = content->GetText())) {
138 return NS_ERROR_FAILURE;
141 int32_t fragLength = frag->GetLength();
142 int32_t endoffset = (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;
154 if (frag->Is2b()) {
155 const char16_t *strStart = frag->Get2b() + aStartOffset;
156 if (aTranslateEntities) {
157 AppendAndTranslateEntities(Substring(strStart, strStart + length), aStr);
159 else {
160 aStr.Append(Substring(strStart, strStart + length));
163 else {
164 if (aTranslateEntities) {
165 AppendAndTranslateEntities(NS_ConvertASCIItoUTF16(frag->Get1b()+aStartOffset, length), aStr);
167 else {
168 aStr.Append(NS_ConvertASCIItoUTF16(frag->Get1b()+aStartOffset, length));
172 return NS_OK;
175 NS_IMETHODIMP
176 nsXMLContentSerializer::AppendText(nsIContent* aText,
177 int32_t aStartOffset,
178 int32_t aEndOffset,
179 nsAString& aStr)
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 (mDoRaw || PreLevel() > 0) {
191 AppendToStringConvertLF(data, aStr);
193 else if (mDoFormat) {
194 AppendToStringFormatedWrapped(data, aStr);
196 else if (mDoWrap) {
197 AppendToStringWrapped(data, aStr);
199 else {
200 AppendToStringConvertLF(data, aStr);
203 return NS_OK;
206 NS_IMETHODIMP
207 nsXMLContentSerializer::AppendCDATASection(nsIContent* aCDATASection,
208 int32_t aStartOffset,
209 int32_t aEndOffset,
210 nsAString& aStr)
212 NS_ENSURE_ARG(aCDATASection);
213 nsresult rv;
215 NS_NAMED_LITERAL_STRING(cdata , "<![CDATA[");
217 if (mDoRaw || PreLevel() > 0) {
218 AppendToString(cdata, aStr);
220 else if (mDoFormat) {
221 AppendToStringFormatedWrapped(cdata, aStr);
223 else if (mDoWrap) {
224 AppendToStringWrapped(cdata, aStr);
226 else {
227 AppendToString(cdata, aStr);
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;
241 NS_IMETHODIMP
242 nsXMLContentSerializer::AppendProcessingInstruction(nsIContent* aPI,
243 int32_t aStartOffset,
244 int32_t aEndOffset,
245 nsAString& aStr)
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 (mDoRaw || PreLevel() > 0) {
264 AppendToString(start, aStr);
266 else if (mDoFormat) {
267 if (mAddSpace) {
268 AppendNewLineToString(aStr);
270 AppendToStringFormatedWrapped(start, aStr);
272 else if (mDoWrap) {
273 AppendToStringWrapped(start, aStr);
275 else {
276 AppendToString(start, aStr);
279 if (!data.IsEmpty()) {
280 AppendToString(char16_t(' '), aStr);
281 AppendToStringConvertLF(data, aStr);
283 AppendToString(NS_LITERAL_STRING("?>"), aStr);
285 MaybeFlagNewlineForRootNode(aPI);
287 return NS_OK;
290 NS_IMETHODIMP
291 nsXMLContentSerializer::AppendComment(nsIContent* aComment,
292 int32_t aStartOffset,
293 int32_t aEndOffset,
294 nsAString& aStr)
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);
314 data.Assign(frag);
317 MaybeAddNewlineForRootNode(aStr);
319 NS_NAMED_LITERAL_STRING(startComment, "<!--");
321 if (mDoRaw || PreLevel() > 0) {
322 AppendToString(startComment, aStr);
324 else if (mDoFormat) {
325 if (mAddSpace) {
326 AppendNewLineToString(aStr);
328 AppendToStringFormatedWrapped(startComment, aStr);
330 else if (mDoWrap) {
331 AppendToStringWrapped(startComment, aStr);
333 else {
334 AppendToString(startComment, aStr);
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;
347 NS_IMETHODIMP
348 nsXMLContentSerializer::AppendDoctype(nsIContent* aDocType,
349 nsAString& aStr)
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('"');
376 else {
377 quote = char16_t('\'');
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('"');
388 else {
389 quote = char16_t('\'');
391 AppendToString(quote, aStr);
392 AppendToString(systemId, aStr);
393 AppendToString(quote, aStr);
396 else if (!systemId.IsEmpty()) {
397 if (systemId.FindChar(char16_t('"')) == -1) {
398 quote = char16_t('"');
400 else {
401 quote = char16_t('\'');
403 AppendToString(NS_LITERAL_STRING(" SYSTEM "), aStr);
404 AppendToString(quote, aStr);
405 AppendToString(systemId, aStr);
406 AppendToString(quote, aStr);
409 if (!internalSubset.IsEmpty()) {
410 AppendToString(NS_LITERAL_STRING(" ["), aStr);
411 AppendToString(internalSubset, aStr);
412 AppendToString(char16_t(']'), aStr);
415 AppendToString(kGreaterThan, aStr);
416 MaybeFlagNewlineForRootNode(aDocType);
418 return NS_OK;
421 nsresult
422 nsXMLContentSerializer::PushNameSpaceDecl(const nsAString& aPrefix,
423 const nsAString& aURI,
424 nsIContent* aOwner)
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;
437 void
438 nsXMLContentSerializer::PopNameSpaceDeclsFor(nsIContent* aOwner)
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;
447 mNameSpaceStack.RemoveElementAt(index);
451 bool
452 nsXMLContentSerializer::ConfirmPrefix(nsAString& aPrefix,
453 const nsAString& aURI,
454 nsIContent* aElement,
455 bool aIsAttribute)
457 if (aPrefix.EqualsLiteral(kXMLNS)) {
458 return false;
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;
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;
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;
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;
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;
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);
552 if (prefixOK) {
553 uriMatch = true;
554 closestURIMatch.Assign(decl.mPrefix);
558 --index;
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;
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);
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;
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;
600 void
601 nsXMLContentSerializer::GenerateNewPrefix(nsAString& aPrefix)
603 aPrefix.Assign('a');
604 char buf[128];
605 PR_snprintf(buf, sizeof(buf), "%d", mPrefixIndex++);
606 AppendASCIItoUTF16(buf, aPrefix);
609 void
610 nsXMLContentSerializer::SerializeAttr(const nsAString& aPrefix,
611 const nsAString& aName,
612 const nsAString& aValue,
613 nsAString& aStr,
614 bool aDoEscapeEntities)
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(':'));
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;
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('\'') )
663 bIncludesSingle = true;
664 if ( bIncludesDouble ) break;
666 else if ( *buf == char16_t('"') )
668 bIncludesDouble = true;
669 if ( bIncludesSingle ) break;
672 // if both have been found we don't need to search further
673 if ( bIncludesDouble && bIncludesSingle ) break;
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("&amp;"));
689 if (bIncludesDouble && bIncludesSingle) {
690 sValue.ReplaceSubstring(NS_LITERAL_STRING("\""),
691 NS_LITERAL_STRING("&quot;"));
693 attrString.Append(sValue);
694 attrString.Append(cDelimiter);
696 if (mDoRaw || PreLevel() > 0) {
697 AppendToStringConvertLF(attrString, aStr);
699 else if (mDoFormat) {
700 AppendToStringFormatedWrapped(attrString, aStr);
702 else if (mDoWrap) {
703 AppendToStringWrapped(attrString, aStr);
705 else {
706 AppendToStringConvertLF(attrString, aStr);
710 uint32_t
711 nsXMLContentSerializer::ScanNamespaceDeclarations(nsIContent* aContent,
712 nsIContent *aOriginalElement,
713 const nsAString& aTagNamespaceURI)
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;
750 else {
751 // Default NS attribute does not have prefix (and the name is "xmlns")
752 PushNameSpaceDecl(EmptyString(), uriStr, aOriginalElement);
755 else {
756 PushNameSpaceDecl(nsDependentAtomString(attrName), uriStr,
757 aOriginalElement);
761 return skipAttr;
765 bool
766 nsXMLContentSerializer::IsJavaScript(nsIContent * aContent, nsIAtom* aAttrNameAtom,
767 int32_t aAttrNamespaceID, const nsAString& aValueString)
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;
791 return aContent->IsEventAttributeName(aAttrNameAtom);
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)
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);
818 else {
819 // Serialize namespace decl
820 SerializeAttr(xmlnsStr, aTagPrefix, aTagNamespaceURI, aStr, true);
822 PushNameSpaceDecl(aTagPrefix, aTagNamespaceURI, aOriginalElement);
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;
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;
847 if (attrPrefix) {
848 attrPrefix->ToString(prefixStr);
850 else {
851 prefixStr.Truncate();
854 bool addNSAttr = false;
855 if (kNameSpaceID_XMLNS != namespaceID) {
856 nsContentUtils::NameSpaceManager()->GetNameSpaceURI(namespaceID, uriStr);
857 addNSAttr = ConfirmPrefix(prefixStr, uriStr, aOriginalElement, true);
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);
876 NS_IMETHODIMP
877 nsXMLContentSerializer::AppendElementStart(Element* aElement,
878 Element* aOriginalElement,
879 nsAString& aStr)
881 NS_ENSURE_ARG(aElement);
883 nsIContent* content = aElement;
885 bool forceFormat = false;
886 if (!CheckElementStart(content, forceFormat, aStr)) {
887 return NS_OK;
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) && !mDoRaw && !PreLevel()) {
902 if (mColPos && lineBreakBeforeOpen) {
903 AppendNewLineToString(aStr);
905 else {
906 MaybeAddNewlineForRootNode(aStr);
908 if (!mColPos) {
909 AppendIndentation(aStr);
911 else if (mAddSpace) {
912 AppendToString(char16_t(' '), aStr);
913 mAddSpace = false;
916 else if (mAddSpace) {
917 AppendToString(char16_t(' '), aStr);
918 mAddSpace = false;
920 else {
921 MaybeAddNewlineForRootNode(aStr);
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);
938 AppendToString(tagLocalName, aStr);
940 MaybeEnterInPreContent(content);
942 if ((mDoFormat || forceFormat) && !mDoRaw && !PreLevel()) {
943 IncrIndentation(name);
946 SerializeAttributes(content, aOriginalElement, tagPrefix, tagNamespaceURI,
947 name, aStr, skipAttr, addNSAttr);
949 AppendEndOfElementStart(aOriginalElement, name, content->GetNameSpaceID(),
950 aStr);
952 if ((mDoFormat || forceFormat) && !mDoRaw && !PreLevel()
953 && LineBreakAfterOpen(content->GetNameSpaceID(), name)) {
954 AppendNewLineToString(aStr);
957 AfterElementStart(content, aOriginalElement, aStr);
959 return NS_OK;
962 void
963 nsXMLContentSerializer::AppendEndOfElementStart(nsIContent *aOriginalElement,
964 nsIAtom * aName,
965 int32_t aNamespaceID,
966 nsAString& aStr)
968 // We don't output a separate end tag for empty elements
969 if (!aOriginalElement->GetChildCount()) {
970 AppendToString(NS_LITERAL_STRING("/>"), aStr);
972 else {
973 AppendToString(kGreaterThan, aStr);
977 NS_IMETHODIMP
978 nsXMLContentSerializer::AppendElementEnd(Element* aElement,
979 nsAString& aStr)
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) && !mDoRaw && !PreLevel()) {
991 DecrIndentation(name);
994 if (!outputElementEnd) {
995 PopNameSpaceDeclsFor(aElement);
996 MaybeFlagNewlineForRootNode(aElement);
997 return NS_OK;
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) && !mDoRaw && !PreLevel()) {
1014 bool lineBreakBeforeClose = LineBreakBeforeClose(content->GetNameSpaceID(), name);
1016 if (mColPos && lineBreakBeforeClose) {
1017 AppendNewLineToString(aStr);
1019 if (!mColPos) {
1020 AppendIndentation(aStr);
1022 else if (mAddSpace) {
1023 AppendToString(char16_t(' '), aStr);
1024 mAddSpace = false;
1027 else if (mAddSpace) {
1028 AppendToString(char16_t(' '), aStr);
1029 mAddSpace = false;
1032 AppendToString(kEndTag, aStr);
1033 if (!tagPrefix.IsEmpty()) {
1034 AppendToString(tagPrefix, aStr);
1035 AppendToString(NS_LITERAL_STRING(":"), aStr);
1037 AppendToString(tagLocalName, aStr);
1038 AppendToString(kGreaterThan, aStr);
1040 PopNameSpaceDeclsFor(aElement);
1042 MaybeLeaveFromPreContent(content);
1044 if ((mDoFormat || forceFormat) && !mDoRaw && !PreLevel()
1045 && LineBreakAfterClose(content->GetNameSpaceID(), name)) {
1046 AppendNewLineToString(aStr);
1048 else {
1049 MaybeFlagNewlineForRootNode(aElement);
1052 AfterElementEnd(content, aStr);
1054 return NS_OK;
1057 NS_IMETHODIMP
1058 nsXMLContentSerializer::AppendDocumentStart(nsIDocument *aDocument,
1059 nsAString& aStr)
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;
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?");
1083 #endif
1085 if (!standalone.IsEmpty()) {
1086 aStr += NS_LITERAL_STRING(" standalone=\"") + standalone + endQuote;
1089 aStr.AppendLiteral("?>");
1090 mAddNewlineForRootNode = true;
1092 return NS_OK;
1095 bool
1096 nsXMLContentSerializer::CheckElementStart(nsIContent * aContent,
1097 bool & aForceFormat,
1098 nsAString& aStr)
1100 aForceFormat = false;
1101 return true;
1104 bool
1105 nsXMLContentSerializer::CheckElementEnd(nsIContent * aContent,
1106 bool & aForceFormat,
1107 nsAString& aStr)
1109 // We don't output a separate end tag for empty element
1110 aForceFormat = false;
1111 return aContent->GetChildCount() > 0;
1114 void
1115 nsXMLContentSerializer::AppendToString(const char16_t aChar,
1116 nsAString& aOutputStr)
1118 if (mBodyOnly && !mInBody) {
1119 return;
1121 mColPos += 1;
1122 aOutputStr.Append(aChar);
1125 void
1126 nsXMLContentSerializer::AppendToString(const nsAString& aStr,
1127 nsAString& aOutputStr)
1129 if (mBodyOnly && !mInBody) {
1130 return;
1132 mColPos += aStr.Length();
1133 aOutputStr.Append(aStr);
1137 static const uint16_t kGTVal = 62;
1138 static const char* kEntities[] = {
1139 "", "", "", "", "", "", "", "", "", "",
1140 "", "", "", "", "", "", "", "", "", "",
1141 "", "", "", "", "", "", "", "", "", "",
1142 "", "", "", "", "", "", "", "", "&amp;", "",
1143 "", "", "", "", "", "", "", "", "", "",
1144 "", "", "", "", "", "", "", "", "", "",
1145 "&lt;", "", "&gt;"
1148 static const char* kAttrEntities[] = {
1149 "", "", "", "", "", "", "", "", "", "",
1150 "", "", "", "", "", "", "", "", "", "",
1151 "", "", "", "", "", "", "", "", "", "",
1152 "", "", "", "", "&quot;", "", "", "", "&amp;", "",
1153 "", "", "", "", "", "", "", "", "", "",
1154 "", "", "", "", "", "", "", "", "", "",
1155 "&lt;", "", "&gt;"
1158 void
1159 nsXMLContentSerializer::AppendAndTranslateEntities(const nsAString& aStr,
1160 nsAString& aOutputStr)
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;
1191 aOutputStr.Append(fragmentStart, advanceLength);
1192 if (entityText) {
1193 AppendASCIItoUTF16(entityText, aOutputStr);
1194 advanceLength++;
1199 void
1200 nsXMLContentSerializer::MaybeAddNewlineForRootNode(nsAString& aStr)
1202 if (mAddNewlineForRootNode) {
1203 AppendNewLineToString(aStr);
1207 void
1208 nsXMLContentSerializer::MaybeFlagNewlineForRootNode(nsINode* aNode)
1210 nsINode* parent = aNode->GetParentNode();
1211 if (parent) {
1212 mAddNewlineForRootNode = parent->IsNodeOfType(nsINode::eDOCUMENT);
1216 void
1217 nsXMLContentSerializer::MaybeEnterInPreContent(nsIContent* aNode)
1219 // support of the xml:space attribute
1220 if (ShouldMaintainPreLevel() &&
1221 aNode->HasAttr(kNameSpaceID_XML, nsGkAtoms::space)) {
1222 nsAutoString space;
1223 aNode->GetAttr(kNameSpaceID_XML, nsGkAtoms::space, space);
1224 if (space.EqualsLiteral("preserve"))
1225 ++PreLevel();
1229 void
1230 nsXMLContentSerializer::MaybeLeaveFromPreContent(nsIContent* aNode)
1232 // support of the xml:space attribute
1233 if (ShouldMaintainPreLevel() &&
1234 aNode->HasAttr(kNameSpaceID_XML, nsGkAtoms::space)) {
1235 nsAutoString space;
1236 aNode->GetAttr(kNameSpaceID_XML, nsGkAtoms::space, space);
1237 if (space.EqualsLiteral("preserve"))
1238 --PreLevel();
1242 void
1243 nsXMLContentSerializer::AppendNewLineToString(nsAString& aStr)
1245 AppendToString(mLineBreak, aStr);
1246 mMayIgnoreLineBreakSequence = true;
1247 mColPos = 0;
1248 mAddSpace = false;
1249 mIsIndentationAddedOnCurrentLine = false;
1252 void
1253 nsXMLContentSerializer::AppendIndentation(nsAString& aStr)
1255 mIsIndentationAddedOnCurrentLine = true;
1256 AppendToString(mIndent, aStr);
1257 mAddSpace = false;
1258 mMayIgnoreLineBreakSequence = false;
1261 void
1262 nsXMLContentSerializer::IncrIndentation(nsIAtom* aName)
1264 // we want to keep the source readable
1265 if (mDoWrap &&
1266 mIndent.Length() >= uint32_t(mMaxColumn) - MIN_INDENTED_LINE_LENGTH) {
1267 ++mIndentOverflow;
1269 else {
1270 mIndent.AppendLiteral(INDENT_STRING);
1274 void
1275 nsXMLContentSerializer::DecrIndentation(nsIAtom* aName)
1277 if(mIndentOverflow)
1278 --mIndentOverflow;
1279 else
1280 mIndent.Cut(0, INDENT_STRING_LENGTH);
1283 bool
1284 nsXMLContentSerializer::LineBreakBeforeOpen(int32_t aNamespaceID, nsIAtom* aName)
1286 return mAddSpace;
1289 bool
1290 nsXMLContentSerializer::LineBreakAfterOpen(int32_t aNamespaceID, nsIAtom* aName)
1292 return false;
1295 bool
1296 nsXMLContentSerializer::LineBreakBeforeClose(int32_t aNamespaceID, nsIAtom* aName)
1298 return mAddSpace;
1301 bool
1302 nsXMLContentSerializer::LineBreakAfterClose(int32_t aNamespaceID, nsIAtom* aName)
1304 return false;
1307 void
1308 nsXMLContentSerializer::AppendToStringConvertLF(const nsAString& aStr,
1309 nsAString& aOutputStr)
1311 if (mBodyOnly && !mInBody) {
1312 return;
1315 if (mDoRaw) {
1316 AppendToString(aStr, aOutputStr);
1318 else {
1319 // Convert line-endings to mLineBreak
1320 uint32_t start = 0;
1321 uint32_t theLen = aStr.Length();
1322 while (start < theLen) {
1323 int32_t eol = aStr.FindChar('\n', start);
1324 if (eol == kNotFound) {
1325 nsDependentSubstring dataSubstring(aStr, start, theLen - start);
1326 AppendToString(dataSubstring, aOutputStr);
1327 start = theLen;
1328 // if there was a line break before this substring
1329 // AppendNewLineToString was called, so we should reverse
1330 // this flag
1331 mMayIgnoreLineBreakSequence = false;
1333 else {
1334 nsDependentSubstring dataSubstring(aStr, start, eol - start);
1335 AppendToString(dataSubstring, aOutputStr);
1336 AppendNewLineToString(aOutputStr);
1337 start = eol + 1;
1343 void
1344 nsXMLContentSerializer::AppendFormatedWrapped_WhitespaceSequence(
1345 nsASingleFragmentString::const_char_iterator &aPos,
1346 const nsASingleFragmentString::const_char_iterator aEnd,
1347 const nsASingleFragmentString::const_char_iterator aSequenceStart,
1348 bool &aMayIgnoreStartOfLineWhitespaceSequence,
1349 nsAString &aOutputStr)
1351 // Handle the complete sequence of whitespace.
1352 // Continue to iterate until we find the first non-whitespace char.
1353 // Updates "aPos" to point to the first unhandled char.
1354 // Also updates the aMayIgnoreStartOfLineWhitespaceSequence flag,
1355 // as well as the other "global" state flags.
1357 bool sawBlankOrTab = false;
1358 bool leaveLoop = false;
1360 do {
1361 switch (*aPos) {
1362 case ' ':
1363 case '\t':
1364 sawBlankOrTab = true;
1365 // no break
1366 case '\n':
1367 ++aPos;
1368 // do not increase mColPos,
1369 // because we will reduce the whitespace to a single char
1370 break;
1371 default:
1372 leaveLoop = true;
1373 break;
1375 } while (!leaveLoop && aPos < aEnd);
1377 if (mAddSpace) {
1378 // if we had previously been asked to add space,
1379 // our situation has not changed
1381 else if (!sawBlankOrTab && mMayIgnoreLineBreakSequence) {
1382 // nothing to do in the case where line breaks have already been added
1383 // before the call of AppendToStringWrapped
1384 // and only if we found line break in the sequence
1385 mMayIgnoreLineBreakSequence = false;
1387 else if (aMayIgnoreStartOfLineWhitespaceSequence) {
1388 // nothing to do
1389 aMayIgnoreStartOfLineWhitespaceSequence = false;
1391 else {
1392 if (sawBlankOrTab) {
1393 if (mDoWrap && mColPos + 1 >= mMaxColumn) {
1394 // no much sense in delaying, we only have one slot left,
1395 // let's write a break now
1396 aOutputStr.Append(mLineBreak);
1397 mColPos = 0;
1398 mIsIndentationAddedOnCurrentLine = false;
1399 mMayIgnoreLineBreakSequence = true;
1401 else {
1402 // do not write out yet, we may write out either a space or a linebreak
1403 // let's delay writing it out until we know more
1404 mAddSpace = true;
1405 ++mColPos; // eat a slot of available space
1408 else {
1409 // Asian text usually does not contain spaces, therefore we should not
1410 // transform a linebreak into a space.
1411 // Since we only saw linebreaks, but no spaces or tabs,
1412 // let's write a linebreak now.
1413 AppendNewLineToString(aOutputStr);
1418 void
1419 nsXMLContentSerializer::AppendWrapped_NonWhitespaceSequence(
1420 nsASingleFragmentString::const_char_iterator &aPos,
1421 const nsASingleFragmentString::const_char_iterator aEnd,
1422 const nsASingleFragmentString::const_char_iterator aSequenceStart,
1423 bool &aMayIgnoreStartOfLineWhitespaceSequence,
1424 bool &aSequenceStartAfterAWhiteSpace,
1425 nsAString& aOutputStr)
1427 mMayIgnoreLineBreakSequence = false;
1428 aMayIgnoreStartOfLineWhitespaceSequence = false;
1430 // Handle the complete sequence of non-whitespace in this block
1431 // Iterate until we find the first whitespace char or an aEnd condition
1432 // Updates "aPos" to point to the first unhandled char.
1433 // Also updates the aMayIgnoreStartOfLineWhitespaceSequence flag,
1434 // as well as the other "global" state flags.
1436 bool thisSequenceStartsAtBeginningOfLine = !mColPos;
1437 bool onceAgainBecauseWeAddedBreakInFront = false;
1438 bool foundWhitespaceInLoop;
1439 uint32_t length, colPos;
1441 do {
1443 if (mColPos) {
1444 colPos = mColPos;
1446 else {
1447 if (mDoFormat && !mDoRaw && !PreLevel() && !onceAgainBecauseWeAddedBreakInFront) {
1448 colPos = mIndent.Length();
1450 else
1451 colPos = 0;
1453 foundWhitespaceInLoop = false;
1454 length = 0;
1455 // we iterate until the next whitespace character
1456 // or until we reach the maximum of character per line
1457 // or until the end of the string to add.
1458 do {
1459 if (*aPos == ' ' || *aPos == '\t' || *aPos == '\n') {
1460 foundWhitespaceInLoop = true;
1461 break;
1464 ++aPos;
1465 ++length;
1466 } while ( (!mDoWrap || colPos + length < mMaxColumn) && aPos < aEnd);
1468 // in the case we don't reached the end of the string, but we reached the maxcolumn,
1469 // we see if there is a whitespace after the maxcolumn
1470 // if yes, then we can append directly the string instead of
1471 // appending a new line etc.
1472 if (*aPos == ' ' || *aPos == '\t' || *aPos == '\n') {
1473 foundWhitespaceInLoop = true;
1476 if (aPos == aEnd || foundWhitespaceInLoop) {
1477 // there is enough room for the complete block we found
1478 if (mDoFormat && !mColPos) {
1479 AppendIndentation(aOutputStr);
1481 else if (mAddSpace) {
1482 aOutputStr.Append(char16_t(' '));
1483 mAddSpace = false;
1486 mColPos += length;
1487 aOutputStr.Append(aSequenceStart, aPos - aSequenceStart);
1489 // We have not yet reached the max column, we will continue to
1490 // fill the current line in the next outer loop iteration
1491 // (this one in AppendToStringWrapped)
1492 // make sure we return in this outer loop
1493 onceAgainBecauseWeAddedBreakInFront = false;
1495 else { // we reach the max column
1496 if (!thisSequenceStartsAtBeginningOfLine &&
1497 (mAddSpace || (!mDoFormat && aSequenceStartAfterAWhiteSpace))) {
1498 // when !mDoFormat, mAddSpace is not used, mAddSpace is always false
1499 // so, in the case where mDoWrap && !mDoFormat, if we want to enter in this condition...
1501 // We can avoid to wrap. We try to add the whole block
1502 // in an empty new line
1504 AppendNewLineToString(aOutputStr);
1505 aPos = aSequenceStart;
1506 thisSequenceStartsAtBeginningOfLine = true;
1507 onceAgainBecauseWeAddedBreakInFront = true;
1509 else {
1510 // we must wrap
1511 onceAgainBecauseWeAddedBreakInFront = false;
1512 bool foundWrapPosition = false;
1513 int32_t wrapPosition;
1515 nsILineBreaker *lineBreaker = nsContentUtils::LineBreaker();
1517 wrapPosition = lineBreaker->Prev(aSequenceStart,
1518 (aEnd - aSequenceStart),
1519 (aPos - aSequenceStart) + 1);
1520 if (wrapPosition != NS_LINEBREAKER_NEED_MORE_TEXT) {
1521 foundWrapPosition = true;
1523 else {
1524 wrapPosition = lineBreaker->Next(aSequenceStart,
1525 (aEnd - aSequenceStart),
1526 (aPos - aSequenceStart));
1527 if (wrapPosition != NS_LINEBREAKER_NEED_MORE_TEXT) {
1528 foundWrapPosition = true;
1532 if (foundWrapPosition) {
1533 if (!mColPos && mDoFormat) {
1534 AppendIndentation(aOutputStr);
1536 else if (mAddSpace) {
1537 aOutputStr.Append(char16_t(' '));
1538 mAddSpace = false;
1540 aOutputStr.Append(aSequenceStart, wrapPosition);
1542 AppendNewLineToString(aOutputStr);
1543 aPos = aSequenceStart + wrapPosition;
1544 aMayIgnoreStartOfLineWhitespaceSequence = true;
1546 else {
1547 // try some simple fallback logic
1548 // go forward up to the next whitespace position,
1549 // in the worst case this will be all the rest of the data
1551 // we update the mColPos variable with the length of
1552 // the part already parsed.
1553 mColPos += length;
1555 // now try to find the next whitespace
1556 do {
1557 if (*aPos == ' ' || *aPos == '\t' || *aPos == '\n') {
1558 break;
1561 ++aPos;
1562 ++mColPos;
1563 } while (aPos < aEnd);
1565 if (mAddSpace) {
1566 aOutputStr.Append(char16_t(' '));
1567 mAddSpace = false;
1569 aOutputStr.Append(aSequenceStart, aPos - aSequenceStart);
1572 aSequenceStartAfterAWhiteSpace = false;
1574 } while (onceAgainBecauseWeAddedBreakInFront);
1577 void
1578 nsXMLContentSerializer::AppendToStringFormatedWrapped(const nsASingleFragmentString& aStr,
1579 nsAString& aOutputStr)
1581 if (mBodyOnly && !mInBody) {
1582 return;
1585 nsASingleFragmentString::const_char_iterator pos, end, sequenceStart;
1587 aStr.BeginReading(pos);
1588 aStr.EndReading(end);
1590 bool sequenceStartAfterAWhitespace = false;
1591 if (pos < end) {
1592 nsAString::const_char_iterator end2;
1593 aOutputStr.EndReading(end2);
1594 --end2;
1595 if (*end2 == ' ' || *end2 == '\n' || *end2 == '\t') {
1596 sequenceStartAfterAWhitespace = true;
1600 // if the current line already has text on it, such as a tag,
1601 // leading whitespace is significant
1602 bool mayIgnoreStartOfLineWhitespaceSequence =
1603 (!mColPos || (mIsIndentationAddedOnCurrentLine &&
1604 sequenceStartAfterAWhitespace &&
1605 uint32_t(mColPos) == mIndent.Length()));
1607 while (pos < end) {
1608 sequenceStart = pos;
1610 // if beginning of a whitespace sequence
1611 if (*pos == ' ' || *pos == '\n' || *pos == '\t') {
1612 AppendFormatedWrapped_WhitespaceSequence(pos, end, sequenceStart,
1613 mayIgnoreStartOfLineWhitespaceSequence, aOutputStr);
1615 else { // any other non-whitespace char
1616 AppendWrapped_NonWhitespaceSequence(pos, end, sequenceStart,
1617 mayIgnoreStartOfLineWhitespaceSequence, sequenceStartAfterAWhitespace, aOutputStr);
1622 void
1623 nsXMLContentSerializer::AppendWrapped_WhitespaceSequence(
1624 nsASingleFragmentString::const_char_iterator &aPos,
1625 const nsASingleFragmentString::const_char_iterator aEnd,
1626 const nsASingleFragmentString::const_char_iterator aSequenceStart,
1627 nsAString &aOutputStr)
1629 // Handle the complete sequence of whitespace.
1630 // Continue to iterate until we find the first non-whitespace char.
1631 // Updates "aPos" to point to the first unhandled char.
1632 mAddSpace = false;
1633 mIsIndentationAddedOnCurrentLine = false;
1635 bool leaveLoop = false;
1636 nsASingleFragmentString::const_char_iterator lastPos = aPos;
1638 do {
1639 switch (*aPos) {
1640 case ' ':
1641 case '\t':
1642 // if there are too many spaces on a line, we wrap
1643 if (mColPos >= mMaxColumn) {
1644 if (lastPos != aPos) {
1645 aOutputStr.Append(lastPos, aPos - lastPos);
1647 AppendToString(mLineBreak, aOutputStr);
1648 mColPos = 0;
1649 lastPos = aPos;
1652 ++mColPos;
1653 ++aPos;
1654 break;
1655 case '\n':
1656 if (lastPos != aPos) {
1657 aOutputStr.Append(lastPos, aPos - lastPos);
1659 AppendToString(mLineBreak, aOutputStr);
1660 mColPos = 0;
1661 ++aPos;
1662 lastPos = aPos;
1663 break;
1664 default:
1665 leaveLoop = true;
1666 break;
1668 } while (!leaveLoop && aPos < aEnd);
1670 if (lastPos != aPos) {
1671 aOutputStr.Append(lastPos, aPos - lastPos);
1675 void
1676 nsXMLContentSerializer::AppendToStringWrapped(const nsASingleFragmentString& aStr,
1677 nsAString& aOutputStr)
1679 if (mBodyOnly && !mInBody) {
1680 return;
1683 nsASingleFragmentString::const_char_iterator pos, end, sequenceStart;
1685 aStr.BeginReading(pos);
1686 aStr.EndReading(end);
1688 // not used in this case, but needed by AppendWrapped_NonWhitespaceSequence
1689 bool mayIgnoreStartOfLineWhitespaceSequence = false;
1690 mMayIgnoreLineBreakSequence = false;
1692 bool sequenceStartAfterAWhitespace = false;
1693 if (pos < end && !aOutputStr.IsEmpty()) {
1694 nsAString::const_char_iterator end2;
1695 aOutputStr.EndReading(end2);
1696 --end2;
1697 if (*end2 == ' ' || *end2 == '\n' || *end2 == '\t') {
1698 sequenceStartAfterAWhitespace = true;
1702 while (pos < end) {
1703 sequenceStart = pos;
1705 // if beginning of a whitespace sequence
1706 if (*pos == ' ' || *pos == '\n' || *pos == '\t') {
1707 sequenceStartAfterAWhitespace = true;
1708 AppendWrapped_WhitespaceSequence(pos, end, sequenceStart, aOutputStr);
1710 else { // any other non-whitespace char
1711 AppendWrapped_NonWhitespaceSequence(pos, end, sequenceStart,
1712 mayIgnoreStartOfLineWhitespaceSequence, sequenceStartAfterAWhitespace, aOutputStr);
1717 bool
1718 nsXMLContentSerializer::ShouldMaintainPreLevel() const
1720 // Only attempt to maintain the pre level for consumers who care about it.
1721 return !mDoRaw || (mFlags & nsIDocumentEncoder::OutputNoFormattingInPre);