How to allow reusing of labels in different sections?

I want to create a template that allows to reuse labels between sections, but having difficulty working with queries and not have them cause issues due to not being able to converge.

The goal of my template is to have multiple sections with the same subsections, and be able to use label and references to jump between the subsections.

For example:

= Section 1
== Sub 1<sub1>
== Sub 2<sub2>
Go to @sub1

= Section 2
== Sub 1<sub1>
Go to @sub2
== Sub 2<sub2>

Is there a way to limit reference or query scopes to within a single section?

You can do it like this. In this case - find the top level headings and decide a “bounding box” - only look locally inside the current top level heading. Then place a custom link to that (not using ref)

#let maybelast(x) = {
  if x.len() > 0 { return x.last() }
}
#let maybefirst(x) = {
  if x.len() > 0 { return x.first() }
}

#let localref(lab) = context {
  let mysections = query(heading.where(level: 1).before(here()))
  let nextsections = query(heading.where(level: 1).after(here()))
  let mysection = maybelast(mysections)
  let nextsection = maybefirst(nextsections)
  let sel = selector(lab)
  if mysection != none {
    sel = sel.after(mysection.location())
  }
  if nextsection != none {
    sel = sel.before(nextsection.location())
  }
  let locations = query(sel)
  for loc in locations {
    link(loc.location())[Section #loc.body]
    break
  }
}

= Section 1
== Sub 1<sub1>
== Sub 2<sub2>
Go to #localref(<sub1>)

= Section 2
== Sub 1<sub1>
Go to #localref(<sub2>)
== Sub 2<sub2>

We can do this because labels don’t need to be unique. But labels used with ref seem to need to be unique, so then we can’t do this with ref.

2 Likes

Ahh I see! I was considering going this way at first, but, wanted to make sure there wasn’t an easier way to modify the @ref behavior to do it. And your code looks a lot simpler and clean than what I would have done lol! I was trying to loop over queries and compare heading levels and stuff…

Thanks for your help! I appreciate it :slight_smile:

I don’t understand why for loop is used if each subsection has a unique name, and you want to reference one at a time. There should be either query(sel).first() or an additional assertion before this.

Hey bluss,

I just noticed that if used in a heading, it is blank when viewed in an outline. Is there a way to circumvent that issue? I’d be satisfied with just printing out the lab variable if the full name at location is not an option. Do you have any clue if this can be achieved?

I’m having difficulty figuring out a solution; when I return the localref result directly, I get my full title wherever localref is called, but if I try to look at the fields, I get (:).

I think you mean this situation: you use the localref function inside a heading title? Two things you can detect - that the current section in localref resolution is Contents. Or that you can’t find any “local match” for the label. Both are true in that case.

I don’t know if we can do correct resolution of the “local reference” in the outline, without redefining how the outline is drawn.

code for handling missing reference
#let maybelast(x) = {
  if x.len() > 0 { return x.last() }
}
#let maybefirst(x) = {
  if x.len() > 0 { return x.first() }
}

#let localref(lab) = context {
  let mysection = maybelast(query(heading.where(level: 1).before(here())))
  let nextsection = maybefirst(query(heading.where(level: 1).after(here())))
  let sel = selector(lab)
  if mysection != none {
    sel = sel.after(mysection.location())
  }
  if nextsection != none {
    sel = sel.before(nextsection.location())
  }
  let locations = query(sel)
  if locations.len() > 0 {
    let loc = locations.at(0)
    link(loc.location())[Section #loc.body]
  } else {
    ['#str(lab)' (My current section is called #mysection.body)]
  }
}

#outline()

= Section 1
== Sub 1<sub1>
== Sub 2<sub2>
Go to #localref(<sub1>)

= Section 2
== Sub 1<sub1>
Go to #localref(<sub2>)
== Sub 2<sub2>
== See also #localref(<sub2>)
1 Like

This is how we can hack the outline so that we can resolve localref inside the outline. We (unfortunately) use show rules for outline entries so that we can track “where we are” in the outline hierarchy.

the code
#let maybelast(x) = {
  if x.len() > 0 { return x.last() }
}
#let maybefirst(x) = {
  if x.len() > 0 { return x.first() }
}

#let currently-outlining = state("currentoutline", none)

#show outline.entry.where(level: 1): it => {
  currently-outlining.update(_ => it.element)
  it
}
#show outline: it => {
  it
  currently-outlining.update(_ => none)
}

#let localref(lab) = context {
  let co = currently-outlining.get()
  let mysection
  let here-i-am = here()
  if co != none {
    // we are drawing the outline right now
    mysection = co
    here-i-am = co.location()
  } else {
    mysection = maybelast(query(heading.where(level: 1).before(here-i-am)))
  }
  let nextsection = maybefirst(query(heading.where(level: 1).after(here-i-am, inclusive: false)))
  let sel = selector(lab)
  if mysection != none {
    sel = sel.after(mysection.location())
  }
  if nextsection != none {
    sel = sel.before(nextsection.location())
  }
  let locations = query(sel)
  if locations.len() > 0 {
    let loc = locations.at(0)
    link(loc.location())[Section #loc.body]
  } else {
    // Fallback case if the local reference is not found. Could also be panic()
    ['#str(lab)']
  }
}

#outline()

= Section 1
== Sub 1<sub1>
== Sub 2<sub2>
Go to #localref(<sub1>)

= Section 2
== Sub 1<sub1>
Go to #localref(<sub2>)
== Sub 2<sub2>
== See also #localref(<sub2>)

bild

1 Like

This solution worked great!

Thanks for helping me out, I appreciate it!

1 Like