Question
P3-XML-Part 1 CS 3370 Program 3 XML-String and Stream Processing Part One Employee Class Write a program that handles I/O of Employee objects containing the
P3-XML-Part 1
CS 3370 Program 3 XML-String and Stream Processing
Part One
Employee Class
Write a program that handles I/O of Employee objects containing the following data attributes:
name (string) id (int) address (string) city (string) state (string) country (string) phone (string) salary (double)
Your Employee class must contain at least the following:
static Employee* fromXML(std::istream&); // Read the XML record from a stream void display(std::ostream&) const; // Write a readable Employee representation to a stream
Parsing from XML
You will read XML representations of Employee objects from a file, and populate a vector containing . Do not use an XML software library or regular expressions for this assignment; just use operations from the std::string class for parsing input (see the Notes section below). The input files have no XML header tag.
The XML tags are named the same as the attributes (ignoring case, of course). For those that know XML, dont worry about top-level, system XML tags; we just want sequences of same-level Employee tag groups with attributes tags nested to one level only, like the following (indented to show the nesting, but they may appear free-form in the file; dont assume that there will be any newlines or formatting):
12345 John Doe 40000
A single XML text file may have multiple Employee records. Internal Employee field tags can appear in any order or not at all, except that name and id are required. When creating Employee objects, use 0.0 as a default for salary and empty strings for the other optional attributes.
We have provided seven XML files to process. All but one of them have errors that you must catch. Throw exceptions of runtime_error (defined in ) for these cases. You will catch these exceptions in your main function.
Do not change any signatures or return types. Define any constructors you feel needful. You do not need a destructor, as Employee objects only contain string objects and numbers. read, retrieve, and fromXML return a nullptr if they read end of file. retrieve also returns a nullptr if the requested id is not found in the file. Throw exceptions for data errors.
Main.cpp
The following is an overview of what your main function should do to test your functions using the file employee.xml (provided in this Zip file):
1) Obtain the name of an XML file to read from the command line (argv[1]). Print an error message and halt the program if there is no command-line argument provided, or if the file does not exist.
2) Read each XML record in the file by repeatedly calling Employee::fromXML, which creates an Employee object on-the-fly, and store it in a vector. Make sure you dont leak any memory! (I recommend using unique_ptrs in the vector). ALSO, make sure you return a nullptr when fromXML encounters EOF. The unit tests will fail if you do not.
3) Loop through your vector and print to cout the Employee data for each object (using the display member function).
Here is sample output from employee.xml (except for steps 1214):
id: 1234 name: John Doe address: 2230 W. Treeline Dr. city: Tucson state: Arizona country: USA phone: 520-742-2448 salary: 40000
id: 4321 name: Jane Doe address: city: state: country: phone: salary: 60000
id: 12345 name: Jack Dough address: 24437 Princeton city: Dearborn state: Michigan country: USA phone: 303-427-0153 salary: 140000
4) The files employee2.xml through employee8.xml have errors that you should catch through exceptions and print a meaningful message. Throw runtime_error exceptions with a suitable message if any required XML tags are missing, or if any end tags for existing start tags are missing, or for any other abnormalities.
Heres my output.
$ ./a.out employee2.xml Missing tag $ ./a.out employee3.xml Missing tag $ ./a.out employee4.xml Invalid tag: $ ./a.out employee5.xml Missing tag $ ./a.out employee6.xml Multiple tags $ ./a.out employee7.xml Invalid tag: $ ./a.out employee8.xml Missing tag
You should get the same output. Zylab unit tests will match these exactly.
Implementation Notes
- Your XML input function should not depend on the line orientation of the input stream, so dont read text a line at a time (i.e., dont use getline( ) with the newline character as its delimiter [other delimiters are okay]the input should be free form, like source code is to a compiler).
- Do not use any third-party XML libraries. I want you to do your own basic, custom parsing by using simple string operations. An important part of this assignment is also the proper use of exceptions.
- You may find some of the following functions useful for this assignment:
- istream::getline(istream&,string&, char), ios::clear, string::copy, string::empty, string::stoi, string::stof, string::find_first_not_of, string::find, string::substr, string::clear, string::c_str.
- The goal here is to understand strings and streams better. Along the way, I ended up creating a few handy XML-related functions for future use, which I named getNextTag, getNextValue, both of which take an input stream as a parameter. You might find such a practice useful.
- Remember to do a case-insensitive compare when looking for tag names. For case-insensitive string comparisons, it is handy to use the non-standard C function, strcasecmp, defined in as a GNU/clang extension. (This function works like std::strcmp but ignores case.) In Microsoft Visual C++ the function is named stricmp (possibly with a leading underscore). Note: to extract a char* from a C++ string object to pass to strcasecmp/stricmp, you need to call c_str( ) on the string objects:
- strcasecmp(s1.c_str(), s2.c_str()) // Returns negative | 0 | positive for < | == | >
-
Since the normal comparison functions are distinct between Windows and Linux, I've provided a function for you: int compareNoCase(const string& s1, const string& s2) { #if defined(_WIN32) return _stricmp(s1.c_str(), s2.c_str()); #elif defined(__GNUC__) return strcasecmp(s1.c_str(), s2.c_str()); #else #error Unsupported compiler. #endif
}
Two points extra credit for writing your own, portable function!
-
How do I keep track of tags that have been found?
You can use a 'Bitset' ( #include ), which is a set of bits that you can turn on or off by index. If you create an enum of tags, you can use the enum value (an int index) to set or reset a given bit. Here is an example of how this technique can be used:
here is an example of a great way to track your tags in program 3, XML. It uses a bitset. Place the static declarations below in your employee.h, and an example of how you might use them is below. This will help you track the fields as you are parsing them.
//declare these in your employee.h in the class.
enum {ID, NAME, ADDRESS, CITY, STATE, COUNTRY, PHONE, SALARY, NUM_FIELDS}; // bit IDs static std::bitset found; static std::string fieldName[NUM_FIELDS];
//In your CPP
#include #include using namespace std;
// Static initialization for Employee bitset Employee::found; string Employee::fieldName[NUM_FIELDS] { "ID", "Name", "Address", "City", "State", "Country", "Phone", "Salary" };
void checkField(int index) { if (found.test(index)) cout << "field " << fieldName[index] << " was already found" << endl; else found.set(index); //if not found already, set bit } int main() { checkField(ID); checkField(NAME); found.reset(); }
Step by Step Solution
There are 3 Steps involved in it
Step: 1
Get Instant Access to Expert-Tailored Solutions
See step-by-step solutions with expert insights and AI powered tools for academic success
Step: 2
Step: 3
Ace Your Homework with AI
Get the answers you need in no time with our AI-driven, step-by-step assistance
Get Started