潛盾機-解決VS2015程式檔BIG5相容問題
6 |
改用VS2015後沒多久就發現它處理BIG5(ANSI)編碼程式碼的原則不同於以往(推測與編譯器改用Roslyn有關),導致部分使用BIG5編碼存檔的古老程式檔,會因許功蓋造成編譯錯誤。
PO文隔兩天同事跟我說,他們換VS2015後也射了好一陣子茶包,最後爬文又爬回我的文章。XD 後來聊到可以寫程式把所有BIG5編碼程式檔轉成UTF8一勞永逸,同事說檔案沒幾個,手動另存就搞定了,還不需要養乳牛。
這兩天,收到網友留言詢問VS2015是否會修正這個問題;也有網友提到手上專案有成千上萬個cs,改了一個BIG5,還有千千萬萬個BIG5,只好跟VS2015說Goodbye。
首先,雖然不確定VS2015會不會針對此一Issue進行修正,但依我之見,BIG5編碼已經過時多年,除了在VS2015產生不相容,遇到中文難字及其他語系文字都得額外處理(在Visual Studio.NET 2003時代就有處理過)。因此,不管VS2015會不會修正,將程式碼統一改存UTF8編碼是正確的方向。
問題來了,如網友所說,若專案有上萬支cs,一一手動轉存UTF8的浩大工程確實令人心寒。我最愛打造這種可以省時省力的潛盾機,就寫支批次轉檔程式搞定吧!
這裡我假設專案的.cs檔案分成兩類,一部分已經是UTF8或Unicode編碼,其餘則為BIG5編碼(假設全部都是BIG5,排除摻雜簡體中文、日文等其他ANSI編碼的情況)。因此,可以用Directory.GetFiles()搜尋找出特定目錄(含子目錄)下的指定檔案型別(例如:cs及js),檢查檔案註記略過UTF8或Unicode編碼檔案,找到BIG5檔案後先用BIG5 Encoding讀取,再以UTF8 Encoding寫入就完成轉檔,而原來的檔案則加上.big5.bak副檔名備份保留。
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; namespace B2UBatchConverter { class Program { public class AnalyzeResult { public string Content; public Encoding Encoding; } //REF:http://goo.gl/jAJgIr by Rick Strahl public static AnalyzeResult AnalyzeFile(string srcFile) { //預設為Big5 Encoding enc = Encoding.GetEncoding(950); //由前五碼識別出UTF8、Unicode、UTF32等編碼,其餘則視為 byte[] buffer = new byte[5]; using (FileStream file = new FileStream(srcFile, FileMode.Open)) { file.Read(buffer, 0, 5); file.Close(); if (buffer[0] == 0xef && buffer[1] == 0xbb && buffer[2] == 0xbf) enc = Encoding.UTF8; else if (buffer[0] == 0xfe && buffer[1] == 0xff) enc = Encoding.Unicode; else if (buffer[0] == 0 && buffer[1] == 0 && buffer[2] == 0xfe && buffer[3] == 0xff) enc = Encoding.UTF32; else if (buffer[0] == 0x2b && buffer[1] == 0x2f && buffer[2] == 0x76) enc = Encoding.UTF7; } //使用指定的Encoding讀取內容 return new AnalyzeResult() { Content = File.ReadAllText(srcFile, enc), Encoding = enc }; } static void Main(string[] args) { //args = new string[] { "D:\\Lab\\L805\\ConApp" }; string path = args[0]; //列舉要搜尋轉碼的副檔名 var scanFileTypes = "cs,js".Split(','); //略過不處理的資料夾名稱 var skipFolders = "bin,obj".Split(','); foreach (var file in //列舉所有子目錄下的檔案 Directory.GetFiles(path, "*.*", SearchOption.AllDirectories)) { //取得副檔名 var ext = Path.GetExtension(file).TrimStart('.').ToLower(); //若非預先指定的副檔名就略過不處理 if (!scanFileTypes.Contains(ext)) continue; //處於\bin\* \obj\*目錄下的檔案也一律略過 if (skipFolders.Any(o => file.Contains( Path.DirectorySeparatorChar + o + Path.DirectorySeparatorChar))) continue; //讀取檔案內容並識別編碼 var analysis = AnalyzeFile(file); if (analysis.Encoding.CodePage == 950) //BIG5編號檔案才要處理 { Console.Write("Process File {0}...", file); //將原檔更名為*.big5.bak var bakFile = file + ".big5.bak"; if (File.Exists(bakFile)) File.Delete(bakFile); File.Move(file, bakFile); //重新以UTF8寫入 File.WriteAllText(file, analysis.Content, Encoding.UTF8); Console.WriteLine(" done!"); } else { Console.WriteLine("Skip File {0} / {1}", file, analysis.Encoding.EncodingName); } } } } } |
專案編譯成Console Application,執行時提供路徑名稱作為參數,轉換程式就會掃瞄該目錄下所有的.cs及.js,一口氣將BIG5編碼程式碼轉存成UTF8。
執行範例如下,原本BIG5編碼的B5Class.cs另存成B5Class.cs.big5.bak,而B5Class.cs已改為UTF8編碼,就算有成千上萬支程式要轉碼也不怕。
希望這個工具能解決一些朋友的困擾。
【提醒】批次轉換前請務必先備份,以避免轉換出錯造成資料遺失。
【2015-08-07 更新】 感謝網友黃文生、Norton Lin在FB專頁留這補充兩款現成軟體工具:ConvertZ以及UTFCast也可以做到批次編碼轉換。 另外,ChrisTorng 也分享可與TFS整合的批次轉檔,並提到靠 StreamReader 建構式之 detectEncodingFromByteOrderMarks 參數偵測Encoding的小技巧。
謝謝大家的回饋。
【2015-08-12 更新】VS2015程式檔BIG5相容問題快速解法-修改csproj/vbproj
Comments
# by ChrisTorng
很久以前我就做過這東西了,原因當然不是 VS2015。 是因為 TFS 對於同一檔案的編碼有異動時,會無法做前後版本的檔案內容比較。記得好像也有遇到網頁輸出到前端有出一些編碼的問題,因此寫程式把所有非 UTF-8 文字檔案均轉成 UTF-8。 我的工具特點在於,要整合 TFS,當時 TFS 未簽出檔案均為唯讀 (我現在用的 TFS2012/VS2013/VS2015 並無唯讀),因此必須要先列出列表,讓使用者先簽出那些檔案,再按一鍵開始轉換。因為是整合 TFS 的功能,因此不做備份檔。如果有需要復原,用 TFS 復原功能即可。新的 TFS 好像沒有唯讀了,就沒有這個必要。 與您程式還有些不同,我的查找條件可以使用 */! 等萬用字元,當然一般是使用副檔名,可由命令列第二個參數傳入,使用 , 或 ; 分隔多個條件。程式中有提示可用的條件。 偵測編碼的方法不是土法,而是靠 StreamReader 建構式之 detectEncodingFromByteOrderMarks 參數,及 StreamReader.CurrentEncoding 取得。以前應該是正常的,剛剛測好像有些問題...還沒找到原因... 另會略過 bin/obj 資料夾。
# by ChrisTorng
using System; using System.IO; using System.Text; namespace ConvertNotUTF8Files { // sample pattern: *.cs,*.cshtml,*.aspx,*.asmx,*.asax*,*.master,.xaml,*.config,*.html,*.htm,*.xml,*.css,*.scss,*.skin,*.browser,*.settings,*.StyleCop,*.resx,*.manifest,*.ts,*.js,*.json internal class Program { private static void Main(string[] args) { if (args.Length != 2 || !Directory.Exists(args[0])) { Console.WriteLine("Usage: ConvertNotUTF8Files [path] [pattern1;pattern2;...]"); Console.ReadKey(); return; } string[] patterns = args[1].Split(new char[] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries); if (!DisplayNotUTF8FilesWithPatterns(args[0], patterns)) { Console.WriteLine("All files are UTF8 encoding, no need to convert. Press any key to exit..."); Console.ReadKey(); return; } Console.WriteLine("Please check out these files first, then press any key to start convertion..."); Console.ReadKey(); ConvertNotUTF8FilesWithPatterns(args[0], patterns); Console.WriteLine("Converted, press any key to exit..."); Console.ReadKey(); } private static bool DisplayNotUTF8FilesWithPatterns(string path, string[] patterns) { bool found = false; foreach (string pattern in patterns) { if (DisplayNotUTF8Files(path, pattern)) { found = true; } } return found; } private static bool DisplayNotUTF8Files(string path, string pattern) { bool found = false; foreach (string file in Directory.GetFiles(path, pattern, SearchOption.AllDirectories)) { if (file.Contains("\\bin\\") || file.Contains("\\Bin\\") || file.Contains("\\obj\\")) { continue; } Encoding encoding = GetEncoding(file); if (encoding != Encoding.UTF8) { found = true; Console.WriteLine("{0}\t{1}", encoding.EncodingName, file.Substring(path.Length)); } } return found; } private static void ConvertNotUTF8FilesWithPatterns(string path, string[] patterns) { foreach (string pattern in patterns) { ConvertNotUTF8Files(path, pattern); } } private static void ConvertNotUTF8Files(string path, string pattern) { foreach (string file in Directory.GetFiles(path, pattern, SearchOption.AllDirectories)) { if (file.Contains("\\bin\\") || file.Contains("\\Bin\\")) { continue; } Encoding encoding = GetEncoding(file); if (encoding != Encoding.UTF8) { Console.WriteLine("{0}\t{1}", encoding.EncodingName, file.Substring(path.Length)); //Console.Write("Convert? (y/N)"); //ConsoleKeyInfo key = Console.ReadKey(); //if (key.KeyChar == 'Y' || key.KeyChar == 'y') ConvertNotUTF8FilesToUTF8(file, encoding); } } } private static void ConvertNotUTF8FilesToUTF8(string file, Encoding encoding) { try { string content; using (StreamReader sr = new StreamReader(file, encoding, true)) { content = sr.ReadToEnd(); } using (StreamWriter sw = new StreamWriter(file, false, Encoding.UTF8)) { sw.Write(content); } } catch { Console.WriteLine("Fail"); } } private static Encoding GetEncoding(string file) { using (StreamReader sr = new StreamReader(file, Encoding.Default, true)) { string text = sr.ReadLine(); return sr.CurrentEncoding; } } } }
# by Jeffrey
to ChrisTorng ,感謝分享!(偷學到detectEncodingFromByteOrderMarks 這招,:P)
# by ChrisTorng
ForceUTF8 (with BOM) https://visualstudiogallery.msdn.microsoft.com/d94a3ad9-0549-4641-89b7-d858407bd6e9 我沒用過,不過也許可以從源頭改善這個問題,轉檔再也沒有需要了?
# by Jeffrey
to ChrisTorng, 謝謝你的分享,讓我找到一個更快的解法:http://blog.darkthread.net/post-2015-08-12-vs2015-big5-fix.aspx
# by najamali9
good posts at https://blog.darkthread.net/