WP_HTML_Tag_Processor::class_name_updates_to_attributes_updates()privateWP 6.2.0

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

Method of the class: WP_HTML_Tag_Processor{}

No Hooks.

Return

null. Nothing (null).

Usage

// private - for code of main (parent) class only
$result = $this->class_name_updates_to_attributes_updates();

Notes

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

Changelog

Since 6.2.0 Introduced.

WP_HTML_Tag_Processor::class_name_updates_to_attributes_updates() code WP 6.6.2

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' );
	}
}