// @(#)root/html:$Name:  $:$Id: THtml.cxx,v 1.39 2003/04/28 05:23:15 brun Exp $
// Author: Nenad Buncic (18/10/95), Axel Naumann <mailto:axel@fnal.gov> (09/28/01)

/*************************************************************************
 * Copyright (C) 1995-2000, Rene Brun and Fons Rademakers.               *
 * All rights reserved.                                                  *
 *                                                                       *
 * For the licensing terms see $ROOTSYS/LICENSE.                         *
 * For the list of contributors see $ROOTSYS/README/CREDITS.             *
 *************************************************************************/

#include "TBaseClass.h"
#include "TVirtualPad.h"
#include "TClass.h"
#include "TClassTable.h"
#include "TDataMember.h"
#include "TDataType.h"
#include "TDatime.h"
#include "TEnv.h"
#include "TError.h"
#include "THtml.h"
#include "TMethod.h"
#include "TMethodArg.h"
#include "TSystem.h"
#include "TObjString.h"
#include "TInterpreter.h"
#include "TRegexp.h"
#include "Riostream.h"
#include "TPluginManager.h"
#include "TVirtualUtilPad.h"
#include "TPaveText.h"

#include <stdlib.h>
#include <string.h>
#include <ctype.h>

THtml *gHtml = 0;
THashList THtml::fgLocalTypes;

const Int_t kSpaceNum = 1;
const char *formatStr = "%12s %5s %s";

enum ESortType { kCaseInsensitive, kCaseSensitive };
enum EFileType { kSource, kInclude, kTree };


////////////////////////////////////////////////////////////////////////////////
//
// The HyperText Markup Language (HTML) is a simple data format used to
// create hypertext documents that are portable from one platform to another.
// HTML documents are SGML documents with generic semantics that are
// appropriate for representing information from a wide range of domains.
//
// The THtml class is designed to provide an easy way for converting ROOT
// classes, and files as well, into HTML documents. Here is the few rules and
// suggestions for a configuration, coding and usage.
//
//
// Configuration:
// -------------
//
// (i)   Input files
//
// Define Root.Html.SourceDir to point to directories containing .cxx and
// .h files ( see: TEnv ) of the classes you want to document. Better yet,
// specify separate Unix.*.Root.Html.SourceDir and WinNT.*.Root.Html.SourceDir.
// Root.Html.SourcePrefix can hold an additional (relative) path to help THtml
// find the source files. Its default value is "". Root's class documentation
// files can be linked in if Root.Html.Root is set to Root's class
// documentation root. It defaults to "".
//
// Examples:
//   Unix.*.Root.Html.SourceDir:  .:src:include
//   WinNT.*.Root.Html.SourceDir: .;src;include
//   Root.Html.SourcePrefix:
//   Root.Html.Root:              http://root.cern.ch/root/html
//
//
// (ii)  Output directory
//
// The output directory can be specified using the Root.Html.OutputDir
// environment variable ( default value: "htmldoc" ). If it doesn't exist, it
// will be created.
//
// Examples (with defaults given):
//   Root.Html.OutputDir:         htmldoc
//
//
// (iii) Class documentation
//
// The class documentation has to appear in the header file containing the
// class, right in front of its declaration. It is introduced by a string
// defined by Root.Html.Description. See below, section "Coding", for
// further details.
//
// Examples (with defaults given):
//   Root.Html.Description:       //____________________
//
//
// (iv)  Source file information
//
// During the conversion, THtml will look for the certain number of user
// defined strings ("tags") in the source file, which have to appear right in
// front of e.g. the author's name, copyright notice, etc. These tags can be
// defined with the following environment variables: Root.Html.Author,
// Root.Html.LastUpdate and Root.Html.Copyright.
//
// If the LastUpdate tag is not found, the current date and time are given.
// This makes sense if one uses THtml's default option force=kFALSE, in which
// case THtml generates documentation only for changed classes.
//
// Authors can be a comma separated list of author entries. Each entry has
// one of the following two formats:
//  * "Name (non-alpha)". THtml will generate an HTML link for Name, taking
//    the Root.Html.XWho environment variable (defaults to
//    "http://consult.cern.ch/xwho/people?") and adding all parts of the name
//    with spaces replaces by '+'. Non-Alphas are printed out behind Name.
//
//    Example: "// Author: Enrico Fermi" appears in the source file. THtml
//    will generate the link
//    "http://consult.cern.ch/xwho/people?Enrico+Fermi". This works well for
//    people at CERN.
//
//  * "Name <link> Info" THtml will generate a HTML link for Name as specified
//    by "link" and print Info behind Name.
//
//    Example: "// Author: Enrico Fermi <http://www.enricos-home.it>" or
//    "// Author: Enrico Fermi <mailto:enrico@fnal.gov>" in the
//    source file. That's world compatible.
//
// Examples (with defaults given):
//       Root.Html.Author:     // Author:
//       Root.Html.LastUpdate: // @(#)
//       Root.Html.Copyright:  * Copyright
//       Root.Html.XWho:       http://consult.cern.ch/xwho/people?
//
//
// (v)   Style
//
// THtml generates a default header and footer for all pages. You can
// specify your own versions with the environment variables Root.Html.Header
// and Root.Html.Footer. Both variables default to "", using the standard Root
// versions. If set the parameter to your file and append a "+", THtml will
// write both versions (user and root) to a file, for the header in the order
// 1st root, 2nd user, and for the footer 1st user, 2nd root (the root
// versions containing "<html>" and </html> tags, resp).
//
// If you want to replace root's header you have to write a file containing
// all HTML elements necessary starting with the <DOCTYPE> tag and ending with
// (and including) the <BODY> tag. If you add your header it will be added
// directly after Root's <BODY> tag. Any occurence of the string "%TITLE%"
// (without the quotation marks) in the user's header file will be replaced by
// a sensible, automatically generated title.
//
// Root's footer starts with the tag "<!--SIGNATURE-->". It includes the
// author(s), last update, copyright, the links to the Root home page, to the
// user home page, to the index file (ClassIndex.html), to the top of the page
// and "this page is automatically generated" infomation. It ends with the
// tags "</body></html>. If you want to replace it, THtml will search for some
// tags in your footer: Occurences of the strings "%AUTHOR%", "%UPDATE%", and
// "%COPYRIGHT%" (without the quotation marks) are replaced by their
// corresponding values before writing the html file. The %AUTHOR% tag will be
// replaced by the exact string that follows Root.Html.Author, no link
// generation will occur.
//
//
// (vi)  Miscellaneous
//
// Additional parameters can be set by Root.Html.Homepage (address of the
// user's home page) and Root.Html.SearchEngine (search engine for the class
// documentation). Both default to "".
//
// Examples:
//       Root.Html.Homepage:     http://www.enricos-home.it
//       Root.Html.SearchEngine: http://root.cern.ch/root/Search.phtml
//
//
//
//
// Coding rules:
// ------------
//
// A class description block, which must be placed before the first
// member function, has a following form:
//
//       ////////////////////////////////////////////////////////////////
//       //                                                            //
//       // TMyClass                                                   //
//       //                                                            //
//       // This is the description block.                             //
//       //                                                            //
//       ////////////////////////////////////////////////////////////////
//
// The environment variable Root.Html.Description ( see: TEnv ) contents
// the delimiter string ( default value: //_________________ ). It means
// that you can also write your class description block like this:
//
//       //_____________________________________________________________
//       // A description of the class starts with the line above, and
//       // will take place here !
//       //
//
// Note that EVERYTHING until the first non-commented line is considered
// as a valid class description block.
//
// A member function description block starts immediately after '{'
// and looks like this:
//
//       void TWorld::HelloWorldFunc(string *text)
//       {
//          // This is an example of description for the
//          // TWorld member function
//
//          helloWorld.Print( text );
//       }
//
// Like in a class description block, EVERYTHING until the first
// non-commented line is considered as a valid member function
// description block.
//
//   ==> The "Begin_Html" and "End_Html" special keywords <=========
//       --------------------------------------------
// You can insert pure html code in your comment lines. During the
// generation of the documentation, this code will be inserted as is
// in the html file.
// Pure html code must be inserted between the keywords "Begin_Html"
// and "End_Html" starting/finishing anywhere in the comment lines.
// Examples of pure html code are given in many Root classes.
// See for example the classes TDataMember and TMinuit.
//
//   ==> The escape character
//       --------------------
// Outside blocks starting with "Begin_Html" and finishing with "End_Html"
// one can prevent the automatic translation of symbols like "<" and ">"
// to "&lt;" and "&gt;" by using the escape character in front.
// The default escape character is backslash and can be changed
// via the member function SetEscape.
//
//   ==> The ClassIndex
//       --------------
// All classes to be documented will have an entry in the ClassIndex.html,
// showing their name with a link to their documentation page and a miniature
// description. This discription for e.g. the class MyClass has to be given
// in MyClass's header as a comment right after ClassDef( MyClass, n ).
//
//
//
// Usage:
// -----
//
//     Root> THtml html;                // create a THtml object
//     Root> html.MakeAll()             // invoke a make for all classes
//     Root> html.MakeClass("TMyClass") // create a HTML files for that class only
//     Root> html.MakeIndex()           // creates an index files only
//     Root> html.MakeTree("TMyClass")  // creates an inheritance tree for a class
//
//     Root> html.Convert( hist1.mac, "Histogram example" )
//
//
// Environment variables:
// ---------------------
//
//   Root.Html.OutputDir    ( default: htmldoc)
//   Root.Html.SourceDir    ( default: .:src/:include/)
//   Root.Html.Author       ( default: // Author:) - start tag for authors
//   Root.Html.LastUpdate   ( default: // @(#)) - start tag for last update
//   Root.Html.Copyright    ( default:  * Copyright) - start tag for copyright notice
//   Root.Html.Description  ( default: //____________________ ) - start tag for class descr
//   Root.Html.HomePage     ( default: ) - URL to the user defined home page
//   Root.Html.Header       ( default: ) - location of user defined header
//   Root.Html.Footer       ( default: ) - location of user defined footer
//   Root.Html.Root         ( default: ) - URL of Root's class documentation
//   Root.Html.SearchEngine ( default: ) - link to the search engine
//   Root.Html.XWho         ( default: http://consult.cern.ch/xwho/people?) - URL stem of CERN's xWho system
//
////////////////////////////////////////////////////////////////////////////////

ClassImp(THtml)
//______________________________________________________________________________
 THtml::THtml():
fMapDocElements(0)
{
   // Create a THtml object.
   // In case output directory does not exist an error
   // will be printed and gHtml stays 0 also zombie bit will be set.

   fLen = 1024;
   fLine = new char[fLen];
   fCounter = new char[6];
   for (Int_t i = 0; i < 6; i++)
      fCounter[i] = 0;
   fEscFlag = kFALSE;
   SetEscape();

   // get prefix for source directory
   fSourcePrefix = gEnv->GetValue("Root.Html.SourcePrefix", "");

   // check for source directory
   fSourceDir = gEnv->GetValue("Root.Html.SourceDir", "./:src/:include/");

   // check for output directory
   fOutputDir = gEnv->GetValue("Root.Html.OutputDir", "htmldoc");

   fXwho =
       gEnv->GetValue("Root.Html.XWho",
                      "http://consult.cern.ch/xwho/people?");

   Int_t st;
   Long_t sId, sSize, sFlags, sModtime;
   if ((st =
        gSystem->GetPathInfo(fOutputDir, &sId, &sSize, &sFlags, &sModtime))
       || !(sFlags & 2)) {
      if (st == 0) {
         Error("THtml", "output directory %s is an existing file",
               fOutputDir);
         MakeZombie();
         return;
      }
      // Try creating directory
      if (gSystem->MakeDirectory(fOutputDir) == -1) {
         Error("THtml", "output directory %s does not exist", fOutputDir);
         MakeZombie();
         return;
      }
   }
   // insert html object in the list of special ROOT objects
   gHtml = this;
   gROOT->GetListOfSpecials()->Add(gHtml);

   // add CInt's types to out list of types
   // to do: add decl pos
   fgLocalTypes.Clear();
//   G__ClassInfo clinfo;
//   while (clinfo.Next())
//      fgLocalTypes.Add(new TLocalType(clinfo.Fullname(), TLocalType::kClass, 0, -1));

   G__TypedefInfo typeinfo;
   while (typeinfo.Next()) 
      fgLocalTypes.Add(new TLocalType(typeinfo.Name(), TLocalType::kTypedef,
      0, -1, typeinfo.TrueName()));
}


//______________________________________________________________________________
 THtml::~THtml()
{
// Default destructor

   if (fLine)
      delete[]fLine;
   if (fCounter)
      delete[]fCounter;

   fSourceDir = 0;
   fLen = 0;
}


//______________________________________________________________________________
int CaseSensitiveSort(const void *name1, const void *name2)
{
// Friend function for sorting strings, case sensitive
//
//
// Input: name1 - pointer to the first string
//        name2 - pointer to the second string
//
//  NOTE: This function compares its arguments and returns an integer less
//        than, equal to, or greater than zero, depending on whether name1
//        is lexicographically less than, equal to, or greater than name2.
//
//

   return (strcmp(*((char **) name1), *((char **) name2)));
}


//______________________________________________________________________________
int CaseInsensitiveSort(const void *name1, const void *name2)
{
// Friend function for sorting strings, case insensitive
//
//
// Input: name1 - pointer to the first string
//        name2 - pointer to the second string
//
//  NOTE: This function compares its arguments and returns an integer less
//        than, equal to, or greater than zero, depending on whether name1
//        is lexicographically less than, equal to, or greater than name2,
//        but characters are forced to lower-case prior to comparison.
//
//

   return (strcasecmp(*((char **) name1), *((char **) name2)));
}


//______________________________________________________________________________
 void THtml::Class2Html(TClass * classPtr, Bool_t force)
{
// It creates HTML file for a single class
//
//
// Input: classPtr - pointer to the class

   const char *tab = "<!--TAB-->";
   const char *tab2 = "<!--TAB2-->  ";
   const char *tab4 = "<!--TAB4-->    ";
   const char *tab6 = "<!--TAB6-->      ";

   gROOT->GetListOfGlobals(kTRUE);

   // create a filename
   char classname[1024];
   strcpy(classname, classPtr->GetName());
   NameSpace2FileName(classname);

   char *tmp1 = gSystem->ExpandPathName(fOutputDir);
   char *tmp2 = gSystem->ConcatFileName(tmp1, classname);

   char *filename = StrDup(tmp2, 6);
   strcat(filename, ".html");

   if (tmp1)
      delete[]tmp1;
   if (tmp2)
      delete[]tmp2;
   tmp1 = tmp2 = 0;

   if (IsModified(classPtr, kSource) || force) {

      // open class file
ofstream classFile;
      classFile.open(filename, ios::out

      Bool_t classFlag = kFALSE;


      if (classFile.good()) {

         Printf(formatStr, "", fCounter, filename);

         // write a HTML header for the classFile file
         WriteHtmlHeader(classFile, classPtr->GetName());

         // make a link to the description
         classFile << "<!--BEGIN-->" << endl;
         classFile << "<center>" << endl;
         classFile << "<h1>" << classPtr->GetName() << "</h1>" << endl;
         classFile << "<hr width=300>" << endl;
         classFile << "<!--SDL--><em><a href="#" << classPtr->GetName()
             << ":description">class description</a>";

         // make a link to the '.cxx' file
         char *cClassFileName = StrDup(classPtr->GetName());
         NameSpace2FileName(cClassFileName);

         classFile << " - <a href="src/" << cClassFileName <<
             ".cxx.html"";
         classFile << ">source file</a>";

         // make a link to the inheritance tree
         classFile << " - <a href="" << cClassFileName << "_Tree.ps"";
         classFile << ">inheritance tree</a>";

         if (cClassFileName != 0)
            delete[]cClassFileName;

         classFile << "</em>" << endl;
         classFile << "<hr width=300>" << endl;
         classFile << "</center>" << endl;


         // make a link to the '.h' file
         classFile << "<h2>" << "class <a name="" << classPtr->
             GetName() << "" href="";
         classFile << GetFileName((const char *) classPtr->
                                  GetDeclFileName()) << """;
         classFile << ">" << classPtr->GetName() << "</a> ";


         // copy .h file to the Html output directory
         char *declf = GetSourceFileName(classPtr->GetDeclFileName());
         if (declf) {
            CopyHtmlFile(declf);
            delete[]declf;
         }

         // make a loop on base classes
         Bool_t first = kTRUE;
         TBaseClass *inheritFrom;
         TIter nextBase(classPtr->GetListOfBases());

         while ((inheritFrom = (TBaseClass *) nextBase())) {
            if (first) {
               classFile << ": ";
               first = kFALSE;
            } else
               classFile << ", ";
            classFile << "public ";

            // get a class
            TClass *classInh =
                GetClass((const char *) inheritFrom->GetName());

            char *htmlFile = GetHtmlFileName(classInh);

            if (htmlFile) {
               classFile << "<a href="";

               // make a link to the base class
               classFile << htmlFile;
               classFile << "">" << inheritFrom->GetName() << "</a>";
               delete[]htmlFile;
               htmlFile = 0;
            } else
               classFile << inheritFrom->GetName();
         }

         classFile << "</h2>" << endl;
         classFile << "<pre>" << endl;


         // make a loop on member functions
         TMethod *method;
         TIter nextMethod(classPtr->GetListOfMethods());

         Int_t len, maxLen[3];
         len = maxLen[0] = maxLen[1] = maxLen[2] = 0;

         // loop to get a pointers to a method names
         const Int_t nMethods = classPtr->GetNmethods();
         const char **methodNames = new const char *[3 * 2 * nMethods];

         Int_t mtype, num[3];
         mtype = num[0] = num[1] = num[2] = 0;

         while ((method = (TMethod *) nextMethod())) {

            if (!strcmp(method->GetName(), "Dictionary") ||
                !strcmp(method->GetName(), "Class_Version") ||
                !strcmp(method->GetName(), "Class_Name") ||
                !strcmp(method->GetName(), "DeclFileName") ||
                !strcmp(method->GetName(), "DeclFileLine") ||
                !strcmp(method->GetName(), "ImplFileName") ||
                !strcmp(method->GetName(), "ImplFileLine")
                )
               continue;


            if (kIsPrivate & method->Property())
               mtype = 0;
            else if (kIsProtected & method->Property())
               mtype = 1;
            else if (kIsPublic & method->Property())
               mtype = 2;

            methodNames[mtype * 2 * nMethods + 2 * num[mtype]] =
                method->GetName();

            if (method->GetReturnTypeName())
               len = strlen(method->GetReturnTypeName());
            else
               len = 0;

            if (kIsVirtual & method->Property())
               len += 8;
            if (kIsStatic & method->Property())
               len += 7;

            maxLen[mtype] = maxLen[mtype] > len ? maxLen[mtype] : len;

            const char *type = strrchr(method->GetReturnTypeName(), ' ');
            if (!type)
               type = method->GetReturnTypeName();
            else
               type++;

            if (classPtr && !strcmp(type, classPtr->GetName()))
               methodNames[mtype * 2 * nMethods + 2 * num[mtype]] =
                   "A00000000";

            // if this is the destructor
            while ('~' ==
                   *methodNames[mtype * 2 * nMethods + 2 * num[mtype]])
               methodNames[mtype * 2 * nMethods + 2 * num[mtype]] =
                   "A00000001";

            methodNames[mtype * 2 * nMethods + 2 * num[mtype] + 1] =
                (char *) method;

            num[mtype]++;
         }

         Int_t i, j;

         for (j = 0; j < 3; j++) {
            if (num[j]) {
                 qsort(methodNames + j * 2 * nMethods, num[j],
                        2 * sizeof(methodNames), CaseInsensitiveSort);

               const char *ftitle = 0;
               switch (j) {
               case 0:
                  ftitle = "private:";
                  break;
               case 1:
                  ftitle = "protected:";
                  break;
               case 2:
                  ftitle = "public:";
                  break;
               }
               if (j)
                  classFile << endl;
               classFile << tab4 << "<b>" << ftitle << "</b><br>" << endl;

               for (i = 0; i < num[j]; i++) {
                  method =
                      (TMethod *) methodNames[j * 2 * nMethods + 2 * i +
                                              1];

                  if (method) {
                     Int_t w = 0;
		     Bool_t isctor=false;
		     Bool_t isdtor=false;
                     if (method->GetReturnTypeName())
                        len = strlen(method->GetReturnTypeName());
                     else
                        len = 0;
		     if (!strcmp(method->GetName(),classPtr->GetName()))
			// it's a c'tor - Cint stores the class name as return type
			isctor=true;
		     if (!strcmp(Form("~%s",classPtr->GetName()),method->GetName()))
			// it's a d'tor - Cint stores "void" as return type
			isdtor=true;
		     if (isctor || isdtor)
			len=0;

                     if (kIsVirtual & method->Property())
                        len += 8;
                     if (kIsStatic & method->Property())
                        len += 7;

                     classFile << tab6;
                     for (w = 0; w < (maxLen[j] - len); w++)
                        classFile << " ";

                     if (kIsVirtual & method->Property())
			if (!isdtor)
			   classFile << "virtual ";
			else
			   classFile << " virtual";

                     if (kIsStatic & method->Property())
                        classFile << "static ";

		     if (!isctor && !isdtor){
			strcpy(fLine, method->GetReturnTypeName());
			ExpandKeywords(classFile, fLine, classPtr, classFlag);
		     }

                     classFile << " " << tab << "<!--BOLD-->";
                     classFile << "<a href="#" << classPtr->GetName();
                     classFile << ":";
                     ReplaceSpecialChars(classFile, method->GetName());
                     classFile << "">";
                     ReplaceSpecialChars(classFile, method->GetName());
                     classFile << "</a><!--PLAIN-->";

                     strcpy(fLine, method->GetSignature());
                     ExpandKeywords(classFile, fLine, classPtr, classFlag);
                     classFile << endl;
                  }
               }
            }
         }

         delete[]methodNames;

         classFile << "</pre>" << endl;

         // make a loop on data members
         first = kFALSE;
         TDataMember *member;
         TIter nextMember(classPtr->GetListOfDataMembers());


         Int_t len1, len2, maxLen1[3], maxLen2[3];
         len1 = len2 = maxLen1[0] = maxLen1[1] = maxLen1[2] = 0;
         maxLen2[0] = maxLen2[1] = maxLen2[2] = 0;
         mtype = num[0] = num[1] = num[2] = 0;

         Int_t ndata = classPtr->GetNdata();

         // if data member exist
         if (ndata) {
            TDataMember **memberArray = new TDataMember *[3 * ndata];

            if (memberArray) {
               while ((member = (TDataMember *) nextMember())) {
                  if (!strcmp(member->GetName(), "fgIsA")
                      )
                     continue;

                  if (kIsPrivate & member->Property())
                     mtype = 0;
                  else if (kIsProtected & member->Property())
                     mtype = 1;
                  else if (kIsPublic & member->Property())
                     mtype = 2;

                  memberArray[mtype * ndata + num[mtype]] = member;
                  num[mtype]++;

                  if (member->GetFullTypeName())
                     len1 = strlen((char *) member->GetFullTypeName());
                  else
                     len1 = 0;
                  if (member->GetName())
                     len2 = strlen(member->GetName());
                  else
                     len2 = 0;

                  if (kIsStatic & member->Property())
                     len1 += 7;

                  // Take in account the room the array index will occupy

                  Int_t dim = member->GetArrayDim();
                  Int_t indx = 0;
                  Int_t maxidx;
                  while (indx < dim) {
                     maxidx = member->GetMaxIndex(indx);
                     if (maxidx <= 0) 
                        break;
                     else
                        len2 += (Int_t)TMath::Log10(maxidx) + 3;
                     indx++;
                  }
                  maxLen1[mtype] =
                      maxLen1[mtype] > len1 ? maxLen1[mtype] : len1;
                  maxLen2[mtype] =
                      maxLen2[mtype] > len2 ? maxLen2[mtype] : len2;
               }

               classFile << endl;
               classFile << "<h3>" << tab2 << "<a name="";
               classFile << classPtr->GetName();
               classFile << ":Data Members">Data Members</a></h3>" <<
                   endl;
               classFile << "<pre>" << endl;

               for (j = 0; j < 3; j++) {
                  if (num[j]) {
                     const char *ftitle = 0;
                     switch (j) {
                     case 0:
                        ftitle = "private:";
                        break;
                     case 1:
                        ftitle = "protected:";
                        break;
                     case 2:
                        ftitle = "public:";
                        break;
                     }
                     if (j)
                        classFile << endl;
                     classFile << tab4 << "<b>" << ftitle << "</b><br>" <<
                         endl;

                     for (i = 0; i < num[j]; i++) {
                        Int_t w = 0;
                        member = memberArray[j * ndata + i];

                        classFile << tab6;
                        if (member->GetFullTypeName())
                           len1 = strlen(member->GetFullTypeName());
                        else
                           len1 = 0;

                        if (kIsStatic & member->Property())
                           len1 += 7;

                        for (w = 0; w < (maxLen1[j] - len1); w++)
                           classFile << " ";

                        if (kIsStatic & member->Property())
                           classFile << "static ";

                        strcpy(fLine, member->GetFullTypeName());
                        ExpandKeywords(classFile, fLine, classPtr,
                                       classFlag);

                        classFile << " " << tab << "<!--BOLD-->";
                        classFile << "<a name="" << classPtr->
                            GetName() << ":";
                        classFile << member->GetName();
                        classFile << "">" << member->GetName();

                        // Add the dimensions to "array" members

                        Int_t dim = member->GetArrayDim();
                        Int_t indx = 0;
                        Int_t indxlen = 0;
                        while (indx < dim) {
                           if (member->GetMaxIndex(indx) <= 0)
                              break;
                           classFile << "[" << member->
                               GetMaxIndex(indx) << "]";
                           // Take in account the room this index will occupy
                           indxlen +=
                               Int_t(TMath::
                                     Log10(member->GetMaxIndex(indx))) + 3;
                           indx++;
                        }

                        classFile << "</a><!--PLAIN--> ";

                        len2 = 0;
                        if (member->GetName())
                           len2 = strlen(member->GetName()) + indxlen;

                        for (w = 0; w < (maxLen2[j] - len2); w++)
                           classFile << " ";
                        classFile << " " << tab;

                        classFile << "<i><a name="Title:";
                        classFile << member->GetName();

                        classFile << "">";

                        strcpy(fLine, member->GetTitle());
                        ReplaceSpecialChars(classFile, fLine);
                        classFile << "</a></i>" << endl;
                     }
                  }
               }
               classFile << "</pre>" << endl;
               delete[]memberArray;
            }
         }

         classFile << "<!--END-->" << endl;

         // create a 'See also' part
         DerivedClasses(classFile, classPtr);

         // process a '.cxx' file
         ClassDescription(classFile, classPtr, classFlag);


         // close a file
         classFile.close();

      } else
         Error("Make", "Can't open file '%s' !", filename);
   } else
      Printf(formatStr, "-no change-", fCounter, filename);

   if (filename)
      delete[]filename;
   filename = 0;
}

//______________________________________________________________________________
 void THtml::ClassDescription(ofstream & out, TClass * classPtr,
                             Bool_t & flag)
{
// This function builds the description of the class
//
//
// Input: out      - output file stream
//        classPtr - pointer to the class
//        flag     - this is a '/' flag
//

   char *ptr, *key;
   Bool_t tempFlag = kFALSE;
   char *filename = 0;


   // allocate memory
   char *nextLine = new char[1024];
   char *pattern = new char[1024];

   char *lastUpdate = new char[1024];
   char *author = new char[1024];
   char *copyright = new char[1024];

   const char *lastUpdateStr;
   const char *authorStr;
   const char *copyrightStr;
   const char *descriptionStr;


   // just in case
   *lastUpdate = *author = *copyright = 0;


   // define pattern
   strcpy(pattern, classPtr->GetName());
   char *nameSpace = 0;
   if ((nameSpace = strstr(pattern, "::")) != 0)
      strcpy(pattern, &(classPtr->GetName()[nameSpace - pattern + 2]));
   strcat(pattern, "::");
   Int_t len = strlen(pattern);


   // get environment variables
   lastUpdateStr = gEnv->GetValue("Root.Html.LastUpdate", "// @(#)");
   authorStr = gEnv->GetValue("Root.Html.Author", "// Author:");
   copyrightStr = gEnv->GetValue("Root.Html.Copyright", " * Copyright");
   descriptionStr =
       gEnv->GetValue("Root.Html.Description", "//____________________");


   // find a .cxx file
   char *tmp1; 
   if (classPtr->GetImplFileLine()) {
      tmp1 = GetSourceFileName(classPtr->GetImplFileName());
   } else {
      tmp1 = GetSourceFileName(classPtr->GetDeclFileName());
   }
   char *realFilename = 0;
   if (tmp1) { 
      realFilename = StrDup(tmp1, 16);
      if (!realFilename)
         Error("Make", "Can't find file '%s' !", tmp1);
      delete[]tmp1;
   }
   tmp1 = 0;

   Bool_t classDescription = kTRUE;

   Bool_t foundLastUpdate = kFALSE;
   Bool_t foundAuthor = kFALSE;
   Bool_t foundCopyright = kFALSE;

   Bool_t firstCommentLine = kTRUE;
   Bool_t extractComments = kFALSE;
   Bool_t thisLineIsCommented = kFALSE;
   Bool_t thisLineIsPpLine = kFALSE;

   // for Doc++ style
   Bool_t useDocxxStyle =
       (strcmp(gEnv->GetValue("Root.Html.DescriptionStyle", ""), "Doc++")
        == 0);
   Bool_t postponeMemberDescr = kFALSE;
   Bool_t skipMemberName = kFALSE;
   Bool_t writeBracket = kFALSE;
   streampos postponedpos = 0;

   // Class Description Title
   out << "<hr>" << endl;
   out << "<!--DESCRIPTION-->";
   out << "<h2><a name="" << classPtr->GetName();
   out << ":description">Class Description</a></h2>" << endl;

   // open source file
   ifstream sourceFile;
   if (realFilename) 
      sourceFile.open(realFilename, ios::in

   if (realFilename && sourceFile.good()) {
      // open a .cxx.html file
      tmp1 = gSystem->ExpandPathName(fOutputDir);
      char *tmp2 = gSystem->ConcatFileName(tmp1, "src");
      char *dirname = StrDup(tmp2);

      if (tmp1)
         delete[]tmp1;
      if (tmp2)
         delete[]tmp2;
      tmp1 = tmp2 = 0;

      // create directory if necessary
      if (gSystem->AccessPathName(dirname))
         gSystem->MakeDirectory(dirname);

      char classname[1024];
      strcpy(classname, classPtr->GetName());
      NameSpace2FileName(classname);
      tmp1 = gSystem->ConcatFileName(dirname, classname);
      filename = StrDup(tmp1, 16);
      strcat(filename, ".cxx.html");

ofstream tempFile;
      tempFile.open(filename, ios::out

      if (dirname)
         delete[]dirname;

      if (tmp1)
         delete[]tmp1;
      tmp1 = 0;

      if (tempFile.good()) {


         // create an array of method names
         Int_t i = 0;
         TMethod *method;
         TIter nextMethod(classPtr->GetListOfMethods());
         Int_t numberOfMethods = classPtr->GetNmethods();
         const char **methodNames = new const char *[2 * numberOfMethods];
         while ((method = (TMethod *) nextMethod())) {
            methodNames[2 * i] = method->GetName();
            methodNames[2 * i + 1] = (const char *) method;
            i++;
         }


         // write a HTML header
         char *sourceTitle = StrDup(classPtr->GetName(), 16);
         strcat(sourceTitle, " - source file");
         WriteHtmlHeader(tempFile, sourceTitle);
         if (sourceTitle)
            delete[]sourceTitle;


         tempFile << "<pre>" << endl;

         while (!sourceFile.eof()) {

            sourceFile.getline(fLine, fLen - 1);
            if (sourceFile.eof())
               break;


            // set start & end of the line
            if (!fLine) {
               fLine = (char *) " ";
               Warning("ClassDescription", "found an empty line");
            }
            char *startOfLine = fLine;
            char *endOfLine = fLine + strlen(fLine) - 1;


            // remove leading spaces
            while (isspace(*startOfLine))
               startOfLine++;

            // remove trailing spaces
            while (isspace(*endOfLine))
               endOfLine--;
            if (*startOfLine == '#' && !tempFlag)
               thisLineIsPpLine = kTRUE;

            // if this line is a comment line
            else if (!strncmp(startOfLine, "//", 2)) {

               thisLineIsCommented = kTRUE;
               thisLineIsPpLine = kFALSE;

               // remove a repeating characters from the end of the line
               while ((*endOfLine == *startOfLine) &&
                      (endOfLine >= startOfLine))
                  endOfLine--;
               endOfLine++;
               char tempChar = *endOfLine;
               *endOfLine = 0;


               if (extractComments) {
                  if (firstCommentLine) {
                     out << "<pre>";
                     firstCommentLine = kFALSE;
                  }
                  if (endOfLine >= startOfLine + 2)
                     ExpandKeywords(out, startOfLine + 2, classPtr, flag);
                  out << endl;
               }

               *endOfLine = tempChar;

               // if line is composed of the same characters
               if ((endOfLine == startOfLine) && *(startOfLine + 2)
                   && classDescription) {
                  extractComments = kTRUE;
                  classDescription = kFALSE;
               }
            } else {
               thisLineIsCommented = kFALSE;
               if (flag) {
                  ExpandKeywords(out, fLine, classPtr, flag);
                  out<<endl;
               } else {
                  extractComments = kFALSE;
                  if (!firstCommentLine) {
                     out << "</pre>";
                     firstCommentLine = kTRUE;
                  }
               }
            }


            // if NOT member function
            key = strstr(fLine, pattern);
            if (!key) {
               // check for a lastUpdate string
               if (!foundLastUpdate && lastUpdateStr) {
                  if (!strncmp
                      (fLine, lastUpdateStr, strlen(lastUpdateStr))) {
                     strcpy(lastUpdate, fLine + strlen(lastUpdateStr));
                     foundLastUpdate = kTRUE;
                  }
               }
               // check for an author string
               if (!foundAuthor && authorStr) {
                  if (!strncmp(fLine, authorStr, strlen(authorStr))) {
                     strcpy(author, fLine + strlen(authorStr));
                     foundAuthor = kTRUE;
                  }
               }
               // check for a copyright string
               if (!foundCopyright && copyrightStr) {
                  if (!strncmp(fLine, copyrightStr, strlen(copyrightStr))) {
                     strcpy(copyright, fLine + strlen(copyrightStr));
                     foundCopyright = kTRUE;
                  }
               }
               // check for a description comments
               if (descriptionStr
                   && !strncmp(fLine, descriptionStr,
                               strlen(descriptionStr))) {
                  if (classDescription) {
                     // write description out
                     classDescription = kFALSE;
                     extractComments = kTRUE;
                  }
                  // for Doc++ style
                  else if (useDocxxStyle) {
                     postponeMemberDescr = kTRUE;
                     postponedpos = sourceFile.tellg();
                  }
               }
            } else {
               Bool_t found = kFALSE;
               // find method name
               char *funcName = key + len;
               char *tmpNameSpace = 0;
               // if we have a namespace check wether the method is given as namespace::class::method
               if (nameSpace != 0) {
                  tmpNameSpace = fLine;
                  while (tmpNameSpace < key
                         && 0 != (tmpNameSpace =
                                  (strstr
                                   (&fLine[tmpNameSpace - fLine],
                                    classPtr->GetName()))))
                     if (key - tmpNameSpace ==
                         (int) strlen(classPtr->GetName()) - (len - 2))
                        key = tmpNameSpace;
                     else
                        tmpNameSpace++;
               }

               while (*funcName && isspace(*funcName))
                  funcName++;
               char *nameEndPtr = funcName;

               // In case of destructor
               if (*nameEndPtr == '~')
                  nameEndPtr++;

               while (*nameEndPtr && IsName(*nameEndPtr))
                  nameEndPtr++;

               char c1 = *nameEndPtr;
               char pe = 0;

               char *params, *paramsEnd;
               params = nameEndPtr;
               paramsEnd = 0;

               while (*params && isspace(*params))
                  params++;
               if (*params != '(')
                  params = 0;
               else
                  params++;
               paramsEnd = params;

               // if signature exist, try to find the ending character
               if (paramsEnd) {
                  Int_t count = 1;
                  while (*paramsEnd) {
                     if (*paramsEnd == '(')
                        count++;
                     if (*paramsEnd == ')')
                        if (!--count)
                           break;
                     paramsEnd++;
                  }
                  pe = *paramsEnd;
                  *paramsEnd = 0;
               }
               *nameEndPtr = 0;

               // get method
               TMethod *method;
               method = classPtr->GetMethodAny(funcName);

               // restore characters
               if (paramsEnd)
                  *paramsEnd = pe;
               if (nameEndPtr)
                  *nameEndPtr = c1;

               if (method) {
                  // for Doc++Style
                  if (useDocxxStyle && skipMemberName) {
                     skipMemberName = kFALSE;
                     writeBracket = kTRUE;
                     sourceFile.seekg(postponedpos);
                  } else {

                     char *typeEnd = 0;
                     char c2 = 0;

                     found = kFALSE;

                     // try to get type
                     typeEnd = key - 1;
                     while ((typeEnd > fLine)
                            && (isspace(*typeEnd) || *typeEnd == '*'
                                || *typeEnd == '&'))
                        typeEnd--;
                     typeEnd++;
                     c2 = *typeEnd;
                     *typeEnd = 0;
                     char *type = typeEnd - 1;
                     while (IsName(*type) && (type > fLine))
                        type--;
                     if (*type == ':' && (type - 1 > fLine)
                         && *(type - 1) == ':') {
                        // found a namespace
                        type--;
                        type--;
                        while (IsName(*type) && (type > fLine))
                           type--;
                     }
                     if (!IsWord(*type))
                        type++;

                     while ((type > fLine) && isspace(*(type - 1)))
                        type--;
                     if (type > fLine) {
                        if (!strncmp(type - 5, "const", 5))
                           found = kTRUE;
                        else
                           found = kFALSE;
                     } else if (type == fLine)
                        found = kTRUE;

                     if (!strcmp(type, "void") && (*funcName == '~'))
                        found = kTRUE;

                     *typeEnd = c2;

                     if (found) {
                        ptr = strchr(nameEndPtr, '{');
                        char *semicolon = strchr(nameEndPtr, ';');
                        if (semicolon)
                           if (!ptr || (semicolon < ptr))
                              found = kFALSE;

                        if (!ptr && found) {
                           found = kFALSE;
                           while (sourceFile.getline(nextLine, 255)
                                  && fLine && nextLine
                                  && (strlen(fLine) <
                                      (fLen - strlen(nextLine)))) {
                              strcat(fLine, "n");
                              strcat(fLine, nextLine);
                              if ((ptr = strchr(fLine, '{'))) {
                                 found = kTRUE;
                                 *ptr = 0;
                                 break;
                              }
                           }
                        } else if (ptr)
                           *ptr = 0;

                        if (found) {
                           char *colonPtr = strrchr(fLine, ':');
                           if (colonPtr > funcName)
                              *colonPtr = 0;
                           if (found) {
                              out << "<hr>" << endl;
                              out << "<!--FUNCTION-->";
                              if (typeEnd) {
                                 c2 = *typeEnd;
                                 *typeEnd = 0;
                                 ExpandKeywords(out, fLine, classPtr,
                                                flag);
                                 *typeEnd = c2;
                                 while (typeEnd < key) {
                                    if (*typeEnd == '*' || *typeEnd == '&')
                                       out << *typeEnd;
                                    typeEnd++;
                                 }
                              }
                              *nameEndPtr = 0;

                              char *cClassFileName =
                                  StrDup(classPtr->GetName());
                              NameSpace2FileName(cClassFileName);
                              out << " <a name="" << classPtr->
                                  GetName() << ":";
                              out << funcName << "" href="src/";
                              out << cClassFileName << ".cxx.html#" <<
                                  classPtr->GetName() << ":";
                              ReplaceSpecialChars(out, funcName);
                              out << "">";
                              ReplaceSpecialChars(out, funcName);
                              out << "</a>";

                              if (cClassFileName != 0)
                                 delete[]cClassFileName;

                              tempFile << "<a name="" << classPtr->
                                  GetName() << ":";
                              ReplaceSpecialChars(tempFile, funcName);
                              tempFile << ""> </a>";

                              // remove this method name from the list of methods
                              i = 0;
                              while (i < numberOfMethods) {
                                 const char *mptr = methodNames[2 * i];
                                 if (mptr) {
                                    while (*mptr == '*')
                                       mptr++;
                                    if (!strcmp(mptr, funcName)) {
                                       methodNames[2 * i] = 0;
                                       break;
                                    }
                                 }
                                 i++;
                              }

                              *nameEndPtr = c1;
                              if (colonPtr)
                                 *colonPtr = ':';
                              ExpandKeywords(out, nameEndPtr, classPtr,
                                             flag);
                              out << "<br>" << endl;

                              // for Doc++ Style
                              if (useDocxxStyle && postponeMemberDescr) {
                                 streampos pos = sourceFile.tellg();
                                 sourceFile.seekg(postponedpos);
                                 postponedpos = pos;
                                 skipMemberName = kTRUE;
                                 postponeMemberDescr = kFALSE;
                              }
                              extractComments = kTRUE;
                           }
                        }
                        if (ptr)
                           *ptr = '{';
                     }
                  }             // if useDocxxStyle
               }
            }

            // write to '.cxx.html' file
            // for Doc++ Style - if enabled, check if skipMemberName is set
            if (!useDocxxStyle || !skipMemberName) {
               if (thisLineIsPpLine)
                  ExpandPpLine(tempFile, fLine);
               else {
                  if (thisLineIsCommented)
                     tempFile << "<b>";
                  ExpandKeywords(tempFile, fLine, classPtr, tempFlag,
                                 "../");
                  if (thisLineIsCommented)
                     tempFile << "</b>";
               }
               tempFile << endl;

               if (useDocxxStyle && writeBracket) {
                  writeBracket = kFALSE;
                  tempFile << "{" << endl;
               }
            }
         }
         tempFile << "</pre>" << endl;

         // do some checking
         Bool_t inlineFunc = kFALSE;
         i = 0;
         while (i++ < numberOfMethods) {
            if (methodNames[2 * i]) {
               inlineFunc = kTRUE;
               break;
            }
         }


         if (inlineFunc) {
            out << "<br><br><br>" << endl;
            out << "<h3>Inline Functions</h3>" << endl;
            out << "<hr>" << endl;
            out << "<pre>" << endl;

            Int_t maxlen = 0, len = 0;
            for (i = 0; i < numberOfMethods; i++) {
               if (methodNames[2 * i]) {
                  method = (TMethod *) methodNames[2 * i + 1];
                  if (method->GetReturnTypeName())
                     len = strlen(method->GetReturnTypeName());
                  else
                     len = 0;
                  maxlen = len > maxlen ? len : maxlen;
               }
            }


            // write out an inline functions
            for (i = 0; i < numberOfMethods; i++) {
               if (methodNames[2 * i]) {

                  method = (TMethod *) methodNames[2 * i + 1];

                  if (method) {

                     if (!strcmp(method->GetName(), "Dictionary") ||
                         !strcmp(method->GetName(), "Class_Version") ||
                         !strcmp(method->GetName(), "Class_Name") ||
                         !strcmp(method->GetName(), "DeclFileName") ||
                         !strcmp(method->GetName(), "DeclFileLine") ||
                         !strcmp(method->GetName(), "ImplFileName") ||
                         !strcmp(method->GetName(), "ImplFileLine")
                         )
                        continue;

                     out << "<!--INLINE FUNCTION-->";
                     if (method->GetReturnTypeName())
                        len = strlen(method->GetReturnTypeName());
                     else
                        len = 0;

                     out << "<!--TAB6-->      ";
                     while (len++ < maxlen + 2)
                        out << " ";

                     char *tmpstr = StrDup(method->GetReturnTypeName());
                     if (tmpstr) {
                        ExpandKeywords(out, tmpstr, classPtr, flag);
                        delete[]tmpstr;
                     }

                     out << " <a name="" << classPtr->GetName();
                     out << ":" << method->GetName() << "" href="";
                     out << GetFileName(classPtr->
                                        GetDeclFileName()) << "">";
                     out << method->GetName() << "</a>";

                     strcpy(fLine, method->GetSignature());
                     ExpandKeywords(out, fLine, classPtr, flag);
                     out << endl;
                  }
               }
            }
            out << "</pre>" << endl;
         }

         // write tempFile footer
         WriteHtmlFooter(tempFile, "../");

         // close a temp file
         tempFile.close();

         delete[]methodNames;
      } else
         Error("MakeClass", "Can't open file '%s' !", filename);

      // close a source file
      sourceFile.close();

   } else
      if (realFilename) 
         Error("Make", "Can't open file '%s' !", realFilename);


   // write classFile footer
   TDatime date;
   WriteHtmlFooter(out, "",
                   (strlen(lastUpdate) ==
                    0 ? date.AsString() : lastUpdate), author, copyright);

   // free memory
   if (nextLine)
      delete[]nextLine;
   if (pattern)
      delete[]pattern;

   if (lastUpdate)
      delete[]lastUpdate;
   if (author)
      delete[]author;
   if (copyright)
      delete[]copyright;

   if (realFilename)
      delete[]realFilename;
   if (filename)
      delete[]filename;
}


//______________________________________________________________________________
 void THtml::ClassTree(TVirtualPad * psCanvas, TClass * classPtr,
                      Bool_t force)
{
// It makes a class tree
//
//
// Input: psCanvas - pointer to the current canvas
//        classPtr - pointer to the class
//

   if (psCanvas && classPtr) {
      char classname[1024];
      strcpy(classname, classPtr->GetName());
      NameSpace2FileName(classname);

      char *tmp1 =
          gSystem->ConcatFileName(gSystem->ExpandPathName(fOutputDir),
                                  classname);
      char *filename = StrDup(tmp1, 16);


      strcat(filename, "_Tree.ps");

      if (tmp1)
         delete[]tmp1;
      tmp1 = 0;

      if (IsModified(classPtr, kTree) || force) {
	 // TCanvas already prints ps being saved
         // Printf(formatStr, "", "", filename);
         classPtr->Draw("same");
         psCanvas->SaveAs(filename);
      } else
         Printf(formatStr, "-no change-", "", filename);

      if (filename)
         delete[]filename;
   }
}


//______________________________________________________________________________
 void THtml::Convert(const char *filename, const char *title,
                    const char *dirname)
{
// It converts a single text file to HTML
//
//
// Input: filename - name of the file to convert
//        title    - title which will be placed at the top of the HTML file
//        dirname  - optional parameter, if it's not specified, output will
//                   be placed in html/examples directory.
//
//  NOTE: Output file name is the same as filename, but with extension .html
//

   const char *dir;
   char *ptr;

   Bool_t isCommentedLine = kFALSE;
   Bool_t tempFlag = kFALSE;

   // if it's not defined, make the "examples" as a default directory
   if (!*dirname) {
      dir =
          gSystem->ConcatFileName(gSystem->ExpandPathName(fOutputDir),
                                  "examples");

      // create directory if necessary
      if (gSystem->AccessPathName(dir))
         gSystem->MakeDirectory(dir);
   } else
      dir = dirname;


   // find a file
   char *realFilename =
       gSystem->Which(fSourceDir, filename, kReadPermission);

   if (realFilename) {

      // open source file
      ifstream sourceFile;
      sourceFile.open(realFilename, ios::in

      delete[]realFilename;
      realFilename = 0;

      if (sourceFile.good()) {

         // open temp file with extension '.html'
         if (!gSystem->AccessPathName(dir)) {
            char *tmp1 =
                gSystem->ConcatFileName(dir, GetFileName(filename));
            char *htmlFilename = StrDup(tmp1, 16);
            strcat(htmlFilename, ".html");

            if (tmp1)
               delete[]tmp1;
            tmp1 = 0;

ofstream tempFile;
            tempFile.open(htmlFilename, ios::out

            if (tempFile.good()) {

               Printf("Convert: %s", htmlFilename);

               // write a HTML header
               WriteHtmlHeader(tempFile, title);

               tempFile << "<h1>" << title << "</h1>" << endl;
               tempFile << "<pre>" << endl;

               while (!sourceFile.eof()) {
                  sourceFile.getline(fLine, fLen - 1);
                  if (sourceFile.eof())
                     break;


                  // remove leading spaces
                  ptr = fLine;
                  while (isspace(*ptr))
                     ptr++;


                  // check for a commented line
                  if (!strncmp(ptr, "//", 2))
                     isCommentedLine = kTRUE;
                  else
                     isCommentedLine = kFALSE;


                  // write to a '.html' file
                  if (isCommentedLine)
                     tempFile << "<b>";
                  gROOT->GetListOfGlobals(kTRUE);	// force update of this list
                  ExpandKeywords(tempFile, fLine, 0, tempFlag, "../");
                  if (isCommentedLine)
                     tempFile << "</b>";
                  tempFile << endl;
               }
               tempFile << "</pre>" << endl;


               // write a HTML footer
               WriteHtmlFooter(tempFile, "../");


               // close a temp file
               tempFile.close();

            } else
               Error("Convert", "Can't open file '%s' !", htmlFilename);

            // close a source file
            sourceFile.close();
            if (htmlFilename)
               delete[]htmlFilename;
            htmlFilename = 0;
         } else
            Error("Convert",
                  "Directory '%s' doesn't exist, or it's write protected !",
                  dir);
      } else
         Error("Convert", "Can't open file '%s' !", realFilename);
   } else
      Error("Convert", "Can't find file '%s' !", filename);
}


//______________________________________________________________________________
 Bool_t THtml::CopyHtmlFile(const char *sourceName, const char *destName)
{
// Copy file to HTML directory
//
//
//  Input: sourceName - source file name
//         destName   - optional destination name, if not
//                      specified it would be the same
//                      as the source file name
//
// Output: TRUE if file is successfully copied, or
//         FALSE if it's not
//
//
//   NOTE: The destination directory is always fOutputDir
//

   Bool_t ret = kFALSE;
   Int_t check = 0;

   // source file name
   char *tmp1 = gSystem->Which(fSourceDir, sourceName, kReadPermission);
   char *sourceFile = StrDup(tmp1, 16);

   if (tmp1)
      delete[]tmp1;
   tmp1 = 0;

   if (sourceFile) {

      // destination file name
      char *tmpstr = 0;
      if (!*destName)
         tmpstr = StrDup(GetFileName(sourceFile), 16);
      else
         tmpstr = StrDup(GetFileName(destName), 16);
      destName = tmpstr;

      tmp1 =
          gSystem->ConcatFileName(gSystem->ExpandPathName(fOutputDir),
                                  destName);
      char *filename = StrDup(tmp1, 16);

      if (tmp1)
         delete[]tmp1;
      tmp1 = 0;

      // Get info about a file
      Long_t sId, sSize, sFlags, sModtime;
      Long_t dId, dSize, dFlags, dModtime;
      if (!
          (check =
           gSystem->GetPathInfo(sourceFile, &sId, &sSize, &sFlags,
                                &sModtime)))
         check =
             gSystem->GetPathInfo(filename, &dId, &dSize, &dFlags,
                                  &dModtime);


      if ((sModtime != dModtime) || check) {

         char *cmd = new char[256];

#ifdef R__UNIX
         strcpy(cmd, "/bin/cp ");
         strcat(cmd, sourceFile);
         strcat(cmd, " ");
         strcat(cmd, filename);
#endif

#ifdef WIN32
         strcpy(cmd, "copy \"");
         strcat(cmd, sourceFile);
         strcat(cmd, "\" \"");
         strcat(cmd, filename);
         strcat(cmd, "\"");
         char *bptr = 0;
         while (bptr = strchr(cmd, '/'))
            *bptr = '\\';
#endif

         ret = !gSystem->Exec(cmd);

         delete[]cmd;
         delete[]filename;
         delete[]tmpstr;
         delete[]sourceFile;
      }
   } else
      Error("Copy", "Can't copy file '%s' to '%s' directory !", sourceName,
            fOutputDir);

   return (ret);
}



//______________________________________________________________________________
 void THtml::CreateIndex(const char **classNames, Int_t numberOfClasses)
{
// Create an index
//
//
// Input: classNames      - pointer to an array of class names
//        numberOfClasses - number of elements
//

   Int_t i, len, maxLen = 0;

   char *tmp1 =
       gSystem->ConcatFileName(gSystem->ExpandPathName(fOutputDir),
                               "ClassIndex.html");
   char *filename = StrDup(tmp1);

   if (tmp1)
      delete[]tmp1;
   tmp1 = 0;

   // open indexFile file
ofstream indexFile;
   indexFile.open(filename, ios::out

   for (i = 0; i < numberOfClasses; i++) {
      len = strlen(classNames[i]);
      maxLen = maxLen > len ? maxLen : len;
   }

   if (indexFile.good()) {

      Printf(formatStr, "", fCounter, filename);

      // write indexFile header
      WriteHtmlHeader(indexFile, "Class Index");
      indexFile << "<h1>Index</h1>" << endl;

      // check for a search engine
      const char *searchEngine =
          gEnv->GetValue("Root.Html.SearchEngine", "");

      // if exists ...
      if (*searchEngine) {

         // create link to search engine page
         indexFile << "<h2><a href="" << searchEngine
             << "">Search the Class Reference Guide</a></h2>" << endl;

      }

      indexFile << "<hr>" << endl;
      indexFile << "<ul>" << endl;

      // loop on all classes
      for (i = 0; i < numberOfClasses; i++) {

         // get class
         TClass *classPtr = GetClass((const char *) classNames[i]);

         indexFile << "<li><tt>";
         char *htmlFile = GetHtmlFileName(classPtr);
         if (htmlFile) {
            indexFile << "<a name="";
            indexFile << classNames[i];
            indexFile << "" href="";
            indexFile << htmlFile;
            indexFile << "">";
            ReplaceSpecialChars(indexFile, classNames[i]);
            indexFile << "</a> ";
            delete[]htmlFile;
            htmlFile = 0;
         } else
            ReplaceSpecialChars(indexFile, classNames[i]);


         // write title
         len = strlen(classNames[i]);
         for (Int_t w = 0; w < (maxLen - len + 2); w++)
            indexFile << ".";
         indexFile << " ";

         indexFile << "<a name="Title:";
         indexFile << classPtr->GetName();
         indexFile << "">";
         ReplaceSpecialChars(indexFile, classPtr->GetTitle());
         indexFile << "</a></tt>" << endl;
      }

      indexFile << "</ul>" << endl;

      // write indexFile footer
      TDatime date;
      WriteHtmlFooter(indexFile, "", date.AsString());


      // close file
      indexFile.close();

   } else
      Error("MakeIndex", "Can't open file '%s' !", filename);

   if (filename)
      delete[]filename;
}


//______________________________________________________________________________
 void THtml::CreateIndexByTopic(char **fileNames, Int_t numberOfNames,
                               Int_t maxLen)
{
// It creates several index files
//
//
// Input: fileNames     - pointer to an array of file names
//        numberOfNames - number of elements in the fileNames array
//        maxLen        - maximum length of a single name
//

ofstream outputFile;
   char *filename = 0;
   Int_t i;

   for (i = 0; i < numberOfNames; i++) {
      if (!filename) {

         // create a filename
         char *tmp1 =
             gSystem->ConcatFileName(gSystem->ExpandPathName(fOutputDir),
                                     fileNames[i]);
         filename = StrDup(tmp1, 16);

         if (tmp1)
            delete[]tmp1;
         tmp1 = 0;

         // look for first _ in basename
         char *underlinePtr = strrchr(filename, '/');
         if (!underlinePtr) 
            underlinePtr=strchr(filename,'_');
            else underlinePtr=strchr(underlinePtr,'_');
         *underlinePtr = 0;

	 char htmltitle[1024];
	 strcpy(htmltitle, "Index of ");
	 strcat(htmltitle, GetFileName(filename));
	 strcat(htmltitle, " classes");
         
	 strcat(filename, "_Index.html");

         // open a file
         outputFile.open(filename, ios::out

         // check if it's OK
         if (outputFile.good()) {

            Printf(formatStr, "", fCounter, filename);

            // write outputFile header
            WriteHtmlHeader(outputFile, htmltitle);
            outputFile << "<h2>" << htmltitle << "</h2><hr>" << endl;
            outputFile << "<ul>" << endl;
         } else
            Error("MakeIndex", "Can't open file '%s' !", filename);
         delete[]filename;
      }
      // get a class
      char *classname = strrchr(fileNames[i], '/');
      if (!classname) 
         classname=strchr(fileNames[i],'_');
         else classname=strchr(classname,'_');
      TClass *classPtr =
          GetClass((const char *) classname + 1);
      if (classPtr) {

         // write a classname to an index file
         outputFile << "<li><tt>";

         char *htmlFile = GetHtmlFileName(classPtr);

         if (htmlFile) {
            outputFile << "<a name="";
            outputFile << classPtr->GetName();
            outputFile << "" href="";
            outputFile << htmlFile;
            outputFile << "">";
            ReplaceSpecialChars(outputFile, classPtr->GetName());
            outputFile << "</a> ";
            delete[]htmlFile;
            htmlFile = 0;
         } else
            ReplaceSpecialChars(outputFile, classPtr->GetName());


         // write title
         Int_t len = strlen(classPtr->GetName());
         for (Int_t w = 0; w < maxLen - len; w++)
            outputFile << ".";
         outputFile << " ";

         outputFile << "<a name="Title:";
         outputFile << classPtr->GetName();
         outputFile << "">";
         ReplaceSpecialChars(outputFile, classPtr->GetTitle());
         outputFile << "</a></tt>" << endl;
      } else
         Error("MakeIndex", "Unknown class '%s' !",
               strchr(fileNames[i], '_') + 1);


      // first base name
      // look for first _ in basename
      char *first = strrchr(fileNames[i], '/');
      if (!first) 
         first=strchr(fileNames[i],'_');
         else first=strchr(first,'_');
      if (first) 
         *first = 0;

      // second base name
      char *second = 0;
      if (i < (numberOfNames - 1)) {
         second = strrchr(fileNames[i + 1], '/');
         if (!second) 
            second=strchr(fileNames[i + 1],'_');
            else second=strchr(second,'_');
         if (second)
            *second = 0;
      }
      // check and close the file if necessary
      if (!first || !second || strcmp(fileNames[i], fileNames[i + 1])) {

         if (outputFile.good()) {

            outputFile << "</ul>" << endl;

            // write outputFile footer
            TDatime date;
            WriteHtmlFooter(outputFile, "", date.AsString());

            // close file
            outputFile.close();

            filename = 0;
         } else
            Error("MakeIndex", "Corrupted file '%s' !", filename);
      }

      if (first)
         *first = '_';
      if (second)
         *second = '_';
   }

   // free memory
   for (i = 0; i < numberOfNames; i++)
      if (*fileNames[i])
         delete[]fileNames[i];
}


//______________________________________________________________________________
 void THtml::CreateListOfTypes()
{
// Create list of all data types

   Int_t maxLen = 0;
   Int_t len;

   // open file
ofstream typesList;

   char *outFile =
       gSystem->ConcatFileName(gSystem->ExpandPathName(fOutputDir),
                               "ListOfTypes.html");
   typesList.open(outFile, ios::out


   if (typesList.good()) {
      Printf(formatStr, "", "", outFile);

      // write typesList header
      WriteHtmlHeader(typesList, "List of data types");
      typesList << "<h2> List of data types </h2><hr>" << endl;

      typesList << "<dl><dd>" << endl;
      typesList << "<pre>" << endl;

      // make loop on data types
      TDataType *type;
      TIter nextType(gROOT->GetListOfTypes());

      while ((type = (TDataType *) nextType())) {
	 // no templates ('<' and '>'), no idea why the '(' is in here...
         if (*type->GetTitle() && !strchr(type->GetName(), '(') 
	     && !( strchr(type->GetName(), '<') && strchr(type->GetName(),'>'))){
            if (type->GetName())
               len = strlen(type->GetName());
            else
               len = 0;
            maxLen = maxLen > len ? maxLen : len;
         }
      }
      nextType.Reset();

      maxLen += kSpaceNum;

      while ((type = (TDataType *) nextType())) {
         if (*type->GetTitle() && !strchr(type->GetName(), '(')
	     && !( strchr(type->GetName(), '<') && strchr(type->GetName(),'>'))){
            typesList << "<b><a name="";
            ReplaceSpecialChars(typesList, type->GetName());
            typesList << "">";
            ReplaceSpecialChars(typesList, type->GetName());
            typesList << "</a></b>";

            if (type->GetName())
               len = strlen(type->GetName());
            else
               len = 0;
            typesList << " ";
            for (Int_t j = 0; j < (maxLen - len); j++)
               typesList << ".";
            typesList << " ";

            typesList << "<a name="Title:";
            ReplaceSpecialChars(typesList, type->GetTitle());
            typesList << "">";
            ReplaceSpecialChars(typesList, type->GetTitle());
            typesList << "</a>" << endl;
         }
      }

      typesList << "</pre>" << endl;
      typesList << "</dl>" << endl;

      // write typesList footer
      TDatime date;
      WriteHtmlFooter(typesList, "", date.AsString());

      // close file
      typesList.close();

   } else
      Error("Make", "Can't open file '%s' !", outFile);

   if (outFile)
      delete[]outFile;
}


//______________________________________________________________________________
 void THtml::DerivedClasses(ofstream & out, TClass * classPtr)
{
// It creates a list of derived classes
//
//
// Input: out      - output file stream
//        classPtr - pointer to the class
//

   Bool_t first = kTRUE;
   Bool_t found = kFALSE;


   // get total number of classes
   Int_t numberOfClasses = gClassTable->Classes();

   // start from begining
   gClassTable->Init();

   // get class names
   TClass *derivedClassPtr;
   const char *derivedClassName;
   for (Int_t i = 0; i < numberOfClasses; i++) {

      // get class name
      derivedClassName = gClassTable->Next();

      // get class pointer
      derivedClassPtr = GetClass(derivedClassName);

      if (!derivedClassPtr) {
         Warning("DerivedClasses",
                 "Can not find a definition for class <%s>",
                 derivedClassName);
         continue;
      }
      // make a loop on base classes
      TBaseClass *inheritFrom;
      TIter nextBase(derivedClassPtr->GetListOfBases());

      while ((inheritFrom = (TBaseClass *) nextBase())) {
         if (!strcmp(inheritFrom->GetName(), classPtr->GetName())) {
            if (first) {
               out << "<br><hr>" << endl;
               out << "<!--SEE ALSO-->";
               out << "<h2>See also</h2><dl><dd>" << endl;
            }
            if (!first)
               out << ", ";

            char *htmlFile = GetHtmlFileName(derivedClassPtr);

            if (htmlFile) {
               out << "<a href="";
               out << htmlFile;
               out << "">";
               ReplaceSpecialChars(out, derivedClassPtr->GetName());
               out << "</a>";
               delete[]htmlFile;
               htmlFile = 0;
            } else
               ReplaceSpecialChars(out, derivedClassPtr->GetName());

            if (first) {
               first = kFALSE;
               found = kTRUE;
            }
         }
      }
   }
   if (found)
      out << "</dl>" << endl;
}


//______________________________________________________________________________
 void THtml::ExpandKeywords(ofstream & out, char *text, TClass * ptr2class,
                           Bool_t & flag, const char *dir)
{
// Find keywords in text & create URLs
//
//
// Input: out       - output file stream
//        text      - pointer to the array of the characters to process
//        ptr2class - pointer to the class
//        flag      - this is a 'html_begin/html_end' flag
//        dir       - usually "" or "../", depends of current file
//                    directory position
//

   char *keyword = text;
   char *end;
   char *funcName;
   char *funcNameEnd;
   char *funcSig;
   char *funcSigEnd;
   char c, c2, c3;
   char *tempEndPtr;
   c2 = c3 = 0;

   Bool_t hide;
   Bool_t mmf = 0;
   static Bool_t pre_is_open = kFALSE;

   Int_t ichar=0;

   do {
      tempEndPtr = end = funcName = funcNameEnd = funcSig = funcSigEnd = 0;

      hide = kFALSE;

      // skip until start of the word
      while (!IsWord(*keyword) && *keyword){
         if (!flag)
            ReplaceSpecialChars(out, *keyword);
         else
            // protect html code from special chars
            if ((unsigned char)*keyword>31)
               if (*keyword=='<'){
                  if (!strcasecmp(keyword,"<pre>"))
                     if (pre_is_open) keyword+=4;
                     else {
                        pre_is_open = kTRUE;
                        out << *keyword;
                        for (ichar=0; ichar<4; ichar++) {
                           keyword++;
                           out << *keyword;
                        }
                     }
                  else
                  if (!strcasecmp(keyword,"</pre>"))
                     if (!pre_is_open) keyword+=5;
                     else {
                        pre_is_open = kFALSE;
                        out << *keyword;
                        for (ichar=0; ichar<5; ichar++) {
                           keyword++;
                           out << *keyword;
                        }
                     }
                  else
                     out << *keyword;
               }
               else
                  out << *keyword;
         keyword++;
      }

      // get end of the word
      end = keyword;
      while (IsName(*end) && *end)
         end++;

      // check wether we have a namespace::class here (2x if to make sure we don't end up in no man's land
      if (*end == ':')
         if (*(end + 1) == ':') {
            char *keywordTmp = StrDup(keyword);
            char *endNameSpace = keywordTmp + (end - keyword) + 2;
            while (IsName(*endNameSpace) && *endNameSpace)
               endNameSpace++;
            *endNameSpace = 0;
            if (GetClass((const char *) keywordTmp) != 0)
               end = keyword + (endNameSpace - keywordTmp);
            if (keywordTmp != 0)
               delete[]keywordTmp;
         }
      // put '0' at the end of the keyword
      c = *end;
      *end = 0;

      if (strlen(keyword) > 50) {
         out << keyword;
         *end = c;
         keyword = end;
         continue;
      }
      // check if this is a HTML block
      if (flag) {
         if (!strcasecmp(keyword, "end_html") && *(keyword - 1) != '"') {
            flag = kFALSE;
            hide = kTRUE;
            pre_is_open = kFALSE;
         }
      } else {
         if (!strcasecmp(keyword, "begin_html") && *(keyword - 1) != '"') {
            flag = kTRUE;
            hide = kTRUE;
            pre_is_open = kTRUE;
         } else {
            *end = c;
            tempEndPtr = end;

            // skip leading spaces
            while (*tempEndPtr && isspace(*tempEndPtr))
               tempEndPtr++;


            // check if we have a something like a 'name[arg].name'
            Int_t count = 0;
            if (*tempEndPtr == '[') {
               count++;
               tempEndPtr++;
            }
            // wait until the last ']'
            while (count && *tempEndPtr) {
               switch (*tempEndPtr) {
               case '[':
                  count++;
                  break;
               case ']':
                  count--;
                  break;
               }
               tempEndPtr++;
            }

            if (!strncmp(tempEndPtr, "::", 2)
                || !strncmp(tempEndPtr, "->", 2) || (*tempEndPtr == '.')) {
               funcName = tempEndPtr;

               // skip leading spaces
               while (isspace(*funcName))
                  funcName++;

               // check if we have a '.' or '->'
               if (*tempEndPtr == '.')
                  funcName++;
               else
                  funcName += 2;

               if (!strncmp(tempEndPtr, "::", 2))
                  mmf = kTRUE;
               else
                  mmf = kFALSE;

               // skip leading spaces
               while (*funcName && isspace(*funcName))
                  funcName++;

               // get the end of the word
               if (!IsWord(*funcName))
                  funcName = 0;

               if (funcName) {
                  funcNameEnd = funcName;

                  // find the end of the function name part
                  while (IsName(*funcNameEnd) && *funcNameEnd)
                     funcNameEnd++;
                  c2 = *funcNameEnd;
                  if (!mmf) {

                     // try to find a signature
                     funcSig = funcNameEnd;

                     // skip leading spaces
                     while (*funcSig && isspace(*funcSig))
                        funcSig++;
                     if (*funcSig != '(')
                        funcSig = 0;
                     else
                        funcSig++;
                     funcSigEnd = funcSig;

                     // if signature exist, try to find the ending character
                     if (funcSigEnd) {
                        Int_t count = 1;
                        while (*funcSigEnd) {
                           if (*funcSigEnd == '(')
                              count++;
                           if (*funcSigEnd == ')')
                              if (!--count)
                                 break;
                           funcSigEnd++;
                        }
                        c3 = *funcSigEnd;
                        *funcSigEnd = 0;
                     }
                  }
                  *funcNameEnd = 0;
               }
            }
            *end = 0;
         }
      }

      if (!flag && !hide && *keyword) {

         // get class
         TClass *classPtr = GetClass((const char *) keyword);

         if (classPtr) {

            char *htmlFile = GetHtmlFileName(classPtr);

            if (htmlFile) {
               out << "<a href="";
               if (*dir  
		  && ( strncmp(htmlFile, "http://", 7) 
		       || strncmp(htmlFile, "https://", 8)))
                  out << dir;
               out << htmlFile;

               if (funcName && mmf) {

                  // make a link to the member function
                  out << "#" << classPtr->GetName() << ":";
                  out << funcName;
                  out << "">";
                  out << classPtr->GetName() << "::";
                  out << funcName;
                  out << "</a>";

                  *funcNameEnd = c2;
                  keyword = funcNameEnd;
               } else {
                  // make a link to the class
                  out << "">";
                  out << classPtr->GetName();
                  out << "</a>";

                  keyword = end;
               }
               delete[]htmlFile;
               htmlFile = 0;

            } else {
               out << keyword;
               keyword = end;
            }
            *end = c;
            if (funcName)
               *funcNameEnd = c2;
            if (funcSig)
               *funcSigEnd = c3;
         } else {
            // get data type
            TDataType *type = gROOT->GetType((const char *) keyword);

            if (type) {

               // make a link to the data type
               out << "<a href="";
               if (*dir)
                  out << dir;
               out << "ListOfTypes.html#";
               out << keyword << "">";
               out << keyword << "</a>";

               *end = c;
               keyword = end;
            } else {
               // look for '('
               Bool_t isfunc = ((*tempEndPtr == '(')
                                || c == '(') ? kTRUE : kFALSE;
               if (!isfunc) {
                  char *bptr = tempEndPtr + 1;
                  while (*bptr && isspace(*bptr))
                     bptr++;
                  if (*bptr == '(')
                     isfunc = kTRUE;
               }

               if (isfunc && ptr2class
                   && (ptr2class->GetMethodAny(keyword))) {
                  out << "<a href="#";
                  out << ptr2class->GetName();
                  out << ":" << keyword << "">";
                  out << keyword << "</a>";
                  *end = c;
                  keyword = end;
               } else {
                  const char *anyname = 0;
                  if (strcmp(keyword, "const"))
                     anyname = gROOT->FindObjectClassName(keyword);

                  const char *namePtr = 0;
                  TClass *cl = 0;
                  TClass *cdl = 0;

                  if (anyname) {
                     cl = GetClass(anyname);
                     namePtr = (const char *) anyname;
                     cdl = cl;
                  } else if (ptr2class) {
                     cl = ptr2class->GetBaseDataMember(keyword);
                     if (cl) {
                        namePtr = cl->GetName();
                        TDataMember *member = cl->GetDataMember(keyword);
                        if (member)
                           cdl = GetClass(member->GetTypeName());
                     }
                  }

                  if (cl && strlen(cl->GetDeclFileName()) > 0) {
                     char *htmlFile = GetHtmlFileName(cl);

                     if (htmlFile) {
                        out << "<a href="";
                        if (*dir 
			   && (strncmp(htmlFile, "http://", 7)
			       || strncmp(htmlFile, "https://", 8)))
                           out << dir;
                        out << htmlFile;
                        if (cl->GetDataMember(keyword)) {
                           out << "#" << namePtr << ":";
                           out << keyword;
                        }
                        out << "">";
                        out << keyword;
                        out << "</a>";
                        delete[]htmlFile;
                        htmlFile = 0;
                     } else
                        out << keyword;

                     if (funcName) {
                        char *ptr = end;
                        ptr++;
                        ReplaceSpecialChars(out, c);
                        while (ptr < funcName)
                           ReplaceSpecialChars(out, *ptr++);

                        TMethod *method = 0;
                        if (cdl)
                           method = cdl->GetMethodAny(funcName);
                        if (method) {
                           TClass *cm = method->GetClass();
                           if (cm) {
                              char *htmlFile2 = GetHtmlFileName(cm);
                              if (htmlFile2) {
                                 out << "<a href="";
                                 if (*dir 
                                     && (strncmp(htmlFile2, "http://", 7)
				         || strncmp(htmlFile2, "https://", 8)))
                                    out << dir;
                                 out << htmlFile2;
                                 out << "#" << cm->GetName() << ":";
                                 out << funcName;
                                 out << "">";
                                 out << funcName;
                                 out << "</a>";
                                 delete[]htmlFile2;
                                 htmlFile2 = 0;
                              } else
                                 out << funcName;

                              keyword = funcNameEnd;
                           } else
                              keyword = funcName;
                        } else
                           keyword = funcName;

                        *funcNameEnd = c2;
                        if (funcSig)
                           *funcSigEnd = c3;
                     } else
                        keyword = end;
                     *end = c;
                  } else {
                     if (funcName)
                        *funcNameEnd = c2;
                     if (funcSig)
                        *funcSigEnd = c3;
                     out << keyword;
                     *end = c;
                     keyword = end;
                  }
               }
            }
         }
      } else {
         if (!hide && *keyword)
            out << keyword;
         *end = c;
         keyword = end;
      }
   } while (*keyword);
}


//______________________________________________________________________________
 void THtml::ExpandPpLine(ofstream & out, char *line)
{
// Expand preprocessor statements
//
//
// Input: out  - output file stream
//        line - pointer to the array of characters,
//               usually one line from the source file
//
//  NOTE: Looks for the #include statements and
//        creates link to the corresponding file
//        if such file exists
//

   const char *ptr;
   const char *ptrStart;
   const char *ptrEnd;
   char *fileName;

   Bool_t linkExist = kFALSE;

   ptrEnd = strstr(line, "include");
   if (ptrEnd) {
      ptrEnd += 7;
      if ((ptrStart = strpbrk(ptrEnd, "<""))) {
         ptrStart++;
         ptrEnd = strpbrk(ptrStart, ">"");
         if (ptrEnd) {
            Int_t len = ptrEnd - ptrStart;
            fileName = new char[len + 1];
            strncpy(fileName, ptrStart, len);
            fileName[len]=0;
            char *tmpstr =
                gSystem->Which(fSourceDir, fileName, kReadPermission);
            if (tmpstr) {
               char *realFileName = StrDup(tmpstr);

               if (realFileName) {
                  CopyHtmlFile(realFileName);

                  ptr = line;
                  while (ptr < ptrStart)
                     ReplaceSpecialChars(out, *ptr++);
                  out << "<a href="../" << GetFileName(realFileName) <<
                      "">";
                  out << fileName << "</a>";
                  out << ptrEnd;

                  linkExist = kTRUE;
               }
               if (realFileName)
                  delete[]realFileName;
               if (fileName)
                  delete[]fileName;
               delete[]tmpstr;
            }
         }
      }
   }

   if (!linkExist)
      ReplaceSpecialChars(out, line);
}

//______________________________________________________________________________
 const char *THtml::GetFileName(const char *filename)
{
// It discards any directory information inside filename
//
//
//  Input: filename - pointer to the file name
//
// Output: pointer to the string containing just a file name
//         without any other directory information, i.e.
//         '/usr/root/test.dat' will return 'test.dat'
//

   return (gSystem->BaseName(gSystem->UnixPathName(filename)));
}

//______________________________________________________________________________
 char *THtml::GetSourceFileName(const char *filename)
{
   // Find the source file. If filename contains a path it will be used
   // together with the possible source prefix. If not found we try
   // old algorithm, by stripping off the path and trying to find it in the
   // specified source search path. Returned string must be deleted by the
   // user. In case filename is not found 0 is returned.

   char *tmp1;
#ifdef WIN32
   if (strchr(filename, '/') || strchr(filename, '\\')) {
#else
   if (strchr(filename, '/')) {
#endif
      char *tmp;
      if (strlen(fSourcePrefix) > 0)
         tmp = gSystem->ConcatFileName(fSourcePrefix, filename);
      else
         tmp = StrDup(filename);
      if ((tmp1 = gSystem->Which(fSourceDir, tmp, kReadPermission))) {
         delete[]tmp;
         return tmp1;
      }
      delete[]tmp;
   }

   if ((tmp1 =
        gSystem->Which(fSourceDir, GetFileName(filename),
                       kReadPermission)))
      return tmp1;

   return 0;
}

//______________________________________________________________________________
 char *THtml::GetHtmlFileName(TClass * classPtr)
{
// Return real HTML filename
//
//
//  Input: classPtr - pointer to a class
//
// Output: pointer to the string containing a full name
//         of the corresponding HTML file. The string must be deleted by the user.
//

   char htmlFileName[128];

   char *ret = 0;
   Bool_t found = kFALSE;

   if (classPtr) {

      const char *filename;
      if ( classPtr->GetImplFileLine() )
         filename = classPtr->GetImplFileName();
      else 
         filename = classPtr->GetDeclFileName();

      char varName[80];
      const char *colon = strchr(filename, ':');


      // this should be a prefix
      strcpy(varName, "Root.Html.");


      if (colon)
         strncat(varName, filename, colon - filename);
      else
         strcat(varName, "Root");

      char *tmp;
      if (!(tmp = gSystem->Which(fSourceDir, filename, kReadPermission))) {
         strcpy(htmlFileName, gEnv->GetValue(varName, ""));
         if (!*htmlFileName)
            found = kFALSE;
         else
            found = kTRUE;
      } else {
         strcpy(htmlFileName, ".");
         found = kTRUE;
      }
      delete[]tmp;

      if (found) {
         tmp = StrDup(classPtr->GetName());
         NameSpace2FileName(tmp);
         char *tmp1 = gSystem->ConcatFileName(htmlFileName, tmp);
         ret = StrDup(tmp1, 16);
         strcat(ret, ".html");

         if (tmp)
            delete[]tmp;
         tmp = 0;
         if (tmp1)
            delete[]tmp1;
         tmp1 = 0;
      } else
         ret = 0;

   }

   return ret;
}

//______________________________________________________________________________
 TClass *THtml::GetClass(const char *name1, Bool_t load)
{
//*-*-*-*-*Return pointer to class with name*-*-*-*-*-*-*-*-*-*-*-*-*
//*-*      =================================
   Int_t n = strlen(name1);
   char *name = new char[n + 1];
   strcpy(name, name1);
   char *t = name + n - 1;
   while (*t == ' ') {
      *t = 0;
      if (t == name)
         break;
      t--;
   }
   t = name;
   while (*t == ' ')
      t++;

   TClass *cl = gROOT->GetClass(t, load);
   delete [] name;
   return (cl && cl->GetDeclFileName() && strlen(cl->GetDeclFileName()) ? cl : 0);
}


//______________________________________________________________________________
 Bool_t THtml::IsModified(TClass * classPtr, const Int_t type)
{
// Check if file is modified
//
//
//  Input: classPtr - pointer to the class
//         type     - file type to compare with
//                    values: kSource, kInclude, kTree
//
// Output: TRUE     - if file is modified since last time
//         FALSE    - if file is up to date
//

   Bool_t ret = kTRUE;

   char sourceFile[1024], filename[1024], classname[1024];
   char *strPtr, *strPtr2;

   switch (type) {
   case kSource:
      if (classPtr->GetImplFileLine()) 
         strPtr2 = GetSourceFileName(classPtr->GetImplFileName());
      else 
         strPtr2 = GetSourceFileName(classPtr->GetDeclFileName());
      if (strPtr2)
         strcpy(sourceFile, strPtr2);
      strPtr =
          gSystem->ConcatFileName(gSystem->ExpandPathName(fOutputDir),
                                  "src");
      strcpy(filename, strPtr);
      delete[]strPtr;
      delete[]strPtr2;
#ifdef WIN32
      strcat(filename, "\\");
#else
      strcat(filename, "/");
#endif
      strcpy(classname,classPtr->GetName());
      NameSpace2FileName(classname);
      strcat(filename, classname);
      strcat(filename, ".cxx.html");
      break;

   case kInclude:
      strPtr2 = GetSourceFileName(classPtr->GetDeclFileName());
      if (strPtr2)
         strcpy(sourceFile, strPtr2);
      strPtr =
          gSystem->ConcatFileName(gSystem->ExpandPathName(fOutputDir),
                                  GetFileName(classPtr->
                                              GetDeclFileName()));
      strcpy(filename, strPtr);
      delete[]strPtr;
      delete[]strPtr2;
      break;

   case kTree:
      strPtr2 = GetSourceFileName(classPtr->GetDeclFileName());
      if (strPtr2)
         strcpy(sourceFile, strPtr2);
      strcpy(classname, classPtr->GetName());
      NameSpace2FileName(classname);
      strPtr = 
          gSystem->ConcatFileName(gSystem->ExpandPathName(fOutputDir),
                                  classname);
      strcpy(filename, strPtr);
      delete[]strPtr;
      delete[]strPtr2;
      strcat(filename, "_Tree.ps");
      break;

   default:
      Error("IsModified", "Unknown file type !");
   }

   // Get info about a file
   Long_t sId, sSize, sFlags, sModtime;
   Long_t dId, dSize, dFlags, dModtime;

   if (!
       (gSystem->
        GetPathInfo(sourceFile, &sId, &sSize, &sFlags, &sModtime)))
      if (!
          (gSystem->
           GetPathInfo(filename, &dId, &dSize, &dFlags, &dModtime)))
         ret = (sModtime > dModtime) ? kTRUE : kFALSE;

   return (ret);
}


//______________________________________________________________________________
 Bool_t THtml::IsName(Int_t c)
{
// Check if c is a valid C++ name character
//
//
//  Input: c - a single character
//
// Output: TRUE if c is a valid C++ name character
//         and FALSE if it's not.
//
//   NOTE: Valid name characters are [a..zA..Z0..9_],
//

   Bool_t ret = kFALSE;

   if (isalnum(c) || c == '_')
      ret = kTRUE;

   return ret;
}


//______________________________________________________________________________
 Bool_t THtml::IsWord(Int_t c)
{
// Check if c is a valid first character for C++ name
//
//
//  Input: c - a single character
//
// Output: TRUE if c is a valid first character for C++ name,
//         and FALSE if it's not.
//
//   NOTE: Valid first characters are [a..zA..Z_]
//

   Bool_t ret = kFALSE;

   if (isalpha(c) || c == '_')
      ret = kTRUE;

   return ret;
}


//______________________________________________________________________________
 void THtml::MakeAll(Bool_t force, const char *filter)
{
// It makes all the classes specified in the filter (by default "*")
// To process all classes having a name starting with XX, do:
//        html.MakeAll(kFALSE,"XX*");
// if force=kFALSE (default), only the classes that have been modified since
// the previous call to this function will be generated.
// if force=kTRUE, all classes passing the filter will be processed.
//

   Int_t i;

   TString reg = filter;
   TRegexp re(reg, kTRUE);
   Int_t nOK = 0;

   MakeIndex(filter);

   Int_t numberOfClasses = gClassTable->Classes();
   const char **className = new const char *[numberOfClasses];

   // start from begining
   gClassTable->Init();


   for (i = 0; i < numberOfClasses; i++) {
      const char *cname = gClassTable->Next();
      TString s = cname;
      if (s.Index(re) == kNPOS)
         continue;
      className[nOK] = cname;
      nOK++;
   }

   for (i = 0; i < nOK; i++) {
      sprintf(fCounter, "%5d", nOK - i);
      MakeClass((char *) className[i], force);
   }

   *fCounter = 0;

   delete[]className;
}


//______________________________________________________________________________
 void THtml::MakeClass(const char *className, Bool_t force)
{
// Make HTML files for a single class
//
//
// Input: className - name of the class to process
//

   TClass *classPtr = GetClass(className);

   if (classPtr) {
      char *htmlFile = GetHtmlFileName(classPtr);
      if (htmlFile 
	  && (!strncmp(htmlFile, "http://", 7)
	       || !strncmp(htmlFile, "https://", 8))) {
         delete[]htmlFile;
         htmlFile = 0;
      }
      if (htmlFile) {
         Class2Html(classPtr, force);
         MakeTree(className, force);
         delete[]htmlFile;
         htmlFile = 0;
      } else
         Printf(formatStr, "-skipped-", fCounter, className);
   } else
      Error("MakeClass", "Unknown class '%s' !", className);

}


//______________________________________________________________________________
 void THtml::MakeIndex(const char *filter)
{
   // It makes an index files
   // by default makes an index of all classes (if filter="*")
   // To generate an index for all classes starting with "XX", do
   //    html.MakeIndex("XX*");

   CreateListOfTypes();

   // get total number of classes
   Int_t numberOfClasses = gClassTable->Classes();


   // allocate memory
   const char **classNames = new const char *[numberOfClasses];
   char **fileNames = new char *[numberOfClasses];

   // start from begining
   gClassTable->Init();

   // get class names
   Int_t len = 0;
   Int_t maxLen = 0;
   Int_t numberOfImpFiles = 0;

   TString reg = filter;
   TRegexp re(reg, kTRUE);
   Int_t nOK = 0;

   for (Int_t i = 0; i < numberOfClasses; i++) {

      // get class name
      const char *cname = gClassTable->Next();
      TString s = cname;
      if (s.Index(re) == kNPOS)
         continue;
      classNames[nOK] = cname;
      len = strlen(classNames[nOK]);
      maxLen = maxLen > len ? maxLen : len;

      // get class & filename
      TClass *classPtr = GetClass((const char *) classNames[nOK]);
      
      const char *impname=0;
      if (classPtr->GetImplFileName() && strlen(classPtr->GetImplFileName())) 
         impname = classPtr->GetImplFileName();
      else 
	 impname = classPtr->GetDeclFileName();

      if (impname && strlen(impname)) {
         fileNames[numberOfImpFiles] = StrDup(impname, 64);

         // for new ROOT install the impl file name has the form: base/src/TROOT.cxx
         char *srcdir = strstr(fileNames[numberOfImpFiles], "/src/T");

	 // if impl is unset, check for decl and see if it matches 
	 // format "base/inc/TROOT.h" - in which case it's not a USER
	 // class, but a BASE class.
         if (!srcdir) 
	    srcdir=strstr(fileNames[numberOfImpFiles],"/inc/T");
         // ROOT's non-classes (e.g. enums) don't start with T, but end with _t
         if (!srcdir && !(classPtr->Property()&kIsClass)) {
            const char* _t=classPtr->GetName()+strlen(classPtr->GetName())-2;
            if (!strcmp(_t,"_t"))
               srcdir=strstr(fileNames[numberOfImpFiles],"/inc/");
         };

         if (srcdir && (!strchr(srcdir + 5, '/'))) {
            strcpy(srcdir, "_");
            for (char *t = fileNames[numberOfImpFiles];
                 (t[0] = toupper(t[0])); t++);
            strcat(srcdir, classNames[nOK]);
         } else {
            strcpy(fileNames[numberOfImpFiles], "USER_");
            strcat(fileNames[numberOfImpFiles], classNames[nOK]);
         }
         numberOfImpFiles++;
      } else
         cout << "WARNING class:" << classNames[i] <<
             " has no implementation file name !" << endl;

      nOK++;
   }
   maxLen += kSpaceNum;

   // quick sort
   SortNames(classNames, nOK);
   SortNames((const char **) fileNames, numberOfImpFiles);

   // create an index
   CreateIndex(classNames, nOK);
   CreateIndexByTopic(fileNames, nOK, maxLen);

   // free allocated memory
   delete[]classNames;
   delete[]fileNames;
}


//______________________________________________________________________________
 void THtml::MakeTree(const char *className, Bool_t force)
{
// Make an inheritance tree
//
//
// Input: className - name of the class to process
//

   // create canvas & set fill color
   TVirtualPad *psCanvas = 0;

   //The pad utility manager is required (a plugin)
   TVirtualUtilPad *util = (TVirtualUtilPad*)gROOT->GetListOfSpecials()->FindObject("R__TVirtualUtilPad");
   if (!util) {
      TPluginHandler *h;
      if ((h = gROOT->GetPluginManager()->FindHandler("TVirtualUtilPad"))) {
          if (h->LoadPlugin() == -1)
            return;
          h->ExecPlugin(0);
          util = (TVirtualUtilPad*)gROOT->GetListOfSpecials()->FindObject("R__TVirtualUtilPad");
      }
   }
   util->MakeCanvas("","psCanvas",0,0,1000,750);

   psCanvas = gPad->GetVirtCanvas();

   TClass *classPtr = GetClass(className);

   if (classPtr) {

      char *htmlFile = GetHtmlFileName(classPtr);
      if (htmlFile 
	  && !(strncmp(htmlFile, "http://", 7)
	       || strncmp(htmlFile, "https://", 8))) {
         delete[]htmlFile;
         htmlFile = 0;
      }
      if (htmlFile) {

         // make a class tree
         ClassTree(psCanvas, classPtr, force);
         delete[]htmlFile;
         htmlFile = 0;
      } else
         Printf(formatStr, "-skipped-", "", className);

   } else
      Error("MakeTree", "Unknown class '%s' !", className);

   // close canvas
   psCanvas->Close();
   delete psCanvas;

}


//______________________________________________________________________________
 void THtml::ReplaceSpecialChars(ofstream & out, const char c)
{
// Replace ampersand, less-than and greater-than character
//
//
// Input: out - output file stream
//        c   - single character
//

   if (fEscFlag) {
      out << c;
      fEscFlag = kFALSE;
   } else if (c == fEsc)
      fEscFlag = kTRUE;
   else {
      switch (c) {
      case '<':
         out << "&lt;";
         break;
      case '&':
         out << "&amp;";
         break;
      case '>':
         out << "&gt;";
         break;
      default:
         out << c;
      }
   }
}


//______________________________________________________________________________
 void THtml::ReplaceSpecialChars(ofstream & out, const char *string)
{
// Replace ampersand, less-than and greater-than characters
//
//
// Input: out    - output file stream
//        string - pointer to an array of characters
//

   if (string) {
      char *data = StrDup(string);
      if (data) {
         char *ptr = 0;
         char *start = data;

         while ((ptr = strpbrk(start, "<&>"))) {
            char c = *ptr;
            *ptr = 0;
            out << start;
            ReplaceSpecialChars(out, c);
            start = ptr + 1;
         }
         out << start;
         delete[]data;
      }
   }
}

//______________________________________________________________________________
 void THtml::SortNames(const char **strings, Int_t num, Bool_t type)
{
// Sort strings
//
//
// Input: strings - pointer to an array of strings
//        type    - sort type
//                  values : kCaseInsensitive, kCaseSensitive
//                  default: kCaseInsensitive
//

   if (type == kCaseSensitive)
      qsort(strings, num, sizeof(strings), CaseSensitiveSort);
   else
      qsort(strings, num, sizeof(strings), CaseInsensitiveSort);
}


//______________________________________________________________________________
 char *THtml::StrDup(const char *s1, Int_t n)
{
// Returns a pointer to a new string which is a duplicate
// of the string to which 's1' points.  The space for the
// new string is obtained using the 'new' operator. The new
// string has the length of 'strlen(s1) + n'.


   char *str = 0;

   if (s1) {
      if (n < 0)
         n = 0;
      str = new char[strlen(s1) + n + 1];
      if (str)
         strcpy(str, s1);
   }

   return (str);
}

//______________________________________________________________________________
 void THtml::WriteHtmlHeader(ofstream & out, const char *title)
{
// Write HTML header
//
//
// Input: out   - output file stream
//        title - title for the HTML page
//
// evaluates the Root.Html.Header setting:
// * if not set, the standard header is written. (ROOT)
// * if set, and ends with a "+", the standard header is written and this file included afterwards. (ROOT, USER)
// * if set but doesn't end on "+" the file specified will be written instead of the standard header (USER)
//
// Any occurence of "%TITLE%" (without the quotation marks) in the user provided header file
// will be replaced by the value of this method's parameter "title" before written to the output file

   const char *addHeader = gEnv->GetValue("Root.Html.Header", "");
   // standard header output if Root.Html.Header is not set, or it's set and it ends with a "+".
   if (addHeader
       && (strlen(addHeader) == 0
           || addHeader[strlen(addHeader) - 1] == '+')) {
      TDatime date;
      out << "<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">" <<
          endl;
      out << "<html>" << endl;
      out << "<!--                                             -->" <<
          endl;
      out << "<!-- Author: ROOT team (rootdev@hpsalo.cern.ch)  -->" <<
          endl;
      out << "<!--                                             -->" <<
          endl;
      out << "<!--   Date: " << date.
          AsString() << "            -->" << endl;
      out << "<!--                                             -->" <<
          endl;
      out << "<head>" << endl;
      out << "<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">" <<
          endl;
      out << "<title>";
      ReplaceSpecialChars(out, title);
      out << "</title>" << endl;
      out << "<link rev=made href="mailto:rootdev@root.cern.ch">" <<
          endl;
      out << "<meta name="rating" content="General">" << endl;
      out << "<meta name="objecttype" content="Manual">" << endl;
      out <<
          "<meta name="keywords" content="software development, oo, object oriented, ";
      out << "unix, x11, windows, c++, html, rene brun, fons rademakers">"
          << endl;
      out <<
          "<meta name="description" content="ROOT - An Object Oriented Framework For Large Scale Data Analysis.">"
          << endl;
      out << "</head>" << endl;

      out <<
          "<body BGCOLOR="#ffffff" LINK="#0000ff" VLINK="#551a8b" ALINK="#ff0000" TEXT="#000000">"
          << endl;
   };
   // do we have an additional header?
   if (addHeader && strlen(addHeader) > 0) {
      ifstream addHeaderFile;
      char *addHeaderTmp = StrDup(addHeader);
      if (addHeaderTmp[strlen(addHeaderTmp) - 1] == '+')
         addHeaderTmp[strlen(addHeaderTmp) - 1] = 0;
      addHeaderFile.open(addHeaderTmp, ios::in

      if (addHeaderFile.good()) {
         while (!addHeaderFile.eof()) {

            addHeaderFile.getline(fLine, fLen - 1);
            fLine[fLen - 1] = 0;	// just to make sure it ends
            if (addHeaderFile.eof())
               break;

            if (fLine) {
               char *titlePos = strstr(fLine, "%TITLE%");
               if (titlePos != 0) {
                  *titlePos = 0;
                  out << fLine << title << titlePos + 7 << endl;
               } else
                  out << fLine << endl;
            }
         }
      } else
         Warning("THtml::WriteHtmlHeader",
                 "Can't open user html header file %sn", addHeaderTmp);


      if (addHeaderTmp)
         delete[]addHeaderTmp;
   }

   out << "<a name="TopOfPage"></a>" << endl;
}


//______________________________________________________________________________
 void THtml::WriteHtmlFooter(ofstream & out, const char *dir,
                            const char *lastUpdate, const char *author,
                            const char *copyright)
{
// Write HTML footer
//
//
// Input: out        - output file stream
//        dir        - usually equal to "" or "../", depends of
//                     current file directory position, i.e. if
//                     file is in the fOutputDir, then dir will be ""
//        lastUpdate - last update string
//        author     - author's name
//        copyright  - copyright note
//
// Allows optional user provided footer to be written. Root.Html.Footer holds the file name for this footer.
// For details see THtml::WriteHtmlHeader (here, the "+" means the user's footer is written in front of Root's!)
// Occurences of %AUTHOR%, %UPDATE% and %COPYRIGHT% in the user's file are replaced by their corresponding
// values (author, lastUpdate and copyright) before written to out.

   out << endl;

   const char *addFooter = gEnv->GetValue("Root.Html.Footer", "");
   // standard footer output if Root.Html.Footer is not set, or it's set and it ends with a "+".
   // do we have an additional footer?
   if (addFooter && strlen(addFooter) > 0) {
      ifstream addFooterFile;
      char *addFooterTmp = StrDup(addFooter);
      if (addFooterTmp[strlen(addFooterTmp) - 1] == '+')
         addFooterTmp[strlen(addFooterTmp) - 1] = 0;
      addFooterFile.open(addFooterTmp, ios::in

      if (addFooterFile.good()) {
         while (!addFooterFile.eof()) {

            addFooterFile.getline(fLine, fLen - 1);
            fLine[fLen - 1] = 0;	// just to make sure it ends
            if (addFooterFile.eof())
               break;

            if (fLine) {
               char *updatePos = strstr(fLine, "%UPDATE%");
               char *authorPos = strstr(fLine, "%AUTHOR%");
               char *copyPos = strstr(fLine, "%COPYRIGHT%");

               char order[5] = "0000";
               int iNum = 0;
               if (updatePos != 0) {
                  order[iNum++] = 'u';
                  *updatePos = 0;
               }
               if (authorPos != 0) {
                  order[iNum++] = 'a';
                  *authorPos = 0;
               }
               if (copyPos != 0) {
                  order[iNum++] = 'c';
                  *copyPos = 0;
               }

               int i, j;
               // in principle n*sqrt(n) is enough, but we're talking about one additional iteration here
               for (j = 0; j < iNum - 1; j++)
                  for (i = 0; i < iNum - 1; i++) {
                     switch (order[i]) {
                     case 'u':
                        if (order[i + 1] != '0')
                           if (order[i + 1] == 'a') {
                              if (updatePos > authorPos) {
                                 order[i] = 'a';
                                 order[i + 1] = 'u';
                              }
                           } else if (updatePos > copyPos) {
                              order[i] = 'c';
                              order[i + 1] = 'u';
                           };
                        break;
                     case 'a':
                        if (order[i + 1] != '0')
                           if (order[i + 1] == 'u') {
                              if (authorPos > updatePos) {
                                 order[i] = 'u';
                                 order[i + 1] = 'a';
                              }
                           } else if (authorPos > copyPos) {
                              order[i] = 'c';
                              order[i + 1] = 'a';
                           };
                        break;
                     case 'c':
                        if (order[i + 1] != '0')
                           if (order[i + 1] == 'u') {
                              if (copyPos > updatePos) {
                                 order[i] = 'u';
                                 order[i + 1] = 'c';
                              }
                           } else if (copyPos > authorPos) {
                              order[i] = 'a';
                              order[i + 1] = 'c';
                           };
                        break;
                     }
                  };

               out << fLine;
               for (i = 0; i < iNum; i++) {
                  switch (order[i]) {
                  case 'u':
                     out << (lastUpdate ==
                             0 ? "" : lastUpdate) << updatePos + 8;
                     break;
                  case 'a':
                     out << (author == 0 ? "" : author) << authorPos + 8;
                     break;
                  case 'c':
                     out << (copyright ==
                             0 ? "" : copyright) << copyPos + 11;
                     break;
                  }
               };
               out << endl;
            }
         }
      } else
         Warning("THtml::WriteHtmlFooter",
                 "Can't open user html footer file %sn", addFooterTmp);


      if (addFooterTmp)
         delete[]addFooterTmp;
   }

   if (addFooter
       && (strlen(addFooter) == 0
           || addFooter[strlen(addFooter) - 1] == '+')) {
      if (*author || *lastUpdate || *copyright)
         out << "<hr><br>" << endl;

      out << "<!--SIGNATURE-->" << endl;

      // get the author( s )
      if (*author) {

         out << "<em>Author: ";

         char *auth = StrDup(author);

         char *name = strtok(auth, ",");

         Bool_t firstAuthor = kTRUE;

         /* now we have two options. name is a comma separated list of tokens
            either in the format
            (i) "FirstName LastName " or
            (ii) "FirstName LastName <link> "
            The first one generates an XWho link (CERN compatible),
            the second a http link (WORLD compatible), e.g.
            <mailto:user@host.bla> or <http://www.host.bla/page>.
          */

         do {
            char *ptr = name;
            // do we have a link for the current name?
            char *cLink = 0;

            // remove leading spaces
            while (*ptr && isspace(*ptr))
               ptr++;

            if (!firstAuthor)
               out << ", ";

            if (!strncmp(ptr, "Nicolas", 7)) {
               out << "<a href=http://pcbrun.cern.ch/nicolas/index.html";
               ptr += 12;
            } else {
               cLink = strchr(ptr, '<');	// look for link start tag
               if (cLink) {
                  out << "<a href="";
                  ptr = cLink-1;
                  for (cLink++; *cLink != 0 && *cLink != '>'; cLink++)
                     if (*cLink != ' ')
                        out << *cLink;
               } else {
                  out << "<a href="" << GetXwho();
                  while (*ptr && !cLink) {
                     // Valery's specific case
                     if (!strncmp(ptr, "Valery", 6)) {
                        out << "Valeri";
                        ptr += 6;
                     } else if (!strncmp(ptr, "Fine", 4)) {
                        out << "Faine";
                        ptr += 4;
                     }
                     while (*ptr && !isspace(*ptr))
                        out << *ptr++;

                     if (isspace(*ptr)) {
                        while (*ptr && isspace(*ptr))
                           ptr++;
                        if (isalpha(*ptr))
                           out << '+';
                        else
                           break;
                     } else
                        break;
                  }
               }
            }
            out << "">";

            char *cCurrentPos = name;
            // remove blanks in front of and behind the name
            while (*cCurrentPos == ' ')
               cCurrentPos++;
            Bool_t bBlank = kFALSE;
            for (; cCurrentPos != ptr && *cCurrentPos != 0; cCurrentPos++) {
               if (*cCurrentPos != ' ') {
                  if (bBlank) {
                     out << ' ';
                     bBlank = kFALSE;
                  }
                  out << *cCurrentPos;
               } else
                  bBlank = kTRUE;
            }
            out << "</a>";
            while (ptr && *ptr==' ') ptr++;
            if (ptr && *ptr=='<') {
               // skip link
               while (*ptr && *ptr!='>') ptr++;
               if (ptr && *ptr=='>') ptr++;
            }
            while (ptr && *ptr==' ') ptr++;
            if (ptr && *ptr)
               out << ' ' << ptr;

            firstAuthor = kFALSE;
            name += strlen(name) + 1;

         } while ((name - auth) < (int) strlen(author)
                  && (name = strtok(name, ",")));
         out << "</em><br>" << endl;
         delete[]auth;
      }

      if (*lastUpdate)
         out << "<em>Last update: " << lastUpdate << "</em><br>" << endl;
      if (*copyright)
         out << "<em>Copyright " << copyright << "</em><br>" << endl;


      // this is a menu
      out << "<br>" << endl;
      out << "<hr>" << endl;
      out << "<center>" << endl;
      out << "<address>" << endl;

      // link to the ROOT home page
      out <<
          "<a href="http://root.cern.ch/root/Welcome.html">ROOT page</a> - ";

      // link to the user home page( if exist )
      const char *userHomePage = gEnv->GetValue("Root.Html.HomePage", "");
      if (*userHomePage) {
         out << "<a href="";
         if (*dir) {
            if (strncmp(userHomePage, "http://", 7)
		|| strncmp(userHomePage, "https://", 8))
               out << dir;
         }
         out << userHomePage;
         out << "">Home page</a> - ";
      }
      // link to the index file
      out << "<a href="";
      if (*dir)
         out << dir;
      out << "ClassIndex.html">Class index</a> - ";

      // link to the top of the page
      out << "<a href="#TopOfPage">Top of the page</a><br>" << endl;
      out << "</address>" << endl;

      out << "</center>" << endl;

      out << "<hr>" << endl;
      out << "<address>" << endl;
      out << "This page has been automatically generated. If you have any comments or suggestions ";
      out <<
          "about the page layout send a mail to <a href="mailto:rootdev@root.cern.ch">ROOT support</a>, or ";
      out <<
          "contact <a href="mailto:rootdev@root.cern.ch">the developers</a> with any questions or problems regarding ROOT."
          << endl;
      out << "</address>" << endl;
      out << "</body>" << endl;
      out << "</html>" << endl;
   }
}

//______________________________________________________________________________
 void THtml::NameSpace2FileName(char *name)
{
   // Replace "::" in name by "__"
   // Replace "<", ">", " ","," in name by "_"
   char *namesp = 0;
   while ((namesp = strstr(name, "::")) != 0)
      *namesp = *(namesp + 1) = '_';
   while ((namesp = strstr(name, "<")) != 0)
      *namesp = '_';
   while ((namesp = strstr(name, ">")) != 0)
      *namesp = '_';
   while ((namesp = strstr(name, " ")) != 0)
      *namesp = '_';
   while ((namesp = strstr(name, ",")) != 0)
      *namesp = '_';
}

#define DEBUG_HELP
#ifdef DEBUG_HELP
#ifdef DEBUG_HELP_INFO
#define DEBUG_CLASS
#define DEBUG_TYPEDEF
#define DEBUG_USING
#define DEBUG_NAMESP
#define DEBUG_DOC
#define DEBUG_METH_ARGS
#define DEBUG_METH
#define DEBUG_METH_PARSE
#endif
#define DEBUG_TYPEDEF_WARN
#define DEBUG_METH_NOTFOUND

#endif

//______________________________________________________________________________
 void THtml::ExtractClassDocumentation(const TClass* classPtr){
   TList listClassesFound;
   if (classPtr->GetDeclFileName() && strlen(classPtr->GetDeclFileName())) 
      ExtractDocumentation(classPtr->GetDeclFileName(), &listClassesFound);
   if (classPtr->GetImplFileName() && strlen(classPtr->GetImplFileName())) 
      ExtractDocumentation(classPtr->GetImplFileName(), &listClassesFound);
}


//______________________________________________________________________________
 void THtml::ExtractDocumentation(const char* cFileName, TList* listClassesFound)
{
// parse this source or header, collect classes and methods, and add their doc to 
// fMapDocElemets
// return the list of class definitions found in listClassesFound
// search only for methods of classes that are in listClassesFound

   const char* cClDescrTag = 
      gEnv->GetValue("Root.Html.Description", "//____________________");
   Int_t lenClDescrTag = strlen(cClDescrTag);
   if (!cClDescrTag || !lenClDescrTag) {
      Error("ExtractDocumentation","Root.Html.Description is unset.");
      return;
   }

   fFilesParsed.Add(new TObjString(cFileName));

   char* filename=GetSourceFileName(cFileName);
   if (!filename) {
//      Error("ExtractDocumentation", "Can't find file '%s'!", cFileName);
      return;
   }

   // open source file
   ifstream sourceFile;
   sourceFile.open(filename);

   if (!sourceFile.good()) {
      Error("ExtractDocumentation", "Can't find source file '%s'!", filename);
      return;
   }

//   Info("ExtractDocumentation", "Extracting documentation from file %s...", filename);
   TParseStack parseStack;
   TParseStack::TParseElement psNext(TParseStack::kUndefined);

   TString strLastClassDoc;
   TDocElement* lastMethodDocElement=0;
   Bool_t inClassDoc=kFALSE;
   Bool_t inMethodDoc=kFALSE;

   // search positions
   const char* cClassPos=0;
   const char* cClassImpPos=0;
   const char* cEnumPos=0;
   const char* cStructPos=0;
   const char* cMethodPos=0;
   const char* cUsingPos=0;
   const char* cNamespacePos=0;
   const char* cTemplatePos=0;
   const char* cTypeDefPos=0;

   while (!sourceFile.eof()) {
      char* cCommentEnd=0;
      TParseStack::EContext ctx=parseStack.Context();
      Int_t lenLine;

      do {
         cCommentEnd=0;
         // get source line while in comment block
         sourceFile.getline(fLine, fLen-1);

         lenLine=strlen(fLine);
         // rtrim
         while (lenLine && fLine[lenLine-1]==' ')
            fLine[--lenLine]=0;

         // check if this line starts a class description
         // with symmetric line (i.e. first==last character, first+1==last-1 etc)
         // set minimum line length to 6 chars
         if (!inClassDoc && lenLine>5) {
            char* start=fLine;
            char* end=fLine+lenLine-1;
            do inClassDoc=(*start==*end);
            while (inClassDoc && end-- - start++>1);
            if (inClassDoc) 
               strLastClassDoc="";
         }

         // skip while in /* ... */ comment
         if (ctx==TParseStack::kComment) {
            cCommentEnd=strstr(fLine, "*/");
            if (cCommentEnd) {
               // if we are in class comment block, add this line
               *cCommentEnd=0;
               if (inClassDoc && strlen(fLine) && !strLastClassDoc.EndsWith("*/")) {
                  strLastClassDoc+=fLine;
                  strLastClassDoc+="*/n";
                  inClassDoc=kFALSE;                  
               }
               *cCommentEnd='*';
            } else 
               if (inClassDoc) {
                  strLastClassDoc+=fLine;
                  strLastClassDoc+="n";
               }
         }
      } while (ctx==TParseStack::kComment && !cCommentEnd);

      // search for defs and decls
      cClassPos=strstr(fLine, "class ");
      cClassImpPos=strstr(fLine, "ClassImp(");
      cEnumPos=strstr(fLine, "enum ");
      cStructPos=strstr(fLine, "struct ");
      cUsingPos=strstr(fLine, "using ");
      cNamespacePos=strstr(fLine, "namespace ");
      cTemplatePos=strstr(fLine, "template");
      cTypeDefPos=strstr(fLine, "typedef ");

      cMethodPos=0;
      if (listClassesFound && (listClassesFound->GetSize())) {
         Bool_t bInClassDef=(ctx==TParseStack::kBlock && parseStack.BlockSpec()==TParseStack::kClassDecl);
         TDictionary* dict;
         TIter iClass(listClassesFound);
         while (!cMethodPos && (dict=(TClass*) iClass())) {
            if (dict->IsA()!=TClass::Class()) 
               continue;
            TClass *cl=(TClass*) dict; 
            const char* clname=cl->GetName();
            const char* col=strchr(clname,':');
            while (col) {
               clname=col+1;
               col=strchr(clname,':');
            }
            UInt_t lenclname=strlen(clname);
            // if we are in a class definition of a different class then ignore this class
            if (bInClassDef && strcmp(parseStack.Top().GetName(), clname)!=0) continue;
            // at least the class name has to be in this line
            const char* cClassNamePos=strstr(fLine, clname);
            if (!bInClassDef && !cClassNamePos 
               || cClassNamePos && (cClassNamePos[lenclname]!=':' || cClassNamePos[lenclname+1]!=':') ) 
               continue;

            TList* listMeth=cl->GetListOfMethods();
            if (!listMeth || !listMeth->GetSize()) continue;

            TMethod* meth=0;
            TIter iMeth(listMeth);
            while ((meth=(TMethod*) iMeth()) && !cMethodPos)
               if (bInClassDef)
                  cMethodPos=strstr(fLine, meth->GetName());
               else {
                  TString strFQIMethodName(meth->GetClass()->GetName());
                  strFQIMethodName+="::";
                  strFQIMethodName+=meth->GetName();

                  // if we have a using directive or are in a namespace block 
                  // then maybe only part of the class is specified
                  const char* cUsing=parseStack.IsUsing(strFQIMethodName);
                  cMethodPos=strstr(fLine, cUsing);
               }
         } // while next known class

         // little restriction here:
         // the parameter list has to be on the same line as the method name
         if (cMethodPos)
            if (!strchr(cMethodPos,'(')) cMethodPos=0;

      } // if we have known classes
      

      // iterate through characters
      for(char* c=(cCommentEnd ? cCommentEnd : fLine); *c && c-fLine<=fLen; c++) {
         // skip leading spaces
         while (*c==' ') c++;

         // update parse back entry
         ctx=parseStack.Context();

         if (!inClassDoc && ctx!=TParseStack::kComment) {
            // look for class descr tag right here
            // this is option one for a class descr,
            // option 2 see getline
            inClassDoc=!strncmp(c, cClDescrTag, lenClDescrTag);
            if (inClassDoc)
               // overwrite old classdescr if still in there
               strLastClassDoc="";
         }

         const char* tag=parseStack.Top().GetCloseTag();
         if (tag && *c==tag[0] && (strlen(tag)==1 || c[1]==tag[1]) ) {
            parseStack.PopAndDel();
            // skip tag
            c+=strlen(tag)-1;
            continue;
         };

         switch (*c) {
         case ''':
            if (c[1]=='\') c+=3;
            else c+=2;
            break;
         case '\': 
            c++; break; // skip the next char

         case '/': 
            if (ctx==TParseStack::kString) break;
            // neither single nor multi line comment
            if (ctx==TParseStack::kComment 
               || ( c[1]!='/' && c[1]!='*')) break;
            // multiline comment
            if (c[1]=='*') {
               parseStack.Push(TParseStack::kComment);
               char* cCommentEnd=strstr(c, "*/");
               if (cCommentEnd) {
                  if (inClassDoc) {
                     *cCommentEnd=0;
                     strLastClassDoc+=c;
                     strLastClassDoc+="n";
                     *cCommentEnd='*';
                  }
                  // let the end of comment tag be dealt with
                  c=cCommentEnd-1;
               } else {
                  if (inClassDoc) {
                     strLastClassDoc+=c;
                     strLastClassDoc+="n";
                  } 
                  // skip to eol
                  c=fLine+lenLine-1;
               }
               break;
            }

         case '#':
            if (ctx==TParseStack::kString) break;
            // single line preproc statement (and single line comment, cont'd)
            if (*c=='/')
               if (inClassDoc) {
                  strLastClassDoc+=c;
                  strLastClassDoc+="n";
               } else if (inMethodDoc) {
                  lastMethodDocElement->AddToDoc(c);
                  lastMethodDocElement->AddToDoc("n");
               }
            if (*c=='#') {
               inClassDoc=kFALSE;
               inMethodDoc=kFALSE;
               char* d=c;
               while (isspace(*(++d)));
               if (!strncmp(d, "include", 7)) {
                  d+=6;
                  while (isspace(*(++d)));
                  TString strIncludeFile;
		  Int_t d_;
                  if (strchr("<"", *d))
                     if (ParseWord(++d, d_, strIncludeFile, "/\._-")) {
			d+=d_;
                        char* store_fLine=new char[fLen];
                        strcpy(store_fLine, fLine);
                        TObjString ostrIncludeFile(strIncludeFile);
                        if (!fFilesParsed.FindObject(&ostrIncludeFile))
                           ExtractDocumentation(strIncludeFile.Data(), NULL);
                        c=++d; // skip the trailing " or >
                        strcpy(fLine, store_fLine);
                        delete[] store_fLine;
                     } 
		     else d+=d_;
               }
            }
            // skip to eol
            c=fLine+lenLine-1;

            while (*c=='\') {
               // continuation char, get next line
               sourceFile.getline(fLine, fLen-1);
               lenLine=strlen(fLine);
               c=fLine+strlen(fLine)-1;
               if (*c=='/') 
                  if (inClassDoc) {
                     strLastClassDoc+=fLine;
                     strLastClassDoc+="n";
                  } else if (inMethodDoc) {
                     lastMethodDocElement->AddToDoc(fLine);
                     lastMethodDocElement->AddToDoc("n");
                  }
            }
            break;

         case '{': 
            if (ctx==TParseStack::kString) break;
            if (psNext.Context()==TParseStack::kBlock) {
               parseStack.Push(new TParseStack::TParseElement(psNext));
/*if (ctx==TParseStack::kTop) {
static char line[1000];
ifstream istr2(sourceFile);
istr2.getline(line,1000);
parseStack.Top().SetName(fLine);
parseStack.Top().SetTitle(line);
}*/
               // invalidate old psNext
               psNext.SetContext(TParseStack::kUndefined);
               break;
            } // else continue
         case '(':
//         case '<':
         case '[':
         case '"':
            if (ctx==TParseStack::kString) break;
            parseStack.Push(*c);
            inClassDoc=kFALSE;
            inMethodDoc=kFALSE;
/*if (ctx==TParseStack::kTop&& *c=='{') {
static char line[1000];
ifstream istr2(sourceFile);
istr2.getline(line,1000);
parseStack.Top().SetName(fLine);
parseStack.Top().SetTitle(line);
}*/
            break;

         default:
            if (ctx==TParseStack::kComment
               || ctx==TParseStack::kString)
               break;
            inClassDoc=kFALSE;
            inMethodDoc=kFALSE;
            if (c==cTemplatePos) {
               // some templated stuff here, ignore the template part

               // first check that this is not "Int_t iContemplate" or something
               TString strWord;
               char* end;
	       Int_t step;
               ParseWord(c, step, strWord);
	       end=c+step;
               // if template isn't actually the full word then ignore
               if (strcmp(strWord.Data(),"template")!=0) break;
               c=end;
               while (*c==0) {
                  // eol
                  sourceFile.getline(fLine, fLen-1);
                  lenLine=strlen(fLine);
                  c=fLine;
                  while (*c==' ') c++;
               }
               if (*c!='<') break;
               end=++c;
               while (*end!='>' && ParseWord(end, step,",* "))
		  end+=step;
               if (*end!='>') {
		  end+=step;
                  Warning("ExtractClassDocumentation", 
                     "Found a templated declaration with an illegal character '%c':%s",
                     *end, fLine);
                  break;
               }
               c=end;

               // now c points to closing '>' of "template < class T, typename S,... >" 
               break;
            } // if "template" in line

            if (c==cClassPos || c==cEnumPos || c==cStructPos) // class def starts here
            {
               TLocalType::ESpec spec=TLocalType::kUndefined;
               if (c==cClassPos) spec=TLocalType::kClass; 
               else if (c==cEnumPos) spec=TLocalType::kEnum;
               else if (c==cStructPos) spec=TLocalType::kStruct; 

	       Int_t step;
               ParseWord(c,step); // skip "class" / "enum" / "struct"
	       c+=step;

               TString strClassName;
               TClass* cldecl=ParseClassDecl(c, parseStack, strClassName);
               if (!cldecl) {
                  if (strClassName && strClassName.Length()) {
                     // add this class to our list of classes 
                     // - it is not linkdef'ed, but it might still be used later
//                     parseStack.AddCustomType(strClassName);
                     fgLocalTypes.Add(new TLocalType(strClassName, spec, filename, -1));
                  }
                  if (c!=&fLine[0]) c--; // just to make sure we don't skip a { or something
                  break;
               }

               // only store the last :: part of the name, the rest is stack
               const char* cClName=strClassName;
               const char* cCol=strchr(cClName, ':');
               while ((cCol=strchr(cClName, ':'))) 
		  cClName=&cCol[1];

               psNext=TParseStack::TParseElement(TParseStack::kBlock, 
                  TParseStack::kClassDecl, cClName, 0, cldecl);
               if (listClassesFound) 
                  listClassesFound->Add(cldecl);
#ifdef DEBUG_CLASS
printf("FOUND CLASS: %s in \n%s\n", cClName, fLine);
#endif

               // if there's no documentation then we can't add it
               if (!strLastClassDoc.Length()) break;
               AddDocElement(cldecl, strLastClassDoc, filename);
               break;
            }; // if class / enum / struct def in line

            if (c==cTypeDefPos) {
	       Int_t step;
               ParseWord(c,step); // skip "typedef"
	       c+=step;
               TString strOldType;
               TString strNewType;
               TString strWord;
               while (ParseWord(c,step, strWord) && *(c+step)!=';' && *(c+step)) {
		  c+=step;
                  while (!IsWord(*c) && *c && *c!=';') {
                     if (strWord.Length()) strWord+=' ';
                     strWord+=*c;
                     c++;
                  }
                  strOldType+=strNewType;
                  strNewType=strWord;
                  strWord="";
               } 
               if (strWord.Length()) {
                  if (strOldType.Length()) strOldType+=' ';
                  strOldType+=strNewType;
                  strNewType=strWord;
               }
               Int_t iPosBracket=strOldType.Index('(');
               if (iPosBracket!=kNPOS) {
                  strOldType+=strNewType;
                  strNewType="";
                  iPosBracket++; // skip '('
                  while (strOldType[iPosBracket]==' ' || strOldType[iPosBracket]=='*')
                     iPosBracket++;
		  Int_t step;
                  ParseWord(&(strOldType.Data()[iPosBracket]), step, strNewType);
                  strOldType="void*";
               }
               parseStack.GetFQI(strNewType);
               if (strNewType.Length()+strOldType.Length()) {
#ifdef DEBUG_TYPEDEF
printf("FOUND Typedef %s -> %s\n", strOldType.Data(), strNewType.Data());
#endif
               fgLocalTypes.Add(new TLocalType(strNewType.Data(), 
                  TLocalType::kTypedef, filename, -1, strOldType.Data()));
               }
#ifdef DEBUG_TYPEDEF_WARN
else
printf("WARNING Typedef %s -> %s in\n%s", strOldType.Data(), strNewType.Data(), fLine);
#endif
            }

            if (c==cClassImpPos) // ClassImp starts here
            {
               c+=9;
               while (*c==' ') c++;

               // if there's no documentation then we can't add it
               if (!strLastClassDoc.Length()) break;

               TString clname(c);
               char* endClassName= (char*)strchr(clname.Data(),')');
               while (*(endClassName-1)==' ') endClassName--;
               if (!endClassName) {
                  Warning("ExtractClassDocumentation",
                     "Found ClassImp macro call without closing ')': ClassImp('%s'n%s", c,
                     "Ignoring macro call for documentation generation.");
                  break;
               }
               *endClassName=0;

               TClass* climp=(TClass*)GetType(clname);
               if (!climp) break;
               AddDocElement(climp, strLastClassDoc, filename);
               break;
            }; // if ClassImp in line

            if (c==cUsingPos) { 
               c+=6;
#ifdef DEBUG_USING
printf("FOUND USING DECL: %s in \n%s\n", c, fLine);
#endif
               break;
            } // if "using" in line
            if (c==cNamespacePos) { 
               // namespace decl
               c+=9;
               TString strNamesp;
	       Int_t step;
               ParseWord(c, step, strNamesp);
	       c+=step;
               char* d=c;
               // now either ';' (forward decl) or '{'
               if (*d==';') // ignore - this is just to make the namespace known
                  break;
               c=--d;
#ifdef DEBUG_NAMESP
printf("FOUND NAMESP DECL: %s \n%s\n", strNamesp.Data(), fLine);
#endif
               psNext=TParseStack::TParseElement(TParseStack::kBlock, 
                  TParseStack::kNamespace, strNamesp);
               break;
            } // if "namespace" in line

            if (c==cMethodPos) {
               // there is a method name in this line - but is it an impl?
               // check that we don't find method "Meth" in "fMemberNameMeth(0)"
               if (c!=fLine && IsName(c[-1])) break;

               char* end;
	       Int_t step;
               TString strMethName;
               while (ParseWord(c, step, strMethName)) {
		  end=c+step;
                  if (!strncmp(end,"::",2)) {
                     strMethName+="::";
                     end+=2;
                  } else break;
                  c=end;
               }
	       end=c+step;

               if (*end!='(') break;

               TString strClassName(strMethName);
               TClass* cl=0;

               // find the class name
               if (ctx!=TParseStack::kBlock || parseStack.BlockSpec()!=TParseStack::kClassDecl) {
                  // we don't do global functions
                  if (!strstr(strMethName,"::")) break; // not in a class def, and no class given 
                  strClassName.Remove(strClassName.Last(':')-1);

                  // try to find strClassName - might have using directives added
                  TDictionary* dict;
                  if (!parseStack.FindType(strClassName, dict)) {
                     Warning("ExtractClassDocumentation",
                        "Found method candidate '%s', but no class it might belong to. Ignoring the method.",
                        strMethName.Data());
                     break;
                  }
                  // cl can still be NULL!
                  cl=(TClass*)dict;

                  // remove class name from method name
                  Int_t iColPos=strMethName.Last(':');
                  if (iColPos!=kNPOS)
                     strMethName.Remove(0, iColPos+1);
               } // if (outside a class def)
               else {
                  strClassName="";
                  cl=(TClass*) parseStack.Dict();
                  if (!cl) {
                     parseStack.GetFQI(strClassName);
                     // remove trailing "::"
                     strClassName.Remove(strClassName.Length()-2);
                     cl=(TClass*) GetType(strClassName);
                  } else 
                     strClassName=cl->GetName();
               } // else if (outside class def)

               if (!cl) {
                  Warning("ExtractClassDocumentation",
                     "Found method candidate '%s' in a class definition, but the class '%s' is unknown. Ignoring the method.",
                     strMethName.Data(), strClassName.Data());
                  break;
               }

               TString strMethFullName(strClassName);
               strMethFullName+="::";
               strMethFullName+=strMethName;

               // now we need to find the argument types, to check 
               // that this is a method decl and not just a call

               end++;
               TString strArg;
               TString strWord;
               TList listArgs;

               // build list of methods with same name
               TList listMeth; // list of methods with this name
               TList* listAllMeth=cl->GetListOfMethods();
               TIter iMeth(listAllMeth);
               TMethod* meth;
               TMethod* methFound=0;
               while ((meth=(TMethod*)iMeth())) { //2do: sort. find, extract until name differs
                  if (!strcmp(meth->GetName(), strMethName.Data()))
                     listMeth.Add(meth);
               }
               if (!listMeth.GetSize()) {
                  Warning("ExtractClassDocumentation",
                     "Found method candidate '%s', but the class '%s' doesn't know it. Ignoring the method.",
                     strMethName.Data(), strClassName.Data());
                  break;
               }

               // parse arguments to
               // * check this is a meth def / impl and not a call
               // * identify the correct method (=entry in listMeth)

               Bool_t bBreak=kFALSE;
               while (*end!=')' && !bBreak && !methFound) {
                  while (*end==0 && !sourceFile.eof()) {
                     sourceFile.getline(fLine,fLen-1);
                     lenLine=strlen(fLine);
                     end=fLine;
                  }
                  if (*end==0 && sourceFile.eof()) {
                     Warning("ExtractClassDocumentation",
                        "Arguments for method '%s' seem not to end. Ignoring the method.",
                        strMethFullName.Data());
                     bBreak=kTRUE;
                     break;
                  }
                  if (*end=='(' || *end=='"') {
                     // type cast / string - this is a method call
                     bBreak=kTRUE;
                     break;
                  }
                  if (*end=='=') { 
                     // default value, skip until ',' or ')'
                     char* cEndDefArg=strchr(end, ',');
                     if (!cEndDefArg) cEndDefArg=strchr(end, ')');

                     while (!cEndDefArg && !sourceFile.eof() && !strchr(fLine, '{')) {
                        sourceFile.getline(fLine, fLen-1);
                        lenLine=strlen(fLine);
                        end=fLine;
                        cEndDefArg=strchr(end, ',');
                        if (!cEndDefArg) cEndDefArg=strchr(end, ')');
                     }
                     if (!cEndDefArg) {
                        Warning("ExtractClassDocumentation",
                           "Found default argument in method '%s' which seems endless. Ignoring the method.",
                           strMethFullName.Data());
                        bBreak=kTRUE;
                        break;
                     }
                     end=cEndDefArg;
                     continue;
                  }

                  if (strchr("*&", *end)){
                     do {
                        if (!strArg.Length()) {
                           // this is a call, (de)referencing some var
                           bBreak=kTRUE;
                           break;
                        }
                        strArg+=*end;
                        strArg+=" ";
                        do end++;
                        while (*end==' ');
                     } while (strchr("*&", *end));
                     continue;
                  }

                  if (*end==',') {
                     listArgs.Add(new TObjString(strArg));
                     strArg="";
                     end++;
                     if (!FindMethodImpl(strMethFullName, listMeth, listArgs, 
                        parseStack)) {
                        bBreak=kTRUE;
                        break;
                     }
                     if (listMeth.GetSize()==1) 
                        methFound=(TMethod*)listMeth.First();
                     continue;
                  } else if (!strncmp(end,"::",2)) {
                     strArg+="::";
                     end+=2;
                  };

                  // now look for real words
                  strWord="";
		  Int_t step;
                  if (!ParseWord(end, step, strWord))
                  {
		     end+=step;
                     if (!strncmp(end,"/*",2)) {
                        while (!(end=strstr(end,"*/")) && !sourceFile.eof()){
                           sourceFile.getline(fLine, fLen-1);
                           lenLine=strlen(fLine);
                           end=fLine;
                        }
                        if (!end && sourceFile.eof())
                           Warning("ExtractClassDocumentation",
                              "Found opening '/*' comment without closing '*/' in method args of %s. Ignoring the method.",
                              *end, strMethFullName.Data());
                     } else
                        Warning("ExtractClassDocumentation",
                           "Found improper character '%c' in method args of %s. Ignoring the method.",
                           *end, strMethFullName.Data());
                     bBreak=kTRUE;
                     break;
                  }
		  end+=step;
                  while (!strcmp(strWord,"const") || !strcmp(strWord,"unsigned")) {
                     if (strArg.Length()) 
                        strArg+=" ";
                     strArg+=strWord;
                     strWord="";
                     if (!*end) {
                        sourceFile.getline(fLine, fLen-1);
                        lenLine=strlen(fLine);
                        end=fLine;
                     }
		     Int_t step;
                     ParseWord(end, step, strWord);
		     end+=step;
                  };
                  if (strArg.Length() && strArg[strArg.Length()-1]!=':') strArg+=" ";
                  strArg+=strWord;
               } // while (parsing arguments)

               if (bBreak) {
                  parseStack.PopAndDel();
                  break;
               }

               if (strArg.Length())
                  listArgs.Add(new TObjString(strArg));

#ifdef DEBUG_METH_ARGS
               printf("METHOD ARGS: ");
               TIter iArg(&listArgs);
               TObjString* str;
               while(str=(TObjString*) iArg())
                  printf("'%s', ", str->String().Data());
               printf("\n");
#endif
               if (!methFound) {
                  // last chance
                  if (!FindMethodImpl(strMethFullName, listMeth, listArgs, parseStack, kTRUE)) {
                     bBreak=kTRUE;
                     break;
                  }
                  if (listMeth.GetSize()==1) 
                     if (listArgs.GetSize() || 
                        !parseStack.IsInStack(TParseStack::kBlock, TParseStack::kMethodDef))
                        // for 0 arg methods we can't decide whether call of impl
                        // look at surrounding block: must not be method
                        methFound=(TMethod*)listMeth.First();
               }
               if (methFound) {
#ifdef DEBUG_METH
printf("FOUND A METH: %s%s in \n%s\n", methFound->GetName(), methFound->GetSignature(), fLine);
if (strstr(methFound->GetName(), "Push"))
printf("DEBUG");
#endif
                  psNext=TParseStack::TParseElement(TParseStack::kBlock, 
                     TParseStack::kMethodDef, 
                     strMethName, strMethFullName, methFound);
                  TString strdummy;
                  lastMethodDocElement=AddDocElement(methFound, strdummy, filename);
                  inMethodDoc=kTRUE;
                  // skip parameters
                  c=end;
                  // skip until { or ; (if we're in a in class def)
                  while (!(end=strchr(c,'{')) && !(end=strchr(c,'{')) && !sourceFile.eof()) {
                     sourceFile.getline(fLine, fLen-1);
                     lenLine=strlen(fLine);
                     end=fLine;
                  }
                  if (sourceFile.eof()) {
#ifdef DEBUG_PARSE_WARN
printf("Warning: Can't find ';' or '{' after method def of %s%s in\n%s\n", 
       methFound->GetName(), methFound->GetSignature(), fLine);
end=0;
#endif
                  }
                  // allow '{' to be parsed
                  if (!end) c=fLine;
                  else c=--end;
               }
               else {
                  printf("METH AMBIG: %s in n%sn", strMethFullName.Data(), fLine);
                  TIter im(&listMeth);
                  TMethod* m;
                  while ((m=(TMethod*)im()))
                     printf(">>> %s%sn", m->GetName(), m->GetSignature());
               }

            } // if methodpos

            char* cc;
	    Int_t step;
            ParseWord(c,step);
	    cc=c+step;
            if (cc!=c) c=--cc;
            break;
         }
      }
   }// while (!sourceFile.eof())

// if there's still a lastClassDoc (i.e. it's not assigned to anything yet), 
// and if we have a class given as input, then assign lastClassDoc to that class.
}

 TClass* THtml::ParseClassDecl(char* &cfirstLinePos, 
                              const TParseStack& parseStack, TString& strClassName) {
   char* d;
   Int_t step;
   ParseWord(cfirstLinePos, step, strClassName);
   d=cfirstLinePos+step;
   if (!strClassName.Length()) return 0; // something is wrong here, let's just go on

   // now comes either a '{' or a ';'
   // if it's a forward def then there's nothing in between the class name and the ';'
   if (*d==';') {
      strClassName="";
      // we don't want to document forward decls, so just go on
      return 0;
   }

   // state that the next block belongs to a class, and store its name
   // if we are in named blocks (class, namespace), then add the names to strClassName
   parseStack.GetFQI(strClassName);
   // handle documentation element
   TClass* cl=(TClass*)THtml::GetType(strClassName);
/*   if (!cl)
      Warning("ParseClassDecl",
         "No documentation generated for unknown class %s.",
         strClassName.Data());
*/   return cl;
}

THtml::TDocElement* THtml::AddDocElement(TDictionary* dict, TString& strDoc, const char* filename) {
// strDoc will be set to "" if used
   TDocElement* de=0;
   if (strDoc.Length()) {
      Int_t iLFPos = strDoc.Index('n');
      if (iLFPos==kNPOS || !strchr(&(strDoc.Data()[iLFPos+1]), 'n')) return 0;
   }
   if (!fMapDocElements) 
      fMapDocElements = new TMap();
   else
      if ((de=(TDocElement*) fMapDocElements->GetValue(dict))) {
         if (de->GetDoc().Length()<strDoc.Length()) {
            de->SetDoc(strDoc);
#ifdef DEBUG_DOC
printf("UPDATED DOCELEMENT: %s, doc: \n%s\n", dict->GetName(), strDoc.Data());
#endif
         };
         strDoc="";
         return de;
      }
   de=new TDocElement(strDoc, filename);
   fMapDocElements->Add(dict, de);

if (strDoc.Length())
#ifdef DEBUG_DOC
printf("ADDED DOCELEMENT: %s, doc: \n%s\n", dict->GetName(), strDoc.Data());
#endif
   strDoc="";
   return de;
}

 Bool_t THtml::ParseWord(const char* begin, Int_t &step, 
                        TString& strWord, const char* allowedChars /*=0*/) {
   const char* end=begin;
   while (isspace(*end)) end++;
   step=end-begin;
   begin=end;
   const char* endAllowed=(allowedChars ? &allowedChars[strlen(allowedChars)] : 0);
   const char* cFound;
   while (IsName(*end) || allowedChars 
      && 0!=(cFound=strchr(allowedChars, *end)) && cFound!=endAllowed)
      strWord+=*(end++);
   Bool_t bGotSomething=!(end==begin);
   while (isspace(*end)) end++;
   step+=end-begin;
   return bGotSomething;
}
 Bool_t THtml::ParseWord(const char* begin, Int_t &step, 
                        const char* allowedChars /*=0*/) {
   const char* end=begin;
   while (isspace(*end))
      end++;
   step=end-begin;
   begin=end;
   const char* cFound;
   const char* endAllowed=(allowedChars ? &allowedChars[strlen(allowedChars)] : 0);
   while (IsName(*end) || allowedChars 
      && 0!=(cFound=strchr(allowedChars, *end)) && cFound!=endAllowed)
      end++;
   Bool_t bGotSomething=!(end==begin);
   while (isspace(*end)) end++;
   step+=end-begin;
   return bGotSomething;
}

 Bool_t THtml::FindMethodImpl(TString strMethFullName, TList& listMethSameName, 
                             TList& listArgs, TParseStack& parseStack, 
                             Bool_t done) const {
// find strArgs in all methods in listMethSameName
// remove those from the list that don't fit
// Return kFALSE if error

   TString strArg(listArgs.GetSize() ? ((TObjString*)listArgs.Last())->String().Data() : "");
   TString strArgPart;
   Int_t iArgSize=listArgs.GetSize();
#ifdef DEBUG_METH_PARSE
printf("Parsing meth %s ... ",strMethFullName.Data());
#endif
   TMethod* meth=(TMethod*)listMethSameName.First();
   TClass* clmeth=meth ? (TClass*)meth->GetClass() : 0;
   // args are in class's context, add "dummy" classdef ctx
   Bool_t bAddedDummyClassDef=!(parseStack.Context()==TParseStack::kBlock
      && parseStack.BlockSpec()==TParseStack::kClassDecl);
   if (clmeth) {
      if (bAddedDummyClassDef) 
         parseStack.Push(TParseStack::kBlock, TParseStack::kClassDecl, clmeth->GetName());
   }
   else return kFALSE;

   TIter iMethSameName(&listMethSameName);
   while ((meth=(TMethod*)iMethSameName())) {
      if (done && meth->GetListOfMethodArgs()->GetSize()!=iArgSize 
         || meth->GetListOfMethodArgs()->GetSize()<iArgSize) {
         // this method has the wrong num of args, remove it
            listMethSameName.Remove(meth);
         continue;
      }

      // iArgSize==0 only when parsing done (otherwise we're not called)
      // if meth also has 0 params then it's a candidate
      if(iArgSize==0) {
         if(meth->GetListOfMethodArgs()->GetSize()>0) 
            listMethSameName.Remove(meth);
         continue;
      }

      // we could check on listMeth.GetSize()==1 here and just return,
      // but we have to check the args first to see whether this is a call
      // of an impl
      TMethodArg* ma=(TMethodArg*)meth->GetListOfMethodArgs()->At(iArgSize-1);
      const char* endArg=strArg;
      TString strThisArg(ma->GetFullTypeName());
      // parse part by part, as the arg parts may be reshuffled by cint
      // e.g. "type* const*" becomes "type* * const"
      while (*endArg) {
         strArgPart="";
	 Int_t step;
         if (!ParseWord(endArg, step, strArgPart, ":")) {
	    endArg+=step;
            // we have a non-word char here, just simulate it being a word
            strArgPart=*endArg;
            endArg++;
         }
	 else
 	    endArg+=step;
        // try and see if there is a FQI for this param type known to CInt
         parseStack.FindType(strArgPart);
         Int_t iPos=strThisArg.Index(strArgPart);
         if (iPos==kNPOS) {
            TDictionary* dict=GetType(strArgPart);
            if (dict && dict->IsA()==TLocalType::Class()
               && ((TLocalType*) dict)->Spec()==TLocalType::kTypedef)
               iPos=strThisArg.Index(((TLocalType*) dict)->RealTypedefName());
         }
         if (iPos==kNPOS) {
            if (*endArg) {
               // this method doesn't have this argument, 
               // and it's not the last word (i.e. can't be a parameter name)
               // remove method from list of candidates
               listMethSameName.Remove(meth);
               meth=0;
            }
            break;
         } else
            strThisArg.Remove(iPos, strArgPart.Length());
         while (isspace(*endArg)) endArg++;
      }

      // if the meth has already been removed go to next method in list
      if (!meth) continue;

      // there might be a param name left, skip it
      Int_t step;
      ParseWord(strThisArg.Data(), step);
      endArg=strThisArg.Data()+step;
      if (strlen(endArg)>0) {
         listMethSameName.Remove(meth);
         continue;
      }
   }

   // remove dummy class def block
   if (bAddedDummyClassDef) 
      parseStack.PopAndDel();

   if (listMethSameName.GetSize()==0) {
#ifdef DEBUG_METH_NOTFOUND
      printf("PARSE METHOD Can't find method named %s with correct number of params (%c=%d) in line\n%s\n", 
         strMethFullName.Data(), (done ? '=' : '>'), iArgSize, fLine);
#endif
      return kFALSE;
   }
#ifdef DEBUG_METH_PARSE
printf("success.\n");
#endif
   return kTRUE;
}

 TPaveText* THtml::GetDocPave(TDictionary* dict){

   TPaveText *pt = new TPaveText();
   pt->SetName(dict->GetName());
   pt->SetLabel("");
   TDocElement* docel=GetDocElement(dict);
   if (docel) {
      Int_t len = strlen(docel->GetDoc());
      char *tmpstr = new char[len+1];
      strcpy(tmpstr, docel->GetDoc());
      char* pos=tmpstr;
      char* end;
      while((end=strchr(pos,'\n'))) {
         *end=0;
         pt->AddText(pos);
         *end='\n';
         pos=end+1;
      }
      // some remaining string without newline?
      if (strlen(pos)) 
         pt->AddText(pos);
      delete [] tmpstr;
   }

   return pt;
}

 TMap* THtml::MakeHelp(TClass* cl)
{
   ExtractClassDocumentation(cl);
   TMap* docmap=new TMap(); // map TDict* -> TPaveText

   docmap->Add(cl, GetDocPave(cl));

   TList* listMem=cl->GetListOfMethods();
   if (listMem) {
      TIter iMeth(listMem);
      TMethod* meth=0;
      while ((meth=(TMethod*) iMeth())) 
         docmap->Add(meth, GetDocPave(meth));
   } // if listMem
            
   listMem=cl->GetListOfDataMembers();
   if (listMem) {
      TIter iMem(listMem);
      TDataMember* mem=0;
      while ((mem=(TDataMember*) iMem())) {
         TPaveText* pt=new TPaveText();
         pt->SetName(mem->GetName());
         pt->AddText(mem->GetTitle());
         docmap->Add(mem, pt);
      }
   } // if listMem
   
   return docmap;
}

// Helper class implementation:
THtml::TParseStack::~TParseStack(){
         if (fStack.GetSize()>1) {
            TString strStack;
            TIter psi(&fStack);
            TParseElement* pe;
            while ((pe=(TParseElement*) psi())) {
               strStack+=" - ";
               switch (pe->Context()){
                  case kComment: strStack+="Comment"; break; 
                  case kBlock: strStack+="Block ("; 
                     switch (pe->BlockSpec()){
                        case kClassDecl: 
                           strStack+="ClassDecl ";
                           strStack+=pe->GetName(); break;
                        case kNamespace: 
                           strStack+="Namespace)"; 
                           strStack+=pe->GetName(); break;
                        case kBlkUndefined: strStack+="BlkUndefined: "; 
                           strStack+=pe->GetName(); strStack+="***";
                           strStack+=pe->GetTitle(); strStack+="***";
                           break;
		     default: break;
                     }
                     strStack+=")";
                     break; 
                  case kParameter: strStack+="Parameter"; break; 
                  case kTemplate: strStack+="Template"; break; 
                  case kArray: strStack+="Array"; break; 
                  case kString: strStack+="String"; break; 
	       default: break;
               }
               strStack+="n";
               fStack.Remove(pe);
            }
            strStack.Remove(strStack.Length()-3);

//printf("Warning in <TParseStack::~TParseStack>: Stack not empty! Elements:n %sn", strStack.Data());
         }
         fStack.Remove(fStack.LastLink());
      };


ROOT page - Class index - Top of the page

This page has been automatically generated. If you have any comments or suggestions about the page layout send a mail to ROOT support, or contact the developers with any questions or problems regarding ROOT.