5 __copyright__
= "Copyright (c) 2002-2005 Free Software Foundation, Inc."
6 __author__
= "Juri Pakaste <juri@iki.fi>"
8 Straw is free software; you can redistribute it and/or modify it under the
9 terms of the GNU General Public License as published by the Free Software
10 Foundation; either version 2 of the License, or (at your option) any later
13 Straw is distributed in the hope that it will be useful, but WITHOUT ANY
14 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
15 A PARTICULAR PURPOSE. See the GNU General Public License for more details.
17 You should have received a copy of the GNU General Public License along with
18 this program; if not, write to the Free Software Foundation, Inc., 59 Temple
19 Place - Suite 330, Boston, MA 02111-1307, USA. """
21 from Fetcher
import FetchTask
22 from JobManager
import Job
, TaskThread
, JobHandler
23 from model
import Category
, Feed
24 from xml
.sax
import saxutils
, make_parser
, SAXParseException
25 from xml
.sax
.handler
import feature_namespaces
, feature_namespace_prefixes
26 from xml
.sax
.saxutils
import XMLGenerator
27 from xml
.sax
.xmlreader
import AttributesImpl
31 import straw
.JobManager
as JobManager
34 import xml
.sax
._exceptions
35 import xml
.sax
.handler
37 class OpmlImportJobHandler(JobHandler
):
38 job_id
= "opml-import"
41 "opml-imported" : (gobject
.SIGNAL_RUN_LAST
, gobject
.TYPE_NONE
, (gobject
.TYPE_PYOBJECT
,))
44 def __init__(self
, id, job
):
45 JobHandler
.__init
__(self
, id, job
)
47 def _on_url_fetched(self
, handler
, task_result
):
48 self
.task_queue
.put((task_result
.task
.user_data
, task_result
.result
))
51 fetch_task
= Fetcher
.create_task(url
= self
.job
.url
, user_data
= None)
52 fetch_result
= fetch_task
.fetch()
54 if not fetch_result
.error
:
55 opml
= read(fetch_result
.content
)
56 tree
= self
._build
_tree
(opml
.roots(), parent
= self
.job
.category
)
57 self
._notify
("opml-imported", tree
)
59 def _build_tree(self
, outlines
, parent
= None):
63 for outline
in outlines
:
64 if not outline
.has_key("type"):
65 # Some feeds exported from Liferea don't have "type" attribute.
66 outline
["type"] = "rss"
68 if outline
["type"] == "folder" or len(outline
.children
) > 0:
71 category
.name
= outline
["text"]
72 category
.parent
= parent
74 save_list
.append(category
)
76 if not outline
.children
:
79 save_list
.extend(self
._build
_tree
(outline
.children
, category
))
84 if outline
.has_key("title"):
85 feed
.title
= outline
["title"]
86 elif outline
.has_key("text"):
87 feed
.title
= outline
["text"]
89 feed
.title
= "[unknown title]"
94 if outline
.has_key("xmlUrl"):
95 feed
.location
= outline
["xmlUrl"]
96 elif outline
.has_key("url"):
97 feed
.location
= outline
["url"]
99 if outline
.has_key("htmlUrl"):
100 feed
.link
= outline
["htmlUrl"]
101 elif outline
.has_key("url"):
102 feed
.link
= outline
["url"]
106 save_list
.append(feed
)
112 class OpmlImportJob(Job
):
113 def __init__(self
, url
, category
, observers
):
114 Job
.__init
__(self
, "opml-import")
116 self
.observers
= observers
118 self
.category
= category
120 JobManager
.register_handler(OpmlImportJobHandler
)
122 def import_opml(url
, category
, observers
):
123 job
= OpmlImportJob(url
, category
, observers
)
124 JobManager
.start(job
)
130 def output(self
, stream
= sys
.stdout
):
131 xg
= XMLGenerator(stream
, encoding
='utf-8')
132 def elemWithContent(name
, content
):
133 xg
.startElement(name
, AttributesImpl({}))
134 if content
is not None:
135 xg
.characters(content
)
138 xg
.startElement("opml", AttributesImpl({'version': '1.1'}))
139 xg
.startElement("head", AttributesImpl({}))
140 for key
in ('title', 'dateCreated', 'dateModified', 'ownerName',
141 'ownerEmail', 'expansionState', 'vertScrollState',
142 'windowTop', 'windowBotton', 'windowRight', 'windowLeft'):
143 if self
.has_key(key
) and self
[key
] != "":
144 elemWithContent(key
, self
[key
])
145 xg
.endElement("head")
146 xg
.startElement("body", AttributesImpl({}))
147 for o
in self
.outlines
:
149 xg
.endElement("body")
150 xg
.endElement("opml")
153 __slots__
= ('_children')
158 def add_child(self
, outline
):
159 self
._children
.append(outline
)
161 def get_children_iter(self
):
162 return self
.OIterator(self
)
164 children
= property(get_children_iter
, None, None, "")
166 def output(self
, xg
):
167 xg
.startElement("outline", AttributesImpl(self
))
168 for c
in self
.children
:
170 xg
.endElement("outline")
174 def __init__(self
, o
):
182 return len(self
._o
._children
)
186 if self
._index
< len(self
._o
._children
):
187 return self
._o
._children
[self
._index
]
191 class OutlineList(object):
196 def add_outline(self
, outline
):
198 self
._stack
[-1].add_child(outline
)
200 self
._roots
.append(outline
)
201 self
._stack
.append(outline
)
203 def close_outline(self
):
210 class OPMLHandler(xml
.sax
.handler
.ContentHandler
):
212 self
._outlines
= OutlineList()
216 def startElement(self
, name
, attrs
):
217 if self
._opml
is None:
219 raise ValueError, "This doesn't look like OPML"
221 if name
== 'outline':
224 self
._outlines
.add_outline(o
)
227 def endElement(self
, name
):
228 if name
== 'outline':
229 self
._outlines
.close_outline()
232 self
._opml
.outlines
= self
._outlines
.roots()
234 for key
in ('title', 'dateCreated', 'dateModified', 'ownerName',
235 'ownerEmail', 'expansionState', 'vertScrollState',
236 'windowTop', 'windowBotton', 'windowRight', 'windowLeft'):
238 self
._opml
[key
] = self
._content
241 def characters(self
, ch
):
247 def get_outlines(self
):
248 return self
._outlines
251 """parser = make_parser()
252 parser.setFeature(feature_namespaces, 0)
253 handler = OPMLHandler()
254 parser.setContentHandler(handler)"""
255 handler
= OPMLHandler()
256 xml
.sax
.parseString(stream
, handler
)
257 print handler
.get_outlines()
258 return handler
.get_outlines()
260 def export(root
, filename
):
262 opml
['title'] = "Exported from Straw"
264 def _export(node
, opml
):
268 o
['text'] = node
.title
.encode('utf-8')
269 o
['description'] = node
.title
.encode('utf-8')
270 o
['htmlUrl'] = node
.link
271 o
['language'] = 'unknown'
272 o
['title'] = node
.title
.encode('utf-8')
275 o
['xmlUrl'] = node
.location
276 elif node
.type == "C":
277 o
['text'] = node
.name
.encode('utf-8')
278 o
['description'] = node
.name
.encode('utf-8')
281 for child_node
in node
.children
:
282 o
.add_child(_export(child_node
, opml
))
286 opml
.outlines
.append(_export(root
, opml
))
288 f
= gnomevfs
.create(filename
, gnomevfs
.OPEN_WRITE
, 0)
289 f
.write('<!DOCTYPE opml PUBLIC "-//Userland//DTD OPML XML V1.0//EN" ' + \
290 '"http://static.userland.com/gems/radiodiscuss/opmlDtd.txt">')
291 f
.write('<?xml version="1.0"?>\n')
295 class BlogListEntry(object):
296 __slots__
= ('text', 'url')
298 def _find_entries(outline
):
300 for c
in outline
.children
:
301 entries
+= _find_entries(c
)
302 type = outline
.get('type', '')
303 text
= outline
.get('text', '')
306 url
= outline
.get('url', '')
312 xmlurl
= outline
.get('xmlUrl', '')
316 title
= outline
.get('title', '')
322 # there's something in xmlurl. There's a good chance that's
326 htmlurl
= outline
.get('htmlUrl', '')
328 # there's something in htmlurl, and xmlurl is empty. This
329 # might be our feed's URL.
332 # nothing else to try.
338 def find_entries(outlines
):
341 entries
+= _find_entries(o
)
347 entries
= find_entries(o
.outlines
)
353 edict
[ek
] = edict
.get(ek
, 0) + 1