diff --git a/docs/source/int_customise.rst b/docs/source/int_customise.rst
index d54afc2a..15a7a348 100644
--- a/docs/source/int_customise.rst
+++ b/docs/source/int_customise.rst
@@ -159,6 +159,7 @@ A syntax theme ``.conf`` file consists of the follwing settings:
doublequotes = 0, 0, 0
singlequotes = 0, 0, 0
hidden = 0, 0, 0
+ shortcode = 0, 0, 0
keyword = 0, 0, 0
value = 0, 0, 0
spellcheckline = 0, 0, 0
@@ -169,3 +170,7 @@ A syntax theme ``.conf`` file consists of the follwing settings:
In the Main section, you must define at least the ``name`` setting. The Syntax colour values are
RGB numbers of the format ``r, g, b`` where each is an integer from to 255. Omitted values default
to black, except ``background`` which defaults to white,
+
+.. versionadded:: 2.2
+ The `shortcode` syntax colour entry was added, so you need to update your custom themes if you
+ made any before version 2.2.
diff --git a/novelwriter/assets/syntax/default_dark.conf b/novelwriter/assets/syntax/default_dark.conf
index f015e5d6..77dc829e 100644
--- a/novelwriter/assets/syntax/default_dark.conf
+++ b/novelwriter/assets/syntax/default_dark.conf
@@ -17,6 +17,7 @@ straightquotes = 200, 46, 0
doublequotes = 184, 200, 0
singlequotes = 136, 200, 0
hidden = 150, 150, 150
+shortcode = 0, 155, 200
keyword = 200, 46, 0
value = 184, 200, 0
spellcheckline = 200, 46, 0
diff --git a/novelwriter/assets/syntax/default_light.conf b/novelwriter/assets/syntax/default_light.conf
index 319fbda3..b4eab626 100644
--- a/novelwriter/assets/syntax/default_light.conf
+++ b/novelwriter/assets/syntax/default_light.conf
@@ -17,6 +17,7 @@ straightquotes = 200, 0, 0
doublequotes = 0, 0, 200
singlequotes = 0, 0, 200
hidden = 100, 100, 100
+shortcode = 0, 100, 0
keyword = 200, 50, 50
value = 50, 150, 50
spellcheckline = 200, 0, 0
diff --git a/novelwriter/assets/syntax/grey_dark.conf b/novelwriter/assets/syntax/grey_dark.conf
index e579e8da..ed09963f 100644
--- a/novelwriter/assets/syntax/grey_dark.conf
+++ b/novelwriter/assets/syntax/grey_dark.conf
@@ -17,6 +17,7 @@ straightquotes = 200, 200, 200
doublequotes = 200, 200, 200
singlequotes = 200, 200, 200
hidden = 150, 150, 150
+shortcode = 225, 225, 225
keyword = 225, 225, 225
value = 200, 200, 200
spellcheckline = 200, 46, 0
diff --git a/novelwriter/assets/syntax/grey_light.conf b/novelwriter/assets/syntax/grey_light.conf
index 32f9561c..e2ca0666 100644
--- a/novelwriter/assets/syntax/grey_light.conf
+++ b/novelwriter/assets/syntax/grey_light.conf
@@ -17,6 +17,7 @@ straightquotes = 20, 20, 20
doublequotes = 20, 20, 20
singlequotes = 20, 20, 20
hidden = 100, 100, 100
+shortcode = 0, 0, 0
keyword = 0, 0, 0
value = 20, 20, 20
spellcheckline = 200, 0, 0
diff --git a/novelwriter/assets/syntax/light_owl.conf b/novelwriter/assets/syntax/light_owl.conf
index fdd0dc67..c42b3155 100644
--- a/novelwriter/assets/syntax/light_owl.conf
+++ b/novelwriter/assets/syntax/light_owl.conf
@@ -37,6 +37,7 @@ straightquotes = 222, 61, 58
doublequotes = 8, 145, 106
singlequotes = 255, 235, 149
hidden = 152, 159, 177
+shortcode = 40, 142, 215
keyword = 222, 61, 58
value = 150, 74, 193
spellcheckline = 222, 61, 58
diff --git a/novelwriter/assets/syntax/night_owl.conf b/novelwriter/assets/syntax/night_owl.conf
index 59ad10da..2ee057e3 100644
--- a/novelwriter/assets/syntax/night_owl.conf
+++ b/novelwriter/assets/syntax/night_owl.conf
@@ -37,6 +37,7 @@ straightquotes = 247, 140, 108
doublequotes = 173, 219, 103
singlequotes = 255, 235, 149
hidden = 99, 119, 119
+shortcode = 130, 170, 255
keyword = 247, 140, 108
value = 199, 146, 234
spellcheckline = 247, 140, 108
diff --git a/novelwriter/assets/syntax/solarized_dark.conf b/novelwriter/assets/syntax/solarized_dark.conf
index ec8a3d8d..ef34ddf4 100644
--- a/novelwriter/assets/syntax/solarized_dark.conf
+++ b/novelwriter/assets/syntax/solarized_dark.conf
@@ -17,6 +17,7 @@ straightquotes = 211, 54, 130
doublequotes = 42, 161, 152
singlequotes = 42, 161, 152
hidden = 147, 161, 161
+shortcode = 147, 161, 161
keyword = 133, 153, 0
value = 203, 75, 22
spellcheckline = 203, 75, 22
diff --git a/novelwriter/assets/syntax/solarized_light.conf b/novelwriter/assets/syntax/solarized_light.conf
index 319a084c..21a31ade 100644
--- a/novelwriter/assets/syntax/solarized_light.conf
+++ b/novelwriter/assets/syntax/solarized_light.conf
@@ -17,6 +17,7 @@ straightquotes = 211, 54, 130
doublequotes = 42, 161, 152
singlequotes = 42, 161, 152
hidden = 88, 110, 117
+shortcode = 88, 110, 117
keyword = 133, 153, 0
value = 203, 75, 22
spellcheckline = 203, 75, 22
diff --git a/novelwriter/assets/syntax/tomorrow.conf b/novelwriter/assets/syntax/tomorrow.conf
index 6333f06e..20430fcd 100644
--- a/novelwriter/assets/syntax/tomorrow.conf
+++ b/novelwriter/assets/syntax/tomorrow.conf
@@ -37,6 +37,7 @@ straightquotes = 240, 40, 41
doublequotes = 113, 140, 0
singlequotes = 234, 183, 0
hidden = 142, 144, 140
+shortcode = 66, 113, 174
keyword = 240, 40, 41
value = 137, 89, 168
spellcheckline = 240, 40, 41
diff --git a/novelwriter/assets/syntax/tomorrow_night.conf b/novelwriter/assets/syntax/tomorrow_night.conf
index 2a4e8751..97c217df 100644
--- a/novelwriter/assets/syntax/tomorrow_night.conf
+++ b/novelwriter/assets/syntax/tomorrow_night.conf
@@ -37,6 +37,7 @@ straightquotes = 204, 102, 102
doublequotes = 181, 189, 104
singlequotes = 240, 198, 116
hidden = 150, 152, 150
+shortcode = 129, 162, 190
keyword = 204, 102, 102
value = 178, 148, 187
spellcheckline = 204, 102, 102
diff --git a/novelwriter/assets/syntax/tomorrow_night_blue.conf b/novelwriter/assets/syntax/tomorrow_night_blue.conf
index 1a8459bf..18c930e7 100644
--- a/novelwriter/assets/syntax/tomorrow_night_blue.conf
+++ b/novelwriter/assets/syntax/tomorrow_night_blue.conf
@@ -37,6 +37,7 @@ straightquotes = 255, 157, 164
doublequotes = 209, 241, 169
singlequotes = 255, 238, 173
hidden = 114, 133, 183
+shortcode = 187, 218, 255
keyword = 255, 157, 164
value = 235, 187, 255
spellcheckline = 255, 157, 164
diff --git a/novelwriter/assets/syntax/tomorrow_night_bright.conf b/novelwriter/assets/syntax/tomorrow_night_bright.conf
index c5bccc99..b7bf5658 100644
--- a/novelwriter/assets/syntax/tomorrow_night_bright.conf
+++ b/novelwriter/assets/syntax/tomorrow_night_bright.conf
@@ -37,6 +37,7 @@ straightquotes = 213, 78, 83
doublequotes = 185, 202, 74
singlequotes = 231, 197, 71
hidden = 150, 152, 150
+shortcode = 122, 166, 218
keyword = 213, 78, 83
value = 195, 151, 216
spellcheckline = 213, 78, 83
diff --git a/novelwriter/assets/syntax/tomorrow_night_eighties.conf b/novelwriter/assets/syntax/tomorrow_night_eighties.conf
index c7314f17..29dbecc5 100644
--- a/novelwriter/assets/syntax/tomorrow_night_eighties.conf
+++ b/novelwriter/assets/syntax/tomorrow_night_eighties.conf
@@ -37,6 +37,7 @@ straightquotes = 242, 119, 122
doublequotes = 153, 204, 153
singlequotes = 255, 204, 102
hidden = 153, 153, 153
+shortcode = 102, 153, 204
keyword = 242, 119, 122
value = 204, 153, 204
spellcheckline = 242, 119, 122
diff --git a/novelwriter/constants.py b/novelwriter/constants.py
index b1855f98..9567c3bb 100644
--- a/novelwriter/constants.py
+++ b/novelwriter/constants.py
@@ -23,6 +23,8 @@ along with this program. If not, see .
"""
from __future__ import annotations
+import re
+
from PyQt5.QtCore import QCoreApplication, QT_TRANSLATE_NOOP
from novelwriter.enum import nwBuildFmt, nwItemClass, nwItemLayout, nwOutline
@@ -59,6 +61,11 @@ class nwRegEx:
FMT_EI = r"(? tuple[int, int, int]:
if nwUnicode.U_EMDASH in text:
text = text.replace(nwUnicode.U_EMDASH, " ")
+ # Strip shortcodes
+ if "[" in text:
+ text = nwRegEx.RX_SC.sub("", text)
+
for line in text.splitlines():
countPara = True
@@ -1241,9 +1245,10 @@ def countWords(text: str) -> tuple[int, int, int]:
continue
if line[0] == "[":
- if line.startswith(("[NEWPAGE]", "[NEW PAGE]", "[VSPACE]")):
+ check = line.lower()
+ if check.startswith(("[newpage]", "[new page]", "[vspace]")):
continue
- elif line.startswith("[VSPACE:") and line.endswith("]"):
+ elif check.startswith("[vspace:") and line.endswith("]"):
continue
elif line[0] == "#":
diff --git a/novelwriter/core/tohtml.py b/novelwriter/core/tohtml.py
index d312767b..66515b26 100644
--- a/novelwriter/core/tohtml.py
+++ b/novelwriter/core/tohtml.py
@@ -31,7 +31,7 @@ from pathlib import Path
from novelwriter import CONFIG
from novelwriter.common import formatTimeStamp
-from novelwriter.constants import nwHeadFmt, nwKeyWords, nwLabels, nwHtmlUnicode
+from novelwriter.constants import nwHeadFmt, nwKeyWords, nwLabels, nwHtmlUnicode, trConst
from novelwriter.core.project import NWProject
from novelwriter.core.tokenizer import Tokenizer, stripEscape
@@ -131,6 +131,8 @@ class ToHtml(Tokenizer):
self.FMT_I_E: "",
self.FMT_D_B: "",
self.FMT_D_E: "",
+ self.FMT_U_B: "",
+ self.FMT_U_E: "",
}
else:
htmlTags = { # HTML5 (for export)
@@ -140,8 +142,15 @@ class ToHtml(Tokenizer):
self.FMT_I_E: "",
self.FMT_D_B: "",
self.FMT_D_E: "",
+ self.FMT_U_B: "",
+ self.FMT_U_E: "",
}
+ htmlTags[self.FMT_SUP_B] = ""
+ htmlTags[self.FMT_SUP_E] = ""
+ htmlTags[self.FMT_SUB_B] = ""
+ htmlTags[self.FMT_SUB_E] = ""
+
if self._isNovel and self._genMode != self.M_PREVIEW:
# For story files, we bump the titles one level up
h1Cl = " class='title'"
@@ -172,18 +181,16 @@ class ToHtml(Tokenizer):
for c in tText:
if c == "<":
cText.append("<")
- tFormat = [[a + 3 if a > i else a, b, c] for a, b, c in tFormat]
+ tFormat = [[p + 3 if p > i else p, f] for p, f in tFormat]
i += 4
elif c == ">":
cText.append(">")
- tFormat = [[a + 3 if a > i else a, b, c] for a, b, c in tFormat]
+ tFormat = [[p + 3 if p > i else p, f] for p, f in tFormat]
i += 4
else:
cText.append(c)
i += 1
-
tText = "".join(cText)
-
else:
# If we don't have formatting, we can do a plain replace
tText = tText.replace("<", "<").replace(">", ">")
@@ -275,8 +282,8 @@ class ToHtml(Tokenizer):
tTemp = tText
if pStyle is None:
pStyle = hStyle
- for xPos, xLen, xFmt in reversed(tFormat):
- tTemp = tTemp[:xPos] + htmlTags[xFmt] + tTemp[xPos+xLen:]
+ for pos, fmt in reversed(tFormat):
+ tTemp = f"{tTemp[:pos]}{htmlTags[fmt]}{tTemp[pos:]}"
para.append(stripEscape(tTemp.rstrip()))
elif tType == self.T_SYNOPSIS and self._doSynopsis:
@@ -467,25 +474,18 @@ class ToHtml(Tokenizer):
def _formatKeywords(self, text: str) -> str:
"""Apply HTML formatting to keywords."""
valid, bits, _ = self._project.index.scanThis("@"+text)
- if not valid or not bits:
+ if not valid or not bits or bits[0] not in nwLabels.KEY_NAME:
return ""
- result = ""
- tags = []
- if bits[0] in nwLabels.KEY_NAME:
- result += f"{nwLabels.KEY_NAME[bits[0]]}: "
- if len(bits) > 1:
- if bits[0] == nwKeyWords.TAG_KEY:
- result += f"{bits[1]}"
+ result = f"{trConst(nwLabels.KEY_NAME[bits[0]])}: "
+ if len(bits) > 1:
+ if bits[0] == nwKeyWords.TAG_KEY:
+ result += f"{bits[1]}"
+ else:
+ if self._genMode == self.M_PREVIEW:
+ result += ", ".join(f"{t}" for t in bits[1:])
else:
- if self._genMode == self.M_PREVIEW:
- for tTag in bits[1:]:
- tags.append(f"{tTag}")
- result += ", ".join(tags)
- else:
- for tTag in bits[1:]:
- tags.append(f"{tTag}")
- result += ", ".join(tags)
+ result += ", ".join(f"{t}" for t in bits[1:])
return result
diff --git a/novelwriter/core/tokenizer.py b/novelwriter/core/tokenizer.py
index cc0becf1..c1a51181 100644
--- a/novelwriter/core/tokenizer.py
+++ b/novelwriter/core/tokenizer.py
@@ -31,7 +31,6 @@ import logging
from abc import ABC, abstractmethod
from time import time
from pathlib import Path
-from operator import itemgetter
from functools import partial
from PyQt5.QtCore import QCoreApplication, QRegularExpression
@@ -43,15 +42,14 @@ from novelwriter.core.project import NWProject
logger = logging.getLogger(__name__)
+ESCAPES = {r"\*": "*", r"\~": "~", r"\_": "_", r"\[": "[", r"\]": "]", r"\ ": ""}
+RX_ESC = re.compile("|".join([re.escape(k) for k in ESCAPES.keys()]), flags=re.DOTALL)
+
def stripEscape(text) -> str:
- """Helper function to strip escaped Markdown characters from
- paragraph text.
- """
+ """Strip escaped Markdown characters from paragraph text."""
if "\\" in text:
- # Checking first is slightly slower when there are escaped
- # characters in the text, but significantly faster when not
- return text.replace(r"\*", "*").replace(r"\~", "~").replace(r"\_", "_")
+ return RX_ESC.sub(lambda x: ESCAPES[x.group(0)], text)
return text
@@ -65,12 +63,18 @@ class Tokenizer(ABC):
"""
# In-Text Format
- 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_D_B = 5 # Begin strikeout
- FMT_D_E = 6 # End strikeout
+ 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_D_B = 5 # Begin strikeout
+ FMT_D_E = 6 # End strikeout
+ FMT_U_B = 7 # Begin underline
+ FMT_U_E = 8 # End underline
+ FMT_SUP_B = 9 # Begin superscript
+ FMT_SUP_E = 10 # End superscript
+ FMT_SUB_B = 11 # Begin subscript
+ FMT_SUB_E = 12 # End subscript
# Block Type
T_EMPTY = 1 # Empty line (new paragraph)
@@ -166,6 +170,24 @@ class Tokenizer(ABC):
# Cached Translations
self._trSynopsis = self.tr("Synopsis")
+ # Format RegEx
+ self._rxMarkdown = [
+ (QRegularExpression(nwRegEx.FMT_EI), [0, self.FMT_I_B, 0, self.FMT_I_E]),
+ (QRegularExpression(nwRegEx.FMT_EB), [0, self.FMT_B_B, 0, self.FMT_B_E]),
+ (QRegularExpression(nwRegEx.FMT_ST), [0, self.FMT_D_B, 0, self.FMT_D_E]),
+ ]
+ self._rxShortCodes = QRegularExpression(nwRegEx.FMT_SC)
+ self._rxShortCodeVals = QRegularExpression(nwRegEx.FMT_SV)
+
+ self._shortCodeFmt = {
+ "[i]": self.FMT_I_B, "[/i]": self.FMT_I_E,
+ "[b]": self.FMT_B_B, "[/b]": self.FMT_B_E,
+ "[s]": self.FMT_D_B, "[/s]": self.FMT_D_E,
+ "[u]": self.FMT_U_B, "[/u]": self.FMT_U_E,
+ "[sup]": self.FMT_SUP_B, "[/sup]": self.FMT_SUP_E,
+ "[sub]": self.FMT_SUB_B, "[/sub]": self.FMT_SUB_E,
+ }
+
return
##
@@ -387,19 +409,12 @@ class Tokenizer(ABC):
4: The internal formatting map of the text, self.FMT_*
5: The style of the block, self.A_*
"""
- # RegExes for adding formatting tags within text lines
- rxFormats = [
- (QRegularExpression(nwRegEx.FMT_EI), [None, self.FMT_I_B, None, self.FMT_I_E]),
- (QRegularExpression(nwRegEx.FMT_EB), [None, self.FMT_B_B, None, self.FMT_B_E]),
- (QRegularExpression(nwRegEx.FMT_ST), [None, self.FMT_D_B, None, self.FMT_D_E]),
- ]
-
self._tokens = []
tmpMarkdown = []
nHead = 0
breakNext = False
for aLine in self._text.splitlines():
- sLine = aLine.strip()
+ sLine = aLine.strip().lower()
# Check for blank lines
if len(sLine) == 0:
@@ -422,18 +437,21 @@ class Tokenizer(ABC):
if aLine[0] == "[":
# Parse special formatting line
+ # This must be a separate if statement, as it may not
+ # reach a continue statement and must thefore proceed to
+ # check other formats.
- if sLine in ("[NEWPAGE]", "[NEW PAGE]"):
+ if sLine in ("[newpage]", "[new page]"):
breakNext = True
continue
- elif sLine == "[VSPACE]":
+ elif sLine == "[vspace]":
self._tokens.append(
(self.T_SKIP, nHead, "", None, sAlign)
)
continue
- elif sLine.startswith("[VSPACE:") and sLine.endswith("]"):
+ elif sLine.startswith("[vspace:") and sLine.endswith("]"):
nSkip = checkInt(sLine[8:-1], 0)
if nSkip >= 1:
self._tokens.append(
@@ -445,7 +463,7 @@ class Tokenizer(ABC):
]
continue
- elif aLine[0] == "%":
+ if aLine[0] == "%":
cLine = aLine[1:].lstrip()
synTag = cLine[:9].lower()
if synTag == "synopsis:":
@@ -570,23 +588,10 @@ class Tokenizer(ABC):
if indRight:
sAlign |= self.A_IND_R
- # Otherwise we use RegEx to find formatting tags within a line of text
- fmtPos = []
- for theRX, theKeys in rxFormats:
- rxThis = theRX.globalMatch(aLine, 0)
- while rxThis.hasNext():
- rxMatch = rxThis.next()
- for n in range(1, len(theKeys)):
- if theKeys[n] is not None:
- xPos = rxMatch.capturedStart(n)
- xLen = rxMatch.capturedLength(n)
- fmtPos.append([xPos, xLen, theKeys[n]])
-
- # Save the line as is, but append the array of formatting locations
- # sorted by position
- fmtPos = sorted(fmtPos, key=itemgetter(0))
+ # Process formats
+ tLine, fmtPos = self._extractFormats(aLine)
self._tokens.append((
- self.T_TEXT, nHead, aLine, fmtPos, sAlign
+ self.T_TEXT, nHead, tLine, fmtPos, sAlign
))
if self._keepMarkdown:
tmpMarkdown.append("%s\n" % aLine)
@@ -763,6 +768,45 @@ class Tokenizer(ABC):
json.dump(data, fObj, indent=2)
return
+ ##
+ # Internal Functions
+ ##
+
+ def _extractFormats(self, text: str) -> tuple[str, list[tuple[int, int]]]:
+ """Extract format markers from a text paragraph."""
+ temp = []
+
+ # Match Markdown
+ for regEx, fmts in self._rxMarkdown:
+ rxItt = regEx.globalMatch(text, 0)
+ while rxItt.hasNext():
+ rxMatch = rxItt.next()
+ temp.extend(
+ [rxMatch.capturedStart(n), rxMatch.capturedLength(n), fmt]
+ for n, fmt in enumerate(fmts) if fmt > 0
+ )
+
+ # Match Shortcodes
+ rxItt = self._rxShortCodes.globalMatch(text, 0)
+ while rxItt.hasNext():
+ rxMatch = rxItt.next()
+ temp.append([
+ rxMatch.capturedStart(1),
+ rxMatch.capturedLength(1),
+ self._shortCodeFmt.get(rxMatch.captured(1).lower(), 0)
+ ])
+
+ # Post-process text and format markers
+ result = text
+ formats = []
+ for pos, n, fmt in reversed(sorted(temp, key=lambda x: x[0])):
+ if fmt > 0:
+ result = result[:pos] + result[pos+n:]
+ formats = [(p-n, f) for p, f in formats]
+ formats.insert(0, (pos, fmt))
+
+ return result, formats
+
# END Class Tokenizer
diff --git a/novelwriter/core/tomd.py b/novelwriter/core/tomd.py
index ec1646ad..cc2e8696 100644
--- a/novelwriter/core/tomd.py
+++ b/novelwriter/core/tomd.py
@@ -27,7 +27,7 @@ import logging
from pathlib import Path
-from novelwriter.constants import nwHeadFmt, nwLabels
+from novelwriter.constants import nwHeadFmt, nwLabels, trConst
from novelwriter.core.project import NWProject
from novelwriter.core.tokenizer import Tokenizer
@@ -38,12 +38,12 @@ class ToMarkdown(Tokenizer):
"""Core: Markdown Document Writer
Extend the Tokenizer class to writer Markdown output. It supports
- both Standard Markdown and GitHub Flavour Markdown (Extended). The
- class also supports concatenating novelWriter markup files.
+ both Standard Markdown and Extended Markdown. The class also
+ supports concatenating novelWriter markup files.
"""
M_STD = 0 # Standard Markdown
- M_GH = 1 # GitHub Markdown
+ M_EXT = 1 # Extended Markdown
def __init__(self, project: NWProject) -> None:
super().__init__(project)
@@ -69,9 +69,9 @@ class ToMarkdown(Tokenizer):
self._genMode = self.M_STD
return
- def setGitHubMarkdown(self) -> None:
- """Set the converter to use GitHub Markdown formatting."""
- self._genMode = self.M_GH
+ def setExtendedMarkdown(self) -> None:
+ """Set the converter to use Extended Markdown formatting."""
+ self._genMode = self.M_EXT
return
##
@@ -85,7 +85,7 @@ class ToMarkdown(Tokenizer):
def doConvert(self) -> None:
"""Convert the list of text tokens into a Markdown document."""
if self._genMode == self.M_STD:
- # Standard
+ # Standard Markdown
mdTags = {
self.FMT_B_B: "**",
self.FMT_B_E: "**",
@@ -93,9 +93,15 @@ class ToMarkdown(Tokenizer):
self.FMT_I_E: "_",
self.FMT_D_B: "",
self.FMT_D_E: "",
+ self.FMT_U_B: "",
+ self.FMT_U_E: "",
+ self.FMT_SUP_B: "",
+ self.FMT_SUP_E: "",
+ self.FMT_SUB_B: "",
+ self.FMT_SUB_E: "",
}
else:
- # GitHub
+ # Extended Markdown
mdTags = {
self.FMT_B_B: "**",
self.FMT_B_E: "**",
@@ -103,6 +109,12 @@ class ToMarkdown(Tokenizer):
self.FMT_I_E: "_",
self.FMT_D_B: "~~",
self.FMT_D_E: "~~",
+ self.FMT_U_B: "",
+ self.FMT_U_E: "",
+ self.FMT_SUP_B: "^",
+ self.FMT_SUP_E: "^",
+ self.FMT_SUB_B: "~",
+ self.FMT_SUB_E: "~",
}
self._result = ""
@@ -150,8 +162,8 @@ class ToMarkdown(Tokenizer):
elif tType == self.T_TEXT:
tTemp = tText
- for xPos, xLen, xFmt in reversed(tFormat):
- tTemp = tTemp[:xPos] + mdTags[xFmt] + tTemp[xPos+xLen:]
+ for pos, fmt in reversed(tFormat):
+ tTemp = f"{tTemp[:pos]}{mdTags[fmt]}{tTemp[pos:]}"
para.append(tTemp.rstrip())
elif tType == self.T_SYNOPSIS and self._doSynopsis:
@@ -197,7 +209,7 @@ class ToMarkdown(Tokenizer):
result = ""
if bits[0] in nwLabels.KEY_NAME:
- result += f"**{nwLabels.KEY_NAME[bits[0]]}:** "
+ result += f"**{trConst(nwLabels.KEY_NAME[bits[0]])}:** "
if len(bits) > 1:
result += ", ".join(bits[1:])
diff --git a/novelwriter/core/toodt.py b/novelwriter/core/toodt.py
index c4f6b19d..c1165e30 100644
--- a/novelwriter/core/toodt.py
+++ b/novelwriter/core/toodt.py
@@ -29,6 +29,7 @@ from __future__ import annotations
import logging
import xml.etree.ElementTree as ET
+from typing import Sequence
from hashlib import sha256
from pathlib import Path
from zipfile import ZipFile
@@ -36,7 +37,7 @@ from datetime import datetime
from novelwriter import __version__
from novelwriter.common import xmlIndent
-from novelwriter.constants import nwHeadFmt, nwKeyWords, nwLabels
+from novelwriter.constants import nwHeadFmt, nwKeyWords, nwLabels, trConst
from novelwriter.core.project import NWProject
from novelwriter.core.tokenizer import Tokenizer, stripEscape
@@ -82,11 +83,17 @@ TAG_STNM = _mkTag("text", "style-name")
X_BLD = 0x01 # Bold format
X_ITA = 0x02 # Italic format
X_DEL = 0x04 # Strikethrough format
+X_UND = 0x08 # Underline format
+X_SUP = 0x10 # Superscript
+X_SUB = 0x20 # Subscript
# Formatting Masks
M_BLD = ~X_BLD
M_ITA = ~X_ITA
M_DEL = ~X_DEL
+M_UND = ~X_UND
+M_SUP = ~X_SUP
+M_SUB = ~X_SUB
class ToOdt(Tokenizer):
@@ -385,17 +392,8 @@ class ToOdt(Tokenizer):
"""Convert the list of text tokens into XML elements."""
self._result = "" # Not used, but cleared just in case
- odtTags = {
- self.FMT_B_B: "_B", # Bold open format
- self.FMT_B_E: "b_", # Bold close format
- self.FMT_I_B: "I", # Italic open format
- self.FMT_I_E: "i", # Italic close format
- self.FMT_D_B: "_S", # Strikethrough open format
- self.FMT_D_E: "s_", # Strikethrough close format
- }
-
- fmt = []
- para = []
+ pFmt = []
+ pText = []
pStyle = None
for tType, _, tText, tFormat, tStyle in self._tokens:
@@ -429,19 +427,21 @@ class ToOdt(Tokenizer):
# Process Text Types
if tType == self.T_EMPTY:
- if len(para) > 1 and pStyle is not None:
+ if len(pText) > 1 and pStyle is not None:
if self._doJustify:
pStyle.setTextAlign("left")
- if len(para) > 0 and pStyle is not None:
- tTemp = "\n".join(para)
- fTemp = " ".join(fmt)
- tTxt = tTemp.rstrip()
- tFmt = fTemp[:len(tTxt)]
- self._addTextPar("Text_20_body", pStyle, tTxt, tFmt=tFmt)
+ if len(pText) > 0 and pStyle is not None:
+ tTxt = ""
+ tFmt = []
+ for nText, nFmt in zip(pText, pFmt):
+ tLen = len(tTxt)
+ tTxt += f"{nText}\n"
+ tFmt.extend((p+tLen, fmt) for p, fmt in nFmt)
+ self._addTextPar("Text_20_body", pStyle, tTxt.rstrip(), tFmt=tFmt)
- fmt = []
- para = []
+ pFmt = []
+ pText = []
pStyle = None
elif tType == self.T_TITLE:
@@ -477,15 +477,8 @@ class ToOdt(Tokenizer):
elif tType == self.T_TEXT:
if pStyle is None:
pStyle = oStyle
-
- tFmt = " "*len(tText)
- for xPos, xLen, xFmt in tFormat:
- tFmt = tFmt[:xPos] + odtTags[xFmt] + tFmt[xPos+xLen:]
-
- tTxt = tText.rstrip()
- tFmt = tFmt[:len(tTxt)]
- para.append(tTxt)
- fmt.append(tFmt)
+ pText.append(tText)
+ pFmt.append(tFormat)
elif tType == self.T_SYNOPSIS and self._doSynopsis:
tTemp, fTemp = self._formatSynopsis(tText)
@@ -559,46 +552,39 @@ class ToOdt(Tokenizer):
# Internal Functions
##
- def _formatSynopsis(self, text: str) -> tuple[str, str]:
+ def _formatSynopsis(self, text: str) -> tuple[str, list[tuple[int, int]]]:
"""Apply formatting to synopsis lines."""
- sSynop = self._localLookup("Synopsis")
- rTxt = "**{0}:** {1}".format(sSynop, text)
- rFmt = "_B{0} b_ {1}".format(" "*len(sSynop), " "*len(text))
+ name = self._localLookup("Synopsis")
+ rTxt = f"{name}: {text}"
+ rFmt = [(0, self.FMT_B_B), (len(name) + 1, self.FMT_B_E)]
return rTxt, rFmt
- def _formatComments(self, text: str) -> tuple[str, str]:
+ def _formatComments(self, text: str) -> tuple[str, list[tuple[int, int]]]:
"""Apply formatting to comments."""
- sComm = self._localLookup("Comment")
- rTxt = "**{0}:** {1}".format(sComm, text)
- rFmt = "_B{0} b_ {1}".format(" "*len(sComm), " "*len(text))
+ name = self._localLookup("Comment")
+ rTxt = f"{name}: {text}"
+ rFmt = [(0, self.FMT_B_B), (len(name) + 1, self.FMT_B_E)]
return rTxt, rFmt
- def _formatKeywords(self, text: str) -> tuple[str, str]:
+ def _formatKeywords(self, text: str) -> tuple[str, list[tuple[int, int]]]:
"""Apply formatting to keywords."""
valid, bits, _ = self._project.index.scanThis("@"+text)
- if not valid or not bits:
- return "", ""
+ if not valid or not bits or bits[0] not in nwLabels.KEY_NAME:
+ return "", []
- rTxt = ""
- rFmt = ""
- if bits[0] in nwLabels.KEY_NAME:
- text = nwLabels.KEY_NAME[bits[0]]
- rTxt += "**{0}:** ".format(text)
- rFmt += "_B{0} b_ ".format(" "*len(text))
- if len(bits) > 1:
- if bits[0] == nwKeyWords.TAG_KEY:
- rTxt += bits[1]
- rFmt += " "*len(bits[1])
- else:
- tTags = ", ".join(bits[1:])
- rTxt += tTags
- rFmt += (" "*len(tTags))
+ rTxt = f"{trConst(nwLabels.KEY_NAME[bits[0]])}: "
+ rFmt = [(0, self.FMT_B_B), (len(rTxt) - 1, self.FMT_B_E)]
+ if len(bits) > 1:
+ if bits[0] == nwKeyWords.TAG_KEY:
+ rTxt += bits[1]
+ else:
+ rTxt += ", ".join(bits[1:])
return rTxt, rFmt
def _addTextPar(
- self, styleName: str, oStyle: ODTParagraphStyle, tText: str, tFmt: str = "",
- isHead: bool = False, oLevel: str | None = None
+ self, styleName: str, oStyle: ODTParagraphStyle, tText: str,
+ tFmt: Sequence[tuple[int, int]] = [], isHead: bool = False, oLevel: str | None = None
) -> None:
"""Add a text paragraph to the text XML element."""
tAttr = {}
@@ -616,58 +602,59 @@ class ToOdt(Tokenizer):
if not tText:
return
- ##
- # Process Formatting
- ##
-
- if len(tText) != len(tFmt):
- # Generate an empty format if there isn't any or it doesn't match
- tFmt = " "*len(tText)
-
- # The formatting loop
- tTemp = ""
- xFmt = 0x00
- pFmt = 0x00
- pErr = 0
+ # Loop Over Fragments
+ # ===================
parProc = XMLParagraph(xElem)
- for i, c in enumerate(tText):
+ pErr = 0
+ xFmt = 0x00
+ tFrag = ""
+ fLast = 0
+ for fPos, fFmt in tFmt:
- if tFmt[i] == " ":
- tTemp += c
- elif tFmt[i] == "_":
- continue
- elif tFmt[i] == "B":
+ # Add the text up to the current fragment
+ if tFrag := tText[fLast:fPos]:
+ if xFmt == 0x00:
+ parProc.appendText(tFrag)
+ else:
+ parProc.appendSpan(tFrag, self._textStyle(xFmt))
+
+ # Calculate the change of format
+ if fFmt == self.FMT_B_B:
xFmt |= X_BLD
- elif tFmt[i] == "b":
+ elif fFmt == self.FMT_B_E:
xFmt &= M_BLD
- elif tFmt[i] == "I":
+ elif fFmt == self.FMT_I_B:
xFmt |= X_ITA
- elif tFmt[i] == "i":
+ elif fFmt == self.FMT_I_E:
xFmt &= M_ITA
- elif tFmt[i] == "S":
+ elif fFmt == self.FMT_D_B:
xFmt |= X_DEL
- elif tFmt[i] == "s":
+ elif fFmt == self.FMT_D_E:
xFmt &= M_DEL
+ elif fFmt == self.FMT_U_B:
+ xFmt |= X_UND
+ elif fFmt == self.FMT_U_E:
+ xFmt &= M_UND
+ elif fFmt == self.FMT_SUP_B:
+ xFmt |= X_SUP
+ elif fFmt == self.FMT_SUP_E:
+ xFmt &= M_SUP
+ elif fFmt == self.FMT_SUB_B:
+ xFmt |= X_SUB
+ elif fFmt == self.FMT_SUB_E:
+ xFmt &= M_SUB
else:
pErr += 1
- if xFmt != pFmt:
- if pFmt == 0x00:
- parProc.appendText(tTemp)
- tTemp = ""
- else:
- parProc.appendSpan(tTemp, self._textStyle(pFmt))
- tTemp = ""
+ fLast = fPos
- pFmt = xFmt
-
- # Save what remains in the buffer
- if pFmt == 0x00:
- parProc.appendText(tTemp)
- else:
- parProc.appendSpan(tTemp, self._textStyle(pFmt))
+ if tFrag := tText[fLast:]:
+ if xFmt == 0x00:
+ parProc.appendText(tFrag)
+ else:
+ parProc.appendSpan(tFrag, self._textStyle(xFmt))
if pErr > 0:
self._errData.append("Unknown format tag encountered")
@@ -713,6 +700,14 @@ class ToOdt(Tokenizer):
if hFmt & X_DEL:
newStyle.setStrikeStyle("solid")
newStyle.setStrikeType("single")
+ if hFmt & X_UND:
+ newStyle.setUnderlineStyle("solid")
+ newStyle.setUnderlineWidth("auto")
+ newStyle.setUnderlineColour("font-color")
+ if hFmt & X_SUP:
+ newStyle.setTextPosition("super")
+ if hFmt & X_SUB:
+ newStyle.setTextPosition("sub")
self._autoText[hFmt] = (newName, newStyle)
@@ -1256,16 +1251,23 @@ class ODTTextStyle:
"""
VALID_WEIGHT = ["normal", "inherit", "bold"]
VALID_STYLE = ["normal", "inherit", "italic"]
+ VALID_POS = ["super", "sub"]
VALID_LSTYLE = ["none", "solid"]
- VALID_LTYPE = ["none", "single", "double"]
+ VALID_LTYPE = ["single", "double"]
+ VALID_LWIDTH = ["auto"]
+ VALID_LCOL = ["font-color"]
def __init__(self) -> None:
# Text Attributes
self._tAttr = {
"font-weight": ["fo", None],
"font-style": ["fo", None],
+ "text-position": ["style", None],
"text-line-through-style": ["style", None],
"text-line-through-type": ["style", None],
+ "text-underline-style": ["style", None],
+ "text-underline-width": ["style", None],
+ "text-underline-color": ["style", None],
}
return
@@ -1287,6 +1289,13 @@ class ODTTextStyle:
self._tAttr["font-style"][1] = None
return
+ def setTextPosition(self, value: str | None) -> None:
+ if value in self.VALID_POS:
+ self._tAttr["text-position"][1] = f"{value} 58%"
+ else:
+ self._tAttr["text-position"][1] = None
+ return
+
def setStrikeStyle(self, value: str | None) -> None:
if value in self.VALID_LSTYLE:
self._tAttr["text-line-through-style"][1] = value
@@ -1301,6 +1310,27 @@ class ODTTextStyle:
self._tAttr["text-line-through-type"][1] = None
return
+ def setUnderlineStyle(self, value: str | None) -> None:
+ if value in self.VALID_LSTYLE:
+ self._tAttr["text-underline-style"][1] = value
+ else:
+ self._tAttr["text-underline-style"][1] = None
+ return
+
+ def setUnderlineWidth(self, value: str | None) -> None:
+ if value in self.VALID_LWIDTH:
+ self._tAttr["text-underline-width"][1] = value
+ else:
+ self._tAttr["text-underline-width"][1] = None
+ return
+
+ def setUnderlineColour(self, value: str | None) -> None:
+ if value in self.VALID_LCOL:
+ self._tAttr["text-underline-color"][1] = value
+ else:
+ self._tAttr["text-underline-color"][1] = None
+ return
+
##
# Methods
##
diff --git a/novelwriter/gui/doceditor.py b/novelwriter/gui/doceditor.py
index 1555e61e..93053f20 100644
--- a/novelwriter/gui/doceditor.py
+++ b/novelwriter/gui/doceditor.py
@@ -55,6 +55,7 @@ from novelwriter.enum import nwDocAction, nwDocInsert, nwDocMode, nwItemClass
from novelwriter.common import minmax, transferCase
from novelwriter.constants import nwKeyWords, nwUnicode
from novelwriter.core.index import countWords
+from novelwriter.core.document import NWDocument
from novelwriter.gui.dochighlight import GuiDocHighlighter
from novelwriter.gui.editordocument import GuiTextDocument
from novelwriter.extensions.wheeleventfilter import WheelEventFilter
@@ -408,12 +409,6 @@ class GuiDocEditor(QPlainTextEdit):
self._qDocument.syntaxHighlighter.rehighlightByType(GuiDocHighlighter.BLOCK_META)
return
- def redrawText(self) -> None:
- """Redraw the text by marking the document content as dirty."""
- self._qDocument.markContentsDirty(0, self._qDocument.characterCount())
- self.updateDocMargins()
- return
-
def replaceText(self, text: str) -> None:
"""Replace the text of the current document with the provided
text. This also clears undo history.
@@ -735,19 +730,17 @@ class GuiDocEditor(QPlainTextEdit):
"""Tell the user where on the file system the file in the editor
is saved.
"""
- if self._nwDocument is None:
- logger.error("No document open")
- return
- SHARED.info(
- "
".join([
- self.tr("Document Details"),
- "–"*40,
- self.tr("Created: {0}").format(self._nwDocument.createdDate),
- self.tr("Updated: {0}").format(self._nwDocument.updatedDate),
- ]),
- details=self.tr("File Location: {0}").format(self._nwDocument.fileLocation),
- log=False
- )
+ if isinstance(self._nwDocument, NWDocument):
+ SHARED.info(
+ "
".join([
+ self.tr("Document Details"),
+ "–"*40,
+ self.tr("Created: {0}").format(self._nwDocument.createdDate),
+ self.tr("Updated: {0}").format(self._nwDocument.updatedDate),
+ ]),
+ details=self.tr("File Location: {0}").format(self._nwDocument.fileLocation),
+ log=False
+ )
return
def insertText(self, insert: str | nwDocInsert) -> bool:
@@ -775,15 +768,15 @@ class GuiDocEditor(QPlainTextEdit):
newBlock = True
goAfter = True
elif insert == nwDocInsert.NEW_PAGE:
- text = "[NEW PAGE]"
+ text = "[newpage]"
newBlock = True
goAfter = False
elif insert == nwDocInsert.VSPACE_S:
- text = "[VSPACE]"
+ text = "[vspace]"
newBlock = True
goAfter = False
elif insert == nwDocInsert.VSPACE_M:
- text = "[VSPACE:2]"
+ text = "[vspace:2]"
newBlock = True
goAfter = False
else:
diff --git a/novelwriter/gui/dochighlight.py b/novelwriter/gui/dochighlight.py
index 5acd7ef7..6a77f267 100644
--- a/novelwriter/gui/dochighlight.py
+++ b/novelwriter/gui/dochighlight.py
@@ -39,7 +39,7 @@ from novelwriter.constants import nwRegEx, nwUnicode
logger = logging.getLogger(__name__)
-SPELLRX = QRegularExpression(r"\b[^\s\-\+\/–—]+\b")
+SPELLRX = QRegularExpression(r"\b[^\s\-\+\/–—\[\]:]+\b")
SPELLRX.setPatternOptions(QRegularExpression.UseUnicodePropertiesOption)
@@ -69,6 +69,7 @@ class GuiDocHighlighter(QSyntaxHighlighter):
self._colDialD = QColor(0, 0, 0)
self._colDialS = QColor(0, 0, 0)
self._colHidden = QColor(0, 0, 0)
+ self._colCode = QColor(0, 0, 0)
self._colKey = QColor(0, 0, 0)
self._colVal = QColor(0, 0, 0)
self._colSpell = QColor(0, 0, 0)
@@ -95,6 +96,7 @@ class GuiDocHighlighter(QSyntaxHighlighter):
self._colDialD = QColor(*SHARED.theme.colDialD)
self._colDialS = QColor(*SHARED.theme.colDialS)
self._colHidden = QColor(*SHARED.theme.colHidden)
+ self._colCode = QColor(*SHARED.theme.colCode)
self._colKey = QColor(*SHARED.theme.colKey)
self._colVal = QColor(*SHARED.theme.colVal)
self._colSpell = QColor(*SHARED.theme.colSpell)
@@ -127,6 +129,7 @@ class GuiDocHighlighter(QSyntaxHighlighter):
"dialogue3": self._makeFormat(self._colDialS),
"replace": self._makeFormat(self._colRepTag),
"hidden": self._makeFormat(self._colHidden),
+ "code": self._makeFormat(self._colCode),
"keyword": self._makeFormat(self._colKey),
"modifier": self._makeFormat(self._colMod),
"value": self._makeFormat(self._colVal, "underline"),
@@ -205,6 +208,22 @@ class GuiDocHighlighter(QSyntaxHighlighter):
}
))
+ # Shortcodes
+ self._hRules.append((
+ nwRegEx.FMT_SC, {
+ 1: self._hStyles["code"],
+ }
+ ))
+
+ # Shortcodes w/Value
+ self._hRules.append((
+ nwRegEx.FMT_SV, {
+ 1: self._hStyles["code"],
+ 2: self._hStyles["codevalue"],
+ 3: self._hStyles["code"],
+ }
+ ))
+
# Alignment Tags
self._hRules.append((
r"(^>{1,2}|<{1,2}$)", {
@@ -347,18 +366,17 @@ class GuiDocHighlighter(QSyntaxHighlighter):
else: # Text Paragraph
if text.startswith("["): # Special Command
- sText = text.rstrip()
- if sText in ("[NEWPAGE]", "[NEW PAGE]", "[VSPACE]"):
- self.setFormat(0, len(text), self._hStyles["keyword"])
+ sText = text.rstrip().lower()
+ if sText in ("[newpage]", "[new page]", "[vspace]"):
+ self.setFormat(0, len(text), self._hStyles["code"])
return
-
- elif sText.startswith("[VSPACE:") and sText.endswith("]"):
+ elif sText.startswith("[vspace:") and sText.endswith("]"):
tLen = len(sText)
tVal = checkInt(sText[8:-1], 0)
cVal = "codevalue" if tVal > 0 else "codeinval"
- self.setFormat(0, 8, self._hStyles["keyword"])
+ self.setFormat(0, 8, self._hStyles["code"])
self.setFormat(8, tLen-9, self._hStyles[cVal])
- self.setFormat(tLen-1, tLen, self._hStyles["keyword"])
+ self.setFormat(tLen-1, tLen, self._hStyles["code"])
return
# Regular Text
diff --git a/novelwriter/gui/theme.py b/novelwriter/gui/theme.py
index a91d9ae1..da8f1be4 100644
--- a/novelwriter/gui/theme.py
+++ b/novelwriter/gui/theme.py
@@ -97,6 +97,7 @@ class GuiTheme:
self.colDialD = [0, 0, 0]
self.colDialS = [0, 0, 0]
self.colHidden = [0, 0, 0]
+ self.colCode = [0, 0, 0]
self.colKey = [0, 0, 0]
self.colVal = [0, 0, 0]
self.colSpell = [0, 0, 0]
@@ -316,6 +317,7 @@ class GuiTheme:
self.colDialD = self._parseColour(confParser, cnfSec, "doublequotes")
self.colDialS = self._parseColour(confParser, cnfSec, "singlequotes")
self.colHidden = self._parseColour(confParser, cnfSec, "hidden")
+ self.colCode = self._parseColour(confParser, cnfSec, "shortcode")
self.colKey = self._parseColour(confParser, cnfSec, "keyword")
self.colVal = self._parseColour(confParser, cnfSec, "value")
self.colSpell = self._parseColour(confParser, cnfSec, "spellcheckline")
diff --git a/sample/content/636b6aa9b697b.nwd b/sample/content/636b6aa9b697b.nwd
index de7f32d7..78c6d8b9 100644
--- a/sample/content/636b6aa9b697b.nwd
+++ b/sample/content/636b6aa9b697b.nwd
@@ -1,8 +1,8 @@
%%~name: Making a Scene
%%~path: 6a2d6d5f4f401/636b6aa9b697b
%%~kind: NOVEL/DOCUMENT
-%%~hash: 7aae771de46c3cab06d8be0e860dc0bd383860a5
-%%~date: Unknown/2023-09-07 19:00:10
+%%~hash: d7920fe95fb48140278ae160fe7c3c1ce530072c
+%%~date: Unknown/2023-10-20 22:57:56
### Making a Scene
@pov: Jane
@@ -13,6 +13,8 @@ A scene is defined by a level three heading, like the one at the top of this pag
Each paragraph in the scene is separated by a blank line. The text supports minimal formatting, like **bold**, _italic_ and **_bold italic_**. You can also ~~strike through~~ text. There is **some support for _nested_ emphasis**, but there are some known limitations. If the syntax highlighter doesn’t show it correctly, the export tool will not either.
+For special formatting aside from standard emphasis, you can use “shortcodes”. Here we have super[sup]script[/sup] and sub[sub]script[/sub], and [b]part[/b]ial bold too, which ends in the middle of a word. The shortcodes also support [u]underline[/u].
+
In addition, the editor supports automatic formatting of “quotes”, both double and ‘single’. Depending on the syntax highlighter settings and colour theme, these can be in different colours. “You can of course use **bold** and _italic_ text inside of quotes too.”
If you have the need for it, you can also add text that can be automatically replaced by other text when you generate a preview or export the project. Now, let’s auto-replace this A with , and this C with . While is just . Press Ctrl+R to see what this looks like in the view pane. The list of auto-replaced text is set in Project Settings.
diff --git a/sample/content/974e400180a99.nwd b/sample/content/974e400180a99.nwd
index 0175798b..5058f0ca 100644
--- a/sample/content/974e400180a99.nwd
+++ b/sample/content/974e400180a99.nwd
@@ -1,11 +1,11 @@
%%~name: Page
%%~path: 7031beac91f75/974e400180a99
%%~kind: NOVEL/DOCUMENT
-%%~hash: 7547809e972205eb2a55dd988595e0aa1d01e139
-%%~date: Unknown/2023-08-25 16:51:54
-[NEW PAGE]
-[VSPACE:2]
+%%~hash: a0b0719e19763bf505bcf797bea0968c505c9b4a
+%%~date: Unknown/2023-11-06 11:19:33
+[newpage]
+[vspace:2]
This is a plain page with some text on it.
-If you want the text to start on a fresh page, add the [NEW PAGE] code above the text. You can also add empty paragraphs with the [VSPACE] code. The above code adds two empty paragraphs before the text starts.
+If you want the text to start on a fresh page, add the [newpage] code above the text. You can also add empty paragraphs with the [vspace] code. The above code adds two empty paragraphs before the text starts.
diff --git a/sample/nwProject.nwx b/sample/nwProject.nwx
index 00ea7567..67b86c1f 100644
--- a/sample/nwProject.nwx
+++ b/sample/nwProject.nwx
@@ -1,6 +1,6 @@
-
-
+
+
Sample Project
Sample Project
Jane Smith
@@ -36,7 +36,7 @@
Main
-
+
-
Novel
@@ -46,7 +46,7 @@
Title Page
-
-
+
Page
-
@@ -58,7 +58,7 @@
Chapter One
-
-
+
Making a Scene
-
diff --git a/tests/reference/coreToOdt_SaveFlat_document.fodt b/tests/reference/coreToOdt_SaveFlat_document.fodt
index 70198aaa..e684d070 100644
--- a/tests/reference/coreToOdt_SaveFlat_document.fodt
+++ b/tests/reference/coreToOdt_SaveFlat_document.fodt
@@ -1,13 +1,13 @@
- 2023-07-19T23:55:29
- novelWriter/2.1-beta1
+ 2023-10-20T22:51:30
+ novelWriter/2.2-alpha1
Jane Smith
1234
P42DT12H34M56S
Test Project
- 2023-07-19T23:55:29
+ 2023-10-20T22:51:30
Jane Smith
@@ -64,7 +64,7 @@
-
+
diff --git a/tests/test_core/test_core_index.py b/tests/test_core/test_core_index.py
index d8b7917e..03b497da 100644
--- a/tests/test_core/test_core_index.py
+++ b/tests/test_core/test_core_index.py
@@ -1281,7 +1281,7 @@ def testCoreIndex_CountWords():
assert wC == 9
assert pC == 4
- # Formatting Codes
+ # Formatting Codes, Upper Case (Old Implementation)
cC, wC, pC = countWords((
"Some text\n\n"
"[NEWPAGE]\n\n"
@@ -1297,4 +1297,29 @@ def testCoreIndex_CountWords():
assert wC == 13
assert pC == 5
+ # Formatting Codes, Lower Case (Current Implementation)
+ cC, wC, pC = countWords((
+ "Some text\n\n"
+ "[newpage]\n\n"
+ "more text\n\n"
+ "[new page]]\n\n"
+ "even more text\n\n"
+ "[vspace]\n\n"
+ "and some final text\n\n"
+ "[vspace:4]\n\n"
+ "THE END\n\n"
+ ))
+ assert cC == 58
+ assert wC == 13
+ assert pC == 5
+
+ # Check ShortCodes
+ cC, wC, pC = countWords((
+ "Text with [b]bold[/b] text and padded [b] bold [/b] text.\n\n"
+ "Text with [b][i] nested [/i] emphasis [/b] in it.\n\n"
+ ))
+ assert cC == 78
+ assert wC == 14
+ assert pC == 2
+
# END Test testCoreIndex_CountWords
diff --git a/tests/test_core/test_core_tokenizer.py b/tests/test_core/test_core_tokenizer.py
index ed13e05c..d5cbee87 100644
--- a/tests/test_core/test_core_tokenizer.py
+++ b/tests/test_core/test_core_tokenizer.py
@@ -19,6 +19,7 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+import json
import pytest
from tools import C, buildTestProject, readFile
@@ -36,96 +37,96 @@ class BareTokenizer(Tokenizer):
@pytest.mark.core
def testCoreToken_Setters(mockGUI):
"""Test all the setters for the Tokenizer class."""
- theProject = NWProject()
- theToken = BareTokenizer(theProject)
+ project = NWProject()
+ tokens = BareTokenizer(project)
# Verify defaults
- assert theToken._fmtTitle == nwHeadFmt.TITLE
- assert theToken._fmtChapter == nwHeadFmt.TITLE
- assert theToken._fmtUnNum == nwHeadFmt.TITLE
- assert theToken._fmtScene == nwHeadFmt.TITLE
- assert theToken._fmtSection == nwHeadFmt.TITLE
- assert theToken._textFont == "Serif"
- assert theToken._textSize == 11
- assert theToken._textFixed is False
- assert theToken._lineHeight == 1.15
- assert theToken._blockIndent == 4.0
- assert theToken._doJustify is False
- assert theToken._marginTitle == (1.000, 0.500)
- assert theToken._marginHead1 == (1.000, 0.500)
- assert theToken._marginHead2 == (0.834, 0.500)
- assert theToken._marginHead3 == (0.584, 0.500)
- assert theToken._marginHead4 == (0.584, 0.500)
- assert theToken._marginText == (0.000, 0.584)
- assert theToken._marginMeta == (0.000, 0.584)
- assert theToken._hideScene is False
- assert theToken._hideSection is False
- assert theToken._linkHeaders is False
- assert theToken._doBodyText is True
- assert theToken._doSynopsis is False
- assert theToken._doComments is False
- assert theToken._doKeywords is False
+ assert tokens._fmtTitle == nwHeadFmt.TITLE
+ assert tokens._fmtChapter == nwHeadFmt.TITLE
+ assert tokens._fmtUnNum == nwHeadFmt.TITLE
+ assert tokens._fmtScene == nwHeadFmt.TITLE
+ assert tokens._fmtSection == nwHeadFmt.TITLE
+ assert tokens._textFont == "Serif"
+ assert tokens._textSize == 11
+ assert tokens._textFixed is False
+ assert tokens._lineHeight == 1.15
+ assert tokens._blockIndent == 4.0
+ assert tokens._doJustify is False
+ assert tokens._marginTitle == (1.000, 0.500)
+ assert tokens._marginHead1 == (1.000, 0.500)
+ assert tokens._marginHead2 == (0.834, 0.500)
+ assert tokens._marginHead3 == (0.584, 0.500)
+ assert tokens._marginHead4 == (0.584, 0.500)
+ assert tokens._marginText == (0.000, 0.584)
+ assert tokens._marginMeta == (0.000, 0.584)
+ assert tokens._hideScene is False
+ assert tokens._hideSection is False
+ assert tokens._linkHeaders is False
+ assert tokens._doBodyText is True
+ assert tokens._doSynopsis is False
+ assert tokens._doComments is False
+ assert tokens._doKeywords is False
# Set new values
- theToken.setTitleFormat(f"T: {nwHeadFmt.TITLE}")
- theToken.setChapterFormat(f"C: {nwHeadFmt.TITLE}")
- theToken.setUnNumberedFormat(f"U: {nwHeadFmt.TITLE}")
- theToken.setSceneFormat(f"S: {nwHeadFmt.TITLE}", True)
- theToken.setSectionFormat(f"X: {nwHeadFmt.TITLE}", True)
- theToken.setFont("Monospace", 10, True)
- theToken.setLineHeight(2.0)
- theToken.setBlockIndent(6.0)
- theToken.setJustify(True)
- theToken.setTitleMargins(2.0, 2.0)
- theToken.setHead1Margins(2.0, 2.0)
- theToken.setHead2Margins(2.0, 2.0)
- theToken.setHead3Margins(2.0, 2.0)
- theToken.setHead4Margins(2.0, 2.0)
- theToken.setTextMargins(2.0, 2.0)
- theToken.setMetaMargins(2.0, 2.0)
- theToken.setLinkHeaders(True)
- theToken.setBodyText(False)
- theToken.setSynopsis(True)
- theToken.setComments(True)
- theToken.setKeywords(True)
+ tokens.setTitleFormat(f"T: {nwHeadFmt.TITLE}")
+ tokens.setChapterFormat(f"C: {nwHeadFmt.TITLE}")
+ tokens.setUnNumberedFormat(f"U: {nwHeadFmt.TITLE}")
+ tokens.setSceneFormat(f"S: {nwHeadFmt.TITLE}", True)
+ tokens.setSectionFormat(f"X: {nwHeadFmt.TITLE}", True)
+ tokens.setFont("Monospace", 10, True)
+ tokens.setLineHeight(2.0)
+ tokens.setBlockIndent(6.0)
+ tokens.setJustify(True)
+ tokens.setTitleMargins(2.0, 2.0)
+ tokens.setHead1Margins(2.0, 2.0)
+ tokens.setHead2Margins(2.0, 2.0)
+ tokens.setHead3Margins(2.0, 2.0)
+ tokens.setHead4Margins(2.0, 2.0)
+ tokens.setTextMargins(2.0, 2.0)
+ tokens.setMetaMargins(2.0, 2.0)
+ tokens.setLinkHeaders(True)
+ tokens.setBodyText(False)
+ tokens.setSynopsis(True)
+ tokens.setComments(True)
+ tokens.setKeywords(True)
# Check new values
- assert theToken._fmtTitle == f"T: {nwHeadFmt.TITLE}"
- assert theToken._fmtChapter == f"C: {nwHeadFmt.TITLE}"
- assert theToken._fmtUnNum == f"U: {nwHeadFmt.TITLE}"
- assert theToken._fmtScene == f"S: {nwHeadFmt.TITLE}"
- assert theToken._fmtSection == f"X: {nwHeadFmt.TITLE}"
- assert theToken._textFont == "Monospace"
- assert theToken._textSize == 10
- assert theToken._textFixed is True
- assert theToken._lineHeight == 2.0
- assert theToken._blockIndent == 6.0
- assert theToken._doJustify is True
- assert theToken._marginTitle == (2.0, 2.0)
- assert theToken._marginHead1 == (2.0, 2.0)
- assert theToken._marginHead2 == (2.0, 2.0)
- assert theToken._marginHead3 == (2.0, 2.0)
- assert theToken._marginHead4 == (2.0, 2.0)
- assert theToken._marginText == (2.0, 2.0)
- assert theToken._marginMeta == (2.0, 2.0)
- assert theToken._hideScene is True
- assert theToken._hideSection is True
- assert theToken._linkHeaders is True
- assert theToken._doBodyText is False
- assert theToken._doSynopsis is True
- assert theToken._doComments is True
- assert theToken._doKeywords is True
+ assert tokens._fmtTitle == f"T: {nwHeadFmt.TITLE}"
+ assert tokens._fmtChapter == f"C: {nwHeadFmt.TITLE}"
+ assert tokens._fmtUnNum == f"U: {nwHeadFmt.TITLE}"
+ assert tokens._fmtScene == f"S: {nwHeadFmt.TITLE}"
+ assert tokens._fmtSection == f"X: {nwHeadFmt.TITLE}"
+ assert tokens._textFont == "Monospace"
+ assert tokens._textSize == 10
+ assert tokens._textFixed is True
+ assert tokens._lineHeight == 2.0
+ assert tokens._blockIndent == 6.0
+ assert tokens._doJustify is True
+ assert tokens._marginTitle == (2.0, 2.0)
+ assert tokens._marginHead1 == (2.0, 2.0)
+ assert tokens._marginHead2 == (2.0, 2.0)
+ assert tokens._marginHead3 == (2.0, 2.0)
+ assert tokens._marginHead4 == (2.0, 2.0)
+ assert tokens._marginText == (2.0, 2.0)
+ assert tokens._marginMeta == (2.0, 2.0)
+ assert tokens._hideScene is True
+ assert tokens._hideSection is True
+ assert tokens._linkHeaders is True
+ assert tokens._doBodyText is False
+ assert tokens._doSynopsis is True
+ assert tokens._doComments is True
+ assert tokens._doKeywords is True
# Check Limits
- theToken.setLineHeight(0.0)
- assert theToken._lineHeight == 0.5
- theToken.setLineHeight(10.0)
- assert theToken._lineHeight == 5.0
+ tokens.setLineHeight(0.0)
+ assert tokens._lineHeight == 0.5
+ tokens.setLineHeight(10.0)
+ assert tokens._lineHeight == 5.0
- theToken.setBlockIndent(-6.0)
- assert theToken._blockIndent == 0.0
- theToken.setBlockIndent(60.0)
- assert theToken._blockIndent == 10.0
+ tokens.setBlockIndent(-6.0)
+ assert tokens._blockIndent == 0.0
+ tokens.setBlockIndent(60.0)
+ assert tokens._blockIndent == 10.0
# END Test testCoreToken_Setters
@@ -133,15 +134,15 @@ def testCoreToken_Setters(mockGUI):
@pytest.mark.core
def testCoreToken_TextOps(monkeypatch, mockGUI, mockRnd, fncPath):
"""Test handling files and text in the Tokenizer class."""
- theProject = NWProject()
+ project = NWProject()
mockRnd.reset()
- buildTestProject(theProject, fncPath)
+ buildTestProject(project, fncPath)
- theProject.data.setLanguage("en")
- theProject._loadProjectLocalisation()
+ project.data.setLanguage("en")
+ project._loadProjectLocalisation()
- theToken = BareTokenizer(theProject)
- theToken.setKeepMarkdown(True)
+ tokens = BareTokenizer(project)
+ tokens.setKeepMarkdown(True)
# Set some content to work with
docText = (
@@ -153,58 +154,65 @@ def testCoreToken_TextOps(monkeypatch, mockGUI, mockRnd, fncPath):
)
docTextR = docText.replace("", "this").replace("", "that")
- nDoc = theProject.storage.getDocument(C.hSceneDoc)
+ nDoc = project.storage.getDocument(C.hSceneDoc)
assert nDoc.writeDocument(docText)
- theProject.data.setAutoReplace({"A": "this", "B": "that"})
+ project.data.setAutoReplace({"A": "this", "B": "that"})
- assert theProject.saveProject()
+ assert project.saveProject()
# Root Heading
- assert theToken.addRootHeading("stuff") is False
- assert theToken.addRootHeading(C.hSceneDoc) is False
+ assert tokens.addRootHeading("stuff") is False
+ assert tokens.addRootHeading(C.hSceneDoc) is False
# First Page
- assert theToken.addRootHeading(C.hPlotRoot) is True
- assert theToken.theMarkdown[-1] == "# Notes: Plot\n\n"
- assert theToken._tokens[-1] == (
+ assert tokens.addRootHeading(C.hPlotRoot) is True
+ assert tokens.theMarkdown[-1] == "# Notes: Plot\n\n"
+ assert tokens._tokens[-1] == (
Tokenizer.T_TITLE, 0, "Notes: Plot", None, Tokenizer.A_CENTRE
)
# Not First Page
- assert theToken.addRootHeading(C.hPlotRoot) is True
- assert theToken.theMarkdown[-1] == "# Notes: Plot\n\n"
- assert theToken._tokens[-1] == (
+ assert tokens.addRootHeading(C.hPlotRoot) is True
+ assert tokens.theMarkdown[-1] == "# Notes: Plot\n\n"
+ assert tokens._tokens[-1] == (
Tokenizer.T_TITLE, 0, "Notes: Plot", None, Tokenizer.A_CENTRE | Tokenizer.A_PBB
)
# Set Text
- assert theToken.setText("stuff") is False
- assert theToken.setText(C.hSceneDoc) is True
- assert theToken._text == docText
+ assert tokens.setText("stuff") is False
+ assert tokens.setText(C.hSceneDoc) is True
+ assert tokens._text == docText
- assert theToken.setText(C.hSceneDoc, docText) is True
- assert theToken._text == docText
+ assert tokens.setText(C.hSceneDoc, docText) is True
+ assert tokens._text == docText
- assert theToken._isNone is False
- assert theToken._isNovel is True
- assert theToken._isNote is False
+ assert tokens._isNone is False
+ assert tokens._isNovel is True
+ assert tokens._isNote is False
# Pre Processing
- theToken.doPreProcessing()
- assert theToken._text == docTextR
+ tokens.doPreProcessing()
+ assert tokens._text == docTextR
# Save File
savePath = fncPath / "dump.nwd"
- theToken.saveRawMarkdown(savePath)
+ tokens.saveRawMarkdown(savePath)
assert readFile(savePath) == (
"# Notes: Plot\n\n"
"# Notes: Plot\n\n"
)
+ tokens.saveRawMarkdownJSON(savePath)
+ assert json.loads(readFile(savePath))["text"] == {
+ "nwd": [
+ ["# Notes: Plot"],
+ ["# Notes: Plot"]
+ ]
+ }
# Check abstract method
with pytest.raises(NotImplementedError):
- theToken.doConvert()
+ tokens.doConvert()
# END Test testCoreToken_TextOps
@@ -223,202 +231,202 @@ def testCoreToken_StripEscape():
@pytest.mark.core
def testCoreToken_HeaderFormat(mockGUI):
"""Test the tokenization of header formats in the Tokenizer class."""
- theProject = NWProject()
- theToken = BareTokenizer(theProject)
- theToken.setKeepMarkdown(True)
+ project = NWProject()
+ tokens = BareTokenizer(project)
+ tokens.setKeepMarkdown(True)
# Title
# =====
# Story File
- theToken._isNovel = True
- theToken._isNote = False
- theToken._isFirst = True
- theToken._text = "#! Novel Title\n"
+ tokens._isNovel = True
+ tokens._isNote = False
+ tokens._isFirst = True
+ tokens._text = "#! Novel Title\n"
- theToken.tokenizeText()
- assert theToken._tokens == [
+ tokens.tokenizeText()
+ assert tokens._tokens == [
(Tokenizer.T_TITLE, 1, "Novel Title", None, Tokenizer.A_CENTRE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
]
- assert theToken.theMarkdown[-1] == "#! Novel Title\n\n"
+ assert tokens.theMarkdown[-1] == "#! Novel Title\n\n"
# Note File
- theToken._isNovel = False
- theToken._isNote = True
- theToken._isFirst = True
- theToken._text = "#! Note Title\n"
+ tokens._isNovel = False
+ tokens._isNote = True
+ tokens._isFirst = True
+ tokens._text = "#! Note Title\n"
- theToken.tokenizeText()
- assert theToken._tokens == [
+ tokens.tokenizeText()
+ assert tokens._tokens == [
(Tokenizer.T_HEAD1, 1, "Note Title", None, Tokenizer.A_CENTRE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
]
- assert theToken.theMarkdown[-1] == "#! Note Title\n\n"
+ assert tokens.theMarkdown[-1] == "#! Note Title\n\n"
# Header 1
# ========
# Story File
- theToken._isNovel = True
- theToken._isNote = False
- theToken._isFirst = True
- theToken._text = "# Novel Title\n"
+ tokens._isNovel = True
+ tokens._isNote = False
+ tokens._isFirst = True
+ tokens._text = "# Novel Title\n"
- theToken.tokenizeText()
- assert theToken._tokens == [
+ tokens.tokenizeText()
+ assert tokens._tokens == [
(Tokenizer.T_HEAD1, 1, "Novel Title", None, Tokenizer.A_CENTRE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
]
- assert theToken.theMarkdown[-1] == "# Novel Title\n\n"
+ assert tokens.theMarkdown[-1] == "# Novel Title\n\n"
# Note File
- theToken._isNovel = False
- theToken._isNote = True
- theToken._isFirst = True
- theToken._text = "# Note Title\n"
+ tokens._isNovel = False
+ tokens._isNote = True
+ tokens._isFirst = True
+ tokens._text = "# Note Title\n"
- theToken.tokenizeText()
- assert theToken._tokens == [
+ tokens.tokenizeText()
+ assert tokens._tokens == [
(Tokenizer.T_HEAD1, 1, "Note Title", None, Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
]
- assert theToken.theMarkdown[-1] == "# Note Title\n\n"
+ assert tokens.theMarkdown[-1] == "# Note Title\n\n"
# Header 2
# ========
# Story File
- theToken._isNovel = True
- theToken._isNote = False
- theToken._text = "## Chapter One\n"
+ tokens._isNovel = True
+ tokens._isNote = False
+ tokens._text = "## Chapter One\n"
- theToken.tokenizeText()
- assert theToken._tokens == [
+ tokens.tokenizeText()
+ assert tokens._tokens == [
(Tokenizer.T_HEAD2, 1, "Chapter One", None, Tokenizer.A_PBB),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
]
- assert theToken.theMarkdown[-1] == "## Chapter One\n\n"
+ assert tokens.theMarkdown[-1] == "## Chapter One\n\n"
# Note File
- theToken._isNovel = False
- theToken._isNote = True
- theToken._text = "## Heading 2\n"
+ tokens._isNovel = False
+ tokens._isNote = True
+ tokens._text = "## Heading 2\n"
- theToken.tokenizeText()
- assert theToken._tokens == [
+ tokens.tokenizeText()
+ assert tokens._tokens == [
(Tokenizer.T_HEAD2, 1, "Heading 2", None, Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
]
- assert theToken.theMarkdown[-1] == "## Heading 2\n\n"
+ assert tokens.theMarkdown[-1] == "## Heading 2\n\n"
# Header 3
# ========
# Story File
- theToken._isNovel = True
- theToken._isNote = False
- theToken._text = "### Scene One\n"
+ tokens._isNovel = True
+ tokens._isNote = False
+ tokens._text = "### Scene One\n"
- theToken.tokenizeText()
- assert theToken._tokens == [
+ tokens.tokenizeText()
+ assert tokens._tokens == [
(Tokenizer.T_HEAD3, 1, "Scene One", None, Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
]
- assert theToken.theMarkdown[-1] == "### Scene One\n\n"
+ assert tokens.theMarkdown[-1] == "### Scene One\n\n"
# Note File
- theToken._isNovel = False
- theToken._isNote = True
- theToken._text = "### Heading 3\n"
+ tokens._isNovel = False
+ tokens._isNote = True
+ tokens._text = "### Heading 3\n"
- theToken.tokenizeText()
- assert theToken._tokens == [
+ tokens.tokenizeText()
+ assert tokens._tokens == [
(Tokenizer.T_HEAD3, 1, "Heading 3", None, Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
]
- assert theToken.theMarkdown[-1] == "### Heading 3\n\n"
+ assert tokens.theMarkdown[-1] == "### Heading 3\n\n"
# Header 4
# ========
# Story File
- theToken._isNovel = True
- theToken._isNote = False
- theToken._text = "#### A Section\n"
+ tokens._isNovel = True
+ tokens._isNote = False
+ tokens._text = "#### A Section\n"
- theToken.tokenizeText()
- assert theToken._tokens == [
+ tokens.tokenizeText()
+ assert tokens._tokens == [
(Tokenizer.T_HEAD4, 1, "A Section", None, Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
]
- assert theToken.theMarkdown[-1] == "#### A Section\n\n"
+ assert tokens.theMarkdown[-1] == "#### A Section\n\n"
# Note File
- theToken._isNovel = False
- theToken._isNote = True
- theToken._text = "#### Heading 4\n"
+ tokens._isNovel = False
+ tokens._isNote = True
+ tokens._text = "#### Heading 4\n"
- theToken.tokenizeText()
- assert theToken._tokens == [
+ tokens.tokenizeText()
+ assert tokens._tokens == [
(Tokenizer.T_HEAD4, 1, "Heading 4", None, Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
]
- assert theToken.theMarkdown[-1] == "#### Heading 4\n\n"
+ assert tokens.theMarkdown[-1] == "#### Heading 4\n\n"
# Title
# =====
# Story File
- theToken._isNovel = True
- theToken._isNote = False
- theToken._text = "#! Title\n"
+ tokens._isNovel = True
+ tokens._isNote = False
+ tokens._text = "#! Title\n"
- theToken.tokenizeText()
- assert theToken._tokens == [
+ tokens.tokenizeText()
+ assert tokens._tokens == [
(Tokenizer.T_TITLE, 1, "Title", None, Tokenizer.A_CENTRE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
]
- assert theToken.theMarkdown[-1] == "#! Title\n\n"
+ assert tokens.theMarkdown[-1] == "#! Title\n\n"
# Note File
- theToken._isNovel = False
- theToken._isNote = True
- theToken._text = "#! Title\n"
+ tokens._isNovel = False
+ tokens._isNote = True
+ tokens._text = "#! Title\n"
- theToken.tokenizeText()
- assert theToken._tokens == [
+ tokens.tokenizeText()
+ assert tokens._tokens == [
(Tokenizer.T_HEAD1, 1, "Title", None, Tokenizer.A_CENTRE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
]
- assert theToken.theMarkdown[-1] == "#! Title\n\n"
+ assert tokens.theMarkdown[-1] == "#! Title\n\n"
# Unnumbered
# ==========
# Story File
- theToken._isNovel = True
- theToken._isNote = False
- theToken._text = "##! Prologue\n"
+ tokens._isNovel = True
+ tokens._isNote = False
+ tokens._text = "##! Prologue\n"
- theToken.tokenizeText()
- assert theToken._tokens == [
+ tokens.tokenizeText()
+ assert tokens._tokens == [
(Tokenizer.T_UNNUM, 1, "Prologue", None, Tokenizer.A_PBB),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
]
- assert theToken.theMarkdown[-1] == "##! Prologue\n\n"
+ assert tokens.theMarkdown[-1] == "##! Prologue\n\n"
# Note File
- theToken._isNovel = False
- theToken._isNote = True
- theToken._text = "##! Prologue\n"
+ tokens._isNovel = False
+ tokens._isNote = True
+ tokens._text = "##! Prologue\n"
- theToken.tokenizeText()
- assert theToken._tokens == [
+ tokens.tokenizeText()
+ assert tokens._tokens == [
(Tokenizer.T_HEAD2, 1, "Prologue", None, Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
]
- assert theToken.theMarkdown[-1] == "##! Prologue\n\n"
+ assert tokens.theMarkdown[-1] == "##! Prologue\n\n"
# END Test testCoreToken_HeaderFormat
@@ -426,67 +434,67 @@ def testCoreToken_HeaderFormat(mockGUI):
@pytest.mark.core
def testCoreToken_MetaFormat(mockGUI):
"""Test the tokenization of meta formats in the Tokenizer class."""
- theProject = NWProject()
- theToken = BareTokenizer(theProject)
- theToken.setKeepMarkdown(True)
+ project = NWProject()
+ tokens = BareTokenizer(project)
+ tokens.setKeepMarkdown(True)
# Comment
- theToken._text = "% A comment\n"
- theToken.tokenizeText()
- assert theToken._tokens == [
+ tokens._text = "% A comment\n"
+ tokens.tokenizeText()
+ assert tokens._tokens == [
(Tokenizer.T_COMMENT, 0, "A comment", None, Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE),
]
- assert theToken.theMarkdown[-1] == "\n"
+ assert tokens.theMarkdown[-1] == "\n"
- theToken.setComments(True)
- theToken.tokenizeText()
- assert theToken.theMarkdown[-1] == "% A comment\n\n"
+ tokens.setComments(True)
+ tokens.tokenizeText()
+ assert tokens.theMarkdown[-1] == "% A comment\n\n"
# Synopsis
- theToken._text = "%synopsis: The synopsis\n"
- theToken.tokenizeText()
- assert theToken._tokens == [
+ tokens._text = "%synopsis: The synopsis\n"
+ tokens.tokenizeText()
+ assert tokens._tokens == [
(Tokenizer.T_SYNOPSIS, 0, "The synopsis", None, Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE),
]
- theToken._text = "% synopsis: The synopsis\n"
- theToken.tokenizeText()
- assert theToken._tokens == [
+ tokens._text = "% synopsis: The synopsis\n"
+ tokens.tokenizeText()
+ assert tokens._tokens == [
(Tokenizer.T_SYNOPSIS, 0, "The synopsis", None, Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE),
]
- assert theToken.theMarkdown[-1] == "\n"
+ assert tokens.theMarkdown[-1] == "\n"
- theToken.setSynopsis(True)
- theToken.tokenizeText()
- assert theToken.theMarkdown[-1] == "% synopsis: The synopsis\n\n"
+ tokens.setSynopsis(True)
+ tokens.tokenizeText()
+ assert tokens.theMarkdown[-1] == "% synopsis: The synopsis\n\n"
# Keyword
- theToken._text = "@char: Bod\n"
- theToken.tokenizeText()
- assert theToken._tokens == [
+ tokens._text = "@char: Bod\n"
+ tokens.tokenizeText()
+ assert tokens._tokens == [
(Tokenizer.T_KEYWORD, 0, "char: Bod", None, Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE),
]
- assert theToken.theMarkdown[-1] == "\n"
+ assert tokens.theMarkdown[-1] == "\n"
- theToken.setKeywords(True)
- theToken.tokenizeText()
- assert theToken.theMarkdown[-1] == "@char: Bod\n\n"
+ tokens.setKeywords(True)
+ tokens.tokenizeText()
+ assert tokens.theMarkdown[-1] == "@char: Bod\n\n"
- theToken._text = "@pov: Bod\n@plot: Main\n@location: Europe\n"
- theToken.tokenizeText()
+ tokens._text = "@pov: Bod\n@plot: Main\n@location: Europe\n"
+ tokens.tokenizeText()
styTop = Tokenizer.A_NONE | Tokenizer.A_Z_BTMMRG
styMid = Tokenizer.A_NONE | Tokenizer.A_Z_BTMMRG | Tokenizer.A_Z_TOPMRG
styBtm = Tokenizer.A_NONE | Tokenizer.A_Z_TOPMRG
- assert theToken._tokens == [
+ assert tokens._tokens == [
(Tokenizer.T_KEYWORD, 0, "pov: Bod", None, styTop),
(Tokenizer.T_KEYWORD, 0, "plot: Main", None, styMid),
(Tokenizer.T_KEYWORD, 0, "location: Europe", None, styBtm),
(Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE),
]
- assert theToken.theMarkdown[-1] == "@pov: Bod\n@plot: Main\n@location: Europe\n\n"
+ assert tokens.theMarkdown[-1] == "@pov: Bod\n@plot: Main\n@location: Europe\n\n"
# END Test testCoreToken_MetaFormat
@@ -494,14 +502,14 @@ def testCoreToken_MetaFormat(mockGUI):
@pytest.mark.core
def testCoreToken_MarginFormat(mockGUI):
"""Test the tokenization of margin formats in the Tokenizer class."""
- theProject = NWProject()
- theToken = BareTokenizer(theProject)
- theToken.setKeepMarkdown(True)
+ project = NWProject()
+ tokens = BareTokenizer(project)
+ tokens.setKeepMarkdown(True)
# Alignment and Indentation
dblIndent = Tokenizer.A_IND_L | Tokenizer.A_IND_R
rIndAlign = Tokenizer.A_RIGHT | Tokenizer.A_IND_R
- theToken._text = (
+ tokens._text = (
"Some regular text\n\n"
"Some left-aligned text <<\n\n"
">> Some right-aligned text\n\n"
@@ -511,8 +519,8 @@ def testCoreToken_MarginFormat(mockGUI):
"> Double-indented block <\n\n"
">> Right-indent, right-aligned <\n\n"
)
- theToken.tokenizeText()
- assert theToken._tokens == [
+ tokens.tokenizeText()
+ assert tokens._tokens == [
(Tokenizer.T_TEXT, 0, "Some regular text", [], Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE),
(Tokenizer.T_TEXT, 0, "Some left-aligned text", [], Tokenizer.A_LEFT),
@@ -531,7 +539,7 @@ def testCoreToken_MarginFormat(mockGUI):
(Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE),
]
- assert theToken.theMarkdown[-1] == (
+ assert tokens.theMarkdown[-1] == (
"Some regular text\n\n"
"Some left-aligned text\n\n"
"Some right-aligned text\n\n"
@@ -545,121 +553,212 @@ def testCoreToken_MarginFormat(mockGUI):
# END Test testCoreToken_MarginFormat
+@pytest.mark.core
+def testCoreToken_ExtractFormats(mockGUI):
+ """Test the extraction of formats in the Tokenizer class."""
+ project = NWProject()
+ tokens = BareTokenizer(project)
+ tokens.setKeepMarkdown(True)
+
+ # Markdown
+ # ========
+
+ # Plain bold
+ text, fmt = tokens._extractFormats("Text with **bold** in it.")
+ assert text == "Text with bold in it."
+ assert fmt == [(10, tokens.FMT_B_B), (14, tokens.FMT_B_E)]
+
+ # Plain italics
+ text, fmt = tokens._extractFormats("Text with _italics_ in it.")
+ assert text == "Text with italics in it."
+ assert fmt == [(10, tokens.FMT_I_B), (17, tokens.FMT_I_E)]
+
+ # Plain strikethrough
+ text, fmt = tokens._extractFormats("Text with ~~strikethrough~~ in it.")
+ assert text == "Text with strikethrough in it."
+ assert fmt == [(10, tokens.FMT_D_B), (23, tokens.FMT_D_E)]
+
+ # Nested bold/italics
+ text, fmt = tokens._extractFormats("Text with **bold and _italics_** in it.")
+ assert text == "Text with bold and italics in it."
+ assert fmt == [
+ (10, tokens.FMT_B_B), (19, tokens.FMT_I_B), (26, tokens.FMT_I_E), (26, tokens.FMT_B_E)
+ ]
+
+ # Bold with overlapping italics
+ # Here, bold is ignored because it is not on word boundary
+ text, fmt = tokens._extractFormats("Text with **bold and overlapping _italics**_ in it.")
+ assert text == "Text with **bold and overlapping italics** in it."
+ assert fmt == [(33, tokens.FMT_I_B), (42, tokens.FMT_I_E)]
+
+ # Shortcodes
+ # ==========
+
+ # Plain bold
+ text, fmt = tokens._extractFormats("Text with [b]bold[/b] in it.")
+ assert text == "Text with bold in it."
+ assert fmt == [(10, tokens.FMT_B_B), (14, tokens.FMT_B_E)]
+
+ # Plain italics
+ text, fmt = tokens._extractFormats("Text with [i]italics[/i] in it.")
+ assert text == "Text with italics in it."
+ assert fmt == [(10, tokens.FMT_I_B), (17, tokens.FMT_I_E)]
+
+ # Plain strikethrough
+ text, fmt = tokens._extractFormats("Text with [s]strikethrough[/s] in it.")
+ assert text == "Text with strikethrough in it."
+ assert fmt == [(10, tokens.FMT_D_B), (23, tokens.FMT_D_E)]
+
+ # Plain underline
+ text, fmt = tokens._extractFormats("Text with [u]underline[/u] in it.")
+ assert text == "Text with underline in it."
+ assert fmt == [(10, tokens.FMT_U_B), (19, tokens.FMT_U_E)]
+
+ # Plain superscript
+ text, fmt = tokens._extractFormats("Text with super[sup]script[/sup] in it.")
+ assert text == "Text with superscript in it."
+ assert fmt == [(15, tokens.FMT_SUP_B), (21, tokens.FMT_SUP_E)]
+
+ # Plain subscript
+ text, fmt = tokens._extractFormats("Text with sub[sub]script[/sub] in it.")
+ assert text == "Text with subscript in it."
+ assert fmt == [(13, tokens.FMT_SUB_B), (19, tokens.FMT_SUB_E)]
+
+ # Nested bold/italics
+ text, fmt = tokens._extractFormats("Text with [b]bold and [i]italics[/i][/b] in it.")
+ assert text == "Text with bold and italics in it."
+ assert fmt == [
+ (10, tokens.FMT_B_B), (19, tokens.FMT_I_B), (26, tokens.FMT_I_E), (26, tokens.FMT_B_E)
+ ]
+
+ # Bold with overlapping italics
+ # With shortcodes, this works
+ text, fmt = tokens._extractFormats(
+ "Text with [b]bold and overlapping [i]italics[/b][/i] in it."
+ )
+ assert text == "Text with bold and overlapping italics in it."
+ assert fmt == [
+ (10, tokens.FMT_B_B), (31, tokens.FMT_I_B), (38, tokens.FMT_B_E), (38, tokens.FMT_I_E)
+ ]
+
+# END Test testCoreToken_ExtractFormats
+
+
@pytest.mark.core
def testCoreToken_TextFormat(mockGUI):
"""Test the tokenization of text formats in the Tokenizer class."""
- theProject = NWProject()
- theToken = BareTokenizer(theProject)
- theToken.setKeepMarkdown(True)
+ project = NWProject()
+ tokens = BareTokenizer(project)
+ tokens.setKeepMarkdown(True)
# Text
- theToken._text = "Some plain text\non two lines\n\n\n"
- theToken.tokenizeText()
- assert theToken._tokens == [
+ tokens._text = "Some plain text\non two lines\n\n\n"
+ tokens.tokenizeText()
+ assert tokens._tokens == [
(Tokenizer.T_TEXT, 0, "Some plain text", [], Tokenizer.A_NONE),
(Tokenizer.T_TEXT, 0, "on two lines", [], Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE),
]
- assert theToken.theMarkdown[-1] == "Some plain text\non two lines\n\n\n\n"
+ assert tokens.theMarkdown[-1] == "Some plain text\non two lines\n\n\n\n"
- theToken.setBodyText(False)
- theToken.tokenizeText()
- assert theToken._tokens == [
+ tokens.setBodyText(False)
+ tokens.tokenizeText()
+ assert tokens._tokens == [
(Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE),
]
- assert theToken.theMarkdown[-1] == "\n\n\n"
- theToken.setBodyText(True)
+ assert tokens.theMarkdown[-1] == "\n\n\n"
+ tokens.setBodyText(True)
# Text Emphasis
- theToken._text = "Some **bolded text** on this lines\n"
- theToken.tokenizeText()
- assert theToken._tokens == [
+ tokens._text = "Some **bolded text** on this lines\n"
+ tokens.tokenizeText()
+ assert tokens._tokens == [
(
Tokenizer.T_TEXT, 0,
- "Some **bolded text** on this lines",
+ "Some bolded text on this lines",
[
- [5, 2, Tokenizer.FMT_B_B],
- [18, 2, Tokenizer.FMT_B_E],
+ (5, Tokenizer.FMT_B_B),
+ (16, Tokenizer.FMT_B_E),
],
Tokenizer.A_NONE
),
(Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE),
]
- assert theToken.theMarkdown[-1] == "Some **bolded text** on this lines\n\n"
+ assert tokens.theMarkdown[-1] == "Some **bolded text** on this lines\n\n"
- theToken._text = "Some _italic text_ on this lines\n"
- theToken.tokenizeText()
- assert theToken._tokens == [
+ tokens._text = "Some _italic text_ on this lines\n"
+ tokens.tokenizeText()
+ assert tokens._tokens == [
(
Tokenizer.T_TEXT, 0,
- "Some _italic text_ on this lines",
+ "Some italic text on this lines",
[
- [5, 1, Tokenizer.FMT_I_B],
- [17, 1, Tokenizer.FMT_I_E],
+ (5, Tokenizer.FMT_I_B),
+ (16, Tokenizer.FMT_I_E),
],
Tokenizer.A_NONE
),
(Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE),
]
- assert theToken.theMarkdown[-1] == "Some _italic text_ on this lines\n\n"
+ assert tokens.theMarkdown[-1] == "Some _italic text_ on this lines\n\n"
- theToken._text = "Some **_bold italic text_** on this lines\n"
- theToken.tokenizeText()
- assert theToken._tokens == [
+ tokens._text = "Some **_bold italic text_** on this lines\n"
+ tokens.tokenizeText()
+ assert tokens._tokens == [
(
Tokenizer.T_TEXT, 0,
- "Some **_bold italic text_** on this lines",
+ "Some bold italic text on this lines",
[
- [5, 2, Tokenizer.FMT_B_B],
- [7, 1, Tokenizer.FMT_I_B],
- [24, 1, Tokenizer.FMT_I_E],
- [25, 2, Tokenizer.FMT_B_E],
+ (5, Tokenizer.FMT_B_B),
+ (5, Tokenizer.FMT_I_B),
+ (21, Tokenizer.FMT_I_E),
+ (21, Tokenizer.FMT_B_E),
],
Tokenizer.A_NONE
),
(Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE),
]
- assert theToken.theMarkdown[-1] == "Some **_bold italic text_** on this lines\n\n"
+ assert tokens.theMarkdown[-1] == "Some **_bold italic text_** on this lines\n\n"
- theToken._text = "Some ~~strikethrough text~~ on this lines\n"
- theToken.tokenizeText()
- assert theToken._tokens == [
+ tokens._text = "Some ~~strikethrough text~~ on this lines\n"
+ tokens.tokenizeText()
+ assert tokens._tokens == [
(
Tokenizer.T_TEXT, 0,
- "Some ~~strikethrough text~~ on this lines",
+ "Some strikethrough text on this lines",
[
- [5, 2, Tokenizer.FMT_D_B],
- [25, 2, Tokenizer.FMT_D_E],
+ (5, Tokenizer.FMT_D_B),
+ (23, Tokenizer.FMT_D_E),
],
Tokenizer.A_NONE
),
(Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE),
]
- assert theToken.theMarkdown[-1] == "Some ~~strikethrough text~~ on this lines\n\n"
+ assert tokens.theMarkdown[-1] == "Some ~~strikethrough text~~ on this lines\n\n"
- theToken._text = "Some **nested bold and _italic_ and ~~strikethrough~~ text** here\n"
- theToken.tokenizeText()
- assert theToken._tokens == [
+ tokens._text = "Some **nested bold and _italic_ and ~~strikethrough~~ text** here\n"
+ tokens.tokenizeText()
+ assert tokens._tokens == [
(
Tokenizer.T_TEXT, 0,
- "Some **nested bold and _italic_ and ~~strikethrough~~ text** here",
+ "Some nested bold and italic and strikethrough text here",
[
- [5, 2, Tokenizer.FMT_B_B],
- [23, 1, Tokenizer.FMT_I_B],
- [30, 1, Tokenizer.FMT_I_E],
- [36, 2, Tokenizer.FMT_D_B],
- [51, 2, Tokenizer.FMT_D_E],
- [58, 2, Tokenizer.FMT_B_E],
+ (5, Tokenizer.FMT_B_B),
+ (21, Tokenizer.FMT_I_B),
+ (27, Tokenizer.FMT_I_E),
+ (32, Tokenizer.FMT_D_B),
+ (45, Tokenizer.FMT_D_E),
+ (50, Tokenizer.FMT_B_E),
],
Tokenizer.A_NONE
),
(Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE),
]
- assert theToken.theMarkdown[-1] == (
+ assert tokens.theMarkdown[-1] == (
"Some **nested bold and _italic_ and ~~strikethrough~~ text** here\n\n"
)
@@ -669,10 +768,10 @@ def testCoreToken_TextFormat(mockGUI):
@pytest.mark.core
def testCoreToken_SpecialFormat(mockGUI):
"""Test the tokenization of special formats in the Tokenizer class."""
- theProject = NWProject()
- theToken = BareTokenizer(theProject)
+ project = NWProject()
+ tokens = BareTokenizer(project)
- theToken._isNovel = True
+ tokens._isNovel = True
# New Page
# ========
@@ -687,45 +786,45 @@ def testCoreToken_SpecialFormat(mockGUI):
]
# Command wo/Space
- theToken._isFirst = True
- theToken._text = (
+ tokens._isFirst = True
+ tokens._text = (
"# Title One\n\n"
- "[NEWPAGE]\n\n"
+ "[newpage]\n\n"
"# Title Two\n\n"
)
- theToken.tokenizeText()
- assert theToken._tokens == correctResp
+ tokens.tokenizeText()
+ assert tokens._tokens == correctResp
# Command w/Space
- theToken._isFirst = True
- theToken._text = (
+ tokens._isFirst = True
+ tokens._text = (
"# Title One\n\n"
- "[NEW PAGE]\n\n"
+ "[new page]\n\n"
"# Title Two\n\n"
)
- theToken.tokenizeText()
- assert theToken._tokens == correctResp
+ tokens.tokenizeText()
+ assert tokens._tokens == correctResp
# Trailing Spaces
- theToken._isFirst = True
- theToken._text = (
+ tokens._isFirst = True
+ tokens._text = (
"# Title One\n\n"
- "[NEW PAGE] \t\n\n"
+ "[new page] \t\n\n"
"# Title Two\n\n"
)
- theToken.tokenizeText()
- assert theToken._tokens == correctResp
+ tokens.tokenizeText()
+ assert tokens._tokens == correctResp
# Single Empty Paragraph
# ======================
- theToken._text = (
+ tokens._text = (
"# Title One\n\n"
- "[VSPACE] \n\n"
+ "[vspace] \n\n"
"Some text to go here ...\n\n"
)
- theToken.tokenizeText()
- assert theToken._tokens == [
+ tokens.tokenizeText()
+ assert tokens._tokens == [
(Tokenizer.T_HEAD1, 1, "Title One", None, Tokenizer.A_PBB | Tokenizer.A_CENTRE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
(Tokenizer.T_SKIP, 1, "", None, Tokenizer.A_NONE),
@@ -739,13 +838,13 @@ def testCoreToken_SpecialFormat(mockGUI):
# =========================
# One Skip
- theToken._text = (
+ tokens._text = (
"# Title One\n\n"
- "[VSPACE:1] \n\n"
+ "[vspace:1] \n\n"
"Some text to go here ...\n\n"
)
- theToken.tokenizeText()
- assert theToken._tokens == [
+ tokens.tokenizeText()
+ assert tokens._tokens == [
(Tokenizer.T_HEAD1, 1, "Title One", None, Tokenizer.A_PBB | Tokenizer.A_CENTRE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
(Tokenizer.T_SKIP, 1, "", None, Tokenizer.A_NONE),
@@ -756,13 +855,13 @@ def testCoreToken_SpecialFormat(mockGUI):
]
# Three Skips
- theToken._text = (
+ tokens._text = (
"# Title One\n\n"
- "[VSPACE:3] \n\n"
+ "[vspace:3] \n\n"
"Some text to go here ...\n\n"
)
- theToken.tokenizeText()
- assert theToken._tokens == [
+ tokens.tokenizeText()
+ assert tokens._tokens == [
(Tokenizer.T_HEAD1, 1, "Title One", None, Tokenizer.A_PBB | Tokenizer.A_CENTRE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
(Tokenizer.T_SKIP, 1, "", None, Tokenizer.A_NONE),
@@ -775,13 +874,13 @@ def testCoreToken_SpecialFormat(mockGUI):
]
# Malformed Command, Case 1
- theToken._text = (
+ tokens._text = (
"# Title One\n\n"
- "[VSPACE:3xa] \n\n"
+ "[vspace:3xa] \n\n"
"Some text to go here ...\n\n"
)
- theToken.tokenizeText()
- assert theToken._tokens == [
+ tokens.tokenizeText()
+ assert tokens._tokens == [
(Tokenizer.T_HEAD1, 1, "Title One", None, Tokenizer.A_PBB | Tokenizer.A_CENTRE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
@@ -791,13 +890,13 @@ def testCoreToken_SpecialFormat(mockGUI):
]
# Malformed Command, Case 2
- theToken._text = (
+ tokens._text = (
"# Title One\n\n"
- "[VSPACE:3.5]\n\n"
+ "[vspace:3.5]\n\n"
"Some text to go here ...\n\n"
)
- theToken.tokenizeText()
- assert theToken._tokens == [
+ tokens.tokenizeText()
+ assert tokens._tokens == [
(Tokenizer.T_HEAD1, 1, "Title One", None, Tokenizer.A_PBB | Tokenizer.A_CENTRE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
@@ -807,13 +906,13 @@ def testCoreToken_SpecialFormat(mockGUI):
]
# Malformed Command, Case 3
- theToken._text = (
+ tokens._text = (
"# Title One\n\n"
- "[VSPACE:-1]\n\n"
+ "[vspace:-1]\n\n"
"Some text to go here ...\n\n"
)
- theToken.tokenizeText()
- assert theToken._tokens == [
+ tokens.tokenizeText()
+ assert tokens._tokens == [
(Tokenizer.T_HEAD1, 1, "Title One", None, Tokenizer.A_PBB | Tokenizer.A_CENTRE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
@@ -826,14 +925,14 @@ def testCoreToken_SpecialFormat(mockGUI):
# ==============================
# Single Skip
- theToken._text = (
+ tokens._text = (
"# Title One\n\n"
- "[NEW PAGE]\n\n"
- "[VSPACE]\n\n"
+ "[new page]\n\n"
+ "[vspace]\n\n"
"Some text to go here ...\n\n"
)
- theToken.tokenizeText()
- assert theToken._tokens == [
+ tokens.tokenizeText()
+ assert tokens._tokens == [
(Tokenizer.T_HEAD1, 1, "Title One", None, Tokenizer.A_PBB | Tokenizer.A_CENTRE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
@@ -845,14 +944,14 @@ def testCoreToken_SpecialFormat(mockGUI):
]
# Multiple Skip
- theToken._text = (
+ tokens._text = (
"# Title One\n\n"
- "[NEW PAGE]\n\n"
- "[VSPACE:3]\n\n"
+ "[new page]\n\n"
+ "[vspace:3]\n\n"
"Some text to go here ...\n\n"
)
- theToken.tokenizeText()
- assert theToken._tokens == [
+ tokens.tokenizeText()
+ assert tokens._tokens == [
(Tokenizer.T_HEAD1, 1, "Title One", None, Tokenizer.A_PBB | Tokenizer.A_CENTRE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
@@ -871,51 +970,51 @@ def testCoreToken_SpecialFormat(mockGUI):
@pytest.mark.core
def testCoreToken_ProcessHeaders(mockGUI):
"""Test the header and page parser of the Tokenizer class."""
- theProject = NWProject()
- theProject.data.setLanguage("en")
- theProject._loadProjectLocalisation()
- theToken = BareTokenizer(theProject)
+ project = NWProject()
+ project.data.setLanguage("en")
+ project._loadProjectLocalisation()
+ tokens = BareTokenizer(project)
# Nothing
- theToken._text = "Some text ...\n"
- assert theToken.doHeaders() is False
- theToken._isNone = True
- assert theToken.doHeaders() is False
- theToken._isNone = False
- assert theToken.doHeaders() is False
- theToken._isNote = True
- assert theToken.doHeaders() is False
- theToken._isNote = False
+ tokens._text = "Some text ...\n"
+ assert tokens.doHeaders() is False
+ tokens._isNone = True
+ assert tokens.doHeaders() is False
+ tokens._isNone = False
+ assert tokens.doHeaders() is False
+ tokens._isNote = True
+ assert tokens.doHeaders() is False
+ tokens._isNote = False
##
# Story FIles
##
- theToken._isNone = False
- theToken._isNote = False
- theToken._isNovel = True
+ tokens._isNone = False
+ tokens._isNote = False
+ tokens._isNovel = True
# Titles
# ======
# H1: Title, First Page
- assert theToken._isFirst is True
- theToken._text = "# Part One\n"
- theToken.setTitleFormat(f"T: {nwHeadFmt.TITLE}")
- theToken.tokenizeText()
- theToken.doHeaders()
- assert theToken._tokens == [
+ assert tokens._isFirst is True
+ tokens._text = "# Part One\n"
+ tokens.setTitleFormat(f"T: {nwHeadFmt.TITLE}")
+ tokens.tokenizeText()
+ tokens.doHeaders()
+ assert tokens._tokens == [
(Tokenizer.T_HEAD1, 1, "T: Part One", None, Tokenizer.A_CENTRE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
]
# H1: Title, Not First Page
- assert theToken._isFirst is False
- theToken._text = "# Part One\n"
- theToken.setTitleFormat(f"T: {nwHeadFmt.TITLE}")
- theToken.tokenizeText()
- theToken.doHeaders()
- assert theToken._tokens == [
+ assert tokens._isFirst is False
+ tokens._text = "# Part One\n"
+ tokens.setTitleFormat(f"T: {nwHeadFmt.TITLE}")
+ tokens.tokenizeText()
+ tokens.doHeaders()
+ assert tokens._tokens == [
(Tokenizer.T_HEAD1, 1, "T: Part One", None, Tokenizer.A_PBB | Tokenizer.A_CENTRE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
]
@@ -924,52 +1023,52 @@ def testCoreToken_ProcessHeaders(mockGUI):
# ========
# H2: Chapter
- theToken._text = "## Chapter One\n"
- theToken.setChapterFormat(f"C: {nwHeadFmt.TITLE}")
- theToken.tokenizeText()
- theToken.doHeaders()
- assert theToken._tokens == [
+ tokens._text = "## Chapter One\n"
+ tokens.setChapterFormat(f"C: {nwHeadFmt.TITLE}")
+ tokens.tokenizeText()
+ tokens.doHeaders()
+ assert tokens._tokens == [
(Tokenizer.T_HEAD2, 1, "C: Chapter One", None, Tokenizer.A_PBB),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
]
# H2: Unnumbered Chapter
- theToken._text = "##! Prologue\n"
- theToken.setUnNumberedFormat(f"U: {nwHeadFmt.TITLE}")
- theToken.tokenizeText()
- theToken.doHeaders()
- assert theToken._tokens == [
+ tokens._text = "##! Prologue\n"
+ tokens.setUnNumberedFormat(f"U: {nwHeadFmt.TITLE}")
+ tokens.tokenizeText()
+ tokens.doHeaders()
+ assert tokens._tokens == [
(Tokenizer.T_UNNUM, 1, "U: Prologue", None, Tokenizer.A_PBB),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
]
# H2: Chapter Word Number
- theToken._text = "## Chapter\n"
- theToken.setChapterFormat(f"Chapter {nwHeadFmt.CH_WORD}")
- theToken._hFormatter._chCount = 0
- theToken.tokenizeText()
- theToken.doHeaders()
- assert theToken._tokens == [
+ tokens._text = "## Chapter\n"
+ tokens.setChapterFormat(f"Chapter {nwHeadFmt.CH_WORD}")
+ tokens._hFormatter._chCount = 0
+ tokens.tokenizeText()
+ tokens.doHeaders()
+ assert tokens._tokens == [
(Tokenizer.T_HEAD2, 1, "Chapter One", None, Tokenizer.A_PBB),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
]
# H2: Chapter Roman Number Upper Case
- theToken._text = "## Chapter\n"
- theToken.setChapterFormat(f"Chapter {nwHeadFmt.CH_ROMU}")
- theToken.tokenizeText()
- theToken.doHeaders()
- assert theToken._tokens == [
+ tokens._text = "## Chapter\n"
+ tokens.setChapterFormat(f"Chapter {nwHeadFmt.CH_ROMU}")
+ tokens.tokenizeText()
+ tokens.doHeaders()
+ assert tokens._tokens == [
(Tokenizer.T_HEAD2, 1, "Chapter II", None, Tokenizer.A_PBB),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
]
# H2: Chapter Roman Number Lower Case
- theToken._text = "## Chapter\n"
- theToken.setChapterFormat(f"Chapter {nwHeadFmt.CH_ROML}")
- theToken.tokenizeText()
- theToken.doHeaders()
- assert theToken._tokens == [
+ tokens._text = "## Chapter\n"
+ tokens.setChapterFormat(f"Chapter {nwHeadFmt.CH_ROML}")
+ tokens.tokenizeText()
+ tokens.doHeaders()
+ assert tokens._tokens == [
(Tokenizer.T_HEAD2, 1, "Chapter iii", None, Tokenizer.A_PBB),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
]
@@ -978,89 +1077,89 @@ def testCoreToken_ProcessHeaders(mockGUI):
# ======
# H3: Scene w/Title
- theToken._text = "### Scene One\n"
- theToken.setSceneFormat(f"S: {nwHeadFmt.TITLE}", False)
- theToken.tokenizeText()
- theToken.doHeaders()
- assert theToken._tokens == [
+ tokens._text = "### Scene One\n"
+ tokens.setSceneFormat(f"S: {nwHeadFmt.TITLE}", False)
+ tokens.tokenizeText()
+ tokens.doHeaders()
+ assert tokens._tokens == [
(Tokenizer.T_HEAD3, 1, "S: Scene One", None, Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
]
# H3: Scene Hidden wo/Format
- theToken._text = "### Scene One\n"
- theToken.setSceneFormat("", True)
- theToken.tokenizeText()
- theToken.doHeaders()
- assert theToken._tokens == [
+ tokens._text = "### Scene One\n"
+ tokens.setSceneFormat("", True)
+ tokens.tokenizeText()
+ tokens.doHeaders()
+ assert tokens._tokens == [
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
]
# H3: Scene wo/Format, first
- theToken._text = "### Scene One\n"
- theToken.setSceneFormat("", False)
- theToken._firstScene = True
- theToken.tokenizeText()
- theToken.doHeaders()
- assert theToken._tokens == [
+ tokens._text = "### Scene One\n"
+ tokens.setSceneFormat("", False)
+ tokens._firstScene = True
+ tokens.tokenizeText()
+ tokens.doHeaders()
+ assert tokens._tokens == [
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
]
# H3: Scene wo/Format, not first
- theToken._text = "### Scene One\n"
- theToken.setSceneFormat("", False)
- theToken._firstScene = False
- theToken.tokenizeText()
- theToken.doHeaders()
- assert theToken._tokens == [
+ tokens._text = "### Scene One\n"
+ tokens.setSceneFormat("", False)
+ tokens._firstScene = False
+ tokens.tokenizeText()
+ tokens.doHeaders()
+ assert tokens._tokens == [
(Tokenizer.T_SKIP, 1, "", None, Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
]
# H3: Scene Separator, first
- theToken._text = "### Scene One\n"
- theToken.setSceneFormat("* * *", False)
- theToken._firstScene = True
- theToken.tokenizeText()
- theToken.doHeaders()
- assert theToken._tokens == [
+ tokens._text = "### Scene One\n"
+ tokens.setSceneFormat("* * *", False)
+ tokens._firstScene = True
+ tokens.tokenizeText()
+ tokens.doHeaders()
+ assert tokens._tokens == [
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
]
# H3: Scene Separator, not first
- theToken._text = "### Scene One\n"
- theToken.setSceneFormat("* * *", False)
- theToken._firstScene = False
- theToken.tokenizeText()
- theToken.doHeaders()
- assert theToken._tokens == [
+ tokens._text = "### Scene One\n"
+ tokens.setSceneFormat("* * *", False)
+ tokens._firstScene = False
+ tokens.tokenizeText()
+ tokens.doHeaders()
+ assert tokens._tokens == [
(Tokenizer.T_SEP, 1, "* * *", None, Tokenizer.A_CENTRE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
]
# H3: Scene w/Absolute Number
- theToken._text = "### A Scene\n"
- theToken.setSceneFormat(f"Scene {nwHeadFmt.SC_ABS}", False)
- theToken._hFormatter._scAbsCount = 0
- theToken._hFormatter._scChCount = 0
- theToken.tokenizeText()
- theToken.doHeaders()
- assert theToken._tokens == [
+ tokens._text = "### A Scene\n"
+ tokens.setSceneFormat(f"Scene {nwHeadFmt.SC_ABS}", False)
+ tokens._hFormatter._scAbsCount = 0
+ tokens._hFormatter._scChCount = 0
+ tokens.tokenizeText()
+ tokens.doHeaders()
+ assert tokens._tokens == [
(Tokenizer.T_HEAD3, 1, "Scene 1", None, Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
]
# H3: Scene w/Chapter Number
- theToken._text = "### A Scene\n"
- theToken.setSceneFormat(f"Scene {nwHeadFmt.CH_NUM}.{nwHeadFmt.SC_NUM}", False)
- theToken._hFormatter._scAbsCount = 0
- theToken._hFormatter._scChCount = 1
- theToken.tokenizeText()
- theToken.doHeaders()
- assert theToken._tokens == [
+ tokens._text = "### A Scene\n"
+ tokens.setSceneFormat(f"Scene {nwHeadFmt.CH_NUM}.{nwHeadFmt.SC_NUM}", False)
+ tokens._hFormatter._scAbsCount = 0
+ tokens._hFormatter._scChCount = 1
+ tokens.tokenizeText()
+ tokens.doHeaders()
+ assert tokens._tokens == [
(Tokenizer.T_HEAD3, 1, "Scene 3.2", None, Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
]
@@ -1069,51 +1168,51 @@ def testCoreToken_ProcessHeaders(mockGUI):
# ========
# H4: Section Hidden wo/Format
- theToken._text = "#### A Section\n"
- theToken.setSectionFormat(r"", True)
- theToken.tokenizeText()
- theToken.doHeaders()
- assert theToken._tokens == [
+ tokens._text = "#### A Section\n"
+ tokens.setSectionFormat(r"", True)
+ tokens.tokenizeText()
+ tokens.doHeaders()
+ assert tokens._tokens == [
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
]
# H4: Section Visible wo/Format
- theToken._text = "#### A Section\n"
- theToken.setSectionFormat("", False)
- theToken.tokenizeText()
- theToken.doHeaders()
- assert theToken._tokens == [
+ tokens._text = "#### A Section\n"
+ tokens.setSectionFormat("", False)
+ tokens.tokenizeText()
+ tokens.doHeaders()
+ assert tokens._tokens == [
(Tokenizer.T_SKIP, 1, "", None, Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
]
# H4: Section w/Format
- theToken._text = "#### A Section\n"
- theToken.setSectionFormat(f"X: {nwHeadFmt.TITLE}", False)
- theToken.tokenizeText()
- theToken.doHeaders()
- assert theToken._tokens == [
+ tokens._text = "#### A Section\n"
+ tokens.setSectionFormat(f"X: {nwHeadFmt.TITLE}", False)
+ tokens.tokenizeText()
+ tokens.doHeaders()
+ assert tokens._tokens == [
(Tokenizer.T_HEAD4, 1, "X: A Section", None, Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
]
# H4: Section Separator
- theToken._text = "#### A Section\n"
- theToken.setSectionFormat("* * *", False)
- theToken.tokenizeText()
- theToken.doHeaders()
- assert theToken._tokens == [
+ tokens._text = "#### A Section\n"
+ tokens.setSectionFormat("* * *", False)
+ tokens.tokenizeText()
+ tokens.doHeaders()
+ assert tokens._tokens == [
(Tokenizer.T_SEP, 1, "* * *", None, Tokenizer.A_CENTRE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
]
# Check the first scene detector
- assert theToken._firstScene is False
- theToken._firstScene = True
- theToken._text = "Some text ...\n"
- theToken.tokenizeText()
- theToken.doHeaders()
- assert theToken._firstScene is False
+ assert tokens._firstScene is False
+ tokens._firstScene = True
+ tokens._text = "Some text ...\n"
+ tokens.tokenizeText()
+ tokens.doHeaders()
+ assert tokens._firstScene is False
# END Test testCoreToken_ProcessHeaders
diff --git a/tests/test_core/test_core_tomd.py b/tests/test_core/test_core_tomd.py
index 06658a85..204f1ac1 100644
--- a/tests/test_core/test_core_tomd.py
+++ b/tests/test_core/test_core_tomd.py
@@ -81,8 +81,8 @@ def testCoreToMarkdown_ConvertFormat(mockGUI):
# Paragraphs
# ==========
- # Text for GitHub Markdown
- theMD.setGitHubMarkdown()
+ # Text for Extended Markdown
+ theMD.setExtendedMarkdown()
theMD._text = "Some **nested bold and _italic_ and ~~strikethrough~~ text** here\n"
theMD.tokenizeText()
theMD.doConvert()
diff --git a/tests/test_core/test_core_toodt.py b/tests/test_core/test_core_toodt.py
index 19962185..24be4883 100644
--- a/tests/test_core/test_core_toodt.py
+++ b/tests/test_core/test_core_toodt.py
@@ -53,53 +53,53 @@ def xmlToText(xElem):
@pytest.mark.core
def testCoreToOdt_Init(mockGUI):
"""Test initialisation of the ODT document."""
- theProject = NWProject()
+ project = NWProject()
# Flat Doc
# ========
- theDoc = ToOdt(theProject, isFlat=True)
- theDoc.initDocument()
+ odt = ToOdt(project, isFlat=True)
+ odt.initDocument()
# Document XML
- assert theDoc._dFlat.tag == _mkTag("office", "document")
- assert theDoc._dCont.tag == ""
- assert theDoc._dMeta.tag == ""
- assert theDoc._dStyl.tag == ""
+ assert odt._dFlat.tag == _mkTag("office", "document")
+ assert odt._dCont.tag == ""
+ assert odt._dMeta.tag == ""
+ assert odt._dStyl.tag == ""
# Content XML
- assert theDoc._xMeta.tag == _mkTag("office", "meta")
- assert theDoc._xFont.tag == _mkTag("office", "font-face-decls")
- assert theDoc._xFnt2.tag == ""
- assert theDoc._xStyl.tag == _mkTag("office", "styles")
- assert theDoc._xAuto.tag == _mkTag("office", "automatic-styles")
- assert theDoc._xAut2.tag == ""
- assert theDoc._xMast.tag == _mkTag("office", "master-styles")
- assert theDoc._xBody.tag == _mkTag("office", "body")
- assert theDoc._xText.tag == _mkTag("office", "text")
+ assert odt._xMeta.tag == _mkTag("office", "meta")
+ assert odt._xFont.tag == _mkTag("office", "font-face-decls")
+ assert odt._xFnt2.tag == ""
+ assert odt._xStyl.tag == _mkTag("office", "styles")
+ assert odt._xAuto.tag == _mkTag("office", "automatic-styles")
+ assert odt._xAut2.tag == ""
+ assert odt._xMast.tag == _mkTag("office", "master-styles")
+ assert odt._xBody.tag == _mkTag("office", "body")
+ assert odt._xText.tag == _mkTag("office", "text")
# ODT Doc
# =======
- theDoc = ToOdt(theProject, isFlat=False)
- theDoc.initDocument()
+ odt = ToOdt(project, isFlat=False)
+ odt.initDocument()
# Document XML
- assert theDoc._dFlat.tag == ""
- assert theDoc._dCont.tag == _mkTag("office", "document-content")
- assert theDoc._dMeta.tag == _mkTag("office", "document-meta")
- assert theDoc._dStyl.tag == _mkTag("office", "document-styles")
+ assert odt._dFlat.tag == ""
+ assert odt._dCont.tag == _mkTag("office", "document-content")
+ assert odt._dMeta.tag == _mkTag("office", "document-meta")
+ assert odt._dStyl.tag == _mkTag("office", "document-styles")
# Content XML
- assert theDoc._xMeta.tag == _mkTag("office", "meta")
- assert theDoc._xFont.tag == _mkTag("office", "font-face-decls")
- assert theDoc._xFnt2.tag == _mkTag("office", "font-face-decls")
- assert theDoc._xStyl.tag == _mkTag("office", "styles")
- assert theDoc._xAuto.tag == _mkTag("office", "automatic-styles")
- assert theDoc._xAut2.tag == _mkTag("office", "automatic-styles")
- assert theDoc._xMast.tag == _mkTag("office", "master-styles")
- assert theDoc._xBody.tag == _mkTag("office", "body")
- assert theDoc._xText.tag == _mkTag("office", "text")
+ assert odt._xMeta.tag == _mkTag("office", "meta")
+ assert odt._xFont.tag == _mkTag("office", "font-face-decls")
+ assert odt._xFnt2.tag == _mkTag("office", "font-face-decls")
+ assert odt._xStyl.tag == _mkTag("office", "styles")
+ assert odt._xAuto.tag == _mkTag("office", "automatic-styles")
+ assert odt._xAut2.tag == _mkTag("office", "automatic-styles")
+ assert odt._xMast.tag == _mkTag("office", "master-styles")
+ assert odt._xBody.tag == _mkTag("office", "body")
+ assert odt._xText.tag == _mkTag("office", "text")
# END Test testCoreToOdt_Init
@@ -108,147 +108,142 @@ def testCoreToOdt_Init(mockGUI):
@pytest.mark.core
def testCoreToOdt_TextFormatting(mockGUI):
"""Test formatting of paragraphs."""
- theProject = NWProject()
- theDoc = ToOdt(theProject, isFlat=True)
+ project = NWProject()
+ odt = ToOdt(project, isFlat=True)
- theDoc.initDocument()
- assert xmlToText(theDoc._xText) == ""
+ odt.initDocument()
+ assert xmlToText(odt._xText) == ""
# Paragraph Style
# ===============
oStyle = ODTParagraphStyle()
- assert theDoc._paraStyle("stuff", oStyle) == "Standard"
- assert theDoc._paraStyle("Text_20_body", oStyle) == "Text_20_body"
+ assert odt._paraStyle("stuff", oStyle) == "Standard"
+ assert odt._paraStyle("Text_20_body", oStyle) == "Text_20_body"
# Create new para style
oStyle.setTextAlign("center")
- assert theDoc._paraStyle("Text_20_body", oStyle) == "P1"
+ assert odt._paraStyle("Text_20_body", oStyle) == "P1"
# Return the same style on second call
- assert theDoc._paraStyle("Text_20_body", oStyle) == "P1"
+ assert odt._paraStyle("Text_20_body", oStyle) == "P1"
- assert list(theDoc._mainPara.keys()) == [
+ assert list(odt._mainPara.keys()) == [
"Text_20_body", "Text_20_Meta", "Title", "Separator",
"Heading_20_1", "Heading_20_2", "Heading_20_3", "Heading_20_4", "Header",
]
theKey = "071d6b2e4764749f8c78d3c1ab9099fa04c07d2d53fd3de61eb1bdf1cb4845c3"
- assert theDoc._autoPara[theKey][0] == "P1"
- assert isinstance(theDoc._autoPara[theKey][1], ODTParagraphStyle)
+ assert odt._autoPara[theKey][0] == "P1"
+ assert isinstance(odt._autoPara[theKey][1], ODTParagraphStyle)
# Paragraph Formatting
# ====================
oStyle = ODTParagraphStyle()
# No Text
- theDoc.initDocument()
- theDoc._addTextPar("Standard", oStyle, "")
- assert xmlToText(theDoc._xText) == (
+ odt.initDocument()
+ odt._addTextPar("Standard", oStyle, "")
+ assert xmlToText(odt._xText) == (
''
''
''
)
# No Format
- theDoc.initDocument()
- theDoc._addTextPar("Standard", oStyle, "Hello World")
- assert theDoc.errData == []
- assert xmlToText(theDoc._xText) == (
+ odt.initDocument()
+ odt._addTextPar("Standard", oStyle, "Hello World")
+ assert odt.errData == []
+ assert xmlToText(odt._xText) == (
''
'Hello World'
''
)
# Heading Level None
- theDoc.initDocument()
- theDoc._addTextPar("Standard", oStyle, "Hello World", isHead=True)
- assert theDoc.errData == []
- assert xmlToText(theDoc._xText) == (
+ odt.initDocument()
+ odt._addTextPar("Standard", oStyle, "Hello World", isHead=True)
+ assert odt.errData == []
+ assert xmlToText(odt._xText) == (
''
'Hello World'
''
)
# Heading Level 1
- theDoc.initDocument()
- theDoc._addTextPar("Standard", oStyle, "Hello World", isHead=True, oLevel="1")
- assert theDoc.errData == []
- assert xmlToText(theDoc._xText) == (
+ odt.initDocument()
+ odt._addTextPar("Standard", oStyle, "Hello World", isHead=True, oLevel="1")
+ assert odt.errData == []
+ assert xmlToText(odt._xText) == (
''
'Hello World'
''
)
# Formatted Text
- theDoc.initDocument()
- theTxt = "A **few** _words_ from ~~our~~ sponsor"
- theFmt = " _B b_ I i _S s_ "
- theDoc._addTextPar("Standard", oStyle, theTxt, tFmt=theFmt)
- assert theDoc.errData == []
- assert xmlToText(theDoc._xText) == (
+ odt.initDocument()
+ text = "A bold word"
+ fmt = [(2, odt.FMT_B_B), (6, odt.FMT_B_E)]
+ odt._addTextPar("Standard", oStyle, text, tFmt=fmt)
+ assert odt.errData == []
+ assert xmlToText(odt._xText) == (
''
- 'A few '
- 'words from '
- 'our sponsor'
+ 'A bold '
+ 'word'
''
)
# Incorrectly Formatted Text
- theDoc.initDocument()
- theTxt = "A **few** _wordsXXX"
- theFmt = " _b b_ I XXX"
- theDoc._addTextPar("Standard", oStyle, theTxt, tFmt=theFmt)
- assert theDoc.errData == ["Unknown format tag encountered"]
- assert xmlToText(theDoc._xText) == (
+ odt.initDocument()
+ text = "A few words"
+ fmt = [(2, odt.FMT_B_B), (5, odt.FMT_B_E), (7, 99999)]
+ odt._addTextPar("Standard", oStyle, text, tFmt=fmt)
+ assert odt.errData == ["Unknown format tag encountered"]
+ assert xmlToText(odt._xText) == (
''
- ''
- 'A few words'
- ''
+ 'A few '
+ 'words'
''
)
- # Formatted Text
- theDoc.initDocument()
- theTxt = "Hello\n\tWorld"
- theFmt = " "
- theDoc._addTextPar("Standard", oStyle, theTxt, tFmt=theFmt)
- assert theDoc.errData == []
- assert xmlToText(theDoc._xText) == (
+ # Unclosed format
+ odt.initDocument()
+ text = "A bold word"
+ fmt = [(2, odt.FMT_B_B)]
+ odt._addTextPar("Standard", oStyle, text, tFmt=fmt)
+ assert odt.errData == []
+ assert xmlToText(odt._xText) == (
+ ''
+ 'A '
+ 'bold word'
+ ''
+ )
+
+ # Tabs and Breaks
+ odt.initDocument()
+ text = "Hello\n\tWorld"
+ fmt = []
+ odt._addTextPar("Standard", oStyle, text, tFmt=fmt)
+ assert odt.errData == []
+ assert xmlToText(odt._xText) == (
''
'HelloWorld'
''
)
- # Test for issue #1412
- # ====================
- # See: https://github.com/vkbo/novelWriter/issues/1412
-
- theDoc.initDocument()
- theTxt = "Test text \\**_bold_** and more."
- theFmt = " I i "
- theDoc._addTextPar("Standard", oStyle, theTxt, tFmt=theFmt)
- assert theDoc.errData == []
- assert xmlToText(theDoc._xText) == (
- ''
- 'Test text **'
- 'bold** and more.'
- ''
- )
-
# END Test testCoreToOdt_TextFormatting
@pytest.mark.core
def testCoreToOdt_Convert(mockGUI):
"""Test the converter of the ToOdt class."""
- theProject = NWProject()
- theDoc = ToOdt(theProject, isFlat=True)
+ project = NWProject()
+ odt = ToOdt(project, isFlat=True)
- theDoc._isNovel = True
+ odt._isNovel = True
def getStyle(styleName):
- for aSet in theDoc._autoPara.values():
+ for aSet in odt._autoPara.values():
if aSet[0] == styleName:
return aSet[1]
return None
@@ -257,78 +252,78 @@ def testCoreToOdt_Convert(mockGUI):
# =======
# Header 1
- theDoc._text = "# Title\n"
- theDoc.tokenizeText()
- theDoc.initDocument()
- theDoc.doConvert()
- theDoc.closeDocument()
- assert theDoc.errData == []
- assert xmlToText(theDoc._xText) == (
+ odt._text = "# Title\n"
+ odt.tokenizeText()
+ odt.initDocument()
+ odt.doConvert()
+ odt.closeDocument()
+ assert odt.errData == []
+ assert xmlToText(odt._xText) == (
''
'Title'
''
)
# Header 2
- theDoc._text = "## Chapter\n"
- theDoc.tokenizeText()
- theDoc.initDocument()
- theDoc.doConvert()
- theDoc.closeDocument()
- assert theDoc.errData == []
- assert xmlToText(theDoc._xText) == (
+ odt._text = "## Chapter\n"
+ odt.tokenizeText()
+ odt.initDocument()
+ odt.doConvert()
+ odt.closeDocument()
+ assert odt.errData == []
+ assert xmlToText(odt._xText) == (
''
'Chapter'
''
)
# Header 3
- theDoc._text = "### Scene\n"
- theDoc.tokenizeText()
- theDoc.initDocument()
- theDoc.doConvert()
- theDoc.closeDocument()
- assert theDoc.errData == []
- assert xmlToText(theDoc._xText) == (
+ odt._text = "### Scene\n"
+ odt.tokenizeText()
+ odt.initDocument()
+ odt.doConvert()
+ odt.closeDocument()
+ assert odt.errData == []
+ assert xmlToText(odt._xText) == (
''
'Scene'
''
)
# Header 4
- theDoc._text = "#### Section\n"
- theDoc.tokenizeText()
- theDoc.initDocument()
- theDoc.doConvert()
- theDoc.closeDocument()
- assert theDoc.errData == []
- assert xmlToText(theDoc._xText) == (
+ odt._text = "#### Section\n"
+ odt.tokenizeText()
+ odt.initDocument()
+ odt.doConvert()
+ odt.closeDocument()
+ assert odt.errData == []
+ assert xmlToText(odt._xText) == (
''
'Section'
''
)
# Title
- theDoc._text = "#! Title\n"
- theDoc.tokenizeText()
- theDoc.initDocument()
- theDoc.doConvert()
- theDoc.closeDocument()
- assert theDoc.errData == []
- assert xmlToText(theDoc._xText) == (
+ odt._text = "#! Title\n"
+ odt.tokenizeText()
+ odt.initDocument()
+ odt.doConvert()
+ odt.closeDocument()
+ assert odt.errData == []
+ assert xmlToText(odt._xText) == (
''
'Title'
''
)
# Unnumbered chapter
- theDoc._text = "##! Prologue\n"
- theDoc.tokenizeText()
- theDoc.initDocument()
- theDoc.doConvert()
- theDoc.closeDocument()
- assert theDoc.errData == []
- assert xmlToText(theDoc._xText) == (
+ odt._text = "##! Prologue\n"
+ odt.tokenizeText()
+ odt.initDocument()
+ odt.doConvert()
+ odt.closeDocument()
+ assert odt.errData == []
+ assert xmlToText(odt._xText) == (
''
'Prologue'
''
@@ -337,14 +332,14 @@ def testCoreToOdt_Convert(mockGUI):
# Paragraphs
# ==========
- # Nested Text
- theDoc._text = "Some ~~nested **bold** and _italics_ text~~ text."
- theDoc.tokenizeText()
- theDoc.initDocument()
- theDoc.doConvert()
- theDoc.closeDocument()
- assert theDoc.errData == []
- assert xmlToText(theDoc._xText) == (
+ # Nested Markdown Text
+ odt._text = "Some ~~nested **bold** and _italics_ text~~ text."
+ odt.tokenizeText()
+ odt.initDocument()
+ odt.doConvert()
+ odt.closeDocument()
+ assert odt.errData == []
+ assert xmlToText(odt._xText) == (
''
'Some '
'nested '
@@ -355,59 +350,94 @@ def testCoreToOdt_Convert(mockGUI):
''
)
+ # Nested Shortcode Text, Emphasis
+ odt._text = "Some [s]nested [b]bold[/b] [u]and[/u] [i]italics[/i] text[/s] text."
+ odt.tokenizeText()
+ odt.initDocument()
+ odt.doConvert()
+ odt.closeDocument()
+ assert odt.errData == []
+ assert xmlToText(odt._xText) == (
+ ''
+ 'Some '
+ 'nested '
+ 'bold'
+ ' '
+ 'and'
+ ' '
+ 'italics'
+ ' text text.'
+ ''
+ )
+
+ # Nested Shortcode Text, Super/Subscript
+ odt._text = "Some super[sup]script[/sup] and sub[sub]script[/sub] text."
+ odt.tokenizeText()
+ odt.initDocument()
+ odt.doConvert()
+ odt.closeDocument()
+ assert odt.errData == []
+ assert xmlToText(odt._xText) == (
+ ''
+ 'Some '
+ 'superscript and '
+ 'subscript text.'
+ ''
+ )
+
# Hard Break
- theDoc._text = "Some text.\nNext line\n"
- theDoc.tokenizeText()
- theDoc.initDocument()
- theDoc.doConvert()
- theDoc.closeDocument()
- assert theDoc.errData == []
- assert xmlToText(theDoc._xText) == (
+ odt._text = "Some text.\nNext line\n"
+ odt.tokenizeText()
+ odt.initDocument()
+ odt.doConvert()
+ odt.closeDocument()
+ assert odt.errData == []
+ assert xmlToText(odt._xText) == (
''
'Some text.Next line'
''
)
# Tab
- theDoc._text = "\tItem 1\tItem 2\n"
- theDoc.tokenizeText()
- theDoc.initDocument()
- theDoc.doConvert()
- theDoc.closeDocument()
- assert theDoc.errData == []
- assert xmlToText(theDoc._xText) == (
+ odt._text = "\tItem 1\tItem 2\n"
+ odt.tokenizeText()
+ odt.initDocument()
+ odt.doConvert()
+ odt.closeDocument()
+ assert odt.errData == []
+ assert xmlToText(odt._xText) == (
''
'Item 1Item 2'
''
)
# Tab in Format
- theDoc._text = "Some **bold\ttext**"
- theDoc.tokenizeText()
- theDoc.initDocument()
- theDoc.doConvert()
- theDoc.closeDocument()
- assert theDoc.errData == []
- assert xmlToText(theDoc._xText) == (
+ odt._text = "Some **bold\ttext**"
+ odt.tokenizeText()
+ odt.initDocument()
+ odt.doConvert()
+ odt.closeDocument()
+ assert odt.errData == []
+ assert xmlToText(odt._xText) == (
''
- 'Some '
+ 'Some '
'boldtext'
''
)
# Multiple Spaces
- theDoc._text = (
+ odt._text = (
"### Scene\n\n"
"Hello World\n\n"
"Hello World\n\n"
"Hello World\n\n"
)
- theDoc.tokenizeText()
- theDoc.initDocument()
- theDoc.doConvert()
- theDoc.closeDocument()
- assert theDoc.errData == []
- assert xmlToText(theDoc._xText) == (
+ odt.tokenizeText()
+ odt.initDocument()
+ odt.doConvert()
+ odt.closeDocument()
+ assert odt.errData == []
+ assert xmlToText(odt._xText) == (
''
'Scene'
'Hello World'
@@ -417,42 +447,42 @@ def testCoreToOdt_Convert(mockGUI):
)
# Synopsis, Comment, Keywords
- theDoc._text = (
+ odt._text = (
"### Scene\n\n"
"@pov: Jane\n\n"
"% synopsis: So it begins\n\n"
"% a plain comment\n\n"
)
- theDoc.setSynopsis(True)
- theDoc.setComments(True)
- theDoc.setKeywords(True)
- theDoc.tokenizeText()
- theDoc.initDocument()
- theDoc.doConvert()
- theDoc.closeDocument()
- assert theDoc.errData == []
- assert xmlToText(theDoc._xText) == (
+ odt.setSynopsis(True)
+ odt.setComments(True)
+ odt.setKeywords(True)
+ odt.tokenizeText()
+ odt.initDocument()
+ odt.doConvert()
+ odt.closeDocument()
+ assert odt.errData == []
+ assert xmlToText(odt._xText) == (
''
'Scene'
- ''
+ ''
'Point of View: Jane'
- ''
+ ''
'Synopsis: So it begins'
- ''
+ ''
'Comment: a plain comment'
''
)
# Scene Separator
- theDoc._text = "### Scene One\n\nText\n\n### Scene Two\n\nText"
- theDoc.setSceneFormat("* * *", False)
- theDoc.tokenizeText()
- theDoc.doHeaders()
- theDoc.initDocument()
- theDoc.doConvert()
- theDoc.closeDocument()
- assert theDoc.errData == []
- assert xmlToText(theDoc._xText) == (
+ odt._text = "### Scene One\n\nText\n\n### Scene Two\n\nText"
+ odt.setSceneFormat("* * *", False)
+ odt.tokenizeText()
+ odt.doHeaders()
+ odt.initDocument()
+ odt.doConvert()
+ odt.closeDocument()
+ assert odt.errData == []
+ assert xmlToText(odt._xText) == (
''
'* * *'
'Text'
@@ -462,15 +492,15 @@ def testCoreToOdt_Convert(mockGUI):
)
# Scene Break
- theDoc._text = "### Scene One\n\nText\n\n### Scene Two\n\nText"
- theDoc.setSceneFormat("", False)
- theDoc.tokenizeText()
- theDoc.doHeaders()
- theDoc.initDocument()
- theDoc.doConvert()
- theDoc.closeDocument()
- assert theDoc.errData == []
- assert xmlToText(theDoc._xText) == (
+ odt._text = "### Scene One\n\nText\n\n### Scene Two\n\nText"
+ odt.setSceneFormat("", False)
+ odt.tokenizeText()
+ odt.doHeaders()
+ odt.initDocument()
+ odt.doConvert()
+ odt.closeDocument()
+ assert odt.errData == []
+ assert xmlToText(odt._xText) == (
''
''
'Text'
@@ -480,7 +510,7 @@ def testCoreToOdt_Convert(mockGUI):
)
# Paragraph Styles
- theDoc._text = (
+ odt._text = (
"### Scene\n\n"
"@pov: Jane\n"
"@char: John\n"
@@ -491,20 +521,20 @@ def testCoreToOdt_Convert(mockGUI):
"> Left indent\n\n"
"Right indent <\n\n"
)
- theDoc.setKeywords(True)
- theDoc.tokenizeText()
- theDoc.initDocument()
- theDoc.doConvert()
- theDoc.closeDocument()
- assert theDoc.errData == []
- assert xmlToText(theDoc._xText) == (
+ odt.setKeywords(True)
+ odt.tokenizeText()
+ odt.initDocument()
+ odt.doConvert()
+ odt.closeDocument()
+ assert odt.errData == []
+ assert xmlToText(odt._xText) == (
''
'Scene'
- ''
+ ''
'Point of View: Jane'
- ''
+ ''
'Characters: John'
- ''
+ ''
'Plot: Main'
'Right align'
'Left Align'
@@ -513,28 +543,28 @@ def testCoreToOdt_Convert(mockGUI):
'Right indent'
''
)
- assert getStyle("P3")._pAttr["margin-bottom"] == ["fo", "0.000cm"]
- assert getStyle("P4")._pAttr["margin-bottom"] == ["fo", "0.000cm"]
- assert getStyle("P4")._pAttr["margin-top"] == ["fo", "0.000cm"]
- assert getStyle("P5")._pAttr["text-align"] == ["fo", "right"]
- assert getStyle("P6")._pAttr["text-align"] == ["fo", "center"]
- assert getStyle("P7")._pAttr["margin-left"] == ["fo", "1.693cm"]
- assert getStyle("P8")._pAttr["margin-right"] == ["fo", "1.693cm"]
+ assert getStyle("P3")._pAttr["margin-bottom"] == ["fo", "0.000cm"] # type: ignore
+ assert getStyle("P4")._pAttr["margin-bottom"] == ["fo", "0.000cm"] # type: ignore
+ assert getStyle("P4")._pAttr["margin-top"] == ["fo", "0.000cm"] # type: ignore
+ assert getStyle("P5")._pAttr["text-align"] == ["fo", "right"] # type: ignore
+ assert getStyle("P6")._pAttr["text-align"] == ["fo", "center"] # type: ignore
+ assert getStyle("P7")._pAttr["margin-left"] == ["fo", "1.693cm"] # type: ignore
+ assert getStyle("P8")._pAttr["margin-right"] == ["fo", "1.693cm"] # type: ignore
# Justified
- theDoc._text = (
+ odt._text = (
"### Scene\n\n"
"Regular paragraph\n\n"
"with\nbreak\n\n"
"Left Align <<\n\n"
)
- theDoc.setJustify(True)
- theDoc.tokenizeText()
- theDoc.initDocument()
- theDoc.doConvert()
- theDoc.closeDocument()
- assert theDoc.errData == []
- assert xmlToText(theDoc._xText) == (
+ odt.setJustify(True)
+ odt.tokenizeText()
+ odt.initDocument()
+ odt.doConvert()
+ odt.closeDocument()
+ assert odt.errData == []
+ assert xmlToText(odt._xText) == (
''
'Scene'
'Regular paragraph'
@@ -542,21 +572,21 @@ def testCoreToOdt_Convert(mockGUI):
'Left Align'
''
)
- assert getStyle("P9")._pAttr["text-align"] == ["fo", "left"]
+ assert getStyle("P9")._pAttr["text-align"] == ["fo", "left"] # type: ignore
# Page Breaks
- theDoc._text = (
+ odt._text = (
"## Chapter One\n\n"
"Text\n\n"
"## Chapter Two\n\n"
"Text\n\n"
)
- theDoc.tokenizeText()
- theDoc.initDocument()
- theDoc.doConvert()
- theDoc.closeDocument()
- assert theDoc.errData == []
- assert xmlToText(theDoc._xText) == (
+ odt.tokenizeText()
+ odt.initDocument()
+ odt.doConvert()
+ odt.closeDocument()
+ assert odt.errData == []
+ assert xmlToText(odt._xText) == (
''
'Chapter One'
'Text'
@@ -565,6 +595,23 @@ def testCoreToOdt_Convert(mockGUI):
''
)
+ # Test for issue #1412
+ # ====================
+ # See: https://github.com/vkbo/novelWriter/issues/1412
+
+ odt._text = "Test text \\**_bold_** and more."
+ odt.tokenizeText()
+ odt.initDocument()
+ odt.doConvert()
+ odt.closeDocument()
+ assert odt.errData == []
+ assert xmlToText(odt._xText) == (
+ ''
+ 'Test text **'
+ 'bold** and more.'
+ ''
+ )
+
# END Test testCoreToOdt_Convert
@@ -626,37 +673,45 @@ def testCoreToOdt_ConvertDirect(mockGUI):
@pytest.mark.core
def testCoreToOdt_SaveFlat(mockGUI, fncPath, tstPaths):
"""Test the document save functions."""
- theProject = NWProject()
- theProject.data.setAuthor("Jane Smith")
- theProject.data.setName("Test Project")
- theProject.data.setSaveCount(1234)
- theProject.data.setEditTime(3674096)
+ project = NWProject()
+ project.data.setAuthor("Jane Smith")
+ project.data.setName("Test Project")
+ project.data.setSaveCount(1234)
+ project.data.setEditTime(3674096)
- theDoc = ToOdt(theProject, isFlat=True)
- theDoc._isNovel = True
- theDoc._dLanguage = ""
- theDoc.setLanguage(None)
- assert theDoc._dLanguage == ""
- theDoc.setLanguage("nb_NO")
- assert theDoc._dLanguage == "nb"
- theDoc.setColourHeaders(True)
+ odt = ToOdt(project, isFlat=True)
+ odt._isNovel = True
+ odt._dLanguage = ""
+ odt.setLanguage(None) # type: ignore
+ assert odt._dLanguage == ""
+ odt.setLanguage("nb_NO")
+ assert odt._dLanguage == "nb"
+ odt.setColourHeaders(True)
- theDoc._text = (
+ odt.setPageLayout(148, 210, 20, 18, 17, 15)
+ assert odt._mDocWidth == "14.800cm"
+ assert odt._mDocHeight == "21.000cm"
+ assert odt._mDocTop == "2.000cm"
+ assert odt._mDocBtm == "1.800cm"
+ assert odt._mDocLeft == "1.700cm"
+ assert odt._mDocRight == "1.500cm"
+
+ odt._text = (
"## Chapter One\n\n"
"Text\n\n"
"## Chapter Two\n\n"
"Text\n\n"
)
- theDoc.tokenizeText()
- theDoc.initDocument()
- theDoc.doConvert()
- theDoc.closeDocument()
+ odt.tokenizeText()
+ odt.initDocument()
+ odt.doConvert()
+ odt.closeDocument()
flatFile = fncPath / "document.fodt"
testFile = tstPaths.outDir / "coreToOdt_SaveFlat_document.fodt"
compFile = tstPaths.refDir / "coreToOdt_SaveFlat_document.fodt"
- theDoc.saveFlatXML(flatFile)
+ odt.saveFlatXML(flatFile)
assert flatFile.exists()
copyfile(flatFile, testFile)
@@ -668,29 +723,29 @@ def testCoreToOdt_SaveFlat(mockGUI, fncPath, tstPaths):
@pytest.mark.core
def testCoreToOdt_SaveFull(mockGUI, fncPath, tstPaths):
"""Test the document save functions."""
- theProject = NWProject()
- theProject.data.setAuthor("Jane Smith")
- theProject.data.setName("Test Project")
- theProject.data.setSaveCount(1234)
- theProject.data.setEditTime(3674096)
+ project = NWProject()
+ project.data.setAuthor("Jane Smith")
+ project.data.setName("Test Project")
+ project.data.setSaveCount(1234)
+ project.data.setEditTime(3674096)
- theDoc = ToOdt(theProject, isFlat=False)
- theDoc._isNovel = True
+ odt = ToOdt(project, isFlat=False)
+ odt._isNovel = True
- theDoc._text = (
+ odt._text = (
"## Chapter One\n\n"
"Text\n\n"
"## Chapter Two\n\n"
"Text\n\n"
)
- theDoc.tokenizeText()
- theDoc.initDocument()
- theDoc.doConvert()
- theDoc.closeDocument()
+ odt.tokenizeText()
+ odt.initDocument()
+ odt.doConvert()
+ odt.closeDocument()
fullFile = fncPath / "document.odt"
- theDoc.saveOpenDocText(fullFile)
+ odt.saveOpenDocText(fullFile)
assert fullFile.exists()
assert zipfile.is_zipfile(fullFile)
@@ -745,26 +800,22 @@ def testCoreToOdt_SaveFull(mockGUI, fncPath, tstPaths):
@pytest.mark.core
def testCoreToOdt_Format(mockGUI):
"""Test the formatters for the ToOdt class."""
- theProject = NWProject()
- theDoc = ToOdt(theProject, isFlat=True)
+ project = NWProject()
+ odt = ToOdt(project, isFlat=True)
- assert theDoc._formatSynopsis("synopsis text") == (
- "**Synopsis:** synopsis text",
- "_B b_ "
+ assert odt._formatSynopsis("synopsis text") == (
+ "Synopsis: synopsis text", [(0, ToOdt.FMT_B_B), (9, ToOdt.FMT_B_E)]
)
- assert theDoc._formatComments("comment text") == (
- "**Comment:** comment text",
- "_B b_ "
+ assert odt._formatComments("comment text") == (
+ "Comment: comment text", [(0, ToOdt.FMT_B_B), (8, ToOdt.FMT_B_E)]
)
- assert theDoc._formatKeywords("") == ("", "")
- assert theDoc._formatKeywords("tag: Jane") == (
- "**Tag:** Jane",
- "_B b_ "
+ assert odt._formatKeywords("") == ("", [])
+ assert odt._formatKeywords("tag: Jane") == (
+ "Tag: Jane", [(0, ToOdt.FMT_B_B), (4, ToOdt.FMT_B_E)]
)
- assert theDoc._formatKeywords("char: Bod, Jane") == (
- "**Characters:** Bod, Jane",
- "_B b_ "
+ assert odt._formatKeywords("char: Bod, Jane") == (
+ "Characters: Bod, Jane", [(0, ToOdt.FMT_B_B), (11, ToOdt.FMT_B_E)]
)
# END Test testCoreToOdt_Format
@@ -1016,6 +1067,17 @@ def testCoreToOdt_ODTTextStyle():
txtStyle.setFontStyle("stuff")
assert txtStyle._tAttr["font-style"] == ["fo", None]
+ # Text Position
+ assert txtStyle._tAttr["text-position"] == ["style", None]
+ txtStyle.setTextPosition("stuff")
+ assert txtStyle._tAttr["text-position"] == ["style", None]
+ txtStyle.setTextPosition("super")
+ assert txtStyle._tAttr["text-position"] == ["style", "super 58%"]
+ txtStyle.setTextPosition("sub")
+ assert txtStyle._tAttr["text-position"] == ["style", "sub 58%"]
+ txtStyle.setTextPosition("stuff")
+ assert txtStyle._tAttr["text-position"] == ["style", None]
+
# Line Through Style
assert txtStyle._tAttr["text-line-through-style"] == ["style", None]
txtStyle.setStrikeStyle("stuff")
@@ -1031,8 +1093,8 @@ def testCoreToOdt_ODTTextStyle():
assert txtStyle._tAttr["text-line-through-type"] == ["style", None]
txtStyle.setStrikeType("stuff")
assert txtStyle._tAttr["text-line-through-type"] == ["style", None]
- txtStyle.setStrikeType("none")
- assert txtStyle._tAttr["text-line-through-type"] == ["style", "none"]
+ txtStyle.setStrikeType("none") # Deprecated in ODF 1.3
+ assert txtStyle._tAttr["text-line-through-type"] == ["style", None]
txtStyle.setStrikeType("single")
assert txtStyle._tAttr["text-line-through-type"] == ["style", "single"]
txtStyle.setStrikeType("double")
@@ -1040,18 +1102,49 @@ def testCoreToOdt_ODTTextStyle():
txtStyle.setStrikeType("stuff")
assert txtStyle._tAttr["text-line-through-type"] == ["style", None]
+ # Underline Style
+ assert txtStyle._tAttr["text-underline-style"] == ["style", None]
+ txtStyle.setUnderlineStyle("stuff")
+ assert txtStyle._tAttr["text-underline-style"] == ["style", None]
+ txtStyle.setUnderlineStyle("none")
+ assert txtStyle._tAttr["text-underline-style"] == ["style", "none"]
+ txtStyle.setUnderlineStyle("solid")
+ assert txtStyle._tAttr["text-underline-style"] == ["style", "solid"]
+ txtStyle.setUnderlineStyle("stuff")
+ assert txtStyle._tAttr["text-underline-style"] == ["style", None]
+
+ # Underline Width
+ assert txtStyle._tAttr["text-underline-width"] == ["style", None]
+ txtStyle.setUnderlineWidth("stuff")
+ assert txtStyle._tAttr["text-underline-width"] == ["style", None]
+ txtStyle.setUnderlineWidth("auto")
+ assert txtStyle._tAttr["text-underline-width"] == ["style", "auto"]
+ txtStyle.setUnderlineWidth("stuff")
+ assert txtStyle._tAttr["text-underline-width"] == ["style", None]
+
+ # Underline Colour
+ assert txtStyle._tAttr["text-underline-color"] == ["style", None]
+ txtStyle.setUnderlineColour("stuff")
+ assert txtStyle._tAttr["text-underline-color"] == ["style", None]
+ txtStyle.setUnderlineColour("font-color")
+ assert txtStyle._tAttr["text-underline-color"] == ["style", "font-color"]
+ txtStyle.setUnderlineColour("stuff")
+ assert txtStyle._tAttr["text-underline-color"] == ["style", None]
+
# Pack XML
# ========
txtStyle.setFontWeight("bold")
txtStyle.setFontStyle("italic")
+ txtStyle.setTextPosition("super")
txtStyle.setStrikeStyle("solid")
txtStyle.setStrikeType("single")
xStyle = ET.Element("test")
txtStyle.packXML(xStyle, "test")
assert xmlToText(xStyle) == (
''
+ 'fo:font-weight="bold" fo:font-style="italic" style:text-position="super 58%" '
+ 'style:text-line-through-style="solid" style:text-line-through-type="single" />'
+ ''
)
# END Test testCoreToOdt_ODTTextStyle
diff --git a/tests/test_gui/test_gui_mainmenu.py b/tests/test_gui/test_gui_mainmenu.py
index 5fe1ae49..e66e5dc6 100644
--- a/tests/test_gui/test_gui_mainmenu.py
+++ b/tests/test_gui/test_gui_mainmenu.py
@@ -600,15 +600,15 @@ def testGuiMenu_Insert(qtbot, monkeypatch, nwGUI, fncPath, projPath, mockRnd):
nwGUI.docEditor.setPlainText("### Stuff\n")
nwGUI.mainMenu.aInsNewPage.activate(QAction.Trigger)
- assert nwGUI.docEditor.getText() == "[NEW PAGE]\n### Stuff\n"
+ assert nwGUI.docEditor.getText() == "[newpage]\n### Stuff\n"
nwGUI.docEditor.setPlainText("### Stuff\n")
nwGUI.mainMenu.aInsVSpaceS.activate(QAction.Trigger)
- assert nwGUI.docEditor.getText() == "[VSPACE]\n### Stuff\n"
+ assert nwGUI.docEditor.getText() == "[vspace]\n### Stuff\n"
nwGUI.docEditor.setPlainText("### Stuff\n")
nwGUI.mainMenu.aInsVSpaceM.activate(QAction.Trigger)
- assert nwGUI.docEditor.getText() == "[VSPACE:2]\n### Stuff\n"
+ assert nwGUI.docEditor.getText() == "[vspace:2]\n### Stuff\n"
nwGUI.docEditor.clear()