Back to index

lightning-sunbird  0.9+nobinonly
nsInternetCiter.cpp
Go to the documentation of this file.
00001 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
00002 /* ***** BEGIN LICENSE BLOCK *****
00003  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00004  *
00005  * The contents of this file are subject to the Mozilla Public License Version
00006  * 1.1 (the "License"); you may not use this file except in compliance with
00007  * the License. You may obtain a copy of the License at
00008  * http://www.mozilla.org/MPL/
00009  *
00010  * Software distributed under the License is distributed on an "AS IS" basis,
00011  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00012  * for the specific language governing rights and limitations under the
00013  * License.
00014  *
00015  * The Original Code is mozilla.org code.
00016  *
00017  * The Initial Developer of the Original Code is
00018  * Netscape Communications Corporation.
00019  * Portions created by the Initial Developer are Copyright (C) 1998
00020  * the Initial Developer. All Rights Reserved.
00021  *
00022  * Contributor(s):
00023  *   Pierre Phaneuf <pp@ludusdesign.com>
00024  *
00025  * Alternatively, the contents of this file may be used under the terms of
00026  * either of the GNU General Public License Version 2 or later (the "GPL"),
00027  * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00028  * in which case the provisions of the GPL or the LGPL are applicable instead
00029  * of those above. If you wish to allow use of your version of this file only
00030  * under the terms of either the GPL or the LGPL, and not to allow others to
00031  * use your version of this file under the terms of the MPL, indicate your
00032  * decision by deleting the provisions above and replace them with the notice
00033  * and other provisions required by the GPL or the LGPL. If you do not delete
00034  * the provisions above, a recipient may use your version of this file under
00035  * the terms of any one of the MPL, the GPL or the LGPL.
00036  *
00037  * ***** END LICENSE BLOCK ***** */
00038 
00039 
00040 #include "nsString.h"
00041 #include "nsReadableUtils.h"
00042 #include "nsInternetCiter.h"
00043 #include "nsCRT.h"
00044 
00045 #include "nsCOMPtr.h"
00046 
00047 // Line breaker stuff
00048 #include "nsIServiceManager.h"
00049 #include "nsILineBreakerFactory.h"
00050 #include "nsLWBrkCIID.h"
00051 
00052 const PRUnichar gt ('>');
00053 const PRUnichar space (' ');
00054 const PRUnichar nbsp (0xa0);
00055 const PRUnichar nl ('\n');
00056 const PRUnichar cr('\r');
00057 
00062 nsInternetCiter::nsInternetCiter()
00063 {
00064 }
00065 
00066 nsInternetCiter::~nsInternetCiter()
00067 {
00068 }
00069 
00070 NS_IMPL_ISUPPORTS1(nsInternetCiter, nsICiter)
00071 
00072 NS_IMETHODIMP
00073 nsInternetCiter::GetCiteString(const nsAString& aInString, nsAString& aOutString)
00074 {
00075   aOutString.Truncate();
00076   PRUnichar uch = nl;
00077 
00078   // Strip trailing new lines which will otherwise turn up
00079   // as ugly quoted empty lines.
00080   nsReadingIterator <PRUnichar> beginIter,endIter;
00081   aInString.BeginReading(beginIter);
00082   aInString.EndReading(endIter);
00083   while(beginIter!= endIter &&
00084         (*endIter == cr ||
00085          *endIter == nl))
00086   {
00087     --endIter;
00088   }
00089 
00090   // Loop over the string:
00091   while (beginIter != endIter)
00092   {
00093     if (uch == nl)
00094     {
00095       aOutString.Append(gt);
00096       // No space between >: this is ">>> " style quoting, for
00097       // compatability with RFC 2646 and format=flowed.
00098       if (*beginIter != gt)
00099         aOutString.Append(space);
00100     }
00101 
00102     uch = *beginIter;
00103     ++beginIter;
00104 
00105     aOutString += uch;
00106   }
00107 
00108   if (uch != nl)
00109     aOutString += nl;
00110 
00111   return NS_OK;
00112 }
00113 
00114 nsresult
00115 nsInternetCiter::StripCitesAndLinebreaks(const nsAString& aInString,
00116                                          nsAString& aOutString,
00117                                          PRBool aLinebreaksToo,
00118                                          PRInt32* aCiteLevel)
00119 {
00120   if (aCiteLevel)
00121     *aCiteLevel = 0;
00122 
00123   aOutString.Truncate();
00124   nsReadingIterator <PRUnichar> beginIter,endIter;
00125   aInString.BeginReading(beginIter);
00126   aInString.EndReading(endIter);
00127   while (beginIter!= endIter)  // loop over lines
00128   {
00129     // Clear out cites first, at the beginning of the line:
00130     PRInt32 thisLineCiteLevel = 0;
00131     while (beginIter!= endIter && (*beginIter == gt || nsCRT::IsAsciiSpace(*beginIter)))
00132     {
00133       if (*beginIter == gt) ++thisLineCiteLevel;
00134       ++beginIter;
00135     }
00136 
00137     // Now copy characters until line end:
00138     while (beginIter != endIter && (*beginIter != '\r' && *beginIter != '\n'))
00139     {
00140       aOutString.Append(*beginIter);
00141       ++beginIter;
00142     }
00143     if (aLinebreaksToo)
00144       aOutString.Append(PRUnichar(' '));
00145     else
00146       aOutString.Append(PRUnichar('\n'));    // DOM linebreaks, not NS_LINEBREAK
00147       // Skip over any more consecutive linebreak-like characters:
00148     while (beginIter != endIter && (*beginIter == '\r' || *beginIter == '\n'))
00149       ++beginIter;
00150 
00151     // Done with this line -- update cite level
00152     if (aCiteLevel && (thisLineCiteLevel > *aCiteLevel))
00153       *aCiteLevel = thisLineCiteLevel;
00154   }
00155   return NS_OK;
00156 }
00157 
00158 NS_IMETHODIMP
00159 nsInternetCiter::StripCites(const nsAString& aInString, nsAString& aOutString)
00160 {
00161   return StripCitesAndLinebreaks(aInString, aOutString, PR_FALSE, 0);
00162 }
00163 
00164 static void AddCite(nsAString& aOutString, PRInt32 citeLevel)
00165 {
00166   for (PRInt32 i = 0; i < citeLevel; ++i)
00167     aOutString.Append(gt);
00168   if (citeLevel > 0)
00169     aOutString.Append(space);
00170 }
00171 
00172 static inline void
00173 BreakLine(nsAString& aOutString, PRUint32& outStringCol,
00174           PRUint32 citeLevel)
00175 {
00176   aOutString.Append(nl);
00177   if (citeLevel > 0)
00178   {
00179     AddCite(aOutString, citeLevel);
00180     outStringCol = citeLevel + 1;
00181   }
00182   else
00183     outStringCol = 0;
00184 }
00185 
00186 static inline PRBool IsSpace(PRUnichar c)
00187 {
00188   return (nsCRT::IsAsciiSpace(c) || (c == nl) || (c == cr) || (c == nbsp));
00189 }
00190 
00191 NS_IMETHODIMP
00192 nsInternetCiter::Rewrap(const nsAString& aInString,
00193                         PRUint32 aWrapCol, PRUint32 aFirstLineOffset,
00194                         PRBool aRespectNewlines,
00195                         nsAString& aOutString)
00196 {
00197   // There shouldn't be returns in this string, only dom newlines.
00198   // Check to make sure:
00199 #ifdef DEBUG
00200   PRInt32 cr = aInString.FindChar(PRUnichar('\r'));
00201   NS_ASSERTION((cr < 0), "Rewrap: CR in string gotten from DOM!\n");
00202 #endif /* DEBUG */
00203 
00204   aOutString.Truncate();
00205 
00206   nsCOMPtr<nsILineBreaker> lineBreaker;
00207   nsILineBreakerFactory *lf;
00208   nsresult rv;
00209   rv = CallGetService(NS_LWBRK_CONTRACTID, &lf);
00210   if (NS_SUCCEEDED(rv))
00211   {
00212     nsAutoString lbarg;
00213     lf->GetBreaker(lbarg, getter_AddRefs(lineBreaker));
00214     NS_RELEASE(lf);
00215   }
00216 
00217   // Loop over lines in the input string, rewrapping each one.
00218   PRUint32 length;
00219   PRUint32 posInString = 0;
00220   PRUint32 outStringCol = 0;
00221   PRUint32 citeLevel = 0;
00222   const nsPromiseFlatString &tString = PromiseFlatString(aInString);
00223   length = tString.Length();
00224 #ifdef DEBUG_wrapping
00225   int loopcount = 0;
00226 #endif
00227   while (posInString < length)
00228   {
00229 #ifdef DEBUG_wrapping
00230     printf("Outer loop: '%s'\n",
00231            NS_LossyConvertUCS2toASCII(Substring(tString, posInString,
00232                                                 length-posInString)).get());
00233     printf("out string is now: '%s'\n",
00234            NS_LossyConvertUCS2toASCII(aOutString).get());
00235 
00236 #endif
00237 
00238     // Get the new cite level here since we're at the beginning of a line
00239     PRUint32 newCiteLevel = 0;
00240     while (posInString < length && tString[posInString] == gt)
00241     {
00242       ++newCiteLevel;
00243       ++posInString;
00244       while (posInString < length && tString[posInString] == space)
00245         ++posInString;
00246     }
00247     if (posInString >= length)
00248       break;
00249 
00250     // Special case: if this is a blank line, maintain a blank line
00251     // (retain the original paragraph breaks)
00252     if (tString[posInString] == nl && !aOutString.IsEmpty())
00253     {
00254       if (aOutString.Last() != nl)
00255         aOutString.Append(nl);
00256       AddCite(aOutString, newCiteLevel);
00257       aOutString.Append(nl);
00258 
00259       ++posInString;
00260       outStringCol = 0;
00261       continue;
00262     }
00263 
00264     // If the cite level has changed, then start a new line with the
00265     // new cite level (but if we're at the beginning of the string,
00266     // don't bother).
00267     if (newCiteLevel != citeLevel && posInString > newCiteLevel+1
00268         && outStringCol != 0)
00269     {
00270       BreakLine(aOutString, outStringCol, 0);
00271     }
00272     citeLevel = newCiteLevel;
00273 
00274     // Prepend the quote level to the out string if appropriate
00275     if (outStringCol == 0)
00276     {
00277       AddCite(aOutString, citeLevel);
00278       outStringCol = citeLevel + (citeLevel ? 1 : 0);
00279     }
00280     // If it's not a cite, and we're not at the beginning of a line in
00281     // the output string, add a space to separate new text from the
00282     // previous text.
00283     else if (outStringCol > citeLevel)
00284     {
00285       aOutString.Append(space);
00286       ++outStringCol;
00287     }
00288 
00289     // find the next newline -- don't want to go farther than that
00290     PRInt32 nextNewline = tString.FindChar(nl, posInString);
00291     if (nextNewline < 0) nextNewline = length;
00292 
00293     // For now, don't wrap unquoted lines at all.
00294     // This is because the plaintext edit window has already wrapped them
00295     // by the time we get them for rewrap, yet when we call the line
00296     // breaker, it will refuse to break backwards, and we'll end up
00297     // with a line that's too long and gets displayed as a lone word
00298     // on a line by itself.  Need special logic to detect this case
00299     // and break it ourselves without resorting to the line breaker.
00300     if (citeLevel == 0)
00301     {
00302       aOutString.Append(Substring(tString, posInString,
00303                                   nextNewline-posInString));
00304       outStringCol += nextNewline - posInString;
00305       if (nextNewline != (PRInt32)length)
00306       {
00307         aOutString.Append(nl);
00308         outStringCol = 0;
00309       }
00310       posInString = nextNewline+1;
00311       continue;
00312     }
00313 
00314     // Otherwise we have to use the line breaker and loop
00315     // over this line of the input string to get all of it:
00316     while ((PRInt32)posInString < nextNewline)
00317     {
00318 #ifdef DEBUG_wrapping
00319       if (++loopcount > 1000)
00320         NS_ASSERTION(PR_FALSE, "possible infinite loop in nsInternetCiter\n");
00321 
00322       printf("Inner loop: '%s'\n",
00323              NS_LossyConvertUCS2toASCII(Substring(tString, posInString,
00324                                               nextNewline-posInString)).get());
00325 #endif
00326 
00327       // Skip over initial spaces:
00328       while ((PRInt32)posInString < nextNewline
00329              && nsCRT::IsAsciiSpace(tString[posInString]))
00330         ++posInString;
00331 
00332       // If this is a short line, just append it and continue:
00333       if (outStringCol + nextNewline - posInString <= aWrapCol-citeLevel-1)
00334       {
00335         // If this short line is the final one in the in string,
00336         // then we need to include the final newline, if any:
00337         if (nextNewline+1 == (PRInt32)length && tString[nextNewline-1] == nl)
00338           ++nextNewline;
00339 
00340         // Trim trailing spaces:
00341         PRInt32 lastRealChar = nextNewline;
00342         while ((PRUint32)lastRealChar > posInString
00343                && nsCRT::IsAsciiSpace(tString[lastRealChar-1]))
00344           --lastRealChar;
00345 
00346         aOutString += Substring(tString,
00347                                 posInString, lastRealChar - posInString);
00348         outStringCol += lastRealChar - posInString;
00349         posInString = nextNewline + 1;
00350         continue;
00351       }
00352 
00353       PRInt32 eol = posInString + aWrapCol - citeLevel - outStringCol;
00354       // eol is the prospective end of line.
00355       // We'll first look backwards from there for a place to break.
00356       // If it's already less than our current position,
00357       // then our line is already too long, so break now.
00358       if (eol <= (PRInt32)posInString)
00359       {
00360         BreakLine(aOutString, outStringCol, citeLevel);
00361         continue;    // continue inner loop, with outStringCol now at bol
00362       }
00363 
00364       PRUint32 breakPt;
00365       rv = NS_ERROR_BASE;
00366       if (lineBreaker)
00367       {
00368         PRBool needMore;
00369         rv = lineBreaker->Prev(tString.get() + posInString,
00370                                length - posInString,
00371                                eol + 1 - posInString, &breakPt, &needMore);
00372         if (NS_FAILED(rv) || needMore)
00373         {
00374           // if we couldn't find a breakpoint looking backwards,
00375           // and we're not starting a new line, then end this line
00376           // and loop around again:
00377           if (outStringCol > citeLevel + 1)
00378           {
00379             BreakLine(aOutString, outStringCol, citeLevel);
00380             continue;    // continue inner loop, with outStringCol now at bol
00381           }
00382 
00383           // Else try looking forwards:
00384           rv = lineBreaker->Next(tString.get() + posInString,
00385                                  length - posInString,
00386                                  eol - posInString, &breakPt, &needMore);
00387           if (needMore) rv = NS_ERROR_BASE;
00388         }
00389       }
00390       // If rv is okay, then breakPt is the place to break.
00391       // If we get out here and rv is set, something went wrong with line
00392       // breaker.  Just break the line, hard.
00393       if (NS_FAILED(rv))
00394       {
00395 #ifdef DEBUG_akkana
00396         printf("nsInternetCiter: LineBreaker not working -- breaking hard\n");
00397 #endif
00398         breakPt = eol;
00399       }
00400 
00401       // Special case: maybe we should have wrapped last time.
00402       // If the first breakpoint here makes the current line too long,
00403       // then if we already have text on the current line,
00404       // break and loop around again.
00405       // If we're at the beginning of the current line, though,
00406       // don't force a break since the long word might be a url
00407       // and breaking it would make it unclickable on the other end.
00408       const int SLOP = 6;
00409       if (outStringCol + breakPt > aWrapCol + SLOP
00410           && outStringCol > citeLevel+1)
00411       {
00412         BreakLine(aOutString, outStringCol, citeLevel);
00413         continue;
00414       }
00415 
00416       nsAutoString sub (Substring(tString, posInString, breakPt));
00417       // skip newlines or whitespace at the end of the string
00418       PRInt32 subend = sub.Length();
00419       while (subend > 0 && IsSpace(sub[subend-1]))
00420         --subend;
00421       sub.Left(sub, subend);
00422       aOutString += sub;
00423       outStringCol += sub.Length();
00424       // Advance past the whitespace which caused the wrap:
00425       posInString += breakPt;
00426       while (posInString < length && IsSpace(tString[posInString]))
00427         ++posInString;
00428 
00429       // Add a newline and the quote level to the out string
00430       if (posInString < length)    // not for the last line, though
00431         BreakLine(aOutString, outStringCol, citeLevel);
00432 
00433     } // end inner loop within one line of aInString
00434 #ifdef DEBUG_wrapping
00435     printf("---------\nEnd inner loop: out string is now '%s'\n-----------\n",
00436            NS_LossyConvertUCS2toASCII(aOutString).get());
00437 #endif
00438   } // end outer loop over lines of aInString
00439 
00440 #ifdef DEBUG_wrapping
00441   printf("Final out string is now: '%s'\n",
00442          NS_LossyConvertUCS2toASCII(aOutString).get());
00443 
00444 #endif
00445   return NS_OK;
00446 }
00447