Merge pull request #793 from gpodder/remove-advertise
[mygpo.git] / mygpo / api / opml.py
blob3ba5c81515cf05aa712e23faf6736cd1f2f115c9
1 # -*- coding: utf-8 -*-
3 """OPML importer and exporter (based on gPodder's "opml" module)
5 This module contains helper classes to import subscriptions from OPML files on
6 the web and to export a list of podcast objects to valid OPML 2.0 files.
7 """
9 import os
11 import xml.dom.minidom
12 from xml.parsers.expat import ExpatError
13 import email.utils
16 class Importer(object):
17 VALID_TYPES = ("rss", "link")
19 def __init__(self, content):
20 """
21 Parses the OPML feed from the given URL into a local data structure
22 containing podcast metadata.
23 """
24 self.items = []
26 try:
27 doc = xml.dom.minidom.parseString(content)
28 except ExpatError as e:
29 raise ValueError from e
31 for outline in doc.getElementsByTagName("outline"):
32 if (
33 outline.getAttribute("type") in self.VALID_TYPES
34 and outline.getAttribute("xmlUrl")
35 or outline.getAttribute("url")
37 channel = {
38 "url": outline.getAttribute("xmlUrl")
39 or outline.getAttribute("url"),
40 "title": outline.getAttribute("title")
41 or outline.getAttribute("text")
42 or outline.getAttribute("xmlUrl")
43 or outline.getAttribute("url"),
44 "description": outline.getAttribute("text")
45 or outline.getAttribute("xmlUrl")
46 or outline.getAttribute("url"),
49 if channel["description"] == channel["title"]:
50 channel["description"] = channel["url"]
52 for attr in ("url", "title", "description"):
53 channel[attr] = channel[attr].strip()
55 self.items.append(channel)
58 class Exporter(object):
59 """
60 Helper class to export a list of channel objects to a local file in OPML
61 2.0 format. See www.opml.org for the OPML specification.
62 """
64 def __init__(self, title="my.gpodder.org Subscriptions"):
65 self.title = title
66 self.created = email.utils.formatdate(localtime=True)
68 def generate(self, channels):
69 """
70 Creates a XML document containing metadata for each channel object in
71 the "channels" parameter, which should be a list of channel objects.
73 Returns: An OPML document as string
74 """
75 doc = xml.dom.minidom.Document()
77 opml = doc.createElement("opml")
78 opml.setAttribute("version", "2.0")
79 doc.appendChild(opml)
81 def create_node(name, content):
82 node = doc.createElement(name)
83 node.appendChild(doc.createTextNode(content))
84 return node
86 head = doc.createElement("head")
87 head.appendChild(create_node("title", self.title or ""))
88 head.appendChild(create_node("dateCreated", self.created))
89 opml.appendChild(head)
91 def create_outline(channel):
92 from mygpo.subscriptions.models import SubscribedPodcast
93 from mygpo.podcasts.models import PodcastGroup
95 outline = doc.createElement("outline")
97 if isinstance(channel, SubscribedPodcast):
98 title = channel.podcast.title
99 outline.setAttribute("xmlUrl", channel.ref_url)
100 outline.setAttribute("description", channel.podcast.description or "")
101 outline.setAttribute("type", "rss")
102 outline.setAttribute("htmlUrl", channel.podcast.link or "")
103 elif isinstance(channel, PodcastGroup):
104 title = channel.title
105 for subchannel in channel.podcast_set.all():
106 outline.appendChild(create_outline(subchannel))
107 else:
108 title = channel.title
109 outline.setAttribute("xmlUrl", channel.url)
110 outline.setAttribute("description", channel.description or "")
111 outline.setAttribute("type", "rss")
112 outline.setAttribute("htmlUrl", channel.link or "")
114 outline.setAttribute("title", title or "")
115 outline.setAttribute("text", title or "")
116 return outline
118 body = doc.createElement("body")
119 for channel in channels:
120 body.appendChild(create_outline(channel))
121 opml.appendChild(body)
123 return doc.toprettyxml(encoding="utf-8", indent=" ", newl=os.linesep)