diff --git a/nw/convert/htmlfile.py b/nw/convert/htmlfile.py new file mode 100644 index 00000000..bfaf4542 --- /dev/null +++ b/nw/convert/htmlfile.py @@ -0,0 +1,57 @@ +# -*- coding: utf-8 -*- +"""novelWriter HTML File + + novelWriter – HTML File +========================= + Writes the project to a html file + + File History: + Created: 2019-10-19 [0.3] + +""" + +import logging +import nw + +from nw.convert.textfile import TextFile +from nw.convert.tohtml import ToHtml + +logger = logging.getLogger(__name__) + +class HtmlFile(TextFile): + + def __init__(self, theProject, theParent): + TextFile.__init__(self, theProject, theParent) + + self.theConv = ToHtml(self.theProject, self.theParent) + + return + + ## + # Internal Functions + ## + + def _doOpenFile(self, filePath): + try: + self.outFile = open(filePath,mode="w+") + self.outFile.write("\n") + self.outFile.write("\n") + self.outFile.write("
\n") + self.outFile.write("\n") + self.outFile.write("\n") + self.outFile.write("\n") + except Exception as e: + self.makeAlert(["Failed to open file.",str(e)], nwAlert.ERROR) + return False + return True + + def _doCloseFile(self): + if self.outFile is not None: + self.outFile.write("\n") + self.outFile.write("\n") + self.outFile.close() + return True + +# END Class HtmlFile diff --git a/nw/convert/markdownfile.py b/nw/convert/markdownfile.py new file mode 100644 index 00000000..ff1ba8bc --- /dev/null +++ b/nw/convert/markdownfile.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- +"""novelWriter Markdown File + + novelWriter – Markdown File +============================= + Writes the project to a markdown file + + File History: + Created: 2019-10-19 [0.3] + +""" + +import logging +import nw + +from nw.convert.textfile import TextFile +from nw.convert.tomarkdown import ToMarkdown + +logger = logging.getLogger(__name__) + +class MarkdownFile(TextFile): + + def __init__(self, theProject, theParent): + TextFile.__init__(self, theProject, theParent) + + self.theConv = ToMarkdown(self.theProject, self.theParent) + + return + + ## + # Internal Functions + ## + + def _doOpenFile(self, filePath): + try: + self.outFile = open(filePath,mode="w+") + except Exception as e: + self.makeAlert(["Failed to open file.",str(e)], nwAlert.ERROR) + return False + return True + + def _doCloseFile(self): + if self.outFile is not None: + self.outFile.close() + return True + +# END Class MarkdownFile diff --git a/nw/convert/textfile.py b/nw/convert/textfile.py index 621b9dec..3ffeb7ff 100644 --- a/nw/convert/textfile.py +++ b/nw/convert/textfile.py @@ -28,7 +28,6 @@ class TextFile(): self.mainConf = nw.CONFIG self.theProject = theProject self.theParent = theParent - self.fileExt = "txt" self.outFile = None self.fileName = "" @@ -169,7 +168,7 @@ class TextFile(): return True def _doCloseFile(self): - """This function closes a file, and is meant to be overloaded by the subclass for other + """This function closes the file, and is meant to be overloaded by the subclass for other file formats. """ if self.outFile is not None: diff --git a/nw/convert/tohtml.py b/nw/convert/tohtml.py index 0f1e0612..da97242a 100644 --- a/nw/convert/tohtml.py +++ b/nw/convert/tohtml.py @@ -22,7 +22,6 @@ class ToHtml(Tokenizer): def __init__(self, theProject, theParent): Tokenizer.__init__(self, theProject, theParent) - return def doAutoReplace(self): @@ -53,24 +52,48 @@ class ToHtml(Tokenizer): thisPar = [] for tType, tText, tFormat, tAlign in self.theTokens: + aStyle = [] + if tAlign == self.A_CENTRE: + aStyle.append("text-align: center;") + + if len(aStyle) > 0: + hStyle = " style='%s'" % (" ".join(aStyle)) + else: + hStyle = "" + if tType == self.T_EMPTY: if len(thisPar) > 0: - self.theResult += "%s
\n" % " ".join(thisPar) + self.theResult += "%s
\n" % (hStyle," ".join(thisPar)) thisPar = [] + elif tType == self.T_HEAD1: - self.theResult += "%s\n" % tText + + elif tType == self.T_COMMAND and self.doCommands: + self.theResult += "
%s\n" % tText + # print(self.theResult) return diff --git a/nw/convert/tokenizer.py b/nw/convert/tokenizer.py index c30734bb..93366888 100644 --- a/nw/convert/tokenizer.py +++ b/nw/convert/tokenizer.py @@ -247,7 +247,7 @@ class Tokenizer(): if self.firstScene: self.theTokens[n] = (self.T_EMPTY,"",None,self.A_LEFT) else: - self.theTokens[n] = (self.T_SEP,tTemp,None,self.A_LEFT) + self.theTokens[n] = (self.T_SEP,tTemp,None,self.A_CENTRE) else: self.theTokens[n] = (tType,tTemp,None,self.A_LEFT) self.firstScene = False @@ -257,7 +257,7 @@ class Tokenizer(): if tTemp == "": self.theTokens[n] = (self.T_EMPTY,"",None,self.A_LEFT) elif tTemp == self.fmtSection: - self.theTokens[n] = (self.T_SEP,tTemp,None,self.A_LEFT) + self.theTokens[n] = (self.T_SEP,tTemp,None,self.A_CENTRE) else: self.theTokens[n] = (tType,tTemp,None,self.A_LEFT) diff --git a/nw/convert/tomarkdown.py b/nw/convert/tomarkdown.py new file mode 100644 index 00000000..c7aa54de --- /dev/null +++ b/nw/convert/tomarkdown.py @@ -0,0 +1,112 @@ +# -*- coding: utf-8 -*- +"""novelWriter Markdown Text Converter + + novelWriter – Markdown Text Converter +======================================= + Extends the Tokenizer class to write Markdown + + File History: + Created: 2019-10-19 [0.3] + +""" + +import textwrap +import logging +import re +import nw + +from nw.convert.tokenizer import Tokenizer + +logger = logging.getLogger(__name__) + +class ToMarkdown(Tokenizer): + + def __init__(self, theProject, theParent): + Tokenizer.__init__(self, theProject, theParent) + return + + def doConvert(self): + + htmlTags = { + self.FMT_B_B : "**", + self.FMT_B_E : "**", + self.FMT_I_B : "_", + self.FMT_I_E : "_", + self.FMT_U_B : "__", + self.FMT_U_E : "__", + } + + if self.wordWrap > 0: + tWrap = textwrap.TextWrapper( + width = self.wordWrap, + initial_indent = "", + subsequent_indent = "", + expand_tabs = True, + replace_whitespace = True, + fix_sentence_endings = False, + break_long_words = True, + drop_whitespace = True, + break_on_hyphens = True, + tabsize = 8, + max_lines = None + ) + + self.theResult = "" + thisPar = [] + for tType, tText, tFormat, tAlign in self.theTokens: + + # First check if we have a comment or plain text, as they need some + # extra replacing before we proceed to wrapping and final formatting. + if tType == self.T_COMMENT: + tText = " %s" % tText + + elif tType == self.T_TEXT: + tTemp = tText + for xPos, xLen, xFmt in reversed(tFormat): + tTemp = tTemp[:xPos]+tTemp[xPos+xLen:] + tText = tTemp + + tLen = len(tText) + + # The text can now be word wrapped, if we have requested this and it's needed. + if self.wordWrap > 0 and tLen > self.wordWrap: + if tType == self.T_COMMENT: + tText = textwrap.fill(tText.strip(),initial_indent=" ",subsequent_indent=" ") + else: + tText = tWrap.fill(tText) + + # Then the text can receive final formatting before we append it to the results. + # We also store text lines in a buffer and merge them only when we find an empty line, + # indicating a new paragraph. + if tType == self.T_EMPTY: + if len(thisPar) > 0: + self.theResult += "%s\n\n" % " ".join(thisPar) + thisPar = [] + + elif tType == self.T_HEAD1: + self.theResult += "# %s\n\n" % tText + + elif tType == self.T_HEAD2: + self.theResult += "## %s\n\n" % tText + + elif tType == self.T_HEAD3: + self.theResult += "### %s\n\n" % tText + + elif tType == self.T_HEAD4: + self.theResult += "#### %s\n\n" % tText + + elif tType == self.T_SEP: + self.theResult += "%s\n\n" % tText + + elif tType == self.T_TEXT: + thisPar.append(tText) + + elif tType == self.T_COMMENT and self.doComments: + self.theResult += "%s\n\n" % tText + + elif tType == self.T_COMMAND and self.doCommands: + self.theResult += "%s\n\n" % tText + + return + +# END Class ToMarkdown diff --git a/nw/gui/export.py b/nw/gui/export.py index e79ce681..0f493993 100644 --- a/nw/gui/export.py +++ b/nw/gui/export.py @@ -24,12 +24,14 @@ from PyQt5.QtWidgets import ( QLabel, QComboBox, QLineEdit, QPushButton, QFileDialog, QProgressBar, QSpinBox ) -from nw.project.document import NWDoc -from nw.tools.translate import numberToWord -from nw.convert.textfile import TextFile -from nw.common import checkString, checkBool, checkInt -from nw.constants import nwFiles -from nw.enum import nwItemType +from nw.project.document import NWDoc +from nw.tools.translate import numberToWord +from nw.convert.textfile import TextFile +from nw.convert.htmlfile import HtmlFile +from nw.convert.markdownfile import MarkdownFile +from nw.common import checkString, checkBool, checkInt +from nw.constants import nwFiles +from nw.enum import nwItemType logger = logging.getLogger(__name__) @@ -121,6 +123,10 @@ class GuiExport(QDialog): outFile = None if eFormat == GuiExportMain.FMT_TXT: outFile = TextFile(self.theProject, self.theParent) + elif eFormat == GuiExportMain.FMT_MD: + outFile = MarkdownFile(self.theProject, self.theParent) + elif eFormat == GuiExportMain.FMT_HTML: + outFile = HtmlFile(self.theProject, self.theParent) if outFile is None: return False @@ -138,11 +144,11 @@ class GuiExport(QDialog): self.exportStatus.setText("Failed to open file for writing ...") return False + time.sleep(0.5) + nDone = 0 for tHandle in self.theProject.treeOrder: - time.sleep(0.1) - self.exportProgress.setValue(nDone) tItem = self.theProject.getItem(tHandle) @@ -338,8 +344,8 @@ class GuiExportMain(QWidget): self.outputFormat = QComboBox(self) self.outputFormat.addItem("Plain Text (.txt)", self.FMT_TXT) - # self.outputFormat.addItem("Markdown (.md)", self.FMT_MD) - # self.outputFormat.addItem("HTML5 (.htm)", self.FMT_HTML) + self.outputFormat.addItem("Markdown (.md)", self.FMT_MD) + self.outputFormat.addItem("HTML5 (.htm)", self.FMT_HTML) # self.outputFormat.addItem("HTML5 for eBook (.htm)", self.FMT_EBOOK) # self.outputFormat.addItem("Open Document (.odt)", self.FMT_ODT) # self.outputFormat.addItem("LaTeX for PDF (.tex)", self.FMT_TEX)