improve.dk
Just another mindless drone looking for the perfect stack
posts - 227, comments - 489

Using IDisposable to write indented text

Written on June 2, 2008 by Mark S. Rasmussen in Development: .NET

I often need to output indented text in one way of the other, it could be HTML, XML, source code etc (please look beyond the actual problem domain - I'd enver write XML this way, it's just an example). Usually that involved me writing tab characters manually (or by calling a function that returned the current indentation string), cluttering the actual output. An example might look like this:

StringBuilder sb = new StringBuilder();
sb.AppendLine("public static void Hello()");
sb.AppendLine("{");
sb.AppendLine("\tif(true)");
sb.AppendLine("\t\tConsole.WriteLine(\"World!\");");
sb.AppendLine("}");

Console.Write(sb.ToString());
Console.Read();

This ought to result in the following snippet:

public static void Hello()
{
	if(true)
		Console.WriteLine("World!");
}

Pretty simple code, but it's a bit hard for the eyes, especially if there's a lot of it.

By utilizing the IDisposable interface, we can create a StringBuilder-esque class that handles the indentation for us. Here's an example of how we might write the previous snippet using the IndentedStringBuilder (note that it's not really a StringBuilder since StringBuilder's a sealed class):

using (IndentedStringBuilder sb = new IndentedStringBuilder())
{
	sb.AppendLine("public static void Hello()");
	sb.AppendLine("{");

	using (sb.IncreaseIndent())
	{
		sb.AppendLine("if(true)");

		using (sb.IncreaseIndent())
			sb.AppendLine("Console.WriteLine(\"World!\");");
	}

	sb.AppendLine("}");

	Console.Write(sb.ToString());
	Console.Read();
}

Each time Dispose() is called on the instance, the indentation level is decreased. Calling IncreaseIndent() increases the indentation level, as well as returning a reference to the IndentedStringBuilder instance itself. The using construct will make sure Dispose is called on the reference each time we exit the using block - and calling Dispose does not do anything to the object other than calling the Dispose method - which'll decrease the indentation level.

Here's the IndentedStringBuilder class:

using System;
using System.Text;

namespace Improve.Framework.Text
{
	public class IndentedStringBuilder : IDisposable
	{
		private StringBuilder sb;
		private string indentationString = "\t";
		private string completeIndentationString = "";
		private int indent = 0;

		/// <summary>
		///  Creates an IndentedStringBuilder
		/// </summary>
		public IndentedStringBuilder()
		{
			sb = new StringBuilder();
		}

		/// <summary>
		/// Appends a string
		/// </summary>
		/// <param name="value"></param>
		public void Append(string value)
		{
			sb.Append(completeIndentationString + value);
		}

		/// <summary>
		/// Appends a line
		/// </summary>
		/// <param name="value"></param>
		public void AppendLine(string value)
		{
			Append(value + Environment.NewLine);
		}

		/// <summary>
		/// The string/chars to use for indentation, \t by default
		/// </summary>
		public string IndentationString
		{
			get { return indentationString; }
			set
			{
				indentationString = value;

				updateCompleteIndentationString();
			}
		}

		/// <summary>
		/// Creates the actual indentation string
		/// </summary>
		private void updateCompleteIndentationString()
		{
			completeIndentationString = "";

			for (int i = 0; i < indent; i++)
				completeIndentationString += indentationString;
		}

		/// <summary>
		/// Increases indentation, returns a reference to an IndentedStringBuilder instance which is only to be used for disposal
		/// </summary>
		/// <returns></returns>
		public IndentedStringBuilder IncreaseIndent()
		{
			indent++;

			updateCompleteIndentationString();

			return this;
		}

		/// <summary>
		/// Decreases indentation, may only be called if indentation > 1
		/// </summary>
		public void DecreaseIndent()
		{
			if (indent > 0)
			{
				indent--;

				updateCompleteIndentationString();
			}
		}

		/// <summary>
		/// Decreases indentation
		/// </summary>
		public void Dispose()
		{
			DecreaseIndent();
		}

		/// <summary>
		/// Returns the text of the internal StringBuilder
		/// </summary>
		/// <returns></returns>
		public override string ToString()
		{
			return sb.ToString();
		}
	}
}

Feedback

Gravatar

Henk wrote on 1/4/2010 12:23 PM

Excellent solution! Thank you Mark!
Gravatar

David Keaveny wrote on 8/25/2011 2:46 AM

Nice solution; I was planning on implementing exactly the same thing, to solve exactly the same problem, but it seems you've beaten me to it.
Gravatar

Christopher wrote on 3/13/2012 7:47 PM

I was hoping you had also solved the issue of what happens when embedded escape sequences (\t\r\n) are in the append string. Let us know if you solved this please.

Post Comment

Name  
Email
Url
Comment
Please add 2 and 5 and type the answer here: