mirror of
https://github.com/typst/typst
synced 2025-05-20 20:15:29 +08:00
Selector rework (#640)
This commit is contained in:
parent
fe2640c552
commit
1198e0cd38
@ -910,3 +910,73 @@ You can access definitions from the module using
|
|||||||
>>>
|
>>>
|
||||||
>>> #(-3)
|
>>> #(-3)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
# Selector
|
||||||
|
A filter for selecting elements within the document.
|
||||||
|
|
||||||
|
You can construct a selector in the following ways:
|
||||||
|
- you can use an element function
|
||||||
|
- you can filter for an element function with
|
||||||
|
[specific fields]($type/function.where)
|
||||||
|
- you can use a [string]($type/string) or [regular expression]($func/regex)
|
||||||
|
- you can use a [`{<label>}`]($func/label)
|
||||||
|
- you can use a [`location`]($func/locate)
|
||||||
|
- call the [`selector`]($func/selector) function to convert any of the above
|
||||||
|
types into a selector value and use the methods below to refine it
|
||||||
|
|
||||||
|
A selector is what you can use to query the document for certain types
|
||||||
|
of elements. It can also be used to apply styling rules to element. You can
|
||||||
|
combine multiple selectors using the methods shown below.
|
||||||
|
|
||||||
|
Selectors can also be passed to several of Typst's built-in functions to
|
||||||
|
configure their behaviour. One such example is the [outline]($func/outline)
|
||||||
|
where it can be use to change which elements are listed within the outline.
|
||||||
|
|
||||||
|
## Example
|
||||||
|
```example
|
||||||
|
#locate(loc => query(
|
||||||
|
heading.where(level: 1)
|
||||||
|
.or(heading.where(level: 2)),
|
||||||
|
loc,
|
||||||
|
))
|
||||||
|
|
||||||
|
= This will be found
|
||||||
|
== So will this
|
||||||
|
=== But this will not.
|
||||||
|
```
|
||||||
|
|
||||||
|
## Methods
|
||||||
|
### or()
|
||||||
|
Allows combining any of a series of selectors. This is used to
|
||||||
|
select multiple components or components with different properties
|
||||||
|
all at once.
|
||||||
|
|
||||||
|
- other: selector (variadic, required)
|
||||||
|
The list of selectors to match on.
|
||||||
|
|
||||||
|
### and()
|
||||||
|
Allows combining all of a series of selectors. This is used to check
|
||||||
|
whether a component meets multiple selection rules simultaneously.
|
||||||
|
|
||||||
|
- other: selector (variadic, required)
|
||||||
|
The list of selectors to match on.
|
||||||
|
|
||||||
|
### before()
|
||||||
|
Returns a modified selector that will only match elements that occur before the
|
||||||
|
first match of the selector argument.
|
||||||
|
|
||||||
|
- end: selector (positional, required)
|
||||||
|
The original selection will end at the first match of `end`.
|
||||||
|
- inclusive: boolean (named)
|
||||||
|
Whether `end` itself should match or not. This is only relevant if both
|
||||||
|
selectors match the same type of element. Defaults to `{true}`.
|
||||||
|
|
||||||
|
### after()
|
||||||
|
Returns a modified selector that will only match elements that occur after the
|
||||||
|
first match of the selector argument.
|
||||||
|
|
||||||
|
- start: selector (positional, required)
|
||||||
|
The original selection will start at the first match of `start`.
|
||||||
|
- inclusive: boolean (named)
|
||||||
|
Whether `start` itself should match or not. This is only relevant if both
|
||||||
|
selectors match the same type of element. Defaults to `{true}`.
|
||||||
|
@ -102,6 +102,7 @@ fn global(math: Module, calc: Module) -> Module {
|
|||||||
global.define("numbering", meta::numbering);
|
global.define("numbering", meta::numbering);
|
||||||
global.define("state", meta::state);
|
global.define("state", meta::state);
|
||||||
global.define("query", meta::query);
|
global.define("query", meta::query);
|
||||||
|
global.define("selector", meta::selector);
|
||||||
|
|
||||||
// Symbols.
|
// Symbols.
|
||||||
global.define("sym", symbols::sym());
|
global.define("sym", symbols::sym());
|
||||||
|
@ -91,7 +91,7 @@ cast_to_value! {
|
|||||||
impl BibliographyElem {
|
impl BibliographyElem {
|
||||||
/// Find the document's bibliography.
|
/// Find the document's bibliography.
|
||||||
pub fn find(introspector: Tracked<Introspector>) -> StrResult<Self> {
|
pub fn find(introspector: Tracked<Introspector>) -> StrResult<Self> {
|
||||||
let mut iter = introspector.query(Self::func().select()).into_iter();
|
let mut iter = introspector.query(&Self::func().select()).into_iter();
|
||||||
let Some(elem) = iter.next() else {
|
let Some(elem) = iter.next() else {
|
||||||
return Err("the document does not contain a bibliography".into());
|
return Err("the document does not contain a bibliography".into());
|
||||||
};
|
};
|
||||||
@ -106,7 +106,7 @@ impl BibliographyElem {
|
|||||||
/// Whether the bibliography contains the given key.
|
/// Whether the bibliography contains the given key.
|
||||||
pub fn has(vt: &Vt, key: &str) -> bool {
|
pub fn has(vt: &Vt, key: &str) -> bool {
|
||||||
vt.introspector
|
vt.introspector
|
||||||
.query(Self::func().select())
|
.query(&Self::func().select())
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.flat_map(|elem| load(vt.world, &elem.to::<Self>().unwrap().path()))
|
.flat_map(|elem| load(vt.world, &elem.to::<Self>().unwrap().path()))
|
||||||
.flatten()
|
.flatten()
|
||||||
@ -395,7 +395,7 @@ impl Works {
|
|||||||
let bibliography = BibliographyElem::find(vt.introspector)?;
|
let bibliography = BibliographyElem::find(vt.introspector)?;
|
||||||
let citations = vt
|
let citations = vt
|
||||||
.introspector
|
.introspector
|
||||||
.query(Selector::Any(eco_vec![
|
.query(&Selector::Or(eco_vec![
|
||||||
RefElem::func().select(),
|
RefElem::func().select(),
|
||||||
CiteElem::func().select(),
|
CiteElem::func().select(),
|
||||||
]))
|
]))
|
||||||
|
@ -335,7 +335,10 @@ impl Counter {
|
|||||||
/// Get the value of the state at the given location.
|
/// Get the value of the state at the given location.
|
||||||
pub fn at(&self, vt: &mut Vt, location: Location) -> SourceResult<CounterState> {
|
pub fn at(&self, vt: &mut Vt, location: Location) -> SourceResult<CounterState> {
|
||||||
let sequence = self.sequence(vt)?;
|
let sequence = self.sequence(vt)?;
|
||||||
let offset = vt.introspector.query_before(self.selector(), location).len();
|
let offset = vt
|
||||||
|
.introspector
|
||||||
|
.query(&Selector::before(self.selector(), location, true))
|
||||||
|
.len();
|
||||||
let (mut state, page) = sequence[offset].clone();
|
let (mut state, page) = sequence[offset].clone();
|
||||||
if self.is_page() {
|
if self.is_page() {
|
||||||
let delta = vt.introspector.page(location).get().saturating_sub(page.get());
|
let delta = vt.introspector.page(location).get().saturating_sub(page.get());
|
||||||
@ -359,7 +362,10 @@ impl Counter {
|
|||||||
/// Get the current and final value of the state combined in one state.
|
/// Get the current and final value of the state combined in one state.
|
||||||
pub fn both(&self, vt: &mut Vt, location: Location) -> SourceResult<CounterState> {
|
pub fn both(&self, vt: &mut Vt, location: Location) -> SourceResult<CounterState> {
|
||||||
let sequence = self.sequence(vt)?;
|
let sequence = self.sequence(vt)?;
|
||||||
let offset = vt.introspector.query_before(self.selector(), location).len();
|
let offset = vt
|
||||||
|
.introspector
|
||||||
|
.query(&Selector::before(self.selector(), location, true))
|
||||||
|
.len();
|
||||||
let (mut at_state, at_page) = sequence[offset].clone();
|
let (mut at_state, at_page) = sequence[offset].clone();
|
||||||
let (mut final_state, final_page) = sequence.last().unwrap().clone();
|
let (mut final_state, final_page) = sequence.last().unwrap().clone();
|
||||||
if self.is_page() {
|
if self.is_page() {
|
||||||
@ -412,11 +418,10 @@ impl Counter {
|
|||||||
let mut page = NonZeroUsize::ONE;
|
let mut page = NonZeroUsize::ONE;
|
||||||
let mut stops = eco_vec![(state.clone(), page)];
|
let mut stops = eco_vec![(state.clone(), page)];
|
||||||
|
|
||||||
for elem in introspector.query(self.selector()) {
|
for elem in introspector.query(&self.selector()) {
|
||||||
if self.is_page() {
|
if self.is_page() {
|
||||||
let location = elem.location().unwrap();
|
|
||||||
let prev = page;
|
let prev = page;
|
||||||
page = introspector.page(location);
|
page = introspector.page(elem.location().unwrap());
|
||||||
|
|
||||||
let delta = page.get() - prev.get();
|
let delta = page.get() - prev.get();
|
||||||
if delta > 0 {
|
if delta > 0 {
|
||||||
@ -446,7 +451,7 @@ impl Counter {
|
|||||||
Selector::Elem(UpdateElem::func(), Some(dict! { "counter" => self.clone() }));
|
Selector::Elem(UpdateElem::func(), Some(dict! { "counter" => self.clone() }));
|
||||||
|
|
||||||
if let CounterKey::Selector(key) = &self.0 {
|
if let CounterKey::Selector(key) = &self.0 {
|
||||||
selector = Selector::Any(eco_vec![selector, key.clone()]);
|
selector = Selector::Or(eco_vec![selector, key.clone()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
selector
|
selector
|
||||||
|
@ -173,14 +173,14 @@ impl Synthesize for FigureElem {
|
|||||||
// Determine the figure's kind.
|
// Determine the figure's kind.
|
||||||
let kind = match self.kind(styles) {
|
let kind = match self.kind(styles) {
|
||||||
Smart::Auto => self
|
Smart::Auto => self
|
||||||
.find_figurable(styles)
|
.find_figurable(vt, styles)
|
||||||
.map(|elem| FigureKind::Elem(elem.func()))
|
.map(|elem| FigureKind::Elem(elem.func()))
|
||||||
.unwrap_or_else(|| FigureKind::Elem(ImageElem::func())),
|
.unwrap_or_else(|| FigureKind::Elem(ImageElem::func())),
|
||||||
Smart::Custom(kind) => kind,
|
Smart::Custom(kind) => kind,
|
||||||
};
|
};
|
||||||
|
|
||||||
let content = match &kind {
|
let content = match &kind {
|
||||||
FigureKind::Elem(func) => self.find_of_elem(*func),
|
FigureKind::Elem(func) => self.find_of_elem(vt, *func),
|
||||||
FigureKind::Name(_) => None,
|
FigureKind::Name(_) => None,
|
||||||
}
|
}
|
||||||
.unwrap_or_else(|| self.body());
|
.unwrap_or_else(|| self.body());
|
||||||
@ -303,9 +303,9 @@ impl Refable for FigureElem {
|
|||||||
impl FigureElem {
|
impl FigureElem {
|
||||||
/// Determines the type of the figure by looking at the content, finding all
|
/// Determines the type of the figure by looking at the content, finding all
|
||||||
/// [`Figurable`] elements and sorting them by priority then returning the highest.
|
/// [`Figurable`] elements and sorting them by priority then returning the highest.
|
||||||
pub fn find_figurable(&self, styles: StyleChain) -> Option<Content> {
|
pub fn find_figurable(&self, vt: &Vt, styles: StyleChain) -> Option<Content> {
|
||||||
self.body()
|
self.body()
|
||||||
.query(Selector::can::<dyn Figurable>())
|
.query(vt.introspector, Selector::can::<dyn Figurable>())
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.max_by_key(|elem| elem.with::<dyn Figurable>().unwrap().priority(styles))
|
.max_by_key(|elem| elem.with::<dyn Figurable>().unwrap().priority(styles))
|
||||||
.cloned()
|
.cloned()
|
||||||
@ -313,9 +313,9 @@ impl FigureElem {
|
|||||||
|
|
||||||
/// Finds the element with the given function in the figure's content.
|
/// Finds the element with the given function in the figure's content.
|
||||||
/// Returns `None` if no element with the given function is found.
|
/// Returns `None` if no element with the given function is found.
|
||||||
pub fn find_of_elem(&self, func: ElemFunc) -> Option<Content> {
|
pub fn find_of_elem(&self, vt: &Vt, func: ElemFunc) -> Option<Content> {
|
||||||
self.body()
|
self.body()
|
||||||
.query(Selector::Elem(func, None))
|
.query(vt.introspector, Selector::Elem(func, None))
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.next()
|
.next()
|
||||||
.cloned()
|
.cloned()
|
||||||
|
@ -154,14 +154,13 @@ impl Show for OutlineElem {
|
|||||||
let lang = TextElem::lang_in(styles);
|
let lang = TextElem::lang_in(styles);
|
||||||
|
|
||||||
let mut ancestors: Vec<&Content> = vec![];
|
let mut ancestors: Vec<&Content> = vec![];
|
||||||
let elems = vt.introspector.query(self.target(styles));
|
let elems = vt.introspector.query(&self.target(styles));
|
||||||
|
|
||||||
for elem in &elems {
|
for elem in &elems {
|
||||||
let Some(refable) = elem.with::<dyn Refable>() else {
|
let Some(refable) = elem.with::<dyn Refable>() else {
|
||||||
bail!(elem.span(), "outlined elements must be referenceable");
|
bail!(elem.span(), "outlined elements must be referenceable");
|
||||||
};
|
};
|
||||||
|
|
||||||
let location = elem.location().expect("missing location");
|
|
||||||
if depth < refable.level() {
|
if depth < refable.level() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -170,6 +169,8 @@ impl Show for OutlineElem {
|
|||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let location = elem.location().unwrap();
|
||||||
|
|
||||||
// Deals with the ancestors of the current element.
|
// Deals with the ancestors of the current element.
|
||||||
// This is only applicable for elements with a hierarchy/level.
|
// This is only applicable for elements with a hierarchy/level.
|
||||||
while ancestors
|
while ancestors
|
||||||
|
@ -38,8 +38,8 @@ use crate::prelude::*;
|
|||||||
/// >>> )
|
/// >>> )
|
||||||
/// #set page(header: locate(loc => {
|
/// #set page(header: locate(loc => {
|
||||||
/// let elems = query(
|
/// let elems = query(
|
||||||
/// heading,
|
/// selector(heading).before(loc),
|
||||||
/// before: loc,
|
/// loc,
|
||||||
/// )
|
/// )
|
||||||
/// let academy = smallcaps[
|
/// let academy = smallcaps[
|
||||||
/// Typst Academy
|
/// Typst Academy
|
||||||
@ -102,8 +102,7 @@ pub fn query(
|
|||||||
/// elements with an explicit label. As a result, you _can_ query for e.g.
|
/// elements with an explicit label. As a result, you _can_ query for e.g.
|
||||||
/// [`strong`]($func/strong) elements, but you will find only those that
|
/// [`strong`]($func/strong) elements, but you will find only those that
|
||||||
/// have an explicit label attached to them. This limitation will be
|
/// have an explicit label attached to them. This limitation will be
|
||||||
/// resolved
|
/// resolved in the future.
|
||||||
/// in the future.
|
|
||||||
target: LocatableSelector,
|
target: LocatableSelector,
|
||||||
|
|
||||||
/// Can be any location. Why is it required then? As noted before, Typst has
|
/// Can be any location. Why is it required then? As noted before, Typst has
|
||||||
@ -115,39 +114,25 @@ pub fn query(
|
|||||||
/// could depend on the query's result.
|
/// could depend on the query's result.
|
||||||
///
|
///
|
||||||
/// Only one of this, `before`, and `after` shall be given.
|
/// Only one of this, `before`, and `after` shall be given.
|
||||||
#[external]
|
|
||||||
#[default]
|
|
||||||
location: Location,
|
location: Location,
|
||||||
|
|
||||||
/// If given, returns only those elements that are before the given
|
|
||||||
/// location. A suitable location can be retrieved from
|
|
||||||
/// [`locate`]($func/locate), but also through the
|
|
||||||
/// [`location()`]($type/content.location) method on content returned by
|
|
||||||
/// another query. Only one of `location`, this, and `after` shall be given.
|
|
||||||
#[named]
|
|
||||||
#[external]
|
|
||||||
#[default]
|
|
||||||
before: Location,
|
|
||||||
|
|
||||||
/// If given, returns only those elements that are after the given location.
|
|
||||||
/// A suitable location can be retrieved from [`locate`]($func/locate), but
|
|
||||||
/// also through the [`location()`]($type/content.location) method on
|
|
||||||
/// content returned by another query. Only one of `location`, `before`, and
|
|
||||||
/// this shall be given.
|
|
||||||
#[named]
|
|
||||||
#[external]
|
|
||||||
#[default]
|
|
||||||
after: Location,
|
|
||||||
) -> Value {
|
) -> Value {
|
||||||
let selector = target.0;
|
let _ = location;
|
||||||
let introspector = vm.vt.introspector;
|
vm.vt.introspector.query(&target.0).into()
|
||||||
let elements = if let Some(location) = args.named("before")? {
|
}
|
||||||
introspector.query_before(selector, location)
|
|
||||||
} else if let Some(location) = args.named("after")? {
|
/// Turns a value into a selector. The following values are accepted:
|
||||||
introspector.query_after(selector, location)
|
/// - An element function like a `heading` or `figure`.
|
||||||
} else {
|
/// - A `{<label>}`.
|
||||||
let _: Location = args.expect("location")?;
|
/// - A more complex selector like `{heading.where(level: 1)}`.
|
||||||
introspector.query(selector)
|
///
|
||||||
};
|
/// Display: Selector
|
||||||
elements.into()
|
/// Category: meta
|
||||||
|
/// Returns: content
|
||||||
|
#[func]
|
||||||
|
pub fn selector(
|
||||||
|
/// Can be an element function like a `heading` or `figure`, a `{<label>}`
|
||||||
|
/// or a more complex selector like `{heading.where(level: 1)}`.
|
||||||
|
target: Selector,
|
||||||
|
) -> Value {
|
||||||
|
target.into()
|
||||||
}
|
}
|
||||||
|
@ -282,7 +282,10 @@ impl State {
|
|||||||
/// Get the value of the state at the given location.
|
/// Get the value of the state at the given location.
|
||||||
pub fn at(self, vt: &mut Vt, location: Location) -> SourceResult<Value> {
|
pub fn at(self, vt: &mut Vt, location: Location) -> SourceResult<Value> {
|
||||||
let sequence = self.sequence(vt)?;
|
let sequence = self.sequence(vt)?;
|
||||||
let offset = vt.introspector.query_before(self.selector(), location).len();
|
let offset = vt
|
||||||
|
.introspector
|
||||||
|
.query(&Selector::before(self.selector(), location, true))
|
||||||
|
.len();
|
||||||
Ok(sequence[offset].clone())
|
Ok(sequence[offset].clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -323,7 +326,7 @@ impl State {
|
|||||||
let mut state = self.init.clone();
|
let mut state = self.init.clone();
|
||||||
let mut stops = eco_vec![state.clone()];
|
let mut stops = eco_vec![state.clone()];
|
||||||
|
|
||||||
for elem in introspector.query(self.selector()) {
|
for elem in introspector.query(&self.selector()) {
|
||||||
let elem = elem.to::<UpdateElem>().unwrap();
|
let elem = elem.to::<UpdateElem>().unwrap();
|
||||||
match elem.update() {
|
match elem.update() {
|
||||||
StateUpdate::Set(value) => state = value,
|
StateUpdate::Set(value) => state = value,
|
||||||
|
@ -4,7 +4,7 @@ use ecow::EcoString;
|
|||||||
|
|
||||||
use super::{Args, Str, Value, Vm};
|
use super::{Args, Str, Value, Vm};
|
||||||
use crate::diag::{At, SourceResult};
|
use crate::diag::{At, SourceResult};
|
||||||
use crate::model::Location;
|
use crate::model::{Location, Selector};
|
||||||
use crate::syntax::Span;
|
use crate::syntax::Span;
|
||||||
|
|
||||||
/// Call a method on a value.
|
/// Call a method on a value.
|
||||||
@ -151,11 +151,29 @@ pub fn call(
|
|||||||
},
|
},
|
||||||
|
|
||||||
Value::Dyn(dynamic) => {
|
Value::Dyn(dynamic) => {
|
||||||
if let Some(&location) = dynamic.downcast::<Location>() {
|
if let Some(location) = dynamic.downcast::<Location>() {
|
||||||
match method {
|
match method {
|
||||||
"page" => vm.vt.introspector.page(location).into(),
|
"page" => vm.vt.introspector.page(*location).into(),
|
||||||
"position" => vm.vt.introspector.position(location).into(),
|
"position" => vm.vt.introspector.position(*location).into(),
|
||||||
"page-numbering" => vm.vt.introspector.page_numbering(location),
|
"page-numbering" => vm.vt.introspector.page_numbering(*location),
|
||||||
|
_ => return missing(),
|
||||||
|
}
|
||||||
|
} else if let Some(selector) = dynamic.downcast::<Selector>() {
|
||||||
|
match method {
|
||||||
|
"or" => selector.clone().or(args.all::<Selector>()?).into(),
|
||||||
|
"and" => selector.clone().and(args.all::<Selector>()?).into(),
|
||||||
|
"before" => {
|
||||||
|
let location = args.expect::<Selector>("selector")?;
|
||||||
|
let inclusive =
|
||||||
|
args.named_or_find::<bool>("inclusive")?.unwrap_or(true);
|
||||||
|
selector.clone().before(location, inclusive).into()
|
||||||
|
}
|
||||||
|
"after" => {
|
||||||
|
let location = args.expect::<Selector>("selector")?;
|
||||||
|
let inclusive =
|
||||||
|
args.named_or_find::<bool>("inclusive")?.unwrap_or(true);
|
||||||
|
selector.clone().after(location, inclusive).into()
|
||||||
|
}
|
||||||
_ => return missing(),
|
_ => return missing(),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -312,6 +330,7 @@ pub fn methods_on(type_name: &str) -> &[(&'static str, bool)] {
|
|||||||
"function" => &[("where", true), ("with", true)],
|
"function" => &[("where", true), ("with", true)],
|
||||||
"arguments" => &[("named", false), ("pos", false)],
|
"arguments" => &[("named", false), ("pos", false)],
|
||||||
"location" => &[("page", false), ("position", false), ("page-numbering", false)],
|
"location" => &[("page", false), ("position", false), ("page-numbering", false)],
|
||||||
|
"selector" => &[("or", true), ("and", true), ("before", true), ("after", true)],
|
||||||
"counter" => &[
|
"counter" => &[
|
||||||
("display", true),
|
("display", true),
|
||||||
("at", true),
|
("at", true),
|
||||||
|
@ -3,11 +3,12 @@ use std::fmt::{self, Debug, Formatter, Write};
|
|||||||
use std::iter::Sum;
|
use std::iter::Sum;
|
||||||
use std::ops::{Add, AddAssign};
|
use std::ops::{Add, AddAssign};
|
||||||
|
|
||||||
|
use comemo::Tracked;
|
||||||
use ecow::{eco_format, EcoString, EcoVec};
|
use ecow::{eco_format, EcoString, EcoVec};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
element, Behave, Behaviour, ElemFunc, Element, Fold, Guard, Label, Locatable,
|
element, Behave, Behaviour, ElemFunc, Element, Fold, Guard, Introspector, Label,
|
||||||
Location, Recipe, Selector, Style, Styles, Synthesize,
|
Locatable, Location, Recipe, Selector, Style, Styles, Synthesize,
|
||||||
};
|
};
|
||||||
use crate::diag::{SourceResult, StrResult};
|
use crate::diag::{SourceResult, StrResult};
|
||||||
use crate::doc::Meta;
|
use crate::doc::Meta;
|
||||||
@ -358,38 +359,50 @@ impl Content {
|
|||||||
///
|
///
|
||||||
/// # Show rules
|
/// # Show rules
|
||||||
/// Elements produced in `show` rules will not be included in the results.
|
/// Elements produced in `show` rules will not be included in the results.
|
||||||
pub fn query(&self, selector: Selector) -> Vec<&Content> {
|
pub fn query(
|
||||||
|
&self,
|
||||||
|
introspector: Tracked<Introspector>,
|
||||||
|
selector: Selector,
|
||||||
|
) -> Vec<&Content> {
|
||||||
let mut results = Vec::new();
|
let mut results = Vec::new();
|
||||||
self.query_into(&selector, &mut results);
|
self.query_into(introspector, &selector, &mut results);
|
||||||
results
|
results
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Queries the content tree for all elements that match the given selector
|
/// Queries the content tree for all elements that match the given selector
|
||||||
/// and stores the results inside of the `results` vec.
|
/// and stores the results inside of the `results` vec.
|
||||||
fn query_into<'a>(&'a self, selector: &Selector, results: &mut Vec<&'a Content>) {
|
fn query_into<'a>(
|
||||||
|
&'a self,
|
||||||
|
introspector: Tracked<Introspector>,
|
||||||
|
selector: &Selector,
|
||||||
|
results: &mut Vec<&'a Content>,
|
||||||
|
) {
|
||||||
if selector.matches(self) {
|
if selector.matches(self) {
|
||||||
results.push(self);
|
results.push(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
for attr in &self.attrs {
|
for attr in &self.attrs {
|
||||||
match attr {
|
match attr {
|
||||||
Attr::Child(child) => child.query_into(selector, results),
|
Attr::Child(child) => child.query_into(introspector, selector, results),
|
||||||
Attr::Value(value) => walk_value(&value, selector, results),
|
Attr::Value(value) => walk_value(introspector, &value, selector, results),
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Walks a given value to find any content that matches the selector.
|
/// Walks a given value to find any content that matches the selector.
|
||||||
fn walk_value<'a>(
|
fn walk_value<'a>(
|
||||||
|
introspector: Tracked<Introspector>,
|
||||||
value: &'a Value,
|
value: &'a Value,
|
||||||
selector: &Selector,
|
selector: &Selector,
|
||||||
results: &mut Vec<&'a Content>,
|
results: &mut Vec<&'a Content>,
|
||||||
) {
|
) {
|
||||||
match value {
|
match value {
|
||||||
Value::Content(content) => content.query_into(selector, results),
|
Value::Content(content) => {
|
||||||
|
content.query_into(introspector, selector, results)
|
||||||
|
}
|
||||||
Value::Array(array) => {
|
Value::Array(array) => {
|
||||||
for value in array {
|
for value in array {
|
||||||
walk_value(value, selector, results);
|
walk_value(introspector, value, selector, results);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
|
@ -84,9 +84,11 @@ impl StabilityProvider {
|
|||||||
|
|
||||||
/// Can be queried for elements and their positions.
|
/// Can be queried for elements and their positions.
|
||||||
pub struct Introspector {
|
pub struct Introspector {
|
||||||
|
/// The number of pages in the document.
|
||||||
pages: usize,
|
pages: usize,
|
||||||
elems: IndexMap<Option<Location>, (Content, Position)>,
|
/// All introspectable elements.
|
||||||
// Indexed by page number.
|
elems: IndexMap<Location, (Content, Position)>,
|
||||||
|
/// The page numberings, indexed by page number minus 1.
|
||||||
page_numberings: Vec<Value>,
|
page_numberings: Vec<Value>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,8 +108,8 @@ impl Introspector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Iterate over all elements.
|
/// Iterate over all elements.
|
||||||
pub fn all(&self) -> impl Iterator<Item = &Content> {
|
pub fn all(&self) -> impl Iterator<Item = Content> + '_ {
|
||||||
self.elems.values().map(|(elem, _)| elem)
|
self.elems.values().map(|(c, _)| c).cloned()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extract metadata from a frame.
|
/// Extract metadata from a frame.
|
||||||
@ -121,11 +123,11 @@ impl Introspector {
|
|||||||
self.extract(&group.frame, page, ts);
|
self.extract(&group.frame, page, ts);
|
||||||
}
|
}
|
||||||
FrameItem::Meta(Meta::Elem(content), _)
|
FrameItem::Meta(Meta::Elem(content), _)
|
||||||
if !self.elems.contains_key(&content.location()) =>
|
if !self.elems.contains_key(&content.location().unwrap()) =>
|
||||||
{
|
{
|
||||||
let pos = pos.transform(ts);
|
let pos = pos.transform(ts);
|
||||||
let ret = self.elems.insert(
|
let ret = self.elems.insert(
|
||||||
content.location(),
|
content.location().unwrap(),
|
||||||
(content.clone(), Position { page, point: pos }),
|
(content.clone(), Position { page, point: pos }),
|
||||||
);
|
);
|
||||||
assert!(ret.is_none(), "duplicate locations");
|
assert!(ret.is_none(), "duplicate locations");
|
||||||
@ -146,32 +148,33 @@ impl Introspector {
|
|||||||
self.pages > 0
|
self.pages > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get an element from the position cache.
|
||||||
|
pub fn location(&self, location: &Location) -> Option<Content> {
|
||||||
|
self.elems.get(location).map(|(c, _)| c).cloned()
|
||||||
|
}
|
||||||
|
|
||||||
/// Query for all matching elements.
|
/// Query for all matching elements.
|
||||||
pub fn query(&self, selector: Selector) -> Vec<Content> {
|
pub fn query<'a>(&'a self, selector: &'a Selector) -> Vec<Content> {
|
||||||
self.all().filter(|elem| selector.matches(elem)).cloned().collect()
|
match selector {
|
||||||
}
|
Selector::Location(location) => self
|
||||||
|
.elems
|
||||||
/// Query for all matching element up to the given location.
|
.get(location)
|
||||||
pub fn query_before(&self, selector: Selector, location: Location) -> Vec<Content> {
|
.map(|(content, _)| content)
|
||||||
let mut matches = vec![];
|
.cloned()
|
||||||
for elem in self.all() {
|
.into_iter()
|
||||||
if selector.matches(elem) {
|
.collect(),
|
||||||
matches.push(elem.clone());
|
_ => selector.match_iter(self).collect(),
|
||||||
}
|
|
||||||
if elem.location() == Some(location) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
matches
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Query for all matching elements starting from the given location.
|
/// Query for the first matching element.
|
||||||
pub fn query_after(&self, selector: Selector, location: Location) -> Vec<Content> {
|
pub fn query_first<'a>(&'a self, selector: &'a Selector) -> Option<Content> {
|
||||||
self.all()
|
match selector {
|
||||||
.skip_while(|elem| elem.location() != Some(location))
|
Selector::Location(location) => {
|
||||||
.filter(|elem| selector.matches(elem))
|
self.elems.get(location).map(|(content, _)| content).cloned()
|
||||||
.cloned()
|
}
|
||||||
.collect()
|
_ => selector.match_iter(self).next(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Query for a unique element with the label.
|
/// Query for a unique element with the label.
|
||||||
@ -205,8 +208,24 @@ impl Introspector {
|
|||||||
/// Find the position for the given location.
|
/// Find the position for the given location.
|
||||||
pub fn position(&self, location: Location) -> Position {
|
pub fn position(&self, location: Location) -> Position {
|
||||||
self.elems
|
self.elems
|
||||||
.get(&Some(location))
|
.get(&location)
|
||||||
.map(|(_, loc)| *loc)
|
.map(|(_, loc)| *loc)
|
||||||
.unwrap_or(Position { page: NonZeroUsize::ONE, point: Point::zero() })
|
.unwrap_or(Position { page: NonZeroUsize::ONE, point: Point::zero() })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Checks whether `a` is before `b` in the document.
|
||||||
|
pub fn is_before(&self, a: Location, b: Location, inclusive: bool) -> bool {
|
||||||
|
let a = self.elems.get_index_of(&a).unwrap();
|
||||||
|
let b = self.elems.get_index_of(&b).unwrap();
|
||||||
|
if inclusive {
|
||||||
|
a <= b
|
||||||
|
} else {
|
||||||
|
a < b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks whether `a` is after `b` in the document.
|
||||||
|
pub fn is_after(&self, a: Location, b: Location, inclusive: bool) -> bool {
|
||||||
|
!self.is_before(a, b, !inclusive)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -152,7 +152,14 @@ fn try_apply(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Not supported here.
|
// Not supported here.
|
||||||
Some(Selector::Any(_) | Selector::All(_) | Selector::Can(_)) => Ok(None),
|
Some(
|
||||||
|
Selector::Or(_)
|
||||||
|
| Selector::And(_)
|
||||||
|
| Selector::Location(_)
|
||||||
|
| Selector::Can(_)
|
||||||
|
| Selector::Before { .. }
|
||||||
|
| Selector::After { .. },
|
||||||
|
) => Ok(None),
|
||||||
|
|
||||||
None => Ok(None),
|
None => Ok(None),
|
||||||
}
|
}
|
||||||
|
@ -2,10 +2,11 @@ use std::any::{Any, TypeId};
|
|||||||
use std::fmt::{self, Debug, Formatter, Write};
|
use std::fmt::{self, Debug, Formatter, Write};
|
||||||
use std::iter;
|
use std::iter;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use ecow::{eco_format, eco_vec, EcoString, EcoVec};
|
use ecow::{eco_format, eco_vec, EcoString, EcoVec};
|
||||||
|
|
||||||
use super::{Content, ElemFunc, Element, Label, Vt};
|
use super::{Content, ElemFunc, Element, Introspector, Label, Location, Vt};
|
||||||
use crate::diag::{SourceResult, StrResult, Trace, Tracepoint};
|
use crate::diag::{SourceResult, StrResult, Trace, Tracepoint};
|
||||||
use crate::eval::{cast_from_value, Args, Cast, CastInfo, Dict, Func, Regex, Value, Vm};
|
use crate::eval::{cast_from_value, Args, Cast, CastInfo, Dict, Func, Regex, Value, Vm};
|
||||||
use crate::model::Locatable;
|
use crate::model::Locatable;
|
||||||
@ -258,6 +259,8 @@ pub enum Selector {
|
|||||||
/// If there is a dictionary, only elements with the fields from the
|
/// If there is a dictionary, only elements with the fields from the
|
||||||
/// dictionary match.
|
/// dictionary match.
|
||||||
Elem(ElemFunc, Option<Dict>),
|
Elem(ElemFunc, Option<Dict>),
|
||||||
|
/// Matches the element at the specified location.
|
||||||
|
Location(Location),
|
||||||
/// Matches elements with a specific label.
|
/// Matches elements with a specific label.
|
||||||
Label(Label),
|
Label(Label),
|
||||||
/// Matches text elements through a regular expression.
|
/// Matches text elements through a regular expression.
|
||||||
@ -265,9 +268,13 @@ pub enum Selector {
|
|||||||
/// Matches elements with a specific capability.
|
/// Matches elements with a specific capability.
|
||||||
Can(TypeId),
|
Can(TypeId),
|
||||||
/// Matches if any of the subselectors match.
|
/// Matches if any of the subselectors match.
|
||||||
Any(EcoVec<Self>),
|
Or(EcoVec<Self>),
|
||||||
/// Matches if all of the subselectors match.
|
/// Matches if all of the subselectors match.
|
||||||
All(EcoVec<Self>),
|
And(EcoVec<Self>),
|
||||||
|
/// Matches all matches of `selector` before `end`.
|
||||||
|
Before { selector: Arc<Self>, end: Arc<Self>, inclusive: bool },
|
||||||
|
/// Matches all matches of `selector` after `start`.
|
||||||
|
After { selector: Arc<Self>, start: Arc<Self>, inclusive: bool },
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Selector {
|
impl Selector {
|
||||||
@ -281,6 +288,107 @@ impl Selector {
|
|||||||
Self::Can(TypeId::of::<T>())
|
Self::Can(TypeId::of::<T>())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Transforms this selector and an iterator of other selectors into a
|
||||||
|
/// [`Selector::Or`] selector.
|
||||||
|
pub fn and(self, others: impl IntoIterator<Item = Self>) -> Self {
|
||||||
|
Self::And(others.into_iter().chain(Some(self)).collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Transforms this selector and an iterator of other selectors into a
|
||||||
|
/// [`Selector::And`] selector.
|
||||||
|
pub fn or(self, others: impl IntoIterator<Item = Self>) -> Self {
|
||||||
|
Self::Or(others.into_iter().chain(Some(self)).collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Transforms this selector into a [`Selector::Before`] selector.
|
||||||
|
pub fn before(self, location: impl Into<Self>, inclusive: bool) -> Self {
|
||||||
|
Self::Before {
|
||||||
|
selector: Arc::new(self),
|
||||||
|
end: Arc::new(location.into()),
|
||||||
|
inclusive,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Transforms this selector into a [`Selector::After`] selector.
|
||||||
|
pub fn after(self, location: impl Into<Self>, inclusive: bool) -> Self {
|
||||||
|
Self::After {
|
||||||
|
selector: Arc::new(self),
|
||||||
|
start: Arc::new(location.into()),
|
||||||
|
inclusive,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Matches the selector for an introspector.
|
||||||
|
pub fn match_iter<'a>(
|
||||||
|
&'a self,
|
||||||
|
introspector: &'a Introspector,
|
||||||
|
) -> Box<dyn Iterator<Item = Content> + 'a> {
|
||||||
|
self.match_iter_inner(introspector, introspector.all())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Match the selector against the given list of elements. Returns an
|
||||||
|
/// iterator over the matching elements.
|
||||||
|
fn match_iter_inner<'a>(
|
||||||
|
&'a self,
|
||||||
|
introspector: &'a Introspector,
|
||||||
|
parent: impl Iterator<Item = Content> + 'a,
|
||||||
|
) -> Box<dyn Iterator<Item = Content> + 'a> {
|
||||||
|
match self {
|
||||||
|
Self::Location(location) => {
|
||||||
|
Box::new(introspector.location(location).into_iter())
|
||||||
|
}
|
||||||
|
Self::Or(selectors) => Box::new(parent.filter(|element| {
|
||||||
|
selectors.iter().any(|selector| {
|
||||||
|
selector
|
||||||
|
.match_iter_inner(introspector, std::iter::once(element.clone()))
|
||||||
|
.next()
|
||||||
|
.is_some()
|
||||||
|
})
|
||||||
|
})),
|
||||||
|
Self::And(selectors) => Box::new(parent.filter(|element| {
|
||||||
|
selectors.iter().all(|selector| {
|
||||||
|
selector
|
||||||
|
.match_iter_inner(introspector, std::iter::once(element.clone()))
|
||||||
|
.next()
|
||||||
|
.is_some()
|
||||||
|
})
|
||||||
|
})),
|
||||||
|
Self::Before { selector, end: location, inclusive } => {
|
||||||
|
if let Some(content) = introspector.query_first(location) {
|
||||||
|
let loc = content.location().unwrap();
|
||||||
|
Box::new(selector.match_iter_inner(introspector, parent).filter(
|
||||||
|
move |elem| {
|
||||||
|
introspector.is_before(
|
||||||
|
elem.location().unwrap(),
|
||||||
|
loc,
|
||||||
|
*inclusive,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
Box::new(selector.match_iter_inner(introspector, parent))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Self::After { selector, start: location, inclusive } => {
|
||||||
|
if let Some(content) = introspector.query_first(location) {
|
||||||
|
let loc = content.location().unwrap();
|
||||||
|
Box::new(selector.match_iter_inner(introspector, parent).filter(
|
||||||
|
move |elem| {
|
||||||
|
introspector.is_after(
|
||||||
|
elem.location().unwrap(),
|
||||||
|
loc,
|
||||||
|
*inclusive,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
Box::new(std::iter::empty())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
other => Box::new(parent.filter(move |content| other.matches(content))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Whether the selector matches for the target.
|
/// Whether the selector matches for the target.
|
||||||
pub fn matches(&self, target: &Content) -> bool {
|
pub fn matches(&self, target: &Content) -> bool {
|
||||||
match self {
|
match self {
|
||||||
@ -297,12 +405,22 @@ impl Selector {
|
|||||||
&& item!(text_str)(target).map_or(false, |text| regex.is_match(&text))
|
&& item!(text_str)(target).map_or(false, |text| regex.is_match(&text))
|
||||||
}
|
}
|
||||||
Self::Can(cap) => target.can_type_id(*cap),
|
Self::Can(cap) => target.can_type_id(*cap),
|
||||||
Self::Any(selectors) => selectors.iter().any(|sel| sel.matches(target)),
|
Self::Or(selectors) => selectors.iter().any(move |sel| sel.matches(target)),
|
||||||
Self::All(selectors) => selectors.iter().all(|sel| sel.matches(target)),
|
Self::And(selectors) => selectors.iter().all(move |sel| sel.matches(target)),
|
||||||
|
Self::Location(location) => target.location() == Some(*location),
|
||||||
|
Self::Before { .. } | Self::After { .. } => {
|
||||||
|
panic!("Cannot match a `Selector::Before` or `Selector::After` selector")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<Location> for Selector {
|
||||||
|
fn from(value: Location) -> Self {
|
||||||
|
Self::Location(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Debug for Selector {
|
impl Debug for Selector {
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
@ -317,12 +435,29 @@ impl Debug for Selector {
|
|||||||
Self::Label(label) => label.fmt(f),
|
Self::Label(label) => label.fmt(f),
|
||||||
Self::Regex(regex) => regex.fmt(f),
|
Self::Regex(regex) => regex.fmt(f),
|
||||||
Self::Can(cap) => cap.fmt(f),
|
Self::Can(cap) => cap.fmt(f),
|
||||||
Self::Any(selectors) | Self::All(selectors) => {
|
Self::Or(selectors) | Self::And(selectors) => {
|
||||||
f.write_str(if matches!(self, Self::Any(_)) { "any" } else { "all" })?;
|
f.write_str(if matches!(self, Self::Or(_)) { "or" } else { "and" })?;
|
||||||
let pieces: Vec<_> =
|
let pieces: Vec<_> =
|
||||||
selectors.iter().map(|sel| eco_format!("{sel:?}")).collect();
|
selectors.iter().map(|sel| eco_format!("{sel:?}")).collect();
|
||||||
f.write_str(&pretty_array_like(&pieces, false))
|
f.write_str(&pretty_array_like(&pieces, false))
|
||||||
}
|
}
|
||||||
|
Self::Location(loc) => loc.fmt(f),
|
||||||
|
Self::Before { selector, end: split, inclusive }
|
||||||
|
| Self::After { selector, start: split, inclusive } => {
|
||||||
|
selector.fmt(f)?;
|
||||||
|
|
||||||
|
if matches!(self, Self::Before { .. }) {
|
||||||
|
f.write_str(".before(")?;
|
||||||
|
} else {
|
||||||
|
f.write_str(".after(")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
split.fmt(f)?;
|
||||||
|
if !*inclusive {
|
||||||
|
f.write_str(", inclusive: false")?;
|
||||||
|
}
|
||||||
|
f.write_char(')')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -336,9 +471,10 @@ cast_from_value! {
|
|||||||
label: Label => Self::Label(label),
|
label: Label => Self::Label(label),
|
||||||
text: EcoString => Self::text(&text),
|
text: EcoString => Self::text(&text),
|
||||||
regex: Regex => Self::Regex(regex),
|
regex: Regex => Self::Regex(regex),
|
||||||
|
location: Location => Self::Location(location),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A selector that can be used with `query`. Hopefully, this is made obsolote
|
/// A selector that can be used with `query`. Hopefully, this is made obsolete
|
||||||
/// by a more powerful query mechanism in the future.
|
/// by a more powerful query mechanism in the future.
|
||||||
#[derive(Clone, PartialEq, Hash)]
|
#[derive(Clone, PartialEq, Hash)]
|
||||||
pub struct LocatableSelector(pub Selector);
|
pub struct LocatableSelector(pub Selector);
|
||||||
@ -352,16 +488,26 @@ impl Cast for LocatableSelector {
|
|||||||
fn cast(value: Value) -> StrResult<Self> {
|
fn cast(value: Value) -> StrResult<Self> {
|
||||||
fn validate(selector: &Selector) -> StrResult<()> {
|
fn validate(selector: &Selector) -> StrResult<()> {
|
||||||
match &selector {
|
match &selector {
|
||||||
Selector::Elem(elem, _) if !elem.can::<dyn Locatable>() => {
|
Selector::Elem(elem, _) => {
|
||||||
Err(eco_format!("{} is not locatable", elem.name()))?
|
if !elem.can::<dyn Locatable>() {
|
||||||
|
Err(eco_format!("{} is not locatable", elem.name()))?
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Selector::Location(_) => {}
|
||||||
|
Selector::Label(_) => {}
|
||||||
Selector::Regex(_) => Err("text is not locatable")?,
|
Selector::Regex(_) => Err("text is not locatable")?,
|
||||||
Selector::Any(list) | Selector::All(list) => {
|
Selector::Can(_) => Err("capability is not locatable")?,
|
||||||
|
Selector::Or(list) | Selector::And(list) => {
|
||||||
for selector in list {
|
for selector in list {
|
||||||
validate(selector)?;
|
validate(selector)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
Selector::Before { selector, end: split, .. }
|
||||||
|
| Selector::After { selector, start: split, .. } => {
|
||||||
|
for selector in [selector, split] {
|
||||||
|
validate(selector)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
BIN
tests/ref/meta/query-before-after.png
Normal file
BIN
tests/ref/meta/query-before-after.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 176 KiB |
Binary file not shown.
Before Width: | Height: | Size: 435 KiB After Width: | Height: | Size: 542 KiB |
@ -96,7 +96,7 @@ Hey
|
|||||||
= Heading
|
= Heading
|
||||||
|
|
||||||
---
|
---
|
||||||
// Error: 7-10 expected function, label, string, regular expression, or selector, found color
|
// Error: 7-10 expected function, label, string, regular expression, location, or selector, found color
|
||||||
#show red: []
|
#show red: []
|
||||||
|
|
||||||
---
|
---
|
||||||
|
69
tests/typ/meta/query-before-after.typ
Normal file
69
tests/typ/meta/query-before-after.typ
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
|
||||||
|
---
|
||||||
|
#set page(
|
||||||
|
paper: "a7",
|
||||||
|
numbering: "1 / 1",
|
||||||
|
margin: (bottom: 1cm, rest: 0.5cm),
|
||||||
|
)
|
||||||
|
|
||||||
|
#show heading.where(level: 1, outlined: true): it => [
|
||||||
|
#it
|
||||||
|
|
||||||
|
#set text(size: 12pt, weight: "regular")
|
||||||
|
#outline(
|
||||||
|
title: "Chapter outline",
|
||||||
|
indent: true,
|
||||||
|
target: heading
|
||||||
|
.where(level: 1)
|
||||||
|
.or(heading.where(level: 2))
|
||||||
|
.after(it.location(), inclusive: true)
|
||||||
|
.before(
|
||||||
|
heading
|
||||||
|
.where(level: 1, outlined: true)
|
||||||
|
.after(it.location(), inclusive: false),
|
||||||
|
inclusive: false,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
#set heading(outlined: true, numbering: "1.")
|
||||||
|
|
||||||
|
= Section 1
|
||||||
|
== Subsection 1
|
||||||
|
== Subsection 2
|
||||||
|
=== Subsubsection 1
|
||||||
|
=== Subsubsection 2
|
||||||
|
== Subsection 3
|
||||||
|
= Section 2
|
||||||
|
== Subsection 1
|
||||||
|
== Subsection 2
|
||||||
|
|
||||||
|
= Section 3
|
||||||
|
== Subsection 1
|
||||||
|
== Subsection 2
|
||||||
|
=== Subsubsection 1
|
||||||
|
=== Subsubsection 2
|
||||||
|
=== Subsubsection 3
|
||||||
|
== Subsection 3
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#set page(
|
||||||
|
paper: "a7",
|
||||||
|
numbering: "1 / 1",
|
||||||
|
margin: (bottom: 1cm, rest: 0.5cm),
|
||||||
|
)
|
||||||
|
|
||||||
|
#set heading(outlined: true, numbering: "1.")
|
||||||
|
|
||||||
|
// This is purposefully an empty
|
||||||
|
#locate(loc => [
|
||||||
|
Non-outlined elements:
|
||||||
|
#(query(selector(heading).and(heading.where(outlined: false)), loc)
|
||||||
|
.map(it => it.body).join(", "))
|
||||||
|
])
|
||||||
|
|
||||||
|
#heading("A", outlined: false)
|
||||||
|
#heading("B", outlined: true)
|
||||||
|
#heading("C", outlined: true)
|
||||||
|
#heading("D", outlined: false)
|
@ -8,8 +8,8 @@
|
|||||||
smallcaps[Typst Academy]
|
smallcaps[Typst Academy]
|
||||||
h(1fr)
|
h(1fr)
|
||||||
locate(it => {
|
locate(it => {
|
||||||
let after = query(heading, after: it)
|
let after = query(selector(heading).after(it), it)
|
||||||
let before = query(heading, before: it)
|
let before = query(selector(heading).before(it), it)
|
||||||
let elem = if before.len() != 0 {
|
let elem = if before.len() != 0 {
|
||||||
before.last()
|
before.last()
|
||||||
} else if after.len() != 0 {
|
} else if after.len() != 0 {
|
||||||
@ -43,7 +43,7 @@
|
|||||||
|
|
||||||
= List of Figures
|
= List of Figures
|
||||||
#locate(it => {
|
#locate(it => {
|
||||||
let elements = query(figure, after: it)
|
let elements = query(selector(figure).after(it), it)
|
||||||
for it in elements [
|
for it in elements [
|
||||||
Figure
|
Figure
|
||||||
#numbering(it.numbering,
|
#numbering(it.numbering,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user