Move outline generation to Tokenizer class instead

This commit is contained in:
Veronica Berglyd Olsen
2024-03-21 16:42:58 +01:00
parent 144ba66d9d
commit 5befd244e6
11 changed files with 109 additions and 87 deletions
+19 -3
View File
@@ -52,7 +52,10 @@ class NWBuildDocument:
manuscript, based on a build definition object (BuildSettings). manuscript, based on a build definition object (BuildSettings).
""" """
__slots__ = ("_project", "_build", "_queue", "_error", "_cache", "_count", "_preview") __slots__ = (
"_project", "_build", "_queue", "_error", "_cache", "_count",
"_outline", "_preview"
)
def __init__(self, project: NWProject, build: BuildSettings) -> None: def __init__(self, project: NWProject, build: BuildSettings) -> None:
self._project = project self._project = project
@@ -61,6 +64,7 @@ class NWBuildDocument:
self._error = None self._error = None
self._cache = None self._cache = None
self._count = False self._count = False
self._outline = False
self._preview = False self._preview = False
return return
@@ -86,13 +90,21 @@ class NWBuildDocument:
## ##
def setCountEnabled(self, state: bool) -> None: def setCountEnabled(self, state: bool) -> None:
"""Turn on/off stats counting for builds.""" """Turn on/off stats for builds."""
self._count = state self._count = state
return return
def setBuildOutline(self, state: bool) -> None:
"""Turn on/off outline for builds."""
self._outline = state
return
def setPreviewMode(self, state: bool) -> None: def setPreviewMode(self, state: bool) -> None:
"""Set the preview mode of the build. Implies count mode.""" """Set the preview mode of the build. This also enables stats
count and outline mode.
"""
self._preview = state self._preview = state
self._outline = state
self._count = state self._count = state
return return
@@ -364,12 +376,16 @@ class NWBuildDocument:
bldObj.doConvert() bldObj.doConvert()
if self._count: if self._count:
bldObj.countStats() bldObj.countStats()
if self._outline:
bldObj.buildOutline()
elif tItem.isFileType(): elif tItem.isFileType():
bldObj.setText(tHandle) bldObj.setText(tHandle)
bldObj.doPreProcessing() bldObj.doPreProcessing()
bldObj.tokenizeText() bldObj.tokenizeText()
if self._count: if self._count:
bldObj.countStats() bldObj.countStats()
if self._outline:
bldObj.buildOutline()
if convert: if convert:
bldObj.doConvert() bldObj.doConvert()
else: else:
+4 -4
View File
@@ -33,7 +33,7 @@ import logging
from time import time from time import time
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from pathlib import Path from pathlib import Path
from collections.abc import Generator, ItemsView, Iterable from collections.abc import ItemsView, Iterable
from novelwriter import SHARED from novelwriter import SHARED
from novelwriter.enum import nwComment, nwItemClass, nwItemType, nwItemLayout from novelwriter.enum import nwComment, nwItemClass, nwItemType, nwItemLayout
@@ -523,7 +523,7 @@ class NWIndex:
return tItem[sTitle] return tItem[sTitle]
return None return None
def iterItemHeadings(self, tHandle: str) -> Generator[str, IndexHeading]: def iterItemHeadings(self, tHandle: str) -> Iterable[tuple[str, IndexHeading]]:
"""Get all headings for a specific item.""" """Get all headings for a specific item."""
if tItem := self._itemIndex[tHandle]: if tItem := self._itemIndex[tHandle]:
yield from tItem.items() yield from tItem.items()
@@ -531,7 +531,7 @@ class NWIndex:
def novelStructure( def novelStructure(
self, rootHandle: str | None = None, activeOnly: bool = True self, rootHandle: str | None = None, activeOnly: bool = True
) -> Generator[tuple[str, str, str, IndexHeading]]: ) -> Iterable[tuple[str, str, str, IndexHeading]]:
"""Iterate over all titles in the novel, in the correct order as """Iterate over all titles in the novel, in the correct order as
they appear in the tree view and in the respective document they appear in the tree view and in the respective document
files, but skipping all note files. files, but skipping all note files.
@@ -673,7 +673,7 @@ class NWIndex:
def getTagsData( def getTagsData(
self, activeOnly: bool = True self, activeOnly: bool = True
) -> Generator[tuple[str, str, str, IndexItem | None, IndexHeading | None]]: ) -> Iterable[tuple[str, str, str, IndexItem | None, IndexHeading | None]]:
"""Return all known tags.""" """Return all known tags."""
for tag, data in self._tagsIndex.items(): for tag, data in self._tagsIndex.items():
iItem = self._itemIndex[data.get("handle")] iItem = self._itemIndex[data.get("handle")]
+2 -2
View File
@@ -31,7 +31,7 @@ from time import time
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from pathlib import Path from pathlib import Path
from functools import partial from functools import partial
from collections.abc import Generator from collections.abc import Iterable
from PyQt5.QtCore import QCoreApplication from PyQt5.QtCore import QCoreApplication
@@ -517,7 +517,7 @@ class NWProject:
# Class Methods # Class Methods
## ##
def iterProjectItems(self) -> Generator[NWItem]: def iterProjectItems(self) -> Iterable[NWItem]:
"""This function ensures that the item tree loaded is sent to """This function ensures that the item tree loaded is sent to
the GUI tree view in such a way that the tree can be built. That the GUI tree view in such a way that the tree can be built. That
is, the parent item must be sent before its child. In principle, is, the parent item must be sent before its child. In principle,
+2 -2
View File
@@ -29,7 +29,7 @@ import logging
from time import time from time import time
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from pathlib import Path from pathlib import Path
from collections.abc import Generator from collections.abc import Iterable
from novelwriter.error import logException from novelwriter.error import logException
from novelwriter.common import formatTimeStamp from novelwriter.common import formatTimeStamp
@@ -110,7 +110,7 @@ class NWSessionLog:
return True return True
def iterRecords(self) -> Generator[dict]: def iterRecords(self) -> Iterable[dict]:
"""Iterate through all records in the log.""" """Iterate through all records in the log."""
sessFile = self._project.storage.getMetaFile(nwFiles.SESS_FILE) sessFile = self._project.storage.getMetaFile(nwFiles.SESS_FILE)
if isinstance(sessFile, Path) and sessFile.is_file(): if isinstance(sessFile, Path) and sessFile.is_file():
+4 -4
View File
@@ -28,10 +28,10 @@ import random
import logging import logging
from typing import TYPE_CHECKING, Literal from typing import TYPE_CHECKING, Literal
from collections.abc import Generator, ItemsView, Iterator, KeysView, ValuesView from collections.abc import ItemsView, Iterable, Iterator, KeysView, ValuesView
from PyQt5.QtGui import QIcon, QPainter, QPainterPath, QPixmap, QColor from PyQt5.QtGui import QIcon, QPainter, QPainterPath, QPixmap, QColor
from PyQt5.QtCore import QRectF, Qt from PyQt5.QtCore import QRectF
from novelwriter import CONFIG from novelwriter import CONFIG
from novelwriter.common import minmax, simplified from novelwriter.common import minmax, simplified
@@ -193,7 +193,7 @@ class NWStatus:
self._store[key]["count"] += 1 self._store[key]["count"] += 1
return return
def pack(self) -> Generator[tuple[str, dict]]: def pack(self) -> Iterable[tuple[str, dict]]:
"""Pack the status entries into a dictionary.""" """Pack the status entries into a dictionary."""
for key, data in self._store.items(): for key, data in self._store.items():
yield (data["name"], { yield (data["name"], {
@@ -248,7 +248,7 @@ class NWStatus:
def _createIcon(self, red: int, green: int, blue: int) -> QIcon: def _createIcon(self, red: int, green: int, blue: int) -> QIcon:
"""Generate an icon for a status label.""" """Generate an icon for a status label."""
pixmap = QPixmap(self._iPX, self._iPX) pixmap = QPixmap(self._iPX, self._iPX)
pixmap.fill(Qt.transparent) pixmap.fill(QColor(0, 0, 0, 0))
painter = QPainter(pixmap) painter = QPainter(pixmap)
painter.setRenderHint(QPainter.Antialiasing) painter.setRenderHint(QPainter.Antialiasing)
+4 -19
View File
@@ -55,7 +55,6 @@ class ToHtml(Tokenizer):
self._genMode = self.M_EXPORT self._genMode = self.M_EXPORT
self._cssStyles = True self._cssStyles = True
self._fullHTML: list[str] = [] self._fullHTML: list[str] = []
self._navMap: dict[str, str] = {}
# Internals # Internals
self._trMap = {} self._trMap = {}
@@ -71,10 +70,6 @@ class ToHtml(Tokenizer):
def fullHTML(self) -> list[str]: def fullHTML(self) -> list[str]:
return self._fullHTML return self._fullHTML
@property
def navigationMap(self) -> dict[str, str]:
return self._navMap
## ##
# Setters # Setters
## ##
@@ -174,6 +169,8 @@ class ToHtml(Tokenizer):
pStyle = None pStyle = None
lines = [] lines = []
tHandle = self._handle
for tType, nHead, tText, tFormat, tStyle in self._tokens: for tType, nHead, tText, tFormat, tStyle in self._tokens:
# Replace < and > with HTML entities # Replace < and > with HTML entities
@@ -232,11 +229,9 @@ class ToHtml(Tokenizer):
else: else:
hStyle = "" hStyle = ""
if self._linkHeadings and self._handle: if self._linkHeadings and tHandle:
tHH = f"{self._handle}:T{nHead:04d}" aNm = f"<a name='{tHandle}:T{nHead:04d}'></a>"
aNm = f"<a name='{tHH}'></a>"
else: else:
tHH = ""
aNm = "" aNm = ""
# Process Text Type # Process Text Type
@@ -256,32 +251,22 @@ class ToHtml(Tokenizer):
elif tType == self.T_TITLE: elif tType == self.T_TITLE:
tHead = tText.replace(nwHeadFmt.BR, "<br/>") tHead = tText.replace(nwHeadFmt.BR, "<br/>")
lines.append(f"<h1 class='title'{hStyle}>{aNm}{tHead}</h1>\n") lines.append(f"<h1 class='title'{hStyle}>{aNm}{tHead}</h1>\n")
if tHH:
self._navMap[tHH] = f"TT:{tHead}"
elif tType == self.T_HEAD1: elif tType == self.T_HEAD1:
tHead = tText.replace(nwHeadFmt.BR, "<br/>") tHead = tText.replace(nwHeadFmt.BR, "<br/>")
lines.append(f"<{h1}{h1Cl}{hStyle}>{aNm}{tHead}</{h1}>\n") lines.append(f"<{h1}{h1Cl}{hStyle}>{aNm}{tHead}</{h1}>\n")
if tHH:
self._navMap[tHH] = f"H1:{tHead}"
elif tType == self.T_HEAD2: elif tType == self.T_HEAD2:
tHead = tText.replace(nwHeadFmt.BR, "<br/>") tHead = tText.replace(nwHeadFmt.BR, "<br/>")
lines.append(f"<{h2}{hStyle}>{aNm}{tHead}</{h2}>\n") lines.append(f"<{h2}{hStyle}>{aNm}{tHead}</{h2}>\n")
if tHH:
self._navMap[tHH] = f"H2:{tHead}"
elif tType == self.T_HEAD3: elif tType == self.T_HEAD3:
tHead = tText.replace(nwHeadFmt.BR, "<br/>") tHead = tText.replace(nwHeadFmt.BR, "<br/>")
lines.append(f"<{h3}{hStyle}>{aNm}{tHead}</{h3}>\n") lines.append(f"<{h3}{hStyle}>{aNm}{tHead}</{h3}>\n")
if tHH:
self._navMap[tHH] = f"H3:{tHead}"
elif tType == self.T_HEAD4: elif tType == self.T_HEAD4:
tHead = tText.replace(nwHeadFmt.BR, "<br/>") tHead = tText.replace(nwHeadFmt.BR, "<br/>")
lines.append(f"<{h4}{hStyle}>{aNm}{tHead}</{h4}>\n") lines.append(f"<{h4}{hStyle}>{aNm}{tHead}</{h4}>\n")
if tHH:
self._navMap[tHH] = f"H4:{tHead}"
elif tType == self.T_SEP: elif tType == self.T_SEP:
lines.append(f"<p class='sep'{hStyle}>{tText}</p>\n") lines.append(f"<p class='sep'{hStyle}>{tText}</p>\n")
+53 -26
View File
@@ -35,13 +35,13 @@ from functools import partial
from PyQt5.QtCore import QCoreApplication, QRegularExpression from PyQt5.QtCore import QCoreApplication, QRegularExpression
from novelwriter.enum import nwComment, nwItemLayout
from novelwriter.common import formatTimeStamp, numberToRoman, checkInt from novelwriter.common import formatTimeStamp, numberToRoman, checkInt
from novelwriter.constants import ( from novelwriter.constants import (
nwHeadFmt, nwKeyWords, nwLabels, nwRegEx, nwShortcode, nwUnicode, trConst nwHeadFmt, nwKeyWords, nwLabels, nwRegEx, nwShortcode, nwUnicode, trConst
) )
from novelwriter.core.index import processComment from novelwriter.core.index import processComment
from novelwriter.core.project import NWProject from novelwriter.core.project import NWProject
from novelwriter.enum import nwComment, nwItemLayout
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -120,13 +120,14 @@ class Tokenizer(ABC):
self._text = "" # The raw text to be tokenized self._text = "" # The raw text to be tokenized
self._handle = None # The item handle currently being processed self._handle = None # The item handle currently being processed
self._result = "" # The result of the last document self._result = "" # The result of the last document
self._counts = {} # Counter data
self._keepMarkdown = False # Whether to keep the markdown text self._keepMarkdown = False # Whether to keep the markdown text
self._allMarkdown = [] # The result novelWriter markdown of all documents self._allMarkdown = [] # The result novelWriter markdown of all documents
# Processed Tokens # Processed Tokens and Meta Data
self._tokens: list[tuple[int, int, str, list[tuple[int, int]], int]] = [] self._tokens: list[tuple[int, int, str, list[tuple[int, int]], int]] = []
self._counts: dict[str, int] = {}
self._outline: dict[str, str] = {}
# User Settings # User Settings
self._textFont = "Serif" # Output text font self._textFont = "Serif" # Output text font
@@ -226,6 +227,11 @@ class Tokenizer(ABC):
"""The collected stats about the text.""" """The collected stats about the text."""
return self._counts return self._counts
@property
def textOutline(self) -> dict[str, str]:
"""The generated outline of the text."""
return self._outline
@property @property
def errData(self) -> list[str]: def errData(self) -> list[str]:
"""The error data.""" """The error data."""
@@ -392,43 +398,42 @@ class Tokenizer(ABC):
def doConvert(self) -> None: def doConvert(self) -> None:
raise NotImplementedError raise NotImplementedError
def addRootHeading(self, tHandle: str) -> bool: def addRootHeading(self, tHandle: str) -> None:
"""Add a heading at the start of a new root folder.""" """Add a heading at the start of a new root folder."""
tItem = self._project.tree[tHandle] self._text = ""
if not tItem or not tItem.isRootType(): self._handle = None
return False
if self._isFirst: if (tItem := self._project.tree[tHandle]) and tItem.isRootType():
textAlign = self.A_CENTRE self._handle = tHandle
self._isFirst = False if self._isFirst:
else: textAlign = self.A_CENTRE
textAlign = self.A_PBB | self.A_CENTRE self._isFirst = False
else:
textAlign = self.A_PBB | self.A_CENTRE
trNotes = self._localLookup("Notes") trNotes = self._localLookup("Notes")
title = f"{trNotes}: {tItem.itemName}" title = f"{trNotes}: {tItem.itemName}"
self._tokens = [] self._tokens = []
self._tokens.append(( self._tokens.append((
self.T_TITLE, 0, title, [], textAlign self.T_TITLE, 1, title, [], textAlign
)) ))
if self._keepMarkdown: if self._keepMarkdown:
self._allMarkdown.append(f"# {title}\n\n") self._allMarkdown.append(f"#! {title}\n\n")
return True return
def setText(self, tHandle: str, text: str | None = None) -> None: def setText(self, tHandle: str, text: str | None = None) -> None:
"""Set the text for the tokenizer from a handle. If text is not """Set the text for the tokenizer from a handle. If text is not
set, its is loaded from the file. set, it's is loaded from the file.
""" """
self._text = "" self._text = ""
self._handle = None self._handle = None
if nwItem := self._project.tree[tHandle]: if nwItem := self._project.tree[tHandle]:
if text is None: if text is None:
text = self._project.storage.getDocument(tHandle).readDocument() or "" text = self._project.storage.getDocument(tHandle).readDocument() or ""
self._text = text self._text = text
self._handle = tHandle self._handle = tHandle
self._isNovel = nwItem.itemLayout == nwItemLayout.DOCUMENT self._isNovel = nwItem.itemLayout == nwItemLayout.DOCUMENT
return return
def doPreProcessing(self) -> None: def doPreProcessing(self) -> None:
@@ -798,7 +803,29 @@ class Tokenizer(ABC):
return return
def countStats(self) -> dict[str, int]: def buildOutline(self) -> None:
"""Build an outline of the text up to level 3 headings."""
tHandle = self._handle or ""
isNovel = self._isNovel
for tType, nHead, tText, _, _ in self._tokens:
if tType == self.T_TITLE:
prefix = "TT"
elif tType == self.T_HEAD1:
prefix = "PT" if isNovel else "H1"
elif tType == self.T_HEAD2:
prefix = "CH" if isNovel else "H2"
elif tType == self.T_HEAD3:
prefix = "SC" if isNovel else "H3"
else:
continue
key = f"{tHandle}:T{nHead:04d}"
text = tText.replace(nwHeadFmt.BR, " ").replace("&amp;", "&")
self._outline[key] = f"{prefix}|{text}"
return
def countStats(self) -> None:
"""Count stats on the tokenized text.""" """Count stats on the tokenized text."""
titleCount = self._counts.get("titleCount", 0) titleCount = self._counts.get("titleCount", 0)
paragraphCount = self._counts.get("paragraphCount", 0) paragraphCount = self._counts.get("paragraphCount", 0)
@@ -905,7 +932,7 @@ class Tokenizer(ABC):
self._counts["textWordChars"] = textWordChars self._counts["textWordChars"] = textWordChars
self._counts["titleWordChars"] = titleWordChars self._counts["titleWordChars"] = titleWordChars
return {} return
def saveRawMarkdown(self, path: str | Path) -> None: def saveRawMarkdown(self, path: str | Path) -> None:
"""Save the raw text to a plain text file.""" """Save the raw text to a plain text file."""
+2 -2
View File
@@ -28,7 +28,7 @@ import logging
from typing import TYPE_CHECKING, Literal, overload from typing import TYPE_CHECKING, Literal, overload
from pathlib import Path from pathlib import Path
from collections.abc import Generator, Iterator from collections.abc import Iterable, Iterator
from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType
from novelwriter.error import logException from novelwriter.error import logException
@@ -387,7 +387,7 @@ class NWTree:
rootClasses.add(nwItem.itemClass) rootClasses.add(nwItem.itemClass)
return rootClasses return rootClasses
def iterRoots(self, itemClass: nwItemClass | None) -> Generator[tuple[str, NWItem]]: def iterRoots(self, itemClass: nwItemClass | None) -> Iterable[tuple[str, NWItem]]:
"""Iterate over all root items of a given class in order.""" """Iterate over all root items of a given class in order."""
for tHandle in self._order: for tHandle in self._order:
nwItem = self.__getitem__(tHandle) nwItem = self.__getitem__(tHandle)
+5 -5
View File
@@ -41,13 +41,13 @@ from PyQt5.QtWidgets import (
) )
from novelwriter import CONFIG, SHARED from novelwriter import CONFIG, SHARED
from novelwriter.constants import nwHeaders, nwUnicode
from novelwriter.core.tohtml import ToHtml
from novelwriter.enum import nwItemType, nwDocAction, nwDocMode from novelwriter.enum import nwItemType, nwDocAction, nwDocMode
from novelwriter.error import logException from novelwriter.error import logException
from novelwriter.constants import nwHeaders, nwUnicode from novelwriter.extensions.eventfilters import WheelEventFilter
from novelwriter.extensions.modified import NIconToolButton from novelwriter.extensions.modified import NIconToolButton
from novelwriter.gui.theme import STYLES_MIN_TOOLBUTTON from novelwriter.gui.theme import STYLES_MIN_TOOLBUTTON
from novelwriter.core.tohtml import ToHtml
from novelwriter.extensions.eventfilters import WheelEventFilter
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -633,7 +633,7 @@ class GuiDocViewHeader(QWidget):
# Internal Variables # Internal Variables
self._docHandle = None self._docHandle = None
self._docOutline: dict[int, tuple[str, int]] = {} self._docOutline: dict[str, tuple[str, int]] = {}
iPx = SHARED.theme.baseIconSize iPx = SHARED.theme.baseIconSize
mPx = CONFIG.pxInt(4) mPx = CONFIG.pxInt(4)
@@ -730,7 +730,7 @@ class GuiDocViewHeader(QWidget):
self.refreshButton.setVisible(False) self.refreshButton.setVisible(False)
return return
def setOutline(self, data: dict[int, tuple[str, int]]) -> None: def setOutline(self, data: dict[str, tuple[str, int]]) -> None:
"""Set the document outline dataset.""" """Set the document outline dataset."""
tHandle = self._docHandle tHandle = self._docHandle
if data != self._docOutline and tHandle: if data != self._docOutline and tHandle:
+2 -2
View File
@@ -23,10 +23,10 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
""" """
from __future__ import annotations from __future__ import annotations
from collections.abc import Generator
import logging import logging
from time import time from time import time
from collections.abc import Iterable
from PyQt5.QtGui import QTextBlock, QTextCursor, QTextDocument from PyQt5.QtGui import QTextBlock, QTextCursor, QTextDocument
from PyQt5.QtCore import QObject, pyqtSlot from PyQt5.QtCore import QObject, pyqtSlot
@@ -114,7 +114,7 @@ class GuiTextDocument(QTextDocument):
return word, cPos, cLen, SHARED.spelling.suggestWords(word) return word, cPos, cLen, SHARED.spelling.suggestWords(word)
return "", -1, -1, [] return "", -1, -1, []
def iterBlockByType(self, cType: int, maxCount: int = 1000) -> Generator[QTextBlock]: def iterBlockByType(self, cType: int, maxCount: int = 1000) -> Iterable[QTextBlock]:
"""Iterate over all text blocks of a given type.""" """Iterate over all text blocks of a given type."""
count = 0 count = 0
for i in range(self.blockCount()): for i in range(self.blockCount()):
+12 -18
View File
@@ -58,14 +58,6 @@ if TYPE_CHECKING: # pragma: no cover
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
OUTLINE_MAP = {
"TT": 0,
"H1": 1,
"H2": 2,
"H3": 3,
"H4": 4,
}
class GuiManuscript(QDialog): class GuiManuscript(QDialog):
"""GUI Tools: Manuscript Tool """GUI Tools: Manuscript Tool
@@ -368,7 +360,7 @@ class GuiManuscript(QDialog):
"uuid": build.buildID, "uuid": build.buildID,
"time": int(time()), "time": int(time()),
"stats": buildObj.textStats, "stats": buildObj.textStats,
"outline": buildObj.navigationMap, "outline": buildObj.textOutline,
"styles": buildObj.getStyleSheet(), "styles": buildObj.getStyleSheet(),
"html": buildObj.fullHTML, "html": buildObj.fullHTML,
} }
@@ -708,22 +700,24 @@ class _OutlineWidget(QWidget):
root = self.listView.invisibleRootItem() root = self.listView.invisibleRootItem()
parent = root parent = root
indent = False indent = False
for anchor, text in data.items(): for anchor, entry in data.items():
level = OUTLINE_MAP.get(text[:2], -1) prefix, _, text = entry.partition("|")
text = text[3:] if prefix in ("TT", "PT", "CH", "SC", "H1", "H2"):
if 0 <= level < 4:
item = QTreeWidgetItem([text]) item = QTreeWidgetItem([text])
item.setData(0, self.D_LINE, anchor) item.setData(0, self.D_LINE, anchor)
if level == 0: if prefix == "TT":
item.setFont(0, tFont) item.setFont(0, tFont)
item.setForeground(0, tBrush) item.setForeground(0, tBrush)
elif level == 1: root.addChild(item)
parent = root
elif prefix == "PT":
item.setFont(0, hFont) item.setFont(0, hFont)
root.addChild(item)
if level < 3: parent = root
elif prefix in ("CH", "H1"):
root.addChild(item) root.addChild(item)
parent = item parent = item
elif parent: elif prefix in ("SC", "H2"):
parent.addChild(item) parent.addChild(item)
indent = True indent = True