$baseDir CSS 가 놓인 디렉토리 세그먼트 * @return array{0: string, 1: string}|null `[확장 기준 경로, 프래그먼트]` 또는 대상 아님이면 null */ private static function resolve(string $ref, array $baseDir): ?array { if ($ref === '') { return null; } // 루트 상대(`/x`) · 프로토콜 상대(`//host/x`) · 스킴 참조(`https:`, `data:`, `#`) 는 그대로 둔다. if ($ref[0] === '/' || $ref[0] === '#' || preg_match('/^[a-zA-Z][a-zA-Z0-9+.-]*:/', $ref) === 1) { return null; } // 프래그먼트는 보존하고(레거시 `#iefix` 등), 참조 자신의 쿼리는 버린다 — // 생성되는 자산 URL 이 자기 캐시 버전 쿼리를 갖는다. $fragment = ''; if (($hash = strpos($ref, '#')) !== false) { $fragment = substr($ref, $hash); $ref = substr($ref, 0, $hash); } if (($q = strpos($ref, '?')) !== false) { $ref = substr($ref, 0, $q); } if ($ref === '') { return null; } $segments = $baseDir; foreach (explode('/', $ref) as $segment) { if ($segment === '' || $segment === '.') { continue; } if ($segment === '..') { array_pop($segments); continue; } $segments[] = $segment; } if ($segments === []) { return null; } return [implode('/', $segments), $fragment]; } /** * CSS 경로가 놓인 디렉토리 세그먼트를 구합니다. * * @param string $cssPath 확장 기준 CSS 경로 * @return array 디렉토리 세그먼트 (루트면 빈 배열) */ private static function baseDirectory(string $cssPath): array { $parts = explode('/', trim(str_replace('\\', '/', $cssPath), '/')); array_pop($parts); return array_values(array_filter($parts, static fn (string $p): bool => $p !== '' && $p !== '.')); } }