|
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("&")); |
|
689 if (bIncludesDouble && bIncludesSingle) { |
|
690 sValue.ReplaceSubstring(NS_LITERAL_STRING("\""), |
|
691 NS_LITERAL_STRING(""")); |
|
692 } |
|
693 attrString.Append(sValue); |
|
694 attrString.Append(cDelimiter); |
|
695 } |
|
696 if (mPreLevel > 0 || mDoRaw) { |
|
697 AppendToStringConvertLF(attrString, aStr); |
|
698 } |
|
699 else if (mDoFormat) { |
|
700 AppendToStringFormatedWrapped(attrString, aStr); |
|
701 } |
|
702 else if (mDoWrap) { |
|
703 AppendToStringWrapped(attrString, aStr); |
|
704 } |
|
705 else { |
|
706 AppendToStringConvertLF(attrString, aStr); |
|
707 } |
|
708 } |
|
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 "", "", "", "", "", "", "", "", "&", "", |
|
1143 "", "", "", "", "", "", "", "", "", "", |
|
1144 "", "", "", "", "", "", "", "", "", "", |
|
1145 "<", "", ">" |
|
1146 }; |
|
1147 |
|
1148 static const char* kAttrEntities[] = { |
|
1149 "", "", "", "", "", "", "", "", "", "", |
|
1150 "", "", "", "", "", "", "", "", "", "", |
|
1151 "", "", "", "", "", "", "", "", "", "", |
|
1152 "", "", "", "", """, "", "", "", "&", "", |
|
1153 "", "", "", "", "", "", "", "", "", "", |
|
1154 "", "", "", "", "", "", "", "", "", "", |
|
1155 "<", "", ">" |
|
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 } |