Create the actual DOM nodes for a given piece of text/ranges and
recurse downward into the range tree if necessary.

@param {string} new_sub_text Plaintext on which ranges act
@param {Array} new_sub_range Position/applicable ranges array
@param {object} range_info The origin range data for this render
@param {Array} range_data All range data
@param {object} options
@returns {DocumentFragment} Computed DOM nodes for all levels at or below passed range
function render_range( new_sub_text, new_sub_range, range_info, range_data, options ) {
	// Its time to build the outer shell of the range we're recursing into.
	let new_container = null,
		new_classes = [],
		type_mappings;

	let range_info_type = range_info.type;

	if ( typeof range_info.type !== 'undefined' ) {
		type_mappings = {
			b: 'strong', // be strong, my friend
			i: 'em', // I am, don't worry
			noticon: 'gridicon',
		};

		// Replace unwanted tags with more popular and cool ones
		if ( range_info.type in type_mappings ) {
			range_info_type = type_mappings[ range_info.type ];
		}

		new_classes.push( `wpnc__${ range_info_type }` );
	}

	// We want to do different things depending on the range type.
	switch ( range_info_type ) {
		// The badges should have their height and width set on
		// the server-side, but just in case they aren't, give
		// good defaults here
		case 'badge':
			if ( ! range_info.hasOwnProperty( 'width' ) ) {
				range_info.width = 256;
			}
			if ( ! range_info.hasOwnProperty( 'height' ) ) {
				range_info.height = 256;
			}
		case 'image':
			// Images and badges are not recursed into
			new_container = document.createElement( 'img' );
			new_container.setAttribute( 'src', range_info.url );
			if ( range_info.hasOwnProperty( 'width' ) ) {
				new_container.setAttribute( 'width', range_info.width );
			}
			if ( range_info.hasOwnProperty( 'height' ) ) {
				new_container.setAttribute( 'height', range_info.height );
			}
			if ( new_sub_text.trim().length > 0 ) {
				new_container.setAttribute( 'alt', new_sub_text );
			}
			break;
		// All of the following are simple element types we want to create and then
		// recurse into for their constituent blocks or texts
		case 'blockquote':
		case 'code':
		case 'strong':
		case 'em':
		case 'list':
			switch ( range_info_type ) {
				case 'list':
					range_info_type = 'span';
					break;
			}
			new_container = document.createElement( range_info_type );
			build_chunks( new_sub_text, new_sub_range, range_data, new_container, options );
			break;
		case 'gridicon':
			// Gridicons have special text, and are thus not recursed into
			new_container = document.createElement( 'span' );
			new_container.innerHTML = ReactDOMServer.renderToStaticMarkup(
				React.createElement( Gridicon, {
					icon: noticon2gridicon( range_info.value ),
					size: 18,
				} )
			);
			break;
		case 'button':
			new_classes.push( 'is-primary' );
		default:
			// Most range types fall here
			if ( options.links && range_info.url ) {
				// We are a link of some sort...
				new_container = document.createElement( 'a' );

				new_container.setAttribute( 'href', range_info.url );
				if ( range_info_type == 'stat' ) {
					// Stat links should change the whole window/tab
					new_container.setAttribute( 'target', '_parent' );
				} else {
					// Other links should link into a new window/tab
					new_container.setAttribute( 'target', '_blank' );
				}

				if ( 'post' === range_info.type ) {
					new_container.setAttribute( 'data-post-id', range_info.id );
					new_container.setAttribute( 'data-site-id', range_info.site_id );
					new_container.setAttribute( 'data-link-type', 'post' );
					new_container.setAttribute( 'target', '_self' );
				} else if ( 'tracks' === range_info.type && range_info.context ) {
					new_container.setAttribute( 'data-link-type', 'tracks' );
					new_container.setAttribute( 'data-tracks-event', range_info.context );
				}

				build_chunks( new_sub_text, new_sub_range, range_data, new_container, options );
			} else {
				// Everything else is a span
				new_container = document.createElement( 'span' );
				if ( new_sub_text.length > 0 ) {
					build_chunks( new_sub_text, new_sub_range, range_data, new_container, options );
				}
			}
			break;
	}

	if ( new_classes.length > 0 ) {
		new_container.className = new_classes.join( ' ' );
	}

	return new_container;
}