mirror of
https://github.com/olivierkes/manuskript.git
synced 2024-05-17 11:22:28 +12:00
Finish OPML Import
This commit is contained in:
parent
44db1a5989
commit
1fa86ddd73
|
@ -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
|
||||
|
|
|
@ -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
|
||||
###############################################################################
|
||||
|
|
|
@ -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
|
||||
###############################################################################
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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"))
|
||||
|
|
Loading…
Reference in a new issue