Add font table, fix core.xml bug, clean up code, update tests, for DocX

This commit is contained in:
Veronica Berglyd Olsen
2024-10-26 17:09:50 +02:00
parent 6c217c2450
commit ceabc666c3
14 changed files with 163 additions and 100 deletions
+121 -71
View File
@@ -87,6 +87,17 @@ def _docXCol(color: QColor) -> str:
return color.name(QtHexRgb).lstrip("#") return color.name(QtHexRgb).lstrip("#")
def _wText(parent: ET.Element, text: str) -> ET.Element:
"""Create a text element and add the preserve flag if necessary."""
attrib = {}
if len(text) > len(text.strip()):
attrib[_mkTag("xml", "space")] = "preserve"
return xmlSubElem(parent, _wTag("t"), text, attrib=attrib)
# Cached
W_VAL = _wTag("val")
# Formatting Codes # Formatting Codes
X_BLD = 0x001 # Bold format X_BLD = 0x001 # Bold format
X_ITA = 0x002 # Italic format X_ITA = 0x002 # Italic format
@@ -145,9 +156,9 @@ class DocXParStyle(NamedTuple):
nextStyle: str | None = None nextStyle: str | None = None
before: float | None = None before: float | None = None
after: float | None = None after: float | None = None
left: float | None = None
line: float | None = None line: float | None = None
indentFirst: float | None = None indentFirst: float | None = None
hanging: float | None = None
align: str | None = None align: str | None = None
default: bool = False default: bool = False
level: int | None = None level: int | None = None
@@ -191,7 +202,7 @@ class ToDocX(Tokenizer):
def setLanguage(self, language: str | None) -> None: def setLanguage(self, language: str | None) -> None:
"""Set language for the document.""" """Set language for the document."""
if language: if language:
self._dLanguage = language self._dLanguage = language.replace("_", "-")
return return
def setPageLayout( def setPageLayout(
@@ -296,6 +307,7 @@ class ToDocX(Tokenizer):
self._coreXml() self._coreXml()
self._appXml() self._appXml()
self._stylesXml() self._stylesXml()
self._fontTableXml()
fId = None fId = None
dId = None dId = None
@@ -451,32 +463,30 @@ class ToDocX(Tokenizer):
if fmt & X_ITA: if fmt & X_ITA:
xmlSubElem(rPr, _wTag("i")) xmlSubElem(rPr, _wTag("i"))
if fmt & X_UND: if fmt & X_UND:
xmlSubElem(rPr, _wTag("u"), attrib={_wTag("val"): "single"}) xmlSubElem(rPr, _wTag("u"), attrib={W_VAL: "single"})
if fmt & X_MRK: if fmt & X_MRK:
xmlSubElem(rPr, _wTag("shd"), attrib={ xmlSubElem(rPr, _wTag("shd"), attrib={
_wTag("fill"): _docXCol(self._theme.highlight), _wTag("val"): "clear", _wTag("fill"): _docXCol(self._theme.highlight), W_VAL: "clear",
}) })
if fmt & X_DEL: if fmt & X_DEL:
xmlSubElem(rPr, _wTag("strike")) xmlSubElem(rPr, _wTag("strike"))
if fmt & X_SUP: if fmt & X_SUP:
xmlSubElem(rPr, _wTag("vertAlign"), attrib={_wTag("val"): "superscript"}) xmlSubElem(rPr, _wTag("vertAlign"), attrib={W_VAL: "superscript"})
if fmt & X_SUB: if fmt & X_SUB:
xmlSubElem(rPr, _wTag("vertAlign"), attrib={_wTag("val"): "subscript"}) xmlSubElem(rPr, _wTag("vertAlign"), attrib={W_VAL: "subscript"})
if fmt & X_COL and (color := self._classes.get(fClass)): if fmt & X_COL and (color := self._classes.get(fClass)):
xmlSubElem(rPr, _wTag("color"), attrib={_wTag("val"): _docXCol(color)}) xmlSubElem(rPr, _wTag("color"), attrib={W_VAL: _docXCol(color)})
for segment in RX_TEXT.split(text): for segment in RX_TEXT.split(text):
if segment == "\n": if segment == "\n":
xmlSubElem(xR, _wTag("br")) xmlSubElem(xR, _wTag("br"))
elif segment == "\t": elif segment == "\t":
xmlSubElem(xR, _wTag("tab")) xmlSubElem(xR, _wTag("tab"))
elif len(segment) != len(segment.strip()):
xmlSubElem(xR, _wTag("t"), segment, attrib={_mkTag("xml", "space"): "preserve"})
elif segment: elif segment:
xmlSubElem(xR, _wTag("t"), segment) _wText(xR, segment)
if fmt & X_HRF and fLink: if fmt & X_HRF and fLink:
xmlSubElem(rPr, _wTag("rStyle"), attrib={_wTag("val"): "InternetLink"}) xmlSubElem(rPr, _wTag("rStyle"), attrib={W_VAL: "InternetLink"})
rId = self._appendExternalRel(fLink) rId = self._appendExternalRel(fLink)
xH = ET.Element(_wTag("hyperlink"), attrib={_mkTag("r", "id"): rId}) xH = ET.Element(_wTag("hyperlink"), attrib={_mkTag("r", "id"): rId})
xH.append(xR) xH.append(xR)
@@ -492,12 +502,12 @@ class ToDocX(Tokenizer):
"""Generate a footnote XML object.""" """Generate a footnote XML object."""
if key in self._footnotes: if key in self._footnotes:
idx = len(self._usedNotes) + 1 idx = len(self._usedNotes) + 1
run = ET.Element(_wTag("r")) xR = ET.Element(_wTag("r"))
rPr = xmlSubElem(run, _wTag("rPr")) rPr = xmlSubElem(xR, _wTag("rPr"))
xmlSubElem(rPr, _wTag("vertAlign"), attrib={_wTag("val"): "superscript"}) xmlSubElem(rPr, _wTag("vertAlign"), attrib={W_VAL: "superscript"})
xmlSubElem(run, _wTag("footnoteReference"), attrib={_wTag("id"): str(idx)}) xmlSubElem(xR, _wTag("footnoteReference"), attrib={_wTag("id"): str(idx)})
self._usedNotes[key] = idx self._usedNotes[key] = idx
return run return xR
return None return None
def _generateStyles(self) -> None: def _generateStyles(self) -> None:
@@ -643,8 +653,8 @@ class ToDocX(Tokenizer):
basedOn=S_NORM, basedOn=S_NORM,
before=0.0, before=0.0,
after=fnSz * self._marginFoot[1], after=fnSz * self._marginFoot[1],
left=fnSz * self._marginFoot[0],
line=fnSz * self._lineHeight, line=fnSz * self._lineHeight,
hanging=fnSz * self._marginFoot[0],
)) ))
# Add to Cache # Add to Cache
@@ -701,7 +711,7 @@ class ToDocX(Tokenizer):
def _coreXml(self) -> str: def _coreXml(self) -> str:
"""Populate app.xml.""" """Populate app.xml."""
rId = self._nextRelId() rId = self._nextRelId()
xRoot = ET.Element("coreProperties") xRoot = ET.Element(_mkTag("cp", "coreProperties"))
self._rels["core.xml"] = DocXXmlRel( self._rels["core.xml"] = DocXXmlRel(
rId=rId, rId=rId,
relType=f"{OOXML_SCM}/package/2006/relationships/metadata/core-properties", relType=f"{OOXML_SCM}/package/2006/relationships/metadata/core-properties",
@@ -718,7 +728,6 @@ class ToDocX(Tokenizer):
xmlSubElem(xRoot, _mkTag("dcterms", "modified"), timeStamp, attrib=tsAttr) xmlSubElem(xRoot, _mkTag("dcterms", "modified"), timeStamp, attrib=tsAttr)
xmlSubElem(xRoot, _mkTag("dc", "creator"), self._project.data.author) xmlSubElem(xRoot, _mkTag("dc", "creator"), self._project.data.author)
xmlSubElem(xRoot, _mkTag("dc", "title"), self._project.data.name) xmlSubElem(xRoot, _mkTag("dc", "title"), self._project.data.name)
xmlSubElem(xRoot, _mkTag("dc", "creator"), self._project.data.author)
xmlSubElem(xRoot, _mkTag("dc", "language"), self._dLanguage) xmlSubElem(xRoot, _mkTag("dc", "language"), self._dLanguage)
xmlSubElem(xRoot, _mkTag("cp", "revision"), str(self._project.data.saveCount)) xmlSubElem(xRoot, _mkTag("cp", "revision"), str(self._project.data.saveCount))
xmlSubElem(xRoot, _mkTag("cp", "lastModifiedBy"), self._project.data.author) xmlSubElem(xRoot, _mkTag("cp", "lastModifiedBy"), self._project.data.author)
@@ -754,9 +763,9 @@ class ToDocX(Tokenizer):
_wTag("hAnsi"): self._fontFamily, _wTag("hAnsi"): self._fontFamily,
_wTag("cs"): self._fontFamily, _wTag("cs"): self._fontFamily,
}) })
xmlSubElem(xRPr, _wTag("sz"), attrib={_wTag("val"): size}) xmlSubElem(xRPr, _wTag("sz"), attrib={W_VAL: size})
xmlSubElem(xRPr, _wTag("szCs"), attrib={_wTag("val"): size}) xmlSubElem(xRPr, _wTag("szCs"), attrib={W_VAL: size})
xmlSubElem(xRPr, _wTag("lang"), attrib={_wTag("val"): self._dLanguage}) xmlSubElem(xRPr, _wTag("lang"), attrib={W_VAL: self._dLanguage})
xmlSubElem(xPPr, _wTag("spacing"), attrib={_wTag("line"): line}) xmlSubElem(xPPr, _wTag("spacing"), attrib={_wTag("line"): line})
# Paragraph Styles # Paragraph Styles
@@ -770,11 +779,11 @@ class ToDocX(Tokenizer):
size = firstFloat(style.size, self._fontSize) size = firstFloat(style.size, self._fontSize)
xStyl = xmlSubElem(xRoot, _wTag("style"), attrib=sAttr) xStyl = xmlSubElem(xRoot, _wTag("style"), attrib=sAttr)
xmlSubElem(xStyl, _wTag("name"), attrib={_wTag("val"): style.name}) xmlSubElem(xStyl, _wTag("name"), attrib={W_VAL: style.name})
if style.basedOn: if style.basedOn:
xmlSubElem(xStyl, _wTag("basedOn"), attrib={_wTag("val"): style.basedOn}) xmlSubElem(xStyl, _wTag("basedOn"), attrib={W_VAL: style.basedOn})
if style.nextStyle: if style.nextStyle:
xmlSubElem(xStyl, _wTag("next"), attrib={_wTag("val"): style.nextStyle}) xmlSubElem(xStyl, _wTag("next"), attrib={W_VAL: style.nextStyle})
# pPr Node # pPr Node
pPr = xmlSubElem(xStyl, _wTag("pPr")) pPr = xmlSubElem(xStyl, _wTag("pPr"))
@@ -783,34 +792,33 @@ class ToDocX(Tokenizer):
_wTag("after"): str(int(20.0 * firstFloat(style.after))), _wTag("after"): str(int(20.0 * firstFloat(style.after))),
_wTag("line"): str(int(20.0 * firstFloat(style.line, size))), _wTag("line"): str(int(20.0 * firstFloat(style.line, size))),
}) })
if style.hanging is not None: if style.left is not None:
xmlSubElem(pPr, _wTag("ind"), attrib={ xmlSubElem(pPr, _wTag("ind"), attrib={
_wTag("left"): str(int(20.0 * style.hanging)), _wTag("left"): str(int(20.0 * style.left)),
_wTag("hanging"): str(int(20.0 * style.hanging)),
}) })
if style.align: if style.align:
xmlSubElem(pPr, _wTag("jc"), attrib={_wTag("val"): style.align}) xmlSubElem(pPr, _wTag("jc"), attrib={W_VAL: style.align})
if style.level is not None: if style.level is not None:
xmlSubElem(pPr, _wTag("outlineLvl"), attrib={_wTag("val"): str(style.level)}) xmlSubElem(pPr, _wTag("outlineLvl"), attrib={W_VAL: str(style.level)})
# rPr Node # rPr Node
rPr = xmlSubElem(xStyl, _wTag("rPr")) rPr = xmlSubElem(xStyl, _wTag("rPr"))
if style.bold: if style.bold:
xmlSubElem(rPr, _wTag("b")) xmlSubElem(rPr, _wTag("b"))
if style.color: if style.color:
xmlSubElem(rPr, _wTag("color"), attrib={_wTag("val"): style.color}) xmlSubElem(rPr, _wTag("color"), attrib={W_VAL: style.color})
xmlSubElem(rPr, _wTag("sz"), attrib={_wTag("val"): str(int(2.0 * size))}) xmlSubElem(rPr, _wTag("sz"), attrib={W_VAL: str(int(2.0 * size))})
xmlSubElem(rPr, _wTag("szCs"), attrib={_wTag("val"): str(int(2.0 * size))}) xmlSubElem(rPr, _wTag("szCs"), attrib={W_VAL: str(int(2.0 * size))})
# Character Style # Character Style
xStyl = xmlSubElem(xRoot, _wTag("style"), attrib={ xStyl = xmlSubElem(xRoot, _wTag("style"), attrib={
_wTag("type"): "character", _wTag("type"): "character",
_wTag("styleId"): "InternetLink" _wTag("styleId"): "InternetLink"
}) })
xmlSubElem(xStyl, _wTag("name"), attrib={_wTag("val"): "Hyperlink"}) xmlSubElem(xStyl, _wTag("name"), attrib={W_VAL: "Hyperlink"})
rPr = xmlSubElem(xStyl, _wTag("rPr")) rPr = xmlSubElem(xStyl, _wTag("rPr"))
xmlSubElem(rPr, _wTag("color"), attrib={_wTag("val"): _docXCol(self._theme.link)}) xmlSubElem(rPr, _wTag("color"), attrib={W_VAL: _docXCol(self._theme.link)})
xmlSubElem(rPr, _wTag("u"), attrib={_wTag("val"): "single"}) xmlSubElem(rPr, _wTag("u"), attrib={W_VAL: "single"})
return rId return rId
@@ -830,8 +838,8 @@ class ToDocX(Tokenizer):
xP = xmlSubElem(xRoot, _wTag("p")) xP = xmlSubElem(xRoot, _wTag("p"))
xPPr = xmlSubElem(xP, _wTag("pPr")) xPPr = xmlSubElem(xP, _wTag("pPr"))
xmlSubElem(xPPr, _wTag("pStyle"), attrib={_wTag("val"): S_HEAD}) xmlSubElem(xPPr, _wTag("pStyle"), attrib={W_VAL: S_HEAD})
xmlSubElem(xPPr, _wTag("jc"), attrib={_wTag("val"): "right"}) xmlSubElem(xPPr, _wTag("jc"), attrib={W_VAL: "right"})
xmlSubElem(xPPr, _wTag("rPr")) xmlSubElem(xPPr, _wTag("rPr"))
pre, page, post = self._headerFormat.partition(nwHeadFmt.DOC_PAGE) pre, page, post = self._headerFormat.partition(nwHeadFmt.DOC_PAGE)
@@ -840,25 +848,24 @@ class ToDocX(Tokenizer):
post = post.replace(nwHeadFmt.DOC_PROJECT, self._project.data.name) post = post.replace(nwHeadFmt.DOC_PROJECT, self._project.data.name)
post = post.replace(nwHeadFmt.DOC_AUTHOR, self._project.data.author) post = post.replace(nwHeadFmt.DOC_AUTHOR, self._project.data.author)
xSpace = _mkTag("xml", "space")
wFldCT = _wTag("fldCharType") wFldCT = _wTag("fldCharType")
parts: list[tuple[str, str | None, str, str]] = []
if pre: if pre:
parts.append(("t", pre, xSpace, "preserve"))
if page:
parts.append(("fldChar", None, wFldCT, "begin"))
parts.append(("t", " PAGE ", xSpace, "preserve"))
parts.append(("fldChar", None, wFldCT, "separate"))
parts.append(("t", "2", xSpace, "preserve"))
parts.append(("fldChar", None, wFldCT, "end"))
if post:
parts.append(("t", post, xSpace, "preserve"))
for part in parts:
xR = xmlSubElem(xP, _wTag("r")) xR = xmlSubElem(xP, _wTag("r"))
xmlSubElem(xR, _wTag("rPr")) _wText(xR, pre)
xmlSubElem(xR, _wTag(part[0]), part[1], attrib={part[2]: part[3]}) if page:
xR = xmlSubElem(xP, _wTag("r"))
xmlSubElem(xR, _wTag("fldChar"), attrib={wFldCT: "begin"})
xR = xmlSubElem(xP, _wTag("r"))
_wText(xR, " PAGE ")
xR = xmlSubElem(xP, _wTag("r"))
xmlSubElem(xR, _wTag("fldChar"), attrib={wFldCT: "separate"})
xR = xmlSubElem(xP, _wTag("r"))
_wText(xR, "0")
xR = xmlSubElem(xP, _wTag("r"))
xmlSubElem(xR, _wTag("fldChar"), attrib={wFldCT: "end"})
if post:
xR = xmlSubElem(xP, _wTag("r"))
_wText(xR, post)
return rId return rId
@@ -878,8 +885,8 @@ class ToDocX(Tokenizer):
xP = xmlSubElem(xRoot, _wTag("p")) xP = xmlSubElem(xRoot, _wTag("p"))
xPPr = xmlSubElem(xP, _wTag("pPr")) xPPr = xmlSubElem(xP, _wTag("pPr"))
xmlSubElem(xPPr, _wTag("pStyle"), attrib={_wTag("val"): S_HEAD}) xmlSubElem(xPPr, _wTag("pStyle"), attrib={W_VAL: S_HEAD})
xmlSubElem(xPPr, _wTag("jc"), attrib={_wTag("val"): "right"}) xmlSubElem(xPPr, _wTag("jc"), attrib={W_VAL: "right"})
xmlSubElem(xPPr, _wTag("rPr")) xmlSubElem(xPPr, _wTag("rPr"))
xR = xmlSubElem(xP, _wTag("r")) xR = xmlSubElem(xP, _wTag("r"))
@@ -891,6 +898,7 @@ class ToDocX(Tokenizer):
"""Populate document.xml.""" """Populate document.xml."""
rId = self._nextRelId() rId = self._nextRelId()
xRoot = ET.Element(_wTag("document")) xRoot = ET.Element(_wTag("document"))
xRoot.set("xmlns:w14", "http://schemas.microsoft.com/office/word/2010/wordml")
xBody = xmlSubElem(xRoot, _wTag("body")) xBody = xmlSubElem(xRoot, _wTag("body"))
self._rels["document.xml"] = DocXXmlRel( self._rels["document.xml"] = DocXXmlRel(
rId=rId, rId=rId,
@@ -931,7 +939,7 @@ class ToDocX(Tokenizer):
xFn = xmlSubElem(xSect, _wTag("footnotePr")) xFn = xmlSubElem(xSect, _wTag("footnotePr"))
xmlSubElem(xFn, _wTag("numFmt"), attrib={ xmlSubElem(xFn, _wTag("numFmt"), attrib={
_wTag("val"): "decimal", W_VAL: "decimal",
}) })
xmlSubElem(xSect, _wTag("pgSz"), attrib={ xmlSubElem(xSect, _wTag("pgSz"), attrib={
@@ -972,12 +980,36 @@ class ToDocX(Tokenizer):
for key, idx in self._usedNotes.items(): for key, idx in self._usedNotes.items():
par = DocXParagraph() par = DocXParagraph()
par.setIsFootnote(True)
if content := self._footnotes.get(key): if content := self._footnotes.get(key):
self._processFragments(par, S_FNOTE, content[0], content[1]) self._processFragments(par, S_FNOTE, content[0], content[1])
par.toXml(xmlSubElem(xRoot, _wTag("footnote"), attrib={_wTag("id"): str(idx)})) par.toXml(xmlSubElem(xRoot, _wTag("footnote"), attrib={_wTag("id"): str(idx)}))
return rId return rId
def _fontTableXml(self) -> str:
"""Populate fontTable.xml."""
rId = self._nextRelId()
xRoot = ET.Element(_wTag("fonts"))
self._rels["fontTable.xml"] = DocXXmlRel(
rId=rId,
relType=f"{RELS_BASE}/fontTable",
)
self._files["fontTable.xml"] = DocXXmlFile(
xml=xRoot,
path="word",
contentType=f"{WORD_BASE}.fontTable+xml",
)
xFont = xmlSubElem(xRoot, _wTag("font"), attrib={
_wTag("name"): self._textFont.family(),
})
xmlSubElem(xFont, _wTag("pitch"), attrib={
W_VAL: "fixed" if self._textFont.fixedPitch() else "variable",
})
return rId
def _settingsXml(self) -> str: def _settingsXml(self) -> str:
"""Populate settings.xml.""" """Populate settings.xml."""
rId = self._nextRelId() rId = self._nextRelId()
@@ -992,15 +1024,22 @@ class ToDocX(Tokenizer):
contentType=f"{WORD_BASE}.settings+xml", contentType=f"{WORD_BASE}.settings+xml",
) )
xFn = xmlSubElem(xRoot, _wTag("footnotePr")) xSet = xmlSubElem(xRoot, _wTag("footnotePr"))
xmlSubElem(xFn, _wTag("numFmt"), attrib={_wTag("val"): "decimal"}) xmlSubElem(xSet, _wTag("numFmt"), attrib={W_VAL: "decimal"})
xSet = xmlSubElem(xRoot, _wTag("compat"))
xmlSubElem(xSet, _wTag("compatSetting"), attrib={
_wTag("name"): "compatibilityMode",
_wTag("uri"): "http://schemas.microsoft.com/office/word",
W_VAL: "12",
})
if self._counts: if self._counts:
xVars = xmlSubElem(xRoot, _wTag("docVars")) xVars = xmlSubElem(xRoot, _wTag("docVars"))
for key, value in self._counts.items(): for key, value in self._counts.items():
xmlSubElem(xVars, _wTag("docVar"), attrib={ xmlSubElem(xVars, _wTag("docVar"), attrib={
_wTag("name"): f"Manuscript{key[:1].upper()}{key[1:]}", _wTag("name"): f"Manuscript{key[:1].upper()}{key[1:]}",
_wTag("val"): str(value), W_VAL: str(value),
}) })
return rId return rId
@@ -1011,7 +1050,7 @@ class DocXParagraph:
__slots__ = ( __slots__ = (
"_content", "_style", "_textAlign", "_content", "_style", "_textAlign",
"_topMargin", "_bottomMargin", "_leftMargin", "_rightMargin", "_topMargin", "_bottomMargin", "_leftMargin", "_rightMargin",
"_indentFirst", "_breakBefore", "_breakAfter", "_indentFirst", "_breakBefore", "_breakAfter", "_footnoteRef",
) )
def __init__(self) -> None: def __init__(self) -> None:
@@ -1025,6 +1064,7 @@ class DocXParagraph:
self._indentFirst = False self._indentFirst = False
self._breakBefore = False self._breakBefore = False
self._breakAfter = False self._breakAfter = False
self._footnoteRef = False
return return
## ##
@@ -1086,6 +1126,11 @@ class DocXParagraph:
self._breakAfter = state self._breakAfter = state
return return
def setIsFootnote(self, state: bool) -> None:
"""Set is footnote flag."""
self._footnoteRef = state
return
## ##
# Methods # Methods
## ##
@@ -1098,7 +1143,7 @@ class DocXParagraph:
def toXml(self, body: ET.Element) -> None: def toXml(self, body: ET.Element) -> None:
"""Called after all content is set.""" """Called after all content is set."""
if style := self._style: if style := self._style:
par = xmlSubElem(body, _wTag("p")) xP = xmlSubElem(body, _wTag("p"))
# Values # Values
indent = {} indent = {}
@@ -1110,8 +1155,8 @@ class DocXParagraph:
indent[_wTag("right")] = str(int(20.0 * self._rightMargin)) indent[_wTag("right")] = str(int(20.0 * self._rightMargin))
# Paragraph # Paragraph
pPr = xmlSubElem(par, _wTag("pPr")) pPr = xmlSubElem(xP, _wTag("pPr"))
xmlSubElem(pPr, _wTag("pStyle"), attrib={_wTag("val"): style.styleId}) xmlSubElem(pPr, _wTag("pStyle"), attrib={W_VAL: style.styleId})
if self._topMargin is not None or self._bottomMargin is not None: if self._topMargin is not None or self._bottomMargin is not None:
xmlSubElem(pPr, _wTag("spacing"), attrib={ xmlSubElem(pPr, _wTag("spacing"), attrib={
_wTag("before"): str(int(20.0 * firstFloat(self._topMargin, style.before))), _wTag("before"): str(int(20.0 * firstFloat(self._topMargin, style.before))),
@@ -1121,16 +1166,21 @@ class DocXParagraph:
if indent: if indent:
xmlSubElem(pPr, _wTag("ind"), attrib=indent) xmlSubElem(pPr, _wTag("ind"), attrib=indent)
if self._textAlign: if self._textAlign:
xmlSubElem(pPr, _wTag("jc"), attrib={_wTag("val"): self._textAlign}) xmlSubElem(pPr, _wTag("jc"), attrib={W_VAL: self._textAlign})
# Text # Text
if self._footnoteRef:
xR = xmlSubElem(xP, _wTag("r"))
rPr = xmlSubElem(xR, _wTag("rPr"))
xmlSubElem(rPr, _wTag("vertAlign"), attrib={W_VAL: "superscript"})
xmlSubElem(xR, _wTag("footnoteRef"))
if self._breakBefore: if self._breakBefore:
wr = xmlSubElem(par, _wTag("r")) xR = xmlSubElem(xP, _wTag("r"))
xmlSubElem(wr, _wTag("br"), attrib={_wTag("type"): "page"}) xmlSubElem(xR, _wTag("br"), attrib={_wTag("type"): "page"})
for run in self._content: for xR in self._content:
par.append(run) xP.append(xR)
if self._breakAfter: if self._breakAfter:
wr = xmlSubElem(par, _wTag("r")) xR = xmlSubElem(xP, _wTag("r"))
xmlSubElem(wr, _wTag("br"), attrib={_wTag("type"): "page"}) xmlSubElem(xR, _wTag("br"), attrib={_wTag("type"): "page"})
return return
+3 -3
View File
@@ -1,14 +1,14 @@
%%~name: Making a Scene %%~name: Making a Scene
%%~path: 6a2d6d5f4f401/636b6aa9b697b %%~path: 6a2d6d5f4f401/636b6aa9b697b
%%~kind: NOVEL/DOCUMENT %%~kind: NOVEL/DOCUMENT
%%~hash: 89d562ae05727028d74bd10cfd6953882d1cd140 %%~hash: 8d245fa740926779d19741ff7f75ef387b55130c
%%~date: Unknown/2024-10-25 12:41:02 %%~date: Unknown/2024-10-25 23:54:52
### Making a Scene ### Making a Scene
@pov: Jane @pov: Jane
@char: John, Jane @char: John, Jane
@location: Earth @location: Earth
@mention: Bob, Space @mention: Space
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. The scene document can be sorted after the chapter document, or as a child of the chapter. Both result in the same output in the end, so it is a matter of preference. 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. The scene document can be sorted after the chapter document, or as a child of the chapter. Both result in the same output in the end, so it is a matter of preference.
+3 -3
View File
@@ -1,6 +1,6 @@
<?xml version='1.0' encoding='utf-8'?> <?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="2.6a2" hexVersion="0x020600a2" fileVersion="1.5" fileRevision="4" timeStamp="2024-10-25 23:19:07"> <novelWriterXML appVersion="2.6a2" hexVersion="0x020600a2" fileVersion="1.5" fileRevision="4" timeStamp="2024-10-26 16:59:09">
<project id="e2be99af-f9bf-4403-857a-c3d1ac25abea" saveCount="2122" autoCount="279" editTime="94855"> <project id="e2be99af-f9bf-4403-857a-c3d1ac25abea" saveCount="2132" autoCount="279" editTime="95062">
<name>Sample Project</name> <name>Sample Project</name>
<author>Jane Smith</author> <author>Jane Smith</author>
</project> </project>
@@ -58,7 +58,7 @@
<name status="sf24ce6" import="ia857f0" active="yes">Chapter One</name> <name status="sf24ce6" import="ia857f0" active="yes">Chapter One</name>
</item> </item>
<item handle="636b6aa9b697b" parent="6a2d6d5f4f401" root="7031beac91f75" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="636b6aa9b697b" parent="6a2d6d5f4f401" root="7031beac91f75" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="no" heading="H3" charCount="2999" wordCount="530" paraCount="16" cursorPos="1663" /> <meta expanded="no" heading="H3" charCount="2999" wordCount="530" paraCount="16" cursorPos="357" />
<name status="s90e6c9" import="ia857f0" active="yes">Making a Scene</name> <name status="s90e6c9" import="ia857f0" active="yes">Making a Scene</name>
</item> </item>
<item handle="bc0cbd2a407f3" parent="6a2d6d5f4f401" root="7031beac91f75" order="1" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="bc0cbd2a407f3" parent="6a2d6d5f4f401" root="7031beac91f75" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
@@ -7,6 +7,7 @@
<ns0:Override PartName="/docProps/core.xml" ContentType="application/vnd.openxmlformats-package.core-properties+xml" /> <ns0:Override PartName="/docProps/core.xml" ContentType="application/vnd.openxmlformats-package.core-properties+xml" />
<ns0:Override PartName="/docProps/app.xml" ContentType="application/vnd.openxmlformats-officedocument.extended-properties+xml" /> <ns0:Override PartName="/docProps/app.xml" ContentType="application/vnd.openxmlformats-officedocument.extended-properties+xml" />
<ns0:Override PartName="/word/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml" /> <ns0:Override PartName="/word/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml" />
<ns0:Override PartName="/word/fontTable.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.fontTable+xml" />
<ns0:Override PartName="/word/header1.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.header+xml" /> <ns0:Override PartName="/word/header1.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.header+xml" />
<ns0:Override PartName="/word/header2.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.header+xml" /> <ns0:Override PartName="/word/header2.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.header+xml" />
<ns0:Override PartName="/word/document.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml" /> <ns0:Override PartName="/word/document.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml" />
@@ -1,11 +1,10 @@
<?xml version='1.0' encoding='utf-8'?> <?xml version='1.0' encoding='utf-8'?>
<coreProperties xmlns:cp="http://schemas.openxmlformats.org/package/2006/metadata/core-properties" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:dcterms="http://purl.org/dc/terms/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <cp:coreProperties xmlns:cp="http://schemas.openxmlformats.org/package/2006/metadata/core-properties" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:dcterms="http://purl.org/dc/terms/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<dcterms:created xsi:type="dcterms:W3CDTF">2024-10-25T19:23:34</dcterms:created> <dcterms:created xsi:type="dcterms:W3CDTF">2024-10-26T17:01:14</dcterms:created>
<dcterms:modified xsi:type="dcterms:W3CDTF">2024-10-25T19:23:34</dcterms:modified> <dcterms:modified xsi:type="dcterms:W3CDTF">2024-10-26T17:01:14</dcterms:modified>
<dc:creator>lipsum.com</dc:creator> <dc:creator>lipsum.com</dc:creator>
<dc:title>Lorem Ipsum</dc:title> <dc:title>Lorem Ipsum</dc:title>
<dc:creator>lipsum.com</dc:creator> <dc:language>en-GB</dc:language>
<dc:language>en_GB</dc:language>
<cp:revision>48</cp:revision> <cp:revision>48</cp:revision>
<cp:lastModifiedBy>lipsum.com</cp:lastModifiedBy> <cp:lastModifiedBy>lipsum.com</cp:lastModifiedBy>
</coreProperties> </cp:coreProperties>
@@ -1398,8 +1398,8 @@
</w:r> </w:r>
</w:p> </w:p>
<w:sectPr> <w:sectPr>
<w:headerReference w:type="first" r:id="rId6" /> <w:headerReference w:type="first" r:id="rId7" />
<w:headerReference w:type="default" r:id="rId5" /> <w:headerReference w:type="default" r:id="rId6" />
<w:footnotePr> <w:footnotePr>
<w:numFmt w:val="decimal" /> <w:numFmt w:val="decimal" />
</w:footnotePr> </w:footnotePr>
@@ -2,8 +2,9 @@
<ns0:Relationships xmlns:ns0="http://schemas.openxmlformats.org/package/2006/relationships"> <ns0:Relationships xmlns:ns0="http://schemas.openxmlformats.org/package/2006/relationships">
<ns0:Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink" Target="http://lipsum.com" TargetMode="External" /> <ns0:Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink" Target="http://lipsum.com" TargetMode="External" />
<ns0:Relationship Id="rId4" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml" /> <ns0:Relationship Id="rId4" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml" />
<ns0:Relationship Id="rId5" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/header" Target="header1.xml" /> <ns0:Relationship Id="rId5" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/fontTable" Target="fontTable.xml" />
<ns0:Relationship Id="rId6" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/header" Target="header2.xml" /> <ns0:Relationship Id="rId6" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/header" Target="header1.xml" />
<ns0:Relationship Id="rId8" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/settings" Target="settings.xml" /> <ns0:Relationship Id="rId7" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/header" Target="header2.xml" />
<ns0:Relationship Id="rId9" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/footnotes" Target="footnotes.xml" /> <ns0:Relationship Id="rId9" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/settings" Target="settings.xml" />
<ns0:Relationship Id="rId10" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/footnotes" Target="footnotes.xml" />
</ns0:Relationships> </ns0:Relationships>
@@ -0,0 +1,6 @@
<?xml version='1.0' encoding='utf-8'?>
<w:fonts xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
<w:font w:name="Source Sans Pro">
<w:pitch w:val="variable" />
</w:font>
</w:fonts>
@@ -5,6 +5,12 @@
<w:pPr> <w:pPr>
<w:pStyle w:val="FootnoteText" /> <w:pStyle w:val="FootnoteText" />
</w:pPr> </w:pPr>
<w:r>
<w:rPr>
<w:vertAlign w:val="superscript" />
</w:rPr>
<w:footnoteRef />
</w:r>
<w:r> <w:r>
<w:rPr> <w:rPr>
<w:i /> <w:i />
@@ -7,31 +7,24 @@
<w:rPr /> <w:rPr />
</w:pPr> </w:pPr>
<w:r> <w:r>
<w:rPr />
<w:t xml:space="preserve">Page </w:t> <w:t xml:space="preserve">Page </w:t>
</w:r> </w:r>
<w:r> <w:r>
<w:rPr />
<w:fldChar w:fldCharType="begin" /> <w:fldChar w:fldCharType="begin" />
</w:r> </w:r>
<w:r> <w:r>
<w:rPr />
<w:t xml:space="preserve"> PAGE </w:t> <w:t xml:space="preserve"> PAGE </w:t>
</w:r> </w:r>
<w:r> <w:r>
<w:rPr />
<w:fldChar w:fldCharType="separate" /> <w:fldChar w:fldCharType="separate" />
</w:r> </w:r>
<w:r> <w:r>
<w:rPr /> <w:t>0</w:t>
<w:t xml:space="preserve">2</w:t>
</w:r> </w:r>
<w:r> <w:r>
<w:rPr />
<w:fldChar w:fldCharType="end" /> <w:fldChar w:fldCharType="end" />
</w:r> </w:r>
<w:r> <w:r>
<w:rPr />
<w:t xml:space="preserve"> - Lorem Ipsum (lipsum.com)</w:t> <w:t xml:space="preserve"> - Lorem Ipsum (lipsum.com)</w:t>
</w:r> </w:r>
</w:p> </w:p>
+1 -1
View File
@@ -2,5 +2,5 @@
<ns0:Relationships xmlns:ns0="http://schemas.openxmlformats.org/package/2006/relationships"> <ns0:Relationships xmlns:ns0="http://schemas.openxmlformats.org/package/2006/relationships">
<ns0:Relationship Id="rId2" Type="http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties" Target="docProps/core.xml" /> <ns0:Relationship Id="rId2" Type="http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties" Target="docProps/core.xml" />
<ns0:Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties" Target="docProps/app.xml" /> <ns0:Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties" Target="docProps/app.xml" />
<ns0:Relationship Id="rId7" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="word/document.xml" /> <ns0:Relationship Id="rId8" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="word/document.xml" />
</ns0:Relationships> </ns0:Relationships>
@@ -3,6 +3,9 @@
<w:footnotePr> <w:footnotePr>
<w:numFmt w:val="decimal" /> <w:numFmt w:val="decimal" />
</w:footnotePr> </w:footnotePr>
<w:compat>
<w:compatSetting w:name="compatibilityMode" w:uri="http://schemas.microsoft.com/office/word" w:val="12" />
</w:compat>
<w:docVars> <w:docVars>
<w:docVar w:name="ManuscriptTitleCount" w:val="11" /> <w:docVar w:name="ManuscriptTitleCount" w:val="11" />
<w:docVar w:name="ManuscriptParagraphCount" w:val="43" /> <w:docVar w:name="ManuscriptParagraphCount" w:val="43" />
@@ -6,7 +6,7 @@
<w:rFonts w:ascii="Source Sans Pro" w:hAnsi="Source Sans Pro" w:cs="Source Sans Pro" /> <w:rFonts w:ascii="Source Sans Pro" w:hAnsi="Source Sans Pro" w:cs="Source Sans Pro" />
<w:sz w:val="24" /> <w:sz w:val="24" />
<w:szCs w:val="24" /> <w:szCs w:val="24" />
<w:lang w:val="en_GB" /> <w:lang w:val="en-GB" />
</w:rPr> </w:rPr>
</w:rPrDefault> </w:rPrDefault>
<w:pPrDefault> <w:pPrDefault>
@@ -142,7 +142,7 @@
<w:basedOn w:val="Normal" /> <w:basedOn w:val="Normal" />
<w:pPr> <w:pPr>
<w:spacing w:before="0" w:after="90" w:line="220" /> <w:spacing w:before="0" w:after="90" w:line="220" />
<w:ind w:left="272" w:hanging="272" /> <w:ind w:left="272" />
</w:pPr> </w:pPr>
<w:rPr> <w:rPr>
<w:sz w:val="19" /> <w:sz w:val="19" />
+4
View File
@@ -556,10 +556,13 @@ def testFmtToDocX_Footnotes(mockGUI):
assert xmlToText(doc._files["footnotes.xml"].xml) == ( assert xmlToText(doc._files["footnotes.xml"].xml) == (
'<w:footnotes>' '<w:footnotes>'
'<w:footnote w:id="1"><w:p><w:pPr><w:pStyle w:val="FootnoteText" /></w:pPr>' '<w:footnote w:id="1"><w:p><w:pPr><w:pStyle w:val="FootnoteText" /></w:pPr>'
'<w:r><w:rPr><w:vertAlign w:val="superscript" /></w:rPr><w:footnoteRef /></w:r>'
'<w:r><w:rPr /><w:t>Footnote text A.</w:t></w:r></w:p></w:footnote>' '<w:r><w:rPr /><w:t>Footnote text A.</w:t></w:r></w:p></w:footnote>'
'<w:footnote w:id="2"><w:p><w:pPr><w:pStyle w:val="FootnoteText" /></w:pPr>' '<w:footnote w:id="2"><w:p><w:pPr><w:pStyle w:val="FootnoteText" /></w:pPr>'
'<w:r><w:rPr><w:vertAlign w:val="superscript" /></w:rPr><w:footnoteRef /></w:r>'
'<w:r><w:rPr /><w:t>Another footnote.</w:t></w:r></w:p></w:footnote>' '<w:r><w:rPr /><w:t>Another footnote.</w:t></w:r></w:p></w:footnote>'
'<w:footnote w:id="3"><w:p><w:pPr><w:pStyle w:val="FootnoteText" /></w:pPr>' '<w:footnote w:id="3"><w:p><w:pPr><w:pStyle w:val="FootnoteText" /></w:pPr>'
'<w:r><w:rPr><w:vertAlign w:val="superscript" /></w:rPr><w:footnoteRef /></w:r>'
'<w:r><w:rPr /><w:t>Again?</w:t></w:r></w:p></w:footnote>' '<w:r><w:rPr /><w:t>Again?</w:t></w:r></w:p></w:footnote>'
'</w:footnotes>' '</w:footnotes>'
) )
@@ -619,6 +622,7 @@ def testFmtToDocX_SaveDocument(mockGUI, prjLipsum, fncPath, tstPaths):
fncPath / "extract" / "word" / "header2.xml", fncPath / "extract" / "word" / "header2.xml",
fncPath / "extract" / "word" / "settings.xml", fncPath / "extract" / "word" / "settings.xml",
fncPath / "extract" / "word" / "styles.xml", fncPath / "extract" / "word" / "styles.xml",
fncPath / "extract" / "word" / "fontTable.xml",
] ]
outDir = tstPaths.outDir / "fmtToDocX_SaveDocument" outDir = tstPaths.outDir / "fmtToDocX_SaveDocument"