Add logic to apply formatting to text
This commit is contained in:
+3
-3
@@ -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
@@ -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
|
||||
# =============================================================================================== #
|
||||
|
||||
@@ -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 isn’t fully Markdown compliant. If the syntax highlighter doesn’t 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 isn’t fully Markdown compliant. If the syntax highlighter doesn’t 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.”
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user