Podzieh Teil 1: Einlesen eines RSS-Feeds für Podcasts

So nun wird es ernst. Willkommen zum ersten Teil des Entwicklungstagebuches für meinen Podcatcher “Podzieh”. Die Anforderungen an das Programm habe ich im letzten Post bereits aufgeführt. Ich werde hier, in hoffentlich regelmäßigen Abständen, über aktuelle Punkte der Implementierung des Programmes schreiben. Und ich würde sagen, beginnen wir mal mit dem wichtigsten Punkt für einen Podcatcher, das Abrufen und Einlesen des RSS-Feeds.

Beispiele für RSS-Feeds habe ich im Post “Podcasts für Entwickler und für nebenbei” genügend geliefert, diese Feeds dienen mir als Referenz für mein Programm. Ich hoffe damit decke ich den Großteil der Möglichkeiten ab. In meinem Programm lege ich mich auf RSS-Feeds der Version 2.0 fest, da alle Feeds die ich bisher gefunden haben auch diese Version hatten. Einen guten Einstieg in den Aufbau eines RSS bietet w3schools.com/rss. Beim RSS Format handelt es sich im Grunde genommen nur um ein genau definiertes XML-Schema. Leider reicht manchen diese Vordefinierte Struktur nicht aus, und so kommen dann Erweiterungen, wie zum Beispiel die für iTunes, dazu. Da es sich einfach nur um XML handelt, kann ich solch einen Feed mit den XML Klassen des dotNet Frameworks einlesen. Um die Erweiterungen zu Nutzen muss ich allerdings beim Analysieren des XML auf die Xml-Namespaces achten. Achja noch kurz zu Anmerkung, das Programm wird mit dem dotNet Framework 4 Client Profile entwickelt.

Zur Verarbeitung des RSS-Feeds müssen nur zwei Klassen angelegt werden. Eine für den “Channel”, dieser Beschreibt den Podcast im allgemeinen, und eine Klasse für das “Item”. Das Item stellt eine Episode im Podcast dar und kommt somit mehrfach als Kindelement des Channel vor. Damit keine fehlerhaften Objekte zustande kommen können, kann eine Instanz der Klassen nur über eine Factory-Methode erstellt werden. So kann ich zum Beispiel beeinflussen, was passiert wenn es sich nicht um einen RSS-Feed mit Version 2.0 handelt. Eingelesen wird das XML mit LINQ und der XDocument Klasse. Eine wirklich schöne Hilfe beim einlesen des XML ist die automatische Konvertierung des Inhaltes eines XElement bzw. XAttribute. Man muss nur den entsprechenden cast davor schreiben. Wenn man das ganz dann auch noch mit Nullable-Typen verbindet kann man sich etliche if Abfragen sparen.

public class Channel
{
    #region Namespaces

    public static XNamespace NsItunes = XNamespace.Get("http://www.itunes.com/dtds/podcast-1.0.dtd");
    public static XNamespace NsMedia = XNamespace.Get("http://search.yahoo.com/mrss/");

    #endregion

    #region Properties

    public string Title { get; protected set; }
    public string Description { get; protected set; }
    public string URL { get; protected set; }
    public IEnumerable<Item> Items { get; protected set; }

    public string LocalFolder { get; set; }

    #endregion

    #region Init

    protected Channel() { }

    public static Channel LoadFromFeed(string url)
    {
        XDocument doc = XDocument.Load(url);
        if(doc == null) return null;
        XElement rss = doc.Element("rss");
        if (rss == null || (string)rss.Attribute("version") != "2.0")
            return null;

        XElement channel = rss.Element("channel");
        if (channel == null) return null;

        Channel c = new Channel()
        {
            URL = url,
            Title = (string)channel.Element("title"),
            Description = (string)channel.Element("description")
        };

        c.Items = channel.Elements("item")
                            .Select(i => Item.LoadFromXml(i))
                            .Where(i => !String.IsNullOrEmpty(i.Download) && !String.IsNullOrEmpty(i.Title));

        return c;
    }

    #endregion
}

Die Liste mit den Items erstelle ich mit Hilfe der LINQ Extension Methoden. Mit “Elements()” geh ich alle Items durch. Auf diese Enumeration wird ein “Select” angewedet, welches aus den XML-Items eine Item-Instanz meiner Klasse macht. Im Anschluss daran, Filter ich gleich die mit raus, bei denen ich keinen Download gefunden habe.

public class Item
{
    #region Properties

    public string Title { get; protected set; }
    public string Description { get; protected set; }
    public string Guid { get; protected set; }
    public string Author { get; protected set; }
    public string Download { get; protected set; }
    public long? DownloadSize { get; protected set; }

    public DateTime? PubDate { get; protected set; }

    public string LocalFile { get; set; }

    #endregion

    #region Init

    protected Item() { }

    public static Item LoadFromXml(XElement el)
    {
        Item i = new Item()
        {
            Title = (string)el.Element("title"),
            Description = (string)el.Element("description"),
            Guid = (string)el.Element("guid"),
            Author = (string)el.Element("author")
        };

        if (String.IsNullOrEmpty(i.Guid))
            i.Guid = (string)el.Element("link");

        if(el.Element("pubDate") != null)
            i.PubDate = (DateTime?)el.Element("pubDate");

        if (i.Description == String.Empty)
        {   // wenn in description nichts drin steht, ist vieleicht im itunes-tag summary was drin
            i.Description = (string)el.Element(Channel.NsItunes + "summary");
        }

        if (el.Element("enclosure") != null)
        {
            i.Download = (string)el.Element("enclosure").Attribute("url");
            i.DownloadSize = (long?)el.Element("enclosure").Attribute("length");
            if (i.DownloadSize.HasValue && i.DownloadSize.Value == 0)
                i.DownloadSize = null;
        }

        if(String.IsNullOrEmpty(i.Download) || !i.DownloadSize.HasValue)
        {   // wenn in enclosure nichts steht, steht das vieleicht in einem media:content-tag
            XElement elMedia = el.Element(Channel.NsMedia + "content");
            if (elMedia != null && elMedia.Attribute("url") != null)
            {
                i.Download = (string)elMedia.Attribute("url");
                i.DownloadSize = (long?)elMedia.Attribute("fileSize");
                if (i.DownloadSize.HasValue && i.DownloadSize.Value == 0)
                    i.DownloadSize = null;
            }
        }

        return i;
    }

    #endregion
}

Wenn ich beim Einlesen eines Items keinen Download finde, versuche ich es mit dem Element media:content. Hier steht im Grunde das gleiche drin wie im enclosure-Element. Die Guid wird verwendet um die Episode eindeutig zu Identifizieren und später mit einer Datenbank abzugleichen. Die Properties “LocalFolder” und “LocalFile” habe ich angelegt um hier nach dem einlesen des Feeds, den Pfad zur lokalen Instanz einzutragen. Mehr dazu in einem späteren Post.

  1. Hinterlasse einen Kommentar

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit deinem WordPress.com-Konto. Abmelden /  Ändern )

Facebook-Foto

Du kommentierst mit deinem Facebook-Konto. Abmelden /  Ändern )

Verbinde mit %s

%d Bloggern gefällt das: