Make HTML output more consistent (#2404)
This commit is contained in:
@@ -159,34 +159,33 @@ class ToHtml(Tokenizer):
|
|||||||
# If we don't have formatting, we can do a plain replace
|
# If we don't have formatting, we can do a plain replace
|
||||||
tText = tText.replace("<", "<").replace(">", ">")
|
tText = tText.replace("<", "<").replace(">", ">")
|
||||||
|
|
||||||
# Styles
|
# Inline Styles
|
||||||
aStyle = []
|
aStyle = []
|
||||||
if self._cssStyles:
|
if tStyle & BlockFmt.LEFT:
|
||||||
if tStyle & BlockFmt.LEFT:
|
aStyle.append("text-align: left;")
|
||||||
aStyle.append("text-align: left;")
|
elif tStyle & BlockFmt.RIGHT:
|
||||||
elif tStyle & BlockFmt.RIGHT:
|
aStyle.append("text-align: right;")
|
||||||
aStyle.append("text-align: right;")
|
elif tStyle & BlockFmt.CENTRE:
|
||||||
elif tStyle & BlockFmt.CENTRE:
|
aStyle.append("text-align: center;")
|
||||||
aStyle.append("text-align: center;")
|
elif tStyle & BlockFmt.JUSTIFY:
|
||||||
elif tStyle & BlockFmt.JUSTIFY:
|
aStyle.append("text-align: justify;")
|
||||||
aStyle.append("text-align: justify;")
|
|
||||||
|
|
||||||
if tStyle & BlockFmt.PBB:
|
if tStyle & BlockFmt.PBB:
|
||||||
aStyle.append("page-break-before: always;")
|
aStyle.append("page-break-before: always;")
|
||||||
if tStyle & BlockFmt.PBA:
|
if tStyle & BlockFmt.PBA:
|
||||||
aStyle.append("page-break-after: always;")
|
aStyle.append("page-break-after: always;")
|
||||||
|
|
||||||
if tStyle & BlockFmt.Z_BTM:
|
if tStyle & BlockFmt.Z_BTM:
|
||||||
aStyle.append("margin-bottom: 0;")
|
aStyle.append("margin-bottom: 0;")
|
||||||
if tStyle & BlockFmt.Z_TOP:
|
if tStyle & BlockFmt.Z_TOP:
|
||||||
aStyle.append("margin-top: 0;")
|
aStyle.append("margin-top: 0;")
|
||||||
|
|
||||||
if tStyle & BlockFmt.IND_L:
|
if tStyle & BlockFmt.IND_L:
|
||||||
aStyle.append(f"margin-left: {self._blockIndent:.2f}em;")
|
aStyle.append(f"margin-left: {self._blockIndent:.2f}em;")
|
||||||
if tStyle & BlockFmt.IND_R:
|
if tStyle & BlockFmt.IND_R:
|
||||||
aStyle.append(f"margin-right: {self._blockIndent:.2f}em;")
|
aStyle.append(f"margin-right: {self._blockIndent:.2f}em;")
|
||||||
if tStyle & BlockFmt.IND_T:
|
if tStyle & BlockFmt.IND_T:
|
||||||
aStyle.append(f"text-indent: {self._firstWidth:.2f}em;")
|
aStyle.append(f"text-indent: {self._firstWidth:.2f}em;")
|
||||||
|
|
||||||
if aStyle:
|
if aStyle:
|
||||||
stVals = " ".join(aStyle)
|
stVals = " ".join(aStyle)
|
||||||
@@ -288,26 +287,25 @@ class ToHtml(Tokenizer):
|
|||||||
json.dump(data, fObj, indent=2)
|
json.dump(data, fObj, indent=2)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
html = []
|
||||||
|
html.append("<!DOCTYPE html>")
|
||||||
|
html.append("<html>")
|
||||||
|
html.append("<head>")
|
||||||
|
html.append(f"<title>{self._project.data.name:s}</title>")
|
||||||
|
html.append("<meta charset='utf-8'>")
|
||||||
|
if self._cssStyles:
|
||||||
|
html.append("<meta name='viewport' content='width=device-width, initial-scale=1'>")
|
||||||
|
html.append("<style>")
|
||||||
|
html.extend(self.getStyleSheet())
|
||||||
|
html.append("</style>")
|
||||||
|
html.append("</head>")
|
||||||
|
html.append("<body>")
|
||||||
|
html.append(("".join(self._pages)).replace("\t", "	").rstrip())
|
||||||
|
html.append("</body>")
|
||||||
|
html.append("</html>\n")
|
||||||
|
|
||||||
with open(path, mode="w", encoding="utf-8") as fObj:
|
with open(path, mode="w", encoding="utf-8") as fObj:
|
||||||
fObj.write((
|
fObj.write("\n".join(html))
|
||||||
"<!DOCTYPE html>\n"
|
|
||||||
"<html>\n"
|
|
||||||
"<head>\n"
|
|
||||||
"<meta charset='utf-8'>\n"
|
|
||||||
"<title>{title:s}</title>\n"
|
|
||||||
"<style>\n"
|
|
||||||
"{style:s}\n"
|
|
||||||
"</style>\n"
|
|
||||||
"</head>\n"
|
|
||||||
"<body>\n"
|
|
||||||
"{body:s}\n"
|
|
||||||
"</body>\n"
|
|
||||||
"</html>\n"
|
|
||||||
).format(
|
|
||||||
title=self._project.data.name,
|
|
||||||
style="\n".join(self.getStyleSheet()),
|
|
||||||
body=("".join(self._pages)).replace("\t", "	").rstrip(),
|
|
||||||
))
|
|
||||||
|
|
||||||
logger.info("Wrote file: %s", path)
|
logger.info("Wrote file: %s", path)
|
||||||
|
|
||||||
@@ -410,7 +408,11 @@ class ToHtml(Tokenizer):
|
|||||||
# isn't already open, and only closed if it has previously been opened.
|
# isn't already open, and only closed if it has previously been opened.
|
||||||
tags: list[tuple[int, str]] = []
|
tags: list[tuple[int, str]] = []
|
||||||
state = dict.fromkeys(HTML_OPENER, False)
|
state = dict.fromkeys(HTML_OPENER, False)
|
||||||
|
plain = not self._cssStyles
|
||||||
for pos, fmt, data in tFmt:
|
for pos, fmt, data in tFmt:
|
||||||
|
if plain and fmt in (TextFmt.COL_B, TextFmt.COL_E):
|
||||||
|
# We ignore colour tags if CSS is off
|
||||||
|
continue
|
||||||
if m := HTML_OPENER.get(fmt):
|
if m := HTML_OPENER.get(fmt):
|
||||||
if not state.get(fmt, True):
|
if not state.get(fmt, True):
|
||||||
if fmt == TextFmt.COL_B and (color := self._classes.get(data)):
|
if fmt == TextFmt.COL_B and (color := self._classes.get(data)):
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset='utf-8'>
|
|
||||||
<title>Lorem Ipsum</title>
|
<title>Lorem Ipsum</title>
|
||||||
|
<meta charset='utf-8'>
|
||||||
|
<meta name='viewport' content='width=device-width, initial-scale=1'>
|
||||||
<style>
|
<style>
|
||||||
body {color: #000000; font-family: 'Arial'; font-size: 12pt; font-weight: 400; font-style: normal;}
|
body {color: #000000; font-family: 'Arial'; font-size: 12pt; font-weight: 400; font-style: normal;}
|
||||||
p {text-align: left; line-height: 150%; margin-top: 0.00em; margin-bottom: 0.60em;}
|
p {text-align: left; line-height: 150%; margin-top: 0.00em; margin-bottom: 0.60em;}
|
||||||
|
|||||||
@@ -147,9 +147,6 @@ def testFmtToHtml_ConvertParagraphs(mockGUI):
|
|||||||
html._isNovel = True
|
html._isNovel = True
|
||||||
html._isFirst = True
|
html._isFirst = True
|
||||||
|
|
||||||
# Paragraphs
|
|
||||||
# ==========
|
|
||||||
|
|
||||||
# Text
|
# Text
|
||||||
html._text = "Some **nested bold and _italic_ and ~~strikethrough~~ text** here\n"
|
html._text = "Some **nested bold and _italic_ and ~~strikethrough~~ text** here\n"
|
||||||
html.tokenizeText()
|
html.tokenizeText()
|
||||||
@@ -225,12 +222,24 @@ def testFmtToHtml_ConvertParagraphs(mockGUI):
|
|||||||
"</p>\n"
|
"</p>\n"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Keywords
|
|
||||||
|
@pytest.mark.core
|
||||||
|
def testFmtToHtml_ConvertMeta(mockGUI):
|
||||||
|
"""Test meta data formats in the ToHtml class."""
|
||||||
|
project = NWProject()
|
||||||
|
html = ToHtml(project)
|
||||||
|
html.initDocument()
|
||||||
|
html._isNovel = True
|
||||||
|
html._isFirst = True
|
||||||
|
|
||||||
|
# Keywords Disabled
|
||||||
|
html.setKeywords(False)
|
||||||
html._text = "@char: Bod, Jane\n"
|
html._text = "@char: Bod, Jane\n"
|
||||||
html.tokenizeText()
|
html.tokenizeText()
|
||||||
html.doConvert()
|
html.doConvert()
|
||||||
assert html._pages[-1] == ""
|
assert html._pages[-1] == ""
|
||||||
|
|
||||||
|
# Keywords Enabled
|
||||||
html.setKeywords(True)
|
html.setKeywords(True)
|
||||||
html._text = "@char: Bod, Jane\n"
|
html._text = "@char: Bod, Jane\n"
|
||||||
html.tokenizeText()
|
html.tokenizeText()
|
||||||
@@ -242,7 +251,8 @@ def testFmtToHtml_ConvertParagraphs(mockGUI):
|
|||||||
"<span style='color: #4271ae'><a href='#tag_jane'>Jane</a></span></p>\n"
|
"<span style='color: #4271ae'><a href='#tag_jane'>Jane</a></span></p>\n"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Tags
|
# Tags w/Colour
|
||||||
|
html.setStyles(True)
|
||||||
html._text = "@tag: Bod\n"
|
html._text = "@tag: Bod\n"
|
||||||
html.tokenizeText()
|
html.tokenizeText()
|
||||||
html.doConvert()
|
html.doConvert()
|
||||||
@@ -260,7 +270,25 @@ def testFmtToHtml_ConvertParagraphs(mockGUI):
|
|||||||
"<span style='color: #4271ae'>Nobody Owens</span></p>\n"
|
"<span style='color: #4271ae'>Nobody Owens</span></p>\n"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Multiple Keywords
|
# Tags wo/Colour
|
||||||
|
html.setStyles(False)
|
||||||
|
html._text = "@tag: Bod\n"
|
||||||
|
html.tokenizeText()
|
||||||
|
html.doConvert()
|
||||||
|
assert html._pages[-1] == (
|
||||||
|
"<p class='meta meta-tag'><strong>Tag:</strong> <a name='tag_bod'>Bod</a></p>\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
html._text = "@tag: Bod | Nobody Owens\n"
|
||||||
|
html.tokenizeText()
|
||||||
|
html.doConvert()
|
||||||
|
assert html._pages[-1] == (
|
||||||
|
"<p class='meta meta-tag'><strong>Tag:</strong> "
|
||||||
|
"<a name='tag_bod'>Bod</a> | Nobody Owens</p>\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Multiple Keywords w/Colour
|
||||||
|
html.setStyles(True)
|
||||||
html._isFirst = False
|
html._isFirst = False
|
||||||
html.setKeywords(True)
|
html.setKeywords(True)
|
||||||
html._text = "## Chapter\n\n@pov: Bod\n@plot: Main\n@location: Europe\n\n"
|
html._text = "## Chapter\n\n@pov: Bod\n@plot: Main\n@location: Europe\n\n"
|
||||||
@@ -279,6 +307,23 @@ def testFmtToHtml_ConvertParagraphs(mockGUI):
|
|||||||
"<span style='color: #4271ae'><a href='#tag_europe'>Europe</a></span></p>\n"
|
"<span style='color: #4271ae'><a href='#tag_europe'>Europe</a></span></p>\n"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Multiple Keywords wo/Colour
|
||||||
|
html.setStyles(False)
|
||||||
|
html._isFirst = False
|
||||||
|
html.setKeywords(True)
|
||||||
|
html._text = "## Chapter\n\n@pov: Bod\n@plot: Main\n@location: Europe\n\n"
|
||||||
|
html.tokenizeText()
|
||||||
|
html.doConvert()
|
||||||
|
assert html._pages[-1] == (
|
||||||
|
"<h1 style='page-break-before: always;'>Chapter</h1>\n"
|
||||||
|
"<p class='meta meta-pov' style='margin-bottom: 0;'>"
|
||||||
|
"<strong>Point of View:</strong> <a href='#tag_bod'>Bod</a></p>\n"
|
||||||
|
"<p class='meta meta-plot' style='margin-bottom: 0; margin-top: 0;'>"
|
||||||
|
"<strong>Plot:</strong> <a href='#tag_main'>Main</a></p>\n"
|
||||||
|
"<p class='meta meta-location' style='margin-top: 0;'>"
|
||||||
|
"<strong>Locations:</strong> <a href='#tag_europe'>Europe</a></p>\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.core
|
@pytest.mark.core
|
||||||
def testFmtToHtml_Dialog(mockGUI):
|
def testFmtToHtml_Dialog(mockGUI):
|
||||||
@@ -440,17 +485,6 @@ def testFmtToHtml_ConvertDirect(mockGUI):
|
|||||||
# =========
|
# =========
|
||||||
|
|
||||||
html.setLinkHeadings(False)
|
html.setLinkHeadings(False)
|
||||||
|
|
||||||
# Align Left
|
|
||||||
html.setStyles(False)
|
|
||||||
html._blocks = [
|
|
||||||
(BlockTyp.PART, tMeta, "A Title", [], BlockFmt.LEFT),
|
|
||||||
]
|
|
||||||
html.doConvert()
|
|
||||||
assert html._pages[-1] == (
|
|
||||||
"<h1 class='title'>A Title</h1>\n"
|
|
||||||
)
|
|
||||||
|
|
||||||
html.setStyles(True)
|
html.setStyles(True)
|
||||||
|
|
||||||
# Align Left
|
# Align Left
|
||||||
@@ -664,33 +698,57 @@ def testFmtToHtml_Save(mockGUI, fncPath):
|
|||||||
|
|
||||||
# Check Files
|
# Check Files
|
||||||
# ===========
|
# ===========
|
||||||
|
|
||||||
# HTML
|
|
||||||
hStyle = html.getStyleSheet()
|
hStyle = html.getStyleSheet()
|
||||||
htmlDoc = (
|
|
||||||
|
# HTML w/Styles
|
||||||
|
html.setStyles(True)
|
||||||
|
htmlDocCSS = (
|
||||||
"<!DOCTYPE html>\n"
|
"<!DOCTYPE html>\n"
|
||||||
"<html>\n"
|
"<html>\n"
|
||||||
"<head>\n"
|
"<head>\n"
|
||||||
"<meta charset='utf-8'>\n"
|
|
||||||
"<title></title>\n"
|
"<title></title>\n"
|
||||||
|
"<meta charset='utf-8'>\n"
|
||||||
|
"<meta name='viewport' content='width=device-width, initial-scale=1'>\n"
|
||||||
"<style>\n"
|
"<style>\n"
|
||||||
"{htmlStyle:s}\n"
|
"{style}\n"
|
||||||
"</style>\n"
|
"</style>\n"
|
||||||
"</head>\n"
|
"</head>\n"
|
||||||
"<body>\n"
|
"<body>\n"
|
||||||
"{bodyText:s}\n"
|
"{body}\n"
|
||||||
"</body>\n"
|
"</body>\n"
|
||||||
"</html>\n"
|
"</html>\n"
|
||||||
).format(
|
).format(
|
||||||
htmlStyle="\n".join(hStyle),
|
style="\n".join(hStyle),
|
||||||
bodyText="".join(resText).rstrip()
|
body="".join(resText).rstrip()
|
||||||
)
|
)
|
||||||
|
|
||||||
saveFile = fncPath / "outFile.htm"
|
saveFile = fncPath / "outFile.htm"
|
||||||
html.saveDocument(saveFile)
|
html.saveDocument(saveFile)
|
||||||
assert saveFile.read_text(encoding="utf-8") == htmlDoc
|
assert saveFile.read_text(encoding="utf-8") == htmlDocCSS
|
||||||
|
|
||||||
# JSON + HTML
|
# HTML wo/Styles
|
||||||
|
html.setStyles(False)
|
||||||
|
htmlDocCSS = (
|
||||||
|
"<!DOCTYPE html>\n"
|
||||||
|
"<html>\n"
|
||||||
|
"<head>\n"
|
||||||
|
"<title></title>\n"
|
||||||
|
"<meta charset='utf-8'>\n"
|
||||||
|
"</head>\n"
|
||||||
|
"<body>\n"
|
||||||
|
"{body}\n"
|
||||||
|
"</body>\n"
|
||||||
|
"</html>\n"
|
||||||
|
).format(
|
||||||
|
body="".join(resText).rstrip()
|
||||||
|
)
|
||||||
|
|
||||||
|
saveFile = fncPath / "outFile.htm"
|
||||||
|
html.saveDocument(saveFile)
|
||||||
|
assert saveFile.read_text(encoding="utf-8") == htmlDocCSS
|
||||||
|
|
||||||
|
# JSON + HTML w/Styles
|
||||||
|
html.setStyles(True)
|
||||||
saveFile = fncPath / "outFile.json"
|
saveFile = fncPath / "outFile.json"
|
||||||
html.saveDocument(saveFile)
|
html.saveDocument(saveFile)
|
||||||
data = json.loads(saveFile.read_text(encoding="utf-8"))
|
data = json.loads(saveFile.read_text(encoding="utf-8"))
|
||||||
|
|||||||
Reference in New Issue
Block a user