From a5729cf0a9a8bda6544e8f1254cb1e016a39556a Mon Sep 17 00:00:00 2001
From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com>
Date: Sat, 23 May 2020 17:59:07 +0200
Subject: [PATCH] Cleaned up html export
---
nw/core/tohtml.py | 141 +++++++++++++++++++++++++++++++------------
nw/core/tokenizer.py | 82 +++++++++++--------------
nw/gui/build.py | 57 +++++++++--------
3 files changed, 166 insertions(+), 114 deletions(-)
diff --git a/nw/core/tohtml.py b/nw/core/tohtml.py
index 12d34404..0fb517b8 100644
--- a/nw/core/tohtml.py
+++ b/nw/core/tohtml.py
@@ -48,20 +48,16 @@ class ToHtml(Tokenizer):
"<" : "<",
">" : ">",
"&" : "&",
- "\t" : " ",
+ "\t" : " "*2,
nwUnicode.U_ENDASH : nwUnicode.H_ENDASH,
nwUnicode.U_EMDASH : nwUnicode.H_EMDASH,
nwUnicode.U_HELLIP : nwUnicode.H_HELLIP,
nwUnicode.U_NBSP : nwUnicode.H_NBSP,
}
- self.revDict = dict(map(reversed, self.repDict.items()))
-
- self.reReplace = re.compile(
- "|".join([re.escape(k) for k in self.repDict.keys()]), flags=re.DOTALL
- )
- self.reReverse = re.compile(
- "|".join([re.escape(k) for k in self.revDict.keys()]), flags=re.DOTALL
- )
+ self.revDict = {}
+ self.reReplace = []
+ self.reReverse = []
+ self._buildRegEx()
return
@@ -79,6 +75,7 @@ class ToHtml(Tokenizer):
self.doKeywords = True
self.doComments = doComments
self.repDict["\t"] = " "*8
+ self._buildRegEx()
return
##
@@ -120,40 +117,61 @@ class ToHtml(Tokenizer):
self.FMT_U_E : "",
}
+ if self.isNovel and self.genMode != self.M_PREVIEW:
+ # For novel files for export, we bump the titles one level
+ # up, as this is more useful for printing and word processor
+ # imports.
+ h1 = "h1 class=\"title\""
+ h2 = "h1"
+ h3 = "h2"
+ h4 = "h3"
+ else:
+ h1 = "h1"
+ h2 = "h2"
+ h3 = "h3"
+ h4 = "h4"
+
+ alignHead = self.A_LEFT
+ if self.doJustify:
+ alignPar = self.A_JUSTIFY
+ else:
+ alignPar = self.A_LEFT
+
self.theResult = ""
thisPar = []
parStyle = None
tmpResult = []
+ hasHardBreak = False
for tType, tText, tFormat, tStyle in self.theTokens:
# Styles
aStyle = []
if tStyle is not None:
- if tStyle & self.A_LEFT:
- aStyle.append("text-align: left;")
- if tStyle & self.A_RIGHT:
- aStyle.append("text-align: right;")
+ # if tStyle & self.A_LEFT:
+ # aStyle.append("text-align: left;")
+ # if tStyle & self.A_RIGHT:
+ # aStyle.append("text-align: right;")
if tStyle & self.A_CENTRE:
aStyle.append("text-align: center;")
- if tStyle & self.A_JUSTIFY:
- aStyle.append("text-align: justify;")
- if tStyle & self.A_PBB:
- aStyle.append("page-break-before: always;")
- if tStyle & self.A_PBB_L:
- aStyle.append("page-break-before: left;")
- if tStyle & self.A_PBB_R:
- aStyle.append("page-break-before: right;")
- if tStyle & self.A_PBB_AV:
- aStyle.append("page-break-before: avoid;")
- if tStyle & self.A_PBA:
- aStyle.append("page-break-after: always;")
- if tStyle & self.A_PBA_L:
- aStyle.append("page-break-after: left;")
- if tStyle & self.A_PBA_R:
- aStyle.append("page-break-after: right;")
- if tStyle & self.A_PBA_AV:
- aStyle.append("page-break-after: avoid;")
+ # if tStyle & self.A_JUSTIFY:
+ # aStyle.append("text-align: justify;")
+ # if tStyle & self.A_PBB:
+ # aStyle.append("page-break-before: always;")
+ # if tStyle & self.A_PBB_L:
+ # aStyle.append("page-break-before: left;")
+ # if tStyle & self.A_PBB_R:
+ # aStyle.append("page-break-before: right;")
+ # if tStyle & self.A_PBB_AV:
+ # aStyle.append("page-break-before: avoid;")
+ # if tStyle & self.A_PBA:
+ # aStyle.append("page-break-after: always;")
+ # if tStyle & self.A_PBA_L:
+ # aStyle.append("page-break-after: left;")
+ # if tStyle & self.A_PBA_R:
+ # aStyle.append("page-break-after: right;")
+ # if tStyle & self.A_PBA_AV:
+ # aStyle.append("page-break-after: avoid;")
if len(aStyle) > 0:
hStyle = " style='%s'" % (" ".join(aStyle))
@@ -164,33 +182,42 @@ class ToHtml(Tokenizer):
if tType == self.T_EMPTY:
if parStyle is None:
parStyle = ""
+ if hasHardBreak:
+ parClass = " class='break'"
+ else:
+ parClass = ""
if len(thisPar) > 0:
tTemp = "".join(thisPar)
- tmpResult.append("
%s
\n" % (parStyle, tTemp.rstrip()))
+ tmpResult.append("%s
\n" % (parStyle, parClass, tTemp.rstrip()))
thisPar = []
parStyle = None
+ hasHardBreak = False
+
+ elif tType == self.T_TITLE:
+ tHead = tText.replace(r"\\", "
")
+ tmpResult.append("\n" % (hStyle, tHead))
elif tType == self.T_HEAD1:
tHead = tText.replace(r"\\", "
")
- tmpResult.append("%s
\n" % (hStyle, tHead))
+ tmpResult.append("<%s%s>%s%s>\n" % (h1, hStyle, tHead, h1))
elif tType == self.T_HEAD2:
tHead = tText.replace(r"\\", "
")
- tmpResult.append("%s
\n" % (hStyle, tHead))
+ tmpResult.append("<%s%s>%s%s>\n" % (h2, hStyle, tHead, h2))
elif tType == self.T_HEAD3:
tHead = tText.replace(r"\\", "
")
- tmpResult.append("%s
\n" % (hStyle, tHead))
+ tmpResult.append("<%s%s>%s%s>\n" % (h3, hStyle, tHead, h3))
elif tType == self.T_HEAD4:
tHead = tText.replace(r"\\", "
")
- tmpResult.append("%s
\n" % (hStyle, tHead))
+ tmpResult.append("<%s%s>%s%s>\n" % (h4, hStyle, tHead, h4))
elif tType == self.T_SEP:
- tmpResult.append("%s
\n" % (hStyle, tText))
+ tmpResult.append("%s
\n" % tText)
elif tType == self.T_SKIP:
- tmpResult.append("
\n" % hStyle)
+ tmpResult.append("
\n")
elif tType == self.T_TEXT:
tTemp = tText
@@ -200,6 +227,7 @@ class ToHtml(Tokenizer):
tTemp = tTemp[:xPos]+htmlTags[xFmt]+tTemp[xPos+xLen:]
if tText.endswith(" "):
thisPar.append(tTemp.rstrip()+"
")
+ hasHardBreak = True
else:
thisPar.append(tTemp.rstrip()+" ")
@@ -217,6 +245,30 @@ class ToHtml(Tokenizer):
return
+ def getStylesheet(self):
+ """Generate a stylesheet appropriate for the current settings.
+ """
+ theStyles = []
+
+ if self.doJustify:
+ theStyles.append(r"p {text-align: justify;}")
+ else:
+ theStyles.append(r"p {text-align: left;}")
+
+ theStyles.append(r"h1, h2 {color: rgb(66, 113, 174);}")
+ theStyles.append(r"h3, h4 {color: rgb(50, 50, 50);}")
+ theStyles.append(r"h1, h2, h3, h4 {page-break-after: avoid;}")
+ theStyles.append(r"h1 {page-break-before: always;}")
+ theStyles.append(r".title {font-size: 2.5em; font-weight: bold; page-break-before: never;}")
+ theStyles.append(r".tags {color: rgb(245, 135, 31); font-weight: bold;}")
+ theStyles.append(r".break {text-align: left;}")
+ theStyles.append(r".sep {text-align: center; margin-top: 1em; margin-bottom: 1em;}")
+ theStyles.append(r".skip {margin-top: 1em; margin-bottom: 1em;}")
+ theStyles.append(r".synopsis {font-style: italic;}")
+ theStyles.append(r".comment {font-style: italic; color: rgb(100, 100, 100);}")
+
+ return theStyles
+
##
# Internal Functions
##
@@ -240,7 +292,6 @@ class ToHtml(Tokenizer):
def _formatKeywords(self, tText):
"""Apply HTML formatting to keywords.
"""
-
tText = "@"+tText
isValid, theBits, thePos = self.theParent.theIndex.scanThis(tText)
if not isValid or not theBits:
@@ -270,4 +321,16 @@ class ToHtml(Tokenizer):
return "%s
" % retText
+ def _buildRegEx(self):
+ """Build the regular expressions
+ """
+ self.revDict = dict(map(reversed, self.repDict.items()))
+ self.reReplace = re.compile(
+ "|".join([re.escape(k) for k in self.repDict.keys()]), flags=re.DOTALL
+ )
+ self.reReverse = re.compile(
+ "|".join([re.escape(k) for k in self.revDict.keys()]), flags=re.DOTALL
+ )
+ return
+
# END Class ToHtml
diff --git a/nw/core/tokenizer.py b/nw/core/tokenizer.py
index a3c04bd8..8773cdb1 100644
--- a/nw/core/tokenizer.py
+++ b/nw/core/tokenizer.py
@@ -51,26 +51,23 @@ class Tokenizer():
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_TITLE = 5 # Title
+ T_HEAD1 = 6 # Header 1
+ T_HEAD2 = 7 # Header 2
+ T_HEAD3 = 8 # Header 3
+ T_HEAD4 = 9 # Header 4
+ T_TEXT = 10 # Text line
+ T_SEP = 11 # Scene separator
+ T_SKIP = 12 # Paragraph break
- A_LEFT = 1 # Left aligned
- A_RIGHT = 2 # Right aligned
- A_CENTRE = 4 # Centred
- A_JUSTIFY = 8 # Justified
- A_PBB = 16 # Page break before
- A_PBB_L = 32 # Page break before, left
- A_PBB_R = 64 # Page break before, right
- A_PBB_AV = 128 # Page break, avoid
- A_PBA = 256 # Page break after
- A_PBA_L = 512 # Page break after, left
- A_PBA_R = 1024 # Page break after, right
- A_PBA_AV = 2048 # Page break, avoid
+ A_LEFT = 1 # Left aligned
+ A_RIGHT = 2 # Right aligned
+ A_CENTRE = 4 # Centred
+ A_JUSTIFY = 8 # Justified
+ A_PBB = 16 # Page break before
+ A_PBB_AV = 32 # Page break, avoid
+ A_PBA = 64 # Page break after
+ A_PBA_AV = 128 # Page break, avoid
def __init__(self, theProject, theParent):
@@ -283,11 +280,6 @@ 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 = []
self.theMarkdown = ""
tmpMarkdown = []
@@ -304,45 +296,45 @@ class Tokenizer():
cLine = aLine[1:].strip()
if cLine.lower().startswith("synopsis:"):
self.theTokens.append((
- self.T_SYNOPSIS, cLine[9:].strip(), None, defAlign
+ self.T_SYNOPSIS, cLine[9:].strip(), None, None
))
if self.doSynopsis:
tmpMarkdown.append("%s\n" % aLine)
else:
self.theTokens.append((
- self.T_COMMENT, aLine[1:].strip(), None, defAlign
+ self.T_COMMENT, aLine[1:].strip(), None, None
))
if self.doComments:
tmpMarkdown.append("%s\n" % aLine)
elif aLine[0] == "@":
self.theTokens.append((
- self.T_KEYWORD, aLine[1:].strip(), None, self.A_LEFT
+ self.T_KEYWORD, aLine[1:].strip(), None, None
))
if self.doKeywords:
tmpMarkdown.append("%s\n" % aLine)
elif aLine[:2] == "# ":
self.theTokens.append((
- self.T_HEAD1, aLine[2:].strip(), None, self.A_LEFT | self.A_PBB
+ self.T_HEAD1, aLine[2:].strip(), None, None
))
tmpMarkdown.append("%s\n" % aLine)
elif aLine[:3] == "## ":
self.theTokens.append((
- self.T_HEAD2, aLine[3:].strip(), None, self.A_LEFT | self.A_PBA_AV
+ self.T_HEAD2, aLine[3:].strip(), None, None
))
tmpMarkdown.append("%s\n" % aLine)
elif aLine[:4] == "### ":
self.theTokens.append((
- self.T_HEAD3, aLine[4:].strip(), None, self.A_LEFT | self.A_PBA_AV
+ self.T_HEAD3, aLine[4:].strip(), None, None
))
tmpMarkdown.append("%s\n" % aLine)
elif aLine[:5] == "#### ":
self.theTokens.append((
- self.T_HEAD4, aLine[5:].strip(), None, self.A_LEFT | self.A_PBA_AV
+ self.T_HEAD4, aLine[5:].strip(), None, None
))
tmpMarkdown.append("%s\n" % aLine)
@@ -366,14 +358,9 @@ class Tokenizer():
# Save the line as is, but append the array of formatting locations
# sorted by position
fmtPos = sorted(fmtPos, key=itemgetter(0))
- if aLine.endswith(" "):
- self.theTokens.append((
- self.T_TEXT, aLine, fmtPos, self.A_LEFT
- ))
- else:
- self.theTokens.append((
- self.T_TEXT, aLine, fmtPos, defAlign
- ))
+ self.theTokens.append((
+ self.T_TEXT, aLine, fmtPos, None
+ ))
tmpMarkdown.append("%s\n" % aLine)
# Always add an empty line at the end
@@ -415,7 +402,7 @@ class Tokenizer():
tText = self._formatHeading(self.fmtTitle, tText)
self.theTokens[n] = (
- tType, tText, None, self.A_LEFT | self.A_PBB_R
+ tType, tText, None, None
)
elif tType == self.T_HEAD2:
@@ -431,7 +418,7 @@ class Tokenizer():
# Format the chapter header
self.theTokens[n] = (
- tType, tText, None, self.A_LEFT | self.A_PBB_R
+ tType, tText, None, None
)
# Set scene variables
@@ -470,7 +457,7 @@ class Tokenizer():
)
else:
self.theTokens[n] = (
- tType, tTemp, None, self.A_LEFT | self.A_PBA_AV
+ tType, tTemp, None, None
)
# Definitely no longer the first scene
@@ -495,21 +482,24 @@ class Tokenizer():
)
else:
self.theTokens[n] = (
- tType, tTemp, None, self.A_LEFT | self.A_PBA_AV
+ tType, tTemp, None, None
)
# For title page and partitions, we need to centre all text.
# For partition, we also add a page break before, and for
# both types we always add a page break after the content.
+ # We also swap header level 1 with a title type instead.
if self.isTitle or self.isPart:
for n, tToken in enumerate(self.theTokens):
tType = tToken[0]
tText = tToken[1]
tFormat = tToken[2]
if self.isTitle:
- self.theTokens[n] = (
- tType, tText, tFormat, self.A_CENTRE
- )
+ if tType == self.T_HEAD1:
+ tType = self.T_TITLE
+ self.theTokens[n] = (
+ tType, tText, tFormat, self.A_CENTRE
+ )
# Add a page break after the last entry
n = len(self.theTokens) - 1
diff --git a/nw/gui/build.py b/nw/gui/build.py
index 737a13c4..8baf00fc 100644
--- a/nw/gui/build.py
+++ b/nw/gui/build.py
@@ -70,6 +70,7 @@ class GuiBuildNovel(QDialog):
self.optState = self.theProject.optState
self.htmlText = [] # List of html document
+ self.htmlStyle = [] # List of html styles
self.nwdText = [] # List of markdown documents
self.textLayout = [] # List of nwItemLayout entries
@@ -320,6 +321,7 @@ class GuiBuildNovel(QDialog):
tStart = time()
self.htmlText = []
+ self.htmlStyle = []
self.nwdText = []
self.textLayout = []
@@ -340,9 +342,11 @@ class GuiBuildNovel(QDialog):
tEnd = time()
logger.debug("Built project in %.3f ms" % (1000*(tEnd-tStart)))
+ self.htmlStyle = makeHtml.getStylesheet()
# Load the preview document with the html data
- self.docView.setHtml("".join(self.htmlText))
+ self.docView.setStyleSheet(self.htmlStyle)
+ self.docView.setContent(self.htmlText)
return
@@ -484,6 +488,9 @@ class GuiBuildNovel(QDialog):
outFile.write("\n")
outFile.write("\n")
outFile.write("\n")
+ outFile.write("\n")
outFile.write("\n")
outFile.write("\n")
for aLine in self.htmlText:
@@ -612,7 +619,8 @@ class GuiBuildNovel(QDialog):
if path.isfile(docPath):
with open(docPath, mode="r", encoding="utf8") as inFile:
helpText = inFile.read()
- self.docView.setText(helpText)
+ self.docView.setStyleSheet()
+ self.docView.setContent(helpText)
else:
self.theParent.makeAlert(
"Could not open help text file for Build Project.", nwAlert.ERROR
@@ -648,7 +656,7 @@ class GuiBuildNovelDocView(QTextBrowser):
docPalette.setColor(QPalette.Text, QColor( 0, 0, 0))
self.setPalette(docPalette)
- self._makeStyleSheet()
+ self.setStyleSheet()
self.show()
@@ -656,35 +664,26 @@ class GuiBuildNovelDocView(QTextBrowser):
return
- def setText(self, theText):
- self.setHtml(theText)
+ def setContent(self, theText):
+ """Set the content, either from text or list of text.
+ """
+ if isinstance(theText, str):
+ self.setHtml(theText)
+ else:
+ self.setHtml("".join(theText))
return
- ##
- # Internal Functions
- ##
+ def setStyleSheet(self, theStyles=[]):
+ """Set the stylesheet for the preview document.
+ """
+ if not theStyles:
+ theStyles.append(r"h1, h2 {color: rgb(66, 113, 174);}")
+ theStyles.append(r"h3, h4 {color: rgb(50, 50, 50);}")
+ theStyles.append(r"a {color: rgb(137, 89, 168);}")
+ theStyles.append(r"mark {background-color: rgb(240, 198, 116);}")
+ theStyles.append(r".tags {color: rgb(245, 135, 31); font-weight: bold;}")
- def _makeStyleSheet(self):
-
- styleSheet = (
- "h1, h2 {"
- " color: rgb(66, 113, 174);"
- "}\n"
- "h3, h4 {"
- " color: rgb(50, 50, 50);"
- "}\n"
- "a {"
- " color: rgb(137, 89, 168);"
- "}\n"
- "mark {"
- " background-color: rgb(240, 198, 116);"
- "}\n"
- ".tags {"
- " color: rgb(245, 135, 31);"
- " font-wright: bold;"
- "}\n"
- )
- self.qDocument.setDefaultStyleSheet(styleSheet)
+ self.qDocument.setDefaultStyleSheet("\n".join(theStyles))
return