Comment investments, profiles, seclist, signon, signup, and types

This commit is contained in:
Aaron Lindsay 2017-04-13 10:18:07 -04:00
parent a1aec204a8
commit 1ff64a9d55
8 changed files with 170 additions and 20 deletions

View File

@ -468,7 +468,8 @@ type InvTranList struct {
BankTransactions []InvBankTransaction
}
// UnmarshalXML handles unmarshalling an InvTranList element from an XML string
// UnmarshalXML handles unmarshalling an InvTranList element from an SGML/XML
// string
func (l *InvTranList) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
for {
tok, err := nextNonWhitespaceToken(d)

View File

@ -5,6 +5,9 @@ import (
"github.com/aclindsa/go/src/encoding/xml"
)
// ProfileRequest represents a request for a server to provide a profile of its
// capabilities (which message sets and versions it supports, how to access
// them, which languages and which types of synchronization they support, etc.)
type ProfileRequest struct {
XMLName xml.Name `xml:"PROFTRNRQ"`
TrnUID UID `xml:"TRNUID"`
@ -12,26 +15,35 @@ type ProfileRequest struct {
TAN String `xml:"TAN,omitempty"` // Transaction authorization number
// TODO `xml:"OFXEXTENSION,omitempty"`
ClientRouting String `xml:"PROFRQ>CLIENTROUTING"` // Forced to NONE
DtProfUp Date `xml:"PROFRQ>DTPROFUP"`
DtProfUp Date `xml:"PROFRQ>DTPROFUP"` // Date and time client last received a profile update
}
// Name returns the name of the top-level transaction XML/SGML element
func (r *ProfileRequest) Name() string {
return "PROFTRNRQ"
}
// Valid returns (true, nil) if this struct would be valid OFX if marshalled
// into XML/SGML
func (r *ProfileRequest) Valid() (bool, error) {
// TODO implement
r.ClientRouting = "NONE"
return true, nil
}
// Type returns which message set this message belongs to (which Request
// element of type []Message it should appended to)
func (r *ProfileRequest) Type() messageType {
return ProfRq
}
// SignonInfo provides the requirements to login to a single signon realm. A
// signon realm consists of all MessageSets which can be accessed using one set
// of login credentials. Most FI's only use one signon realm to make it easier
// and less confusing for the user.
type SignonInfo struct {
XMLName xml.Name `xml:"SIGNONINFO"`
SignonRealm String `xml:"SIGNONREALM"`
SignonRealm String `xml:"SIGNONREALM"` // The SignonRealm for which this SignonInfo provides information. This SignonInfo is valid for all MessageSets with SignonRealm fields matching this one
Min Int `xml:"MIN"` // Minimum number of password characters
Max Int `xml:"MAX"` // Maximum number of password characters
CharType charType `xml:"CHARTYPE"` // One of ALPHAONLY, NUMERICONLY, ALPHAORNUMERIC, ALPHAANDNUMERIC
@ -51,11 +63,13 @@ type SignonInfo struct {
AccessTokenReq Boolean `xml:"ACCESSTOKENREQ,omitempty"` // Server requires ACCESSTOKEN to be sent with all requests except profile
}
// MessageSet represents one message set supported by an FI and its
// capabilities
type MessageSet struct {
XMLName xml.Name // <xxxMSGSETVn>
Name string // <xxxMSGSETVn> (copy of XMLName.Local)
Ver Int `xml:"MSGSETCORE>VER"` // Message set version - should always match 'n' in <xxxMSGSETVn>
Url String `xml:"MSGSETCORE>URL"` // URL where messages in this set are to be set
Ver Int `xml:"MSGSETCORE>VER"` // Message set version - should always match 'n' in <xxxMSGSETVn> of Name
URL String `xml:"MSGSETCORE>URL"` // URL where messages in this set are to be set
OfxSec ofxSec `xml:"MSGSETCORE>OFXSEC"` // NONE or 'TYPE 1'
TranspSec Boolean `xml:"MSGSETCORE>TRANSPSEC"` // Transport-level security must be used
SignonRealm String `xml:"MSGSETCORE>SIGNONREALM"` // Used to identify which SignonInfo to use for to this MessageSet
@ -67,8 +81,11 @@ type MessageSet struct {
// TODO MessageSet-specific stuff?
}
// MessageSetList is a list of MessageSets (necessary because they must be
// manually parsed)
type MessageSetList []MessageSet
// UnmarshalXML handles unmarshalling a MessageSetList element from an XML string
func (msl *MessageSetList) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
for {
var msgset MessageSet
@ -106,6 +123,11 @@ func (msl *MessageSetList) UnmarshalXML(d *xml.Decoder, start xml.StartElement)
}
}
// ProfileResponse contains a requested profile of the server's capabilities
// (which message sets and versions it supports, how to access them, which
// languages and which types of synchronization they support, etc.). Note that
// if the server does not support ClientRouting=NONE (as we always send with
// ProfileRequest), this may be an error)
type ProfileResponse struct {
XMLName xml.Name `xml:"PROFTRNRS"`
TrnUID UID `xml:"TRNUID"`
@ -130,15 +152,19 @@ type ProfileResponse struct {
Email String `xml:"PROFRS>EMAIL,omitempty"`
}
// Name returns the name of the top-level transaction XML/SGML element
func (pr *ProfileResponse) Name() string {
return "PROFTRNRS"
}
// Valid returns (true, nil) if this struct was valid OFX when unmarshalled
func (pr *ProfileResponse) Valid() (bool, error) {
//TODO implement
return true, nil
}
// Type returns which message set this message belongs to (which Response
// element of type []Message it belongs to)
func (pr *ProfileResponse) Type() messageType {
return ProfRs
}

View File

@ -232,7 +232,7 @@ NEWFILEUID:NONE
ofxgo.MessageSet{
Name: "SIGNONMSGSETV1",
Ver: 1,
Url: "https://ofx.example.com/cgi-ofx/exampleofx",
URL: "https://ofx.example.com/cgi-ofx/exampleofx",
OfxSec: ofxgo.OfxSecNone,
TranspSec: true,
SignonRealm: "Example Trade",
@ -244,7 +244,7 @@ NEWFILEUID:NONE
ofxgo.MessageSet{
Name: "SIGNUPMSGSETV1",
Ver: 1,
Url: "https://ofx.example.com/cgi-ofx/exampleofx",
URL: "https://ofx.example.com/cgi-ofx/exampleofx",
OfxSec: ofxgo.OfxSecNone,
TranspSec: true,
SignonRealm: "Example Trade",
@ -256,7 +256,7 @@ NEWFILEUID:NONE
ofxgo.MessageSet{
Name: "INVSTMTMSGSETV1",
Ver: 1,
Url: "https://ofx.example.com/cgi-ofx/exampleofx",
URL: "https://ofx.example.com/cgi-ofx/exampleofx",
OfxSec: ofxgo.OfxSecNone,
TranspSec: true,
SignonRealm: "Example Trade",
@ -268,7 +268,7 @@ NEWFILEUID:NONE
ofxgo.MessageSet{
Name: "SECLISTMSGSETV1",
Ver: 1,
Url: "https://ofx.example.com/cgi-ofx/exampleofx",
URL: "https://ofx.example.com/cgi-ofx/exampleofx",
OfxSec: ofxgo.OfxSecNone,
TranspSec: true,
SignonRealm: "Example Trade",
@ -280,7 +280,7 @@ NEWFILEUID:NONE
ofxgo.MessageSet{
Name: "PROFMSGSETV1",
Ver: 1,
Url: "https://ofx.example.com/cgi-ofx/exampleofx",
URL: "https://ofx.example.com/cgi-ofx/exampleofx",
OfxSec: ofxgo.OfxSecNone,
TranspSec: true,
SignonRealm: "Example Trade",

View File

@ -60,7 +60,8 @@ func marshalMessageSet(e *xml.Encoder, requests []Message, set messageType) erro
return nil
}
// Overwrite the fields in this Request object controlled by the Client
// SetClientFields overwrites the fields in this Request object controlled by
// the Client
func (oq *Request) SetClientFields(c *Client) {
oq.Signon.DtClient.Time = time.Now()

View File

@ -5,12 +5,17 @@ import (
"github.com/aclindsa/go/src/encoding/xml"
)
// SecurityID identifies a security by its CUSIP (for US-based FI's, others may
// use UniqueID types other than CUSIP)
type SecurityID struct {
XMLName xml.Name `xml:"SECID"`
UniqueID String `xml:"UNIQUEID"` // CUSIP for US FI's
UniqueIDType String `xml:"UNIQUEIDTYPE"` // Should always be "CUSIP" for US FI's
}
// SecurityRequest represents a request for one security. It is specified with
// a SECID aggregate, a ticker symbol, or an FI assigned identifier (but no
// more than one of them at a time)
type SecurityRequest struct {
XMLName xml.Name `xml:"SECRQ"`
// Only one of the next three should be present
@ -19,6 +24,8 @@ type SecurityRequest struct {
FiID String `xml:"FIID,omitempty"`
}
// SecListRequest represents a request for information (namely price) about one
// or more securities
type SecListRequest struct {
XMLName xml.Name `xml:"SECLISTTRNRQ"`
TrnUID UID `xml:"TRNUID"`
@ -28,19 +35,29 @@ type SecListRequest struct {
Securities []SecurityRequest `xml:"SECLISTRQ>SECRQ,omitempty"`
}
// Name returns the name of the top-level transaction XML/SGML element
func (r *SecListRequest) Name() string {
return "SECLISTTRNRQ"
}
// Valid returns (true, nil) if this struct would be valid OFX if marshalled
// into XML/SGML
func (r *SecListRequest) Valid() (bool, error) {
// TODO implement
return true, nil
}
// Type returns which message set this message belongs to (which Request
// element of type []Message it should appended to)
func (r *SecListRequest) Type() messageType {
return SecListRq
}
// SecListResponse is always empty (except for the transaction UID, status, and
// optional client cookie). Its presence signifies that the SecurityList (a
// different element from this one) immediately after this element in
// Response.SecList was been generated in response to the same SecListRequest
// this is a response to.
type SecListResponse struct {
XMLName xml.Name `xml:"SECLISTTRNRS"`
TrnUID UID `xml:"TRNUID"`
@ -50,23 +67,31 @@ type SecListResponse struct {
// SECLISTRS is always empty, so we don't parse it here. The actual securities list will be in a top-level element parallel to SECLISTTRNRS
}
// Name returns the name of the top-level transaction XML/SGML element
func (r *SecListResponse) Name() string {
return "SECLISTTRNRS"
}
// Valid returns (true, nil) if this struct was valid OFX when unmarshalled
func (r *SecListResponse) Valid() (bool, error) {
// TODO implement
return true, nil
}
// Type returns which message set this message belongs to (which Response
// element of type []Message it belongs to)
func (r *SecListResponse) Type() messageType {
return SecListRs
}
// Security is satisfied by all *Info elements providing information about
// securities for SecurityList
type Security interface {
SecurityType() string
}
// SecInfo represents the generic information about a security. It is included
// in most other *Info elements.
type SecInfo struct {
XMLName xml.Name `xml:"SECINFO"`
SecID SecurityID `xml:"SECID"`
@ -80,6 +105,7 @@ type SecInfo struct {
Memo String `xml:"MEMO,omitempty"`
}
// DebtInfo provides information about a debt security
type DebtInfo struct {
XMLName xml.Name `xml:"DEBTINFO"`
SecInfo SecInfo `xml:"SECINFO"`
@ -99,22 +125,29 @@ type DebtInfo struct {
FiAssetClass String `xml:"FIASSETCLASS,omitempty"` // FI-defined asset class
}
// SecurityType returns a string representation of this security's type
func (i DebtInfo) SecurityType() string {
return "DEBTINFO"
}
// AssetPortion represents the percentage of a mutual fund with the given asset
// classification
type AssetPortion struct {
XMLName xml.Name `xml:"PORTION"`
AssetClass assetClass `xml:"ASSETCLASS"` // One of DOMESTICBOND, INTLBOND, LARGESTOCK, SMALLSTOCK, INTLSTOCK, MONEYMRKT, OTHER
Percent Amount `xml:"PERCENT"` // Percentage of the fund that falls under this asset class
}
// FiAssetPortion represents the percentage of a mutual fund with the given
// FI-defined asset classification (AssetPortion should be used for all asset
// classifications defined by the assetClass enum)
type FiAssetPortion struct {
XMLName xml.Name `xml:"FIPORTION"`
FiAssetClass String `xml:"FIASSETCLASS,omitempty"` // FI-defined asset class
Percent Amount `xml:"PERCENT"` // Percentage of the fund that falls under this asset class
}
// MFInfo provides information about a mutual fund
type MFInfo struct {
XMLName xml.Name `xml:"MFINFO"`
SecInfo SecInfo `xml:"SECINFO"`
@ -125,10 +158,12 @@ type MFInfo struct {
FiAssetClasses []FiAssetPortion `xml:"FIMFASSETCLASS>FIPORTION"`
}
// SecurityType returns a string representation of this security's type
func (i MFInfo) SecurityType() string {
return "MFINFO"
}
// OptInfo provides information about an option
type OptInfo struct {
XMLName xml.Name `xml:"OPTINFO"`
SecInfo SecInfo `xml:"SECINFO"`
@ -141,10 +176,13 @@ type OptInfo struct {
FiAssetClass String `xml:"FIASSETCLASS,omitempty"` // FI-defined asset class
}
// SecurityType returns a string representation of this security's type
func (i OptInfo) SecurityType() string {
return "OPTINFO"
}
// OtherInfo provides information about a security type not covered by the
// other *Info elements
type OtherInfo struct {
XMLName xml.Name `xml:"OTHERINFO"`
SecInfo SecInfo `xml:"SECINFO"`
@ -153,10 +191,12 @@ type OtherInfo struct {
FiAssetClass String `xml:"FIASSETCLASS,omitempty"` // FI-defined asset class
}
// SecurityType returns a string representation of this security's type
func (i OtherInfo) SecurityType() string {
return "OTHERINFO"
}
// StockInfo provides information about a security type
type StockInfo struct {
XMLName xml.Name `xml:"STOCKINFO"`
SecInfo SecInfo `xml:"SECINFO"`
@ -167,27 +207,35 @@ type StockInfo struct {
FiAssetClass String `xml:"FIASSETCLASS,omitempty"` // FI-defined asset class
}
// SecurityType returns a string representation of this security's type
func (i StockInfo) SecurityType() string {
return "STOCKINFO"
}
// SecurityList is a container for Security objects containaing information
// about securities
type SecurityList struct {
Securities []Security
}
// Name returns the name of the top-level transaction XML/SGML element
func (r *SecurityList) Name() string {
return "SECLIST"
}
// Valid returns (true, nil) if this struct was valid OFX when unmarshalled
func (r *SecurityList) Valid() (bool, error) {
// TODO implement
return true, nil
}
// Type returns which message set this message belongs to (which Response
// element of type []Message it belongs to)
func (r *SecurityList) Type() messageType {
return SecListRs
}
// UnmarshalXML handles unmarshalling a SecurityList from an SGML/XML string
func (r *SecurityList) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
for {
tok, err := nextNonWhitespaceToken(d)

View File

@ -6,6 +6,8 @@ import (
"github.com/aclindsa/go/src/encoding/xml"
)
// SignonRequest identifies and authenticates a user to their FI and is
// provided with every Request
type SignonRequest struct {
XMLName xml.Name `xml:"SONRQ"`
DtClient Date `xml:"DTCLIENT"` // Current time on client, overwritten in Client.Request()
@ -20,10 +22,13 @@ type SignonRequest struct {
ClientUID UID `xml:"CLIENTUID,omitempty"`
}
// Name returns the name of the top-level transaction XML/SGML element
func (r *SignonRequest) Name() string {
return "SONRQ"
}
// Valid returns (true, nil) if this struct would be valid OFX if marshalled
// into XML/SGML
func (r *SignonRequest) Valid() (bool, error) {
if len(r.UserID) < 1 || len(r.UserID) > 32 {
return false, errors.New("SONRQ>USERID invalid length")
@ -40,7 +45,7 @@ func (r *SignonRequest) Valid() (bool, error) {
if len(r.Language) == 0 {
r.Language = "ENG"
} else if len(r.Language) != 3 {
return false, fmt.Errorf("SONRQ>LANGUAGE invalid length: \"%s\"\n", r.Language)
return false, fmt.Errorf("SONRQ>LANGUAGE invalid length: \"%s\"", r.Language)
}
if len(r.AppID) < 1 || len(r.AppID) > 5 {
return false, errors.New("SONRQ>APPID invalid length")
@ -51,6 +56,8 @@ func (r *SignonRequest) Valid() (bool, error) {
return true, nil
}
// SignonResponse is provided with every Response and indicates the success or
// failure of the SignonRequest in the corresponding Request
type SignonResponse struct {
XMLName xml.Name `xml:"SONRS"`
Status Status `xml:"STATUS"`
@ -66,13 +73,15 @@ type SignonResponse struct {
AccessKey String `xml:"ACCESSKEY,omitempty"`
}
// Name returns the name of the top-level transaction XML/SGML element
func (r *SignonResponse) Name() string {
return "SONRS"
}
// Valid returns (true, nil) if this struct was valid OFX when unmarshalled
func (r *SignonResponse) Valid() (bool, error) {
if len(r.Language) != 3 {
return false, fmt.Errorf("SONRS>LANGUAGE invalid length: \"%s\"\n", r.Language)
return false, fmt.Errorf("SONRS>LANGUAGE invalid length: \"%s\"", r.Language)
}
return r.Status.Valid()
}

View File

@ -5,6 +5,8 @@ import (
"github.com/aclindsa/go/src/encoding/xml"
)
// AcctInfoRequest represents a request for the server to provide information
// for all of the user's available accounts at this FI
type AcctInfoRequest struct {
XMLName xml.Name `xml:"ACCTINFOTRNRQ"`
TrnUID UID `xml:"TRNUID"`
@ -14,19 +16,25 @@ type AcctInfoRequest struct {
DtAcctUp Date `xml:"ACCTINFORQ>DTACCTUP"`
}
// Name returns the name of the top-level transaction XML/SGML element
func (r *AcctInfoRequest) Name() string {
return "ACCTINFOTRNRQ"
}
// Valid returns (true, nil) if this struct would be valid OFX if marshalled
// into XML/SGML
func (r *AcctInfoRequest) Valid() (bool, error) {
// TODO implement
return true, nil
}
// Type returns which message set this message belongs to (which Request
// element of type []Message it should appended to)
func (r *AcctInfoRequest) Type() messageType {
return SignupRq
}
// HolderInfo contains the information a FI has about an account-holder
type HolderInfo struct {
XMLName xml.Name
FirstName String `xml:"FIRSTNAME"`
@ -45,6 +53,9 @@ type HolderInfo struct {
HolderType holderType `xml:"HOLDERTYPE,omitempty"` // One of INDIVIDUAL, JOINT, CUSTODIAL, TRUST, OTHER
}
// BankAcctInfo contains information about a bank account, including how to
// access it (BankAcct), and whether it supports downloading transactions
// (SupTxDl).
type BankAcctInfo struct {
XMLName xml.Name `xml:"BANKACCTINFO"`
BankAcctFrom BankAcct `xml:"BANKACCTFROM"`
@ -59,11 +70,14 @@ type BankAcctInfo struct {
SvcStatus svcStatus `xml:"SVCSTATUS"` // One of AVAIL (available, but not yet requested), PEND (requested, but not yet available), ACTIVE
}
// Make pointers to these structs print nicely
// String makes pointers to BankAcctInfo structs print nicely
func (bai *BankAcctInfo) String() string {
return fmt.Sprintf("%+v", *bai)
}
// CCAcctInfo contains information about a credit card account, including how
// to access it (CCAcct), and whether it supports downloading transactions
// (SupTxDl).
type CCAcctInfo struct {
XMLName xml.Name `xml:"CCACCTINFO"`
CCAcctFrom CCAcct `xml:"CCACCTFROM"`
@ -74,11 +88,14 @@ type CCAcctInfo struct {
SvcStatus svcStatus `xml:"SVCSTATUS"` // One of AVAIL (available, but not yet requested), PEND (requested, but not yet available), ACTIVE
}
// Make pointers to these structs print nicely
// String makes pointers to CCAcctInfo structs print nicely
func (ci *CCAcctInfo) String() string {
return fmt.Sprintf("%+v", *ci)
}
// InvAcctInfo contains information about an investment account, including how
// to access it (InvAcct), and whether it supports downloading transactions
// (SupTxDl).
type InvAcctInfo struct {
XMLName xml.Name `xml:"INVACCTINFO"`
InvAcctFrom InvAcct `xml:"INVACCTFROM"`
@ -89,11 +106,14 @@ type InvAcctInfo struct {
OptionLevel String `xml:"OPTIONLEVEL,omitempty"` // Text desribing option trading privileges
}
// Make pointers to these structs print nicely
// String makes pointers to InvAcctInfo structs print nicely
func (iai *InvAcctInfo) String() string {
return fmt.Sprintf("%+v", *iai)
}
// AcctInfo represents generic account information. It should contain one (and
// only one) *AcctInfo element corresponding to the tyep of account it
// represents.
type AcctInfo struct {
XMLName xml.Name `xml:"ACCTINFO"`
Name String `xml:"NAME,omitempty"`
@ -101,6 +121,7 @@ type AcctInfo struct {
Phone String `xml:"PHONE,omitempty"`
PrimaryHolder HolderInfo `xml:"HOLDERINFO>PRIMARYHOLDER,omitempty"`
SecondaryHolder HolderInfo `xml:"HOLDERINFO>SECONDARYHOLDER,omitempty"`
// Only one of the rest of the fields will be valid for any given AcctInfo
BankAcctInfo *BankAcctInfo `xml:"BANKACCTINFO,omitempty"`
CCAcctInfo *CCAcctInfo `xml:"CCACCTINFO,omitempty"`
@ -109,6 +130,8 @@ type AcctInfo struct {
// TODO BPACCTINFO?
}
// AcctInfoResponse contains the information about all a user's accounts
// accessible from this FI
type AcctInfoResponse struct {
XMLName xml.Name `xml:"ACCTINFOTRNRS"`
TrnUID UID `xml:"TRNUID"`
@ -119,15 +142,19 @@ type AcctInfoResponse struct {
AcctInfo []AcctInfo `xml:"ACCTINFORS>ACCTINFO,omitempty"`
}
// Name returns the name of the top-level transaction XML/SGML element
func (air *AcctInfoResponse) Name() string {
return "ACCTINFOTRNRS"
}
// Valid returns (true, nil) if this struct was valid OFX when unmarshalled
func (air *AcctInfoResponse) Valid() (bool, error) {
//TODO implement
return true, nil
}
// Type returns which message set this message belongs to (which Response
// element of type []Message it belongs to)
func (air *AcctInfoResponse) Type() messageType {
return SignupRs
}

View File

@ -12,8 +12,11 @@ import (
"time"
)
// Int provides helper methods to unmarshal int64 values from SGML/XML
type Int int64
// UnmarshalXML handles unmarshalling an Int from an SGML/XML string. Leading
// and trailing whitespace is ignored.
func (i *Int) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
var value string
@ -33,14 +36,19 @@ func (i *Int) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
return nil
}
// Equal returns true if the two Ints are equal in value
func (i Int) Equal(o Int) bool {
return i == o
}
// Amount represents non-integer values (or at least values for fields that may
// not necessarily be integers)
type Amount struct {
big.Rat
}
// UnmarshalXML handles unmarshalling an Amount from an SGML/XML string.
// Leading and trailing whitespace is ignored.
func (a *Amount) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
var value string
@ -61,18 +69,22 @@ func (a *Amount) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
return nil
}
// String prints a string representation of an Amount
func (a Amount) String() string {
return strings.TrimRight(strings.TrimRight(a.FloatString(100), "0"), ".")
}
// MarshalXML marshals an Amount to SGML/XML
func (a *Amount) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
return e.EncodeElement(a.String(), start)
}
// Equal returns true if two Amounts are equal in value
func (a Amount) Equal(o Amount) bool {
return (&a).Cmp(&o.Rat) == 0
}
// Date represents OFX date/time values
type Date struct {
time.Time
}
@ -86,6 +98,10 @@ var ofxDateFormats = []string{
}
var ofxDateZoneRegex = regexp.MustCompile(`^([+-]?[0-9]+)(\.([0-9]{2}))?(:([A-Z]+))?$`)
// UnmarshalXML handles unmarshalling a Date from an SGML/XML string. It
// attempts to unmarshal the valid date formats in order of decreasing length
// and defaults to GMT if a time zone is not provided, as per the OFX spec.
// Leading and trailing whitespace is ignored.
func (od *Date) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
var value, zone, zoneFormat string
err := d.DecodeElement(&value, &start)
@ -148,6 +164,7 @@ func (od *Date) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
return errors.New("OFX: Couldn't parse date:" + value)
}
// String returns a string representation of the Date abiding by the OFX spec
func (od Date) String() string {
format := od.Format(ofxDateFormats[0])
zonename, zoneoffset := od.Zone()
@ -165,31 +182,39 @@ func (od Date) String() string {
if len(zonename) > 0 {
return format + ":" + zonename + "]"
} else {
return format + "]"
}
return format + "]"
}
// MarshalXML marshals a Date to XML
func (od *Date) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
return e.EncodeElement(od.String(), start)
}
// Equal returns true if the two Dates represent the same time (time zones are
// accounted for when comparing, but are not required to match)
func (od Date) Equal(o Date) bool {
return od.Time.Equal(o.Time)
}
// NewDate returns a new Date object with the provided date, time, and timezone
func NewDate(year int, month time.Month, day, hour, min, sec, nsec int, loc *time.Location) *Date {
return &Date{Time: time.Date(year, month, day, hour, min, sec, nsec, loc)}
}
var gmt = time.FixedZone("GMT", 0)
// NewDateGMT returns a new Date object with the provided date and time in the
// GMT timezone
func NewDateGMT(year int, month time.Month, day, hour, min, sec, nsec int) *Date {
return &Date{Time: time.Date(year, month, day, hour, min, sec, nsec, gmt)}
}
// String provides helper methods to unmarshal OFX string values from SGML/XML
type String string
// UnmarshalXML handles unmarshalling a String from an SGML/XML string. Leading
// and trailing whitespace is ignored.
func (os *String) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
var value string
err := d.DecodeElement(&value, &start)
@ -200,16 +225,21 @@ func (os *String) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
return nil
}
// String returns the string
func (os *String) String() string {
return string(*os)
}
// Equal returns true if the two Strings are equal in value
func (os String) Equal(o String) bool {
return os == o
}
// Boolean provides helper methods to unmarshal bool values from OFX SGML/XML
type Boolean bool
// UnmarshalXML handles unmarshalling a Boolean from an SGML/XML string.
// Leading and trailing whitespace is ignored.
func (ob *Boolean) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
var value string
err := d.DecodeElement(&value, &start)
@ -228,6 +258,7 @@ func (ob *Boolean) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
return nil
}
// MarshalXML marshals a Boolean to XML
func (ob *Boolean) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
if *ob {
return e.EncodeElement("Y", start)
@ -235,16 +266,21 @@ func (ob *Boolean) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
return e.EncodeElement("N", start)
}
// String returns a string representation of a Boolean value
func (ob *Boolean) String() string {
return fmt.Sprintf("%v", *ob)
}
// Equal returns true if the two Booleans are the same
func (ob Boolean) Equal(o Boolean) bool {
return ob == o
}
// UID represents an UID according to the OFX spec
type UID string
// UnmarshalXML handles unmarshalling an UID from an SGML/XML string. Leading
// and trailing whitespace is ignored.
func (ou *UID) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
var value string
err := d.DecodeElement(&value, &start)
@ -255,8 +291,8 @@ func (ou *UID) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
return nil
}
// The OFX specification recommends that UIDs follow the standard UUID
// 36-character format
// RecommendedFormat returns true iff this UID meets the OFX specification's
// recommendation that UIDs follow the standard UUID 36-character format
func (ou UID) RecommendedFormat() (bool, error) {
if len(ou) != 36 {
return false, errors.New("UID not 36 characters long")
@ -267,10 +303,12 @@ func (ou UID) RecommendedFormat() (bool, error) {
return true, nil
}
// Equal returns true if the two UIDs are the same
func (ou UID) Equal(o UID) bool {
return ou == o
}
// RandomUID creates a new randomly-generated UID
func RandomUID() (*UID, error) {
uidbytes := make([]byte, 16)
n, err := rand.Read(uidbytes[:])