WP_HTML_Tag_Processor::class_name_updates_to_attributes_updates()

In this article

This function’s access is marked private. This means it is not intended for use by plugin or theme developers, only in other core functions. It is listed here for completeness. Use WP_HTML_Tag_Processor::$lexical_updates instead.

Converts class name updates into tag attributes updates (they are accumulated in different data formats for performance).

Description

See also

  • WP_HTML_Tag_Processor::$lexical_updates
  • WP_HTML_Tag_Processor::$classname_updates

Source

private function class_name_updates_to_attributes_updates() {
	if ( count( $this->classname_updates ) === 0 ) {
		return;
	}

	$existing_class = $this->get_enqueued_attribute_value( 'class' );
	if ( null === $existing_class || true === $existing_class ) {
		$existing_class = '';
	}

	if ( false === $existing_class && isset( $this->attributes['class'] ) ) {
		$existing_class = substr(
			$this->html,
			$this->attributes['class']->value_starts_at,
			$this->attributes['class']->value_length
		);
	}

	if ( false === $existing_class ) {
		$existing_class = '';
	}

	/**
	 * Updated "class" attribute value.
	 *
	 * This is incrementally built while scanning through the existing class
	 * attribute, skipping removed classes on the way, and then appending
	 * added classes at the end. Only when finished processing will the
	 * value contain the final new value.

	 * @var string $class
	 */
	$class = '';

	/**
	 * Tracks the cursor position in the existing
	 * class attribute value while parsing.
	 *
	 * @var int $at
	 */
	$at = 0;

	/**
	 * Indicates if there's any need to modify the existing class attribute.
	 *
	 * If a call to `add_class()` and `remove_class()` wouldn't impact
	 * the `class` attribute value then there's no need to rebuild it.
	 * For example, when adding a class that's already present or
	 * removing one that isn't.
	 *
	 * This flag enables a performance optimization when none of the enqueued
	 * class updates would impact the `class` attribute; namely, that the
	 * processor can continue without modifying the input document, as if
	 * none of the `add_class()` or `remove_class()` calls had been made.
	 *
	 * This flag is set upon the first change that requires a string update.
	 *
	 * @var bool $modified
	 */
	$modified = false;

	// Remove unwanted classes by only copying the new ones.
	$existing_class_length = strlen( $existing_class );
	while ( $at < $existing_class_length ) {
		// Skip to the first non-whitespace character.
		$ws_at     = $at;
		$ws_length = strspn( $existing_class, " \t\f\r\n", $ws_at );
		$at       += $ws_length;

		// Capture the class name – it's everything until the next whitespace.
		$name_length = strcspn( $existing_class, " \t\f\r\n", $at );
		if ( 0 === $name_length ) {
			// If no more class names are found then that's the end.
			break;
		}

		$name = substr( $existing_class, $at, $name_length );
		$at  += $name_length;

		// If this class is marked for removal, start processing the next one.
		$remove_class = (
			isset( $this->classname_updates[ $name ] ) &&
			self::REMOVE_CLASS === $this->classname_updates[ $name ]
		);

		// If a class has already been seen then skip it; it should not be added twice.
		if ( ! $remove_class ) {
			$this->classname_updates[ $name ] = self::SKIP_CLASS;
		}

		if ( $remove_class ) {
			$modified = true;
			continue;
		}

		/*
		 * Otherwise, append it to the new "class" attribute value.
		 *
		 * There are options for handling whitespace between tags.
		 * Preserving the existing whitespace produces fewer changes
		 * to the HTML content and should clarify the before/after
		 * content when debugging the modified output.
		 *
		 * This approach contrasts normalizing the inter-class
		 * whitespace to a single space, which might appear cleaner
		 * in the output HTML but produce a noisier change.
		 */
		$class .= substr( $existing_class, $ws_at, $ws_length );
		$class .= $name;
	}

	// Add new classes by appending those which haven't already been seen.
	foreach ( $this->classname_updates as $name => $operation ) {
		if ( self::ADD_CLASS === $operation ) {
			$modified = true;

			$class .= strlen( $class ) > 0 ? ' ' : '';
			$class .= $name;
		}
	}

	$this->classname_updates = array();
	if ( ! $modified ) {
		return;
	}

	if ( strlen( $class ) > 0 ) {
		$this->set_attribute( 'class', $class );
	} else {
		$this->remove_attribute( 'class' );
	}
}

Changelog

VersionDescription
6.2.0Introduced.

User Contributed Notes

You must log in before being able to contribute a note or feedback.