How to parse ODBC connection string with special characters - c++

While trying to figure out how to parse ODBC connection string containing special characters([]{}(),;?) I'd like to know if what I am trying to achieve is a valid requirement or not.
The SQLDriverConnect reads:
A DSN or connection string value enclosed with braces ({}) containing
any of the characters []{}(),;?*=!# is passed intact to the driver.
To me, it means special characters([]{}(),;?) are allowed including curly braces and semicolon in between (at least in values).
So, is it valid to expect PWD key(for example) look like this PWD={a{b}};c};?
And should its value be parsed to a{b}};c?
And if yes, can such a requirement be even easily achieved? Inspired by this solution my code so far:
#include <iostream>
#include <istream>
#include <string>
#include <vector>
enum class CSVState {
UnquotedField,
QuotedField
};
std::vector<std::string> readCSVRow(const std::string &row) {
CSVState state = CSVState::UnquotedField;
std::vector<std::string> fields {""};
size_t i = 0; // index of the current field
int depth = 0;
for (int ii = 0; ii < row.size(); ++ii) {
auto& c = row[ii];
switch (state) {
case CSVState::UnquotedField:
switch (c) {
case ';': // end of field
fields.push_back(""); i++;
break;
case '{': state = CSVState::QuotedField;
depth++;
break;
default: fields[i].push_back(c);
break;
}
break;
case CSVState::QuotedField:
switch (c) {
case '{': //state = CSVState::QuotedQuote;
depth++;
fields[i].push_back(c);
break;
case '}':
depth--;
if (depth == 0) {
state = CSVState::UnquotedField;
} else {
fields[i].push_back(c);
}
break;
default: fields[i].push_back(c);
break;
}
break;
}
}
std::cout << "fields: " << fields.size() << std::endl;
return fields;
}
/// Read CSV file, Excel dialect. Accept "quoted fields ""with quotes"""
std::vector<std::vector<std::string>> readCSV(std::istream &in) {
std::vector<std::vector<std::string>> table;
std::string row;
std::getline(in, row);
if (in.bad() || in.fail()) {
std::cout << "bad\n";
return {};
}
auto fields = readCSVRow(row);
table.push_back(fields);
for(auto& f : fields) {
std::cout << "'" << f << "' ";
}
std::cout << std::endl;
}
return table;
}
int main() {
auto res = readCSV(std::cin);
}
For input {a{b}};c}; it will produce 'a{b}' 'c}' '' while I think it should be modified to produce a{b}};c}.
Any clues how to do that?

The paragraph you mentioned refers to attributes, not strings. So for the string {a{b}};c}; the attributes separated by semicolon are {a{b}}, c} and ''.
A DSN or connection string value enclosed with braces
The key word here is value, meaning attribute value in the string.
So any attribute value can be enclosed in braces to be passed as is, but not the entire string.
Edit: If you want to parse values in braces no matter what they contain you can simply check the depth when encountering a semicolon:
case ';': // end of field
if (depth == 0) {
fields.push_back(""); i++;
} else {
fields[i].push_back(c);
}
break;
This way {a{b}};c}; will be parsed as {a{b}};c} and '' because only the semicolon that is not in braces is used.

Related

How to print spaces between words when trying to decrypt a text message from an input file, to an output file in C++?

For example, let's say you have this encrypted text message "ASBBU KQMCF" in input.txt, and when you decrypt it, it will become "HELLO WORLD" in output.txt.
As you can see, "A" becomes "H", "S" becomes "E", "B" becomes "L", "U" becomes "O", "K" becomes "W", "Q" becomes "O", "M" becomes "R", "C" becomes "L", and "F" becomes "D".
The problem is that, when I decrypt the whole text, it's in one piece and there are no spaces. It looks like this: "HELLOWORLD", while it has to look like this: "HELLO WORLD". Is it possible to read the text and decrypt it while retaining the spaces that are between words?
I wrote a code that reads a letter and changes it to the correct one, as shown in the code, but I can't figure out how to make spaces between words.
Here's my code:
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
int main() {
char ch;
ifstream read("input.txt");
ofstream write("output.txt");
while(read>>ch){
switch (ch) {
case 'A':
ch='H';
write<<ch;
}
switch(ch){
case 'S':
ch='E';
write<<ch;
}
switch(ch){
case 'B':
ch='L';
write<<ch;
}
switch(ch){
case 'U':
ch='O';
write<<ch;
}
switch(ch){
case 'K':
ch='W';
write<<ch;
}
switch(ch){
case 'Q':
ch='O';
write<<ch;
}
switch(ch){
case 'M':
ch='R';
write<<ch;
}
switch(ch){
case 'C':
ch='L';
write<<ch;
}
switch(ch){
case 'F':
ch='D';
write<<ch;
}
}
read.close();
write.close();
return 0;
}
operator>> skips leading whitespace by default, and then reads until whitespace is encountered. As such, your code will never see space characters in your ch variable.
Since you want to preserve whitespace, you should use std::getline() instead of operator>>. Read the file line-by-line into a std::string, and then you can manipulate the characters in each std::string as needed.
Also, you are misusing switch. You don't need a separate switch for each case. That is no better than just using a bunch of if-else blocks. You can have multiple cases in a single switch (that is its primary design - to make a selection from multiple choices).
Try this instead:
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
int main() {
string str;
ifstream read("input.txt");
ofstream write("output.txt");
while (getline(read, str)) {
for(char &ch : str) {
switch (ch) {
case 'A':
ch = 'H';
break;
case 'S':
ch = 'E';
break;
case 'B':
ch = 'L';
break;
case 'U':
ch = 'O';
break;
case 'K':
ch = 'W';
break;
case 'Q':
ch = 'O';
break;
case 'M':
ch = 'R';
break;
case 'C':
ch = 'L';
break;
case 'F':
ch = 'D';
break;
}
}
write << str << '\n';
}
read.close();
write.close();
return 0;
}
Also, just on a side note: your "encryption" has a flaw - you have both B and C being "encrypted" into L, and both Q and U being "encrypted" into O. Which means you will lose data on "decryption" if either L and/or O are present, since you won't know which original characters to "decrypt" them back to. Each unique input characters needs a unique output character.
im not sur about what u want to do .
but like i see before u can do unique switch.
and use default case where u write actual char like
write<<ch;
on this example i use switch to print hello world and change some letter
#include <iostream>
#include <string>
int main()
{
std::string str = "Hello, world!";
for (char c : str)
{
switch (c)
{
case 'H':
std::cout << "S" << std::endl;
break;
case 'e':
std::cout << "Q" << std::endl;
break;
default:
std::cout << c << std::endl;
}
}
return 0;
}
Do you think you can reuse this example to fix your issue?

reading and writing to a file with fstream

bool remove_vowels(const std::string& file_name) {
std::fstream fs{ file_name , std::ios_base::in | std::ios_base::out };
if (!fs) { std::cout << "Error file not open"; return false; }
char in{};
while (fs >> in) {
switch (tolower(in)) {
case 'a': case 'e': case 'i': case 'o': case 'u':
{
int64_t pos{ fs.tellg() };
fs.seekp(pos - 1);
fs << ' ';
fs.seekg(pos);
break;
}
}
}
return true;
}
I try to solve one exercise from: Programming: Principles and Practice Using C++ Bjarne Stroustrup
Write a program that removes all vowels from a file (“disemvowels”). For
example, Once upon a time! becomes nc pn tm!. Surprisingly often, the
result is still readable; try it on your friends.
The text file contain: "Once upon a time"
When i try this code inside the switch case:
int64_t pos { fs.tellg() }; // it get position one character forward
fs.seekp(pos - 1); // i set the write position to the the character
fs << ' '; // then i replace the vowel to whitespace and increment one position
i get infinite loop that overwrite all the file with these characters "nc "
its only work for me this way:
int64_t pos{ fs.tellg() };
fs.seekp(pos - 1);
fs << ' ';
fs.seekg(pos);
Why it is necessary to set the position after fs << ' '; if its already increases it?

get string after getline()

I want to hold a string with spaces therefore I used getline() but after it I want to get another string(no spaces) if there is a -e for example and the string after it in s2, but since in my code I lose the dash when using getline() I can't seem to achieve what I'm trying to do. any suggestions would be really helpful.
//example input: -f name -b blah blah -e email
//looking for output:
//name
//blah blah
//email
string s,s1,s2;
char check_character;
while (cin.peek() != '\n')
{
if (cin.get() == '-')
{
check_character = cin.get();
switch(check_character)
{
case 'f':
cin >> s;
break;
case 'b':
if(cin.peek() != '\n')
getline(cin, s1, '-');
else if(cin.peek() =='\n')
getline(cin, s1);
break;
case 'e':
cin>> s2;
break;
}
}
}
cout << s << endl << s1 << endl << s2 << endl;
return 0;
}
A better option would be to do a single call to getline() then parse the "command" string. There are many options of achieving this, from a simple split() on "-" or find('-')
getline() extracts characters from is and stores them into str until the delimitation character delim is found or the newline character, '\n'.
If the delimiter is found, it is extracted and discarded (i.e. it is not stored and the next input operation will begin after it).
I'm going to make a couple assumptions here:
You never expect a '\n' except at the end of the input string, even after "-b" (which your code will currently read in)
You expect to only accept 1 of each type of argument (cause your current code will stomp any previous entries)
A regex_search will handle this nicely with the regex:
(?:\s*-f\s+(\w+)|\s*-b\s+([^-]+)|\s*-e\s+(\w+))*
Live Example
You'll need to start by reading from cin into a variable, for example string input. This could be done like:
getline(cin, input)
Once you have your input you can simply do:
if(smatch m; regex_search(input, m, regex{ "(?:\\s*-f\\s+(\\w+)|\\s*-b\\s+([^-]+)|\\s*-e\\s+(\\w+))*" })) {
if(m[1].length() > 0U) {
cout << "-f " << m[1] << endl;
}
if(m[2].length() > 0U) {
cout << "-b " << m[2] << endl;
}
if(m[3].length() > 0U) {
cout << "-e " << m[3] << endl;
}
}
Live Example
If you go the approach of reading in the entire line, and you do not want to use Boost program options, or getopts, you could parse the line yourself (as has been suggested). Here would be one way of doing it, as an alternative of parsing on the fly in your code:
#include <iostream>
#include <string>
#include <tuple>
#include <vector>
using std::cout;
using std::endl;
using std::get;
using std::literals::string_literals::operator""s;
using std::make_tuple;
using std::string;
using std::tuple;
using std::vector;
static auto chunkLine(string const& line)
{
auto result = vector<string>{};
auto i = string::size_type{};
while (i != string::npos && i < line.size())
{
auto pos = line.find(" -", i);
auto count = pos == string::npos ? pos : (pos - i);
result.push_back(line.substr(i, count));
i = pos + (pos != string::npos ? 1 : 0);
}
return result;
}
static auto parseChunks(vector<string> const& chunks)
{
auto result = vector<tuple<string, string>>{};
for (auto const& chunk : chunks)
{
auto pos = chunk.find(" ");
if (pos != string::npos && chunk[0] == '-')
{
auto kv = make_tuple(chunk.substr(1, pos-1), chunk.substr(pos+1));
result.push_back(kv);
}
}
return result;
}
int main()
{
auto line = "-f name -b blah blah -e email"s;
auto keyValueTuples = parseChunks(chunkLine(line));
for (auto const& kv : keyValueTuples)
{
cout << get<1>(kv) << endl;
}
}
The way you parse the arguments could certainly be improved, but this answer is not about it. I think what you are looking for is to simply put the - char back into the stream after std::getline removed it. In this case you could just use .putback() method
if (std::cin.peek() != '\n')
{
std::getline(std::cin, s1, '-');
std::cin.putback('-');
}
I think you should put cin.ignore before typing getline as in your code:
`string s,s1,s2;
char check_character;
while (cin.peek() != '\n')
{
if (cin.get() == '-')
{
check_character = cin.get();
switch(check_character)
{
case 'f':
cin >> s;
break;
case 'b':
if(cin.peek() != '\n')
cin.ignore
getline(cin, s1, '-');
else if(cin.peek() =='\n')
cin.ignore
getline(cin, s1);
break;
case 'e':
cin>> s2;
break;
}
}
}
cout << s << endl << s1 << endl << s2 << endl;
return 0; `

Debug error when trying to read from file and output altered string

So I'm testing out a small program that reads a file containing a few lines of one or two characters each and, based on what characters are in the line, outputs directional arrows (Think DDR output style: "< ^ v >")
Just reading and outputting the lines as they appear in the text file works fine, but I can't edit the way they appear without getting a super generic debug error with no code or anything to tell me what the problem is. Is this just something I'm not allowed to do, or should I replace my string with an array?
Code below:
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
void output(string raw);
int main()
{
string content_line;
ifstream file;
file.open("level1.txt", ios::out);
if (file.is_open())
{
while (getline(file, content_line))
{
output(content_line);
}
file.close();
}
return 0;
}
void output(string raw)
{
string row = " ";
for (int i = 0; i <= raw.size(); i++)
{
switch (raw.at(i))
{
case 'l': row.at(0) = '<';
break;
case 'u': row.at(2) = '^';
break;
case 'd': row.at(4) = 'v';
break;
case 'r': row.at(6) = '>';
break;
}
}
cout << row << '\n';
}
The text file as it appears right now is just:
u
d
l
r
ud
lr
ul
dr

how to read a long string from a file into an array or something similar?

So I have searched for quite a while and found nothing that solves my problem currently I have a mess of a code from my previous problem, can someone attempt to tidy this so it works or simply remake it, I have 4 different text files to read from depending on what it is so the switch statement was my best idea and I intend to keep that bit. Just to add this code does not work.. .and can't work out why not.
ifstream QuestionFile;
int i = 0;
switch (x){
case 1:
QuestionFile.open("Topic1 Questions.txt", ios::app);
break;
case 2:
QuestionFile.open("Topic2 Questions.txt", ios::app);
break;
case 3:
QuestionFile.open("Topic3 Questions.txt", ios::app);
break;
case 4:
QuestionFile.open("Topic4 Questions.txt", ios::app);
break;
}
stringstream buffer;
buffer << QuestionFile.rdbuf();
string test = buffer.str();
size_t pos1 = 0;
size_t pos2;
if (!QuestionFile)
{
cout << "Cannot load file" << endl;
}
else
{
if (QuestionFile.peek() != ifstream::traits_type::eof()) {
while (!QuestionFile.eof())
{
pos2 = test.find("|", pos1);
questions[i] = test.substr(pos1, (pos2 - pos1));
cout << questions[i];
i++;
}
QuestionFile.close();
}
}
Okay so solved this myself in the end was actually really simple... so just in case anyone else comes across this and doesn't find a simple answer I've done it like this instead.
ifstream QuestionFile;
int i = 0;
//switch statement for checking which text file I personally wanted to use as it
//depending on which the user was allowed to use.
switch (x){
case 1:
QuestionFile.open("Topic1 Questions.txt", ios::app);
break;
case 2:
QuestionFile.open("Topic2 Questions.txt", ios::app);
break;
case 3:
QuestionFile.open("Topic3 Questions.txt", ios::app);
break;
case 4:
QuestionFile.open("Topic4 Questions.txt", ios::app);
break;
}
if (!QuestionFile)
{
cout << "Cannot load file" << endl;
}
else
{
if (QuestionFile.peek() != ifstream::traits_type::eof()) {
while (!QuestionFile.eof())
{
//This simply gets which ever line you want and puts it into that part of the array
//and does this till the end of the array.
getline(QuestionFile, questions[i]);
i++;
}
QuestionFile.close();
}
}
Since you'd like to keep the switch statement, try this:
const char *filename;
size_t pos;
size_t oldPos;
stringstream buffer;
string test;
filename = NULL;
switch(x)
{
case 0:
filename = "Topic1 Questions.txt";
break;
case 1:
filename = "Topic2 Questions.txt";
break;
case 2:
filename = "Topic3 Questions.txt";
break;
case 3:
filename = "Topic4 Questions.txt";
break;
}
QuestionFile.open(filename, ios::app); // try opening the file
if(QuestionFile.is_open()) // if open succeeds...
{
buffer << QuestionFile.rdbuf();
test = buffer.str(); // read entire file into 'test'
pos = 0; // start at beginning
do
{
oldPos = pos;
pos = test.find("|", oldPos); // find position of next line
questions[i] = test.substr(oldPos, (pos - oldPos)); // put in array
i++;
}
while(pos != oldPos); // as long as pos2 changes.
QuestionFile.close();
}
else
{
cout << "Cannot load file" << endl;
}
Note: I have not allocated an array for you, so you still need to do this yourself.