~ubuntu-branches/ubuntu/precise/kompozer/precise

« back to all changes in this revision

Viewing changes to mozilla/content/base/src/nsPlainTextSerializer.cpp

  • Committer: Bazaar Package Importer
  • Author(s): Anthony Yarusso
  • Date: 2007-08-27 01:11:03 UTC
  • Revision ID: james.westby@ubuntu.com-20070827011103-2jgf4s6532gqu2ka
Tags: upstream-0.7.10
ImportĀ upstreamĀ versionĀ 0.7.10

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 
2
/* ***** BEGIN LICENSE BLOCK *****
 
3
 * Version: NPL 1.1/GPL 2.0/LGPL 2.1
 
4
 *
 
5
 * The contents of this file are subject to the Netscape Public License
 
6
 * Version 1.1 (the "License"); you may not use this file except in
 
7
 * compliance with the License. You may obtain a copy of the License at
 
8
 * http://www.mozilla.org/NPL/
 
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
 *     Daniel Bratell <bratell@lysator.liu.se>
 
24
 *     Ben Bucksch <mozilla@bucksch.org>
 
25
 *
 
26
 *
 
27
 * Alternatively, the contents of this file may be used under the terms of
 
28
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 
29
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 
30
 * in which case the provisions of the GPL or the LGPL are applicable instead
 
31
 * of those above. If you wish to allow use of your version of this file only
 
32
 * under the terms of either the GPL or the LGPL, and not to allow others to
 
33
 * use your version of this file under the terms of the NPL, indicate your
 
34
 * decision by deleting the provisions above and replace them with the notice
 
35
 * and other provisions required by the GPL or the LGPL. If you do not delete
 
36
 * the provisions above, a recipient may use your version of this file under
 
37
 * the terms of any one of the NPL, the GPL or the LGPL.
 
38
 *
 
39
 * ***** END LICENSE BLOCK ***** */
 
40
 
 
41
#include "nsPlainTextSerializer.h"
 
42
#include "nsILineBreakerFactory.h"
 
43
#include "nsLWBrkCIID.h"
 
44
#include "nsIPrefBranch.h"
 
45
#include "nsIPrefService.h"
 
46
#include "nsIServiceManager.h"
 
47
#include "nsHTMLAtoms.h"
 
48
#include "nsIDOMText.h"
 
49
#include "nsIDOMCDATASection.h"
 
50
#include "nsIDOMElement.h"
 
51
#include "nsINameSpaceManager.h"
 
52
#include "nsITextContent.h"
 
53
#include "nsTextFragment.h"
 
54
#include "nsContentUtils.h"
 
55
#include "nsReadableUtils.h"
 
56
#include "nsUnicharUtils.h"
 
57
#include "nsCRT.h"
 
58
#include "nsIParserService.h"
 
59
#include "nsIDOMHTMLDocument.h"
 
60
#include "nsIDOMHTMLElement.h"
 
61
 
 
62
static NS_DEFINE_CID(kLWBrkCID, NS_LWBRK_CID);
 
63
 
 
64
#define PREF_STRUCTS "converter.html2txt.structs"
 
65
#define PREF_HEADER_STRATEGY "converter.html2txt.header_strategy"
 
66
 
 
67
static const  PRInt32 kTabSize=4;
 
68
static const  PRInt32 kOLNumberWidth = 3;
 
69
static const  PRInt32 kIndentSizeHeaders = 2;  /* Indention of h1, if
 
70
                                                mHeaderStrategy = 1 or = 2.
 
71
                                                Indention of other headers
 
72
                                                is derived from that.
 
73
                                                XXX center h1? */
 
74
static const  PRInt32 kIndentIncrementHeaders = 2;  /* If mHeaderStrategy = 1,
 
75
                                                indent h(x+1) this many
 
76
                                                columns more than h(x) */
 
77
static const  PRInt32 kIndentSizeList = (kTabSize > kOLNumberWidth+3) ? kTabSize: kOLNumberWidth+3;
 
78
                               // Indention of non-first lines of ul and ol
 
79
static const  PRInt32 kIndentSizeDD = kTabSize;  // Indention of <dd>
 
80
 
 
81
static PRInt32 HeaderLevel(eHTMLTags aTag);
 
82
static PRInt32 GetUnicharWidth(PRUnichar ucs);
 
83
static PRInt32 GetUnicharStringWidth(const PRUnichar* pwcs, PRInt32 n);
 
84
 
 
85
// Someday may want to make this non-const:
 
86
static const PRUint32 TagStackSize = 500;
 
87
static const PRUint32 OLStackSize = 100;
 
88
 
 
89
nsresult NS_NewPlainTextSerializer(nsIContentSerializer** aSerializer)
 
90
{
 
91
  nsPlainTextSerializer* it = new nsPlainTextSerializer();
 
92
  if (!it) {
 
93
    return NS_ERROR_OUT_OF_MEMORY;
 
94
  }
 
95
 
 
96
  return CallQueryInterface(it, aSerializer);
 
97
}
 
98
 
 
99
nsPlainTextSerializer::nsPlainTextSerializer()
 
100
  : kSpace(NS_LITERAL_STRING(" ")) // Init of "constant"
 
101
{
 
102
 
 
103
  mOutputString = nsnull;
 
104
  mInHead = PR_FALSE;
 
105
  mAtFirstColumn = PR_TRUE;
 
106
  mIndent = 0;
 
107
  mCiteQuoteLevel = 0;
 
108
  mStructs = PR_TRUE;       // will be read from prefs later
 
109
  mHeaderStrategy = 1 /*indent increasingly*/;   // ditto
 
110
  mQuotesPreformatted = PR_FALSE;                // ditto
 
111
  mDontWrapAnyQuotes = PR_FALSE;                 // ditto
 
112
  mSpanLevel = 0;
 
113
  for (PRInt32 i = 0; i <= 6; i++) {
 
114
    mHeaderCounter[i] = 0;
 
115
  }
 
116
 
 
117
  // Line breaker
 
118
  mWrapColumn = 72;     // XXX magic number, we expect someone to reset this
 
119
  mCurrentLineWidth = 0;
 
120
 
 
121
  // Flow
 
122
  mEmptyLines = 1; // The start of the document is an "empty line" in itself,
 
123
  mInWhitespace = PR_TRUE;
 
124
  mPreFormatted = PR_FALSE;
 
125
  mStartedOutput = PR_FALSE;
 
126
 
 
127
  // initialize the tag stack to zero:
 
128
  mTagStack = new nsHTMLTag[TagStackSize];
 
129
  mTagStackIndex = 0;
 
130
  mIgnoreAboveIndex = (PRUint32)kNotFound;
 
131
 
 
132
  // initialize the OL stack, where numbers for ordered lists are kept:
 
133
  mOLStack = new PRInt32[OLStackSize];
 
134
  mOLStackIndex = 0;
 
135
 
 
136
  mULCount = 0;
 
137
}
 
138
 
 
139
nsPlainTextSerializer::~nsPlainTextSerializer()
 
140
{
 
141
  delete[] mTagStack;
 
142
  delete[] mOLStack;
 
143
}
 
144
 
 
145
NS_IMPL_ISUPPORTS4(nsPlainTextSerializer, 
 
146
                   nsIContentSerializer,
 
147
                   nsIContentSink,
 
148
                   nsIHTMLContentSink,
 
149
                   nsIHTMLToTextSink)
 
150
 
 
151
 
 
152
NS_IMETHODIMP 
 
153
nsPlainTextSerializer::Init(PRUint32 aFlags, PRUint32 aWrapColumn,
 
154
                            const char* aCharSet, PRBool aIsCopying)
 
155
{
 
156
#ifdef DEBUG
 
157
  // Check if the major control flags are set correctly.
 
158
  if(aFlags & nsIDocumentEncoder::OutputFormatFlowed) {
 
159
    NS_ASSERTION(aFlags & nsIDocumentEncoder::OutputFormatted,
 
160
                 "If you want format=flowed, you must combine it with "
 
161
                 "nsIDocumentEncoder::OutputFormatted");
 
162
  }
 
163
 
 
164
  if(aFlags & nsIDocumentEncoder::OutputFormatted) {
 
165
    NS_ASSERTION(!(aFlags & nsIDocumentEncoder::OutputPreformatted),
 
166
                 "Can't do formatted and preformatted output at the same time!");
 
167
  }
 
168
#endif
 
169
 
 
170
  NS_ENSURE_TRUE(nsContentUtils::GetParserServiceWeakRef(),
 
171
                 NS_ERROR_UNEXPECTED);
 
172
 
 
173
  nsresult rv;
 
174
  
 
175
  mFlags = aFlags;
 
176
  mWrapColumn = aWrapColumn;
 
177
 
 
178
  // Only create a linebreaker if we will handle wrapping.
 
179
  if (MayWrap()) {
 
180
    nsCOMPtr<nsILineBreakerFactory> lf(do_GetService(kLWBrkCID, &rv));
 
181
    if (NS_SUCCEEDED(rv)) {
 
182
      nsAutoString lbarg;
 
183
      rv = lf->GetBreaker(lbarg, getter_AddRefs(mLineBreaker));
 
184
      if (NS_FAILED(rv)) return NS_ERROR_FAILURE;
 
185
    }
 
186
  }
 
187
 
 
188
  // Set the line break character:
 
189
  if ((mFlags & nsIDocumentEncoder::OutputCRLineBreak)
 
190
      && (mFlags & nsIDocumentEncoder::OutputLFLineBreak)) {
 
191
    // Windows
 
192
    mLineBreak.Assign(NS_LITERAL_STRING("\r\n"));
 
193
  }
 
194
  else if (mFlags & nsIDocumentEncoder::OutputCRLineBreak) {
 
195
    // Mac
 
196
    mLineBreak.Assign(PRUnichar('\r'));
 
197
  }
 
198
  else if (mFlags & nsIDocumentEncoder::OutputLFLineBreak) {
 
199
    // Unix/DOM
 
200
    mLineBreak.Assign(PRUnichar('\n'));
 
201
  }
 
202
  else {
 
203
    // Platform/default
 
204
    mLineBreak.AssignWithConversion(NS_LINEBREAK);
 
205
  }
 
206
 
 
207
  mLineBreakDue = PR_FALSE;
 
208
  mFloatingLines = -1;
 
209
 
 
210
  nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID));
 
211
  if (!prefBranch) {
 
212
    NS_WARNING("Could not get a pref branch!");
 
213
    return NS_OK;
 
214
  }
 
215
 
 
216
  PRBool tempBool = PR_FALSE;
 
217
  if (mFlags & nsIDocumentEncoder::OutputFormatted) {
 
218
    // Get some prefs that controls how we do formatted output
 
219
    prefBranch->GetBoolPref(PREF_STRUCTS, &tempBool);
 
220
    mStructs = tempBool;
 
221
    prefBranch->GetIntPref(PREF_HEADER_STRATEGY, &mHeaderStrategy);
 
222
    // The quotesPreformatted pref is a temporary measure. See bug 69638.
 
223
    prefBranch->GetBoolPref("editor.quotesPreformatted", &tempBool);
 
224
    mQuotesPreformatted = tempBool;
 
225
    // DontWrapAnyQuotes is set according to whether plaintext mail
 
226
    // is wrapping to window width -- see bug 134439.
 
227
    // We'll only want this if we're wrapping and formatted.
 
228
    if (mFlags & nsIDocumentEncoder::OutputWrap || mWrapColumn > 0) {
 
229
      prefBranch->GetBoolPref("mail.compose.wrap_to_window_width", &tempBool);
 
230
      mDontWrapAnyQuotes = tempBool;
 
231
    }
 
232
  }
 
233
 
 
234
  // XXX We should let the caller pass this in.
 
235
  prefBranch->GetBoolPref("browser.frames.enabled", &tempBool);
 
236
  if (tempBool) {
 
237
    mFlags &= ~nsIDocumentEncoder::OutputNoFramesContent;
 
238
  }
 
239
  else {
 
240
    mFlags |= nsIDocumentEncoder::OutputNoFramesContent;
 
241
  }
 
242
 
 
243
  return NS_OK;
 
244
}
 
245
 
 
246
PRBool
 
247
nsPlainTextSerializer::GetLastBool(const nsVoidArray& aStack)
 
248
{
 
249
  PRUint32 size = aStack.Count();
 
250
  if (size == 0) {
 
251
    return PR_FALSE;
 
252
  }
 
253
  return (aStack.ElementAt(size-1) != NS_REINTERPRET_CAST(void*, PR_FALSE));
 
254
}
 
255
 
 
256
void
 
257
nsPlainTextSerializer::SetLastBool(nsVoidArray& aStack, PRBool aValue)
 
258
{
 
259
  PRUint32 size = aStack.Count();
 
260
  if (size > 0) {
 
261
    aStack.ReplaceElementAt(NS_REINTERPRET_CAST(void*, aValue), size-1);
 
262
  }
 
263
  else {
 
264
    NS_ERROR("There is no \"Last\" value");
 
265
  }
 
266
}
 
267
 
 
268
void
 
269
nsPlainTextSerializer::PushBool(nsVoidArray& aStack, PRBool aValue)
 
270
{
 
271
    aStack.AppendElement(NS_REINTERPRET_CAST(void*, aValue));
 
272
}
 
273
 
 
274
PRBool
 
275
nsPlainTextSerializer::PopBool(nsVoidArray& aStack)
 
276
{
 
277
  PRBool returnValue = PR_FALSE;
 
278
  PRUint32 size = aStack.Count();
 
279
  if (size > 0) {
 
280
    returnValue = (aStack.ElementAt(size-1) != NS_REINTERPRET_CAST(void*, PR_FALSE));
 
281
    aStack.RemoveElementAt(size-1);
 
282
  }
 
283
  return returnValue;
 
284
}
 
285
 
 
286
NS_IMETHODIMP
 
287
nsPlainTextSerializer::Initialize(nsAString* aOutString,
 
288
                                  PRUint32 aFlags, PRUint32 aWrapCol)
 
289
{
 
290
  nsresult rv = Init(aFlags, aWrapCol, nsnull, PR_FALSE);
 
291
  NS_ENSURE_SUCCESS(rv, rv);
 
292
 
 
293
  // XXX This is wrong. It violates XPCOM string ownership rules.
 
294
  // We're only getting away with this because instances of this
 
295
  // class are restricted to single function scope.
 
296
  mOutputString = aOutString;
 
297
 
 
298
  return NS_OK;
 
299
}
 
300
 
 
301
NS_IMETHODIMP 
 
302
nsPlainTextSerializer::AppendText(nsIDOMText* aText, 
 
303
                                  PRInt32 aStartOffset,
 
304
                                  PRInt32 aEndOffset, 
 
305
                                  nsAString& aStr)
 
306
{
 
307
  if (mIgnoreAboveIndex != (PRUint32)kNotFound) {
 
308
    return NS_OK;
 
309
  }
 
310
    
 
311
  NS_ASSERTION(aStartOffset >= 0, "Negative start offset for text fragment!");
 
312
  if ( aStartOffset < 0 )
 
313
    return NS_ERROR_INVALID_ARG;
 
314
 
 
315
  NS_ENSURE_ARG(aText);
 
316
 
 
317
  nsresult rv = NS_OK;
 
318
  PRInt32 length = 0;
 
319
  nsAutoString textstr;
 
320
 
 
321
  nsCOMPtr<nsITextContent> content = do_QueryInterface(aText);
 
322
  if (!content) return NS_ERROR_FAILURE;
 
323
  
 
324
  const nsTextFragment* frag;
 
325
  content->GetText(&frag);
 
326
 
 
327
  if (frag) {
 
328
    PRInt32 endoffset = (aEndOffset == -1) ? frag->GetLength() : aEndOffset;
 
329
    NS_ASSERTION(aStartOffset <= endoffset, "A start offset is beyond the end of the text fragment!");
 
330
 
 
331
    length = endoffset - aStartOffset;
 
332
    if (length <= 0) {
 
333
      return NS_OK;
 
334
    }
 
335
 
 
336
    if (frag->Is2b()) {
 
337
      textstr.Assign(frag->Get2b() + aStartOffset, length);
 
338
    }
 
339
    else {
 
340
      textstr.AssignWithConversion(frag->Get1b()+aStartOffset, length);
 
341
    }
 
342
  }
 
343
 
 
344
  mOutputString = &aStr;
 
345
 
 
346
  // We have to split the string across newlines
 
347
  // to match parser behavior
 
348
  PRInt32 start = 0;
 
349
  PRInt32 offset = textstr.FindCharInSet("\n\r");
 
350
  while (offset != kNotFound) {
 
351
 
 
352
    if(offset>start) {
 
353
      // Pass in the line
 
354
      rv = DoAddLeaf(nsnull,
 
355
                     eHTMLTag_text,
 
356
                     Substring(textstr, start, offset-start));
 
357
      if (NS_FAILED(rv)) break;
 
358
    }
 
359
 
 
360
    // Pass in a newline
 
361
    rv = DoAddLeaf(nsnull, eHTMLTag_newline, mLineBreak);
 
362
    if (NS_FAILED(rv)) break;
 
363
    
 
364
    start = offset+1;
 
365
    offset = textstr.FindCharInSet("\n\r", start);
 
366
  }
 
367
 
 
368
  // Consume the last bit of the string if there's any left
 
369
  if (NS_SUCCEEDED(rv) && start < length) {
 
370
    if (start) {
 
371
      rv = DoAddLeaf(nsnull,
 
372
                     eHTMLTag_text,
 
373
                     Substring(textstr, start, length-start));
 
374
    }
 
375
    else {
 
376
      rv = DoAddLeaf(nsnull, eHTMLTag_text, textstr);
 
377
    }
 
378
  }
 
379
  
 
380
  mOutputString = nsnull;
 
381
 
 
382
  return rv;
 
383
}
 
384
 
 
385
NS_IMETHODIMP
 
386
nsPlainTextSerializer::AppendCDATASection(nsIDOMCDATASection* aCDATASection,
 
387
                                          PRInt32 aStartOffset,
 
388
                                          PRInt32 aEndOffset,
 
389
                                          nsAString& aStr)
 
390
{
 
391
  return AppendText(aCDATASection, aStartOffset, aEndOffset, aStr);
 
392
}
 
393
 
 
394
NS_IMETHODIMP
 
395
nsPlainTextSerializer::AppendElementStart(nsIDOMElement *aElement,
 
396
                                          PRBool aHasChildren,
 
397
                                          nsAString& aStr)
 
398
{
 
399
  NS_ENSURE_ARG(aElement);
 
400
 
 
401
  mContent = do_QueryInterface(aElement);
 
402
  if (!mContent) return NS_ERROR_FAILURE;
 
403
 
 
404
  nsresult rv;
 
405
  PRInt32 id = GetIdForContent(mContent);
 
406
 
 
407
  PRBool isContainer = IsContainer(id);
 
408
 
 
409
  mOutputString = &aStr;
 
410
 
 
411
  if (isContainer) {
 
412
    rv = DoOpenContainer(nsnull, id);
 
413
  }
 
414
  else {
 
415
    nsAutoString empty;
 
416
    rv = DoAddLeaf(nsnull, id, empty);
 
417
  }
 
418
 
 
419
  mContent = 0;
 
420
  mOutputString = nsnull;
 
421
 
 
422
  if (!mInHead && id == eHTMLTag_head)
 
423
    mInHead = PR_TRUE;    
 
424
 
 
425
  return rv;
 
426
 
427
 
 
428
NS_IMETHODIMP 
 
429
nsPlainTextSerializer::AppendElementEnd(nsIDOMElement *aElement,
 
430
                                        nsAString& aStr)
 
431
{
 
432
  NS_ENSURE_ARG(aElement);
 
433
 
 
434
  mContent = do_QueryInterface(aElement);
 
435
  if (!mContent) return NS_ERROR_FAILURE;
 
436
 
 
437
  nsresult rv;
 
438
  PRInt32 id = GetIdForContent(mContent);
 
439
 
 
440
  PRBool isContainer = IsContainer(id);
 
441
 
 
442
  mOutputString = &aStr;
 
443
 
 
444
  rv = NS_OK;
 
445
  if (isContainer) {
 
446
    rv = DoCloseContainer(id);
 
447
  }
 
448
 
 
449
  mContent = 0;
 
450
  mOutputString = nsnull;
 
451
 
 
452
  if (mInHead && id == eHTMLTag_head)
 
453
    mInHead = PR_FALSE;    
 
454
 
 
455
  return rv;
 
456
}
 
457
 
 
458
NS_IMETHODIMP 
 
459
nsPlainTextSerializer::Flush(nsAString& aStr)
 
460
{
 
461
  mOutputString = &aStr;
 
462
  FlushLine();
 
463
  mOutputString = nsnull;
 
464
  return NS_OK;
 
465
}
 
466
 
 
467
NS_IMETHODIMP
 
468
nsPlainTextSerializer::AppendDocumentStart(nsIDOMDocument *aDocument,
 
469
                                           nsAString& aStr)
 
470
{
 
471
#ifdef MOZ_STANDALONE_COMPOSER
 
472
  NS_ENSURE_ARG(aDocument);
 
473
 
 
474
  nsCOMPtr<nsIDOMHTMLDocument> htmldoc = do_QueryInterface(aDocument);
 
475
  nsCOMPtr<nsIDOMElement> bodyElt;
 
476
  nsresult res;
 
477
  if (htmldoc)
 
478
  {
 
479
    nsCOMPtr<nsIDOMHTMLElement> bodyElement;
 
480
    res = htmldoc->GetBody(getter_AddRefs(bodyElement));
 
481
    if (NS_FAILED(res) || !bodyElement)
 
482
      return res;
 
483
 
 
484
    bodyElt = do_QueryInterface(bodyElement);
 
485
  }
 
486
 
 
487
  nsAutoString sourceViewAttr;
 
488
  res = bodyElt->GetAttribute(NS_LITERAL_STRING("_moz_sourceview"), sourceViewAttr);
 
489
  if (NS_FAILED(res))
 
490
    return res;
 
491
 
 
492
  if (sourceViewAttr.Equals(NS_LITERAL_STRING("true"), nsCaseInsensitiveStringComparator()))
 
493
    mFlags |= nsIDocumentEncoder::OutputRaw;
 
494
#endif
 
495
  return NS_OK;
 
496
}
 
497
 
 
498
NS_IMETHODIMP
 
499
nsPlainTextSerializer::OpenContainer(const nsIParserNode& aNode)
 
500
{
 
501
  PRInt32 type = aNode.GetNodeType();
 
502
 
 
503
  return DoOpenContainer(&aNode, type);
 
504
}
 
505
 
 
506
NS_IMETHODIMP 
 
507
nsPlainTextSerializer::CloseContainer(const nsHTMLTag aTag)
 
508
{
 
509
  return DoCloseContainer(aTag);
 
510
}
 
511
 
 
512
NS_IMETHODIMP 
 
513
nsPlainTextSerializer::AddHeadContent(const nsIParserNode& aNode)
 
514
{
 
515
  OpenHead(aNode);
 
516
  nsresult rv = AddLeaf(aNode);
 
517
  CloseHead();
 
518
  return rv;
 
519
}
 
520
 
 
521
NS_IMETHODIMP 
 
522
nsPlainTextSerializer::AddLeaf(const nsIParserNode& aNode)
 
523
{
 
524
  if (mIgnoreAboveIndex != (PRUint32)kNotFound) {
 
525
    return NS_OK;
 
526
  }
 
527
 
 
528
  eHTMLTags type = (eHTMLTags)aNode.GetNodeType();
 
529
  const nsAString& text = aNode.GetText();
 
530
 
 
531
  if ((type == eHTMLTag_text) ||
 
532
      (type == eHTMLTag_whitespace) ||
 
533
      (type == eHTMLTag_newline)) {
 
534
    // Copy the text out, stripping out CRs
 
535
    nsAutoString str;
 
536
    PRUint32 length;
 
537
    str.SetCapacity(text.Length());
 
538
    nsReadingIterator<PRUnichar> srcStart, srcEnd;
 
539
    length = nsContentUtils::CopyNewlineNormalizedUnicodeTo(text.BeginReading(srcStart), text.EndReading(srcEnd), str);
 
540
    str.SetLength(length);
 
541
    return DoAddLeaf(&aNode, type, str);
 
542
  }
 
543
  else {
 
544
    return DoAddLeaf(&aNode, type, text);
 
545
  }
 
546
}
 
547
 
 
548
NS_IMETHODIMP
 
549
nsPlainTextSerializer::OpenHTML(const nsIParserNode& aNode)
 
550
{
 
551
  return OpenContainer(aNode);
 
552
}
 
553
 
 
554
NS_IMETHODIMP 
 
555
nsPlainTextSerializer::CloseHTML()
 
556
{
 
557
  return CloseContainer(eHTMLTag_html);
 
558
}
 
559
 
 
560
NS_IMETHODIMP 
 
561
nsPlainTextSerializer::OpenHead(const nsIParserNode& aNode)
 
562
{
 
563
  mInHead = PR_TRUE;
 
564
  return NS_OK;
 
565
}
 
566
 
 
567
NS_IMETHODIMP 
 
568
nsPlainTextSerializer::CloseHead()
 
569
{
 
570
  mInHead = PR_FALSE;
 
571
  return NS_OK;
 
572
}
 
573
 
 
574
NS_IMETHODIMP 
 
575
nsPlainTextSerializer::OpenBody(const nsIParserNode& aNode)
 
576
{
 
577
  return OpenContainer(aNode);
 
578
}
 
579
 
 
580
NS_IMETHODIMP 
 
581
nsPlainTextSerializer::CloseBody()
 
582
{
 
583
  return CloseContainer(eHTMLTag_body);
 
584
}
 
585
 
 
586
NS_IMETHODIMP 
 
587
nsPlainTextSerializer::OpenForm(const nsIParserNode& aNode)
 
588
{
 
589
  return OpenContainer(aNode);
 
590
}
 
591
 
 
592
NS_IMETHODIMP 
 
593
nsPlainTextSerializer::CloseForm()
 
594
{
 
595
  return CloseContainer(eHTMLTag_form);
 
596
}
 
597
 
 
598
NS_IMETHODIMP 
 
599
nsPlainTextSerializer::OpenMap(const nsIParserNode& aNode)
 
600
{
 
601
  return OpenContainer(aNode);
 
602
}
 
603
 
 
604
NS_IMETHODIMP 
 
605
nsPlainTextSerializer::CloseMap()
 
606
{
 
607
  return CloseContainer(eHTMLTag_map);
 
608
}
 
609
 
 
610
NS_IMETHODIMP 
 
611
nsPlainTextSerializer::OpenFrameset(const nsIParserNode& aNode)
 
612
{
 
613
  return OpenContainer(aNode);
 
614
}
 
615
 
 
616
NS_IMETHODIMP 
 
617
nsPlainTextSerializer::CloseFrameset()
 
618
{
 
619
  return CloseContainer(eHTMLTag_frameset);
 
620
}
 
621
 
 
622
NS_IMETHODIMP
 
623
nsPlainTextSerializer::IsEnabled(PRInt32 aTag, PRBool* aReturn)
 
624
{
 
625
  nsHTMLTag theHTMLTag = nsHTMLTag(aTag);
 
626
 
 
627
  if (theHTMLTag == eHTMLTag_script) {
 
628
    *aReturn = !(mFlags & nsIDocumentEncoder::OutputNoScriptContent);
 
629
  }
 
630
  else if (theHTMLTag == eHTMLTag_frameset) {
 
631
    *aReturn = !(mFlags & nsIDocumentEncoder::OutputNoFramesContent);
 
632
  }
 
633
  else {
 
634
    *aReturn = PR_FALSE;
 
635
  }
 
636
 
 
637
  return NS_OK;
 
638
}
 
639
 
 
640
/**
 
641
 * aNode may be null when we're working with the DOM, but then mContent is
 
642
 * useable instead.
 
643
 */
 
644
nsresult
 
645
nsPlainTextSerializer::DoOpenContainer(const nsIParserNode* aNode, PRInt32 aTag)
 
646
{
 
647
  if (mFlags & nsIDocumentEncoder::OutputRaw) {
 
648
    // Raw means raw.  Don't even think about doing anything fancy
 
649
    // here like indenting, adding line breaks or any other
 
650
    // characters such as list item bullets, quote characters
 
651
    // around <q>, etc.  I mean it!  Don't make me smack you!
 
652
 
 
653
    return NS_OK;
 
654
  }
 
655
 
 
656
  eHTMLTags type = (eHTMLTags)aTag;
 
657
 
 
658
  if (mTagStackIndex < TagStackSize) {
 
659
    mTagStack[mTagStackIndex++] = type;
 
660
  }
 
661
 
 
662
  if (mIgnoreAboveIndex != (PRUint32)kNotFound) {
 
663
    return NS_OK;
 
664
  }
 
665
 
 
666
  if (mLineBreakDue)
 
667
    EnsureVerticalSpace(mFloatingLines);
 
668
 
 
669
  // Check if this tag's content that should not be output
 
670
  if ((type == eHTMLTag_noscript &&
 
671
       !(mFlags & nsIDocumentEncoder::OutputNoScriptContent)) ||
 
672
      ((type == eHTMLTag_iframe || type == eHTMLTag_noframes) &&
 
673
       !(mFlags & nsIDocumentEncoder::OutputNoFramesContent))) {
 
674
    // Ignore everything that follows the current tag in 
 
675
    // question until a matching end tag is encountered.
 
676
    mIgnoreAboveIndex = mTagStackIndex - 1;
 
677
    return NS_OK;
 
678
  }
 
679
 
 
680
  if (type == eHTMLTag_body) {
 
681
    // Try to figure out here whether we have a
 
682
    // preformatted style attribute.
 
683
    //
 
684
    // Trigger on the presence of a "-moz-pre-wrap" in the
 
685
    // style attribute. That's a very simplistic way to do
 
686
    // it, but better than nothing.
 
687
    // Also set mWrapColumn to the value given there
 
688
    // (which arguably we should only do if told to do so).
 
689
    nsAutoString style;
 
690
    PRInt32 whitespace;
 
691
    if(NS_SUCCEEDED(GetAttributeValue(aNode, nsHTMLAtoms::style, style)) &&
 
692
       (kNotFound != (whitespace = style.Find("white-space:")))) {
 
693
 
 
694
      if (kNotFound != style.Find("-moz-pre-wrap", PR_TRUE, whitespace)) {
 
695
#ifdef DEBUG_preformatted
 
696
        printf("Set mPreFormatted based on style moz-pre-wrap\n");
 
697
#endif
 
698
        mPreFormatted = PR_TRUE;
 
699
        PRInt32 widthOffset = style.Find("width:");
 
700
        if (widthOffset >= 0) {
 
701
          // We have to search for the ch before the semicolon,
 
702
          // not for the semicolon itself, because nsString::ToInteger()
 
703
          // considers 'c' to be a valid numeric char (even if radix=10)
 
704
          // but then gets confused if it sees it next to the number
 
705
          // when the radix specified was 10, and returns an error code.
 
706
          PRInt32 semiOffset = style.Find("ch", widthOffset+6);
 
707
          PRInt32 length = (semiOffset > 0 ? semiOffset - widthOffset - 6
 
708
                            : style.Length() - widthOffset);
 
709
          nsAutoString widthstr;
 
710
          style.Mid(widthstr, widthOffset+6, length);
 
711
          PRInt32 err;
 
712
          PRInt32 col = widthstr.ToInteger(&err);
 
713
 
 
714
          if (NS_SUCCEEDED(err)) {
 
715
            mWrapColumn = (PRUint32)col;
 
716
#ifdef DEBUG_preformatted
 
717
            printf("Set wrap column to %d based on style\n", mWrapColumn);
 
718
#endif
 
719
          }
 
720
        }
 
721
      }
 
722
      else if (kNotFound != style.Find("pre", PR_TRUE, whitespace)) {
 
723
#ifdef DEBUG_preformatted
 
724
        printf("Set mPreFormatted based on style pre\n");
 
725
#endif
 
726
        mPreFormatted = PR_TRUE;
 
727
        mWrapColumn = 0;
 
728
      }
 
729
    } 
 
730
    else {
 
731
      mPreFormatted = PR_FALSE;
 
732
    }
 
733
 
 
734
    return NS_OK;
 
735
  }
 
736
 
 
737
  if (!DoOutput()) {
 
738
    return NS_OK;
 
739
  }
 
740
 
 
741
  if (type == eHTMLTag_p || type == eHTMLTag_pre) {
 
742
    EnsureVerticalSpace(1); // Should this be 0 in unformatted case?
 
743
  }
 
744
  else if (type == eHTMLTag_tr) {
 
745
    PushBool(mHasWrittenCellsForRow, PR_FALSE);
 
746
  }
 
747
  else if (type == eHTMLTag_td || type == eHTMLTag_th) {
 
748
    // We must make sure that the content of two table cells get a
 
749
    // space between them.
 
750
 
 
751
    // To make the separation between cells most obvious and
 
752
    // importable, we use a TAB.
 
753
    if (GetLastBool(mHasWrittenCellsForRow)) {
 
754
      // Bypass |Write| so that the TAB isn't compressed away.
 
755
      AddToLine(NS_LITERAL_STRING("\t").get(), 1);
 
756
      mInWhitespace = PR_TRUE;
 
757
    }
 
758
    else if (mHasWrittenCellsForRow.Count() == 0) {
 
759
      // We don't always see a <tr> (nor a <table>) before the <td> if we're
 
760
      // copying part of a table
 
761
      PushBool(mHasWrittenCellsForRow, PR_TRUE); // will never be popped
 
762
    }
 
763
    else {
 
764
      SetLastBool(mHasWrittenCellsForRow, PR_TRUE);
 
765
    }
 
766
  }
 
767
  else if (type == eHTMLTag_ul) {
 
768
    // Indent here to support nested lists, which aren't included in li :-(
 
769
    EnsureVerticalSpace(mULCount + mOLStackIndex == 0 ? 1 : 0);
 
770
         // Must end the current line before we change indention
 
771
    mIndent += kIndentSizeList;
 
772
    mULCount++;
 
773
  }
 
774
  else if (type == eHTMLTag_ol) {
 
775
    EnsureVerticalSpace(mULCount + mOLStackIndex == 0 ? 1 : 0);
 
776
    // Must end the current line before we change indention
 
777
    if (mOLStackIndex < OLStackSize) {
 
778
      nsAutoString startAttr;
 
779
      PRInt32 startVal = 1;
 
780
      if(NS_SUCCEEDED(GetAttributeValue(aNode, nsHTMLAtoms::start, startAttr))){
 
781
        PRInt32 rv = 0;
 
782
        startVal = startAttr.ToInteger(&rv);
 
783
        if (NS_FAILED(rv))
 
784
          startVal = 1;
 
785
      }
 
786
      mOLStack[mOLStackIndex++] = startVal;
 
787
    }
 
788
    mIndent += kIndentSizeList;  // see ul
 
789
  }
 
790
  else if (type == eHTMLTag_li) {
 
791
    if (mTagStackIndex > 1 && IsInOL()) {
 
792
      if (mOLStackIndex > 0) {
 
793
        nsAutoString valueAttr;
 
794
        if(NS_SUCCEEDED(GetAttributeValue(aNode, nsHTMLAtoms::value, valueAttr))){
 
795
          PRInt32 rv = 0;
 
796
          PRInt32 valueAttrVal = valueAttr.ToInteger(&rv);
 
797
          if (NS_SUCCEEDED(rv))
 
798
            mOLStack[mOLStackIndex-1] = valueAttrVal;
 
799
        }
 
800
        // This is what nsBulletFrame does for OLs:
 
801
        mInIndentString.AppendInt(mOLStack[mOLStackIndex-1]++, 10);
 
802
      }
 
803
      else {
 
804
        mInIndentString.Append(PRUnichar('#'));
 
805
      }
 
806
 
 
807
      mInIndentString.Append(PRUnichar('.'));
 
808
 
 
809
    }
 
810
    else {
 
811
      static char bulletCharArray[] = "*o+#";
 
812
      NS_ASSERTION(mULCount > 0, "mULCount should be greater than 0 here");
 
813
      char bulletChar = bulletCharArray[(mULCount - 1) % 4];
 
814
      mInIndentString.Append(PRUnichar(bulletChar));
 
815
    }
 
816
    
 
817
    mInIndentString.Append(PRUnichar(' '));
 
818
  }
 
819
  else if (type == eHTMLTag_dl) {
 
820
    EnsureVerticalSpace(1);
 
821
  }
 
822
  else if (type == eHTMLTag_dt) {
 
823
    EnsureVerticalSpace(0);
 
824
  }
 
825
  else if (type == eHTMLTag_dd) {
 
826
    EnsureVerticalSpace(0);
 
827
    mIndent += kIndentSizeDD;
 
828
  }
 
829
  else if (type == eHTMLTag_span) {
 
830
    ++mSpanLevel;
 
831
  }
 
832
  else if (type == eHTMLTag_blockquote) {
 
833
    EnsureVerticalSpace(1);
 
834
 
 
835
    nsAutoString value;
 
836
    nsresult rv = GetAttributeValue(aNode, nsHTMLAtoms::type, value);
 
837
 
 
838
    PRBool isInCiteBlockquote =
 
839
      NS_SUCCEEDED(rv) && value.EqualsIgnoreCase("cite");
 
840
    // Push
 
841
    PushBool(mIsInCiteBlockquote, isInCiteBlockquote);
 
842
    if (isInCiteBlockquote) {
 
843
      mCiteQuoteLevel++;
 
844
    }
 
845
    else {
 
846
      mIndent += kTabSize; // Check for some maximum value?
 
847
    }
 
848
  }
 
849
 
 
850
  // Else make sure we'll separate block level tags,
 
851
  // even if we're about to leave, before doing any other formatting.
 
852
  else if (IsBlockLevel(aTag)) {
 
853
    EnsureVerticalSpace(0);
 
854
  }
 
855
 
 
856
  //////////////////////////////////////////////////////////////
 
857
  if (!(mFlags & nsIDocumentEncoder::OutputFormatted)) {
 
858
    return NS_OK;
 
859
  }
 
860
  //////////////////////////////////////////////////////////////
 
861
  // The rest of this routine is formatted output stuff,
 
862
  // which we should skip if we're not formatted:
 
863
  //////////////////////////////////////////////////////////////
 
864
 
 
865
  // Push on stack
 
866
  PRBool currentNodeIsConverted = IsCurrentNodeConverted(aNode);
 
867
  PushBool(mCurrentNodeIsConverted, currentNodeIsConverted);
 
868
 
 
869
  if (type == eHTMLTag_h1 || type == eHTMLTag_h2 ||
 
870
      type == eHTMLTag_h3 || type == eHTMLTag_h4 ||
 
871
      type == eHTMLTag_h5 || type == eHTMLTag_h6)
 
872
  {
 
873
    EnsureVerticalSpace(2);
 
874
    if (mHeaderStrategy == 2) {  // numbered
 
875
      mIndent += kIndentSizeHeaders;
 
876
      // Caching
 
877
      PRInt32 level = HeaderLevel(type);
 
878
      // Increase counter for current level
 
879
      mHeaderCounter[level]++;
 
880
      // Reset all lower levels
 
881
      PRInt32 i;
 
882
 
 
883
      for (i = level + 1; i <= 6; i++) {
 
884
        mHeaderCounter[i] = 0;
 
885
      }
 
886
 
 
887
      // Construct numbers
 
888
      nsAutoString leadup;
 
889
      for (i = 1; i <= level; i++) {
 
890
        leadup.AppendInt(mHeaderCounter[i]);
 
891
        leadup.Append(PRUnichar('.'));
 
892
      }
 
893
      leadup.Append(PRUnichar(' '));
 
894
      Write(leadup);
 
895
    }
 
896
    else if (mHeaderStrategy == 1) { // indent increasingly
 
897
      mIndent += kIndentSizeHeaders;
 
898
      for (PRInt32 i = HeaderLevel(type); i > 1; i--) {
 
899
           // for h(x), run x-1 times
 
900
        mIndent += kIndentIncrementHeaders;
 
901
      }
 
902
    }
 
903
  }
 
904
  else if (type == eHTMLTag_a && !currentNodeIsConverted) {
 
905
    nsAutoString url;
 
906
    if (NS_SUCCEEDED(GetAttributeValue(aNode, nsHTMLAtoms::href, url))
 
907
        && !url.IsEmpty()) {
 
908
      mURL = url;
 
909
    }
 
910
  }
 
911
  else if (type == eHTMLTag_q) {
 
912
    Write(NS_LITERAL_STRING("\""));
 
913
  }
 
914
  else if (type == eHTMLTag_sup && mStructs && !currentNodeIsConverted) {
 
915
    Write(NS_LITERAL_STRING("^"));
 
916
  }
 
917
  else if (type == eHTMLTag_sub && mStructs && !currentNodeIsConverted) { 
 
918
    Write(NS_LITERAL_STRING("_"));
 
919
  }
 
920
  else if (type == eHTMLTag_code && mStructs && !currentNodeIsConverted) {
 
921
    Write(NS_LITERAL_STRING("|"));
 
922
  }
 
923
  else if ((type == eHTMLTag_strong || type == eHTMLTag_b)
 
924
           && mStructs && !currentNodeIsConverted) {
 
925
    Write(NS_LITERAL_STRING("*"));
 
926
  }
 
927
  else if ((type == eHTMLTag_em || type == eHTMLTag_i)
 
928
           && mStructs && !currentNodeIsConverted) {
 
929
    Write(NS_LITERAL_STRING("/"));
 
930
  }
 
931
  else if (type == eHTMLTag_u && mStructs && !currentNodeIsConverted) {
 
932
    Write(NS_LITERAL_STRING("_"));
 
933
  }
 
934
 
 
935
  return NS_OK;
 
936
}
 
937
 
 
938
nsresult
 
939
nsPlainTextSerializer::DoCloseContainer(PRInt32 aTag)
 
940
{
 
941
  eHTMLTags type = (eHTMLTags)aTag;
 
942
  if (mFlags & nsIDocumentEncoder::OutputLineBreaksWhenClosingLI) {
 
943
    if (type == eHTMLTag_li)
 
944
    {
 
945
      Write(mLineBreak);
 
946
    }
 
947
  }
 
948
 
 
949
  if (mFlags & nsIDocumentEncoder::OutputRaw) {
 
950
    // Raw means raw.  Don't even think about doing anything fancy
 
951
    // here like indenting, adding line breaks or any other
 
952
    // characters such as list item bullets, quote characters
 
953
    // around <q>, etc.  I mean it!  Don't make me smack you!
 
954
 
 
955
    return NS_OK;
 
956
  }
 
957
 
 
958
  if (mTagStackIndex > 0) {
 
959
    --mTagStackIndex;
 
960
  }
 
961
 
 
962
  if (mTagStackIndex >= mIgnoreAboveIndex) {
 
963
    if (mTagStackIndex == mIgnoreAboveIndex) {
 
964
      // We're dealing with the close tag whose matching
 
965
      // open tag had set the mIgnoreAboveIndex value.
 
966
      // Reset mIgnoreAboveIndex before discarding this tag.
 
967
      mIgnoreAboveIndex = (PRUint32)kNotFound;
 
968
    }
 
969
    return NS_OK;
 
970
  }
 
971
 
 
972
  // End current line if we're ending a block level tag
 
973
  if((type == eHTMLTag_body) || (type == eHTMLTag_html)) {
 
974
    // We want the output to end with a new line,
 
975
    // but in preformatted areas like text fields,
 
976
    // we can't emit newlines that weren't there.
 
977
    // So add the newline only in the case of formatted output.
 
978
    if (mFlags & nsIDocumentEncoder::OutputFormatted) {
 
979
      EnsureVerticalSpace(0);
 
980
    }
 
981
    else {
 
982
      FlushLine();
 
983
    }
 
984
    // We won't want to do anything with these in formatted mode either,
 
985
    // so just return now:
 
986
    return NS_OK;
 
987
  }
 
988
  else if (type == eHTMLTag_tr) {
 
989
    PopBool(mHasWrittenCellsForRow);
 
990
    // Should always end a line, but get no more whitespace
 
991
    if (mFloatingLines < 0)
 
992
      mFloatingLines = 0;
 
993
    mLineBreakDue = PR_TRUE;
 
994
  } 
 
995
  else if ((type == eHTMLTag_li) ||
 
996
           (type == eHTMLTag_dt)) {
 
997
    // Items that should always end a line, but get no more whitespace
 
998
    if (mFloatingLines < 0)
 
999
      mFloatingLines = 0;
 
1000
    mLineBreakDue = PR_TRUE;
 
1001
  } 
 
1002
  else if (type == eHTMLTag_pre) {
 
1003
    mFloatingLines = 1;
 
1004
    mLineBreakDue = PR_TRUE;
 
1005
  }
 
1006
  else if (type == eHTMLTag_ul) {
 
1007
    FlushLine();
 
1008
    mIndent -= kIndentSizeList;
 
1009
    if (--mULCount + mOLStackIndex == 0) {
 
1010
      mFloatingLines = 1;
 
1011
      mLineBreakDue = PR_TRUE;
 
1012
    }
 
1013
  }
 
1014
  else if (type == eHTMLTag_ol) {
 
1015
    FlushLine(); // Doing this after decreasing OLStackIndex would be wrong.
 
1016
    mIndent -= kIndentSizeList;
 
1017
    mOLStackIndex--;
 
1018
    if (mULCount + mOLStackIndex == 0) {
 
1019
      mFloatingLines = 1;
 
1020
      mLineBreakDue = PR_TRUE;
 
1021
    }
 
1022
  }  
 
1023
  else if (type == eHTMLTag_dl) {
 
1024
    mFloatingLines = 1;
 
1025
    mLineBreakDue = PR_TRUE;
 
1026
  }
 
1027
  else if (type == eHTMLTag_dd) {
 
1028
    FlushLine();
 
1029
    mIndent -= kIndentSizeDD;
 
1030
  }
 
1031
  else if (type == eHTMLTag_span) {
 
1032
    --mSpanLevel;
 
1033
  }
 
1034
  else if (type == eHTMLTag_div) {
 
1035
    if (mFloatingLines < 0)
 
1036
      mFloatingLines = 0;
 
1037
    mLineBreakDue = PR_TRUE;
 
1038
  }
 
1039
  else if (type == eHTMLTag_blockquote) {
 
1040
    FlushLine();    // Is this needed?
 
1041
 
 
1042
    // Pop
 
1043
    PRBool isInCiteBlockquote = PopBool(mIsInCiteBlockquote);
 
1044
 
 
1045
    if (isInCiteBlockquote) {
 
1046
      mCiteQuoteLevel--;
 
1047
    }
 
1048
    else {
 
1049
      mIndent -= kTabSize;
 
1050
    }
 
1051
 
 
1052
    mFloatingLines = 1;
 
1053
    mLineBreakDue = PR_TRUE;
 
1054
  }
 
1055
  else if (IsBlockLevel(aTag)
 
1056
           && type != eHTMLTag_script
 
1057
           && type != eHTMLTag_doctypeDecl
 
1058
           && type != eHTMLTag_markupDecl) {
 
1059
    // All other blocks get 1 vertical space after them
 
1060
    // in formatted mode, otherwise 0.
 
1061
    // This is hard. Sometimes 0 is a better number, but
 
1062
    // how to know?
 
1063
    if (mFlags & nsIDocumentEncoder::OutputFormatted)
 
1064
      EnsureVerticalSpace(1);
 
1065
    else {
 
1066
      if (mFloatingLines < 0)
 
1067
        mFloatingLines = 0;
 
1068
      mLineBreakDue = PR_TRUE;
 
1069
    }
 
1070
  }
 
1071
 
 
1072
  //////////////////////////////////////////////////////////////
 
1073
  if (!(mFlags & nsIDocumentEncoder::OutputFormatted)) {
 
1074
    return NS_OK;
 
1075
  }
 
1076
  //////////////////////////////////////////////////////////////
 
1077
  // The rest of this routine is formatted output stuff,
 
1078
  // which we should skip if we're not formatted:
 
1079
  //////////////////////////////////////////////////////////////
 
1080
 
 
1081
  // Pop the currentConverted stack
 
1082
  PRBool currentNodeIsConverted = PopBool(mCurrentNodeIsConverted);
 
1083
  
 
1084
  if (type == eHTMLTag_h1 || type == eHTMLTag_h2 ||
 
1085
      type == eHTMLTag_h3 || type == eHTMLTag_h4 ||
 
1086
      type == eHTMLTag_h5 || type == eHTMLTag_h6) {
 
1087
    
 
1088
    if (mHeaderStrategy) {  /*numbered or indent increasingly*/ 
 
1089
      mIndent -= kIndentSizeHeaders;
 
1090
    }
 
1091
    if (mHeaderStrategy == 1 /*indent increasingly*/ ) {
 
1092
      for (PRInt32 i = HeaderLevel(type); i > 1; i--) {
 
1093
           // for h(x), run x-1 times
 
1094
        mIndent -= kIndentIncrementHeaders;
 
1095
      }
 
1096
    }
 
1097
    EnsureVerticalSpace(1);
 
1098
  }
 
1099
  else if (type == eHTMLTag_a && !currentNodeIsConverted && !mURL.IsEmpty()) {
 
1100
    nsAutoString temp; 
 
1101
    temp.Assign(NS_LITERAL_STRING(" <"));
 
1102
    temp += mURL;
 
1103
    temp.Append(PRUnichar('>'));
 
1104
    Write(temp);
 
1105
    mURL.Truncate();
 
1106
  }
 
1107
  else if (type == eHTMLTag_q) {
 
1108
    Write(NS_LITERAL_STRING("\""));
 
1109
  }
 
1110
  else if ((type == eHTMLTag_sup || type == eHTMLTag_sub) 
 
1111
           && mStructs && !currentNodeIsConverted) {
 
1112
    Write(kSpace);
 
1113
  }
 
1114
  else if (type == eHTMLTag_code && mStructs && !currentNodeIsConverted) {
 
1115
    Write(NS_LITERAL_STRING("|"));
 
1116
  }
 
1117
  else if ((type == eHTMLTag_strong || type == eHTMLTag_b)
 
1118
           && mStructs && !currentNodeIsConverted) {
 
1119
    Write(NS_LITERAL_STRING("*"));
 
1120
  }
 
1121
  else if ((type == eHTMLTag_em || type == eHTMLTag_i)
 
1122
           && mStructs && !currentNodeIsConverted) {
 
1123
    Write(NS_LITERAL_STRING("/"));
 
1124
  }
 
1125
  else if (type == eHTMLTag_u && mStructs && !currentNodeIsConverted) {
 
1126
    Write(NS_LITERAL_STRING("_"));
 
1127
  }
 
1128
 
 
1129
  return NS_OK;
 
1130
}
 
1131
 
 
1132
/**
 
1133
 * aNode may be null when we're working with the DOM, but then mContent is
 
1134
 * useable instead.
 
1135
 */
 
1136
nsresult
 
1137
nsPlainTextSerializer::DoAddLeaf(const nsIParserNode *aNode, PRInt32 aTag, 
 
1138
                                 const nsAString& aText)
 
1139
{
 
1140
  // If we don't want any output, just return
 
1141
  if (!DoOutput()) {
 
1142
    return NS_OK;
 
1143
  }
 
1144
  
 
1145
  if (mLineBreakDue)
 
1146
    EnsureVerticalSpace(mFloatingLines);
 
1147
 
 
1148
  eHTMLTags type = (eHTMLTags)aTag;
 
1149
  
 
1150
  if ((mTagStackIndex > 1 &&
 
1151
       mTagStack[mTagStackIndex-2] == eHTMLTag_select) ||
 
1152
      (mTagStackIndex > 0 &&
 
1153
        mTagStack[mTagStackIndex-1] == eHTMLTag_select)) {
 
1154
    // Don't output the contents of SELECT elements;
 
1155
    // Might be nice, eventually, to output just the selected element.
 
1156
    // Read more in bug 31994.
 
1157
    return NS_OK;
 
1158
  }
 
1159
  else if (mTagStackIndex > 0 && mTagStack[mTagStackIndex-1] == eHTMLTag_script) {
 
1160
    // Don't output the contents of <script> tags;
 
1161
    return NS_OK;
 
1162
  }
 
1163
  else if (type == eHTMLTag_text) {
 
1164
    /* Check, if we are in a link (symbolized with mURL containing the URL)
 
1165
       and the text is equal to the URL. In that case we don't want to output
 
1166
       the URL twice so we scrap the text in mURL. */
 
1167
    if (!mURL.IsEmpty() && mURL.Equals(aText)) {
 
1168
      mURL.Truncate();
 
1169
    }
 
1170
    Write(aText);
 
1171
  }
 
1172
  else if (type == eHTMLTag_entity) {
 
1173
    nsIParserService* parserService =
 
1174
      nsContentUtils::GetParserServiceWeakRef();
 
1175
    if (parserService) {
 
1176
      nsAutoString str(aText);
 
1177
      PRInt32 entity;
 
1178
      parserService->HTMLConvertEntityToUnicode(str, &entity);
 
1179
      if (entity == -1 && 
 
1180
          !str.IsEmpty() &&
 
1181
          str.First() == (PRUnichar) '#') {
 
1182
        PRInt32 err = 0;
 
1183
        entity = str.ToInteger(&err, kAutoDetect);  // NCR
 
1184
      }
 
1185
      nsAutoString temp;
 
1186
      temp.Append(PRUnichar(entity));
 
1187
      Write(temp);
 
1188
    }
 
1189
  }
 
1190
  else if (type == eHTMLTag_br) {
 
1191
    // Another egregious editor workaround, see bug 38194:
 
1192
    // ignore the bogus br tags that the editor sticks here and there.
 
1193
    nsAutoString typeAttr;
 
1194
    if (NS_FAILED(GetAttributeValue(aNode, nsHTMLAtoms::type, typeAttr))
 
1195
        || !typeAttr.Equals(NS_LITERAL_STRING("_moz"))) {
 
1196
      EnsureVerticalSpace(mEmptyLines+1);
 
1197
    }
 
1198
  }
 
1199
  else if (type == eHTMLTag_whitespace) {
 
1200
    // The only times we want to pass along whitespace from the original
 
1201
    // html source are if we're forced into preformatted mode via flags,
 
1202
    // or if we're prettyprinting and we're inside a <pre>.
 
1203
    // Otherwise, either we're collapsing to minimal text, or we're
 
1204
    // prettyprinting to mimic the html format, and in neither case
 
1205
    // does the formatting of the html source help us.
 
1206
    // One exception: at the very beginning of a selection,
 
1207
    // we want to preserve whitespace.
 
1208
    if (mFlags & nsIDocumentEncoder::OutputPreformatted ||
 
1209
        (mPreFormatted && !mWrapColumn) ||
 
1210
        IsInPre()) {
 
1211
      Write(aText);
 
1212
    }
 
1213
    else if(!mInWhitespace ||
 
1214
            (!mStartedOutput
 
1215
             && mFlags | nsIDocumentEncoder::OutputSelectionOnly)) {
 
1216
      mInWhitespace = PR_FALSE;
 
1217
      Write(kSpace);
 
1218
      mInWhitespace = PR_TRUE;
 
1219
    }
 
1220
  }
 
1221
  else if (type == eHTMLTag_newline) {
 
1222
    if (mFlags & nsIDocumentEncoder::OutputPreformatted ||
 
1223
        (mPreFormatted && !mWrapColumn) ||
 
1224
        IsInPre()) {
 
1225
      EnsureVerticalSpace(mEmptyLines+1);
 
1226
    }
 
1227
    else {
 
1228
      Write(kSpace);
 
1229
    }
 
1230
  }
 
1231
  else if (type == eHTMLTag_hr &&
 
1232
           (mFlags & nsIDocumentEncoder::OutputFormatted)) {
 
1233
    EnsureVerticalSpace(0);
 
1234
 
 
1235
    // Make a line of dashes as wide as the wrap width
 
1236
    // XXX honoring percentage would be nice
 
1237
    nsAutoString line;
 
1238
    PRUint32 width = (mWrapColumn > 0 ? mWrapColumn : 25);
 
1239
    while (line.Length() < width) {
 
1240
      line.Append(PRUnichar('-'));
 
1241
    }
 
1242
    Write(line);
 
1243
 
 
1244
    EnsureVerticalSpace(0);
 
1245
  }
 
1246
  else if (type == eHTMLTag_img) {
 
1247
    /* Output (in decreasing order of preference)
 
1248
       alt, title or nothing */
 
1249
    // See <http://www.w3.org/TR/REC-html40/struct/objects.html#edef-IMG>
 
1250
    nsAutoString imageDescription;
 
1251
    if (NS_SUCCEEDED(GetAttributeValue(aNode,
 
1252
                                       nsHTMLAtoms::alt,
 
1253
                                       imageDescription))) {
 
1254
      // If the alt attribute has an empty value (|alt=""|), output nothing
 
1255
    }
 
1256
    else if (NS_SUCCEEDED(GetAttributeValue(aNode,
 
1257
                                            nsHTMLAtoms::title,
 
1258
                                            imageDescription))
 
1259
             && !imageDescription.IsEmpty()) {
 
1260
      imageDescription = NS_LITERAL_STRING(" [") +
 
1261
                         imageDescription +
 
1262
                         NS_LITERAL_STRING("] ");
 
1263
    }
 
1264
   
 
1265
    Write(imageDescription);
 
1266
  }
 
1267
 
 
1268
 
 
1269
  return NS_OK;
 
1270
}
 
1271
 
 
1272
/**
 
1273
 * Adds as many newline as necessary to get |noOfRows| empty lines
 
1274
 *
 
1275
 * noOfRows = -1    :   Being in the middle of some line of text
 
1276
 * noOfRows =  0    :   Being at the start of a line
 
1277
 * noOfRows =  n>0  :   Having n empty lines before the current line.
 
1278
 */
 
1279
void
 
1280
nsPlainTextSerializer::EnsureVerticalSpace(PRInt32 noOfRows)
 
1281
{
 
1282
  // If we have something in the indent we probably want to output
 
1283
  // it and it's not included in the count for empty lines so we don't
 
1284
  // realize that we should start a new line.
 
1285
  if(noOfRows >= 0 && !mInIndentString.IsEmpty()) {
 
1286
    EndLine(PR_FALSE);
 
1287
  }
 
1288
 
 
1289
  while(mEmptyLines < noOfRows) {
 
1290
    EndLine(PR_FALSE);
 
1291
  }
 
1292
  mLineBreakDue = PR_FALSE;
 
1293
  mFloatingLines = -1;
 
1294
}
 
1295
 
 
1296
/**
 
1297
 * This empties the current line cache without adding a NEWLINE.
 
1298
 * Should not be used if line wrapping is of importance since
 
1299
 * this function destroys the cache information.
 
1300
 *
 
1301
 * It will also write indentation and quotes if we believe us to be
 
1302
 * at the start of the line.
 
1303
 */
 
1304
void
 
1305
nsPlainTextSerializer::FlushLine()
 
1306
{
 
1307
  if(!mCurrentLine.IsEmpty()) {
 
1308
    if(mAtFirstColumn) {
 
1309
      OutputQuotesAndIndent(); // XXX: Should we always do this? Bug?
 
1310
    }
 
1311
 
 
1312
    Output(mCurrentLine);
 
1313
    mAtFirstColumn = mAtFirstColumn && mCurrentLine.IsEmpty();
 
1314
    mCurrentLine.Truncate();
 
1315
    mCurrentLineWidth = 0;
 
1316
  }
 
1317
}
 
1318
 
 
1319
/**
 
1320
 * Prints the text to output to our current output device (the string mOutputString).
 
1321
 * The only logic here is to replace non breaking spaces with a normal space since
 
1322
 * most (all?) receivers of the result won't understand the nbsp and even be
 
1323
 * confused by it.
 
1324
 */
 
1325
void 
 
1326
nsPlainTextSerializer::Output(nsString& aString)
 
1327
{
 
1328
  if (!aString.IsEmpty()) {
 
1329
    mStartedOutput = PR_TRUE;
 
1330
  }
 
1331
 
 
1332
  // First, replace all nbsp characters with spaces,
 
1333
  // which the unicode encoder won't do for us.
 
1334
  static PRUnichar nbsp = 160;
 
1335
  static PRUnichar space = ' ';
 
1336
  aString.ReplaceChar(nbsp, space);
 
1337
 
 
1338
  mOutputString->Append(aString);
 
1339
}
 
1340
 
 
1341
/**
 
1342
 * This function adds a piece of text to the current stored line. If we are
 
1343
 * wrapping text and the stored line will become too long, a suitable
 
1344
 * location to wrap will be found and the line that's complete will be
 
1345
 * output.
 
1346
 */
 
1347
void
 
1348
nsPlainTextSerializer::AddToLine(const PRUnichar * aLineFragment, 
 
1349
                                 PRInt32 aLineFragmentLength)
 
1350
{
 
1351
  PRUint32 prefixwidth = (mCiteQuoteLevel > 0 ? mCiteQuoteLevel + 1:0)+mIndent;
 
1352
  
 
1353
  if (mLineBreakDue)
 
1354
    EnsureVerticalSpace(mFloatingLines);
 
1355
 
 
1356
  PRInt32 linelength = mCurrentLine.Length();
 
1357
  if(0 == linelength) {
 
1358
    if(0 == aLineFragmentLength) {
 
1359
      // Nothing at all. Are you kidding me?
 
1360
      return;
 
1361
    }
 
1362
 
 
1363
    if(mFlags & nsIDocumentEncoder::OutputFormatFlowed) {
 
1364
      if(
 
1365
         (
 
1366
          '>' == aLineFragment[0] ||
 
1367
          ' ' == aLineFragment[0] ||
 
1368
          !nsCRT::strncmp(aLineFragment, NS_LITERAL_STRING("From ").get(), 5)
 
1369
          )
 
1370
         && mCiteQuoteLevel == 0  // We space-stuff quoted lines anyway
 
1371
         )
 
1372
        {
 
1373
          // Space stuffing a la RFC 2646 (format=flowed).
 
1374
          mCurrentLine.Append(PRUnichar(' '));
 
1375
          
 
1376
          if(MayWrap()) {
 
1377
            mCurrentLineWidth += GetUnicharWidth(' ');
 
1378
#ifdef DEBUG_wrapping
 
1379
            NS_ASSERTION(GetUnicharStringWidth(mCurrentLine.get(),
 
1380
                                               mCurrentLine.Length()) ==
 
1381
                         (PRInt32)mCurrentLineWidth,
 
1382
                         "mCurrentLineWidth and reality out of sync!");
 
1383
#endif
 
1384
          }
 
1385
        }
 
1386
    }
 
1387
    mEmptyLines=-1;
 
1388
  }
 
1389
    
 
1390
  mCurrentLine.Append(aLineFragment, aLineFragmentLength);
 
1391
  if(MayWrap()) {
 
1392
    mCurrentLineWidth += GetUnicharStringWidth(aLineFragment,
 
1393
                                               aLineFragmentLength);
 
1394
#ifdef DEBUG_wrapping
 
1395
    NS_ASSERTION(GetUnicharstringWidth(mCurrentLine.get(),
 
1396
                                       mCurrentLine.Length()) ==
 
1397
                 (PRInt32)mCurrentLineWidth,
 
1398
                 "mCurrentLineWidth and reality out of sync!");
 
1399
#endif
 
1400
  }
 
1401
 
 
1402
  linelength = mCurrentLine.Length();
 
1403
 
 
1404
  //  Wrap?
 
1405
  if(MayWrap())
 
1406
  {
 
1407
#ifdef DEBUG_wrapping
 
1408
    NS_ASSERTION(GetUnicharstringWidth(mCurrentLine.get(),
 
1409
                                  mCurrentLine.Length()) ==
 
1410
                 (PRInt32)mCurrentLineWidth,
 
1411
                 "mCurrentLineWidth and reality out of sync!");
 
1412
#endif
 
1413
    // Yes, wrap!
 
1414
    // The "+4" is to avoid wrap lines that only would be a couple
 
1415
    // of letters too long. We give this bonus only if the
 
1416
    // wrapcolumn is more than 20.
 
1417
    PRUint32 bonuswidth = (mWrapColumn > 20) ? 4 : 0;
 
1418
 
 
1419
    // XXX: Should calculate prefixwidth with GetUnicharStringWidth
 
1420
    while(mCurrentLineWidth+prefixwidth > mWrapColumn+bonuswidth) {
 
1421
      // Must wrap. Let's find a good place to do that.
 
1422
      nsresult result = NS_OK;
 
1423
      
 
1424
      // We go from the end removing one letter at a time until
 
1425
      // we have a reasonable width
 
1426
      PRInt32 goodSpace = mCurrentLine.Length();
 
1427
      PRUint32 width = mCurrentLineWidth;
 
1428
      while(goodSpace > 0 && (width+prefixwidth > mWrapColumn)) {
 
1429
        goodSpace--;
 
1430
        width -= GetUnicharWidth(mCurrentLine[goodSpace]);
 
1431
      }
 
1432
 
 
1433
      goodSpace++;
 
1434
      
 
1435
      PRBool oNeedMoreText;
 
1436
      if (nsnull != mLineBreaker) {
 
1437
        result = mLineBreaker->Prev(mCurrentLine.get(), 
 
1438
                                    mCurrentLine.Length(), goodSpace,
 
1439
                                    (PRUint32 *) &goodSpace, &oNeedMoreText);
 
1440
        if (oNeedMoreText) {
 
1441
          goodSpace = -1;
 
1442
        }
 
1443
        else if (nsCRT::IsAsciiSpace(mCurrentLine.CharAt(goodSpace-1))) {
 
1444
          --goodSpace;    // adjust the position since line breaker returns a position next to space
 
1445
        }
 
1446
      }
 
1447
      // fallback if the line breaker is unavailable or failed
 
1448
      if (nsnull == mLineBreaker || NS_FAILED(result)) {
 
1449
        goodSpace = mWrapColumn-prefixwidth;
 
1450
        while (goodSpace >= 0 &&
 
1451
               !nsCRT::IsAsciiSpace(mCurrentLine.CharAt(goodSpace))) {
 
1452
          goodSpace--;
 
1453
        }
 
1454
      }
 
1455
      
 
1456
      nsAutoString restOfLine;
 
1457
      if (goodSpace < 0) {
 
1458
        // If we don't found a good place to break, accept long line and
 
1459
        // try to find another place to break
 
1460
        goodSpace=(prefixwidth>mWrapColumn+1)?1:mWrapColumn-prefixwidth+1;
 
1461
        result = NS_OK;
 
1462
        if (nsnull != mLineBreaker) {
 
1463
          result = mLineBreaker->Next(mCurrentLine.get(), 
 
1464
                                      mCurrentLine.Length(), goodSpace,
 
1465
                                      (PRUint32 *) &goodSpace, &oNeedMoreText);
 
1466
        }
 
1467
        // fallback if the line breaker is unavailable or failed
 
1468
        if (nsnull == mLineBreaker || NS_FAILED(result)) {
 
1469
          goodSpace=(prefixwidth>mWrapColumn)?1:mWrapColumn-prefixwidth;
 
1470
          while (goodSpace < linelength &&
 
1471
                 !nsCRT::IsAsciiSpace(mCurrentLine.CharAt(goodSpace))) {
 
1472
            goodSpace++;
 
1473
          }
 
1474
        }
 
1475
      }
 
1476
      
 
1477
      if((goodSpace < linelength) && (goodSpace > 0)) {
 
1478
        // Found a place to break
 
1479
 
 
1480
        // -1 (trim a char at the break position)
 
1481
        // only if the line break was a space.
 
1482
        if (nsCRT::IsAsciiSpace(mCurrentLine.CharAt(goodSpace))) {
 
1483
          mCurrentLine.Right(restOfLine, linelength-goodSpace-1);
 
1484
        }
 
1485
        else {
 
1486
          mCurrentLine.Right(restOfLine, linelength-goodSpace);
 
1487
        }
 
1488
        mCurrentLine.Truncate(goodSpace); 
 
1489
        EndLine(PR_TRUE);
 
1490
        mCurrentLine.Truncate();
 
1491
        // Space stuff new line?
 
1492
        if(mFlags & nsIDocumentEncoder::OutputFormatFlowed) {
 
1493
          if(
 
1494
              !restOfLine.IsEmpty()
 
1495
              &&
 
1496
              (
 
1497
                restOfLine[0] == '>' ||
 
1498
                restOfLine[0] == ' ' ||
 
1499
                StringBeginsWith(restOfLine, NS_LITERAL_STRING("From "))
 
1500
              )
 
1501
              && mCiteQuoteLevel == 0  // We space-stuff quoted lines anyway
 
1502
            )
 
1503
          {
 
1504
            // Space stuffing a la RFC 2646 (format=flowed).
 
1505
            mCurrentLine.Append(PRUnichar(' '));
 
1506
            //XXX doesn't seem to work correctly for ' '
 
1507
          }
 
1508
        }
 
1509
        mCurrentLine.Append(restOfLine);
 
1510
        mCurrentLineWidth = GetUnicharStringWidth(mCurrentLine.get(),
 
1511
                                                  mCurrentLine.Length());
 
1512
        linelength = mCurrentLine.Length();
 
1513
        mEmptyLines = -1;
 
1514
      } 
 
1515
      else {
 
1516
        // Nothing to do. Hopefully we get more data later
 
1517
        // to use for a place to break line
 
1518
        break;
 
1519
      }
 
1520
    }
 
1521
  } 
 
1522
  else {
 
1523
    // No wrapping.
 
1524
  }
 
1525
}
 
1526
 
 
1527
/**
 
1528
 * Outputs the contents of mCurrentLine, and resets line specific
 
1529
 * variables. Also adds an indentation and prefix if there is
 
1530
 * one specified. Strips ending spaces from the line if it isn't
 
1531
 * preformatted.
 
1532
 */
 
1533
void
 
1534
nsPlainTextSerializer::EndLine(PRBool aSoftlinebreak)
 
1535
{
 
1536
  PRUint32 currentlinelength = mCurrentLine.Length();
 
1537
 
 
1538
  if(aSoftlinebreak && 0 == currentlinelength) {
 
1539
    // No meaning
 
1540
    return;
 
1541
  }
 
1542
  
 
1543
  // In non-preformatted mode, remove SPACE from the end
 
1544
  // of the line, unless we got "-- " in a format=flowed
 
1545
  // output. "-- " is the sig delimiter by convention and
 
1546
  // shouldn't be touched even in format=flowed
 
1547
  // (see RFC 2646). We only check for "-- " when it's a hard line
 
1548
  // break for obvious reasons.
 
1549
  if(!(mFlags & nsIDocumentEncoder::OutputPreformatted) &&
 
1550
     (aSoftlinebreak || !mCurrentLine.Equals(NS_LITERAL_STRING("-- ")))) {
 
1551
    // Remove SPACE:s from the end of the line.
 
1552
    while(currentlinelength > 0 &&
 
1553
          mCurrentLine[currentlinelength-1] == ' ') {
 
1554
      --currentlinelength;
 
1555
    }
 
1556
    mCurrentLine.SetLength(currentlinelength);
 
1557
  }
 
1558
  
 
1559
  if(aSoftlinebreak &&
 
1560
     (mFlags & nsIDocumentEncoder::OutputFormatFlowed) &&
 
1561
     (mIndent == 0)) {
 
1562
    // Add the soft part of the soft linebreak (RFC 2646 4.1)
 
1563
    // We only do this when there is no indentation since format=flowed
 
1564
    // lines and indentation doesn't work well together.
 
1565
    mCurrentLine.Append(PRUnichar(' '));
 
1566
  }
 
1567
 
 
1568
  if(aSoftlinebreak) {
 
1569
    mEmptyLines=0;
 
1570
  } 
 
1571
  else {
 
1572
    // Hard break
 
1573
    if(!mCurrentLine.IsEmpty() || !mInIndentString.IsEmpty()) {
 
1574
      mEmptyLines=-1;
 
1575
    }
 
1576
 
 
1577
    mEmptyLines++;
 
1578
  }
 
1579
 
 
1580
  if(mAtFirstColumn) {
 
1581
    // If we don't have anything "real" to output we have to
 
1582
    // make sure the indent doesn't end in a space since that
 
1583
    // would trick a format=flowed-aware receiver.
 
1584
    PRBool stripTrailingSpaces = mCurrentLine.IsEmpty();
 
1585
    OutputQuotesAndIndent(stripTrailingSpaces);
 
1586
  }
 
1587
 
 
1588
  mCurrentLine.Append(mLineBreak);
 
1589
  Output(mCurrentLine);
 
1590
  mCurrentLine.Truncate();
 
1591
  mCurrentLineWidth = 0;
 
1592
  mAtFirstColumn=PR_TRUE;
 
1593
  mInWhitespace=PR_TRUE;
 
1594
  mLineBreakDue = PR_FALSE;
 
1595
  mFloatingLines = -1;
 
1596
}
 
1597
 
 
1598
 
 
1599
/**
 
1600
 * Outputs the calculated and stored indent and text in the indentation. That is
 
1601
 * quote chars and numbers for numbered lists and such. It will also reset any
 
1602
 * stored text to put in the indentation after using it.
 
1603
 */
 
1604
void
 
1605
nsPlainTextSerializer::OutputQuotesAndIndent(PRBool stripTrailingSpaces /* = PR_FALSE */)
 
1606
{
 
1607
  nsAutoString stringToOutput;
 
1608
  
 
1609
  // Put the mail quote "> " chars in, if appropriate:
 
1610
  if (mCiteQuoteLevel > 0) {
 
1611
    nsAutoString quotes;
 
1612
    for(int i=0; i < mCiteQuoteLevel; i++) {
 
1613
      quotes.Append(PRUnichar('>'));
 
1614
    }
 
1615
    if (!mCurrentLine.IsEmpty()) {
 
1616
      /* Better don't output a space here, if the line is empty,
 
1617
         in case a recieving f=f-aware UA thinks, this were a flowed line,
 
1618
         which it isn't - it's just empty.
 
1619
         (Flowed lines may be joined with the following one,
 
1620
         so the empty line may be lost completely.) */
 
1621
      quotes.Append(PRUnichar(' '));
 
1622
    }
 
1623
    stringToOutput = quotes;
 
1624
    mAtFirstColumn = PR_FALSE;
 
1625
  }
 
1626
  
 
1627
  // Indent if necessary
 
1628
  PRInt32 indentwidth = mIndent - mInIndentString.Length();
 
1629
  if (indentwidth > 0
 
1630
      && (!mCurrentLine.IsEmpty() || !mInIndentString.IsEmpty())
 
1631
      // Don't make empty lines look flowed
 
1632
      ) {
 
1633
    nsAutoString spaces;
 
1634
    for (int i=0; i < indentwidth; ++i)
 
1635
      spaces.Append(PRUnichar(' '));
 
1636
    stringToOutput += spaces;
 
1637
    mAtFirstColumn = PR_FALSE;
 
1638
  }
 
1639
  
 
1640
  if(!mInIndentString.IsEmpty()) {
 
1641
    stringToOutput += mInIndentString;
 
1642
    mAtFirstColumn = PR_FALSE;
 
1643
    mInIndentString.Truncate();
 
1644
  }
 
1645
 
 
1646
  if(stripTrailingSpaces) {
 
1647
    PRInt32 lineLength = stringToOutput.Length();
 
1648
    while(lineLength > 0 &&
 
1649
          ' ' == stringToOutput[lineLength-1]) {
 
1650
      --lineLength;
 
1651
    }
 
1652
    stringToOutput.SetLength(lineLength);
 
1653
  }
 
1654
 
 
1655
  if(!stringToOutput.IsEmpty()) {
 
1656
    Output(stringToOutput);
 
1657
  }
 
1658
    
 
1659
}
 
1660
 
 
1661
/**
 
1662
 * Write a string. This is the highlevel function to use to get text output.
 
1663
 * By using AddToLine, Output, EndLine and other functions it handles quotation,
 
1664
 * line wrapping, indentation, whitespace compression and other things.
 
1665
 */
 
1666
void
 
1667
nsPlainTextSerializer::Write(const nsAString& aString)
 
1668
{
 
1669
#ifdef DEBUG_wrapping
 
1670
  printf("Write(%s): wrap col = %d\n",
 
1671
         NS_ConvertUCS2toUTF8(aString).get(), mWrapColumn);
 
1672
#endif
 
1673
 
 
1674
  PRInt32 bol = 0;
 
1675
  PRInt32 newline;
 
1676
  
 
1677
  PRInt32 totLen = aString.Length();
 
1678
 
 
1679
  // If the string is empty, do nothing:
 
1680
  if (totLen <= 0) return;
 
1681
 
 
1682
  // We have two major codepaths here. One that does preformatted text and one
 
1683
  // that does normal formatted text. The one for preformatted text calls
 
1684
  // Output directly while the other code path goes through AddToLine.
 
1685
  if ((mPreFormatted && !mWrapColumn) || IsInPre()
 
1686
      || ((((!mQuotesPreformatted && mSpanLevel > 0) || mDontWrapAnyQuotes))
 
1687
          && mEmptyLines >= 0 && aString.First() == PRUnichar('>'))) {
 
1688
    // No intelligent wrapping.
 
1689
 
 
1690
    // This mustn't be mixed with intelligent wrapping without clearing
 
1691
    // the mCurrentLine buffer before!!!
 
1692
    NS_WARN_IF_FALSE(mCurrentLine.IsEmpty(),
 
1693
                 "Mixed wrapping data and nonwrapping data on the same line");
 
1694
    if (!mCurrentLine.IsEmpty()) {
 
1695
      FlushLine();
 
1696
    }
 
1697
    
 
1698
    // Put the mail quote "> " chars in, if appropriate.
 
1699
    // Have to put it in before every line.
 
1700
    while(bol<totLen) {
 
1701
      if(mAtFirstColumn) {
 
1702
        OutputQuotesAndIndent();
 
1703
      }
 
1704
 
 
1705
      // Find one of '\n' or '\r' using iterators since nsAString
 
1706
      // doesn't have the old FindCharInSet function.
 
1707
      nsAString::const_iterator iter;           aString.BeginReading(iter);
 
1708
      nsAString::const_iterator done_searching; aString.EndReading(done_searching);
 
1709
      iter.advance(bol); 
 
1710
      PRInt32 new_newline = bol;
 
1711
      newline = kNotFound;
 
1712
      while(iter != done_searching) {
 
1713
        if('\n' == *iter || '\r' == *iter) {
 
1714
          newline = new_newline;
 
1715
          break;
 
1716
        }
 
1717
        ++new_newline;
 
1718
        ++iter;
 
1719
      }
 
1720
 
 
1721
      // Done searching
 
1722
      if(newline == kNotFound) {
 
1723
        // No new lines.
 
1724
        nsAutoString stringpart(Substring(aString, bol, totLen - bol));
 
1725
        if(!stringpart.IsEmpty()) {
 
1726
          PRUnichar lastchar = stringpart[stringpart.Length()-1];
 
1727
          if((lastchar == '\t') || (lastchar == ' ') ||
 
1728
             (lastchar == '\r') ||(lastchar == '\n')) {
 
1729
            mInWhitespace = PR_TRUE;
 
1730
          } 
 
1731
          else {
 
1732
            mInWhitespace = PR_FALSE;
 
1733
          }
 
1734
        }
 
1735
        Output(stringpart);
 
1736
        mEmptyLines=-1;
 
1737
        mAtFirstColumn = mAtFirstColumn && (totLen-bol)==0;
 
1738
        bol = totLen;
 
1739
      } 
 
1740
      else {
 
1741
        // There is a newline
 
1742
        nsAutoString stringpart(Substring(aString, bol, newline-bol));
 
1743
        mInWhitespace = PR_TRUE;
 
1744
        Output(stringpart);
 
1745
        // and write the newline
 
1746
        Output(mLineBreak);
 
1747
        mEmptyLines=0;
 
1748
        mAtFirstColumn = PR_TRUE;
 
1749
        bol = newline+1;
 
1750
        if('\r' == *iter && bol < totLen && '\n' == *++iter) {
 
1751
          // There was a CRLF in the input. This used to be illegal and
 
1752
          // stripped by the parser. Apparently not anymore. Let's skip
 
1753
          // over the LF.
 
1754
          bol++;
 
1755
        }
 
1756
      }
 
1757
    }
 
1758
 
 
1759
#ifdef DEBUG_wrapping
 
1760
    printf("No wrapping: newline is %d, totLen is %d\n",
 
1761
           newline, totLen);
 
1762
#endif
 
1763
    return;
 
1764
  }
 
1765
 
 
1766
  // XXX Copy necessary to use nsString methods and gain
 
1767
  // access to underlying buffer
 
1768
  nsAutoString str(aString);
 
1769
 
 
1770
  // Intelligent handling of text
 
1771
  // If needed, strip out all "end of lines"
 
1772
  // and multiple whitespace between words
 
1773
  PRInt32 nextpos;
 
1774
  nsAutoString tempstr;
 
1775
  const PRUnichar * offsetIntoBuffer = nsnull;
 
1776
  
 
1777
  while (bol < totLen) {    // Loop over lines
 
1778
    // Find a place where we may have to do whitespace compression
 
1779
    nextpos = str.FindCharInSet(" \t\n\r", bol);
 
1780
#ifdef DEBUG_wrapping
 
1781
    nsAutoString remaining;
 
1782
    str.Right(remaining, totLen - bol);
 
1783
    foo = ToNewCString(remaining);
 
1784
    //    printf("Next line: bol = %d, newlinepos = %d, totLen = %d, string = '%s'\n",
 
1785
    //           bol, nextpos, totLen, foo);
 
1786
    nsMemory::Free(foo);
 
1787
#endif
 
1788
 
 
1789
    if(nextpos == kNotFound) {
 
1790
      // The rest of the string
 
1791
      offsetIntoBuffer = str.get() + bol;
 
1792
      AddToLine(offsetIntoBuffer, totLen-bol);
 
1793
      bol=totLen;
 
1794
      mInWhitespace=PR_FALSE;
 
1795
    } 
 
1796
    else {
 
1797
      // There's still whitespace left in the string
 
1798
      if (nextpos != 0 && (nextpos + 1) < totLen) {
 
1799
        offsetIntoBuffer = str.get() + nextpos;
 
1800
        // skip '\n' if it is between CJ chars
 
1801
        if (offsetIntoBuffer[0] == '\n' && IS_CJ_CHAR(offsetIntoBuffer[-1]) && IS_CJ_CHAR(offsetIntoBuffer[1])) {
 
1802
          offsetIntoBuffer = str.get() + bol;
 
1803
          AddToLine(offsetIntoBuffer, nextpos-bol);
 
1804
          bol = nextpos + 1;
 
1805
          continue;
 
1806
        }
 
1807
      }
 
1808
      // If we're already in whitespace and not preformatted, just skip it:
 
1809
      if (mInWhitespace && (nextpos == bol) && !mPreFormatted &&
 
1810
          !(mFlags & nsIDocumentEncoder::OutputPreformatted)) {
 
1811
        // Skip whitespace
 
1812
        bol++;
 
1813
        continue;
 
1814
      }
 
1815
 
 
1816
      if(nextpos == bol) {
 
1817
        // Note that we are in whitespace.
 
1818
        mInWhitespace = PR_TRUE;
 
1819
        offsetIntoBuffer = str.get() + nextpos;
 
1820
        AddToLine(offsetIntoBuffer, 1);
 
1821
        bol++;
 
1822
        continue;
 
1823
      }
 
1824
      
 
1825
      mInWhitespace = PR_TRUE;
 
1826
      
 
1827
      offsetIntoBuffer = str.get() + bol;
 
1828
      if(mPreFormatted || (mFlags & nsIDocumentEncoder::OutputPreformatted)) {
 
1829
        // Preserve the real whitespace character
 
1830
        nextpos++;
 
1831
        AddToLine(offsetIntoBuffer, nextpos-bol);
 
1832
        bol = nextpos;
 
1833
      } 
 
1834
      else {
 
1835
        // Replace the whitespace with a space
 
1836
        AddToLine(offsetIntoBuffer, nextpos-bol);
 
1837
        AddToLine(kSpace.get(),1);
 
1838
        bol = nextpos + 1; // Let's eat the whitespace
 
1839
      }
 
1840
    }
 
1841
  } // Continue looping over the string
 
1842
}
 
1843
 
 
1844
 
 
1845
/**
 
1846
 * Gets the value of an attribute in a string. If the function returns
 
1847
 * NS_ERROR_NOT_AVAILABLE, there was none such attribute specified.
 
1848
 */
 
1849
nsresult
 
1850
nsPlainTextSerializer::GetAttributeValue(const nsIParserNode* aNode,
 
1851
                                         nsIAtom* aName,
 
1852
                                         nsString& aValueRet)
 
1853
{
 
1854
  if (mContent) {
 
1855
    if (NS_CONTENT_ATTR_NOT_THERE != mContent->GetAttr(kNameSpaceID_None,
 
1856
                                                       aName, aValueRet)) {
 
1857
      return NS_OK;
 
1858
    }
 
1859
  }
 
1860
  else if (aNode) {
 
1861
    nsAutoString name; 
 
1862
    aName->ToString(name);
 
1863
 
 
1864
    PRInt32 count = aNode->GetAttributeCount();
 
1865
    for (PRInt32 i=0;i<count;i++) {
 
1866
      const nsAString& key = aNode->GetKeyAt(i);
 
1867
      if (key.Equals(name, nsCaseInsensitiveStringComparator())) {
 
1868
        aValueRet = aNode->GetValueAt(i);
 
1869
        return NS_OK;
 
1870
      }
 
1871
    }
 
1872
  }
 
1873
 
 
1874
  return NS_ERROR_NOT_AVAILABLE;
 
1875
}
 
1876
 
 
1877
/**
 
1878
 * Returns true, if the element was inserted by Moz' TXT->HTML converter.
 
1879
 * In this case, we should ignore it.
 
1880
 */
 
1881
PRBool 
 
1882
nsPlainTextSerializer::IsCurrentNodeConverted(const nsIParserNode* aNode)
 
1883
{
 
1884
  nsAutoString value;
 
1885
  nsresult rv = GetAttributeValue(aNode, nsHTMLAtoms::kClass, value);
 
1886
  return (NS_SUCCEEDED(rv) &&
 
1887
          (value.EqualsIgnoreCase("moz-txt", 7) ||
 
1888
           value.EqualsIgnoreCase("\"moz-txt", 8)));
 
1889
}
 
1890
 
 
1891
 
 
1892
// static
 
1893
PRInt32
 
1894
nsPlainTextSerializer::GetIdForContent(nsIContent* aContent)
 
1895
{
 
1896
  if (!aContent->IsContentOfType(nsIContent::eHTML)) {
 
1897
    return eHTMLTag_unknown;
 
1898
  }
 
1899
 
 
1900
  nsIParserService* parserService = nsContentUtils::GetParserServiceWeakRef();
 
1901
 
 
1902
  PRInt32 id;
 
1903
  nsresult rv = parserService->HTMLAtomTagToId(aContent->Tag(), &id);
 
1904
  NS_ASSERTION(NS_SUCCEEDED(rv), "Can't map HTML tag to id!");
 
1905
 
 
1906
  return id;
 
1907
}
 
1908
 
 
1909
/**
 
1910
 * Returns true if the id represents an element of block type.
 
1911
 * Can be used to determine if a new paragraph should be started.
 
1912
 */
 
1913
PRBool 
 
1914
nsPlainTextSerializer::IsBlockLevel(PRInt32 aId)
 
1915
{
 
1916
  PRBool isBlock = PR_FALSE;
 
1917
 
 
1918
  nsIParserService* parserService = nsContentUtils::GetParserServiceWeakRef();
 
1919
  if (parserService) {
 
1920
    parserService->IsBlock(aId, isBlock);
 
1921
  }
 
1922
 
 
1923
  return isBlock;
 
1924
}
 
1925
 
 
1926
/**
 
1927
 * Returns true if the id represents a container.
 
1928
 */
 
1929
PRBool 
 
1930
nsPlainTextSerializer::IsContainer(PRInt32 aId)
 
1931
{
 
1932
  PRBool isContainer = PR_FALSE;
 
1933
 
 
1934
  nsIParserService* parserService = nsContentUtils::GetParserServiceWeakRef();
 
1935
  if (parserService) {
 
1936
    parserService->IsContainer(aId, isContainer);
 
1937
  }
 
1938
 
 
1939
  return isContainer;
 
1940
}
 
1941
 
 
1942
/**
 
1943
 * Returns true if we currently are inside a <pre>. The check is done
 
1944
 * by traversing the tag stack looking for <pre> until we hit a block
 
1945
 * level tag which is assumed to override any <pre>:s below it in
 
1946
 * the stack. To do this correctly to a 100% would require access
 
1947
 * to style which we don't support in this converter.
 
1948
 */  
 
1949
PRBool
 
1950
nsPlainTextSerializer::IsInPre()
 
1951
{
 
1952
  PRInt32 i = mTagStackIndex;
 
1953
  while(i > 0) {
 
1954
    if(mTagStack[i-1] == eHTMLTag_pre)
 
1955
      return PR_TRUE;
 
1956
    if(IsBlockLevel(mTagStack[i-1])) {
 
1957
      // We assume that every other block overrides a <pre>
 
1958
      return PR_FALSE;
 
1959
    }
 
1960
    --i;
 
1961
  }
 
1962
 
 
1963
  // Not a <pre> in the whole stack
 
1964
  return PR_FALSE;
 
1965
}
 
1966
 
 
1967
/**
 
1968
 * This method is required only to indentify LI's inside OL.
 
1969
 * Returns TRUE if we are inside an OL tag and FALSE otherwise.
 
1970
 */
 
1971
PRBool
 
1972
nsPlainTextSerializer::IsInOL()
 
1973
{
 
1974
  PRInt32 i = mTagStackIndex;
 
1975
  while(--i >= 0) {
 
1976
    if(mTagStack[i] == eHTMLTag_ol)
 
1977
      return PR_TRUE;
 
1978
    if (mTagStack[i] == eHTMLTag_ul) {
 
1979
      // If a UL is reached first, LI belongs the UL nested in OL.
 
1980
      return PR_FALSE;
 
1981
    }
 
1982
  }
 
1983
  // We may reach here for orphan LI's.
 
1984
  return PR_FALSE;
 
1985
}
 
1986
 
 
1987
/*
 
1988
  @return 0 = no header, 1 = h1, ..., 6 = h6
 
1989
*/
 
1990
PRInt32 HeaderLevel(eHTMLTags aTag)
 
1991
{
 
1992
  PRInt32 result;
 
1993
  switch (aTag)
 
1994
  {
 
1995
    case eHTMLTag_h1:
 
1996
      result = 1; break;
 
1997
    case eHTMLTag_h2:
 
1998
      result = 2; break;
 
1999
    case eHTMLTag_h3:
 
2000
      result = 3; break;
 
2001
    case eHTMLTag_h4:
 
2002
      result = 4; break;
 
2003
    case eHTMLTag_h5:
 
2004
      result = 5; break;
 
2005
    case eHTMLTag_h6:
 
2006
      result = 6; break;
 
2007
    default:
 
2008
      result = 0; break;
 
2009
  }
 
2010
  return result;
 
2011
}
 
2012
 
 
2013
 
 
2014
/*
 
2015
 * This is an implementation of GetUnicharWidth() and
 
2016
 * GetUnicharStringWidth() as defined in
 
2017
 * "The Single UNIX Specification, Version 2, The Open Group, 1997"
 
2018
 * <http://www.UNIX-systems.org/online.html>
 
2019
 *
 
2020
 * Markus Kuhn -- 2000-02-08 -- public domain
 
2021
 *
 
2022
 * Minor alterations to fit Mozilla's data types by Daniel Bratell
 
2023
 */
 
2024
 
 
2025
/* These functions define the column width of an ISO 10646 character
 
2026
 * as follows:
 
2027
 *
 
2028
 *    - The null character (U+0000) has a column width of 0.
 
2029
 *
 
2030
 *    - Other C0/C1 control characters and DEL will lead to a return
 
2031
 *      value of -1.
 
2032
 *
 
2033
 *    - Non-spacing and enclosing combining characters (general
 
2034
 *      category code Mn or Me in the Unicode database) have a
 
2035
 *      column width of 0.
 
2036
 *
 
2037
 *    - Spacing characters in the East Asian Wide (W) or East Asian
 
2038
 *      FullWidth (F) category as defined in Unicode Technical
 
2039
 *      Report #11 have a column width of 2.
 
2040
 *
 
2041
 *    - All remaining characters (including all printable
 
2042
 *      ISO 8859-1 and WGL4 characters, Unicode control characters,
 
2043
 *      etc.) have a column width of 1.
 
2044
 *
 
2045
 * This implementation assumes that wchar_t characters are encoded
 
2046
 * in ISO 10646.
 
2047
 */
 
2048
 
 
2049
PRInt32 GetUnicharWidth(PRUnichar ucs)
 
2050
{
 
2051
  /* sorted list of non-overlapping intervals of non-spacing characters */
 
2052
  static const struct interval {
 
2053
    PRUint16 first;
 
2054
    PRUint16 last;
 
2055
  } combining[] = {
 
2056
    { 0x0300, 0x034E }, { 0x0360, 0x0362 }, { 0x0483, 0x0486 },
 
2057
    { 0x0488, 0x0489 }, { 0x0591, 0x05A1 }, { 0x05A3, 0x05B9 },
 
2058
    { 0x05BB, 0x05BD }, { 0x05BF, 0x05BF }, { 0x05C1, 0x05C2 },
 
2059
    { 0x05C4, 0x05C4 }, { 0x064B, 0x0655 }, { 0x0670, 0x0670 },
 
2060
    { 0x06D6, 0x06E4 }, { 0x06E7, 0x06E8 }, { 0x06EA, 0x06ED },
 
2061
    { 0x0711, 0x0711 }, { 0x0730, 0x074A }, { 0x07A6, 0x07B0 },
 
2062
    { 0x0901, 0x0902 }, { 0x093C, 0x093C }, { 0x0941, 0x0948 },
 
2063
    { 0x094D, 0x094D }, { 0x0951, 0x0954 }, { 0x0962, 0x0963 },
 
2064
    { 0x0981, 0x0981 }, { 0x09BC, 0x09BC }, { 0x09C1, 0x09C4 },
 
2065
    { 0x09CD, 0x09CD }, { 0x09E2, 0x09E3 }, { 0x0A02, 0x0A02 },
 
2066
    { 0x0A3C, 0x0A3C }, { 0x0A41, 0x0A42 }, { 0x0A47, 0x0A48 },
 
2067
    { 0x0A4B, 0x0A4D }, { 0x0A70, 0x0A71 }, { 0x0A81, 0x0A82 },
 
2068
    { 0x0ABC, 0x0ABC }, { 0x0AC1, 0x0AC5 }, { 0x0AC7, 0x0AC8 },
 
2069
    { 0x0ACD, 0x0ACD }, { 0x0B01, 0x0B01 }, { 0x0B3C, 0x0B3C },
 
2070
    { 0x0B3F, 0x0B3F }, { 0x0B41, 0x0B43 }, { 0x0B4D, 0x0B4D },
 
2071
    { 0x0B56, 0x0B56 }, { 0x0B82, 0x0B82 }, { 0x0BC0, 0x0BC0 },
 
2072
    { 0x0BCD, 0x0BCD }, { 0x0C3E, 0x0C40 }, { 0x0C46, 0x0C48 },
 
2073
    { 0x0C4A, 0x0C4D }, { 0x0C55, 0x0C56 }, { 0x0CBF, 0x0CBF },
 
2074
    { 0x0CC6, 0x0CC6 }, { 0x0CCC, 0x0CCD }, { 0x0D41, 0x0D43 },
 
2075
    { 0x0D4D, 0x0D4D }, { 0x0DCA, 0x0DCA }, { 0x0DD2, 0x0DD4 },
 
2076
    { 0x0DD6, 0x0DD6 }, { 0x0E31, 0x0E31 }, { 0x0E34, 0x0E3A },
 
2077
    { 0x0E47, 0x0E4E }, { 0x0EB1, 0x0EB1 }, { 0x0EB4, 0x0EB9 },
 
2078
    { 0x0EBB, 0x0EBC }, { 0x0EC8, 0x0ECD }, { 0x0F18, 0x0F19 },
 
2079
    { 0x0F35, 0x0F35 }, { 0x0F37, 0x0F37 }, { 0x0F39, 0x0F39 },
 
2080
    { 0x0F71, 0x0F7E }, { 0x0F80, 0x0F84 }, { 0x0F86, 0x0F87 },
 
2081
    { 0x0F90, 0x0F97 }, { 0x0F99, 0x0FBC }, { 0x0FC6, 0x0FC6 },
 
2082
    { 0x102D, 0x1030 }, { 0x1032, 0x1032 }, { 0x1036, 0x1037 },
 
2083
    { 0x1039, 0x1039 }, { 0x1058, 0x1059 }, { 0x17B7, 0x17BD },
 
2084
    { 0x17C6, 0x17C6 }, { 0x17C9, 0x17D3 }, { 0x18A9, 0x18A9 },
 
2085
    { 0x20D0, 0x20E3 }, { 0x302A, 0x302F }, { 0x3099, 0x309A },
 
2086
    { 0xFB1E, 0xFB1E }, { 0xFE20, 0xFE23 }
 
2087
  };
 
2088
  PRInt32 min = 0;
 
2089
  PRInt32 max = sizeof(combining) / sizeof(struct interval) - 1;
 
2090
  PRInt32 mid;
 
2091
 
 
2092
  /* test for 8-bit control characters */
 
2093
  if (ucs == 0)
 
2094
    return 0;
 
2095
  if (ucs < 32 || (ucs >= 0x7f && ucs < 0xa0))
 
2096
    return -1;
 
2097
 
 
2098
  /* first quick check for Latin-1 etc. characters */
 
2099
  if (ucs < combining[0].first)
 
2100
    return 1;
 
2101
 
 
2102
  /* binary search in table of non-spacing characters */
 
2103
  while (max >= min) {
 
2104
    mid = (min + max) / 2;
 
2105
    if (combining[mid].last < ucs)
 
2106
      min = mid + 1;
 
2107
    else if (combining[mid].first > ucs)
 
2108
      max = mid - 1;
 
2109
    else if (combining[mid].first <= ucs && combining[mid].last >= ucs)
 
2110
      return 0;
 
2111
  }
 
2112
 
 
2113
  /* if we arrive here, ucs is not a combining or C0/C1 control character */
 
2114
 
 
2115
  /* fast test for majority of non-wide scripts */
 
2116
  if (ucs < 0x1100)
 
2117
    return 1;
 
2118
 
 
2119
  return 1 +
 
2120
    ((ucs >= 0x1100 && ucs <= 0x115f) || /* Hangul Jamo */
 
2121
     (ucs >= 0x2e80 && ucs <= 0xa4cf && (ucs & ~0x0011) != 0x300a &&
 
2122
      ucs != 0x303f) ||                  /* CJK ... Yi */
 
2123
     (ucs >= 0xac00 && ucs <= 0xd7a3) || /* Hangul Syllables */
 
2124
     (ucs >= 0xf900 && ucs <= 0xfaff) || /* CJK Compatibility Ideographs */
 
2125
     (ucs >= 0xfe30 && ucs <= 0xfe6f) || /* CJK Compatibility Forms */
 
2126
     (ucs >= 0xff00 && ucs <= 0xff5f) || /* Fullwidth Forms */
 
2127
     (ucs >= 0xffe0 && ucs <= 0xffe6));
 
2128
}
 
2129
 
 
2130
 
 
2131
PRInt32 GetUnicharStringWidth(const PRUnichar* pwcs, PRInt32 n)
 
2132
{
 
2133
  PRInt32 w, width = 0;
 
2134
 
 
2135
  for (;*pwcs && n-- > 0; pwcs++)
 
2136
    if ((w = GetUnicharWidth(*pwcs)) < 0)
 
2137
      ++width; // Taking 1 as the width of non-printable character, for bug# 94475.
 
2138
    else
 
2139
      width += w;
 
2140
 
 
2141
  return width;
 
2142
}
 
2143
 
 
2144
NS_IMETHODIMP
 
2145
nsPlainTextSerializer::PreserveSelection(nsIDOMNode * aStartContainer,
 
2146
                                         PRInt32 aStartOffset,
 
2147
                                         nsIDOMNode * aEndContainer,
 
2148
                                         PRInt32 aEndOffset)
 
2149
 
 
2150
{
 
2151
  return NS_OK;
 
2152
}
 
2153