#Single cell transformation in table

155 messages · Page 1 of 1 (latest)

hollow pulsar
#

im trying to match a specific cell in col 2, row 3 to colorize its contents based on a regex pattern —like shown in the picture—

#

?r theme=light

#show table.cell: it => {
  if it.x == 1 or it.y == 3 {
    show regex("^(7(\.0|(\.[0-9]))?|8(\.[0-8]|\.9)?\sHIGH)$"): it => {
      highlight(fill: red)[#it]
    }
    it
  } else  {
    it
  }
}

#table(
  columns: 2,
  [Name], [Name Text],
  [Id.],  [ID],
  [CVSS], [7.8 HIGH],
)
hollow pulsar
#

thx in advance typstguy

gray mirage
#

when you have special characters, including spaces, your text is split into multiple elements (text or not) by Typst, while show rules on text (and regex) can only match on a single text element

#

so im not sure if you will be able to do that

#

if you have the original text as a string, i.e. "7.8 HIGH" , that's potentially doable, however

#

so depends on your context, on where you're getting this table data from

gray mirage
#

then, assuming you have your data in a dictionary or array or .json file or smth, you can go through it and manually convert the strings to content , applying that rule as applicable

hollow pulsar
#

oh the data is directly on the table itself

#

but i could create a dict or json if needed

gray mirage
#

well, if you want to batch-apply it, you will have to use strings stored somewhere else

#

due to that limitation

#

otherwise you'd just manually highlight on the table itself

hollow pulsar
gray mirage
#

yep

#

if it can be inside a larger string, you can use regex to split the string into matching parts

#

and then you convert matching individual parts into highlight(stuff), the rest is just directly translated to content

#

though

#

it appears theres a workaround you can try

#

?r

#show "a. * b": "c"

#"a. * b"
gray mirage
#

simply writing strings instead of content seems to be enough to make the show rules work

#

so you can try that instead

hollow pulsar
#

im new to that how would it apply to the above?

gray mirage
#

well

#

im presenting a separate idea

#

unrelated to the dict or json idea

#

this separate idea would work with your original solution

#

?r theme=light

#show table.cell: it => {
    show "7.8 HIGH": it => {
      highlight(fill: red)[#it]
    }
    it 
}

#table(
  columns: 2,
  [Name], [Name Text],
  [Id.],  [ID],
  [CVSS], "7.8 HIGH",
)
gray mirage
#

i think your regex is wrong as it wasnt matching

#

either way i added the literal string as a PoC

#

hmm

#

actually

#

it works either way lol, kinda funny

#

?r theme=light

#show table.cell: it => {
    show regex("7\\.8 HIGH"): it => {
      highlight(fill: red)[#it]
    }
    it 
}

#table(
  columns: 2,
  [Name], [Name Text],
  [Id.],  [ID],
  [CVSS], [7.8 HIGH],
)
gray mirage
#

though the problem does manifest itself for certain characters:

#

?r theme=light

#show table.cell: it => {
    show regex("7\\.8\\* HIGH"): it => {
      highlight(fill: red)[#it]
    }
    it 
}

#table(
  columns: 2,
  [Name], [Name Text],
  [Id.],  [ID],
  [CVSS], [7.8\* HIGH],
)

#table(
  columns: 2,
  [Name], [Name Text],
  [Id.],  [ID],
  [CVSS], "7.8* HIGH",
)
gray mirage
#

so it's best to use strings where possible if you're gonna be using show rules on text

hollow pulsar
#

damn i skill issued myself again

#

im trying to match a range of nums maybe typst regex doesn't support extended

gray mirage
#

it does

#

in particular it seems you match either 7.(0|1|...|9) (theres a redundant \.0 there btw), or 8.(0|1|...|9) (note that the [0-8] | 9 there could be merged into just [0-9]) - or just 8 - followed by HIGH

#

also note that [0-9] can be written as \d (Actually \\d since you're writing a string literal where backslashes are syntactically relevant)

gray mirage
#

i'd thus probably write it as regex("[78](?:\\.\\d)?\\s+HIGH")

#

without ^ $ as well to avoid potential problems

hollow pulsar
#

thx kindly! i got it to where i wanted and turned it into a more sophisticated highlighter! typstguy

#

?r theme=light

#show table.cell: it => {
  let none_    = regex("^0\.0 NONE$") // 0.0
  let low      = regex("^(0\.[1-9]|[1-2]\.[0-9]|3\.[0-9]) LOW$") // 0.1-3.9
  let medium   = regex("^(4\.[0-9]|5\.[0-9]|6\.[0-9]) MEDIUM$") // 4.0-6.9
  let high     = regex("^(7\.[0-9]|8\.[0-9]) HIGH$") // 7.0-8.9
  let critical = regex("^(9\.[0-9]|10\.0) CRITICAL$") // 9.0-10.0

  if it.x == 1 {
    show none_    : it => {}
    show low      : it => highlight(fill: yellow)[#it]
    show medium   : it => highlight(fill: orange)[#it]
    show high     : it => highlight(fill: red)[#it]
    show critical : it => highlight(fill: black)[#text(fill: white)[#it]]
    it
  } else {
    it
  }
}

#table(
  columns: 2,
  [Name], [Name Text],
  [Id.],  [ID],
  [CVSS], "7.8 HIGH",
  [CVSS], "6.2 MEDIUM",
  [CVSS], "3.0 LOW",
  [CVSS], "9.5 CRITICAL"
)
hollow pulsar
#

typst is so mighty and formidable typstguy

scenic cove
#

crazy idea why not just make a CVSS function instead of regex?

gray mirage
# hollow pulsar ?r theme=light ``` #show table.cell: it => { let none_ = regex("^0\.0 NONE$...

?r theme=light

#show table.cell: it => {
  let none_    = regex("^0\.0 NONE$") // 0.0
  let low      = regex("^(0\.[1-9]|[1-2]\.[0-9]|3\.[0-9]) LOW$") // 0.1-3.9
  let medium   = regex("^(4\.[0-9]|5\.[0-9]|6\.[0-9]) MEDIUM$") // 4.0-6.9
  let high     = regex("^(7\.[0-9]|8\.[0-9]) HIGH$") // 7.0-8.9
  let critical = regex("^(9\.[0-9]|10\.0) CRITICAL$") // 9.0-10.0

  if it.x == 1 {
    show none_    : none
    show low      : highlight.with(fill: yellow)
    show medium   : highlight.with(fill: orange)
    show high     : highlight.with(fill: red)
    show critical : highlight.with(fill: black)
    show critical : set text(white)
    it
  } else {
    it
  }
}

#table(
  columns: 2,
  [Name], [Name Text],
  [Id.],  [ID],
  [CVSS], "7.8 HIGH",
  [CVSS], "6.2 MEDIUM",
  [CVSS], "3.0 LOW",
  [CVSS], "9.5 CRITICAL"
)
gray mirage
#

maybe a lil' fancier

#

but yea I agree

#

writing CVSS(7.8), CVSS(6.2) etc. would likely be more appropriate

#

it'd then generate the label and the highlighting for you automatically

scenic cove
#

could also map high medium etc

gray mirage
#

yea thats what i mean by label

hollow pulsar
#

like this?

gray mirage
#

no

#

first, set is used to set properties on existing elements

#

so you'd write set highlight(fill: black) (no with)

#

but still, theres no highlight there for that to apply to

#

that'd work with #show something: highlight.with(...) to wrap something in a new highlight element

#

however those set rules are not applying to anything, since they apply only to things in their scope, and the scope there is
{ set ... }
so they dont have any effect in practice

#

you'd have to return num on each branch, in theory

#

but actually dont return bcuz otherwise you're making the show rules useless as well

#

to elaborate on that: note that
{ let x = 5 (x + 2,) (2,) (3,) }

is the same as (let x = 5) + (5 + 2,) + (2,) + (3,) (and assignments always evaluate to none), so you get (7, 2, 3)

but if you write

{ (2,) (3,) return 10 }

you get just 10

#

and the way show rules, counter().update(...), state().update(...), and other stuff works is that what you return is not just the value you actually return, but the value joined with all those other things

#

so basically dont return if you want to apply "side effects " to things

#

in fact you will almost never want to return so when in doubt just dont

#

now, with that out of the way

#

the solution is

// note that Typst doesn't have type annotations,
// you were setting the default value for a named arg
#let CVSS(num) = {
  let (highlight-fill, text-fill) = if num >= 0.1 and num <= 3.9 { 
    (rgb("E5C639"), auto)
  } else if num >= 4.0 and num <= 6.9 {
    (rgb("E59C39", auto)
  } ... {  // omitted for brevity
  } else if num >= 9.0 and num <= 10.0 {
    (black, white)
  } else {
    (none, auto)
  }

  let highlighted = if highlight-fill != none { highlight(fill: highlighted-fill)[#num] } else [#num]
  let with-text-fill = if text-fill != auto { text(text-fill, highlighted) } else { highlighted }

  with-text-fill
}
hollow pulsar
#

?r theme=light

#let CVSS(num, str) = {
  if num >= 0.1 and num <= 3.9 {
    highlight(fill: rgb("E5C639"))[#num #str]
  } else if num >= 4.0 and num <= 6.9 {
    highlight(fill: rgb("E59C39"))[#num #str]
  } else if num >= 7.0 and num <= 8.9 {
    highlight(fill: rgb("E5394B"))[#num #str]
  } else if num >= 9.0 and num <= 10.0 {
    highlight(fill: black)[#text(fill: white)[#num #str]]
  } else {
    num
  }
}

#table(
  columns: 2,
  [Name], [Name Text],
  [Id.],  [ID],
  [CVSS], [#CVSS(7.8, "HIGH")],
  [CVSS], [#CVSS(6.2, "MEDIUM")],
  [CVSS], [#CVSS(3.0, "LOW")],
  [CVSS], [#CVSS(9.5, "CRITICAL")]
)
hollow pulsar
#

i was doing this ❤️

#

yea you're right thx for all the above i'll try to be mindful on the effects that and idiomatic typst is what im struggling w/ the most but little by little science

gray mirage
#

yea dont worry

#

just wanted to give you all the tips upfront so you'll slowly get used to how it works

hollow pulsar
#

also if there's anything more interesting to answer or support pls go for it haha i've asked a lot already

gray mirage
#

typst's code mode indeed has a few differences from other programming languages

hollow pulsar
#

i'll try on my own to change the highlight with round corners with a box function have a great one!

#

should be as easy as defining something like

#let linebox(color, body) = box(
  fill: color,
  width: 5em,
  outset: 1pt,
  inset: 1pt,
  radius: 2pt,
  body
)
#

looking good

scenic cove
#

I wrote this shortly after getting to know about Typst

hollow pulsar
hollow pulsar
#

im going for something kinda fancier but i'd love to share the template when i have it polished ❤️

#

im not very smart w/ code or with security kinda starting but i love typography books 💜

#

i could give a lot of recommendations if needed

scenic cove
#

I already have something much fancier but can't share it anymore^^ but maybe you could even extract some of the stuff into its own library

hollow pulsar
#

tfw when the template is under NDA

#

oof

#

jkjk haha

scenic cove
#

it was more like a proof of concept that it's better than latex :D some of the things I've done there I wouldn't do today, like how I used query

hollow pulsar
#

it's inspiring still

#

i've done a lot of latex and latex3 specifically ❤️

#

feel free to PM or add to friends list btw, any of u

#

and thx again for all the help typstguy

scenic cove
#

writing a complex template in latex is hard xD and at some point it's not fun anymore and takes a long time to compile if u add some logic to it

scenic cove
hollow pulsar
#

yes typst is so cozy!

hollow pulsar
#

but it's too chomky

#

maybe manually fine-tuning the relative width with measure(body).width / 0.95?

#

if there's a better way lmk ❤️

hollow pulsar
fading dew
fading dew
#

I would feel partly indemnified if if … {} <else {}> would have a syntax with a braceless variëty, since there's also no Ternary Conditional Operator.

hollow pulsar
# hollow pulsar ?r theme=light ``` #let CVSS(num, str) = { if num >= 0.1 and num <= 3.9 { ...

?r theme=light

#let linebox(color, body) = box(
  fill  : color,
  outset: 1pt,
  inset : 1pt,
  radius: 2pt,
  body
)

#let CVSS(num, str) = {
  (
    (num >= 0.1 and num <= 3.9,
      linebox(rgb("E5C639"))[#num #str]),
    (num >= 4.0 and num <= 6.9,
      linebox(rgb("E59C39"))[#num #str]),
    (num >= 7.0 and num <= 8.9,
      linebox(rgb("E5394B"))[#num #str]),
    (num >= 9.0 and num <= 10.0,
      linebox(rgb("000000"))[#text(fill: rgb("FFFFFF"))[#num #str]]),
    (true, num)
  ).find(t => t.at(0)).at(1)
}

#table(
  columns: 2,
  [Name], [Name Text],
  [Id.],  [ID],
  [CVSS], [#CVSS(7.8, "HIGH")],
  [CVSS], [#CVSS(6.2, "MEDIUM")],
  [CVSS], [#CVSS(3.0, "LOW")],
  [CVSS], [#CVSS(9.5, "CRITICAL")]
)
hollow pulsar
#

for lack of a match/case 😅

#

thx a lot @gray mirage, @scenic cove and @fading dew u guys are helping me write cleaner code ✨

fading dew
hollow pulsar
fading dew
#

?r

/// Return a value corresp. to the first case with a head (i.e. `haulm`) equal to `needle`
/// - cb-args (none, arguments, array):
///     / … (none):
///         So that the value \@ `cb` is directly taken and not its return value
///     / … (…):
///         To get passed to the `cb` corresponding to the match
/// - needle (any,                                          -pos):
///         To be tested for equality with `haulm`s
/// - ..cases (array,                                       -vic):
///     _Elements:_
///     / haulm (any):
///         The basis on which the corresponding value gets selected
///     / cb (function, type, any,                          -opt):
///         When not given, the the current `case` gets or-combinated with the following,
///         so that the `cb` of the last of them gets used,
///         if itself or one of the cases without before matches.
///
/// -> any
/// === →
///     The corresp. `cb` respectively its result
#let switch(cb-args: (), needle, ..cases) = {
    let mat = cases.pos().position(
        case=> needle == case.first(),
    )
    let cb = if none != mat {
        cases.pos().slice(mat).find(
            ((_, ..tail))=> () != tail,
        ).last()
    }

    if none != cb-args and (function, type).contains(type(cb)) {
        cb(..cb-args)
    } else {cb}
}

#switch(
    cb-args: (2,),
    "hey",
    ("bye", calc.odd),
    ("hi"),
    ("hey", calc.even),
) = true
#

Why doesn't typst-ansi-hl work any longer? (After reinstalling the same revision, everything's as expected again. 🤔)

fading dew
fading dew
fading dew
# fading dew Have been showcasing it's features, but I feel like it verily needs a `default:`...

?r

#import calc: *

#let switch(cb-args: (), default: {}, needle, ..args) = {
    let call-types = (function, type)

    let cases = args.pos()
    let mat = cases.position(
        case=> needle == case.first(),
    )
    let cb = if {} == mat {default} else {
        cases.slice(mat).find(
            ((_, ..tail))=> () != tail,
        ).last()
    }

    //  `none` → opt-out
    if {} != cb-args and call-types.contains(type(cb)) {
        cb(..cb-args)
    } else {cb}
}

#switch(
    cb-args: (2,),
    default: false,
    "swag",
    ("bye", odd),
    ("hi"),
    ("hey", even),
)
fading dew
#
/// Return a value corresp. to the first case with a head (i.e. `haulm`) equal to `needle`
/// - cb-args (none, arguments, array):
///     / … (none):
///         So that the value \@ `cb` is directly taken and not its return value
///     / … (…):
///         To get passed to the `cb` corresponding to the match
/// - default (any):
///     To become the `cb` if no `haulm` matches
/// - needle (any,                                          -pos):
///     To be tested for equality with `haulm`s
/// - ..cases (array,                                       -vic):
///     _Elements:_
///     / haulm (any):
///         The basis on which the corresponding value gets selected
///     / cb (function, type, any,                          -opt):
///         When not given, the the current `case` gets or-combinated with the following,
///         so that the `cb` of the last of them gets used,
///         if itself or one of the cases without before matches.
///
/// -> any
/// === →
///     The corresp. `cb` respectively its result
hollow pulsar
#

hey low how do you make it so discord syntax highlights typst's code blocks?

hollow pulsar
#

im using .at() to extract the 3.0 info from str, open to suggestions if there's a cleaner way:

#

?r theme=light

#let str = "CVSS:3.0/AV:L/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:H/E:P/RL:W/RC:C"
#let url = "https://www.first.org/cvss/calculator/"
#let sub = {str.at(5); str.at(6); str.at(7); "#"}
#let all = {url; sub; str}

#set text(6pt)
#link(all)
scenic cove
#

?r

#let cvss40 = regex("^CVSS:4[.]0\/AV:[NALP]\/AC:[LH]\/AT:[NP]\/PR:[NLH]\/UI:[NPA]\/VC:[HLN]\/VI:[HLN]\/VA:[HLN]\/SC:[HLN]\/SI:[HLN]\/SA:[HLN](\/E:[XAPU])?(\/CR:[XHML])?(\/IR:[XHML])?(\/AR:[XHML])?(\/MAV:[XNALP])?(\/MAC:[XLH])?(\/MAT:[XNP])?(\/MPR:[XNLH])?(\/MUI:[XNPA])?(\/MVC:[XNLH])?(\/MVI:[XNLH])?(\/MVA:[XNLH])?(\/MSC:[XNLH])?(\/MSI:[XNLHS])?(\/MSA:[XNLHS])?(\/S:[XNP])?(\/AU:[XNY])?(\/R:[XAUI])?(\/V:[XDC])?(\/RE:[XLMH])?(\/U:(X|Clear|Green|Amber|Red))?$")
#let cvss31 = regex("^CVSS:3[.]1/((AV:[NALP]|AC:[LH]|PR:[NLH]|UI:[NR]|S:[UC]|[CIA]:[NLH]|E:[XUPFH]|RL:[XOTWU]|RC:[XURC]|[CIA]R:[XLMH]|MAV:[XNALP]|MAC:[XLH]|MPR:[XNLH]|MUI:[XNR]|MS:[XUC]|M[CIA]:[XNLH])/)*(AV:[NALP]|AC:[LH]|PR:[NLH]|UI:[NR]|S:[UC]|[CIA]:[NLH]|E:[XUPFH]|RL:[XOTWU]|RC:[XURC]|[CIA]R:[XLMH]|MAV:[XNALP]|MAC:[XLH]|MPR:[XNLH]|MUI:[XNR]|MS:[XUC]|M[CIA]:[XNLH])$")
#let cvss30 = regex("^CVSS:3[.]0/((AV:[NALP]|AC:[LH]|PR:[NLH]|UI:[NR]|S:[UC]|[CIA]:[NLH]|E:[XUPFH]|RL:[XOTWU]|RC:[XURC]|[CIA]R:[XLMH]|MAV:[XNALP]|MAC:[XLH]|MPR:[XNLH]|MUI:[XNR]|MS:[XUC]|M[CIA]:[XNLH])/)*(AV:[NALP]|AC:[LH]|PR:[NLH]|UI:[NR]|S:[UC]|[CIA]:[NLH]|E:[XUPFH]|RL:[XOTWU]|RC:[XURC]|[CIA]R:[XLMH]|MAV:[XNALP]|MAC:[XLH]|MPR:[XNLH]|MUI:[XNR]|MS:[XUC]|M[CIA]:[XNLH])$")
#let cvss20 = regex("^((AV:[NAL]|AC:[LMH]|Au:[MSN]|[CIA]:[NPC]|E:(U|POC|F|H|ND)|RL:(OF|TF|W|U|ND)|RC:(UC|UR|C|ND)|CDP:(N|L|LM|MH|H|ND)|TD:(N|L|M|H|ND)|[CIA]R:(L|M|H|ND))/)*(AV:[NAL]|AC:[LMH]|Au:[MSN]|[CIA]:[NPC]|E:(U|POC|F|H|ND)|RL:(OF|TF|W|U|ND)|RC:(UC|UR|C|ND)|CDP:(N|L|LM|MH|H|ND)|TD:(N|L|M|H|ND)|[CIA]R:(L|M|H|ND))$")

#let vs = "CVSS:3.0/AV:L/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:H/E:P/RL:W/RC:C"

#vs.matches(cvss20).len()

#vs.matches(cvss30).len()

#vs.matches(cvss31).len()

#vs.matches(cvss40).len()
scenic cove
#

Then construct the URL from that. But apparently only 3.0, 3.1 and 4.0 have a calculator URL.

#

So you can also cut the regex to only look for the version if that's all you're interested in.

#

However, if you also validate it, you can be sure that the URL works