Add logic to apply formatting to text

This commit is contained in:
Veronica K. B. Olsen
2021-01-27 19:36:34 +01:00
parent 4df294f0a2
commit 8715ab9322
4 changed files with 246 additions and 42 deletions
+3 -3
View File
@@ -236,12 +236,12 @@ class ToHtml(Tokenizer):
if parStyle is None:
parStyle = hStyle
for xPos, xLen, xFmt in reversed(tFormat):
tTemp = tTemp[:xPos]+htmlTags[xFmt]+tTemp[xPos+xLen:]
tTemp = tTemp[:xPos] + htmlTags[xFmt] + tTemp[xPos+xLen:]
if tText.endswith(" "):
thisPar.append(tTemp.rstrip()+"<br/>")
thisPar.append(tTemp.rstrip() + "<br/>")
hasHardBreak = True
else:
thisPar.append(tTemp.rstrip()+" ")
thisPar.append(tTemp.rstrip() + " ")
elif tType == self.T_SYNOPSIS and self.doSynopsis:
tmpResult.append(self._formatSynopsis(tText))
+201 -38
View File
@@ -26,7 +26,6 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
import nw
import logging
import os
from lxml import etree
from hashlib import sha256
@@ -45,11 +44,14 @@ XML_NS = {
"fo" : "urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0",
}
X_BR = "{%s}line-break" % XML_NS["text"]
X_TAB = "{%s}tab" % XML_NS["text"]
class ToOdt(Tokenizer):
X_BLD = 0x01
X_ITA = 0x02
X_DEL = 0x04
X_BRK = 0x08
X_TAB = 0x10
def __init__(self, theProject, theParent):
Tokenizer.__init__(self, theProject, theParent)
@@ -208,7 +210,17 @@ class ToOdt(Tokenizer):
"""
self.theResult = ""
odtTags = {
self.FMT_B_B : "_B",
self.FMT_B_E : "b_",
self.FMT_I_B : "I",
self.FMT_I_E : "i",
self.FMT_D_B : "_S",
self.FMT_D_E : "s_",
}
thisPar = []
thisFmt = []
parStyle = None
hasHardBreak = False
for tType, tLine, tText, tFormat, tStyle in self.theTokens:
@@ -238,10 +250,16 @@ class ToOdt(Tokenizer):
if hasHardBreak and parStyle is not None:
if self.doJustify:
parStyle.setTextAlign("left")
if len(thisPar) > 0:
tTemp = "".join(thisPar)
self._addTextPar("Text_Body", parStyle, tTemp.rstrip())
fTemp = "".join(thisFmt)
tTxt = tTemp.rstrip()
tFmt = fTemp[:len(tTxt)]
self._addTextPar("Text_Body", parStyle, tTxt, theFmt=tFmt)
thisPar = []
thisFmt = []
parStyle = None
hasHardBreak = False
@@ -275,21 +293,31 @@ class ToOdt(Tokenizer):
tTemp = tText
if parStyle is None:
parStyle = oStyle
# for xPos, xLen, xFmt in reversed(tFormat):
# tTemp = tTemp[:xPos] + htmlTags[xFmt] + tTemp[xPos+xLen:]
tFmt = " "*len(tTemp)
for xPos, xLen, xFmt in tFormat:
tFmt = tFmt[:xPos] + odtTags[xFmt] + tFmt[xPos+xLen:]
tTxt = tTemp.rstrip()
tFmt = tFmt[:len(tTxt)]
if tText.endswith(" "):
thisPar.append(tTemp.rstrip()+"\n")
thisPar.append(tTxt + "\n")
thisFmt.append(tFmt + " ")
hasHardBreak = True
else:
thisPar.append(tTemp.rstrip()+" ")
thisPar.append(tTxt + " ")
thisFmt.append(tFmt + " ")
return
def closeDocument(self):
"""Return the serialised XML document
"""
# Build the auto-generated styles
for styleName, styleObj in self._autoPara.values():
styleObj.packXML(self._xAuto, styleName)
for styleName, styleObj in self._autoText.values():
styleObj.packXML(self._xAuto, styleName)
self.theResult = etree.tostring(
self._xRoot,
@@ -298,17 +326,13 @@ class ToOdt(Tokenizer):
xml_declaration = True
)
cacheFile = os.path.join(os.path.expanduser("~"), "Temp", "odtGen.fodt")
with open(cacheFile, mode="wb") as outFile:
outFile.write(self.theResult)
return
##
# Internal Functions
##
def _addTextPar(self, styleName, oStyle, theText, isHead=False, oLevel=None):
def _addTextPar(self, styleName, oStyle, theText, theFmt="", isHead=False, oLevel=None):
"""Add a text paragraph to the text XML element.
"""
tAttr = {}
@@ -322,36 +346,85 @@ class ToOdt(Tokenizer):
if not theText:
return
if "\t" not in theText and "\n" not in theText:
xElem.text = theText
return
##
# Process Formatting
##
# Process tabs and line breaks
tTemp = ""
if len(theText) != len(theFmt):
# Genrate dummu format if there isn't any
theFmt = " "*len(theText)
# XML functions
xTail = None
for c in theText:
if c == "\t":
def appendText(tText):
nonlocal xElem, xTail
if tText:
if xTail is None:
xElem.text = tTemp
xElem.text = tText
else:
xTail.tail = tTemp
tTemp = ""
xTail = etree.SubElement(xElem, X_TAB)
elif c == "\n":
if xTail is None:
xElem.text = tTemp
else:
xTail.tail = tTemp
tTemp = ""
xTail = etree.SubElement(xElem, X_BR)
else:
xTail.tail = tText
def appendSpan(tText, tFmt):
nonlocal xElem, xTail
if tText:
xTail = etree.SubElement(xElem, _mkTag("text", "span"), attrib={
_mkTag("text", "style-name"): self._textStyle(tFmt)
})
xTail.text = tText
# The formatting loop
tTemp = ""
xFmt = 0x00
pFmt = 0x00
for i, c in enumerate(theText):
if theFmt[i] == "_":
continue
elif theFmt[i] == "B":
xFmt |= self.X_BLD
elif theFmt[i] == "b":
xFmt ^= self.X_BLD
elif theFmt[i] == "I":
xFmt |= self.X_ITA
elif theFmt[i] == "i":
xFmt ^= self.X_ITA
elif theFmt[i] == "S":
xFmt |= self.X_DEL
elif theFmt[i] == "s":
xFmt ^= self.X_DEL
if c == "\n":
xFmt |= self.X_BRK
c = ""
elif c == "\t":
xFmt |= self.X_TAB
c = ""
if theFmt[i] == " ":
tTemp += c
if tTemp != "":
if xTail is None:
xElem.text = tTemp
else:
xTail.tail = tTemp
if xFmt != pFmt:
if pFmt == 0x00:
appendText(tTemp)
tTemp = ""
else:
appendSpan(tTemp, pFmt)
tTemp = ""
if xFmt & self.X_BRK:
xTail = etree.SubElement(xElem, _mkTag("text", "line-break"))
xFmt ^= self.X_BRK
if xFmt & self.X_TAB:
xTail = etree.SubElement(xElem, _mkTag("text", "tab"))
xFmt ^= self.X_TAB
pFmt = xFmt
# Save what remains in the buffer
appendText(tTemp)
return
@@ -376,6 +449,26 @@ class ToOdt(Tokenizer):
return newName
def _textStyle(self, styleCode):
"""Return a text style for a given style code.
"""
if styleCode in self._autoText:
return self._autoText[styleCode][0]
newName = "T%d" % (len(self._autoText) + 1)
newStyle = ODTTextStyle()
if styleCode & self.X_BLD:
newStyle.setFontWeight("bold")
if styleCode & self.X_ITA:
newStyle.setFontStyle("italic")
if styleCode & self.X_DEL:
newStyle.setStrikeStyle("solid")
newStyle.setStrikeType("single")
self._autoText[styleCode] = (newName, newStyle)
return newName
def _emToCm(self, emVal):
"""Converts an em value to centimetres.
"""
@@ -803,6 +896,76 @@ class ODTParagraphStyle():
# END Class ODTParagraphStyle
class ODTTextStyle():
"""Wrapper class for the text style setting used by the exporter.
Only the used settings are exposed here to keep the class minimal
and fast.
"""
VALID_WEIGHT = ["normal", "inherit", "bold"]
VALID_STYLE = ["normal", "inherit", "italic"]
VALID_LSTYLE = ["none", "solid"]
VALID_LTYPE = ["none", "single", "double"]
def __init__(self):
# Text Attributes
self._tAttr = {
"font-weight": ["fo", None],
"font-style": ["fo", None],
"text-line-through-style": ["style", None],
"text-line-through-type": ["style", None],
}
return
##
# Setters
##
def setFontWeight(self, theValue):
if theValue in self.VALID_WEIGHT:
self._tAttr["font-weight"][1] = str(theValue)
return
def setFontStyle(self, theValue):
if theValue in self.VALID_STYLE:
self._tAttr["font-style"][1] = str(theValue)
return
def setStrikeStyle(self, theValue):
if theValue in self.VALID_LSTYLE:
self._tAttr["text-line-through-style"][1] = str(theValue)
return
def setStrikeType(self, theValue):
if theValue in self.VALID_LTYPE:
self._tAttr["text-line-through-type"][1] = str(theValue)
return
##
# Methods
##
def packXML(self, xParent, xName):
"""Pack the content into an xml element.
"""
theAttr = {}
theAttr[_mkTag("style", "name")] = xName
theAttr[_mkTag("style", "family")] = "text"
xEntry = etree.SubElement(xParent, _mkTag("style", "style"), attrib=theAttr)
theAttr = {}
for aName, (aNm, aVal) in self._tAttr.items():
if aVal is not None:
theAttr[_mkTag(aNm, aName)] = aVal
if theAttr:
etree.SubElement(xEntry, _mkTag("style", "text-properties"), attrib=theAttr)
return
# END Class ODTTextStyle
# =============================================================================================== #
# Local Functions
# =============================================================================================== #
+1 -1
View File
@@ -9,7 +9,7 @@
A scene is defined by a level three heading, like the one at the top of this page. The scene will be assigned to the chapter preceding it in the project tree.
Each paragraph in the scene is separated by a blank line. The text supports minimal formatting, like **bold**, _italic_ and **_bold italic_**. You can also ~~strike through~~ text. There is **some support for _nested_ emphasis**, but it isnt fully Markdown compliant. If the syntax highlighter doesnt show it correctly, the export tool will not either.
Each paragraph in the scene is separated by a blank line. The text supports minimal formatting, like **bold**, _italic_ and **_bold italic_**. You can also ~~strike through~~ text. There is **some support for _nested_ emphasis**, but it isnt fully Markdown compliant. If the syntax highlighter doesnt show it correctly, the export tool will not either.
In addition, the editor supports automatic formatting of “quotes”, both double and single. Depending on the syntax highlighter, these can be in different colours. “You can of course use **bold** and _italic_ text inside of quotes too.”
+41
View File
@@ -83,4 +83,45 @@ def testCoreToOdt_Convert(tmpConf, dummyGUI):
'</office:text>'
)
# Nested Text
theDoc.theText = "Some ~~nested **bold** and _italics_ text~~ text.\nNo format\n"
theDoc.tokenizeText()
theDoc.initDocument()
theDoc.doConvert()
theDoc.closeDocument()
assert xmlToText(theDoc._xText) == (
'<office:text>'
'<text:p text:style-name="Text_Body">Some '
'<text:span text:style-name="T1">nested </text:span>'
'<text:span text:style-name="T2">bold</text:span>'
'<text:span text:style-name="T1"> and </text:span>'
'<text:span text:style-name="T3">italics</text:span>'
'<text:span text:style-name="T1"> text</text:span> text. No format</text:p>'
'</office:text>'
)
# Hard Break
theDoc.theText = "Some text. \nNext line\n"
theDoc.tokenizeText()
theDoc.initDocument()
theDoc.doConvert()
theDoc.closeDocument()
assert xmlToText(theDoc._xText) == (
'<office:text>'
'<text:p text:style-name="Text_Body">Some text.<text:line-break/>Next line</text:p>'
'</office:text>'
)
# Tab
theDoc.theText = "\tItem 1\tItem 2\n"
theDoc.tokenizeText()
theDoc.initDocument()
theDoc.doConvert()
theDoc.closeDocument()
assert xmlToText(theDoc._xText) == (
'<office:text>'
'<text:p text:style-name="Text_Body"><text:tab/>Item 1<text:tab/>Item 2</text:p>'
'</office:text>'
)
# END Test testCoreToOdt_Convert