Finished cleaning up formatting on export and connected all dialog options

This commit is contained in:
Veronica K. B. Olsen
2020-05-10 22:51:50 +02:00
parent 52eb51b44b
commit eecf1e000d
4 changed files with 238 additions and 84 deletions
+42 -17
View File
@@ -104,15 +104,24 @@ class ToHtml(Tokenizer):
thisPar = []
for tType, tText, tFormat, tAlign in self.theTokens:
# Styles
aStyle = []
if tAlign == self.A_CENTRE:
aStyle.append("text-align: center;")
elif tAlign == self.A_RIGHT:
aStyle.append("text-align: right;")
elif tAlign == self.A_JUSTIFY:
aStyle.append("text-align: justify;")
if tType == self.T_HEAD2:
aStyle.append("page-break-before: always;")
if len(aStyle) > 0:
hStyle = " style='%s'" % (" ".join(aStyle))
else:
hStyle = ""
# Process TextType
if tType == self.T_EMPTY:
if len(thisPar) > 0:
tTemp = "".join(thisPar)
@@ -120,23 +129,30 @@ class ToHtml(Tokenizer):
thisPar = []
elif tType == self.T_HEAD1:
self.theResult += "<h1%s>%s</h1>\n" % (hStyle,tText)
tHead = tText.replace(r"\\", "<br/>")
self.theResult += "<h1%s>%s</h1>\n" % (hStyle, tHead)
elif tType == self.T_HEAD2:
self.theResult += "<h2%s>%s</h2>\n" % (hStyle,tText)
tHead = tText.replace(r"\\", "<br/>")
self.theResult += "<h2%s>%s</h2>\n" % (hStyle, tHead)
elif tType == self.T_HEAD3:
self.theResult += "<h3%s>%s</h3>\n" % (hStyle,tText)
tHead = tText.replace(r"\\", "<br/>")
self.theResult += "<h3%s>%s</h3>\n" % (hStyle, tHead)
elif tType == self.T_HEAD4:
self.theResult += "<h4%s>%s</h4>\n" % (hStyle,tText)
tHead = tText.replace(r"\\", "<br/>")
self.theResult += "<h4%s>%s</h4>\n" % (hStyle, tHead)
elif tType == self.T_SEP:
self.theResult += "<p%s>%s</p>\n" % (hStyle,tText)
self.theResult += "<p%s>%s</p>\n" % (hStyle, tText)
elif tType == self.T_SKIP:
self.theResult += "<p>&nbsp;</p>\n"
elif tType == self.T_PBREAK:
self.theResult += "<p style='page-break-after: always;'>&nbsp;</p>\n"
elif tType == self.T_TEXT:
tTemp = tText
for xPos, xLen, xFmt in reversed(tFormat):
@@ -146,6 +162,9 @@ class ToHtml(Tokenizer):
else:
thisPar.append(tTemp.rstrip()+" ")
elif tType == self.T_SYNOPSIS and self.doSynopsis:
self.theResult += self._formatSynopsis(tText)
elif tType == self.T_COMMENT and self.doComments:
self.theResult += self._formatComments(tText)
@@ -158,12 +177,27 @@ class ToHtml(Tokenizer):
# Internal Functions
##
def _formatKeywords(self, tText):
"""Apply HTML formatting to keywords.
def _formatSynopsis(self, tText):
"""Apply HTML formatting to synopsis.
"""
if not self.forPreview:
return "<pre>@%s</pre>\n" % tText
return "<p class='synopsis'><strong>Synopsis: </strong>%s</p>\n" % tText
return "<p class='comment'>%s</p>\n" % tText
def _formatComments(self, tText):
"""Apply HTML formatting to comments.
"""
if not self.forPreview:
return "<p class='comment'><strong>Comment: </strong>%s</p>\n" % tText
return "<p class='comment'>%s</p>\n" % tText
def _formatKeywords(self, tText):
"""Apply HTML formatting to keywords.
"""
tText = "@"+tText
isValid, theBits, thePos = self.theParent.theIndex.scanThis(tText)
@@ -182,13 +216,4 @@ class ToHtml(Tokenizer):
return "<div>%s</div>" % retText
def _formatComments(self, tText):
"""Apply HTML formatting to comments.
"""
if not self.forPreview:
return "<div class='comment'>%s</div>\n" % tText
return "<p class='comment'>%s</p>\n" % tText
# END Class ToHtml
+81 -52
View File
@@ -40,29 +40,30 @@ 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_KEYWORD = 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_SKIP = 10 # Paragraph break
T_PBREAK = 11 # Page break
T_EMPTY = 1 # Empty line (new paragraph)
T_SYNOPSIS = 2 # Synopsis comment
T_COMMENT = 3 # Comment line
T_KEYWORD = 4 # Command line
T_HEAD1 = 5 # Header 1 (title)
T_HEAD2 = 6 # Header 2 (chapter)
T_HEAD3 = 7 # Header 3 (scene)
T_HEAD4 = 8 # Header 4
T_TEXT = 9 # Text line
T_SEP = 10 # Scene separator
T_SKIP = 11 # Paragraph break
T_PBREAK = 12 # 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):
@@ -78,8 +79,11 @@ class Tokenizer():
self.theResult = None # The result text after conversion
# User Settings
self.doBodyText = True # Include body text
self.doSynopsis = False # Also process synopsis comments
self.doComments = False # Also process comments
self.doKeywords = False # Also process keywords like tags and references
self.doJustify = False # Justify text
self.fmtTitle = "%title%" # Formatting for titles
self.fmtChapter = "%title%" # Formatting for numbered chapters
@@ -113,14 +117,6 @@ class Tokenizer():
# Setters
##
def setComments(self, doComments):
self.doComments = doComments
return
def setKeywords(self, doKeywords):
self.doKeywords = doKeywords
return
def setTitleFormat(self, fmtTitle):
self.fmtTitle = fmtTitle
return
@@ -143,6 +139,26 @@ class Tokenizer():
self.hideSection = hideSection
return
def setBodyText(self, doBodyText):
self.doBodyText = doBodyText
return
def setSynopsis(self, doSynopsis):
self.doSynopsis = doSynopsis
return
def setComments(self, doComments):
self.doComments = doComments
return
def setKeywords(self, doKeywords):
self.doKeywords = doKeywords
return
def setJustify(self, doJustify):
self.doJustify = doJustify
return
##
# Class Methods
##
@@ -207,25 +223,38 @@ class Tokenizer():
[None, self.FMT_U_B, None, self.FMT_U_E]
)]
if self.doJustify:
defAlign = self.A_JUSTIFY
else:
defAlign = self.A_LEFT
self.theTokens = []
for aLine in self.theText.splitlines():
# Tag lines starting with specific characters
if len(aLine.strip()) == 0:
self.theTokens.append((self.T_EMPTY,"",None,self.A_LEFT))
self.theTokens.append((self.T_EMPTY, "", None, self.A_LEFT))
elif aLine[0] == "%":
self.theTokens.append((self.T_COMMENT,aLine[1:].strip(),None,self.A_LEFT))
cLine = aLine[1:].strip()
if cLine.lower().startswith("synopsis:"):
self.theTokens.append((self.T_SYNOPSIS, cLine[9:].strip(), None, defAlign))
else:
self.theTokens.append((self.T_COMMENT, aLine[1:].strip(), None, defAlign))
elif aLine[0] == "@":
self.theTokens.append((self.T_KEYWORD,aLine[1:].strip(),None,self.A_LEFT))
self.theTokens.append((self.T_KEYWORD, aLine[1:].strip(), None, self.A_LEFT))
elif aLine[:2] == "# ":
self.theTokens.append((self.T_HEAD1,aLine[2:].strip(),None,self.A_LEFT))
self.theTokens.append((self.T_HEAD1, aLine[2:].strip(), None, self.A_LEFT))
elif aLine[:3] == "## ":
self.theTokens.append((self.T_HEAD2,aLine[3:].strip(),None,self.A_LEFT))
self.theTokens.append((self.T_HEAD2, aLine[3:].strip(), None, self.A_LEFT))
elif aLine[:4] == "### ":
self.theTokens.append((self.T_HEAD3,aLine[4:].strip(),None,self.A_LEFT))
self.theTokens.append((self.T_HEAD3, aLine[4:].strip(), None, self.A_LEFT))
elif aLine[:5] == "#### ":
self.theTokens.append((self.T_HEAD4,aLine[5:].strip(),None,self.A_LEFT))
self.theTokens.append((self.T_HEAD4, aLine[5:].strip(), None, self.A_LEFT))
else:
if not self.doBodyText:
# Skip all body text
continue
# Otherwise we use RegEx to find formatting tags within a line of text
fmtPos = []
for theRX, theKeys in rxFormats:
@@ -240,11 +269,11 @@ class Tokenizer():
# Save the line as is, but append the array of formatting locations
# sorted by position
fmtPos = sorted(fmtPos,key=itemgetter(0))
self.theTokens.append((self.T_TEXT,aLine,fmtPos,self.A_LEFT))
fmtPos = sorted(fmtPos, key=itemgetter(0))
self.theTokens.append((self.T_TEXT, aLine, fmtPos, defAlign))
# Always add an empty line at the end
self.theTokens.append((self.T_EMPTY,"",None,self.A_LEFT))
self.theTokens.append((self.T_EMPTY, "", None, self.A_LEFT))
return
@@ -283,37 +312,37 @@ class Tokenizer():
if not isUnNum:
self.numChapter += 1
tText = self._formatChapter(tText,isUnNum)
self.theTokens[n] = (tType,tText,None,self.A_LEFT)
self.theTokens[n] = (tType, tText, None, self.A_LEFT)
self.firstScene = True
elif tType == self.T_HEAD3:
tTemp = self._formatScene(tText)
if tTemp == "" and self.hideScene:
self.theTokens[n] = (self.T_EMPTY,"",None,self.A_LEFT)
self.theTokens[n] = (self.T_EMPTY, "", None, self.A_LEFT)
elif tTemp == "" and not self.hideScene:
if self.firstScene:
self.theTokens[n] = (self.T_EMPTY,"",None,self.A_LEFT)
self.theTokens[n] = (self.T_EMPTY, "", None, self.A_LEFT)
else:
self.theTokens[n] = (self.T_SKIP,"",None,self.A_LEFT)
self.theTokens[n] = (self.T_SKIP, "", None, self.A_LEFT)
elif tTemp == self.fmtScene:
if self.firstScene:
self.theTokens[n] = (self.T_EMPTY,"",None,self.A_LEFT)
self.theTokens[n] = (self.T_EMPTY, "", None, self.A_LEFT)
else:
self.theTokens[n] = (self.T_SEP,tTemp,None,self.A_CENTRE)
self.theTokens[n] = (self.T_SEP, tTemp, None, self.A_CENTRE)
else:
self.theTokens[n] = (tType,tTemp,None,self.A_LEFT)
self.theTokens[n] = (tType, tTemp, None, self.A_LEFT)
self.firstScene = False
elif tType == self.T_HEAD4:
tTemp = self._formatSection(tText)
if tTemp == "" and self.hideSection:
self.theTokens[n] = (self.T_EMPTY,"",None,self.A_LEFT)
self.theTokens[n] = (self.T_EMPTY, "", None, self.A_LEFT)
elif tTemp == "" and not self.hideSection:
self.theTokens[n] = (self.T_SKIP,"",None,self.A_LEFT)
self.theTokens[n] = (self.T_SKIP, "", None, self.A_LEFT)
elif tTemp == self.fmtSection:
self.theTokens[n] = (self.T_SEP,tTemp,None,self.A_CENTRE)
self.theTokens[n] = (self.T_SEP, tTemp, None, self.A_CENTRE)
else:
self.theTokens[n] = (tType,tTemp,None,self.A_LEFT)
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
@@ -323,9 +352,9 @@ class Tokenizer():
tType = tToken[0]
tText = tToken[1]
tFormat = tToken[2]
self.theTokens[n] = (tType,tText,tFormat,self.A_CENTRE)
self.theTokens[n] = (tType, tText, tFormat, self.A_CENTRE)
self.theTokens.append((self.T_PBREAK,"",None,self.A_LEFT))
self.theTokens.append((self.T_PBREAK, "", None, self.A_LEFT))
return
+113 -13
View File
@@ -42,7 +42,9 @@ from PyQt5.QtWidgets import (
from nw.gui.additions import QSwitch
from nw.core import ToHtml
from nw.constants import nwConst, nwFiles, nwAlert, nwItemType
from nw.constants import (
nwConst, nwFiles, nwAlert, nwItemType, nwItemLayout, nwItemClass
)
logger = logging.getLogger(__name__)
@@ -70,11 +72,11 @@ class GuiBuildNovel(QDialog):
self.setWindowTitle("Build Project")
self.setMinimumWidth(800)
self.setMinimumHeight(700)
self.setMinimumHeight(800)
self.resize(
self.optState.getInt("GuiBuildNovel", "winWidth", 800),
self.optState.getInt("GuiBuildNovel", "winHeight", 700)
self.optState.getInt("GuiBuildNovel", "winHeight", 800)
)
self.outerBox = QVBoxLayout()
@@ -128,6 +130,21 @@ class GuiBuildNovel(QDialog):
self.titleForm.setColumnStretch(0, 1)
self.titleForm.setColumnStretch(1, 0)
# Text Options
# =============
self.textGroup = QGroupBox("Text Options", self)
self.textForm = QGridLayout(self)
self.textGroup.setLayout(self.textForm)
self.justifyText = QSwitch()
self.justifyText.setChecked(self.optState.getBool("GuiBuildNovel", "justifyText", False))
self.textForm.addWidget(QLabel("Justify text"), 0, 0)
self.textForm.addWidget(self.justifyText, 0, 1)
self.textForm.setColumnStretch(0, 1)
self.textForm.setColumnStretch(1, 0)
# Build Settings
# ==============
self.buildGroup = QGroupBox("Build Overrides", self)
@@ -156,11 +173,11 @@ class GuiBuildNovel(QDialog):
self.includeKeywords = QSwitch()
self.includeKeywords.setChecked(self.theProject.titleFormat["withKeywords"])
self.includeForm.addWidget(QLabel("Include Synopsis"), 0, 0)
self.includeForm.addWidget(QLabel("Include synopsis"), 0, 0)
self.includeForm.addWidget(self.includeSynopsis, 0, 1)
self.includeForm.addWidget(QLabel("Include Comments"), 1, 0)
self.includeForm.addWidget(QLabel("Include comments"), 1, 0)
self.includeForm.addWidget(self.includeComments, 1, 1)
self.includeForm.addWidget(QLabel("Include Keywords"), 2, 0)
self.includeForm.addWidget(QLabel("Include keywords"), 2, 0)
self.includeForm.addWidget(self.includeKeywords, 2, 1)
self.includeForm.setColumnStretch(0, 1)
@@ -179,11 +196,11 @@ class GuiBuildNovel(QDialog):
self.ignoreFlag = QSwitch()
self.ignoreFlag.setChecked(self.optState.getBool("GuiBuildNovel", "ignoreFlag", False))
self.addsForm.addWidget(QLabel("Include Novel Files"), 0, 0)
self.addsForm.addWidget(QLabel("Include novel files"), 0, 0)
self.addsForm.addWidget(self.novelFiles, 0, 1)
self.addsForm.addWidget(QLabel("Include Note Files"), 1, 0)
self.addsForm.addWidget(QLabel("Include note files"), 1, 0)
self.addsForm.addWidget(self.noteFiles, 1, 1)
self.addsForm.addWidget(QLabel("Ignore Export Flag"), 2, 0)
self.addsForm.addWidget(QLabel("Ignore export flag"), 2, 0)
self.addsForm.addWidget(self.ignoreFlag, 2, 1)
self.addsForm.setColumnStretch(0, 1)
@@ -245,6 +262,7 @@ class GuiBuildNovel(QDialog):
# Assemble GUI
# ============
self.toolsBox.addWidget(self.titleGroup)
self.toolsBox.addWidget(self.textGroup)
self.toolsBox.addWidget(self.buildGroup)
self.toolsBox.addWidget(self.includeGroup)
self.toolsBox.addWidget(self.addsGroup)
@@ -280,11 +298,50 @@ class GuiBuildNovel(QDialog):
"""Build a preview of the project in the document viewer.
"""
makeHtml = ToHtml(self.theProject, self.theParent)
self.htmlText = ""
# Get Settings
fmtTitle = self.fmtTitle.text().strip()
fmtChapter = self.fmtChapter.text().strip()
fmtUnnumbered = self.fmtUnnumbered.text().strip()
fmtScene = self.fmtScene.text().strip()
fmtSection = self.fmtSection.text().strip()
justifyText = self.justifyText.isChecked()
outlineMode = self.outlineMode.isChecked()
incSynopsis = self.includeSynopsis.isChecked()
incComments = self.includeComments.isChecked()
incKeywords = self.includeKeywords.isChecked()
novelFiles = self.novelFiles.isChecked()
noteFiles = self.noteFiles.isChecked()
ignoreFlag = self.ignoreFlag.isChecked()
doBodyText = True
for tItem in self.theProject.projTree:
if tItem is not None and tItem.itemType == nwItemType.FILE:
if outlineMode:
fmtTitle = "%title%"
fmtChapter = "Chapter: %title%"
fmtUnnumbered = "Chapter: %title%"
fmtScene = "Scene: %title%"
fmtSection = "Section: %title%"
doBodyText = False
incSynopsis = True
novelFiles = True
noteFiles = False
makeHtml = ToHtml(self.theProject, self.theParent)
makeHtml.setTitleFormat(fmtTitle)
makeHtml.setChapterFormat(fmtChapter)
makeHtml.setUnNumberedFormat(fmtUnnumbered)
makeHtml.setSceneFormat(fmtScene, fmtScene == "")
makeHtml.setSectionFormat(fmtSection, fmtSection == "")
makeHtml.setBodyText(doBodyText)
makeHtml.setSynopsis(incSynopsis)
makeHtml.setComments(incComments)
makeHtml.setKeywords(incKeywords)
makeHtml.setJustify(justifyText)
self.htmlText = ""
self.buildProgress.setMaximum(len(self.theProject.projTree))
self.buildProgress.setValue(0)
for nItt, tItem in enumerate(self.theProject.projTree):
if self._checkInclude(tItem, noteFiles, novelFiles, ignoreFlag):
makeHtml.setText(tItem.itemHandle)
makeHtml.doAutoReplace()
makeHtml.tokenizeText()
@@ -292,11 +349,49 @@ class GuiBuildNovel(QDialog):
makeHtml.doConvert()
makeHtml.doPostProcessing()
self.htmlText += makeHtml.getResult()
self.buildProgress.setValue(nItt+1)
self.docView.setHtml(self.htmlText)
return
def _checkInclude(self, theItem, noteFiles, novelFiles, ignoreFlag):
"""This function checks whether a file should be included in the
export or not. For standard note and novel files, this is
controlled by the options selected by the user. For other files
classified as non-exportable, a few checks must be made, and the
following are not:
* Items that are not actual files.
* Items that have been orphaned which are tagged as NO_LAYOUT
and NO_CLASS.
* Items that appear in the TRASH folder or have parent set to
None (orphaned files).
"""
if theItem is None:
return False
if not theItem.isExported and not ignoreFlag:
return False
isNone = theItem.itemType != nwItemType.FILE
isNone |= theItem.itemLayout == nwItemLayout.NO_LAYOUT
isNone |= theItem.itemClass == nwItemClass.NO_CLASS
isNone |= theItem.itemClass == nwItemClass.TRASH
isNone |= theItem.parHandle == self.theProject.projTree.trashRoot()
isNone |= theItem.parHandle is None
isNote = theItem.itemLayout == nwItemLayout.NOTE
isNovel = not isNone and not isNote
if isNone:
return False
if isNote and not noteFiles:
return False
if isNovel and not novelFiles:
return False
return True
def _saveDocument(self, theFormat):
"""Save the document to various formats.
"""
@@ -474,6 +569,7 @@ class GuiBuildNovel(QDialog):
# GUI Settings
self.optState.setValue("GuiBuildNovel", "winWidth", self.width())
self.optState.setValue("GuiBuildNovel", "winHeight", self.height())
self.optState.setValue("GuiBuildNovel", "justifyText", self.justifyText.isChecked())
self.optState.setValue("GuiBuildNovel", "outlineMode", self.outlineMode.isChecked())
self.optState.setValue("GuiBuildNovel", "addNovel", self.novelFiles.isChecked())
self.optState.setValue("GuiBuildNovel", "addNotes", self.noteFiles.isChecked())
@@ -557,6 +653,10 @@ class GuiBuildNovelDocView(QTextBrowser):
"mark {"
" background-color: rgb(240, 198, 116);"
"}\n"
".tags {"
" color: rgb(245, 135, 31);"
" font-wright: bold;"
"}\n"
)
self.qDocument.setDefaultStyleSheet(styleSheet)
+2 -2
View File
@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="0.5" fileVersion="1.0" saveCount="240" autoCount="28" timeStamp="2020-05-10 21:26:54">
<novelWriterXML appVersion="0.5" fileVersion="1.0" saveCount="257" autoCount="31" timeStamp="2020-05-10 22:50:38">
<project>
<name>Sample Project</name>
<title>Sample Project</title>
@@ -20,7 +20,7 @@
</autoReplace>
<titleFormat>
<title>%title%</title>
<chapter>Chapter %num%\\%title%</chapter>
<chapter>Chapter %num%.\\%title%</chapter>
<unnumbered>%title%</unnumbered>
<scene>* * *</scene>
<section></section>