Add font table, fix core.xml bug, clean up code, update tests, for DocX
This commit is contained in:
+121
-71
@@ -87,6 +87,17 @@ def _docXCol(color: QColor) -> str:
|
||||
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
|
||||
X_BLD = 0x001 # Bold format
|
||||
X_ITA = 0x002 # Italic format
|
||||
@@ -145,9 +156,9 @@ class DocXParStyle(NamedTuple):
|
||||
nextStyle: str | None = None
|
||||
before: float | None = None
|
||||
after: float | None = None
|
||||
left: float | None = None
|
||||
line: float | None = None
|
||||
indentFirst: float | None = None
|
||||
hanging: float | None = None
|
||||
align: str | None = None
|
||||
default: bool = False
|
||||
level: int | None = None
|
||||
@@ -191,7 +202,7 @@ class ToDocX(Tokenizer):
|
||||
def setLanguage(self, language: str | None) -> None:
|
||||
"""Set language for the document."""
|
||||
if language:
|
||||
self._dLanguage = language
|
||||
self._dLanguage = language.replace("_", "-")
|
||||
return
|
||||
|
||||
def setPageLayout(
|
||||
@@ -296,6 +307,7 @@ class ToDocX(Tokenizer):
|
||||
self._coreXml()
|
||||
self._appXml()
|
||||
self._stylesXml()
|
||||
self._fontTableXml()
|
||||
|
||||
fId = None
|
||||
dId = None
|
||||
@@ -451,32 +463,30 @@ class ToDocX(Tokenizer):
|
||||
if fmt & X_ITA:
|
||||
xmlSubElem(rPr, _wTag("i"))
|
||||
if fmt & X_UND:
|
||||
xmlSubElem(rPr, _wTag("u"), attrib={_wTag("val"): "single"})
|
||||
xmlSubElem(rPr, _wTag("u"), attrib={W_VAL: "single"})
|
||||
if fmt & X_MRK:
|
||||
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:
|
||||
xmlSubElem(rPr, _wTag("strike"))
|
||||
if fmt & X_SUP:
|
||||
xmlSubElem(rPr, _wTag("vertAlign"), attrib={_wTag("val"): "superscript"})
|
||||
xmlSubElem(rPr, _wTag("vertAlign"), attrib={W_VAL: "superscript"})
|
||||
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)):
|
||||
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):
|
||||
if segment == "\n":
|
||||
xmlSubElem(xR, _wTag("br"))
|
||||
elif segment == "\t":
|
||||
xmlSubElem(xR, _wTag("tab"))
|
||||
elif len(segment) != len(segment.strip()):
|
||||
xmlSubElem(xR, _wTag("t"), segment, attrib={_mkTag("xml", "space"): "preserve"})
|
||||
elif segment:
|
||||
xmlSubElem(xR, _wTag("t"), segment)
|
||||
_wText(xR, segment)
|
||||
|
||||
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)
|
||||
xH = ET.Element(_wTag("hyperlink"), attrib={_mkTag("r", "id"): rId})
|
||||
xH.append(xR)
|
||||
@@ -492,12 +502,12 @@ class ToDocX(Tokenizer):
|
||||
"""Generate a footnote XML object."""
|
||||
if key in self._footnotes:
|
||||
idx = len(self._usedNotes) + 1
|
||||
run = ET.Element(_wTag("r"))
|
||||
rPr = xmlSubElem(run, _wTag("rPr"))
|
||||
xmlSubElem(rPr, _wTag("vertAlign"), attrib={_wTag("val"): "superscript"})
|
||||
xmlSubElem(run, _wTag("footnoteReference"), attrib={_wTag("id"): str(idx)})
|
||||
xR = ET.Element(_wTag("r"))
|
||||
rPr = xmlSubElem(xR, _wTag("rPr"))
|
||||
xmlSubElem(rPr, _wTag("vertAlign"), attrib={W_VAL: "superscript"})
|
||||
xmlSubElem(xR, _wTag("footnoteReference"), attrib={_wTag("id"): str(idx)})
|
||||
self._usedNotes[key] = idx
|
||||
return run
|
||||
return xR
|
||||
return None
|
||||
|
||||
def _generateStyles(self) -> None:
|
||||
@@ -643,8 +653,8 @@ class ToDocX(Tokenizer):
|
||||
basedOn=S_NORM,
|
||||
before=0.0,
|
||||
after=fnSz * self._marginFoot[1],
|
||||
left=fnSz * self._marginFoot[0],
|
||||
line=fnSz * self._lineHeight,
|
||||
hanging=fnSz * self._marginFoot[0],
|
||||
))
|
||||
|
||||
# Add to Cache
|
||||
@@ -701,7 +711,7 @@ class ToDocX(Tokenizer):
|
||||
def _coreXml(self) -> str:
|
||||
"""Populate app.xml."""
|
||||
rId = self._nextRelId()
|
||||
xRoot = ET.Element("coreProperties")
|
||||
xRoot = ET.Element(_mkTag("cp", "coreProperties"))
|
||||
self._rels["core.xml"] = DocXXmlRel(
|
||||
rId=rId,
|
||||
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("dc", "creator"), self._project.data.author)
|
||||
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("cp", "revision"), str(self._project.data.saveCount))
|
||||
xmlSubElem(xRoot, _mkTag("cp", "lastModifiedBy"), self._project.data.author)
|
||||
@@ -754,9 +763,9 @@ class ToDocX(Tokenizer):
|
||||
_wTag("hAnsi"): self._fontFamily,
|
||||
_wTag("cs"): self._fontFamily,
|
||||
})
|
||||
xmlSubElem(xRPr, _wTag("sz"), attrib={_wTag("val"): size})
|
||||
xmlSubElem(xRPr, _wTag("szCs"), attrib={_wTag("val"): size})
|
||||
xmlSubElem(xRPr, _wTag("lang"), attrib={_wTag("val"): self._dLanguage})
|
||||
xmlSubElem(xRPr, _wTag("sz"), attrib={W_VAL: size})
|
||||
xmlSubElem(xRPr, _wTag("szCs"), attrib={W_VAL: size})
|
||||
xmlSubElem(xRPr, _wTag("lang"), attrib={W_VAL: self._dLanguage})
|
||||
xmlSubElem(xPPr, _wTag("spacing"), attrib={_wTag("line"): line})
|
||||
|
||||
# Paragraph Styles
|
||||
@@ -770,11 +779,11 @@ class ToDocX(Tokenizer):
|
||||
size = firstFloat(style.size, self._fontSize)
|
||||
|
||||
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:
|
||||
xmlSubElem(xStyl, _wTag("basedOn"), attrib={_wTag("val"): style.basedOn})
|
||||
xmlSubElem(xStyl, _wTag("basedOn"), attrib={W_VAL: style.basedOn})
|
||||
if style.nextStyle:
|
||||
xmlSubElem(xStyl, _wTag("next"), attrib={_wTag("val"): style.nextStyle})
|
||||
xmlSubElem(xStyl, _wTag("next"), attrib={W_VAL: style.nextStyle})
|
||||
|
||||
# pPr Node
|
||||
pPr = xmlSubElem(xStyl, _wTag("pPr"))
|
||||
@@ -783,34 +792,33 @@ class ToDocX(Tokenizer):
|
||||
_wTag("after"): str(int(20.0 * firstFloat(style.after))),
|
||||
_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={
|
||||
_wTag("left"): str(int(20.0 * style.hanging)),
|
||||
_wTag("hanging"): str(int(20.0 * style.hanging)),
|
||||
_wTag("left"): str(int(20.0 * style.left)),
|
||||
})
|
||||
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:
|
||||
xmlSubElem(pPr, _wTag("outlineLvl"), attrib={_wTag("val"): str(style.level)})
|
||||
xmlSubElem(pPr, _wTag("outlineLvl"), attrib={W_VAL: str(style.level)})
|
||||
|
||||
# rPr Node
|
||||
rPr = xmlSubElem(xStyl, _wTag("rPr"))
|
||||
if style.bold:
|
||||
xmlSubElem(rPr, _wTag("b"))
|
||||
if style.color:
|
||||
xmlSubElem(rPr, _wTag("color"), attrib={_wTag("val"): style.color})
|
||||
xmlSubElem(rPr, _wTag("sz"), attrib={_wTag("val"): str(int(2.0 * size))})
|
||||
xmlSubElem(rPr, _wTag("szCs"), attrib={_wTag("val"): str(int(2.0 * size))})
|
||||
xmlSubElem(rPr, _wTag("color"), attrib={W_VAL: style.color})
|
||||
xmlSubElem(rPr, _wTag("sz"), attrib={W_VAL: str(int(2.0 * size))})
|
||||
xmlSubElem(rPr, _wTag("szCs"), attrib={W_VAL: str(int(2.0 * size))})
|
||||
|
||||
# Character Style
|
||||
xStyl = xmlSubElem(xRoot, _wTag("style"), attrib={
|
||||
_wTag("type"): "character",
|
||||
_wTag("styleId"): "InternetLink"
|
||||
})
|
||||
xmlSubElem(xStyl, _wTag("name"), attrib={_wTag("val"): "Hyperlink"})
|
||||
xmlSubElem(xStyl, _wTag("name"), attrib={W_VAL: "Hyperlink"})
|
||||
rPr = xmlSubElem(xStyl, _wTag("rPr"))
|
||||
xmlSubElem(rPr, _wTag("color"), attrib={_wTag("val"): _docXCol(self._theme.link)})
|
||||
xmlSubElem(rPr, _wTag("u"), attrib={_wTag("val"): "single"})
|
||||
xmlSubElem(rPr, _wTag("color"), attrib={W_VAL: _docXCol(self._theme.link)})
|
||||
xmlSubElem(rPr, _wTag("u"), attrib={W_VAL: "single"})
|
||||
|
||||
return rId
|
||||
|
||||
@@ -830,8 +838,8 @@ class ToDocX(Tokenizer):
|
||||
|
||||
xP = xmlSubElem(xRoot, _wTag("p"))
|
||||
xPPr = xmlSubElem(xP, _wTag("pPr"))
|
||||
xmlSubElem(xPPr, _wTag("pStyle"), attrib={_wTag("val"): S_HEAD})
|
||||
xmlSubElem(xPPr, _wTag("jc"), attrib={_wTag("val"): "right"})
|
||||
xmlSubElem(xPPr, _wTag("pStyle"), attrib={W_VAL: S_HEAD})
|
||||
xmlSubElem(xPPr, _wTag("jc"), attrib={W_VAL: "right"})
|
||||
xmlSubElem(xPPr, _wTag("rPr"))
|
||||
|
||||
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_AUTHOR, self._project.data.author)
|
||||
|
||||
xSpace = _mkTag("xml", "space")
|
||||
wFldCT = _wTag("fldCharType")
|
||||
|
||||
parts: list[tuple[str, str | None, str, str]] = []
|
||||
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"))
|
||||
xmlSubElem(xR, _wTag("rPr"))
|
||||
xmlSubElem(xR, _wTag(part[0]), part[1], attrib={part[2]: part[3]})
|
||||
_wText(xR, pre)
|
||||
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
|
||||
|
||||
@@ -878,8 +885,8 @@ class ToDocX(Tokenizer):
|
||||
|
||||
xP = xmlSubElem(xRoot, _wTag("p"))
|
||||
xPPr = xmlSubElem(xP, _wTag("pPr"))
|
||||
xmlSubElem(xPPr, _wTag("pStyle"), attrib={_wTag("val"): S_HEAD})
|
||||
xmlSubElem(xPPr, _wTag("jc"), attrib={_wTag("val"): "right"})
|
||||
xmlSubElem(xPPr, _wTag("pStyle"), attrib={W_VAL: S_HEAD})
|
||||
xmlSubElem(xPPr, _wTag("jc"), attrib={W_VAL: "right"})
|
||||
xmlSubElem(xPPr, _wTag("rPr"))
|
||||
|
||||
xR = xmlSubElem(xP, _wTag("r"))
|
||||
@@ -891,6 +898,7 @@ class ToDocX(Tokenizer):
|
||||
"""Populate document.xml."""
|
||||
rId = self._nextRelId()
|
||||
xRoot = ET.Element(_wTag("document"))
|
||||
xRoot.set("xmlns:w14", "http://schemas.microsoft.com/office/word/2010/wordml")
|
||||
xBody = xmlSubElem(xRoot, _wTag("body"))
|
||||
self._rels["document.xml"] = DocXXmlRel(
|
||||
rId=rId,
|
||||
@@ -931,7 +939,7 @@ class ToDocX(Tokenizer):
|
||||
|
||||
xFn = xmlSubElem(xSect, _wTag("footnotePr"))
|
||||
xmlSubElem(xFn, _wTag("numFmt"), attrib={
|
||||
_wTag("val"): "decimal",
|
||||
W_VAL: "decimal",
|
||||
})
|
||||
|
||||
xmlSubElem(xSect, _wTag("pgSz"), attrib={
|
||||
@@ -972,12 +980,36 @@ class ToDocX(Tokenizer):
|
||||
|
||||
for key, idx in self._usedNotes.items():
|
||||
par = DocXParagraph()
|
||||
par.setIsFootnote(True)
|
||||
if content := self._footnotes.get(key):
|
||||
self._processFragments(par, S_FNOTE, content[0], content[1])
|
||||
par.toXml(xmlSubElem(xRoot, _wTag("footnote"), attrib={_wTag("id"): str(idx)}))
|
||||
|
||||
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:
|
||||
"""Populate settings.xml."""
|
||||
rId = self._nextRelId()
|
||||
@@ -992,15 +1024,22 @@ class ToDocX(Tokenizer):
|
||||
contentType=f"{WORD_BASE}.settings+xml",
|
||||
)
|
||||
|
||||
xFn = xmlSubElem(xRoot, _wTag("footnotePr"))
|
||||
xmlSubElem(xFn, _wTag("numFmt"), attrib={_wTag("val"): "decimal"})
|
||||
xSet = xmlSubElem(xRoot, _wTag("footnotePr"))
|
||||
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:
|
||||
xVars = xmlSubElem(xRoot, _wTag("docVars"))
|
||||
for key, value in self._counts.items():
|
||||
xmlSubElem(xVars, _wTag("docVar"), attrib={
|
||||
_wTag("name"): f"Manuscript{key[:1].upper()}{key[1:]}",
|
||||
_wTag("val"): str(value),
|
||||
W_VAL: str(value),
|
||||
})
|
||||
|
||||
return rId
|
||||
@@ -1011,7 +1050,7 @@ class DocXParagraph:
|
||||
__slots__ = (
|
||||
"_content", "_style", "_textAlign",
|
||||
"_topMargin", "_bottomMargin", "_leftMargin", "_rightMargin",
|
||||
"_indentFirst", "_breakBefore", "_breakAfter",
|
||||
"_indentFirst", "_breakBefore", "_breakAfter", "_footnoteRef",
|
||||
)
|
||||
|
||||
def __init__(self) -> None:
|
||||
@@ -1025,6 +1064,7 @@ class DocXParagraph:
|
||||
self._indentFirst = False
|
||||
self._breakBefore = False
|
||||
self._breakAfter = False
|
||||
self._footnoteRef = False
|
||||
return
|
||||
|
||||
##
|
||||
@@ -1086,6 +1126,11 @@ class DocXParagraph:
|
||||
self._breakAfter = state
|
||||
return
|
||||
|
||||
def setIsFootnote(self, state: bool) -> None:
|
||||
"""Set is footnote flag."""
|
||||
self._footnoteRef = state
|
||||
return
|
||||
|
||||
##
|
||||
# Methods
|
||||
##
|
||||
@@ -1098,7 +1143,7 @@ class DocXParagraph:
|
||||
def toXml(self, body: ET.Element) -> None:
|
||||
"""Called after all content is set."""
|
||||
if style := self._style:
|
||||
par = xmlSubElem(body, _wTag("p"))
|
||||
xP = xmlSubElem(body, _wTag("p"))
|
||||
|
||||
# Values
|
||||
indent = {}
|
||||
@@ -1110,8 +1155,8 @@ class DocXParagraph:
|
||||
indent[_wTag("right")] = str(int(20.0 * self._rightMargin))
|
||||
|
||||
# Paragraph
|
||||
pPr = xmlSubElem(par, _wTag("pPr"))
|
||||
xmlSubElem(pPr, _wTag("pStyle"), attrib={_wTag("val"): style.styleId})
|
||||
pPr = xmlSubElem(xP, _wTag("pPr"))
|
||||
xmlSubElem(pPr, _wTag("pStyle"), attrib={W_VAL: style.styleId})
|
||||
if self._topMargin is not None or self._bottomMargin is not None:
|
||||
xmlSubElem(pPr, _wTag("spacing"), attrib={
|
||||
_wTag("before"): str(int(20.0 * firstFloat(self._topMargin, style.before))),
|
||||
@@ -1121,16 +1166,21 @@ class DocXParagraph:
|
||||
if indent:
|
||||
xmlSubElem(pPr, _wTag("ind"), attrib=indent)
|
||||
if self._textAlign:
|
||||
xmlSubElem(pPr, _wTag("jc"), attrib={_wTag("val"): self._textAlign})
|
||||
xmlSubElem(pPr, _wTag("jc"), attrib={W_VAL: self._textAlign})
|
||||
|
||||
# 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:
|
||||
wr = xmlSubElem(par, _wTag("r"))
|
||||
xmlSubElem(wr, _wTag("br"), attrib={_wTag("type"): "page"})
|
||||
for run in self._content:
|
||||
par.append(run)
|
||||
xR = xmlSubElem(xP, _wTag("r"))
|
||||
xmlSubElem(xR, _wTag("br"), attrib={_wTag("type"): "page"})
|
||||
for xR in self._content:
|
||||
xP.append(xR)
|
||||
if self._breakAfter:
|
||||
wr = xmlSubElem(par, _wTag("r"))
|
||||
xmlSubElem(wr, _wTag("br"), attrib={_wTag("type"): "page"})
|
||||
xR = xmlSubElem(xP, _wTag("r"))
|
||||
xmlSubElem(xR, _wTag("br"), attrib={_wTag("type"): "page"})
|
||||
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user