Skip to content

Commit

Permalink
Allow inserting stats in documents (#2073)
Browse files Browse the repository at this point in the history
  • Loading branch information
vkbo authored Oct 29, 2024
2 parents 8725eda + 3d935e2 commit 92d148b
Show file tree
Hide file tree
Showing 57 changed files with 1,086 additions and 653 deletions.
33 changes: 29 additions & 4 deletions novelwriter/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -492,6 +492,10 @@ def jsonEncode(data: dict | list | tuple, n: int = 0, nmax: int = 0) -> str:
return "".join(buffer)


##
# XML Helpers
##

def xmlIndent(tree: ET.Element | ET.ElementTree) -> None:
"""A modified version of the XML indent function in the standard
library. It behaves more closely to how the one from lxml does.
Expand Down Expand Up @@ -535,21 +539,42 @@ def indentChildren(elem: ET.Element, level: int) -> None:
return


def xmlElement(
tag: str,
text: str | int | float | bool | None = None,
*,
attrib: dict | None = None,
tail: str | None = None,
) -> ET.Element:
"""A custom implementation of Element with more arguments."""
xSub = ET.Element(tag, attrib=attrib or {})
if text is not None:
if isinstance(text, bool):
xSub.text = str(text).lower()
else:
xSub.text = str(text)
if tail is not None:
xSub.tail = tail
return xSub


def xmlSubElem(
parent: ET.Element,
tag: str,
text: str | int | float | bool | None = None,
attrib: dict | None = None
*,
attrib: dict | None = None,
tail: str | None = None,
) -> ET.Element:
"""A custom implementation of SubElement that takes text as an
argument.
"""
"""A custom implementation of SubElement with more arguments."""
xSub = ET.SubElement(parent, tag, attrib=attrib or {})
if text is not None:
if isinstance(text, bool):
xSub.text = str(text).lower()
else:
xSub.text = str(text)
if tail is not None:
xSub.tail = tail
return xSub


Expand Down
4 changes: 4 additions & 0 deletions novelwriter/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,10 @@ def __init__(self) -> None:
def hasError(self) -> bool:
return self._hasError

@property
def locale(self) -> QLocale:
return self._dLocale

@property
def recentProjects(self) -> RecentProjects:
return self._recentProjects
Expand Down
68 changes: 62 additions & 6 deletions novelwriter/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ class nwRegEx:
FMT_EB = r"(?<![\w\\])(\*{2})(?![\s\*])(.+?)(?<![\s\\])(\1)(?!\w)"
FMT_ST = r"(?<![\w\\])(~{2})(?![\s~])(.+?)(?<![\s\\])(\1)(?!\w)"
FMT_SC = r"(?i)(?<!\\)(\[(?:b|/b|i|/i|s|/s|u|/u|m|/m|sup|/sup|sub|/sub|br)\])"
FMT_SV = r"(?i)(?<!\\)(\[(?:footnote):)(.+?)(?<!\\)(\])"
FMT_SV = r"(?i)(?<!\\)(\[(?:footnote|field):)(.+?)(?<!\\)(\])"


class nwShortcode:
Expand All @@ -89,12 +89,15 @@ class nwShortcode:
BREAK = "[br]"

FOOTNOTE_B = "[footnote:"
FIELD_B = "[field:"

COMMENT_STYLES = {
nwComment.FOOTNOTE: "[footnote:{0}]",
nwComment.COMMENT: "[comment:{0}]",
}

FIELD_VALUE = "[field:{0}]"


class nwStyles:

Expand Down Expand Up @@ -159,11 +162,14 @@ class nwKeyWords:
STORY_KEY = "@story"
MENTION_KEY = "@mention"

# Set of Valid Keys
VALID_KEYS = {
# Note: The order here affects the order of menu entries
ALL_KEYS = [
TAG_KEY, POV_KEY, FOCUS_KEY, CHAR_KEY, PLOT_KEY, TIME_KEY, WORLD_KEY,
OBJECT_KEY, ENTITY_KEY, CUSTOM_KEY, STORY_KEY, MENTION_KEY,
}
]

# Set of Valid Keys
VALID_KEYS = set(ALL_KEYS)

# Map from Keys to Item Class
KEY_CLASS = {
Expand Down Expand Up @@ -193,6 +199,29 @@ class nwLists:
]


class nwStats:

CHARS_ALL = "allChars"
CHARS_TEXT = "textChars"
CHARS_TITLE = "titleChars"
PARAGRAPHS = "paragraphCount"
TITLES = "titleCount"
WCHARS_ALL = "allWordChars"
WCHARS_TEXT = "textWordChars"
WCHARS_TITLE = "titleWordChars"
WORDS_ALL = "allWords"
WORDS_TEXT = "textWords"
WORDS_TITLE = "titleWords"

# Note: The order here affects the order of menu entries
ALL_FIELDS = [
WORDS_ALL, WORDS_TEXT, WORDS_TITLE,
CHARS_ALL, CHARS_TEXT, CHARS_TITLE,
WCHARS_ALL, WCHARS_TEXT, WCHARS_TITLE,
PARAGRAPHS, TITLES,
]


class nwLabels:

CLASS_NAME = {
Expand Down Expand Up @@ -253,6 +282,20 @@ class nwLabels:
nwKeyWords.STORY_KEY: QT_TRANSLATE_NOOP("Constant", "Story"),
nwKeyWords.MENTION_KEY: QT_TRANSLATE_NOOP("Constant", "Mentions"),
}
KEY_SHORTCUT = {
nwKeyWords.TAG_KEY: "Ctrl+K, G",
nwKeyWords.POV_KEY: "Ctrl+K, V",
nwKeyWords.FOCUS_KEY: "Ctrl+K, F",
nwKeyWords.CHAR_KEY: "Ctrl+K, C",
nwKeyWords.PLOT_KEY: "Ctrl+K, P",
nwKeyWords.TIME_KEY: "Ctrl+K, T",
nwKeyWords.WORLD_KEY: "Ctrl+K, L",
nwKeyWords.OBJECT_KEY: "Ctrl+K, O",
nwKeyWords.ENTITY_KEY: "Ctrl+K, E",
nwKeyWords.CUSTOM_KEY: "Ctrl+K, X",
nwKeyWords.STORY_KEY: "Ctrl+K, N",
nwKeyWords.MENTION_KEY: "Ctrl+K, M",
}
OUTLINE_COLS = {
nwOutline.TITLE: QT_TRANSLATE_NOOP("Constant", "Title"),
nwOutline.LEVEL: QT_TRANSLATE_NOOP("Constant", "Level"),
Expand All @@ -274,16 +317,29 @@ class nwLabels:
nwOutline.MENTION: KEY_NAME[nwKeyWords.MENTION_KEY],
nwOutline.SYNOP: QT_TRANSLATE_NOOP("Constant", "Synopsis"),
}
STATS_NAME = {
nwStats.CHARS_ALL: QT_TRANSLATE_NOOP("Constant", "Characters"),
nwStats.CHARS_TEXT: QT_TRANSLATE_NOOP("Constant", "Characters in Text"),
nwStats.CHARS_TITLE: QT_TRANSLATE_NOOP("Constant", "Characters in Headings"),
nwStats.PARAGRAPHS: QT_TRANSLATE_NOOP("Constant", "Paragraphs"),
nwStats.TITLES: QT_TRANSLATE_NOOP("Constant", "Headings"),
nwStats.WCHARS_ALL: QT_TRANSLATE_NOOP("Constant", "Characters, No Spaces"),
nwStats.WCHARS_TEXT: QT_TRANSLATE_NOOP("Constant", "Characters in Text, No Spaces"),
nwStats.WCHARS_TITLE: QT_TRANSLATE_NOOP("Constant", "Characters in Headings, No Spaces"),
nwStats.WORDS_ALL: QT_TRANSLATE_NOOP("Constant", "Words"),
nwStats.WORDS_TEXT: QT_TRANSLATE_NOOP("Constant", "Words in Text"),
nwStats.WORDS_TITLE: QT_TRANSLATE_NOOP("Constant", "Words in Headings"),
}
BUILD_FMT = {
nwBuildFmt.ODT: QT_TRANSLATE_NOOP("Constant", "Open Document (.odt)"),
nwBuildFmt.FODT: QT_TRANSLATE_NOOP("Constant", "Flat Open Document (.fodt)"),
nwBuildFmt.DOCX: QT_TRANSLATE_NOOP("Constant", "Microsoft Word Document (.docx)"),
nwBuildFmt.HTML: QT_TRANSLATE_NOOP("Constant", "novelWriter HTML (.html)"),
nwBuildFmt.HTML: QT_TRANSLATE_NOOP("Constant", "HTML 5 (.html)"),
nwBuildFmt.NWD: QT_TRANSLATE_NOOP("Constant", "novelWriter Markup (.txt)"),
nwBuildFmt.STD_MD: QT_TRANSLATE_NOOP("Constant", "Standard Markdown (.md)"),
nwBuildFmt.EXT_MD: QT_TRANSLATE_NOOP("Constant", "Extended Markdown (.md)"),
nwBuildFmt.PDF: QT_TRANSLATE_NOOP("Constant", "Portable Document Format (.pdf)"),
nwBuildFmt.J_HTML: QT_TRANSLATE_NOOP("Constant", "JSON + novelWriter HTML (.json)"),
nwBuildFmt.J_HTML: QT_TRANSLATE_NOOP("Constant", "JSON + HTML 5 (.json)"),
nwBuildFmt.J_NWD: QT_TRANSLATE_NOOP("Constant", "JSON + novelWriter Markup (.json)"),
}
BUILD_EXT = {
Expand Down
29 changes: 7 additions & 22 deletions novelwriter/core/docbuild.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,16 +127,11 @@ def iterBuildPreview(self) -> Iterable[tuple[int, bool]]:
makeObj = ToQTextDocument(self._project)
filtered = self._setupBuild(makeObj)
makeObj.initDocument()

self._outline = True

yield from self._iterBuild(makeObj, filtered)

makeObj.appendFootnotes()

makeObj.closeDocument()
self._error = None
self._cache = makeObj

return

def iterBuildDocument(self, path: Path, bFormat: nwBuildFmt) -> Iterable[tuple[int, bool]]:
Expand All @@ -152,58 +147,47 @@ def iterBuildDocument(self, path: Path, bFormat: nwBuildFmt) -> Iterable[tuple[i
makeObj = ToOdt(self._project, bFormat == nwBuildFmt.FODT)
filtered = self._setupBuild(makeObj)
makeObj.initDocument()

yield from self._iterBuild(makeObj, filtered)

makeObj.closeDocument()

elif bFormat in (nwBuildFmt.HTML, nwBuildFmt.J_HTML):
makeObj = ToHtml(self._project)
filtered = self._setupBuild(makeObj)
makeObj.initDocument()

yield from self._iterBuild(makeObj, filtered)

makeObj.appendFootnotes()
makeObj.closeDocument()
if not self._build.getBool("html.preserveTabs"):
makeObj.replaceTabs()

elif bFormat in (nwBuildFmt.STD_MD, nwBuildFmt.EXT_MD):
makeObj = ToMarkdown(self._project, bFormat == nwBuildFmt.EXT_MD)
filtered = self._setupBuild(makeObj)

yield from self._iterBuild(makeObj, filtered)

makeObj.appendFootnotes()
makeObj.closeDocument()
if self._build.getBool("format.replaceTabs"):
makeObj.replaceTabs(nSpaces=4, spaceChar=" ")

elif bFormat in (nwBuildFmt.NWD, nwBuildFmt.J_NWD):
makeObj = ToRaw(self._project)
filtered = self._setupBuild(makeObj)

yield from self._iterBuild(makeObj, filtered)

makeObj.closeDocument()
if self._build.getBool("format.replaceTabs"):
makeObj.replaceTabs(nSpaces=4, spaceChar=" ")

elif bFormat == nwBuildFmt.DOCX:
makeObj = ToDocX(self._project)
filtered = self._setupBuild(makeObj)
makeObj.initDocument()

yield from self._iterBuild(makeObj, filtered)

makeObj.closeDocument()

elif bFormat == nwBuildFmt.PDF:
makeObj = ToQTextDocument(self._project)
filtered = self._setupBuild(makeObj)
makeObj.initDocument()

yield from self._iterBuild(makeObj, filtered)

makeObj.appendFootnotes()
makeObj.closeDocument()

else:
logger.error("Unsupported document format")
Expand Down Expand Up @@ -240,7 +224,9 @@ def _setupBuild(self, bldObj: Tokenizer) -> dict:
# Get Settings
textFont = QFont(CONFIG.textFont)
textFont.fromString(self._build.getStr("format.textFont"))

bldObj.setFont(textFont)
bldObj.setLanguage(self._project.data.language)

bldObj.setPartitionFormat(
self._build.getStr("headings.fmtPart"),
Expand Down Expand Up @@ -338,7 +324,6 @@ def _setupBuild(self, bldObj: Tokenizer) -> dict:
bldObj.setReplaceUnicode(self._build.getBool("format.stripUnicode"))

if isinstance(bldObj, (ToOdt, ToDocX)):
bldObj.setLanguage(self._project.data.language)
bldObj.setHeaderFormat(
self._build.getStr("doc.pageHeader"),
self._build.getInt("doc.pageCountOffset"),
Expand Down
6 changes: 3 additions & 3 deletions novelwriter/enum.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,11 +184,11 @@ class nwBuildFmt(Enum):
ODT = 0
FODT = 1
DOCX = 2
HTML = 3
NWD = 4
PDF = 3
HTML = 4
STD_MD = 5
EXT_MD = 6
PDF = 7
NWD = 7
J_HTML = 8
J_NWD = 9

Expand Down
3 changes: 2 additions & 1 deletion novelwriter/formats/shared.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,8 @@ class TextFmt(IntEnum):
HRF_B = 21 # Begin href link
HRF_E = 22 # End href link
FNOTE = 23 # Footnote marker
STRIP = 24 # Strip the format code
FIELD = 24 # Data field
STRIP = 25 # Strip the format code


class BlockTyp(IntEnum):
Expand Down
Loading

0 comments on commit 92d148b

Please sign in to comment.