From 2abacc1b2fc59ad53b593b3510020fe24398a737 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Thu, 24 Oct 2019 22:18:41 +0200 Subject: [PATCH 01/10] Some cleanup of the export gui --- nw/gui/export.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/nw/gui/export.py b/nw/gui/export.py index da09f3f2..5cd16d3f 100644 --- a/nw/gui/export.py +++ b/nw/gui/export.py @@ -371,7 +371,7 @@ class GuiExportMain(QWidget): self.fixedWidth.setMaximum(999) self.fixedWidth.setSingleStep(1) self.fixedWidth.setValue(self.optState.getSetting("fixWidth")) - self.fixedWidth.setToolTip("0 disables the feature. Applies to .txt, .md and .tex files.") + self.fixedWidth.setToolTip("Applies to .txt, .md and .tex files. 0 disables the feature.") self.addSettingsForm.addWidget(QLabel("Fixed width"), 0, 0) self.addSettingsForm.addWidget(self.fixedWidth, 0, 1) @@ -416,8 +416,9 @@ class GuiExportMain(QWidget): "Text files (*.txt)", "Markdown files (*.md)", "HTML files (*.htm *.html)", - "Open document files (*.odt)", - "LaTeX files (*.tex)", + # "Open document files (*.odt)", + # "LaTeX files (*.tex)", + "All files (*.*)", ] dlgOpt = QFileDialog.Options() From a67d3ed599c63301e8ee67a7066cc5eea2a8adfa Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Fri, 25 Oct 2019 00:06:39 +0200 Subject: [PATCH 02/10] Set autoreplace characters with unicode code instead to make it easier to read the source code --- nw/config.py | 4 ++-- nw/gui/doceditor.py | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/nw/config.py b/nw/config.py index dbe6c3c5..6baf30a2 100644 --- a/nw/config.py +++ b/nw/config.py @@ -87,8 +87,8 @@ class Config: self.doReplaceDots = True self.wordCountTimer = 5.0 - self.fmtSingleQuotes = ["‘","’"] - self.fmtDoubleQuotes = ["“","”"] + self.fmtSingleQuotes = ["\u2018","\u2019"] + self.fmtDoubleQuotes = ["\u201c","\u201d"] self.spellLanguage = "en_GB" diff --git a/nw/gui/doceditor.py b/nw/gui/doceditor.py index 88d2460f..3ecc9f3c 100644 --- a/nw/gui/doceditor.py +++ b/nw/gui/doceditor.py @@ -422,15 +422,15 @@ class GuiDocEditor(QTextEdit): elif self.mainConf.doReplaceDash and theTwo == "--": theCursor.movePosition(QTextCursor.Left, QTextCursor.KeepAnchor, 2) - theCursor.insertText("–") + theCursor.insertText("\u2013") - elif self.mainConf.doReplaceDash and theTwo == "–-": + elif self.mainConf.doReplaceDash and theTwo == "\u2013-": theCursor.movePosition(QTextCursor.Left, QTextCursor.KeepAnchor, 2) - theCursor.insertText("—") + theCursor.insertText("\u2014") elif self.mainConf.doReplaceDots and theThree == "...": theCursor.movePosition(QTextCursor.Left, QTextCursor.KeepAnchor, 3) - theCursor.insertText("…") + theCursor.insertText("\u2026") return From 01dde7c8a401b55ae72215f2cbe384d075f15ef1 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Fri, 25 Oct 2019 00:07:37 +0200 Subject: [PATCH 03/10] Update tokenizer and html converter --- nw/convert/tohtml.py | 10 +++++++--- nw/convert/tokenizer.py | 42 ++++++++++++++++++++++------------------- 2 files changed, 30 insertions(+), 22 deletions(-) diff --git a/nw/convert/tohtml.py b/nw/convert/tohtml.py index 98835a9a..af61dbac 100644 --- a/nw/convert/tohtml.py +++ b/nw/convert/tohtml.py @@ -28,9 +28,13 @@ class ToHtml(Tokenizer): Tokenizer.doAutoReplace(self) repDict = { - "<" : "<", - ">" : ">", - "&" : "&", + "<" : "<", + ">" : ">", + "&" : "&", + "\u2013" : "&endash;", + "\u2014" : "$emdash;", + "\u2500" : "$emdash;", + "\u2026" : "…", } xRep = re.compile("|".join([re.escape(k) for k in repDict.keys()]), flags=re.DOTALL) self.theText = xRep.sub(lambda x: repDict[x.group(0)], self.theText) diff --git a/nw/convert/tokenizer.py b/nw/convert/tokenizer.py index 2636b2e9..e4fb34ac 100644 --- a/nw/convert/tokenizer.py +++ b/nw/convert/tokenizer.py @@ -26,27 +26,28 @@ logger = logging.getLogger(__name__) class Tokenizer(): - FMT_B_B = 1 # Begin bold - FMT_B_E = 2 # End bold - FMT_I_B = 3 # Begin italics - FMT_I_E = 4 # End italics - FMT_U_B = 5 # Begin underline - FMT_U_E = 6 # End underline + FMT_B_B = 1 # Begin bold + FMT_B_E = 2 # End bold + FMT_I_B = 3 # Begin italics + FMT_I_E = 4 # End italics + FMT_U_B = 5 # Begin underline + FMT_U_E = 6 # End underline - T_EMPTY = 1 # Empty line (new paragraph) - T_COMMENT = 2 # Comment line - T_COMMAND = 3 # Command line - T_HEAD1 = 4 # Header 1 (title) - T_HEAD2 = 5 # Header 2 (chapter) - T_HEAD3 = 6 # Header 3 (scene) - T_HEAD4 = 7 # Header 4 - T_TEXT = 8 # Text line - T_SEP = 9 # Scene separator + T_EMPTY = 1 # Empty line (new paragraph) + T_COMMENT = 2 # Comment line + T_COMMAND = 3 # Command line + T_HEAD1 = 4 # Header 1 (title) + T_HEAD2 = 5 # Header 2 (chapter) + T_HEAD3 = 6 # Header 3 (scene) + T_HEAD4 = 7 # Header 4 + T_TEXT = 8 # Text line + T_SEP = 9 # Scene separator + T_PBREAK = 10 # Page break - A_LEFT = 1 # Left aligned - A_RIGHT = 2 # Right aligned - A_CENTRE = 3 # Centred - A_JUSTIFY = 4 # Justified + A_LEFT = 1 # Left aligned + A_RIGHT = 2 # Right aligned + A_CENTRE = 3 # Centred + A_JUSTIFY = 4 # Justified def __init__(self, theProject, theParent): @@ -262,6 +263,7 @@ class Tokenizer(): self.theTokens[n] = (tType,tTemp,None,self.A_LEFT) # For title page and partitions, we need to centre all text + # and for some formats, we need a page break if isTitle or isPart: for n in range(len(self.theTokens)): tToken = self.theTokens[n] @@ -270,6 +272,8 @@ class Tokenizer(): tFormat = tToken[2] self.theTokens[n] = (tType,tText,tFormat,self.A_CENTRE) + self.theTokens[n] = (self.T_PBREAK,"",None,self.A_LEFT) + return def doConvert(self): From b9608dcdbdb154d0a4ef2a533c161200855d9b50 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Fri, 25 Oct 2019 00:07:54 +0200 Subject: [PATCH 04/10] Add support for LaTeX export --- nw/convert/latexfile.py | 66 +++++++++++++++++ nw/convert/tolatex.py | 155 ++++++++++++++++++++++++++++++++++++++++ nw/gui/export.py | 7 +- 3 files changed, 226 insertions(+), 2 deletions(-) create mode 100644 nw/convert/latexfile.py create mode 100644 nw/convert/tolatex.py diff --git a/nw/convert/latexfile.py b/nw/convert/latexfile.py new file mode 100644 index 00000000..448236ff --- /dev/null +++ b/nw/convert/latexfile.py @@ -0,0 +1,66 @@ +# -*- coding: utf-8 -*- +"""novelWriter LaTeX File + + novelWriter – LaTeX File +========================== + Writes the project to a LaTeX file + + File History: + Created: 2019-10-24 [0.3.1] + +""" + +import logging +import nw + +from nw.convert.textfile import TextFile +from nw.convert.tolatex import ToLaTeX +from nw.enum import nwAlert + +logger = logging.getLogger(__name__) + +class LaTeXFile(TextFile): + + def __init__(self, theProject, theParent): + TextFile.__init__(self, theProject, theParent) + + self.theConv = ToLaTeX(self.theProject, self.theParent) + + return + + ## + # Internal Functions + ## + + def _doOpenFile(self, filePath): + + if self.winEnding: + tN = "\r\n" + else: + tN = "\n" + + try: + self.outFile = open(filePath,mode="w+") + self.outFile.write(r"\documentclass[12pt]{report}"+tN+tN) + self.outFile.write(r"\usepackage[utf8]{inputenc}"+tN+tN) + self.outFile.write(r"\begin{document}"+tN+tN) + except Exception as e: + self.makeAlert(["Failed to open file.",str(e)], nwAlert.ERROR) + return False + + return True + + def _doCloseFile(self): + + if self.winEnding: + tN = "\r\n" + else: + tN = "\n" + + if self.outFile is not None: + self.outFile.write(r"\end{document}"+tN) + self.outFile.close() + + return True + +# END Class LaTeXFile diff --git a/nw/convert/tolatex.py b/nw/convert/tolatex.py new file mode 100644 index 00000000..7b8a6f51 --- /dev/null +++ b/nw/convert/tolatex.py @@ -0,0 +1,155 @@ +# -*- coding: utf-8 -*- +"""novelWriter LaTeX Converter + + novelWriter – LaTeX Converter +=============================== + Extends the Tokenizer class to write LaTeX + + File History: + Created: 2019-10-24 [0.3.1] + +""" + +import textwrap +import logging +import re +import nw + +from nw.convert.tokenizer import Tokenizer + +logger = logging.getLogger(__name__) + +class ToLaTeX(Tokenizer): + + def __init__(self, theProject, theParent): + Tokenizer.__init__(self, theProject, theParent) + return + + def doAutoReplace(self): + Tokenizer.doAutoReplace(self) + + repDict = { + "\u2013" : "--", + "\u2014" : "---", + "\u2500" : "---", + "\u2026" : "...", + } + xRep = re.compile("|".join([re.escape(k) for k in repDict.keys()]), flags=re.DOTALL) + self.theText = xRep.sub(lambda x: repDict[x.group(0)], self.theText) + + return + + def doConvert(self): + + texTags = { + self.FMT_B_B : r"\textbf{", + self.FMT_B_E : r"}", + self.FMT_I_B : r"\textit{", + self.FMT_I_E : r"}", + self.FMT_U_B : r"\underline{", + self.FMT_U_E : r"}", + } + + 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 + ) + tComm = textwrap.TextWrapper( + width = self.wordWrap-2, + 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: + + begText = "" + endText = "\n" + if tAlign == self.A_CENTRE: + begText = "\\begin{center}\n" + endText = "\\end{center}\n\n" + + # 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]+texTags[xFmt]+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: + aText = tComm.wrap(tText) + tText = "\n% ".join(aText) + 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 += begText + self.theResult += "%s\n" % tWrap.fill(" ".join(thisPar)) + self.theResult += endText + thisPar = [] + + elif tType == self.T_HEAD1: + self.theResult += begText + self.theResult += "{\\Huge %s}\n" % tText + self.theResult += endText + + elif tType == self.T_HEAD2: + self.theResult += "\\chapter*{%s}\n\n" % tText + + elif tType == self.T_HEAD3: + self.theResult += "\\section*{%s}\n\n" % tText + + elif tType == self.T_HEAD4: + self.theResult += "\\subsection*{%s}\n\n" % tText + + elif tType == self.T_SEP: + self.theResult += begText + self.theResult += "%s\n" % tText + self.theResult += endText + + elif tType == self.T_TEXT: + thisPar.append(tText) + + elif tType == self.T_PBREAK: + self.theResult += "\\newpage\n\n" + + 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 ToLaTeX diff --git a/nw/gui/export.py b/nw/gui/export.py index 5cd16d3f..0fc82e6b 100644 --- a/nw/gui/export.py +++ b/nw/gui/export.py @@ -29,6 +29,7 @@ from nw.tools.optlaststate import OptLastState from nw.convert.textfile import TextFile from nw.convert.htmlfile import HtmlFile from nw.convert.markdownfile import MarkdownFile +from nw.convert.latexfile import LaTeXFile from nw.constants import nwFiles from nw.enum import nwItemType @@ -127,6 +128,8 @@ class GuiExport(QDialog): outFile = MarkdownFile(self.theProject, self.theParent) elif eFormat == GuiExportMain.FMT_HTML: outFile = HtmlFile(self.theProject, self.theParent) + elif eFormat == GuiExportMain.FMT_TEX: + outFile = LaTeXFile(self.theProject, self.theParent) if outFile is None: return False @@ -348,7 +351,7 @@ class GuiExportMain(QWidget): 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) + self.outputFormat.addItem("LaTeX for PDF (.tex)", self.FMT_TEX) self.outputFormat.currentIndexChanged.connect(self._updateFormat) optIdx = self.outputFormat.findData(self.optState.getSetting("eFormat")) @@ -417,7 +420,7 @@ class GuiExportMain(QWidget): "Markdown files (*.md)", "HTML files (*.htm *.html)", # "Open document files (*.odt)", - # "LaTeX files (*.tex)", + "LaTeX files (*.tex)", "All files (*.*)", ] From 753b248b9f81e726e53aa967d23021c8f55cdd40 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sat, 26 Oct 2019 22:24:59 +0200 Subject: [PATCH 05/10] Cleaned up the nw/convert folder a bit --- nw/convert/{htmlfile.py => file/html.py} | 6 +- nw/convert/{latexfile.py => file/latex.py} | 6 +- .../{markdownfile.py => file/markdown.py} | 6 +- nw/convert/{textfile.py => file/text.py} | 6 +- nw/convert/{ => text}/tohtml.py | 0 nw/convert/{ => text}/tolatex.py | 0 nw/convert/{ => text}/tomarkdown.py | 0 nw/convert/text/totext.py | 119 ++++++++++++++++++ nw/convert/tokenizer.py | 91 -------------- nw/gui/docviewer.py | 6 +- nw/gui/export.py | 18 +-- 11 files changed, 143 insertions(+), 115 deletions(-) rename nw/convert/{htmlfile.py => file/html.py} (91%) rename nw/convert/{latexfile.py => file/latex.py} (90%) rename nw/convert/{markdownfile.py => file/markdown.py} (86%) rename nw/convert/{textfile.py => file/text.py} (96%) rename nw/convert/{ => text}/tohtml.py (100%) rename nw/convert/{ => text}/tolatex.py (100%) rename nw/convert/{ => text}/tomarkdown.py (100%) create mode 100644 nw/convert/text/totext.py diff --git a/nw/convert/htmlfile.py b/nw/convert/file/html.py similarity index 91% rename from nw/convert/htmlfile.py rename to nw/convert/file/html.py index 0b175e77..b5acaf2e 100644 --- a/nw/convert/htmlfile.py +++ b/nw/convert/file/html.py @@ -13,9 +13,9 @@ import logging import nw -from nw.convert.textfile import TextFile -from nw.convert.tohtml import ToHtml -from nw.enum import nwAlert +from nw.convert.file.text import TextFile +from nw.convert.text.tohtml import ToHtml +from nw.enum import nwAlert logger = logging.getLogger(__name__) diff --git a/nw/convert/latexfile.py b/nw/convert/file/latex.py similarity index 90% rename from nw/convert/latexfile.py rename to nw/convert/file/latex.py index 448236ff..cdaeb66e 100644 --- a/nw/convert/latexfile.py +++ b/nw/convert/file/latex.py @@ -13,9 +13,9 @@ import logging import nw -from nw.convert.textfile import TextFile -from nw.convert.tolatex import ToLaTeX -from nw.enum import nwAlert +from nw.convert.file.text import TextFile +from nw.convert.text.tolatex import ToLaTeX +from nw.enum import nwAlert logger = logging.getLogger(__name__) diff --git a/nw/convert/markdownfile.py b/nw/convert/file/markdown.py similarity index 86% rename from nw/convert/markdownfile.py rename to nw/convert/file/markdown.py index 0f490d1e..b5d2c32c 100644 --- a/nw/convert/markdownfile.py +++ b/nw/convert/file/markdown.py @@ -13,9 +13,9 @@ import logging import nw -from nw.convert.textfile import TextFile -from nw.convert.tomarkdown import ToMarkdown -from nw.enum import nwAlert +from nw.convert.file.text import TextFile +from nw.convert.text.tomarkdown import ToMarkdown +from nw.enum import nwAlert logger = logging.getLogger(__name__) diff --git a/nw/convert/textfile.py b/nw/convert/file/text.py similarity index 96% rename from nw/convert/textfile.py rename to nw/convert/file/text.py index 3ffeb7ff..b4adee27 100644 --- a/nw/convert/textfile.py +++ b/nw/convert/file/text.py @@ -16,8 +16,8 @@ import nw from os import path from PyQt5.QtWidgets import QMessageBox -from nw.convert.tokenizer import Tokenizer -from nw.enum import nwAlert, nwItemLayout +from nw.convert.text.totext import ToText +from nw.enum import nwAlert, nwItemLayout logger = logging.getLogger(__name__) @@ -36,7 +36,7 @@ class TextFile(): self.expNotes = False self.winEnding = False - self.theConv = Tokenizer(self.theProject, self.theParent) + self.theConv = ToText(self.theProject, self.theParent) self.makeAlert = self.theParent.makeAlert self.setComments(False) diff --git a/nw/convert/tohtml.py b/nw/convert/text/tohtml.py similarity index 100% rename from nw/convert/tohtml.py rename to nw/convert/text/tohtml.py diff --git a/nw/convert/tolatex.py b/nw/convert/text/tolatex.py similarity index 100% rename from nw/convert/tolatex.py rename to nw/convert/text/tolatex.py diff --git a/nw/convert/tomarkdown.py b/nw/convert/text/tomarkdown.py similarity index 100% rename from nw/convert/tomarkdown.py rename to nw/convert/text/tomarkdown.py diff --git a/nw/convert/text/totext.py b/nw/convert/text/totext.py new file mode 100644 index 00000000..abb42e7c --- /dev/null +++ b/nw/convert/text/totext.py @@ -0,0 +1,119 @@ +# -*- coding: utf-8 -*- +"""novelWriter Plain Text Converter + + novelWriter – Plain Text Converter +==================================== + Extends the Tokenizer class to convert to plain text + + File History: + Created: 2019-10-26 [0.3.1] + +""" + +import textwrap +import logging +import re +import nw + +from nw.convert.tokenizer import Tokenizer + +logger = logging.getLogger(__name__) + +class ToText(Tokenizer): + + def __init__(self, theProject, theParent): + Tokenizer.__init__(self, theProject, theParent) + return + + def doConvert(self): + """Converts the tokenized text into plain text. + """ + + 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 tAlign == self.A_CENTRE: + if self.wordWrap > 0: + if tLen > self.wordWrap: + aText = tWrap.wrap(tText) + for n in range(len(aText)): + aText[n] = self._centreText(aText[n],self.wordWrap) + tText = "\n".join(aText) + else: + tText = self._centreText(tText,self.wordWrap) + else: + if self.wordWrap > 0 and tLen > self.wordWrap: + 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: + uLine = "="*min(tLen,self.wordWrap) + if tAlign == self.A_CENTRE: + uLine = self._centreText(uLine,self.wordWrap) + self.theResult += "%s\n%s\n\n" % (tText,uLine) + + elif tType == self.T_HEAD2: + uLine = "~"*min(tLen,self.wordWrap) + self.theResult += "%s\n%s\n\n" % (tText,uLine) + + elif tType == self.T_HEAD3: + uLine = "-"*min(tLen,self.wordWrap) + self.theResult += "%s\n%s\n\n" % (tText,uLine) + + elif tType == self.T_HEAD4: + self.theResult += "%s\n\n" % tText + + elif tType == self.T_SEP: + if self.wordWrap > 0 and tLen < self.wordWrap: + tText = self._centreText(tText,self.wordWrap) + 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 ToText diff --git a/nw/convert/tokenizer.py b/nw/convert/tokenizer.py index e4fb34ac..a59b4f13 100644 --- a/nw/convert/tokenizer.py +++ b/nw/convert/tokenizer.py @@ -276,97 +276,6 @@ class Tokenizer(): return - def doConvert(self): - """Converts the tokenized text into plain text. - """ - - 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 tAlign == self.A_CENTRE: - if self.wordWrap > 0: - if tLen > self.wordWrap: - aText = tWrap.wrap(tText) - for n in range(len(aText)): - aText[n] = self._centreText(aText[n],self.wordWrap) - tText = "\n".join(aText) - else: - tText = self._centreText(tText,self.wordWrap) - else: - if self.wordWrap > 0 and tLen > self.wordWrap: - 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: - uLine = "="*min(tLen,self.wordWrap) - if tAlign == self.A_CENTRE: - uLine = self._centreText(uLine,self.wordWrap) - self.theResult += "%s\n%s\n\n" % (tText,uLine) - - elif tType == self.T_HEAD2: - uLine = "~"*min(tLen,self.wordWrap) - self.theResult += "%s\n%s\n\n" % (tText,uLine) - - elif tType == self.T_HEAD3: - uLine = "-"*min(tLen,self.wordWrap) - self.theResult += "%s\n%s\n\n" % (tText,uLine) - - elif tType == self.T_HEAD4: - self.theResult += "%s\n\n" % tText - - elif tType == self.T_SEP: - if self.wordWrap > 0 and tLen < self.wordWrap: - tText = self._centreText(tText,self.wordWrap) - 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 - def windowsEndings(self): self.theResult = self.theResult.replace("\n","\r\n") return diff --git a/nw/gui/docviewer.py b/nw/gui/docviewer.py index 68cba7ec..72c85ee4 100644 --- a/nw/gui/docviewer.py +++ b/nw/gui/docviewer.py @@ -17,9 +17,9 @@ from PyQt5.QtCore import Qt from PyQt5.QtWidgets import QTextBrowser from PyQt5.QtGui import QTextOption, QFont, QPalette, QColor -from nw.convert.tokenizer import Tokenizer -from nw.convert.tohtml import ToHtml -from nw.enum import nwItemType +from nw.convert.tokenizer import Tokenizer +from nw.convert.text.tohtml import ToHtml +from nw.enum import nwItemType logger = logging.getLogger(__name__) diff --git a/nw/gui/export.py b/nw/gui/export.py index 0fc82e6b..efcbca80 100644 --- a/nw/gui/export.py +++ b/nw/gui/export.py @@ -23,15 +23,15 @@ 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.tools.optlaststate import OptLastState -from nw.convert.textfile import TextFile -from nw.convert.htmlfile import HtmlFile -from nw.convert.markdownfile import MarkdownFile -from nw.convert.latexfile import LaTeXFile -from nw.constants import nwFiles -from nw.enum import nwItemType +from nw.project.document import NWDoc +from nw.tools.translate import numberToWord +from nw.tools.optlaststate import OptLastState +from nw.convert.file.text import TextFile +from nw.convert.file.html import HtmlFile +from nw.convert.file.markdown import MarkdownFile +from nw.convert.file.latex import LaTeXFile +from nw.constants import nwFiles +from nw.enum import nwItemType logger = logging.getLogger(__name__) From f38e770402cee5edbfa599a6c302a9b64e6496b0 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sat, 26 Oct 2019 22:58:16 +0200 Subject: [PATCH 06/10] Added novelWriter flavour markdown as an export format, and cleaned up windows/unix line endings code --- nw/convert/file/concat.py | 81 +++++++++++++++++++++++++++++++++++++++ nw/convert/file/html.py | 20 +++++----- nw/convert/file/latex.py | 18 ++------- nw/convert/file/text.py | 13 ++++--- nw/gui/export.py | 35 +++++++++++------ 5 files changed, 126 insertions(+), 41 deletions(-) create mode 100644 nw/convert/file/concat.py diff --git a/nw/convert/file/concat.py b/nw/convert/file/concat.py new file mode 100644 index 00000000..ba51e1d7 --- /dev/null +++ b/nw/convert/file/concat.py @@ -0,0 +1,81 @@ +# -*- coding: utf-8 -*- +"""novelWriter Concatenated File + + novelWriter – Concatenated File +================================= + Concatenate the standard novelWriter files to a single file + + File History: + Created: 2019-10-26 [0.3.1] + +""" + +import logging +import nw + +from os import path +from PyQt5.QtWidgets import QMessageBox + +from nw.convert.file.text import TextFile +from nw.convert.tokenizer import Tokenizer +from nw.enum import nwAlert, nwItemLayout + +logger = logging.getLogger(__name__) + +class ConcatFile(TextFile): + + def __init__(self, theProject, theParent): + TextFile.__init__(self, theProject, theParent) + + self.theConv = Tokenizer(self.theProject, self.theParent) + + return + + def addText(self, tHandle): + + logger.verbose("Parsing content of item '%s'" % tHandle) + + theItem = self.theProject.getItem(tHandle) + isNone = theItem.itemLayout == nwItemLayout.NO_LAYOUT + isNote = theItem.itemLayout == nwItemLayout.NOTE + isNovel = not isNone and not isNote + + if isNone: + return False + if isNote and not self.expNotes: + return False + if isNovel and not self.expNovel: + return False + + self.theConv.setText(tHandle) + + theResult = self.theConv.theText + + if self.winEnding: + self.theConv.windowsEndings() + + if theResult is not None and self.outFile is not None: + self.outFile.write(theResult.rstrip()) + self.outFile.write(self.endLine) + self.outFile.write(self.endLine) + + return True + + ## + # 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 ConcatFile diff --git a/nw/convert/file/html.py b/nw/convert/file/html.py index b5acaf2e..72ed1e3f 100644 --- a/nw/convert/file/html.py +++ b/nw/convert/file/html.py @@ -35,14 +35,14 @@ class HtmlFile(TextFile): 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") + self.outFile.write(""+self.endLine) + self.outFile.write(""+self.endLine) + self.outFile.write(""+self.endLine) + self.outFile.write(""+self.endLine) + self.outFile.write(""+self.endLine) + self.outFile.write(""+self.endLine) except Exception as e: self.makeAlert(["Failed to open file.",str(e)], nwAlert.ERROR) return False @@ -50,8 +50,8 @@ class HtmlFile(TextFile): def _doCloseFile(self): if self.outFile is not None: - self.outFile.write("\n") - self.outFile.write("\n") + self.outFile.write(""+self.endLine) + self.outFile.write(""+self.endLine) self.outFile.close() return True diff --git a/nw/convert/file/latex.py b/nw/convert/file/latex.py index cdaeb66e..2fd2536c 100644 --- a/nw/convert/file/latex.py +++ b/nw/convert/file/latex.py @@ -34,16 +34,11 @@ class LaTeXFile(TextFile): def _doOpenFile(self, filePath): - if self.winEnding: - tN = "\r\n" - else: - tN = "\n" - try: self.outFile = open(filePath,mode="w+") - self.outFile.write(r"\documentclass[12pt]{report}"+tN+tN) - self.outFile.write(r"\usepackage[utf8]{inputenc}"+tN+tN) - self.outFile.write(r"\begin{document}"+tN+tN) + self.outFile.write(r"\documentclass[12pt]{report}"+self.endLine) + self.outFile.write(r"\usepackage[utf8]{inputenc}"+self.endLine) + self.outFile.write(r"\begin{document}"+self.endLine) except Exception as e: self.makeAlert(["Failed to open file.",str(e)], nwAlert.ERROR) return False @@ -52,13 +47,8 @@ class LaTeXFile(TextFile): def _doCloseFile(self): - if self.winEnding: - tN = "\r\n" - else: - tN = "\n" - if self.outFile is not None: - self.outFile.write(r"\end{document}"+tN) + self.outFile.write(r"\end{document}"+self.endLine) self.outFile.close() return True diff --git a/nw/convert/file/text.py b/nw/convert/file/text.py index b4adee27..95f8f7b4 100644 --- a/nw/convert/file/text.py +++ b/nw/convert/file/text.py @@ -34,7 +34,7 @@ class TextFile(): self.theText = "" self.expNovel = True self.expNotes = False - self.winEnding = False + self.winEnding = self.mainConf.osWindows self.theConv = ToText(self.theProject, self.theParent) self.makeAlert = self.theParent.makeAlert @@ -43,6 +43,11 @@ class TextFile(): self.setMeta(False) self.setWordWrap(80) + if self.winEnding: + self.endLine = "\r\n" + else: + self.endLine = "\n" + return ## @@ -158,10 +163,8 @@ class TextFile(): """ try: self.outFile = open(filePath,mode="w+") - if self.winEnding: - self.outFile.write("\r\n\r\n") - else: - self.outFile.write("\n\n") + self.outFile.write(self.endLine) + self.outFile.write(self.endLine) except Exception as e: self.makeAlert(["Failed to open file.",str(e)], nwAlert.ERROR) return False diff --git a/nw/gui/export.py b/nw/gui/export.py index efcbca80..70e20d5b 100644 --- a/nw/gui/export.py +++ b/nw/gui/export.py @@ -30,6 +30,7 @@ from nw.convert.file.text import TextFile from nw.convert.file.html import HtmlFile from nw.convert.file.markdown import MarkdownFile from nw.convert.file.latex import LaTeXFile +from nw.convert.file.concat import ConcatFile from nw.constants import nwFiles from nw.enum import nwItemType @@ -130,6 +131,8 @@ class GuiExport(QDialog): outFile = HtmlFile(self.theProject, self.theParent) elif eFormat == GuiExportMain.FMT_TEX: outFile = LaTeXFile(self.theProject, self.theParent) + elif eFormat == GuiExportMain.FMT_NWD: + outFile = ConcatFile(self.theProject, self.theParent) if outFile is None: return False @@ -206,12 +209,13 @@ class GuiExport(QDialog): class GuiExportMain(QWidget): - FMT_TXT = 1 - FMT_MD = 2 - FMT_HTML = 3 - FMT_EBOOK = 4 - FMT_ODT = 5 - FMT_TEX = 6 + FMT_TXT = 1 # Plain text file + FMT_MD = 2 # Markdown file + FMT_HTML = 3 # HTML file + FMT_EBOOK = 4 # E-book friendly HTML + FMT_ODT = 5 # Open document + FMT_TEX = 6 # LaTeX file + FMT_NWD = 7 # novelWriter markdown FMT_EXT = { FMT_TXT : ".txt", FMT_MD : ".md", @@ -219,6 +223,7 @@ class GuiExportMain(QWidget): FMT_EBOOK : ".htm", FMT_ODT : ".odt", FMT_TEX : ".tex", + FMT_NWD : ".nwd", } FMT_HELP = { FMT_TXT : ( @@ -245,6 +250,10 @@ class GuiExportMain(QWidget): "Exports a LaTeX file that can be compiled to PDF using for instance PDFLaTeX. " "Comments are exported as LaTeX comments." ), + FMT_NWD : ( + "Exports a document using the novelWriter markdown format. " + "The files selected by the filters are appended as-is." + ), } def __init__(self, theParent, theProject, optState): @@ -346,12 +355,13 @@ class GuiExportMain(QWidget): self.outputHelp.setAlignment(Qt.AlignTop) 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("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) + 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("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) + self.outputFormat.addItem("novelWriter Markdown (.nwd)", self.FMT_NWD) self.outputFormat.currentIndexChanged.connect(self._updateFormat) optIdx = self.outputFormat.findData(self.optState.getSetting("eFormat")) @@ -421,6 +431,7 @@ class GuiExportMain(QWidget): "HTML files (*.htm *.html)", # "Open document files (*.odt)", "LaTeX files (*.tex)", + "novelWriter document files (*.nwd)", "All files (*.*)", ] From fde7975a0b7c71396a4d2e603cfcbf6d0692a713 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sat, 26 Oct 2019 23:05:12 +0200 Subject: [PATCH 07/10] No need for hardcoding line endings for windows. Python handles this by default --- nw/convert/file/concat.py | 8 ++------ nw/convert/file/html.py | 22 +++++++++++----------- nw/convert/file/latex.py | 10 +++++----- nw/convert/file/markdown.py | 2 +- nw/convert/file/text.py | 14 ++------------ nw/convert/tokenizer.py | 4 ---- 6 files changed, 21 insertions(+), 39 deletions(-) diff --git a/nw/convert/file/concat.py b/nw/convert/file/concat.py index ba51e1d7..cca2769b 100644 --- a/nw/convert/file/concat.py +++ b/nw/convert/file/concat.py @@ -51,13 +51,9 @@ class ConcatFile(TextFile): theResult = self.theConv.theText - if self.winEnding: - self.theConv.windowsEndings() - if theResult is not None and self.outFile is not None: self.outFile.write(theResult.rstrip()) - self.outFile.write(self.endLine) - self.outFile.write(self.endLine) + self.outFile.write("\n\n") return True @@ -67,7 +63,7 @@ class ConcatFile(TextFile): def _doOpenFile(self, filePath): try: - self.outFile = open(filePath,mode="w+") + self.outFile = open(filePath,mode="wt+") except Exception as e: self.makeAlert(["Failed to open file.",str(e)], nwAlert.ERROR) return False diff --git a/nw/convert/file/html.py b/nw/convert/file/html.py index 72ed1e3f..aa15756e 100644 --- a/nw/convert/file/html.py +++ b/nw/convert/file/html.py @@ -34,15 +34,15 @@ class HtmlFile(TextFile): def _doOpenFile(self, filePath): try: - self.outFile = open(filePath,mode="w+") - self.outFile.write(""+self.endLine) - self.outFile.write(""+self.endLine) - self.outFile.write(""+self.endLine) - self.outFile.write(""+self.endLine) - self.outFile.write(""+self.endLine) - self.outFile.write(""+self.endLine) + self.outFile = open(filePath,mode="wt+") + 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 @@ -50,8 +50,8 @@ class HtmlFile(TextFile): def _doCloseFile(self): if self.outFile is not None: - self.outFile.write(""+self.endLine) - self.outFile.write(""+self.endLine) + self.outFile.write("\n") + self.outFile.write("\n") self.outFile.close() return True diff --git a/nw/convert/file/latex.py b/nw/convert/file/latex.py index 2fd2536c..10968a97 100644 --- a/nw/convert/file/latex.py +++ b/nw/convert/file/latex.py @@ -35,10 +35,10 @@ class LaTeXFile(TextFile): def _doOpenFile(self, filePath): try: - self.outFile = open(filePath,mode="w+") - self.outFile.write(r"\documentclass[12pt]{report}"+self.endLine) - self.outFile.write(r"\usepackage[utf8]{inputenc}"+self.endLine) - self.outFile.write(r"\begin{document}"+self.endLine) + self.outFile = open(filePath,mode="wt+") + self.outFile.write(r"\documentclass[12pt]{report}\n") + self.outFile.write(r"\usepackage[utf8]{inputenc}\n") + self.outFile.write(r"\begin{document}\n") except Exception as e: self.makeAlert(["Failed to open file.",str(e)], nwAlert.ERROR) return False @@ -48,7 +48,7 @@ class LaTeXFile(TextFile): def _doCloseFile(self): if self.outFile is not None: - self.outFile.write(r"\end{document}"+self.endLine) + self.outFile.write(r"\end{document}\n") self.outFile.close() return True diff --git a/nw/convert/file/markdown.py b/nw/convert/file/markdown.py index b5d2c32c..ea4bf5c6 100644 --- a/nw/convert/file/markdown.py +++ b/nw/convert/file/markdown.py @@ -34,7 +34,7 @@ class MarkdownFile(TextFile): def _doOpenFile(self, filePath): try: - self.outFile = open(filePath,mode="w+") + self.outFile = open(filePath,mode="wt+") except Exception as e: self.makeAlert(["Failed to open file.",str(e)], nwAlert.ERROR) return False diff --git a/nw/convert/file/text.py b/nw/convert/file/text.py index 95f8f7b4..e04036c1 100644 --- a/nw/convert/file/text.py +++ b/nw/convert/file/text.py @@ -34,7 +34,6 @@ class TextFile(): self.theText = "" self.expNovel = True self.expNotes = False - self.winEnding = self.mainConf.osWindows self.theConv = ToText(self.theProject, self.theParent) self.makeAlert = self.theParent.makeAlert @@ -43,11 +42,6 @@ class TextFile(): self.setMeta(False) self.setWordWrap(80) - if self.winEnding: - self.endLine = "\r\n" - else: - self.endLine = "\n" - return ## @@ -145,9 +139,6 @@ class TextFile(): self.theConv.doHeaders() self.theConv.doConvert() - if self.winEnding: - self.theConv.windowsEndings() - if self.theConv.theResult is not None and self.outFile is not None: self.outFile.write(self.theConv.theResult) @@ -162,9 +153,8 @@ class TextFile(): that uses a different file format that requires a different approach. """ try: - self.outFile = open(filePath,mode="w+") - self.outFile.write(self.endLine) - self.outFile.write(self.endLine) + self.outFile = open(filePath,mode="wt+") + self.outFile.write("\n\n") except Exception as e: self.makeAlert(["Failed to open file.",str(e)], nwAlert.ERROR) return False diff --git a/nw/convert/tokenizer.py b/nw/convert/tokenizer.py index a59b4f13..78aa5754 100644 --- a/nw/convert/tokenizer.py +++ b/nw/convert/tokenizer.py @@ -276,10 +276,6 @@ class Tokenizer(): return - def windowsEndings(self): - self.theResult = self.theResult.replace("\n","\r\n") - return - ## # Internal Functions ## From 25c92d8b2e33cc57ba9c9e8edeccab102862d673 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sun, 27 Oct 2019 14:18:01 +0100 Subject: [PATCH 08/10] Improved the html export a bit, with a max width and comments formatted as yellow notes --- nw/convert/file/html.py | 13 ++++++++++++- nw/convert/text/tohtml.py | 2 +- nw/gui/export.py | 32 ++++++++++++++++---------------- 3 files changed, 29 insertions(+), 18 deletions(-) diff --git a/nw/convert/file/html.py b/nw/convert/file/html.py index aa15756e..d9893c00 100644 --- a/nw/convert/file/html.py +++ b/nw/convert/file/html.py @@ -39,10 +39,20 @@ class HtmlFile(TextFile): self.outFile.write("\n") self.outFile.write("\n") self.outFile.write("\n") self.outFile.write("\n") self.outFile.write("\n") + self.outFile.write("%s\n" % tText + self.theResult += "
%s\n" % tText diff --git a/nw/gui/export.py b/nw/gui/export.py index 70e20d5b..85b42f37 100644 --- a/nw/gui/export.py +++ b/nw/gui/export.py @@ -209,23 +209,27 @@ class GuiExport(QDialog): class GuiExportMain(QWidget): - FMT_TXT = 1 # Plain text file - FMT_MD = 2 # Markdown file - FMT_HTML = 3 # HTML file - FMT_EBOOK = 4 # E-book friendly HTML - FMT_ODT = 5 # Open document - FMT_TEX = 6 # LaTeX file - FMT_NWD = 7 # novelWriter markdown + FMT_NWD = 1 # novelWriter markdown + FMT_TXT = 2 # Plain text file + FMT_MD = 3 # Markdown file + FMT_HTML = 4 # HTML file + FMT_EBOOK = 5 # E-book friendly HTML + FMT_ODT = 6 # Open document + FMT_TEX = 7 # LaTeX file FMT_EXT = { + FMT_NWD : ".nwd", FMT_TXT : ".txt", FMT_MD : ".md", FMT_HTML : ".htm", FMT_EBOOK : ".htm", FMT_ODT : ".odt", FMT_TEX : ".tex", - FMT_NWD : ".nwd", } FMT_HELP = { + FMT_NWD : ( + "Exports a document using the novelWriter markdown format. " + "The files selected by the filters are appended as-is, including comments and other settings." + ), FMT_TXT : ( "Exports a plain text file. " "All formatting is stripped and comments are in square brackets." @@ -236,7 +240,7 @@ class GuiExportMain(QWidget): ), FMT_HTML : ( "Exports a plain html5 file. " - "Comments are converted to preformatted text blocks." + "Comments are wrapped in blocks with a yellow background colour." ), FMT_EBOOK : ( "Exports an html5 file that can be converted to eBook with Calibre. " @@ -250,10 +254,6 @@ class GuiExportMain(QWidget): "Exports a LaTeX file that can be compiled to PDF using for instance PDFLaTeX. " "Comments are exported as LaTeX comments." ), - FMT_NWD : ( - "Exports a document using the novelWriter markdown format. " - "The files selected by the filters are appended as-is." - ), } def __init__(self, theParent, theProject, optState): @@ -355,13 +355,13 @@ class GuiExportMain(QWidget): self.outputHelp.setAlignment(Qt.AlignTop) self.outputFormat = QComboBox(self) + self.outputFormat.addItem("novelWriter Markdown (.nwd)", self.FMT_NWD) 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("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) - self.outputFormat.addItem("novelWriter Markdown (.nwd)", self.FMT_NWD) self.outputFormat.currentIndexChanged.connect(self._updateFormat) optIdx = self.outputFormat.findData(self.optState.getSetting("eFormat")) @@ -426,12 +426,12 @@ class GuiExportMain(QWidget): currDir = "" extFilter = [ + "novelWriter document files (*.nwd)", "Text files (*.txt)", "Markdown files (*.md)", "HTML files (*.htm *.html)", # "Open document files (*.odt)", "LaTeX files (*.tex)", - "novelWriter document files (*.nwd)", "All files (*.*)", ] @@ -465,7 +465,7 @@ class ExportLastState(OptLastState): self.theState = { "wNovel" : True, "wNotes" : False, - "eFormat" : 2, + "eFormat" : 1, "fixWidth" : 80, "wComments" : False, "chFormat" : "Chapter %numword%", From aba339f8e3ac96c2f0eb1588818887610fe39531 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sun, 27 Oct 2019 14:55:11 +0100 Subject: [PATCH 09/10] Added documentation for export, and renamed all files to .txt as per recommendation --- docs/source/conf.py | 4 +- docs/source/export.txt | 70 +++++++++++++++++++ docs/source/{index.rst => index.txt} | 10 ++- docs/source/{interface.rst => interface.txt} | 10 +-- .../{introduction.rst => introduction.txt} | 0 docs/source/{notes.rst => notes.txt} | 0 docs/source/{projects.rst => projects.txt} | 0 docs/source/{structure.rst => structure.txt} | 0 docs/source/{technical.rst => technical.txt} | 0 9 files changed, 85 insertions(+), 9 deletions(-) create mode 100644 docs/source/export.txt rename docs/source/{index.rst => index.txt} (66%) rename docs/source/{interface.rst => interface.txt} (97%) rename docs/source/{introduction.rst => introduction.txt} (100%) rename docs/source/{notes.rst => notes.txt} (100%) rename docs/source/{projects.rst => projects.txt} (100%) rename docs/source/{structure.rst => structure.txt} (100%) rename docs/source/{technical.rst => technical.txt} (100%) diff --git a/docs/source/conf.py b/docs/source/conf.py index d9602e38..4046d2fc 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -48,7 +48,7 @@ templates_path = ["_templates"] # You can specify multiple suffix as a list of string: # # source_suffix = [".rst", ".md"] -source_suffix = ".rst" +source_suffix = ".txt" # The master toctree document. master_doc = "index" @@ -150,7 +150,7 @@ man_pages = [ # dir menu entry, description, category) texinfo_documents = [ (master_doc, "novelWriter", "novelWriter Documentation", - author, "novelWriter", "One line description of project.", + author, "novelWriter", "Markdown-like editor for novels.", "Miscellaneous"), ] diff --git a/docs/source/export.txt b/docs/source/export.txt new file mode 100644 index 00000000..52f374d2 --- /dev/null +++ b/docs/source/export.txt @@ -0,0 +1,70 @@ +################## +Exporting Projects +################## + +The novelWriter project can be exported in various formats using the export tool available from :menuselection:`Project --> Export Project` or by pressing :kbd:`F5`. + +************** +File Selection +************** + +Which files are selected for export can be controlled from the "Selection" section. +The check box for "Novel files" will select any file that isn't classified as a note. +This is useful when exporting a document containing the novel itself. + +It is also possible to select the "Note files" for export, with or without the "Novel files". +Comments can also optionally be included in the export for those formats that supports this. + +************** +Export Formats +************** + +Currently, five formats are supported for exporting. + +novelWriter Markdown +==================== + +This is simply a concatenation of the files selected by the filters. +The files in the project are stacked together in the order they appear in the tree view, with comments, tags, etc. included. +This is a useful format for exporting the project for later import back into novelWriter. + +Plain Text +========== + +The plain text export format writes a simple ``.txt`` file without any formatting at all. +It does, however, respect the centering of text if the + +***************** +Header Formatting +***************** + +The chapter, scene and section headers for novel files can receive some special treatment using the formats under "Chapter Headings" and "Other Headings". +The chapters can either be of numbered or unnumbered type, the latter being suitable for prologues, epilogues, interludes, etc. +The headers can be generated using free text in combination with replace tags. +The tooltip will explain which tags are available for each heading type. + +.. note:: + Header formatting only applies to novel files. + Headings in note files will will be left as-is, but heading levels 1 through 4 are converted to the correct heading level in the respective output formats. + +Numbered Chapters +================= + +Numbered chapters can be automatically assigned a number from 1 and upwards. +The number is inserted in place of the ``%num%`` tag. +Alternatively, the number can be translated to a word by using the ``%numword%`` tag. +The word numbers are currently only supported in English, and work for the range from "One" to "Nine Hundred and Ninety-Nine". + +Unnumbered Chapters +=================== + +Unnumbered chapters only support the ``%title%`` tag for auto-replacement. + +Scenes and Sections +=================== + +Both scenes and sections support the ``%title%`` tag. +In addition, scenes behave differently than the other headings when the format does not contain any auto-replace tags. +If the text in the box is any static text, the heading formatter will treat it as a scene separator, and insert that text as centred text between scenes, and ignoring the first scene of a chapter if the chapter heading is not followed by any text. + +Leaving these fields blank will disable any output of scene and section headers or separators to the exported file. diff --git a/docs/source/index.rst b/docs/source/index.txt similarity index 66% rename from docs/source/index.rst rename to docs/source/index.txt index c52b0877..a7435733 100644 --- a/docs/source/index.rst +++ b/docs/source/index.txt @@ -1,10 +1,12 @@ +#################################### Welcome to novelWriter Documentation -==================================== +#################################### This is the documentation for novelWriter |version|. +******** Contents -^^^^^^^^ +******** .. toctree:: :maxdepth: 2 @@ -14,10 +16,12 @@ Contents projects structure notes + export technical +****************** Indices and Tables -^^^^^^^^^^^^^^^^^^ +****************** * :ref:`genindex` * :ref:`modindex` diff --git a/docs/source/interface.rst b/docs/source/interface.txt similarity index 97% rename from docs/source/interface.rst rename to docs/source/interface.txt index 0d29c1c8..c38bfdc4 100644 --- a/docs/source/interface.rst +++ b/docs/source/interface.txt @@ -1,6 +1,6 @@ - +############## User Interface -============== +############## The user interface is kept as simple as possible to avoid distractions. The main window contains a tree vew pane with the entire structure of the project, and a small details panel below it to display additional information. @@ -11,8 +11,9 @@ This will open the source editor which uses a simplified markdown format describ The document can also be viewed as html with all the comments and commands stripped out. To view a document, simply press Ctrl+R or select a file and go to :menuselection:`Document --> View Document` in the menu. The document viewed does not need to be the same document currently being edited. +*************** Markdown Format -^^^^^^^^^^^^^^^ +*************** the document editor uses a simplified markdown format. That is, it supports basic formatting like bold, italics and underline, as well as four levels of headings. @@ -31,8 +32,9 @@ The editor also has a minimal set of commands used for setting tags and referenc * ``% text...``: A comment. The text is not exported, seen in viewer, or counted towards word counts. * ``@keyword: value``: A keyword argument followed by a value, or a comma separated list of values. +****************** Keyboard Shortcuts -^^^^^^^^^^^^^^^^^^ +****************** All features are available as keyboard shortcuts. These are as following: diff --git a/docs/source/introduction.rst b/docs/source/introduction.txt similarity index 100% rename from docs/source/introduction.rst rename to docs/source/introduction.txt diff --git a/docs/source/notes.rst b/docs/source/notes.txt similarity index 100% rename from docs/source/notes.rst rename to docs/source/notes.txt diff --git a/docs/source/projects.rst b/docs/source/projects.txt similarity index 100% rename from docs/source/projects.rst rename to docs/source/projects.txt diff --git a/docs/source/structure.rst b/docs/source/structure.txt similarity index 100% rename from docs/source/structure.rst rename to docs/source/structure.txt diff --git a/docs/source/technical.rst b/docs/source/technical.txt similarity index 100% rename from docs/source/technical.rst rename to docs/source/technical.txt From 43ebcec4e9efb788b6ee45e7f882e6763ea5aeff Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sun, 27 Oct 2019 15:44:40 +0100 Subject: [PATCH 10/10] Updated documentation for export and the user interface --- docs/source/export.txt | 27 ++++++++- docs/source/interface.txt | 110 ++++++++++++++++++++--------------- docs/source/introduction.txt | 4 +- docs/source/notes.txt | 6 +- docs/source/projects.txt | 25 ++++---- docs/source/structure.txt | 13 +++-- docs/source/technical.txt | 11 ++-- 7 files changed, 122 insertions(+), 74 deletions(-) diff --git a/docs/source/export.txt b/docs/source/export.txt index 52f374d2..c2240aa5 100644 --- a/docs/source/export.txt +++ b/docs/source/export.txt @@ -32,13 +32,34 @@ Plain Text ========== The plain text export format writes a simple ``.txt`` file without any formatting at all. -It does, however, respect the centering of text if the +Word-wrapping to a fixed column width is supported. +It does, however, respect the centering of text if the :guilabel:`Fixed width` setting is larger than ``0``. + +Markdown +======== + +The markdown export formats writes a file using the valid markdown formats used by novelWriter, and converts comments to preformatted blocks by indenting it with four spaces. +Word-wrapping to a fixed column width is supported. + +HTML5 Document +============== + +The html5 export format writes a single ``.htm`` file with a minimal style settings block. +The style setting block sets a maximum page width of ``768px``. +Comments are exported as indented text boxes with a light yellow background. + +LaTeX Document +============== + +The latex export formats generats a simple ``.tex`` file using a ``report`` document format. +All chapters and sections are exported as unnumbered, and the internal header numbering formatting of novelWriter is used instead. +Word-wrapping to a fixed column width in the LaTeX source is supported. ***************** Header Formatting ***************** -The chapter, scene and section headers for novel files can receive some special treatment using the formats under "Chapter Headings" and "Other Headings". +The chapter, scene and section headers for novel files can receive some special treatment using the formats under :guilabel:`Chapter Headings` and :guilabel:`Other Headings`. The chapters can either be of numbered or unnumbered type, the latter being suitable for prologues, epilogues, interludes, etc. The headers can be generated using free text in combination with replace tags. The tooltip will explain which tags are available for each heading type. @@ -53,7 +74,7 @@ Numbered Chapters Numbered chapters can be automatically assigned a number from 1 and upwards. The number is inserted in place of the ``%num%`` tag. Alternatively, the number can be translated to a word by using the ``%numword%`` tag. -The word numbers are currently only supported in English, and work for the range from "One" to "Nine Hundred and Ninety-Nine". +The word numbers feature is currently only supported in English, and works for the range from "One" to "Nine Hundred and Ninety-Nine". Unnumbered Chapters =================== diff --git a/docs/source/interface.txt b/docs/source/interface.txt index c38bfdc4..6a077c44 100644 --- a/docs/source/interface.txt +++ b/docs/source/interface.txt @@ -22,15 +22,19 @@ The formats are listed below. In addition to these standard markdown features, the editor also allows for comments, that is text that is ignored by the word counter and not exported or seen in the document viewer. The editor also has a minimal set of commands used for setting tags and references between files. -* ``# Title``: Heading level one. The space after the # is mandatory. -* ``## Title``: Heading level two. The space after the # is mandatory. -* ``### Title``: Heading level three. The space after the # is mandatory. -* ``#### Title``: Heading level four. The space after the # is mandatory. -* ``**text**``: The text is renderred as bold text. -* ``_text_``:The text is renderred as italics text. -* ``__text__``: The text is renderred as underlined text. -* ``% text...``: A comment. The text is not exported, seen in viewer, or counted towards word counts. -* ``@keyword: value``: A keyword argument followed by a value, or a comma separated list of values. +.. csv-table:: Formatting Syntax + :header: "Format", "Description" + :widths: 15, 70 + + "``# Title``", "Heading level one. The space after the # is mandatory." + "``## Title``", "Heading level two. The space after the # is mandatory." + "``### Title``", "Heading level three. The space after the # is mandatory." + "``#### Title``", "Heading level four. The space after the # is mandatory." + "``**text**``", "The text is renderred as bold text." + "``_text_``", "The text is renderred as italics text." + "``__text__``", "The text is renderred as underlined text." + "``% text...``", "A comment. The text is not exported, seen in viewer, or counted towards word counts." + "``@keyword: value``", "A keyword argument followed by a value, or a comma separated list of values." ****************** Keyboard Shortcuts @@ -39,41 +43,53 @@ Keyboard Shortcuts All features are available as keyboard shortcuts. These are as following: -* ``Ctrl+Shift+O``: Open a project. -* ``Ctrl+Shift+S``: Save the current project. -* ``Ctrl+Shift+W``: Close the current project. -* ``Ctrl+Shift+,``: Change project settings. -* ``Ctrl+Shift+N``: Create new folder. -* ``Ctrl+E, F2``: If in tree view, edit a document or folder settings. -* ``Ctrl+Del``: If in tree view, move a document to trash, or delete a folder. -* ``Ctrl+Q``: Exit novelWriter. -* ``Ctrl+N``: Create new document. -* ``Ctrl+O``: Open selected document. -* ``Return``: If in tree view, open a document for editing. -* ``Ctrl+S``: Save the current document in the editor. -* ``Ctrl+W``: Close the current document in the editor. -* ``Ctrl+R``: If in tree view, open a document for viewing. If in editor pane, open current document for viewing. -* ``Ctrl+Shift+R``: Close the document view pane. -* ``Ctrl+Z``: Undo latest changes. -* ``Ctrl+Y``: Redo latest undo. -* ``Ctrl+C``: Copy selected text to clipboard. -* ``Ctrl+X``: Cut selected text to clipboard. -* ``Ctrl+V``: Paste text from clipboard to cursor position. -* ``Ctrl+A``: Select all text in document. -* ``Ctrl+Shift+A``: Select all text in current paragraph. -* ``Ctrl+1``: Switch focus to tree view pane. -* ``Ctrl+2``: Switch focus to document editor pane. -* ``Ctrl+3``: Switch focus to document viewer pane. -* ``Ctrl+T``: Show project timeline. -* ``Ctrl+B``: Format selected text, or word under cursor, as bold. -* ``Ctrl+I``: Format selected text, or word under cursor, as italic. -* ``Ctrl+U``: Format selected text, or word under cursor, as underline. -* ``Ctrl+D``: Wrap selected text, or word under cursor, in double quotes. -* ``Ctrl+Shift+D``: Wrap selected text, or word under cursor, in single quotes. -* ``Ctrl+Shift+Up``: Move item one step up in the tree view. -* ``Ctrl+Shift+Down``: Move item one step down in the tree view. -* ``Ctrl+F7``: Toggle spell checking. -* ``F7``: Re-run spell checker. -* ``Ctrl+.``: Correct word under cursor. -* ``F9``: Re-build project indices. -* ``F1``: Open documentation. +.. csv-table:: Keyboard Shortcuts + :header: "Shortcut", "Description" + :widths: 15, 70 + + ":kbd:`Ctrl-.`", "Correct word under cursor." + ":kbd:`Ctrl-1`", "Switch focus to tree view pane." + ":kbd:`Ctrl-2`", "Switch focus to document editor pane." + ":kbd:`Ctrl-3`", "Switch focus to document viewer pane." + ":kbd:`Ctrl-A`", "Select all text in document." + ":kbd:`Ctrl-B`", "Format selected text, or word under cursor, as bold." + ":kbd:`Ctrl-C`", "Copy selected text to clipboard." + ":kbd:`Ctrl-D`", "Wrap selected text, or word under cursor, in double quotes." + ":kbd:`Ctrl-E`", "If in tree view, edit a document or folder settings." + ":kbd:`Ctrl-F`", "Open the search bar and search for selected word, if any is selected." + ":kbd:`Ctrl-H`", "Open the search and replace bar and search for selected word, if any is selected." + ":kbd:`Ctrl-I`", "Format selected text, or word under cursor, as italic." + ":kbd:`Ctrl-N`", "Create new document." + ":kbd:`Ctrl-O`", "Open selected document." + ":kbd:`Ctrl-Q`", "Exit novelWriter." + ":kbd:`Ctrl-R`", "If in tree view, open a document for viewing. If in editor pane, open current document for viewing." + ":kbd:`Ctrl-S`", "Save the current document in the editor." + ":kbd:`Ctrl-T`", "Show project timeline." + ":kbd:`Ctrl-U`", "Format selected text, or word under cursor, as underline." + ":kbd:`Ctrl-V`", "Paste text from clipboard to cursor position." + ":kbd:`Ctrl-W`", "Close the current document in the editor." + ":kbd:`Ctrl-X`", "Cut selected text to clipboard." + ":kbd:`Ctrl-Y`", "Redo latest undo." + ":kbd:`Ctrl-Z`", "Undo latest changes." + ":kbd:`Ctrl-F7`", "Toggle spell checking." + ":kbd:`Ctrl-Del`", "If in tree view, move a document to trash, or delete a folder." + ":kbd:`Ctrl-Shift-,`", "Change project settings." + ":kbd:`Ctrl-Shift-1`", "Replace occurrence of word in current document, and search for next occurrence." + ":kbd:`Ctrl-Shift-A`", "Select all text in current paragraph." + ":kbd:`Ctrl-Shift-D`", "Wrap selected text, or word under cursor, in single quotes." + ":kbd:`Ctrl-Shift-I`", "Import text to the current document from a text file." + ":kbd:`Ctrl-Shift-N`", "Create new folder." + ":kbd:`Ctrl-Shift-O`", "Open a project." + ":kbd:`Ctrl-Shift-R`", "Close the document view pane." + ":kbd:`Ctrl-Shift-S`", "Save the current project." + ":kbd:`Ctrl-Shift-W`", "Close the current project." + ":kbd:`Ctrl-Shift-Up`", "Move item one step up in the tree view." + ":kbd:`Ctrl-Shift-Down`", "Move item one step down in the tree view." + ":kbd:`F1`", "Open documentation." + ":kbd:`F2`", "Alternative to :kbd:`Ctrl-E`." + ":kbd:`F3`", "Find next occurrence of word in current document." + ":kbd:`F5`", "Export project dialog." + ":kbd:`F7`", "Re-run spell checker." + ":kbd:`F9`", "Re-build project indices." + ":kbd:`Shift-F3`", "Find previous occurrence of word in current document." + ":kbd:`Return`", "If in tree view, open a document for editing." diff --git a/docs/source/introduction.txt b/docs/source/introduction.txt index 6e3b56ec..78d01174 100644 --- a/docs/source/introduction.txt +++ b/docs/source/introduction.txt @@ -1,6 +1,6 @@ - +############ Introduction -============ +############ novelWriter is a simple multi-document plain text editor using a modified markdown syntax to apply simple formatting. Additional features are available diff --git a/docs/source/notes.txt b/docs/source/notes.txt index 961e0714..ef477191 100644 --- a/docs/source/notes.txt +++ b/docs/source/notes.txt @@ -1,12 +1,14 @@ +######################## Supporting Files (Notes) -======================== +######################## Supporting files, or notes, are any files stored in root folders that are not the Novel root folder. These files are intended for summaries and outlines of the various plot elements, characters, locations, and so on, of the novel. These are not required, but making at least minimal files for each such element makes it possible to use the timeline view feature to see how each element intersects with each section of the novel itself. +********* File Tags -^^^^^^^^^ +********* Each note file can have a tag associated with it, The format of a tag is ``@tag: tagname``, where tagname is a unique identifier. diff --git a/docs/source/projects.txt b/docs/source/projects.txt index db9cff2f..1fb63392 100644 --- a/docs/source/projects.txt +++ b/docs/source/projects.txt @@ -1,5 +1,6 @@ +############## Novel Projects -============== +############## A novelWriter project requires a dedicated folder for storing its files. See the Technical Information section for further details. @@ -10,8 +11,9 @@ A list of recently opened projects is also maintained and can be selected from t The project specific settings are available in :menuselection:`Project --> Project Settings`. See further details below. +***************** Project Structure -^^^^^^^^^^^^^^^^^ +***************** Projects are structured into a set of root folders, visible in the left side tree view panel. @@ -36,7 +38,7 @@ Deleted files will be moved into a special "Trash" root folder. Currently, these files cannot be permanently deleted from the project. Orphaned Documents -~~~~~~~~~~~~~~~~~~ +================== In the event the editor crashes or otherwise exits without saving the project state, files that have been added to the project tree and are saved to disk will appear in a special "Orphaned Items" root folder next time the application is started. These orphaned files will not have any meta data associated with them, so the title and other information has to be set again, and the files moved back to the correct location in the project. @@ -44,14 +46,15 @@ Using Project Folders Folders, aside from root folders, have no structural significance to the project. They are there purely as a way for the user to organise the files in meaningful sections and to be able to close them in the tree view. When processing the files in the novel, the folders are ignored. +**************** Project Settings -^^^^^^^^^^^^^^^^ +**************** The project settings can be accessed from the :menuselection:`Project --> Project Settings` menu entry. This will open a dialog box. Settings Tab -~~~~~~~~~~~~ +============ The Settings tab holds the project title and author settings. Working Title can be set to a different title than the Book Title. @@ -62,7 +65,7 @@ The Book Authors text box takes one author per line. The line breaks matter in that this is converted to a list for later correct formatting. Status Tab -~~~~~~~~~~ +========== Each file of type NOVEL can be given a status level, signified by a coloured icon. These are purely there for the user's convenience, and you are not required to use them for any other feature to work. @@ -71,7 +74,7 @@ The intention is to use this list to set what stage of writing you are on, altho Note that status levels currently in use by a file cannot be deleted. Importance Tab -~~~~~~~~~~~~~~ +============== Each file of types PLOT, CHARACTER, WORLD, TIMELINE, OBJECT or CUSTOM can be given an importance level, signified by a coloured icon like for status level. These are also purely there for the user's convenience, and you are not required to use them for any other feature to work. @@ -81,7 +84,7 @@ Again, these can in principle be used for whatever you want. Note that importance levels currently in use by a file cannot be deleted. Auto-Replace Tab -~~~~~~~~~~~~~~~~ +================ A set of automatically replaced keywords can be added in this tab. The keywords in the left column wile be replaced by the text in the right column when documents are opened in the viewer. @@ -91,8 +94,9 @@ Note that a keyword cannot contain any spaces. The angle brackets are added by default, and when used in the text are a part of the keyword to be replaced. This is to ensure that parts of the text isn't unintentionally replaced by the content of the list. +************* Writing Files -^^^^^^^^^^^^^ +************* New document files can be created from the Document menu, or by pressing Ctrl+N while in the tree view pane. This will create a new, empty file, and open the item settings dialog where the filename and various other settings can be set. @@ -100,8 +104,9 @@ This dialog can also be opened again later from either the menu, :menuselection: The different classes of documents have some restrictions. +****** Backup -^^^^^^ +****** An automatic backup system is built into novelWriter. In order to use it, a backup path to where the backups are to be stored needs to be provided in :menuselection:`Tools --> Preferences`. diff --git a/docs/source/structure.txt b/docs/source/structure.txt index abf5493e..e54dd1e3 100644 --- a/docs/source/structure.txt +++ b/docs/source/structure.txt @@ -1,12 +1,13 @@ - +################# Project Structure -================= +################# This section concerns files under the Novel type root folder. There are some restrictions and features that only applies to these type of files. +********************** Importance of Headings -^^^^^^^^^^^^^^^^^^^^^^ +********************** Subfolders under root folders have no impact on the structure of the novel itself. The structure is instead dictated by the heading level. @@ -23,8 +24,9 @@ The different header levels are interpreted as specific section types of the nov * ``### Header3``: Header level 3 signifies a scene level partition. * ``#### Header4``: Header level 4 signifies a sub-scene level partition. +************** Tag References -^^^^^^^^^^^^^^ +************** Each section, indicated by a heading, can contain references to tags set in the supporting files of the project. See the File Tags section. @@ -62,8 +64,9 @@ the syntax highlighter will alert the user that only the correct keywords are us If the index of defined tags is out of date, press F9 to regenerate it, or select :menuselection:`Tools --> Rebuild Indices` from the menu. In general, the index for a file is regenerated when a file is saved, so this shouldn't normally be necessary. +***************** Novel File Layout -^^^^^^^^^^^^^^^^^ +***************** Files that exist under the NOVEL type root folder can have a number of layouts set. See overview below. diff --git a/docs/source/technical.txt b/docs/source/technical.txt index 4c4a58c7..88aced07 100644 --- a/docs/source/technical.txt +++ b/docs/source/technical.txt @@ -1,14 +1,15 @@ - +##################### Technical Information -===================== +##################### This section contains details of how novelWriter stores and handles the project data. +****************** How Data is Stored -^^^^^^^^^^^^^^^^^^ +****************** Main Project File -~~~~~~~~~~~~~~~~~ +================= The project itself requires a dedicated folder for storing its files. The main project file is stored as an XML file with the name ``nwProject.nwx``. @@ -21,7 +22,7 @@ It is important to keep this file backed up. The project XML file is suitable for diff tools and version control, although a timesetamp is set in the meta section on line 2 each time the file is saved. Project Documents -~~~~~~~~~~~~~~~~~ +================= The project documents are saved in folders staring with ``data_``. Each document has a file handle taken from the first 13 characters of a SHA256 hash of the system time.