### 17.7 Solution for `capitalize` ¶

The `capitalize` macro (see Substituting text by regular expression) as presented earlier does not allow clients to follow the quoting rule of thumb. Consider the three macros `active`, `Active`, and `ACTIVE`, and the difference between calling `capitalize` with the expansion of a macro, expanding the result of a case change, and changing the case of a double-quoted string:

    $ m4 -I examples
    include(`capitalize.m4')dnl
    define(`active', `act1, ive')dnl
    define(`Active', `Act2, Ive')dnl
    define(`ACTIVE', `ACT3, IVE')dnl
    upcase(active)
    ⇒ACT1,IVE
    upcase(`active')
    ⇒ACT3, IVE
    upcase(``active'')
    ⇒ACTIVE
    downcase(ACTIVE)
    ⇒act3,ive
    downcase(`ACTIVE')
    ⇒act1, ive
    downcase(``ACTIVE'')
    ⇒active
    capitalize(active)
    ⇒Act1
    capitalize(`active')
    ⇒Active
    capitalize(``active'')
    ⇒_capitalize(`active')
    define(`A', `OOPS')
    ⇒
    capitalize(active)
    ⇒OOPSct1
    capitalize(`active')
    ⇒OOPSctive

First, when `capitalize` is called with more than one argument, it was throwing away later arguments, whereas `upcase` and `downcase` used ‘`$*`’ to collect them all. The fix is simple: use ‘`$*`’ consistently.

Next, with single-quoting, `capitalize` outputs a single character, a set of quotes, then the rest of the characters, making it impossible to invoke `Active` after the fact, and allowing the alternate macro `A` to interfere. Here, the solution is to use additional quoting in the helper macros, then pass the final over-quoted output string through `_arg1` to remove the extra quoting and finally invoke the concatenated portions as a single string.

Finally, when passed a double-quoted string, the nested macro `_capitalize` is never invoked because it ended up nested inside quotes. This one is the toughest to fix. In short, we have no idea how many levels of quotes are in effect on the substring being altered by `patsubst`. If the replacement string cannot be expressed entirely in terms of literal text and backslash substitutions, then we need a mechanism to guarantee that the helper macros are invoked outside of quotes. In other words, this sounds like a job for `changequote` (see Changing the quote characters). By changing the active quoting characters, we can guarantee that replacement text injected by `patsubst` always occurs in the middle of a string that has exactly one level of over-quoting using alternate quotes; so the replacement text closes the quoted string, invokes the helper macros, then reopens the quoted string. In turn, that means the replacement text has unbalanced quotes, necessitating another round of `changequote`.

In the fixed version below, (also shipped as `m4-1.4.21/examples/capitalize2.m4`), `capitalize` uses the alternate quotes of ‘`<<[`’ and ‘`]>>`’ (the longer strings are chosen so as to be less likely to appear in the text being converted). The helpers `_to_alt` and `_from_alt` merely reduce the number of characters required to perform a `changequote`, since the definition changes twice. The outermost pair means that `patsubst` and `_capitalize_alt` are invoked with alternate quoting; the innermost pair is used so that the third argument to `patsubst` can contain an unbalanced ‘`]>>`’/‘`<<[`’ pair. Note that `upcase` and `downcase` must be redefined as `_upcase_alt` and `_downcase_alt`, since they contain nested quotes but are invoked with the alternate quoting scheme in effect.

    $ m4 -I examples
    include(`capitalize2.m4')dnl
    define(`active', `act1, ive')dnl
    define(`Active', `Act2, Ive')dnl
    define(`ACTIVE', `ACT3, IVE')dnl
    define(`A', `OOPS')dnl
    capitalize(active; `active'; ``active''; ```actIVE''')
    ⇒Act1,Ive; Act2, Ive; Active; `Active'
    undivert(`capitalize2.m4')dnl
    ⇒divert(`-1')
    ⇒# upcase(text)
    ⇒# downcase(text)
    ⇒# capitalize(text)
    ⇒#   change case of text, improved version
    ⇒define(`upcase', `translit(`$*', `a-z', `A-Z')')
    ⇒define(`downcase', `translit(`$*', `A-Z', `a-z')')
    ⇒define(`_arg1', `$1')
    ⇒define(`_to_alt', `changequote(`<<[', `]>>')')
    ⇒define(`_from_alt', `changequote(<<[`]>>, <<[']>>)')
    ⇒define(`_upcase_alt', `translit(<<[$*]>>, <<[a-z]>>, <<[A-Z]>>)')
    ⇒define(`_downcase_alt', `translit(<<[$*]>>, <<[A-Z]>>, <<[a-z]>>)')
    ⇒define(`_capitalize_alt',
    ⇒  `regexp(<<[$1]>>, <<[^\(\w\)\(\w*\)]>>,
    ⇒    <<[_upcase_alt(<<[<<[\1]>>]>>)_downcase_alt(<<[<<[\2]>>]>>)]>>)')
    ⇒define(`capitalize',
    ⇒  `_arg1(_to_alt()patsubst(<<[<<[$*]>>]>>, <<[\w+]>>,
    ⇒    _from_alt()`]>>_$0_alt(<<[\&]>>)<<['_to_alt())_from_alt())')
    ⇒divert`'dnl