CSV Parsing with c++ - c++

I'm trying to create a code that will parse through a csv database with stock information. Currently, I have the code generated so that it will search with a keyword and print the whole row, but I'm trying to get it so that it will print the whole row with the header row in a neatly formatted way.
I'm trying to get it so that if I searched Google, it'd return
Symbol GOOG
NAME Google Inc
High Today $568.77
How the csv looks like:
Symbol,Name,Price,High Today,Low Today,52 Week Low
GOOG,Google Inc.,$568.77 ,$570.25 ,$560.35
AAPL,Apple Inc.,$93.28 ,$63.89 ,$99.44.
Code:
string NameSearch::getInput()
{
cout << "Enter the name of the company you would like to search for: ";
getline(cin, input);
return input;
}
void NameSearch::NameAlgorithm()
{
string line;
ifstream fs("Stock Database.csv");
while (!fs.eof())
{
getline(fs, line);
string companyname = "";
string a;
int column = 1;
int commacount = 0;
int ChrCount = 0;
while (line != "\0")
{
a = line[ChrCount];
ChrCount++;
if (a == ",")
{
commacount++;
}
else if (commacount == column)
{
companyname.append(a);
}
else if (commacount > column)
{
break;
}
if (companyname == input)
{
cout << endl << line;
}
}
}
}

First a comma should be parsed as whitespace. You can do this by changing the internal std::ctype<charT> facet in the stream's locale:
struct csv_classification : std::ctype<char> {
csv_classification() : ctype(make_table()) { }
private:
static mask* make_table() {
const mask* classic = classic_table();
static std::vector<mask> v(classic, classic + table_size);
v[','] |= space;
v[' '] &= ~space;
return &v[0];
}
};
Then set the locale using:
ifs.imbue(std::locale(ifs.getloc(), new csv_classification));
Next make a manipulator that checks to see if you're at the end of the line. If you are it sets the std::ios_base::failbit flag in the stream state. Also use internal storage to tell if the record belongs as a key or value in the map. Borrowing a bit from Dietmar...
static int row_end = std::ios_base::xalloc();
std::istream& record(std::istream& is) {
while (std::isspace(is.peek())) {
int c(is.peek());
is.ignore();
if (c == '\n') {
is.iword(row_end) = !is.iword(row_end);
is.setstate(std::ios_base::failbit);
}
}
return is;
}
Then you can do:
std::vector<std::string> keys, values;
for (std::string item;;) {
if (ifs >> record >> item)
keys.push_back(item);
else if (ifs.eof())
break;
else if (ifs.iword(row_end)) {
ifs.clear();
while (ifs >> record >> item)
values.push_back(item);
}
else
break;
}
Now we need to apply both the keys and values and print them out. We can create a new algorithm for that:
template<class Iter1, class Iter2, class Function>
void for_each_binary_range(Iter1 first1, Iter1 last1,
Iter2 first2, Iter2 last2, Function f)
{
assert(std::distance(first1, last1) <= std::distance(first2, last2));
while (first1 != last1) {
f(*first1++, *first2++);
}
}
Finally we do:
for_each_binary_range(std::begin(keys), std::end(keys),
std::begin(values), std::end(values),
[&] (std::string const& key, std::string const& value)
{
std::cout << key << ": " << value << std::endl;
}
Live Demo

Here it is your solution (the most close to what you requested):
#include <iostream>
#include <fstream>
#include <string>
#include <sstream>
#include <vector>
using namespace std;
typedef std::vector<std::string> record;
std::istream&
operator>>(
std::istream& is,
record& r)
{
r.clear();
string line;
getline(is, line);
istringstream iss(line);
string field;
while (getline(iss, field, ',' ))
r.push_back(field);
return is;
}
int
main()
{
ifstream file("Stock Database.csv");
record headers, r;
if (file.good())
file >> headers;
while (file >> r)
{
for (int i = 0; i < r.size(); i++)
cout << headers[i] << ":\t" << r[i] << endl;
cout << "------------------------------" << endl;
}
return 0;
}
// EOF
The content of the "Stock Database.csv" file is:
Symbol,Name,Price,High Today,Low Today,52 Week Low
GOOG,Google Inc.,$568.77 ,$570.25 ,$560.35
AAPL,Apple Inc.,$93.28 ,$63.89 ,$99.44
Just do with the record whatever you want. The first read from the file suppose to bring you headers. Every next read fill each record with csv values.

Related

How to determine last string value in a text

I'm given a text in std::string that i want to analyze using stringstream.
The text is a line from a csv file in the following format:
SPIN;5;WIN;10;STOPPOSITIONS;27;1;14
I must create a key value pair (in a map) with the key being a string value from the line (ex: "SPIN") and the value a vector populated with the next integer value from the line (ex: 5). (KVP: {"SPIN", {5}}).
The problem is that I dont know how to determine the last string value of the line (in this example "STOPPOSITIONS").
When i get the word "STOPPOSITIONS" at the next iteration the variable word is changed to "1" which is wrong because i should create the following kvp (KVP: {"STOPPOSITIONS", {27,1,14}}).
What should i fix in order to find the last string value of a line?
Here is the code I'm using:
std::map<std::string, std::vector<uint64_t>> CsvReader::readAllKvp()
{
if (!_ifs->is_open())
{
_ifs->open(_fileName);
}
std::map<std::string, std::vector<uint64_t>> result;
std::string line;
std::string word;
uint64_t val;
while(getline(*_ifs,line,'\n') >> std::ws)
{
/* do stuff with word */
std::istringstream ss(line);
while(getline(ss, word, ';') >> std::ws)
{
//no more strings found
if(word == "")
{
//read all integers at the end of the line and put them
//in the map at the last key added (in our case: STOPPOSITIONS)
while(ss >> val)
{
result[result.rbegin()->first].push_back(val);
}
break;
}
if (result.find(word) == result.end()) //word not found in map
{
std::vector<uint64_t> newV;
result.insert(
std::pair<std::string, std::vector<uint64_t>>(word, newV));
}
ss >> val;
result[word].push_back(val);
ss.ignore(std::numeric_limits<std::streamsize>::max(),';');
}
}
_ifs->close();
return result;
}
I made an example of my suggested method. It only read one line, but adding another outer loop and processing all the lines of the file is a simple task.
#include <iostream>
#include <sstream>
#include <fstream>
#include <map>
#include <vector>
using std::cout;
using std::endl;
std::map<std::string, std::vector<uint64_t>> readAllKvp()
{
std::string str = "SPIN;5;WIN;10;STOPPOSITIONS;27;1;14";
std::stringstream ss(str); // Emulating input from file
std::map<std::string, std::vector<uint64_t>> result;
std::string word;
std::string last_string;
uint64_t val;
while(getline(ss >> std::ws, word, ';') >> std::ws)
{
try {
val = std::stoi(word);
if(!last_string.empty())
result[last_string].push_back(val);
} catch (std::invalid_argument&) {
last_string = word;
}
}
return result;
}
int main() {
auto map = readAllKvp();
for (auto& m : map) {
cout << m.first << ": ";
for (auto v : m.second)
cout << v << ' ';
cout << endl;
}
}

Parsing through text file with C++

I am trying to figure out the best way to read in numbers from a text file and set these numbers to variables. I am having trouble because there will be multiple text files that will be testing my code and they are all of different lengths and sizes. A sample test one looks like this:
0 (1,3) (3,5)
1 (2,6)
2 (4,2)
3 (1,1) (2,4) (4,6)
4 (0,3) (2,7)
Where the first number represents a vertex on a graph, the first number in a coordinate is the vertex it is going towards in a directed graph, and the second number is the weight of the edge. I tried doing getline and putting it into arrays but in certain test cases there could be 100 coordinates and I am not sure how to specify array size. I am also having trouble parsing through the parenthesis and comma and am not sure how to initialize the variables with the correct number from the text file.
Parsing shouldn't be that difficult, espacially when you can use std::stringstream to separate all the elements from input. Indeed, you want to remove all the paranthesis first then emplace the elements into the container.
#include <fstream>
#include <vector>
#include <string>
#include <sstream>
#include <algorithm>
#include <cctype>
int main()
{
std::ifstream read("file.txt");
std::vector<std::vector<std::pair<int, int>>> graph;
// read until you reach the end of the file
for (std::string line; std::getline(read, line); ) {
// removing punctuation like paranthesis, commas, etc.
std::replace_if(std::begin(line), std::end(line), [] (char x) { return std::ispunct(x); }, ' ');
// inserting the line into a stream that helps us parse the content
std::stringstream ss(line);
// read the node number
int source, destination, weight;
ss >> source;
// create a new vector for the new node, so you can place all it's destinations / weights in it
graph.insert(std::next(std::begin(graph), source), {{}});
// read the dests / weights until you reach the end of the current line
while (ss >> destination >> weight)
graph[source].emplace_back(destination, weight);
}
read.close();
std::ofstream write("output.txt");
for (const auto node : graph) {
for (const auto [dest, weight] : node)
write << "(" << dest << ", " << weight << ") ";
write << '\n';
}
}
Note that you need C++17 to compile the code. You have to use basic loops instead of ranged-for loops and omit auto if you use an older C++ standard. Also I used a vector of pairs, but it's better if you use a struct / class for nodes, to make the code more maintanable.
This works too.
#include<iostream>
#include<sstream>
#include<string>
using namespace std;
int main() {
int vertices[1000][3], qv = 0; //use number more than 1000 if it is required
while (cin) {
int n;
char c;
string s;
getline(cin, s);
istringstream is(s);
is >> n;
is >> c;
while (c == '(') {
vertices[qv][0] = n;
is >> vertices[qv][1];
is >> c; //,
is >> vertices[qv++][2];
is >> c; //)
is >> c; //(
}
}
for (int i = 0; i < qv; i++) //unified view
cout << vertices[i][0] << ' ' << vertices[i][1] << ' ' << vertices[i][2] << endl;
for (int i = 0; i < qv; i++) { //initial view
cout << vertices[i][0];
cout << " (" << vertices[i][1] << "," << vertices[i][2] << ")";
while (i + 1 < qv && vertices[i][0] == vertices[i + 1][0]) {
i++;
cout << " (" << vertices[i][1] << "," << vertices[i][2] << ")";
}
cout << endl;
}
}
I would use something like this:
#include <string>
#include <sstream>
#include <fstream>
#include <vector>
#include <algorithm>
struct coordinate
{
int vertex;
int weight;
};
struct vertex_set
{
int vertex;
std::vector<coordinate> coordinates;
};
std::istream& operator>>(std::istream &in, coordinate &out)
{
char ch1, ch2, ch3;
if (in >> ch1 >> out.to_vertex >> ch2 >> out.weight >> ch3)
{
if ((ch1 != '(') || (ch2 != ',') || (ch3 != ')'))
in.setstate(std::ios_base::failbit);
}
return in;
}
std::istream& operator>>(std::istream &in, std::vector<coordinate> &out)
{
out.clear();
coordinate coord;
while (in >> coord)
out.push_back(coord);
return in;
}
std::istream& operator>>(std::istream &in, vertex_set &out)
{
return in >> out.vertex >> out.coordinates;
}
std::ifstream f("file.txt");
std::string line;
while (std::getline(f, line))
{
vertex_set vs;
if (std::istringstream(line) >> vs)
{
// use vs.vertex and vs.coordinates as needed...
}
}

Read columns from a comma delimited data file c++

I have been trying to read following data table and create an object for the HUBs(rows) and another object for continent (columns). Since I am not a C++ experienced user I have been facing some difficulties. The data is in following. The number after HUB and the dash shows the order from the hub. The other numbers under each continent are the corresponding cost and tariffs between a HUB and continent. I would like to be able to cout for instance following and get the result which would be 73.
cout << hub(1)->cont(USA)->transport() << endl;
,USA,EUROPE,ASIA
HUB1-12000,,,
Transportation Cost,73,129,141
Tariffs,5,5,1
ShippingType,a,b,c
OtherFees,0.6,0.3,0.8
HUB2-11000,,,
Transportation Cost,57,101,57
Tariffs,7,7,5
ShippingType,b,b,d
OtherFees,0.7,0.3,0.6
Really appreciate your help. Here is what I have tried so far:
void Hub()
{
string file = "/hubs.csv";
// 1-First read the first line and save the continent name
string str, field;
getline( fin, str );
vector<string> contList;
stringstream linestr( str );
while ( linestr.good() )
{
getline( linestr, field, ',' );
string contname;
contList.push_back(contname);
}
// 2-Then read the rest
getline( fin, str );
while ( !fin.eof() ) // Read the whole file
{
stringstream linestr( str );
string contname, order;
if ( qstr[0] == 'HUB1' || qstr[0] == 'HUB2')
{
// Read the name of the hub
getline( linestr, hubname, ',' ); // Read the hub name
getline( linestr, order, ',' ); // Read the order quantityity
int quantity;
istringstream orderstream( order);
orderstream >> quantity;
// Find the hub and add the order to the hub
Hub* hub = glob->FindHubName( hubname ); // this returns a pointer
if ( glob->FindHubName( hubname ) == nullptr )
{
hubNotFound.push_back( hubname );
getline( fin, qstr );
continue;
}
hub->TotalOrder( quantity );
}
else if ( qstr[0] != 'HUB1' || qstr[0] != 'HUB2')
{
// Read costs and tariffs
cout << hub(1)->cont(ASIA)->transport()
}
getline( fin, qstr );
}
fin.close();
}
Something like this:
#include <iostream>
#include <fstream>
#include <boost/tokenizer.hpp>
#include <string>
int main() {
using namespace std;
using namespace boost;
string line, file_contents;
fstream file("test.csv");
if (!file.is_open()) {
cerr << "Unable to open file" << endl;
return 1;
}
getline(file, line);
tokenizer<> tok_head(line);
int n_columns = 0;
for (tokenizer<>::iterator beg=tok_head.begin(); beg!=tok_head.end(); ++beg) {
cout << *beg << '\t';
n_columns++;
}
cout << endl;
while (getline(file, line)) {
file_contents += line;
}
file.close();
tokenizer<> tok(file_contents);
int i = 0;
for (tokenizer<>::iterator beg=tok.begin(); beg!=tok.end(); ++beg, ++i) {
cout << *beg;
if (i % n_columns) {
cout << '\t';
} else {
cout << endl;
}
}
return 0;
}
Makefile
all: t
t: csv.cpp
g++ -I /usr/include/boost csv.cpp -o t
It looks like you must parse each line using different logic, so you should check first column first and using it apply appropriate logic, below is some pseudocode for that:
std::fstream fs("test.txt");
std::string line;
//
// Read line by line
while (std::getline(fs, line)) {
std::istringstream str(line);
std::string rec_type;
// Read record type (your first two lines looks like are of no type?)
if ( !std::getline(str, rec_type, ',') )
continue;
// Decide type of record, and parse it accordingly
if ( rec_type == "Transportation Cost") {
std::string val;
// Read comma delimited values
if ( !std::getline(str, val, ',') )
continue;
int ival1 = std::stoi(val);
if ( !std::getline(str, val, ',') )
continue;
int ival2 = std::stoi(val);
// ...
}
if ( rec_type == "Tariffs") {
std::string val;
if ( !std::getline(str, val, ',') )
continue;
int ival = std::stoi(val);
// ...
}
}
One method is to consider each line as a separate record and object.
Let the objects read their data.
For example:
class Tariff
{
int values[3];
public:
friend std::istream& operator>>(std::istream& input, Tariff& t);
};
std::istream& operator>>(std::istream& input, Tariff& t)
{
// Read and ignore the label "Tariff"
std::string name;
std::getline(input, name, ','); // Read until ',' delimiter.
input >> t.value[0];
// Note: the ',' is not a digit, so it causes an error state,
// which must be cleared.
input.clear();
input >> t.value[1];
input.clear();
input >> t.value[2];
input.clear();
}
Another method is to read the label first, then delegate to a function that reads in the row.
std::string row_text;
std::getline(text_file, row_text); // Read in first line and ignore.
while (std::getline(text_file, row_text))
{
std::istringstream text_stream(row_text);
std::string label;
std::getline(text_stream, label, ','); // Parse the label.
// Delegate based on label.
// Note: can't use switch for strings.
if (label == "Tariffs")
{
Input_Tariff_Data(text_stream);
}
else if (label == "ShippingType")
{
Input_Shipping_Type_Data(text_stream);
}
//...
} // End-while
The if-else ladder can be replaced by a lookup table that uses function pointers. Sometimes the table is easier to read.
typedef void (*P_Input_Processor)(std::istringstream& text_stream);
struct Table_Entry
{
char const * label;
*P_Input_Processor input_processor;
};
//...
const Table_Entry delegation_table[] =
{
{"Tariffs", Input_Tariff_Data},
{"ShippingType", Input_Shipping_Type_Data},
};
const unsigned int entry_quantity =
sizeof(delegation_table) / sizeof(delegation_table[0]);
// ...
std::string row_text;
std::getline(input_file, row_text); // Read and ignore first line.
while (std::getline(input_file, row_text))
{
// Create a stream for parsing.
std::istringstream text_stream(row_text);
// Extract label text
std::string label;
std::getline(text_stream, label, ',');
// Lookup label in table and execute associated function.
for (unsigned int index = 0; index < entry_quantity; ++index)
{
if (label == delegation_table[index].name)
{
// Execute the associated input function
// by derferencing the function pointer.
delegation_table[index](text_stream);
break;
}
}
}
An alternative to the lookup table is to use:
std::map<std::string, P_Input_Processor>
or
std::map<std::string, void (*P_Input_Processor)(std::istringstream&)>

C++ , read line by line and search a keyword that links to a line in a file

I've been working for days now on a machine simulator that reads instructions from a file. Just started working with C++ and im studying on vectors and parsing.
A sample file looks like this :
label0: left
right
if <1> goto label1
write 0
goto label2
label1: right
write 1
label2: halt
my code for the tape looks like this :
int main() {
int total_lines=0;
int RWH=0;
// Create a vector of strings
std::vector<string> Instruction;
// Create a string variable to hold each line read from the file.
string InstLine;
// Read through file and push onto our vector
vector<char> TapeVector; //declare tape
char temp;
cin >> noskipws;
cout << "Enter the input (end with ~):"; //initialize tape
while (cin >> temp && temp != '~') {
TapeVector.push_back(temp);
}
cout << "INPUT:";
for (int i=0;i<TapeVector.size();++i)
{
cout << TapeVector[i];
}
cout << endl;
// Open our file
ifstream InFile("input.txt",ifstream::in);
// If we can read/write great
if (InFile.good())
{
{
while(std::getline(InFile, InstLine))
Instruction.push_back(InstLine);
++total_lines;
}
}
else
cout << "Error reading file";
and here is for the parsing which I'm having problems:
while (std::getline(infile, line))
std::istringstream iss(line);
string a, b, c, d;
if (iss >> a >> b >> c >> d) //if 4 parameters
{
if (a=="if" && b==VectorTape[RWH])
{
counter=0; // counter will reset if b
}
;
}
else if (iss >> a >> b >> ) //if 2 parameters
{
if (a=="goto" //unfinished
}
else if (iss >> a >> b) //if 2 parameters
{
if (a=="goto" //unfinished
}
else if (iss >> a) //if 1 parameter
{
}
enter code here
on line 3 if label1 is detected, how do I jump and read the line with the label label1?
#include <iostream>
#include <fstream>
#include <map>
#include <string>
#include <vector>
#include <cstdlib>
#include "label.h"
using namespace std;
int main() {
int RWH=0;
std::vector<string> Instruction;
int total_lines=0;
vector<char> TapeVector; //declare tape
char temp;
cin >> noskipws;
cout << "Enter the input (end with ~):"; //initialize tape
while (cin >> temp && temp != '~')
{
TapeVector.push_back(temp);
}
cout << "INPUT:";
for (int j=0;j<TapeVector.size();++j)
{
cout << TapeVector[j];
}
cout << endl;
// Open our file
ifstream InFile("input.txt",ifstream::in);
// If we can read/write great
if (InFile.good())
{
{
string InstLine;
// Read through file and push onto our vector
while(std::getline(InFile, InstLine))
Instruction.push_back(InstLine);
++total_lines;
}
}
else
cout << "Error reading file";
vector<string> labels;
labels = lookupLabels(Instruction);
size_t i = 0;
while (i < Instruction.size())
{
istringstream iss(Instruction[i]);
// ...
string a,b,c,d;
iss >> a >> b >> c >> d;
if (a == "if")
{
if (b == TapeVector[RWH])
{
i = labels[d];
continue;
}
else
;
}
else if (a == "goto")
{
i=labels[b];
continue;
}
else if (a == "write")
{
b = TapeVector[RWH];
}
else if (a == "right")
{
++RWH;
}
else if (a == "left")
{
--RWH;
}
else if (a == "halt")
{
goto end;
}
else
{
continue;
}
++i;
} //end while
end:
for (int k=0;k<TapeVector.size();++k)
{
cout << TapeVector[k];
}
cout << endl;
InFile.close();
system("Pause");
return 0;
}
this is what i've done so far.. but there are plenty of errors i dont seem to understand.
51 C:\Users\JHONIN\Documents\THISIS\readtest 2.cpp no match for 'operator=' in 'labels = lookupLabels(const std::vector<std::string, std::allocator<std::string> >&)()'
55 C:\Users\JHONIN\Documents\THISIS\readtest 2.cpp variable `std::istringstream iss' has initializer but incomplete type
You cannot just jump forward in input file to unknown location.
You need to read entire program, store it in memory in a datastructure, at least annotated with labels, then jump within that structure, e.g.:
typedef map<string, size_t> Labels; // map label -> position in Instructions vector;
Labels lookupLabels(const vector<string>& instr) {
Labels ret;
for (size_t i = 0; i < instr.size(); ++i) {
const string& s = instr[i];
size_t colonpos = s.find(':');
if (colonpos != string::npos)
ret[s.substr(0, colonpos)] = i;
}
return ret;
}
now you can modify you "parsing with problems" in the same way - i.e. loop over lines in instructions vector, not in file. Once you hit a goto just pick another line number in labels and proceed with it, roughly like this
labels = lookupLabels(Instruction)
size_t i = 0;
while (i < Instruction.size()) {
istringstream iss(Instruction[i]);
// ...
iss >> a;
if (a == "goto") {
string label;
iss >> label;
i = labels[label];
continue;
}
++i;
}

c++ reading a a txt file line by line into a char array within an array of structures

I'm new to C++ and having a bit of trouble understanding the whole reading a file stream thing.. any help would be appreciated... here's where i'm having trouble
I Have an array of structures like this; (no I'm not allowed to use string to store these things apparently, or vectors or any other more advanced thing I haven't covered)...
struct Staff
{
char title[TITLESIZE];
char firstName[NAMESIZE];
char familyName[NAMESIZE];
char position[POSSIZE];
char room[TITLESIZE];
char email[POSSIZE];
};
Then I have an array of these structure;
Staff record[MAXNOSTAFF];
The data is contained in a text file separated by tabs. However some fields may contain whitespace.
Data Like below:
Dr Sherine ANTOUN Lecturer 4327 3.204 sherine_antoun#gmail.com
Here is what I have written in my code...
//function prototypes
bool getRecord (ifstream& infile, Staff dataAr[], bool& fileFound);
int main()
{
Staff record[MAXNOSTAFF];
bool fileFound;
ifstream infile;
getRecord(infile, record, fileFound); //function call
if (fileFound==true)
{
cerr <<"Exiting Program"<<endl;
exit(1);
}
return 0;
}
//function definitions
bool getRecord (ifstream& infile, Staff dataAr[], bool& fileFound)
{
infile.open("phonebook.txt");
if (infile)
{
fileFound = true;
cout << "File " <<PHONEBOOK<< " opened successfully.\n\n";
}
else if (!infile)
{
fileFound = false;
cerr << "Error! File could not be opened. \n";
}
while (infile.good())
{
for (int lineIndex=0; lineIndex<MAXNOSTAFF; lineIndex++)
for (int titleIndex=0; titleIndex<TITLESIZE; titleIndex++)
{
cin.getline(dataAr[lineIndex].title[titleIndex], MAXNOSTAFF, '/t');
}
}
//check it works properly
for (int k=0;k<10; k++)
{
for (int m=0; m<11; m++)
{
cout << k <<". Title is : "<<dataAr[k].title[m]<<endl;
}
}
infile.close();
return fileFound;
}
Any help would be greatly appreciated.. thank you
Let me show you the Boost Spirit approach to parsing input data like this.
If you start with a struct like
struct Staff
{
std::string title;
std::string firstName;
std::string familyName;
std::string position;
std::string room;
std::string email;
};
You can use a Spirit grammar like:
column = lexeme [ *~char_("\t\r\n") ];
start = column >> '\t' >> column >> '\t' >> column >> '\t' >> column >> '\t' >> column >> '\t' >> column;
And parse all rows into a vector like:
It f(std::cin), l;
std::vector<Staff> staff_members;
bool ok = qi::parse(f, l, grammar % qi::eol, staff_members);
if (ok)
{
for(auto const& member : staff_members)
{
std::cout << boost::fusion::as_vector(member) << "\n";
}
} else
{
std::cout << "Parsing failed\n";
}
if (f != l)
std::cout << "Remaining input '" << std::string(f, l) << "'\n";
Here's the complete test program Live on Coliru, sample run:
clang++ -std=c++11 -Os -Wall -pedantic main.cpp && ./a.out <<INPUT
Dr Sherine ANTOUN Lecturer 4327 3.204 sherine_antoun#gmail.com
Mr Jason SCRYPT Enthusiast 3472 9.204 jason_scrypt#yahoo.com
INPUT
Output:
(Dr Sherine ANTOUN Lecturer 4327 3.204 sherine_antoun#gmail.com)
(Mr Jason SCRYPT Enthusiast 3472 9.204 jason_scrypt#yahoo.com)
Remaining input '
'
Full Listing
#include <boost/fusion/adapted/struct.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/tuple/tuple_io.hpp>
namespace qi = boost::spirit::qi;
struct Staff
{
std::string title;
std::string firstName;
std::string familyName;
std::string position;
std::string room;
std::string email;
};
BOOST_FUSION_ADAPT_STRUCT(Staff,
(std::string, title)
(std::string, firstName)
(std::string, familyName)
(std::string, position)
(std::string, room)
(std::string, email))
template <typename It, typename Skipper = qi::unused_type>
struct grammar : qi::grammar<It, Staff(), Skipper>
{
grammar() : grammar::base_type(start)
{
using namespace qi;
column = lexeme [ *~char_("\t\r\n") ];
start = column >> '\t' >> column >> '\t' >> column >> '\t' >> column >> '\t' >> column >> '\t' >> column;
}
private:
qi::rule<It, std::string(), Skipper> column;
qi::rule<It, Staff(), Skipper> start;
};
int main()
{
std::cin.unsetf(std::ios::skipws);
typedef boost::spirit::istream_iterator It;
grammar<It> grammar;
It f(std::cin), l;
std::vector<Staff> staff_members;
bool ok = qi::parse(f, l, grammar % qi::eol, staff_members);
if (ok)
{
for(auto const& member : staff_members)
{
std::cout << boost::fusion::as_vector(member) << "\n";
}
} else
{
std::cout << "Parsing failed\n";
}
if (f != l)
std::cout << "Remaining input '" << std::string(f, l) << "'\n";
}
Since you can't use std::string and std::vector, sscanf() may be your choice:
while (infile.good())
{
char line[BUF_SIZE];
for (int lineIndex=0; lineIndex<MAXNOSTAFF; lineIndex++)
{
infile.getline(line, BUF_SIZE);
sscanf(line, "%s %s %s %[^\t] %s %s", dataAr[lineIndex].title, dataAr[lineIndex].firstName, dataAr[lineIndex].familyName, dataAr[lineIndex].position, dataAr[lineIndex].room, dataAr[lineIndex].email);
}
}
Note the %[^\t] format specifier, it will match every character that's not \t(because of ^), so that the fileds that contain whitespace can be read correctly. I don't know which fields exactly contain whitespace, so I just write an example.
EDIT:
if std::string and std::stirngstreamis allow to use, you can split the string after get a line from the file stream:
while (infile.good())
{
char line[BUF_SIZE];
for (int lineIndex=0; lineIndex<MAXNOSTAFF; lineIndex++)
{
infile.getline(line, BUF_SIZE);
stringstream ss(line);
std::string s;
getline(ss, s, '\t'); // get the first field
getline(ss, s, '\t'); // get the second field
// ...
}
}