diff --git a/manuskript/import_export/__init__.py b/manuskript/import_export/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/manuskript/import_export/opml.py b/manuskript/import_export/opml.py new file mode 100644 index 00000000..9dc63277 --- /dev/null +++ b/manuskript/import_export/opml.py @@ -0,0 +1,136 @@ +#!/usr/bin/env python +# --!-- coding: utf8 --!-- + +# Import/export outline cards in OPML format +from PyQt5.QtWidgets import QMessageBox +from manuskript.models.outlineModel import outlineItem +from manuskript.enums import Outline +from lxml import etree as ET +from manuskript.functions import mainWindow + + +def importOpml(opmlFilePath, idx): + ret = False + mw = mainWindow() + + try: + with open(opmlFilePath, 'r') as opmlFile: + opmlContent = saveNewlines(opmlFile.read()) + except: + # TODO: Translation + QMessageBox.critical(mw, mw.tr("OPML Import"), + mw.tr("File open failed.")) + return False + + mdl = mw.mdlOutline + + if idx.internalPointer() is not None: + parentItem = idx.internalPointer() + else: + parentItem = mdl.rootItem + + try: + parsed = ET.fromstring(bytes(opmlContent, 'utf-8')) + + opmlNode = parsed + bodyNode = opmlNode.find("body") + + if bodyNode is not None: + outlineEls = bodyNode.findall("outline") + + if outlineEls is not None: + for element in outlineEls: + parseItems(element, parentItem) + + mdl.layoutChanged.emit() + mw.treeRedacOutline.viewport().update() + ret = True + except: + pass + + # TODO: Translation + if ret: + QMessageBox.information(mw, mw.tr("OPML Import"), + mw.tr("Import Complete.")) + else: + QMessageBox.critical(mw, mw.tr("OPML Import"), + mw.tr("This does not appear to be a valid OPML file.")) + + return ret + + +def parseItems(underElement, parentItem): + text = underElement.get('text') + if text is not None: + """ + In the case where the title is exceptionally long, trim it so it isn't + distracting in the tab label + """ + title = text[0:32] + if len(title) < len(text): + title += '...' + + card = outlineItem(parent=parentItem, title=title) + + body = "" + summary = "" + note = underElement.get('_note') + if note is not None and not isWhitespaceOnly(note): + body = restoreNewLines(note) + summary = body[0:128] + else: + """ + There's no note (body), but there is a title. Fill the + body with the title to support cards that consist only + of a title. + """ + body = text + + card.setData(Outline.summaryFull.value, summary) + + children = underElement.findall('outline') + if children is not None and len(children) > 0: + for el in children: + parseItems(el, card) + else: + card.setData(Outline.type.value, 'md') + card.setData(Outline.text.value, body) + + # I assume I don't have to do the following + # parentItem.appendChild(card) + + return + + +""" +Since XML parsers are notorious for stripping out significant newlines, +save them in a form we can restore after the parse. +""" + + +def saveNewlines(inString): + inString = inString.replace("\r\n", "\n") + inString = inString.replace("\n", "{{lf}}") + + return inString + + +""" +Restore any significant newlines +""" + + +def restoreNewLines(inString): + return inString.replace("{{lf}}", "\n") + + +""" +Determine whether or not a string only contains whitespace. +""" + + +def isWhitespaceOnly(inString): + str = restoreNewLines(inString) + str = ''.join(str.split()) + + return len(str) is 0 diff --git a/manuskript/ui/editors/mainEditor.py b/manuskript/ui/editors/mainEditor.py index 16358b08..d5dcc678 100644 --- a/manuskript/ui/editors/mainEditor.py +++ b/manuskript/ui/editors/mainEditor.py @@ -14,6 +14,7 @@ from manuskript.ui import style from manuskript.ui.editors.editorWidget import editorWidget from manuskript.ui.editors.fullScreenEditor import fullScreenEditor from manuskript.ui.editors.mainEditor_ui import Ui_mainEditor +from manuskript.import_export import opml as opmlInputExport locale.setlocale(locale.LC_ALL, '') @@ -83,6 +84,10 @@ class mainEditor(QWidget, Ui_mainEditor): self.btnRedacFullscreen.clicked.connect( self.showFullScreen, AUC) + self.btnImport.clicked.connect( + lambda v: self.importOPML() + ) + # self.tab.setDocumentMode(False) # Bug in Qt < 5.5: doesn't always load icons from custom theme. @@ -259,6 +264,7 @@ class mainEditor(QWidget, Ui_mainEditor): self.btnRedacFolderText.setVisible(visible) self.btnRedacFolderCork.setVisible(visible) self.btnRedacFolderOutline.setVisible(visible) + self.btnImport.setVisible(visible) self.sldCorkSizeFactor.setVisible(visible and self.btnRedacFolderCork.isChecked()) self.btnRedacFullscreen.setVisible(not visible) @@ -338,6 +344,20 @@ class mainEditor(QWidget, Ui_mainEditor): if self.currentEditor(): self._fullScreen = fullScreenEditor(self.currentEditor().currentIndex) + def importOPML(self): + from PyQt5.QtWidgets import QFileDialog + options = QFileDialog.Options() + options |= QFileDialog.DontUseNativeDialog + fileName, _ = QFileDialog.getOpenFileName(self, "Import OPML", "", + "OPML Files (*.opml)", options=options) + if fileName: + if len(self.mw.treeRedacOutline.selectionModel(). + selection().indexes()) == 0: + idx = QModelIndex() + else: + idx = self.mw.treeRedacOutline.currentIndex() + opmlInputExport.importOpml(fileName, idx) + ############################################################################### # DICT AND STUFF LIKE THAT ############################################################################### diff --git a/manuskript/ui/editors/mainEditor_ui.py b/manuskript/ui/editors/mainEditor_ui.py index 269f0fc1..22fd1f0c 100644 --- a/manuskript/ui/editors/mainEditor_ui.py +++ b/manuskript/ui/editors/mainEditor_ui.py @@ -55,6 +55,14 @@ class Ui_mainEditor(object): self.btnRedacFolderOutline.setObjectName("btnRedacFolderOutline") self.buttonGroup.addButton(self.btnRedacFolderOutline) self.horizontalLayout_19.addWidget(self.btnRedacFolderOutline) + self.btnImport = QtWidgets.QPushButton(mainEditor) + self.btnImport.setText("") + icon = QtGui.QIcon.fromTheme("document-open") + self.btnImport.setIcon(icon) + self.btnImport.setFlat(True) + self.btnImport.setObjectName("btnImport") + self.buttonGroup.addButton(self.btnImport) + self.horizontalLayout_19.addWidget(self.btnImport) self.sldCorkSizeFactor = QtWidgets.QSlider(mainEditor) self.sldCorkSizeFactor.setMinimumSize(QtCore.QSize(100, 0)) self.sldCorkSizeFactor.setMaximumSize(QtCore.QSize(200, 16777215)) @@ -109,6 +117,8 @@ class Ui_mainEditor(object): self.btnRedacFolderCork.setText(_translate("mainEditor", "Index cards")) self.btnRedacFolderOutline.setText(_translate("mainEditor", "Outline")) self.btnRedacFullscreen.setShortcut(_translate("mainEditor", "F11")) + # TODO: Translation + self.btnImport.setToolTip(_translate("mainEditor", "Import items from an OPML file into the current folder")) from manuskript.ui.editors.tabSplitter import tabSplitter from manuskript.ui.editors.textFormat import textFormat diff --git a/manuskript/ui/editors/mainEditor_ui.ui b/manuskript/ui/editors/mainEditor_ui.ui index 68a3a68f..ce73c973 100644 --- a/manuskript/ui/editors/mainEditor_ui.ui +++ b/manuskript/ui/editors/mainEditor_ui.ui @@ -111,6 +111,23 @@ + + + + + Import items from an OPML file into the current folder + + + + + + + + + true + + +