Masowa zmiana nieprzyjaznych nazw plików w C#
[caption id=”attachment_3158″ align=”aligncenter” width=”640″] Renamer 2013[/caption] [demo url=”http://mateuszmanaj.pl/application/media/download/Renamer-bin.zip” files=”http://mateuszmanaj.pl/application/media/download/Renamer-src.zip”] Tworzenie nazw plików, które nie zawierają znaków specjalnych od zawsze było problemem nie tylko…
[caption id=”attachment_3158″ align=”aligncenter” width=”640″] Renamer 2013[/caption]
[demo url=”http://mateuszmanaj.pl/application/media/download/Renamer-bin.zip” files=”http://mateuszmanaj.pl/application/media/download/Renamer-src.zip”]
Tworzenie nazw plików, które nie zawierają znaków specjalnych od zawsze było problemem nie tylko leżącym po naszej stronie, lecz również po stronie naszych klientów.
Możesz od razu pobrać pliki źródłowe lub skompilowany program Renamer
Ile to razy dostałeś/łaś plik który zawiera znaki specjalne a umieszczenie takiego pliku w sieci wiąże się z google’owaniem co tak dokładnie oznacza błąd 404.
W tym artykule przełamując niejako standard tworzenia aplikacji za pomocą PHP na tym blogu, utworzymy mały programik w Microsoft’owym C# i nazwiemy go 'ReNamer 2013′ 🙂
Wykorzystamy do tego Visual Studio 2010
Rozpoczynamy od utworzenia nowego projektu o nazwie Renamer. (kliknij, aby powiększyć)
Świeżo utworzony projekt w panelu Solution Explorer powinien wyglądać mniej więcej tak
Ja w swojej aplikacji na głównej formie umieściłem obraz oraz kilka label’i. Możesz to zrobić przeciągając odpowiednie komponenty z panelu Toolbox po lewej stronie okna. Tak umieszczony element możesz dowolnie układać na formie, zmieniać jego wymiary, zawartość, kolor itd… Efekt końcowy wygląda następująco:
Od razu utworzymy sobie plik pomocnika o nazwie FilesystemUtil.cs. Aby to zrobić należy kliknąć prawym przyciskiem myszy w prawej części okna w panelu Solution Explorer na nazwę naszego projektu czyli na Renamer i wybrać Add -> Class…
W oknie, któro się pojawi wpisujemy nazwę pliku FilesystemUtil.cs i klikamy OK.
W pliku tym umieścimy metodę, która będzie 'czyścić’ nam tekst ze znaków specjalnych. W zasadzie to ze wszystkich znaków innych niż liczby czy cyfry. Schemat działania będzie bardzo prosty. Paramter tej metody będzie przechowywał ciąg znaków do wyczyszczenia. Zastosujemy kolejno następujące kroki:
- Zamienimy wszystkie znaki diakrytyczne na ich odpowiedniki bez 'ogonków’ np. ą na a, ę na e itd..
- Wyrażeniem regularnym [^a-z0-9A-Z] zamienimy odpowiednie znaki (różne od a-z, 0-9 i A-Z) na myślniki
- Jeśli w/w myślników powstanie za dużo obok siebie należy je zamienić na jeden myślnik stosując kolejne wyrażenie regularne [-]{1,} i zamieniając myślniki będące obok siebie (np. —) na jeden (-)
- Tak przygotowany tekst zwracamy. Koniec, główne założenie aplikacji spełnione.
Dodatkowo zmieniłem jeszcze nazwę Form1.cs na bardziej sugestywną Renamer.cs. Możesz to zrobić poprzez panel Solution Explorer klikając prawym przyciskiem na plik -> Rename.
Po kolei… Otwieramy plik FilesystemUtil.cs, umieszczamy w nim następujący kod:
using System.Collections.Generic; using System.Text.RegularExpressions; namespace Renamer { public static class FilesystemUtil { public static string SafeFilename(this string input) { string result = input; var plS = new List { "ę", "ó", "ą", "ś", "ł", "ż", "ź", "ć", "ń" }; var plR = new List { "e", "o", "a", "s", "l", "z", "z", "c", "n" }; result = result.ToLower(); for (int i = 0; i < plS.Count; i++) result = result.Replace(plS[i], plR[i]); result = Regex.Replace(result, "[^a-z0-9A-Z]", "-"); result = Regex.Replace(result, "[-]{1,}", "-"); return result; } } }
Zostosowalismy tutaj klasę statyczną i metodę typu extension, która również jest statyczna. Co daje takie rozwiązanie ?
Metody statyczne bardzo ładnie nadają się do tworzenia odrębnych narzędzi w naszej aplikacji, takich które nie muszą łączyć się z innymi obiektami i ich wywoływać (chyba że odnoszą się do innych metod statycznych). Przy ich używaniu nie musimy deklarować nowej instancji klasy zawierającej tą metodę.
Dodatkowo przy metodzie statycznej możemy użyć czegoś takiego jak metoda extension. Czym ona jest ?
Jest to zwykła metoda spełniająca taką samą rolę jak każda inna, lecz różniąca się sposobem wywołania. Poniżej pokażę tą różnicę na przykładzie.
public static string MetodaNieExt(string input) { return input; } public static string MetodaExt(this string input) { return input; }
Mamy tutaj dwie metody statyczne MetodaNieExt oraz MetodaExt. Jak się łatwo domyślić pierwsza nie jest typu extension, natomiast druga jest. Sposoby odwołania się do tych metod:
string testowyCiag = "Lorem lipsum sit dolor amet..."; // Wywołanie metody nie będącej typem EXTENSION string rezultat = MetodaNieExt(testowyCiag); Console.WriteLine(rezultat); // Wywołanie metody EXTENSION string rezultat2 = testowyCiag.MetodaExt(); Console.WriteLine(rezultat2);
W obu przypadkach na ekranie pojawi się ten sam tekst, lecz tutaj chodzi o linijki 4 i 8 w/w kodu. Zatem jaka jest różnica ? Generalnie w wygodzie stosowania np. przy większej liczbie paramterów. Przy dobrym nazewnictwie uzyskamy na prawdę piękny kod używając właśnie metod tego typu. Taki kod jest czytelny i wygodny do przetwarzania.
Praktycznym przykładem może być kawałek kodu, który opracowaliśmy chwilę temu:
[...] for (int i = 0; i < plS.Count; i++) result = result.Replace(plS[i], plR[i]); [...]
gdzie result.Replace(plS[i], plR[i]) jest metodą typu extension z dodatkowymi dwoma paramterami. Jak widać zastosowania metod tego typu są ogromne i wykorzystywane w bibliotekach framework’u .NET 4.
Tak przygotowaną metodę możesz przetestować np. bezpośrednio w głównym oknie Renamer.cs. W Solution Explorer kliknij prawym przyciskiem na ten plik i wybierz opcję View Code.
Naszą metodę przetestujemy w konstruktorze tuż pod wywołaniem metody: InitializeCompnent();
using System; using System.Diagnostics; using System.IO; using System.Windows.Forms; using Renamer.Properties; namespace Renamer { public partial class Renamer : Form { public Renamer() { InitializeComponent(); string test = "bardzo]@###__ochydna-+=nazwa_$$%sprawiająca``' dużo!@# proble@#ów!@"; Console.WriteLine(test.SafeFilename()); } } }
Po wykonaniu tego kodu CTRL + F5 w panelu Output zobaczymy skonwertowany ciąg:
bardzo-ochydna-nazwa-sprawiajaca-duzo-proble-ow-
Teraz tylko musimy załatwić obsługę wielu plików w stylu Drag’n’Drop i ich nowe nazwy. Od czego zacząć ?
Najpierw musimy się zastanowić w jaki sposób są przekazywane parametry do naszej aplikacji. Możemy to zrobić poprzez linię komend (np. wywołanie z konsoli) oraz poprzez przęciągnięcie i upuszczenie na naszym EXE’ku dowolnych plików. Wtedy do programu zostaje przekazana tablica string’ów ze ścieżkami tych plików.
Ok, w takim razie do dzieła.
W pliku Program.cs – głównym pliku startowym naszej aplikacji – musimy dokonać pewnych modyfikacji. Poniżej umieszczę oryginalną treść pliku a następnie zmodyfikowaną.
using System; using System.Windows.Forms; namespace Renamer { static class Program { /// /// The main entry point for the application. /// [STAThread] static void Main() { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.Run(new Renamer()); } } }
Gdy przekazujemy parametry do aplikacji pojawiają się one w postaci tablicy string’ów jako paramter metody Main(). Wystarczy że obsłużymy to odpowiednio i gotowe!
using System; using System.Windows.Forms; namespace Renamer { static class Program { /// /// The main entry point for the application. /// [STAThread] static void Main(string[] args) { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.Run(new Renamer(args)); } } }
Oczywiście wiąże się to ze zmianą również konstruktora klasy Renamer na sposó, który pozwoli obsłużyć tablicę string’ów.
using System; using System.Diagnostics; using System.IO; using System.Windows.Forms; using Renamer.Properties; namespace Renamer { public partial class Renamer : Form { public Renamer(string[] args) { InitializeComponent(); if(args != null && args.Length > 0) { // Obsługiwanie args[]... } } } }
Po sprawdzeniu czy tablica z argumentami nie jest pusta możemy przejść do właściwej zamiany nazwy plików. Wykorzystamy do tego metodę Rename(string[] files). Opracujmy ją w pliku Renamer.cs
public void Rename(string[] files) { foreach (string filename in files) { try { if (File.Exists(filename)) { string fnWoExt = Path.GetFileNameWithoutExtension(filename); string fnExt = Path.GetExtension(filename); string path = Path.GetDirectoryName(filename) ?? ""; string newFilename = string.Format("{0}{1}", fnWoExt.SafeFilename(), fnExt); File.Copy(filename, Path.Combine(path, newFilename), false); Environment.Exit(1); } } catch (Exception err) { if (Settings.Default.ShowErrors) { var msg = MessageBox.Show(string.Format("{0}{1}{2}", "Nie mogłem utworzyć pliku. Treść błędu:", Environment.NewLine, err.Message), "Wystąpił błąd", MessageBoxButtons.OK, MessageBoxIcon.Warning); if (msg == DialogResult.OK) Environment.Exit(1); } } } }
Linijką 20 się nie przejmuj w tej chwili.
W linii 3 mamy pętlę foreach, w której iterujemy – przetwarzamy każdy element tablicy files osobno. Każde przejście pętli zapisuje do zmiennej filename (linia 3) pojedyńczy plik – w zasadzie jego ścieżkę, którą dalej przetwarzamy.
Mamy tutaj również blok try…catch służący do wyłapania błędów i obsłużenia ich na swój sposób. W części try umieszczamy kod, który chcemy wykonać a w catch kod jaki wykonamy podczas gdy wystąpi błąd np. plik nie będzie istniał lub czy możemy zapisywać pliki w konkretnej lokalizacji.
W linii 7 sprawdzamy czy dany plik istnieje.
Linie 9,10,11 to odczytanie właściwości pliku. Kolejno:
- Nazwa pliku bez rozszerzenia
- Rozszerzenie pliku
- Ścieżkę do pliku (bez jego nazwy)
Pewnie zauważyłeś w linii 11 ten dziwny operator ?? „”. Taki zapis oznacza w tym przypadku że jesli Path.GetDirectoryName(filename) ma wartość NULL czyli poprostu jest pusty (precyzyjnie wskazanie nie określające żadnego adresu) to przypisz do zmiennej path pusty string „”. Chodzi tylko o to aby zabezpieczyć się przez uzyskaniem NULL z Path.GetDirectoryName(filename).
W linii 13 składamy nową nazwę pliku z uwzględnieniem metody statycznej SafeFilename().
Polecam stosowanie konstrukcji z uwzględnieniem string.Format() (btw. kolejny przykład metody typu extension). Jeśli chcemy połączyć dwa lub więcej ciągów tekstowych (konkatenacja) to ta metoda na prawdę się sprawdza. Jako jej pierwszy paramter podajemy sposób połączenia np. w tym przypadku łączyliśmy ze sobą dwa stringi więc zapis sposobu łączenie (bez uwzględnienia formatowania) wygląda następująco {0}{1}. Za {0} podstawiany jest ciąg fnWoExt.SafeFilename(), natomiast za {1} ciąg fnExt.
Niby szybciej jest napisać „ciąg1” + „ciąg 2” + „ciąg3”, ale wierz mi – to tylko pozory. Metoda ta oferuje dodatkowo szereg argumentów formatujących np. {0}:0.00 czyli wyświetlenie tekstu jako typ zmienno przecinkowy z dwoma znakami po przecinku czy choćby {0}:yyyy formatowanie roku.
Więcej o tej metodzie możesz poczytać na http://msdn.microsoft.com/pl-pl/library/system.string.format.aspx
Wracając do głównego wątku… z tak przygotowanej – już bezpiecznej nazwy pliku – możemy przejść dalej.
Całość będzie polegała na skopiowaniu pod inną nazwą (bezpieczną) pliku bazowego, a zatem w linii 14 mamy to kopiowanie pliku. Pierwszy parametr do adres pliku źródłowego a drugi to adres pliku docelowego.
Po skopiowaniu zamykamy program Enviroment.Exit(1).
Czymże była by aplikacja bez ustawień, które użytkownik potem może zmienić np. klikając w checkboxa ? Zatem i my tak zrobimy. Ustawienia możemy zapisać z poziomu kodu programu
np:.
Settings.Default.ShowErrors = cbShowErrors.Checked;
lub korzystając z edytora w Visual Studio. Aby utworzyć pierwsze ustawienie z panelu Solution Explorer kliknij na trójkącik przy opcji Properties – rozwinie się lista podopcji. Następnie dwukrotnie na opcję Settings.settings. Pokaże się okno ustawień w zasadzie tabela w stylu klucz-wartość i to właśnie tam musimy wprowadzić nasze pierwsze ustawienie a potem tylko się do niego odnosić z kodu.
Jeśli już to mamy to ustawienia w VS to nic innego jak zmienne, które możemy zapisać aby po zamknięciu programu utrzymały swoje wartości. Robimy to w następujący sposób. Ustawienie ShowErrors jest typu bool czyli przechowuje wartości true/false.
Settings.Default.ShowErrors = true; Settings.Default.Save();
taki zapis spowoduje, że po zamknięciu i otwrciu programu ponownie zmienna ShowErrors nie zmieni się i będzie miała wartość true. Jak widać spsoób używania ustawień jest bardzo prosty i intuicyjny.
Na naszą formę wrzuciłem również checkbox z panelu Toolbox, zmieniłem mu nazwę na cbShowErrors w panelu Properties znajdującym się po prawej stronie okna.
Połączmy teraz ze sobą te dwa elementy. Mając zaznaczony checkbox kliknijmy w ikonkę Events znajdującą się w panelu Properties.
następnie przy opcji CheckedChanged kliknij dwukrotnie. VS wygeneruje automatycznie podpięcie się do zdarzenia (zostanie wywołana metoda) zawsze gdy zmieni się zaznaczenie chceckbox’a. Zostaniemy przekierowani do edytora kodu i nowo utworzonej metody obsługującej to zdarzenie.
private void cbShowErrors_CheckedChanged(object sender, EventArgs e) { Settings.Default.ShowErrors = cbShowErrors.Checked; Settings.Default.Save(); }
Zawsze po zmianie zaznaczenia tego chceckboxa do ustawienia ShowErrors trafi wartość true jeśli chceckbox jest zaznaczony lub false jeśli nie jest. Służy do tego właściwość cbShowErrors.Checked;
Następnie zapisujemy ustawienia na stałe.
Brakuje nam jeszcze automatycznego zaznaczania/odznaczania chceckbox’a tuż po uruchomieniu programu. W tym celu przenosimy się do konstruktora i tuż za metodą InitializeComponent(); wpisujemy
[...] public Renamer(string[] args) { InitializeComponent(); cbShowErrors.Checked = Settings.Default.ShowErrors; if(args != null && args.Length > 0) { [...]
Teraz do właściwości cbShowErrors.Checked; wprowadzamy to co mamy w ustawieniach. Właściwość ta zapewni nam zaznaczenie lub odznaczenie checkbox’a w zależności od wartości ustawienia ShowErrors.
Wróćmy teraz do linii 20 i bloku catch w której sprawdzamy czy ustawienie ShowErrors ma wartość true. Jeśli tak wyświetlamy błąd na ekranie. W linii 23 po kliknięciu OK w wyświetlonym komunikacie błędu zamykamy aplikację.
W zasadzie to wszystko, możemy przetestować go za pomocą klawiszy CTRL + F5, ale należy pamiętać że aby ten program zadziałał jak należy należy na jego ikonkę Rename.exe upuścić jakiś plik tudzież pliki. W rezultacie otrzymujemy kopię pliku o zmienionej nazwie.
s’il vous plaît!
Serdecznie zapraszam do komentowania i oceniania na moim blogu: http://mateuszmanaj.pl/