文字列操作ライブラリ

文字列操作用にいくつかの関数を追加してみました。

使い方

下記のスクリプトをstring.luaを名前で保存する。
それを、system.luaの中から読み出す。

NL_dofile("string.lua")

そうすると、stringに色々と新しい関数が追加されて便利になる。

string.lua

-- string.lua
-- 文字列操作について、いささかの増強をする。
do
	-- 文字コードを入れると、
	-- それが半角文字であればtrueを、そうでなければfalseを返す。
	local code_decision = function(num)
		if num < 127 then return true  end
		if num < 161 then return false end
		if num < 224 then return true  end
		return false
	end
	
	-- 文字コードを入れると、
	-- Shift-JISの漢字であればtrueを、そうでなければfalseを返す。
	local kanji_decision = function(num)
		-- そもそも半角じゃ漢字じゃない。
		if code_decision(num) then return false end
		return num > 136
	end

	-- 文字列の先頭の一文字を返す。
	string.initial = function(str)
		if str:len()==0 then return "" end
		if str:len()==1 then return str end
		if code_decision(str:byte()) then return str:sub(1, 1) end
		return str:sub(1, 2)
	end

	-- 文字列の先頭が漢字かどうかを判別する。
	string.is_kanji = function(str)
		if str:len() == 0 then return nil end
		local test = str
		if test:len() > 2 then test = str:initial() end
		return kanji_decision(test:byte())
	end

	-- 文字列を文字単位に分解する。返り値はテーブル。
	string.separate_table = function(str)
		if str:len()==0 then return {} end -- 空文字列は何もしない。
		local res = {} -- 回答用
		local byte = {str:byte(1, str:len())} -- コードの配列にする。
		local code
		while 0 < #byte do
			code = table.remove(byte, 1) -- 一文字取ってくる。
			if code_decision(code) then -- 半角文字だった。
				res[1+#res] = string.char(code)
			else -- 全角文字だった。
				res[1+#res] = string.char(code, table.remove(byte, 1))
			end
		end
		return res
	end
	
	-- 文字列を文字単位に分解する。返り値はバラで。
	string.separate = function(str)
		return unpack(str:separate_table())
	end
	
	-- 文字列の先頭に指定された文字を追加していく。
	-- 引数は必要な桁数と、埋めるのに使う文字
	string.fill = function(str, digit, letter)
		-- digitは数値である必要がある。
		if type(digit)~="number" then return str end -- 数値でなければ何もしない。
		-- 後何文字必要かを計算する。
		local last = digit - str:len()
		if last < 1 then return str end -- 一文字未満であれば、何もしない。
 		-- letterは文字である必要がある。
 		if type(letter)~="string" then letter = " " end -- 文字にする。
 		if letter:len()==0 then letter = " " end -- 必ず一文字以上にする。
 		letter = letter:initial()
 
		local count = 0 -- repの実行回数。
		while last > 0 do
			last = last - letter:len()
			count = count + 1
		end
		return letter:rep(count)..str
	end
end
do
	-- 変換テーブル
	local han2zen_table = {
		[" "]=" ",
		["!"]="!",
		["\""]="”",
		["#"]="#",
		["$"]="$",
		["%"]="%",
		["&"]="&",
		["'"]="’",
		["("]="(",
		[")"]=")",
		["*"]="*",
		["+"]="+",
		[","]=",",
		["-"]="−",
		["."]=".",
		["/"]="/",
		["0"]="0",
		["1"]="1",
		["2"]="2",
		["3"]="3",
		["4"]="4",
		["5"]="5",
		["6"]="6",
		["7"]="7",
		["8"]="8",
		["9"]="9",
		[":"]=":",
		[";"]=";",
		["<"]="<",
		["]="]="=",
		[">"]=">",
		["?"]="?",
		["@"]="@",
		["A"]="A",
		["B"]="B",
		["C"]="C",
		["D"]="D",
		["E"]="E",
		["F"]="F",
		["G"]="G",
		["H"]="H",
		["I"]="I",
		["J"]="J",
		["K"]="K",
		["L"]="L",
		["M"]="M",
		["N"]="N",
		["O"]="O",
		["P"]="P",
		["Q"]="Q",
		["R"]="R",
		["S"]="S",
		["T"]="T",
		["U"]="U",
		["V"]="V",
		["W"]="W",
		["X"]="X",
		["Y"]="Y",
		["Z"]="Z",
		["["]="[",
		["\\"]="¥",
		["]"]="]",
		["^"]="^",
		["_"]="_",
		["`"]="‘",
		["a"]="a",
		["b"]="b",
		["c"]="c",
		["d"]="d",
		["e"]="e",
		["f"]="f",
		["g"]="g",
		["h"]="h",
		["i"]="i",
		["j"]="j",
		["k"]="k",
		["l"]="l",
		["m"]="m",
		["n"]="n",
		["o"]="o",
		["p"]="p",
		["q"]="q",
		["r"]="r",
		["s"]="s",
		["t"]="t",
		["u"]="u",
		["v"]="v",
		["w"]="w",
		["x"]="x",
		["y"]="y",
		["z"]="z",
		["{"]="{",
		["|"]="|",
		["}"]="}",
		["~"]="〜",
		["。"]="。",
		["「"]="「",
		["」"]="」",
		["、"]="、",
		["・"]="・",
		["ヲ"]="ヲ",
		["ァ"]="ァ",
		["ィ"]="ィ",
		["ゥ"]="ゥ",
		["ェ"]="ェ",
		["ォ"]="ォ",
		["ャ"]="ャ",
		["ュ"]="ュ",
		["ョ"]="ョ",
		["ッ"]="ッ",
		["ー"]="ー",
		["ア"]="ア",
		["イ"]="イ",
		["ウ"]="ウ",
		["エ"]="エ",
		["オ"]="オ",
		["カ"]="カ",
		["キ"]="キ",
		["ク"]="ク",
		["ケ"]="ケ",
		["コ"]="コ",
		["サ"]="サ",
		["シ"]="シ",
		["ス"]="ス",
		["セ"]="セ",
		["ソ"]="ソ",
		["タ"]="タ",
		["チ"]="チ",
		["ツ"]="ツ",
		["テ"]="テ",
		["ト"]="ト",
		["ナ"]="ナ",
		["ニ"]="ニ",
		["ヌ"]="ヌ",
		["ネ"]="ネ",
		["ノ"]="ノ",
		["ハ"]="ハ",
		["ヒ"]="ヒ",
		["フ"]="フ",
		["ヘ"]="ヘ",
		["ホ"]="ホ",
		["マ"]="マ",
		["ミ"]="ミ",
		["ム"]="ム",
		["メ"]="メ",
		["モ"]="モ",
		["ヤ"]="ヤ",
		["ユ"]="ユ",
		["ヨ"]="ヨ",
		["ラ"]="ラ",
		["リ"]="リ",
		["ル"]="ル",
		["レ"]="レ",
		["ロ"]="ロ",
		["ワ"]="ワ",
		["ン"]="ン",
		["゙"]="゛",
		["゚"]="゜",
		["ガ"]="ガ",
		["ギ"]="ギ",
		["グ"]="グ",
		["ゲ"]="ゲ",
		["ゴ"]="ゴ",
		["ザ"]="ザ",
		["ジ"]="ジ",
		["ズ"]="ズ",
		["ゼ"]="ゼ",
		["ゾ"]="ゾ",
		["ダ"]="ダ",
		["ヂ"]="ヂ",
		["ヅ"]="ヅ",
		["デ"]="デ",
		["ド"]="ド",
		["バ"]="バ",
		["パ"]="パ",
		["ビ"]="ビ",
		["ピ"]="ピ",
		["ブ"]="ブ",
		["プ"]="プ",
		["ベ"]="ベ",
		["ペ"]="ペ",
		["ボ"]="ボ",
		["ポ"]="ポ"
	}
	
	-- 半角文字に変換。テーブル化
	string.han2zen_table = function(str)
		if str:len()==0 then return {} end
		local t = str:separate_table() -- 文字毎に分割
		local res = {}
		local letter1 = nil
		local letter2 = nil
		while 0<#t do
			letter1 = table.remove(t, 1) -- 先頭の文字を取得
			if 0<#t then letter2 = letter1..t[1] else letter2 = "" end
			if han2zen_table[letter2] then
				res[1+#res] = han2zen_table[letter2]
				table.remove(t, 1)
			elseif han2zen_table[letter1] then
				res[1+#res] = han2zen_table[letter1]
			else
				res[1+#res] = letter1
			end
		end
		return res
	end
	
	-- 半角文字を全角文字に変換
	string.han2zen = function(str) return table.concat(str:han2zen_table()) end
	
	local zen2han_table = {}
	for k, v in pairs(han2zen_table) do
		zen2han_table[v] = k
	end
	
	-- 全角文字を半角に変換
	string.zen2han_table = function(str)
		local t = str:separate_table()
		local res = {}
		local letter
		while 0<#t do
			letter = table.remove(t, 1)
			res[1+#res] = zen2han_table[letter] or letter
		end
		return res
	end

	-- 全角を半角に変換
	string.zen2han = function(str) return table.concat(str:zen2han_table()) end
	
	-- 数値の構成要素として許可できる文字
	local digit_list = {
		["0"] = true,
		["1"] = true,
		["2"] = true,
		["3"] = true,
		["4"] = true,
		["5"] = true,
		["6"] = true,
		["7"] = true,
		["8"] = true,
		["9"] = true,
		["."] = true,
		["-"] = true
	}

	-- 全角混じり文字列を数値に変換
	string.tonumber = function(str)
		local t = str:zen2han_table()
		local res = {}
		for i, v in ipairs(t) do if digit_list[v] then res[1+#res] = v end end
		return tonumber(table.concat(res))
	end

	-- 文字列を単純に特定の文字で区切る。返り値はテーブル
	-- csvのパースには使えませんよ?
	-- 区切り文字は一文字である必要がある。そうでなければ、先頭の一文字が使われる。
	string.split_table_infinity = function(str, separater)
		if str:len()==0 then return {""} end -- ショートカット
		local t = str:separate_table() -- まずは文字単位に分割
		if separater:len()==0 then return t end
		local res = {""} -- 回答用テーブル
		local letter = nil -- 一時変数
		while 0<#t do -- tが存在し続ける限り回す。
			letter = table.remove(t, 1) -- tの最初の一つを取得
			if letter == separater then
				res[1+#res] = "" -- 文字列を追加
			else
				res[#res] = res[#res]..letter -- 最後の文字列に追加
			end
		end
		return res
	end
	
	string.split_table = function(str, separater, max)
		-- セパレータのデフォルト設定
		if type(separater)~="string" then separater="" end
		separater = separater:initial() -- 先頭だけが有効。
		local t = str:split_table_infinity(separater) -- とりあえず、無制限分割
		if type(max)~="number" then return t end -- maxがなければ終了
		if max<1 then return t end -- maxが1未満なら、分割制限なし扱い。終了。
		max = math.floor(max) -- 念のため、端数切捨て。
		if max==1 then return str end -- maxが1なら、分割しないの意味。
		-- それ以外なら、分割制限をする。
		local letter = nil
		while max<#t do
			letter = table.remove(t) -- 最後を削る。
			t[#t] = t[#t]..separater..letter -- 新しい最後に加える。
		end
		return t
	end
	
	-- 文字列を単純に特定の文字で区切る。返り値はバラで。
	string.split = function(str, separater, max)
		return unpack(str:split_table(separater, max))
	end

	-- メタ文字用の変換テーブルの作成
	local chara_code = {}
	for i=0, 255 do chara_code[i] = string.char(i) end
	local temp = "^$()%.[]*+-?"
	for i, v in ipairs({temp:byte(1, temp:len())}) do
		chara_code[v] = "%"..string.char(v)
	end

	string.kill_meta_chara = function(str)
		local ret = ""
		for i, v in ipairs({str:byte(1, str:len())}) do
			ret = ret .. chara_code[v]
		end
		return ret
	end
end

解説

追加されるのは以下のものです。

string.initial
その文字列の先頭の文字を返します。半角全角を考慮します。
string.is_kanji
その文字列の先頭がShift-JISの漢字かどうかを返します。
string.separate
その文字列を文字に分解します。半角全角を考慮します。
string.fill
長さと文字を指定し、元の長さの先頭に指定した文字を、全体が指定した長さに達するまで繰り返し追加します。
string.han2zen
その文字列に含まれる半角文字を(変換できるだけ)全角文字に変換して返します。半角文字の「ジ」を「ジ」に変換するくらいはします。
string.zen2han
han2zenの逆です。
string.tonumber
全角を半角にした後、数値として不要な文字列を消去し、それから数値に変換して返します。
string.split
文字列を特定の文字で分割して返します。分割数も指定できます。
string.kill_meta_chara
文字列に含まれる正規表現用のメタ文字を殺して、「安全」にします。

string.initial

先頭の文字を取得します。

local str = "English"
local ini = str:initial() -- ini = "E"

string:initial(str) -- これでも同じ結果に。

local str2 = "日本語"
local ini2 = str:initial() -- ini2 = "日"

string.is_kanji

その文字列の先頭の文字が漢字であれば、trueを返します。

local str = "English"
local is_kanji = str:is_kanji() -- is_kanji = false

string:is_kanji(str) -- これでも同じ結果に。

local str2 = "ひらがな"
local is_kanji2 = str2:is_kanji() -- is_kanji2 = false

local str3 = "漢字"
local is_kanji3 = str3:is_kanji() -- is_kanji3 = true

string.separate

文字列を文字単位でバラバラにします。

local str = "日本語"
local letter1, letter2, letter3 = str:separate() -- "日", "本", "語"

もし、バラバラに受け取るのが嫌ならば、テーブルにまとめることもできます。

local str = "English"
local res = str:separate_table() -- res = {[1]="E", [2]="n", [3]="g", [4]="l", [5]="i", [6]="s", [7]="h"}

string.fill

文字列の先頭に特定の文字列を追加します。

local num = 200
local str = tostring(num)     -- str  =      "200"
local str2 = str:fill(8, " ") -- str2 = "     200"
local str3 = str:fill(8, "0") -- str3 = "00000200"

引数には、桁数と、埋めるのに使う文字(列)を指定します。

string.han2zen

その文字列に含まれる半角文字を全て全角に変換して返します。

local str = "ガッチャマン"
local str2 = str:han2zen() -- str2 = "ガッチャマン"

string.zen2han

han2zenの逆です。その文字列に含まれる半角文字に変換可能な全角文字を全て半角文字に変換して返します。

local str = "012ガッチャマン"
local str2 = str:zen2han() -- str2 = "012ガッチャマン"

string.tonumber

文字列を数値に変換しますが、tonumberとの違いは、

  • 全角数字を半角にする。
  • 数字表現に使えない文字は全て削除する。

点で、数値が返されます。

local str = "01日本語23"
local num = str:tonumber() -- num = 123

string.split

文字列を指定した文字で分割して返します。最大分割数も指定できます。指定しなければ無制限です。

local str = "1,2,3,4,5,6,7"
str:split(",") -- 1 2 3 4 5 6 7 に分割される。
local t = str:split_table(",") -- t = {"1", "2", "3", "4", "5", "6", "7"}
  • 分割文字を省略すると、""が採用され、string.separate/str.separate_tableと同じ結果になります。
  • 分割文字に文字列を指定すると、その先頭の文字だけが採用されます。
  • 最大分割数を省略すると、無制限に分割します。
  • 最大分割数を指定すると、こうなります。
local str = "1,2,3,4,5,6,7"
local t = str:split_table(",", 3) -- t = {"1", "2", "3,4,5,6,7"}

string.kill_meta_chara(2012/10/13追記)

文字列の中から正規表現で意味を持つメタ文字を見つけ出し、エスケープして「安全」にします。
この方法で「安全」にした文字列は、string.gsubなどでpaternとしてそのまま使うことができます。もちろん、メタ文字は無効にしていますので、単純に元になった文字列と全く同じ文字列を検索するだけになります。

local text = "これからテキストウィンドウに表示する予定の文字列"
text:gsub("リア充", "リア(充/じゅう)") -- テキスト中に「リア充」の文字があればフリガナを振りたい。
-- しかし、上文はエラーを出して失敗する。「充」の文字コードにメタ文字が含まれるからである。

local ruby = string.kill_meta_chara("リア充")
text:gsub(ruby, "リア(充/じゅう)") -- これなら、エラーを出さない。