Allow inserting characters in manuscript headers (#1677)

This commit is contained in:
Veronica Berglyd Olsen
2024-01-31 20:36:25 +01:00
committed by GitHub
48 changed files with 729 additions and 533 deletions
+5
View File
@@ -174,6 +174,7 @@ A syntax theme ``.conf`` file consists of the following settings:
shortcode = 0, 0, 0 shortcode = 0, 0, 0
keyword = 0, 0, 0 keyword = 0, 0, 0
value = 0, 0, 0 value = 0, 0, 0
optional = 0, 0, 0
spellcheckline = 0, 0, 0 spellcheckline = 0, 0, 0
errorline = 0, 0, 0 errorline = 0, 0, 0
replacetag = 0, 0, 0 replacetag = 0, 0, 0
@@ -186,3 +187,7 @@ to black, except ``background`` which defaults to white,
.. versionadded:: 2.2 .. versionadded:: 2.2
The `shortcode` syntax colour entry was added, so you need to update your custom themes if you The `shortcode` syntax colour entry was added, so you need to update your custom themes if you
made any before version 2.2. made any before version 2.2.
.. versionadded:: 2.3
The `optional` syntax colour entry was added, so you need to update your custom themes if you
made any before version 2.3.
+35 -15
View File
@@ -47,16 +47,28 @@ How to Use Tags
=============== ===============
A "tag" in novelWriter is a word or phrase that you define as belonging to a heading. Tags are set A "tag" in novelWriter is a word or phrase that you define as belonging to a heading. Tags are set
by using the ``@tags`` :term:`keyword`. The full format of a tag is ``@tag: tagname``, where by using the ``@tags`` :term:`keyword`.
``tagname`` is an identifier of your choosing. You can only set *one* tag per heading, and the tag
has to be unique across all documents in the project.
.. versionadded:: 2.2 The general format of a tag is ``@tag: tagName``.
Tags are now case insensitive.
After the tags have been defined, they can then be referenced in the novel documents, or The full format of a tag is ``@tag: tagName | displayName``.
cross-referenced in other notes. they will also show up in the :guilabel:`Outline View` and in the
back-reference panel when a document is opened in the viewer. ``tagName`` (Required)
This is a unique identifier of your choosing. It is the value you use later for making
references back to this document, or section of the document.
``displayName`` (Optional)
This is an optional display name used for the tag. When you build your manuscript, you can for
instance insert the point of view character name into chapter headings. By default, the
``tagName`` value is used in headings, but if you use a shortened format internally in your
project, you can use this to specify a more suitable format for your manuscript.
You can only set *one* tag per heading, and the tag has to be unique across *all* documents in the
project.
After a tag has been defined, it can be referenced in novel documents, or cross-referenced in other
notes. Tags will also show up in the :guilabel:`Outline View` and in the back-reference panel when
a document is opened in the viewer.
The syntax highlighter will indicate to you that the keyword is correctly used and that the tag is The syntax highlighter will indicate to you that the keyword is correctly used and that the tag is
allowed, that is, the tag is unique. Duplicate tags should be detected as long as the index is up allowed, that is, the tag is unique. Duplicate tags should be detected as long as the index is up
@@ -68,22 +80,30 @@ there for you to use in whatever way you wish. Of course, the content of the doc
to the manuscript, or an outline document. If you want to compile a single document of all your to the manuscript, or an outline document. If you want to compile a single document of all your
notes, you can do this from the :guilabel:`Manuscript Build` tool. notes, you can do this from the :guilabel:`Manuscript Build` tool.
.. versionadded:: 2.2
Tags are no longer case sensitive. The tags are by default displayed with the capitalisation you
use when defining the tag, but you don't have to use the same capitalisation when referencing
it later.
.. versionadded:: 2.3
Tags can have an optional display name for manuscript builds.
Example of a heading with a tag for a character of the story: Example of a heading with a tag for a character of the story:
.. code-block:: none .. code-block:: none
# Jane Doe # Character: Jane Doe
@tag: Jane @tag: Jane | Jane Doe
Some information about the character Jane Doe. Some information about the character Jane Doe.
When this is done in a document in a :term:`Root Folder` of type "Characters", the tag is When this is done in a document in a :term:`Root Folder` of type "Characters", the tag is
automatically treated as an available character in your project, and you will be able to reference automatically treated as an available character in your project with the value "Jane", and you will
it in any of your other documents using the reference keywords for characters. It will also show up be able to reference it in any of your other documents using the reference keywords for characters.
in the Character tab in the Reference panel below the document viewer, and in the reference It will also show up in the Character tab in the Reference panel below the document viewer, and in
auto-completer menu in the editor when you fill in references. See :ref:`a_ui_view` and the reference auto-completer menu in the editor when you fill in references. See :ref:`a_ui_view`
:ref:`a_references_completer`. and :ref:`a_references_completer`.
It is the root folder type that defines what category of story elements the tag is indexed under. It is the root folder type that defines what category of story elements the tag is indexed under.
See the :ref:`a_proj_roots` section for an overview of available root folder types. They are also See the :ref:`a_proj_roots` section for an overview of available root folder types. They are also
@@ -20,6 +20,7 @@ hidden = 150, 150, 150
shortcode = 0, 155, 200 shortcode = 0, 155, 200
keyword = 200, 46, 0 keyword = 200, 46, 0
value = 184, 200, 0 value = 184, 200, 0
optional = 0, 155, 200
spellcheckline = 200, 46, 0 spellcheckline = 200, 46, 0
errorline = 46, 200, 0 errorline = 46, 200, 0
replacetag = 0, 184, 46 replacetag = 0, 184, 46
@@ -20,6 +20,7 @@ hidden = 100, 100, 100
shortcode = 0, 100, 0 shortcode = 0, 100, 0
keyword = 200, 50, 50 keyword = 200, 50, 50
value = 50, 150, 50 value = 50, 150, 50
optional = 0, 100, 0
spellcheckline = 200, 0, 0 spellcheckline = 200, 0, 0
errorline = 0, 150, 0 errorline = 0, 150, 0
replacetag = 0, 150, 0 replacetag = 0, 150, 0
+1
View File
@@ -20,6 +20,7 @@ hidden = 150, 150, 150
shortcode = 225, 225, 225 shortcode = 225, 225, 225
keyword = 225, 225, 225 keyword = 225, 225, 225
value = 200, 200, 200 value = 200, 200, 200
optional = 225, 225, 225
spellcheckline = 200, 46, 0 spellcheckline = 200, 46, 0
errorline = 46, 200, 0 errorline = 46, 200, 0
replacetag = 225, 225, 225 replacetag = 225, 225, 225
@@ -20,6 +20,7 @@ hidden = 100, 100, 100
shortcode = 0, 0, 0 shortcode = 0, 0, 0
keyword = 0, 0, 0 keyword = 0, 0, 0
value = 20, 20, 20 value = 20, 20, 20
optional = 0, 0, 0
spellcheckline = 200, 0, 0 spellcheckline = 200, 0, 0
errorline = 0, 150, 0 errorline = 0, 150, 0
replacetag = 0, 0, 0 replacetag = 0, 0, 0
+1
View File
@@ -40,6 +40,7 @@ hidden = 152, 159, 177
shortcode = 40, 142, 215 shortcode = 40, 142, 215
keyword = 222, 61, 58 keyword = 222, 61, 58
value = 150, 74, 193 value = 150, 74, 193
optional = 40, 142, 215
spellcheckline = 222, 61, 58 spellcheckline = 222, 61, 58
errorline = 8, 145, 106 errorline = 8, 145, 106
replacetag = 42, 162, 152 replacetag = 42, 162, 152
+1
View File
@@ -40,6 +40,7 @@ hidden = 99, 119, 119
shortcode = 130, 170, 255 shortcode = 130, 170, 255
keyword = 247, 140, 108 keyword = 247, 140, 108
value = 199, 146, 234 value = 199, 146, 234
optional = 130, 170, 255
spellcheckline = 247, 140, 108 spellcheckline = 247, 140, 108
errorline = 173, 219, 103 errorline = 173, 219, 103
replacetag = 127, 219, 202 replacetag = 127, 219, 202
@@ -20,6 +20,7 @@ hidden = 147, 161, 161
shortcode = 147, 161, 161 shortcode = 147, 161, 161
keyword = 133, 153, 0 keyword = 133, 153, 0
value = 203, 75, 22 value = 203, 75, 22
optional = 147, 161, 161
spellcheckline = 203, 75, 22 spellcheckline = 203, 75, 22
errorline = 220, 50, 47 errorline = 220, 50, 47
replacetag = 133, 153, 0 replacetag = 133, 153, 0
@@ -20,6 +20,7 @@ hidden = 88, 110, 117
shortcode = 88, 110, 117 shortcode = 88, 110, 117
keyword = 133, 153, 0 keyword = 133, 153, 0
value = 203, 75, 22 value = 203, 75, 22
optional = 88, 110, 117
spellcheckline = 203, 75, 22 spellcheckline = 203, 75, 22
errorline = 220, 50, 47 errorline = 220, 50, 47
replacetag = 133, 153, 0 replacetag = 133, 153, 0
+1
View File
@@ -40,6 +40,7 @@ hidden = 142, 144, 140
shortcode = 66, 113, 174 shortcode = 66, 113, 174
keyword = 240, 40, 41 keyword = 240, 40, 41
value = 137, 89, 168 value = 137, 89, 168
optional = 66, 113, 174
spellcheckline = 240, 40, 41 spellcheckline = 240, 40, 41
errorline = 113, 140, 0 errorline = 113, 140, 0
replacetag = 62, 153, 159 replacetag = 62, 153, 159
@@ -40,6 +40,7 @@ hidden = 150, 152, 150
shortcode = 129, 162, 190 shortcode = 129, 162, 190
keyword = 204, 102, 102 keyword = 204, 102, 102
value = 178, 148, 187 value = 178, 148, 187
optional = 129, 162, 190
spellcheckline = 204, 102, 102 spellcheckline = 204, 102, 102
errorline = 181, 189, 104 errorline = 181, 189, 104
replacetag = 138, 190, 183 replacetag = 138, 190, 183
@@ -40,6 +40,7 @@ hidden = 114, 133, 183
shortcode = 187, 218, 255 shortcode = 187, 218, 255
keyword = 255, 157, 164 keyword = 255, 157, 164
value = 235, 187, 255 value = 235, 187, 255
optional = 187, 218, 255
spellcheckline = 255, 157, 164 spellcheckline = 255, 157, 164
errorline = 209, 241, 169 errorline = 209, 241, 169
replacetag = 153, 255, 255 replacetag = 153, 255, 255
@@ -40,6 +40,7 @@ hidden = 150, 152, 150
shortcode = 122, 166, 218 shortcode = 122, 166, 218
keyword = 213, 78, 83 keyword = 213, 78, 83
value = 195, 151, 216 value = 195, 151, 216
optional = 122, 166, 218
spellcheckline = 213, 78, 83 spellcheckline = 213, 78, 83
errorline = 185, 202, 74 errorline = 185, 202, 74
replacetag = 112, 192, 177 replacetag = 112, 192, 177
@@ -40,6 +40,7 @@ hidden = 153, 153, 153
shortcode = 102, 153, 204 shortcode = 102, 153, 204
keyword = 242, 119, 122 keyword = 242, 119, 122
value = 204, 153, 204 value = 204, 153, 204
optional = 102, 153, 204
spellcheckline = 242, 119, 122 spellcheckline = 242, 119, 122
errorline = 153, 204, 153 errorline = 153, 204, 153
replacetag = 102, 204, 204 replacetag = 102, 204, 204
+13 -20
View File
@@ -29,7 +29,7 @@ import logging
import unicodedata import unicodedata
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
from typing import Any, Literal from typing import TYPE_CHECKING, Any, Literal
from pathlib import Path from pathlib import Path
from datetime import datetime from datetime import datetime
from configparser import ConfigParser from configparser import ConfigParser
@@ -43,6 +43,9 @@ from novelwriter.enum import nwItemClass, nwItemType, nwItemLayout
from novelwriter.error import logException from novelwriter.error import logException
from novelwriter.constants import nwConst, nwUnicode from novelwriter.constants import nwConst, nwUnicode
if TYPE_CHECKING: # pragma: no cover
from typing import TypeGuard # Requires Python 3.10
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -104,15 +107,6 @@ def checkBool(value: Any, default: bool) -> bool:
return default return default
def checkHandle(value, default, allowNone=False):
"""Check if a value is a handle."""
if allowNone and (value is None or value == "None"):
return None
if isHandle(value):
return str(value)
return default
def checkUuid(value: Any, default: str) -> str: def checkUuid(value: Any, default: str) -> str:
"""Try to process a value as an UUID, or return a default.""" """Try to process a value as an UUID, or return a default."""
try: try:
@@ -135,7 +129,7 @@ def checkPath(value: Any, default: Path) -> Path:
# Validator Functions # Validator Functions
## ##
def isHandle(value: Any) -> bool: def isHandle(value: Any) -> TypeGuard[str]:
"""Check if a string is a valid novelWriter handle. """Check if a string is a valid novelWriter handle.
Note: This is case sensitive. Must be lower case! Note: This is case sensitive. Must be lower case!
""" """
@@ -149,7 +143,7 @@ def isHandle(value: Any) -> bool:
return True return True
def isTitleTag(value: Any) -> bool: def isTitleTag(value: Any) -> TypeGuard[str]:
"""Check if a string is a valid title tag string.""" """Check if a string is a valid title tag string."""
if not isinstance(value, str): if not isinstance(value, str):
return False return False
@@ -163,19 +157,19 @@ def isTitleTag(value: Any) -> bool:
return True return True
def isItemClass(value: str) -> bool: def isItemClass(value: Any) -> TypeGuard[str]:
"""Check if a string is a valid nwItemClass identifier.""" """Check if a string is a valid nwItemClass identifier."""
return value in nwItemClass.__members__ return isinstance(value, str) and value in nwItemClass.__members__
def isItemType(value: str) -> bool: def isItemType(value: Any) -> TypeGuard[str]:
"""Check if a string is a valid nwItemType identifier.""" """Check if a string is a valid nwItemType identifier."""
return value in nwItemType.__members__ return isinstance(value, str) and value in nwItemType.__members__
def isItemLayout(value: str) -> bool: def isItemLayout(value: Any) -> TypeGuard[str]:
"""Check if a string is a valid nwItemLayout identifier.""" """Check if a string is a valid nwItemLayout identifier."""
return value in nwItemLayout.__members__ return isinstance(value, str) and value in nwItemLayout.__members__
def hexToInt(value: Any, default: int = 0) -> int: def hexToInt(value: Any, default: int = 0) -> int:
@@ -189,8 +183,7 @@ def hexToInt(value: Any, default: int = 0) -> int:
def minmax(value: int, minVal: int, maxVal: int) -> int: def minmax(value: int, minVal: int, maxVal: int) -> int:
"""Make sure an integer is between min and max value (inclusive). """Check that an value is between min and max value (inclusive)."""
"""
return min(maxVal, max(minVal, value)) return min(maxVal, max(minVal, value))
+14 -9
View File
@@ -300,16 +300,21 @@ class nwLabels:
class nwHeadFmt: class nwHeadFmt:
BR = "{BR}" BR = "{BR}"
TITLE = "{Title}" TITLE = "{Title}"
CH_NUM = "{Chapter}" CH_NUM = "{Chapter}"
CH_WORD = "{Chapter:Word}" CH_WORD = "{Chapter:Word}"
CH_ROMU = "{Chapter:URoman}" CH_ROMU = "{Chapter:URoman}"
CH_ROML = "{Chapter:LRoman}" CH_ROML = "{Chapter:LRoman}"
SC_NUM = "{Scene}" SC_NUM = "{Scene}"
SC_ABS = "{Scene:Abs}" SC_ABS = "{Scene:Abs}"
CHAR_POV = "{Char:POV}"
CHAR_FOCUS = "{Char:Focus}"
PAGE_HEADERS = [TITLE, CH_NUM, CH_WORD, CH_ROMU, CH_ROML, SC_NUM, SC_ABS] PAGE_HEADERS = [
TITLE, CH_NUM, CH_WORD, CH_ROMU, CH_ROML, SC_NUM, SC_ABS,
CHAR_POV, CHAR_FOCUS
]
# ODT Document Page Header # ODT Document Page Header
ODT_PROJECT = "{Project}" ODT_PROJECT = "{Project}"
+58 -33
View File
@@ -420,10 +420,10 @@ class NWIndex:
return return
if tBits[0] == nwKeyWords.TAG_KEY: if tBits[0] == nwKeyWords.TAG_KEY:
tagName = tBits[1] tagKey, displayName = self.parseValue(tBits[1])
self._tagsIndex.add(tagName, tHandle, sTitle, itemClass) self._tagsIndex.add(tagKey, displayName, tHandle, sTitle, itemClass.name)
self._itemIndex.setHeadingTag(tHandle, sTitle, tagName) self._itemIndex.setHeadingTag(tHandle, sTitle, tagKey)
tags[tagName.lower()] = True tags[tagKey.lower()] = True
else: else:
self._itemIndex.addHeadingRef(tHandle, sTitle, tBits[1:], tBits[0]) self._itemIndex.addHeadingRef(tHandle, sTitle, tBits[1:], tBits[0])
@@ -471,10 +471,8 @@ class NWIndex:
return True, tBits, tPos return True, tBits, tPos
def checkThese(self, tBits: list[str], nwItem: NWItem) -> list[bool]: def checkThese(self, tBits: list[str], tHandle: str) -> list[bool]:
"""Check the tags against the index to see if they are valid """Check tags against the index to see if they are valid."""
tags. This is needed for syntax highlighting.
"""
nBits = len(tBits) nBits = len(tBits)
isGood = [False]*nBits isGood = [False]*nBits
if nBits == 0: if nBits == 0:
@@ -488,19 +486,25 @@ class NWIndex:
# For a tag, only the first value is accepted, the rest are ignored # For a tag, only the first value is accepted, the rest are ignored
if tBits[0] == nwKeyWords.TAG_KEY and nBits > 1: if tBits[0] == nwKeyWords.TAG_KEY and nBits > 1:
if tBits[1] in self._tagsIndex: if tBits[1] in self._tagsIndex:
isGood[1] = self._tagsIndex.tagHandle(tBits[1]) == nwItem.itemHandle isGood[1] = self._tagsIndex.tagHandle(tBits[1]) == tHandle
else: else:
isGood[1] = True isGood[1] = True
return isGood return isGood
# If we're still here, we check that the references exist # If we're still here, we check that the references exist
# Class references cannot have the | symbol in them
refKey = nwKeyWords.KEY_CLASS[tBits[0]].name refKey = nwKeyWords.KEY_CLASS[tBits[0]].name
for n in range(1, nBits): for n in range(1, nBits):
if tBits[n] in self._tagsIndex: if (aBit := tBits[n]) in self._tagsIndex:
isGood[n] = self._tagsIndex.tagClass(tBits[n]) == refKey isGood[n] = self._tagsIndex.tagClass(aBit) == refKey and "|" not in aBit
return isGood return isGood
def parseValue(self, text: str) -> tuple[str, str]:
"""Parse a single value into a name and display part."""
name, _, display = text.partition("|")
return name.rstrip(), display.lstrip()
## ##
# Extract Data # Extract Data
## ##
@@ -615,9 +619,18 @@ class NWIndex:
for refType in refTypes: for refType in refTypes:
if refType in tRefs: if refType in tRefs:
tRefs[refType].append(self._tagsIndex.tagName(aTag)) tRefs[refType].append(self._tagsIndex.tagName(aTag))
return tRefs return tRefs
def getReferenceForHeader(self, tHandle: str, nHead: int, keyClass: str) -> list[str]:
"""Get the display names for a tags class for insertion into a
heading by one of the build classes.
"""
if iItem := self._itemIndex[tHandle]:
if hItem := iItem[f"T{nHead:04d}"]:
hRefs = [k for k, v in hItem.references.items() if keyClass in v]
return [self._tagsIndex.tagDisplay(k) for k in hRefs]
return []
def getBackReferenceList(self, tHandle: str) -> dict[str, tuple[str, IndexHeading]]: def getBackReferenceList(self, tHandle: str) -> dict[str, tuple[str, IndexHeading]]:
"""Build a dict of files referring back to our file.""" """Build a dict of files referring back to our file."""
if tHandle is None or tHandle not in self._itemIndex: if tHandle is None or tHandle not in self._itemIndex:
@@ -715,17 +728,26 @@ class TagsIndex:
"""Return a dictionary view of all tags.""" """Return a dictionary view of all tags."""
return self._tags.items() return self._tags.items()
def add(self, tagKey: str, tHandle: str, sTitle: str, itemClass: nwItemClass) -> None: def add(self, tagKey: str, displayName: str, tHandle: str,
sTitle: str, className: str) -> None:
"""Add a key to the index and set all values.""" """Add a key to the index and set all values."""
self._tags[tagKey.lower()] = { self._tags[tagKey.lower()] = {
"name": tagKey, "handle": tHandle, "heading": sTitle, "class": itemClass.name "name": tagKey,
"display": displayName or tagKey,
"handle": tHandle,
"heading": sTitle,
"class": className,
} }
return return
def tagName(self, tagKey: str) -> str: def tagName(self, tagKey: str) -> str:
"""Get the display name of a given tag.""" """Get the name of a given tag."""
return self._tags.get(tagKey.lower(), {}).get("name", "") return self._tags.get(tagKey.lower(), {}).get("name", "")
def tagDisplay(self, tagKey: str) -> str:
"""Get the display name of a given tag."""
return self._tags.get(tagKey.lower(), {}).get("display", "")
def tagHandle(self, tagKey: str) -> str | None: def tagHandle(self, tagKey: str) -> str | None:
"""Get the handle of a given tag.""" """Get the handle of a given tag."""
return self._tags.get(tagKey.lower(), {}).get("handle", None) return self._tags.get(tagKey.lower(), {}).get("handle", None)
@@ -760,27 +782,30 @@ class TagsIndex:
if not isinstance(data, dict): if not isinstance(data, dict):
raise ValueError("tagsIndex is not a dict") raise ValueError("tagsIndex is not a dict")
for tagKey, tagData in data.items(): for key, entry in data.items():
if not isinstance(tagKey, str): if not isinstance(key, str):
raise ValueError("tagsIndex keys must be a strings") raise ValueError("tagsIndex keys must be a string")
if "name" not in tagData: if not isinstance(entry, dict):
raise KeyError("A tagIndex item is missing a name entry") raise ValueError("tagsIndex entry is not a dict")
if "handle" not in tagData:
raise KeyError("A tagIndex item is missing a handle entry") name = entry.get("name")
if "heading" not in tagData: display = entry.get("display")
raise KeyError("A tagIndex item is missing a heading entry") handle = entry.get("handle")
if "class" not in tagData: heading = entry.get("heading")
raise KeyError("A tagIndex item is missing a class entry") className = entry.get("class")
if tagData["name"].lower() != tagKey:
raise ValueError("tagsIndex name must match key") if not isinstance(name, str):
if not isHandle(tagData["handle"]): raise ValueError("tagsIndex name is not a string")
if not isinstance(display, str):
raise ValueError("tagsIndex display is not a string")
if not isHandle(handle):
raise ValueError("tagsIndex handle must be a handle") raise ValueError("tagsIndex handle must be a handle")
if not isTitleTag(tagData["heading"]): if not isTitleTag(heading):
raise ValueError("tagsIndex heading must be a title tag") raise ValueError("tagsIndex heading must be a title tag")
if not isItemClass(tagData["class"]): if not isItemClass(className):
raise ValueError("tagsIndex handle must be an nwItemClass") raise ValueError("tagsIndex handle must be an nwItemClass")
self._tags = data self.add(name, display, handle, heading, className)
return return
@@ -1165,7 +1190,7 @@ class IndexHeading:
return self._tag return self._tag
@property @property
def references(self) -> dict: def references(self) -> dict[str, set[str]]:
return self._refs return self._refs
## ##
+4 -1
View File
@@ -484,7 +484,10 @@ class ToHtml(Tokenizer):
result = f"<span class='tags'>{self._localLookup(nwLabels.KEY_NAME[bits[0]])}:</span> " result = f"<span class='tags'>{self._localLookup(nwLabels.KEY_NAME[bits[0]])}:</span> "
if len(bits) > 1: if len(bits) > 1:
if bits[0] == nwKeyWords.TAG_KEY: if bits[0] == nwKeyWords.TAG_KEY:
result += f"<a name='tag_{bits[1]}'>{bits[1]}</a>" one, two = self._project.index.parseValue(bits[1])
result += f"<a name='tag_{one}'>{one}</a>"
if two:
result += f" | <span class='optional'>{two}</a>"
else: else:
if self._genMode == self.M_PREVIEW: if self._genMode == self.M_PREVIEW:
result += ", ".join(f"<a href='#{bits[0][1:]}={t}'>{t}</a>" for t in bits[1:]) result += ", ".join(f"<a href='#{bits[0][1:]}={t}'>{t}</a>" for t in bits[1:])
+64 -39
View File
@@ -37,7 +37,9 @@ from PyQt5.QtCore import QCoreApplication, QRegularExpression
from novelwriter.enum import nwComment, nwItemLayout from novelwriter.enum import nwComment, nwItemLayout
from novelwriter.common import formatTimeStamp, numberToRoman, checkInt from novelwriter.common import formatTimeStamp, numberToRoman, checkInt
from novelwriter.constants import nwHeadFmt, nwRegEx, nwShortcode, nwUnicode from novelwriter.constants import (
nwHeadFmt, nwKeyWords, nwLabels, nwRegEx, nwShortcode, nwUnicode, trConst
)
from novelwriter.core.index import processComment from novelwriter.core.index import processComment
from novelwriter.core.project import NWProject from novelwriter.core.project import NWProject
@@ -112,13 +114,15 @@ class Tokenizer(ABC):
# Data Variables # Data Variables
self._text = "" # The raw text to be tokenized self._text = "" # The raw text to be tokenized
self._nwItem = None # The NWItem associated with the handle self._nwItem = None # The NWItem currently being processed
self._tokens = [] # The list of the processed tokens
self._result = "" # The result of the last document self._result = "" # The result of the last document
self._keepMarkdown = False # Whether to keep the markdown text self._keepMarkdown = False # Whether to keep the markdown text
self._allMarkdown = [] # The result novelWriter markdown of all documents self._allMarkdown = [] # The result novelWriter markdown of all documents
# Processed Tokens
self._tokens: list[tuple[int, int, str, list[tuple[int, int]], int]] = []
# User Settings # User Settings
self._textFont = "Serif" # Output text font self._textFont = "Serif" # Output text font
self._textSize = 11 # Output text size self._textSize = 11 # Output text size
@@ -350,7 +354,7 @@ class Tokenizer(ABC):
title = f"{trNotes}: {tItem.itemName}" title = f"{trNotes}: {tItem.itemName}"
self._tokens = [] self._tokens = []
self._tokens.append(( self._tokens.append((
self.T_TITLE, 0, title, None, textAlign self.T_TITLE, 0, title, [], textAlign
)) ))
if self._keepMarkdown: if self._keepMarkdown:
self._allMarkdown.append(f"# {title}\n\n") self._allMarkdown.append(f"# {title}\n\n")
@@ -418,7 +422,7 @@ class Tokenizer(ABC):
# Check for blank lines # Check for blank lines
if len(sLine) == 0: if len(sLine) == 0:
self._tokens.append(( self._tokens.append((
self.T_EMPTY, nHead, "", None, self.A_NONE self.T_EMPTY, nHead, "", [], self.A_NONE
)) ))
if self._keepMarkdown: if self._keepMarkdown:
tmpMarkdown.append("\n") tmpMarkdown.append("\n")
@@ -446,7 +450,7 @@ class Tokenizer(ABC):
elif sLine == "[vspace]": elif sLine == "[vspace]":
self._tokens.append( self._tokens.append(
(self.T_SKIP, nHead, "", None, sAlign) (self.T_SKIP, nHead, "", [], sAlign)
) )
continue continue
@@ -454,11 +458,11 @@ class Tokenizer(ABC):
nSkip = checkInt(sLine[8:-1], 0) nSkip = checkInt(sLine[8:-1], 0)
if nSkip >= 1: if nSkip >= 1:
self._tokens.append( self._tokens.append(
(self.T_SKIP, nHead, "", None, sAlign) (self.T_SKIP, nHead, "", [], sAlign)
) )
if nSkip > 1: if nSkip > 1:
self._tokens += (nSkip - 1) * [ self._tokens += (nSkip - 1) * [
(self.T_SKIP, nHead, "", None, self.A_NONE) (self.T_SKIP, nHead, "", [], self.A_NONE)
] ]
continue continue
@@ -466,26 +470,26 @@ class Tokenizer(ABC):
cStyle, cText, _ = processComment(aLine) cStyle, cText, _ = processComment(aLine)
if cStyle == nwComment.SYNOPSIS: if cStyle == nwComment.SYNOPSIS:
self._tokens.append(( self._tokens.append((
self.T_SYNOPSIS, nHead, cText, None, sAlign self.T_SYNOPSIS, nHead, cText, [], sAlign
)) ))
if self._doSynopsis and self._keepMarkdown: if self._doSynopsis and self._keepMarkdown:
tmpMarkdown.append("%s\n" % aLine) tmpMarkdown.append("%s\n" % aLine)
elif cStyle == nwComment.SHORT: elif cStyle == nwComment.SHORT:
self._tokens.append(( self._tokens.append((
self.T_SHORT, nHead, cText, None, sAlign self.T_SHORT, nHead, cText, [], sAlign
)) ))
if self._doSynopsis and self._keepMarkdown: if self._doSynopsis and self._keepMarkdown:
tmpMarkdown.append("%s\n" % aLine) tmpMarkdown.append("%s\n" % aLine)
else: else:
self._tokens.append(( self._tokens.append((
self.T_COMMENT, nHead, cText, None, sAlign self.T_COMMENT, nHead, cText, [], sAlign
)) ))
if self._doComments and self._keepMarkdown: if self._doComments and self._keepMarkdown:
tmpMarkdown.append("%s\n" % aLine) tmpMarkdown.append("%s\n" % aLine)
elif aLine[0] == "@": elif aLine[0] == "@":
self._tokens.append(( self._tokens.append((
self.T_KEYWORD, nHead, aLine[1:].strip(), None, sAlign self.T_KEYWORD, nHead, aLine[1:].strip(), [], sAlign
)) ))
if self._doKeywords and self._keepMarkdown: if self._doKeywords and self._keepMarkdown:
tmpMarkdown.append("%s\n" % aLine) tmpMarkdown.append("%s\n" % aLine)
@@ -497,7 +501,7 @@ class Tokenizer(ABC):
nHead += 1 nHead += 1
self._tokens.append(( self._tokens.append((
self.T_HEAD1, nHead, aLine[2:].strip(), None, sAlign self.T_HEAD1, nHead, aLine[2:].strip(), [], sAlign
)) ))
if self._keepMarkdown: if self._keepMarkdown:
tmpMarkdown.append("%s\n" % aLine) tmpMarkdown.append("%s\n" % aLine)
@@ -508,7 +512,7 @@ class Tokenizer(ABC):
nHead += 1 nHead += 1
self._tokens.append(( self._tokens.append((
self.T_HEAD2, nHead, aLine[3:].strip(), None, sAlign self.T_HEAD2, nHead, aLine[3:].strip(), [], sAlign
)) ))
if self._keepMarkdown: if self._keepMarkdown:
tmpMarkdown.append("%s\n" % aLine) tmpMarkdown.append("%s\n" % aLine)
@@ -516,7 +520,7 @@ class Tokenizer(ABC):
elif aLine[:4] == "### ": elif aLine[:4] == "### ":
nHead += 1 nHead += 1
self._tokens.append(( self._tokens.append((
self.T_HEAD3, nHead, aLine[4:].strip(), None, sAlign self.T_HEAD3, nHead, aLine[4:].strip(), [], sAlign
)) ))
if self._keepMarkdown: if self._keepMarkdown:
tmpMarkdown.append("%s\n" % aLine) tmpMarkdown.append("%s\n" % aLine)
@@ -524,7 +528,7 @@ class Tokenizer(ABC):
elif aLine[:5] == "#### ": elif aLine[:5] == "#### ":
nHead += 1 nHead += 1
self._tokens.append(( self._tokens.append((
self.T_HEAD4, nHead, aLine[5:].strip(), None, sAlign self.T_HEAD4, nHead, aLine[5:].strip(), [], sAlign
)) ))
if self._keepMarkdown: if self._keepMarkdown:
tmpMarkdown.append("%s\n" % aLine) tmpMarkdown.append("%s\n" % aLine)
@@ -537,7 +541,7 @@ class Tokenizer(ABC):
tStyle = self.T_HEAD1 tStyle = self.T_HEAD1
self._tokens.append(( self._tokens.append((
tStyle, nHead, aLine[3:].strip(), None, sAlign | self.A_CENTRE tStyle, nHead, aLine[3:].strip(), [], sAlign | self.A_CENTRE
)) ))
if self._keepMarkdown: if self._keepMarkdown:
tmpMarkdown.append("%s\n" % aLine) tmpMarkdown.append("%s\n" % aLine)
@@ -551,7 +555,7 @@ class Tokenizer(ABC):
tStyle = self.T_HEAD2 tStyle = self.T_HEAD2
self._tokens.append(( self._tokens.append((
tStyle, nHead, aLine[4:].strip(), None, sAlign tStyle, nHead, aLine[4:].strip(), [], sAlign
)) ))
if self._keepMarkdown: if self._keepMarkdown:
tmpMarkdown.append("%s\n" % aLine) tmpMarkdown.append("%s\n" % aLine)
@@ -614,7 +618,7 @@ class Tokenizer(ABC):
# Always add an empty line at the end of the file # Always add an empty line at the end of the file
self._tokens.append(( self._tokens.append((
self.T_EMPTY, nHead, "", None, self.A_NONE self.T_EMPTY, nHead, "", [], self.A_NONE
)) ))
if self._keepMarkdown: if self._keepMarkdown:
tmpMarkdown.append("\n") tmpMarkdown.append("\n")
@@ -626,8 +630,8 @@ class Tokenizer(ABC):
# =========== # ===========
# Some items need a second pass # Some items need a second pass
pToken = (self.T_EMPTY, 0, "", None, self.A_NONE) pToken = (self.T_EMPTY, 0, "", [], self.A_NONE)
nToken = (self.T_EMPTY, 0, "", None, self.A_NONE) nToken = (self.T_EMPTY, 0, "", [], self.A_NONE)
tCount = len(self._tokens) tCount = len(self._tokens)
for n, tToken in enumerate(self._tokens): for n, tToken in enumerate(self._tokens):
@@ -655,6 +659,8 @@ class Tokenizer(ABC):
if not self._isNovel: if not self._isNovel:
return False return False
self._hFormatter.setHandle(self._nwItem.itemHandle if self._nwItem else None)
for n, tToken in enumerate(self._tokens): for n, tToken in enumerate(self._tokens):
# In case we see text before a scene, we reset the flag # In case we see text before a scene, we reset the flag
@@ -664,9 +670,9 @@ class Tokenizer(ABC):
elif tToken[0] == self.T_HEAD1: elif tToken[0] == self.T_HEAD1:
# Partition # Partition
tTemp = self._hFormatter.apply(self._fmtTitle, tToken[2]) tTemp = self._hFormatter.apply(self._fmtTitle, tToken[2], tToken[1])
self._tokens[n] = ( self._tokens[n] = (
tToken[0], tToken[1], tTemp, None, tToken[4] tToken[0], tToken[1], tTemp, [], tToken[4]
) )
elif tToken[0] in (self.T_HEAD2, self.T_UNNUM): elif tToken[0] in (self.T_HEAD2, self.T_UNNUM):
@@ -674,14 +680,14 @@ class Tokenizer(ABC):
# Numbered or Unnumbered # Numbered or Unnumbered
if tToken[0] == self.T_UNNUM: if tToken[0] == self.T_UNNUM:
tTemp = self._hFormatter.apply(self._fmtUnNum, tToken[2]) tTemp = self._hFormatter.apply(self._fmtUnNum, tToken[2], tToken[1])
else: else:
self._hFormatter.incChapter() self._hFormatter.incChapter()
tTemp = self._hFormatter.apply(self._fmtChapter, tToken[2]) tTemp = self._hFormatter.apply(self._fmtChapter, tToken[2], tToken[1])
# Format the chapter header # Format the chapter header
self._tokens[n] = ( self._tokens[n] = (
tToken[0], tToken[1], tTemp, None, tToken[4] tToken[0], tToken[1], tTemp, [], tToken[4]
) )
# Set scene variables # Set scene variables
@@ -693,32 +699,32 @@ class Tokenizer(ABC):
self._hFormatter.incScene() self._hFormatter.incScene()
tTemp = self._hFormatter.apply(self._fmtScene, tToken[2]) tTemp = self._hFormatter.apply(self._fmtScene, tToken[2], tToken[1])
if tTemp == "" and self._hideScene: if tTemp == "" and self._hideScene:
self._tokens[n] = ( self._tokens[n] = (
self.T_EMPTY, tToken[1], "", None, self.A_NONE self.T_EMPTY, tToken[1], "", [], self.A_NONE
) )
elif tTemp == "" and not self._hideScene: elif tTemp == "" and not self._hideScene:
if self._firstScene: if self._firstScene:
self._tokens[n] = ( self._tokens[n] = (
self.T_EMPTY, tToken[1], "", None, self.A_NONE self.T_EMPTY, tToken[1], "", [], self.A_NONE
) )
else: else:
self._tokens[n] = ( self._tokens[n] = (
self.T_SKIP, tToken[1], "", None, tToken[4] self.T_SKIP, tToken[1], "", [], tToken[4]
) )
elif tTemp == self._fmtScene: elif tTemp == self._fmtScene:
if self._firstScene: if self._firstScene:
self._tokens[n] = ( self._tokens[n] = (
self.T_EMPTY, tToken[1], "", None, self.A_NONE self.T_EMPTY, tToken[1], "", [], self.A_NONE
) )
else: else:
self._tokens[n] = ( self._tokens[n] = (
self.T_SEP, tToken[1], tTemp, None, tToken[4] | self.A_CENTRE self.T_SEP, tToken[1], tTemp, [], tToken[4] | self.A_CENTRE
) )
else: else:
self._tokens[n] = ( self._tokens[n] = (
tToken[0], tToken[1], tTemp, None, tToken[4] tToken[0], tToken[1], tTemp, [], tToken[4]
) )
self._firstScene = False self._firstScene = False
@@ -726,22 +732,22 @@ class Tokenizer(ABC):
elif tToken[0] == self.T_HEAD4: elif tToken[0] == self.T_HEAD4:
# Section # Section
tTemp = self._hFormatter.apply(self._fmtSection, tToken[2]) tTemp = self._hFormatter.apply(self._fmtSection, tToken[2], tToken[1])
if tTemp == "" and self._hideSection: if tTemp == "" and self._hideSection:
self._tokens[n] = ( self._tokens[n] = (
self.T_EMPTY, tToken[1], "", None, self.A_NONE self.T_EMPTY, tToken[1], "", [], self.A_NONE
) )
elif tTemp == "" and not self._hideSection: elif tTemp == "" and not self._hideSection:
self._tokens[n] = ( self._tokens[n] = (
self.T_SKIP, tToken[1], "", None, tToken[4] self.T_SKIP, tToken[1], "", [], tToken[4]
) )
elif tTemp == self._fmtSection: elif tTemp == self._fmtSection:
self._tokens[n] = ( self._tokens[n] = (
self.T_SEP, tToken[1], tTemp, None, tToken[4] | self.A_CENTRE self.T_SEP, tToken[1], tTemp, [], tToken[4] | self.A_CENTRE
) )
else: else:
self._tokens[n] = ( self._tokens[n] = (
tToken[0], tToken[1], tTemp, None, tToken[4] tToken[0], tToken[1], tTemp, [], tToken[4]
) )
return True return True
@@ -817,11 +823,17 @@ class HeadingFormatter:
def __init__(self, project: NWProject) -> None: def __init__(self, project: NWProject) -> None:
self._project = project self._project = project
self._handle = None
self._chCount = 0 self._chCount = 0
self._scChCount = 0 self._scChCount = 0
self._scAbsCount = 0 self._scAbsCount = 0
return return
def setHandle(self, tHandle: str | None) -> None:
"""Set the handle currently being processed."""
self._handle = tHandle
return
def incChapter(self) -> None: def incChapter(self) -> None:
"""Increment the chapter counter.""" """Increment the chapter counter."""
self._chCount += 1 self._chCount += 1
@@ -838,7 +850,7 @@ class HeadingFormatter:
self._scChCount = 0 self._scChCount = 0
return return
def apply(self, hFormat: str, text: str) -> str: def apply(self, hFormat: str, text: str, nHead: int) -> str:
"""Apply formatting to a specific heading.""" """Apply formatting to a specific heading."""
hFormat = hFormat.replace(nwHeadFmt.TITLE, text) hFormat = hFormat.replace(nwHeadFmt.TITLE, text)
hFormat = hFormat.replace(nwHeadFmt.CH_NUM, str(self._chCount)) hFormat = hFormat.replace(nwHeadFmt.CH_NUM, str(self._chCount))
@@ -854,6 +866,19 @@ class HeadingFormatter:
chRom = numberToRoman(self._chCount, toLower=False) chRom = numberToRoman(self._chCount, toLower=False)
hFormat = hFormat.replace(nwHeadFmt.CH_ROMU, chRom) hFormat = hFormat.replace(nwHeadFmt.CH_ROMU, chRom)
if nwHeadFmt.CHAR_POV in hFormat or nwHeadFmt.CHAR_FOCUS in hFormat:
if self._handle and nHead > 0:
index = self._project.index
pList = index.getReferenceForHeader(self._handle, nHead, nwKeyWords.POV_KEY)
fList = index.getReferenceForHeader(self._handle, nHead, nwKeyWords.FOCUS_KEY)
pText = pList[0] if pList else nwUnicode.U_ENDASH
fText = fList[0] if fList else nwUnicode.U_ENDASH
else:
pText = trConst(nwLabels.KEY_NAME[nwKeyWords.POV_KEY])
fText = trConst(nwLabels.KEY_NAME[nwKeyWords.FOCUS_KEY])
hFormat = hFormat.replace(nwHeadFmt.CHAR_POV, pText)
hFormat = hFormat.replace(nwHeadFmt.CHAR_FOCUS, fText)
return hFormat return hFormat
# END Class HeadingFormatter # END Class HeadingFormatter
+1 -1
View File
@@ -50,7 +50,7 @@ def logException() -> None:
"""Log the content of an exception message.""" """Log the content of an exception message."""
exType, exValue, _ = sys.exc_info() exType, exValue, _ = sys.exc_info()
if exType is not None: if exType is not None:
logger.error("%s: %s", exType.__name__, str(exValue)) logger.error(f"{exType.__name__}: {str(exValue)}", stacklevel=2)
return return
+3 -4
View File
@@ -56,7 +56,6 @@ from novelwriter import CONFIG, SHARED
from novelwriter.enum import nwDocAction, nwDocInsert, nwDocMode, nwItemClass, nwTrinary from novelwriter.enum import nwDocAction, nwDocInsert, nwDocMode, nwItemClass, nwTrinary
from novelwriter.common import minmax, transferCase from novelwriter.common import minmax, transferCase
from novelwriter.constants import nwKeyWords, nwLabels, nwShortcode, nwUnicode, trConst from novelwriter.constants import nwKeyWords, nwLabels, nwShortcode, nwUnicode, trConst
from novelwriter.core.item import NWItem
from novelwriter.core.index import countWords from novelwriter.core.index import countWords
from novelwriter.tools.lipsum import GuiLipsum from novelwriter.tools.lipsum import GuiLipsum
from novelwriter.core.document import NWDocument from novelwriter.core.document import NWDocument
@@ -1835,16 +1834,16 @@ class GuiDocEditor(QPlainTextEdit):
if len(text) == 0: if len(text) == 0:
return nwTrinary.NEUTRAL return nwTrinary.NEUTRAL
if text.startswith("@") and isinstance(self._nwItem, NWItem): if text.startswith("@") and self._docHandle:
isGood, tBits, tPos = SHARED.project.index.scanThis(text) isGood, tBits, tPos = SHARED.project.index.scanThis(text)
if not isGood: if not isGood or not tBits or tBits[0] == nwKeyWords.TAG_KEY:
return nwTrinary.NEUTRAL return nwTrinary.NEUTRAL
tag = "" tag = ""
exist = False exist = False
cPos = cursor.selectionStart() - block.position() cPos = cursor.selectionStart() - block.position()
tExist = SHARED.project.index.checkThese(tBits, self._nwItem) tExist = SHARED.project.index.checkThese(tBits, self._docHandle)
for sTag, sPos, sExist in zip(reversed(tBits), reversed(tPos), reversed(tExist)): for sTag, sPos, sExist in zip(reversed(tBits), reversed(tPos), reversed(tExist)):
if cPos >= sPos: if cPos >= sPos:
# The cursor is between the start of two tags # The cursor is between the start of two tags
+20 -21
View File
@@ -35,10 +35,10 @@ from PyQt5.QtGui import (
) )
from novelwriter import CONFIG, SHARED from novelwriter import CONFIG, SHARED
from novelwriter.enum import nwComment
from novelwriter.common import checkInt from novelwriter.common import checkInt
from novelwriter.constants import nwRegEx, nwUnicode from novelwriter.constants import nwRegEx, nwUnicode
from novelwriter.core.index import processComment from novelwriter.core.index import processComment
from novelwriter.enum import nwComment
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -107,7 +107,8 @@ class GuiDocHighlighter(QSyntaxHighlighter):
"code": self._makeFormat(SHARED.theme.colCode), "code": self._makeFormat(SHARED.theme.colCode),
"keyword": self._makeFormat(SHARED.theme.colKey), "keyword": self._makeFormat(SHARED.theme.colKey),
"modifier": self._makeFormat(SHARED.theme.colMod), "modifier": self._makeFormat(SHARED.theme.colMod),
"value": self._makeFormat(SHARED.theme.colVal, "underline"), "value": self._makeFormat(SHARED.theme.colVal),
"optional": self._makeFormat(SHARED.theme.colOpt),
"codevalue": self._makeFormat(SHARED.theme.colVal), "codevalue": self._makeFormat(SHARED.theme.colVal),
"codeinval": self._makeFormat(None, "errline"), "codeinval": self._makeFormat(None, "errline"),
} }
@@ -278,23 +279,23 @@ class GuiDocHighlighter(QSyntaxHighlighter):
if text.startswith("@"): # Keywords and commands if text.startswith("@"): # Keywords and commands
self.setCurrentBlockState(self.BLOCK_META) self.setCurrentBlockState(self.BLOCK_META)
if self._tItem: index = SHARED.project.index
pIndex = SHARED.project.index isValid, bits, pos = index.scanThis(text)
isValid, bits, pos = pIndex.scanThis(text) isGood = index.checkThese(bits, self._tHandle)
isGood = pIndex.checkThese(bits, self._tItem) if isValid:
if isValid: for n, bit in enumerate(bits):
for n, bit in enumerate(bits): xPos = pos[n]
xPos = pos[n] xLen = len(bit)
xLen = len(bit) if not isGood[n]:
if isGood[n]: self.setFormat(xPos, xLen, self._hStyles["codeinval"])
if n == 0: elif n == 0:
self.setFormat(xPos, xLen, self._hStyles["keyword"]) self.setFormat(xPos, xLen, self._hStyles["keyword"])
else: else:
self.setFormat(xPos, xLen, self._hStyles["value"]) one, two = index.parseValue(bit)
else: self.setFormat(xPos, len(one), self._hStyles["value"])
kwFmt = self.format(xPos) if two:
kwFmt.merge(self._hStyles["codeinval"]) yPos = xPos + len(bit) - len(two)
self.setFormat(xPos, xLen, kwFmt) self.setFormat(yPos, len(two), self._hStyles["optional"])
# We never want to run the spell checker on keyword/values, # We never want to run the spell checker on keyword/values,
# so we force a return here # so we force a return here
@@ -406,8 +407,6 @@ class GuiDocHighlighter(QSyntaxHighlighter):
if "errline" in styles: if "errline" in styles:
charFormat.setUnderlineColor(SHARED.theme.colError) charFormat.setUnderlineColor(SHARED.theme.colError)
charFormat.setUnderlineStyle(QTextCharFormat.SpellCheckUnderline) charFormat.setUnderlineStyle(QTextCharFormat.SpellCheckUnderline)
if "underline" in styles:
charFormat.setFontUnderline(True)
if "background" in styles and color is not None: if "background" in styles and color is not None:
charFormat.setBackground(QBrush(color, Qt.SolidPattern)) charFormat.setBackground(QBrush(color, Qt.SolidPattern))
+17 -26
View File
@@ -461,15 +461,16 @@ class GuiDocViewer(QTextBrowser):
def _makeStyleSheet(self) -> None: def _makeStyleSheet(self) -> None:
"""Generate an appropriate style sheet for the document viewer, """Generate an appropriate style sheet for the document viewer,
based on the current syntax highlighter theme, based on the current syntax highlighter theme.
""" """
colText = SHARED.theme.colText colText = SHARED.theme.colText
colHead = SHARED.theme.colHead colHead = SHARED.theme.colHead
colVal = SHARED.theme.colVal colVals = SHARED.theme.colVal
colEmph = SHARED.theme.colEmph colEmph = SHARED.theme.colEmph
colKey = SHARED.theme.colKey colKeys = SHARED.theme.colKey
colHidden = SHARED.theme.colHidden colHide = SHARED.theme.colHidden
colMod = SHARED.theme.colMod colMods = SHARED.theme.colMod
colOpts = SHARED.theme.colOpt
styleSheet = ( styleSheet = (
"body {{" "body {{"
" color: rgb({tColR}, {tColG}, {tColB});" " color: rgb({tColR}, {tColG}, {tColB});"
@@ -486,6 +487,9 @@ class GuiDocViewer(QTextBrowser):
".tags {{" ".tags {{"
" color: rgb({kColR}, {kColG}, {kColB});" " color: rgb({kColR}, {kColG}, {kColB});"
"}}\n" "}}\n"
".optional {{"
" color: rgb({oColR}, {oColG}, {oColB});"
"}}\n"
".comment {{" ".comment {{"
" color: rgb({cColR}, {cColG}, {cColB});" " color: rgb({cColR}, {cColG}, {cColB});"
"}}\n" "}}\n"
@@ -496,27 +500,14 @@ class GuiDocViewer(QTextBrowser):
" text-align: center;" " text-align: center;"
"}}\n" "}}\n"
).format( ).format(
tColR=colText.red(), tColR=colText.red(), tColG=colText.green(), tColB=colText.blue(),
tColG=colText.green(), hColR=colHead.red(), hColG=colHead.green(), hColB=colHead.blue(),
tColB=colText.blue(), aColR=colVals.red(), aColG=colVals.green(), aColB=colVals.blue(),
hColR=colHead.red(), eColR=colEmph.red(), eColG=colEmph.green(), eColB=colEmph.blue(),
hColG=colHead.green(), kColR=colKeys.red(), kColG=colKeys.green(), kColB=colKeys.blue(),
hColB=colHead.blue(), cColR=colHide.red(), cColG=colHide.green(), cColB=colHide.blue(),
aColR=colVal.red(), mColR=colMods.red(), mColG=colMods.green(), mColB=colMods.blue(),
aColG=colVal.green(), oColR=colOpts.red(), oColG=colOpts.green(), oColB=colOpts.blue(),
aColB=colVal.blue(),
eColR=colEmph.red(),
eColG=colEmph.green(),
eColB=colEmph.blue(),
kColR=colKey.red(),
kColG=colKey.green(),
kColB=colKey.blue(),
cColR=colHidden.red(),
cColG=colHidden.green(),
cColB=colHidden.blue(),
mColR=colMod.red(),
mColG=colMod.green(),
mColB=colMod.blue(),
) )
self.document().setDefaultStyleSheet(styleSheet) self.document().setDefaultStyleSheet(styleSheet)
+2
View File
@@ -101,6 +101,7 @@ class GuiTheme:
self.colCode = QColor(0, 0, 0) self.colCode = QColor(0, 0, 0)
self.colKey = QColor(0, 0, 0) self.colKey = QColor(0, 0, 0)
self.colVal = QColor(0, 0, 0) self.colVal = QColor(0, 0, 0)
self.colOpt = QColor(0, 0, 0)
self.colSpell = QColor(0, 0, 0) self.colSpell = QColor(0, 0, 0)
self.colError = QColor(0, 0, 0) self.colError = QColor(0, 0, 0)
self.colRepTag = QColor(0, 0, 0) self.colRepTag = QColor(0, 0, 0)
@@ -323,6 +324,7 @@ class GuiTheme:
self.colCode = self._parseColour(confParser, cnfSec, "shortcode") self.colCode = self._parseColour(confParser, cnfSec, "shortcode")
self.colKey = self._parseColour(confParser, cnfSec, "keyword") self.colKey = self._parseColour(confParser, cnfSec, "keyword")
self.colVal = self._parseColour(confParser, cnfSec, "value") self.colVal = self._parseColour(confParser, cnfSec, "value")
self.colOpt = self._parseColour(confParser, cnfSec, "optional")
self.colSpell = self._parseColour(confParser, cnfSec, "spellcheckline") self.colSpell = self._parseColour(confParser, cnfSec, "spellcheckline")
self.colError = self._parseColour(confParser, cnfSec, "errorline") self.colError = self._parseColour(confParser, cnfSec, "errorline")
self.colRepTag = self._parseColour(confParser, cnfSec, "replacetag") self.colRepTag = self._parseColour(confParser, cnfSec, "replacetag")
+1 -1
View File
@@ -604,7 +604,7 @@ class _DetailsWidget(QWidget):
for key in entries: for key in entries:
sub = QTreeWidgetItem() sub = QTreeWidgetItem()
sub.setText(0, build.getLabel(key)) sub.setText(0, build.getLabel(key))
sub.setText(1, hFmt.apply(build.getStr(key), title)) sub.setText(1, hFmt.apply(build.getStr(key), title, 0))
item.addChild(sub) item.addChild(sub)
for key in ["headings.hideScene", "headings.hideSection"]: for key in ["headings.hideScene", "headings.hideSection"]:
sub = QTreeWidgetItem() sub = QTreeWidgetItem()
+4
View File
@@ -722,6 +722,8 @@ class _HeadingsTab(NScrollablePage):
self.aInsChRomL = self.menuInsert.addAction(self.tr("Chapter Number (Lower Case Roman)")) self.aInsChRomL = self.menuInsert.addAction(self.tr("Chapter Number (Lower Case Roman)"))
self.aInsScNum = self.menuInsert.addAction(self.tr("Scene Number (In Chapter)")) self.aInsScNum = self.menuInsert.addAction(self.tr("Scene Number (In Chapter)"))
self.aInsScAbs = self.menuInsert.addAction(self.tr("Scene Number (Absolute)")) self.aInsScAbs = self.menuInsert.addAction(self.tr("Scene Number (Absolute)"))
self.aInsCharPOV = self.menuInsert.addAction(self.tr("Point of View Character"))
self.aInsCharFocus = self.menuInsert.addAction(self.tr("Focus Character"))
self.aInsTitle.triggered.connect(lambda: self._insertIntoForm(nwHeadFmt.TITLE)) self.aInsTitle.triggered.connect(lambda: self._insertIntoForm(nwHeadFmt.TITLE))
self.aInsChNum.triggered.connect(lambda: self._insertIntoForm(nwHeadFmt.CH_NUM)) self.aInsChNum.triggered.connect(lambda: self._insertIntoForm(nwHeadFmt.CH_NUM))
@@ -730,6 +732,8 @@ class _HeadingsTab(NScrollablePage):
self.aInsChRomL.triggered.connect(lambda: self._insertIntoForm(nwHeadFmt.CH_ROML)) self.aInsChRomL.triggered.connect(lambda: self._insertIntoForm(nwHeadFmt.CH_ROML))
self.aInsScNum.triggered.connect(lambda: self._insertIntoForm(nwHeadFmt.SC_NUM)) self.aInsScNum.triggered.connect(lambda: self._insertIntoForm(nwHeadFmt.SC_NUM))
self.aInsScAbs.triggered.connect(lambda: self._insertIntoForm(nwHeadFmt.SC_ABS)) self.aInsScAbs.triggered.connect(lambda: self._insertIntoForm(nwHeadFmt.SC_ABS))
self.aInsCharPOV.triggered.connect(lambda: self._insertIntoForm(nwHeadFmt.CHAR_POV))
self.aInsCharFocus.triggered.connect(lambda: self._insertIntoForm(nwHeadFmt.CHAR_FOCUS))
self.btnInsert = QPushButton(self.tr("Insert")) self.btnInsert = QPushButton(self.tr("Insert"))
self.btnInsert.setMenu(self.menuInsert) self.btnInsert.setMenu(self.menuInsert)
+3 -3
View File
@@ -1,11 +1,11 @@
%%~name: John Smith %%~name: John Smith
%%~path: f7e2d9f330615/14298de4d9524 %%~path: f7e2d9f330615/14298de4d9524
%%~kind: CHARACTER/NOTE %%~kind: CHARACTER/NOTE
%%~hash: fda91c416d874aa41a47fceaedb3d62f040c7e32 %%~hash: f85b4da6ba4a557db0c4d5f1b59f33ba01542606
%%~date: Unknown/2023-11-25 18:16:13 %%~date: Unknown/2024-01-30 10:58:18
# John Smith # John Smith
@tag: John @tag: John | John Smith
% Short: The sidekick % Short: The sidekick
+3 -3
View File
@@ -1,11 +1,11 @@
%%~name: Jane Smith %%~name: Jane Smith
%%~path: f7e2d9f330615/bb2c23b3c42cc %%~path: f7e2d9f330615/bb2c23b3c42cc
%%~kind: CHARACTER/NOTE %%~kind: CHARACTER/NOTE
%%~hash: b7291713899bd0356617a36ae606b08e5fee1b65 %%~hash: 10f072a4bf7389b0c178a4181e50833d39b945af
%%~date: Unknown/2023-11-25 18:16:07 %%~date: Unknown/2024-01-30 11:05:37
# Jane Smith # Jane Smith
@tag: Jane @tag: Jane | Jane Smith
% Short: The heroine % Short: The heroine
+5 -5
View File
@@ -1,6 +1,6 @@
<?xml version='1.0' encoding='utf-8'?> <?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="2.3a3" hexVersion="0x020300a3" fileVersion="1.5" fileRevision="2" timeStamp="2024-01-27 18:20:02"> <novelWriterXML appVersion="2.3a3" hexVersion="0x020300a3" fileVersion="1.5" fileRevision="2" timeStamp="2024-01-29 17:42:31">
<project id="e2be99af-f9bf-4403-857a-c3d1ac25abea" saveCount="1621" autoCount="255" editTime="81261"> <project id="e2be99af-f9bf-4403-857a-c3d1ac25abea" saveCount="1660" autoCount="256" editTime="81969">
<name>Sample Project</name> <name>Sample Project</name>
<author>Jane Smith</author> <author>Jane Smith</author>
</project> </project>
@@ -57,7 +57,7 @@
<name status="sf24ce6" import="ia857f0" active="yes">Chapter One</name> <name status="sf24ce6" import="ia857f0" active="yes">Chapter One</name>
</item> </item>
<item handle="636b6aa9b697b" parent="6a2d6d5f4f401" root="7031beac91f75" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="636b6aa9b697b" parent="6a2d6d5f4f401" root="7031beac91f75" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="no" heading="H3" charCount="2901" wordCount="513" paraCount="15" cursorPos="392" /> <meta expanded="no" heading="H3" charCount="2901" wordCount="513" paraCount="15" cursorPos="66" />
<name status="s90e6c9" import="ia857f0" active="yes">Making a Scene</name> <name status="s90e6c9" import="ia857f0" active="yes">Making a Scene</name>
</item> </item>
<item handle="bc0cbd2a407f3" parent="6a2d6d5f4f401" root="7031beac91f75" order="1" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="bc0cbd2a407f3" parent="6a2d6d5f4f401" root="7031beac91f75" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
@@ -101,11 +101,11 @@
<name status="sf12341" import="ia857f0">Main Characters</name> <name status="sf12341" import="ia857f0">Main Characters</name>
</item> </item>
<item handle="14298de4d9524" parent="f7e2d9f330615" root="f6622b4617424" order="0" type="FILE" class="CHARACTER" layout="NOTE"> <item handle="14298de4d9524" parent="f7e2d9f330615" root="f6622b4617424" order="0" type="FILE" class="CHARACTER" layout="NOTE">
<meta expanded="no" heading="H1" charCount="49" wordCount="9" paraCount="1" cursorPos="48" /> <meta expanded="no" heading="H1" charCount="49" wordCount="9" paraCount="1" cursorPos="15" />
<name status="sf12341" import="icfb3a5" active="yes">John Smith</name> <name status="sf12341" import="icfb3a5" active="yes">John Smith</name>
</item> </item>
<item handle="bb2c23b3c42cc" parent="f7e2d9f330615" root="f6622b4617424" order="1" type="FILE" class="CHARACTER" layout="NOTE"> <item handle="bb2c23b3c42cc" parent="f7e2d9f330615" root="f6622b4617424" order="1" type="FILE" class="CHARACTER" layout="NOTE">
<meta expanded="no" heading="H1" charCount="55" wordCount="9" paraCount="1" cursorPos="47" /> <meta expanded="no" heading="H1" charCount="55" wordCount="9" paraCount="1" cursorPos="31" />
<name status="sf12341" import="i2d7a54" active="yes">Jane Smith</name> <name status="sf12341" import="i2d7a54" active="yes">Jane Smith</name>
</item> </item>
<item handle="15c4492bd5107" parent="None" root="15c4492bd5107" order="3" type="ROOT" class="WORLD"> <item handle="15c4492bd5107" parent="None" root="15c4492bd5107" order="3" type="ROOT" class="WORLD">
+3 -3
View File
@@ -1,11 +1,11 @@
%%~name: Ancient Europe %%~name: Ancient Europe
%%~path: 60bdf227455cc/04468803b92e1 %%~path: 60bdf227455cc/04468803b92e1
%%~kind: WORLD/NOTE %%~kind: WORLD/NOTE
%%~hash: b4318c2de40a1fc4055d18f8d02696b9cf171ea2 %%~hash: 7b746485a1d06c64a5e0bf671e3141806ed51a54
%%~date: Unknown/Unknown %%~date: Unknown/2024-01-30 21:36:44
# Ancient Europe # Ancient Europe
@tag: Europe @tag: Europe | Ancient Europe
Vivamus sodales risus ac accumsan posuere. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Nunc vel enim felis. Vestibulum dignissim massa nunc, a auctor magna eleifend et. Proin dignissim sodales erat vitae convallis. Aliquam id tellus dui. Curabitur sollicitudin scelerisque ex sit amet posuere. Nam rutrum felis id rhoncus feugiat. Duis sagittis quam quis purus efficitur, quis rutrum odio iaculis. Maecenas semper ante turpis, at vulputate mi consectetur non. Sed rutrum nibh turpis, quis rhoncus purus ornare quis. Vestibulum at rutrum mauris. Integer dolor nisi, tincidunt eget vehicula ac, ultricies at ligula. Vivamus sodales risus ac accumsan posuere. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Nunc vel enim felis. Vestibulum dignissim massa nunc, a auctor magna eleifend et. Proin dignissim sodales erat vitae convallis. Aliquam id tellus dui. Curabitur sollicitudin scelerisque ex sit amet posuere. Nam rutrum felis id rhoncus feugiat. Duis sagittis quam quis purus efficitur, quis rutrum odio iaculis. Maecenas semper ante turpis, at vulputate mi consectetur non. Sed rutrum nibh turpis, quis rhoncus purus ornare quis. Vestibulum at rutrum mauris. Integer dolor nisi, tincidunt eget vehicula ac, ultricies at ligula.
+3 -3
View File
@@ -1,11 +1,11 @@
%%~name: Mr. Nobody %%~name: Mr. Nobody
%%~path: 67a8707f2f249/4c4f28287af27 %%~path: 67a8707f2f249/4c4f28287af27
%%~kind: CHARACTER/NOTE %%~kind: CHARACTER/NOTE
%%~hash: 07a86c2001669de7d7a7545286c22b963e3ccf63 %%~hash: 2a9b9751e8207b719bfa6233ebe04e24928ff8a8
%%~date: Unknown/Unknown %%~date: Unknown/2024-01-30 21:36:26
# Nobody Owens # Nobody Owens
@tag: Bod @tag: Bod | Nobody Owens
@plot: Main @plot: Main
Pellentesque nec erat ut nulla posuere commodo. Curabitur nisi augue, imperdiet et porta imperdiet, efficitur id leo. Cras finibus arcu at nibh commodo congue. Proin suscipit placerat condimentum. Aenean ante enim, cursus id lorem a, blandit venenatis nibh. Maecenas suscipit porta elit, sit amet porta felis porttitor eu. Sed a dui nibh. Phasellus sed faucibus dui. Pellentesque felis nulla, ultrices non efficitur quis, rutrum id mi. Mauris tempus auctor nisl, in bibendum enim pellentesque sit amet. Proin nunc lacus, imperdiet nec posuere ac, interdum non lectus. Pellentesque nec erat ut nulla posuere commodo. Curabitur nisi augue, imperdiet et porta imperdiet, efficitur id leo. Cras finibus arcu at nibh commodo congue. Proin suscipit placerat condimentum. Aenean ante enim, cursus id lorem a, blandit venenatis nibh. Maecenas suscipit porta elit, sit amet porta felis porttitor eu. Sed a dui nibh. Phasellus sed faucibus dui. Pellentesque felis nulla, ultrices non efficitur quis, rutrum id mi. Mauris tempus auctor nisl, in bibendum enim pellentesque sit amet. Proin nunc lacus, imperdiet nec posuere ac, interdum non lectus.
+3 -3
View File
@@ -1,10 +1,10 @@
%%~name: Prologue %%~name: Prologue
%%~path: b3643d0f92e32/88d59a277361b %%~path: b3643d0f92e32/88d59a277361b
%%~kind: NOVEL/DOCUMENT %%~kind: NOVEL/DOCUMENT
%%~hash: 19a2aa95b07ce10eaea753c46569929439648c63 %%~hash: 5f965566ba82bbb83b8aa24f3ab7efde0c5e61cf
%%~date: Unknown/Unknown %%~date: Unknown/2024-01-30 21:36:00
##! Prologue ##! Prologue
% Synopsis:Explanation from the lipsum.com website. % Synopsis: Explanation from the lipsum.com website.
_Lorem Ipsum_ is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum. _Lorem Ipsum_ is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
+1 -1
View File
@@ -2,7 +2,7 @@
%%~path: 45e6b01ca35c1/fb609cd8319dc %%~path: 45e6b01ca35c1/fb609cd8319dc
%%~kind: NOVEL/DOCUMENT %%~kind: NOVEL/DOCUMENT
%%~hash: 5dabeaa7a58238a6ad99ce73176b1bdfad1a71b7 %%~hash: 5dabeaa7a58238a6ad99ce73176b1bdfad1a71b7
%%~date: Unknown/Unknown %%~date: Unknown/2024-01-30 21:35:57
## Chapter One ## Chapter One
@pov: Bod @pov: Bod
+7 -8
View File
@@ -1,8 +1,7 @@
<?xml version='1.0' encoding='utf-8'?> <?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="2.1-beta1" hexVersion="0x020100b1" fileVersion="1.5" fileRevision="1" timeStamp="2023-08-25 18:03:37"> <novelWriterXML appVersion="2.3a3" hexVersion="0x020300a3" fileVersion="1.5" fileRevision="2" timeStamp="2024-01-30 21:37:01">
<project id="5ac0df12-8b8b-476b-9905-21d33685687c" saveCount="43" autoCount="24" editTime="1935"> <project id="5ac0df12-8b8b-476b-9905-21d33685687c" saveCount="44" autoCount="25" editTime="2039">
<name>Lorem Ipsum</name> <name>Lorem Ipsum</name>
<title>Lorem Ipsum</title>
<author>lipsum.com</author> <author>lipsum.com</author>
</project> </project>
<settings> <settings>
@@ -46,7 +45,7 @@
<name status="sedd043" import="i613591" active="yes">Front Matter</name> <name status="sedd043" import="i613591" active="yes">Front Matter</name>
</item> </item>
<item handle="88d59a277361b" parent="b3643d0f92e32" root="b3643d0f92e32" order="2" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="88d59a277361b" parent="b3643d0f92e32" root="b3643d0f92e32" order="2" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="no" heading="H2" charCount="584" wordCount="92" paraCount="1" cursorPos="4" /> <meta expanded="no" heading="H2" charCount="584" wordCount="92" paraCount="1" cursorPos="16" />
<name status="s92a87b" import="i613591" active="yes">Prologue</name> <name status="s92a87b" import="i613591" active="yes">Prologue</name>
</item> </item>
<item handle="db7e733775d4d" parent="b3643d0f92e32" root="b3643d0f92e32" order="3" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="db7e733775d4d" parent="b3643d0f92e32" root="b3643d0f92e32" order="3" type="FILE" class="NOVEL" layout="DOCUMENT">
@@ -58,11 +57,11 @@
<name status="s92a87b" import="i613591">Chapter One</name> <name status="s92a87b" import="i613591">Chapter One</name>
</item> </item>
<item handle="fb609cd8319dc" parent="45e6b01ca35c1" root="b3643d0f92e32" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="fb609cd8319dc" parent="45e6b01ca35c1" root="b3643d0f92e32" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="no" heading="H2" charCount="419" wordCount="67" paraCount="1" cursorPos="56" /> <meta expanded="no" heading="H2" charCount="419" wordCount="67" paraCount="1" cursorPos="59" />
<name status="s92a87b" import="i613591" active="yes">Chapter One</name> <name status="s92a87b" import="i613591" active="yes">Chapter One</name>
</item> </item>
<item handle="88243afbe5ed8" parent="45e6b01ca35c1" root="b3643d0f92e32" order="1" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="88243afbe5ed8" parent="45e6b01ca35c1" root="b3643d0f92e32" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="no" heading="H3" charCount="2758" wordCount="404" paraCount="4" cursorPos="1528" /> <meta expanded="no" heading="H3" charCount="2758" wordCount="404" paraCount="4" cursorPos="57" />
<name status="sedd043" import="i613591" active="yes">Scene One</name> <name status="sedd043" import="i613591" active="yes">Scene One</name>
</item> </item>
<item handle="f96ec11c6a3da" parent="45e6b01ca35c1" root="b3643d0f92e32" order="2" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="f96ec11c6a3da" parent="45e6b01ca35c1" root="b3643d0f92e32" order="2" type="FILE" class="NOVEL" layout="DOCUMENT">
@@ -98,7 +97,7 @@
<name status="sbaa94f" import="i613591">Characters</name> <name status="sbaa94f" import="i613591">Characters</name>
</item> </item>
<item handle="4c4f28287af27" parent="67a8707f2f249" root="67a8707f2f249" order="0" type="FILE" class="CHARACTER" layout="NOTE"> <item handle="4c4f28287af27" parent="67a8707f2f249" root="67a8707f2f249" order="0" type="FILE" class="CHARACTER" layout="NOTE">
<meta expanded="no" heading="H1" charCount="1864" wordCount="284" paraCount="3" cursorPos="1883" /> <meta expanded="no" heading="H1" charCount="1864" wordCount="284" paraCount="3" cursorPos="40" />
<name status="sbaa94f" import="i613591" active="yes">Mr. Nobody</name> <name status="sbaa94f" import="i613591" active="yes">Mr. Nobody</name>
</item> </item>
<item handle="6c6afb1247750" parent="None" root="6c6afb1247750" order="2" type="ROOT" class="PLOT"> <item handle="6c6afb1247750" parent="None" root="6c6afb1247750" order="2" type="ROOT" class="PLOT">
@@ -114,7 +113,7 @@
<name status="sbaa94f" import="i613591">World</name> <name status="sbaa94f" import="i613591">World</name>
</item> </item>
<item handle="04468803b92e1" parent="60bdf227455cc" root="60bdf227455cc" order="0" type="FILE" class="WORLD" layout="NOTE"> <item handle="04468803b92e1" parent="60bdf227455cc" root="60bdf227455cc" order="0" type="FILE" class="WORLD" layout="NOTE">
<meta expanded="no" heading="H1" charCount="1770" wordCount="259" paraCount="3" cursorPos="1792" /> <meta expanded="no" heading="H1" charCount="1770" wordCount="259" paraCount="3" cursorPos="47" />
<name status="sbaa94f" import="i613591" active="yes">Ancient Europe</name> <name status="sbaa94f" import="i613591" active="yes">Ancient Europe</name>
</item> </item>
</content> </content>
@@ -1,8 +1,8 @@
{ {
"novelWriter.tagsIndex": { "novelWriter.tagsIndex": {
"bod": {"name": "Bod", "handle": "4c4f28287af27", "heading": "T0001", "class": "CHARACTER"}, "bod": {"name": "Bod", "display": "Nobody Owens", "handle": "4c4f28287af27", "heading": "T0001", "class": "CHARACTER"},
"main": {"name": "Main", "handle": "2426c6f0ca922", "heading": "T0001", "class": "PLOT"}, "main": {"name": "Main", "display": "Main", "handle": "2426c6f0ca922", "heading": "T0001", "class": "PLOT"},
"europe": {"name": "Europe", "handle": "04468803b92e1", "heading": "T0001", "class": "WORLD"} "europe": {"name": "Europe", "display": "Ancient Europe", "handle": "04468803b92e1", "heading": "T0001", "class": "WORLD"}
}, },
"novelWriter.itemIndex": { "novelWriter.itemIndex": {
"7a992350f3eb6": { "7a992350f3eb6": {
@@ -150,7 +150,7 @@ Integer egestas maximus leo eu facilisis. Nunc rhoncus dignissim lectus eu lacin
# Nobody Owens # Nobody Owens
**Tag:** Bod **Tag:** Bod | Nobody Owens
**Plot:** Main **Plot:** Main
Pellentesque nec erat ut nulla posuere commodo. Curabitur nisi augue, imperdiet et porta imperdiet, efficitur id leo. Cras finibus arcu at nibh commodo congue. Proin suscipit placerat condimentum. Aenean ante enim, cursus id lorem a, blandit venenatis nibh. Maecenas suscipit porta elit, sit amet porta felis porttitor eu. Sed a dui nibh. Phasellus sed faucibus dui. Pellentesque felis nulla, ultrices non efficitur quis, rutrum id mi. Mauris tempus auctor nisl, in bibendum enim pellentesque sit amet. Proin nunc lacus, imperdiet nec posuere ac, interdum non lectus. Pellentesque nec erat ut nulla posuere commodo. Curabitur nisi augue, imperdiet et porta imperdiet, efficitur id leo. Cras finibus arcu at nibh commodo congue. Proin suscipit placerat condimentum. Aenean ante enim, cursus id lorem a, blandit venenatis nibh. Maecenas suscipit porta elit, sit amet porta felis porttitor eu. Sed a dui nibh. Phasellus sed faucibus dui. Pellentesque felis nulla, ultrices non efficitur quis, rutrum id mi. Mauris tempus auctor nisl, in bibendum enim pellentesque sit amet. Proin nunc lacus, imperdiet nec posuere ac, interdum non lectus.
@@ -173,7 +173,7 @@ Mauris ut erat eu lorem malesuada egestas vel vel urna. Maecenas ac semper quam.
# Ancient Europe # Ancient Europe
**Tag:** Europe **Tag:** Europe | Ancient Europe
Vivamus sodales risus ac accumsan posuere. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Nunc vel enim felis. Vestibulum dignissim massa nunc, a auctor magna eleifend et. Proin dignissim sodales erat vitae convallis. Aliquam id tellus dui. Curabitur sollicitudin scelerisque ex sit amet posuere. Nam rutrum felis id rhoncus feugiat. Duis sagittis quam quis purus efficitur, quis rutrum odio iaculis. Maecenas semper ante turpis, at vulputate mi consectetur non. Sed rutrum nibh turpis, quis rhoncus purus ornare quis. Vestibulum at rutrum mauris. Integer dolor nisi, tincidunt eget vehicula ac, ultricies at ligula. Vivamus sodales risus ac accumsan posuere. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Nunc vel enim felis. Vestibulum dignissim massa nunc, a auctor magna eleifend et. Proin dignissim sodales erat vitae convallis. Aliquam id tellus dui. Curabitur sollicitudin scelerisque ex sit amet posuere. Nam rutrum felis id rhoncus feugiat. Duis sagittis quam quis purus efficitur, quis rutrum odio iaculis. Maecenas semper ante turpis, at vulputate mi consectetur non. Sed rutrum nibh turpis, quis rhoncus purus ornare quis. Vestibulum at rutrum mauris. Integer dolor nisi, tincidunt eget vehicula ac, ultricies at ligula.
@@ -104,7 +104,7 @@ a {color: rgb(66, 113, 174);}
<p>Integer egestas maximus leo eu facilisis. Nunc rhoncus dignissim lectus eu lacinia. Praesent lacinia urna porttitor aliquam condimentum. Nulla eu eros dictum, dictum nunc vitae, sagittis nibh. Integer ante neque, consequat nec sollicitudin id, consectetur vitae dolor. Nullam volutpat sem orci, quis viverra magna auctor a. Suspendisse potenti. Maecenas commodo sed neque pellentesque vehicula. Sed luctus nisl risus, elementum semper purus interdum vel. Ut pulvinar, massa sit amet venenatis placerat, nunc lacus hendrerit odio, non aliquet nunc risus eu lectus. Maecenas feugiat semper ligula, id lobortis sem porta eu. Integer posuere elit magna, at mollis eros bibendum et. Ut imperdiet purus vel nulla aliquam maximus. Morbi sodales purus tellus, a rhoncus sem rutrum sit amet. Quisque risus sem, laoreet nec convallis nec, rutrum vitae justo.</p> <p>Integer egestas maximus leo eu facilisis. Nunc rhoncus dignissim lectus eu lacinia. Praesent lacinia urna porttitor aliquam condimentum. Nulla eu eros dictum, dictum nunc vitae, sagittis nibh. Integer ante neque, consequat nec sollicitudin id, consectetur vitae dolor. Nullam volutpat sem orci, quis viverra magna auctor a. Suspendisse potenti. Maecenas commodo sed neque pellentesque vehicula. Sed luctus nisl risus, elementum semper purus interdum vel. Ut pulvinar, massa sit amet venenatis placerat, nunc lacus hendrerit odio, non aliquet nunc risus eu lectus. Maecenas feugiat semper ligula, id lobortis sem porta eu. Integer posuere elit magna, at mollis eros bibendum et. Ut imperdiet purus vel nulla aliquam maximus. Morbi sodales purus tellus, a rhoncus sem rutrum sit amet. Quisque risus sem, laoreet nec convallis nec, rutrum vitae justo.</p>
<h1 class='title' style='text-align: center; page-break-before: always;'>Notes: Characters</h1> <h1 class='title' style='text-align: center; page-break-before: always;'>Notes: Characters</h1>
<h1>Nobody Owens</h1> <h1>Nobody Owens</h1>
<p style='margin-bottom: 0;'><span class='tags'>Tag:</span> <a name='tag_Bod'>Bod</a></p> <p style='margin-bottom: 0;'><span class='tags'>Tag:</span> <a name='tag_Bod'>Bod</a> | <span class='optional'>Nobody Owens</a></p>
<p style='margin-top: 0;'><span class='tags'>Plot:</span> <a href='#tag_Main'>Main</a></p> <p style='margin-top: 0;'><span class='tags'>Plot:</span> <a href='#tag_Main'>Main</a></p>
<p>Pellentesque nec erat ut nulla posuere commodo. Curabitur nisi augue, imperdiet et porta imperdiet, efficitur id leo. Cras finibus arcu at nibh commodo congue. Proin suscipit placerat condimentum. Aenean ante enim, cursus id lorem a, blandit venenatis nibh. Maecenas suscipit porta elit, sit amet porta felis porttitor eu. Sed a dui nibh. Phasellus sed faucibus dui. Pellentesque felis nulla, ultrices non efficitur quis, rutrum id mi. Mauris tempus auctor nisl, in bibendum enim pellentesque sit amet. Proin nunc lacus, imperdiet nec posuere ac, interdum non lectus.</p> <p>Pellentesque nec erat ut nulla posuere commodo. Curabitur nisi augue, imperdiet et porta imperdiet, efficitur id leo. Cras finibus arcu at nibh commodo congue. Proin suscipit placerat condimentum. Aenean ante enim, cursus id lorem a, blandit venenatis nibh. Maecenas suscipit porta elit, sit amet porta felis porttitor eu. Sed a dui nibh. Phasellus sed faucibus dui. Pellentesque felis nulla, ultrices non efficitur quis, rutrum id mi. Mauris tempus auctor nisl, in bibendum enim pellentesque sit amet. Proin nunc lacus, imperdiet nec posuere ac, interdum non lectus.</p>
<p>Suspendisse faucibus est auctor orci mollis luctus. Praesent quis sodales neque. Interdum et malesuada fames ac ante ipsum primis in faucibus. Donec sodales rutrum mattis. In in sem ornare, consequat nulla ac, convallis arcu. Duis ac metus id felis commodo commodo sit amet eget diam. Curabitur rhoncus lacinia leo at sodales. Etiam finibus porta diam a viverra. Praesent nisi urna, volutpat sit amet odio at, vehicula vehicula leo. In non enim eget nisl luctus commodo. Pellentesque pellentesque at lectus at luctus. Quisque nec felis bibendum, lacinia libero ut, lacinia eros. Integer finibus ultricies nibh sit amet placerat.</p> <p>Suspendisse faucibus est auctor orci mollis luctus. Praesent quis sodales neque. Interdum et malesuada fames ac ante ipsum primis in faucibus. Donec sodales rutrum mattis. In in sem ornare, consequat nulla ac, convallis arcu. Duis ac metus id felis commodo commodo sit amet eget diam. Curabitur rhoncus lacinia leo at sodales. Etiam finibus porta diam a viverra. Praesent nisi urna, volutpat sit amet odio at, vehicula vehicula leo. In non enim eget nisl luctus commodo. Pellentesque pellentesque at lectus at luctus. Quisque nec felis bibendum, lacinia libero ut, lacinia eros. Integer finibus ultricies nibh sit amet placerat.</p>
@@ -116,7 +116,7 @@ a {color: rgb(66, 113, 174);}
<p>Mauris ut erat eu lorem malesuada egestas vel vel urna. Maecenas ac semper quam. Maecenas aliquet metus non interdum mattis. Proin consectetur molestie ligula. Aliquam sollicitudin pulvinar urna a pellentesque. Suspendisse ultrices, est mattis scelerisque porta, nisi nisi laoreet nisl, non condimentum quam ante a velit. Proin scelerisque justo augue, nec laoreet ligula egestas at. Etiam enim quam, ultrices non accumsan hendrerit, elementum vel ligula. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nam efficitur odio libero, in vestibulum arcu aliquam at. Cras non vehicula augue. Integer lobortis, est vitae aliquam facilisis, metus ligula aliquet eros, at porttitor sem tortor eget massa. Aliquam varius scelerisque neque sed gravida. Aenean eleifend lorem id ante elementum sollicitudin. Proin commodo massa a quam volutpat, mollis fermentum turpis efficitur.</p> <p>Mauris ut erat eu lorem malesuada egestas vel vel urna. Maecenas ac semper quam. Maecenas aliquet metus non interdum mattis. Proin consectetur molestie ligula. Aliquam sollicitudin pulvinar urna a pellentesque. Suspendisse ultrices, est mattis scelerisque porta, nisi nisi laoreet nisl, non condimentum quam ante a velit. Proin scelerisque justo augue, nec laoreet ligula egestas at. Etiam enim quam, ultrices non accumsan hendrerit, elementum vel ligula. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nam efficitur odio libero, in vestibulum arcu aliquam at. Cras non vehicula augue. Integer lobortis, est vitae aliquam facilisis, metus ligula aliquet eros, at porttitor sem tortor eget massa. Aliquam varius scelerisque neque sed gravida. Aenean eleifend lorem id ante elementum sollicitudin. Proin commodo massa a quam volutpat, mollis fermentum turpis efficitur.</p>
<h1 class='title' style='text-align: center; page-break-before: always;'>Notes: World</h1> <h1 class='title' style='text-align: center; page-break-before: always;'>Notes: World</h1>
<h1>Ancient Europe</h1> <h1>Ancient Europe</h1>
<p><span class='tags'>Tag:</span> <a name='tag_Europe'>Europe</a></p> <p><span class='tags'>Tag:</span> <a name='tag_Europe'>Europe</a> | <span class='optional'>Ancient Europe</a></p>
<p>Vivamus sodales risus ac accumsan posuere. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Nunc vel enim felis. Vestibulum dignissim massa nunc, a auctor magna eleifend et. Proin dignissim sodales erat vitae convallis. Aliquam id tellus dui. Curabitur sollicitudin scelerisque ex sit amet posuere. Nam rutrum felis id rhoncus feugiat. Duis sagittis quam quis purus efficitur, quis rutrum odio iaculis. Maecenas semper ante turpis, at vulputate mi consectetur non. Sed rutrum nibh turpis, quis rhoncus purus ornare quis. Vestibulum at rutrum mauris. Integer dolor nisi, tincidunt eget vehicula ac, ultricies at ligula.</p> <p>Vivamus sodales risus ac accumsan posuere. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Nunc vel enim felis. Vestibulum dignissim massa nunc, a auctor magna eleifend et. Proin dignissim sodales erat vitae convallis. Aliquam id tellus dui. Curabitur sollicitudin scelerisque ex sit amet posuere. Nam rutrum felis id rhoncus feugiat. Duis sagittis quam quis purus efficitur, quis rutrum odio iaculis. Maecenas semper ante turpis, at vulputate mi consectetur non. Sed rutrum nibh turpis, quis rhoncus purus ornare quis. Vestibulum at rutrum mauris. Integer dolor nisi, tincidunt eget vehicula ac, ultricies at ligula.</p>
<p>Aenean semper turpis quis varius rhoncus. Vivamus ac mi eget felis euismod vulputate. Nam eu tempus velit. Etiam ut est porta, finibus erat sit amet, consectetur felis. Nullam consequat felis ut lacus pharetra, in lobortis urna mollis. Nulla varius eros nec lorem rhoncus, sed venenatis risus ultrices. Phasellus pellentesque laoreet neque, ut ultricies lacus vulputate quis. In malesuada dui sit amet est interdum, eget consectetur mi gravida. Cras vel bibendum purus. Quisque commodo tempor arcu, non lacinia sem blandit eleifend. Quisque at neque gravida, porttitor metus a, suscipit diam. Quisque convallis sodales lacus et condimentum. Donec a suscipit diam. Pellentesque eget cursus neque.</p> <p>Aenean semper turpis quis varius rhoncus. Vivamus ac mi eget felis euismod vulputate. Nam eu tempus velit. Etiam ut est porta, finibus erat sit amet, consectetur felis. Nullam consequat felis ut lacus pharetra, in lobortis urna mollis. Nulla varius eros nec lorem rhoncus, sed venenatis risus ultrices. Phasellus pellentesque laoreet neque, ut ultricies lacus vulputate quis. In malesuada dui sit amet est interdum, eget consectetur mi gravida. Cras vel bibendum purus. Quisque commodo tempor arcu, non lacinia sem blandit eleifend. Quisque at neque gravida, porttitor metus a, suscipit diam. Quisque convallis sodales lacus et condimentum. Donec a suscipit diam. Pellentesque eget cursus neque.</p>
<p>Nunc ullamcorper magna quis elit condimentum rhoncus. Aenean dictum pulvinar dolor suscipit interdum. Aliquam elit massa, elementum nec cursus eu, maximus nec ipsum. Donec ullamcorper iaculis dolor eu commodo. Nunc eget tortor quis turpis consectetur varius. Vestibulum nec justo vel tellus venenatis condimentum. Duis auctor iaculis massa. Nunc risus magna, rutrum vitae eros non, tristique mollis enim.</p> <p>Nunc ullamcorper magna quis elit condimentum rhoncus. Aenean dictum pulvinar dolor suscipit interdum. Aliquam elit massa, elementum nec cursus eu, maximus nec ipsum. Donec ullamcorper iaculis dolor eu commodo. Nunc eget tortor quis turpis consectetur varius. Vestibulum nec justo vel tellus venenatis condimentum. Duis auctor iaculis massa. Nunc risus magna, rutrum vitae eros non, tristique mollis enim.</p>
@@ -2,8 +2,8 @@
"meta": { "meta": {
"projectName": "Lorem Ipsum", "projectName": "Lorem Ipsum",
"novelAuthor": "lipsum.com", "novelAuthor": "lipsum.com",
"buildTime": 1706306020, "buildTime": 1706648755,
"buildTimeStr": "2024-01-26 22:53:40" "buildTimeStr": "2024-01-30 22:05:55"
}, },
"text": { "text": {
"css": [ "css": [
@@ -132,7 +132,7 @@
], ],
[ [
"<h1>Nobody Owens</h1>", "<h1>Nobody Owens</h1>",
"<p style='margin-bottom: 0;'><span class='tags'>Tag:</span> <a name='tag_Bod'>Bod</a></p>", "<p style='margin-bottom: 0;'><span class='tags'>Tag:</span> <a name='tag_Bod'>Bod</a> | <span class='optional'>Nobody Owens</a></p>",
"<p style='margin-top: 0;'><span class='tags'>Plot:</span> <a href='#tag_Main'>Main</a></p>", "<p style='margin-top: 0;'><span class='tags'>Plot:</span> <a href='#tag_Main'>Main</a></p>",
"<p>Pellentesque nec erat ut nulla posuere commodo. Curabitur nisi augue, imperdiet et porta imperdiet, efficitur id leo. Cras finibus arcu at nibh commodo congue. Proin suscipit placerat condimentum. Aenean ante enim, cursus id lorem a, blandit venenatis nibh. Maecenas suscipit porta elit, sit amet porta felis porttitor eu. Sed a dui nibh. Phasellus sed faucibus dui. Pellentesque felis nulla, ultrices non efficitur quis, rutrum id mi. Mauris tempus auctor nisl, in bibendum enim pellentesque sit amet. Proin nunc lacus, imperdiet nec posuere ac, interdum non lectus.</p>", "<p>Pellentesque nec erat ut nulla posuere commodo. Curabitur nisi augue, imperdiet et porta imperdiet, efficitur id leo. Cras finibus arcu at nibh commodo congue. Proin suscipit placerat condimentum. Aenean ante enim, cursus id lorem a, blandit venenatis nibh. Maecenas suscipit porta elit, sit amet porta felis porttitor eu. Sed a dui nibh. Phasellus sed faucibus dui. Pellentesque felis nulla, ultrices non efficitur quis, rutrum id mi. Mauris tempus auctor nisl, in bibendum enim pellentesque sit amet. Proin nunc lacus, imperdiet nec posuere ac, interdum non lectus.</p>",
"<p>Suspendisse faucibus est auctor orci mollis luctus. Praesent quis sodales neque. Interdum et malesuada fames ac ante ipsum primis in faucibus. Donec sodales rutrum mattis. In in sem ornare, consequat nulla ac, convallis arcu. Duis ac metus id felis commodo commodo sit amet eget diam. Curabitur rhoncus lacinia leo at sodales. Etiam finibus porta diam a viverra. Praesent nisi urna, volutpat sit amet odio at, vehicula vehicula leo. In non enim eget nisl luctus commodo. Pellentesque pellentesque at lectus at luctus. Quisque nec felis bibendum, lacinia libero ut, lacinia eros. Integer finibus ultricies nibh sit amet placerat.</p>", "<p>Suspendisse faucibus est auctor orci mollis luctus. Praesent quis sodales neque. Interdum et malesuada fames ac ante ipsum primis in faucibus. Donec sodales rutrum mattis. In in sem ornare, consequat nulla ac, convallis arcu. Duis ac metus id felis commodo commodo sit amet eget diam. Curabitur rhoncus lacinia leo at sodales. Etiam finibus porta diam a viverra. Praesent nisi urna, volutpat sit amet odio at, vehicula vehicula leo. In non enim eget nisl luctus commodo. Pellentesque pellentesque at lectus at luctus. Quisque nec felis bibendum, lacinia libero ut, lacinia eros. Integer finibus ultricies nibh sit amet placerat.</p>",
@@ -152,7 +152,7 @@
], ],
[ [
"<h1>Ancient Europe</h1>", "<h1>Ancient Europe</h1>",
"<p><span class='tags'>Tag:</span> <a name='tag_Europe'>Europe</a></p>", "<p><span class='tags'>Tag:</span> <a name='tag_Europe'>Europe</a> | <span class='optional'>Ancient Europe</a></p>",
"<p>Vivamus sodales risus ac accumsan posuere. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Nunc vel enim felis. Vestibulum dignissim massa nunc, a auctor magna eleifend et. Proin dignissim sodales erat vitae convallis. Aliquam id tellus dui. Curabitur sollicitudin scelerisque ex sit amet posuere. Nam rutrum felis id rhoncus feugiat. Duis sagittis quam quis purus efficitur, quis rutrum odio iaculis. Maecenas semper ante turpis, at vulputate mi consectetur non. Sed rutrum nibh turpis, quis rhoncus purus ornare quis. Vestibulum at rutrum mauris. Integer dolor nisi, tincidunt eget vehicula ac, ultricies at ligula.</p>", "<p>Vivamus sodales risus ac accumsan posuere. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Nunc vel enim felis. Vestibulum dignissim massa nunc, a auctor magna eleifend et. Proin dignissim sodales erat vitae convallis. Aliquam id tellus dui. Curabitur sollicitudin scelerisque ex sit amet posuere. Nam rutrum felis id rhoncus feugiat. Duis sagittis quam quis purus efficitur, quis rutrum odio iaculis. Maecenas semper ante turpis, at vulputate mi consectetur non. Sed rutrum nibh turpis, quis rhoncus purus ornare quis. Vestibulum at rutrum mauris. Integer dolor nisi, tincidunt eget vehicula ac, ultricies at ligula.</p>",
"<p>Aenean semper turpis quis varius rhoncus. Vivamus ac mi eget felis euismod vulputate. Nam eu tempus velit. Etiam ut est porta, finibus erat sit amet, consectetur felis. Nullam consequat felis ut lacus pharetra, in lobortis urna mollis. Nulla varius eros nec lorem rhoncus, sed venenatis risus ultrices. Phasellus pellentesque laoreet neque, ut ultricies lacus vulputate quis. In malesuada dui sit amet est interdum, eget consectetur mi gravida. Cras vel bibendum purus. Quisque commodo tempor arcu, non lacinia sem blandit eleifend. Quisque at neque gravida, porttitor metus a, suscipit diam. Quisque convallis sodales lacus et condimentum. Donec a suscipit diam. Pellentesque eget cursus neque.</p>", "<p>Aenean semper turpis quis varius rhoncus. Vivamus ac mi eget felis euismod vulputate. Nam eu tempus velit. Etiam ut est porta, finibus erat sit amet, consectetur felis. Nullam consequat felis ut lacus pharetra, in lobortis urna mollis. Nulla varius eros nec lorem rhoncus, sed venenatis risus ultrices. Phasellus pellentesque laoreet neque, ut ultricies lacus vulputate quis. In malesuada dui sit amet est interdum, eget consectetur mi gravida. Cras vel bibendum purus. Quisque commodo tempor arcu, non lacinia sem blandit eleifend. Quisque at neque gravida, porttitor metus a, suscipit diam. Quisque convallis sodales lacus et condimentum. Donec a suscipit diam. Pellentesque eget cursus neque.</p>",
"<p>Nunc ullamcorper magna quis elit condimentum rhoncus. Aenean dictum pulvinar dolor suscipit interdum. Aliquam elit massa, elementum nec cursus eu, maximus nec ipsum. Donec ullamcorper iaculis dolor eu commodo. Nunc eget tortor quis turpis consectetur varius. Vestibulum nec justo vel tellus venenatis condimentum. Duis auctor iaculis massa. Nunc risus magna, rutrum vitae eros non, tristique mollis enim.</p>" "<p>Nunc ullamcorper magna quis elit condimentum rhoncus. Aenean dictum pulvinar dolor suscipit interdum. Aliquam elit massa, elementum nec cursus eu, maximus nec ipsum. Donec ullamcorper iaculis dolor eu commodo. Nunc eget tortor quis turpis consectetur varius. Vestibulum nec justo vel tellus venenatis condimentum. Duis auctor iaculis massa. Nunc risus magna, rutrum vitae eros non, tristique mollis enim.</p>"
@@ -2,8 +2,8 @@
"meta": { "meta": {
"projectName": "Lorem Ipsum", "projectName": "Lorem Ipsum",
"novelAuthor": "lipsum.com", "novelAuthor": "lipsum.com",
"buildTime": 1706306020, "buildTime": 1706648873,
"buildTimeStr": "2024-01-26 22:53:40" "buildTimeStr": "2024-01-30 22:07:53"
}, },
"text": { "text": {
"nwd": [ "nwd": [
@@ -27,7 +27,7 @@
[ [
"##! Prologue", "##! Prologue",
"", "",
"% Synopsis:Explanation from the lipsum.com website.", "% Synopsis: Explanation from the lipsum.com website.",
"", "",
"_Lorem Ipsum_ is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum." "_Lorem Ipsum_ is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum."
], ],
@@ -174,7 +174,7 @@
[ [
"# Nobody Owens", "# Nobody Owens",
"", "",
"@tag: Bod", "@tag: Bod | Nobody Owens",
"@plot: Main", "@plot: Main",
"", "",
"Pellentesque nec erat ut nulla posuere commodo. Curabitur nisi augue, imperdiet et porta imperdiet, efficitur id leo. Cras finibus arcu at nibh commodo congue. Proin suscipit placerat condimentum. Aenean ante enim, cursus id lorem a, blandit venenatis nibh. Maecenas suscipit porta elit, sit amet porta felis porttitor eu. Sed a dui nibh. Phasellus sed faucibus dui. Pellentesque felis nulla, ultrices non efficitur quis, rutrum id mi. Mauris tempus auctor nisl, in bibendum enim pellentesque sit amet. Proin nunc lacus, imperdiet nec posuere ac, interdum non lectus.", "Pellentesque nec erat ut nulla posuere commodo. Curabitur nisi augue, imperdiet et porta imperdiet, efficitur id leo. Cras finibus arcu at nibh commodo congue. Proin suscipit placerat condimentum. Aenean ante enim, cursus id lorem a, blandit venenatis nibh. Maecenas suscipit porta elit, sit amet porta felis porttitor eu. Sed a dui nibh. Phasellus sed faucibus dui. Pellentesque felis nulla, ultrices non efficitur quis, rutrum id mi. Mauris tempus auctor nisl, in bibendum enim pellentesque sit amet. Proin nunc lacus, imperdiet nec posuere ac, interdum non lectus.",
@@ -201,7 +201,7 @@
[ [
"# Ancient Europe", "# Ancient Europe",
"", "",
"@tag: Europe", "@tag: Europe | Ancient Europe",
"", "",
"Vivamus sodales risus ac accumsan posuere. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Nunc vel enim felis. Vestibulum dignissim massa nunc, a auctor magna eleifend et. Proin dignissim sodales erat vitae convallis. Aliquam id tellus dui. Curabitur sollicitudin scelerisque ex sit amet posuere. Nam rutrum felis id rhoncus feugiat. Duis sagittis quam quis purus efficitur, quis rutrum odio iaculis. Maecenas semper ante turpis, at vulputate mi consectetur non. Sed rutrum nibh turpis, quis rhoncus purus ornare quis. Vestibulum at rutrum mauris. Integer dolor nisi, tincidunt eget vehicula ac, ultricies at ligula.", "Vivamus sodales risus ac accumsan posuere. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Nunc vel enim felis. Vestibulum dignissim massa nunc, a auctor magna eleifend et. Proin dignissim sodales erat vitae convallis. Aliquam id tellus dui. Curabitur sollicitudin scelerisque ex sit amet posuere. Nam rutrum felis id rhoncus feugiat. Duis sagittis quam quis purus efficitur, quis rutrum odio iaculis. Maecenas semper ante turpis, at vulputate mi consectetur non. Sed rutrum nibh turpis, quis rhoncus purus ornare quis. Vestibulum at rutrum mauris. Integer dolor nisi, tincidunt eget vehicula ac, ultricies at ligula.",
"", "",
@@ -15,7 +15,7 @@ The standard chunk of Lorem Ipsum used since the 1500s is reproduced below for t
##! Prologue ##! Prologue
% Synopsis:Explanation from the lipsum.com website. % Synopsis: Explanation from the lipsum.com website.
_Lorem Ipsum_ is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum. _Lorem Ipsum_ is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
@@ -77,11 +77,11 @@ Ut et consequat enim, quis ornare nibh. In lectus neque, mollis et suscipit et,
% Exctracted from the lipsum.com website. % Exctracted from the lipsum.com website.
It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout.
The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using 'Content here, content here', making it look like readable English. The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using 'Content here, content here', making it look like readable English.
Many desktop publishing packages and web page editors now use Lorem Ipsum as their default model text, and a search for 'lorem ipsum' will uncover many web sites still in their infancy. Various versions have evolved over the years, sometimes by accident, sometimes on purpose (injected humour and the like). Many desktop publishing packages and web page editors now use Lorem Ipsum as their default model text, and a search for 'lorem ipsum' will uncover many web sites still in their infancy. Various versions have evolved over the years, sometimes by accident, sometimes on purpose (injected humour and the like).
## Chapter Two ## Chapter Two
@@ -151,7 +151,7 @@ Integer egestas maximus leo eu facilisis. Nunc rhoncus dignissim lectus eu lacin
# Nobody Owens # Nobody Owens
@tag: Bod @tag: Bod | Nobody Owens
@plot: Main @plot: Main
Pellentesque nec erat ut nulla posuere commodo. Curabitur nisi augue, imperdiet et porta imperdiet, efficitur id leo. Cras finibus arcu at nibh commodo congue. Proin suscipit placerat condimentum. Aenean ante enim, cursus id lorem a, blandit venenatis nibh. Maecenas suscipit porta elit, sit amet porta felis porttitor eu. Sed a dui nibh. Phasellus sed faucibus dui. Pellentesque felis nulla, ultrices non efficitur quis, rutrum id mi. Mauris tempus auctor nisl, in bibendum enim pellentesque sit amet. Proin nunc lacus, imperdiet nec posuere ac, interdum non lectus. Pellentesque nec erat ut nulla posuere commodo. Curabitur nisi augue, imperdiet et porta imperdiet, efficitur id leo. Cras finibus arcu at nibh commodo congue. Proin suscipit placerat condimentum. Aenean ante enim, cursus id lorem a, blandit venenatis nibh. Maecenas suscipit porta elit, sit amet porta felis porttitor eu. Sed a dui nibh. Phasellus sed faucibus dui. Pellentesque felis nulla, ultrices non efficitur quis, rutrum id mi. Mauris tempus auctor nisl, in bibendum enim pellentesque sit amet. Proin nunc lacus, imperdiet nec posuere ac, interdum non lectus.
@@ -174,7 +174,7 @@ Mauris ut erat eu lorem malesuada egestas vel vel urna. Maecenas ac semper quam.
# Ancient Europe # Ancient Europe
@tag: Europe @tag: Europe | Ancient Europe
Vivamus sodales risus ac accumsan posuere. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Nunc vel enim felis. Vestibulum dignissim massa nunc, a auctor magna eleifend et. Proin dignissim sodales erat vitae convallis. Aliquam id tellus dui. Curabitur sollicitudin scelerisque ex sit amet posuere. Nam rutrum felis id rhoncus feugiat. Duis sagittis quam quis purus efficitur, quis rutrum odio iaculis. Maecenas semper ante turpis, at vulputate mi consectetur non. Sed rutrum nibh turpis, quis rhoncus purus ornare quis. Vestibulum at rutrum mauris. Integer dolor nisi, tincidunt eget vehicula ac, ultricies at ligula. Vivamus sodales risus ac accumsan posuere. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Nunc vel enim felis. Vestibulum dignissim massa nunc, a auctor magna eleifend et. Proin dignissim sodales erat vitae convallis. Aliquam id tellus dui. Curabitur sollicitudin scelerisque ex sit amet posuere. Nam rutrum felis id rhoncus feugiat. Duis sagittis quam quis purus efficitur, quis rutrum odio iaculis. Maecenas semper ante turpis, at vulputate mi consectetur non. Sed rutrum nibh turpis, quis rhoncus purus ornare quis. Vestibulum at rutrum mauris. Integer dolor nisi, tincidunt eget vehicula ac, ultricies at ligula.
@@ -1,13 +1,13 @@
<?xml version='1.0' encoding='utf-8'?> <?xml version='1.0' encoding='utf-8'?>
<office:document xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" office:version="1.3" office:mimetype="application/vnd.oasis.opendocument.text"> <office:document xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" office:version="1.3" office:mimetype="application/vnd.oasis.opendocument.text">
<office:meta> <office:meta>
<meta:creation-date>2023-07-19T23:55:28</meta:creation-date> <meta:creation-date>2024-01-30T21:56:12</meta:creation-date>
<meta:generator>novelWriter/2.1-beta1</meta:generator> <meta:generator>novelWriter/2.3a3</meta:generator>
<meta:initial-creator>lipsum.com</meta:initial-creator> <meta:initial-creator>lipsum.com</meta:initial-creator>
<meta:editing-cycles>42</meta:editing-cycles> <meta:editing-cycles>44</meta:editing-cycles>
<meta:editing-duration>P0DT0H31M53S</meta:editing-duration> <meta:editing-duration>P0DT0H33M59S</meta:editing-duration>
<dc:title>Lorem Ipsum</dc:title> <dc:title>Lorem Ipsum</dc:title>
<dc:date>2023-07-19T23:55:28</dc:date> <dc:date>2024-01-30T21:56:12</dc:date>
<dc:creator>lipsum.com</dc:creator> <dc:creator>lipsum.com</dc:creator>
</office:meta> </office:meta>
<office:font-face-decls> <office:font-face-decls>
@@ -192,7 +192,7 @@
<text:p text:style-name="Text_20_body">Integer egestas maximus leo eu facilisis. Nunc rhoncus dignissim lectus eu lacinia. Praesent lacinia urna porttitor aliquam condimentum. Nulla eu eros dictum, dictum nunc vitae, sagittis nibh. Integer ante neque, consequat nec sollicitudin id, consectetur vitae dolor. Nullam volutpat sem orci, quis viverra magna auctor a. Suspendisse potenti. Maecenas commodo sed neque pellentesque vehicula. Sed luctus nisl risus, elementum semper purus interdum vel. Ut pulvinar, massa sit amet venenatis placerat, nunc lacus hendrerit odio, non aliquet nunc risus eu lectus. Maecenas feugiat semper ligula, id lobortis sem porta eu. Integer posuere elit magna, at mollis eros bibendum et. Ut imperdiet purus vel nulla aliquam maximus. Morbi sodales purus tellus, a rhoncus sem rutrum sit amet. Quisque risus sem, laoreet nec convallis nec, rutrum vitae justo.</text:p> <text:p text:style-name="Text_20_body">Integer egestas maximus leo eu facilisis. Nunc rhoncus dignissim lectus eu lacinia. Praesent lacinia urna porttitor aliquam condimentum. Nulla eu eros dictum, dictum nunc vitae, sagittis nibh. Integer ante neque, consequat nec sollicitudin id, consectetur vitae dolor. Nullam volutpat sem orci, quis viverra magna auctor a. Suspendisse potenti. Maecenas commodo sed neque pellentesque vehicula. Sed luctus nisl risus, elementum semper purus interdum vel. Ut pulvinar, massa sit amet venenatis placerat, nunc lacus hendrerit odio, non aliquet nunc risus eu lectus. Maecenas feugiat semper ligula, id lobortis sem porta eu. Integer posuere elit magna, at mollis eros bibendum et. Ut imperdiet purus vel nulla aliquam maximus. Morbi sodales purus tellus, a rhoncus sem rutrum sit amet. Quisque risus sem, laoreet nec convallis nec, rutrum vitae justo.</text:p>
<text:p text:style-name="P7">Notes: Characters</text:p> <text:p text:style-name="P7">Notes: Characters</text:p>
<text:h text:style-name="Heading_20_1" text:outline-level="1">Nobody Owens</text:h> <text:h text:style-name="Heading_20_1" text:outline-level="1">Nobody Owens</text:h>
<text:p text:style-name="P5"><text:span text:style-name="T1">Tag:</text:span> Bod</text:p> <text:p text:style-name="P5"><text:span text:style-name="T1">Tag:</text:span> Bod | Nobody Owens</text:p>
<text:p text:style-name="Text_20_Meta"><text:span text:style-name="T1">Plot:</text:span> Main</text:p> <text:p text:style-name="Text_20_Meta"><text:span text:style-name="T1">Plot:</text:span> Main</text:p>
<text:p text:style-name="Text_20_body">Pellentesque nec erat ut nulla posuere commodo. Curabitur nisi augue, imperdiet et porta imperdiet, efficitur id leo. Cras finibus arcu at nibh commodo congue. Proin suscipit placerat condimentum. Aenean ante enim, cursus id lorem a, blandit venenatis nibh. Maecenas suscipit porta elit, sit amet porta felis porttitor eu. Sed a dui nibh. Phasellus sed faucibus dui. Pellentesque felis nulla, ultrices non efficitur quis, rutrum id mi. Mauris tempus auctor nisl, in bibendum enim pellentesque sit amet. Proin nunc lacus, imperdiet nec posuere ac, interdum non lectus.</text:p> <text:p text:style-name="Text_20_body">Pellentesque nec erat ut nulla posuere commodo. Curabitur nisi augue, imperdiet et porta imperdiet, efficitur id leo. Cras finibus arcu at nibh commodo congue. Proin suscipit placerat condimentum. Aenean ante enim, cursus id lorem a, blandit venenatis nibh. Maecenas suscipit porta elit, sit amet porta felis porttitor eu. Sed a dui nibh. Phasellus sed faucibus dui. Pellentesque felis nulla, ultrices non efficitur quis, rutrum id mi. Mauris tempus auctor nisl, in bibendum enim pellentesque sit amet. Proin nunc lacus, imperdiet nec posuere ac, interdum non lectus.</text:p>
<text:p text:style-name="Text_20_body">Suspendisse faucibus est auctor orci mollis luctus. Praesent quis sodales neque. Interdum et malesuada fames ac ante ipsum primis in faucibus. Donec sodales rutrum mattis. In in sem ornare, consequat nulla ac, convallis arcu. Duis ac metus id felis commodo commodo sit amet eget diam. Curabitur rhoncus lacinia leo at sodales. Etiam finibus porta diam a viverra. Praesent nisi urna, volutpat sit amet odio at, vehicula vehicula leo. In non enim eget nisl luctus commodo. Pellentesque pellentesque at lectus at luctus. Quisque nec felis bibendum, lacinia libero ut, lacinia eros. Integer finibus ultricies nibh sit amet placerat.</text:p> <text:p text:style-name="Text_20_body">Suspendisse faucibus est auctor orci mollis luctus. Praesent quis sodales neque. Interdum et malesuada fames ac ante ipsum primis in faucibus. Donec sodales rutrum mattis. In in sem ornare, consequat nulla ac, convallis arcu. Duis ac metus id felis commodo commodo sit amet eget diam. Curabitur rhoncus lacinia leo at sodales. Etiam finibus porta diam a viverra. Praesent nisi urna, volutpat sit amet odio at, vehicula vehicula leo. In non enim eget nisl luctus commodo. Pellentesque pellentesque at lectus at luctus. Quisque nec felis bibendum, lacinia libero ut, lacinia eros. Integer finibus ultricies nibh sit amet placerat.</text:p>
@@ -204,7 +204,7 @@
<text:p text:style-name="Text_20_body">Mauris ut erat eu lorem malesuada egestas vel vel urna. Maecenas ac semper quam. Maecenas aliquet metus non interdum mattis. Proin consectetur molestie ligula. Aliquam sollicitudin pulvinar urna a pellentesque. Suspendisse ultrices, est mattis scelerisque porta, nisi nisi laoreet nisl, non condimentum quam ante a velit. Proin scelerisque justo augue, nec laoreet ligula egestas at. Etiam enim quam, ultrices non accumsan hendrerit, elementum vel ligula. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nam efficitur odio libero, in vestibulum arcu aliquam at. Cras non vehicula augue. Integer lobortis, est vitae aliquam facilisis, metus ligula aliquet eros, at porttitor sem tortor eget massa. Aliquam varius scelerisque neque sed gravida. Aenean eleifend lorem id ante elementum sollicitudin. Proin commodo massa a quam volutpat, mollis fermentum turpis efficitur.</text:p> <text:p text:style-name="Text_20_body">Mauris ut erat eu lorem malesuada egestas vel vel urna. Maecenas ac semper quam. Maecenas aliquet metus non interdum mattis. Proin consectetur molestie ligula. Aliquam sollicitudin pulvinar urna a pellentesque. Suspendisse ultrices, est mattis scelerisque porta, nisi nisi laoreet nisl, non condimentum quam ante a velit. Proin scelerisque justo augue, nec laoreet ligula egestas at. Etiam enim quam, ultrices non accumsan hendrerit, elementum vel ligula. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nam efficitur odio libero, in vestibulum arcu aliquam at. Cras non vehicula augue. Integer lobortis, est vitae aliquam facilisis, metus ligula aliquet eros, at porttitor sem tortor eget massa. Aliquam varius scelerisque neque sed gravida. Aenean eleifend lorem id ante elementum sollicitudin. Proin commodo massa a quam volutpat, mollis fermentum turpis efficitur.</text:p>
<text:p text:style-name="P7">Notes: World</text:p> <text:p text:style-name="P7">Notes: World</text:p>
<text:h text:style-name="Heading_20_1" text:outline-level="1">Ancient Europe</text:h> <text:h text:style-name="Heading_20_1" text:outline-level="1">Ancient Europe</text:h>
<text:p text:style-name="Text_20_Meta"><text:span text:style-name="T1">Tag:</text:span> Europe</text:p> <text:p text:style-name="Text_20_Meta"><text:span text:style-name="T1">Tag:</text:span> Europe | Ancient Europe</text:p>
<text:p text:style-name="Text_20_body">Vivamus sodales risus ac accumsan posuere. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Nunc vel enim felis. Vestibulum dignissim massa nunc, a auctor magna eleifend et. Proin dignissim sodales erat vitae convallis. Aliquam id tellus dui. Curabitur sollicitudin scelerisque ex sit amet posuere. Nam rutrum felis id rhoncus feugiat. Duis sagittis quam quis purus efficitur, quis rutrum odio iaculis. Maecenas semper ante turpis, at vulputate mi consectetur non. Sed rutrum nibh turpis, quis rhoncus purus ornare quis. Vestibulum at rutrum mauris. Integer dolor nisi, tincidunt eget vehicula ac, ultricies at ligula.</text:p> <text:p text:style-name="Text_20_body">Vivamus sodales risus ac accumsan posuere. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Nunc vel enim felis. Vestibulum dignissim massa nunc, a auctor magna eleifend et. Proin dignissim sodales erat vitae convallis. Aliquam id tellus dui. Curabitur sollicitudin scelerisque ex sit amet posuere. Nam rutrum felis id rhoncus feugiat. Duis sagittis quam quis purus efficitur, quis rutrum odio iaculis. Maecenas semper ante turpis, at vulputate mi consectetur non. Sed rutrum nibh turpis, quis rhoncus purus ornare quis. Vestibulum at rutrum mauris. Integer dolor nisi, tincidunt eget vehicula ac, ultricies at ligula.</text:p>
<text:p text:style-name="Text_20_body">Aenean semper turpis quis varius rhoncus. Vivamus ac mi eget felis euismod vulputate. Nam eu tempus velit. Etiam ut est porta, finibus erat sit amet, consectetur felis. Nullam consequat felis ut lacus pharetra, in lobortis urna mollis. Nulla varius eros nec lorem rhoncus, sed venenatis risus ultrices. Phasellus pellentesque laoreet neque, ut ultricies lacus vulputate quis. In malesuada dui sit amet est interdum, eget consectetur mi gravida. Cras vel bibendum purus. Quisque commodo tempor arcu, non lacinia sem blandit eleifend. Quisque at neque gravida, porttitor metus a, suscipit diam. Quisque convallis sodales lacus et condimentum. Donec a suscipit diam. Pellentesque eget cursus neque.</text:p> <text:p text:style-name="Text_20_body">Aenean semper turpis quis varius rhoncus. Vivamus ac mi eget felis euismod vulputate. Nam eu tempus velit. Etiam ut est porta, finibus erat sit amet, consectetur felis. Nullam consequat felis ut lacus pharetra, in lobortis urna mollis. Nulla varius eros nec lorem rhoncus, sed venenatis risus ultrices. Phasellus pellentesque laoreet neque, ut ultricies lacus vulputate quis. In malesuada dui sit amet est interdum, eget consectetur mi gravida. Cras vel bibendum purus. Quisque commodo tempor arcu, non lacinia sem blandit eleifend. Quisque at neque gravida, porttitor metus a, suscipit diam. Quisque convallis sodales lacus et condimentum. Donec a suscipit diam. Pellentesque eget cursus neque.</text:p>
<text:p text:style-name="Text_20_body">Nunc ullamcorper magna quis elit condimentum rhoncus. Aenean dictum pulvinar dolor suscipit interdum. Aliquam elit massa, elementum nec cursus eu, maximus nec ipsum. Donec ullamcorper iaculis dolor eu commodo. Nunc eget tortor quis turpis consectetur varius. Vestibulum nec justo vel tellus venenatis condimentum. Duis auctor iaculis massa. Nunc risus magna, rutrum vitae eros non, tristique mollis enim.</text:p> <text:p text:style-name="Text_20_body">Nunc ullamcorper magna quis elit condimentum rhoncus. Aenean dictum pulvinar dolor suscipit interdum. Aliquam elit massa, elementum nec cursus eu, maximus nec ipsum. Donec ullamcorper iaculis dolor eu commodo. Nunc eget tortor quis turpis consectetur varius. Vestibulum nec justo vel tellus venenatis condimentum. Duis auctor iaculis massa. Nunc risus magna, rutrum vitae eros non, tristique mollis enim.</text:p>
@@ -150,7 +150,7 @@ Integer egestas maximus leo eu facilisis. Nunc rhoncus dignissim lectus eu lacin
# Nobody Owens # Nobody Owens
**Tag:** Bod **Tag:** Bod | Nobody Owens
**Plot:** Main **Plot:** Main
Pellentesque nec erat ut nulla posuere commodo. Curabitur nisi augue, imperdiet et porta imperdiet, efficitur id leo. Cras finibus arcu at nibh commodo congue. Proin suscipit placerat condimentum. Aenean ante enim, cursus id lorem a, blandit venenatis nibh. Maecenas suscipit porta elit, sit amet porta felis porttitor eu. Sed a dui nibh. Phasellus sed faucibus dui. Pellentesque felis nulla, ultrices non efficitur quis, rutrum id mi. Mauris tempus auctor nisl, in bibendum enim pellentesque sit amet. Proin nunc lacus, imperdiet nec posuere ac, interdum non lectus. Pellentesque nec erat ut nulla posuere commodo. Curabitur nisi augue, imperdiet et porta imperdiet, efficitur id leo. Cras finibus arcu at nibh commodo congue. Proin suscipit placerat condimentum. Aenean ante enim, cursus id lorem a, blandit venenatis nibh. Maecenas suscipit porta elit, sit amet porta felis porttitor eu. Sed a dui nibh. Phasellus sed faucibus dui. Pellentesque felis nulla, ultrices non efficitur quis, rutrum id mi. Mauris tempus auctor nisl, in bibendum enim pellentesque sit amet. Proin nunc lacus, imperdiet nec posuere ac, interdum non lectus.
@@ -173,7 +173,7 @@ Mauris ut erat eu lorem malesuada egestas vel vel urna. Maecenas ac semper quam.
# Ancient Europe # Ancient Europe
**Tag:** Europe **Tag:** Europe | Ancient Europe
Vivamus sodales risus ac accumsan posuere. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Nunc vel enim felis. Vestibulum dignissim massa nunc, a auctor magna eleifend et. Proin dignissim sodales erat vitae convallis. Aliquam id tellus dui. Curabitur sollicitudin scelerisque ex sit amet posuere. Nam rutrum felis id rhoncus feugiat. Duis sagittis quam quis purus efficitur, quis rutrum odio iaculis. Maecenas semper ante turpis, at vulputate mi consectetur non. Sed rutrum nibh turpis, quis rhoncus purus ornare quis. Vestibulum at rutrum mauris. Integer dolor nisi, tincidunt eget vehicula ac, ultricies at ligula. Vivamus sodales risus ac accumsan posuere. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Nunc vel enim felis. Vestibulum dignissim massa nunc, a auctor magna eleifend et. Proin dignissim sodales erat vitae convallis. Aliquam id tellus dui. Curabitur sollicitudin scelerisque ex sit amet posuere. Nam rutrum felis id rhoncus feugiat. Duis sagittis quam quis purus efficitur, quis rutrum odio iaculis. Maecenas semper ante turpis, at vulputate mi consectetur non. Sed rutrum nibh turpis, quis rhoncus purus ornare quis. Vestibulum at rutrum mauris. Integer dolor nisi, tincidunt eget vehicula ac, ultricies at ligula.
+8 -17
View File
@@ -33,9 +33,9 @@ from PyQt5.QtGui import QDesktopServices
from PyQt5.QtCore import QUrl from PyQt5.QtCore import QUrl
from novelwriter.common import ( from novelwriter.common import (
checkBool, checkFloat, checkHandle, checkInt, checkIntTuple, checkPath, checkBool, checkFloat, checkInt, checkIntTuple, checkPath, checkString,
checkString, checkStringNone, checkUuid, formatInt, formatTime, checkStringNone, checkUuid, formatInt, formatTime, formatTimeStamp,
formatTimeStamp, formatVersion, fuzzyTime, getFileSize, hexToInt, isHandle, isItemClass, formatVersion, fuzzyTime, getFileSize, hexToInt, isHandle, isItemClass,
isItemLayout, isItemType, isTitleTag, jsonEncode, makeFileNameSafe, minmax, isItemLayout, isItemType, isTitleTag, jsonEncode, makeFileNameSafe, minmax,
numberToRoman, NWConfigParser, openExternalPath, readTextFile, simplified, numberToRoman, NWConfigParser, openExternalPath, readTextFile, simplified,
transferCase, xmlIndent, yesNo transferCase, xmlIndent, yesNo
@@ -151,19 +151,6 @@ def testBaseCommon_checkBool():
# END Test testBaseCommon_checkBool # END Test testBaseCommon_checkBool
@pytest.mark.base
def testBaseCommon_checkHandle():
"""Test the checkHandle function."""
assert checkHandle("None", 1, True) is None
assert checkHandle("None", 1, False) == 1
assert checkHandle(None, 1, True) is None
assert checkHandle(None, 1, False) == 1
assert checkHandle("47666c91c7ccf", None, False) == "47666c91c7ccf"
assert checkHandle("h7666c91c7ccf", None, False) is None
# END Test testBaseCommon_checkHandle
@pytest.mark.base @pytest.mark.base
def testBaseCommon_checkUuid(): def testBaseCommon_checkUuid():
"""Test the checkUuid function.""" """Test the checkUuid function."""
@@ -236,7 +223,7 @@ def testBaseCommon_isItemClass():
# Invalid # Invalid
assert isItemClass("None") is False assert isItemClass("None") is False
assert isItemClass(None) is False # type: ignore assert isItemClass(None) is False
assert isItemClass("STUFF") is False assert isItemClass("STUFF") is False
# END Test testBaseCommon_isItemClass # END Test testBaseCommon_isItemClass
@@ -682,6 +669,7 @@ def testBaseCommon_NWConfigParser(fncPath):
"list1 = a, b, c\n" "list1 = a, b, c\n"
"list2 = 17, 18, 19\n" "list2 = 17, 18, 19\n"
"float1 = 4.2\n" "float1 = 4.2\n"
f"path1 = {fncPath}\n"
)) ))
cfgParser = NWConfigParser() cfgParser = NWConfigParser()
@@ -724,6 +712,9 @@ def testBaseCommon_NWConfigParser(fncPath):
assert cfgParser.rdFlt("nope", "intopt1", 13.0) == 13.0 assert cfgParser.rdFlt("nope", "intopt1", 13.0) == 13.0
assert cfgParser.rdFlt("main", "blabla", 13.0) == 13.0 assert cfgParser.rdFlt("main", "blabla", 13.0) == 13.0
# Read Path
assert cfgParser.rdPath("main", "path1", Path.home()) == fncPath
# Read String List # Read String List
assert cfgParser.rdStrList("main", "list1", []) == [] assert cfgParser.rdStrList("main", "list1", []) == []
assert cfgParser.rdStrList("main", "list1", ["x"]) == ["a"] assert cfgParser.rdStrList("main", "list1", ["x"]) == ["a"]
+87 -67
View File
@@ -269,26 +269,30 @@ def testCoreIndex_CheckThese(mockGUI, fncPath, mockRnd):
assert nItem.mainHeading == "H1" assert nItem.mainHeading == "H1"
# Zero Items # Zero Items
assert index.checkThese([], cItem) == [] assert index.checkThese([], cHandle) == []
# One Item # One Item
assert index.checkThese(["@tag"], cItem) == [True] assert index.checkThese(["@tag"], cHandle) == [True]
assert index.checkThese(["@who"], cItem) == [False] assert index.checkThese(["@who"], cHandle) == [False]
# Two Items # Two Items
assert index.checkThese(["@tag", "Jane"], cItem) == [True, True] assert index.checkThese(["@tag", "Jane"], cHandle) == [True, True]
assert index.checkThese(["@tag", "John"], cItem) == [True, True] assert index.checkThese(["@tag", "John"], cHandle) == [True, True]
assert index.checkThese(["@tag", "Jane"], nItem) == [True, False] assert index.checkThese(["@tag", "Jane"], nHandle) == [True, False]
assert index.checkThese(["@tag", "John"], nItem) == [True, True] assert index.checkThese(["@tag", "John"], nHandle) == [True, True]
assert index.checkThese(["@pov", "John"], nItem) == [True, False] assert index.checkThese(["@pov", "John"], nHandle) == [True, False]
assert index.checkThese(["@pov", "Jane"], nItem) == [True, True] assert index.checkThese(["@pov", "Jane"], nHandle) == [True, True]
assert index.checkThese(["@ pov", "Jane"], nItem) == [False, False] assert index.checkThese(["@ pov", "Jane"], nHandle) == [False, False]
assert index.checkThese(["@what", "Jane"], nItem) == [False, False] assert index.checkThese(["@what", "Jane"], nHandle) == [False, False]
# Three Items # Three Items
assert index.checkThese(["@tag", "Jane", "John"], cItem) == [True, True, False] assert index.checkThese(["@tag", "Jane", "John"], cHandle) == [True, True, False]
assert index.checkThese(["@who", "Jane", "John"], cItem) == [False, False, False] assert index.checkThese(["@who", "Jane", "John"], cHandle) == [False, False, False]
assert index.checkThese(["@pov", "Jane", "John"], nItem) == [True, True, False] assert index.checkThese(["@pov", "Jane", "John"], nHandle) == [True, True, False]
# Parse a Checked Value
assert index.parseValue("Jane | Jane Smith") == ("Jane", "Jane Smith")
assert index.parseValue("Jane | Jane Smith") == ("Jane", "Jane Smith")
project.closeProject() project.closeProject()
@@ -544,8 +548,10 @@ def testCoreIndex_ExtractData(mockGUI, fncPath, mockRnd):
nHandle = project.newFile("Hello", C.hNovelRoot) nHandle = project.newFile("Hello", C.hNovelRoot)
cHandle = project.newFile("Jane", C.hCharRoot) cHandle = project.newFile("Jane", C.hCharRoot)
dHandle = project.newFile("John", C.hCharRoot)
assert isinstance(nHandle, str) assert isinstance(nHandle, str)
assert isinstance(cHandle, str) assert isinstance(cHandle, str)
assert isinstance(dHandle, str)
assert index.getItemHeader("", "") is None assert index.getItemHeader("", "") is None
assert index.getItemHeader(C.hNovelRoot, "") is None assert index.getItemHeader(C.hNovelRoot, "") is None
@@ -554,10 +560,14 @@ def testCoreIndex_ExtractData(mockGUI, fncPath, mockRnd):
"# Jane Smith\n" "# Jane Smith\n"
"@tag: Jane\n" "@tag: Jane\n"
)) ))
assert index.scanText(dHandle, (
"# John Smith\n"
"@tag: John\n"
))
assert index.scanText(nHandle, ( assert index.scanText(nHandle, (
"# Hello World!\n" "# Hello World!\n"
"@pov: Jane\n" "@pov: Jane\n"
"@char: Jane\n\n" "@char: Jane, John\n\n"
"% this is a comment\n\n" "% this is a comment\n\n"
"This is a story about Jane Smith.\n\n" "This is a story about Jane Smith.\n\n"
"Well, not really.\n" "Well, not really.\n"
@@ -625,7 +635,14 @@ def testCoreIndex_ExtractData(mockGUI, fncPath, mockRnd):
# The novel file should now refer to Jane as @pov and @char # The novel file should now refer to Jane as @pov and @char
refs = index.getReferences(nHandle) refs = index.getReferences(nHandle)
assert refs["@pov"] == ["Jane"] assert refs["@pov"] == ["Jane"]
assert refs["@char"] == ["Jane"] assert refs["@char"] == ["Jane", "John"]
# getReferenceForHeader
# =====================
assert index.getReferenceForHeader(nHandle, 1, "@pov") == ["Jane"]
assert index.getReferenceForHeader(nHandle, 1, "@char") == ["Jane", "John"]
assert index.getReferenceForHeader(nHandle, 1, "@focus") == []
assert index.getReferenceForHeader("00000", 1, "@focus") == []
# getBackReferenceList # getBackReferenceList
# ==================== # ====================
@@ -644,7 +661,8 @@ def testCoreIndex_ExtractData(mockGUI, fncPath, mockRnd):
# ============ # ============
assert index.getTagSource("Jane") == (cHandle, "T0001") assert index.getTagSource("Jane") == (cHandle, "T0001")
assert index.getTagSource("John") == (None, "T0000") assert index.getTagSource("John") == (dHandle, "T0001")
assert index.getTagSource("Hans") == (None, "T0000")
# getDocumentTags # getDocumentTags
# =============== # ===============
@@ -653,7 +671,7 @@ def testCoreIndex_ExtractData(mockGUI, fncPath, mockRnd):
# getClassTags # getClassTags
# ============ # ============
assert index.getClassTags(nwItemClass.CHARACTER) == ["Jane"] assert index.getClassTags(nwItemClass.CHARACTER) == ["Jane", "John"]
# getTagsData # getTagsData
# =========== # ===========
@@ -661,6 +679,10 @@ def testCoreIndex_ExtractData(mockGUI, fncPath, mockRnd):
"jane", "Jane", "CHARACTER", "jane", "Jane", "CHARACTER",
index.getItemData(cHandle), index.getItemData(cHandle),
index.getItemHeader(cHandle, "T0001") index.getItemHeader(cHandle, "T0001")
), (
"john", "John", "CHARACTER",
index.getItemData(dHandle),
index.getItemHeader(dHandle, "T0001")
)] )]
# getSingleTag # getSingleTag
@@ -849,18 +871,21 @@ def testCoreIndex_TagsIndex():
content = { content = {
"tag1": { "tag1": {
"name": "Tag1", "name": "Tag1",
"display": "Tag 1",
"handle": "0000000000001", "handle": "0000000000001",
"heading": "T0001", "heading": "T0001",
"class": nwItemClass.NOVEL.name, "class": nwItemClass.NOVEL.name,
}, },
"tag2": { "tag2": {
"name": "Tag2", "name": "Tag2",
"display": "Tag 2",
"handle": "0000000000002", "handle": "0000000000002",
"heading": "T0002", "heading": "T0002",
"class": nwItemClass.CHARACTER.name, "class": nwItemClass.CHARACTER.name,
}, },
"tag3": { "tag3": {
"name": "Tag3", "name": "Tag3",
"display": "Tag 3",
"handle": "0000000000003", "handle": "0000000000003",
"heading": "T0003", "heading": "T0003",
"class": nwItemClass.PLOT.name, "class": nwItemClass.PLOT.name,
@@ -868,9 +893,9 @@ def testCoreIndex_TagsIndex():
} }
# Add data # Add data
tagsIndex.add("Tag1", "0000000000001", "T0001", nwItemClass.NOVEL) tagsIndex.add("Tag1", "Tag 1", "0000000000001", "T0001", "NOVEL")
tagsIndex.add("Tag2", "0000000000002", "T0002", nwItemClass.CHARACTER) tagsIndex.add("Tag2", "Tag 2", "0000000000002", "T0002", "CHARACTER")
tagsIndex.add("Tag3", "0000000000003", "T0003", nwItemClass.PLOT) tagsIndex.add("Tag3", "Tag 3", "0000000000003", "T0003", "PLOT")
assert tagsIndex._tags == content assert tagsIndex._tags == content
# Get items # Get items
@@ -885,6 +910,18 @@ def testCoreIndex_TagsIndex():
assert "Tag3" in tagsIndex assert "Tag3" in tagsIndex
assert "Tag4" not in tagsIndex assert "Tag4" not in tagsIndex
# Read back names
assert tagsIndex.tagName("Tag1") == "Tag1"
assert tagsIndex.tagName("Tag2") == "Tag2"
assert tagsIndex.tagName("Tag3") == "Tag3"
assert tagsIndex.tagName("Tag4") == ""
# Read back display names
assert tagsIndex.tagDisplay("Tag1") == "Tag 1"
assert tagsIndex.tagDisplay("Tag2") == "Tag 2"
assert tagsIndex.tagDisplay("Tag3") == "Tag 3"
assert tagsIndex.tagDisplay("Tag4") == ""
# Read back handles # Read back handles
assert tagsIndex.tagHandle("Tag1") == "0000000000001" assert tagsIndex.tagHandle("Tag1") == "0000000000001"
assert tagsIndex.tagHandle("Tag2") == "0000000000002" assert tagsIndex.tagHandle("Tag2") == "0000000000002"
@@ -935,59 +972,39 @@ def testCoreIndex_TagsIndex():
with pytest.raises(ValueError): with pytest.raises(ValueError):
tagsIndex.unpackData({ tagsIndex.unpackData({
1234: { 1234: {
"name": "1234",
"handle": "0000000000001",
"heading": "T0001",
"class": "NOVEL",
}
})
# Missing name
with pytest.raises(KeyError):
tagsIndex.unpackData({
"tag1": {
"handle": "0000000000001",
"heading": "T0001",
"class": "NOVEL",
}
})
# Missing handle
with pytest.raises(KeyError):
tagsIndex.unpackData({
"tag1": {
"name": "Tag1", "name": "Tag1",
"display": "Tag1",
"handle": "0000000000001",
"heading": "T0001", "heading": "T0001",
"class": "NOVEL", "class": "NOVEL",
} }
}) })
# Missing heading # Invalid entry
with pytest.raises(KeyError):
tagsIndex.unpackData({
"tag1": {
"name": "Tag1",
"handle": "0000000000001",
"class": "NOVEL",
}
})
# Missing class
with pytest.raises(KeyError):
tagsIndex.unpackData({
"tag1": {
"name": "Tag1",
"handle": "0000000000001",
"heading": "T0001",
}
})
# Invalid key case
with pytest.raises(ValueError): with pytest.raises(ValueError):
tagsIndex.unpackData({ tagsIndex.unpackData({
"Tag1": { "tag1": None
})
# Invalid name
with pytest.raises(ValueError):
tagsIndex.unpackData({
"tag1": {
"name": 1234,
"display": "Tag1",
"handle": "0000000000001",
"heading": "T0001",
"class": "NOVEL",
}
})
# Invalid display
with pytest.raises(ValueError):
tagsIndex.unpackData({
"tag1": {
"name": "Tag1", "name": "Tag1",
"handle": "blablabla", "display": 1234,
"handle": "0000000000001",
"heading": "T0001", "heading": "T0001",
"class": "NOVEL", "class": "NOVEL",
} }
@@ -998,6 +1015,7 @@ def testCoreIndex_TagsIndex():
tagsIndex.unpackData({ tagsIndex.unpackData({
"tag1": { "tag1": {
"name": "Tag1", "name": "Tag1",
"display": "Tag1",
"handle": "blablabla", "handle": "blablabla",
"heading": "T0001", "heading": "T0001",
"class": "NOVEL", "class": "NOVEL",
@@ -1009,8 +1027,9 @@ def testCoreIndex_TagsIndex():
tagsIndex.unpackData({ tagsIndex.unpackData({
"tag1": { "tag1": {
"name": "Tag1", "name": "Tag1",
"display": "Tag1",
"handle": "0000000000001", "handle": "0000000000001",
"heading": "blabla", "heading": "stuff",
"class": "NOVEL", "class": "NOVEL",
} }
}) })
@@ -1020,9 +1039,10 @@ def testCoreIndex_TagsIndex():
tagsIndex.unpackData({ tagsIndex.unpackData({
"tag1": { "tag1": {
"name": "Tag1", "name": "Tag1",
"display": "Tag1",
"handle": "0000000000001", "handle": "0000000000001",
"heading": "T0001", "heading": "T0001",
"class": "blabla", "class": "STUFF",
} }
}) })
+262 -160
View File
@@ -27,7 +27,7 @@ from tools import C, buildTestProject, readFile
from novelwriter.constants import nwHeadFmt from novelwriter.constants import nwHeadFmt
from novelwriter.core.project import NWProject from novelwriter.core.project import NWProject
from novelwriter.core.tokenizer import Tokenizer, stripEscape from novelwriter.core.tokenizer import HeadingFormatter, Tokenizer, stripEscape
class BareTokenizer(Tokenizer): class BareTokenizer(Tokenizer):
@@ -170,14 +170,14 @@ def testCoreToken_TextOps(monkeypatch, mockGUI, mockRnd, fncPath):
assert tokens.addRootHeading(C.hPlotRoot) is True assert tokens.addRootHeading(C.hPlotRoot) is True
assert tokens.allMarkdown[-1] == "# Notes: Plot\n\n" assert tokens.allMarkdown[-1] == "# Notes: Plot\n\n"
assert tokens._tokens[-1] == ( assert tokens._tokens[-1] == (
Tokenizer.T_TITLE, 0, "Notes: Plot", None, Tokenizer.A_CENTRE Tokenizer.T_TITLE, 0, "Notes: Plot", [], Tokenizer.A_CENTRE
) )
# Not First Page # Not First Page
assert tokens.addRootHeading(C.hPlotRoot) is True assert tokens.addRootHeading(C.hPlotRoot) is True
assert tokens.allMarkdown[-1] == "# Notes: Plot\n\n" assert tokens.allMarkdown[-1] == "# Notes: Plot\n\n"
assert tokens._tokens[-1] == ( assert tokens._tokens[-1] == (
Tokenizer.T_TITLE, 0, "Notes: Plot", None, Tokenizer.A_CENTRE | Tokenizer.A_PBB Tokenizer.T_TITLE, 0, "Notes: Plot", [], Tokenizer.A_CENTRE | Tokenizer.A_PBB
) )
# Set Text # Set Text
@@ -247,8 +247,8 @@ def testCoreToken_HeaderFormat(mockGUI):
tokens.tokenizeText() tokens.tokenizeText()
assert tokens._tokens == [ assert tokens._tokens == [
(Tokenizer.T_TITLE, 1, "Novel Title", None, Tokenizer.A_CENTRE), (Tokenizer.T_TITLE, 1, "Novel Title", [], Tokenizer.A_CENTRE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE),
] ]
assert tokens.allMarkdown[-1] == "#! Novel Title\n\n" assert tokens.allMarkdown[-1] == "#! Novel Title\n\n"
@@ -260,8 +260,8 @@ def testCoreToken_HeaderFormat(mockGUI):
tokens.tokenizeText() tokens.tokenizeText()
assert tokens._tokens == [ assert tokens._tokens == [
(Tokenizer.T_HEAD1, 1, "Note Title", None, Tokenizer.A_CENTRE), (Tokenizer.T_HEAD1, 1, "Note Title", [], Tokenizer.A_CENTRE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE),
] ]
assert tokens.allMarkdown[-1] == "#! Note Title\n\n" assert tokens.allMarkdown[-1] == "#! Note Title\n\n"
@@ -276,8 +276,8 @@ def testCoreToken_HeaderFormat(mockGUI):
tokens.tokenizeText() tokens.tokenizeText()
assert tokens._tokens == [ assert tokens._tokens == [
(Tokenizer.T_HEAD1, 1, "Novel Title", None, Tokenizer.A_CENTRE), (Tokenizer.T_HEAD1, 1, "Novel Title", [], Tokenizer.A_CENTRE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE),
] ]
assert tokens.allMarkdown[-1] == "# Novel Title\n\n" assert tokens.allMarkdown[-1] == "# Novel Title\n\n"
@@ -289,8 +289,8 @@ def testCoreToken_HeaderFormat(mockGUI):
tokens.tokenizeText() tokens.tokenizeText()
assert tokens._tokens == [ assert tokens._tokens == [
(Tokenizer.T_HEAD1, 1, "Note Title", None, Tokenizer.A_NONE), (Tokenizer.T_HEAD1, 1, "Note Title", [], Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE),
] ]
assert tokens.allMarkdown[-1] == "# Note Title\n\n" assert tokens.allMarkdown[-1] == "# Note Title\n\n"
@@ -304,8 +304,8 @@ def testCoreToken_HeaderFormat(mockGUI):
tokens.tokenizeText() tokens.tokenizeText()
assert tokens._tokens == [ assert tokens._tokens == [
(Tokenizer.T_HEAD2, 1, "Chapter One", None, Tokenizer.A_PBB), (Tokenizer.T_HEAD2, 1, "Chapter One", [], Tokenizer.A_PBB),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE),
] ]
assert tokens.allMarkdown[-1] == "## Chapter One\n\n" assert tokens.allMarkdown[-1] == "## Chapter One\n\n"
@@ -316,8 +316,8 @@ def testCoreToken_HeaderFormat(mockGUI):
tokens.tokenizeText() tokens.tokenizeText()
assert tokens._tokens == [ assert tokens._tokens == [
(Tokenizer.T_HEAD2, 1, "Heading 2", None, Tokenizer.A_NONE), (Tokenizer.T_HEAD2, 1, "Heading 2", [], Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE),
] ]
assert tokens.allMarkdown[-1] == "## Heading 2\n\n" assert tokens.allMarkdown[-1] == "## Heading 2\n\n"
@@ -331,8 +331,8 @@ def testCoreToken_HeaderFormat(mockGUI):
tokens.tokenizeText() tokens.tokenizeText()
assert tokens._tokens == [ assert tokens._tokens == [
(Tokenizer.T_HEAD3, 1, "Scene One", None, Tokenizer.A_NONE), (Tokenizer.T_HEAD3, 1, "Scene One", [], Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE),
] ]
assert tokens.allMarkdown[-1] == "### Scene One\n\n" assert tokens.allMarkdown[-1] == "### Scene One\n\n"
@@ -343,8 +343,8 @@ def testCoreToken_HeaderFormat(mockGUI):
tokens.tokenizeText() tokens.tokenizeText()
assert tokens._tokens == [ assert tokens._tokens == [
(Tokenizer.T_HEAD3, 1, "Heading 3", None, Tokenizer.A_NONE), (Tokenizer.T_HEAD3, 1, "Heading 3", [], Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE),
] ]
assert tokens.allMarkdown[-1] == "### Heading 3\n\n" assert tokens.allMarkdown[-1] == "### Heading 3\n\n"
@@ -358,8 +358,8 @@ def testCoreToken_HeaderFormat(mockGUI):
tokens.tokenizeText() tokens.tokenizeText()
assert tokens._tokens == [ assert tokens._tokens == [
(Tokenizer.T_HEAD4, 1, "A Section", None, Tokenizer.A_NONE), (Tokenizer.T_HEAD4, 1, "A Section", [], Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE),
] ]
assert tokens.allMarkdown[-1] == "#### A Section\n\n" assert tokens.allMarkdown[-1] == "#### A Section\n\n"
@@ -370,8 +370,8 @@ def testCoreToken_HeaderFormat(mockGUI):
tokens.tokenizeText() tokens.tokenizeText()
assert tokens._tokens == [ assert tokens._tokens == [
(Tokenizer.T_HEAD4, 1, "Heading 4", None, Tokenizer.A_NONE), (Tokenizer.T_HEAD4, 1, "Heading 4", [], Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE),
] ]
assert tokens.allMarkdown[-1] == "#### Heading 4\n\n" assert tokens.allMarkdown[-1] == "#### Heading 4\n\n"
@@ -385,8 +385,8 @@ def testCoreToken_HeaderFormat(mockGUI):
tokens.tokenizeText() tokens.tokenizeText()
assert tokens._tokens == [ assert tokens._tokens == [
(Tokenizer.T_TITLE, 1, "Title", None, Tokenizer.A_CENTRE), (Tokenizer.T_TITLE, 1, "Title", [], Tokenizer.A_CENTRE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE),
] ]
assert tokens.allMarkdown[-1] == "#! Title\n\n" assert tokens.allMarkdown[-1] == "#! Title\n\n"
@@ -397,8 +397,8 @@ def testCoreToken_HeaderFormat(mockGUI):
tokens.tokenizeText() tokens.tokenizeText()
assert tokens._tokens == [ assert tokens._tokens == [
(Tokenizer.T_HEAD1, 1, "Title", None, Tokenizer.A_CENTRE), (Tokenizer.T_HEAD1, 1, "Title", [], Tokenizer.A_CENTRE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE),
] ]
assert tokens.allMarkdown[-1] == "#! Title\n\n" assert tokens.allMarkdown[-1] == "#! Title\n\n"
@@ -412,8 +412,8 @@ def testCoreToken_HeaderFormat(mockGUI):
tokens.tokenizeText() tokens.tokenizeText()
assert tokens._tokens == [ assert tokens._tokens == [
(Tokenizer.T_UNNUM, 1, "Prologue", None, Tokenizer.A_PBB), (Tokenizer.T_UNNUM, 1, "Prologue", [], Tokenizer.A_PBB),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE),
] ]
assert tokens.allMarkdown[-1] == "##! Prologue\n\n" assert tokens.allMarkdown[-1] == "##! Prologue\n\n"
@@ -424,8 +424,8 @@ def testCoreToken_HeaderFormat(mockGUI):
tokens.tokenizeText() tokens.tokenizeText()
assert tokens._tokens == [ assert tokens._tokens == [
(Tokenizer.T_HEAD2, 1, "Prologue", None, Tokenizer.A_NONE), (Tokenizer.T_HEAD2, 1, "Prologue", [], Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE),
] ]
assert tokens.allMarkdown[-1] == "##! Prologue\n\n" assert tokens.allMarkdown[-1] == "##! Prologue\n\n"
@@ -443,8 +443,8 @@ def testCoreToken_MetaFormat(mockGUI):
tokens._text = "% A comment\n" tokens._text = "% A comment\n"
tokens.tokenizeText() tokens.tokenizeText()
assert tokens._tokens == [ assert tokens._tokens == [
(Tokenizer.T_COMMENT, 0, "A comment", None, Tokenizer.A_NONE), (Tokenizer.T_COMMENT, 0, "A comment", [], Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 0, "", [], Tokenizer.A_NONE),
] ]
assert tokens.allMarkdown[-1] == "\n" assert tokens.allMarkdown[-1] == "\n"
@@ -456,14 +456,14 @@ def testCoreToken_MetaFormat(mockGUI):
tokens._text = "%synopsis: The synopsis\n" tokens._text = "%synopsis: The synopsis\n"
tokens.tokenizeText() tokens.tokenizeText()
assert tokens._tokens == [ assert tokens._tokens == [
(Tokenizer.T_SYNOPSIS, 0, "The synopsis", None, Tokenizer.A_NONE), (Tokenizer.T_SYNOPSIS, 0, "The synopsis", [], Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 0, "", [], Tokenizer.A_NONE),
] ]
tokens._text = "% synopsis: The synopsis\n" tokens._text = "% synopsis: The synopsis\n"
tokens.tokenizeText() tokens.tokenizeText()
assert tokens._tokens == [ assert tokens._tokens == [
(Tokenizer.T_SYNOPSIS, 0, "The synopsis", None, Tokenizer.A_NONE), (Tokenizer.T_SYNOPSIS, 0, "The synopsis", [], Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 0, "", [], Tokenizer.A_NONE),
] ]
assert tokens.allMarkdown[-1] == "\n" assert tokens.allMarkdown[-1] == "\n"
@@ -476,8 +476,8 @@ def testCoreToken_MetaFormat(mockGUI):
tokens._text = "% short: A short description\n" tokens._text = "% short: A short description\n"
tokens.tokenizeText() tokens.tokenizeText()
assert tokens._tokens == [ assert tokens._tokens == [
(Tokenizer.T_SHORT, 0, "A short description", None, Tokenizer.A_NONE), (Tokenizer.T_SHORT, 0, "A short description", [], Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 0, "", [], Tokenizer.A_NONE),
] ]
assert tokens.allMarkdown[-1] == "\n" assert tokens.allMarkdown[-1] == "\n"
@@ -489,8 +489,8 @@ def testCoreToken_MetaFormat(mockGUI):
tokens._text = "@char: Bod\n" tokens._text = "@char: Bod\n"
tokens.tokenizeText() tokens.tokenizeText()
assert tokens._tokens == [ assert tokens._tokens == [
(Tokenizer.T_KEYWORD, 0, "char: Bod", None, Tokenizer.A_NONE), (Tokenizer.T_KEYWORD, 0, "char: Bod", [], Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 0, "", [], Tokenizer.A_NONE),
] ]
assert tokens.allMarkdown[-1] == "\n" assert tokens.allMarkdown[-1] == "\n"
@@ -504,10 +504,10 @@ def testCoreToken_MetaFormat(mockGUI):
styMid = Tokenizer.A_NONE | Tokenizer.A_Z_BTMMRG | Tokenizer.A_Z_TOPMRG styMid = Tokenizer.A_NONE | Tokenizer.A_Z_BTMMRG | Tokenizer.A_Z_TOPMRG
styBtm = Tokenizer.A_NONE | Tokenizer.A_Z_TOPMRG styBtm = Tokenizer.A_NONE | Tokenizer.A_Z_TOPMRG
assert tokens._tokens == [ assert tokens._tokens == [
(Tokenizer.T_KEYWORD, 0, "pov: Bod", None, styTop), (Tokenizer.T_KEYWORD, 0, "pov: Bod", [], styTop),
(Tokenizer.T_KEYWORD, 0, "plot: Main", None, styMid), (Tokenizer.T_KEYWORD, 0, "plot: Main", [], styMid),
(Tokenizer.T_KEYWORD, 0, "location: Europe", None, styBtm), (Tokenizer.T_KEYWORD, 0, "location: Europe", [], styBtm),
(Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 0, "", [], Tokenizer.A_NONE),
] ]
assert tokens.allMarkdown[-1] == "@pov: Bod\n@plot: Main\n@location: Europe\n\n" assert tokens.allMarkdown[-1] == "@pov: Bod\n@plot: Main\n@location: Europe\n\n"
@@ -537,22 +537,22 @@ def testCoreToken_MarginFormat(mockGUI):
tokens.tokenizeText() tokens.tokenizeText()
assert tokens._tokens == [ assert tokens._tokens == [
(Tokenizer.T_TEXT, 0, "Some regular text", [], Tokenizer.A_NONE), (Tokenizer.T_TEXT, 0, "Some regular text", [], Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 0, "", [], Tokenizer.A_NONE),
(Tokenizer.T_TEXT, 0, "Some left-aligned text", [], Tokenizer.A_LEFT), (Tokenizer.T_TEXT, 0, "Some left-aligned text", [], Tokenizer.A_LEFT),
(Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 0, "", [], Tokenizer.A_NONE),
(Tokenizer.T_TEXT, 0, "Some right-aligned text", [], Tokenizer.A_RIGHT), (Tokenizer.T_TEXT, 0, "Some right-aligned text", [], Tokenizer.A_RIGHT),
(Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 0, "", [], Tokenizer.A_NONE),
(Tokenizer.T_TEXT, 0, "Some centered text", [], Tokenizer.A_CENTRE), (Tokenizer.T_TEXT, 0, "Some centered text", [], Tokenizer.A_CENTRE),
(Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 0, "", [], Tokenizer.A_NONE),
(Tokenizer.T_TEXT, 0, "Left-indented block", [], Tokenizer.A_IND_L), (Tokenizer.T_TEXT, 0, "Left-indented block", [], Tokenizer.A_IND_L),
(Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 0, "", [], Tokenizer.A_NONE),
(Tokenizer.T_TEXT, 0, "Right-indented block", [], Tokenizer.A_IND_R), (Tokenizer.T_TEXT, 0, "Right-indented block", [], Tokenizer.A_IND_R),
(Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 0, "", [], Tokenizer.A_NONE),
(Tokenizer.T_TEXT, 0, "Double-indented block", [], dblIndent), (Tokenizer.T_TEXT, 0, "Double-indented block", [], dblIndent),
(Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 0, "", [], Tokenizer.A_NONE),
(Tokenizer.T_TEXT, 0, "Right-indent, right-aligned", [], rIndAlign), (Tokenizer.T_TEXT, 0, "Right-indent, right-aligned", [], rIndAlign),
(Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 0, "", [], Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 0, "", [], Tokenizer.A_NONE),
] ]
assert tokens.allMarkdown[-1] == ( assert tokens.allMarkdown[-1] == (
"Some regular text\n\n" "Some regular text\n\n"
@@ -672,18 +672,18 @@ def testCoreToken_TextFormat(mockGUI):
assert tokens._tokens == [ assert tokens._tokens == [
(Tokenizer.T_TEXT, 0, "Some plain text", [], Tokenizer.A_NONE), (Tokenizer.T_TEXT, 0, "Some plain text", [], Tokenizer.A_NONE),
(Tokenizer.T_TEXT, 0, "on two lines", [], Tokenizer.A_NONE), (Tokenizer.T_TEXT, 0, "on two lines", [], Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 0, "", [], Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 0, "", [], Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 0, "", [], Tokenizer.A_NONE),
] ]
assert tokens.allMarkdown[-1] == "Some plain text\non two lines\n\n\n\n" assert tokens.allMarkdown[-1] == "Some plain text\non two lines\n\n\n\n"
tokens.setBodyText(False) tokens.setBodyText(False)
tokens.tokenizeText() tokens.tokenizeText()
assert tokens._tokens == [ assert tokens._tokens == [
(Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 0, "", [], Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 0, "", [], Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 0, "", [], Tokenizer.A_NONE),
] ]
assert tokens.allMarkdown[-1] == "\n\n\n" assert tokens.allMarkdown[-1] == "\n\n\n"
tokens.setBodyText(True) tokens.setBodyText(True)
@@ -701,7 +701,7 @@ def testCoreToken_TextFormat(mockGUI):
], ],
Tokenizer.A_NONE Tokenizer.A_NONE
), ),
(Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 0, "", [], Tokenizer.A_NONE),
] ]
assert tokens.allMarkdown[-1] == "Some **bolded text** on this lines\n\n" assert tokens.allMarkdown[-1] == "Some **bolded text** on this lines\n\n"
@@ -717,7 +717,7 @@ def testCoreToken_TextFormat(mockGUI):
], ],
Tokenizer.A_NONE Tokenizer.A_NONE
), ),
(Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 0, "", [], Tokenizer.A_NONE),
] ]
assert tokens.allMarkdown[-1] == "Some _italic text_ on this lines\n\n" assert tokens.allMarkdown[-1] == "Some _italic text_ on this lines\n\n"
@@ -735,7 +735,7 @@ def testCoreToken_TextFormat(mockGUI):
], ],
Tokenizer.A_NONE Tokenizer.A_NONE
), ),
(Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 0, "", [], Tokenizer.A_NONE),
] ]
assert tokens.allMarkdown[-1] == "Some **_bold italic text_** on this lines\n\n" assert tokens.allMarkdown[-1] == "Some **_bold italic text_** on this lines\n\n"
@@ -751,7 +751,7 @@ def testCoreToken_TextFormat(mockGUI):
], ],
Tokenizer.A_NONE Tokenizer.A_NONE
), ),
(Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 0, "", [], Tokenizer.A_NONE),
] ]
assert tokens.allMarkdown[-1] == "Some ~~strikethrough text~~ on this lines\n\n" assert tokens.allMarkdown[-1] == "Some ~~strikethrough text~~ on this lines\n\n"
@@ -771,7 +771,7 @@ def testCoreToken_TextFormat(mockGUI):
], ],
Tokenizer.A_NONE Tokenizer.A_NONE
), ),
(Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 0, "", [], Tokenizer.A_NONE),
] ]
assert tokens.allMarkdown[-1] == ( assert tokens.allMarkdown[-1] == (
"Some **nested bold and _italic_ and ~~strikethrough~~ text** here\n\n" "Some **nested bold and _italic_ and ~~strikethrough~~ text** here\n\n"
@@ -792,12 +792,12 @@ def testCoreToken_SpecialFormat(mockGUI):
# ======== # ========
correctResp = [ correctResp = [
(Tokenizer.T_HEAD1, 1, "Title One", None, Tokenizer.A_CENTRE), (Tokenizer.T_HEAD1, 1, "Title One", [], Tokenizer.A_CENTRE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE),
(Tokenizer.T_HEAD1, 2, "Title Two", None, Tokenizer.A_CENTRE | Tokenizer.A_PBB), (Tokenizer.T_HEAD1, 2, "Title Two", [], Tokenizer.A_CENTRE | Tokenizer.A_PBB),
(Tokenizer.T_EMPTY, 2, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 2, "", [], Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 2, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 2, "", [], Tokenizer.A_NONE),
] ]
# Command wo/Space # Command wo/Space
@@ -840,13 +840,13 @@ def testCoreToken_SpecialFormat(mockGUI):
) )
tokens.tokenizeText() tokens.tokenizeText()
assert tokens._tokens == [ assert tokens._tokens == [
(Tokenizer.T_HEAD1, 1, "Title One", None, Tokenizer.A_PBB | Tokenizer.A_CENTRE), (Tokenizer.T_HEAD1, 1, "Title One", [], Tokenizer.A_PBB | Tokenizer.A_CENTRE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE),
(Tokenizer.T_SKIP, 1, "", None, Tokenizer.A_NONE), (Tokenizer.T_SKIP, 1, "", [], Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE),
(Tokenizer.T_TEXT, 1, "Some text to go here ...", [], Tokenizer.A_NONE), (Tokenizer.T_TEXT, 1, "Some text to go here ...", [], Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE),
] ]
# Multiple Empty Paragraphs # Multiple Empty Paragraphs
@@ -860,13 +860,13 @@ def testCoreToken_SpecialFormat(mockGUI):
) )
tokens.tokenizeText() tokens.tokenizeText()
assert tokens._tokens == [ assert tokens._tokens == [
(Tokenizer.T_HEAD1, 1, "Title One", None, Tokenizer.A_PBB | Tokenizer.A_CENTRE), (Tokenizer.T_HEAD1, 1, "Title One", [], Tokenizer.A_PBB | Tokenizer.A_CENTRE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE),
(Tokenizer.T_SKIP, 1, "", None, Tokenizer.A_NONE), (Tokenizer.T_SKIP, 1, "", [], Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE),
(Tokenizer.T_TEXT, 1, "Some text to go here ...", [], Tokenizer.A_NONE), (Tokenizer.T_TEXT, 1, "Some text to go here ...", [], Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE),
] ]
# Three Skips # Three Skips
@@ -877,15 +877,15 @@ def testCoreToken_SpecialFormat(mockGUI):
) )
tokens.tokenizeText() tokens.tokenizeText()
assert tokens._tokens == [ assert tokens._tokens == [
(Tokenizer.T_HEAD1, 1, "Title One", None, Tokenizer.A_PBB | Tokenizer.A_CENTRE), (Tokenizer.T_HEAD1, 1, "Title One", [], Tokenizer.A_PBB | Tokenizer.A_CENTRE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE),
(Tokenizer.T_SKIP, 1, "", None, Tokenizer.A_NONE), (Tokenizer.T_SKIP, 1, "", [], Tokenizer.A_NONE),
(Tokenizer.T_SKIP, 1, "", None, Tokenizer.A_NONE), (Tokenizer.T_SKIP, 1, "", [], Tokenizer.A_NONE),
(Tokenizer.T_SKIP, 1, "", None, Tokenizer.A_NONE), (Tokenizer.T_SKIP, 1, "", [], Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE),
(Tokenizer.T_TEXT, 1, "Some text to go here ...", [], Tokenizer.A_NONE), (Tokenizer.T_TEXT, 1, "Some text to go here ...", [], Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE),
] ]
# Malformed Command, Case 1 # Malformed Command, Case 1
@@ -896,12 +896,12 @@ def testCoreToken_SpecialFormat(mockGUI):
) )
tokens.tokenizeText() tokens.tokenizeText()
assert tokens._tokens == [ assert tokens._tokens == [
(Tokenizer.T_HEAD1, 1, "Title One", None, Tokenizer.A_PBB | Tokenizer.A_CENTRE), (Tokenizer.T_HEAD1, 1, "Title One", [], Tokenizer.A_PBB | Tokenizer.A_CENTRE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE),
(Tokenizer.T_TEXT, 1, "Some text to go here ...", [], Tokenizer.A_NONE), (Tokenizer.T_TEXT, 1, "Some text to go here ...", [], Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE),
] ]
# Malformed Command, Case 2 # Malformed Command, Case 2
@@ -912,12 +912,12 @@ def testCoreToken_SpecialFormat(mockGUI):
) )
tokens.tokenizeText() tokens.tokenizeText()
assert tokens._tokens == [ assert tokens._tokens == [
(Tokenizer.T_HEAD1, 1, "Title One", None, Tokenizer.A_PBB | Tokenizer.A_CENTRE), (Tokenizer.T_HEAD1, 1, "Title One", [], Tokenizer.A_PBB | Tokenizer.A_CENTRE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE),
(Tokenizer.T_TEXT, 1, "Some text to go here ...", [], Tokenizer.A_NONE), (Tokenizer.T_TEXT, 1, "Some text to go here ...", [], Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE),
] ]
# Malformed Command, Case 3 # Malformed Command, Case 3
@@ -928,12 +928,12 @@ def testCoreToken_SpecialFormat(mockGUI):
) )
tokens.tokenizeText() tokens.tokenizeText()
assert tokens._tokens == [ assert tokens._tokens == [
(Tokenizer.T_HEAD1, 1, "Title One", None, Tokenizer.A_PBB | Tokenizer.A_CENTRE), (Tokenizer.T_HEAD1, 1, "Title One", [], Tokenizer.A_PBB | Tokenizer.A_CENTRE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE),
(Tokenizer.T_TEXT, 1, "Some text to go here ...", [], Tokenizer.A_NONE), (Tokenizer.T_TEXT, 1, "Some text to go here ...", [], Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE),
] ]
# Empty Paragraph and Page Break # Empty Paragraph and Page Break
@@ -948,14 +948,14 @@ def testCoreToken_SpecialFormat(mockGUI):
) )
tokens.tokenizeText() tokens.tokenizeText()
assert tokens._tokens == [ assert tokens._tokens == [
(Tokenizer.T_HEAD1, 1, "Title One", None, Tokenizer.A_PBB | Tokenizer.A_CENTRE), (Tokenizer.T_HEAD1, 1, "Title One", [], Tokenizer.A_PBB | Tokenizer.A_CENTRE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE),
(Tokenizer.T_SKIP, 1, "", None, Tokenizer.A_PBB), (Tokenizer.T_SKIP, 1, "", [], Tokenizer.A_PBB),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE),
(Tokenizer.T_TEXT, 1, "Some text to go here ...", [], 0), (Tokenizer.T_TEXT, 1, "Some text to go here ...", [], 0),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE),
] ]
# Multiple Skip # Multiple Skip
@@ -967,16 +967,16 @@ def testCoreToken_SpecialFormat(mockGUI):
) )
tokens.tokenizeText() tokens.tokenizeText()
assert tokens._tokens == [ assert tokens._tokens == [
(Tokenizer.T_HEAD1, 1, "Title One", None, Tokenizer.A_PBB | Tokenizer.A_CENTRE), (Tokenizer.T_HEAD1, 1, "Title One", [], Tokenizer.A_PBB | Tokenizer.A_CENTRE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE),
(Tokenizer.T_SKIP, 1, "", None, Tokenizer.A_PBB), (Tokenizer.T_SKIP, 1, "", [], Tokenizer.A_PBB),
(Tokenizer.T_SKIP, 1, "", None, Tokenizer.A_NONE), (Tokenizer.T_SKIP, 1, "", [], Tokenizer.A_NONE),
(Tokenizer.T_SKIP, 1, "", None, Tokenizer.A_NONE), (Tokenizer.T_SKIP, 1, "", [], Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE),
(Tokenizer.T_TEXT, 1, "Some text to go here ...", [], 0), (Tokenizer.T_TEXT, 1, "Some text to go here ...", [], 0),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE),
] ]
# END Test testCoreToken_SpecialFormat # END Test testCoreToken_SpecialFormat
@@ -1019,8 +1019,8 @@ def testCoreToken_ProcessHeaders(mockGUI):
tokens.tokenizeText() tokens.tokenizeText()
tokens.doHeaders() tokens.doHeaders()
assert tokens._tokens == [ assert tokens._tokens == [
(Tokenizer.T_HEAD1, 1, "T: Part One", None, Tokenizer.A_CENTRE), (Tokenizer.T_HEAD1, 1, "T: Part One", [], Tokenizer.A_CENTRE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE),
] ]
# H1: Title, Not First Page # H1: Title, Not First Page
@@ -1030,8 +1030,8 @@ def testCoreToken_ProcessHeaders(mockGUI):
tokens.tokenizeText() tokens.tokenizeText()
tokens.doHeaders() tokens.doHeaders()
assert tokens._tokens == [ assert tokens._tokens == [
(Tokenizer.T_HEAD1, 1, "T: Part One", None, Tokenizer.A_PBB | Tokenizer.A_CENTRE), (Tokenizer.T_HEAD1, 1, "T: Part One", [], Tokenizer.A_PBB | Tokenizer.A_CENTRE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE),
] ]
# Chapters # Chapters
@@ -1043,8 +1043,8 @@ def testCoreToken_ProcessHeaders(mockGUI):
tokens.tokenizeText() tokens.tokenizeText()
tokens.doHeaders() tokens.doHeaders()
assert tokens._tokens == [ assert tokens._tokens == [
(Tokenizer.T_HEAD2, 1, "C: Chapter One", None, Tokenizer.A_PBB), (Tokenizer.T_HEAD2, 1, "C: Chapter One", [], Tokenizer.A_PBB),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE),
] ]
# H2: Unnumbered Chapter # H2: Unnumbered Chapter
@@ -1053,8 +1053,8 @@ def testCoreToken_ProcessHeaders(mockGUI):
tokens.tokenizeText() tokens.tokenizeText()
tokens.doHeaders() tokens.doHeaders()
assert tokens._tokens == [ assert tokens._tokens == [
(Tokenizer.T_UNNUM, 1, "U: Prologue", None, Tokenizer.A_PBB), (Tokenizer.T_UNNUM, 1, "U: Prologue", [], Tokenizer.A_PBB),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE),
] ]
# H2: Chapter Word Number # H2: Chapter Word Number
@@ -1064,8 +1064,8 @@ def testCoreToken_ProcessHeaders(mockGUI):
tokens.tokenizeText() tokens.tokenizeText()
tokens.doHeaders() tokens.doHeaders()
assert tokens._tokens == [ assert tokens._tokens == [
(Tokenizer.T_HEAD2, 1, "Chapter One", None, Tokenizer.A_PBB), (Tokenizer.T_HEAD2, 1, "Chapter One", [], Tokenizer.A_PBB),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE),
] ]
# H2: Chapter Roman Number Upper Case # H2: Chapter Roman Number Upper Case
@@ -1074,8 +1074,8 @@ def testCoreToken_ProcessHeaders(mockGUI):
tokens.tokenizeText() tokens.tokenizeText()
tokens.doHeaders() tokens.doHeaders()
assert tokens._tokens == [ assert tokens._tokens == [
(Tokenizer.T_HEAD2, 1, "Chapter II", None, Tokenizer.A_PBB), (Tokenizer.T_HEAD2, 1, "Chapter II", [], Tokenizer.A_PBB),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE),
] ]
# H2: Chapter Roman Number Lower Case # H2: Chapter Roman Number Lower Case
@@ -1084,8 +1084,8 @@ def testCoreToken_ProcessHeaders(mockGUI):
tokens.tokenizeText() tokens.tokenizeText()
tokens.doHeaders() tokens.doHeaders()
assert tokens._tokens == [ assert tokens._tokens == [
(Tokenizer.T_HEAD2, 1, "Chapter iii", None, Tokenizer.A_PBB), (Tokenizer.T_HEAD2, 1, "Chapter iii", [], Tokenizer.A_PBB),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE),
] ]
# Scenes # Scenes
@@ -1097,8 +1097,8 @@ def testCoreToken_ProcessHeaders(mockGUI):
tokens.tokenizeText() tokens.tokenizeText()
tokens.doHeaders() tokens.doHeaders()
assert tokens._tokens == [ assert tokens._tokens == [
(Tokenizer.T_HEAD3, 1, "S: Scene One", None, Tokenizer.A_NONE), (Tokenizer.T_HEAD3, 1, "S: Scene One", [], Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE),
] ]
# H3: Scene Hidden wo/Format # H3: Scene Hidden wo/Format
@@ -1107,8 +1107,8 @@ def testCoreToken_ProcessHeaders(mockGUI):
tokens.tokenizeText() tokens.tokenizeText()
tokens.doHeaders() tokens.doHeaders()
assert tokens._tokens == [ assert tokens._tokens == [
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE),
] ]
# H3: Scene wo/Format, first # H3: Scene wo/Format, first
@@ -1118,8 +1118,8 @@ def testCoreToken_ProcessHeaders(mockGUI):
tokens.tokenizeText() tokens.tokenizeText()
tokens.doHeaders() tokens.doHeaders()
assert tokens._tokens == [ assert tokens._tokens == [
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE),
] ]
# H3: Scene wo/Format, not first # H3: Scene wo/Format, not first
@@ -1129,8 +1129,8 @@ def testCoreToken_ProcessHeaders(mockGUI):
tokens.tokenizeText() tokens.tokenizeText()
tokens.doHeaders() tokens.doHeaders()
assert tokens._tokens == [ assert tokens._tokens == [
(Tokenizer.T_SKIP, 1, "", None, Tokenizer.A_NONE), (Tokenizer.T_SKIP, 1, "", [], Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE),
] ]
# H3: Scene Separator, first # H3: Scene Separator, first
@@ -1140,8 +1140,8 @@ def testCoreToken_ProcessHeaders(mockGUI):
tokens.tokenizeText() tokens.tokenizeText()
tokens.doHeaders() tokens.doHeaders()
assert tokens._tokens == [ assert tokens._tokens == [
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE),
] ]
# H3: Scene Separator, not first # H3: Scene Separator, not first
@@ -1151,8 +1151,8 @@ def testCoreToken_ProcessHeaders(mockGUI):
tokens.tokenizeText() tokens.tokenizeText()
tokens.doHeaders() tokens.doHeaders()
assert tokens._tokens == [ assert tokens._tokens == [
(Tokenizer.T_SEP, 1, "* * *", None, Tokenizer.A_CENTRE), (Tokenizer.T_SEP, 1, "* * *", [], Tokenizer.A_CENTRE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE),
] ]
# H3: Scene w/Absolute Number # H3: Scene w/Absolute Number
@@ -1163,8 +1163,8 @@ def testCoreToken_ProcessHeaders(mockGUI):
tokens.tokenizeText() tokens.tokenizeText()
tokens.doHeaders() tokens.doHeaders()
assert tokens._tokens == [ assert tokens._tokens == [
(Tokenizer.T_HEAD3, 1, "Scene 1", None, Tokenizer.A_NONE), (Tokenizer.T_HEAD3, 1, "Scene 1", [], Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE),
] ]
# H3: Scene w/Chapter Number # H3: Scene w/Chapter Number
@@ -1175,8 +1175,8 @@ def testCoreToken_ProcessHeaders(mockGUI):
tokens.tokenizeText() tokens.tokenizeText()
tokens.doHeaders() tokens.doHeaders()
assert tokens._tokens == [ assert tokens._tokens == [
(Tokenizer.T_HEAD3, 1, "Scene 3.2", None, Tokenizer.A_NONE), (Tokenizer.T_HEAD3, 1, "Scene 3.2", [], Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE),
] ]
# Sections # Sections
@@ -1188,8 +1188,8 @@ def testCoreToken_ProcessHeaders(mockGUI):
tokens.tokenizeText() tokens.tokenizeText()
tokens.doHeaders() tokens.doHeaders()
assert tokens._tokens == [ assert tokens._tokens == [
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE),
] ]
# H4: Section Visible wo/Format # H4: Section Visible wo/Format
@@ -1198,8 +1198,8 @@ def testCoreToken_ProcessHeaders(mockGUI):
tokens.tokenizeText() tokens.tokenizeText()
tokens.doHeaders() tokens.doHeaders()
assert tokens._tokens == [ assert tokens._tokens == [
(Tokenizer.T_SKIP, 1, "", None, Tokenizer.A_NONE), (Tokenizer.T_SKIP, 1, "", [], Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE),
] ]
# H4: Section w/Format # H4: Section w/Format
@@ -1208,8 +1208,8 @@ def testCoreToken_ProcessHeaders(mockGUI):
tokens.tokenizeText() tokens.tokenizeText()
tokens.doHeaders() tokens.doHeaders()
assert tokens._tokens == [ assert tokens._tokens == [
(Tokenizer.T_HEAD4, 1, "X: A Section", None, Tokenizer.A_NONE), (Tokenizer.T_HEAD4, 1, "X: A Section", [], Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE),
] ]
# H4: Section Separator # H4: Section Separator
@@ -1218,8 +1218,8 @@ def testCoreToken_ProcessHeaders(mockGUI):
tokens.tokenizeText() tokens.tokenizeText()
tokens.doHeaders() tokens.doHeaders()
assert tokens._tokens == [ assert tokens._tokens == [
(Tokenizer.T_SEP, 1, "* * *", None, Tokenizer.A_CENTRE), (Tokenizer.T_SEP, 1, "* * *", [], Tokenizer.A_CENTRE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE),
] ]
# Check the first scene detector # Check the first scene detector
@@ -1231,3 +1231,105 @@ def testCoreToken_ProcessHeaders(mockGUI):
assert tokens._firstScene is False assert tokens._firstScene is False
# END Test testCoreToken_ProcessHeaders # END Test testCoreToken_ProcessHeaders
@pytest.mark.core
def testCoreIndex_HeadingFormatter(fncPath, mockRnd):
"""Check the HeadingFormatter class."""
project = NWProject()
project.setProjectLang("en_GB")
mockRnd.reset()
buildTestProject(project, fncPath)
nHandle = project.newFile("Hello", C.hNovelRoot)
cHandle = project.newFile("Jane", C.hCharRoot)
dHandle = project.newFile("John", C.hCharRoot)
assert isinstance(nHandle, str)
assert isinstance(cHandle, str)
assert isinstance(dHandle, str)
assert project.index.scanText(cHandle, (
"# Jane Smith\n"
"@tag: Jane | Jane Smith\n"
))
assert project.index.scanText(dHandle, (
"# John Smith\n"
"@tag: John | John Smith\n"
))
assert project.index.scanText(nHandle, (
"## Hi Bob\n"
"@pov: Jane\n"
"@focus: John\n"
"@char: Jane, John\n\n"
"This is a story about Jane Smith.\n\n"
"Well, not really.\n"
))
formatter = HeadingFormatter(project)
formatter.setHandle(nHandle)
# Counters
# ========
cFormat = (
f"Chapter {nwHeadFmt.CH_NUM}.{nwHeadFmt.SC_NUM} - "
f"Scene {nwHeadFmt.SC_ABS} - {nwHeadFmt.TITLE}"
)
# Initial
assert formatter.apply(cFormat, "Hi Bob", 1) == "Chapter 0.0 - Scene 0 - Hi Bob"
# First Chapter and Scene
formatter.incChapter()
formatter.incScene()
assert formatter.apply(cFormat, "Hi Bob", 1) == "Chapter 1.1 - Scene 1 - Hi Bob"
# Next Scenes
formatter.incScene()
assert formatter.apply(cFormat, "Hi Bob", 1) == "Chapter 1.2 - Scene 2 - Hi Bob"
formatter.incScene()
assert formatter.apply(cFormat, "Hi Bob", 1) == "Chapter 1.3 - Scene 3 - Hi Bob"
formatter.incScene()
assert formatter.apply(cFormat, "Hi Bob", 1) == "Chapter 1.4 - Scene 4 - Hi Bob"
# Next Chapter and Scene
formatter.incChapter()
formatter.resetScene()
formatter.incScene()
assert formatter.apply(cFormat, "Hi Bob", 1) == "Chapter 2.1 - Scene 5 - Hi Bob"
# Special Formats
# ===============
formatter._chCount = 2
formatter._scChCount = 3
formatter._scAbsCount = 8
# Chapter Number Word
cFormat = f"Chapter {nwHeadFmt.CH_WORD}, Scene {nwHeadFmt.SC_NUM} - {nwHeadFmt.TITLE}"
assert formatter.apply(cFormat, "Hi Bob", 1) == "Chapter Two, Scene 3 - Hi Bob"
# Chapter Upper Case Roman
cFormat = f"Chapter {nwHeadFmt.CH_ROMU}, Scene {nwHeadFmt.SC_NUM} - {nwHeadFmt.TITLE}"
assert formatter.apply(cFormat, "Hi Bob", 1) == "Chapter II, Scene 3 - Hi Bob"
# Chapter Lower Case Roman
cFormat = f"Chapter {nwHeadFmt.CH_ROML}, Scene {nwHeadFmt.SC_NUM} - {nwHeadFmt.TITLE}"
assert formatter.apply(cFormat, "Hi Bob", 1) == "Chapter ii, Scene 3 - Hi Bob"
# Chapter w/PoV Character
cFormat = f"Chapter {nwHeadFmt.CH_NUM}, Scene {nwHeadFmt.SC_NUM} - {nwHeadFmt.CHAR_POV}"
assert formatter.apply(cFormat, "Hi Bob", 1) == "Chapter 2, Scene 3 - Jane Smith"
# Chapter w/Focus Character
cFormat = f"Chapter {nwHeadFmt.CH_NUM}, Scene {nwHeadFmt.SC_NUM} - {nwHeadFmt.CHAR_FOCUS}"
assert formatter.apply(cFormat, "Hi Bob", 1) == "Chapter 2, Scene 3 - John Smith"
# Chapter w/Fallback PoV
cFormat = f"Chapter {nwHeadFmt.CH_NUM}, Scene {nwHeadFmt.SC_NUM} - {nwHeadFmt.CHAR_POV}"
assert formatter.apply(cFormat, "Hi Bob", 0) == "Chapter 2, Scene 3 - Point of View"
# Chapter w/Fallback Focus
cFormat = f"Chapter {nwHeadFmt.CH_NUM}, Scene {nwHeadFmt.SC_NUM} - {nwHeadFmt.CHAR_FOCUS}"
assert formatter.apply(cFormat, "Hi Bob", 0) == "Chapter 2, Scene 3 - Focus"
# END Test testCoreIndex_HeadingFormatter
+19 -19
View File
@@ -1615,7 +1615,7 @@ def testGuiEditor_Search(qtbot, monkeypatch, nwGUI, prjLipsum):
origText = nwGUI.docEditor.getText() origText = nwGUI.docEditor.getText()
# Select the Word "est" # Select the Word "est"
nwGUI.docEditor.setCursorPosition(630) nwGUI.docEditor.setCursorPosition(645)
nwGUI.docEditor._makeSelection(QTextCursor.WordUnderCursor) nwGUI.docEditor._makeSelection(QTextCursor.WordUnderCursor)
cursor = nwGUI.docEditor.textCursor() cursor = nwGUI.docEditor.textCursor()
assert cursor.selectedText() == "est" assert cursor.selectedText() == "est"
@@ -1628,11 +1628,11 @@ def testGuiEditor_Search(qtbot, monkeypatch, nwGUI, prjLipsum):
# Find next by enter key # Find next by enter key
monkeypatch.setattr(nwGUI.docEditor.docSearch.searchBox, "hasFocus", lambda: True) monkeypatch.setattr(nwGUI.docEditor.docSearch.searchBox, "hasFocus", lambda: True)
qtbot.keyClick(nwGUI.docEditor.docSearch.searchBox, Qt.Key_Return, delay=KEY_DELAY) qtbot.keyClick(nwGUI.docEditor.docSearch.searchBox, Qt.Key_Return, delay=KEY_DELAY)
assert abs(nwGUI.docEditor.getCursorPosition() - 1284) < 3 assert abs(nwGUI.docEditor.getCursorPosition() - 1299) < 3
# Find next by button # Find next by button
qtbot.mouseClick(nwGUI.docEditor.docSearch.searchButton, Qt.LeftButton, delay=KEY_DELAY) qtbot.mouseClick(nwGUI.docEditor.docSearch.searchButton, Qt.LeftButton, delay=KEY_DELAY)
assert abs(nwGUI.docEditor.getCursorPosition() - 1498) < 3 assert abs(nwGUI.docEditor.getCursorPosition() - 1513) < 3
# Activate loop search # Activate loop search
nwGUI.docEditor.docSearch.toggleLoop.activate(QAction.Trigger) nwGUI.docEditor.docSearch.toggleLoop.activate(QAction.Trigger)
@@ -1641,7 +1641,7 @@ def testGuiEditor_Search(qtbot, monkeypatch, nwGUI, prjLipsum):
# Find next by menu Search > Find Next # Find next by menu Search > Find Next
nwGUI.mainMenu.aFindNext.activate(QAction.Trigger) nwGUI.mainMenu.aFindNext.activate(QAction.Trigger)
assert abs(nwGUI.docEditor.getCursorPosition() - 632) < 3 assert abs(nwGUI.docEditor.getCursorPosition() - 647) < 3
# Close search # Close search
nwGUI.docEditor.docSearch.cancelSearch.activate(QAction.Trigger) nwGUI.docEditor.docSearch.cancelSearch.activate(QAction.Trigger)
@@ -1680,13 +1680,13 @@ def testGuiEditor_Search(qtbot, monkeypatch, nwGUI, prjLipsum):
# Set valid RegEx # Set valid RegEx
nwGUI.docEditor.docSearch.setSearchText(r"\bSus") nwGUI.docEditor.docSearch.setSearchText(r"\bSus")
qtbot.mouseClick(nwGUI.docEditor.docSearch.searchButton, Qt.LeftButton, delay=KEY_DELAY) qtbot.mouseClick(nwGUI.docEditor.docSearch.searchButton, Qt.LeftButton, delay=KEY_DELAY)
assert abs(nwGUI.docEditor.getCursorPosition() - 208) < 3 assert abs(nwGUI.docEditor.getCursorPosition() - 223) < 3
# Find next and then prev # Find next and then prev
nwGUI.mainMenu.aFindNext.activate(QAction.Trigger) nwGUI.mainMenu.aFindNext.activate(QAction.Trigger)
assert abs(nwGUI.docEditor.getCursorPosition() - 309) < 3 assert abs(nwGUI.docEditor.getCursorPosition() - 324) < 3
nwGUI.mainMenu.aFindPrev.activate(QAction.Trigger) nwGUI.mainMenu.aFindPrev.activate(QAction.Trigger)
assert abs(nwGUI.docEditor.getCursorPosition() - 208) < 3 assert abs(nwGUI.docEditor.getCursorPosition() - 223) < 3
# Make RegEx case sensitive # Make RegEx case sensitive
nwGUI.docEditor.docSearch.toggleCase.activate(QAction.Trigger) nwGUI.docEditor.docSearch.toggleCase.activate(QAction.Trigger)
@@ -1695,11 +1695,11 @@ def testGuiEditor_Search(qtbot, monkeypatch, nwGUI, prjLipsum):
# Find next/prev (one result) # Find next/prev (one result)
nwGUI.mainMenu.aFindNext.activate(QAction.Trigger) nwGUI.mainMenu.aFindNext.activate(QAction.Trigger)
assert abs(nwGUI.docEditor.getCursorPosition() - 611) < 3 assert abs(nwGUI.docEditor.getCursorPosition() - 626) < 3
nwGUI.mainMenu.aFindPrev.activate(QAction.Trigger) nwGUI.mainMenu.aFindPrev.activate(QAction.Trigger)
assert abs(nwGUI.docEditor.getCursorPosition() - 611) < 3 assert abs(nwGUI.docEditor.getCursorPosition() - 626) < 3
nwGUI.mainMenu.aFindNext.activate(QAction.Trigger) nwGUI.mainMenu.aFindNext.activate(QAction.Trigger)
assert abs(nwGUI.docEditor.getCursorPosition() - 611) < 3 assert abs(nwGUI.docEditor.getCursorPosition() - 626) < 3
# Trigger replace # Trigger replace
nwGUI.mainMenu.aReplace.activate(QAction.Trigger) nwGUI.mainMenu.aReplace.activate(QAction.Trigger)
@@ -1716,22 +1716,22 @@ def testGuiEditor_Search(qtbot, monkeypatch, nwGUI, prjLipsum):
assert nwGUI.docEditor.docSearch.doMatchCap is True assert nwGUI.docEditor.docSearch.doMatchCap is True
# Replace "Sus" with "Foo" via menu # Replace "Sus" with "Foo" via menu
nwGUI.docEditor.setCursorPosition(590) nwGUI.docEditor.setCursorPosition(605)
nwGUI.mainMenu.aFindNext.activate(QAction.Trigger) nwGUI.mainMenu.aFindNext.activate(QAction.Trigger)
nwGUI.mainMenu.aReplaceNext.activate(QAction.Trigger) nwGUI.mainMenu.aReplaceNext.activate(QAction.Trigger)
assert nwGUI.docEditor.getText()[608:619] == "Foopendisse" assert nwGUI.docEditor.getText()[623:634] == "Foopendisse"
# Find next/prev to loop file # Find next/prev to loop file
nwGUI.mainMenu.aFindNext.activate(QAction.Trigger) nwGUI.mainMenu.aFindNext.activate(QAction.Trigger)
assert abs(nwGUI.docEditor.getCursorPosition() - 208) < 3 assert abs(nwGUI.docEditor.getCursorPosition() - 223) < 3
nwGUI.mainMenu.aFindPrev.activate(QAction.Trigger) nwGUI.mainMenu.aFindPrev.activate(QAction.Trigger)
assert abs(nwGUI.docEditor.getCursorPosition() - 1790) < 3 assert abs(nwGUI.docEditor.getCursorPosition() - 1805) < 3
nwGUI.mainMenu.aFindNext.activate(QAction.Trigger) nwGUI.mainMenu.aFindNext.activate(QAction.Trigger)
assert abs(nwGUI.docEditor.getCursorPosition() - 208) < 3 assert abs(nwGUI.docEditor.getCursorPosition() - 223) < 3
# Replace "sus" with "foo" via replace button # Replace "sus" with "foo" via replace button
qtbot.mouseClick(nwGUI.docEditor.docSearch.replaceButton, Qt.LeftButton, delay=KEY_DELAY) qtbot.mouseClick(nwGUI.docEditor.docSearch.replaceButton, Qt.LeftButton, delay=KEY_DELAY)
assert nwGUI.docEditor.getText()[205:213] == "foocipit" assert nwGUI.docEditor.getText()[220:228] == "foocipit"
# Revert last two replaces # Revert last two replaces
assert nwGUI.docEditor.docAction(nwDocAction.UNDO) assert nwGUI.docEditor.docAction(nwDocAction.UNDO)
@@ -1745,7 +1745,7 @@ def testGuiEditor_Search(qtbot, monkeypatch, nwGUI, prjLipsum):
# Close search and select "est" again # Close search and select "est" again
nwGUI.docEditor.docSearch.cancelSearch.activate(QAction.Trigger) nwGUI.docEditor.docSearch.cancelSearch.activate(QAction.Trigger)
nwGUI.docEditor.setCursorPosition(630) nwGUI.docEditor.setCursorPosition(645)
nwGUI.docEditor._makeSelection(QTextCursor.WordUnderCursor) nwGUI.docEditor._makeSelection(QTextCursor.WordUnderCursor)
cursor = nwGUI.docEditor.textCursor() cursor = nwGUI.docEditor.textCursor()
assert cursor.selectedText() == "est" assert cursor.selectedText() == "est"
@@ -1762,9 +1762,9 @@ def testGuiEditor_Search(qtbot, monkeypatch, nwGUI, prjLipsum):
# Only one match # Only one match
nwGUI.mainMenu.aFindNext.activate(QAction.Trigger) nwGUI.mainMenu.aFindNext.activate(QAction.Trigger)
assert abs(nwGUI.docEditor.getCursorPosition() - 632) < 3 assert abs(nwGUI.docEditor.getCursorPosition() - 647) < 3
nwGUI.mainMenu.aFindNext.activate(QAction.Trigger) nwGUI.mainMenu.aFindNext.activate(QAction.Trigger)
assert abs(nwGUI.docEditor.getCursorPosition() - 632) < 3 assert abs(nwGUI.docEditor.getCursorPosition() - 647) < 3
# Enable next doc search # Enable next doc search
nwGUI.docEditor.docSearch.toggleProject.activate(QAction.Trigger) nwGUI.docEditor.docSearch.toggleProject.activate(QAction.Trigger)
+40 -40
View File
@@ -46,146 +46,146 @@ def testGuiMenu_EditFormat(qtbot, monkeypatch, nwGUI, prjLipsum):
# Split By Chapter # Split By Chapter
assert nwGUI.openDocument("4c4f28287af27") is True assert nwGUI.openDocument("4c4f28287af27") is True
nwGUI.docEditor.setCursorPosition(42) nwGUI.docEditor.setCursorPosition(57)
cleanText = nwGUI.docEditor.getText()[39:86] cleanText = nwGUI.docEditor.getText()[54:101]
# Bold # Bold
nwGUI.mainMenu.aFmtBold.activate(QAction.Trigger) nwGUI.mainMenu.aFmtBold.activate(QAction.Trigger)
fmtStr = "**Pellentesque** nec erat ut nulla posuere commodo." fmtStr = "**Pellentesque** nec erat ut nulla posuere commodo."
assert nwGUI.docEditor.getText()[39:90] == fmtStr assert nwGUI.docEditor.getText()[54:105] == fmtStr
nwGUI.mainMenu.aFmtBold.activate(QAction.Trigger) nwGUI.mainMenu.aFmtBold.activate(QAction.Trigger)
assert nwGUI.docEditor.getText()[39:86] == cleanText assert nwGUI.docEditor.getText()[54:101] == cleanText
# Italic # Italic
nwGUI.mainMenu.aFmtItalic.activate(QAction.Trigger) nwGUI.mainMenu.aFmtItalic.activate(QAction.Trigger)
fmtStr = "_Pellentesque_ nec erat ut nulla posuere commodo." fmtStr = "_Pellentesque_ nec erat ut nulla posuere commodo."
assert nwGUI.docEditor.getText()[39:88] == fmtStr assert nwGUI.docEditor.getText()[54:103] == fmtStr
nwGUI.mainMenu.aFmtItalic.activate(QAction.Trigger) nwGUI.mainMenu.aFmtItalic.activate(QAction.Trigger)
assert nwGUI.docEditor.getText()[39:86] == cleanText assert nwGUI.docEditor.getText()[54:101] == cleanText
# Strikethrough # Strikethrough
nwGUI.mainMenu.aFmtStrike.activate(QAction.Trigger) nwGUI.mainMenu.aFmtStrike.activate(QAction.Trigger)
fmtStr = "~~Pellentesque~~ nec erat ut nulla posuere commodo." fmtStr = "~~Pellentesque~~ nec erat ut nulla posuere commodo."
assert nwGUI.docEditor.getText()[39:90] == fmtStr assert nwGUI.docEditor.getText()[54:105] == fmtStr
nwGUI.mainMenu.aFmtStrike.activate(QAction.Trigger) nwGUI.mainMenu.aFmtStrike.activate(QAction.Trigger)
assert nwGUI.docEditor.getText()[39:86] == cleanText assert nwGUI.docEditor.getText()[54:101] == cleanText
# Should get us back to plain # Should get us back to plain
nwGUI.mainMenu.aFmtBold.activate(QAction.Trigger) nwGUI.mainMenu.aFmtBold.activate(QAction.Trigger)
nwGUI.mainMenu.aFmtItalic.activate(QAction.Trigger) nwGUI.mainMenu.aFmtItalic.activate(QAction.Trigger)
nwGUI.mainMenu.aFmtItalic.activate(QAction.Trigger) nwGUI.mainMenu.aFmtItalic.activate(QAction.Trigger)
nwGUI.mainMenu.aFmtBold.activate(QAction.Trigger) nwGUI.mainMenu.aFmtBold.activate(QAction.Trigger)
assert nwGUI.docEditor.getText()[39:86] == cleanText assert nwGUI.docEditor.getText()[54:101] == cleanText
# Double Quotes # Double Quotes
nwGUI.mainMenu.aFmtDQuote.activate(QAction.Trigger) nwGUI.mainMenu.aFmtDQuote.activate(QAction.Trigger)
fmtStr = "“Pellentesque” nec erat ut nulla posuere commodo." fmtStr = "“Pellentesque” nec erat ut nulla posuere commodo."
assert nwGUI.docEditor.getText()[39:88] == fmtStr assert nwGUI.docEditor.getText()[54:103] == fmtStr
nwGUI.mainMenu.aEditUndo.activate(QAction.Trigger) nwGUI.mainMenu.aEditUndo.activate(QAction.Trigger)
assert nwGUI.docEditor.getText()[39:86] == cleanText assert nwGUI.docEditor.getText()[54:101] == cleanText
# Single Quotes # Single Quotes
nwGUI.mainMenu.aFmtSQuote.activate(QAction.Trigger) nwGUI.mainMenu.aFmtSQuote.activate(QAction.Trigger)
fmtStr = "Pellentesque nec erat ut nulla posuere commodo." fmtStr = "Pellentesque nec erat ut nulla posuere commodo."
assert nwGUI.docEditor.getText()[39:88] == fmtStr assert nwGUI.docEditor.getText()[54:103] == fmtStr
nwGUI.mainMenu.aEditUndo.activate(QAction.Trigger) nwGUI.mainMenu.aEditUndo.activate(QAction.Trigger)
assert nwGUI.docEditor.getText()[39:86] == cleanText assert nwGUI.docEditor.getText()[54:101] == cleanText
# Block Formats # Block Formats
# ============= # =============
# cSpell:ignore Pellentesque erat nulla posuere commodo # cSpell:ignore Pellentesque erat nulla posuere commodo
nwGUI.docEditor.setCursorPosition(42) nwGUI.docEditor.setCursorPosition(57)
# Header 1 # Header 1
nwGUI.mainMenu.aFmtHead1.activate(QAction.Trigger) nwGUI.mainMenu.aFmtHead1.activate(QAction.Trigger)
fmtStr = "# Pellentesque nec erat ut nulla posuere commodo." fmtStr = "# Pellentesque nec erat ut nulla posuere commodo."
assert nwGUI.docEditor.getText()[39:88] == fmtStr assert nwGUI.docEditor.getText()[54:103] == fmtStr
# Header 2 # Header 2
nwGUI.mainMenu.aFmtHead2.activate(QAction.Trigger) nwGUI.mainMenu.aFmtHead2.activate(QAction.Trigger)
fmtStr = "## Pellentesque nec erat ut nulla posuere commodo." fmtStr = "## Pellentesque nec erat ut nulla posuere commodo."
assert nwGUI.docEditor.getText()[39:89] == fmtStr assert nwGUI.docEditor.getText()[54:104] == fmtStr
# Header 3 # Header 3
nwGUI.mainMenu.aFmtHead3.activate(QAction.Trigger) nwGUI.mainMenu.aFmtHead3.activate(QAction.Trigger)
fmtStr = "### Pellentesque nec erat ut nulla posuere commodo." fmtStr = "### Pellentesque nec erat ut nulla posuere commodo."
assert nwGUI.docEditor.getText()[39:90] == fmtStr assert nwGUI.docEditor.getText()[54:105] == fmtStr
# Header 4 # Header 4
nwGUI.mainMenu.aFmtHead4.activate(QAction.Trigger) nwGUI.mainMenu.aFmtHead4.activate(QAction.Trigger)
fmtStr = "#### Pellentesque nec erat ut nulla posuere commodo." fmtStr = "#### Pellentesque nec erat ut nulla posuere commodo."
assert nwGUI.docEditor.getText()[39:91] == fmtStr assert nwGUI.docEditor.getText()[54:106] == fmtStr
# Title Format # Title Format
nwGUI.mainMenu.aFmtTitle.activate(QAction.Trigger) nwGUI.mainMenu.aFmtTitle.activate(QAction.Trigger)
fmtStr = "#! Pellentesque nec erat ut nulla posuere commodo." fmtStr = "#! Pellentesque nec erat ut nulla posuere commodo."
assert nwGUI.docEditor.getText()[39:89] == fmtStr assert nwGUI.docEditor.getText()[54:104] == fmtStr
# Unnumbered Chapter # Unnumbered Chapter
nwGUI.mainMenu.aFmtUnNum.activate(QAction.Trigger) nwGUI.mainMenu.aFmtUnNum.activate(QAction.Trigger)
fmtStr = "##! Pellentesque nec erat ut nulla posuere commodo." fmtStr = "##! Pellentesque nec erat ut nulla posuere commodo."
assert nwGUI.docEditor.getText()[39:90] == fmtStr assert nwGUI.docEditor.getText()[54:105] == fmtStr
# Clear Format # Clear Format
nwGUI.mainMenu.aFmtNoFormat.activate(QAction.Trigger) nwGUI.mainMenu.aFmtNoFormat.activate(QAction.Trigger)
assert nwGUI.docEditor.getText()[39:86] == cleanText assert nwGUI.docEditor.getText()[54:101] == cleanText
# Comment On # Comment On
nwGUI.mainMenu.aFmtComment.activate(QAction.Trigger) nwGUI.mainMenu.aFmtComment.activate(QAction.Trigger)
fmtStr = "% Pellentesque nec erat ut nulla posuere commodo." fmtStr = "% Pellentesque nec erat ut nulla posuere commodo."
assert nwGUI.docEditor.getText()[39:88] == fmtStr assert nwGUI.docEditor.getText()[54:103] == fmtStr
# Comment Off # Comment Off
nwGUI.mainMenu.aFmtComment.activate(QAction.Trigger) nwGUI.mainMenu.aFmtComment.activate(QAction.Trigger)
assert nwGUI.docEditor.getText()[39:86] == cleanText assert nwGUI.docEditor.getText()[54:101] == cleanText
# Check comment with no space before text # Check comment with no space before text
nwGUI.docEditor.setCursorPosition(39) nwGUI.docEditor.setCursorPosition(54)
assert nwGUI.docEditor.insertText("%") assert nwGUI.docEditor.insertText("%")
fmtStr = "%Pellentesque nec erat ut nulla posuere commodo." fmtStr = "%Pellentesque nec erat ut nulla posuere commodo."
assert nwGUI.docEditor.getText()[39:87] == fmtStr assert nwGUI.docEditor.getText()[54:102] == fmtStr
nwGUI.mainMenu.aFmtNoFormat.activate(QAction.Trigger) nwGUI.mainMenu.aFmtNoFormat.activate(QAction.Trigger)
assert nwGUI.docEditor.getText()[39:86] == cleanText assert nwGUI.docEditor.getText()[54:101] == cleanText
# Undo/Redo # Undo/Redo
nwGUI.mainMenu.aEditUndo.activate(QAction.Trigger) nwGUI.mainMenu.aEditUndo.activate(QAction.Trigger)
fmtStr = "%Pellentesque nec erat ut nulla posuere commodo." fmtStr = "%Pellentesque nec erat ut nulla posuere commodo."
assert nwGUI.docEditor.getText()[39:87] == fmtStr assert nwGUI.docEditor.getText()[54:102] == fmtStr
nwGUI.mainMenu.aEditRedo.activate(QAction.Trigger) nwGUI.mainMenu.aEditRedo.activate(QAction.Trigger)
assert nwGUI.docEditor.getText()[39:86] == cleanText assert nwGUI.docEditor.getText()[54:101] == cleanText
# Cut, Copy and Paste # Cut, Copy and Paste
nwGUI.docEditor.setCursorPosition(39) nwGUI.docEditor.setCursorPosition(54)
nwGUI.docEditor._makeSelection(QTextCursor.WordUnderCursor) nwGUI.docEditor._makeSelection(QTextCursor.WordUnderCursor)
nwGUI.mainMenu.aEditCut.activate(QAction.Trigger) nwGUI.mainMenu.aEditCut.activate(QAction.Trigger)
assert nwGUI.docEditor.getText()[39:89] == ( assert nwGUI.docEditor.getText()[54:104] == (
" nec erat ut nulla posuere commodo. Curabitur nisi" " nec erat ut nulla posuere commodo. Curabitur nisi"
) )
nwGUI.mainMenu.aEditPaste.activate(QAction.Trigger) nwGUI.mainMenu.aEditPaste.activate(QAction.Trigger)
assert nwGUI.docEditor.getText()[39:89] == ( assert nwGUI.docEditor.getText()[54:104] == (
"Pellentesque nec erat ut nulla posuere commodo. Cu" "Pellentesque nec erat ut nulla posuere commodo. Cu"
) )
nwGUI.docEditor.setCursorPosition(39) nwGUI.docEditor.setCursorPosition(54)
nwGUI.docEditor._makeSelection(QTextCursor.WordUnderCursor) nwGUI.docEditor._makeSelection(QTextCursor.WordUnderCursor)
nwGUI.mainMenu.aEditCopy.activate(QAction.Trigger) nwGUI.mainMenu.aEditCopy.activate(QAction.Trigger)
assert nwGUI.docEditor.getText()[39:89] == ( assert nwGUI.docEditor.getText()[54:104] == (
"Pellentesque nec erat ut nulla posuere commodo. Cu" "Pellentesque nec erat ut nulla posuere commodo. Cu"
) )
nwGUI.docEditor.setCursorPosition(39) nwGUI.docEditor.setCursorPosition(54)
nwGUI.mainMenu.aEditPaste.activate(QAction.Trigger) nwGUI.mainMenu.aEditPaste.activate(QAction.Trigger)
assert nwGUI.docEditor.getText()[39:89] == ( assert nwGUI.docEditor.getText()[54:104] == (
"PellentesquePellentesque nec erat ut nulla posuere" "PellentesquePellentesque nec erat ut nulla posuere"
) )
nwGUI.mainMenu.aEditUndo.activate(QAction.Trigger) nwGUI.mainMenu.aEditUndo.activate(QAction.Trigger)
# Select Paragraph/All # Select Paragraph/All
nwGUI.docEditor.setCursorPosition(42) nwGUI.docEditor.setCursorPosition(57)
nwGUI.mainMenu.aSelectPar.activate(QAction.Trigger) nwGUI.mainMenu.aSelectPar.activate(QAction.Trigger)
cursor = nwGUI.docEditor.textCursor() cursor = nwGUI.docEditor.textCursor()
assert cursor.selectedText() == ( assert cursor.selectedText() == (
@@ -198,10 +198,10 @@ def testGuiMenu_EditFormat(qtbot, monkeypatch, nwGUI, prjLipsum):
"nunc lacus, imperdiet nec posuere ac, interdum non lectus." "nunc lacus, imperdiet nec posuere ac, interdum non lectus."
) )
nwGUI.docEditor.setCursorPosition(42) nwGUI.docEditor.setCursorPosition(57)
nwGUI.mainMenu.aSelectAll.activate(QAction.Trigger) nwGUI.mainMenu.aSelectAll.activate(QAction.Trigger)
cursor = nwGUI.docEditor.textCursor() cursor = nwGUI.docEditor.textCursor()
assert len(cursor.selectedText()) == 1895 assert len(cursor.selectedText()) == 1910
# Clear the Text # Clear the Text
nwGUI.docEditor.clear() nwGUI.docEditor.clear()
@@ -349,7 +349,7 @@ def testGuiMenu_ContextMenus(qtbot, nwGUI, prjLipsum):
# Editor Context Menu # Editor Context Menu
cursor = nwGUI.docEditor.textCursor() cursor = nwGUI.docEditor.textCursor()
cursor.setPosition(112) cursor.setPosition(127)
nwGUI.docEditor.setTextCursor(cursor) nwGUI.docEditor.setTextCursor(cursor)
rect = nwGUI.docEditor.cursorRect() rect = nwGUI.docEditor.cursorRect()
@@ -376,7 +376,7 @@ def testGuiMenu_ContextMenus(qtbot, nwGUI, prjLipsum):
assert nwGUI.viewDocument("4c4f28287af27") assert nwGUI.viewDocument("4c4f28287af27")
cursor = nwGUI.docViewer.textCursor() cursor = nwGUI.docViewer.textCursor()
cursor.setPosition(112) cursor.setPosition(127)
nwGUI.docViewer.setTextCursor(cursor) nwGUI.docViewer.setTextCursor(cursor)
rect = nwGUI.docViewer.cursorRect() rect = nwGUI.docViewer.cursorRect()