Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/editor/libeditor/InternetCiter.cpp
Line
Count
Source (jump to first uncovered line)
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
#include "InternetCiter.h"
7
8
#include "nsAString.h"
9
#include "nsCOMPtr.h"
10
#include "nsCRT.h"
11
#include "nsDebug.h"
12
#include "nsDependentSubstring.h"
13
#include "nsError.h"
14
#include "mozilla/intl/LineBreaker.h"
15
#include "nsServiceManagerUtils.h"
16
#include "nsString.h"
17
#include "nsStringIterator.h"
18
19
namespace mozilla {
20
21
const char16_t gt ('>');
22
const char16_t space (' ');
23
const char16_t nl ('\n');
24
const char16_t cr('\r');
25
26
/**
27
 * Mail citations using the Internet style: > This is a citation.
28
 */
29
30
nsresult
31
InternetCiter::GetCiteString(const nsAString& aInString,
32
                             nsAString& aOutString)
33
0
{
34
0
  aOutString.Truncate();
35
0
  char16_t uch = nl;
36
0
37
0
  // Strip trailing new lines which will otherwise turn up
38
0
  // as ugly quoted empty lines.
39
0
  nsReadingIterator <char16_t> beginIter,endIter;
40
0
  aInString.BeginReading(beginIter);
41
0
  aInString.EndReading(endIter);
42
0
  while(beginIter!= endIter &&
43
0
        (*endIter == cr || *endIter == nl)) {
44
0
    --endIter;
45
0
  }
46
0
47
0
  // Loop over the string:
48
0
  while (beginIter != endIter) {
49
0
    if (uch == nl) {
50
0
      aOutString.Append(gt);
51
0
      // No space between >: this is ">>> " style quoting, for
52
0
      // compatibility with RFC 2646 and format=flowed.
53
0
      if (*beginIter != gt) {
54
0
        aOutString.Append(space);
55
0
      }
56
0
    }
57
0
58
0
    uch = *beginIter;
59
0
    ++beginIter;
60
0
61
0
    aOutString += uch;
62
0
  }
63
0
64
0
  if (uch != nl) {
65
0
    aOutString += nl;
66
0
  }
67
0
  return NS_OK;
68
0
}
69
70
static void AddCite(nsAString& aOutString, int32_t citeLevel)
71
0
{
72
0
  for (int32_t i = 0; i < citeLevel; ++i) {
73
0
    aOutString.Append(gt);
74
0
  }
75
0
  if (citeLevel > 0) {
76
0
    aOutString.Append(space);
77
0
  }
78
0
}
79
80
static inline void
81
BreakLine(nsAString& aOutString, uint32_t& outStringCol,
82
          uint32_t citeLevel)
83
0
{
84
0
  aOutString.Append(nl);
85
0
  if (citeLevel > 0) {
86
0
    AddCite(aOutString, citeLevel);
87
0
    outStringCol = citeLevel + 1;
88
0
  } else {
89
0
    outStringCol = 0;
90
0
  }
91
0
}
92
93
static inline bool IsSpace(char16_t c)
94
0
{
95
0
  const char16_t nbsp (0xa0);
96
0
  return (nsCRT::IsAsciiSpace(c) || (c == nl) || (c == cr) || (c == nbsp));
97
0
}
98
99
nsresult
100
InternetCiter::Rewrap(const nsAString& aInString,
101
                      uint32_t aWrapCol,
102
                      uint32_t aFirstLineOffset,
103
                      bool aRespectNewlines,
104
                      nsAString& aOutString)
105
0
{
106
0
  // There shouldn't be returns in this string, only dom newlines.
107
0
  // Check to make sure:
108
#ifdef DEBUG
109
  int32_t cr = aInString.FindChar(char16_t('\r'));
110
  NS_ASSERTION((cr < 0), "Rewrap: CR in string gotten from DOM!\n");
111
#endif /* DEBUG */
112
113
0
  aOutString.Truncate();
114
0
115
0
  nsresult rv;
116
0
117
0
  RefPtr<mozilla::intl::LineBreaker> lineBreaker =
118
0
    mozilla::intl::LineBreaker::Create();
119
0
  MOZ_ASSERT(lineBreaker);
120
0
121
0
  // Loop over lines in the input string, rewrapping each one.
122
0
  uint32_t length;
123
0
  uint32_t posInString = 0;
124
0
  uint32_t outStringCol = 0;
125
0
  uint32_t citeLevel = 0;
126
0
  const nsPromiseFlatString &tString = PromiseFlatString(aInString);
127
0
  length = tString.Length();
128
0
  while (posInString < length) {
129
0
    // Get the new cite level here since we're at the beginning of a line
130
0
    uint32_t newCiteLevel = 0;
131
0
    while (posInString < length && tString[posInString] == gt) {
132
0
      ++newCiteLevel;
133
0
      ++posInString;
134
0
      while (posInString < length && tString[posInString] == space) {
135
0
        ++posInString;
136
0
      }
137
0
    }
138
0
    if (posInString >= length) {
139
0
      break;
140
0
    }
141
0
142
0
    // Special case: if this is a blank line, maintain a blank line
143
0
    // (retain the original paragraph breaks)
144
0
    if (tString[posInString] == nl && !aOutString.IsEmpty()) {
145
0
      if (aOutString.Last() != nl) {
146
0
        aOutString.Append(nl);
147
0
      }
148
0
      AddCite(aOutString, newCiteLevel);
149
0
      aOutString.Append(nl);
150
0
151
0
      ++posInString;
152
0
      outStringCol = 0;
153
0
      continue;
154
0
    }
155
0
156
0
    // If the cite level has changed, then start a new line with the
157
0
    // new cite level (but if we're at the beginning of the string,
158
0
    // don't bother).
159
0
    if (newCiteLevel != citeLevel && posInString > newCiteLevel+1 &&
160
0
        outStringCol) {
161
0
      BreakLine(aOutString, outStringCol, 0);
162
0
    }
163
0
    citeLevel = newCiteLevel;
164
0
165
0
    // Prepend the quote level to the out string if appropriate
166
0
    if (!outStringCol) {
167
0
      AddCite(aOutString, citeLevel);
168
0
      outStringCol = citeLevel + (citeLevel ? 1 : 0);
169
0
    }
170
0
    // If it's not a cite, and we're not at the beginning of a line in
171
0
    // the output string, add a space to separate new text from the
172
0
    // previous text.
173
0
    else if (outStringCol > citeLevel) {
174
0
      aOutString.Append(space);
175
0
      ++outStringCol;
176
0
    }
177
0
178
0
    // find the next newline -- don't want to go farther than that
179
0
    int32_t nextNewline = tString.FindChar(nl, posInString);
180
0
    if (nextNewline < 0) {
181
0
      nextNewline = length;
182
0
    }
183
0
184
0
    // For now, don't wrap unquoted lines at all.
185
0
    // This is because the plaintext edit window has already wrapped them
186
0
    // by the time we get them for rewrap, yet when we call the line
187
0
    // breaker, it will refuse to break backwards, and we'll end up
188
0
    // with a line that's too long and gets displayed as a lone word
189
0
    // on a line by itself.  Need special logic to detect this case
190
0
    // and break it ourselves without resorting to the line breaker.
191
0
    if (!citeLevel) {
192
0
      aOutString.Append(Substring(tString, posInString,
193
0
                                  nextNewline-posInString));
194
0
      outStringCol += nextNewline - posInString;
195
0
      if (nextNewline != (int32_t)length) {
196
0
        aOutString.Append(nl);
197
0
        outStringCol = 0;
198
0
      }
199
0
      posInString = nextNewline+1;
200
0
      continue;
201
0
    }
202
0
203
0
    // Otherwise we have to use the line breaker and loop
204
0
    // over this line of the input string to get all of it:
205
0
    while ((int32_t)posInString < nextNewline) {
206
0
      // Skip over initial spaces:
207
0
      while ((int32_t)posInString < nextNewline &&
208
0
             nsCRT::IsAsciiSpace(tString[posInString])) {
209
0
        ++posInString;
210
0
      }
211
0
212
0
      // If this is a short line, just append it and continue:
213
0
      if (outStringCol + nextNewline - posInString <= aWrapCol-citeLevel-1) {
214
0
        // If this short line is the final one in the in string,
215
0
        // then we need to include the final newline, if any:
216
0
        if (nextNewline+1 == (int32_t)length && tString[nextNewline-1] == nl) {
217
0
          ++nextNewline;
218
0
        }
219
0
        // Trim trailing spaces:
220
0
        int32_t lastRealChar = nextNewline;
221
0
        while ((uint32_t)lastRealChar > posInString &&
222
0
               nsCRT::IsAsciiSpace(tString[lastRealChar-1])) {
223
0
          --lastRealChar;
224
0
        }
225
0
226
0
        aOutString += Substring(tString,
227
0
                                posInString, lastRealChar - posInString);
228
0
        outStringCol += lastRealChar - posInString;
229
0
        posInString = nextNewline + 1;
230
0
        continue;
231
0
      }
232
0
233
0
      int32_t eol = posInString + aWrapCol - citeLevel - outStringCol;
234
0
      // eol is the prospective end of line.
235
0
      // We'll first look backwards from there for a place to break.
236
0
      // If it's already less than our current position,
237
0
      // then our line is already too long, so break now.
238
0
      if (eol <= (int32_t)posInString) {
239
0
        BreakLine(aOutString, outStringCol, citeLevel);
240
0
        continue;    // continue inner loop, with outStringCol now at bol
241
0
      }
242
0
243
0
      int32_t breakPt = 0;
244
0
      // XXX Why this uses NS_ERROR_"BASE"?
245
0
      rv = NS_ERROR_BASE;
246
0
      if (lineBreaker) {
247
0
        breakPt = lineBreaker->Prev(tString.get() + posInString,
248
0
                                 length - posInString, eol + 1 - posInString);
249
0
        if (breakPt == NS_LINEBREAKER_NEED_MORE_TEXT) {
250
0
          // if we couldn't find a breakpoint looking backwards,
251
0
          // and we're not starting a new line, then end this line
252
0
          // and loop around again:
253
0
          if (outStringCol > citeLevel + 1) {
254
0
            BreakLine(aOutString, outStringCol, citeLevel);
255
0
            continue;    // continue inner loop, with outStringCol now at bol
256
0
          }
257
0
258
0
          // Else try looking forwards:
259
0
          breakPt = lineBreaker->Next(tString.get() + posInString,
260
0
                                      length - posInString, eol - posInString);
261
0
262
0
          rv = breakPt == NS_LINEBREAKER_NEED_MORE_TEXT ? NS_ERROR_BASE :
263
0
                                                          NS_OK;
264
0
        } else {
265
0
          rv = NS_OK;
266
0
        }
267
0
      }
268
0
      // If rv is okay, then breakPt is the place to break.
269
0
      // If we get out here and rv is set, something went wrong with line
270
0
      // breaker.  Just break the line, hard.
271
0
      if (NS_FAILED(rv)) {
272
0
        breakPt = eol;
273
0
      }
274
0
275
0
      // Special case: maybe we should have wrapped last time.
276
0
      // If the first breakpoint here makes the current line too long,
277
0
      // then if we already have text on the current line,
278
0
      // break and loop around again.
279
0
      // If we're at the beginning of the current line, though,
280
0
      // don't force a break since the long word might be a url
281
0
      // and breaking it would make it unclickable on the other end.
282
0
      const int SLOP = 6;
283
0
      if (outStringCol + breakPt > aWrapCol + SLOP &&
284
0
          outStringCol > citeLevel+1) {
285
0
        BreakLine(aOutString, outStringCol, citeLevel);
286
0
        continue;
287
0
      }
288
0
289
0
      nsAutoString sub (Substring(tString, posInString, breakPt));
290
0
      // skip newlines or whitespace at the end of the string
291
0
      int32_t subend = sub.Length();
292
0
      while (subend > 0 && IsSpace(sub[subend-1])) {
293
0
        --subend;
294
0
      }
295
0
      sub.Left(sub, subend);
296
0
      aOutString += sub;
297
0
      outStringCol += sub.Length();
298
0
      // Advance past the whitespace which caused the wrap:
299
0
      posInString += breakPt;
300
0
      while (posInString < length && IsSpace(tString[posInString])) {
301
0
        ++posInString;
302
0
      }
303
0
304
0
      // Add a newline and the quote level to the out string
305
0
      if (posInString < length) {  // not for the last line, though
306
0
        BreakLine(aOutString, outStringCol, citeLevel);
307
0
      }
308
0
    } // end inner loop within one line of aInString
309
0
  } // end outer loop over lines of aInString
310
0
311
0
  return NS_OK;
312
0
}
313
314
} // namespace mozilla