How to process tags depending on the parent tags?

Hello. I have a tag <quotation-mark-open> and <quotation-mark-close> dynamically added by my pollen.rkt file around dialog lines from my novel.

I want those tags to contain quotation marks depending on the value of their nearest parent having a special attribute like formatting-style="default" and formatting-style="fr/FR".

With default, it adds english style double quotes (/). With fr/FR, it simply adds nothing (the tag may be removed entirely then).

The current structure is like this:

<book-body formatting-style="fr/FR">
  <p-dial>
    <body-voice>
      <quotation-mark-open></quotation-mark-open>
      Ligne de dialogue en style français,
      <quotation-mark-close></quotation-mark-close>
    </body-voice>
    dit Michel.
  </p-dial>
  <p-dial>
    <body-voice>
      <quotation-mark-open></quotation-mark-open>
      Deuxième ligne,
      <quotation-mark-close></quotation-mark-close>
    </body-voice>
    répond Jean.
  </p-dial>
</book>
<book-body formatting-style="default">
  <p-dial>
    <body-voice>
      <quotation-mark-open></quotation-mark-open>
      English-styled dialog line here.
      <quotation-mark-close></quotation-mark-close>
    </body-voice>
    said Michael.
  </p-dial>
</book>

and should give something like this:

« Ligne de dialogue en style français, dit Michel.
— Deuxième ligne, répond Jean. »

“English-styled dialog line here.” said Michael.

I have not found means to read the parents when processing the body-voice tag (I’m using define-tag-function).

I have tried to implement a pollen decode function, but I’m not good enough in racket yet to make my own. Is it the right way to achieve the desired effect, though?

Note 1: I can solve my issue with CSS, but having the quotation marks stripped from the real text doesn’t seem the best for accessibility, and would work only with the help of a web page renderer.

Note 2: You may notice the full dash at the start of the second french line. This is also something I want to learn to make programmatically, as it appears only from the second line and replaces the quotation mark.

This would be easy in CSS — use the q tag and set the lang attrbitute on the containing tag.

It’s possible to accomplish this directly in Pollen, though it requires some trickier Racketeering.

First, rather than surrounding your quoted material with quotation-mark-open and quotation-mark-closed, you can have one surrounding tag. Let’s call it quotation.

Second, we want to make the result of quotation tag dependent on an attribute of the book-body tag. The problem is that tag functions are evaluated from the bottom up. Meaning: by the time the tag function for book-body is triggered, quotation has already been evaluated.

There are multiple ways to change the order of evaluation. Since you’re new to Racket, probably the simplest is to delay evaluation of the quotation marks by wrapping each one in a parameter. A parameter is a little function that holds a value. We can control the value of a parameter by using parameterize.

We start by setting up a parameter for current-left-quote and current-right-quote (by convention, parameters are often named with the current prefix to remind us that the value can change.)

In quotation, we surround elems with current-left-quote and current-right-quote. Notice that we don’t actually evaluate these parameters for their value yet (e.g., (current-left-quote)) — we just pass them through as functions. This is a little weird, since it’s not a valid X-expression, but keep reading …

For the tag function book-body, we can’t use define-tag-function because it is strict about elems only containing things that are part of a valid X-expression. But we can use define. This time, we look at the formatting-style tag to set the left-quote and right-quote values. Then we use parameterize to assign these values to our two parameters.

Finally, we use the helper function evaluate-params to traverse the elems argument recursively and evaluate the values that are parameters — namely, all appearances of current-left-quote and current-right-quote. At that point, all the occurrences of our parameters have been converted into the actual quotation marks based on the setting of formatting-style.

test.html.pm

#lang pollen

◊book-body[#:formatting-style "fr/FR"]{
  ◊p-dial{
    ◊body-voice{
      ◊quotation{Ligne de dialogue en style français,}
    }
    dit Michel.
  }
  ◊p-dial{
    ◊body-voice{
      ◊quotation{Deuxième ligne,}
    }
    répond Jean.
  }
}

◊book-body{
  ◊p-dial{
    ◊body-voice{
      ◊quotation{English-styled dialog line here.}
    }
    said Michael.
  }
}

pollen.rkt

#lang racket/base
(require racket/match pollen/tag txexpr pollen/decode)
(provide (all-defined-out))

(define current-left-quote (make-parameter #false))
(define current-right-quote (make-parameter #false))

(define (evaluate-params x)
  (match x
    [(? list? xs) (map evaluate-params xs)]
    [(? parameter? param) (param)]
    [_ x]))

(define (book-body #:formatting-style [formatting-style 'default] . elems)
  (define-values (left-quote right-quote)
    (match formatting-style
      ["fr/FR" (values "« " " »")]
      [_ (values "“" "”")]))
  (parameterize ([current-left-quote left-quote]
                 [current-right-quote right-quote])
    (txexpr 'book-body null (evaluate-params elems))))

(define-tag-function (quotation attrs elems)
  (list* '@ attrs (append (list current-left-quote) elems (list current-right-quote))))
1 Like

Thank you very much, @mbutterick! It took time to adapt it to my current code, but I’m glad to inform you it worked!

As you said, I’ve had to convert my define-tag-function into simple define to make it work.

The sad part is that I have to list every keyword used in my pollen markup files (e.g. the class attribute) into the procedures definitions. It’s less dynamic than define-tag-function on this part. I will miss the handy attrs. But that’s a minor issue.

Thank you again for your fast reply. It was really informative.

You can use make-keyword-procedure to convert an ordinary function into one that accepts arbitrary keywords so you don’t have to “list every keyword”. This is how define-tag-function works. So you could write your own appoximation of define-tag-function that is less strict about inputs and outputs.