Add forced line breaks feature (#2063)
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
"Short Description": "Short Description",
|
||||
"Footnotes": "Footnotes",
|
||||
"Comment": "Comment",
|
||||
"Note": "Note",
|
||||
"Notes": "Notes",
|
||||
"Tag": "Tag",
|
||||
"Point of View": "Point of View",
|
||||
"Focus": "Focus",
|
||||
|
||||
@@ -61,10 +61,11 @@ class nwConst:
|
||||
class nwRegEx:
|
||||
|
||||
WORDS = r"\b[^\s\-\+\/–—\[\]:]+\b"
|
||||
BREAK = r"(?i)(?<!\\)(\[br\]\n?)"
|
||||
FMT_EI = r"(?<![\w\\])(_)(?![\s_])(.+?)(?<![\s\\])(\1)(?!\w)"
|
||||
FMT_EB = r"(?<![\w\\])(\*{2})(?![\s\*])(.+?)(?<![\s\\])(\1)(?!\w)"
|
||||
FMT_ST = r"(?<![\w\\])(~{2})(?![\s~])(.+?)(?<![\s\\])(\1)(?!\w)"
|
||||
FMT_SC = r"(?i)(?<!\\)(\[[\/\!]?(?:b|i|s|u|m|sup|sub)\])"
|
||||
FMT_SC = r"(?i)(?<!\\)(\[(?:b|/b|i|/i|s|/s|u|/u|m|/m|sup|/sup|sub|/sub|br)\])"
|
||||
FMT_SV = r"(?i)(?<!\\)(\[(?:footnote):)(.+?)(?<!\\)(\])"
|
||||
|
||||
|
||||
@@ -84,6 +85,7 @@ class nwShortcode:
|
||||
SUP_C = "[/sup]"
|
||||
SUB_O = "[sub]"
|
||||
SUB_C = "[/sub]"
|
||||
BREAK = "[br]"
|
||||
|
||||
FOOTNOTE_B = "[footnote:"
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ SETTINGS_TEMPLATE: dict[str, tuple[type, str | int | float | bool]] = {
|
||||
"headings.centerPart": (bool, True),
|
||||
"headings.centerChapter": (bool, False),
|
||||
"headings.centerScene": (bool, False),
|
||||
"headings.breakTitle": (bool, True),
|
||||
"headings.breakTitle": (bool, False),
|
||||
"headings.breakPart": (bool, True),
|
||||
"headings.breakChapter": (bool, True),
|
||||
"headings.breakScene": (bool, False),
|
||||
|
||||
@@ -267,8 +267,8 @@ class NWBuildDocument:
|
||||
self._build.getBool("headings.hideSection")
|
||||
)
|
||||
bldObj.setTitleStyle(
|
||||
self._build.getBool("headings.centerPart"),
|
||||
self._build.getBool("headings.breakPart")
|
||||
self._build.getBool("headings.centerTitle"),
|
||||
self._build.getBool("headings.breakTitle")
|
||||
)
|
||||
bldObj.setPartitionStyle(
|
||||
self._build.getBool("headings.centerPart"),
|
||||
|
||||
@@ -137,6 +137,7 @@ class nwDocInsert(Enum):
|
||||
VSPACE_M = 9
|
||||
LIPSUM = 10
|
||||
FOOTNOTE = 11
|
||||
LINE_BRK = 12
|
||||
|
||||
|
||||
class nwView(Enum):
|
||||
|
||||
@@ -255,24 +255,19 @@ class ToDocX(Tokenizer):
|
||||
self._processFragments(par, S_NORM, tText, tFormat)
|
||||
|
||||
elif tType == BlockTyp.TITLE:
|
||||
tHead = tText.replace(nwHeadFmt.BR, "\n")
|
||||
self._processFragments(par, S_TITLE, tHead, tFormat)
|
||||
self._processFragments(par, S_TITLE, tText, tFormat)
|
||||
|
||||
elif tType == BlockTyp.HEAD1:
|
||||
tHead = tText.replace(nwHeadFmt.BR, "\n")
|
||||
self._processFragments(par, S_HEAD1, tHead, tFormat)
|
||||
self._processFragments(par, S_HEAD1, tText, tFormat)
|
||||
|
||||
elif tType == BlockTyp.HEAD2:
|
||||
tHead = tText.replace(nwHeadFmt.BR, "\n")
|
||||
self._processFragments(par, S_HEAD2, tHead, tFormat)
|
||||
self._processFragments(par, S_HEAD2, tText, tFormat)
|
||||
|
||||
elif tType == BlockTyp.HEAD3:
|
||||
tHead = tText.replace(nwHeadFmt.BR, "\n")
|
||||
self._processFragments(par, S_HEAD3, tHead, tFormat)
|
||||
self._processFragments(par, S_HEAD3, tText, tFormat)
|
||||
|
||||
elif tType == BlockTyp.HEAD4:
|
||||
tHead = tText.replace(nwHeadFmt.BR, "\n")
|
||||
self._processFragments(par, S_HEAD4, tHead, tFormat)
|
||||
self._processFragments(par, S_HEAD4, tText, tFormat)
|
||||
|
||||
elif tType == BlockTyp.SEP:
|
||||
self._processFragments(par, S_SEP, tText)
|
||||
|
||||
@@ -30,7 +30,7 @@ from pathlib import Path
|
||||
from time import time
|
||||
|
||||
from novelwriter.common import formatTimeStamp
|
||||
from novelwriter.constants import nwHeadFmt, nwHtmlUnicode
|
||||
from novelwriter.constants import nwHtmlUnicode
|
||||
from novelwriter.core.project import NWProject
|
||||
from novelwriter.formats.shared import BlockFmt, BlockTyp, T_Formats, TextFmt, stripEscape
|
||||
from novelwriter.formats.tokenizer import Tokenizer
|
||||
@@ -211,23 +211,23 @@ class ToHtml(Tokenizer):
|
||||
lines.append(f"<p{hStyle}>{self._formatText(tText, tFmt)}</p>\n")
|
||||
|
||||
elif tType == BlockTyp.TITLE:
|
||||
tHead = tText.replace(nwHeadFmt.BR, "<br>")
|
||||
tHead = tText.replace("\n", "<br>")
|
||||
lines.append(f"<h1 class='title'{hStyle}>{aNm}{tHead}</h1>\n")
|
||||
|
||||
elif tType == BlockTyp.HEAD1:
|
||||
tHead = tText.replace(nwHeadFmt.BR, "<br>")
|
||||
tHead = tText.replace("\n", "<br>")
|
||||
lines.append(f"<{h1}{h1Cl}{hStyle}>{aNm}{tHead}</{h1}>\n")
|
||||
|
||||
elif tType == BlockTyp.HEAD2:
|
||||
tHead = tText.replace(nwHeadFmt.BR, "<br>")
|
||||
tHead = tText.replace("\n", "<br>")
|
||||
lines.append(f"<{h2}{hStyle}>{aNm}{tHead}</{h2}>\n")
|
||||
|
||||
elif tType == BlockTyp.HEAD3:
|
||||
tHead = tText.replace(nwHeadFmt.BR, "<br>")
|
||||
tHead = tText.replace("\n", "<br>")
|
||||
lines.append(f"<{h3}{hStyle}>{aNm}{tHead}</{h3}>\n")
|
||||
|
||||
elif tType == BlockTyp.HEAD4:
|
||||
tHead = tText.replace(nwHeadFmt.BR, "<br>")
|
||||
tHead = tText.replace("\n", "<br>")
|
||||
lines.append(f"<{h4}{hStyle}>{aNm}{tHead}</{h4}>\n")
|
||||
|
||||
elif tType == BlockTyp.SEP:
|
||||
|
||||
@@ -56,7 +56,6 @@ class ComStyle(NamedTuple):
|
||||
textClass: str = ""
|
||||
|
||||
|
||||
B_EMPTY: T_Block = (BlockTyp.EMPTY, "", "", [], BlockFmt.NONE)
|
||||
COMMENT_STYLE = {
|
||||
nwComment.PLAIN: ComStyle("Comment", "comment", "comment"),
|
||||
nwComment.IGNORE: ComStyle(),
|
||||
@@ -67,13 +66,12 @@ COMMENT_STYLE = {
|
||||
nwComment.COMMENT: ComStyle(),
|
||||
nwComment.STORY: ComStyle("", "modifier", "note"),
|
||||
}
|
||||
|
||||
# Lookups
|
||||
HEADINGS = [BlockTyp.TITLE, BlockTyp.HEAD1, BlockTyp.HEAD2, BlockTyp.HEAD3, BlockTyp.HEAD4]
|
||||
SKIP_INDENT = [
|
||||
BlockTyp.TITLE, BlockTyp.HEAD1, BlockTyp.HEAD2, BlockTyp.HEAD2, BlockTyp.HEAD3,
|
||||
BlockTyp.HEAD4, BlockTyp.SEP, BlockTyp.SKIP,
|
||||
]
|
||||
B_EMPTY: T_Block = (BlockTyp.EMPTY, "", "", [], BlockFmt.NONE)
|
||||
|
||||
|
||||
class Tokenizer(ABC):
|
||||
@@ -182,8 +180,6 @@ class Tokenizer(ABC):
|
||||
(REGEX_PATTERNS.markdownBold, [0, TextFmt.B_B, 0, TextFmt.B_E]),
|
||||
(REGEX_PATTERNS.markdownStrike, [0, TextFmt.D_B, 0, TextFmt.D_E]),
|
||||
]
|
||||
self._rxShortCodes = REGEX_PATTERNS.shortcodePlain
|
||||
self._rxShortCodeVals = REGEX_PATTERNS.shortcodeValue
|
||||
|
||||
self._shortCodeFmt = {
|
||||
nwShortcode.ITALIC_O: TextFmt.I_B, nwShortcode.ITALIC_C: TextFmt.I_E,
|
||||
@@ -456,20 +452,22 @@ class Tokenizer(ABC):
|
||||
self._text = ""
|
||||
self._handle = None
|
||||
|
||||
if (tItem := self._project.tree[tHandle]) and tItem.isRootType():
|
||||
if (item := self._project.tree[tHandle]) and item.isRootType():
|
||||
self._handle = tHandle
|
||||
style = BlockFmt.CENTRE
|
||||
if self._isFirst:
|
||||
textAlign = BlockFmt.CENTRE
|
||||
self._isFirst = False
|
||||
else:
|
||||
textAlign = BlockFmt.PBB | BlockFmt.CENTRE
|
||||
style |= BlockFmt.PBB
|
||||
|
||||
trNotes = self._localLookup("Notes")
|
||||
title = f"{trNotes}: {tItem.itemName}"
|
||||
self._blocks = []
|
||||
self._blocks.append((
|
||||
BlockTyp.TITLE, f"{self._handle}:T0001", title, [], textAlign
|
||||
))
|
||||
title = item.itemName
|
||||
if not item.isNovelLike():
|
||||
notes = self._localLookup("Notes")
|
||||
title = f"{notes}: {title}"
|
||||
|
||||
self._blocks = [(
|
||||
BlockTyp.TITLE, f"{self._handle}:T0001", title, [], style
|
||||
)]
|
||||
if self._keepRaw:
|
||||
self._raw.append(f"#! {title}\n\n")
|
||||
|
||||
@@ -523,25 +521,30 @@ class Tokenizer(ABC):
|
||||
isNovel = self._isNovel
|
||||
keepRaw = self._keepRaw
|
||||
doJustify = self._doJustify
|
||||
keepBreaks = self._keepBreaks
|
||||
indentFirst = self._indentFirst
|
||||
firstIndent = self._firstIndent
|
||||
|
||||
if self._isNovel:
|
||||
self._hFormatter.setHandle(self._handle)
|
||||
|
||||
# Replace all instances of [br] with a placeholder character
|
||||
text = REGEX_PATTERNS.lineBreak.sub("\uffff", self._text)
|
||||
|
||||
nHead = 0
|
||||
breakNext = False
|
||||
tmpMarkdown = []
|
||||
rawText = []
|
||||
tHandle = self._handle or ""
|
||||
tBlocks: list[T_Block] = [B_EMPTY]
|
||||
for aLine in self._text.splitlines():
|
||||
for bLine in text.splitlines():
|
||||
aLine = bLine.replace("\uffff", "") # Remove placeholder characters
|
||||
sLine = aLine.strip().lower()
|
||||
|
||||
# Check for blank lines
|
||||
if not sLine:
|
||||
tBlocks.append(B_EMPTY)
|
||||
if keepRaw:
|
||||
tmpMarkdown.append("\n")
|
||||
rawText.append("\n")
|
||||
continue
|
||||
|
||||
if breakNext:
|
||||
@@ -607,13 +610,13 @@ class Tokenizer(ABC):
|
||||
BlockTyp.COMMENT, "", tLine, tFmt, sAlign
|
||||
))
|
||||
if keepRaw:
|
||||
tmpMarkdown.append(f"{aLine}\n")
|
||||
rawText.append(f"{aLine}\n")
|
||||
|
||||
elif cStyle == nwComment.FOOTNOTE:
|
||||
tLine, tFmt = self._extractFormats(cText, skip=TextFmt.FNOTE)
|
||||
self._footnotes[f"{tHandle}:{cKey}"] = (tLine, tFmt)
|
||||
if keepRaw:
|
||||
tmpMarkdown.append(f"{aLine}\n")
|
||||
rawText.append(f"{aLine}\n")
|
||||
|
||||
elif aLine.startswith("@"):
|
||||
# Keywords
|
||||
@@ -628,7 +631,7 @@ class Tokenizer(ABC):
|
||||
BlockTyp.KEYWORD, tTag[1:], tLine, tFmt, sAlign
|
||||
))
|
||||
if keepRaw:
|
||||
tmpMarkdown.append(f"{aLine}\n")
|
||||
rawText.append(f"{aLine}\n")
|
||||
|
||||
elif aLine.startswith(("# ", "#! ")):
|
||||
# Title or Partition Headings
|
||||
@@ -664,7 +667,7 @@ class Tokenizer(ABC):
|
||||
tType, f"{tHandle}:T{nHead:04d}", tText, [], tStyle
|
||||
))
|
||||
if keepRaw:
|
||||
tmpMarkdown.append(f"{aLine}\n")
|
||||
rawText.append(f"{aLine}\n")
|
||||
|
||||
elif aLine.startswith(("## ", "##! ")):
|
||||
# (Unnumbered) Chapter Headings
|
||||
@@ -699,7 +702,7 @@ class Tokenizer(ABC):
|
||||
tType, f"{tHandle}:T{nHead:04d}", tText, [], tStyle
|
||||
))
|
||||
if keepRaw:
|
||||
tmpMarkdown.append(f"{aLine}\n")
|
||||
rawText.append(f"{aLine}\n")
|
||||
|
||||
elif aLine.startswith(("### ", "###! ")):
|
||||
# (Alternative) Scene Headings
|
||||
@@ -740,7 +743,7 @@ class Tokenizer(ABC):
|
||||
tType, f"{tHandle}:T{nHead:04d}", tText, [], tStyle
|
||||
))
|
||||
if keepRaw:
|
||||
tmpMarkdown.append(f"{aLine}\n")
|
||||
rawText.append(f"{aLine}\n")
|
||||
|
||||
elif aLine.startswith("#### "):
|
||||
# Section Headings
|
||||
@@ -770,7 +773,7 @@ class Tokenizer(ABC):
|
||||
tType, f"{tHandle}:T{nHead:04d}", tText, [], tStyle
|
||||
))
|
||||
if keepRaw:
|
||||
tmpMarkdown.append(f"{aLine}\n")
|
||||
rawText.append(f"{aLine}\n")
|
||||
|
||||
else:
|
||||
# Text Lines
|
||||
@@ -786,19 +789,19 @@ class Tokenizer(ABC):
|
||||
alnRight = False
|
||||
indLeft = False
|
||||
indRight = False
|
||||
if aLine.startswith(">>"):
|
||||
if bLine.startswith(">>"):
|
||||
alnRight = True
|
||||
aLine = aLine[2:].lstrip(" ")
|
||||
elif aLine.startswith(">"):
|
||||
bLine = bLine[2:].lstrip(" ")
|
||||
elif bLine.startswith(">"):
|
||||
indLeft = True
|
||||
aLine = aLine[1:].lstrip(" ")
|
||||
bLine = bLine[1:].lstrip(" ")
|
||||
|
||||
if aLine.endswith("<<"):
|
||||
if bLine.endswith("<<"):
|
||||
alnLeft = True
|
||||
aLine = aLine[:-2].rstrip(" ")
|
||||
elif aLine.endswith("<"):
|
||||
bLine = bLine[:-2].rstrip(" ")
|
||||
elif bLine.endswith("<"):
|
||||
indRight = True
|
||||
aLine = aLine[:-1].rstrip(" ")
|
||||
bLine = bLine[:-1].rstrip(" ")
|
||||
|
||||
if alnLeft and alnRight:
|
||||
sAlign |= BlockFmt.CENTRE
|
||||
@@ -813,12 +816,12 @@ class Tokenizer(ABC):
|
||||
sAlign |= BlockFmt.IND_R
|
||||
|
||||
# Process formats
|
||||
tLine, tFmt = self._extractFormats(aLine, hDialog=isNovel)
|
||||
tLine, tFmt = self._extractFormats(bLine, hDialog=isNovel)
|
||||
tBlocks.append((
|
||||
BlockTyp.TEXT, "", tLine, tFmt, sAlign
|
||||
))
|
||||
if keepRaw:
|
||||
tmpMarkdown.append(f"{aLine}\n")
|
||||
rawText.append(f"{aLine}\n")
|
||||
|
||||
# If we have content, turn off the first page flag
|
||||
if self._isFirst and len(tBlocks) > 1:
|
||||
@@ -834,8 +837,8 @@ class Tokenizer(ABC):
|
||||
# Always add an empty line at the end of the file
|
||||
tBlocks.append(B_EMPTY)
|
||||
if keepRaw:
|
||||
tmpMarkdown.append("\n")
|
||||
self._raw.append("".join(tmpMarkdown))
|
||||
rawText.append("\n")
|
||||
self._raw.append("".join(rawText))
|
||||
|
||||
# Second Pass
|
||||
# ===========
|
||||
@@ -844,7 +847,7 @@ class Tokenizer(ABC):
|
||||
# It also ensures that there isn't paragraph spacing between
|
||||
# meta data lines for formats that have spacing.
|
||||
|
||||
lineSep = "\n" if self._keepBreaks else " "
|
||||
lineSep = "\n" if keepBreaks else " "
|
||||
|
||||
pLines: list[T_Block] = []
|
||||
sBlocks: list[T_Block] = []
|
||||
@@ -894,9 +897,12 @@ class Tokenizer(ABC):
|
||||
# enabled, and there is no alignment, we apply it.
|
||||
if doJustify and not cStyle & BlockFmt.ALIGNED:
|
||||
cStyle |= BlockFmt.JUSTIFY
|
||||
|
||||
pTxt = pLines[0][2].replace("\uffff", "\n")
|
||||
sBlocks.append((
|
||||
BlockTyp.TEXT, pLines[0][1], pLines[0][2], pLines[0][3], cStyle
|
||||
BlockTyp.TEXT, pLines[0][1], pTxt, pLines[0][3], cStyle
|
||||
))
|
||||
|
||||
elif nLines > 1:
|
||||
# The paragraph contains multiple lines, so we need to
|
||||
# join them according to the line break policy, and
|
||||
@@ -907,8 +913,11 @@ class Tokenizer(ABC):
|
||||
tLen = len(tTxt)
|
||||
tTxt += f"{aBlock[2]}{lineSep}"
|
||||
tFmt.extend((p+tLen, fmt, key) for p, fmt, key in aBlock[3])
|
||||
cStyle |= aBlock[4]
|
||||
|
||||
pTxt = tTxt[:-1].replace("\uffff", "\n")
|
||||
sBlocks.append((
|
||||
BlockTyp.TEXT, pLines[0][1], tTxt[:-1], tFmt, cStyle
|
||||
BlockTyp.TEXT, pLines[0][1], pTxt, tFmt, cStyle
|
||||
))
|
||||
|
||||
# Reset buffer and make sure text indent is on for next pass
|
||||
@@ -1136,12 +1145,12 @@ class Tokenizer(ABC):
|
||||
# Post-process text and format
|
||||
result = text
|
||||
formats = []
|
||||
for pos, end, fmt, key in reversed(sorted(temp, key=lambda x: x[0])):
|
||||
for pos, end, fmt, meta in reversed(sorted(temp, key=lambda x: x[0])):
|
||||
if fmt > 0:
|
||||
if end > pos:
|
||||
result = result[:pos] + result[end:]
|
||||
formats = [(p+pos-end if p > pos else p, f, k) for p, f, k in formats]
|
||||
formats.insert(0, (pos, fmt, key))
|
||||
formats = [(p+pos-end if p > pos else p, f, m) for p, f, m in formats]
|
||||
formats.insert(0, (pos, fmt, meta))
|
||||
|
||||
return result, formats
|
||||
|
||||
@@ -1187,6 +1196,7 @@ class HeadingFormatter:
|
||||
def apply(self, hFormat: str, text: str, nHead: int) -> str:
|
||||
"""Apply formatting to a specific heading."""
|
||||
hFormat = hFormat.replace(nwHeadFmt.TITLE, text)
|
||||
hFormat = hFormat.replace(nwHeadFmt.BR, "\n")
|
||||
hFormat = hFormat.replace(nwHeadFmt.CH_NUM, str(self._chCount))
|
||||
hFormat = hFormat.replace(nwHeadFmt.SC_NUM, str(self._scChCount))
|
||||
hFormat = hFormat.replace(nwHeadFmt.SC_ABS, str(self._scAbsCount))
|
||||
|
||||
@@ -27,7 +27,7 @@ import logging
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from novelwriter.constants import nwHeadFmt, nwUnicode
|
||||
from novelwriter.constants import nwUnicode
|
||||
from novelwriter.core.project import NWProject
|
||||
from novelwriter.formats.shared import BlockFmt, BlockTyp, T_Formats, TextFmt
|
||||
from novelwriter.formats.tokenizer import Tokenizer
|
||||
@@ -113,23 +113,23 @@ class ToMarkdown(Tokenizer):
|
||||
lines.append(f"{tTemp}\n\n")
|
||||
|
||||
elif tType == BlockTyp.TITLE:
|
||||
tHead = tText.replace(nwHeadFmt.BR, "\n")
|
||||
tHead = tText.replace("\n", " - ")
|
||||
lines.append(f"# {tHead}\n\n")
|
||||
|
||||
elif tType == BlockTyp.HEAD1:
|
||||
tHead = tText.replace(nwHeadFmt.BR, "\n")
|
||||
tHead = tText.replace("\n", " - ")
|
||||
lines.append(f"# {tHead}\n\n")
|
||||
|
||||
elif tType == BlockTyp.HEAD2:
|
||||
tHead = tText.replace(nwHeadFmt.BR, "\n")
|
||||
tHead = tText.replace("\n", " - ")
|
||||
lines.append(f"## {tHead}\n\n")
|
||||
|
||||
elif tType == BlockTyp.HEAD3:
|
||||
tHead = tText.replace(nwHeadFmt.BR, "\n")
|
||||
tHead = tText.replace("\n", " - ")
|
||||
lines.append(f"### {tHead}\n\n")
|
||||
|
||||
elif tType == BlockTyp.HEAD4:
|
||||
tHead = tText.replace(nwHeadFmt.BR, "\n")
|
||||
tHead = tText.replace("\n", " - ")
|
||||
lines.append(f"#### {tHead}\n\n")
|
||||
|
||||
elif tType == BlockTyp.SEP:
|
||||
|
||||
@@ -444,24 +444,19 @@ class ToOdt(Tokenizer):
|
||||
|
||||
elif tType == BlockTyp.TITLE:
|
||||
# Title must be text:p
|
||||
tHead = tText.replace(nwHeadFmt.BR, "\n")
|
||||
self._addTextPar(xText, S_TITLE, oStyle, tHead, isHead=False)
|
||||
self._addTextPar(xText, S_TITLE, oStyle, tText, isHead=False)
|
||||
|
||||
elif tType == BlockTyp.HEAD1:
|
||||
tHead = tText.replace(nwHeadFmt.BR, "\n")
|
||||
self._addTextPar(xText, S_HEAD1, oStyle, tHead, isHead=True, oLevel="1")
|
||||
self._addTextPar(xText, S_HEAD1, oStyle, tText, isHead=True, oLevel="1")
|
||||
|
||||
elif tType == BlockTyp.HEAD2:
|
||||
tHead = tText.replace(nwHeadFmt.BR, "\n")
|
||||
self._addTextPar(xText, S_HEAD2, oStyle, tHead, isHead=True, oLevel="2")
|
||||
self._addTextPar(xText, S_HEAD2, oStyle, tText, isHead=True, oLevel="2")
|
||||
|
||||
elif tType == BlockTyp.HEAD3:
|
||||
tHead = tText.replace(nwHeadFmt.BR, "\n")
|
||||
self._addTextPar(xText, S_HEAD3, oStyle, tHead, isHead=True, oLevel="3")
|
||||
self._addTextPar(xText, S_HEAD3, oStyle, tText, isHead=True, oLevel="3")
|
||||
|
||||
elif tType == BlockTyp.HEAD4:
|
||||
tHead = tText.replace(nwHeadFmt.BR, "\n")
|
||||
self._addTextPar(xText, S_HEAD4, oStyle, tHead, isHead=True, oLevel="4")
|
||||
self._addTextPar(xText, S_HEAD4, oStyle, tText, isHead=True, oLevel="4")
|
||||
|
||||
elif tType == BlockTyp.SEP:
|
||||
self._addTextPar(xText, S_SEP, oStyle, tText)
|
||||
|
||||
@@ -34,7 +34,7 @@ from PyQt5.QtGui import (
|
||||
)
|
||||
from PyQt5.QtPrintSupport import QPrinter
|
||||
|
||||
from novelwriter.constants import nwHeadFmt, nwStyles, nwUnicode
|
||||
from novelwriter.constants import nwStyles, nwUnicode
|
||||
from novelwriter.core.project import NWProject
|
||||
from novelwriter.formats.shared import BlockFmt, BlockTyp, T_Formats, TextFmt
|
||||
from novelwriter.formats.tokenizer import HEADINGS, Tokenizer
|
||||
@@ -217,7 +217,7 @@ class ToQTextDocument(Tokenizer):
|
||||
elif tType in HEADINGS:
|
||||
bFmt, cFmt = self._genHeadStyle(tType, tMeta, bFmt)
|
||||
newBlock(cursor, bFmt)
|
||||
cursor.insertText(tText.replace(nwHeadFmt.BR, "\n"), cFmt)
|
||||
cursor.insertText(tText, cFmt)
|
||||
|
||||
elif tType == BlockTyp.SEP:
|
||||
newBlock(cursor, bFmt)
|
||||
|
||||
@@ -877,6 +877,8 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
after = False
|
||||
elif insert == nwDocInsert.FOOTNOTE:
|
||||
self._insertCommentStructure(nwComment.FOOTNOTE)
|
||||
elif insert == nwDocInsert.LINE_BRK:
|
||||
text = nwShortcode.BREAK
|
||||
|
||||
if text:
|
||||
if block:
|
||||
|
||||
@@ -570,8 +570,8 @@ class GuiMainMenu(QMenuBar):
|
||||
lambda: self.requestDocInsert.emit(nwDocInsert.SHORT)
|
||||
)
|
||||
|
||||
# Insert > Symbols
|
||||
self.mInsBreaks = self.insMenu.addMenu(self.tr("Page Break and Space"))
|
||||
# Insert > Breaks and Vertical Space
|
||||
self.mInsBreaks = self.insMenu.addMenu(self.tr("Breaks and Vertical Space"))
|
||||
|
||||
# Insert > New Page
|
||||
self.aInsNewPage = self.mInsBreaks.addAction(self.tr("Page Break"))
|
||||
@@ -579,6 +579,12 @@ class GuiMainMenu(QMenuBar):
|
||||
lambda: self.requestDocInsert.emit(nwDocInsert.NEW_PAGE)
|
||||
)
|
||||
|
||||
# Insert > Forced Line Break
|
||||
self.aInsLineBreak = self.mInsBreaks.addAction(self.tr("Forced Line Break"))
|
||||
self.aInsLineBreak.triggered.connect(
|
||||
lambda: self.requestDocInsert.emit(nwDocInsert.LINE_BRK)
|
||||
)
|
||||
|
||||
# Insert > Vertical Space (Single)
|
||||
self.aInsVSpaceS = self.mInsBreaks.addAction(self.tr("Vertical Space (Single)"))
|
||||
self.aInsVSpaceS.triggered.connect(
|
||||
|
||||
@@ -33,6 +33,7 @@ class RegExPatterns:
|
||||
|
||||
# Static RegExes
|
||||
_rxWords = re.compile(nwRegEx.WORDS, re.UNICODE)
|
||||
_rxBreak = re.compile(nwRegEx.BREAK, re.UNICODE)
|
||||
_rxItalic = re.compile(nwRegEx.FMT_EI, re.UNICODE)
|
||||
_rxBold = re.compile(nwRegEx.FMT_EB, re.UNICODE)
|
||||
_rxStrike = re.compile(nwRegEx.FMT_ST, re.UNICODE)
|
||||
@@ -44,6 +45,11 @@ class RegExPatterns:
|
||||
"""Split text into words."""
|
||||
return self._rxWords
|
||||
|
||||
@property
|
||||
def lineBreak(self) -> re.Pattern:
|
||||
"""Find forced line break."""
|
||||
return self._rxBreak
|
||||
|
||||
@property
|
||||
def markdownItalic(self) -> re.Pattern:
|
||||
"""Markdown italic style."""
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
%%~name: Title Page
|
||||
%%~path: 7031beac91f75/53b69b83cdafc
|
||||
%%~kind: NOVEL/DOCUMENT
|
||||
%%~hash: c5dc35d18ecb074a9e41a1410d1bff8021cf0a5b
|
||||
%%~date: Unknown/2023-08-25 16:51:52
|
||||
%%~hash: 4072adb6d21ff877577f033f19714d9bd01396f3
|
||||
%%~date: Unknown/2024-10-24 16:25:44
|
||||
|
||||
Jane Smith[br]
|
||||
42 Main Street[br]
|
||||
1234 Capital City <<
|
||||
|
||||
[vspace:5]
|
||||
|
||||
#! My Novel
|
||||
|
||||
>> **By Jane Smith** <<
|
||||
|
||||
>> This is the title page. <<
|
||||
>> It should be the first document of the project. <<
|
||||
>> It should be the first document of the project. <<
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
%%~name: Interlude
|
||||
%%~path: 7031beac91f75/ba8a28a246524
|
||||
%%~kind: NOVEL/DOCUMENT
|
||||
%%~hash: 721d3d15e0233186354ac6fa61f27db10f38e6bc
|
||||
%%~date: Unknown/2024-03-14 22:55:04
|
||||
%%~hash: 5c8f68d48573b576dcaacf6d6928c496ec361b9d
|
||||
%%~date: Unknown/2024-10-24 01:22:15
|
||||
##! Interlude
|
||||
|
||||
% Notice that this document has a title with a ‘!’ in it in addition to the two hash symbols. This means it is an unnumbered chapter. Unnumbered chapters can be treated separately from numbered chapters during export. Perfect for when you want to add an interlude, or for a prologue or epilogue.
|
||||
|
||||
I am the very model of a modern Major-General
|
||||
I've information vegetable, animal, and mineral
|
||||
I know the kings of England, and I quote the fights historical
|
||||
From Marathon to Waterloo, in order categorical
|
||||
I am the very model of a modern Major-General[br]
|
||||
I've information vegetable, animal, and mineral[br]
|
||||
I know the kings of England, and I quote the fights historical[br]
|
||||
From Marathon to Waterloo, in order categorical <<
|
||||
|
||||
I'm very well acquainted, too, with matters mathematical <<
|
||||
I understand equations, both the simple and quadratical
|
||||
About binomial theorem I'm teeming with a lot o’ news
|
||||
With many cheerful facts about the square of the hypotenuse
|
||||
I'm very well acquainted, too, with matters mathematical[br]
|
||||
I understand equations, both the simple and quadratical[br]
|
||||
About binomial theorem I'm teeming with a lot o’ news[br]
|
||||
With many cheerful facts about the square of the hypotenuse <<
|
||||
|
||||
With many cheerful facts about the square of the hypotenuse <<
|
||||
With many cheerful facts about the square of the hypotenuse
|
||||
With many cheerful facts about the square of the hypotepotenuse
|
||||
With many cheerful facts about the square of the hypotenuse[br]
|
||||
With many cheerful facts about the square of the hypotenuse[br]
|
||||
With many cheerful facts about the square of the hypotepotenuse <<
|
||||
|
||||
% Notice that the lines in the verse end in a single line break. Single line breaks do not create a new paragraph but instead insert a break within the paragraph. Press Ctrl+R to see what this renders like.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<novelWriterXML appVersion="2.6a2" hexVersion="0x020600a2" fileVersion="1.5" fileRevision="4" timeStamp="2024-10-23 17:41:09">
|
||||
<project id="e2be99af-f9bf-4403-857a-c3d1ac25abea" saveCount="2074" autoCount="277" editTime="93039">
|
||||
<novelWriterXML appVersion="2.6a2" hexVersion="0x020600a2" fileVersion="1.5" fileRevision="4" timeStamp="2024-10-24 16:26:12">
|
||||
<project id="e2be99af-f9bf-4403-857a-c3d1ac25abea" saveCount="2077" autoCount="279" editTime="93511">
|
||||
<name>Sample Project</name>
|
||||
<author>Jane Smith</author>
|
||||
</project>
|
||||
@@ -36,13 +36,13 @@
|
||||
<entry key="i56be10" count="1" red="220" green="138" blue="221" shape="BLOCK_4">Main</entry>
|
||||
</importance>
|
||||
</settings>
|
||||
<content items="31" novelWords="996" notesWords="416">
|
||||
<content items="31" novelWords="1004" notesWords="416">
|
||||
<item handle="7031beac91f75" parent="None" root="7031beac91f75" order="0" type="ROOT" class="NOVEL">
|
||||
<meta expanded="yes" />
|
||||
<name status="sc24b8f" import="ia857f0">Novel</name>
|
||||
</item>
|
||||
<item handle="53b69b83cdafc" parent="7031beac91f75" root="7031beac91f75" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta expanded="no" heading="H1" charCount="95" wordCount="19" paraCount="2" cursorPos="31" />
|
||||
<meta expanded="no" heading="H1" charCount="136" wordCount="27" paraCount="3" cursorPos="69" />
|
||||
<name status="sc24b8f" import="ia857f0" active="yes">Title Page</name>
|
||||
</item>
|
||||
<item handle="974e400180a99" parent="7031beac91f75" root="7031beac91f75" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
@@ -58,7 +58,7 @@
|
||||
<name status="sf24ce6" import="ia857f0" active="yes">Chapter One</name>
|
||||
</item>
|
||||
<item handle="636b6aa9b697b" parent="6a2d6d5f4f401" root="7031beac91f75" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta expanded="no" heading="H3" charCount="2953" wordCount="520" paraCount="15" cursorPos="66" />
|
||||
<meta expanded="no" heading="H3" charCount="2937" wordCount="520" paraCount="15" cursorPos="4" />
|
||||
<name status="s90e6c9" import="ia857f0" active="yes">Making a Scene</name>
|
||||
</item>
|
||||
<item handle="bc0cbd2a407f3" parent="6a2d6d5f4f401" root="7031beac91f75" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
@@ -66,7 +66,7 @@
|
||||
<name status="s90e6c9" import="ia857f0" active="yes">Another Scene</name>
|
||||
</item>
|
||||
<item handle="ba8a28a246524" parent="7031beac91f75" root="7031beac91f75" order="4" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta expanded="no" heading="H2" charCount="617" wordCount="101" paraCount="3" cursorPos="357" />
|
||||
<meta expanded="no" heading="H2" charCount="617" wordCount="101" paraCount="3" cursorPos="1182" />
|
||||
<name status="s78ea90" import="ia857f0" active="yes">Interlude</name>
|
||||
</item>
|
||||
<item handle="96b68994dfa3d" parent="7031beac91f75" root="7031beac91f75" order="5" type="FILE" class="NOVEL" layout="NOTE">
|
||||
|
||||
@@ -2,19 +2,19 @@
|
||||
"meta": {
|
||||
"projectName": "Lorem Ipsum",
|
||||
"novelAuthor": "lipsum.com",
|
||||
"buildTime": 1729029144,
|
||||
"buildTimeStr": "2024-10-15 23:52:24"
|
||||
"buildTime": 1729725334,
|
||||
"buildTimeStr": "2024-10-24 01:15:34"
|
||||
},
|
||||
"text": {
|
||||
"nwd": [
|
||||
[
|
||||
"#! Lorem Ipsum",
|
||||
"",
|
||||
"**By lipsum.com**",
|
||||
">> **By lipsum.com** <<",
|
||||
"",
|
||||
"\u201cNeque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit\u2026\u201d",
|
||||
">> \u201cNeque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit\u2026\u201d <<",
|
||||
"",
|
||||
"\u201cThere is no one who loves pain itself, who seeks after it and wants to have it, simply because it is pain\u2026\u201d"
|
||||
">> \u201cThere is no one who loves pain itself, who seeks after it and wants to have it, simply because it is pain\u2026\u201d <<"
|
||||
],
|
||||
[
|
||||
"",
|
||||
@@ -36,7 +36,7 @@
|
||||
[
|
||||
"# Act One",
|
||||
"",
|
||||
"\u201cFusce maximus felis libero\u201d"
|
||||
">> \u201cFusce maximus felis libero\u201d <<"
|
||||
],
|
||||
[
|
||||
"## Chapter One",
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
#! Lorem Ipsum
|
||||
|
||||
**By lipsum.com**
|
||||
>> **By lipsum.com** <<
|
||||
|
||||
“Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit…”
|
||||
>> “Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit…” <<
|
||||
|
||||
“There is no one who loves pain itself, who seeks after it and wants to have it, simply because it is pain…”
|
||||
>> “There is no one who loves pain itself, who seeks after it and wants to have it, simply because it is pain…” <<
|
||||
|
||||
|
||||
% Exctracted from the lipsum.com website.
|
||||
@@ -23,7 +23,7 @@ _Lorem Ipsum_ is simply dummy text[footnote:f9kgf] of the printing and typesetti
|
||||
|
||||
# Act One
|
||||
|
||||
“Fusce maximus felis libero”
|
||||
>> “Fusce maximus felis libero” <<
|
||||
|
||||
## Chapter One
|
||||
|
||||
|
||||
@@ -412,7 +412,7 @@ def testCoreDocBuild_Custom(mockGUI, fncPath: Path):
|
||||
assert error == []
|
||||
assert docFile.read_text(encoding="utf-8") == (
|
||||
"#! New Novel\n\n"
|
||||
"By Jane Doe\n\n"
|
||||
">> By Jane Doe <<\n\n"
|
||||
"## New Chapter\n\n\n"
|
||||
"### New Scene\n\n\n"
|
||||
)
|
||||
@@ -442,7 +442,7 @@ def testCoreDocBuild_Custom(mockGUI, fncPath: Path):
|
||||
assert error == []
|
||||
assert docFile.read_text(encoding="utf-8") == (
|
||||
"#! New Novel\n\n"
|
||||
"By Jane Doe\n\n"
|
||||
">> By Jane Doe <<\n\n"
|
||||
"## New Chapter\n\n\n"
|
||||
"### New Scene\n\n\n"
|
||||
)
|
||||
@@ -566,7 +566,7 @@ def testCoreDocBuild_IterBuild(mockGUI, fncPath: Path, mockRnd):
|
||||
assert isinstance(docBuild.lastBuild, ToRaw)
|
||||
assert docFile.read_text(encoding="utf-8") == (
|
||||
"#! New Novel\n\n"
|
||||
"By Jane Doe\n\n"
|
||||
">> By Jane Doe <<\n\n"
|
||||
"## New Chapter\n\n\n"
|
||||
"### New Scene\n\n\n"
|
||||
"#! Notes: Plot\n\n"
|
||||
|
||||
@@ -32,10 +32,7 @@ from novelwriter.core.docbuild import NWBuildDocument
|
||||
from novelwriter.core.project import NWProject
|
||||
from novelwriter.enum import nwBuildFmt
|
||||
from novelwriter.formats.shared import BlockFmt, BlockTyp
|
||||
from novelwriter.formats.todocx import (
|
||||
S_FNOTE, S_HEAD1, S_HEAD2, S_HEAD3, S_HEAD4, S_META, S_NORM, S_SEP,
|
||||
S_TITLE, ToDocX, _mkTag, _wTag
|
||||
)
|
||||
from novelwriter.formats.todocx import ToDocX, _mkTag, _wTag
|
||||
|
||||
from tests.tools import DOCX_IGNORE, cmpFiles
|
||||
|
||||
@@ -59,6 +56,120 @@ def xmlToText(xElem):
|
||||
return rTxt
|
||||
|
||||
|
||||
@pytest.mark.core
|
||||
def testFmtToDocX_HeadingStyles(mockGUI):
|
||||
"""Test formatting of headings."""
|
||||
project = NWProject()
|
||||
doc = ToDocX(project)
|
||||
doc._isNovel = True
|
||||
doc.initDocument()
|
||||
|
||||
# Title
|
||||
# =====
|
||||
|
||||
xTest = ET.Element(_wTag("body"))
|
||||
doc._text = "#! Hello World"
|
||||
doc.tokenizeText()
|
||||
doc.doConvert()
|
||||
doc._pars[-1].toXml(xTest)
|
||||
assert xmlToText(xTest) == (
|
||||
'<w:body><w:p><w:pPr><w:pStyle w:val="Title" /><w:jc w:val="center" /></w:pPr>'
|
||||
'<w:r><w:rPr /><w:t>Hello World</w:t></w:r></w:p></w:body>'
|
||||
)
|
||||
|
||||
# Heading Level 1
|
||||
# ===============
|
||||
doc._text = "# Hello World"
|
||||
|
||||
# Plain
|
||||
xTest = ET.Element(_wTag("body"))
|
||||
doc.tokenizeText()
|
||||
doc.doConvert()
|
||||
doc._pars[-1].toXml(xTest)
|
||||
assert xmlToText(xTest) == (
|
||||
'<w:body><w:p><w:pPr><w:pStyle w:val="Heading1" /><w:jc w:val="center" /></w:pPr>'
|
||||
'<w:r><w:br w:type="page" /></w:r>'
|
||||
'<w:r><w:rPr /><w:t>Hello World</w:t></w:r></w:p></w:body>'
|
||||
)
|
||||
|
||||
# Formatted
|
||||
xTest = ET.Element(_wTag("body"))
|
||||
doc.setPartitionFormat(f"Part{nwHeadFmt.BR}{nwHeadFmt.TITLE}")
|
||||
doc.tokenizeText()
|
||||
doc.doConvert()
|
||||
doc._pars[-1].toXml(xTest)
|
||||
assert xmlToText(xTest) == (
|
||||
'<w:body><w:p><w:pPr><w:pStyle w:val="Heading1" /><w:jc w:val="center" /></w:pPr>'
|
||||
'<w:r><w:br w:type="page" /></w:r>'
|
||||
'<w:r><w:rPr /><w:t>Part</w:t><w:br /><w:t>Hello World</w:t></w:r></w:p></w:body>'
|
||||
)
|
||||
|
||||
# Heading Level 2
|
||||
# ===============
|
||||
doc._text = "## Hello World"
|
||||
|
||||
# Plain
|
||||
xTest = ET.Element(_wTag("body"))
|
||||
doc.tokenizeText()
|
||||
doc.doConvert()
|
||||
doc._pars[-1].toXml(xTest)
|
||||
assert xmlToText(xTest) == (
|
||||
'<w:body><w:p><w:pPr><w:pStyle w:val="Heading2" /></w:pPr>'
|
||||
'<w:r><w:br w:type="page" /></w:r>'
|
||||
'<w:r><w:rPr /><w:t>Hello World</w:t></w:r></w:p></w:body>'
|
||||
)
|
||||
|
||||
# Formatted
|
||||
xTest = ET.Element(_wTag("body"))
|
||||
doc.setChapterFormat(f"Chapter {nwHeadFmt.CH_NUM}{nwHeadFmt.BR}{nwHeadFmt.TITLE}")
|
||||
doc.tokenizeText()
|
||||
doc.doConvert()
|
||||
doc._pars[-1].toXml(xTest)
|
||||
assert xmlToText(xTest) == (
|
||||
'<w:body><w:p><w:pPr><w:pStyle w:val="Heading2" /></w:pPr>'
|
||||
'<w:r><w:br w:type="page" /></w:r>'
|
||||
'<w:r><w:rPr /><w:t>Chapter 2</w:t><w:br /><w:t>Hello World</w:t></w:r></w:p></w:body>'
|
||||
)
|
||||
|
||||
# Heading Level 3
|
||||
# ===============
|
||||
doc._text = "### Hello World"
|
||||
|
||||
# Plain
|
||||
xTest = ET.Element(_wTag("body"))
|
||||
doc.tokenizeText()
|
||||
doc.doConvert()
|
||||
doc._pars[-1].toXml(xTest)
|
||||
assert xmlToText(xTest) == (
|
||||
'<w:body><w:p><w:pPr><w:pStyle w:val="Heading3" /></w:pPr><w:r><w:rPr />'
|
||||
'<w:t>Hello World</w:t></w:r></w:p></w:body>'
|
||||
)
|
||||
|
||||
# Formatted
|
||||
xTest = ET.Element(_wTag("body"))
|
||||
doc.setSceneFormat(f"Scene {nwHeadFmt.SC_ABS}{nwHeadFmt.BR}{nwHeadFmt.TITLE}")
|
||||
doc.tokenizeText()
|
||||
doc.doConvert()
|
||||
doc._pars[-1].toXml(xTest)
|
||||
assert xmlToText(xTest) == (
|
||||
'<w:body><w:p><w:pPr><w:pStyle w:val="Heading3" /></w:pPr>'
|
||||
'<w:r><w:rPr /><w:t>Scene 2</w:t><w:br /><w:t>Hello World</w:t></w:r></w:p></w:body>'
|
||||
)
|
||||
|
||||
# Heading Level 4
|
||||
# ===============
|
||||
doc._text = "#### Hello World"
|
||||
|
||||
xTest = ET.Element(_wTag("body"))
|
||||
doc.tokenizeText()
|
||||
doc.doConvert()
|
||||
doc._pars[-1].toXml(xTest)
|
||||
assert xmlToText(xTest) == (
|
||||
'<w:body><w:p><w:pPr><w:pStyle w:val="Heading4" /></w:pPr><w:r><w:rPr />'
|
||||
'<w:t>Hello World</w:t></w:r></w:p></w:body>'
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.core
|
||||
def testFmtToDocX_ParagraphStyles(mockGUI):
|
||||
"""Test formatting of paragraphs."""
|
||||
@@ -71,61 +182,12 @@ def testFmtToDocX_ParagraphStyles(mockGUI):
|
||||
|
||||
# Normal Text
|
||||
xTest = ET.Element(_wTag("body"))
|
||||
doc._blocks = [(BlockTyp.TEXT, "", "Hello World", [], BlockFmt.NONE)]
|
||||
doc._text = "Hello World"
|
||||
doc.tokenizeText()
|
||||
doc.doConvert()
|
||||
doc._pars[-1].toXml(xTest)
|
||||
assert xmlToText(xTest) == (
|
||||
f'<w:body><w:p><w:pPr><w:pStyle w:val="{S_NORM}" /></w:pPr><w:r><w:rPr />'
|
||||
'<w:t>Hello World</w:t></w:r></w:p></w:body>'
|
||||
)
|
||||
|
||||
# Title
|
||||
xTest = ET.Element(_wTag("body"))
|
||||
doc._blocks = [(BlockTyp.TITLE, "", "Hello World", [], BlockFmt.NONE)]
|
||||
doc.doConvert()
|
||||
doc._pars[-1].toXml(xTest)
|
||||
assert xmlToText(xTest) == (
|
||||
f'<w:body><w:p><w:pPr><w:pStyle w:val="{S_TITLE}" /></w:pPr><w:r><w:rPr />'
|
||||
'<w:t>Hello World</w:t></w:r></w:p></w:body>'
|
||||
)
|
||||
|
||||
# Heading Level 1
|
||||
xTest = ET.Element(_wTag("body"))
|
||||
doc._blocks = [(BlockTyp.HEAD1, "", "Hello World", [], BlockFmt.NONE)]
|
||||
doc.doConvert()
|
||||
doc._pars[-1].toXml(xTest)
|
||||
assert xmlToText(xTest) == (
|
||||
f'<w:body><w:p><w:pPr><w:pStyle w:val="{S_HEAD1}" /></w:pPr><w:r><w:rPr />'
|
||||
'<w:t>Hello World</w:t></w:r></w:p></w:body>'
|
||||
)
|
||||
|
||||
# Heading Level 2
|
||||
xTest = ET.Element(_wTag("body"))
|
||||
doc._blocks = [(BlockTyp.HEAD2, "", "Hello World", [], BlockFmt.NONE)]
|
||||
doc.doConvert()
|
||||
doc._pars[-1].toXml(xTest)
|
||||
assert xmlToText(xTest) == (
|
||||
f'<w:body><w:p><w:pPr><w:pStyle w:val="{S_HEAD2}" /></w:pPr><w:r><w:rPr />'
|
||||
'<w:t>Hello World</w:t></w:r></w:p></w:body>'
|
||||
)
|
||||
|
||||
# Heading Level 3
|
||||
xTest = ET.Element(_wTag("body"))
|
||||
doc._blocks = [(BlockTyp.HEAD3, "", "Hello World", [], BlockFmt.NONE)]
|
||||
doc.doConvert()
|
||||
doc._pars[-1].toXml(xTest)
|
||||
assert xmlToText(xTest) == (
|
||||
f'<w:body><w:p><w:pPr><w:pStyle w:val="{S_HEAD3}" /></w:pPr><w:r><w:rPr />'
|
||||
'<w:t>Hello World</w:t></w:r></w:p></w:body>'
|
||||
)
|
||||
|
||||
# Heading Level 4
|
||||
xTest = ET.Element(_wTag("body"))
|
||||
doc._blocks = [(BlockTyp.HEAD4, "", "Hello World", [], BlockFmt.NONE)]
|
||||
doc.doConvert()
|
||||
doc._pars[-1].toXml(xTest)
|
||||
assert xmlToText(xTest) == (
|
||||
f'<w:body><w:p><w:pPr><w:pStyle w:val="{S_HEAD4}" /></w:pPr><w:r><w:rPr />'
|
||||
'<w:body><w:p><w:pPr><w:pStyle w:val="Normal" /></w:pPr><w:r><w:rPr />'
|
||||
'<w:t>Hello World</w:t></w:r></w:p></w:body>'
|
||||
)
|
||||
|
||||
@@ -135,7 +197,7 @@ def testFmtToDocX_ParagraphStyles(mockGUI):
|
||||
doc.doConvert()
|
||||
doc._pars[-1].toXml(xTest)
|
||||
assert xmlToText(xTest) == (
|
||||
f'<w:body><w:p><w:pPr><w:pStyle w:val="{S_SEP}" /></w:pPr><w:r><w:rPr />'
|
||||
'<w:body><w:p><w:pPr><w:pStyle w:val="Separator" /></w:pPr><w:r><w:rPr />'
|
||||
'<w:t>* * *</w:t></w:r></w:p></w:body>'
|
||||
)
|
||||
|
||||
@@ -145,7 +207,7 @@ def testFmtToDocX_ParagraphStyles(mockGUI):
|
||||
doc.doConvert()
|
||||
doc._pars[-1].toXml(xTest)
|
||||
assert xmlToText(xTest) == (
|
||||
f'<w:body><w:p><w:pPr><w:pStyle w:val="{S_NORM}" /></w:pPr></w:p></w:body>'
|
||||
'<w:body><w:p><w:pPr><w:pStyle w:val="Normal" /></w:pPr></w:p></w:body>'
|
||||
)
|
||||
|
||||
# Synopsis
|
||||
@@ -155,7 +217,7 @@ def testFmtToDocX_ParagraphStyles(mockGUI):
|
||||
doc.doConvert()
|
||||
doc._pars[-1].toXml(xTest)
|
||||
assert xmlToText(xTest) == (
|
||||
f'<w:body><w:p><w:pPr><w:pStyle w:val="{S_META}" /></w:pPr>'
|
||||
'<w:body><w:p><w:pPr><w:pStyle w:val="MetaText" /></w:pPr>'
|
||||
'<w:r><w:rPr><w:b /><w:color w:val="813709" /></w:rPr><w:t>Synopsis:</w:t></w:r>'
|
||||
'<w:r><w:rPr /><w:t xml:space="preserve"> </w:t></w:r>'
|
||||
'<w:r><w:rPr><w:color w:val="813709" /></w:rPr><w:t>Hello World</w:t></w:r>'
|
||||
@@ -169,7 +231,7 @@ def testFmtToDocX_ParagraphStyles(mockGUI):
|
||||
doc.doConvert()
|
||||
doc._pars[-1].toXml(xTest)
|
||||
assert xmlToText(xTest) == (
|
||||
f'<w:body><w:p><w:pPr><w:pStyle w:val="{S_META}" /></w:pPr>'
|
||||
'<w:body><w:p><w:pPr><w:pStyle w:val="MetaText" /></w:pPr>'
|
||||
'<w:r><w:rPr><w:b /><w:color w:val="813709" /></w:rPr><w:t>Short Description:</w:t></w:r>'
|
||||
'<w:r><w:rPr /><w:t xml:space="preserve"> </w:t></w:r>'
|
||||
'<w:r><w:rPr><w:color w:val="813709" /></w:rPr><w:t>Hello World</w:t></w:r>'
|
||||
@@ -183,7 +245,7 @@ def testFmtToDocX_ParagraphStyles(mockGUI):
|
||||
doc.doConvert()
|
||||
doc._pars[-1].toXml(xTest)
|
||||
assert xmlToText(xTest) == (
|
||||
f'<w:body><w:p><w:pPr><w:pStyle w:val="{S_META}" /></w:pPr>'
|
||||
'<w:body><w:p><w:pPr><w:pStyle w:val="MetaText" /></w:pPr>'
|
||||
'<w:r><w:rPr><w:b /><w:color w:val="646464" /></w:rPr><w:t>Comment:</w:t></w:r>'
|
||||
'<w:r><w:rPr /><w:t xml:space="preserve"> </w:t></w:r>'
|
||||
'<w:r><w:rPr><w:color w:val="646464" /></w:rPr><w:t>Hello World</w:t></w:r>'
|
||||
@@ -197,7 +259,7 @@ def testFmtToDocX_ParagraphStyles(mockGUI):
|
||||
doc.doConvert()
|
||||
doc._pars[-1].toXml(xTest)
|
||||
assert xmlToText(xTest) == (
|
||||
f'<w:body><w:p><w:pPr><w:pStyle w:val="{S_META}" /></w:pPr>'
|
||||
'<w:body><w:p><w:pPr><w:pStyle w:val="MetaText" /></w:pPr>'
|
||||
'<w:r><w:rPr><w:b /><w:color w:val="f5871f" /></w:rPr><w:t>Tag:</w:t></w:r>'
|
||||
'<w:r><w:rPr /><w:t xml:space="preserve"> </w:t></w:r>'
|
||||
'<w:r><w:rPr><w:color w:val="4271ae" /></w:rPr><w:t>Stuff</w:t></w:r>'
|
||||
@@ -211,7 +273,7 @@ def testFmtToDocX_ParagraphStyles(mockGUI):
|
||||
doc.doConvert()
|
||||
doc._pars[-1].toXml(xTest)
|
||||
assert xmlToText(xTest) == (
|
||||
f'<w:body><w:p><w:pPr><w:pStyle w:val="{S_META}" /></w:pPr>'
|
||||
'<w:body><w:p><w:pPr><w:pStyle w:val="MetaText" /></w:pPr>'
|
||||
'<w:r><w:rPr><w:b /><w:color w:val="f5871f" /></w:rPr><w:t>Characters:</w:t></w:r>'
|
||||
'<w:r><w:rPr /><w:t xml:space="preserve"> </w:t></w:r>'
|
||||
'<w:r><w:rPr><w:color w:val="4271ae" /></w:rPr><w:t>Jane</w:t></w:r>'
|
||||
@@ -245,7 +307,7 @@ def testFmtToDocX_ParagraphFormatting(mockGUI):
|
||||
doc.doConvert()
|
||||
doc._pars[-1].toXml(xTest)
|
||||
assert xmlToText(xTest) == (
|
||||
f'<w:body><w:p><w:pPr><w:pStyle w:val="{S_NORM}" /><w:jc w:val="left" /></w:pPr>'
|
||||
'<w:body><w:p><w:pPr><w:pStyle w:val="Normal" /><w:jc w:val="left" /></w:pPr>'
|
||||
'<w:r><w:rPr /><w:t>Hello World</w:t></w:r></w:p></w:body>'
|
||||
)
|
||||
|
||||
@@ -255,7 +317,7 @@ def testFmtToDocX_ParagraphFormatting(mockGUI):
|
||||
doc.doConvert()
|
||||
doc._pars[-1].toXml(xTest)
|
||||
assert xmlToText(xTest) == (
|
||||
f'<w:body><w:p><w:pPr><w:pStyle w:val="{S_NORM}" /><w:jc w:val="right" /></w:pPr>'
|
||||
'<w:body><w:p><w:pPr><w:pStyle w:val="Normal" /><w:jc w:val="right" /></w:pPr>'
|
||||
'<w:r><w:rPr /><w:t>Hello World</w:t></w:r></w:p></w:body>'
|
||||
)
|
||||
|
||||
@@ -265,7 +327,7 @@ def testFmtToDocX_ParagraphFormatting(mockGUI):
|
||||
doc.doConvert()
|
||||
doc._pars[-1].toXml(xTest)
|
||||
assert xmlToText(xTest) == (
|
||||
f'<w:body><w:p><w:pPr><w:pStyle w:val="{S_NORM}" /><w:jc w:val="center" /></w:pPr>'
|
||||
'<w:body><w:p><w:pPr><w:pStyle w:val="Normal" /><w:jc w:val="center" /></w:pPr>'
|
||||
'<w:r><w:rPr /><w:t>Hello World</w:t></w:r></w:p></w:body>'
|
||||
)
|
||||
|
||||
@@ -275,7 +337,7 @@ def testFmtToDocX_ParagraphFormatting(mockGUI):
|
||||
doc.doConvert()
|
||||
doc._pars[-1].toXml(xTest)
|
||||
assert xmlToText(xTest) == (
|
||||
f'<w:body><w:p><w:pPr><w:pStyle w:val="{S_NORM}" /><w:jc w:val="both" /></w:pPr>'
|
||||
'<w:body><w:p><w:pPr><w:pStyle w:val="Normal" /><w:jc w:val="both" /></w:pPr>'
|
||||
'<w:r><w:rPr /><w:t>Hello World</w:t></w:r></w:p></w:body>'
|
||||
)
|
||||
|
||||
@@ -285,7 +347,7 @@ def testFmtToDocX_ParagraphFormatting(mockGUI):
|
||||
doc.doConvert()
|
||||
doc._pars[-1].toXml(xTest)
|
||||
assert xmlToText(xTest) == (
|
||||
f'<w:body><w:p><w:pPr><w:pStyle w:val="{S_NORM}" /></w:pPr>'
|
||||
'<w:body><w:p><w:pPr><w:pStyle w:val="Normal" /></w:pPr>'
|
||||
'<w:r><w:br w:type="page" /></w:r>'
|
||||
'<w:r><w:rPr /><w:t>Hello World</w:t></w:r>'
|
||||
'</w:p></w:body>'
|
||||
@@ -297,7 +359,7 @@ def testFmtToDocX_ParagraphFormatting(mockGUI):
|
||||
doc.doConvert()
|
||||
doc._pars[-1].toXml(xTest)
|
||||
assert xmlToText(xTest) == (
|
||||
f'<w:body><w:p><w:pPr><w:pStyle w:val="{S_NORM}" /></w:pPr>'
|
||||
'<w:body><w:p><w:pPr><w:pStyle w:val="Normal" /></w:pPr>'
|
||||
'<w:r><w:rPr /><w:t>Hello World</w:t></w:r>'
|
||||
'<w:r><w:br w:type="page" /></w:r>'
|
||||
'</w:p></w:body>'
|
||||
@@ -309,7 +371,7 @@ def testFmtToDocX_ParagraphFormatting(mockGUI):
|
||||
doc.doConvert()
|
||||
doc._pars[-1].toXml(xTest)
|
||||
assert xmlToText(xTest) == (
|
||||
f'<w:body><w:p><w:pPr><w:pStyle w:val="{S_NORM}" />'
|
||||
'<w:body><w:p><w:pPr><w:pStyle w:val="Normal" />'
|
||||
'<w:spacing w:before="0" w:after="0" w:line="252" /></w:pPr>'
|
||||
'<w:r><w:rPr /><w:t>Hello World</w:t></w:r></w:p></w:body>'
|
||||
)
|
||||
@@ -320,7 +382,7 @@ def testFmtToDocX_ParagraphFormatting(mockGUI):
|
||||
doc.doConvert()
|
||||
doc._pars[-1].toXml(xTest)
|
||||
assert xmlToText(xTest) == (
|
||||
f'<w:body><w:p><w:pPr><w:pStyle w:val="{S_NORM}" />'
|
||||
'<w:body><w:p><w:pPr><w:pStyle w:val="Normal" />'
|
||||
'<w:ind w:left="880" w:right="880" /></w:pPr>'
|
||||
'<w:r><w:rPr /><w:t>Hello World</w:t></w:r></w:p></w:body>'
|
||||
)
|
||||
@@ -331,7 +393,7 @@ def testFmtToDocX_ParagraphFormatting(mockGUI):
|
||||
doc.doConvert()
|
||||
doc._pars[-1].toXml(xTest)
|
||||
assert xmlToText(xTest) == (
|
||||
f'<w:body><w:p><w:pPr><w:pStyle w:val="{S_NORM}" />'
|
||||
'<w:body><w:p><w:pPr><w:pStyle w:val="Normal" />'
|
||||
'<w:ind w:firstLine="308" /></w:pPr>'
|
||||
'<w:r><w:rPr /><w:t>Hello World</w:t></w:r></w:p></w:body>'
|
||||
)
|
||||
@@ -351,7 +413,7 @@ def testFmtToDocX_TextFormatting(mockGUI):
|
||||
doc.doConvert()
|
||||
doc._pars[-1].toXml(xTest)
|
||||
assert xmlToText(xTest) == (
|
||||
f'<w:body><w:p><w:pPr><w:pStyle w:val="{S_NORM}" /></w:pPr>'
|
||||
'<w:body><w:p><w:pPr><w:pStyle w:val="Normal" /></w:pPr>'
|
||||
'<w:r><w:rPr /><w:t xml:space="preserve">Text </w:t></w:r>'
|
||||
'<w:r><w:rPr><w:b /></w:rPr><w:t>bold</w:t></w:r>'
|
||||
'<w:r><w:rPr /><w:t xml:space="preserve">, </w:t></w:r>'
|
||||
@@ -369,7 +431,7 @@ def testFmtToDocX_TextFormatting(mockGUI):
|
||||
doc.doConvert()
|
||||
doc._pars[-1].toXml(xTest)
|
||||
assert xmlToText(xTest) == (
|
||||
f'<w:body><w:p><w:pPr><w:pStyle w:val="{S_NORM}" /></w:pPr>'
|
||||
'<w:body><w:p><w:pPr><w:pStyle w:val="Normal" /></w:pPr>'
|
||||
'<w:r><w:rPr /><w:t xml:space="preserve">Some </w:t></w:r>'
|
||||
'<w:r><w:rPr><w:strike /></w:rPr><w:t xml:space="preserve">nested </w:t></w:r>'
|
||||
'<w:r><w:rPr><w:b /><w:strike /></w:rPr><w:t>bold</w:t></w:r>'
|
||||
@@ -389,7 +451,7 @@ def testFmtToDocX_TextFormatting(mockGUI):
|
||||
doc.doConvert()
|
||||
doc._pars[-1].toXml(xTest)
|
||||
assert xmlToText(xTest) == (
|
||||
f'<w:body><w:p><w:pPr><w:pStyle w:val="{S_NORM}" /></w:pPr>'
|
||||
'<w:body><w:p><w:pPr><w:pStyle w:val="Normal" /></w:pPr>'
|
||||
'<w:r><w:rPr /><w:t>Some super</w:t></w:r>'
|
||||
'<w:r><w:rPr><w:vertAlign w:val="superscript" /></w:rPr><w:t>script</w:t></w:r>'
|
||||
'<w:r><w:rPr /><w:t xml:space="preserve"> and sub</w:t></w:r>'
|
||||
@@ -405,7 +467,7 @@ def testFmtToDocX_TextFormatting(mockGUI):
|
||||
doc.doConvert()
|
||||
doc._pars[-1].toXml(xTest)
|
||||
assert xmlToText(xTest) == (
|
||||
f'<w:body><w:p><w:pPr><w:pStyle w:val="{S_NORM}" /></w:pPr>'
|
||||
'<w:body><w:p><w:pPr><w:pStyle w:val="Normal" /></w:pPr>'
|
||||
'<w:r><w:rPr /><w:t xml:space="preserve">Some </w:t></w:r>'
|
||||
'<w:r><w:rPr><w:u w:val="single" /></w:rPr>'
|
||||
'<w:t xml:space="preserve">underlined and </w:t></w:r>'
|
||||
@@ -422,7 +484,7 @@ def testFmtToDocX_TextFormatting(mockGUI):
|
||||
doc.doConvert()
|
||||
doc._pars[-1].toXml(xTest)
|
||||
assert xmlToText(xTest) == (
|
||||
f'<w:body><w:p><w:pPr><w:pStyle w:val="{S_NORM}" /></w:pPr>'
|
||||
'<w:body><w:p><w:pPr><w:pStyle w:val="Normal" /></w:pPr>'
|
||||
'<w:r><w:rPr /><w:t>Some text.</w:t><w:br /><w:t>Next line</w:t></w:r>'
|
||||
'</w:p></w:body>'
|
||||
)
|
||||
@@ -434,7 +496,7 @@ def testFmtToDocX_TextFormatting(mockGUI):
|
||||
doc.doConvert()
|
||||
doc._pars[-1].toXml(xTest)
|
||||
assert xmlToText(xTest) == (
|
||||
f'<w:body><w:p><w:pPr><w:pStyle w:val="{S_NORM}" /></w:pPr>'
|
||||
'<w:body><w:p><w:pPr><w:pStyle w:val="Normal" /></w:pPr>'
|
||||
'<w:r><w:rPr /><w:tab /><w:t>Item 1</w:t><w:tab /><w:t>Item 2</w:t></w:r>'
|
||||
'</w:p></w:body>'
|
||||
)
|
||||
@@ -446,7 +508,7 @@ def testFmtToDocX_TextFormatting(mockGUI):
|
||||
doc.doConvert()
|
||||
doc._pars[-1].toXml(xTest)
|
||||
assert xmlToText(xTest) == (
|
||||
f'<w:body><w:p><w:pPr><w:pStyle w:val="{S_NORM}" /></w:pPr>'
|
||||
'<w:body><w:p><w:pPr><w:pStyle w:val="Normal" /></w:pPr>'
|
||||
'<w:r><w:rPr /><w:t xml:space="preserve">Some </w:t></w:r>'
|
||||
'<w:r><w:rPr><w:b /></w:rPr><w:t>bold</w:t><w:tab /><w:t>text</w:t></w:r>'
|
||||
'</w:p></w:body>'
|
||||
@@ -474,7 +536,7 @@ def testFmtToDocX_Footnotes(mockGUI):
|
||||
doc.doConvert()
|
||||
doc._pars[-1].toXml(xTest)
|
||||
assert xmlToText(xTest) == (
|
||||
f'<w:body><w:p><w:pPr><w:pStyle w:val="{S_NORM}" /></w:pPr>'
|
||||
'<w:body><w:p><w:pPr><w:pStyle w:val="Normal" /></w:pPr>'
|
||||
'<w:r><w:rPr /><w:t>Text with one</w:t></w:r>'
|
||||
'<w:r><w:rPr><w:vertAlign w:val="superscript" /></w:rPr>'
|
||||
'<w:footnoteReference w:id="1" /></w:r>'
|
||||
@@ -493,11 +555,11 @@ def testFmtToDocX_Footnotes(mockGUI):
|
||||
doc._footnotesXml()
|
||||
assert xmlToText(doc._files["footnotes.xml"].xml) == (
|
||||
'<w:footnotes>'
|
||||
f'<w:footnote w:id="1"><w:p><w:pPr><w:pStyle w:val="{S_FNOTE}" /></w:pPr>'
|
||||
'<w:footnote w:id="1"><w:p><w:pPr><w:pStyle w:val="FootnoteText" /></w:pPr>'
|
||||
'<w:r><w:rPr /><w:t>Footnote text A.</w:t></w:r></w:p></w:footnote>'
|
||||
f'<w:footnote w:id="2"><w:p><w:pPr><w:pStyle w:val="{S_FNOTE}" /></w:pPr>'
|
||||
'<w:footnote w:id="2"><w:p><w:pPr><w:pStyle w:val="FootnoteText" /></w:pPr>'
|
||||
'<w:r><w:rPr /><w:t>Another footnote.</w:t></w:r></w:p></w:footnote>'
|
||||
f'<w:footnote w:id="3"><w:p><w:pPr><w:pStyle w:val="{S_FNOTE}" /></w:pPr>'
|
||||
'<w:footnote w:id="3"><w:p><w:pPr><w:pStyle w:val="FootnoteText" /></w:pPr>'
|
||||
'<w:r><w:rPr /><w:t>Again?</w:t></w:r></w:p></w:footnote>'
|
||||
'</w:footnotes>'
|
||||
)
|
||||
|
||||
@@ -25,6 +25,7 @@ import json
|
||||
import pytest
|
||||
|
||||
from novelwriter import CONFIG
|
||||
from novelwriter.constants import nwHeadFmt
|
||||
from novelwriter.core.project import NWProject
|
||||
from novelwriter.formats.shared import BlockFmt, BlockTyp
|
||||
from novelwriter.formats.tohtml import ToHtml
|
||||
@@ -44,32 +45,35 @@ def testFmtToHtml_ConvertHeaders(mockGUI):
|
||||
html._isFirst = True
|
||||
|
||||
# Header 1
|
||||
html._text = "# Partition\n"
|
||||
html._text = "# Title\n"
|
||||
html.setPartitionFormat(f"Part{nwHeadFmt.BR}{nwHeadFmt.TITLE}")
|
||||
html.tokenizeText()
|
||||
html.doConvert()
|
||||
assert html._pages[-1] == (
|
||||
"<h1 class='title' style='text-align: center;'>Partition</h1>\n"
|
||||
"<h1 class='title' style='text-align: center;'>Part<br>Title</h1>\n"
|
||||
)
|
||||
|
||||
# Header 2
|
||||
html._text = "## Chapter Title\n"
|
||||
html._text = "## Title\n"
|
||||
html.setChapterFormat(f"Chapter {nwHeadFmt.CH_NUM}{nwHeadFmt.BR}{nwHeadFmt.TITLE}")
|
||||
html.tokenizeText()
|
||||
html.doConvert()
|
||||
assert html._pages[-1] == (
|
||||
"<h1 style='page-break-before: always;'>Chapter Title</h1>\n"
|
||||
"<h1 style='page-break-before: always;'>Chapter 1<br>Title</h1>\n"
|
||||
)
|
||||
|
||||
# Header 3
|
||||
html._text = "### Scene Title\n"
|
||||
html._text = "### Title\n"
|
||||
html.setSceneFormat(f"Scene {nwHeadFmt.SC_ABS}{nwHeadFmt.BR}{nwHeadFmt.TITLE}")
|
||||
html.tokenizeText()
|
||||
html.doConvert()
|
||||
assert html._pages[-1] == "<h2>Scene Title</h2>\n"
|
||||
assert html._pages[-1] == "<h2>Scene 1<br>Title</h2>\n"
|
||||
|
||||
# Header 4
|
||||
html._text = "#### Section Title\n"
|
||||
html._text = "#### Title\n"
|
||||
html.tokenizeText()
|
||||
html.doConvert()
|
||||
assert html._pages[-1] == "<h3>Section Title</h3>\n"
|
||||
assert html._pages[-1] == "<h3>Title</h3>\n"
|
||||
|
||||
# Title
|
||||
html._text = "#! Title\n"
|
||||
|
||||
@@ -421,8 +421,8 @@ def testFmtToken_HeaderFormat(mockGUI):
|
||||
|
||||
|
||||
@pytest.mark.core
|
||||
def testFmtToken_HeaderStyle(mockGUI):
|
||||
"""Test the styling of headers in the Tokenizer class."""
|
||||
def testFmtToken_HeaderStyleNone(mockGUI):
|
||||
"""Test header styling disabled."""
|
||||
project = NWProject()
|
||||
tokens = BareTokenizer(project)
|
||||
|
||||
@@ -432,13 +432,12 @@ def testFmtToken_HeaderStyle(mockGUI):
|
||||
tokens.tokenizeText()
|
||||
return tokens._blocks[0][4]
|
||||
|
||||
# No Styles
|
||||
# =========
|
||||
|
||||
tokens.setTitleStyle(False, False)
|
||||
tokens.setPartitionStyle(False, False)
|
||||
tokens.setChapterStyle(False, False)
|
||||
tokens.setSceneStyle(False, False)
|
||||
|
||||
assert tokens._titleStyle == BlockFmt.NONE
|
||||
assert tokens._partStyle == BlockFmt.NONE
|
||||
assert tokens._chapterStyle == BlockFmt.NONE
|
||||
assert tokens._sceneStyle == BlockFmt.NONE
|
||||
@@ -451,7 +450,7 @@ def testFmtToken_HeaderStyle(mockGUI):
|
||||
assert processStyle("## Chapter\n", False) == BlockFmt.NONE
|
||||
assert processStyle("### Scene\n", False) == BlockFmt.NONE
|
||||
assert processStyle("#### Section\n", False) == BlockFmt.NONE
|
||||
assert processStyle("#! My Novel\n", False) == BlockFmt.CENTRE | BlockFmt.PBB
|
||||
assert processStyle("#! My Novel\n", False) == BlockFmt.NONE
|
||||
assert processStyle("##! Prologue\n", False) == BlockFmt.NONE
|
||||
|
||||
# First Document is True
|
||||
@@ -459,7 +458,7 @@ def testFmtToken_HeaderStyle(mockGUI):
|
||||
assert processStyle("## Chapter\n", True) == BlockFmt.NONE
|
||||
assert processStyle("### Scene\n", True) == BlockFmt.NONE
|
||||
assert processStyle("#### Section\n", True) == BlockFmt.NONE
|
||||
assert processStyle("#! My Novel\n", True) == BlockFmt.CENTRE
|
||||
assert processStyle("#! My Novel\n", True) == BlockFmt.NONE
|
||||
assert processStyle("##! Prologue\n", True) == BlockFmt.NONE
|
||||
|
||||
# Note Docs
|
||||
@@ -470,7 +469,7 @@ def testFmtToken_HeaderStyle(mockGUI):
|
||||
assert processStyle("## Chapter\n", False) == BlockFmt.NONE
|
||||
assert processStyle("### Scene\n", False) == BlockFmt.NONE
|
||||
assert processStyle("#### Section\n", False) == BlockFmt.NONE
|
||||
assert processStyle("#! My Novel\n", False) == BlockFmt.CENTRE | BlockFmt.PBB
|
||||
assert processStyle("#! My Novel\n", False) == BlockFmt.NONE
|
||||
assert processStyle("##! Prologue\n", False) == BlockFmt.NONE
|
||||
|
||||
# First Document is True
|
||||
@@ -478,16 +477,28 @@ def testFmtToken_HeaderStyle(mockGUI):
|
||||
assert processStyle("## Chapter\n", True) == BlockFmt.NONE
|
||||
assert processStyle("### Scene\n", True) == BlockFmt.NONE
|
||||
assert processStyle("#### Section\n", True) == BlockFmt.NONE
|
||||
assert processStyle("#! My Novel\n", True) == BlockFmt.CENTRE
|
||||
assert processStyle("#! My Novel\n", True) == BlockFmt.NONE
|
||||
assert processStyle("##! Prologue\n", True) == BlockFmt.NONE
|
||||
|
||||
# Center Headers
|
||||
# ==============
|
||||
|
||||
@pytest.mark.core
|
||||
def testFmtToken_HeaderStyleCenter(mockGUI):
|
||||
"""Test header styling centred."""
|
||||
project = NWProject()
|
||||
tokens = BareTokenizer(project)
|
||||
|
||||
def processStyle(text: str, first: bool) -> BlockFmt:
|
||||
tokens._text = text
|
||||
tokens._isFirst = first
|
||||
tokens.tokenizeText()
|
||||
return tokens._blocks[0][4]
|
||||
|
||||
tokens.setTitleStyle(True, False)
|
||||
tokens.setPartitionStyle(True, False)
|
||||
tokens.setChapterStyle(True, False)
|
||||
tokens.setSceneStyle(True, False)
|
||||
|
||||
assert tokens._titleStyle == BlockFmt.CENTRE
|
||||
assert tokens._partStyle == BlockFmt.CENTRE
|
||||
assert tokens._chapterStyle == BlockFmt.CENTRE
|
||||
assert tokens._sceneStyle == BlockFmt.CENTRE
|
||||
@@ -500,7 +511,7 @@ def testFmtToken_HeaderStyle(mockGUI):
|
||||
assert processStyle("## Chapter\n", False) == BlockFmt.CENTRE
|
||||
assert processStyle("### Scene\n", False) == BlockFmt.CENTRE
|
||||
assert processStyle("#### Section\n", False) == BlockFmt.NONE
|
||||
assert processStyle("#! My Novel\n", False) == BlockFmt.CENTRE | BlockFmt.PBB
|
||||
assert processStyle("#! My Novel\n", False) == BlockFmt.CENTRE
|
||||
assert processStyle("##! Prologue\n", False) == BlockFmt.CENTRE
|
||||
|
||||
# First Document is True
|
||||
@@ -519,7 +530,7 @@ def testFmtToken_HeaderStyle(mockGUI):
|
||||
assert processStyle("## Chapter\n", False) == BlockFmt.NONE
|
||||
assert processStyle("### Scene\n", False) == BlockFmt.NONE
|
||||
assert processStyle("#### Section\n", False) == BlockFmt.NONE
|
||||
assert processStyle("#! My Novel\n", False) == BlockFmt.CENTRE | BlockFmt.PBB
|
||||
assert processStyle("#! My Novel\n", False) == BlockFmt.CENTRE
|
||||
assert processStyle("##! Prologue\n", False) == BlockFmt.NONE
|
||||
|
||||
# First Document is True
|
||||
@@ -530,13 +541,25 @@ def testFmtToken_HeaderStyle(mockGUI):
|
||||
assert processStyle("#! My Novel\n", True) == BlockFmt.CENTRE
|
||||
assert processStyle("##! Prologue\n", True) == BlockFmt.NONE
|
||||
|
||||
# Page Break Headers
|
||||
# ==================
|
||||
|
||||
@pytest.mark.core
|
||||
def testFmtToken_HeaderStylePageBreak(mockGUI):
|
||||
"""Test header styling page break."""
|
||||
project = NWProject()
|
||||
tokens = BareTokenizer(project)
|
||||
|
||||
def processStyle(text: str, first: bool) -> BlockFmt:
|
||||
tokens._text = text
|
||||
tokens._isFirst = first
|
||||
tokens.tokenizeText()
|
||||
return tokens._blocks[0][4]
|
||||
|
||||
tokens.setTitleStyle(False, True)
|
||||
tokens.setPartitionStyle(False, True)
|
||||
tokens.setChapterStyle(False, True)
|
||||
tokens.setSceneStyle(False, True)
|
||||
|
||||
assert tokens._titleStyle == BlockFmt.PBB
|
||||
assert tokens._partStyle == BlockFmt.PBB
|
||||
assert tokens._chapterStyle == BlockFmt.PBB
|
||||
assert tokens._sceneStyle == BlockFmt.PBB
|
||||
@@ -549,7 +572,7 @@ def testFmtToken_HeaderStyle(mockGUI):
|
||||
assert processStyle("## Chapter\n", False) == BlockFmt.PBB
|
||||
assert processStyle("### Scene\n", False) == BlockFmt.PBB
|
||||
assert processStyle("#### Section\n", False) == BlockFmt.NONE
|
||||
assert processStyle("#! My Novel\n", False) == BlockFmt.CENTRE | BlockFmt.PBB
|
||||
assert processStyle("#! My Novel\n", False) == BlockFmt.PBB
|
||||
assert processStyle("##! Prologue\n", False) == BlockFmt.PBB
|
||||
|
||||
# First Document is True
|
||||
@@ -557,7 +580,7 @@ def testFmtToken_HeaderStyle(mockGUI):
|
||||
assert processStyle("## Chapter\n", True) == BlockFmt.NONE
|
||||
assert processStyle("### Scene\n", True) == BlockFmt.NONE
|
||||
assert processStyle("#### Section\n", True) == BlockFmt.NONE
|
||||
assert processStyle("#! My Novel\n", True) == BlockFmt.CENTRE
|
||||
assert processStyle("#! My Novel\n", True) == BlockFmt.NONE
|
||||
assert processStyle("##! Prologue\n", True) == BlockFmt.NONE
|
||||
|
||||
# Note Docs
|
||||
@@ -568,7 +591,7 @@ def testFmtToken_HeaderStyle(mockGUI):
|
||||
assert processStyle("## Chapter\n", False) == BlockFmt.NONE
|
||||
assert processStyle("### Scene\n", False) == BlockFmt.NONE
|
||||
assert processStyle("#### Section\n", False) == BlockFmt.NONE
|
||||
assert processStyle("#! My Novel\n", False) == BlockFmt.CENTRE | BlockFmt.PBB
|
||||
assert processStyle("#! My Novel\n", False) == BlockFmt.PBB
|
||||
assert processStyle("##! Prologue\n", False) == BlockFmt.NONE
|
||||
|
||||
# First Document is True
|
||||
@@ -576,16 +599,28 @@ def testFmtToken_HeaderStyle(mockGUI):
|
||||
assert processStyle("## Chapter\n", True) == BlockFmt.NONE
|
||||
assert processStyle("### Scene\n", True) == BlockFmt.NONE
|
||||
assert processStyle("#### Section\n", True) == BlockFmt.NONE
|
||||
assert processStyle("#! My Novel\n", True) == BlockFmt.CENTRE
|
||||
assert processStyle("#! My Novel\n", True) == BlockFmt.NONE
|
||||
assert processStyle("##! Prologue\n", True) == BlockFmt.NONE
|
||||
|
||||
# Page Break and Centre Headers
|
||||
# =============================
|
||||
|
||||
@pytest.mark.core
|
||||
def testFmtToken_HeaderStylePageBreakCenter(mockGUI):
|
||||
"""Test header styling page break and centred."""
|
||||
project = NWProject()
|
||||
tokens = BareTokenizer(project)
|
||||
|
||||
def processStyle(text: str, first: bool) -> BlockFmt:
|
||||
tokens._text = text
|
||||
tokens._isFirst = first
|
||||
tokens.tokenizeText()
|
||||
return tokens._blocks[0][4]
|
||||
|
||||
tokens.setTitleStyle(True, True)
|
||||
tokens.setPartitionStyle(True, True)
|
||||
tokens.setChapterStyle(True, True)
|
||||
tokens.setSceneStyle(True, True)
|
||||
|
||||
assert tokens._titleStyle == BlockFmt.CENTRE | BlockFmt.PBB
|
||||
assert tokens._partStyle == BlockFmt.CENTRE | BlockFmt.PBB
|
||||
assert tokens._chapterStyle == BlockFmt.CENTRE | BlockFmt.PBB
|
||||
assert tokens._sceneStyle == BlockFmt.CENTRE | BlockFmt.PBB
|
||||
@@ -628,15 +663,46 @@ def testFmtToken_HeaderStyle(mockGUI):
|
||||
assert processStyle("#! My Novel\n", True) == BlockFmt.CENTRE
|
||||
assert processStyle("##! Prologue\n", True) == BlockFmt.NONE
|
||||
|
||||
# Check Separation
|
||||
# ================
|
||||
|
||||
@pytest.mark.core
|
||||
def testFmtToken_HeaderStyleSeparation(mockGUI):
|
||||
"""Test header styling separation."""
|
||||
project = NWProject()
|
||||
tokens = BareTokenizer(project)
|
||||
|
||||
def processStyle(text: str, first: bool) -> BlockFmt:
|
||||
tokens._text = text
|
||||
tokens._isFirst = first
|
||||
tokens.tokenizeText()
|
||||
return tokens._blocks[0][4]
|
||||
|
||||
tokens._isNovel = True
|
||||
|
||||
# Title Styles
|
||||
tokens.setTitleStyle(True, True)
|
||||
tokens.setPartitionStyle(False, False)
|
||||
tokens.setChapterStyle(False, False)
|
||||
tokens.setSceneStyle(False, False)
|
||||
|
||||
assert tokens._titleStyle == BlockFmt.CENTRE | BlockFmt.PBB
|
||||
assert tokens._partStyle == BlockFmt.NONE
|
||||
assert tokens._chapterStyle == BlockFmt.NONE
|
||||
assert tokens._sceneStyle == BlockFmt.NONE
|
||||
|
||||
assert processStyle("# Title\n", False) == BlockFmt.NONE
|
||||
assert processStyle("## Chapter\n", False) == BlockFmt.NONE
|
||||
assert processStyle("### Scene\n", False) == BlockFmt.NONE
|
||||
assert processStyle("#### Section\n", False) == BlockFmt.NONE
|
||||
assert processStyle("#! My Novel\n", False) == BlockFmt.CENTRE | BlockFmt.PBB
|
||||
assert processStyle("##! Prologue\n", False) == BlockFmt.NONE
|
||||
|
||||
# Partition Styles
|
||||
tokens.setTitleStyle(False, False)
|
||||
tokens.setPartitionStyle(True, True)
|
||||
tokens.setChapterStyle(False, False)
|
||||
tokens.setSceneStyle(False, False)
|
||||
|
||||
assert tokens._titleStyle == BlockFmt.NONE
|
||||
assert tokens._partStyle == BlockFmt.CENTRE | BlockFmt.PBB
|
||||
assert tokens._chapterStyle == BlockFmt.NONE
|
||||
assert tokens._sceneStyle == BlockFmt.NONE
|
||||
@@ -645,14 +711,16 @@ def testFmtToken_HeaderStyle(mockGUI):
|
||||
assert processStyle("## Chapter\n", False) == BlockFmt.NONE
|
||||
assert processStyle("### Scene\n", False) == BlockFmt.NONE
|
||||
assert processStyle("#### Section\n", False) == BlockFmt.NONE
|
||||
assert processStyle("#! My Novel\n", False) == BlockFmt.CENTRE | BlockFmt.PBB
|
||||
assert processStyle("#! My Novel\n", False) == BlockFmt.NONE
|
||||
assert processStyle("##! Prologue\n", False) == BlockFmt.NONE
|
||||
|
||||
# Chapter Styles
|
||||
tokens.setTitleStyle(False, False)
|
||||
tokens.setPartitionStyle(False, False)
|
||||
tokens.setChapterStyle(True, True)
|
||||
tokens.setSceneStyle(False, False)
|
||||
|
||||
assert tokens._titleStyle == BlockFmt.NONE
|
||||
assert tokens._partStyle == BlockFmt.NONE
|
||||
assert tokens._chapterStyle == BlockFmt.CENTRE | BlockFmt.PBB
|
||||
assert tokens._sceneStyle == BlockFmt.NONE
|
||||
@@ -661,14 +729,16 @@ def testFmtToken_HeaderStyle(mockGUI):
|
||||
assert processStyle("## Chapter\n", False) == BlockFmt.CENTRE | BlockFmt.PBB
|
||||
assert processStyle("### Scene\n", False) == BlockFmt.NONE
|
||||
assert processStyle("#### Section\n", False) == BlockFmt.NONE
|
||||
assert processStyle("#! My Novel\n", False) == BlockFmt.CENTRE | BlockFmt.PBB
|
||||
assert processStyle("#! My Novel\n", False) == BlockFmt.NONE
|
||||
assert processStyle("##! Prologue\n", False) == BlockFmt.CENTRE | BlockFmt.PBB
|
||||
|
||||
# Scene Styles
|
||||
tokens.setTitleStyle(False, False)
|
||||
tokens.setPartitionStyle(False, False)
|
||||
tokens.setChapterStyle(False, False)
|
||||
tokens.setSceneStyle(True, True)
|
||||
|
||||
assert tokens._titleStyle == BlockFmt.NONE
|
||||
assert tokens._partStyle == BlockFmt.NONE
|
||||
assert tokens._chapterStyle == BlockFmt.NONE
|
||||
assert tokens._sceneStyle == BlockFmt.CENTRE | BlockFmt.PBB
|
||||
@@ -677,7 +747,7 @@ def testFmtToken_HeaderStyle(mockGUI):
|
||||
assert processStyle("## Chapter\n", False) == BlockFmt.NONE
|
||||
assert processStyle("### Scene\n", False) == BlockFmt.CENTRE | BlockFmt.PBB
|
||||
assert processStyle("#### Section\n", False) == BlockFmt.NONE
|
||||
assert processStyle("#! My Novel\n", False) == BlockFmt.CENTRE | BlockFmt.PBB
|
||||
assert processStyle("#! My Novel\n", False) == BlockFmt.NONE
|
||||
assert processStyle("##! Prologue\n", False) == BlockFmt.NONE
|
||||
|
||||
|
||||
@@ -842,13 +912,13 @@ def testFmtToken_MarginFormat(mockGUI):
|
||||
]
|
||||
assert tokens._raw[-1] == (
|
||||
"Some regular text\n\n"
|
||||
"Some left-aligned text\n\n"
|
||||
"Some right-aligned text\n\n"
|
||||
"Some centered text\n\n"
|
||||
"Left-indented block\n\n"
|
||||
"Right-indented block\n\n"
|
||||
"Double-indented block\n\n"
|
||||
"Right-indent, right-aligned\n\n\n"
|
||||
"Some left-aligned text <<\n\n"
|
||||
">> Some right-aligned text\n\n"
|
||||
">> Some centered text <<\n\n"
|
||||
"> Left-indented block\n\n"
|
||||
"Right-indented block <\n\n"
|
||||
"> Double-indented block <\n\n"
|
||||
">> Right-indent, right-aligned <\n\n\n"
|
||||
)
|
||||
|
||||
|
||||
@@ -1056,72 +1126,121 @@ def testFmtToken_TextFormat(mockGUI):
|
||||
tokens._text = "Some **bolded text** on this lines\n"
|
||||
tokens.tokenizeText()
|
||||
assert tokens._blocks == [(
|
||||
BlockTyp.TEXT, "", "Some bolded text on this lines",
|
||||
[
|
||||
BlockTyp.TEXT, "", "Some bolded text on this lines", [
|
||||
(5, TextFmt.B_B, ""),
|
||||
(16, TextFmt.B_E, ""),
|
||||
],
|
||||
BlockFmt.NONE
|
||||
], BlockFmt.NONE
|
||||
)]
|
||||
assert tokens._raw[-1] == "Some **bolded text** on this lines\n\n"
|
||||
|
||||
tokens._text = "Some _italic text_ on this lines\n"
|
||||
tokens.tokenizeText()
|
||||
assert tokens._blocks == [(
|
||||
BlockTyp.TEXT, "", "Some italic text on this lines",
|
||||
[
|
||||
BlockTyp.TEXT, "", "Some italic text on this lines", [
|
||||
(5, TextFmt.I_B, ""),
|
||||
(16, TextFmt.I_E, ""),
|
||||
],
|
||||
BlockFmt.NONE
|
||||
], BlockFmt.NONE
|
||||
)]
|
||||
assert tokens._raw[-1] == "Some _italic text_ on this lines\n\n"
|
||||
|
||||
tokens._text = "Some **_bold italic text_** on this lines\n"
|
||||
tokens.tokenizeText()
|
||||
assert tokens._blocks == [(
|
||||
BlockTyp.TEXT, "", "Some bold italic text on this lines",
|
||||
[
|
||||
BlockTyp.TEXT, "", "Some bold italic text on this lines", [
|
||||
(5, TextFmt.B_B, ""),
|
||||
(5, TextFmt.I_B, ""),
|
||||
(21, TextFmt.I_E, ""),
|
||||
(21, TextFmt.B_E, ""),
|
||||
],
|
||||
BlockFmt.NONE
|
||||
], BlockFmt.NONE
|
||||
)]
|
||||
assert tokens._raw[-1] == "Some **_bold italic text_** on this lines\n\n"
|
||||
|
||||
tokens._text = "Some ~~strikethrough text~~ on this lines\n"
|
||||
tokens.tokenizeText()
|
||||
assert tokens._blocks == [(
|
||||
BlockTyp.TEXT, "", "Some strikethrough text on this lines",
|
||||
[
|
||||
BlockTyp.TEXT, "", "Some strikethrough text on this lines", [
|
||||
(5, TextFmt.D_B, ""),
|
||||
(23, TextFmt.D_E, ""),
|
||||
],
|
||||
BlockFmt.NONE
|
||||
], BlockFmt.NONE
|
||||
)]
|
||||
assert tokens._raw[-1] == "Some ~~strikethrough text~~ on this lines\n\n"
|
||||
|
||||
tokens._text = "Some **nested bold and _italic_ and ~~strikethrough~~ text** here\n"
|
||||
tokens.tokenizeText()
|
||||
assert tokens._blocks == [(
|
||||
BlockTyp.TEXT, "", "Some nested bold and italic and strikethrough text here",
|
||||
[
|
||||
BlockTyp.TEXT, "", "Some nested bold and italic and strikethrough text here", [
|
||||
(5, TextFmt.B_B, ""),
|
||||
(21, TextFmt.I_B, ""),
|
||||
(27, TextFmt.I_E, ""),
|
||||
(32, TextFmt.D_B, ""),
|
||||
(45, TextFmt.D_E, ""),
|
||||
(50, TextFmt.B_E, ""),
|
||||
],
|
||||
BlockFmt.NONE
|
||||
], BlockFmt.NONE
|
||||
)]
|
||||
assert tokens._raw[-1] == (
|
||||
"Some **nested bold and _italic_ and ~~strikethrough~~ text** here\n\n"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.core
|
||||
def testFmtToken_LineBreak(mockGUI):
|
||||
"""Test processing of forced line breaks in the Tokenizer class."""
|
||||
project = NWProject()
|
||||
tokens = BareTokenizer(project)
|
||||
tokens._handle = TMH
|
||||
tokens.setComments(True)
|
||||
|
||||
# They are stripped in headers
|
||||
tokens._text = "## Hello[br] World"
|
||||
tokens.tokenizeText()
|
||||
assert tokens._blocks == [
|
||||
(BlockTyp.HEAD2, TM1, "Hello World", [], BlockFmt.NONE)
|
||||
]
|
||||
|
||||
# They are stripped in comments
|
||||
tokens._text = "% Hello[br] World"
|
||||
tokens.tokenizeText()
|
||||
assert tokens._blocks == [(
|
||||
BlockTyp.COMMENT, "", "Comment: Hello World", [
|
||||
(0, TextFmt.B_B, ""), (0, TextFmt.COL_B, "comment"),
|
||||
(8, TextFmt.COL_E, ""), (8, TextFmt.B_E, ""),
|
||||
(9, TextFmt.COL_B, "comment"), (20, TextFmt.COL_E, ""),
|
||||
], BlockFmt.NONE
|
||||
)]
|
||||
|
||||
# They are used in text, with breaks enabled
|
||||
tokens.setKeepLineBreaks(True)
|
||||
tokens._text = "Hello[br]\nWorld"
|
||||
tokens.tokenizeText()
|
||||
assert tokens._blocks == [
|
||||
(BlockTyp.TEXT, "", "Hello\nWorld", [], BlockFmt.NONE)
|
||||
]
|
||||
|
||||
# They are used in text, with breaks disabled
|
||||
tokens.setKeepLineBreaks(False)
|
||||
tokens._text = "Hello[br]\nWorld"
|
||||
tokens.tokenizeText()
|
||||
assert tokens._blocks == [
|
||||
(BlockTyp.TEXT, "", "Hello\nWorld", [], BlockFmt.NONE)
|
||||
]
|
||||
|
||||
# Without forced breaks, they are preserved with breaks enabled
|
||||
tokens.setKeepLineBreaks(True)
|
||||
tokens._text = "Hello\nWorld"
|
||||
tokens.tokenizeText()
|
||||
assert tokens._blocks == [
|
||||
(BlockTyp.TEXT, "", "Hello\nWorld", [], BlockFmt.NONE)
|
||||
]
|
||||
|
||||
# Without forced breaks, they are not preserved with breaks disabled
|
||||
tokens.setKeepLineBreaks(False)
|
||||
tokens._text = "Hello\nWorld"
|
||||
tokens.tokenizeText()
|
||||
assert tokens._blocks == [
|
||||
(BlockTyp.TEXT, "", "Hello World", [], BlockFmt.NONE)
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.core
|
||||
def testFmtToken_Dialogue(mockGUI):
|
||||
"""Test the tokenization of dialogue in the Tokenizer class."""
|
||||
|
||||
@@ -22,6 +22,7 @@ from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from novelwriter.constants import nwHeadFmt
|
||||
from novelwriter.core.project import NWProject
|
||||
from novelwriter.formats.shared import BlockFmt, BlockTyp
|
||||
from novelwriter.formats.tomarkdown import ToMarkdown
|
||||
@@ -37,22 +38,25 @@ def testFmtToMarkdown_ConvertHeaders(mockGUI):
|
||||
md._isFirst = True
|
||||
|
||||
# Header 1
|
||||
md._text = "# Partition\n"
|
||||
md._text = "# Title\n"
|
||||
md.setPartitionFormat(f"Part{nwHeadFmt.BR}{nwHeadFmt.TITLE}")
|
||||
md.tokenizeText()
|
||||
md.doConvert()
|
||||
assert md._pages[-1] == "# Partition\n\n"
|
||||
assert md._pages[-1] == "# Part - Title\n\n"
|
||||
|
||||
# Header 2
|
||||
md._text = "## Chapter Title\n"
|
||||
md._text = "## Title\n"
|
||||
md.setChapterFormat(f"Chapter {nwHeadFmt.CH_NUM}{nwHeadFmt.BR}{nwHeadFmt.TITLE}")
|
||||
md.tokenizeText()
|
||||
md.doConvert()
|
||||
assert md._pages[-1] == "## Chapter Title\n\n"
|
||||
assert md._pages[-1] == "## Chapter 1 - Title\n\n"
|
||||
|
||||
# Header 3
|
||||
md._text = "### Scene Title\n"
|
||||
md._text = "### Title\n"
|
||||
md.setSceneFormat(f"Scene {nwHeadFmt.SC_ABS}{nwHeadFmt.BR}{nwHeadFmt.TITLE}")
|
||||
md.tokenizeText()
|
||||
md.doConvert()
|
||||
assert md._pages[-1] == "### Scene Title\n\n"
|
||||
assert md._pages[-1] == "### Scene 1 - Title\n\n"
|
||||
|
||||
# Header 4
|
||||
md._text = "#### Section Title\n"
|
||||
|
||||
@@ -281,6 +281,7 @@ def testFmtToOdt_ConvertHeaders(mockGUI):
|
||||
|
||||
# Header 1
|
||||
odt._text = "# Title\n"
|
||||
odt.setPartitionFormat(f"Part{nwHeadFmt.BR}{nwHeadFmt.TITLE}")
|
||||
odt.tokenizeText()
|
||||
odt.initDocument()
|
||||
odt.doConvert()
|
||||
@@ -288,12 +289,14 @@ def testFmtToOdt_ConvertHeaders(mockGUI):
|
||||
assert odt.errData == []
|
||||
assert xmlToText(odt._xText) == (
|
||||
'<office:text>'
|
||||
'<text:h text:style-name="P1" text:outline-level="1">Title</text:h>'
|
||||
'<text:h text:style-name="P1" text:outline-level="1">Part'
|
||||
'<text:line-break />Title</text:h>'
|
||||
'</office:text>'
|
||||
)
|
||||
|
||||
# Header 2
|
||||
odt._text = "## Chapter\n"
|
||||
odt._text = "## Title\n"
|
||||
odt.setChapterFormat(f"Chapter {nwHeadFmt.CH_NUM}{nwHeadFmt.BR}{nwHeadFmt.TITLE}")
|
||||
odt.tokenizeText()
|
||||
odt.initDocument()
|
||||
odt.doConvert()
|
||||
@@ -301,12 +304,14 @@ def testFmtToOdt_ConvertHeaders(mockGUI):
|
||||
assert odt.errData == []
|
||||
assert xmlToText(odt._xText) == (
|
||||
'<office:text>'
|
||||
'<text:h text:style-name="P2" text:outline-level="2">Chapter</text:h>'
|
||||
'<text:h text:style-name="P2" text:outline-level="2">Chapter 1'
|
||||
'<text:line-break />Title</text:h>'
|
||||
'</office:text>'
|
||||
)
|
||||
|
||||
# Header 3
|
||||
odt._text = "### Scene\n"
|
||||
odt._text = "### Title\n"
|
||||
odt.setSceneFormat(f"Scene {nwHeadFmt.SC_ABS}{nwHeadFmt.BR}{nwHeadFmt.TITLE}")
|
||||
odt.tokenizeText()
|
||||
odt.initDocument()
|
||||
odt.doConvert()
|
||||
@@ -314,12 +319,13 @@ def testFmtToOdt_ConvertHeaders(mockGUI):
|
||||
assert odt.errData == []
|
||||
assert xmlToText(odt._xText) == (
|
||||
'<office:text>'
|
||||
'<text:h text:style-name="Heading_20_3" text:outline-level="3">Scene</text:h>'
|
||||
'<text:h text:style-name="Heading_20_3" text:outline-level="3">Scene 1'
|
||||
'<text:line-break />Title</text:h>'
|
||||
'</office:text>'
|
||||
)
|
||||
|
||||
# Header 4
|
||||
odt._text = "#### Section\n"
|
||||
odt._text = "#### Title\n"
|
||||
odt.tokenizeText()
|
||||
odt.initDocument()
|
||||
odt.doConvert()
|
||||
@@ -327,7 +333,7 @@ def testFmtToOdt_ConvertHeaders(mockGUI):
|
||||
assert odt.errData == []
|
||||
assert xmlToText(odt._xText) == (
|
||||
'<office:text>'
|
||||
'<text:h text:style-name="Heading_20_4" text:outline-level="4">Section</text:h>'
|
||||
'<text:h text:style-name="Heading_20_4" text:outline-level="4">Title</text:h>'
|
||||
'</office:text>'
|
||||
)
|
||||
|
||||
|
||||
@@ -522,13 +522,18 @@ def testGuiMainMenu_Insert(qtbot, monkeypatch, nwGUI, fncPath, projPath, mockRnd
|
||||
nwGUI.mainMenu.aInsShort.activate(QAction.ActionEvent.Trigger)
|
||||
assert nwGUI.docEditor.getText() == "Stuff\n%Short: \n"
|
||||
|
||||
# Insert Break or Space
|
||||
# =====================
|
||||
# Breaks and Vertical Space
|
||||
# =========================
|
||||
|
||||
nwGUI.docEditor.setPlainText("### Stuff\n")
|
||||
nwGUI.mainMenu.aInsNewPage.activate(QAction.ActionEvent.Trigger)
|
||||
assert nwGUI.docEditor.getText() == "[newpage]\n### Stuff\n"
|
||||
|
||||
nwGUI.docEditor.setPlainText("Line OneLine Two\n")
|
||||
nwGUI.docEditor.setCursorPosition(8)
|
||||
nwGUI.mainMenu.aInsLineBreak.activate(QAction.ActionEvent.Trigger)
|
||||
assert nwGUI.docEditor.getText() == "Line One[br]Line Two\n"
|
||||
|
||||
nwGUI.docEditor.setPlainText("### Stuff\n")
|
||||
nwGUI.mainMenu.aInsVSpaceS.activate(QAction.ActionEvent.Trigger)
|
||||
assert nwGUI.docEditor.getText() == "[vspace]\n### Stuff\n"
|
||||
|
||||
@@ -47,6 +47,7 @@ def testTextCounting_preProcessText():
|
||||
"[vspace:3]\n\n"
|
||||
"[New Page]\n\n"
|
||||
"[footnote:abcd]\n\n"
|
||||
"[br]\n\n"
|
||||
"Dashes\u2013and even longer\u2014dashes.\n\n"
|
||||
)
|
||||
|
||||
@@ -60,7 +61,7 @@ def testTextCounting_preProcessText():
|
||||
"#### Heading Four",
|
||||
"", "", "",
|
||||
"A paragraph.", "",
|
||||
"", "", "", "", "", "",
|
||||
"", "", "", "", "", "", "", "",
|
||||
"Dashes and even longer dashes.", ""
|
||||
]
|
||||
|
||||
@@ -68,7 +69,7 @@ def testTextCounting_preProcessText():
|
||||
assert preProcessText(text, keepHeaders=False) == [
|
||||
"", "", "",
|
||||
"A paragraph.", "",
|
||||
"", "", "", "", "", "",
|
||||
"", "", "", "", "", "", "", "",
|
||||
"Dashes and even longer dashes.", ""
|
||||
]
|
||||
|
||||
|
||||
@@ -198,6 +198,17 @@ def testTextPatterns_ShortcodesPlain():
|
||||
|
||||
assert allMatches(regEx, "one [x]two[/x] three") == []
|
||||
|
||||
# Line Break Substitution
|
||||
# =======================
|
||||
regEx = REGEX_PATTERNS.lineBreak
|
||||
|
||||
assert regEx.sub("\n", "one[br]two") == "one\ntwo"
|
||||
assert regEx.sub("\n", "one[br]\ntwo") == "one\ntwo"
|
||||
assert regEx.sub("\n", "one[br]\n\ntwo") == "one\n\ntwo"
|
||||
assert regEx.sub("\n", "one[BR]two") == "one\ntwo"
|
||||
assert regEx.sub("\n", "one[BR]\ntwo") == "one\ntwo"
|
||||
assert regEx.sub("\n", "one[BR]\n\ntwo") == "one\n\ntwo"
|
||||
|
||||
|
||||
@pytest.mark.core
|
||||
def testTextPatterns_ShortcodesValue():
|
||||
|
||||
Reference in New Issue
Block a user