Passing argument string which contains spaces and quotes - c++

Using QProcess::startDetached, I need to pass a dynamic argument list which is coming from another process to the starting process.
const QString & prog, const QStringList & args, const QString & workingDirectory ...)
Note that arguments that contain spaces are not passed to the process
as separate arguments.
...
Windows: Arguments that contain spaces are wrapped in quotes. The
started process will run as a regular standalone process.
I have a string which contains below text, It comes from an external program without any control on it:
-c "resume" -c "print 'Hi!'" -c "print 'Hello World'"
I need to pass above string to QProcess::startDetached so that the starting program catches it as same as above string.
Do I have to parse the string and build a string-list? Or anyone has a better solution?

You don't have to use a QStringList at all for the arguments, as there is this overloaded function: -
bool QProcess::startDetached(const QString & program)
Which, as the documentation states: -
Starts the program program in a new process. program is a single string of text containing both the program name and its arguments. The arguments are separated by one or more spaces.
The program string can also contain quotes, to ensure that arguments containing spaces are correctly supplied to the new process.
You may need to replace " with \", but you can do that from a QString
You can use parseCombinedArgString (from Qt's source code) to parse:
QStringList parseCombinedArgString(const QString &program)
{
QStringList args;
QString tmp;
int quoteCount = 0;
bool inQuote = false;
// handle quoting. tokens can be surrounded by double quotes
// "hello world". three consecutive double quotes represent
// the quote character itself.
for (int i = 0; i < program.size(); ++i)
{
if (program.at(i) == QLatin1Char('"'))
{
++quoteCount;
if (quoteCount == 3)
{
// third consecutive quote
quoteCount = 0;
tmp += program.at(i);
}
continue;
}
if (quoteCount)
{
if (quoteCount == 1)
inQuote = !inQuote;
quoteCount = 0;
}
if (!inQuote && program.at(i).isSpace())
{
if (!tmp.isEmpty())
{
args += tmp;
tmp.clear();
}
}
else
{
tmp += program.at(i);
}
}
if (!tmp.isEmpty())
args += tmp;
return args;
}

Yes you have to "parse" the string, splitting it at the correct positions, and enter each sub-string into the QStringList object you pass to the function.

Related

In Qt; what is the best method to capitalise the first letter of every word in a QString?

I am thinking of regular expressions, but that is not exactly readable. There are also functions like s.toUpper() to consider, and probably other things as well.
So what is the best method for capitalising the first letter of words in a QString?
Using this example as a reference, you can do something like this:
QString toCamelCase(const QString& s)
{
QStringList parts = s.split(' ', QString::SkipEmptyParts);
for (int i = 0; i < parts.size(); ++i)
parts[i].replace(0, 1, parts[i][0].toUpper());
return parts.join(" ");
}
Exactly the same, but written differently :
QString toCamelCase(const QString& s)
{
QStringList cased;
foreach (QString word, s.split(" ", QString::SkipEmptyParts))cased << word.at(0).toUpper() + word.mid(1);
return cased.join(" ");
}
This uses more memory but is without pointer access (no brackets operator).
There is an alternative way of doing this which iterates using references to the words and modifies the first character using a QChar reference instead:
QString capitalise_each_word(const QString& sentence)
{
QStringList words = sentence.split(" ", Qt::SkipEmptyParts);
for (QString& word : words)
word.front() = word.front().toUpper();
return words.join(" ");
}
Note that Qt::SkipEmptyParts is required here (as in the other answers to this question) since the first character of each word is assumed to exist when capitalising. This assumption will not hold with Qt::KeepEmptyParts (the default).
Incredible C++/Qt... You just want to get some chars ored with 0x20...

How do I extract a single character from a line and put it in a const char*

I have a text file which I'm going through line by line and taking relevant information from.
However, one of these lines I cannot seem to take the appropriate information from. I'm trying to take a single char from this line and put it into a const char*
The line is like this:
"character" "B"
And I'm trying to extract the 'B' character from inside the quotation marks.
std::string Output;
int Quotations = 0;
for (int i = 0; i < LineText.size(); ++i)
{
if ('"' == LineText[i])
{
Quotations += 1;
if (3 == Quotations)
{
Output += LineText[i + 1];
break;
}
}
}
return Output.c_str();
I'm grabbing the line just fine and also counting the quotation marks correctly but for some reason the outputted result is:
/=�UL��'F/�$/U�
Rather than:
B
Any ideas where I'm going wrong?
Thanks in advanced.
You return a pointer to local data. The Output string goes out of scope as soon as the function returns, and the pointer is then invalid.
Why don't you return a std::string instead?

Split a string in C++ after a space, if more than 1 space leave it in the string

I need to split a string by single spaces and store it into an array of strings. I can achieve this using the fonction boost:split, but what I am not being able to achieve is this:
If there is more than one space, I want to integrate the space in the vector
For example:
(underscore denotes space)
This_is_a_string. gets split into: A[0]=This A[1]=is A[2]=a A[3]=string.
This__is_a_string. gets split into: A[0]=This A[1] =_is A[2]=a A[4]=string.
How can I implement this?
Thanks
For this, you can use a combination of the find and substr functions for string parsing.
Suppose there was just a single space everywhere, then the code would be:
while (str.find(" ") != string::npos)
{
string temp = str.substr(0,str.find(" "));
ans.push_back(temp);
str = str.substr(str.find(" ")+1);
}
The additional request you have raised suggests that we call the find function after we are sure that it is not looking at leading spaces. For this, we can iterate over the leading spaces to count how many there are, and then call the find function to search from thereon. To use the find function from say after x positions (because there are x leading spaces), the call would be str.find(" ",x).
You should also take care of corner cases such as when the entire string is composed of spaces at any point. In that case the while condition in the current form will not terminate. Add the x parameter there as well.
This is by no means the most elegant solution, but it will get the job done:
void bizarre_string_split(const std::string& input,
std::vector<std::string>& output)
{
std::size_t begin_break = 0;
std::size_t end_break = 0;
// count how many spaces we need to add onto the start of the next substring
std::size_t append = 0;
while (end_break != std::string::npos)
{
std::string temp;
end_break = input.find(' ', begin_break);
temp = input.substr(begin_break, end_break - begin_break);
// if the string is empty it is because end_break == begin_break
// this happens because the first char of the substring is whitespace
if (!temp.empty())
{
std::string temp2;
while (append)
{
temp2 += ' ';
--append;
}
temp2 += temp;
output.push_back(temp2);
}
else
{
++append;
}
begin_break = end_break + 1;
}
}

How to separate a executing path to file path and parameter?

There are such lines as
C:\Program Files\Realtek\Audio\HDA\RAVCpl64.exe -s
in the registry key
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Run
Now I want to separate the absolute path and parameters from the line.
If the line is
C:\Space Dir\Dot.Dir\Sample Name.Dot.exe param path."C:\Space Dir\Dot.Dir\Sample Name.Dot.exe"
Which separator should I use to deal with this line? Is there any Windows API function to solve this problem?
The function you want is in the standard C library that you can use in Windows.
char theDrive[5],thePath[MAX_PATH],theFilename[MAX_PATH],theExtension[MAX_PATH];
_splitpath(theSCTDataFilename,theDrive,thePath,theFilename,theExtension);
You can also use a more general tokenizing function like this which takes any string, a char and a CStringArray..
void tokenizeString(CString theString, TCHAR theToken, CStringArray *theParameters)
{
CString temp = "";
int i = 0;
for(i = 0; i < theString.GetLength(); i++ )
{
if (theString.GetAt(i) != theToken)
{
temp += theString.GetAt(i);
}
else
{
theParameters->Add(temp);
temp = "";
}
if(i == theString.GetLength()-1)
theParameters->Add(temp);
}
}
CStringArray thePathParts;
tokenizeString("your\complex\path\of\strings\separated\by\slashes",'/',&thePathParts);
This will give you an array of CString (CStringArray object) that contains each section of the input string. You can use this function to parse the major chunks then the minor ones as long as you know the seperator charactor you want to split the string on.

how to pass command-line arguments as a string to an embedded Python script executed from C++?

I have a C++ program which exposes a Python interface to execute users' embedded Python scripts.
The user inserts the path of the Python script to run and the command-line arguments.
Then the script is executed through
boost::python::exec_file(filename, main_globals, main_globals)
To pass the command-line arguments to the Python script we have to set them through the Python C-API function
PySys_SetArgv(int args, char** argv)
before calling exec_file().
But this requires to tokenize the user's string containing the command-line arguments to get the list of arguments, and then to pass them back to the Python interpreter through PySys_SetArgv.
And that's more than a mere waste of time, because in this way the main C++ program has to take the responsibility of tokenizing the command-line string without knowing the logics behind, which is only defined in the custom user's script.
A much nicer and cleaner approach would be something like this in metacode:
string command_line_args = '-v -p "filename" -t="anotherfile" --list="["a", "b"]" --myFunnyOpt'
exec_file( filename, command_line_args, ...)
I spent hours looking at the Boost and Python C-API documentation but I did not find anything useful.
Do you know if there is a way to achieve this, i.e. passing a whole string of command line
arguments to an embedded Python script from C++?
Update:
As Steve suggested in the comments here below, I solved my problem tokenizing the input string, following https://stackoverflow.com/a/8965249/320369.
In my case I used:
// defining the separators
std::string escape_char = "\\"; // the escape character
std::string sep_char = " "; // empty space as separator
std::string quote_char = ""; // empty string --> we don't want a quote char'
boost::escaped_list_separator<char> sep( escape_char, sep_char, quote_char );
because I wanted to be able to parse tuples containing strings as well, like:
'--option-two=("A", "B")'
and if you use:
escaped_list_separator<char> sep('\\', ' ', '"');
as in the original post, you don't get the quoted strings tokenized correctly.
Since you are not adverse to executing an external file, you can use a helper program to make your shell command do the parsing for you. Your helper program could be:
#include <stdio.h>
int main (int argc, char *argv[])
{
for (int i = 1; i < argc; ++i) printf("%s\n", argv[i]);
return 0;
}
And then you could have code that sends your single string of arguments to the helper program (perhaps using popen) and read back the parsed arguments, each arg on a separate line.
unparsed_line.insert(0, "./parser_helper ");
FILE *helper = popen(unparsed_line.c_str(), "r");
std::vector<std::string> args;
std::vector<const char *> argv;
std::string arg;
while (fgetstring(arg, helper)) {
args.push_back(arg);
argv.push_back(args.rbegin()->c_str());
}
pclose(helper);
The fgetstring routine is something I wrote that is like a cross between fgets and std::getline. It reads from the FILE * one line at a time, populating a std:string argument.
static bool
fgetstring (std::string &s, FILE *in)
{
bool ok = false;
std::string r;
char buf[512];
while (fgets(buf, sizeof(buf), in) != 0) {
++ok;
r += buf;
if (*r.rbegin() == '\n') {
r.resize(r.size()-1);
break;
}
}
if (ok) s = r;
return ok;
}
I seem to remember a post on SO that had a routine similar to this, but I couldn't find it. I'll update my post if I find it later.