Module tf.advanced.render
Render
Rendering is the process of generating HTML for a node, taking into account
display options (tf.advanced.options
) and app settings (tf.advanced.settings
).
It is organized as an unravel step (tf.advanced.unravel
),
that generates a tree of node fragments
followed by an HTML generating step, that generates HTML for a tree in a recursive way.
The unravel step retrieves all relevant settings and options and stores them in the tree in such a way that the essential information for rendering a subtree is readily available at the top of that subtree.
Information shielding
The recursive render step does not have to consult the app
object anymore,
because all information it needs from the app
object is stored in the tree,
and all methods that need to be invoked on the app
object are also accessible
directly from an attribute in the tree.
Expand source code Browse git
"""
# Render
Rendering is the process of generating HTML for a node, taking into account
display options (`tf.advanced.options`) and app settings (`tf.advanced.settings`).
It is organized as an *unravel* step (`tf.advanced.unravel`),
that generates a tree of node fragments
followed by an HTML generating step, that generates HTML for a tree in a recursive way.
The *unravel* step retrieves all relevant settings and options and stores them
in the tree in such a way that the essential information for rendering a subtree
is readily available at the top of that subtree.
## Information shielding
The recursive render step does not have to consult the `app` object anymore,
because all information it needs from the `app` object is stored in the tree,
and all methods that need to be invoked on the `app` object are also accessible
directly from an attribute in the tree.
"""
import re
from .helpers import htmlSafe, NB, dh
from .unravel import _unravel
from ..core.helpers import NBSP, htmlEsc, flattenToSet
def render(app, isPretty, n, _inTuple, _asString, explain, **options):
"""Renders a node, in plain or pretty mode.
We take care that when a node has graphics, and the node is split into several
chunks/fragments, the graphics only occurs on the first fragment.
"""
graphicsFetched = set()
inNb = app.inNb
display = app.display
if not display.check("pretty" if isPretty else "plain", options):
return ""
_browse = app._browse
dContext = display.distill(options)
if isPretty:
tupleFeatures = dContext.tupleFeatures
extraFeatures = dContext.extraFeatures
multiFeatures = dContext.multiFeatures
dContext.set(
"features",
sorted(flattenToSet(extraFeatures[0]) | flattenToSet(tupleFeatures)),
)
dContext.set("featuresIndirect", extraFeatures[1])
if multiFeatures:
dContext.set("featuresAll", tuple(app.api.Fall(warp=False)))
tree = _unravel(app, isPretty, dContext, n, _inTuple=_inTuple, explain=explain)
(chunk, info, subTrees) = tree
settings = info.settings
passage = _getPassage(isPretty, info, n)
html = []
for subTree in subTrees:
_render(isPretty, subTree, True, True, 0, passage, html, graphicsFetched)
rep = "".join(html)
ltr = settings.ltr
elem = "span" if _inTuple else "div"
ubd = " ubd" if _inTuple else ""
result = (
f"""{passage}<{elem} class="{ltr} children">{rep}</{elem}>"""
if isPretty
else f"""<{elem} class="{ltr}{ubd}">{passage}{rep}</{elem}>"""
)
if _browse or _asString:
return result
dh(result, inNb=inNb)
def _render(
isPretty,
tree,
first,
last,
level,
passage,
html,
graphicsFetched,
switched=False,
_asString=False,
):
outer = level == 0
(chunk, info, children) = tree
(n, (b, e)) = chunk
settings = info.settings
props = info.props
boundaryCls = info.boundaryCls
ltr = settings.ltr
isBaseNonSlot = props.isBaseNonSlot
plainCustom = props.plainCustom
if isPretty:
nodePlain = None
if isBaseNonSlot:
nodePlain = _render(
False,
tree,
first,
last,
level,
"",
[],
graphicsFetched,
switched=True,
_asString=True,
)
(label, featurePart) = _prettyTree(tree, outer, first, last, level, nodePlain)
(containerB, containerE) = _prettyPre(
tree,
outer,
label,
featurePart,
boundaryCls,
html,
graphicsFetched,
)
cls = props.cls
childCls = cls["children"]
if children and not isBaseNonSlot:
html.append(f'<div class="{childCls} {ltr}">')
after = props.after
else:
(contribB, contribE) = _plainPre(info, n, boundaryCls, outer, switched)
contrib = _plainTree(
contribB,
tree,
outer,
first,
last,
level,
boundaryCls,
passage,
graphicsFetched,
)
if contrib:
html.append(contrib)
lastCh = len(children) - 1
if not ((isPretty and isBaseNonSlot) or (not isPretty and plainCustom)):
for (i, subTree) in enumerate(children):
thisFirst = first and i == 0
thisLast = last and i == lastCh
_render(
isPretty,
subTree,
thisFirst,
thisLast,
level + 1,
"",
html,
graphicsFetched,
)
if isPretty and after:
html.append(after(subTree[0][0]))
if isPretty:
if children and not isBaseNonSlot:
html.append("</div>")
_prettyPost(label, featurePart, html, containerB, containerE)
else:
_plainPost(contribE, html)
return "".join(html) if outer or _asString else None
# PLAIN LOW-LEVEL
def _plainPre(info, n, boundaryCls, outer, switched):
isPretty = False
options = info.options
plainGaps = options.plainGaps
settings = info.settings
ltr = settings.ltr
props = info.props
hlCls = props.hlCls[isPretty]
hlStyle = props.hlStyle[isPretty]
nodePart = _getNodePart(False, info, n, outer, switched)
boundary = boundaryCls if plainGaps else ""
theHlCls = "" if switched else hlCls
theHlStyle = "" if switched else hlStyle
if boundary in {"r", "l"} or theHlCls or theHlStyle or nodePart or switched:
clses = f"plain {ltr} {boundary} {theHlCls}"
contribB = f'<span class="{clses}" {theHlStyle}>'
contribE = "</span>"
else:
contribB = ""
contribE = ""
if nodePart:
contribB += nodePart
return (contribB, contribE)
def _plainPost(contribE, html):
if contribE:
html.append(contribE)
SPAN_RE = re.compile(r"^(<span\b[^>]*>)(.*)(</span>)$", re.S)
def _plainTree(
contribB,
tree,
outer,
first,
last,
level,
boundaryCls,
passage,
graphicsFetched,
):
(chunk, info, subTrees) = tree
options = info.options
isHtml = options.isHtml
fmt = options.fmt
showGraphics = options.showGraphics
showMath = options.showMath
settings = info.settings
textMethod = settings.textMethod
ltr = settings.ltr
getText = settings.getText
getGraphics = settings.getGraphics
props = info.props
hasGraphics = props.hasGraphics
textCls = props.textCls
nType = props.nType
isSlotOrDescend = props.isSlotOrDescend
descend = props.descend
plainCustom = props.plainCustom
chunk = tree[0]
n = chunk[0]
if showGraphics and hasGraphics and n not in graphicsFetched:
graphics = getGraphics(False, n, nType, outer)
graphicsFetched.add(n)
else:
graphics = ""
contrib = ""
if plainCustom is not None:
contrib = plainCustom(options, chunk, nType, outer)
return contribB + contrib + graphics
if isSlotOrDescend:
text = textMethod(
n,
fmt=fmt,
descend=descend,
outer=outer,
first=first,
last=last,
level=level,
)
if text:
material = htmlSafe(text, isHtml, math=showMath)
cb = f'<span class="{textCls}">'
ce = "</span>"
# a <br> in flex box has no effect
# so we create a "breaking" span by setting the width to 100% and
# the height to 0
# See https://tobiasahlin.com/blog/flexbox-break-to-new-row/
if "<br>" in material:
match = SPAN_RE.match(material)
if match:
(start, content, end) = match.group(1, 2, 3)
else:
(start, content, end) = ("", material, "")
parts = content.split("<br>")
material = '<span class="break"><br></span>'.join(
f"{cb}{start}{part}{end}{ce}" for part in parts
)
contrib = material
else:
contrib = f"{cb}{material}{ce}"
else:
tplFilled = getText(
False,
n,
nType,
outer,
first,
last,
level,
passage if outer else "",
descend,
options=options,
)
if tplFilled:
contrib = f'<span class="{textCls} {ltr}">{tplFilled}</span>'
return contribB + contrib + graphics
# PRETTY LOW-LEVEL
def _prettyPre(tree, outer, label, featurePart, boundaryCls, html, graphicsFetched):
isPretty = True
(chunk, info, subTrees) = tree
n = chunk[0]
options = info.options
showGraphics = options.showGraphics
settings = info.settings
getGraphics = settings.getGraphics
ltr = settings.ltr
props = info.props
hasGraphics = props.hasGraphics
nType = props.nType
cls = props.cls
isBaseNonSlot = props.isBaseNonSlot
hlCls = props.hlCls[isPretty]
hlStyle = props.hlStyle[isPretty]
contCls = cls["container"]
label0 = label.get("", None)
labelB = label.get("b", None)
n = tree[0][0]
containerB = f'<div class="{contCls} {{}} {ltr} {boundaryCls} {hlCls}" {hlStyle}>'
containerE = "</div>"
terminalCls = "trm"
material = featurePart
if labelB is not None:
trm = terminalCls
html.append(f"{containerB.format(trm)}{labelB}{material}{containerE}")
if label0 is not None:
trm = terminalCls if isBaseNonSlot or not subTrees else ""
html.append(f"{containerB.format(trm)}{label0}{material}")
if showGraphics and hasGraphics and n not in graphicsFetched:
html.append(getGraphics(True, n, nType, outer))
graphicsFetched.add(n)
return (containerB, containerE)
def _prettyPost(label, featurePart, html, containerB, containerE):
label0 = label.get("", None)
labelE = label.get("e", None)
if label0 is not None:
html.append(containerE)
if labelE is not None:
html.append(f"{containerB}{labelE} {featurePart}{containerE}")
def _prettyTree(tree, outer, first, last, level, nodePlain):
isPretty = True
(chunk, info, subTrees) = tree
n = chunk[0]
options = info.options
settings = info.settings
upMethod = settings.upMethod
slotsMethod = settings.slotsMethod
webLink = settings.webLink
getText = settings.getText
props = info.props
nType = props.nType
cls = props.cls
hlCls = props.hlCls[isPretty]
hlStyle = props.hlStyle[isPretty]
descend = props.descend
isBaseNonSlot = props.isBaseNonSlot
isLexType = props.isLexType
lexType = props.lexType
textCls = props.textCls
nodePart = _getNodePart(True, info, n, outer, False)
labelHlCls = hlCls
labelHlStyle = hlStyle
if isBaseNonSlot:
heading = nodePlain
else:
heading = getText(
True, n, nType, outer, first, last, level, "", descend, options=options
)
heading = f'<span class="{textCls}">{heading}</span>' if heading else ""
featurePart = _getFeatures(info, n, nType)
if isLexType:
slots = slotsMethod(n)
extremeOccs = (slots[0],) if len(slots) == 1 else (slots[0], slots[-1])
linkOccs = " - ".join(webLink(lo, _asString=True) for lo in extremeOccs)
featurePart += f'<div class="occs">{linkOccs}</div>'
if lexType:
lx = upMethod(n, otype=lexType)
if lx:
heading = webLink(lx[0], heading, _asString=True)
label = {}
for x in ("", "b", "e"):
key = f"label{x}"
if key in cls:
val = cls[key]
terminalCls = "trm" if x or isBaseNonSlot or not subTrees else ""
sep = " " if nodePart and heading else ""
material = f"{nodePart}{sep}{heading}" if nodePart or heading else ""
label[x] = (
f'<div class="{val} {terminalCls} {labelHlCls}" {labelHlStyle}>'
f"{material}</div>"
if material
else ""
)
return (label, featurePart)
def _getPassage(isPretty, info, n):
options = info.options
withPassage = options.withPassage
settings = info.settings
webLink = settings.webLink
if not withPassage:
return ""
ltr = settings.ltr
passage = webLink(n, _asString=True)
wrap = "div" if isPretty else "span"
sep = "" if isPretty else NB * 2
return (
f"""<{wrap} class="tfsechead {ltr}">"""
f"""<span class="ltr">{passage}</span></{wrap}>{sep}"""
)
def _getNodePart(isPretty, info, n, outer, switched):
options = info.options
withNodes = options.withNodes and not switched
withTypes = options.withTypes and not switched
prettyTypes = options.prettyTypes and not switched
lineNumbers = options.lineNumbers and not switched
settings = info.settings
browsing = settings.browsing
lookupMethod = settings.lookupMethod
props = info.props
nType = props.nType
isSlot = props.isSlot
hlCls = props.hlCls[isPretty]
lineNumberFeature = props.lineNumberFeature
allowInfo = isPretty or (outer and not switched) or hlCls != ""
num = ""
if withNodes and allowInfo:
num = n
ntp = ""
if (withTypes or isPretty and prettyTypes) and not isSlot and allowInfo:
ntp = nType
line = ""
if lineNumbers and allowInfo:
if lineNumberFeature:
line = lookupMethod(lineNumberFeature).v(n)
if line:
line = f"@{line}" if line else ""
elemb = 'a href="#"' if browsing else "span"
eleme = "a" if browsing else "span"
sep = ":" if ntp and num else ""
return (
f'<{elemb} class="nd">{ntp}{sep}{num}{line} </{eleme}>'
if ntp or num or line
else ""
)
def _getFeatures(info, n, nType):
"""Feature fetcher.
Helper for `pretty` that wraps the requested features and their values for
*node* in HTML for pretty display.
"""
options = info.options
dFeatures = options.features
dFeaturesIndirect = options.featuresIndirect
multiFeatures = options.multiFeatures
if multiFeatures:
featuresAll = options.featuresAll
queryFeatures = options.queryFeatures
standardFeatures = options.standardFeatures
suppress = options.suppress
noneValues = options.noneValues
showMath = options.showMath
settings = info.settings
upMethod = settings.upMethod
lookupMethod = settings.lookupMethod
props = info.props
(features, indirect) = props.features
(featuresBare, indirectBare) = props.featuresBare
# a feature can be nType:feature
# do a upMethod(n, otype=nType)[0] and take the feature from there
givenFeatureSet = set(features) | set(featuresBare)
xFeatures = tuple(
f for f in dFeatures if not standardFeatures or f not in givenFeatureSet
)
featureList = tuple(featuresBare + features) + xFeatures
if multiFeatures:
featureList += featuresAll
bFeatures = len(featuresBare)
nbFeatures = len(featuresBare) + len(features)
featurePart = ""
if standardFeatures or queryFeatures or multiFeatures:
seen = set()
for (i, name) in enumerate(featureList):
if name not in suppress and name not in seen:
seen.add(name)
fsName = lookupMethod(name)
if fsName is None:
continue
fsNamev = fsName.v
value = None
if (
name in dFeaturesIndirect
or name in indirectBare
or name in indirect
):
refType = (
dFeaturesIndirect[name]
if name in dFeaturesIndirect
else indirectBare[name]
if name in indirectBare
else indirect[name]
)
refNode = upMethod(n, otype=refType)
refNode = refNode[0] if refNode else None
else:
refNode = n
if refNode is not None:
value = fsNamev(refNode)
value = (
None if value in noneValues else htmlEsc(value or "", math=showMath)
)
if value is not None:
value = value.replace("\n", "\\n<br>")
if value.endswith(" "):
value = value[0:-1] + NBSP
isBare = i < bFeatures
isExtra = i >= nbFeatures
if not multiFeatures and (
isExtra
and not queryFeatures
or not isExtra
and (not standardFeatures and name not in dFeatures)
):
continue
nameRep = "" if isBare else f'<span class="f">{name}=</span>'
titleRep = f'title="{name}"' if isBare else ""
xCls = "xft" if isExtra else ""
featurePart += (
f'<span class="{name.lower()} {xCls}" {titleRep}>'
f"{nameRep}{value}</span>"
)
if not featurePart:
return ""
return f"""<div class="features">{featurePart}</div>"""
Functions
def render(app, isPretty, n, _inTuple, _asString, explain, **options)
-
Renders a node, in plain or pretty mode.
We take care that when a node has graphics, and the node is split into several chunks/fragments, the graphics only occurs on the first fragment.
Expand source code Browse git
def render(app, isPretty, n, _inTuple, _asString, explain, **options): """Renders a node, in plain or pretty mode. We take care that when a node has graphics, and the node is split into several chunks/fragments, the graphics only occurs on the first fragment. """ graphicsFetched = set() inNb = app.inNb display = app.display if not display.check("pretty" if isPretty else "plain", options): return "" _browse = app._browse dContext = display.distill(options) if isPretty: tupleFeatures = dContext.tupleFeatures extraFeatures = dContext.extraFeatures multiFeatures = dContext.multiFeatures dContext.set( "features", sorted(flattenToSet(extraFeatures[0]) | flattenToSet(tupleFeatures)), ) dContext.set("featuresIndirect", extraFeatures[1]) if multiFeatures: dContext.set("featuresAll", tuple(app.api.Fall(warp=False))) tree = _unravel(app, isPretty, dContext, n, _inTuple=_inTuple, explain=explain) (chunk, info, subTrees) = tree settings = info.settings passage = _getPassage(isPretty, info, n) html = [] for subTree in subTrees: _render(isPretty, subTree, True, True, 0, passage, html, graphicsFetched) rep = "".join(html) ltr = settings.ltr elem = "span" if _inTuple else "div" ubd = " ubd" if _inTuple else "" result = ( f"""{passage}<{elem} class="{ltr} children">{rep}</{elem}>""" if isPretty else f"""<{elem} class="{ltr}{ubd}">{passage}{rep}</{elem}>""" ) if _browse or _asString: return result dh(result, inNb=inNb)