简体   繁体   中英

Reversing a regex based parser

I inherited a bank interface parser. The previous developer actually did this pretty slick. The file that comes in from the bank is a fixed length field. The way he parses that record from the download is this

    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 ) );
        }

I have a new requirement to reverse this process and actually generate mock files from the bank so we can test. Using this method, is there a way i can reverse the process using this same regex method to generate the file or do i just go back to building a standard parser.

thanks

This basically does what you ask for. You can play with it until it suits your needs.

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));
    }
}

The output of that example is:

123123456789ABC123456789ABCDEFGHIJK121212123456789ABC12

Here's the ideone.

If by reversing you mean outputting an object to a file, then parser is not what you need. All you need to do is implement a method that outputs the same data members using a similar format to a file. You can use String.format with the field lengths you have in the regex. With some refactoring, you can extract commonalities between the regex and the string format, although you might consider this an overkill, as this regex is fairly simple.

You need to step away from letting the regex control you. If you define your structure in another way (I use an enum below) from which you can derive your regex and a formatter then not only will the code become much more extensible but you will also be able to make a marshaller and an unmarshaller from it too.

Something like this may be a good start:

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;
  }
}

Note how each enum encapsulates the essence of each field. The type, the number of characters and where it goes in the Account object.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM