Re: Multiple successive "patsubst"s…

Eric Blake <[email protected]> Wed, 19 Sep 2018 09:22:09 -0500
Newsgroups gmane.comp.gnu.m4.general
Organization Red Hat, Inc.
Message-ID <[email protected]>
On 9/18/18 6:26 PM, Grant Taylor wrote:
> I need some help with multiple successive "patsubst" statements.
> 
> I have a string of text like "Grant Taylor's Blog (:test:)" (accessible 
> to a macro as $2) that I need to escape spaces, apostrophes, colons, and 
> parenthesis.  I need the output of the above string to be "Grant\ 
> Taylor\'s\ Blog\ \(\:test\:\)".

What changequote() settings are you using? That particular string is 
awkward with the default `' quoting (since it contains ' itself, and 
since you want to modify ' into \').  Note that your choice of 
changequote needs to be made early, before defining other macros, as the 
operation of user-defined macros depends heavily on the quoting in effect.

> 
> I can get patsubst to replace one or more occurrences of any single 
> character.  But I can't seem to get things correct to replace (escape) 
> multiple different characters.
> 
> I have looked at the documentation of translit and think that it would 
> handle multiple characters, but I don't see how to add the leading 
> forward slash to escape said characters.

Here's an example, without the problematic ' and with the default `' 
quoting characters:

patsubst(``Grant Taylors Blog (:test:)'', `\([ :()]\)', `\\\1')
Grant\ Taylors\ Blog\ \(\:test\:\)

Wrapping that in a macro:

define(`escape', `patsubst(``$1'', `\([ :()]\)', `\\\1')')dnl
escape(`Grant Taylors Blog (:test:)')
Grant\ Taylors\ Blog\ \(\:test\:\)

Now with altered changequotes, so that you can also handle and escape an 
embedded ':

changequote([[,]])
define([[escape]],[[patsubst([[[[$1]]]], [[\([ :()']\)]], [[\\\1]])]])
escape([[Grant Taylor's Blog (:test:)]])
Grant\ Taylor\'s\ Blog\ \(\:test\:\)

> 
> I think I could do what I want to do with the following regular 
> expression in sed.  But I want to do this in m4, for various reasons.
> 
> s/\([ :()]\)/\1/g

Actually, in sed, it would be:

s/\([ :()']\)/\\\1/g

It may also be easier to help debug if you paste what you've tried. 
Note that one-character quotes that might occur imbalanced in your text 
are inherently difficult to manage correctly through pattern 
substitutions; using a two-character quote delimiter that is unlikely to 
appear in any of the text you are processing can make life much easier 
(I chose [[ and ]]).

-- 
Eric Blake, Principal Software Engineer
Red Hat, Inc.           +1-919-301-3266
Virtualization:  qemu.org | libvirt.org