Notice: Any messages purporting to come from this site telling you that your password has expired, or that you need to "verify" your details, making threats, or asking for money, are
spam. We do not email users with any such messages. If you have lost your password you can obtain a new one by using the
password reset link.
Entire forum
➜ Programming
➜ STL
➜ General message argument substitution
General message argument substitution
|
It is now over 60 days since the last post. This thread is closed.
Refresh page
Posted by
| Nick Gammon
Australia (23,046 posts) Bio
Forum Administrator |
Date
| Sat 05 Jul 2003 12:52 AM (UTC) Amended on Sun 06 Jul 2003 12:58 AM (UTC) by Nick Gammon
|
Message
| The function FixMessage below was written to solve the problem of wanting to supply arguments to messages in a language-independent fashion (eg. in some language nouns and verbs may come in different orders), so that doing something like the following would not allow for the message to be translated without needing code changes:
printf ("The %s is in the %s", "fish", "bag");
Instead, this system uses the idea of substituting by number (eg. %1 is argument 1, %2 is argument 2), and supplying the arguments as a vector of strings.
vector <string> args;
args.push_back ("fish"); // arg %1
args.push_back ("bag"); // arg %2
cout << FixMessage ("The %1 is in the %2", args) << endl;
The same argument can be re-used (eg. "%1 %1 %1" would give "fish fish fish").
You can insert a % by using %%.
You can have up to 35 replacements by using %1 to %9 and then %A to %Z (not case-sensitive).
If a replacement can't be made (eg. you use %Z and there are not 35 strings in the vector) then the replacement sequence is displayed (eg. %Z) as a warning of what you have omitted.
// disable warnings about long names
#ifdef WIN32
#pragma warning( disable : 4786)
#endif
#include <string>
#include <vector>
#include <iostream>
using namespace std;
string FixMessage (const string & msg, const vector<string> args)
{
string result;
string::size_type pos, // where we are now
percent; // where the % is
for (pos = 0;
(percent = msg.find ('%', pos)) != string::npos;
pos = percent)
{
result += msg.substr (pos, percent++ - pos); // before the %
// the % might be the last character, so just return it at the end
if (percent >= msg.size ())
return result + "%";
// get parameter (eg. %1)
char c (msg [percent++]);
// %% represents a single %
if (c == '%')
result += '%';
else
{
// convert into subscript (eg. '1' becomes 1, 'a' becomes 10, and so on).
// cast to unsigned in case it is >= 0x80
unsigned int argnumber = toupper (static_cast<unsigned char> (c));
// a becomes 10, b becomes 11 and so on
if (argnumber >= 'A' && argnumber <= 'Z')
argnumber -= 'A' - 10;
else
argnumber -= '0';
// if character in array put in substitute
if (argnumber >= 1 && argnumber <= args.size ())
result += args [argnumber - 1];
else
result += string ("%") + c; // otherwise put character back
} // end of not %%
} // end of for loop - searching for % symbols
// append rest of string after final argument
result += msg.substr (pos); // rest of string
return result;
} // end of FixMessage
int main (void)
{
vector <string> args;
args.push_back ("fish"); // arg %1
args.push_back ("bag"); // arg %2
cout << FixMessage ("The %1 is in the %2", args) << endl;
cout << FixMessage ("The %2 contains %1 and more %1", args) << endl;
cout << FixMessage ("I sometimes eat 90%% %1", args) << endl;
return 0;
} // end of main
Output
The fish is in the bag
The bag contains fish and more fish
I sometimes eat 90% fish
|
- Nick Gammon
www.gammon.com.au, www.mushclient.com | Top |
|
Posted by
| Nick Gammon
Australia (23,046 posts) Bio
Forum Administrator |
Date
| Reply #1 on Sun 06 Jul 2003 04:58 AM (UTC) |
Message
| Revamped argument substitution
After looking at the above code and trying to use it in exceptions I decided it would be nicer to make a more general version, which is presented below.
What this does is encapsulate the functionality described above into a single class (the message class) which can be instantiated on-the-fly to insert arguments into a string in a single command. This could be used, for instance, to throw an exception with arguments (eg. "error at word 'foo' on line 22").
I also thought you might want to do the arguments first, and then change the message (eg. to a different language, like German, French, Italian) but retain the same arguments. Thus there are a couple of methods provided to allow you to do that.
The code presented below (with example output) shows the generalised argument inserter.
// disable warnings about long names
#ifdef WIN32
#pragma warning( disable : 4786)
#endif
#include <string>
#include <vector>
#include <iostream>
#include <sstream>
#include <stdexcept>
using namespace std;
/*
Operations
----------
message m; // construct empty message
message m ("You see %1"); // message with no arguments yet
m << "a road" << 42; // insert arguments 1 and 2
m [9] = "a car"; // insert argument 9
m ['d'] = "some dogs"; // insert argument 'd'
string s1 = m.str (); // get message with arguments
string s2 = m [2]; // get argument 2
string s3 = m ['d']; // get argument 'd'
m.clear (); // clear all arguments
m.set_msg ("You eat %1"); // change the message
// example of doing a message on-the-fly
cout << ((message ("You see %1 and %2") << "food" << "water").str ()) << endl;
*/
class message
{
public:
// default constructor does nothing in particular
message () { };
// constructor with supplied message
message (const string & msg)
: m_msg (msg) { };
// copy constructor
message (const message & rhs)
: m_msg (rhs.m_msg), m_args (rhs.m_args) { };
// operator= (assign one message to another)
const message & operator= (const message & rhs)
{
if (this != &rhs)
{
m_msg = rhs.m_msg;
m_args.assign (rhs.m_args.begin (), rhs.m_args.end ());
}
return *this;
};
// supply string argument
message & operator << (const string & s)
{
m_args.push_back (s);
return *this;
};
// supply integer argument
message & operator << (const int & i)
{
m_args.push_back (((ostringstream&) (ostringstream() << i)).str ());
return *this;
};
// supply double argument
message & operator << (const double & f)
{
m_args.push_back (((ostringstream&) (ostringstream() << f)).str ());
return *this;
};
// calculate message with arguments
string str() const
{
string result;
string::size_type pos, // where we are now
percent; // where the % is
for (pos = 0;
(percent = m_msg.find ('%', pos)) != string::npos;
pos = percent)
{
result += m_msg.substr (pos, percent++ - pos); // before the %
// the % might be the last character, so just return it at the end
if (percent >= m_msg.size ())
return result + "%";
// get parameter (eg. %1)
char c (m_msg [percent++]);
// %% represents a single %
if (c == '%')
result += '%';
else
{
vector<string>::size_type argnumber = get_subscript (c);
// if character in array put in substitute
if (argnumber >= 1 &&
argnumber <= MAX_ARG &&
argnumber <= m_args.size ())
result += m_args [argnumber - 1];
else
result += string ("%") + c; // otherwise put character back
} // end of not %%
} // end of for loop - searching for % symbols
// append rest of string after final argument
result += m_msg.substr (pos); // rest of string
return result;
};
// set new message (eg. in a different language)
void set_msg (const string & msg)
{
m_msg = msg;
};
// set an argument by position
void set_arg (const int iNum, const string & arg)
{
if (iNum >= 1 && iNum <= MAX_ARG)
{
// ensure vector large enough
if (static_cast<int> (m_args.size ()) < iNum)
m_args.resize (iNum);
m_args [iNum - 1] = arg; // set argument
}
};
// set an argument by position number
void set_arg (const char c, const string & arg)
{
set_arg (get_subscript (c), arg);
};
// get reference to argument by position
// eg. msg [5] = "newvalue";
string & operator[] (int iNum)
{
// force argument to be in range - if not, use maximum
if (iNum < 1 || iNum > MAX_ARG)
iNum = MAX_ARG;
// ensure vector large enough
if (static_cast<int> (m_args.size ()) < iNum)
m_args.resize (iNum);
return m_args [--iNum]; // get reference to argument
};
// get reference to argument by position letter
// eg. msg ['b'] = "newvalue";
string & operator[] (const char c)
{
return (*this) [get_subscript (c)];
};
// clear all arguments
void clear (void) { m_args.clear (); };
// get an argument by position number
string get_arg (const int iNum) const
{
if (iNum >= 1 && iNum <= static_cast<int> (m_args.size ()))
return m_args [iNum - 1]; // get argument
else
return "";
};
// get an argument by position letter
string get_arg (const char c) const
{
return get_arg (get_subscript (c));
};
// get an argument by position number
string operator[] (const int iNum) const
{
return get_arg (iNum);
};
// get an argument by position letter
string operator[] (const char c) const
{
return get_arg (get_subscript (c));
};
protected:
// member variables
string m_msg; // the message with %1 etc. in it
vector <string> m_args; // the arguments (for replacing %1, %2 etc.)
enum {MAX_ARG = 35}; // 1 to 9 and A to Z
// helper routine
// convert '1' to '9' and 'A' to 'Z' to a number: 1 to 35
int get_subscript (const char c) const
{
// convert into subscript (eg. '1' becomes 1, 'a' becomes 10, and so on).
// cast to unsigned in case it is >= 0x80
int i = toupper (static_cast<unsigned char> (c));
// a becomes 10, b becomes 11 and so on
if (i >= 'A' && i <= 'Z')
i -= 'A' - 10; // subtract -10 to add 10 to the result ;)
else
i -= '0';
return i;
}; // end of get_subscript
}; // end of class message
// call this to throw a runtime error
#define ERROR(msg, args)\
throw runtime_error ((message (msg) << args).str ());
// test
int main (void)
{
try
{
cout << "Before exception." << endl;
ERROR ("The %1 is in the %2 %3 with %4 eyes",
"fish" << "green" << "bag" << 2);
cout << "After exception." << endl;
}
catch (runtime_error & e)
{
cout << "Exception: " << e.what () << endl;
}
// construct message with no arguments yet
message x ("The %1 contains %g");
// add argument 1
x << "sack";
cout << x.str () << endl;
// change argument 1
x [1] = "bag";
cout << x.str () << endl;
// change argument 'g'
x ['g'] = "fruit";
cout << x.str () << endl;
// get argument 'g'
cout << "arg g = " << x ['g'] << endl;
// test copy constructor
message y (x);
cout << "y = " << y.str () << endl;
// test assignment
message z;
z = y;
cout << "z = " << z.str () << endl;
// use same arguments but do message in German
z.set_msg ("Der %1 enthält %g");
cout << "German = " << z.str () << endl;
// example of outputting in one line
cout << ((message ("You see %1 and %2") << "food" << "water").str ()) << endl;
return 0;
} // end of main
Output
Before exception.
Exception: The fish is in the green bag with 2 eyes
The sack contains %g
The bag contains %g
The bag contains fruit
arg g = fruit
y = The bag contains fruit
z = The bag contains fruit
German = Der bag enthõlt fruit
You see food and water
|
- Nick Gammon
www.gammon.com.au, www.mushclient.com | Top |
|
The dates and times for posts above are shown in Universal Co-ordinated Time (UTC).
To show them in your local time you can join the forum, and then set the 'time correction' field in your profile to the number of hours difference between your location and UTC time.
10,266 views.
It is now over 60 days since the last post. This thread is closed.
Refresh page
top