size();
Container::iterator rmit = removals.begin();
while(rmit != removals.end())
names.remove(*rmit++); // Removes all matches
ofstream out(argv[3]);
assure(out, argv[3]);
copy(names.begin(), names.end(),
ostream_iterator<string>(out,"\n"));
long removed = original - names.size();
cout << "On removal list: " << removals.size()
<< "\n Removed: " << removed << endl;
} ///:~
Here, a list is used instead of a vector (since readLower( ) is a template, it adapts). Although there is a remove( ) algorithm that can be applied to containers, the built-in list::remove( ) seems to work better. The second command-line argument is the file containing the list of names to be removed. An iterator is used to step through that list, and the list::remove( ) function removes every instance of each name from the master list. Here, the list doesn’t need to be sorted first.
Unfortunately, that’s not all there is to it. The messiest part about maintaining a mailing list is the bounced messages. Presumably, you’ll just want to remove the addresses that produce
bounces. If you can combine all the bounced messages into a single file, the following
program has a pretty good chance of extracting the email addresses; then you can use
RemoveGroup to delete them from your list.
//: C26:ExtractUndeliverable.cpp
// Find undeliverable names to remove from
// mailing list from within a mail file
// containing many messages
#include "../require.h"
#include <cstdio>
#include <string>
#include <set>
Appendix A: Et cetera
1026
using namespace std;
char* start_str[] = {
"following address",
"following recipient",
"following destination",
"undeliverable to the following",
"following invalid",
};
char* continue_str[] = {
"Message-ID",
"Please reply to",
};
// The in() function allows you to check whether
// a string in this set is part of your argument.
class StringSet {
char** ss;
int sz;
public:
StringSet(char** sa, int sza):ss(sa),sz(sza) {}
bool in(char* s) {
for(int i = 0; i < sz; i++)
if (strstr(s, ss[i]) != 0)
return true;
return false;
}
};
// Calculate array length:
#define ALEN(A) ((sizeof A)/(sizeof *A))
StringSet
starts(start_str, ALEN(start_str)),
continues(continue_str, ALEN(continue_str));
int main(int argc, char* argv[]) {
requireArgs(argc, 2,
"Usage:ExtractUndeliverable infile outfile");
FILE* infile = fopen(argv[1], "rb");
FILE* outfile = fopen(argv[2], "w");
require(infile != 0); require(outfile != 0);
Appendix A: Et cetera
1027
set<string> names;
const int sz = 1024;
char buf[sz];
while(fgets(buf, sz, infile) != 0) {
if(starts.in(buf)) {
puts(buf);
while(fgets(buf, sz, infile) != 0) {
if(continues.in(buf)) continue;
if(strstr(buf, "---") != 0) break;
const char* delimiters= " \t<>():;,\n\"";
char* name = strtok(buf, delimiters);
while(name != 0) {
if(strstr(name, "@") != 0)
names.insert(string(name));
name = strtok(0, delimiters);
}
}
}
}
set<string>::iterator i = names.begin();
while(i != names.end())
fprintf(outfile, "%s\n", (*i++).c_str());
} ///:~
The first thing you’ll notice about this program is that contains some C functions, including C
I/O. This is not because of any particular design insight. It just seemed to work when I used the C elements, and it started behaving strangely with C++ I/O. So the C is just because it
works, and you may be able to rewrite the program in more “pure C++” using your C++
compiler and produce correct results.
A lot of what this program does is read lines looking for string matches. To make this
convenient, I created a StringSet class with a member function in( ) that tells you whether any of the strings in the set are in the argument. The StringSet is initialized with a constant two-dimensional of strings and the size of that array. Although the StringSet makes the code easier to read, it’s also easy to add new strings to the arrays.