Add DocX test coverage
This commit is contained in:
@@ -109,7 +109,7 @@ S_HEAD2 = "Heading2"
|
||||
S_HEAD3 = "Heading3"
|
||||
S_HEAD4 = "Heading4"
|
||||
S_SEP = "Separator"
|
||||
S_META = "TextMeta"
|
||||
S_META = "MetaText"
|
||||
S_HEAD = "Header"
|
||||
S_FNOTE = "FootnoteText"
|
||||
|
||||
@@ -423,7 +423,8 @@ class ToDocX(Tokenizer):
|
||||
par.addContent(xNode)
|
||||
xNode = None
|
||||
|
||||
par.addContent(self._textRunToXml(text[fStart:fPos], xFmt))
|
||||
if temp := text[fStart:fPos]:
|
||||
par.addContent(self._textRunToXml(temp, xFmt))
|
||||
|
||||
if fFmt == self.FMT_B_B:
|
||||
xFmt |= X_BLD
|
||||
@@ -647,7 +648,7 @@ class ToDocX(Tokenizer):
|
||||
|
||||
# Add Text Meta Style
|
||||
styles.append(DocXParStyle(
|
||||
name="Text Meta",
|
||||
name="Meta Text",
|
||||
styleId=S_META,
|
||||
size=fSz,
|
||||
basedOn=S_NORM,
|
||||
@@ -905,15 +906,8 @@ class ToDocX(Tokenizer):
|
||||
for i, par in enumerate(self._pars):
|
||||
if i > 0 and par.pageBreakBefore:
|
||||
prev = self._pars[i-1]
|
||||
if prev.pageBreakAfter:
|
||||
# We already have a break, so we inject a new paragraph instead
|
||||
empty = DocXParagraph()
|
||||
empty.setStyle(self._styles.get(S_NORM))
|
||||
empty.setPageBreakAfter(True)
|
||||
pars.append(empty)
|
||||
else:
|
||||
par.setPageBreakBefore(False)
|
||||
prev.setPageBreakAfter(True)
|
||||
par.setPageBreakBefore(False)
|
||||
prev.setPageBreakAfter(True)
|
||||
|
||||
pars.append(par)
|
||||
|
||||
@@ -1037,11 +1031,6 @@ class DocXParagraph:
|
||||
"""Has page break before."""
|
||||
return self._breakBefore
|
||||
|
||||
@property
|
||||
def pageBreakAfter(self) -> bool:
|
||||
"""Has page break after."""
|
||||
return self._breakAfter
|
||||
|
||||
##
|
||||
# Setters
|
||||
##
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<ns0:Types xmlns:ns0="http://schemas.openxmlformats.org/package/2006/content-types">
|
||||
<ns0:Default Extension="xml" ContentType="application/xml" />
|
||||
<ns0:Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml" />
|
||||
<ns0:Override PartName="/_rels/.rels" ContentType="application/vnd.openxmlformats-package.relationships+xml" />
|
||||
<ns0:Override PartName="/word/_rels/document.xml.rels" ContentType="application/vnd.openxmlformats-package.relationships+xml" />
|
||||
<ns0:Override PartName="/docProps/core.xml" ContentType="application/vnd.openxmlformats-package.core-properties+xml" />
|
||||
<ns0:Override PartName="/docProps/app.xml" ContentType="application/vnd.openxmlformats-officedocument.extended-properties+xml" />
|
||||
<ns0:Override PartName="/word/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml" />
|
||||
<ns0:Override PartName="/word/header1.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.header+xml" />
|
||||
<ns0:Override PartName="/word/header2.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.header+xml" />
|
||||
<ns0:Override PartName="/word/document.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml" />
|
||||
<ns0:Override PartName="/word/settings.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.settings+xml" />
|
||||
<ns0:Override PartName="/word/footnotes.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.footnotes+xml" />
|
||||
</ns0:Types>
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<ns0:Properties xmlns:ns0="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties">
|
||||
<ns0:TotalTime>36</ns0:TotalTime>
|
||||
<ns0:Application>novelWriter/2.6a1</ns0:Application>
|
||||
<ns0:Words>4029</ns0:Words>
|
||||
<ns0:Characters>21251</ns0:Characters>
|
||||
<ns0:CharactersWithSpaces>24914</ns0:CharactersWithSpaces>
|
||||
<ns0:Paragraphs>42</ns0:Paragraphs>
|
||||
</ns0:Properties>
|
||||
@@ -0,0 +1,11 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<coreProperties xmlns:cp="http://schemas.openxmlformats.org/package/2006/metadata/core-properties" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:dcterms="http://purl.org/dc/terms/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<dcterms:created xsi:type="dcterms:W3CDTF">2024-10-21T13:54:49</dcterms:created>
|
||||
<dcterms:modified xsi:type="dcterms:W3CDTF">2024-10-21T13:54:49</dcterms:modified>
|
||||
<dc:creator>lipsum.com</dc:creator>
|
||||
<dc:title>Lorem Ipsum</dc:title>
|
||||
<dc:creator>lipsum.com</dc:creator>
|
||||
<dc:language>en_GB</dc:language>
|
||||
<cp:revision>45</cp:revision>
|
||||
<cp:lastModifiedBy>lipsum.com</cp:lastModifiedBy>
|
||||
</coreProperties>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,8 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<ns0:Relationships xmlns:ns0="http://schemas.openxmlformats.org/package/2006/relationships">
|
||||
<ns0:Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml" />
|
||||
<ns0:Relationship Id="rId4" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/header" Target="header1.xml" />
|
||||
<ns0:Relationship Id="rId5" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/header" Target="header2.xml" />
|
||||
<ns0:Relationship Id="rId7" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/settings" Target="settings.xml" />
|
||||
<ns0:Relationship Id="rId8" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/footnotes" Target="footnotes.xml" />
|
||||
</ns0:Relationships>
|
||||
@@ -0,0 +1,20 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<w:footnotes xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
|
||||
<w:footnote w:id="1">
|
||||
<w:p>
|
||||
<w:pPr>
|
||||
<w:pStyle w:val="FootnoteText" />
|
||||
</w:pPr>
|
||||
<w:r>
|
||||
<w:rPr>
|
||||
<w:i />
|
||||
</w:rPr>
|
||||
<w:t>Lorem ipsum</w:t>
|
||||
</w:r>
|
||||
<w:r>
|
||||
<w:rPr />
|
||||
<w:t xml:space="preserve"> is typically a corrupted version of De finibus bonorum et malorum, a 1st-century BC text by the Roman statesman and philosopher Cicero, with words altered, added, and removed to make it nonsensical and improper Latin. (Source: Wikipedia)</w:t>
|
||||
</w:r>
|
||||
</w:p>
|
||||
</w:footnote>
|
||||
</w:footnotes>
|
||||
@@ -0,0 +1,38 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<w:hdr xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
|
||||
<w:p>
|
||||
<w:pPr>
|
||||
<w:pStyle w:val="Header" />
|
||||
<w:jc w:val="right" />
|
||||
<w:rPr />
|
||||
</w:pPr>
|
||||
<w:r>
|
||||
<w:rPr />
|
||||
<w:t xml:space="preserve">Page </w:t>
|
||||
</w:r>
|
||||
<w:r>
|
||||
<w:rPr />
|
||||
<w:fldChar w:fldCharType="begin" />
|
||||
</w:r>
|
||||
<w:r>
|
||||
<w:rPr />
|
||||
<w:t xml:space="preserve"> PAGE </w:t>
|
||||
</w:r>
|
||||
<w:r>
|
||||
<w:rPr />
|
||||
<w:fldChar w:fldCharType="separate" />
|
||||
</w:r>
|
||||
<w:r>
|
||||
<w:rPr />
|
||||
<w:t xml:space="preserve">2</w:t>
|
||||
</w:r>
|
||||
<w:r>
|
||||
<w:rPr />
|
||||
<w:fldChar w:fldCharType="end" />
|
||||
</w:r>
|
||||
<w:r>
|
||||
<w:rPr />
|
||||
<w:t xml:space="preserve"> - Lorem Ipsum (lipsum.com)</w:t>
|
||||
</w:r>
|
||||
</w:p>
|
||||
</w:hdr>
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<w:hdr xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
|
||||
<w:p>
|
||||
<w:pPr>
|
||||
<w:pStyle w:val="Header" />
|
||||
<w:jc w:val="right" />
|
||||
<w:rPr />
|
||||
</w:pPr>
|
||||
<w:r>
|
||||
<w:rPr />
|
||||
</w:r>
|
||||
</w:p>
|
||||
</w:hdr>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<ns0:Relationships xmlns:ns0="http://schemas.openxmlformats.org/package/2006/relationships">
|
||||
<ns0:Relationship Id="rId1" Type="http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties" Target="docProps/core.xml" />
|
||||
<ns0:Relationship Id="rId2" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties" Target="docProps/app.xml" />
|
||||
<ns0:Relationship Id="rId6" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="word/document.xml" />
|
||||
</ns0:Relationships>
|
||||
@@ -0,0 +1,19 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<w:settings xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
|
||||
<w:footnotePr>
|
||||
<w:numFmt w:val="decimal" />
|
||||
</w:footnotePr>
|
||||
<w:docVars>
|
||||
<w:docVar w:name="ManuscriptTitleCount" w:val="11" />
|
||||
<w:docVar w:name="ManuscriptParagraphCount" w:val="42" />
|
||||
<w:docVar w:name="ManuscriptAllWords" w:val="4029" />
|
||||
<w:docVar w:name="ManuscriptTextWords" w:val="3705" />
|
||||
<w:docVar w:name="ManuscriptTitleWords" w:val="21" />
|
||||
<w:docVar w:name="ManuscriptAllChars" w:val="27014" />
|
||||
<w:docVar w:name="ManuscriptTextChars" w:val="24914" />
|
||||
<w:docVar w:name="ManuscriptTitleChars" w:val="123" />
|
||||
<w:docVar w:name="ManuscriptAllWordChars" w:val="23075" />
|
||||
<w:docVar w:name="ManuscriptTextWordChars" w:val="21251" />
|
||||
<w:docVar w:name="ManuscriptTitleWordChars" w:val="113" />
|
||||
</w:docVars>
|
||||
</w:settings>
|
||||
@@ -0,0 +1,153 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<w:styles xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
|
||||
<w:docDefaults>
|
||||
<w:rPrDefault>
|
||||
<w:rPr>
|
||||
<w:rFonts w:ascii="Source Sans Pro" w:hAnsi="Source Sans Pro" w:cs="Source Sans Pro" />
|
||||
<w:sz w:val="24" />
|
||||
<w:szCs w:val="24" />
|
||||
<w:lang w:val="en_GB" />
|
||||
</w:rPr>
|
||||
</w:rPrDefault>
|
||||
<w:pPrDefault>
|
||||
<w:pPr>
|
||||
<w:spacing w:line="276" />
|
||||
</w:pPr>
|
||||
</w:pPrDefault>
|
||||
</w:docDefaults>
|
||||
<w:style w:type="paragraph" w:styleId="Normal" w:default="1">
|
||||
<w:name w:val="Normal" />
|
||||
<w:pPr>
|
||||
<w:spacing w:before="0" w:after="139" w:line="276" />
|
||||
<w:jc w:val="left" />
|
||||
</w:pPr>
|
||||
<w:rPr>
|
||||
<w:sz w:val="24" />
|
||||
<w:szCs w:val="24" />
|
||||
</w:rPr>
|
||||
</w:style>
|
||||
<w:style w:type="paragraph" w:styleId="Title">
|
||||
<w:name w:val="Title" />
|
||||
<w:basedOn w:val="Normal" />
|
||||
<w:next w:val="Normal" />
|
||||
<w:pPr>
|
||||
<w:spacing w:before="340" w:after="120" w:line="690" />
|
||||
<w:outlineLvl w:val="0" />
|
||||
</w:pPr>
|
||||
<w:rPr>
|
||||
<w:b />
|
||||
<w:sz w:val="60" />
|
||||
<w:szCs w:val="60" />
|
||||
</w:rPr>
|
||||
</w:style>
|
||||
<w:style w:type="paragraph" w:styleId="Heading1">
|
||||
<w:name w:val="Heading 1" />
|
||||
<w:basedOn w:val="Normal" />
|
||||
<w:next w:val="Normal" />
|
||||
<w:pPr>
|
||||
<w:spacing w:before="340" w:after="120" w:line="552" />
|
||||
<w:outlineLvl w:val="0" />
|
||||
</w:pPr>
|
||||
<w:rPr>
|
||||
<w:b />
|
||||
<w:color w:val="2a6099" />
|
||||
<w:sz w:val="48" />
|
||||
<w:szCs w:val="48" />
|
||||
</w:rPr>
|
||||
</w:style>
|
||||
<w:style w:type="paragraph" w:styleId="Heading2">
|
||||
<w:name w:val="Heading 2" />
|
||||
<w:basedOn w:val="Normal" />
|
||||
<w:next w:val="Normal" />
|
||||
<w:pPr>
|
||||
<w:spacing w:before="400" w:after="120" w:line="483" />
|
||||
<w:outlineLvl w:val="1" />
|
||||
</w:pPr>
|
||||
<w:rPr>
|
||||
<w:b />
|
||||
<w:color w:val="2a6099" />
|
||||
<w:sz w:val="42" />
|
||||
<w:szCs w:val="42" />
|
||||
</w:rPr>
|
||||
</w:style>
|
||||
<w:style w:type="paragraph" w:styleId="Heading3">
|
||||
<w:name w:val="Heading 3" />
|
||||
<w:basedOn w:val="Normal" />
|
||||
<w:next w:val="Normal" />
|
||||
<w:pPr>
|
||||
<w:spacing w:before="280" w:after="120" w:line="414" />
|
||||
<w:outlineLvl w:val="1" />
|
||||
</w:pPr>
|
||||
<w:rPr>
|
||||
<w:b />
|
||||
<w:color w:val="444444" />
|
||||
<w:sz w:val="36" />
|
||||
<w:szCs w:val="36" />
|
||||
</w:rPr>
|
||||
</w:style>
|
||||
<w:style w:type="paragraph" w:styleId="Heading4">
|
||||
<w:name w:val="Heading 4" />
|
||||
<w:basedOn w:val="Normal" />
|
||||
<w:next w:val="Normal" />
|
||||
<w:pPr>
|
||||
<w:spacing w:before="280" w:after="120" w:line="345" />
|
||||
<w:outlineLvl w:val="1" />
|
||||
</w:pPr>
|
||||
<w:rPr>
|
||||
<w:b />
|
||||
<w:color w:val="444444" />
|
||||
<w:sz w:val="30" />
|
||||
<w:szCs w:val="30" />
|
||||
</w:rPr>
|
||||
</w:style>
|
||||
<w:style w:type="paragraph" w:styleId="Separator">
|
||||
<w:name w:val="Separator" />
|
||||
<w:basedOn w:val="Normal" />
|
||||
<w:next w:val="Normal" />
|
||||
<w:pPr>
|
||||
<w:spacing w:before="280" w:after="280" w:line="276" />
|
||||
<w:jc w:val="center" />
|
||||
</w:pPr>
|
||||
<w:rPr>
|
||||
<w:sz w:val="24" />
|
||||
<w:szCs w:val="24" />
|
||||
</w:rPr>
|
||||
</w:style>
|
||||
<w:style w:type="paragraph" w:styleId="MetaText">
|
||||
<w:name w:val="Meta Text" />
|
||||
<w:basedOn w:val="Normal" />
|
||||
<w:next w:val="Normal" />
|
||||
<w:pPr>
|
||||
<w:spacing w:before="0" w:after="139" w:line="276" />
|
||||
</w:pPr>
|
||||
<w:rPr>
|
||||
<w:color w:val="813709" />
|
||||
<w:sz w:val="24" />
|
||||
<w:szCs w:val="24" />
|
||||
</w:rPr>
|
||||
</w:style>
|
||||
<w:style w:type="paragraph" w:styleId="Header">
|
||||
<w:name w:val="Header" />
|
||||
<w:basedOn w:val="Normal" />
|
||||
<w:pPr>
|
||||
<w:spacing w:before="0" w:after="0" w:line="240" />
|
||||
<w:jc w:val="right" />
|
||||
</w:pPr>
|
||||
<w:rPr>
|
||||
<w:sz w:val="24" />
|
||||
<w:szCs w:val="24" />
|
||||
</w:rPr>
|
||||
</w:style>
|
||||
<w:style w:type="paragraph" w:styleId="FootnoteText">
|
||||
<w:name w:val="Footnote Text" />
|
||||
<w:basedOn w:val="Normal" />
|
||||
<w:pPr>
|
||||
<w:spacing w:before="0" w:after="90" w:line="220" />
|
||||
<w:ind w:left="272" w:hanging="272" />
|
||||
</w:pPr>
|
||||
<w:rPr>
|
||||
<w:sz w:val="19" />
|
||||
<w:szCs w:val="19" />
|
||||
</w:rPr>
|
||||
</w:style>
|
||||
</w:styles>
|
||||
@@ -33,11 +33,12 @@ from PyQt5.QtGui import QColor, QDesktopServices, QFontDatabase
|
||||
from novelwriter.common import (
|
||||
NWConfigParser, checkBool, checkFloat, checkInt, checkIntTuple, checkPath,
|
||||
checkString, checkStringNone, checkUuid, compact, cssCol, describeFont,
|
||||
elide, formatFileFilter, formatInt, formatTime, formatTimeStamp,
|
||||
formatVersion, fuzzyTime, getFileSize, hexToInt, isHandle, isItemClass,
|
||||
isItemLayout, isItemType, isListInstance, isTitleTag, jsonEncode,
|
||||
makeFileNameSafe, minmax, numberToRoman, openExternalPath, readTextFile,
|
||||
simplified, transferCase, uniqueCompact, xmlIndent, yesNo
|
||||
elide, firstFloat, formatFileFilter, formatInt, formatTime,
|
||||
formatTimeStamp, formatVersion, fuzzyTime, getFileSize, hexToInt, isHandle,
|
||||
isItemClass, isItemLayout, isItemType, isListInstance, isTitleTag,
|
||||
jsonEncode, makeFileNameSafe, minmax, numberToRoman, openExternalPath,
|
||||
readTextFile, simplified, transferCase, uniqueCompact, xmlIndent,
|
||||
xmlSubElem, yesNo
|
||||
)
|
||||
|
||||
from tests.mocked import causeOSError
|
||||
@@ -291,6 +292,15 @@ def testBaseCommon_checkIntTuple():
|
||||
assert checkIntTuple(5, (0, 1, 2), 3) == 3
|
||||
|
||||
|
||||
@pytest.mark.base
|
||||
def testBaseCommon_firstFloat():
|
||||
"""Test the firstFloat function."""
|
||||
assert firstFloat(None, 1.0) == 1.0
|
||||
assert firstFloat(1.0, None) == 1.0
|
||||
assert firstFloat(None, 1) == 0.0
|
||||
assert firstFloat(None, "1.0") == 0.0
|
||||
|
||||
|
||||
@pytest.mark.base
|
||||
def testBaseCommon_formatTimeStamp():
|
||||
"""Test the formatTimeStamp function."""
|
||||
@@ -624,6 +634,26 @@ def testBaseCommon_xmlIndent():
|
||||
assert data == "foobar"
|
||||
|
||||
|
||||
@pytest.mark.base
|
||||
def testBaseCommon_xmlSubElem():
|
||||
"""Test the xmlSubElem function."""
|
||||
assert ET.tostring(
|
||||
xmlSubElem(ET.Element("r"), "node", None, attrib={"a": "b"})
|
||||
) == b'<node a="b" />'
|
||||
assert ET.tostring(
|
||||
xmlSubElem(ET.Element("r"), "node", "text", attrib={"a": "b"})
|
||||
) == b'<node a="b">text</node>'
|
||||
assert ET.tostring(
|
||||
xmlSubElem(ET.Element("r"), "node", 42, attrib={"a": "b"})
|
||||
) == b'<node a="b">42</node>'
|
||||
assert ET.tostring(
|
||||
xmlSubElem(ET.Element("r"), "node", 3.14, attrib={"a": "b"})
|
||||
) == b'<node a="b">3.14</node>'
|
||||
assert ET.tostring(
|
||||
xmlSubElem(ET.Element("r"), "node", True, attrib={"a": "b"})
|
||||
) == b'<node a="b">true</node>'
|
||||
|
||||
|
||||
@pytest.mark.base
|
||||
def testBaseCommon_readTextFile(monkeypatch, fncPath, ipsumText):
|
||||
"""Test the readTextFile function."""
|
||||
|
||||
@@ -0,0 +1,566 @@
|
||||
"""
|
||||
novelWriter – ToDocX Class Tester
|
||||
=================================
|
||||
|
||||
This file is a part of novelWriter
|
||||
Copyright 2018–2024, Veronica Berglyd Olsen
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import xml.etree.ElementTree as ET
|
||||
import zipfile
|
||||
|
||||
import pytest
|
||||
|
||||
from novelwriter.common import xmlIndent
|
||||
from novelwriter.constants import nwHeadFmt
|
||||
from novelwriter.core.buildsettings import BuildSettings
|
||||
from novelwriter.core.docbuild import NWBuildDocument
|
||||
from novelwriter.core.project import NWProject
|
||||
from novelwriter.enum import nwBuildFmt
|
||||
from novelwriter.formats.todocx import (
|
||||
S_FNOTE, S_HEAD1, S_HEAD2, S_HEAD3, S_HEAD4, S_META, S_NORM, S_SEP,
|
||||
S_TITLE, ToDocX, _mkTag, _wTag
|
||||
)
|
||||
|
||||
from tests.tools import DOCX_IGNORE, cmpFiles
|
||||
|
||||
OOXML_SCM = "http://schemas.openxmlformats.org"
|
||||
XML_NS = [
|
||||
f' xmlns:r="{OOXML_SCM}/officeDocument/2006/relationships"',
|
||||
f' xmlns:w="{OOXML_SCM}/wordprocessingml/2006/main"',
|
||||
f' xmlns:cp="{OOXML_SCM}/package/2006/metadata/core-properties"',
|
||||
' xmlns:dc="http://purl.org/dc/elements/1.1/"',
|
||||
' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"',
|
||||
' xmlns:xml="http://www.w3.org/XML/1998/namespace"',
|
||||
' xmlns:dcterms="http://purl.org/dc/terms/"',
|
||||
]
|
||||
|
||||
|
||||
def xmlToText(xElem):
|
||||
"""Get the text content of an XML element."""
|
||||
rTxt = ET.tostring(xElem, encoding="utf-8", xml_declaration=False).decode()
|
||||
for ns in XML_NS:
|
||||
rTxt = rTxt.replace(ns, "")
|
||||
return rTxt
|
||||
|
||||
|
||||
@pytest.mark.core
|
||||
def testFmtToDocX_ParagraphStyles(mockGUI):
|
||||
"""Test formatting of paragraphs."""
|
||||
project = NWProject()
|
||||
doc = ToDocX(project)
|
||||
doc.setSynopsis(True)
|
||||
doc.setComments(True)
|
||||
doc.setKeywords(True)
|
||||
doc.initDocument()
|
||||
|
||||
# Normal Text
|
||||
xTest = ET.Element(_wTag("body"))
|
||||
doc._tokens = [(doc.T_TEXT, 0, "Hello World", [], doc.A_NONE)]
|
||||
doc.doConvert()
|
||||
doc._pars[-1].toXml(xTest)
|
||||
assert xmlToText(xTest) == (
|
||||
f'<w:body><w:p><w:pPr><w:pStyle w:val="{S_NORM}" /></w:pPr><w:r><w:rPr />'
|
||||
'<w:t>Hello World</w:t></w:r></w:p></w:body>'
|
||||
)
|
||||
|
||||
# Title
|
||||
xTest = ET.Element(_wTag("body"))
|
||||
doc._tokens = [(doc.T_TITLE, 0, "Hello World", [], doc.A_NONE)]
|
||||
doc.doConvert()
|
||||
doc._pars[-1].toXml(xTest)
|
||||
assert xmlToText(xTest) == (
|
||||
f'<w:body><w:p><w:pPr><w:pStyle w:val="{S_TITLE}" /></w:pPr><w:r><w:rPr />'
|
||||
'<w:t>Hello World</w:t></w:r></w:p></w:body>'
|
||||
)
|
||||
|
||||
# Heading Level 1
|
||||
xTest = ET.Element(_wTag("body"))
|
||||
doc._tokens = [(doc.T_HEAD1, 0, "Hello World", [], doc.A_NONE)]
|
||||
doc.doConvert()
|
||||
doc._pars[-1].toXml(xTest)
|
||||
assert xmlToText(xTest) == (
|
||||
f'<w:body><w:p><w:pPr><w:pStyle w:val="{S_HEAD1}" /></w:pPr><w:r><w:rPr />'
|
||||
'<w:t>Hello World</w:t></w:r></w:p></w:body>'
|
||||
)
|
||||
|
||||
# Heading Level 2
|
||||
xTest = ET.Element(_wTag("body"))
|
||||
doc._tokens = [(doc.T_HEAD2, 0, "Hello World", [], doc.A_NONE)]
|
||||
doc.doConvert()
|
||||
doc._pars[-1].toXml(xTest)
|
||||
assert xmlToText(xTest) == (
|
||||
f'<w:body><w:p><w:pPr><w:pStyle w:val="{S_HEAD2}" /></w:pPr><w:r><w:rPr />'
|
||||
'<w:t>Hello World</w:t></w:r></w:p></w:body>'
|
||||
)
|
||||
|
||||
# Heading Level 3
|
||||
xTest = ET.Element(_wTag("body"))
|
||||
doc._tokens = [(doc.T_HEAD3, 0, "Hello World", [], doc.A_NONE)]
|
||||
doc.doConvert()
|
||||
doc._pars[-1].toXml(xTest)
|
||||
assert xmlToText(xTest) == (
|
||||
f'<w:body><w:p><w:pPr><w:pStyle w:val="{S_HEAD3}" /></w:pPr><w:r><w:rPr />'
|
||||
'<w:t>Hello World</w:t></w:r></w:p></w:body>'
|
||||
)
|
||||
|
||||
# Heading Level 4
|
||||
xTest = ET.Element(_wTag("body"))
|
||||
doc._tokens = [(doc.T_HEAD4, 0, "Hello World", [], doc.A_NONE)]
|
||||
doc.doConvert()
|
||||
doc._pars[-1].toXml(xTest)
|
||||
assert xmlToText(xTest) == (
|
||||
f'<w:body><w:p><w:pPr><w:pStyle w:val="{S_HEAD4}" /></w:pPr><w:r><w:rPr />'
|
||||
'<w:t>Hello World</w:t></w:r></w:p></w:body>'
|
||||
)
|
||||
|
||||
# Separator
|
||||
xTest = ET.Element(_wTag("body"))
|
||||
doc._tokens = [(doc.T_SEP, 0, "* * *", [], doc.A_NONE)]
|
||||
doc.doConvert()
|
||||
doc._pars[-1].toXml(xTest)
|
||||
assert xmlToText(xTest) == (
|
||||
f'<w:body><w:p><w:pPr><w:pStyle w:val="{S_SEP}" /></w:pPr><w:r><w:rPr />'
|
||||
'<w:t>* * *</w:t></w:r></w:p></w:body>'
|
||||
)
|
||||
|
||||
# Empty Paragraph
|
||||
xTest = ET.Element(_wTag("body"))
|
||||
doc._tokens = [(doc.T_SKIP, 0, "* * *", [], doc.A_NONE)]
|
||||
doc.doConvert()
|
||||
doc._pars[-1].toXml(xTest)
|
||||
assert xmlToText(xTest) == (
|
||||
f'<w:body><w:p><w:pPr><w:pStyle w:val="{S_NORM}" /></w:pPr></w:p></w:body>'
|
||||
)
|
||||
|
||||
# Synopsis
|
||||
xTest = ET.Element(_wTag("body"))
|
||||
doc._tokens = [(doc.T_SYNOPSIS, 0, "Hello World", [], doc.A_NONE)]
|
||||
doc.doConvert()
|
||||
doc._pars[-1].toXml(xTest)
|
||||
assert xmlToText(xTest) == (
|
||||
f'<w:body><w:p><w:pPr><w:pStyle w:val="{S_META}" /></w:pPr>'
|
||||
'<w:r><w:rPr><w:b /></w:rPr><w:t>Synopsis:</w:t></w:r>'
|
||||
'<w:r><w:rPr /><w:t xml:space="preserve"> Hello World</w:t></w:r>'
|
||||
'</w:p></w:body>'
|
||||
)
|
||||
|
||||
# Short
|
||||
xTest = ET.Element(_wTag("body"))
|
||||
doc._tokens = [(doc.T_SHORT, 0, "Hello World", [], doc.A_NONE)]
|
||||
doc.doConvert()
|
||||
doc._pars[-1].toXml(xTest)
|
||||
assert xmlToText(xTest) == (
|
||||
f'<w:body><w:p><w:pPr><w:pStyle w:val="{S_META}" /></w:pPr>'
|
||||
'<w:r><w:rPr><w:b /></w:rPr><w:t>Short Description:</w:t></w:r>'
|
||||
'<w:r><w:rPr /><w:t xml:space="preserve"> Hello World</w:t></w:r>'
|
||||
'</w:p></w:body>'
|
||||
)
|
||||
|
||||
# Comment
|
||||
xTest = ET.Element(_wTag("body"))
|
||||
doc._tokens = [(doc.T_COMMENT, 0, "Hello World", [], doc.A_NONE)]
|
||||
doc.doConvert()
|
||||
doc._pars[-1].toXml(xTest)
|
||||
assert xmlToText(xTest) == (
|
||||
f'<w:body><w:p><w:pPr><w:pStyle w:val="{S_META}" /></w:pPr>'
|
||||
'<w:r><w:rPr><w:b /></w:rPr><w:t>Comment:</w:t></w:r>'
|
||||
'<w:r><w:rPr /><w:t xml:space="preserve"> Hello World</w:t></w:r>'
|
||||
'</w:p></w:body>'
|
||||
)
|
||||
|
||||
# Tags and References (Single)
|
||||
xTest = ET.Element(_wTag("body"))
|
||||
doc._tokens = [(doc.T_KEYWORD, 0, "tag: Stuff", [], doc.A_NONE)]
|
||||
doc.doConvert()
|
||||
doc._pars[-1].toXml(xTest)
|
||||
assert xmlToText(xTest) == (
|
||||
f'<w:body><w:p><w:pPr><w:pStyle w:val="{S_META}" /></w:pPr>'
|
||||
'<w:r><w:rPr><w:b /></w:rPr><w:t>Tag:</w:t></w:r>'
|
||||
'<w:r><w:rPr /><w:t xml:space="preserve"> Stuff</w:t></w:r>'
|
||||
'</w:p></w:body>'
|
||||
)
|
||||
|
||||
# Tags and References (Multiple)
|
||||
xTest = ET.Element(_wTag("body"))
|
||||
doc._tokens = [(doc.T_KEYWORD, 0, "char: Jane, John", [], doc.A_NONE)]
|
||||
doc.doConvert()
|
||||
doc._pars[-1].toXml(xTest)
|
||||
assert xmlToText(xTest) == (
|
||||
f'<w:body><w:p><w:pPr><w:pStyle w:val="{S_META}" /></w:pPr>'
|
||||
'<w:r><w:rPr><w:b /></w:rPr><w:t>Characters:</w:t></w:r>'
|
||||
'<w:r><w:rPr /><w:t xml:space="preserve"> Jane, John</w:t></w:r>'
|
||||
'</w:p></w:body>'
|
||||
)
|
||||
|
||||
# Tags and References (Invalid)
|
||||
xTest = ET.Element(_wTag("body"))
|
||||
doc._tokens = [(doc.T_KEYWORD, 0, "stuff: Stuff", [], doc.A_NONE)]
|
||||
doc.doConvert()
|
||||
doc._pars[-1].toXml(xTest)
|
||||
assert xmlToText(xTest) == (
|
||||
f'<w:body><w:p><w:pPr><w:pStyle w:val="{S_META}" /></w:pPr></w:p></w:body>'
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.core
|
||||
def testFmtToDocX_ParagraphFormatting(mockGUI):
|
||||
"""Test formatting of paragraphs."""
|
||||
project = NWProject()
|
||||
doc = ToDocX(project)
|
||||
doc.setSynopsis(True)
|
||||
doc.setComments(True)
|
||||
doc.setKeywords(True)
|
||||
doc.initDocument()
|
||||
|
||||
# Left Align
|
||||
xTest = ET.Element(_wTag("body"))
|
||||
doc._tokens = [(doc.T_TEXT, 0, "Hello World", [], doc.A_LEFT)]
|
||||
doc.doConvert()
|
||||
doc._pars[-1].toXml(xTest)
|
||||
assert xmlToText(xTest) == (
|
||||
f'<w:body><w:p><w:pPr><w:pStyle w:val="{S_NORM}" /><w:jc w:val="left" /></w:pPr>'
|
||||
'<w:r><w:rPr /><w:t>Hello World</w:t></w:r></w:p></w:body>'
|
||||
)
|
||||
|
||||
# Right Align
|
||||
xTest = ET.Element(_wTag("body"))
|
||||
doc._tokens = [(doc.T_TEXT, 0, "Hello World", [], doc.A_RIGHT)]
|
||||
doc.doConvert()
|
||||
doc._pars[-1].toXml(xTest)
|
||||
assert xmlToText(xTest) == (
|
||||
f'<w:body><w:p><w:pPr><w:pStyle w:val="{S_NORM}" /><w:jc w:val="right" /></w:pPr>'
|
||||
'<w:r><w:rPr /><w:t>Hello World</w:t></w:r></w:p></w:body>'
|
||||
)
|
||||
|
||||
# Center Align
|
||||
xTest = ET.Element(_wTag("body"))
|
||||
doc._tokens = [(doc.T_TEXT, 0, "Hello World", [], doc.A_CENTRE)]
|
||||
doc.doConvert()
|
||||
doc._pars[-1].toXml(xTest)
|
||||
assert xmlToText(xTest) == (
|
||||
f'<w:body><w:p><w:pPr><w:pStyle w:val="{S_NORM}" /><w:jc w:val="center" /></w:pPr>'
|
||||
'<w:r><w:rPr /><w:t>Hello World</w:t></w:r></w:p></w:body>'
|
||||
)
|
||||
|
||||
# Justify
|
||||
xTest = ET.Element(_wTag("body"))
|
||||
doc._tokens = [(doc.T_TEXT, 0, "Hello World", [], doc.A_JUSTIFY)]
|
||||
doc.doConvert()
|
||||
doc._pars[-1].toXml(xTest)
|
||||
assert xmlToText(xTest) == (
|
||||
f'<w:body><w:p><w:pPr><w:pStyle w:val="{S_NORM}" /><w:jc w:val="both" /></w:pPr>'
|
||||
'<w:r><w:rPr /><w:t>Hello World</w:t></w:r></w:p></w:body>'
|
||||
)
|
||||
|
||||
# Page Break Before
|
||||
xTest = ET.Element(_wTag("body"))
|
||||
doc._tokens = [(doc.T_TEXT, 0, "Hello World", [], doc.A_PBB)]
|
||||
doc.doConvert()
|
||||
doc._pars[-1].toXml(xTest)
|
||||
assert xmlToText(xTest) == (
|
||||
f'<w:body><w:p><w:pPr><w:pStyle w:val="{S_NORM}" /></w:pPr>'
|
||||
'<w:r><w:br w:type="page" /></w:r>'
|
||||
'<w:r><w:rPr /><w:t>Hello World</w:t></w:r>'
|
||||
'</w:p></w:body>'
|
||||
)
|
||||
|
||||
# Page Break After
|
||||
xTest = ET.Element(_wTag("body"))
|
||||
doc._tokens = [(doc.T_TEXT, 0, "Hello World", [], doc.A_PBA)]
|
||||
doc.doConvert()
|
||||
doc._pars[-1].toXml(xTest)
|
||||
assert xmlToText(xTest) == (
|
||||
f'<w:body><w:p><w:pPr><w:pStyle w:val="{S_NORM}" /></w:pPr>'
|
||||
'<w:r><w:rPr /><w:t>Hello World</w:t></w:r>'
|
||||
'<w:r><w:br w:type="page" /></w:r>'
|
||||
'</w:p></w:body>'
|
||||
)
|
||||
|
||||
# Zero Margins
|
||||
xTest = ET.Element(_wTag("body"))
|
||||
doc._tokens = [(doc.T_TEXT, 0, "Hello World", [], doc.A_Z_TOPMRG | doc.A_Z_BTMMRG)]
|
||||
doc.doConvert()
|
||||
doc._pars[-1].toXml(xTest)
|
||||
assert xmlToText(xTest) == (
|
||||
f'<w:body><w:p><w:pPr><w:pStyle w:val="{S_NORM}" />'
|
||||
'<w:spacing w:before="0" w:after="0" w:line="252" /></w:pPr>'
|
||||
'<w:r><w:rPr /><w:t>Hello World</w:t></w:r></w:p></w:body>'
|
||||
)
|
||||
|
||||
# Indent
|
||||
xTest = ET.Element(_wTag("body"))
|
||||
doc._tokens = [(doc.T_TEXT, 0, "Hello World", [], doc.A_IND_L | doc.A_IND_R)]
|
||||
doc.doConvert()
|
||||
doc._pars[-1].toXml(xTest)
|
||||
assert xmlToText(xTest) == (
|
||||
f'<w:body><w:p><w:pPr><w:pStyle w:val="{S_NORM}" />'
|
||||
'<w:ind w:left="880" w:right="880" /></w:pPr>'
|
||||
'<w:r><w:rPr /><w:t>Hello World</w:t></w:r></w:p></w:body>'
|
||||
)
|
||||
|
||||
# First Line Indent
|
||||
xTest = ET.Element(_wTag("body"))
|
||||
doc._tokens = [(doc.T_TEXT, 0, "Hello World", [], doc.A_IND_T)]
|
||||
doc.doConvert()
|
||||
doc._pars[-1].toXml(xTest)
|
||||
assert xmlToText(xTest) == (
|
||||
f'<w:body><w:p><w:pPr><w:pStyle w:val="{S_NORM}" />'
|
||||
'<w:ind w:firstLine="308" /></w:pPr>'
|
||||
'<w:r><w:rPr /><w:t>Hello World</w:t></w:r></w:p></w:body>'
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.core
|
||||
def testFmtToDocX_TextFormatting(mockGUI):
|
||||
"""Test formatting of text."""
|
||||
project = NWProject()
|
||||
doc = ToDocX(project)
|
||||
doc.initDocument()
|
||||
|
||||
# Markdown
|
||||
xTest = ET.Element(_wTag("body"))
|
||||
doc._text = "Text **bold**, _italic_, ~~strike~~."
|
||||
doc.tokenizeText()
|
||||
doc.doConvert()
|
||||
doc._pars[-1].toXml(xTest)
|
||||
assert xmlToText(xTest) == (
|
||||
f'<w:body><w:p><w:pPr><w:pStyle w:val="{S_NORM}" /></w:pPr>'
|
||||
'<w:r><w:rPr /><w:t xml:space="preserve">Text </w:t></w:r>'
|
||||
'<w:r><w:rPr><w:b /></w:rPr><w:t>bold</w:t></w:r>'
|
||||
'<w:r><w:rPr /><w:t xml:space="preserve">, </w:t></w:r>'
|
||||
'<w:r><w:rPr><w:i /></w:rPr><w:t>italic</w:t></w:r>'
|
||||
'<w:r><w:rPr /><w:t xml:space="preserve">, </w:t></w:r>'
|
||||
'<w:r><w:rPr><w:strike /></w:rPr><w:t>strike</w:t></w:r>'
|
||||
'<w:r><w:rPr /><w:t>.</w:t></w:r>'
|
||||
'</w:p></w:body>'
|
||||
)
|
||||
|
||||
# Nested Shortcode Text, Emphasis
|
||||
xTest = ET.Element(_wTag("body"))
|
||||
doc._text = "Some [s]nested [b]bold[/b] [u]and[/u] [i]italics[/i] text[/s] text."
|
||||
doc.tokenizeText()
|
||||
doc.doConvert()
|
||||
doc._pars[-1].toXml(xTest)
|
||||
assert xmlToText(xTest) == (
|
||||
f'<w:body><w:p><w:pPr><w:pStyle w:val="{S_NORM}" /></w:pPr>'
|
||||
'<w:r><w:rPr /><w:t xml:space="preserve">Some </w:t></w:r>'
|
||||
'<w:r><w:rPr><w:strike /></w:rPr><w:t xml:space="preserve">nested </w:t></w:r>'
|
||||
'<w:r><w:rPr><w:b /><w:strike /></w:rPr><w:t>bold</w:t></w:r>'
|
||||
'<w:r><w:rPr><w:strike /></w:rPr><w:t xml:space="preserve"> </w:t></w:r>'
|
||||
'<w:r><w:rPr><w:u w:val="single" /><w:strike /></w:rPr><w:t>and</w:t></w:r>'
|
||||
'<w:r><w:rPr><w:strike /></w:rPr><w:t xml:space="preserve"> </w:t></w:r>'
|
||||
'<w:r><w:rPr><w:i /><w:strike /></w:rPr><w:t>italics</w:t></w:r>'
|
||||
'<w:r><w:rPr><w:strike /></w:rPr><w:t xml:space="preserve"> text</w:t></w:r>'
|
||||
'<w:r><w:rPr /><w:t xml:space="preserve"> text.</w:t></w:r>'
|
||||
'</w:p></w:body>'
|
||||
)
|
||||
|
||||
# Shortcode Text, Super/Subscript
|
||||
xTest = ET.Element(_wTag("body"))
|
||||
doc._text = "Some super[sup]script[/sup] and sub[sub]script[/sub] text."
|
||||
doc.tokenizeText()
|
||||
doc.doConvert()
|
||||
doc._pars[-1].toXml(xTest)
|
||||
assert xmlToText(xTest) == (
|
||||
f'<w:body><w:p><w:pPr><w:pStyle w:val="{S_NORM}" /></w:pPr>'
|
||||
'<w:r><w:rPr /><w:t>Some super</w:t></w:r>'
|
||||
'<w:r><w:rPr><w:vertAlign w:val="superscript" /></w:rPr><w:t>script</w:t></w:r>'
|
||||
'<w:r><w:rPr /><w:t xml:space="preserve"> and sub</w:t></w:r>'
|
||||
'<w:r><w:rPr><w:vertAlign w:val="subscript" /></w:rPr><w:t>script</w:t></w:r>'
|
||||
'<w:r><w:rPr /><w:t xml:space="preserve"> text.</w:t></w:r>'
|
||||
'</w:p></w:body>'
|
||||
)
|
||||
|
||||
# Shortcode Text, Underline/Highlight
|
||||
xTest = ET.Element(_wTag("body"))
|
||||
doc._text = "Some [u]underlined and [m]highlighted[/m][/u] text."
|
||||
doc.tokenizeText()
|
||||
doc.doConvert()
|
||||
doc._pars[-1].toXml(xTest)
|
||||
assert xmlToText(xTest) == (
|
||||
f'<w:body><w:p><w:pPr><w:pStyle w:val="{S_NORM}" /></w:pPr>'
|
||||
'<w:r><w:rPr /><w:t xml:space="preserve">Some </w:t></w:r>'
|
||||
'<w:r><w:rPr><w:u w:val="single" /></w:rPr>'
|
||||
'<w:t xml:space="preserve">underlined and </w:t></w:r>'
|
||||
'<w:r><w:rPr><w:u w:val="single" /><w:shd w:fill="ffffa6" w:val="clear" /></w:rPr>'
|
||||
'<w:t>highlighted</w:t></w:r>'
|
||||
'<w:r><w:rPr /><w:t xml:space="preserve"> text.</w:t></w:r>'
|
||||
'</w:p></w:body>'
|
||||
)
|
||||
|
||||
# Hard Break
|
||||
xTest = ET.Element(_wTag("body"))
|
||||
doc._text = "Some text.\nNext line\n"
|
||||
doc.tokenizeText()
|
||||
doc.doConvert()
|
||||
doc._pars[-1].toXml(xTest)
|
||||
assert xmlToText(xTest) == (
|
||||
f'<w:body><w:p><w:pPr><w:pStyle w:val="{S_NORM}" /></w:pPr>'
|
||||
'<w:r><w:rPr /><w:t>Some text.</w:t><w:br /><w:t>Next line</w:t></w:r>'
|
||||
'</w:p></w:body>'
|
||||
)
|
||||
|
||||
# Tab
|
||||
xTest = ET.Element(_wTag("body"))
|
||||
doc._text = "\tItem 1\tItem 2\n"
|
||||
doc.tokenizeText()
|
||||
doc.doConvert()
|
||||
doc._pars[-1].toXml(xTest)
|
||||
assert xmlToText(xTest) == (
|
||||
f'<w:body><w:p><w:pPr><w:pStyle w:val="{S_NORM}" /></w:pPr>'
|
||||
'<w:r><w:rPr /><w:tab /><w:t>Item 1</w:t><w:tab /><w:t>Item 2</w:t></w:r>'
|
||||
'</w:p></w:body>'
|
||||
)
|
||||
|
||||
# Tab in Format
|
||||
xTest = ET.Element(_wTag("body"))
|
||||
doc._text = "Some **bold\ttext**"
|
||||
doc.tokenizeText()
|
||||
doc.doConvert()
|
||||
doc._pars[-1].toXml(xTest)
|
||||
assert xmlToText(xTest) == (
|
||||
f'<w:body><w:p><w:pPr><w:pStyle w:val="{S_NORM}" /></w:pPr>'
|
||||
'<w:r><w:rPr /><w:t xml:space="preserve">Some </w:t></w:r>'
|
||||
'<w:r><w:rPr><w:b /></w:rPr><w:t>bold</w:t><w:tab /><w:t>text</w:t></w:r>'
|
||||
'</w:p></w:body>'
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.core
|
||||
def testFmtToDocX_Footnotes(mockGUI):
|
||||
"""Test formatting of footnotes."""
|
||||
project = NWProject()
|
||||
doc = ToDocX(project)
|
||||
doc.initDocument()
|
||||
|
||||
# Text
|
||||
xTest = ET.Element(_wTag("body"))
|
||||
doc._text = (
|
||||
"Text with one[footnote:fa], **two**[footnote:fd], "
|
||||
"or three[footnote:fb] footnotes.[footnote:fe]\n\n"
|
||||
"%footnote.fa: Footnote text A.[footnote:fc]\n\n"
|
||||
"%footnote.fc: This footnote is skipped.\n\n"
|
||||
"%footnote.fd: Another footnote.\n\n"
|
||||
"%footnote.fe: Again?\n\n"
|
||||
)
|
||||
doc.tokenizeText()
|
||||
doc.doConvert()
|
||||
doc._pars[-1].toXml(xTest)
|
||||
assert xmlToText(xTest) == (
|
||||
f'<w:body><w:p><w:pPr><w:pStyle w:val="{S_NORM}" /></w:pPr>'
|
||||
'<w:r><w:rPr /><w:t>Text with one</w:t></w:r>'
|
||||
'<w:r><w:rPr><w:vertAlign w:val="superscript" /></w:rPr>'
|
||||
'<w:footnoteReference w:id="1" /></w:r>'
|
||||
'<w:r><w:rPr /><w:t xml:space="preserve">, </w:t></w:r>'
|
||||
'<w:r><w:rPr><w:b /></w:rPr><w:t>two</w:t></w:r>'
|
||||
'<w:r><w:rPr><w:vertAlign w:val="superscript" /></w:rPr>'
|
||||
'<w:footnoteReference w:id="2" /></w:r>'
|
||||
'<w:r><w:rPr /><w:t>, or three</w:t></w:r>'
|
||||
'<w:r><w:rPr /><w:t xml:space="preserve"> footnotes.</w:t></w:r>'
|
||||
'<w:r><w:rPr><w:vertAlign w:val="superscript" /></w:rPr>'
|
||||
'<w:footnoteReference w:id="3" /></w:r>'
|
||||
'</w:p></w:body>'
|
||||
)
|
||||
|
||||
# Footnotes
|
||||
doc._footnotesXml()
|
||||
assert xmlToText(doc._files["footnotes.xml"].xml) == (
|
||||
'<w:footnotes>'
|
||||
f'<w:footnote w:id="1"><w:p><w:pPr><w:pStyle w:val="{S_FNOTE}" /></w:pPr>'
|
||||
'<w:r><w:rPr /><w:t>Footnote text A.</w:t></w:r></w:p></w:footnote>'
|
||||
f'<w:footnote w:id="2"><w:p><w:pPr><w:pStyle w:val="{S_FNOTE}" /></w:pPr>'
|
||||
'<w:r><w:rPr /><w:t>Another footnote.</w:t></w:r></w:p></w:footnote>'
|
||||
f'<w:footnote w:id="3"><w:p><w:pPr><w:pStyle w:val="{S_FNOTE}" /></w:pPr>'
|
||||
'<w:r><w:rPr /><w:t>Again?</w:t></w:r></w:p></w:footnote>'
|
||||
'</w:footnotes>'
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.core
|
||||
def testFmtToDocX_SaveDocument(mockGUI, prjLipsum, fncPath, tstPaths):
|
||||
"""Test document output."""
|
||||
project = NWProject()
|
||||
project.openProject(prjLipsum)
|
||||
|
||||
pageHeader = f"Page {nwHeadFmt.ODT_PAGE} - {nwHeadFmt.ODT_PROJECT} ({nwHeadFmt.ODT_AUTHOR})"
|
||||
|
||||
build = BuildSettings()
|
||||
build.setValue("filter.includeNovel", True)
|
||||
build.setValue("filter.includeNotes", True)
|
||||
build.setValue("filter.includeInactive", False)
|
||||
build.setValue("text.includeSynopsis", True)
|
||||
build.setValue("text.includeComments", True)
|
||||
build.setValue("text.includeKeywords", True)
|
||||
build.setValue("format.textFont", "Source Sans Pro,12")
|
||||
build.setValue("format.firstLineIndent", True)
|
||||
build.setValue("odt.pageHeader", pageHeader)
|
||||
|
||||
docBuild = NWBuildDocument(project, build)
|
||||
docBuild.queueAll()
|
||||
|
||||
docPath = fncPath / "document.docx"
|
||||
assert list(docBuild.iterBuildDocument(docPath, nwBuildFmt.DOCX)) == [
|
||||
(0, True), (1, True), (2, True), (3, True), (4, True), (5, False),
|
||||
(6, True), (7, True), (8, True), (9, False), (10, False), (11, True),
|
||||
(12, True), (13, True), (14, True), (15, True), (16, True), (17, True),
|
||||
(18, True), (19, True), (20, True),
|
||||
]
|
||||
|
||||
assert docPath.exists()
|
||||
assert zipfile.is_zipfile(docPath)
|
||||
|
||||
with zipfile.ZipFile(docPath, mode="r") as zipObj:
|
||||
zipObj.extractall(fncPath / "extract")
|
||||
|
||||
def prettifyXml(inFile, outFile):
|
||||
with open(outFile, mode="wb") as fStream:
|
||||
xml = ET.parse(inFile)
|
||||
xmlIndent(xml)
|
||||
xml.write(fStream, encoding="utf-8", xml_declaration=True)
|
||||
|
||||
expected = [
|
||||
fncPath / "extract" / "[Content_Types].xml",
|
||||
fncPath / "extract" / "_rels" / ".rels",
|
||||
fncPath / "extract" / "docProps" / "app.xml",
|
||||
fncPath / "extract" / "docProps" / "core.xml",
|
||||
fncPath / "extract" / "word" / "_rels" / "document.xml.rels",
|
||||
fncPath / "extract" / "word" / "document.xml",
|
||||
fncPath / "extract" / "word" / "footnotes.xml",
|
||||
fncPath / "extract" / "word" / "header1.xml",
|
||||
fncPath / "extract" / "word" / "header2.xml",
|
||||
fncPath / "extract" / "word" / "settings.xml",
|
||||
fncPath / "extract" / "word" / "styles.xml",
|
||||
]
|
||||
|
||||
outDir = tstPaths.outDir / "fmtToDocX_SaveDocument"
|
||||
outDir.mkdir()
|
||||
for file in expected:
|
||||
assert file.is_file()
|
||||
name = file.name.replace("[", "").replace("]", "").lstrip(".")
|
||||
outFile = outDir / name
|
||||
refFile = tstPaths.refDir / f"fmtToDocX_SaveDocument_{name}"
|
||||
prettifyXml(file, outFile)
|
||||
assert cmpFiles(outFile, refFile, ignoreStart=DOCX_IGNORE)
|
||||
|
||||
|
||||
@pytest.mark.core
|
||||
def testFmtToDocX_MkTag():
|
||||
"""Test the tag maker function."""
|
||||
assert _mkTag("r", "id") == f"{{{OOXML_SCM}/officeDocument/2006/relationships}}id"
|
||||
assert _mkTag("w", "t") == f"{{{OOXML_SCM}/wordprocessingml/2006/main}}t"
|
||||
assert _mkTag("q", "t") == "t"
|
||||
@@ -1,6 +1,6 @@
|
||||
"""
|
||||
novelWriter – ToOdt Class Tester
|
||||
=================================
|
||||
================================
|
||||
|
||||
This file is a part of novelWriter
|
||||
Copyright 2018–2024, Veronica Berglyd Olsen
|
||||
@@ -48,8 +48,8 @@ XML_NS = [
|
||||
def xmlToText(xElem):
|
||||
"""Get the text content of an XML element."""
|
||||
rTxt = ET.tostring(xElem, encoding="utf-8", xml_declaration=False).decode()
|
||||
for nSpace in XML_NS:
|
||||
rTxt = rTxt.replace(nSpace, "")
|
||||
for ns in XML_NS:
|
||||
rTxt = rTxt.replace(ns, "")
|
||||
return rTxt
|
||||
|
||||
|
||||
|
||||
+3
-1
@@ -30,6 +30,7 @@ from PyQt5.QtWidgets import QDialog, QVBoxLayout, QWidget
|
||||
XML_IGNORE = ("<novelWriterXML", "<project")
|
||||
ODT_IGNORE = ("<meta:generator", "<meta:creation-date", "<dc:date", "<meta:editing")
|
||||
NWD_IGNORE = ("%%~date:",)
|
||||
DOCX_IGNORE = ("<dcterms:created", "<dcterms:modified")
|
||||
MOCK_TIME = datetime(2019, 5, 10, 18, 52, 0).timestamp()
|
||||
|
||||
|
||||
@@ -59,7 +60,8 @@ class C:
|
||||
|
||||
|
||||
def cmpFiles(
|
||||
fileOne: str | Path, fileTwo: str | Path,
|
||||
fileOne: str | Path,
|
||||
fileTwo: str | Path,
|
||||
ignoreLines: list | None = None,
|
||||
ignoreStart: tuple | None = None
|
||||
) -> bool:
|
||||
|
||||
Reference in New Issue
Block a user