2.4. Line-Wrapping Filters
假定你打算使用一个filter来限制一行文本从而确保其长度不超过某个最大值。为了使这个问题简化一下,我们不考虑单词界限和插入连字符的情况。基本算法如下所示:你每次检查一个字符,原封不动地转发他们,并记录当前行字符个数。当你遇到一个换行符时,转发他们,并将行字符个数清零。当行字符个数达到最大值时,你在当前字符前插入一个换行符,并将行字符个数清零。
在以下3节中,我将使用stdio_filter,InputFilter和OutputFilter来描述这个算法,
line_wrapping_stdio_filter
你可以如下例所示使用一个stdio_filter来描述一个line_wrapping Filter:
#include
<
cstdio
>
//
EOF
#include
<
iostream
>
//
cin, cout
#include
<
boost
/
iostreams
/
filter
/
stdio.hpp
>


namespace
boost
...
{ namespace iostreams ...{ namespace example ...{


class line_wrapping_stdio_filter : public stdio_filter ...{
public:
explicit line_wrapping_stdio_filter(int line_length = 80)
: line_length_(line_length), col_no_(0)

...{ }
private:
void do_filter();
void do_close();
void put_char(int c);
int line_length_;
int col_no_;
};

} } }
//
End namespace boost::iostreams:example
首先让我们看一下辅助函数put_char的定义:
void
put_char(
int
c)

...
{
std::cout.put(c);
if (c != ' ')
++col_no_;
else
col_no_ = 0;
}
此函数将给定字符写入到std::cout,如果此字符为换行符,那么就将行字符个数清零,否则就将行字符个数加一。如下例所示,你可以使用put_char来实现虚函数do_filter。
void
do_filter()

...
{
int c;

while ((c = std::cin.get()) != EOF) ...{
if (c != ' ' && col_no_ >= line_length_)
put_char(' ');
put_char(c);
}
}
while循环简单地从std::cin中读取一个字符并将此字符写入到std::cout中,为了不使行字符个数超出行长度,循环体会在需要时添加一个换行符。
最后,成员函数do_close重载stdio_filter中声明的虚函数。
void do_close() { col_no_ = 0; }
它所起到的作用是在关闭一个stream时,将Filter的状态重新复位。
line_wrapping_input_filter
你可以如下例所示使用一个Input_Filter来描述一个line_wrapping 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 line_wrapping_input_filter : public input_filter ...{
public:
explicit line_wrapping_input_filter(int line_length = 80)
: line_length_(line_length), col_no_(0), has_next_(false)

...{ }

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

template<typename Sink>
void close(Sink&);
private:
int get_char(int c);
int line_length_;
int col_no_;
int next_;
int has_next_;
};

} } }
//
End namespace boost::iostreams:example
首先让我们先看看辅助函数 get_char:
int
get_char(
int
c)

...
{
if (c != ' ')
++col_no_;
else
col_no_ = 0;
return c;
}
这个函数根据指定字符c更新行字符个数,然后返回c。 你可以如下例所示使用get_char()来实现get():
template
<
typename Source
>
int
get
(Source
&
src)

...
{

if (has_next_) ...{
has_next_ = false;
return get_char(next_);
}

int c;
if ((c = iostreams::get(src)) == EOF || c == WOULD_BLOCK)
return c;


if (c != ' ' && col_no_ >= line_length_) ...{
next_ = c;
has_next_ = true;
return get_char(' ');
}

return get_char(c);
}

一个不是MultiCharFilter的InputFilter一次只能返回一个字符。因此,从src中读取一个字符c后,如果你希望在字符c前插入一个换行符,你必须存储c并在下次调用get的时候返回c。成员变量next_是用来存储这个字符;成员变量has_next_表明是否存储了一个字符。
get函数体首先检查是否有被存储的字符,如果有的话,便返回这个被存储的字符。否则,它就会试图从src中读取一个字符。如果没有读取到字符,它返回EOF或WOULD_BLOCK等特殊值,它还会检查是否需要插入一个换行符。如果需要,它就将当前字符存储起来并返回换行符。否则,它就返回当前字符。
最后,成员函数close重新复位Filter的状态值:
template
<
typename Sink
>
void
close(Sink
&
)

...
{
col_no_ = 0;
has_next_ = false;
}
line_wrapping_output_filter
你可以如下例所示使用OutputFilter来描述一个line_wrapping Filter:
#include
<
boost
/
iostreams
/
concepts.hpp
>
//
output_filter
#include
<
boost
/
iostreams
/
operations.hpp
>
//
put

namespace
boost
...
{ namespace iostreams ...{ namespace example ...{


class line_wrapping_output_filter : public output_filter ...{
public:
explicit line_wrapping_output_filter(int line_length = 80)
: line_length_(line_length), col_no_(0)

...{ }

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

template<typename Sink>
void close(Sink&);
private:
template<typename Sink>
bool put_char(Sink& dest, int c);
int line_length_;
int col_no_;
};

} } }
//
End namespace boost::iostreams:example
首先让我们看一下辅助函数put_char:
template
<
typename Sink
>
bool
put_char(Sink
&
dest,
int
c)

...
{
if (!iostreams::put(dest, c))
return false;
if (c != ' ')
++col_no_;
else
col_no_ = 0;
return true;
}
这个函数尝试着向指定的Sink中写入字符c,并在成功后更新行字符个数。你可以如下例所示使用put_char实现函数put:
template
<
typename Sink
>
bool
put(Sink
&
dest,
int
c)

...
{
if (c != ' ' && col_no_ >= line_length_ && !put_char(dest, ' '))
return false;
return put_char(dest, c);
}

这个函数首先根据指定字符及行字符个数来判断是否需要插入换行符。如果需要,它尝试使用put_char写入一个换行符,写入失败便会返回false。否则它尝试使用put_char写入指定字符。值得注意的是,如果成功插入了一个换行符但又没有成功写入指定字符,那么行字符个数会被更新来反映已经插入了换行符,以便下次写入指定字符是不会再次插入一个换行符。
最后,成员函数close将Filter的状态重新复位:
template
<
typename Sink
>

void
close(Sink
&
)
...
{ col_no_ = 0; }
2.5. Tab-Expanding Filters
假定你准备使用一个filter将tab字符替换成一个或多个空格字符,从而使文档显示效果与原来的一致。基本算法如下:你每次检查一个字符, 原封不动地转发他们并记录下行字符个数。当你遇到一个tab字符时,将其替换成空格序列,序列的长度取决于当前行字符个数。当遇到一个换行符时,转发它,并将行字符个数清零。
在接下来的3节中,我将使用StdioFilter,InputFilter和OutputFilter来实现这个算法。
tab_expanding_stdio_filter
你可以如下例所示使用stdio_filter来实现这个算法:
#include
<
cstdio
>
//
EOF
#include
<
iostream
>
//
cin, cout
#include
<
boost
/
iostreams
/
filter
/
stdio.hpp
>


class
tab_expanding_stdio_filter :
public
stdio_filter
...
{
public:
explicit tab_expanding_stdio_filter(int tab_size = 8)
: tab_size_(tab_size), col_no_(0)

...{
assert(tab_size > 0);
}
private:
void do_filter();
void do_close();
void put_char(int c);
int tab_size_;
int col_no_;
}
;

} } }
//
End namespace boost::iostreams:example
辅助函数put_char和line_wrapping_stdio_filter中put_char函数是一样的。它向std::cout中写入一个字符并更新行字符数:
void
put_char(
int
c)

...
{
std::cout.put(c);

if (c == ' ') ...{
col_no_ = 0;

} else ...{
++col_no_;
}
}
你可以如下例所示使用put_char来实现do_filter:
void
do_filter()

...
{
int c;

while ((c = std::cin.get()) != EOF) ...{

if (c == ' ') ...{
int spaces = tab_size_ - (col_no_ % tab_size_);
for (; spaces > 0; --spaces)
put_char(' ');

} else ...{
put_char(c);
}
}
}
while循环体从std::cin中读取一个字符,并将其写入到std::cout,如果读取的是tab字符,它会写入一定数量的空格字符到std::cout中。
和line_wrapping_stdio_filter一样,虚函数do_close将filter的状态重新复位:
void
do_close()
...
{ col_no_ = 0; }
tab_expanding_input_filter
你可以如下例所示使用InputFilter来实现tab-expanding 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 tab_expanding_input_filter : public input_filter ...{
public:
explicit tab_expanding_input_filter(int tab_size = 8)
: tab_size_(tab_size), col_no_(0), spaces_(0)

...{
assert(tab_size > 0);
}

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

template<typename Source>
void close(Source&);
private:
int get_char(int c);
int tab_size_;
int col_no_;
int spaces_;
};

} } }
//
End namespace boost::iostreams:example
首先让我们先看一下辅助函数get_char():
int
get_char(
int
c)

...
{

if (c == ' ') ...{
col_no_ = 0;

} else ...{
++col_no_;
}
return c;
}
这个函数根据指定字符c更新行字符数并返回c。你可以如下例所示使用get_char()来实现get():
template
<
typename Source
>
int
get
(Source
&
src)

...
{

if (spaces_ > 0) ...{
--spaces_;
return get_char(' ');
}

int c;
if ((c = iostreams::get(src)) == EOF || c == WOULD_BLOCK)
return c;

if (c != ' ')
return get_char(c);

// Found a tab. Call this filter recursively.
spaces_ = tab_size_ - (col_no_ % tab_size_);
return this->get(src);
}

这个函数与line_wrapping_input_filter中get很相似。由于get一次只能返回一个字符,因此在将tab字符替换成一个空格字符序列时,只有第一个空格字符能够被返回。剩余空格字符会在以后的get()调用中依次被返回。 成员变量spaces_用来存储剩余空格字符数。
函数体首先检查是否有剩余的空格字符需要被返回。如果有, 它将spaces_递减并返回一个空格。否则便从src中读取一个字符。普通字符以及特殊值如EOF和WOULD_BLOCK都会被返回。当处理一个tab字符时,会记录下需要被返回的空格字符数,并返回一个空格。
函数close依然是将Filter的状态重新复位:
void
close(Source
&
)

...
{
col_no_ = 0;
spaces_ = 0;
}
tab_expanding_output_filter
你可以如下所示使用OutputFIlter来实现一个tab_expanding Filter:
#include
<
boost
/
iostreams
/
concepts.hpp
>
//
output_filter
#include
<
boost
/
iostreams
/
operations.hpp
>
//
put

namespace
boost
...
{ namespace iostreams ...{ namespace example ...{


class tab_expanding_output_filter : public output_filter ...{
public:
explicit tab_expanding_output_filter(int tab_size = 8)
: tab_size_(tab_size), col_no_(0), spaces_(0)

...{
assert(tab_size > 0);
}

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

template<typename Sink>
void close(Sink&);
private:
template<typename Sink>
bool put_char(Sink& dest, int c);
int tab_size_;
int col_no_;
int spaces_;
};

} } }
//
End namespace boost::iostreams:example
辅助函数put_char的实现和line_wrapping_output_filter::put_char一样:它将指定的字符写入到std::cout中,如果指定字符是换行符,它会将行文本字符数清零,否则将行字符数递增。
template
<
typename Sink
>
bool
put_char(Sink
&
dest,
int
c)

...
{
if (!iostreams::put(dest, c))
return false;
if (c != ' ')
++col_no_;
else
col_no_ = 0;
return true;
}
你可以如下所示使用put_char来实现put函数:
template
<
typename Sink
>
bool
put(Sink
&
dest,
int
c)

...
{
for (; spaces_ > 0; --spaces_)
if (!put_char(dest, ' '))
return false;


if (c == ' ') ...{
spaces_ = tab_size_ - (col_no_ % tab_size_) - 1;
return this->put(dest, ' ');
}

return put_char(dest, c);
}

函数首先尝试着写入先前处理tab字符时剩下的空格字符。如果成功,它检查指定字符,如果不是tab字符,它尝试写入dest中,否则,它就计算出需要插入的空格字符数,并递归调用自己。使用递归可以使我们避免在代码中的两个地方递减变量space_。
注意,处理tab字符时,除非相关的空格字符都被写入,否则会返回false。
函数close依然是将Filter的状态重新复位:
void
close(Source
&
)

...
{
col_no_ = 0;
spaces_ = 0;
}
待续......