How to Easily Indent Output to Ofstream

How to easily indent output to ofstream?

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

C++ Indenting output class inheriting ofstream

You usually shouldn't inherit std::ostream or implementations of it like std::ofstream. Wrap them into another class instead.

Here's a short sketch of my ideas mentioned in the comments

#include <iostream>
#include <fstream>

using namespace std;

class Logger {
public:
Logger(ostream& os) : os_(os), curIndentLevel_(0) {}
void increaseLevel() { ++curIndentLevel_; }
void decreaseLevel() { --curIndentLevel_; }

private:
template<typename T> friend ostream& operator<<(Logger&, T);

ostream& os_;
int curIndentLevel_;
};

template<typename T>
ostream& operator<<(Logger& log, T op) {
for(int i = 0; i < log.curIndentLevel_ * 4; ++i) {
log.os_ << ' ';
}
log.os_ << op;
return log.os_;
}

int main() {
Logger log(cout);
log.increaseLevel();
log << "Hello World!" << endl;
log.decreaseLevel();
log << "Hello World!" << endl;
return 0;
}

Output

    Hello World!
Hello World!

Live Sample


Here's a little variant, showing how you can shortcut the coding with operator<<() overloads:

class Logger {
public:
Logger(ostream& os) : os_(os), curIndentLevel_(0) {}
Logger& increaseLevel() { ++curIndentLevel_; return *this; }
Logger& decreaseLevel() { --curIndentLevel_; return *this; }

// ... as before ...
};

int main() {
Logger log(cout);

log.increaseLevel() << "Hello World!" << endl;
log.decreaseLevel() << "Hello World!" << endl;
return 0;
}

Live Sample

The same way you can go to provide additional I/O manipulator style free functions.

How to add indention to the stream operator

The simplest solution is to slip a filtering streambuf between the
ostream and the actual streambuf. Something like:

class IndentingOStreambuf : public std::streambuf
{
std::streambuf* myDest;
bool myIsAtStartOfLine;
std::string myIndent;
std::ostream* myOwner;
protected:
virtual int overflow( int ch )
{
if ( myIsAtStartOfLine && ch != '\n' ) {
myDest->sputn( myIndent.data(), myIndent.size() );
}
myIsAtStartOfLine = ch == '\n';
return myDest->sputc( ch );
}
public:
explicit IndentingOStreambuf(
std::streambuf* dest, int indent = 4 )
: myDest( dest )
, myIsAtStartOfLine( true )
, myIndent( indent, ' ' )
, myOwner( NULL )
{
}
explicit IndentingOStreambuf(
std::ostream& dest, int indent = 4 )
: myDest( dest.rdbuf() )
, myIsAtStartOfLine( true )
, myIndent( indent, ' ' )
, myOwner( &dest )
{
myOwner->rdbuf( this );
}
virtual ~IndentingOStreambuf()
{
if ( myOwner != NULL ) {
myOwner->rdbuf( myDest );
}
}
};

To insert, just create an instance of the streambuf:

IndentingOStreambuf indent( std::cout );
// Indented output...

When indent goes out of scope, everything returns to normal.

(For logging, I have one that is a bit more complex: the
LoggingOStreambuf takes __FILE__ and __LINE__ as arguments, sets
myIndent to a formatted string with these arguments, plus a time
stamp, resets it to an indentation string after each output, collects
all of the output in an std::ostringstream, and outputs it atomically
to myDest in the destructor.)

make std::ostream automatically ident when encountering special characters

boost::iostreams makes this fairly easy, you can define filters then chain them together with an output stream to transform the input to the desired output:

#include <iostream>
#include <boost/iostreams/filtering_stream.hpp>

namespace io = boost::iostreams;

struct QuoteOutputFilter {
typedef char char_type;
typedef io::output_filter_tag category;

int indent = 0;

template<typename Sink>
bool newLine(Sink& snk)
{
std::string str = "\n" + std::string(indent * 4, ' ');
return io::write(snk, str.c_str(), str.size());
}

template<typename Sink>
bool put(Sink& snk, char c)
{
switch (c)
{
case '<':
io::put(snk, c);
indent += 1;
return newLine(snk);
case ',':
io::put(snk, c);
return newLine(snk);
case '>':
indent -= 1;
newLine(snk);
return io::put(snk, c);
default:
return io::put(snk, c);
}
}
};

int main()
{
io::filtering_ostream out;
out.push(QuoteOutputFilter());
out.push(std::cout);

out << "test0<test1<test2, test3<test4> > >";
}

c++ custom output stream with indentation

The iostreams support adding custom data to them, so you don't need to write a full derived class just to add an indentation level that will be operated on by manipulators. This is a little-known feature of iostreams, but comes in handy here.

You would write your manipulators like this:

/* Helper function to get a storage index in a stream */
int get_indent_index() {
/* ios_base::xalloc allocates indices for custom-storage locations. These indices are valid for all streams */
static int index = ios_base::xalloc();
return index;
}

ios_base& inc_ind(ios_base& stream) {
/* The iword(index) function gives a reference to the index-th custom storage location as a integer */
stream.iword(get_indent_index())++;
return stream;
}

ios_base& dec_ind(ios_base& stream) {
/* The iword(index) function gives a reference to the index-th custom storage location as a integer */
stream.iword(get_indent_index())--;
return stream;
}

template<class charT, class traits>
basic_ostream<charT, traits>& endl_ind(basic_ostream<charT, traits>& stream) {
int indent = stream.iword(get_indent_index());
stream.put(stream.widen('\n');
while (indent) {
stream.put(stream.widen('\t');
indent--;
}
stream.flush();
return stream;
}

aligning output of ofstream

Yes, <iomanip> header provides the setw manipulator, letting you set the width of each field that you output to an ostream. Using setw manipulator for each row instead of tab characters would provide tighter control over the output:

output << setw(25) << "Start time part 1 " << timeStr << " on " << dateStr << endl;
output << setw(25) << "Start time part 1000000 " << timeStr << " on " << dateStr << endl;

To align strings on the left, add left manipulator:

output << left << setw(25) << "Start time part 1 " << timeStr << " on " << dateStr << endl;
output << left << setw(25) << "Start time part 1000000 " << timeStr << " on " << dateStr << endl;


Related Topics



Leave a reply



Submit