How to save a list of show rules and use them?

I have a code like this

#let en2ch_point_dict = (
  "\\.\\s*": "。",
  "\\,\\s*": ","
)

#let auto_chinese_point(doc) = {
  show par: it => {
    show text: it => {
      for i in en2ch_point_dict.keys() {
        show regex(i): en2ch_point_dict.at(i)
        it
      }
      it
    }
    it
  }
  doc
}

It will change the style of points. But when I use it in document, it repaet the document. I have known the times it repeat equals the length of the dict. How to fix it.

The duplication comes from the innermost it (the one inside the for loop). For each entry into en2ch_point_dict the it that was captured by show text: it is displayed.
In theory a better way would be to apply the show rules in a for loop and only have a single it. But as far as I know it’s not possible because the effect of the each show would only exist within the scope of the loop.
Another way would be to somehow not show that it whenever there is not a match with for that key in the dictionary.

Another question on the forum was similar:

But the difference there was they wanted to match on multiple characters but always replace them with the same character. In your case this solution doesn’t work.

An iterative for loop doesn’t work, but what does work is recursion:

//Template.typ
#let en2ch_point_dict = (
  "\\.\\s*": "。",
  "\\,\\s*": "X ",
  "\\;\\s*": "Y "
)

#let apply-regex(p, remaining-pairs, it) = {
  //Apply the current substitution
  let pattern = p.at(0)
  let replace-with = p.at(1)
  let current-regex-applied = {
    show regex(pattern): replace-with
    it
  }
  
  //Any more substitutions in the list?
  if remaining-pairs.len() > 0 {
    let next-pair = remaining-pairs.first()
    let remaining = remaining-pairs.slice(1)
    //Apply remaining substitutions
    apply-regex(next-pair, remaining, current-regex-applied)
  } else {
    //No more substitutions, return existing
    current-regex-applied
  }
}

#let auto_chinese_point(doc) = {
  let dict-pairs = en2ch_point_dict.pairs()
  apply-regex(dict-pairs.first(), dict-pairs.slice(1), doc)
}
//Main.typ
#import "Template.typ": auto_chinese_point

#show: auto_chinese_point

a.,;

image

Basically the current show substitution is created and stored in a variable, then that is given to the next substitution, etc… At some point there are no more substitutions to apply so the most recent result (which includes all previous) is returned.

For this tiny example it is working fine, but I suspect if you do this for a real document it will slow the compilation time a lot.

The best solutions for applying a dynamic amount of show rules are listed here: How can I create show rules in a loop? - #2 by SillyFreak

Your example is almost correct, but you forgot to update the it variable on each iteration to also hold previous show rules. (As is, you’re simply making a copy of your text where each copy only applies exactly one of the show rules.)

Here’s how a fixed version might look like:

#let en2ch_point_dict = (
  "\\.\\s*": "。",
  "\\,\\s*": ","
)

#let auto_chinese_point(doc) = {
  show par: it => {
    show text: it => {
      for (pat, replacement) in en2ch_point_dict {
        // New 'it' with one more show rule each time
        it = {
          show regex(pat): replacement
          it
        }
      }
      it
    }
    it
  }
  doc
}
2 Likes

Furthermore, the escaped comma can be dropped. Actually, the double backslashes as well.

#let en2ch_point_dict = (
  "\.\s*": "。",
  ",\s*": ",",
)

#let auto-chinese-point(doc) = {
  show par: it => {
    show text: it => {
      for (pat, replacement) in en2ch_point_dict {
        // New 'it' with one more show rule each time
        it = {
          show regex(pat): replacement
          it
        }
      }
      it
    }
    it
  }
  doc
}


#show: auto-chinese-point

a.,;

I’d keep the double backslashes, otherwise this can generate an unexpected escape.

#let en2ch_point_dict = (
  "\\.\\s*": "。",
  ",\\s*": ","
)