content/base/src/nsXMLContentSerializer.cpp

changeset 0
6474c204b198
equal deleted inserted replaced
-1:000000000000 0:7270307976a0
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/. */
5
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 */
11
12 #include "nsXMLContentSerializer.h"
13
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"
32
33 using namespace mozilla::dom;
34
35 #define kXMLNS "xmlns"
36
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
41
42 // the string used to indent.
43 #define INDENT_STRING " "
44 #define INDENT_STRING_LENGTH 2
45
46 nsresult NS_NewXMLContentSerializer(nsIContentSerializer** aSerializer)
47 {
48 nsXMLContentSerializer* it = new nsXMLContentSerializer();
49 if (!it) {
50 return NS_ERROR_OUT_OF_MEMORY;
51 }
52
53 return CallQueryInterface(it, aSerializer);
54 }
55
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 }
69
70 nsXMLContentSerializer::~nsXMLContentSerializer()
71 {
72 }
73
74 NS_IMPL_ISUPPORTS(nsXMLContentSerializer, nsIContentSerializer)
75
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;
91
92 mCharset = aCharSet;
93 mFlags = aFlags;
94
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 }
109
110 mDoRaw = !!(mFlags & nsIDocumentEncoder::OutputRaw);
111
112 mDoFormat = (mFlags & nsIDocumentEncoder::OutputFormatted && !mDoRaw);
113
114 mDoWrap = (mFlags & nsIDocumentEncoder::OutputWrap && !mDoRaw);
115
116 if (!aWrapColumn) {
117 mMaxColumn = 72;
118 }
119 else {
120 mMaxColumn = aWrapColumn;
121 }
122
123 mPreLevel = 0;
124 mIsIndentationAddedOnCurrentLine = false;
125 return NS_OK;
126 }
127
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 }
140
141 int32_t fragLength = frag->GetLength();
142 int32_t endoffset = (aEndOffset == -1) ? fragLength : std::min(aEndOffset, fragLength);
143 int32_t length = endoffset - aStartOffset;
144
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!");
147
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 }
153
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 }
171
172 return NS_OK;
173 }
174
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);
182
183 nsAutoString data;
184 nsresult rv;
185
186 rv = AppendTextData(aText, aStartOffset, aEndOffset, data, true);
187 if (NS_FAILED(rv))
188 return NS_ERROR_FAILURE;
189
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 }
202
203 return NS_OK;
204 }
205
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;
214
215 NS_NAMED_LITERAL_STRING(cdata , "<![CDATA[");
216
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 }
229
230 nsAutoString data;
231 rv = AppendTextData(aCDATASection, aStartOffset, aEndOffset, data, false);
232 if (NS_FAILED(rv)) return NS_ERROR_FAILURE;
233
234 AppendToStringConvertLF(data, aStr);
235
236 AppendToString(NS_LITERAL_STRING("]]>"), aStr);
237
238 return NS_OK;
239 }
240
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;
251
252 MaybeAddNewlineForRootNode(aStr);
253
254 rv = pi->GetTarget(target);
255 if (NS_FAILED(rv)) return NS_ERROR_FAILURE;
256
257 rv = pi->GetData(data);
258 if (NS_FAILED(rv)) return NS_ERROR_FAILURE;
259
260 start.AppendLiteral("<?");
261 start.Append(target);
262
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 }
278
279 if (!data.IsEmpty()) {
280 AppendToString(char16_t(' '), aStr);
281 AppendToStringConvertLF(data, aStr);
282 }
283 AppendToString(NS_LITERAL_STRING("?>"), aStr);
284
285 MaybeFlagNewlineForRootNode(aPI);
286
287 return NS_OK;
288 }
289
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;
300
301 rv = comment->GetData(data);
302 if (NS_FAILED(rv)) return NS_ERROR_FAILURE;
303
304 int32_t dataLength = data.Length();
305 if (aStartOffset || (aEndOffset != -1 && aEndOffset < dataLength)) {
306 int32_t length =
307 (aEndOffset == -1) ? dataLength : std::min(aEndOffset, dataLength);
308 length -= aStartOffset;
309
310 nsAutoString frag;
311 if (length > 0) {
312 data.Mid(frag, aStartOffset, length);
313 }
314 data.Assign(frag);
315 }
316
317 MaybeAddNewlineForRootNode(aStr);
318
319 NS_NAMED_LITERAL_STRING(startComment, "<!--");
320
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 }
336
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);
341
342 MaybeFlagNewlineForRootNode(aComment);
343
344 return NS_OK;
345 }
346
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;
355
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;
364
365 MaybeAddNewlineForRootNode(aStr);
366
367 AppendToString(NS_LITERAL_STRING("<!DOCTYPE "), aStr);
368 AppendToString(name, aStr);
369
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);
382
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 }
408
409 if (!internalSubset.IsEmpty()) {
410 AppendToString(NS_LITERAL_STRING(" ["), aStr);
411 AppendToString(internalSubset, aStr);
412 AppendToString(char16_t(']'), aStr);
413 }
414
415 AppendToString(kGreaterThan, aStr);
416 MaybeFlagNewlineForRootNode(aDocType);
417
418 return NS_OK;
419 }
420
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;
428
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 }
436
437 void
438 nsXMLContentSerializer::PopNameSpaceDeclsFor(nsIContent* aOwner)
439 {
440 int32_t index, count;
441
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 }
450
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 }
460
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");
465
466 return false;
467 }
468
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 }
477
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 }
484
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;
490
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;
495
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)) {
502
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 }
512
513 haveSeenOurPrefix = true;
514
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.");
530
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 }
541
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 }
551
552 if (prefixOK) {
553 uriMatch = true;
554 closestURIMatch.Assign(decl.mPrefix);
555 }
556 }
557
558 --index;
559 }
560
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)
567
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 }
575
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 }
585
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 }
594
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 }
599
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 }
608
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_;
621
622 attrString.Append(char16_t(' '));
623 if (!aPrefix.IsEmpty()) {
624 attrString.Append(aPrefix);
625 attrString.Append(char16_t(':'));
626 }
627 attrString.Append(aName);
628
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("=\"");
633
634 mInAttribute = true;
635 AppendAndTranslateEntities(aValue, attrString);
636 mInAttribute = false;
637
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.
650
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 }
675
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;"));
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 }
709
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;
717
718 count = aContent->GetAttrCount();
719
720 // First scan for namespace declarations, pushing each on the stack
721 uint32_t skipAttr = count;
722 for (index = 0; index < count; index++) {
723
724 const nsAttrName* name = aContent->GetAttrNameAt(index);
725 int32_t namespaceID = name->NamespaceID();
726 nsIAtom *attrName = name->LocalName();
727
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);
737
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 }
763
764
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();
772
773 if (aAttrNamespaceID == kNameSpaceID_None &&
774 (isHtml || isXul || isSvg) &&
775 (aAttrNameAtom == nsGkAtoms::href ||
776 aAttrNameAtom == nsGkAtoms::src)) {
777
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 }
790
791 return aContent->IsEventAttributeName(aAttrNameAtom);
792 }
793
794
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 {
805
806 nsAutoString prefixStr, uriStr, valueStr;
807 nsAutoString xmlnsStr;
808 xmlnsStr.AssignLiteral(kXMLNS);
809 uint32_t index, count;
810
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 }
824
825 count = aContent->GetAttrCount();
826
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 }
834
835 const nsAttrName* name = aContent->GetAttrNameAt(index);
836 int32_t namespaceID = name->NamespaceID();
837 nsIAtom* attrName = name->LocalName();
838 nsIAtom* attrPrefix = name->GetPrefix();
839
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 }
846
847 if (attrPrefix) {
848 attrPrefix->ToString(prefixStr);
849 }
850 else {
851 prefixStr.Truncate();
852 }
853
854 bool addNSAttr = false;
855 if (kNameSpaceID_XMLNS != namespaceID) {
856 nsContentUtils::NameSpaceManager()->GetNameSpaceURI(namespaceID, uriStr);
857 addNSAttr = ConfirmPrefix(prefixStr, uriStr, aOriginalElement, true);
858 }
859
860 aContent->GetAttr(namespaceID, attrName, valueStr);
861
862 nsDependentAtomString nameStr(attrName);
863 bool isJS = IsJavaScript(aContent, attrName, namespaceID, valueStr);
864
865 SerializeAttr(prefixStr, nameStr, valueStr, aStr, !isJS);
866
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 }
875
876 NS_IMETHODIMP
877 nsXMLContentSerializer::AppendElementStart(Element* aElement,
878 Element* aOriginalElement,
879 nsAString& aStr)
880 {
881 NS_ENSURE_ARG(aElement);
882
883 nsIContent* content = aElement;
884
885 bool forceFormat = false;
886 if (!CheckElementStart(content, forceFormat, aStr)) {
887 return NS_OK;
888 }
889
890 nsAutoString tagPrefix, tagLocalName, tagNamespaceURI;
891 aElement->NodeInfo()->GetPrefix(tagPrefix);
892 aElement->NodeInfo()->GetName(tagLocalName);
893 aElement->NodeInfo()->GetNamespaceURI(tagNamespaceURI);
894
895 uint32_t skipAttr = ScanNamespaceDeclarations(content,
896 aOriginalElement, tagNamespaceURI);
897
898 nsIAtom *name = content->Tag();
899 bool lineBreakBeforeOpen = LineBreakBeforeOpen(content->GetNameSpaceID(), name);
900
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 }
923
924 // Always reset to avoid false newlines in case MaybeAddNewlineForRootNode wasn't
925 // called
926 mAddNewlineForRootNode = false;
927
928 bool addNSAttr;
929 addNSAttr = ConfirmPrefix(tagPrefix, tagNamespaceURI, aOriginalElement,
930 false);
931
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);
939
940 MaybeEnterInPreContent(content);
941
942 if ((mDoFormat || forceFormat) && !mPreLevel && !mDoRaw) {
943 IncrIndentation(name);
944 }
945
946 SerializeAttributes(content, aOriginalElement, tagPrefix, tagNamespaceURI,
947 name, aStr, skipAttr, addNSAttr);
948
949 AppendEndOfElementStart(aOriginalElement, name, content->GetNameSpaceID(),
950 aStr);
951
952 if ((mDoFormat || forceFormat) && !mPreLevel
953 && !mDoRaw && LineBreakAfterOpen(content->GetNameSpaceID(), name)) {
954 AppendNewLineToString(aStr);
955 }
956
957 AfterElementStart(content, aOriginalElement, aStr);
958
959 return NS_OK;
960 }
961
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 }
976
977 NS_IMETHODIMP
978 nsXMLContentSerializer::AppendElementEnd(Element* aElement,
979 nsAString& aStr)
980 {
981 NS_ENSURE_ARG(aElement);
982
983 nsIContent* content = aElement;
984
985 bool forceFormat = false, outputElementEnd;
986 outputElementEnd = CheckElementEnd(content, forceFormat, aStr);
987
988 nsIAtom *name = content->Tag();
989
990 if ((mDoFormat || forceFormat) && !mPreLevel && !mDoRaw) {
991 DecrIndentation(name);
992 }
993
994 if (!outputElementEnd) {
995 PopNameSpaceDeclsFor(aElement);
996 MaybeFlagNewlineForRootNode(aElement);
997 return NS_OK;
998 }
999
1000 nsAutoString tagPrefix, tagLocalName, tagNamespaceURI;
1001
1002 aElement->NodeInfo()->GetPrefix(tagPrefix);
1003 aElement->NodeInfo()->GetName(tagLocalName);
1004 aElement->NodeInfo()->GetNamespaceURI(tagNamespaceURI);
1005
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!");
1011
1012 if ((mDoFormat || forceFormat) && !mPreLevel && !mDoRaw) {
1013
1014 bool lineBreakBeforeClose = LineBreakBeforeClose(content->GetNameSpaceID(), name);
1015
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 }
1031
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);
1039
1040 PopNameSpaceDeclsFor(aElement);
1041
1042 MaybeLeaveFromPreContent(content);
1043
1044 if ((mDoFormat || forceFormat) && !mPreLevel
1045 && !mDoRaw && LineBreakAfterClose(content->GetNameSpaceID(), name)) {
1046 AppendNewLineToString(aStr);
1047 }
1048 else {
1049 MaybeFlagNewlineForRootNode(aElement);
1050 }
1051
1052 AfterElementEnd(content, aStr);
1053
1054 return NS_OK;
1055 }
1056
1057 NS_IMETHODIMP
1058 nsXMLContentSerializer::AppendDocumentStart(nsIDocument *aDocument,
1059 nsAString& aStr)
1060 {
1061 NS_ENSURE_ARG_POINTER(aDocument);
1062
1063 nsAutoString version, encoding, standalone;
1064 aDocument->GetXMLDeclaration(version, encoding, standalone);
1065
1066 if (version.IsEmpty())
1067 return NS_OK; // A declaration must have version, or there is no decl
1068
1069 NS_NAMED_LITERAL_STRING(endQuote, "\"");
1070
1071 aStr += NS_LITERAL_STRING("<?xml version=\"") + version + endQuote;
1072
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
1084
1085 if (!standalone.IsEmpty()) {
1086 aStr += NS_LITERAL_STRING(" standalone=\"") + standalone + endQuote;
1087 }
1088
1089 aStr.AppendLiteral("?>");
1090 mAddNewlineForRootNode = true;
1091
1092 return NS_OK;
1093 }
1094
1095 bool
1096 nsXMLContentSerializer::CheckElementStart(nsIContent * aContent,
1097 bool & aForceFormat,
1098 nsAString& aStr)
1099 {
1100 aForceFormat = false;
1101 return true;
1102 }
1103
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 }
1113
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 }
1124
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 }
1135
1136
1137 static const uint16_t kGTVal = 62;
1138 static const char* kEntities[] = {
1139 "", "", "", "", "", "", "", "", "", "",
1140 "", "", "", "", "", "", "", "", "", "",
1141 "", "", "", "", "", "", "", "", "", "",
1142 "", "", "", "", "", "", "", "", "&amp;", "",
1143 "", "", "", "", "", "", "", "", "", "",
1144 "", "", "", "", "", "", "", "", "", "",
1145 "&lt;", "", "&gt;"
1146 };
1147
1148 static const char* kAttrEntities[] = {
1149 "", "", "", "", "", "", "", "", "", "",
1150 "", "", "", "", "", "", "", "", "", "",
1151 "", "", "", "", "", "", "", "", "", "",
1152 "", "", "", "", "&quot;", "", "", "", "&amp;", "",
1153 "", "", "", "", "", "", "", "", "", "",
1154 "", "", "", "", "", "", "", "", "", "",
1155 "&lt;", "", "&gt;"
1156 };
1157
1158 void
1159 nsXMLContentSerializer::AppendAndTranslateEntities(const nsAString& aStr,
1160 nsAString& aOutputStr)
1161 {
1162 nsReadingIterator<char16_t> done_reading;
1163 aStr.EndReading(done_reading);
1164
1165 // for each chunk of |aString|...
1166 uint32_t advanceLength = 0;
1167 nsReadingIterator<char16_t> iter;
1168
1169 const char **entityTable = mInAttribute ? kAttrEntities : kEntities;
1170
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;
1179
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 }
1190
1191 aOutputStr.Append(fragmentStart, advanceLength);
1192 if (entityText) {
1193 AppendASCIItoUTF16(entityText, aOutputStr);
1194 advanceLength++;
1195 }
1196 }
1197 }
1198
1199 void
1200 nsXMLContentSerializer::MaybeAddNewlineForRootNode(nsAString& aStr)
1201 {
1202 if (mAddNewlineForRootNode) {
1203 AppendNewLineToString(aStr);
1204 }
1205 }
1206
1207 void
1208 nsXMLContentSerializer::MaybeFlagNewlineForRootNode(nsINode* aNode)
1209 {
1210 nsINode* parent = aNode->GetParentNode();
1211 if (parent) {
1212 mAddNewlineForRootNode = parent->IsNodeOfType(nsINode::eDOCUMENT);
1213 }
1214 }
1215
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 }
1227
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 }
1239
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 }
1249
1250 void
1251 nsXMLContentSerializer::AppendIndentation(nsAString& aStr)
1252 {
1253 mIsIndentationAddedOnCurrentLine = true;
1254 AppendToString(mIndent, aStr);
1255 mAddSpace = false;
1256 mMayIgnoreLineBreakSequence = false;
1257 }
1258
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 }
1271
1272 void
1273 nsXMLContentSerializer::DecrIndentation(nsIAtom* aName)
1274 {
1275 if(mIndentOverflow)
1276 --mIndentOverflow;
1277 else
1278 mIndent.Cut(0, INDENT_STRING_LENGTH);
1279 }
1280
1281 bool
1282 nsXMLContentSerializer::LineBreakBeforeOpen(int32_t aNamespaceID, nsIAtom* aName)
1283 {
1284 return mAddSpace;
1285 }
1286
1287 bool
1288 nsXMLContentSerializer::LineBreakAfterOpen(int32_t aNamespaceID, nsIAtom* aName)
1289 {
1290 return false;
1291 }
1292
1293 bool
1294 nsXMLContentSerializer::LineBreakBeforeClose(int32_t aNamespaceID, nsIAtom* aName)
1295 {
1296 return mAddSpace;
1297 }
1298
1299 bool
1300 nsXMLContentSerializer::LineBreakAfterClose(int32_t aNamespaceID, nsIAtom* aName)
1301 {
1302 return false;
1303 }
1304
1305 void
1306 nsXMLContentSerializer::AppendToStringConvertLF(const nsAString& aStr,
1307 nsAString& aOutputStr)
1308 {
1309 if (mBodyOnly && !mInBody) {
1310 return;
1311 }
1312
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 }
1340
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.
1354
1355 bool sawBlankOrTab = false;
1356 bool leaveLoop = false;
1357
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);
1374
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 }
1415
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;
1427
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.
1433
1434 bool thisSequenceStartsAtBeginningOfLine = !mColPos;
1435 bool onceAgainBecauseWeAddedBreakInFront = false;
1436 bool foundWhitespaceInLoop;
1437 uint32_t length, colPos;
1438
1439 do {
1440
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 }
1461
1462 ++aPos;
1463 ++length;
1464 } while ( (!mDoWrap || colPos + length < mMaxColumn) && aPos < aEnd);
1465
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 }
1473
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 }
1483
1484 mColPos += length;
1485 aOutputStr.Append(aSequenceStart, aPos - aSequenceStart);
1486
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...
1498
1499 // We can avoid to wrap. We try to add the whole block
1500 // in an empty new line
1501
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;
1512
1513 nsILineBreaker *lineBreaker = nsContentUtils::LineBreaker();
1514
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 }
1529
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);
1539
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
1548
1549 // we update the mColPos variable with the length of
1550 // the part already parsed.
1551 mColPos += length;
1552
1553 // now try to find the next whitespace
1554 do {
1555 if (*aPos == ' ' || *aPos == '\t' || *aPos == '\n') {
1556 break;
1557 }
1558
1559 ++aPos;
1560 ++mColPos;
1561 } while (aPos < aEnd);
1562
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 }
1574
1575 void
1576 nsXMLContentSerializer::AppendToStringFormatedWrapped(const nsASingleFragmentString& aStr,
1577 nsAString& aOutputStr)
1578 {
1579 if (mBodyOnly && !mInBody) {
1580 return;
1581 }
1582
1583 nsASingleFragmentString::const_char_iterator pos, end, sequenceStart;
1584
1585 aStr.BeginReading(pos);
1586 aStr.EndReading(end);
1587
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 }
1597
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()));
1604
1605 while (pos < end) {
1606 sequenceStart = pos;
1607
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 }
1619
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;
1632
1633 bool leaveLoop = false;
1634 nsASingleFragmentString::const_char_iterator lastPos = aPos;
1635
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 }
1649
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);
1667
1668 if (lastPos != aPos) {
1669 aOutputStr.Append(lastPos, aPos - lastPos);
1670 }
1671 }
1672
1673 void
1674 nsXMLContentSerializer::AppendToStringWrapped(const nsASingleFragmentString& aStr,
1675 nsAString& aOutputStr)
1676 {
1677 if (mBodyOnly && !mInBody) {
1678 return;
1679 }
1680
1681 nsASingleFragmentString::const_char_iterator pos, end, sequenceStart;
1682
1683 aStr.BeginReading(pos);
1684 aStr.EndReading(end);
1685
1686 // not used in this case, but needed by AppendWrapped_NonWhitespaceSequence
1687 bool mayIgnoreStartOfLineWhitespaceSequence = false;
1688 mMayIgnoreLineBreakSequence = false;
1689
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 }
1699
1700 while (pos < end) {
1701 sequenceStart = pos;
1702
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 }

mercurial