You can specify an image to appear at the top of the html file and specify it's dimensions.
You can give HTMLLogRenderer a list of strings to be displayed with newlines between them as a header to the file.
You can specify special formatting strings to be applied to Log entries depeding on the first word in the entry. For instance if you've defined a style whose "flag" is "Error:" with text color white and highlight color red, that formatting will be applied to each Log message that starts with "Error:". A style can have user prefix and postfix strings which can be used for things like making the message a url or adding an annoying <blink>.
You can log "Additional Information" by logging a message in this format: +Section_Name Entry_String. The information is displayed at the bottom of the file with each Entry_String appearing numbered below each Section_Name.
Here's what it looks like in practice. This is a contrived example because Ogre's various log entries obviously don't take this naming scheme into account.

Here's the config file that defines the styles used in that screenshot. You'll need to either copy the ogrelogo-small.jpg image to the program's running folder or change the path to the image.
Code: Select all
# comment out these top two lines if you don't want an image at the top
headerImage=ogrelogo-small.jpg
headerImageDimensions=269 158
# style=FLAG TEXT_COLOR HIGHLIGHT_COLOR
style=App: blue white
style=Error: red white
style=Fatal_Error: white red
style=Resources: green white
style=Info: orange white
style=Blinky: black white <blink> </blink>
Code: Select all
#ifndef __HTMLLogRenderer_h__
#define __HTMLLogRenderer_h__
#include "Ogre.h"
using namespace Ogre;
#include <list>
#include <map>
using namespace std;
typedef list<String> StringList;
typedef map<String,StringList*> StringMap;
class StringUtilEx : public StringUtil
{
public:
/*
found this find and replace code on the internet at
http://www.pscode.com/vb/scripts/ShowCode.asp?txtCodeId=7447&lngWId=3
I don't know if it's ok to just take it or not
*/
static string findAndReplace( String str, String find, String replaceWith )
{
unsigned long iIndex1 = str.find(find, 0);
unsigned long iIndex2 = 0;
unsigned long iLengthOld = find.length();
unsigned long iLengthNew = replaceWith.length();
while (iIndex1 != string::npos)
{
iIndex2 = iIndex1 + iLengthNew + 1;
str = str.erase(iIndex1, iLengthOld);
str = str.insert(iIndex1, replaceWith);
iIndex1 = str.find(find, iIndex2);
}
return str;
}
static bool areStringsEqual( String str1, String str2 )
{
int length = ( (str1.length() > str2.length()) ? str1.length() : str2.length() );
if( !strncmp( str1.c_str(), str2.c_str(), length ) )
return true;
return false;
}
};
class HTMLLogRenderer : public LogListener
{
protected:
// stores the formatting strings that are put before and after a message if
// it's first word matches the style's flag string
class Style
{
public:
Style( String flag, String prefix, String postfix )
{
mFlag = flag;
mPrefix = prefix;
mPostfix = postfix;
}
String mFlag;
String mPrefix;
String mPostfix;
};
typedef list<Style*> StyleList;
public:
HTMLLogRenderer( const String &originalLogFilename, const String &rendererConfigFilename,
StringList &headerInfo ) : LogListener()
{
mLoggingLevel = LL_NORMAL;
mOriginalLogFilename = originalLogFilename;
mNumEntries = 0;
// open renderer config file
ConfigFile cf;
cf.loadDirect( rendererConfigFilename, "\t=" );
String headerImageFilename;
int headerImageWidth = 0;
int headerImageHeight = 0;
// parse the config file
ConfigFile::SettingsIterator config_iter = cf.getSettingsIterator();
while( config_iter.hasMoreElements() )
{
String key = config_iter.peekNextKey();
String value = config_iter.peekNextValue();
// if this element is a style, add it
if( StringUtilEx::areStringsEqual( key, "style" ) )
{
stringstream ss( value );
String flag, textColour, highlightColour, prefix, postfix;
ss >> flag >> textColour >> highlightColour >> prefix >> postfix;
addStyle( flag, textColour, highlightColour, prefix, postfix );
}
// set the header image filename
else if( StringUtilEx::areStringsEqual( key, "headerImage" ) )
{
stringstream ss( value );
ss >> headerImageFilename;
}
// set the header dimensions
else if( StringUtilEx::areStringsEqual( key, "headerImageDimensions" ) )
{
stringstream ss( value );
ss >> headerImageWidth >> headerImageHeight;
}
config_iter.getNext();
}
// compile renderer output filename
String newFilename = originalLogFilename;
newFilename.append( ".html" );
mFile.open( newFilename.c_str() );
// write HTML header
mFile << "<header></header><body>";
if( !StringUtilEx::areStringsEqual( "", headerImageFilename ) )
{
mFile << "<img src=\"" << headerImageFilename << "\" ";
//if header image dimensions have been specified ( and the value isn't 0 ) write dimensions
if( headerImageHeight > 0 && headerImageWidth > 0 )
mFile << " width=" << headerImageWidth << " height=" << headerImageHeight;
// close img tag
mFile << "><br>";
}
// write font tag
mFile << "<font style=\"FONT-FAMILY: \'Courier New\'\" size=2>";
// write any supplied header strings
for( StringList::iterator iter = headerInfo.begin(); iter != headerInfo.end(); ++iter )
{
String headerLine = static_cast<String>(*iter);
mFile << headerLine << "<br>";
}
// write the stuff that preceeds the log entries
mFile << "<br>Log<br>";
mFile << "--------------------------------------------------" << "<br>";
mFile << "</b>";
mFile.flush();
}
virtual ~HTMLLogRenderer()
{
// this is the line that appears after the last message in the log
mFile << "--------------------------------------------------" << "<br>";
mFile << "</b>";
mFile << "<br>";
renderAdditionalInfo();
mFile << "</body>";
// not sure if it's necessary to call this before close()
mFile.flush();
mFile.close();
// delete the styles
for( StyleList::iterator iter = mStyleList.begin(); iter != mStyleList.end(); iter )
{
Style *style = *iter;
delete style;
iter = mStyleList.erase( iter );
}
}
virtual void write( const String& name, const String& message,
LogMessageLevel lml = LML_NORMAL, bool maskDebug = false )
{
// check if the message is meant for the log this is shadowing
if( StringUtilEx::areStringsEqual( name, mOriginalLogFilename ) )
// check for the '+' sign that indicates an additional info entry
if( StringUtil::startsWith( message, "+" ) )
addAdditionalInfo( message );
// check if the logging level of the message meets the threshold, copied from the Log class
else if( ( mLoggingLevel + lml ) >= OGRE_LOG_THRESHOLD )
{
// write time ( slightly changed code from Ogre's log class )
struct tm *pTime;
time_t ctTime; time(&ctTime);
pTime = localtime( &ctTime );
mFile << "#" << std::setw(4) << std::setfill('0') << mNumEntries++;
mFile
<< " " << std::setw(2) << std::setfill('0') << pTime->tm_hour
<< ":" << std::setw(2) << std::setfill('0') << pTime->tm_min
<< ":" << std::setw(2) << std::setfill('0') << pTime->tm_sec;
mFile << " ";
// get the first word in the message, which will be the prefix if one is present
stringstream ss( message );
String firstWord;
ss >> firstWord;
// if firstWord is equal to a style's flag string, get a pointer to that style
Style *style = 0;
for( StyleList::iterator iter = mStyleList.begin(); iter != mStyleList.end(); ++iter )
{
if( StringUtilEx::areStringsEqual( firstWord, static_cast<Style*>(*iter)->mFlag ) )
style = *iter;
}
// this string is the string to be output to mFile
String outMessage;
// if a style was found
if( style )
{
// add the prefix and postfix around the original message
outMessage.append( style->mPrefix );
outMessage.append( message );
outMessage.append( style->mPostfix );
}
// otherwise just copy the original message
else
{
outMessage = message;
}
// change any \n newlines into HTML's <br>,
// and add enough spaces so that the new line is indented past the entry number and time
outMessage = StringUtilEx::findAndReplace( outMessage, "\n", "<br>\t\t\t\t " );
// change any \t tabs into five HTML  
outMessage = StringUtilEx::findAndReplace( outMessage, "\t", " " );
outMessage.append( "<br>" );
mFile << outMessage;
mFile.flush();
}
}
void setLoggingLevel( LoggingLevel lml )
{
mLoggingLevel = lml;
}
protected:
void addStyle( String flag,
String textColour, String highlightColour,
String customPrefix, String customPostfix )
{
stringstream prefix;
prefix << "<font color=\"" << textColour <<
"\" style=\"FONT-FAMILY: 'Courier New';BACKGROUND-COLOR:" << highlightColour << "\" size=2>" << customPrefix;
stringstream postfix;
postfix << customPostfix << "</font>";
Style *style = new Style( flag, prefix.str(), postfix.str() );
mStyleList.push_back( style );
}
void addAdditionalInfo( String sInfo )
{
stringstream ss( sInfo );
String section;
String message;
ss >> section;
message = sInfo.substr( section.length(), sInfo.length() );
// remove '+' character
section = section.substr( 1, section.length() - 1 );
StringList *itemList = 0;
for( StringMap::iterator iter = mAdditionalInfo.begin(); iter != mAdditionalInfo.end(); ++iter )
{
if( !strncmp( static_cast<String>(iter->first).c_str(), section.c_str(), section.length() ) )
itemList = iter->second;
}
if( !itemList )
{
StringList *itemList = new StringList;
mAdditionalInfo[section] = itemList;
}
itemList = mAdditionalInfo[section];
itemList->push_back( message );
}
void renderAdditionalInfo()
{
mFile << "Additional Info: " << "<br>";
mFile << "--------------------------------------------------" << "<br><br>";
// for each additional info section
for( StringMap::iterator k_iter = mAdditionalInfo.begin(); k_iter != mAdditionalInfo.end(); ++k_iter )
{
string headerName = k_iter->first;
// underscores can be substituted for space in a section name, so fix that up
headerName = StringUtilEx::findAndReplace( headerName, "_", " " );
mFile << headerName << ": " << "<br>";
int i = 1;
StringList *lines = k_iter->second;
// for each entry in the section
for( StringList::iterator v_iter = lines->begin(); v_iter != lines->end(); ++v_iter )
{
String line = *v_iter;
mFile << " " << i << ")" << " " << line << "<br>";
++i;
}
mFile << "<br>";
}
mFile << "--------------------------------------------------" << "<br>";
}
protected:
ofstream mFile;
LoggingLevel mLoggingLevel;
String mOriginalLogFilename;
StyleList mStyleList;
StringMap mAdditionalInfo;
int mNumEntries;
};
#endif
Edit:
You may notice a shotgun scattering of "const" throughout the file. That's because I've never really bothered to "const" things before and I'm still a little shakey on where and when and why to do that. I meant to check that all over before I posted this, but forgot so here you go.