OGRE Wiki
Support and community documentation for Ogre3D
Ogre Forums
ogre3d.org
Log in
Username:
Password:
CapsLock is on.
Remember me (for 1 year)
Log in
Home
Tutorials
Tutorials Home
Basic Tutorials
Intermediate Tutorials
Mad Marx Tutorials
In Depth Tutorials
Older Tutorials
External Tutorials
Cookbook
Cookbook Home
CodeBank
Snippets
Experiences
Ogre Articles
Libraries
Libraries Home
Alternative Languages
Assembling A Toolset
Development Tools
OGRE Libraries
List of Libraries
Tools
Tools Home
DCC Tools
DCC Tutorials
DCC Articles
DCC Resources
Assembling a production pipeline
Development
Development Home
Roadmap
Building Ogre
Installing the Ogre SDK
Setting Up An Application
Ogre Wiki Tutorial Framework
Frequently Asked Questions
Google Summer Of Code
Help Requested
Ogre Core Articles
Community
Community Home
Projects Using Ogre
Recommended Reading
Contractors
Wiki
Immediate Wiki Tasklist
Wiki Ideas
Wiki Guidelines
Article Writing Guidelines
Wiki Styles
Wiki Page Tracker
Ogre Wiki Help
Ogre Wiki Help Overview
Help - Basic Syntax
Help - Images
Help - Pages and Structures
Help - Wiki Plugins
Toolbox
Freetags
Categories
List Pages
Structures
Trackers
Statistics
Rankings
List Galleries
Ogre Lexicon
Comments
History: HTMLLogRenderer
View page
Source of version: 7
(current)
{REMARKSBOX(type="comment",title="",close="n")}This was posted at the Ogre3D Forum by Johnpus - [http://www.ogre3d.org/forums/viewtopic.php?f=5&t=13089|topic]{REMARKSBOX} {img align=right fileId=2042 thumb=y width=250 rel=box[g]} !Introduction I've put together a class that derives from LogListener and shadows a given Log's output with an html version that adds a couple of features. 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. {maketoc} !Configuration 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(wrap="1", colors="ini")} # 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} !Code {CODE(wrap="1", colors="c++")} #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 {CODE} !Fun Fact Fun Fact: I wrote a version of this that output to RTF format before I realized "oh yeah, html". !Notes 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.
Search by Tags
Search Wiki by Freetags
Latest Changes
Ogre 2.1 FAQ
Minimal Ogre Collision
Artifex Terra
OpenMB
Advanced Mogre Framework
MogreSocks
Critter AI
Mogre Add-ons
MOGRE
Mogre MyGUI wrapper
...more
Search
Find
Advanced
Search Help
Online Users
132 online users