Emacs Lisp の文字列操作まとめ

元記事アーカイブのアーカイブ。なにか問題があれば教えて下さい。以下、アーカイブ。


基本的には Emacs Lisp の Info を読むと何となくパーツはそろっていることは分かる。 ただ、パーツが特殊だったりしてよく使い方を忘れるのでよく見る情報をメモ。

基本的情報源

以下の情報はローカルにブックマークするなどして即引けるようにしておくと便利。

  • Info 4 Strings and Characters
      1. 文字列と文字
    • 文字列の操作、比較、変換など
  • Info 32.2 Examining Buffer Contents
    • 31.2 バッファの内容を調べる
    • バッファの文字列取得
  • Info 32.19 Text Properties
    • 31.19 テキスト属性
    • テキストプロパティ(テキスト属性)のいろいろ
  • Info 34 Searching and Matching
      1. 探索と一致
    • 探索、正規表現
  • Info 38.10 Width
    • 38.9 表示幅
    • 非 ASCII 文字列を含む文字や文字列の幅(画面上の長さ)、端っこ切る
  • 逆引き Emacs Lisp 文字列
    • それなりに数が多い
  • no title
    • 実は必要なことはここに大体書いてあったりする

Ruby との文字列操作対応表

参照: 文字列操作の比較表: Ruby, Python, JavaScript, Perl, C++ - bkブログ 基本的に破壊的操作は無し。

Ruby (String)Emacs Lisp注釈
s = “abc”(setq s “abc”)変数にバインドするならlet使う方が一般的
s = x + y(concat x y)
s == x(equal s x)string-equal が正式
s % [x, y]
sprintf(s, x, y)(format s x y)
[x, y, z].join(s)(mapconcat ‘identity (list x y z) s)単に結合するなら (apply ‘concat (list x y z)) もアリ
s.capitalize(capitalize s)
s.capitalize!
s.center(x)(cfw:render-center x s)calfw.el 参照
s.chomp(regexp-in-string “[\n\r]+$” "” s)
s.chomp!
s.chop(substring s 0 -1)改行文字の扱いが違う
s.chop!
s.clear(clear-string s)
s.concat(x)
s.count(x)ループで回すか、一時バッファを使ってre-search-forward で回数を数えるとか
s.crypt(x)rot13-string とか、 EPA とか
EzCrypt
s.delete!(x)(regexp-in-string x "” s)破壊的には行えない
s.downcase(downcase s)
s.downcase!
s.each_byte { x … )(loop for x across s do … )文字単位でループ、xは文字コード(数値)文字コードからstringにするには char-to-string
s.each_line { x … )(loop for x in (split-string s “[\n\r]") do … )
s.empty?(= 0 (length s))nullチェックが必要
s.end_with?(x)(string-match (concat x “$”) s)sに改行が入らないことが前提
s.gsub(x, y)(regexp-in-string x y s)
s.gsub!(x, y)
s.hex
s.to_i(16)(string-to-number s 16)
s.include?(x)(string-match x s)もし見つかれば開始位置が返ってくる
s.index(x)(string-match x s)
s.insert(i, x)一時バッファでやるか、自分でちぎってつなげるか
s.intern
s.to_sym(intern s)シンボルテーブルに登録したくなければ make-symbol
s.length
s.size(length s)文字数が返る
s.ljust(x)(cfw:render-left x s)calfw.el 参照
s.lstrip(regexp-in-string “^[ \t]+” "” s)sに改行が入らないことが前提
s.lstrip!
s.match(x)
x.match(s)(string-match x s)マッチ結果はグローバルに保持している match-string, match-beginning, match-end 等参照
s.next!
s.succ!
s.oct
s.to_i(8)(string-to-number s 8)
s.partition(x)string-matchで自分でちぎる
s.replace(x)
s.reverse(apply ‘string (reverse (append s nil)))
s.reverse!
s.rindex(x)一時バッファで search-backward 等を使う
s.rjust(x)(cfw:render-right x s)calfw.el 参照
s.rpartition(x)string-matchで自分でちぎる
s.rstrip(regexp-in-string “[ \t]+$” "” s)sに改行が入らないことが前提
s.rstrip!
s.scan(x) { … }一時バッファで re-search-forward 等を使う
s[i]
s.slice(i)(aref s i)文字コード(数値)が返ってくる文字コードからstringにするには char-to-string
s[i..-1]
s.slice(i..-1)(substring s i)
s[i, l]
s.slice(i, l)(substring s i (+ i l))
s[i..j]
s.slice(i..j)(substring s i (1+ j))
s[i…j]
s.slice(i…j)(substring s i j)
s.split(x)(split-string s x)
s.start_with?(x)*4(string-match (concat “^” x) s)sに改行が入らないことが前提
s.strip(regexp-in-string “\\(^[ \t\n\r]+\ [ \t\n\r]+$\)” "” s)sに改行が入らないことが前提
s.strip!
s.sub(x, y)string-match を使って自分で初回を取ってくる
s.sub!(x, y)
s.swapcase
s.to_f(string-to-number s)整数に見える場合は整数が返る
s.to_i(string-to-number s)
s.to_s(substring s 0)
s.tr!(x, y)
s.tr_s!(x, y)
s.unpack(x)bindatを使う
s.upcase(upcase s)
s.upcase!
Regexp.escape(s)
Regexp.quote(s)(regexp-quote s)

※もし間違いを見つけたら教えてください。

表示幅について

format の桁指定("%5s” など)で表示幅のパディングが出来るが、あふれた文字列は切り落とされずに全部表示されてしまう。

指定幅で切り取りたい場合は、自分であらかじめ文字列の長さを見て切り落と(truncate)しておく必要がある。

切り落としには substring とかではなくて、全角文字などについても適切に幅を見てくれる truncate-string-to-width を使うと良い感じ。

文字列内の複雑な検索、置換など

replace-regexp-in-string を使えば大抵のことが出来る。関数も取れるので Ruby の gsub 相当も出来る。

ただ、複雑な正規表現をこねくり回したい場合は以下のように一時バッファを使う。

;; input に文字列が入っている
;; output に変換後の文字列を入れたい
(setq output
  (with-temp-buffer
    (insert input)
    (goto-char (point-min))
    (while (re-search-forward REGEXP nil t)
        ;; ここで match-string などを使って何かする
     )
    (buffer-string)))

テキストプロパティについて

HTML の DOM オブジェクトに独自属性を付加するように、自由に属性を入れることが出来る。 HTML とは違って1文字単位でプロパティが設定できるが、大抵はまとまった領域にプロパティを設定する。

テキスト属性を使えるようになると、かなり複雑なアプリケーションが作れるようになる(と思う)。

アプリケーションからの利用

テキストプロパティに必要な値をごっそり入れておき、キー操作でカーソル上の文字列からテキストプロパティを取ってきて必要なアクションを行うというような方法が一般的に行われている。大抵は、行全体や必要な領域全体にプロパティをつけておいて、キー操作で取得するときは行頭の1文字目のプロパティを見るということがよく行われている。

Dired や orgmode など、多くのプログラムがこのような設計になっている。しかしながら、場当たり的にテキストプロパティを使い始めると、テキストプロパティを舞台にしたスパゲティコードになるため、本格的に使う場合はあらかじめ構造化したデータを代表して入れておく等の工夫をした方がよいと考えている。

はまりポイントなど

属性は文字列の操作などでもそのままコピーされてしまうので、欲しくない場合は substring-no-properties などで消す。

属性の操作は破壊的なので、うっかり変えてはいけないところまで変えてしまうことがある。また、ソースコードリテラルの文字列オブジェクトには属性の操作ができない。なので、外から来た文字列に対して属性を操作する場合は、文字列をコピーしておくと安全。

font-lock が有効なバッファで face に何か入れても速攻でクリアされてしまう。そんなときは font-lock-face に入れる。

テキスト属性のループパターン バッファの中の特定のテキストプロパティについてループを書きたいときがある。そんなときは next-property-change または next-single-property-change などを使う。 Info の next-property-change にループのサンプルがある。

以下は face の付いている領域についてループするイディオム。

(defun iterate-text-prop (prop func)
  (loop with pos = (point-min)
        for next = (next-single-property-change pos prop)
        for text-val = (and next (get-text-property next prop))
        while next do
        (when text-val
          (funcall func pos next text-val))
        (setq pos next)))

(iterate-text-prop 'face
  (lambda (begin end val)
    (and val (message ">> %S : %S" val (buffer-substring-no-properties begin end)))))

N オーダーではあるけどもなかなか高速に動作するので、 O(N^2) のアルゴリズムでなければ問題なさそう。