package gov.nysenate.openleg.processor.bill; import gov.nysenate.openleg.model.base.SessionYear; import gov.nysenate.openleg.model.bill.BillTextType; import gov.nysenate.openleg.model.bill.VetoId; import gov.nysenate.openleg.model.bill.VetoMessage; import gov.nysenate.openleg.model.bill.VetoType; import gov.nysenate.openleg.processor.base.ParseError; import gov.nysenate.openleg.util.OutputUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.regex.Matcher; import java.util.regex.Pattern; public class VetoMemoParser extends BillTextParser { private static final Logger logger = LoggerFactory.getLogger(VetoMemoParser.class); /** --- RegEx Patterns --- */ private static final Pattern vetoHeaderPattern = Pattern.compile("00000\\.SO DOC VETO(\\d{4})\\s{8}([*A-Z ]{9})[A-Z0-9 ]{16}VETO\\s*(\\d{4})"); private static final Pattern datePattern = Pattern.compile("\\d{5}TO THE (SENATE|ASSEMBLY):\\s*([a-zA-Z]+ \\d+, \\d+)?"); private static final Pattern chapterPattern = Pattern.compile("\\d{5}CHAPTER (\\d+)"); private static final Pattern lineReferencePattern = Pattern.compile("\\d{5}Bill Page (\\d+), Line (\\d+)( through Line (\\d+))?.*"); private static final Pattern signerPattern = Pattern.compile("\\d{5}\\s*(?:(?:The|This|These) bills? (?:is|are) disapproved\\.)?\\s*\\(signed\\) ([a-zA-Z.'\\- ]*[a-zA-Z.])"); /** A veto message object that is constructed while parsing the veto memo */ private VetoMessage vetoMessage; /** --- Constructors --- */ public VetoMemoParser (String data, LocalDateTime date) { super(data, BillTextType.VETO_APPROVAL , date); vetoMessage = new VetoMessage(); vetoMessage.setType(VetoType.STANDARD); } /** --- Overrides --- */ /** * {@inheritDoc} * * Sets the vetoMessage text to the extracted text. */ @Override public String extractText() throws ParseError { String memoText = super.extractText(); vetoMessage.setMemoText(memoText); return memoText; } /** * {@inheritDoc} * * Performs additional vetoMessage parsing in addition to the standard bill text parsing */ @Override protected String parseLine(String line, StringBuilder text, String fullText) throws ParseError { int lineNum = Integer.parseInt(line.substring(0,5)); if (lineNum == 0) { Matcher headerMatcher = vetoHeaderPattern.matcher(line); if (headerMatcher.find()) { vetoMessage.setVetoNumber(Integer.parseInt(headerMatcher.group(1))); vetoMessage.setYear(Integer.parseInt(headerMatcher.group(3))); vetoMessage.setSession(new SessionYear(vetoMessage.getYear())); } } else if (lineNum == 4) { Matcher dateMatcher = datePattern.matcher(line); if (dateMatcher.find()) { if (dateMatcher.group(2)==null) { // This date is only present on line vetos vetoMessage.setType(VetoType.STANDARD); } else { vetoMessage.setType(VetoType.LINE_ITEM); vetoMessage.setSignedDate(LocalDate.parse(dateMatcher.group(2), DateTimeFormatter.ofPattern("MMMM d, yyyy"))); } } } else if (lineNum == 11 && vetoMessage.getType() == VetoType.LINE_ITEM) { Matcher chapterMatcher = chapterPattern.matcher(line); if (chapterMatcher.find()) { vetoMessage.setChapter(Integer.parseInt(chapterMatcher.group(1))); } } else if (lineNum > 16 && vetoMessage.getType() == VetoType.LINE_ITEM && vetoMessage.getBillPage()==0) { Matcher lineReferenceMatcher = lineReferencePattern.matcher(line); if (lineReferenceMatcher.find()) { vetoMessage.setBillPage(Integer.parseInt(lineReferenceMatcher.group(1))); vetoMessage.setLineStart(Integer.parseInt(lineReferenceMatcher.group(2))); if (lineReferenceMatcher.group(4) == null) { vetoMessage.setLineEnd(vetoMessage.getLineStart()); } else { vetoMessage.setLineEnd(Integer.parseInt(lineReferenceMatcher.group(4))); } } } else if (lineNum > 29 || lineNum > 14 && vetoMessage.getType() == VetoType.STANDARD) { Matcher signerMatcher = signerPattern.matcher(line); if (signerMatcher.find()) { vetoMessage.setSigner(signerMatcher.group(1)); } } return super.parseLine(line, text, fullText); } /** --- Internal --- */ /** * Verifies that all fields of the veto message have been populated * BillId is not checked since it is added outside of this class * @throws ParseError */ private void verifyVetoMessage() throws ParseError { boolean completeVetoMessage = vetoMessage.getVetoNumber()!=0 && vetoMessage.getYear()!=0 && vetoMessage.getSession()!=null && vetoMessage.getMemoText()!=null; if (vetoMessage.getType() == VetoType.LINE_ITEM) { completeVetoMessage = completeVetoMessage && vetoMessage.getSignedDate()!=null && vetoMessage.getChapter()!=0 && vetoMessage.getBillPage()!=0 && vetoMessage.getLineStart()!=0 && vetoMessage.getLineEnd()!=0; } if (!completeVetoMessage) { logger.warn("{}", OutputUtils.toJson(vetoMessage)); throw new ParseError("End of message reached before all veto information could be extracted"); } } /** --- Functional Getters/Setters --- */ public VetoMessage getVetoMessage() throws ParseError { if (isDeleted()) { return null; } verifyVetoMessage(); return vetoMessage; } public VetoId getVetoId() { if (vetoMessage != null) { return vetoMessage.getVetoId(); } else { return null; } } }