1 # -*- coding: iso-8859-1 -*- 2 """ 3 MoinMoin - SharedContent macro, based on the FeedReader macro 4 5 @copyright: 2008, 2012, 2013 by Paul Boddie <paul@boddie.org.uk> 6 @license: GNU GPL (v2 or later), see COPYING.txt for details. 7 """ 8 9 from MoinMoin.Page import Page 10 from MoinRemoteSupport import * 11 from MoinSupport import parseMacroArguments 12 import xml.dom.pulldom 13 14 try: 15 from cStringIO import StringIO 16 except ImportError: 17 from StringIO import StringIO 18 19 Dependencies = ["time"] 20 21 MAX_ENTRIES = 5 22 ATOM_NS = "http://www.w3.org/2005/Atom" 23 24 def text(element): 25 nodes = [] 26 for node in element.childNodes: 27 if node.nodeType == node.TEXT_NODE: 28 nodes.append(node.nodeValue) 29 return "".join(nodes) 30 31 def unescape(text): 32 return text.replace("<", "<").replace(">", ">").replace("&", "&") 33 34 def linktext(element, feed_type): 35 if feed_type == "rss": 36 return text(element) 37 else: 38 return element.getAttribute("href") 39 40 def execute(macro, args): 41 request = macro.request 42 fmt = macro.formatter 43 _ = request.getText 44 45 feed_url = None 46 show_content = None 47 max_entries = None 48 49 for arg, value in parseMacroArguments(args): 50 if arg == "url": 51 feed_url = value 52 elif arg == "show": 53 show_content = value in ("true", "True", "yes") 54 elif arg == "limit": 55 try: 56 max_entries = int(value) 57 except ValueError: 58 return fmt.text(_("SharedContent: limit must be set to the maximum number of entries to be shown")) 59 60 if not feed_url: 61 return fmt.text(_("SharedContent: a feed URL must be specified")) 62 63 show_content = show_content or False 64 max_entries = max_entries or MAX_ENTRIES 65 66 # Obtain the resource, using a cached version if appropriate. 67 68 max_cache_age = int(getattr(request.cfg, "moin_share_max_cache_age", "300")) 69 data = getCachedResource(request, feed_url, "MoinShare", "wiki", max_cache_age) 70 if not data: 71 return fmt.text(_("SharedContent: updates could not be retrieved for %s") % feed_url) 72 73 feed = StringIO(data) 74 75 _url, content_type, _encoding, _metadata = getCachedResourceMetadata(feed) 76 77 if content_type not in ("application/atom+xml", "application/rss+xml"): 78 return fmt.text(_("SharedContent: updates for %s were not provided in Atom or RSS format") % feed_url) 79 80 try: 81 # Parse each node from the feed. 82 83 title = link = content = content_type = None 84 channel_title = channel_link = None 85 86 output = [] 87 append = output.append 88 89 feed_type = None 90 in_item = False 91 nentries = 0 92 93 events = xml.dom.pulldom.parse(feed) 94 95 if not show_content: 96 append(fmt.bullet_list(on=1)) 97 98 for event, value in events: 99 100 if event == xml.dom.pulldom.START_ELEMENT: 101 tagname = value.localName 102 103 # Detect the feed type and items. 104 105 if tagname == "feed" and value.namespaceURI == ATOM_NS: 106 feed_type = "atom" 107 108 elif tagname == "rss": 109 feed_type = "rss" 110 111 # Detect items. 112 113 elif feed_type == "rss" and tagname == "item" or \ 114 feed_type == "atom" and tagname == "entry": 115 116 in_item = True 117 118 elif tagname == "title": 119 events.expandNode(value) 120 if in_item: 121 title = value 122 else: 123 channel_title = value 124 125 elif tagname == "link": 126 events.expandNode(value) 127 if in_item: 128 link = value 129 else: 130 channel_link = value 131 132 elif feed_type == "atom" and tagname == "content": 133 events.expandNode(value) 134 if in_item: 135 content = value 136 content_type = value.getAttribute("type") 137 138 elif event == xml.dom.pulldom.END_ELEMENT: 139 tagname = value.localName 140 141 if feed_type == "rss" and tagname == "item" or \ 142 feed_type == "atom" and tagname == "entry": 143 144 in_item = False 145 146 # Emit content where appropriate. 147 # NOTE: HTML should be sanitised. 148 149 if show_content: 150 if content and content_type == "html": 151 append(fmt.rawHTML(unescape(text(content)))) 152 153 # Or emit title and link information for items. 154 155 elif title and link and nentries < max_entries: 156 link_text = linktext(link, feed_type) 157 158 append(fmt.listitem(on=1)) 159 append(fmt.url(on=1, href=link_text)) 160 append(fmt.icon('www')) 161 append(fmt.text(" " + text(title))) 162 append(fmt.url(on=0)) 163 append(fmt.listitem(on=0)) 164 165 title = link = content = content_type = None 166 nentries += 1 167 168 if not show_content: 169 append(fmt.bullet_list(on=0)) 170 171 if channel_title and channel_link: 172 channel_link_text = linktext(channel_link, feed_type) 173 174 append(fmt.paragraph(on=1)) 175 append(fmt.url(on=1, href=channel_link_text)) 176 append(fmt.text(text(channel_title))) 177 append(fmt.url(on=0)) 178 append(fmt.text(" ")) 179 append(fmt.url(on=1, href=feed_url)) 180 append(fmt.icon('rss')) 181 append(fmt.url(on=0)) 182 append(fmt.paragraph(on=0)) 183 184 finally: 185 feed.close() 186 187 return ''.join(output) 188 189 # vim: tabstop=4 expandtab shiftwidth=4