Documentation and ODT exporter updates (#859)

* Clean up a few things in the ODT exporter
* Remove local HTML docs and instead add PDF docs
* Update documentation
This commit is contained in:
Veronica Berglyd Olsen
2021-08-20 17:16:14 +02:00
committed by GitHub
parent eee0f5e4c9
commit 95c79e1c23
13 changed files with 255 additions and 138 deletions
+2
View File
@@ -18,6 +18,8 @@ i18n/*.qph
# Documentation
/docs/build/
/nw/assets/help/html/
/nw/assets/help/manual.pdf
/UserManual.pdf
*.qch
*.qhc
+20 -41
View File
@@ -4,20 +4,15 @@
# This file does only contain a selection of the most common options. For a
# full list see the documentation:
# http://www.sphinx-doc.org/en/master/config
# -- Path setup --------------------------------------------------------------
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#
# import sys
# sys.path.insert(0, os.path.abspath("."))
# -- Imports -----------------------------------------------------------------
import os
import time
import sphinx_rtd_theme # noqa: F401
import sphinx_rtd_theme # noqa: F401
# -- Project information -----------------------------------------------------
# -- Project Information -----------------------------------------------------
project = "novelWriter"
copyright = "20182021, Veronica Berglyd Olsen"
@@ -28,7 +23,7 @@ version = "1.5"
# The full version, including alpha/beta/rc tags
release = "1.5-alpha0"
# -- General configuration ---------------------------------------------------
# -- General Configuration ---------------------------------------------------
os.environ["TZ"] = "Europe/Oslo"
time.tzset()
@@ -45,7 +40,7 @@ language = None
exclude_patterns = []
pygments_style = None
# -- Options for HTML output -------------------------------------------------
# -- Options for HTML Output -------------------------------------------------
html_theme = "sphinx_rtd_theme"
html_logo = "images/novelwriter.png"
@@ -64,48 +59,32 @@ html_css_files = [
"css/custom.css",
]
# Custom sidebar templates, must be a dictionary that maps document names
# to template names.
# The default sidebars (for documents that don"t match any pattern) are
# defined by theme itself. Builtin themes are using these templates by
# default: ``["localtoc.html", "relations.html", "sourcelink.html",
# "searchbox.html"]``.
# html_sidebars = {}
# -- Options for HTMLHelp output ---------------------------------------------
# -- 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 = {
# The paper size ("letterpaper" or "a4paper").
#
# "papersize": "letterpaper",
# The font size ("10pt", "11pt" or "12pt").
#
# "pointsize": "10pt",
# Additional stuff for the LaTeX preamble.
#
# "preamble": "",
# Latex figure (float) alignment
#
# "figure_align": "htbp",
"papersize": "a4paper",
"pointsize": "11pt",
"preamble": (
"\\usepackage[utf8]{inputenc}\n"
"\\DeclareUnicodeCharacter{2212}{\\textendash}\n"
),
"figure_align": "htbp",
}
# 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 = [(
master_doc, "novelWriter.tex", "novelWriter Documentation",
master_doc, "manual.tex", "novelWriter Documentation",
author, "manual"
)]
# -- Options for manual page output ------------------------------------------
# -- Options for Man Page Output ---------------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
@@ -113,7 +92,7 @@ man_pages = [(
master_doc, "novelwriter", "novelWriter Documentation", [author], 1
)]
# -- Options for Texinfo output ----------------------------------------------
# -- Options for Texinfo Output ----------------------------------------------
# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
@@ -123,7 +102,7 @@ texinfo_documents = [(
"novelWriter", "Markdown-like editor for novels.", "Miscellaneous"
)]
# -- Options for Epub output -------------------------------------------------
# -- Options for EPub Output -------------------------------------------------
# Bibliographic Dublin Core info.
epub_title = project
+21 -18
View File
@@ -2,29 +2,31 @@
novelWriter |release|
#####################
.. image:: https://github.com/vkbo/novelWriter/workflows/python%203.8/badge.svg?branch=main
:target: https://github.com/vkbo/novelWriter/actions
:alt: Python Tests
.. only:: html
.. image:: https://codecov.io/gh/vkbo/novelWriter/branch/main/graph/badge.svg
:target: https://codecov.io/gh/vkbo/novelWriter
:alt: Code Coverage
.. image:: https://github.com/vkbo/novelWriter/workflows/python%203.8/badge.svg?branch=main
:target: https://github.com/vkbo/novelWriter/actions
:alt: Python Tests
.. image:: https://readthedocs.org/projects/novelwriter/badge/?version=latest
:target: https://novelwriter.readthedocs.io/en/latest/?badge=latest
:alt: Documentation
.. image:: https://codecov.io/gh/vkbo/novelWriter/branch/main/graph/badge.svg
:target: https://codecov.io/gh/vkbo/novelWriter
:alt: Code Coverage
.. image:: https://img.shields.io/github/v/release/vkbo/novelwriter
:target: https://github.com/vkbo/novelWriter/releases
:alt: GitHub Release
.. image:: https://readthedocs.org/projects/novelwriter/badge/?version=latest
:target: https://novelwriter.readthedocs.io/en/latest/?badge=latest
:alt: Documentation
.. image:: https://img.shields.io/pypi/v/novelwriter
:target: https://pypi.org/project/novelWriter/
:alt: PyPI
.. image:: https://img.shields.io/github/v/release/vkbo/novelwriter
:target: https://github.com/vkbo/novelWriter/releases
:alt: GitHub Release
.. image:: https://img.shields.io/pypi/pyversions/novelwriter
:target: https://pypi.org/project/novelWriter/
:alt: Python Version
.. image:: https://img.shields.io/pypi/v/novelwriter
:target: https://pypi.org/project/novelWriter/
:alt: PyPI
.. image:: https://img.shields.io/pypi/pyversions/novelwriter
:target: https://pypi.org/project/novelWriter/
:alt: Python Version
**Last Updated:** |today|
@@ -101,6 +103,7 @@ for your operating system for further details.
:caption: Under the Hood
tech_storage
tech_tests
Indices and Tables
+8 -6
View File
@@ -157,13 +157,15 @@ The documentation can then be built from the ``docs`` folder in the source code
If successful, the documentation should be available in the ``docs/build/html`` folder and you can
open the ``index.html`` file in your browser.
You can also build the documentation by using the setup script:
You can also build the PDF documentation locally using the setup script:
.. code-block:: console
python setup.py docs
python setup.py pdfdocs
This does the same as the ``make help`` command, but in addition it copies the documentation into
novelWriter's assets folder. This will make it possible to open the documentation from inside of
novelWriter by pressing :kbd:`F1` even without an internet connection, as it will instead open the
local copy.
This will build the documentation as a PDF using LaTeX. The file will then be copied into the
assets folder and made available in the :guilabel:`Help` menu in novelWriter. The Sphinx build
system has a few extra dependencies when building the PDF. Please check the `Sphinx Docs`_ if you
have any issues.
.. _Sphinx Docs: https://www.sphinx-doc.org/
+6 -6
View File
@@ -35,9 +35,9 @@ your own backup solution.
extensions `.json` as JSON files are used to cache the index and various run-time settings and
are generally large files that change often. You'd also want to exclude the ``cache`` folder.
The project XML file is indent-formatted, suitable for diff tools and version control since most of
the file will stay static, although a timesetamp is set in the meta section on line 2, and various
meta data entries incremented, on each save.
The project XML file is indent-formatted, and is suitable for diff tools and version control since
most of the file will stay static, although a timesetamp is set in the meta section on line 2, and
various meta data entries incremented, on each save.
Project Documents
@@ -45,7 +45,7 @@ Project Documents
All the project documents are saved in a folder in the main project folder named ``content``. Each
document has a file handle taken from the first 13 characters of a SHA256 hash of the system time
plus an incremented number when the document was first created. The documents are saved with a
when the document was first created, plus an incremented number. The documents are saved with a
filename assembled from this hash and the file extension ``.nwd``.
If you wish to find the file system location of a document in the project, you can either look it
@@ -60,7 +60,7 @@ what the user names the document within the project, or changes it to.
Each document file contains a plain text version of the text from the editor. The file can in
principle be edited in any text editor, and is suitable for diffing and version control if so
desired. Just make sure the file remains in utf-8 encoding, otherwise unicode chatracters may
desired. Just make sure the file remains in utf-8 encoding, otherwise unicode characters may
become mangled when the file is opened in novelWriter again.
Editing these files is generally not recommended outside of special circumstances, whatever they
@@ -115,7 +115,7 @@ project, and the word counts within each header section.
The integrity of the index is checked when the file is loaded. It is possible to corrupt the index
if the file is manually edited and manipulated, so the check is important to avoid sudden crashes
of novelWriter. if the file contains errors, novelWriter will automatically build it anew. If the
of novelWriter. If the file contains errors, novelWriter will automatically build it anew. If the
check somehow fails and novelWriter keeps crashing, you can delete the file manually and rebuild
the index. If this too fails, you have likely encountered a bug.
+85
View File
@@ -0,0 +1,85 @@
.. _a_pytest:
*************
Running Tests
*************
The novelWriter source code is well covered by tests. The test framework used for the development
is ``pytest`` with the use of an extension for Qt.
Dependencies
============
The dependencies for running the tests can be installed with:
.. code-block:: console
pip install -r requirements-dev.txt
This will install a couple of extra packages for coverage and test management. The minimum
requirement is just ``pytest`` and ``pytest-qt``.
Simple Test Run
===============
To run the tests, you simply need to execute the following from the root of the source folder:
.. code-block:: console
pytest
Since several of the tests involve opening up the novelWriter GUI, you may want to disable the GUI
for the duration of the test run. Moving your mouse while the tests are running may otherwise
interfere with the execution of some tests.
You can disable the renderring of the GUI by setting the flag ``export QT_QPA_PLATFORM=offscreen``,
or alternatively run the tests with the ``xvfb`` package, like so:
.. code-block:: console
xvfb-run pytest
Advanced Options
================
Adding the flag ``-v`` to the ``pytest`` command will increase verbosity of the test execution.
You can also add coverage report generation. For instance to HTML:
.. code-block:: console
xvfb-run pytest -v --cov=nw --cov-report=html
Other useful report formats are ``xml``, and ``term`` for terminal output.
You can also run tests per subpackage of novelWriter with the ``-m`` command. The available
subpackage groups are ``base``, ``core``, and ``gui``. Consider for instance:
.. code-block:: console
xvfb-run pytest -v --cov=nw --cov-report=html -m core
This will only run the tests of the "core" package, that is, all the classes that deal with the
project data of a novelWriter project. The "gui" tests, likewise, will run the tests for the GUI
components, and the "base" tests cover the bits in-between.
You can also filter the tests with the ``-k`` switch. The following will do the same as
``-m core``:
.. code-block:: console
xvfb-run pytest -v --cov=nw --cov-report=html -k testCore
All tests are named in such a way that you can filter them by adding more bits of the test names.
They all start with the word "test". Then comes the group: "Core", "Base", "Dlg", "Tool", or "Gui".
Finally comes the name of the class or module, which generally corresponds to a single source code
file. For instance, running the following will run all tests for the document editor:
.. code-block:: console
xvfb-run pytest -v --cov=nw --cov-report=html -k testGuiEditor
To run a single test, simply add the full test name to the ``-k`` switch.
+2 -2
View File
@@ -80,7 +80,7 @@ The main shorcuts are as follows:
":kbd:`Ctrl`:kbd:`Shift`:kbd:`W`", "Close the current project."
":kbd:`Ctrl`:kbd:`Shift`:kbd:`Z`", "Undo move of project tree item."
":kbd:`Ctrl`:kbd:`Shift`:kbd:`Del`", "If in the project tree, move a document to trash, or delete a folder."
":kbd:`F1`", "Open the documentation. This will either open the Qt Assistant, if available, or send you to the documentation website."
":kbd:`F1`", "Open the online user manual."
":kbd:`F2`", "If in the project tree, edit a document or folder settings. (Same as :kbd:`Ctrl`:kbd:`E`)"
":kbd:`F3`", "Find next occurrence of search word in current document. (Same as :kbd:`Ctrl`:kbd:`G`)"
":kbd:`F5`", "Open the :guilabel:`Build Novel Project` dialog."
@@ -90,7 +90,7 @@ The main shorcuts are as follows:
":kbd:`F9`", "Re-build the project index."
":kbd:`F10`", "Re-build the project outline."
":kbd:`F11`", "Activate full screen mode."
":kbd:`Shift`:kbd:`F1`", "Open the online documentation in the system default browser."
":kbd:`Shift`:kbd:`F1`", "Open the local user manual (PDF) if it is available."
":kbd:`Shift`:kbd:`F3`", "Find previous occurrence of search word in current document. (Same as :kbd:`Ctrl`:kbd:`Shift`:kbd:`G`.)"
":kbd:`Shift`:kbd:`F6`", "Open the :guilabel:`Project Details` dialog."
":kbd:`Return`", "If in the project tree, open a document for editing."
+11
View File
@@ -71,6 +71,7 @@ class Config:
self.themeRoot = None # The full path to the nw/assets/themes folder
self.dictPath = None # The full path to the nw/assets/dict folder
self.iconPath = None # The full path to the nw/assets/icons folder
self.pdfDocs = None # The location of the PDF manual, if it exists
# Runtime Settings and Variables
self.confChanged = False # True whenever the config has chenged, false after save
@@ -356,6 +357,16 @@ class Config:
if self.spellLanguage is None:
self.spellLanguage = "en"
# Look for a PDF version of the manual
lookIn = [
os.path.join(self.assetPath, "help", "manual.pdf"),
os.path.join(self.appRoot, "UserManual.pdf"),
]
for pdfDocs in lookIn:
if os.path.isfile(pdfDocs):
self.pdfDocs = pdfDocs
break
logger.debug("Config initialisation complete")
return True
+22 -14
View File
@@ -107,7 +107,7 @@ class ToOdt(Tokenizer):
self.headerText = ""
# Internal
self._fontFamily = "'Liberation Sans'"
self._fontFamily = "'Liberation Serif'"
self._fontPitch = "variable"
self._fSizeTitle = "30pt"
self._fSizeHead1 = "24pt"
@@ -239,6 +239,9 @@ class ToOdt(Tokenizer):
self._blockIndent = self._emToCm(self.blockIndent)
self._textAlign = "justify" if self.doJustify else "left"
# Clear Errors
self._errData = []
# Document Header
# ===============
@@ -546,8 +549,8 @@ class ToOdt(Tokenizer):
rFmt += "_B%s b_ " % (" "*len(tText))
if len(theBits) > 1:
if theBits[0] == nwKeyWords.TAG_KEY:
rTxt += "%s" % theBits[1]
rFmt += "%s" % (" "*len(theBits[1]))
rTxt += theBits[1]
rFmt += " "*len(theBits[1])
else:
tTags = ", ".join(theBits[1:])
rTxt += tTags
@@ -585,12 +588,15 @@ class ToOdt(Tokenizer):
tTemp = ""
xFmt = 0x00
pFmt = 0x00
pErr = 0
parProc = XMLParagraph(xElem)
for i, c in enumerate(theText):
if theFmt[i] == "_":
if theFmt[i] == " ":
tTemp += c
elif theFmt[i] == "_":
continue
elif theFmt[i] == "B":
xFmt |= X_BLD
@@ -604,9 +610,8 @@ class ToOdt(Tokenizer):
xFmt |= X_DEL
elif theFmt[i] == "s":
xFmt &= M_DEL
if theFmt[i] == " ":
tTemp += c
else:
pErr += 1
if xFmt != pFmt:
if pFmt == 0x00:
@@ -624,6 +629,9 @@ class ToOdt(Tokenizer):
else:
parProc.appendSpan(tTemp, self._textStyle(pFmt))
if pErr > 0:
self._errData.append("Unknown format tag encountered")
nErr, errMsg = parProc.checkError()
if nErr > 0: # pragma: no cover
# This one should only capture bugs
@@ -652,23 +660,23 @@ class ToOdt(Tokenizer):
return newName
def _textStyle(self, styleCode):
def _textStyle(self, tFmt):
"""Return a text style for a given style code.
"""
if styleCode in self._autoText:
return self._autoText[styleCode][0]
if tFmt in self._autoText:
return self._autoText[tFmt][0]
newName = "T%d" % (len(self._autoText) + 1)
newStyle = ODTTextStyle()
if styleCode & X_BLD:
if tFmt & X_BLD:
newStyle.setFontWeight("bold")
if styleCode & X_ITA:
if tFmt & X_ITA:
newStyle.setFontStyle("italic")
if styleCode & X_DEL:
if tFmt & X_DEL:
newStyle.setStrikeStyle("solid")
newStyle.setStrikeType("single")
self._autoText[styleCode] = (newName, newStyle)
self._autoText[tFmt] = (newName, newStyle)
return newName
+17 -29
View File
@@ -23,11 +23,9 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
import os
import nw
import logging
from http.client import HTTPSConnection
from urllib.parse import urljoin
from urllib.request import pathname2url
@@ -137,31 +135,13 @@ class GuiMainMenu(QMenuBar):
QDesktopServices.openUrl(QUrl(theUrl))
return True
def _openDocumentation(self):
"""Open the documentation, and select whether it should be the
stable or latest version. If no internet connection, open
local.
def _openUserManualFile(self):
"""Open the documentation in PDF format.
"""
if nw.__hexversion__[-2] == "f":
docsPath = "/en/stable/"
else:
docsPath = "/en/latest/"
try:
conn = HTTPSConnection(nw.__docurl__.replace("https://", ""))
conn.request("HEAD", docsPath)
resp = conn.getresponse()
hasAccess = resp.code == 200
except Exception:
hasAccess = False
docsFile = os.path.join(self.mainConf.assetPath, "help", "html", "index.html")
if hasAccess or not os.path.isfile(docsFile):
self._openWebsite(nw.__docurl__ + docsPath)
else:
self._openWebsite(urljoin("file:", pathname2url(docsFile)))
return
if self.mainConf.pdfDocs is None:
return False
QDesktopServices.openUrl(QUrl(urljoin("file:", pathname2url(self.mainConf.pdfDocs))))
return True
##
# Menu Builders
@@ -1106,13 +1086,21 @@ class GuiMainMenu(QMenuBar):
# Help > Separator
self.helpMenu.addSeparator()
# Help > Documentation
self.aHelpDocs = QAction(self.tr("Documentation"), self)
# Help > User Manual (Online)
self.aHelpDocs = QAction(self.tr("User Manual (Online)"), self)
self.aHelpDocs.setStatusTip(self.tr("Open documentation in browser"))
self.aHelpDocs.triggered.connect(self._openDocumentation)
self.aHelpDocs.setShortcut("F1")
self.aHelpDocs.triggered.connect(lambda: self._openWebsite(nw.__docurl__))
self.helpMenu.addAction(self.aHelpDocs)
# Help > User Manual (PDF)
if self.mainConf.pdfDocs is not None:
self.aPdfDocs = QAction(self.tr("User Manual (PDF)"), self)
self.aPdfDocs.setStatusTip(self.tr("Open PDF documentation"))
self.aPdfDocs.setShortcut("Shift+F1")
self.aPdfDocs.triggered.connect(self._openUserManualFile)
self.helpMenu.addAction(self.aPdfDocs)
# Help > Separator
self.helpMenu.addSeparator()
+2
View File
@@ -1373,6 +1373,8 @@ class GuiMain(QMainWindow):
# Help
self.addAction(self.mainMenu.aHelpDocs)
if self.mainConf.pdfDocs is not None:
self.addAction(self.mainMenu.aPdfDocs)
return True
+56 -19
View File
@@ -222,35 +222,51 @@ def buildQtDocs():
##
# Html Documentation Builder (docs)
# Html or PDF Documentation Builder (docs, docs_pdf)
##
def buildHtmlDocs():
"""This function will build the Sphinx HTML documentation. The files
are then copied into the nw/assets/help/html directory and can be
included in builds.
def buildLocalDocs(bldFmt="HTML"):
"""This function will build the Sphinx HTML or PDF documentation.
For HTML, the files are then copied into the nw/assets/help/html
directory and can be included in builds.
"""
buildDir = os.path.join("docs", "build", "html")
helpDir = os.path.join("nw", "assets", "help", "html")
print("")
print("Building Documentation")
print("======================")
print("Format: %s" % bldFmt)
print("")
if bldFmt == "HTML":
buildDir = os.path.join("docs", "build", "html")
elif bldFmt == "PDF":
buildDir = os.path.join("docs", "build", "latex")
else:
print("Docs Build Error:")
return
buildFail = False
try:
subprocess.call(["make", "-C", "docs", "clean"])
subprocess.call(["make", "-C", "docs", "html"])
if bldFmt == "HTML":
subprocess.call(["make", "-C", "docs", "html"])
elif bldFmt == "PDF":
subprocess.call(["make", "-C", "docs", "latexpdf"])
except Exception as e:
print("Docs Build Error:")
print(str(e))
buildFail = True
try:
if os.path.isdir(helpDir):
shutil.rmtree(helpDir)
shutil.copytree(buildDir, helpDir)
if bldFmt == "HTML":
helpDir = os.path.join("nw", "assets", "help", "html")
if os.path.isdir(helpDir):
shutil.rmtree(helpDir)
shutil.copytree(buildDir, helpDir)
elif bldFmt == "PDF":
os.rename(
os.path.join(buildDir, "manual.pdf"),
os.path.join("nw", "assets", "help", "manual.pdf")
)
except Exception as e:
print("Docs Build Error:")
print(str(e))
@@ -262,7 +278,11 @@ def buildHtmlDocs():
print("")
print("Dependencies:")
print(" * pip install sphinx")
print(" * pip install sphinx-rtd-theme")
if bldFmt == "HTML":
print(" * pip install sphinx-rtd-theme")
elif bldFmt == "PDF":
print(" * Package latexmk")
print(" * LaTeX build system")
sys.exit(1)
else:
print("Documentation build: OK")
@@ -401,7 +421,7 @@ def makeMinimalPackage(targetOS):
# Build docs
try:
buildHtmlDocs()
buildLocalDocs(bldFmt="PDF")
except Exception as e:
print("Failed with error:")
print(str(e))
@@ -440,7 +460,19 @@ def makeMinimalPackage(targetOS):
if os.path.isfile(outFile):
os.unlink(outFile)
rootFiles = ["LICENSE.md", "README.md", "CHANGELOG.md", "requirements.txt", "setup.py"]
# Add the manual also to the root
pdfDocs = os.path.join("nw", "assets", "help", "manual.pdf")
if os.path.isfile(pdfDocs):
os.rename(pdfDocs, "UserManual.pdf")
rootFiles = [
"CHANGELOG.md",
"LICENSE.md",
"README.md",
"requirements.txt",
"setup.py",
"UserManual.pdf",
]
with ZipFile(outFile, "w", compression=ZIP_DEFLATED, compresslevel=9) as zipObj:
@@ -1195,7 +1227,8 @@ if __name__ == "__main__":
"",
"Additional Builds:",
"",
" docs Build the help documentation as HTML."
" htmldocs Build the help documentation as HTML.",
" pdfdocs Build the help documentation as PDF (requires LaTeX).",
" qthelp Build the help documentation for use with the Qt Assistant.",
" qtlupdate Update the translation files for internationalisation.",
" qtlrelease Build the language files for internationalisation.",
@@ -1262,9 +1295,13 @@ if __name__ == "__main__":
# Additional Builds
# =================
if "docs" in sys.argv:
sys.argv.remove("docs")
buildHtmlDocs()
if "htmldocs" in sys.argv:
sys.argv.remove("htmldocs")
buildLocalDocs(bldFmt="HTML")
if "pdfdocs" in sys.argv:
sys.argv.remove("pdfdocs")
buildLocalDocs(bldFmt="PDF")
if "qthelp" in sys.argv:
sys.argv.remove("qthelp")
+3 -3
View File
@@ -199,10 +199,10 @@ def testCoreToOdt_TextFormatting(mockGUI):
# Incorrectly Formatted Text
theDoc.initDocument()
theTxt = "A **few** _words"
theFmt = " _b b_ I "
theTxt = "A **few** _wordsXXX"
theFmt = " _b b_ I XXX"
theDoc._addTextPar("Standard", oStyle, theTxt, theFmt=theFmt)
assert theDoc.getErrors() == []
assert theDoc.getErrors() == ["Unknown format tag encountered"]
assert xmlToText(theDoc._xText) == (
"<office:text>"
"<text:p text:style-name=\"Standard\">"