"""
novelWriter – ToOdt Class Tester
================================
This file is a part of novelWriter
Copyright (C) 2021 Veronica Berglyd Olsen and novelWriter contributors
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 .
""" # noqa
from __future__ import annotations
import xml.etree.ElementTree as ET
import zipfile
from shutil import copyfile
import pytest
from PyQt6.QtGui import QColor
from novelwriter.common import xmlIndent
from novelwriter.constants import nwHeadFmt
from novelwriter.core.project import NWProject
from novelwriter.enum import nwComment
from novelwriter.formats.shared import BlockFmt, BlockTyp, TextFmt
from novelwriter.formats.toodt import ODTParagraphStyle, ODTTextStyle, ToOdt, XMLParagraph, _mkTag
from tests.tools import ODT_IGNORE, cmpFiles, xmlToText
@pytest.mark.core
def testFmtToOdt_Init(mockGUI):
"""Test initialisation of the ODT document."""
project = NWProject()
# Flat Doc
# ========
odt = ToOdt(project, isFlat=True)
odt.initDocument()
# Document XML
assert odt._dFlat.tag == _mkTag("office", "document")
assert odt._dCont.tag == ""
assert odt._dMeta.tag == ""
assert odt._dStyl.tag == ""
# Content XML
assert odt._xMeta.tag == _mkTag("office", "meta")
assert odt._xFont.tag == _mkTag("office", "font-face-decls")
assert odt._xFnt2.tag == ""
assert odt._xStyl.tag == _mkTag("office", "styles")
assert odt._xAuto.tag == _mkTag("office", "automatic-styles")
assert odt._xAut2.tag == ""
assert odt._xMast.tag == _mkTag("office", "master-styles")
assert odt._xBody.tag == _mkTag("office", "body")
assert odt._xText.tag == _mkTag("office", "text")
# ODT Doc
# =======
odt = ToOdt(project, isFlat=False)
odt.initDocument()
# Document XML
assert odt._dFlat.tag == ""
assert odt._dCont.tag == _mkTag("office", "document-content")
assert odt._dMeta.tag == _mkTag("office", "document-meta")
assert odt._dStyl.tag == _mkTag("office", "document-styles")
# Content XML
assert odt._xMeta.tag == _mkTag("office", "meta")
assert odt._xFont.tag == _mkTag("office", "font-face-decls")
assert odt._xFnt2.tag == _mkTag("office", "font-face-decls")
assert odt._xStyl.tag == _mkTag("office", "styles")
assert odt._xAuto.tag == _mkTag("office", "automatic-styles")
assert odt._xAut2.tag == _mkTag("office", "automatic-styles")
assert odt._xMast.tag == _mkTag("office", "master-styles")
assert odt._xBody.tag == _mkTag("office", "body")
assert odt._xText.tag == _mkTag("office", "text")
@pytest.mark.core
def testFmtToOdt_TextFormatting(mockGUI):
"""Test formatting of paragraphs."""
project = NWProject()
odt = ToOdt(project, isFlat=True)
odt.initDocument()
assert xmlToText(odt._xText) == ""
# Paragraph Style
# ===============
oStyle = ODTParagraphStyle("test")
assert odt._paraStyle("stuff", oStyle) == "Standard"
assert odt._paraStyle("Text_20_body", oStyle) == "Text_20_body"
# Create new para style
oStyle.setTextAlign("center")
assert odt._paraStyle("Text_20_body", oStyle) == "P1"
# Return the same style on second call
assert odt._paraStyle("Text_20_body", oStyle) == "P1"
assert list(odt._mainPara.keys()) == [
"Text_20_body", "First_20_line_20_indent", "Text_20_Meta", "Title", "Separator",
"Heading_20_1", "Heading_20_2", "Heading_20_3", "Heading_20_4", "Header", "Footnote",
]
key = "55db6c1d22ff5aba93f0f67c8d4a857a26e2d3813dfbcba1ef7c0d424f501be5"
assert key in odt._autoPara
assert isinstance(odt._autoPara[key], ODTParagraphStyle)
assert odt._autoPara[key].name == "P1"
# Paragraph Formatting
# ====================
oStyle = ODTParagraphStyle("test")
# No Text
xTest = ET.Element(_mkTag("office", "text"))
odt._addTextPar(xTest, "Standard", oStyle, "")
assert xmlToText(xTest) == (
''
''
''
)
# No Format
xTest = ET.Element(_mkTag("office", "text"))
odt._addTextPar(xTest, "Standard", oStyle, "Hello World")
assert odt.errData == []
assert xmlToText(xTest) == (
''
'Hello World'
''
)
# Heading Level None
xTest = ET.Element(_mkTag("office", "text"))
odt._addTextPar(xTest, "Standard", oStyle, "Hello World", isHead=True)
assert odt.errData == []
assert xmlToText(xTest) == (
''
'Hello World'
''
)
# Heading Level 1
xTest = ET.Element(_mkTag("office", "text"))
odt._addTextPar(xTest, "Standard", oStyle, "Hello World", isHead=True, oLevel="1")
assert odt.errData == []
assert xmlToText(xTest) == (
''
'Hello World'
''
)
# Formatted Text
text = "A bold word"
fmt = [(2, TextFmt.B_B, ""), (6, TextFmt.B_E, "")]
xTest = ET.Element(_mkTag("office", "text"))
odt._addTextPar(xTest, "Standard", oStyle, text, tFmt=fmt)
assert odt.errData == []
assert xmlToText(xTest) == (
''
'A bold '
'word'
''
)
# Incorrectly Formatted Text
text = "A few words"
fmt = [(2, TextFmt.B_B, ""), (5, TextFmt.B_E, ""), (7, 99999, "")]
xTest = ET.Element(_mkTag("office", "text"))
odt._addTextPar(xTest, "Standard", oStyle, text, tFmt=fmt)
assert xmlToText(xTest) == (
''
'A few '
'words'
''
)
odt._errData = []
# Unclosed format
text = "A bold word"
fmt = [(2, TextFmt.B_B, "")]
xTest = ET.Element(_mkTag("office", "text"))
odt._addTextPar(xTest, "Standard", oStyle, text, tFmt=fmt)
assert odt.errData == []
assert xmlToText(xTest) == (
''
'A '
'bold word'
''
)
# Tabs and Breaks
text = "Hello\n\tWorld"
fmt = []
xTest = ET.Element(_mkTag("office", "text"))
odt._addTextPar(xTest, "Standard", oStyle, text, tFmt=fmt)
assert odt.errData == []
assert xmlToText(xTest) == (
''
'HelloWorld'
''
)
@pytest.mark.core
def testFmtToOdt_Fields(mockGUI):
"""Test formatting of footnotes."""
project = NWProject()
odt = ToOdt(project, True)
odt.initDocument()
# Field Builder
xNode = odt._generateField("a:allWords", 0x00)
assert isinstance(xNode, ET.Element)
assert xmlToText(xNode) == (
''
'0'
)
# assert odt._usedFields == [(xNode.find(_wTag("t")), "b")]
assert odt._generateField("a", 0x00) is None
# Full Processing
odt._text = (
"Word Count: [field:allWords]\n"
"Character Count: [field:allChars]\n"
"Chicken Count: [field:allChickens]\n"
)
odt.tokenizeText()
odt.doConvert()
odt.countStats()
assert xmlToText(odt._xBody) == (
''
''
'Word Count: 0'
'Character Count: 0'
'Chicken Count: 0'
''
)
@pytest.mark.core
def testFmtToOdt_DialogueFormatting(mockGUI):
"""Test formatting of dialogue."""
project = NWProject()
odt = ToOdt(project, isFlat=True)
odt.setDialogHighlight(True)
odt.initDocument()
oStyle = ODTParagraphStyle("test")
# Regular dialogue
text = "Text with 'dialogue in it.'"
fmt = [(10, TextFmt.COL_B, "dialog"), (27, TextFmt.COL_E, "")]
xTest = ET.Element(_mkTag("office", "text"))
odt._addTextPar(xTest, "Standard", oStyle, text, tFmt=fmt)
assert odt.errData == []
assert xmlToText(xTest) == (
''
'Text with '
'\'dialogue in it.\''
''
)
# Alternative dialogue
text = "Text with ::dialogue in it.::"
fmt = [(10, TextFmt.COL_B, "altdialog"), (29, TextFmt.COL_E, "")]
xTest = ET.Element(_mkTag("office", "text"))
odt._addTextPar(xTest, "Standard", oStyle, text, tFmt=fmt)
assert odt.errData == []
assert xmlToText(xTest) == (
''
'Text with '
'::dialogue in it.::'
''
)
@pytest.mark.core
def testFmtToOdt_ConvertNovelHeadings(mockGUI):
"""Test the novel heading converter of the ToOdt class."""
project = NWProject()
odt = ToOdt(project, isFlat=True)
odt._isNovel = True
# Title
odt._isFirst = True
odt._text = "#! Title\n"
odt.tokenizeText()
odt.initDocument()
odt.doConvert()
odt.closeDocument()
assert odt.errData == []
assert xmlToText(odt._xText) == (
''
'Title'
''
)
# Partition
odt._text = "# Title\n"
odt.setPartitionFormat(f"Part{nwHeadFmt.BR}{nwHeadFmt.TITLE}")
odt.tokenizeText()
odt.initDocument()
odt.doConvert()
odt.closeDocument()
assert odt.errData == []
assert xmlToText(odt._xText) == (
''
'PartTitle'
''
)
# Chapter
odt._text = "## Title\n"
odt.setChapterFormat(f"Chapter {nwHeadFmt.CH_NUM}{nwHeadFmt.BR}{nwHeadFmt.TITLE}")
odt.tokenizeText()
odt.initDocument()
odt.doConvert()
odt.closeDocument()
assert odt.errData == []
assert xmlToText(odt._xText) == (
''
'Chapter 1'
'Title'
''
)
# Unnumbered Chapter
odt._text = "##! Prologue\n"
odt.tokenizeText()
odt.initDocument()
odt.doConvert()
odt.closeDocument()
assert odt.errData == []
assert xmlToText(odt._xText) == (
''
'Prologue'
''
)
# Scene
odt._text = "### Title\n"
odt.setSceneFormat(f"Scene {nwHeadFmt.SC_ABS}{nwHeadFmt.BR}{nwHeadFmt.TITLE}")
odt.tokenizeText()
odt.initDocument()
odt.doConvert()
odt.closeDocument()
assert odt.errData == []
assert xmlToText(odt._xText) == (
''
'Scene 1'
'Title'
''
)
# Alt. Scene
odt._text = "###! Title\n"
odt.setHardSceneFormat(f"Scene {nwHeadFmt.SC_ABS}{nwHeadFmt.BR}{nwHeadFmt.TITLE}")
odt.tokenizeText()
odt.initDocument()
odt.doConvert()
odt.closeDocument()
assert odt.errData == []
assert xmlToText(odt._xText) == (
''
'Scene 2'
'Title'
''
)
# Section
odt._text = "#### Title\n"
odt.tokenizeText()
odt.initDocument()
odt.doConvert()
odt.closeDocument()
assert odt.errData == []
assert xmlToText(odt._xText) == (
''
'Title'
''
)
@pytest.mark.core
def testFmtToOdt_ConvertNotesHeadings(mockGUI):
"""Test the notes headings converter of the ToOdt class."""
project = NWProject()
odt = ToOdt(project, isFlat=True)
odt._isNovel = False
# Header 1
odt._text = "# Title\n"
odt.tokenizeText()
odt.initDocument()
odt.doConvert()
odt.closeDocument()
assert odt.errData == []
assert xmlToText(odt._xText) == (
''
'Title'
''
)
# Header 2
odt._text = "## Title\n"
odt.tokenizeText()
odt.initDocument()
odt.doConvert()
odt.closeDocument()
assert odt.errData == []
assert xmlToText(odt._xText) == (
''
'Title'
''
)
# Header 3
odt._text = "### Title\n"
odt.tokenizeText()
odt.initDocument()
odt.doConvert()
odt.closeDocument()
assert odt.errData == []
assert xmlToText(odt._xText) == (
''
'Title'
''
)
# Header 4
odt._text = "#### Title\n"
odt.tokenizeText()
odt.initDocument()
odt.doConvert()
odt.closeDocument()
assert odt.errData == []
assert xmlToText(odt._xText) == (
''
'Title'
''
)
@pytest.mark.core
def testFmtToOdt_ConvertParagraphs(mockGUI):
"""Test the converter of the ToOdt class."""
project = NWProject()
odt = ToOdt(project, isFlat=True)
odt._isNovel = True
def getStyle(styleName):
for style in odt._autoPara.values():
if style.name == styleName:
return style
return None
# Nested Markdown Text
odt._text = "Some ~~nested **bold** and _italics_ text~~ text."
odt.tokenizeText()
odt.initDocument()
odt.doConvert()
odt.closeDocument()
assert odt.errData == []
assert xmlToText(odt._xText) == (
''
'Some '
'nested '
'bold'
' and '
'italics'
' text text.'
''
)
# Nested Shortcode Text, Emphasis
odt._text = "Some [s]nested [b]bold[/b] [u]and[/u] [i]italics[/i] text[/s] text."
odt.tokenizeText()
odt.initDocument()
odt.doConvert()
odt.closeDocument()
assert odt.errData == []
assert xmlToText(odt._xText) == (
''
'Some '
'nested '
'bold'
' '
'and'
' '
'italics'
' text text.'
''
)
# Shortcode Text, Super/Subscript
odt._text = "Some super[sup]script[/sup] and sub[sub]script[/sub] text."
odt.tokenizeText()
odt.initDocument()
odt.doConvert()
odt.closeDocument()
assert odt.errData == []
assert xmlToText(odt._xText) == (
''
'Some '
'superscript and '
'subscript text.'
''
)
# Shortcode Text, Underline/Highlight
odt._text = "Some [u]underlined and [m]highlighted[/m][/u] text."
odt.tokenizeText()
odt.initDocument()
odt.doConvert()
odt.closeDocument()
assert odt.errData == []
assert xmlToText(odt._xText) == (
''
'Some '
'underlined and '
'highlighted text.'
''
)
# Hard Break
odt._text = "Some text.\nNext line\n"
odt.tokenizeText()
odt.initDocument()
odt.doConvert()
odt.closeDocument()
assert odt.errData == []
assert xmlToText(odt._xText) == (
''
'Some text.Next line'
''
)
# Tab
odt._text = "\tItem 1\tItem 2\n"
odt.tokenizeText()
odt.initDocument()
odt.doConvert()
odt.closeDocument()
assert odt.errData == []
assert xmlToText(odt._xText) == (
''
'Item 1Item 2'
''
)
# Tab in Format
odt._text = "Some **bold\ttext**"
odt.tokenizeText()
odt.initDocument()
odt.doConvert()
odt.closeDocument()
assert odt.errData == []
assert xmlToText(odt._xText) == (
''
'Some '
'boldtext'
''
)
# Multiple Spaces
odt._text = (
"### Scene\n\n"
"Hello World\n\n"
"Hello World\n\n"
"Hello World\n\n"
)
odt.tokenizeText()
odt.initDocument()
odt.doConvert()
odt.closeDocument()
assert odt.errData == []
assert xmlToText(odt._xText) == (
''
'Scene'
'Hello World'
'Hello World'
'Hello World'
''
)
# Synopsis, Short, Comment, Keywords
odt._text = (
"### Scene\n\n"
"@pov: Jane\n\n"
"% synopsis: So it begins\n\n"
"% short: Then what\n\n"
"% A plain comment\n\n"
)
odt.setCommentType(nwComment.SYNOPSIS, True)
odt.setCommentType(nwComment.SHORT, True)
odt.setCommentType(nwComment.PLAIN, True)
odt.setKeywords(True)
odt.tokenizeText()
odt.initDocument()
odt.doConvert()
odt.closeDocument()
assert odt.errData == []
assert xmlToText(odt._xText) == (
''
'Scene'
''
'Point of View: Jane'
''
'Synopsis: '
'So it begins'
''
'Short Description: '
'Then what'
''
'Comment: '
'A plain comment'
''
)
# Scene Separator
odt._text = "### Scene One\n\nText\n\n### Scene Two\n\nText"
odt.setSceneFormat("* * *", False)
odt.tokenizeText()
odt.initDocument()
odt.doConvert()
odt.closeDocument()
assert odt.errData == []
assert xmlToText(odt._xText) == (
''
'* * *'
'Text'
'* * *'
'Text'
''
)
# Scene Break
odt._text = "### Scene One\n\nText\n\n### Scene Two\n\nText"
odt.setSceneFormat("", False)
odt.tokenizeText()
odt.initDocument()
odt.doConvert()
odt.closeDocument()
assert odt.errData == []
assert xmlToText(odt._xText) == (
''
''
'Text'
''
'Text'
''
)
# Paragraph Styles
odt._text = (
"### Scene\n\n"
"@pov: Jane\n"
"@char: John\n"
"@plot: Main\n\n"
">> Right align\n\n"
"Left Align <<\n\n"
">> Centered <<\n\n"
"> Left indent\n\n"
"Right indent <\n\n"
)
odt.setSceneFormat(nwHeadFmt.TITLE, False)
odt.setKeywords(True)
odt.tokenizeText()
odt.initDocument()
odt.doConvert()
odt.closeDocument()
assert odt.errData == []
assert xmlToText(odt._xText) == (
''
'Scene'
''
'Point of View: Jane'
''
'Characters: John'
''
'Plot: Main'
'Right align'
'Left Align'
'Centered'
'Left indent'
'Right indent'
''
)
assert getStyle("P1")._pAttr["margin-bottom"] == ["fo", "0.000cm"] # type: ignore
assert getStyle("P2")._pAttr["margin-bottom"] == ["fo", "0.000cm"] # type: ignore
assert getStyle("P2")._pAttr["margin-top"] == ["fo", "0.000cm"] # type: ignore
assert getStyle("P3")._pAttr["text-align"] == ["fo", "right"] # type: ignore
assert getStyle("P4")._pAttr["text-align"] == ["fo", "center"] # type: ignore
assert getStyle("P5")._pAttr["margin-left"] == ["fo", "1.693cm"] # type: ignore
assert getStyle("P6")._pAttr["margin-right"] == ["fo", "1.693cm"] # type: ignore
# Justified
odt._text = (
"### Scene\n\n"
"Regular paragraph\n\n"
"with\nbreak\n\n"
"Left Align <<\n\n"
)
odt.setJustify(True)
odt.tokenizeText()
odt.initDocument()
odt.doConvert()
odt.closeDocument()
odt.setJustify(False)
assert odt.errData == []
assert xmlToText(odt._xText) == (
''
'Scene'
'Regular paragraph'
'withbreak'
'Left Align'
''
)
assert getStyle("P7")._pAttr["text-align"] == ["fo", "justify"] # type: ignore
# Page Breaks
odt._text = (
"## Chapter One\n\n"
"Text\n\n"
"## Chapter Two\n\n"
"Text\n\n"
)
odt.tokenizeText()
odt.initDocument()
odt.doConvert()
odt.closeDocument()
assert odt.errData == []
assert xmlToText(odt._xText) == (
''
'Chapter One'
'Text'
'Chapter Two'
'Text'
''
)
# Footnotes
odt._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"
)
odt.tokenizeText()
odt.initDocument()
odt.doConvert()
odt.closeDocument()
assert xmlToText(odt._xText) == (
''
'Text with one'
''
'1'
''
'Footnote text A.'
''
', two'
''
'2'
''
'Another footnote.'
''
', or three footnotes.'
''
'3'
''
'Again?'
''
''
''
)
# Test for issue #1412
# ====================
# See: https://github.com/vkbo/novelWriter/issues/1412
odt._text = "Test text \\**_bold_** and more."
odt.tokenizeText()
odt.initDocument()
odt.doConvert()
odt.closeDocument()
assert odt.errData == []
assert xmlToText(odt._xText) == (
''
'Test text **'
'bold** and more.'
''
)
@pytest.mark.core
def testFmtToOdt_ConvertDirect(mockGUI):
"""Test the converter directly using the ToOdt class to reach some
otherwise hard to reach conditions.
"""
project = NWProject()
doc = ToOdt(project, isFlat=True)
doc._isNovel = True
# Justified
doc = ToOdt(project, isFlat=True)
doc._blocks = [
(BlockTyp.TEXT, "", "This is a paragraph", [], BlockFmt.JUSTIFY),
]
doc.initDocument()
doc.doConvert()
doc.closeDocument()
assert (
''
''
''
) in xmlToText(doc._xAuto)
assert xmlToText(doc._xText) == (
''
'This is a paragraph'
''
)
# Page Break After
doc = ToOdt(project, isFlat=True)
doc._blocks = [
(BlockTyp.TEXT, "", "This is a paragraph", [], BlockFmt.PBA),
]
doc.initDocument()
doc.doConvert()
doc.closeDocument()
assert (
''
''
''
) in xmlToText(doc._xAuto)
assert xmlToText(doc._xText) == (
''
'This is a paragraph'
''
)
@pytest.mark.core
def testFmtToOdt_SaveFlat(mockGUI, fncPath, tstPaths, ipsumText):
"""Test the document save functions."""
project = NWProject()
project.data.setAuthor("Jane Smith")
project.data.setName("Test Project")
project.data.setSaveCount(1234)
project.data.setEditTime(3674096)
odt = ToOdt(project, isFlat=True)
odt._isNovel = True
odt.setHeaderFormat(nwHeadFmt.DOC_AUTO, 1)
assert odt._headerFormat == nwHeadFmt.DOC_AUTO
odt.setPageLayout(148, 210, 20, 18, 17, 15)
assert odt._mDocWidth == "14.800cm"
assert odt._mDocHeight == "21.000cm"
assert odt._mDocTop == "2.000cm"
assert odt._mDocBtm == "1.800cm"
assert odt._mDocLeft == "1.700cm"
assert odt._mDocRight == "1.500cm"
odt._text = (
"#! My Novel\n\n"
"**Word Count: [field:allWords]**\n"
"[field:paragraphCount] paragrphs\n"
"Web: http://example.com\n\n"
"## Chapter One\n\n"
f"{ipsumText[0]}\n\n"
"## Chapter Two\n\n"
f"{ipsumText[1]}[footnote:abc]\n\n"
"%Footnote.abc: Lorem ipsum\n\n"
)
odt.tokenizeText()
odt.initDocument()
odt.doConvert()
odt.countStats()
odt.closeDocument()
flatFile = fncPath / "document.fodt"
testFile = tstPaths.outDir / "fmtToOdt_SaveFlat_document.fodt"
compFile = tstPaths.refDir / "fmtToOdt_SaveFlat_document.fodt"
odt.saveDocument(flatFile)
assert flatFile.exists()
copyfile(flatFile, testFile)
assert cmpFiles(testFile, compFile, ignoreStart=ODT_IGNORE)
@pytest.mark.core
def testFmtToOdt_SaveFull(mockGUI, fncPath, tstPaths, ipsumText):
"""Test the document save functions."""
project = NWProject()
project.data.setAuthor("Jane Smith")
project.data.setName("Test Project")
project.data.setSaveCount(1234)
project.data.setEditTime(3674096)
odt = ToOdt(project, isFlat=False)
odt._isNovel = True
# Set a format without page number
odt.setHeaderFormat(f"{nwHeadFmt.DOC_PROJECT} - {nwHeadFmt.DOC_AUTHOR}", 0)
odt._text = (
"#! My Novel\n\n"
"**Word Count: [field:allWords]**\n"
"[field:paragraphCount] paragrphs\n"
"Web: http://example.com\n\n"
"## Chapter One\n\n"
f"{ipsumText[0]}\n\n"
"## Chapter Two\n\n"
f"{ipsumText[1]}[footnote:abc]\n\n"
"%Footnote.abc: Lorem ipsum\n\n"
)
odt.tokenizeText()
odt.initDocument()
odt.doConvert()
odt.countStats()
odt.closeDocument()
fullFile = fncPath / "document.odt"
odt.saveDocument(fullFile)
assert fullFile.exists()
assert zipfile.is_zipfile(fullFile)
maniFile = tstPaths.outDir / "fmtToOdt_SaveFull_manifest.xml"
settFile = tstPaths.outDir / "fmtToOdt_SaveFull_settings.xml"
contFile = tstPaths.outDir / "fmtToOdt_SaveFull_content.xml"
metaFile = tstPaths.outDir / "fmtToOdt_SaveFull_meta.xml"
stylFile = tstPaths.outDir / "fmtToOdt_SaveFull_styles.xml"
maniComp = tstPaths.refDir / "fmtToOdt_SaveFull_manifest.xml"
settComp = tstPaths.refDir / "fmtToOdt_SaveFull_settings.xml"
contComp = tstPaths.refDir / "fmtToOdt_SaveFull_content.xml"
metaComp = tstPaths.refDir / "fmtToOdt_SaveFull_meta.xml"
stylComp = tstPaths.refDir / "fmtToOdt_SaveFull_styles.xml"
extaxtTo = tstPaths.outDir / "fmtToOdt_SaveFull"
with zipfile.ZipFile(fullFile, mode="r") as zipObj:
zipObj.extract("META-INF/manifest.xml", extaxtTo)
zipObj.extract("settings.xml", extaxtTo)
zipObj.extract("content.xml", extaxtTo)
zipObj.extract("meta.xml", extaxtTo)
zipObj.extract("styles.xml", extaxtTo)
maniOut = tstPaths.outDir / "fmtToOdt_SaveFull" / "META-INF" / "manifest.xml"
settOut = tstPaths.outDir / "fmtToOdt_SaveFull" / "settings.xml"
contOut = tstPaths.outDir / "fmtToOdt_SaveFull" / "content.xml"
metaOut = tstPaths.outDir / "fmtToOdt_SaveFull" / "meta.xml"
stylOut = tstPaths.outDir / "fmtToOdt_SaveFull" / "styles.xml"
def prettifyXml(inFile, outFile):
with open(outFile, mode="wb") as fStream:
xml = ET.parse(inFile)
xmlIndent(xml.getroot())
xml.write(fStream, encoding="utf-8", xml_declaration=True)
prettifyXml(maniOut, maniFile)
prettifyXml(settOut, settFile)
prettifyXml(contOut, contFile)
prettifyXml(metaOut, metaFile)
prettifyXml(stylOut, stylFile)
assert cmpFiles(maniFile, maniComp)
assert cmpFiles(settFile, settComp)
assert cmpFiles(contFile, contComp)
assert cmpFiles(metaFile, metaComp, ignoreStart=ODT_IGNORE)
assert cmpFiles(stylFile, stylComp)
@pytest.mark.core
def testFmtToOdt_ODTParagraphStyle():
"""Test the ODTParagraphStyle class."""
parStyle = ODTParagraphStyle("test")
# Set Attributes
# ==============
# Display, Parent, Next Style
assert parStyle._mAttr["display-name"] == ["style", None]
assert parStyle._mAttr["parent-style-name"] == ["style", None]
assert parStyle._mAttr["next-style-name"] == ["style", None]
parStyle.setDisplayName("Name")
parStyle.setParentStyleName("Name")
parStyle.setNextStyleName("Name")
assert parStyle._mAttr["display-name"] == ["style", "Name"]
assert parStyle._mAttr["parent-style-name"] == ["style", "Name"]
assert parStyle._mAttr["next-style-name"] == ["style", "Name"]
# Outline Level
assert parStyle._mAttr["default-outline-level"] == ["style", None]
parStyle.setOutlineLevel("0")
assert parStyle._mAttr["default-outline-level"] == ["style", None]
parStyle.setOutlineLevel("1")
assert parStyle._mAttr["default-outline-level"] == ["style", "1"]
parStyle.setOutlineLevel("2")
assert parStyle._mAttr["default-outline-level"] == ["style", "2"]
parStyle.setOutlineLevel("3")
assert parStyle._mAttr["default-outline-level"] == ["style", "3"]
parStyle.setOutlineLevel("4")
assert parStyle._mAttr["default-outline-level"] == ["style", "4"]
parStyle.setOutlineLevel("5")
assert parStyle._mAttr["default-outline-level"] == ["style", None]
# Class
assert parStyle._mAttr["class"] == ["style", None]
parStyle.setClass("stuff")
assert parStyle._mAttr["class"] == ["style", None]
parStyle.setClass("text")
assert parStyle._mAttr["class"] == ["style", "text"]
parStyle.setClass("chapter")
assert parStyle._mAttr["class"] == ["style", "chapter"]
parStyle.setClass("stuff")
assert parStyle._mAttr["class"] == ["style", None]
# Set Paragraph Style
# ===================
# Margins & Line Height
assert parStyle._pAttr["margin-top"] == ["fo", None]
assert parStyle._pAttr["margin-bottom"] == ["fo", None]
assert parStyle._pAttr["margin-left"] == ["fo", None]
assert parStyle._pAttr["margin-right"] == ["fo", None]
assert parStyle._pAttr["text-indent"] == ["fo", None]
assert parStyle._pAttr["line-height"] == ["fo", None]
parStyle.setMarginTop("0.000cm")
parStyle.setMarginBottom("0.000cm")
parStyle.setMarginLeft("0.000cm")
parStyle.setMarginRight("0.000cm")
parStyle.setTextIndent("0.000cm")
parStyle.setLineHeight("1.15")
assert parStyle._pAttr["margin-top"] == ["fo", "0.000cm"]
assert parStyle._pAttr["margin-bottom"] == ["fo", "0.000cm"]
assert parStyle._pAttr["margin-left"] == ["fo", "0.000cm"]
assert parStyle._pAttr["margin-right"] == ["fo", "0.000cm"]
assert parStyle._pAttr["text-indent"] == ["fo", "0.000cm"]
assert parStyle._pAttr["line-height"] == ["fo", "1.15"]
# Text Alignment
assert parStyle._pAttr["text-align"] == ["fo", None]
parStyle.setTextAlign("stuff")
assert parStyle._pAttr["text-align"] == ["fo", None]
parStyle.setTextAlign("start")
assert parStyle._pAttr["text-align"] == ["fo", "start"]
parStyle.setTextAlign("center")
assert parStyle._pAttr["text-align"] == ["fo", "center"]
parStyle.setTextAlign("end")
assert parStyle._pAttr["text-align"] == ["fo", "end"]
parStyle.setTextAlign("justify")
assert parStyle._pAttr["text-align"] == ["fo", "justify"]
parStyle.setTextAlign("left")
assert parStyle._pAttr["text-align"] == ["fo", "left"]
parStyle.setTextAlign("right")
assert parStyle._pAttr["text-align"] == ["fo", "right"]
parStyle.setTextAlign("stuff")
assert parStyle._pAttr["text-align"] == ["fo", None]
# Break Before
assert parStyle._pAttr["break-before"] == ["fo", None]
parStyle.setBreakBefore("stuff")
assert parStyle._pAttr["break-before"] == ["fo", None]
parStyle.setBreakBefore("auto")
assert parStyle._pAttr["break-before"] == ["fo", "auto"]
parStyle.setBreakBefore("page")
assert parStyle._pAttr["break-before"] == ["fo", "page"]
parStyle.setBreakBefore("even-page")
assert parStyle._pAttr["break-before"] == ["fo", "even-page"]
parStyle.setBreakBefore("odd-page")
assert parStyle._pAttr["break-before"] == ["fo", "odd-page"]
parStyle.setBreakBefore("inherit")
assert parStyle._pAttr["break-before"] == ["fo", "inherit"]
parStyle.setBreakBefore("stuff")
assert parStyle._pAttr["break-before"] == ["fo", None]
# Break After
assert parStyle._pAttr["break-after"] == ["fo", None]
parStyle.setBreakAfter("stuff")
assert parStyle._pAttr["break-after"] == ["fo", None]
parStyle.setBreakAfter("auto")
assert parStyle._pAttr["break-after"] == ["fo", "auto"]
parStyle.setBreakAfter("page")
assert parStyle._pAttr["break-after"] == ["fo", "page"]
parStyle.setBreakAfter("even-page")
assert parStyle._pAttr["break-after"] == ["fo", "even-page"]
parStyle.setBreakAfter("odd-page")
assert parStyle._pAttr["break-after"] == ["fo", "odd-page"]
parStyle.setBreakAfter("inherit")
assert parStyle._pAttr["break-after"] == ["fo", "inherit"]
parStyle.setBreakAfter("stuff")
assert parStyle._pAttr["break-after"] == ["fo", None]
# Text Attributes
# ===============
# Font Name, Family and Size
assert parStyle._tAttr["font-name"] == ["style", None]
assert parStyle._tAttr["font-family"] == ["fo", None]
assert parStyle._tAttr["font-size"] == ["fo", None]
parStyle.setFontName("Verdana")
parStyle.setFontFamily("Verdana")
parStyle.setFontSize("12pt")
assert parStyle._tAttr["font-name"] == ["style", "Verdana"]
assert parStyle._tAttr["font-family"] == ["fo", "Verdana"]
assert parStyle._tAttr["font-size"] == ["fo", "12pt"]
# Font Weight
assert parStyle._tAttr["font-weight"] == ["fo", None]
parStyle.setFontWeight("stuff")
assert parStyle._tAttr["font-weight"] == ["fo", None]
parStyle.setFontWeight("normal")
assert parStyle._tAttr["font-weight"] == ["fo", "normal"]
parStyle.setFontWeight("bold")
assert parStyle._tAttr["font-weight"] == ["fo", "bold"]
parStyle.setFontWeight("100")
assert parStyle._tAttr["font-weight"] == ["fo", "100"]
parStyle.setFontWeight("200")
assert parStyle._tAttr["font-weight"] == ["fo", "200"]
parStyle.setFontWeight("300")
assert parStyle._tAttr["font-weight"] == ["fo", "300"]
parStyle.setFontWeight("400")
assert parStyle._tAttr["font-weight"] == ["fo", "400"]
parStyle.setFontWeight("500")
assert parStyle._tAttr["font-weight"] == ["fo", "500"]
parStyle.setFontWeight("600")
assert parStyle._tAttr["font-weight"] == ["fo", "600"]
parStyle.setFontWeight("700")
assert parStyle._tAttr["font-weight"] == ["fo", "700"]
parStyle.setFontWeight("800")
assert parStyle._tAttr["font-weight"] == ["fo", "800"]
parStyle.setFontWeight("900")
assert parStyle._tAttr["font-weight"] == ["fo", "900"]
parStyle.setFontWeight("stuff")
assert parStyle._tAttr["font-weight"] == ["fo", None]
# Colour & Opacity
assert parStyle._tAttr["color"] == ["fo", None]
assert parStyle._tAttr["opacity"] == ["loext", None]
parStyle.setColor(QColor(0, 0, 0, 128))
assert parStyle._tAttr["color"] == ["fo", "#000000"]
assert parStyle._tAttr["opacity"] == ["loext", "50%"]
parStyle.setColor(None)
assert parStyle._tAttr["color"] == ["fo", None]
assert parStyle._tAttr["opacity"] == ["loext", None]
# Pack XML
# ========
xStyle = ET.Element("test")
parStyle.packXML(xStyle)
assert xmlToText(xStyle) == (
''
''
''
''
''
''
)
# Changes
# =======
aStyle = ODTParagraphStyle("test")
oStyle = ODTParagraphStyle("test")
assert aStyle.checkNew(oStyle) is False
assert aStyle.getID() == oStyle.getID()
aStyle.setDisplayName("Name1")
oStyle.setDisplayName("Name2")
assert aStyle.checkNew(oStyle) is True
assert aStyle.getID() != oStyle.getID()
aStyle = ODTParagraphStyle("test")
oStyle = ODTParagraphStyle("test")
aStyle.setMarginTop("0.000cm")
oStyle.setMarginTop("1.000cm")
assert aStyle.checkNew(oStyle) is True
assert aStyle.getID() != oStyle.getID()
aStyle = ODTParagraphStyle("test")
oStyle = ODTParagraphStyle("test")
aStyle.setColor(QColor(0, 0, 0))
oStyle.setColor(QColor(42, 42, 42))
assert aStyle.checkNew(oStyle) is True
assert aStyle.getID() != oStyle.getID()
@pytest.mark.core
def testFmtToOdt_ODTTextStyle():
"""Test the ODTTextStyle class."""
txtStyle = ODTTextStyle("test")
# Font Weight
assert txtStyle._tAttr["font-weight"] == ["fo", None]
txtStyle.setFontWeight("stuff")
assert txtStyle._tAttr["font-weight"] == ["fo", None]
txtStyle.setFontWeight("normal")
assert txtStyle._tAttr["font-weight"] == ["fo", "normal"]
txtStyle.setFontWeight("bold")
assert txtStyle._tAttr["font-weight"] == ["fo", "bold"]
txtStyle.setFontWeight("100")
assert txtStyle._tAttr["font-weight"] == ["fo", "100"]
txtStyle.setFontWeight("200")
assert txtStyle._tAttr["font-weight"] == ["fo", "200"]
txtStyle.setFontWeight("300")
assert txtStyle._tAttr["font-weight"] == ["fo", "300"]
txtStyle.setFontWeight("400")
assert txtStyle._tAttr["font-weight"] == ["fo", "400"]
txtStyle.setFontWeight("500")
assert txtStyle._tAttr["font-weight"] == ["fo", "500"]
txtStyle.setFontWeight("600")
assert txtStyle._tAttr["font-weight"] == ["fo", "600"]
txtStyle.setFontWeight("700")
assert txtStyle._tAttr["font-weight"] == ["fo", "700"]
txtStyle.setFontWeight("800")
assert txtStyle._tAttr["font-weight"] == ["fo", "800"]
txtStyle.setFontWeight("900")
assert txtStyle._tAttr["font-weight"] == ["fo", "900"]
txtStyle.setFontWeight("stuff")
assert txtStyle._tAttr["font-weight"] == ["fo", None]
# Font Style
assert txtStyle._tAttr["font-style"] == ["fo", None]
txtStyle.setFontStyle("stuff")
assert txtStyle._tAttr["font-style"] == ["fo", None]
txtStyle.setFontStyle("normal")
assert txtStyle._tAttr["font-style"] == ["fo", "normal"]
txtStyle.setFontStyle("italic")
assert txtStyle._tAttr["font-style"] == ["fo", "italic"]
txtStyle.setFontStyle("oblique")
assert txtStyle._tAttr["font-style"] == ["fo", "oblique"]
txtStyle.setFontStyle("stuff")
assert txtStyle._tAttr["font-style"] == ["fo", None]
# Text Color
assert txtStyle._tAttr["color"] == ["fo", None]
txtStyle.setColor("#012345") # type: ignore
assert txtStyle._tAttr["color"] == ["fo", None]
txtStyle.setColor(QColor(255, 128, 0))
assert txtStyle._tAttr["color"] == ["fo", "#ff8000"]
txtStyle.setColor(None)
assert txtStyle._tAttr["color"] == ["fo", None]
# Background Color
assert txtStyle._tAttr["background-color"] == ["fo", None]
txtStyle.setBackgroundColor("#012345") # type: ignore
assert txtStyle._tAttr["background-color"] == ["fo", None]
txtStyle.setBackgroundColor(QColor(255, 128, 0))
assert txtStyle._tAttr["background-color"] == ["fo", "#ff8000"]
txtStyle.setBackgroundColor(None)
assert txtStyle._tAttr["background-color"] == ["fo", None]
# Text Position
assert txtStyle._tAttr["text-position"] == ["style", None]
txtStyle.setTextPosition("stuff")
assert txtStyle._tAttr["text-position"] == ["style", None]
txtStyle.setTextPosition("super")
assert txtStyle._tAttr["text-position"] == ["style", "super 58%"]
txtStyle.setTextPosition("sub")
assert txtStyle._tAttr["text-position"] == ["style", "sub 58%"]
txtStyle.setTextPosition("stuff")
assert txtStyle._tAttr["text-position"] == ["style", None]
# Line Through Style
assert txtStyle._tAttr["text-line-through-style"] == ["style", None]
txtStyle.setStrikeStyle("stuff")
assert txtStyle._tAttr["text-line-through-style"] == ["style", None]
txtStyle.setStrikeStyle("none")
assert txtStyle._tAttr["text-line-through-style"] == ["style", "none"]
txtStyle.setStrikeStyle("solid")
assert txtStyle._tAttr["text-line-through-style"] == ["style", "solid"]
txtStyle.setStrikeStyle("stuff")
assert txtStyle._tAttr["text-line-through-style"] == ["style", None]
# Line Through Type
assert txtStyle._tAttr["text-line-through-type"] == ["style", None]
txtStyle.setStrikeType("stuff")
assert txtStyle._tAttr["text-line-through-type"] == ["style", None]
txtStyle.setStrikeType("none") # Deprecated in ODF 1.3
assert txtStyle._tAttr["text-line-through-type"] == ["style", None]
txtStyle.setStrikeType("single")
assert txtStyle._tAttr["text-line-through-type"] == ["style", "single"]
txtStyle.setStrikeType("double")
assert txtStyle._tAttr["text-line-through-type"] == ["style", "double"]
txtStyle.setStrikeType("stuff")
assert txtStyle._tAttr["text-line-through-type"] == ["style", None]
# Underline Style
assert txtStyle._tAttr["text-underline-style"] == ["style", None]
txtStyle.setUnderlineStyle("stuff")
assert txtStyle._tAttr["text-underline-style"] == ["style", None]
txtStyle.setUnderlineStyle("none")
assert txtStyle._tAttr["text-underline-style"] == ["style", "none"]
txtStyle.setUnderlineStyle("solid")
assert txtStyle._tAttr["text-underline-style"] == ["style", "solid"]
txtStyle.setUnderlineStyle("stuff")
assert txtStyle._tAttr["text-underline-style"] == ["style", None]
# Underline Width
assert txtStyle._tAttr["text-underline-width"] == ["style", None]
txtStyle.setUnderlineWidth("stuff")
assert txtStyle._tAttr["text-underline-width"] == ["style", None]
txtStyle.setUnderlineWidth("auto")
assert txtStyle._tAttr["text-underline-width"] == ["style", "auto"]
txtStyle.setUnderlineWidth("stuff")
assert txtStyle._tAttr["text-underline-width"] == ["style", None]
# Underline Colour
assert txtStyle._tAttr["text-underline-color"] == ["style", None]
txtStyle.setUnderlineColor("stuff")
assert txtStyle._tAttr["text-underline-color"] == ["style", None]
txtStyle.setUnderlineColor("font-color")
assert txtStyle._tAttr["text-underline-color"] == ["style", "font-color"]
txtStyle.setUnderlineColor("stuff")
assert txtStyle._tAttr["text-underline-color"] == ["style", None]
# Pack XML
# ========
txtStyle.setFontWeight("bold")
txtStyle.setFontStyle("italic")
txtStyle.setTextPosition("super")
txtStyle.setStrikeStyle("solid")
txtStyle.setStrikeType("single")
xStyle = ET.Element("test")
txtStyle.packXML(xStyle)
assert xmlToText(xStyle) == (
''
''
)
@pytest.mark.core
def testFmtToOdt_XMLParagraph():
"""Test XML encoding of paragraph."""
# Stage 1 : Text
# ==============
xRoot = ET.Element("root")
xElem = ET.SubElement(xRoot, _mkTag("text", "p"))
xmlPar = XMLParagraph(xElem)
# Plain Text
xmlPar.appendText("Hello World")
assert xmlToText(xRoot) == (
""
"Hello World"
""
)
# Text Span
xmlPar.appendSpan("spanned text", "T1", "")
assert xmlToText(xRoot) == (
''
'Hello World'
'spanned text'
''
''
)
# Tail Text
xmlPar.appendText("more text")
assert xmlToText(xRoot) == (
''
'Hello World'
'spanned text'
'more text'
''
)
assert xmlPar.checkError() == (0, "")
# Stage 2 : Line Breaks
# =====================
xRoot = ET.Element("root")
xElem = ET.SubElement(xRoot, _mkTag("text", "p"))
xmlPar = XMLParagraph(xElem)
# Plain Text w/Line Break
xmlPar.appendText("Hello\nWorld\n!!")
assert xmlToText(xRoot) == (
""
"HelloWorld!!"
""
)
# Text Span w/Line Break
xmlPar.appendSpan("spanned\ntext", "T1", "")
assert xmlToText(xRoot) == (
''
'HelloWorld!!'
'spannedtext'
''
)
# Tail Text w/Line Break
xmlPar.appendText("more\ntext")
assert xmlToText(xRoot) == (
''
'HelloWorld!!'
'spannedtext'
'moretext'
''
)
assert xmlPar.checkError() == (0, "")
# Stage 3 : Tabs
# ==============
xRoot = ET.Element("root")
xElem = ET.SubElement(xRoot, _mkTag("text", "p"))
xmlPar = XMLParagraph(xElem)
# Plain Text w/Line Break
xmlPar.appendText("Hello\tWorld\t!!")
assert xmlToText(xRoot) == (
""
"HelloWorld!!"
""
)
# Text Span w/Line Break
xmlPar.appendSpan("spanned\ttext", "T1", "")
assert xmlToText(xRoot) == (
''
'HelloWorld!!'
'spannedtext'
''
)
# Tail Text w/Line Break
xmlPar.appendText("more\ttext")
assert xmlToText(xRoot) == (
''
'HelloWorld!!'
'spannedtext'
'moretext'
''
)
# Tail Text w/Link
xmlPar.appendSpan("Example", "T1", "http://www.example.com")
assert xmlToText(xRoot) == (
''
'HelloWorld!!'
'spannedtext'
'moretext'
''
'Example'
''
)
assert xmlPar.checkError() == (0, "")
# Stage 4 : Spaces
# ================
xRoot = ET.Element("root")
xElem = ET.SubElement(xRoot, _mkTag("text", "p"))
xmlPar = XMLParagraph(xElem)
# Plain Text w/Spaces
xmlPar.appendText("Hello World !!")
assert xmlToText(xRoot) == (
''
'Hello World !!'
''
)
# Text Span w/Spaces
xmlPar.appendSpan("spanned text", "T1", "")
assert xmlToText(xRoot) == (
''
'Hello World !!'
'spanned text'
''
)
# Tail Text w/Spaces
xmlPar.appendText("more text")
assert xmlToText(xRoot) == (
''
'Hello World !!'
'spanned text'
'more text'
''
)
assert xmlPar.checkError() == (0, "")
# Stage 5 : Lots of Spaces
# ========================
xRoot = ET.Element("root")
xElem = ET.SubElement(xRoot, _mkTag("text", "p"))
xmlPar = XMLParagraph(xElem)
# Plain Text w/Many Spaces
xmlPar.appendText(" \t A \n B ")
assert xmlToText(xRoot) == (
''
' A B '
''
)
# Text Span w/Many Spaces
xmlPar.appendSpan(" C \t D \n E ", "T1", "")
assert xmlToText(xRoot) == (
''
' A B '
' C D '
' E '
''
)
assert xmlPar.checkError() == (0, "")
# Check Error
# ===========
xRoot = ET.Element("root")
xElem = ET.SubElement(xRoot, _mkTag("text", "p"))
xmlPar = XMLParagraph(xElem)
xmlPar.appendText("A")
xmlPar._nState = 5
xmlPar.appendText("B")
assert xmlPar.checkError() == (1, "1 char(s) were not written: 'AB'")
@pytest.mark.core
def testFmtToOdt_MkTag():
"""Test the tag maker function."""
assert _mkTag("office", "text") == "{urn:oasis:names:tc:opendocument:xmlns:office:1.0}text"
assert _mkTag("style", "text") == "{urn:oasis:names:tc:opendocument:xmlns:style:1.0}text"
assert _mkTag("blabla", "text") == "text"