1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* ***** BEGIN LICENSE BLOCK *****
3 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4 : *
5 : * The contents of this file are subject to the Mozilla Public License Version
6 : * 1.1 (the "License"); you may not use this file except in compliance with
7 : * the License. You may obtain a copy of the License at
8 : * http://www.mozilla.org/MPL/
9 : *
10 : * Software distributed under the License is distributed on an "AS IS" basis,
11 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 : * for the specific language governing rights and limitations under the
13 : * License.
14 : *
15 : * The Original Code is mozilla.org code.
16 : *
17 : * The Initial Developer of the Original Code is
18 : * Netscape Communications Corporation.
19 : * Portions created by the Initial Developer are Copyright (C) 1998
20 : * the Initial Developer. All Rights Reserved.
21 : *
22 : * Contributor(s):
23 : * Pierre Phaneuf <pp@ludusdesign.com>
24 : *
25 : * Alternatively, the contents of this file may be used under the terms of
26 : * either of the GNU General Public License Version 2 or later (the "GPL"),
27 : * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
28 : * in which case the provisions of the GPL or the LGPL are applicable instead
29 : * of those above. If you wish to allow use of your version of this file only
30 : * under the terms of either the GPL or the LGPL, and not to allow others to
31 : * use your version of this file under the terms of the MPL, indicate your
32 : * decision by deleting the provisions above and replace them with the notice
33 : * and other provisions required by the GPL or the LGPL. If you do not delete
34 : * the provisions above, a recipient may use your version of this file under
35 : * the terms of any one of the MPL, the GPL or the LGPL.
36 : *
37 : * ***** END LICENSE BLOCK ***** */
38 :
39 :
40 : #include "nsString.h"
41 : #include "nsReadableUtils.h"
42 : #include "nsInternetCiter.h"
43 : #include "nsCRT.h"
44 :
45 : #include "nsCOMPtr.h"
46 :
47 : // Line breaker stuff
48 : #include "nsIServiceManager.h"
49 : #include "nsLWBrkCIID.h"
50 : #include "nsILineBreaker.h"
51 :
52 : const PRUnichar gt ('>');
53 : const PRUnichar space (' ');
54 : const PRUnichar nbsp (0xa0);
55 : const PRUnichar nl ('\n');
56 : const PRUnichar cr('\r');
57 :
58 : /** Mail citations using the Internet style: > This is a citation
59 : */
60 :
61 : nsresult
62 0 : nsInternetCiter::GetCiteString(const nsAString& aInString, nsAString& aOutString)
63 : {
64 0 : aOutString.Truncate();
65 0 : PRUnichar uch = nl;
66 :
67 : // Strip trailing new lines which will otherwise turn up
68 : // as ugly quoted empty lines.
69 0 : nsReadingIterator <PRUnichar> beginIter,endIter;
70 0 : aInString.BeginReading(beginIter);
71 0 : aInString.EndReading(endIter);
72 0 : while(beginIter!= endIter &&
73 0 : (*endIter == cr ||
74 0 : *endIter == nl))
75 : {
76 0 : --endIter;
77 : }
78 :
79 : // Loop over the string:
80 0 : while (beginIter != endIter)
81 : {
82 0 : if (uch == nl)
83 : {
84 0 : aOutString.Append(gt);
85 : // No space between >: this is ">>> " style quoting, for
86 : // compatibility with RFC 2646 and format=flowed.
87 0 : if (*beginIter != gt)
88 0 : aOutString.Append(space);
89 : }
90 :
91 0 : uch = *beginIter;
92 0 : ++beginIter;
93 :
94 0 : aOutString += uch;
95 : }
96 :
97 0 : if (uch != nl)
98 0 : aOutString += nl;
99 :
100 0 : return NS_OK;
101 : }
102 :
103 : nsresult
104 0 : nsInternetCiter::StripCitesAndLinebreaks(const nsAString& aInString,
105 : nsAString& aOutString,
106 : bool aLinebreaksToo,
107 : PRInt32* aCiteLevel)
108 : {
109 0 : if (aCiteLevel)
110 0 : *aCiteLevel = 0;
111 :
112 0 : aOutString.Truncate();
113 0 : nsReadingIterator <PRUnichar> beginIter,endIter;
114 0 : aInString.BeginReading(beginIter);
115 0 : aInString.EndReading(endIter);
116 0 : while (beginIter!= endIter) // loop over lines
117 : {
118 : // Clear out cites first, at the beginning of the line:
119 0 : PRInt32 thisLineCiteLevel = 0;
120 0 : while (beginIter!= endIter && (*beginIter == gt || nsCRT::IsAsciiSpace(*beginIter)))
121 : {
122 0 : if (*beginIter == gt) ++thisLineCiteLevel;
123 0 : ++beginIter;
124 : }
125 :
126 : // Now copy characters until line end:
127 0 : while (beginIter != endIter && (*beginIter != '\r' && *beginIter != '\n'))
128 : {
129 0 : aOutString.Append(*beginIter);
130 0 : ++beginIter;
131 : }
132 0 : if (aLinebreaksToo)
133 0 : aOutString.Append(PRUnichar(' '));
134 : else
135 0 : aOutString.Append(PRUnichar('\n')); // DOM linebreaks, not NS_LINEBREAK
136 : // Skip over any more consecutive linebreak-like characters:
137 0 : while (beginIter != endIter && (*beginIter == '\r' || *beginIter == '\n'))
138 0 : ++beginIter;
139 :
140 : // Done with this line -- update cite level
141 0 : if (aCiteLevel && (thisLineCiteLevel > *aCiteLevel))
142 0 : *aCiteLevel = thisLineCiteLevel;
143 : }
144 0 : return NS_OK;
145 : }
146 :
147 : nsresult
148 0 : nsInternetCiter::StripCites(const nsAString& aInString, nsAString& aOutString)
149 : {
150 0 : return StripCitesAndLinebreaks(aInString, aOutString, false, 0);
151 : }
152 :
153 0 : static void AddCite(nsAString& aOutString, PRInt32 citeLevel)
154 : {
155 0 : for (PRInt32 i = 0; i < citeLevel; ++i)
156 0 : aOutString.Append(gt);
157 0 : if (citeLevel > 0)
158 0 : aOutString.Append(space);
159 0 : }
160 :
161 : static inline void
162 0 : BreakLine(nsAString& aOutString, PRUint32& outStringCol,
163 : PRUint32 citeLevel)
164 : {
165 0 : aOutString.Append(nl);
166 0 : if (citeLevel > 0)
167 : {
168 0 : AddCite(aOutString, citeLevel);
169 0 : outStringCol = citeLevel + 1;
170 : }
171 : else
172 0 : outStringCol = 0;
173 0 : }
174 :
175 0 : static inline bool IsSpace(PRUnichar c)
176 : {
177 0 : return (nsCRT::IsAsciiSpace(c) || (c == nl) || (c == cr) || (c == nbsp));
178 : }
179 :
180 : nsresult
181 0 : nsInternetCiter::Rewrap(const nsAString& aInString,
182 : PRUint32 aWrapCol, PRUint32 aFirstLineOffset,
183 : bool aRespectNewlines,
184 : nsAString& aOutString)
185 : {
186 : // There shouldn't be returns in this string, only dom newlines.
187 : // Check to make sure:
188 : #ifdef DEBUG
189 0 : PRInt32 cr = aInString.FindChar(PRUnichar('\r'));
190 0 : NS_ASSERTION((cr < 0), "Rewrap: CR in string gotten from DOM!\n");
191 : #endif /* DEBUG */
192 :
193 0 : aOutString.Truncate();
194 :
195 : nsresult rv;
196 0 : nsCOMPtr<nsILineBreaker> lineBreaker = do_GetService(NS_LBRK_CONTRACTID, &rv);
197 0 : NS_ENSURE_SUCCESS(rv, rv);
198 :
199 : // Loop over lines in the input string, rewrapping each one.
200 : PRUint32 length;
201 0 : PRUint32 posInString = 0;
202 0 : PRUint32 outStringCol = 0;
203 0 : PRUint32 citeLevel = 0;
204 0 : const nsPromiseFlatString &tString = PromiseFlatString(aInString);
205 0 : length = tString.Length();
206 : #ifdef DEBUG_wrapping
207 : int loopcount = 0;
208 : #endif
209 0 : while (posInString < length)
210 : {
211 : #ifdef DEBUG_wrapping
212 : printf("Outer loop: '%s'\n",
213 : NS_LossyConvertUTF16toASCII(Substring(tString, posInString,
214 : length-posInString)).get());
215 : printf("out string is now: '%s'\n",
216 : NS_LossyConvertUTF16toASCII(aOutString).get());
217 :
218 : #endif
219 :
220 : // Get the new cite level here since we're at the beginning of a line
221 0 : PRUint32 newCiteLevel = 0;
222 0 : while (posInString < length && tString[posInString] == gt)
223 : {
224 0 : ++newCiteLevel;
225 0 : ++posInString;
226 0 : while (posInString < length && tString[posInString] == space)
227 0 : ++posInString;
228 : }
229 0 : if (posInString >= length)
230 0 : break;
231 :
232 : // Special case: if this is a blank line, maintain a blank line
233 : // (retain the original paragraph breaks)
234 0 : if (tString[posInString] == nl && !aOutString.IsEmpty())
235 : {
236 0 : if (aOutString.Last() != nl)
237 0 : aOutString.Append(nl);
238 0 : AddCite(aOutString, newCiteLevel);
239 0 : aOutString.Append(nl);
240 :
241 0 : ++posInString;
242 0 : outStringCol = 0;
243 0 : continue;
244 : }
245 :
246 : // If the cite level has changed, then start a new line with the
247 : // new cite level (but if we're at the beginning of the string,
248 : // don't bother).
249 0 : if (newCiteLevel != citeLevel && posInString > newCiteLevel+1
250 : && outStringCol != 0)
251 : {
252 0 : BreakLine(aOutString, outStringCol, 0);
253 : }
254 0 : citeLevel = newCiteLevel;
255 :
256 : // Prepend the quote level to the out string if appropriate
257 0 : if (outStringCol == 0)
258 : {
259 0 : AddCite(aOutString, citeLevel);
260 0 : outStringCol = citeLevel + (citeLevel ? 1 : 0);
261 : }
262 : // If it's not a cite, and we're not at the beginning of a line in
263 : // the output string, add a space to separate new text from the
264 : // previous text.
265 0 : else if (outStringCol > citeLevel)
266 : {
267 0 : aOutString.Append(space);
268 0 : ++outStringCol;
269 : }
270 :
271 : // find the next newline -- don't want to go farther than that
272 0 : PRInt32 nextNewline = tString.FindChar(nl, posInString);
273 0 : if (nextNewline < 0) nextNewline = length;
274 :
275 : // For now, don't wrap unquoted lines at all.
276 : // This is because the plaintext edit window has already wrapped them
277 : // by the time we get them for rewrap, yet when we call the line
278 : // breaker, it will refuse to break backwards, and we'll end up
279 : // with a line that's too long and gets displayed as a lone word
280 : // on a line by itself. Need special logic to detect this case
281 : // and break it ourselves without resorting to the line breaker.
282 0 : if (citeLevel == 0)
283 : {
284 : aOutString.Append(Substring(tString, posInString,
285 0 : nextNewline-posInString));
286 0 : outStringCol += nextNewline - posInString;
287 0 : if (nextNewline != (PRInt32)length)
288 : {
289 0 : aOutString.Append(nl);
290 0 : outStringCol = 0;
291 : }
292 0 : posInString = nextNewline+1;
293 0 : continue;
294 : }
295 :
296 : // Otherwise we have to use the line breaker and loop
297 : // over this line of the input string to get all of it:
298 0 : while ((PRInt32)posInString < nextNewline)
299 : {
300 : #ifdef DEBUG_wrapping
301 : if (++loopcount > 1000)
302 : NS_ASSERTION(false, "possible infinite loop in nsInternetCiter\n");
303 :
304 : printf("Inner loop: '%s'\n",
305 : NS_LossyConvertUTF16toASCII(Substring(tString, posInString,
306 : nextNewline-posInString)).get());
307 : #endif
308 :
309 : // Skip over initial spaces:
310 0 : while ((PRInt32)posInString < nextNewline
311 0 : && nsCRT::IsAsciiSpace(tString[posInString]))
312 0 : ++posInString;
313 :
314 : // If this is a short line, just append it and continue:
315 0 : if (outStringCol + nextNewline - posInString <= aWrapCol-citeLevel-1)
316 : {
317 : // If this short line is the final one in the in string,
318 : // then we need to include the final newline, if any:
319 0 : if (nextNewline+1 == (PRInt32)length && tString[nextNewline-1] == nl)
320 0 : ++nextNewline;
321 :
322 : // Trim trailing spaces:
323 0 : PRInt32 lastRealChar = nextNewline;
324 0 : while ((PRUint32)lastRealChar > posInString
325 0 : && nsCRT::IsAsciiSpace(tString[lastRealChar-1]))
326 0 : --lastRealChar;
327 :
328 : aOutString += Substring(tString,
329 0 : posInString, lastRealChar - posInString);
330 0 : outStringCol += lastRealChar - posInString;
331 0 : posInString = nextNewline + 1;
332 0 : continue;
333 : }
334 :
335 0 : PRInt32 eol = posInString + aWrapCol - citeLevel - outStringCol;
336 : // eol is the prospective end of line.
337 : // We'll first look backwards from there for a place to break.
338 : // If it's already less than our current position,
339 : // then our line is already too long, so break now.
340 0 : if (eol <= (PRInt32)posInString)
341 : {
342 0 : BreakLine(aOutString, outStringCol, citeLevel);
343 0 : continue; // continue inner loop, with outStringCol now at bol
344 : }
345 :
346 0 : PRInt32 breakPt = 0;
347 0 : rv = NS_ERROR_BASE;
348 0 : if (lineBreaker)
349 : {
350 0 : breakPt = lineBreaker->Prev(tString.get() + posInString,
351 0 : length - posInString, eol + 1 - posInString);
352 0 : if (breakPt == NS_LINEBREAKER_NEED_MORE_TEXT)
353 : {
354 : // if we couldn't find a breakpoint looking backwards,
355 : // and we're not starting a new line, then end this line
356 : // and loop around again:
357 0 : if (outStringCol > citeLevel + 1)
358 : {
359 0 : BreakLine(aOutString, outStringCol, citeLevel);
360 0 : continue; // continue inner loop, with outStringCol now at bol
361 : }
362 :
363 : // Else try looking forwards:
364 0 : breakPt = lineBreaker->Next(tString.get() + posInString,
365 0 : length - posInString, eol - posInString);
366 0 : if (breakPt == NS_LINEBREAKER_NEED_MORE_TEXT) rv = NS_ERROR_BASE;
367 0 : else rv = NS_OK;
368 : }
369 0 : else rv = NS_OK;
370 : }
371 : // If rv is okay, then breakPt is the place to break.
372 : // If we get out here and rv is set, something went wrong with line
373 : // breaker. Just break the line, hard.
374 0 : if (NS_FAILED(rv))
375 : {
376 : #ifdef DEBUG_akkana
377 : printf("nsInternetCiter: LineBreaker not working -- breaking hard\n");
378 : #endif
379 0 : breakPt = eol;
380 : }
381 :
382 : // Special case: maybe we should have wrapped last time.
383 : // If the first breakpoint here makes the current line too long,
384 : // then if we already have text on the current line,
385 : // break and loop around again.
386 : // If we're at the beginning of the current line, though,
387 : // don't force a break since the long word might be a url
388 : // and breaking it would make it unclickable on the other end.
389 0 : const int SLOP = 6;
390 0 : if (outStringCol + breakPt > aWrapCol + SLOP
391 : && outStringCol > citeLevel+1)
392 : {
393 0 : BreakLine(aOutString, outStringCol, citeLevel);
394 0 : continue;
395 : }
396 :
397 0 : nsAutoString sub (Substring(tString, posInString, breakPt));
398 : // skip newlines or whitespace at the end of the string
399 0 : PRInt32 subend = sub.Length();
400 0 : while (subend > 0 && IsSpace(sub[subend-1]))
401 0 : --subend;
402 0 : sub.Left(sub, subend);
403 0 : aOutString += sub;
404 0 : outStringCol += sub.Length();
405 : // Advance past the whitespace which caused the wrap:
406 0 : posInString += breakPt;
407 0 : while (posInString < length && IsSpace(tString[posInString]))
408 0 : ++posInString;
409 :
410 : // Add a newline and the quote level to the out string
411 0 : if (posInString < length) // not for the last line, though
412 0 : BreakLine(aOutString, outStringCol, citeLevel);
413 :
414 : } // end inner loop within one line of aInString
415 : #ifdef DEBUG_wrapping
416 : printf("---------\nEnd inner loop: out string is now '%s'\n-----------\n",
417 : NS_LossyConvertUTF16toASCII(aOutString).get());
418 : #endif
419 : } // end outer loop over lines of aInString
420 :
421 : #ifdef DEBUG_wrapping
422 : printf("Final out string is now: '%s'\n",
423 : NS_LossyConvertUTF16toASCII(aOutString).get());
424 :
425 : #endif
426 0 : return NS_OK;
427 : }
428 :
429 :
|