novelWriter 2.5.2__py3-none-any.whl → 2.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {novelWriter-2.5.2.dist-info → novelWriter-2.6.dist-info}/METADATA +5 -4
- {novelWriter-2.5.2.dist-info → novelWriter-2.6.dist-info}/RECORD +126 -105
- {novelWriter-2.5.2.dist-info → novelWriter-2.6.dist-info}/WHEEL +1 -1
- novelwriter/__init__.py +50 -11
- novelwriter/assets/i18n/nw_de_DE.qm +0 -0
- novelwriter/assets/i18n/nw_en_US.qm +0 -0
- novelwriter/assets/i18n/nw_es_419.qm +0 -0
- novelwriter/assets/i18n/nw_fr_FR.qm +0 -0
- novelwriter/assets/i18n/nw_it_IT.qm +0 -0
- novelwriter/assets/i18n/nw_ja_JP.qm +0 -0
- novelwriter/assets/i18n/nw_nb_NO.qm +0 -0
- novelwriter/assets/i18n/nw_nl_NL.qm +0 -0
- novelwriter/assets/i18n/nw_pl_PL.qm +0 -0
- novelwriter/assets/i18n/nw_pt_BR.qm +0 -0
- novelwriter/assets/i18n/nw_ru_RU.qm +0 -0
- novelwriter/assets/i18n/nw_zh_CN.qm +0 -0
- novelwriter/assets/i18n/project_de_DE.json +4 -2
- novelwriter/assets/i18n/project_en_GB.json +1 -0
- novelwriter/assets/i18n/project_en_US.json +2 -0
- novelwriter/assets/i18n/project_it_IT.json +2 -0
- novelwriter/assets/i18n/project_ja_JP.json +2 -0
- novelwriter/assets/i18n/project_nb_NO.json +2 -0
- novelwriter/assets/i18n/project_nl_NL.json +2 -0
- novelwriter/assets/i18n/project_pl_PL.json +2 -0
- novelwriter/assets/i18n/project_pt_BR.json +2 -0
- novelwriter/assets/i18n/project_ru_RU.json +11 -0
- novelwriter/assets/i18n/project_zh_CN.json +2 -0
- novelwriter/assets/icons/typicons_dark/icons.conf +8 -0
- novelwriter/assets/icons/typicons_dark/mixed_copy.svg +4 -0
- novelwriter/assets/icons/typicons_dark/mixed_margin-bottom.svg +6 -0
- novelwriter/assets/icons/typicons_dark/mixed_margin-left.svg +6 -0
- novelwriter/assets/icons/typicons_dark/mixed_margin-right.svg +6 -0
- novelwriter/assets/icons/typicons_dark/mixed_margin-top.svg +6 -0
- novelwriter/assets/icons/typicons_dark/mixed_size-height.svg +6 -0
- novelwriter/assets/icons/typicons_dark/mixed_size-width.svg +6 -0
- novelwriter/assets/icons/typicons_dark/nw_toolbar.svg +5 -0
- novelwriter/assets/icons/typicons_light/icons.conf +8 -0
- novelwriter/assets/icons/typicons_light/mixed_copy.svg +4 -0
- novelwriter/assets/icons/typicons_light/mixed_margin-bottom.svg +6 -0
- novelwriter/assets/icons/typicons_light/mixed_margin-left.svg +6 -0
- novelwriter/assets/icons/typicons_light/mixed_margin-right.svg +6 -0
- novelwriter/assets/icons/typicons_light/mixed_margin-top.svg +6 -0
- novelwriter/assets/icons/typicons_light/mixed_size-height.svg +6 -0
- novelwriter/assets/icons/typicons_light/mixed_size-width.svg +6 -0
- novelwriter/assets/icons/typicons_light/nw_toolbar.svg +5 -0
- novelwriter/assets/manual.pdf +0 -0
- novelwriter/assets/sample.zip +0 -0
- novelwriter/assets/text/credits_en.htm +1 -0
- novelwriter/assets/themes/default_light.conf +2 -2
- novelwriter/common.py +101 -3
- novelwriter/config.py +30 -17
- novelwriter/constants.py +189 -81
- novelwriter/core/buildsettings.py +74 -40
- novelwriter/core/coretools.py +146 -148
- novelwriter/core/docbuild.py +133 -171
- novelwriter/core/document.py +1 -1
- novelwriter/core/index.py +39 -38
- novelwriter/core/item.py +42 -9
- novelwriter/core/itemmodel.py +518 -0
- novelwriter/core/options.py +5 -2
- novelwriter/core/project.py +68 -90
- novelwriter/core/projectdata.py +8 -2
- novelwriter/core/projectxml.py +1 -1
- novelwriter/core/sessions.py +1 -1
- novelwriter/core/spellcheck.py +10 -15
- novelwriter/core/status.py +24 -8
- novelwriter/core/storage.py +1 -1
- novelwriter/core/tree.py +269 -288
- novelwriter/dialogs/about.py +1 -1
- novelwriter/dialogs/docmerge.py +8 -18
- novelwriter/dialogs/docsplit.py +1 -1
- novelwriter/dialogs/editlabel.py +1 -1
- novelwriter/dialogs/preferences.py +47 -34
- novelwriter/dialogs/projectsettings.py +149 -99
- novelwriter/dialogs/quotes.py +1 -1
- novelwriter/dialogs/wordlist.py +11 -10
- novelwriter/enum.py +37 -24
- novelwriter/error.py +2 -2
- novelwriter/extensions/configlayout.py +28 -13
- novelwriter/extensions/eventfilters.py +1 -1
- novelwriter/extensions/modified.py +30 -6
- novelwriter/extensions/novelselector.py +4 -3
- novelwriter/extensions/pagedsidebar.py +9 -9
- novelwriter/extensions/progressbars.py +4 -4
- novelwriter/extensions/statusled.py +3 -3
- novelwriter/extensions/switch.py +3 -3
- novelwriter/extensions/switchbox.py +1 -1
- novelwriter/extensions/versioninfo.py +1 -1
- novelwriter/formats/shared.py +156 -0
- novelwriter/formats/todocx.py +1191 -0
- novelwriter/formats/tohtml.py +454 -0
- novelwriter/{core → formats}/tokenizer.py +497 -495
- novelwriter/formats/tomarkdown.py +218 -0
- novelwriter/{core → formats}/toodt.py +312 -433
- novelwriter/formats/toqdoc.py +486 -0
- novelwriter/formats/toraw.py +91 -0
- novelwriter/gui/doceditor.py +347 -287
- novelwriter/gui/dochighlight.py +97 -85
- novelwriter/gui/docviewer.py +90 -33
- novelwriter/gui/docviewerpanel.py +18 -26
- novelwriter/gui/editordocument.py +18 -3
- novelwriter/gui/itemdetails.py +27 -29
- novelwriter/gui/mainmenu.py +130 -64
- novelwriter/gui/noveltree.py +46 -48
- novelwriter/gui/outline.py +202 -256
- novelwriter/gui/projtree.py +590 -1238
- novelwriter/gui/search.py +11 -19
- novelwriter/gui/sidebar.py +8 -7
- novelwriter/gui/statusbar.py +20 -3
- novelwriter/gui/theme.py +11 -6
- novelwriter/guimain.py +101 -201
- novelwriter/shared.py +67 -28
- novelwriter/text/counting.py +3 -1
- novelwriter/text/patterns.py +169 -61
- novelwriter/tools/dictionaries.py +3 -3
- novelwriter/tools/lipsum.py +1 -1
- novelwriter/tools/manusbuild.py +15 -13
- novelwriter/tools/manuscript.py +121 -79
- novelwriter/tools/manussettings.py +424 -291
- novelwriter/tools/noveldetails.py +1 -1
- novelwriter/tools/welcome.py +6 -6
- novelwriter/tools/writingstats.py +4 -4
- novelwriter/types.py +25 -9
- novelwriter/core/tohtml.py +0 -530
- novelwriter/core/tomarkdown.py +0 -252
- novelwriter/core/toqdoc.py +0 -419
- {novelWriter-2.5.2.dist-info → novelWriter-2.6.dist-info}/LICENSE.md +0 -0
- {novelWriter-2.5.2.dist-info → novelWriter-2.6.dist-info}/entry_points.txt +0 -0
- {novelWriter-2.5.2.dist-info → novelWriter-2.6.dist-info}/top_level.txt +0 -0
@@ -9,7 +9,7 @@ Created: 2021-01-27 [1.2b1] ODTTextStyle
|
|
9
9
|
Created: 2021-08-14 [1.5b1] XMLParagraph
|
10
10
|
|
11
11
|
This file is a part of novelWriter
|
12
|
-
Copyright
|
12
|
+
Copyright (C) 2021 Veronica Berglyd Olsen and novelWriter contributors
|
13
13
|
|
14
14
|
This program is free software: you can redistribute it and/or modify
|
15
15
|
it under the terms of the GNU General Public License as published by
|
@@ -33,29 +33,32 @@ from collections.abc import Sequence
|
|
33
33
|
from datetime import datetime
|
34
34
|
from hashlib import sha256
|
35
35
|
from pathlib import Path
|
36
|
-
from zipfile import ZipFile
|
36
|
+
from zipfile import ZIP_DEFLATED, ZipFile
|
37
37
|
|
38
|
-
from PyQt5.QtGui import QFont
|
38
|
+
from PyQt5.QtGui import QColor, QFont
|
39
39
|
|
40
40
|
from novelwriter import __version__
|
41
|
-
from novelwriter.common import xmlIndent
|
42
|
-
from novelwriter.constants import nwHeadFmt,
|
41
|
+
from novelwriter.common import xmlElement, xmlIndent, xmlSubElem
|
42
|
+
from novelwriter.constants import nwHeadFmt, nwStyles
|
43
43
|
from novelwriter.core.project import NWProject
|
44
|
-
from novelwriter.
|
45
|
-
from novelwriter.
|
44
|
+
from novelwriter.formats.shared import BlockFmt, BlockTyp, TextFmt, stripEscape
|
45
|
+
from novelwriter.formats.tokenizer import Tokenizer
|
46
|
+
from novelwriter.types import FONT_STYLE, QtHexRgb
|
46
47
|
|
47
48
|
logger = logging.getLogger(__name__)
|
48
49
|
|
49
50
|
# Main XML NameSpaces
|
50
51
|
XML_NS = {
|
52
|
+
"dc": "http://purl.org/dc/elements/1.1/",
|
53
|
+
"fo": "urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0",
|
54
|
+
"loext": "urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0",
|
51
55
|
"manifest": "urn:oasis:names:tc:opendocument:xmlns:manifest:1.0",
|
56
|
+
"meta": "urn:oasis:names:tc:opendocument:xmlns:meta:1.0",
|
57
|
+
"number": "urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0",
|
52
58
|
"office": "urn:oasis:names:tc:opendocument:xmlns:office:1.0",
|
53
59
|
"style": "urn:oasis:names:tc:opendocument:xmlns:style:1.0",
|
54
|
-
"loext": "urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0",
|
55
60
|
"text": "urn:oasis:names:tc:opendocument:xmlns:text:1.0",
|
56
|
-
"
|
57
|
-
"fo": "urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0",
|
58
|
-
"dc": "http://purl.org/dc/elements/1.1/",
|
61
|
+
"xlink": "http://www.w3.org/1999/xlink",
|
59
62
|
}
|
60
63
|
for ns, uri in XML_NS.items():
|
61
64
|
ET.register_namespace(ns, uri)
|
@@ -79,7 +82,6 @@ TAG_SPC = _mkTag("text", "s")
|
|
79
82
|
TAG_NSPC = _mkTag("text", "c")
|
80
83
|
TAG_TAB = _mkTag("text", "tab")
|
81
84
|
TAG_SPAN = _mkTag("text", "span")
|
82
|
-
TAG_STNM = _mkTag("text", "style-name")
|
83
85
|
|
84
86
|
# Formatting Codes
|
85
87
|
X_BLD = 0x001 # Bold format
|
@@ -89,8 +91,8 @@ X_UND = 0x008 # Underline format
|
|
89
91
|
X_MRK = 0x010 # Marked format
|
90
92
|
X_SUP = 0x020 # Superscript
|
91
93
|
X_SUB = 0x040 # Subscript
|
92
|
-
|
93
|
-
|
94
|
+
X_COL = 0x080 # Coloured text
|
95
|
+
X_HRF = 0x100 # Link
|
94
96
|
|
95
97
|
# Formatting Masks
|
96
98
|
M_BLD = ~X_BLD
|
@@ -100,8 +102,8 @@ M_UND = ~X_UND
|
|
100
102
|
M_MRK = ~X_MRK
|
101
103
|
M_SUP = ~X_SUP
|
102
104
|
M_SUB = ~X_SUB
|
103
|
-
|
104
|
-
|
105
|
+
M_COL = ~X_COL
|
106
|
+
M_HRF = ~X_HRF
|
105
107
|
|
106
108
|
# ODT Styles
|
107
109
|
S_TITLE = "Title"
|
@@ -114,6 +116,7 @@ S_FIND = "First_20_line_20_indent"
|
|
114
116
|
S_TEXT = "Text_20_body"
|
115
117
|
S_META = "Text_20_Meta"
|
116
118
|
S_HNF = "Header_20_and_20_Footer"
|
119
|
+
S_NUM = "N0"
|
117
120
|
|
118
121
|
# Font Data
|
119
122
|
FONT_WEIGHT_NUM = ["100", "200", "300", "400", "500", "600", "700", "800", "900"]
|
@@ -151,17 +154,15 @@ class ToOdt(Tokenizer):
|
|
151
154
|
|
152
155
|
self._mainPara: dict[str, ODTParagraphStyle] = {} # User-accessible paragraph styles
|
153
156
|
self._autoPara: dict[str, ODTParagraphStyle] = {} # Auto-generated paragraph styles
|
154
|
-
self._autoText: dict[
|
157
|
+
self._autoText: dict[str, ODTTextStyle] = {} # Auto-generated text styles
|
155
158
|
|
156
|
-
#
|
159
|
+
# Storage
|
157
160
|
self._nNote = 0
|
158
|
-
self._etNotes: dict[str, ET.Element] = {} # Generated note elements
|
159
|
-
|
160
161
|
self._errData = [] # List of errors encountered
|
161
162
|
|
162
163
|
# Properties
|
163
164
|
self._textFont = QFont("Liberation Serif", 12)
|
164
|
-
self.
|
165
|
+
self._headWeight = "bold"
|
165
166
|
self._headerFormat = ""
|
166
167
|
self._pageOffset = 0
|
167
168
|
|
@@ -172,45 +173,10 @@ class ToOdt(Tokenizer):
|
|
172
173
|
self._fontStyle = "normal"
|
173
174
|
self._fontPitch = "variable"
|
174
175
|
self._fontBold = "bold"
|
175
|
-
self._fSizeTitle = "30pt"
|
176
|
-
self._fSizeHead1 = "24pt"
|
177
|
-
self._fSizeHead2 = "20pt"
|
178
|
-
self._fSizeHead3 = "16pt"
|
179
|
-
self._fSizeHead4 = "14pt"
|
180
|
-
self._fSizeHead = "14pt"
|
181
|
-
self._fSizeText = "12pt"
|
182
|
-
self._fSizeFoot = "10pt"
|
183
|
-
self._fLineHeight = "115%"
|
184
176
|
self._fBlockIndent = "1.693cm"
|
185
|
-
self._fTextIndent = "0.499cm"
|
186
|
-
self._textAlign = "left"
|
187
177
|
self._dLanguage = "en"
|
188
178
|
self._dCountry = "GB"
|
189
179
|
|
190
|
-
# Text Margins
|
191
|
-
self._mTopTitle = "0.423cm"
|
192
|
-
self._mTopHead1 = "0.423cm"
|
193
|
-
self._mTopHead2 = "0.353cm"
|
194
|
-
self._mTopHead3 = "0.247cm"
|
195
|
-
self._mTopHead4 = "0.247cm"
|
196
|
-
self._mTopHead = "0.423cm"
|
197
|
-
self._mTopText = "0.000cm"
|
198
|
-
self._mTopMeta = "0.000cm"
|
199
|
-
self._mTopSep = "0.247cm"
|
200
|
-
|
201
|
-
self._mBotTitle = "0.212cm"
|
202
|
-
self._mBotHead1 = "0.212cm"
|
203
|
-
self._mBotHead2 = "0.212cm"
|
204
|
-
self._mBotHead3 = "0.212cm"
|
205
|
-
self._mBotHead4 = "0.212cm"
|
206
|
-
self._mBotHead = "0.212cm"
|
207
|
-
self._mBotText = "0.247cm"
|
208
|
-
self._mBotMeta = "0.106cm"
|
209
|
-
self._mBotSep = "0.247cm"
|
210
|
-
|
211
|
-
self._mBotFoot = "0.106cm"
|
212
|
-
self._mLeftFoot = "0.600cm"
|
213
|
-
|
214
180
|
# Document Size and Margins
|
215
181
|
self._mDocWidth = "21.0cm"
|
216
182
|
self._mDocHeight = "29.7cm"
|
@@ -219,39 +185,14 @@ class ToOdt(Tokenizer):
|
|
219
185
|
self._mDocLeft = "2.000cm"
|
220
186
|
self._mDocRight = "2.000cm"
|
221
187
|
|
222
|
-
# Colour
|
223
|
-
self._colHead12 = None
|
224
|
-
self._opaHead12 = None
|
225
|
-
self._colHead34 = None
|
226
|
-
self._opaHead34 = None
|
227
|
-
self._colMetaTx = None
|
228
|
-
self._opaMetaTx = None
|
229
|
-
self._colDialogM = None
|
230
|
-
self._colDialogA = None
|
231
|
-
self._markText = "#ffffa6"
|
232
|
-
|
233
188
|
return
|
234
189
|
|
235
190
|
##
|
236
191
|
# Setters
|
237
192
|
##
|
238
193
|
|
239
|
-
def setLanguage(self, language: str | None) -> None:
|
240
|
-
"""Set language for the document."""
|
241
|
-
if language:
|
242
|
-
lang, _, country = language.partition("_")
|
243
|
-
self._dLanguage = lang or self._dLanguage
|
244
|
-
self._dCountry = country or self._dCountry
|
245
|
-
return
|
246
|
-
|
247
|
-
def setColourHeaders(self, state: bool) -> None:
|
248
|
-
"""Enable/disable coloured headings and comments."""
|
249
|
-
self._colourHead = state
|
250
|
-
return
|
251
|
-
|
252
194
|
def setPageLayout(
|
253
|
-
self, width:
|
254
|
-
top: int | float, bottom: int | float, left: int | float, right: int | float
|
195
|
+
self, width: float, height: float, top: float, bottom: float, left: float, right: float
|
255
196
|
) -> None:
|
256
197
|
"""Set the document page size and margins in millimetres."""
|
257
198
|
self._mDocWidth = f"{width/10.0:.3f}cm"
|
@@ -274,70 +215,21 @@ class ToOdt(Tokenizer):
|
|
274
215
|
|
275
216
|
def initDocument(self) -> None:
|
276
217
|
"""Initialises a new open document XML tree."""
|
218
|
+
super().initDocument()
|
219
|
+
|
277
220
|
# Initialise Variables
|
278
221
|
# ====================
|
279
222
|
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
self._fontFamily
|
285
|
-
self._fontSize
|
286
|
-
self.
|
287
|
-
self.
|
288
|
-
self.
|
289
|
-
self._fontBold = FONT_WEIGHT_MAP.get(fontBold, fontBold)
|
290
|
-
|
291
|
-
self._fSizeTitle = f"{round(2.50 * self._fontSize):d}pt"
|
292
|
-
self._fSizeHead1 = f"{round(2.00 * self._fontSize):d}pt"
|
293
|
-
self._fSizeHead2 = f"{round(1.60 * self._fontSize):d}pt"
|
294
|
-
self._fSizeHead3 = f"{round(1.30 * self._fontSize):d}pt"
|
295
|
-
self._fSizeHead4 = f"{round(1.15 * self._fontSize):d}pt"
|
296
|
-
self._fSizeHead = f"{round(1.15 * self._fontSize):d}pt"
|
297
|
-
self._fSizeText = f"{self._fontSize:d}pt"
|
298
|
-
self._fSizeFoot = f"{round(0.8*self._fontSize):d}pt"
|
299
|
-
|
300
|
-
mScale = self._lineHeight/1.15
|
301
|
-
|
302
|
-
self._mTopTitle = self._emToCm(mScale * self._marginTitle[0])
|
303
|
-
self._mTopHead1 = self._emToCm(mScale * self._marginHead1[0])
|
304
|
-
self._mTopHead2 = self._emToCm(mScale * self._marginHead2[0])
|
305
|
-
self._mTopHead3 = self._emToCm(mScale * self._marginHead3[0])
|
306
|
-
self._mTopHead4 = self._emToCm(mScale * self._marginHead4[0])
|
307
|
-
self._mTopHead = self._emToCm(mScale * self._marginHead4[0])
|
308
|
-
self._mTopText = self._emToCm(mScale * self._marginText[0])
|
309
|
-
self._mTopMeta = self._emToCm(mScale * self._marginMeta[0])
|
310
|
-
self._mTopSep = self._emToCm(mScale * self._marginSep[0])
|
311
|
-
|
312
|
-
self._mBotTitle = self._emToCm(mScale * self._marginTitle[1])
|
313
|
-
self._mBotHead1 = self._emToCm(mScale * self._marginHead1[1])
|
314
|
-
self._mBotHead2 = self._emToCm(mScale * self._marginHead2[1])
|
315
|
-
self._mBotHead3 = self._emToCm(mScale * self._marginHead3[1])
|
316
|
-
self._mBotHead4 = self._emToCm(mScale * self._marginHead4[1])
|
317
|
-
self._mBotHead = self._emToCm(mScale * self._marginHead4[1])
|
318
|
-
self._mBotText = self._emToCm(mScale * self._marginText[1])
|
319
|
-
self._mBotMeta = self._emToCm(mScale * self._marginMeta[1])
|
320
|
-
self._mBotSep = self._emToCm(mScale * self._marginSep[1])
|
321
|
-
|
322
|
-
self._mLeftFoot = self._emToCm(self._marginFoot[0])
|
323
|
-
self._mBotFoot = self._emToCm(self._marginFoot[1])
|
324
|
-
|
325
|
-
if self._colourHead:
|
326
|
-
self._colHead12 = "#2a6099"
|
327
|
-
self._opaHead12 = "100%"
|
328
|
-
self._colHead34 = "#444444"
|
329
|
-
self._opaHead34 = "100%"
|
330
|
-
self._colMetaTx = "#813709"
|
331
|
-
self._opaMetaTx = "100%"
|
332
|
-
|
333
|
-
if self._showDialog:
|
334
|
-
self._colDialogM = "#2a6099"
|
335
|
-
self._colDialogA = "#813709"
|
336
|
-
|
337
|
-
self._fLineHeight = f"{round(100 * self._lineHeight):d}%"
|
223
|
+
lang, _, country = self._dLocale.name().partition("_")
|
224
|
+
self._dLanguage = lang or self._dLanguage
|
225
|
+
self._dCountry = country or self._dCountry
|
226
|
+
|
227
|
+
self._fontFamily = self._textFont.family()
|
228
|
+
self._fontSize = self._textFont.pointSize()
|
229
|
+
self._fontStyle = FONT_STYLE.get(self._textFont.style(), "normal")
|
230
|
+
self._fontPitch = "fixed" if self._textFont.fixedPitch() else "variable"
|
231
|
+
self._headWeight = self._fontBold if self._boldHeads else None
|
338
232
|
self._fBlockIndent = self._emToCm(self._blockIndent)
|
339
|
-
self._fTextIndent = self._emToCm(self._firstWidth)
|
340
|
-
self._textAlign = "justify" if self._doJustify else "left"
|
341
233
|
|
342
234
|
# Clear Errors
|
343
235
|
self._errData = []
|
@@ -408,33 +300,21 @@ class ToOdt(Tokenizer):
|
|
408
300
|
timeStamp = datetime.now().isoformat(sep="T", timespec="seconds")
|
409
301
|
|
410
302
|
# Office Meta Data
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
xMeta.text = f"novelWriter/{__version__}"
|
416
|
-
|
417
|
-
xMeta = ET.SubElement(self._xMeta, _mkTag("meta", "initial-creator"))
|
418
|
-
xMeta.text = self._project.data.author
|
419
|
-
|
420
|
-
xMeta = ET.SubElement(self._xMeta, _mkTag("meta", "editing-cycles"))
|
421
|
-
xMeta.text = str(self._project.data.saveCount)
|
303
|
+
xmlSubElem(self._xMeta, _mkTag("meta", "creation-date"), timeStamp)
|
304
|
+
xmlSubElem(self._xMeta, _mkTag("meta", "generator"), f"novelWriter/{__version__}")
|
305
|
+
xmlSubElem(self._xMeta, _mkTag("meta", "initial-creator"), self._project.data.author)
|
306
|
+
xmlSubElem(self._xMeta, _mkTag("meta", "editing-cycles"), self._project.data.saveCount)
|
422
307
|
|
423
308
|
# Format is: PnYnMnDTnHnMnS
|
424
309
|
# https://www.w3.org/TR/2004/REC-xmlschema-2-20041028/#duration
|
425
310
|
eT = self._project.data.editTime
|
426
|
-
|
427
|
-
|
311
|
+
fT = f"P{eT//86400:d}DT{eT%86400//3600:d}H{eT%3600//60:d}M{eT%60:d}S"
|
312
|
+
xmlSubElem(self._xMeta, _mkTag("meta", "editing-duration"), fT)
|
428
313
|
|
429
314
|
# Dublin Core Meta Data
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
xMeta = ET.SubElement(self._xMeta, _mkTag("dc", "date"))
|
434
|
-
xMeta.text = timeStamp
|
435
|
-
|
436
|
-
xMeta = ET.SubElement(self._xMeta, _mkTag("dc", "creator"))
|
437
|
-
xMeta.text = self._project.data.author
|
315
|
+
xmlSubElem(self._xMeta, _mkTag("dc", "title"), self._project.data.name)
|
316
|
+
xmlSubElem(self._xMeta, _mkTag("dc", "date"), timeStamp)
|
317
|
+
xmlSubElem(self._xMeta, _mkTag("dc", "creator"), self._project.data.author)
|
438
318
|
|
439
319
|
self._pageStyles()
|
440
320
|
self._defaultStyles()
|
@@ -445,140 +325,130 @@ class ToOdt(Tokenizer):
|
|
445
325
|
|
446
326
|
def doConvert(self) -> None:
|
447
327
|
"""Convert the list of text tokens into XML elements."""
|
448
|
-
self._result = "" # Not used, but cleared just in case
|
449
|
-
|
450
328
|
xText = self._xText
|
451
|
-
for tType, _, tText, tFormat, tStyle in self.
|
329
|
+
for tType, _, tText, tFormat, tStyle in self._blocks:
|
452
330
|
|
453
331
|
# Styles
|
454
332
|
oStyle = ODTParagraphStyle("New")
|
455
|
-
if tStyle
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
oStyle.setMarginRight(self._fBlockIndent)
|
333
|
+
if tStyle & BlockFmt.LEFT:
|
334
|
+
oStyle.setTextAlign("left")
|
335
|
+
elif tStyle & BlockFmt.RIGHT:
|
336
|
+
oStyle.setTextAlign("right")
|
337
|
+
elif tStyle & BlockFmt.CENTRE:
|
338
|
+
oStyle.setTextAlign("center")
|
339
|
+
elif tStyle & BlockFmt.JUSTIFY:
|
340
|
+
oStyle.setTextAlign("justify")
|
341
|
+
|
342
|
+
if tStyle & BlockFmt.PBB:
|
343
|
+
oStyle.setBreakBefore("page")
|
344
|
+
if tStyle & BlockFmt.PBA:
|
345
|
+
oStyle.setBreakAfter("page")
|
346
|
+
|
347
|
+
if tStyle & BlockFmt.Z_BTM:
|
348
|
+
oStyle.setMarginBottom("0.000cm")
|
349
|
+
if tStyle & BlockFmt.Z_TOP:
|
350
|
+
oStyle.setMarginTop("0.000cm")
|
351
|
+
|
352
|
+
if tStyle & BlockFmt.IND_L:
|
353
|
+
oStyle.setMarginLeft(self._fBlockIndent)
|
354
|
+
if tStyle & BlockFmt.IND_R:
|
355
|
+
oStyle.setMarginRight(self._fBlockIndent)
|
479
356
|
|
480
357
|
# Process Text Types
|
481
|
-
if tType ==
|
358
|
+
if tType == BlockTyp.TEXT:
|
482
359
|
# Text indentation is processed here because there is a
|
483
360
|
# dedicated pre-defined style for it
|
484
|
-
if tStyle &
|
361
|
+
if tStyle & BlockFmt.IND_T:
|
485
362
|
self._addTextPar(xText, S_FIND, oStyle, tText, tFmt=tFormat)
|
486
363
|
else:
|
487
364
|
self._addTextPar(xText, S_TEXT, oStyle, tText, tFmt=tFormat)
|
488
365
|
|
489
|
-
elif tType
|
366
|
+
elif tType in (BlockTyp.TITLE, BlockTyp.PART):
|
490
367
|
# Title must be text:p
|
491
|
-
|
492
|
-
self._addTextPar(xText, S_TITLE, oStyle, tHead, isHead=False)
|
368
|
+
self._addTextPar(xText, S_TITLE, oStyle, tText, isHead=False)
|
493
369
|
|
494
|
-
elif tType ==
|
495
|
-
|
496
|
-
self._addTextPar(xText, S_HEAD1, oStyle, tHead, isHead=True, oLevel="1")
|
370
|
+
elif tType == BlockTyp.HEAD1:
|
371
|
+
self._addTextPar(xText, S_HEAD1, oStyle, tText, isHead=True, oLevel="1")
|
497
372
|
|
498
|
-
elif tType ==
|
499
|
-
|
500
|
-
self._addTextPar(xText, S_HEAD2, oStyle, tHead, isHead=True, oLevel="2")
|
373
|
+
elif tType == BlockTyp.HEAD2:
|
374
|
+
self._addTextPar(xText, S_HEAD2, oStyle, tText, isHead=True, oLevel="2")
|
501
375
|
|
502
|
-
elif tType ==
|
503
|
-
|
504
|
-
self._addTextPar(xText, S_HEAD3, oStyle, tHead, isHead=True, oLevel="3")
|
376
|
+
elif tType == BlockTyp.HEAD3:
|
377
|
+
self._addTextPar(xText, S_HEAD3, oStyle, tText, isHead=True, oLevel="3")
|
505
378
|
|
506
|
-
elif tType ==
|
507
|
-
|
508
|
-
self._addTextPar(xText, S_HEAD4, oStyle, tHead, isHead=True, oLevel="4")
|
379
|
+
elif tType == BlockTyp.HEAD4:
|
380
|
+
self._addTextPar(xText, S_HEAD4, oStyle, tText, isHead=True, oLevel="4")
|
509
381
|
|
510
|
-
elif tType ==
|
382
|
+
elif tType == BlockTyp.SEP:
|
511
383
|
self._addTextPar(xText, S_SEP, oStyle, tText)
|
512
384
|
|
513
|
-
elif tType ==
|
385
|
+
elif tType == BlockTyp.SKIP:
|
514
386
|
self._addTextPar(xText, S_TEXT, oStyle, "")
|
515
387
|
|
516
|
-
elif tType ==
|
517
|
-
|
518
|
-
self._addTextPar(xText, S_META, oStyle, tTemp, tFmt=tFmt)
|
519
|
-
|
520
|
-
elif tType == self.T_SHORT and self._doSynopsis:
|
521
|
-
tTemp, tFmt = self._formatSynopsis(tText, tFormat, False)
|
522
|
-
self._addTextPar(xText, S_META, oStyle, tTemp, tFmt=tFmt)
|
388
|
+
elif tType == BlockTyp.COMMENT:
|
389
|
+
self._addTextPar(xText, S_META, oStyle, tText, tFmt=tFormat)
|
523
390
|
|
524
|
-
elif tType ==
|
525
|
-
|
526
|
-
self._addTextPar(xText, S_META, oStyle, tTemp, tFmt=tFmt)
|
527
|
-
|
528
|
-
elif tType == self.T_KEYWORD and self._doKeywords:
|
529
|
-
tTemp, tFmt = self._formatKeywords(tText)
|
530
|
-
self._addTextPar(xText, S_META, oStyle, tTemp, tFmt=tFmt)
|
391
|
+
elif tType == BlockTyp.KEYWORD:
|
392
|
+
self._addTextPar(xText, S_META, oStyle, tText, tFmt=tFormat)
|
531
393
|
|
532
394
|
return
|
533
395
|
|
534
396
|
def closeDocument(self) -> None:
|
535
|
-
"""
|
397
|
+
"""Add additional collected information to the XML."""
|
536
398
|
for style in self._autoPara.values():
|
537
399
|
style.packXML(self._xAuto)
|
538
400
|
for style in self._autoText.values():
|
539
401
|
style.packXML(self._xAuto)
|
402
|
+
if self._counts:
|
403
|
+
xFields = ET.Element(_mkTag("text", "user-field-decls"))
|
404
|
+
for key, value in self._counts.items():
|
405
|
+
ET.SubElement(xFields, _mkTag("text", "user-field-decl"), attrib={
|
406
|
+
_mkTag("office", "value-type"): "float",
|
407
|
+
_mkTag("office", "value"): str(value),
|
408
|
+
_mkTag("text", "name"): f"Manuscript{key[:1].upper()}{key[1:]}",
|
409
|
+
})
|
410
|
+
self._xText.insert(0, xFields)
|
540
411
|
return
|
541
412
|
|
542
|
-
def
|
543
|
-
"""Save the data to an .fodt file."""
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
logger.info("Wrote file: %s", path)
|
549
|
-
return
|
550
|
-
|
551
|
-
def saveOpenDocText(self, path: str | Path) -> None:
|
552
|
-
"""Save the data to an .odt file."""
|
553
|
-
mMani = _mkTag("manifest", "manifest")
|
554
|
-
mVers = _mkTag("manifest", "version")
|
555
|
-
mPath = _mkTag("manifest", "full-path")
|
556
|
-
mType = _mkTag("manifest", "media-type")
|
557
|
-
mFile = _mkTag("manifest", "file-entry")
|
558
|
-
|
559
|
-
xMani = ET.Element(mMani, attrib={mVers: X_VERS})
|
560
|
-
ET.SubElement(xMani, mFile, attrib={mPath: "/", mVers: X_VERS, mType: X_MIME})
|
561
|
-
ET.SubElement(xMani, mFile, attrib={mPath: "settings.xml", mType: "text/xml"})
|
562
|
-
ET.SubElement(xMani, mFile, attrib={mPath: "content.xml", mType: "text/xml"})
|
563
|
-
ET.SubElement(xMani, mFile, attrib={mPath: "meta.xml", mType: "text/xml"})
|
564
|
-
ET.SubElement(xMani, mFile, attrib={mPath: "styles.xml", mType: "text/xml"})
|
565
|
-
|
566
|
-
oRoot = _mkTag("office", "document-settings")
|
567
|
-
oVers = _mkTag("office", "version")
|
568
|
-
xSett = ET.Element(oRoot, attrib={oVers: X_VERS})
|
569
|
-
|
570
|
-
def putInZip(name: str, xObj: ET.Element, zipObj: ZipFile) -> None:
|
571
|
-
with zipObj.open(name, mode="w") as fObj:
|
572
|
-
xml = ET.ElementTree(xObj)
|
413
|
+
def saveDocument(self, path: Path) -> None:
|
414
|
+
"""Save the data to an .fodt or .odt file."""
|
415
|
+
if self._isFlat:
|
416
|
+
with open(path, mode="wb") as fObj:
|
417
|
+
xml = ET.ElementTree(self._dFlat)
|
418
|
+
xmlIndent(xml)
|
573
419
|
xml.write(fObj, encoding="utf-8", xml_declaration=True)
|
574
420
|
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
421
|
+
else:
|
422
|
+
mMani = _mkTag("manifest", "manifest")
|
423
|
+
mVers = _mkTag("manifest", "version")
|
424
|
+
mPath = _mkTag("manifest", "full-path")
|
425
|
+
mType = _mkTag("manifest", "media-type")
|
426
|
+
mFile = _mkTag("manifest", "file-entry")
|
427
|
+
|
428
|
+
xMani = ET.Element(mMani, attrib={mVers: X_VERS})
|
429
|
+
ET.SubElement(xMani, mFile, attrib={mPath: "/", mVers: X_VERS, mType: X_MIME})
|
430
|
+
ET.SubElement(xMani, mFile, attrib={mPath: "settings.xml", mType: "text/xml"})
|
431
|
+
ET.SubElement(xMani, mFile, attrib={mPath: "content.xml", mType: "text/xml"})
|
432
|
+
ET.SubElement(xMani, mFile, attrib={mPath: "meta.xml", mType: "text/xml"})
|
433
|
+
ET.SubElement(xMani, mFile, attrib={mPath: "styles.xml", mType: "text/xml"})
|
434
|
+
|
435
|
+
oRoot = _mkTag("office", "document-settings")
|
436
|
+
oVers = _mkTag("office", "version")
|
437
|
+
xSett = ET.Element(oRoot, attrib={oVers: X_VERS})
|
438
|
+
|
439
|
+
def xmlToZip(name: str, root: ET.Element, zipObj: ZipFile) -> None:
|
440
|
+
zipObj.writestr(
|
441
|
+
name, ET.tostring(root, encoding="utf-8", xml_declaration=True),
|
442
|
+
compress_type=ZIP_DEFLATED, compresslevel=3,
|
443
|
+
)
|
444
|
+
|
445
|
+
with ZipFile(path, mode="w") as outZip:
|
446
|
+
outZip.writestr("mimetype", X_MIME, compress_type=None, compresslevel=None)
|
447
|
+
xmlToZip("META-INF/manifest.xml", xMani, outZip)
|
448
|
+
xmlToZip("settings.xml", xSett, outZip)
|
449
|
+
xmlToZip("content.xml", self._dCont, outZip)
|
450
|
+
xmlToZip("meta.xml", self._dMeta, outZip)
|
451
|
+
xmlToZip("styles.xml", self._dStyl, outZip)
|
582
452
|
|
583
453
|
logger.info("Wrote file: %s", path)
|
584
454
|
|
@@ -588,43 +458,14 @@ class ToOdt(Tokenizer):
|
|
588
458
|
# Internal Functions
|
589
459
|
##
|
590
460
|
|
591
|
-
def _formatSynopsis(self, text: str, fmt: T_Formats, synopsis: bool) -> tuple[str, T_Formats]:
|
592
|
-
"""Apply formatting to synopsis lines."""
|
593
|
-
name = self._localLookup("Synopsis" if synopsis else "Short Description")
|
594
|
-
shift = len(name) + 2
|
595
|
-
rTxt = f"{name}: {text}"
|
596
|
-
rFmt: T_Formats = [(0, self.FMT_B_B, ""), (len(name) + 1, self.FMT_B_E, "")]
|
597
|
-
rFmt.extend((p + shift, f, d) for p, f, d in fmt)
|
598
|
-
return rTxt, rFmt
|
599
|
-
|
600
|
-
def _formatComments(self, text: str, fmt: T_Formats) -> tuple[str, T_Formats]:
|
601
|
-
"""Apply formatting to comments."""
|
602
|
-
name = self._localLookup("Comment")
|
603
|
-
shift = len(name) + 2
|
604
|
-
rTxt = f"{name}: {text}"
|
605
|
-
rFmt: T_Formats = [(0, self.FMT_B_B, ""), (len(name) + 1, self.FMT_B_E, "")]
|
606
|
-
rFmt.extend((p + shift, f, d) for p, f, d in fmt)
|
607
|
-
return rTxt, rFmt
|
608
|
-
|
609
|
-
def _formatKeywords(self, text: str) -> tuple[str, T_Formats]:
|
610
|
-
"""Apply formatting to keywords."""
|
611
|
-
valid, bits, _ = self._project.index.scanThis("@"+text)
|
612
|
-
if not valid or not bits or bits[0] not in nwLabels.KEY_NAME:
|
613
|
-
return "", []
|
614
|
-
|
615
|
-
rTxt = f"{self._localLookup(nwLabels.KEY_NAME[bits[0]])}: "
|
616
|
-
rFmt: T_Formats = [(0, self.FMT_B_B, ""), (len(rTxt) - 1, self.FMT_B_E, "")]
|
617
|
-
if len(bits) > 1:
|
618
|
-
if bits[0] == nwKeyWords.TAG_KEY:
|
619
|
-
rTxt += bits[1]
|
620
|
-
else:
|
621
|
-
rTxt += ", ".join(bits[1:])
|
622
|
-
|
623
|
-
return rTxt, rFmt
|
624
|
-
|
625
461
|
def _addTextPar(
|
626
|
-
self,
|
627
|
-
|
462
|
+
self,
|
463
|
+
xParent: ET.Element,
|
464
|
+
styleName: str, oStyle: ODTParagraphStyle,
|
465
|
+
tText: str,
|
466
|
+
tFmt: Sequence[tuple[int, int, str]] | None = None,
|
467
|
+
isHead: bool = False,
|
468
|
+
oLevel: str | None = None,
|
628
469
|
) -> None:
|
629
470
|
"""Add a text paragraph to the text XML element."""
|
630
471
|
tAttr = {_mkTag("text", "style-name"): self._paraStyle(styleName, oStyle)}
|
@@ -646,12 +487,13 @@ class ToOdt(Tokenizer):
|
|
646
487
|
|
647
488
|
parProc = XMLParagraph(xElem)
|
648
489
|
|
649
|
-
pErr = 0
|
650
490
|
xFmt = 0x00
|
651
491
|
tFrag = ""
|
652
492
|
fLast = 0
|
653
493
|
xNode = None
|
654
|
-
|
494
|
+
fClass = ""
|
495
|
+
fLink = ""
|
496
|
+
for fPos, fFmt, fData in tFmt or []:
|
655
497
|
|
656
498
|
# Add any extra nodes
|
657
499
|
if xNode is not None:
|
@@ -663,51 +505,55 @@ class ToOdt(Tokenizer):
|
|
663
505
|
if xFmt == 0x00:
|
664
506
|
parProc.appendText(tFrag)
|
665
507
|
else:
|
666
|
-
parProc.appendSpan(tFrag, self._textStyle(xFmt))
|
508
|
+
parProc.appendSpan(tFrag, self._textStyle(xFmt, fClass), fLink)
|
667
509
|
|
668
510
|
# Calculate the change of format
|
669
|
-
if fFmt ==
|
511
|
+
if fFmt == TextFmt.B_B:
|
670
512
|
xFmt |= X_BLD
|
671
|
-
elif fFmt ==
|
513
|
+
elif fFmt == TextFmt.B_E:
|
672
514
|
xFmt &= M_BLD
|
673
|
-
elif fFmt ==
|
515
|
+
elif fFmt == TextFmt.I_B:
|
674
516
|
xFmt |= X_ITA
|
675
|
-
elif fFmt ==
|
517
|
+
elif fFmt == TextFmt.I_E:
|
676
518
|
xFmt &= M_ITA
|
677
|
-
elif fFmt ==
|
519
|
+
elif fFmt == TextFmt.D_B:
|
678
520
|
xFmt |= X_DEL
|
679
|
-
elif fFmt ==
|
521
|
+
elif fFmt == TextFmt.D_E:
|
680
522
|
xFmt &= M_DEL
|
681
|
-
elif fFmt ==
|
523
|
+
elif fFmt == TextFmt.U_B:
|
682
524
|
xFmt |= X_UND
|
683
|
-
elif fFmt ==
|
525
|
+
elif fFmt == TextFmt.U_E:
|
684
526
|
xFmt &= M_UND
|
685
|
-
elif fFmt ==
|
527
|
+
elif fFmt == TextFmt.M_B:
|
686
528
|
xFmt |= X_MRK
|
687
|
-
elif fFmt ==
|
529
|
+
elif fFmt == TextFmt.M_E:
|
688
530
|
xFmt &= M_MRK
|
689
|
-
elif fFmt ==
|
531
|
+
elif fFmt == TextFmt.SUP_B:
|
690
532
|
xFmt |= X_SUP
|
691
|
-
elif fFmt ==
|
533
|
+
elif fFmt == TextFmt.SUP_E:
|
692
534
|
xFmt &= M_SUP
|
693
|
-
elif fFmt ==
|
535
|
+
elif fFmt == TextFmt.SUB_B:
|
694
536
|
xFmt |= X_SUB
|
695
|
-
elif fFmt ==
|
537
|
+
elif fFmt == TextFmt.SUB_E:
|
696
538
|
xFmt &= M_SUB
|
697
|
-
elif fFmt ==
|
698
|
-
xFmt |=
|
699
|
-
|
700
|
-
|
701
|
-
|
702
|
-
|
703
|
-
elif fFmt ==
|
704
|
-
xFmt
|
705
|
-
|
539
|
+
elif fFmt == TextFmt.COL_B:
|
540
|
+
xFmt |= X_COL
|
541
|
+
fClass = fData
|
542
|
+
elif fFmt == TextFmt.COL_E:
|
543
|
+
xFmt &= M_COL
|
544
|
+
fClass = ""
|
545
|
+
elif fFmt == TextFmt.HRF_B:
|
546
|
+
xFmt |= X_HRF
|
547
|
+
fLink = fData
|
548
|
+
elif fFmt == TextFmt.HRF_E:
|
549
|
+
xFmt &= M_HRF
|
550
|
+
fLink = ""
|
551
|
+
elif fFmt == TextFmt.FNOTE:
|
706
552
|
xNode = self._generateFootnote(fData)
|
707
|
-
elif fFmt ==
|
553
|
+
elif fFmt == TextFmt.FIELD:
|
554
|
+
xNode = self._generateField(fData, xFmt)
|
555
|
+
elif fFmt == TextFmt.STRIP:
|
708
556
|
pass
|
709
|
-
else:
|
710
|
-
pErr += 1
|
711
557
|
|
712
558
|
fLast = fPos
|
713
559
|
|
@@ -718,10 +564,7 @@ class ToOdt(Tokenizer):
|
|
718
564
|
if xFmt == 0x00:
|
719
565
|
parProc.appendText(tFrag)
|
720
566
|
else:
|
721
|
-
parProc.appendSpan(tFrag, self._textStyle(xFmt))
|
722
|
-
|
723
|
-
if pErr > 0:
|
724
|
-
self._errData.append("Unknown format tag encountered")
|
567
|
+
parProc.appendSpan(tFrag, self._textStyle(xFmt, fClass), fLink)
|
725
568
|
|
726
569
|
nErr, errMsg = parProc.checkError()
|
727
570
|
if nErr > 0: # pragma: no cover
|
@@ -752,10 +595,13 @@ class ToOdt(Tokenizer):
|
|
752
595
|
|
753
596
|
return modStyle.name
|
754
597
|
|
755
|
-
def _textStyle(self, hFmt: int) -> str:
|
598
|
+
def _textStyle(self, hFmt: int, fClass: str = "") -> str:
|
756
599
|
"""Return a text style for a given style code."""
|
757
|
-
|
758
|
-
|
600
|
+
tKey = str(hFmt)
|
601
|
+
if fClass and (color := self._classes.get(fClass)):
|
602
|
+
tKey = f"{tKey}:{fClass}"
|
603
|
+
if tKey in self._autoText:
|
604
|
+
return self._autoText[tKey].name
|
759
605
|
|
760
606
|
style = ODTTextStyle(f"T{len(self._autoText)+1:d}")
|
761
607
|
if hFmt & X_BLD:
|
@@ -768,18 +614,21 @@ class ToOdt(Tokenizer):
|
|
768
614
|
if hFmt & X_UND:
|
769
615
|
style.setUnderlineStyle("solid")
|
770
616
|
style.setUnderlineWidth("auto")
|
771
|
-
style.
|
617
|
+
style.setUnderlineColor("font-color")
|
772
618
|
if hFmt & X_MRK:
|
773
|
-
style.
|
619
|
+
style.setBackgroundColor(self._theme.highlight)
|
774
620
|
if hFmt & X_SUP:
|
775
621
|
style.setTextPosition("super")
|
776
622
|
if hFmt & X_SUB:
|
777
623
|
style.setTextPosition("sub")
|
778
|
-
if hFmt &
|
779
|
-
style.
|
780
|
-
if hFmt &
|
781
|
-
style.
|
782
|
-
|
624
|
+
if hFmt & X_COL and color:
|
625
|
+
style.setColor(color)
|
626
|
+
if hFmt & X_HRF:
|
627
|
+
style.setColor(self._theme.link)
|
628
|
+
style.setUnderlineStyle("solid")
|
629
|
+
style.setUnderlineWidth("auto")
|
630
|
+
style.setUnderlineColor("font-color")
|
631
|
+
self._autoText[tKey] = style
|
783
632
|
|
784
633
|
return style.name
|
785
634
|
|
@@ -788,20 +637,40 @@ class ToOdt(Tokenizer):
|
|
788
637
|
if content := self._footnotes.get(key):
|
789
638
|
self._nNote += 1
|
790
639
|
nStyle = ODTParagraphStyle("New")
|
791
|
-
xNote =
|
640
|
+
xNote = xmlElement(_mkTag("text", "note"), attrib={
|
792
641
|
_mkTag("text", "id"): f"ftn{self._nNote}",
|
793
642
|
_mkTag("text", "note-class"): "footnote",
|
794
643
|
})
|
795
|
-
|
796
|
-
|
797
|
-
xBody = ET.SubElement(xNote, _mkTag("text", "note-body"))
|
644
|
+
xmlSubElem(xNote, _mkTag("text", "note-citation"), self._nNote)
|
645
|
+
xBody = xmlSubElem(xNote, _mkTag("text", "note-body"))
|
798
646
|
self._addTextPar(xBody, "Footnote", nStyle, content[0], tFmt=content[1])
|
799
647
|
return xNote
|
800
648
|
return None
|
801
649
|
|
650
|
+
def _generateField(self, key: str, fmt: int) -> ET.Element | None:
|
651
|
+
"""Generate a data field XML object."""
|
652
|
+
if key and (field := key.partition(":")[2]):
|
653
|
+
xField = xmlElement(_mkTag("text", "user-field-get"), "0", tail="", attrib={
|
654
|
+
_mkTag("style", "data-style-name"): S_NUM,
|
655
|
+
_mkTag("text", "name"): f"Manuscript{field[:1].upper()}{field[1:]}",
|
656
|
+
})
|
657
|
+
if fmt == 0x00:
|
658
|
+
return xField
|
659
|
+
else:
|
660
|
+
xSpan = xmlElement(TAG_SPAN, "", tail="", attrib={
|
661
|
+
_mkTag("text", "style-name"): self._textStyle(fmt),
|
662
|
+
})
|
663
|
+
xSpan.append(xField)
|
664
|
+
return xSpan
|
665
|
+
return None
|
666
|
+
|
802
667
|
def _emToCm(self, value: float) -> str:
|
803
668
|
"""Converts an em value to centimetres."""
|
804
|
-
return f"{value*2.54/72
|
669
|
+
return f"{value*self._fontSize*2.54/72.0:.3f}cm"
|
670
|
+
|
671
|
+
def _emToPt(self, scale: float) -> str:
|
672
|
+
"""Compute relative font size in points."""
|
673
|
+
return f"{round(scale * self._fontSize):d}pt"
|
805
674
|
|
806
675
|
##
|
807
676
|
# Style Elements
|
@@ -825,16 +694,19 @@ class ToOdt(Tokenizer):
|
|
825
694
|
|
826
695
|
xHead = ET.SubElement(xPage, _mkTag("style", "header-style"))
|
827
696
|
ET.SubElement(xHead, _mkTag("style", "header-footer-properties"), attrib={
|
828
|
-
_mkTag("fo", "min-height"):
|
697
|
+
_mkTag("fo", "min-height"): self._emToCm(1.5),
|
829
698
|
_mkTag("fo", "margin-left"): "0.000cm",
|
830
699
|
_mkTag("fo", "margin-right"): "0.000cm",
|
831
|
-
_mkTag("fo", "margin-bottom"):
|
700
|
+
_mkTag("fo", "margin-bottom"): self._emToCm(0.5),
|
832
701
|
})
|
833
702
|
|
834
703
|
return
|
835
704
|
|
836
705
|
def _defaultStyles(self) -> None:
|
837
706
|
"""Set the default styles."""
|
707
|
+
hScale = self._scaleHeads
|
708
|
+
textSize = self._emToPt(nwStyles.T_NORMAL)
|
709
|
+
|
838
710
|
# Add Paragraph Family Style
|
839
711
|
xStyl = ET.SubElement(self._xStyl, _mkTag("style", "default-style"), attrib={
|
840
712
|
_mkTag("style", "family"): "paragraph",
|
@@ -849,7 +721,7 @@ class ToOdt(Tokenizer):
|
|
849
721
|
_mkTag("fo", "font-family"): self._fontFamily,
|
850
722
|
_mkTag("fo", "font-weight"): self._fontWeight,
|
851
723
|
_mkTag("fo", "font-style"): self._fontStyle,
|
852
|
-
_mkTag("fo", "font-size"):
|
724
|
+
_mkTag("fo", "font-size"): textSize,
|
853
725
|
_mkTag("fo", "language"): self._dLanguage,
|
854
726
|
_mkTag("fo", "country"): self._dCountry,
|
855
727
|
})
|
@@ -865,7 +737,7 @@ class ToOdt(Tokenizer):
|
|
865
737
|
_mkTag("fo", "font-family"): self._fontFamily,
|
866
738
|
_mkTag("fo", "font-weight"): self._fontWeight,
|
867
739
|
_mkTag("fo", "font-style"): self._fontStyle,
|
868
|
-
_mkTag("fo", "font-size"):
|
740
|
+
_mkTag("fo", "font-size"): textSize,
|
869
741
|
})
|
870
742
|
|
871
743
|
# Add Default Heading Style
|
@@ -877,8 +749,8 @@ class ToOdt(Tokenizer):
|
|
877
749
|
_mkTag("style", "class"): "text",
|
878
750
|
})
|
879
751
|
ET.SubElement(xStyl, _mkTag("style", "paragraph-properties"), attrib={
|
880
|
-
_mkTag("fo", "margin-top"): self.
|
881
|
-
_mkTag("fo", "margin-bottom"): self.
|
752
|
+
_mkTag("fo", "margin-top"): self._emToCm(self._marginHead4[0]),
|
753
|
+
_mkTag("fo", "margin-bottom"): self._emToCm(self._marginHead4[1]),
|
882
754
|
_mkTag("fo", "keep-with-next"): "always",
|
883
755
|
})
|
884
756
|
ET.SubElement(xStyl, _mkTag("style", "text-properties"), attrib={
|
@@ -886,7 +758,7 @@ class ToOdt(Tokenizer):
|
|
886
758
|
_mkTag("fo", "font-family"): self._fontFamily,
|
887
759
|
_mkTag("fo", "font-weight"): self._fontWeight,
|
888
760
|
_mkTag("fo", "font-style"): self._fontStyle,
|
889
|
-
_mkTag("fo", "font-size"): self.
|
761
|
+
_mkTag("fo", "font-size"): self._emToPt(nwStyles.H_SIZES[4] if hScale else 1.0),
|
890
762
|
})
|
891
763
|
|
892
764
|
# Add Header and Footer Styles
|
@@ -898,22 +770,36 @@ class ToOdt(Tokenizer):
|
|
898
770
|
_mkTag("style", "class"): "extra",
|
899
771
|
})
|
900
772
|
|
773
|
+
# Numbers Style
|
774
|
+
xStyl = ET.SubElement(self._xStyl, _mkTag("number", "number-style"), attrib={
|
775
|
+
_mkTag("style", "name"): S_NUM,
|
776
|
+
})
|
777
|
+
ET.SubElement(xStyl, _mkTag("number", "number"), attrib={
|
778
|
+
_mkTag("number", "min-integer-digits"): "1",
|
779
|
+
})
|
780
|
+
|
901
781
|
return
|
902
782
|
|
903
783
|
def _useableStyles(self) -> None:
|
904
784
|
"""Set the usable styles."""
|
785
|
+
hScale = self._scaleHeads
|
786
|
+
hColor = self._theme.head if self._colorHeads else None
|
787
|
+
|
788
|
+
textSize = self._emToPt(nwStyles.T_NORMAL)
|
789
|
+
lineHeight = f"{round(100 * self._lineHeight):d}%"
|
790
|
+
|
905
791
|
# Add Text Body Style
|
906
792
|
style = ODTParagraphStyle(S_TEXT)
|
907
793
|
style.setDisplayName("Text body")
|
908
794
|
style.setParentStyleName("Standard")
|
909
795
|
style.setClass("text")
|
910
|
-
style.setMarginTop(self.
|
911
|
-
style.setMarginBottom(self.
|
912
|
-
style.setLineHeight(
|
913
|
-
style.setTextAlign(self.
|
796
|
+
style.setMarginTop(self._emToCm(self._marginText[0]))
|
797
|
+
style.setMarginBottom(self._emToCm(self._marginText[1]))
|
798
|
+
style.setLineHeight(lineHeight)
|
799
|
+
style.setTextAlign(self._defaultAlign)
|
914
800
|
style.setFontName(self._fontFamily)
|
915
801
|
style.setFontFamily(self._fontFamily)
|
916
|
-
style.setFontSize(
|
802
|
+
style.setFontSize(textSize)
|
917
803
|
style.setFontWeight(self._fontWeight)
|
918
804
|
style.packXML(self._xStyl)
|
919
805
|
self._mainPara[style.name] = style
|
@@ -923,7 +809,7 @@ class ToOdt(Tokenizer):
|
|
923
809
|
style.setDisplayName("First line indent")
|
924
810
|
style.setParentStyleName(S_TEXT)
|
925
811
|
style.setClass("text")
|
926
|
-
style.setTextIndent(self.
|
812
|
+
style.setTextIndent(self._emToCm(self._firstWidth))
|
927
813
|
style.packXML(self._xStyl)
|
928
814
|
self._mainPara[style.name] = style
|
929
815
|
|
@@ -932,15 +818,13 @@ class ToOdt(Tokenizer):
|
|
932
818
|
style.setDisplayName("Text Meta")
|
933
819
|
style.setParentStyleName("Standard")
|
934
820
|
style.setClass("text")
|
935
|
-
style.setMarginTop(self.
|
936
|
-
style.setMarginBottom(self.
|
937
|
-
style.setLineHeight(
|
821
|
+
style.setMarginTop(self._emToCm(self._marginMeta[0]))
|
822
|
+
style.setMarginBottom(self._emToCm(self._marginMeta[1]))
|
823
|
+
style.setLineHeight(lineHeight)
|
938
824
|
style.setFontName(self._fontFamily)
|
939
825
|
style.setFontFamily(self._fontFamily)
|
940
|
-
style.setFontSize(
|
826
|
+
style.setFontSize(textSize)
|
941
827
|
style.setFontWeight(self._fontWeight)
|
942
|
-
style.setColour(self._colMetaTx)
|
943
|
-
style.setOpacity(self._opaMetaTx)
|
944
828
|
style.packXML(self._xStyl)
|
945
829
|
self._mainPara[style.name] = style
|
946
830
|
|
@@ -950,13 +834,13 @@ class ToOdt(Tokenizer):
|
|
950
834
|
style.setParentStyleName("Heading")
|
951
835
|
style.setNextStyleName(S_TEXT)
|
952
836
|
style.setClass("chapter")
|
953
|
-
style.setMarginTop(self.
|
954
|
-
style.setMarginBottom(self.
|
837
|
+
style.setMarginTop(self._emToCm(self._marginTitle[0]))
|
838
|
+
style.setMarginBottom(self._emToCm(self._marginTitle[1]))
|
955
839
|
style.setTextAlign("center")
|
956
840
|
style.setFontName(self._fontFamily)
|
957
841
|
style.setFontFamily(self._fontFamily)
|
958
|
-
style.setFontSize(self.
|
959
|
-
style.setFontWeight(self.
|
842
|
+
style.setFontSize(self._emToPt(nwStyles.H_SIZES[0] if hScale else 1.0))
|
843
|
+
style.setFontWeight(self._headWeight)
|
960
844
|
style.packXML(self._xStyl)
|
961
845
|
self._mainPara[style.name] = style
|
962
846
|
|
@@ -966,13 +850,13 @@ class ToOdt(Tokenizer):
|
|
966
850
|
style.setParentStyleName("Standard")
|
967
851
|
style.setNextStyleName(S_TEXT)
|
968
852
|
style.setClass("text")
|
969
|
-
style.setMarginTop(self.
|
970
|
-
style.setMarginBottom(self.
|
971
|
-
style.setLineHeight(
|
853
|
+
style.setMarginTop(self._emToCm(self._marginSep[0]))
|
854
|
+
style.setMarginBottom(self._emToCm(self._marginSep[1]))
|
855
|
+
style.setLineHeight(lineHeight)
|
972
856
|
style.setTextAlign("center")
|
973
857
|
style.setFontName(self._fontFamily)
|
974
858
|
style.setFontFamily(self._fontFamily)
|
975
|
-
style.setFontSize(
|
859
|
+
style.setFontSize(textSize)
|
976
860
|
style.setFontWeight(self._fontWeight)
|
977
861
|
style.packXML(self._xStyl)
|
978
862
|
self._mainPara[style.name] = style
|
@@ -984,14 +868,13 @@ class ToOdt(Tokenizer):
|
|
984
868
|
style.setNextStyleName(S_TEXT)
|
985
869
|
style.setOutlineLevel("1")
|
986
870
|
style.setClass("text")
|
987
|
-
style.setMarginTop(self.
|
988
|
-
style.setMarginBottom(self.
|
871
|
+
style.setMarginTop(self._emToCm(self._marginHead1[0]))
|
872
|
+
style.setMarginBottom(self._emToCm(self._marginHead1[1]))
|
989
873
|
style.setFontName(self._fontFamily)
|
990
874
|
style.setFontFamily(self._fontFamily)
|
991
|
-
style.setFontSize(self.
|
992
|
-
style.setFontWeight(self.
|
993
|
-
style.
|
994
|
-
style.setOpacity(self._opaHead12)
|
875
|
+
style.setFontSize(self._emToPt(nwStyles.H_SIZES[1] if hScale else 1.0))
|
876
|
+
style.setFontWeight(self._headWeight)
|
877
|
+
style.setColor(hColor)
|
995
878
|
style.packXML(self._xStyl)
|
996
879
|
self._mainPara[style.name] = style
|
997
880
|
|
@@ -1002,14 +885,13 @@ class ToOdt(Tokenizer):
|
|
1002
885
|
style.setNextStyleName(S_TEXT)
|
1003
886
|
style.setOutlineLevel("2")
|
1004
887
|
style.setClass("text")
|
1005
|
-
style.setMarginTop(self.
|
1006
|
-
style.setMarginBottom(self.
|
888
|
+
style.setMarginTop(self._emToCm(self._marginHead2[0]))
|
889
|
+
style.setMarginBottom(self._emToCm(self._marginHead2[1]))
|
1007
890
|
style.setFontName(self._fontFamily)
|
1008
891
|
style.setFontFamily(self._fontFamily)
|
1009
|
-
style.setFontSize(self.
|
1010
|
-
style.setFontWeight(self.
|
1011
|
-
style.
|
1012
|
-
style.setOpacity(self._opaHead12)
|
892
|
+
style.setFontSize(self._emToPt(nwStyles.H_SIZES[2] if hScale else 1.0))
|
893
|
+
style.setFontWeight(self._headWeight)
|
894
|
+
style.setColor(hColor)
|
1013
895
|
style.packXML(self._xStyl)
|
1014
896
|
self._mainPara[style.name] = style
|
1015
897
|
|
@@ -1020,14 +902,13 @@ class ToOdt(Tokenizer):
|
|
1020
902
|
style.setNextStyleName(S_TEXT)
|
1021
903
|
style.setOutlineLevel("3")
|
1022
904
|
style.setClass("text")
|
1023
|
-
style.setMarginTop(self.
|
1024
|
-
style.setMarginBottom(self.
|
905
|
+
style.setMarginTop(self._emToCm(self._marginHead3[0]))
|
906
|
+
style.setMarginBottom(self._emToCm(self._marginHead3[1]))
|
1025
907
|
style.setFontName(self._fontFamily)
|
1026
908
|
style.setFontFamily(self._fontFamily)
|
1027
|
-
style.setFontSize(self.
|
1028
|
-
style.setFontWeight(self.
|
1029
|
-
style.
|
1030
|
-
style.setOpacity(self._opaHead34)
|
909
|
+
style.setFontSize(self._emToPt(nwStyles.H_SIZES[3] if hScale else 1.0))
|
910
|
+
style.setFontWeight(self._headWeight)
|
911
|
+
style.setColor(hColor)
|
1031
912
|
style.packXML(self._xStyl)
|
1032
913
|
self._mainPara[style.name] = style
|
1033
914
|
|
@@ -1038,14 +919,13 @@ class ToOdt(Tokenizer):
|
|
1038
919
|
style.setNextStyleName(S_TEXT)
|
1039
920
|
style.setOutlineLevel("4")
|
1040
921
|
style.setClass("text")
|
1041
|
-
style.setMarginTop(self.
|
1042
|
-
style.setMarginBottom(self.
|
922
|
+
style.setMarginTop(self._emToCm(self._marginHead4[0]))
|
923
|
+
style.setMarginBottom(self._emToCm(self._marginHead4[1]))
|
1043
924
|
style.setFontName(self._fontFamily)
|
1044
925
|
style.setFontFamily(self._fontFamily)
|
1045
|
-
style.setFontSize(self.
|
1046
|
-
style.setFontWeight(self.
|
1047
|
-
style.
|
1048
|
-
style.setOpacity(self._opaHead34)
|
926
|
+
style.setFontSize(self._emToPt(nwStyles.H_SIZES[4] if hScale else 1.0))
|
927
|
+
style.setFontWeight(self._headWeight)
|
928
|
+
style.setColor(hColor)
|
1049
929
|
style.packXML(self._xStyl)
|
1050
930
|
self._mainPara[style.name] = style
|
1051
931
|
|
@@ -1062,10 +942,10 @@ class ToOdt(Tokenizer):
|
|
1062
942
|
style.setDisplayName("Footnote")
|
1063
943
|
style.setParentStyleName("Standard")
|
1064
944
|
style.setClass("extra")
|
1065
|
-
style.setMarginLeft(self.
|
1066
|
-
style.setMarginBottom(self.
|
1067
|
-
style.setTextIndent("-"+self.
|
1068
|
-
style.setFontSize(self.
|
945
|
+
style.setMarginLeft(self._emToCm(self._marginFoot[0]))
|
946
|
+
style.setMarginBottom(self._emToCm(self._marginFoot[1]))
|
947
|
+
style.setTextIndent("-"+self._emToCm(self._marginFoot[0]))
|
948
|
+
style.setFontSize(self._emToPt(nwStyles.T_SMALL))
|
1069
949
|
style.packXML(self._xStyl)
|
1070
950
|
self._mainPara[style.name] = style
|
1071
951
|
|
@@ -1080,12 +960,12 @@ class ToOdt(Tokenizer):
|
|
1080
960
|
|
1081
961
|
# Standard Page Header
|
1082
962
|
if self._headerFormat:
|
1083
|
-
pre, page, post = self._headerFormat.partition(nwHeadFmt.
|
963
|
+
pre, page, post = self._headerFormat.partition(nwHeadFmt.DOC_PAGE)
|
1084
964
|
|
1085
|
-
pre = pre.replace(nwHeadFmt.
|
1086
|
-
pre = pre.replace(nwHeadFmt.
|
1087
|
-
post = post.replace(nwHeadFmt.
|
1088
|
-
post = post.replace(nwHeadFmt.
|
965
|
+
pre = pre.replace(nwHeadFmt.DOC_PROJECT, self._project.data.name)
|
966
|
+
pre = pre.replace(nwHeadFmt.DOC_AUTHOR, self._project.data.author)
|
967
|
+
post = post.replace(nwHeadFmt.DOC_PROJECT, self._project.data.name)
|
968
|
+
post = post.replace(nwHeadFmt.DOC_AUTHOR, self._project.data.author)
|
1089
969
|
|
1090
970
|
xHead = ET.SubElement(xPage, _mkTag("style", "header"))
|
1091
971
|
xPar = ET.SubElement(xHead, _mkTag("text", "p"), attrib={
|
@@ -1167,16 +1047,6 @@ class ODTParagraphStyle:
|
|
1167
1047
|
def name(self) -> str:
|
1168
1048
|
return self._name
|
1169
1049
|
|
1170
|
-
##
|
1171
|
-
# Checkers
|
1172
|
-
##
|
1173
|
-
|
1174
|
-
def isUnaligned(self) -> bool:
|
1175
|
-
"""Check if paragraph has any sort of alignment or margins."""
|
1176
|
-
return all(
|
1177
|
-
self._pAttr[n][1] is None for n in ["text-align", "margin-left", "margin-right"]
|
1178
|
-
)
|
1179
|
-
|
1180
1050
|
##
|
1181
1051
|
# Setters
|
1182
1052
|
##
|
@@ -1306,14 +1176,14 @@ class ODTParagraphStyle:
|
|
1306
1176
|
self._tAttr["font-weight"][1] = None
|
1307
1177
|
return
|
1308
1178
|
|
1309
|
-
def
|
1179
|
+
def setColor(self, value: QColor | None) -> None:
|
1310
1180
|
"""Set text colour."""
|
1311
|
-
|
1312
|
-
|
1313
|
-
|
1314
|
-
|
1315
|
-
|
1316
|
-
|
1181
|
+
if isinstance(value, QColor):
|
1182
|
+
self._tAttr["color"][1] = value.name(QtHexRgb)
|
1183
|
+
self._tAttr["opacity"][1] = f"{int(100.0 * value.alphaF())}%"
|
1184
|
+
else:
|
1185
|
+
self._tAttr["color"][1] = None
|
1186
|
+
self._tAttr["opacity"][1] = None
|
1317
1187
|
return
|
1318
1188
|
|
1319
1189
|
##
|
@@ -1414,18 +1284,18 @@ class ODTTextStyle:
|
|
1414
1284
|
self._tAttr["font-style"][1] = None
|
1415
1285
|
return
|
1416
1286
|
|
1417
|
-
def
|
1287
|
+
def setColor(self, value: QColor | None) -> None:
|
1418
1288
|
"""Set text colour."""
|
1419
|
-
if value
|
1420
|
-
self._tAttr["color"][1] = value
|
1289
|
+
if isinstance(value, QColor):
|
1290
|
+
self._tAttr["color"][1] = value.name(QtHexRgb)
|
1421
1291
|
else:
|
1422
1292
|
self._tAttr["color"][1] = None
|
1423
1293
|
return
|
1424
1294
|
|
1425
|
-
def
|
1295
|
+
def setBackgroundColor(self, value: QColor | None) -> None:
|
1426
1296
|
"""Set text background colour."""
|
1427
|
-
if value
|
1428
|
-
self._tAttr["background-color"][1] = value
|
1297
|
+
if isinstance(value, QColor):
|
1298
|
+
self._tAttr["background-color"][1] = value.name(QtHexRgb)
|
1429
1299
|
else:
|
1430
1300
|
self._tAttr["background-color"][1] = None
|
1431
1301
|
return
|
@@ -1470,7 +1340,7 @@ class ODTTextStyle:
|
|
1470
1340
|
self._tAttr["text-underline-width"][1] = None
|
1471
1341
|
return
|
1472
1342
|
|
1473
|
-
def
|
1343
|
+
def setUnderlineColor(self, value: str | None) -> None:
|
1474
1344
|
"""Set text underline colour."""
|
1475
1345
|
if value in self.VALID_LCOL:
|
1476
1346
|
self._tAttr["text-underline-color"][1] = value
|
@@ -1595,13 +1465,22 @@ class XMLParagraph:
|
|
1595
1465
|
|
1596
1466
|
return
|
1597
1467
|
|
1598
|
-
def appendSpan(self, text: str,
|
1468
|
+
def appendSpan(self, text: str, style: str, link: str) -> None:
|
1599
1469
|
"""Append a text span to the XML element. The span is always
|
1600
|
-
closed since we do not
|
1470
|
+
closed since we do not produce nested spans (like Libre Office).
|
1601
1471
|
Therefore we return to the root element level when we're done
|
1602
1472
|
processing the text of the span.
|
1603
1473
|
"""
|
1604
|
-
|
1474
|
+
if link:
|
1475
|
+
self._xTail = ET.SubElement(self._xRoot, _mkTag("text", "a"), attrib={
|
1476
|
+
_mkTag("xlink", "type"): "simple",
|
1477
|
+
_mkTag("xlink", "href"): link,
|
1478
|
+
_mkTag("text", "style-name"): style,
|
1479
|
+
})
|
1480
|
+
else:
|
1481
|
+
self._xTail = ET.SubElement(self._xRoot, TAG_SPAN, attrib={
|
1482
|
+
_mkTag("text", "style-name"): style,
|
1483
|
+
})
|
1605
1484
|
self._xTail.text = "" # Defaults to None
|
1606
1485
|
self._xTail.tail = "" # Defaults to None
|
1607
1486
|
self._nState = X_SPAN_TEXT
|