michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: michael@0: #include "nsAString.h" michael@0: #include "nsCOMPtr.h" michael@0: #include "nsCRT.h" michael@0: #include "nsDebug.h" michael@0: #include "nsDependentSubstring.h" michael@0: #include "nsError.h" michael@0: #include "nsILineBreaker.h" michael@0: #include "nsInternetCiter.h" michael@0: #include "nsLWBrkCIID.h" michael@0: #include "nsServiceManagerUtils.h" michael@0: #include "nsString.h" michael@0: #include "nsStringIterator.h" michael@0: michael@0: const char16_t gt ('>'); michael@0: const char16_t space (' '); michael@0: const char16_t nbsp (0xa0); michael@0: const char16_t nl ('\n'); michael@0: const char16_t cr('\r'); michael@0: michael@0: /** Mail citations using the Internet style: > This is a citation michael@0: */ michael@0: michael@0: nsresult michael@0: nsInternetCiter::GetCiteString(const nsAString& aInString, nsAString& aOutString) michael@0: { michael@0: aOutString.Truncate(); michael@0: char16_t uch = nl; michael@0: michael@0: // Strip trailing new lines which will otherwise turn up michael@0: // as ugly quoted empty lines. michael@0: nsReadingIterator beginIter,endIter; michael@0: aInString.BeginReading(beginIter); michael@0: aInString.EndReading(endIter); michael@0: while(beginIter!= endIter && michael@0: (*endIter == cr || michael@0: *endIter == nl)) michael@0: { michael@0: --endIter; michael@0: } michael@0: michael@0: // Loop over the string: michael@0: while (beginIter != endIter) michael@0: { michael@0: if (uch == nl) michael@0: { michael@0: aOutString.Append(gt); michael@0: // No space between >: this is ">>> " style quoting, for michael@0: // compatibility with RFC 2646 and format=flowed. michael@0: if (*beginIter != gt) michael@0: aOutString.Append(space); michael@0: } michael@0: michael@0: uch = *beginIter; michael@0: ++beginIter; michael@0: michael@0: aOutString += uch; michael@0: } michael@0: michael@0: if (uch != nl) michael@0: aOutString += nl; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsInternetCiter::StripCitesAndLinebreaks(const nsAString& aInString, michael@0: nsAString& aOutString, michael@0: bool aLinebreaksToo, michael@0: int32_t* aCiteLevel) michael@0: { michael@0: if (aCiteLevel) michael@0: *aCiteLevel = 0; michael@0: michael@0: aOutString.Truncate(); michael@0: nsReadingIterator beginIter,endIter; michael@0: aInString.BeginReading(beginIter); michael@0: aInString.EndReading(endIter); michael@0: while (beginIter!= endIter) // loop over lines michael@0: { michael@0: // Clear out cites first, at the beginning of the line: michael@0: int32_t thisLineCiteLevel = 0; michael@0: while (beginIter!= endIter && (*beginIter == gt || nsCRT::IsAsciiSpace(*beginIter))) michael@0: { michael@0: if (*beginIter == gt) ++thisLineCiteLevel; michael@0: ++beginIter; michael@0: } michael@0: michael@0: // Now copy characters until line end: michael@0: while (beginIter != endIter && (*beginIter != '\r' && *beginIter != '\n')) michael@0: { michael@0: aOutString.Append(*beginIter); michael@0: ++beginIter; michael@0: } michael@0: if (aLinebreaksToo) michael@0: aOutString.Append(char16_t(' ')); michael@0: else michael@0: aOutString.Append(char16_t('\n')); // DOM linebreaks, not NS_LINEBREAK michael@0: // Skip over any more consecutive linebreak-like characters: michael@0: while (beginIter != endIter && (*beginIter == '\r' || *beginIter == '\n')) michael@0: ++beginIter; michael@0: michael@0: // Done with this line -- update cite level michael@0: if (aCiteLevel && (thisLineCiteLevel > *aCiteLevel)) michael@0: *aCiteLevel = thisLineCiteLevel; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsInternetCiter::StripCites(const nsAString& aInString, nsAString& aOutString) michael@0: { michael@0: return StripCitesAndLinebreaks(aInString, aOutString, false, 0); michael@0: } michael@0: michael@0: static void AddCite(nsAString& aOutString, int32_t citeLevel) michael@0: { michael@0: for (int32_t i = 0; i < citeLevel; ++i) michael@0: aOutString.Append(gt); michael@0: if (citeLevel > 0) michael@0: aOutString.Append(space); michael@0: } michael@0: michael@0: static inline void michael@0: BreakLine(nsAString& aOutString, uint32_t& outStringCol, michael@0: uint32_t citeLevel) michael@0: { michael@0: aOutString.Append(nl); michael@0: if (citeLevel > 0) michael@0: { michael@0: AddCite(aOutString, citeLevel); michael@0: outStringCol = citeLevel + 1; michael@0: } michael@0: else michael@0: outStringCol = 0; michael@0: } michael@0: michael@0: static inline bool IsSpace(char16_t c) michael@0: { michael@0: return (nsCRT::IsAsciiSpace(c) || (c == nl) || (c == cr) || (c == nbsp)); michael@0: } michael@0: michael@0: nsresult michael@0: nsInternetCiter::Rewrap(const nsAString& aInString, michael@0: uint32_t aWrapCol, uint32_t aFirstLineOffset, michael@0: bool aRespectNewlines, michael@0: nsAString& aOutString) michael@0: { michael@0: // There shouldn't be returns in this string, only dom newlines. michael@0: // Check to make sure: michael@0: #ifdef DEBUG michael@0: int32_t cr = aInString.FindChar(char16_t('\r')); michael@0: NS_ASSERTION((cr < 0), "Rewrap: CR in string gotten from DOM!\n"); michael@0: #endif /* DEBUG */ michael@0: michael@0: aOutString.Truncate(); michael@0: michael@0: nsresult rv; michael@0: nsCOMPtr lineBreaker = do_GetService(NS_LBRK_CONTRACTID, &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Loop over lines in the input string, rewrapping each one. michael@0: uint32_t length; michael@0: uint32_t posInString = 0; michael@0: uint32_t outStringCol = 0; michael@0: uint32_t citeLevel = 0; michael@0: const nsPromiseFlatString &tString = PromiseFlatString(aInString); michael@0: length = tString.Length(); michael@0: #ifdef DEBUG_wrapping michael@0: int loopcount = 0; michael@0: #endif michael@0: while (posInString < length) michael@0: { michael@0: #ifdef DEBUG_wrapping michael@0: printf("Outer loop: '%s'\n", michael@0: NS_LossyConvertUTF16toASCII(Substring(tString, posInString, michael@0: length-posInString)).get()); michael@0: printf("out string is now: '%s'\n", michael@0: NS_LossyConvertUTF16toASCII(aOutString).get()); michael@0: michael@0: #endif michael@0: michael@0: // Get the new cite level here since we're at the beginning of a line michael@0: uint32_t newCiteLevel = 0; michael@0: while (posInString < length && tString[posInString] == gt) michael@0: { michael@0: ++newCiteLevel; michael@0: ++posInString; michael@0: while (posInString < length && tString[posInString] == space) michael@0: ++posInString; michael@0: } michael@0: if (posInString >= length) michael@0: break; michael@0: michael@0: // Special case: if this is a blank line, maintain a blank line michael@0: // (retain the original paragraph breaks) michael@0: if (tString[posInString] == nl && !aOutString.IsEmpty()) michael@0: { michael@0: if (aOutString.Last() != nl) michael@0: aOutString.Append(nl); michael@0: AddCite(aOutString, newCiteLevel); michael@0: aOutString.Append(nl); michael@0: michael@0: ++posInString; michael@0: outStringCol = 0; michael@0: continue; michael@0: } michael@0: michael@0: // If the cite level has changed, then start a new line with the michael@0: // new cite level (but if we're at the beginning of the string, michael@0: // don't bother). michael@0: if (newCiteLevel != citeLevel && posInString > newCiteLevel+1 michael@0: && outStringCol != 0) michael@0: { michael@0: BreakLine(aOutString, outStringCol, 0); michael@0: } michael@0: citeLevel = newCiteLevel; michael@0: michael@0: // Prepend the quote level to the out string if appropriate michael@0: if (outStringCol == 0) michael@0: { michael@0: AddCite(aOutString, citeLevel); michael@0: outStringCol = citeLevel + (citeLevel ? 1 : 0); michael@0: } michael@0: // If it's not a cite, and we're not at the beginning of a line in michael@0: // the output string, add a space to separate new text from the michael@0: // previous text. michael@0: else if (outStringCol > citeLevel) michael@0: { michael@0: aOutString.Append(space); michael@0: ++outStringCol; michael@0: } michael@0: michael@0: // find the next newline -- don't want to go farther than that michael@0: int32_t nextNewline = tString.FindChar(nl, posInString); michael@0: if (nextNewline < 0) nextNewline = length; michael@0: michael@0: // For now, don't wrap unquoted lines at all. michael@0: // This is because the plaintext edit window has already wrapped them michael@0: // by the time we get them for rewrap, yet when we call the line michael@0: // breaker, it will refuse to break backwards, and we'll end up michael@0: // with a line that's too long and gets displayed as a lone word michael@0: // on a line by itself. Need special logic to detect this case michael@0: // and break it ourselves without resorting to the line breaker. michael@0: if (citeLevel == 0) michael@0: { michael@0: aOutString.Append(Substring(tString, posInString, michael@0: nextNewline-posInString)); michael@0: outStringCol += nextNewline - posInString; michael@0: if (nextNewline != (int32_t)length) michael@0: { michael@0: aOutString.Append(nl); michael@0: outStringCol = 0; michael@0: } michael@0: posInString = nextNewline+1; michael@0: continue; michael@0: } michael@0: michael@0: // Otherwise we have to use the line breaker and loop michael@0: // over this line of the input string to get all of it: michael@0: while ((int32_t)posInString < nextNewline) michael@0: { michael@0: #ifdef DEBUG_wrapping michael@0: if (++loopcount > 1000) michael@0: NS_ASSERTION(false, "possible infinite loop in nsInternetCiter\n"); michael@0: michael@0: printf("Inner loop: '%s'\n", michael@0: NS_LossyConvertUTF16toASCII(Substring(tString, posInString, michael@0: nextNewline-posInString)).get()); michael@0: #endif michael@0: michael@0: // Skip over initial spaces: michael@0: while ((int32_t)posInString < nextNewline michael@0: && nsCRT::IsAsciiSpace(tString[posInString])) michael@0: ++posInString; michael@0: michael@0: // If this is a short line, just append it and continue: michael@0: if (outStringCol + nextNewline - posInString <= aWrapCol-citeLevel-1) michael@0: { michael@0: // If this short line is the final one in the in string, michael@0: // then we need to include the final newline, if any: michael@0: if (nextNewline+1 == (int32_t)length && tString[nextNewline-1] == nl) michael@0: ++nextNewline; michael@0: michael@0: // Trim trailing spaces: michael@0: int32_t lastRealChar = nextNewline; michael@0: while ((uint32_t)lastRealChar > posInString michael@0: && nsCRT::IsAsciiSpace(tString[lastRealChar-1])) michael@0: --lastRealChar; michael@0: michael@0: aOutString += Substring(tString, michael@0: posInString, lastRealChar - posInString); michael@0: outStringCol += lastRealChar - posInString; michael@0: posInString = nextNewline + 1; michael@0: continue; michael@0: } michael@0: michael@0: int32_t eol = posInString + aWrapCol - citeLevel - outStringCol; michael@0: // eol is the prospective end of line. michael@0: // We'll first look backwards from there for a place to break. michael@0: // If it's already less than our current position, michael@0: // then our line is already too long, so break now. michael@0: if (eol <= (int32_t)posInString) michael@0: { michael@0: BreakLine(aOutString, outStringCol, citeLevel); michael@0: continue; // continue inner loop, with outStringCol now at bol michael@0: } michael@0: michael@0: int32_t breakPt = 0; michael@0: rv = NS_ERROR_BASE; michael@0: if (lineBreaker) michael@0: { michael@0: breakPt = lineBreaker->Prev(tString.get() + posInString, michael@0: length - posInString, eol + 1 - posInString); michael@0: if (breakPt == NS_LINEBREAKER_NEED_MORE_TEXT) michael@0: { michael@0: // if we couldn't find a breakpoint looking backwards, michael@0: // and we're not starting a new line, then end this line michael@0: // and loop around again: michael@0: if (outStringCol > citeLevel + 1) michael@0: { michael@0: BreakLine(aOutString, outStringCol, citeLevel); michael@0: continue; // continue inner loop, with outStringCol now at bol michael@0: } michael@0: michael@0: // Else try looking forwards: michael@0: breakPt = lineBreaker->Next(tString.get() + posInString, michael@0: length - posInString, eol - posInString); michael@0: if (breakPt == NS_LINEBREAKER_NEED_MORE_TEXT) rv = NS_ERROR_BASE; michael@0: else rv = NS_OK; michael@0: } michael@0: else rv = NS_OK; michael@0: } michael@0: // If rv is okay, then breakPt is the place to break. michael@0: // If we get out here and rv is set, something went wrong with line michael@0: // breaker. Just break the line, hard. michael@0: if (NS_FAILED(rv)) michael@0: { michael@0: #ifdef DEBUG_akkana michael@0: printf("nsInternetCiter: LineBreaker not working -- breaking hard\n"); michael@0: #endif michael@0: breakPt = eol; michael@0: } michael@0: michael@0: // Special case: maybe we should have wrapped last time. michael@0: // If the first breakpoint here makes the current line too long, michael@0: // then if we already have text on the current line, michael@0: // break and loop around again. michael@0: // If we're at the beginning of the current line, though, michael@0: // don't force a break since the long word might be a url michael@0: // and breaking it would make it unclickable on the other end. michael@0: const int SLOP = 6; michael@0: if (outStringCol + breakPt > aWrapCol + SLOP michael@0: && outStringCol > citeLevel+1) michael@0: { michael@0: BreakLine(aOutString, outStringCol, citeLevel); michael@0: continue; michael@0: } michael@0: michael@0: nsAutoString sub (Substring(tString, posInString, breakPt)); michael@0: // skip newlines or whitespace at the end of the string michael@0: int32_t subend = sub.Length(); michael@0: while (subend > 0 && IsSpace(sub[subend-1])) michael@0: --subend; michael@0: sub.Left(sub, subend); michael@0: aOutString += sub; michael@0: outStringCol += sub.Length(); michael@0: // Advance past the whitespace which caused the wrap: michael@0: posInString += breakPt; michael@0: while (posInString < length && IsSpace(tString[posInString])) michael@0: ++posInString; michael@0: michael@0: // Add a newline and the quote level to the out string michael@0: if (posInString < length) // not for the last line, though michael@0: BreakLine(aOutString, outStringCol, citeLevel); michael@0: michael@0: } // end inner loop within one line of aInString michael@0: #ifdef DEBUG_wrapping michael@0: printf("---------\nEnd inner loop: out string is now '%s'\n-----------\n", michael@0: NS_LossyConvertUTF16toASCII(aOutString).get()); michael@0: #endif michael@0: } // end outer loop over lines of aInString michael@0: michael@0: #ifdef DEBUG_wrapping michael@0: printf("Final out string is now: '%s'\n", michael@0: NS_LossyConvertUTF16toASCII(aOutString).get()); michael@0: michael@0: #endif michael@0: return NS_OK; michael@0: } michael@0: michael@0: