From 232aa68ad09b4650d825089b6f4e471483a578ea Mon Sep 17 00:00:00 2001
From: Jim Dale <26042891+jim-dale@users.noreply.github.com>
Date: Mon, 18 Dec 2023 09:24:34 +0000
Subject: [PATCH] Revert change to build for x64 only; build for 'AnyCPU' (#15)
Started documenting public methods
Updated .editrorconfig and fixing code analysis issues
Added stylecop
---
.editorconfig | 22 +-
BankingTools.sln | 1 +
Directory.Build.props | 18 +-
README.md | 4 +-
src/OfxNet/IOfxElement.cs | 20 ++
src/OfxNet/Models/OfxAccount.cs | 10 +
src/OfxNet/Models/OfxAccountBalance.cs | 1 +
src/OfxNet/Models/OfxAccountType.cs | 2 +-
src/OfxNet/Models/OfxBankAccount.cs | 14 +
src/OfxNet/Models/OfxCorrectiveAction.cs | 14 +
src/OfxNet/Models/OfxCurrency.cs | 1 +
src/OfxNet/Models/OfxPayee.cs | 8 +
src/OfxNet/Models/OfxSeverity.cs | 20 +-
src/OfxNet/Models/OfxSignOn.cs | 3 +
src/OfxNet/Models/OfxStatement.cs | 3 +
src/OfxNet/Models/OfxStatementTransaction.cs | 21 ++
src/OfxNet/Models/OfxStatus.cs | 14 +
src/OfxNet/Models/OfxTransactionList.cs | 4 +
src/OfxNet/Models/OfxTransactionType.cs | 2 +
src/OfxNet/Models/OfxVersion.cs | 54 ++-
src/OfxNet/OfxConstants.cs | 315 +++++++++++++++++-
src/OfxNet/OfxDocument.cs | 257 +++++++-------
src/OfxNet/OfxDocumentSettings.cs | 5 +-
src/OfxNet/OfxException.cs | 25 +-
src/OfxNet/OfxNet.csproj | 6 +-
src/OfxNet/OfxParser.cs | 91 +++--
src/OfxNet/Sgml/SgmlConstants.cs | 97 +++++-
src/OfxNet/Sgml/SgmlDocument.cs | 7 +-
src/OfxNet/Sgml/SgmlElement.cs | 38 ++-
src/OfxNet/Sgml/SgmlHeader.cs | 42 ++-
src/OfxNet/Sgml/SgmlHeaderExtensions.cs | 14 +-
src/OfxNet/Sgml/SgmlHeaderParser.cs | 109 +++---
src/OfxNet/Sgml/SgmlParseException.cs | 6 +-
src/OfxNet/Sgml/SgmlParseResult.cs | 18 +-
src/OfxNet/Sgml/SgmlParser.cs | 165 ++++-----
src/OfxNet/Sgml/SgmlTagType.cs | 2 +-
src/OfxNet/Xml/XElementAdapter.cs | 11 +-
stylecop.json | 17 +
test/OfxNet.IntegrationTests/.editorconfig | 8 +
.../OfxDocumentTests.cs | 25 +-
.../OfxNet.IntegrationTests.csproj | 1 -
test/OfxNet.UnitTests/OfxParserTests.cs | 100 +++---
test/OfxNet.UnitTests/SgmlOfxElementTests.cs | 4 +-
43 files changed, 1169 insertions(+), 430 deletions(-)
create mode 100644 stylecop.json
create mode 100644 test/OfxNet.IntegrationTests/.editorconfig
diff --git a/.editorconfig b/.editorconfig
index 7c9ca24..f92774a 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -18,6 +18,7 @@ indent_style = space
indent_size = 4
insert_final_newline = true
trim_trailing_whitespace = true
+# CS1591: Missing XML comment for publicly visible type or member
dotnet_diagnostic.CS1591.severity = none
# Xml project files
@@ -37,10 +38,10 @@ indent_style = space
[*.{cs,vb}]
# Sort using and Import directives with System.* appearing first
dotnet_sort_system_directives_first = true
-dotnet_style_qualification_for_field = false:warning
-dotnet_style_qualification_for_property = false:warning
-dotnet_style_qualification_for_method = false:warning
-dotnet_style_qualification_for_event = false:warning
+dotnet_style_qualification_for_field = true:suggestion
+dotnet_style_qualification_for_property = true:suggestion
+dotnet_style_qualification_for_method = true:suggestion
+dotnet_style_qualification_for_event = true:suggestion
# Use language keywords instead of framework type names for type references
dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
@@ -195,5 +196,18 @@ csharp_prefer_simple_default_expression = true:suggestion
csharp_style_prefer_local_over_anonymous_function = true:suggestion
csharp_style_prefer_index_operator = true:suggestion
+# SA1000: Keywords should be spaced correctly
+dotnet_diagnostic.SA1000.severity = none
+# SA1010: Opening square brackets should be spaced correctly
+dotnet_diagnostic.SA1010.severity = none
+# SA1600: Elements must be documented
+dotnet_diagnostic.SA1600.severity = none
+# SA1602: Enumeration items should be documented
+dotnet_diagnostic.SA1602.severity = none
+# SA1623: Property summary documentation must match accessors
+dotnet_diagnostic.SA1623.severity = none
+# SA1633: File should have header
+dotnet_diagnostic.SA1633.severity = none
+
[*.sln]
indent_style = tab
diff --git a/BankingTools.sln b/BankingTools.sln
index 69632b3..f0bb480 100644
--- a/BankingTools.sln
+++ b/BankingTools.sln
@@ -17,6 +17,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
.github\workflows\ofxnet-pr.yml = .github\workflows\ofxnet-pr.yml
.github\workflows\ofxnet-publish.yml = .github\workflows\ofxnet-publish.yml
README.md = README.md
+ stylecop.json = stylecop.json
EndProjectSection
EndProject
Global
diff --git a/Directory.Build.props b/Directory.Build.props
index 723234e..f4f1733 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -6,9 +6,25 @@
true
true
true
- x64
en
Jim Dale
true
+ latest-all
+ true
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+ Analysis/stylecop.json
+
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
index 3c32f6b..2c66bdf 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
# BankingTools
-Banking tools v1.4.0
+Banking tools v1.4.1
-[![PR build](https://github.com/jim-dale/BankingTools/actions/workflows/ofxnet-publish.yml/badge.svg)](https://github.com/jim-dale/BankingTools/actions/workflows/ofxnet-publish.yml)
+[![PR build](https://github.com/jim-dale/BankingTools/actions/workflows/ofxnet-pr.yml/badge.svg)](https://github.com/jim-dale/BankingTools/actions/workflows/ofxnet-pr.yml)
[![Published build](https://github.com/jim-dale/BankingTools/actions/workflows/ofxnet-publish.yml/badge.svg)](https://github.com/jim-dale/BankingTools/actions/workflows/ofxnet-publish.yml)
[![Nuget](https://img.shields.io/nuget/v/OfxNet)](https://www.nuget.org/packages/OfxNet/)
diff --git a/src/OfxNet/IOfxElement.cs b/src/OfxNet/IOfxElement.cs
index 44999ef..def9c90 100644
--- a/src/OfxNet/IOfxElement.cs
+++ b/src/OfxNet/IOfxElement.cs
@@ -3,9 +3,29 @@
using System;
using System.Collections.Generic;
+///
+/// Interface for elements within an SGML or XML document.
+///
public interface IOfxElement
{
+ ///
+ /// Gets the value of the element as a string.
+ ///
public string? Value { get; }
+
+ ///
+ /// Searches for the child element matching the name specified.
+ ///
+ /// The name of the child element.
+ /// The to use to match the element name.
+ /// The child element if found, otherwise .
public IOfxElement? Element(string name, StringComparer comparer);
+
+ ///
+ /// Searches for all the child elements matching the name specified.
+ ///
+ /// The name of the child elements.
+ /// The to use to match the element name.
+ /// The collection of child elements if found, otherwise an empty collection.
public IEnumerable Elements(string name, StringComparer comparer);
}
diff --git a/src/OfxNet/Models/OfxAccount.cs b/src/OfxNet/Models/OfxAccount.cs
index 3931556..b4c405a 100644
--- a/src/OfxNet/Models/OfxAccount.cs
+++ b/src/OfxNet/Models/OfxAccount.cs
@@ -1,7 +1,17 @@
namespace OfxNet;
+///
+/// Base class inherited by the different bank account types.
+///
public class OfxAccount
{
+ ///
+ /// Gets or sets the account number.
+ ///
public string? AccountNumber { get; set; }
+
+ ///
+ /// Gets or sets the checksum.
+ ///
public string? Checksum { get; set; }
}
diff --git a/src/OfxNet/Models/OfxAccountBalance.cs b/src/OfxNet/Models/OfxAccountBalance.cs
index fa2a68f..4fb9227 100644
--- a/src/OfxNet/Models/OfxAccountBalance.cs
+++ b/src/OfxNet/Models/OfxAccountBalance.cs
@@ -5,5 +5,6 @@
public class OfxAccountBalance
{
public decimal Balance { get; set; }
+
public DateTimeOffset DateAsOf { get; set; }
}
diff --git a/src/OfxNet/Models/OfxAccountType.cs b/src/OfxNet/Models/OfxAccountType.cs
index 764e993..8ef2683 100644
--- a/src/OfxNet/Models/OfxAccountType.cs
+++ b/src/OfxNet/Models/OfxAccountType.cs
@@ -14,5 +14,5 @@ public enum OfxAccountType
[Description("Line of credit")]
CREDITLINE,
[Description("Certificate of Deposit")]
- CD
+ CD,
}
diff --git a/src/OfxNet/Models/OfxBankAccount.cs b/src/OfxNet/Models/OfxBankAccount.cs
index 7237c97..0e7d970 100644
--- a/src/OfxNet/Models/OfxBankAccount.cs
+++ b/src/OfxNet/Models/OfxBankAccount.cs
@@ -1,8 +1,22 @@
namespace OfxNet;
+///
+/// OFX Bank account information.
+///
public class OfxBankAccount : OfxAccount
{
+ ///
+ /// Gets or sets the bank identifier.
+ ///
public string? BankId { get; set; }
+
+ ///
+ /// Gets or sets the branch identifier.
+ ///
public string? BranchId { get; set; }
+
+ ///
+ /// Gets or sets the type of account.
+ ///
public OfxAccountType AccountType { get; set; }
}
diff --git a/src/OfxNet/Models/OfxCorrectiveAction.cs b/src/OfxNet/Models/OfxCorrectiveAction.cs
index ccd9dc6..defd63c 100644
--- a/src/OfxNet/Models/OfxCorrectiveAction.cs
+++ b/src/OfxNet/Models/OfxCorrectiveAction.cs
@@ -2,11 +2,25 @@
using System.ComponentModel;
+///
+/// OFX corrective action enum values.
+///
public enum OfxCorrectiveAction
{
+ ///
+ /// Not set.
+ ///
NotSet,
+
+ ///
+ /// Replace this transaction with one referenced by CORRECTFITID.
+ ///
[Description("Replace this transaction with one referenced by CORRECTFITID")]
REPLACE,
+
+ ///
+ /// Delete transaction.
+ ///
[Description("Delete transaction")]
DELETE,
}
diff --git a/src/OfxNet/Models/OfxCurrency.cs b/src/OfxNet/Models/OfxCurrency.cs
index 08e9306..a9f9f09 100644
--- a/src/OfxNet/Models/OfxCurrency.cs
+++ b/src/OfxNet/Models/OfxCurrency.cs
@@ -3,5 +3,6 @@
public class OfxCurrency(decimal rate, string symbol)
{
public decimal Rate { get; init; } = rate;
+
public string Symbol { get; init; } = symbol;
}
diff --git a/src/OfxNet/Models/OfxPayee.cs b/src/OfxNet/Models/OfxPayee.cs
index 87ad3b0..49b5ab1 100644
--- a/src/OfxNet/Models/OfxPayee.cs
+++ b/src/OfxNet/Models/OfxPayee.cs
@@ -3,12 +3,20 @@
public class OfxPayee
{
public string? Name { get; set; }
+
public string? AddressLine1 { get; set; }
+
public string? AddressLine2 { get; set; }
+
public string? AddressLine3 { get; set; }
+
public string? City { get; set; }
+
public string? State { get; set; }
+
public string? PostalCode { get; set; }
+
public string? Country { get; set; }
+
public string? PhoneNumber { get; set; }
}
diff --git a/src/OfxNet/Models/OfxSeverity.cs b/src/OfxNet/Models/OfxSeverity.cs
index 53ee39c..915b0f8 100644
--- a/src/OfxNet/Models/OfxSeverity.cs
+++ b/src/OfxNet/Models/OfxSeverity.cs
@@ -2,13 +2,31 @@
using System.ComponentModel;
+///
+/// OFX severity values.
+///
public enum OfxSeverity
{
+ ///
+ /// Not set.
+ ///
NotSet,
+
+ ///
+ /// Informational only.
+ ///
[Description("Informational only")]
INFO,
+
+ ///
+ /// Some problem with the request occurred but a valid response still present.
+ ///
[Description("Some problem with the request occurred but a valid response still present")]
WARN,
+
+ ///
+ /// A problem severe enough that response could not be made.
+ ///
[Description("A problem severe enough that response could not be made")]
- ERROR
+ ERROR,
}
diff --git a/src/OfxNet/Models/OfxSignOn.cs b/src/OfxNet/Models/OfxSignOn.cs
index 5714ebb..04cc32b 100644
--- a/src/OfxNet/Models/OfxSignOn.cs
+++ b/src/OfxNet/Models/OfxSignOn.cs
@@ -5,7 +5,10 @@ namespace OfxNet;
public class OfxSignOn
{
public OfxStatus? Status { get; set; }
+
public DateTimeOffset ServerDate { get; set; }
+
public string? Language { get; set; }
+
public string? IntuBid { get; set; }
}
diff --git a/src/OfxNet/Models/OfxStatement.cs b/src/OfxNet/Models/OfxStatement.cs
index 93b6bbc..6bb1eaa 100644
--- a/src/OfxNet/Models/OfxStatement.cs
+++ b/src/OfxNet/Models/OfxStatement.cs
@@ -3,8 +3,11 @@
public class OfxStatement
{
public string? DefaultCurrency { get; set; }
+
public OfxAccountBalance? LedgerBalance { get; set; }
+
public OfxAccountBalance? AvailableBalance { get; set; }
+
public OfxTransactionList? TransactionList { get; set; }
}
diff --git a/src/OfxNet/Models/OfxStatementTransaction.cs b/src/OfxNet/Models/OfxStatementTransaction.cs
index 5af4e42..63b94f4 100644
--- a/src/OfxNet/Models/OfxStatementTransaction.cs
+++ b/src/OfxNet/Models/OfxStatementTransaction.cs
@@ -5,25 +5,46 @@
public class OfxStatementTransaction
{
public OfxTransactionType TxType { get; set; }
+
public DateTimeOffset DatePosted { get; set; }
+
public DateTimeOffset? DateUser { get; set; }
+
public DateTimeOffset? DateAvailable { get; set; }
+
public decimal Amount { get; set; }
+
public string? FitId { get; set; }
+
public string? Name { get; set; }
+
public string? Memo { get; set; }
+
public string? Memo2 { get; set; }
+
public string? ChequeNumber { get; set; }
+
public string? ReferenceNumber { get; set; }
+
public string? CorrectFitId { get; set; }
+
public OfxCorrectiveAction CorrectAction { get; set; }
+
public string? ServiceProviderName { get; set; }
+
public string? ServerTxId { get; set; }
+
public int? StandardIndustrialCode { get; set; }
+
public string? PayeeId { get; set; }
+
public OfxPayee? Payee { get; set; }
+
public OfxCurrency? Currency { get; set; }
+
public OfxCurrency? OriginalCurrency { get; set; }
+
public OfxBankAccount? BankAccountTo { get; set; }
+
public OfxCreditCardAccount? CreditCardAccountTo { get; set; }
}
diff --git a/src/OfxNet/Models/OfxStatus.cs b/src/OfxNet/Models/OfxStatus.cs
index c1ae0ec..2e02321 100644
--- a/src/OfxNet/Models/OfxStatus.cs
+++ b/src/OfxNet/Models/OfxStatus.cs
@@ -1,8 +1,22 @@
namespace OfxNet;
+///
+/// OFX status aggregate.
+///
public class OfxStatus
{
+ ///
+ /// Gets or sets the OFX error code.
+ ///
public int Code { get; set; }
+
+ ///
+ /// Gets or sets the severity of the error.
+ ///
public OfxSeverity Severity { get; set; }
+
+ ///
+ /// Gets or sets the textual explanation from the financial institution.
+ ///
public string? Message { get; set; }
}
diff --git a/src/OfxNet/Models/OfxTransactionList.cs b/src/OfxNet/Models/OfxTransactionList.cs
index 41bf1e4..8eff028 100644
--- a/src/OfxNet/Models/OfxTransactionList.cs
+++ b/src/OfxNet/Models/OfxTransactionList.cs
@@ -6,6 +6,10 @@
public class OfxTransactionList
{
public DateTimeOffset StartDate { get; set; }
+
public DateTimeOffset EndDate { get; set; }
+
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1002:Do not expose generic lists", Justification = "Simple implementation.")]
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2227:Collection properties should be read only", Justification = "Simple implementation.")]
public List Transactions { get; set; } = [];
}
diff --git a/src/OfxNet/Models/OfxTransactionType.cs b/src/OfxNet/Models/OfxTransactionType.cs
index a148e13..b0eb940 100644
--- a/src/OfxNet/Models/OfxTransactionType.cs
+++ b/src/OfxNet/Models/OfxTransactionType.cs
@@ -10,7 +10,9 @@ public enum OfxTransactionType
[Description("Generic debit")]
DEBIT,
[Description("Interest earned or paid. Note: Depends on signage of amount")]
+#pragma warning disable CA1720 // Identifier contains type name
INT,
+#pragma warning restore CA1720 // Identifier contains type name
[Description("Dividend")]
DIV,
[Description("FI fee")]
diff --git a/src/OfxNet/Models/OfxVersion.cs b/src/OfxNet/Models/OfxVersion.cs
index 24dccbb..36f0621 100644
--- a/src/OfxNet/Models/OfxVersion.cs
+++ b/src/OfxNet/Models/OfxVersion.cs
@@ -2,23 +2,39 @@
using System;
-public readonly struct OfxVersion(int major, int minor, int revision) : IEquatable
+///
+/// OFX version structure.
+///
+/// The OFX major version number.
+/// The OFX minor version number.
+/// The OFX revision number.
+public readonly struct OfxVersion(int major, int minor, int revision)
+ : IEquatable
{
- public static readonly OfxVersion InvalidHeader = new OfxVersion(-1, -1, -1);
- public static readonly OfxVersion HeaderV1 = new OfxVersion(1, 0, 0);
+ ///
+ /// A read-only instance of the structure whose value represents an invalid value.
+ ///
+ public static readonly OfxVersion InvalidHeader = new(-1, -1, -1);
+ ///
+ /// The OFX standard version number.
+ ///
+ public static readonly OfxVersion HeaderV1 = new(1, 0, 0);
+
+ ///
+ /// Gets the major version number.
+ ///
public int Major { get; } = major;
- public int Minor { get; } = minor;
- public int Revision { get; } = revision;
- public override bool Equals(object? obj) => (obj is OfxVersion other) && Equals(other);
+ ///
+ /// Gets the minor version number.
+ ///
+ public int Minor { get; } = minor;
- public bool Equals(OfxVersion other)
- {
- return (Major == other.Major)
- && (Minor == other.Minor)
- && (Revision == other.Revision);
- }
+ ///
+ /// Gets the revision number.
+ ///
+ public int Revision { get; } = revision;
public static bool operator ==(OfxVersion left, OfxVersion right)
{
@@ -30,8 +46,20 @@ public bool Equals(OfxVersion other)
return !(left == right);
}
+ ///
+ public override bool Equals(object? obj) => (obj is OfxVersion other) && this.Equals(other);
+
+ ///
+ public bool Equals(OfxVersion other)
+ {
+ return (this.Major == other.Major)
+ && (this.Minor == other.Minor)
+ && (this.Revision == other.Revision);
+ }
+
+ ///
public override int GetHashCode()
{
- return HashCode.Combine(Major, Minor, Revision);
+ return HashCode.Combine(this.Major, this.Minor, this.Revision);
}
}
diff --git a/src/OfxNet/OfxConstants.cs b/src/OfxNet/OfxConstants.cs
index 3078a1b..7cc3513 100644
--- a/src/OfxNet/OfxConstants.cs
+++ b/src/OfxNet/OfxConstants.cs
@@ -1,84 +1,396 @@
namespace OfxNet;
+using System;
+using System.Diagnostics.Metrics;
using System.Globalization;
+using System.Transactions;
+///
+/// Constants for parsing OFX documents.
+///
public static class OfxConstants
{
+ ///
+ /// The OFX document root node.
+ ///
public const string OfxTag = "OFX";
+
+ ///
+ /// Sign-on message set response.
+ ///
public const string SignonMessageSetResponseV1 = "SIGNONMSGSRSV1";
+
+ ///
+ /// Sign-on response.
+ ///
public const string SignonResponse = "SONRS";
+ ///
+ /// Bank messsage set response.
+ ///
public const string BankMessageSetResponseV1 = "BANKMSGSRSV1";
+
+ ///
+ /// Statement transactions response.
+ ///
public const string StatementTxResponse = "STMTTRNRS";
+
+ ///
+ /// Statement response.
+ ///
public const string StatementResponse = "STMTRS";
+
+ ///
+ /// Bank account from aggregate.
+ ///
public const string BankAccountFrom = "BANKACCTFROM";
+
+ ///
+ /// Bank account to aggregate.
+ ///
public const string BankAccountTo = "BANKACCTTO";
+
+ ///
+ /// Statement transaction data aggregate.
+ ///
public const string BankTransactionList = "BANKTRANLIST";
+ ///
+ /// Credit Card message set response version 1.
+ ///
public const string CreditCardMessageSetResponseV1 = "CREDITCARDMSGSRSV1";
+
+ ///
+ /// Credit Card message set response version 2.
+ ///
public const string CreditCardMessageSetResponseV2 = "CREDITCARDMSGSRSV2";
+
+ ///
+ /// Credit Card statement transaction response.
+ ///
public const string CreditCardStatementTxResponse = "CCSTMTTRNRS";
+
+ ///
+ /// Credit card statement download response.
+ ///
public const string CreditCardStatementResponse = "CCSTMTRS";
+
+ ///
+ /// Credit Card account from aggregate.
+ ///
public const string CreditCardAccountFrom = "CCACCTFROM";
+
+ ///
+ /// Credit Card account to aggregate.
+ ///
public const string CreditCardAccountTo = "CCACCTTO";
+ ///
+ /// Intuit bank identifier.
+ ///
public const string IntuBId = "INTU.BID";
+
+ ///
+ /// Identifies the human-readable language used for such things as status messages and email.
+ /// Language is specified as a three-letter code based on ISO-639.
+ ///
public const string Language = "LANGUAGE";
+
+ ///
+ /// Date and time of the server response.
+ ///
public const string ServerDate = "DTSERVER";
+
+ ///
+ /// Client-assigned globally-unique ID for this transaction.
+ ///
public const string TransactionUid = "TRNUID";
+
+ ///
+ /// Status aggregate.
+ ///
public const string Status = "STATUS";
+
+ ///
+ /// OFX error code.
+ ///
public const string Code = "CODE";
+
+ ///
+ /// Severity of the error.
+ ///
+ /// -
+ /// INFO
+ /// Informational only.
+ ///
+ /// -
+ /// WARN
+ /// Some problem with the request occurred but a valid response still present.
+ ///
+ /// -
+ /// ERROR
+ /// A problem severe enough that response could not be made.
+ ///
+ ///
+ ///
public const string Severity = "SEVERITY";
+
+ ///
+ /// Default currency identifier. The values are based on the ISO-4217 three-letter currency identifiers.
+ ///
public const string DefaultCurrency = "CURDEF";
+
+ ///
+ /// Currency identifier. The values are based on the ISO-4217 three-letter currency identifiers.
+ ///
public const string Currency = "CURRENCY";
+
+ ///
+ /// Currency identifier. The values are based on the ISO-4217 three-letter currency identifiers.
+ ///
public const string OriginalCurrency = "ORIGCURRENCY";
+
+ ///
+ /// Ratio of <CURDEF> currency to <CURSYM> currency, in decimal form.
+ ///
public const string CurrencyRate = "CURRATE";
+
+ ///
+ /// ISO-4217 3-letter currency identifier.
+ ///
public const string CurrencySymbol = "CURSYM";
+
+ ///
+ /// Start date of statement requested.
+ ///
public const string StartDate = "DTSTART";
+
+ ///
+ /// End date of statement requested.
+ ///
public const string EndDate = "DTEND";
+
+ ///
+ /// Routing: ABA number or S.W.I.F.T. number.
+ ///
public const string BankId = "BANKID";
+
+ ///
+ /// Branch identifier. May be required for some banks.
+ ///
public const string BranchId = "BRANCHID";
+
+ ///
+ /// Account number.
+ ///
public const string AccountId = "ACCTID";
+
+ ///
+ /// Type of account, version 1.
+ ///
public const string AccountType = "ACCTTYPE";
+
+ ///
+ /// Type of account, version 2.
+ ///
public const string AccountType2 = "ACCTTYPE2";
+
+ ///
+ /// Checksum.
+ ///
public const string AccountKey = "ACCTKEY";
+
+ ///
+ /// Statement transaction.
+ ///
public const string StatementTransaction = "STMTTRN";
+
+ ///
+ /// Transaction type.
+ ///
public const string TransactionType = "TRNTYPE";
+
+ ///
+ /// Date transaction was posted to account.
+ ///
public const string DatePosted = "DTPOSTED";
+
+ ///
+ /// Date user initiated transaction, if known.
+ ///
public const string UserDate = "DTUSER";
+
+ ///
+ /// Date funds are available.
+ ///
public const string DateAvailable = "DTAVAIL";
+
+ ///
+ /// Amount of transaction.
+ ///
public const string TransactionAmount = "TRNAMT";
+
+ ///
+ /// Transaction ID issued by financial institution.
+ ///
public const string FitId = "FITID";
+
+ ///
+ /// Name of payee or description of transaction.
+ ///
public const string Name = "NAME";
+
+ ///
+ /// Extra information (not in <NAME>), version 1.
+ ///
public const string Memo = "MEMO";
+
+ ///
+ /// Extra information (not in <NAME>), version 2.
+ ///
public const string Memo2 = "MEMO2";
+
+ ///
+ /// Check (or other reference) number.
+ ///
public const string ChequeNumber = "CHECKNUM";
+
+ ///
+ /// Reference number that uniquely identifies the transaction.
+ ///
public const string ReferenceNumber = "REFNUM";
+
+ ///
+ /// If present, the FITID of a previously sent transaction that is corrected by this record.
+ /// This transaction replaces or deletes the transaction that it corrects, based on the value of <CORRECTACTION>.
+ ///
public const string CorrectFitId = "CORRECTFITID";
+
+ ///
+ /// Actions can be REPLACE or DELETE. REPLACE replaces the transaction referenced by CORRECTFITID; DELETE deletes it.
+ ///
public const string CorrectAction = "CORRECTACTION";
+
+ ///
+ /// Service provider name.
+ ///
public const string ServiceProviderName = "SPNAME";
+
+ ///
+ /// Server assigned transaction ID; used for transactions initiated by client, such as payment or funds transfer.
+ ///
public const string ServerTxId = "SRVRTID";
+
+ ///
+ /// Server assigned transaction ID; used for transactions initiated by client, such as payment or funds transfer.
+ ///
public const string ServerTxId2 = "SRVRTID2";
+
+ ///
+ /// Standard Industrial Code.
+ ///
public const string StandardIndustrialCode = "SIC";
+
+ ///
+ /// Payee identifier if available, version 1.
+ ///
public const string PayeeId = "PAYEEID";
+
+ ///
+ /// Payee identifier if available, version 2.
+ ///
public const string PayeeId2 = "PAYEEID2";
+
+ ///
+ /// Payee aggregate, version 1.
+ ///
public const string Payee = "PAYEE";
+
+ ///
+ /// Payee aggregate, version 2.
+ ///
public const string Payee2 = "PAYEE2";
+
+ ///
+ /// FI address, line 1.
+ ///
public const string Address1 = "ADDR1";
+
+ ///
+ /// FI address, line 2.
+ ///
public const string Address2 = "ADDR2";
+
+ ///
+ /// FI address, line 3.
+ ///
public const string Address3 = "ADDR3";
+
+ ///
+ /// FI address city.
+ ///
public const string City = "CITY";
+
+ ///
+ /// FI address state.
+ ///
public const string State = "STATE";
+
+ ///
+ /// FI address postal code.
+ ///
public const string PostalCode = "POSTALCODE";
+
+ ///
+ /// FI address country.
+ ///
public const string Country = "COUNTRY";
+
+ ///
+ /// Telephone number for the account.
+ ///
public const string Phone = "PHONE";
+
+ ///
+ /// Ledger balance aggregate.
+ ///
public const string LedgerBalance = "LEDGERBAL";
+
+ ///
+ /// Available balance aggregate.
+ ///
public const string AvailableBalance = "AVAILBAL";
+
+ ///
+ /// Balance amount.
+ ///
public const string BalanceAmount = "BALAMT";
+
+ ///
+ /// Balance date.
+ ///
public const string DateAsOf = "DTASOF";
+ ///
+ /// Used when parsing datetime values to set defaults.
+ ///
public const DateTimeStyles DefaultDateTimeStyles = DateTimeStyles.AssumeUniversal;
+ ///
+ /// Used when parsing datetime values to remove the timezone name.
+ ///
+ public const string TimeZoneRegexPattern = @":(\w+)\]\s*$";
+
+ ///
+ /// Used when parsing datetime values to remove the timezone name.
+ ///
+ public const string TimeZoneReplacement = "]";
+
+ ///
+ /// Supported OFX datetime formats.
+ ///
+ /// The full specification is YYYYMMDDHHMMSS.XXX [gmt offset:tz name].
+ /// Datetime also accepts values with fields omitted from the right.
public static readonly string[] DateTimeFormats =
[
"yyyyMMdd",
@@ -88,7 +400,4 @@ public static class OfxConstants
"yyyyMMddHHmmss.fff",
"yyyyMMddHHmmss.fff[z]"
];
-
- public const string TimeZoneRegexPattern = @":(\w+)\]\s*$";
- public const string TimeZoneReplacement = "]";
}
diff --git a/src/OfxNet/OfxDocument.cs b/src/OfxNet/OfxDocument.cs
index 6039fd1..b46690c 100644
--- a/src/OfxNet/OfxDocument.cs
+++ b/src/OfxNet/OfxDocument.cs
@@ -10,8 +10,6 @@ public class OfxDocument
{
private readonly object document;
- public OfxDocumentSettings Settings { get; }
-
public OfxDocument(object document)
: this(document, OfxDocumentSettings.Default)
{
@@ -20,9 +18,11 @@ public OfxDocument(object document)
public OfxDocument(object document, OfxDocumentSettings settings)
{
this.document = document;
- Settings = settings;
+ this.Settings = settings;
}
+ public OfxDocumentSettings Settings { get; }
+
public static OfxDocument Load(string path)
{
return Load(path, OfxDocumentSettings.Default);
@@ -44,14 +44,15 @@ public static OfxDocument Load(string path, OfxDocumentSettings settings)
return result;
}
+ [SuppressMessage("Design", "CA1024:Use properties where appropriate", Justification = "Breaking change.")]
public IOfxElement? GetRoot()
{
IOfxElement? result = default;
- if (document is SgmlDocument sgmlDocument)
+ if (this.document is SgmlDocument sgmlDocument)
{
result = sgmlDocument.Root;
}
- else if (document is XDocument { Root: not null } xmlDocument)
+ else if (this.document is XDocument { Root: not null } xmlDocument)
{
result = new XElementAdapter(xmlDocument.Root);
}
@@ -61,29 +62,31 @@ public static OfxDocument Load(string path, OfxDocumentSettings settings)
public IEnumerable GetStatements()
{
- IOfxElement? element = GetRoot();
+ IOfxElement? element = this.GetRoot();
- return GetStatements(element);
+ return this.GetStatements(element);
}
public IEnumerable GetStatements(IOfxElement? element)
{
IEnumerable result = (element is null)
? Enumerable.Empty()
- : GetBankStatements(element).Concat(GetCreditCardStatements(element));
+ : this.GetBankStatements(element).Concat(this.GetCreditCardStatements(element));
return result;
}
public IEnumerable GetBankStatements(IOfxElement element)
{
- IOfxElement? set = GetElement(element, OfxConstants.BankMessageSetResponseV1);
- IEnumerable elements = GetElements(set, OfxConstants.StatementTxResponse);
- IEnumerable statements = GetBankStatements(elements);
+ ArgumentNullException.ThrowIfNull(element);
+
+ IOfxElement? set = this.GetElement(element, OfxConstants.BankMessageSetResponseV1);
+ IEnumerable elements = this.GetElements(set, OfxConstants.StatementTxResponse);
+ IEnumerable statements = this.GetBankStatements(elements);
if (statements != null)
{
- foreach (var statement in statements)
+ foreach (OfxBankStatement statement in statements)
{
yield return statement;
}
@@ -92,13 +95,13 @@ public IEnumerable GetBankStatements(IOfxElement element)
public IEnumerable GetCreditCardStatements(IOfxElement element)
{
- IOfxElement? set = GetElement(element, OfxConstants.CreditCardMessageSetResponseV1, OfxConstants.CreditCardMessageSetResponseV2);
- IEnumerable elements = GetElements(set, OfxConstants.CreditCardStatementTxResponse);
- IEnumerable creditCardStatements = GetCreditCardStatements(elements);
+ IOfxElement? set = this.GetElement(element, OfxConstants.CreditCardMessageSetResponseV1, OfxConstants.CreditCardMessageSetResponseV2);
+ IEnumerable elements = this.GetElements(set, OfxConstants.CreditCardStatementTxResponse);
+ IEnumerable creditCardStatements = this.GetCreditCardStatements(elements);
if (creditCardStatements != null)
{
- foreach (var statement in creditCardStatements)
+ foreach (OfxCreditCardStatement statement in creditCardStatements)
{
yield return statement;
}
@@ -107,24 +110,28 @@ public IEnumerable GetCreditCardStatements(IOfxElement element)
public IEnumerable GetBankStatements(IEnumerable elements)
{
+ ArgumentNullException.ThrowIfNull(elements);
+
foreach (IOfxElement element in elements)
{
- IOfxElement? response = GetElement(element, OfxConstants.StatementResponse);
+ IOfxElement? response = this.GetElement(element, OfxConstants.StatementResponse);
if (response != null)
{
- yield return GetBankStatement(response);
+ yield return this.GetBankStatement(response);
}
}
}
public IEnumerable GetCreditCardStatements(IEnumerable elements)
{
+ ArgumentNullException.ThrowIfNull(elements);
+
foreach (IOfxElement element in elements)
{
- IOfxElement? response = GetElement(element, OfxConstants.CreditCardStatementResponse);
+ IOfxElement? response = this.GetElement(element, OfxConstants.CreditCardStatementResponse);
if (response != null)
{
- yield return GetCreditCardStatement(response);
+ yield return this.GetCreditCardStatement(response);
}
}
}
@@ -136,10 +143,10 @@ public IEnumerable GetCreditCardStatements(IEnumerable GetCreditCardStatements(IEnumerable query = from t in GetElements(element, OfxConstants.StatementTransaction)
- select GetStatementTransaction(t);
+ IEnumerable query = from t in this.GetElements(element, OfxConstants.StatementTransaction)
+ select this.GetStatementTransaction(t);
result = new OfxTransactionList
{
- StartDate = GetAsDateTimeOffset(element, OfxConstants.StartDate),
- EndDate = GetAsDateTimeOffset(element, OfxConstants.EndDate),
- Transactions = query.ToList()
+ StartDate = this.GetAsDateTimeOffset(element, OfxConstants.StartDate),
+ EndDate = this.GetAsDateTimeOffset(element, OfxConstants.EndDate),
+ Transactions = query.ToList(),
};
}
@@ -197,28 +206,28 @@ public OfxCreditCardStatement GetCreditCardStatement(IOfxElement element)
? null
: new OfxStatementTransaction
{
- TxType = GetTransactionType(element),
- DatePosted = GetAsDateTimeOffset(element, OfxConstants.DatePosted),
- DateUser = GetAsNullableDateTimeOffset(element, OfxConstants.UserDate),
- DateAvailable = GetAsNullableDateTimeOffset(element, OfxConstants.DateAvailable),
- Amount = GetAsRequiredDecimal(element, OfxConstants.TransactionAmount, "Missing or invalid transaction amount from transaction element"),
- FitId = GetAsString(element, OfxConstants.FitId),
- Name = GetAsString(element, OfxConstants.Name),
- Memo = GetAsString(element, OfxConstants.Memo),
- Memo2 = GetAsString(element, OfxConstants.Memo2),
- ChequeNumber = GetAsString(element, OfxConstants.ChequeNumber),
- ReferenceNumber = GetAsString(element, OfxConstants.ReferenceNumber),
- CorrectFitId = GetAsString(element, OfxConstants.CorrectFitId),
- CorrectAction = GetCorrectiveAction(element),
- ServiceProviderName = GetAsString(element, OfxConstants.ServiceProviderName),
- ServerTxId = GetAsString(element, OfxConstants.ServerTxId2, OfxConstants.ServerTxId),
- StandardIndustrialCode = GetAsNullableInt(element, OfxConstants.StandardIndustrialCode),
- PayeeId = GetAsString(element, OfxConstants.PayeeId2, OfxConstants.PayeeId),
- Payee = GetPayee(GetElement(element, OfxConstants.Payee2, OfxConstants.Payee)),
- Currency = GetCurrency(GetElement(element, OfxConstants.Currency)),
- OriginalCurrency = GetCurrency(GetElement(element, OfxConstants.OriginalCurrency)),
- BankAccountTo = GetBankAccount(GetElement(element, OfxConstants.BankAccountTo)),
- CreditCardAccountTo = GetCreditCardAccount(GetElement(element, OfxConstants.CreditCardAccountTo))
+ TxType = this.GetTransactionType(element),
+ DatePosted = this.GetAsDateTimeOffset(element, OfxConstants.DatePosted),
+ DateUser = this.GetAsNullableDateTimeOffset(element, OfxConstants.UserDate),
+ DateAvailable = this.GetAsNullableDateTimeOffset(element, OfxConstants.DateAvailable),
+ Amount = this.GetAsRequiredDecimal(element, OfxConstants.TransactionAmount, "Missing or invalid transaction amount from transaction element"),
+ FitId = this.GetAsString(element, OfxConstants.FitId),
+ Name = this.GetAsString(element, OfxConstants.Name),
+ Memo = this.GetAsString(element, OfxConstants.Memo),
+ Memo2 = this.GetAsString(element, OfxConstants.Memo2),
+ ChequeNumber = this.GetAsString(element, OfxConstants.ChequeNumber),
+ ReferenceNumber = this.GetAsString(element, OfxConstants.ReferenceNumber),
+ CorrectFitId = this.GetAsString(element, OfxConstants.CorrectFitId),
+ CorrectAction = this.GetCorrectiveAction(element),
+ ServiceProviderName = this.GetAsString(element, OfxConstants.ServiceProviderName),
+ ServerTxId = this.GetAsString(element, OfxConstants.ServerTxId2, OfxConstants.ServerTxId),
+ StandardIndustrialCode = this.GetAsNullableInt(element, OfxConstants.StandardIndustrialCode),
+ PayeeId = this.GetAsString(element, OfxConstants.PayeeId2, OfxConstants.PayeeId),
+ Payee = this.GetPayee(this.GetElement(element, OfxConstants.Payee2, OfxConstants.Payee)),
+ Currency = this.GetCurrency(this.GetElement(element, OfxConstants.Currency)),
+ OriginalCurrency = this.GetCurrency(this.GetElement(element, OfxConstants.OriginalCurrency)),
+ BankAccountTo = this.GetBankAccount(this.GetElement(element, OfxConstants.BankAccountTo)),
+ CreditCardAccountTo = this.GetCreditCardAccount(this.GetElement(element, OfxConstants.CreditCardAccountTo)),
};
}
@@ -228,9 +237,8 @@ public OfxCreditCardStatement GetCreditCardStatement(IOfxElement element)
return (element is null)
? null
: new OfxCurrency(
- GetAsRequiredDecimal(element, OfxConstants.CurrencyRate, "Missing required currency rate in currency element."),
- GetAsRequiredString(element, OfxConstants.CurrencySymbol, "Missing required currency symbol in currency element.")
- );
+ this.GetAsRequiredDecimal(element, OfxConstants.CurrencyRate, "Missing required currency rate in currency element."),
+ this.GetAsRequiredString(element, OfxConstants.CurrencySymbol, "Missing required currency symbol in currency element."));
}
[return: NotNullIfNotNull(nameof(element))]
@@ -240,15 +248,15 @@ public OfxCreditCardStatement GetCreditCardStatement(IOfxElement element)
? null
: new OfxPayee
{
- Name = GetAsString(element, OfxConstants.Name),
- AddressLine1 = GetAsString(element, OfxConstants.Address1),
- AddressLine2 = GetAsString(element, OfxConstants.Address2),
- AddressLine3 = GetAsString(element, OfxConstants.Address3),
- City = GetAsString(element, OfxConstants.City),
- State = GetAsString(element, OfxConstants.State),
- PostalCode = GetAsString(element, OfxConstants.PostalCode),
- Country = GetAsString(element, OfxConstants.Country),
- PhoneNumber = GetAsString(element, OfxConstants.Phone),
+ Name = this.GetAsString(element, OfxConstants.Name),
+ AddressLine1 = this.GetAsString(element, OfxConstants.Address1),
+ AddressLine2 = this.GetAsString(element, OfxConstants.Address2),
+ AddressLine3 = this.GetAsString(element, OfxConstants.Address3),
+ City = this.GetAsString(element, OfxConstants.City),
+ State = this.GetAsString(element, OfxConstants.State),
+ PostalCode = this.GetAsString(element, OfxConstants.PostalCode),
+ Country = this.GetAsString(element, OfxConstants.Country),
+ PhoneNumber = this.GetAsString(element, OfxConstants.Phone),
};
}
@@ -259,8 +267,8 @@ public OfxCreditCardStatement GetCreditCardStatement(IOfxElement element)
? null
: new OfxAccountBalance
{
- Balance = GetAsRequiredDecimal(element, OfxConstants.BalanceAmount, "Missing or invalid balance from balance element."),
- DateAsOf = GetAsDateTimeOffset(element, OfxConstants.DateAsOf)
+ Balance = this.GetAsRequiredDecimal(element, OfxConstants.BalanceAmount, "Missing or invalid balance from balance element."),
+ DateAsOf = this.GetAsDateTimeOffset(element, OfxConstants.DateAsOf),
};
}
@@ -270,8 +278,8 @@ public OfxCreditCardStatement GetCreditCardStatement(IOfxElement element)
? null
: new OfxStatus
{
- Code = GetAsRequiredInteger(element, OfxConstants.Code, "Missing required Code from status element."),
- Severity = GetSeverity(element)
+ Code = this.GetAsRequiredInteger(element, OfxConstants.Code, "Missing required Code from status element."),
+ Severity = this.GetSeverity(element),
};
}
@@ -282,11 +290,11 @@ public OfxCreditCardStatement GetCreditCardStatement(IOfxElement element)
? null
: new OfxBankAccount
{
- BankId = GetAsString(element, OfxConstants.BankId),
- BranchId = GetAsString(element, OfxConstants.BranchId),
- AccountNumber = GetAsString(element, OfxConstants.AccountId),
- AccountType = GetAccountType(element),
- Checksum = GetAsString(element, OfxConstants.AccountKey)
+ BankId = this.GetAsString(element, OfxConstants.BankId),
+ BranchId = this.GetAsString(element, OfxConstants.BranchId),
+ AccountNumber = this.GetAsString(element, OfxConstants.AccountId),
+ AccountType = this.GetAccountType(element),
+ Checksum = this.GetAsString(element, OfxConstants.AccountKey),
};
}
@@ -297,111 +305,121 @@ public OfxCreditCardStatement GetCreditCardStatement(IOfxElement element)
? null
: new OfxCreditCardAccount
{
- AccountNumber = GetAsString(element, OfxConstants.AccountId),
- Checksum = GetAsString(element, OfxConstants.AccountKey)
+ AccountNumber = this.GetAsString(element, OfxConstants.AccountId),
+ Checksum = this.GetAsString(element, OfxConstants.AccountKey),
};
}
+ public IOfxElement? GetElement(IOfxElement parent, string first, string second)
+ {
+ ArgumentNullException.ThrowIfNull(parent);
+
+ return this.GetElement(parent, first) ?? this.GetElement(parent, second);
+ }
+
+ public string? GetAsString(IOfxElement element, string first, string second)
+ {
+ var result = this.GetAsString(element, first);
+ if (string.IsNullOrWhiteSpace(result))
+ {
+ result = this.GetAsString(element, second);
+ }
+
+ return result;
+ }
+
private int GetAsRequiredInteger(IOfxElement parent, string name, string errorString)
{
- string? value = GetAsString(parent, name);
+ string? s = this.GetAsString(parent, name);
- (bool NullOrWhiteSpace, bool NotInteger, int Value) = OfxParser.ParseInteger(value);
- if (NullOrWhiteSpace || NotInteger)
+ (bool nullOrWhiteSpace, bool notInteger, int value) = OfxParser.ParseInteger(s);
+ if (nullOrWhiteSpace || notInteger)
{
throw new OfxException(errorString);
}
- return Value;
+ return value;
}
private int? GetAsNullableInt(IOfxElement parent, string name)
{
- string? value = GetAsString(parent, name);
+ string? value = this.GetAsString(parent, name);
return int.TryParse(value, out var result) ? result : default(int?);
}
private decimal GetAsRequiredDecimal(IOfxElement parent, string name, string errorString)
{
- string? value = GetAsString(parent, name);
+ string? s = this.GetAsString(parent, name);
- (bool NullOrWhiteSpace, bool NotDecimal, decimal Value) = OfxParser.ParseDecimal(value);
- if (NullOrWhiteSpace || NotDecimal)
+ (bool nullOrWhiteSpace, bool notDecimal, decimal value) = OfxParser.ParseDecimal(s);
+ if (nullOrWhiteSpace || notDecimal)
{
throw new OfxException(errorString);
}
- return Value;
+ return value;
}
private DateTimeOffset GetAsDateTimeOffset(IOfxElement parent, string name)
{
- string? value = GetAsString(parent, name);
+ string? value = this.GetAsString(parent, name);
return OfxParser.ParseDateTime(value);
}
private DateTimeOffset? GetAsNullableDateTimeOffset(IOfxElement parent, string name)
{
- string? value = GetAsString(parent, name);
+ string? value = this.GetAsString(parent, name);
return OfxParser.ParseNullableDateTime(value);
}
private OfxAccountType GetAccountType(IOfxElement parent)
{
return OfxParser.ParseAccountType(
- GetAsString(parent, OfxConstants.AccountType2, OfxConstants.AccountType));
+ this.GetAsString(parent, OfxConstants.AccountType2, OfxConstants.AccountType));
}
private OfxSeverity GetSeverity(IOfxElement parent)
{
return OfxParser.ParseSeverity(
- GetAsString(parent, OfxConstants.Severity));
+ this.GetAsString(parent, OfxConstants.Severity));
}
private OfxTransactionType GetTransactionType(IOfxElement parent)
{
return OfxParser.ParseTransactionType(
- GetAsString(parent, OfxConstants.TransactionType));
+ this.GetAsString(parent, OfxConstants.TransactionType));
}
private OfxCorrectiveAction GetCorrectiveAction(IOfxElement parent)
{
return OfxParser.ParseCorrectiveAction(
- GetAsString(parent, OfxConstants.CorrectAction));
- }
-
- public string? GetAsString(IOfxElement element, string first, string second)
- {
- var result = GetAsString(element, first);
- if (string.IsNullOrWhiteSpace(result))
- {
- result = GetAsString(element, second);
- }
-
- return result;
+ this.GetAsString(parent, OfxConstants.CorrectAction));
}
private string? GetAsString(IOfxElement parent, string name)
{
- var result = GetElement(parent, name)?.Value;
- if (Settings.TrimValues && string.IsNullOrEmpty(result) == false)
+ string? result = this.GetElement(parent, name)?.Value;
+ if (this.Settings.TrimValues && string.IsNullOrEmpty(result) == false)
{
result = result.Trim();
}
+
return result;
}
private string GetAsRequiredString(IOfxElement parent, string name, string errorString)
{
- var result = GetElement(parent, name)?.Value;
+ string? result = this.GetElement(parent, name)?.Value;
if (string.IsNullOrWhiteSpace(result))
{
throw new OfxException(errorString);
}
- if (Settings.TrimValues && string.IsNullOrEmpty(result) == false)
+
+ if (this.Settings.TrimValues && string.IsNullOrEmpty(result) == false)
{
result = result.Trim();
}
+
return result;
}
@@ -409,8 +427,8 @@ private IEnumerable GetElements(IOfxElement? parent, string name)
{
if (parent != null)
{
- var items = parent.Elements(name, Settings.TagComparer);
- foreach (var item in items)
+ IEnumerable items = parent.Elements(name, this.Settings.TagComparer);
+ foreach (IOfxElement item in items)
{
yield return item;
}
@@ -419,11 +437,6 @@ private IEnumerable GetElements(IOfxElement? parent, string name)
private IOfxElement? GetElement(IOfxElement parent, string name)
{
- return parent.Element(name, Settings.TagComparer);
- }
-
- public IOfxElement? GetElement(IOfxElement parent, string first, string second)
- {
- return GetElement(parent, first) ?? GetElement(parent, second);
+ return parent.Element(name, this.Settings.TagComparer);
}
}
diff --git a/src/OfxNet/OfxDocumentSettings.cs b/src/OfxNet/OfxDocumentSettings.cs
index 3c62a6f..9b52bba 100644
--- a/src/OfxNet/OfxDocumentSettings.cs
+++ b/src/OfxNet/OfxDocumentSettings.cs
@@ -6,12 +6,13 @@
[SuppressMessage("Performance", "CA1815:Override equals and operator equals on value types", Justification = "Not currently required.")]
public struct OfxDocumentSettings
{
- public readonly static OfxDocumentSettings Default = new()
+ public static readonly OfxDocumentSettings Default = new()
{
TrimValues = true,
- TagComparer = StringComparer.CurrentCultureIgnoreCase
+ TagComparer = StringComparer.CurrentCultureIgnoreCase,
};
public bool TrimValues { get; set; }
+
public StringComparer TagComparer { get; set; }
}
diff --git a/src/OfxNet/OfxException.cs b/src/OfxNet/OfxException.cs
index 19e3f8d..16ab82d 100644
--- a/src/OfxNet/OfxException.cs
+++ b/src/OfxNet/OfxException.cs
@@ -3,18 +3,37 @@
using System;
using System.Runtime.Serialization;
+///
+/// Represents errors that occur during parsing OFX documents.
+///
[Serializable]
-internal class OfxException : Exception
+public sealed class OfxException : Exception
{
+ ///
+ /// Initializes a new instance of the class.
+ ///
public OfxException()
+ : base()
{
}
- public OfxException(string? message) : base(message)
+ ///
+ /// Initializes a new instance of the class with a specified error message.
+ ///
+ /// The message that describes the error.
+ public OfxException(string? message)
+ : base(message)
{
}
- public OfxException(string? message, Exception? innerException) : base(message, innerException)
+ ///
+ /// Initializes a new instance of the class with a specified error message
+ /// and a reference to the inner exception that is the cause of the exception.
+ ///
+ /// The message that describes the error.
+ /// The exception that is the cause of the current exception, or a null reference if no inner exception is specified.
+ public OfxException(string? message, Exception? innerException)
+ : base(message, innerException)
{
}
}
diff --git a/src/OfxNet/OfxNet.csproj b/src/OfxNet/OfxNet.csproj
index c80387c..46f4d2f 100644
--- a/src/OfxNet/OfxNet.csproj
+++ b/src/OfxNet/OfxNet.csproj
@@ -7,13 +7,9 @@
OFX
MIT
README.md
- 1.4.0.0
+ 1.4.1.0
-
-
-
-
diff --git a/src/OfxNet/OfxParser.cs b/src/OfxNet/OfxParser.cs
index 634ae34..e7af0cb 100644
--- a/src/OfxNet/OfxParser.cs
+++ b/src/OfxNet/OfxParser.cs
@@ -14,7 +14,7 @@ public static class OfxParser
/// Parses an OFX version string. The expected format is 3 digits - [Major][Minor][Revision] e.g. "100".
///
/// The string containing the version number to convert.
- ///
+ /// The .
public static OfxVersion ParseVersion(string s)
{
OfxVersion result = TryParseVersion(s);
@@ -30,7 +30,7 @@ public static OfxVersion ParseVersion(string s)
/// Try to parse an OFX version string. The expected format is 3 digits - [Major][Minor][Revision] e.g. "100".
///
/// The string containing the version number to convert.
- ///
+ /// The if successfully parsed, otherwise .
public static OfxVersion TryParseVersion(string s)
{
OfxVersion result = OfxVersion.InvalidHeader;
@@ -51,14 +51,15 @@ public static OfxVersion TryParseVersion(string s)
///
/// Parses an OFX integer value.
///
- /// The string containing the number to convert.
- ///
+ /// The string containing the number to parse.
+ /// The result of parsing the specified string.
public static (bool NullOrWhiteSpace, bool NotInteger, int Value) ParseInteger(string? s)
{
if (string.IsNullOrWhiteSpace(s))
{
return (true, false, default);
}
+
if (int.TryParse(s, out int temp))
{
return (false, false, temp);
@@ -69,13 +70,19 @@ public static (bool NullOrWhiteSpace, bool NotInteger, int Value) ParseInteger(s
}
}
- public static (bool NullOrWhiteSpace, bool NotDecimal, decimal Value) ParseDecimal(string? value)
+ ///
+ /// Parses an OFX decimal value.
+ ///
+ /// The string containing the decimal to parse.
+ /// The result of parsing the specified string.
+ public static (bool NullOrWhiteSpace, bool NotDecimal, decimal Value) ParseDecimal(string? s)
{
- if (string.IsNullOrWhiteSpace(value))
+ if (string.IsNullOrWhiteSpace(s))
{
return (true, false, default);
}
- if (decimal.TryParse(value, out decimal temp))
+
+ if (decimal.TryParse(s, out decimal temp))
{
return (false, false, temp);
}
@@ -85,25 +92,35 @@ public static (bool NullOrWhiteSpace, bool NotDecimal, decimal Value) ParseDecim
}
}
- public static DateTimeOffset? ParseNullableDateTime(string? value)
+ ///
+ /// Parses an optional OFX datetime value.
+ ///
+ /// The string containing the datetime to parse.
+ /// The result of parsing the specified string.
+ public static DateTimeOffset? ParseNullableDateTime(string? s)
{
DateTimeOffset? result = default;
- if (string.IsNullOrWhiteSpace(value) == false)
+ if (string.IsNullOrWhiteSpace(s) == false)
{
- result = ParseDateTime(value);
+ result = ParseDateTime(s);
}
return result;
}
- public static DateTimeOffset ParseDateTime(string? value)
+ ///
+ /// Parses an OFX datetime value.
+ ///
+ /// The string containing the datetime to parse.
+ /// The result of parsing the specified string.
+ public static DateTimeOffset ParseDateTime(string? s)
{
DateTimeOffset result = default;
- if (string.IsNullOrWhiteSpace(value) == false)
+ if (string.IsNullOrWhiteSpace(s) == false)
{
- if (TryParseDateTimeOffset(value, OfxConstants.DefaultDateTimeStyles, out result) == false)
+ if (TryParseDateTimeOffset(s, OfxConstants.DefaultDateTimeStyles, out result) == false)
{
throw new FormatException("String was not recognized as a valid DateTimeOffset.");
}
@@ -112,24 +129,44 @@ public static DateTimeOffset ParseDateTime(string? value)
return result;
}
- public static OfxAccountType ParseAccountType(string? value)
+ ///
+ /// Parses an OFX account type string.
+ ///
+ /// The string containing the account type to parse.
+ /// The result of parsing the specified string.
+ public static OfxAccountType ParseAccountType(string? s)
{
- return ParseEnumString(value);
+ return ParseEnumString(s);
}
- public static OfxSeverity ParseSeverity(string? value)
+ ///
+ /// Parses an OFX error severity string.
+ ///
+ /// The string containing the error severity to parse.
+ /// The result of parsing the specified string.
+ public static OfxSeverity ParseSeverity(string? s)
{
- return ParseEnumString(value);
+ return ParseEnumString(s);
}
- public static OfxTransactionType ParseTransactionType(string? value)
+ ///
+ /// Parses an OFX transaction type string.
+ ///
+ /// The string containing the transation type to parse.
+ /// The result of parsing the specified string.
+ public static OfxTransactionType ParseTransactionType(string? s)
{
- return ParseEnumString(value);
+ return ParseEnumString(s);
}
- public static OfxCorrectiveAction ParseCorrectiveAction(string? value)
+ ///
+ /// Parses an OFX corrective action string.
+ ///
+ /// The string containing the corrective action to parse.
+ /// The result of parsing the specified string.
+ public static OfxCorrectiveAction ParseCorrectiveAction(string? s)
{
- return ParseEnumString(value);
+ return ParseEnumString(s);
}
private static bool TryGetDigitValue(char ch, out int result)
@@ -145,21 +182,23 @@ private static bool TryParseDateTimeOffset(string str, DateTimeStyles style, out
// Remove possible time zone name from string to be parsed
var noTimeZoneName = Regex.Replace(str, OfxConstants.TimeZoneRegexPattern, OfxConstants.TimeZoneReplacement);
- return DateTimeOffset.TryParseExact(noTimeZoneName,
+ return DateTimeOffset.TryParseExact(
+ noTimeZoneName,
OfxConstants.DateTimeFormats,
CultureInfo.InvariantCulture,
style,
out result);
}
- private static EnumType ParseEnumString(string? value) where EnumType : Enum
+ private static TEnum ParseEnumString(string? value)
+ where TEnum : Enum
{
- EnumType result = default!;
+ TEnum result = default!;
if (string.IsNullOrWhiteSpace(value) == false
- && Enum.IsDefined(typeof(EnumType), value))
+ && Enum.IsDefined(typeof(TEnum), value))
{
- result = (EnumType)Enum.Parse(typeof(EnumType), value, true);
+ result = (TEnum)Enum.Parse(typeof(TEnum), value, true);
}
return result;
diff --git a/src/OfxNet/Sgml/SgmlConstants.cs b/src/OfxNet/Sgml/SgmlConstants.cs
index 7171d71..921c030 100644
--- a/src/OfxNet/Sgml/SgmlConstants.cs
+++ b/src/OfxNet/Sgml/SgmlConstants.cs
@@ -1,33 +1,98 @@
namespace OfxNet;
+using System.Reflection.PortableExecutable;
using System.Text.RegularExpressions;
+using Microsoft.VisualBasic;
+///
+/// OFX SGML constants used during parsing.
+///
public static class SgmlConstants
{
+ ///
+ /// OFXHEADER specifies the version number of the Open Financial Exchange headers.
+ ///
public const string Header = "OFXHEADER";
+
+ ///
+ /// Specifies the content type, in this case OFXSGML.
+ ///
public const string DataHeader = "DATA";
+
+ ///
+ /// Specifies the version number of the Document Type Definition (DTD) used for parsing.
+ ///
public const string VersionHeader = "VERSION";
+
+ ///
+ /// Defines the type of application-level security, if any, that is used for the <OFX> block. The values for SECURITY can be NONE or TYPE1.
+ ///
public const string SecurityHeader = "SECURITY";
+
+ ///
+ /// Defines the text encoding used for character data. The values for ENCODING can be USASCII or UTF-8.
+ ///
public const string EncodingHeader = "ENCODING";
+
+ ///
+ /// Gets or sets the character set used for character data. The values for CHARSET may be ISO-8859-1 (Latin-1), 1252 (Windows Latin-1), or NONE.
+ /// Any value specified here is likely to be ignored by an OFX client or server.
+ ///
public const string CharsetHeader = "CHARSET";
+
+ ///
+ /// Gets or sets the compression.
+ ///
+ /// Not supported.
public const string CompressionHeader = "COMPRESSION";
+
+ ///
+ /// Gets or sets the unique identifier the last request and response that was received and processed by the client.
+ ///
public const string OldFileUIDHeader = "OLDFILEUID";
+
+ ///
+ /// Gets or sets the unique identifier for this request file.
+ ///
public const string NewFileUIDHeader = "NEWFILEUID";
- public const string HeaderRegexPrefix = "^";
- public const string HeaderRegexSeparator = @"\s*:\s*";
- public const string HeaderVersionRegexPattern = HeaderRegexPrefix + Header + HeaderRegexSeparator + @"(\d{3})" + @"\s*$";
- public const string HeaderRegexPattern = HeaderRegexPrefix + @"(\w+)" + HeaderRegexSeparator + @"(.+)" + @"$";
-
- public const string OpeningTagRegexPattern = @"^\s*<([\w\.]+)>\s*$";
- public const string ClosingTagRegexPattern = @"^\s*([\w\.]+)>\s*$";
- public const string ValueFullTagRegexPattern = @"^\s*<([\w\.]+)>(.+)([\w\.]+)>\s*$";
- public const string ValuePartialTagRegexPattern = @"^\s*<([\w\.]+)>(.+)$";
-
- public static readonly Regex HeaderVersionRegex = new Regex(HeaderVersionRegexPattern, RegexOptions.IgnoreCase);
- public static readonly Regex HeaderRegex = new Regex(HeaderRegexPattern, RegexOptions.IgnoreCase);
- public static readonly Regex OpeningTagRegex = new Regex(OpeningTagRegexPattern, RegexOptions.IgnoreCase);
- public static readonly Regex ClosingTagRegex = new Regex(ClosingTagRegexPattern, RegexOptions.IgnoreCase);
- public static readonly Regex ValueFullTagRegex = new Regex(ValueFullTagRegexPattern, RegexOptions.IgnoreCase);
- public static readonly Regex ValuePartialTagRegex = new Regex(ValuePartialTagRegexPattern, RegexOptions.IgnoreCase);
+ ///
+ /// Regex pattern to parse OFX version header.
+ ///
+ public static readonly Regex HeaderVersionRegex = new(HeaderVersionRegexPattern, RegexOptions.IgnoreCase);
+
+ ///
+ /// Regex pattern for parsing OFX headers in the form <name>:<value>.
+ ///
+ public static readonly Regex HeaderRegex = new(HeaderRegexPattern, RegexOptions.IgnoreCase);
+
+ ///
+ /// Regex pattern to parse an opening tag.
+ ///
+ public static readonly Regex OpeningTagRegex = new(OpeningTagRegexPattern, RegexOptions.IgnoreCase);
+
+ ///
+ /// Regex pattern to parse a clsing tag.
+ ///
+ public static readonly Regex ClosingTagRegex = new(ClosingTagRegexPattern, RegexOptions.IgnoreCase);
+
+ ///
+ /// Regex pattern to parse a fully enclosed tag with a value e.g. <CODE>0</CODE>.
+ ///
+ public static readonly Regex ValueFullTagRegex = new(ValueFullTagRegexPattern, RegexOptions.IgnoreCase);
+
+ ///
+ /// Regex pattern to parse a partial tag with a value e.g. <CODE>0.
+ ///
+ public static readonly Regex ValuePartialTagRegex = new(ValuePartialTagRegexPattern, RegexOptions.IgnoreCase);
+
+ private const string HeaderRegexPrefix = "^";
+ private const string HeaderRegexSeparator = @"\s*:\s*";
+ private const string HeaderVersionRegexPattern = HeaderRegexPrefix + Header + HeaderRegexSeparator + @"(\d{3})" + @"\s*$";
+ private const string HeaderRegexPattern = HeaderRegexPrefix + @"(\w+)" + HeaderRegexSeparator + @"(.+)" + @"$";
+
+ private const string OpeningTagRegexPattern = @"^\s*<([\w\.]+)>\s*$";
+ private const string ClosingTagRegexPattern = @"^\s*([\w\.]+)>\s*$";
+ private const string ValueFullTagRegexPattern = @"^\s*<([\w\.]+)>(.+)([\w\.]+)>\s*$";
+ private const string ValuePartialTagRegexPattern = @"^\s*<([\w\.]+)>(.+)$";
}
diff --git a/src/OfxNet/Sgml/SgmlDocument.cs b/src/OfxNet/Sgml/SgmlDocument.cs
index 70e89bc..688ebf0 100644
--- a/src/OfxNet/Sgml/SgmlDocument.cs
+++ b/src/OfxNet/Sgml/SgmlDocument.cs
@@ -7,11 +7,12 @@ public class SgmlDocument
{
private SgmlDocument(SgmlHeader header, SgmlElement root)
{
- Header = header;
- Root = root;
+ this.Header = header;
+ this.Root = root;
}
public SgmlHeader Header { get; set; }
+
public SgmlElement Root { get; set; }
public static bool TryLoad(string path, [NotNullWhen(true)] out SgmlDocument? result)
@@ -30,6 +31,6 @@ public static bool TryLoad(string path, [NotNullWhen(true)] out SgmlDocument? re
}
}
- return (result != null);
+ return result != null;
}
}
diff --git a/src/OfxNet/Sgml/SgmlElement.cs b/src/OfxNet/Sgml/SgmlElement.cs
index 77bfbff..d504b57 100644
--- a/src/OfxNet/Sgml/SgmlElement.cs
+++ b/src/OfxNet/Sgml/SgmlElement.cs
@@ -6,13 +6,7 @@
public class SgmlElement : IOfxElement
{
- public static readonly SgmlElement Empty = new SgmlElement(string.Empty, string.Empty);
-
- public string Name { get; }
- public string? Value { get; }
- public string Text { get; }
- public SgmlElement? Parent { get; }
- public IList? Children { get; private set; }
+ public static readonly SgmlElement Empty = new(string.Empty, string.Empty);
public SgmlElement(string name, string text)
: this(name, null, text, null)
@@ -26,30 +20,42 @@ public SgmlElement(string name, string text, SgmlElement parent)
public SgmlElement(string name, string? value, string text, SgmlElement? parent)
{
- Name = name;
- Value = value;
- Text = text;
- Parent = parent;
+ this.Name = name;
+ this.Value = value;
+ this.Text = text;
+ this.Parent = parent;
}
+ public string Name { get; }
+
+ public string? Value { get; }
+
+ public string Text { get; }
+
+ public SgmlElement? Parent { get; }
+
+ public IList? Children { get; private set; }
+
public SgmlElement AddChild(SgmlElement item)
{
- Children ??= new List();
- Children.Add(item);
+ this.Children ??= new List();
+ this.Children.Add(item);
return item;
}
public IOfxElement? Element(string name, StringComparer comparer)
{
- return Children?.SingleOrDefault(e => comparer.Equals(name, e.Name));
+ return this.Children?.SingleOrDefault(e => comparer.Equals(name, e.Name));
}
public IEnumerable Elements(string name, StringComparer comparer)
{
- if (Children != null)
+ ArgumentNullException.ThrowIfNull(comparer);
+
+ if (this.Children != null)
{
- foreach (SgmlElement child in Children)
+ foreach (SgmlElement child in this.Children)
{
if (comparer.Equals(name, child.Name))
{
diff --git a/src/OfxNet/Sgml/SgmlHeader.cs b/src/OfxNet/Sgml/SgmlHeader.cs
index 70395d0..1103b99 100644
--- a/src/OfxNet/Sgml/SgmlHeader.cs
+++ b/src/OfxNet/Sgml/SgmlHeader.cs
@@ -1,14 +1,54 @@
namespace OfxNet;
+///
+/// The OFX SGML header.
+///
public class SgmlHeader
{
+ ///
+ /// Gets or sets the OFX header version.
+ ///
public OfxVersion HeaderVersion { get; set; }
+
+ ///
+ /// Gets or sets the content type, e.g. OFXSGML.
+ ///
public string? Data { get; set; }
+
+ ///
+ /// Gets or sets the version number of the Document Type Definition (DTD) used for parsing.
+ ///
public OfxVersion Version { get; set; }
+
+ ///
+ /// Gets or sets the type of application-level security, if any, that is used for the <OFX> block.
+ ///
public string? Security { get; set; }
+
+ ///
+ /// Gets or sets the text encoding used for character data. The values for ENCODING can be USASCII or UTF-8.
+ ///
public string? Encoding { get; set; }
+
+ ///
+ /// Gets or sets the character set used for character data. The values for CHARSET may be ISO-8859-1 (Latin-1), 1252 (Windows Latin-1), or NONE.
+ /// Any value specified here is likely to be ignored by an OFX client or server.
+ ///
public string? Charset { get; set; }
+
+ ///
+ /// Gets or sets the compression.
+ ///
+ /// Not supported.
public string? Compression { get; set; }
- public string? OldFileUid { get; set; }
+
+ ///
+ /// Gets or sets the unique identifier for this request file.
+ ///
public string? NewFileUid { get; set; }
+
+ ///
+ /// Gets or sets the unique identifier the last request and response that was received and processed by the client.
+ ///
+ public string? OldFileUid { get; set; }
}
diff --git a/src/OfxNet/Sgml/SgmlHeaderExtensions.cs b/src/OfxNet/Sgml/SgmlHeaderExtensions.cs
index bfe7de4..bf798ff 100644
--- a/src/OfxNet/Sgml/SgmlHeaderExtensions.cs
+++ b/src/OfxNet/Sgml/SgmlHeaderExtensions.cs
@@ -3,11 +3,21 @@
using System;
using System.Text;
+///
+/// extension methods.
+///
public static partial class SgmlHeaderExtensions
{
+ ///
+ /// Gets the character encoding for the remaining data in the document from the SGML header.
+ ///
+ /// The SGML header object.
+ /// The character enconding.
public static Encoding GetEncoding(this SgmlHeader item)
{
- var result = Encoding.Default;
+ ArgumentNullException.ThrowIfNull(item);
+
+ Encoding result = Encoding.Default;
if (string.Equals("USASCII", item.Encoding, StringComparison.OrdinalIgnoreCase))
{
@@ -29,11 +39,9 @@ public static Encoding GetEncoding(this SgmlHeader item)
{
result = Encoding.GetEncoding(item.Charset);
}
-#pragma warning disable CA1031 // Justification - this is the exact exception thrown
catch (ArgumentException)
{
}
-#pragma warning restore CA1031
}
}
else if (string.Equals("UTF-8", item.Encoding, StringComparison.OrdinalIgnoreCase))
diff --git a/src/OfxNet/Sgml/SgmlHeaderParser.cs b/src/OfxNet/Sgml/SgmlHeaderParser.cs
index 039ba07..25b3293 100644
--- a/src/OfxNet/Sgml/SgmlHeaderParser.cs
+++ b/src/OfxNet/Sgml/SgmlHeaderParser.cs
@@ -5,35 +5,78 @@
using System.Text;
using System.Text.RegularExpressions;
+///
+/// Implements methods to parse an OFX SGML header.
+///
public class SgmlHeaderParser
{
- private int _lineNumber;
+ private int lineNumber;
public SgmlHeader? TryGetHeader(string path)
{
using StreamReader stream = new (path, Encoding.ASCII);
- OfxVersion headerVersion = TryGetOfxHeaderVersion(stream);
+ OfxVersion headerVersion = this.TryGetOfxHeaderVersion(stream);
- return (headerVersion == OfxVersion.HeaderV1) ? GetHeader(stream, headerVersion) : default;
+ return (headerVersion == OfxVersion.HeaderV1) ? this.GetHeader(stream, headerVersion) : default;
}
public int SkipToContent(TextReader reader)
{
- SkipNoneContentLines(reader);
+ ArgumentNullException.ThrowIfNull(reader);
- ReadHeaders(reader, (line) => false);
+ this.SkipNoneContentLines(reader);
- return _lineNumber;
+ this.ReadHeaders(reader, (line) => false);
+
+ return this.lineNumber;
+ }
+
+ private static bool TrySetHeaderValue(SgmlHeader item, string name, string value)
+ {
+ bool result = true;
+
+ switch (name)
+ {
+ case SgmlConstants.DataHeader:
+ item.Data = value;
+ break;
+ case SgmlConstants.VersionHeader:
+ item.Version = OfxParser.ParseVersion(value);
+ break;
+ case SgmlConstants.SecurityHeader:
+ item.Security = value;
+ break;
+ case SgmlConstants.EncodingHeader:
+ item.Encoding = value;
+ break;
+ case SgmlConstants.CharsetHeader:
+ item.Charset = value;
+ break;
+ case SgmlConstants.CompressionHeader:
+ item.Compression = value;
+ break;
+ case SgmlConstants.OldFileUIDHeader:
+ item.OldFileUid = value;
+ break;
+ case SgmlConstants.NewFileUIDHeader:
+ item.NewFileUid = value;
+ break;
+ default:
+ result = false;
+ break;
+ }
+
+ return result;
}
private OfxVersion TryGetOfxHeaderVersion(TextReader reader)
{
OfxVersion result = OfxVersion.InvalidHeader;
- SkipNoneContentLines(reader);
+ this.SkipNoneContentLines(reader);
- ReadHeaders(reader, (line) =>
+ this.ReadHeaders(reader, (line) =>
{
// First header must be the OFX version header e.g. 'OFXHEADER:100'
Match match = SgmlConstants.HeaderVersionRegex.Match(line);
@@ -50,12 +93,12 @@ private OfxVersion TryGetOfxHeaderVersion(TextReader reader)
private SgmlHeader GetHeader(TextReader reader, OfxVersion headerVersion)
{
- var result = new SgmlHeader
+ var result = new SgmlHeader()
{
- HeaderVersion = headerVersion
+ HeaderVersion = headerVersion,
};
- ReadHeaders(reader, (line) =>
+ this.ReadHeaders(reader, (line) =>
{
Match match = SgmlConstants.HeaderRegex.Match(line);
if (match.Success)
@@ -67,7 +110,7 @@ private SgmlHeader GetHeader(TextReader reader, OfxVersion headerVersion)
}
else
{
- throw new SgmlParseException("Invalid format while parsing OFX headers, line number " + _lineNumber + ".");
+ throw new SgmlParseException("Invalid format while parsing OFX headers, line number " + this.lineNumber + ".");
}
return false;
@@ -89,7 +132,7 @@ private void SkipNoneContentLines(TextReader reader)
}
_ = reader.ReadLine();
- ++_lineNumber;
+ ++this.lineNumber;
}
}
@@ -112,7 +155,7 @@ private void ReadHeaders(TextReader reader, Func processLine)
break;
}
- ++_lineNumber;
+ ++this.lineNumber;
if (processLine.Invoke(line))
{
@@ -120,42 +163,4 @@ private void ReadHeaders(TextReader reader, Func processLine)
}
}
}
-
- private bool TrySetHeaderValue(SgmlHeader item, string name, string value)
- {
- bool result = true;
-
- switch (name)
- {
- case SgmlConstants.DataHeader:
- item.Data = value;
- break;
- case SgmlConstants.VersionHeader:
- item.Version = OfxParser.ParseVersion(value);
- break;
- case SgmlConstants.SecurityHeader:
- item.Security = value;
- break;
- case SgmlConstants.EncodingHeader:
- item.Encoding = value;
- break;
- case SgmlConstants.CharsetHeader:
- item.Charset = value;
- break;
- case SgmlConstants.CompressionHeader:
- item.Compression = value;
- break;
- case SgmlConstants.OldFileUIDHeader:
- item.OldFileUid = value;
- break;
- case SgmlConstants.NewFileUIDHeader:
- item.NewFileUid = value;
- break;
- default:
- result = false;
- break;
- }
-
- return result;
- }
}
diff --git a/src/OfxNet/Sgml/SgmlParseException.cs b/src/OfxNet/Sgml/SgmlParseException.cs
index ea6fac0..bd44d1c 100644
--- a/src/OfxNet/Sgml/SgmlParseException.cs
+++ b/src/OfxNet/Sgml/SgmlParseException.cs
@@ -10,11 +10,13 @@ public SgmlParseException()
{
}
- public SgmlParseException(string message) : base(message)
+ public SgmlParseException(string message)
+ : base(message)
{
}
- public SgmlParseException(string message, Exception inner) : base(message, inner)
+ public SgmlParseException(string message, Exception inner)
+ : base(message, inner)
{
}
}
diff --git a/src/OfxNet/Sgml/SgmlParseResult.cs b/src/OfxNet/Sgml/SgmlParseResult.cs
index 746d10e..3112aca 100644
--- a/src/OfxNet/Sgml/SgmlParseResult.cs
+++ b/src/OfxNet/Sgml/SgmlParseResult.cs
@@ -1,11 +1,7 @@
namespace OfxNet;
-internal class SgmlParseResult
+internal sealed class SgmlParseResult
{
- internal SgmlTagType TagType { get; set; }
- internal string Tag { get; set; }
- internal string? Value { get; set; }
-
internal SgmlParseResult(SgmlTagType tagType, string tag)
: this(tagType, tag, null)
{
@@ -13,8 +9,14 @@ internal SgmlParseResult(SgmlTagType tagType, string tag)
internal SgmlParseResult(SgmlTagType tagType, string tag, string? value)
{
- TagType = tagType;
- Tag = tag;
- Value = value;
+ this.TagType = tagType;
+ this.Tag = tag;
+ this.Value = value;
}
+
+ internal SgmlTagType TagType { get; set; }
+
+ internal string Tag { get; set; }
+
+ internal string? Value { get; set; }
}
diff --git a/src/OfxNet/Sgml/SgmlParser.cs b/src/OfxNet/Sgml/SgmlParser.cs
index 3e630bd..0b1b292 100644
--- a/src/OfxNet/Sgml/SgmlParser.cs
+++ b/src/OfxNet/Sgml/SgmlParser.cs
@@ -6,121 +6,76 @@
using System.Text;
using System.Text.RegularExpressions;
+///
+/// Class to parse SGML formatted OFX documents.
+///
public class SgmlParser
{
- private int _lineNumber;
- private SgmlElement _root = SgmlElement.Empty;
- private SgmlElement _currentNode = SgmlElement.Empty;
-
+ private int lineNumber;
+ private SgmlElement root = SgmlElement.Empty;
+ private SgmlElement currentNode = SgmlElement.Empty;
+
+ ///
+ /// Parse teh specified file as an SGML formatted OFX document.
+ ///
+ /// The complete file path to the document to be parsed.
+ /// The document .
+ /// The root SGML element.
public SgmlElement Parse(string path, Encoding encoding)
{
using StreamReader reader = new(path, encoding);
- _lineNumber = new SgmlHeaderParser().SkipToContent(reader);
+ this.lineNumber = new SgmlHeaderParser().SkipToContent(reader);
while (!reader.EndOfStream)
{
var line = reader.ReadLine();
- ++_lineNumber;
+ ++this.lineNumber;
if (string.IsNullOrWhiteSpace(line) == false)
{
- ProcessLine(line);
+ this.ProcessLine(line);
}
}
- return _root;
- }
-
- #region Private methods
- private void ProcessLine(string text)
- {
- SgmlParseResult? parseResult = TryParseLine(text);
- if (parseResult == null)
- {
- throw new SgmlParseException("Invalid OFX SGML, line " + _lineNumber + ".");
- }
- else
- {
- switch (parseResult.TagType)
- {
- case SgmlTagType.OpeningTag:
- ProcessOpeningTag(parseResult.Tag, text);
- break;
- case SgmlTagType.ValueTag:
- ProcessValueTag(parseResult.Tag, parseResult.Value, text);
- break;
- case SgmlTagType.ClosingTag:
- ProcessClosingTag(parseResult.Tag);
- break;
- default:
- break;
- }
- }
+ return this.root;
}
- private void ProcessOpeningTag(string tag, string text)
+ private static SgmlParseResult? TryParseOpeningTag(string line)
{
- if (_root == SgmlElement.Empty)
- {
- _root = new SgmlElement(tag, text);
- _currentNode = _root;
- }
- else
- {
- _currentNode = _currentNode.AddChild(new SgmlElement(tag, text, _currentNode));
- }
- }
-
- private void ProcessValueTag(string tag, string? value, string text)
- {
- value = GetValue(value);
-
- _currentNode.AddChild(new SgmlElement(tag, value, text, _currentNode));
- }
+ SgmlParseResult? result = default;
- private void ProcessClosingTag(string tag)
- {
- string expectedTag = _currentNode.Name;
- if (string.Equals(expectedTag, tag, StringComparison.CurrentCultureIgnoreCase) == false)
+ Match match = SgmlConstants.OpeningTagRegex.Match(line);
+ if (match.Success && match.Groups.Count == 2)
{
- throw new SgmlParseException($"Closing tag '{tag}' does not match opening tag '{expectedTag}', line {_lineNumber}.");
+ result = new SgmlParseResult(SgmlTagType.OpeningTag, match.Groups[1].Value);
}
- _currentNode = _currentNode.Parent ?? _root;
+ return result;
}
- private SgmlParseResult? TryParseLine(string line)
+ private static SgmlParseResult? TryParseLine(string line)
{
SgmlParseResult? result = TryParseOpeningTag(line);
if (result == default)
{
result = TryParseClosingTag(line);
}
+
if (result == default)
{
result = TryParseValueFullTag(line);
}
+
if (result == default)
{
result = TryParseValuePartialTag(line);
}
- return result;
- }
-
- private SgmlParseResult? TryParseOpeningTag(string line)
- {
- SgmlParseResult? result = default;
- Match match = SgmlConstants.OpeningTagRegex.Match(line);
- if (match.Success && match.Groups.Count == 2)
- {
- result = new SgmlParseResult(SgmlTagType.OpeningTag, match.Groups[1].Value);
- }
return result;
}
- private SgmlParseResult? TryParseClosingTag(string line)
+ private static SgmlParseResult? TryParseClosingTag(string line)
{
SgmlParseResult? result = default;
@@ -129,10 +84,11 @@ private void ProcessClosingTag(string tag)
{
result = new SgmlParseResult(SgmlTagType.ClosingTag, match.Groups[1].Value);
}
+
return result;
}
- private SgmlParseResult? TryParseValueFullTag(string line)
+ private static SgmlParseResult? TryParseValueFullTag(string line)
{
SgmlParseResult? result = default;
@@ -141,10 +97,11 @@ private void ProcessClosingTag(string tag)
{
result = new SgmlParseResult(SgmlTagType.ValueTag, match.Groups[1].Value, match.Groups[2].Value);
}
+
return result;
}
- private SgmlParseResult? TryParseValuePartialTag(string line)
+ private static SgmlParseResult? TryParseValuePartialTag(string line)
{
SgmlParseResult? result = default;
@@ -153,13 +110,69 @@ private void ProcessClosingTag(string tag)
{
result = new SgmlParseResult(SgmlTagType.ValueTag, match.Groups[1].Value, match.Groups[2].Value);
}
+
return result;
}
- private string? GetValue(string? value)
+ private static string? GetValue(string? value)
{
return WebUtility.HtmlDecode(value);
}
- #endregion
+ private void ProcessLine(string text)
+ {
+ SgmlParseResult? parseResult = TryParseLine(text);
+ if (parseResult == null)
+ {
+ throw new SgmlParseException("Invalid OFX SGML, line " + this.lineNumber + ".");
+ }
+ else
+ {
+ switch (parseResult.TagType)
+ {
+ case SgmlTagType.OpeningTag:
+ this.ProcessOpeningTag(parseResult.Tag, text);
+ break;
+ case SgmlTagType.ValueTag:
+ this.ProcessValueTag(parseResult.Tag, parseResult.Value, text);
+ break;
+ case SgmlTagType.ClosingTag:
+ this.ProcessClosingTag(parseResult.Tag);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ private void ProcessOpeningTag(string tag, string text)
+ {
+ if (this.root == SgmlElement.Empty)
+ {
+ this.root = new SgmlElement(tag, text);
+ this.currentNode = this.root;
+ }
+ else
+ {
+ this.currentNode = this.currentNode.AddChild(new SgmlElement(tag, text, this.currentNode));
+ }
+ }
+
+ private void ProcessValueTag(string tag, string? value, string text)
+ {
+ value = GetValue(value);
+
+ this.currentNode.AddChild(new SgmlElement(tag, value, text, this.currentNode));
+ }
+
+ private void ProcessClosingTag(string tag)
+ {
+ string expectedTag = this.currentNode.Name;
+ if (string.Equals(expectedTag, tag, StringComparison.OrdinalIgnoreCase) == false)
+ {
+ throw new SgmlParseException($"Closing tag '{tag}' does not match opening tag '{expectedTag}', line {this.lineNumber}.");
+ }
+
+ this.currentNode = this.currentNode.Parent ?? this.root;
+ }
}
diff --git a/src/OfxNet/Sgml/SgmlTagType.cs b/src/OfxNet/Sgml/SgmlTagType.cs
index a9b693e..0dcbc04 100644
--- a/src/OfxNet/Sgml/SgmlTagType.cs
+++ b/src/OfxNet/Sgml/SgmlTagType.cs
@@ -4,5 +4,5 @@ internal enum SgmlTagType
{
OpeningTag,
ValueTag,
- ClosingTag
+ ClosingTag,
}
diff --git a/src/OfxNet/Xml/XElementAdapter.cs b/src/OfxNet/Xml/XElementAdapter.cs
index d450aef..1d4147e 100644
--- a/src/OfxNet/Xml/XElementAdapter.cs
+++ b/src/OfxNet/Xml/XElementAdapter.cs
@@ -9,20 +9,21 @@
///
/// The XElementAdapter provides a IOfxElement interface for built-in XElement objects.
///
+[SuppressMessage("Performance", "CA1815:Override equals and operator equals on value types", Justification = "Not currently required.")]
public readonly struct XElementAdapter : IOfxElement
{
- private readonly XElement _element;
+ private readonly XElement element;
public XElementAdapter(XElement element)
{
- _element = element;
+ this.element = element;
}
- public string Value => _element.Value;
+ public string Value => this.element.Value;
IOfxElement? IOfxElement.Element(string name, StringComparer comparer)
{
- XElement? element = (from e in _element.Elements()
+ XElement? element = (from e in this.element.Elements()
where comparer.Equals(name, e.Name.LocalName)
select e)
.FirstOrDefault();
@@ -32,7 +33,7 @@ where comparer.Equals(name, e.Name.LocalName)
public IEnumerable Elements(string name, StringComparer comparer)
{
- return from element in _element.Elements()
+ return from element in this.element.Elements()
where comparer.Equals(name, element.Name.LocalName)
select new XElementAdapter(element) as IOfxElement;
}
diff --git a/stylecop.json b/stylecop.json
new file mode 100644
index 0000000..e63674a
--- /dev/null
+++ b/stylecop.json
@@ -0,0 +1,17 @@
+{
+ // ACTION REQUIRED: This file was automatically added to your project, but it
+ // will not take effect until additional steps are taken to enable it. See the
+ // following page for additional information:
+ //
+ // https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/EnableConfiguration.md
+
+ "$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json",
+ "settings": {
+ "documentationRules": {
+ "fileNamingConvention": "stylecop"
+ },
+ "layoutRules": {
+ "newlineAtEndOfFile": "require"
+ }
+ }
+}
diff --git a/test/OfxNet.IntegrationTests/.editorconfig b/test/OfxNet.IntegrationTests/.editorconfig
new file mode 100644
index 0000000..5a6cdd6
--- /dev/null
+++ b/test/OfxNet.IntegrationTests/.editorconfig
@@ -0,0 +1,8 @@
+# EditorConfig is awesome:http://EditorConfig.org
+
+# top-most EditorConfig file
+root = false
+
+# Code files
+[*.{cs,csx,vb,vbx,h,cpp,idl}]
+dotnet_diagnostic.CS1591.severity = none
diff --git a/test/OfxNet.IntegrationTests/OfxDocumentTests.cs b/test/OfxNet.IntegrationTests/OfxDocumentTests.cs
index cab1dee..ab25ac9 100644
--- a/test/OfxNet.IntegrationTests/OfxDocumentTests.cs
+++ b/test/OfxNet.IntegrationTests/OfxDocumentTests.cs
@@ -34,7 +34,7 @@ public void Setup()
[DataTestMethod]
[DynamicData(nameof(SampleOfxFiles), DynamicDataSourceType.Property)]
[SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "Required for testing.")]
- public void OfxDocumentLoad_Succeeds(string path, int statementCount, int txCount)
+ public void OfxDocumentLoadSucceeds(string path, int statementCount, int txCount)
{
var actual = OfxDocument.Load(path);
Assert.IsNotNull(actual);
@@ -42,13 +42,13 @@ public void OfxDocumentLoad_Succeeds(string path, int statementCount, int txCoun
[DataTestMethod]
[DynamicData(nameof(SampleOfxFiles), DynamicDataSourceType.Property)]
- public void OfxDocumentLoad_GetStatements_ReturnsCorrectNumberOfStatementsAndTransactions(string path, int statementCount, int txCount)
+ public void OfxDocumentLoadGetStatementsReturnsCorrectNumberOfStatementsAndTransactions(string path, int statementCount, int txCount)
{
var actual = OfxDocument.Load(path);
Assert.IsNotNull(actual);
- IEnumerable allStatements = actual.GetStatements();
- Assert.AreEqual(statementCount, allStatements.Count());
+ OfxStatement[] allStatements = actual.GetStatements().ToArray();
+ Assert.AreEqual(statementCount, allStatements.Length);
IEnumerable allTransactions = allStatements.SelectMany(s => s.TransactionList!.Transactions);
Assert.AreEqual(txCount, allTransactions.Count());
@@ -57,12 +57,14 @@ public void OfxDocumentLoad_GetStatements_ReturnsCorrectNumberOfStatementsAndTra
[TestMethod]
public void CanParseItau()
{
+ string[] expectedMemos = ["RSHOP", "REND PAGO APLIC AUT MAIS", "SISDEB"];
+
IEnumerable actual = OfxDocument.Load(@"Sample-itau.ofx")
.GetStatements();
OfxStatement statement = actual.First();
Assert.IsInstanceOfType(statement, typeof(OfxBankStatement));
- OfxBankStatement? bankStatement = statement as OfxBankStatement;
+ var bankStatement = statement as OfxBankStatement;
Assert.IsNotNull(bankStatement);
Assert.IsNotNull(bankStatement.Account);
@@ -71,14 +73,16 @@ public void CanParseItau()
Assert.IsNotNull(statement.TransactionList);
Assert.AreEqual(3, statement.TransactionList.Transactions.Count);
- CollectionAssert.AreEqual(
- statement.TransactionList.Transactions.Select(x => x.Memo).ToArray(),
- new string[] { "RSHOP", "REND PAGO APLIC AUT MAIS", "SISDEB" });
+
+ string?[] actualMemos = statement.TransactionList.Transactions.Select(x => x.Memo).ToArray();
+ CollectionAssert.AreEqual(actualMemos, expectedMemos);
}
[TestMethod]
public void CanParseBancoDoBrasil()
{
+ string[] expectedMemos = ["Transferência Agendada", "Compra com Cartão", "Saque"];
+
IEnumerable actual = OfxDocument.Load("Sample-Banco do Brasil.ofx")
.GetStatements();
@@ -95,8 +99,7 @@ public void CanParseBancoDoBrasil()
Assert.IsNotNull(statement.TransactionList);
Assert.AreEqual(3, statement.TransactionList.Transactions.Count);
- CollectionAssert.AreEqual(
- statement.TransactionList.Transactions.Select(x => x.Memo).ToArray(),
- new string[] { "Transferência Agendada", "Compra com Cartão", "Saque" });
+ string?[] actualMemos = statement.TransactionList.Transactions.Select(x => x.Memo).ToArray();
+ CollectionAssert.AreEqual(actualMemos, expectedMemos);
}
}
diff --git a/test/OfxNet.IntegrationTests/OfxNet.IntegrationTests.csproj b/test/OfxNet.IntegrationTests/OfxNet.IntegrationTests.csproj
index 4a3711e..1f1995e 100644
--- a/test/OfxNet.IntegrationTests/OfxNet.IntegrationTests.csproj
+++ b/test/OfxNet.IntegrationTests/OfxNet.IntegrationTests.csproj
@@ -6,7 +6,6 @@
-
diff --git a/test/OfxNet.UnitTests/OfxParserTests.cs b/test/OfxNet.UnitTests/OfxParserTests.cs
index 4607ee6..26e0275 100644
--- a/test/OfxNet.UnitTests/OfxParserTests.cs
+++ b/test/OfxNet.UnitTests/OfxParserTests.cs
@@ -7,8 +7,52 @@ namespace OfxNet.UnitTests;
[TestClass]
public class OfxParserTests
{
+ private static IEnumerable