#!/usr/bin/env python3
# -*- coding: utf-8; mode: python -*-
# pylint: disable=C0103,R0912,R0914,R0915
u"""
dbxml2rst.hooks
~~~~~~~~~~~~~~~
Hooks used by the dbxml2rst library
:copyright: Copyright (C) 2017 Markus Heiser
:license: GPL V3.0, see LICENSE for details.
"""
# ==============================================================================
# imports
# ==============================================================================
from fspath import FSPath
from .nodes import XMLTag, Table
# ==============================================================================
# constants
# ==============================================================================
RESOUCE_FORMAT = "%s_files"
# ==============================================================================
[docs]def hook_copy_file_resource(srcFolder):
# ==============================================================================
srcFolder = FSPath(srcFolder)
def hookFunc(node, rstPrefix, parseData): # pylint: disable=W0613
# run this hook only on the root node
if node.getparent() is not None:
return node
# are there any filerefs in?
filerefList = node.findall(".//*[@fileref]")
if not filerefList:
return node
thisFile = FSPath(parseData.fname)
bookBase = FSPath(parseData.folder)
bookOffset = thisFile.DIRNAME
resFolder = FSPath(RESOUCE_FORMAT % thisFile.BASENAME.SKIPSUFFIX)
dstFolder = bookBase / bookOffset / resFolder
dstFolder.makedirs()
for tag in filerefList:
fileref = FSPath(tag.get("fileref")).BASENAME
src = next(srcFolder.reMatchFind(fileref), None)
if src is None:
raise Exception("fileref: %s could not found in %s" % (fileref, srcFolder))
src.copyfile(dstFolder)
tag.set("fileref", resFolder / fileref )
#SDK.CONSOLE()
return node
return hookFunc
# ==============================================================================
[docs]def hook_chunk_by_tag(*chunkPathes):
# ==============================================================================
u"""
Chunk DocBook XML document into XML fragments.
Requirement for the division is an ID (e.g. ``<chapter id="intro" ...``)
"""
chunkPathes = chunkPathes
def hookFunc(node, rstPrefix, parseData): # pylint: disable=W0613
# run this hook only on the root node
if node.getparent() is not None:
return node
realNode = node
if (node.tag == "dummy"
and len(node) == 1
and node[0].get("chunkNode") is not None):
realNode = node[0]
for tag in chunkPathes:
# pylint: disable=R0204
chunkNodes = realNode.findall("%s" % tag)
for elem in chunkNodes:
if elem.get("chunkNode") is not None:
continue
ID = elem.get("id")
if ID is None:
# generate unique ID by counting preceding siblings on any
# hierarchy level.
_p = elem
ID = []
while _p is not None:
if _p.tag == "dummy":
break
ID.insert(0, len(list(_p.itersiblings(preceding=True))))
_p = _p.getparent()
ID = "-".join(["%03d" % x for x in ID])
ID = "%s-%s" % (parseData.fname.BASENAME.SKIPSUFFIX, ID)
ext_entity = parseData.fname.DIRNAME / ("%s.xml" % ID)
XMLTag.chunkNode(
elem
, parseData.folder
, ext_entity.suffix(parseData.fname.SUFFIX))
if len(chunkNodes):
break
return node
return hookFunc
# ==============================================================================
[docs]def hook_html2db_table(node, rstPrefix, parseData): # pylint: disable=W0613
# ==============================================================================
u"""This hook converts a HTML table to a DocBook table
This is done by simply change ``<tr>`` and ``<td>`` (``<th>``) tags to
``<row>`` and ``<entry>`` tags
"""
# run this hook only on the root node
if node.getparent() is not None:
return node
for elem in node.findall(".//tr"):
newNode = XMLTag.copyNode(elem, "row", moveID=True)
XMLTag.replaceNode(elem, newNode)
for elem in node.findall(".//th") + node.findall(".//td"):
newNode = XMLTag.copyNode(elem, "entry", moveID=True)
XMLTag.replaceNode(elem, newNode)
return node
# ==============================================================================
[docs]def hook_fix_broken_tables(fname_list=None, id_list=None):
# ==============================================================================
u"""This hooks add missing colspecs to the *broken* table.
A complete colspec definition is needed by pandoc, otherwise the build of
the ASCII-art table fails within pandoc."""
fname_list = fname_list or []
id_list = id_list or []
def hookFunc(node, rstPrefix, parseData):
# pylint: disable=W0613, R0912
if node.tag != "table":
return node
fname = parseData.folder.BASENAME / parseData.fname.SKIPSUFFIX
if (node.get("id") not in id_list
and fname not in fname_list):
return node
table = node
tgroup = table.find("tgroup")
cols = int(tgroup.get("cols"))
thead = tgroup.find("thead")
tbody = tgroup.find("tbody")
if thead is not None:
# is there any row in the header with more entries as defined cols?
for row in thead.findall("row"):
entries = row.findall("entry")
if len(entries) > cols:
cols = len(entries)
tgroup.set("cols", str(cols))
for row in tbody.findall("row"):
# is there any row in the body with more entries as defined cols?
entries = row.findall("entry")
if len(entries) > cols:
cols = len(entries)
tgroup.set("cols", str(cols))
if thead is not None:
# add missing entries to header rows
for row in thead.findall("row"):
entries = row.findall("entry")
if len(entries) < cols:
for _x in range(cols - len(entries)):
row.append(node.makeelement("entry"))
# add missing entries to body rows
for row in tbody.findall("row"):
entries = row.findall("entry")
if len(entries) < cols:
for _x in range(cols - len(entries)):
row.append(node.makeelement("entry"))
return node
return hookFunc
# ==============================================================================
[docs]def hook_replaceTag(id2TagMap):
# ==============================================================================
id2TagMap = id2TagMap
def hookFunc(node, rstPrefix, parseData): # pylint: disable=W0613
# run this hook only on the root node
if node.getparent() is not None:
return node
for ID, newTag in id2TagMap.items():
elem = node.find(".//*[@id='%s']" % ID)
if elem is not None:
newNode = XMLTag.copyNode(elem, newTag, moveID=True)
XMLTag.replaceNode(elem, newNode)
return node
return hookFunc
# ==============================================================================
# ==============================================================================
[docs]def hook_flatten_tables(table_id_list="all"):
# ==============================================================================
u"""Hook to convert tables into a double-stage list.
This hook converts a tables (by id or "all") into the ``flat-table``
directive"""
table_id_list = table_id_list
def hookFunc(node, rstPrefix, parseData): # pylint: disable=W0613
# run this hook only on the root node
if node.getparent() is not None:
return node
for table in node.findall(".//table") + node.findall(".//informaltable"):
if (table_id_list == "all"
or table.get("id") in table_id_list):
# seek column widths and col-spans from DocBook is the hell,
# particularly there are <entrytbl> which left up to the outer
# table and broken colspec definitions which has not been
# rejected by the docbook toolchains. These colspecs must be
# *repaired* in any matter.
colspec = {}
colspecByNumber = {}
for colspec_count, c in enumerate(table.findall(".//colspec")):
colnum = c.get("colnum")
if colnum is not None:
try:
colnum = int(colnum)
except Exception: # pylint: disable=W0703
pass
if colnum is None:
colnum = colspec_count
colname = c.get("colname")
if colname is None:
colname = ""
align = c.get("align")
colwidth = c.get("colwidth")
if colwidth is not None:
colwidth = colwidth.replace("*","")
# some colspec definition use the "⋆" entity
colwidth = colwidth.replace(u"⋆", "")
try:
colwidth = int(colwidth)
except Exception: # pylint: disable=W0703
colwidth = None
colspec[colname] = (colnum, align, colwidth )
colspecByNumber[colnum] = (colname, align, colwidth)
spanspec = {}
for item in table.findall(".//spanspec"):
spanname = item.get("spanname")
namest = item.get("namest")
nameend = item.get("nameend")
if spanname is None or namest is None or nameend is None:
continue
start = colspec.get(namest, [None, None,None])[0]
end = colspec.get(nameend, [None, None,None])[0]
if start is None or end is None:
continue
spanspec[spanname] = end - start
# convert table to double-stage list
max_cols = 0
row_count = 0
flatTable = node.makeelement("itemizedlist")
# eat all rows, include the rows from entrytbl
for row in table.findall(".//row"):
row_count += 1
flatRow = node.makeelement("listitem")
row_id = row.get("id")
if row_id:
flatRow.text = ".. _`%s`:" % row_id
else:
flatRow.text = ".. table row"
#flatRow.text = ".. row %s" % row_count
flatTable.append(flatRow)
colList = node.makeelement("itemizedlist")
flatRow.append(colList)
col_count = 0
for entry in row.findall(".//entry"):
col_count += 1
cspan = spanspec.get(entry.get("spanname"), 0)
if cspan == 0:
# the colspan attribut comes from tables which has
# been converted from html (see hook_html2db_table)
cspan = int(entry.get("colspan", 1)) - 1
newCol = node.makeelement("listitem")
# a ID in a cell needs some extras
cell_id = entry.get("id")
if cell_id is not None:
del entry.attrib["id"]
para_id= node.makeelement("para")
para_id.text = (".. _`%s`:\n\n" % cell_id)
newCol.append(para_id)
# move content of entry to a para and insert
# rat-spanning markup
para = XMLTag.copyNode(entry, "para", moveID=False)
if cspan:
para.text = (":cspan:`%s` " % cspan) + (para.text or "")
col_count += cspan
morerows = entry.get("morerows", 0)
if morerows == 0:
# the rowspan attribut comes from tables which has
# been converted from html (see hook_html2db_table)
morerows = int(entry.get("rowspan", 1)) -1
if morerows:
para.text = (":rspan:`%s` " % morerows) + (para.text or "")
newCol.append(para)
colList.append(newCol)
if col_count > max_cols:
max_cols = col_count
# estimate colwidths
widths = []
for i in range(max_cols):
colname, align, colwidth = colspecByNumber.get(i, (None, None, None))
if colwidth is None:
colwidth = 1
widths.append(str(colwidth))
# use widths only when they are meaningful
useWidth = False
for w in widths:
if w != widths[0]:
useWidth = True
break
if len(colspec) > max_cols:
# The colspec definition has more entries than columns
# existing in the table definition. This indicates, that the
# colspec definition is buggy. I don't use width definitions
# from buggy colspecs
useWidth = False
# insert prefix
ctx = Table().getContext(table)
ctx.header_rows = len(table.findall(".//thead/row") or [])
ctx.stub_columns = 1 if table.get("rowheader") == "firstcol" else 0
ctx.widths = " ".join(widths)
preText = "\n" if not ctx.ID else Table.rstAnchor
preText += "\n.. flat-table::%(title)s"
preText += "\n :header-rows: %(header_rows)s"
preText += "\n :stub-columns: %(stub_columns)s"
if useWidth:
preText += "\n :widths: %(widths)s"
preText += "\n "
preText += "\n%(tableStartMark)s"
new = XMLTag.getInjBlockTag()
new.text += preText % ctx
table.addprevious(new)
# insert postfix
rstPostText = "\n%(tableEndMark)s\n"
new = XMLTag.getInjBlockTag()
new.text += rstPostText % ctx
table.addnext(new)
# replace table
XMLTag.replaceNode(table, flatTable)
return node
return hookFunc