- •Introduction
- •Saving Time with This Book
- •Conventions Used in This Book
- •Part II: Working with the Pre-Processor
- •Part III: Types
- •Part IV: Classes
- •Part V: Arrays and Templates
- •Part VI: Input and Output
- •Part VII: Using the Built-in Functionality
- •Part VIII: Utilities
- •Part IX: Debugging C++ Applications
- •Part X: The Scary (or Fun!) Stuff
- •Icons Used in This Book
- •Creating and Implementing an Encapsulated Class
- •Creating a Mailing-List Application
- •Testing the Mailing-List Application
- •Customizing a Class with Polymorphism
- •Testing the Virtual Function Code
- •Why Do the Destructors Work?
- •Delayed Construction
- •The cDate Class
- •Testing the cDate Class
- •Creating the Header File
- •Testing the Header File
- •The Assert Problem
- •Fixing the Assert Problem
- •Using the const Construct
- •Identifying the Errors
- •Fixing the Errors
- •Fixing What Went Wrong with the Macro
- •Using Macros Appropriately
- •Using the sizeof Function
- •Evaluating the Results
- •Using sizeof with Pointers
- •Implementing the Range Class
- •Testing the Range Class
- •Creating the Matrix Class
- •Matrix Operations
- •Multiplying a Matrix by a Scalar Value
- •Multiplying a Matrix by Scalar Values, Take 2
- •Testing the Matrix Class
- •Implementing the Enumeration Class
- •Testing the Enumeration Class
- •Implementing Structures
- •Interpreting the Output
- •Defining Constants
- •Testing the Constant Application
- •Using the const Keyword
- •Illustrating Scope
- •Interpreting the Output
- •Using Casts
- •Addressing the Compiler Problems
- •Testing the Changes
- •Implementing Member-Function Pointers
- •Updating Your Code with Member-Function Pointers
- •Testing the Member Pointer Code
- •Customizing Functions We Wrote Ourselves
- •Testing the Default Code
- •Fixing the Problem
- •Testing the Complete Class
- •Implementing Virtual Inheritance
- •Correcting the Code
- •Rules for Creating Overloaded Operators
- •Using Conversion Operators
- •Using Overloaded Operators
- •Testing the MyString Class
- •Rules for Implementing new and delete Handlers
- •Overloading new and delete Handlers
- •Testing the Memory Allocation Tracker
- •Implementing Properties
- •Testing the Property Class
- •Implementing Data Validation with Classes
- •Testing Your SSN Validator Class
- •Creating the Date Class
- •Testing the Date Class
- •Some Final Thoughts on the Date Class
- •Creating a Factory Class
- •Testing the Factory
- •Enhancing the Manager Class
- •Implementing Mix-In Classes
- •Testing the Template Classes
- •Implementing Function Templates
- •Creating Method Templates
- •Using the Vector Class
- •Creating the String Array Class
- •Working with Vector Algorithms
- •Creating an Array of Heterogeneous Objects
- •Creating the Column Class
- •Creating the Row Class
- •Creating the Spreadsheet Class
- •Testing Your Spreadsheet
- •Working with Streams
- •Testing the File-Reading Code
- •Creating the Test File
- •Reading Delimited Files
- •Testing the Code
- •Creating the XML Writer
- •Testing the XML Writer
- •Creating the Configuration-File Class
- •Setting Up Your Test File
- •Building the Language Files
- •Creating an Input Text File
- •Reading the International File
- •Testing the String Reader
- •Creating a Translator Class
- •Testing the Translator Class
- •Creating a Virtual File Class
- •Testing the Virtual File Class
- •Using the auto_ptr Class
- •Creating a Memory Safe Buffer Class
- •Throwing and Logging Exceptions
- •Dealing with Unhandled Exceptions
- •Re-throwing Exceptions
- •Creating the Wildcard Matching Class
- •Testing the Wildcard Matching Class
- •Creating the URL Codec Class
- •Testing the URL Codec Class
- •Testing the Rot13 Algorithm
- •Testing the XOR Algorithm
- •Implementing the transform Function to Convert Strings
- •Testing the String Conversions
- •Implementing the Serialization Interface
- •Creating the Buffer Class
- •Testing the Buffer Class
- •Creating the Multiple-Search-Path Class
- •Testing the Multiple-Search-Path Class
- •Testing the Flow Trace System
- •The assert Macro
- •Logging
- •Testing the Logger Class
- •Design by Contract
- •Adding Logging to the Application
- •Making Functions Inline
- •Avoiding Temporary Objects
- •Passing Objects by Reference
- •Choosing Initialization Instead of Assignment
- •Learning How Code Operates
- •Testing the Properties Class
- •Creating the Locking Mechanism
- •Testing the Locking Mechanism
- •Testing the File-Guardian Class
- •Implementing the Complex Class
- •Creating the Conversion Code
- •Testing the Conversion Code
- •A Sample Program
- •Componentizing
- •Restructuring
- •Specialization
- •Index
238 Technique 42: How to Read Delimited Files
LISTING 42-1 (continued)
virtual ~DelimitedFileParser()
{
_in.close();
}
virtual bool Open( const char *fileName )
{
_fileName = fileName; _in.open( _fileName.c_str() ); return !_in.fail();
}
virtual bool Parse()
{
// Make sure the file is open. if ( _in.fail() )
{
return false;
}
while ( !_in.eof() )
{
string sIn = “”;
while ( !_in.eof() )
{
// Get an input line. char c;
_in.get(c);
if ( _in.fail() ) break;
if ( c != ‘\r’ && c != ‘\n’ )
sIn += c;
if ( c == ‘\n’ ) break;
}
// Parse it.
if ( sIn.length() ) _ParseLine( sIn );
}
return true;
}
int NumRows()
{
return _rows.size();
}
DelimitedRow getRow( int index )
{
if ( index < 0 || index >=
NumRows() )
throw “getRow: index out of range”;
return _rows[index];
}
};
This code implements a generic parser and container of delimited files. By specifying the delimiter between fields, the end-of-record indicator, and the source filename, you can then use this class to read in and retrieve all of the individual fields in the file.
As you can see from the code, the entire application is made up of three classes, each of which manages a specific part of the process. The process has three parts: file management (shown at 3), data storage (shown at 2), and delimiter management (shown at 1).
3. Save the source file in the source-code editor and close the editor application.
Always separate the individual components of a process into separate classes. That way you can easily modify one piece of the process without affecting the rest of the system. More importantly, you can reuse smaller components in other applications without having to pull in the entire system.
Testing the Code
After you have created the functionality, you should create a test driver that not only ensures that your code is correct, but also shows people how to use your code.
The following steps show you how to create a test driver that illustrates how the file parser is used — and what the output will be.
1. In the code editor of your choice, reopen
the existing file to hold the code for your test program.
Testing the Code 239
In this example, I named the test program ch42.cpp.
2. Append the code from Listing 42-2 into your file.
Better yet, copy the code from the source file on this book’s companion Web site.
LISTING 42-2: THE DELIMITER TEST DRIVER
int main(int argc, char **argv)
{
if ( argc < 2 )
{
printf(“Usage ch5_2 <delimitedFile>\n”);
exit(1);
}
DelimitedFileParser fileParser( argv[1], “:” );
fileParser.Parse();
printf(“%d Rows found\n”, fileParser.NumRows() );
for ( int i=0; i<fileParser.NumRows(); ++i )
{
DelimitedRow row = fileParser.getRow(i);
printf(“Row: %d\n”, i );
for ( int j=0; j<row.NumColumns(); ++j )
printf(“Column %d = [%s]\n”, j, row.getColumn(j).c_str() );
}
}
3. Save the source-code file and close the codeeditor application.
4. Create a test file for testing the application in the text editor of your choice.
This file is used for input to the application and contains the delimited records.
In this case, I used the name test_delimited.txt for the test file.
5. Type the following text into the test file:
Line 1:Column 2:This is a test:100:200:300
Line 2:Column 2:This is another test:200:300:400
6. Save the test file and close the code-editor application.
7. Compile and run the program with your favorite compiler and operating system.
If you have done everything properly, you should see the following output from the program on your console window:
$ ./a.exe test_delimited.txt
2 Rows found |
|
|
Row: 0 |
|
|
Column 0 |
= [Line |
1] |
Column 1 |
= [Column 2] |
|
Column 2 |
= [This is a test] |
|
Column 3 |
= [100] |
|
Column 4 |
= [200] |
|
Column 5 |
= [300] |
|
Row: 1 |
|
|
Column 0 |
= [Line |
2] |
Column 1 |
= [Column 2] |
|
Column 2 |
= [This |
is another test] |
Column 3 |
= [200] |
|
Column 4 |
= [300] |
|
Column 5 |
= [400] |
|
As you can see from the output, the parser properly determines that there are two records in the test file that we gave it for input. Each of the input lines contains five columns of data, separated by a colon (:) character. By telling the parser that the delimiter is a colon, it then breaks each line into individual columns and returns that data to the user as a DelimitedRow object in a vector of such objects.
This class can now be moved from project to project, in any situation where we need to read in a delimited file and use the individual components of each record (or line) in the file. This saves us time in implementing the functionality over and over, and saves us effort because the code will already be debugged.
43 Writing Your
Objects as XML
Technique
Save Time By
Comparing XML code to C++ code
Using XML to store and restore data to and from C++ classes
Creating an XMLWriter class
Testing your XMLWriter class
Interpreting your output
The current buzzword of the programming world is XML and XML compatibility. XML, which stands for eXtended Markup Language, is really just a variant of the SGML display language (from which
HTML was also derived) that has been optimized for data storage instead of for display.
It’s no surprise that the capability to output data as XML code has become very important in the programming world. Because the structure of XML is so much like the structure of C++ — in terms of hierarchical display and classes and attributes — you can easily use XML to store and restore data to and from C++ classes.
The general format of an XML structure is as follows:
<xml> <structure-name>
<element-name> value </element-name>
</structure-name> </xml>
As you can see, it looks very much like a C++ class structure with a structure name as the name of the class and an element name as the name of each piece of member data of that class. Here’s an example:
Class Foo
{
int x; // We can think of the semicolon as </int> // And so forth
};
In the above class definition, we have an object name (class Foo), an element (int), and a value for that element (x). This maps quite directly into the XML general schema. The initial definition was a true XML object, whereas this definition is a true C++ class. Yet you can see how one maps to the other. We could write the above class as
Creating the XML Writer 241
<Foo>
<int> x </int> </Foo>
and, as you can see, the two map quite well. By storing data in XML format, we make it possible to read the data not only into C++ applications, but also into any other applications that understand the XML format, such as databases. Storing data in a known, standard format saves time by eliminating the need to write translators for your data formats, and by allowing you to use existing applications with your data. In this example, we will look at how to write out a C++ class in XML format, and then how to read back in that XML data to a C++ class.
Creating the XML Writer
The first step in the process is to add the ability to write out the data for our class in XML format. We will call the element that does this processing an XMLWriter object. Let’s look at a generic way to create an XMLWriter that will save us time by allowing us to apply this functionality to all objects in our system.
1. In the code editor of your choice, create a new file to hold the code for the definition of the class.
In this example, the file is named ch43.cpp, although you can use whatever you choose. This file will contain the class definition for the needed automation object.
2. Type the code from Listing 43-1 into your file.
Better yet, copy the code from the source file on this book’s companion Web site.
LISTING 43-1: THE XML WRITER CLASS
#include <stdio.h> #include <string> #include <vector> #include <fstream>
using namespace std;
//Class to manage the storage of the XML data.
class XMLElement
{
private:
string _name; string _value;
vector< XMLElement > _subElements; protected:
virtual void Init()
{
_name = “”; _value = “”;
_subElements.erase( _subElements.begin(), _subElements.end() );
}
virtual void Copy( const XMLElement& aCopy )
{
setName( aCopy.getName().c_str() ); setValue ( aCopy.getValue().c_str()
);
vector< XMLElement >::const_ iterator iter;
for ( iter = aCopy._subElements.begin();
iter != aCopy._subElements.end();
++iter ) _subElements.insert
( _subElements.end(), (*iter) );
}
public:
XMLElement( void )
{
Init();
}
XMLElement( const char *name, const char *value )
{
setName( name ); setValue( value );
}
XMLElement( const char *name, int value )
{
(continued)
242 Technique 43: Writing Your Objects as XML
LISTING 43-1 (continued)
setName( name ); char szBuffer[10];
sprintf(szBuffer, “%d”, value ); setValue( szBuffer );
}
XMLElement( const char *name, double value )
{
setName( name ); char szBuffer[10];
sprintf(szBuffer, “%lf”, value ); setValue( szBuffer );
}
XMLElement( const XMLElement& aCopy )
{
Copy( aCopy );
}
virtual ~XMLElement()
{
}
XMLElement operator=( const XMLElement& aCopy )
{
Copy( aCopy ); return *this;
}
// Accessors
void setName( const char *name )
{
_name = name;
}
void setValue( const char *value )
{
_value = value;
}
string getName( void ) const
{
return _name;
}
string getValue( void ) const
{
return _value;
}
// Sub-element maintenance.
void addSubElement( const XMLElement& anElement )
{
_subElements.insert( _subElements. end(), anElement );
}
int numSubElements( void )
{
return _subElements.size();
}
XMLElement& getSubElement( int index )
{
if ( index < 0 || index >= numSubElements() )
throw “getSubElement: index out of range”;
return _subElements[ index ];
}
};
// Class to manage the output of XML data. class XMLWriter
{
private:
ofstream _out; public:
XMLWriter( void )
{
}
XMLWriter( const char *fileName )
{
_out.open(fileName);
if ( _out.fail() == false )
{
_out << “<xml>” << endl;
}
}
XMLWriter( const XMLWriter& aCopy )
{
}
virtual ~XMLWriter()
{
if ( _out.fail() == false )
{
_out << “</xml>” << endl;
}
_out.close();
}