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