📝

Clojure 文字列操作ライブラリ - cuerdas

2023/05/04に公開

Clojure/Clojurescript の文字列操作のユーティリティを提供するライブラリ cuerdas を一通り全部試す。なんとなくの挙動はイメージできるけど、こういうときどうだっけをいつも忘れて毎回試してるので備忘がてら。

全部やるつもりだったが多過ぎてやめた。残りはまたそのうち更新して追記する。

テストコードも見れば?
https://github.com/funcool/cuerdas/blob/master/test/cuerdas/core_test.cljc

準備

Leiningen の新しいプロジェクトを作って、依存を追加して、 REPL を起動。

$ lein new hi-cuerdas
$ lein cd hi-cuerdas
$ vi project.clj # [funcool/cuerdas "2022.03.27-397"] を :dependencies に追加
$ lein repl

cuerdas.core 名前空間

require する。
いくつか clojure.core ns の関数と同じ名前の関数があるので 警告 が出る。
reverse, format, empty?, concat, unquote, keyword, chars, repeat

user=> (require '[cuerdas.core :refer :all])
WARNING: reverse already refers to: #'clojure.core/reverse in namespace: user.core, being replaced by: #'cuerdas.core/reverse
WARNING: format already refers to: #'clojure.core/format in namespace: user.core, being replaced by: #'cuerdas.core/format
WARNING: empty? already refers to: #'clojure.core/empty? in namespace: user.core, being replaced by: #'cuerdas.core/empty?
WARNING: replace already refers to: #'clojure.core/replace in namespace: user.core, being replaced by: #'cuerdas.core/replace
WARNING: concat already refers to: #'clojure.core/concat in namespace: user.core, being replaced by: #'cuerdas.core/concat
WARNING: unquote already refers to: #'clojure.core/unquote in namespace: user.core, being replaced by: #'cuerdas.core/unquote
WARNING: keyword already refers to: #'clojure.core/keyword in namespace: user.core, being replaced by: #'cuerdas.core/keyword
WARNING: chars already refers to: #'clojure.core/chars in namespace: user.core, being replaced by: #'cuerdas.core/chars
WARNING: repeat already refers to: #'clojure.core/repeat in namespace: user.core, being replaced by: #'cuerdas.core/repeat
nil

ちなみに cuerdas.core 以外に cuerdas.regexp もあり、cljs 向けに少数の 正規表現ヘルパー関数がある。

早見表

  • 文字列構成の判定
    • empty? 空文字列か
    • empty-or-nil? 空文字列 or nil か
    • blank? 空文字列 or 空白文字 のみか
    • alpha? アルファベット文字のみか
    • letters? alpha? のユニコード対応版
    • alnum? アルファベット文字 or 数字 のみか
    • word? alnum? のユニコード対応版
    • numeric? 数値を表す文字のみか(負の数や少数を含める
    • digits? 数値を表す文字のみか(負の数や少数を含めない
    • starts-with? 特定の文字列で始まるか
    • ends-with? 特定の文字列で終わるか
    • includes? 特定の文字列を含むか
    • one-of? includes? の第二引数が Charactor 型版?
  • 記法変換
    • pascal パスカルケース(区切りで大文字かつ先頭が大文字: FooBarBaz)に変換
    • camel キャメルケース(区切りで大文字かつ先頭が小文字: fooBarBaz)に変換
    • snake スネークケース(アンダーバー区切り: foo_bar_baz)に変換
    • kebab ケバブケース(ハイフン区切り: foo-bar-baz)に変換
    • human ヒューマンケース?(半角スペース区切り: foo bar baz)に変換
    • title タイトルケース?(半角スペース区切りで各単語の先頭が大文字: Foo Bar Baz)に変換
    • phrase フレーズケース?(半角スペース区切りで最初の単語の先頭のみ大文字: Foo bar baz)に変換
    • css-selector CSS のセレクタ形式(-foo-bar-baz or foo-bar-baz)に変換
    • js-selector Javascript のセレクタ形式(FooBarBaz or fooBarBaz)に変換
  • 大文字小文字変換
    • lower 全ての文字を小文字に変換
    • upper 全ての文字を大文字に変換
    • capital 先頭の文字を大文字に変換
  • フォーマット
    • istr ES6 の Template Strings に近い文字列フォーマット。 ~{value}
    • ffmt Clojure の匿名関数に近い文字列フォーマット。 %, %1, %2...
    • fmt format のエイリアス
    • format C言語などの format 関数に近い文字列フォーマット。 %s or $1, $2...
  • 空白や文字列の除去
    • trim 文字列の前後から空白文字かあるいは指定した文字を取り除く
    • ltrim trim の先頭側だけ版。
    • rtrim trim の末尾側だけ版。
    • strip trim のエイリアス
    • lstrip ltrim のエイリアス
    • rstrip rtrim のエイリアス
    • strip-newlines 改行を半角スペースに変換する
    • strip-prefix 文字列から接頭辞文字列を取り除く
    • strip-suffix 文字列から接尾辞文字列を取り除く
    • strip-tags 文字列から HTML タグを取り除く
    • clean 先頭と末尾の whitespaces は取り除き、それ以外の連続する whitespaces を1つの半角スペースに変換する。
    • collapse-whitespace 隣接する空白文字を半角スペースに変換する
  • 分割
    • chars 文字列を1文字ずつの文字列に分割する
    • words 文字列を単語に分割する
    • lines 文字列を改行で分割する
    • split 文字列を指定した個数に分割する。デフォルトは空白文字ですべて分割する。
  • 結合
    • concat clojure.core/str 関数のマクロ版。マクロにしたことで性能がよくなるケースがある。
    • join 指定したセパレータで結合した文字列を作る
    • unlines 改行で結合する。lines の逆。
  • インデックス
    • index-of 指定した文字列が最初に現れるインデックスを返す
    • last-index-of 指定した文字列が最後に現れるインデックスを返す
  • その他変換
    • << istr の後方互換性のために残されている。
    • <<- インデントをコード上での見た目と同じような感じにしてくれる。
    • escape-html HTML の特殊文字をエスケープする。
    • unescape-html escape-html の逆
    • keyword clojure.core/keyword のより柔軟なバージョン
    • to-bool True っぽい文字列を Boolean の true に変換。
    • pad 指定した文字列の数に足りるまでパディングする。
    • prune 文字列を指定された長さに刈り取る
    • surround 文字列を別の文字列で囲む。
    • unsurround surround の逆の操作
    • quote 文字列を " で囲む
    • unquote quote の逆の操作
    • stylize 英語の文章文字列を統一的に整形する
    • repeat
    • replace
    • replace-first
    • reverse
    • slice
    • slug
    • substr-between
    • uslug

文字列構成の判定

empty?

文字列が空か。

empty-or-nil? と比較すると nil の場合が違うだけ。

user=> (empty? nil)
false
user=> (empty? "")
true
user=> (empty? " ")
false
user=> (empty? "foo")
false

参考: clojure.core/empty? は Collection 一般に使う関数。

user=> (clojure.core/empty? " ")
false

user=> (clojure.core/empty? "")
true
user=> (clojure.core/empty? [])
true
user=> (clojure.core/empty? {})
true
user=> (clojure.core/empty? '())
true
user=> (clojure.core/empty? #{})
true

user=> (clojure.core/empty? nil)
true

ちなみに clojure.core/empty? のほうは seq との使い分けがあるで注意。

user=> (clojure.repl/doc clojure.core/empty?)
-------------------------
clojure.core/empty?
([coll])
  Returns true if coll has no items - same as (not (seq coll)).
  Please use the idiom (seq x) rather than (not (empty? x))

empty-or-nil?

文字列が空または nil か。

user=> (empty-or-nil? nil)
true
user=> (empty-or-nil? "")
true

user=> (empty-or-nil? " ")
false
user=> (empty-or-nil? "foo")
false

blank?

文字列が空か whitespaces / 空白文字 かどうか。

user=> (blank? nil)
false
user=> (blank? "")
true
user=> (blank? " ")
true
user=> (blank? " a ")
false
user=> (blank? " あ ")
false

; 全角スペースもいける。
user=> (blank? " ")
true
; ちなみに全角スペースはコードポイントで表すと U+3000
user=> (blank? (str \u3000))
true

; もちろん文字列型でないとだめ
user=> (blank? \u3000)
false

; 改行文字なども含まれる
user=> (blank? "\n\t")
true
user=> (blank? (str \space \newline \formfeed \return \tab))
true
; ただし backspace は含まれない
user=> (blank? (str \backspace))
false

alpha?

alphabet / アルファベット かどうか。

user=> (alpha? nil)
false
user=> (alpha? "")
false
user=> (alpha? "Foo")
true
user=> (alpha? "Foo Bar")
false

; 全角はとうぜん false
user=> (alpha? "Foo")
false

letters?

alpha? のユニコード対応バージョン。

; return false
user=> (letters? nil)
false
user=> (letters? "")
false
user=> (letters? " ")
false
user=> (letters? "Foo1")
false
user=> (letters? "Foo ")
false

; return true
user=> (letters? "Foo")
true
user=> (letters? "Русский")
true
; 参考比較
user=> (alpha? "Русский")
false

alnum?

alphanumeric (アルファベットあるいは数)の文字列かどうか。

user=> (alnum? nil)
false
user=> (alnum? "")
false
user=> (alnum? "Foo123")
true
user=> (alnum? "Foo 123")
false

; 文字列型じゃないと問答無用で false
user=> (alnum? 123)
false
user=> (alnum? "123")
true

; 全角はとうぜん false
user=> (alnum? "Foo123")
false
user=> (alnum? "Foo123")
false

word?

alnum? のユニコード対応バージョン。

user=> (word? nil)
false
user=> (word? "")
false
user=> (word? "Foo123")
true
user=> (word? "Foo 123")
false

user=> (word? "Русский123")
true
; 参考比較
user=> (alnum? "Русский123")
false

numeric?

文字列が 数値 のみで構成されるか。
少数や負の数の表記を true とするあたりが digits? との違い。

; return true
user=> (numeric? "123")
true
user=> (numeric? "0123")
true
user=> (numeric? "-123.04") ; 負の数や少数
true
user=> (numeric? "2e10")
true

; return false
user=> (numeric? nil)
false
user=> (numeric? 10)
false
user=> (numeric? :hoge)
false
user=> (numeric? "")
false
user=> (numeric? "-123.04 ") ; スペース入り
false
user=> (numeric? "1/2") ; 分数
false

digits?

文字列が digit のみで構成されるか。

; return true 
user=> (digits? "123")
true
user=> (digits? "0123")
true

; return false
user=> (digits? nil)
false
user=> (digits? 10)
false
user=> (digits? :hoge)
false
user=> (digits? "") ; 空文字
false
user=> (digits? " 123 ") ; スペース入り
false
user=> (digits? "-123") ; 負の数
false
user=> (digits? "123.4") ; 少数
false
user=> (digits? "1/2") ; 分数
false

starts-with?

; return true
ser=> (starts-with? "FooBar" "Foo")
true
user=> (starts-with? "FooBar" "F")
true
user=> (starts-with? "FooBar" "")
true
user=> (starts-with? "" "")
true

; return false
user=> (starts-with? "FooBar" "Baz")
false
; どっちかの引数が nil だと結果は必ず false
user=> (starts-with? "" nil)
false
user=> (starts-with? nil "")
false

; 一文字だとしても Character 型は Exception に。
user=> (starts-with? "FooBar" "Baz")
false
user=> (starts-with? "FooBar" \F)
Execution error (UnsupportedOperationException) at cuerdas.core/starts-with? (core.cljc:127).
count not supported on this type: Character
; 数値型は通るけど暗黙でキャストされたりはしない。
user=> (starts-with? "1FooBar" 1)
false

ends-with?

第一引数の文字列が、第二引数の文字列を接尾辞として持つか。

; return true
user=> (ends-with? "FooBar" "Bar")
true
user=> (ends-with? "FooBar" "r")
true
user=> (ends-with? "FooBar" "")
true
user=> (ends-with? "" "")
true

: return true
user=> (ends-with? "FooBar" "Baz")
false
; どっちかの引数が nil だと結果は必ず false
user=> (ends-with? "" nil)
false
user=> (ends-with? nil "")
false

; 一文字だとしても Character 型は Exception に。
user=> (ends-with? "FooBar" \r)
Execution error (UnsupportedOperationException) at cuerdas.core/ends-with? (core.cljc:138).
count not supported on this type: Character
; 数値型は通るけど暗黙でキャストされたりはしない。
user=> (ends-with? "FooBar1" 1)
false

includes?

第一引数の文字列に第二引数の文字列が含まれているか。

user=> (includes? "" "")
true
user=> (includes? "FooBar" "oB")
true
user=> (includes? "FooBar" "")
true

; 引数のどっちかが nil だと false
user=> (includes? "" nil)
false
user=> (includes? nil  "")
false

one-of?

includes? の第二引数が Charactor 型版?
文字列型で渡してもエラーにならずだまって false になるし includes? で十分なように思える。

; 
user=> (one-of? "FooBar" \o)
true

user=> (one-of? "FooBar" \x)
false
user=> (one-of? "FooBar" "o") ; Charactor 型ではなく文字列型の場合
false
user=> (one-of? "FooBar" "")
false
user=> (one-of? "" "")
false

記法変換

pascal

パスカルケース(区切りで大文字かつ先頭が大文字)に変換する。

user=> (pascal "FooBarBaz") ; from pascal
"FooBarBaz"
user=> (pascal "fooBarBaz") ; from camel
"FooBarBaz"
user=> (pascal "foo_bar_baz") ; from snake
"FooBarBaz"
user=> (pascal "foo-bar-baz") ; from kebab
"FooBarBaz"
user=> (pascal "foo bar baz") ; from human
"FooBarBaz"

; それらが混ぜって、複数で、先頭や末尾にも区切り文字があるパターン
user=> (pascal "  __--Foo  __--Bar  __--baz  __--")
"FooBarBaz"

; return nil
user=> (pascal nil)
nil
user=> (pascal "")
nil
user=> (pascal " _-")
nil

; 全角アルファベットも OK
user=> (pascal "aaa bbb-ccc_ddd")
"AaaBbbCccDdd"

; 全角ひらがなや半角カナはとうぜんだめ
user=> (pascal "ぁああぃいい")
"ぁああぃいい"
user=> (pascal "ァアアィイイ")
"ァアアィイイ"

; keyword
user=> (pascal :foo-Bar_baz)
"FooBarBaz"

camel

キャメルケース(区切りで大文字かつ先頭が小文字)に変換する。

user=> (camel "FooBarBaz") ; from pascal
"fooBarBaz"
user=> (camel "fooBarBaz") ; from camel
"fooBarBaz"
user=> (camel "foo_bar_baz") ; from snake
"fooBarBaz"
user=> (camel "foo-bar-baz") ; from kebab
"fooBarBaz"
user=> (camel "foo bar baz") ; from human
"fooBarBaz"

; return nil
user=> (camel nil)
nil
user=> (camel "")
nil
user=> (camel " _-")
nil

; 全角アルファベットも OK
user=> (camel "aaa bbb-ccc_ddd")
"aaaBbbCccDdd"

; 全角ひらがなや半角カナはとうぜんだめ
user=> (camel "ぁああぃいい")
"ぁああぃいい"
user=> (camel "ァアアィイイ")
"ァアアィイイ"

; keyword
user=> (camel :foo-Bar_baz)
"fooBarBaz"

snake

スネークケース(アンダーバー区切り)に変換する。

user=> (snake "FooBarBaz") ; from pascal
"foo_bar_baz"
user=> (snake "fooBarBaz") ; from camel
"foo_bar_baz"
user=> (snake "foo_bar_baz") ; from snake
"foo_bar_baz"
user=> (snake "foo-bar-baz") ; from kebab
"foo_bar_baz"
user=> (snake "foo bar baz") ; from human
"foo_bar_baz"

; それらが混ぜって、複数で、先頭や末尾にも区切り文字があるパターン
user=> (snake "  __--Foo  __--Bar  __--baz  __--")
"foo_bar_baz"

; return nil
user=> (snake nil)
nil
user=> (snake "")
nil
user=> (snake " _-")
nil

; keyword
user=> (snake :foo-Bar_baz)
"foo_bar_baz"

kebab

ケバブケース(ハイフン区切り)に変換する。

user=> (kebab "FooBarBaz") ; from pascal
"foo-bar-baz"
user=> (kebab "fooBarBaz") ; from camel
"foo-bar-baz"
user=> (kebab "foo_bar_baz") ; from snake
"foo-bar-baz"
user=> (kebab "foo-bar-baz") ; from kebab
"foo-bar-baz"
user=> (kebab "foo bar baz") ; from human
"foo-bar-baz"

; それらが混ぜって、複数で、先頭や末尾にも区切り文字があるパターン
user=> (kebab "  __--Foo  __--Bar  __--baz  __--")
"foo-bar-baz"

; return nil
user=> (kebab nil)
nil
user=> (kebab "")
nil
(kebab " _-")
nil

; keyword
user=> (kebab :foo-Bar_baz)
"foo-bar-baz"

human

シングル半角スペース区切りに変換する。

user=> (human "FooBarBaz") ; from pascal
"foo bar baz"
user=> (human "fooBarBaz") ; from camel
"foo bar baz"
user=> (human "foo_bar_baz") ; from snake
"foo bar baz"
user=> (human "foo-bar-baz") ; from kebab
"foo bar baz"
user=> (human "foo bar baz") ; from human
"foo bar baz"

; それらが混ぜって、複数で、先頭や末尾にも区切り文字があるパターン
user=> (human "  __--Foo  __--Bar  __--baz  __--")
"foo bar baz"

; return nil
user=> (human nil)
nil
user=> (human "")
nil
user=> (human " _-")
nil

; keyword
user=> (human :foo-Bar_baz)
"foo bar baz"

title

シングル半角スペース区切りかつ各単語の先頭を大文字に変換する。

user=> (title "FooBarBaz") ; from pascal
"Foo Bar Baz"
user=> (title "fooBarBaz") ; from camel
"Foo Bar Baz"
user=> (title "foo_bar_baz") ; from snake
"Foo Bar Baz"
user=> (title "foo-bar-baz") ; from kebab
"Foo Bar Baz"
user=> (title "foo bar baz") ; from human
"Foo Bar Baz"

; それらが混ぜって、複数で、先頭や末尾にも区切り文字があるパターン
user=> (title "  __--Foo  __--Bar  __--baz  __--")
"Foo Bar Baz"

; return nil
user=> (title nil)
nil
user=> (title "")
nil
user=> (title " _-")
nil

; keyword
user=> (title :foo-Bar_baz)
"Foo Bar Baz"

phrase

シングル半角スペース区切りかつ最初の単語のみ先頭を大文字に変換する。

user=> (phrase "FooBarBaz") ; from pascal
"Foo bar baz"
user=> (phrase "fooBarBaz") ; from camel
"Foo bar baz"
user=> (phrase "foo_bar_baz") ; from snake
"Foo bar baz"
user=> (phrase "foo-bar-baz") ; from kebab
"Foo bar baz"
user=> (phrase "foo bar baz") ; from human
"Foo bar baz"

; それらが混ぜって、複数で、先頭や末尾にも区切り文字があるパターン
user=> (phrase "  __--Foo  __--Bar  __--baz  __--")
"Foo bar baz"

; return nil
user=> (phrase nil)
nil
user=> (phrase "")
nil
user=> (phrase " _-")
nil

; keyword
user=> (phrase :foo-Bar_baz)
"Foo bar baz"

css-selector

Javascript のセレクタ形式から CSS のセレクタ形式に変換する。
キャメルケースからケバブケースへの変換に近いけどちょっと違う。

js-selector の逆。

; 先頭が大文字だと - が付いて、先頭が小文字だと - は付かない。
user=> (css-selector "FooBarBaz")
"-foo-bar-baz"
user=> (css-selector "fooBarBaz")
"foo-bar-baz"

user=> (css-selector "  __--Foo  __--Bar  __--baz  __--")
"-foo-bar-baz"

; return nil
user=> (css-selector nil)
nil
user=> (css-selector "")
""
user=> (css-selector "  ")
nil

js-selector

CSS のセレクタ形式から Javascript のセレクタ形式に変換する。
ケバブケースからキャメルケースへの変換に近いけどちょっと違う。
css-selector の逆。

user=> (js-selector "foo-bar-baz")
"fooBarBaz"
user=> (js-selector "-foo-bar-baz")
"FooBarBaz"

user=> (js-selector "  __--Foo  __--Bar  __--baz  __--")
"FooBarBaz"

; return nil
user=> (js-selector nil)
nil
user=> (js-selector "")
""
user=> (js-selector "  ")
nil

大文字小文字変換

lower

すべて小文字に変換する。

user=> (lower "FooBar")
"foobar"

; 全角アルファベットもいける。
user=> (lower "FooBar") ; 全角アルファベットもいける。
"foobar"

user=> (lower nil)
nil

upper

すべて大文字に変換する。

user=> (upper "FooBar")
"FOOBAR"

; 全角アルファベットもいける。
user=> (upper "FooBar")
"FOOBAR"

user=> (upper nil)
nil

capital

先頭文字を大文字に変える。

user=> (capital "")
""
user=> (capital "a")
"A"
user=> (capital "foo")
"Foo"
user=> (capital "Foo")
"Foo"
user=> (capital " foo")
" foo"

; 全角アルファベットもいける。
user=> (capital "foo")
"Foo"

; 全角ひらがなや半角カナはとうぜんだめ
user=> (capital "ぁぁぁ")
"ぁああ"
user=> (capital "ァァァ")
"ァアア"

user=> (capital nil)
nil

フォーマット

istr

ES6 の Template Strings に近い。

user=> (def value1 111)
#'user/value1
user=> (let [value2 222] (istr "1: ~{value1}, 2: ~{value2}."))
"1: 111, 2: 222."
user=> (let [value1 nil] (istr "1: ~{value1}"))
"1: "

; 対応するシンボルがスコープに存在しない場合は Exception
user=> (let [value2 222] (istr "1: ~{value1}, 2: ~{value2}, 3: ~{value3}."))
Syntax error compiling at (/private/var/folders/d8/y7wwr0wd2q112kpg7b982b7c0000gn/T/form-init18111034881246950275.clj:1:19).
Unable to resolve symbol: value3 in this context

ffmt

文字列フォーマット関数。
istr の代替で、(コンパイル時に文字列内容が決定できるものは)コンパイル時にフォーマット処理がなされる。内部的に concat マクロを使っている。

2種類の記法。

  1. sequential: % を並べて順番で対応。
  2. indexed: %1, %2, %3, ... でどの変数かも指定。
1. sequential: % を並べて順番で対応。
; 複数の変数を順に受け取れる。
user=> (ffmt "1:[%], 2:[%]" "1st" "2nd")
"1:[1st], 2:[2nd]"
; 多すぎる引数は無視される。
user=> (ffmt "1:[%], 2:[%]" "1st" "2nd" "3rd")
"1:[1st], 2:[2nd]"
; 逆に使ってる変数の参照の数に対して引数が足りないと Exception
user=> (ffmt "1:[%], 2:[%]" "1st")
Unexpected error (IndexOutOfBoundsException) macroexpanding ffmt at (/private/var/folders/d8/y7wwr0wd2q112kpg7b982b7c0000gn/T/form-init18111034881246950275.clj:1:1).
; 文字列型以外を渡したとき
user=> (ffmt "1:[%], 2:[%] 3:[%]" 111 :two nil)
"1:[111], 2:[:two] 3:[]"

; % 自体を使いたいときは %% と書く。
user=> (ffmt "%1%%" 100)
"100%"
2. indexed: %1, %2, %3, ... でどの変数かも指定。
user=> (ffmt "1:[%2], 2:[%1]" "1st" "2nd")
"1:[2nd], 2:[1st]"

; 引数の変数を全部使用しなくてもいい。
user=> (ffmt "1:[%3], 2:[%3]" "1st" "2nd" "3rd")
"1:[3rd], 2:[3rd]"

; % と %1 は同じ扱い。匿名関数の書き方と一緒。
user=> (ffmt "1:[%], 2:[%1]" "1st" "2nd")
"1:[1st], 2:[1st]"

fmt

format のエイリアス: (def fmt format) 。ちょっと短く書ける。

format

文字列フォーマット関数。対応する引数が足りない場合(※)に特にエラーにもならずそのままフォーマット文字列が残るのは微妙な気がする。

1. sequential 形式 %s
user=> (format "1:[%s], 2:[%s]" "1st" "2nd" "3rd")
"1:[1st], 2:[2nd]"

; ※
user=> (format "1:[%s], 2:[%s], 3rd:[%s]" 111 nil)
"1:[111], 2:[], 3rd:[%s]"
2. associative 形式 $foo
user=> (format "1:[$a], 2:[$b]" {:a "1st" :b "2nd" :c "3rd"})
"1:[1st], 2:[2nd]"

user=> (format "1:[$a], 2:[$b], 3:[$c]" {:a 111 :b nil})
"1:[111], 2:[], 3:[]"

空白や文字列の除去

trim

文字列の前後から空白文字かあるいは指定した文字を取り除く。
第二引数に指定する除去文字は、文字列ではなく、各文字として扱われる。

user=> (trim "  abc  ")
"abc"
; 全角スペースは除去されない
user=> (trim " abc ")
" abc "

; 第二引数で除去する文字を明示できる
user=> (trim "-abc-" "-")
"abc"
user=> (trim "--abc--" "-")
"abc"
; 第二引数は文字列としてではなく、1つ1つの文字として除去対象となる。
user=> (trim "::abc_-_" "-_:")
"abc"


user=> (trim "")
""
user=> (trim " ")
""

user=> (trim nil)
nil

ltrim

trim の先頭側だけ版。

user=> (ltrim "  abc  ")
"abc  "
; 全角スペースは除去されない
user=> (ltrim " abc ")
" abc "


; 第二引数で除去する文字を明示できる
user=> (ltrim "-abc-" "-")
"abc-"
user=> (ltrim "--abc--" "-")
"abc--"
; 第二引数は文字列としてではなく、1つ1つの文字として除去対象となる。
user=> (ltrim "::abc_-_" "-_:")
"abc_-_"

user=> (ltrim "")
""
user=> (ltrim " ")
""

user=> (ltrim nil)
nil

rtrim

trim の末尾側だけ版。

user=> (rtrim "  abc  ")
"  abc"
; 全角スペースは除去されない
user=> (rtrim " abc ")
" abc "

; 第二引数で除去する文字を明示できる
user=> (rtrim "-abc-" "-")
"-abc"
user=> (rtrim "--abc--" "-")
"--abc"
; 第二引数は文字列としてではなく、1つ1つの文字として除去対象となる。
user=> (rtrim "::abc_-_" "-_:")
"::abc"

user=> (rtrim "")
""
user=> (rtrim " ")
""

user=> (rtrim nil)
nil

strip

trim のエイリアス: (def strip trim)

lstrip

ltrim のエイリアス: (def lstrip ltrim)

rstrip

rtrim のエイリアス: (def rstrip rtrim)

strip-newlines

改行を半角スペースに変換する

user=> (strip-newlines "aaa\nbbb\n")
"aaa bbb "
user=> (strip-newlines "aaa\rbbb\r")
"aaa bbb "
; 複数の連続する改行も単一の半角スペースに変換
user=> (strip-newlines "aaa\n\nbbb\n\n")
"aaa bbb "

user=> (strip-newlines "")
""
user=> (strip-newlines "\n")
" "

user=> (strip-newlines nil)
nil

strip-prefix

文字列から接尾辞文字列を取り除く。
第二引数に指定する文字列と完全に一致した場合のみ取り除かれる。

user=> (strip-prefix "::abcdef::" ":")
":abcdef::"
user=> (strip-prefix "::abcdef::" "::")
"abcdef::"
user=> (strip-prefix "::abcdef::" ":::")
"::abcdef::"

; 第二引数を文字列として完全に一致するかをみるのが lstrip や ltrim との違い
; 参考
user=> (lstrip "::abcdef::" ":")
"abcdef::"
user=> (lstrip "::abcdef::" "::")
"abcdef::"
user=> (lstrip "::abcdef::" ":::")
"abcdef::"

; マッチしない
user=> (strip-prefix "::abcdef::" nil)
"::abcdef::"
user=> (strip-prefix "::abcdef::" "")
"::abcdef::"

user=> (strip-prefix nil "abc")
nil

strip-suffix

文字列から接頭辞文字列を取り除く。
第二引数に指定する文字列と完全に一致した場合のみ取り除かれる。

user=> (strip-suffix "::abcdef::" ":")
"::abcdef:"
user=> (strip-suffix "::abcdef::" "::")
"::abcdef"
user=> (strip-suffix "::abcdef::" ":::")
"::abcdef::"

; 第二引数を文字列として完全に一致するかをみるのが rstrip や rtrim との違い
; 参考
user=> (rstrip "::abcdef::" ":")
"::abcdef"
user=> (rstrip "::abcdef::" "::")
"::abcdef"
user=> (rstrip "::abcdef::" ":::")
"::abcdef"

; マッチしない
user=> (strip-suffix "::abcdef::" nil)
"::abcdef::"
user=> (strip-suffix "::abcdef::" "")
"::abcdef::"

user=> (strip-suffix nil "abc")
nil

strip-tags

文字列から HTML タグを取り除く

user=> (strip-tags "<div>aaaa bbbb</div>")
"aaaa bbbb"
user=> (strip-tags "<div class=\"foo bar\"> aaaa <br /> bbbb </div>")
" aaaa  bbbb "

; 特定のタグのみ除去。第二引数はタグを keyword か 文字列で複数指定可能。
user=> (strip-tags "<div> aaaa <br /> bbbb </div>" [:div])
" aaaa <br /> bbbb "
user=> (strip-tags "<div> aaaa <br /> bbbb </div>" ["div"])
" aaaa <br /> bbbb "
user=> (strip-tags "<div> aaaa <br /> bbbb </div>" ["div" "span"])
" aaaa <br /> bbbb "

; タグの個別指定の場合、タグに属性があると反応しない
user=> (strip-tags "<div class='foo bar'> aaaa <br /> bbbb </div>" [:div])
"<div class='foo bar'> aaaa <br /> bbbb "

; 除去ではなく変換も可能
; br タグを改行に変換し、その他のタグは除去。
user=> (strip-tags "<div> aaaa <br /> bbbb </div>" {:br "\n"})
" aaaa \n bbbb "
; br タグを怪猫に変換し、その他のタグは維持。
user=> (strip-tags "<div> aaaa <br /> bbbb </div>" [:br] {:br "\n"})
"<div> aaaa \n bbbb </div>"
; 複数の変換
user=> (strip-tags "<div> aaaa <br /> bbbb </div>" {:br "\n" :div "■"})
"■ aaaa \n bbbb ■"

user=> (strip-tags "<div>aaaa bbbb</div>" nil)
"aaaa bbbb"
user=> (strip-tags "<div>aaaa bbbb</div>" nil nil)
"aaaa bbbb"

user=> (strip-tags nil)
nil

clean

先頭と末尾の whitespaces は取り除き、それ以外の連続する whitespaces を1つの半角スペースに変換する。

user=> (clean "  foo\n\nbar \n ")
"foo bar"

user=> (clean "")
""
user=> (clean " ")
""
user=> (clean "  ")
""

user=> (clean nil)
nil
user=> (clean 10)
nil

collapse-whitespace

隣接する空白文字を半角スペースに変換する。
clean との違いがほとんど分からないが、コード見ると使ってる正規表現を含めて実装は異なってる。
用途次第だけど、全角スペースもハンドリングしてくれる clean のほうが使えそう?

user=> (collapse-whitespace " a ")
"a"
user=> (collapse-whitespace "  a  ")
"a"
user=> (collapse-whitespace " a b")
"a b"
user=> (collapse-whitespace " a  b")
"a b"

user=> (collapse-whitespace "  foo\n\nbar \n ")
"foo bar"

user=> (collapse-whitespace "")
""
user=> (collapse-whitespace " ")
""
user=> (collapse-whitespace "  ")
""

user=> (collapse-whitespace nil)
nil
user=> (collapse-whitespace 10)
nil

分割

chars

1文字ずつに分けたベクタに変換。

user=> (chars nil)
nil
user=> (chars "")
[""]
user=> (chars " ")
[" "]
user=> (chars "  ")
[" " " "]
user=> (chars " foo")
[" " "f" "o" "o"]
user=> (chars " あいう")
[" " "あ" "い" "う"]
user=> (chars "foo\nbar")
["f" "o" "o" "\n" "b" "a" "r"]

; 文字列型以外を渡しても例外にはならず nil が返る
user=> (chars :foo)
nil
user=> (chars 12)
nil

; 返り値の型は PersistentVector
user=> (type (chars "foo"))
clojure.lang.PersistentVector
; 返り値の各要素の型は java.lang.String であり java.lang.Character ではない。
user=> (map type (chars "foo"))
(java.lang.String java.lang.String java.lang.String)

words

文字列を単語に分解しベクタに変換する。

user=> (words "Yes, true.")
["Yes" "true"]
user=> (words "it's - no matter how - no problem.")
["it" "s" "-" "no" "matter" "how" "-" "no" "problem"]

; 第二引数で正規表現を直接指定できる。デフォルトは [a-zA-Z0-9_-]+ 。
user=> (words "it's - no matter how - no problem." #"[^-,. ]+")
["it's" "no" "matter" "how" "no" "problem"]

lines

改行で分割する

user=> (lines "foo\nbar\nbaz")
["foo" "bar" "baz"]

; 末尾の改行は無視して取り除かれるが、その他の空行も解釈する。
user=> (lines "\nfoo\n\nbar\n")
["" "foo" "" "bar"]

user=> (lines nil)
nil

split

文字列を指定した個数に分割する。デフォルトは空白文字ですべて分割する。

user=> (split "aaa bbb  ccc\nddd ")
["aaa" "bbb" "ccc" "ddd"]
; 分割文字列指定
user=> (split "aaa bbb  ccc\nddd " " ")
["aaa" "bbb" "" "ccc\nddd"]
; 分割数も指定
user=> (split "aaa bbb  ccc\nddd " " " 0) ; 0 を指定しても意味なし
["aaa" "bbb" "" "ccc\nddd"]
user=> (split "aaa bbb  ccc\nddd " " " 1)
["aaa bbb  ccc\nddd "]
user=> (split "aaa bbb  ccc\nddd " " " 2)
["aaa" "bbb  ccc\nddd "]
user=> (split "aaa bbb  ccc\nddd " " " 3)
["aaa" "bbb" " ccc\nddd "]
user=> (split "aaa bbb  ccc\nddd " " " 4)
["aaa" "bbb" "" "ccc\nddd "]
user=> (split "aaa bbb  ccc\nddd " " " 5) ; 4 と変わらず
["aaa" "bbb" "" "ccc\nddd" ""]

; 正規表現で指定も可能
user=> (split "aaa bbb  ccc\nddd " #"\s+")
["aaa" "bbb" "ccc" "ddd"]
user=> (split "aaa bbb  ccc\nddd " #"\s+" 2)
["aaa" "bbb  ccc\nddd "]

user=> (split "")
[""]
user=> (split " ")
[]

user=> (split nil)
nil

結合

concat

clojure.core/str 関数のマクロ版。
cljs ではかなり速く clj でも少し速くなるとのこと。
マクロになったことで clj の場合は連続する文字列インスタンス同士はコンパイル時に結合させてしまうし、 cljs の場合は + オペレータを使っている。

user=> (concat)
""
user=> (concat nil)
""
user=> (concat "")
""

user=> (concat 123 nil "foo" :abc "  ")
"123foo:abc  "

; 参考比較。
user=> (str 123 nil "foo" :abc "  ")
"123foo:abc  "

join

指定したセパレータで結合した文字列を作る

user=> (join ["aaa" "bbb" "ccc"])
"aaabbbccc"
user=> (join " && " ["aaa" "bbb" "ccc"])
"aaa && bbb && ccc"
user=> (join " && " [])
""
user=> (join " && " nil)
""

user=> (join nil ["aaa" "bbb" "ccc"])
"aaabbbccc"
user=> (join "" ["aaa" "bbb" "ccc"])
"aaabbbccc"

; これのつもりで・・・
user=> (join ["aaa" "bbb"])
"aaabbb"
; ・・・これをするミスはありそう
user=> (join "aaa" "bbb")
"baaabaaab"

unlines

改行で結合する。lines の逆。

user=> (unlines ["aaa" "bbb" nil "ccc"])
"aaa\nbbb\n\nccc"
user=> (unlines ["aaa"])
"aaa"
user=> (unlines [])
""

user=> (unlines nil)
nil
user=> (unlines "")
nil

インデックス

index-of

第一引数の文字列において、第二引数の文字列が登場するインデックスを返す。

user=> (index-of "FooBar" "")
0
user=> (index-of "FooBar" "B")
3
user=> (index-of "FooBar" "Bar")
3

user=> (index-of "FooBar" "Baz")
nil
user=> (index-of nil "")
nil
user=> (index-of "" nil)
nil

last-index-of

マッチする文字列のインデックスを返す。複数マッチする場合は最後にマッチしたところのインデックス。

user=> (last-index-of "foobarbaz" "foo")
0
user=> (last-index-of "foobarbaz foo" "foo")
10

; 参考比較
user=> (index-of "foobarbaz foo" "foo")
0

その他変換

<<

istr の後方互換性のためにのこされているそう。試してない。

<<-

インデントをコード上での見た目と同じような感じにしてくれる。
; 1行目を除いて最もインデントが少ない行のインデント数をその他の行のインデントから除去する

user=> (println (<<- "aaa
  #_=>                bbb
  #_=>                 ccc
  #_=>                   ddd
  #_=>                eee"))
aaa
bbb
 ccc
   ddd
eee
nil

escape-html

HTML の特殊文字をエスケープする。

; 変化しない
user=> (escape-html "")
""
user=> (escape-html "<>")
"<>"
user=> (escape-html "Foo")
"Foo"

; エスケープされる
user=> (escape-html "<div>    &    \"    </div>")
"&lt;div&gt;    &amp;    &quot;    &lt;/div&gt;"

unescape-html

escape-html の逆

user=> (unescape-html "&lt;div&gt;    &amp;    &quot;    &lt;/div&gt;")
"<div>    &    \"    </div>"

user=> (unescape-html "Foo")
"Foo"
user=> (unescape-html "")
""

user=> (unescape-html nil)
nil

keyword

clojure.core/keyword のより柔軟なバージョン。

user=> (keyword " Foo/Bar_baz qux//--__  ")
:foo-bar-baz-qux

; keyword も受け付ける
user=> (keyword :Foo-Bar_baz)
:foo-bar-baz
; けど / はちょっと特殊な挙動
user=> (keyword :foo/bar)
:bar

; 引数2つにすると第二引数に名前空間を取る。
user=> (keyword "hoge" "foo/Bar_baz qux")
:hoge/foo-bar-baz-qux
user=> (keyword *ns* "foo/Bar_baz qux")
:user/foo-bar-baz-qux

; このあたりは直感的でないかも。
user=> (keyword :hoge "foo/Bar_baz qux")
::hoge/foo-bar-baz-qux
user=> (keyword ":hoge" "foo/Bar_baz qux")
::hoge/foo-bar-baz-qux
user=> (keyword ::hoge "foo/Bar_baz qux")
::user/hoge/foo-bar-baz-qux
user=> (keyword nil "foo/Bar_baz qux")
:/foo-bar-baz-qux
user=> (keyword "" "foo/Bar_baz qux")
:/foo-bar-baz-qux

; return nil
user=> (keyword nil)
nil
user=> (keyword "")
nil
user=> (keyword " ")
nil

to-bool

True っぽい文字列を Boolean の `true` に変換。
大文字小文字は無視される。

; return true
user=> (to-bool "1")
true
user=> (to-bool "on")
true
user=> (to-bool "true")
true
user=> (to-bool "yes")
true
; case-insensitive
user=> (to-bool "Yes")
true
user=> (to-bool "YES")
true

; return false
user=> (to-bool nil)
false
user=> (to-bool "")
false
user=> (to-bool "ok")
false
user=> (to-bool "y")
false
user=> (to-bool "yes ") ; 余計な文字がまざってたらだめ
false
user=> (to-bool 1) ; 数値型はだめ
false
user=> (to-bool '1) ; Character 型もだめ
false
user=> (to-bool "1") ; 全角ももちろんだめ
false

pad

指定した文字列の数に足りるまでパディングする。デフォルトは 半角スペース を 左パディング。

user=> (pad "12" {:length 4})
"  12"
user=> (pad "12" {:length 4 :padding "0"})
"0012"
user=> (pad "12" {:length 4 :padding "0" :type :right})
"1200"
user=> (pad "12" {:length 4 :padding "0" :type :both})
"0120"

; 全角
user=> (pad "12" {:length 4 :padding "零"})
"零零12"
user=> (pad "壱弐" {:length 4 :padding "零"})
"零零壱弐"

; :type :both で padding する数が奇数になるとあまりの1つは left にはいる。
user=> (pad "12" {:length 5 :padding "0" :type :both})
"00120"

; :padding に長さ2以上の文字列を指定しても先頭一文字だけ使われるので注意。
user=> (pad "12" {:length 6 :padding "ab"})
"aaaa12"

; そのまま return される
user=> (pad "12")
"12"
user=> (pad "12" nil)
"12"
user=> (pad "12" {})
"12"
user=> (pad "12" {:length 1}) ; 元の length より短い :length を指定
"12"
user=> (pad "12" {:length 4 :padding 0}) ; :padding は文字列で指定する
"12"

; return nil
user=> (pad 12)
nil

prune

文字列を指定された長さに刈り取る。デフォルトでは末尾に ... が付加される。

; 半角スペースを単語の区切りとして解釈し、単語の途中では切らない
user=> (prune "hi, how are you?" 10) ; hi, how で 6文字
"hi, how..."
user=> (prune "hi, how are you?" 11) ; hi, how are でちょうど 11文字
"hi, how are..."

; デフォルトは末尾に ... が足されるが変更可能
user=> (prune "hi, how are you?" 11 " 以下省略")
"hi, how are 以下省略"
user=> (prune "hi, how are you?" 11 "")
"hi, how are"
user=> (prune "hi, how are you?" 11 nil)
"hi, how are"

; 結果が元の文字数を超える場合は元の文字列を返す
user=> (prune "hi, how are you?" 11 "------")
"hi, how are you?"
user=> (prune "hi, how are you?" 11 "-----")
"hi, how are-----"

user=> (prune "hi, how are you?" 0)
"..."

surround

文字列を別の文字列で囲む。
quote は第二引数がデフォルトで指定されている。それだけが違い?

user=> (surround "abc" "**")
"**abc**"
user=> (surround "abc" "***")
"***abc***"
user=> (surround "" "**")
"****"

user=> (surround "abc" "")
"abc"
user=> (surround "abc" nil)
"abc"

user=> (surround nil "**")
nil

user=> (surround "abc")
Execution error (ArityException) at user/eval2943 (form-init10559948635999286691.clj:1).
Wrong number of args (1) passed to: cuerdas.core/surround
; 参考
user=> (cuerdas.core/quote "abc")
"\"abc\""

unsurround

surround の逆の操作。
unquote は第二引数がデフォルトで指定されている。それだけが違い?

user=> (unsurround "**abc**" "*")
"*abc*"
user=> (unsurround "**abc**" "**")
"abc"
user=> (unsurround "**abc**" "***")
"**abc**"

user=> (unsurround "**abc**" "")
"**abc**"

user=> (unsurround "\"abc\"")
Execution error (ArityException) at user/eval2956 (form-init10559948635999286691.clj:1).
Wrong number of args (1) passed to: cuerdas.core/unsurround
; 参考
user=> (unquote "\"abc\"")
"abc"

quote

文字列を " で囲む。囲む文字は第二引数で指定可能。

user=> (cuerdas.core/quote "abc")
"\"abc\""

user=> (cuerdas.core/quote "abc" "'")
"'abc'"
user=> (cuerdas.core/quote "abc" "**")
"**abc**"

; そのまま return される
user=> (cuerdas.core/quote "abc" "")
"abc"
user=> (cuerdas.core/quote "abc" nil)
"abc"

user=> (cuerdas.core/quote "")
"\"\""

unquote

quote の逆の操作。

user=> (unquote "\"abc\"")
"abc"
user=> (unquote "**abc**" "**")
"abc"
user=> (unquote "**abc**" "*")
"*abc*"

user=> (unquote "abc")
"abc"
user=> (unquote "\"abc\"" "")
"\"abc\""

stylize

英語の文章文字列を統一的に整形する。

(stylize s every-fn join-with)
(stylize s first-fn rest-fn join-with)
という形式で、 単語毎に分解し join-with の文字列を挟ませて結合する。先頭の単語には first-fn 関数を適用し、残りの単語には rest-fn 関数を適用する(あるいは全ての単語に every-fn 関数を適用する)。

文章を統一的にゴリッと整形してきれいにしたいときに使うのかな。日本語だとあまり使いみちはなさそう。

この関数だけテストコードもドキュメントもない。

user=> (stylize " hi, how   are you? " #(str "[" % "]") ", ")
"[hi], [how], [are], [you]"

user=> (stylize " hi, how   are you? " #(capital %) #(lower %) " ")
"Hi how are you"

user=> (stylize " hi, how   are you? " #(capital %) nil " ")
Execution error (NullPointerException) at cuerdas.core/join (core.cljc:477).
Cannot invoke "clojure.lang.IFn.invoke(Object)" because "this.f" is null

repeat


replace


replace-first


reverse


slice


slug


substr-between


uslug


Discussion