1

I want to generalize this query-replace regexp:

(perform-replace "Geophysical[\s\t\n]*Research[\s\t\n]*Letters" 
                 "Geophys.\\\\ Res.\\\\ Lett." 
                 t t nil 1 nil (point-min) (point-max))

that replaces "Geophysical Research Letters" with "Geophys.\ Res.\ Lett." in my LaTeX code, with a function that I figured out to call later with mapcar on a list of pairs of values.

I wrote this function:

(defun ExtendedJNameAbbreviator (COUPLE) ; 13/07/2017
  (interactive)
  (let* ((case-fold-search nil)
     (ExtendedName (car COUPLE))
     (ExtendedName (replace-regexp-in-string "[\s\t\n]"
                                             "[\s\t\n]*" ExtendedName t))
     (Abbreviation (nth 1 COUPLE))
     (Abbreviation (replace-regexp-in-string "\\."
                                             ".\\\\\\\\" Abbreviation t)))

    (perform-replace ExtendedName Abbreviation t t nil 1 nil (point-min) (point-max))
    ))

That works. You can test it with:

(setq mycouple '("Geophysical Research Letters"
                 "Geophys. Res. Lett."))

(ExtendedJNameAbbreviator mycouple)

But I don't understand "how it works". I mean why

(Abbreviation (replace-regexp-in-string "\\."
                                        ".\\\\\\\\" Abbreviation t)))

works? I tough it should be:

(Abbreviation (replace-regexp-in-string "\\."
                                        ".\\\\\\\\\\\\\\\\" Abbreviation t)))

instead if I put:

(read-string Abbreviation)

just before the perform-replace line it return:

"Geophys.\\ Res.\\ Lett.\\"

Should't it be

"Geophys.\\\\ Res.\\\\ Lett.\\\\"

as the direct perform-replace command?

More: I want to remove the last backslash in the replacement string. But if I wrote:

(defun ExtendedJNameAbbreviator (COUPLE) ; 13/07/2017
  (interactive)
  (let* ((case-fold-search nil)
     (ExtendedName (car COUPLE))
     (ExtendedName (replace-regexp-in-string "[\s\t\n]"
                                             "[\s\t\n]*" ExtendedName t))
     (Abbreviation (nth 1 COUPLE))
     (Abbreviation (replace-regexp-in-string "\\."
                                             ".\\\\\\\\" Abbreviation t))

     ;; To remove last \ in replacement string:
     (Abbreviation (replace-regexp-in-string "\\\\\\'"
                                             "" Abbreviation t)))

    (perform-replace ExtendedName Abbreviation t t nil 1 nil (point-min) (point-max))
    ))

I get

Invalid use of `\' in replacement text

I can't fix that. Do I need to set some string property? Do I miss something in the escaping logic? I think I don't understand the logic of the string sub-replacement in these cases.


Edit. Ok, I made it working but yet I cant understand why it works like that. This is the working code. (I realized that perform-replace and replace-regexp-in-string have slight differences in the rexexps management).

This is wy working code:

(defun ExtendedJNameAbbreviator (COUPLE) ; 13/07/2017
      (interactive)
      (let* ((case-fold-search nil)
         (ExtendedName (car COUPLE))
         (ExtendedName (replace-regexp-in-string "[\s\t\n]"
                                                 "[\s\t\n]*" ExtendedName t))
         (Abbreviation (nth 1 COUPLE))
         (Abbreviation (replace-regexp-in-string "\\."
                                                 ".\\\\\\\\" Abbreviation t))

         ;; To remove last \ in replacement string:
         (Abbreviation (replace-regexp-in-string "\\\\\\\\\\'"
                                                 "" Abbreviation t)))

        (perform-replace ExtendedName Abbreviation t t nil 1 nil (point-min) (point-max))
        ))
2
  • Note that the docstring for perform-replace reads "Don’t use this in your own program unless you want to query and set the mark just as query-replace does." It then gives you a suggestion for a loop to perform the replacements.
    – Dan
    Jul 13, 2017 at 16:13
  • @Dan I know. I want to use exactly this command because I want to query and I want to set the mark. This is just a generalization of my function. Jul 13, 2017 at 16:20

1 Answer 1

3

The way backslashes work is logical but you have to keep careful track of how many levels of backslash expansion are going on.

When you use "\\." as a regular expression, you're starting from the string literal "\\.". This string literal evaluates to the string object \., a two-character string. That's because in a string literal, a backslash quotes the next character when it isn't alphanumeric; so \\ in a string literal results in \ in the string. In a regular expression, \. matches a . character (this is true of all backslash-character pairs where the second character has a special meaning in regular expressions).

When you use ".\\\\\\\\" as the replacement template, that string literal evaluates to the string .\\\\. In a replacement template, \\ results in a literal backslash, so the replacement text ends up being .\\ (with two backslashes).

You misinterpreted read-string. It shows its argument as it is. If you see Geophys.\\ Res.\\ Lett.\\ in the minibuffer, it means that the string is Geophys.\\ Res.\\ Lett.\\. A string literal that evaluates to this string is "Geophys.\\\\ Res.\\\\ Lett.\\\\". There are other string literals that evaluate to the same string, since superfluous backslashes are allowed before many characters; for example you could write "Geophys.\\\\ Res.\\\\\ Lett\.\\\\" (with unnecessary backslashes before the last space and before the last dot).

A useful test if you aren't sure whether strings are printed literally or with backslash quoting is to see how newlines are printed. If you see a line break, which is the case with read-string, then no backslash quoting is going on. If you see \n then backslash quoting is going on.

You use Abbreviation as a replacement template. That means it undergoes another round of backslash expansion, as part of replacement template expansion. In total, that's three rounds of backslash expansion from the original string literal — one to go from the Lisp source syntax to the string object, one because that string is used as a replacement template, and one because the result of the replacement is in turn used as a replacement template. Three rounds of backslash expansion turn \\\\\\\\ (eight backslashes) into one.

When you use "\\\\\\'" as source code for a regular expression, the string literal results in the string \\\' which is used as a regular expression. This matches a backslash at the end of a string. Recall that your replacement template ends with two backslashes (it's Geophys.\\ Res.\\ Lett.\\). You're removing one in that last replace-regexp call, so one backslash remains (Geophys.\\ Res.\\ Lett.\). But a lone backslash at the end of a replacement template is invalid replacement template syntax. You wanted the replacement template to be Geophys.\\ Res.\\ Lett., so you need to remove two backslashes:

(replace-regexp "\\\\\\\\\\'" "" Abbreviation t)

Rather than hard-code the abbreviation, you should probably use a LaTeX macro. This would let you adjust to house styles such as whether to abbreviate or not, whether to use italics or not, etc. There's also the issue of when the name appears at the end of a sentence which already has a full stop; if you blindly abbreviate the last word, you'll get two dots at the end of the sentence (“Geophys. Res. Lett..”). See Macros for common abbreviations for a macro and some packages that you can use to take care of this issue.

1
  • Thank you very much. Now all is clear. I need to get the correct abbreviations of the journals' names to query the InSpire (inspirehep.net) database and get the bibtex infos. It works very well. It is not for layout purposes... thank you again. Jul 13, 2017 at 16:57

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.