tkinterdnd2で、ドラッグ&ドロップしたファイルのフルパスの取得
梅雨らしく、雨。
でも、降ったり止んだりで、ザーザーと降り続く、という感じではなかった。
Pythonのtkinterdnd2で、ドラッグ&ドロップしたファイルのフルパスの取得で、ちょっと苦労したので、それについて。
ネットで検索したのだけれど、皆さん、あまりこういう使い方をしていない?
それとも、困っていない?
自分はかなり困ったんだけれど。(苦笑)
tkinterdnd2で、ドラッグ&ドロップしたファイルのフルパスを取得しようとすると、フォルダ名やファイル名にスペースがある時とない時とで、データの扱い方が違う。
サンプルで書くと、こういう感じになる。
c:\aa\bb.txt {d:\ee e\f f.txt} {d:\gg\gg\g g.txt} c:\h\i\jj.txt c:\h\i\jj.txt {d:\kk\k kk\k.txt}
何もスペースがないと、スペース区切り。
フォルダ名やファイル名にスペースが入っていると、「{}」で囲われたスペース区切りになる。
こういう状態から、フルパスを取得しようとすると、どうすればいいのだ?!?!となってしまった。
正規表現を駆使してやろうとするが、なかなかうまくいかない。
もう一息!というところまではいくんだけれど、イレギュラー処理、つまり、一般的にはあり得ないファイル名に対応しようとすると、対処しきれなかった。
例えば、ファイル名に「.」が何個もある(拡張子が複数あるような感じ)とか、拡張子の中にスペースがあるとか、拡張子に記号や日本語があるとか。
一般的な命名規則の場合は、自分の考えた、正規表現のフルパス記号でなんとかなったんだけれどね。
困ったので、Bing Search(Bing AIチャット)に相談したら、なるほどなぁ、そういうやり方もあるんだ、と、感心させられた。
フルパスを抜き出す処理だけを下記に。
import tkinter as tk
from tkinterdnd2 import *
import re
#(略)
filepath = event.data
# フォルダの区切りが「/」になっているので、Windows用に「\」に置き換え
filepath = filepath.replace("/", "\\")
pattern = r"{.+?}|[^ ]+"
filepaths = re.findall(pattern, filepath)
#filepaths = sorted(filepaths)
filename = "a.txt"
# ファイル名が空でない場合
if filename:
# ファイルを追記モードで開く
with open(filename, "a", encoding="utf-8") as file:
# ファイルにパスを書き込む
for filepath in filepaths:
# フォルダ名、ファイル名に、スペースがあると、前後を「{}」で閉じられているので、それを削除
file.write(re.sub("{|}", "", filepath + "\n"))
#(略)
簡単に解説。
データを受け取ったら、まず、フォルダの区切り文字をWindows用に置き換える。
そして、正規表現のfindallで、その条件に合うものを抜き出してくれるので、そのパターンを作成。
Bing Searchは、「{.+?}|[^ ]+」というのを出してくれた。
「|」で区切られているので、orということで、どちらかの時、それを抜き出してくれる。
左側の方は、「{}」で囲われている文字列を抜き出してくれる。
つまり、フォルダ名かファイル名にスペースの入っているフルパスが対象となる。
これはわかりやすい。
次の「[^(スペース1個)]+」が感心した。
これは、「スペースではないものが連続している場合」ということで、フォルダ名やファイル名にスペースがなければ、連続した文字列となり、拡張子の後に、区切り文字としてスペースがあるから、それまでのものを抜き出してくれる、というもの。
なるほどねぇ、と、目から鱗!
これで、どちらであっても、フルパスで抜き出してくれるようになった。
本当なら、「{}」をなくしたいので、「{(.+?)}|([^ ]+)」という風にしたいのだけれど、findall関数では、()が2個以上あると、エラーでダメだった。
テキストエディターとかそういうものでは、問題ないのだけれど。
バグなのかなんなのか。
仕方ないので、ファイルに書き込みする時に、「re.sub("{|}"・・・)」として、それらを削除するようにした。
ちょっと煮え切らないけれど、これが一番納得のいくやり方となった。
これは、tkinterdnd2の仕様に関わることだけれど、フォルダ名やファイル名にスペースがあってもなくても、「{}」で囲ってくれていれば、抜き出すのも楽になったのにねぇ。
ちなみに、上記のやり方の前に思いついたものとして、フルパスを正規表現で抜き出すのではなく、区切り文字を変えてやるやり方。
filepath = re.sub("} {| {|} ", "|||", filepath)
filepath = re.sub("{|}", "", filepath)
filepath = re.sub(" (?=[a-zA-Z]:\\\)", "|||", filepath)
filepaths = filepath.split("|||")
「}(スペース){|(スペース){|}(スペース)」というのは、「{}」の前後に、スペースがあるので、それの組み合わせ。
スペース区切りではなく、絶対に出てこないであろう文字列「|||」を、新たに区切り文字にする。
配列の一番最初と最後に、「{}」がある可能性があるので、それを削除。
次に、残りのスペース区切りを「|||」にするために、ドライブ文字の前のスペースを対象に、「|||」に置き換え。
(ここで、まさか、昨日書いた、正規表現の肯定先読みを使うことになろうとは!)
最後は、その区切り文字「|||」で、変数に分割して挿入。
というやり方でやったのである。
このやり方であれば、ファイル名がどうであれ、区切り文字だけをきちんと対処すればいいので、ある意味楽。
が、やはり、フルパスを対象に抜き出したかったので、前者を採用した次第。
先述したけれど、区切り文字、もうちょっと、扱いやすくしてくれればよかったのにねぇ。
| 固定リンク
コメント