Finish OPML Import

This commit is contained in:
Cam Stevenson 2017-10-29 11:26:43 -04:00
parent 44db1a5989
commit 1fa86ddd73
6 changed files with 142 additions and 47 deletions

View file

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

View file

@ -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
###############################################################################

View file

@ -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
###############################################################################

View file

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

View file

@ -111,6 +111,23 @@
</attribute>
</widget>
</item>
<item>
<widget class="QPushButton" name="btnImport">
<property name="toolTip">
<!-- TODO: Translation -->
<string>Import items from an OPML file into the current folder</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset theme="document-open"/>
</property>
<property name="flat">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QSlider" name="sldCorkSizeFactor">
<property name="minimumSize">

View file

@ -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"))