最近在学习C#。由于有PowerShell处理文字的经验,我寻思用C#写个cin码表的解析应该不成问题。当然,就我这能力水平,问题大了ww
有些东西我也不好解释,再就是System.Text.Json
着实有点难用,暂且只放代码。
环境
操作系统Win10 1909,dotnet --version
为3.1.100
。
参考
- JSON serialization in .NET - overview
- openvanilla/CinHowTo.markdown(这个其实有点旧,不过在这个例子中不影响)
实践
项目作成
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; }
关于
JsonSerializerOptions
的Encoder
,需要注意的是,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
。再就是选择数据类型时全是周折,最后为了保持原汁原味才使用这种冗长的手段。
不过,好玩确实好玩👍