diff --git a/manuskript/import_export/opml.py b/manuskript/import_export/opml.py
index e178ddc3..9dc63277 100644
--- a/manuskript/import_export/opml.py
+++ b/manuskript/import_export/opml.py
@@ -2,69 +2,112 @@
# --!-- 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
-import xmltodict
+from lxml import etree as ET
from manuskript.functions import mainWindow
-from PyQt5.QtCore import QModelIndex
-def exportOpml():
- return True
-
-
-def importOpml(opmlFilePath):
- with open(opmlFilePath, 'r') as opmlFile:
- opmlContent = saveNewlines(opmlFile.read())
-
+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
- dict = xmltodict.parse(opmlContent, strip_whitespace=False)
+ if idx.internalPointer() is not None:
+ parentItem = idx.internalPointer()
+ else:
+ parentItem = mdl.rootItem
- opmlNode = dict['opml']
- bodyNode = opmlNode['body']
+ try:
+ parsed = ET.fromstring(bytes(opmlContent, 'utf-8'))
- outline = bodyNode['outline']
+ opmlNode = parsed
+ bodyNode = opmlNode.find("body")
- for element in outline:
- parseItems(element, mdl.rootItem)
+ if bodyNode is not None:
+ outlineEls = bodyNode.findall("outline")
- mdl.layoutChanged.emit()
+ if outlineEls is not None:
+ for element in outlineEls:
+ parseItems(element, parentItem)
- mw.treeRedacOutline.viewport().update()
+ mdl.layoutChanged.emit()
+ mw.treeRedacOutline.viewport().update()
+ ret = True
+ except:
+ pass
- return True
+ # 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):
- if '@text' in underElement:
- card = outlineItem(parent=parentItem, title=underElement['@text'])
+ 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 += '...'
- text = ""
+ card = outlineItem(parent=parentItem, title=title)
+
+ body = ""
summary = ""
- if '@_note' in underElement:
- text = restoreNewLines(underElement['@_note'])
- summary = text[0:128]
+ 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)
- if 'outline' in underElement:
- elements = underElement['outline']
-
- for el in elements:
+ 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, text)
+ card.setData(Outline.text.value, body)
- # I assume I don't have to do the following
- # parentItem.appendChild(card)
+ # 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}}")
@@ -72,6 +115,22 @@ def saveNewlines(inString):
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/mainWindow.py b/manuskript/mainWindow.py
index 92850ff8..36d40e17 100644
--- a/manuskript/mainWindow.py
+++ b/manuskript/mainWindow.py
@@ -27,7 +27,6 @@ from manuskript.ui.mainWindow import Ui_MainWindow
from manuskript.ui.tools.frequencyAnalyzer import frequencyAnalyzer
from manuskript.ui.views.outlineDelegates import outlineCharacterDelegate
from manuskript.ui.views.plotDelegate import plotDelegate
-from manuskript.import_export import opml as opmlInputExport
# Spellcheck support
from manuskript.ui.views.textEditView import textEditView
@@ -98,13 +97,12 @@ class MainWindow(QMainWindow, Ui_MainWindow):
# Main Menu
for i in [self.actSave, self.actSaveAs, self.actCloseProject,
self.menuEdit, self.menuView, self.menuTools, self.menuHelp,
- self.actCompile, self.actImport, self.actSettings]:
+ self.actCompile, self.actSettings]:
i.setEnabled(False)
self.actOpen.triggered.connect(self.welcome.openFile)
self.actSave.triggered.connect(self.saveDatas)
self.actSaveAs.triggered.connect(self.welcome.saveAsFile)
- self.actImport.triggered.connect(self.importOutline)
self.actCompile.triggered.connect(self.doCompile)
self.actLabels.triggered.connect(self.settingsLabel)
self.actStatus.triggered.connect(self.settingsStatus)
@@ -447,7 +445,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
i.setEnabled(False)
for i in [self.actSave, self.actSaveAs, self.actCloseProject,
self.menuEdit, self.menuView, self.menuTools, self.menuHelp,
- self.actCompile, self.actImport, self.actSettings]:
+ self.actCompile, self.actSettings]:
i.setEnabled(True)
# Add project name to Window's name
@@ -491,7 +489,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
i.setEnabled(True)
for i in [self.actSave, self.actSaveAs, self.actCloseProject,
self.menuEdit, self.menuView, self.menuTools, self.menuHelp,
- self.actCompile, self.actImport, self.actSettings]:
+ self.actCompile, self.actSettings]:
i.setEnabled(False)
# Set Window's name - no project loaded
@@ -627,10 +625,6 @@ class MainWindow(QMainWindow, Ui_MainWindow):
self.statusBar().showMessage(
self.tr("Project {} loaded with some errors.").format(project), 5000)
- def importOutline(self, project):
- opmlInputExport.importOpml('/home/cstevenson/End Plan 2.opml')
- return True
-
###############################################################################
# MAIN CONNECTIONS
###############################################################################
diff --git a/manuskript/ui/editors/mainEditor.py b/manuskript/ui/editors/mainEditor.py
index ab5c1b60..71571e7d 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, '')
@@ -44,6 +45,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.
@@ -217,6 +222,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)
@@ -296,6 +302,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
+
+
+
-
diff --git a/manuskript/ui/mainWindow.py b/manuskript/ui/mainWindow.py
index ad5d0933..a2a9a74e 100644
--- a/manuskript/ui/mainWindow.py
+++ b/manuskript/ui/mainWindow.py
@@ -1123,9 +1123,6 @@ class Ui_MainWindow(object):
self.actSaveAs.setObjectName("actSaveAs")
self.actQuit = QtWidgets.QAction(MainWindow)
icon = QtGui.QIcon.fromTheme("application-exit")
- self.actImport = QtWidgets.QAction(MainWindow)
- self.actImport.setIcon(icon)
- self.actImport.setObjectName("actImport")
self.actQuit.setIcon(icon)
self.actQuit.setObjectName("actQuit")
self.actShowHelp = QtWidgets.QAction(MainWindow)
@@ -1186,7 +1183,6 @@ class Ui_MainWindow(object):
self.menuFile.addAction(self.menuRecents.menuAction())
self.menuFile.addAction(self.actSave)
self.menuFile.addAction(self.actSaveAs)
- self.menuFile.addAction(self.actImport)
self.menuFile.addAction(self.actCloseProject)
self.menuFile.addSeparator()
self.menuFile.addAction(self.actCompile)
@@ -1328,7 +1324,6 @@ class Ui_MainWindow(object):
self.actSave.setShortcut(_translate("MainWindow", "Ctrl+S"))
self.actSaveAs.setText(_translate("MainWindow", "Sa&ve as..."))
self.actSaveAs.setShortcut(_translate("MainWindow", "Ctrl+Shift+S"))
- self.actImport.setText("Import")
self.actQuit.setText(_translate("MainWindow", "&Quit"))
self.actQuit.setShortcut(_translate("MainWindow", "Ctrl+Q"))
self.actShowHelp.setText(_translate("MainWindow", "&Show help texts"))