public function linkify($value, $protocols = ['http', 'mail'], array $attributes = [], $mode = 'normal')
    {
        if (!isset($value)) {
            return null;
        }
        
        // Link attributes
        $attr = '';
        foreach ($attributes as $key => $val) {
            $attr .= ' ' . $key . '="' . htmlentities($val) . '"';
        }
        
        $links = [];
        
        // Extract existing links and tags
        $text = preg_replace_callback('~(<a .*?>.*?</a>|<.*?>)~i', function ($match) use (&$links) {
            return '<' . array_push($links, $match[1]) . '>';
        }, $value);
        
        // Extract text links for each protocol
        foreach ((array)$protocols as $protocol) {
            switch ($protocol) {
                case 'http':
                case 'https':   $text = $this->linkifyHttp($protocol, $text, $links, $attr, $mode); break;
                case 'mail':    $text = $this->linkifyMail($text, $links, $attr); break;
                default:        $text = $this->linkifyOther($protocol, $text, $links, $attr, $mode); break;
            }
        }
        
        // Insert all link
        return preg_replace_callback('/<(\d+)>/', function ($match) use (&$links) {
            return $links[$match[1] - 1];
        }, $text);
    }