Files
novelWriter/tests/test_formats/test_fmt_toodt.py
T
Veronica Berglyd Olsen 5bdbd7c195 Update linting for tests
2025-08-27 21:04:32 +02:00

1585 lines
54 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
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 <https://www.gnu.org/licenses/>.
""" # 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) == "<office:text />"
# 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) == (
'<office:text>'
'<text:p text:style-name="Standard" />'
'</office:text>'
)
# No Format
xTest = ET.Element(_mkTag("office", "text"))
odt._addTextPar(xTest, "Standard", oStyle, "Hello World")
assert odt.errData == []
assert xmlToText(xTest) == (
'<office:text>'
'<text:p text:style-name="Standard">Hello World</text:p>'
'</office:text>'
)
# Heading Level None
xTest = ET.Element(_mkTag("office", "text"))
odt._addTextPar(xTest, "Standard", oStyle, "Hello World", isHead=True)
assert odt.errData == []
assert xmlToText(xTest) == (
'<office:text>'
'<text:h text:style-name="Standard">Hello World</text:h>'
'</office:text>'
)
# 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) == (
'<office:text>'
'<text:h text:style-name="Standard" text:outline-level="1">Hello World</text:h>'
'</office:text>'
)
# 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) == (
'<office:text>'
'<text:p text:style-name="Standard">A <text:span text:style-name="T1">bold</text:span> '
'word</text:p>'
'</office:text>'
)
# 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) == (
'<office:text>'
'<text:p text:style-name="Standard">A <text:span text:style-name="T1">few</text:span> '
'words</text:p>'
'</office:text>'
)
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) == (
'<office:text>'
'<text:p text:style-name="Standard">A '
'<text:span text:style-name="T1">bold word</text:span></text:p>'
'</office:text>'
)
# 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) == (
'<office:text>'
'<text:p text:style-name="Standard">Hello<text:line-break /><text:tab />World</text:p>'
'</office:text>'
)
@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) == (
'<text:user-field-getstyle:data-style-name="N0" text:name="ManuscriptAllWords">'
'0</text:user-field-get>'
)
# 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) == (
'<office:body><office:text>'
'<text:p text:style-name="Text_20_body">'
'Word Count: <text:user-field-get style:data-style-name="N0" '
'text:name="ManuscriptAllWords">0</text:user-field-get><text:line-break />'
'Character Count: <text:user-field-get style:data-style-name="N0" '
'text:name="ManuscriptAllChars">0</text:user-field-get><text:line-break />'
'Chicken Count: <text:user-field-get style:data-style-name="N0" '
'text:name="ManuscriptAllChickens">0</text:user-field-get></text:p>'
'</office:text></office:body>'
)
@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) == (
'<office:text>'
'<text:p text:style-name="Standard">Text with '
'<text:span text:style-name="T1">\'dialogue in it.\'</text:span></text:p>'
'</office:text>'
)
# 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) == (
'<office:text>'
'<text:p text:style-name="Standard">Text with '
'<text:span text:style-name="T2">::dialogue in it.::</text:span></text:p>'
'</office:text>'
)
@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) == (
'<office:text>'
'<text:p text:style-name="Title">Title</text:p>'
'</office:text>'
)
# 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) == (
'<office:text>'
'<text:p text:style-name="P1">Part<text:line-break />Title</text:p>'
'</office:text>'
)
# 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) == (
'<office:text>'
'<text:h text:style-name="P2" text:outline-level="1">Chapter 1'
'<text:line-break />Title</text:h>'
'</office:text>'
)
# Unnumbered Chapter
odt._text = "##! Prologue\n"
odt.tokenizeText()
odt.initDocument()
odt.doConvert()
odt.closeDocument()
assert odt.errData == []
assert xmlToText(odt._xText) == (
'<office:text>'
'<text:h text:style-name="P2" text:outline-level="1">Prologue</text:h>'
'</office:text>'
)
# 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) == (
'<office:text>'
'<text:h text:style-name="Heading_20_2" text:outline-level="2">Scene 1'
'<text:line-break />Title</text:h>'
'</office:text>'
)
# 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) == (
'<office:text>'
'<text:h text:style-name="Heading_20_2" text:outline-level="2">Scene 2'
'<text:line-break />Title</text:h>'
'</office:text>'
)
# Section
odt._text = "#### Title\n"
odt.tokenizeText()
odt.initDocument()
odt.doConvert()
odt.closeDocument()
assert odt.errData == []
assert xmlToText(odt._xText) == (
'<office:text>'
'<text:h text:style-name="Heading_20_3" text:outline-level="3">Title</text:h>'
'</office:text>'
)
@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) == (
'<office:text>'
'<text:h text:style-name="Heading_20_1" text:outline-level="1">Title</text:h>'
'</office:text>'
)
# Header 2
odt._text = "## Title\n"
odt.tokenizeText()
odt.initDocument()
odt.doConvert()
odt.closeDocument()
assert odt.errData == []
assert xmlToText(odt._xText) == (
'<office:text>'
'<text:h text:style-name="Heading_20_2" text:outline-level="2">Title</text:h>'
'</office:text>'
)
# Header 3
odt._text = "### Title\n"
odt.tokenizeText()
odt.initDocument()
odt.doConvert()
odt.closeDocument()
assert odt.errData == []
assert xmlToText(odt._xText) == (
'<office:text>'
'<text:h text:style-name="Heading_20_3" text:outline-level="3">Title</text:h>'
'</office:text>'
)
# Header 4
odt._text = "#### Title\n"
odt.tokenizeText()
odt.initDocument()
odt.doConvert()
odt.closeDocument()
assert odt.errData == []
assert xmlToText(odt._xText) == (
'<office:text>'
'<text:h text:style-name="Heading_20_4" text:outline-level="4">Title</text:h>'
'</office:text>'
)
@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) == (
'<office:text>'
'<text:p text:style-name="Text_20_body">Some '
'<text:span text:style-name="T1">nested </text:span>'
'<text:span text:style-name="T2">bold</text:span>'
'<text:span text:style-name="T1"> and </text:span>'
'<text:span text:style-name="T3">italics</text:span>'
'<text:span text:style-name="T1"> text</text:span> text.</text:p>'
'</office: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) == (
'<office:text>'
'<text:p text:style-name="Text_20_body">Some '
'<text:span text:style-name="T1">nested </text:span>'
'<text:span text:style-name="T2">bold</text:span>'
'<text:span text:style-name="T1"> </text:span>'
'<text:span text:style-name="T4">and</text:span>'
'<text:span text:style-name="T1"> </text:span>'
'<text:span text:style-name="T3">italics</text:span>'
'<text:span text:style-name="T1"> text</text:span> text.</text:p>'
'</office: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) == (
'<office:text>'
'<text:p text:style-name="Text_20_body">Some '
'super<text:span text:style-name="T5">script</text:span> and '
'sub<text:span text:style-name="T6">script</text:span> text.</text:p>'
'</office: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) == (
'<office:text>'
'<text:p text:style-name="Text_20_body">Some '
'<text:span text:style-name="T7">underlined and </text:span>'
'<text:span text:style-name="T8">highlighted</text:span> text.</text:p>'
'</office: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) == (
'<office:text>'
'<text:p text:style-name="Text_20_body">Some text.<text:line-break />Next line</text:p>'
'</office:text>'
)
# Tab
odt._text = "\tItem 1\tItem 2\n"
odt.tokenizeText()
odt.initDocument()
odt.doConvert()
odt.closeDocument()
assert odt.errData == []
assert xmlToText(odt._xText) == (
'<office:text>'
'<text:p text:style-name="Text_20_body"><text:tab />Item 1<text:tab />Item 2</text:p>'
'</office:text>'
)
# Tab in Format
odt._text = "Some **bold\ttext**"
odt.tokenizeText()
odt.initDocument()
odt.doConvert()
odt.closeDocument()
assert odt.errData == []
assert xmlToText(odt._xText) == (
'<office:text>'
'<text:p text:style-name="Text_20_body">Some <text:span text:style-name="T9">'
'bold<text:tab />text</text:span></text:p>'
'</office:text>'
)
# 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) == (
'<office:text>'
'<text:h text:style-name="Heading_20_2" text:outline-level="2">Scene</text:h>'
'<text:p text:style-name="Text_20_body">Hello World</text:p>'
'<text:p text:style-name="Text_20_body">Hello <text:s />World</text:p>'
'<text:p text:style-name="Text_20_body">Hello <text:s text:c="2" />World</text:p>'
'</office:text>'
)
# 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) == (
'<office:text>'
'<text:h text:style-name="Heading_20_2" text:outline-level="2">Scene</text:h>'
'<text:p text:style-name="Text_20_Meta"><text:span text:style-name="T10">'
'Point of View:</text:span> <text:span text:style-name="T11">Jane</text:span></text:p>'
'<text:p text:style-name="Text_20_Meta"><text:span text:style-name="T12">'
'Synopsis:</text:span> '
'<text:span text:style-name="T13">So it begins</text:span></text:p>'
'<text:p text:style-name="Text_20_Meta"><text:span text:style-name="T12">'
'Short Description:</text:span> '
'<text:span text:style-name="T13">Then what</text:span></text:p>'
'<text:p text:style-name="Text_20_Meta"><text:span text:style-name="T14">'
'Comment:</text:span> '
'<text:span text:style-name="T15">A plain comment</text:span></text:p>'
'</office:text>'
)
# 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) == (
'<office:text>'
'<text:p text:style-name="Separator">* * *</text:p>'
'<text:p text:style-name="Text_20_body">Text</text:p>'
'<text:p text:style-name="Separator">* * *</text:p>'
'<text:p text:style-name="Text_20_body">Text</text:p>'
'</office: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) == (
'<office:text>'
'<text:p text:style-name="Text_20_body" />'
'<text:p text:style-name="Text_20_body">Text</text:p>'
'<text:p text:style-name="Text_20_body" />'
'<text:p text:style-name="Text_20_body">Text</text:p>'
'</office: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) == (
'<office:text>'
'<text:h text:style-name="Heading_20_2" text:outline-level="2">Scene</text:h>'
'<text:p text:style-name="P1"><text:span text:style-name="T10">'
'Point of View:</text:span> <text:span text:style-name="T11">Jane</text:span></text:p>'
'<text:p text:style-name="P2"><text:span text:style-name="T10">'
'Characters:</text:span> <text:span text:style-name="T11">John</text:span></text:p>'
'<text:p text:style-name="Text_20_Meta"><text:span text:style-name="T10">'
'Plot:</text:span> <text:span text:style-name="T11">Main</text:span></text:p>'
'<text:p text:style-name="P3">Right align</text:p>'
'<text:p text:style-name="Text_20_body">Left Align</text:p>'
'<text:p text:style-name="P4">Centered</text:p>'
'<text:p text:style-name="P5">Left indent</text:p>'
'<text:p text:style-name="P6">Right indent</text:p>'
'</office:text>'
)
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) == (
'<office:text>'
'<text:h text:style-name="Heading_20_2" text:outline-level="2">Scene</text:h>'
'<text:p text:style-name="P7">Regular paragraph</text:p>'
'<text:p text:style-name="P7">with<text:line-break />break</text:p>'
'<text:p text:style-name="Text_20_body">Left Align</text:p>'
'</office:text>'
)
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) == (
'<office:text>'
'<text:h text:style-name="P8" text:outline-level="1">Chapter One</text:h>'
'<text:p text:style-name="Text_20_body">Text</text:p>'
'<text:h text:style-name="P8" text:outline-level="1">Chapter Two</text:h>'
'<text:p text:style-name="Text_20_body">Text</text:p>'
'</office: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) == (
'<office:text>'
'<text:p text:style-name="Text_20_body">Text with one'
'<text:note text:id="ftn1" text:note-class="footnote">'
'<text:note-citation>1</text:note-citation>'
'<text:note-body>'
'<text:p text:style-name="Footnote">Footnote text A.</text:p>'
'</text:note-body>'
'</text:note>, <text:span text:style-name="T9">two</text:span>'
'<text:note text:id="ftn2" text:note-class="footnote">'
'<text:note-citation>2</text:note-citation>'
'<text:note-body>'
'<text:p text:style-name="Footnote">Another footnote.</text:p>'
'</text:note-body>'
'</text:note>, or three footnotes.'
'<text:note text:id="ftn3" text:note-class="footnote">'
'<text:note-citation>3</text:note-citation>'
'<text:note-body>'
'<text:p text:style-name="Footnote">Again?</text:p>'
'</text:note-body>'
'</text:note></text:p>'
'</office:text>'
)
# 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) == (
'<office:text>'
'<text:p text:style-name="Text_20_body">Test text **<text:span text:style-name="T16">'
'bold</text:span>** and more.</text:p>'
'</office:text>'
)
@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 (
'<style:style style:name="P1" style:family="paragraph" '
'style:parent-style-name="Text_20_body">'
'<style:paragraph-properties fo:text-align="justify" />'
'</style:style>'
) in xmlToText(doc._xAuto)
assert xmlToText(doc._xText) == (
'<office:text>'
'<text:p text:style-name="P1">This is a paragraph</text:p>'
'</office:text>'
)
# 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 (
'<style:style style:name="P1" style:family="paragraph" '
'style:parent-style-name="Text_20_body">'
'<style:paragraph-properties fo:break-after="page" />'
'</style:style>'
) in xmlToText(doc._xAuto)
assert xmlToText(doc._xText) == (
'<office:text>'
'<text:p text:style-name="P1">This is a paragraph</text:p>'
'</office:text>'
)
@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) == (
'<test>'
'<style:style style:name="test" style:family="paragraph" style:display-name="Name" '
'style:parent-style-name="Name" style:next-style-name="Name">'
'<style:paragraph-properties fo:margin-top="0.000cm" fo:margin-bottom="0.000cm" '
'fo:margin-left="0.000cm" fo:margin-right="0.000cm" fo:text-indent="0.000cm" '
'fo:line-height="1.15" />'
'<style:text-properties style:font-name="Verdana" fo:font-family="Verdana" '
'fo:font-size="12pt" />'
'</style:style>'
'</test>'
)
# 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) == (
'<test><style:style style:name="test" style:family="text"><style:text-properties '
'fo:font-weight="bold" fo:font-style="italic" style:text-position="super 58%" '
'style:text-line-through-style="solid" style:text-line-through-type="single" />'
'</style:style></test>'
)
@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) == (
"<root>"
"<text:p>Hello World</text:p>"
"</root>"
)
# Text Span
xmlPar.appendSpan("spanned text", "T1", "")
assert xmlToText(xRoot) == (
'<root>'
'<text:p>Hello World'
'<text:span text:style-name="T1">spanned text</text:span>'
'</text:p>'
'</root>'
)
# Tail Text
xmlPar.appendText("more text")
assert xmlToText(xRoot) == (
'<root>'
'<text:p>Hello World'
'<text:span text:style-name="T1">spanned text</text:span>'
'more text</text:p>'
'</root>'
)
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) == (
"<root>"
"<text:p>Hello<text:line-break />World<text:line-break />!!</text:p>"
"</root>"
)
# Text Span w/Line Break
xmlPar.appendSpan("spanned\ntext", "T1", "")
assert xmlToText(xRoot) == (
'<root>'
'<text:p>Hello<text:line-break />World<text:line-break />!!'
'<text:span text:style-name="T1">spanned<text:line-break />text</text:span></text:p>'
'</root>'
)
# Tail Text w/Line Break
xmlPar.appendText("more\ntext")
assert xmlToText(xRoot) == (
'<root>'
'<text:p>Hello<text:line-break />World<text:line-break />!!'
'<text:span text:style-name="T1">spanned<text:line-break />text</text:span>'
'more<text:line-break />text</text:p>'
'</root>'
)
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) == (
"<root>"
"<text:p>Hello<text:tab />World<text:tab />!!</text:p>"
"</root>"
)
# Text Span w/Line Break
xmlPar.appendSpan("spanned\ttext", "T1", "")
assert xmlToText(xRoot) == (
'<root>'
'<text:p>Hello<text:tab />World<text:tab />!!'
'<text:span text:style-name="T1">spanned<text:tab />text</text:span></text:p>'
'</root>'
)
# Tail Text w/Line Break
xmlPar.appendText("more\ttext")
assert xmlToText(xRoot) == (
'<root>'
'<text:p>Hello<text:tab />World<text:tab />!!'
'<text:span text:style-name="T1">spanned<text:tab />text</text:span>'
'more<text:tab />text</text:p>'
'</root>'
)
# Tail Text w/Link
xmlPar.appendSpan("Example", "T1", "http://www.example.com")
assert xmlToText(xRoot) == (
'<root>'
'<text:p>Hello<text:tab />World<text:tab />!!'
'<text:span text:style-name="T1">spanned<text:tab />text</text:span>'
'more<text:tab />text'
'<text:a xlink:type="simple" xlink:href="http://www.example.com" text:style-name="T1">'
'Example</text:a></text:p>'
'</root>'
)
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) == (
'<root>'
'<text:p>Hello <text:s />World <text:s text:c="2" />!!</text:p>'
'</root>'
)
# Text Span w/Spaces
xmlPar.appendSpan("spanned text", "T1", "")
assert xmlToText(xRoot) == (
'<root>'
'<text:p>Hello <text:s />World <text:s text:c="2" />!!'
'<text:span text:style-name="T1">spanned <text:s text:c="3" />text</text:span></text:p>'
'</root>'
)
# Tail Text w/Spaces
xmlPar.appendText("more text")
assert xmlToText(xRoot) == (
'<root>'
'<text:p>Hello <text:s />World <text:s text:c="2" />!!'
'<text:span text:style-name="T1">spanned <text:s text:c="3" />text</text:span>'
'more <text:s text:c="4" />text</text:p>'
'</root>'
)
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) == (
'<root>'
'<text:p><text:s text:c="2" /><text:tab /> A <text:line-break /> <text:s />B </text:p>'
'</root>'
)
# Text Span w/Many Spaces
xmlPar.appendSpan(" C \t D \n E ", "T1", "")
assert xmlToText(xRoot) == (
'<root>'
'<text:p><text:s text:c="2" /><text:tab /> A <text:line-break /> <text:s />B '
'<text:span text:style-name="T1"> <text:s />C <text:s /><text:tab /> <text:s />D '
'<text:line-break /> E </text:span></text:p>'
'</root>'
)
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"