Click here to Skip to main content
15,885,875 members
Articles / Desktop Programming / MFC

LintProject - Improving the Usability of PC-Lint with Visual C++ Solutions and Projects

,
Rate me:
Please Sign up or sign in to vote.
4.83/5 (36 votes)
29 Jan 2009CPOL13 min read 279.6K   1.7K   52  
Utility to run PC-Lint on Visual C++ solutions and projects, generating XML and HTML reports of the results.
/************************************************************************
 *
 *  Description : CProjectLintAnalyser - class for analysing Visual C++
 *                projects (.vcproj or .dsp) using the PC-Lint code analysis
 *                tool.
 *
 *     (c) Copyright 2004-2008 by Anna-Jayne Metcalfe (anna@riverblade.co.uk)
 *     and Beth Mackenzie (beth@riverblade.co.uk) / Riverblade Limited
 *
 *  Licence Terms:
 *
 *     This code may be freely reused, subject to the licence terms below.
 *     Please do let us know of any bugs you find or improvements you make,
 *     so that we can pass them on to the rest of the development community.
 *
 *     This code is free software; you can redistribute it and/or
 *     modify it under the terms of the Code Project Open License (CPOL)
 *     version 1.0 (http://www.codeproject.com/info/cpol10.aspx).
 *
 *     This code is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     Code Project Open Licence for further details.
 *
 ************************************************************************
 *    $Archive: /Projects/Applications/LintProject/1.4/ProjectLintAnalyser.cpp $
 *   $Revision: 30 $
 *       $Date: 7/01/09 12:53 $
 *     $Author: Anna $
 * 
 * $Nokeywords: $
 ************************************************************************/

/// \file	
/// \brief CProjectLintAnalyser class implementation.

#include "StdAfx.h"
#include "Shared/ModuleVersion.h"
#include "Shared/StringUtils.h"
#include "Shared/PathUtils.h"
#include "Shared/FileUtils.h"
#include "Shared/ResourceUtils.h"
#include "Shared/BrowserUtils.h"
#include "Shared/SplitPath.h"
#include "Shared/ProjectFileReader.h"
#include "Resource.h"


#include "SolutionLintAnalyser.h"
#include "FileLintAnalyser.h"

#include "ProjectLintAnalyser.h"


using namespace std;



CProjectLintAnalyser::CProjectLintAnalyser(void)
	:	m_sLintFolder(_T("") ),
		m_sProjectFilePathName(_T("") ),
		m_sProjectFileFolder(_T("") ),
		m_sSolutionFileFolder(_T("") ), // [Alex McCarthy 21.10.2008]
		m_sPlatformName(_T("") ), // [Alex McCarthy 23.12.2008]
		m_sLintConfigFile(_T("") ),
		m_sAdditionalProjectParams(_T("") ),
		m_sAdditionalParams(_T("") ),
		m_sConfiguration(_T("") ),
		m_sConfigurationString(_T("") ),
		m_sIntermediateFilesFolder(_T("") ),
		m_bShowHtml(false),
		m_sSolutionFileResultFolder(_T("") ),
		m_sProjectName(_T("") ),
		m_sResultsFileFolder(_T("") ),
		m_timeStarted(),
		m_nWarningCount(0),
		m_sResultsIndexFilePathName(_T("") ),
		m_sElapsedTime(_T("") ),
		m_arrayImplementationFilePathNames(),
		m_arrayConfigurations(),
		m_arrayFileResults(),
		m_bAnalysed(false),
		m_pSolutionAnalyser(NULL)		// Pointer to the containing solution analyser. Only needed for WriteIndexFile() callbacks.
{
	m_timeStarted.SetStatus(COleDateTime::null);
}


CProjectLintAnalyser::CProjectLintAnalyser(const CProjectLintAnalyser& src)
	:	m_sLintFolder(_T("") ),
		m_sProjectFilePathName(_T("") ),
		m_sProjectFileFolder(_T("") ),
		m_sSolutionFileFolder(_T("") ), // [Alex McCarthy 21.10.2008]
		m_sPlatformName(_T("") ), // [Alex McCarthy 23.12.2008]
		m_sLintConfigFile(_T("") ),
		m_sAdditionalProjectParams(_T("") ),
		m_sAdditionalParams(_T("") ),
		m_sConfiguration(_T("") ),
		m_sConfigurationString(_T("") ),
		m_sIntermediateFilesFolder(_T("") ),
		m_bShowHtml(false),
		m_sSolutionFileResultFolder(_T("") ),
		m_sProjectName(_T("") ),
		m_sResultsFileFolder(_T("") ),
		m_timeStarted(),		//lint !e747 (Information -- Significant prototype coercion (arg. no. 1) int to long long)
		m_nWarningCount(0),
		m_sResultsIndexFilePathName(_T("") ),
		m_sElapsedTime(_T("") ),
		m_arrayImplementationFilePathNames(),
		m_arrayConfigurations(),
		m_arrayFileResults(),
		m_bAnalysed(false),
		m_pSolutionAnalyser(NULL)				// Pointer to the containing solution analyser. Only needed for WriteIndexFile() callbacks.
{
	*this = src;
}


CProjectLintAnalyser::CProjectLintAnalyser(	CSolutionLintAnalyser* pSolutionAnalyser,
											const CString& sProjectFilePathName,
											const CString& sSolutionFileResultFolder,
											const CString& sResultsFileFolder,
											const CString& sSolutionFileFolder, // [Alex McCarthy 21.10.2008]
											const CString& sLintConfigFile,
											const CString& sAdditionalProjectParams,
											const CString& sAdditionalParams,
											const CString& sConfiguration)
	:	m_sLintFolder(_T("") ),
		m_sProjectFilePathName(sProjectFilePathName),
		m_sProjectFileFolder(_T("") ),
		m_sSolutionFileFolder(sSolutionFileFolder), // [Alex McCarthy 21.10.2008]
		m_sPlatformName(_T("") ), // [Alex McCarthy 23.12.2008]
		m_sLintConfigFile(sLintConfigFile),
		m_sAdditionalProjectParams(sAdditionalProjectParams),
		m_sAdditionalParams(sAdditionalParams),
		m_sConfiguration(sConfiguration),
		m_sConfigurationString(_T("") ),
		m_sIntermediateFilesFolder(_T("") ),
		m_bShowHtml(false),
		m_sSolutionFileResultFolder(sSolutionFileResultFolder),
		m_sProjectName(_T("") ),
		m_sResultsFileFolder(sResultsFileFolder),
		m_timeStarted(),
		m_nWarningCount(0),
		m_sResultsIndexFilePathName(_T("") ),
		m_sElapsedTime(_T("") ),
		m_arrayImplementationFilePathNames(),
		m_arrayConfigurations(),
		m_arrayFileResults(),
		m_bAnalysed(false),
		m_pSolutionAnalyser(pSolutionAnalyser)		// Pointer to the containing solution analyser. Only needed for WriteIndexFile() callbacks.
{
	m_timeStarted.SetStatus(COleDateTime::null);

	m_sResultsIndexFilePathName = m_sResultsFileFolder + _T("\\index.html");

	// Strip leading/trailing quotes - just in  case
	m_sProjectFilePathName.TrimLeft( _T("\"") );
	m_sProjectFilePathName.TrimRight( _T("\"") );

	m_sResultsFileFolder.TrimLeft( _T("\"") );
	m_sResultsFileFolder.TrimRight( _T("\"") );

	Utils::CSplitPath splitproject(m_sProjectFilePathName);

	m_sProjectFileFolder	= splitproject.GetDrive() + splitproject.GetDirectory();
	m_sProjectName			= splitproject.GetFileName();

	(void)ReadProjectFile(	m_sProjectFilePathName,
							m_sConfiguration,
							m_arrayImplementationFilePathNames,
							m_sConfigurationString,
							m_sIntermediateFilesFolder);

	if (!m_sConfiguration.IsEmpty() )
	{
		// TODO: Check configuration was found

		// [Alex McCarthy 23.12.2008] for VS2002 onwards, the configuration string will be
		// of the form CONFIG|PLATFORM
		const int nPos = m_sConfiguration.Find(_T("|") );

		if (nPos >= 0)
		{
			m_sPlatformName = m_sConfiguration.Mid(nPos + 1);
		}
	}
}


CProjectLintAnalyser::~CProjectLintAnalyser(void)
{
	m_pSolutionAnalyser = NULL;
}


bool CProjectLintAnalyser::operator==(const CProjectLintAnalyser& src) const
{
	if ( (m_sLintFolder == src.m_sLintFolder) &&
		 (m_sProjectFilePathName == src.m_sProjectFilePathName) &&
		 (m_sProjectFileFolder == src.m_sProjectFileFolder) &&
		 (m_sSolutionFileFolder == src.m_sSolutionFileFolder) && // [Alex McCarthy 21.10.2008]
		 (m_sPlatformName == src.m_sPlatformName) && // [Alex McCarthy 23.12.2008]
		 (m_sLintConfigFile == m_sLintConfigFile) &&
		 (m_sAdditionalProjectParams == src.m_sAdditionalProjectParams) &&
		 (m_sAdditionalParams == src.m_sAdditionalParams) &&
		 (m_sConfiguration == src.m_sConfiguration) &&
		 (m_sConfigurationString == src.m_sConfigurationString) &&
		 (m_sIntermediateFilesFolder == src.m_sIntermediateFilesFolder) &&
		 (m_bShowHtml == src.m_bShowHtml) &&
		 (m_sSolutionFileResultFolder == src.m_sSolutionFileResultFolder) &&
		 (m_sProjectName == src.m_sProjectName) &&
		 (m_sResultsFileFolder == src.m_sResultsFileFolder) &&
		 (m_timeStarted == src.m_timeStarted) &&
		 (m_nWarningCount == src.m_nWarningCount) &&
		 (m_sResultsIndexFilePathName == src.m_sResultsIndexFilePathName) &&
		 (m_sElapsedTime == src.m_sElapsedTime) &&
		 (m_bAnalysed == src.m_bAnalysed) &&
		 (m_pSolutionAnalyser == src.m_pSolutionAnalyser) )
	{
		// NB m_arrayImplementationFilePathNames, m_arrayConfigurations and m_arrayFileResults are not currently comparsed
		return true;
	}
	return false;
}


CProjectLintAnalyser& CProjectLintAnalyser::operator=(const CProjectLintAnalyser& src)
{
	if (this != &src)
	{
		m_pSolutionAnalyser			= src.m_pSolutionAnalyser;				//lint !e1555 (Warning -- Direct pointer copy of member within copy assignment operator: 'CProjectLintAnalyser::operator=(const CProjectLintAnalyser &)')
																			// (there is only ever one CSolutionLintAnalyser object, so this is (sort of) OK.
		m_sLintFolder				= src.m_sLintFolder;
		m_bShowHtml					= src.m_bShowHtml;
		m_sSolutionFileResultFolder	= src.m_sSolutionFileResultFolder;
		m_sLintConfigFile			= src.m_sLintConfigFile;
		m_sAdditionalProjectParams	= src.m_sAdditionalProjectParams;
		m_sAdditionalParams			= src.m_sAdditionalParams;

		m_sProjectFilePathName		= src.m_sProjectFilePathName;
		m_sProjectFileFolder		= src.m_sProjectFileFolder;
		m_sProjectName				= src.m_sProjectName;

		m_sSolutionFileFolder		= src.m_sSolutionFileFolder; // [Alex McCarthy 21.10.2008]

		m_sPlatformName				= src.m_sPlatformName; // [Alex McCarthy 23.12.2008]
		m_sConfiguration			= src.m_sConfiguration;
		m_sConfigurationString		= src.m_sConfigurationString;
		m_sIntermediateFilesFolder	= src.m_sIntermediateFilesFolder;

		m_sResultsFileFolder		= src.m_sResultsFileFolder;
		m_sResultsIndexFilePathName	= src.m_sResultsIndexFilePathName;

		m_timeStarted				= src.m_timeStarted;
		m_nWarningCount				= src.m_nWarningCount;
		m_sElapsedTime				= src.m_sElapsedTime;

		m_bAnalysed					= src.m_bAnalysed;

		for (size_t k = 0; k < src.m_arrayConfigurations.GetCount(); k++)
		{
			(void)m_arrayConfigurations.Add(src.m_arrayConfigurations[k] );
		}

		for (size_t m = 0; m < src.m_arrayImplementationFilePathNames.GetCount(); m++)
		{
			(void)m_arrayImplementationFilePathNames.Add(src.m_arrayImplementationFilePathNames[m] );
		}
		
		for (size_t n = 0; n < src.m_arrayFileResults.GetCount(); n++)
		{
			m_arrayFileResults.Add(src.m_arrayFileResults[n] );		// (const CFileLintAnalyser&)
		}
	}
	return *this;
}


bool CProjectLintAnalyser::IsVSDotNetProject(void) const
{
	Utils::CSplitPath split(m_sProjectFilePathName);

	CString sExtension = split.GetExtension();

	sExtension.MakeLower();

	return ( _T(".vcproj") == sExtension);
}


bool CProjectLintAnalyser::SetLintFolder(const CString& sFolder)
{
	m_sLintFolder = sFolder;
	ATLASSERT(!m_sLintFolder.IsEmpty() );

	return !m_sLintFolder.IsEmpty();
}


bool CProjectLintAnalyser::ShowHtml(const bool bShowHtml)
{
	m_bShowHtml = bShowHtml;

	return true;
}


size_t CProjectLintAnalyser::GetConfigurations(CAtlArray<CString>& rarrayConfigurations) const
{
	rarrayConfigurations.RemoveAll();
	(void)rarrayConfigurations.Append(m_arrayConfigurations);

	return rarrayConfigurations.GetCount();
}


int CProjectLintAnalyser::GetWarningCount(void) const
{
	return m_nWarningCount;
}


CString CProjectLintAnalyser::GetResultsIndexFilePathName(void) const
{
	return m_sResultsIndexFilePathName;
}


CString CProjectLintAnalyser::GetResultsIndexFileName(void) const
{
	const int nPos = m_sResultsIndexFilePathName.ReverseFind( _T('\\') );
	ATLASSERT(nPos > 0);
	if (nPos > 0)
	{
		CString sResultsIndexFileName = m_sResultsIndexFilePathName.Mid(nPos);

		sResultsIndexFileName.TrimLeft( _T("\\") );

		return sResultsIndexFileName;
	}
	return _T("");
}


bool CProjectLintAnalyser::Analyse(void)
{
	ATLASSERT(!m_sProjectFilePathName.IsEmpty() );

	bool bResult = false;

	if (!m_sProjectFilePathName.IsEmpty() )
	{
		if (m_arrayImplementationFilePathNames.GetCount() > 0)
		{
			if (!Utils::FolderExists(m_sResultsFileFolder) )
			{
				// Create target and intermediate folders as required
				(void)::SHCreateDirectoryEx(NULL, m_sResultsFileFolder, NULL);
			}

			m_nWarningCount = 0;

			// Set project specific environment variables
			ATLVERIFY(SetEnvironmentVariables() );

			bResult = LintFiles(m_arrayImplementationFilePathNames);
			
			// Clear project specific environment variables
			ATLVERIFY(ResetEnvironmentVariables() );
		}
	}
	return bResult;
}


bool CProjectLintAnalyser::LintFiles(const CAtlArray<CString>& arrayPathNames)
{
	bool bResult = false;

	CString sMsg = _T("Analysing project: ") + m_sProjectName + _T("...");

	cout << CStringA(sMsg).GetString() << endl << endl;

	(void)SetCurrentDirectory(m_sProjectFileFolder);

	// Run PC-Lint to generate the  .lnt file for the project. This file contains the options to use in the analysis
	//
	// We use _tsystem() for this as its the simplest way to run a command line process with redirected output.

	// e.g.	c:\lint\lint-nt.exe <modulename>.dsp >Lint\<modulename>.lnt
	CString sLintOptionsFileName;
	
	if (!m_sConfiguration.IsEmpty() )
	{
		CString sConfiguration = m_sConfiguration;

		sConfiguration.Replace( _T("|"), _T("_") );
		sConfiguration.Replace( _T(" "), _T("_") );

		sLintOptionsFileName.Format( _T("%s_%s.lnt"), m_sProjectName, sConfiguration);
	}
	else
	{
		sLintOptionsFileName.Format( _T("%s.lnt"), m_sProjectName);
	}
	
	sMsg.Format( _T("Generating project options file %s (Configuration: %s)"),
					sLintOptionsFileName,
					!m_sConfigurationString.IsEmpty() ? m_sConfigurationString : CString( _T("Unknown") ) );

	cout << CStringA(sMsg).GetString() << endl << endl;

	CString sParams;
	ATLASSERT(!m_sLintFolder.IsEmpty() );

	CString sConfigurationOption;

	if (!m_sConfigurationString.IsEmpty() )
	{
		if ( IsVSDotNetProject() )
		{
			sConfigurationOption.Format( _T("+d\"Configuration=%s\""), m_sConfigurationString);
		}
		else
		{
			sConfigurationOption.Format( _T("+d\"CFG=%s\""), m_sConfigurationString);
		}
	}

	sParams.Format( _T("+linebuf %s -i\"%s\" %s \"%s\" >\"%s\""),
					m_sAdditionalProjectParams,
					m_sLintFolder,
					sConfigurationOption,
					m_sProjectFilePathName,
					sLintOptionsFileName);

	// Note that if PC-Lint is installed in a folder with a pathname containing spaces, _tsystem()
	// will fail as it doesn't understand long filenames. To work around this, we obtain the short
	// pathname of the PC-Lint executable and pass that to the command interpreter instead. 
	CString sShortPathName;
	(void)GetShortPathName(m_sLintFolder + _T("\\lint-nt.exe"), sShortPathName.GetBuffer(_MAX_PATH + 1), _MAX_PATH);
	sShortPathName.ReleaseBuffer();

	(void)_tsystem(sShortPathName + _T(" ") + sParams);

	m_timeStarted = COleDateTime::GetCurrentTime();

	// Prescan to add all files, so they show as "pending"
	for (size_t n = 0; n < arrayPathNames.GetCount(); n++)
	{
		const CString sPathName = arrayPathNames[n];
		ATLASSERT(!sPathName.IsEmpty() );

		if (!sPathName.IsEmpty() )
		{
			CFileLintAnalyser FileAnalyser(	sPathName,
											m_sProjectFilePathName,
											m_sSolutionFileFolder, // [Alex McCarthy 21.10.2008]
											m_sPlatformName, // [Alex McCarthy 23.12.2008]
											m_sResultsFileFolder,
											m_sLintConfigFile,
											m_sAdditionalParams,
											m_sConfiguration,
											m_sIntermediateFilesFolder);

			ATLVERIFY(FileAnalyser.SetLintFolder(m_sLintFolder) );

			m_arrayFileResults.Add(FileAnalyser);
		}
	}

	ATLVERIFY(WriteResultsIndexFile() );

	if (m_bShowHtml)
	{
		// Open the output file in a browser window
		(void)::ShellExecute(	NULL,
								_T("open"),
								m_sResultsIndexFilePathName,
								NULL,
								NULL,
								SW_SHOWNORMAL);
	}

	// Now do the analysis
	for (size_t n = 0; n < m_arrayFileResults.GetCount(); n++)
	{
		CFileLintAnalyser& rFileAnalyser = m_arrayFileResults.GetAt(n);

		ATLVERIFY(rFileAnalyser.Analyse() );

		const int nWarnings = rFileAnalyser.GetWarningCount();

		m_nWarningCount += nWarnings;

		ATLVERIFY(WriteResultsIndexFile() );

		bResult = true;
	}

	CString sSummary;
	sSummary.Format( _T("%d issues for project %s"), m_nWarningCount, m_sProjectName);

	cout << endl << CStringA(sSummary).GetString() << endl <<endl;

	m_bAnalysed  = true;

	return bResult;
}


int CProjectLintAnalyser::ReadProjectFile(	const CString& sProjectFilePathName,
											const CString& sConfiguration,
											CAtlArray<CString>& rarrayPathNames,
											CString& rsConfigurationString,
											CString& rsIntermediateFilesFolder)
{
	ATLASSERT(!sProjectFilePathName.IsEmpty() );

	CString sErrorMsg;

	if (!Utils::FileExists(sProjectFilePathName) )
	{
		return -1;
	}

	AddInSolutionModel::CProjectFileReader parser;
	const bool bReadOk = parser.Read(sProjectFilePathName);
	ATLASSERT(bReadOk);

	if (bReadOk)
	{
		(void)parser.GetConfigurations(m_arrayConfigurations);

		rsConfigurationString		= parser.GetConfigurationString(sConfiguration);
		rsIntermediateFilesFolder	= parser.GetIntermediateFilesFolder(sConfiguration);

		CAtlArray<CString> arrayCppPathNames, arrayCPathNames;

		(void)parser.GetFiles(arrayCppPathNames, _T(".cpp") );
		(void)parser.GetFiles(arrayCPathNames, _T(".c") );

		rarrayPathNames.RemoveAll();

		(void)rarrayPathNames.Append(arrayCppPathNames);
		(void)rarrayPathNames.Append(arrayCPathNames);

		return static_cast<int>(rarrayPathNames.GetCount() );
	}
	return -1;
}


bool CProjectLintAnalyser::WriteResultsIndexFile(void)
{
	if (m_timeStarted.m_dt > 0.0)
	{
		COleDateTimeSpan timeElapsed(COleDateTime::GetCurrentTime() - m_timeStarted);

		// BR 23/10/2008: Allow for long analysis times without rollover (longer than 59 minutes and 59 seconds)
		m_sElapsedTime = timeElapsed.Format( _T("%H hours, %M minutes, %S seconds") );
	}

	const CString sXslTemplate = Utils::LoadXsl(_Module.GetResourceInstance(), IDR_LINT_PROJECT_TEMPLATE_HTML);
	ATLASSERT(!sXslTemplate.IsEmpty() );
	
	Utils::CSplitPath split(m_sResultsIndexFilePathName);
	
	const CString sXmlResultsFilePathName =	split.GetDrive() +
											split.GetDirectory() +
											split.GetFileName() + _T(".xml");
	ATLVERIFY(WriteXml(sXmlResultsFilePathName) );

	CString sErrorMsg;
	const bool bResult = WriteReport(m_sResultsIndexFilePathName, sXslTemplate, &sErrorMsg);

	if ( !sErrorMsg.IsEmpty() )
	{
		cout << CStringA(sErrorMsg).GetString() << endl;
	}

	if (NULL != m_pSolutionAnalyser)
	{
		ATLVERIFY(m_pSolutionAnalyser->WriteResultsIndexFile() );
	}

	(void)PostWriteResultsFile(m_sResultsIndexFilePathName);

	return bResult;
}


/// \brief Called when the results HTML file has been updated
///
/// Ideally, when this happens any browser windows with the file open should be refreshed
///
bool CProjectLintAnalyser::PostWriteResultsFile(const CString& sResultsIndexFilePathName)
{
	ATLASSERT(Utils::FileExists(sResultsIndexFilePathName) );

	if (m_bShowHtml && Utils::FileExists(sResultsIndexFilePathName) )
	{
		ATLVERIFY(Utils::RefreshAllOpenBrowserWindows(sResultsIndexFilePathName) );

		return true;
	}
	return false;
}	//lint !e1961 (Elective Note -- virtual member function could be made const --- Eff. C++ 3rd Ed. item 3)


/// \brief Write an XML Report stream to the given file
///
/// \param	sPathName		The pathname of the output file
/// \param	psMsg			A pointer to a CString object to return error information. May be NULL
/// \return					true if the file was written sucessfully, false otherwise
///
bool CProjectLintAnalyser::WriteXml(const CString& sPathName, CString* psMsg /*= NULL*/)
{
	ATLASSERT(!sPathName.IsEmpty() );

	bool bResult = false;

	CString sErrorMsg;

	if (!sPathName.IsEmpty() )
	{
		try
		{
			MSXML2::IXMLDOMDocumentPtr ptrDOMDoc;

			// Create empty DOM
			HRESULT hr = ptrDOMDoc.CreateInstance("Msxml2.DOMDocument.3.0");

			if (FAILED(hr) )
			{
				sErrorMsg = _T("Failed to create XML DOM");

				throw _com_error(hr);
			}

			// tell our shape to save its state as XML (into ptrDOMDoc)
			hr = GenerateXml(ptrDOMDoc, &sErrorMsg);

			if (FAILED(hr) )
			{
				sErrorMsg = _T("Failed to save XML stream - ") + sErrorMsg;

				throw _com_error(hr);
			}

			try
			{
				const _variant_t varPathName = static_cast<LPCTSTR>(sPathName);

				ATLVERIFY(SUCCEEDED(ptrDOMDoc->save(varPathName) ) );

				bResult = true;
			}
			catch (const _com_error& e)
			{
				sErrorMsg = static_cast<LPCTSTR>(e.Description() );
			}
		}
		catch (const _com_error& e)
		{
			sErrorMsg = static_cast<LPCTSTR>(e.Description() );
		}
	}
	if (!bResult)
	{
		if ( (NULL != psMsg) && !sErrorMsg.IsEmpty() )
		{
			*psMsg = sErrorMsg;
		}
		ATLTRACE("ERROR in CXmlReportGenerator::WriteXml(): %s\n", sErrorMsg);
	}
	return bResult;
}	//lint !e1762


/// \brief Generate a Report and write it to the given file
///
/// \param	sPathName			The pathname of the output file
/// \param	sXslStyleSheetText	The text of the XSLT stylesheet
/// \param	psMsg				A pointer to a CString object to return error information. May be NULL
/// \return						true if the file was written sucessfully, false otherwise
///
bool CProjectLintAnalyser::WriteReport(	const CString& sPathName,
										const CString& sXslStyleSheetText,
										CString* psMsg /*= NULL*/)
{
	ATLASSERT(!sPathName.IsEmpty() );

	bool bResult = false;

	CString sErrorMsg;

	if (!sPathName.IsEmpty() )
	{
		const CString sReportText = GenerateReportText(	sXslStyleSheetText,
														&sErrorMsg);

		if (!sReportText.IsEmpty() )
		{
			bResult = SUCCEEDED(Utils::WriteTextFile(sPathName, sReportText) );
		}
	}
	if (!bResult)
	{
		if ( (NULL != psMsg) && !sErrorMsg.IsEmpty() )
		{
			*psMsg = sErrorMsg;
		}
		ATLTRACE("ERROR in CProjectLintAnalyser::WriteReport(): %s\n", sErrorMsg);
	}
	return bResult;
}	//lint !e1762


CString CProjectLintAnalyser::GenerateReportText(	const CString& sXslStyleSheetText,
													CString* psMsg /*= NULL*/) const
{
	CString sReportText;

	ATLASSERT(!sXslStyleSheetText.IsEmpty() );
	if (!sXslStyleSheetText.IsEmpty() )
	{
		CString sErrorMsg;
		
		try
		{
			MSXML2::IXMLDOMDocumentPtr ptrDOMDoc;
			MSXML2::IXMLDOMDocumentPtr ptrXSLTDOMDoc;

			// Create empty DOM
			HRESULT hr = ptrDOMDoc.CreateInstance(L"Msxml2.DOMDocument.3.0");
			if (FAILED(hr) )
			{
				sErrorMsg = _T("Failed to create XML DOM");

				throw _com_error(hr);
			}

			hr = ptrXSLTDOMDoc.CreateInstance(L"Msxml2.DOMDocument.3.0");
			if (FAILED(hr) )
			{
				sErrorMsg = _T("Failed to create XSLT DOM");

				throw _com_error(hr);
			}

			ptrXSLTDOMDoc->async = false;
			ATLVERIFY(ptrXSLTDOMDoc->loadXML( _bstr_t(sXslStyleSheetText) ) );

			sReportText = GenerateReportText(	ptrDOMDoc,
												ptrXSLTDOMDoc,
												psMsg);

			// Workaround for XSLT bug: replace <cr><cr><lf> sequences with <cr><lf>
			sReportText.Replace( _T("\r\n"), _T("\n") );
		}
		catch (const _com_error& e)
		{
			const HRESULT hr = e.Error();
			ATLTRACE( _T("Unable to display report. Error code 0x%08x"), hr);
			if (NULL != psMsg)
			{
				psMsg->Format( _T("CProjectLintAnalyser::GenerateReportText()\nException 0x%08x"), hr);
			}
		}
	}
	return sReportText;
}


/// \brief Generate a report using the given XML DOM document and XSLT stylesheet
///
/// \param	ptrDOMDoc			An interface pointer to the XML DOM document containing the stream
/// \param	pXslStyleSheetDoc	An interface pointer to an XML DOM document containing the XSLT template
/// \param	psMsg				A pointer to a CString object to return error information. May be NULL
/// \return						A Cstring object containing the text of the report
///
CString CProjectLintAnalyser::GenerateReportText(	const MSXML2::IXMLDOMDocumentPtr& ptrXmlDoc,
													const MSXML2::IXMLDOMDocumentPtr& ptrXslStyleSheetDoc,
													CString* psMsg /*= NULL*/) const
{
	USES_CONVERSION;

	CString sErrorMsg;
	CString sReportText;

	try
	{
		// Generate the XML in ptrDOMDoc
		const HRESULT hr = GenerateXml(ptrXmlDoc, &sErrorMsg);

		if (FAILED(hr) )
		{
			sErrorMsg = _T("Failed to generate XML stream - ") + sErrorMsg;

			throw;
		}

		ptrXslStyleSheetDoc->async = false;
 
		// Perform the transformation
		sReportText = OLE2CT(ptrXmlDoc->transformNode(ptrXslStyleSheetDoc) );
	}
	catch (const _com_error& e)
	{
		const HRESULT hr = e.Error();
		ATLTRACE( _T("Unable to display report. Error code 0x%08x"), hr);

		sErrorMsg.Format( _T("CSolutionLintAnalyser::GenerateReportText()\nException 0x%08x"), hr);
	}
	if (sReportText.IsEmpty())
	{
		if ( (NULL != psMsg) && !sErrorMsg.IsEmpty() )
		{
			*psMsg = sErrorMsg;
		}
		ATLTRACE( _T("ERROR in CProjectLintAnalyser::GenerateReportText(): %s\n"), sErrorMsg);
	}
	return sReportText;
}


/// \brief Generate the XML stream
///
/// \param	pDOMDoc			An interface pointer to the XML DOM document containing the stream
/// \param	psMsg			A pointer to a CString object to return error information. May be NULL
/// \return					A Cstring object containing the text of the report
///
HRESULT CProjectLintAnalyser::GenerateXml(	const MSXML2::IXMLDOMDocumentPtr& ptrDOMDoc,
											CString* psMsg /*= NULL*/) const
{
	USES_CONVERSION;
	
	HRESULT hr = S_OK;

	try
	{
		// Add xml version node
		const CString sAttribs = _T("version=\"1.0\" encoding=\"iso-8859-1\"");

		MSXML2::IXMLDOMProcessingInstructionPtr ptrIns;
		ptrIns = ptrDOMDoc->createProcessingInstruction(L"xml", T2COLE(sAttribs) );
		(void)ptrDOMDoc->appendChild(ptrIns);

		// Create root element
		MSXML2::IXMLDOMElementPtr const ptrRoot = ptrDOMDoc->createElement(L"ProjectLintResults");
		(void)ptrDOMDoc->appendChild(ptrRoot);

		Utils::CModuleVersion ver;
		ATLVERIFY(ver.GetFileVersionInfo() );

		const CString sVersion = ver.GetValue( _T("FileVersion") );

		MSXML2::IXMLDOMElementPtr const ptrApplication = ptrDOMDoc->createElement(L"Application");

		ATLVERIFY(SUCCEEDED(ptrApplication->setAttribute(L"Name",		PRODUCT_NAME) ) );
		ATLVERIFY(SUCCEEDED(ptrApplication->setAttribute(L"Version",	T2COLE(sVersion) ) ) );
		ATLVERIFY(SUCCEEDED(ptrApplication->setAttribute(L"Url",		PRODUCT_URL) ) );
		
		(void)ptrRoot->appendChild(ptrApplication);

		ATLVERIFY(SUCCEEDED(GenerateXml(ptrDOMDoc,
										ptrRoot,
										psMsg) ) );
	}
	catch (const _com_error& e)
	{
		hr = e.Error();

		if (NULL != psMsg)
		{
			psMsg->Format( _T("CProjectLintAnalyser::GenerateXml()\nException 0x%08x"), hr);
		}
	}
	return hr;
}


HRESULT CProjectLintAnalyser::GenerateXml(	const MSXML2::IXMLDOMDocumentPtr& ptrDOMDoc,
											const MSXML2::IXMLDOMElementPtr& ptrParentElement,
											CString* psMsg /*= NULL*/) const
{
	USES_CONVERSION;

	HRESULT hr = S_OK;

	try
	{
		MSXML2::IXMLDOMElementPtr const ptrProject = ptrDOMDoc->createElement(L"Project");

		MSXML2::IXMLDOMAttributePtr const ptrProjectNameAttr = ptrDOMDoc->createAttribute(L"name");
		ptrProjectNameAttr->value = _bstr_t(m_sProjectName);
		(void)ptrProject->setAttributeNode(ptrProjectNameAttr);
		
		if (!m_sSolutionFileResultFolder.IsEmpty() && (m_timeStarted.m_dt > 0.0) )
		{
			MSXML2::IXMLDOMAttributePtr const ptrResultsFileAttr = ptrDOMDoc->createAttribute( L"resultsfile");

			CString sResultsIndexFile = Utils::CreateRelativePath(m_sSolutionFileResultFolder, m_sResultsIndexFilePathName);

			// Replace backslashes in paths with forward slashes. This seems to circumvent problems with
			// the Windows XP Local Machine Lockdown policy.
			sResultsIndexFile.Replace( _T("\\"), _T("/") );

			ptrResultsFileAttr->value = _bstr_t(sResultsIndexFile);
			(void)ptrProject->setAttributeNode(ptrResultsFileAttr);
		}

		MSXML2::IXMLDOMAttributePtr const ptrConfigurationAttr = ptrDOMDoc->createAttribute(L"configuration");
		ptrConfigurationAttr->value = _bstr_t(m_sConfiguration);
		(void)ptrProject->setAttributeNode(ptrConfigurationAttr);

		(void)ptrParentElement->appendChild(ptrProject);

		MSXML2::IXMLDOMElementPtr const ptrFiles = ptrDOMDoc->createElement(L"Files");
		(void)ptrProject->appendChild(ptrFiles);

		for (size_t n = 0; n < m_arrayFileResults.GetCount(); n++)
		{
			const CFileLintAnalyser& file = m_arrayFileResults[n];

			ATLVERIFY(SUCCEEDED(file.GenerateXml(	ptrDOMDoc,
													ptrFiles,
													psMsg) ) );
		}
		
		MSXML2::IXMLDOMElementPtr const ptrStatus = ptrDOMDoc->createElement(L"Status");
		ptrStatus->text = m_bAnalysed ? L"Complete" : L"Pending";
		(void)ptrProject->appendChild(ptrStatus);

		MSXML2::IXMLDOMElementPtr const ptrSummary = ptrDOMDoc->createElement(L"Summary");

		(void)ptrProject->appendChild(ptrSummary);

		MSXML2::IXMLDOMElementPtr const ptrWarnings = ptrDOMDoc->createElement(L"Warnings");

		ptrWarnings->text = T2COLE(Utils::IntToStr(m_nWarningCount) );
		(void)ptrSummary->appendChild(ptrWarnings);

		MSXML2::IXMLDOMElementPtr const ptrStartTime = ptrDOMDoc->createElement(L"StartTime");

		const CString sStartTime = m_timeStarted.Format( _T("%a %d %b %Y, %H:%M:%S") );

		ptrStartTime->text = T2COLE(sStartTime);
		(void)ptrSummary->appendChild(ptrStartTime);

		MSXML2::IXMLDOMElementPtr const ptrElapsedTime = ptrDOMDoc->createElement(L"ElapsedTime");

		ptrElapsedTime->text = _bstr_t(m_sElapsedTime);
		(void)ptrSummary->appendChild(ptrElapsedTime);
	}
	catch (const _com_error& e)
	{
		hr = e.Error();

		if (NULL != psMsg)
		{
			psMsg->Format(	_T("Exception 0x%08x in CProjectLintAnalyser::GenerateXml()\n"), hr);
		}
	}
	return hr;

}


/// \brief Set solution specific environment variables.
///
/// \return					\em true if everything worked; \em false otherwise.
///
bool CProjectLintAnalyser::SetEnvironmentVariables(void)
{
	ATLVERIFY(::SetEnvironmentVariable( _T("ProjectDir"), m_sProjectFileFolder) );
	ATLVERIFY(::SetEnvironmentVariable( _T("InputDir"), m_sProjectFileFolder) );

	if (!m_sConfiguration.IsEmpty() )
	{
		ATLVERIFY(::SetEnvironmentVariable( _T("ConfigurationName"), m_sConfiguration) );
	}
	// [Alex McCarthy 21.10.2008]
	if (!m_sSolutionFileFolder.IsEmpty() )
	{
		ATLVERIFY(::SetEnvironmentVariable( _T("SolutionDir"), m_sSolutionFileFolder) );
	}
	// [Alex McCarthy 23.12.2008]
	if (!m_sPlatformName.IsEmpty() )
	{
		ATLVERIFY(::SetEnvironmentVariable( _T("PlatformName"), m_sPlatformName) );
	}
	// End changes by [Alex McCarthy]
	return true;
}	//lint !e1762 (Information -- Member function could be made const --- Eff. C++ 3rd Ed. item 3)


/// \brief Reset solution specific environment variables.
///
/// \return					\em true if everything worked; \em false otherwise.
///
bool CProjectLintAnalyser::ResetEnvironmentVariables(void)
{
	ATLVERIFY(::SetEnvironmentVariable( _T("ProjectDir"),	_T("") ) );
	ATLVERIFY(::SetEnvironmentVariable( _T("InputDir"),		_T("") ) );
	ATLVERIFY(::SetEnvironmentVariable( _T("SolutionDir"),	_T("") ) );
	ATLVERIFY(::SetEnvironmentVariable( _T("PlatformName"),	_T("") ) );

	return true;
}	//lint !e1762 (Information -- Member function could be made const --- Eff. C++ 3rd Ed. item 3)


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
Founder Riverblade Limited
United Kingdom United Kingdom
I haven't always written software for a living. When I graduated from Surrey University in 1989, it was with an Electronic Engineering degree, but unfortunately that never really gave me the opportunity to do anything particularly interesting (with the possible exception of designing Darth Vader's Codpiece * for the UK Army in 1990).
    * Also known as the Standard Army Bootswitch. But that's another story...
Since the opportunity arose to lead a software team developing C++ software for Avionic Test Systems in 1996, I've not looked back. More recently I've been involved in the development of subsea acoustic navigation systems, digital TV broadcast systems, port security/tracking systems, and most recently software development tools with my own company, Riverblade Ltd.

One of my personal specialities is IDE plug-in development. ResOrg was my first attempt at a plug-in, but my day to day work is with Visual Lint, an interactive code analysis tool environment with works within the Visual Studio and Eclipse IDEs or on build servers.

I love lots of things, but particularly music, photography and anything connected with history or engineering. I despise ignorant, intolerant and obstructive people - and it shows...I can be a bolshy cow if you wind me up the wrong way...Laugh | :laugh:

I'm currently based 15 minutes walk from the beach in Bournemouth on the south coast of England. Since I moved here I've grown to love the place - even if it is full of grockles in Summer!

Written By
Software Developer Riverblade Ltd
United Kingdom United Kingdom
I'm a software developer and/or tester with Riverblade Ltd (www.riverblade.co.uk) developing our core product range including our Visual Lint integration product and Lint Project Professional.

I incorporate a number of technologies into a daily basis including Windows API, C++ (VS2008), Managed C++, CLI, Databases, Java, JNI, Eclipse Framework, CDT and of course Visual Studio Extensibility (VSIP VSX).

In my spare time I enjoy cooking (prepping ingredients from scratch!), running, cycling, swimming, reading, interested in experimental electronic music (such as ClockDVA), movies, volunteering my IT skills where I can.

Comments and Discussions