From 6dcdd34fe110aba2f27b6ccab375c9bc73668711 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Thu, 10 Apr 2025 21:59:36 +0200 Subject: [PATCH 1/2] Export the entire novel structure from the Outline view --- novelwriter/core/index.py | 8 +++++ novelwriter/gui/outline.py | 72 ++++++++++++++++++++++++++++++++++---- 2 files changed, 74 insertions(+), 6 deletions(-) diff --git a/novelwriter/core/index.py b/novelwriter/core/index.py index 967861bf..0a74cf7e 100644 --- a/novelwriter/core/index.py +++ b/novelwriter/core/index.py @@ -608,6 +608,10 @@ class Index: yield from tItem.items() return + def getStoryKeys(self) -> set[str]: + """Return all story structure keys.""" + return self._itemIndex.allStoryKeys() + def novelStructure( self, rootHandle: str | None = None, activeOnly: bool = True ) -> Iterable[tuple[str, str, str, IndexHeading]]: @@ -956,6 +960,10 @@ class ItemIndex: self._items[tHandle] = IndexNode(self._cache, tHandle, nwItem) return + def allStoryKeys(self) -> set[str]: + """Return all story structure keys.""" + return self._cache.story.copy() + def allItemTags(self, tHandle: str) -> list[str]: """Get all tags set for headings of an item.""" if tHandle in self._items: diff --git a/novelwriter/gui/outline.py b/novelwriter/gui/outline.py index 10a396b4..3937ed53 100644 --- a/novelwriter/gui/outline.py +++ b/novelwriter/gui/outline.py @@ -557,14 +557,11 @@ class GuiOutlineTree(QTreeWidget): )[0]: CONFIG.setLastPath("outline", path) logger.info("Writing CSV file: %s", path) - cols = [col for col in self._treeOrder if not self._colHidden[col]] - order = [self._colIdx[col] for col in cols] with open(path, mode="w", newline="", encoding="utf-8") as csvFile: writer = csv.writer(csvFile, dialect="excel", quoting=csv.QUOTE_ALL) - writer.writerow([trConst(nwLabels.OUTLINE_COLS[col]) for col in cols]) - for i in range(self.topLevelItemCount()): - if item := self.topLevelItem(i): - writer.writerow(item.text(i) for i in order) + writer.writerows( + self._dumpNovelData(self.outlineView.outlineBar.novelValue.handle) + ) return ## @@ -751,6 +748,69 @@ class GuiOutlineTree(QTreeWidget): return + def _dumpNovelData(self, rootHandle: str | None) -> list[list[str | int]]: + """Dump all novel data into a table.""" + sLabel = SHARED.project.localLookup("Story Structure") + sKeys = sorted(SHARED.project.index.getStoryKeys()) + sMatch = [f"story.{k}" for k in sKeys] + sHeaders = [f"{sLabel} ({k})" for k in sKeys] + + data: list[list[str | int]] = [[ + "H", + trConst(nwLabels.OUTLINE_COLS[nwOutline.TITLE]), + trConst(nwLabels.OUTLINE_COLS[nwOutline.LABEL]), + trConst(nwLabels.OUTLINE_COLS[nwOutline.LINE]), + trConst(nwLabels.OUTLINE_COLS[nwOutline.STATUS]), + trConst(nwLabels.OUTLINE_COLS[nwOutline.CCOUNT]), + trConst(nwLabels.OUTLINE_COLS[nwOutline.WCOUNT]), + trConst(nwLabels.OUTLINE_COLS[nwOutline.PCOUNT]), + trConst(nwLabels.OUTLINE_COLS[nwOutline.POV]), + trConst(nwLabels.OUTLINE_COLS[nwOutline.FOCUS]), + trConst(nwLabels.OUTLINE_COLS[nwOutline.CHAR]), + trConst(nwLabels.OUTLINE_COLS[nwOutline.PLOT]), + trConst(nwLabels.OUTLINE_COLS[nwOutline.TIME]), + trConst(nwLabels.OUTLINE_COLS[nwOutline.WORLD]), + trConst(nwLabels.OUTLINE_COLS[nwOutline.OBJECT]), + trConst(nwLabels.OUTLINE_COLS[nwOutline.ENTITY]), + trConst(nwLabels.OUTLINE_COLS[nwOutline.CUSTOM]), + trConst(nwLabels.OUTLINE_COLS[nwOutline.STORY]), + trConst(nwLabels.OUTLINE_COLS[nwOutline.MENTION]), + trConst(nwLabels.OUTLINE_COLS[nwOutline.SYNOP]), + *sHeaders, + ]] + + for _, tHandle, sTitle, novIdx in SHARED.project.index.novelStructure( + rootHandle=rootHandle, activeOnly=True + ): + if novIdx.level != "H0" and (nwItem := SHARED.project.tree[tHandle]): + refs = SHARED.project.index.getReferences(tHandle, sTitle) + story = {k: v for k, v in novIdx.comments.items() if k in sMatch}.values() + data.append([ + novIdx.level, + novIdx.title, + nwItem.itemName, + novIdx.line, + nwItem.getImportStatus()[0], + novIdx.charCount, + novIdx.wordCount, + novIdx.paraCount, + ", ".join(refs[nwKeyWords.POV_KEY]), + ", ".join(refs[nwKeyWords.FOCUS_KEY]), + ", ".join(refs[nwKeyWords.CHAR_KEY]), + ", ".join(refs[nwKeyWords.PLOT_KEY]), + ", ".join(refs[nwKeyWords.TIME_KEY]), + ", ".join(refs[nwKeyWords.WORLD_KEY]), + ", ".join(refs[nwKeyWords.OBJECT_KEY]), + ", ".join(refs[nwKeyWords.ENTITY_KEY]), + ", ".join(refs[nwKeyWords.CUSTOM_KEY]), + ", ".join(refs[nwKeyWords.STORY_KEY]), + ", ".join(refs[nwKeyWords.MENTION_KEY]), + novIdx.synopsis, + *story, + ]) + + return data + class GuiOutlineHeaderMenu(QMenu): From 0728f43d9699d1e67c2d9fd2ca9c0e00b4e4e87d Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Thu, 10 Apr 2025 22:01:25 +0200 Subject: [PATCH 2/2] Update test --- .../reference/guiOutline_Content_outline.csv | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/tests/reference/guiOutline_Content_outline.csv b/tests/reference/guiOutline_Content_outline.csv index 15ca2c46..62edb467 100644 --- a/tests/reference/guiOutline_Content_outline.csv +++ b/tests/reference/guiOutline_Content_outline.csv @@ -1,13 +1,13 @@ -"Title","Document","Words","Pars","POV","Characters","Plot","Locations","Synopsis" -"Lorem Ipsum","Lorem Ipsum","44","5","","","","","" -"Prologue","Prologue","94","2","","","","","Explanation from the lipsum.com website." -"Act One","Act One","6","1","","","","","" -"Chapter One","Chapter One","67","1","Bod","","Main","Europe","Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque at aliquam quam." -"Scene One","Scene One","174","2","Bod","","Main","Europe","Aenean ut placerat velit. Etiam laoreet ullamcorper risus, eget lobortis enim scelerisque non. Suspendisse id maximus nunc, et mollis sapien. Curabitur vel semper sapien, non pulvinar dolor. Etiam finibus nisi vel mi molestie consectetur." -"Scene One, Section Two","Scene One","230","2","","","","","" -"Scene Two","Scene Two","299","3","Bod","","Main","Europe","Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Integer sapien nulla, dictum at lacus a, dignissim consectetur dolor. Nunc vel eleifend lacus, eu dapibus orci." -"Scene Two, Section Two","Scene Two","301","3","","","","","" -"Chapter Two","Chapter Two","70","1","Bod","","Main","Europe","Curabitur a elit posuere, varius ex et, convallis neque. Phasellus sagittis pharetra sem vitae dapibus. Curabitur varius lorem non pulvinar congue." -"Scene Three","Scene Three","439","4","Bod","","Main","Europe","Aenean ut libero ut lectus porttitor rhoncus vel et massa. Nam pretium, nibh et varius vehicula, urna metus blandit eros, euismod pharetra diam diam et libero. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos." -"Scene Four","Scene Four","563","6","Bod","","Main","Europe","Nam tempor blandit magna laoreet aliquet. Vestibulum auctor posuere leo, ac gravida nisi rhoncus varius. Aenean posuere dolor vitae condimentum volutpat. Donec egestas volutpat risus, quis luctus justo." -"Scene Five","Scene Five","543","5","Bod","","Main","Europe","Praesent eget est porta, dictum ante in, egestas risus. Mauris risus mauris, consequat aliquam mauris et, feugiat iaculis ipsum. Aliquam arcu ipsum, fermentum ut arcu sed, lobortis euismod sem. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus." +"H","Title","Document","Line","Status","Chars","Words","Pars","POV","Focus","Characters","Plot","Timeline","Locations","Objects","Entities","Custom","Story","Mentions","Synopsis" +"H1","Lorem Ipsum","Lorem Ipsum","1","Finished","259","44","5","","","","","","","","","","","","" +"H2","Prologue","Prologue","1","Draft","605","94","2","","","","","","","","","","","","Explanation from the lipsum.com website." +"H1","Act One","Act One","1","New","35","6","1","","","","","","","","","","","","" +"H2","Chapter One","Chapter One","1","Draft","419","67","1","Bod","","","Main","","Europe","","","","","","Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque at aliquam quam." +"H3","Scene One","Scene One","1","Finished","1197","174","2","Bod","","","Main","","Europe","","","","","","Aenean ut placerat velit. Etiam laoreet ullamcorper risus, eget lobortis enim scelerisque non. Suspendisse id maximus nunc, et mollis sapien. Curabitur vel semper sapien, non pulvinar dolor. Etiam finibus nisi vel mi molestie consectetur." +"H4","Scene One, Section Two","Scene One","13","Finished","1561","230","2","","","","","","","","","","","","" +"H3","Scene Two","Scene Two","1","Finished","2034","299","3","Bod","","","Main","","Europe","","","","","","Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Integer sapien nulla, dictum at lacus a, dignissim consectetur dolor. Nunc vel eleifend lacus, eu dapibus orci." +"H4","Scene Two, Section Two","Scene Two","15","Finished","2009","301","3","","","","","","","","","","","","" +"H2","Chapter Two","Chapter Two","1","Draft","477","70","1","Bod","","","Main","","Europe","","","","","","Curabitur a elit posuere, varius ex et, convallis neque. Phasellus sagittis pharetra sem vitae dapibus. Curabitur varius lorem non pulvinar congue." +"H3","Scene Three","Scene Three","1","Finished","3006","439","4","Bod","","","Main","","Europe","","","","","","Aenean ut libero ut lectus porttitor rhoncus vel et massa. Nam pretium, nibh et varius vehicula, urna metus blandit eros, euismod pharetra diam diam et libero. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos." +"H3","Scene Four","Scene Four","1","Finished","3839","563","6","Bod","","","Main","","Europe","","","","","","Nam tempor blandit magna laoreet aliquet. Vestibulum auctor posuere leo, ac gravida nisi rhoncus varius. Aenean posuere dolor vitae condimentum volutpat. Donec egestas volutpat risus, quis luctus justo." +"H3","Scene Five","Scene Five","1","Finished","3644","543","5","Bod","","","Main","","Europe","","","","","","Praesent eget est porta, dictum ante in, egestas risus. Mauris risus mauris, consequat aliquam mauris et, feugiat iaculis ipsum. Aliquam arcu ipsum, fermentum ut arcu sed, lobortis euismod sem. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus."