I am trying to get all text between two headings (including the first heading, but not the second).
= first section
some
text _and_
more
= second section
test
I would like to be able to creating something like:
#show section-selector: it =>[
heading is: “#box(it.heading)” -- and content is “#box(it.content)”
]
Which would be called two times, the first one should print (with the orginal formatting)
heading is: “first section” – and content is “some text and more”
And the second
heading is: “second section” – and content is “test”
So far, the best I’ve done is to:
emulate “match all content” by “match all level 3 headings”
match the header + the content by using a #show on the heading, but of course the “content” (here the level 3 heading) is printed two times (1 in my function, and 1 “normally”
// It should apply on the heading + the content, not just the content
// Alternatively I could also add a second #show rule to hide the content
#show heading.where(level: 1): it => {
let all-text = heading.where(level: 3); // I don’t know how to do this
let content = all-text.after(here())
let next-headings = query(heading.where(level: 1).after(here()))
if next-headings.len() > 1 {
content = content.before(next-headings.at(1).location())
}
[ #box[#it] --> content is : >> #box[#query(content).join()] << ]
}
= first section
=== some
=== text _and_
=== more
= second section
=== test
Compared to the introspective way you suggested, this method requires more markup to write, but it converges immediately (no context required), and is easier to skip certain texts.
@Y.D.X First of all, thanks a lot, and secondly sorry to not have responded earlier.
You did nudge me in the right direction. Here is the solution to my original question:
#let is-heading(it) = {
it.func() == heading
}
/// A show rule that wrap everything between two headings of the same level
///
/// Usage: ```
/// #show: wrapp-section.with(
/// wrapper: it => rect(it),
/// include-heading-in-wrapper: false, // optional
/// depth: 1, // optional
/// )
/// ```
///
/// Note: `#set` rules (like `#set text(lang: "fr")`) must be declared before calling `#show: wrapp-section`
#let wrapp-section(
body,
wrapper: none,
depth: 1,
include-heading-in-wrapper: false,
) = {
// The wrapper function
let fn = none
// The fenced elements to be wrapped
let fenced = ()
for it in body.children {
let x = it.func();
if (is-heading(it) and it.fields().at("depth") < depth) {
if fn != none {
// Complete the last fence
fn(fenced.join())
fn = none
fenced = ()
}
it
} else if is-heading(it) and it.fields().at("depth") == depth {
if fn != none {
// Complete the last fence
fn(fenced.join())
fn = none
fenced = ()
}
// Print the heading itself and start a new fence
if include-heading-in-wrapper {
fn = wrapper
fenced.push(it)
} else {
it
fn = wrapper
}
} else if fn != none {
// Continue the fence
fenced.push(it)
} else {
it // if not in any fence
}
}
// Complete the last fence
if fn != none {
fn(fenced.join())
}
}
And a usage example:
#set text(lang: "en")
#show: wrapp-section.with(
wrapper: it => rect(it),
include-heading-in-wrapper: true,
depth: 2,
)
= heading 1 - a
== heading 2 - a
some
text _and_
more
=== heading 3 - a
stuff
== heading 2 - b
test
A slightly better version, that pass both the heading and the content to a single function:
#let is-heading(it) = {
it.func() == heading
}
/// A show rule that wrap everything between two headings of the same level
///
/// Usage: ```
/// #show: wrapp-section.with(
/// depth: 1, // optional
/// wrapper: (heading: none, section: none) => {
/// rect[
/// heading: #box[#rect[#heading]]
///
/// section: #box[#rect[#section]]
/// ]
/// }
/// )
/// ```
///
/// Note: `#set` rules (like `#set text(lang: "fr")`) must be declared before calling `#show: wrapp-section`
#let wrapp-section(
body,
depth: 1,
wrapper: none,
) = {
// The heading of the current section
let heading = none
// The content of the current section
let section = ()
for it in body.children {
let x = it.func();
if (is-heading(it) and it.fields().at("depth") < depth) {
if heading != none {
// Complete the last section
wrapper(heading: heading, section: section.join())
heading = none
section = ()
}
it
} else if is-heading(it) and it.fields().at("depth") == depth {
if heading != none {
// Complete the last section
wrapper(heading: heading, section: section.join())
heading = none
section = ()
}
heading = it
} else if heading != none {
// Continue the current section
section.push(it)
} else {
it // if not in any section (before the first heading of the appropriate depth)
}
}
// Complete the last section
if heading != none {
wrapper(heading: heading, section: section.join())
}
}
The same example as above:
#set text(lang: "en")
#show: wrapp-section.with(
depth: 2,
wrapper: (heading: none, section: none) => {
heading
rect(section)
}
)
= heading 1 - a
== heading 2 - a
some
text _and_
more
=== heading 3 - a
stuff
== heading 2 - b
test
And a more interesting example that demonstrate how manipulate both the heading and the content of the section level you want to format:
#set text(lang: "en")
#show: wrapp-section.with(
depth: 2,
wrapper: (heading: none, section: none) => {
rect[
heading: #box[#rect[#heading]]
section: #box[#rect[#section]]
]
}
)
= heading 1 - a
== heading 2 - a
some
text _and_
more
=== heading 3 - a
stuff
== heading 2 - b
test
= heading 1 - c
== heading 2 - c
some
text _and_
more
=== heading 3 - c
stuff
== heading 2 - d
test