Click here to Skip to main content
15,881,852 members
Articles / Desktop Programming / MFC

Include File Hierarchy Viewer

Rate me:
Please Sign up or sign in to vote.
4.84/5 (108 votes)
29 Jul 2003CPOL8 min read 379.9K   4.5K   151  
A tool to view the include file hierarchy of your source code.
//====================================================================
// Although great care has gone into developing this software,
// it is provided without any guarantee of reliability, accuracy
// of information, or correctness of operation.  I am not responsible
// for any damages that may occur as a result of using this software.
// Use this software entirely at your own risk.
// Copyright 2003, Chris Richardson
//
// Description: Classes used to read VC6 projects and workspaces.
//
//====================================================================

#include "stdafx.h"
#include "VC6Objects.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

using namespace std;

//////////////////////////////////////////////////////////////////////
// Strings we care about in the workspace file.
//////////////////////////////////////////////////////////////////////

static const TCHAR s_pszProject[] = _T("Project:");
static int s_iProjectLen = sizeof( s_pszProject ) - 1;

//////////////////////////////////////////////////////////////////////
// Strings we care about in the project file.
//////////////////////////////////////////////////////////////////////

static const TCHAR s_pszProjectFile[] = _T("# Microsoft Developer Studio Project File");
static const int s_iProjectFileLen = sizeof( s_pszProjectFile ) - 1;

static const TCHAR s_pszBeginProject[] = _T("# Begin Project");
static const int s_iBeginProjectLen = sizeof( s_pszBeginProject ) - 1;

static const TCHAR s_pszBeginTarget[] = _T("# Begin Target");
static const int s_iBeginTargetLen = sizeof( s_pszBeginTarget ) - 1;

static const TCHAR s_pszName[] = _T("# Name");
static const int s_iNameLen = sizeof( s_pszName ) - 1;

static const TCHAR s_pszBeginSource[] = _T("# Begin Source File");
static const int s_iBeginSourceLen = sizeof( s_pszBeginSource ) - 1;

static const TCHAR s_pszIf[] = _T("IF");
static const int s_iIfLen = sizeof( s_pszIf ) - 1;

static const TCHAR s_pszElseIf[] = _T("ELSEIF");
static const int s_iElseIfLen = sizeof( s_pszElseIf ) - 1;

static const TCHAR s_pszEndIf[] = _T("ENDIF");
static const int s_iEndIfLen = sizeof( s_pszEndIf ) - 1;

static const TCHAR s_pszAddCPP[] = _T("# ADD CPP");
static const int s_iAddCPPLen = sizeof( s_pszAddCPP ) - 1;

static const TCHAR s_pszSubtractCPP[] = _T("# SUBTRACT CPP");
static const int s_iSubtractCPPLen = sizeof( s_pszSubtractCPP ) - 1;

static const TCHAR s_pszSource[] = _T("SOURCE=");
static const int s_iSourceLen = sizeof( s_pszSource ) - 1;

static const TCHAR s_pszEndSource[] = _T("# End Source File");
static const int s_iEndSourceLen = sizeof( s_pszEndSource ) - 1;


//////////////////////////////////////////////////////////////////////
// CVC6Settings Implementation.
//////////////////////////////////////////////////////////////////////

CVC6Settings::CVC6Settings()
{
}
//
// ------------------------------------------------------------------
//
CVC6Settings::~CVC6Settings()
{
}
//
// ------------------------------------------------------------------
//
TCHAR * CVC6Settings::ExtractOptionAndAppend( CDSSettingArray &         p_roSettings,
                                              TCHAR *                   p_pszOpt,
                                              CDSSetting::DSSettingType p_eType )
{
   TCHAR a_cOption = *p_pszOpt;

   p_pszOpt++;
   while( _istspace( *p_pszOpt ) )
      p_pszOpt++;
   
   TCHAR a_cEndChar = _T('\"');
   if( *p_pszOpt == _T('\"') )
   {
      p_pszOpt++;
   }
   else
      a_cEndChar = _T(' ');

   // Extract the option.
   TCHAR * a_pszSep = _tcschr( p_pszOpt, a_cEndChar );
   if( !a_pszSep )
   {
      a_pszSep = _tcschr( p_pszOpt, _T('\n') );
      if( !a_pszSep )
         a_pszSep = p_pszOpt;
   }
   *a_pszSep = 0;
   
   if( *p_pszOpt )
   {
      p_roSettings.push_back( CDSSetting( p_eType, p_pszOpt ) );
   }

   return a_pszSep+1;
}
//
// ------------------------------------------------------------------
//
void CVC6Settings::ParseCPPCommandLine( TCHAR * p_pszLine )
{
   CDSSettingArray * a_poSettings = NULL;
   const TCHAR * a_pszStart = NULL;
   if( !_tcsncmp( p_pszLine, s_pszAddCPP, s_iAddCPPLen ) )
   {
      a_poSettings = &c_oAddSettings;
      
      a_pszStart = p_pszLine + s_iAddCPPLen;
   }
   else
   if( !_tcsncmp( p_pszLine, s_pszSubtractCPP, s_iSubtractCPPLen ) )
   {
      a_poSettings = &c_oSubtractSettings;
      
      a_pszStart = p_pszLine + s_iSubtractCPPLen;
   }
   else
      // Somebody passed us an invalid line.
      return;
   
   // Read the preprocessor macro definitions and include directories from the line.
   TCHAR * a_pszSep = p_pszLine;
   while( 1 )
   {
      a_pszSep = _tcschr( a_pszSep, _T('/') );
      if( !a_pszSep )
         break;
      
      a_pszSep++;
      
      if( *a_pszSep == _T('X') )
      {
         // Ignore standard include directories.
         a_poSettings->push_back( CDSSetting( CDSSetting::SETTING_FLAG, _T("X") ) );
      }
      else
      if( *a_pszSep == _T('D') )
      {
         // Define a symbol.

         a_pszSep = ExtractOptionAndAppend( *a_poSettings, a_pszSep, CDSSetting::SETTING_DEFINE );
      }
      else
      if( *a_pszSep == _T('U') )
      {
         // Undefine a symbol.

         a_pszSep = ExtractOptionAndAppend( *a_poSettings, a_pszSep, CDSSetting::SETTING_UNDEFINE );
      }
      else
      if( *a_pszSep == _T('u') )
      {
         // Undefine all symbols.
         a_poSettings->push_back( CDSSetting( CDSSetting::SETTING_FLAG, _T("u") ) );
      }
      else
      if( *a_pszSep == _T('I') )
      {
         // Additional include directory.
         a_pszSep = ExtractOptionAndAppend( *a_poSettings, a_pszSep, CDSSetting::SETTING_INCLUDE );
      }
   }
}
//
// ------------------------------------------------------------------
//
void CVC6Settings::ProcessAddSetting( const CDSSetting * p_poSetting,
                                      CStdStringArray & p_roStdIncludes,
                                      CStdStringArray & p_roIncludes,
                                      CStdStringArray & p_roDefines,
                                      CStdStringArray & p_roNewIncludes,
                                      CStdStringArray & p_roNewDefines ) const
{
   const TCHAR * a_pszText = p_poSetting->GetText();

   switch( p_poSetting->GetType() )
   {
      case CDSSetting::SETTING_FLAG:
         if( *a_pszText == _T('X') )
         {
            // Ignore standard include directories.
            p_roStdIncludes.clear();
         }
         else
         if( *a_pszText == _T('u') )
         {
            // Undefine all symbols
            p_roDefines.clear();
         }
         break;

      case CDSSetting::SETTING_DEFINE:
         // Add a new definition.
         p_roNewDefines.push_back( a_pszText );
         break;

      case CDSSetting::SETTING_UNDEFINE:
      {
         // Remove the definition.
         for( int i = 0; i<p_roDefines.size(); ++i )
         {
            if( !p_roDefines[i].compare( a_pszText ) )
            {
               p_roDefines.erase( &p_roDefines[i] );
               break;
            }
         }
         break;
      }

      case CDSSetting::SETTING_INCLUDE:
         // Add a new include dir.
         p_roNewIncludes.push_back( a_pszText );
         break;

      default:
         assert( false );
         break;
   }
}
//
// ------------------------------------------------------------------
//
void CVC6Settings::ParseSettings( CDSSettings * p_poFileSettings,
                                  CDSParsedSettings & p_roSettings ) const
{
   CVC6Settings * a_poFileSettings = static_cast<CVC6Settings*>(p_poFileSettings);

   CStdStringArray & a_roIncludes = p_roSettings.GetIncludes();
   CStdStringArray & a_roDefines = p_roSettings.GetDefines();
   
   CStdStringArray a_oStdIncludes;
   CDSWorkspace::GetStdIncludes( a_oStdIncludes );
   CStdStringArray a_oNewIncludes;
   CStdStringArray a_oNewDefines;

   // Parse our settings.
   CDSSettingArray::const_iterator a_oIter = c_oAddSettings.begin();
   while( a_oIter != c_oAddSettings.end() )
   {
      const CDSSetting & a_roSetting = *a_oIter;
      a_oIter++;

      // Check if the setting is in any of the file's "subtract" settings.
      if( a_poFileSettings )
      {
         if( a_poFileSettings->IsSubtractedSetting( a_roSetting ) )
            continue;
      }

      ProcessAddSetting( &a_roSetting,
                         a_oStdIncludes,
                         a_roIncludes,
                         a_roDefines,
                         a_oNewIncludes,
                         a_oNewDefines );

   }

   if( a_poFileSettings )
   {
      // Parse the file's settings.
      CDSSettingArray::iterator a_oIter = a_poFileSettings->c_oAddSettings.begin();
      while( a_oIter != a_poFileSettings->c_oAddSettings.end() )
      {
         CDSSetting & a_roSetting = *a_oIter;
         a_oIter++;

         ProcessAddSetting( &a_roSetting,
                            a_oStdIncludes,
                            a_roIncludes,
                            a_roDefines,
                            a_oNewIncludes,
                            a_oNewDefines );
      }
   }

   // Add the new settings to the output settings.
   a_roIncludes.insert( a_roIncludes.begin(), a_oStdIncludes.begin(), a_oStdIncludes.end() );
   a_roIncludes.insert( a_roIncludes.end(), a_oNewIncludes.begin(), a_oNewIncludes.end() );
   a_roDefines.insert( a_roDefines.end(), a_oNewDefines.begin(), a_oNewDefines.end() );
}
//
// ------------------------------------------------------------------
//


//////////////////////////////////////////////////////////////////////
// CVC6ConfigurationHolder Implementation.
//////////////////////////////////////////////////////////////////////

CVC6ConfigurationHolder::CVC6ConfigurationHolder()
{
}
//
// ------------------------------------------------------------------
//
CVC6ConfigurationHolder::~CVC6ConfigurationHolder()
{
}
//
// ------------------------------------------------------------------
//
ParseStatus CVC6ConfigurationHolder::ParseIfElseBlock( CStream & p_roStream )
{
   enum{ MAX_LINE_LENGTH = 2048 };

   TCHAR          a_szLine[MAX_LINE_LENGTH] = {0};
   CVC6Settings * a_poSettings = NULL;
   TCHAR          a_szCurrentConfigName[MAX_PATH + 1] = {0};
   
   while( p_roStream.GetS( a_szLine, MAX_LINE_LENGTH ) )
   {
      if( !_tcsncmp( a_szLine + 1, s_pszEndIf, s_iEndIfLen ) )
      {
         // We're done the configuration settings.
         break;
      }
      else
      if( !_tcsncmp( a_szLine + 1, s_pszElseIf, s_iElseIfLen ) ||
          !_tcsncmp( a_szLine + 1, s_pszIf, s_iIfLen ) )
      {
         // I've only seen cases that look like this:
         // !IF  "$(CFG)" == "IncFinder - Win32 Release"
         // so that's what we look for here.
      
         // Get the name of the variable.
         TCHAR a_szVariable[MAX_PATH + 1] = {0};
         TCHAR * a_pszSep = ExtractString( a_szVariable, a_szLine, _T('\"') );
         if( !a_pszSep )
            continue;
      
         if( !_tcscmp( a_szVariable, _T("$(CFG)") ) )
         {
            // Get the value of the variable that's being tested.  This is the name of the configuration
            // that the next set of options is specified for.
            a_pszSep = ExtractString( a_szCurrentConfigName, a_pszSep, _T('\"') );
            
            a_poSettings = NULL;
         }
      }
      else
      if( !_tcsncmp( a_szLine, s_pszAddCPP, s_iAddCPPLen ) || !_tcsncmp( a_szLine, s_pszSubtractCPP, s_iSubtractCPPLen ) )
      {
         // The current line is specifiying C++ compiler command line parameters.
         if( !a_poSettings )
         {
            // Create the settings object, since we need it now.
            a_poSettings = new CVC6Settings;
            c_oSettings.insert( CDSMapConfigToSettings::value_type( a_szCurrentConfigName, a_poSettings ) );
         }
         a_poSettings->ParseCPPCommandLine( a_szLine );
      }
   }
   return PARSE_STATUS_OK;
}
//
// ------------------------------------------------------------------
//


//////////////////////////////////////////////////////////////////////
// CVC6File Implementation.
//////////////////////////////////////////////////////////////////////

CVC6File::CVC6File()
{
   c_poConfigHolder = new CVC6ConfigurationHolder;
}
//
// ------------------------------------------------------------------
//
CVC6File::~CVC6File()
{
   if( c_poConfigHolder )
   {
      delete c_poConfigHolder;
      c_poConfigHolder = NULL;
   }
}
//
// ------------------------------------------------------------------
//
ParseStatus CVC6File::ReadProject( const TCHAR * p_pszProjectDir,
                                   CStream & p_roStream )
{
   ParseStatus a_eStatus = PARSE_STATUS_OK;

   enum{ MAX_LINE_LENGTH = 2048 };
   TCHAR a_szLine[MAX_LINE_LENGTH] = {0};
   while( p_roStream.GetS( a_szLine, MAX_LINE_LENGTH ) )
   {
      if( !_tcsncmp( a_szLine, s_pszSource, s_iSourceLen ) )
      {
         // Find the separator before the source file path.
         TCHAR * a_pszSep = a_szLine + s_iSourceLen;
         if( !*a_pszSep )
            continue;
         
         if( *a_pszSep == _T('\"') )
         {
            a_pszSep++;
            
            // Find the end quote and remove it.
            TCHAR * a_pszEndSep = _tcschr( a_pszSep, _T('\"') );
            if( !a_pszEndSep )
               continue;

            *a_pszEndSep = 0;
         }
         else
         {
            // Remove the newline from the end of the line.
            int a_iLen = _tcslen( a_pszSep );
            if( a_iLen && a_pszSep[a_iLen-1] == _T('\n') )
               a_pszSep[a_iLen-1] = _T('\0');
         }
         
         // Get the full path of the project from the relative path.
         c_sPath = GetFullPath( p_pszProjectDir, a_pszSep );

         DetermineName();
      }
      else
      if( !_tcsncmp( a_szLine, _T("!"), _tcslen( _T("!") ) ) )
      {
         // These are expressions or MESSAGE statements.
         // We only care about the expressions.
         if( !_tcsncmp( a_szLine + 1, s_pszIf, s_iIfLen ) )
         {
            unsigned long a_ulPos = p_roStream.GetPos();
            a_ulPos -= _tcslen( a_szLine ) + sizeof( TCHAR );
            p_roStream.SetPos( a_ulPos );
            
            a_eStatus = static_cast<CVC6ConfigurationHolder*>(c_poConfigHolder)->ParseIfElseBlock( p_roStream );
         }
      }
      else
      if( !_tcsncmp( a_szLine, s_pszEndSource, s_iEndSourceLen ) )
      {
         // We're done reading the current file.
         break;
      }
   }

   return PARSE_STATUS_OK;
}
//
// ------------------------------------------------------------------
//


//////////////////////////////////////////////////////////////////////
// CVC6Project Implementation.
//////////////////////////////////////////////////////////////////////

CVC6Project::CVC6Project()
{
}
//
// ------------------------------------------------------------------
//
CVC6Project::~CVC6Project()
{
}
//
// ------------------------------------------------------------------
//
ParseStatus CVC6Project::ReadProject( const TCHAR * p_pszPath )
{
   ParseStatus a_eStatus = PARSE_STATUS_OK;
   CFILEStream a_oStream;

   // First open a stream on the file.
   a_eStatus = a_oStream.Open( p_pszPath );
   if( a_eStatus != PARSE_STATUS_OK )
      return a_eStatus;
   
   // Get the directory and drive information out of the project path.
   TCHAR a_szDriveAndDir[MAX_PATH + 1] = {0};
   TCHAR a_szDirectory[MAX_PATH + 1] = {0};
   _tsplitpath( p_pszPath, a_szDriveAndDir, a_szDirectory, NULL, NULL );
   _tcscat( a_szDriveAndDir, a_szDirectory );

   CVC6ConfigurationHolder a_oConfigurationSettings;
   
   BOOL a_bInProject = FALSE;
   enum{ MAX_LINE_LENGTH = 2048 };
   TCHAR a_szLine[MAX_LINE_LENGTH] = {0};
   while( a_oStream.GetS( a_szLine, MAX_LINE_LENGTH ) )
   {
      if( !_tcsncmp( a_szLine, s_pszProjectFile, s_iProjectFileLen ) )
      {
         // Extract the project name.
         TCHAR a_szName[MAX_PATH + 1] = {0};
         ExtractString( a_szName, a_szLine, _T('\"') );
         c_sName = a_szName;
      }

      if( !_tcsncmp( a_szLine, s_pszBeginProject, s_iBeginProjectLen ) )
      {
         // We think we have a valid project.
         a_bInProject = TRUE;
      }

      // If we haven't reached the "# Begin Project" yet, we don't know what kind of information we could have,
      // so we won't bother parsing it.
      if( !a_bInProject )
         continue;
      
      if( !_tcsncmp( a_szLine, s_pszBeginTarget, s_iBeginTargetLen ) )
      {
         // Read the targets (configurations).
         while( 1 )
         {
            unsigned long a_ulPos = a_oStream.GetPos();

            if( !a_oStream.GetS( a_szLine, MAX_LINE_LENGTH ) )
               break;
            
            if( !_tcslen( a_szLine ) || a_szLine[0] == _T('\n') )
               continue;

            if( !_tcsncmp( a_szLine, s_pszName, s_iNameLen ) )
            {
               // Extract the name of the target.
               TCHAR * a_pszTarget = ExtractString( NULL, a_szLine, _T('\"') );
               if( !a_pszTarget )
                  continue;

               // Lookup the settings we read for the configuration.
               CDSMapConfigToSettings & a_roSettings = a_oConfigurationSettings.GetSettings();

               CDSMapConfigToSettings::iterator a_oIter = a_roSettings.find( a_pszTarget );
               CDSSettings * a_poSettings = a_oIter->second;
               if( a_oIter != a_roSettings.end() )
               {
                  // Create a new configuration.
                  CDSConfiguration * a_poConfiguration = new CDSConfiguration( a_pszTarget, a_poSettings );
                  c_oConfigurations.push_back( a_poConfiguration );
                  
                  // Remove the settings object from the configuration holder, since the new configuration object is storing them.
                  a_roSettings.erase( a_oIter );
               }
               else
               {
                  // There is no settings object for the configuration.  Looks like a problem (maybe a manually added target string?).
                  // ?? Not sure how I want to handle it.
               }
            }
            else
            {
               // Done reading the targets, push the stream back to the position it was at before we read the current line.
               a_oStream.SetPos( a_ulPos );
               break;
            }
         }
      }
      else
      if( !_tcsncmp( a_szLine, _T("!"), _tcslen( _T("!") ) ) )
      {
         // These are expressions or MESSAGE statements.
         // We only care about the expressions.
         if( !_tcsncmp( a_szLine + 1, s_pszIf, s_iIfLen ) )
         {
            unsigned long a_ulPos = a_oStream.GetPos();
            a_ulPos -= _tcslen( a_szLine ) + sizeof( TCHAR );
            a_oStream.SetPos( a_ulPos );
            
            a_eStatus = a_oConfigurationSettings.ParseIfElseBlock( a_oStream );
         }
      }
      else
      if( !_tcsncmp( a_szLine, s_pszBeginSource, s_iBeginSourceLen ) )
      {
         // Create a new source file, and have it read it's info from the project file.
         CVC6File * a_poFile = new CVC6File;
         a_eStatus = a_poFile->ReadProject( a_szDriveAndDir, a_oStream );
         c_oFiles.push_back( a_poFile );
      }
   }

   return PARSE_STATUS_OK;
}
//
// ------------------------------------------------------------------
//


//////////////////////////////////////////////////////////////////////
// CVC6Workspace Implementation.
//////////////////////////////////////////////////////////////////////

bool CVC6Workspace::s_bHaveStandardIncludes = false;

CVC6Workspace::CVC6Workspace()
{
   if( !s_bHaveStandardIncludes )
   {
      // Read the standard include directories from the registry.

      CRegKey a_oKey;
      if( GetRegKey( _T("HKEY_CURRENT_USER\\Software\\Microsoft\\DevStudio\\6.0\\Build System\\Components\\Platforms\\Win32 (x86)\\Directories"), a_oKey ) )
      {
         // We found the key, now read the directory list and parse it.
         enum { MAX_INCLUDE_DIRS = MAX_PATH * 20 };
         TCHAR a_szIncludeDirs[MAX_INCLUDE_DIRS] = {0};
         unsigned long a_ulCount = MAX_INCLUDE_DIRS;
         if( a_oKey.QueryValue( a_szIncludeDirs, _T("Include Dirs"), &a_ulCount ) == ERROR_SUCCESS )
         {
            ParseSeparatedList( a_szIncludeDirs, _T(";"), s_oStdIncludes );
         }
      }

      // Don't need to read them again.
      s_bHaveStandardIncludes = true;
   }
}
//
// ------------------------------------------------------------------
//
CVC6Workspace::~CVC6Workspace()
{
}
//
// ------------------------------------------------------------------
//
ParseStatus CVC6Workspace::ParseLine( TCHAR * p_pszLine )
{
   ParseStatus a_eStatus = PARSE_STATUS_OK;

   if( !_tcsncmp( p_pszLine, s_pszProject, s_iProjectLen ) )
   {
      // Find the project name.
      //
      TCHAR a_szProjectName[MAX_PATH + 1] = {0};
      TCHAR * a_pszEndSep = ExtractString( a_szProjectName, p_pszLine, _T('\"') );
      if( !a_pszEndSep )
         return PARSE_STATUS_OK;
      
      // Find the path of the project.  We can't use ExtractString because there are a few different cases to deal with.
      //
      TCHAR * a_pszSep = _tcschr( a_pszEndSep, _T('=') );
      if( !a_pszSep )
         return PARSE_STATUS_OK;

      // Not all project paths have quotes around them.
      a_pszEndSep = NULL;
      if( *++a_pszSep == _T('\"') )
      {
         // The current project path should have an end quote.
         a_pszEndSep = _tcschr( ++a_pszSep, _T('\"') );
         if( !a_pszEndSep )
            return PARSE_STATUS_OK;
      }
      else
      {
         // The project path doesn't have a quote, so search for the "- Package Owner" beginning.
         a_pszEndSep = _tcschr( a_pszSep, _T('-') );
         if( !a_pszEndSep )
            return PARSE_STATUS_OK;
         else
            a_pszEndSep--;
      }

      *a_pszEndSep = 0;
      
      // Get the full path of the project from the relative path.
      CTCharString a_sProjectPath = GetFullPath( c_sDirectory.c_str(), a_pszSep );
      
      a_eStatus = ReadOneProject( a_sProjectPath.c_str() );
   }

   return PARSE_STATUS_OK;
}
//
// ------------------------------------------------------------------
//
ParseStatus CVC6Workspace::ReadOneProject( const TCHAR * p_pszProjectPath )
{
   // Create a project and parse it's contents.
   CVC6Project * a_poProject = new CVC6Project;
   ParseStatus a_eStatus = a_poProject->ReadProject( p_pszProjectPath );
   if( a_eStatus != PARSE_STATUS_OK )
      delete a_poProject;
   else
      c_oProjects.push_back( a_poProject );
   
   return a_eStatus;
}
//
// ------------------------------------------------------------------
//

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
United States United States
I like to program, I like to sail.

Comments and Discussions