Exemplo n.º 1
0
        ////////////////////////////////////////////////////////////////
        // 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();
            }
        }
Exemplo n.º 2
0
        /*++

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