教程

2.2.4. 换行过滤器

假设你想编写一个过滤器,将文本行换行,以确保没有一行超过某个最大长度。为简单起见,我们不要费心在词语边界换行或插入连字符。基本算法如下:你逐个检查字符,按原样转发,并跟踪当前列号。当你遇到换行符时,你转发它并重置列计数。当列计数达到最大值时,你在当前字符之前插入一个换行符,并重置列计数。

在接下来的三个部分中,我将把此算法表达为一个 stdio_filter、一个 InputFilter 和一个 OutputFilter。源代码可以在 <libs/iostreams/example/line_wrapping_filter.hpp> 头文件中找到。这些示例受到 James Kanze 的 LineWrappingInserter.hh 的启发(参见 [Kanze])。

line_wrapping_stdio_filter

你可以将换行过滤器表达为一个 stdio_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 != '\n')
            ++col_no_;
        else
            col_no_ = 0;
    }

此函数将给定字符写入 std::cout 并递增列号,除非字符是换行符,在这种情况下,列号将重置。使用 put_char,你可以实现 virtual 函数 do_filter,如下所示

    void do_filter() 
    {
        int c;
        while ((c = std::cin.get()) != EOF) {
            if (c != '\n' && col_no_ >= line_length_)
                put_char('\n');
            put_char(c);
        }
    }

while 循环只是从 std::cin 读取一个字符并将其写入 std::cout,并根据需要插入额外的换行符以防止列计数超过 line_length_

最后,成员函数 do_close 覆盖了 stdio_filter 中声明的 private virtual 函数。

    void do_close() { col_no_ = 0; }

它的目的是在流关闭时重置过滤器的状态。

line_wrapping_input_filter

你可以将换行过滤器表达为一个 InputFilter,如下所示

#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 != '\n')
            ++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 != '\n' && col_no_ >= line_length_) {
            next_ = c;
            has_next_ = true;
            return get_char('\n');
        }

        return get_char(c);
    }

一个不是 MultiCharacterFilterInputFilter 只能一次返回一个字符。因此,如果你希望在从 src 读取的字符 c 之前插入一个换行符,你必须存储 c 并在下一次调用 get 时返回它。成员变量 next_ 用于存储这样的字符;成员变量 has_next_ 用于跟踪是否存储了这样的字符。

get 的实现首先检查是否有存储的字符,如果有就返回它。否则,它尝试从 src 读取一个字符。如果无法读取任何字符,它将返回特殊值之一 EOFWOULD_BLOCK。否则,它将检查是否必须插入换行符。如果是,它将存储当前字符并返回换行符。否则,它将返回当前字符。

最后,成员函数 close 重置过滤器的状态

    template<typename Sink>
    void close(Sink&)
    { 
        col_no_ = 0; 
        has_next_ = false; 
    }

line_wrapping_output_filter

你可以将换行过滤器表达为一个 OutputFilter,如下所示

#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 != '\n')
            ++col_no_;
        else
            col_no_ = 0;
        return true;
    }

此函数尝试将字符 c 写入给定的 Sink 并更新列计数(如果成功)。使用 put_char,你可以实现 put,如下所示

    template<typename Sink>
    bool put(Sink& dest, int c)
    {
        if (c != '\n' && col_no_ >= line_length_ && !put_char(dest, '\n'))
            return false;
        return put_char(dest, c);
    }

此函数首先检查给定字符和列计数以查看是否必须插入换行符。如果是,它将尝试使用 put_char 写入换行符,如果操作失败则返回 false。否则,它将尝试使用 put_char 写入给定字符。注意,如果成功插入换行符但尝试写入给定字符失败,则列计数将更新以反映换行符,这样下次尝试写入给定字符时不会导致换行符被插入。

最后,成员函数 close 重置过滤器的状态

    template<typename Sink>
    void close(Sink&) { col_no_ = 0; }