Jun 17 2012
Изчистване на BOM от много файлове.
Какво е BOM, за какво ни е и кога ни пречи. Моето решение на проблема, за изчистване на BOM от много файлове едновременно.
Какво е BOM ?
Ако създадете чисто нов текстови документ ( txt) и го запишете празен, имащ единствено име, то той би следвало да е 0 bytes. А случвало ли ви се е да е 3 bytes ? Е това е заради BOM или “Byte order mark”. Всъщност това представляват 3 байта в началото на текстовия файл, който оказват, че текста е в даден “encoding”. При отваряне на файла не се забелязва разлика, но при отваряне в двоичен вид се забелязва, че в началото на файла има записана последователност, която несме писали ние. Примерно за UTF – 8 ще бъде записано “EF BB BF
” или тези специални символи “ï»¿
” в зависимост как се отвори файла, след което ще се намира съдържанието на файла. Това в повечето случай не е проблем и дори помага на програмите който отварят файловете.
Какъв е проблема ?
В една от системите се зареждат файлове подадени по електронен път. Файловете са XML формат, но се подават от много хора и от много системи. Явно някои от потребителите бяха нагласили да се създават файловете им с BOM. Както казах това в общия случай не е лош, но в моя се появи проблем. При зареждане на файловете в системата, връщаше грешка, че формата не е коректен и ги отхвърляше. Това се получава при “stream”-то на файла. Тогава системите прочитат и тези 3 първоначални байта и съответно изгърмяват за некоректни данни.
Едно решение на проблема
След като разбрахме с моя колега какъв е проблема, започнахме да редактираме файловете. Естествено първото, което ни дойде на ум е да ги отваряме, и да ги конвертираме да са без BOM. Това става много лесно ако имате инсталиран Notepad++. Това е безплатна програма с изключително много възможности. Като отворите файла с тази програмка, просто от менюто “Encoding” избирате “Convert to UTF – 8 without BOM”.
Всичко беше идеално. Файловете се приемаха от системата нямаше проблем вече. Проблема беше че установихме че са няколко стотин файла в различни директории и поддиректории. Ами какво да се прави, щом се налага щяхме да го правим. Но изведнъж се оказа, че тези файлове са само за един месец, а трябва да оправим за цялата година. Стана ни ясно, че ще трябва да измислим нещо по хитро. Както на всички е известно, програмистите, необичат да правят монотонна и скучна работа (като да конвертират файлове по цял ден). Освен това аз съм инженер и са ме учили, че основната работа на инженера е да автоматизира човешкия труд. Затова реших да направя нещо по хитро.
Глобално решение на проблема
За щастие в Софтуерната академия на Телерик, тъкмо взехме материала за рекурсия и как рекурсивно да си обхождаме файловата система. Реших да приложа наученото и се заех да напиша малка програмка. която да ми реши проблема глобално. Предварително искам да отбележа, че това е оригиналния код от тогава, т.е. е един от първите ми проекти с рекурсивно обхождане и не е блестящо направен, не отговаря на много от критериите за качествен код, но тогава ми свърши перфектна работа и исках да го публикувам оригиналния и работещ код (приемете го за beta version).
Проекта се състой от 2 класа на C#. В единия е главната функционалност на програмата, а в другия съм отделил рекурсията.
Main – метода
В основния (Main) на програмата прихващам някой настройки от потребителя, извиквам рекурсията, която ми връща опашка от стрингове (това са пътищата, заедно с имената на файловете) след което се извиква метода за изчистване на BOM.
1: static void Main(string[] args)
2: {
3: Console.WriteLine("Enter path ot press \"Enter\" to get current directory");
4: Console.Write("Path: ");
5: string enterPath = Console.ReadLine();
6: string rootDirectory;
7: if (enterPath != null && enterPath != string.Empty)
8: {
9: rootDirectory = enterPath;
10: }
11: else
12: {
13: rootDirectory = Directory.GetCurrentDirectory();
14: }
15:
16: string filesType = @"*.xml";
17: Queue<string> filesNames = DisplayAllFiles.GetFilesInDirectory(rootDirectory, filesType);
18:
19: Console.WriteLine();
20: Console.WriteLine("-------------------------------------------------");
21: Console.WriteLine("Found " + filesNames.Count + " liles");
22: Console.WriteLine("Do you wont to change this files ? Yes or No");
23: Console.WriteLine("Files will be overwritten !!!");
24: Console.Write("Choice : ");
25:
26: string choice = Console.ReadLine();
27: if (choice.Equals("Yes"))
28: {
29: CorrectFoundedXMLFiles(filesNames);
30: }
31:
32: Console.WriteLine("End...");
33: Console.ReadLine();
34: }
От ред 3 до 14 прочитам главната директория от която ще започне рекурсията. Тук давам възможност ако потребителя иска да въведе директория да може да го направи, а ако не, то просто да е сложил EXE файла в директорията която са файловете и трябва да натисне просто Enter. Следва да задам типа на файловете, който ще редактирам ред 16, в моя случай XML. На ред 17 извиквам рекурсивния алгоритъм и резултата го записвам в опашка от стрингове. По този начин създавам абстракция на рекурсивния алгоритъм и мога да го сменя по всяко време, като резултата от него трябва да е стандартна стрингова опашка. Или да използвам алгоритъма в друга програма като просто и подам главната директория от която да почне рекурсията и файловете които търся.
Редове от 19 до 24 са информативни. Показвам на потребителя колко файла са открити и питам дали е сигурен. че иска да ги презапише. За по голяма сигурност приемам за сигурен потребител който е въвел точно израза “Yes” (ред 27). Ако е сигурен, че иска да ги презапише, извиквам метода за изчистване на BOM и презаписване на файловете (ред 29).
Метод за премахване на BOM от файловете
Метода който направих е изключително просто реализиран. Първо ето кода, а после обясненията
1: private static void CorrectFoundedXMLFiles(Queue<string> filesNames)
2: {
3: int counter = 0;
4: int allFiles = filesNames.Count;
5: while (filesNames.Count > 0)
6: {
7: string fileName = filesNames.Dequeue();
8: string newFileName = fileName;
9:
10: StreamReader reader = new StreamReader(fileName);
11: string allText;
12: using (reader)
13: {
14: allText = reader.ReadToEnd();
15: }
16: if (allText != null)
17: {
18: StreamWriter writer = new StreamWriter(newFileName,false);
19: using (writer)
20: {
21: counter++;
22: writer.Write(allText);
23: Console.WriteLine(fileName);
24: }
25: }
26: else
27: {
28: Console.WriteLine(fileName + " =========== is empty !");
29: }
30: }
31: Console.WriteLine("Found files = " + allFiles);
32: Console.WriteLine("Change files = " + counter);
33: }
С няколко думи казано, правя един цикъл, който се върти докато има записи в опашката т.е. докато има още файлове за обработка. Името на новия файл нарочно го задавам с нова променлива, това е защото имах идея в началото, файловете да не се презаписват, а да се създават нови. В този случай просто трябва да се зададе на променливата “newFileName” това, което желаете да бъде новото име на файловете.
Та какво правя, създавам си един “StreamReader” прочитам целия файл, ако не е празен, създавам “StreamWriter”, като параметри му подавам името на файла и параметър “FALSE”. Точно този параметър оказва дали файла да има или на няма BOM. След което просто го записвам.
За потребителя съм създал една променлива “counter”, която инкрементирам когато записвам файл. По този начин накрая вадя статистика колко са били всичките файлове и колко съм променил.
Рекурсивно обхождане на файловата система
Както споменах вече, реших да използвам рекурсивно обхождане на файловата система и по конкретно търсене в дълбочина. Това означава, че рекурсивно стигам до най – вложената директория (папка), и тогава извличам файловете от нея, след което се връщам едно ниво нагоре и отново вземам файловете, които отговарят на критериите, и така докато стигна до главната директория (която е зададена).След което извличам и файловете от главната директория и връщам опашка от стрингове. Стринговете всъщност са целия път до файла, заедно с неговото име. Файловете и директориите, които немогат да бъдат достигнати, ги отпечатвам на конзолата и съобщавам причината. А сега ето и кода
1: class DisplayAllFiles
2: {
3: private static string searchFiles;
4: private static bool haveException = false;
5: private static Queue<string> filesNames;
6: private static void GetDir(string dirName)
7: {
8: string[] currDirNames;
9: try
10: {
11: currDirNames = Directory.GetDirectories(dirName);
12: }
13: catch (UnauthorizedAccessException e)
14: {
15: Console.WriteLine(dirName + "- unautorization");
16: haveException = true;
17: return;
18: }
19:
20: catch (ArgumentNullException ane)
21: {
22: Console.WriteLine(dirName + "- ArgumentNullException");
23: haveException = true;
24: return;
25: }
26: catch (ArgumentException a)
27: {
28: Console.WriteLine(dirName + "- ArgumentException");
29: haveException = true;
30: return;
31: }
32: catch (PathTooLongException ane)
33: {
34: Console.WriteLine(dirName + "- PathTooLongException");
35: haveException = true;
36: return;
37: }
38:
39: catch (DirectoryNotFoundException ane)
40: {
41: Console.WriteLine(dirName + "- DirectoryNotFoundException");
42: haveException = true;
43: return;
44: }
45: catch (IOException ane)
46: {
47: Console.WriteLine(dirName + "- IOException");
48: haveException = true;
49: return;
50: }
51:
52: foreach (string currDirName in currDirNames)
53: {
54: GetDir(currDirName);
55: if (!haveException)
56: {
57: GetFilesNames(currDirName);
58: }
59: haveException = false;
60: }
61: }
62:
63: private static void GetFilesNames(string directoryName)
64: {
65: string[] currDirFilesNames = Directory.GetFiles(directoryName, searchFiles);
66: foreach (var file in currDirFilesNames)
67: {
68: filesNames.Enqueue(file);
69: }
70: }
71:
72: public static Queue<string> GetFilesInDirectory(string rootDirectoryName, string filesType)
73: {
74: filesNames = new Queue<string>();
75: searchFiles = filesType;
76: GetDir(rootDirectoryName);
77: try
78: {
79: GetFilesNames(rootDirectoryName);
80: }
81: catch (UnauthorizedAccessException)
82: {
83: Console.WriteLine(rootDirectoryName + "- unautorization");
84: }
85: catch (ArgumentNullException ane)
86: {
87: Console.WriteLine(rootDirectoryName + "- ArgumentNullException");
88: }
89: catch (ArgumentException a)
90: {
91: Console.WriteLine(rootDirectoryName + "- ArgumentException");
92: }
93: catch (PathTooLongException ane)
94: {
95: Console.WriteLine(rootDirectoryName + "- PathTooLongException");
96: }
97: catch (DirectoryNotFoundException ane)
98: {
99: Console.WriteLine(rootDirectoryName + "- DirectoryNotFoundException");
100: }
101: catch (IOException ane)
102: {
103: Console.WriteLine(rootDirectoryName + "- IOException");
104: }
105:
106: return filesNames;
107: }
108: }
Както казах, това е първа версия, има много несъвършенства и неотговаря на всички принципи на качествен код. Времето което и отделих небеше много, но ми свърши чудесна работа. Благодарение на знанията, които придобих в софтуерната академия на Телерик, успях да направя тази малка програмка и да свърша зададената ми работа в срок и с успех . Програмата може да се направи да променя и други файлове, не само XML, като се зададе друг тип или да се направи да се въвежда динамично.
Ако някой желае, предоставям целия проект : Clear BOM from XML Overwrite Files
А тук е само EXE файла ако някой има нужда просто да я използва: Clear BOM from XML Overwrite Files – EXE