潛盾機-解決VS2015程式檔BIG5相容問題

改用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

歡迎推文分享:
Published 07 August 2015 07:34 AM 由 Jeffrey
Views: 8,962



意見

# ChrisTorng said on 06 August, 2015 10:39 PM

很久以前我就做過這東西了,原因當然不是 VS2015。

是因為 TFS 對於同一檔案的編碼有異動時,會無法做前後版本的檔案內容比較。記得好像也有遇到網頁輸出到前端有出一些編碼的問題,因此寫程式把所有非 UTF-8 文字檔案均轉成 UTF-8。

我的工具特點在於,要整合 TFS,當時 TFS 未簽出檔案均為唯讀 (我現在用的 TFS2012/VS2013/VS2015 並無唯讀),因此必須要先列出列表,讓使用者先簽出那些檔案,再按一鍵開始轉換。因為是整合 TFS 的功能,因此不做備份檔。如果有需要復原,用 TFS 復原功能即可。新的 TFS 好像沒有唯讀了,就沒有這個必要。

與您程式還有些不同,我的查找條件可以使用 */! 等萬用字元,當然一般是使用副檔名,可由命令列第二個參數傳入,使用 , 或 ; 分隔多個條件。程式中有提示可用的條件。

偵測編碼的方法不是土法,而是靠 StreamReader 建構式之 detectEncodingFromByteOrderMarks 參數,及 StreamReader.CurrentEncoding 取得。以前應該是正常的,剛剛測好像有些問題...還沒找到原因...

另會略過 bin/obj 資料夾。

# ChrisTorng said on 06 August, 2015 10:40 PM

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;

           }

       }

   }

}

# Jeffrey said on 07 August, 2015 12:04 AM

to ChrisTorng ,感謝分享!(偷學到detectEncodingFromByteOrderMarks 這招,:P)

# ChrisTorng said on 11 August, 2015 09:10 PM

ForceUTF8 (with BOM) visualstudiogallery.msdn.microsoft.com/d94a3ad9-0549-4641-89b7-d858407bd6e9 我沒用過,不過也許可以從源頭改善這個問題,轉檔再也沒有需要了?

# Jeffrey said on 12 August, 2015 01:24 AM

to ChrisTorng, 謝謝你的分享,讓我找到一個更快的解法:blog.darkthread.net/post-2015-08-12-vs2015-big5-fix.aspx

你的看法呢?

(必要的) 
(必要的) 
(選擇性的)
(必要的) 
(提醒: 因快取機制,您的留言幾分鐘後才會顯示在網站,請耐心稍候)

5 + 3 =

搜尋

Go

<August 2015>
SunMonTueWedThuFriSat
2627282930311
2345678
9101112131415
16171819202122
23242526272829
303112345
 
RSS
創用 CC 授權條款
【廣告】
twMVC
最新回應

Tags 分類檢視
關於作者

一個醉心技術又酷愛分享的Coding魔人,十年的IT職場生涯,寫過系統、管過專案, 也帶過團隊,最後還是無怨無悔地選擇了技術鑽研這條路,近年來則以做一個"有為的中年人"自許。

文章典藏
其他功能

這個部落格


Syndication