【问题标题】:How can I read and parse text files with configurable contents?如何读取和解析具有可配置内容的文本文件?
【发布时间】:2014-02-09 13:35:24
【问题描述】:

我用 C# (WinForm) 编写了一个名为 address_parser.exe 的应用程序,针对运行 Windows XP、Vista、7 和 8 的 PC。使用 .NET Framework 3.5 版是最低设置...

应用程序读取并解析文本文件(仅限纯文本文件,因为我无法控制输入文件,所以很遗憾,XML 不是一个选项)。

这些文本文件包含一组数据,比如一个地址,分成多个不连续的行。

请看以下两个文本文件作为演示:

address_type_1.txt:

Elm Grove
47

PO5 1JF


Southsea

address_type_2.txt:

Southsea

Albert Road



147b


PO4 0JW

现在,目前我已经在我的代码中硬编码了输入文件中街道、门牌号、邮政编码和城市所在的信息。所以对于每个地址文件类型如果都创建了一套规则,哪一行包含了哪些信息。

此外,我还有一组正则表达式,用于检查每个信息(街道、门牌号、邮政编码、城市)的有效性。

由于这两组规则/检查(哪一行包含每个信息的信息/正则表达式模式)因不同的地址类型而异,我想将这些规则存储在一种配置文件中。因此,我不想硬编码,而是希望为每种地址类型都有一个配置文件,我的应用程序可以读取并配置自己如何解析特定的地址文件类型。

我想从你那里得到一些想法和灵感。请分享您的想法和最佳实践!

谢谢!

以下是我的一些想法,以及我目前使用的代码 sn-ps...

我目前的硬编码地址文件解析运行如下:

public static Address Parse(string fileName)
{
    var a = new Address();
    a.OriginalFile = fileName;
    int i = 0;
    using (var fs = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.None))
    {
        using (var reader = new StreamReader(fs, Encoding.GetEncoding(65001)))
        {
            Regex rgxStreet = new Regex(@"^([\w\.,:\/\\\-öäüÖÄÜß_\s\(\)\[\]-[=;]]){0,128}$");
            Regex rgxNumber = new Regex(@"^([\w\.,:\/\\\-öäüÖÄÜß_\s\(\)\[\]-[=;]]){0,20}$");
            Regex rgxCity = new Regex(@"^([\w\.,:\/\\\-öäüÖÄÜß_\s\(\)\[\]-[=;]]){0,128}$");
            Regex rgxZIP = new Regex(@"^([0-9]){5}$");
            while (!reader.EndOfStream)
            {
                var line = reader.ReadLine().TrimEnd(';').Trim();
                if (line != null)
                {
                    if (i == 4 && rgxStreet.IsMatch(line))
                    {
                        a.Street = line;
                    }
                    else if (i == 7 && rgxNumber.IsMatch(line))
                    {
                        a.Number = line;
                    }
                    else if (i == 12 && (rgxZIP.IsMatch(line) || String.IsNullOrEmpty(line)))
                    {
                        a.Zip = line;
                    }
                    else if (i == 15 && rgxCity.IsMatch(line))
                    {
                        a.City = line;
                    }
                }
                i++;
            }
        }
    }
    return a;
}

如您所见,我还在这 4 个属性上使用了单独的正则表达式来检查我正在阅读的内容是否有效。

现在,我想修改这个硬编码信息(行 X 包含带有正则表达式 Z 的字段 Y),以便我可以支持读取和解析相同信息存储在不同文件中的文件顺序,或具有不同的有效值。

上面的示例针对包含德国地址(邮政编码为 5 位)的文件。

解析另一种类型的包含英国地址的文本文件可能如下所示:

line 1: city;
line 2: zip;
line 20: street;
line 159: number;

在此示例中,信息的顺序以及邮政编码所需的 reg ex 已更改(英国的邮政编码长度为 6 位,包含字母和数字)。

我想要一个配置文件之类的东西,它告诉我的应用程序如何解析特定类型的文件,而不是硬编码如何解析此类文件的信息。像这样的:

#config file for UK address files:
#line;field;regex;
1;city;@"^([\w\.,:\/\\\-öäüÖÄÜß_\s\(\)\[\]-[=;]]){0,128}$";
2;zip;@"^([A-Za-z0-9]){6}$";
20;street;@"^([\w\.,:\/\\\-öäüÖÄÜß_\s\(\)\[\]-[=;]]){0,128}$";
150;number;@"^([\w\.,:\/\\\-öäüÖÄÜß_\s\(\)\[\]-[=;]]){0,20}$";

我的问题是:这是个好主意,还是有更好的方法来实现这一点(告诉我的应用程序需要如何读取和解析特定文件以及解释和验证其内容) ?

谢谢!

【问题讨论】:

  • 使用 XML。订单在那里并不重要。创建一个代表您的配置的类并使用 XML-Serializer 加载数据
  • 输入格式是纯文本 - 因为我无法控制输入文件,不幸的是我不能使用 XML (:

标签: c# regex file-io configuration-files


【解决方案1】:

是的,是个好主意,使用Newtonsoft.Json 来帮助您完成配置加载

private class StartSettings
{
    public string CityReg;
    public int CityNum;
    public string ZipReg;
    public int ZipNum;
    public string StreetReg;
    public int StreetNum;
    public string NumberReg;
    public int NumberNum;
}

var configString = File.ReadAllText(configFilePath);
var config = JsonConvert.DeserializeObject<StartSettings>(configString);

读取文件只需使用

Regex rgxStreet = new Regex(config.StreetReg);
Regex rgxNumber = new Regex(config.NumberReg);
Regex rgxCity = new Regex(config.CityReg);
Regex rgxZIP = new Regex(config.ZipReg");

foreach (var line = File.ReadLines(fileName, Encoding.GetEncoding(65001))
                        .Select(l => l.TrimEnd(';').Trim())
{
    if(config.CityNum == i && rgxCity.IsMatch(line))
        a.City = line;
    ...
    i++;
}
return a;

【讨论】:

  • 感谢 Pedro The Kid 的回答!它引导我找到解决问题的正确方法,非常感谢! :)
【解决方案2】:

由于我怀疑是否有可能确定一个值是街道还是城市名称,因此您需要至少指定一些有关 iput-data 的信息,这些信息采用数据的“格式”组成。

如果您仍然可以决定数据格式,请选择 XML。

像这样使用 XML 和 XmlSerializer:

[Serializable]
public class AdressData
{
    [XmlArrayItem("Adress")]
    public Adress[] Adresses

}

[Serializable]
public class Adress
{
    public string Street {get; set;}
    public int Number {get; set;}
    public int Zip{get; set;}
    public string City{get; set;}
    public string State{get; set;}
}

然后像这样使用它:

XmlSerializer serializer = new XmlSerializer(typeof(AdressData));
AdressData data = (AdressData)serializer.Deserialize(File.Open(fileName));

foreach(Adress adress in data.Adresses)
{
    checkIfItExists(adress);
}

您的 XML 应如下所示:

<AdressData>
  <Adresses>
    <Adress>
         <Street>WhateverStr</Street>
         <Number>7</Number>
         <Zip>5675765</Zip>
         <City>Citytown</City>
         <State>Alabama</State>
    </Adress>
      <Adress>
         <!-- Order doesnt matter here -->
         <Number>7</Number>
         <Zip>5675765</Zip>
         <City>Citytown</City>
         <State>Alabama</State>
         <Street>WhateverStr</Street>
    </Adress>
  </Adresses>
</AdressData>

XML 中数据的顺序无关紧要,只要它符合层次结构即可。 序列化程序进行一些验证,例如尝试解析数值。您需要做的就是检查信息本身是否有效。

它也能够解析枚举,因此您可以(虽然不推荐)创建一个包含所有美国州名的枚举...

【讨论】:

  • 谢谢@CShapie,但不幸的是我无法控制我需要解析的文本文件的输入格式(它们都是纯文本,没有xml)...谢谢!
  • 然后创建一个“Schemafile”,您可以在其中指定哪一行是什么。然后在导入数据时,用户需要指定提供的数据在什么Schema中。
  • 听起来很有趣!我可以为纯文本文件创建架构文件,以便我可以借助该架构文件将该纯文本文件导入我的应用程序吗?我可以在架构中指定一个正则表达式吗?
  • 让它变得简单。创建一个从字符串读取街道的函数,一个读取状态的函数,一个读取城市的函数......在 schmeafile 中,只需告诉哪一行是什么
  • 我现在会调查的!到目前为止谢谢你!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-05-14
  • 1970-01-01
  • 2020-09-30
  • 1970-01-01
  • 2020-12-01
  • 1970-01-01
  • 2014-05-31
相关资源
最近更新 更多