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