- xah-replace-pairs 20170804.2216(in MELPA)
- Multi-pair find/replace in strings and region.
概要
elispで置換処理を書くのはけっこう面倒です。
xah-replace-pairs.el は、バッファ内・文字列の置換を
一発で記述できるようにするライブラリです。
インストール
パッケージシステムを初めて使う人は
以下の設定を ~/.emacs.d/init.el の
先頭に加えてください。
(package-initialize) (setq package-archives '(("gnu" . "http://elpa.gnu.org/packages/") ("melpa" . "http://melpa.org/packages/") ("org" . "http://orgmode.org/elpa/")))
初めてxah-replace-pairsを使う方は
以下のコマンドを実行します。
M-x package-install xah-replace-pairs
アップグレードする方は、
以下のコマンドでアップグレードしてください。
そのためにはpackage-utilsパッケージが必要です。
M-x package-install package-utils (初めてアップグレードする場合のみ) M-x package-utils-upgrade-by-name xah-replace-pairs
replace-stringやreplace-regexpをelispで使ってはいけない
バッファ内の文字列や正規表現を置換するならば、
素直に replace-string や replace-regexp を使えばいいじゃないか
という声が聞こえてきそうですが、Lispプログラミングではだめな方法です。
replace-stringのdocstringにはこう書いてあります。
This function is usually the wrong thing to use in a Lisp program. What you probably want is a loop like this: (while (search-forward FROM-STRING nil t) (replace-match TO-STRING nil t)) which will run faster and will not set the mark or print anything. (You may need a more complex loop if FROM-STRING can match the null string and TO-STRING is also null.)
replace-regexpには同様にこう書けと言われています。
(while (re-search-forward REGEXP nil t) (replace-match TO-STRING nil nil))
elispでは頻繁にテキスト処理が行われるのだから、
置換くらい標準関数で用意されてしかるべきなんですが、
何年たってもなぜか用意されていないんですよね。
文字列内の置換ならば replace-regexp-in-string が使えますが、
複数の置換を行うには何度もネストする必要があります。
この関数もEmacs21になってやっと導入されたのだから
これまで無数の自前置換関数が定義されたことでしょう。
HTMLで実体参照に変換する例
HTMLでは「&」を表すには「&」と実体参照で書かなければなりませんが、
region内で置換するコマンドを考えます。
作者のサイトでの例です。
(defun replace-html-chars-region (begin end) "Replace “<” to “<” etc in region." (interactive "r") (save-restriction (narrow-to-region begin end) (goto-char (point-min)) (while (search-forward "&" nil t) (replace-match "&" nil t)) (goto-char (point-min)) (while (search-forward "<" nil t) (replace-match "<" nil t)) (goto-char (point-min)) (while (search-forward ">" nil t) (replace-match ">" nil t))))
うざってぇ…置換ごときでなんでこんなに行数喰うんだよ
と思われるでしょう。
でも、 xah-replace-pairs-region を使うと一発です!
(require 'xah-replace-pairs) (defun replace-html-chars-region (begin end) (interactive "r") (xah-replace-pairs-region begin end '( ["&" "&"] ["<" "<"] [">" ">"] )))
作者はベクタ好きのようですが、リストでもいいです。
(defun replace-html-chars-region (begin end) (interactive "r") (xah-replace-pairs-region begin end '( ("&" "&") ("<" "<") (">" ">") )))
pairsと名乗っていますが、単純に1回の置換も
すんなりと記述できます。
(with-temp-buffer (insert "foobarbaz") (xah-replace-pairs-region (point-min) (point-max) '(("bar" "BAR"))) (buffer-string)) ; => "fooBARbaz"
正規表現置換バージョンの xah-replace-regexp-pairs-region もあります。
文字列置換も楽々
文字列置換も replace-regexp-in-string を連発するより簡単です。
xah-replace-pairs-in-string を使います。
この関数は xah-replace-pairs-region を
with-temp-buffer 、 insert 、 buffer-string
でくるんでいるだけです。
正規表現置換バージョンの xah-replace-regexp-pairs-in-string
もあります。
;;; BEFORE (replace-regexp-in-string ">" ">" (replace-regexp-in-string "<" "<" (replace-regexp-in-string "&" "&" "<<<A&B>>>"))) ;; => "<<<A&B>>>" ;;; AFTER (xah-replace-pairs-in-string "<<<A&B>>>" '(("&" "&") ("<" "<") (">" ">"))) ;; => "<<<A&B>>>"
再帰置換a→c、c→dもできる
実はxah-replace-pairs-(region|in-string)は
置換先を一旦一時的な別な文字に置換し、
その文字を置換先に置換しています。
それをせずに単純に置換するだけの
xah-replace-pairs-region-recursive と
xah-replace-pairs-in-string-recursive
も用意されています。
当然、こちらの方が動作は高速です。
;;; 置換先のcを一時的に別な文字に置換しているのでa→cになる (xah-replace-pairs-in-string "abcd" '(("a" "c") ("c" "d"))) ;; => "cbdd" ;;; a→cの後にc→dが適用されるのでa→dになる (xah-replace-pairs-in-string-recursive "abcd" '(("a" "c") ("c" "d"))) ;; => "dbdd" ;;; regexpの方はrecursiveな挙動をする (xah-replace-regexp-pairs-in-string "abcd" '(("a" "c") ("c" "d"))) ;; => "dbdd"
まとめ
このライブラリには6つの関数が定義されています。
統制が取れた名前で正規表現で書くと
xah-replace(-regexp)?-pairs-(region|in-string)(-recursive)?
となります。
- xah-replace-pairs-region
- xah-replace-pairs-in-string
- xah-replace-pairs-region-recursive
- xah-replace-pairs-in-string-recursive
- xah-replace-regexp-pairs-region
- xah-replace-regexp-pairs-in-string
が定義されています。
- xah-replace-regexp-pairs-region-recursive
- xah-replace-regexp-pairs-in-string-recursive
は定義されていませんが、regexp版はrecursiveな挙動をします。
elispプログラマならば、ぜひ導入したいところです。
本日もお読みいただき、ありがとうございました。参考になれば嬉しいです。