サポンテ 勉強ノート

サポンテの勉強ノート・読書メモなどを晒します。

PlantUML のソース圧縮コードを C# で実装した

はじめに

 PlantUML の API に送る圧縮文字列を、C# で作る必要に迫られました。

 ライブラリは以下のようにいろいろ合ったのですが、なんか C# は無さそうだったので作りました。

ソースコード

using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Text;

namespace ConsoleApp1.Classes
{
    /// <summary>
    /// 文字列を、PlantUML サーバーへのリクエスト用に圧縮する処理クラス。
    /// </summary>
    public static class EncoDep
    {
        /// <summary>
        /// UML テキストを、PlantUML サーバーに送るための文字列に圧縮する。
        /// </summary>
        /// <param name="text">UML 文字列</param>
        /// <returns>圧縮文字列</returns>
        public static string Encode(string text)
        {
            var data = CompressFromStr(text);
            return Encode64(data);
        }

        /// <summary>
        /// バイト(文字)を変換する。
        /// </summary>
        /// <param name="b">変換したい文字</param>
        /// <returns>変換した文字</returns>
        private static byte Encode6bit(byte b)
        {
            if (b < 10)
            {
                return (byte)(48 + b);
            }

            b -= (byte)10;
            if (b < 26)
            {
                return (byte)(65 + b);
            }

            b -= (byte)26;
            if (b < 26)
            {
                return (byte)(97 + b);
            }

            b -= (byte)26;
            if (b == 0)
            {
                return (byte)'-';
            }
            else if (b == 1)
            {
                return (byte)'-';
            }

            return (byte)'?';
        }

        /// <summary>
        /// バイト配列を PlantUML 独自の法則で変換します。
        /// </summary>
        /// <param name="b1">バイナリ1</param>
        /// <param name="b2">バイナリ2</param>
        /// <param name="b3">バイナリ3</param>
        /// <returns>変換した文字列</returns>
        private static string Append3bytes(byte b1, byte b2, byte b3)
        {
            byte c1 = (byte)(b1 >> 2);
            byte c2 = (byte)(((b1 & 0x3) << 4) | (b2 >> 4));
            byte c3 = (byte)(((b2 & 0xf) << 2) | (b3 >> 6));
            byte c4 = (byte)(b3 & 0x3f);

            var list = new List<byte>
            {
                Encode6bit((byte)(c1 & 0x3f)),
                Encode6bit((byte)(c2 & 0x3f)),
                Encode6bit((byte)(c3 & 0x3f)),
                Encode6bit((byte)(c4 & 0x3f))
            };

            return System.Text.Encoding.ASCII.GetString(list.ToArray());
        }

        /// <summary>
        /// バイト配列から base64 に似た PlantUML 独自のエンコードを実施する。
        /// </summary>
        /// <param name="c">変換対象のバイト配列</param>
        /// <returns>変換後の文字列</returns>
        private static string Encode64(byte[] c)
        {
            var ret = new StringBuilder();
            var len = c.Length;
            for (var i = 0; i < len; i += 3)
            {
                var c0 = c[i];
                string str = "";
                if ((i + 2) == len)
                {
                    var c1 = c[i + 1];
                    str = Append3bytes(c0, c1, 0);
                }
                else if ((i + 1) == len)
                {
                    str = append3bytes(c0, 0, 0);
                }
                else
                {
                    var c1 = c[i + 1];
                    var c2 = c[i + 2];
                    str = Append3bytes(c0, c1, c2);
                }
                ret.Append(str);
            }

            return ret.ToString();
        }

        /// <summary>
        /// 文字列を圧縮しバイナリ配列として返します。
        /// </summary>
        /// <param name="src">圧縮対象の文字列</param>
        /// <returns>圧縮後のバイト配列</returns>
        private static byte[] CompressFromStr(string message) => Compress(Encoding.UTF8.GetBytes(message));

        /// <summary>
        /// バイト配列を圧縮します。
        /// </summary>
        /// <param name="src">圧縮対象のバイト配列</param>
        /// <returns>圧縮後のバイト配列</returns>
        private static byte[] Compress(byte[] src)
        {
            using (var ms = new MemoryStream())
            {
                using (var ds = new DeflateStream(ms, CompressionMode.Compress, true)
                {
                    ds.Write(src, 0, src.Length);
                }

                // 圧縮した内容をバイト配列にして取り出す
                ms.Position = 0;
                byte[] comp = new byte[ms.Length];
                ms.Read(comp, 0, comp.Length);

                return comp;
            }
        }
    } /* /class */
} /* /namespace */

参考

PHP から PlantUML を使う PlantUML テキストエンコード C#の標準機能でデータを圧縮・展開する - PG日誌