diff --git a/source/OFXSharp.Console/App.config b/source/OFXSharp.Console/App.config new file mode 100644 index 0000000..8e15646 --- /dev/null +++ b/source/OFXSharp.Console/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/source/OFXSharp.Console/OFXSharp.Console.csproj b/source/OFXSharp.Console/OFXSharp.Console.csproj new file mode 100644 index 0000000..942f8ba --- /dev/null +++ b/source/OFXSharp.Console/OFXSharp.Console.csproj @@ -0,0 +1,64 @@ + + + + + Debug + AnyCPU + {BD64B333-EFE8-400F-B61A-F3EC0E98889B} + Exe + Properties + OFXSharp.Console + OFXSharp.Console + v4.5 + 512 + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + {7bba813d-ee93-491a-b0c7-fbd588e393cb} + OFXSharp + + + + + \ No newline at end of file diff --git a/source/OFXSharp.Console/Program.cs b/source/OFXSharp.Console/Program.cs new file mode 100644 index 0000000..b6115db --- /dev/null +++ b/source/OFXSharp.Console/Program.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OFXSharp.Console +{ + class Program + { + static void Main(string[] args) + { + var parser = new OFXDocumentParser(); + var ofxDocument = parser.Import(new FileStream(@"F:\Sandboxes\TM\OFXSharp\source\OFXSharp.Tests\itau.ofx", FileMode.Open)); + } + } +} diff --git a/source/OFXSharp.Console/Properties/AssemblyInfo.cs b/source/OFXSharp.Console/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..cc6abe6 --- /dev/null +++ b/source/OFXSharp.Console/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("OFXSharp.Console")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Microsoft")] +[assembly: AssemblyProduct("OFXSharp.Console")] +[assembly: AssemblyCopyright("Copyright © Microsoft 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("401deb64-26de-48e7-8f07-a64dd948dffc")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/source/OFXSharp.sln b/source/OFXSharp.sln index c73e5ab..1f2a282 100644 --- a/source/OFXSharp.sln +++ b/source/OFXSharp.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 2013 -VisualStudioVersion = 12.0.30110.0 +VisualStudioVersion = 12.0.40629.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OFXSharp", "OFXSharp\OFXSharp.csproj", "{7BBA813D-EE93-491A-B0C7-FBD588E393CB}" EndProject @@ -13,6 +13,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{4456DF EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OFXSharp.Tests", "OFXSharp.Tests\OFXSharp.Tests.csproj", "{4753DE21-42DB-405F-BB2A-A3F189A62482}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OFXSharp.Console", "OFXSharp.Console\OFXSharp.Console.csproj", "{BD64B333-EFE8-400F-B61A-F3EC0E98889B}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -27,6 +29,10 @@ Global {4753DE21-42DB-405F-BB2A-A3F189A62482}.Debug|Any CPU.Build.0 = Debug|Any CPU {4753DE21-42DB-405F-BB2A-A3F189A62482}.Release|Any CPU.ActiveCfg = Release|Any CPU {4753DE21-42DB-405F-BB2A-A3F189A62482}.Release|Any CPU.Build.0 = Release|Any CPU + {BD64B333-EFE8-400F-B61A-F3EC0E98889B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BD64B333-EFE8-400F-B61A-F3EC0E98889B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BD64B333-EFE8-400F-B61A-F3EC0E98889B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BD64B333-EFE8-400F-B61A-F3EC0E98889B}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/source/OFXSharp/Account.cs b/source/OFXSharp/Account.cs index b80acf4..fe91b53 100644 --- a/source/OFXSharp/Account.cs +++ b/source/OFXSharp/Account.cs @@ -1,94 +1,96 @@ -using System; -using System.Xml; - -namespace OFXSharp -{ - public class Account - { - public string AccountID { get; set; } - public string AccountKey { get; set; } - public AccountType AccountType { get; set; } - - #region Bank Only - - private BankAccountType _BankAccountType = BankAccountType.NA; - - public string BankID { get; set; } - - public string BranchID { get; set; } - - - public BankAccountType BankAccountType - { - get - { - if (AccountType == AccountType.BANK) - return _BankAccountType; - - return BankAccountType.NA; - } - set - { - _BankAccountType = AccountType == AccountType.BANK ? value : BankAccountType.NA; - } - } - - #endregion - - public Account(XmlNode node, AccountType type) - { - AccountType = type; - - AccountID = node.GetValue("//ACCTID"); - AccountKey = node.GetValue("//ACCTKEY"); - - switch (AccountType) - { - case AccountType.BANK: - InitializeBank(node); - break; - case AccountType.AP: - InitializeAP(node); - break; - case AccountType.AR: - InitializeAR(node); - break; - default: - break; - } - } - - /// - /// Initializes information specific to bank - /// - private void InitializeBank(XmlNode node) - { - BankID = node.GetValue("//BANKID"); - BranchID = node.GetValue("//BRANCHID"); - +using System; +using System.Xml; + +namespace OFXSharp +{ + public class Account + { + public string AccountID { get; set; } + public string AccountKey { get; set; } + public AccountType AccountType { get; set; } + + #region Bank Only + + private BankAccountType _BankAccountType = BankAccountType.NA; + + public string BankID { get; set; } + + public string BranchID { get; set; } + + + public BankAccountType BankAccountType + { + get + { + if (AccountType == AccountType.Bank) + return _BankAccountType; + + return BankAccountType.NA; + } + set + { + _BankAccountType = AccountType == AccountType.Bank ? value : BankAccountType.NA; + } + } + + #endregion + + public Account(XmlNode node, AccountType type) + { + AccountType = type; + + AccountID = node.GetValue(".//ACCTID"); + AccountKey = node.GetValue(".//ACCTKEY"); + + switch (AccountType) + { + case AccountType.Bank: + InitializeBank(node); + break; + case AccountType.AccountsPayable: + InitializeAP(node); + break; + case AccountType.AccountsReceivable: + InitializeAR(node); + break; + default: + break; + } + } + + public Account() { } + + /// + /// Initializes information specific to bank + /// + private void InitializeBank(XmlNode node) + { + BankID = node.GetValue(".//BANKID"); + BranchID = node.GetValue(".//BRANCHID"); + //Get Bank Account Type from XML - string bankAccountType = node.GetValue("//ACCTTYPE"); - - //Check that it has been set - if (String.IsNullOrEmpty(bankAccountType)) - throw new OFXParseException("Bank Account type unknown"); - - //Set bank account enum - _BankAccountType = bankAccountType.GetBankAccountType(); - } - - #region Account types not supported - - private void InitializeAP(XmlNode node) - { - throw new OFXParseException("AP Account type not supported"); - } - - private void InitializeAR(XmlNode node) - { - throw new OFXParseException("AR Account type not supported"); - } - - #endregion - } + string bankAccountType = node.GetValue(".//ACCTTYPE"); + + //Check that it has been set + if (String.IsNullOrEmpty(bankAccountType)) + throw new OFXParseException("Bank Account type unknown"); + + //Set bank account enum + _BankAccountType = bankAccountType.GetBankAccountType(); + } + + #region Account types not supported + + private void InitializeAP(XmlNode node) + { + throw new OFXParseException("AP Account type not supported"); + } + + private void InitializeAR(XmlNode node) + { + throw new OFXParseException("AR Account type not supported"); + } + + #endregion + } } \ No newline at end of file diff --git a/source/OFXSharp/AccountType.cs b/source/OFXSharp/AccountType.cs index bb79556..1b48fb6 100644 --- a/source/OFXSharp/AccountType.cs +++ b/source/OFXSharp/AccountType.cs @@ -5,13 +5,13 @@ namespace OFXSharp public enum AccountType { [Description("Bank Account")] - BANK, + Bank, [Description("Credit Card")] - CC, + CreditCard, [Description("Accounts Payable")] - AP, + AccountsPayable, [Description("Accounts Recievable")] - AR, + AccountsReceivable, NA, } } \ No newline at end of file diff --git a/source/OFXSharp/Balance.cs b/source/OFXSharp/Balance.cs index 19d15ec..7ea2117 100644 --- a/source/OFXSharp/Balance.cs +++ b/source/OFXSharp/Balance.cs @@ -16,7 +16,7 @@ public class Balance public Balance(XmlNode ledgerNode, XmlNode avaliableNode) { - var tempLedgerBalance = ledgerNode.GetValue("//BALAMT"); + var tempLedgerBalance = ledgerNode.GetValue(".//BALAMT"); if (!String.IsNullOrEmpty(tempLedgerBalance)) { @@ -47,7 +47,7 @@ public Balance(XmlNode ledgerNode, XmlNode avaliableNode) } else { - var tempAvaliableBalance = avaliableNode.GetValue("//BALAMT"); + var tempAvaliableBalance = avaliableNode.GetValue(".//BALAMT"); if (!String.IsNullOrEmpty(tempAvaliableBalance)) { @@ -58,10 +58,12 @@ public Balance(XmlNode ledgerNode, XmlNode avaliableNode) { throw new OFXParseException("Avaliable balance has not been set"); } - AvaliableBalanceDate = avaliableNode.GetValue("//DTASOF").ToDate(); + AvaliableBalanceDate = avaliableNode.GetValue(".//DTASOF").ToDate(); } - LedgerBalanceDate = ledgerNode.GetValue("//DTASOF").ToDate(); + LedgerBalanceDate = ledgerNode.GetValue(".//DTASOF").ToDate(); } + + public Balance() { } } } \ No newline at end of file diff --git a/source/OFXSharp/ModelDiagram.cd b/source/OFXSharp/ModelDiagram.cd index 39de33a..db37bb6 100644 --- a/source/OFXSharp/ModelDiagram.cd +++ b/source/OFXSharp/ModelDiagram.cd @@ -1,53 +1,64 @@  - + - AACBAAAAAAAAAAAEAAgAAAABCAAAAAAAAAAAAAAAAEI= + AAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAI= OFXDocument.cs - - - - + - + AAAAAAAAAAAAAAAAAAIIAAAAAAAAAAEAAAAAAAAAAAQ= Balance.cs - + BACAAIAAIQAAAAAAAgAAIAAAAAAAAAAAACAAQAAAAAA= Account.cs - + - AAAAAAAACAAAAAAAAAAAAABAAAAABAAAIAQAAAAAAAA= + AAAAAAAACAAAAAAAAAAAAABAAEAABAAAIAQAAAAQAAA= SignOn.cs - + AAAAAgAAAACAAQAAAAAEAgSgCBABFAUDgAAAAAAAIAA= Transaction.cs + + + + AACBAAAAAAAAAAAEAAgAAAABCAAAAAAAAAAAAAAAAEA= + Statement.cs + + + + + + + + + - + - AAAAAAIAAAAAAAAQAAAAAAEgAAAAAgAAAAAAAAAAAAA= + AQAAAAIAAAAAQAAAAQAAACAAAAAAAAAAAAAAAAAAAAA= AccountType.cs diff --git a/source/OFXSharp/OFXDocument.cs b/source/OFXSharp/OFXDocument.cs index a4efada..f04e49b 100644 --- a/source/OFXSharp/OFXDocument.cs +++ b/source/OFXSharp/OFXDocument.cs @@ -5,20 +5,7 @@ namespace OFXSharp { public class OFXDocument { - public DateTime StatementStart { get; set; } - - public DateTime StatementEnd { get; set; } - - public AccountType AccType { get; set; } - - public string Currency { get; set; } - public SignOn SignOn { get; set; } - - public Account Account { get; set; } - - public Balance Balance { get; set; } - - public List Transactions { get; set; } + public IList Statements { get; set; } } } \ No newline at end of file diff --git a/source/OFXSharp/OFXDocumentParser.cs b/source/OFXSharp/OFXDocumentParser.cs index 7529077..7ad9429 100644 --- a/source/OFXSharp/OFXDocumentParser.cs +++ b/source/OFXSharp/OFXDocumentParser.cs @@ -6,281 +6,325 @@ namespace OFXSharp { - public class OFXDocumentParser - { - public OFXDocument Import(FileStream stream) - { - using (var reader = new StreamReader(stream)) - { - return Import(reader.ReadToEnd()); - } - } - - public OFXDocument Import(string ofx) - { - return ParseOfxDocument(ofx); - } - - private OFXDocument ParseOfxDocument(string ofxString) - { - //If OFX file in SGML format, convert to XML - if (!IsXmlVersion(ofxString)) - { - ofxString = SGMLToXML(ofxString); - } - - return Parse(ofxString); - } - - private OFXDocument Parse(string ofxString) - { - var ofx = new OFXDocument {AccType = GetAccountType(ofxString)}; - - //Load into xml document - var doc = new XmlDocument(); - doc.Load(new StringReader(ofxString)); - - var currencyNode = doc.SelectSingleNode(GetXPath(ofx.AccType, OFXSection.CURRENCY)); - - if (currencyNode != null) - { - ofx.Currency = currencyNode.FirstChild.Value; - } - else - { - throw new OFXParseException("Currency not found"); - } - - //Get sign on node from OFX file - var signOnNode = doc.SelectSingleNode(Resources.SignOn); - - //If exists, populate signon obj, else throw parse error - if (signOnNode != null) - { - ofx.SignOn = new SignOn(signOnNode); - } - else - { - throw new OFXParseException("Sign On information not found"); - } - - //Get Account information for ofx doc - var accountNode = doc.SelectSingleNode(GetXPath(ofx.AccType, OFXSection.ACCOUNTINFO)); - - //If account info present, populate account object - if (accountNode != null) - { - ofx.Account = new Account(accountNode, ofx.AccType); - } - else - { - throw new OFXParseException("Account information not found"); - } - - //Get list of transactions - ImportTransations(ofx, doc); - - //Get balance info from ofx doc - var ledgerNode = doc.SelectSingleNode(GetXPath(ofx.AccType, OFXSection.BALANCE) + "/LEDGERBAL"); - var avaliableNode = doc.SelectSingleNode(GetXPath(ofx.AccType, OFXSection.BALANCE) + "/AVAILBAL"); - - //If balance info present, populate balance object - // ***** OFX files from my bank don't have the 'avaliableNode' node, so i manage a 'null' situation - if (ledgerNode != null) // && avaliableNode != null - { - ofx.Balance = new Balance(ledgerNode, avaliableNode); - } - else - { - throw new OFXParseException("Balance information not found"); - } - - return ofx; - } - - - /// - /// Returns the correct xpath to specified section for given account type - /// - /// Account type - /// Section of OFX document, e.g. Transaction Section - /// Thrown in account type not supported - private string GetXPath(AccountType type, OFXSection section) - { - string xpath, accountInfo; - - switch (type) - { - case AccountType.BANK: - xpath = Resources.BankAccount; - accountInfo = "/BANKACCTFROM"; - break; - case AccountType.CC: - xpath = Resources.CCAccount; - accountInfo = "/CCACCTFROM"; - break; - default: - throw new OFXException("Account Type not supported. Account type " + type); - } - - switch (section) - { - case OFXSection.ACCOUNTINFO: - return xpath + accountInfo; - case OFXSection.BALANCE: - return xpath; - case OFXSection.TRANSACTIONS: - return xpath + "/BANKTRANLIST"; - case OFXSection.SIGNON: - return Resources.SignOn; - case OFXSection.CURRENCY: - return xpath + "/CURDEF"; - default: - throw new OFXException("Unknown section found when retrieving XPath. Section " + section); - } - } - - /// - /// Returns list of all transactions in OFX document - /// - /// OFX document - /// List of transactions found in OFX document - private void ImportTransations(OFXDocument ofxDocument, XmlDocument doc) - { - var xpath = GetXPath(ofxDocument.AccType, OFXSection.TRANSACTIONS); - - ofxDocument.StatementStart = doc.GetValue(xpath + "//DTSTART").ToDate(); - ofxDocument.StatementEnd = doc.GetValue(xpath + "//DTEND").ToDate(); - - var transactionNodes = doc.SelectNodes(xpath + "//STMTTRN"); - - ofxDocument.Transactions = new List(); - - foreach (XmlNode node in transactionNodes) - ofxDocument.Transactions.Add(new Transaction(node, ofxDocument.Currency)); - } - - - /// - /// Checks account type of supplied file - /// OFX file want to check - /// Account type for account supplied in ofx file - private AccountType GetAccountType(string file) - { - if (file.IndexOf("") != -1) - return AccountType.CC; - - if (file.IndexOf("") != -1) - return AccountType.BANK; - - throw new OFXException("Unsupported Account Type"); - } - - /// - /// Check if OFX file is in SGML or XML format - /// - /// - /// - private bool IsXmlVersion(string file) - { - return (file.IndexOf("OFXHEADER:100") == -1); - } - - /// - /// Converts SGML to XML - /// - /// OFX File (SGML Format) - /// OFX File in XML format - private string SGMLToXML(string file) - { - var reader = new SgmlReader(); - - //Inititialize SGML reader - reader.InputStream = new StringReader(ParseHeader(file)); - reader.DocType = "OFX"; - - var sw = new StringWriter(); - var xml = new XmlTextWriter(sw); - - //write output of sgml reader to xml text writer - while (!reader.EOF) - xml.WriteNode(reader, true); - - //close xml text writer - xml.Flush(); - xml.Close(); - - var temp = sw.ToString().TrimStart().Split(new[] {'\n', '\r'}, StringSplitOptions.RemoveEmptyEntries); - - return String.Join("", temp); - } - - /// - /// Checks that the file is supported by checking the header. Removes the header. - /// - /// OFX file - /// File, without the header - private string ParseHeader(string file) - { - //Select header of file and split into array - //End of header worked out by finding first instance of '<' - //Array split based of new line & carrige return - var header = file.Substring(0, file.IndexOf('<')) - .Split(new[] {'\n', '\r'}, StringSplitOptions.RemoveEmptyEntries); - - //Check that no errors in header - CheckHeader(header); - - //Remove header - return file.Substring(file.IndexOf('<') - 1); - } - - /// - /// Checks that all the elements in the header are supported - /// - /// Header of OFX file in array - private void CheckHeader(string[] header) - { - if (header[0] != "OFXHEADER:100") - throw new OFXParseException("Incorrect header format"); - - if (header[1] != "DATA:OFXSGML") - throw new OFXParseException("Data type unsupported: " + header[1] + ". OFXSGML required"); - - if (header[2] != "VERSION:102") - throw new OFXParseException("OFX version unsupported. " + header[2]); - - if (header[3] != "SECURITY:NONE") - throw new OFXParseException("OFX security unsupported"); - - if (header[4] != "ENCODING:USASCII") - throw new OFXParseException("ASCII Format unsupported:" + header[4]); - - if (header[5] != "CHARSET:1252") - throw new OFXParseException("Charecter set unsupported:" + header[5]); - - if (header[6] != "COMPRESSION:NONE") - throw new OFXParseException("Compression unsupported"); - - if (header[7] != "OLDFILEUID:NONE") - throw new OFXParseException("OLDFILEUID incorrect"); - } - - #region Nested type: OFXSection - - /// - /// Section of OFX Document - /// - private enum OFXSection - { - SIGNON, - ACCOUNTINFO, - TRANSACTIONS, - BALANCE, - CURRENCY - } - - #endregion - } + public class OFXDocumentParser + { + public OFXDocument Import(Stream stream) + { + using (var reader = new StreamReader(stream)) + { + return Import(reader.ReadToEnd()); + } + } + + public OFXDocument Import(string ofx) + { + return ParseOfxDocument(ofx); + } + + private OFXDocument ParseOfxDocument(string ofxString) + { + //If OFX file in SGML format, convert to XML + if (!IsXmlVersion(ofxString)) + { + ofxString = SGMLToXML(ofxString); + } + + return Parse(ofxString); + } + + private OFXDocument Parse(string ofxString) + { + var accountTypes = GetAccountTypes(ofxString); + + //Load into xml document + var doc = new XmlDocument(); + doc.Load(new StringReader(ofxString)); + + var ofx = new OFXDocument { Statements = new List() }; + + //Get sign on node from OFX file + var signOnNode = doc.SelectSingleNode(Resources.SignOn); + + //If exists, populate signon obj, else throw parse error + if (signOnNode != null) + { + ofx.SignOn = new SignOn(signOnNode); + } + else + { + throw new OFXParseException("Sign On information not found"); + } + + // OFX supports multiple statements and account types in a single file, so loop through them + foreach (var accountType in accountTypes) + { + // get the statement responses for this account type + var statementNodes = doc.SelectNodes(GetXPath(accountType, OFXSection.Statement)); + + if (statementNodes == null || statementNodes.Count == 0) + { + throw new OFXParseException("No statement responses found for account type " + accountType); + } + + // now we can get into the nitty gritty + foreach (XmlNode statementNode in statementNodes) + { + var statement = new Statement { AccType = accountType }; + + // Get currency info + var currencyNode = statementNode.SelectSingleNode(GetXPath(accountType, OFXSection.Currency)); + if (currencyNode != null) + { + statement.Currency = currencyNode.FirstChild.Value; + } + else + { + throw new OFXParseException("Currency not found"); + } + + // Get account information for ofx doc + var accountNode = statementNode.SelectSingleNode(GetXPath(accountType, OFXSection.AccountInfo)); + if (accountNode != null) + { + statement.Account = new Account(accountNode, accountType); + } + else + { + throw new OFXParseException("Account information not found"); + } + + // Get list of transactions + ImportTransations(statement, statementNode); + + // Get balance info from ofx doc + var ledgerNode = statementNode.SelectSingleNode(".//LEDGERBAL"); //GetXPath(accountType, OFXSection.Balance) + + var avaliableNode = statementNode.SelectSingleNode(".//AVAILBAL"); //GetXPath(accountType, OFXSection.Balance) + + + // ***** OFX files from my bank don't have the 'avaliableNode' node, so i manage a 'null' situation + if (ledgerNode != null) // && avaliableNode != null + { + statement.Balance = new Balance(ledgerNode, avaliableNode); + } + else + { + throw new OFXParseException("Balance information not found"); + } + + ofx.Statements.Add(statement); + } + + } + + return ofx; + } + + + /// + /// Returns the correct xpath to specified section for given account type + /// + /// Account type + /// Section of OFX document, e.g. Transaction Section + /// Thrown in account type not supported + private string GetXPath(AccountType type, OFXSection section) + { + string xpath, accountInfo; + + switch (type) + { + case AccountType.Bank: + xpath = Resources.BankAccount; + accountInfo = ".//BANKACCTFROM"; + break; + case AccountType.CreditCard: + xpath = Resources.CCAccount; + accountInfo = ".//CCACCTFROM"; + break; + default: + throw new OFXException("Account Type not supported. Account type " + type); + } + + switch (section) + { + case OFXSection.Statement: + return xpath; + case OFXSection.AccountInfo: + return accountInfo; //xpath + + case OFXSection.Balance: + return xpath; + case OFXSection.Transactions: + return ".//BANKTRANLIST"; //xpath + + case OFXSection.SignOn: + return Resources.SignOn; + case OFXSection.Currency: + return ".//CURDEF"; //xpath + + default: + throw new OFXException("Unknown section found when retrieving XPath. Section " + section); + } + } + + /// + /// Returns list of all transactions in OFX document + /// + /// OFX Statement + /// OFX document + /// List of transactions found in OFX document + private void ImportTransations(Statement ofxStatement, XmlNode node) + { + var xpath = GetXPath(ofxStatement.AccType, OFXSection.Transactions); + + ofxStatement.StatementStart = node.GetValue(xpath + "//DTSTART").ToDate(); + ofxStatement.StatementEnd = node.GetValue(xpath + "//DTEND").ToDate(); + + var transactionNodes = node.SelectNodes(xpath + "//STMTTRN"); + ofxStatement.Transactions = new List(); + + if (transactionNodes == null || transactionNodes.Count == 0) + { + return; + } + + foreach (XmlNode txNode in transactionNodes) + { + ofxStatement.Transactions.Add(new Transaction(txNode, ofxStatement.Currency)); + } + } + + + /// + /// Checks account types of supplied file + /// + /// OFX file want to check + /// Account type for account supplied in ofx file + private IEnumerable GetAccountTypes(string file) + { + var accountTypes = new List(); + var accountTypeFound = false; + + if (file.IndexOf("") != -1) + { + accountTypes.Add(AccountType.CreditCard); + accountTypeFound = true; + } + + if (file.IndexOf("") != -1) + { + accountTypes.Add(AccountType.Bank); + accountTypeFound = true; + } + + if (!accountTypeFound) + { + throw new OFXException("Unsupported Account Type"); + } + + return accountTypes; + } + + /// + /// Check if OFX file is in SGML or XML format + /// + /// + /// + private static bool IsXmlVersion(string file) + { + return (file.IndexOf("OFXHEADER:100") == -1); + } + + /// + /// Converts SGML to XML + /// + /// OFX File (SGML Format) + /// OFX File in XML format + private static string SGMLToXML(string file) + { + var reader = new SgmlReader(); + + //Inititialize SGML reader + reader.InputStream = new StringReader(ParseHeader(file)); + reader.DocType = "OFX"; + + var sw = new StringWriter(); + var xml = new XmlTextWriter(sw); + + //write output of sgml reader to xml text writer + while (!reader.EOF) + xml.WriteNode(reader, true); + + //close xml text writer + xml.Flush(); + xml.Close(); + + var temp = sw.ToString().TrimStart().Split(new[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries); + + return String.Join("", temp); + } + + /// + /// Checks that the file is supported by checking the header. Removes the header. + /// + /// OFX file + /// File, without the header + private static string ParseHeader(string file) + { + //Select header of file and split into array + //End of header worked out by finding first instance of '<' + //Array split based of new line & carrige return + var header = file.Substring(0, file.IndexOf('<')) + .Split(new[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries); + + //Check that no errors in header + CheckHeader(header); + + //Remove header + return file.Substring(file.IndexOf('<') - 1); + } + + /// + /// Checks that all the elements in the header are supported + /// + /// Header of OFX file in array + private static void CheckHeader(string[] header) + { + if (header[0] != "OFXHEADER:100") + throw new OFXParseException("Incorrect header format"); + + if (header[1] != "DATA:OFXSGML") + throw new OFXParseException("Data type unsupported: " + header[1] + ". OFXSGML required"); + + if (header[2] != "VERSION:102") + throw new OFXParseException("OFX version unsupported. " + header[2]); + + if (header[3] != "SECURITY:NONE") + throw new OFXParseException("OFX security unsupported"); + + if (header[4] != "ENCODING:USASCII") + throw new OFXParseException("ASCII Format unsupported:" + header[4]); + + if (header[5] != "CHARSET:1252") + throw new OFXParseException("Charecter set unsupported:" + header[5]); + + if (header[6] != "COMPRESSION:NONE") + throw new OFXParseException("Compression unsupported"); + + if (header[7] != "OLDFILEUID:NONE") + throw new OFXParseException("OLDFILEUID incorrect"); + } + + #region Nested type: OFXSection + + /// + /// Section of OFX Document + /// + private enum OFXSection + { + SignOn, + Statement, + AccountInfo, + Transactions, + Balance, + Currency + } + + #endregion + } } \ No newline at end of file diff --git a/source/OFXSharp/OFXSharp.csproj b/source/OFXSharp/OFXSharp.csproj index b7013ce..f1ffe77 100644 --- a/source/OFXSharp/OFXSharp.csproj +++ b/source/OFXSharp/OFXSharp.csproj @@ -80,6 +80,7 @@ + diff --git a/source/OFXSharp/Resources.Designer.cs b/source/OFXSharp/Resources.Designer.cs index 119f382..c760e03 100644 --- a/source/OFXSharp/Resources.Designer.cs +++ b/source/OFXSharp/Resources.Designer.cs @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ // // This code was generated by a tool. -// Runtime Version:4.0.30319.34003 +// Runtime Version:4.0.30319.18408 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. @@ -61,7 +61,7 @@ internal Resources() { } /// - /// Looks up a localized string similar to OFX/BANKMSGSRSV1/STMTTRNRS/. + /// Looks up a localized string similar to OFX/BANKMSGSRSV1/STMTTRNRS. /// internal static string BankAccount { get { @@ -70,7 +70,7 @@ internal static string BankAccount { } /// - /// Looks up a localized string similar to OFX/CREDITCARDMSGSRSV1/CCSTMTTRNRS/CCSTMTRS. + /// Looks up a localized string similar to OFX/CREDITCARDMSGSRSV1/CCSTMTTRNRS. /// internal static string CCAccount { get { diff --git a/source/OFXSharp/Resources.resx b/source/OFXSharp/Resources.resx index 4629925..6bbbaa3 100644 --- a/source/OFXSharp/Resources.resx +++ b/source/OFXSharp/Resources.resx @@ -118,10 +118,10 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - OFX/BANKMSGSRSV1/STMTTRNRS/ + OFX/BANKMSGSRSV1/STMTTRNRS - OFX/CREDITCARDMSGSRSV1/CCSTMTTRNRS/ + OFX/CREDITCARDMSGSRSV1/CCSTMTTRNRS There are insufficent funds to pay your {item}. We have allocated what you have evenly between the {item} but there is no room in the budget for anything else, sorry... @@ -135,4 +135,4 @@ OFX/SIGNONMSGSRSV1/SONRS - + \ No newline at end of file diff --git a/source/OFXSharp/SignOn.cs b/source/OFXSharp/SignOn.cs index 4f5dc46..117dfaf 100644 --- a/source/OFXSharp/SignOn.cs +++ b/source/OFXSharp/SignOn.cs @@ -6,22 +6,24 @@ namespace OFXSharp public class SignOn { public string StatusSeverity { get; set; } - public DateTime DTServer { get; set; } - public int StatusCode { get; set; } - public string Language { get; set; } - public string IntuBid { get; set; } + public string FinancialInstitutionName { get; set; } + public string FinancialInstitutionId { get; set; } public SignOn(XmlNode node) { - StatusCode = Convert.ToInt32(node.GetValue("//CODE")); - StatusSeverity = node.GetValue("//SEVERITY"); - DTServer = node.GetValue("//DTSERVER").ToDate(); - Language = node.GetValue("//LANGUAGE"); - IntuBid = node.GetValue("//INTU.BID"); + StatusCode = Convert.ToInt32(node.GetValue(".//CODE")); + StatusSeverity = node.GetValue(".//SEVERITY"); + DTServer = node.GetValue(".//DTSERVER").ToDate(); + Language = node.GetValue(".//LANGUAGE"); + IntuBid = node.GetValue(".//INTU.BID"); + FinancialInstitutionName = node.GetValue(".//ORG"); + FinancialInstitutionId = node.GetValue(".//FID"); } + + public SignOn() { } } } \ No newline at end of file diff --git a/source/OFXSharp/Statement.cs b/source/OFXSharp/Statement.cs new file mode 100644 index 0000000..fcd39da --- /dev/null +++ b/source/OFXSharp/Statement.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace OFXSharp +{ + public class Statement + { + public DateTime StatementStart { get; set; } + public DateTime StatementEnd { get; set; } + public AccountType AccType { get; set; } + public string Currency { get; set; } + public Account Account { get; set; } + public Balance Balance { get; set; } + public List Transactions { get; set; } + } +} diff --git a/source/OFXSharp/Transaction.cs b/source/OFXSharp/Transaction.cs index 4124b37..66649ca 100644 --- a/source/OFXSharp/Transaction.cs +++ b/source/OFXSharp/Transaction.cs @@ -4,134 +4,118 @@ namespace OFXSharp { - public class Transaction - { - public OFXTransactionType TransType { get; set; } - - public DateTime Date { get; set; } - - public decimal Amount { get; set; } - - public string TransactionID { get; set; } - - public string Name { get; set; } - - public DateTime TransactionInitializationDate { get; set; } - - public DateTime FundAvaliabilityDate { get; set; } - - public string Memo { get; set; } - - public string IncorrectTransactionID { get; set; } - - public TransactionCorrectionType TransactionCorrectionAction { get; set; } - - public string ServerTransactionID { get; set; } - - public string CheckNum { get; set; } - - public string ReferenceNumber { get; set; } - - public string Sic { get; set; } - - public string PayeeID { get; set; } - - public Account TransactionSenderAccount { get; set; } - - public string Currency { get; set; } - - public Transaction() - { - } - - public Transaction(XmlNode node, string currency) - { - TransType = GetTransactionType(node.GetValue(".//TRNTYPE")); - Date = node.GetValue(".//DTPOSTED").ToDate(); - TransactionInitializationDate = node.GetValue(".//DTUSER").ToDate(); - FundAvaliabilityDate = node.GetValue(".//DTAVAIL").ToDate(); - - try - { - Amount = Convert.ToDecimal(node.GetValue(".//TRNAMT"), CultureInfo.InvariantCulture); - } - catch (Exception ex) - { - throw new OFXParseException("Transaction Amount unknown", ex); - } - - try - { - TransactionID = node.GetValue(".//FITID"); - } - catch (Exception ex) - { - throw new OFXParseException("Transaction ID unknown", ex); - } - - IncorrectTransactionID = node.GetValue(".//CORRECTFITID"); - - - //If Transaction Correction Action exists, populate - var tempCorrectionAction = node.GetValue(".//CORRECTACTION"); - - TransactionCorrectionAction = !String.IsNullOrEmpty(tempCorrectionAction) - ? GetTransactionCorrectionType(tempCorrectionAction) - : TransactionCorrectionType.NA; - - ServerTransactionID = node.GetValue(".//SRVRTID"); - CheckNum = node.GetValue(".//CHECKNUM"); - ReferenceNumber = node.GetValue(".//REFNUM"); - Sic = node.GetValue(".//SIC"); - PayeeID = node.GetValue(".//PAYEEID"); - Name = node.GetValue(".//NAME"); - Memo = node.GetValue(".//MEMO"); - - //If differenct currency to CURDEF, populate currency - if (NodeExists(node, ".//CURRENCY")) - Currency = node.GetValue(".//CURRENCY"); - else if (NodeExists(node, ".//ORIGCURRENCY")) - Currency = node.GetValue(".//ORIGCURRENCY"); + public class Transaction + { + public OFXTransactionType TransType { get; set; } + public DateTime Date { get; set; } + public decimal Amount { get; set; } + public string TransactionID { get; set; } + public string Name { get; set; } + public DateTime TransactionInitializationDate { get; set; } + public DateTime FundAvaliabilityDate { get; set; } + public string Memo { get; set; } + public string IncorrectTransactionID { get; set; } + public TransactionCorrectionType TransactionCorrectionAction { get; set; } + public string ServerTransactionID { get; set; } + public string CheckNum { get; set; } + public string ReferenceNumber { get; set; } + public string Sic { get; set; } + public string PayeeID { get; set; } + public Account TransactionSenderAccount { get; set; } + public string Currency { get; set; } + + public Transaction() + { + } + + public Transaction(XmlNode node, string currency) + { + TransType = GetTransactionType(node.GetValue(".//TRNTYPE")); + Date = node.GetValue(".//DTPOSTED").ToDate(); + TransactionInitializationDate = node.GetValue(".//DTUSER").ToDate(); + FundAvaliabilityDate = node.GetValue(".//DTAVAIL").ToDate(); + + try + { + Amount = Convert.ToDecimal(node.GetValue(".//TRNAMT"), CultureInfo.InvariantCulture); + } + catch (Exception ex) + { + throw new OFXParseException("Transaction Amount unknown", ex); + } + + try + { + TransactionID = node.GetValue(".//FITID"); + } + catch (Exception ex) + { + throw new OFXParseException("Transaction ID unknown", ex); + } + + IncorrectTransactionID = node.GetValue(".//CORRECTFITID"); + + + //If Transaction Correction Action exists, populate + var tempCorrectionAction = node.GetValue(".//CORRECTACTION"); + + TransactionCorrectionAction = !String.IsNullOrEmpty(tempCorrectionAction) + ? GetTransactionCorrectionType(tempCorrectionAction) + : TransactionCorrectionType.NA; + + ServerTransactionID = node.GetValue(".//SRVRTID"); + CheckNum = node.GetValue(".//CHECKNUM"); + ReferenceNumber = node.GetValue(".//REFNUM"); + Sic = node.GetValue(".//SIC"); + PayeeID = node.GetValue(".//PAYEEID"); + Name = node.GetValue(".//NAME"); + Memo = node.GetValue(".//MEMO"); + + //If differenct currency to CURDEF, populate currency + if (NodeExists(node, ".//CURRENCY")) + Currency = node.GetValue(".//CURRENCY"); + else if (NodeExists(node, ".//ORIGCURRENCY")) + Currency = node.GetValue(".//ORIGCURRENCY"); //If currency not different, set to CURDEF - else - Currency = currency; - - //If senders bank/credit card details avaliable, add - if (NodeExists(node, ".//BANKACCTTO")) - TransactionSenderAccount = new Account(node.SelectSingleNode(".//BANKACCTTO"), AccountType.BANK); - else if (NodeExists(node, ".//CCACCTTO")) - TransactionSenderAccount = new Account(node.SelectSingleNode(".//CCACCTTO"), AccountType.CC); - } - - /// - /// Returns TransactionType from string version - /// - /// string version of transaction type - /// Enum version of given transaction type string - private OFXTransactionType GetTransactionType(string transactionType) - { - return (OFXTransactionType) Enum.Parse(typeof (OFXTransactionType), transactionType); - } - - /// - /// Returns TransactionCorrectionType from string version - /// - /// string version of Transaction Correction Type - /// Enum version of given TransactionCorrectionType string - private TransactionCorrectionType GetTransactionCorrectionType(string transactionCorrectionType) - { - return (TransactionCorrectionType) Enum.Parse(typeof (TransactionCorrectionType), transactionCorrectionType); - } - - /// - /// Checks if a node exists - /// - /// Node to search in - /// XPath to node you want to see if exists - /// - private bool NodeExists(XmlNode node, string xpath) - { - return (node.SelectSingleNode(xpath) != null); - } - } + else + Currency = currency; + + //If senders bank/credit card details avaliable, add + if (NodeExists(node, ".//BANKACCTTO")) + TransactionSenderAccount = new Account(node.SelectSingleNode(".//BANKACCTTO"), AccountType.Bank); + else if (NodeExists(node, ".//CCACCTTO")) + TransactionSenderAccount = new Account(node.SelectSingleNode(".//CCACCTTO"), AccountType.CreditCard); + } + + /// + /// Returns TransactionType from string version + /// + /// string version of transaction type + /// Enum version of given transaction type string + private OFXTransactionType GetTransactionType(string transactionType) + { + return (OFXTransactionType)Enum.Parse(typeof(OFXTransactionType), transactionType); + } + + /// + /// Returns TransactionCorrectionType from string version + /// + /// string version of Transaction Correction Type + /// Enum version of given TransactionCorrectionType string + private TransactionCorrectionType GetTransactionCorrectionType(string transactionCorrectionType) + { + return (TransactionCorrectionType)Enum.Parse(typeof(TransactionCorrectionType), transactionCorrectionType); + } + + /// + /// Checks if a node exists + /// + /// Node to search in + /// XPath to node you want to see if exists + /// + private bool NodeExists(XmlNode node, string xpath) + { + return (node.SelectSingleNode(xpath) != null); + } + } } \ No newline at end of file