diff --git a/icons/NumixMsk/16x16/actions/document-import.svg b/icons/NumixMsk/16x16/actions/document-import.svg new file mode 100644 index 0000000..8ddaea3 --- /dev/null +++ b/icons/NumixMsk/16x16/actions/document-import.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/icons/NumixMsk/22x22/actions/document-import.svg b/icons/NumixMsk/22x22/actions/document-import.svg new file mode 100644 index 0000000..a78c48c --- /dev/null +++ b/icons/NumixMsk/22x22/actions/document-import.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/icons/NumixMsk/24x24/actions/document-import.svg b/icons/NumixMsk/24x24/actions/document-import.svg new file mode 100644 index 0000000..77e06c3 --- /dev/null +++ b/icons/NumixMsk/24x24/actions/document-import.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/icons/NumixMsk/32x32/actions/document-import.svg b/icons/NumixMsk/32x32/actions/document-import.svg new file mode 100644 index 0000000..53d3682 --- /dev/null +++ b/icons/NumixMsk/32x32/actions/document-import.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/icons/NumixMsk/48x48/actions/document-import.svg b/icons/NumixMsk/48x48/actions/document-import.svg new file mode 100644 index 0000000..b2e1682 --- /dev/null +++ b/icons/NumixMsk/48x48/actions/document-import.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/icons/NumixMsk/64x64/actions/document-import.svg b/icons/NumixMsk/64x64/actions/document-import.svg new file mode 100644 index 0000000..3760178 --- /dev/null +++ b/icons/NumixMsk/64x64/actions/document-import.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/manuskript/converters/__init__.py b/manuskript/converters/__init__.py new file mode 100644 index 0000000..b4c7eb1 --- /dev/null +++ b/manuskript/converters/__init__.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python +# --!-- coding: utf8 --!-- + +""" +The converters package provide functions to quickly convert on the fly from +one format to another. It is responsible to check what external library are +present, and do the job as best as possible with what we have in hand. +""" + +from manuskript.converters.abstractConverter import abstractConverter +from manuskript.converters.pandocConverter import pandocConverter +#from manuskript.converters.markdownConverter import markdownConverter + + +def HTML2MD(html): + + # Convert using pandoc + if pandocConverter.isValid(): + return pandocConverter.convert(html, _from="html", to="markdown") + + # Convert to plain text using QTextEdit + return HTML2PlainText(html) + + +def HTML2PlainText(html): + """ + Convert from HTML to plain text. + """ + + if pandocConverter.isValid(): + return pandocConverter.convert(html, _from="html", to="plain") + + # Last resort: probably resource ineficient + e = QTextEdit() + e.setHtml(html) + return e.toPlainText() diff --git a/manuskript/converters/abstractConverter.py b/manuskript/converters/abstractConverter.py new file mode 100644 index 0000000..d38c3af --- /dev/null +++ b/manuskript/converters/abstractConverter.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python +# --!-- coding: utf8 --!-- + + +class abstractConverter: + """ + A convertor is used to convert (duh) between stuff. They provide access + to external libraries that may or may not be present. + + Now, things are a bit messy, since classes in `exporter` (and `importer` to a lesser extent) do + the same. In a better world, classes from `exporter` and `importer` would + use convertors to do their stuff. (TODO) + """ + name = "" + + @classmethod + def isValid(cls): + return False diff --git a/manuskript/converters/markdownConverter.py b/manuskript/converters/markdownConverter.py new file mode 100644 index 0000000..f0e577b --- /dev/null +++ b/manuskript/converters/markdownConverter.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python +# --!-- coding: utf8 --!-- +import os +import shutil +import subprocess + +from PyQt5.QtCore import Qt +from PyQt5.QtWidgets import qApp, QMessageBox +from PyQt5.QtGui import QCursor + +from manuskript.converters import abstractConverter +from manuskript.functions import mainWindow + +try: + import markdown as MD +except ImportError: + MD = None + + +class markdownConverter(abstractConverter): + """ + Converter using python module markdown. + """ + + name = "python module markdown" + + @classmethod + def isValid(self): + return MD is not None + + @classmethod + def convert(self, markdown): + if not self.isValid: + print("ERROR: markdownConverter is called but not valid.") + return "" + + html = MD.markdown(markdown) + return html diff --git a/manuskript/converters/pandocConverter.py b/manuskript/converters/pandocConverter.py new file mode 100644 index 0000000..ea91e9a --- /dev/null +++ b/manuskript/converters/pandocConverter.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python +# --!-- coding: utf8 --!-- +import os +import shutil +import subprocess + +from PyQt5.QtCore import Qt +from PyQt5.QtWidgets import qApp, QMessageBox +from PyQt5.QtGui import QCursor + +from manuskript.converters import abstractConverter +from manuskript.functions import mainWindow + + +class pandocConverter(abstractConverter): + + name = "pandoc" + cmd = "pandoc" + + @classmethod + def isValid(self): + if self.path() != None: + return 2 + elif self.customPath() and os.path.exists(self.customPath): + return 1 + else: + return 0 + + @classmethod + def customPath(self): + settings = QSettings() + return settings.value("Exporters/{}_customPath".format(self.name), "") + + @classmethod + def path(self): + return shutil.which(self.cmd) + + @classmethod + def convert(self, src, _from="markdown", to="html", args=None, outputfile=None): + if not self.isValid: + print("ERROR: pandocConverter is called but not valid.") + return "" + + cmd = [self.runCmd()] + + cmd += ["--from={}".format(_from)] + cmd += ["--to={}".format(to)] + + if args: + cmd += args + + if outputfile: + cmd.append("--output={}".format(outputfile)) + + qApp.setOverrideCursor(QCursor(Qt.WaitCursor)) + + p = subprocess.Popen( + cmd, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE + ) + + if not type(src) == bytes: + src = src.encode("utf-8") # assumes utf-8 + + stdout, stderr = p.communicate(src) + + qApp.restoreOverrideCursor() + + if stderr: + err = stderr.decode("utf-8") + print(err) + QMessageBox.critical(mainWindow().dialog, + qApp.translate("Export", "Error"), err) + return None + + return stdout.decode("utf-8") + + @classmethod + def runCmd(self): + if self.isValid() == 2: + return self.cmd + elif self.isValid() == 1: + return self.customPath diff --git a/manuskript/functions.py b/manuskript/functions.py index 03e2a79..7db44c9 100644 --- a/manuskript/functions.py +++ b/manuskript/functions.py @@ -276,17 +276,6 @@ def findFirstFile(regex, path="resources"): if re.match(regex, l): return os.path.join(p, l) - -def HTML2PlainText(html): - """ - Ressource-inefficient way to convert HTML to plain text. - @param html: - @return: - """ - e = QTextEdit() - e.setHtml(html) - return e.toPlainText() - def customIcons(): """ Returns a list of possible customIcons. String from theme. diff --git a/manuskript/importer/mindMapImporter.py b/manuskript/importer/mindMapImporter.py index f5599f1..6d377ca 100644 --- a/manuskript/importer/mindMapImporter.py +++ b/manuskript/importer/mindMapImporter.py @@ -7,6 +7,7 @@ from manuskript.enums import Outline from lxml import etree as ET from manuskript.functions import mainWindow from manuskript.importer.abstractImporter import abstractImporter +from manuskript.converters import HTML2MD, HTML2PlainText class mindMapImporter(abstractImporter): @@ -71,7 +72,7 @@ class mindMapImporter(abstractImporter): self.addSetting("importTipAs", "combo", qApp.translate("Import", "Import tip as:"), - vals="Folder|Text", + vals="Text|Folder", ) for s in self.settings: @@ -81,17 +82,65 @@ class mindMapImporter(abstractImporter): def parseItems(self, underElement, parentItem=None): items = [] - title = underElement.get('TEXT') - if title is not None: - item = outlineItem(parent=parentItem, title=title) - items.append(item) + # Title + title = underElement.get('TEXT', "").replace("\n", " ") + if not title: + title = qApp.translate("Import", "Untitled") - children = underElement.findall('node') - if children is not None and len(children) > 0: - for c in children: - items.extend(self.parseItems(c, item)) - elif self.getSetting("importTipAs").value() == "Text": - item.setData(Outline.type.value, 'md') + item = outlineItem(parent=parentItem, title=title) + items.append(item) + + # URL + url = underElement.get('LINK', None) + + # Rich text content + content = "" + content = underElement.find("richcontent") + if content is not None: + # In Freemind, can be note or node + # Note: it's a note + # Node: it's the title of the node, in rich text + content_type = content.get("TYPE", "NOTE") + content = ET.tostring(content.find("html")) + + if content and content_type == "NODE": + # Content is title + # convert rich text title (in html) to plain text + title = HTML2PlainText(content) #.replace("\n", " ").strip() + # Count the number of lines + lines = [l.strip() for l in title.split("\n") if l.strip()] + + # If there is one line, we use it as title. + # Otherwise we leave it to be inserted as a note. + if len(lines) == 1: + item.setData(Outline.title.value, "".join(lines)) + content = "" + + if content: + # Set the note content as text value + content = HTML2MD(content) + item.setData(Outline.notes.value, content) + + if url: + # Set the url in notes + item.setData(Outline.notes.value, + item.data(Outline.notes.value) + "\n\n" + url) + + children = underElement.findall('node') + + # Process children + if children is not None and len(children) > 0: + for c in children: + items.extend(self.parseItems(c, item)) + + # Process if no children + elif self.getSetting("importTipAs").value() == "Text": + # Transform item to text + item.setData(Outline.type.value, 'md') + # Move notes to text + if item.data(Outline.notes.value): + item.setData(Outline.text.value, item.data(Outline.notes.value)) + item.setData(Outline.notes.value, "") return items diff --git a/manuskript/load_save/version_1.py b/manuskript/load_save/version_1.py index be1eded..6015a3e 100644 --- a/manuskript/load_save/version_1.py +++ b/manuskript/load_save/version_1.py @@ -18,7 +18,8 @@ from PyQt5.QtGui import QColor, QStandardItem from manuskript import settings from manuskript.enums import Character, World, Plot, PlotStep, Outline -from manuskript.functions import mainWindow, iconColor, iconFromColorString, HTML2PlainText +from manuskript.functions import mainWindow, iconColor, iconFromColorString +from manuskript.converters import HTML2PlainText from lxml import etree as ET from manuskript.load_save.version_0 import loadFilesFromZip diff --git a/manuskript/models/outlineModel.py b/manuskript/models/outlineModel.py index 140def2..4ce6772 100644 --- a/manuskript/models/outlineModel.py +++ b/manuskript/models/outlineModel.py @@ -15,7 +15,8 @@ from manuskript import settings from lxml import etree as ET from manuskript.enums import Outline -from manuskript.functions import mainWindow, toInt, wordCount, HTML2PlainText +from manuskript.functions import mainWindow, toInt, wordCount +from manuskript.converters import HTML2PlainText try: locale.setlocale(locale.LC_ALL, '') diff --git a/manuskript/ui/importers/importer.py b/manuskript/ui/importers/importer.py index e944183..ac03ef5 100644 --- a/manuskript/ui/importers/importer.py +++ b/manuskript/ui/importers/importer.py @@ -226,6 +226,9 @@ class importerDialog(QWidget, Ui_importer): def preview(self): + if not self.fileName: + return + # Creating a temporary outlineModel previewModel = outlineModel(self) previewModel.loadFromXML( diff --git a/manuskript/ui/importers/importer_ui.py b/manuskript/ui/importers/importer_ui.py index d23c8e4..327914c 100644 --- a/manuskript/ui/importers/importer_ui.py +++ b/manuskript/ui/importers/importer_ui.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'manuskript/ui/importers/importer_ui.ui' # -# Created by: PyQt5 UI code generator 5.9 +# Created by: PyQt5 UI code generator 5.5.1 # # WARNING! All changes made in this file will be lost! diff --git a/manuskript/ui/importers/importer_ui.ui b/manuskript/ui/importers/importer_ui.ui index a7f2f90..cbf72b9 100644 --- a/manuskript/ui/importers/importer_ui.ui +++ b/manuskript/ui/importers/importer_ui.ui @@ -45,8 +45,7 @@ Chose file - - ../../../../../../../../../../.designer/backup../../../../../../../../../../.designer/backup +