version = $version;
        // Setting default values for essential channel elements
        $this->channels['title'] = $version . ' Feed';
        $this->channels['link'] = 'http://www.ajaxray.com/blog';
        $this->channels["feedUrl"] = "http://example.com/feed";
        // Tag names to encode in CDATA
        $this->CDATAEncoding = ['description', 'content:encoded', 'summary'];
    }
    // Public functions
    /**
     * Set a channel element
     *
     * @param string $elementName Name of the channel tag
     * @param string $content Content of the channel tag
     */
    public function setChannelElement(string $elementName, string|array $content): void
    {
        $this->channels[$elementName] = $content;
    }
    /**
     * Generate the actual RSS/Atom file
     */
    public function generateFeed(): void
    {
        $this->printHead();
        $this->printChannels();
        $this->printItems();
        $this->printTail();
    }
    /**
     * Create a new FeedItem.
     *
     * @return FeedItem An instance of FeedItem class
     */
    public function createNewItem(): FeedItem
    {
        $item = new FeedItem($this->version);
        return $item;
    }
    /**
     * Add a FeedItem to the main class
     *
     * @param FeedItem $feedItem An instance of FeedItem class
     */
    public function addItem(FeedItem $feedItem): void
    {
        $this->items[] = $feedItem;
    }
    // Wrapper functions
    /**
     * Set the 'title' channel element
     *
     * @param string $title Value of 'title' channel tag
     */
    public function setTitle(string $title): void
    {
        $this->setChannelElement('title', $title);
    }
    /**
     * Set the 'description' channel element
     *
     * @param string $description Value of 'description' channel tag
     */
    public function setDescription(string $description): void
    {
        $this->setChannelElement('description', $description);
    }
    /**
     * Set the 'link' channel element
     *
     * @param string $link Value of 'link' channel tag
     */
    public function setLink(string $link): void
    {
        $this->setChannelElement('link', $link);
    }
    /**
     * Set the 'image' channel element
     *
     * @param string $title Title of the image
     * @param string $link Link URL of the image
     * @param string $url Path URL of the image
     */
    public function setImage(string $title, string $link, string $url): void
    {
        $this->setChannelElement('image', ['title' => $title, 'link' => $link, 'url' => $url]);
    }
    /**
     * Set the 'about' channel element. Only for RSS 1.0
     *
     * @param string $url Value of 'about' channel tag
     */
    public function setChannelAbout(string $url): void
    {
        $this->data['ChannelAbout'] = $url;
    }
    // Other functions
    /**
     * Generates a UUID
     *
     * @param string $key An optional prefix
     * @param string $prefix A prefix
     * @return string The formatted UUID
     */
    public static function uuid(?string $key = null, string $prefix = ''): string
    {
        $key = $key ?? uniqid((string)rand());
        $chars = md5($key);
        $uuid = substr($chars, 0, 8) . '-';
        $uuid .= substr($chars, 8, 4) . '-';
        $uuid .= substr($chars, 12, 4) . '-';
        $uuid .= substr($chars, 16, 4) . '-';
        $uuid .= substr($chars, 20, 12);
        return $prefix . $uuid;
    }
    // Private functions
    /**
     * Prints the XML and RSS namespace
     */
    private function printHead(): void
    {
        $out = '' . "\n";
        if ($this->version == RSS2)
        {
            $out .= '' . PHP_EOL;
        }
        elseif ($this->version == RSS1)
        {
            $out .= '' . PHP_EOL;
        }
        elseif ($this->version == ATOM)
        {
            $out .= '' . PHP_EOL;
        }
        echo $out;
    }
    /**
     * Closes the open tags at the end of the file
     */
    private function printTail(): void
    {
        if ($this->version == RSS2)
        {
            echo '' . PHP_EOL . '';
        }
        elseif ($this->version == RSS1)
        {
            echo '';
        }
        elseif ($this->version == ATOM)
        {
            echo '';
        }
    }
    /**
     * Creates a single node in XML format
     *
     * @param string $tagName Name of the tag
     * @param mixed $tagContent Tag value as a string or an array of nested tags in 'tagName' => 'tagValue' format
     * @param array|null $attributes Attributes (if any) in 'attrName' => 'attrValue' format
     * @return string Formatted XML tag
     */
    private function makeNode(string $tagName, $tagContent, ?array $attributes = null): string
    {
        $nodeText = '';
        $attrText = '';
        if (is_array($attributes))
        {
            foreach ($attributes as $key => $value)
            {
                $attrText .= " $key=\"$value\"";
            }
        }
        if (is_array($tagContent) && $this->version == RSS1)
        {
            $attrText = ' rdf:parseType="Resource"';
        }
        $attrText .= (in_array($tagName, $this->CDATAEncoding) && $this->version == ATOM) ? ' type="html" ' : '';
        $nodeText .= (in_array($tagName, $this->CDATAEncoding)) ? "<{$tagName}{$attrText}>";
        if (is_array($tagContent))
        {
            foreach ($tagContent as $key => $value)
            {
                $nodeText .= $this->makeNode($key, $value);
            }
        }
        else
        {
            $nodeText .= (in_array($tagName, $this->CDATAEncoding)) ? $tagContent : htmlentities($tagContent);
        }
        $nodeText .= (in_array($tagName, $this->CDATAEncoding)) ? "]]>$tagName>" : "$tagName>";
        return $nodeText . PHP_EOL;
    }
    /**
     * Prints channel elements
     */
    private function printChannels(): void
    {
        // Start channel tag
        switch ($this->version)
        {
            case RSS2:
                echo '' . PHP_EOL;
                break;
            case RSS1:
                echo (isset($this->data['ChannelAbout'])) ? "data['ChannelAbout']}\">" : "channels['link']}\">";
                break;
        }
        // Print items of channel
        foreach ($this->channels as $key => $value)
        {
            if ($this->version == ATOM && $key == 'link')
            {
                // ATOM prints the link element as an href attribute
                echo $this->makeNode($key, '', ['href' => $value]);
                // Add the id for ATOM
                echo $this->makeNode('id', $this->uuid($value, 'urn:uuid:'));
            }
            else if ($this->version == ATOM && $key == 'feedUrl')
            {
                echo $this->makeNode('link', '', ['rel' => 'self', 'href' => $value]);
            }
            else
            {
                echo $this->makeNode($key, $value);
            }
        }
        // RSS 1.0 has a special tag  with channel
        if ($this->version == RSS1)
        {
            echo "" . PHP_EOL . "" . PHP_EOL;
            foreach ($this->items as $item)
            {
                $thisItems = $item->getElements();
                echo "" . PHP_EOL;
            }
            echo "" . PHP_EOL . "" . PHP_EOL . "" . PHP_EOL;
        }
    }
    /**
     * Prints formatted feed items
     */
    private function printItems(): void
    {
        foreach ($this->items as $item)
        {
            $thisItems = $item->getElements();
            // The argument is printed as rdf:about attribute of item in RSS 1.0
            echo $this->startItem($thisItems['link']['content']);
            foreach ($thisItems as $feedItem)
            {
                echo $this->makeNode($feedItem['name'], $feedItem['content'], $feedItem['attributes']);
            }
            echo $this->endItem();
        }
    }
    /**
     * Makes the starting tag of items
     *
     * @param string|false $about The value of about tag, which is used only for RSS 1.0
     */
    private function startItem($about = false): void
    {
        if ($this->version == RSS2)
        {
            echo '- ' . PHP_EOL;
        }
        elseif ($this->version == RSS1)
        {
            if ($about)
            {
                echo "
- " . PHP_EOL;
            }
            else
            {
                die("link element is not set.\n It's required for RSS 1.0 to be used as the about attribute of the item");
            }
        }
        elseif ($this->version == ATOM)
        {
            echo "" . PHP_EOL;
        }
    }
    /**
     * Closes the feed item tag
     */
    private function endItem(): void
    {
        if ($this->version == RSS2 || $this->version == RSS1)
        {
            echo '
 ' . PHP_EOL;
        }
        elseif ($this->version == ATOM)
        {
            echo "" . PHP_EOL;
        }
    }
    /**
     * Set the Feed URL
     * @param string $string - The URL of the feed
     * @return void
     */
    public function setFeedURL(string $string): void
    {
        $this->setChannelElement("feedUrl", $string);
    }
}
// Define constants for RSS 1.0, RSS 2.0, and Atom
const RSS1 = 'RSS 1.0';
const RSS2 = 'RSS 2.0';
const ATOM = 'ATOM';