Skip to content

Commit

Permalink
Clean up and document attribute selector
Browse files Browse the repository at this point in the history
  • Loading branch information
sirreal committed Dec 5, 2024
1 parent 32778c8 commit b7e032e
Showing 1 changed file with 122 additions and 92 deletions.
214 changes: 122 additions & 92 deletions src/wp-includes/html-api/class-wp-css-attribute-selector.php
Original file line number Diff line number Diff line change
@@ -1,96 +1,23 @@
<?php

/**
* HTML API: WP_CSS_Attribute_Selector class
*
* @package WordPress
* @subpackage HTML-API
* @since TBD
*/

/**
* CSS attribute selector.
*
* This class implements a CSS attribute selector and is used to test for matching HTML tags
* in a {@see WP_HTML_Tag_Processor}.
*
* @since TBD
*
* @access private
*/
final class WP_CSS_Attribute_Selector implements WP_CSS_HTML_Tag_Processor_Matcher {
const WHITESPACE_CHARACTERS = " \t\r\n\f";

public function matches( WP_HTML_Tag_Processor $processor ): bool {
$att_value = $processor->get_attribute( $this->name );
if ( null === $att_value ) {
return false;
}

if ( null === $this->value ) {
return true;
}

if ( true === $att_value ) {
$att_value = '';
}

$case_insensitive = self::MODIFIER_CASE_INSENSITIVE === $this->modifier;

switch ( $this->matcher ) {
case self::MATCH_EXACT:
return $case_insensitive
? 0 === strcasecmp( $att_value, $this->value )
: $att_value === $this->value;

case self::MATCH_ONE_OF_EXACT:
foreach ( $this->whitespace_delimited_list( $att_value ) as $val ) {
if (
$case_insensitive
? 0 === strcasecmp( $val, $this->value )
: $val === $this->value
) {
return true;
}
}
return false;

case self::MATCH_EXACT_OR_EXACT_WITH_HYPHEN:
// Attempt the full match first
if (
$case_insensitive
? 0 === strcasecmp( $att_value, $this->value )
: $att_value === $this->value
) {
return true;
}

// Partial match
if ( strlen( $att_value ) < strlen( $this->value ) + 1 ) {
return false;
}

$starts_with = "{$this->value}-";
return 0 === substr_compare( $att_value, $starts_with, 0, strlen( $starts_with ), $case_insensitive );

case self::MATCH_PREFIXED_BY:
return 0 === substr_compare( $att_value, $this->value, 0, strlen( $this->value ), $case_insensitive );

case self::MATCH_SUFFIXED_BY:
return 0 === substr_compare( $att_value, $this->value, -strlen( $this->value ), null, $case_insensitive );

case self::MATCH_CONTAINS:
return false !== (
$case_insensitive
? stripos( $att_value, $this->value )
: strpos( $att_value, $this->value )
);
}
}

/**
* @param string $input
*
* @return Generator<string>
*/
private function whitespace_delimited_list( string $input ): Generator {
// Start by skipping whitespace.
$offset = strspn( $input, self::WHITESPACE_CHARACTERS );

while ( $offset < strlen( $input ) ) {
// Find the byte length until the next boundary.
$length = strcspn( $input, self::WHITESPACE_CHARACTERS, $offset );
$value = substr( $input, $offset, $length );

// Move past trailing whitespace.
$offset += $length + strspn( $input, self::WHITESPACE_CHARACTERS, $offset + $length );

yield $value;
}
}

/**
* [att=val]
* Represents an element with the att attribute whose value is exactly "val".
Expand Down Expand Up @@ -145,36 +72,41 @@ private function whitespace_delimited_list( string $input ): Generator {
*/
const MODIFIER_CASE_INSENSITIVE = 'case-insensitive';


/**
* The attribute name.
*
* @var string
* @readonly
*/
public $name;

/**
* The attribute matcher.
*
* @var null|self::MATCH_*
* @readonly
*/
public $matcher;

/**
* The attribute value.
*
* @var string|null
* @readonly
*/
public $value;

/**
* The attribute modifier.
*
* @var null|self::MODIFIER_*
* @readonly
*/
public $modifier;

/**
* Constructor.
*
* @param string $name
* @param null|self::MATCH_* $matcher
* @param null|string $value
Expand All @@ -186,4 +118,102 @@ public function __construct( string $name, ?string $matcher = null, ?string $val
$this->value = $value;
$this->modifier = $modifier;
}

/**
* Determines if the processor's current position matches the selector.
*
* @param WP_HTML_Tag_Processor $processor
* @return bool True if the processor's current position matches the selector.
*/
public function matches( WP_HTML_Tag_Processor $processor ): bool {
$att_value = $processor->get_attribute( $this->name );
if ( null === $att_value ) {
return false;
}

if ( null === $this->value ) {
return true;
}

if ( true === $att_value ) {
$att_value = '';
}

$case_insensitive = self::MODIFIER_CASE_INSENSITIVE === $this->modifier;

switch ( $this->matcher ) {
case self::MATCH_EXACT:
return $case_insensitive
? 0 === strcasecmp( $att_value, $this->value )
: $att_value === $this->value;

case self::MATCH_ONE_OF_EXACT:
foreach ( $this->whitespace_delimited_list( $att_value ) as $val ) {
if (
$case_insensitive
? 0 === strcasecmp( $val, $this->value )
: $val === $this->value
) {
return true;
}
}
return false;

case self::MATCH_EXACT_OR_EXACT_WITH_HYPHEN:
// Attempt the full match first
if (
$case_insensitive
? 0 === strcasecmp( $att_value, $this->value )
: $att_value === $this->value
) {
return true;
}

// Partial match
if ( strlen( $att_value ) < strlen( $this->value ) + 1 ) {
return false;
}

$starts_with = "{$this->value}-";
return 0 === substr_compare( $att_value, $starts_with, 0, strlen( $starts_with ), $case_insensitive );

case self::MATCH_PREFIXED_BY:
return 0 === substr_compare( $att_value, $this->value, 0, strlen( $this->value ), $case_insensitive );

case self::MATCH_SUFFIXED_BY:
return 0 === substr_compare( $att_value, $this->value, -strlen( $this->value ), null, $case_insensitive );

case self::MATCH_CONTAINS:
return false !== (
$case_insensitive
? stripos( $att_value, $this->value )
: strpos( $att_value, $this->value )
);
}
}

/**
* Splits a string into a list of whitespace delimited values.
*
* This is useful for the {@see WP_CSS_Attribute_Selector::MATCH_ONE_OF_EXACT} matcher.
*
* @param string $input
*
* @return Generator<string>
*/
private function whitespace_delimited_list( string $input ): Generator {
// Start by skipping whitespace.
$offset = strspn( $input, " \t\r\n\f" );

while ( $offset < strlen( $input ) ) {
// Find the byte length until the next boundary.
$length = strcspn( $input, " \t\r\n\f", $offset );
$value = substr( $input, $offset, $length );

// Move past trailing whitespace.
$offset += $length + strspn( $input, " \t\r\n\f", $offset + $length );

yield $value;
}
}
}

0 comments on commit b7e032e

Please sign in to comment.