views:

907

answers:

4

Hi,

Is there an easy way to indent the output going to an ofstream object? I have a C++ character array that is null terminate and includes newlines. I'd like to output this to the stream but indent each line with two spaces. Is there an easy way to do this with the stream manipulators like you can change the base for integer output with special directives to the stream or do I have to manually process the array and insert the extra spaces manually at each line break detected?

Seems like the string::right() manipulator is close:

http://www.cplusplus.com/reference/iostream/manipulators/right/

Thanks.

-William

+1  A: 

Well this is not the answer I'm looking for, but in case there is no such answer, here is a way to do this manually:

void
indentedOutput(ostream &outStream, const char *message, bool &newline)
{
  while (char cur = *message) {
    if (newline) {
      outStream << "  ";
      newline = false;
    }
    outStream << cur;
    if (cur == '\n') {
      newline = true;
    }
    ++message;
  }
}
WilliamKF
+1  A: 

There is no simple way, but a lot has been written about the complex ways to achieve this. Read this article for a good explanation of the topic. Here is another article, unfortunately in German. But its source code should help you.

For example you could write a function which logs a recursive structure. For each level of recursion the indentation is increased:

std::ostream& operator<<(std::ostream& stream, Parameter* rp) 
{
    stream << "Parameter: " << std::endl;

    // Get current indent
    int w = format::get_indent(stream);

    stream << "Name: "  << rp->getName();
    // ... log other attributes as well

    if ( rp->hasParameters() )
    {
        stream << "subparameter (" << rp->getNumParameters() << "):\n";

        // Change indent for sub-levels in the hierarchy
        stream << format::indent(w+4);

        // write sub parameters        
        stream << rp->getParameters();
    }

    // Now reset indent
    stream << format::indent(w);

    return stream; 

}
Ralph
+2  A: 

A way to add such feature would be to write a filtering streambuf (i.e. a streambuf which forwards the IO operation to another streambuf but manipulate the data transfered) which add the indentation as part of its filter operation. I gave an example of writing a streambuf here and boost provides a library to help in that.

If your case, the overflow() member would simply test for '\n' and then add the indent just after if needed (exactly what you have done in your indentedOuput function, excepted that newline would be a member of the streambuf). You could probably have a setting to increase or decrease the indent size (perhaps accessible via a manipulator, the manipulator would have to do a dynamic_cast to ensure that the streambuf associated to the stream is of the correct type; there is a mechanism to add user data to stream -- basic_ios::xalloc, iword and pword -- but here we want to act on the streambuf).

AProgrammer
+2  A: 

This is the perfect situation to use a facet.

A custom version of the codecvt facet can be imbued onto a stream.

So your usage would look like this:

int main()
{
    std::locale         indentLocale(std::locale::classic(), new IndentFacet());

    /* Imbue std::cout before it is used */
    std::cout.imbue(indentLocale);

    std::cout << "Line 1\nLine 2\nLine 3\n";

    /* You must imbue a file stream beore it is opened. */
    std::ofstream       data;
    data.imbue(indentLocale);
    data.open("PLOP");

    data << "Martin\nUses Locale\nTo do something silly\n";
}

The definition of the facet is slightly complex.
But the whole point is that somebody using the facet does not need to know anything about the formatting. The formatting is applied independent of how the stream is being used.

#include <locale>
#include <algorithm>
#include <iostream>
#include <fstream>

class IndentFacet: public std::codecvt<char,char,std::mbstate_t>
{
  public:
   explicit IndentFacet(size_t ref = 0): std::codecvt<char,char,std::mbstate_t>(ref)    {}

    typedef std::codecvt_base::result               result;
    typedef std::codecvt<char,char,std::mbstate_t>  parent;
    typedef parent::intern_type                     intern_type;
    typedef parent::extern_type                     extern_type;
    typedef parent::state_type                      state_type;

    int&    state(state_type& s) const          {return *reinterpret_cast<int*>(&s);}
  protected:
    virtual result do_out(state_type& tabNeeded,
                         const intern_type* rStart, const intern_type*  rEnd, const intern_type*&   rNewStart,
                         extern_type*       wStart, extern_type*        wEnd, extern_type*&         wNewStart) const
    {
        result  res = std::codecvt_base::noconv;

        for(;(rStart < rEnd) && (wStart < wEnd);++rStart,++wStart)
        {
            // 0 indicates that the last character seen was a newline.
            // thus we will print a tab before it. Ignore it the next
            // character is also a newline
            if ((state(tabNeeded) == 0) && (*rStart != '\n'))
            {
                res                 = std::codecvt_base::ok;
                state(tabNeeded)    = 1;
                *wStart             = '\t';
                ++wStart;
                if (wStart == wEnd)
                {
                    res     = std::codecvt_base::partial;
                    break;
                }
            }
            // Copy the next character.
            *wStart         = *rStart;

            // If the character copied was a '\n' mark that state
            if (*rStart == '\n')
            {
                state(tabNeeded)    = 0;
            }
        }

        if (rStart != rEnd)
        {
            res = std::codecvt_base::partial;
        }
        rNewStart   = rStart;
        wNewStart   = wStart;

        return res;
    }

    // Override so the do_out() virtual function is called.
    virtual bool do_always_noconv() const throw()
    {
        return false;   // Sometime we add extra tabs
    }

};
Martin York