Mark S. Rasmussen improve.dk
Jun 02
2008

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();
		}
	}
}
Mark S. Rasmussen
I'm the CTO at iPaper where I cuddle with databases, mold code and maintain the overall technical & team responsibility. I'm an avid speaker at user groups & conferences. I love life, motorcycles, photography and all things technical. Say hi on Twitter, write me an email or look me up on LinkedIn.