public function resolveRelativePath(string $basePath, string $relativePath) : string
    {
        if (strpos($relativePath, 'data://') === 0) {
            return $relativePath;
        }

        list($basePathStreamWrapper, $basePath) = $this->splitStreamWrapperAndPath($basePath);
        list($relativeStreamWrapper, $relativePathWithoutStreamWrapper) = $this->splitStreamWrapperAndPath($relativePath);

        $isAbsolutePath = $relativeStreamWrapper !== null;

        if ($isAbsolutePath) {
            return $relativePath;
        } else {
            $relativePath = $relativePathWithoutStreamWrapper;
        }

        $basePath     = str_replace('\\', '/', $basePath);
        $relativePath = str_replace('\\', '/', $relativePath);

        if (substr($basePath, -1) !== '/') {
            $basePath .= '/';
        }

        if (strpos($relativePath, '.') === false) {
            return ($basePathStreamWrapper ? $basePathStreamWrapper . '://' : '')
            . PathHelper::normalize(str_replace('//', '/', $basePath . '/' . $relativePath));
        }

        $parts          = explode('/', $basePath);
        $relativeParts  = explode('/', trim($relativePath, '/'));
        $isDrivePath    = substr($basePath, 1, 1) === ':';
        $precedingSlash = $basePathStreamWrapper || $isDrivePath ? '' : '/';
        $trailingSlash  = !$basePathStreamWrapper && substr($relativePath, -1) === '/' ? '/' : '';

        foreach ($parts as $key => $part) {
            if ($part === '') {
                unset($parts[$key]);
            }
        }

        foreach ($relativeParts as $part) {
            if ($part === '.') {
                continue;
            } elseif ($part === '..') {
                array_pop($parts);
            } elseif ($part !== '') {
                $parts[] = $part;
            }
        }

        return ($basePathStreamWrapper ? $basePathStreamWrapper . '://' : '')
        . PathHelper::normalize($precedingSlash . implode('/', $parts) . ($parts ? $trailingSlash : ''));
    }