public static function parse($msg)
    {
        $msg = preg_replace_callback(
            '/<(\/)?(.+?)(?: (.+?))?>/',
            function ($matches) {
                $tag = $matches[2];
                $closingTag = $matches[1] == '/';
                $attributes = isset($matches[3]) ? $matches[3] : null;

                if (isset(self::$tags[$tag])) {
                    return self::$formatting[self::$tags[$tag]];
                } else if ($tag == 'color') {
                    if ($closingTag) {
                        return "\x03";
                    } else if (!$closingTag && !$attributes) {
                        throw new Exception('<color> has no attributes set.');
                    }

                    $colors = [];
                    if (0 === preg_match('/fg=["|\'](.+?)["|\'](?: bg=["|\'](.+?)["|\'])?/', $attributes, $colors)) {
                        throw new Exception('<color> has invalid attributes set.');
                    }

                    $fg = $colors[1];
                    if (isset(self::$colors[$fg])) {
                        $fg = self::$colors[$fg];
                    } else {
                        throw new Exception('Invalid foreground color "'.$fg.'".');
                    }

                    if (isset($colors[2])) {
                        $bg = $colors[2];
                        if (isset(self::$colors[$bg])) {
                            $bg = ','.self::$colors[$bg];
                        } else {
                            throw new Exception('Invalid background color "'.$bg.'".');
                        }
                    } else {
                        $bg = '';
                    }

                    return "\x03".$fg.$bg;
                } else {
                    return null;
                }
            },
            $msg
        );

        return $msg;
    }