Click here to Skip to main content
15,879,065 members
Articles / Desktop Programming / MFC

XML class for processing and building simple XML documents

Rate me:
Please Sign up or sign in to vote.
4.92/5 (131 votes)
23 Sep 2003CPOL 2.8M   23.8K   415  
Link CMarkup into your VC++ app and avoid complex XML tools and dependencies
// MarkupDlg.cpp : implementation file
//
// CMarkup Release 6.5 Lite
// Copyright (C) 1999-2003 First Objective Software, Inc. All rights reserved
// This entire notice must be retained in this source code
// Redistributing this source code requires written permission
// This software is provided "as is", with no warranty.
// Latest fixes enhancements and documentation at www.firstobject.com

#include "stdafx.h"
#include "MarkupApp.h"
#include "MarkupDlg.h"
#include "Markup.h"

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

/////////////////////////////////////////////////////////////////////////////
// CMarkupDlg dialog

CMarkupDlg::CMarkupDlg(CWnd* pParent /*=NULL*/)
	: CDialog(CMarkupDlg::IDD, pParent)
{
	//{{AFX_DATA_INIT(CMarkupDlg)
		// NOTE: the ClassWizard will add member initialization here
	//}}AFX_DATA_INIT
	m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}

void CMarkupDlg::DoDataExchange(CDataExchange* pDX)
{
	CDialog::DoDataExchange(pDX);
	//{{AFX_DATA_MAP(CMarkupDlg)
		// NOTE: the ClassWizard will add DDX and DDV calls here
	//}}AFX_DATA_MAP
}

BEGIN_MESSAGE_MAP(CMarkupDlg, CDialog)
	//{{AFX_MSG_MAP(CMarkupDlg)
	ON_WM_PAINT()
	ON_WM_QUERYDRAGICON()
	ON_BN_CLICKED(IDC_BUTTON_BROWSE, OnButtonBrowse)
	ON_BN_CLICKED(IDC_BUTTON_PARSE, OnButtonParse)
	ON_WM_DESTROY()
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

void CMarkupDlg::OnPaint() 
{
	if (IsIconic())
	{
		CPaintDC dc(this); // device context for painting

		SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0);

		// Center icon in client rectangle
		int cxIcon = GetSystemMetrics(SM_CXICON);
		int cyIcon = GetSystemMetrics(SM_CYICON);
		CRect rect;
		GetClientRect(&rect);
		int x = (rect.Width() - cxIcon + 1) / 2;
		int y = (rect.Height() - cyIcon + 1) / 2;

		// Draw the icon
		dc.DrawIcon(x, y, m_hIcon);
	}
	else
	{
		CDialog::OnPaint();
	}
}

HCURSOR CMarkupDlg::OnQueryDragIcon()
{
	return (HCURSOR) m_hIcon;
}
/////////////////////////////////////////////////////////////////////////////
// CMarkupDlg message handlers

BOOL CMarkupDlg::OnInitDialog()
{
	CDialog::OnInitDialog();

	SetIcon(m_hIcon, TRUE);			// Set big icon
	SetIcon(m_hIcon, FALSE);		// Set small icon

	m_nErrorCount = 0;

	// Load settings
	CString csFilename = AfxGetApp()->GetProfileString( _T("Settings"), _T("Filename"), _T("") );
	if ( ! csFilename.IsEmpty() )
		GetDlgItem( IDC_EDIT_FILE )->SetWindowText( csFilename );
	CString csPos = AfxGetApp()->GetProfileString( _T("Settings"), _T("Position"), _T("") );
	CPoint ptDlg;
	if ( _stscanf( csPos, _T("%d,%d"), &ptDlg.x, &ptDlg.y ) == 2 )
	{
		CRect rect;
		GetWindowRect( &rect );
		if ( rect.Width() > 10 )
		{
			rect.OffsetRect( ptDlg.x - rect.left, ptDlg.y - rect.top ); 
			MoveWindow( &rect );
		}
	}

	// Determine version
	CString csVersion, csV0, csV1;
	CString csTitle;
	csVersion.LoadString( ID_APP_VERSION );
	AfxExtractSubString( csV0, csVersion, 0, ',' );
	AfxExtractSubString( csV1, csVersion, 1, ',' );
	CString csClass = _T("CMarkup Lite");
	CString csBuild;
	#if defined( _DEBUG )
	csBuild += _T(" Debug");
	#endif
	#if defined( _UNICODE )
	csBuild += _T(" Unicode");
	#endif
	#if defined( _MBCS )
	csBuild += _T(" MBCS");
	#endif
	csTitle.Format( _T("%s %s.%s%s\r\n"), csClass, csV0, csV1, csBuild );
	OutputTestResults( csTitle );
	OutputParseResults( _T("") );

	RunTest();

	return TRUE;  // return TRUE  unless you set the focus to a control
}

void CMarkupDlg::OnDestroy() 
{
	// Save settings
	CString csFilename;
	GetDlgItem( IDC_EDIT_FILE )->GetWindowText( csFilename );
	AfxGetApp()->WriteProfileString( _T("Settings"), _T("Filename"), csFilename );
	CRect rect;
	GetWindowRect( &rect );
	CString csPos;
	csPos.Format( _T("%d,%d"), rect.left, rect.top );
	AfxGetApp()->WriteProfileString( _T("Settings"), _T("Position"), csPos );

	CDialog::OnDestroy();
}

void CMarkupDlg::OutputTestResults( CString csMsg )
{
	m_csTestResults += csMsg;
	GetDlgItem( IDC_ST_TEST_RESULTS )->SetWindowText( m_csTestResults );
}

int CMarkupDlg::Alert( CString csMsg )
{
	OutputTestResults( csMsg + _T("\r\n") );
	++m_nErrorCount;
	return -1;
}

void CMarkupDlg::StartCheckZone( CString csCheckZone )
{
	m_nCheckCount = 0;
	m_csCheckZone = csCheckZone;
}

int CMarkupDlg::Check( BOOL bCorrect )
{
	++m_nCheckCount;
	if ( ! bCorrect )
	{
		if ( m_csCheckZone.IsEmpty() )
			m_csCheckZone = _T("Unknown Check Zone");
		CString csMsg;
		csMsg.Format( _T("Error: %s, check %d"), m_csCheckZone, m_nCheckCount );
		return Alert( csMsg );
	}
	return 0;
}

int CMarkupDlg::RunTest()
{
	// Implement tests here
	CMarkup xml;

	// Sample code from the article
	StartCheckZone( _T("Article Tests") );
	Check( xml.AddElem( _T("ORDER") ) );
	Check( xml.AddChildElem( _T("ITEM") ) );
	Check( xml.IntoElem() );
	Check( xml.AddChildElem( _T("SN"), _T("132487A-J") ) );
	Check( xml.AddChildElem( _T("NAME"), _T("crank casing") ) );
	Check( xml.AddChildElem( _T("QTY"), _T("1") ) );
	CString csXML = xml.GetDoc();

	xml.SetDoc( csXML );
	int nTotalQty = 0;
	while ( xml.FindChildElem(_T("ITEM")) )
	{
		Check( xml.IntoElem() );
		Check( xml.FindChildElem( _T("SN") ) );
		CString csSN = xml.GetChildData();
		Check( xml.FindChildElem( _T("QTY") ) );
		nTotalQty += _ttoi( xml.GetChildData() );
		Check( xml.OutOfElem() );
	}

	struct SampItem { LPCTSTR szSN; LPCTSTR szName; int nQty; } aItems[] =
	{
		_T("132487A-J"), _T("crank casing"), 1,
		_T("4238764-A"), _T("bearing"), 15,
		NULL, NULL, 0
	};
	struct SampPOS { LPCTSTR szType; LPCTSTR szName; LPCTSTR szTel; } poc =
	{
		_T("non-emergency"), _T("John Smith"), _T("555-1234")
	};
	xml.SetDoc( NULL );
	Check( xml.AddElem( _T("ORDER") ) );
	Check( xml.IntoElem() ); // inside ORDER
	_TCHAR szQty[20];
	for ( int nItem=0; aItems[nItem].szSN; ++nItem )
	{
		Check( xml.AddElem( _T("ITEM") ) );
		Check( xml.IntoElem() ); // inside ITEM
		Check( xml.AddElem( _T("SN"), aItems[nItem].szSN ) );
		Check( xml.AddElem( _T("NAME"), aItems[nItem].szName ) );
		Check( xml.AddElem( _T("QTY"), _itot(aItems[nItem].nQty,szQty,10) ) );
		Check( xml.OutOfElem() ); // back out to ITEM level
	}
	Check( xml.AddElem( _T("SHIPMENT") ) );
	Check( xml.IntoElem() ); // inside SHIPMENT
	Check( xml.AddElem( _T("POC") ) );
	Check( xml.SetAttrib( _T("type"), poc.szType ) );
	Check( xml.IntoElem() ); // inside POC
	Check( xml.AddElem( _T("NAME"), poc.szName ) );
	Check( xml.AddElem( _T("TEL"), poc.szTel ) );

#if ! defined( _UNICODE )
	xml.SetDoc( "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\r\n" );
	Check( xml.AddElem( "island", "Cura�ao" ) );
	Check( xml.GetData() == "Cura�ao" );
#endif

	// ORDER Test, simple XML example
	StartCheckZone( _T("ORDER Test") );
	xml.SetDoc( NULL );
	Check( xml.AddElem( _T("ORDER") ) );
	Check( xml.IntoElem() );
	Check( xml.AddElem( _T("ITEM") ) );
	Check( xml.AddChildElem( _T("NAME"), _T("carrots") ) );
	Check( xml.AddChildElem( _T("QTY"), _T("1") ) );
	Check( xml.AddChildElem( _T("PRICE"), _T(".98") ) );
	Check( xml.AddChildAttrib( _T("unit"), _T("1 lb") ) );
	Check( xml.AddElem( _T("ITEM") ) );
	Check( xml.AddChildElem( _T("NAME"), _T("onions") ) );
	Check( xml.AddChildElem( _T("QTY"), _T("1") ) );
	Check( xml.AddChildElem( _T("PRICE"), _T("1.10") ) );
	Check( xml.AddChildAttrib( _T("unit"), _T("3 lb bag") ) );
	Check( xml.AddChildElem( _T("SUPPLIER"), _T("Hanover") ) );

	// Create List
	StartCheckZone( _T("List Test") );
	CString csList;
	xml.ResetPos();
	Check( xml.FindElem( _T("ORDER") ) );
	while ( xml.FindChildElem( _T("ITEM") ) )
	{
		xml.IntoElem();
		xml.FindChildElem( _T("NAME") );
		csList += xml.GetChildData();
		csList += _T("\n");
		xml.OutOfElem();
	}
	Check( csList == _T("carrots\nonions\n") );

	// Car Test, add and replace randomly chosen attributes
	StartCheckZone( _T("Random Car Part Test") );
	srand( (unsigned)time( NULL ) );
	struct CarTestData { _TCHAR* szLow; _TCHAR* szUp; _TCHAR* szMix; } aCT[] =
	{
		_T("up"), _T("SKY"), _T("Light"),
		_T("down"), _T("FLOOR"), _T("Dust"),
		_T("left"), _T("DOOR"), _T("Handle"),
		_T("right"), _T("SEAT"), _T("Gear"),
		_T("back"), _T("TRUNK"), _T("Tread"),
		_T("forward"), _T("GRILL"), _T("Motor"),
		_T(""), _T(""), _T("")
	};
	xml.SetDoc( _T("") );
	xml.AddElem( _T("CAR") );
	int nAt;
	LPCTSTR szLow, szUp, szMix;
	for ( nAt = 0; nAt < 20; ++nAt )
	{
		szLow = aCT[RandInt(6)].szLow;
		szMix = aCT[RandInt(6)].szMix;
		xml.SetAttrib( szLow, szMix );
		Check( xml.GetAttrib(szLow) == szMix );
	}
	for ( int nPart=0; nPart<100; ++nPart )
	{
		xml.AddChildElem( aCT[RandInt(6)].szUp );
		for ( nAt = 0; nAt < 8; ++nAt )
		{
			szLow = aCT[RandInt(6)].szLow;
			szMix = aCT[RandInt(6)].szMix;
			xml.SetChildAttrib( szLow, szMix );
			Check( xml.GetChildAttrib(szLow) == szMix );
		}
		szLow = aCT[RandInt(6)].szLow;
		szUp = aCT[RandInt(6)].szUp;
		xml.SetChildAttrib( szLow, szUp );
		Check( xml.GetChildAttrib(szLow) == szUp );
	}
	CString csCarDoc = xml.GetDoc();
	Check( xml.SetDoc( csCarDoc ) );

	// Comments Test, make sure it bypasses declaration, DTD, and comments
	StartCheckZone( _T("Comments Test") );
	CString csPTDoc =
		_T("<?xml version=\"1.0\"?>\n")
		_T("<!DOCTYPE PARSETEST [\n")
		_T("<!ELEMENT PARSETEST (ITEM*)>\n")
		_T("<!ATTLIST PARSETEST v CDATA \"\" s CDATA \"\">\n")
		_T("<!ELEMENT ITEM ANY>\n")
		_T("<!ATTLIST ITEM note CDATA \"\">\n")
		_T("]>\n")
		_T("<PARSETEST v=\"1\" s=\'6\'>\n")
			_T("<!--- </P>& special chars -->\n")
			_T("<ITEM note=\"Here&apos;s to &quot;us&quot;\"/>\n")
			_T("<ITEM note=\"see data\">Here's to \"us\"</ITEM>\n")
		_T("</PARSETEST>\n")
		;
	Check( xml.SetDoc( csPTDoc ) );
	Check( xml.FindChildElem() );
	Check( xml.GetAttrib( _T("v") ) == _T("1") );
	Check( xml.GetChildAttrib(_T("note")) == _T("Here's to \"us\"") );
	Check( xml.IntoElem() );
	Check( xml.FindElem() );
	Check( xml.GetData() == _T("Here's to \"us\"") );

	// Depth First Traversal Test, loop through all elements
	StartCheckZone( _T("Depth First Traversal Test") );
	xml.SetDoc( _T("<A><B><C/><C/><C><D/></C></B><B/></A>") );
	CString csListOfTagNames;
	BOOL bFinished = FALSE;
	if ( xml.FindElem() )
		csListOfTagNames = xml.GetTagName();
	if ( ! xml.FindChildElem() )
		bFinished = TRUE;
	while ( ! bFinished )
	{
		// Process Element
		xml.IntoElem();
		csListOfTagNames += xml.GetTagName();

		// Next element (depth first)
		BOOL bFound = xml.FindChildElem();
		while ( ! bFound && ! bFinished )
		{
			if ( xml.OutOfElem() )
				bFound = xml.FindChildElem();
			else
				bFinished = TRUE;
		}
	}
	Check( csListOfTagNames == _T("ABCCCDB") );


	// Success?
	if ( ! m_nErrorCount )
		this->OutputTestResults( _T("RunTest complete\r\n") );
	return 0;
}


void CMarkupDlg::OnButtonBrowse() 
{
	LPCTSTR szTypes =
		_T("XML Files (*.xml)|*.xml|")
		_T("Text Files (*.txt)|*.txt|")
		_T("All Files (*.*)|*.*||");

	CFileDialog dlg( TRUE, _T("xml"), NULL, OFN_HIDEREADONLY, szTypes );
	if ( dlg.DoModal() == IDCANCEL )
		return;

	CString csFilename = dlg.GetPathName();
	GetDlgItem( IDC_EDIT_FILE )->SetWindowText( csFilename );
	OutputParseResults( _T("") );
}

int CMarkupDlg::RandInt( int nNumber )
{
	//
	// Return an integer between 0 and nNumber
	//
	static BOOL bSeeded = FALSE;
	if ( ! bSeeded )
	{
		srand( (unsigned)time( NULL ) );
		bSeeded = TRUE;
	}

	// Sometimes rand() returns something close enough to 1 to make nRandom == nNumber
	int nRandom = rand() * nNumber / RAND_MAX;
	if ( nRandom == nNumber )
		--nRandom;
	return nRandom;
}

void CMarkupDlg::TimeStart()
{
	// Keep track of time before operation
	GetSystemTime( &m_stBefore );
}

int CMarkupDlg::TimeStop()
{
	// Determine time span
	SYSTEMTIME stAfter;
	GetSystemTime( &stAfter );
	int nBefore = m_stBefore.wMilliseconds + m_stBefore.wSecond * 1000 + m_stBefore.wMinute * 60000;
	int nAfter = stAfter.wMilliseconds + stAfter.wSecond * 1000 + stAfter.wMinute * 60000;
	int nDiff = nAfter - nBefore;
	if ( m_stBefore.wHour < stAfter.wHour )
		nDiff += 24*60000;

	return nDiff;
}

void CMarkupDlg::OutputParseResults( CString csMsg )
{
	GetDlgItem( IDC_ST_PARSE_RESULTS )->SetWindowText( csMsg );
}

void CMarkupDlg::OnButtonParse() 
{
	OutputParseResults( _T("") );

	// Get pathname
	CString csPath;
	GetDlgItem( IDC_EDIT_FILE )->GetWindowText( csPath );
	if ( csPath.IsEmpty() )
	{
		OnButtonBrowse();
		GetDlgItem( IDC_EDIT_FILE )->GetWindowText( csPath );
		if ( csPath.IsEmpty() )
			return;
		RedrawWindow();
	}

	// Open file
	CWaitCursor wait;
	TimeStart();
	CString csText;
	CString csNotes;
	CFile file;
	if ( ! file.Open( csPath, CFile::modeRead ) )
	{
		OutputParseResults( _T("unable to open file") );
		return;
	}
	int nFileLen = (int)file.GetLength();

	// Allocate buffer for binary file data
	unsigned char* pBuffer = new unsigned char[nFileLen + 2];
	nFileLen = file.Read( pBuffer, nFileLen );
	file.Close();
	pBuffer[nFileLen] = '\0';
	pBuffer[nFileLen+1] = '\0'; // in case 2-byte encoded

	// Windows Unicode file is detected if starts with FEFF
	if ( pBuffer[0] == 0xff && pBuffer[1] == 0xfe )
	{
		// Contains byte order mark, so assume wide char content
		// non _UNICODE builds should perform UCS-2 (wide char) to UTF-8 conversion here
		csText = (LPCWSTR)(&pBuffer[2]);
		csNotes += _T("File starts with hex FFFE, assumed to be wide char format. ");
	}
	else
	{
		// _UNICODE builds should perform UTF-8 to UCS-2 (wide char) conversion here
		csText = (LPCSTR)pBuffer;
	}
	delete [] pBuffer;
	int nTimeLoading = TimeStop();

	// If it is too short, assume it got truncated due to non-text content
	if ( csText.GetLength() < nFileLen / 2 - 20 )
	{
		OutputParseResults( _T("Error converting file to string (may contain binary data)") );
		return;
	}

	// Parse
	CMarkup xml;
	TimeStart();
	BOOL bResult = xml.SetDoc( csText );
	int nTimeParsing = TimeStop();

	// Display results
	CString csMsg;
	if ( bResult )
		csMsg.Format( _T("Loaded in %s milliseconds, parsed in %s milliseconds\r\n")
			_T("File size: %s bytes"),
			AddNumSeps(nTimeLoading), AddNumSeps(nTimeParsing),
			AddNumSeps(nFileLen) );
	else
	{
		CString csError = xml.GetError();
		csMsg.Format( _T("Error: %s\r\n")
			_T("Loaded in %s milliseconds, parse failed in %s milliseconds\r\n")
			_T("File size: %s bytes"),
			csError,
			AddNumSeps(nTimeLoading), AddNumSeps(nTimeParsing),
			AddNumSeps(nFileLen) );
	}
	OutputParseResults( csMsg );
}

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
United States United States
Raised in Southern Ontario Canada. Bachelor of Science from the University of Toronto in Computer Science and Anthropology. Living near Washington D.C. in Virginia, USA.

Comments and Discussions