typst_library/introspection/query.rs
1use comemo::Tracked;
2
3use crate::diag::HintedStrResult;
4use crate::engine::Engine;
5use crate::foundations::{func, Array, Context, LocatableSelector, Value};
6
7/// Finds elements in the document.
8///
9/// The `query` functions lets you search your document for elements of a
10/// particular type or with a particular label. To use it, you first need to
11/// ensure that [context] is available.
12///
13
14/// # Finding elements
15/// In the example below, we manually create a table of contents instead of
16/// using the [`outline`] function.
17///
18/// To do this, we first query for all headings in the document at level 1 and
19/// where `outlined` is true. Querying only for headings at level 1 ensures
20/// that, for the purpose of this example, sub-headings are not included in the
21/// table of contents. The `outlined` field is used to exclude the "Table of
22/// Contents" heading itself.
23///
24/// Note that we open a `context` to be able to use the `query` function.
25///
26/// ```example
27/// >>> #set page(
28/// >>> width: 240pt,
29/// >>> height: 180pt,
30/// >>> margin: (top: 20pt, bottom: 35pt)
31/// >>> )
32/// #set page(numbering: "1")
33///
34/// #heading(outlined: false)[
35/// Table of Contents
36/// ]
37/// #context {
38/// let chapters = query(
39/// heading.where(
40/// level: 1,
41/// outlined: true,
42/// )
43/// )
44/// for chapter in chapters {
45/// let loc = chapter.location()
46/// let nr = numbering(
47/// loc.page-numbering(),
48/// ..counter(page).at(loc),
49/// )
50/// [#chapter.body #h(1fr) #nr \ ]
51/// }
52/// }
53///
54/// = Introduction
55/// #lorem(10)
56/// #pagebreak()
57///
58/// == Sub-Heading
59/// #lorem(8)
60///
61/// = Discussion
62/// #lorem(18)
63/// ```
64///
65/// To get the page numbers, we first get the location of the elements returned
66/// by `query` with [`location`]($content.location). We then also retrieve the
67/// [page numbering]($location.page-numbering) and [page
68/// counter]($counter/#page-counter) at that location and apply the numbering to
69/// the counter.
70///
71/// # A word of caution { #caution }
72/// To resolve all your queries, Typst evaluates and layouts parts of the
73/// document multiple times. However, there is no guarantee that your queries
74/// can actually be completely resolved. If you aren't careful a query can
75/// affect itself—leading to a result that never stabilizes.
76///
77/// In the example below, we query for all headings in the document. We then
78/// generate as many headings. In the beginning, there's just one heading,
79/// titled `Real`. Thus, `count` is `1` and one `Fake` heading is generated.
80/// Typst sees that the query's result has changed and processes it again. This
81/// time, `count` is `2` and two `Fake` headings are generated. This goes on and
82/// on. As we can see, the output has a finite amount of headings. This is
83/// because Typst simply gives up after a few attempts.
84///
85/// In general, you should try not to write queries that affect themselves. The
86/// same words of caution also apply to other introspection features like
87/// [counters]($counter) and [state].
88///
89/// ```example
90/// = Real
91/// #context {
92/// let elems = query(heading)
93/// let count = elems.len()
94/// count * [= Fake]
95/// }
96/// ```
97///
98/// # Command line queries
99/// You can also perform queries from the command line with the `typst query`
100/// command. This command executes an arbitrary query on the document and
101/// returns the resulting elements in serialized form. Consider the following
102/// `example.typ` file which contains some invisible [metadata]:
103///
104/// ```typ
105/// #metadata("This is a note") <note>
106/// ```
107///
108/// You can execute a query on it as follows using Typst's CLI:
109/// ```sh
110/// $ typst query example.typ "<note>"
111/// [
112/// {
113/// "func": "metadata",
114/// "value": "This is a note",
115/// "label": "<note>"
116/// }
117/// ]
118/// ```
119///
120/// Frequently, you're interested in only one specific field of the resulting
121/// elements. In the case of the `metadata` element, the `value` field is the
122/// interesting one. You can extract just this field with the `--field`
123/// argument.
124///
125/// ```sh
126/// $ typst query example.typ "<note>" --field value
127/// ["This is a note"]
128/// ```
129///
130/// If you are interested in just a single element, you can use the `--one`
131/// flag to extract just it.
132///
133/// ```sh
134/// $ typst query example.typ "<note>" --field value --one
135/// "This is a note"
136/// ```
137#[func(contextual)]
138pub fn query(
139 engine: &mut Engine,
140 context: Tracked<Context>,
141 /// Can be
142 /// - an element function like a `heading` or `figure`,
143 /// - a `{<label>}`,
144 /// - a more complex selector like `{heading.where(level: 1)}`,
145 /// - or `{selector(heading).before(here())}`.
146 ///
147 /// Only [locatable]($location/#locatable) element functions are supported.
148 target: LocatableSelector,
149) -> HintedStrResult<Array> {
150 context.introspect()?;
151 let vec = engine.introspector.query(&target.0);
152 Ok(vec.into_iter().map(Value::Content).collect())
153}