Thank you for that, that seems to solve it!
I think this idea is working:
// Re-enable recursion for deeper elements
set bibliography(title: metadata(blocked))
and in the function I posted the equivalent is this:
} else {
show markerlabel: set bibliography(title: orig-title)
body
}
I appreciate the simplification but I think the part you removed - smuggling the bibliography.title
setting by setting it only for a specific context - the label - is necessary to make this fully interoperable with other code and other packages. As this was inspired by elembic - they don’t just borrow bibliography.title
, they do it in such a way that it’s not noticeable.
Multiple cheeky abuses of the same bibliography.title
can happen interleaved with each other if we smuggle this correctly, that’s what it looks like.
Simpler explanation (speaking to everyone): we can use the regular style chain to store information this way, in this case in bibliography.title
keyed under a unique label, and the label means that only those that bother looking under that label will find the information.
Edited: Here’s an updated version that incorporates that, and also a newly learned thing:
The show rule produces func(body)
- a styled element or a new element. We can’t store and check that. When we first revisit an element we receive just body
and we can store that because it comes directly from the document.
So we combine the two approaches - a simple sentinel string to detect first recursion, and an array of already seen elements.
- We already solved the test case in the first post
- This also solves the question in the title (“by element identity”). This guards against global reentrancy, let’s say you apply the same style rule twice for some reason, which can happen when multiple packages do the same thing…
Updated Code
/// Take an element and replace it. The field `_positional` will be positional, and it can be a single thing or an array; _map is applied to each element of this array.
#let replace-elt(element, ..args, _positional: "body", _map: x => x) = {
let fields = element.fields()
let body = fields.remove(_positional)
if type(body) != array { body = (body, )}
(element.func())(..fields, ..args, ..body.map(_map))
}
#let append-to-marker(mark, body) = {
let arr = ()
if type(mark) == content and mark.func() == metadata {
arr = mark.value
}
arr.push(body)
metadata(arr)
}
#let contained-in-marker(mark, body) = {
let arr = ()
if type(mark) == content and mark.func() == metadata {
arr = mark.value
}
body in arr
}
/// Apply a style function, like for a show rule, but block recursive application of it
///
/// - markerlabel (label): unique label for this style function
/// - func (function): the style function; signature function(content) -> content
/// - body (content): the style function argument
/// - allow-nesting (bool): allow nesting of the same show rule, after having
/// blocked repeated calls on the same element once. If you say false here,
/// then no recursion at all is allowed (only outermost table of nested
/// tables are affected by a table rule for example). If you say true here,
/// nested tables are affected in this example.
#let recursion-block(markerlabel, func, body, allow-nesting: false) = {
let sentinel = [#str(markerlabel)]
context {
let orig-title = bibliography.title
[#context {
let ctx-title = bibliography.title
set bibliography(title: orig-title)
if ctx-title != sentinel and (not allow-nesting or not contained-in-marker(ctx-title, body)) {
// set simple recursion marker, just the sentinel
show markerlabel: set bibliography(title: sentinel)
func(body)
} else {
// we arrived back at something we have seen before
// store the whole element in the marker this time
// this guards against global reentrancy (if we have multiple instances of the same rule)
// note: we can store `body` because it comes directly from the document.
// storing `func(body)` from above would match nothing we see again - the document element that
// results from `func(body)` is not exactly equal to that.
let marker = append-to-marker(ctx-title, body)
show markerlabel: set bibliography(title: marker) if allow-nesting
body
}
}#markerlabel]
}
}
// I can add text to paragraphs..
#show par: recursion-block.with(<__par_recursion>, it => {
replace-elt(it, _map: body => body + text(red)[ (cheekily modified this paragraph)])
let a = context { [#bibliography.title] }
let b = [#context { [#bibliography.title] }<__par_recursion>]
block[(debugging: inside par, bibliography.title=#a and #repr(<__par_recursion>)bibliography.title=#b )]
})
Paragraph one
Paragraph two
// I can make all table strokes red..
#show table: recursion-block.with(<__table_recursion>, allow-nesting: true, tab => {
replace-elt(tab, stroke: 1pt + red, _positional: "children")
let a = context { [#bibliography.title] }
let b = [#context { [#bibliography.title] }<__table_recursion>]
//block[(debugging: inside table, bibliography.title=#a and #repr(<__table_recursion>)bibliography.title=#b )]
})
== Tables
Note how the nested tables *are now included, thanks sijo!*
#table[A][B]
#table(columns: 2,
table[C][D],
table[E][#table[F][Nested paragraph#parbreak()]],
)
== Grids
// I can make all grid strokes blue..
#let blue-grids = recursion-block.with(<__grid_recursion>, allow-nesting: true, tab => {
replace-elt(tab, stroke: blue + 2pt, _positional: "children")
let a = context { [#bibliography.title] }
let b = [#context { [#bibliography.title] }<__grid_recursion>]
//block[(debugging: inside grid, bibliography.title=#a and #repr(<__grid_recursion>)bibliography.title=#b )]
})
// just for the example, apply the rule twice..
#show grid: blue-grids
#show grid: blue-grids
#grid(inset: 1em, fill: yellow)[A][B][
#grid(inset: 1em)[#grid(inset: 0.5em)[M][N]][#grid(inset: 0.5em)[P][Q]]
]