novelWriter 2.4.3__py3-none-any.whl → 2.5b1__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.4.3.dist-info → novelWriter-2.5b1.dist-info}/METADATA +4 -5
- {novelWriter-2.4.3.dist-info → novelWriter-2.5b1.dist-info}/RECORD +109 -101
- novelwriter/__init__.py +33 -39
- novelwriter/assets/i18n/project_en_GB.json +1 -0
- novelwriter/assets/icons/typicons_dark/icons.conf +2 -0
- novelwriter/assets/icons/typicons_dark/nw_font.svg +4 -0
- novelwriter/assets/icons/typicons_dark/nw_quote.svg +4 -0
- novelwriter/assets/icons/typicons_light/icons.conf +2 -0
- novelwriter/assets/icons/typicons_light/nw_font.svg +4 -0
- novelwriter/assets/icons/typicons_light/nw_quote.svg +4 -0
- novelwriter/assets/manual.pdf +0 -0
- novelwriter/assets/sample.zip +0 -0
- novelwriter/assets/syntax/cyberpunk_night.conf +5 -3
- novelwriter/assets/syntax/default_dark.conf +32 -18
- novelwriter/assets/syntax/default_light.conf +24 -10
- novelwriter/assets/syntax/dracula.conf +44 -0
- novelwriter/assets/syntax/grey_dark.conf +5 -4
- novelwriter/assets/syntax/grey_light.conf +5 -4
- novelwriter/assets/syntax/light_owl.conf +7 -6
- novelwriter/assets/syntax/night_owl.conf +7 -6
- novelwriter/assets/syntax/snazzy.conf +42 -0
- novelwriter/assets/syntax/solarized_dark.conf +4 -3
- novelwriter/assets/syntax/solarized_light.conf +4 -3
- novelwriter/assets/syntax/tango.conf +27 -11
- novelwriter/assets/syntax/tomorrow.conf +6 -5
- novelwriter/assets/syntax/tomorrow_night.conf +7 -6
- novelwriter/assets/syntax/tomorrow_night_blue.conf +6 -5
- novelwriter/assets/syntax/tomorrow_night_bright.conf +6 -5
- novelwriter/assets/syntax/tomorrow_night_eighties.conf +6 -5
- novelwriter/assets/text/credits_en.htm +4 -1
- novelwriter/assets/themes/cyberpunk_night.conf +2 -0
- novelwriter/assets/themes/default_dark.conf +1 -0
- novelwriter/assets/themes/default_light.conf +1 -0
- novelwriter/assets/themes/dracula.conf +47 -0
- novelwriter/assets/themes/solarized_dark.conf +1 -0
- novelwriter/assets/themes/solarized_light.conf +1 -0
- novelwriter/common.py +31 -9
- novelwriter/config.py +118 -84
- novelwriter/constants.py +40 -26
- novelwriter/core/buildsettings.py +63 -66
- novelwriter/core/coretools.py +2 -22
- novelwriter/core/docbuild.py +51 -40
- novelwriter/core/document.py +3 -5
- novelwriter/core/index.py +115 -45
- novelwriter/core/item.py +8 -19
- novelwriter/core/options.py +2 -4
- novelwriter/core/project.py +23 -57
- novelwriter/core/projectdata.py +1 -3
- novelwriter/core/projectxml.py +12 -15
- novelwriter/core/sessions.py +3 -5
- novelwriter/core/spellcheck.py +4 -9
- novelwriter/core/status.py +211 -164
- novelwriter/core/storage.py +0 -8
- novelwriter/core/tohtml.py +94 -100
- novelwriter/core/tokenizer.py +199 -112
- novelwriter/core/{tomd.py → tomarkdown.py} +97 -78
- novelwriter/core/toodt.py +212 -148
- novelwriter/core/toqdoc.py +403 -0
- novelwriter/core/tree.py +5 -7
- novelwriter/dialogs/about.py +3 -5
- novelwriter/dialogs/docmerge.py +1 -3
- novelwriter/dialogs/docsplit.py +1 -3
- novelwriter/dialogs/editlabel.py +0 -2
- novelwriter/dialogs/preferences.py +111 -88
- novelwriter/dialogs/projectsettings.py +216 -180
- novelwriter/dialogs/quotes.py +3 -4
- novelwriter/dialogs/wordlist.py +3 -9
- novelwriter/enum.py +31 -25
- novelwriter/error.py +8 -15
- novelwriter/extensions/circularprogress.py +5 -6
- novelwriter/extensions/configlayout.py +18 -18
- novelwriter/extensions/eventfilters.py +1 -5
- novelwriter/extensions/modified.py +50 -13
- novelwriter/extensions/novelselector.py +1 -3
- novelwriter/extensions/pagedsidebar.py +9 -12
- novelwriter/extensions/simpleprogress.py +1 -3
- novelwriter/extensions/statusled.py +1 -3
- novelwriter/extensions/switch.py +4 -6
- novelwriter/extensions/switchbox.py +7 -6
- novelwriter/extensions/versioninfo.py +3 -9
- novelwriter/gui/doceditor.py +98 -126
- novelwriter/gui/dochighlight.py +237 -183
- novelwriter/gui/docviewer.py +46 -94
- novelwriter/gui/docviewerpanel.py +3 -10
- novelwriter/gui/editordocument.py +1 -3
- novelwriter/gui/itemdetails.py +7 -11
- novelwriter/gui/mainmenu.py +11 -7
- novelwriter/gui/noveltree.py +11 -24
- novelwriter/gui/outline.py +11 -23
- novelwriter/gui/projtree.py +26 -43
- novelwriter/gui/search.py +1 -3
- novelwriter/gui/sidebar.py +2 -6
- novelwriter/gui/statusbar.py +6 -10
- novelwriter/gui/theme.py +23 -48
- novelwriter/guimain.py +50 -71
- novelwriter/shared.py +30 -15
- novelwriter/tools/dictionaries.py +8 -12
- novelwriter/tools/lipsum.py +2 -4
- novelwriter/tools/manusbuild.py +1 -3
- novelwriter/tools/manuscript.py +66 -145
- novelwriter/tools/manussettings.py +67 -73
- novelwriter/tools/noveldetails.py +6 -11
- novelwriter/tools/welcome.py +2 -16
- novelwriter/tools/writingstats.py +6 -9
- novelwriter/types.py +45 -3
- {novelWriter-2.4.3.dist-info → novelWriter-2.5b1.dist-info}/LICENSE.md +0 -0
- {novelWriter-2.4.3.dist-info → novelWriter-2.5b1.dist-info}/WHEEL +0 -0
- {novelWriter-2.4.3.dist-info → novelWriter-2.5b1.dist-info}/entry_points.txt +0 -0
- {novelWriter-2.4.3.dist-info → novelWriter-2.5b1.dist-info}/top_level.txt +0 -0
novelwriter/core/toodt.py
CHANGED
@@ -29,17 +29,20 @@ from __future__ import annotations
|
|
29
29
|
import logging
|
30
30
|
import xml.etree.ElementTree as ET
|
31
31
|
|
32
|
+
from collections.abc import Sequence
|
33
|
+
from datetime import datetime
|
32
34
|
from hashlib import sha256
|
33
35
|
from pathlib import Path
|
34
36
|
from zipfile import ZipFile
|
35
|
-
|
36
|
-
from
|
37
|
+
|
38
|
+
from PyQt5.QtGui import QFont
|
37
39
|
|
38
40
|
from novelwriter import __version__
|
39
41
|
from novelwriter.common import xmlIndent
|
40
42
|
from novelwriter.constants import nwHeadFmt, nwKeyWords, nwLabels
|
41
43
|
from novelwriter.core.project import NWProject
|
42
|
-
from novelwriter.core.tokenizer import Tokenizer, stripEscape
|
44
|
+
from novelwriter.core.tokenizer import T_Formats, Tokenizer, stripEscape
|
45
|
+
from novelwriter.types import FONT_STYLE, FONT_WEIGHTS
|
43
46
|
|
44
47
|
logger = logging.getLogger(__name__)
|
45
48
|
|
@@ -96,6 +99,22 @@ M_MRK = ~X_MRK
|
|
96
99
|
M_SUP = ~X_SUP
|
97
100
|
M_SUB = ~X_SUB
|
98
101
|
|
102
|
+
# ODT Styles
|
103
|
+
S_TITLE = "Title"
|
104
|
+
S_HEAD1 = "Heading_20_1"
|
105
|
+
S_HEAD2 = "Heading_20_2"
|
106
|
+
S_HEAD3 = "Heading_20_3"
|
107
|
+
S_HEAD4 = "Heading_20_4"
|
108
|
+
S_SEP = "Separator"
|
109
|
+
S_FIND = "First_20_line_20_indent"
|
110
|
+
S_TEXT = "Text_20_body"
|
111
|
+
S_META = "Text_20_Meta"
|
112
|
+
S_HNF = "Header_20_and_20_Footer"
|
113
|
+
|
114
|
+
# Font Data
|
115
|
+
FONT_WEIGHT_NUM = ["100", "200", "300", "400", "500", "600", "700", "800", "900"]
|
116
|
+
FONT_WEIGHT_MAP = {"400": "normal", "700": "bold"}
|
117
|
+
|
99
118
|
|
100
119
|
class ToOdt(Tokenizer):
|
101
120
|
"""Core: Open Document Writer
|
@@ -130,20 +149,25 @@ class ToOdt(Tokenizer):
|
|
130
149
|
self._autoPara: dict[str, ODTParagraphStyle] = {} # Auto-generated paragraph styles
|
131
150
|
self._autoText: dict[int, ODTTextStyle] = {} # Auto-generated text styles
|
132
151
|
|
152
|
+
# Footnotes
|
153
|
+
self._nNote = 0
|
154
|
+
self._etNotes: dict[str, ET.Element] = {} # Generated note elements
|
155
|
+
|
133
156
|
self._errData = [] # List of errors encountered
|
134
157
|
|
135
158
|
# Properties
|
136
|
-
self._textFont = "Liberation Serif"
|
137
|
-
self._textSize = 12
|
138
|
-
self._textFixed = False
|
159
|
+
self._textFont = QFont("Liberation Serif", 12)
|
139
160
|
self._colourHead = False
|
140
|
-
self._firstIndent = False
|
141
161
|
self._headerFormat = ""
|
142
162
|
self._pageOffset = 0
|
143
163
|
|
144
164
|
# Internal
|
145
|
-
self._fontFamily = "
|
165
|
+
self._fontFamily = "Liberation Serif"
|
166
|
+
self._fontSize = 12
|
167
|
+
self._fontWeight = "normal"
|
168
|
+
self._fontStyle = "normal"
|
146
169
|
self._fontPitch = "variable"
|
170
|
+
self._fontBold = "bold"
|
147
171
|
self._fSizeTitle = "30pt"
|
148
172
|
self._fSizeHead1 = "24pt"
|
149
173
|
self._fSizeHead2 = "20pt"
|
@@ -151,6 +175,7 @@ class ToOdt(Tokenizer):
|
|
151
175
|
self._fSizeHead4 = "14pt"
|
152
176
|
self._fSizeHead = "14pt"
|
153
177
|
self._fSizeText = "12pt"
|
178
|
+
self._fSizeFoot = "10pt"
|
154
179
|
self._fLineHeight = "115%"
|
155
180
|
self._fBlockIndent = "1.693cm"
|
156
181
|
self._fTextIndent = "0.499cm"
|
@@ -167,6 +192,7 @@ class ToOdt(Tokenizer):
|
|
167
192
|
self._mTopHead = "0.423cm"
|
168
193
|
self._mTopText = "0.000cm"
|
169
194
|
self._mTopMeta = "0.000cm"
|
195
|
+
self._mTopSep = "0.247cm"
|
170
196
|
|
171
197
|
self._mBotTitle = "0.212cm"
|
172
198
|
self._mBotHead1 = "0.212cm"
|
@@ -176,6 +202,10 @@ class ToOdt(Tokenizer):
|
|
176
202
|
self._mBotHead = "0.212cm"
|
177
203
|
self._mBotText = "0.247cm"
|
178
204
|
self._mBotMeta = "0.106cm"
|
205
|
+
self._mBotSep = "0.247cm"
|
206
|
+
|
207
|
+
self._mBotFoot = "0.106cm"
|
208
|
+
self._mLeftFoot = "0.600cm"
|
179
209
|
|
180
210
|
# Document Size and Margins
|
181
211
|
self._mDocWidth = "21.0cm"
|
@@ -232,11 +262,6 @@ class ToOdt(Tokenizer):
|
|
232
262
|
self._pageOffset = offset
|
233
263
|
return
|
234
264
|
|
235
|
-
def setFirstLineIndent(self, state: bool) -> None:
|
236
|
-
"""Enable or disable first line indent."""
|
237
|
-
self._firstIndent = state
|
238
|
-
return
|
239
|
-
|
240
265
|
##
|
241
266
|
# Class Methods
|
242
267
|
##
|
@@ -246,18 +271,25 @@ class ToOdt(Tokenizer):
|
|
246
271
|
# Initialise Variables
|
247
272
|
# ====================
|
248
273
|
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
self.
|
255
|
-
self.
|
256
|
-
self.
|
257
|
-
self.
|
258
|
-
self.
|
259
|
-
|
260
|
-
self.
|
274
|
+
intWeight = FONT_WEIGHTS.get(self._textFont.weight(), 400)
|
275
|
+
fontWeight = str(intWeight)
|
276
|
+
fontBold = str(min(intWeight + 300, 900))
|
277
|
+
|
278
|
+
self._fontFamily = self._textFont.family()
|
279
|
+
self._fontSize = self._textFont.pointSize()
|
280
|
+
self._fontWeight = FONT_WEIGHT_MAP.get(fontWeight, fontWeight)
|
281
|
+
self._fontStyle = FONT_STYLE.get(self._textFont.style(), "normal")
|
282
|
+
self._fontPitch = "fixed" if self._textFont.fixedPitch() else "variable"
|
283
|
+
self._fontBold = FONT_WEIGHT_MAP.get(fontBold, fontBold)
|
284
|
+
|
285
|
+
self._fSizeTitle = f"{round(2.50 * self._fontSize):d}pt"
|
286
|
+
self._fSizeHead1 = f"{round(2.00 * self._fontSize):d}pt"
|
287
|
+
self._fSizeHead2 = f"{round(1.60 * self._fontSize):d}pt"
|
288
|
+
self._fSizeHead3 = f"{round(1.30 * self._fontSize):d}pt"
|
289
|
+
self._fSizeHead4 = f"{round(1.15 * self._fontSize):d}pt"
|
290
|
+
self._fSizeHead = f"{round(1.15 * self._fontSize):d}pt"
|
291
|
+
self._fSizeText = f"{self._fontSize:d}pt"
|
292
|
+
self._fSizeFoot = f"{round(0.8*self._fontSize):d}pt"
|
261
293
|
|
262
294
|
mScale = self._lineHeight/1.15
|
263
295
|
|
@@ -269,6 +301,7 @@ class ToOdt(Tokenizer):
|
|
269
301
|
self._mTopHead = self._emToCm(mScale * self._marginHead4[0])
|
270
302
|
self._mTopText = self._emToCm(mScale * self._marginText[0])
|
271
303
|
self._mTopMeta = self._emToCm(mScale * self._marginMeta[0])
|
304
|
+
self._mTopSep = self._emToCm(mScale * self._marginSep[0])
|
272
305
|
|
273
306
|
self._mBotTitle = self._emToCm(mScale * self._marginTitle[1])
|
274
307
|
self._mBotHead1 = self._emToCm(mScale * self._marginHead1[1])
|
@@ -278,6 +311,10 @@ class ToOdt(Tokenizer):
|
|
278
311
|
self._mBotHead = self._emToCm(mScale * self._marginHead4[1])
|
279
312
|
self._mBotText = self._emToCm(mScale * self._marginText[1])
|
280
313
|
self._mBotMeta = self._emToCm(mScale * self._marginMeta[1])
|
314
|
+
self._mBotSep = self._emToCm(mScale * self._marginSep[1])
|
315
|
+
|
316
|
+
self._mLeftFoot = self._emToCm(self._marginFoot[0])
|
317
|
+
self._mBotFoot = self._emToCm(self._marginFoot[1])
|
281
318
|
|
282
319
|
if self._colourHead:
|
283
320
|
self._colHead12 = "#2a6099"
|
@@ -289,6 +326,7 @@ class ToOdt(Tokenizer):
|
|
289
326
|
|
290
327
|
self._fLineHeight = f"{round(100 * self._lineHeight):d}%"
|
291
328
|
self._fBlockIndent = self._emToCm(self._blockIndent)
|
329
|
+
self._fTextIndent = self._emToCm(self._firstWidth)
|
292
330
|
self._textAlign = "justify" if self._doJustify else "left"
|
293
331
|
|
294
332
|
# Clear Errors
|
@@ -301,7 +339,7 @@ class ToOdt(Tokenizer):
|
|
301
339
|
tAttr[_mkTag("office", "version")] = X_VERS
|
302
340
|
|
303
341
|
fAttr = {}
|
304
|
-
fAttr[_mkTag("style", "name")] = self.
|
342
|
+
fAttr[_mkTag("style", "name")] = self._fontFamily
|
305
343
|
fAttr[_mkTag("style", "font-pitch")] = self._fontPitch
|
306
344
|
|
307
345
|
if self._isFlat:
|
@@ -399,9 +437,7 @@ class ToOdt(Tokenizer):
|
|
399
437
|
"""Convert the list of text tokens into XML elements."""
|
400
438
|
self._result = "" # Not used, but cleared just in case
|
401
439
|
|
402
|
-
|
403
|
-
pText = []
|
404
|
-
pStyle = None
|
440
|
+
xText = self._xText
|
405
441
|
pIndent = True
|
406
442
|
for tType, _, tText, tFormat, tStyle in self._tokens:
|
407
443
|
|
@@ -433,82 +469,59 @@ class ToOdt(Tokenizer):
|
|
433
469
|
if tStyle & self.A_IND_R:
|
434
470
|
oStyle.setMarginRight(self._fBlockIndent)
|
435
471
|
|
436
|
-
if
|
472
|
+
if not self._indentFirst and tType in self.L_SKIP_INDENT:
|
437
473
|
pIndent = False
|
438
474
|
|
439
475
|
# Process Text Types
|
440
|
-
if tType == self.
|
441
|
-
if
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
tTxt = ""
|
447
|
-
tFmt = []
|
448
|
-
for nText, nFmt in zip(pText, pFmt):
|
449
|
-
tLen = len(tTxt)
|
450
|
-
tTxt += f"{nText}\n"
|
451
|
-
tFmt.extend((p+tLen, fmt) for p, fmt in nFmt)
|
452
|
-
|
453
|
-
# Don't indent a paragraph if it has alignment set
|
454
|
-
tIndent = self._firstIndent and pIndent and pStyle.isUnaligned()
|
455
|
-
self._addTextPar(
|
456
|
-
"First_20_line_20_indent" if tIndent else "Text_20_body",
|
457
|
-
pStyle, tTxt.rstrip(), tFmt=tFmt
|
458
|
-
)
|
459
|
-
pIndent = True
|
460
|
-
|
461
|
-
pFmt = []
|
462
|
-
pText = []
|
463
|
-
pStyle = None
|
476
|
+
if tType == self.T_TEXT:
|
477
|
+
if self._firstIndent and pIndent and oStyle.isUnaligned():
|
478
|
+
self._addTextPar(xText, S_FIND, oStyle, tText, tFmt=tFormat)
|
479
|
+
else:
|
480
|
+
self._addTextPar(xText, S_TEXT, oStyle, tText, tFmt=tFormat)
|
481
|
+
pIndent = True
|
464
482
|
|
465
483
|
elif tType == self.T_TITLE:
|
484
|
+
# Title must be text:p
|
466
485
|
tHead = tText.replace(nwHeadFmt.BR, "\n")
|
467
|
-
self._addTextPar(
|
486
|
+
self._addTextPar(xText, S_TITLE, oStyle, tHead, isHead=False)
|
468
487
|
|
469
488
|
elif tType == self.T_HEAD1:
|
470
489
|
tHead = tText.replace(nwHeadFmt.BR, "\n")
|
471
|
-
self._addTextPar(
|
490
|
+
self._addTextPar(xText, S_HEAD1, oStyle, tHead, isHead=True, oLevel="1")
|
472
491
|
|
473
492
|
elif tType == self.T_HEAD2:
|
474
493
|
tHead = tText.replace(nwHeadFmt.BR, "\n")
|
475
|
-
self._addTextPar(
|
494
|
+
self._addTextPar(xText, S_HEAD2, oStyle, tHead, isHead=True, oLevel="2")
|
476
495
|
|
477
496
|
elif tType == self.T_HEAD3:
|
478
497
|
tHead = tText.replace(nwHeadFmt.BR, "\n")
|
479
|
-
self._addTextPar(
|
498
|
+
self._addTextPar(xText, S_HEAD3, oStyle, tHead, isHead=True, oLevel="3")
|
480
499
|
|
481
500
|
elif tType == self.T_HEAD4:
|
482
501
|
tHead = tText.replace(nwHeadFmt.BR, "\n")
|
483
|
-
self._addTextPar(
|
502
|
+
self._addTextPar(xText, S_HEAD4, oStyle, tHead, isHead=True, oLevel="4")
|
484
503
|
|
485
504
|
elif tType == self.T_SEP:
|
486
|
-
self._addTextPar(
|
505
|
+
self._addTextPar(xText, S_SEP, oStyle, tText)
|
487
506
|
|
488
507
|
elif tType == self.T_SKIP:
|
489
|
-
self._addTextPar(
|
490
|
-
|
491
|
-
elif tType == self.T_TEXT:
|
492
|
-
if pStyle is None:
|
493
|
-
pStyle = oStyle
|
494
|
-
pText.append(tText)
|
495
|
-
pFmt.append(tFormat)
|
508
|
+
self._addTextPar(xText, S_TEXT, oStyle, "")
|
496
509
|
|
497
510
|
elif tType == self.T_SYNOPSIS and self._doSynopsis:
|
498
|
-
tTemp,
|
499
|
-
self._addTextPar(
|
511
|
+
tTemp, tFmt = self._formatSynopsis(tText, tFormat, True)
|
512
|
+
self._addTextPar(xText, S_META, oStyle, tTemp, tFmt=tFmt)
|
500
513
|
|
501
514
|
elif tType == self.T_SHORT and self._doSynopsis:
|
502
|
-
tTemp,
|
503
|
-
self._addTextPar(
|
515
|
+
tTemp, tFmt = self._formatSynopsis(tText, tFormat, False)
|
516
|
+
self._addTextPar(xText, S_META, oStyle, tTemp, tFmt=tFmt)
|
504
517
|
|
505
518
|
elif tType == self.T_COMMENT and self._doComments:
|
506
|
-
tTemp,
|
507
|
-
self._addTextPar(
|
519
|
+
tTemp, tFmt = self._formatComments(tText, tFormat)
|
520
|
+
self._addTextPar(xText, S_META, oStyle, tTemp, tFmt=tFmt)
|
508
521
|
|
509
522
|
elif tType == self.T_KEYWORD and self._doKeywords:
|
510
|
-
tTemp,
|
511
|
-
self._addTextPar(
|
523
|
+
tTemp, tFmt = self._formatKeywords(tText)
|
524
|
+
self._addTextPar(xText, S_META, oStyle, tTemp, tFmt=tFmt)
|
512
525
|
|
513
526
|
return
|
514
527
|
|
@@ -569,28 +582,32 @@ class ToOdt(Tokenizer):
|
|
569
582
|
# Internal Functions
|
570
583
|
##
|
571
584
|
|
572
|
-
def _formatSynopsis(self, text: str, synopsis: bool) -> tuple[str,
|
585
|
+
def _formatSynopsis(self, text: str, fmt: T_Formats, synopsis: bool) -> tuple[str, T_Formats]:
|
573
586
|
"""Apply formatting to synopsis lines."""
|
574
587
|
name = self._localLookup("Synopsis" if synopsis else "Short Description")
|
588
|
+
shift = len(name) + 2
|
575
589
|
rTxt = f"{name}: {text}"
|
576
|
-
rFmt = [(0, self.FMT_B_B), (len(name) + 1, self.FMT_B_E)]
|
590
|
+
rFmt: T_Formats = [(0, self.FMT_B_B, ""), (len(name) + 1, self.FMT_B_E, "")]
|
591
|
+
rFmt.extend((p + shift, f, d) for p, f, d in fmt)
|
577
592
|
return rTxt, rFmt
|
578
593
|
|
579
|
-
def _formatComments(self, text: str) -> tuple[str,
|
594
|
+
def _formatComments(self, text: str, fmt: T_Formats) -> tuple[str, T_Formats]:
|
580
595
|
"""Apply formatting to comments."""
|
581
596
|
name = self._localLookup("Comment")
|
597
|
+
shift = len(name) + 2
|
582
598
|
rTxt = f"{name}: {text}"
|
583
|
-
rFmt = [(0, self.FMT_B_B), (len(name) + 1, self.FMT_B_E)]
|
599
|
+
rFmt: T_Formats = [(0, self.FMT_B_B, ""), (len(name) + 1, self.FMT_B_E, "")]
|
600
|
+
rFmt.extend((p + shift, f, d) for p, f, d in fmt)
|
584
601
|
return rTxt, rFmt
|
585
602
|
|
586
|
-
def _formatKeywords(self, text: str) -> tuple[str,
|
603
|
+
def _formatKeywords(self, text: str) -> tuple[str, T_Formats]:
|
587
604
|
"""Apply formatting to keywords."""
|
588
605
|
valid, bits, _ = self._project.index.scanThis("@"+text)
|
589
606
|
if not valid or not bits or bits[0] not in nwLabels.KEY_NAME:
|
590
607
|
return "", []
|
591
608
|
|
592
609
|
rTxt = f"{self._localLookup(nwLabels.KEY_NAME[bits[0]])}: "
|
593
|
-
rFmt = [(0, self.FMT_B_B), (len(rTxt) - 1, self.FMT_B_E)]
|
610
|
+
rFmt: T_Formats = [(0, self.FMT_B_B, ""), (len(rTxt) - 1, self.FMT_B_E, "")]
|
594
611
|
if len(bits) > 1:
|
595
612
|
if bits[0] == nwKeyWords.TAG_KEY:
|
596
613
|
rTxt += bits[1]
|
@@ -600,8 +617,8 @@ class ToOdt(Tokenizer):
|
|
600
617
|
return rTxt, rFmt
|
601
618
|
|
602
619
|
def _addTextPar(
|
603
|
-
self, styleName: str, oStyle: ODTParagraphStyle, tText: str,
|
604
|
-
tFmt: Sequence[tuple[int, int]] = [], isHead: bool = False, oLevel: str | None = None
|
620
|
+
self, xParent: ET.Element, styleName: str, oStyle: ODTParagraphStyle, tText: str,
|
621
|
+
tFmt: Sequence[tuple[int, int, str]] = [], isHead: bool = False, oLevel: str | None = None
|
605
622
|
) -> None:
|
606
623
|
"""Add a text paragraph to the text XML element."""
|
607
624
|
tAttr = {_mkTag("text", "style-name"): self._paraStyle(styleName, oStyle)}
|
@@ -609,7 +626,7 @@ class ToOdt(Tokenizer):
|
|
609
626
|
tAttr[_mkTag("text", "outline-level")] = oLevel
|
610
627
|
|
611
628
|
pTag = "h" if isHead else "p"
|
612
|
-
xElem = ET.SubElement(
|
629
|
+
xElem = ET.SubElement(xParent, _mkTag("text", pTag), attrib=tAttr)
|
613
630
|
|
614
631
|
# It's important to set the initial text field to empty, otherwise
|
615
632
|
# xmlIndent will add a line break if the first subelement is a span.
|
@@ -627,7 +644,13 @@ class ToOdt(Tokenizer):
|
|
627
644
|
xFmt = 0x00
|
628
645
|
tFrag = ""
|
629
646
|
fLast = 0
|
630
|
-
|
647
|
+
xNode = None
|
648
|
+
for fPos, fFmt, fData in tFmt:
|
649
|
+
|
650
|
+
# Add any extra nodes
|
651
|
+
if xNode is not None:
|
652
|
+
parProc.appendNode(xNode)
|
653
|
+
xNode = None
|
631
654
|
|
632
655
|
# Add the text up to the current fragment
|
633
656
|
if tFrag := tText[fLast:fPos]:
|
@@ -665,11 +688,18 @@ class ToOdt(Tokenizer):
|
|
665
688
|
xFmt |= X_SUB
|
666
689
|
elif fFmt == self.FMT_SUB_E:
|
667
690
|
xFmt &= M_SUB
|
691
|
+
elif fFmt == self.FMT_FNOTE:
|
692
|
+
xNode = self._generateFootnote(fData)
|
693
|
+
elif fFmt == self.FMT_STRIP:
|
694
|
+
pass
|
668
695
|
else:
|
669
696
|
pErr += 1
|
670
697
|
|
671
698
|
fLast = fPos
|
672
699
|
|
700
|
+
if xNode is not None:
|
701
|
+
parProc.appendNode(xNode)
|
702
|
+
|
673
703
|
if tFrag := tText[fLast:]:
|
674
704
|
if xFmt == 0x00:
|
675
705
|
parProc.appendText(tFrag)
|
@@ -715,7 +745,7 @@ class ToOdt(Tokenizer):
|
|
715
745
|
|
716
746
|
style = ODTTextStyle(f"T{len(self._autoText)+1:d}")
|
717
747
|
if hFmt & X_BLD:
|
718
|
-
style.setFontWeight(
|
748
|
+
style.setFontWeight(self._fontBold)
|
719
749
|
if hFmt & X_ITA:
|
720
750
|
style.setFontStyle("italic")
|
721
751
|
if hFmt & X_DEL:
|
@@ -735,9 +765,25 @@ class ToOdt(Tokenizer):
|
|
735
765
|
|
736
766
|
return style.name
|
737
767
|
|
768
|
+
def _generateFootnote(self, key: str) -> ET.Element | None:
|
769
|
+
"""Generate a footnote XML object."""
|
770
|
+
if content := self._footnotes.get(key):
|
771
|
+
self._nNote += 1
|
772
|
+
nStyle = ODTParagraphStyle("New")
|
773
|
+
xNote = ET.Element(_mkTag("text", "note"), attrib={
|
774
|
+
_mkTag("text", "id"): f"ftn{self._nNote}",
|
775
|
+
_mkTag("text", "note-class"): "footnote",
|
776
|
+
})
|
777
|
+
xCite = ET.SubElement(xNote, _mkTag("text", "note-citation"))
|
778
|
+
xCite.text = str(self._nNote)
|
779
|
+
xBody = ET.SubElement(xNote, _mkTag("text", "note-body"))
|
780
|
+
self._addTextPar(xBody, "Footnote", nStyle, content[0], tFmt=content[1])
|
781
|
+
return xNote
|
782
|
+
return None
|
783
|
+
|
738
784
|
def _emToCm(self, value: float) -> str:
|
739
785
|
"""Converts an em value to centimetres."""
|
740
|
-
return f"{value*2.54/72*self.
|
786
|
+
return f"{value*2.54/72*self._fontSize:.3f}cm"
|
741
787
|
|
742
788
|
##
|
743
789
|
# Style Elements
|
@@ -757,7 +803,6 @@ class ToOdt(Tokenizer):
|
|
757
803
|
_mkTag("fo", "margin-bottom"): self._mDocBtm,
|
758
804
|
_mkTag("fo", "margin-left"): self._mDocLeft,
|
759
805
|
_mkTag("fo", "margin-right"): self._mDocRight,
|
760
|
-
_mkTag("fo", "print-orientation"): "portrait",
|
761
806
|
})
|
762
807
|
|
763
808
|
xHead = ET.SubElement(xPage, _mkTag("style", "header-style"))
|
@@ -782,8 +827,10 @@ class ToOdt(Tokenizer):
|
|
782
827
|
_mkTag("style", "writing-mode"): "page",
|
783
828
|
})
|
784
829
|
ET.SubElement(xStyl, _mkTag("style", "text-properties"), attrib={
|
785
|
-
_mkTag("style", "font-name"): self.
|
830
|
+
_mkTag("style", "font-name"): self._fontFamily,
|
786
831
|
_mkTag("fo", "font-family"): self._fontFamily,
|
832
|
+
_mkTag("fo", "font-weight"): self._fontWeight,
|
833
|
+
_mkTag("fo", "font-style"): self._fontStyle,
|
787
834
|
_mkTag("fo", "font-size"): self._fSizeText,
|
788
835
|
_mkTag("fo", "language"): self._dLanguage,
|
789
836
|
_mkTag("fo", "country"): self._dCountry,
|
@@ -796,8 +843,10 @@ class ToOdt(Tokenizer):
|
|
796
843
|
_mkTag("style", "class"): "text",
|
797
844
|
})
|
798
845
|
ET.SubElement(xStyl, _mkTag("style", "text-properties"), attrib={
|
799
|
-
_mkTag("style", "font-name"): self.
|
846
|
+
_mkTag("style", "font-name"): self._fontFamily,
|
800
847
|
_mkTag("fo", "font-family"): self._fontFamily,
|
848
|
+
_mkTag("fo", "font-weight"): self._fontWeight,
|
849
|
+
_mkTag("fo", "font-style"): self._fontStyle,
|
801
850
|
_mkTag("fo", "font-size"): self._fSizeText,
|
802
851
|
})
|
803
852
|
|
@@ -806,7 +855,7 @@ class ToOdt(Tokenizer):
|
|
806
855
|
_mkTag("style", "name"): "Heading",
|
807
856
|
_mkTag("style", "family"): "paragraph",
|
808
857
|
_mkTag("style", "parent-style-name"): "Standard",
|
809
|
-
_mkTag("style", "next-style-name"):
|
858
|
+
_mkTag("style", "next-style-name"): S_TEXT,
|
810
859
|
_mkTag("style", "class"): "text",
|
811
860
|
})
|
812
861
|
ET.SubElement(xStyl, _mkTag("style", "paragraph-properties"), attrib={
|
@@ -815,14 +864,16 @@ class ToOdt(Tokenizer):
|
|
815
864
|
_mkTag("fo", "keep-with-next"): "always",
|
816
865
|
})
|
817
866
|
ET.SubElement(xStyl, _mkTag("style", "text-properties"), attrib={
|
818
|
-
_mkTag("style", "font-name"): self.
|
867
|
+
_mkTag("style", "font-name"): self._fontFamily,
|
819
868
|
_mkTag("fo", "font-family"): self._fontFamily,
|
869
|
+
_mkTag("fo", "font-weight"): self._fontWeight,
|
870
|
+
_mkTag("fo", "font-style"): self._fontStyle,
|
820
871
|
_mkTag("fo", "font-size"): self._fSizeHead,
|
821
872
|
})
|
822
873
|
|
823
874
|
# Add Header and Footer Styles
|
824
875
|
ET.SubElement(self._xStyl, _mkTag("style", "style"), attrib={
|
825
|
-
_mkTag("style", "name"):
|
876
|
+
_mkTag("style", "name"): S_HNF,
|
826
877
|
_mkTag("style", "display-name"): "Header and Footer",
|
827
878
|
_mkTag("style", "family"): "paragraph",
|
828
879
|
_mkTag("style", "parent-style-name"): "Standard",
|
@@ -834,7 +885,7 @@ class ToOdt(Tokenizer):
|
|
834
885
|
def _useableStyles(self) -> None:
|
835
886
|
"""Set the usable styles."""
|
836
887
|
# Add Text Body Style
|
837
|
-
style = ODTParagraphStyle(
|
888
|
+
style = ODTParagraphStyle(S_TEXT)
|
838
889
|
style.setDisplayName("Text body")
|
839
890
|
style.setParentStyleName("Standard")
|
840
891
|
style.setClass("text")
|
@@ -842,136 +893,139 @@ class ToOdt(Tokenizer):
|
|
842
893
|
style.setMarginBottom(self._mBotText)
|
843
894
|
style.setLineHeight(self._fLineHeight)
|
844
895
|
style.setTextAlign(self._textAlign)
|
845
|
-
style.setFontName(self.
|
896
|
+
style.setFontName(self._fontFamily)
|
846
897
|
style.setFontFamily(self._fontFamily)
|
847
898
|
style.setFontSize(self._fSizeText)
|
899
|
+
style.setFontWeight(self._fontWeight)
|
848
900
|
style.packXML(self._xStyl)
|
849
901
|
self._mainPara[style.name] = style
|
850
902
|
|
851
903
|
# Add First Line Indent Style
|
852
|
-
style = ODTParagraphStyle(
|
904
|
+
style = ODTParagraphStyle(S_FIND)
|
853
905
|
style.setDisplayName("First line indent")
|
854
|
-
style.setParentStyleName(
|
906
|
+
style.setParentStyleName(S_TEXT)
|
855
907
|
style.setClass("text")
|
856
908
|
style.setTextIndent(self._fTextIndent)
|
857
909
|
style.packXML(self._xStyl)
|
858
910
|
self._mainPara[style.name] = style
|
859
911
|
|
860
912
|
# Add Text Meta Style
|
861
|
-
style = ODTParagraphStyle(
|
913
|
+
style = ODTParagraphStyle(S_META)
|
862
914
|
style.setDisplayName("Text Meta")
|
863
915
|
style.setParentStyleName("Standard")
|
864
916
|
style.setClass("text")
|
865
917
|
style.setMarginTop(self._mTopMeta)
|
866
918
|
style.setMarginBottom(self._mBotMeta)
|
867
919
|
style.setLineHeight(self._fLineHeight)
|
868
|
-
style.setFontName(self.
|
920
|
+
style.setFontName(self._fontFamily)
|
869
921
|
style.setFontFamily(self._fontFamily)
|
870
922
|
style.setFontSize(self._fSizeText)
|
923
|
+
style.setFontWeight(self._fontWeight)
|
871
924
|
style.setColour(self._colMetaTx)
|
872
925
|
style.setOpacity(self._opaMetaTx)
|
873
926
|
style.packXML(self._xStyl)
|
874
927
|
self._mainPara[style.name] = style
|
875
928
|
|
876
929
|
# Add Title Style
|
877
|
-
style = ODTParagraphStyle(
|
930
|
+
style = ODTParagraphStyle(S_TITLE)
|
878
931
|
style.setDisplayName("Title")
|
879
932
|
style.setParentStyleName("Heading")
|
880
|
-
style.setNextStyleName(
|
933
|
+
style.setNextStyleName(S_TEXT)
|
881
934
|
style.setClass("chapter")
|
882
935
|
style.setMarginTop(self._mTopTitle)
|
883
936
|
style.setMarginBottom(self._mBotTitle)
|
884
937
|
style.setTextAlign("center")
|
885
|
-
style.setFontName(self.
|
938
|
+
style.setFontName(self._fontFamily)
|
886
939
|
style.setFontFamily(self._fontFamily)
|
887
940
|
style.setFontSize(self._fSizeTitle)
|
888
|
-
style.setFontWeight(
|
941
|
+
style.setFontWeight(self._fontBold)
|
889
942
|
style.packXML(self._xStyl)
|
890
943
|
self._mainPara[style.name] = style
|
891
944
|
|
892
945
|
# Add Separator Style
|
893
|
-
style = ODTParagraphStyle(
|
946
|
+
style = ODTParagraphStyle(S_SEP)
|
894
947
|
style.setDisplayName("Separator")
|
895
948
|
style.setParentStyleName("Standard")
|
896
|
-
style.setNextStyleName(
|
949
|
+
style.setNextStyleName(S_TEXT)
|
897
950
|
style.setClass("text")
|
898
|
-
style.setMarginTop(self.
|
899
|
-
style.setMarginBottom(self.
|
951
|
+
style.setMarginTop(self._mTopSep)
|
952
|
+
style.setMarginBottom(self._mBotSep)
|
900
953
|
style.setLineHeight(self._fLineHeight)
|
901
954
|
style.setTextAlign("center")
|
902
|
-
style.setFontName(self.
|
955
|
+
style.setFontName(self._fontFamily)
|
903
956
|
style.setFontFamily(self._fontFamily)
|
904
957
|
style.setFontSize(self._fSizeText)
|
958
|
+
style.setFontWeight(self._fontWeight)
|
905
959
|
style.packXML(self._xStyl)
|
906
960
|
self._mainPara[style.name] = style
|
907
961
|
|
908
962
|
# Add Heading 1 Style
|
909
|
-
style = ODTParagraphStyle(
|
963
|
+
style = ODTParagraphStyle(S_HEAD1)
|
910
964
|
style.setDisplayName("Heading 1")
|
911
965
|
style.setParentStyleName("Heading")
|
912
|
-
style.setNextStyleName(
|
966
|
+
style.setNextStyleName(S_TEXT)
|
913
967
|
style.setOutlineLevel("1")
|
914
968
|
style.setClass("text")
|
915
969
|
style.setMarginTop(self._mTopHead1)
|
916
970
|
style.setMarginBottom(self._mBotHead1)
|
917
|
-
style.setFontName(self.
|
971
|
+
style.setFontName(self._fontFamily)
|
918
972
|
style.setFontFamily(self._fontFamily)
|
919
973
|
style.setFontSize(self._fSizeHead1)
|
920
|
-
style.setFontWeight(
|
974
|
+
style.setFontWeight(self._fontBold)
|
921
975
|
style.setColour(self._colHead12)
|
922
976
|
style.setOpacity(self._opaHead12)
|
923
977
|
style.packXML(self._xStyl)
|
924
978
|
self._mainPara[style.name] = style
|
925
979
|
|
926
980
|
# Add Heading 2 Style
|
927
|
-
style = ODTParagraphStyle(
|
981
|
+
style = ODTParagraphStyle(S_HEAD2)
|
928
982
|
style.setDisplayName("Heading 2")
|
929
983
|
style.setParentStyleName("Heading")
|
930
|
-
style.setNextStyleName(
|
984
|
+
style.setNextStyleName(S_TEXT)
|
931
985
|
style.setOutlineLevel("2")
|
932
986
|
style.setClass("text")
|
933
987
|
style.setMarginTop(self._mTopHead2)
|
934
988
|
style.setMarginBottom(self._mBotHead2)
|
935
|
-
style.setFontName(self.
|
989
|
+
style.setFontName(self._fontFamily)
|
936
990
|
style.setFontFamily(self._fontFamily)
|
937
991
|
style.setFontSize(self._fSizeHead2)
|
938
|
-
style.setFontWeight(
|
992
|
+
style.setFontWeight(self._fontBold)
|
939
993
|
style.setColour(self._colHead12)
|
940
994
|
style.setOpacity(self._opaHead12)
|
941
995
|
style.packXML(self._xStyl)
|
942
996
|
self._mainPara[style.name] = style
|
943
997
|
|
944
998
|
# Add Heading 3 Style
|
945
|
-
style = ODTParagraphStyle(
|
999
|
+
style = ODTParagraphStyle(S_HEAD3)
|
946
1000
|
style.setDisplayName("Heading 3")
|
947
1001
|
style.setParentStyleName("Heading")
|
948
|
-
style.setNextStyleName(
|
1002
|
+
style.setNextStyleName(S_TEXT)
|
949
1003
|
style.setOutlineLevel("3")
|
950
1004
|
style.setClass("text")
|
951
1005
|
style.setMarginTop(self._mTopHead3)
|
952
1006
|
style.setMarginBottom(self._mBotHead3)
|
953
|
-
style.setFontName(self.
|
1007
|
+
style.setFontName(self._fontFamily)
|
954
1008
|
style.setFontFamily(self._fontFamily)
|
955
1009
|
style.setFontSize(self._fSizeHead3)
|
956
|
-
style.setFontWeight(
|
1010
|
+
style.setFontWeight(self._fontBold)
|
957
1011
|
style.setColour(self._colHead34)
|
958
1012
|
style.setOpacity(self._opaHead34)
|
959
1013
|
style.packXML(self._xStyl)
|
960
1014
|
self._mainPara[style.name] = style
|
961
1015
|
|
962
1016
|
# Add Heading 4 Style
|
963
|
-
style = ODTParagraphStyle(
|
1017
|
+
style = ODTParagraphStyle(S_HEAD4)
|
964
1018
|
style.setDisplayName("Heading 4")
|
965
1019
|
style.setParentStyleName("Heading")
|
966
|
-
style.setNextStyleName(
|
1020
|
+
style.setNextStyleName(S_TEXT)
|
967
1021
|
style.setOutlineLevel("4")
|
968
1022
|
style.setClass("text")
|
969
1023
|
style.setMarginTop(self._mTopHead4)
|
970
1024
|
style.setMarginBottom(self._mBotHead4)
|
971
|
-
style.setFontName(self.
|
1025
|
+
style.setFontName(self._fontFamily)
|
972
1026
|
style.setFontFamily(self._fontFamily)
|
973
1027
|
style.setFontSize(self._fSizeHead4)
|
974
|
-
style.setFontWeight(
|
1028
|
+
style.setFontWeight(self._fontBold)
|
975
1029
|
style.setColour(self._colHead34)
|
976
1030
|
style.setOpacity(self._opaHead34)
|
977
1031
|
style.packXML(self._xStyl)
|
@@ -980,11 +1034,23 @@ class ToOdt(Tokenizer):
|
|
980
1034
|
# Add Header Style
|
981
1035
|
style = ODTParagraphStyle("Header")
|
982
1036
|
style.setDisplayName("Header")
|
983
|
-
style.setParentStyleName(
|
1037
|
+
style.setParentStyleName(S_HNF)
|
984
1038
|
style.setTextAlign("right")
|
985
1039
|
style.packXML(self._xStyl)
|
986
1040
|
self._mainPara[style.name] = style
|
987
1041
|
|
1042
|
+
# Add Footnote Style
|
1043
|
+
style = ODTParagraphStyle("Footnote")
|
1044
|
+
style.setDisplayName("Footnote")
|
1045
|
+
style.setParentStyleName("Standard")
|
1046
|
+
style.setClass("extra")
|
1047
|
+
style.setMarginLeft(self._mLeftFoot)
|
1048
|
+
style.setMarginBottom(self._mBotFoot)
|
1049
|
+
style.setTextIndent("-"+self._mLeftFoot)
|
1050
|
+
style.setFontSize(self._fSizeFoot)
|
1051
|
+
style.packXML(self._xStyl)
|
1052
|
+
self._mainPara[style.name] = style
|
1053
|
+
|
988
1054
|
return
|
989
1055
|
|
990
1056
|
def _writeHeader(self) -> None:
|
@@ -1026,12 +1092,9 @@ class ToOdt(Tokenizer):
|
|
1026
1092
|
|
1027
1093
|
return
|
1028
1094
|
|
1029
|
-
# END Class ToOdt
|
1030
|
-
|
1031
1095
|
|
1032
|
-
#
|
1033
|
-
#
|
1034
|
-
# =============================================================================================== #
|
1096
|
+
# Auto-Style Classes
|
1097
|
+
# ==================
|
1035
1098
|
|
1036
1099
|
class ODTParagraphStyle:
|
1037
1100
|
"""Wrapper class for the paragraph style setting used by the
|
@@ -1041,8 +1104,8 @@ class ODTParagraphStyle:
|
|
1041
1104
|
VALID_ALIGN = ["start", "center", "end", "justify", "inside", "outside", "left", "right"]
|
1042
1105
|
VALID_BREAK = ["auto", "column", "page", "even-page", "odd-page", "inherit"]
|
1043
1106
|
VALID_LEVEL = ["1", "2", "3", "4"]
|
1044
|
-
VALID_CLASS = ["text", "chapter"]
|
1045
|
-
VALID_WEIGHT = ["normal", "
|
1107
|
+
VALID_CLASS = ["text", "chapter", "extra"]
|
1108
|
+
VALID_WEIGHT = ["normal", "bold"] + FONT_WEIGHT_NUM
|
1046
1109
|
|
1047
1110
|
def __init__(self, name: str) -> None:
|
1048
1111
|
|
@@ -1279,16 +1342,14 @@ class ODTParagraphStyle:
|
|
1279
1342
|
|
1280
1343
|
return
|
1281
1344
|
|
1282
|
-
# END Class ODTParagraphStyle
|
1283
|
-
|
1284
1345
|
|
1285
1346
|
class ODTTextStyle:
|
1286
1347
|
"""Wrapper class for the text style setting used by the exporter.
|
1287
1348
|
Only the used settings are exposed here to keep the class minimal
|
1288
1349
|
and fast.
|
1289
1350
|
"""
|
1290
|
-
VALID_WEIGHT = ["normal", "
|
1291
|
-
VALID_STYLE = ["normal", "
|
1351
|
+
VALID_WEIGHT = ["normal", "bold"] + FONT_WEIGHT_NUM
|
1352
|
+
VALID_STYLE = ["normal", "italic", "oblique"]
|
1292
1353
|
VALID_POS = ["super", "sub"]
|
1293
1354
|
VALID_LSTYLE = ["none", "solid"]
|
1294
1355
|
VALID_LTYPE = ["single", "double"]
|
@@ -1404,12 +1465,9 @@ class ODTTextStyle:
|
|
1404
1465
|
ET.SubElement(xEntry, _mkTag("style", "text-properties"), attrib=attr)
|
1405
1466
|
return
|
1406
1467
|
|
1407
|
-
# END Class ODTTextStyle
|
1408
|
-
|
1409
1468
|
|
1410
|
-
#
|
1411
|
-
#
|
1412
|
-
# =============================================================================================== #
|
1469
|
+
# XML Complex Element Helper Class
|
1470
|
+
# ================================
|
1413
1471
|
|
1414
1472
|
X_ROOT_TEXT = 0
|
1415
1473
|
X_ROOT_TAIL = 1
|
@@ -1464,7 +1522,6 @@ class XMLParagraph:
|
|
1464
1522
|
if c == " ":
|
1465
1523
|
nSpaces += 1
|
1466
1524
|
continue
|
1467
|
-
|
1468
1525
|
elif nSpaces > 0:
|
1469
1526
|
self._processSpaces(nSpaces)
|
1470
1527
|
nSpaces = 0
|
@@ -1475,26 +1532,22 @@ class XMLParagraph:
|
|
1475
1532
|
self._xTail.tail = ""
|
1476
1533
|
self._nState = X_ROOT_TAIL
|
1477
1534
|
self._chrPos += 1
|
1478
|
-
|
1479
1535
|
elif self._nState in (X_SPAN_TEXT, X_SPAN_SING):
|
1480
1536
|
self._xSing = ET.SubElement(self._xTail, TAG_BR)
|
1481
1537
|
self._xSing.tail = ""
|
1482
1538
|
self._nState = X_SPAN_SING
|
1483
1539
|
self._chrPos += 1
|
1484
|
-
|
1485
1540
|
elif c == "\t":
|
1486
1541
|
if self._nState in (X_ROOT_TEXT, X_ROOT_TAIL):
|
1487
1542
|
self._xTail = ET.SubElement(self._xRoot, TAG_TAB)
|
1488
1543
|
self._xTail.tail = ""
|
1489
1544
|
self._nState = X_ROOT_TAIL
|
1490
1545
|
self._chrPos += 1
|
1491
|
-
|
1492
1546
|
elif self._nState in (X_SPAN_TEXT, X_SPAN_SING):
|
1493
1547
|
self._xSing = ET.SubElement(self._xTail, TAG_TAB)
|
1494
1548
|
self._xSing.tail = ""
|
1495
1549
|
self._chrPos += 1
|
1496
1550
|
self._nState = X_SPAN_SING
|
1497
|
-
|
1498
1551
|
else:
|
1499
1552
|
if self._nState == X_ROOT_TEXT:
|
1500
1553
|
self._xRoot.text = (self._xRoot.text or "") + c
|
@@ -1529,6 +1582,19 @@ class XMLParagraph:
|
|
1529
1582
|
self._nState = X_ROOT_TAIL
|
1530
1583
|
return
|
1531
1584
|
|
1585
|
+
def appendNode(self, xNode: ET.Element | None) -> None:
|
1586
|
+
"""Append an XML node to the paragraph. We only check for the
|
1587
|
+
X_ROOT_TEXT and X_ROOT_TAIL states. X_SPAN_TEXT is not possible
|
1588
|
+
at all, and X_SPAN_SING only happens internally in an appendSpan
|
1589
|
+
call, returning us to an X_ROOT_TAIL state.
|
1590
|
+
"""
|
1591
|
+
if xNode is not None and self._nState in (X_ROOT_TEXT, X_ROOT_TAIL):
|
1592
|
+
self._xRoot.append(xNode)
|
1593
|
+
self._xTail = xNode
|
1594
|
+
self._xTail.tail = ""
|
1595
|
+
self._nState = X_ROOT_TAIL
|
1596
|
+
return
|
1597
|
+
|
1532
1598
|
def checkError(self) -> tuple[int, str]:
|
1533
1599
|
"""Check that the number of characters written matches the
|
1534
1600
|
number of characters received.
|
@@ -1600,5 +1666,3 @@ class XMLParagraph:
|
|
1600
1666
|
self._chrPos += nSpaces - 1
|
1601
1667
|
|
1602
1668
|
return
|
1603
|
-
|
1604
|
-
# END Class XMLParagraph
|