2.6. Dictionary Filters
一个dictionary Filter是实现文本替换的Filter。他维护有一个许多对字符串的集合,字符串对中的第一个字符串是单词,第二个字符串则代表用来替换的文本(我把这些字符串对的集合成为“dictionary”,并称这些字符串对为“definition”)。当一个dictionary filter处理一个出现在某个definition中第一部分的单词时,它会将替代文本转发出去。也就是说空格和标点符号不会被改变。
基本算法如下:你每次检查一个字符,将他们添加到一个被称作“当前单词”的字符串中。当你碰到的是一个非字母字符时,参照dictionary来判断当前单词是不是某个definition的第一部分。如果是,那么你转发替换文本,并接着将此非字母字符转发出去。否则,你将当前单词连同非字母字符一起转发出去。当到达stream的结尾时,你同样需要参照dictionary来决定是转发当前单词还是替换文本。
在一下的几节中,我将使用stdio_filter,InputFilter和OutputFilter来实现这个算法。
dictionary
你可以使用下面的类来表示一个dictionary:
#include
<
map
>
#include
<
string
>

namespace
boost
{ namespace iostreams { namespace example {

class dictionary {
public:
void add(std::string key, const std::string& value);
void replace(std::string& key);

/* ... */
};

} } }
//
End namespace boost::iostreams:example
成员函数add()将关键字转化为小写格式,然后将关键字和值一起加入dictionary中。成员函数replace()查找一个第一部分和key的小写格式相同的定义。如果找到这样的一个定义,它将替换文本赋值给key。
dictionary_stdio_filter
你可以如下所示使用一个stdio_filter实现一个dicitonary filter:
#include
<
cstdio
>
//
EOF
#include
<
iostream
>
//
cin, cout
#include
<
boost
/
iostreams
/
filter
/
stdio.hpp
>

namespace
boost
{ namespace iostreams { namespace example {

class dictionary_stdio_filter : public stdio_filter {
public:
dictionary_stdio_filter(dictionary& d) : dictionary_(d) { }
private:
void do_filter()
{
using namespace std;
while (true) {
int c = std::cin.get();
if (c == EOF || !std::isalpha((unsigned char) c)) {
dictionary_.replace(current_word_);
cout.write( current_word_.data(),
static_cast<streamsize>(current_word_.size()) );
current_word_.erase();
if (c == EOF)
break;
cout.put(c);
} else {
current_word_ += c;
}
}
}
dictionary& dictionary_;
std::string current_word_;
};

} } }
//
End namespace boost::iostreams:example
do_filter的函数体只是一个简单的循环,它从std::cin中读取字符,将他们添加到成员变量current_word_中直到读取了一个非字母字符或者到达end-of-stream。此时,如果必要的话,它会使用作为一个dictionary的成员变量dictionary_来替换但前单词。最后它会将当前单词以及可能的非字母字符写入到std::cout中。
dictionary_input_filter
你可以如下所示使用一个InputFilter实现一个dicitonary filter:
#include
<
boost
/
iostreams
/
char_traits.hpp
>
//
EOF, WOULD_BLOCK
#include
<
boost
/
iostreams
/
concepts.hpp
>
//
input_filter
#include
<
boost
/
iostreams
/
operations.hpp
>
//
get
namespace
boost
{ namespace iostreams { namespace example {

class dictionary_input_filter : public input_filter {
public:
dictionary_input_filter(dictionary& d)
: dictionary_(d), off_(std::string::npos), eof_(false)
{ }

template<typename Source>
int get(Source& src);

template<typename Source>
void close(Source&);
private:
dictionary& dictionary_;
std::string current_word_;
std::string::size_type off_;
bool eof_;
};

} } }
//
End namespace boost::iostreams:example
函数get()如下所示:
template
<
typename Source
>
int
get
(Source
&
src)
{
// Handle unfinished business.
if (off_ != std::string::npos && off_ < current_word_.size())
return current_word_[off_++];
if (off_ == current_word_.size()) {
current_word_.erase();
off_ = std::string::npos;
}
if (eof_)
return EOF;

// Compute curent word.
while (true) {
int c;
if ((c = iostreams::get(src)) == WOULD_BLOCK)
return WOULD_BLOCK;

if (c == EOF || !std::isalpha((unsigned char) c)) {
dictionary_.replace(current_word_);
off_ = 0;
if (c == EOF)
eof_ = true;
else
current_word_ += c;
break;
} else {
current_word_ += c;
}
}

return this->get(src); // Note: current_word_ is not empty.
}
你首先检查是否存在在先前的get()调用中没有处理的字符。如果有,就返回第一个字符。
while循环体和dictionary_stdio_filter::do_filter类似:它从Source src中读取字符,将他们添加到current_word中,直到读取的是一个非字母字符,EOF或者WOULD_BLOCK。
最后,get()被递归调用并返回当前单词的第一个字符。
函数close()仍然是重新设置Filter的状态:
template
<
typename Source
>
void
close(Source
&
)
{
current_word_.erase();
off_ = std::string::npos;
eof_ = false;
}
dictionary_output_filter
你可以如下所示使用一个OutputFilter实现一个dicitonary filter:
#include
<
algorithm
>
//
swap
#include
<
boost
/
iostreams
/
concepts.hpp
>
//
output_filter
#include
<
boost
/
iostreams
/
operations.hpp
>
//
write
namespace
boost
{ namespace iostreams { namespace example {

class dictionary_output_filter : public output_filter {
public:
typedef std::map<std::string, std::string> map_type;
dictionary_output_filter(dictionary& d)
: dictionary_(d), off_(std::string::npos)
{ }

template<typename Sink>
bool put(Sink& dest, int c);

template<typename Sink>
void close(Sink& dest);
private:
template<typename Sink>
bool write_current_word(Sink& dest);
dictionary& dictionary_;
std::string current_word_;
std::string::size_type off_;
bool initialized_;
};

} } }
//
End namespace boost::iostreams:example
首先让我们看看辅助函数write_current_word:
template
<
typename Sink
>
bool
write_current_word(Sink
&
dest)
{
using namespace std;
streamsize amt = static_cast<streamsize>(current_word_.size() - off_);
streamsize result =
iostreams::write(dest, current_word_.data() + off_, amt);
if (result == amt) {
current_word_.erase();
off_ = string::npos;
return true;
} else {
off_ += result;
return false;
}
}
这个函数尝试将current_word_中从off_处开始的字符写入到直到的Sink中。如果所有字符序列写入成功,current_word_就会被清除,并且返回true。否则成员变量off_会被更新到第一个未写的字符位置并返回false。
你可以如下所示使用write_current_word来实现put()函数:
template
<
typename Sink
>
bool
put(Sink
&
dest,
int
c)
{
if (off_ != std::string::npos && !write_current_word(dest))
return false;
if (!std::isalpha((unsigned char) c)) {
dictionary_.replace(current_word_);
off_ = 0;
}

current_word_ += c;
return true;
}
和dictionary_input_filter::get()函数类似,你首先检查是否有在先前put()调用没有处理完的字符,如果有,就尝试通过write_current_word写入这些字符。如果成功,就继续检测指定字符c。如果他是一个非字母字符,参考dictionary来决定合适的替换文本,将c添加到current_word_后面并返回true。
函数close除了重新设置Filter的状态外,还需要做些其他事情。除非没有过滤前的字符序列最后一个字符是非字母字符,否则current_word_中的内容不会被写入:
template
<
typename Sink
>
void
close(Sink
&
dest)
{
// Reset current_word_ and off_, saving old values.
std::string current_word;
std::string::size_type off = 0;
current_word.swap(current_word_);
std::swap(off, off_);

// Write remaining characters to dest.
if (off == std::string::npos) {
dictionary_.replace(current_word);
off = 0;
}
if (!current_word.empty())
iostreams::write(
dest,
current_word.data() + off,
static_cast<std::streamsize>(current_word.size() - off)
);
}
要注意的是,你使用的模板参数可能是一个阻塞型的Sink,所以在写入抛出一个异常后,你必须在再次调用write前重新设置current_word_和off_。
2.7. UNIX-to-DOS Filters
假定你准备使用一个Filter将UNIX的行结束格式转换为DOS的行结束格式。基本的想法很简单:你一次处理一个字符,当你遇到字符'/n'时将其替换为两个字符'/r','/n'。在下面几节中,我将使用stdio_filter,InputFilter和OutputFilter来实现这个算法。
unix2dos_stdio_filter
你可以如下所示,通过从stdio_filter派生类并重载虚函数do_filter来实现一个UNIX_to_DOS Filter:
#include
<
cstdio
>
//
EOF
#include
<
iostream
>
//
cin, cout
#include
<
boost
/
iostreams
/
filter
/
stdio.hpp
>

namespace
boost
{ namespace iostreams { namespace example {

class unix2dos_stdio_filter : public stdio_filter {
private:
void do_filter()
{
int c;
while ((c = std::cin.get()) != EOF) {
if (c == ' ')
std::cout.put(' ');
std::cout.put(c);
}
}
};

} } }
//
End namespace boost::iostreams:example
函数do_filter包含上述算法的一个简单的实现:它从标准输入中读取字符,除非读取的是'/n',此时写入'/r','/n',否则并将他们原封不动写入标准输出。
unix2dos_input_filter
现在,让我们使用一个InputFilter来实现一个UNIX-to-DOS Filter。
#include
<
boost
/
iostreams
/
categories.hpp
>
//
input_filter_tag
#include
<
boost
/
iostreams
/
operations.hpp
>
//
get
namespace
boost
{ namespace iostreams { namespace example {

class unix2dos_input_filter {
public:
typedef char char_type;
typedef input_filter_tag category;

unix2dos_input_filter() : has_linefeed_(false) { }

template<typename Source>
int get(Source& src)
{
// Handle unfinished business
if (has_linefeed_) {
has_linefeed_ = false;
return ' ';
}

// Forward all characters except ' '
int c;
if ((c = iostreams::get(src)) == ' ') {
has_linefeed_ = true;
return ' ';
}

return c;
}

template<typename Source>
void close(Source&);
private:
bool has_linefeed_;
};

} } }
//
End namespace boost::iostreams:example
对函数get说明如下:大多数情况下,你只需简单地从src中读取一个字符并返回它。特殊值EOF和WOULD_BLOCK同样处理。例外情况是在iostream::get返回'/n',此时你返回'/r'并在下次调用get时返回'/n'。
成员函数close依然是重新设置Filter的状态:
template
<
typename Source
>
void
close(Source
&
)
{ skip_ = false; }
unix2dos_output_filter
你可以如下所示使用一个OutputFilter来实现UNIX-to-DOS Filter:
#include
<
boost
/
iostreams
/
concepts.hpp
>
//
output_filter
#include
<
boost
/
iostreams
/
operations.hpp
>
//
put
namespace
boost
{ namespace iostreams { namespace example {

class unix2dos_output_filter : public output_filter {
public:
unix2dos_output_filter() : has_linefeed_(false) { }

template<typename Sink>
bool put(Sink& dest, int c);

template<typename Sink>
void close(Sink&) { has_linefeed_ = false; }
private:
template<typename Sink>
bool put_char(Sink& dest, int c);

bool has_linefeed_;
};

} } }
//
End namespace boost::iostreams:example
在这个例子中,我从辅助类output_filter派生一个类,这个辅助类提供了一个等价于char的成员类型char_type以及可以转换为output_filter_tag和to_closable_tag的类别标志。
首先让我们看看辅助函数put_char:
template
<
typename Sink
>
bool
put_char(Sink
&
dest,
int
c)
{
bool result;
if ((result = iostreams::put(dest, c)) == true) {
has_linefeed_ =
c == ' ' ?
true :
c == ' ' ?
false :
has_linefeed_;
}
return result;
}
这个函数尝试着向Sink dest中写入一个单字符,并在成功的时候返回true。如果成功,它更新标志has_linefeed_,这个标志是用来指示在写入dos行结束符序列第一个字符后失败。
你可以如下所示使用put_char来实现put:
bool
put(Sink
&
dest,
int
c)
{
if (c == ' ')
return has_linefeed_ ?
put_char(dest, ' ') :
put_char(dest, ' ') ?
this->put(dest, ' ') :
false;
return iostreams::put(dest, c);
}
这个函数工作过程如下:
1. 如果你正准备处理一个DOS 行结束符序列,也就是c是'/n'并且has_line_feed_为false,你尝试着想dest写入'/r'然后写入'/n'。
2. 如果你在处理DOS行结束符过程中,也即是c是'/n',并且has_line_feed为true,你尝试着通过写入'/n'来完成操作。
3、否则,你尝试着将c写入dest。
这里有两个小地方需要注意:第一,为什么c=='/n'并且has_line_feed_为ture意味着你正在处理Dos line_ending序列过程中?这是由于在你尝试写入'/r','/n'过程中只有第一个字符写入成功,于是你将vhas_line_feed_置为ture并返回false。第二,在写入line_ending序列中的第二个字符时,你递归调用put而不是put_char。
将unix2dos_input_filter和unix2dos_output_filter进行比较,你会发现这种情况下,使用InputFilter来实现算法更加容易。如果你想避免使用以上定义所导致的复杂性,你可以使用模板inverse从一个unix2dos_input_filter中构造出一个vOutputFilter 。
#include
<
boost
/
iostreams
/
invert.hpp
>
//
inverse
namespace
io
=
boost::iostreams;
namespace
ex
=
boost::iostreams::example;

typedef io::inverse
<
ex::unix2dos_input_filter
>
unix2dos_output_filter;
其实我们还有更简洁的方法,例如我们可以通过内置的newline_filter来处理line_ending转换。
一个dictionary Filter是实现文本替换的Filter。他维护有一个许多对字符串的集合,字符串对中的第一个字符串是单词,第二个字符串则代表用来替换的文本(我把这些字符串对的集合成为“dictionary”,并称这些字符串对为“definition”)。当一个dictionary filter处理一个出现在某个definition中第一部分的单词时,它会将替代文本转发出去。也就是说空格和标点符号不会被改变。
基本算法如下:你每次检查一个字符,将他们添加到一个被称作“当前单词”的字符串中。当你碰到的是一个非字母字符时,参照dictionary来判断当前单词是不是某个definition的第一部分。如果是,那么你转发替换文本,并接着将此非字母字符转发出去。否则,你将当前单词连同非字母字符一起转发出去。当到达stream的结尾时,你同样需要参照dictionary来决定是转发当前单词还是替换文本。
在一下的几节中,我将使用stdio_filter,InputFilter和OutputFilter来实现这个算法。
dictionary
你可以使用下面的类来表示一个dictionary:















成员函数add()将关键字转化为小写格式,然后将关键字和值一起加入dictionary中。成员函数replace()查找一个第一部分和key的小写格式相同的定义。如果找到这样的一个定义,它将替换文本赋值给key。
dictionary_stdio_filter
你可以如下所示使用一个stdio_filter实现一个dicitonary filter:

































do_filter的函数体只是一个简单的循环,它从std::cin中读取字符,将他们添加到成员变量current_word_中直到读取了一个非字母字符或者到达end-of-stream。此时,如果必要的话,它会使用作为一个dictionary的成员变量dictionary_来替换但前单词。最后它会将当前单词以及可能的非字母字符写入到std::cout中。
dictionary_input_filter
你可以如下所示使用一个InputFilter实现一个dicitonary filter:

























函数get()如下所示:



































你首先检查是否存在在先前的get()调用中没有处理的字符。如果有,就返回第一个字符。
while循环体和dictionary_stdio_filter::do_filter类似:它从Source src中读取字符,将他们添加到current_word中,直到读取的是一个非字母字符,EOF或者WOULD_BLOCK。
最后,get()被递归调用并返回当前单词的第一个字符。
函数close()仍然是重新设置Filter的状态:







dictionary_output_filter
你可以如下所示使用一个OutputFilter实现一个dicitonary filter:




























首先让我们看看辅助函数write_current_word:
















这个函数尝试将current_word_中从off_处开始的字符写入到直到的Sink中。如果所有字符序列写入成功,current_word_就会被清除,并且返回true。否则成员变量off_会被更新到第一个未写的字符位置并返回false。
你可以如下所示使用write_current_word来实现put()函数:













和dictionary_input_filter::get()函数类似,你首先检查是否有在先前put()调用没有处理完的字符,如果有,就尝试通过write_current_word写入这些字符。如果成功,就继续检测指定字符c。如果他是一个非字母字符,参考dictionary来决定合适的替换文本,将c添加到current_word_后面并返回true。
函数close除了重新设置Filter的状态外,还需要做些其他事情。除非没有过滤前的字符序列最后一个字符是非字母字符,否则current_word_中的内容不会被写入:





















要注意的是,你使用的模板参数可能是一个阻塞型的Sink,所以在写入抛出一个异常后,你必须在再次调用write前重新设置current_word_和off_。
2.7. UNIX-to-DOS Filters
假定你准备使用一个Filter将UNIX的行结束格式转换为DOS的行结束格式。基本的想法很简单:你一次处理一个字符,当你遇到字符'/n'时将其替换为两个字符'/r','/n'。在下面几节中,我将使用stdio_filter,InputFilter和OutputFilter来实现这个算法。
unix2dos_stdio_filter
你可以如下所示,通过从stdio_filter派生类并重载虚函数do_filter来实现一个UNIX_to_DOS Filter:




















函数do_filter包含上述算法的一个简单的实现:它从标准输入中读取字符,除非读取的是'/n',此时写入'/r','/n',否则并将他们原封不动写入标准输出。
unix2dos_input_filter
现在,让我们使用一个InputFilter来实现一个UNIX-to-DOS Filter。






































对函数get说明如下:大多数情况下,你只需简单地从src中读取一个字符并返回它。特殊值EOF和WOULD_BLOCK同样处理。例外情况是在iostream::get返回'/n',此时你返回'/r'并在下次调用get时返回'/n'。
成员函数close依然是重新设置Filter的状态:



unix2dos_output_filter
你可以如下所示使用一个OutputFilter来实现UNIX-to-DOS Filter:






















在这个例子中,我从辅助类output_filter派生一个类,这个辅助类提供了一个等价于char的成员类型char_type以及可以转换为output_filter_tag和to_closable_tag的类别标志。
首先让我们看看辅助函数put_char:














这个函数尝试着向Sink dest中写入一个单字符,并在成功的时候返回true。如果成功,它更新标志has_linefeed_,这个标志是用来指示在写入dos行结束符序列第一个字符后失败。
你可以如下所示使用put_char来实现put:










这个函数工作过程如下:
1. 如果你正准备处理一个DOS 行结束符序列,也就是c是'/n'并且has_line_feed_为false,你尝试着想dest写入'/r'然后写入'/n'。
2. 如果你在处理DOS行结束符过程中,也即是c是'/n',并且has_line_feed为true,你尝试着通过写入'/n'来完成操作。
3、否则,你尝试着将c写入dest。
这里有两个小地方需要注意:第一,为什么c=='/n'并且has_line_feed_为ture意味着你正在处理Dos line_ending序列过程中?这是由于在你尝试写入'/r','/n'过程中只有第一个字符写入成功,于是你将vhas_line_feed_置为ture并返回false。第二,在写入line_ending序列中的第二个字符时,你递归调用put而不是put_char。
将unix2dos_input_filter和unix2dos_output_filter进行比较,你会发现这种情况下,使用InputFilter来实现算法更加容易。如果你想避免使用以上定义所导致的复杂性,你可以使用模板inverse从一个unix2dos_input_filter中构造出一个vOutputFilter 。






其实我们还有更简洁的方法,例如我们可以通过内置的newline_filter来处理line_ending转换。