Contribution by Daniel Bratell <bratell@lysator.liu.se> to implement

format=flowed for mail plaintext output.  Restructures the plaintext
output wrapping code, wholly or partially fixes numerous bugs in plaintext
output (13278, 13442, 17823, 17824, 13753, 12551), makes the automated
tests work on Windows and adds some new automated tests. r=akkana.
This commit is contained in:
akkana%netscape.com 1999-11-03 02:44:44 +00:00
Родитель 76f15c6c2c
Коммит eaec46cadb
30 изменённых файлов: 1188 добавлений и 456 удалений

Просмотреть файл

@ -45,7 +45,7 @@
static NS_DEFINE_CID(kCharsetConverterManagerCID, NS_ICHARSETCONVERTERMANAGER_CID);
const int gTabSize=2;
const PRInt32 gTabSize=2;
static PRBool IsInline(eHTMLTags aTag);
static PRBool IsBlockLevel(eHTMLTags aTag);
@ -164,7 +164,7 @@ nsHTMLToTXTSinkStream::nsHTMLToTXTSinkStream()
NS_INIT_REFCNT();
mColPos = 0;
mIndent = 0;
mCiteQuote = PR_FALSE;
mCiteQuoteLevel = 0;
mDoFragment = PR_FALSE;
mBufferSize = 0;
mBufferLength = 0;
@ -172,6 +172,13 @@ nsHTMLToTXTSinkStream::nsHTMLToTXTSinkStream()
mUnicodeEncoder = nsnull;
mWrapColumn = 72; // XXX magic number, we expect someone to reset this
// Flow
mEmptyLines=1; // The start of the document is an "empty line" in itself,
mCurrentLine = "";
mInWhitespace = PR_TRUE;
mPreFormatted = PR_FALSE;
mCacheLine = PR_FALSE;
// initialize the tag stack to zero:
mTagStack = new nsHTMLTag[TagStackSize];
mTagStackIndex = 0;
@ -190,7 +197,10 @@ nsHTMLToTXTSinkStream::nsHTMLToTXTSinkStream()
*/
nsHTMLToTXTSinkStream::~nsHTMLToTXTSinkStream()
{
delete [] mBuffer;
NS_ASSERTION(mCurrentLine.Length() == 0, "Buffer don't flushed! Probably illegal input to class.");
if(mBuffer)
delete[] mBuffer;
delete[] mTagStack;
delete[] mOLStack;
NS_IF_RELEASE(mUnicodeEncoder);
@ -403,7 +413,11 @@ PRBool nsHTMLToTXTSinkStream::DoOutput()
NS_IMETHODIMP
nsHTMLToTXTSinkStream::OpenContainer(const nsIParserNode& aNode)
{
eHTMLTags type = (eHTMLTags)aNode.GetNodeType();
#ifdef DEBUG_bratell
printf("OpenContainer: %d ", type);
#endif
const nsString& name = aNode.GetText();
if (name.Equals("XIF_DOC_INFO"))
{
@ -423,8 +437,31 @@ nsHTMLToTXTSinkStream::OpenContainer(const nsIParserNode& aNode)
if (type == eHTMLTag_body)
{
// body -> can turn on cacheing unless it's already preformatted
if(!(mFlags & nsIDocumentEncoder::OutputPreformatted) &&
((mFlags & nsIDocumentEncoder::OutputFormatted) ||
(mFlags & nsIDocumentEncoder::OutputWrap))) {
mCacheLine = PR_TRUE;
}
// Would be cool to figure out here whether we have a
// preformatted style attribute. It's hard, though.
// Trigger on the presence of a "-moz-pre-wrap" in the
// style attribute. That's a very simplistic way to do
// it, but better than nothing.
nsString value;
if(NS_SUCCEEDED(GetValueOfAttribute(aNode, "style", value)) &&
(-1 != value.Find("-moz-pre-wrap"))) {
mPreFormatted = PR_TRUE;
mCacheLine = PR_TRUE;
} else {
mPreFormatted = PR_FALSE;
mCacheLine = PR_TRUE; // Cache lines unless something else tells us not to
}
return NS_OK;
}
@ -453,25 +490,27 @@ nsHTMLToTXTSinkStream::OpenContainer(const nsIParserNode& aNode)
temp = "#";
}
Write(temp);
mColPos++;
// mColPos++; This is done in Write(temp) above
}
else if (type == eHTMLTag_blockquote)
{
// Find out whether it's a type=cite, and insert "> " instead.
// Eventually we should get the value of the pref controlling citations,
// and handle AOL-style citations as well.
// If we want to support RFC 2646 (and we do!) we have to have:
// >>>> text
// >>> fdfd
// when a mail is sent.
nsString value;
if (NS_SUCCEEDED(GetValueOfAttribute(aNode, "type", value))
&& value.StripChars("\"").Equals("cite", PR_TRUE))
mCiteQuote = PR_TRUE;
mCiteQuoteLevel++;
else
mIndent += gTabSize;
mIndent += gTabSize; // Check for some maximum value?
}
else if (type == eHTMLTag_pre)
{
nsAutoString temp(NS_LINEBREAK);
Write(temp);
mColPos = 0;
EnsureVerticalSpace(0);
}
// Finally, the list of tags before which we want some vertical space:
@ -482,9 +521,7 @@ nsHTMLToTXTSinkStream::OpenContainer(const nsIParserNode& aNode)
case eHTMLTag_ol:
case eHTMLTag_p:
{
nsAutoString temp(NS_LINEBREAK);
Write(temp);
mColPos = 0;
EnsureVerticalSpace((mFlags & nsIDocumentEncoder::OutputFormatted) ? 1 : 0);
break;
}
default:
@ -506,32 +543,64 @@ NS_IMETHODIMP
nsHTMLToTXTSinkStream::CloseContainer(const nsIParserNode& aNode)
{
eHTMLTags type = (eHTMLTags)aNode.GetNodeType();
#ifdef DEBUG_bratell
printf("CloseContainer: %d ", type);
#endif
if (mTagStackIndex > 0)
--mTagStackIndex;
if (type == eHTMLTag_ol)
--mOLStackIndex;
else if (type == eHTMLTag_blockquote)
{
if (mCiteQuote)
mCiteQuote = PR_FALSE;
else
if (mCiteQuoteLevel>0)
mCiteQuoteLevel--;
else if(mIndent >= gTabSize)
mIndent -= gTabSize;
}
else if (type == eHTMLTag_td)
{
// We are after a table cell an thus maybe between two cells.
// Something should be done to avoid the two cells to be written
// together. This really need some intelligence about how the
// contents in the cell looks.
// Fow now, I will only add a SPACE. Could be a TAB or something
// else but I'm not sure everything can handle the TAB so SPACE
// seems like a better solution.
if(!mInWhitespace) {
// Maybe add something else? Several spaces? A TAB? SPACE+TAB?
if(mCacheLine) {
AddToLine(" ");
} else {
WriteSimple(" ");
}
mInWhitespace = PR_TRUE;
}
}
// End current line if we're ending a block level tag
if (IsBlockLevel(type) && type != eHTMLTag_body && type != eHTMLTag_html
&& type != eHTMLTag_comment)
{
if (mColPos != 0)
{
nsAutoString temp(NS_LINEBREAK);
Write(temp);
mColPos = 0;
if(IsBlockLevel(type)) {
if((type == eHTMLTag_body) || (type == eHTMLTag_html)) {
// We want the output to end with a new line,
// but in preformatted areas like text fields,
// we can't emit newlines that weren't there.
if (mPreFormatted || (mFlags & nsIDocumentEncoder::OutputPreformatted))
FlushLine();
else
EnsureVerticalSpace(0);
} else if((type == eHTMLTag_tr) ||
(type == eHTMLTag_blockquote)) {
EnsureVerticalSpace(0);
} else {
// All other blocks get 1 vertical space after them
// in formatted mode, otherwise 0.
// This is hard. Sometimes 0 is a better number, but
// how to know?
EnsureVerticalSpace((mFlags & nsIDocumentEncoder::OutputFormatted) ? 1 : 0);
}
}
return NS_OK;
}
@ -546,71 +615,77 @@ nsHTMLToTXTSinkStream::CloseContainer(const nsIParserNode& aNode)
NS_IMETHODIMP
nsHTMLToTXTSinkStream::AddLeaf(const nsIParserNode& aNode)
{
eHTMLTags type = (eHTMLTags)aNode.GetNodeType();
#ifdef DEBUG_bratell
printf("Addleaf: %d (%d) ", (eHTMLTags)aNode.GetNodeType(),mFlags);
#endif
// If we don't want any output, just return
if (!DoOutput())
return NS_OK;
eHTMLTags type = (eHTMLTags)aNode.GetNodeType();
nsString text = aNode.GetText();
if (!DoOutput())
return NS_OK;
#ifdef DEBUG_bratell
printf(" '%s' ", text.ToNewCString());
#endif
if (type == eHTMLTag_text)
{
#if 0
// This is too generous about putting spaces around text nodes
// even when there's no indication for a space.
if (mColPos > mIndent)
{
nsAutoString temp(" ");
Write(temp);
mColPos++;
}
#endif
Write(text);
}
else if (type == eHTMLTag_entity)
{
PRUnichar entity = nsHTMLEntities::EntityToUnicode(aNode.GetText());
nsAutoString temp;
temp.Append(entity);
Write(temp);
mColPos++;
}
else if (type == eHTMLTag_br)
{
// Do this even if we're not doing formatted output:
nsAutoString temp(NS_LINEBREAK);
Write(temp);
mColPos = 0;
EnsureVerticalSpace(mEmptyLines+1);
}
// The only times we want to pass along whitespace from the original
// html source are if we're forced into preformatted mode via flags,
// or if we're prettyprinting and we're inside a <pre>.
// Otherwise, either we're collapsing to minimal text, or we're
// prettyprinting to mimic the html format, and in neither case
// does the formatting of the html source help us.
else if (mFlags & nsIDocumentEncoder::OutputPreformatted ||
((mFlags & nsIDocumentEncoder::OutputFormatted)
&& (mTagStackIndex > 0)
&& (mTagStack[mTagStackIndex-1] == eHTMLTag_pre)))
else if (type == eHTMLTag_whitespace)
{
if (type == eHTMLTag_whitespace)
// The only times we want to pass along whitespace from the original
// html source are if we're forced into preformatted mode via flags,
// or if we're prettyprinting and we're inside a <pre>.
// Otherwise, either we're collapsing to minimal text, or we're
// prettyprinting to mimic the html format, and in neither case
// does the formatting of the html source help us.
if (mFlags & nsIDocumentEncoder::OutputPreformatted ||
((mFlags & nsIDocumentEncoder::OutputFormatted)
&& (mTagStackIndex > 0)
&& (mTagStack[mTagStackIndex-1] == eHTMLTag_pre)) ||
(mPreFormatted && !mWrapColumn))
{
text = aNode.GetText();
WriteSimple(text);
mColPos += text.Length();
mEmptyLines = -1;
} else if(!mInWhitespace) {
if(mCacheLine) {
AddToLine(" ");
} else {
WriteSimple(" ");
}
mInWhitespace = PR_TRUE;
}
}
else if (type == eHTMLTag_newline)
{
if (mFlags & nsIDocumentEncoder::OutputPreformatted ||
((mFlags & nsIDocumentEncoder::OutputFormatted)
&& (mTagStackIndex > 0)
&& (mTagStack[mTagStackIndex-1] == eHTMLTag_pre)) ||
(mPreFormatted && !mWrapColumn))
{
text = aNode.GetText();
Write(text);
mColPos += text.Length();
}
else if (type == eHTMLTag_newline)
{
nsAutoString temp(NS_LINEBREAK);
Write(temp);
mColPos = 0;
EnsureVerticalSpace(mEmptyLines+1);
}
}
return NS_OK;
}
@ -668,6 +743,25 @@ void nsHTMLToTXTSinkStream::EncodeToBuffer(const nsString& aSrc)
}
void
nsHTMLToTXTSinkStream::EnsureVerticalSpace(PRInt32 noOfRows)
{
while(mEmptyLines < noOfRows)
EndLine(PR_FALSE);
}
// This empties the current line cache without adding a NEWLINE.
// Should not be used if line wrapping is of importance since
// this function destroys the cache information.
void
nsHTMLToTXTSinkStream::FlushLine()
{
WriteSimple(mCurrentLine);
mCurrentLine.SetString("");
}
/**
* WriteSimple places the contents of aString into either the output stream
@ -711,6 +805,151 @@ void nsHTMLToTXTSinkStream::WriteSimple(const nsString& aString)
}
}
void
nsHTMLToTXTSinkStream::AddToLine(const nsString &linefragment)
{
PRUint32 prefixwidth = (mCiteQuoteLevel>0?mCiteQuoteLevel+1:0)+mIndent;
PRInt32 linelength = mCurrentLine.Length();
if(0 == linelength) {
if(0 == linefragment.Length()) {
// Nothing at all. Are you kidding me?
return;
}
if(mFlags & nsIDocumentEncoder::OutputFormatFlowed) {
if((linefragment[0] == '>') ||
(linefragment[0] == ' ') ||
(!linefragment.Compare("From ",PR_FALSE,5))) {
// Space stuffing a la RFC 2646 if this will be used in a mail,
// but how can I know that??? Now space stuffing is done always
// when formatting text as HTML and that is wrong! XXX: Fix this!
mCurrentLine.Append(' ');
}
}
mEmptyLines=-1;
}
mCurrentLine.Append(linefragment);
linelength = mCurrentLine.Length();
// Wrap?
if(mWrapColumn &&
((mFlags & nsIDocumentEncoder::OutputFormatted) ||
(mFlags & nsIDocumentEncoder::OutputWrap))) {
// Yes, wrap!
// The "+4" is to avoid wrap lines that only should be a couple
// of letters too long.
while(linelength+prefixwidth > mWrapColumn+4) {
// Must wrap. Let's find a good place to do that.
PRInt32 goodSpace = mWrapColumn-prefixwidth;
while (goodSpace >= 0 &&
!nsString::IsSpace(mCurrentLine.CharAt(goodSpace))) {
goodSpace--;
}
nsAutoString restOfLine = "";
if(goodSpace<0) {
// If we don't found a good place to break, accept long line and
// try to find another place to break
goodSpace=mWrapColumn-prefixwidth;
while (goodSpace < linelength &&
!nsString::IsSpace(mCurrentLine.CharAt(goodSpace))) {
goodSpace++;
}
}
if(goodSpace < linelength && goodSpace > 0) {
// Found a place to break
mCurrentLine.Right(restOfLine, linelength-goodSpace-1);
mCurrentLine.Cut(goodSpace, linelength-goodSpace);
EndLine(PR_TRUE);
mCurrentLine.SetString(restOfLine);
linelength = mCurrentLine.Length();
mEmptyLines = -1;
} else {
// Nothing to do. Hopefully we get more data later
// to use for a place to break line
break;
}
}
} else {
// No wrapping.
}
}
void
nsHTMLToTXTSinkStream::EndLine(PRBool softlinebreak)
{
if(softlinebreak) {
if(0 == mCurrentLine.Length()) {
// No meaning
return;
}
WriteQuotesAndIndent();
// Remove whitespace from the end of the line.
mCurrentLine.CompressWhitespace(PR_FALSE,PR_TRUE);
if(mFlags & nsIDocumentEncoder::OutputFormatFlowed) {
// Add the soft part of the soft linebreak (RFC 2646 4.1)
mCurrentLine.Append(' ');
}
mCurrentLine.Append(NS_LINEBREAK);
WriteSimple(mCurrentLine);
mCurrentLine.SetString("");
mColPos=0;
mEmptyLines=0;
mInWhitespace=PR_TRUE;
} else {
// Hard break
if(0 == mColPos) {
WriteQuotesAndIndent();
}
if(mCurrentLine.Length()>0)
mEmptyLines=-1;
// Output current line
mCurrentLine.CompressWhitespace(PR_FALSE,PR_TRUE);
mCurrentLine.Append(NS_LINEBREAK);
WriteSimple(mCurrentLine);
mCurrentLine.SetString("");
mColPos=0;
mEmptyLines++;
mInWhitespace=PR_TRUE;
}
}
void
nsHTMLToTXTSinkStream::WriteQuotesAndIndent()
{
// Put the mail quote "> " chars in, if appropriate:
if (mCiteQuoteLevel>0) {
// Check for out of memory?
char* gts = NS_STATIC_CAST(char*, nsAllocator::Alloc(mCiteQuoteLevel+2));
for(int i=0; i<mCiteQuoteLevel; i++) {
gts[i]='>';
}
gts[mCiteQuoteLevel] = ' ';
gts[mCiteQuoteLevel+1] = '\0';
nsAutoString temp(gts);
WriteSimple(temp);
mColPos += (mCiteQuoteLevel+1);
nsAllocator::Free(gts);
}
// Indent if necessary
if (mIndent > 0) {
char* spaces = NS_STATIC_CAST(char*, nsAllocator::Alloc(mIndent+1));
for (int i=0; i<mIndent; ++i)
spaces[i] = ' ';
spaces[mIndent] = '\0';
nsAutoString temp(spaces);
WriteSimple(temp);
mColPos += mIndent;
nsAllocator::Free(spaces);
}
}
#ifdef DEBUG_akkana_not
#define DEBUG_wrapping 1
#endif
@ -729,49 +968,85 @@ nsHTMLToTXTSinkStream::Write(const nsString& aString)
nsAllocator::Free(foo);
#endif
PRInt32 bol = 0;
int totLen = aString.Length();
// Put the mail quote "> " chars in, if appropriate:
if (mColPos == 0)
{
if (mCiteQuote)
{
nsAutoString temp("> ");
WriteSimple(temp);
mColPos += 2;
}
// Indent if necessary
if (mIndent > 0)
{
char* spaces = NS_STATIC_CAST(char*, nsAllocator::Alloc(mIndent+1));
for (int i=0; i<mIndent; ++i)
spaces[i] = ' ';
spaces[mIndent] = '\0';
nsAutoString temp(spaces);
WriteSimple(temp);
mColPos += mIndent;
nsAllocator::Free(spaces);
}
}
PRInt32 newline;
PRInt32 totLen = aString.Length();
// Don't wrap mail-quoted text
PRUint32 wrapcol = (mCiteQuote ? 0 : mWrapColumn);
// Yes do! /Daniel Bratell
// PRUint32 wrapcol = (mCiteQuote ? 0 : mWrapColumn);
// See if there's a newline in the string:
PRInt32 newline = aString.FindCharInSet("\n\r", bol);
if ((!(mFlags & nsIDocumentEncoder::OutputFormatted)
&& !(mFlags & nsIDocumentEncoder::OutputWrap))
|| wrapcol == 0)
// PRInt32 prefixwidth = (mCiteQuoteLevel>0?mCiteQuoteLevel+1:0)+mIndent;
// PRInt32 linewidth = mWrapColumn-prefixwidth;
// if ((!(mFlags & nsIDocumentEncoder::OutputFormatted)
// && !(mFlags & nsIDocumentEncoder::OutputWrap)) ||
// ((mTagStackIndex > 0) &&
// (mTagStack[mTagStackIndex-1] == eHTMLTag_pre)))
if (((mTagStackIndex > 0) &&
(mTagStack[mTagStackIndex-1] == eHTMLTag_pre)) ||
(mPreFormatted && !mWrapColumn))
{
WriteSimple(aString);
// No intelligent wrapping. This mustn't be mixed with
// intelligent wrapping without clearing the mCurrentLine
// buffer before!!!
NS_ASSERTION(mCurrentLine.Length() == 0, "Mixed wrapping data and nonwrapping data on the same line");
// Put the mail quote "> " chars in, if appropriate.
// Have to put it in before every line.
PRInt32 newCR, newLF;
while(bol<totLen) {
if(0 == mColPos)
WriteQuotesAndIndent();
newCR = aString.FindCharInSet("\r",bol);
newLF = aString.FindCharInSet("\n",bol);
if(newCR>=0) {
if(newLF==newCR+1) {
// Found CRLF
newline=newLF;
} else if(newLF>=0 && newLF<newCR) {
// Found single LF
newline=newLF;
} else {
// Single CR
newline=newCR;
}
} else {
newline=newLF;
}
if(newline < 0) {
// No new lines.
nsAutoString stringpart;
aString.Right(stringpart, totLen-bol);
if(stringpart.Length()>0) {
PRUnichar lastchar = stringpart[stringpart.Length()-1];
if((lastchar == '\t') || (lastchar == ' ') ||
(lastchar == '\r') ||(lastchar == '\n')) {
mInWhitespace = PR_TRUE;
} else {
mInWhitespace = PR_FALSE;
}
}
WriteSimple(stringpart);
mEmptyLines=-1;
mColPos += totLen-bol;
bol = totLen;
} else {
nsAutoString stringpart;
aString.Mid(stringpart, bol, newline-bol+1);
mInWhitespace = PR_TRUE;
WriteSimple(stringpart);
mEmptyLines=0;
mColPos=0;
bol = newline+1;
}
}
// Simple attempt to be smart about col pos:
if (newline >= 0)
mColPos = totLen - newline - 1;
else
mColPos += totLen;
#ifdef DEBUG_wrapping
printf("No wrapping: newline is %d, totLen is %d; leaving mColPos = %d\n",
newline, totLen, mColPos);
@ -779,109 +1054,63 @@ nsHTMLToTXTSinkStream::Write(const nsString& aString)
return;
}
while (bol < totLen) // Loop over lines
{
// Intelligent handling of text
// Strip out all "end of lines" and multiple whitespace between words
PRInt32 nextpos;
nsAutoString tempstr;
while (bol < totLen) { // Loop over lines
nextpos = aString.FindCharInSet(" \t\n\r", bol);
#ifdef DEBUG_wrapping
nsString remaining;
aString.Right(remaining, totLen - bol);
foo = remaining.ToNewCString();
printf("Next line: bol = %d, newline = %d, totLen = %d, string = '%s'\n",
bol, newline, totLen, foo);
// printf("Next line: bol = %d, newlinepos = %d, totLen = %d, string = '%s'\n",
// bol, nextpos, totLen, foo);
nsAllocator::Free(foo);
#endif
// Set eol to the end of the string or the first newline,
// whichever comes first:
int eol = bol + wrapcol - mColPos;
if (eol > totLen || wrapcol == 0)
eol = bol + totLen;
else if (newline > 0 && eol > newline)
eol = newline;
// else we have to wrap
else
{
// search backward to find last IsSpace char:
int lastSpace = eol;
while (lastSpace > bol && !nsString::IsSpace(aString[lastSpace]))
{
#ifdef DEBUG_wrapping
aString.Right(remaining, totLen - bol);
foo = remaining.ToNewCString();
printf("Searching backward: bol = %d, string = '%s'\n", bol, foo);
nsAllocator::Free(foo);
#endif
--lastSpace;
if(nextpos < 0) {
// The rest of the string
aString.Right(tempstr, totLen-bol);
if(!mCacheLine) {
WriteSimple(tempstr);
} else {
AddToLine(tempstr);
}
if (lastSpace > bol)
{
// We found a space, so set eol to just before that
eol = lastSpace - 1;
bol=totLen;
mInWhitespace=PR_FALSE;
} else {
if(mInWhitespace && (nextpos == bol)) {
// Skip whitespace
bol++;
continue;
}
else
{
#ifdef NOTSURE
// If we reached the bol, it might just be because we were close
// to the end already and should have wrapped last time.
// In that case, write a linebreak and come around again.
// I don't remember what this comment means, so skip it for now.
if (mColPos > mIndent)
{
nsAutoString linebreak(NS_LINEBREAK);
WriteSimple(linebreak);
mColPos = 0;
continue;
}
#endif /* NOTSURE */
// Else apparently we really can't break this line at whitespace --
// so scan forward to the next space or newline, and dump a long line.
lastSpace = eol;
while (eol < totLen && !nsString::IsSpace(aString[eol])
&& (newline < 0 || eol < newline))
{
#ifdef DEBUG_wrapping
aString.Mid(remaining, bol, lastSpace - bol);
foo = remaining.ToNewCString();
printf("Searching foreward: '%c' is not a space\n line = '%s'\n",
(char)aString[lastSpace], foo);
nsAllocator::Free(foo);
#endif
++eol;
if(nextpos == bol) {
// Note that we are in whitespace.
mInWhitespace = PR_TRUE;
if(!mCacheLine) {
WriteSimple(" ");
} else {
AddToLine(" ");
}
bol++;
continue;
}
aString.Mid(tempstr,bol,nextpos-bol);
tempstr.Append(" ");
if(!mCacheLine) {
WriteSimple(tempstr);
} else {
AddToLine(tempstr);
}
mInWhitespace = PR_TRUE;
bol = nextpos + 1;
}
// At this point, bol and eol should represent the line
// we really want to dump; lastSpace isn't necessarily set.
nsAutoString lineStr;
aString.Mid(lineStr, bol, eol-bol+1);
if (eol == newline)
mColPos = 0;
else if (eol != totLen) // we're wrapping
{
lineStr.Append(NS_LINEBREAK);
mColPos = 0;
// If we broke at a space, skip that space:
// (but not necessarily any spaces that might follow it,
// see bug 12984)
if (eol < totLen && nsString::IsSpace(aString[eol+1]))
++eol;
}
else // Not wrapping and not writing a newline
mColPos += lineStr.Length();
WriteSimple(lineStr);
#ifdef DEBUG_wrapping
foo = lineStr.ToNewCString();
printf("Calling WriteSimple(%s), leaving mColPos = %d\n", foo, mColPos);
nsAllocator::Free(foo);
#endif
// Reset bol and newline:
bol = eol+1;
newline = aString.FindCharInSet("\n\r", bol);
} // Continue looping over the string
}

Просмотреть файл

@ -137,6 +137,11 @@ protected:
nsresult InitEncoder(const nsString& aCharset);
void AddToLine(const nsString& linefragment);
void EndLine(PRBool softlinebreak);
void EnsureVerticalSpace(PRInt32 noOfRows);
void FlushLine();
void WriteQuotesAndIndent();
void WriteSimple(const nsString& aString);
void Write(const nsString& aString);
void EncodeToBuffer(const nsString& aString);
@ -148,14 +153,23 @@ protected:
protected:
nsIOutputStream* mStream;
nsString* mString;
nsString mCurrentLine;
PRInt32 mIndent;
PRBool mCiteQuote;
PRInt32 mCiteQuoteLevel;
PRInt32 mColPos;
PRInt32 mFlags;
PRUint32 mWrapColumn;
PRBool mDoFragment;
// For format=flowed
PRInt32 mEmptyLines; // Will be the number of empty lines before
// the current. 0 if we are starting a new
// line and -1 if we are in a line.
PRBool mInWhitespace;
PRBool mPreFormatted;
PRBool mCacheLine; // If the line should be cached before output. This makes it possible to do smarter wrapping.
// The tag stack: the stack of tags we're operating on, so we can nest:
nsHTMLTag *mTagStack;
PRUint32 mTagStackIndex;

Просмотреть файл

@ -176,7 +176,7 @@ int main(int argc, char** argv)
Usage: %s [-i intype] [-o outtype] [-f flags] [-w wrapcol] [-c comparison_file] infile\n\
\tIn/out types are mime types (e.g. text/html)\n\
\tcomparison_file is a file against which to compare the output\n\
\t (not yet implemented\n\
\n\
\tDefaults are -i text/html -o text/plain -f 0 -w 72 [stdin]\n",
progname);
exit(0);

Просмотреть файл

@ -45,6 +45,8 @@ TEST_FILES = \
entityxif.out \
mailquote.html \
mailquote.out \
htmltable.html \
htmltable.out \
xifstuff.xif \
xifstuff.out \
$(NULL)

Просмотреть файл

@ -65,15 +65,22 @@ endif
echo "Testing conversion of XIF entities ..."
TestOutput -i text/xif -o text/plain -c OutTestData/entityxif.out OutTestData/entityxif.xif
if ($status != 0) then
echo "XIF entity convertsion test failed."
echo "XIF entity conversion test failed."
set errmsg = ($errmsg "entityxif.out")
endif
echo "Testing XIF to HTML ..."
TestOutput -i text/xif -o text/html -c OutTestData/xifstuff.out OutTestData/xifstuff.xif
if ($status != 0) then
echo "XIF entity convertsion test failed."
set errmsg = ($errmsg "entityxif.out")
echo "XIF to HTML conversion test failed."
set errmsg = ($errmsg "xifstuff.out")
endif
echo "Testing HTML Table to Text ..."
TestOutput -i text/html -o text/plain -c OutTestData/htmltable.out OutTestData/htmltable.html
if ($status != 0) then
echo "HTML Table to Plain text failed."
set errmsg = ($errmsg "htmltable.out")
endif
if (errmsg != "") then

Просмотреть файл

@ -0,0 +1,61 @@
@echo off
REM The contents of this file are subject to the Netscape Public
REM License Version 1.1 (the "License"); you may not use this file
REM except in compliance with the License. You may obtain a copy of
REM the License at http://www.mozilla.org/NPL/
REM
REM Software distributed under the License is distributed on an "AS
REM IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
REM implied. See the License for the specific language governing
REM rights and limitations under the License.
REM
REM The Original Code is Mozilla Communicator client code, released
REM March 31, 1998.
REM
REM The Initial Developer of the Original Code is Netscape
REM Communications Corporation. Portions created by Netscape are
REM Copyright (C) 1998-1999 Netscape Communications Corporation. All
REM Rights Reserved.
REM
REM Contributor(s): Akkana Peck, Daniel Bratell.
REM
REM This is a collection of test files to guard against regressions
REM in the Gecko output system.
REM
set errmsg=
echo Testing simple html to html ...
TestOutput -i text/html -o text/html -f 0 -c OutTestData/simple.html OutTestData/simple.html
IF ERRORLEVEL 1 echo Simple html to html failed (%errorlevel%). && set errmsg=%errmsg% simple.html
echo Testing simple copy case ...
TestOutput -i text/html -o text/plain -f 0 -w 0 -c OutTestData/simplecopy.out OutTestData/simple.html
IF ERRORLEVEL 1 echo Simple copy test failed. && set errmsg=%errmsg% simplecopy.out
echo Testing non-wrapped plaintext ...
TestOutput -i text/html -o text/plain -f 0 -w 0 -c OutTestData/plainnowrap.out OutTestData/plain.html
IF ERRORLEVEL 1 echo Non-wrapped plaintext test failed. && set errmsg=%errmsg% plainnowrap.out
echo Testing wrapped bug unformatted plaintext ...
TestOutput -i text/html -o text/plain -f 32 -w 50 -c OutTestData/plainwrap.out OutTestData/plain.html
IF ERRORLEVEL 1 echo Wrapped plaintext test failed. && set errmsg=%errmsg% plainwrap.out
echo Testing mail quoting ...
TestOutput -i text/html -o text/plain -c OutTestData/mailquote.out OutTestData/mailquote.html
IF ERRORLEVEL 1 echo Mail quoting test failed. && set errmsg=%errmsg% mailquote.out
echo Testing conversion of XIF entities ...
TestOutput -i text/xif -o text/plain -c OutTestData/entityxif.out OutTestData/entityxif.xif
IF ERRORLEVEL 1 echo XIF entity conversion test failed. && set errmsg=%errmsg% entityxif.out
echo Testing XIF to HTML ...
TestOutput -i text/xif -o text/html -c OutTestData/xifstuff.out OutTestData/xifstuff.xif
IF ERRORLEVEL 1 echo XIF to HTML conversion test failed. && set errmsg=%errmsg% xifstuff.out
echo Testing HTML Table to Text ...
TestOutput -i text/html -o text/plain -c OutTestData/htmltable.out OutTestData/htmltable.html
IF ERRORLEVEL 1 echo HTML Table to Plain text failed (%errorlevel%). && set errmsg=%errmsg% htmltable.out
IF DEFINED %errmsg% echo && echo TESTS FAILED: %errmsg% && exit 1

Просмотреть файл

@ -0,0 +1,16 @@
<html>
<head>
<title>HTML To Text Test Page</title>
</head>
<body>
Below is a table.<br>
<table>
<tr><td>Row 1 Col 1<td>Row 1 Col 2</td><td>Row 1 Col 3</tr>
<tr><td>Row 2 Col 1 <td>Row 2 Col 2</td><td> Row 2 Col 3</tr>
<tr><td>Row 3 Col 1</td><td>Row 3 Col 2</td><td>Row 3 Col 3</tr>
</table>
Here is after table.
</body>
</html>

Просмотреть файл

@ -0,0 +1,7 @@
Below is a table.
Row 1 Col 1 Row 1 Col 2 Row 1 Col 3
Row 2 Col 1 Row 2 Col 2 Row 2 Col 3
Row 3 Col 1 Row 3 Col 2 Row 3 Col 3
Here is after table.

Просмотреть файл

@ -20,17 +20,20 @@
--
-- Contributor(s):
-->
<title>Ender Plain Text Test Page</title>
<title>Mail Quoting Test</title>
</head>
<body>
This page is a text of mail quoting.
This page is a test of mail quoting.
<p>
I hope you will enjoy these quotes from <em>Hamlet</em>, introduced by a fairly long line to see how quotations get wrapped:
<p>
<blockquote type="cite">
I hope you will enjoy this quote from <em>Hamlet</em>, introduced by a fairly long line to see how quotations get wrapped:
<p>
<blockquote type="cite">
<em>(These have <b>br</b> tags after them.</em><br>
To be, or not to be, that is the question<br>
Whether 'tis nobler in the mind to suffer<br>
The slings and fortunes of outrageous fortune<br>
@ -38,5 +41,20 @@ Or to take arms against a sea of troubles<br>
And by opposing end them.<br>
</blockquote>
<p>
Oh, what a mind is here o'erthrown.<br>
<blockquote type="cite">
<em>(The next line does not end with a <b>br</b> tag.).</em><br>
Oh, what a rogue and peasant slave am I.
</blockquote>
<em>(Neither does the next line:)</em></br>
The observed of all observers, quite, quite down!
</blockquote>
<p>
Now we're outside all blockquotes.
</body>
</html>

Просмотреть файл

@ -1,9 +1,19 @@
This page is a text of mail quoting.
This page is a test of mail quoting.
> I hope you will enjoy this quote from Hamlet, introduced by a fairly long line to see how quotations get wrapped:
I hope you will enjoy these quotes from Hamlet, introduced by a fairly long line to see how quotations get wrapped:
>> (These have br tags after them.
>> To be, or not to be, that is the question
>> Whether 'tis nobler in the mind to suffer
>> The slings and fortunes of outrageous fortune
>> Or to take arms against a sea of troubles
>> And by opposing end them.
>
> To be, or not to be, that is the question
> Whether 'tis nobler in the mind to suffer
> The slings and fortunes of outrageous fortune
> Or to take arms against a sea of troubles
> And by opposing end them.
> Oh, what a mind is here o'erthrown.
>
>> (The next line does not end with a br tag.).
>> Oh, what a rogue and peasant slave am I.
> (Neither does the next line:)
> The observed of all observers, quite, quite down!
Now we're outside all blockquotes.

Просмотреть файл

@ -19,7 +19,8 @@ DEPTH=..\..\..
MAKE_OBJ_TYPE = EXE
PROGRAM = .\$(OBJDIR)\Convert.exe
PROGRAM = .\$(OBJDIR)\TestOutput.exe
BATCHSCRIPT = TestOutSinks.bat
OBJS = \
.\$(OBJDIR)\Convert.obj \
@ -38,6 +39,8 @@ TEST_FILES = \
mailquote.out \
xifstuff.xif \
xifstuff.out \
htmltable.html \
htmltable.out \
$(NULL)
LINCS= \
@ -60,6 +63,7 @@ include <$(DEPTH)\config\rules.mak>
install:: $(PROGRAM)
$(MAKE_INSTALL) $(PROGRAM) $(DIST)\bin
$(MAKE_INSTALL) $(BATCHSCRIPT) $(DIST)\bin
$(MAKE_INSTALL) $(TEST_FILES) $(DIST)/bin/OutTestData
clobber::

Просмотреть файл

@ -1,14 +1,9 @@
80 char width (for reference only):
---------|---------|---------|---------|---------|---------|---------|---------|
Here is a
link to mozilla.org.
Here is some underlined and boldenedified
text.
This is a test to make sure the output converters
pick up the moz-pre-wrap style. They don't
necessarily have to pick up the exact wrap
setting.
- This should be tested with wrapping on.
- This should be tested with wrapping off.
This is the end.
Here is a link to mozilla.org. Here is some
underlined and boldenedified text. This is a test
to make sure the output converters pick up the
moz-pre-wrap style. They don't necessarily have to
pick up the exact wrap setting. - This should be
tested with wrapping on. - This should be tested
with wrapping off. This is the end.

Просмотреть файл

@ -28,7 +28,8 @@
<h1>Simple html page</h1>
Here is a <a href="http://www.mozilla.org">link to the mozilla.org</a> page.
Here is some <u>underlined and <b>bold</b>ened</u>ified text.
Here is some <u>underlined and <b>bold</b>ened</u>ified text
plus some &lt;angle bracket entities&gt;.
<p>
Here is a line ending with a space
@ -36,4 +37,5 @@ followed by a line break.
Plaintext output should contain only one space (and no line breaks) between "space" and "followed".
</p>
</body></html>
</body>
</html>

Просмотреть файл

@ -39,4 +39,4 @@ under development. It's also a great place to not use latin.
Here is more of the comment.
-->
</p></body>
</p></body></html>

Просмотреть файл

@ -85,6 +85,7 @@ It's also a great place to not use latin.
</content>
</container><!--p-->
</container><!--body-->
</container><!--html-->
</section_body>

Просмотреть файл

@ -45,7 +45,7 @@
static NS_DEFINE_CID(kCharsetConverterManagerCID, NS_ICHARSETCONVERTERMANAGER_CID);
const int gTabSize=2;
const PRInt32 gTabSize=2;
static PRBool IsInline(eHTMLTags aTag);
static PRBool IsBlockLevel(eHTMLTags aTag);
@ -164,7 +164,7 @@ nsHTMLToTXTSinkStream::nsHTMLToTXTSinkStream()
NS_INIT_REFCNT();
mColPos = 0;
mIndent = 0;
mCiteQuote = PR_FALSE;
mCiteQuoteLevel = 0;
mDoFragment = PR_FALSE;
mBufferSize = 0;
mBufferLength = 0;
@ -172,6 +172,13 @@ nsHTMLToTXTSinkStream::nsHTMLToTXTSinkStream()
mUnicodeEncoder = nsnull;
mWrapColumn = 72; // XXX magic number, we expect someone to reset this
// Flow
mEmptyLines=1; // The start of the document is an "empty line" in itself,
mCurrentLine = "";
mInWhitespace = PR_TRUE;
mPreFormatted = PR_FALSE;
mCacheLine = PR_FALSE;
// initialize the tag stack to zero:
mTagStack = new nsHTMLTag[TagStackSize];
mTagStackIndex = 0;
@ -190,7 +197,10 @@ nsHTMLToTXTSinkStream::nsHTMLToTXTSinkStream()
*/
nsHTMLToTXTSinkStream::~nsHTMLToTXTSinkStream()
{
delete [] mBuffer;
NS_ASSERTION(mCurrentLine.Length() == 0, "Buffer don't flushed! Probably illegal input to class.");
if(mBuffer)
delete[] mBuffer;
delete[] mTagStack;
delete[] mOLStack;
NS_IF_RELEASE(mUnicodeEncoder);
@ -403,7 +413,11 @@ PRBool nsHTMLToTXTSinkStream::DoOutput()
NS_IMETHODIMP
nsHTMLToTXTSinkStream::OpenContainer(const nsIParserNode& aNode)
{
eHTMLTags type = (eHTMLTags)aNode.GetNodeType();
#ifdef DEBUG_bratell
printf("OpenContainer: %d ", type);
#endif
const nsString& name = aNode.GetText();
if (name.Equals("XIF_DOC_INFO"))
{
@ -423,8 +437,31 @@ nsHTMLToTXTSinkStream::OpenContainer(const nsIParserNode& aNode)
if (type == eHTMLTag_body)
{
// body -> can turn on cacheing unless it's already preformatted
if(!(mFlags & nsIDocumentEncoder::OutputPreformatted) &&
((mFlags & nsIDocumentEncoder::OutputFormatted) ||
(mFlags & nsIDocumentEncoder::OutputWrap))) {
mCacheLine = PR_TRUE;
}
// Would be cool to figure out here whether we have a
// preformatted style attribute. It's hard, though.
// Trigger on the presence of a "-moz-pre-wrap" in the
// style attribute. That's a very simplistic way to do
// it, but better than nothing.
nsString value;
if(NS_SUCCEEDED(GetValueOfAttribute(aNode, "style", value)) &&
(-1 != value.Find("-moz-pre-wrap"))) {
mPreFormatted = PR_TRUE;
mCacheLine = PR_TRUE;
} else {
mPreFormatted = PR_FALSE;
mCacheLine = PR_TRUE; // Cache lines unless something else tells us not to
}
return NS_OK;
}
@ -453,25 +490,27 @@ nsHTMLToTXTSinkStream::OpenContainer(const nsIParserNode& aNode)
temp = "#";
}
Write(temp);
mColPos++;
// mColPos++; This is done in Write(temp) above
}
else if (type == eHTMLTag_blockquote)
{
// Find out whether it's a type=cite, and insert "> " instead.
// Eventually we should get the value of the pref controlling citations,
// and handle AOL-style citations as well.
// If we want to support RFC 2646 (and we do!) we have to have:
// >>>> text
// >>> fdfd
// when a mail is sent.
nsString value;
if (NS_SUCCEEDED(GetValueOfAttribute(aNode, "type", value))
&& value.StripChars("\"").Equals("cite", PR_TRUE))
mCiteQuote = PR_TRUE;
mCiteQuoteLevel++;
else
mIndent += gTabSize;
mIndent += gTabSize; // Check for some maximum value?
}
else if (type == eHTMLTag_pre)
{
nsAutoString temp(NS_LINEBREAK);
Write(temp);
mColPos = 0;
EnsureVerticalSpace(0);
}
// Finally, the list of tags before which we want some vertical space:
@ -482,9 +521,7 @@ nsHTMLToTXTSinkStream::OpenContainer(const nsIParserNode& aNode)
case eHTMLTag_ol:
case eHTMLTag_p:
{
nsAutoString temp(NS_LINEBREAK);
Write(temp);
mColPos = 0;
EnsureVerticalSpace((mFlags & nsIDocumentEncoder::OutputFormatted) ? 1 : 0);
break;
}
default:
@ -506,32 +543,64 @@ NS_IMETHODIMP
nsHTMLToTXTSinkStream::CloseContainer(const nsIParserNode& aNode)
{
eHTMLTags type = (eHTMLTags)aNode.GetNodeType();
#ifdef DEBUG_bratell
printf("CloseContainer: %d ", type);
#endif
if (mTagStackIndex > 0)
--mTagStackIndex;
if (type == eHTMLTag_ol)
--mOLStackIndex;
else if (type == eHTMLTag_blockquote)
{
if (mCiteQuote)
mCiteQuote = PR_FALSE;
else
if (mCiteQuoteLevel>0)
mCiteQuoteLevel--;
else if(mIndent >= gTabSize)
mIndent -= gTabSize;
}
else if (type == eHTMLTag_td)
{
// We are after a table cell an thus maybe between two cells.
// Something should be done to avoid the two cells to be written
// together. This really need some intelligence about how the
// contents in the cell looks.
// Fow now, I will only add a SPACE. Could be a TAB or something
// else but I'm not sure everything can handle the TAB so SPACE
// seems like a better solution.
if(!mInWhitespace) {
// Maybe add something else? Several spaces? A TAB? SPACE+TAB?
if(mCacheLine) {
AddToLine(" ");
} else {
WriteSimple(" ");
}
mInWhitespace = PR_TRUE;
}
}
// End current line if we're ending a block level tag
if (IsBlockLevel(type) && type != eHTMLTag_body && type != eHTMLTag_html
&& type != eHTMLTag_comment)
{
if (mColPos != 0)
{
nsAutoString temp(NS_LINEBREAK);
Write(temp);
mColPos = 0;
if(IsBlockLevel(type)) {
if((type == eHTMLTag_body) || (type == eHTMLTag_html)) {
// We want the output to end with a new line,
// but in preformatted areas like text fields,
// we can't emit newlines that weren't there.
if (mPreFormatted || (mFlags & nsIDocumentEncoder::OutputPreformatted))
FlushLine();
else
EnsureVerticalSpace(0);
} else if((type == eHTMLTag_tr) ||
(type == eHTMLTag_blockquote)) {
EnsureVerticalSpace(0);
} else {
// All other blocks get 1 vertical space after them
// in formatted mode, otherwise 0.
// This is hard. Sometimes 0 is a better number, but
// how to know?
EnsureVerticalSpace((mFlags & nsIDocumentEncoder::OutputFormatted) ? 1 : 0);
}
}
return NS_OK;
}
@ -546,71 +615,77 @@ nsHTMLToTXTSinkStream::CloseContainer(const nsIParserNode& aNode)
NS_IMETHODIMP
nsHTMLToTXTSinkStream::AddLeaf(const nsIParserNode& aNode)
{
eHTMLTags type = (eHTMLTags)aNode.GetNodeType();
#ifdef DEBUG_bratell
printf("Addleaf: %d (%d) ", (eHTMLTags)aNode.GetNodeType(),mFlags);
#endif
// If we don't want any output, just return
if (!DoOutput())
return NS_OK;
eHTMLTags type = (eHTMLTags)aNode.GetNodeType();
nsString text = aNode.GetText();
if (!DoOutput())
return NS_OK;
#ifdef DEBUG_bratell
printf(" '%s' ", text.ToNewCString());
#endif
if (type == eHTMLTag_text)
{
#if 0
// This is too generous about putting spaces around text nodes
// even when there's no indication for a space.
if (mColPos > mIndent)
{
nsAutoString temp(" ");
Write(temp);
mColPos++;
}
#endif
Write(text);
}
else if (type == eHTMLTag_entity)
{
PRUnichar entity = nsHTMLEntities::EntityToUnicode(aNode.GetText());
nsAutoString temp;
temp.Append(entity);
Write(temp);
mColPos++;
}
else if (type == eHTMLTag_br)
{
// Do this even if we're not doing formatted output:
nsAutoString temp(NS_LINEBREAK);
Write(temp);
mColPos = 0;
EnsureVerticalSpace(mEmptyLines+1);
}
// The only times we want to pass along whitespace from the original
// html source are if we're forced into preformatted mode via flags,
// or if we're prettyprinting and we're inside a <pre>.
// Otherwise, either we're collapsing to minimal text, or we're
// prettyprinting to mimic the html format, and in neither case
// does the formatting of the html source help us.
else if (mFlags & nsIDocumentEncoder::OutputPreformatted ||
((mFlags & nsIDocumentEncoder::OutputFormatted)
&& (mTagStackIndex > 0)
&& (mTagStack[mTagStackIndex-1] == eHTMLTag_pre)))
else if (type == eHTMLTag_whitespace)
{
if (type == eHTMLTag_whitespace)
// The only times we want to pass along whitespace from the original
// html source are if we're forced into preformatted mode via flags,
// or if we're prettyprinting and we're inside a <pre>.
// Otherwise, either we're collapsing to minimal text, or we're
// prettyprinting to mimic the html format, and in neither case
// does the formatting of the html source help us.
if (mFlags & nsIDocumentEncoder::OutputPreformatted ||
((mFlags & nsIDocumentEncoder::OutputFormatted)
&& (mTagStackIndex > 0)
&& (mTagStack[mTagStackIndex-1] == eHTMLTag_pre)) ||
(mPreFormatted && !mWrapColumn))
{
text = aNode.GetText();
WriteSimple(text);
mColPos += text.Length();
mEmptyLines = -1;
} else if(!mInWhitespace) {
if(mCacheLine) {
AddToLine(" ");
} else {
WriteSimple(" ");
}
mInWhitespace = PR_TRUE;
}
}
else if (type == eHTMLTag_newline)
{
if (mFlags & nsIDocumentEncoder::OutputPreformatted ||
((mFlags & nsIDocumentEncoder::OutputFormatted)
&& (mTagStackIndex > 0)
&& (mTagStack[mTagStackIndex-1] == eHTMLTag_pre)) ||
(mPreFormatted && !mWrapColumn))
{
text = aNode.GetText();
Write(text);
mColPos += text.Length();
}
else if (type == eHTMLTag_newline)
{
nsAutoString temp(NS_LINEBREAK);
Write(temp);
mColPos = 0;
EnsureVerticalSpace(mEmptyLines+1);
}
}
return NS_OK;
}
@ -668,6 +743,25 @@ void nsHTMLToTXTSinkStream::EncodeToBuffer(const nsString& aSrc)
}
void
nsHTMLToTXTSinkStream::EnsureVerticalSpace(PRInt32 noOfRows)
{
while(mEmptyLines < noOfRows)
EndLine(PR_FALSE);
}
// This empties the current line cache without adding a NEWLINE.
// Should not be used if line wrapping is of importance since
// this function destroys the cache information.
void
nsHTMLToTXTSinkStream::FlushLine()
{
WriteSimple(mCurrentLine);
mCurrentLine.SetString("");
}
/**
* WriteSimple places the contents of aString into either the output stream
@ -711,6 +805,151 @@ void nsHTMLToTXTSinkStream::WriteSimple(const nsString& aString)
}
}
void
nsHTMLToTXTSinkStream::AddToLine(const nsString &linefragment)
{
PRUint32 prefixwidth = (mCiteQuoteLevel>0?mCiteQuoteLevel+1:0)+mIndent;
PRInt32 linelength = mCurrentLine.Length();
if(0 == linelength) {
if(0 == linefragment.Length()) {
// Nothing at all. Are you kidding me?
return;
}
if(mFlags & nsIDocumentEncoder::OutputFormatFlowed) {
if((linefragment[0] == '>') ||
(linefragment[0] == ' ') ||
(!linefragment.Compare("From ",PR_FALSE,5))) {
// Space stuffing a la RFC 2646 if this will be used in a mail,
// but how can I know that??? Now space stuffing is done always
// when formatting text as HTML and that is wrong! XXX: Fix this!
mCurrentLine.Append(' ');
}
}
mEmptyLines=-1;
}
mCurrentLine.Append(linefragment);
linelength = mCurrentLine.Length();
// Wrap?
if(mWrapColumn &&
((mFlags & nsIDocumentEncoder::OutputFormatted) ||
(mFlags & nsIDocumentEncoder::OutputWrap))) {
// Yes, wrap!
// The "+4" is to avoid wrap lines that only should be a couple
// of letters too long.
while(linelength+prefixwidth > mWrapColumn+4) {
// Must wrap. Let's find a good place to do that.
PRInt32 goodSpace = mWrapColumn-prefixwidth;
while (goodSpace >= 0 &&
!nsString::IsSpace(mCurrentLine.CharAt(goodSpace))) {
goodSpace--;
}
nsAutoString restOfLine = "";
if(goodSpace<0) {
// If we don't found a good place to break, accept long line and
// try to find another place to break
goodSpace=mWrapColumn-prefixwidth;
while (goodSpace < linelength &&
!nsString::IsSpace(mCurrentLine.CharAt(goodSpace))) {
goodSpace++;
}
}
if(goodSpace < linelength && goodSpace > 0) {
// Found a place to break
mCurrentLine.Right(restOfLine, linelength-goodSpace-1);
mCurrentLine.Cut(goodSpace, linelength-goodSpace);
EndLine(PR_TRUE);
mCurrentLine.SetString(restOfLine);
linelength = mCurrentLine.Length();
mEmptyLines = -1;
} else {
// Nothing to do. Hopefully we get more data later
// to use for a place to break line
break;
}
}
} else {
// No wrapping.
}
}
void
nsHTMLToTXTSinkStream::EndLine(PRBool softlinebreak)
{
if(softlinebreak) {
if(0 == mCurrentLine.Length()) {
// No meaning
return;
}
WriteQuotesAndIndent();
// Remove whitespace from the end of the line.
mCurrentLine.CompressWhitespace(PR_FALSE,PR_TRUE);
if(mFlags & nsIDocumentEncoder::OutputFormatFlowed) {
// Add the soft part of the soft linebreak (RFC 2646 4.1)
mCurrentLine.Append(' ');
}
mCurrentLine.Append(NS_LINEBREAK);
WriteSimple(mCurrentLine);
mCurrentLine.SetString("");
mColPos=0;
mEmptyLines=0;
mInWhitespace=PR_TRUE;
} else {
// Hard break
if(0 == mColPos) {
WriteQuotesAndIndent();
}
if(mCurrentLine.Length()>0)
mEmptyLines=-1;
// Output current line
mCurrentLine.CompressWhitespace(PR_FALSE,PR_TRUE);
mCurrentLine.Append(NS_LINEBREAK);
WriteSimple(mCurrentLine);
mCurrentLine.SetString("");
mColPos=0;
mEmptyLines++;
mInWhitespace=PR_TRUE;
}
}
void
nsHTMLToTXTSinkStream::WriteQuotesAndIndent()
{
// Put the mail quote "> " chars in, if appropriate:
if (mCiteQuoteLevel>0) {
// Check for out of memory?
char* gts = NS_STATIC_CAST(char*, nsAllocator::Alloc(mCiteQuoteLevel+2));
for(int i=0; i<mCiteQuoteLevel; i++) {
gts[i]='>';
}
gts[mCiteQuoteLevel] = ' ';
gts[mCiteQuoteLevel+1] = '\0';
nsAutoString temp(gts);
WriteSimple(temp);
mColPos += (mCiteQuoteLevel+1);
nsAllocator::Free(gts);
}
// Indent if necessary
if (mIndent > 0) {
char* spaces = NS_STATIC_CAST(char*, nsAllocator::Alloc(mIndent+1));
for (int i=0; i<mIndent; ++i)
spaces[i] = ' ';
spaces[mIndent] = '\0';
nsAutoString temp(spaces);
WriteSimple(temp);
mColPos += mIndent;
nsAllocator::Free(spaces);
}
}
#ifdef DEBUG_akkana_not
#define DEBUG_wrapping 1
#endif
@ -729,49 +968,85 @@ nsHTMLToTXTSinkStream::Write(const nsString& aString)
nsAllocator::Free(foo);
#endif
PRInt32 bol = 0;
int totLen = aString.Length();
// Put the mail quote "> " chars in, if appropriate:
if (mColPos == 0)
{
if (mCiteQuote)
{
nsAutoString temp("> ");
WriteSimple(temp);
mColPos += 2;
}
// Indent if necessary
if (mIndent > 0)
{
char* spaces = NS_STATIC_CAST(char*, nsAllocator::Alloc(mIndent+1));
for (int i=0; i<mIndent; ++i)
spaces[i] = ' ';
spaces[mIndent] = '\0';
nsAutoString temp(spaces);
WriteSimple(temp);
mColPos += mIndent;
nsAllocator::Free(spaces);
}
}
PRInt32 newline;
PRInt32 totLen = aString.Length();
// Don't wrap mail-quoted text
PRUint32 wrapcol = (mCiteQuote ? 0 : mWrapColumn);
// Yes do! /Daniel Bratell
// PRUint32 wrapcol = (mCiteQuote ? 0 : mWrapColumn);
// See if there's a newline in the string:
PRInt32 newline = aString.FindCharInSet("\n\r", bol);
if ((!(mFlags & nsIDocumentEncoder::OutputFormatted)
&& !(mFlags & nsIDocumentEncoder::OutputWrap))
|| wrapcol == 0)
// PRInt32 prefixwidth = (mCiteQuoteLevel>0?mCiteQuoteLevel+1:0)+mIndent;
// PRInt32 linewidth = mWrapColumn-prefixwidth;
// if ((!(mFlags & nsIDocumentEncoder::OutputFormatted)
// && !(mFlags & nsIDocumentEncoder::OutputWrap)) ||
// ((mTagStackIndex > 0) &&
// (mTagStack[mTagStackIndex-1] == eHTMLTag_pre)))
if (((mTagStackIndex > 0) &&
(mTagStack[mTagStackIndex-1] == eHTMLTag_pre)) ||
(mPreFormatted && !mWrapColumn))
{
WriteSimple(aString);
// No intelligent wrapping. This mustn't be mixed with
// intelligent wrapping without clearing the mCurrentLine
// buffer before!!!
NS_ASSERTION(mCurrentLine.Length() == 0, "Mixed wrapping data and nonwrapping data on the same line");
// Put the mail quote "> " chars in, if appropriate.
// Have to put it in before every line.
PRInt32 newCR, newLF;
while(bol<totLen) {
if(0 == mColPos)
WriteQuotesAndIndent();
newCR = aString.FindCharInSet("\r",bol);
newLF = aString.FindCharInSet("\n",bol);
if(newCR>=0) {
if(newLF==newCR+1) {
// Found CRLF
newline=newLF;
} else if(newLF>=0 && newLF<newCR) {
// Found single LF
newline=newLF;
} else {
// Single CR
newline=newCR;
}
} else {
newline=newLF;
}
if(newline < 0) {
// No new lines.
nsAutoString stringpart;
aString.Right(stringpart, totLen-bol);
if(stringpart.Length()>0) {
PRUnichar lastchar = stringpart[stringpart.Length()-1];
if((lastchar == '\t') || (lastchar == ' ') ||
(lastchar == '\r') ||(lastchar == '\n')) {
mInWhitespace = PR_TRUE;
} else {
mInWhitespace = PR_FALSE;
}
}
WriteSimple(stringpart);
mEmptyLines=-1;
mColPos += totLen-bol;
bol = totLen;
} else {
nsAutoString stringpart;
aString.Mid(stringpart, bol, newline-bol+1);
mInWhitespace = PR_TRUE;
WriteSimple(stringpart);
mEmptyLines=0;
mColPos=0;
bol = newline+1;
}
}
// Simple attempt to be smart about col pos:
if (newline >= 0)
mColPos = totLen - newline - 1;
else
mColPos += totLen;
#ifdef DEBUG_wrapping
printf("No wrapping: newline is %d, totLen is %d; leaving mColPos = %d\n",
newline, totLen, mColPos);
@ -779,109 +1054,63 @@ nsHTMLToTXTSinkStream::Write(const nsString& aString)
return;
}
while (bol < totLen) // Loop over lines
{
// Intelligent handling of text
// Strip out all "end of lines" and multiple whitespace between words
PRInt32 nextpos;
nsAutoString tempstr;
while (bol < totLen) { // Loop over lines
nextpos = aString.FindCharInSet(" \t\n\r", bol);
#ifdef DEBUG_wrapping
nsString remaining;
aString.Right(remaining, totLen - bol);
foo = remaining.ToNewCString();
printf("Next line: bol = %d, newline = %d, totLen = %d, string = '%s'\n",
bol, newline, totLen, foo);
// printf("Next line: bol = %d, newlinepos = %d, totLen = %d, string = '%s'\n",
// bol, nextpos, totLen, foo);
nsAllocator::Free(foo);
#endif
// Set eol to the end of the string or the first newline,
// whichever comes first:
int eol = bol + wrapcol - mColPos;
if (eol > totLen || wrapcol == 0)
eol = bol + totLen;
else if (newline > 0 && eol > newline)
eol = newline;
// else we have to wrap
else
{
// search backward to find last IsSpace char:
int lastSpace = eol;
while (lastSpace > bol && !nsString::IsSpace(aString[lastSpace]))
{
#ifdef DEBUG_wrapping
aString.Right(remaining, totLen - bol);
foo = remaining.ToNewCString();
printf("Searching backward: bol = %d, string = '%s'\n", bol, foo);
nsAllocator::Free(foo);
#endif
--lastSpace;
if(nextpos < 0) {
// The rest of the string
aString.Right(tempstr, totLen-bol);
if(!mCacheLine) {
WriteSimple(tempstr);
} else {
AddToLine(tempstr);
}
if (lastSpace > bol)
{
// We found a space, so set eol to just before that
eol = lastSpace - 1;
bol=totLen;
mInWhitespace=PR_FALSE;
} else {
if(mInWhitespace && (nextpos == bol)) {
// Skip whitespace
bol++;
continue;
}
else
{
#ifdef NOTSURE
// If we reached the bol, it might just be because we were close
// to the end already and should have wrapped last time.
// In that case, write a linebreak and come around again.
// I don't remember what this comment means, so skip it for now.
if (mColPos > mIndent)
{
nsAutoString linebreak(NS_LINEBREAK);
WriteSimple(linebreak);
mColPos = 0;
continue;
}
#endif /* NOTSURE */
// Else apparently we really can't break this line at whitespace --
// so scan forward to the next space or newline, and dump a long line.
lastSpace = eol;
while (eol < totLen && !nsString::IsSpace(aString[eol])
&& (newline < 0 || eol < newline))
{
#ifdef DEBUG_wrapping
aString.Mid(remaining, bol, lastSpace - bol);
foo = remaining.ToNewCString();
printf("Searching foreward: '%c' is not a space\n line = '%s'\n",
(char)aString[lastSpace], foo);
nsAllocator::Free(foo);
#endif
++eol;
if(nextpos == bol) {
// Note that we are in whitespace.
mInWhitespace = PR_TRUE;
if(!mCacheLine) {
WriteSimple(" ");
} else {
AddToLine(" ");
}
bol++;
continue;
}
aString.Mid(tempstr,bol,nextpos-bol);
tempstr.Append(" ");
if(!mCacheLine) {
WriteSimple(tempstr);
} else {
AddToLine(tempstr);
}
mInWhitespace = PR_TRUE;
bol = nextpos + 1;
}
// At this point, bol and eol should represent the line
// we really want to dump; lastSpace isn't necessarily set.
nsAutoString lineStr;
aString.Mid(lineStr, bol, eol-bol+1);
if (eol == newline)
mColPos = 0;
else if (eol != totLen) // we're wrapping
{
lineStr.Append(NS_LINEBREAK);
mColPos = 0;
// If we broke at a space, skip that space:
// (but not necessarily any spaces that might follow it,
// see bug 12984)
if (eol < totLen && nsString::IsSpace(aString[eol+1]))
++eol;
}
else // Not wrapping and not writing a newline
mColPos += lineStr.Length();
WriteSimple(lineStr);
#ifdef DEBUG_wrapping
foo = lineStr.ToNewCString();
printf("Calling WriteSimple(%s), leaving mColPos = %d\n", foo, mColPos);
nsAllocator::Free(foo);
#endif
// Reset bol and newline:
bol = eol+1;
newline = aString.FindCharInSet("\n\r", bol);
} // Continue looping over the string
}

Просмотреть файл

@ -137,6 +137,11 @@ protected:
nsresult InitEncoder(const nsString& aCharset);
void AddToLine(const nsString& linefragment);
void EndLine(PRBool softlinebreak);
void EnsureVerticalSpace(PRInt32 noOfRows);
void FlushLine();
void WriteQuotesAndIndent();
void WriteSimple(const nsString& aString);
void Write(const nsString& aString);
void EncodeToBuffer(const nsString& aString);
@ -148,14 +153,23 @@ protected:
protected:
nsIOutputStream* mStream;
nsString* mString;
nsString mCurrentLine;
PRInt32 mIndent;
PRBool mCiteQuote;
PRInt32 mCiteQuoteLevel;
PRInt32 mColPos;
PRInt32 mFlags;
PRUint32 mWrapColumn;
PRBool mDoFragment;
// For format=flowed
PRInt32 mEmptyLines; // Will be the number of empty lines before
// the current. 0 if we are starting a new
// line and -1 if we are in a line.
PRBool mInWhitespace;
PRBool mPreFormatted;
PRBool mCacheLine; // If the line should be cached before output. This makes it possible to do smarter wrapping.
// The tag stack: the stack of tags we're operating on, so we can nest:
nsHTMLTag *mTagStack;
PRUint32 mTagStackIndex;

Просмотреть файл

@ -176,7 +176,7 @@ int main(int argc, char** argv)
Usage: %s [-i intype] [-o outtype] [-f flags] [-w wrapcol] [-c comparison_file] infile\n\
\tIn/out types are mime types (e.g. text/html)\n\
\tcomparison_file is a file against which to compare the output\n\
\t (not yet implemented\n\
\n\
\tDefaults are -i text/html -o text/plain -f 0 -w 72 [stdin]\n",
progname);
exit(0);

Просмотреть файл

@ -45,6 +45,8 @@ TEST_FILES = \
entityxif.out \
mailquote.html \
mailquote.out \
htmltable.html \
htmltable.out \
xifstuff.xif \
xifstuff.out \
$(NULL)

Просмотреть файл

@ -65,15 +65,22 @@ endif
echo "Testing conversion of XIF entities ..."
TestOutput -i text/xif -o text/plain -c OutTestData/entityxif.out OutTestData/entityxif.xif
if ($status != 0) then
echo "XIF entity convertsion test failed."
echo "XIF entity conversion test failed."
set errmsg = ($errmsg "entityxif.out")
endif
echo "Testing XIF to HTML ..."
TestOutput -i text/xif -o text/html -c OutTestData/xifstuff.out OutTestData/xifstuff.xif
if ($status != 0) then
echo "XIF entity convertsion test failed."
set errmsg = ($errmsg "entityxif.out")
echo "XIF to HTML conversion test failed."
set errmsg = ($errmsg "xifstuff.out")
endif
echo "Testing HTML Table to Text ..."
TestOutput -i text/html -o text/plain -c OutTestData/htmltable.out OutTestData/htmltable.html
if ($status != 0) then
echo "HTML Table to Plain text failed."
set errmsg = ($errmsg "htmltable.out")
endif
if (errmsg != "") then

Просмотреть файл

@ -0,0 +1,61 @@
@echo off
REM The contents of this file are subject to the Netscape Public
REM License Version 1.1 (the "License"); you may not use this file
REM except in compliance with the License. You may obtain a copy of
REM the License at http://www.mozilla.org/NPL/
REM
REM Software distributed under the License is distributed on an "AS
REM IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
REM implied. See the License for the specific language governing
REM rights and limitations under the License.
REM
REM The Original Code is Mozilla Communicator client code, released
REM March 31, 1998.
REM
REM The Initial Developer of the Original Code is Netscape
REM Communications Corporation. Portions created by Netscape are
REM Copyright (C) 1998-1999 Netscape Communications Corporation. All
REM Rights Reserved.
REM
REM Contributor(s): Akkana Peck, Daniel Bratell.
REM
REM This is a collection of test files to guard against regressions
REM in the Gecko output system.
REM
set errmsg=
echo Testing simple html to html ...
TestOutput -i text/html -o text/html -f 0 -c OutTestData/simple.html OutTestData/simple.html
IF ERRORLEVEL 1 echo Simple html to html failed (%errorlevel%). && set errmsg=%errmsg% simple.html
echo Testing simple copy case ...
TestOutput -i text/html -o text/plain -f 0 -w 0 -c OutTestData/simplecopy.out OutTestData/simple.html
IF ERRORLEVEL 1 echo Simple copy test failed. && set errmsg=%errmsg% simplecopy.out
echo Testing non-wrapped plaintext ...
TestOutput -i text/html -o text/plain -f 0 -w 0 -c OutTestData/plainnowrap.out OutTestData/plain.html
IF ERRORLEVEL 1 echo Non-wrapped plaintext test failed. && set errmsg=%errmsg% plainnowrap.out
echo Testing wrapped bug unformatted plaintext ...
TestOutput -i text/html -o text/plain -f 32 -w 50 -c OutTestData/plainwrap.out OutTestData/plain.html
IF ERRORLEVEL 1 echo Wrapped plaintext test failed. && set errmsg=%errmsg% plainwrap.out
echo Testing mail quoting ...
TestOutput -i text/html -o text/plain -c OutTestData/mailquote.out OutTestData/mailquote.html
IF ERRORLEVEL 1 echo Mail quoting test failed. && set errmsg=%errmsg% mailquote.out
echo Testing conversion of XIF entities ...
TestOutput -i text/xif -o text/plain -c OutTestData/entityxif.out OutTestData/entityxif.xif
IF ERRORLEVEL 1 echo XIF entity conversion test failed. && set errmsg=%errmsg% entityxif.out
echo Testing XIF to HTML ...
TestOutput -i text/xif -o text/html -c OutTestData/xifstuff.out OutTestData/xifstuff.xif
IF ERRORLEVEL 1 echo XIF to HTML conversion test failed. && set errmsg=%errmsg% xifstuff.out
echo Testing HTML Table to Text ...
TestOutput -i text/html -o text/plain -c OutTestData/htmltable.out OutTestData/htmltable.html
IF ERRORLEVEL 1 echo HTML Table to Plain text failed (%errorlevel%). && set errmsg=%errmsg% htmltable.out
IF DEFINED %errmsg% echo && echo TESTS FAILED: %errmsg% && exit 1

Просмотреть файл

@ -0,0 +1,16 @@
<html>
<head>
<title>HTML To Text Test Page</title>
</head>
<body>
Below is a table.<br>
<table>
<tr><td>Row 1 Col 1<td>Row 1 Col 2</td><td>Row 1 Col 3</tr>
<tr><td>Row 2 Col 1 <td>Row 2 Col 2</td><td> Row 2 Col 3</tr>
<tr><td>Row 3 Col 1</td><td>Row 3 Col 2</td><td>Row 3 Col 3</tr>
</table>
Here is after table.
</body>
</html>

Просмотреть файл

@ -0,0 +1,7 @@
Below is a table.
Row 1 Col 1 Row 1 Col 2 Row 1 Col 3
Row 2 Col 1 Row 2 Col 2 Row 2 Col 3
Row 3 Col 1 Row 3 Col 2 Row 3 Col 3
Here is after table.

Просмотреть файл

@ -20,17 +20,20 @@
--
-- Contributor(s):
-->
<title>Ender Plain Text Test Page</title>
<title>Mail Quoting Test</title>
</head>
<body>
This page is a text of mail quoting.
This page is a test of mail quoting.
<p>
I hope you will enjoy these quotes from <em>Hamlet</em>, introduced by a fairly long line to see how quotations get wrapped:
<p>
<blockquote type="cite">
I hope you will enjoy this quote from <em>Hamlet</em>, introduced by a fairly long line to see how quotations get wrapped:
<p>
<blockquote type="cite">
<em>(These have <b>br</b> tags after them.</em><br>
To be, or not to be, that is the question<br>
Whether 'tis nobler in the mind to suffer<br>
The slings and fortunes of outrageous fortune<br>
@ -38,5 +41,20 @@ Or to take arms against a sea of troubles<br>
And by opposing end them.<br>
</blockquote>
<p>
Oh, what a mind is here o'erthrown.<br>
<blockquote type="cite">
<em>(The next line does not end with a <b>br</b> tag.).</em><br>
Oh, what a rogue and peasant slave am I.
</blockquote>
<em>(Neither does the next line:)</em></br>
The observed of all observers, quite, quite down!
</blockquote>
<p>
Now we're outside all blockquotes.
</body>
</html>

Просмотреть файл

@ -1,9 +1,19 @@
This page is a text of mail quoting.
This page is a test of mail quoting.
> I hope you will enjoy this quote from Hamlet, introduced by a fairly long line to see how quotations get wrapped:
I hope you will enjoy these quotes from Hamlet, introduced by a fairly long line to see how quotations get wrapped:
>> (These have br tags after them.
>> To be, or not to be, that is the question
>> Whether 'tis nobler in the mind to suffer
>> The slings and fortunes of outrageous fortune
>> Or to take arms against a sea of troubles
>> And by opposing end them.
>
> To be, or not to be, that is the question
> Whether 'tis nobler in the mind to suffer
> The slings and fortunes of outrageous fortune
> Or to take arms against a sea of troubles
> And by opposing end them.
> Oh, what a mind is here o'erthrown.
>
>> (The next line does not end with a br tag.).
>> Oh, what a rogue and peasant slave am I.
> (Neither does the next line:)
> The observed of all observers, quite, quite down!
Now we're outside all blockquotes.

Просмотреть файл

@ -19,7 +19,8 @@ DEPTH=..\..\..
MAKE_OBJ_TYPE = EXE
PROGRAM = .\$(OBJDIR)\Convert.exe
PROGRAM = .\$(OBJDIR)\TestOutput.exe
BATCHSCRIPT = TestOutSinks.bat
OBJS = \
.\$(OBJDIR)\Convert.obj \
@ -38,6 +39,8 @@ TEST_FILES = \
mailquote.out \
xifstuff.xif \
xifstuff.out \
htmltable.html \
htmltable.out \
$(NULL)
LINCS= \
@ -60,6 +63,7 @@ include <$(DEPTH)\config\rules.mak>
install:: $(PROGRAM)
$(MAKE_INSTALL) $(PROGRAM) $(DIST)\bin
$(MAKE_INSTALL) $(BATCHSCRIPT) $(DIST)\bin
$(MAKE_INSTALL) $(TEST_FILES) $(DIST)/bin/OutTestData
clobber::

Просмотреть файл

@ -1,14 +1,9 @@
80 char width (for reference only):
---------|---------|---------|---------|---------|---------|---------|---------|
Here is a
link to mozilla.org.
Here is some underlined and boldenedified
text.
This is a test to make sure the output converters
pick up the moz-pre-wrap style. They don't
necessarily have to pick up the exact wrap
setting.
- This should be tested with wrapping on.
- This should be tested with wrapping off.
This is the end.
Here is a link to mozilla.org. Here is some
underlined and boldenedified text. This is a test
to make sure the output converters pick up the
moz-pre-wrap style. They don't necessarily have to
pick up the exact wrap setting. - This should be
tested with wrapping on. - This should be tested
with wrapping off. This is the end.

Просмотреть файл

@ -28,7 +28,8 @@
<h1>Simple html page</h1>
Here is a <a href="http://www.mozilla.org">link to the mozilla.org</a> page.
Here is some <u>underlined and <b>bold</b>ened</u>ified text.
Here is some <u>underlined and <b>bold</b>ened</u>ified text
plus some &lt;angle bracket entities&gt;.
<p>
Here is a line ending with a space
@ -36,4 +37,5 @@ followed by a line break.
Plaintext output should contain only one space (and no line breaks) between "space" and "followed".
</p>
</body></html>
</body>
</html>

Просмотреть файл

@ -39,4 +39,4 @@ under development. It's also a great place to not use latin.
Here is more of the comment.
-->
</p></body>
</p></body></html>

Просмотреть файл

@ -85,6 +85,7 @@ It's also a great place to not use latin.
</content>
</container><!--p-->
</container><!--body-->
</container><!--html-->
</section_body>