mirror of
https://github.com/olivierkes/manuskript.git
synced 2024-05-17 11:22:28 +12:00
Merge pull request #192 from camstevenson/import-export
Adds: Import OPML
This commit is contained in:
commit
1f04bf6f54
0
manuskript/import_export/__init__.py
Normal file
0
manuskript/import_export/__init__.py
Normal file
136
manuskript/import_export/opml.py
Normal file
136
manuskript/import_export/opml.py
Normal file
|
@ -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
|
|
@ -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
|
||||
###############################################################################
|
||||
|
|
|
@ -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">
|
||||
|
|
Loading…
Reference in a new issue