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