WP_HTML_Decoder::attribute_starts_with( string $haystack, string $search_text, string $case_sensitivity = 'case-sensitive' ): bool

Indicates if an attribute value starts with a given raw string value.

Description

Use this method to determine if an attribute value starts with a given string, regardless of how it might be encoded in HTML. For instance, http: could be represented as http: or as http: or as http: or as http:, or in many other ways.

This is equivalent to a byte-prefix test against the decoded attribute value, without the need to allocate and decode the full string.

Example:

$value = 'http://wordpress.org/';
true   === WP_HTML_Decoder::attribute_starts_with( $value, 'http:', 'ascii-case-insensitive' );
false  === WP_HTML_Decoder::attribute_starts_with( $value, 'https:', 'ascii-case-insensitive' );

Parameters

$haystackstringrequired
String containing the raw non-decoded attribute value.
$search_textstringrequired
Does the attribute value start with this plain string.
$case_sensitivitystringoptional
Pass 'ascii-case-insensitive' to ignore ASCII case when matching.
Default 'case-sensitive'.

Default:'case-sensitive'

Return

bool Whether the attribute value starts with the given string.

Source

public static function attribute_starts_with( $haystack, $search_text, $case_sensitivity = 'case-sensitive' ): bool {
	$search_length = strlen( $search_text );
	$loose_case    = 'ascii-case-insensitive' === $case_sensitivity;
	$haystack_end  = strlen( $haystack );
	$search_at     = 0;
	$haystack_at   = 0;

	while ( $search_at < $search_length && $haystack_at < $haystack_end ) {
		$chars_match = $loose_case
			? strtolower( $haystack[ $haystack_at ] ) === strtolower( $search_text[ $search_at ] )
			: $haystack[ $haystack_at ] === $search_text[ $search_at ];

		$is_introducer = '&' === $haystack[ $haystack_at ];
		$next_chunk    = $is_introducer
			? self::read_character_reference( 'attribute', $haystack, $haystack_at, $token_length )
			: null;

		// If there's no character reference and the characters don't match, the match fails.
		if ( null === $next_chunk && ! $chars_match ) {
			return false;
		}

		// If there's no character reference but the characters do match, then it could still match.
		if ( null === $next_chunk && $chars_match ) {
			++$haystack_at;
			++$search_at;
			continue;
		}

		/**
		 * The decoded character reference in `$next_chunk` must be compared with the
		 * corresponding `$search_text` bytes checking for matching prefixes. The remaining
		 * search text may be shorter than the decoded chunk, in which case a partial match
		 * satisfies the prefix. Otherwise, if the decoded chunk is fully matched, the
		 * comparison must continue after advancing the appropriate byte lengths: the character
		 * reference token length in the haystack and the decoded chunk length in the
		 * search text.
		 *
		 * For example, consider searches that have reached the character reference
		 * `&fjlig;` (7 bytes), decoded into the 2-byte chunk `fj`:
		 *
		 *                       $haystack_at
		 *                       │
		 *                       │      ┌─after matching `fj` continue here
		 *                       │      │ (advance by $token_length, 7 bytes)
		 *                       ↓      ↓
		 *     Haystack:    start&fjlig;ord
		 *                       ╰──┬──╯
		 *                          fj - the decoded chunk, tested against the search text.
		 *
		 *                       $search_at
		 *                       │
		 *                       │ ┌─after matching `fj` continue here
		 *                       │ │ (advance by $match_length, 2 bytes)
		 *                       ↓ ↓
		 *     Search A:    startfjord      Compare 2 bytes: `fj` matches,
		 *                                  continue matching at `o`.
		 *
		 *                       $search_at
		 *                       ↓
		 *     Search B:    startf          Compare 1 byte: `f` matches and the
		 *                                  search text is exhausted — prefix confirmed.
		 *
		 *                       $search_at
		 *                       ↓
		 *     Search C:    startfr         Compare 2 bytes: `fj` differs
		 *                                  from `fr`, no match is possible.
		 *
		 * The `min()` is required in both directions: Search A fails if the
		 * comparison length comes from the search text, Search B if it comes
		 * from the chunk.
		 *
		 * After a match each cursor must advance by the appropriate length, the haystack
		 * cursor by the character reference token length, and the search cursor by the
		 * matched length.
		 */
		$match_length = min( strlen( $next_chunk ), $search_length - $search_at );
		if ( 0 !== substr_compare( $search_text, $next_chunk, $search_at, $match_length, $loose_case ) ) {
			return false;
		}

		// The character reference matched, so continue checking.
		$haystack_at += $token_length;
		$search_at   += $match_length;
	}

	return $search_at === $search_length;
}

Changelog

VersionDescription
6.6.0Introduced.

User Contributed Notes

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