尽可能将行与固定列匹配

时间:2013-08-21 14:22:27

标签: c# regex parsing text-parsing string-parsing

我将从遗留系统解析位置基础文件。文件中的每列都有固定的列宽,每行最多可以有80个字符长。问题是你不知道一行有多长。有时他们只填写前五列,有时使用所有列。

如果我知道所有使用的80个字符,那么我就可以这样做:

^\s*
 (?<a>\w{3})
 (?<b>[ \d]{2})
 (?<c>[ 0-9a-fA-F]{2})
 (?<d>.{20})
 ...

但问题是,如果缺少最后一列,则该行将不匹配。最后一列甚至可以是较少的字符数,然后是该列的最大值。

参见示例

Text to match         a   b  c  d
"AQM45A3A text   " => AQM 45 A3 "A text   "  //group d has 9 chars instead of 20
"AQM45F5"          => AQM 45 F5              //group d is missing
"AQM4"             => AQM  4                 //group b has 1 char instead of 2
"AQM4  ASome Text" => AQM  4  A "Some Text"  //group b and c only uses one char, but fill up the gap with space
"AQM4FSome Text"   => No match, group b should have two numbers, but it is only one.
"COM*A comment"    => Comments do not match (all comments are prefixed with COM*)
"       "          => Empty lines do not match

我应该如何设计正则表达式以匹配它?

修改1

在此示例中,我要解析的EACH行以AQM

开头
  • 列a始终从位置0开始
  • 列b始终从位置3开始
  • C栏始终从第5位开始
  • 列d始终从位置7开始

如果列未使用其所有空间,则文件是空格 只能使用最后一列

编辑2 为了使它更清晰,我在这里附上了数据可能看起来如何的例子,以及列的定义(注意我在前面提到的例子中的例子被大大简化了)

Example for AQM Definition for AQM

4 个答案:

答案 0 :(得分:3)

我不确定regexp在这里使用是否正确。如果我了解你的结构,你需要像

这样的东西
if (length >= 8) 
   d = everything 8th column on
   remove field d
else
   d = empty

if (length >= 6)
   c = everything 6th column on
   remove field c
else
   c = empty

等。也许正则表达式可以做到,但它可能会相当做作。

答案 1 :(得分:1)

尝试在不在那里的群组之后使用?。在这种情况下,如果缺少某些组,您将获得匹配。

在Sguazz回答后编辑n

我会用

(?<a>AQM)(?<b>[ \d]{2})?(?<c>[ 0-9a-fA-F]{2})?(?<d>.{0,20})?

甚至是+而不是最后一组的{0,20},如果可能超过20个字符。

编辑n + 1,

这样更好吗?

(?<a>\w{3})(?<b>\d[ \d])(?<c>[0-9a-fA-F][ 0-9a-fA-F])(?<d>.+)

答案 2 :(得分:1)

所以,只是改写一下:在你的例子中你有一个字符序列,你知道前3个属于A组,后面2个属于B组,然后2个属于C组,20个属于D组,但可能没有这么多元素。

尝试:

(?<a>\w{0,3})(?<b>[ \d]{0,2})(?<c>[ 0-9a-fA-F]{0,2})(?<d>.{0,20})

基本上这些数字现在是该组的上限而不是固定大小。

编辑,以反映您的上一条评论:如果您知道所有相关行都以“AQM”开头,则可以将组A替换为(?<a>AQM)

另一个编辑:让我们试试吧。

(?<a>AQM)(?<b>[ \d]{2}|[ \d]$)(?<c>[ 0-9a-fA-F]{0,2})(?<d>.{0,20})

答案 3 :(得分:0)

也许您可以使用类似这样的函数将字符串分解为其列值。它不解析注释字符串,并且能够处理短于80个字符的字符串。但它并没有验证列的内容。也许你可以在使用这些值时这样做。

/// <summary>
/// Break a data row into a collection of strings based on the expected column widths.
/// </summary>
/// <param name="input">The width delimited input data to break into sub strings.</param>
/// <returns>
/// An empty collection if the input string is empty or a comment.
/// A collection of the width delimited values contained in the input string otherwise.
/// </returns>
private static IEnumerable<string> ParseRow(string input) {
    const string COMMENT_PREFIX = "COM*";
    var columnWidths = new int[] { 3, 2, 2, 3, 6, 14, 2, 2, 3, 2, 2, 10, 7, 7, 2, 1, 1, 2, 7, 1, 1 };
    int inputCursor = 0;
    int columnIndex = 0;
    var parsedValues = new List<string>();

    if (String.IsNullOrEmpty(input) || input.StartsWith(COMMENT_PREFIX) || input.Trim().Length == 0) {
        return parsedValues;
    }

    while (inputCursor < input.Length && columnIndex < columnWidths.Length) {
        //Make sure the column width never exceeds the bounds of the input string. This can happen if the input string doesn't end on the edge of a column.
        int columnWidth = Math.Min(columnWidths[columnIndex++], input.Length - inputCursor);
        string columnValue = input.Substring(inputCursor, columnWidth);
        parsedValues.Add(columnValue);
        inputCursor += columnWidth;
    }
    return parsedValues;
}