Notepad++には関数補完とコールチップ機能*1があって、その実現にXMLで書かれた設定ファイルを使用している。例えばC言語用のXMLはこんな感じ。
<?xml version="1.0" encoding="WINDOWS-1252" ?> <NotepadPlus> <AutoComplete language="C"> <Environment ignoreCase="no" startFunc="(" stopFunc=")" paramSeparator="," terminal=";" /> <KeyWord name="#define" /> <KeyWord name="#elif" /> <!-- 中略 --> <KeyWord name="abort" func="yes"> <Overload retVal="void" descr="C89: stdlib.h: stops the program"> <Param name="void" /> </Overload> </KeyWord> <!-- 以下略 --> </AutoComplete> </NotepadPlus>
C言語用の設定ファイルはデフォルトで付いているのだが、個人的に気に入らないので*2書き直している。書き直すにあたりKeyWord要素をC言語の規格等で分けて記述している。こうするとデータの過不足を調べたりする時に都合が良いのだが、しかし都合が悪いことにNotepad++側ではKeyWord要素が属性nameの昇順にソートされていないと補完機能等がうまく動作しないようだ。
そこで作成したXMLを読み込み、KeyWord要素を属性nameの順でソートして、整形して出力するツールを作ってみることにした。
Rubyで初めてREXMLを使い*3、当初は真面目にXMLのノードを操作することで実現しようと四苦八苦していたのだが、うまくいかなかったので力技で解決してみた。
#!/usr/bin/ruby -w -Ks # #= Notepad++の関数補完用の設定XMLファイルをキーワード順にソートするツール # #author:: eel3 @ TRASH BOX #date:: 2011/02/24 # #== 動作確認環境 # - ruby 1.8.7 (2010-12-23 patchlevel 330) [i386-mswin32] @ Windows XP Pro SP3 # require 'rexml/document' # XML文書を整形して出力するクラス class XMLWriter # 初期化メソッド。出力先 _output_ を指定すること。 def initialize(output, defindent = 0) @formatter = REXML::Formatters::Pretty.new @output = output @default_indent = ' ' * defindent end # 要素 _elem_ 以下のノードを整形して出力する def write(elem) @output << @default_indent @formatter.write elem, @output @output << "\n" end end # XML文書の文字列 _s_ 整形した文字列を返す def format_xml(s) output = '' XMLWriter.new(output).write REXML::Document.new(s) output end # 設定XMLファイルの先頭部分のみを文字列として返す def npp_xml_header(s) s.select {|i| /^<\?xml/ =~ i || /<NotepadPlus>/ =~ i || /<AutoComplete/ =~ i }.join end # 設定XMLファイルの末尾部分のみを文字列として返す def npp_xml_footer(s) s.select {|i| /<\/AutoComplete/ =~ i || /<\/NotepadPlus>/ =~ i }.join end # 設定XMLファイルの子要素をソートして、結果を文字列として返す def npp_sorted_childs(s) doc = REXML::Document.new(s) output = '' writer = XMLWriter.new(output) doc.root.elements[1].elements.to_a.sort {|a, b| if a.fully_expanded_name != b.fully_expanded_name a.fully_expanded_name <=> b.fully_expanded_name else a.attributes.get_attribute('name').value <=> b.attributes.get_attribute('name').value end }.each {|elem| writer.write elem } output end # メインルーチン def main src_xml = readlines.join tmp_xml = npp_xml_header(src_xml) tmp_xml << npp_sorted_childs(src_xml) tmp_xml << npp_xml_footer(src_xml) print format_xml(tmp_xml) end main
まず第一段階として、KeyWord要素を設定ファイルの事実上のリーフに位置する要素と見なして、それより上位の要素を単純なテキスト処理で取り扱い、KeyWord要素と同じ階層の要素のみをXMLとしてREXMLを使って操作している。
第一段階でソートされたXML文書が文字列として生成されるが、この状態では正しくインデントされていない。改めてREXMLでXMLとして取り扱い、REXML::Formattersでお気楽に整形出力している。
設定ファイルを行単位で2回も舐めたり、REXMLで2回もXML文書を読み込んだりと、どう見ても力技でしかないのだが、手元で使う分には特に不満はない。というか総データ量の少なさとマシンスペックで誤魔化されてしまう所が恐ろしい。
とはいえ個人的に納得いかないので書き換えるつもりだ。