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
+
+
+
-