Tutorial

2.2.4. Line-Wrapping Filters

Suppose you want to write a filter which wraps lines of text to ensure that no line exceeds a certain maximum length. For simplicity, let's not bother to wrap lines at word boundaries or to insert hyphens. The basic algorithm is as follows: You examine characters one at a time, fowarding them as-is, keeping track of the current column number. When you encounter a newline character, you forward it and reset the column count. When the column count reaches the maxim value, you insert a newline character before the current character and reset the column count.

In the next three sections, I'll express this algorithm as a stdio_filter, an InputFilter and an OutputFilter. The source code can be found in the header <libs/iostreams/example/line_wrapping_filter.hpp>. These examples were inspired by James Kanze's LineWrappingInserter.hh (see [Kanze]).

line_wrapping_stdio_filter

You can express a line-wrapping Filter as a stdio_filter as follows:

#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

Let's look first at the definition of the helper function put_char:

    void put_char(int c)
    {
        std::cout.put(c);
        if (c != '\n')
            ++col_no_;
        else
            col_no_ = 0;
    }

This function writes the given character to std::cout and increments the column number, unless the character is a newline, in which case the column number is reset. Using put_char, you can implement the virtual function do_filter as follows:

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

The while loop simply reads a character from std::cin and writes it to std::cout, inserting an extra newline character as needed to prevent the column count from exceeding line_length_.

Finally, the member function do_close overrides a private virtual function declared in stdio_filter.

    void do_close() { col_no_ = 0; }

Its purpose is to reset the state of the Filter when a stream is closed.

line_wrapping_input_filter

You can express a line-wrapping Filter as an InputFilter as follows:

#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

Let's look first at the helper function get_char:

    int get_char(int c)
    {
        if (c != '\n')
            ++col_no_;
        else
            col_no_ = 0;
        return c;
    }

This function updates the column count based on the given character c, then returns c. Using get_char, you can implement get as follows:

    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);
    }

An InputFilter which is not a MultiCharacterFilter can only return a single character at a time. Consequently, if you wish to insert a newline before a character c read from src, you must store c and return it the next time get is called. The member variable next_ is used to store such a character; the member variable has_next_ keeps track of whether such a character is stored.

The implementation of get first checks to see if there is stored character, and returns it if there is. Otherwise, it attemps to read a character from src. If no character can be read, it returns one of the special values EOF or WOULD_BLOCK. Otherwise, it checks whether a newline must be inserted. If so, it stores the current character and returns a newline. Otherwise, it returns the current character.

Finally, the member function close resets the Filter's state:

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

line_wrapping_output_filter

You can express a line-wrapping Filter as an OutputFilter as follows:

#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

Let's look first at the helper function 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;
    }

This function attempts to write the character c to the given Sink and updates the column count if successful. Using put_char, you can implement put as follows:

    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);
    }

This function first checks the given character and the column count to see whether a newline character must be inserted. If so, it attempts to write a newline using put_char and returns false if the operation fails. Otherwise, it attempts to write the the given character using put_char. Note that if a newline is successfully inserted but the attempt to write the given character fails, the column count will be updated to reflect the newline character so that the next attempt to write the given character will not cause a newline to be inserted.

Finally, the member function close resets the Filter's state:

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