Improves the Mind Map importer #208

This commit is contained in:
Olivier Keshavjee 2017-11-13 22:55:33 +01:00
parent c4f8d0da60
commit fd0cd2cd4f
17 changed files with 282 additions and 27 deletions

View file

@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<g transform="matrix(-1,0,0,1,16,0)" style="fill-rule:evenodd">
<path d="m 1.714 0 8.571 0 c 1.714 0 1.714 0 1.714 1.778 l 0 12.444 c 0 1.778 0 1.778 -1.714 1.778 l -8.571 0 c -1.714 0 -1.714 0 -1.714 -1.778 l 0 -12.444 c 0 -1.778 0 -1.778 1.714 -1.778" style="fill:#fff"/>
<path d="m 9.376 2.664 c 0.141 0 0.318 0.154 0.594 0.375 1.667 1.333 5 4 5 4 1.108 0.887 1.104 1.117 0 2 l -5 4 c -0.828 0.663 -0.953 0.375 -1 -1.125 l -0.031 -1.875 c -4.969 0 -6.969 -3 -6.938 -7.03 1.969 2.031 3.969 3.03 6.969 2.969 l 0 -1.813 c 0 -1 0.125 -1.5 0.406 -1.5 z" style="fill:#268bd2"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 654 B

View file

@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
<g transform="matrix(-1,0,0,1,22.000425,0)" style="fill-rule:evenodd">
<path d="m 3,2 10,0 c 2,0 2,0 2,2 l 0,14 c 0,2 0,2 -2,2 L 3,20 C 1,20 1,20 1,18 L 1,4 C 1,2 1,2 3,2 Z" style="fill:#fff"/>
<path d="m 13.929 4.163 6.857 5.38 c 1.667 1.308 1.57 1.458 0 2.69 l -6.857 5.38 c -0.955 0.751 -0.929 0.472 -0.929 -1.384 l 0 -2.228 c -6.814 0 -10 -4.457 -10 -9.879 c 2.7 2.732 5.885 3.963 10 3.879 l 0 -2 c 0 -0.978 -0.025 -2.499 0.929 -1.834 z" style="fill:#268bd2"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 544 B

View file

@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<g transform="matrix(-1,0,0,1,23.000425,1)" style="fill-rule:evenodd">
<path d="m 3,2 10,0 c 2,0 2,0 2,2 l 0,14 c 0,2 0,2 -2,2 L 3,20 C 1,20 1,20 1,18 L 1,4 C 1,2 1,2 3,2 Z" style="fill:#fff"/>
<path d="m 13.929 4.163 6.857 5.38 c 1.667 1.308 1.57 1.458 0 2.69 l -6.857 5.38 c -0.955 0.751 -0.929 0.472 -0.929 -1.384 l 0 -2.228 c -6.814 0 -10 -4.457 -10 -9.879 c 2.7 2.732 5.885 3.963 10 3.879 l 0 -2 c 0 -0.978 -0.025 -2.499 0.929 -1.834 z" style="fill:#268bd2"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 544 B

View file

@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
<g transform="matrix(-1,0,0,1,31.99928,0)" style="fill-rule:evenodd">
<path d="M 3.429688,0 20.570312,0 C 24,0 24,0 24,3.554688 l 0,24.890624 C 24,32 24,32 20.570312,32 L 3.429688,32 C 0,32 0,32 0,28.445312 L 0,3.554688 C 0,0 0,0 3.429688,0 Z" style="fill:#fff"/>
<path d="m 18.751 5.27 c 0.281 0 0.635 0.308 1.188 0.75 3.334 2.667 10 8 10 8 2.217 1.774 2.209 2.233 0 4 l -10 8 c -1.657 1.325 -1.938 1 -2 -2.25 -0.019 -1 0 -3.75 0 -3.75 l -0.063 0 c -9.938 0 -13.939 -6 -13.876 -14.06 3.938 4.062 7.938 6.06 13.939 5.937 0 -1.434 -0.031 -2.66 0 -3.625 0.063 -2.25 0.25 -3 0.813 -3 z" style="fill:#268bd2"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 686 B

View file

@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48">
<g transform="matrix(-1,0,0,1,47.999149,0)" style="fill-rule:evenodd">
<path d="m 8,6 20,0 c 4,0 4,0 4,4 l 0,28 c 0,4 0,4 -4,4 L 8,42 C 4,42 4,42 4,38 L 4,10 C 4,6 4,6 8,6 Z" style="fill:#fff"/>
<path d="m 29.945 10.325 13.77 10.781 c 3.05 2.388 3.041 3.01 0 5.391 l -13.77 10.781 c -1.919 1.502 -1.945 0.697 -1.945 -2.772 -0.039 -1.348 0 -4.506 0 -4.506 c -0.031 0 0.031 0 0 0 c -13.684 0 -20.09 -8.893 -20 -19.759 5.422 5.475 11.738 7.849 20 7.68 l 0 -3.92 c 0 -1.96 0.029 -5 1.945 -3.675 z" style="fill:#268bd2"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 596 B

View file

@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
<g transform="matrix(-1,0,0,1,63.998608,0)" style="fill-rule:evenodd">
<path d="M 6.855469,0 41.144531,0 C 48,0 48,0 48,7.109375 l 0,49.78125 C 48,64 48,64 41.144531,64 L 6.855469,64 C 0,64 0,64 0,56.890625 L 0,7.109375 C 0,0 0,0 6.855469,0 Z" style="fill:#fff"/>
<path d="m 36.861 11.5 c 0.55 0 1.243 0.603 2.324 1.466 l 19.567 15.641 c 4.338 3.468 4.322 4.366 0 7.821 l -19.567 15.641 c -3.241 2.59 -3.184 1.967 -3.184 -4.399 0 0 0.183 -7.67 0.061 -7.67 l -0.122 0 c -19.444 0 -28.06 -11.392 -27.939 -27.16 7.704 7.943 16.26 12.4 28 12.156 l 0 -7.635 c 0.122 -3.91 -0.24 -5.865 0.861 -5.865" style="fill:#268bd2"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 696 B

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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, '')

View file

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

View file

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

View file

@ -45,8 +45,7 @@
<string>Chose file</string>
</property>
<property name="icon">
<iconset theme="document-import">
<normaloff>../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../.designer/backup</iconset>
<iconset theme="document-import"/>
</property>
</widget>
</item>