レベル: 中級 Santhosh Krishnamoorthy, Staff Software Engineer, IBM
2009年 8月 18日 Ruby は充実した機能を備えた、拡張も移植も可能な無料のオブジェクト指向スクリプト言語です。強力なテキスト処理言語として、Ruby は計り知れない力を秘めています。その強力な組み込みライブラリー、そして一連の外部ライブラリーを組み合わせて使うことで、Ruby は考え得るあらゆる日常的なテキスト処理タスクのソリューションとして有望な選択肢となります。
Perl や Python と同じ目線で見ると、Ruby には強力なテキスト処理言語となるに値する優れた機能が備わっています。この記事では Ruby のテキスト・データ処理機能について概説した後、Ruby を利用して多様なテキスト・データ (それが CSV データであるか XML データであるかに関わらず) を効率的に処理する方法を説明します。
Ruby の文字列
 | よく使われる頭字語
- CSV: Comma Separated Values
- REXML: Ruby Electric XML
- XML: Extensible Markup Language
|
|
Ruby の文字列 (String) は、テキスト・データを保持、比較、操作するのに有効な手段です。Ruby では、String::new を呼び出すか、単にリテラル値を割り当てるだけで String クラスをインスタンス化することができます。
文字列に値を割り当てる際には、単一引用符 (') または二重引用符 (") の対を使って値を囲むことができます。単一引用符と二重引用符のどちらを使用するかによって、文字列の指定方法にいくつかの違いが出てきます。二重引用符では、バックスラッシュ (\) で始まるエスケープ・シーケンスを使用できるだけでなく、#{} 演算子を使って文字列に含まれる式を評価することもできます。一方、単一引用符で囲んだ文字列は、単純なそのままのリテラルとなります。
リスト 1 に、一例を示します。
リスト 1. Ruby 文字列を扱う: 文字列の定義
message = 'Heal the World…'
puts message
message1 = "Take home Rs #{100*3/2} "
puts message1
Output :
# ./string1.rb
# Heal the World…
# Take home Rs 150
|
上記の最初の文字列は、単一引用符の対で定義されています。2 番目の文字列では二重引用符の対を使用しているため、この文字列は #{} 内の式が評価されてから表示されます。
文字列を定義するには、もう 1 つ便利な方法があります。この方法は通常、複数の行からなる文字列を定義する場合に用いられます。
ここからは、対話型 Ruby コンソール、irb>> を使用して説明します。このコンソールは、Ruby のインストール時にインストールされているはずです。インストールされていない場合は、是非とも irb Ruby gem を入手して、インストールしてください。Ruby コンソールは、Ruby とそのモジュールを学ぶためのツールとして大いに役立ちます。インストール後は、irb>> コマンドを使って実行することができます。
リスト 2. Ruby 文字列を扱う: 複数行にわたる文字列の定義
irb>> str = >>EOF
irb>> "hello world
irb>> "how do you feel?
irb>> "how r u ?
irb>> EOF
"hello, world\nhow do you feel?\nhow r u?\n"
irb>> puts str
hello, world
how do you feel?
how r u?
|
リスト 2 では、\n (改行) 文字を含め、>>EOF から EOF までのすべての文字が文字列に含まれるとみなされます。
Ruby の String クラスには、保存されたデータの操作および処理に使用できるメソッドが充実して揃っています。以降のリスト 3、リスト 4、リスト 5 で、そのうちのいくつかを抜粋して説明します。
リスト 3. Ruby 文字列を扱う: 文字列の連結
irb>> str = "The world for a horse" # String initialized with a value
The world for a horse
irb>> str*2 # Multiplying with an integer returns a
# new string containing that many times
# of the old string.
The world for a horseThe world for a horse
irb>> str + " Who said it ? " # Concatenation of strings using the '+' operator
The world for a horse Who said it ?
irb>> str<<" is it? " # Concatenation using the '<<' operator
The world for a horse is it?
|
サブストリングを抽出し、文字列の一部を操作する
リスト 4. Ruby 文字列を扱う: 抽出および操作
irb>> str[0] # The '[]' operator can be used to extract substrings, just
# like accessing entries in an array.
# The index starts from 0.
84 # A single index returns the ascii value
# of the character at that position
irb>> str[0,5] # a range can be specified as a pair. The first is the starting
# index , second is the length of the substring from the
# starting index.
The w
irb>> str[16,5]="Ferrari" # The same '[]' operator can be used
# to replace substrings in a string
# by using the assignment like '[]='
irb>>str
The world for a Ferrari
Irb>> str[10..22] # The range can also be specified using [x1..x2]
for a Ferrari
irb>> str[" Ferrari"]=" horse" # A substring can be specified to be replaced by a new
# string. Ruby strings are intelligent enough to adjust the
# size of the string to make up for the replacement string.
irb>> s
The world for a horse
irb>> s.split # Split, splits the string based on the given delimiter
# default is a whitespace, returning an array of strings.
["The", "world", "for", "a", "horse"]
irb>> s.each(' ') { |str| p str.chomp(' ') }
# each , is a way of block processing the
# string splitting it on a record separator
# Here, I use chomp() to cut off the trailing space
"The"
"world"
"for"
"a"
"horse"
|
Ruby の String クラスには上記の他にも、例えば大文字/小文字の置換、文字列長の取得、レコード・セパレーターの除去、文字列のスキャン、文字列の暗号化と暗号化解除などの操作を行う数多くの実用的なメソッドがあります。さらに、文字列を変更不可能にする freeze という便利なメソッドもあります。このメソッドを文字列 str で呼び出すと (str.freeze)、str は変更できなくなります。
Ruby にはデストラクターと呼ばれるメソッドもあります。これは、文字列を恒久的に変更する、感嘆符 (!) で終わるメソッドのことです。標準メソッド (末尾に感嘆符がないメソッド) を文字列で呼び出した場合、メソッドが変更して返すのは文字列のコピーですが、感嘆符で終わるメソッドは文字列自体を変更します。
リスト 5. Ruby 文字列を扱う: 文字列の恒久的変更
irb>> str = "hello, world"
hello, world
irb>> str.upcase
HELLO, WORLD
irb>>str # str, remains as is.
Hello, world
irb>> str.upcase! # here, str gets modified by the '!' at the end of
# upcase.
HELLO, WORLD
irb>> str
HELLO, WORLD
|
リスト 5 では、str 内の文字列は upcase! メソッドによって変更されますが、単なる upcase メソッドだと、大文字に置換された文字列のコピーが返されます。これらの ! メソッドは、場合によっては非常に重宝します。
Ruby の String は極めて有力です。String オブジェクトにいったんデータを取り込めば、あとは豊富なメソッドを意のままに使って、至って簡単かつ効率的にデータを処理することができます。
CSV ファイルの操作
CSV ファイルは表形式のデータを表現する極めて一般的な方法で、スプレッドシートからエクスポートしたデータ (連絡先とその詳細情報のリストなど) の形式として最もよく使用されています。
Ruby には、このようなファイルの操作と処理に力を発揮するライブラリーがあります。それが、CSV ファイルを扱う Ruby モジュール csv です。このモジュールには、CSV のようなファイルの作成、読み取り、構文解析を行うメソッドが用意されています。
リスト 6 の例で、CSV ファイルを作成した後に、Ruby の csv モジュールを使ってファイルを構文解析する方法を説明します。
リスト 6. CSV ファイルを扱う: CSV ファイルの作成と構文解析
require 'csv'
writer = CSV.open('mycsvfile.csv','w')
begin
print "Enter Contact Name: "
name = STDIN.gets.chomp
print "Enter Contact No: "
num = STDIN.gets.chomp
s = name+" "+num
row1 = s.split
writer << row1
print "Do you want to add more ? (y/n): "
ans = STDIN.gets.chomp
end while ans != "n"
writer.close
file = File.new('mycsvfile.csv')
lines = file.readlines
parsed = CSV.parse(lines.to_s)
p parsed
puts ""
puts "Details of Contacts stored are as follows..."
puts ""
puts "-------------------------------"
puts "Contact Name | Contact No"
puts "-------------------------------"
puts ""
CSV.open('mycsvfile.csv','r') do |row|
puts row[0] + " | " + row[1]
puts ""
end
|
リスト 7 に、上記による出力を記載します。
リスト 7. CSV ファイルを扱う: CSV ファイルの作成および構文解析による出力
Enter Contact Name: Santhosh
Enter Contact No: 989898
Do you want to add more ? (y/n): y
Enter Contact Name: Sandy
Enter Contact No: 98988
Do you want to add more ? (y/n): n
Details of Contacts stored are as follows...
---------------------------------
Contact Name | Contact No
---------------------------------
Santhosh | 989898
Sandy | 98988
|
この例の内容を以下に概略します。
まず、csv モジュールを組み込みます (require 'csv')。
mycsvfile.csv という名前の新規 CSV ファイルを作成するために、CSV.open() を呼び出してファイルを開きます。これによって、ライター・オブジェクトが返されます。
この例で作成するのは、個人の名前とその電話番号からなる単純な連絡先リストを保持する CSV ファイルです。ループでは、ユーザーが連絡先の名前と電話番号を入力するように求められます。名前と電話番号は 1 つの文字列に連結された後、2 つに分割されて文字列の配列になります。この配列がライター・オブジェクトに渡されて、CSV ファイルに書き込まれます。その結果、CSV 値の対がそれぞれ 1 つの行としてファイルに格納されます。
ループを抜けると、すべて完了です。この時点でライターをクローズし、ファイル内のデータを保存します。
次のステップは、作成された CSV ファイルの構文解析です。
ファイルを開いて構文解析する 1 つの方法は、この新しい CSV ファイルの名前を使用した File オブジェクトを新規に作成することです。
readlines メソッドを呼び出して、ファイル内のすべての行を lines という名前の配列に読み込みます。
lines.to_s を呼び出して lines 配列を String オブジェクトに変換し、この文字列を CSV.parse メソッドに渡します。すると、CSV データが構文解析されて、その内容が配列の配列として返されます。
これに続いて、ファイルを開いて構文解析するもう 1 つの方法も示されています。今度は読み取りモードで CSV.open を呼び出してファイルを開きます。すると行の配列が返されるので、何らかのフォーマット設定を使用して各行を出力し、連絡先の詳細を表示します。この場合、それぞれの行はファイル内での 1 行に対応します。
このように、Ruby は CSV ファイルおよびデータを扱うのにも強力なモジュールを提供します。
XML ファイルを扱う
Ruby には、XML ファイルを扱うための強力な組み込みライブラリー REXML があります。XML 文書の読み取りと構文解析には、このライブラリーを使用することができます。
Ruby と REXML を使って、以下に記載するサンプル XML ファイルを構文解析してみてください。
これは、オンライン・ショッピング・モールの典型的なショッピング・カートの内容を記載する単純な XML ファイルで、要素には以下のものがあります。
cart – ルート要素
user – 買い物をしているユーザー
item – ユーザーが自分のカートに追加した商品
id、price、quantity – item のサブ要素
リスト 8 に、この XML の構造を示します。
リスト 8. XML ファイルを扱う: サンプル XML ファイル
<cart id="userid">
<item code="item-id">
<price>
<price/unit>
</price>
<qty>
<number-of-units>
</qty>
</item>
</cart>
|
このサンプル XML ファイルは、「ダウンロード」セクションから入手することができます。ここで、この XML ファイルをロードして、REXML を使用したツリーで構文解析します。
リスト 9. XML ファイルを扱う: XML ファイルの構文解析
require 'rexml/document'
include REXML
file = File.new('shoppingcart.xml')
doc = Document.new(file)
root = doc.root
puts ""
puts "Hello, #{root.attributes['id']}, Find below the bill generated for your purchase..."
puts ""
sumtotal = 0
puts "-----------------------------------------------------------------------"
puts "Item\t\tQuantity\t\tPrice/unit\t\tTotal"
puts "-----------------------------------------------------------------------"
root.each_element('//item') { |item|
code = item.attributes['code']
qty = item.elements["qty"].text.split(' ')
price = item.elements["price"].text.split(' ')
total = item.elements["price"].text.to_i * item.elements["qty"].text.to_i
puts "#{code}\t\t #{qty}\t\t #{price}\t\t #{total}"
puts ""
sumtotal += total
}
puts "-----------------------------------------------------------------------"
puts "\t\t\t\t\t\t Sum total : " + sumtotal.to_s
puts "-----------------------------------------------------------------------"
|
上記による出力は、リスト 10 のとおりです。
リスト 10. XML ファイルを扱う: XML ファイルの構文解析による出力
Hello, santhosh, Find below the bill generated for your purchase...
-------------------------------------------------------------------------
Item Quantity Price/unit Total
-------------------------------------------------------------------------
CS001 2 100 200
CS002 5 200 1000
CS003 3 500 1500
CS004 5 150 750
-------------------------------------------------------------------------
Sum total : 3450
--------------------------------------------------------------------------
|
リスト 9 の例では、ショッピング・カートの XML ファイルを構文解析し、商品ごとの小計と購入総額で請求書を生成しています (リスト 10)。
以下に、この例の内容を簡単に説明します。
まず、Ruby の REXML モジュールを組み込みます。このモジュールには、XML ファイルを構文解析するためのメソッドがあります。
shoppingcart.xml ファイルを開き、このファイルから Document オブジェクトを作成します。この Document オブジェクトが、構文解析された XML ファイルを含めるオブジェクトとなります。
文書のルートを要素オブジェクト root に割り当てます。これで、文書のルートは XML内の cart タグを指すことになります。
それぞれの要素オブジェクトには、属性オブジェクトがあります。このオブジェクトは要素の属性名とその値のハッシュで構成され、属性名がキー、属性の値がキーの値となります。ここでは、root.attributes['id'] が root 要素の id 属性の値を提供します。この例の場合、値は userid です。
次に、sumtotal を 0 に初期化し、ヘッダーを出力します。
各要素オブジェクトには、elements という名前のオブジェクトもあります。そのサブ要素には、each および [] メソッドでアクセスします。このブロックは root 要素のサブ要素である item をすべて処理します。これを指定しているのは、//item という XPath 式です。各要素オブジェクトには、その要素のテキスト値を保持する text 属性もあります。
続いて item 要素の code 属性、そして price 要素と qty 要素のテキスト値を取得し、商品ごとの小計を計算します。そして詳細を請求書に出力し、さらに商品の小計を sumtotal に追加します。
最後に、総額を出力します。
以下の例から、REXML と Ruby では簡単かつ単純に XML ファイルを構文解析できることがわかるはずです。XML ファイルを即座に生成するのも、要素とその属性を追加、削除するのもわけありません。
リスト 11. XML ファイルを扱う: XML ファイルの生成
doc = Document.new
doc.add_element("cart1", {"id" => "user2"})
cart = doc.root.elements[1]
item = Element.new("item")
item.add_element("price")
item.elements["price"].text = "100"
item.add_element("qty")
item.elements["qty"].text = "4"
cart .elements << item
|
リスト 11 に記載したスニペットは、cart 要素と item 要素およびそのサブ要素を作成することで、XML 構造を作成します。そして、これらの要素に値を取り込んだ上で、Document ルートに追加します。
同様に、要素と属性を削除するには Element オブジェクトの delete_element メソッドと delete_attribute メソッドをそれぞれ使用します。
上記の例は、ツリー方式の構文解析と呼ばれるものですが、XML 文書を構文解析する方法としてはストリーム方式の構文解析もあります。ストリーム方式の構文解析はツリー方式よりも早く処理が終わるため、速度が重視される場合には、ストリーム方式の構文解析を使用することができます。ストリーム方式の構文解析はイベントをベースであり、リスナーと連動します。タグが検出されるとリスナーが呼び出され、リスナーが処理を行うという仕組みです。
リスト 12 に、一例を示します。
リスト 12. XML ファイルを扱う: ストリーム方式の構文解析
require 'rexml/document'
require 'rexml/streamlistener'
include REXML
class Listener
include StreamListener
def tag_start(name, attributes)
puts "Start #{name}"
end
def tag_end(name)
puts "End #{name}"
end
end
listener = Listener.new
parser = Parsers::StreamParser.new(File.new("shoppingcart.xml"), listener)
parser.parse
|
リスト 13 に、上記による出力を記載します。
リスト 13. XML ファイルを扱う: ストリーム方式の構文解析による出力
Start cart
Start item
Start price
End price
Start qty
End qty
End item
Start item
Start price
End price
Start qty
End qty
End item
Start item
Start price
End price
Start qty
End qty
End item
Start item
Start price
End price
Start qty
End qty
End item
End cart
|
以上のように、REXML と Ruby の強力な組み合わせにより、XML データを極めて効率的に、そして直観的に処理、操作することができます。
まとめ
Ruby には、迅速かつ強力で効率的なテキスト処理を可能にする充実した組み込みライブラリーと外部ライブラリーが揃っています。この機能を利用すれば、必要となるさまざまなテキスト・データ処理を単純化し、強化することができます。この記事では、Ruby に備わったテキスト・データ処理機能のいくつかの側面に触れただけに過ぎません。Ruby には、これより遙かに多くの機能があります。
Ruby がツール・ボックスに欠かせない素晴らしいツールであることに、疑問の余地はありません。
参考文献 学ぶために
製品や技術を入手するために
議論するために
著者について  | 
|  | Santhosh Krishnamoorthy は、インドのバンガロールにある IBM Software Labs の TXSeries チームで、テスト・エンジニアとしてシステム間通信および Java 技術の分野に取り組んでいます。彼は、Ruby、Ruby on Rails、Python プログラミングに興味を持っています。 |
記事の評価
|