【问题标题】:Reversing a regex based parser反转基于正则表达式的解析器
【发布时间】:2012-11-08 20:59:51
【问题描述】:

我继承了一个银行接口解析器。以前的开发人员实际上做得很漂亮。从银行进来的文件是一个固定长度的字段。他从下载中解析该记录的方式是这样的

    public static final String HEADER_RECORD_REGEX = "^(\\d{3})(\\d{12})(.{20})(\\d\\d)(\\d\\d)(\\d\\d)(\\d{12})(\\d\\d)$";

private static final int BANK_ID      = 1;
    private static final int ACCOUNT_ID   = 2;
    private static final int COMPANY_NAME = 3;
    private static final int MONTH              = 4;
    private static final int DAY                    = 5;
    private static final int YEAR                 = 6;
    private static final int SEQUENCE     = 7;
    private static final int TYPE_CODE      = 8;
    private static final int GROUP_COUNT  = TYPE_CODE;

if ( GROUP_COUNT == matcher.groupCount() )  {
            setBankId( matcher.group( BANK_ID ) );
            setAccountId( matcher.group( ACCOUNT_ID ) );
            setCompanyName( matcher.group( COMPANY_NAME ) );
            setProcessDate( matcher.group( MONTH ), matcher.group( DAY ),
                            matcher.group( YEAR ) );
            setSeqNumber( matcher.group( SEQUENCE ) );
            setTypeCode( matcher.group( TYPE_CODE ) );
        }

我有一个新的要求来扭转这个过程,并从银行实际生成模拟文件,以便我们进行测试。使用这种方法,有没有办法我可以使用相同的正则表达式方法来反转该过程来生成文件,或者我只是回到构建标准解析器。

谢谢

【问题讨论】:

  • 反向是什么意思?从模拟结果文件创建固定长度的数据文件?
  • 好吧,逆向这个过程不是解析,而是它的格式。所以,new Formatter().format("%3.3s%12.12s%20.20s%2.2s%2.2s%2.2s%12.12s%2.2s", bankID, acctID, companyName, month, day, year, seq, typeCode); 或类似的东西。此外,回想起来,对于我们聪明的解析器编写者来说,以编程方式定义每个字段的长度而不是将它们硬编码到他的正则表达式中会更有用。
  • 我希望他们没有解雇最初写这篇文章的人,因为你可以用各种可怕的方式来解决这样的问题,他一点也不差。
  • GROUP_COUNT 的那一点没有意义。 matcher.groupCount() 返回的值是与 Matcher 关联的 Pattern 对象的静态属性。即使匹配尝试失败,它也将始终相同。

标签: java regex


【解决方案1】:

这基本上可以满足您的要求。您可以使用它,直到它适合您的需要为止。

import java.util.*;

class Main
{
    public static String getLine(String bankID, String acctID, String companyName, String month, String day, String year, String seq, String typeCode)
    {
        return new Formatter()
               .format("%3.3s%12.12s%20.20s%2.2s%2.2s%2.2s%12.12s%2.2s", 
                       bankID, acctID, companyName, month,
                       day, year, seq, typeCode)
               .toString(); // 1 semicolon, technically a 1 liner.  aww yeah
    }

    public static void main(String[] args)
    {
        String tester = "123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
        System.out.println(getLine(tester, tester, tester, tester,
                                   tester, tester, tester, tester));
    }
}

该示例的输出是:

123123456789ABC123456789ABCDEFGHIJK121212123456789ABC12

Here's the ideone.

【讨论】:

  • 也大声笑。 Ideone 报告说它的运行空间约为 250MB。天哪,Java 有时很糟糕。
【解决方案2】:

如果通过反转您的意思是将对象输出到文件,那么解析器不是您所需要的。您需要做的就是实现一个方法,该方法使用类似的格式将相同的数据成员输出到文件。您可以将 String.format 与正则表达式中的字段长度一起使用。通过一些重构,您可以提取正则表达式和字符串格式之间的共同点,尽管您可能认为这有点过头了,因为这个正则表达式相当简单。

【讨论】:

    【解决方案3】:

    您需要远离让正则表达式控制您。如果您以另一种方式定义您的结构(我在下面使用enum),您可以从中派生正则表达式和格式化程序,那么不仅代码将变得更具可扩展性,而且您还可以创建编组器和解组器也来自它。

    这样的事情可能是一个好的开始:

    public class BankRecords {
      static enum AccountField {
        BANK_ID("\\d", 3) {
          @Override
          void fill ( Account a, String s ) {
            a.bankId = s;
          }
        },
        ACCOUNT_ID("\\d", 12) {
          @Override
          void fill ( Account a, String s ) {
            a.accountID = s;
          }
        },
        COMPANY_NAME(".", 20) {
          @Override
          void fill ( Account a, String s ) {
            a.companyName = s;
          }
        },
        MONTH("\\d", 2) {
          @Override
          void fill ( Account a, String s ) {
            a.month = s;
          }
        },
        DAY("\\d", 2) {
          @Override
          void fill ( Account a, String s ) {
            a.day = s;
          }
        },
        YEAR("\\d", 2) {
          @Override
          void fill ( Account a, String s ) {
            a.year = s;
          }
        },
        SEQUENCE("\\d", 12) {
          @Override
          void fill ( Account a, String s ) {
            a.seqNumber = s;
          }
        },
        TYPE_CODE("\\d", 2) {
          @Override
          void fill ( Account a, String s ) {
            a.typeCode = s;
          }
        };
        // The type string in the regex.
        final String type;
        // How many characters.
        final int count;
    
        AccountField(String type, int count) {
          this.type = type;
          this.count = count;
        }
    
        // Each field can fill its part in the Account.
        abstract void fill ( Account a, String s );
    
        // My pattern.
        static Pattern pattern = Pattern.compile(asRegex());
    
        public static Account parse ( String record ) {
          Account account = new Account ();
          // Fire off the matcher with the regex and put each field in the Account object.
          Matcher matcher = pattern.matcher(record);
          for ( AccountField f : AccountField.values() ) {
            f.fill(account, matcher.group(f.ordinal() + 1));
          }
          return account;
        }
    
        public static String format ( Account account ) {
          StringBuilder s = new StringBuilder ();
          // Roll each field of the account into the string using the correct length from the enum.
          return s.toString();
        }
    
        private static String regex = null;
    
        static String asRegex() {
          // Only do this once.
          if (regex == null) {
            // Grow my regex from the field definitions.
            StringBuilder r = new StringBuilder("^");
            for (AccountField f : AccountField.values()) {
              r.append("(").append(f.type);
              // Special case count = 1 or 2.
              switch (f.count) {
                case 1:
                  break;
                case 2:
                  // Just one more.
                  r.append(f.type);
                  break;
                default:
                  // More than that shoudl use the {} notation.
                  r.append("{").append(f.count).append("}");
                  break;
              }
              r.append(")");
            }
            // End of record.
            r.append("$");
            regex = r.toString();
          }
          return regex;
        }
      }
    
      public static class Account {
        String bankId;
        String accountID;
        String companyName;
        String month;
        String day;
        String year;
        String seqNumber;
        String typeCode;
      }
    }
    

    注意每个enum 是如何封装每个字段的本质的。类型、字符数及其在 Account 对象中的位置。

    【讨论】:

    • 我过去做过这个。它很快就会失控。我对此不太满意。
    • 您选择了一种解决方案,该解决方案使用完全不同的机制来格式化数据而不是解析数据。如果您的数据格式发生变化,您将需要在两个不同的地方更改您的代码。我会称之为“失控”。 Anywhoo ...你的决定。 :)
    猜你喜欢
    • 2010-10-29
    • 2011-07-20
    • 1970-01-01
    • 1970-01-01
    • 2022-01-25
    • 2011-12-03
    • 2012-06-15
    • 2013-12-31
    • 2012-07-01
    相关资源
    最近更新 更多