XML (HTML) の特殊文字をエスケープするVimスクリプトを書いた

スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

XML (HTML) の特殊文字をエスケープするVimスクリプトを書いた

XMLやHTMLの5つの特殊文字 (&, <, >, ', ") を定義済みのエンティティ (&amp;, &lt;, &gt;, &apos;, &quot;) に変換するVimスクリプトを書いてみた。

5つの文字を置換するだけだから簡単そうに思えるけど、選択範囲だけを対象にしようとするとなかなか難しくて、今まで納得のいくものを作れずにいた。それが、たまたま今日挑戦してみたら、そこそこいい感じのスクリプトが書けたので、記念にブログポストしてみる。

vnoremap <Leader>e "xx:call <SID>EscapeXml('x')<CR>"xP

function s:EscapeXml(regname)
  let x = getreg(a:regname)
  let x = substitute(x, '&', '\&amp;', 'g')
  let x = substitute(x, '<', '\&lt;', 'g')
  let x = substitute(x, '>', '\&gt;', 'g')
  let x = substitute(x, "'", '\&apos;', 'g')
  let x = substitute(x, '"', '\&quot;', 'g')
  call setreg(a:regname, x)
endfunction

使い方やコードの解説は続きで。

使い方

前述のコードをvimrcもしくはFiletype Pluginの中に貼り付けます。

Filetype Pluginに貼り付ける場合は、マッピングの設定を

vnoremap <buffer> <LocalLeader>e "xx:call <SID>EscapeXml('x')<CR>"xP

にした方がいいでしょう。

VimでXMLファイルを開いたら、特殊文字をエスケープしたい部分を選択し、

Select target area / Vim Screenshot

\e

を入力します (マップリーダーがデフォルトのバックスラッシュの場合)。

Type \e / Vim Screenshot

選択箇所が複数行にまたがった場合や、行選択モード (<S-V>) で選択した場合でも問題なく変換することができます。ただし、矩形選択モード (<C-V>) で選択した場合は正しく動作しません。

コード解説

"xx:call <SID>EscapeXml('x')<CR>"xP
  1. 選択領域を削除し、内容を x レジスタに保存する ("xx)

  2. 利用するレジスタの名前 ('x') を引数として、s:EscapeXml()関数を呼び出す (:call <SID>EscapeXml('x')<CR>)

    s:EscapeXml()関数では、引数で指定されたレジスタから値を取得し (let x = getreg(a:regname))、substitute()関数でXML特殊文字をすべて置換し (let x = substitute(x, ...) た後に、置換結果をレジスタに戻す (call setreg(a:regname, x))。

  3. s:EscapeXml()関数で書き換えた x レジスタの内容を、改めてバッファにペーストする ("xP)

試行錯誤の跡

↑のスクリプトを作るときに、僕がはじめに書いた関数が↓だった。

function EscapeXml()
  s/&/\&amp;/eg
  s/</\&lt;/eg
  s/>/\&gt;/eg
  s/'/\&apos;/eg
  s/"/\&quot;/eg
endfunction

この関数では望む動作をしないことは明らかで、↓のように選択して関数を呼び出しても、

Select target area / Vim Screenshot

選択範囲を含む行全てが対象になって↓のような結果になってしまう。

Too much / Vim Screenshot

ということで、次に書いたのが↓のような関数。

function EscapeXml()
  s/\%V&/\&amp;/eg
  s/\%V</\&lt;/eg
  s/\%V>/\&gt;/eg
  s/\%V'/\&apos;/eg
  s/\%V"/\&quot;/eg
endfunction

選択したテキストにマッチするアトム (\%V) を使ってみたもの。

これでうまく行くかと思っていたが、同様に↓のように選択して関数を呼び出してみたら、

Select target area / Vim Screenshot

↓のような結果になってしまった。理由は、置換によって選択されたテキストが長くなっても、それに伴って選択範囲が拡張されないため。

Not enough / Vim Screenshot

最終的には、xmleditというプラグインで使われていたアプローチを採用して、選択範囲を削除して内容をレジスタに送ってみることにした。で、書きあがったのが↓。

vnoremap <buffer> <LocalLeader>e "xx:call <SID>EscapeXml()<CR>"xP

function s:EscapeXml()
  let @x = substitute(@x, '&', '\&amp;', 'g')
  let @x = substitute(@x, '<', '\&lt;', 'g')
  let @x = substitute(@x, '>', '\&gt;', 'g')
  let @x = substitute(@x, "'", '\&apos;', 'g')
  let @x = substitute(@x, '"', '\&quot;', 'g')
endfunction

↑を元に、利用するレジスタ名を引数で指定できるように改良したのが、冒頭で紹介した完成版のスクリプト。

本当は、関数単独で使えるような形にしたかったんだけど、結局マッピングと組み合わせないと目的の機能は実現できなかった。もう少し改良の余地があるかも。

スポンサーサイト

関連記事

トラックバック URL

http://liosk.blog103.fc2.com/tb.php/187-f3187f34

トラックバック

コメント

コメントの投稿

お名前
コメント
編集キー
 
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。