The topic about auto labeling got me thinking, whether it would be possible to generate hierarchical labels.
For example: if my heading 1. has the label , then all sub headings labels should also start with the prefix heading-1.
The following code is the closest I’ve gotten so far.
Does anyone have an idea on how to improve this?
Preferably I’d like to avoid having to specify the label prefix and read it directly from the previous heading.
#let heading-numbering = "1."
#set heading(numbering: heading-numbering)
#let labeled_heading(body, prefix: none) = {
show heading: this => {
let key = lower(this.body.text.replace(" ", "-"))
if prefix != none {
key = prefix + "-" + key
}
return [
#this
#v(-1em)
#figure(
kind: "heading",
numbering: (..numbers) => numbering(heading-numbering, ..(counter(heading).get())),
supplement: "Section",
)[]
#label(key)
]
}
body
}
#labeled_heading(prefix: none)[
#let heading_level = 1
#heading([Heading 1], level: heading_level)
Heading 1 body
#labeled_heading(prefix: "heading-1")[
#let heading_level = 2
#heading([Heading 1.1], level: heading_level)
Heading 1.1 body
#labeled_heading(prefix: "heading-1-heading-1.1")[
#let heading_level = 3
#heading([Heading 1.1.1], level: heading_level)
Heading 1.1.1 body
#heading([Heading 1.1.2], level: heading_level)
Heading 1.1.2 body
]
]
#labeled_heading(prefix: "heading-1")[
#let heading_level = 2
#heading([Heading 1.2], level: heading_level)
Heading 1.2 body
]
#heading([Heading 2], level: heading_level)
Heading 2 body
]
Reference to heading 1: @heading-1\
Reference to heading 1.1: @heading-1-heading-1.1\
Reference to heading 1.1.1: @heading-1-heading-1.1-heading-1.1.1\
Reference to heading 1.1.2: @heading-1-heading-1.1-heading-1.1.2\
Reference to heading 1.2: @heading-1-heading-1.2\
Reference to heading 2: @heading-2
Expansion to the original solution that allows for maximum hierarchical depth and label key override using the heading supplement field:
#let heading-numbering = "1."
#set heading(numbering: heading-numbering)
#let to-string(content) = {
if content.has("text") and type(content.text) == "string" {
content.text
} else if content.has("children") {
content.children.map(to-string).join("")
} else if content.has("body") {
to-string(content.body)
} else if content == [ ] {
" "
}
}
#show heading: it => context {
// give users the option to disable auto labeling
if it.supplement == [-] {
return it
}
let get_label_key = it => {
if it.supplement == [] {
// if no supplement is provided, use the heading text to generate the label
return lower(to-string(it).trim().replace(" ", "-"))
} else {
// if a supplement is provided, use the supplement to generate the label
return lower(to-string(it.supplement).trim().replace(" ", "-"))
}
}
// recursively construct hierarchical label key
let prev = query(selector(heading).before(here())).rev()
let key = ""
let level = it.level
for heading in prev {
if level == 1 {
break
}
if heading.level >= level {
continue
}
level -= 1
key = get_label_key(heading) + "-" + key
}
key = key + get_label_key(it)
// return the heading with the auto-generated label
return [
#it
// unfortunately there is an issue with attaching programmatically attaching labels to headings (see https://github.com/typst/typst/issues/2926)
// therefore we have to use a fake figure to attach the label to the heading
#v(-1em)
#figure(
kind: "heading",
numbering: (..numbers) => numbering(heading-numbering, ..(counter(heading).get())),
supplement: "Section",
)[]
#label(key)
//#key // print the label key for debugging
]
}
#set heading(numbering: (..numbers) => numbering((..nums) => nums.pos().map(str).join("."), ..numbers), supplement: [])
#heading(supplement: [h1])[Section 1]
@h1
== Section 1.1
@h1-section-1.1
#heading(supplement: [-], level: 2)[Section 1.2]
Auto labeling has been disabled for this heading using the supplement field value `[-]`.
== Section 1.3
@h1-section-1.3
=== Section 1.3.1
@h1-section-1.3-section-1.3.1
=== Section 1.3.2
@h1-section-1.3-section-1.3.2
== Section 1.4
@h1-section-1.4
= Section 2
@section-2