You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
655 lines
20 KiB
655 lines
20 KiB
/*
|
|
This file is part of the KDE project
|
|
Copyright (C) 2001, 2002, 2004 Nicolas GOUTTE <goutte@kde.org>
|
|
|
|
This library is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU Library General Public
|
|
License as published by the Free Software Foundation; either
|
|
version 2 of the License, or (at your option) any later version.
|
|
|
|
This library is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
Library General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Library General Public License
|
|
along with this library; see the file COPYING.LIB. If not, write to
|
|
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
|
* Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#include <tqstring.h>
|
|
#include <tqtextcodec.h>
|
|
#include <tqfile.h>
|
|
#include <tqfileinfo.h>
|
|
#include <tqdir.h>
|
|
#include <tqpicture.h>
|
|
|
|
#include <tdelocale.h>
|
|
#include <kdebug.h>
|
|
|
|
#include <KWEFUtil.h>
|
|
#include <KWEFBaseWorker.h>
|
|
|
|
#include "ExportFilter.h"
|
|
|
|
TQString HtmlWorker::escapeHtmlText(const TQString& strText) const
|
|
{
|
|
// Escape quotes (needed in attributes)
|
|
// Do not escape apostrophs (only allowed in XHTML!)
|
|
return KWEFUtil::EscapeSgmlText(getCodec(),strText,true,false);
|
|
}
|
|
|
|
bool HtmlWorker::makeTable(const FrameAnchor& anchor)
|
|
{
|
|
*m_streamOut << "<table>\n";
|
|
*m_streamOut << "<tbody>\n";
|
|
|
|
TQValueList<TableCell>::ConstIterator itCell;
|
|
|
|
int rowCurrent=0;
|
|
*m_streamOut << "<tr>\n";
|
|
|
|
|
|
for (itCell=anchor.table.cellList.begin();
|
|
itCell!=anchor.table.cellList.end(); itCell++)
|
|
{
|
|
if (rowCurrent!=(*itCell).row)
|
|
{
|
|
rowCurrent=(*itCell).row;
|
|
*m_streamOut << "</tr>\n<tr>\n";
|
|
}
|
|
|
|
*m_streamOut << "<td";
|
|
if ( (*itCell).m_rows > 1 )
|
|
*m_streamOut << " rowspan=\"" << (*itCell).m_rows << "\"";
|
|
if ( (*itCell).m_cols > 1 )
|
|
*m_streamOut << " colspan=\"" << (*itCell).m_cols << "\"";
|
|
*m_streamOut << ">\n";
|
|
|
|
if (!doFullAllParagraphs(*(*itCell).paraList))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
*m_streamOut << "</td>\n";
|
|
}
|
|
|
|
*m_streamOut << "</tr>\n";
|
|
*m_streamOut << "</tbody>\n";
|
|
*m_streamOut << "</table>\n";
|
|
|
|
return true;
|
|
}
|
|
|
|
TQString HtmlWorker::getAdditionalFileName(const TQString& additionalName)
|
|
{
|
|
kdDebug(30503) << "HtmlWorker::getAdditionalFileName " << additionalName << endl;
|
|
|
|
TQDir dir(m_strFileDir);
|
|
kdDebug(30503) << "Base directory: " << m_strFileDir << endl;
|
|
|
|
if (!dir.exists(m_strSubDirectoryName))
|
|
{
|
|
// Make the directory, as it does not exist yet!
|
|
kdDebug(30503) << "Creating directory: " << m_strSubDirectoryName << endl;
|
|
dir.mkdir(m_strSubDirectoryName);
|
|
}
|
|
|
|
TQString strFileName(m_strSubDirectoryName);
|
|
strFileName+="/";
|
|
const int result=additionalName.findRev("/");
|
|
if (result>=0)
|
|
{
|
|
strFileName+=additionalName.mid(result+1);
|
|
}
|
|
else
|
|
{
|
|
strFileName+=additionalName;
|
|
}
|
|
|
|
// Now, we have to create a backup file.
|
|
|
|
TQString strBackupName(strFileName);
|
|
strBackupName+="~";
|
|
kdDebug(30503) << "Remove backup file: " << strBackupName << endl;
|
|
// We need to remove the backup file, as not all filesystems or ports can do it themselves on a rename.
|
|
dir.remove(strBackupName);
|
|
kdDebug(30503) << "Moving file: " << additionalName << " => " << strBackupName << endl;
|
|
dir.rename(strFileName,strBackupName);
|
|
|
|
return strFileName;
|
|
}
|
|
|
|
bool HtmlWorker::makeImage(const FrameAnchor& anchor)
|
|
{
|
|
const TQString strImageName(getAdditionalFileName(anchor.picture.koStoreName));
|
|
|
|
TQString strImagePath(m_strFileDir);
|
|
strImagePath+='/';
|
|
strImagePath+=strImageName;
|
|
|
|
TQByteArray image;
|
|
|
|
kdDebug(30503) << "Image " << anchor.picture.koStoreName << " will be written in " << strImageName << endl;
|
|
|
|
if (loadSubFile(anchor.picture.koStoreName,image))
|
|
{
|
|
bool writePicture = false;
|
|
|
|
const double height = anchor.frame.bottom - anchor.frame.top;
|
|
const double width = anchor.frame.right - anchor.frame.left;
|
|
|
|
const int pos = anchor.picture.koStoreName.findRev( '.' );
|
|
TQString extension;
|
|
if ( pos > -1 )
|
|
extension = anchor.picture.koStoreName.mid( pos+1 ).lower();
|
|
|
|
if ( extension == "png" || extension == "jpeg" || extension == "jpg" || extension == "gif"
|
|
|| extension == "bmp" ) // A few file types known by all HTML user agents
|
|
{
|
|
*m_streamOut << "<img "; // This is an empty element!
|
|
*m_streamOut << "src=\"" << escapeHtmlText(strImageName) << "\" ";
|
|
*m_streamOut << "alt=\"" << escapeHtmlText(anchor.picture.key.filename()) << "\"";
|
|
*m_streamOut << (isXML()?"/>":">");
|
|
writePicture = true;
|
|
}
|
|
else if ( extension == "svg" )
|
|
{
|
|
// Save picture as SVG
|
|
*m_streamOut << "<object data=\"" << escapeHtmlText(strImageName) << "\"";
|
|
*m_streamOut << " type=\"image/svg+xml\"";
|
|
*m_streamOut << " height=\"" << height << "\" width=\"" << width << "\">\n";
|
|
*m_streamOut << "</object>"; // <object> is *not* an empty element in HTML!
|
|
writePicture = true;
|
|
}
|
|
else if ( extension == "qpic" )
|
|
{
|
|
|
|
TQPicture picture;
|
|
|
|
TQIODevice* io=getSubFileDevice(anchor.picture.koStoreName);
|
|
if (!io)
|
|
{
|
|
// NO message error, as there must be already one
|
|
return false;
|
|
}
|
|
|
|
// TODO: if we have alreasy SVG, do *not* go through TQPicture!
|
|
if (picture.load(io))
|
|
{
|
|
|
|
// Save picture as SVG
|
|
*m_streamOut << "<object data=\"" << escapeHtmlText(strImageName) << "\"";
|
|
*m_streamOut << " type=\"image/svg+xml\"";
|
|
*m_streamOut << " height=\"" << height << "\" width=\"" << width << "\">\n";
|
|
*m_streamOut << "</object>"; // <object> is *not* an empty element in HTML!
|
|
// TODO: other props for image
|
|
|
|
kdDebug(30506) << "Trying to save clipart to " << strImageName << endl;
|
|
if (!picture.save(strImagePath,"svg"))
|
|
{
|
|
kdError(30506) << "Could not save clipart: " << anchor.picture.koStoreName
|
|
<< " to " << strImageName << endl;
|
|
return false;
|
|
}
|
|
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// ### TODO: avoid loading the picture 2 times
|
|
image.resize( 0 );
|
|
if ( ! loadAndConvertToImage( anchor.picture.koStoreName, extension, "PNG", image ) )
|
|
{
|
|
kdWarning(30503) << "Could not convert picture to PNG!" << endl;
|
|
return false;
|
|
}
|
|
*m_streamOut << "<img "; // This is an empty element!
|
|
*m_streamOut << "src=\"" << escapeHtmlText(strImageName) << "\" ";
|
|
*m_streamOut << "alt=\"" << escapeHtmlText(anchor.picture.key.filename()) << "\"";
|
|
*m_streamOut << (isXML()?"/>":">");
|
|
writePicture = true;
|
|
}
|
|
|
|
// Do we still need to write the original picture?
|
|
if ( writePicture )
|
|
{
|
|
TQFile file(strImagePath);
|
|
|
|
if ( !file.open (IO_WriteOnly) )
|
|
{
|
|
kdError(30503) << "Unable to open image output file!" << endl;
|
|
return false;
|
|
}
|
|
|
|
file.writeBlock(image);
|
|
file.close();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
kdWarning(30503) << "Unable to load picture " << anchor.picture.koStoreName << endl;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void HtmlWorker::formatTextParagraph(const TQString& strText,
|
|
const FormatData& formatOrigin, const FormatData& format)
|
|
{
|
|
TQString strEscaped(escapeHtmlText(strText));
|
|
|
|
// Replace line feeds by line breaks
|
|
int pos;
|
|
TQString strBr(isXML()?TQString("<br />"):TQString("<br>"));
|
|
while ((pos=strEscaped.find(TQChar(10)))>-1)
|
|
{
|
|
strEscaped.replace(pos,1,strBr);
|
|
}
|
|
|
|
if (!format.text.missing)
|
|
{
|
|
// Opening elements
|
|
openSpan(formatOrigin,format);
|
|
}
|
|
|
|
// TODO: first and last characters of partialText should not be a space (white space problems!)
|
|
// TODO: replace multiples spaces by non-breaking spaces!
|
|
|
|
if (strText==" ")
|
|
{//Just a space as text. Therefore we must use a non-breaking space.
|
|
*m_streamOut << " ";
|
|
// TODO/FIXME: only needed for <p> </p>, but not for </span> <span>
|
|
}
|
|
else
|
|
{
|
|
*m_streamOut << strEscaped;
|
|
}
|
|
|
|
if (!format.text.missing)
|
|
{
|
|
// Closing elements
|
|
closeSpan(formatOrigin,format);
|
|
}
|
|
}
|
|
|
|
void HtmlWorker::ProcessParagraphData (const TQString& strTag, const TQString ¶Text,
|
|
const LayoutData& layout, const ValueListFormatData ¶FormatDataList)
|
|
{
|
|
if (paraText.isEmpty() && paraFormatDataList.first().id != 6)
|
|
{
|
|
openParagraph(strTag,layout);
|
|
*m_streamOut << " " ; // A paragraph can never be empty in HTML
|
|
closeParagraph(strTag,layout);
|
|
}
|
|
else
|
|
{
|
|
bool paragraphNotOpened=true;
|
|
|
|
ValueListFormatData::ConstIterator paraFormatDataIt;
|
|
|
|
TQString partialText;
|
|
|
|
for ( paraFormatDataIt = paraFormatDataList.begin ();
|
|
paraFormatDataIt != paraFormatDataList.end ();
|
|
paraFormatDataIt++ )
|
|
{
|
|
if (1==(*paraFormatDataIt).id)
|
|
{
|
|
//Retrieve text
|
|
partialText=paraText.mid ( (*paraFormatDataIt).pos, (*paraFormatDataIt).len );
|
|
// For normal text, we need an opened paragraph
|
|
if (paragraphNotOpened)
|
|
{
|
|
openParagraph(strTag,layout,partialText.ref(0).direction());
|
|
paragraphNotOpened=false;
|
|
}
|
|
formatTextParagraph(partialText,layout.formatData,*paraFormatDataIt);
|
|
}
|
|
else if (4==(*paraFormatDataIt).id)
|
|
{
|
|
// For variables, we need an opened paragraph
|
|
if (paragraphNotOpened)
|
|
{
|
|
openParagraph(strTag,layout);
|
|
paragraphNotOpened=false;
|
|
}
|
|
if (9==(*paraFormatDataIt).variable.m_type)
|
|
{
|
|
// A link
|
|
*m_streamOut << "<a href=\""
|
|
<< escapeHtmlText((*paraFormatDataIt).variable.getHrefName())
|
|
<< "\">"
|
|
<< escapeHtmlText((*paraFormatDataIt).variable.getLinkName())
|
|
<< "</a>";
|
|
}
|
|
else
|
|
{
|
|
// Generic variable
|
|
*m_streamOut << escapeHtmlText((*paraFormatDataIt).variable.m_text);
|
|
}
|
|
}
|
|
else if (6==(*paraFormatDataIt).id)
|
|
{
|
|
// We have an image, a clipart or a table
|
|
|
|
if (6==(*paraFormatDataIt).frameAnchor.type)
|
|
{
|
|
// We have a table
|
|
// But first, we must sure that the paragraph is not opened.
|
|
if (!paragraphNotOpened)
|
|
{
|
|
// The paragraph was opened, so close it.
|
|
closeParagraph(strTag,layout);
|
|
}
|
|
makeTable((*paraFormatDataIt).frameAnchor);
|
|
// The paragraph will need to be opened again
|
|
paragraphNotOpened=true;
|
|
|
|
}
|
|
|
|
else if ( ( 2 == (*paraFormatDataIt).frameAnchor.type )
|
|
|| ( 5 == (*paraFormatDataIt).frameAnchor.type ) )
|
|
{
|
|
// <img> and <object> need to be in a paragraph
|
|
if (paragraphNotOpened)
|
|
{
|
|
openParagraph( strTag, layout,partialText.ref(0). direction() );
|
|
paragraphNotOpened=false;
|
|
}
|
|
makeImage((*paraFormatDataIt).frameAnchor);
|
|
}
|
|
else
|
|
{
|
|
kdWarning(30503) << "Unknown anchor type: "
|
|
<< (*paraFormatDataIt).frameAnchor.type << endl;
|
|
}
|
|
}
|
|
}
|
|
if (!paragraphNotOpened)
|
|
{
|
|
// The paragraph was opened, so close it.
|
|
closeParagraph(strTag,layout);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool HtmlWorker::doFullParagraph(const TQString& paraText,
|
|
const LayoutData& layout, const ValueListFormatData& paraFormatDataList)
|
|
{
|
|
kdDebug(30503) << "Entering HtmlWorker::doFullParagraph" << endl << paraText << endl;
|
|
TQString strParaText=paraText;
|
|
TQString strTag; // Tag that will be written.
|
|
|
|
if ( layout.counter.numbering == CounterData::NUM_LIST )
|
|
{
|
|
const uint layoutDepth=layout.counter.depth+1; // Word's depth starts at 0!
|
|
const uint listDepth=m_listStack.size();
|
|
// We are in a list, but has it the right depth?
|
|
if (layoutDepth>listDepth)
|
|
{
|
|
ListInfo newList;
|
|
newList.m_typeList=layout.counter.style;
|
|
for (uint i=listDepth; i<layoutDepth; i++)
|
|
{
|
|
*m_streamOut << getStartOfListOpeningTag(layout.counter.style,newList.m_orderedList);
|
|
m_listStack.push(newList);
|
|
}
|
|
}
|
|
else if (layoutDepth<listDepth)
|
|
{
|
|
for (uint i=listDepth; i>layoutDepth; i--)
|
|
{
|
|
ListInfo oldList=m_listStack.pop();
|
|
if (oldList.m_orderedList)
|
|
{
|
|
*m_streamOut << "</ol>\n";
|
|
}
|
|
else
|
|
{
|
|
*m_streamOut << "</ul>\n";
|
|
}
|
|
}
|
|
}
|
|
|
|
// We have a list but does it have the right type?
|
|
if ( layout.counter.style!=m_listStack.top().m_typeList)
|
|
{
|
|
// No, then close the previous list
|
|
ListInfo oldList=m_listStack.pop();
|
|
if (oldList.m_orderedList)
|
|
{
|
|
*m_streamOut << "</ol>\n";
|
|
}
|
|
else
|
|
{
|
|
*m_streamOut << "</ul>\n";
|
|
}
|
|
ListInfo newList;
|
|
*m_streamOut << getStartOfListOpeningTag(layout.counter.style,newList.m_orderedList);
|
|
newList.m_typeList=layout.counter.style;
|
|
m_listStack.push(newList);
|
|
}
|
|
|
|
// TODO: with Cascaded Style Sheet, we could add the exact counter type that we want
|
|
strTag="li";
|
|
}
|
|
else
|
|
{
|
|
// Close all open lists first
|
|
if (!m_listStack.isEmpty())
|
|
{
|
|
for (uint i=m_listStack.size(); i>0; i--)
|
|
{
|
|
ListInfo oldList=m_listStack.pop();
|
|
if (oldList.m_orderedList)
|
|
{
|
|
*m_streamOut << "</ol>\n";
|
|
}
|
|
else
|
|
{
|
|
*m_streamOut << "</ul>\n";
|
|
}
|
|
}
|
|
}
|
|
if ( (layout.counter.numbering == CounterData::NUM_CHAPTER)
|
|
&& (layout.counter.depth<6) )
|
|
{
|
|
strTag=TQString("h%1").arg(layout.counter.depth + 1); // H1 ... H6
|
|
}
|
|
else
|
|
{
|
|
strTag="p";
|
|
}
|
|
}
|
|
|
|
ProcessParagraphData(strTag, strParaText, layout, paraFormatDataList);
|
|
|
|
kdDebug(30503) << "Quiting HtmlWorker::doFullParagraph" << endl;
|
|
return true;
|
|
}
|
|
|
|
bool HtmlWorker::doOpenFile(const TQString& filenameOut, const TQString& /*to*/)
|
|
{
|
|
m_ioDevice=TQT_TQIODEVICE(new TQFile(filenameOut));
|
|
|
|
if (!m_ioDevice)
|
|
{
|
|
kdError(30503) << "No output file! Aborting!" << endl;
|
|
return false;
|
|
}
|
|
|
|
if ( !m_ioDevice->open (IO_WriteOnly) )
|
|
{
|
|
kdError(30503) << "Unable to open output file!" << endl;
|
|
return false;
|
|
}
|
|
|
|
m_streamOut=new TQTextStream(m_ioDevice);
|
|
|
|
if (!getCodec())
|
|
{
|
|
kdError(30503) << "Could not create TQTextCodec! Aborting" << endl;
|
|
return false;
|
|
}
|
|
|
|
kdDebug(30503) << "Charset used: " << getCodec()->name() << endl;
|
|
|
|
m_streamOut->setCodec( getCodec() );
|
|
|
|
m_fileName=filenameOut;
|
|
TQFileInfo base(m_fileName);
|
|
m_strFileDir=base.dirPath();
|
|
m_strTitle=base.fileName();
|
|
m_strSubDirectoryName=base.fileName();
|
|
m_strSubDirectoryName+=".dir";
|
|
|
|
return true;
|
|
}
|
|
|
|
bool HtmlWorker::doCloseFile(void)
|
|
{
|
|
kdDebug(30503) << __FILE__ << ":" << __LINE__ << endl;
|
|
delete m_streamOut;
|
|
m_streamOut=NULL;
|
|
if (m_ioDevice)
|
|
m_ioDevice->close();
|
|
return true;
|
|
}
|
|
|
|
void HtmlWorker::writeDocType(void)
|
|
{
|
|
// write <!DOCTYPE
|
|
*m_streamOut << "<!DOCTYPE ";
|
|
if (isXML())
|
|
{
|
|
*m_streamOut << "html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"DTD/xhtml1-strict.dtd\">\n";
|
|
}
|
|
else
|
|
{
|
|
*m_streamOut << "HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\n";
|
|
}
|
|
}
|
|
|
|
bool HtmlWorker::doOpenDocument(void)
|
|
{
|
|
// Make the file header
|
|
|
|
if (isXML())
|
|
{ //Write out the XML declaration
|
|
*m_streamOut << "<?xml version=\"1.0\" encoding=\""
|
|
<< getCodec()->mimeName() << "\"?>" << endl;
|
|
}
|
|
|
|
// write <!DOCTYPE
|
|
writeDocType();
|
|
|
|
// No "lang" or "xml:lang" attribute for <html>, as we do not know in which language the document is!
|
|
*m_streamOut << "<html";
|
|
if (isXML())
|
|
{
|
|
// XHTML has an extra attribute defining its namespace (in the <html> opening tag)
|
|
*m_streamOut << " xmlns=\"http://www.w3.org/1999/xhtml\"";
|
|
}
|
|
*m_streamOut << ">\n";
|
|
return true;
|
|
}
|
|
|
|
bool HtmlWorker::doCloseDocument(void)
|
|
{
|
|
kdDebug(30503) << __FILE__ << ":" << __LINE__ << endl;
|
|
*m_streamOut << "</html>\n";
|
|
return true;
|
|
}
|
|
|
|
bool HtmlWorker::doFullDocumentInfo(const KWEFDocumentInfo& docInfo)
|
|
{
|
|
TQString strText=docInfo.title;
|
|
if (!strText.isEmpty())
|
|
{
|
|
m_strTitle=strText; // Set title only if it is not empty!
|
|
kdDebug(30503) << "Found new title " << m_strTitle << endl;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool HtmlWorker::doOpenHead(void)
|
|
{
|
|
*m_streamOut << "<head>" << endl;
|
|
|
|
// Declare what charset we are using
|
|
*m_streamOut << "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=";
|
|
*m_streamOut << getCodec()->mimeName() << '"';
|
|
*m_streamOut << (isXML()?" /":"") << ">\n" ;
|
|
|
|
// Say who we are (with the CVS revision number) in case we have a bug in our filter output!
|
|
TQString strVersion("$Revision: 466447 $");
|
|
// Eliminate the dollar signs
|
|
// (We don't want that the version number changes if the HTML file is itself put in a CVS storage.)
|
|
*m_streamOut << "<meta name=\"Generator\" content=\"KWord HTML Export Filter Version"
|
|
<< strVersion.mid( 10 ).remove( '$' )
|
|
<< "\""<< (isXML()?" /":"") // X(HT)ML closes empty elements, HTML not!
|
|
<< ">\n";
|
|
|
|
if (m_strTitle.isEmpty())
|
|
{
|
|
// Somehow we have still an empty title (this should not happen!)
|
|
kdWarning(30503) << "Title still empty! (HtmlWorker::doOpenHead)" << endl;
|
|
m_strTitle=i18n("Untitled Document");
|
|
}
|
|
*m_streamOut << "<title>"<< escapeHtmlText(m_strTitle) <<"</title>\n"; // <TITLE> is mandatory!
|
|
|
|
if( !customCSSURL().isEmpty() )
|
|
{
|
|
*m_streamOut << "<link ref=\"stylesheet\" type=\"text/css\" href=\"" << customCSSURL() << "\" title=\"Style\" >\n" << endl;
|
|
}
|
|
|
|
//TODO: transform documentinfo.xml into many <META> elements (at least the author!)
|
|
|
|
return true;
|
|
}
|
|
|
|
bool HtmlWorker::doCloseHead(void)
|
|
{
|
|
*m_streamOut << "</head>\n";
|
|
return true;
|
|
}
|
|
|
|
bool HtmlWorker::doOpenBody(void)
|
|
{
|
|
*m_streamOut << "<body>\n";
|
|
return true;
|
|
}
|
|
|
|
bool HtmlWorker::doCloseBody(void)
|
|
{
|
|
*m_streamOut << "</body>\n";
|
|
return true;
|
|
}
|
|
|
|
bool HtmlWorker::doOpenTextFrameSet(void)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
bool HtmlWorker::doCloseTextFrameSet(void)
|
|
{
|
|
if (!m_listStack.isEmpty())
|
|
{
|
|
for (uint i=m_listStack.size(); i>0; i--)
|
|
{
|
|
ListInfo oldList=m_listStack.pop();
|
|
if (oldList.m_orderedList)
|
|
{
|
|
*m_streamOut << "</ol>\n";
|
|
}
|
|
else
|
|
{
|
|
*m_streamOut << "</ul>\n";
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|