How to hierarchically label headings automatically?

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

You can query the previous heading to do that!

#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 {
  let f = it => lower(to-string(it).replace(" ", "-"))
  let prev = query(selector(heading).before(here()))
  if prev.len() > 1 {
    prev = prev.at(-2)
  } else {
    prev = [ ]
  }
  let prev-key = if prev != [ ] {f(prev) + "-"} else {""}
  let key = prev-key + f(it)
  return [
    #it
    #v(-1em)
    #figure(
      kind: "heading",
      numbering: (..numbers) => numbering(heading-numbering, ..(counter(heading).get())),
      supplement: "Section",
    )[]
    #label(key)
  ]
}

= Heading 1

@heading-1

== Heading 1.1

@heading-1-heading-1.1

=== Heading 1.1.1

@heading-1.1-heading-1.1.1

=== Heading 1.1.2

@heading-1.1.1-heading-1.1.2

== Heading 1.2

@heading-1.1.2-heading-1.2

= Heading 2

@heading-1.2-heading-2
1 Like

Hey @Simon_Schneider! I’ve changed your question post’s title to better fit our guidelines: How to post in the Questions category

For future posts, please make sure your title is a question you’d ask to a friend about Typst. :wink: