更改xlsm文件中的宏脚本编码

之前任职于一家台湾公司, 公司员工电脑默认的默认编码格式使用的是港台地区的 Big5 编码, 俗称 大五码, 且员工没有管理员权限, 不能修改, 而服务器使用的编码是GBK

由于VBA年代比较久, 使用的还是代码页表示不同语言的字符集(GBK的代码页 936, Big5 的代码页面 950), 没有使用Unicode, 当在员工电脑录制编写的VBA脚本, 保存在xlsm文件中, 需要放到服务器上去执行时, 中文部分会显示乱码

于是, 编写了下面这段代码来转换xlsm文件中的VBA脚本编码, 主要做了以下事情

  1. xlsm文件中的VBA代码导出为.bas代码文件
  2. 转换纯文本代码文件的编码格式(big5改为GBK)
  3. 将转换编码之后的代码导入xlsm文件
  4. 生成exe后, 可以通过命令行执行, 也可以直接拖动图标将xlsm文件拖到生成的.exe文件上

如果有需求要批量导出xlsm文件中的脚本代码, 下面的代码修改一下也可以实现

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
using System;
using System.IO;
using System.Text;
using Excel = Microsoft.Office.Interop.Excel;
using Microsoft.Vbe.Interop;

namespace XLSM_Big52GBK_Converter
{
    class Program{
        static void ConvertEncoding(string sourcePath, string destPath){
            string content;
            // 以 Big5 编码读取文本内容
            content = File.ReadAllText(sourcePath, Encoding.GetEncoding(950));
            // 以 GBK 编码写入新文本文件
            File.WriteAllText(destPath, content, Encoding.GetEncoding(936));
        }

        static void End(){
            Console.WriteLine("OK");
            Console.WriteLine("Press Any Key to Close");
            Console.ReadKey();
        }

        static void Main(string[] args){
            Excel.Application app = new Excel.Application();
            app.DisplayAlerts = false;
            string oFilename, nFilename, dir, oBas, nBas;
            if (args.Length == 0){
                // 因为命令行窗口也是使用的big5代码页, 所以此处输出内容需要使用 繁体字
                Console.WriteLine(" 輸入xlsm文件的完整路徑:");
                nFilename = Console.ReadLine();
            }
            else{
                nFilename = args[0];
            }
            nFilename = nFilename.Trim('\"');
            if (!nFilename.ToLower().EndsWith(".xlsm")){
                return;
            }
            Console.WriteLine("converting...");

            dir = Path.GetDirectoryName(nFilename);
            oFilename = Path.Combine(dir, Path.GetFileNameWithoutExtension(nFilename) + "_old.xlsm");
            oBas = Path.Combine(dir, "old.bas");
            nBas = Path.Combine(dir, "new.bas");
            File.Copy(nFilename, oFilename, true);

            Excel.Workbook oWb = app.Workbooks.Open(oFilename);
            Excel.Workbook nWb = app.Workbooks.Open(nFilename);

            foreach (VBComponent cp in nWb.VBProject.VBComponents){
                if (cp.Type == vbext_ComponentType.vbext_ct_StdModule){
                    int n = cp.CodeModule.CountOfLines;
                    cp.CodeModule.DeleteLines(1, n);
                }
            }

            foreach (VBComponent comp in oWb.VBProject.VBComponents){
                if (comp.Type == vbext_ComponentType.vbext_ct_StdModule){
                    comp.Export(oBas);
                    // 转换文本编码
                    ConvertEncoding(oBas, nBas);
                    // 导入转换编码后的 bas 文件
                    nWb.VBProject.VBComponents.Item(comp.Name).CodeModule.AddFromFile(nBas);
                }
            }
            oWb.Close(false);
            nWb.Close(true);
            End();
        }
    }
}

VBA 实现Excel单元格(或者Word)中的繁简转换, 可以使用以下两种方法

  1. 调用 EXCEL 的 TCSCConv.SharedAddin, 对sheet内容进行繁简转换
  2. 调用系统API  LCMapString 对String进行繁简转换

受限于本机非Unicode程式的编码格式, 可能会出现VBA本身就无法识别出简体中文的情况(当本机编码为big5时)

1
2
Private Declare Function LCMapString Lib "kernel32" Alias "LCMapStringA" (ByVal Locale As Long, ByVal dwMapFlags As Long, ByVal lpSrcStr As String, ByVal cchSrc As Long, ByVal lpDestStr As String, ByVal cchDest As Long) As Long
Private Declare Function lstrlen Lib "kernel32" Alias "lstrlenA" (ByVal lpString As String) As Long