id:eel3:20110225:1298647360 に載せたコードは個人的に「これはいかがなものか」と問い質したくなる代物だったので、全面的に書き直してみることにした。
前回の反省を踏まえて、こんな方針で攻めてみた。
ちなみに、色々あって昨年購入した『Scheme手習い』を序盤で放り出したままなのにも関わらず、微妙に影響を受けたコードになっている気がする*1。
#!/usr/bin/ruby -w -Ks # #= Notepad++の関数補完用の設定XMLファイルをキーワード順にソートするツール # #author:: eel3 @ TRASH BOX #date:: 2011/02/25 # #== 動作確認環境 # - ruby 1.8.7 (2010-12-23 patchlevel 330) [i386-mswin32] @ Windows XP Pro SP3 # require 'rexml/document' # 要素 _elem_ の要素名、属性、属性値をダンプした文字列を返す def dump(elem) str = '' str << elem.fully_expanded_name elem.attributes.each_attribute do |attr| str << " #{attr.to_string.gsub(/'/, '"')}" end str end # 要素 _elem_ の子要素をソートした配列を返す def sort_childs(elem) elem.elements.to_a.sort {|a, b| if a.fully_expanded_name != b.fully_expanded_name a.fully_expanded_name <=> b.fully_expanded_name elsif a.fully_expanded_name == 'KeyWord' a.attributes.get_attribute('name').value <=> b.attributes.get_attribute('name').value else 0 # XXX: do not sort order end } end # 要素 _elem_ を含むXMLノードを整形した文字列を返す def format_xml(elem, recurs_lv = 0) indent = "\t" * recurs_lv if elem == nil '' elsif elem.has_elements? "#{indent}<#{dump(elem)}>\n"\ "#{sort_childs(elem).collect {|i| format_xml(i, recurs_lv + 1) }.join}"\ "#{indent}</#{elem.fully_expanded_name}>\n" else "#{indent}<#{dump(elem)} />\n" end end # XML宣言 _xml_decl_ を整形した文字列を返す def format_decl(xml_decl) output = '' xml_decl.write output output.gsub(/'/, '"') << "\n" end # メインルーチン def main doc = REXML::Document.new(readlines.join) print format_decl(doc.xml_decl) print format_xml(doc.root) end main
属性値がシングルクォートで括られているのが少々気に入らないので、ダブルクォートにしている。実は属性の順番が元のXMLと違う点も気になるのだが、これといった解決策は思いついていない。
物凄く細かい話だが、書き方で1点だけ気になっている所がある。メソッド sort_childs の中で配列をソートしている所のブロックをdo ... endにするか{ ... }にするかだ。というのも複数回呼び出されるブロックなのでdo ... endにするべきかと思う反面、ソートした結果をメソッドの戻り値として返すので{ ... }の方が適当な気もするのだ。sortの戻り値を使わないのなら迷わずdo ... endで書くのだが。