Elisp: Multi-Pair String Replacement with Report

By Xah Lee. Date: . Last updated: .

This page shows you how to write a emacs lisp command that does multi-pair find replace on current buffer or text selection, and print a report of changed items.

Problem

change bracketed text like this

• 〈The Rise of “Worse is Better”〉 (1991)
• 《The Unix-Hater's Handbook》 (1994)

to this:

<cite>The Rise of “Worse is Better”</cite> (1991)
• <cite class="book">The Unix-Hater's Handbook</cite> (1994)

The command also should generate a report of all changes made, in a separate buffer.

Solution

(defun xah-angle-brackets-to-html (&optional P-begin P-end)
  "Replace all 〈…〉 to <cite>…</cite> and 《…》 to <cite class=\"book\">… in current text block or selection.

When called non-interactively, P-begin P-end are region positions.

URL `http://xahlee.info/emacs/emacs/elisp_replace_title_tags.html'
version 2017-06-10"
  (interactive)
  (let ((xchangedItems '())
        (case-fold-search nil)
        xp1 xp2
        )
    (if (and P-begin P-end)
        (progn
          (setq xp1 (region-beginning))
          (setq xp2 (region-end)))
      (if (use-region-p)
          (progn
            (setq xp1 (region-beginning))
            (setq xp2 (region-end)))
        (save-excursion
          (if (re-search-backward "\n[ \t]*\n" nil "move")
              (progn (re-search-forward "\n[ \t]*\n")
                     (setq xp1 (point)))
            (setq xp1 (point)))
          (if (re-search-forward "\n[ \t]*\n" nil "move")
              (progn (re-search-backward "\n[ \t]*\n")
                     (setq xp2 (point)))
            (setq xp2 (point))))))
    (save-restriction
      (narrow-to-region xp1 xp2)
      (goto-char (point-min))
      (while (re-search-forward "《\\([^》]+?\\)》" nil t)
        (push (match-string-no-properties 1) xchangedItems)
        (replace-match "<cite class=\"book\">\\1</cite>" "FIXEDCASE"))
      (goto-char (point-min))
      (while (re-search-forward "〈\\([^〉]+?\\)〉" nil t)
        (push (match-string-no-properties 1) xchangedItems)
        (replace-match "<cite>\\1</cite>" t)))
    (if (> (length xchangedItems) 0)
        (mapcar
         (lambda (xx)
           (princ xx)
           (terpri))
         (reverse xchangedItems))
      (message "No change needed."))))

Here's a outline of the algorithm:

  1. Search forward by regex for 《…》
  2. If found, replace it with cite tag.
  3. Push the replacement into a list (for the report of changed items later).
  4. Repeat the above until no more title brackets found.
  5. goto top, do the same for 〈…〉.
  6. When no more found, print the changed items.

All the functions in this code are very basic and is frequently used for text processing tasks. You should master them. You can just use this function as a template to write your own.

The code is easy to understand. If you find it difficult, have a look at Elisp: Quick Start and Emacs Lisp Idioms.

Showing the changed items is important, because your text may have a mis-matched bracket. The output lets you verify correctness in a glance.