1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/editor/libeditor/text/nsInternetCiter.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,397 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 + 1.10 +#include "nsAString.h" 1.11 +#include "nsCOMPtr.h" 1.12 +#include "nsCRT.h" 1.13 +#include "nsDebug.h" 1.14 +#include "nsDependentSubstring.h" 1.15 +#include "nsError.h" 1.16 +#include "nsILineBreaker.h" 1.17 +#include "nsInternetCiter.h" 1.18 +#include "nsLWBrkCIID.h" 1.19 +#include "nsServiceManagerUtils.h" 1.20 +#include "nsString.h" 1.21 +#include "nsStringIterator.h" 1.22 + 1.23 +const char16_t gt ('>'); 1.24 +const char16_t space (' '); 1.25 +const char16_t nbsp (0xa0); 1.26 +const char16_t nl ('\n'); 1.27 +const char16_t cr('\r'); 1.28 + 1.29 +/** Mail citations using the Internet style: > This is a citation 1.30 + */ 1.31 + 1.32 +nsresult 1.33 +nsInternetCiter::GetCiteString(const nsAString& aInString, nsAString& aOutString) 1.34 +{ 1.35 + aOutString.Truncate(); 1.36 + char16_t uch = nl; 1.37 + 1.38 + // Strip trailing new lines which will otherwise turn up 1.39 + // as ugly quoted empty lines. 1.40 + nsReadingIterator <char16_t> beginIter,endIter; 1.41 + aInString.BeginReading(beginIter); 1.42 + aInString.EndReading(endIter); 1.43 + while(beginIter!= endIter && 1.44 + (*endIter == cr || 1.45 + *endIter == nl)) 1.46 + { 1.47 + --endIter; 1.48 + } 1.49 + 1.50 + // Loop over the string: 1.51 + while (beginIter != endIter) 1.52 + { 1.53 + if (uch == nl) 1.54 + { 1.55 + aOutString.Append(gt); 1.56 + // No space between >: this is ">>> " style quoting, for 1.57 + // compatibility with RFC 2646 and format=flowed. 1.58 + if (*beginIter != gt) 1.59 + aOutString.Append(space); 1.60 + } 1.61 + 1.62 + uch = *beginIter; 1.63 + ++beginIter; 1.64 + 1.65 + aOutString += uch; 1.66 + } 1.67 + 1.68 + if (uch != nl) 1.69 + aOutString += nl; 1.70 + 1.71 + return NS_OK; 1.72 +} 1.73 + 1.74 +nsresult 1.75 +nsInternetCiter::StripCitesAndLinebreaks(const nsAString& aInString, 1.76 + nsAString& aOutString, 1.77 + bool aLinebreaksToo, 1.78 + int32_t* aCiteLevel) 1.79 +{ 1.80 + if (aCiteLevel) 1.81 + *aCiteLevel = 0; 1.82 + 1.83 + aOutString.Truncate(); 1.84 + nsReadingIterator <char16_t> beginIter,endIter; 1.85 + aInString.BeginReading(beginIter); 1.86 + aInString.EndReading(endIter); 1.87 + while (beginIter!= endIter) // loop over lines 1.88 + { 1.89 + // Clear out cites first, at the beginning of the line: 1.90 + int32_t thisLineCiteLevel = 0; 1.91 + while (beginIter!= endIter && (*beginIter == gt || nsCRT::IsAsciiSpace(*beginIter))) 1.92 + { 1.93 + if (*beginIter == gt) ++thisLineCiteLevel; 1.94 + ++beginIter; 1.95 + } 1.96 + 1.97 + // Now copy characters until line end: 1.98 + while (beginIter != endIter && (*beginIter != '\r' && *beginIter != '\n')) 1.99 + { 1.100 + aOutString.Append(*beginIter); 1.101 + ++beginIter; 1.102 + } 1.103 + if (aLinebreaksToo) 1.104 + aOutString.Append(char16_t(' ')); 1.105 + else 1.106 + aOutString.Append(char16_t('\n')); // DOM linebreaks, not NS_LINEBREAK 1.107 + // Skip over any more consecutive linebreak-like characters: 1.108 + while (beginIter != endIter && (*beginIter == '\r' || *beginIter == '\n')) 1.109 + ++beginIter; 1.110 + 1.111 + // Done with this line -- update cite level 1.112 + if (aCiteLevel && (thisLineCiteLevel > *aCiteLevel)) 1.113 + *aCiteLevel = thisLineCiteLevel; 1.114 + } 1.115 + return NS_OK; 1.116 +} 1.117 + 1.118 +nsresult 1.119 +nsInternetCiter::StripCites(const nsAString& aInString, nsAString& aOutString) 1.120 +{ 1.121 + return StripCitesAndLinebreaks(aInString, aOutString, false, 0); 1.122 +} 1.123 + 1.124 +static void AddCite(nsAString& aOutString, int32_t citeLevel) 1.125 +{ 1.126 + for (int32_t i = 0; i < citeLevel; ++i) 1.127 + aOutString.Append(gt); 1.128 + if (citeLevel > 0) 1.129 + aOutString.Append(space); 1.130 +} 1.131 + 1.132 +static inline void 1.133 +BreakLine(nsAString& aOutString, uint32_t& outStringCol, 1.134 + uint32_t citeLevel) 1.135 +{ 1.136 + aOutString.Append(nl); 1.137 + if (citeLevel > 0) 1.138 + { 1.139 + AddCite(aOutString, citeLevel); 1.140 + outStringCol = citeLevel + 1; 1.141 + } 1.142 + else 1.143 + outStringCol = 0; 1.144 +} 1.145 + 1.146 +static inline bool IsSpace(char16_t c) 1.147 +{ 1.148 + return (nsCRT::IsAsciiSpace(c) || (c == nl) || (c == cr) || (c == nbsp)); 1.149 +} 1.150 + 1.151 +nsresult 1.152 +nsInternetCiter::Rewrap(const nsAString& aInString, 1.153 + uint32_t aWrapCol, uint32_t aFirstLineOffset, 1.154 + bool aRespectNewlines, 1.155 + nsAString& aOutString) 1.156 +{ 1.157 + // There shouldn't be returns in this string, only dom newlines. 1.158 + // Check to make sure: 1.159 +#ifdef DEBUG 1.160 + int32_t cr = aInString.FindChar(char16_t('\r')); 1.161 + NS_ASSERTION((cr < 0), "Rewrap: CR in string gotten from DOM!\n"); 1.162 +#endif /* DEBUG */ 1.163 + 1.164 + aOutString.Truncate(); 1.165 + 1.166 + nsresult rv; 1.167 + nsCOMPtr<nsILineBreaker> lineBreaker = do_GetService(NS_LBRK_CONTRACTID, &rv); 1.168 + NS_ENSURE_SUCCESS(rv, rv); 1.169 + 1.170 + // Loop over lines in the input string, rewrapping each one. 1.171 + uint32_t length; 1.172 + uint32_t posInString = 0; 1.173 + uint32_t outStringCol = 0; 1.174 + uint32_t citeLevel = 0; 1.175 + const nsPromiseFlatString &tString = PromiseFlatString(aInString); 1.176 + length = tString.Length(); 1.177 +#ifdef DEBUG_wrapping 1.178 + int loopcount = 0; 1.179 +#endif 1.180 + while (posInString < length) 1.181 + { 1.182 +#ifdef DEBUG_wrapping 1.183 + printf("Outer loop: '%s'\n", 1.184 + NS_LossyConvertUTF16toASCII(Substring(tString, posInString, 1.185 + length-posInString)).get()); 1.186 + printf("out string is now: '%s'\n", 1.187 + NS_LossyConvertUTF16toASCII(aOutString).get()); 1.188 + 1.189 +#endif 1.190 + 1.191 + // Get the new cite level here since we're at the beginning of a line 1.192 + uint32_t newCiteLevel = 0; 1.193 + while (posInString < length && tString[posInString] == gt) 1.194 + { 1.195 + ++newCiteLevel; 1.196 + ++posInString; 1.197 + while (posInString < length && tString[posInString] == space) 1.198 + ++posInString; 1.199 + } 1.200 + if (posInString >= length) 1.201 + break; 1.202 + 1.203 + // Special case: if this is a blank line, maintain a blank line 1.204 + // (retain the original paragraph breaks) 1.205 + if (tString[posInString] == nl && !aOutString.IsEmpty()) 1.206 + { 1.207 + if (aOutString.Last() != nl) 1.208 + aOutString.Append(nl); 1.209 + AddCite(aOutString, newCiteLevel); 1.210 + aOutString.Append(nl); 1.211 + 1.212 + ++posInString; 1.213 + outStringCol = 0; 1.214 + continue; 1.215 + } 1.216 + 1.217 + // If the cite level has changed, then start a new line with the 1.218 + // new cite level (but if we're at the beginning of the string, 1.219 + // don't bother). 1.220 + if (newCiteLevel != citeLevel && posInString > newCiteLevel+1 1.221 + && outStringCol != 0) 1.222 + { 1.223 + BreakLine(aOutString, outStringCol, 0); 1.224 + } 1.225 + citeLevel = newCiteLevel; 1.226 + 1.227 + // Prepend the quote level to the out string if appropriate 1.228 + if (outStringCol == 0) 1.229 + { 1.230 + AddCite(aOutString, citeLevel); 1.231 + outStringCol = citeLevel + (citeLevel ? 1 : 0); 1.232 + } 1.233 + // If it's not a cite, and we're not at the beginning of a line in 1.234 + // the output string, add a space to separate new text from the 1.235 + // previous text. 1.236 + else if (outStringCol > citeLevel) 1.237 + { 1.238 + aOutString.Append(space); 1.239 + ++outStringCol; 1.240 + } 1.241 + 1.242 + // find the next newline -- don't want to go farther than that 1.243 + int32_t nextNewline = tString.FindChar(nl, posInString); 1.244 + if (nextNewline < 0) nextNewline = length; 1.245 + 1.246 + // For now, don't wrap unquoted lines at all. 1.247 + // This is because the plaintext edit window has already wrapped them 1.248 + // by the time we get them for rewrap, yet when we call the line 1.249 + // breaker, it will refuse to break backwards, and we'll end up 1.250 + // with a line that's too long and gets displayed as a lone word 1.251 + // on a line by itself. Need special logic to detect this case 1.252 + // and break it ourselves without resorting to the line breaker. 1.253 + if (citeLevel == 0) 1.254 + { 1.255 + aOutString.Append(Substring(tString, posInString, 1.256 + nextNewline-posInString)); 1.257 + outStringCol += nextNewline - posInString; 1.258 + if (nextNewline != (int32_t)length) 1.259 + { 1.260 + aOutString.Append(nl); 1.261 + outStringCol = 0; 1.262 + } 1.263 + posInString = nextNewline+1; 1.264 + continue; 1.265 + } 1.266 + 1.267 + // Otherwise we have to use the line breaker and loop 1.268 + // over this line of the input string to get all of it: 1.269 + while ((int32_t)posInString < nextNewline) 1.270 + { 1.271 +#ifdef DEBUG_wrapping 1.272 + if (++loopcount > 1000) 1.273 + NS_ASSERTION(false, "possible infinite loop in nsInternetCiter\n"); 1.274 + 1.275 + printf("Inner loop: '%s'\n", 1.276 + NS_LossyConvertUTF16toASCII(Substring(tString, posInString, 1.277 + nextNewline-posInString)).get()); 1.278 +#endif 1.279 + 1.280 + // Skip over initial spaces: 1.281 + while ((int32_t)posInString < nextNewline 1.282 + && nsCRT::IsAsciiSpace(tString[posInString])) 1.283 + ++posInString; 1.284 + 1.285 + // If this is a short line, just append it and continue: 1.286 + if (outStringCol + nextNewline - posInString <= aWrapCol-citeLevel-1) 1.287 + { 1.288 + // If this short line is the final one in the in string, 1.289 + // then we need to include the final newline, if any: 1.290 + if (nextNewline+1 == (int32_t)length && tString[nextNewline-1] == nl) 1.291 + ++nextNewline; 1.292 + 1.293 + // Trim trailing spaces: 1.294 + int32_t lastRealChar = nextNewline; 1.295 + while ((uint32_t)lastRealChar > posInString 1.296 + && nsCRT::IsAsciiSpace(tString[lastRealChar-1])) 1.297 + --lastRealChar; 1.298 + 1.299 + aOutString += Substring(tString, 1.300 + posInString, lastRealChar - posInString); 1.301 + outStringCol += lastRealChar - posInString; 1.302 + posInString = nextNewline + 1; 1.303 + continue; 1.304 + } 1.305 + 1.306 + int32_t eol = posInString + aWrapCol - citeLevel - outStringCol; 1.307 + // eol is the prospective end of line. 1.308 + // We'll first look backwards from there for a place to break. 1.309 + // If it's already less than our current position, 1.310 + // then our line is already too long, so break now. 1.311 + if (eol <= (int32_t)posInString) 1.312 + { 1.313 + BreakLine(aOutString, outStringCol, citeLevel); 1.314 + continue; // continue inner loop, with outStringCol now at bol 1.315 + } 1.316 + 1.317 + int32_t breakPt = 0; 1.318 + rv = NS_ERROR_BASE; 1.319 + if (lineBreaker) 1.320 + { 1.321 + breakPt = lineBreaker->Prev(tString.get() + posInString, 1.322 + length - posInString, eol + 1 - posInString); 1.323 + if (breakPt == NS_LINEBREAKER_NEED_MORE_TEXT) 1.324 + { 1.325 + // if we couldn't find a breakpoint looking backwards, 1.326 + // and we're not starting a new line, then end this line 1.327 + // and loop around again: 1.328 + if (outStringCol > citeLevel + 1) 1.329 + { 1.330 + BreakLine(aOutString, outStringCol, citeLevel); 1.331 + continue; // continue inner loop, with outStringCol now at bol 1.332 + } 1.333 + 1.334 + // Else try looking forwards: 1.335 + breakPt = lineBreaker->Next(tString.get() + posInString, 1.336 + length - posInString, eol - posInString); 1.337 + if (breakPt == NS_LINEBREAKER_NEED_MORE_TEXT) rv = NS_ERROR_BASE; 1.338 + else rv = NS_OK; 1.339 + } 1.340 + else rv = NS_OK; 1.341 + } 1.342 + // If rv is okay, then breakPt is the place to break. 1.343 + // If we get out here and rv is set, something went wrong with line 1.344 + // breaker. Just break the line, hard. 1.345 + if (NS_FAILED(rv)) 1.346 + { 1.347 +#ifdef DEBUG_akkana 1.348 + printf("nsInternetCiter: LineBreaker not working -- breaking hard\n"); 1.349 +#endif 1.350 + breakPt = eol; 1.351 + } 1.352 + 1.353 + // Special case: maybe we should have wrapped last time. 1.354 + // If the first breakpoint here makes the current line too long, 1.355 + // then if we already have text on the current line, 1.356 + // break and loop around again. 1.357 + // If we're at the beginning of the current line, though, 1.358 + // don't force a break since the long word might be a url 1.359 + // and breaking it would make it unclickable on the other end. 1.360 + const int SLOP = 6; 1.361 + if (outStringCol + breakPt > aWrapCol + SLOP 1.362 + && outStringCol > citeLevel+1) 1.363 + { 1.364 + BreakLine(aOutString, outStringCol, citeLevel); 1.365 + continue; 1.366 + } 1.367 + 1.368 + nsAutoString sub (Substring(tString, posInString, breakPt)); 1.369 + // skip newlines or whitespace at the end of the string 1.370 + int32_t subend = sub.Length(); 1.371 + while (subend > 0 && IsSpace(sub[subend-1])) 1.372 + --subend; 1.373 + sub.Left(sub, subend); 1.374 + aOutString += sub; 1.375 + outStringCol += sub.Length(); 1.376 + // Advance past the whitespace which caused the wrap: 1.377 + posInString += breakPt; 1.378 + while (posInString < length && IsSpace(tString[posInString])) 1.379 + ++posInString; 1.380 + 1.381 + // Add a newline and the quote level to the out string 1.382 + if (posInString < length) // not for the last line, though 1.383 + BreakLine(aOutString, outStringCol, citeLevel); 1.384 + 1.385 + } // end inner loop within one line of aInString 1.386 +#ifdef DEBUG_wrapping 1.387 + printf("---------\nEnd inner loop: out string is now '%s'\n-----------\n", 1.388 + NS_LossyConvertUTF16toASCII(aOutString).get()); 1.389 +#endif 1.390 + } // end outer loop over lines of aInString 1.391 + 1.392 +#ifdef DEBUG_wrapping 1.393 + printf("Final out string is now: '%s'\n", 1.394 + NS_LossyConvertUTF16toASCII(aOutString).get()); 1.395 + 1.396 +#endif 1.397 + return NS_OK; 1.398 +} 1.399 + 1.400 +