Merge branch 'dev' into sub_super

This commit is contained in:
Veronica Berglyd Olsen
2023-11-05 21:28:32 +01:00
30 changed files with 576 additions and 468 deletions
+2 -2
View File
@@ -9,7 +9,7 @@ jobs:
- name: Python Setup - name: Python Setup
uses: actions/setup-python@v4 uses: actions/setup-python@v4
with: with:
python-version: 3 python-version: "3.11"
architecture: x64 architecture: x64
- name: Install Packages (apt) - name: Install Packages (apt)
@@ -48,7 +48,7 @@ jobs:
- name: Python Setup - name: Python Setup
uses: actions/setup-python@v4 uses: actions/setup-python@v4
with: with:
python-version: 3 python-version: "3.11"
architecture: x64 architecture: x64
- name: Install Packages (pip) - name: Install Packages (pip)
-11
View File
@@ -1,11 +0,0 @@
# Read the Docs configuration file
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
version: 2
sphinx:
configuration: docs/source/conf.py
formats:
- htmlzip
- pdf
python:
install:
- requirements: docs/source/requirements.txt
+57
View File
@@ -1,5 +1,62 @@
# novelWriter Changelog # novelWriter Changelog
## Version 2.1.1 [2023-11-05]
### Release Notes
This is a patch release that fixes a layout issue and internationalisation issues with the new
Manuscript Build tool. It also fixes a number of issues related to bugs in the underlying Qt
framework that affects drag and drop functionality in the project tree. These issues were mostly
only affecting Debian Linux package releases.
Other, minor issues related to updating the editor on colour theme change and project word list
changes have been fixed as well. See the full changelog for more details.
### Detailed Changelog
**Bugfixes**
* Fix an issue with width of the last two columns on Selection page of the Build Settings dialog on
Windows. They were far too wide by default. Issue #1551. PR #1553.
* Fix an issue where a lot of string were not translated to the UI language in the new Manuscript
Build tool. Issue #1563. PR #1565.
* Fix an issue in the Document Viewer where it wouldn't scroll to a heading further down the page
when following a reference pointing to it. Issue #1566. PR #1568.
* Add back in checks for illegal drag and drop moves in the project tree. In 2.0, the logic here
was changed to set certain restrictions on the elements of the project tree itself, but there are
numerous bugs in the Qt framework related to drag and drop, so the checks are ignored on at least
Qt 5.15.8. In particular, it is possible to drop items on the root level, and it's possible to
move root items to other locations. Neither should be possible and will severely mess up the
project if done. Issue #1569. PR #1570.
* Add a custom autoscroll feature when dragging an item in the project tree to near the top or
bottom. This is actually a default feature of the tree widget in the Qt library, but this too is
broken in some versions of Qt 5.15.x. The default feature has been permanently disabled and
replaced by a custom written feature that behaves similarly. Issue #1561. PR #1571.
* Fix an issue where the editor document wasn't re-highlighted when the Syntax Theme for it was
changed. Issue #1535. PR #1573.
* Fix an issue where editing the Project Word List would not refresh the spell checking of the
editor. Issue #1559. PR #1573.
**Usability**
* Changed how the default UI language is selected. It used to default to the system locale, but
that is now changed to British English if the system local is not available in novelWriter. The
only real effects of this is that the dropdown box in Preferences now selects British English if
the system locale is not available rather than the first in the list (currently Deutch). The
second effect is that the language on buttons and other Qt components will match the rest of the
UI. Issue #1564. PR #1565.
* There is a bug in Qt on Wayland desktops where menus don't open in the correct location.
According to one Qt ticket, QTBUG-68636, this can be mitigated by ensuring all QMenu instances
have a parent set. This does not fix all issues, but it should help. The menus without a parent
set have now been updated. Issue #1536. PR #1572.
**Documentation**
* Fixed a number of spelling errors and typing mistakes in the documentation for 2.1. Contributed
by @nisemono-neko. PR #1567.
----
## Version 2.1 [2023-10-17] ## Version 2.1 [2023-10-17]
### Release Notes ### Release Notes
+2 -12
View File
@@ -27,7 +27,7 @@ with open(initFile) as inFile:
else: else:
release = "unknown" release = "unknown"
version = release.split("-")[0] version = release.partition("-")[0]
# -- General Configuration --------------------------------------------------- # -- General Configuration ---------------------------------------------------
@@ -67,11 +67,6 @@ html_sidebars = {
"**": ["navbar-logo", "sidebar-title", "sbt-sidebar-nav"], "**": ["navbar-logo", "sidebar-title", "sbt-sidebar-nav"],
} }
# -- Options for HTMLHelp Output ---------------------------------------------
# Output file base name for HTML help builder.
htmlhelp_basename = "novelWriterDoc"
# -- Options for LaTeX Output ------------------------------------------------ # -- Options for LaTeX Output ------------------------------------------------
latex_elements = { latex_elements = {
@@ -84,11 +79,6 @@ latex_elements = {
"figure_align": "htbp", "figure_align": "htbp",
} }
latex_logo = "_static/novelwriter-pdf.png" latex_logo = "_static/novelwriter-pdf.png"
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [( latex_documents = [(
master_doc, "manual.tex", "User Guide", master_doc, "manual.tex", "User Guide", author, "manual"
author, "manual"
)] )]
+3 -3
View File
@@ -87,7 +87,7 @@ set as ``name`` inside the file.
Gustom GUI and Icons Theme Gustom GUI and Icons Theme
-------------------------- --------------------------
A GUI theme ``.conf`` file consists of the follwing settings: A GUI theme ``.conf`` file consists of the following settings:
.. code-block:: cfg .. code-block:: cfg
@@ -127,7 +127,7 @@ In the Main section you must at least define the ``name`` and ``icontheme`` sett
``typicons_light`` or ``typicons_dark``, or to an icon theme in your custom icons directory. The ``typicons_light`` or ``typicons_dark``, or to an icon theme in your custom icons directory. The
setting must match the icon theme's folder name. setting must match the icon theme's folder name.
The Palette values correspond the the Qt enum values for QPalette::ColorRole, see the The Palette values correspond to the Qt enum values for QPalette::ColorRole, see the
`Qt documentation <https://doc.qt.io/qt-5.15/qpalette.html#ColorRole-enum>`_ for more details. The `Qt documentation <https://doc.qt.io/qt-5.15/qpalette.html#ColorRole-enum>`_ for more details. The
colour values are RGB numbers on the format ``r, g, b`` where each is an integer from to 255. colour values are RGB numbers on the format ``r, g, b`` where each is an integer from to 255.
Omitted values are not loaded and will use default values. Omitted values are not loaded and will use default values.
@@ -168,7 +168,7 @@ A syntax theme ``.conf`` file consists of the follwing settings:
modifier = 0, 0, 0 modifier = 0, 0, 0
In the Main section, you must define at least the ``name`` setting. The Syntax colour values are In the Main section, you must define at least the ``name`` setting. The Syntax colour values are
RGB numbers on the format ``r, g, b`` where each is an integer from to 255. Omitted values default RGB numbers of the format ``r, g, b`` where each is an integer from to 255. Omitted values default
to black, except ``background`` which defaults to white, to black, except ``background`` which defaults to white,
.. versionadded:: 2.2 .. versionadded:: 2.2
+2 -2
View File
@@ -32,7 +32,7 @@ Glossary
for more details. for more details.
Reference Reference
A references is one of a set of :term:`keywords<keyword>` that can be used to link to a A reference is one of a set of :term:`keywords<keyword>` that can be used to link to a
:term:`tag` in another document. The reference keywords are specific to the different :term:`tag` in another document. The reference keywords are specific to the different
:term:`root folder` types. A full overview is available in the :ref:`a_references` chapter. :term:`root folder` types. A full overview is available in the :ref:`a_references` chapter.
@@ -55,7 +55,7 @@ Glossary
Keyword Keyword
A keyword in novelWriter is a special command you put in the text of your documents. They are A keyword in novelWriter is a special command you put in the text of your documents. They are
not standard Markdown, but is used in novelWriter to add information that is interpreted by not standard Markdown, but are used in novelWriter to add information that is interpreted by
the application. For instance, keywords are used for :term:`tags<tag>` and the application. For instance, keywords are used for :term:`tags<tag>` and
:term:`references<reference>`. :term:`references<reference>`.
+3 -3
View File
@@ -10,7 +10,7 @@ Key Features
At its core, novelWriter is a multi-document plain text editor. It uses a markup syntax inspired by At its core, novelWriter is a multi-document plain text editor. It uses a markup syntax inspired by
Markdown_ to apply simple formatting to the text. It is designed for writing fiction, so the Markdown_ to apply simple formatting to the text. It is designed for writing fiction, so the
formatting features available are limited to those relevant for this purpose. It is *not* suitable formatting features available are limited to those relevant for this purpose. It is *not* suitable
for technical writing, and it is *not* a full-feature Markdown editor. for technical writing, and it is *not* a full-featured Markdown editor.
Your novel project is organised as a collection of separate plain text documents instead of a Your novel project is organised as a collection of separate plain text documents instead of a
single, large document. The idea here is to make it easier to reorganise your project structure single, large document. The idea here is to make it easier to reorganise your project structure
@@ -32,11 +32,11 @@ Below are some key features of novelWriter.
formatting text. Formatting is therefore limited to a small set of formatting tags for simple formatting text. Formatting is therefore limited to a small set of formatting tags for simple
things like text emphasis and paragraph alignment. When you really want to focus on just things like text emphasis and paragraph alignment. When you really want to focus on just
writing, you can switch the editor into :guilabel:`Focus Mode` where only the text editor panel writing, you can switch the editor into :guilabel:`Focus Mode` where only the text editor panel
itself is vissible, and the project structure view is hidden away. itself is visible, and the project structure view is hidden away.
**Keep an eye on your notes** **Keep an eye on your notes**
The main window can optionally show a document viewer to the right of the editor. This view The main window can optionally show a document viewer to the right of the editor. This view
panel is intended for displaying another scene document, you character notes, plot notes, or any panel is intended for displaying another scene document, your character notes, plot notes, or any
other document you may need to reference while writing. It is not intended as a preview panel other document you may need to reference while writing. It is not intended as a preview panel
for the document you're editing, but if you wish, you can also use it for this purpose. for the document you're editing, but if you wish, you can also use it for this purpose.
+4 -4
View File
@@ -14,8 +14,8 @@ novelWriter is built as a cross-platform application using `Python 3 <https://ww
the programming language, and `Qt 5 <https://www.qt.io/>`_ for the user interface. the programming language, and `Qt 5 <https://www.qt.io/>`_ for the user interface.
novelWriter is built for Linux first, and this is where it works best. However, it also runs fine novelWriter is built for Linux first, and this is where it works best. However, it also runs fine
on Windows and MacOS due to the cross-platform framework it's build on. The author of the on Windows and MacOS due to the cross-platform framework it's built on. The author of the
application doesn't own a Mac, so on-going Mac support is dependant on user feedback and user application doesn't own a Mac, so on-going Mac support is dependent on user feedback and user
contributions. contributions.
Spell checking in novelWriter is provided by a third party library called Spell checking in novelWriter is provided by a third party library called
@@ -36,7 +36,7 @@ read on.
This chapter explains the basics of how the application works and what it can and cannot do. This chapter explains the basics of how the application works and what it can and cannot do.
:ref:`a_ui_project` Recommended Reading :ref:`a_ui_project` Recommended Reading
This chapter will give you a more detailed explanation of how you the user interface components This chapter will give you a more detailed explanation of how you can use the user interface components
to organise and view your project work. to organise and view your project work.
:ref:`a_ui_writing` Recommended Reading :ref:`a_ui_writing` Recommended Reading
@@ -68,7 +68,7 @@ read on.
Organising Your Projects Organising Your Projects
======================== ========================
In addition to manage a collection of plain text files, novelWriter can interpret and map the In addition to managing a collection of plain text files, novelWriter can interpret and map the
structure of your novel and show you additional information about its flow and content. In order structure of your novel and show you additional information about its flow and content. In order
to take advantage of these features, you must structure your text in a specific way and add some to take advantage of these features, you must structure your text in a specific way and add some
meta data for it to extract. meta data for it to extract.
+2 -2
View File
@@ -105,7 +105,7 @@ much all collected meta data is available here in different columns.
You can select which novel root folder to display from the dropdown box, and you can select which You can select which novel root folder to display from the dropdown box, and you can select which
columns to show or hide from the menu button. You can also rearrange the columns by drag and drop. columns to show or hide from the menu button. You can also rearrange the columns by drag and drop.
The app will remember you column order and size between sessions, and for each individual project. The app will remember your column order and size between sessions, and for each individual project.
Colour Themes Colour Themes
@@ -220,7 +220,7 @@ makes it easier to track them with version control software.
Drive is mapped on Linux Gnome desktops using gvfs/gio. Drive is mapped on Linux Gnome desktops using gvfs/gio.
.. caution:: .. caution::
You should not add additional files to the project folder yourself. Nor should you,a s a rule, You should not add additional files to the project folder yourself. Nor should you, as a rule,
manually edit files within it. If you really must manually edit the text files, e.g. with some manually edit files within it. If you really must manually edit the text files, e.g. with some
automated task you want to perform, you need to rebuild the :term:`Project Index` when you open automated task you want to perform, you need to rebuild the :term:`Project Index` when you open
the project again. the project again.
+6 -6
View File
@@ -34,7 +34,7 @@ have a distinct colour, and the references themselves will get a colour if they
references will get a squiggly error line underneath. The same applies to duplicate tags. references will get a squiggly error line underneath. The same applies to duplicate tags.
There are a number of syntax highlighter colour themes available, both for light and dark GUIs. You There are a number of syntax highlighter colour themes available, both for light and dark GUIs. You
can select them for :guilabel:`Preferences`. can select them from :guilabel:`Preferences`.
.. _a_fmt_head: .. _a_fmt_head:
@@ -72,7 +72,7 @@ For headers level one and two, adding a ``!`` modifies the behaviour of the head
``#! Title Text`` ``#! Title Text``
This tells the build tool that the level one heading is intended to be used for the novel's This tells the build tool that the level one heading is intended to be used for the novel's
main title, like for instance on the front page. When building the manuscript, this will use a main title, like for instance on the front page. When building the manuscript, this will use a
different styling and will exclude the title from for instance a Table of Contents in Libre different styling and will exclude the title from, for instance, a Table of Contents in Libre
Office. Office.
``##! Title Text`` ``##! Title Text``
@@ -170,7 +170,7 @@ in a special manner and will show up in the :ref:`a_ui_outline` in a dedicated c
indicate this by altering the colour of the word. indicate this by altering the colour of the word.
``% text...`` ``% text...``
This is a comment. The text is not renderred by default (this can be overridden), seen in the This is a comment. The text is not rendered by default (this can be overridden), seen in the
document viewer, or counted towards word counts. document viewer, or counted towards word counts.
``% Synopsis: text...`` ``% Synopsis: text...``
@@ -196,10 +196,10 @@ Tags use the command ``@tag:`` to define a tag. The tag can be set once per sect
heading. Setting it multiple times under the same heading will just override the previous setting. heading. Setting it multiple times under the same heading will just override the previous setting.
``@tag: value`` ``@tag: value``
A tag commad followed by the tag value, like for instance the name of a character. A tag command followed by the tag value, like for instance the name of a character.
References can be set anywhere within a section, and are collected according to their category. References can be set anywhere within a section, and are collected according to their category.
References are on the form: References are in the form:
``@keyword: value`` ``@keyword: value``
A reference keyword followed by a value, or a comma separated list of values. A reference keyword followed by a value, or a comma separated list of values.
@@ -236,7 +236,7 @@ Examples:
"``>> Centred text <<``", "The text paragraph is centred." "``>> Centred text <<``", "The text paragraph is centred."
"``> Left indented text``", "The text has an increased left margin." "``> Left indented text``", "The text has an increased left margin."
"``Right indented text <``", "The text has an increased right margin." "``Right indented text <``", "The text has an increased right margin."
"``> Left/right indented text <``", "The text has an both margins increased." "``> Left/right indented text <``", "The text has both margins increased."
.. note:: .. note::
The text editor will not show the alignment and indentation live. But the viewer will show them The text editor will not show the alignment and indentation live. But the viewer will show them
+1 -1
View File
@@ -4,7 +4,7 @@
Keyboard Shortcuts Keyboard Shortcuts
****************** ******************
Most features in novelWriter are available as keyboard shortcuts. This is a reference list if these Most features in novelWriter are available as keyboard shortcuts. This is a reference list of those
shortcuts. Most of them are also listed in the application's user interface. shortcuts. Most of them are also listed in the application's user interface.
.. note:: .. note::
+7
View File
@@ -48,6 +48,7 @@ instance `en_GB` for British English.
You will need the translation tool Qt 5 Linguist on your system. You will need the translation tool Qt 5 Linguist on your system.
For Ubuntu/Debian, run: For Ubuntu/Debian, run:
```bash ```bash
sudo apt install qttools5-dev-tools sudo apt install qttools5-dev-tools
``` ```
@@ -75,6 +76,7 @@ The `i18n` folder at the root of the repository contains the `nw_XX.ts` translat
Whether to add a new language to the translation framework, or to update the file against the Whether to add a new language to the translation framework, or to update the file against the
current source code, you must first run the `qtlupdate` command: current source code, you must first run the `qtlupdate` command:
```bash ```bash
python3 pkgutils.py qtlupdate i18n/nw_XX.ts python3 pkgutils.py qtlupdate i18n/nw_XX.ts
``` ```
@@ -86,6 +88,10 @@ be a valid ISO language code, otherwise novelWriter will not accept the file.
For instance, the French translation uses the language code `fr_FR`, so its translation file will For instance, the French translation uses the language code `fr_FR`, so its translation file will
be `nw_fr_FR.ts` be `nw_fr_FR.ts`
Note: The `qtlupdate` command needs the `lupdate` tool provided by PyQt6, which uses the latest
TS file format. The tool in PyQt5 generates an older file format. On Debian/Ubuntu it is provided
by the package `pyqt6-dev-tools`.
### Edit the Translation File in Qt Linguist ### Edit the Translation File in Qt Linguist
@@ -100,6 +106,7 @@ Please select "English" and "United Kingdom" as the _source_ language if prompte
The application does not use `.ts` files directly. The `novelwriter/assets/i18n/nw_XX.qm` files are The application does not use `.ts` files directly. The `novelwriter/assets/i18n/nw_XX.qm` files are
the actual files used to translate the GUI into another language other than the default British the actual files used to translate the GUI into another language other than the default British
English. These files are not generated by default, but they can be built with: English. These files are not generated by default, but they can be built with:
```bash ```bash
python3 pkgutils.py qtlrelease python3 pkgutils.py qtlrelease
``` ```
+13
View File
@@ -5,6 +5,8 @@
<h2>Release Notes for 2.1</h2> <h2>Release Notes for 2.1</h2>
<p><i>Released on 17 October 2023</i></p> <p><i>Released on 17 October 2023</i></p>
<p>Scroll down for <a href="#patch">Patch Notes</a></p>
<p>The primary focus of this release has been a complete redesign of the Build Tool, that is, the <p>The primary focus of this release has been a complete redesign of the Build Tool, that is, the
tool that assembles your project into a manuscript document. The new tool, called the "Manuscript tool that assembles your project into a manuscript document. The new tool, called the "Manuscript
Build Tool" allows you to define multiple build definitions for your project. The build definitions Build Tool" allows you to define multiple build definitions for your project. The build definitions
@@ -30,5 +32,16 @@ a full list of changes, see the detailed changelogs.</p>
<p><i>See also the <a href="https://github.com/vkbo/novelWriter/releases">Releases</a> page.</i></p> <p><i>See also the <a href="https://github.com/vkbo/novelWriter/releases">Releases</a> page.</i></p>
<a name="patch"></a><h2>Patch Notes</h2>
<h3>Patch 2.2.1 &ndash; 5 November 2023</h3>
<p>This is a patch release that fixes a layout issue and internationalisation issues with the new
Manuscript Build tool. It also fixes a number of issues related to bugs in the underlying Qt
framework that affects drag and drop functionality in the project tree. These issues were mostly
only affecting Debian Linux package releases.</p>
<p>Other, minor issues related to updating the editor on colour theme change and project word list
changes have been fixed as well. See the full changelog for more details.</p>
</body> </body>
</html> </html>
+8 -6
View File
@@ -81,10 +81,12 @@ class Config:
# Localisation # Localisation
# Note that these paths must be strings # Note that these paths must be strings
self._qLocale = QLocale.system() self._nwLangPath = self._appPath / "assets" / "i18n"
self._qtTrans = {}
self._qtLangPath = QLibraryInfo.location(QLibraryInfo.TranslationsPath) self._qtLangPath = QLibraryInfo.location(QLibraryInfo.TranslationsPath)
self._nwLangPath = str(self._appPath / "assets" / "i18n")
wantedLocale = self._nwLangPath / f"nw_{QLocale.system().name()}.qm"
self._qLocale = QLocale.system() if wantedLocale.exists() else QLocale("en_GB")
self._qtTrans = {}
# PDF Manual # PDF Manual
pdfDocs = self._appPath / "assets" / "manual.pdf" pdfDocs = self._appPath / "assets" / "manual.pdf"
@@ -425,7 +427,7 @@ class Config:
else: else:
return [] return []
for qmFile in Path(self._nwLangPath).iterdir(): for qmFile in self._nwLangPath.iterdir():
qmName = qmFile.name qmName = qmFile.name
if not (qmFile.is_file() and qmName.startswith(fPre) and qmName.endswith(fExt)): if not (qmFile.is_file() and qmName.startswith(fPre) and qmName.endswith(fExt)):
continue continue
@@ -493,8 +495,8 @@ class Config:
self._qtTrans = {} self._qtTrans = {}
langList = [ langList = [
(self._qtLangPath, "qtbase"), # Qt 5.x (self._qtLangPath, "qtbase"), # Qt 5.x
(self._nwLangPath, "nw"), # novelWriter (str(self._nwLangPath), "nw"), # novelWriter
] ]
for lngPath, lngBase in langList: for lngPath, lngBase in langList:
for lngCode in self._qLocale.uiLanguages(): for lngCode in self._qLocale.uiLanguages():
+2 -2
View File
@@ -32,7 +32,7 @@ from enum import Enum
from typing import Iterable from typing import Iterable
from pathlib import Path from pathlib import Path
from PyQt5.QtCore import QT_TRANSLATE_NOOP from PyQt5.QtCore import QT_TRANSLATE_NOOP, QCoreApplication
from novelwriter import CONFIG from novelwriter import CONFIG
from novelwriter.enum import nwBuildFmt from novelwriter.enum import nwBuildFmt
@@ -209,7 +209,7 @@ class BuildSettings:
@staticmethod @staticmethod
def getLabel(key: str) -> str: def getLabel(key: str) -> str:
"""Extract the GUI label for a specific setting.""" """Extract the GUI label for a specific setting."""
return SETTINGS_LABELS.get(key, "ERROR") return QCoreApplication.translate("Builds", SETTINGS_LABELS.get(key, "ERROR"))
def getStr(self, key: str) -> str: def getStr(self, key: str) -> str:
"""Type safe value access for strings.""" """Type safe value access for strings."""
+1 -1
View File
@@ -404,7 +404,7 @@ class NWIndex:
tagName = tBits[1] tagName = tBits[1]
self._tagsIndex.add(tagName, tHandle, sTitle, itemClass) self._tagsIndex.add(tagName, tHandle, sTitle, itemClass)
self._itemIndex.setHeadingTag(tHandle, sTitle, tagName) self._itemIndex.setHeadingTag(tHandle, sTitle, tagName)
tags[tagName] = True tags[tagName.lower()] = True
else: else:
self._itemIndex.addHeadingRef(tHandle, sTitle, tBits[1:], tBits[0]) self._itemIndex.addHeadingRef(tHandle, sTitle, tBits[1:], tBits[0])
+2 -2
View File
@@ -171,7 +171,7 @@ class ToHtml(Tokenizer):
pStyle = None pStyle = None
lines = [] lines = []
for tType, tLine, tText, tFormat, tStyle in self._tokens: for tType, nHead, tText, tFormat, tStyle in self._tokens:
# Replace < and > with HTML entities # Replace < and > with HTML entities
if tFormat: if tFormat:
@@ -230,7 +230,7 @@ class ToHtml(Tokenizer):
hStyle = "" hStyle = ""
if self._linkHeaders: if self._linkHeaders:
aNm = f"<a name='T{tLine:06d}'></a>" aNm = f"<a name='T{nHead:04d}'></a>"
else: else:
aNm = "" aNm = ""
+23 -18
View File
@@ -404,23 +404,22 @@ class Tokenizer(ABC):
The format of the token list is an entry with a five-tuple for The format of the token list is an entry with a five-tuple for
each line in the file. The tuple is as follows: each line in the file. The tuple is as follows:
1: The type of the block, self.T_* 1: The type of the block, self.T_*
2: The line in the file where this block occurred 2: The header number under which the text is placed
3: The text content of the block, without leading tags 3: The text content of the block, without leading tags
4: The internal formatting map of the text, self.FMT_* 4: The internal formatting map of the text, self.FMT_*
5: The style of the block, self.A_* 5: The style of the block, self.A_*
""" """
self._tokens = [] self._tokens = []
tmpMarkdown = [] tmpMarkdown = []
nLine = 0 nHead = 0
breakNext = False breakNext = False
for aLine in self._text.splitlines(): for aLine in self._text.splitlines():
nLine += 1
sLine = aLine.strip().lower() sLine = aLine.strip().lower()
# 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, nLine, "", None, self.A_NONE self.T_EMPTY, nHead, "", None, self.A_NONE
)) ))
if self._keepMarkdown: if self._keepMarkdown:
tmpMarkdown.append("\n") tmpMarkdown.append("\n")
@@ -448,7 +447,7 @@ class Tokenizer(ABC):
elif sLine == "[vspace]": elif sLine == "[vspace]":
self._tokens.append( self._tokens.append(
(self.T_SKIP, nLine, "", None, sAlign) (self.T_SKIP, nHead, "", None, sAlign)
) )
continue continue
@@ -456,11 +455,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, nLine, "", None, sAlign) (self.T_SKIP, nHead, "", None, sAlign)
) )
if nSkip > 1: if nSkip > 1:
self._tokens += (nSkip - 1) * [ self._tokens += (nSkip - 1) * [
(self.T_SKIP, nLine, "", None, self.A_NONE) (self.T_SKIP, nHead, "", None, self.A_NONE)
] ]
continue continue
@@ -469,20 +468,20 @@ class Tokenizer(ABC):
synTag = cLine[:9].lower() synTag = cLine[:9].lower()
if synTag == "synopsis:": if synTag == "synopsis:":
self._tokens.append(( self._tokens.append((
self.T_SYNOPSIS, nLine, cLine[9:].strip(), None, sAlign self.T_SYNOPSIS, nHead, cLine[9:].strip(), None, 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, nLine, aLine[1:].strip(), None, sAlign self.T_COMMENT, nHead, aLine[1:].strip(), None, 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, nLine, aLine[1:].strip(), None, sAlign self.T_KEYWORD, nHead, aLine[1:].strip(), None, sAlign
)) ))
if self._doKeywords and self._keepMarkdown: if self._doKeywords and self._keepMarkdown:
tmpMarkdown.append("%s\n" % aLine) tmpMarkdown.append("%s\n" % aLine)
@@ -492,8 +491,9 @@ class Tokenizer(ABC):
sAlign |= self.A_CENTRE sAlign |= self.A_CENTRE
sAlign |= self.A_PBB sAlign |= self.A_PBB
nHead += 1
self._tokens.append(( self._tokens.append((
self.T_HEAD1, nLine, aLine[2:].strip(), None, sAlign self.T_HEAD1, nHead, aLine[2:].strip(), None, sAlign
)) ))
if self._keepMarkdown: if self._keepMarkdown:
tmpMarkdown.append("%s\n" % aLine) tmpMarkdown.append("%s\n" % aLine)
@@ -502,39 +502,44 @@ class Tokenizer(ABC):
if self._isNovel: if self._isNovel:
sAlign |= self.A_PBB sAlign |= self.A_PBB
nHead += 1
self._tokens.append(( self._tokens.append((
self.T_HEAD2, nLine, aLine[3:].strip(), None, sAlign self.T_HEAD2, nHead, aLine[3:].strip(), None, sAlign
)) ))
if self._keepMarkdown: if self._keepMarkdown:
tmpMarkdown.append("%s\n" % aLine) tmpMarkdown.append("%s\n" % aLine)
elif aLine[:4] == "### ": elif aLine[:4] == "### ":
nHead += 1
self._tokens.append(( self._tokens.append((
self.T_HEAD3, nLine, aLine[4:].strip(), None, sAlign self.T_HEAD3, nHead, aLine[4:].strip(), None, sAlign
)) ))
if self._keepMarkdown: if self._keepMarkdown:
tmpMarkdown.append("%s\n" % aLine) tmpMarkdown.append("%s\n" % aLine)
elif aLine[:5] == "#### ": elif aLine[:5] == "#### ":
nHead += 1
self._tokens.append(( self._tokens.append((
self.T_HEAD4, nLine, aLine[5:].strip(), None, sAlign self.T_HEAD4, nHead, aLine[5:].strip(), None, sAlign
)) ))
if self._keepMarkdown: if self._keepMarkdown:
tmpMarkdown.append("%s\n" % aLine) tmpMarkdown.append("%s\n" % aLine)
elif aLine[:3] == "#! ": elif aLine[:3] == "#! ":
nHead += 1
if self._isNovel: if self._isNovel:
tStyle = self.T_TITLE tStyle = self.T_TITLE
else: else:
tStyle = self.T_HEAD1 tStyle = self.T_HEAD1
self._tokens.append(( self._tokens.append((
tStyle, nLine, aLine[3:].strip(), None, sAlign | self.A_CENTRE tStyle, nHead, aLine[3:].strip(), None, sAlign | self.A_CENTRE
)) ))
if self._keepMarkdown: if self._keepMarkdown:
tmpMarkdown.append("%s\n" % aLine) tmpMarkdown.append("%s\n" % aLine)
elif aLine[:4] == "##! ": elif aLine[:4] == "##! ":
nHead += 1
if self._isNovel: if self._isNovel:
tStyle = self.T_UNNUM tStyle = self.T_UNNUM
sAlign |= self.A_PBB sAlign |= self.A_PBB
@@ -542,7 +547,7 @@ class Tokenizer(ABC):
tStyle = self.T_HEAD2 tStyle = self.T_HEAD2
self._tokens.append(( self._tokens.append((
tStyle, nLine, aLine[4:].strip(), None, sAlign tStyle, nHead, aLine[4:].strip(), None, sAlign
)) ))
if self._keepMarkdown: if self._keepMarkdown:
tmpMarkdown.append("%s\n" % aLine) tmpMarkdown.append("%s\n" % aLine)
@@ -586,7 +591,7 @@ class Tokenizer(ABC):
# Process formats # Process formats
tLine, fmtPos = self._extractFormats(aLine) tLine, fmtPos = self._extractFormats(aLine)
self._tokens.append(( self._tokens.append((
self.T_TEXT, nLine, tLine, fmtPos, sAlign self.T_TEXT, nHead, tLine, fmtPos, sAlign
)) ))
if self._keepMarkdown: if self._keepMarkdown:
tmpMarkdown.append("%s\n" % aLine) tmpMarkdown.append("%s\n" % aLine)
@@ -605,7 +610,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, nLine, "", None, self.A_NONE self.T_EMPTY, nHead, "", None, self.A_NONE
)) ))
if self._keepMarkdown: if self._keepMarkdown:
tmpMarkdown.append("\n") tmpMarkdown.append("\n")
+2
View File
@@ -176,6 +176,8 @@ class GuiPreferencesGeneral(QWidget):
for lang, langName in theLangs: for lang, langName in theLangs:
self.guiLocale.addItem(langName, lang) self.guiLocale.addItem(langName, lang)
langIdx = self.guiLocale.findData(CONFIG.guiLocale) langIdx = self.guiLocale.findData(CONFIG.guiLocale)
if langIdx < 0:
langIdx = self.guiLocale.findData("en_GB")
if langIdx != -1: if langIdx != -1:
self.guiLocale.setCurrentIndex(langIdx) self.guiLocale.setCurrentIndex(langIdx)
+2 -2
View File
@@ -338,7 +338,7 @@ class GuiDocEditor(QPlainTextEdit):
if self._docHandle is None: if self._docHandle is None:
self.clearEditor() self.clearEditor()
else: else:
self.redrawText() self._qDocument.syntaxHighlighter.rehighlight()
return return
@@ -980,7 +980,7 @@ class GuiDocEditor(QPlainTextEdit):
uCursor = self.textCursor() uCursor = self.textCursor()
pCursor = self.cursorForPosition(pos) pCursor = self.cursorForPosition(pos)
ctxMenu = QMenu() ctxMenu = QMenu(self)
# Follow # Follow
if self._followTag(cursor=pCursor, loadTag=False): if self._followTag(cursor=pCursor, loadTag=False):
+1 -1
View File
@@ -381,7 +381,7 @@ class GuiDocViewer(QTextBrowser):
userCursor = self.textCursor() userCursor = self.textCursor()
userSelection = userCursor.hasSelection() userSelection = userCursor.hasSelection()
mnuContext = QMenu() mnuContext = QMenu(self)
# Cut, Copy and Paste # Cut, Copy and Paste
# =================== # ===================
+1 -1
View File
@@ -228,7 +228,7 @@ class GuiNovelToolBar(QWidget):
self.tbRefresh.clicked.connect(self._refreshNovelTree) self.tbRefresh.clicked.connect(self._refreshNovelTree)
# More Options Menu # More Options Menu
self.mMore = QMenu() self.mMore = QMenu(self)
self.mLastCol = self.mMore.addMenu(self.tr("Last Column")) self.mLastCol = self.mMore.addMenu(self.tr("Last Column"))
self.gLastCol = QActionGroup(self.mMore) self.gLastCol = QActionGroup(self.mMore)
+54 -7
View File
@@ -31,8 +31,8 @@ from enum import Enum
from time import time from time import time
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from PyQt5.QtGui import QDropEvent, QMouseEvent, QPalette from PyQt5.QtGui import QDragMoveEvent, QDropEvent, QMouseEvent, QPalette
from PyQt5.QtCore import QPoint, Qt, QSize, pyqtSignal, pyqtSlot from PyQt5.QtCore import QPoint, QTimer, Qt, QSize, pyqtSignal, pyqtSlot
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
QAbstractItemView, QDialog, QFrame, QHBoxLayout, QHeaderView, QLabel, QAbstractItemView, QDialog, QFrame, QHBoxLayout, QHeaderView, QLabel,
QMenu, QShortcut, QSizePolicy, QToolButton, QTreeWidget, QTreeWidgetItem, QMenu, QShortcut, QSizePolicy, QToolButton, QTreeWidget, QTreeWidgetItem,
@@ -248,7 +248,7 @@ class GuiProjectToolBar(QWidget):
self.viewLabel.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) self.viewLabel.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
# Quick Links # Quick Links
self.mQuick = QMenu() self.mQuick = QMenu(self)
self.tbQuick = QToolButton(self) self.tbQuick = QToolButton(self)
self.tbQuick.setToolTip("%s [Ctrl+L]" % self.tr("Quick Links")) self.tbQuick.setToolTip("%s [Ctrl+L]" % self.tr("Quick Links"))
@@ -269,7 +269,7 @@ class GuiProjectToolBar(QWidget):
self.tbMoveD.clicked.connect(lambda: self.projTree.moveTreeItem(1)) self.tbMoveD.clicked.connect(lambda: self.projTree.moveTreeItem(1))
# Add Item Menu # Add Item Menu
self.mAdd = QMenu() self.mAdd = QMenu(self)
self.aAddEmpty = self.mAdd.addAction(trConst(nwLabels.ITEM_DESCRIPTION["document"])) self.aAddEmpty = self.mAdd.addAction(trConst(nwLabels.ITEM_DESCRIPTION["document"]))
self.aAddEmpty.triggered.connect( self.aAddEmpty.triggered.connect(
@@ -307,7 +307,7 @@ class GuiProjectToolBar(QWidget):
self.tbAdd.setPopupMode(QToolButton.InstantPopup) self.tbAdd.setPopupMode(QToolButton.InstantPopup)
# More Options Menu # More Options Menu
self.mMore = QMenu() self.mMore = QMenu(self)
self.aExpand = self.mMore.addAction(self.tr("Expand All")) self.aExpand = self.mMore.addAction(self.tr("Expand All"))
self.aExpand.triggered.connect(lambda: self.projTree.setExpandedFromHandle(None, True)) self.aExpand.triggered.connect(lambda: self.projTree.setExpandedFromHandle(None, True))
@@ -507,8 +507,15 @@ class GuiProjectTree(QTreeWidget):
# Allow Move by Drag & Drop # Allow Move by Drag & Drop
self.setDragEnabled(True) self.setDragEnabled(True)
self.setDragDropMode(QAbstractItemView.InternalMove) self.setDragDropMode(QAbstractItemView.InternalMove)
self.setDropIndicatorShown(True)
# Disable built-in autoscroll as it isn't working in some Qt
# releases (see #1561) and instead use our own implementation
self.setAutoScroll(False)
# But don't allow drop on root level # But don't allow drop on root level
# Due to a bug, this stops working somewhere between Qt 5.15.3
# and 5.15.8, so this is also blocked in dropEvent (see #1569)
trRoot = self.invisibleRootItem() trRoot = self.invisibleRootItem()
trRoot.setFlags(trRoot.flags() ^ Qt.ItemIsDropEnabled) trRoot.setFlags(trRoot.flags() ^ Qt.ItemIsDropEnabled)
@@ -524,6 +531,13 @@ class GuiProjectTree(QTreeWidget):
self.itemDoubleClicked.connect(self._treeDoubleClick) self.itemDoubleClicked.connect(self._treeDoubleClick)
self.itemSelectionChanged.connect(self._treeSelectionChange) self.itemSelectionChanged.connect(self._treeSelectionChange)
# Autoscroll
self._scrollMargin = SHARED.theme.baseIconSize
self._scrollDirection = 0
self._scrollTimer = QTimer()
self._scrollTimer.timeout.connect(self._doAutoScroll)
self._scrollTimer.setInterval(250)
# Set custom settings # Set custom settings
self.initSettings() self.initSettings()
@@ -1196,7 +1210,7 @@ class GuiProjectTree(QTreeWidget):
logger.debug("No item found") logger.debug("No item found")
return False return False
ctxMenu = QMenu() ctxMenu = QMenu(self)
# Trash Folder # Trash Folder
# ============ # ============
@@ -1343,6 +1357,17 @@ class GuiProjectTree(QTreeWidget):
return True return True
@pyqtSlot()
def _doAutoScroll(self) -> None:
"""Scroll one item up or down based on direction value."""
if self._scrollDirection == -1:
self.scrollToItem(self.itemAbove(self.itemAt(1, 1)))
elif self._scrollDirection == 1:
self.scrollToItem(self.itemBelow(self.itemAt(1, self.height() - 1)))
self._scrollDirection = 0
self._scrollTimer.stop()
return
## ##
# Events # Events
## ##
@@ -1374,14 +1399,36 @@ class GuiProjectTree(QTreeWidget):
return return
def dragMoveEvent(self, event: QDragMoveEvent) -> None:
"""Capture the drag move event to enable edge autoscroll."""
y = event.pos().y()
if y < self._scrollMargin:
if not self._scrollTimer.isActive():
self._scrollDirection = -1
self._scrollTimer.start()
elif y > self.height() - self._scrollMargin:
if not self._scrollTimer.isActive():
self._scrollDirection = 1
self._scrollTimer.start()
super().dragMoveEvent(event)
return
def dropEvent(self, event: QDropEvent) -> None: def dropEvent(self, event: QDropEvent) -> None:
"""Overload the drop item event to ensure relevant data has been """Overload the drop item event to ensure relevant data has been
updated. updated.
""" """
sHandle = self.getSelectedHandle() sHandle = self.getSelectedHandle()
sItem = self._getTreeItem(sHandle) if sHandle else None sItem = self._getTreeItem(sHandle) if sHandle else None
if sHandle is None or sItem is None: if sHandle is None or sItem is None or sItem.parent() is None:
logger.error("Invalid drag and drop event") logger.error("Invalid drag and drop event")
event.ignore()
return
if not self.indexAt(event.pos()).isValid():
# Needed due to a bug somewhere around Qt 5.15.8 that
# ignores the invisible root item flags
logger.error("Invalid drop location")
event.ignore()
return return
logger.debug("Drag'n'drop of item '%s' accepted", sHandle) logger.debug("Drag'n'drop of item '%s' accepted", sHandle)
+1
View File
@@ -991,6 +991,7 @@ class GuiMain(QMainWindow):
if dlgWords.result() == QDialog.Accepted: if dlgWords.result() == QDialog.Accepted:
logger.debug("Reloading word list") logger.debug("Reloading word list")
SHARED.updateSpellCheckLanguage(reload=True) SHARED.updateSpellCheckLanguage(reload=True)
self.docEditor.spellCheckDocument()
return True return True
+8 -6
View File
@@ -332,14 +332,16 @@ class _FilterTab(QWidget):
treeHeader = self.optTree.header() treeHeader = self.optTree.header()
treeHeader.setStretchLastSection(False) treeHeader.setStretchLastSection(False)
treeHeader.setMinimumSectionSize(iPx + cMg) # See Issue #1551
treeHeader.setSectionResizeMode(self.C_NAME, QHeaderView.Stretch) treeHeader.setSectionResizeMode(self.C_NAME, QHeaderView.Stretch)
treeHeader.setSectionResizeMode(self.C_ACTIVE, QHeaderView.Fixed) treeHeader.setSectionResizeMode(self.C_ACTIVE, QHeaderView.Fixed)
treeHeader.setSectionResizeMode(self.C_STATUS, QHeaderView.Fixed) treeHeader.setSectionResizeMode(self.C_STATUS, QHeaderView.Fixed)
treeHeader.resizeSection(self.C_ACTIVE, iPx + cMg) treeHeader.resizeSection(self.C_ACTIVE, iPx + cMg)
treeHeader.resizeSection(self.C_STATUS, iPx + cMg) treeHeader.resizeSection(self.C_STATUS, iPx + cMg)
self.optTree.setSelectionMode(QAbstractItemView.ExtendedSelection)
self.optTree.setDragDropMode(QAbstractItemView.NoDragDrop) self.optTree.setDragDropMode(QAbstractItemView.NoDragDrop)
self.optTree.setSelectionMode(QAbstractItemView.ExtendedSelection)
self.optTree.setSelectionBehavior(QAbstractItemView.SelectRows)
# Filters # Filters
# ======= # =======
@@ -390,11 +392,11 @@ class _FilterTab(QWidget):
self.mainSplit.addWidget(self.filterOpt) self.mainSplit.addWidget(self.filterOpt)
self.mainSplit.setCollapsible(0, False) self.mainSplit.setCollapsible(0, False)
self.mainSplit.setCollapsible(1, False) self.mainSplit.setCollapsible(1, False)
self.mainSplit.setStretchFactor(0, 0) self.mainSplit.setStretchFactor(0, 1)
self.mainSplit.setStretchFactor(1, 1) self.mainSplit.setStretchFactor(1, 0)
self.mainSplit.setSizes([ self.mainSplit.setSizes([
CONFIG.pxInt(pOptions.getInt("GuiBuildSettings", "treeWidth", 1)), CONFIG.pxInt(pOptions.getInt("GuiBuildSettings", "treeWidth", 300)),
CONFIG.pxInt(pOptions.getInt("GuiBuildSettings", "filterWidth", 1)) CONFIG.pxInt(pOptions.getInt("GuiBuildSettings", "filterWidth", 300))
]) ])
self.outerBox = QHBoxLayout() self.outerBox = QHBoxLayout()
@@ -718,7 +720,7 @@ class _HeadingsTab(QWidget):
self.formSyntax = _HeadingSyntaxHighlighter(self.editTextBox.document()) self.formSyntax = _HeadingSyntaxHighlighter(self.editTextBox.document())
self.menuInsert = QMenu() self.menuInsert = QMenu(self)
self.aInsTitle = self.menuInsert.addAction(self.tr("Title")) self.aInsTitle = self.menuInsert.addAction(self.tr("Title"))
self.aInsChNum = self.menuInsert.addAction(self.tr("Chapter Number")) self.aInsChNum = self.menuInsert.addAction(self.tr("Chapter Number"))
self.aInsChWord = self.menuInsert.addAction(self.tr("Chapter Number (Word)")) self.aInsChWord = self.menuInsert.addAction(self.tr("Chapter Number (Word)"))
+1
View File
@@ -325,6 +325,7 @@ def buildQtI18nTS(sysArgs):
from PyQt6.lupdate import lupdate from PyQt6.lupdate import lupdate
except ImportError: except ImportError:
print("ERROR: This command requires lupdate from PyQt6") print("ERROR: This command requires lupdate from PyQt6")
print("On Debian/Ubuntu, install: pyqt6-dev-tools")
sys.exit(1) sys.exit(1)
print("") print("")
+2 -4
View File
@@ -1,12 +1,10 @@
%%~name: Space %%~name: Space
%%~path: 15c4492bd5107/f1471bef9f2ae %%~path: 15c4492bd5107/f1471bef9f2ae
%%~kind: WORLD/NOTE %%~kind: WORLD/NOTE
%%~hash: bc139a020239c9c0d936aa9d0bcc3644ae2f370c %%~hash: b08c9796fd9918a87e107bf26606e75341a49ce8
%%~date: Unknown/2023-08-25 16:52:06 %%~date: Unknown/2023-11-03 00:36:58
# Space # Space
@tag: Space @tag: Space
Space … its an awful lot of nothing, with bits in it here and there. Some of which, people like to call home. Space … its an awful lot of nothing, with bits in it here and there. Some of which, people like to call home.
+3 -3
View File
@@ -166,21 +166,21 @@ def testBaseConfig_Localisation(fncPath, tstPaths):
i18nDir = fncPath / "i18n" i18nDir = fncPath / "i18n"
i18nDir.mkdir() i18nDir.mkdir()
tstConf._nwLangPath = str(i18nDir) tstConf._nwLangPath = i18nDir
copyfile(tstPaths.filesDir / "nw_en_GB.qm", i18nDir / "nw_en_GB.qm") copyfile(tstPaths.filesDir / "nw_en_GB.qm", i18nDir / "nw_en_GB.qm")
writeFile(i18nDir / "nw_en_GB.ts", "") writeFile(i18nDir / "nw_en_GB.ts", "")
writeFile(i18nDir / "nw_abcd.qm", "") writeFile(i18nDir / "nw_abcd.qm", "")
tstApp = MockApp() tstApp = MockApp()
tstConf.initLocalisation(tstApp) tstConf.initLocalisation(tstApp) # type: ignore
# Check Lists # Check Lists
theList = tstConf.listLanguages(tstConf.LANG_NW) theList = tstConf.listLanguages(tstConf.LANG_NW)
assert theList == [("en_GB", "British English")] assert theList == [("en_GB", "British English")]
theList = tstConf.listLanguages(tstConf.LANG_PROJ) theList = tstConf.listLanguages(tstConf.LANG_PROJ)
assert theList == [("en_GB", "British English")] assert theList == [("en_GB", "British English")]
theList = tstConf.listLanguages(None) theList = tstConf.listLanguages(None) # type: ignore
assert theList == [] assert theList == []
# Add Language # Add Language
+261 -267
View File
@@ -29,177 +29,176 @@ from novelwriter.core.project import NWProject
@pytest.mark.core @pytest.mark.core
def testCoreToHtml_ConvertFormat(mockGUI): def testCoreToHtml_ConvertFormat(mockGUI):
"""Test the tokenizer and converter chain using the ToHtml class. """Test the tokenizer and converter chain using the ToHtml class."""
""" project = NWProject()
theProject = NWProject() html = ToHtml(project)
theHtml = ToHtml(theProject)
# Novel Files Headers # Novel Files Headers
# =================== # ===================
theHtml._isNovel = True html._isNovel = True
theHtml._isNote = False html._isNote = False
theHtml._isFirst = True html._isFirst = True
# Header 1 # Header 1
theHtml._text = "# Partition\n" html._text = "# Partition\n"
theHtml.tokenizeText() html.tokenizeText()
theHtml.doConvert() html.doConvert()
assert theHtml.theResult == ( assert html.theResult == (
"<h1 class='title' style='text-align: center;'>Partition</h1>\n" "<h1 class='title' style='text-align: center;'>Partition</h1>\n"
) )
# Header 2 # Header 2
theHtml._text = "## Chapter Title\n" html._text = "## Chapter Title\n"
theHtml.tokenizeText() html.tokenizeText()
theHtml.doConvert() html.doConvert()
assert theHtml.theResult == ( assert html.theResult == (
"<h1 style='page-break-before: always;'>Chapter Title</h1>\n" "<h1 style='page-break-before: always;'>Chapter Title</h1>\n"
) )
# Header 3 # Header 3
theHtml._text = "### Scene Title\n" html._text = "### Scene Title\n"
theHtml.tokenizeText() html.tokenizeText()
theHtml.doConvert() html.doConvert()
assert theHtml.theResult == "<h2>Scene Title</h2>\n" assert html.theResult == "<h2>Scene Title</h2>\n"
# Header 4 # Header 4
theHtml._text = "#### Section Title\n" html._text = "#### Section Title\n"
theHtml.tokenizeText() html.tokenizeText()
theHtml.doConvert() html.doConvert()
assert theHtml.theResult == "<h3>Section Title</h3>\n" assert html.theResult == "<h3>Section Title</h3>\n"
# Title # Title
theHtml._text = "#! Title\n" html._text = "#! Title\n"
theHtml.tokenizeText() html.tokenizeText()
theHtml.doConvert() html.doConvert()
assert theHtml.theResult == ( assert html.theResult == (
"<h1 class='title' style='text-align: center;'>Title</h1>\n" "<h1 class='title' style='text-align: center;'>Title</h1>\n"
) )
# Unnumbered # Unnumbered
theHtml._text = "##! Prologue\n" html._text = "##! Prologue\n"
theHtml.tokenizeText() html.tokenizeText()
theHtml.doConvert() html.doConvert()
assert theHtml.theResult == "<h1 style='page-break-before: always;'>Prologue</h1>\n" assert html.theResult == "<h1 style='page-break-before: always;'>Prologue</h1>\n"
# Note Files Headers # Note Files Headers
# ================== # ==================
theHtml._isNovel = False html._isNovel = False
theHtml._isNote = True html._isNote = True
theHtml._isFirst = True html._isFirst = True
theHtml.setLinkHeaders(True) html.setLinkHeaders(True)
# Header 1 # Header 1
theHtml._text = "# Heading One\n" html._text = "# Heading One\n"
theHtml.tokenizeText() html.tokenizeText()
theHtml.doConvert() html.doConvert()
assert theHtml.theResult == "<h1><a name='T000001'></a>Heading One</h1>\n" assert html.theResult == "<h1><a name='T0001'></a>Heading One</h1>\n"
# Header 2 # Header 2
theHtml._text = "## Heading Two\n" html._text = "## Heading Two\n"
theHtml.tokenizeText() html.tokenizeText()
theHtml.doConvert() html.doConvert()
assert theHtml.theResult == "<h2><a name='T000001'></a>Heading Two</h2>\n" assert html.theResult == "<h2><a name='T0001'></a>Heading Two</h2>\n"
# Header 3 # Header 3
theHtml._text = "### Heading Three\n" html._text = "### Heading Three\n"
theHtml.tokenizeText() html.tokenizeText()
theHtml.doConvert() html.doConvert()
assert theHtml.theResult == "<h3><a name='T000001'></a>Heading Three</h3>\n" assert html.theResult == "<h3><a name='T0001'></a>Heading Three</h3>\n"
# Header 4 # Header 4
theHtml._text = "#### Heading Four\n" html._text = "#### Heading Four\n"
theHtml.tokenizeText() html.tokenizeText()
theHtml.doConvert() html.doConvert()
assert theHtml.theResult == "<h4><a name='T000001'></a>Heading Four</h4>\n" assert html.theResult == "<h4><a name='T0001'></a>Heading Four</h4>\n"
# Title # Title
theHtml._text = "#! Heading One\n" html._text = "#! Heading One\n"
theHtml.tokenizeText() html.tokenizeText()
theHtml.doConvert() html.doConvert()
assert theHtml.theResult == ( assert html.theResult == (
"<h1 style='text-align: center;'><a name='T000001'></a>Heading One</h1>\n" "<h1 style='text-align: center;'><a name='T0001'></a>Heading One</h1>\n"
) )
# Unnumbered # Unnumbered
theHtml._text = "##! Heading Two\n" html._text = "##! Heading Two\n"
theHtml.tokenizeText() html.tokenizeText()
theHtml.doConvert() html.doConvert()
assert theHtml.theResult == "<h2><a name='T000001'></a>Heading Two</h2>\n" assert html.theResult == "<h2><a name='T0001'></a>Heading Two</h2>\n"
# Paragraphs # Paragraphs
# ========== # ==========
# Text # Text
theHtml._text = "Some **nested bold and _italic_ and ~~strikethrough~~ text** here\n" html._text = "Some **nested bold and _italic_ and ~~strikethrough~~ text** here\n"
theHtml.tokenizeText() html.tokenizeText()
theHtml.doConvert() html.doConvert()
assert theHtml.theResult == ( assert html.theResult == (
"<p>Some <strong>nested bold and <em>italic</em> and " "<p>Some <strong>nested bold and <em>italic</em> and "
"<del>strikethrough</del> text</strong> here</p>\n" "<del>strikethrough</del> text</strong> here</p>\n"
) )
# Text w/Hard Break # Text w/Hard Break
theHtml._text = "Line one \nLine two \nLine three\n" html._text = "Line one \nLine two \nLine three\n"
theHtml.tokenizeText() html.tokenizeText()
theHtml.doConvert() html.doConvert()
assert theHtml.theResult == ( assert html.theResult == (
"<p class='break'>Line one<br/>Line two<br/>Line three</p>\n" "<p class='break'>Line one<br/>Line two<br/>Line three</p>\n"
) )
# Synopsis # Synopsis
theHtml._text = "%synopsis: The synopsis ...\n" html._text = "%synopsis: The synopsis ...\n"
theHtml.tokenizeText() html.tokenizeText()
theHtml.doConvert() html.doConvert()
assert theHtml.theResult == "" assert html.theResult == ""
theHtml.setSynopsis(True) html.setSynopsis(True)
theHtml._text = "%synopsis: The synopsis ...\n" html._text = "%synopsis: The synopsis ...\n"
theHtml.tokenizeText() html.tokenizeText()
theHtml.doConvert() html.doConvert()
assert theHtml.theResult == ( assert html.theResult == (
"<p class='synopsis'><strong>Synopsis:</strong> The synopsis ...</p>\n" "<p class='synopsis'><strong>Synopsis:</strong> The synopsis ...</p>\n"
) )
# Comment # Comment
theHtml._text = "% A comment ...\n" html._text = "% A comment ...\n"
theHtml.tokenizeText() html.tokenizeText()
theHtml.doConvert() html.doConvert()
assert theHtml.theResult == "" assert html.theResult == ""
theHtml.setComments(True) html.setComments(True)
theHtml._text = "% A comment ...\n" html._text = "% A comment ...\n"
theHtml.tokenizeText() html.tokenizeText()
theHtml.doConvert() html.doConvert()
assert theHtml.theResult == ( assert html.theResult == (
"<p class='comment'><strong>Comment:</strong> A comment ...</p>\n" "<p class='comment'><strong>Comment:</strong> A comment ...</p>\n"
) )
# Keywords # Keywords
theHtml._text = "@char: Bod, Jane\n" html._text = "@char: Bod, Jane\n"
theHtml.tokenizeText() html.tokenizeText()
theHtml.doConvert() html.doConvert()
assert theHtml.theResult == "" assert html.theResult == ""
theHtml.setKeywords(True) html.setKeywords(True)
theHtml._text = "@char: Bod, Jane\n" html._text = "@char: Bod, Jane\n"
theHtml.tokenizeText() html.tokenizeText()
theHtml.doConvert() html.doConvert()
assert theHtml.theResult == ( assert html.theResult == (
"<p><span class='tags'>Characters:</span> " "<p><span class='tags'>Characters:</span> "
"<a href='#tag_Bod'>Bod</a>, <a href='#tag_Jane'>Jane</a></p>\n" "<a href='#tag_Bod'>Bod</a>, <a href='#tag_Jane'>Jane</a></p>\n"
) )
# Multiple Keywords # Multiple Keywords
theHtml.setKeywords(True) html.setKeywords(True)
theHtml._text = "## Chapter\n\n@pov: Bod\n@plot: Main\n@location: Europe\n\n" html._text = "## Chapter\n\n@pov: Bod\n@plot: Main\n@location: Europe\n\n"
theHtml.tokenizeText() html.tokenizeText()
theHtml.doConvert() html.doConvert()
assert theHtml.theResult == ( assert html.theResult == (
"<h2>" "<h2>"
"<a name='T000001'></a>Chapter</h2>\n" "<a name='T0001'></a>Chapter</h2>\n"
"<p style='margin-bottom: 0;'>" "<p style='margin-bottom: 0;'>"
"<span class='tags'>Point of View:</span> <a href='#tag_Bod'>Bod</a>" "<span class='tags'>Point of View:</span> <a href='#tag_Bod'>Bod</a>"
"</p>\n" "</p>\n"
@@ -214,13 +213,13 @@ def testCoreToHtml_ConvertFormat(mockGUI):
# Preview Mode # Preview Mode
# ============ # ============
theHtml.setPreview(True, True) html.setPreview(True, True)
# Text (HTML4) # Text (HTML4)
theHtml._text = "Some **nested bold and _italic_ and ~~strikethrough~~ text** here\n" html._text = "Some **nested bold and _italic_ and ~~strikethrough~~ text** here\n"
theHtml.tokenizeText() html.tokenizeText()
theHtml.doConvert() html.doConvert()
assert theHtml.theResult == ( assert html.theResult == (
"<p>Some <b>nested bold and <i>italic</i> and " "<p>Some <b>nested bold and <i>italic</i> and "
"<span style='text-decoration: line-through;'>strikethrough</span> " "<span style='text-decoration: line-through;'>strikethrough</span> "
"text</b> here</p>\n" "text</b> here</p>\n"
@@ -231,109 +230,108 @@ def testCoreToHtml_ConvertFormat(mockGUI):
@pytest.mark.core @pytest.mark.core
def testCoreToHtml_ConvertDirect(mockGUI): def testCoreToHtml_ConvertDirect(mockGUI):
"""Test the converter directly using the ToHtml class. """Test the converter directly using the ToHtml class."""
""" project = NWProject()
theProject = NWProject() html = ToHtml(project)
theHtml = ToHtml(theProject)
theHtml._isNovel = True html._isNovel = True
theHtml._isNote = False html._isNote = False
theHtml.setLinkHeaders(True) html.setLinkHeaders(True)
# Special Titles # Special Titles
# ============== # ==============
# Title # Title
theHtml._tokens = [ html._tokens = [
(theHtml.T_TITLE, 1, "A Title", None, theHtml.A_PBB | theHtml.A_CENTRE), (html.T_TITLE, 1, "A Title", None, html.A_PBB | html.A_CENTRE),
(theHtml.T_EMPTY, 1, "", None, theHtml.A_NONE), (html.T_EMPTY, 1, "", None, html.A_NONE),
] ]
theHtml.doConvert() html.doConvert()
assert theHtml.theResult == ( assert html.theResult == (
"<h1 class='title' style='text-align: center; page-break-before: always;'>" "<h1 class='title' style='text-align: center; page-break-before: always;'>"
"<a name='T000001'></a>A Title</h1>\n" "<a name='T0001'></a>A Title</h1>\n"
) )
# Unnumbered # Unnumbered
theHtml._tokens = [ html._tokens = [
(theHtml.T_UNNUM, 1, "Prologue", None, theHtml.A_PBB), (html.T_UNNUM, 1, "Prologue", None, html.A_PBB),
(theHtml.T_EMPTY, 1, "", None, theHtml.A_NONE), (html.T_EMPTY, 1, "", None, html.A_NONE),
] ]
theHtml.doConvert() html.doConvert()
assert theHtml.theResult == ( assert html.theResult == (
"<h1 style='page-break-before: always;'>" "<h1 style='page-break-before: always;'>"
"<a name='T000001'></a>Prologue</h1>\n" "<a name='T0001'></a>Prologue</h1>\n"
) )
# Separators # Separators
# ========== # ==========
# Separator # Separator
theHtml._tokens = [ html._tokens = [
(theHtml.T_SEP, 1, "* * *", None, theHtml.A_CENTRE), (html.T_SEP, 1, "* * *", None, html.A_CENTRE),
(theHtml.T_EMPTY, 1, "", None, theHtml.A_NONE), (html.T_EMPTY, 1, "", None, html.A_NONE),
] ]
theHtml.doConvert() html.doConvert()
assert theHtml.theResult == "<p class='sep' style='text-align: center;'>* * *</p>\n" assert html.theResult == "<p class='sep' style='text-align: center;'>* * *</p>\n"
# Skip # Skip
theHtml._tokens = [ html._tokens = [
(theHtml.T_SKIP, 1, "", None, theHtml.A_NONE), (html.T_SKIP, 1, "", None, html.A_NONE),
(theHtml.T_EMPTY, 1, "", None, theHtml.A_NONE), (html.T_EMPTY, 1, "", None, html.A_NONE),
] ]
theHtml.doConvert() html.doConvert()
assert theHtml.theResult == "<p class='skip'>&nbsp;</p>\n" assert html.theResult == "<p class='skip'>&nbsp;</p>\n"
# Alignment # Alignment
# ========= # =========
theHtml.setLinkHeaders(False) html.setLinkHeaders(False)
# Align Left # Align Left
theHtml.setStyles(False) html.setStyles(False)
theHtml._tokens = [ html._tokens = [
(theHtml.T_HEAD1, 1, "A Title", None, theHtml.A_LEFT), (html.T_HEAD1, 1, "A Title", None, html.A_LEFT),
] ]
theHtml.doConvert() html.doConvert()
assert theHtml.theResult == ( assert html.theResult == (
"<h1 class='title'>A Title</h1>\n" "<h1 class='title'>A Title</h1>\n"
) )
theHtml.setStyles(True) html.setStyles(True)
# Align Left # Align Left
theHtml._tokens = [ html._tokens = [
(theHtml.T_HEAD1, 1, "A Title", None, theHtml.A_LEFT), (html.T_HEAD1, 1, "A Title", None, html.A_LEFT),
] ]
theHtml.doConvert() html.doConvert()
assert theHtml.theResult == ( assert html.theResult == (
"<h1 class='title' style='text-align: left;'>A Title</h1>\n" "<h1 class='title' style='text-align: left;'>A Title</h1>\n"
) )
# Align Right # Align Right
theHtml._tokens = [ html._tokens = [
(theHtml.T_HEAD1, 1, "A Title", None, theHtml.A_RIGHT), (html.T_HEAD1, 1, "A Title", None, html.A_RIGHT),
] ]
theHtml.doConvert() html.doConvert()
assert theHtml.theResult == ( assert html.theResult == (
"<h1 class='title' style='text-align: right;'>A Title</h1>\n" "<h1 class='title' style='text-align: right;'>A Title</h1>\n"
) )
# Align Centre # Align Centre
theHtml._tokens = [ html._tokens = [
(theHtml.T_HEAD1, 1, "A Title", None, theHtml.A_CENTRE), (html.T_HEAD1, 1, "A Title", None, html.A_CENTRE),
] ]
theHtml.doConvert() html.doConvert()
assert theHtml.theResult == ( assert html.theResult == (
"<h1 class='title' style='text-align: center;'>A Title</h1>\n" "<h1 class='title' style='text-align: center;'>A Title</h1>\n"
) )
# Align Justify # Align Justify
theHtml._tokens = [ html._tokens = [
(theHtml.T_HEAD1, 1, "A Title", None, theHtml.A_JUSTIFY), (html.T_HEAD1, 1, "A Title", None, html.A_JUSTIFY),
] ]
theHtml.doConvert() html.doConvert()
assert theHtml.theResult == ( assert html.theResult == (
"<h1 class='title' style='text-align: justify;'>A Title</h1>\n" "<h1 class='title' style='text-align: justify;'>A Title</h1>\n"
) )
@@ -341,11 +339,11 @@ def testCoreToHtml_ConvertDirect(mockGUI):
# ========== # ==========
# Page Break Always # Page Break Always
theHtml._tokens = [ html._tokens = [
(theHtml.T_HEAD1, 1, "A Title", None, theHtml.A_PBB | theHtml.A_PBA), (html.T_HEAD1, 1, "A Title", None, html.A_PBB | html.A_PBA),
] ]
theHtml.doConvert() html.doConvert()
assert theHtml.theResult == ( assert html.theResult == (
"<h1 class='title' " "<h1 class='title' "
"style='page-break-before: always; page-break-after: always;'>A Title</h1>\n" "style='page-break-before: always; page-break-after: always;'>A Title</h1>\n"
) )
@@ -354,22 +352,22 @@ def testCoreToHtml_ConvertDirect(mockGUI):
# ====== # ======
# Indent Left # Indent Left
theHtml._tokens = [ html._tokens = [
(theHtml.T_TEXT, 1, "Some text ...", [], theHtml.A_IND_L), (html.T_TEXT, 1, "Some text ...", [], html.A_IND_L),
(theHtml.T_EMPTY, 2, "", None, theHtml.A_NONE), (html.T_EMPTY, 2, "", None, html.A_NONE),
] ]
theHtml.doConvert() html.doConvert()
assert theHtml.theResult == ( assert html.theResult == (
"<p style='margin-left: 40px;'>Some text ...</p>\n" "<p style='margin-left: 40px;'>Some text ...</p>\n"
) )
# Indent Right # Indent Right
theHtml._tokens = [ html._tokens = [
(theHtml.T_TEXT, 1, "Some text ...", [], theHtml.A_IND_R), (html.T_TEXT, 1, "Some text ...", [], html.A_IND_R),
(theHtml.T_EMPTY, 2, "", None, theHtml.A_NONE), (html.T_EMPTY, 2, "", None, html.A_NONE),
] ]
theHtml.doConvert() html.doConvert()
assert theHtml.theResult == ( assert html.theResult == (
"<p style='margin-right: 40px;'>Some text ...</p>\n" "<p style='margin-right: 40px;'>Some text ...</p>\n"
) )
@@ -378,40 +376,39 @@ def testCoreToHtml_ConvertDirect(mockGUI):
@pytest.mark.core @pytest.mark.core
def testCoreToHtml_SpecialCases(mockGUI): def testCoreToHtml_SpecialCases(mockGUI):
"""Test some special cases that have caused errors in the past. """Test some special cases that have caused errors in the past."""
""" project = NWProject()
theProject = NWProject() html = ToHtml(project)
theHtml = ToHtml(theProject) html._isNovel = True
theHtml._isNovel = True
# Greater/Lesser than symbols # Greater/Lesser than symbols
# =========================== # ===========================
theHtml._text = "Text with > and < with some **bold text** in it.\n" html._text = "Text with > and < with some **bold text** in it.\n"
theHtml.tokenizeText() html.tokenizeText()
theHtml.doConvert() html.doConvert()
assert theHtml.theResult == ( assert html.theResult == (
"<p>Text with &gt; and &lt; with some <strong>bold text</strong> in it.</p>\n" "<p>Text with &gt; and &lt; with some <strong>bold text</strong> in it.</p>\n"
) )
theHtml._text = "Text with some <**bold text**> in it.\n" html._text = "Text with some <**bold text**> in it.\n"
theHtml.tokenizeText() html.tokenizeText()
theHtml.doConvert() html.doConvert()
assert theHtml.theResult == ( assert html.theResult == (
"<p>Text with some &lt;<strong>bold text</strong>&gt; in it.</p>\n" "<p>Text with some &lt;<strong>bold text</strong>&gt; in it.</p>\n"
) )
theHtml._text = "Let's > be > _difficult **shall** > we_?\n" html._text = "Let's > be > _difficult **shall** > we_?\n"
theHtml.tokenizeText() html.tokenizeText()
theHtml.doConvert() html.doConvert()
assert theHtml.theResult == ( assert html.theResult == (
"<p>Let's &gt; be &gt; <em>difficult <strong>shall</strong> &gt; we</em>?</p>\n" "<p>Let's &gt; be &gt; <em>difficult <strong>shall</strong> &gt; we</em>?</p>\n"
) )
theHtml._text = "Test > text _<**bold**>_ and more.\n" html._text = "Test > text _<**bold**>_ and more.\n"
theHtml.tokenizeText() html.tokenizeText()
theHtml.doConvert() html.doConvert()
assert theHtml.theResult == ( assert html.theResult == (
"<p>Test &gt; text <em>&lt;<strong>bold</strong>&gt;</em> and more.</p>\n" "<p>Test &gt; text <em>&lt;<strong>bold</strong>&gt;</em> and more.</p>\n"
) )
@@ -419,20 +416,20 @@ def testCoreToHtml_SpecialCases(mockGUI):
# =================== # ===================
# See: https://github.com/vkbo/novelWriter/issues/950 # See: https://github.com/vkbo/novelWriter/issues/950
theHtml.setComments(True) html.setComments(True)
theHtml._text = "% Test > text _<**bold**>_ and more.\n" html._text = "% Test > text _<**bold**>_ and more.\n"
theHtml.tokenizeText() html.tokenizeText()
theHtml.doConvert() html.doConvert()
assert theHtml.theResult == ( assert html.theResult == (
"<p class='comment'>" "<p class='comment'>"
"<strong>Comment:</strong> Test &gt; text _&lt;**bold**&gt;_ and more." "<strong>Comment:</strong> Test &gt; text _&lt;**bold**&gt;_ and more."
"</p>\n" "</p>\n"
) )
theHtml._text = "## Heading <1>\n" html._text = "## Heading <1>\n"
theHtml.tokenizeText() html.tokenizeText()
theHtml.doConvert() html.doConvert()
assert theHtml.theResult == ( assert html.theResult == (
"<h1 style='page-break-before: always;'>Heading &lt;1&gt;</h1>\n" "<h1 style='page-break-before: always;'>Heading &lt;1&gt;</h1>\n"
) )
@@ -440,10 +437,10 @@ def testCoreToHtml_SpecialCases(mockGUI):
# ==================== # ====================
# See: https://github.com/vkbo/novelWriter/issues/1412 # See: https://github.com/vkbo/novelWriter/issues/1412
theHtml._text = "Test text \\**_bold_** and more.\n" html._text = "Test text \\**_bold_** and more.\n"
theHtml.tokenizeText() html.tokenizeText()
theHtml.doConvert() html.doConvert()
assert theHtml.theResult == ( assert html.theResult == (
"<p>Test text **<em>bold</em>** and more.</p>\n" "<p>Test text **<em>bold</em>** and more.</p>\n"
) )
@@ -452,11 +449,10 @@ def testCoreToHtml_SpecialCases(mockGUI):
@pytest.mark.core @pytest.mark.core
def testCoreToHtml_Complex(mockGUI, fncPath): def testCoreToHtml_Complex(mockGUI, fncPath):
"""Test the save method of the ToHtml class. """Test the save method of the ToHtml class."""
""" project = NWProject()
theProject = NWProject() html = ToHtml(project)
theHtml = ToHtml(theProject) html._isNovel = True
theHtml._isNovel = True
# Build Project # Build Project
# ============= # =============
@@ -502,21 +498,21 @@ def testCoreToHtml_Complex(mockGUI, fncPath):
] ]
for i in range(len(docText)): for i in range(len(docText)):
theHtml._text = docText[i] html._text = docText[i]
theHtml.doPreProcessing() html.doPreProcessing()
theHtml.tokenizeText() html.tokenizeText()
theHtml.doConvert() html.doConvert()
assert theHtml.theResult == resText[i] assert html.theResult == resText[i]
assert theHtml.fullHTML == resText assert html.fullHTML == resText
theHtml.replaceTabs(nSpaces=2, spaceChar="&nbsp;") html.replaceTabs(nSpaces=2, spaceChar="&nbsp;")
resText[6] = "<h3>A Section</h3>\n<p>&nbsp;&nbsp;More text in scene two.</p>\n" resText[6] = "<h3>A Section</h3>\n<p>&nbsp;&nbsp;More text in scene two.</p>\n"
# Check File # Check File
# ========== # ==========
theStyle = theHtml.getStyleSheet() theStyle = html.getStyleSheet()
htmlDoc = ( htmlDoc = (
"<!DOCTYPE html>\n" "<!DOCTYPE html>\n"
"<html>\n" "<html>\n"
@@ -539,7 +535,7 @@ def testCoreToHtml_Complex(mockGUI, fncPath):
) )
saveFile = fncPath / "outFile.htm" saveFile = fncPath / "outFile.htm"
theHtml.saveHtml5(saveFile) html.saveHtml5(saveFile)
assert readFile(saveFile) == htmlDoc assert readFile(saveFile) == htmlDoc
# END Test testCoreToHtml_Complex # END Test testCoreToHtml_Complex
@@ -547,86 +543,84 @@ def testCoreToHtml_Complex(mockGUI, fncPath):
@pytest.mark.core @pytest.mark.core
def testCoreToHtml_Methods(mockGUI): def testCoreToHtml_Methods(mockGUI):
"""Test all the other methods of the ToHtml class. """Test all the other methods of the ToHtml class."""
""" project = NWProject()
theProject = NWProject() html = ToHtml(project)
theHtml = ToHtml(theProject) html.setKeepMarkdown(True)
theHtml.setKeepMarkdown(True)
# Auto-Replace, keep Unicode # Auto-Replace, keep Unicode
docText = "Text with <brackets> & shortdash, long—dash …\n" docText = "Text with <brackets> & shortdash, long—dash …\n"
theHtml._text = docText html._text = docText
theHtml.setReplaceUnicode(False) html.setReplaceUnicode(False)
theHtml.doPreProcessing() html.doPreProcessing()
theHtml.tokenizeText() html.tokenizeText()
theHtml.doConvert() html.doConvert()
assert theHtml.theResult == ( assert html.theResult == (
"<p>Text with &lt;brackets&gt; &amp; shortdash, long—dash …</p>\n" "<p>Text with &lt;brackets&gt; &amp; shortdash, long—dash …</p>\n"
) )
# Auto-Replace, replace Unicode # Auto-Replace, replace Unicode
docText = "Text with <brackets> & shortdash, long—dash …\n" docText = "Text with <brackets> & shortdash, long—dash …\n"
theHtml._text = docText html._text = docText
theHtml.setReplaceUnicode(True) html.setReplaceUnicode(True)
theHtml.doPreProcessing() html.doPreProcessing()
theHtml.tokenizeText() html.tokenizeText()
theHtml.doConvert() html.doConvert()
assert theHtml.theResult == ( assert html.theResult == (
"<p>Text with &lt;brackets&gt; &amp; short&ndash;dash, long&mdash;dash &hellip;</p>\n" "<p>Text with &lt;brackets&gt; &amp; short&ndash;dash, long&mdash;dash &hellip;</p>\n"
) )
# With Preview # With Preview
theHtml.setPreview(True, True) html.setPreview(True, True)
theHtml._text = docText html._text = docText
theHtml.doPreProcessing() html.doPreProcessing()
theHtml.tokenizeText() html.tokenizeText()
theHtml.doConvert() html.doConvert()
assert theHtml.theMarkdown[-1] == ( assert html.theMarkdown[-1] == (
"Text with <brackets> &amp; short&ndash;dash, long&mdash;dash &hellip;\n\n" "Text with <brackets> &amp; short&ndash;dash, long&mdash;dash &hellip;\n\n"
) )
# Result Size # Result Size
assert theHtml.getFullResultSize() == 147 assert html.getFullResultSize() == 147
# CSS # CSS
# === # ===
assert len(theHtml.getStyleSheet()) > 1 assert len(html.getStyleSheet()) > 1
assert "p {text-align: left;" in " ".join(theHtml.getStyleSheet()) assert "p {text-align: left;" in " ".join(html.getStyleSheet())
assert "p {text-align: justify;" not in " ".join(theHtml.getStyleSheet()) assert "p {text-align: justify;" not in " ".join(html.getStyleSheet())
theHtml.setJustify(True) html.setJustify(True)
assert "p {text-align: left;" not in " ".join(theHtml.getStyleSheet()) assert "p {text-align: left;" not in " ".join(html.getStyleSheet())
assert "p {text-align: justify;" in " ".join(theHtml.getStyleSheet()) assert "p {text-align: justify;" in " ".join(html.getStyleSheet())
theHtml.setStyles(False) html.setStyles(False)
assert theHtml.getStyleSheet() == [] assert html.getStyleSheet() == []
# END Test testCoreToHtml_Methods # END Test testCoreToHtml_Methods
@pytest.mark.core @pytest.mark.core
def testCoreToHtml_Format(mockGUI): def testCoreToHtml_Format(mockGUI):
"""Test all the formatters for the ToHtml class. """Test all the formatters for the ToHtml class."""
""" project = NWProject()
theProject = NWProject() html = ToHtml(project)
theHtml = ToHtml(theProject)
# Export Mode # Export Mode
# =========== # ===========
assert theHtml._formatSynopsis("synopsis text") == ( assert html._formatSynopsis("synopsis text") == (
"<p class='synopsis'><strong>Synopsis:</strong> synopsis text</p>\n" "<p class='synopsis'><strong>Synopsis:</strong> synopsis text</p>\n"
) )
assert theHtml._formatComments("comment text") == ( assert html._formatComments("comment text") == (
"<p class='comment'><strong>Comment:</strong> comment text</p>\n" "<p class='comment'><strong>Comment:</strong> comment text</p>\n"
) )
assert theHtml._formatKeywords("") == "" assert html._formatKeywords("") == ""
assert theHtml._formatKeywords("tag: Jane") == ( assert html._formatKeywords("tag: Jane") == (
"<span class='tags'>Tag:</span> <a name='tag_Jane'>Jane</a>" "<span class='tags'>Tag:</span> <a name='tag_Jane'>Jane</a>"
) )
assert theHtml._formatKeywords("char: Bod, Jane") == ( assert html._formatKeywords("char: Bod, Jane") == (
"<span class='tags'>Characters:</span> " "<span class='tags'>Characters:</span> "
"<a href='#tag_Bod'>Bod</a>, " "<a href='#tag_Bod'>Bod</a>, "
"<a href='#tag_Jane'>Jane</a>" "<a href='#tag_Jane'>Jane</a>"
@@ -635,20 +629,20 @@ def testCoreToHtml_Format(mockGUI):
# Preview Mode # Preview Mode
# ============ # ============
theHtml.setPreview(True, True) html.setPreview(True, True)
assert theHtml._formatSynopsis("synopsis text") == ( assert html._formatSynopsis("synopsis text") == (
"<p class='comment'><span class='synopsis'>Synopsis:</span> synopsis text</p>\n" "<p class='comment'><span class='synopsis'>Synopsis:</span> synopsis text</p>\n"
) )
assert theHtml._formatComments("comment text") == ( assert html._formatComments("comment text") == (
"<p class='comment'>comment text</p>\n" "<p class='comment'>comment text</p>\n"
) )
assert theHtml._formatKeywords("") == "" assert html._formatKeywords("") == ""
assert theHtml._formatKeywords("tag: Jane") == ( assert html._formatKeywords("tag: Jane") == (
"<span class='tags'>Tag:</span> <a name='tag_Jane'>Jane</a>" "<span class='tags'>Tag:</span> <a name='tag_Jane'>Jane</a>"
) )
assert theHtml._formatKeywords("char: Bod, Jane") == ( assert html._formatKeywords("char: Bod, Jane") == (
"<span class='tags'>Characters:</span> " "<span class='tags'>Characters:</span> "
"<a href='#char=Bod'>Bod</a>, " "<a href='#char=Bod'>Bod</a>, "
"<a href='#char=Jane'>Jane</a>" "<a href='#char=Jane'>Jane</a>"
+102 -102
View File
@@ -442,8 +442,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, 1, "A comment", None, Tokenizer.A_NONE), (Tokenizer.T_COMMENT, 0, "A comment", None, Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE),
] ]
assert tokens.theMarkdown[-1] == "\n" assert tokens.theMarkdown[-1] == "\n"
@@ -455,14 +455,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, 1, "The synopsis", None, Tokenizer.A_NONE), (Tokenizer.T_SYNOPSIS, 0, "The synopsis", None, Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 0, "", None, 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, 1, "The synopsis", None, Tokenizer.A_NONE), (Tokenizer.T_SYNOPSIS, 0, "The synopsis", None, Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE),
] ]
assert tokens.theMarkdown[-1] == "\n" assert tokens.theMarkdown[-1] == "\n"
@@ -474,8 +474,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, 1, "char: Bod", None, Tokenizer.A_NONE), (Tokenizer.T_KEYWORD, 0, "char: Bod", None, Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE),
] ]
assert tokens.theMarkdown[-1] == "\n" assert tokens.theMarkdown[-1] == "\n"
@@ -489,10 +489,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, 1, "pov: Bod", None, styTop), (Tokenizer.T_KEYWORD, 0, "pov: Bod", None, styTop),
(Tokenizer.T_KEYWORD, 2, "plot: Main", None, styMid), (Tokenizer.T_KEYWORD, 0, "plot: Main", None, styMid),
(Tokenizer.T_KEYWORD, 3, "location: Europe", None, styBtm), (Tokenizer.T_KEYWORD, 0, "location: Europe", None, styBtm),
(Tokenizer.T_EMPTY, 3, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE),
] ]
assert tokens.theMarkdown[-1] == "@pov: Bod\n@plot: Main\n@location: Europe\n\n" assert tokens.theMarkdown[-1] == "@pov: Bod\n@plot: Main\n@location: Europe\n\n"
@@ -521,23 +521,23 @@ def testCoreToken_MarginFormat(mockGUI):
) )
tokens.tokenizeText() tokens.tokenizeText()
assert tokens._tokens == [ assert tokens._tokens == [
(Tokenizer.T_TEXT, 1, "Some regular text", [], Tokenizer.A_NONE), (Tokenizer.T_TEXT, 0, "Some regular text", [], Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 2, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE),
(Tokenizer.T_TEXT, 3, "Some left-aligned text", [], Tokenizer.A_LEFT), (Tokenizer.T_TEXT, 0, "Some left-aligned text", [], Tokenizer.A_LEFT),
(Tokenizer.T_EMPTY, 4, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE),
(Tokenizer.T_TEXT, 5, "Some right-aligned text", [], Tokenizer.A_RIGHT), (Tokenizer.T_TEXT, 0, "Some right-aligned text", [], Tokenizer.A_RIGHT),
(Tokenizer.T_EMPTY, 6, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE),
(Tokenizer.T_TEXT, 7, "Some centered text", [], Tokenizer.A_CENTRE), (Tokenizer.T_TEXT, 0, "Some centered text", [], Tokenizer.A_CENTRE),
(Tokenizer.T_EMPTY, 8, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE),
(Tokenizer.T_TEXT, 9, "Left-indented block", [], Tokenizer.A_IND_L), (Tokenizer.T_TEXT, 0, "Left-indented block", [], Tokenizer.A_IND_L),
(Tokenizer.T_EMPTY, 10, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE),
(Tokenizer.T_TEXT, 11, "Right-indented block", [], Tokenizer.A_IND_R), (Tokenizer.T_TEXT, 0, "Right-indented block", [], Tokenizer.A_IND_R),
(Tokenizer.T_EMPTY, 12, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE),
(Tokenizer.T_TEXT, 13, "Double-indented block", [], dblIndent), (Tokenizer.T_TEXT, 0, "Double-indented block", [], dblIndent),
(Tokenizer.T_EMPTY, 14, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE),
(Tokenizer.T_TEXT, 15, "Right-indent, right-aligned", [], rIndAlign), (Tokenizer.T_TEXT, 0, "Right-indent, right-aligned", [], rIndAlign),
(Tokenizer.T_EMPTY, 16, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 16, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE),
] ]
assert tokens.theMarkdown[-1] == ( assert tokens.theMarkdown[-1] == (
"Some regular text\n\n" "Some regular text\n\n"
@@ -655,20 +655,20 @@ def testCoreToken_TextFormat(mockGUI):
tokens._text = "Some plain text\non two lines\n\n\n" tokens._text = "Some plain text\non two lines\n\n\n"
tokens.tokenizeText() tokens.tokenizeText()
assert tokens._tokens == [ assert tokens._tokens == [
(Tokenizer.T_TEXT, 1, "Some plain text", [], Tokenizer.A_NONE), (Tokenizer.T_TEXT, 0, "Some plain text", [], Tokenizer.A_NONE),
(Tokenizer.T_TEXT, 2, "on two lines", [], Tokenizer.A_NONE), (Tokenizer.T_TEXT, 0, "on two lines", [], Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 3, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 4, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 4, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE),
] ]
assert tokens.theMarkdown[-1] == "Some plain text\non two lines\n\n\n\n" assert tokens.theMarkdown[-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, 3, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 4, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 4, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE),
] ]
assert tokens.theMarkdown[-1] == "\n\n\n" assert tokens.theMarkdown[-1] == "\n\n\n"
tokens.setBodyText(True) tokens.setBodyText(True)
@@ -678,7 +678,7 @@ def testCoreToken_TextFormat(mockGUI):
tokens.tokenizeText() tokens.tokenizeText()
assert tokens._tokens == [ assert tokens._tokens == [
( (
Tokenizer.T_TEXT, 1, Tokenizer.T_TEXT, 0,
"Some bolded text on this lines", "Some bolded text on this lines",
[ [
(5, Tokenizer.FMT_B_B), (5, Tokenizer.FMT_B_B),
@@ -686,7 +686,7 @@ def testCoreToken_TextFormat(mockGUI):
], ],
Tokenizer.A_NONE Tokenizer.A_NONE
), ),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE),
] ]
assert tokens.theMarkdown[-1] == "Some **bolded text** on this lines\n\n" assert tokens.theMarkdown[-1] == "Some **bolded text** on this lines\n\n"
@@ -694,7 +694,7 @@ def testCoreToken_TextFormat(mockGUI):
tokens.tokenizeText() tokens.tokenizeText()
assert tokens._tokens == [ assert tokens._tokens == [
( (
Tokenizer.T_TEXT, 1, Tokenizer.T_TEXT, 0,
"Some italic text on this lines", "Some italic text on this lines",
[ [
(5, Tokenizer.FMT_I_B), (5, Tokenizer.FMT_I_B),
@@ -702,7 +702,7 @@ def testCoreToken_TextFormat(mockGUI):
], ],
Tokenizer.A_NONE Tokenizer.A_NONE
), ),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE),
] ]
assert tokens.theMarkdown[-1] == "Some _italic text_ on this lines\n\n" assert tokens.theMarkdown[-1] == "Some _italic text_ on this lines\n\n"
@@ -710,7 +710,7 @@ def testCoreToken_TextFormat(mockGUI):
tokens.tokenizeText() tokens.tokenizeText()
assert tokens._tokens == [ assert tokens._tokens == [
( (
Tokenizer.T_TEXT, 1, Tokenizer.T_TEXT, 0,
"Some bold italic text on this lines", "Some bold italic text on this lines",
[ [
(5, Tokenizer.FMT_B_B), (5, Tokenizer.FMT_B_B),
@@ -720,7 +720,7 @@ def testCoreToken_TextFormat(mockGUI):
], ],
Tokenizer.A_NONE Tokenizer.A_NONE
), ),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE),
] ]
assert tokens.theMarkdown[-1] == "Some **_bold italic text_** on this lines\n\n" assert tokens.theMarkdown[-1] == "Some **_bold italic text_** on this lines\n\n"
@@ -728,7 +728,7 @@ def testCoreToken_TextFormat(mockGUI):
tokens.tokenizeText() tokens.tokenizeText()
assert tokens._tokens == [ assert tokens._tokens == [
( (
Tokenizer.T_TEXT, 1, Tokenizer.T_TEXT, 0,
"Some strikethrough text on this lines", "Some strikethrough text on this lines",
[ [
(5, Tokenizer.FMT_D_B), (5, Tokenizer.FMT_D_B),
@@ -736,7 +736,7 @@ def testCoreToken_TextFormat(mockGUI):
], ],
Tokenizer.A_NONE Tokenizer.A_NONE
), ),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE),
] ]
assert tokens.theMarkdown[-1] == "Some ~~strikethrough text~~ on this lines\n\n" assert tokens.theMarkdown[-1] == "Some ~~strikethrough text~~ on this lines\n\n"
@@ -744,7 +744,7 @@ def testCoreToken_TextFormat(mockGUI):
tokens.tokenizeText() tokens.tokenizeText()
assert tokens._tokens == [ assert tokens._tokens == [
( (
Tokenizer.T_TEXT, 1, Tokenizer.T_TEXT, 0,
"Some nested bold and italic and strikethrough text here", "Some nested bold and italic and strikethrough text here",
[ [
(5, Tokenizer.FMT_B_B), (5, Tokenizer.FMT_B_B),
@@ -756,7 +756,7 @@ def testCoreToken_TextFormat(mockGUI):
], ],
Tokenizer.A_NONE Tokenizer.A_NONE
), ),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE),
] ]
assert tokens.theMarkdown[-1] == ( assert tokens.theMarkdown[-1] == (
"Some **nested bold and _italic_ and ~~strikethrough~~ text** here\n\n" "Some **nested bold and _italic_ and ~~strikethrough~~ text** here\n\n"
@@ -778,11 +778,11 @@ def testCoreToken_SpecialFormat(mockGUI):
correctResp = [ correctResp = [
(Tokenizer.T_HEAD1, 1, "Title One", None, Tokenizer.A_CENTRE), (Tokenizer.T_HEAD1, 1, "Title One", None, Tokenizer.A_CENTRE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
(Tokenizer.T_HEAD1, 2, "Title Two", None, Tokenizer.A_CENTRE | Tokenizer.A_PBB),
(Tokenizer.T_EMPTY, 2, "", None, Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 2, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 2, "", None, Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 4, "", None, Tokenizer.A_NONE),
(Tokenizer.T_HEAD1, 5, "Title Two", None, Tokenizer.A_CENTRE | Tokenizer.A_PBB),
(Tokenizer.T_EMPTY, 6, "", None, Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 6, "", None, Tokenizer.A_NONE),
] ]
# Command wo/Space # Command wo/Space
@@ -826,12 +826,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", None, Tokenizer.A_PBB | Tokenizer.A_CENTRE),
(Tokenizer.T_EMPTY, 2, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
(Tokenizer.T_SKIP, 3, "", None, Tokenizer.A_NONE), (Tokenizer.T_SKIP, 1, "", None, Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 4, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
(Tokenizer.T_TEXT, 5, "Some text to go here ...", [], Tokenizer.A_NONE), (Tokenizer.T_TEXT, 1, "Some text to go here ...", [], Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 6, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 6, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
] ]
# Multiple Empty Paragraphs # Multiple Empty Paragraphs
@@ -846,12 +846,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", None, Tokenizer.A_PBB | Tokenizer.A_CENTRE),
(Tokenizer.T_EMPTY, 2, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
(Tokenizer.T_SKIP, 3, "", None, Tokenizer.A_NONE), (Tokenizer.T_SKIP, 1, "", None, Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 4, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
(Tokenizer.T_TEXT, 5, "Some text to go here ...", [], Tokenizer.A_NONE), (Tokenizer.T_TEXT, 1, "Some text to go here ...", [], Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 6, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 6, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
] ]
# Three Skips # Three Skips
@@ -863,14 +863,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", None, Tokenizer.A_PBB | Tokenizer.A_CENTRE),
(Tokenizer.T_EMPTY, 2, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
(Tokenizer.T_SKIP, 3, "", None, Tokenizer.A_NONE), (Tokenizer.T_SKIP, 1, "", None, Tokenizer.A_NONE),
(Tokenizer.T_SKIP, 3, "", None, Tokenizer.A_NONE), (Tokenizer.T_SKIP, 1, "", None, Tokenizer.A_NONE),
(Tokenizer.T_SKIP, 3, "", None, Tokenizer.A_NONE), (Tokenizer.T_SKIP, 1, "", None, Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 4, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
(Tokenizer.T_TEXT, 5, "Some text to go here ...", [], Tokenizer.A_NONE), (Tokenizer.T_TEXT, 1, "Some text to go here ...", [], Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 6, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 6, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
] ]
# Malformed Command, Case 1 # Malformed Command, Case 1
@@ -882,11 +882,11 @@ 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", None, Tokenizer.A_PBB | Tokenizer.A_CENTRE),
(Tokenizer.T_EMPTY, 2, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 4, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
(Tokenizer.T_TEXT, 5, "Some text to go here ...", [], Tokenizer.A_NONE), (Tokenizer.T_TEXT, 1, "Some text to go here ...", [], Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 6, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 6, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
] ]
# Malformed Command, Case 2 # Malformed Command, Case 2
@@ -898,11 +898,11 @@ 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", None, Tokenizer.A_PBB | Tokenizer.A_CENTRE),
(Tokenizer.T_EMPTY, 2, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 4, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
(Tokenizer.T_TEXT, 5, "Some text to go here ...", [], Tokenizer.A_NONE), (Tokenizer.T_TEXT, 1, "Some text to go here ...", [], Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 6, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 6, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
] ]
# Malformed Command, Case 3 # Malformed Command, Case 3
@@ -914,11 +914,11 @@ 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", None, Tokenizer.A_PBB | Tokenizer.A_CENTRE),
(Tokenizer.T_EMPTY, 2, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 4, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
(Tokenizer.T_TEXT, 5, "Some text to go here ...", [], Tokenizer.A_NONE), (Tokenizer.T_TEXT, 1, "Some text to go here ...", [], Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 6, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 6, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
] ]
# Empty Paragraph and Page Break # Empty Paragraph and Page Break
@@ -934,13 +934,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", None, Tokenizer.A_PBB | Tokenizer.A_CENTRE),
(Tokenizer.T_EMPTY, 2, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 4, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
(Tokenizer.T_SKIP, 5, "", None, Tokenizer.A_PBB), (Tokenizer.T_SKIP, 1, "", None, Tokenizer.A_PBB),
(Tokenizer.T_EMPTY, 6, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
(Tokenizer.T_TEXT, 7, "Some text to go here ...", [], 0), (Tokenizer.T_TEXT, 1, "Some text to go here ...", [], 0),
(Tokenizer.T_EMPTY, 8, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 8, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
] ]
# Multiple Skip # Multiple Skip
@@ -953,15 +953,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", None, Tokenizer.A_PBB | Tokenizer.A_CENTRE),
(Tokenizer.T_EMPTY, 2, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 4, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
(Tokenizer.T_SKIP, 5, "", None, Tokenizer.A_PBB), (Tokenizer.T_SKIP, 1, "", None, Tokenizer.A_PBB),
(Tokenizer.T_SKIP, 5, "", None, Tokenizer.A_NONE), (Tokenizer.T_SKIP, 1, "", None, Tokenizer.A_NONE),
(Tokenizer.T_SKIP, 5, "", None, Tokenizer.A_NONE), (Tokenizer.T_SKIP, 1, "", None, Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 6, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
(Tokenizer.T_TEXT, 7, "Some text to go here ...", [], 0), (Tokenizer.T_TEXT, 1, "Some text to go here ...", [], 0),
(Tokenizer.T_EMPTY, 8, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 8, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
] ]
# END Test testCoreToken_SpecialFormat # END Test testCoreToken_SpecialFormat