Skip to main content
Ben Nadel at cf.Objective() 2010 (Minneapolis, MN) with: Ed Bartram and Anne Porosoff
Ben Nadel at cf.Objective() 2010 (Minneapolis, MN) with: Ed Bartram Anne Porosoff

Dedenting Text In ColdFusion

By
Published in Comments (1)

I've been on a real regular expression kick this week. Between my previous post on parsing Postmark Bounce details and creating custom trim functions, it seems that regular expressions make a daily appearance in my programming life. And, today is no exception. As a fun code kata, I want to try dedenting text in ColdFusion. Spoiler alert: I use regular expressions.

Dedenting text is the act of removing the leading whitespace from each line of text. However, my dedent() function differs from my inlineTrim() function in that it maintains the relative indentation across lines. That is, it will dedent the text only until any one of the lines no longer has leading whitespace.

Basically, I examine every line of text, find the one with least amount of indentation characters, and then remove that amount of indentation from all lines of text.

Here's my ColdFusion code. By default, it assumes the indentation character is the TAB. However, for legacy contexts, it can be configured to use the SPACE character if necessary.

<cfscript>

	include "./utils.cfm"; // concat(), line(), and showWhitespace().

	// ------------------------------------------------------------------------------- //
	// ------------------------------------------------------------------------------- //

	text = concat(
		line( "				We will find the minimum indentation width" ),
		line( "			and then remove that width" ),
		line( "		from each line of the content," ),
		line( "			such that the file remains" ),
		line( "				uniformly indented." )
	);

	// We're going to strip the leading tabs from the given content such that the line
	// starting with "from" touches the left-edge of the content and the rest of the lines
	// are all indented relative to that line.
	echo( "<pre>" & showWhitespace( dedent( text ) ) & "</pre>" );

	// ------------------------------------------------------------------------------- //
	// ------------------------------------------------------------------------------- //

	/**
	* I remove indentation from the given input but leave the input such that the relative
	* indentation across lines remains constant.
	*/
	public string function dedent(
		required string input,
		string indentation = "tab"
		) {

		var indentationCharacter = ( indentation == "tab" )
			? chr( 9 )  // Tab.
			: chr( 32 ) // Space.
		;

		// In order to figure out how much we can dedent the text, we must first locate
		// the smallest amount of indentation that currently exists across all lines of
		// text. However, we only care about lines of text that have non-indentation
		// characters on them (ie, we want to skip over empty lines). As such, we're going
		// to create a pattern that must end on non-indentation (or line-break) character.
		var minWidth = input
			.reMatch( "(?m)^#indentationCharacter#*[^#indentationCharacter#\r\n]" )
			.map(
				( linePrefix ) => {

					return ( linePrefix.len() - 1 );

				}
			)
			// NOTE: ArrayMin() returns zero if array is empty.
			.min()
		;

		if ( ! minWidth ) {

			return input;

		}

		// Now that we've found the smallest amount of indentation across all lines of
		// text, we can remove exactly that amount of indentation from each line of text.
		var dedentedInput = input
			.reReplace( "(?m)^#indentationCharacter#{#minWidth#}", "", "all" )
		;

		return dedentedInput;

	}

</cfscript>

Now, if we run this ColdFusion code, we get the following output (in which I've replaced all whitespace characters with visual characters for easier viewing):

--We.will.find.the.minimum.indentation.width+
-and.then.remove.that.width+
from.each.line.of.the.content,+
-such.that.the.file.remains+
--uniformly.indented.+

As you can see, the line starting with from now touches the left-edge of the text block. And, all the lines surrounding it still have indentation; but, their indentation remains relative to the from line.

The reason this concept came to mind has to do with the CFSaveContent tag. When defining content using this tag in ColdFusion, the content must be indented in order to maintain an appropriate code aesthetic:

<cfoutput>
	<cfsavecontent variable="text">
		This code is indented with 2 tabs.
		But, that's for aesthetics -
		Not because it needs to be indented.
	</cfsavecontent>
</cfoutput>

Given this context, I would need to dedent the resultant text value by two tabs in order to end up with my intended content.

Want to use code from this post? Check out the license.

Reader Comments

Post A Comment — I'd Love To Hear From You!

Post a Comment

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel