17

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

WilliamKF
  • 41,123
  • 68
  • 193
  • 295
  • maybe it's time someone wrote a library for this :) – xtofl Sep 08 '09 at 05:04
  • 1
    Its already available. Its called a facet. It is used to format stream output. Thus the user of the stream can just output there data as normal. The facet can then perform any format independently (thus the output format can be changed merely by changing the facet the stream uses without altering the code that produces the output). – Martin York Sep 09 '09 at 10:44

7 Answers7

21

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()
{
    /* Imbue std::cout before it is used */
    std::ios::sync_with_stdio(false);
    std::cout.imbue(std::locale(std::locale::classic(), new IndentFacet()));

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

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

    data << "Loki\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
    }

};

See: Tom's notes below

Community
  • 1
  • 1
Martin York
  • 257,169
  • 86
  • 333
  • 562
  • What is the expected outcome here? Doesn't seem to work as advertised here: [http://liveworkspace.org/code/T4tCi$0](http://liveworkspace.org/code/T4tCi$0) – sehe Feb 18 '13 at 16:51
  • @sehe: std::cout is funny. The imbue() will not work on any stream if the stream has been used in anyway. Some implementations use std::cout before main() thus the imbue may fail() in the above code. But it will always work on the file. So check the content of the file PLOP. – Martin York Feb 18 '13 at 17:20
  • I think I saw it not working with ostringstream too. I'll try to check later – sehe Feb 18 '13 at 17:24
  • When compiling with clang++ I ahev following warning: warning: 'IndentFacet' has no out-of-line virtual method definitions; its vtable will be emitted in every translation unit [-Wweak-vtables]. Can this be avoided? – Mathieu Dutour Sikiric May 16 '16 at 09:11
  • @MathieuDutourSikiric: Yes. Rather than have all the functions in the header file split the above into two files a header file with the declaration and a source file with the definitions. I just put it all together for ease of displaying on stackoverflow. **BUT** I would not bother I would turn of that warning (It just means if you change this class you need to force a rebuild of all translation units that include this header). If you don't intend to change the class then it will not be an issue. – Martin York May 16 '16 at 14:37
2

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
  • 41,123
  • 68
  • 193
  • 295
2

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).

Community
  • 1
  • 1
AProgrammer
  • 51,233
  • 8
  • 91
  • 143
2

I've had good success with Martin's codecvt facet based suggestion, but I had problems using it on std::cout on OSX, since by default this stream uses a basic_streambuf based streambuf which ignores the imbued facet. The following line switches std::cout and friends to use a basic_filebuf based streambuf, which will use the imbued facet.

std::ios::sync_with_stdio(false);

With the associated side effect that the iostream standard stream objects may operate independently of the standard C streams.

Another note is since this facet does not have a static std::locale::id, which meant that calling std::has_facet<IndentFacet> on the locale always returned true. Adding a std::local::id meant that the facet was not used, since basic_filebuf looks for the base class template.

Tom M
  • 141
  • 2
  • 4
1

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
  • 5,154
  • 1
  • 21
  • 19
1

I have generalized Loki Astarti's solution to work with arbitrary indentation levels. The solution has a nice, easy to use interface, but the actual implementation is a little fishy. It can be found on github:https://github.com/spacemoose/ostream_indenter

There's a more involved demo in the github repo, but given:

#include "indent_facet.hpp"

/// This probably has to be called once for every program:
// http://stackoverflow.com/questions/26387054/how-can-i-use-stdimbue-to-set-the-locale-for-stdwcout
std::ios_base::sync_with_stdio(false);

// This is the demo code:
std::cout << "I want to push indentation levels:\n" << indent_manip::push
          << "To arbitrary depths\n" << indent_manip::push
          << "and pop them\n" << indent_manip::pop
          << "back down\n" << indent_manip::pop
          << "like this.\n" << indent_manip::pop;

}

It produces the following output:

I want to push indentation levels:
    To arbitrary depths
        and pop them
    back down
like this.

I would appreciate any feedback as to the utility of the code.

Spacemoose
  • 3,856
  • 1
  • 27
  • 48
  • while this is a really great idea about using codecvt for autoindenting, there are few problems with your source code: 1) When printing tabulations in a loop, you do not check for `_to` buffer overflow, this one can be solved easily; (to be continued in the next comment) – segfault Apr 17 '17 at 08:32
  • 1
    2) After fixing the first problem, there is another issue with libstdc++ implementation of codecvt, which calls `do_out` only once if the result of last operation was `std::codecvt_base::partial`, so if the line has only few characters (say, e.g. opening brace) and the indent level is big, some characters will be lost. I do not know how to solve this correctly, the only ugly solution I know is to override `do_max_length()` to return a large value forcing libstdc++ to allocate big enough output buffer. – segfault Apr 17 '17 at 08:32
  • @segfault all these crazy facet solutions that needs to know so much about these details, corners and flatout bugs; is insanity. Hopefully with rangesV3 or C++20 you just do `cout << (mydata | indent_view) << "\n";` and be done with your day. – v.oddou Apr 12 '19 at 06:48
0

Simple whitespace manipulator

struct Whitespace
{
    Whitespace(int n)
        : n(n)
    {
    }
    int n;
};

std::ostream& operator<<(std::ostream& stream, const Whitespace &ws)
{
    for(int i = 0; i < ws.n; i++)
    {
        stream << " ";
    }
    return stream;
}