dotNET Core 3.1实现cin码表简易解析

最近在学习C#。由于有PowerShell处理文字的经验,我寻思用C#写个cin码表的解析应该不成问题。当然,就我这能力水平,问题大了ww

有些东西我也不好解释,再就是System.Text.Json着实有点难用,暂且只放代码。

环境

操作系统Win10 1909,dotnet --version3.1.100

参考

实践

项目作成

Set-Location D:\prj
dotnet new console -o CinParsing

这样就建好了D:\prj\CinParsing这个控制台项目。语言种类取默认,也就是C#。

确认.\CinParsing.csproj

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp3.1</TargetFramework>
  </PropertyGroup>

</Project>

修改.\Program.cs

using System;
using System.Collections.Generic;
using System.Text.Json;
using System.Text.Unicode;
using System.Text.Encodings.Web;
using System.Text.RegularExpressions;
using System.IO;

namespace CinParsing
{
    using CODE_TABLE = List<Tuple<string, List<string>>>;
    using CODE_WORDS_PAIR = Tuple<string, List<string>>;
    using WORDS = List<string>;
    public static class Program
    {
        public static void Main(string[] args)
        {
            var path = @"D:\work\repo\array30\OpenVanilla\array-special-201509.cin";
            var a = ReadFile(path);

            var j = JsonSerializer.Serialize(a, new JsonSerializerOptions
            {
                Encoder = JavaScriptEncoder.Create(UnicodeRanges.All),
            });
            File.WriteAllText(@"D:\work\b.txt", j);
        }

        private static CODE_TABLE ReadFile(string filePath)
        {
            var list = new CODE_TABLE();

            string startPattern = @"^%chardef\s+begin$";
            string endPattern = @"^%chardef\s+end$";
            string linePattern = @"^(\S+)\s+(.+)$";
            bool flag = false;
            string s = "";

            using (var sr = new StreamReader(filePath))
            {
                while ((s = sr.ReadLine()) != null)
                {
                    if (Regex.Match(s, endPattern).Success)
                    {
                        flag = false;
                    }
                    if (flag)
                    {
                        var match = Regex.Match(s, linePattern);
                        var code = match.Groups[1].Value;
                        var words = new WORDS { match.Groups[2].Value };
                        list.Add(new CODE_WORDS_PAIR(code, words));
                    }
                    if (Regex.Match(s, startPattern).Success)
                    {
                        flag = true;
                    }
                }
            }
            return list;
        }
    }
}

运行

稍微注意一下,代码中的path是从gontera/array30拖下来的。格式还算简单,当它是个CSV(或者说CST?)的扩展就好了。

dotnet run

之后就能看到C:\work\b.txt里面写好了紧凑形式的json,内容大概是[...{"Item1":"to","Item2":["提"]},...]这个样子。

补充

  • Regex.Match(s, endPattern).Success这种写法有些冗长,可以用更方便的Regex.IsMatch(s, startPattern)

  • cin档允许用#开头的行表示注释,这些在当前的例子里不用parse也可以:

    if (s.StartsWith("#")) {
    continue;
    }
  • 关于JsonSerializerOptionsEncoder,需要注意的是,JavaScriptEncoder.Create(UnicodeRanges.All)的范围只能涵盖Basic Multilingual Plane,也就是U+0000~U+FFFF这些;遇到代理对并不会处理,而是保留escaped unicode的形式。这个是JSON格式的设计使然。如果真的很想连这种情况也要解码,可以用Regex.Unescape(j)解决。

  • 刚才写的转换成JSON的办法是不是挺简单?那么换个更复杂但灵活的如何?

    private static string ConvertToJson(CODE_TABLE codeTable)
    {
    string s;
    var options = new JsonWriterOptions
    {
        Indented = true,
    };
    using (var stream = new MemoryStream())
    {
        using (var writer = new Utf8JsonWriter(stream, options))
        {
            writer.WriteStartObject();
            foreach (Tuple<string, List<string>> pair in codeTable)
            {
                writer.WritePropertyName(pair.Item1);
                    writer.WriteStartArray();
                foreach (string word in pair.Item2)
                {
                    writer.WriteStringValue(word);
                }
                    writer.WriteEndArray();
            }
            writer.WriteEndObject();
        }
        s = Encoding.UTF8.GetString(stream.ToArray());
    }
    // return s;
    return Regex.Unescape(s);
    }

    这个Utf8JsonWriter是不是很爽www不过好处在于可以更加灵活地控制。结果比如:

    {
    "ak": [
    "大"
    ],
    "ah": [
    "不"
    ],
    ...
  • 查码和反查的办法也一并放上来:

    private static WORDS Lookup(string code, CODE_TABLE codeTable)
    {
    var words = new WORDS { };
    foreach (var pair in codeTable)
    {
        if (pair.Item1.Equals(code))
        {
            foreach (var word in pair.Item2)
            {
                words.Add(word);
            }
        }
    }
    return words;
    }
    private static CODES ReverseLookup(string word, CODE_TABLE codeTable)
    {
    var codes = new CODES { };
    foreach (var pair in codeTable)
    {
        if (pair.Item2.Contains(word))
        {
            codes.Add(pair.Item1);
        }
    }
    return codes;
    }

后记

真的是有点麻烦,不过终归能用了。

很多语法和库都是初次接触,比如using CODE_TABLE = List<Tuple<string, List<string>>>;这种神奇操作以及(大概)新鲜出炉但麻烦得不行的System.Text.Json。再就是选择数据类型时全是周折,最后为了保持原汁原味才使用这种冗长的手段。

不过,好玩确实好玩👍

InSb

InSb

只是跟工作和生活相关的记录