I have this example here:
#show heading.where(level: 6): it => {
show regex("^(.*\.)"): first-word => text(fill: rgb("#7a1f1f"), weight: "bold", first-word)
it
}
====== One Word. The phrase "One Word" should be the only one highlited. But it isn't.
Every other regex engine I tested captures the phrase “One Word.”, but in typst:
Is it some sort of quirk with Typst or am I misunderstanding something? and how can I correctly capture everything before the first period?
I think this is because content in typst gets turned into a sequence in the background, so raw text in the body of the text doesn’t necessarily maintain the same structure post-processing. Thus, ^ ends up behaving differently. Take a look at this modified version of your example:
#show text: it => {
show regex("^(.*\.)"): first-word => text(fill: rgb("#7a1f1f"), weight: "bold", first-word)
it
}
#let txt = [One Word. The phrase "One Word" should be the only one highlighted. But it isn't.]
#txt
#repr(txt)
The repr function shows how the text is processed by typst. You can see the highlighting behavior correctly applies the regex to each line in the sequence.
Not sure why the whole title is highlighted for the header, though. Regardless, hopefully this shows that ^ doesn’t end up being a very helpful regex symbol in typst.
Thanks for the reply, but my question still stands, how do I only highlight the first sentence (or in other words, everything before the first period)?
Sorry, I posted to give a quick response but was looking for alternatives. I’ll add more when I figure something out.
1 Like
relevant bug report: Regex anchors behave unpredictably in regex show rules · Issue #5273 · typst/typst · GitHub
One hacky solution would be to convert the heading to a string, and match that string:
#show heading.where(level: 6): it => {
let match = to-string(it).match(regex(`^([^.]*)\.`.text)).text
show match: text.with(fill: rgb("#7a1f1f"))
it
}
String conversion function
#let to-string(it) = {
if it == none {
none
}
else if type(it) == str {
it
} else if type(it) != content {
str(it)
} else if it.has("text") {
it.text
} else if it.has("children") {
it.children.map(to-string).join()
} else if it.has("body") {
to-string(it.body)
} else if it == [ ] {
""
}
}
(I changed the regex rule, yours doesn’t seem to work)
The string conversion isn’t perfect and it will match many occurenses of the first sentence, but it should at least get you started.
Man, this is a bit of a mess, but I think it works as a drop-in solution for what you were hoping to do.
#let get_body_children(it) = if not it.body.has("children") {(it.body,)} else {it.body.children}
#let is_processed(first_child) = first_child.at("value", default:"") == "processed"
#show heading.where(level: 6): it => {
let children = get_body_children(it)
if not is_processed(children.first()) {
heading(level:6)[#metadata("processed")
#let is_before_period = true
#let style_words = text.with(fill: rgb("#7a1f1f"), weight: "bold")
#for c in children {
if is_before_period {
if c.has("text") and c.text.contains(".") {
is_before_period = false
show regex(".*\."): style_words
c
} else if is_before_period {
show: style_words
c
}
} else {
c
}
}
]
} else {
it
}
}
====== One Word. The phrase "One Word" should be the only one highlighted. And now it is!
You might be better off defining your own function that will do this though. Hope this helps.
Also, to make your question’s title more searchable for others, you might rename this thread as “Why does regex start-line character ^ not match text as expected?” or something like that.
1 Like