Visual Studio 拡張と Roslyn インクリメンタルジェネレーターを使用した Base64 プリプロセッサの作成

Visual Studio 拡張と Roslyn インクリメンタルジェネレーターを用いて、Base64 変換ツールを作成します。

VS 拡張バージョン

以下のコードでは、EnvDTE80 NuGet パッケージが必要です。


using EnvDTE;
using EnvDTE80;
using System;
using System.Collections.Generic;
using System.IO;

namespace base64Tool
{
    public static class MimeTypeConverter
    {
        public static string ConvertExtToMimeType(string ext)
        {
            var mappings = new Dictionary<string, string>
            {
                { ".jpg", "image/jpeg" },
                { ".jpeg", "image/jpeg" },
                { ".png", "image/png" },
                { ".gif", "image/gif" }
            };

            return mappings.TryGetValue(ext.ToLower(), out var mimeType) ? mimeType : string.Empty;
        }

        public static string GetFileMimeType(string filePath)
        {
            var ext = Path.GetExtension(filePath);
            return ConvertExtToMimeType(ext);
        }
    }

    public static class Base64Encoder
    {
        public static string EncodeFileToDataURL(string filePath)
        {
            var mimeType = MimeTypeConverter.GetFileMimeType(filePath);
            var bytes = File.ReadAllBytes(filePath);
            var encodedContent = Convert.ToBase64String(bytes);

            return CreateDataURI(mimeType, encodedContent);
        }

        private static string CreateDataURI(string mimeType, string encodedContent)
        {
            return !string.IsNullOrWhiteSpace(mimeType) 
                ? $"data:{mimeType};base64,{encodedContent}" 
                : encodedContent;
        }
    }

    internal class Program
    {
        static void Main()
        {
            var dte = Marshal.GetActiveObject("VisualStudio.DTE") as DTE2;
            var selectedItem = dte.ToolWindows.SolutionExplorer.SelectedItems.Item(1);
            var projectItem = (ProjectItem)selectedItem.Object;

            var filePath = projectItem.FileNames[0];
            var encodedContent = Base64Encoder.EncodeFileToDataURL(filePath);

            var outputFilePath = GenerateOutputPath(filePath);
            File.WriteAllText(outputFilePath, encodedContent);

            System.Diagnostics.Process.Start(outputFilePath);
        }

        private static string GenerateOutputPath(string sourcePath)
        {
            var directory = Path.GetDirectoryName(sourcePath);
            var fileNameWithoutExt = Path.GetFileNameWithoutExtension(sourcePath);
            return Path.Combine(directory, $"{fileNameWithoutExt}_base64.txt");
        }
    }
}

Roslyn インクリメンタルジェネレーター

以下は、Roslyn ジェネレーターでの実装例です。


using Microsoft.CodeAnalysis;
using System.Linq;

namespace ResourceBase64GeneratorApp
{
    public static class MimeTypeMapper
    {
        public static string MapExtToMimeType(string ext)
        {
            var mimeMap = new Dictionary<string, string>
            {
                { ".jpg", "image/jpeg" },
                { ".jpeg", "image/jpeg" },
                { ".png", "image/png" },
                { ".gif", "image/gif" }
            };

            return mimeMap.TryGetValue(ext.ToLower(), out var type) ? type : string.Empty;
        }

        public static string FetchFileMimeType(string path)
        {
            var extension = Path.GetExtension(path);
            return MapExtToMimeType(extension);
        }
    }

    public static class DataURIEncoder
    {
        public static string EncodeFile(string filePath)
        {
            var mimeType = MimeTypeMapper.FetchFileMimeType(filePath);
            var fileBytes = File.ReadAllBytes(filePath);
            var base64Str = Convert.ToBase64String(fileBytes);

            return ConstructDataURI(mimeType, base64Str);
        }

        private static string ConstructDataURI(string type, string base64)
        {
            return !string.IsNullOrWhiteSpace(type) 
                ? $"data:{type};base64,{base64}" 
                : base64;
        }
    }

    [Generator]
    public class Base64ResourceGen : IIncrementalGenerator
    {
        public void Initialize(IncrementalGeneratorInitializationContext ctx)
        {
            var files = ctx.AdditionalTextsProvider;

            ctx.RegisterSourceOutput(files, (ctxOut, file) =>
            {
                var projPath = RetrieveProjectPath(file.Path);
                WriteCode(ctxOut, projPath, file.Path);
            });
        }

        private static void WriteCode(SourceProductionContext ctx, string projPath, string resourcePath)
        {
            ExtractNamespaceAndConstName(projPath, resourcePath, out var ns, out var constName);
            var dataURI = DataURIEncoder.EncodeFile(resourcePath);
            var code = $@"using System;

namespace {ns}
{{
    public static partial class Base64Resources
    {{
        public const string {constName} = ""{dataURI}"";
    }}
}}
";

            var genFileName = $"{constName}_{ns.Replace(".", "_")}.cs";
            ctx.AddSource(genFileName, code);
        }

        private static void ExtractNamespaceAndConstName(string projPath, string resPath, out string ns, out string constName)
        {
            var rootDir = Path.GetDirectoryName(projPath);
            var resDir = Path.GetDirectoryName(resPath);

            var subNsPart = resDir.Substring(rootDir.Length).TrimStart(Path.DirectorySeparatorChar);
            var rootNs = Path.GetFileNameWithoutExtension(projPath);

            var subNsTokens = subNsPart.Split(Path.DirectorySeparatorChar, StringSplitOptions.RemoveEmptyEntries);
            var namespaces = new List<string> { rootNs };
            namespaces.AddRange(subNsTokens);

            ns = string.Join(".", namespaces);
            constName = Path.GetFileNameWithoutExtension(resPath);
        }

        private static string RetrieveProjectPath(string origPath)
        {
            var dir = Path.GetDirectoryName(origPath);
            for (int depth = 0; depth < 4; depth++)
            {
                if (HasCsProj(dir)) return FindCsProjInFolder(dir);
                dir = Path.GetDirectoryName(dir);
            }
            return string.Empty;
        }

        private static bool HasCsProj(string folder)
        {
            return Directory.GetFiles(folder, "*.csproj").Any();
        }

        private static string FindCsProjInFolder(string folder)
        {
            return Directory.GetFiles(folder, "*.csproj").FirstOrDefault();
        }
    }
}

タグ: VisualStudio Roslyn csharp Base64 CodeGeneration

5月31日 00:57 投稿