WP_HTML_Tag_Processor::parse_next_attribute()privateWP 6.2.0

Parses the next attribute.

Method of the class: WP_HTML_Tag_Processor{}

No Hooks.

Return

true|false. Whether an attribute was found before the end of the document.

Usage

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

Changelog

Since 6.2.0 Introduced.

WP_HTML_Tag_Processor::parse_next_attribute() code WP 6.6.2

private function parse_next_attribute() {
	// Skip whitespace and slashes.
	$this->bytes_already_parsed += strspn( $this->html, " \t\f\r\n/", $this->bytes_already_parsed );
	if ( $this->bytes_already_parsed >= strlen( $this->html ) ) {
		$this->parser_state = self::STATE_INCOMPLETE_INPUT;

		return false;
	}

	/*
	 * Treat the equal sign as a part of the attribute
	 * name if it is the first encountered byte.
	 *
	 * @see https://html.spec.whatwg.org/multipage/parsing.html#before-attribute-name-state
	 */
	$name_length = '=' === $this->html[ $this->bytes_already_parsed ]
		? 1 + strcspn( $this->html, "=/> \t\f\r\n", $this->bytes_already_parsed + 1 )
		: strcspn( $this->html, "=/> \t\f\r\n", $this->bytes_already_parsed );

	// No attribute, just tag closer.
	if ( 0 === $name_length || $this->bytes_already_parsed + $name_length >= strlen( $this->html ) ) {
		return false;
	}

	$attribute_start             = $this->bytes_already_parsed;
	$attribute_name              = substr( $this->html, $attribute_start, $name_length );
	$this->bytes_already_parsed += $name_length;
	if ( $this->bytes_already_parsed >= strlen( $this->html ) ) {
		$this->parser_state = self::STATE_INCOMPLETE_INPUT;

		return false;
	}

	$this->skip_whitespace();
	if ( $this->bytes_already_parsed >= strlen( $this->html ) ) {
		$this->parser_state = self::STATE_INCOMPLETE_INPUT;

		return false;
	}

	$has_value = '=' === $this->html[ $this->bytes_already_parsed ];
	if ( $has_value ) {
		++$this->bytes_already_parsed;
		$this->skip_whitespace();
		if ( $this->bytes_already_parsed >= strlen( $this->html ) ) {
			$this->parser_state = self::STATE_INCOMPLETE_INPUT;

			return false;
		}

		switch ( $this->html[ $this->bytes_already_parsed ] ) {
			case "'":
			case '"':
				$quote                      = $this->html[ $this->bytes_already_parsed ];
				$value_start                = $this->bytes_already_parsed + 1;
				$value_length               = strcspn( $this->html, $quote, $value_start );
				$attribute_end              = $value_start + $value_length + 1;
				$this->bytes_already_parsed = $attribute_end;
				break;

			default:
				$value_start                = $this->bytes_already_parsed;
				$value_length               = strcspn( $this->html, "> \t\f\r\n", $value_start );
				$attribute_end              = $value_start + $value_length;
				$this->bytes_already_parsed = $attribute_end;
		}
	} else {
		$value_start   = $this->bytes_already_parsed;
		$value_length  = 0;
		$attribute_end = $attribute_start + $name_length;
	}

	if ( $attribute_end >= strlen( $this->html ) ) {
		$this->parser_state = self::STATE_INCOMPLETE_INPUT;

		return false;
	}

	if ( $this->is_closing_tag ) {
		return true;
	}

	/*
	 * > There must never be two or more attributes on
	 * > the same start tag whose names are an ASCII
	 * > case-insensitive match for each other.
	 *     - HTML 5 spec
	 *
	 * @see https://html.spec.whatwg.org/multipage/syntax.html#attributes-2:ascii-case-insensitive
	 */
	$comparable_name = strtolower( $attribute_name );

	// If an attribute is listed many times, only use the first declaration and ignore the rest.
	if ( ! array_key_exists( $comparable_name, $this->attributes ) ) {
		$this->attributes[ $comparable_name ] = new WP_HTML_Attribute_Token(
			$attribute_name,
			$value_start,
			$value_length,
			$attribute_start,
			$attribute_end - $attribute_start,
			! $has_value
		);

		return true;
	}

	/*
	 * Track the duplicate attributes so if we remove it, all disappear together.
	 *
	 * While `$this->duplicated_attributes` could always be stored as an `array()`,
	 * which would simplify the logic here, storing a `null` and only allocating
	 * an array when encountering duplicates avoids needless allocations in the
	 * normative case of parsing tags with no duplicate attributes.
	 */
	$duplicate_span = new WP_HTML_Span( $attribute_start, $attribute_end - $attribute_start );
	if ( null === $this->duplicate_attributes ) {
		$this->duplicate_attributes = array( $comparable_name => array( $duplicate_span ) );
	} elseif ( ! array_key_exists( $comparable_name, $this->duplicate_attributes ) ) {
		$this->duplicate_attributes[ $comparable_name ] = array( $duplicate_span );
	} else {
		$this->duplicate_attributes[ $comparable_name ][] = $duplicate_span;
	}

	return true;
}