For my project I need a (partial) .slk (SYLK) parser which need to load 10+ fairly large tables (30.000-50.000 lines) quickly. The subset is very limited to just the following format:
ID;PWXL;N;E
Always first and it's only important if "ID" is there
B;X79;Y838;D0
The size of the table, always comes before "C" rows. X is the width and Y is the height.
C;X25;K0.25
C;X26;K"always"
C;X26;Y32;K"never"
X is the column and an optional Y sets the row (if missing the last row or 0 is used). The "K" part can either be in quotes or without. Either way the storage format is std::string (for now).
Now for the actual code
std::vector<std::vector<std::string>> table_data;
std::unordered_map<std::string, int> header_to_column;
std::unordered_map<std::string, int> header_to_row;
std::ifstream stream(path);
std::string line;
size_t position = 0;
size_t length = 0;
size_t column = 0;
size_t row = 0;
const auto parse_int_part = [&]() {
position++;
length = line.find_first_of(';', position) - position;
position += length;
return std::stoi(line.substr(position - length, length));
};
if (std::getline(file, line)) {
if (line.substr(0, 2) != "ID") {
std::cout << "Invalid SLK file, does not contain \"ID\" as first record" << std::endl;
return;
}
} else {
return;
}
while (std::getline(stream, line)) {
position = 2;
switch (line.front()) {
case 'B':
while (true) {
switch (line[position]) {
case 'X':
columns = parse_int_part();
break;
case 'Y':
rows = parse_int_part();
break;
default:
table_data.resize(rows, std::vector<std::string>(columns));
goto nextline;
}
if (position < line.size() - 1) {
position++;
}
}
case 'C':
while (true) {
switch (line[position]) {
case 'X':
column = parse_int_part() - 1;
break;
case 'Y':
row = parse_int_part() - 1;
break;
case 'K': {
position++;
if (line[position] == '\"') {
position++;
}
length = line.size() - position - ((line.back() == '\r') ? 1 : 0);
const std::string part = line.substr(position, length - ((line[position + length - 1] == '\"') ? 1 : 0) );
if (row == 0) {
header_to_column.emplace(part, column);
}
if (column == 0) {
header_to_row.emplace(part, row);
}
table_data[row][column] = part;
goto nextline;
}
}
if (position < line.size() - 1) {
position++;
}
}
case 'E':
goto exitloop;
}
nextline:
position = 2;
}
exitloop:;
I use some gotos which are probably ok to use here since the other solutions are a lot more verbose, but are apparently bad practice? And since the file may have "\r\n" line endings those have to be handled manually, but it feels like there should be a better way.
How could the parsing be improved while keeping the same performance?
Edit
I've changed the example so it can be run easily and a simple [test .slk].3.
switch
statements to aif() else if()
cascade, you could easily usebreak
andcontinue
instead of thegoto
statements. \$\endgroup\$table_data
for example. It would be nice if this were a function that a reviewer could copy and paste into a text file and actually test. \$\endgroup\$