*/ public static function parse( mixed $value ): array { if ( is_string( $value ) ) { $parts = preg_split( '/[,、\r\n]+/u', $value ); return self::normalize( is_array( $parts ) ? $parts : array() ); } return is_array( $value ) ? self::normalize( array_values( $value ) ) : array(); } /** * Normalize, deduplicate and bound brand names. * * @param list $values Raw values. * * @return list */ public static function normalize( array $values ): array { $brands = array(); $seen = array(); foreach ( $values as $value ) { if ( ! is_string( $value ) ) { continue; } $brand = preg_replace( '/[\x00-\x1F\x7F]/u', '', trim( $value ) ); if ( ! is_string( $brand ) || $brand === '' ) { continue; } $brand = self::truncate( $brand ); $key = function_exists( 'mb_strtolower' ) ? mb_strtolower( $brand, 'UTF-8' ) : strtolower( $brand ); if ( isset( $seen[ $key ] ) ) { continue; } $seen[ $key ] = true; $brands[] = $brand; if ( count( $brands ) >= self::MAX_COUNT ) { break; } } return $brands; } /** * Match complete brand names or parenthesized aliases, never title keywords. * * @param string $requested Requested priority brand. * @param string|null $actual Brand returned in product metadata. */ public static function matches( string $requested, ?string $actual ): bool { $actualNames = self::matchingNames( $actual ?? '' ); foreach ( self::matchingNames( $requested ) as $name ) { foreach ( $actualNames as $actualName ) { if ( preg_match( '/\A' . preg_quote( $name, '/' ) . '\z/iu', $actualName ) === 1 ) { return true; } } } return false; } /** * Preserve word boundaries while accepting labels such as Anker(アンカー). * * @param string $brand Brand label. * * @return list */ private static function matchingNames( string $brand ): array { $normalized = preg_replace( '/\s+/u', ' ', $brand ); if ( ! is_string( $normalized ) || trim( $normalized ) === '' ) { return array(); } $normalized = trim( $normalized ); $names = array( $normalized ); if ( preg_match( '/\A([^()()]+)[((]([^()()]+)[))]\z/u', $normalized, $parts ) === 1 ) { foreach ( array( $parts[1], $parts[2] ) as $part ) { if ( trim( $part ) !== '' ) { $names[] = trim( $part ); } } } return $names; } /** * Return the repository key used to isolate one brand snapshot. * * @param string $brand Normalized brand. */ public static function storageKey( string $brand ): string { $normalized = self::normalize( array( $brand ) ); return self::STORAGE_PREFIX . ( $normalized[0] ?? '' ); } /** * Resolve a brand name from a repository key. * * @param string $key Stored collection key. */ public static function fromStorageKey( string $key ): ?string { if ( ! str_starts_with( $key, self::STORAGE_PREFIX ) ) { return null; } $brand = substr( $key, strlen( self::STORAGE_PREFIX ) ); return $brand === '' ? null : $brand; } /** * Truncate a brand without splitting a multibyte character. * * @param string $brand Brand name. */ private static function truncate( string $brand ): string { if ( function_exists( 'mb_substr' ) ) { return mb_substr( $brand, 0, self::MAX_LENGTH, 'UTF-8' ); } return substr( $brand, 0, self::MAX_LENGTH ); } }