Related
Same question as Create a streambuf from const char* except that I can't use boost.
I have to implement an function that takes a const char * as input parameter, and to do so I have to call an other function that takes a istream as input parameter.
Here is a sample of code very simplified:
#include <iostream>
#include <string>
using namespace std ;
void inner_function_I_cant_change ( istream & input ) // the function I must use
{
string s ; // dummy implementation
input >> s ;
cout << s.size() << " : <" << s << ">" << endl ;
}
struct externbuf : streambuf // my own streambuf using a char*...
{
int size ;
bool done ;
char * buffer ;
externbuf ( const char * buffer , int size ) :
size(size),
done(false),
buffer(const_cast<char*>( buffer )) {} // ...that forces me to an ugly const_cast !
int underflow ()
{
if (this->gptr() == this->egptr())
{
if (done) return std::char_traits<char>::eof() ;
this->setg( buffer,buffer,buffer+size ) ;
done = true ;
}
return char_traits<char>::to_int_type( *this->gptr()) ;
}
};
void API_function_I_must_povide ( const char * data , int size ) // the function I must implement
{
externbuf buf( data,size ) ;
istream input( &buf ) ;
inner_function_I_cant_change( input ) ;
}
int main ()
{
API_function_I_must_povide( "bazinga!",8 ) ;
}
This code I works well but I had to do an ugly const_cast !
I tried using a basic_streambuf<const char, char_traits<const char> > instead of a streambuf but I get many errors that I didn't understand well.
Is there a proper way to do it ?
(and, as I said, I can't use boost)
Thanks !
Thanks Remy, your link to Art Of Code made my day!
So, for those who are interested, here my new code, without memcpy and ugly const_cast:
#include <iostream>
#include <string>
using namespace std ;
void inner_function_I_cant_change ( istream & input )
{
char buffer [1000] ;
input.read( buffer,sizeof buffer ) ;
string s1( buffer,input.gcount()) ;
string s2( "hello \0 world !",15 ) ;
if (s1 == s2)
cout << "success!" ;
}
struct externbuf : public streambuf
{
externbuf ( const char * data , unsigned int len ) : begin(data),crt(data),end(data + len) {}
int_type underflow ()
{
return crt == end ? traits_type::eof() : traits_type::to_int_type( *crt ) ;
}
int_type uflow ()
{
return crt == end ? traits_type::eof() : traits_type::to_int_type( *crt++ ) ;
}
int_type pbackfail ( int_type ch )
{
bool cond = crt == begin || (ch != traits_type::eof() && ch != crt[-1]) ;
return cond ? traits_type::eof() : traits_type::to_int_type( *--crt ) ;
}
streamsize showmanyc ()
{
return end - crt ;
}
const char *begin,*crt,*end ;
};
void API_function_I_must_povide ( const char * data , int size )
{
externbuf buf( data,size ) ;
istream input( &buf ) ;
inner_function_I_cant_change( input ) ;
}
int main ()
{
API_function_I_must_povide( "hello \0 world !",15 ) ;
}
Thanks Pete, your solution is development-less but I'm afraid it induces a memcpy from data to the inner buffer of the std::string. And since my buffer may be very big, I try to avoid them as much as possible.
What's the best way to expand
${MyPath}/filename.txt to /home/user/filename.txt
or
%MyPath%/filename.txt to c:\Documents and settings\user\filename.txt
with out traversing the path string looking for environement variables directly?
I see that wxWidgets has a wxExpandEnvVars function. I can't use wxWidgets in this case, so I was hoping to find a boost::filesystem equivalent or similar. I am only using the home directory as an example, I am looking for general purpose path expansion.
For UNIX (or at least POSIX) systems, have a look at wordexp:
#include <iostream>
#include <wordexp.h>
using namespace std;
int main() {
wordexp_t p;
char** w;
wordexp( "$HOME/bin", &p, 0 );
w = p.we_wordv;
for (size_t i=0; i<p.we_wordc;i++ ) cout << w[i] << endl;
wordfree( &p );
return 0;
}
It seems it will even do glob-like expansions (which may or may not be useful for your particular situation).
On Windows, you can use ExpandEnvironmentStrings. Not sure about a Unix equivalent yet.
If you have the luxury of using C++11, then regular expressions are quite handy. I wrote a version for updating in place and a declarative version.
#include <string>
#include <regex>
// Update the input string.
void autoExpandEnvironmentVariables( std::string & text ) {
static std::regex env( "\\$\\{([^}]+)\\}" );
std::smatch match;
while ( std::regex_search( text, match, env ) ) {
const char * s = getenv( match[1].str().c_str() );
const std::string var( s == NULL ? "" : s );
text.replace( match[0].first, match[0].second, var );
}
}
// Leave input alone and return new string.
std::string expandEnvironmentVariables( const std::string & input ) {
std::string text = input;
autoExpandEnvironmentVariables( text );
return text;
}
An advantage of this approach is that it can be adapted easily to cope with syntactic variations and deal with wide strings too. (Compiled and tested using Clang on OS X with the flag -std=c++0x)
Simple and portable:
#include <cstdlib>
#include <string>
static std::string expand_environment_variables( const std::string &s ) {
if( s.find( "${" ) == std::string::npos ) return s;
std::string pre = s.substr( 0, s.find( "${" ) );
std::string post = s.substr( s.find( "${" ) + 2 );
if( post.find( '}' ) == std::string::npos ) return s;
std::string variable = post.substr( 0, post.find( '}' ) );
std::string value = "";
post = post.substr( post.find( '}' ) + 1 );
const char *v = getenv( variable.c_str() );
if( v != NULL ) value = std::string( v );
return expand_environment_variables( pre + value + post );
}
expand_environment_variables( "${HOME}/.myconfigfile" ); yields /home/joe/.myconfigfile
As the question is tagged "wxWidgets", you can use wxExpandEnvVars() function used by wxConfig for its environment variable expansion. The function itself is unfortunately not documented but it basically does what you think it should and expands any occurrences of $VAR, $(VAR) or ${VAR} on all platforms and also of %VAR% under Windows only.
Within the C/C++ language, here is what I do to resolve environmental variables under Unix. The fs_parm pointer would contain the filespec (or text) of possible environmental variables to be expanded. The space that wrkSpc points to must be MAX_PATH+60 chars long. The double quotes in the echo string are to prevent the wild cards from being processed. Most default shells should be able to handle this.
FILE *fp1;
sprintf(wrkSpc, "echo \"%s\" 2>/dev/null", fs_parm);
if ((fp1 = popen(wrkSpc, "r")) == NULL || /* do echo cmd */
fgets(wrkSpc, MAX_NAME, fp1) == NULL)/* Get echo results */
{ /* open/get pipe failed */
pclose(fp1); /* close pipe */
return (P_ERROR); /* pipe function failed */
}
pclose(fp1); /* close pipe */
wrkSpc[strlen(wrkSpc)-1] = '\0';/* remove newline */
For MS Windows, use the ExpandEnvironmentStrings() function.
This is what I use:
const unsigned short expandEnvVars(std::string& original)
{
const boost::regex envscan("%([0-9A-Za-z\\/]*)%");
const boost::sregex_iterator end;
typedef std::list<std::tuple<const std::string,const std::string>> t2StrLst;
t2StrLst replacements;
for (boost::sregex_iterator rit(original.begin(), original.end(), envscan); rit != end; ++rit)
replacements.push_back(std::make_pair((*rit)[0],(*rit)[1]));
unsigned short cnt = 0;
for (t2StrLst::const_iterator lit = replacements.begin(); lit != replacements.end(); ++lit)
{
const char* expanded = std::getenv(std::get<1>(*lit).c_str());
if (expanded == NULL)
continue;
boost::replace_all(original, std::get<0>(*lit), expanded);
cnt++;
}
return cnt;
}
Using Qt, this works for me:
#include <QString>
#include <QRegExp>
QString expand_environment_variables( QString s )
{
QString r(s);
QRegExp env_var("\\$([A-Za-z0-9_]+)");
int i;
while((i = env_var.indexIn(r)) != -1) {
QByteArray value(qgetenv(env_var.cap(1).toLatin1().data()));
if(value.size() > 0) {
r.remove(i, env_var.matchedLength());
r.insert(i, value);
} else
break;
}
return r;
}
expand_environment_variables(QString("$HOME/.myconfigfile")); yields /home/martin/.myconfigfile
(It also works with nested expansions)
What's the best way to expand
${MyPath}/filename.txt to /home/user/filename.txt
or
%MyPath%/filename.txt to c:\Documents and settings\user\filename.txt
with out traversing the path string looking for environement variables directly?
I see that wxWidgets has a wxExpandEnvVars function. I can't use wxWidgets in this case, so I was hoping to find a boost::filesystem equivalent or similar. I am only using the home directory as an example, I am looking for general purpose path expansion.
For UNIX (or at least POSIX) systems, have a look at wordexp:
#include <iostream>
#include <wordexp.h>
using namespace std;
int main() {
wordexp_t p;
char** w;
wordexp( "$HOME/bin", &p, 0 );
w = p.we_wordv;
for (size_t i=0; i<p.we_wordc;i++ ) cout << w[i] << endl;
wordfree( &p );
return 0;
}
It seems it will even do glob-like expansions (which may or may not be useful for your particular situation).
On Windows, you can use ExpandEnvironmentStrings. Not sure about a Unix equivalent yet.
If you have the luxury of using C++11, then regular expressions are quite handy. I wrote a version for updating in place and a declarative version.
#include <string>
#include <regex>
// Update the input string.
void autoExpandEnvironmentVariables( std::string & text ) {
static std::regex env( "\\$\\{([^}]+)\\}" );
std::smatch match;
while ( std::regex_search( text, match, env ) ) {
const char * s = getenv( match[1].str().c_str() );
const std::string var( s == NULL ? "" : s );
text.replace( match[0].first, match[0].second, var );
}
}
// Leave input alone and return new string.
std::string expandEnvironmentVariables( const std::string & input ) {
std::string text = input;
autoExpandEnvironmentVariables( text );
return text;
}
An advantage of this approach is that it can be adapted easily to cope with syntactic variations and deal with wide strings too. (Compiled and tested using Clang on OS X with the flag -std=c++0x)
Simple and portable:
#include <cstdlib>
#include <string>
static std::string expand_environment_variables( const std::string &s ) {
if( s.find( "${" ) == std::string::npos ) return s;
std::string pre = s.substr( 0, s.find( "${" ) );
std::string post = s.substr( s.find( "${" ) + 2 );
if( post.find( '}' ) == std::string::npos ) return s;
std::string variable = post.substr( 0, post.find( '}' ) );
std::string value = "";
post = post.substr( post.find( '}' ) + 1 );
const char *v = getenv( variable.c_str() );
if( v != NULL ) value = std::string( v );
return expand_environment_variables( pre + value + post );
}
expand_environment_variables( "${HOME}/.myconfigfile" ); yields /home/joe/.myconfigfile
As the question is tagged "wxWidgets", you can use wxExpandEnvVars() function used by wxConfig for its environment variable expansion. The function itself is unfortunately not documented but it basically does what you think it should and expands any occurrences of $VAR, $(VAR) or ${VAR} on all platforms and also of %VAR% under Windows only.
Within the C/C++ language, here is what I do to resolve environmental variables under Unix. The fs_parm pointer would contain the filespec (or text) of possible environmental variables to be expanded. The space that wrkSpc points to must be MAX_PATH+60 chars long. The double quotes in the echo string are to prevent the wild cards from being processed. Most default shells should be able to handle this.
FILE *fp1;
sprintf(wrkSpc, "echo \"%s\" 2>/dev/null", fs_parm);
if ((fp1 = popen(wrkSpc, "r")) == NULL || /* do echo cmd */
fgets(wrkSpc, MAX_NAME, fp1) == NULL)/* Get echo results */
{ /* open/get pipe failed */
pclose(fp1); /* close pipe */
return (P_ERROR); /* pipe function failed */
}
pclose(fp1); /* close pipe */
wrkSpc[strlen(wrkSpc)-1] = '\0';/* remove newline */
For MS Windows, use the ExpandEnvironmentStrings() function.
This is what I use:
const unsigned short expandEnvVars(std::string& original)
{
const boost::regex envscan("%([0-9A-Za-z\\/]*)%");
const boost::sregex_iterator end;
typedef std::list<std::tuple<const std::string,const std::string>> t2StrLst;
t2StrLst replacements;
for (boost::sregex_iterator rit(original.begin(), original.end(), envscan); rit != end; ++rit)
replacements.push_back(std::make_pair((*rit)[0],(*rit)[1]));
unsigned short cnt = 0;
for (t2StrLst::const_iterator lit = replacements.begin(); lit != replacements.end(); ++lit)
{
const char* expanded = std::getenv(std::get<1>(*lit).c_str());
if (expanded == NULL)
continue;
boost::replace_all(original, std::get<0>(*lit), expanded);
cnt++;
}
return cnt;
}
Using Qt, this works for me:
#include <QString>
#include <QRegExp>
QString expand_environment_variables( QString s )
{
QString r(s);
QRegExp env_var("\\$([A-Za-z0-9_]+)");
int i;
while((i = env_var.indexIn(r)) != -1) {
QByteArray value(qgetenv(env_var.cap(1).toLatin1().data()));
if(value.size() > 0) {
r.remove(i, env_var.matchedLength());
r.insert(i, value);
} else
break;
}
return r;
}
expand_environment_variables(QString("$HOME/.myconfigfile")); yields /home/martin/.myconfigfile
(It also works with nested expansions)
I'm doing UVa Problem 10082 and I'm trying to read in some sample input to test my solution. However, when I read in the text '''CCC it outputs ;;XXX. Note that there are only 2 semi-colons, when there should be 3 since there are 3 single quotes in the input. Why is getline() ignoring the first single quote?
Here's my code:
#include <iostream>
#include <string>
using namespace std;
char mapToWertyu(char c)
{
char newC = c;
char wertyu[] = {'1','2','3','4','5','6','7','8','9','0','-','=',
'Q','W','E','R','T','Y','U','I','O','P','[',']','\\',
'A','S','D','F','G','H','J','K','L',';','\'',
'Z','X','C','V','B','N','M',',','.','/'};
char qwerty[] = {'~','1','2','3','4','5','6','7','8','9','0','-','=',
'Q','W','E','R','T','Y','U','I','O','P','[',']','\\',
'A','S','D','F','G','H','J','K','L',';','\'',
'Z','X','C','V','B','N','M',',','.','/'};
if(c=='A' || c=='Q' || c=='Z')
return c;
for(int i = 0; i < 47; ++i)
{
if(wertyu[i]==c)
{
newC = qwerty[i];
break;
}
}
return newC;
}
int main()
{
string input;
while(cin.peek()!=-1)
{
getline(cin,input);
for(int i = 0; i < input.length(); ++i)
{
if(input[i]!='\\')
cout << mapToWertyu(input[i]);
}
cin.ignore(1,'\n');
cout << endl;
}
return 0;
}
Because you tell it to. What do you thing std::cin.ignore( 1,
'\n' ) should do, if not ignore a character. std::getline
extracts the '\n' character, even if it doesn't put it into
the string.
For the rest, you're not doing the input right. For starters, std::cin.peek() will return an integer in the
range [0...UCHAR_MAX] or EOF. EOF is often defined to be
-1, but this is certainly not guaranteed. But more generally:
why not use the usual idiom:
while ( std::getline( std::cin, input ) ) {
// ...
}
You also construct the arrays in mapToWertyu each time you
call it. That is definitly not what you want to do. You could
use just one static array, indexed directly by the character,
bug this does make the program dependent on the encoding. To
use two arrays, however:
static char const wertyu[] = { ... };
static char const qwerty[] = { ... };
char const* pos = std::find( std::begin( wertyu ), std::end( wertyu ), c );
return pos == std::end( wertyu )
? c
: qwerty[ pos - std::begin( wertyu ) ];
Solves the problem in a much simpler manner. (And there's no
need to special case 'A', 'Q' and 'Z'. If you don't want
to transcode them, just don't put them in the table.)
Or...
struct Map
{
char from;
char to;
};
static Map const map[] =
{
{ '1', '~' },
{ '2', '1' },
// ...
};
Map const* pos = std::find_if( std::begin( map ), std::end( map ),
[]( char ch ) { return c == ch; } );
return pos == std::end( map )
? c
: pos->to;
This has the advantage of making the exact mapping visible.
Or if you're 100% sure you'll never have to worry about threads:
struct Map
{
char from;
char to;
};
static Map map[] =
{
{ '1', '~' },
{ '2', '1' },
// ...
{ 0, 0 }
};
Map* sentinal = std::end( map ) - 1;
sentinal->to = sentinal->from = c;
Map const* pos = std::find_if( std::begin( map ), std::end( map ),
[]( Map const& entry ) { return c == entry.from; } );
return pos->to;
By inserting the sentinal, you can be sure that the entry will
be found.
Or you could sort the map, and use std::lower_bound.
Also, why do you call the function mapToWertyu, when it maps
to querty?
What's the best way to expand
${MyPath}/filename.txt to /home/user/filename.txt
or
%MyPath%/filename.txt to c:\Documents and settings\user\filename.txt
with out traversing the path string looking for environement variables directly?
I see that wxWidgets has a wxExpandEnvVars function. I can't use wxWidgets in this case, so I was hoping to find a boost::filesystem equivalent or similar. I am only using the home directory as an example, I am looking for general purpose path expansion.
For UNIX (or at least POSIX) systems, have a look at wordexp:
#include <iostream>
#include <wordexp.h>
using namespace std;
int main() {
wordexp_t p;
char** w;
wordexp( "$HOME/bin", &p, 0 );
w = p.we_wordv;
for (size_t i=0; i<p.we_wordc;i++ ) cout << w[i] << endl;
wordfree( &p );
return 0;
}
It seems it will even do glob-like expansions (which may or may not be useful for your particular situation).
On Windows, you can use ExpandEnvironmentStrings. Not sure about a Unix equivalent yet.
If you have the luxury of using C++11, then regular expressions are quite handy. I wrote a version for updating in place and a declarative version.
#include <string>
#include <regex>
// Update the input string.
void autoExpandEnvironmentVariables( std::string & text ) {
static std::regex env( "\\$\\{([^}]+)\\}" );
std::smatch match;
while ( std::regex_search( text, match, env ) ) {
const char * s = getenv( match[1].str().c_str() );
const std::string var( s == NULL ? "" : s );
text.replace( match[0].first, match[0].second, var );
}
}
// Leave input alone and return new string.
std::string expandEnvironmentVariables( const std::string & input ) {
std::string text = input;
autoExpandEnvironmentVariables( text );
return text;
}
An advantage of this approach is that it can be adapted easily to cope with syntactic variations and deal with wide strings too. (Compiled and tested using Clang on OS X with the flag -std=c++0x)
Simple and portable:
#include <cstdlib>
#include <string>
static std::string expand_environment_variables( const std::string &s ) {
if( s.find( "${" ) == std::string::npos ) return s;
std::string pre = s.substr( 0, s.find( "${" ) );
std::string post = s.substr( s.find( "${" ) + 2 );
if( post.find( '}' ) == std::string::npos ) return s;
std::string variable = post.substr( 0, post.find( '}' ) );
std::string value = "";
post = post.substr( post.find( '}' ) + 1 );
const char *v = getenv( variable.c_str() );
if( v != NULL ) value = std::string( v );
return expand_environment_variables( pre + value + post );
}
expand_environment_variables( "${HOME}/.myconfigfile" ); yields /home/joe/.myconfigfile
As the question is tagged "wxWidgets", you can use wxExpandEnvVars() function used by wxConfig for its environment variable expansion. The function itself is unfortunately not documented but it basically does what you think it should and expands any occurrences of $VAR, $(VAR) or ${VAR} on all platforms and also of %VAR% under Windows only.
Within the C/C++ language, here is what I do to resolve environmental variables under Unix. The fs_parm pointer would contain the filespec (or text) of possible environmental variables to be expanded. The space that wrkSpc points to must be MAX_PATH+60 chars long. The double quotes in the echo string are to prevent the wild cards from being processed. Most default shells should be able to handle this.
FILE *fp1;
sprintf(wrkSpc, "echo \"%s\" 2>/dev/null", fs_parm);
if ((fp1 = popen(wrkSpc, "r")) == NULL || /* do echo cmd */
fgets(wrkSpc, MAX_NAME, fp1) == NULL)/* Get echo results */
{ /* open/get pipe failed */
pclose(fp1); /* close pipe */
return (P_ERROR); /* pipe function failed */
}
pclose(fp1); /* close pipe */
wrkSpc[strlen(wrkSpc)-1] = '\0';/* remove newline */
For MS Windows, use the ExpandEnvironmentStrings() function.
This is what I use:
const unsigned short expandEnvVars(std::string& original)
{
const boost::regex envscan("%([0-9A-Za-z\\/]*)%");
const boost::sregex_iterator end;
typedef std::list<std::tuple<const std::string,const std::string>> t2StrLst;
t2StrLst replacements;
for (boost::sregex_iterator rit(original.begin(), original.end(), envscan); rit != end; ++rit)
replacements.push_back(std::make_pair((*rit)[0],(*rit)[1]));
unsigned short cnt = 0;
for (t2StrLst::const_iterator lit = replacements.begin(); lit != replacements.end(); ++lit)
{
const char* expanded = std::getenv(std::get<1>(*lit).c_str());
if (expanded == NULL)
continue;
boost::replace_all(original, std::get<0>(*lit), expanded);
cnt++;
}
return cnt;
}
Using Qt, this works for me:
#include <QString>
#include <QRegExp>
QString expand_environment_variables( QString s )
{
QString r(s);
QRegExp env_var("\\$([A-Za-z0-9_]+)");
int i;
while((i = env_var.indexIn(r)) != -1) {
QByteArray value(qgetenv(env_var.cap(1).toLatin1().data()));
if(value.size() > 0) {
r.remove(i, env_var.matchedLength());
r.insert(i, value);
} else
break;
}
return r;
}
expand_environment_variables(QString("$HOME/.myconfigfile")); yields /home/martin/.myconfigfile
(It also works with nested expansions)