Skip to main content
Ben Nadel at CFinNC 2009 (Raleigh, North Carolina) with: Phil Molaro and Andy Matthews
Ben Nadel at CFinNC 2009 (Raleigh, North Carolina) with: Phil Molaro Andy Matthews

Unified ColdFusion Custom Tag Traversal In CFML Engines

By
Published in

In an attempt to formalize CFMailML, my ColdFusion mail custom tag DSL (Domain Specific Language), I need a way to traverse a stack of custom tag instances across the major CFML platforms. Adobe ColdFusion and Boxlang appear to be equivalent; but, Lucee CFML (as of version 6) still has some large discrepancies. And, of course, there are some bugs to contend with as well. This post is just an isolated custom tag stack traversal exploration that I'll roll into my CFMailML project.

To set the stage, I created a series of ColdFusion custom tags that contain zero logic, but which setup a deep tag hierarchy. In the following CFML, notice that some tags are used once (ex, cf_layer1) and some tags are used multiple times (ex, cf_layer2). Each tag takes a key attribute which is just used to uniquely identify each tag within the stack:

<cf_layer1 key="1.1">
	<cf_layer2 key="2.1">
		<cf_layer2 key="2.2">
			<cfmodule template="noop.cfm">
				<cfmodule template="noop.cfm">
					<cf_layer3 key="3.1">
						<cf_layer4 key="4.1">
							<cf_layer4 key="4.2">

								<!--- Where we are doing our look-up. --->
								<cf_layer5 key="5.1" />

							</cf_layer4>
						</cf_layer4>
					</cf_layer3>
				</cfmodule>
			</cfmodule>
		</cf_layer2>
	</cf_layer2>
</cf_layer1>

We'll be doing our stack lookup in the cf_layer5 custom tag. The <cfmodule> tags are there to try and trip-up the various engines. This is one of the major discrepancies across platforms. Adobe ColdFusion and Boxlang both report those tags as cf_noop but Lucee CFML will report them as cfmodule. Furthermore, Lucee CFML will not expose custom tag data on the cfmodule-based invocations.

In this exploration, my cf_layer5 custom tag defines a customTagStackGet() method, which walks up the hierarchy of custom tags and collects them into an array. This includes the data for each tag, which is another place that the CFML engines have variance: Adobe ColdFusion and Boxlang both navigate using "instance count" whereas Lucee CFML navigates using "skip levels".

<cfscript>

	platform = platformGet();

	writeDump(
		label = "Engine: #platform.name# (#platform.version.major#)",
		var = customTagStackGet(),
		showUDFs = false
	);
	exit;

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

	/**
	* I get the ColdFusion custom tag stack, normalizing differences between Lucee CFML
	* and Adobe ColdFusion / Boxlang.
	*
	* Caution: Lucee CFML doesn't handle the CFModule tag invocations properly - it hides
	* the underlying tag name and doesn't expose tag data. As such, CFModule calls are
	* skipped in terms of data but acknowledged in terms of hierarchy level. This does NOT
	* affect Adobe ColdFusion, which properly reports CFModule tags with their tag name.
	*/
	private any function customTagStackGet() {

		var names = getBaseTagList().listToArray();
		var stack = [];
		var level = 0;
		var counts = {};

		// Caution: we CANNOT use .map() on names because Adobe ColdFusion has a bug in
		// which the list of tag names breaks inside a closure.
		for ( var name in names ) {

			// Some native ColdFusion tags (ex, CFDump, CFTimer, CFSaveContent) are
			// implemented as custom tags and will show up in the list of names. We need
			// to skip over these tags since they don't expose any data. However, we DO
			// need to track the level depth so that we know how many levels to skip when
			// we access tag data for our own custom tags.
			level++;
			// Note: Lucee reports CFModule tags as "cfmodule" but does NOT expose base
			// tag data on those instances. As such, we have to skip them in Lucee. Adobe
			// ColdFusion reports CFModule tags using "cf_" notation. As such, we neither
			// have to skip them nor worry about any special logic.
			if ( name.left( 3 ) != "cf_" ) {

				continue;

			}

			var instance = counts[ name ] = ( ( counts[ name ] ?: 0 ) + 1 );

			// Adobe ColdFusion and Lucee CFML diverge in how they traverse the tag stack.
			// Search offset is a normalized value that can be used to locate the same tag
			// in both platforms.
			var searchOffset = server.keyExists( "lucee" )
				// Lucee CFML uses "skip levels" in getBaseTagData().
				? ( level - 1 )
				// Adobe ColdFusion uses "instance count" in getBaseTagData().
				: instance
			;

			stack.append({
				name: name,
				data: getBaseTagData( name, searchOffset ),
				level: level,
				instance: instance,
				searchOffset: searchOffset
			});

		}

		return stack;

	}


	/**
	* I get details about the current runtime engine.
	*/
	private struct function platformGet() {

		if ( server.keyExists( "lucee" ) ) {

			return {
				name: "Lucee CFML",
				version: {
					major: listFirst( server.lucee.version, "." ),
					details: server.lucee.version
				}
			};

		}

		if ( server.keyExists( "boxlang" ) ) {

			return {
				name: "Boxlang",
				version: {
					major: listFirst( server.boxlang.version, "." ),
					details: server.boxlang.version
				}
			};

		}

		return {
			name: "Adobe ColdFusion",
			version: {
				major: listFirst( server.coldfusion.productVersion ),
				details: server.coldfusion.productVersion
			}
		};

	}

</cfscript>

When I was putting this experiment together, I was mostly looking to make sure that the key attributes were all reported in reverse order. And that the repeated custom tags (ex, cf_layer2, cf_layer4) all those multiple times in the stack in the correct order:

Screenshot of browser showing custom tag stack results for Adobe ColdFusion 2025.

If Lucee CFML were to ever "fix" the divergence, it would be a fairly large breaking change in the platform implementation. So I assume this is just a logic issue that we'll have to live with going forward. That said, I know that custom tags aren't a wildly popular part of the CFML specification; so, maybe Lucee will converge at some point without breaking too much existing code.

Anyway, this post was mostly a note to self.

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
Managed ColdFusion hosting services provided by:
xByte Cloud Logo