From 6b01bd7ffeb3480e016e7724ccf2ab2982f64de6 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Thu, 24 Oct 2024 17:47:14 +0200 Subject: [PATCH] Fix title page break issue and add test coverage --- novelwriter/assets/i18n/project_en_GB.json | 2 +- novelwriter/core/buildsettings.py | 2 +- novelwriter/core/docbuild.py | 4 +- novelwriter/formats/tokenizer.py | 44 ++++---- sample/content/53b69b83cdafc.nwd | 13 ++- sample/nwProject.nwx | 12 +- tests/test_formats/test_fmt_tokenizer.py | 122 ++++++++++++++++----- 7 files changed, 139 insertions(+), 60 deletions(-) diff --git a/novelwriter/assets/i18n/project_en_GB.json b/novelwriter/assets/i18n/project_en_GB.json index fe4be646..39d52e6e 100644 --- a/novelwriter/assets/i18n/project_en_GB.json +++ b/novelwriter/assets/i18n/project_en_GB.json @@ -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", diff --git a/novelwriter/core/buildsettings.py b/novelwriter/core/buildsettings.py index c26c94b1..f8cc1c40 100644 --- a/novelwriter/core/buildsettings.py +++ b/novelwriter/core/buildsettings.py @@ -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), diff --git a/novelwriter/core/docbuild.py b/novelwriter/core/docbuild.py index d587a2dd..a2417e8f 100644 --- a/novelwriter/core/docbuild.py +++ b/novelwriter/core/docbuild.py @@ -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"), diff --git a/novelwriter/formats/tokenizer.py b/novelwriter/formats/tokenizer.py index 69a422c8..f341cb1e 100644 --- a/novelwriter/formats/tokenizer.py +++ b/novelwriter/formats/tokenizer.py @@ -452,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") @@ -531,7 +533,7 @@ class Tokenizer(ABC): nHead = 0 breakNext = False - tmpMarkdown = [] + rawText = [] tHandle = self._handle or "" tBlocks: list[T_Block] = [B_EMPTY] for bLine in text.splitlines(): @@ -542,7 +544,7 @@ class Tokenizer(ABC): if not sLine: tBlocks.append(B_EMPTY) if keepRaw: - tmpMarkdown.append("\n") + rawText.append("\n") continue if breakNext: @@ -608,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 @@ -629,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 @@ -665,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 @@ -700,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 @@ -741,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 @@ -771,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 @@ -819,7 +821,7 @@ class Tokenizer(ABC): 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: @@ -835,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 # =========== diff --git a/sample/content/53b69b83cdafc.nwd b/sample/content/53b69b83cdafc.nwd index 9163e10f..f793413a 100644 --- a/sample/content/53b69b83cdafc.nwd +++ b/sample/content/53b69b83cdafc.nwd @@ -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. << \ No newline at end of file +>> It should be the first document of the project. << diff --git a/sample/nwProject.nwx b/sample/nwProject.nwx index 373111cc..53f3a5e7 100644 --- a/sample/nwProject.nwx +++ b/sample/nwProject.nwx @@ -1,6 +1,6 @@ - - + + Sample Project Jane Smith @@ -36,13 +36,13 @@ Main - + Novel - + Title Page @@ -58,7 +58,7 @@ Chapter One - + Making a Scene @@ -66,7 +66,7 @@ Another Scene - + Interlude diff --git a/tests/test_formats/test_fmt_tokenizer.py b/tests/test_formats/test_fmt_tokenizer.py index 115203e3..1a7691e5 100644 --- a/tests/test_formats/test_fmt_tokenizer.py +++ b/tests/test_formats/test_fmt_tokenizer.py @@ -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