【问题标题】:Match a row with fixed columns as long as possible尽可能长地匹配具有固定列的行
【发布时间】:2013-08-21 14:22:27
【问题描述】:

我要从遗留系统中解析一个位置基础文件。文件中的每一列都有固定的列宽,每行最多可以有 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

在此示例中,我要解析的每一行都以 AQM 开头

  • a 列始终从位置 0 开始
  • b 列总是从位置 3 开始
  • 列 c 始终从位置 5 开始
  • d 列始终从位置 7 开始

如果一列没有使用其所有空间,则文件是否包含空格 只能修剪最后使用的列

编辑 2 为了更清楚起见,我在这里附上了一些数据可能是什么样子的例子,以及列的定义(请注意,我在问题前面提到的例子被大大简化了)

【问题讨论】:

  • 您的要求不是很清楚。您的示例中的哪些行应该匹配,哪些不应该匹配?为什么?
  • 对不起。示例中的所有行都应该匹配。任务是获取每行中存在的所有列。我添加了更多示例来显示不匹配的行
  • 您遇到的主要问题是列之间没有任何明确的分隔符,由于每列中的字符数是可变的,因此什么可以限定某个字符出现在该列或该列中?
  • 每一列的字符数是固定的,除了最后一列。最后一列可以修剪
  • 我添加了一个新的列示例,列更短并用空格填充空白

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


【解决方案1】:

我不确定在这里使用正则表达式是否正确。如果我理解你的结构,你想要类似的东西

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

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

【讨论】:

    【解决方案2】:

    尝试在无法出现的组之后使用?。在这种情况下,如果缺少某些组,您将获得匹配项。

    在 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>.+)
    

    【讨论】:

    • 好的,我编辑,即使你的评论会让人困惑,谁会稍后再读。
    • 我已经尝试过这样做,但是当他们对最后一列进行计时时会发生什么
    • 如果你把 ?在每组之后,每一列都成为可选的。但没有列是可选的。我知道如果 d 列存在,它总是从位置 7 开始。
    • 好的,所以只需使用(?&lt;d&gt;.+) 并忘记其他组的?。可能使用\d[ \d] 而不是[ \d]{2},所以消耗的列数是正确的,避免只有空格。
    • 对不起,我太含糊了。我的意思是它是最后一个可以缩短的 USED 列。然而,哪一列是未知的。他们可能只使用前 2-3 列,你永远不知道。
    【解决方案3】:

    所以,换个说法:在您的示例中,您有一个字符序列,并且您知道前 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 组替换为 (?&lt;a&gt;AQM)

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

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

    【讨论】:

    • 我也想过这样做。问题是你不能保证列的宽度是固定的。
    • 我以为你的意思是最后一行可能没有固定宽度;其他的则填充了实际上只是另一个字符的空格。不对吗?
    • 另外 - 也许我误解了一些东西,但我很难找到失败的行。你能提供一个吗?
    • 每一行都是匹配的个体。它是最后一个可以修剪的USED COLUMN。但你不知道希望专栏是最新的。我添加了一个不匹配的新示例
    • 啊,我明白了。这个怎么样?
    【解决方案4】:

    也许您可以使用这样的函数将字符串分解为其列值。它不解析注释字符串,并且能够处理短于 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;
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-06-07
      • 2019-06-12
      • 1970-01-01
      • 2010-10-17
      • 1970-01-01
      • 1970-01-01
      • 2019-08-19
      • 1970-01-01
      相关资源
      最近更新 更多