//////////////////////////////////////////////////////////////// // Dump ALL properties // //////////////////////////////////////////////////////////////// public void DumpAll() { while (Read()) { CError.Write("NodeType = " + NodeType + "\t|\t"); CError.Write("NodeName = " + Name + "\t|\t"); CError.Write("NodeLocalName = " + LocalName + "\t|\t"); CError.Write("NodeNamespace = " + NamespaceURI + "\t|\t"); CError.Write("NodePrefix = " + Prefix + "\t|\t"); CError.Write("NodeHasValue = " + (HasValue).ToString() + "\t|\t"); CError.Write("NodeValue = " + Value + "\t|\t"); CError.Write("NodeDepth = " + Depth + "\t|\t"); CError.Write("IsEmptyElement = " + IsEmptyElement.ToString() + "\t|\t"); CError.Write("IsDefault = " + IsDefault.ToString() + "\t|\t"); CError.Write("XmlSpace = " + XmlSpace + "\t|\t"); CError.Write("XmlLang = " + XmlLang + "\t|\t"); CError.Write("AttributeCount = " + AttributeCount + "\t|\t"); CError.Write("HasAttributes = " + HasAttributes.ToString() + "\t|\t"); CError.Write("EOF = " + EOF.ToString() + "\t|\t"); CError.Write("ReadState = " + ReadState.ToString() + "\t|\t"); CError.WriteLine(); } }
/*++ ParseResponseData - Parses the incomming headers, and handles creation of new streams that are found while parsing, and passes extra data the new streams Input: returnResult - returns an object containing items that need to be called at the end of the read callback Returns: bool - true if one should continue reading more data --*/ private DataParseStatus ParseResponseData(ref ConnectionReturnResult returnResult) { DataParseStatus parseStatus = DataParseStatus.NeedMoreData; DataParseStatus parseSubStatus; GlobalLog.Enter("Connection#" + ValidationHelper.HashString(this) + "::ParseResponseData()"); // loop in case of multiple sets of headers or streams, // that may be generated due to a pipelined response do { // Invariants: at the start of this loop, m_BytesRead // is the number of bytes in the buffer, and m_BytesScanned // is how many bytes of the buffer we've consumed so far. // and the m_ReadState var will be updated at end of // each code path, call to this function to reflect, // the state, or error condition of the parsing of data // // We use the following variables in the code below: // // m_ReadState - tracks the current state of our Parsing in a // response. z.B. // Start - initial start state and begining of response // StatusLine - the first line sent in response, include status code // Headers - \r\n delimiated Header parsing until we find entity body // Data - Entity Body parsing, if we have all data, we create stream directly // // m_ResponseData - An object used to gather Stream, Headers, and other // tidbits so that a Request/Response can receive this data when // this code is finished processing // // m_ReadBuffer - Of course the buffer of data we are parsing from // // m_CurrentRequestIndex - index into the window of a buffer where // we are currently parsing. Since there can be multiple requests // this index is used to the track the offset, based off of 0 // // m_BytesScanned - The bytes scanned in this buffer so far, // since its always assumed that parse to completion, this // var becomes ended of known data at the end of this function, // based off of 0 // // m_BytesRead - The total bytes read in buffer, should be const, // till its updated at end of function. // // m_HeadersBytesUnparsed - The bytes scanned in the headers, // needs to be seperate because headers are not always completely // parsed to completion on each interation // // // Now attempt to parse the data, // we first parse status line, // then read headers, // and finally transfer results to a new stream, and tell request // switch (m_ReadState) { case ReadState.Start: m_ResponseData = new CoreResponseData(); m_ReadState = ReadState.StatusLine; m_CurrentRequestIndex = m_BytesScanned; InitializeParseStatueLine(); goto case ReadState.StatusLine; case ReadState.StatusLine: // // Reads HTTP status response line // parseSubStatus = ParseStatusLine( m_ReadBuffer, // buffer we're working with m_BytesRead, // total bytes read so far ref m_BytesScanned, // index off of what we've scanned ref m_StatusLineInts, ref m_StatusDescription, ref m_StatusState ); if (parseSubStatus == DataParseStatus.Invalid) { // // report error // GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::ParseStatusLine() ParseHeaders() returned Invalid protocol usage"); parseStatus = DataParseStatus.Invalid; // advance back to state 0, in failure m_ReadState = ReadState.Start; break; } else if (parseSubStatus == DataParseStatus.Done) { SetStatusLineParsed(); m_ReadState = ReadState.Headers; m_HeadersBytesUnparsed = m_BytesScanned; m_ResponseData.m_ResponseHeaders = new WebHeaderCollection(true); goto case ReadState.Headers; } break; // read more data case ReadState.Headers: // // Parse additional lines of header-name: value pairs // if (m_HeadersBytesUnparsed>=m_BytesRead) { // // we already can tell we need more data // break; } parseSubStatus = m_ResponseData.m_ResponseHeaders.ParseHeaders( m_ReadBuffer, m_BytesRead, ref m_HeadersBytesUnparsed ); if (parseSubStatus == DataParseStatus.Invalid) { // // report error // GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::ParseResponseData() ParseHeaders() returned Invalid protocol usage"); parseStatus = DataParseStatus.Invalid; // advance back to state 0, in failure m_ReadState = ReadState.Start; break; } else if (parseSubStatus == DataParseStatus.Done) { m_BytesScanned = m_HeadersBytesUnparsed; // update actual size of scanned headers GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::ParseResponseData() got (" + ((int)m_ResponseData.m_StatusCode).ToString() + ") from the server"); // If we have an HTTP continue, eat these headers and look // for the 200 OK if (m_ResponseData.m_StatusCode == HttpStatusCode.Continue) { HttpWebRequest Request; // wakeup post code if needed lock(this) { GlobalLog.Assert( m_WriteList.Count > 0, "Connection#" + ValidationHelper.HashString(this) + "::ParseResponseData(): m_WriteList.Count <= 0", ""); if ( m_WriteList.Count == 0 ) { parseStatus = DataParseStatus.Invalid; // advance back to state 0, in failure m_ReadState = ReadState.Start; break; } Request = (HttpWebRequest)m_WriteList[0]; } GlobalLog.Assert( Request != null, "Connection#" + ValidationHelper.HashString(this) + "::ParseResponseData(): Request == null", ""); GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::ParseResponseData() HttpWebRequest#" + ValidationHelper.HashString(Request)); // // we got a 100 Continue. set this on the HttpWebRequest // Request.Saw100Continue = true; if (!m_Server.Understands100Continue) { // // and start expecting it again if this behaviour was turned off // GlobalLog.Leave("HttpWebRequest#" + ValidationHelper.HashString(Request) + " ServicePoint#" + ValidationHelper.HashString(m_Server) + " sent UNexpected 100 Continue"); m_Server.Understands100Continue = true; } // // set Continue Ack on Request. // if (Request.ContinueDelegate != null) { // // invoke the 100 continue delegate if the user supplied one // GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::ParseResponseData() calling ContinueDelegate()"); Request.ContinueDelegate((int)m_ResponseData.m_StatusCode, m_ResponseData.m_ResponseHeaders); } GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::ParseResponseData() calling SetRequestContinue()"); Request.SetRequestContinue(); m_ReadState = ReadState.Start; goto case ReadState.Start; } m_ReadState = ReadState.Data; goto case ReadState.Data; } // need more data, // // But unfornately ParseHeaders does not work, // the same way as all other code in this function, // since its old code, it assumes BytesScanned bytes will be always // around between calls until it has all the data, but that is NOT // true, since we can do block copy between calls. // // To handle this we fix up the offsets. // We assume we scanned all the way to the end of valid buffer data m_BytesScanned = m_BytesRead; break; case ReadState.Data: // (check status code for continue handling) // 1. Figure out if its Chunked, content-length, or encoded // 2. Takes extra data, place in stream(s) // 3. Wake up blocked stream requests // // advance back to state 0 m_ReadState = ReadState.Start; // parse and create a stream if needed DataParseStatus result = ParseStreamData(ref returnResult); GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::ParseResponseData() result - " + result.ToString()); switch (result) { case DataParseStatus.NeedMoreData: // // cont reading. Only way we can get here is if the request consumed // all the bytes in the buffer. So reset everything. // m_BytesRead = 0; m_BytesScanned = 0; m_CurrentRequestIndex = 0; parseStatus = DataParseStatus.NeedMoreData; break; case DataParseStatus.ContinueParsing: continue; case DataParseStatus.Invalid: case DataParseStatus.Done: // // NOTE: WARNING: READ THIS: // Upon update of this code, keep in mind, // that when DataParseStatus.Done is returned // from ParseStreamData, it can mean that ReadStartNextRequest() // has been called. That means that we should no longer // change ALL this.m_* vars, since those variables, can now // be used on other threads. // // DataParseStatus.Done will cause an error below parseStatus = result; break; } break; } break; } while (true); GlobalLog.Print("m_ReadState - " + m_ReadState.ToString()); GlobalLog.Leave("Connection#" + ValidationHelper.HashString(this) + "::ParseResponseData()", parseStatus.ToString()); return parseStatus; }
private void ReadMessages(byte[] input, int size) { List <PowerLineModemMessage> receivedMessages = new List <PowerLineModemMessage>(); lock (port) { string strDebug = "Input: ["; // First we look for a 0x2, which they all start with. for (int i = 0; i < size; i++) { int data = input[i]; strDebug += String.Format("{0:x2}-", data, readData, (this.data == null ? 0 :this.data.Length), state.ToString()); switch (state) { case ReadState.START: if (data == START_OF_MESSAGE) { state = ReadState.MESSAGE_TYPE; } break; case ReadState.MESSAGE_TYPE: // This next one is the message type. message = (PowerLineModemMessage.Message)data; state = ReadState.MESSAGE_DATA; this.data = new byte[MessageFactory.SizeOfMessage(message, sending)]; readData = 0; break; case ReadState.MESSAGE_DATA: if (readData < this.data.Length) { this.data[readData++] = (byte)data; } if (readData >= this.data.Length) { // If this is a response, update the request with the response data. if (sending != null && message == sending.MessageType) { sendingResponse = sending.VerifyResponse(this.data); sending = null; receivedAck.Set(); } else { // Create the received message. PowerLineModemMessage packet = MessageFactory.GetReceivedMessage(message, this.data); if (packet != null) { receivedMessages.Add(packet); } } state = ReadState.START; } break; } } log.Debug(strDebug + "]"); } if (ReceivedMessage != null) { // Switch context so we don't end up deadlocked. ThreadPool.QueueUserWorkItem(delegate { foreach (PowerLineModemMessage mess in receivedMessages) { ReceivedMessage(this, new RecievedMessageEventArgs(mess)); } }); } }
public static List <Transaction> ReadChaseCreditPDF(Statement statement) { List <Transaction> ret = new List <Transaction>(); string pdfText = FileHelpers.GetPdfText(statement.Filepath); string[] lines = pdfText.Split('\n'); ReadState state = ReadState.Header; DateTime? statementDate = null; foreach (string line in lines) { string trimmed = line.Trim(); DateTime?entryDate = TryToScanDate(trimmed, "Opening/Closing Date", statementDate); Dictionary <string, ReadState> stateTransitions = new Dictionary <string, ReadState> { { "TransactionMerchantNameorTransactionDescription$Amount", ReadState.Credit }, { "TransactionMerchantNameorTransactionDescription$AmountRewards", ReadState.Header } }; //Special logic: spacing for the target line was funny sometimes (double or missing) //So we'll strip all spacing from the string string stripped = trimmed.Replace(" ", ""); CheckForStateTransition(stripped, stateTransitions, false, ref state); switch (state) { case ReadState.Header: if (entryDate.HasValue && !statementDate.HasValue) { statementDate = entryDate; } string searchHeader = "Previous Balance $"; if (trimmed.StartsWith(searchHeader)) { double.TryParse(trimmed.Substring(searchHeader.Length), out double start); statement.StartingBalance = -1 * start; } else { searchHeader = "Previous Balance -$"; if (trimmed.StartsWith(searchHeader)) { double.TryParse(trimmed.Substring(searchHeader.Length), out double start); statement.StartingBalance = start; } else { searchHeader = "New Balance $"; if (trimmed.StartsWith(searchHeader)) { double.TryParse(trimmed.Substring(searchHeader.Length), out double end); statement.EndingBalance = -1 * end; } else { searchHeader = "New Balance -$"; if (trimmed.StartsWith(searchHeader)) { double.TryParse(trimmed.Substring(searchHeader.Length), out double end); statement.EndingBalance = end; } } } } break; case ReadState.Credit: case ReadState.Check: case ReadState.Debit: if (entryDate.HasValue) { //Starting a new entry List <string> entryParts = trimmed.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries).ToList(); if (entryParts.Count >= 3) { Transaction curEntry = new Transaction { Date = entryDate.Value, Accounts = new List <string> { statement.AccountName }, Type = state.ToString() }; entryParts.RemoveAt(0); string amountString = entryParts.Last(); entryParts.RemoveAt(entryParts.Count - 1); double.TryParse(amountString, out double amount); curEntry.CreditAmount = -1 * amount; if (curEntry.CreditAmount < 0) { curEntry.Type = "Debit"; } curEntry.Description = FileHelpers.RemoveTrailingNumbers(string.Join(" ", entryParts)); ret.Add(curEntry); } } break; } //Console.WriteLine("{0}: {1} ({2})", state.ToString(), line, entryDate.HasValue ? entryDate.ToString() : "-"); } ret.OrderBy(e => e.Date); return(ret); }
public static List <Transaction> ReadBjsCreditPDF(Statement statement) { List <Transaction> ret = new List <Transaction>(); string pdfText = FileHelpers.GetPdfText(statement.Filepath); string[] lines = pdfText.Split('\n'); ReadState state = ReadState.Header; DateTime? statementDate = null; Transaction curEntry = null; foreach (string line in lines) { string trimmed = line.Trim(); DateTime?entryDate = TryToScanDate(trimmed, "Statement closing date", statementDate); Dictionary <string, ReadState> stateTransitions = new Dictionary <string, ReadState> { { "TRANS DATE TRANSACTION DESCRIPTION/LOCATION AMOUNT", ReadState.Credit } }; CheckForStateTransition(trimmed, stateTransitions, false, ref state); switch (state) { case ReadState.Header: if (entryDate.HasValue && !statementDate.HasValue) { statementDate = entryDate; } string searchHeader = "Previ ous balance $"; if (trimmed.StartsWith(searchHeader)) { double.TryParse(trimmed.Substring(searchHeader.Length), out double start); statement.StartingBalance = -1 * start; } else { searchHeader = "New balance $"; if (trimmed.StartsWith(searchHeader)) { double.TryParse(trimmed.Substring(searchHeader.Length), out double end); statement.EndingBalance = -1 * end; } } break; case ReadState.Credit: case ReadState.Check: case ReadState.Debit: if (entryDate.HasValue) { //Starting a new entry if (curEntry != null) { ret.Add(curEntry); curEntry = null; } List <string> entryParts = trimmed.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries).ToList(); if (entryParts.Count >= 3) { curEntry = new Transaction { Date = entryDate.Value, Accounts = new List <string> { statement.AccountName }, Type = state.ToString() }; entryParts.RemoveAt(0); string amountString = entryParts.Last(); double.TryParse(amountString, out double amount); if (amount != 0) { entryParts.RemoveAt(entryParts.Count - 1); } curEntry.CreditAmount = -1 * amount; if (curEntry.CreditAmount < 0) { curEntry.Type = "Debit"; } curEntry.Description = FileHelpers.RemoveTrailingNumbers(string.Join(" ", entryParts)); //ret.Add(curEntry); } } else if (trimmed.StartsWith("Interest charge on purchases")) { List <string> entryParts = trimmed.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries).ToList(); string amountString = entryParts.Last().Substring(1); double.TryParse(amountString, out double amount); if (amount != 0) { amount *= -1; entryParts.RemoveAt(entryParts.Count - 1); curEntry = new Transaction { Date = statementDate.Value, Accounts = new List <string> { statement.AccountName }, Type = "Debit", CreditAmount = amount, Description = string.Join(" ", entryParts) }; ret.Add(curEntry); curEntry = null; } } else if (!trimmed.StartsWith("(CONTINUED)") && !trimmed.StartsWith("Total for")) { if (curEntry != null) { if (double.TryParse(trimmed, out double amount)) { curEntry.CreditAmount = -1 * amount; if (curEntry.CreditAmount < 0) { curEntry.Type = "Debit"; } if (curEntry != null) { ret.Add(curEntry); curEntry = null; } } else { curEntry.Description = string.Format("{0} {1}", curEntry.Description, trimmed); } } } else { if (curEntry != null) { ret.Add(curEntry); curEntry = null; } } break; } //Console.WriteLine("{0}: {1} ({2})", state.ToString(), line, entryDate.HasValue ? entryDate.ToString() : "-"); } ret.OrderBy(e => e.Date); return(ret); }
public static List <Transaction> ReadUsaaSavingsPDF(Statement statement) { List <Transaction> ret = new List <Transaction>(); string pdfText = FileHelpers.GetPdfText(statement.Filepath); string[] lines = pdfText.Split('\n'); ReadState state = ReadState.Header; int linesInEntry = 0; Transaction curEntry = null; DateTime? statementDate = null; foreach (string line in lines) { string trimmed = line.Trim(); DateTime?entryDate = TryToScanDate(trimmed, null, statementDate); Dictionary <string, ReadState> stateTransitions = new Dictionary <string, ReadState> { { "DEPOSITS AND OTHER CREDITS", ReadState.Credit }, { "CHECKS", ReadState.Check }, { "OTHER DEBITS", ReadState.Debit }, { "ACCOUNT BALANCE SUMMARY", ReadState.Interim } }; if (CheckForStateTransition(trimmed, stateTransitions, false, ref state)) { if (curEntry != null) { ret.Add(curEntry); curEntry = null; } } string searchHeader = "USAA SAVINGS"; if (state == ReadState.Header && trimmed.StartsWith(searchHeader)) { state = ReadState.Balance; } switch (state) { case ReadState.Header: if (entryDate.HasValue && !statementDate.HasValue) { statementDate = entryDate; } break; case ReadState.Balance: List <string> balanceParts = trimmed.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries).ToList(); if (balanceParts.Count == 7) { if (double.TryParse(balanceParts[0], out double start)) { statement.StartingBalance = start; } if (double.TryParse(balanceParts.Last(), out double end)) { statement.EndingBalance = end; } } break; case ReadState.Credit: case ReadState.Check: case ReadState.Debit: if (entryDate.HasValue) { //Starting a new entry if (curEntry != null) { ret.Add(curEntry); } curEntry = new Transaction { Date = entryDate.Value, Accounts = new List <string> { statement.AccountName }, Type = state.ToString() }; List <string> entryParts = trimmed.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries).ToList(); entryParts.RemoveAt(0); string amountString = ""; if (entryParts.Count > 0) { amountString = entryParts[0]; entryParts.RemoveAt(0); } string typeString = FileHelpers.RemoveTrailingNumbers(string.Join(" ", entryParts)); if (state == ReadState.Check) { //Swap amount and description for checks string temp = amountString; amountString = typeString; typeString = temp; } double.TryParse(amountString, out double amount); int multiplier = state == ReadState.Credit ? 1 : -1; curEntry.SavingsAmount = amount * multiplier; curEntry.Description = typeString; linesInEntry = 1; } else { //Either continuing an entry or left the section linesInEntry++; if (linesInEntry > 3) { if (curEntry != null) { //Commit the previous entry ret.Add(curEntry); curEntry = null; state = ReadState.Interim; linesInEntry = 0; } } else if (curEntry != null) { curEntry.Description = FileHelpers.RemoveTrailingNumbers(trimmed); } } break; } //Console.WriteLine("{0}: {1} ({2})", state.ToString(), line, entryDate.HasValue ? entryDate.ToString() : "-"); } ret.OrderBy(e => e.Date); return(ret); }
public static List <Transaction> ReadUsaaCreditPDF(Statement statement) { List <Transaction> ret = new List <Transaction>(); string pdfText = FileHelpers.GetPdfText(statement.Filepath); string[] lines = pdfText.Split('\n'); ReadState state = ReadState.Header; DateTime? statementDate = null; Transaction curEntry = null; foreach (string line in lines) { string trimmed = line.Trim(); DateTime?entryDate = TryToScanDate(trimmed, "Statement Closing Date", statementDate); Dictionary <string, ReadState> stateTransitions = new Dictionary <string, ReadState> { { "Payments and Credits", ReadState.Credit }, { "Transactions", ReadState.Debit }, { "Interest Charged", ReadState.Debit }, { "Summary of Account Activity", ReadState.Balance }, { "Fees", ReadState.Debit } }; CheckForStateTransition(trimmed, stateTransitions, false, ref state); switch (state) { case ReadState.Header: if (entryDate.HasValue && !statementDate.HasValue) { statementDate = entryDate; } break; case ReadState.Balance: string searchHeader = "Previous Balance $"; if (trimmed.StartsWith(searchHeader)) { double.TryParse(trimmed.Substring(searchHeader.Length), out double start); statement.StartingBalance = -1 * start; } else { searchHeader = "New Balance $"; if (trimmed.StartsWith(searchHeader)) { double.TryParse(trimmed.Substring(searchHeader.Length), out double end); statement.EndingBalance = -1 * end; } } break; case ReadState.Credit: case ReadState.Check: case ReadState.Debit: if (curEntry == null) { if (entryDate.HasValue) { //Starting a new entry List <string> entryParts = trimmed.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries).ToList(); if (entryParts.Count >= 5) { curEntry = new Transaction { Date = entryDate.Value, Accounts = new List <string> { statement.AccountName }, Type = state.ToString() }; entryParts.RemoveAt(0); entryParts.RemoveAt(0); if (!entryParts[0].Equals("Interest")) { entryParts.RemoveAt(0); } string amountString = entryParts.Last().Substring(1); if (amountString.EndsWith("-")) { amountString = amountString.Substring(0, amountString.Length - 1); } bool gotAmount = double.TryParse(amountString, out double amount); if (gotAmount) { entryParts.RemoveAt(entryParts.Count - 1); } curEntry.Description = FileHelpers.RemoveTrailingNumbers(string.Join(" ", entryParts)); if (gotAmount) { int multiplier = state == ReadState.Credit ? 1 : -1; curEntry.CreditAmount = amount * multiplier; ret.Add(curEntry); curEntry = null; } } } } else { //Look for a line containing the amount if (trimmed.StartsWith("$") && double.TryParse(trimmed.Substring(1), out double amount)) { int multiplier = state == ReadState.Credit ? 1 : -1; curEntry.CreditAmount = amount * multiplier; ret.Add(curEntry); curEntry = null; } else { curEntry.Description += " " + FileHelpers.RemoveTrailingNumbers(trimmed); } } break; } //Console.WriteLine("{0}: {1} ({2})", state.ToString(), line, entryDate.HasValue ? entryDate.ToString() : "-"); } ret.OrderBy(e => e.Date); return(ret); }
public static List <Transaction> ReadUsaaCheckingPDF(Statement statement) { List <Transaction> ret = new List <Transaction>(); string pdfText = FileHelpers.GetPdfText(statement.Filepath); string[] lines = pdfText.Split('\n'); ReadState state = ReadState.Header; int linesInEntry = 0; Transaction curEntry = null; DateTime? statementDate = null; foreach (string line in lines) { string trimmed = line.Trim(); DateTime?entryDate = TryToScanDate(trimmed, null, statementDate); Dictionary <string, ReadState> stateTransitions = new Dictionary <string, ReadState> { { "DEPOSITS AND OTHER CREDITS", ReadState.Credit }, { "CHECKS", ReadState.Check }, { "OTHER DEBITS", ReadState.Debit }, { "USAA CLASSIC CHECKING", ReadState.Balance }, { "ACCOUNT BALANCE SUMMARY", ReadState.Interim } }; if (CheckForStateTransition(trimmed, stateTransitions, true, ref state)) { if (curEntry != null) { ret.Add(curEntry); curEntry = null; } } switch (state) { case ReadState.Header: if (entryDate.HasValue && !statementDate.HasValue) { statementDate = entryDate; } break; case ReadState.Balance: List <string> balanceParts = trimmed.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries).ToList(); if (balanceParts.Count == 7) { if (double.TryParse(balanceParts[0], out double start) && double.TryParse(balanceParts.Last(), out double end)) { statement.StartingBalance = start; statement.EndingBalance = end; } } break; case ReadState.Credit: case ReadState.Check: case ReadState.Debit: string[] entries = { trimmed }; bool singleLineEntry = false; if (state == ReadState.Check) { //Look for multiline check entries string lineFooter = "USAA FEDERAL SAVINGS BANK"; if (trimmed.EndsWith(lineFooter)) { trimmed = trimmed.Substring(0, trimmed.Length - lineFooter.Length); entries[0] = trimmed; singleLineEntry = true; } List <string> entryParts = trimmed.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries).ToList(); if (entryParts.Count >= 6) { entries = new[] { string.Join(" ", entryParts.GetRange(0, 3)), string.Join(" ", entryParts.GetRange(3, 3)) }; } } foreach (string entry in entries) { entryDate = TryToScanDate(entry, null, statementDate); if (entryDate.HasValue) { //Starting a new entry if (curEntry != null) { ret.Add(curEntry); } curEntry = new Transaction { Date = entryDate.Value, Accounts = new List <string> { statement.AccountName }, Type = state.ToString() }; List <string> entryParts = entry.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries).ToList(); entryParts.RemoveAt(0); string amountString = ""; if (entryParts.Count > 0) { //amountString = entryParts[0]; //entryParts.RemoveAt(0); if (state == ReadState.Check) { //Amount comes last for checks amountString = entryParts.Last(); entryParts.RemoveAt(entryParts.Count - 1); ////Swap amount and description for checks //string temp = amountString; //amountString = typeString; //typeString = temp; } else { amountString = entryParts[0]; entryParts.RemoveAt(0); } } string typeString = FileHelpers.RemoveTrailingNumbers(string.Join(" ", entryParts)); double.TryParse(amountString, out double amount); int multiplier = state == ReadState.Credit ? 1 : -1; curEntry.CheckingAmount = amount * multiplier; curEntry.Description = (state == ReadState.Check ? "Check " : string.Empty) + typeString; linesInEntry = 1; if (singleLineEntry) { ret.Add(curEntry); curEntry = null; } } else if (!entry.Equals("FEDERAL")) { //Either continuing an entry or left the section linesInEntry++; //int allowableLines = state == ReadState.Credit ? 3 : 2; if (linesInEntry > 3) { if (curEntry != null) { //Commit the previous entry ret.Add(curEntry); curEntry = null; state = ReadState.Interim; linesInEntry = 0; } } else if (curEntry != null) { curEntry.Description += " " + FileHelpers.RemoveTrailingNumbers(trimmed); } } else { state = ReadState.Interim; } } if (singleLineEntry) { state = ReadState.Interim; } break; } //Console.WriteLine("{0}: {1} ({2})", state.ToString(), line, entryDate.HasValue ? entryDate.ToString() : "-"); } ret.OrderBy(e => e.Date); return(ret); }