在要由 C# 读取的 .txt 文件中包含控制字符

Including control characters in a .txt file to be read by C#

我正在开发一个使用纯 ASCII .txt 文件作为 key/value 配置文件的项目。 ConfigFile.txt 的当前格式类似于

名字=埃尔默|姓氏=Fudd|用户 ID=EFudd|密码=fubar|日期=2016 年 7 月 29 日

这很容易读入程序并使用 KeyValuePairs 创建字典,代码如下:

   using (FileStream fs = new FileStream("ConfigFile.txt", FileMode.Open))
    {
      using (StreamReader sr = new StreamReader(fs))
      {
        string fileText = sr.ReadToEnd();

        //  Tokenize the entire file string into separate key=value strings.
        string[] tokens = fileText.Split('|');

        //  Iterate through all of the key=value strings, tokenize each one into a key=Value 
        //  pair and add the key and value as separate strings into the dictionary.
        foreach (string token in tokens)
        {
          string[] keyValuePair = token.Split('=');
          configDict.Add(keyValuePair[0], keyValuePair[1]);
        }
      }
    }

它首先使用'|'将每个key/value拆分为一个单独的字符串作为分隔符。

名字=埃尔默

姓氏=Fudd

UserId=EFudd

密码=foobar

日期=2016 年 7 月 29 日

然后,对于每个 key/value 字符串,它在 '=' 分隔符上分隔键和值,创建一个 KeyValuePair,并将其插入到字典中供以后在程序中查找。

到目前为止一切顺利。指示用户不要使用任何一个分隔符创建密码。但是,我现在必须在将密码包含在文件中之前对其进行加密,并且加密例程可以生成从 0x20 到 0x7F 的任何可打印字符。因此,加密密码可以以其中一个或两个分隔符结尾。我最终可以得到 'foobar'(或其他)被加密引擎加密成 P#|=g%。这扰乱了 split 函数正常工作的能力。

所以,我想更改输入到记事本 .txt 文件中的分隔符来控制字符,而不是“|”分隔符,我正在使用 0x1E(记录分隔符)并将“=”符号替换为 0x1F(单元分隔符)。

我可以直接在 C# 中进行转义和编码,没有任何问题,但是我如何修改原始的 .txt 磁盘文件,以便它能够正确读取分隔符作为单个(不可打印的)字符?

因此,我不会使用那样的纯文本,而是使用适当的序列化格式,例如 JSON。

有一些工具可以为您完成艰苦的工作。
内置的 System.Web.Script.Serialization 命名空间有一些工具可以使用,但我更喜欢使用 Json.Net。如果您有 Visual Studio,您可以使用 nuGet 安装它(如果您需要更多帮助,请在评论中告诉我)。

但是一旦你将它添加到你的项目中,你就可以做这样的事情

using System.Collections.Generic;
using System.IO;
using Newtonsoft.Json;

namespace ConsoleApplication1
{
    public class Program
    {
        static void Main(string[] args)
        {
            var dict = new Dictionary<string, string>();

            dict.Add("FirstName", "Elmer");
            dict.Add("LastName", "Fudd");
            dict.Add("Password", @"\a\ansld\sb\b8d95nj");

            var json = JsonConvert.SerializeObject(dict);

            File.WriteAllText("ConfigFile.txt, json);

            var txt = File.ReadAllText("ConfigFile.txt");
            var newDict = JsonConvert.DeserializeObject<Dictionary<string, string>>(txt);

        }
    }
}

和ConfigFile.txt看起来像这样

{"FirstName":"Elmer","LastName":"Fudd","Password":"\a\ansld\sb\b8d95nj"}

如果您希望它更易于阅读,请使用

var json = JsonConvert.SerializeObject(dict, Formatting.Indented);

你会得到

{
  "FirstName": "Elmer",
  "LastName": "Fudd",
  "Password": "\a\ansld\sb\b8d95nj"
}

您可以将整数转换为字符,所以只需这样做...

string[] tokens = fileText.Split((char)0x1e);
// ...
string[] keyValuePair = token.Split((char)0x1f);

...但是将您的密码编码为 base64 会更容易和更清晰...

string base64 = Convert.ToBase64String(passwordHash);
byte[] passwordHash = Convert.FromBase64String(base64);

... 注意: hashes/encrypted 数据可能包含这些字符,因此我不会将 hases 转储到文本文件中。

以下 class 使用正则表达式提取字符串段并支持包含不可打印字符的密码:0x00 .. 0xFF class 包含配置段的属性

您可以 运行 演示示例 .NEt Fiddle

using System;
using System.Text.RegularExpressions;


class ConfigParser
{
    public string Text { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string UserId { get; set; }
    public string Password { get; set; }
    public string Date { get; set; }

    public ConfigParser(string text)
    {
        Text =text;
        Parse(text);
    }


    private static string pattern = @"
     ^FirstName=(?<firstname>\w+)    \|          
     LastName=(?<lastname>\w+)       \|              
     UserId=(?<userid>\w+)           \|                  
     Password=(?<pasword>.+)        
     Date=(?<date>.+)                         
     $
    ";

    private Regex regex = new Regex(pattern,
           RegexOptions.Singleline
           | RegexOptions.ExplicitCapture
           | RegexOptions.CultureInvariant
           | RegexOptions.IgnorePatternWhitespace
           | RegexOptions.Compiled
           );



    private void Parse(string text)
    {
        Console.WriteLine("text: {0}",text);
        Match m = regex.Match(text);
        FirstName = m.Groups["firstname"].ToString();
        LastName = m.Groups["lastname"].ToString();
        UserId = m.Groups["userid"].ToString();
        Password = m.Groups["pasword"].ToString();
        Date = m.Groups["date"].ToString();

    }

}

使用方法:

   var text ="your text here"; 
   var c = new ConfigParser(text );             

   you can access the properties of the class: FirstName, LastName,....

   Console.WriteLine("firstname: {0}", c.FirstName);
   Console.WriteLine("lastname: {0}", c.LastName);
   Console.WriteLine("UserId: {0}", c.UserId);
   Console.WriteLine("Password: {0}", c.Password);
   Console.WriteLine("date {0}", c.Date);

示例输出: 密码包含不可打印字符 |分隔符和符号

text: FirstName=Elmer|LastName=Fudd|UserId=EFudd|Password=fg%|uy|◄¶|hj↑khg|Date=7/29/2016
firstname: Elmer
lastname: Fudd
UserId: EFudd
Password: fg%|uy|◄¶|hj↑khg
date: 7/29/2016

最简单的答案:

使用 ALT-数字键盘值技巧将特殊字符插入字符串。记录组 ALT-31 (▼) 用于分隔 Key/Value 对的结尾,项目组 ALT-30 (▲) 用于分隔键和值。将字符串保存为 UTF-8。

分隔符代码为

private static char tokenDelimiter = ('▲');
private static char keyValuePairDelimiter = ('▼');

使用相同的 ALT 数字小键盘技巧来放入上下三角形。包括黑色三角形永远不会被编辑或删除的说明,并解释它们的含义。

它让我回到了 DOS 时代。简单,只需 5 分钟即可实现 - 不需要对现有代码库进行实质性更改 - 只需更改两个定界符即可。