%PDF- %PDF-
Direktori : /lib/calibre/calibre/gui2/dialogs/ |
Current File : //lib/calibre/calibre/gui2/dialogs/opml.py |
#!/usr/bin/env python3 __license__ = 'GPL v3' __copyright__ = '2014, Kovid Goyal <kovid at kovidgoyal.net>' from collections import defaultdict, namedtuple from operator import itemgetter from qt.core import ( QDialog, QFormLayout, QHBoxLayout, QLineEdit, QToolButton, QIcon, QDialogButtonBox, Qt, QSpinBox, QCheckBox) from lxml import etree from calibre.gui2 import choose_files, error_dialog from calibre.utils.xml_parse import safe_xml_fromstring from calibre.utils.icu import sort_key Group = namedtuple('Group', 'title feeds') def uniq(vals, kmap=lambda x:x): ''' Remove all duplicates from vals, while preserving order. kmap must be a callable that returns a hashable value for every item in vals ''' vals = vals or () lvals = (kmap(x) for x in vals) seen = set() seen_add = seen.add return tuple(x for x, k in zip(vals, lvals) if k not in seen and not seen_add(k)) def import_opml(raw, preserve_groups=True): root = safe_xml_fromstring(raw) groups = defaultdict(list) ax = etree.XPath('ancestor::outline[@title or @text]') for outline in root.xpath('//outline[@type="rss" and @xmlUrl]'): url = outline.get('xmlUrl') parent = outline.get('title', '') or url title = parent if ('title' in outline.attrib and parent) else None if preserve_groups: for ancestor in ax(outline): if ancestor.get('type', None) != 'rss': text = ancestor.get('title') or ancestor.get('text') if text: parent = text break groups[parent].append((title, url)) for title in sorted(groups, key=sort_key): yield Group(title, uniq(groups[title], kmap=itemgetter(1))) class ImportOPML(QDialog): def __init__(self, parent=None): QDialog.__init__(self, parent=parent) self.l = l = QFormLayout(self) self.setLayout(l) self.setWindowTitle(_('Import OPML file')) self.setWindowIcon(QIcon(I('opml.png'))) self.h = h = QHBoxLayout() self.path = p = QLineEdit(self) p.setMinimumWidth(300) p.setPlaceholderText(_('Path to OPML file')) h.addWidget(p) self.cfb = b = QToolButton(self) b.setIcon(QIcon(I('document_open.png'))) b.setToolTip(_('Browse for OPML file')) b.clicked.connect(self.choose_file) h.addWidget(b) l.addRow(_('&OPML file:'), h) l.labelForField(h).setBuddy(p) b.setFocus(Qt.FocusReason.OtherFocusReason) self._articles_per_feed = a = QSpinBox(self) a.setMinimum(1), a.setMaximum(1000), a.setValue(100) a.setToolTip(_('Maximum number of articles to download per RSS feed')) l.addRow(_('&Maximum articles per feed:'), a) self._oldest_article = o = QSpinBox(self) o.setMinimum(1), o.setMaximum(3650), o.setValue(7) o.setSuffix(_(' days')) o.setToolTip(_('Articles in the RSS feeds older than this will be ignored')) l.addRow(_('&Oldest article:'), o) self.preserve_groups = g = QCheckBox(_('Preserve groups in the OPML file')) g.setToolTip('<p>' + _( 'If enabled, every group of feeds in the OPML file will be converted into a single recipe. Otherwise every feed becomes its own recipe')) g.setChecked(True) l.addRow(g) self._replace_existing = r = QCheckBox(_('Replace existing recipes')) r.setToolTip('<p>' + _( 'If enabled, any existing recipes with the same titles as entries in the OPML file will be replaced.' ' Otherwise, new entries with modified titles will be created')) r.setChecked(True) l.addRow(r) self.bb = bb = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok|QDialogButtonBox.StandardButton.Cancel) bb.accepted.connect(self.accept), bb.rejected.connect(self.reject) l.addRow(bb) self.recipes = () @property def articles_per_feed(self): return self._articles_per_feed.value() @property def oldest_article(self): return self._oldest_article.value() @property def replace_existing(self): return self._replace_existing.isChecked() def choose_file(self): opml_files = choose_files( self, 'opml-select-dialog', _('Select OPML file'), filters=[(_('OPML files'), ['opml'])], all_files=False, select_only_single_file=True) if opml_files: self.path.setText(opml_files[0]) def accept(self): path = str(self.path.text()) if not path: return error_dialog(self, _('Path not specified'), _( 'You must specify the path to the OPML file to import'), show=True) with open(path, 'rb') as f: raw = f.read() self.recipes = tuple(import_opml(raw, self.preserve_groups.isChecked())) if len(self.recipes) == 0: return error_dialog(self, _('No feeds found'), _( 'No importable RSS feeds found in the OPML file'), show=True) QDialog.accept(self) if __name__ == '__main__': import sys for group in import_opml(open(sys.argv[-1], 'rb').read()): print(group.title) for title, url in group.feeds: print(f'\t{title} - {url}') print()