From f5d82fc6aa63bf4b7e3bfef7d292363153e632b8 Mon Sep 17 00:00:00 2001 From: Bel LaPointe <153096461+breel-render@users.noreply.github.com> Date: Fri, 23 May 2025 18:59:39 -0600 Subject: [PATCH] ofx died 3y ago --- go.mod | 7 + go.sum | 12 + src/bank/ofx/ofx.go | 1 + src/bank/ofx/ofx_integration_test.go | 87 + vendor/github.com/aclindsa/ofxgo/LICENSE | 280 ++ vendor/github.com/aclindsa/ofxgo/README.md | 132 + vendor/github.com/aclindsa/ofxgo/bank.go | 366 ++ .../github.com/aclindsa/ofxgo/basic_client.go | 104 + vendor/github.com/aclindsa/ofxgo/client.go | 105 + vendor/github.com/aclindsa/ofxgo/common.go | 372 ++ vendor/github.com/aclindsa/ofxgo/constants.go | 2703 +++++++++++++ .../github.com/aclindsa/ofxgo/creditcard.go | 91 + .../aclindsa/ofxgo/discovercard_client.go | 109 + vendor/github.com/aclindsa/ofxgo/doc.go | 127 + .../aclindsa/ofxgo/generate_constants.py | 239 ++ vendor/github.com/aclindsa/ofxgo/invstmt.go | 1414 +++++++ .../aclindsa/ofxgo/leaf_elements.go | 344 ++ vendor/github.com/aclindsa/ofxgo/profile.go | 206 + vendor/github.com/aclindsa/ofxgo/request.go | 151 + vendor/github.com/aclindsa/ofxgo/response.go | 505 +++ vendor/github.com/aclindsa/ofxgo/seclist.go | 332 ++ vendor/github.com/aclindsa/ofxgo/signon.go | 88 + vendor/github.com/aclindsa/ofxgo/signup.go | 166 + vendor/github.com/aclindsa/ofxgo/types.go | 386 ++ vendor/github.com/aclindsa/ofxgo/util.go | 25 + .../aclindsa/ofxgo/vanguard_client.go | 83 + vendor/github.com/aclindsa/xml/AUTHORS | 1107 ++++++ vendor/github.com/aclindsa/xml/CONTRIBUTORS | 1445 +++++++ vendor/github.com/aclindsa/xml/LICENSE | 27 + vendor/github.com/aclindsa/xml/PATENTS | 22 + vendor/github.com/aclindsa/xml/marshal.go | 1075 +++++ vendor/github.com/aclindsa/xml/read.go | 749 ++++ vendor/github.com/aclindsa/xml/typeinfo.go | 364 ++ vendor/github.com/aclindsa/xml/xml.go | 2075 ++++++++++ vendor/github.com/google/uuid/CHANGELOG.md | 41 + vendor/github.com/google/uuid/CONTRIBUTING.md | 26 + vendor/github.com/google/uuid/CONTRIBUTORS | 9 + vendor/github.com/google/uuid/LICENSE | 27 + vendor/github.com/google/uuid/README.md | 21 + vendor/github.com/google/uuid/dce.go | 80 + vendor/github.com/google/uuid/doc.go | 12 + vendor/github.com/google/uuid/hash.go | 59 + vendor/github.com/google/uuid/marshal.go | 38 + vendor/github.com/google/uuid/node.go | 90 + vendor/github.com/google/uuid/node_js.go | 12 + vendor/github.com/google/uuid/node_net.go | 33 + vendor/github.com/google/uuid/null.go | 118 + vendor/github.com/google/uuid/sql.go | 59 + vendor/github.com/google/uuid/time.go | 134 + vendor/github.com/google/uuid/util.go | 43 + vendor/github.com/google/uuid/uuid.go | 365 ++ vendor/github.com/google/uuid/version1.go | 44 + vendor/github.com/google/uuid/version4.go | 76 + vendor/github.com/google/uuid/version6.go | 56 + vendor/github.com/google/uuid/version7.go | 104 + vendor/golang.org/x/text/LICENSE | 27 + vendor/golang.org/x/text/PATENTS | 22 + vendor/golang.org/x/text/currency/common.go | 67 + vendor/golang.org/x/text/currency/currency.go | 185 + vendor/golang.org/x/text/currency/format.go | 220 ++ vendor/golang.org/x/text/currency/query.go | 152 + vendor/golang.org/x/text/currency/tables.go | 2629 +++++++++++++ .../x/text/internal/format/format.go | 41 + .../x/text/internal/format/parser.go | 358 ++ .../x/text/internal/language/common.go | 16 + .../x/text/internal/language/compact.go | 29 + .../text/internal/language/compact/compact.go | 61 + .../internal/language/compact/language.go | 260 ++ .../text/internal/language/compact/parents.go | 120 + .../text/internal/language/compact/tables.go | 1015 +++++ .../x/text/internal/language/compact/tags.go | 91 + .../x/text/internal/language/compose.go | 167 + .../x/text/internal/language/coverage.go | 28 + .../x/text/internal/language/language.go | 627 +++ .../x/text/internal/language/lookup.go | 412 ++ .../x/text/internal/language/match.go | 226 ++ .../x/text/internal/language/parse.go | 608 +++ .../x/text/internal/language/tables.go | 3494 +++++++++++++++++ .../x/text/internal/language/tags.go | 48 + .../x/text/internal/number/common.go | 55 + .../x/text/internal/number/decimal.go | 500 +++ .../x/text/internal/number/format.go | 533 +++ .../x/text/internal/number/number.go | 152 + .../x/text/internal/number/pattern.go | 485 +++ .../internal/number/roundingmode_string.go | 30 + .../x/text/internal/number/tables.go | 1219 ++++++ .../x/text/internal/stringset/set.go | 86 + vendor/golang.org/x/text/internal/tag/tag.go | 100 + vendor/golang.org/x/text/language/coverage.go | 187 + vendor/golang.org/x/text/language/doc.go | 98 + vendor/golang.org/x/text/language/language.go | 605 +++ vendor/golang.org/x/text/language/match.go | 735 ++++ vendor/golang.org/x/text/language/parse.go | 256 ++ vendor/golang.org/x/text/language/tables.go | 298 ++ vendor/golang.org/x/text/language/tags.go | 145 + vendor/modules.txt | 19 + 96 files changed, 33152 insertions(+) create mode 100644 src/bank/ofx/ofx.go create mode 100644 src/bank/ofx/ofx_integration_test.go create mode 100644 vendor/github.com/aclindsa/ofxgo/LICENSE create mode 100644 vendor/github.com/aclindsa/ofxgo/README.md create mode 100644 vendor/github.com/aclindsa/ofxgo/bank.go create mode 100644 vendor/github.com/aclindsa/ofxgo/basic_client.go create mode 100644 vendor/github.com/aclindsa/ofxgo/client.go create mode 100644 vendor/github.com/aclindsa/ofxgo/common.go create mode 100644 vendor/github.com/aclindsa/ofxgo/constants.go create mode 100644 vendor/github.com/aclindsa/ofxgo/creditcard.go create mode 100644 vendor/github.com/aclindsa/ofxgo/discovercard_client.go create mode 100644 vendor/github.com/aclindsa/ofxgo/doc.go create mode 100644 vendor/github.com/aclindsa/ofxgo/generate_constants.py create mode 100644 vendor/github.com/aclindsa/ofxgo/invstmt.go create mode 100644 vendor/github.com/aclindsa/ofxgo/leaf_elements.go create mode 100644 vendor/github.com/aclindsa/ofxgo/profile.go create mode 100644 vendor/github.com/aclindsa/ofxgo/request.go create mode 100644 vendor/github.com/aclindsa/ofxgo/response.go create mode 100644 vendor/github.com/aclindsa/ofxgo/seclist.go create mode 100644 vendor/github.com/aclindsa/ofxgo/signon.go create mode 100644 vendor/github.com/aclindsa/ofxgo/signup.go create mode 100644 vendor/github.com/aclindsa/ofxgo/types.go create mode 100644 vendor/github.com/aclindsa/ofxgo/util.go create mode 100644 vendor/github.com/aclindsa/ofxgo/vanguard_client.go create mode 100644 vendor/github.com/aclindsa/xml/AUTHORS create mode 100644 vendor/github.com/aclindsa/xml/CONTRIBUTORS create mode 100644 vendor/github.com/aclindsa/xml/LICENSE create mode 100644 vendor/github.com/aclindsa/xml/PATENTS create mode 100644 vendor/github.com/aclindsa/xml/marshal.go create mode 100644 vendor/github.com/aclindsa/xml/read.go create mode 100644 vendor/github.com/aclindsa/xml/typeinfo.go create mode 100644 vendor/github.com/aclindsa/xml/xml.go create mode 100644 vendor/github.com/google/uuid/CHANGELOG.md create mode 100644 vendor/github.com/google/uuid/CONTRIBUTING.md create mode 100644 vendor/github.com/google/uuid/CONTRIBUTORS create mode 100644 vendor/github.com/google/uuid/LICENSE create mode 100644 vendor/github.com/google/uuid/README.md create mode 100644 vendor/github.com/google/uuid/dce.go create mode 100644 vendor/github.com/google/uuid/doc.go create mode 100644 vendor/github.com/google/uuid/hash.go create mode 100644 vendor/github.com/google/uuid/marshal.go create mode 100644 vendor/github.com/google/uuid/node.go create mode 100644 vendor/github.com/google/uuid/node_js.go create mode 100644 vendor/github.com/google/uuid/node_net.go create mode 100644 vendor/github.com/google/uuid/null.go create mode 100644 vendor/github.com/google/uuid/sql.go create mode 100644 vendor/github.com/google/uuid/time.go create mode 100644 vendor/github.com/google/uuid/util.go create mode 100644 vendor/github.com/google/uuid/uuid.go create mode 100644 vendor/github.com/google/uuid/version1.go create mode 100644 vendor/github.com/google/uuid/version4.go create mode 100644 vendor/github.com/google/uuid/version6.go create mode 100644 vendor/github.com/google/uuid/version7.go create mode 100644 vendor/golang.org/x/text/LICENSE create mode 100644 vendor/golang.org/x/text/PATENTS create mode 100644 vendor/golang.org/x/text/currency/common.go create mode 100644 vendor/golang.org/x/text/currency/currency.go create mode 100644 vendor/golang.org/x/text/currency/format.go create mode 100644 vendor/golang.org/x/text/currency/query.go create mode 100644 vendor/golang.org/x/text/currency/tables.go create mode 100644 vendor/golang.org/x/text/internal/format/format.go create mode 100644 vendor/golang.org/x/text/internal/format/parser.go create mode 100644 vendor/golang.org/x/text/internal/language/common.go create mode 100644 vendor/golang.org/x/text/internal/language/compact.go create mode 100644 vendor/golang.org/x/text/internal/language/compact/compact.go create mode 100644 vendor/golang.org/x/text/internal/language/compact/language.go create mode 100644 vendor/golang.org/x/text/internal/language/compact/parents.go create mode 100644 vendor/golang.org/x/text/internal/language/compact/tables.go create mode 100644 vendor/golang.org/x/text/internal/language/compact/tags.go create mode 100644 vendor/golang.org/x/text/internal/language/compose.go create mode 100644 vendor/golang.org/x/text/internal/language/coverage.go create mode 100644 vendor/golang.org/x/text/internal/language/language.go create mode 100644 vendor/golang.org/x/text/internal/language/lookup.go create mode 100644 vendor/golang.org/x/text/internal/language/match.go create mode 100644 vendor/golang.org/x/text/internal/language/parse.go create mode 100644 vendor/golang.org/x/text/internal/language/tables.go create mode 100644 vendor/golang.org/x/text/internal/language/tags.go create mode 100644 vendor/golang.org/x/text/internal/number/common.go create mode 100644 vendor/golang.org/x/text/internal/number/decimal.go create mode 100644 vendor/golang.org/x/text/internal/number/format.go create mode 100644 vendor/golang.org/x/text/internal/number/number.go create mode 100644 vendor/golang.org/x/text/internal/number/pattern.go create mode 100644 vendor/golang.org/x/text/internal/number/roundingmode_string.go create mode 100644 vendor/golang.org/x/text/internal/number/tables.go create mode 100644 vendor/golang.org/x/text/internal/stringset/set.go create mode 100644 vendor/golang.org/x/text/internal/tag/tag.go create mode 100644 vendor/golang.org/x/text/language/coverage.go create mode 100644 vendor/golang.org/x/text/language/doc.go create mode 100644 vendor/golang.org/x/text/language/language.go create mode 100644 vendor/golang.org/x/text/language/match.go create mode 100644 vendor/golang.org/x/text/language/parse.go create mode 100644 vendor/golang.org/x/text/language/tables.go create mode 100644 vendor/golang.org/x/text/language/tags.go diff --git a/go.mod b/go.mod index cffcfc8..7c62533 100644 --- a/go.mod +++ b/go.mod @@ -5,10 +5,17 @@ go 1.23.0 toolchain go1.24.2 require ( + github.com/aclindsa/ofxgo v0.1.3 github.com/go-echarts/go-echarts/v2 v2.3.1 + github.com/google/uuid v1.6.0 golang.org/x/crypto v0.38.0 ) +require ( + github.com/aclindsa/xml v0.0.0-20201125035057-bbd5c9ec99ac // indirect + golang.org/x/text v0.25.0 // indirect +) + require ( github.com/guptarohit/asciigraph v0.7.3 golang.org/x/sys v0.33.0 // indirect diff --git a/go.sum b/go.sum index 17355f0..2ad47b6 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,13 @@ +github.com/aclindsa/ofxgo v0.1.3 h1:20Ckjpg5gG4rdGh2juGfa5I1gnWULMXGWxpseVLCVaM= +github.com/aclindsa/ofxgo v0.1.3/go.mod h1:q2mYxGiJr5X3rlyoQjQq+qqHAQ8cTLntPOtY0Dq0pzE= +github.com/aclindsa/xml v0.0.0-20201125035057-bbd5c9ec99ac h1:xCNSfPWpcx3Sdz/+aB/Re4L8oA6Y4kRRRuTh1CHCDEw= +github.com/aclindsa/xml v0.0.0-20201125035057-bbd5c9ec99ac/go.mod h1:GjqOUT8xlg5+T19lFv6yAGNrtMKkZ839Gt4e16mBXlY= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-echarts/go-echarts/v2 v2.3.1 h1:Yw0HVjVTxpYm48l974dMjRzx8ni2ql0kKi/kawSgxFE= github.com/go-echarts/go-echarts/v2 v2.3.1/go.mod h1:56YlvzhW/a+du15f3S2qUGNDfKnFOeJSThBIrVFHDtI= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/guptarohit/asciigraph v0.7.3 h1:p05XDDn7cBTWiBqWb30mrwxd6oU0claAjqeytllnsPY= github.com/guptarohit/asciigraph v0.7.3/go.mod h1:dYl5wwK4gNsnFf9Zp+l06rFiDZ5YtXM6x7SRWZ3KGag= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -10,9 +16,15 @@ github.com/stretchr/testify v1.6.0 h1:jlIyCplCJFULU/01vCkhKuTyc3OorI3bJFuw6obfgh github.com/stretchr/testify v1.6.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg= golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= +golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gopkg.in/yaml.v3 v3.0.0 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA= gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/src/bank/ofx/ofx.go b/src/bank/ofx/ofx.go new file mode 100644 index 0000000..3e94f6d --- /dev/null +++ b/src/bank/ofx/ofx.go @@ -0,0 +1 @@ +package ofx diff --git a/src/bank/ofx/ofx_integration_test.go b/src/bank/ofx/ofx_integration_test.go new file mode 100644 index 0000000..c8d4386 --- /dev/null +++ b/src/bank/ofx/ofx_integration_test.go @@ -0,0 +1,87 @@ +////go:build integration + +package ofx_test + +import ( + "net/http" + "strconv" + "testing" + "time" + + "github.com/aclindsa/ofxgo" + "github.com/google/uuid" +) + +func TestIntegrationChaseCredit(t *testing.T) { + http.DefaultClient = &http.Client{ + Timeout: time.Minute, + Transport: &http.Transport{ + DisableKeepAlives: true, + }, + } + + client := ofxgo.BasicClient{ + AppVer: "2700", + SpecVersion: 220, + } + + query := ofxgo.Request{ + URL: "https://ofx.chase.com", + Signon: ofxgo.SignonRequest{ + Org: ofxgo.String("B1"), + Fid: ofxgo.String("10898"), + UserID: ofxgo.String("squeaky2x3"), + UserPass: ofxgo.String("ZFpdwiMzvb9ZWGTQa@"), + ClientUID: ofxgo.UID("FAACF8BA-E7E6-43F8-A76D-9CC69219DE14"), + }, + Bank: []ofxgo.Message{&ofxgo.StatementRequest{ + TrnUID: ofxgo.UID(uuid.New().String()), + BankAcctFrom: ofxgo.BankAcct{ + BankID: ofxgo.String("14720"), // routing + AcctID: ofxgo.String("257826587"), // acc + AcctType: ofxgo.AcctTypeCreditLine, // ofxgo.AcctTypeChecking, + }, + Include: true, // transactions + }}, + } + + response, err := client.Request(&query) + if err != nil { + t.Fatal(err) + } else if code := response.Signon.Status.Code; code != 0 { + body, _ := response.Signon.Status.CodeMeaning() + t.Fatalf("failed to sign in: (%d) %s", code, body) + } + + if len(response.Bank) < 1 { + t.Fatalf("no banking messages") + } + + for i, bank := range response.Bank { + i, bank := strconv.Itoa(i), bank + t.Run(i, func(t *testing.T) { + statement, ok := bank.(*ofxgo.StatementResponse) + if !ok { + t.Fatalf("not a statement response: %T", bank) + } + for j, xaction := range statement.BankTranList.Transactions { + j, xaction := strconv.Itoa(j), xaction + t.Run(j, func(t *testing.T) { + currency := statement.CurDef + if ok, _ := xaction.Currency.Valid(); ok { + currency = xaction.Currency.CurSym + } + t.Logf("%s %-15s %-11s %s%s%s\n", + xaction.DtPosted, + xaction.TrnAmt.String()+" "+currency.String(), + xaction.TrnType, + xaction.Name, + xaction.Payee.Name, + xaction.Memo, + ) + t.Errorf("%+v", xaction) + }) + } + }) + } +} diff --git a/vendor/github.com/aclindsa/ofxgo/LICENSE b/vendor/github.com/aclindsa/ofxgo/LICENSE new file mode 100644 index 0000000..d8cf7d4 --- /dev/null +++ b/vendor/github.com/aclindsa/ofxgo/LICENSE @@ -0,0 +1,280 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS diff --git a/vendor/github.com/aclindsa/ofxgo/README.md b/vendor/github.com/aclindsa/ofxgo/README.md new file mode 100644 index 0000000..c7808a1 --- /dev/null +++ b/vendor/github.com/aclindsa/ofxgo/README.md @@ -0,0 +1,132 @@ +# OFXGo + +[![Go Report Card](https://goreportcard.com/badge/github.com/aclindsa/ofxgo)](https://goreportcard.com/report/github.com/aclindsa/ofxgo) +[![Build Status](https://github.com/aclindsa/ofxgo/workflows/ofxgo%20CI%20Test/badge.svg?branch=master)](https://github.com/aclindsa/ofxgo/actions?query=workflow%3A%22ofxgo+CI+Test%22+branch%3Amaster) +[![Coverage Status](https://coveralls.io/repos/github/aclindsa/ofxgo/badge.svg?branch=master)](https://coveralls.io/github/aclindsa/ofxgo?branch=master) +[![PkgGoDev](https://pkg.go.dev/badge/github.com/aclindsa?ofxgo)](https://pkg.go.dev/github.com/aclindsa/ofxgo) + +**OFXGo** is a library for querying OFX servers and/or parsing the responses. It +also provides an example command-line client to demonstrate the use of the +library. + +## Goals + +The main purpose of this project is to provide a library to make it easier to +query financial information with OFX from the comfort of Golang, without having +to marshal/unmarshal to SGML or XML. The library does _not_ intend to abstract +away all of the details of the OFX specification, which would be difficult to do +well. Instead, it exposes the OFX SGML/XML hierarchy as structs which mostly +resemble it. Its primary goal is to enable the creation of other personal +finance software in Go (as it was created to allow me to fetch OFX transactions +for my own project, [MoneyGo](https://github.com/aclindsa/moneygo)). + +Because the OFX specification is rather... 'comprehensive,' it can be difficult +for those unfamiliar with it to figure out where to start. To that end, I have +created a sample command-line client which uses the library to do simple tasks +(currently it does little more than list accounts and query for balances and +transactions). My hope is that by studying its code, new users will be able to +figure out how to use the library much faster than staring at the OFX +specification (or this library's [API +documentation](https://pkg.go.dev/github.com/aclindsa/ofxgo)). The command-line client +also serves as an easy way for me to test/debug the library with actual +financial institutions, which frequently have 'quirks' in their implementations. +The command-line client can be found in the [cmd/ofx +directory](https://github.com/aclindsa/ofxgo/tree/master/cmd/ofx) of this +repository. + +## Library documentation + +Documentation can be found with the `go doc` tool, or at +https://pkg.go.dev/github.com/aclindsa/ofxgo + +## Example Usage + +The following code snippet demonstrates how to use OFXGo to query and parse +OFX code from a checking account, printing the balance and returned transactions: + +```go +client := ofxgo.BasicClient{} // Accept the default Client settings + +// These values are specific to your bank +var query ofxgo.Request +query.URL = "https://secu.example.com/ofx" +query.Signon.Org = ofxgo.String("SECU") +query.Signon.Fid = ofxgo.String("1234") + +// Set your username/password +query.Signon.UserID = ofxgo.String("username") +query.Signon.UserPass = ofxgo.String("hunter2") + +uid, _ := ofxgo.RandomUID() // Handle error in real code +query.Bank = append(query.Bank, &ofxgo.StatementRequest{ + TrnUID: *uid, + BankAcctFrom: ofxgo.BankAcct{ + BankID: ofxgo.String("123456789"), // Possibly your routing number + AcctID: ofxgo.String("00011122233"), // Possibly your account number + AcctType: ofxgo.AcctTypeChecking, + }, + Include: true, // Include transactions (instead of only balance information) +}) + +response, _ := client.Request(&query) // Handle error in real code + +// Was there an OFX error while processing our request? +if response.Signon.Status.Code != 0 { + meaning, _ := response.Signon.Status.CodeMeaning() + fmt.Printf("Nonzero signon status (%d: %s) with message: %s\n", response.Signon.Status.Code, meaning, response.Signon.Status.Message) + os.Exit(1) +} + +if len(response.Bank) < 1 { + fmt.Println("No banking messages received") + os.Exit(1) +} + +if stmt, ok := response.Bank[0].(*ofxgo.StatementResponse); ok { + fmt.Printf("Balance: %s %s (as of %s)\n", stmt.BalAmt, stmt.CurDef, stmt.DtAsOf) + fmt.Println("Transactions:") + for _, tran := range stmt.BankTranList.Transactions { + currency := stmt.CurDef + if ok, _ := tran.Currency.Valid(); ok { + currency = tran.Currency.CurSym + } + fmt.Printf("%s %-15s %-11s %s%s%s\n", tran.DtPosted, tran.TrnAmt.String()+" "+currency.String(), tran.TrnType, tran.Name, tran.Payee.Name, tran.Memo) + } +} +``` + +Similarly, if you have an OFX file available locally, you can parse it directly: + +```go +func main() { + f, err := os.Open("./transactions.qfx") + if err != nil { + fmt.Printf("can't open file: %v\n", err) + return + } + defer f.Close() + + resp, err := ofxgo.ParseResponse(f) + if err != nil { + fmt.Printf("can't parse response: %v\n", err) + return + } + + // do something with resp (*ofxgo.Response) +} +``` + +## Requirements + +OFXGo requires go >= 1.12 + +## Using the command-line client + +To install the command-line client and test it out, you may do the following: + +$ go get -v github.com/aclindsa/ofxgo/cmd/ofx && go install -v github.com/aclindsa/ofxgo/cmd/ofx + +Once installed (at ~/go/bin/ofx by default, if you haven't set $GOPATH), the +command's usage should help you to use it (`./ofx --help` for a listing of the +available subcommands and their purposes, `./ofx subcommand --help` for +individual subcommand usage). diff --git a/vendor/github.com/aclindsa/ofxgo/bank.go b/vendor/github.com/aclindsa/ofxgo/bank.go new file mode 100644 index 0000000..53a7015 --- /dev/null +++ b/vendor/github.com/aclindsa/ofxgo/bank.go @@ -0,0 +1,366 @@ +package ofxgo + +import ( + "errors" + "github.com/aclindsa/xml" +) + +// StatementRequest represents a request for a bank statement. It is used to +// request balances and/or transactions for checking, savings, money market, +// and line of credit accounts. See CCStatementRequest for the analog for +// credit card accounts. +type StatementRequest struct { + XMLName xml.Name `xml:"STMTTRNRQ"` + TrnUID UID `xml:"TRNUID"` + CltCookie String `xml:"CLTCOOKIE,omitempty"` + TAN String `xml:"TAN,omitempty"` // Transaction authorization number + // TODO `xml:"OFXEXTENSION,omitempty"` + BankAcctFrom BankAcct `xml:"STMTRQ>BANKACCTFROM"` + DtStart *Date `xml:"STMTRQ>INCTRAN>DTSTART,omitempty"` + DtEnd *Date `xml:"STMTRQ>INCTRAN>DTEND,omitempty"` + Include Boolean `xml:"STMTRQ>INCTRAN>INCLUDE"` // Include transactions (instead of just balance) + IncludePending Boolean `xml:"STMTRQ>INCLUDEPENDING,omitempty"` // Include pending transactions + IncTranImg Boolean `xml:"STMTRQ>INCTRANIMG,omitempty"` // Include transaction images +} + +// Name returns the name of the top-level transaction XML/SGML element +func (r *StatementRequest) Name() string { + return "STMTTRNRQ" +} + +// Valid returns (true, nil) if this struct would be valid OFX if marshalled +// into XML/SGML +func (r *StatementRequest) Valid(version ofxVersion) (bool, error) { + if ok, err := r.TrnUID.Valid(); !ok { + return false, err + } + if r.IncludePending && version < OfxVersion220 { + return false, errors.New("StatementRequest.IncludePending invalid for OFX < 2.2") + } + if r.IncTranImg && version < OfxVersion210 { + return false, errors.New("StatementRequest.IncTranImg invalid for OFX < 2.1") + } + return r.BankAcctFrom.Valid() +} + +// Type returns which message set this message belongs to (which Request +// element of type []Message it should appended to) +func (r *StatementRequest) Type() messageType { + return BankRq +} + +// Payee specifies a complete billing address for a payee +type Payee struct { + XMLName xml.Name `xml:"PAYEE"` + Name String `xml:"NAME"` + Addr1 String `xml:"ADDR1"` + Addr2 String `xml:"ADDR2,omitempty"` + Addr3 String `xml:"ADDR3,omitempty"` + City String `xml:"CITY"` + State String `xml:"STATE"` + PostalCode String `xml:"POSTALCODE"` + Country String `xml:"COUNTRY,omitempty"` + Phone String `xml:"PHONE"` +} + +// Valid returns (true, nil) if this struct is valid OFX +func (p Payee) Valid() (bool, error) { + if len(p.Name) == 0 { + return false, errors.New("Payee.Name empty") + } else if len(p.Addr1) == 0 { + return false, errors.New("Payee.Addr1 empty") + } else if len(p.City) == 0 { + return false, errors.New("Payee.City empty") + } else if len(p.State) == 0 { + return false, errors.New("Payee.State empty") + } else if len(p.PostalCode) == 0 { + return false, errors.New("Payee.PostalCode empty") + } else if len(p.Country) != 0 && len(p.Country) != 3 { + return false, errors.New("Payee.Country invalid length") + } else if len(p.Phone) == 0 { + return false, errors.New("Payee.Phone empty") + } + return true, nil +} + +// ImageData represents the metadata surrounding a check or other image file, +// including how to retrieve the image +type ImageData struct { + XMLName xml.Name `xml:"IMAGEDATA"` + ImageType imageType `xml:"IMAGETYPE"` // One of STATEMENT, TRANSACTION, TAX + ImageRef String `xml:"IMAGEREF"` // URL or identifier, depending on IMAGEREFTYPE + ImageRefType imageRefType `xml:"IMAGEREFTYPE"` // One of OPAQUE, URL, FORMURL (see spec for more details on how to access images of each of these types) + // Only one of the next two should be valid at any given time + ImageDelay Int `xml:"IMAGEDELAY,omitempty"` // Number of calendar days from DTSERVER (for statement images) or DTPOSTED (for transaction image) the image will become available + DtImageAvail *Date `xml:"DTIMAGEAVAIL,omitempty"` // Date image will become available + ImageTTL Int `xml:"IMAGETTL,omitempty"` // Number of days after image becomes available that it will remain available + CheckSup checkSup `xml:"CHECKSUP,omitempty"` // What is contained in check images. One of FRONTONLY, BACKONLY, FRONTANDBACK +} + +// Transaction represents a single banking transaction. At a minimum, it +// identifies the type of transaction (TrnType) and the date it was posted +// (DtPosted). Ideally it also provides metadata to help the user recognize +// this transaction (i.e. CheckNum, Name or Payee, Memo, etc.) +type Transaction struct { + XMLName xml.Name `xml:"STMTTRN"` + TrnType trnType `xml:"TRNTYPE"` // One of CREDIT, DEBIT, INT (interest earned or paid. Note: Depends on signage of amount), DIV, FEE, SRVCHG (service charge), DEP (deposit), ATM (Note: Depends on signage of amount), POS (Note: Depends on signage of amount), XFER, CHECK, PAYMENT, CASH, DIRECTDEP, DIRECTDEBIT, REPEATPMT, OTHER + DtPosted Date `xml:"DTPOSTED"` + DtUser *Date `xml:"DTUSER,omitempty"` + DtAvail *Date `xml:"DTAVAIL,omitempty"` + TrnAmt Amount `xml:"TRNAMT"` + FiTID String `xml:"FITID"` // Client uses FITID to detect whether it has previously downloaded the transaction + CorrectFiTID String `xml:"CORRECTFITID,omitempty"` // Transaction ID that this transaction corrects, if present + CorrectAction correctAction `xml:"CORRECTACTION,omitempty"` // One of DELETE, REPLACE + SrvrTID String `xml:"SRVRTID,omitempty"` + CheckNum String `xml:"CHECKNUM,omitempty"` + RefNum String `xml:"REFNUM,omitempty"` + SIC Int `xml:"SIC,omitempty"` // Standard Industrial Code + PayeeID String `xml:"PAYEEID,omitempty"` + // Note: Servers should provide NAME or PAYEE, but not both + Name String `xml:"NAME,omitempty"` + Payee *Payee `xml:"PAYEE,omitempty"` + ExtdName String `xml:"EXTDNAME,omitempty"` // Extended name of payee or transaction description + BankAcctTo *BankAcct `xml:"BANKACCTTO,omitempty"` // If the transfer was to a bank account we have the account information for + CCAcctTo *CCAcct `xml:"CCACCTTO,omitempty"` // If the transfer was to a credit card account we have the account information for + Memo String `xml:"MEMO,omitempty"` // Extra information (not in NAME) + ImageData []ImageData `xml:"IMAGEDATA,omitempty"` + + // Only one of Currency and OrigCurrency can ever be Valid() for the same transaction + Currency *Currency `xml:"CURRENCY,omitempty"` // Represents the currency of TrnAmt (instead of CURDEF in STMTRS) if Valid + OrigCurrency *Currency `xml:"ORIGCURRENCY,omitempty"` // Represents the currency TrnAmt was converted to STMTRS' CURDEF from if Valid + Inv401kSource inv401kSource `xml:"INV401KSOURCE,omitempty"` // One of PRETAX, AFTERTAX, MATCH, PROFITSHARING, ROLLOVER, OTHERVEST, OTHERNONVEST (Default if not present is OTHERNONVEST. The following cash source types are subject to vesting: MATCH, PROFITSHARING, and OTHERVEST.) +} + +// Valid returns (true, nil) if this struct is valid OFX +func (t Transaction) Valid(version ofxVersion) (bool, error) { + var emptyDate Date + if !t.TrnType.Valid() || t.TrnType == TrnTypeHold { + return false, errors.New("Transaction.TrnType invalid") + } else if t.DtPosted.Equal(emptyDate) { + return false, errors.New("Transaction.DtPosted not filled") + } else if len(t.FiTID) == 0 { + return false, errors.New("Transaction.FiTID empty") + } else if len(t.CorrectFiTID) > 0 && t.CorrectAction.Valid() { + return false, errors.New("Transaction.CorrectFiTID nonempty but CorrectAction invalid") + } else if len(t.Name) > 0 && t.Payee != nil { + return false, errors.New("Only one of Transaction.Name and Payee may be specified") + } + if t.Payee != nil { + if ok, err := t.Payee.Valid(); !ok { + return false, err + } + } + if t.BankAcctTo != nil && t.CCAcctTo != nil { + return false, errors.New("Only one of Transaction.BankAcctTo and CCAcctTo may be specified") + } else if t.BankAcctTo != nil { + if ok, err := t.BankAcctTo.Valid(); !ok { + return false, err + } + } else if t.CCAcctTo != nil { + if ok, err := t.CCAcctTo.Valid(); !ok { + return false, err + } + } + if version < OfxVersion220 && len(t.ImageData) > 0 { + return false, errors.New("Transaction.ImageData only supportd for OFX > 220") + } else if len(t.ImageData) > 2 { + return false, errors.New("Only 2 of ImageData allowed in Transaction") + } + var ok1, ok2 bool + if t.Currency != nil { + ok1, _ = t.Currency.Valid() + } + if t.OrigCurrency != nil { + ok2, _ = t.OrigCurrency.Valid() + } + if ok1 && ok2 { + return false, errors.New("Currency and OrigCurrency both supplied for Pending Transaction, only one allowed") + } + return true, nil +} + +// TransactionList represents a list of bank transactions, and also includes +// the date range its transactions cover. +type TransactionList struct { + XMLName xml.Name `xml:"BANKTRANLIST"` + DtStart Date `xml:"DTSTART"` // Start date for transaction data + DtEnd Date `xml:"DTEND"` // Value that client should send in next request to ensure that it does not miss any transactions + Transactions []Transaction `xml:"STMTTRN,omitempty"` +} + +// Valid returns (true, nil) if this struct is valid OFX +func (l TransactionList) Valid(version ofxVersion) (bool, error) { + var emptyDate Date + if l.DtStart.Equal(emptyDate) { + return false, errors.New("TransactionList.DtStart not filled") + } else if l.DtEnd.Equal(emptyDate) { + return false, errors.New("TransactionList.DtEnd not filled") + } + for _, t := range l.Transactions { + if ok, err := t.Valid(version); !ok { + return false, err + } + } + + return true, nil +} + +// PendingTransaction represents a single pending transaction. It is similar to +// Transaction, but is not finalized (and may never be). For instance, it lacks +// FiTID and DtPosted fields. +type PendingTransaction struct { + XMLName xml.Name `xml:"STMTTRNP"` + TrnType trnType `xml:"TRNTYPE"` // One of CREDIT, DEBIT, INT (interest earned or paid. Note: Depends on signage of amount), DIV, FEE, SRVCHG (service charge), DEP (deposit), ATM (Note: Depends on signage of amount), POS (Note: Depends on signage of amount), XFER, CHECK, PAYMENT, CASH, DIRECTDEP, DIRECTDEBIT, REPEATPMT, HOLD, OTHER + DtTran Date `xml:"DTTRAN"` + DtExpire *Date `xml:"DTEXPIRE,omitempty"` // only valid for TrnType==HOLD, the date the hold will expire + TrnAmt Amount `xml:"TRNAMT"` + RefNum String `xml:"REFNUM,omitempty"` + Name String `xml:"NAME,omitempty"` + ExtdName String `xml:"EXTDNAME,omitempty"` // Extended name of payee or transaction description + Memo String `xml:"MEMO,omitempty"` // Extra information (not in NAME) + ImageData []ImageData `xml:"IMAGEDATA,omitempty"` + + // Only one of Currency and OrigCurrency can ever be Valid() for the same transaction + Currency Currency `xml:"CURRENCY,omitempty"` // Represents the currency of TrnAmt (instead of CURDEF in STMTRS) if Valid + OrigCurrency Currency `xml:"ORIGCURRENCY,omitempty"` // Represents the currency TrnAmt was converted to STMTRS' CURDEF from if Valid +} + +// Valid returns (true, nil) if this struct is valid OFX +func (t PendingTransaction) Valid() (bool, error) { + var emptyDate Date + if !t.TrnType.Valid() { + return false, errors.New("PendingTransaction.TrnType invalid") + } else if t.DtTran.Equal(emptyDate) { + return false, errors.New("PendingTransaction.DtTran not filled") + } else if len(t.Name) == 0 { + return false, errors.New("PendingTransaction.Name empty") + } + ok1, _ := t.Currency.Valid() + ok2, _ := t.OrigCurrency.Valid() + if ok1 && ok2 { + return false, errors.New("Currency and OrigCurrency both supplied for Pending Transaction, only one allowed") + } + return true, nil +} + +// PendingTransactionList represents a list of pending transactions, along with +// the date they were generated +type PendingTransactionList struct { + XMLName xml.Name `xml:"BANKTRANLISTP"` + DtAsOf Date `xml:"DTASOF"` // Date and time this set of pending transactions was generated + Transactions []PendingTransaction `xml:"STMTTRNP,omitempty"` +} + +// Valid returns (true, nil) if this struct is valid OFX +func (l PendingTransactionList) Valid() (bool, error) { + var emptyDate Date + if l.DtAsOf.Equal(emptyDate) { + return false, errors.New("PendingTransactionList.DtAsOf not filled") + } + for _, t := range l.Transactions { + if ok, err := t.Valid(); !ok { + return false, err + } + } + + return true, nil +} + +// Balance represents a generic (free-form) balance defined by an FI. +type Balance struct { + XMLName xml.Name `xml:"BAL"` + Name String `xml:"NAME"` + Desc String `xml:"DESC"` + + // Balance type: + // DOLLAR = dollar (value formatted DDDD.cc) + // PERCENT = percentage (value formatted XXXX.YYYY) + // NUMBER = number (value formatted as is) + BalType balType `xml:"BALTYPE"` + + Value Amount `xml:"VALUE"` + DtAsOf *Date `xml:"DTASOF,omitempty"` + Currency *Currency `xml:"CURRENCY,omitempty"` // if BALTYPE is DOLLAR +} + +// Valid returns (true, nil) if this struct is valid OFX +func (b Balance) Valid() (bool, error) { + if len(b.Name) == 0 || len(b.Desc) == 0 { + return false, errors.New("Balance Name and Desc not supplied") + } + if !b.BalType.Valid() { + return false, errors.New("Balance BALTYPE not specified") + } + return true, nil +} + +// StatementResponse represents a bank account statement, including its +// balances and possibly transactions. It is a response to StatementRequest, or +// sometimes provided as part of an OFX file downloaded manually from an FI. +type StatementResponse struct { + XMLName xml.Name `xml:"STMTTRNRS"` + TrnUID UID `xml:"TRNUID"` + Status Status `xml:"STATUS"` + CltCookie String `xml:"CLTCOOKIE,omitempty"` + // TODO `xml:"OFXEXTENSION,omitempty"` + CurDef CurrSymbol `xml:"STMTRS>CURDEF"` + BankAcctFrom BankAcct `xml:"STMTRS>BANKACCTFROM"` + BankTranList *TransactionList `xml:"STMTRS>BANKTRANLIST,omitempty"` + BankTranListP *PendingTransactionList `xml:"STMTRS>BANKTRANLISTP,omitempty"` + BalAmt Amount `xml:"STMTRS>LEDGERBAL>BALAMT"` + DtAsOf Date `xml:"STMTRS>LEDGERBAL>DTASOF"` + AvailBalAmt *Amount `xml:"STMTRS>AVAILBAL>BALAMT,omitempty"` + AvailDtAsOf *Date `xml:"STMTRS>AVAILBAL>DTASOF,omitempty"` + CashAdvBalAmt *Amount `xml:"STMTRS>CASHADVBALAMT,omitempty"` // Only for CREDITLINE accounts, available balance for cash advances + IntRate *Amount `xml:"STMTRS>INTRATE,omitempty"` // Current interest rate + BalList []Balance `xml:"STMTRS>BALLIST>BAL,omitempty"` + MktgInfo String `xml:"STMTRS>MKTGINFO,omitempty"` // Marketing information +} + +// Name returns the name of the top-level transaction XML/SGML element +func (sr *StatementResponse) Name() string { + return "STMTTRNRS" +} + +// Valid returns (true, nil) if this struct was valid OFX when unmarshalled +func (sr *StatementResponse) Valid(version ofxVersion) (bool, error) { + var emptyDate Date + if ok, err := sr.TrnUID.Valid(); !ok { + return false, err + } else if ok, err := sr.Status.Valid(); !ok { + return false, err + } else if ok, err := sr.CurDef.Valid(); !ok { + return false, err + } else if ok, err := sr.BankAcctFrom.Valid(); !ok { + return false, err + } else if sr.DtAsOf.Equal(emptyDate) { + return false, errors.New("StatementResponse.DtAsOf not filled") + } else if (sr.AvailBalAmt == nil) != (sr.AvailDtAsOf == nil) { + return false, errors.New("StatementResponse.Avail* must both either be present or absent") + } + if sr.BankTranList != nil { + if ok, err := sr.BankTranList.Valid(version); !ok { + return false, err + } + } + if sr.BankTranListP != nil { + if version < OfxVersion220 { + return false, errors.New("StatementResponse.BankTranListP invalid for OFX < 2.2") + } + if ok, err := sr.BankTranListP.Valid(); !ok { + return false, err + } + } + for _, bal := range sr.BalList { + if ok, err := bal.Valid(); !ok { + return false, err + } + } + return true, nil +} + +// Type returns which message set this message belongs to (which Response +// element of type []Message it belongs to) +func (sr *StatementResponse) Type() messageType { + return BankRs +} diff --git a/vendor/github.com/aclindsa/ofxgo/basic_client.go b/vendor/github.com/aclindsa/ofxgo/basic_client.go new file mode 100644 index 0000000..c79c2c0 --- /dev/null +++ b/vendor/github.com/aclindsa/ofxgo/basic_client.go @@ -0,0 +1,104 @@ +package ofxgo + +import ( + "errors" + "io" + "net/http" + "strings" +) + +// BasicClient provides a standard Client implementation suitable for most +// financial institutions. BasicClient uses default, non-zero settings, even if +// its fields are not initialized. +type BasicClient struct { + // Request fields to overwrite with the client's values. If nonempty, + // defaults are used + SpecVersion ofxVersion // VERSION in header + AppID string // SONRQ>APPID + AppVer string // SONRQ>APPVER + + // Don't insert newlines or indentation when marshalling to SGML/XML + NoIndent bool + // Use carriage returns on new lines + CarriageReturn bool + // Set User-Agent header to this string, if not empty + UserAgent string +} + +// OfxVersion returns the OFX specification version this BasicClient will marshal +// Requests as. Defaults to "203" if the client's SpecVersion field is empty. +func (c *BasicClient) OfxVersion() ofxVersion { + if c.SpecVersion.Valid() { + return c.SpecVersion + } + return OfxVersion203 +} + +// ID returns this BasicClient's OFX AppID field, defaulting to "OFXGO" if +// unspecified. +func (c *BasicClient) ID() String { + if len(c.AppID) > 0 { + return String(c.AppID) + } + return String("OFXGO") +} + +// Version returns this BasicClient's version number as a string, defaulting to +// "0001" if unspecified. +func (c *BasicClient) Version() String { + if len(c.AppVer) > 0 { + return String(c.AppVer) + } + return String("0001") +} + +// IndentRequests returns true if the marshaled XML should be indented (and +// contain newlines, since the two are linked in the current implementation) +func (c *BasicClient) IndentRequests() bool { + return !c.NoIndent +} + +// CarriageReturnNewLines returns true if carriage returns should be used on new lines, false otherwise +func (c *BasicClient) CarriageReturnNewLines() bool { + return c.CarriageReturn +} + +// RawRequest is a convenience wrapper around http.Post. It is exposed only for +// when you need to read/inspect the raw HTTP response yourself. +func (c *BasicClient) RawRequest(URL string, r io.Reader) (*http.Response, error) { + if !strings.HasPrefix(URL, "https://") { + return nil, errors.New("Refusing to send OFX request with possible plain-text password over non-https protocol") + } + + request, err := http.NewRequest("POST", URL, r) + if err != nil { + return nil, err + } + request.Header.Set("Content-Type", "application/x-ofx") + request.Header.Add("Accept", "*/*, application/x-ofx") + if c.UserAgent != "" { + request.Header.Set("User-Agent", c.UserAgent) + } + response, err := http.DefaultClient.Do(request) + if err != nil { + return nil, err + } + + if response.StatusCode != 200 { + return response, errors.New("OFXQuery request status: " + response.Status) + } + + return response, nil +} + +// RequestNoParse marshals a Request to XML, makes an HTTP request, and returns +// the raw HTTP response +func (c *BasicClient) RequestNoParse(r *Request) (*http.Response, error) { + return clientRequestNoParse(c, r) +} + +// Request marshals a Request to XML, makes an HTTP request, and then +// unmarshals the response into a Response object. +func (c *BasicClient) Request(r *Request) (*Response, error) { + return clientRequest(c, r) +} diff --git a/vendor/github.com/aclindsa/ofxgo/client.go b/vendor/github.com/aclindsa/ofxgo/client.go new file mode 100644 index 0000000..a9d43dd --- /dev/null +++ b/vendor/github.com/aclindsa/ofxgo/client.go @@ -0,0 +1,105 @@ +package ofxgo + +import ( + "io" + "net/http" + "strings" +) + +// Client serves to aggregate OFX client settings that may be necessary to talk +// to a particular server due to quirks in that server's implementation. +// Client also provides the Request and RequestNoParse helper methods to aid in +// making and parsing requests. +type Client interface { + // Used to fill out a Request object + OfxVersion() ofxVersion + ID() String + Version() String + IndentRequests() bool + CarriageReturnNewLines() bool + + // Request marshals a Request object into XML, makes an HTTP request + // against it's URL, and then unmarshals the response into a Response + // object. + // + // Before being marshaled, some of the the Request object's values are + // overwritten, namely those dictated by the BasicClient's configuration + // (Version, AppID, AppVer fields), and the client's current time + // (DtClient). These are updated in place in the supplied Request object so + // they may later be inspected by the caller. + Request(r *Request) (*Response, error) + + // RequestNoParse marshals a Request object into XML, makes an HTTP + // request, and returns the raw HTTP response. Unlike RawRequest(), it + // takes client settings into account. Unlike Request(), it doesn't parse + // the response into an ofxgo.Request object. + // + // Caveat: The caller is responsible for closing the http Response.Body + // (see the http module's documentation for more information) + RequestNoParse(r *Request) (*http.Response, error) + + // RawRequest is little more than a thin wrapper around http.Post + // + // In most cases, you should probably be using Request() instead, but + // RawRequest can be useful if you need to read the raw unparsed http + // response yourself (perhaps for downloading an OFX file for use by an + // external program, or debugging server behavior), or have a handcrafted + // request you'd like to try. + // + // Caveats: RawRequest does *not* take client settings into account as + // Client.Request() does, so your particular server may or may not like + // whatever we read from 'r'. The caller is responsible for closing the + // http Response.Body (see the http module's documentation for more + // information) + RawRequest(URL string, r io.Reader) (*http.Response, error) +} + +type clientCreationFunc func(*BasicClient) Client + +// GetClient returns a new Client for a given URL. It attempts to find a +// specialized client for this URL, but simply returns the passed-in +// BasicClient if no such match is found. +func GetClient(URL string, bc *BasicClient) Client { + clients := []struct { + URL string + Func clientCreationFunc + }{ + {"https://ofx.discovercard.com", NewDiscoverCardClient}, + {"https://vesnc.vanguard.com/us/OfxDirectConnectServlet", NewVanguardClient}, + } + for _, client := range clients { + if client.URL == strings.Trim(URL, "/") { + return client.Func(bc) + } + } + return bc +} + +// clientRequestNoParse can be used for building clients' RequestNoParse +// methods if they require fairly standard behavior +func clientRequestNoParse(c Client, r *Request) (*http.Response, error) { + r.SetClientFields(c) + + b, err := r.Marshal() + if err != nil { + return nil, err + } + + return c.RawRequest(r.URL, b) +} + +// clientRequest can be used for building clients' Request methods if they +// require fairly standard behavior +func clientRequest(c Client, r *Request) (*Response, error) { + response, err := c.RequestNoParse(r) + if err != nil { + return nil, err + } + defer response.Body.Close() + + ofxresp, err := ParseResponse(response.Body) + if err != nil { + return nil, err + } + return ofxresp, nil +} diff --git a/vendor/github.com/aclindsa/ofxgo/common.go b/vendor/github.com/aclindsa/ofxgo/common.go new file mode 100644 index 0000000..bfbaeb3 --- /dev/null +++ b/vendor/github.com/aclindsa/ofxgo/common.go @@ -0,0 +1,372 @@ +package ofxgo + +//go:generate ./generate_constants.py + +import ( + "bytes" + "errors" + "fmt" + "strings" + + "github.com/aclindsa/xml" +) + +func writeHeader(b *bytes.Buffer, v ofxVersion, carriageReturn bool) error { + // Write the header appropriate to our version + switch v { + case OfxVersion102, OfxVersion103, OfxVersion151, OfxVersion160: + header := `OFXHEADER:100 +DATA:OFXSGML +VERSION:` + v.String() + ` +SECURITY:NONE +ENCODING:USASCII +CHARSET:1252 +COMPRESSION:NONE +OLDFILEUID:NONE +NEWFILEUID:NONE + +` + if carriageReturn { + header = strings.Replace(header, "\n", "\r\n", -1) + } + b.WriteString(header) + case OfxVersion200, OfxVersion201, OfxVersion202, OfxVersion203, OfxVersion210, OfxVersion211, OfxVersion220: + b.WriteString(``) + if carriageReturn { + b.WriteByte('\r') + } + b.WriteByte('\n') + b.WriteString(``) + if carriageReturn { + b.WriteByte('\r') + } + b.WriteByte('\n') + default: + return fmt.Errorf("%d is not a valid OFX version string", v) + } + return nil +} + +// Message represents an OFX message in a message set. it is used to ease +// marshalling and unmarshalling. +type Message interface { + Name() string // The name of the OFX transaction wrapper element this represents + Valid(version ofxVersion) (bool, error) // Called before a Message is marshaled and after it's unmarshaled to ensure the request or response is valid + Type() messageType // The message set this message belongs to +} + +type messageType uint + +// These constants are returned by Messages' Type() functions to determine +// which message set they belong to +const ( + // Requests + SignonRq messageType = iota + SignupRq + BankRq + CreditCardRq + LoanRq + InvStmtRq + InterXferRq + WireXferRq + BillpayRq + EmailRq + SecListRq + PresDirRq + PresDlvRq + ProfRq + ImageRq + + // Responses + SignonRs + SignupRs + BankRs + CreditCardRs + LoanRs + InvStmtRs + InterXferRs + WireXferRs + BillpayRs + EmailRs + SecListRs + PresDirRs + PresDlvRs + ProfRs + ImageRs +) + +func (t messageType) String() string { + switch t { + case SignonRq: + return "SIGNONMSGSRQV1" + case SignupRq: + return "SIGNUPMSGSRQV1" + case BankRq: + return "BANKMSGSRQV1" + case CreditCardRq: + return "CREDITCARDMSGSRQV1" + case LoanRq: + return "LOANMSGSRQV1" + case InvStmtRq: + return "INVSTMTMSGSRQV1" + case InterXferRq: + return "INTERXFERMSGSRQV1" + case WireXferRq: + return "WIREXFERMSGSRQV1" + case BillpayRq: + return "BILLPAYMSGSRQV1" + case EmailRq: + return "EMAILMSGSRQV1" + case SecListRq: + return "SECLISTMSGSRQV1" + case PresDirRq: + return "PRESDIRMSGSRQV1" + case PresDlvRq: + return "PRESDLVMSGSRQV1" + case ProfRq: + return "PROFMSGSRQV1" + case ImageRq: + return "IMAGEMSGSRQV1" + case SignonRs: + return "SIGNONMSGSRSV1" + case SignupRs: + return "SIGNUPMSGSRSV1" + case BankRs: + return "BANKMSGSRSV1" + case CreditCardRs: + return "CREDITCARDMSGSRSV1" + case LoanRs: + return "LOANMSGSRSV1" + case InvStmtRs: + return "INVSTMTMSGSRSV1" + case InterXferRs: + return "INTERXFERMSGSRSV1" + case WireXferRs: + return "WIREXFERMSGSRSV1" + case BillpayRs: + return "BILLPAYMSGSRSV1" + case EmailRs: + return "EMAILMSGSRSV1" + case SecListRs: + return "SECLISTMSGSRSV1" + case PresDirRs: + return "PRESDIRMSGSRSV1" + case PresDlvRs: + return "PRESDLVMSGSRSV1" + case ProfRs: + return "PROFMSGSRSV1" + case ImageRs: + return "IMAGEMSGSRSV1" + } + panic("Invalid messageType") +} + +// Map of error codes to their meanings, SEVERITY, and conditions under which +// OFX servers are expected to return them +var statusMeanings = map[Int][3]string{ + 0: {"Success", "INFO", "The server successfully processed the request."}, + 1: {"Client is up-to-date", "INFO", "Based on the client timestamp, the client has the latest information. The response does not supply any additional information."}, + 2000: {"General error", "ERROR", "Error other than those specified by the remaining error codes. Note: Servers should provide a more specific error whenever possible. Error code 2000 should be reserved for cases in which a more specific code is not available."}, + 2001: {"Invalid account", "ERROR", ""}, + 2002: {"General account error", "ERROR", "Account error not specified by the remaining error codes."}, + 2003: {"Account not found", "ERROR", "The specified account number does not correspond to one of the user’s accounts."}, + 2004: {"Account closed", "ERROR", "The specified account number corresponds to an account that has been closed."}, + 2005: {"Account not authorized", "ERROR", "The user is not authorized to perform this action on the account, or the server does not allow this type of action to be performed on the account."}, + 2006: {"Source account not found", "ERROR", "The specified account number does not correspond to one of the user’s accounts."}, + 2007: {"Source account closed", "ERROR", "The specified account number corresponds to an account that has been closed."}, + 2008: {"Source account not authorized", "ERROR", "The user is not authorized to perform this action on the account, or the server does not allow this type of action to be performed on the account."}, + 2009: {"Destination account not found", "ERROR", "The specified account number does not correspond to one of the user’s accounts."}, + 2010: {"Destination account closed", "ERROR", "The specified account number corresponds to an account that has been closed."}, + 2011: {"Destination account not authorized", "ERROR", "The user is not authorized to perform this action on the account, or the server does not allow this type of action to be performed on the account."}, + 2012: {"Invalid amount", "ERROR", "The specified amount is not valid for this action; for example, the user specified a negative payment amount."}, + 2014: {"Date too soon", "ERROR", "The server cannot process the requested action by the date specified by the user."}, + 2015: {"Date too far in future", "ERROR", "The server cannot accept requests for an action that far in the future."}, + 2016: {"Transaction already committed", "ERROR", "Transaction has entered the processing loop and cannot be modified/cancelled using OFX. The transaction may still be cancelled or modified using other means (for example, a phone call to Customer Service)."}, + 2017: {"Already canceled", "ERROR", "The transaction cannot be canceled or modified because it has already been canceled."}, + 2018: {"Unknown server ID", "ERROR", "The specified server ID does not exist or no longer exists."}, + 2019: {"Duplicate request", "ERROR", "A request with this has already been received and processed."}, + 2020: {"Invalid date", "ERROR", "The specified datetime stamp cannot be parsed; for instance, the datetime stamp specifies 25:00 hours."}, + 2021: {"Unsupported version", "ERROR", "The server does not support the requested version. The version of the message set specified by the client is not supported by this server."}, + 2022: {"Invalid TAN", "ERROR", "The server was unable to validate the TAN sent in the request."}, + 2023: {"Unknown FITID", "ERROR", "The specified FITID/BILLID does not exist or no longer exists. [BILLID not found (ERROR) in the billing message sets]"}, + 2025: {"Branch ID missing", "ERROR", "A value must be provided in the aggregate for this country system, but this field is missing."}, + 2026: {"Bank name doesn’t match bank ID", "ERROR", "The value of in the aggregate is inconsistent with the value of in the aggregate."}, + 2027: {"Invalid date range", "ERROR", "Response for non-overlapping dates, date ranges in the future, et cetera."}, + 2028: {"Requested element unknown", "WARN", "One or more elements of the request were not recognized by the server or the server (as noted in the FI Profile) does not support the elements. The server executed the element transactions it understood and supported. For example, the request file included private tags in a but the server was able to execute the rest of the request."}, + 3000: {"MFA Challenge authentication required", "ERROR", "User credentials are correct, but further authentication required. Client should send in next request."}, + 3001: {"MFA Challenge information is invalid", "ERROR", "User or client information sent in MFACHALLENGEA contains invalid information"}, + 6500: {"Y invalid without ", "ERROR", "This error code may appear in the element of an wrapper (in and V2 message set responses) or the contained in any embedded transaction wrappers within a sync response. The corresponding sync request wrapper included Y with Y or Y, which is illegal."}, + 6501: {"Embedded transactions in request failed to process: Out of date", "WARN", "Y and embedded transactions appeared in the request sync wrapper and the provided was out of date. This code should be used in the of the response sync wrapper."}, + 6502: {"Unable to process embedded transaction due to out-of-date ", "ERROR", "Used in response transaction wrapper for embedded transactions when 6501 appears in the surrounding sync wrapper."}, + 10000: {"Stop check in process", "INFO", "Stop check is already in process."}, + 10500: {"Too many checks to process", "ERROR", "The stop-payment request specifies too many checks."}, + 10501: {"Invalid payee", "ERROR", "Payee error not specified by the remaining error codes."}, + 10502: {"Invalid payee address", "ERROR", "Some portion of the payee’s address is incorrect or unknown."}, + 10503: {"Invalid payee account number", "ERROR", "The account number of the requested payee is invalid."}, + 10504: {"Insufficient funds", "ERROR", "The server cannot process the request because the specified account does not have enough funds."}, + 10505: {"Cannot modify element", "ERROR", "The server does not allow modifications to one or more values in a modification request."}, + 10506: {"Cannot modify source account", "ERROR", "Reserved for future use."}, + 10507: {"Cannot modify destination account", "ERROR", "Reserved for future use."}, + 10508: {"Invalid frequency", "ERROR", "The specified frequency does not match one of the accepted frequencies for recurring transactions."}, + 10509: {"Model already canceled", "ERROR", "The server has already canceled the specified recurring model."}, + 10510: {"Invalid payee ID", "ERROR", "The specified payee ID does not exist or no longer exists."}, + 10511: {"Invalid payee city", "ERROR", "The specified city is incorrect or unknown."}, + 10512: {"Invalid payee state", "ERROR", "The specified state is incorrect or unknown."}, + 10513: {"Invalid payee postal code", "ERROR", "The specified postal code is incorrect or unknown."}, + 10514: {"Transaction already processed", "ERROR", "Transaction has already been sent or date due is past"}, + 10515: {"Payee not modifiable by client", "ERROR", "The server does not allow clients to change payee information."}, + 10516: {"Wire beneficiary invalid", "ERROR", "The specified wire beneficiary does not exist or no longer exists."}, + 10517: {"Invalid payee name", "ERROR", "The server does not recognize the specified payee name."}, + 10518: {"Unknown model ID", "ERROR", "The specified model ID does not exist or no longer exists."}, + 10519: {"Invalid payee list ID", "ERROR", "The specified payee list ID does not exist or no longer exists."}, + 10600: {"Table type not found", "ERROR", "The specified table type is not recognized or does not exist."}, + 12250: {"Investment transaction download not supported", "WARN", "The server does not support investment transaction download."}, + 12251: {"Investment position download not supported", "WARN", "The server does not support investment position download."}, + 12252: {"Investment positions for specified date not available", "WARN", "The server does not support investment positions for the specified date."}, + 12253: {"Investment open order download not supported", "WARN", "The server does not support open order download."}, + 12254: {"Investment balances download not supported", "WARN", "The server does not support investment balances download."}, + 12255: {"401(k) not available for this account", "ERROR", "401(k) information requested from a non- 401(k) account."}, + 12500: {"One or more securities not found", "ERROR", "The server could not find the requested securities."}, + 13000: {"User ID & password will be sent out-of-band", "INFO", "The server will send the user ID and password via postal mail, e-mail, or another means. The accompanying message will provide details."}, + 13500: {"Unable to enroll user", "ERROR", "The server could not enroll the user."}, + 13501: {"User already enrolled", "ERROR", "The server has already enrolled the user."}, + 13502: {"Invalid service", "ERROR", "The server does not support the service specified in the service-activation request."}, + 13503: {"Cannot change user information", "ERROR", "The server does not support the request."}, + 13504: {" Missing or Invalid in ", "ERROR", "The FI requires the client to provide the aggregate in the request, but either none was provided, or the one provided was invalid."}, + 14500: {"1099 forms not available", "ERROR", "1099 forms are not yet available for the tax year requested."}, + 14501: {"1099 forms not available for user ID", "ERROR", "This user does not have any 1099 forms available."}, + 14600: {"W2 forms not available", "ERROR", "W2 forms are not yet available for the tax year requested."}, + 14601: {"W2 forms not available for user ID", "ERROR", "The user does not have any W2 forms available."}, + 14700: {"1098 forms not available", "ERROR", "1098 forms are not yet available for the tax year requested."}, + 14701: {"1098 forms not available for user ID", "ERROR", "The user does not have any 1098 forms available."}, + 15000: {"Must change USERPASS", "INFO", "The user must change his or her number as part of the next OFX request."}, + 15500: {"Signon invalid", "ERROR", "The user cannot signon because he or she entered an invalid user ID or password."}, + 15501: {"Customer account already in use", "ERROR", "The server allows only one connection at a time, and another user is already signed on. Please try again later."}, + 15502: {"USERPASS lockout", "ERROR", "The server has received too many failed signon attempts for this user. Please call the FI’s technical support number."}, + 15503: {"Could not change USERPASS", "ERROR", "The server does not support the request."}, + 15504: {"Could not provide random data", "ERROR", "The server could not generate random data as requested by the ."}, + 15505: {"Country system not supported", "ERROR", "The server does not support the country specified in the field of the aggregate."}, + 15506: {"Empty signon not supported", "ERROR", "The server does not support signons not accompanied by some other transaction."}, + 15507: {"Signon invalid without supporting pin change request", "ERROR", "The OFX block associated with the signon does not contain a pin change request and should."}, + 15508: {"Transaction not authorized. ", "ERROR", "Current user is not authorized to perform this action on behalf of the ."}, + 15510: {"CLIENTUID error", "ERROR", "The CLIENTUID sent by the client was incorrect. User must register the Client UID."}, + 15511: {"MFA error", "ERROR", "User should contact financial institution."}, + 15512: {"AUTHTOKEN required", "ERROR", "User needs to contact financial institution to obtain AUTHTOKEN. Client should send it in the next request."}, + 15513: {"AUTHTOKEN invalid", "ERROR", "The AUTHTOKEN sent by the client was invalid."}, + 16500: {"HTML not allowed", "ERROR", "The server does not accept HTML formatting in the request."}, + 16501: {"Unknown mail To:", "ERROR", "The server was unable to send mail to the specified Internet address."}, + 16502: {"Invalid URL", "ERROR", "The server could not parse the URL."}, + 16503: {"Unable to get URL", "ERROR", "The server was unable to retrieve the information at this URL (e.g., an HTTP 400 or 500 series error)."}, +} + +// Status represents the status of a Response (both top-level Request objects, +// and *Response objects) +type Status struct { + XMLName xml.Name `xml:"STATUS"` + Code Int `xml:"CODE"` + Severity String `xml:"SEVERITY"` + Message String `xml:"MESSAGE,omitempty"` +} + +// Valid returns whether the Status is valid according to the OFX spec +func (s *Status) Valid() (bool, error) { + switch s.Severity { + case "INFO", "WARN", "ERROR": + default: + return false, errors.New("Invalid STATUS>SEVERITY") + } + + if arr, ok := statusMeanings[s.Code]; ok { + if arr[1] != string(s.Severity) { + return false, errors.New("Unexpected SEVERITY for STATUS>CODE") + } + } else { + return false, errors.New("Unknown OFX status code") + } + + return true, nil +} + +// CodeMeaning returns the meaning of the current status Code +func (s *Status) CodeMeaning() (string, error) { + if arr, ok := statusMeanings[s.Code]; ok { + return arr[0], nil + } + return "", errors.New("Unknown OFX status code") +} + +// CodeConditions returns the conditions under which an OFX server is expected +// to return the current status Code +func (s *Status) CodeConditions() (string, error) { + if arr, ok := statusMeanings[s.Code]; ok { + return arr[2], nil + } + return "", errors.New("Unknown OFX status code") +} + +// BankAcct represents the identifying information for one bank account +type BankAcct struct { + XMLName xml.Name // BANKACCTTO or BANKACCTFROM + BankID String `xml:"BANKID"` + BranchID String `xml:"BRANCHID,omitempty"` // Unused in USA + AcctID String `xml:"ACCTID"` + AcctType acctType `xml:"ACCTTYPE"` // One of CHECKING, SAVINGS, MONEYMRKT, CREDITLINE, CD + AcctKey String `xml:"ACCTKEY,omitempty"` // Unused in USA +} + +// Valid returns whether the BankAcct is valid according to the OFX spec +func (b BankAcct) Valid() (bool, error) { + if len(b.BankID) == 0 { + return false, errors.New("BankAcct.BankID empty") + } + if len(b.AcctID) == 0 { + return false, errors.New("BankAcct.AcctID empty") + } + if !b.AcctType.Valid() { + return false, errors.New("Invalid or unspecified BankAcct.AcctType") + } + return true, nil +} + +// CCAcct represents the identifying information for one checking account +type CCAcct struct { + XMLName xml.Name // CCACCTTO or CCACCTFROM + AcctID String `xml:"ACCTID"` + AcctKey String `xml:"ACCTKEY,omitempty"` // Unused in USA +} + +// Valid returns whether the CCAcct is valid according to the OFX spec +func (c CCAcct) Valid() (bool, error) { + if len(c.AcctID) == 0 { + return false, errors.New("CCAcct.AcctID empty") + } + return true, nil +} + +// InvAcct represents the identifying information for one investment account +type InvAcct struct { + XMLName xml.Name // INVACCTTO or INVACCTFROM + BrokerID String `xml:"BROKERID"` + AcctID String `xml:"ACCTID"` +} + +// Currency represents one ISO-4217 currency. CURRENCY elements signify that +// the transaction containing this Currency struct is in this currency instead +// of being converted to the statement's default. ORIGCURRENCY elements signify +// that the transaction containing this Currency struct was converted to the +// statement's default from the specified currency. +type Currency struct { + XMLName xml.Name // CURRENCY or ORIGCURRENCY + CurRate Amount `xml:"CURRATE"` // Ratio of statement's currency (CURDEF) to transaction currency (CURSYM) + CurSym CurrSymbol `xml:"CURSYM"` // ISO-4217 3-character currency identifier +} + +// Valid returns whether the Currency is valid according to the OFX spec +func (c Currency) Valid() (bool, error) { + if c.CurRate.IsInt() && c.CurRate.Num().Int64() == 0 { + return false, errors.New("CurRate may not be zero") + } else if ok, err := c.CurSym.Valid(); !ok { + return false, err + } + return true, nil +} diff --git a/vendor/github.com/aclindsa/ofxgo/constants.go b/vendor/github.com/aclindsa/ofxgo/constants.go new file mode 100644 index 0000000..6233d5f --- /dev/null +++ b/vendor/github.com/aclindsa/ofxgo/constants.go @@ -0,0 +1,2703 @@ +package ofxgo + +/* + * Do not edit this file by hand. It is auto-generated by calling `go generate`. + * To make changes, edit generate_constants.py, re-run `go generate`, and check + * in the result. + */ + +import ( + "errors" + "fmt" + "strings" + + "github.com/aclindsa/xml" +) + +type ofxVersion uint + +// OfxVersion* constants represent the OFX specification version in use +const ( + OfxVersion102 ofxVersion = 1 + iota + OfxVersion103 + OfxVersion151 + OfxVersion160 + OfxVersion200 + OfxVersion201 + OfxVersion202 + OfxVersion203 + OfxVersion210 + OfxVersion211 + OfxVersion220 +) + +var ofxVersions = [...]string{"102", "103", "151", "160", "200", "201", "202", "203", "210", "211", "220"} + +func (e ofxVersion) Valid() bool { + // This check is mostly out of paranoia, ensuring e != 0 should be + // sufficient + return e >= OfxVersion102 && e <= OfxVersion220 +} + +func (e ofxVersion) String() string { + if e.Valid() { + return ofxVersions[e-1] + } + return fmt.Sprintf("invalid ofxVersion (%d)", e) +} + +func (e *ofxVersion) FromString(in string) error { + value := strings.TrimSpace(in) + + for i, s := range ofxVersions { + if s == value { + *e = ofxVersion(i + 1) + return nil + } + } + *e = 0 + return errors.New("Invalid OfxVersion: \"" + in + "\"") +} + +func (e *ofxVersion) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + var value string + err := d.DecodeElement(&value, &start) + if err != nil { + return err + } + + return e.FromString(value) +} + +func (e *ofxVersion) MarshalXML(enc *xml.Encoder, start xml.StartElement) error { + if !e.Valid() { + return nil + } + enc.EncodeElement(ofxVersions[*e-1], start) + return nil +} + +// NewOfxVersion returns returns an 'enum' value of type ofxVersion given its +// string representation +func NewOfxVersion(s string) (ofxVersion, error) { + var e ofxVersion + err := e.FromString(s) + if err != nil { + return 0, err + } + return e, nil +} + +type acctType uint + +// AcctType* constants represent types of bank accounts +const ( + AcctTypeChecking acctType = 1 + iota + AcctTypeSavings + AcctTypeMoneyMrkt + AcctTypeCreditLine + AcctTypeCD +) + +var acctTypes = [...]string{"CHECKING", "SAVINGS", "MONEYMRKT", "CREDITLINE", "CD"} + +func (e acctType) Valid() bool { + // This check is mostly out of paranoia, ensuring e != 0 should be + // sufficient + return e >= AcctTypeChecking && e <= AcctTypeCD +} + +func (e acctType) String() string { + if e.Valid() { + return acctTypes[e-1] + } + return fmt.Sprintf("invalid acctType (%d)", e) +} + +func (e *acctType) FromString(in string) error { + value := strings.TrimSpace(in) + + for i, s := range acctTypes { + if s == value { + *e = acctType(i + 1) + return nil + } + } + *e = 0 + return errors.New("Invalid AcctType: \"" + in + "\"") +} + +func (e *acctType) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + var value string + err := d.DecodeElement(&value, &start) + if err != nil { + return err + } + + return e.FromString(value) +} + +func (e *acctType) MarshalXML(enc *xml.Encoder, start xml.StartElement) error { + if !e.Valid() { + return nil + } + enc.EncodeElement(acctTypes[*e-1], start) + return nil +} + +// NewAcctType returns returns an 'enum' value of type acctType given its +// string representation +func NewAcctType(s string) (acctType, error) { + var e acctType + err := e.FromString(s) + if err != nil { + return 0, err + } + return e, nil +} + +type trnType uint + +// TrnType* constants represent types of transactions. INT, ATM, and POS depend on the signage of the account. +const ( + TrnTypeCredit trnType = 1 + iota + TrnTypeDebit + TrnTypeInt + TrnTypeDiv + TrnTypeFee + TrnTypeSrvChg + TrnTypeDep + TrnTypeATM + TrnTypePOS + TrnTypeXfer + TrnTypeCheck + TrnTypePayment + TrnTypeCash + TrnTypeDirectDep + TrnTypeDirectDebit + TrnTypeRepeatPmt + TrnTypeHold + TrnTypeOther +) + +var trnTypes = [...]string{"CREDIT", "DEBIT", "INT", "DIV", "FEE", "SRVCHG", "DEP", "ATM", "POS", "XFER", "CHECK", "PAYMENT", "CASH", "DIRECTDEP", "DIRECTDEBIT", "REPEATPMT", "HOLD", "OTHER"} + +func (e trnType) Valid() bool { + // This check is mostly out of paranoia, ensuring e != 0 should be + // sufficient + return e >= TrnTypeCredit && e <= TrnTypeOther +} + +func (e trnType) String() string { + if e.Valid() { + return trnTypes[e-1] + } + return fmt.Sprintf("invalid trnType (%d)", e) +} + +func (e *trnType) FromString(in string) error { + value := strings.TrimSpace(in) + + for i, s := range trnTypes { + if s == value { + *e = trnType(i + 1) + return nil + } + } + *e = 0 + return errors.New("Invalid TrnType: \"" + in + "\"") +} + +func (e *trnType) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + var value string + err := d.DecodeElement(&value, &start) + if err != nil { + return err + } + + return e.FromString(value) +} + +func (e *trnType) MarshalXML(enc *xml.Encoder, start xml.StartElement) error { + if !e.Valid() { + return nil + } + enc.EncodeElement(trnTypes[*e-1], start) + return nil +} + +// NewTrnType returns returns an 'enum' value of type trnType given its +// string representation +func NewTrnType(s string) (trnType, error) { + var e trnType + err := e.FromString(s) + if err != nil { + return 0, err + } + return e, nil +} + +type imageType uint + +// ImageType* constants represent what this image contains +const ( + ImageTypeStatement imageType = 1 + iota + ImageTypeTransaction + ImageTypeTax +) + +var imageTypes = [...]string{"STATEMENT", "TRANSACTION", "TAX"} + +func (e imageType) Valid() bool { + // This check is mostly out of paranoia, ensuring e != 0 should be + // sufficient + return e >= ImageTypeStatement && e <= ImageTypeTax +} + +func (e imageType) String() string { + if e.Valid() { + return imageTypes[e-1] + } + return fmt.Sprintf("invalid imageType (%d)", e) +} + +func (e *imageType) FromString(in string) error { + value := strings.TrimSpace(in) + + for i, s := range imageTypes { + if s == value { + *e = imageType(i + 1) + return nil + } + } + *e = 0 + return errors.New("Invalid ImageType: \"" + in + "\"") +} + +func (e *imageType) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + var value string + err := d.DecodeElement(&value, &start) + if err != nil { + return err + } + + return e.FromString(value) +} + +func (e *imageType) MarshalXML(enc *xml.Encoder, start xml.StartElement) error { + if !e.Valid() { + return nil + } + enc.EncodeElement(imageTypes[*e-1], start) + return nil +} + +// NewImageType returns returns an 'enum' value of type imageType given its +// string representation +func NewImageType(s string) (imageType, error) { + var e imageType + err := e.FromString(s) + if err != nil { + return 0, err + } + return e, nil +} + +type imageRefType uint + +// ImageRefType* constants represent the type of reference to the image +const ( + ImageRefTypeOpaque imageRefType = 1 + iota + ImageRefTypeURL + ImageRefTypeFormURL +) + +var imageRefTypes = [...]string{"OPAQUE", "URL", "FORMURL"} + +func (e imageRefType) Valid() bool { + // This check is mostly out of paranoia, ensuring e != 0 should be + // sufficient + return e >= ImageRefTypeOpaque && e <= ImageRefTypeFormURL +} + +func (e imageRefType) String() string { + if e.Valid() { + return imageRefTypes[e-1] + } + return fmt.Sprintf("invalid imageRefType (%d)", e) +} + +func (e *imageRefType) FromString(in string) error { + value := strings.TrimSpace(in) + + for i, s := range imageRefTypes { + if s == value { + *e = imageRefType(i + 1) + return nil + } + } + *e = 0 + return errors.New("Invalid ImageRefType: \"" + in + "\"") +} + +func (e *imageRefType) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + var value string + err := d.DecodeElement(&value, &start) + if err != nil { + return err + } + + return e.FromString(value) +} + +func (e *imageRefType) MarshalXML(enc *xml.Encoder, start xml.StartElement) error { + if !e.Valid() { + return nil + } + enc.EncodeElement(imageRefTypes[*e-1], start) + return nil +} + +// NewImageRefType returns returns an 'enum' value of type imageRefType given its +// string representation +func NewImageRefType(s string) (imageRefType, error) { + var e imageRefType + err := e.FromString(s) + if err != nil { + return 0, err + } + return e, nil +} + +type checkSup uint + +// CheckSup* constants represent what portions of the check this image contains +const ( + CheckSupFrontOnly checkSup = 1 + iota + CheckSupBackOnly + CheckSupFrontAndBack +) + +var checkSups = [...]string{"FRONTONLY", "BACKONLY", "FRONTANDBACK"} + +func (e checkSup) Valid() bool { + // This check is mostly out of paranoia, ensuring e != 0 should be + // sufficient + return e >= CheckSupFrontOnly && e <= CheckSupFrontAndBack +} + +func (e checkSup) String() string { + if e.Valid() { + return checkSups[e-1] + } + return fmt.Sprintf("invalid checkSup (%d)", e) +} + +func (e *checkSup) FromString(in string) error { + value := strings.TrimSpace(in) + + for i, s := range checkSups { + if s == value { + *e = checkSup(i + 1) + return nil + } + } + *e = 0 + return errors.New("Invalid CheckSup: \"" + in + "\"") +} + +func (e *checkSup) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + var value string + err := d.DecodeElement(&value, &start) + if err != nil { + return err + } + + return e.FromString(value) +} + +func (e *checkSup) MarshalXML(enc *xml.Encoder, start xml.StartElement) error { + if !e.Valid() { + return nil + } + enc.EncodeElement(checkSups[*e-1], start) + return nil +} + +// NewCheckSup returns returns an 'enum' value of type checkSup given its +// string representation +func NewCheckSup(s string) (checkSup, error) { + var e checkSup + err := e.FromString(s) + if err != nil { + return 0, err + } + return e, nil +} + +type correctAction uint + +// CorrectAction* constants represent whether this transaction correction replaces or deletes the transaction matching its CORRECTFITID +const ( + CorrectActionDelete correctAction = 1 + iota + CorrectActionReplace +) + +var correctActions = [...]string{"DELETE", "REPLACE"} + +func (e correctAction) Valid() bool { + // This check is mostly out of paranoia, ensuring e != 0 should be + // sufficient + return e >= CorrectActionDelete && e <= CorrectActionReplace +} + +func (e correctAction) String() string { + if e.Valid() { + return correctActions[e-1] + } + return fmt.Sprintf("invalid correctAction (%d)", e) +} + +func (e *correctAction) FromString(in string) error { + value := strings.TrimSpace(in) + + for i, s := range correctActions { + if s == value { + *e = correctAction(i + 1) + return nil + } + } + *e = 0 + return errors.New("Invalid CorrectAction: \"" + in + "\"") +} + +func (e *correctAction) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + var value string + err := d.DecodeElement(&value, &start) + if err != nil { + return err + } + + return e.FromString(value) +} + +func (e *correctAction) MarshalXML(enc *xml.Encoder, start xml.StartElement) error { + if !e.Valid() { + return nil + } + enc.EncodeElement(correctActions[*e-1], start) + return nil +} + +// NewCorrectAction returns returns an 'enum' value of type correctAction given its +// string representation +func NewCorrectAction(s string) (correctAction, error) { + var e correctAction + err := e.FromString(s) + if err != nil { + return 0, err + } + return e, nil +} + +type balType uint + +// BalType* constants represent how this BAL's VALUE field should be interpreted +const ( + BalTypeDollar balType = 1 + iota + BalTypePercent + BalTypeNumber +) + +var balTypes = [...]string{"DOLLAR", "PERCENT", "NUMBER"} + +func (e balType) Valid() bool { + // This check is mostly out of paranoia, ensuring e != 0 should be + // sufficient + return e >= BalTypeDollar && e <= BalTypeNumber +} + +func (e balType) String() string { + if e.Valid() { + return balTypes[e-1] + } + return fmt.Sprintf("invalid balType (%d)", e) +} + +func (e *balType) FromString(in string) error { + value := strings.TrimSpace(in) + + for i, s := range balTypes { + if s == value { + *e = balType(i + 1) + return nil + } + } + *e = 0 + return errors.New("Invalid BalType: \"" + in + "\"") +} + +func (e *balType) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + var value string + err := d.DecodeElement(&value, &start) + if err != nil { + return err + } + + return e.FromString(value) +} + +func (e *balType) MarshalXML(enc *xml.Encoder, start xml.StartElement) error { + if !e.Valid() { + return nil + } + enc.EncodeElement(balTypes[*e-1], start) + return nil +} + +// NewBalType returns returns an 'enum' value of type balType given its +// string representation +func NewBalType(s string) (balType, error) { + var e balType + err := e.FromString(s) + if err != nil { + return 0, err + } + return e, nil +} + +type inv401kSource uint + +// Inv401kSource* constants represent the source of money used for this security in a 401(k) account. Default if not present is OTHERNONVEST. The following cash source types are subject to vesting: MATCH, PROFITSHARING, and OTHERVEST. +const ( + Inv401kSourcePreTax inv401kSource = 1 + iota + Inv401kSourceAfterTax + Inv401kSourceMatch + Inv401kSourceProfitSharing + Inv401kSourceRollover + Inv401kSourceOtherVest + Inv401kSourceOtherNonVest +) + +var inv401kSources = [...]string{"PRETAX", "AFTERTAX", "MATCH", "PROFITSHARING", "ROLLOVER", "OTHERVEST", "OTHERNONVEST"} + +func (e inv401kSource) Valid() bool { + // This check is mostly out of paranoia, ensuring e != 0 should be + // sufficient + return e >= Inv401kSourcePreTax && e <= Inv401kSourceOtherNonVest +} + +func (e inv401kSource) String() string { + if e.Valid() { + return inv401kSources[e-1] + } + return fmt.Sprintf("invalid inv401kSource (%d)", e) +} + +func (e *inv401kSource) FromString(in string) error { + value := strings.TrimSpace(in) + + for i, s := range inv401kSources { + if s == value { + *e = inv401kSource(i + 1) + return nil + } + } + *e = 0 + return errors.New("Invalid Inv401kSource: \"" + in + "\"") +} + +func (e *inv401kSource) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + var value string + err := d.DecodeElement(&value, &start) + if err != nil { + return err + } + + return e.FromString(value) +} + +func (e *inv401kSource) MarshalXML(enc *xml.Encoder, start xml.StartElement) error { + if !e.Valid() { + return nil + } + enc.EncodeElement(inv401kSources[*e-1], start) + return nil +} + +// NewInv401kSource returns returns an 'enum' value of type inv401kSource given its +// string representation +func NewInv401kSource(s string) (inv401kSource, error) { + var e inv401kSource + err := e.FromString(s) + if err != nil { + return 0, err + } + return e, nil +} + +type subAcctType uint + +// SubAcctType* constants represent the sub-account type for a source and/or destination of a transaction. Used in fields named SubAcctFrom, SubAcctTo, SubAcctSec, SubAcctFund, HeldInAcct. +const ( + SubAcctTypeCash subAcctType = 1 + iota + SubAcctTypeMargin + SubAcctTypeShort + SubAcctTypeOther +) + +var subAcctTypes = [...]string{"CASH", "MARGIN", "SHORT", "OTHER"} + +func (e subAcctType) Valid() bool { + // This check is mostly out of paranoia, ensuring e != 0 should be + // sufficient + return e >= SubAcctTypeCash && e <= SubAcctTypeOther +} + +func (e subAcctType) String() string { + if e.Valid() { + return subAcctTypes[e-1] + } + return fmt.Sprintf("invalid subAcctType (%d)", e) +} + +func (e *subAcctType) FromString(in string) error { + value := strings.TrimSpace(in) + + for i, s := range subAcctTypes { + if s == value { + *e = subAcctType(i + 1) + return nil + } + } + *e = 0 + return errors.New("Invalid SubAcctType: \"" + in + "\"") +} + +func (e *subAcctType) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + var value string + err := d.DecodeElement(&value, &start) + if err != nil { + return err + } + + return e.FromString(value) +} + +func (e *subAcctType) MarshalXML(enc *xml.Encoder, start xml.StartElement) error { + if !e.Valid() { + return nil + } + enc.EncodeElement(subAcctTypes[*e-1], start) + return nil +} + +// NewSubAcctType returns returns an 'enum' value of type subAcctType given its +// string representation +func NewSubAcctType(s string) (subAcctType, error) { + var e subAcctType + err := e.FromString(s) + if err != nil { + return 0, err + } + return e, nil +} + +type buyType uint + +// BuyType* constants represent types of purchases +const ( + BuyTypeBuy buyType = 1 + iota + BuyTypeBuyToCover +) + +var buyTypes = [...]string{"BUY", "BUYTOCOVER"} + +func (e buyType) Valid() bool { + // This check is mostly out of paranoia, ensuring e != 0 should be + // sufficient + return e >= BuyTypeBuy && e <= BuyTypeBuyToCover +} + +func (e buyType) String() string { + if e.Valid() { + return buyTypes[e-1] + } + return fmt.Sprintf("invalid buyType (%d)", e) +} + +func (e *buyType) FromString(in string) error { + value := strings.TrimSpace(in) + + for i, s := range buyTypes { + if s == value { + *e = buyType(i + 1) + return nil + } + } + *e = 0 + return errors.New("Invalid BuyType: \"" + in + "\"") +} + +func (e *buyType) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + var value string + err := d.DecodeElement(&value, &start) + if err != nil { + return err + } + + return e.FromString(value) +} + +func (e *buyType) MarshalXML(enc *xml.Encoder, start xml.StartElement) error { + if !e.Valid() { + return nil + } + enc.EncodeElement(buyTypes[*e-1], start) + return nil +} + +// NewBuyType returns returns an 'enum' value of type buyType given its +// string representation +func NewBuyType(s string) (buyType, error) { + var e buyType + err := e.FromString(s) + if err != nil { + return 0, err + } + return e, nil +} + +type optAction uint + +// OptAction* constants represent types of actions for options +const ( + OptActionExercise optAction = 1 + iota + OptActionAssign + OptActionExpire +) + +var optActions = [...]string{"EXERCISE", "ASSIGN", "EXPIRE"} + +func (e optAction) Valid() bool { + // This check is mostly out of paranoia, ensuring e != 0 should be + // sufficient + return e >= OptActionExercise && e <= OptActionExpire +} + +func (e optAction) String() string { + if e.Valid() { + return optActions[e-1] + } + return fmt.Sprintf("invalid optAction (%d)", e) +} + +func (e *optAction) FromString(in string) error { + value := strings.TrimSpace(in) + + for i, s := range optActions { + if s == value { + *e = optAction(i + 1) + return nil + } + } + *e = 0 + return errors.New("Invalid OptAction: \"" + in + "\"") +} + +func (e *optAction) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + var value string + err := d.DecodeElement(&value, &start) + if err != nil { + return err + } + + return e.FromString(value) +} + +func (e *optAction) MarshalXML(enc *xml.Encoder, start xml.StartElement) error { + if !e.Valid() { + return nil + } + enc.EncodeElement(optActions[*e-1], start) + return nil +} + +// NewOptAction returns returns an 'enum' value of type optAction given its +// string representation +func NewOptAction(s string) (optAction, error) { + var e optAction + err := e.FromString(s) + if err != nil { + return 0, err + } + return e, nil +} + +type tferAction uint + +// TferAction* constants represent whether the transfer is into or out of this account +const ( + TferActionIn tferAction = 1 + iota + TferActionOut +) + +var tferActions = [...]string{"IN", "OUT"} + +func (e tferAction) Valid() bool { + // This check is mostly out of paranoia, ensuring e != 0 should be + // sufficient + return e >= TferActionIn && e <= TferActionOut +} + +func (e tferAction) String() string { + if e.Valid() { + return tferActions[e-1] + } + return fmt.Sprintf("invalid tferAction (%d)", e) +} + +func (e *tferAction) FromString(in string) error { + value := strings.TrimSpace(in) + + for i, s := range tferActions { + if s == value { + *e = tferAction(i + 1) + return nil + } + } + *e = 0 + return errors.New("Invalid TferAction: \"" + in + "\"") +} + +func (e *tferAction) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + var value string + err := d.DecodeElement(&value, &start) + if err != nil { + return err + } + + return e.FromString(value) +} + +func (e *tferAction) MarshalXML(enc *xml.Encoder, start xml.StartElement) error { + if !e.Valid() { + return nil + } + enc.EncodeElement(tferActions[*e-1], start) + return nil +} + +// NewTferAction returns returns an 'enum' value of type tferAction given its +// string representation +func NewTferAction(s string) (tferAction, error) { + var e tferAction + err := e.FromString(s) + if err != nil { + return 0, err + } + return e, nil +} + +type posType uint + +// PosType* constants represent position type +const ( + PosTypeLong posType = 1 + iota + PosTypeShort +) + +var posTypes = [...]string{"LONG", "SHORT"} + +func (e posType) Valid() bool { + // This check is mostly out of paranoia, ensuring e != 0 should be + // sufficient + return e >= PosTypeLong && e <= PosTypeShort +} + +func (e posType) String() string { + if e.Valid() { + return posTypes[e-1] + } + return fmt.Sprintf("invalid posType (%d)", e) +} + +func (e *posType) FromString(in string) error { + value := strings.TrimSpace(in) + + for i, s := range posTypes { + if s == value { + *e = posType(i + 1) + return nil + } + } + *e = 0 + return errors.New("Invalid PosType: \"" + in + "\"") +} + +func (e *posType) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + var value string + err := d.DecodeElement(&value, &start) + if err != nil { + return err + } + + return e.FromString(value) +} + +func (e *posType) MarshalXML(enc *xml.Encoder, start xml.StartElement) error { + if !e.Valid() { + return nil + } + enc.EncodeElement(posTypes[*e-1], start) + return nil +} + +// NewPosType returns returns an 'enum' value of type posType given its +// string representation +func NewPosType(s string) (posType, error) { + var e posType + err := e.FromString(s) + if err != nil { + return 0, err + } + return e, nil +} + +type secured uint + +// Secured* constants represent how an option is secured +const ( + SecuredNaked secured = 1 + iota + SecuredCovered +) + +var secureds = [...]string{"NAKED", "COVERED"} + +func (e secured) Valid() bool { + // This check is mostly out of paranoia, ensuring e != 0 should be + // sufficient + return e >= SecuredNaked && e <= SecuredCovered +} + +func (e secured) String() string { + if e.Valid() { + return secureds[e-1] + } + return fmt.Sprintf("invalid secured (%d)", e) +} + +func (e *secured) FromString(in string) error { + value := strings.TrimSpace(in) + + for i, s := range secureds { + if s == value { + *e = secured(i + 1) + return nil + } + } + *e = 0 + return errors.New("Invalid Secured: \"" + in + "\"") +} + +func (e *secured) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + var value string + err := d.DecodeElement(&value, &start) + if err != nil { + return err + } + + return e.FromString(value) +} + +func (e *secured) MarshalXML(enc *xml.Encoder, start xml.StartElement) error { + if !e.Valid() { + return nil + } + enc.EncodeElement(secureds[*e-1], start) + return nil +} + +// NewSecured returns returns an 'enum' value of type secured given its +// string representation +func NewSecured(s string) (secured, error) { + var e secured + err := e.FromString(s) + if err != nil { + return 0, err + } + return e, nil +} + +type duration uint + +// Duration* constants represent how long the investment order is good for +const ( + DurationDay duration = 1 + iota + DurationGoodTilCancel + DurationImmediate +) + +var durations = [...]string{"DAY", "GOODTILCANCEL", "IMMEDIATE"} + +func (e duration) Valid() bool { + // This check is mostly out of paranoia, ensuring e != 0 should be + // sufficient + return e >= DurationDay && e <= DurationImmediate +} + +func (e duration) String() string { + if e.Valid() { + return durations[e-1] + } + return fmt.Sprintf("invalid duration (%d)", e) +} + +func (e *duration) FromString(in string) error { + value := strings.TrimSpace(in) + + for i, s := range durations { + if s == value { + *e = duration(i + 1) + return nil + } + } + *e = 0 + return errors.New("Invalid Duration: \"" + in + "\"") +} + +func (e *duration) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + var value string + err := d.DecodeElement(&value, &start) + if err != nil { + return err + } + + return e.FromString(value) +} + +func (e *duration) MarshalXML(enc *xml.Encoder, start xml.StartElement) error { + if !e.Valid() { + return nil + } + enc.EncodeElement(durations[*e-1], start) + return nil +} + +// NewDuration returns returns an 'enum' value of type duration given its +// string representation +func NewDuration(s string) (duration, error) { + var e duration + err := e.FromString(s) + if err != nil { + return 0, err + } + return e, nil +} + +type restriction uint + +// Restriction* constants represent a special restriction on an investment order +const ( + RestrictionAllOrNone restriction = 1 + iota + RestrictionMinUnits + RestrictionNone +) + +var restrictions = [...]string{"ALLORNONE", "MINUNITS", "NONE"} + +func (e restriction) Valid() bool { + // This check is mostly out of paranoia, ensuring e != 0 should be + // sufficient + return e >= RestrictionAllOrNone && e <= RestrictionNone +} + +func (e restriction) String() string { + if e.Valid() { + return restrictions[e-1] + } + return fmt.Sprintf("invalid restriction (%d)", e) +} + +func (e *restriction) FromString(in string) error { + value := strings.TrimSpace(in) + + for i, s := range restrictions { + if s == value { + *e = restriction(i + 1) + return nil + } + } + *e = 0 + return errors.New("Invalid Restriction: \"" + in + "\"") +} + +func (e *restriction) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + var value string + err := d.DecodeElement(&value, &start) + if err != nil { + return err + } + + return e.FromString(value) +} + +func (e *restriction) MarshalXML(enc *xml.Encoder, start xml.StartElement) error { + if !e.Valid() { + return nil + } + enc.EncodeElement(restrictions[*e-1], start) + return nil +} + +// NewRestriction returns returns an 'enum' value of type restriction given its +// string representation +func NewRestriction(s string) (restriction, error) { + var e restriction + err := e.FromString(s) + if err != nil { + return 0, err + } + return e, nil +} + +type unitType uint + +// UnitType* constants represent type of the UNITS value +const ( + UnitTypeShares unitType = 1 + iota + UnitTypeCurrency +) + +var unitTypes = [...]string{"SHARES", "CURRENCY"} + +func (e unitType) Valid() bool { + // This check is mostly out of paranoia, ensuring e != 0 should be + // sufficient + return e >= UnitTypeShares && e <= UnitTypeCurrency +} + +func (e unitType) String() string { + if e.Valid() { + return unitTypes[e-1] + } + return fmt.Sprintf("invalid unitType (%d)", e) +} + +func (e *unitType) FromString(in string) error { + value := strings.TrimSpace(in) + + for i, s := range unitTypes { + if s == value { + *e = unitType(i + 1) + return nil + } + } + *e = 0 + return errors.New("Invalid UnitType: \"" + in + "\"") +} + +func (e *unitType) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + var value string + err := d.DecodeElement(&value, &start) + if err != nil { + return err + } + + return e.FromString(value) +} + +func (e *unitType) MarshalXML(enc *xml.Encoder, start xml.StartElement) error { + if !e.Valid() { + return nil + } + enc.EncodeElement(unitTypes[*e-1], start) + return nil +} + +// NewUnitType returns returns an 'enum' value of type unitType given its +// string representation +func NewUnitType(s string) (unitType, error) { + var e unitType + err := e.FromString(s) + if err != nil { + return 0, err + } + return e, nil +} + +type optBuyType uint + +// OptBuyType* constants represent types of purchases for options +const ( + OptBuyTypeBuyToOpen optBuyType = 1 + iota + OptBuyTypeBuyToClose +) + +var optBuyTypes = [...]string{"BUYTOOPEN", "BUYTOCLOSE"} + +func (e optBuyType) Valid() bool { + // This check is mostly out of paranoia, ensuring e != 0 should be + // sufficient + return e >= OptBuyTypeBuyToOpen && e <= OptBuyTypeBuyToClose +} + +func (e optBuyType) String() string { + if e.Valid() { + return optBuyTypes[e-1] + } + return fmt.Sprintf("invalid optBuyType (%d)", e) +} + +func (e *optBuyType) FromString(in string) error { + value := strings.TrimSpace(in) + + for i, s := range optBuyTypes { + if s == value { + *e = optBuyType(i + 1) + return nil + } + } + *e = 0 + return errors.New("Invalid OptBuyType: \"" + in + "\"") +} + +func (e *optBuyType) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + var value string + err := d.DecodeElement(&value, &start) + if err != nil { + return err + } + + return e.FromString(value) +} + +func (e *optBuyType) MarshalXML(enc *xml.Encoder, start xml.StartElement) error { + if !e.Valid() { + return nil + } + enc.EncodeElement(optBuyTypes[*e-1], start) + return nil +} + +// NewOptBuyType returns returns an 'enum' value of type optBuyType given its +// string representation +func NewOptBuyType(s string) (optBuyType, error) { + var e optBuyType + err := e.FromString(s) + if err != nil { + return 0, err + } + return e, nil +} + +type sellType uint + +// SellType* constants represent types of sales +const ( + SellTypeSell sellType = 1 + iota + SellTypeSellShort +) + +var sellTypes = [...]string{"SELL", "SELLSHORT"} + +func (e sellType) Valid() bool { + // This check is mostly out of paranoia, ensuring e != 0 should be + // sufficient + return e >= SellTypeSell && e <= SellTypeSellShort +} + +func (e sellType) String() string { + if e.Valid() { + return sellTypes[e-1] + } + return fmt.Sprintf("invalid sellType (%d)", e) +} + +func (e *sellType) FromString(in string) error { + value := strings.TrimSpace(in) + + for i, s := range sellTypes { + if s == value { + *e = sellType(i + 1) + return nil + } + } + *e = 0 + return errors.New("Invalid SellType: \"" + in + "\"") +} + +func (e *sellType) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + var value string + err := d.DecodeElement(&value, &start) + if err != nil { + return err + } + + return e.FromString(value) +} + +func (e *sellType) MarshalXML(enc *xml.Encoder, start xml.StartElement) error { + if !e.Valid() { + return nil + } + enc.EncodeElement(sellTypes[*e-1], start) + return nil +} + +// NewSellType returns returns an 'enum' value of type sellType given its +// string representation +func NewSellType(s string) (sellType, error) { + var e sellType + err := e.FromString(s) + if err != nil { + return 0, err + } + return e, nil +} + +type loanPmtFreq uint + +// LoanPmtFreq* constants represent the frequency of loan payments +const ( + LoanPmtFreqWeekly loanPmtFreq = 1 + iota + LoanPmtFreqBiweekly + LoanPmtFreqTwiceMonthly + LoanPmtFreqMonthly + LoanPmtFreqFourWeeks + LoanPmtFreqBiMonthly + LoanPmtFreqQuarterly + LoanPmtFreqSemiannually + LoanPmtFreqAnnually + LoanPmtFreqOther +) + +var loanPmtFreqs = [...]string{"WEEKLY", "BIWEEKLY", "TWICEMONTHLY", "MONTHLY", "FOURWEEKS", "BIMONTHLY", "QUARTERLY", "SEMIANNUALLY", "ANNUALLY", "OTHER"} + +func (e loanPmtFreq) Valid() bool { + // This check is mostly out of paranoia, ensuring e != 0 should be + // sufficient + return e >= LoanPmtFreqWeekly && e <= LoanPmtFreqOther +} + +func (e loanPmtFreq) String() string { + if e.Valid() { + return loanPmtFreqs[e-1] + } + return fmt.Sprintf("invalid loanPmtFreq (%d)", e) +} + +func (e *loanPmtFreq) FromString(in string) error { + value := strings.TrimSpace(in) + + for i, s := range loanPmtFreqs { + if s == value { + *e = loanPmtFreq(i + 1) + return nil + } + } + *e = 0 + return errors.New("Invalid LoanPmtFreq: \"" + in + "\"") +} + +func (e *loanPmtFreq) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + var value string + err := d.DecodeElement(&value, &start) + if err != nil { + return err + } + + return e.FromString(value) +} + +func (e *loanPmtFreq) MarshalXML(enc *xml.Encoder, start xml.StartElement) error { + if !e.Valid() { + return nil + } + enc.EncodeElement(loanPmtFreqs[*e-1], start) + return nil +} + +// NewLoanPmtFreq returns returns an 'enum' value of type loanPmtFreq given its +// string representation +func NewLoanPmtFreq(s string) (loanPmtFreq, error) { + var e loanPmtFreq + err := e.FromString(s) + if err != nil { + return 0, err + } + return e, nil +} + +type incomeType uint + +// IncomeType* constants represent types of investment income +const ( + IncomeTypeCGLong incomeType = 1 + iota + IncomeTypeCGShort + IncomeTypeDiv + IncomeTypeInterest + IncomeTypeMisc +) + +var incomeTypes = [...]string{"CGLONG", "CGSHORT", "DIV", "INTEREST", "MISC"} + +func (e incomeType) Valid() bool { + // This check is mostly out of paranoia, ensuring e != 0 should be + // sufficient + return e >= IncomeTypeCGLong && e <= IncomeTypeMisc +} + +func (e incomeType) String() string { + if e.Valid() { + return incomeTypes[e-1] + } + return fmt.Sprintf("invalid incomeType (%d)", e) +} + +func (e *incomeType) FromString(in string) error { + value := strings.TrimSpace(in) + + for i, s := range incomeTypes { + if s == value { + *e = incomeType(i + 1) + return nil + } + } + *e = 0 + return errors.New("Invalid IncomeType: \"" + in + "\"") +} + +func (e *incomeType) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + var value string + err := d.DecodeElement(&value, &start) + if err != nil { + return err + } + + return e.FromString(value) +} + +func (e *incomeType) MarshalXML(enc *xml.Encoder, start xml.StartElement) error { + if !e.Valid() { + return nil + } + enc.EncodeElement(incomeTypes[*e-1], start) + return nil +} + +// NewIncomeType returns returns an 'enum' value of type incomeType given its +// string representation +func NewIncomeType(s string) (incomeType, error) { + var e incomeType + err := e.FromString(s) + if err != nil { + return 0, err + } + return e, nil +} + +type sellReason uint + +// SellReason* constants represent the reason the sell of a debt security was generated: CALL (the debt was called), SELL (the debt was sold), MATURITY (the debt reached maturity) +const ( + SellReasonCall sellReason = 1 + iota + SellReasonSell + SellReasonMaturity +) + +var sellReasons = [...]string{"CALL", "SELL", "MATURITY"} + +func (e sellReason) Valid() bool { + // This check is mostly out of paranoia, ensuring e != 0 should be + // sufficient + return e >= SellReasonCall && e <= SellReasonMaturity +} + +func (e sellReason) String() string { + if e.Valid() { + return sellReasons[e-1] + } + return fmt.Sprintf("invalid sellReason (%d)", e) +} + +func (e *sellReason) FromString(in string) error { + value := strings.TrimSpace(in) + + for i, s := range sellReasons { + if s == value { + *e = sellReason(i + 1) + return nil + } + } + *e = 0 + return errors.New("Invalid SellReason: \"" + in + "\"") +} + +func (e *sellReason) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + var value string + err := d.DecodeElement(&value, &start) + if err != nil { + return err + } + + return e.FromString(value) +} + +func (e *sellReason) MarshalXML(enc *xml.Encoder, start xml.StartElement) error { + if !e.Valid() { + return nil + } + enc.EncodeElement(sellReasons[*e-1], start) + return nil +} + +// NewSellReason returns returns an 'enum' value of type sellReason given its +// string representation +func NewSellReason(s string) (sellReason, error) { + var e sellReason + err := e.FromString(s) + if err != nil { + return 0, err + } + return e, nil +} + +type optSellType uint + +// OptSellType* constants represent types of sales for options +const ( + OptSellTypeSellToClose optSellType = 1 + iota + OptSellTypeSellToOpen +) + +var optSellTypes = [...]string{"SELLTOCLOSE", "SELLTOOPEN"} + +func (e optSellType) Valid() bool { + // This check is mostly out of paranoia, ensuring e != 0 should be + // sufficient + return e >= OptSellTypeSellToClose && e <= OptSellTypeSellToOpen +} + +func (e optSellType) String() string { + if e.Valid() { + return optSellTypes[e-1] + } + return fmt.Sprintf("invalid optSellType (%d)", e) +} + +func (e *optSellType) FromString(in string) error { + value := strings.TrimSpace(in) + + for i, s := range optSellTypes { + if s == value { + *e = optSellType(i + 1) + return nil + } + } + *e = 0 + return errors.New("Invalid OptSellType: \"" + in + "\"") +} + +func (e *optSellType) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + var value string + err := d.DecodeElement(&value, &start) + if err != nil { + return err + } + + return e.FromString(value) +} + +func (e *optSellType) MarshalXML(enc *xml.Encoder, start xml.StartElement) error { + if !e.Valid() { + return nil + } + enc.EncodeElement(optSellTypes[*e-1], start) + return nil +} + +// NewOptSellType returns returns an 'enum' value of type optSellType given its +// string representation +func NewOptSellType(s string) (optSellType, error) { + var e optSellType + err := e.FromString(s) + if err != nil { + return 0, err + } + return e, nil +} + +type relType uint + +// RelType* constants represent related option transaction types +const ( + RelTypeSpread relType = 1 + iota + RelTypeStraddle + RelTypeNone + RelTypeOther +) + +var relTypes = [...]string{"SPREAD", "STRADDLE", "NONE", "OTHER"} + +func (e relType) Valid() bool { + // This check is mostly out of paranoia, ensuring e != 0 should be + // sufficient + return e >= RelTypeSpread && e <= RelTypeOther +} + +func (e relType) String() string { + if e.Valid() { + return relTypes[e-1] + } + return fmt.Sprintf("invalid relType (%d)", e) +} + +func (e *relType) FromString(in string) error { + value := strings.TrimSpace(in) + + for i, s := range relTypes { + if s == value { + *e = relType(i + 1) + return nil + } + } + *e = 0 + return errors.New("Invalid RelType: \"" + in + "\"") +} + +func (e *relType) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + var value string + err := d.DecodeElement(&value, &start) + if err != nil { + return err + } + + return e.FromString(value) +} + +func (e *relType) MarshalXML(enc *xml.Encoder, start xml.StartElement) error { + if !e.Valid() { + return nil + } + enc.EncodeElement(relTypes[*e-1], start) + return nil +} + +// NewRelType returns returns an 'enum' value of type relType given its +// string representation +func NewRelType(s string) (relType, error) { + var e relType + err := e.FromString(s) + if err != nil { + return 0, err + } + return e, nil +} + +type charType uint + +// CharType* constants represent types of characters allowed in password +const ( + CharTypeAlphaOnly charType = 1 + iota + CharTypeNumericOnly + CharTypeAlphaOrNumeric + CharTypeAlphaAndNumeric +) + +var charTypes = [...]string{"ALPHAONLY", "NUMERICONLY", "ALPHAORNUMERIC", "ALPHAANDNUMERIC"} + +func (e charType) Valid() bool { + // This check is mostly out of paranoia, ensuring e != 0 should be + // sufficient + return e >= CharTypeAlphaOnly && e <= CharTypeAlphaAndNumeric +} + +func (e charType) String() string { + if e.Valid() { + return charTypes[e-1] + } + return fmt.Sprintf("invalid charType (%d)", e) +} + +func (e *charType) FromString(in string) error { + value := strings.TrimSpace(in) + + for i, s := range charTypes { + if s == value { + *e = charType(i + 1) + return nil + } + } + *e = 0 + return errors.New("Invalid CharType: \"" + in + "\"") +} + +func (e *charType) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + var value string + err := d.DecodeElement(&value, &start) + if err != nil { + return err + } + + return e.FromString(value) +} + +func (e *charType) MarshalXML(enc *xml.Encoder, start xml.StartElement) error { + if !e.Valid() { + return nil + } + enc.EncodeElement(charTypes[*e-1], start) + return nil +} + +// NewCharType returns returns an 'enum' value of type charType given its +// string representation +func NewCharType(s string) (charType, error) { + var e charType + err := e.FromString(s) + if err != nil { + return 0, err + } + return e, nil +} + +type syncMode uint + +// SyncMode* constants represent data synchronization mode supported (see OFX spec for more details) +const ( + SyncModeFull syncMode = 1 + iota + SyncModeLite +) + +var syncModes = [...]string{"FULL", "LITE"} + +func (e syncMode) Valid() bool { + // This check is mostly out of paranoia, ensuring e != 0 should be + // sufficient + return e >= SyncModeFull && e <= SyncModeLite +} + +func (e syncMode) String() string { + if e.Valid() { + return syncModes[e-1] + } + return fmt.Sprintf("invalid syncMode (%d)", e) +} + +func (e *syncMode) FromString(in string) error { + value := strings.TrimSpace(in) + + for i, s := range syncModes { + if s == value { + *e = syncMode(i + 1) + return nil + } + } + *e = 0 + return errors.New("Invalid SyncMode: \"" + in + "\"") +} + +func (e *syncMode) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + var value string + err := d.DecodeElement(&value, &start) + if err != nil { + return err + } + + return e.FromString(value) +} + +func (e *syncMode) MarshalXML(enc *xml.Encoder, start xml.StartElement) error { + if !e.Valid() { + return nil + } + enc.EncodeElement(syncModes[*e-1], start) + return nil +} + +// NewSyncMode returns returns an 'enum' value of type syncMode given its +// string representation +func NewSyncMode(s string) (syncMode, error) { + var e syncMode + err := e.FromString(s) + if err != nil { + return 0, err + } + return e, nil +} + +type ofxSec uint + +// OfxSec* constants represent the type of application-level security required for the message set +const ( + OfxSecNone ofxSec = 1 + iota + OfxSecType1 +) + +var ofxSecs = [...]string{"NONE", "TYPE 1"} + +func (e ofxSec) Valid() bool { + // This check is mostly out of paranoia, ensuring e != 0 should be + // sufficient + return e >= OfxSecNone && e <= OfxSecType1 +} + +func (e ofxSec) String() string { + if e.Valid() { + return ofxSecs[e-1] + } + return fmt.Sprintf("invalid ofxSec (%d)", e) +} + +func (e *ofxSec) FromString(in string) error { + value := strings.TrimSpace(in) + + for i, s := range ofxSecs { + if s == value { + *e = ofxSec(i + 1) + return nil + } + } + *e = 0 + return errors.New("Invalid OfxSec: \"" + in + "\"") +} + +func (e *ofxSec) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + var value string + err := d.DecodeElement(&value, &start) + if err != nil { + return err + } + + return e.FromString(value) +} + +func (e *ofxSec) MarshalXML(enc *xml.Encoder, start xml.StartElement) error { + if !e.Valid() { + return nil + } + enc.EncodeElement(ofxSecs[*e-1], start) + return nil +} + +// NewOfxSec returns returns an 'enum' value of type ofxSec given its +// string representation +func NewOfxSec(s string) (ofxSec, error) { + var e ofxSec + err := e.FromString(s) + if err != nil { + return 0, err + } + return e, nil +} + +type debtType uint + +// DebtType* constants represent debt type +const ( + DebtTypeCoupon debtType = 1 + iota + DebtTypeZero +) + +var debtTypes = [...]string{"COUPON", "ZERO"} + +func (e debtType) Valid() bool { + // This check is mostly out of paranoia, ensuring e != 0 should be + // sufficient + return e >= DebtTypeCoupon && e <= DebtTypeZero +} + +func (e debtType) String() string { + if e.Valid() { + return debtTypes[e-1] + } + return fmt.Sprintf("invalid debtType (%d)", e) +} + +func (e *debtType) FromString(in string) error { + value := strings.TrimSpace(in) + + for i, s := range debtTypes { + if s == value { + *e = debtType(i + 1) + return nil + } + } + *e = 0 + return errors.New("Invalid DebtType: \"" + in + "\"") +} + +func (e *debtType) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + var value string + err := d.DecodeElement(&value, &start) + if err != nil { + return err + } + + return e.FromString(value) +} + +func (e *debtType) MarshalXML(enc *xml.Encoder, start xml.StartElement) error { + if !e.Valid() { + return nil + } + enc.EncodeElement(debtTypes[*e-1], start) + return nil +} + +// NewDebtType returns returns an 'enum' value of type debtType given its +// string representation +func NewDebtType(s string) (debtType, error) { + var e debtType + err := e.FromString(s) + if err != nil { + return 0, err + } + return e, nil +} + +type debtClass uint + +// DebtClass* constants represent the class of debt +const ( + DebtClassTreasury debtClass = 1 + iota + DebtClassMunicipal + DebtClassCorporate + DebtClassOther +) + +var debtClasss = [...]string{"TREASURY", "MUNICIPAL", "CORPORATE", "OTHER"} + +func (e debtClass) Valid() bool { + // This check is mostly out of paranoia, ensuring e != 0 should be + // sufficient + return e >= DebtClassTreasury && e <= DebtClassOther +} + +func (e debtClass) String() string { + if e.Valid() { + return debtClasss[e-1] + } + return fmt.Sprintf("invalid debtClass (%d)", e) +} + +func (e *debtClass) FromString(in string) error { + value := strings.TrimSpace(in) + + for i, s := range debtClasss { + if s == value { + *e = debtClass(i + 1) + return nil + } + } + *e = 0 + return errors.New("Invalid DebtClass: \"" + in + "\"") +} + +func (e *debtClass) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + var value string + err := d.DecodeElement(&value, &start) + if err != nil { + return err + } + + return e.FromString(value) +} + +func (e *debtClass) MarshalXML(enc *xml.Encoder, start xml.StartElement) error { + if !e.Valid() { + return nil + } + enc.EncodeElement(debtClasss[*e-1], start) + return nil +} + +// NewDebtClass returns returns an 'enum' value of type debtClass given its +// string representation +func NewDebtClass(s string) (debtClass, error) { + var e debtClass + err := e.FromString(s) + if err != nil { + return 0, err + } + return e, nil +} + +type couponFreq uint + +// CouponFreq* constants represent when debt coupons mature +const ( + CouponFreqMonthly couponFreq = 1 + iota + CouponFreqQuarterly + CouponFreqSemiannual + CouponFreqAnnual + CouponFreqOther +) + +var couponFreqs = [...]string{"MONTHLY", "QUARTERLY", "SEMIANNUAL", "ANNUAL", "OTHER"} + +func (e couponFreq) Valid() bool { + // This check is mostly out of paranoia, ensuring e != 0 should be + // sufficient + return e >= CouponFreqMonthly && e <= CouponFreqOther +} + +func (e couponFreq) String() string { + if e.Valid() { + return couponFreqs[e-1] + } + return fmt.Sprintf("invalid couponFreq (%d)", e) +} + +func (e *couponFreq) FromString(in string) error { + value := strings.TrimSpace(in) + + for i, s := range couponFreqs { + if s == value { + *e = couponFreq(i + 1) + return nil + } + } + *e = 0 + return errors.New("Invalid CouponFreq: \"" + in + "\"") +} + +func (e *couponFreq) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + var value string + err := d.DecodeElement(&value, &start) + if err != nil { + return err + } + + return e.FromString(value) +} + +func (e *couponFreq) MarshalXML(enc *xml.Encoder, start xml.StartElement) error { + if !e.Valid() { + return nil + } + enc.EncodeElement(couponFreqs[*e-1], start) + return nil +} + +// NewCouponFreq returns returns an 'enum' value of type couponFreq given its +// string representation +func NewCouponFreq(s string) (couponFreq, error) { + var e couponFreq + err := e.FromString(s) + if err != nil { + return 0, err + } + return e, nil +} + +type callType uint + +// CallType* constants represent type of next call (for a debt) +const ( + CallTypeCall callType = 1 + iota + CallTypePut + CallTypePrefund + CallTypeMaturity +) + +var callTypes = [...]string{"CALL", "PUT", "PREFUND", "MATURITY"} + +func (e callType) Valid() bool { + // This check is mostly out of paranoia, ensuring e != 0 should be + // sufficient + return e >= CallTypeCall && e <= CallTypeMaturity +} + +func (e callType) String() string { + if e.Valid() { + return callTypes[e-1] + } + return fmt.Sprintf("invalid callType (%d)", e) +} + +func (e *callType) FromString(in string) error { + value := strings.TrimSpace(in) + + for i, s := range callTypes { + if s == value { + *e = callType(i + 1) + return nil + } + } + *e = 0 + return errors.New("Invalid CallType: \"" + in + "\"") +} + +func (e *callType) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + var value string + err := d.DecodeElement(&value, &start) + if err != nil { + return err + } + + return e.FromString(value) +} + +func (e *callType) MarshalXML(enc *xml.Encoder, start xml.StartElement) error { + if !e.Valid() { + return nil + } + enc.EncodeElement(callTypes[*e-1], start) + return nil +} + +// NewCallType returns returns an 'enum' value of type callType given its +// string representation +func NewCallType(s string) (callType, error) { + var e callType + err := e.FromString(s) + if err != nil { + return 0, err + } + return e, nil +} + +type assetClass uint + +// AssetClass* constants represent type of asset classes +const ( + AssetClassDomesticBond assetClass = 1 + iota + AssetClassIntlBond + AssetClassLargeStock + AssetClassSmallStock + AssetClassIntlStock + AssetClassMoneyMrkt + AssetClassOther +) + +var assetClasss = [...]string{"DOMESTICBOND", "INTLBOND", "LARGESTOCK", "SMALLSTOCK", "INTLSTOCK", "MONEYMRKT", "OTHER"} + +func (e assetClass) Valid() bool { + // This check is mostly out of paranoia, ensuring e != 0 should be + // sufficient + return e >= AssetClassDomesticBond && e <= AssetClassOther +} + +func (e assetClass) String() string { + if e.Valid() { + return assetClasss[e-1] + } + return fmt.Sprintf("invalid assetClass (%d)", e) +} + +func (e *assetClass) FromString(in string) error { + value := strings.TrimSpace(in) + + for i, s := range assetClasss { + if s == value { + *e = assetClass(i + 1) + return nil + } + } + *e = 0 + return errors.New("Invalid AssetClass: \"" + in + "\"") +} + +func (e *assetClass) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + var value string + err := d.DecodeElement(&value, &start) + if err != nil { + return err + } + + return e.FromString(value) +} + +func (e *assetClass) MarshalXML(enc *xml.Encoder, start xml.StartElement) error { + if !e.Valid() { + return nil + } + enc.EncodeElement(assetClasss[*e-1], start) + return nil +} + +// NewAssetClass returns returns an 'enum' value of type assetClass given its +// string representation +func NewAssetClass(s string) (assetClass, error) { + var e assetClass + err := e.FromString(s) + if err != nil { + return 0, err + } + return e, nil +} + +type mfType uint + +// MfType* constants represent types of mutual funds +const ( + MfTypeOpenEnd mfType = 1 + iota + MfTypeCloseEnd + MfTypeOther +) + +var mfTypes = [...]string{"OPENEND", "CLOSEEND", "OTHER"} + +func (e mfType) Valid() bool { + // This check is mostly out of paranoia, ensuring e != 0 should be + // sufficient + return e >= MfTypeOpenEnd && e <= MfTypeOther +} + +func (e mfType) String() string { + if e.Valid() { + return mfTypes[e-1] + } + return fmt.Sprintf("invalid mfType (%d)", e) +} + +func (e *mfType) FromString(in string) error { + value := strings.TrimSpace(in) + + for i, s := range mfTypes { + if s == value { + *e = mfType(i + 1) + return nil + } + } + *e = 0 + return errors.New("Invalid MfType: \"" + in + "\"") +} + +func (e *mfType) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + var value string + err := d.DecodeElement(&value, &start) + if err != nil { + return err + } + + return e.FromString(value) +} + +func (e *mfType) MarshalXML(enc *xml.Encoder, start xml.StartElement) error { + if !e.Valid() { + return nil + } + enc.EncodeElement(mfTypes[*e-1], start) + return nil +} + +// NewMfType returns returns an 'enum' value of type mfType given its +// string representation +func NewMfType(s string) (mfType, error) { + var e mfType + err := e.FromString(s) + if err != nil { + return 0, err + } + return e, nil +} + +type optType uint + +// OptType* constants represent whether the option is a PUT or a CALL +const ( + OptTypePut optType = 1 + iota + OptTypeCall +) + +var optTypes = [...]string{"PUT", "CALL"} + +func (e optType) Valid() bool { + // This check is mostly out of paranoia, ensuring e != 0 should be + // sufficient + return e >= OptTypePut && e <= OptTypeCall +} + +func (e optType) String() string { + if e.Valid() { + return optTypes[e-1] + } + return fmt.Sprintf("invalid optType (%d)", e) +} + +func (e *optType) FromString(in string) error { + value := strings.TrimSpace(in) + + for i, s := range optTypes { + if s == value { + *e = optType(i + 1) + return nil + } + } + *e = 0 + return errors.New("Invalid OptType: \"" + in + "\"") +} + +func (e *optType) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + var value string + err := d.DecodeElement(&value, &start) + if err != nil { + return err + } + + return e.FromString(value) +} + +func (e *optType) MarshalXML(enc *xml.Encoder, start xml.StartElement) error { + if !e.Valid() { + return nil + } + enc.EncodeElement(optTypes[*e-1], start) + return nil +} + +// NewOptType returns returns an 'enum' value of type optType given its +// string representation +func NewOptType(s string) (optType, error) { + var e optType + err := e.FromString(s) + if err != nil { + return 0, err + } + return e, nil +} + +type stockType uint + +// StockType* constants represent types of stock +const ( + StockTypeCommon stockType = 1 + iota + StockTypePreferred + StockTypeConvertible + StockTypeOther +) + +var stockTypes = [...]string{"COMMON", "PREFERRED", "CONVERTIBLE", "OTHER"} + +func (e stockType) Valid() bool { + // This check is mostly out of paranoia, ensuring e != 0 should be + // sufficient + return e >= StockTypeCommon && e <= StockTypeOther +} + +func (e stockType) String() string { + if e.Valid() { + return stockTypes[e-1] + } + return fmt.Sprintf("invalid stockType (%d)", e) +} + +func (e *stockType) FromString(in string) error { + value := strings.TrimSpace(in) + + for i, s := range stockTypes { + if s == value { + *e = stockType(i + 1) + return nil + } + } + *e = 0 + return errors.New("Invalid StockType: \"" + in + "\"") +} + +func (e *stockType) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + var value string + err := d.DecodeElement(&value, &start) + if err != nil { + return err + } + + return e.FromString(value) +} + +func (e *stockType) MarshalXML(enc *xml.Encoder, start xml.StartElement) error { + if !e.Valid() { + return nil + } + enc.EncodeElement(stockTypes[*e-1], start) + return nil +} + +// NewStockType returns returns an 'enum' value of type stockType given its +// string representation +func NewStockType(s string) (stockType, error) { + var e stockType + err := e.FromString(s) + if err != nil { + return 0, err + } + return e, nil +} + +type holderType uint + +// HolderType* constants represent how the account is held +const ( + HolderTypeIndividual holderType = 1 + iota + HolderTypeJoint + HolderTypeCustodial + HolderTypeTrust + HolderTypeOther +) + +var holderTypes = [...]string{"INDIVIDUAL", "JOINT", "CUSTODIAL", "TRUST", "OTHER"} + +func (e holderType) Valid() bool { + // This check is mostly out of paranoia, ensuring e != 0 should be + // sufficient + return e >= HolderTypeIndividual && e <= HolderTypeOther +} + +func (e holderType) String() string { + if e.Valid() { + return holderTypes[e-1] + } + return fmt.Sprintf("invalid holderType (%d)", e) +} + +func (e *holderType) FromString(in string) error { + value := strings.TrimSpace(in) + + for i, s := range holderTypes { + if s == value { + *e = holderType(i + 1) + return nil + } + } + *e = 0 + return errors.New("Invalid HolderType: \"" + in + "\"") +} + +func (e *holderType) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + var value string + err := d.DecodeElement(&value, &start) + if err != nil { + return err + } + + return e.FromString(value) +} + +func (e *holderType) MarshalXML(enc *xml.Encoder, start xml.StartElement) error { + if !e.Valid() { + return nil + } + enc.EncodeElement(holderTypes[*e-1], start) + return nil +} + +// NewHolderType returns returns an 'enum' value of type holderType given its +// string representation +func NewHolderType(s string) (holderType, error) { + var e holderType + err := e.FromString(s) + if err != nil { + return 0, err + } + return e, nil +} + +type acctClassification uint + +// AcctClassification* constants represent the type of an account +const ( + AcctClassificationPersonal acctClassification = 1 + iota + AcctClassificationBusiness + AcctClassificationCorporate + AcctClassificationOther +) + +var acctClassifications = [...]string{"PERSONAL", "BUSINESS", "CORPORATE", "OTHER"} + +func (e acctClassification) Valid() bool { + // This check is mostly out of paranoia, ensuring e != 0 should be + // sufficient + return e >= AcctClassificationPersonal && e <= AcctClassificationOther +} + +func (e acctClassification) String() string { + if e.Valid() { + return acctClassifications[e-1] + } + return fmt.Sprintf("invalid acctClassification (%d)", e) +} + +func (e *acctClassification) FromString(in string) error { + value := strings.TrimSpace(in) + + for i, s := range acctClassifications { + if s == value { + *e = acctClassification(i + 1) + return nil + } + } + *e = 0 + return errors.New("Invalid AcctClassification: \"" + in + "\"") +} + +func (e *acctClassification) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + var value string + err := d.DecodeElement(&value, &start) + if err != nil { + return err + } + + return e.FromString(value) +} + +func (e *acctClassification) MarshalXML(enc *xml.Encoder, start xml.StartElement) error { + if !e.Valid() { + return nil + } + enc.EncodeElement(acctClassifications[*e-1], start) + return nil +} + +// NewAcctClassification returns returns an 'enum' value of type acctClassification given its +// string representation +func NewAcctClassification(s string) (acctClassification, error) { + var e acctClassification + err := e.FromString(s) + if err != nil { + return 0, err + } + return e, nil +} + +type svcStatus uint + +// SvcStatus* constants represent the status of the account: AVAIL = Available, but not yet requested, PEND = Requested, but not yet available, ACTIVE = In use +const ( + SvcStatusAvail svcStatus = 1 + iota + SvcStatusPend + SvcStatusActive +) + +var svcStatuss = [...]string{"AVAIL", "PEND", "ACTIVE"} + +func (e svcStatus) Valid() bool { + // This check is mostly out of paranoia, ensuring e != 0 should be + // sufficient + return e >= SvcStatusAvail && e <= SvcStatusActive +} + +func (e svcStatus) String() string { + if e.Valid() { + return svcStatuss[e-1] + } + return fmt.Sprintf("invalid svcStatus (%d)", e) +} + +func (e *svcStatus) FromString(in string) error { + value := strings.TrimSpace(in) + + for i, s := range svcStatuss { + if s == value { + *e = svcStatus(i + 1) + return nil + } + } + *e = 0 + return errors.New("Invalid SvcStatus: \"" + in + "\"") +} + +func (e *svcStatus) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + var value string + err := d.DecodeElement(&value, &start) + if err != nil { + return err + } + + return e.FromString(value) +} + +func (e *svcStatus) MarshalXML(enc *xml.Encoder, start xml.StartElement) error { + if !e.Valid() { + return nil + } + enc.EncodeElement(svcStatuss[*e-1], start) + return nil +} + +// NewSvcStatus returns returns an 'enum' value of type svcStatus given its +// string representation +func NewSvcStatus(s string) (svcStatus, error) { + var e svcStatus + err := e.FromString(s) + if err != nil { + return 0, err + } + return e, nil +} + +type usProductType uint + +// UsProductType* constants represent type of investment account (in the US) +const ( + UsProductType401K usProductType = 1 + iota + UsProductType403B + UsProductTypeIRA + UsProductTypeKEOGH + UsProductTypeOther + UsProductTypeSARSEP + UsProductTypeSimple + UsProductTypeNormal + UsProductTypeTDA + UsProductTypeTrust + UsProductTypeUGMA +) + +var usProductTypes = [...]string{"401K", "403B", "IRA", "KEOGH", "OTHER", "SARSEP", "SIMPLE", "NORMAL", "TDA", "TRUST", "UGMA"} + +func (e usProductType) Valid() bool { + // This check is mostly out of paranoia, ensuring e != 0 should be + // sufficient + return e >= UsProductType401K && e <= UsProductTypeUGMA +} + +func (e usProductType) String() string { + if e.Valid() { + return usProductTypes[e-1] + } + return fmt.Sprintf("invalid usProductType (%d)", e) +} + +func (e *usProductType) FromString(in string) error { + value := strings.TrimSpace(in) + + for i, s := range usProductTypes { + if s == value { + *e = usProductType(i + 1) + return nil + } + } + *e = 0 + return errors.New("Invalid UsProductType: \"" + in + "\"") +} + +func (e *usProductType) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + var value string + err := d.DecodeElement(&value, &start) + if err != nil { + return err + } + + return e.FromString(value) +} + +func (e *usProductType) MarshalXML(enc *xml.Encoder, start xml.StartElement) error { + if !e.Valid() { + return nil + } + enc.EncodeElement(usProductTypes[*e-1], start) + return nil +} + +// NewUsProductType returns returns an 'enum' value of type usProductType given its +// string representation +func NewUsProductType(s string) (usProductType, error) { + var e usProductType + err := e.FromString(s) + if err != nil { + return 0, err + } + return e, nil +} diff --git a/vendor/github.com/aclindsa/ofxgo/creditcard.go b/vendor/github.com/aclindsa/ofxgo/creditcard.go new file mode 100644 index 0000000..365b2eb --- /dev/null +++ b/vendor/github.com/aclindsa/ofxgo/creditcard.go @@ -0,0 +1,91 @@ +package ofxgo + +import ( + "github.com/aclindsa/xml" +) + +// CCStatementRequest represents a request for a credit card statement. It is +// used to request balances and/or transactions. See StatementRequest for the +// analog for all other bank accounts. +type CCStatementRequest struct { + XMLName xml.Name `xml:"CCSTMTTRNRQ"` + TrnUID UID `xml:"TRNUID"` + CltCookie String `xml:"CLTCOOKIE,omitempty"` + TAN String `xml:"TAN,omitempty"` + // TODO OFXEXTENSION + CCAcctFrom CCAcct `xml:"CCSTMTRQ>CCACCTFROM"` + DtStart *Date `xml:"CCSTMTRQ>INCTRAN>DTSTART,omitempty"` + DtEnd *Date `xml:"CCSTMTRQ>INCTRAN>DTEND,omitempty"` + Include Boolean `xml:"CCSTMTRQ>INCTRAN>INCLUDE"` // Include transactions (instead of just balance) + IncludePending Boolean `xml:"CCSTMTRQ>INCLUDEPENDING,omitempty"` // Include pending transactions + IncTranImg Boolean `xml:"CCSTMTRQ>INCTRANIMG,omitempty"` // Include transaction images +} + +// Name returns the name of the top-level transaction XML/SGML element +func (r *CCStatementRequest) Name() string { + return "CCSTMTTRNRQ" +} + +// Valid returns (true, nil) if this struct would be valid OFX if marshalled +// into XML/SGML +func (r *CCStatementRequest) Valid(version ofxVersion) (bool, error) { + if ok, err := r.TrnUID.Valid(); !ok { + return false, err + } + // 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 *CCStatementRequest) Type() messageType { + return CreditCardRq +} + +// CCStatementResponse represents a credit card statement, including its +// balances and possibly transactions. It is a response to CCStatementRequest, +// or sometimes provided as part of an OFX file downloaded manually from an FI. +type CCStatementResponse struct { + XMLName xml.Name `xml:"CCSTMTTRNRS"` + TrnUID UID `xml:"TRNUID"` + Status Status `xml:"STATUS"` + CltCookie String `xml:"CLTCOOKIE,omitempty"` + // TODO `xml:"OFXEXTENSION,omitempty"` + CurDef CurrSymbol `xml:"CCSTMTRS>CURDEF"` + CCAcctFrom CCAcct `xml:"CCSTMTRS>CCACCTFROM"` + BankTranList *TransactionList `xml:"CCSTMTRS>BANKTRANLIST,omitempty"` + //BANKTRANLISTP + BalAmt Amount `xml:"CCSTMTRS>LEDGERBAL>BALAMT"` + DtAsOf Date `xml:"CCSTMTRS>LEDGERBAL>DTASOF"` + AvailBalAmt *Amount `xml:"CCSTMTRS>AVAILBAL>BALAMT,omitempty"` + AvailDtAsOf *Date `xml:"CCSTMTRS>AVAILBAL>DTASOF,omitempty"` + CashAdvBalAmt Amount `xml:"CCSTMTRS>CASHADVBALAMT,omitempty"` // Only for CREDITLINE accounts, available balance for cash advances + IntRatePurch Amount `xml:"CCSTMTRS>INTRATEPURCH,omitempty"` // Current interest rate for purchases + IntRateCash Amount `xml:"CCSTMTRS>INTRATECASH,omitempty"` // Current interest rate for cash advances + IntRateXfer Amount `xml:"CCSTMTRS>INTRATEXFER,omitempty"` // Current interest rate for cash advances + RewardName String `xml:"CCSTMTRS>REWARDINFO>NAME,omitempty"` // Name of the reward program referred to by the next two elements + RewardBal Amount `xml:"CCSTMTRS>REWARDINFO>REWARDBAL,omitempty"` // Current balance of the reward program + RewardEarned Amount `xml:"CCSTMTRS>REWARDINFO>REWARDEARNED,omitempty"` // Reward amount earned YTD + BalList []Balance `xml:"CCSTMTRS>BALLIST>BAL,omitempty"` + MktgInfo String `xml:"CCSTMTRS>MKTGINFO,omitempty"` // Marketing information +} + +// Name returns the name of the top-level transaction XML/SGML element +func (sr *CCStatementResponse) Name() string { + return "CCSTMTTRNRS" +} + +// Valid returns (true, nil) if this struct was valid OFX when unmarshalled +func (sr *CCStatementResponse) Valid(version ofxVersion) (bool, error) { + if ok, err := sr.TrnUID.Valid(); !ok { + return false, err + } + //TODO implement + return true, nil +} + +// Type returns which message set this message belongs to (which Response +// element of type []Message it belongs to) +func (sr *CCStatementResponse) Type() messageType { + return CreditCardRs +} diff --git a/vendor/github.com/aclindsa/ofxgo/discovercard_client.go b/vendor/github.com/aclindsa/ofxgo/discovercard_client.go new file mode 100644 index 0000000..820f448 --- /dev/null +++ b/vendor/github.com/aclindsa/ofxgo/discovercard_client.go @@ -0,0 +1,109 @@ +package ofxgo + +import ( + "bufio" + "bytes" + "crypto/tls" + "errors" + "fmt" + "io" + "net/http" + "net/url" + "strings" +) + +// DiscoverCardClient provides a Client implementation which handles +// DiscoverCard's broken HTTP header behavior. DiscoverCardClient uses default, +// non-zero settings, if its fields are not initialized. +type DiscoverCardClient struct { + *BasicClient +} + +// NewDiscoverCardClient returns a Client interface configured to handle +// Discover Card's brand of idiosyncrasy +func NewDiscoverCardClient(bc *BasicClient) Client { + return &DiscoverCardClient{bc} +} + +func discoverCardHTTPPost(URL string, r io.Reader) (*http.Response, error) { + // Either convert or copy to a bytes.Buffer to be able to determine the + // request length for the Content-Length header + buf, ok := r.(*bytes.Buffer) + if !ok { + buf = &bytes.Buffer{} + _, err := io.Copy(buf, r) + if err != nil { + return nil, err + } + } + + url, err := url.Parse(URL) + if err != nil { + return nil, err + } + + path := url.Path + if path == "" { + path = "/" + } + + // Discover requires only these headers and in this exact order, or it + // returns HTTP 403 + headers := fmt.Sprintf("POST %s HTTP/1.1\r\n"+ + "Content-Type: application/x-ofx\r\n"+ + "Host: %s\r\n"+ + "Content-Length: %d\r\n"+ + "Connection: Keep-Alive\r\n"+ + "\r\n", path, url.Hostname(), buf.Len()) + + host := url.Host + if url.Port() == "" { + host += ":443" + } + + // BUGBUG: cannot do defer conn.Close() until body is read, + // we are "leaking" a socket here, but it will be finalized + conn, err := tls.Dial("tcp", host, nil) + if err != nil { + return nil, err + } + + fmt.Fprint(conn, headers) + _, err = io.Copy(conn, buf) + if err != nil { + return nil, err + } + + return http.ReadResponse(bufio.NewReader(conn), nil) +} + +// RawRequest is a convenience wrapper around http.Post. It is exposed only for +// when you need to read/inspect the raw HTTP response yourself. +func (c *DiscoverCardClient) RawRequest(URL string, r io.Reader) (*http.Response, error) { + if !strings.HasPrefix(URL, "https://") { + return nil, errors.New("Refusing to send OFX request with possible plain-text password over non-https protocol") + } + + response, err := discoverCardHTTPPost(URL, r) + if err != nil { + return nil, err + } + + if response.StatusCode != 200 { + return nil, errors.New("OFXQuery request status: " + response.Status) + } + + return response, nil +} + +// RequestNoParse marshals a Request to XML, makes an HTTP request, and returns +// the raw HTTP response +func (c *DiscoverCardClient) RequestNoParse(r *Request) (*http.Response, error) { + return clientRequestNoParse(c, r) +} + +// Request marshals a Request to XML, makes an HTTP request, and then +// unmarshals the response into a Response object. +func (c *DiscoverCardClient) Request(r *Request) (*Response, error) { + return clientRequest(c, r) +} diff --git a/vendor/github.com/aclindsa/ofxgo/doc.go b/vendor/github.com/aclindsa/ofxgo/doc.go new file mode 100644 index 0000000..1f1d3aa --- /dev/null +++ b/vendor/github.com/aclindsa/ofxgo/doc.go @@ -0,0 +1,127 @@ +/* +Package ofxgo seeks to provide a library to make it easier to query and/or +parse financial information with OFX from the comfort of Golang, without having +to deal with marshalling/unmarshalling the SGML or XML. The library does *not* +intend to abstract away all of the details of the OFX specification, which +would be difficult to do well. Instead, it exposes the OFX SGML/XML hierarchy +as structs which mostly resemble it. For more information on OFX and to read +the specification, see http://ofx.net. + +There are three main top-level objects defined in ofxgo. These are Client, +Request, and Response. The Request and Response objects represent OFX requests +and responses as Golang structs. Client contains settings which control how +requests and responses are marshalled and unmarshalled (the OFX version used, +client id and version, whether to indent SGML/XML tags, etc.), and provides +helper methods for making requests and optionally parsing the response using +those settings. + +Every Request object contains a SignonRequest element, called Signon. This +element contains the username, password (or key), and the ORG and FID fields +particular to the financial institution being queried, and an optional ClientUID +field (required by some FIs). Likewise, each Response contains a SignonResponse +object which contains, among other things, the Status of the request. Any status +with a nonzero Code should be inspected for a possible error (using the Severity +and Message fields populated by the server, or the CodeMeaning() and +CodeConditions() functions which return information about a particular code as +specified by the OFX specification). + +Each top-level Request or Response object may contain zero or more messages, +sorted into named slices by message set, just as the OFX specification groups +them. Here are the supported types of Request/Response objects (along with the +name of the slice of Messages they belong to in parentheses): + +Requests: + var r AcctInfoRequest // (Signup) Request a list of the valid accounts + // for this user + var r CCStatementRequest // (CreditCard) Request the balance (and optionally + // list of transactions) for a credit card + var r StatementRequest // (Bank) Request the balance (and optionally list + // of transactions) for a bank account + var r InvStatementRequest // (InvStmt) Request balance, transactions, + // existing positions, and/or open orders for an + // investment account + var r SecListRequest // (SecList) Request securities details and prices + var r ProfileRequest // (Prof) Request the server's capabilities (which + // messages sets it supports, along with features) + +Responses: + var r AcctInfoResponse // (Signup) List of the valid accounts for this + // user + var r CCStatementResponse // (CreditCard) The balance (and optionally list of + // transactions) for a credit card + var r StatementResponse // (Bank) The balance (and optionally list of + // transactions) for a bank account + var r InvStatementResponse // (InvStmt) The balance, transactions, existing + // positions, and/or open orders for an + // investment account + var r SecListResponse // (SecList) Returned as a result of + // SecListRequest, but only contains request + // status + var r SecurityList // (SecList) The actual list of securities, prices, + // etc. (sent as a result of SecListRequest or + // InvStatementRequest) + var r ProfileResponse // (Prof) Describes the server's capabilities + +When constructing a Request, simply append the desired message to the message +set it belongs to. For Responses, it is the user's responsibility to make type +assertions on objects found inside one of these message sets before using them. + +For example, the following code would request a bank statement for a checking +account and print the balance: + + import ( + "fmt" + "os" + ) + + var client Client // By not initializing them, we accept all default + // client values + var request Request + + // These are all specific to you and your financial institution + request.URL = "https://ofx.example.com" + request.Signon.UserID = String("john") + request.Signon.UserPass = String("hunter2") + request.Signon.Org = String("MyBank") + request.Signon.Fid = String("0001") + + uid, err := RandomUID() + if err != nil { + fmt.Println("Error creating uid for transaction:", err) + os.Exit(1) + } + + statementRequest := StatementRequest{ + TrnUID: *uid, + BankAcctFrom: BankAcct{ + BankID: String("123456789"), + AcctID: String("11111111111"), + AcctType: AcctTypeChecking, + }, + } + + request.Bank = append(request.Bank, &statementRequest) + + response, err := client.Request(request) + if err != nil { + fmt.Println("Error requesting account statement:", err) + os.Exit(1) + } + + if response.Signon.Status.Code != 0 { + meaning, _ := response.Signon.Status.CodeMeaning() + fmt.Printf("Nonzero signon status (%d: %s) with message: %s\n", response.Signon.Status.Code, meaning, response.Signon.Status.Message) + os.Exit(1) + } + + if len(response.Bank) < 1 { + fmt.Println("No banking messages received") + } else if stmt, ok := response.Bank[0].(*StatementResponse); ok { + fmt.Printf("Balance: %s %s (as of %s)\n", stmt.BalAmt, stmt.CurDef, stmt.DtAsOf) + } + +More usage examples may be found in the example command-line client provided +with this library, in the cmd/ofx directory of the source. + +*/ +package ofxgo diff --git a/vendor/github.com/aclindsa/ofxgo/generate_constants.py b/vendor/github.com/aclindsa/ofxgo/generate_constants.py new file mode 100644 index 0000000..704d31b --- /dev/null +++ b/vendor/github.com/aclindsa/ofxgo/generate_constants.py @@ -0,0 +1,239 @@ +#!/usr/bin/env python + +enums = { + # OFX spec version + "OfxVersion": (["102", "103", "151", "160", "200", "201", "202", "203", "210", "211", "220"], "the OFX specification version in use"), + + # Bank/general + "AcctType": (["Checking", "Savings", "MoneyMrkt", "CreditLine", "CD"], "types of bank accounts"), + "TrnType": (["Credit", "Debit", "Int", "Div", "Fee", "SrvChg", "Dep", "ATM", "POS", "Xfer", "Check", "Payment", "Cash", "DirectDep", "DirectDebit", "RepeatPmt", "Hold", "Other"], "types of transactions. INT, ATM, and POS depend on the signage of the account."), + "ImageType": (["Statement", "Transaction", "Tax"], "what this image contains"), + "ImageRefType": (["Opaque", "URL", "FormURL"], "the type of reference to the image"), + "CheckSup": (["FrontOnly", "BackOnly", "FrontAndBack"], "what portions of the check this image contains"), + "CorrectAction": (["Delete", "Replace"], "whether this transaction correction replaces or deletes the transaction matching its CORRECTFITID"), + "BalType": (["Dollar", "Percent", "Number"], "how this BAL's VALUE field should be interpreted"), + + # InvStmt + "Inv401kSource": (["PreTax", "AfterTax", "Match", "ProfitSharing", "Rollover", "OtherVest", "OtherNonVest"], "the source of money used for this security in a 401(k) account. Default if not present is OTHERNONVEST. The following cash source types are subject to vesting: MATCH, PROFITSHARING, and OTHERVEST."), + "SubAcctType": (["Cash", "Margin", "Short", "Other"], "the sub-account type for a source and/or destination of a transaction. Used in fields named SubAcctFrom, SubAcctTo, SubAcctSec, SubAcctFund, HeldInAcct."), + "BuyType": (["Buy", "BuyToCover"], "types of purchases"), + "OptAction": (["Exercise", "Assign", "Expire"], "types of actions for options"), + "TferAction": (["In", "Out"], "whether the transfer is into or out of this account"), + "PosType": (["Long", "Short"], "position type"), + "Secured": (["Naked", "Covered"], "how an option is secured"), + "Duration": (["Day", "GoodTilCancel", "Immediate"], "how long the investment order is good for"), + "Restriction": (["AllOrNone", "MinUnits", "None"], "a special restriction on an investment order"), + "UnitType": (["Shares", "Currency"], "type of the UNITS value"), + "OptBuyType": (["BuyToOpen", "BuyToClose"], "types of purchases for options"), + "SellType": (["Sell", "SellShort"], "types of sales"), + "LoanPmtFreq": (["Weekly", "Biweekly", "TwiceMonthly", "Monthly", "FourWeeks", "BiMonthly", "Quarterly", "Semiannually", "Annually", "Other"], "the frequency of loan payments"), + "IncomeType": (["CGLong", "CGShort", "Div", "Interest", "Misc"], "types of investment income"), + "SellReason": (["Call", "Sell", "Maturity"], "the reason the sell of a debt security was generated: CALL (the debt was called), SELL (the debt was sold), MATURITY (the debt reached maturity)"), + "OptSellType": (["SellToClose", "SellToOpen"], "types of sales for options"), + "RelType": (["Spread", "Straddle", "None", "Other"], "related option transaction types"), + + # Prof + "CharType": (["AlphaOnly", "NumericOnly", "AlphaOrNumeric", "AlphaAndNumeric"], "types of characters allowed in password"), + "SyncMode": (["Full", "Lite"], "data synchronization mode supported (see OFX spec for more details)"), + "OfxSec": (["None", "Type 1"], "the type of application-level security required for the message set"), + + # SecList + "DebtType": (["Coupon", "Zero"], "debt type"), + "DebtClass": (["Treasury", "Municipal", "Corporate", "Other"], "the class of debt"), + "CouponFreq": (["Monthly", "Quarterly", "Semiannual", "Annual", "Other"], "when debt coupons mature"), + "CallType": (["Call", "Put", "Prefund", "Maturity"], "type of next call (for a debt)"), + "AssetClass": (["DomesticBond", "IntlBond", "LargeStock", "SmallStock", "IntlStock", "MoneyMrkt", "Other"], "type of asset classes"), + "MfType": (["OpenEnd", "CloseEnd", "Other"], "types of mutual funds"), + "OptType": (["Put", "Call"], "whether the option is a PUT or a CALL"), + "StockType": (["Common", "Preferred", "Convertible", "Other"], "types of stock"), + + # Signup + "HolderType": (["Individual", "Joint", "Custodial", "Trust", "Other"], "how the account is held"), + "AcctClassification": (["Personal", "Business", "Corporate", "Other"], "the type of an account"), + "SvcStatus": (["Avail", "Pend", "Active"], "the status of the account: AVAIL = Available, but not yet requested, PEND = Requested, but not yet available, ACTIVE = In use"), + "UsProductType": (["401K", "403B", "IRA", "KEOGH", "Other", "SARSEP", "Simple", "Normal", "TDA", "Trust", "UGMA"], "type of investment account (in the US)"), +} + +header = """package ofxgo + +/* + * Do not edit this file by hand. It is auto-generated by calling `go generate`. + * To make changes, edit generate_constants.py, re-run `go generate`, and check + * in the result. + */ + +import ( + "errors" + "fmt" + "strings" + + "github.com/aclindsa/xml" +) +""" + +template = """ +type {enumLower} uint + +// {enum}* constants represent {comment} +const ( +{constNames}) + +var {enumLower}s = [...]string{{"{upperValueString}"}} + +func (e {enumLower}) Valid() bool {{ + // This check is mostly out of paranoia, ensuring e != 0 should be + // sufficient + return e >= {firstValue} && e <= {lastValue} +}} + +func (e {enumLower}) String() string {{ + if e.Valid() {{ + return {enumLower}s[e-1] + }} + return fmt.Sprintf("invalid {enumLower} (%d)", e) +}} + +func (e *{enumLower}) FromString(in string) error {{ + value := strings.TrimSpace(in) + + for i, s := range {enumLower}s {{ + if s == value {{ + *e = {enumLower}(i + 1) + return nil + }} + }} + *e = 0 + return errors.New("Invalid {enum}: \\\"" + in + "\\\"") +}} + +func (e *{enumLower}) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {{ + var value string + err := d.DecodeElement(&value, &start) + if err != nil {{ + return err + }} + + return e.FromString(value) +}} + +func (e *{enumLower}) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {{ + if !e.Valid() {{ + return nil + }} + enc.EncodeElement({enumLower}s[*e-1], start) + return nil +}} + +// New{enum} returns returns an 'enum' value of type {enumLower} given its +// string representation +func New{enum}(s string) ({enumLower}, error) {{ + var e {enumLower} + err := e.FromString(s) + if err != nil {{ + return 0, err + }} + return e, nil +}} +""" + +with open("constants.go", 'w') as f: + f.write(header) + + for enum in enums: + enumLower = enum[:1].lower() + enum[1:].replace(" ", "") + firstValue = enum+enums[enum][0][0].replace(" ", "") + lastValue = enum+enums[enum][0][-1].replace(" ", "") + + comment = enums[enum][1] + + constNames = "\t{firstValue} {enumLower} = 1 + iota\n".format( + enum=enum, + firstValue=firstValue, + enumLower=enumLower) + for value in enums[enum][0][1:]: + constNames += "\t{enum}{value}\n".format( + enum=enum, + value=value.replace(" ", "")) + + upperValueString = "\", \"".join([s.upper() for s in enums[enum][0]]) + + f.write(template.format(enum=enum, + enumLower=enumLower, + comment=comment, + firstValue=firstValue, + lastValue=lastValue, + constNames=constNames, + upperValueString=upperValueString)) + +test_header = """package ofxgo + +/* + * Do not edit this file by hand. It is auto-generated by calling `go generate`. + * To make changes, edit generate_constants.py, re-run `go generate`, and check + * in the result. + */ + +import ( + "strings" + "testing" + + "github.com/aclindsa/xml" +) +""" + +test_template = """ +func Test{enum}(t *testing.T) {{ + e, err := New{enum}("{firstValueUpper}") + if err != nil {{ + t.Fatalf("Unexpected error creating new {enum} from string \\\"{firstValueUpper}\\\"\\n") + }} + if !e.Valid() {{ + t.Fatalf("{enum} unexpectedly invalid\\n") + }} + err = e.FromString("{lastValueUpper}") + if err != nil {{ + t.Fatalf("Unexpected error on {enum}.FromString(\\\"{lastValueUpper}\\\")\\n") + }} + if e.String() != "{lastValueUpper}" {{ + t.Fatalf("{enum}.String() expected to be \\\"{lastValueUpper}\\\"\\n") + }} + + marshalHelper(t, "{lastValueUpper}", &e) + + overwritten, err := New{enum}("THISWILLNEVERBEAVALIDENUMSTRING") + if err == nil {{ + t.Fatalf("Expected error creating new {enum} from string \\\"THISWILLNEVERBEAVALIDENUMSTRING\\\"\\n") + }} + if overwritten.Valid() {{ + t.Fatalf("{enum} created with string \\\"THISWILLNEVERBEAVALIDENUMSTRING\\\" should not be valid\\n") + }} + if !strings.Contains(strings.ToLower(overwritten.String()), "invalid") {{ + t.Fatalf("{enum} created with string \\\"THISWILLNEVERBEAVALIDENUMSTRING\\\" should not return valid string from String()\\n") + }} + + b, err := xml.Marshal(&overwritten) + if err != nil {{ + t.Fatalf("Unexpected error on xml.Marshal({enum}): %s\\n", err) + }} + if string(b) != "" {{ + t.Fatalf("Expected empty string, got '%s'\\n", string(b)) + }} + + unmarshalHelper(t, "{lastValueUpper}", &e, &overwritten) + + err = xml.Unmarshal([]byte(""), &overwritten) + if err == nil {{ + t.Fatalf("Expected error unmarshalling garbage value\\n") + }} +}} +""" + +with open("constants_test.go", 'w') as f: + f.write(test_header) + + for enum in enums: + firstValueUpper = enums[enum][0][0].upper() + lastValueUpper = enums[enum][0][-1].upper() + f.write(test_template.format(enum=enum, + firstValueUpper=firstValueUpper, + lastValueUpper=lastValueUpper)) diff --git a/vendor/github.com/aclindsa/ofxgo/invstmt.go b/vendor/github.com/aclindsa/ofxgo/invstmt.go new file mode 100644 index 0000000..bffd93b --- /dev/null +++ b/vendor/github.com/aclindsa/ofxgo/invstmt.go @@ -0,0 +1,1414 @@ +package ofxgo + +import ( + "errors" + "github.com/aclindsa/xml" +) + +// InvStatementRequest allows a customer to request transactions, positions, +// open orders, and balances. It specifies what types of information to include +// in hte InvStatementResponse and which account to include it for. +type InvStatementRequest struct { + XMLName xml.Name `xml:"INVSTMTTRNRQ"` + TrnUID UID `xml:"TRNUID"` + CltCookie String `xml:"CLTCOOKIE,omitempty"` + TAN String `xml:"TAN,omitempty"` // Transaction authorization number + // TODO `xml:"OFXEXTENSION,omitempty"` + InvAcctFrom InvAcct `xml:"INVSTMTRQ>INVACCTFROM"` + DtStart *Date `xml:"INVSTMTRQ>INCTRAN>DTSTART,omitempty"` + DtEnd *Date `xml:"INVSTMTRQ>INCTRAN>DTEND,omitempty"` + Include Boolean `xml:"INVSTMTRQ>INCTRAN>INCLUDE"` // Include transactions (instead of just balance) + IncludeOO Boolean `xml:"INVSTMTRQ>INCOO"` // Include open orders + PosDtAsOf *Date `xml:"INVSTMTRQ>INCPOS>DTASOF,omitempty"` // Date that positions should be sent down for, if present + IncludePos Boolean `xml:"INVSTMTRQ>INCPOS>INCLUDE"` // Include position data in response + IncludeBalance Boolean `xml:"INVSTMTRQ>INCBAL"` // Include investment balance in response + Include401K Boolean `xml:"INVSTMTRQ>INC401K,omitempty"` // Include 401k information + Include401KBal Boolean `xml:"INVSTMTRQ>INC401KBAL,omitempty"` // Include 401k balance information + IncludeTranImage Boolean `xml:"INVSTMTRQ>INCTRANIMAGE,omitempty"` // Include transaction images +} + +// Name returns the name of the top-level transaction XML/SGML element +func (r *InvStatementRequest) Name() string { + return "INVSTMTTRNRQ" +} + +// Valid returns (true, nil) if this struct would be valid OFX if marshalled +// into XML/SGML +func (r *InvStatementRequest) Valid(version ofxVersion) (bool, error) { + if ok, err := r.TrnUID.Valid(); !ok { + return false, err + } + // 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 *InvStatementRequest) Type() messageType { + return InvStmtRq +} + +// InvTran represents generic investment transaction. It is included in both +// InvBuy and InvSell as well as many of the more specific transaction +// aggregates. +type InvTran struct { + XMLName xml.Name `xml:"INVTRAN"` + FiTID String `xml:"FITID"` // Unique FI-assigned transaction ID. This ID is used to detect duplicate downloads + SrvrTID String `xml:"SRVRTID,omitempty"` // Server assigned transaction ID + DtTrade Date `xml:"DTTRADE"` // trade date; for stock splits, day of record + DtSettle *Date `xml:"DTSETTLE,omitempty"` // settlement date; for stock splits, execution date + ReversalFiTID String `xml:"REVERSALFITID,omitempty"` // For a reversal transaction, the FITID of the transaction that is being reversed. + Memo String `xml:"MEMO,omitempty"` +} + +// InvBuy represents generic investment purchase transaction. It is included +// in many of the more specific transaction Buy* aggregates below. +type InvBuy struct { + XMLName xml.Name `xml:"INVBUY"` + InvTran InvTran `xml:"INVTRAN"` + SecID SecurityID `xml:"SECID"` + Units Amount `xml:"UNITS"` // For stocks, MFs, other, number of shares held. Bonds = face value. Options = number of contracts + UnitPrice Amount `xml:"UNITPRICE"` // For stocks, MFs, other, price per share. Bonds = percentage of par. Option = premium per share of underlying security + Markup Amount `xml:"MARKUP,omitempty"` // Portion of UNITPRICE that is attributed to the dealer markup + Commission Amount `xml:"COMMISSION,omitempty"` + Taxes Amount `xml:"TAXES,omitempty"` + Fees Amount `xml:"FEES,omitempty"` + Load Amount `xml:"LOAD,omitempty"` + Total Amount `xml:"TOTAL"` // Transaction total. Buys, sells, etc.:((quan. * (price +/- markup/markdown)) +/-(commission + fees + load + taxes + penalty + withholding + statewithholding)). Distributions, interest, margin interest, misc. expense, etc.: amount. Return of cap: cost basis + Currency Currency `xml:"CURRENCY,omitempty"` // Represents the currency this transaction is in (instead of CURDEF in INVSTMTRS) if Valid() + OrigCurrency Currency `xml:"ORIGCURRENCY,omitempty"` // Represents the currency this transaction was converted to INVSTMTRS' CURDEF from if Valid + SubAcctSec subAcctType `xml:"SUBACCTSEC"` // Sub-account type for this security. One of CASH, MARGIN, SHORT, OTHER + SubAcctFund subAcctType `xml:"SUBACCTFUND"` // Where did the money for the transaction come from or go to? CASH, MARGIN, SHORT, OTHER + + // The next three elements must either all be provided, or none of them + LoanID String `xml:"LOANID,omitempty"` // For 401(k) accounts only. Indicates that the transaction was due to a loan or a loan repayment, and which loan it was + LoanPrincipal Amount `xml:"LOANPRINCIPAL,omitempty"` // For 401(k) accounts only. Indicates how much of the loan repayment was principal + LoanInterest Amount `xml:"LOANINTEREST,omitempty"` // For 401(k) accounts only. Indicates how much of the loan repayment was interest + + Inv401kSource inv401kSource `xml:"INV401KSOURCE,omitempty"` // Source of money for this transaction. One of PRETAX, AFTERTAX, MATCH, PROFITSHARING, ROLLOVER, OTHERVEST, OTHERNONVEST for 401(k) accounts. Default if not present is OTHERNONVEST. The following cash source types are subject to vesting: MATCH, PROFITSHARING, and OTHERVEST + DtPayroll *Date `xml:"DTPAYROLL,omitempty"` // For 401(k)accounts, date the funds for this transaction was obtained via payroll deduction + PriorYearContrib Boolean `xml:"PRIORYEARCONTRIB,omitempty"` // For 401(k) accounts, indicates that this Buy was made with a prior year contribution +} + +// InvSell represents generic investment sale transaction. It is included in +// many of the more specific transaction Sell* aggregates below. +type InvSell struct { + XMLName xml.Name `xml:"INVSELL"` + InvTran InvTran `xml:"INVTRAN"` + SecID SecurityID `xml:"SECID"` + Units Amount `xml:"UNITS"` // For stocks, MFs, other, number of shares held. Bonds = face value. Options = number of contracts + UnitPrice Amount `xml:"UNITPRICE"` // For stocks, MFs, other, price per share. Bonds = percentage of par. Option = premium per share of underlying security + Markdown Amount `xml:"MARKDOWN,omitempty"` // Portion of UNITPRICE that is attributed to the dealer markdown + Commission Amount `xml:"COMMISSION,omitempty"` + Taxes Amount `xml:"TAXES,omitempty"` + Fees Amount `xml:"FEES,omitempty"` + Load Amount `xml:"LOAD,omitempty"` + Withholding Amount `xml:"WITHHOLDING,omitempty"` // Federal tax withholdings + TaxExempt Boolean `xml:"TAXEXEMPT,omitempty"` // Tax-exempt transaction + Total Amount `xml:"TOTAL"` // Transaction total. Buys, sells, etc.:((quan. * (price +/- markup/markdown)) +/-(commission + fees + load + taxes + penalty + withholding + statewithholding)). Distributions, interest, margin interest, misc. expense, etc.: amount. Return of cap: cost basis + Gain Amount `xml:"GAIN,omitempty"` // Total gain + Currency Currency `xml:"CURRENCY,omitempty"` // Represents the currency this transaction is in (instead of CURDEF in INVSTMTRS) if Valid() + OrigCurrency Currency `xml:"ORIGCURRENCY,omitempty"` // Represents the currency this transaction was converted to INVSTMTRS' CURDEF from if Valid + SubAcctSec subAcctType `xml:"SUBACCTSEC"` // Sub-account type for this security. One of CASH, MARGIN, SHORT, OTHER + SubAcctFund subAcctType `xml:"SUBACCTFUND"` // Where did the money for the transaction come from or go to? CASH, MARGIN, SHORT, OTHER + + LoanID String `xml:"LOANID,omitempty"` // For 401(k) accounts only. Indicates that the transaction was due to a loan or a loan repayment, and which loan it was + StateWithholding Amount `xml:"STATEWITHHOLDING,omitempty"` // State tax withholdings + Penalty Amount `xml:"PENALTY,omitempty"` // Amount withheld due to penalty + + Inv401kSource inv401kSource `xml:"INV401KSOURCE,omitempty"` // Source of money for this transaction. One of PRETAX, AFTERTAX, MATCH, PROFITSHARING, ROLLOVER, OTHERVEST, OTHERNONVEST for 401(k) accounts. Default if not present is OTHERNONVEST. The following cash source types are subject to vesting: MATCH, PROFITSHARING, and OTHERVEST +} + +// BuyDebt represents a transaction purchasing a debt security +type BuyDebt struct { + XMLName xml.Name `xml:"BUYDEBT"` + InvBuy InvBuy `xml:"INVBUY"` + AccrdInt Amount `xml:"ACCRDINT,omitempty"` // Accrued interest. This amount is not reflected in the field of a containing aggregate. +} + +// TransactionType returns a string representation of this transaction's type +func (t BuyDebt) TransactionType() string { + return "BUYDEBT" +} + +// BuyMF represents a transaction purchasing a mutual fund +type BuyMF struct { + XMLName xml.Name `xml:"BUYMF"` + InvBuy InvBuy `xml:"INVBUY"` + BuyType buyType `xml:"BUYTYPE"` // One of BUY, BUYTOCOVER (BUYTOCOVER used to close short sales.) + RelFiTID String `xml:"RELFITID,omitempty"` // used to relate transactions associated with mutual fund exchanges +} + +// TransactionType returns a string representation of this transaction's type +func (t BuyMF) TransactionType() string { + return "BUYMF" +} + +// BuyOpt represents a transaction purchasing an option +type BuyOpt struct { + XMLName xml.Name `xml:"BUYOPT"` + InvBuy InvBuy `xml:"INVBUY"` + OptBuyType optBuyType `xml:"OPTBUYTYPE"` // type of purchase: BUYTOOPEN, BUYTOCLOSE (The BUYTOOPEN buy type is like “ordinary” buying of option and works like stocks.) + ShPerCtrct Int `xml:"SHPERCTRCT"` // Shares per contract +} + +// TransactionType returns a string representation of this transaction's type +func (t BuyOpt) TransactionType() string { + return "BUYOPT" +} + +// BuyOther represents a transaction purchasing a type of security not covered +// by the other Buy* structs +type BuyOther struct { + XMLName xml.Name `xml:"BUYOTHER"` + InvBuy InvBuy `xml:"INVBUY"` +} + +// TransactionType returns a string representation of this transaction's type +func (t BuyOther) TransactionType() string { + return "BUYOTHER" +} + +// BuyStock represents a transaction purchasing stock +type BuyStock struct { + XMLName xml.Name `xml:"BUYSTOCK"` + InvBuy InvBuy `xml:"INVBUY"` + BuyType buyType `xml:"BUYTYPE"` // One of BUY, BUYTOCOVER (BUYTOCOVER used to close short sales.) +} + +// TransactionType returns a string representation of this transaction's type +func (t BuyStock) TransactionType() string { + return "BUYSTOCK" +} + +// ClosureOpt represents a transaction closing a position for an option +type ClosureOpt struct { + XMLName xml.Name `xml:"CLOSUREOPT"` + InvTran InvTran `xml:"INVTRAN"` + SecID SecurityID `xml:"SECID"` + OptAction optAction `xml:"OPTACTION"` // One of EXERCISE, ASSIGN, EXPIRE. The EXERCISE action is used to close out an option that is exercised. The ASSIGN action is used when an option writer is assigned. The EXPIRE action is used when the option’s expired date is reached + Units Amount `xml:"UNITS"` // For stocks, MFs, other, number of shares held. Bonds = face value. Options = number of contracts + ShPerCtrct Int `xml:"SHPERCTRCT"` // Shares per contract + SubAcctSec subAcctType `xml:"SUBACCTSEC"` // Sub-account type for this security. One of CASH, MARGIN, SHORT, OTHER + RelFiTID String `xml:"RELFITID,omitempty"` // used to relate transactions associated with mutual fund exchanges + Gain Amount `xml:"GAIN,omitempty"` // Total gain +} + +// TransactionType returns a string representation of this transaction's type +func (t ClosureOpt) TransactionType() string { + return "CLOSUREOPT" +} + +// Income represents a transaction where investment income is being realized as +// cash into the investment account +type Income struct { + XMLName xml.Name `xml:"INCOME"` + InvTran InvTran `xml:"INVTRAN"` + SecID SecurityID `xml:"SECID"` + IncomeType incomeType `xml:"INCOMETYPE"` // Type of investment income: CGLONG (capital gains-long term), CGSHORT (capital gains-short term), DIV (dividend), INTEREST, MISC + Total Amount `xml:"TOTAL"` + SubAcctSec subAcctType `xml:"SUBACCTSEC"` // Sub-account type for this security. One of CASH, MARGIN, SHORT, OTHER + SubAcctFund subAcctType `xml:"SUBACCTFUND"` // Where did the money for the transaction come from or go to? CASH, MARGIN, SHORT, OTHER + TaxExempt Boolean `xml:"TAXEXEMPT,omitempty"` // Tax-exempt transaction + Withholding Amount `xml:"WITHHOLDING,omitempty"` // Federal tax withholdings + Currency Currency `xml:"CURRENCY,omitempty"` // Represents the currency this transaction is in (instead of CURDEF in INVSTMTRS) if Valid() + OrigCurrency Currency `xml:"ORIGCURRENCY,omitempty"` // Represents the currency this transaction was converted to INVSTMTRS' CURDEF from if Valid + Inv401kSource inv401kSource `xml:"INV401KSOURCE,omitempty"` // Source of money for this transaction. One of PRETAX, AFTERTAX, MATCH, PROFITSHARING, ROLLOVER, OTHERVEST, OTHERNONVEST for 401(k) accounts. Default if not present is OTHERNONVEST. The following cash source types are subject to vesting: MATCH, PROFITSHARING, and OTHERVEST +} + +// TransactionType returns a string representation of this transaction's type +func (t Income) TransactionType() string { + return "INCOME" +} + +// InvExpense represents a transaction realizing an expense associated with an +// investment +type InvExpense struct { + XMLName xml.Name `xml:"INVEXPENSE"` + InvTran InvTran `xml:"INVTRAN"` + SecID SecurityID `xml:"SECID"` + Total Amount `xml:"TOTAL"` + SubAcctSec subAcctType `xml:"SUBACCTSEC"` // Sub-account type for this security. One of CASH, MARGIN, SHORT, OTHER + SubAcctFund subAcctType `xml:"SUBACCTFUND"` // Where did the money for the transaction come from or go to? CASH, MARGIN, SHORT, OTHER + Currency Currency `xml:"CURRENCY,omitempty"` // Represents the currency this transaction is in (instead of CURDEF in INVSTMTRS) if Valid() + OrigCurrency Currency `xml:"ORIGCURRENCY,omitempty"` // Represents the currency this transaction was converted to INVSTMTRS' CURDEF from if Valid + Inv401kSource inv401kSource `xml:"INV401KSOURCE,omitempty"` // Source of money for this transaction. One of PRETAX, AFTERTAX, MATCH, PROFITSHARING, ROLLOVER, OTHERVEST, OTHERNONVEST for 401(k) accounts. Default if not present is OTHERNONVEST. The following cash source types are subject to vesting: MATCH, PROFITSHARING, and OTHERVEST +} + +// TransactionType returns a string representation of this transaction's type +func (t InvExpense) TransactionType() string { + return "INVEXPENSE" +} + +// JrnlFund represents a transaction journaling cash holdings between +// sub-accounts within the same investment account +type JrnlFund struct { + XMLName xml.Name `xml:"JRNLFUND"` + InvTran InvTran `xml:"INVTRAN"` + Total Amount `xml:"TOTAL"` + SubAcctFrom subAcctType `xml:"SUBACCTFROM"` // Sub-account cash is being transferred from: CASH, MARGIN, SHORT, OTHER + SubAcctTo subAcctType `xml:"SUBACCTTO"` // Sub-account cash is being transferred to: CASH, MARGIN, SHORT, OTHER +} + +// TransactionType returns a string representation of this transaction's type +func (t JrnlFund) TransactionType() string { + return "JRNLFUND" +} + +// JrnlSec represents a transaction journaling security holdings between +// sub-accounts within the same investment account +type JrnlSec struct { + XMLName xml.Name `xml:"JRNLSEC"` + InvTran InvTran `xml:"INVTRAN"` + SecID SecurityID `xml:"SECID"` + SubAcctFrom subAcctType `xml:"SUBACCTFROM"` // Sub-account cash is being transferred from: CASH, MARGIN, SHORT, OTHER + SubAcctTo subAcctType `xml:"SUBACCTTO"` // Sub-account cash is being transferred to: CASH, MARGIN, SHORT, OTHER + Units Amount `xml:"UNITS"` // For stocks, MFs, other, number of shares held. Bonds = face value. Options = number of contracts +} + +// TransactionType returns a string representation of this transaction's type +func (t JrnlSec) TransactionType() string { + return "JRNLSEC" +} + +// MarginInterest represents a transaction realizing a margin interest expense +type MarginInterest struct { + XMLName xml.Name `xml:"MARGININTEREST"` + InvTran InvTran `xml:"INVTRAN"` + Total Amount `xml:"TOTAL"` + SubAcctFund subAcctType `xml:"SUBACCTFUND"` // Where did the money for the transaction come from or go to? CASH, MARGIN, SHORT, OTHER + Currency Currency `xml:"CURRENCY,omitempty"` // Represents the currency this transaction is in (instead of CURDEF in INVSTMTRS) if Valid() + OrigCurrency Currency `xml:"ORIGCURRENCY,omitempty"` // Represents the currency this transaction was converted to INVSTMTRS' CURDEF from if Valid +} + +// TransactionType returns a string representation of this transaction's type +func (t MarginInterest) TransactionType() string { + return "MARGININTEREST" +} + +// Reinvest is a single transaction that contains both income and an investment +// transaction. If servers can’t track this as a single transaction they should +// return an Income transaction and an InvTran. +type Reinvest struct { + XMLName xml.Name `xml:"REINVEST"` + InvTran InvTran `xml:"INVTRAN"` + SecID SecurityID `xml:"SECID"` + IncomeType incomeType `xml:"INCOMETYPE"` // Type of investment income: CGLONG (capital gains-long term), CGSHORT (capital gains-short term), DIV (dividend), INTEREST, MISC + Total Amount `xml:"TOTAL"` // Transaction total. Buys, sells, etc.:((quan. * (price +/- markup/markdown)) +/-(commission + fees + load + taxes + penalty + withholding + statewithholding)). Distributions, interest, margin interest, misc. expense, etc.: amount. Return of cap: cost basis + SubAcctSec subAcctType `xml:"SUBACCTSEC"` // Sub-account type for this security. One of CASH, MARGIN, SHORT, OTHER + Units Amount `xml:"UNITS"` // For stocks, MFs, other, number of shares held. Bonds = face value. Options = number of contracts + UnitPrice Amount `xml:"UNITPRICE"` // For stocks, MFs, other, price per share. Bonds = percentage of par. Option = premium per share of underlying security + Commission Amount `xml:"COMMISSION,omitempty"` + Taxes Amount `xml:"TAXES,omitempty"` + Fees Amount `xml:"FEES,omitempty"` + Load Amount `xml:"LOAD,omitempty"` + TaxExempt Boolean `xml:"TAXEXEMPT,omitempty"` // Tax-exempt transaction + Currency Currency `xml:"CURRENCY,omitempty"` // Represents the currency this transaction is in (instead of CURDEF in INVSTMTRS) if Valid() + OrigCurrency Currency `xml:"ORIGCURRENCY,omitempty"` // Represents the currency this transaction was converted to INVSTMTRS' CURDEF from if Valid + Inv401kSource inv401kSource `xml:"INV401KSOURCE,omitempty"` // Source of money for this transaction. One of PRETAX, AFTERTAX, MATCH, PROFITSHARING, ROLLOVER, OTHERVEST, OTHERNONVEST for 401(k) accounts. Default if not present is OTHERNONVEST. The following cash source types are subject to vesting: MATCH, PROFITSHARING, and OTHERVEST +} + +// TransactionType returns a string representation of this transaction's type +func (t Reinvest) TransactionType() string { + return "REINVEST" +} + +// RetOfCap represents a transaction where capital is being returned to the +// account holder +type RetOfCap struct { + XMLName xml.Name `xml:"RETOFCAP"` + InvTran InvTran `xml:"INVTRAN"` + SecID SecurityID `xml:"SECID"` + Total Amount `xml:"TOTAL"` + SubAcctSec subAcctType `xml:"SUBACCTSEC"` // Sub-account type for this security. One of CASH, MARGIN, SHORT, OTHER + SubAcctFund subAcctType `xml:"SUBACCTFUND"` // Where did the money for the transaction come from or go to? CASH, MARGIN, SHORT, OTHER + Currency Currency `xml:"CURRENCY,omitempty"` // Represents the currency this transaction is in (instead of CURDEF in INVSTMTRS) if Valid() + OrigCurrency Currency `xml:"ORIGCURRENCY,omitempty"` // Represents the currency this transaction was converted to INVSTMTRS' CURDEF from if Valid + Inv401kSource inv401kSource `xml:"INV401KSOURCE,omitempty"` // Source of money for this transaction. One of PRETAX, AFTERTAX, MATCH, PROFITSHARING, ROLLOVER, OTHERVEST, OTHERNONVEST for 401(k) accounts. Default if not present is OTHERNONVEST. The following cash source types are subject to vesting: MATCH, PROFITSHARING, and OTHERVEST +} + +// TransactionType returns a string representation of this transaction's type +func (t RetOfCap) TransactionType() string { + return "RETOFCAP" +} + +// SellDebt represents the sale of a debt security. Used when debt is sold, +// called, or reaches maturity. +type SellDebt struct { + XMLName xml.Name `xml:"SELLDEBT"` + InvSell InvSell `xml:"INVSELL"` + SellReason sellReason `xml:"SELLREASON"` // CALL (the debt was called), SELL (the debt was sold), MATURITY (the debt reached maturity) + AccrdInt Amount `xml:"ACCRDINT,omitempty"` // Accrued interest +} + +// TransactionType returns a string representation of this transaction's type +func (t SellDebt) TransactionType() string { + return "SELLDEBT" +} + +// SellMF represents a transaction selling a mutual fund +type SellMF struct { + XMLName xml.Name `xml:"SELLMF"` + InvSell InvSell `xml:"INVSELL"` + SellType sellType `xml:"SELLTYPE"` // Type of sell. SELL, SELLSHORT + AvgCostBasis Amount `xml:"AVGCOSTBASIS"` + RelFiTID String `xml:"RELFITID,omitempty"` // used to relate transactions associated with mutual fund exchanges +} + +// TransactionType returns a string representation of this transaction's type +func (t SellMF) TransactionType() string { + return "SELLMF" +} + +// SellOpt represents a transaction selling an option. Depending on the value +// of OptSellType, can be used to sell a previously bought option or write a +// new option. +type SellOpt struct { + XMLName xml.Name `xml:"SELLOPT"` + InvSell InvSell `xml:"INVSELL"` + OptSellType optSellType `xml:"OPTSELLTYPE"` // For options, type of sell: SELLTOCLOSE, SELLTOOPEN. The SELLTOCLOSE action is selling a previously bought option. The SELLTOOPEN action is writing an option + ShPerCtrct Int `xml:"SHPERCTRCT"` // Shares per contract + RelFiTID String `xml:"RELFITID,omitempty"` // used to relate transactions associated with mutual fund exchanges + RelType relType `xml:"RELTYPE,omitempty"` // Related option transaction type: SPREAD, STRADDLE, NONE, OTHER + Secured secured `xml:"SECURED,omitempty"` // NAKED, COVERED +} + +// TransactionType returns a string representation of this transaction's type +func (t SellOpt) TransactionType() string { + return "SELLOPT" +} + +// SellOther represents a transaction selling a security type not covered by +// the other Sell* structs +type SellOther struct { + XMLName xml.Name `xml:"SELLOTHER"` + InvSell InvSell `xml:"INVSELL"` +} + +// TransactionType returns a string representation of this transaction's type +func (t SellOther) TransactionType() string { + return "SELLOTHER" +} + +// SellStock represents a transaction selling stock +type SellStock struct { + XMLName xml.Name `xml:"SELLSTOCK"` + InvSell InvSell `xml:"INVSELL"` + SellType sellType `xml:"SELLTYPE"` // Type of sell. SELL, SELLSHORT +} + +// TransactionType returns a string representation of this transaction's type +func (t SellStock) TransactionType() string { + return "SELLSTOCK" +} + +// Split represents a stock or mutual fund split +type Split struct { + XMLName xml.Name `xml:"SPLIT"` + InvTran InvTran `xml:"INVTRAN"` + SecID SecurityID `xml:"SECID"` + SubAcctSec subAcctType `xml:"SUBACCTSEC"` // Sub-account type for this security. One of CASH, MARGIN, SHORT, OTHER + OldUnits Amount `xml:"OLDUNITS"` // number of shares before the split + NewUnits Amount `xml:"NEWUNITS"` // number of shares after the split + Numerator Int `xml:"NUMERATOR"` // split ratio numerator + Denominator Int `xml:"DENOMINATOR"` // split ratio denominator + Currency Currency `xml:"CURRENCY,omitempty"` // Represents the currency this transaction is in (instead of CURDEF in INVSTMTRS) if Valid() + OrigCurrency Currency `xml:"ORIGCURRENCY,omitempty"` // Represents the currency this transaction was converted to INVSTMTRS' CURDEF from if Valid + FracCash Amount `xml:"FRACCASH,omitempty"` // cash for fractional units + SubAcctFund subAcctType `xml:"SUBACCTFUND,omitempty"` // Where did the money for the transaction come from or go to? CASH, MARGIN, SHORT, OTHER + Inv401kSource inv401kSource `xml:"INV401KSOURCE,omitempty"` // Source of money for this transaction. One of PRETAX, AFTERTAX, MATCH, PROFITSHARING, ROLLOVER, OTHERVEST, OTHERNONVEST for 401(k) accounts. Default if not present is OTHERNONVEST. The following cash source types are subject to vesting: MATCH, PROFITSHARING, and OTHERVEST +} + +// TransactionType returns a string representation of this transaction's type +func (t Split) TransactionType() string { + return "SPLIT" +} + +// Transfer represents the transfer of securities into or out of an account +type Transfer struct { + XMLName xml.Name `xml:"TRANSFER"` + InvTran InvTran `xml:"INVTRAN"` + SecID SecurityID `xml:"SECID"` + SubAcctSec subAcctType `xml:"SUBACCTSEC"` // Sub-account type for this security. One of CASH, MARGIN, SHORT, OTHER + Units Amount `xml:"UNITS"` // For stocks, MFs, other, number of shares held. Bonds = face value. Options = number of contracts + TferAction tferAction `xml:"TFERACTION"` // One of IN, OUT + PosType posType `xml:"POSTYPE"` // Position type. One of LONG, SHORT + InvAcctFrom InvAcct `xml:"INVACCTFROM,omitempty"` + AvgCostBasis Amount `xml:"AVGCOSTBASIS,omitempty"` + UnitPrice Amount `xml:"UNITPRICE,omitempty"` // For stocks, MFs, other, price per share. Bonds = percentage of par. Option = premium per share of underlying security + DtPurchase *Date `xml:"DTPURCHASE,omitempty"` + Inv401kSource inv401kSource `xml:"INV401KSOURCE,omitempty"` // Source of money for this transaction. One of PRETAX, AFTERTAX, MATCH, PROFITSHARING, ROLLOVER, OTHERVEST, OTHERNONVEST for 401(k) accounts. Default if not present is OTHERNONVEST. The following cash source types are subject to vesting: MATCH, PROFITSHARING, and OTHERVEST +} + +// TransactionType returns a string representation of this transaction's type +func (t Transfer) TransactionType() string { + return "TRANSFER" +} + +// InvTransaction is a generic interface met by all investment transactions +// (Buy*, Sell*, & co.) +type InvTransaction interface { + TransactionType() string +} + +// InvBankTransaction is a banking transaction performed in an investment +// account. This represents all transactions not related to securities - for +// instance, funding the account using cash from another bank. +type InvBankTransaction struct { + XMLName xml.Name `xml:"INVBANKTRAN"` + Transactions []Transaction `xml:"STMTTRN,omitempty"` + SubAcctFund subAcctType `xml:"SUBACCTFUND"` // Where did the money for the transaction come from or go to? CASH, MARGIN, SHORT, OTHER +} + +// InvTranList represents a list of investment account transactions. It +// includes the date range its transactions cover, as well as the bank- and +// security-related transactions themselves. It must be unmarshalled manually +// due to the structure (don't know what kind of InvTransaction is coming next) +type InvTranList struct { + XMLName xml.Name `xml:"INVTRANLIST"` + DtStart Date + DtEnd Date // This is the value that should be sent as in the next InvStatementRequest to ensure that no transactions are missed + InvTransactions []InvTransaction + BankTransactions []InvBankTransaction +} + +// 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) + if err != nil { + return err + } else if end, ok := tok.(xml.EndElement); ok && end.Name.Local == start.Name.Local { + // If we found the end of our starting element, we're done parsing + return nil + } else if startElement, ok := tok.(xml.StartElement); ok { + switch startElement.Name.Local { + case "DTSTART": + var dtstart Date + if err := d.DecodeElement(&dtstart, &startElement); err != nil { + return err + } + l.DtStart = dtstart + case "DTEND": + var dtend Date + if err := d.DecodeElement(&dtend, &startElement); err != nil { + return err + } + l.DtEnd = dtend + case "BUYDEBT": + var tran BuyDebt + if err := d.DecodeElement(&tran, &startElement); err != nil { + return err + } + l.InvTransactions = append(l.InvTransactions, InvTransaction(tran)) + case "BUYMF": + var tran BuyMF + if err := d.DecodeElement(&tran, &startElement); err != nil { + return err + } + l.InvTransactions = append(l.InvTransactions, InvTransaction(tran)) + case "BUYOPT": + var tran BuyOpt + if err := d.DecodeElement(&tran, &startElement); err != nil { + return err + } + l.InvTransactions = append(l.InvTransactions, InvTransaction(tran)) + case "BUYOTHER": + var tran BuyOther + if err := d.DecodeElement(&tran, &startElement); err != nil { + return err + } + l.InvTransactions = append(l.InvTransactions, InvTransaction(tran)) + case "BUYSTOCK": + var tran BuyStock + if err := d.DecodeElement(&tran, &startElement); err != nil { + return err + } + l.InvTransactions = append(l.InvTransactions, InvTransaction(tran)) + case "CLOSUREOPT": + var tran ClosureOpt + if err := d.DecodeElement(&tran, &startElement); err != nil { + return err + } + l.InvTransactions = append(l.InvTransactions, InvTransaction(tran)) + case "INCOME": + var tran Income + if err := d.DecodeElement(&tran, &startElement); err != nil { + return err + } + l.InvTransactions = append(l.InvTransactions, InvTransaction(tran)) + case "INVEXPENSE": + var tran InvExpense + if err := d.DecodeElement(&tran, &startElement); err != nil { + return err + } + l.InvTransactions = append(l.InvTransactions, InvTransaction(tran)) + case "JRNLFUND": + var tran JrnlFund + if err := d.DecodeElement(&tran, &startElement); err != nil { + return err + } + l.InvTransactions = append(l.InvTransactions, InvTransaction(tran)) + case "JRNLSEC": + var tran JrnlSec + if err := d.DecodeElement(&tran, &startElement); err != nil { + return err + } + l.InvTransactions = append(l.InvTransactions, InvTransaction(tran)) + case "MARGININTEREST": + var tran MarginInterest + if err := d.DecodeElement(&tran, &startElement); err != nil { + return err + } + l.InvTransactions = append(l.InvTransactions, InvTransaction(tran)) + case "REINVEST": + var tran Reinvest + if err := d.DecodeElement(&tran, &startElement); err != nil { + return err + } + l.InvTransactions = append(l.InvTransactions, InvTransaction(tran)) + case "RETOFCAP": + var tran RetOfCap + if err := d.DecodeElement(&tran, &startElement); err != nil { + return err + } + l.InvTransactions = append(l.InvTransactions, InvTransaction(tran)) + case "SELLDEBT": + var tran SellDebt + if err := d.DecodeElement(&tran, &startElement); err != nil { + return err + } + l.InvTransactions = append(l.InvTransactions, InvTransaction(tran)) + case "SELLMF": + var tran SellMF + if err := d.DecodeElement(&tran, &startElement); err != nil { + return err + } + l.InvTransactions = append(l.InvTransactions, InvTransaction(tran)) + case "SELLOPT": + var tran SellOpt + if err := d.DecodeElement(&tran, &startElement); err != nil { + return err + } + l.InvTransactions = append(l.InvTransactions, InvTransaction(tran)) + case "SELLOTHER": + var tran SellOther + if err := d.DecodeElement(&tran, &startElement); err != nil { + return err + } + l.InvTransactions = append(l.InvTransactions, InvTransaction(tran)) + case "SELLSTOCK": + var tran SellStock + if err := d.DecodeElement(&tran, &startElement); err != nil { + return err + } + l.InvTransactions = append(l.InvTransactions, InvTransaction(tran)) + case "SPLIT": + var tran Split + if err := d.DecodeElement(&tran, &startElement); err != nil { + return err + } + l.InvTransactions = append(l.InvTransactions, InvTransaction(tran)) + case "TRANSFER": + var tran Transfer + if err := d.DecodeElement(&tran, &startElement); err != nil { + return err + } + l.InvTransactions = append(l.InvTransactions, InvTransaction(tran)) + case "INVBANKTRAN": + var tran InvBankTransaction + if err := d.DecodeElement(&tran, &startElement); err != nil { + return err + } + l.BankTransactions = append(l.BankTransactions, tran) + default: + return errors.New("Invalid INVTRANLIST child tag: " + startElement.Name.Local) + } + } else { + return errors.New("Didn't find an opening element") + } + } +} + +// MarshalXML handles marshalling an InvTranList element to an SGML/XML string +func (l *InvTranList) MarshalXML(e *xml.Encoder, start xml.StartElement) error { + invTranListElement := xml.StartElement{Name: xml.Name{Local: "INVTRANLIST"}} + if err := e.EncodeToken(invTranListElement); err != nil { + return err + } + err := e.EncodeElement(&l.DtStart, xml.StartElement{Name: xml.Name{Local: "DTSTART"}}) + if err != nil { + return err + } + err = e.EncodeElement(&l.DtEnd, xml.StartElement{Name: xml.Name{Local: "DTEND"}}) + if err != nil { + return err + } + for _, t := range l.InvTransactions { + start := xml.StartElement{Name: xml.Name{Local: t.TransactionType()}} + switch tran := t.(type) { + case BuyDebt: + if err := e.EncodeElement(&tran, start); err != nil { + return err + } + case BuyMF: + if err := e.EncodeElement(&tran, start); err != nil { + return err + } + case BuyOpt: + if err := e.EncodeElement(&tran, start); err != nil { + return err + } + case BuyOther: + if err := e.EncodeElement(&tran, start); err != nil { + return err + } + case BuyStock: + if err := e.EncodeElement(&tran, start); err != nil { + return err + } + case ClosureOpt: + if err := e.EncodeElement(&tran, start); err != nil { + return err + } + case Income: + if err := e.EncodeElement(&tran, start); err != nil { + return err + } + case InvExpense: + if err := e.EncodeElement(&tran, start); err != nil { + return err + } + case JrnlFund: + if err := e.EncodeElement(&tran, start); err != nil { + return err + } + case JrnlSec: + if err := e.EncodeElement(&tran, start); err != nil { + return err + } + case MarginInterest: + if err := e.EncodeElement(&tran, start); err != nil { + return err + } + case Reinvest: + if err := e.EncodeElement(&tran, start); err != nil { + return err + } + case RetOfCap: + if err := e.EncodeElement(&tran, start); err != nil { + return err + } + case SellDebt: + if err := e.EncodeElement(&tran, start); err != nil { + return err + } + case SellMF: + if err := e.EncodeElement(&tran, start); err != nil { + return err + } + case SellOpt: + if err := e.EncodeElement(&tran, start); err != nil { + return err + } + case SellOther: + if err := e.EncodeElement(&tran, start); err != nil { + return err + } + case SellStock: + if err := e.EncodeElement(&tran, start); err != nil { + return err + } + case Split: + if err := e.EncodeElement(&tran, start); err != nil { + return err + } + case Transfer: + if err := e.EncodeElement(&tran, start); err != nil { + return err + } + default: + return errors.New("Invalid INVTRANLIST child type: " + tran.TransactionType()) + } + } + for _, tran := range l.BankTransactions { + err = e.EncodeElement(&tran, xml.StartElement{Name: xml.Name{Local: "INVBANKTRAN"}}) + if err != nil { + return err + } + } + if err := e.EncodeToken(invTranListElement.End()); err != nil { + return err + } + return nil +} + +// InvPosition contains generic position information included in each of the +// other *Position types +type InvPosition struct { + XMLName xml.Name `xml:"INVPOS"` + SecID SecurityID `xml:"SECID"` + HeldInAcct subAcctType `xml:"HELDINACCT"` // Sub-account type, one of CASH, MARGIN, SHORT, OTHER + PosType posType `xml:"POSTYPE"` // SHORT = Writer for options, Short for all others; LONG = Holder for options, Long for all others. + Units Amount `xml:"UNITS"` // For stocks, MFs, other, number of shares held. Bonds = face value. Options = number of contracts + UnitPrice Amount `xml:"UNITPRICE"` // For stocks, MFs, other, price per share. Bonds = percentage of par. Option = premium per share of underlying security + MktVal Amount `xml:"MKTVAL"` // Market value of this position + AvgCostBasis Amount `xml:"AVGCOSTBASIS,omitempty"` // + DtPriceAsOf Date `xml:"DTPRICEASOF"` // Date and time of unit price and market value, and cost basis. If this date is unknown, use 19900101 as the placeholder; do not use 0, + Currency *Currency `xml:"CURRENCY,omitempty"` // Overriding currency for UNITPRICE + Memo String `xml:"MEMO,omitempty"` + Inv401kSource inv401kSource `xml:"INV401KSOURCE,omitempty"` // One of PRETAX, AFTERTAX, MATCH, PROFITSHARING, ROLLOVER, OTHERVEST, OTHERNONVEST for 401(k) accounts. Default if not present is OTHERNONVEST. The following cash source types are subject to vesting: MATCH, PROFITSHARING, and OTHERVEST +} + +// Position is an interface satisfied by all the other *Position types +type Position interface { + PositionType() string +} + +// DebtPosition represents a position held in a debt security +type DebtPosition struct { + XMLName xml.Name `xml:"POSDEBT"` + InvPos InvPosition `xml:"INVPOS"` +} + +// PositionType returns a string representation of this position's type +func (p DebtPosition) PositionType() string { + return "POSDEBT" +} + +// MFPosition represents a position held in a mutual fund +type MFPosition struct { + XMLName xml.Name `xml:"POSMF"` + InvPos InvPosition `xml:"INVPOS"` + UnitsStreet Amount `xml:"UNITSSTREET,omitempty"` // Units in the FI’s street name + UnitsUser Amount `xml:"UNITSUSER,omitempty"` // Units in the user's name directly + ReinvDiv Boolean `xml:"REINVDIV,omitempty"` // Reinvest dividends + ReinvCG Boolean `xml:"REINVCG,omitempty"` // Reinvest capital gains +} + +// PositionType returns a string representation of this position's type +func (p MFPosition) PositionType() string { + return "POSMF" +} + +// OptPosition represents a position held in an option +type OptPosition struct { + XMLName xml.Name `xml:"POSOPT"` + InvPos InvPosition `xml:"INVPOS"` + Secured secured `xml:"SECURED,omitempty"` // One of NAKED, COVERED +} + +// PositionType returns a string representation of this position's type +func (p OptPosition) PositionType() string { + return "POSOPT" +} + +// OtherPosition represents a position held in a security type not covered by +// the other *Position elements +type OtherPosition struct { + XMLName xml.Name `xml:"POSOTHER"` + InvPos InvPosition `xml:"INVPOS"` +} + +// PositionType returns a string representation of this position's type +func (p OtherPosition) PositionType() string { + return "POSOTHER" +} + +// StockPosition represents a position held in a stock +type StockPosition struct { + XMLName xml.Name `xml:"POSSTOCK"` + InvPos InvPosition `xml:"INVPOS"` + UnitsStreet Amount `xml:"UNITSSTREET,omitempty"` // Units in the FI’s street name + UnitsUser Amount `xml:"UNITSUSER,omitempty"` // Units in the user's name directly + ReinvDiv Boolean `xml:"REINVDIV,omitempty"` // Reinvest dividends +} + +// PositionType returns a string representation of this position's type +func (p StockPosition) PositionType() string { + return "POSSTOCK" +} + +// PositionList represents a list of positions held in securities in an +// investment account +type PositionList []Position + +// UnmarshalXML handles unmarshalling a PositionList from an XML string +func (p *PositionList) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + for { + tok, err := nextNonWhitespaceToken(d) + if err != nil { + return err + } else if end, ok := tok.(xml.EndElement); ok && end.Name.Local == start.Name.Local { + // If we found the end of our starting element, we're done parsing + return nil + } else if startElement, ok := tok.(xml.StartElement); ok { + switch startElement.Name.Local { + case "POSDEBT": + var position DebtPosition + if err := d.DecodeElement(&position, &startElement); err != nil { + return err + } + *p = append(*p, Position(position)) + case "POSMF": + var position MFPosition + if err := d.DecodeElement(&position, &startElement); err != nil { + return err + } + *p = append(*p, Position(position)) + case "POSOPT": + var position OptPosition + if err := d.DecodeElement(&position, &startElement); err != nil { + return err + } + *p = append(*p, Position(position)) + case "POSOTHER": + var position OtherPosition + if err := d.DecodeElement(&position, &startElement); err != nil { + return err + } + *p = append(*p, Position(position)) + case "POSSTOCK": + var position StockPosition + if err := d.DecodeElement(&position, &startElement); err != nil { + return err + } + *p = append(*p, Position(position)) + default: + return errors.New("Invalid INVPOSLIST child tag: " + startElement.Name.Local) + } + } else { + return errors.New("Didn't find an opening element") + } + } +} + +// MarshalXML handles marshalling a PositionList to an XML string +func (p *PositionList) MarshalXML(e *xml.Encoder, start xml.StartElement) error { + invPosListElement := xml.StartElement{Name: xml.Name{Local: "INVPOSLIST"}} + if err := e.EncodeToken(invPosListElement); err != nil { + return err + } + for _, position := range *p { + start := xml.StartElement{Name: xml.Name{Local: position.PositionType()}} + switch pos := position.(type) { + case DebtPosition: + if err := e.EncodeElement(&pos, start); err != nil { + return err + } + case MFPosition: + if err := e.EncodeElement(&pos, start); err != nil { + return err + } + case OptPosition: + if err := e.EncodeElement(&pos, start); err != nil { + return err + } + case OtherPosition: + if err := e.EncodeElement(&pos, start); err != nil { + return err + } + case StockPosition: + if err := e.EncodeElement(&pos, start); err != nil { + return err + } + default: + return errors.New("Invalid INVPOSLIST child type: " + pos.PositionType()) + } + } + if err := e.EncodeToken(invPosListElement.End()); err != nil { + return err + } + return nil +} + +// InvBalance contains three (or optionally four) specified balances as well as +// a free-form list of generic balance information which may be provided by an +// FI. +type InvBalance struct { + XMLName xml.Name `xml:"INVBAL"` + AvailCash Amount `xml:"AVAILCASH"` // Available cash across all sub-accounts, including sweep funds + MarginBalance Amount `xml:"MARGINBALANCE"` // Negative means customer has borrowed funds + ShortBalance Amount `xml:"SHORTBALANCE"` // Always positive, market value of all short positions + BuyPower Amount `xml:"BUYPOWER, omitempty"` + BalList []Balance `xml:"BALLIST>BAL,omitempty"` +} + +// OO represents a generic open investment order. It is included in the other +// OO* elements. +type OO struct { + XMLName xml.Name `xml:"OO"` + FiTID String `xml:"FITID"` + SrvrTID String `xml:"SRVRTID,omitempty"` + SecID SecurityID `xml:"SECID"` + DtPlaced Date `xml:"DTPLACED"` // Date the order was placed + Units Amount `xml:"UNITS"` // Quantity of the security the open order is for + SubAcct subAcctType `xml:"SUBACCT"` // One of CASH, MARGIN, SHORT, OTHER + Duration duration `xml:"DURATION"` // How long the order is good for. One of DAY, GOODTILCANCEL, IMMEDIATE + Restriction restriction `xml:"RESTRICTION"` // Special restriction on the order: One of ALLORNONE, MINUNITS, NONE + MinUnits Amount `xml:"MINUNITS,omitempty"` // Minimum number of units that must be filled for the order + LimitPrice Amount `xml:"LIMITPRICE,omitempty"` + StopPrice Amount `xml:"STOPPRICE,omitempty"` + Memo String `xml:"MEMO,omitempty"` + Currency *Currency `xml:"CURRENCY,omitempty"` // Overriding currency for UNITPRICE + Inv401kSource inv401kSource `xml:"INV401KSOURCE,omitempty"` // One of PRETAX, AFTERTAX, MATCH, PROFITSHARING, ROLLOVER, OTHERVEST, OTHERNONVEST for 401(k) accounts. Default if not present is OTHERNONVEST. The following cash source types are subject to vesting: MATCH, PROFITSHARING, and OTHERVEST +} + +// OpenOrder is an interface satisfied by all the OO* elements. +type OpenOrder interface { + OrderType() string +} + +// OOBuyDebt represents an open order to purchase a debt security +type OOBuyDebt struct { + XMLName xml.Name `xml:"OOBUYDEBT"` + OO OO `xml:"OO"` + Auction Boolean `xml:"AUCTION"` // whether the debt should be purchased at the auction + DtAuction *Date `xml:"DTAUCTION,omitempty"` +} + +// OrderType returns a string representation of this order's type +func (o OOBuyDebt) OrderType() string { + return "OOBUYDEBT" +} + +// OOBuyMF represents an open order to purchase a mutual fund +type OOBuyMF struct { + XMLName xml.Name `xml:"OOBUYMF"` + OO OO `xml:"OO"` + BuyType buyType `xml:"BUYTYPE"` // One of BUY, BUYTOCOVER + UnitType unitType `xml:"UNITTYPE"` // What the units represent: one of SHARES, CURRENCY +} + +// OrderType returns a string representation of this order's type +func (o OOBuyMF) OrderType() string { + return "OOBUYMF" +} + +// OOBuyOpt represents an open order to purchase an option +type OOBuyOpt struct { + XMLName xml.Name `xml:"OOBUYOPT"` + OO OO `xml:"OO"` + OptBuyType optBuyType `xml:"OPTBUYTYPE"` // One of BUYTOOPEN, BUYTOCLOSE +} + +// OrderType returns a string representation of this order's type +func (o OOBuyOpt) OrderType() string { + return "OOBUYOPT" +} + +// OOBuyOther represents an open order to purchase a security type not covered +// by the other OOBuy* elements +type OOBuyOther struct { + XMLName xml.Name `xml:"OOBUYOTHER"` + OO OO `xml:"OO"` + UnitType unitType `xml:"UNITTYPE"` // What the units represent: one of SHARES, CURRENCY +} + +// OrderType returns a string representation of this order's type +func (o OOBuyOther) OrderType() string { + return "OOBUYOTHER" +} + +// OOBuyStock represents an open order to purchase stock +type OOBuyStock struct { + XMLName xml.Name `xml:"OOBUYSTOCK"` + OO OO `xml:"OO"` + BuyType buyType `xml:"BUYTYPE"` // One of BUY, BUYTOCOVER +} + +// OrderType returns a string representation of this order's type +func (o OOBuyStock) OrderType() string { + return "OOBUYSTOCK" +} + +// OOSellDebt represents an open order to sell a debt security +type OOSellDebt struct { + XMLName xml.Name `xml:"OOSELLDEBT"` + OO OO `xml:"OO"` +} + +// OrderType returns a string representation of this order's type +func (o OOSellDebt) OrderType() string { + return "OOSELLDEBT" +} + +// OOSellMF represents an open order to sell a mutual fund +type OOSellMF struct { + XMLName xml.Name `xml:"OOSELLMF"` + OO OO `xml:"OO"` + SellType sellType `xml:"SELLTYPE"` // One of SELL, SELLSHORT + UnitType unitType `xml:"UNITTYPE"` // What the units represent: one of SHARES, CURRENCY + SellAll Boolean `xml:"SELLALL"` // Sell entire holding +} + +// OrderType returns a string representation of this order's type +func (o OOSellMF) OrderType() string { + return "OOSELLMF" +} + +// OOSellOpt represents an open order to sell an option +type OOSellOpt struct { + XMLName xml.Name `xml:"OOSELLOPT"` + OO OO `xml:"OO"` + OptSellType optSellType `xml:"OPTSELLTYPE"` // One of SELLTOOPEN, SELLTOCLOSE +} + +// OrderType returns a string representation of this order's type +func (o OOSellOpt) OrderType() string { + return "OOSELLOPT" +} + +// OOSellOther represents an open order to sell a security type not covered by +// the other OOSell* elements +type OOSellOther struct { + XMLName xml.Name `xml:"OOSELLOTHER"` + OO OO `xml:"OO"` + UnitType unitType `xml:"UNITTYPE"` // What the units represent: one of SHARES, CURRENCY +} + +// OrderType returns a string representation of this order's type +func (o OOSellOther) OrderType() string { + return "OOSELLOTHER" +} + +// OOSellStock represents an open order to sell stock +type OOSellStock struct { + XMLName xml.Name `xml:"OOSELLSTOCK"` + OO OO `xml:"OO"` + SellType sellType `xml:"SELLTYPE"` // One of SELL, SELLSHORT +} + +// OrderType returns a string representation of this order's type +func (o OOSellStock) OrderType() string { + return "OOSELLSTOCK" +} + +// OOSwitchMF represents an open order to switch to or purchase a different +// mutual fund +type OOSwitchMF struct { + XMLName xml.Name `xml:"SWITCHMF"` + OO OO `xml:"OO"` + SecID SecurityID `xml:"SECID"` // Security ID of the fund to switch to or purchase + UnitType unitType `xml:"UNITTYPE"` // What the units represent: one of SHARES, CURRENCY + SwitchAll Boolean `xml:"SWITCHALL"` // Switch entire holding +} + +// OrderType returns a string representation of this order's type +func (o OOSwitchMF) OrderType() string { + return "SWITCHMF" +} + +// OOList represents a list of open orders (OO* elements) +type OOList []OpenOrder + +// UnmarshalXML handles unmarshalling an OOList element from an XML string +func (o *OOList) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + for { + tok, err := nextNonWhitespaceToken(d) + if err != nil { + return err + } else if end, ok := tok.(xml.EndElement); ok && end.Name.Local == start.Name.Local { + // If we found the end of our starting element, we're done parsing + return nil + } else if startElement, ok := tok.(xml.StartElement); ok { + switch startElement.Name.Local { + case "OOBUYDEBT": + var oo OOBuyDebt + if err := d.DecodeElement(&oo, &startElement); err != nil { + return err + } + *o = append(*o, OpenOrder(oo)) + case "OOBUYMF": + var oo OOBuyMF + if err := d.DecodeElement(&oo, &startElement); err != nil { + return err + } + *o = append(*o, OpenOrder(oo)) + case "OOBUYOPT": + var oo OOBuyOpt + if err := d.DecodeElement(&oo, &startElement); err != nil { + return err + } + *o = append(*o, OpenOrder(oo)) + case "OOBUYOTHER": + var oo OOBuyOther + if err := d.DecodeElement(&oo, &startElement); err != nil { + return err + } + *o = append(*o, OpenOrder(oo)) + case "OOBUYSTOCK": + var oo OOBuyStock + if err := d.DecodeElement(&oo, &startElement); err != nil { + return err + } + *o = append(*o, OpenOrder(oo)) + case "OOSELLDEBT": + var oo OOSellDebt + if err := d.DecodeElement(&oo, &startElement); err != nil { + return err + } + *o = append(*o, OpenOrder(oo)) + case "OOSELLMF": + var oo OOSellMF + if err := d.DecodeElement(&oo, &startElement); err != nil { + return err + } + *o = append(*o, OpenOrder(oo)) + case "OOSELLOPT": + var oo OOSellOpt + if err := d.DecodeElement(&oo, &startElement); err != nil { + return err + } + *o = append(*o, OpenOrder(oo)) + case "OOSELLOTHER": + var oo OOSellOther + if err := d.DecodeElement(&oo, &startElement); err != nil { + return err + } + *o = append(*o, OpenOrder(oo)) + case "OOSELLSTOCK": + var oo OOSellStock + if err := d.DecodeElement(&oo, &startElement); err != nil { + return err + } + *o = append(*o, OpenOrder(oo)) + case "SWITCHMF": + var oo OOSwitchMF + if err := d.DecodeElement(&oo, &startElement); err != nil { + return err + } + *o = append(*o, OpenOrder(oo)) + default: + return errors.New("Invalid OOList child tag: " + startElement.Name.Local) + } + } else { + return errors.New("Didn't find an opening element") + } + } +} + +// MarshalXML handles marshalling an OOList to an XML string +func (o *OOList) MarshalXML(e *xml.Encoder, start xml.StartElement) error { + ooListElement := xml.StartElement{Name: xml.Name{Local: "INVOOLIST"}} + if err := e.EncodeToken(ooListElement); err != nil { + return err + } + for _, openorder := range *o { + start := xml.StartElement{Name: xml.Name{Local: openorder.OrderType()}} + switch oo := openorder.(type) { + case OOBuyDebt: + if err := e.EncodeElement(&oo, start); err != nil { + return err + } + case OOBuyMF: + if err := e.EncodeElement(&oo, start); err != nil { + return err + } + case OOBuyOpt: + if err := e.EncodeElement(&oo, start); err != nil { + return err + } + case OOBuyOther: + if err := e.EncodeElement(&oo, start); err != nil { + return err + } + case OOBuyStock: + if err := e.EncodeElement(&oo, start); err != nil { + return err + } + case OOSellDebt: + if err := e.EncodeElement(&oo, start); err != nil { + return err + } + case OOSellMF: + if err := e.EncodeElement(&oo, start); err != nil { + return err + } + case OOSellOpt: + if err := e.EncodeElement(&oo, start); err != nil { + return err + } + case OOSellOther: + if err := e.EncodeElement(&oo, start); err != nil { + return err + } + case OOSellStock: + if err := e.EncodeElement(&oo, start); err != nil { + return err + } + case OOSwitchMF: + if err := e.EncodeElement(&oo, start); err != nil { + return err + } + default: + return errors.New("Invalid OOLIST child type: " + oo.OrderType()) + } + } + if err := e.EncodeToken(ooListElement.End()); err != nil { + return err + } + return nil +} + +// ContribSecurity identifies current contribution allocation for a security in +// a 401(k) account +type ContribSecurity struct { + XMLName xml.Name `xml:"CONTRIBSECURITY"` + SecID SecurityID `xml:"SECID"` + PreTaxContribPct Amount `xml:"PRETAXCONTRIBPCT,omitempty"` // Percentage of each new employee pretax contribution allocated to this security, rate. + PreTaxContribAmt Amount `xml:"PRETAXCONTRIBAMT,omitempty"` // Fixed amount of each new employee pretax contribution allocated to this security, amount + AfterTaxContribPct Amount `xml:"AFTERTAXCONTRIBPCT,omitempty"` // Percentage of each new employee after tax contribution allocated to this security, rate. + AfterTaxContribAmt Amount `xml:"AFTERTAXCONTRIBAMT,omitempty"` // Fixed amount of each new employee pretax contribution allocated to this security, amount. + MatchContribPct Amount `xml:"MATCHCONTRIBPCT,omitempty"` // Percentage of each new employer match contribution allocated to this security, rate. + MatchContribAmt Amount `xml:"MATCHCONTRIBAMT,omitempty"` // Fixed amount of each new employer match contribution allocated to this security, amount. + ProfitSharingContribPct Amount `xml:"PROFITSHARINGCONTRIBPCT,omitempty"` // Percentage of each new employer profit sharing contribution allocated to this security, rate. + ProfitSharingContribAmt Amount `xml:"PROFITSHARINGCONTRIBAMT,omitempty"` // Fixed amount of each new employer profit sharing contribution allocated to this security, amount. + RolloverContribPct Amount `xml:"ROLLOVERCONTRIBPCT,omitempty"` // Percentage of new rollover contributions allocated to this security, rate. + RolloverContribAmt Amount `xml:"ROLLOVERCONTRIBAMT,omitempty"` // Fixed amount of new rollover contributions allocated to this security, amount. + OtherVestPct Amount `xml:"OTHERVESTPCT,omitempty"` // Percentage of each new other employer contribution allocated to this security, rate. + OtherVestAmt Amount `xml:"OTHERVESTAMT,omitempty"` // Fixed amount of each new other employer contribution allocated to this security, amount. + OtherNonVestPct Amount `xml:"OTHERNONVESTPCT,omitempty"` // Percentage of each new other employee contribution allocated to this security, rate. + OtherNonVestAmt Amount `xml:"OTHERNONVESTAMT,omitempty"` // Fixed amount of each new other employee contribution allocated to this security, amount +} + +// VestInfo provides the vesting percentage of a 401(k) account as of a +// particular date (past, present, or future) +type VestInfo struct { + XMLName xml.Name `xml:"VESTINFO"` + VestDate *Date `xml:"VESTDATE,omitempty"` // Date at which vesting percentage changes. Default (if empty) is that the vesting percentage below applies to the current date + VestPct Amount `xml:"VESTPCT"` +} + +// LoanInfo represents a loan outstanding against this 401(k) account +type LoanInfo struct { + XMLName xml.Name `xml:"VESTINFO"` + LoanID String `xml:"LOANID"` // Identifier of this loan + LoanDesc String `xml:"LOANDESC,omitempty"` // Loan description + InitialLoanBal Amount `xml:"INITIALLOANBAL,omitempty"` // Initial loan balance + LoanStartDate *Date `xml:"LOANSTARTDATE,omitempty"` // Start date of loan + CurrentLoanBal Amount `xml:"CURRENTLOANBAL"` // Current loan principal balance + DtAsOf *Date `xml:"DTASOF"` // Date and time of the current loan balance + LoanRate Amount `xml:"LOANRATE,omitempty"` // Loan annual interest rate + LoanPmtAmt Amount `xml:"LOANPMTAMT,omitempty"` // Loan payment amount + LoanPmtFreq loanPmtFreq `xml:"LOANPMTFREQ,omitempty"` // Frequency of loan repayments: WEEKLY, BIWEEKLY, TWICEMONTHLY, MONTHLY, FOURWEEKS, BIMONTHLY, QUARTERLY, SEMIANNUALLY, ANNUALLY, OTHER. See section 10.2.1 for calculation rules. + LoanPmtsInitial Int `xml:"LOANPMTSINITIAL,omitempty"` // Initial number of loan payments. + LoanPmtsRemaining Int `xml:"LOANPMTSREMAINING,omitempty"` // Remaining number of loan payments + LoanMaturityDate *Date `xml:"LOANMATURITYDATE,omitempty"` // Expected loan end date + LoanTotalProjInterest Amount `xml:"LOANTOTALPROJINTEREST,omitempty"` // Total projected interest to be paid on this loan + LoanInterestToDate Amount `xml:"LOANINTERESTTODATE,omitempty"` // Total interested paid to date on this loan + LoanExtPmtDate *Date `xml:"LOANNEXTPMTDATE,omitempty"` // Next payment due date +} + +// Inv401KSummaryAggregate represents the total of either contributions, +// withdrawals, or earnings made in each contribution type in a given period +// (dates specified in a containing Inv401KSummaryPeriod) +type Inv401KSummaryAggregate struct { + XMLName xml.Name // One of CONTRIBUTIONS, WITHDRAWALS, EARNINGS + PreTax Amount `xml:"PRETAX,omitempty"` // Pretax contributions, withdrawals, or earlings. + AfterTax Amount `xml:"AFTERTAX,omitempty"` // After tax contributions, withdrawals, or earlings. + Match Amount `xml:"MATCH,omitempty"` // Employer matching contributions, withdrawals, or earlings. + ProfitSharing Amount `xml:"PROFITSHARING,omitempty"` // Profit sharing contributions, withdrawals, or earlings. + Rollover Amount `xml:"ROLLOVER,omitempty"` // Rollover contributions, withdrawals, or earlings. + OtherVest Amount `xml:"OTHERVEST,omitempty"` // Other vesting contributions, withdrawals, or earlings. + OtherNonVest Amount `xml:"OTHERNONVEST,omitempty"` // Other non-vesting contributions, withdrawals, or earlings. + Total Amount `xml:"TOTAL"` // Sum of contributions, withdrawals, or earlings from all fund sources. +} + +// Inv401KSummaryPeriod contains the total contributions, withdrawals, and +// earnings made in the given date range +type Inv401KSummaryPeriod struct { + XMLName xml.Name // One of YEARTODATE, INCEPTODATE, or PERIODTODATE + DtStart Date `xml:"DTSTART"` + DtEnd Date `xml:"DTEND"` + Contributions *Inv401KSummaryAggregate `xml:"CONTRIBUTIONS,omitempty"` // 401(k) contribution aggregate. Note: this includes loan payments. + Withdrawls *Inv401KSummaryAggregate `xml:"WITHDRAWLS,omitempty"` // 401(k) withdrawals aggregate. Note: this includes loan withdrawals. + Earnings *Inv401KSummaryAggregate `xml:"EARNINGS,omitempty"` // 401(k) earnings aggregate. This is the market value change. It includes dividends/interest, and capital gains - realized and unrealized. +} + +// Inv401K is included in InvStatementResponse for 401(k) accounts and provides +// a summary of the 401(k) specific information about the user's account. +type Inv401K struct { + XMLName xml.Name `xml:"INV401K"` + EmployerName String `xml:"EMPLOYERNAME"` + PlanID String `xml:"PLANID,omitempty"` // Plan number + PlanJoinDate *Date `xml:"PLANJOINDATE,omitempty"` // Date the employee joined the plan + EmployerContactInfo String `xml:"EMPLOYERCONTACTINFO,omitempty"` // Name of contact person at employer, plus any available contact information, such as phone number + BrokerContactInfo String `xml:"BROKERCONTACTINFO,omitempty"` // Name of contact person at broker, plus any available contact information, such as phone number + DeferPctPreTax Amount `xml:"DEFERPCTPRETAX,omitempty"` // Percent of employee salary deferred before tax + DeferPctAfterTax Amount `xml:"DEFERPCTAFTERTAX,omitempty"` // Percent of employee salary deferred after tax + + // Aggregate containing employer match information. Absent if employer does not contribute matching funds. + MatchPct Amount `xml:"MATCHINFO>MATCHPCT,omitempty"` // Percent of employee contribution matched, e.g., 75% if contribution rate is $0.75/$1.00 + MaxMatchAmt Amount `xml:"MATCHINFO>MAXMATCHAMT,omitempty"` // Maximum employer contribution amount in any year + MaxMatchPct Amount `xml:"MATCHINFO>MAXMATCHPCT,omitempty"` // Current maximum employer contribution percentage. Maximum match in a year is MAXMATCHPCT up to the MAXMATCHAMT, if provided + StartOfYear *Date `xml:"MATCHINFO>STARTOFYEAR,omitempty"` // Specifies when the employer contribution max is reset. Some plans have a maximum based on the company fiscal year rather than calendar year. Assume calendar year if omitted. Only the month and day (MMDD) are used; year (YYYY) and time are ignored + BaseMatchAmt Amount `xml:"MATCHINFO>BASEMATCHAMT"` // Specifies a fixed dollar amount contributed by the employer if the employee participates in the plan at all. This may be present in addition to the . $0 if omitted + BaseMatchPct Amount `xml:"MATCHINFO>BASEMATCHPCT"` // Specifies a fixed percent of employee salary matched if the employee participates in the plan at all. This may be present in addition to the MATCHPCT>. 0% if omitted. Base match in a year is BASEMATCHPCT up to the BASEMATCHAMT,if provided + ContribInfo []ContribSecurity `xml:"CONTRIBINTO>CONTRIBSECURITY"` // Aggregate to describe how new contributions are distributed among the available securities. + CurrentVestPct Amount `xml:"CURRENTVESTPCT,omitempty"` // Estimated percentage of employer contributions vested as of the current date. If omitted, assume 100% + VestInfo []VestInfo `xml:"VESTINFO,omitempty"` // Vest change dates. Provides the vesting percentage as of any particular past, current, or future date. 0 or more. + LoanInfo []LoanInfo `xml:"LOANINFO,omitempty"` // List of any loans outstanding against this account + YearToDateSummary Inv401KSummaryPeriod `xml:"INV401KSUMMARY>YEARTODATE"` // Contributions to date for this calendar year. + InceptToDateSummary *Inv401KSummaryPeriod `xml:"INV401KSUMMARY>INCEPTODATE,omitempty"` // Total contributions to date (since inception) + PeriodToDate *Inv401KSummaryPeriod `xml:"INV401KSUMMARY>PERIODTODATE,omitempty"` // Total contributions this contribution period +} + +// Inv401KBal provides the balances for different 401(k) subaccount types, as +// well as the total cash value of the securities held +type Inv401KBal struct { + XMLName xml.Name `xml:"INV401KBAL"` + CashBal Amount `xml:"CASHBAL,omitempty"` // Available cash balance + PreTax Amount `xml:"PRETAX,omitempty"` // Current value of all securities purchased with Before Tax Employee contributions + AfterTax Amount `xml:"AFTERTAX,omitempty"` // Current value of all securities purchased with After Tax Employee contributions + Match Amount `xml:"MATCH,omitempty"` // Current value of all securities purchased with Employer Match contributions + ProfitSharing Amount `xml:"PROFITSHARING,omitempty"` // Current value of all securities purchased with Employer Profit Sharing contributions + Rollover Amount `xml:"ROLLOVER,omitempty"` // Current value of all securities purchased with Rollover contributions + OtherVest Amount `xml:"OTHERVEST,omitempty"` // Current value of all securities purchased with Other (vesting) Employer contributions + OtherNonVest Amount `xml:"OTHERNONVEST,omitempty"` // Current value of all securities purchased with Other (non-vesting) Employer contributions + Total Amount `xml:"TOTAL"` // Current value of all securities purchased with all contributions + BalList []Balance `xml:"BALLIST>BAL,omitempty"` +} + +// InvStatementResponse includes requested transaction, position, open order, +// and balance information for an investment account. It is in response to an +// InvStatementRequest or sometimes provided as part of an OFX file downloaded +// manually from an FI. +type InvStatementResponse struct { + XMLName xml.Name `xml:"INVSTMTTRNRS"` + TrnUID UID `xml:"TRNUID"` + Status Status `xml:"STATUS"` + CltCookie String `xml:"CLTCOOKIE,omitempty"` + // TODO `xml:"OFXEXTENSION,omitempty"` + DtAsOf Date `xml:"INVSTMTRS>DTASOF"` + CurDef CurrSymbol `xml:"INVSTMTRS>CURDEF"` + InvAcctFrom InvAcct `xml:"INVSTMTRS>INVACCTFROM"` + InvTranList *InvTranList `xml:"INVSTMTRS>INVTRANLIST,omitempty"` + InvPosList PositionList `xml:"INVSTMTRS>INVPOSLIST,omitempty"` + InvBal *InvBalance `xml:"INVSTMTRS>INVBAL,omitempty"` + InvOOList OOList `xml:"INVSTMTRS>INVOOLIST,omitempty"` + MktgInfo String `xml:"INVSTMTRS>MKTGINFO,omitempty"` // Marketing information + Inv401K *Inv401K `xml:"INVSTMTRS>INV401K,omitempty"` + Inv401KBal *Inv401KBal `xml:"INVSTMTRS>INV401KBAL,omitempty"` +} + +// Name returns the name of the top-level transaction XML/SGML element +func (sr *InvStatementResponse) Name() string { + return "INVSTMTTRNRS" +} + +// Valid returns (true, nil) if this struct was valid OFX when unmarshalled +func (sr *InvStatementResponse) Valid(version ofxVersion) (bool, error) { + if ok, err := sr.TrnUID.Valid(); !ok { + return false, err + } + //TODO implement + return true, nil +} + +// Type returns which message set this message belongs to (which Response +// element of type []Message it belongs to) +func (sr *InvStatementResponse) Type() messageType { + return InvStmtRs +} diff --git a/vendor/github.com/aclindsa/ofxgo/leaf_elements.go b/vendor/github.com/aclindsa/ofxgo/leaf_elements.go new file mode 100644 index 0000000..a888caa --- /dev/null +++ b/vendor/github.com/aclindsa/ofxgo/leaf_elements.go @@ -0,0 +1,344 @@ +package ofxgo + +// A list of all the leaf elements in OFX 1.0.3 (the last SGML version of the +// spec). These are all the elements that are possibly left unclosed, and which +// can have no children of their own. Fortunately these two sets of elements +// are the same. We use this list when parsing to remove ambiguities about +// element nesting. +// +// Generated using the following command with the 1.0.3 SPEC .dtd file: +// # sed -rn 's/^.*$/\t"\1",/p' *.dtd | sort +var ofxLeafElements = []string{ + "ACCESSKEY", + "ACCRDINT", + "ACCTID", + "ACCTKEY", + "ACCTREQUIRED", + "ACCTTYPE", + "ADDR1", + "ADDR2", + "ADDR3", + "ADJAMT", + "ADJDATE", + "ADJDESC", + "ADJNO", + "APPID", + "APPVER", + "ASSETCLASS", + "AUCTION", + "AUTHTOKEN", + "AUTHTOKENFIRST", + "AUTHTOKENINFOURL", + "AUTHTOKENLABEL", + "AVAILACCTS", + "AVAILCASH", + "AVGCOSTBASIS", + "BALAMT", + "BALCLOSE", + "BALDNLD", + "BALMIN", + "BALOPEN", + "BALTYPE", + "BANKID", + "BILLREFINFO", + "BRANCHID", + "BROKERID", + "BUYPOWER", + "BUYTYPE", + "CALLPRICE", + "CALLTYPE", + "CANADDPAYEE", + "CANBILLPAY", + "CANCELWND", + "CANEMAIL", + "CANMODMDLS", + "CANMODPMTS", + "CANMODXFERS", + "CANNOTIFY", + "CANPENDING", + "CANRECUR", + "CANSCHED", + "CANUSEDESC", + "CANUSERANGE", + "CASESEN", + "CHARTYPE", + "CHECKING", + "CHECKNUM", + "CHGPINFIRST", + "CHGUSERINFO", + "CHKANDDEB", + "CHKERROR", + "CHKNUMEND", + "CHKNUMSTART", + "CHKSTATUS", + "CITY", + "CLIENTACTREQ", + "CLIENTROUTING", + "CLIENTUID", + "CLIENTUIDREQ", + "CLOSINGAVAIL", + "CLTCOOKIE", + "CODE", + "COMMISSION", + "CONFMSG", + "CORRECTACTION", + "CORRECTFITID", + "COUNTRY", + "COUPONFREQ", + "COUPONRT", + "CREDITLIMIT", + "CSPHONE", + "CURDEF", + "CURRATE", + "CURSYM", + "DATEBIRTH", + "DAYPHONE", + "DAYSTOPAY", + "DAYSWITH", + "DEBADJ", + "DEBTCLASS", + "DEBTTYPE", + "DENOMINATOR", + "DEPANDCREDIT", + "DESC", + "DFLTDAYSTOPAY", + "DIFFFIRSTPMT", + "DIFFLASTPMT", + "DOMXFERFEE", + "DSCAMT", + "DSCDATE", + "DSCDESC", + "DSCRATE", + "DTACCTUP", + "DTASOF", + "DTAUCTION", + "DTAVAIL", + "DTCALL", + "DTCHANGED", + "DTCLIENT", + "DTCLOSE", + "DTCOUPON", + "DTCREATED", + "DTDUE", + "DTEND", + "DTEXPIRE", + "DTINFOCHG", + "DTMAT", + "DTNEXT", + "DTOPEN", + "DTPLACED", + "DTPMTDUE", + "DTPMTPRC", + "DTPOSTED", + "DTPOSTEND", + "DTPOSTSTART", + "DTPRICEASOF", + "DTPROFUP", + "DTPURCHASE", + "DTSERVER", + "DTSETTLE", + "DTSTART", + "DTTRADE", + "DTUSER", + "DTXFERPRC", + "DTXFERPRJ", + "DTYIELDASOF", + "DURATION", + "EMAIL", + "EVEPHONE", + "EXTDPMTCHK", + "EXTDPMTFOR", + "FAXPHONE", + "FEE", + "FEEMSG", + "FEES", + "FIASSETCLASS", + "FICERTID", + "FID", + "FIID", + "FINALAMT", + "FINAME", + "FINCHG", + "FIRSTNAME", + "FITID", + "FRACCASH", + "FREQ", + "FROM", + "GAIN", + "GENUSERKEY", + "GETMIMESUP", + "HASEXTDPMT", + "HELDINACCT", + "IDSCOPE", + "INCBAL", + "INCIMAGES", + "INCLUDE", + "INCOMETYPE", + "INCOO", + "INITIALAMT", + "INTLXFERFEE", + "INVACCTTYPE", + "INVALIDACCTTYPE", + "INVDATE", + "INVDESC", + "INVNO", + "INVPAIDAMT", + "INVTOTALAMT", + "LANGUAGE", + "LASTNAME", + "LIMITPRICE", + "LITMAMT", + "LITMDESC", + "LOAD", + "LOSTSYNC", + "MAILSUP", + "MARGINBALANCE", + "MARKDOWN", + "MARKUP", + "MAX", + "MEMO", + "MESSAGE", + "MFACHALLENGEFIRST", + "MFACHALLENGESUPT", + "MFAPHRASEA", + "MFAPHRASEID", + "MFAPHRASELABEL", + "MFTYPE", + "MIDDLENAME", + "MIN", + "MINPMTDUE", + "MINUNITS", + "MKTGINFO", + "MKTVAL", + "MODELWND", + "MODPENDING", + "NAME", + "NEWUNITS", + "NEWUSERPASS", + "NINSTS", + "NONCE", + "NUMERATOR", + "OFXSEC", + "OLDUNITS", + "OODNLD", + "OPTACTION", + "OPTBUYTYPE", + "OPTIONLEVEL", + "OPTSELLTYPE", + "OPTTYPE", + "ORG", + "PARVALUE", + "PAYACCT", + "PAYANDCREDIT", + "PAYEEID", + "PAYEELSTID", + "PAYINSTRUCT", + "PERCENT", + "PHONE", + "PINCH", + "PMTBYADDR", + "PMTBYPAYEEID", + "PMTBYXFER", + "PMTPRCCODE", + "POSDNLD", + "POSTALCODE", + "POSTPROCWND", + "POSTYPE", + "PROCDAYSOFF", + "PROCENDTM", + "PURANDADV", + "RATING", + "RECSRVRTID", + "REFNUM", + "REFRESH", + "REFRESHSUPT", + "REINVCG", + "REINVDIV", + "REJECTIFMISSING", + "RELFITID", + "RELTYPE", + "RESPFILEER", + "RESTRICTION", + "SECLISTRQDNLD", + "SECNAME", + "SECURED", + "SECURITYNAME", + "SELLALL", + "SELLREASON", + "SELLTYPE", + "SESSCOOKIE", + "SEVERITY", + "SHORTBALANCE", + "SHPERCTRCT", + "SIC", + "SIGNONREALM", + "SPACES", + "SPECIAL", + "SPNAME", + "SRVRTID", + "STATE", + "STOCKTYPE", + "STOPPRICE", + "STPCHKFEE", + "STRIKEPRICE", + "STSVIAMODS", + "SUBACCT", + "SUBACCTFROM", + "SUBACCTSEC", + "SUBACCTTO", + "SUBJECT", + "SUPTXDL", + "SVC", + "SVCSTATUS", + "SWITCHALL", + "SYNCMODE", + "TAN", + "TAXES", + "TAXEXEMPT", + "TAXID", + "TEMPPASS", + "TFERACTION", + "TICKER", + "TO", + "TOKEN", + "TOKENONLY", + "TOTAL", + "TOTALFEES", + "TOTALINT", + "TRANDNLD", + "TRANSPSEC", + "TRNAMT", + "TRNTYPE", + "TRNUID", + "TSKEYEXPIRE", + "TSPHONE", + "TYPEDESC", + "UNIQUEID", + "UNIQUEIDTYPE", + "UNITPRICE", + "UNITS", + "UNITSSTREET", + "UNITSUSER", + "UNITTYPE", + "URL", + "USEHTML", + "USERCRED1", + "USERCRED1LABEL", + "USERCRED2", + "USERCRED2LABEL", + "USERID", + "USERKEY", + "USERPASS", + "USPRODUCTTYPE", + "VALUE", + "VER", + "WITHHOLDING", + "XFERDAYSWITH", + "XFERDEST", + "XFERDFLTDAYSTOPAY", + "XFERPRCCODE", + "XFERSRC", + "YIELD", + "YIELDTOCALL", + "YIELDTOMAT", +} diff --git a/vendor/github.com/aclindsa/ofxgo/profile.go b/vendor/github.com/aclindsa/ofxgo/profile.go new file mode 100644 index 0000000..8bacc60 --- /dev/null +++ b/vendor/github.com/aclindsa/ofxgo/profile.go @@ -0,0 +1,206 @@ +package ofxgo + +import ( + "errors" + "github.com/aclindsa/xml" + "strings" +) + +// 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"` + CltCookie String `xml:"CLTCOOKIE,omitempty"` + 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"` // 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(version ofxVersion) (bool, error) { + if ok, err := r.TrnUID.Valid(); !ok { + return false, err + } + // 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"` // 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 + CaseSen Boolean `xml:"CASESEN"` // Password is case-sensitive? + Special Boolean `xml:"SPECIAL"` // Special characters allowed? + Spaces Boolean `xml:"SPACES"` // Spaces allowed? + PinCh Boolean `xml:"PINCH"` // Pin change requests allowed + ChgPinFirst Boolean `xml:"CHGPINFIRST"` // Server requires user to change password at first signon + UserCred1Label String `xml:"USERCRED1LABEL,omitempty"` // Prompt for USERCRED1 (if this field is present, USERCRED1 is required) + UserCred2Label String `xml:"USERCRED2LABEL,omitempty"` // Prompt for USERCRED2 (if this field is present, USERCRED2 is required) + ClientUIDReq Boolean `xml:"CLIENTUIDREQ,omitempty"` // CLIENTUID required? + AuthTokenFirst Boolean `xml:"AUTHTOKENFIRST,omitempty"` // Server requires AUTHTOKEN as part of first signon + AuthTokenLabel String `xml:"AUTHTOKENLABEL,omitempty"` + AuthTokenInfoURL String `xml:"AUTHTOKENINFOURL,omitempty"` + MFAChallengeSupt Boolean `xml:"MFACHALLENGESUPT,omitempty"` // Server supports MFACHALLENGE + MFAChallengeFIRST Boolean `xml:"MFACHALLENGEFIRST,omitempty"` // Server requires MFACHALLENGE to be sent with first signon + 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 // + Name string // (copy of XMLName.Local) + Ver Int `xml:"MSGSETCORE>VER"` // Message set version - should always match 'n' in 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 + Language []String `xml:"MSGSETCORE>LANGUAGE"` // List of supported languages + SyncMode syncMode `xml:"MSGSETCORE>SYNCMODE"` // One of FULL, LITE + RefreshSupt Boolean `xml:"MSGSETCORE>REFRESHSUPT,omitempty"` // Y if server supports Y within synchronizations. This option is irrelevant for full synchronization servers. Clients must ignore (or its absence) if the profile also specifies FULL. For lite synchronization, the default is N. Without Y, lite synchronization servers are not required to support Y requests + RespFileER Boolean `xml:"MSGSETCORE>RESPFILEER"` // server supports file-based error recovery + SpName String `xml:"MSGSETCORE>SPNAME"` // Name of service provider + // 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 + tok, err := nextNonWhitespaceToken(d) + if err != nil { + return err + } else if end, ok := tok.(xml.EndElement); ok && end.Name.Local == start.Name.Local { + // If we found the end of our starting element, we're done parsing + return nil + } else if _, ok := tok.(xml.StartElement); ok { + // Found starting tag for . Get the next one (xxxMSGSETVn) and decode that struct + tok, err := nextNonWhitespaceToken(d) + if err != nil { + return err + } else if versionStart, ok := tok.(xml.StartElement); ok { + if err := d.DecodeElement(&msgset, &versionStart); err != nil { + return err + } + } else { + return errors.New("Invalid MSGSETLIST formatting") + } + msgset.Name = msgset.XMLName.Local + + // Eat ending tags for + tok, err = nextNonWhitespaceToken(d) + if err != nil { + return err + } else if _, ok := tok.(xml.EndElement); !ok { + return errors.New("Invalid MSGSETLIST formatting") + } + } else { + return errors.New("MSGSETLIST didn't find an opening xxxMSGSETVn element") + } + *msl = MessageSetList(append(*(*[]MessageSet)(msl), msgset)) + } +} + +// MarshalXML handles marshalling a MessageSetList element to an XML string +func (msl *MessageSetList) MarshalXML(e *xml.Encoder, start xml.StartElement) error { + messageSetListElement := xml.StartElement{Name: xml.Name{Local: "MSGSETLIST"}} + if err := e.EncodeToken(messageSetListElement); err != nil { + return err + } + for _, messageset := range *msl { + if !strings.HasSuffix(messageset.Name, "V1") { + return errors.New("Expected MessageSet.Name to end with \"V1\"") + } + messageSetName := strings.TrimSuffix(messageset.Name, "V1") + messageSetElement := xml.StartElement{Name: xml.Name{Local: messageSetName}} + if err := e.EncodeToken(messageSetElement); err != nil { + return err + } + start := xml.StartElement{Name: xml.Name{Local: messageset.Name}} + if err := e.EncodeElement(&messageset, start); err != nil { + return err + } + if err := e.EncodeToken(messageSetElement.End()); err != nil { + return err + } + } + if err := e.EncodeToken(messageSetListElement.End()); err != nil { + return err + } + return nil +} + +// 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"` + Status Status `xml:"STATUS"` + CltCookie String `xml:"CLTCOOKIE,omitempty"` + // TODO `xml:"OFXEXTENSION,omitempty"` + MessageSetList MessageSetList `xml:"PROFRS>MSGSETLIST"` + SignonInfoList []SignonInfo `xml:"PROFRS>SIGNONINFOLIST>SIGNONINFO"` + DtProfUp Date `xml:"PROFRS>DTPROFUP"` + FiName String `xml:"PROFRS>FINAME"` + Addr1 String `xml:"PROFRS>ADDR1"` + Addr2 String `xml:"PROFRS>ADDR2,omitempty"` + Addr3 String `xml:"PROFRS>ADDR3,omitempty"` + City String `xml:"PROFRS>CITY"` + State String `xml:"PROFRS>STATE"` + PostalCode String `xml:"PROFRS>POSTALCODE"` + Country String `xml:"PROFRS>COUNTRY"` + CsPhone String `xml:"PROFRS>CSPHONE,omitempty"` + TsPhone String `xml:"PROFRS>TSPHONE,omitempty"` + FaxPhone String `xml:"PROFRS>FAXPHONE,omitempty"` + URL String `xml:"PROFRS>URL,omitempty"` + 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(version ofxVersion) (bool, error) { + if ok, err := pr.TrnUID.Valid(); !ok { + return false, err + } + //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 +} diff --git a/vendor/github.com/aclindsa/ofxgo/request.go b/vendor/github.com/aclindsa/ofxgo/request.go new file mode 100644 index 0000000..4c31f9d --- /dev/null +++ b/vendor/github.com/aclindsa/ofxgo/request.go @@ -0,0 +1,151 @@ +package ofxgo + +import ( + "bytes" + "errors" + "github.com/aclindsa/xml" + "time" +) + +// Request is the top-level object marshalled and sent to OFX servers. It is +// constructed by appending one or more request objects to the message set they +// correspond to (i.e. appending StatementRequest to Request.Bank to get a bank +// statemement). If a *Request object is appended to the wrong message set, an +// error will be returned when Marshal() is called on this Request. +type Request struct { + URL string + Version ofxVersion // OFX version, overwritten in Client.Request() + Signon SignonRequest // + Signup []Message // + Bank []Message // + CreditCard []Message // + Loan []Message // + InvStmt []Message // + InterXfer []Message // + WireXfer []Message // + Billpay []Message // + Email []Message // + SecList []Message // + PresDir []Message // + PresDlv []Message // + Prof []Message // + Image []Message // + + indent bool // Whether to indent the marshaled XML + carriageReturn bool // Whether to user carriage returns in new lines for marshaled XML +} + +func encodeMessageSet(e *xml.Encoder, requests []Message, set messageType, version ofxVersion) error { + if len(requests) > 0 { + messageSetElement := xml.StartElement{Name: xml.Name{Local: set.String()}} + if err := e.EncodeToken(messageSetElement); err != nil { + return err + } + + for _, request := range requests { + if request.Type() != set { + return errors.New("Expected " + set.String() + " message , found " + request.Type().String()) + } + if ok, err := request.Valid(version); !ok { + return err + } + if err := e.Encode(request); err != nil { + return err + } + } + + if err := e.EncodeToken(messageSetElement.End()); err != nil { + return err + } + } + return nil +} + +// SetClientFields overwrites the fields in this Request object controlled by +// the Client +func (oq *Request) SetClientFields(c Client) { + oq.Signon.DtClient.Time = time.Now() + + // Overwrite fields that the client controls + oq.Version = c.OfxVersion() + oq.Signon.AppID = c.ID() + oq.Signon.AppVer = c.Version() + oq.indent = c.IndentRequests() + oq.carriageReturn = c.CarriageReturnNewLines() +} + +// Marshal this Request into its SGML/XML representation held in a bytes.Buffer +// +// If error is non-nil, this bytes.Buffer is ready to be sent to an OFX server +func (oq *Request) Marshal() (*bytes.Buffer, error) { + var b bytes.Buffer + + // Write the header appropriate to our version + writeHeader(&b, oq.Version, oq.carriageReturn) + + encoder := xml.NewEncoder(&b) + if oq.indent { + encoder.Indent("", " ") + } + if oq.carriageReturn { + encoder.CarriageReturn(true) + } + if oq.Version < OfxVersion200 { + // OFX 100 series versions should avoid element close tags for compatibility + encoder.SetDisableAutoClose(ofxLeafElements...) + } + + ofxElement := xml.StartElement{Name: xml.Name{Local: "OFX"}} + + if err := encoder.EncodeToken(ofxElement); err != nil { + return nil, err + } + + if ok, err := oq.Signon.Valid(oq.Version); !ok { + return nil, err + } + signonMsgSet := xml.StartElement{Name: xml.Name{Local: SignonRq.String()}} + if err := encoder.EncodeToken(signonMsgSet); err != nil { + return nil, err + } + if err := encoder.Encode(&oq.Signon); err != nil { + return nil, err + } + if err := encoder.EncodeToken(signonMsgSet.End()); err != nil { + return nil, err + } + + messageSets := []struct { + Messages []Message + Type messageType + }{ + {oq.Signup, SignupRq}, + {oq.Bank, BankRq}, + {oq.CreditCard, CreditCardRq}, + {oq.Loan, LoanRq}, + {oq.InvStmt, InvStmtRq}, + {oq.InterXfer, InterXferRq}, + {oq.WireXfer, WireXferRq}, + {oq.Billpay, BillpayRq}, + {oq.Email, EmailRq}, + {oq.SecList, SecListRq}, + {oq.PresDir, PresDirRq}, + {oq.PresDlv, PresDlvRq}, + {oq.Prof, ProfRq}, + {oq.Image, ImageRq}, + } + for _, set := range messageSets { + if err := encodeMessageSet(encoder, set.Messages, set.Type, oq.Version); err != nil { + return nil, err + } + } + + if err := encoder.EncodeToken(ofxElement.End()); err != nil { + return nil, err + } + + if err := encoder.Flush(); err != nil { + return nil, err + } + return &b, nil +} diff --git a/vendor/github.com/aclindsa/ofxgo/response.go b/vendor/github.com/aclindsa/ofxgo/response.go new file mode 100644 index 0000000..61f471e --- /dev/null +++ b/vendor/github.com/aclindsa/ofxgo/response.go @@ -0,0 +1,505 @@ +package ofxgo + +import ( + "bufio" + "bytes" + "errors" + "fmt" + "io" + "reflect" + "regexp" + "strings" + + "github.com/aclindsa/xml" +) + +// Response is the top-level object returned from a parsed OFX response file. +// It can be inspected by using type assertions or switches on the message set +// you're interested in. +type Response struct { + Version ofxVersion // OFX header version + Signon SignonResponse // + Signup []Message // + Bank []Message // + CreditCard []Message // + Loan []Message // + InvStmt []Message // + InterXfer []Message // + WireXfer []Message // + Billpay []Message // + Email []Message // + SecList []Message // + PresDir []Message // + PresDlv []Message // + Prof []Message // + Image []Message // +} + +func (or *Response) readSGMLHeaders(r *bufio.Reader) error { + b, err := r.ReadSlice('<') + if err != nil { + return err + } + + s := string(b) + err = r.UnreadByte() + if err != nil { + return err + } + + // According to the latest OFX SGML spec (1.6), headers should be CRLF-separated + // and written as KEY:VALUE. However, some banks include a whitespace after the + // colon (KEY: VALUE), while others include no line breaks at all. The spec doesn't + // require a line break after the OFX headers, but it is allowed, and will be + // optionally captured & discarded by the trailing `\s*`. Valid SGML headers must + // always be present in exactly this order, so a regular expression is acceptable. + headerExp := regexp.MustCompile( + `^OFXHEADER:\s*(?P\d+)\s*` + + `DATA:\s*(?P[A-Z]+)\s*` + + `VERSION:\s*(?P\d+)\s*` + + `SECURITY:\s*(?P[\w]+)\s*` + + `ENCODING:\s*(?P[A-Z0-9-]+)\s*` + + `CHARSET:\s*(?P[\w-]+)\s*` + + `COMPRESSION:\s*(?P[A-Z]+)\s*` + + `OLDFILEUID:\s*(?P[\w-]+)\s*` + + `NEWFILEUID:\s*(?P[\w-]+)\s*<$`) + + matches := headerExp.FindStringSubmatch(s) + if len(matches) == 0 { + return errors.New("OFX headers malformed") + } + + for i, name := range headerExp.SubexpNames() { + if i == 0 { + continue + } + + headerValue := matches[i] + switch name { + case "OFXHEADER": + if headerValue != "100" { + return errors.New("OFXHEADER is not 100") + } + case "DATA": + if headerValue != "OFXSGML" { + return errors.New("OFX DATA header does not contain OFXSGML") + } + case "VERSION": + err := or.Version.FromString(headerValue) + if err != nil { + return err + } + if or.Version > OfxVersion160 { + return errors.New("OFX VERSION > 160 in SGML header") + } + case "SECURITY": + if !(headerValue == "NONE" || headerValue == "TYPE1") { + return errors.New("OFX SECURITY header must be NONE or TYPE1") + } + case "COMPRESSION": + if headerValue != "NONE" { + return errors.New("OFX COMPRESSION header not NONE") + } + case "ENCODING", "CHARSET", "OLDFILEUID", "NEWFILEUID": + // TODO: check/handle these headers? + } + } + + return nil +} + +func (or *Response) readXMLHeaders(decoder *xml.Decoder) error { + var tok xml.Token + tok, err := nextNonWhitespaceToken(decoder) + if err != nil { + return err + } else if xmlElem, ok := tok.(xml.ProcInst); !ok || xmlElem.Target != "xml" { + return errors.New("Missing xml processing instruction") + } + + // parse the OFX header + tok, err = nextNonWhitespaceToken(decoder) + if err != nil { + return err + } else if ofxElem, ok := tok.(xml.ProcInst); ok && ofxElem.Target == "OFX" { + var seenHeader, seenVersion bool = false, false + + headers := bytes.TrimSpace(ofxElem.Inst) + for len(headers) > 0 { + tmp := bytes.SplitN(headers, []byte("=\""), 2) + if len(tmp) != 2 { + return errors.New("Malformed OFX header") + } + header := string(tmp[0]) + headers = tmp[1] + tmp = bytes.SplitN(headers, []byte("\""), 2) + if len(tmp) != 2 { + return errors.New("Malformed OFX header") + } + value := string(tmp[0]) + headers = bytes.TrimSpace(tmp[1]) + + switch header { + case "OFXHEADER": + if value != "200" { + return errors.New("OFXHEADER is not 200") + } + seenHeader = true + case "VERSION": + err := or.Version.FromString(value) + if err != nil { + return err + } + seenVersion = true + + if or.Version < OfxVersion200 { + return errors.New("OFX VERSION < 200 in XML header") + } + case "SECURITY": + if value != "NONE" { + return errors.New("OFX SECURITY header not NONE") + } + case "OLDFILEUID", "NEWFILEUID": + // TODO check/handle these headers? + default: + return errors.New("Invalid OFX header: " + header) + } + } + + if !seenHeader { + return errors.New("OFXHEADER version missing") + } + if !seenVersion { + return errors.New("OFX VERSION header missing") + } + + } else { + return errors.New("Missing xml 'OFX' processing instruction") + } + return nil +} + +// Number of bytes of response to read when attempting to figure out whether +// we're using OFX or SGML +const guessVersionCheckBytes = 1024 + +// Defaults to XML if it can't determine the version or if there is any +// ambiguity +// Returns false for SGML, true (for XML) otherwise. +func guessVersion(r *bufio.Reader) (bool, error) { + b, _ := r.Peek(guessVersionCheckBytes) + if b == nil { + return false, errors.New("Failed to read OFX header") + } + sgmlIndex := bytes.Index(b, []byte("OFXHEADER:")) + xmlIndex := bytes.Index(b, []byte("OFXHEADER=")) + if sgmlIndex < 0 { + return true, nil + } else if xmlIndex < 0 { + return false, nil + } else { + return xmlIndex <= sgmlIndex, nil + } +} + +// A map of message set tags to a map of transaction wrapper tags to the +// reflect.Type of the struct for that transaction type. Used when decoding +// Responses. Newly-implemented response transaction types *must* be added to +// this map in order to be unmarshalled. +var responseTypes = map[string]map[string]reflect.Type{ + SignupRs.String(): { + (&AcctInfoResponse{}).Name(): reflect.TypeOf(AcctInfoResponse{})}, + BankRs.String(): { + (&StatementResponse{}).Name(): reflect.TypeOf(StatementResponse{})}, + CreditCardRs.String(): { + (&CCStatementResponse{}).Name(): reflect.TypeOf(CCStatementResponse{})}, + LoanRs.String(): {}, + InvStmtRs.String(): { + (&InvStatementResponse{}).Name(): reflect.TypeOf(InvStatementResponse{})}, + InterXferRs.String(): {}, + WireXferRs.String(): {}, + BillpayRs.String(): {}, + EmailRs.String(): {}, + SecListRs.String(): { + (&SecListResponse{}).Name(): reflect.TypeOf(SecListResponse{}), + (&SecurityList{}).Name(): reflect.TypeOf(SecurityList{})}, + PresDirRs.String(): {}, + PresDlvRs.String(): {}, + ProfRs.String(): { + (&ProfileResponse{}).Name(): reflect.TypeOf(ProfileResponse{})}, + ImageRs.String(): {}, +} + +func decodeMessageSet(d *xml.Decoder, start xml.StartElement, msgs *[]Message, version ofxVersion) error { + setTypes, ok := responseTypes[start.Name.Local] + if !ok { + return errors.New("Invalid message set: " + start.Name.Local) + } + for { + tok, err := nextNonWhitespaceToken(d) + if err != nil { + return err + } else if end, ok := tok.(xml.EndElement); ok && end.Name.Local == start.Name.Local { + // If we found the end of our starting element, we're done parsing + return nil + } else if startElement, ok := tok.(xml.StartElement); ok { + responseType, ok := setTypes[startElement.Name.Local] + if !ok { + // If you are a developer and received this message after you + // thought you added a new transaction type, make sure you + // added it to the responseTypes map above + return errors.New("Unsupported response transaction for " + + start.Name.Local + ": " + startElement.Name.Local) + } + response := reflect.New(responseType).Interface() + responseMessage := response.(Message) + if err := d.DecodeElement(responseMessage, &startElement); err != nil { + return err + } + *msgs = append(*msgs, responseMessage) + } else { + return errors.New("Didn't find an opening element") + } + } +} + +// ParseResponse parses and validates an OFX response in SGML or XML into a +// Response object from the given io.Reader +// +// It is commonly used as part of Client.Request(), but may be used on its own +// to parse already-downloaded OFX files (such as those from 'Web Connect'). It +// performs version autodetection if it can and attempts to be as forgiving as +// possible about the input format. +func ParseResponse(reader io.Reader) (*Response, error) { + resp, err := DecodeResponse(reader) + if err != nil { + return nil, err + } + _, err = resp.Valid() + return resp, err +} + +// DecodeResponse parses an OFX response in SGML or XML into a Response object +// from the given io.Reader +func DecodeResponse(reader io.Reader) (*Response, error) { + var or Response + + r := bufio.NewReaderSize(reader, guessVersionCheckBytes) + xmlVersion, err := guessVersion(r) + if err != nil { + return nil, err + } + + // parse SGML headers before creating XML decoder + if !xmlVersion { + if err := or.readSGMLHeaders(r); err != nil { + return nil, err + } + } + + decoder := xml.NewDecoder(r) + if !xmlVersion { + decoder.Strict = false + decoder.AutoCloseAfterCharData = ofxLeafElements + } + decoder.CharsetReader = func(charset string, input io.Reader) (io.Reader, error) { + return input, nil + } + + if xmlVersion { + // parse the xml header + if err := or.readXMLHeaders(decoder); err != nil { + return nil, err + } + } + + tok, err := nextNonWhitespaceToken(decoder) + if err != nil { + return nil, err + } else if ofxStart, ok := tok.(xml.StartElement); !ok || ofxStart.Name.Local != "OFX" { + return nil, errors.New("Missing opening OFX xml element") + } + + // Unmarshal the signon message + tok, err = nextNonWhitespaceToken(decoder) + if err != nil { + return nil, err + } else if signonStart, ok := tok.(xml.StartElement); ok && signonStart.Name.Local == SignonRs.String() { + if err := decoder.Decode(&or.Signon); err != nil { + return nil, err + } + } else { + return nil, errors.New("Missing opening SIGNONMSGSRSV1 xml element") + } + + tok, err = nextNonWhitespaceToken(decoder) + if err != nil { + return nil, err + } else if signonEnd, ok := tok.(xml.EndElement); !ok || signonEnd.Name.Local != SignonRs.String() { + return nil, errors.New("Missing closing SIGNONMSGSRSV1 xml element") + } + + var messageSlices = map[string]*[]Message{ + SignupRs.String(): &or.Signup, + BankRs.String(): &or.Bank, + CreditCardRs.String(): &or.CreditCard, + LoanRs.String(): &or.Loan, + InvStmtRs.String(): &or.InvStmt, + InterXferRs.String(): &or.InterXfer, + WireXferRs.String(): &or.WireXfer, + BillpayRs.String(): &or.Billpay, + EmailRs.String(): &or.Email, + SecListRs.String(): &or.SecList, + PresDirRs.String(): &or.PresDir, + PresDlvRs.String(): &or.PresDlv, + ProfRs.String(): &or.Prof, + ImageRs.String(): &or.Image, + } + + for { + tok, err = nextNonWhitespaceToken(decoder) + if err != nil { + return nil, err + } else if ofxEnd, ok := tok.(xml.EndElement); ok && ofxEnd.Name.Local == "OFX" { + return &or, nil // found closing XML element, so we're done + } else if start, ok := tok.(xml.StartElement); ok { + slice, ok := messageSlices[start.Name.Local] + if !ok { + return nil, errors.New("Invalid message set: " + start.Name.Local) + } + if err := decodeMessageSet(decoder, start, slice, or.Version); err != nil { + return nil, err + } + } else { + return nil, errors.New("Found unexpected token") + } + } +} + +// Valid returns whether the Response is valid according to the OFX spec +func (or *Response) Valid() (bool, error) { + var errs errInvalid + if ok, err := or.Signon.Valid(or.Version); !ok { + errs.AddErr(err) + } + for _, messageSet := range [][]Message{ + or.Signup, + or.Bank, + or.CreditCard, + or.Loan, + or.InvStmt, + or.InterXfer, + or.WireXfer, + or.Billpay, + or.Email, + or.SecList, + or.PresDir, + or.PresDlv, + or.Prof, + or.Image, + } { + for _, message := range messageSet { + if ok, err := message.Valid(or.Version); !ok { + errs.AddErr(err) + } + } + } + err := errs.ErrOrNil() + return err == nil, err +} + +// Marshal this Response into its SGML/XML representation held in a bytes.Buffer +// +// If error is non-nil, this bytes.Buffer is ready to be sent to an OFX client +func (or *Response) Marshal() (*bytes.Buffer, error) { + var b bytes.Buffer + + // Write the header appropriate to our version + writeHeader(&b, or.Version, false) + + encoder := xml.NewEncoder(&b) + encoder.Indent("", " ") + + ofxElement := xml.StartElement{Name: xml.Name{Local: "OFX"}} + + if err := encoder.EncodeToken(ofxElement); err != nil { + return nil, err + } + + if ok, err := or.Signon.Valid(or.Version); !ok { + return nil, err + } + signonMsgSet := xml.StartElement{Name: xml.Name{Local: SignonRs.String()}} + if err := encoder.EncodeToken(signonMsgSet); err != nil { + return nil, err + } + if err := encoder.Encode(&or.Signon); err != nil { + return nil, err + } + if err := encoder.EncodeToken(signonMsgSet.End()); err != nil { + return nil, err + } + + messageSets := []struct { + Messages []Message + Type messageType + }{ + {or.Signup, SignupRs}, + {or.Bank, BankRs}, + {or.CreditCard, CreditCardRs}, + {or.Loan, LoanRs}, + {or.InvStmt, InvStmtRs}, + {or.InterXfer, InterXferRs}, + {or.WireXfer, WireXferRs}, + {or.Billpay, BillpayRs}, + {or.Email, EmailRs}, + {or.SecList, SecListRs}, + {or.PresDir, PresDirRs}, + {or.PresDlv, PresDlvRs}, + {or.Prof, ProfRs}, + {or.Image, ImageRs}, + } + for _, set := range messageSets { + if err := encodeMessageSet(encoder, set.Messages, set.Type, or.Version); err != nil { + return nil, err + } + } + + if err := encoder.EncodeToken(ofxElement.End()); err != nil { + return nil, err + } + + if err := encoder.Flush(); err != nil { + return nil, err + } + return &b, nil +} + +// errInvalid represents validation failures while parsing an OFX response +// If an institution returns slightly malformed data, ParseResponse will return a best-effort parsed response and a validation error. +type errInvalid []error + +func (e errInvalid) Error() string { + var errStrings []string + for _, err := range e { + errStrings = append(errStrings, err.Error()) + } + return fmt.Sprintf("Validation failed: %s", strings.Join(errStrings, "; ")) +} + +func (e *errInvalid) AddErr(err error) { + if err != nil { + if errs, ok := err.(errInvalid); ok { + *e = append(*e, errs...) + } else { + *e = append(*e, err) + } + } +} + +func (e errInvalid) ErrOrNil() error { + if len(e) > 0 { + return e + } + return nil +} diff --git a/vendor/github.com/aclindsa/ofxgo/seclist.go b/vendor/github.com/aclindsa/ofxgo/seclist.go new file mode 100644 index 0000000..c8d3334 --- /dev/null +++ b/vendor/github.com/aclindsa/ofxgo/seclist.go @@ -0,0 +1,332 @@ +package ofxgo + +import ( + "errors" + "github.com/aclindsa/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 + SecID *SecurityID `xml:"SECID,omitempty"` + Ticker String `xml:"TICKER,omitempty"` + 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"` + CltCookie String `xml:"CLTCOOKIE,omitempty"` + TAN String `xml:"TAN,omitempty"` // Transaction authorization number + // TODO `xml:"OFXEXTENSION,omitempty"` + 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(version ofxVersion) (bool, error) { + if ok, err := r.TrnUID.Valid(); !ok { + return false, err + } + // 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"` + Status Status `xml:"STATUS"` + CltCookie String `xml:"CLTCOOKIE,omitempty"` + // TODO `xml:"OFXEXTENSION,omitempty"` + // 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(version ofxVersion) (bool, error) { + if ok, err := r.TrnUID.Valid(); !ok { + return false, err + } + // 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"` + SecName String `xml:"SECNAME"` // Full name of security + Ticker String `xml:"TICKER,omitempty"` // Ticker symbol + FiID String `xml:"FIID,omitempty"` + Rating String `xml:"RATING,omitempty"` + UnitPrice Amount `xml:"UNITPRICE,omitempty"` // Current price, as of DTASOF + DtAsOf *Date `xml:"DTASOF,omitempty"` // Date UNITPRICE was for + Currency *Currency `xml:"CURRENCY,omitempty"` // Overriding currency for UNITPRICE + Memo String `xml:"MEMO,omitempty"` +} + +// DebtInfo provides information about a debt security +type DebtInfo struct { + XMLName xml.Name `xml:"DEBTINFO"` + SecInfo SecInfo `xml:"SECINFO"` + ParValue Amount `xml:"PARVALUE"` + DebtType debtType `xml:"DEBTTYPE"` // One of COUPON, ZERO (zero coupon) + DebtClass debtClass `xml:"DEBTCLASS,omitempty"` // One of TREASURY, MUNICIPAL, CORPORATE, OTHER + CouponRate Amount `xml:"COUPONRT,omitempty"` // Bond coupon rate for next closest call date + DtCoupon *Date `xml:"DTCOUPON,omitempty"` // Maturity date for next coupon + CouponFreq couponFreq `xml:"COUPONFREQ,omitempty"` // When coupons mature - one of MONTHLY, QUARTERLY, SEMIANNUAL, ANNUAL, or OTHER + CallPrice Amount `xml:"CALLPRICE,omitempty"` // Bond call price + YieldToCall Amount `xml:"YIELDTOCALL,omitempty"` // Yield to next call + DtCall *Date `xml:"DTCALL,omitempty"` // Next call date + CallType callType `xml:"CALLTYPE,omitempt"` // Type of next call. One of CALL, PUT, PREFUND, MATURITY + YieldToMat Amount `xml:"YIELDTOMAT,omitempty"` // Yield to maturity + DtMat *Date `xml:"DTMAT,omitempty"` // Debt maturity date + AssetClass assetClass `xml:"ASSETCLASS,omitempty"` // One of DOMESTICBOND, INTLBOND, LARGESTOCK, SMALLSTOCK, INTLSTOCK, MONEYMRKT, OTHER + 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"` + MfType mfType `xml:"MFTYPE"` // One of OPEN, END, CLOSEEND, OTHER + Yield Amount `xml:"YIELD,omitempty"` // Current yield reported as the dividend expressed as a portion of the current stock price + DtYieldAsOf *Date `xml:"DTYIELDASOF,omitempty"` // Date YIELD is valid for + AssetClasses []AssetPortion `xml:"MFASSETCLASS>PORTION"` + 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"` + OptType optType `xml:"OPTTYPE"` // One of PUT, CALL + StrikePrice Amount `xml:"STRIKEPRICE"` + DtExpire Date `xml:"DTEXPIRE"` // Expiration date + ShPerCtrct Int `xml:"SHPERCTRCT"` // Shares per contract + SecID *SecurityID `xml:"SECID,omitempty"` // Security ID of the underlying security + AssetClass assetClass `xml:"ASSETCLASS,omitempty"` // One of DOMESTICBOND, INTLBOND, LARGESTOCK, SMALLSTOCK, INTLSTOCK, MONEYMRKT, OTHER + 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"` + TypeDesc String `xml:"TYPEDESC,omitempty"` // Description of security type + AssetClass assetClass `xml:"ASSETCLASS,omitempty"` // One of DOMESTICBOND, INTLBOND, LARGESTOCK, SMALLSTOCK, INTLSTOCK, MONEYMRKT, OTHER + 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"` + StockType stockType `xml:"STOCKTYPE,omitempty"` // One of COMMON, PREFERRED, CONVERTIBLE, OTHER + Yield Amount `xml:"YIELD,omitempty"` // Current yield reported as the dividend expressed as a portion of the current stock price + DtYieldAsOf *Date `xml:"DTYIELDASOF,omitempty"` // Date YIELD is valid for + AssetClass assetClass `xml:"ASSETCLASS,omitempty"` // One of DOMESTICBOND, INTLBOND, LARGESTOCK, SMALLSTOCK, INTLSTOCK, MONEYMRKT, OTHER + 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 { + XMLName xml.Name `xml:"SECLIST"` + 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(version ofxVersion) (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) + if err != nil { + return err + } else if end, ok := tok.(xml.EndElement); ok && end.Name.Local == start.Name.Local { + // If we found the end of our starting element, we're done parsing + return nil + } else if startElement, ok := tok.(xml.StartElement); ok { + switch startElement.Name.Local { + case "DEBTINFO": + var info DebtInfo + if err := d.DecodeElement(&info, &startElement); err != nil { + return err + } + r.Securities = append(r.Securities, Security(info)) + case "MFINFO": + var info MFInfo + if err := d.DecodeElement(&info, &startElement); err != nil { + return err + } + r.Securities = append(r.Securities, Security(info)) + case "OPTINFO": + var info OptInfo + if err := d.DecodeElement(&info, &startElement); err != nil { + return err + } + r.Securities = append(r.Securities, Security(info)) + case "OTHERINFO": + var info OtherInfo + if err := d.DecodeElement(&info, &startElement); err != nil { + return err + } + r.Securities = append(r.Securities, Security(info)) + case "STOCKINFO": + var info StockInfo + if err := d.DecodeElement(&info, &startElement); err != nil { + return err + } + r.Securities = append(r.Securities, Security(info)) + default: + return errors.New("Invalid SECLIST child tag: " + startElement.Name.Local) + } + } else { + return errors.New("Didn't find an opening element") + } + } +} + +// MarshalXML handles marshalling a SecurityList to an SGML/XML string +func (r *SecurityList) MarshalXML(e *xml.Encoder, start xml.StartElement) error { + secListElement := xml.StartElement{Name: xml.Name{Local: "SECLIST"}} + if err := e.EncodeToken(secListElement); err != nil { + return err + } + for _, s := range r.Securities { + start := xml.StartElement{Name: xml.Name{Local: s.SecurityType()}} + switch sec := s.(type) { + case DebtInfo: + if err := e.EncodeElement(&sec, start); err != nil { + return err + } + case MFInfo: + if err := e.EncodeElement(&sec, start); err != nil { + return err + } + case OptInfo: + if err := e.EncodeElement(&sec, start); err != nil { + return err + } + case OtherInfo: + if err := e.EncodeElement(&sec, start); err != nil { + return err + } + case StockInfo: + if err := e.EncodeElement(&sec, start); err != nil { + return err + } + default: + return errors.New("Invalid SECLIST child type: " + sec.SecurityType()) + } + } + if err := e.EncodeToken(secListElement.End()); err != nil { + return err + } + return nil +} diff --git a/vendor/github.com/aclindsa/ofxgo/signon.go b/vendor/github.com/aclindsa/ofxgo/signon.go new file mode 100644 index 0000000..6c9cebd --- /dev/null +++ b/vendor/github.com/aclindsa/ofxgo/signon.go @@ -0,0 +1,88 @@ +package ofxgo + +import ( + "errors" + "fmt" + "github.com/aclindsa/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() + UserID String `xml:"USERID"` + UserPass String `xml:"USERPASS,omitempty"` + UserKey String `xml:"USERKEY,omitempty"` + GenUserKey Boolean `xml:"GENUSERKEY,omitempty"` + Language String `xml:"LANGUAGE"` // Defaults to ENG + Org String `xml:"FI>ORG"` + Fid String `xml:"FI>FID"` + AppID String `xml:"APPID"` // Overwritten in Client.Request() + AppVer String `xml:"APPVER"` // Overwritten in Client.Request() + 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(version ofxVersion) (bool, error) { + if len(r.UserID) < 1 || len(r.UserID) > 32 { + return false, errors.New("SONRQ>USERID invalid length") + } + if (len(r.UserPass) == 0) == (len(r.UserKey) == 0) { + return false, errors.New("One and only one of SONRQ>USERPASS and USERKEY must be supplied") + } + if len(r.UserPass) > 32 { + return false, errors.New("SONRQ>USERPASS invalid length") + } + if len(r.UserKey) > 64 { + return false, errors.New("SONRQ>USERKEY invalid length") + } + if len(r.Language) == 0 { + r.Language = "ENG" + } else if len(r.Language) != 3 { + 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") + } + if len(r.AppVer) < 1 || len(r.AppVer) > 4 { + return false, errors.New("SONRQ>APPVER invalid length") + } + 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"` + DtServer Date `xml:"DTSERVER"` + UserKey String `xml:"USERKEY,omitempty"` + TsKeyExpire *Date `xml:"TSKEYEXPIRE,omitempty"` + Language String `xml:"LANGUAGE"` + DtProfUp *Date `xml:"DTPROFUP,omitempty"` + DtAcctUp *Date `xml:"DTACCTUP,omitempty"` + Org String `xml:"FI>ORG"` + Fid String `xml:"FI>FID"` + SessCookie String `xml:"SESSCOOKIE,omitempty"` + 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(version ofxVersion) (bool, error) { + if len(r.Language) != 3 { + return false, fmt.Errorf("SONRS>LANGUAGE invalid length: \"%s\"", r.Language) + } + return r.Status.Valid() +} diff --git a/vendor/github.com/aclindsa/ofxgo/signup.go b/vendor/github.com/aclindsa/ofxgo/signup.go new file mode 100644 index 0000000..74feda2 --- /dev/null +++ b/vendor/github.com/aclindsa/ofxgo/signup.go @@ -0,0 +1,166 @@ +package ofxgo + +import ( + "fmt" + "github.com/aclindsa/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"` + CltCookie String `xml:"CLTCOOKIE,omitempty"` + TAN String `xml:"TAN,omitempty"` // Transaction authorization number + // TODO `xml:"OFXEXTENSION,omitempty"` + 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(version ofxVersion) (bool, error) { + if ok, err := r.TrnUID.Valid(); !ok { + return false, err + } + // 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"` + MiddleName String `xml:"MIDDLENAME,omitempty"` + LastName String `xml:"LASTNAME"` + Addr1 String `xml:"ADDR1"` + Addr2 String `xml:"ADDR2,omitempty"` + Addr3 String `xml:"ADDR3,omitempty"` + City String `xml:"CITY"` + State String `xml:"STATE"` + PostalCode String `xml:"POSTALCODE"` + Country String `xml:"COUNTRY,omitempty"` + DayPhone String `xml:"DAYPHONE,omitempty"` + EvePhone String `xml:"EVEPHONE,omitempty"` + Email String `xml:"EMAIL,omitempty"` + 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"` + SupTxDl Boolean `xml:"SUPTXDL"` // Supports downloading transactions (as opposed to balance only) + XferSrc Boolean `xml:"XFERSRC"` // Enabled as source for intra/interbank transfer + XferDest Boolean `xml:"XFERDEST"` // Enabled as destination for intra/interbank transfer + MaturityDate Date `xml:"MATURITYDATE,omitempty"` // Maturity date for CD, if CD + MaturityAmt Amount `xml:"MATURITYAMOUNT,omitempty"` // Maturity amount for CD, if CD + MinBalReq Amount `xml:"MINBALREQ,omitempty"` // Minimum balance required to avoid service fees + AcctClassification acctClassification `xml:"ACCTCLASSIFICATION,omitempty"` // One of PERSONAL, BUSINESS, CORPORATE, OTHER + OverdraftLimit Amount `xml:"OVERDRAFTLIMIT,omitempty"` + SvcStatus svcStatus `xml:"SVCSTATUS"` // One of AVAIL (available, but not yet requested), PEND (requested, but not yet available), ACTIVE +} + +// 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"` + SupTxDl Boolean `xml:"SUPTXDL"` // Supports downloading transactions (as opposed to balance only) + XferSrc Boolean `xml:"XFERSRC"` // Enabled as source for intra/interbank transfer + XferDest Boolean `xml:"XFERDEST"` // Enabled as destination for intra/interbank transfer + AcctClassification acctClassification `xml:"ACCTCLASSIFICATION,omitempty"` // One of PERSONAL, BUSINESS, CORPORATE, OTHER + SvcStatus svcStatus `xml:"SVCSTATUS"` // One of AVAIL (available, but not yet requested), PEND (requested, but not yet available), ACTIVE +} + +// 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"` + UsProductType usProductType `xml:"USPRODUCTTYPE"` // One of 401K, 403B, IRA, KEOGH, OTHER, SARSEP, SIMPLE, NORMAL, TDA, TRUST, UGMA + Checking Boolean `xml:"CHECKING"` // Has check-writing privileges + SvcStatus svcStatus `xml:"SVCSTATUS"` // One of AVAIL (available, but not yet requested), PEND (requested, but not yet available), ACTIVE + InvAcctType holderType `xml:"INVACCTTYPE,omitempty"` // One of INDIVIDUAL, JOINT, TRUST, CORPORATE + OptionLevel String `xml:"OPTIONLEVEL,omitempty"` // Text desribing option trading privileges +} + +// 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"` + Desc String `xml:"DESC,omitempty"` + 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"` + InvAcctInfo *InvAcctInfo `xml:"INVACCTINFO,omitempty"` + // TODO LOANACCTINFO + // 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"` + Status Status `xml:"STATUS"` + CltCookie String `xml:"CLTCOOKIE,omitempty"` + // TODO `xml:"OFXEXTENSION,omitempty"` + DtAcctUp Date `xml:"ACCTINFORS>DTACCTUP"` + 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(version ofxVersion) (bool, error) { + if ok, err := air.TrnUID.Valid(); !ok { + return false, err + } + //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 +} diff --git a/vendor/github.com/aclindsa/ofxgo/types.go b/vendor/github.com/aclindsa/ofxgo/types.go new file mode 100644 index 0000000..3004091 --- /dev/null +++ b/vendor/github.com/aclindsa/ofxgo/types.go @@ -0,0 +1,386 @@ +package ofxgo + +import ( + "crypto/rand" + "errors" + "fmt" + "github.com/aclindsa/xml" + "golang.org/x/text/currency" + "math/big" + "regexp" + "strconv" + "strings" + "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 + + err := d.DecodeElement(&value, &start) + if err != nil { + return err + } + + value = strings.TrimSpace(value) + + i2, err := strconv.ParseInt(value, 10, 64) + if err != nil { + return err + } + + *i = Int(i2) + 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 + + err := d.DecodeElement(&value, &start) + if err != nil { + return err + } + + value = strings.TrimSpace(value) + + // The OFX spec allows the start of the fractional amount to be delineated + // by a comma, so fix that up before attempting to parse it into big.Rat + value = strings.Replace(value, ",", ".", 1) + + if _, ok := a.SetString(value); !ok { + return errors.New("Failed to parse OFX amount") + } + 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 +} + +var ofxDateFormats = []string{ + "20060102150405.000", + "20060102150405", + "200601021504", + "2006010215", + "20060102", +} +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) + if err != nil { + return err + } + + value = strings.SplitN(value, "]", 2)[0] + value = strings.TrimSpace(value) + + // Split the time zone off, if any + split := strings.SplitN(value, "[", 2) + if len(split) == 2 { + value = split[0] + zoneFormat = " -0700" + zone = strings.TrimRight(split[1], "]") + + matches := ofxDateZoneRegex.FindStringSubmatch(zone) + if matches == nil { + return errors.New("Invalid OFX Date timezone format: " + zone) + } + var err error + var zonehours, zoneminutes int + zonehours, err = strconv.Atoi(matches[1]) + if err != nil { + return err + } + if len(matches[3]) > 0 { + zoneminutes, err = strconv.Atoi(matches[3]) + if err != nil { + return err + } + zoneminutes = zoneminutes * 60 / 100 + } + zone = fmt.Sprintf(" %+03d%02d", zonehours, zoneminutes) + + // Get the time zone name if it's there, default to GMT if the offset + // is 0 and a name isn't supplied + if len(matches[5]) > 0 { + zone = zone + " " + matches[5] + zoneFormat = zoneFormat + " MST" + } else if zonehours == 0 && zoneminutes == 0 { + zone = zone + " GMT" + zoneFormat = zoneFormat + " MST" + } + } else { + // Default to GMT if no time zone was specified + zone = " +0000 GMT" + zoneFormat = " -0700 MST" + } + + // Try all the date formats, from longest to shortest + for _, format := range ofxDateFormats { + t, err := time.Parse(format+zoneFormat, value+zone) + if err == nil { + od.Time = t + return nil + } + } + 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() + if zoneoffset < 0 { + format += "[" + fmt.Sprintf("%+d", zoneoffset/3600) + } else { + format += "[" + fmt.Sprintf("%d", zoneoffset/3600) + } + fractionaloffset := (zoneoffset % 3600) / 36 + if fractionaloffset > 0 { + format += "." + fmt.Sprintf("%02d", fractionaloffset) + } else if fractionaloffset < 0 { + format += "." + fmt.Sprintf("%02d", -fractionaloffset) + } + + if len(zonename) > 0 { + return format + ":" + zonename + "]" + } + 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) + if err != nil { + return err + } + *os = String(strings.TrimSpace(value)) + 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) + if err != nil { + return err + } + tmpob := strings.TrimSpace(value) + switch tmpob { + case "Y": + *ob = Boolean(true) + case "N": + *ob = Boolean(false) + default: + return errors.New("Invalid OFX Boolean") + } + 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) + } + 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) + if err != nil { + return err + } + *ou = UID(strings.TrimSpace(value)) + return nil +} + +// 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") + } + if ou[8] != '-' || ou[13] != '-' || ou[18] != '-' || ou[23] != '-' { + return false, errors.New("UID missing hyphens at the appropriate places") + } + return true, nil +} + +// Valid returns true, nil if the UID is valid. This is less strict than +// RecommendedFormat, and will always return true, nil if it does. +func (ou UID) Valid() (bool, error) { + if len(ou) == 0 || len(ou) > 36 { + return false, errors.New("UID invalid length") + } + 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[:]) + if err != nil { + return nil, err + } + if n != 16 { + return nil, errors.New("RandomUID failed to read 16 random bytes") + } + uid := UID(fmt.Sprintf("%08x-%04x-%04x-%04x-%012x", uidbytes[:4], uidbytes[4:6], uidbytes[6:8], uidbytes[8:10], uidbytes[10:])) + return &uid, nil +} + +// CurrSymbol represents an ISO-4217 currency +type CurrSymbol struct { + currency.Unit +} + +// UnmarshalXML handles unmarshalling a CurrSymbol from an SGML/XML string. +// Leading and trailing whitespace is ignored. +func (c *CurrSymbol) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + var value string + + err := d.DecodeElement(&value, &start) + if err != nil { + return err + } + + value = strings.TrimSpace(value) + + unit, err := currency.ParseISO(value) + if err != nil { + return errors.New("Error parsing CurrSymbol:" + err.Error()) + } + c.Unit = unit + return nil +} + +// MarshalXML marshals a CurrSymbol to SGML/XML +func (c *CurrSymbol) MarshalXML(e *xml.Encoder, start xml.StartElement) error { + return e.EncodeElement(c.String(), start) +} + +// Equal returns true if the two Currencies are the same +func (c CurrSymbol) Equal(o CurrSymbol) bool { + return c.String() == o.String() +} + +// Valid returns true, nil if the CurrSymbol is valid. +func (c CurrSymbol) Valid() (bool, error) { + if c.String() == "XXX" { + return false, fmt.Errorf("Invalid CurrSymbol: %s", c.Unit) + } + return true, nil +} + +// NewCurrSymbol returns a new CurrSymbol given a three-letter ISO-4217 +// currency symbol as a string +func NewCurrSymbol(s string) (*CurrSymbol, error) { + unit, err := currency.ParseISO(s) + if err != nil { + return nil, errors.New("Error parsing string to create new CurrSymbol:" + err.Error()) + } + return &CurrSymbol{unit}, nil +} diff --git a/vendor/github.com/aclindsa/ofxgo/util.go b/vendor/github.com/aclindsa/ofxgo/util.go new file mode 100644 index 0000000..6910912 --- /dev/null +++ b/vendor/github.com/aclindsa/ofxgo/util.go @@ -0,0 +1,25 @@ +package ofxgo + +import ( + "bytes" + "github.com/aclindsa/xml" +) + +// Returns the next available Token from the xml.Decoder that is not CharData +// made up entirely of whitespace. This is useful to skip whitespace when +// manually unmarshaling XML. +func nextNonWhitespaceToken(decoder *xml.Decoder) (xml.Token, error) { + for { + tok, err := decoder.Token() + if err != nil { + return nil, err + } else if chars, ok := tok.(xml.CharData); ok { + strippedBytes := bytes.TrimSpace(chars) + if len(strippedBytes) != 0 { + return tok, nil + } + } else { + return tok, nil + } + } +} diff --git a/vendor/github.com/aclindsa/ofxgo/vanguard_client.go b/vendor/github.com/aclindsa/ofxgo/vanguard_client.go new file mode 100644 index 0000000..e367259 --- /dev/null +++ b/vendor/github.com/aclindsa/ofxgo/vanguard_client.go @@ -0,0 +1,83 @@ +package ofxgo + +import ( + "errors" + "io" + "net/http" + "strings" +) + +// VanguardClient provides a Client implementation which handles Vanguard's +// cookie-passing requirements. VanguardClient uses default, non-zero settings, +// if its fields are not initialized. +type VanguardClient struct { + *BasicClient +} + +// NewVanguardClient returns a Client interface configured to handle Vanguard's +// brand of idiosyncrasy +func NewVanguardClient(bc *BasicClient) Client { + return &VanguardClient{bc} +} + +// rawRequestCookies is RawRequest with the added feature of sending cookies +func rawRequestCookies(URL string, r io.Reader, cookies []*http.Cookie) (*http.Response, error) { + if !strings.HasPrefix(URL, "https://") { + return nil, errors.New("Refusing to send OFX request with possible plain-text password over non-https protocol") + } + + request, err := http.NewRequest("POST", URL, r) + if err != nil { + return nil, err + } + request.Header.Set("Content-Type", "application/x-ofx") + for _, cookie := range cookies { + request.AddCookie(cookie) + } + + response, err := http.DefaultClient.Do(request) + if err != nil { + return nil, err + } + + if response.StatusCode != 200 { + return nil, errors.New("OFXQuery request status: " + response.Status) + } + + return response, nil +} + +// RequestNoParse marshals a Request to XML, makes an HTTP request, and returns +// the raw HTTP response +func (c *VanguardClient) RequestNoParse(r *Request) (*http.Response, error) { + r.SetClientFields(c) + + b, err := r.Marshal() + if err != nil { + return nil, err + } + + response, err := c.RawRequest(r.URL, b) + + // Some financial institutions (cough, Vanguard, cough), require a cookie + // to be set on the http request, or they return empty responses. + // Fortunately, the initial response contains the cookie we need, so if we + // detect an empty response with cookies set that didn't have any errors, + // re-try the request while sending their cookies back to them. + if response != nil && response.ContentLength <= 0 && len(response.Cookies()) > 0 { + b, err = r.Marshal() + if err != nil { + return nil, err + } + + return rawRequestCookies(r.URL, b, response.Cookies()) + } + + return response, err +} + +// Request marshals a Request to XML, makes an HTTP request, and then +// unmarshals the response into a Response object. +func (c *VanguardClient) Request(r *Request) (*Response, error) { + return clientRequest(c, r) +} diff --git a/vendor/github.com/aclindsa/xml/AUTHORS b/vendor/github.com/aclindsa/xml/AUTHORS new file mode 100644 index 0000000..c510d4b --- /dev/null +++ b/vendor/github.com/aclindsa/xml/AUTHORS @@ -0,0 +1,1107 @@ +# This is the official list of Go authors for copyright purposes. +# This file is distinct from the CONTRIBUTORS files. +# See the latter for an explanation. + +# Names should be added to this file as one of +# Organization's name +# Individual's name +# Individual's name +# See CONTRIBUTORS for the meaning of multiple email addresses. + +# Please keep the list sorted. + +A Medium Corporation +Aamir Khan +Aaron France +Aaron Torres +Abe Haskins +Abhinav Gupta +Adam Eijdenberg +Adam Kisala +Aditya Mukerjee +Adrian Nos +Adrian O'Grady +Adrien Bustany +Aécio Júnior +Agis Anastasopoulos +Ahmed Waheed Moanes +Ahmy Yulrizka +Aiden Scandella +Ainar Garipov +Aishraj Dahal +Akhil Indurti +Akihiro Suda +Akshat Kumar +Alan Shreve +Albert Nigmatzianov +Albert Strasheim +Albert Yu +Alberto Bertogli +Alberto Donizetti +Alberto García Hierro +Aleksandar Dezelin +Alessandro Arzilli +Alessandro Baffa +Alex A Skinner +Alex Brainman +Alex Browne +Alex Carol +Alex Jin +Alex Plugaru +Alex Schroeder +Alex Sergeyev +Alexander Demakin +Alexander Döring +Alexander Guz +Alexander Kauer +Alexander Kucherenko +Alexander Larsson +Alexander Menzhinsky +Alexander Morozov +Alexander Neumann +Alexander Orlov +Alexander Reece +Alexander Surma +Alexander Zhavnerchik +Alexander Zolotov +Alexandre Cesaro +Alexandre Fiori +Alexandre Normand +Alexei Sholik +Alexey Borzenkov +Alexey Neganov +Alexey Palazhchenko +Alexis Hildebrandt +Aliaksandr Valialkin +Alif Rachmawadi +Allan Simon +Alok Menghrajani +Amazon.com, Inc +Amir Mohammad Saied +Amrut Joshi +Anders Pearson +André Carvalho +Andre Nathan +Andreas Auernhammer +Andreas Litt +Andrei Korzhevskii +Andrei Vieru +Andrew Austin +Andrew Balholm +Andrew Benton +Andrew Bonventre +Andrew Bursavich +Andrew Ekstedt +Andrew Etter +Andrew Harding +Andrew Lutomirski +Andrew Pogrebnoy +Andrew Pritchard +Andrew Radev +Andrew Skiba +Andrew Szeto +Andrew Wilkins +Andrew Williams +Andrey Mirtchovski +Andrey Petrov +Andriy Lytvynov +Andy Balholm +Andy Davis +Andy Finkenstadt +Andy Maloney +Andy Walker +Anfernee Yongkun Gui +Angelo Bulfone +Anh Hai Trinh +Anmol Sethi +Anschel Schaffer-Cohen +Anthony Canino +Anthony Eufemio +Anthony Martin +Anthony Starks +Anthony Voutas +Anthony Woods +Antoine Martin +Antonio Bibiano +Antonio Troina +Apisak Darakananda +Apsalar +Aram Hăvărneanu +Areski Belaid +Arlo Breault +ARM Ltd. +Arnaud Ysmal +Arne Hormann +Arnout Engelen +Aron Nopanen +Artyom Pervukhin +Arvindh Rajesh Tamilmani +Atin Malaviya +Ato Araki +Audrey Lim +Augusto Roman +Aulus Egnatius Varialus +awaw fumin +Awn Umar +Ayanamist Yang +Aymerick Jéhanne +Baiju Muthukadan +Bartosz Grzybowski +Bastian Ike +Ben Burkert +Ben Lubar +Ben Olive +Ben Shi +Benjamin Black +Benny Siegert +Benoit Sigoure +Berengar Lehr +Billie Harold Cleek +Bjorn Tillenius +Bjorn Tipling +Blake Gentry +Blake Mizerany +Blixt +Bobby Powers +Bolt +Brady Catherman +Brady Sullivan +Brendan Daniel Tracey +Brett Cannon +Brian Dellisanti +Brian Downs +Brian G. Merrell +Brian Gitonga Marete +Brian Kennedy +Brian Ketelsen +Brian Smith +Brian Starke +Bryan Alexander +Bryan Ford +Bulat Gaifullin +Caine Tighe +Caleb Spare +Carl Chatfield +Carl Henrik Lunde +Carl Johnson +Carlisia Campos +Carlo Alberto Ferraris +Carlos Castillo +Carlos Cirello +Carolyn Van Slyck +Case Nelson +Casey Marshall +Cezar Sá Espinola +ChaiShushan +Charles L. Dorian +Charles Lee +Chew Choon Keat +Chris Biscardi +Chris Dollin +Chris Farmiloe +Chris Hines +Chris Howey +Chris Jones +Chris Kastorff +Chris Lennert +Chris McGee +Chris Roche +Chris Stockton +Christian Couder +Christian Himpel +Christine Hansmann +Christoffer Buchholz +Christoph Hack +Christopher Cahoon +Christopher Guiney +Christopher Nelson +Christopher Nielsen +Christopher Redden +Christopher Wedgwood +CL Sung +Clement Skau +CloudFlare Inc. +Cody Oss +Colin Edwards +Colin Kennedy +Conrad Irwin +Conrad Meyer +CoreOS, Inc. +Corey Thomasson +Cristian Staretu +Currant +Cyrill Schumacher +Daisuke Fujita +Damian Gryski +Damien Lespiau +Dan Ballard +Dan Caddigan +Dan Callahan +Dan Peterson +Dan Sinclair +Daniel Fleischman +Daniel Johansson +Daniel Kerwin +Daniel Krech +Daniel Lidén +Daniel Martí +Daniel Morsing +Daniel Ortiz Pereira da Silva +Daniel Skinner +Daniel Speichert +Daniel Theophanes +Daniel Upton +Darren Elwood +Datong Sun +Dave Cheney +Dave MacFarlane +David Brophy +David Bürgin <676c7473@gmail.com> +David Calavera +David du Colombier <0intro@gmail.com> +David Forsythe +David G. Andersen +David Howden +David Jakob Fritz +David Leon Gil +David NewHamlet +David R. Jenni +David Sansome +David Stainton +David Thomas +David Titarenco +David Volquartz Lebech +Davies Liu +Dean Prichard +Deepak Jois +Denis Bernard +Denis Brandolini +Denys Honsiorovskyi +Derek Buitenhuis +Derek McGowan +Derek Parker +Derek Shockey +Develer SRL +Devon H. O'Dell +Dhaivat Pandit +Dhiru Kholia +Didier Spezia +Dimitri Tcaciuc +Dirk Gadsden +Diwaker Gupta +Dmitri Popov +Dmitri Shuralyov +Dmitriy Dudkin +Dmitriy Shelenin +Dmitry Chestnykh +Dmitry Savintsev +Dmitry Yakunin +Dominic Green +Dominik Honnef +Donald Huang +Dong-hee Na +Donovan Hide +Dropbox, Inc. +Duncan Holm +Dustin Herbison +Dustin Sallings +Dustin Shields-Cloues +Dvir Volk +Dylan Waits +Eden Li +Edward Muller +Egon Elbre +Ehren Kret +Eitan Adler +Eivind Uggedal +Elias Naur +Elliot Morrison-Reed +Emil Hessman +Emilien Kenler +Emmanuel Odeke +Empirical Interfaces Inc. +Eoghan Sherry +Eric Chiang +Eric Clark +Eric Engestrom +Eric Lagergren +Eric Milliken +Eric Roshan-Eisner +Erik Aigner +Erik Dubbelboer +Erik St. Martin +Erik Westrup +Ernest Chiang +Esko Luontola +Euan Kemp +Evan Hicks +Evan Phoenix +Evan Shaw +Evgeniy Polyakov +Ewan Chou +Ewan Valentine +Eyal Posener +Fabian Wickborn +Fabian Zaremba +Fabrizio Milo +Facebook, Inc. +Faiyaz Ahmed +Fan Hongjian +Fastly, Inc. +Fatih Arslan +Fazlul Shahriar +Fedor Indutny +Felipe Oliveira +Felix Geisendörfer +Filip Gruszczyński +Filippo Valsorda +Firmansyah Adiputra +Florian Uekermann +Florian Weimer +Florin Patan +Ford Hurley +Francisco Claude +Francisco Rojas +Francisco Souza +Frederick Kelly Mayle III +Fredrik Enestad +Fredrik Forsmo +Frithjof Schulze +Frits van Bommel +Gabríel Arthúr Pétursson +Gabriel Aszalos +Gabriel Nicolas Avellaneda +Gabriel Russell +Gareth Paul Jones +Gary Burd +Gaurish Sharma +Gautham Thambidorai +Geert-Johan Riemer +Gengliang Wang +Geoffroy Lorieux +Georg Reinke +George Gkirtsou +George Shammas +Gerasimos Dimitriadis +Gideon Jan-Wessel Redelinghuys +Giles Lean +Giulio Iotti +Gleb Stepanov +Google Inc. +Gordon Klaus +Graham King +Graham Miller +Greg Poirier +Greg Ward +Gregory Man +Guillaume J. Charmes +Guobiao Mei +Gustav Paul +Gustav Westling +Gustavo Niemeyer +Gwenael Treguier +Gyu-Ho Lee +H. İbrahim Güngör +Hajime Hoshi +Hang Qian +Hari haran +Hariharan Srinath +Harley Laue +Harry Moreno +Harshavardhana +Hauke Löffler +Håvard Haugen +Hector Chu +Hector Martin Cantero +Henning Schmiedehausen +Henrik Edwards +Henrik Hodne +Henry Chang +Herbert Georg Fischer +Hironao OTSUBO +Hiroshi Ioka +Hitoshi Mitake +Holden Huang +Hong Ruiqi +Hongfei Tan +Hsin-Ho Yeh +Hu Keping +Hugues Bruant +Ian Gudger +IBM +Ibrahim AshShohail +Icarus Sparry +Iccha Sethi +Idora Shinatose +Igneous Systems, Inc. +Igor Dolzhikov +INADA Naoki +Ingo Krabbe +Ingo Oeser +Intel Corporation +Irieda Noboru +Isaac Wagner +Ivan Babrou +Ivan Moscoso +Ivan Ukhov +Jacob Hoffman-Andrews +Jae Kwon +Jakob Borg +Jakub Ryszard Czarnowicz +James Bardin +James Clarke +James Cowgill +James David Chalfant +James Fysh +James Gray +James Hartig +James Meneghello +James Myers +James Neve +James P. Cooper +James Schofield +James Smith +James Sweet +James Toy +James Whitehead +Jamie Beverly +Jamie Kerr +Jamie Stackhouse +Jamil Djadala +Jan Berktold +Jan H. Hosang +Jan Mercl <0xjnml@gmail.com> +Jan Newmarch +Jan Ziak <0xe2.0x9a.0x9b@gmail.com> +Jani Monoses +Jaroslavas Počepko +Jason Barnett +Jason Chu +Jason Del Ponte +Jason Smale +Jason Travis +Jay Weisskopf +Jean-Nicolas Moal +Jeff Hodges +Jeff R. Allen +Jeff Sickel +Jeff Wendling +Jeffrey H +Jens Frederich +Jeremy Jackins +Jeroen Bobbeldijk +Jess Frazelle +Jesse Szwedko +Jihyun Yu +Jim McGrath +Jimmy Zelinskie +Jin-wook Jeong +Jingcheng Zhang +Jingguo Yao +Jiong Du +Jirka Daněk +Joakim Sernbrant +Joe Farrell +Joe Harrison +Joe Henke +Joe Kyo +Joe Poirier +Joe Shaw +Joe Sylve +Joe Tsai +Joel Stemmer +Joey Geiger +Johan Brandhorst +Johan Sageryd +John Asmuth +John C Barstow +John Graham-Cumming +John Howard Palevich +John Jeffery +John Jenkins +John Potocny +John R. Lenton +John Schnake +John Shahid +John Tuley +Johnny Luo +Jonathan Boulle +Jonathan Gold +Jonathan Mark +Jonathan Rudenberg +Jonathan Stacks +Jonathan Wills +Jongmin Kim +Joonas Kuorilehto +Joop Kiefte +Jordan Krage +Jordan Lewis +Jose Luis Vázquez González +Joseph Holsten +Josh Bleecher Snyder +Josh Chorlton +Josh Deprez +Josh Goebel +Josh Holland +Josh Roppo +Joshua Chase +Josselin Costanzi +Jostein Stuhaug +Joyent, Inc. +JT Olds +Jukka-Pekka Kekkonen +Julian Kornberger +Julian Phillips +Julien Schmidt +Justin Nuß +Justyn Temme +Kai Backman +Kai Trukenmüller +Kale Blankenship +Kaleb Elwert +Kamil Chmielewski +Kamil Kisiel +Kang Hu +Karoly Negyesi +Kashav Madan +Kate Manson +Kato Kazuyoshi +Katrina Owen +Kaviraj Kanagaraj +Keegan Carruthers-Smith +Kei Son +Keiji Yoshida +Keith Ball +Keith Rarick +Kelsey Hightower +Kelvin Foo Chuan Lyi +Ken Friedenbach +Ken Rockot +Ken Sedgwick +Kenji Kaneda +Kenneth Shaw +Kenny Grant +Kevin Ballard +Kevin Burke +Kevin Kirsche +Kevin Vu +Kim Yongbin +Klaus Post +Kodie Goodwin +Koichi Shiraishi +Koki Ide +Konstantin +Konstantin Shaposhnikov +KPCompass, Inc. +Kris Nova +Kristopher Watts +Kun Li +Kyle Consalus +Kyle Isom +Kyle Jones +Kyle Lemons +Kyrylo Silin +L Campbell +Lai Jiangshan +Lars Jeppesen +Lars Wiegman +Larz Conwell +Laurie Clark-Michalek +LE Manh Cuong +Lee Hinman +Lee Packham +Leon Klingele +Lev Shamardin +Lewin Bormann +Liberty Fund Inc +Linaro Limited +Lion Yang +Lloyd Dewolf +Lorenzo Masini +Lorenzo Stoakes +Luan Santos +Luca Greco +Lucas Bremgartner +Lucien Stuker +Lucio De Re +Ludi Rehak +Luigi Riefolo +Luit van Drongelen +Luka Zakrajšek +Luke Curley +Ma Peiqi +Maksym Trykur +Mal Curtis +Manfred Touron +Manu S Ajith +Manuel Mendez +Marc Weistroff +Marcel Edmund Franke +Marcelo E. Magallon +Marco Hennings +Marin Bašić +Mark Adams +Mark Bucciarelli +Mark Severson +Mark Theunissen +Marko Juhani Silokunnas +Marko Mudrinic +Marko Tiikkaja +Markover Inc. DBA Poptip +Markus Duft +Markus Sonderegger +Markus Zimmermann +Martin Bertschler +Martin Garton +Martin Hamrle +Martin Hoefling +Martin Lindhe +Martin Möhrmann +Martin Neubauer +Martin Olsen +Martin Olsson +Martin Probst +Marvin Stenger +Marwan Sulaiman +Maryan Hratson +Masahiro Furudate +Masahiro Wakame +Masaki Yoshida +Máté Gulyás +Mateusz Czapliński +Mathias Beke +Mathias Hall-Andersen +Mathias Leppich +Mathieu Lonjaret +Mats Lidell +Matt Aimonetti +Matt Blair +Matt Bostock +Matt Drollette +Matt Harden +Matt Jibson +Matt Joiner +Matt Layher +Matt Reiferson +Matt Robenolt +Matt Strong +Matt T. Proud +Matt Williams +Matthew Brennan +Matthew Cottingham +Matthew Denton +Matthew Holt +Matthew Horsnell +Matthieu Hauglustaine +Matthieu Olivier +Max Riveiro +Maxim Khitrov +Maxime de Roucy +Máximo Cuadros Ortiz +Maxwell Krohn +MediaMath, Inc +Meir Fischer +Meng Zhuo +Meteor Development Group +Mhd Sulhan +Micah Stetson +Michael Chaten +Michael Edwards +Michael Elkins +Michael Fraenkel +Michael Gehring +Michael Hendricks +Michael Hoisie +Michael Käufl +Michael Lewis +Michael MacInnis +Michael McConville +Michael Pearson +Michael Schaller +Michael Stapelberg +Michael Teichgräber +Michael Vetter +Michal Bohuslávek +Michał Derkacz +Miek Gieben +Miguel Mendez +Miguel Molina +Mihai Borobocea +Mikael Tillenius +Mike Andrews +Mike Appleby +Mike Houston +Mike Rosset +Mikhail Gusarov +Mikhail Panchenko +Miki Tebeka +Mikio Hara +Mikkel Krautz +Milutin Jovanović +Miquel Sabaté Solà +Miroslav Genov +Mohit Agarwal +Momchil Velikov +Monty Taylor +Moov Corporation +Moriyoshi Koizumi +Morten Siebuhr +Môshe van der Sterre +Mostyn Bramley-Moore +Muhammed Uluyol +Mura Li +Nan Deng +Nathan Caza +Nathan Humphreys +Nathan John Youngman +Nathan Otterness +Nathan P Finch +Nathan VanBenschoten +Nathan Youngman +Nathaniel Cook +Neelesh Chandola +Neil Lyons +Netflix, Inc. +Neuman Vong +Nevins Bartolomeo +Nexedi +ngmoco, LLC +Niall Sheridan +Nic Day +Nicholas Katsaros +Nicholas Maniscalco +Nicholas Presta +Nicholas Sullivan +Nicholas Waples +Nick Craig-Wood +Nick Leli +Nick Miyake +Nick Patavalis +Nick Petroni +Nick Robinson +Nicolas Kaiser +Nicolas Owens +Nicolas S. Dade +Niels Widger +Nigel Kerr +Nik Nyby +Niklas Schnelle +Niko Dziemba +Nikolay Turpitko +Niranjan Godbole +Noah Campbell +Norberto Lopes +Odin Ugedal +Oleg Bulatov +Oleg Vakheta +Oleku Konko +Oling Cat +Oliver Hookins +Oliver Tonnhofer +Olivier Antoine +Olivier Duperray +Olivier Poitrey +Olivier Saingre +Oracle +Orange +Özgür Kesim +Pablo Lalloni +Pablo Santiago Blum de Aguiar +Padraig Kitterick +Palm Stone Games +Paolo Giarrusso +Paolo Martini +Parker Moore +Pascal S. de Kloe +Pat Moroney +Patrick Crosby +Patrick Gavlin +Patrick Higgins +Patrick Lee +Patrick Mézard +Patrick Mylund Nielsen +Patrick Pelletier +Patrick Smith +Paul A Querna +Paul Hammond +Paul Jolly +Paul Lalonde +Paul Meyer +Paul Querna +Paul Rosania +Paul Sbarra +Paul Smith +Paul van Brouwershaven +Paulo Casaretto +Pavel Paulau +Pavel Zinovkin +Pavlo Sumkin +Pawel Knap +Percy Wegmann +Perry Abbott +Petar Maymounkov +Peter Armitage +Peter Bourgon +Peter Froehlich +Peter Kleiweg +Peter Moody +Peter Morjan +Peter Mundy +Peter Nguyen +Péter Surányi +Péter Szilágyi +Peter Waldschmidt +Peter Waller +Peter Williams +Philip Børgesen +Philip Hofer +Philip K. Warren +Pierre Durand +Pierre Roullon +Piers +Pieter Droogendijk +Pietro Gagliardi +Prashant Varanasi +Pravendra Singh +Preetam Jinka +Qiuxuan Zhu +Quan Tran +Quan Yong Zhai +Quentin Perez +Quentin Renard +Quoc-Viet Nguyen +RackTop Systems Inc. +Radu Berinde +Rafal Jeczalik +Raif S. Naffah +RainTank +Rajat Goel +Ralph Corderoy +Raphael Geronimi +Ray Tung +Raymond Kazlauskas +Red Hat, Inc. +Reinaldo de Souza Jr +Remi Gillig +Rémy Oudompheng +Ricardo Padilha +Richard Barnes +Richard Crowley +Richard Dingwall +Richard Eric Gavaletz +Richard Gibson +Richard Miller +Richard Musiol +Rick Arnold +Rick Sayre +Risto Jaakko Saarelma +Rob Norman +Rob Phoenix +Robert Daniel Kortschak +Robert Dinu +Robert Figueiredo +Robert Hencke +Robert Obryk +Robert Stepanek +Robin Eklind +Rodrigo Moraes de Oliveira +Rodrigo Rafael Monti Kochenburger +Roger Pau Monné +Roger Peppe +Roland Shoemaker +Ron Hashimoto +Ron Minnich +Ross Light +Rowan Worth +Russell Haering +Ryan Bagwell +Ryan Boehning +Ryan Hitchman +Ryan Lower +Ryan Seys +Ryan Slade +Ryuzo Yamamoto +S.Çağlar Onur +Sakeven Jiang +Salmān Aljammāz +Sam Boyer +Sam Hug +Sam Whited +Samuele Pedroni +Sanjay Menakuru +Sascha Brawer +Sasha Sobol +Scott Barron +Scott Bell +Scott Ferguson +Scott Lawrence +Sean Rees +Sebastien Binet +Sébastien Paolacci +Sergei Skorobogatov +Sergey 'SnakE' Gromov +Sergey Mishin +Sergio Luis O. B. Correia +Seth Hoenig +Seth Vargo +Shahar Kohanim +Shane Hansen +Shaozhen Ding +Shawn Smith +Shenghou Ma +Shi Han Ng +Shinji Tanaka +Shintaro Kaneko +Shivakumar GN +Silvan Jegen +Simon Jefford +Simon Rawet +Simon Thulbourn +Simon Whitehead +Sina Siadat +Sokolov Yura +Song Gao +Sourcegraph Inc +Spencer Nelson +Spring Mc +Square, Inc. +Sridhar Venkatakrishnan +StalkR +Stan Schwertly +Stefan Nilsson +Stéphane Travostino +Stephen McQuay +Stephen Searles +Stephen Weinberg +Steve McCoy +Steve Phillips +Steve Streeting +Steven Elliot Harris +Steven Erenst +Steven Hartland +Steven Wilkin +Stripe, Inc. +Sunny +Suyash +Sven Almgren +Syohei YOSHIDA +Szabolcs Nagy +Tad Glines +Taj Khattra +Takeshi YAMANASHI <9.nashi@gmail.com> +Takuya Ueda +Tal Shprecher +Tamir Duberstein +Tarmigan Casebolt +Taro Aoki +Taru Karttunen +Tatsuhiro Tsujikawa +Ted Kornish +Teleport Inc. +Terrel Shumway +Tetsuo Kiso +Thanatat Tamtan +Thiago Fransosi Farina +Thomas Alan Copeland +Thomas Bonfort +Thomas de Zeeuw +Thomas Desrosiers +Thomas Kappler +Thorben Krueger +Thordur Bjornsson +Tilman Dilo +Tim Cooijmans +Tim Ebringer +Tim Heckman +Tim Henderson +Timo Savola +Timo Truyts +Timothy Studd +Tobias Columbus +Tobias Klauser +Todd Neal +Tom Heng +Tom Linford +Tommy Schaefer +Tonis Tiigi +Tony Walker +Tor Andersson +Tormod Erevik Lea +Toshiki Shima +Totoro W +Travis Cline +Trey Lawrence +Trey Roessig +Trey Tacon +Tristan Colgate +Tristan Ooohry +Trung Nguyen +Tudor Golubenco +Tuo Shan +Tyler Bunnell +Tyler Treat +Ugorji Nwoke +Ulf Holm Nielsen +Ulrich Kunitz +Upthere, Inc. +Uriel Mangado +Vadim Grek +Vadim Vygonets +Vendasta +Victor Vrantchan +Vincent Ambo +Vincent Batts +Vincent Vanackere +Vinu Rajashekhar +Vishvananda Ishaya +Vitor De Mario +Vladimir Mihailenco +Vladimir Nikishenko +Vladimir Stefanovic +Vladimir Varankin +Volker Dobler +Wade Simmons +Wander Lairson Costa +Weaveworks +Wei Guangjing +Weichao Tang +Will Storey +Willem van der Schyff +William Josephson +William Orr +Wisdom Omuya +Wu Yunzhou +Xia Bin +Xing Xing +Xu Fei +Xudong Zhang +Xuyang Kang +Yahoo Inc. +Yann Kerhervé +Yao Zhang +Yasha Bubnov +Yasuharu Goto +Yasuhiro Matsumoto +Yestin Sun +Yesudeep Mangalapilly +Yissakhar Z. Beck +Yo-An Lin +Yongjian Xu +Yorman Arias +Yoshiyuki Kanno +Yusuke Kagiwada +Yuusei Kuwana +Yuval Pavel Zholkover +Zac Bergquist +Zach Bintliff +Zak +Zakatell Kanda +Zellyn Hunter +Zemanta d.o.o. +Zev Goldstein +Ziad Hatahet +Zorion Arrizabalaga +Максим Федосеев +Фахриддин Балтаев +张嵩 +申习之 diff --git a/vendor/github.com/aclindsa/xml/CONTRIBUTORS b/vendor/github.com/aclindsa/xml/CONTRIBUTORS new file mode 100644 index 0000000..b1854ed --- /dev/null +++ b/vendor/github.com/aclindsa/xml/CONTRIBUTORS @@ -0,0 +1,1445 @@ +# This is the official list of people who can contribute +# (and typically have contributed) code to the Go repository. +# The AUTHORS file lists the copyright holders; this file +# lists people. For example, Google employees are listed here +# but not in AUTHORS, because Google holds the copyright. +# +# The submission process automatically checks to make sure +# that people submitting code are listed in this file (by email address). +# +# Names should be added to this file only after verifying that +# the individual or the individual's organization has agreed to +# the appropriate Contributor License Agreement, found here: +# +# http://code.google.com/legal/individual-cla-v1.0.html +# http://code.google.com/legal/corporate-cla-v1.0.html +# +# The agreement for individuals can be filled out on the web. +# +# When adding J Random Contributor's name to this file, +# either J's name or J's organization's name should be +# added to the AUTHORS file, depending on whether the +# individual or corporate CLA was used. + +# Names should be added to this file like so: +# Individual's name +# Individual's name +# +# An entry with multiple email addresses specifies that the +# first address should be used in the submit logs and +# that the other addresses should be recognized as the +# same person when interacting with Gerrit. + +# Please keep the list sorted. + +Aamir Khan +Aaron France +Aaron Jacobs +Aaron Kemp +Aaron Torres +Aaron Zinman +Abe Haskins +Abhinav Gupta +Adam Bender +Adam Eijdenberg +Adam Kisala +Adam Langley +Aditya Mukerjee +Adrian Nos +Adrian O'Grady +Adrien Bustany +Aécio Júnior +Agis Anastasopoulos +Ahmed Waheed Moanes +Ahmet Alp Balkan +Ahmy Yulrizka +Aiden Scandella +Ainar Garipov +Aishraj Dahal +Akhil Indurti +Akihiro Suda +Akshat Kumar +Alan Donovan +Alan Shreve +Albert Nigmatzianov +Albert Strasheim +Albert Yu +Alberto Bertogli +Alberto Donizetti +Alberto García Hierro +Aleksandar Dezelin +Alessandro Arzilli +Alessandro Baffa +Alex A Skinner +Alex Brainman +Alex Bramley +Alex Browne +Alex Carol +Alex Jin +Alex Plugaru +Alex Schroeder +Alex Sergeyev +Alex Vaghin +Alexander Demakin +Alexander Döring +Alexander Guz +Alexander Kauer +Alexander Kucherenko +Alexander Larsson +Alexander Menzhinsky +Alexander Morozov +Alexander Neumann +Alexander Orlov +Alexander Polcyn +Alexander Reece +Alexander Surma +Alexander Zhavnerchik +Alexander Zolotov +Alexandre Cesaro +Alexandre Fiori +Alexandre Normand +Alexandru Moșoi +Alexei Sholik +Alexey Borzenkov +Alexey Neganov +Alexey Palazhchenko +Alexis Hildebrandt +Alexis Hunt +Alexis Imperial-Legrand +Aliaksandr Valialkin +Alif Rachmawadi +Allan Simon +Alok Menghrajani +Amir Mohammad Saied +Amrut Joshi +Anders Pearson +André Carvalho +Andre Nathan +Andrea Spadaccini +Andreas Auernhammer +Andreas Jellinghaus +Andreas Litt +Andrei Korzhevskii +Andrei Vieru +Andres Erbsen +Andrew Austin +Andrew Balholm +Andrew Benton +Andrew Bonventre +Andrew Bursavich +Andrew Ekstedt +Andrew Etter +Andrew Gerrand +Andrew Harding +Andrew Jackura +Andrew Lutomirski +Andrew Pilloud +Andrew Pogrebnoy +Andrew Pritchard +Andrew Radev +Andrew Skiba +Andrew Szeto +Andrew Werner +Andrew Wilkins +Andrew Williams +Andrey Mirtchovski +Andrey Petrov +Andriy Lytvynov +Andy Balholm +Andy Davis +Andy Finkenstadt +Andy Maloney +Andy Walker +Anfernee Yongkun Gui +Angelo Bulfone +Anh Hai Trinh +Anmol Sethi +Anschel Schaffer-Cohen +Anthony Canino +Anthony Eufemio +Anthony Martin +Anthony Starks +Anthony Voutas +Anthony Woods +Antoine Martin +Antonio Bibiano +Antonio Murdaca +Antonio Troina +Apisak Darakananda +Aram Hăvărneanu +Areski Belaid +Arkadi Pyuro +Arlo Breault +Arnaud Ysmal +Arne Hormann +Arnout Engelen +Aron Nopanen +Artyom Pervukhin +Arvindh Rajesh Tamilmani +Asim Shankar +Atin Malaviya +Ato Araki +Audrey Lim +Augusto Roman +Aulus Egnatius Varialus +Austin Clements +awaw fumin +Awn Umar +Ayanamist Yang +Aymerick Jéhanne +Baiju Muthukadan +Balazs Lecz +Bartosz Grzybowski +Bastian Ike +Ben Burkert +Ben Eitzen +Ben Fried +Ben Lubar +Ben Lynn +Ben Olive +Ben Shi +Benjamin Black +Benjamin Prosnitz +Benjamin Wester +Benny Siegert +Benoit Sigoure +Berengar Lehr +Bill Neubauer +Bill O'Farrell +Bill Prin +Bill Thiede +Billie Harold Cleek +Billy Lynch +Bjorn Tillenius +Bjorn Tipling +Blake Gentry +Blake Mizerany +Blixt +Bobby Powers +Boris Nagaev +Brad Fitzpatrick +Brad Garcia +Brad Jones +Brad Whitaker +Braden Bassingthwaite +Brady Catherman +Brady Sullivan +Brandon Bennett +Brandon Gilmore +Brendan Daniel Tracey +Brendan O'Dea +Brett Cannon +Brian Dellisanti +Brian Downs +Brian G. Merrell +Brian Gitonga Marete +Brian Kennedy +Brian Ketelsen +Brian Slesinsky +Brian Smith +Brian Starke +Bryan Alexander +Bryan C. Mills +Bryan Chan +Bryan Ford +Bulat Gaifullin +Caine Tighe +Caio Marcelo de Oliveira Filho +Caleb Spare +Carl Chatfield +Carl Henrik Lunde +Carl Jackson +Carl Johnson +Carl Mastrangelo +Carl Shapiro +Carlisia Campos +Carlo Alberto Ferraris +Carlos Castillo +Carlos Cirello +Carlos Eduardo Seo +Carolyn Van Slyck +Cary Hull +Case Nelson +Casey Marshall +Catalin Nicutar +Catalin Patulea +Cedric Staub +Cezar Sá Espinola +ChaiShushan +Charles L. Dorian +Charles Lee +Charles Weill +Cherry Zhang +Chew Choon Keat +Chris Biscardi +Chris Broadfoot +Chris Dollin +Chris Farmiloe +Chris Hines +Chris Howey +Chris Hundt +Chris Jones +Chris Kastorff +Chris Lennert +Chris Manghane +Chris McGee +Chris Raynor +Chris Roche +Chris Stockton +Chris Zou +Christian Couder +Christian Himpel +Christine Hansmann +Christoffer Buchholz +Christoph Hack +Christopher Cahoon +Christopher Guiney +Christopher Nelson +Christopher Nielsen +Christopher Redden +Christopher Swenson +Christopher Wedgwood +Christy Perez +CL Sung +Clement Skau +Cody Oss +Colby Ranger +Colin Cross +Colin Edwards +Colin Kennedy +Conrad Irwin +Conrad Meyer +Corey Thomasson +Cosmos Nicolaou +Costin Chirvasuta +Cristian Staretu +Cuihtlauac ALVARADO +Cyrill Schumacher +Daisuke Fujita +Daker Fernandes Pinheiro +Damian Gryski +Damien Lespiau +Damien Neil +Dan Ballard +Dan Caddigan +Dan Callahan +Dan Harrington +Dan Jacques +Dan Peterson +Dan Pupius +Dan Sinclair +Daniel Fleischman +Daniel Johansson +Daniel Kerwin +Daniel Krech +Daniel Lidén +Daniel Martí +Daniel Morsing +Daniel Nadasi +Daniel Ortiz Pereira da Silva +Daniel Skinner +Daniel Speichert +Daniel Theophanes +Daniel Upton +Daria Kolistratova +Darren Elwood +Datong Sun +Dave Borowitz +Dave Bort +Dave Cheney +Dave Day +Dave Grijalva +Dave MacFarlane +David Anderson +David Barnett +David Benjamin +David Brophy +David Bürgin <676c7473@gmail.com> +David Calavera +David Chase +David Covert +David Crawshaw +David du Colombier <0intro@gmail.com> +David Forsythe +David G. Andersen +David Glasser +David Howden +David Hubbard +David Jakob Fritz +David Lazar +David Leon Gil +David McLeish +David NewHamlet +David Presotto +David R. Jenni +David Sansome +David Stainton +David Symonds +David Thomas +David Titarenco +David Volquartz Lebech +Davies Liu +Dean Prichard +Deepak Jois +Denis Bernard +Denis Brandolini +Denis Nagorny +Denys Honsiorovskyi +Derek Buitenhuis +Derek Che +Derek McGowan +Derek Parker +Derek Shockey +Devon H. O'Dell +Dhaivat Pandit +Dhananjay Nakrani +Dhiru Kholia +Di Xiao +Didier Spezia +Dieter Plaetinck +Dimitri Tcaciuc +Dirk Gadsden +Diwaker Gupta +Dmitri Popov +Dmitri Shuralyov +Dmitriy Dudkin +Dmitriy Shelenin +Dmitriy Vyukov +Dmitry Chestnykh +Dmitry Savintsev +Dmitry Yakunin +Dominic Green +Dominik Honnef +Dominik Vogt +Donald Huang +Dong-hee Na +Donovan Hide +Doug Anderson +Doug Fawley +Drew Hintz +Duncan Holm +Dustin Carlino +Dustin Herbison +Dustin Long +Dustin Sallings +Dustin Shields-Cloues +Dvir Volk +Dylan Waits +Eden Li +Edward Muller +Egon Elbre +Ehren Kret +Eitan Adler +Eivind Uggedal +Elias Naur +Elliot Morrison-Reed +Emil Hessman +Emilien Kenler +Emmanuel Odeke +Eoghan Sherry +Eric Chiang +Eric Clark +Eric Engestrom +Eric Garrido +Eric Koleda +Eric Lagergren +Eric Milliken +Eric Roshan-Eisner +Erik Aigner +Erik Dubbelboer +Erik St. Martin +Erik Staab +Erik Westrup +Ernest Chiang +Esko Luontola +Ethan Burns +Ethan Miller +Euan Kemp +Evan Broder +Evan Brown +Evan Hicks +Evan Kroske +Evan Martin +Evan Phoenix +Evan Shaw +Evgeniy Polyakov +Ewan Chou +Ewan Valentine +Eyal Posener +Fabian Wickborn +Fabian Zaremba +Fabrizio Milo +Faiyaz Ahmed +Fan Hongjian +Fangming Fang +Fannie Zhang +Fatih Arslan +Fazal Majid +Fazlul Shahriar +Federico Simoncelli +Fedor Indutny +Felipe Oliveira +Felix Geisendörfer +Filip Gruszczyński +Filippo Valsorda +Firmansyah Adiputra +Florian Uekermann +Florian Weimer +Florin Patan +Folke Behrens +Ford Hurley +Francesc Campoy +Francisco Claude +Francisco Rojas +Francisco Souza +Frederick Kelly Mayle III +Fredrik Enestad +Fredrik Forsmo +Frithjof Schulze +Frits van Bommel +Fumitoshi Ukai +Gaal Yahas +Gabríel Arthúr Pétursson +Gabriel Aszalos +Gabriel Nicolas Avellaneda +Gabriel Russell +Gareth Paul Jones +Garrick Evans +Gary Burd +Gary Elliott +Gaurish Sharma +Gautham Thambidorai +Geert-Johan Riemer +Gengliang Wang +Geoffroy Lorieux +Georg Reinke +George Gkirtsou +George Shammas +Gerasimos Dimitriadis +Gideon Jan-Wessel Redelinghuys +Giles Lean +Giovanni Bajo +Giulio Iotti +Gleb Stepanov +Glenn Brown +Glenn Lewis +Gordon Klaus +Graham King +Graham Miller +Greg Poirier +Greg Ward +Gregory Man +Guillaume J. Charmes +Guobiao Mei +Gustav Paul +Gustav Westling +Gustavo Franco +Gustavo Niemeyer +Gwenael Treguier +Gyu-Ho Lee +H. İbrahim Güngör +Hajime Hoshi +Hallgrimur Gunnarsson +Han-Wen Nienhuys +Hang Qian +Hari haran +Hariharan Srinath +Harley Laue +Harry Moreno +Harshavardhana +Hauke Löffler +Håvard Haugen +Hector Chu +Hector Martin Cantero +Henning Schmiedehausen +Henrik Edwards +Henrik Hodne +Henry Chang +Herbert Georg Fischer +Heschi Kreinick +Hironao OTSUBO +Hiroshi Ioka +Hitoshi Mitake +Holden Huang +Hong Ruiqi +Hongfei Tan +Hossein Sheikh Attar +Hsin Tsao +Hsin-Ho Yeh +Hu Keping +Hugues Bruant +Hyang-Ah Hana Kim +Ian Gudger +Ian Lance Taylor +Ibrahim AshShohail +Icarus Sparry +Iccha Sethi +Idora Shinatose +Igor Bernstein +Igor Dolzhikov +Ilya Tocar +INADA Naoki +Ingo Krabbe +Ingo Oeser +Irieda Noboru +Isaac Wagner +Ivan Babrou +Ivan Krasin +Ivan Moscoso +Ivan Ukhov +Jaana Burcu Dogan +Jack Lindamood +Jacob Baskin +Jacob H. Haven +Jacob Hoffman-Andrews +Jae Kwon +Jakob Borg +Jakub Čajka +Jakub Ryszard Czarnowicz +James Aguilar +James Bardin +James Chacon +James Clarke +James Cowgill +James David Chalfant +James Fysh +James Gray +James Hartig +James Meneghello +James Myers +James Neve +James P. Cooper +James Robinson +James Schofield +James Smith +James Sweet +James Toy +James Tucker +James Whitehead +Jamie Beverly +Jamie Gennis +Jamie Kerr +Jamie Stackhouse +Jamie Turner +Jamie Wilkinson +Jamil Djadala +Jan Berktold +Jan H. Hosang +Jan Kratochvil +Jan Mercl <0xjnml@gmail.com> +Jan Newmarch +Jan Ziak <0xe2.0x9a.0x9b@gmail.com> +Jani Monoses +Jaroslavas Počepko +Jason Barnett +Jason Buberel +Jason Chu +Jason Del Ponte +Jason Hall +Jason Smale +Jason Travis +Jay Conrod +Jay Weisskopf +Jean-Marc Eurin +Jean-Nicolas Moal +Jed Denlea +Jeff (Zhefu) Jiang +Jeff Craig +Jeff Hodges +Jeff Johnson +Jeff R. Allen +Jeff Sickel +Jeff Wendling +Jeffrey H +Jens Frederich +Jeremiah Harmsen +Jeremy Jackins +Jeremy Schlatter +Jeroen Bobbeldijk +Jess Frazelle +Jesse Szwedko +Jianing Yu +Jianqiao Li +Jihyun Yu +Jim Cote +Jim Kingdon +Jim McGrath +Jimmy Zelinskie +Jin-wook Jeong +Jingcheng Zhang +Jingguo Yao +Jiong Du +Jirka Daněk +Joakim Sernbrant +Joe Farrell +Joe Harrison +Joe Henke +Joe Kyo +Joe Poirier +Joe Richey joerichey@google.com +Joe Shaw +Joe Sylve +Joe Tsai +Joel Sing +Joël Stemmer +Joel Stemmer +Joey Geiger +Johan Brandhorst +Johan Euphrosine +Johan Sageryd +John Asmuth +John Beisley +John C Barstow +John DeNero +John Dethridge +John Graham-Cumming +John Howard Palevich +John Jeffery +John Jenkins +John Newlin +John Potocny +John R. Lenton +John Schnake +John Shahid +John Tuley +Johnny Luo +Jon Chen +Jonathan Allie +Jonathan Amsterdam +Jonathan Boulle +Jonathan Feinberg +Jonathan Gold +Jonathan Hseu +Jonathan Mark +Jonathan Nieder +Jonathan Pittman +Jonathan Rudenberg +Jonathan Stacks +Jonathan Wills +Jongmin Kim +Joonas Kuorilehto +Joop Kiefte +Jordan Krage +Jordan Lewis +Jos Visser +Jose Luis Vázquez González +Joseph Bonneau +Joseph Holsten +Josh Bleecher Snyder +Josh Chorlton +Josh Deprez +Josh Goebel +Josh Hoak +Josh Holland +Josh Roppo +Joshua Boelter +Joshua Chase +Josselin Costanzi +Jostein Stuhaug +JP Sugarbroad +JT Olds +Jukka-Pekka Kekkonen +Julia Hansbrough +Julian Kornberger +Julian Pastarmov +Julian Phillips +Julien Schmidt +Julio Montes +Jungho Ahn +Jure Ham +Justin Nuß +Justyn Temme +Kai Backman +Kai Trukenmüller +Kale Blankenship +Kaleb Elwert +Kamal Aboul-Hosn +Kamil Chmielewski +Kamil Kisiel +Kang Hu +Karan Dhiman +Karoly Negyesi +Kashav Madan +Kate Manson +Kato Kazuyoshi +Katrina Owen +Kaviraj Kanagaraj +Kay Zhu +KB Sriram +Keegan Carruthers-Smith +Kei Son +Keiji Yoshida +Keith Ball +Keith Randall +Keith Rarick +Kelsey Hightower +Kelvin Foo Chuan Lyi +Ken Friedenbach +Ken Rockot +Ken Sedgwick +Ken Thompson +Kenji Kaneda +Kenneth Shaw +Kenny Grant +Kevin Ballard +Kevin Burke +Kevin Kirsche +Kevin Klues +Kevin Malachowski +Kevin Vu +Kim Shrier +Kim Yongbin +Kirill Smelkov +Kirklin McDonald +Klaus Post +Kodie Goodwin +Koichi Shiraishi +Koki Ide +Konstantin +Konstantin Shaposhnikov +Kris Nova +Kris Rousey +Kristopher Watts +Kun Li +Kyle Consalus +Kyle Isom +Kyle Jones +Kyle Lemons +Kyrylo Silin +L Campbell +Lai Jiangshan +Larry Hosken +Lars Jeppesen +Lars Wiegman +Larz Conwell +Laurie Clark-Michalek +LE Manh Cuong +Lee Hinman +Lee Packham +Leo Rudberg +Leon Klingele +Lev Shamardin +Lewin Bormann +Lion Yang +Lloyd Dewolf +Lorenzo Masini +Lorenzo Stoakes +Louis Kruger +Luan Santos +Luca Greco +Lucas Bremgartner +Lucas Clemente +Lucien Stuker +Lucio De Re +Ludi Rehak +Luigi Riefolo +Luit van Drongelen +Luka Zakrajšek +Lukasz Milewski +Luke Curley +Luna Duclos +Luuk van Dijk +Lynn Boger +Ma Peiqi +Magnus Hiie +Maksym Trykur +Mal Curtis +Manfred Touron +Manoj Dayaram +Manu Garg +Manu S Ajith +Manuel Mendez +Marc Weistroff +Marc-Antoine Ruel +Marcel Edmund Franke +Marcel van Lohuizen +Marcelo E. Magallon +Marco Hennings +Marga Manterola +Marin Bašić +Marius Nuennerich +Mark Adams +Mark Bucciarelli +Mark Harrison +Mark Ryan +Mark Severson +Mark Theunissen +Mark Zavislak +Marko Juhani Silokunnas +Marko Mikulicic +Marko Mudrinic +Marko Tiikkaja +Markus Duft +Markus Sonderegger +Markus Zimmermann +Martin Bertschler +Martin Garton +Martin Habbecke +Martin Hamrle +Martin Hoefling +Martin Kreichgauer +Martin Lindhe +Martin Möhrmann +Martin Neubauer +Martin Olsen +Martin Olsson +Martin Probst +Martynas Budriūnas +Marvin Stenger +Marwan Sulaiman +Maryan Hratson +Masahiro Furudate +Masahiro Wakame +Masaki Yoshida +Máté Gulyás +Mateusz Czapliński +Mathias Beke +Mathias Hall-Andersen +Mathias Leppich +Mathieu Lonjaret +Mats Lidell +Matt Aimonetti +Matt Blair +Matt Bostock +Matt Brown +Matt Drollette +Matt Harden +Matt Jibson +Matt Joiner +Matt Jones +Matt Layher +Matt Reiferson +Matt Robenolt +Matt Strong +Matt T. Proud +Matt Williams +Matthew Brennan +Matthew Cottingham +Matthew Dempsky +Matthew Denton +Matthew Holt +Matthew Horsnell +Matthieu Hauglustaine +Matthieu Olivier +Max Riveiro +Maxim Khitrov +Maxim Pimenov +Maxim Ushakov +Maxime de Roucy +Máximo Cuadros Ortiz +Maxwell Krohn +Meir Fischer +Meng Zhuo +Mhd Sulhan +Micah Stetson +Michael Chaten +Michael Darakananda +Michael Edwards +Michael Elkins +Michael Fraenkel +Michael Gehring +Michael Hendricks +Michael Hoisie +Michael Hudson-Doyle +Michael Käufl +Michael Kelly +Michael Lewis +Michael MacInnis +Michael Marineau +Michael Matloob +Michael McConville +Michael McGreevy +Michael Munday +Michael Pearson +Michael Piatek +Michael Pratt +Michael Schaller +Michael Shields +Michael Stapelberg +Michael T. Jones +Michael Teichgräber +Michael Vetter +Michal Bohuslávek +Michal Cierniak +Michał Derkacz +Michalis Kargakis +Michel Lespinasse +Miek Gieben +Miguel Mendez +Miguel Molina +Mihai Borobocea +Mikael Tillenius +Mike Andrews +Mike Appleby +Mike Danese +Mike Houston +Mike Rosset +Mike Samuel +Mike Solomon +Mike Strosaker +Mike Wiacek +Mikhail Gusarov +Mikhail Panchenko +Miki Tebeka +Mikio Hara +Mikkel Krautz +Milutin Jovanović +Miquel Sabaté Solà +Miroslav Genov +Mohit Agarwal +Momchil Velikov +Monis Khan +Monty Taylor +Moriyoshi Koizumi +Morten Siebuhr +Môshe van der Sterre +Mostyn Bramley-Moore +Mrunal Patel +Muhammed Uluyol +Mura Li +Nan Deng +Nathan Caza +Nathan Humphreys +Nathan John Youngman +Nathan Otterness +Nathan P Finch +Nathan VanBenschoten +Nathan Youngman +Nathan(yinian) Hu +Nathaniel Cook +Neelesh Chandola +Neil Lyons +Neuman Vong +Nevins Bartolomeo +Niall Sheridan +Nic Day +Nicholas Katsaros +Nicholas Maniscalco +Nicholas Presta +Nicholas Sullivan +Nicholas Waples +Nick Cooper +Nick Craig-Wood +Nick Harper +Nick Kubala +Nick Leli +Nick Miyake +Nick Patavalis +Nick Petroni +Nick Robinson +Nicolas Kaiser +Nicolas Owens +Nicolas S. Dade +Niels Widger +Nigel Kerr +Nigel Tao +Nik Nyby +Niklas Schnelle +Niko Dziemba +Nikolay Turpitko +Niranjan Godbole +Noah Campbell +Nodir Turakulov +Norberto Lopes +Odin Ugedal +Oleg Bulatov +Oleg Vakheta +Oleku Konko +Oling Cat +Oliver Hookins +Oliver Tonnhofer +Olivier Antoine +Olivier Duperray +Olivier Poitrey +Olivier Saingre +Omar Jarjur +Özgür Kesim +Pablo Lalloni +Pablo Santiago Blum de Aguiar +Padraig Kitterick +Paolo Giarrusso +Paolo Martini +Parker Moore +Pascal S. de Kloe +Pat Moroney +Patrick Crosby +Patrick Gavlin +Patrick Higgins +Patrick Lee +Patrick Mézard +Patrick Mylund Nielsen +Patrick Pelletier +Patrick Riley +Patrick Smith +Paul A Querna +Paul Borman +Paul Chang +Paul Hammond +Paul Hankin +Paul Jolly +Paul Lalonde +Paul Marks +Paul Meyer +Paul Nasrat +Paul Querna +Paul Rosania +Paul Sbarra +Paul Smith +Paul van Brouwershaven +Paul Wankadia +Paulo Casaretto +Paulo Flabiano Smorigo +Pavel Paulau +Pavel Zinovkin +Pavlo Sumkin +Pawel Knap +Pawel Szczur +Percy Wegmann +Perry Abbott +Petar Maymounkov +Peter Armitage +Peter Bourgon +Peter Collingbourne +Peter Froehlich +Peter Gonda +Peter Kleiweg +Peter McKenzie +Peter Moody +Peter Morjan +Peter Mundy +Peter Nguyen +Péter Surányi +Péter Szabó +Péter Szilágyi +Peter Tseng +Peter Waldschmidt +Peter Waller +Peter Weinberger +Peter Williams +Phil Pennock +Philip Børgesen +Philip Hofer +Philip K. Warren +Pierre Durand +Pierre Roullon +Piers +Pieter Droogendijk +Pietro Gagliardi +Prasanna Swaminathan +Prashant Varanasi +Pravendra Singh +Preetam Jinka +Qiuxuan Zhu +Quan Tran +Quan Yong Zhai +Quentin Perez +Quentin Renard +Quentin Smith +Quinn Slack +Quoc-Viet Nguyen +Radu Berinde +Rafal Jeczalik +Rahul Chaudhry +Raif S. Naffah +Rajat Goel +Ralph Corderoy +Ramesh Dharan +Raph Levien +Raphael Geronimi +Raul Silvera +Ray Tung +Raymond Kazlauskas +Rebecca Stambler +Reinaldo de Souza Jr +Remi Gillig +Rémy Oudompheng +Rhys Hiltner +Ricardo Padilha +Richard Barnes +Richard Crowley +Richard Dingwall +Richard Eric Gavaletz +Richard Gibson +Richard Miller +Richard Musiol +Rick Arnold +Rick Hudson +Rick Sayre +Riku Voipio +Risto Jaakko Saarelma +Rob Earhart +Rob Norman +Rob Phoenix +Rob Pike +Robert Daniel Kortschak +Robert Dinu +Robert Figueiredo +Robert Griesemer +Robert Hencke +Robert Iannucci +Robert Obryk +Robert Sesek +Robert Snedegar +Robert Stepanek +Robin Eklind +Rodrigo Moraes de Oliveira +Rodrigo Rafael Monti Kochenburger +Roger Pau Monné +Roger Peppe +Roland Shoemaker +Ron Hashimoto +Ron Minnich +Ross Light +Rowan Worth +Rui Ueyama +Russ Cox +Russell Haering +Ryan Bagwell +Ryan Barrett +Ryan Boehning +Ryan Brown +Ryan Hitchman +Ryan Lower +Ryan Seys +Ryan Slade +Ryuzo Yamamoto +S.Çağlar Onur +Sai Cheemalapati +Sakeven Jiang +Salmān Aljammāz +Sam Boyer +Sam Ding +Sam Hug +Sam Thorogood +Sam Whited +Sameer Ajmani +Sami Commerot +Samuel Tan +Samuele Pedroni +Sanjay Menakuru +Sarah Adams +Sascha Brawer +Sasha Lionheart +Sasha Sobol +Scott Barron +Scott Bell +Scott Ferguson +Scott Lawrence +Scott Mansfield +Scott Schwartz +Scott Van Woudenberg +Sean Burford +Sean Chittenden +Sean Christopherson +Sean Dolphin +Sean Harger +Sean Rees +Sebastien Binet +Sébastien Paolacci +Sergei Skorobogatov +Sergey 'SnakE' Gromov +Sergey Arseev +Sergey Mishin +Sergio Luis O. B. Correia +Seth Hoenig +Seth Vargo +Shahar Kohanim +Shane Hansen +Shaozhen Ding +Shawn Ledbetter +Shawn Smith +Shawn Walker-Salas +Shenghou Ma +Shi Han Ng +Shinji Tanaka +Shintaro Kaneko +Shivakumar GN +Shun Fan +Silvan Jegen +Simon Jefford +Simon Rawet +Simon Thulbourn +Simon Whitehead +Sina Siadat +Sokolov Yura +Song Gao +Spencer Nelson +Spencer Tung +Spring Mc +Srdjan Petrovic +Sridhar Venkatakrishnan +StalkR +Stan Schwertly +Stefan Nilsson +Stéphane Travostino +Stephen Ma +Stephen McQuay +Stephen Searles +Stephen Weinberg +Steve Francia +Steve McCoy +Steve Newman +Steve Phillips +Steve Streeting +Steven Buss +Steven Elliot Harris +Steven Erenst +Steven Hartland +Steven Wilkin +Sugu Sougoumarane +Suharsh Sivakumar +Sunny +Suyash +Suzy Mueller +Sven Almgren +Sven Blumenstein +Syohei YOSHIDA +Szabolcs Nagy +Tad Glines +Taj Khattra +Takashi Matsuo +Takeshi YAMANASHI <9.nashi@gmail.com> +Takuto Ikuta +Takuya Ueda +Tal Shprecher +Tamir Duberstein +Tarmigan Casebolt +Taro Aoki +Taru Karttunen +Tatsuhiro Tsujikawa +Ted Kornish +Terrel Shumway +Tetsuo Kiso +Than McIntosh +Thanatat Tamtan +Thiago Fransosi Farina +Thomas Alan Copeland +Thomas Bonfort +Thomas Bouldin +Thomas de Zeeuw +Thomas Desrosiers +Thomas Habets +Thomas Kappler +Thorben Krueger +Thordur Bjornsson +Tilman Dilo +Tim Cooijmans +Tim Ebringer +Tim Heckman +Tim Henderson +Tim Hockin +Tim Swast +Timo Savola +Timo Truyts +Timothy Studd +Tipp Moseley +Tobias Columbus +Tobias Klauser +Toby Burress +Todd Neal +Todd Wang +Tom Bergan +Tom Heng +Tom Linford +Tom Szymanski +Tom Wilkie +Tommy Schaefer +Tonis Tiigi +Tony Walker +Tor Andersson +Tormod Erevik Lea +Toshiki Shima +Totoro W +Travis Cline +Trevor Strohman +Trey Lawrence +Trey Roessig +Trey Tacon +Tristan Amini +Tristan Colgate +Tristan Ooohry +Trung Nguyen +Tudor Golubenco +Tuo Shan +Tyler Bunnell +Tyler Treat +Tzu-Jung Lee +Ugorji Nwoke +Ulf Holm Nielsen +Ulrich Kunitz +Uriel Mangado +Uttam C Pawar +Vadim Grek +Vadim Vygonets +Vega Garcia Luis Alfonso +Victor Chudnovsky +Victor Vrantchan +Vikas Kedia +Vincent Ambo +Vincent Batts +Vincent Vanackere +Vinu Rajashekhar +Vish Subramanian +Vishvananda Ishaya +Vitor De Mario +Vlad Krasnov +Vladimir Mihailenco +Vladimir Nikishenko +Vladimir Stefanovic +Vladimir Varankin +Volker Dobler +Volodymyr Paprotski +Wade Simmons +Walter Poupore +Wander Lairson Costa +Wedson Almeida Filho +Wei Guangjing +Wei Xiao +Weichao Tang +Will Chan +Will Norris +Will Storey +Willem van der Schyff +William Chan +William Josephson +William Orr +Wisdom Omuya +Wu Yunzhou +Xia Bin +Xing Xing +Xu Fei +Xudong Zhang +Xuyang Kang +Yan Zou +Yann Kerhervé +Yao Zhang +Yasha Bubnov +Yasuharu Goto +Yasuhiro Matsumoto +Yestin Sun +Yesudeep Mangalapilly +Yissakhar Z. Beck +Yo-An Lin +Yongjian Xu +Yorman Arias +Yoshiyuki Kanno +Yu Heng Zhang +Yu Xuan Zhang +Yuki Yugui Sonoda +Yusuke Kagiwada +Yuusei Kuwana +Yuval Pavel Zholkover +Yves Junqueira +Zac Bergquist +Zach Bintliff +Zak +Zakatell Kanda +Zellyn Hunter +Zev Goldstein +Zhongwei Yao +Ziad Hatahet +Zorion Arrizabalaga +Максим Федосеев +Фахриддин Балтаев +张嵩 +申习之 diff --git a/vendor/github.com/aclindsa/xml/LICENSE b/vendor/github.com/aclindsa/xml/LICENSE new file mode 100644 index 0000000..6a66aea --- /dev/null +++ b/vendor/github.com/aclindsa/xml/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/aclindsa/xml/PATENTS b/vendor/github.com/aclindsa/xml/PATENTS new file mode 100644 index 0000000..7330990 --- /dev/null +++ b/vendor/github.com/aclindsa/xml/PATENTS @@ -0,0 +1,22 @@ +Additional IP Rights Grant (Patents) + +"This implementation" means the copyrightable works distributed by +Google as part of the Go project. + +Google hereby grants to You a perpetual, worldwide, non-exclusive, +no-charge, royalty-free, irrevocable (except as stated in this section) +patent license to make, have made, use, offer to sell, sell, import, +transfer and otherwise run, modify and propagate the contents of this +implementation of Go, where such license applies only to those patent +claims, both currently owned or controlled by Google and acquired in +the future, licensable by Google that are necessarily infringed by this +implementation of Go. This grant does not include claims that would be +infringed only as a consequence of further modification of this +implementation. If you or your agent or exclusive licensee institute or +order or agree to the institution of patent litigation against any +entity (including a cross-claim or counterclaim in a lawsuit) alleging +that this implementation of Go or any code incorporated within this +implementation of Go constitutes direct or contributory patent +infringement, or inducement of patent infringement, then any patent +rights granted to you under this License for this implementation of Go +shall terminate as of the date such litigation is filed. diff --git a/vendor/github.com/aclindsa/xml/marshal.go b/vendor/github.com/aclindsa/xml/marshal.go new file mode 100644 index 0000000..9bcefc0 --- /dev/null +++ b/vendor/github.com/aclindsa/xml/marshal.go @@ -0,0 +1,1075 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package xml + +import ( + "bufio" + "bytes" + "encoding" + "fmt" + "io" + "reflect" + "sort" + "strconv" + "strings" +) + +const ( + // Header is a generic XML header suitable for use with the output of Marshal. + // This is not automatically added to any output of this package, + // it is provided as a convenience. + Header = `` + "\n" + xmlNamespacePrefix = "xml" +) + +// Marshal returns the XML encoding of v. +// +// Marshal handles an array or slice by marshaling each of the elements. +// Marshal handles a pointer by marshaling the value it points at or, if the +// pointer is nil, by writing nothing. Marshal handles an interface value by +// marshaling the value it contains or, if the interface value is nil, by +// writing nothing. Marshal handles all other data by writing one or more XML +// elements containing the data. +// +// The name for the XML elements is taken from, in order of preference: +// - the tag on the XMLName field, if the data is a struct +// - the value of the XMLName field of type Name +// - the tag of the struct field used to obtain the data +// - the name of the struct field used to obtain the data +// - the name of the marshaled type +// +// The XML element for a struct contains marshaled elements for each of the +// exported fields of the struct, with these exceptions: +// - the XMLName field, described above, is omitted. +// - a field with tag "-" is omitted. +// - a field with tag "name,attr" becomes an attribute with +// the given name in the XML element. +// - a field with tag ",attr" becomes an attribute with the +// field name in the XML element. +// - a field with tag ",chardata" is written as character data, +// not as an XML element. +// - a field with tag ",cdata" is written as character data +// wrapped in one or more tags, not as an XML element. +// - a field with tag ",innerxml" is written verbatim, not subject +// to the usual marshaling procedure. +// - a field with tag ",comment" is written as an XML comment, not +// subject to the usual marshaling procedure. It must not contain +// the "--" string within it. +// - a field with a tag including the "omitempty" option is omitted +// if the field value is empty. The empty values are false, 0, any +// nil pointer or interface value, and any array, slice, map, or +// string of length zero. +// - an anonymous struct field is handled as if the fields of its +// value were part of the outer struct. +// +// If a field uses a tag "a>b>c", then the element c will be nested inside +// parent elements a and b. Fields that appear next to each other that name +// the same parent will be enclosed in one XML element. +// +// See MarshalIndent for an example. +// +// Marshal will return an error if asked to marshal a channel, function, or map. +func Marshal(v interface{}) ([]byte, error) { + var b bytes.Buffer + if err := NewEncoder(&b).Encode(v); err != nil { + return nil, err + } + return b.Bytes(), nil +} + +// Marshaler is the interface implemented by objects that can marshal +// themselves into valid XML elements. +// +// MarshalXML encodes the receiver as zero or more XML elements. +// By convention, arrays or slices are typically encoded as a sequence +// of elements, one per entry. +// Using start as the element tag is not required, but doing so +// will enable Unmarshal to match the XML elements to the correct +// struct field. +// One common implementation strategy is to construct a separate +// value with a layout corresponding to the desired XML and then +// to encode it using e.EncodeElement. +// Another common strategy is to use repeated calls to e.EncodeToken +// to generate the XML output one token at a time. +// The sequence of encoded tokens must make up zero or more valid +// XML elements. +type Marshaler interface { + MarshalXML(e *Encoder, start StartElement) error +} + +// MarshalerAttr is the interface implemented by objects that can marshal +// themselves into valid XML attributes. +// +// MarshalXMLAttr returns an XML attribute with the encoded value of the receiver. +// Using name as the attribute name is not required, but doing so +// will enable Unmarshal to match the attribute to the correct +// struct field. +// If MarshalXMLAttr returns the zero attribute Attr{}, no attribute +// will be generated in the output. +// MarshalXMLAttr is used only for struct fields with the +// "attr" option in the field tag. +type MarshalerAttr interface { + MarshalXMLAttr(name Name) (Attr, error) +} + +// MarshalIndent works like Marshal, but each XML element begins on a new +// indented line that starts with prefix and is followed by one or more +// copies of indent according to the nesting depth. +func MarshalIndent(v interface{}, prefix, indent string) ([]byte, error) { + var b bytes.Buffer + enc := NewEncoder(&b) + enc.Indent(prefix, indent) + if err := enc.Encode(v); err != nil { + return nil, err + } + return b.Bytes(), nil +} + +// An Encoder writes XML data to an output stream. +type Encoder struct { + p printer +} + +// NewEncoder returns a new encoder that writes to w. +func NewEncoder(w io.Writer) *Encoder { + e := &Encoder{printer{Writer: bufio.NewWriter(w)}} + e.p.encoder = e + return e +} + +// SetDisableAutoClose disables auto-closing for the provided tags +func (enc *Encoder) SetDisableAutoClose(tagNames ...string) { + sort.Strings(tagNames) + enc.p.disableAutoClose = tagNames +} + +// CarriageReturn enables or disables use of the carriage return '\r\n' when writing new lines +func (enc *Encoder) CarriageReturn(enable bool) { + enc.p.carriageReturn = enable +} + +// Indent sets the encoder to generate XML in which each element +// begins on a new indented line that starts with prefix and is followed by +// one or more copies of indent according to the nesting depth. +func (enc *Encoder) Indent(prefix, indent string) { + enc.p.prefix = prefix + enc.p.indent = indent + enc.p.doindent = true +} + +func (enc *Encoder) NoIndent() { + enc.p.doindent = false +} + +// Encode writes the XML encoding of v to the stream. +// +// See the documentation for Marshal for details about the conversion +// of Go values to XML. +// +// Encode calls Flush before returning. +func (enc *Encoder) Encode(v interface{}) error { + err := enc.p.marshalValue(reflect.ValueOf(v), nil, nil) + if err != nil { + return err + } + return enc.p.Flush() +} + +// EncodeElement writes the XML encoding of v to the stream, +// using start as the outermost tag in the encoding. +// +// See the documentation for Marshal for details about the conversion +// of Go values to XML. +// +// EncodeElement calls Flush before returning. +func (enc *Encoder) EncodeElement(v interface{}, start StartElement) error { + err := enc.p.marshalValue(reflect.ValueOf(v), nil, &start) + if err != nil { + return err + } + return enc.p.Flush() +} + +var ( + begComment = []byte("") + endProcInst = []byte("?>") +) + +// EncodeToken writes the given XML token to the stream. +// It returns an error if StartElement and EndElement tokens are not properly matched. +// +// EncodeToken does not call Flush, because usually it is part of a larger operation +// such as Encode or EncodeElement (or a custom Marshaler's MarshalXML invoked +// during those), and those will call Flush when finished. +// Callers that create an Encoder and then invoke EncodeToken directly, without +// using Encode or EncodeElement, need to call Flush when finished to ensure +// that the XML is written to the underlying writer. +// +// EncodeToken allows writing a ProcInst with Target set to "xml" only as the first token +// in the stream. +func (enc *Encoder) EncodeToken(t Token) error { + + p := &enc.p + switch t := t.(type) { + case StartElement: + if err := p.writeStart(&t); err != nil { + return err + } + case EndElement: + if err := p.writeEnd(t.Name); err != nil { + return err + } + case CharData: + escapeText(p, t, false) + case Comment: + if bytes.Contains(t, endComment) { + return fmt.Errorf("xml: EncodeToken of Comment containing --> marker") + } + p.WriteString("") + return p.cachedWriteError() + case ProcInst: + // First token to be encoded which is also a ProcInst with target of xml + // is the xml declaration. The only ProcInst where target of xml is allowed. + if t.Target == "xml" && p.Buffered() != 0 { + return fmt.Errorf("xml: EncodeToken of ProcInst xml target only valid for xml declaration, first token encoded") + } + if !isNameString(t.Target) { + return fmt.Errorf("xml: EncodeToken of ProcInst with invalid Target") + } + if bytes.Contains(t.Inst, endProcInst) { + return fmt.Errorf("xml: EncodeToken of ProcInst containing ?> marker") + } + p.WriteString(" 0 { + p.WriteByte(' ') + p.Write(t.Inst) + } + p.WriteString("?>") + case Directive: + if !isValidDirective(t) { + return fmt.Errorf("xml: EncodeToken of Directive containing wrong < or > markers") + } + p.WriteString("") + default: + return fmt.Errorf("xml: EncodeToken of invalid token type") + + } + return p.cachedWriteError() +} + +// isValidDirective reports whether dir is a valid directive text, +// meaning angle brackets are matched, ignoring comments and strings. +func isValidDirective(dir Directive) bool { + var ( + depth int + inquote uint8 + incomment bool + ) + for i, c := range dir { + switch { + case incomment: + if c == '>' { + if n := 1 + i - len(endComment); n >= 0 && bytes.Equal(dir[n:i+1], endComment) { + incomment = false + } + } + // Just ignore anything in comment + case inquote != 0: + if c == inquote { + inquote = 0 + } + // Just ignore anything within quotes + case c == '\'' || c == '"': + inquote = c + case c == '<': + if i+len(begComment) < len(dir) && bytes.Equal(dir[i:i+len(begComment)], begComment) { + incomment = true + } else { + depth++ + } + case c == '>': + if depth == 0 { + return false + } + depth-- + } + } + return depth == 0 && inquote == 0 && !incomment +} + +// Flush flushes any buffered XML to the underlying writer. +// See the EncodeToken documentation for details about when it is necessary. +func (enc *Encoder) Flush() error { + return enc.p.Flush() +} + +type printer struct { + *bufio.Writer + encoder *Encoder + seq int + indent string + prefix string + doindent bool + depth int + indentedIn bool + putNewline bool + attrNS map[string]string // map prefix -> name space + attrPrefix map[string]string // map name space -> prefix + prefixes []string + tags []Name + + disableAutoClose []string + carriageReturn bool +} + +// createAttrPrefix finds the name space prefix attribute to use for the given name space, +// defining a new prefix if necessary. It returns the prefix. +func (p *printer) createAttrPrefix(url string) string { + if prefix := p.attrPrefix[url]; prefix != "" { + return prefix + } + + // The "http://www.w3.org/XML/1998/namespace" name space is predefined as "xml" + // and must be referred to that way. + // (The "http://www.w3.org/2000/xmlns/" name space is also predefined as "xmlns", + // but users should not be trying to use that one directly - that's our job.) + if url == xmlURL { + return xmlNamespacePrefix + } + + // Need to define a new name space. + if p.attrPrefix == nil { + p.attrPrefix = make(map[string]string) + p.attrNS = make(map[string]string) + } + + // Pick a name. We try to use the final element of the path + // but fall back to _. + prefix := strings.TrimRight(url, "/") + if i := strings.LastIndexByte(prefix, '/'); i >= 0 { + prefix = prefix[i+1:] + } + if prefix == "" || !isName([]byte(prefix)) || strings.Contains(prefix, ":") { + prefix = "_" + } + if strings.HasPrefix(prefix, "xml") { + // xmlanything is reserved. + prefix = "_" + prefix + } + if p.attrNS[prefix] != "" { + // Name is taken. Find a better one. + for p.seq++; ; p.seq++ { + if id := prefix + "_" + strconv.Itoa(p.seq); p.attrNS[id] == "" { + prefix = id + break + } + } + } + + p.attrPrefix[url] = prefix + p.attrNS[prefix] = url + + p.WriteString(`xmlns:`) + p.WriteString(prefix) + p.WriteString(`="`) + EscapeText(p, []byte(url)) + p.WriteString(`" `) + + p.prefixes = append(p.prefixes, prefix) + + return prefix +} + +// deleteAttrPrefix removes an attribute name space prefix. +func (p *printer) deleteAttrPrefix(prefix string) { + delete(p.attrPrefix, p.attrNS[prefix]) + delete(p.attrNS, prefix) +} + +func (p *printer) markPrefix() { + p.prefixes = append(p.prefixes, "") +} + +func (p *printer) popPrefix() { + for len(p.prefixes) > 0 { + prefix := p.prefixes[len(p.prefixes)-1] + p.prefixes = p.prefixes[:len(p.prefixes)-1] + if prefix == "" { + break + } + p.deleteAttrPrefix(prefix) + } +} + +var ( + marshalerType = reflect.TypeOf((*Marshaler)(nil)).Elem() + marshalerAttrType = reflect.TypeOf((*MarshalerAttr)(nil)).Elem() + textMarshalerType = reflect.TypeOf((*encoding.TextMarshaler)(nil)).Elem() +) + +// marshalValue writes one or more XML elements representing val. +// If val was obtained from a struct field, finfo must have its details. +func (p *printer) marshalValue(val reflect.Value, finfo *fieldInfo, startTemplate *StartElement) error { + if startTemplate != nil && startTemplate.Name.Local == "" { + return fmt.Errorf("xml: EncodeElement of StartElement with missing name") + } + + if !val.IsValid() { + return nil + } + if finfo != nil && finfo.flags&fOmitEmpty != 0 && isEmptyValue(val) { + return nil + } + + // Drill into interfaces and pointers. + // This can turn into an infinite loop given a cyclic chain, + // but it matches the Go 1 behavior. + for val.Kind() == reflect.Interface || val.Kind() == reflect.Ptr { + if val.IsNil() { + return nil + } + val = val.Elem() + } + + kind := val.Kind() + typ := val.Type() + + // Check for marshaler. + if val.CanInterface() && typ.Implements(marshalerType) { + return p.marshalInterface(val.Interface().(Marshaler), defaultStart(typ, finfo, startTemplate)) + } + if val.CanAddr() { + pv := val.Addr() + if pv.CanInterface() && pv.Type().Implements(marshalerType) { + return p.marshalInterface(pv.Interface().(Marshaler), defaultStart(pv.Type(), finfo, startTemplate)) + } + } + + // Check for text marshaler. + if val.CanInterface() && typ.Implements(textMarshalerType) { + return p.marshalTextInterface(val.Interface().(encoding.TextMarshaler), defaultStart(typ, finfo, startTemplate)) + } + if val.CanAddr() { + pv := val.Addr() + if pv.CanInterface() && pv.Type().Implements(textMarshalerType) { + return p.marshalTextInterface(pv.Interface().(encoding.TextMarshaler), defaultStart(pv.Type(), finfo, startTemplate)) + } + } + + // Slices and arrays iterate over the elements. They do not have an enclosing tag. + if (kind == reflect.Slice || kind == reflect.Array) && typ.Elem().Kind() != reflect.Uint8 { + for i, n := 0, val.Len(); i < n; i++ { + if err := p.marshalValue(val.Index(i), finfo, startTemplate); err != nil { + return err + } + } + return nil + } + + tinfo, err := getTypeInfo(typ) + if err != nil { + return err + } + + // Create start element. + // Precedence for the XML element name is: + // 0. startTemplate + // 1. XMLName field in underlying struct; + // 2. field name/tag in the struct field; and + // 3. type name + var start StartElement + + if startTemplate != nil { + start.Name = startTemplate.Name + start.Attr = append(start.Attr, startTemplate.Attr...) + } else if tinfo.xmlname != nil { + xmlname := tinfo.xmlname + if xmlname.name != "" { + start.Name.Space, start.Name.Local = xmlname.xmlns, xmlname.name + } else if v, ok := xmlname.value(val).Interface().(Name); ok && v.Local != "" { + start.Name = v + } + } + if start.Name.Local == "" && finfo != nil { + start.Name.Space, start.Name.Local = finfo.xmlns, finfo.name + } + if start.Name.Local == "" { + name := typ.Name() + if name == "" { + return &UnsupportedTypeError{typ} + } + start.Name.Local = name + } + + // Attributes + for i := range tinfo.fields { + finfo := &tinfo.fields[i] + if finfo.flags&fAttr == 0 { + continue + } + fv := finfo.value(val) + + if finfo.flags&fOmitEmpty != 0 && isEmptyValue(fv) { + continue + } + + if fv.Kind() == reflect.Interface && fv.IsNil() { + continue + } + + name := Name{Space: finfo.xmlns, Local: finfo.name} + if err := p.marshalAttr(&start, name, fv); err != nil { + return err + } + } + + if err := p.writeStart(&start); err != nil { + return err + } + + if val.Kind() == reflect.Struct { + err = p.marshalStruct(tinfo, val) + } else { + s, b, err1 := p.marshalSimple(typ, val) + if err1 != nil { + err = err1 + } else if b != nil { + EscapeText(p, b) + } else { + p.EscapeString(s) + } + } + if err != nil { + return err + } + + if err := p.writeEnd(start.Name); err != nil { + return err + } + + return p.cachedWriteError() +} + +// marshalAttr marshals an attribute with the given name and value, adding to start.Attr. +func (p *printer) marshalAttr(start *StartElement, name Name, val reflect.Value) error { + if val.CanInterface() && val.Type().Implements(marshalerAttrType) { + attr, err := val.Interface().(MarshalerAttr).MarshalXMLAttr(name) + if err != nil { + return err + } + if attr.Name.Local != "" { + start.Attr = append(start.Attr, attr) + } + return nil + } + + if val.CanAddr() { + pv := val.Addr() + if pv.CanInterface() && pv.Type().Implements(marshalerAttrType) { + attr, err := pv.Interface().(MarshalerAttr).MarshalXMLAttr(name) + if err != nil { + return err + } + if attr.Name.Local != "" { + start.Attr = append(start.Attr, attr) + } + return nil + } + } + + if val.CanInterface() && val.Type().Implements(textMarshalerType) { + text, err := val.Interface().(encoding.TextMarshaler).MarshalText() + if err != nil { + return err + } + start.Attr = append(start.Attr, Attr{name, string(text)}) + return nil + } + + if val.CanAddr() { + pv := val.Addr() + if pv.CanInterface() && pv.Type().Implements(textMarshalerType) { + text, err := pv.Interface().(encoding.TextMarshaler).MarshalText() + if err != nil { + return err + } + start.Attr = append(start.Attr, Attr{name, string(text)}) + return nil + } + } + + // Dereference or skip nil pointer, interface values. + switch val.Kind() { + case reflect.Ptr, reflect.Interface: + if val.IsNil() { + return nil + } + val = val.Elem() + } + + // Walk slices. + if val.Kind() == reflect.Slice && val.Type().Elem().Kind() != reflect.Uint8 { + n := val.Len() + for i := 0; i < n; i++ { + if err := p.marshalAttr(start, name, val.Index(i)); err != nil { + return err + } + } + return nil + } + + if val.Type() == attrType { + start.Attr = append(start.Attr, val.Interface().(Attr)) + return nil + } + + s, b, err := p.marshalSimple(val.Type(), val) + if err != nil { + return err + } + if b != nil { + s = string(b) + } + start.Attr = append(start.Attr, Attr{name, s}) + return nil +} + +// defaultStart returns the default start element to use, +// given the reflect type, field info, and start template. +func defaultStart(typ reflect.Type, finfo *fieldInfo, startTemplate *StartElement) StartElement { + var start StartElement + // Precedence for the XML element name is as above, + // except that we do not look inside structs for the first field. + if startTemplate != nil { + start.Name = startTemplate.Name + start.Attr = append(start.Attr, startTemplate.Attr...) + } else if finfo != nil && finfo.name != "" { + start.Name.Local = finfo.name + start.Name.Space = finfo.xmlns + } else if typ.Name() != "" { + start.Name.Local = typ.Name() + } else { + // Must be a pointer to a named type, + // since it has the Marshaler methods. + start.Name.Local = typ.Elem().Name() + } + return start +} + +// marshalInterface marshals a Marshaler interface value. +func (p *printer) marshalInterface(val Marshaler, start StartElement) error { + // Push a marker onto the tag stack so that MarshalXML + // cannot close the XML tags that it did not open. + p.tags = append(p.tags, Name{}) + n := len(p.tags) + + err := val.MarshalXML(p.encoder, start) + if err != nil { + return err + } + + // Make sure MarshalXML closed all its tags. p.tags[n-1] is the mark. + if len(p.tags) > n { + return fmt.Errorf("xml: %s.MarshalXML wrote invalid XML: <%s> not closed", receiverType(val), p.tags[len(p.tags)-1].Local) + } + p.tags = p.tags[:n-1] + return nil +} + +// marshalTextInterface marshals a TextMarshaler interface value. +func (p *printer) marshalTextInterface(val encoding.TextMarshaler, start StartElement) error { + if err := p.writeStart(&start); err != nil { + return err + } + text, err := val.MarshalText() + if err != nil { + return err + } + EscapeText(p, text) + return p.writeEnd(start.Name) +} + +// writeStart writes the given start element. +func (p *printer) writeStart(start *StartElement) error { + if start.Name.Local == "" { + return fmt.Errorf("xml: start tag with no name") + } + + p.tags = append(p.tags, start.Name) + p.markPrefix() + + p.writeIndent(1) + p.WriteByte('<') + p.WriteString(start.Name.Local) + + if start.Name.Space != "" { + p.WriteString(` xmlns="`) + p.EscapeString(start.Name.Space) + p.WriteByte('"') + } + + // Attributes + for _, attr := range start.Attr { + name := attr.Name + if name.Local == "" { + continue + } + p.WriteByte(' ') + if name.Space != "" { + p.WriteString(p.createAttrPrefix(name.Space)) + p.WriteByte(':') + } + p.WriteString(name.Local) + p.WriteString(`="`) + p.EscapeString(attr.Value) + p.WriteByte('"') + } + p.WriteByte('>') + return nil +} + +func (p *printer) writeEnd(name Name) error { + if name.Local == "" { + return fmt.Errorf("xml: end tag with no name") + } + if len(p.tags) == 0 || p.tags[len(p.tags)-1].Local == "" { + return fmt.Errorf("xml: end tag without start tag", name.Local) + } + if top := p.tags[len(p.tags)-1]; top != name { + if top.Local != name.Local { + return fmt.Errorf("xml: end tag does not match start tag <%s>", name.Local, top.Local) + } + return fmt.Errorf("xml: end tag in namespace %s does not match start tag <%s> in namespace %s", name.Local, name.Space, top.Local, top.Space) + } + p.tags = p.tags[:len(p.tags)-1] + if sortedContains(p.disableAutoClose, name.Local) { + p.popPrefix() + p.writeIndent(-2) + return nil + } + + p.writeIndent(-1) + p.WriteByte('<') + p.WriteByte('/') + p.WriteString(name.Local) + p.WriteByte('>') + p.popPrefix() + return nil +} + +func (p *printer) marshalSimple(typ reflect.Type, val reflect.Value) (string, []byte, error) { + switch val.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return strconv.FormatInt(val.Int(), 10), nil, nil + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return strconv.FormatUint(val.Uint(), 10), nil, nil + case reflect.Float32, reflect.Float64: + return strconv.FormatFloat(val.Float(), 'g', -1, val.Type().Bits()), nil, nil + case reflect.String: + return val.String(), nil, nil + case reflect.Bool: + return strconv.FormatBool(val.Bool()), nil, nil + case reflect.Array: + if typ.Elem().Kind() != reflect.Uint8 { + break + } + // [...]byte + var bytes []byte + if val.CanAddr() { + bytes = val.Slice(0, val.Len()).Bytes() + } else { + bytes = make([]byte, val.Len()) + reflect.Copy(reflect.ValueOf(bytes), val) + } + return "", bytes, nil + case reflect.Slice: + if typ.Elem().Kind() != reflect.Uint8 { + break + } + // []byte + return "", val.Bytes(), nil + } + return "", nil, &UnsupportedTypeError{typ} +} + +var ddBytes = []byte("--") + +// indirect drills into interfaces and pointers, returning the pointed-at value. +// If it encounters a nil interface or pointer, indirect returns that nil value. +// This can turn into an infinite loop given a cyclic chain, +// but it matches the Go 1 behavior. +func indirect(vf reflect.Value) reflect.Value { + for vf.Kind() == reflect.Interface || vf.Kind() == reflect.Ptr { + if vf.IsNil() { + return vf + } + vf = vf.Elem() + } + return vf +} + +func (p *printer) marshalStruct(tinfo *typeInfo, val reflect.Value) error { + s := parentStack{p: p} + for i := range tinfo.fields { + finfo := &tinfo.fields[i] + if finfo.flags&fAttr != 0 { + continue + } + vf := finfo.value(val) + + switch finfo.flags & fMode { + case fCDATA, fCharData: + emit := EscapeText + if finfo.flags&fMode == fCDATA { + emit = emitCDATA + } + if err := s.trim(finfo.parents); err != nil { + return err + } + if vf.CanInterface() && vf.Type().Implements(textMarshalerType) { + data, err := vf.Interface().(encoding.TextMarshaler).MarshalText() + if err != nil { + return err + } + if err := emit(p, data); err != nil { + return err + } + continue + } + if vf.CanAddr() { + pv := vf.Addr() + if pv.CanInterface() && pv.Type().Implements(textMarshalerType) { + data, err := pv.Interface().(encoding.TextMarshaler).MarshalText() + if err != nil { + return err + } + if err := emit(p, data); err != nil { + return err + } + continue + } + } + + var scratch [64]byte + vf = indirect(vf) + switch vf.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + if err := emit(p, strconv.AppendInt(scratch[:0], vf.Int(), 10)); err != nil { + return err + } + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + if err := emit(p, strconv.AppendUint(scratch[:0], vf.Uint(), 10)); err != nil { + return err + } + case reflect.Float32, reflect.Float64: + if err := emit(p, strconv.AppendFloat(scratch[:0], vf.Float(), 'g', -1, vf.Type().Bits())); err != nil { + return err + } + case reflect.Bool: + if err := emit(p, strconv.AppendBool(scratch[:0], vf.Bool())); err != nil { + return err + } + case reflect.String: + if err := emit(p, []byte(vf.String())); err != nil { + return err + } + case reflect.Slice: + if elem, ok := vf.Interface().([]byte); ok { + if err := emit(p, elem); err != nil { + return err + } + } + } + continue + + case fComment: + if err := s.trim(finfo.parents); err != nil { + return err + } + vf = indirect(vf) + k := vf.Kind() + if !(k == reflect.String || k == reflect.Slice && vf.Type().Elem().Kind() == reflect.Uint8) { + return fmt.Errorf("xml: bad type for comment field of %s", val.Type()) + } + if vf.Len() == 0 { + continue + } + p.writeIndent(0) + p.WriteString("" is invalid grammar. Make it "- -->" + p.WriteByte(' ') + } + p.WriteString("-->") + continue + + case fInnerXml: + vf = indirect(vf) + iface := vf.Interface() + switch raw := iface.(type) { + case []byte: + p.Write(raw) + continue + case string: + p.WriteString(raw) + continue + } + + case fElement, fElement | fAny: + if err := s.trim(finfo.parents); err != nil { + return err + } + if len(finfo.parents) > len(s.stack) { + if vf.Kind() != reflect.Ptr && vf.Kind() != reflect.Interface || !vf.IsNil() { + if err := s.push(finfo.parents[len(s.stack):]); err != nil { + return err + } + } + } + } + if err := p.marshalValue(vf, finfo, nil); err != nil { + return err + } + } + s.trim(nil) + return p.cachedWriteError() +} + +// return the bufio Writer's cached write error +func (p *printer) cachedWriteError() error { + _, err := p.Write(nil) + return err +} + +func (p *printer) writeIndent(depthDelta int) { + if !p.doindent { + return + } + if depthDelta < 0 { + p.depth-- + if p.indentedIn { + p.indentedIn = false + return + } + p.indentedIn = false + } + if p.putNewline { + if p.carriageReturn { + p.WriteByte('\r') + } + p.WriteByte('\n') + } else { + p.putNewline = true + } + if len(p.prefix) > 0 { + p.WriteString(p.prefix) + } + if len(p.indent) > 0 { + for i := 0; i < p.depth; i++ { + p.WriteString(p.indent) + } + } + if depthDelta > 0 { + p.depth++ + p.indentedIn = true + } +} + +type parentStack struct { + p *printer + stack []string +} + +// trim updates the XML context to match the longest common prefix of the stack +// and the given parents. A closing tag will be written for every parent +// popped. Passing a zero slice or nil will close all the elements. +func (s *parentStack) trim(parents []string) error { + split := 0 + for ; split < len(parents) && split < len(s.stack); split++ { + if parents[split] != s.stack[split] { + break + } + } + for i := len(s.stack) - 1; i >= split; i-- { + if err := s.p.writeEnd(Name{Local: s.stack[i]}); err != nil { + return err + } + } + s.stack = s.stack[:split] + return nil +} + +// push adds parent elements to the stack and writes open tags. +func (s *parentStack) push(parents []string) error { + for i := 0; i < len(parents); i++ { + if err := s.p.writeStart(&StartElement{Name: Name{Local: parents[i]}}); err != nil { + return err + } + } + s.stack = append(s.stack, parents...) + return nil +} + +// UnsupportedTypeError is returned when Marshal encounters a type +// that cannot be converted into XML. +type UnsupportedTypeError struct { + Type reflect.Type +} + +func (e *UnsupportedTypeError) Error() string { + return "xml: unsupported type: " + e.Type.String() +} + +func isEmptyValue(v reflect.Value) bool { + switch v.Kind() { + case reflect.Array, reflect.Map, reflect.Slice, reflect.String: + return v.Len() == 0 + case reflect.Bool: + return !v.Bool() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return v.Int() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return v.Uint() == 0 + case reflect.Float32, reflect.Float64: + return v.Float() == 0 + case reflect.Interface, reflect.Ptr: + return v.IsNil() + } + return false +} + +func sortedContains(haystack []string, needle string) bool { + i := sort.SearchStrings(haystack, needle) + return i < len(haystack) && haystack[i] == needle +} diff --git a/vendor/github.com/aclindsa/xml/read.go b/vendor/github.com/aclindsa/xml/read.go new file mode 100644 index 0000000..2fd6e06 --- /dev/null +++ b/vendor/github.com/aclindsa/xml/read.go @@ -0,0 +1,749 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package xml + +import ( + "bytes" + "encoding" + "errors" + "fmt" + "reflect" + "strconv" + "strings" +) + +// BUG(rsc): Mapping between XML elements and data structures is inherently flawed: +// an XML element is an order-dependent collection of anonymous +// values, while a data structure is an order-independent collection +// of named values. +// See package json for a textual representation more suitable +// to data structures. + +// Unmarshal parses the XML-encoded data and stores the result in +// the value pointed to by v, which must be an arbitrary struct, +// slice, or string. Well-formed data that does not fit into v is +// discarded. +// +// Because Unmarshal uses the reflect package, it can only assign +// to exported (upper case) fields. Unmarshal uses a case-sensitive +// comparison to match XML element names to tag values and struct +// field names. +// +// Unmarshal maps an XML element to a struct using the following rules. +// In the rules, the tag of a field refers to the value associated with the +// key 'xml' in the struct field's tag (see the example above). +// +// * If the struct has a field of type []byte or string with tag +// ",innerxml", Unmarshal accumulates the raw XML nested inside the +// element in that field. The rest of the rules still apply. +// +// * If the struct has a field named XMLName of type Name, +// Unmarshal records the element name in that field. +// +// * If the XMLName field has an associated tag of the form +// "name" or "namespace-URL name", the XML element must have +// the given name (and, optionally, name space) or else Unmarshal +// returns an error. +// +// * If the XML element has an attribute whose name matches a +// struct field name with an associated tag containing ",attr" or +// the explicit name in a struct field tag of the form "name,attr", +// Unmarshal records the attribute value in that field. +// +// * If the XML element has an attribute not handled by the previous +// rule and the struct has a field with an associated tag containing +// ",any,attr", Unmarshal records the attribute value in the first +// such field. +// +// * If the XML element contains character data, that data is +// accumulated in the first struct field that has tag ",chardata". +// The struct field may have type []byte or string. +// If there is no such field, the character data is discarded. +// +// * If the XML element contains comments, they are accumulated in +// the first struct field that has tag ",comment". The struct +// field may have type []byte or string. If there is no such +// field, the comments are discarded. +// +// * If the XML element contains a sub-element whose name matches +// the prefix of a tag formatted as "a" or "a>b>c", unmarshal +// will descend into the XML structure looking for elements with the +// given names, and will map the innermost elements to that struct +// field. A tag starting with ">" is equivalent to one starting +// with the field name followed by ">". +// +// * If the XML element contains a sub-element whose name matches +// a struct field's XMLName tag and the struct field has no +// explicit name tag as per the previous rule, unmarshal maps +// the sub-element to that struct field. +// +// * If the XML element contains a sub-element whose name matches a +// field without any mode flags (",attr", ",chardata", etc), Unmarshal +// maps the sub-element to that struct field. +// +// * If the XML element contains a sub-element that hasn't matched any +// of the above rules and the struct has a field with tag ",any", +// unmarshal maps the sub-element to that struct field. +// +// * An anonymous struct field is handled as if the fields of its +// value were part of the outer struct. +// +// * A struct field with tag "-" is never unmarshaled into. +// +// Unmarshal maps an XML element to a string or []byte by saving the +// concatenation of that element's character data in the string or +// []byte. The saved []byte is never nil. +// +// Unmarshal maps an attribute value to a string or []byte by saving +// the value in the string or slice. +// +// Unmarshal maps an attribute value to an Attr by saving the attribute, +// including its name, in the Attr. +// +// Unmarshal maps an XML element or attribute value to a slice by +// extending the length of the slice and mapping the element or attribute +// to the newly created value. +// +// Unmarshal maps an XML element or attribute value to a bool by +// setting it to the boolean value represented by the string. +// +// Unmarshal maps an XML element or attribute value to an integer or +// floating-point field by setting the field to the result of +// interpreting the string value in decimal. There is no check for +// overflow. +// +// Unmarshal maps an XML element to a Name by recording the element +// name. +// +// Unmarshal maps an XML element to a pointer by setting the pointer +// to a freshly allocated value and then mapping the element to that value. +// +// A missing element or empty attribute value will be unmarshaled as a zero value. +// If the field is a slice, a zero value will be appended to the field. Otherwise, the +// field will be set to its zero value. +func Unmarshal(data []byte, v interface{}) error { + return NewDecoder(bytes.NewReader(data)).Decode(v) +} + +// Decode works like Unmarshal, except it reads the decoder +// stream to find the start element. +func (d *Decoder) Decode(v interface{}) error { + return d.DecodeElement(v, nil) +} + +// DecodeElement works like Unmarshal except that it takes +// a pointer to the start XML element to decode into v. +// It is useful when a client reads some raw XML tokens itself +// but also wants to defer to Unmarshal for some elements. +func (d *Decoder) DecodeElement(v interface{}, start *StartElement) error { + val := reflect.ValueOf(v) + if val.Kind() != reflect.Ptr { + return errors.New("non-pointer passed to Unmarshal") + } + return d.unmarshal(val.Elem(), start) +} + +// An UnmarshalError represents an error in the unmarshaling process. +type UnmarshalError string + +func (e UnmarshalError) Error() string { return string(e) } + +// Unmarshaler is the interface implemented by objects that can unmarshal +// an XML element description of themselves. +// +// UnmarshalXML decodes a single XML element +// beginning with the given start element. +// If it returns an error, the outer call to Unmarshal stops and +// returns that error. +// UnmarshalXML must consume exactly one XML element. +// One common implementation strategy is to unmarshal into +// a separate value with a layout matching the expected XML +// using d.DecodeElement, and then to copy the data from +// that value into the receiver. +// Another common strategy is to use d.Token to process the +// XML object one token at a time. +// UnmarshalXML may not use d.RawToken. +type Unmarshaler interface { + UnmarshalXML(d *Decoder, start StartElement) error +} + +// UnmarshalerAttr is the interface implemented by objects that can unmarshal +// an XML attribute description of themselves. +// +// UnmarshalXMLAttr decodes a single XML attribute. +// If it returns an error, the outer call to Unmarshal stops and +// returns that error. +// UnmarshalXMLAttr is used only for struct fields with the +// "attr" option in the field tag. +type UnmarshalerAttr interface { + UnmarshalXMLAttr(attr Attr) error +} + +// receiverType returns the receiver type to use in an expression like "%s.MethodName". +func receiverType(val interface{}) string { + t := reflect.TypeOf(val) + if t.Name() != "" { + return t.String() + } + return "(" + t.String() + ")" +} + +// unmarshalInterface unmarshals a single XML element into val. +// start is the opening tag of the element. +func (d *Decoder) unmarshalInterface(val Unmarshaler, start *StartElement) error { + // Record that decoder must stop at end tag corresponding to start. + d.pushEOF() + + d.unmarshalDepth++ + err := val.UnmarshalXML(d, *start) + d.unmarshalDepth-- + if err != nil { + d.popEOF() + return err + } + + if !d.popEOF() { + return fmt.Errorf("xml: %s.UnmarshalXML did not consume entire <%s> element", receiverType(val), start.Name.Local) + } + + return nil +} + +// unmarshalTextInterface unmarshals a single XML element into val. +// The chardata contained in the element (but not its children) +// is passed to the text unmarshaler. +func (d *Decoder) unmarshalTextInterface(val encoding.TextUnmarshaler) error { + var buf []byte + depth := 1 + for depth > 0 { + t, err := d.Token() + if err != nil { + return err + } + switch t := t.(type) { + case CharData: + if depth == 1 { + buf = append(buf, t...) + } + case StartElement: + depth++ + case EndElement: + depth-- + } + } + return val.UnmarshalText(buf) +} + +// unmarshalAttr unmarshals a single XML attribute into val. +func (d *Decoder) unmarshalAttr(val reflect.Value, attr Attr) error { + if val.Kind() == reflect.Ptr { + if val.IsNil() { + val.Set(reflect.New(val.Type().Elem())) + } + val = val.Elem() + } + if val.CanInterface() && val.Type().Implements(unmarshalerAttrType) { + // This is an unmarshaler with a non-pointer receiver, + // so it's likely to be incorrect, but we do what we're told. + return val.Interface().(UnmarshalerAttr).UnmarshalXMLAttr(attr) + } + if val.CanAddr() { + pv := val.Addr() + if pv.CanInterface() && pv.Type().Implements(unmarshalerAttrType) { + return pv.Interface().(UnmarshalerAttr).UnmarshalXMLAttr(attr) + } + } + + // Not an UnmarshalerAttr; try encoding.TextUnmarshaler. + if val.CanInterface() && val.Type().Implements(textUnmarshalerType) { + // This is an unmarshaler with a non-pointer receiver, + // so it's likely to be incorrect, but we do what we're told. + return val.Interface().(encoding.TextUnmarshaler).UnmarshalText([]byte(attr.Value)) + } + if val.CanAddr() { + pv := val.Addr() + if pv.CanInterface() && pv.Type().Implements(textUnmarshalerType) { + return pv.Interface().(encoding.TextUnmarshaler).UnmarshalText([]byte(attr.Value)) + } + } + + if val.Type().Kind() == reflect.Slice && val.Type().Elem().Kind() != reflect.Uint8 { + // Slice of element values. + // Grow slice. + n := val.Len() + val.Set(reflect.Append(val, reflect.Zero(val.Type().Elem()))) + + // Recur to read element into slice. + if err := d.unmarshalAttr(val.Index(n), attr); err != nil { + val.SetLen(n) + return err + } + return nil + } + + if val.Type() == attrType { + val.Set(reflect.ValueOf(attr)) + return nil + } + + return copyValue(val, []byte(attr.Value)) +} + +var ( + attrType = reflect.TypeOf(Attr{}) + unmarshalerType = reflect.TypeOf((*Unmarshaler)(nil)).Elem() + unmarshalerAttrType = reflect.TypeOf((*UnmarshalerAttr)(nil)).Elem() + textUnmarshalerType = reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem() +) + +// Unmarshal a single XML element into val. +func (d *Decoder) unmarshal(val reflect.Value, start *StartElement) error { + // Find start element if we need it. + if start == nil { + for { + tok, err := d.Token() + if err != nil { + return err + } + if t, ok := tok.(StartElement); ok { + start = &t + break + } + } + } + + // Load value from interface, but only if the result will be + // usefully addressable. + if val.Kind() == reflect.Interface && !val.IsNil() { + e := val.Elem() + if e.Kind() == reflect.Ptr && !e.IsNil() { + val = e + } + } + + if val.Kind() == reflect.Ptr { + if val.IsNil() { + val.Set(reflect.New(val.Type().Elem())) + } + val = val.Elem() + } + + if val.CanInterface() && val.Type().Implements(unmarshalerType) { + // This is an unmarshaler with a non-pointer receiver, + // so it's likely to be incorrect, but we do what we're told. + return d.unmarshalInterface(val.Interface().(Unmarshaler), start) + } + + if val.CanAddr() { + pv := val.Addr() + if pv.CanInterface() && pv.Type().Implements(unmarshalerType) { + return d.unmarshalInterface(pv.Interface().(Unmarshaler), start) + } + } + + if val.CanInterface() && val.Type().Implements(textUnmarshalerType) { + return d.unmarshalTextInterface(val.Interface().(encoding.TextUnmarshaler)) + } + + if val.CanAddr() { + pv := val.Addr() + if pv.CanInterface() && pv.Type().Implements(textUnmarshalerType) { + return d.unmarshalTextInterface(pv.Interface().(encoding.TextUnmarshaler)) + } + } + + var ( + data []byte + saveData reflect.Value + comment []byte + saveComment reflect.Value + saveXML reflect.Value + saveXMLIndex int + saveXMLData []byte + saveAny reflect.Value + sv reflect.Value + tinfo *typeInfo + err error + ) + + switch v := val; v.Kind() { + default: + return errors.New("unknown type " + v.Type().String()) + + case reflect.Interface: + // TODO: For now, simply ignore the field. In the near + // future we may choose to unmarshal the start + // element on it, if not nil. + return d.Skip() + + case reflect.Slice: + typ := v.Type() + if typ.Elem().Kind() == reflect.Uint8 { + // []byte + saveData = v + break + } + + // Slice of element values. + // Grow slice. + n := v.Len() + v.Set(reflect.Append(val, reflect.Zero(v.Type().Elem()))) + + // Recur to read element into slice. + if err := d.unmarshal(v.Index(n), start); err != nil { + v.SetLen(n) + return err + } + return nil + + case reflect.Bool, reflect.Float32, reflect.Float64, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr, reflect.String: + saveData = v + + case reflect.Struct: + typ := v.Type() + if typ == nameType { + v.Set(reflect.ValueOf(start.Name)) + break + } + + sv = v + tinfo, err = getTypeInfo(typ) + if err != nil { + return err + } + + // Validate and assign element name. + if tinfo.xmlname != nil { + finfo := tinfo.xmlname + if finfo.name != "" && finfo.name != start.Name.Local { + return UnmarshalError("expected element type <" + finfo.name + "> but have <" + start.Name.Local + ">") + } + if finfo.xmlns != "" && finfo.xmlns != start.Name.Space { + e := "expected element <" + finfo.name + "> in name space " + finfo.xmlns + " but have " + if start.Name.Space == "" { + e += "no name space" + } else { + e += start.Name.Space + } + return UnmarshalError(e) + } + fv := finfo.value(sv) + if _, ok := fv.Interface().(Name); ok { + fv.Set(reflect.ValueOf(start.Name)) + } + } + + // Assign attributes. + for _, a := range start.Attr { + handled := false + any := -1 + for i := range tinfo.fields { + finfo := &tinfo.fields[i] + switch finfo.flags & fMode { + case fAttr: + strv := finfo.value(sv) + if a.Name.Local == finfo.name && (finfo.xmlns == "" || finfo.xmlns == a.Name.Space) { + if err := d.unmarshalAttr(strv, a); err != nil { + return err + } + handled = true + } + + case fAny | fAttr: + if any == -1 { + any = i + } + } + } + if !handled && any >= 0 { + finfo := &tinfo.fields[any] + strv := finfo.value(sv) + if err := d.unmarshalAttr(strv, a); err != nil { + return err + } + } + } + + // Determine whether we need to save character data or comments. + for i := range tinfo.fields { + finfo := &tinfo.fields[i] + switch finfo.flags & fMode { + case fCDATA, fCharData: + if !saveData.IsValid() { + saveData = finfo.value(sv) + } + + case fComment: + if !saveComment.IsValid() { + saveComment = finfo.value(sv) + } + + case fAny, fAny | fElement: + if !saveAny.IsValid() { + saveAny = finfo.value(sv) + } + + case fInnerXml: + if !saveXML.IsValid() { + saveXML = finfo.value(sv) + if d.saved == nil { + saveXMLIndex = 0 + d.saved = new(bytes.Buffer) + } else { + saveXMLIndex = d.savedOffset() + } + } + } + } + } + + // Find end element. + // Process sub-elements along the way. +Loop: + for { + var savedOffset int + if saveXML.IsValid() { + savedOffset = d.savedOffset() + } + tok, err := d.Token() + if err != nil { + return err + } + switch t := tok.(type) { + case StartElement: + consumed := false + if sv.IsValid() { + consumed, err = d.unmarshalPath(tinfo, sv, nil, &t) + if err != nil { + return err + } + if !consumed && saveAny.IsValid() { + consumed = true + if err := d.unmarshal(saveAny, &t); err != nil { + return err + } + } + } + if !consumed { + if err := d.Skip(); err != nil { + return err + } + } + + case EndElement: + if saveXML.IsValid() { + saveXMLData = d.saved.Bytes()[saveXMLIndex:savedOffset] + if saveXMLIndex == 0 { + d.saved = nil + } + } + break Loop + + case CharData: + if saveData.IsValid() { + data = append(data, t...) + } + + case Comment: + if saveComment.IsValid() { + comment = append(comment, t...) + } + } + } + + if saveData.IsValid() && saveData.CanInterface() && saveData.Type().Implements(textUnmarshalerType) { + if err := saveData.Interface().(encoding.TextUnmarshaler).UnmarshalText(data); err != nil { + return err + } + saveData = reflect.Value{} + } + + if saveData.IsValid() && saveData.CanAddr() { + pv := saveData.Addr() + if pv.CanInterface() && pv.Type().Implements(textUnmarshalerType) { + if err := pv.Interface().(encoding.TextUnmarshaler).UnmarshalText(data); err != nil { + return err + } + saveData = reflect.Value{} + } + } + + if err := copyValue(saveData, data); err != nil { + return err + } + + switch t := saveComment; t.Kind() { + case reflect.String: + t.SetString(string(comment)) + case reflect.Slice: + t.Set(reflect.ValueOf(comment)) + } + + switch t := saveXML; t.Kind() { + case reflect.String: + t.SetString(string(saveXMLData)) + case reflect.Slice: + if t.Type().Elem().Kind() == reflect.Uint8 { + t.Set(reflect.ValueOf(saveXMLData)) + } + } + + return nil +} + +func copyValue(dst reflect.Value, src []byte) (err error) { + dst0 := dst + + if dst.Kind() == reflect.Ptr { + if dst.IsNil() { + dst.Set(reflect.New(dst.Type().Elem())) + } + dst = dst.Elem() + } + + // Save accumulated data. + switch dst.Kind() { + case reflect.Invalid: + // Probably a comment. + default: + return errors.New("cannot unmarshal into " + dst0.Type().String()) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + if len(src) == 0 { + dst.SetInt(0) + return nil + } + itmp, err := strconv.ParseInt(string(src), 10, dst.Type().Bits()) + if err != nil { + return err + } + dst.SetInt(itmp) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + if len(src) == 0 { + dst.SetUint(0) + return nil + } + utmp, err := strconv.ParseUint(string(src), 10, dst.Type().Bits()) + if err != nil { + return err + } + dst.SetUint(utmp) + case reflect.Float32, reflect.Float64: + if len(src) == 0 { + dst.SetFloat(0) + return nil + } + ftmp, err := strconv.ParseFloat(string(src), dst.Type().Bits()) + if err != nil { + return err + } + dst.SetFloat(ftmp) + case reflect.Bool: + if len(src) == 0 { + dst.SetBool(false) + return nil + } + value, err := strconv.ParseBool(strings.TrimSpace(string(src))) + if err != nil { + return err + } + dst.SetBool(value) + case reflect.String: + dst.SetString(string(src)) + case reflect.Slice: + if len(src) == 0 { + // non-nil to flag presence + src = []byte{} + } + dst.SetBytes(src) + } + return nil +} + +// unmarshalPath walks down an XML structure looking for wanted +// paths, and calls unmarshal on them. +// The consumed result tells whether XML elements have been consumed +// from the Decoder until start's matching end element, or if it's +// still untouched because start is uninteresting for sv's fields. +func (d *Decoder) unmarshalPath(tinfo *typeInfo, sv reflect.Value, parents []string, start *StartElement) (consumed bool, err error) { + recurse := false +Loop: + for i := range tinfo.fields { + finfo := &tinfo.fields[i] + if finfo.flags&fElement == 0 || len(finfo.parents) < len(parents) || finfo.xmlns != "" && finfo.xmlns != start.Name.Space { + continue + } + for j := range parents { + if parents[j] != finfo.parents[j] { + continue Loop + } + } + if len(finfo.parents) == len(parents) && finfo.name == start.Name.Local { + // It's a perfect match, unmarshal the field. + return true, d.unmarshal(finfo.value(sv), start) + } + if len(finfo.parents) > len(parents) && finfo.parents[len(parents)] == start.Name.Local { + // It's a prefix for the field. Break and recurse + // since it's not ok for one field path to be itself + // the prefix for another field path. + recurse = true + + // We can reuse the same slice as long as we + // don't try to append to it. + parents = finfo.parents[:len(parents)+1] + break + } + } + if !recurse { + // We have no business with this element. + return false, nil + } + // The element is not a perfect match for any field, but one + // or more fields have the path to this element as a parent + // prefix. Recurse and attempt to match these. + for { + var tok Token + tok, err = d.Token() + if err != nil { + return true, err + } + switch t := tok.(type) { + case StartElement: + consumed2, err := d.unmarshalPath(tinfo, sv, parents, &t) + if err != nil { + return true, err + } + if !consumed2 { + if err := d.Skip(); err != nil { + return true, err + } + } + case EndElement: + return true, nil + } + } +} + +// Skip reads tokens until it has consumed the end element +// matching the most recent start element already consumed. +// It recurs if it encounters a start element, so it can be used to +// skip nested structures. +// It returns nil if it finds an end element matching the start +// element; otherwise it returns an error describing the problem. +func (d *Decoder) Skip() error { + for { + tok, err := d.Token() + if err != nil { + return err + } + switch tok.(type) { + case StartElement: + if err := d.Skip(); err != nil { + return err + } + case EndElement: + return nil + } + } +} diff --git a/vendor/github.com/aclindsa/xml/typeinfo.go b/vendor/github.com/aclindsa/xml/typeinfo.go new file mode 100644 index 0000000..b3346d3 --- /dev/null +++ b/vendor/github.com/aclindsa/xml/typeinfo.go @@ -0,0 +1,364 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package xml + +import ( + "fmt" + "reflect" + "strings" + "sync" +) + +// typeInfo holds details for the xml representation of a type. +type typeInfo struct { + xmlname *fieldInfo + fields []fieldInfo +} + +// fieldInfo holds details for the xml representation of a single field. +type fieldInfo struct { + idx []int + name string + xmlns string + flags fieldFlags + parents []string +} + +type fieldFlags int + +const ( + fElement fieldFlags = 1 << iota + fAttr + fCDATA + fCharData + fInnerXml + fComment + fAny + + fOmitEmpty + + fMode = fElement | fAttr | fCDATA | fCharData | fInnerXml | fComment | fAny + + xmlName = "XMLName" +) + +var tinfoMap sync.Map // map[reflect.Type]*typeInfo + +var nameType = reflect.TypeOf(Name{}) + +// getTypeInfo returns the typeInfo structure with details necessary +// for marshaling and unmarshaling typ. +func getTypeInfo(typ reflect.Type) (*typeInfo, error) { + if ti, ok := tinfoMap.Load(typ); ok { + return ti.(*typeInfo), nil + } + + tinfo := &typeInfo{} + if typ.Kind() == reflect.Struct && typ != nameType { + n := typ.NumField() + for i := 0; i < n; i++ { + f := typ.Field(i) + if (f.PkgPath != "" && !f.Anonymous) || f.Tag.Get("xml") == "-" { + continue // Private field + } + + // For embedded structs, embed its fields. + if f.Anonymous { + t := f.Type + if t.Kind() == reflect.Ptr { + t = t.Elem() + } + if t.Kind() == reflect.Struct { + inner, err := getTypeInfo(t) + if err != nil { + return nil, err + } + if tinfo.xmlname == nil { + tinfo.xmlname = inner.xmlname + } + for _, finfo := range inner.fields { + finfo.idx = append([]int{i}, finfo.idx...) + if err := addFieldInfo(typ, tinfo, &finfo); err != nil { + return nil, err + } + } + continue + } + } + + finfo, err := structFieldInfo(typ, &f) + if err != nil { + return nil, err + } + + if f.Name == xmlName { + tinfo.xmlname = finfo + continue + } + + // Add the field if it doesn't conflict with other fields. + if err := addFieldInfo(typ, tinfo, finfo); err != nil { + return nil, err + } + } + } + + ti, _ := tinfoMap.LoadOrStore(typ, tinfo) + return ti.(*typeInfo), nil +} + +// structFieldInfo builds and returns a fieldInfo for f. +func structFieldInfo(typ reflect.Type, f *reflect.StructField) (*fieldInfo, error) { + finfo := &fieldInfo{idx: f.Index} + + // Split the tag from the xml namespace if necessary. + tag := f.Tag.Get("xml") + if i := strings.IndexByte(tag, ' '); i >= 0 { + finfo.xmlns, tag = tag[:i], tag[i+1:] + } + + // Parse flags. + tokens := strings.Split(tag, ",") + if len(tokens) == 1 { + finfo.flags = fElement + } else { + tag = tokens[0] + for _, flag := range tokens[1:] { + switch flag { + case "attr": + finfo.flags |= fAttr + case "cdata": + finfo.flags |= fCDATA + case "chardata": + finfo.flags |= fCharData + case "innerxml": + finfo.flags |= fInnerXml + case "comment": + finfo.flags |= fComment + case "any": + finfo.flags |= fAny + case "omitempty": + finfo.flags |= fOmitEmpty + } + } + + // Validate the flags used. + valid := true + switch mode := finfo.flags & fMode; mode { + case 0: + finfo.flags |= fElement + case fAttr, fCDATA, fCharData, fInnerXml, fComment, fAny, fAny | fAttr: + if f.Name == xmlName || tag != "" && mode != fAttr { + valid = false + } + default: + // This will also catch multiple modes in a single field. + valid = false + } + if finfo.flags&fMode == fAny { + finfo.flags |= fElement + } + if finfo.flags&fOmitEmpty != 0 && finfo.flags&(fElement|fAttr) == 0 { + valid = false + } + if !valid { + return nil, fmt.Errorf("xml: invalid tag in field %s of type %s: %q", + f.Name, typ, f.Tag.Get("xml")) + } + } + + // Use of xmlns without a name is not allowed. + if finfo.xmlns != "" && tag == "" { + return nil, fmt.Errorf("xml: namespace without name in field %s of type %s: %q", + f.Name, typ, f.Tag.Get("xml")) + } + + if f.Name == xmlName { + // The XMLName field records the XML element name. Don't + // process it as usual because its name should default to + // empty rather than to the field name. + finfo.name = tag + return finfo, nil + } + + if tag == "" { + // If the name part of the tag is completely empty, get + // default from XMLName of underlying struct if feasible, + // or field name otherwise. + if xmlname := lookupXMLName(f.Type); xmlname != nil { + finfo.xmlns, finfo.name = xmlname.xmlns, xmlname.name + } else { + finfo.name = f.Name + } + return finfo, nil + } + + // Prepare field name and parents. + parents := strings.Split(tag, ">") + if parents[0] == "" { + parents[0] = f.Name + } + if parents[len(parents)-1] == "" { + return nil, fmt.Errorf("xml: trailing '>' in field %s of type %s", f.Name, typ) + } + finfo.name = parents[len(parents)-1] + if len(parents) > 1 { + if (finfo.flags & fElement) == 0 { + return nil, fmt.Errorf("xml: %s chain not valid with %s flag", tag, strings.Join(tokens[1:], ",")) + } + finfo.parents = parents[:len(parents)-1] + } + + // If the field type has an XMLName field, the names must match + // so that the behavior of both marshaling and unmarshaling + // is straightforward and unambiguous. + if finfo.flags&fElement != 0 { + ftyp := f.Type + xmlname := lookupXMLName(ftyp) + if xmlname != nil && xmlname.name != finfo.name { + return nil, fmt.Errorf("xml: name %q in tag of %s.%s conflicts with name %q in %s.XMLName", + finfo.name, typ, f.Name, xmlname.name, ftyp) + } + } + return finfo, nil +} + +// lookupXMLName returns the fieldInfo for typ's XMLName field +// in case it exists and has a valid xml field tag, otherwise +// it returns nil. +func lookupXMLName(typ reflect.Type) (xmlname *fieldInfo) { + for typ.Kind() == reflect.Ptr { + typ = typ.Elem() + } + if typ.Kind() != reflect.Struct { + return nil + } + for i, n := 0, typ.NumField(); i < n; i++ { + f := typ.Field(i) + if f.Name != xmlName { + continue + } + finfo, err := structFieldInfo(typ, &f) + if finfo.name != "" && err == nil { + return finfo + } + // Also consider errors as a non-existent field tag + // and let getTypeInfo itself report the error. + break + } + return nil +} + +func min(a, b int) int { + if a <= b { + return a + } + return b +} + +// addFieldInfo adds finfo to tinfo.fields if there are no +// conflicts, or if conflicts arise from previous fields that were +// obtained from deeper embedded structures than finfo. In the latter +// case, the conflicting entries are dropped. +// A conflict occurs when the path (parent + name) to a field is +// itself a prefix of another path, or when two paths match exactly. +// It is okay for field paths to share a common, shorter prefix. +func addFieldInfo(typ reflect.Type, tinfo *typeInfo, newf *fieldInfo) error { + var conflicts []int +Loop: + // First, figure all conflicts. Most working code will have none. + for i := range tinfo.fields { + oldf := &tinfo.fields[i] + if oldf.flags&fMode != newf.flags&fMode { + continue + } + if oldf.xmlns != "" && newf.xmlns != "" && oldf.xmlns != newf.xmlns { + continue + } + minl := min(len(newf.parents), len(oldf.parents)) + for p := 0; p < minl; p++ { + if oldf.parents[p] != newf.parents[p] { + continue Loop + } + } + if len(oldf.parents) > len(newf.parents) { + if oldf.parents[len(newf.parents)] == newf.name { + conflicts = append(conflicts, i) + } + } else if len(oldf.parents) < len(newf.parents) { + if newf.parents[len(oldf.parents)] == oldf.name { + conflicts = append(conflicts, i) + } + } else { + if newf.name == oldf.name { + conflicts = append(conflicts, i) + } + } + } + // Without conflicts, add the new field and return. + if conflicts == nil { + tinfo.fields = append(tinfo.fields, *newf) + return nil + } + + // If any conflict is shallower, ignore the new field. + // This matches the Go field resolution on embedding. + for _, i := range conflicts { + if len(tinfo.fields[i].idx) < len(newf.idx) { + return nil + } + } + + // Otherwise, if any of them is at the same depth level, it's an error. + for _, i := range conflicts { + oldf := &tinfo.fields[i] + if len(oldf.idx) == len(newf.idx) { + f1 := typ.FieldByIndex(oldf.idx) + f2 := typ.FieldByIndex(newf.idx) + return &TagPathError{typ, f1.Name, f1.Tag.Get("xml"), f2.Name, f2.Tag.Get("xml")} + } + } + + // Otherwise, the new field is shallower, and thus takes precedence, + // so drop the conflicting fields from tinfo and append the new one. + for c := len(conflicts) - 1; c >= 0; c-- { + i := conflicts[c] + copy(tinfo.fields[i:], tinfo.fields[i+1:]) + tinfo.fields = tinfo.fields[:len(tinfo.fields)-1] + } + tinfo.fields = append(tinfo.fields, *newf) + return nil +} + +// A TagPathError represents an error in the unmarshaling process +// caused by the use of field tags with conflicting paths. +type TagPathError struct { + Struct reflect.Type + Field1, Tag1 string + Field2, Tag2 string +} + +func (e *TagPathError) Error() string { + return fmt.Sprintf("%s field %q with tag %q conflicts with field %q with tag %q", e.Struct, e.Field1, e.Tag1, e.Field2, e.Tag2) +} + +// value returns v's field value corresponding to finfo. +// It's equivalent to v.FieldByIndex(finfo.idx), but initializes +// and dereferences pointers as necessary. +func (finfo *fieldInfo) value(v reflect.Value) reflect.Value { + for i, x := range finfo.idx { + if i > 0 { + t := v.Type() + if t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct { + if v.IsNil() { + v.Set(reflect.New(v.Type().Elem())) + } + v = v.Elem() + } + } + v = v.Field(x) + } + return v +} diff --git a/vendor/github.com/aclindsa/xml/xml.go b/vendor/github.com/aclindsa/xml/xml.go new file mode 100644 index 0000000..ef14a73 --- /dev/null +++ b/vendor/github.com/aclindsa/xml/xml.go @@ -0,0 +1,2075 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package xml implements a simple XML 1.0 parser that +// understands XML name spaces. +package xml + +// References: +// Annotated XML spec: http://www.xml.com/axml/testaxml.htm +// XML name spaces: http://www.w3.org/TR/REC-xml-names/ + +// TODO(rsc): +// Test error handling. + +import ( + "bufio" + "bytes" + "errors" + "fmt" + "io" + "strconv" + "strings" + "unicode" + "unicode/utf8" +) + +// A SyntaxError represents a syntax error in the XML input stream. +type SyntaxError struct { + Msg string + Line int +} + +func (e *SyntaxError) Error() string { + return "XML syntax error on line " + strconv.Itoa(e.Line) + ": " + e.Msg +} + +// A Name represents an XML name (Local) annotated +// with a name space identifier (Space). +// In tokens returned by Decoder.Token, the Space identifier +// is given as a canonical URL, not the short prefix used +// in the document being parsed. +type Name struct { + Space, Local string +} + +// An Attr represents an attribute in an XML element (Name=Value). +type Attr struct { + Name Name + Value string +} + +// A Token is an interface holding one of the token types: +// StartElement, EndElement, CharData, Comment, ProcInst, or Directive. +type Token interface{} + +// A StartElement represents an XML start element. +type StartElement struct { + Name Name + Attr []Attr +} + +// Copy creates a new copy of StartElement. +func (e StartElement) Copy() StartElement { + attrs := make([]Attr, len(e.Attr)) + copy(attrs, e.Attr) + e.Attr = attrs + return e +} + +// End returns the corresponding XML end element. +func (e StartElement) End() EndElement { + return EndElement{e.Name} +} + +// An EndElement represents an XML end element. +type EndElement struct { + Name Name +} + +// A CharData represents XML character data (raw text), +// in which XML escape sequences have been replaced by +// the characters they represent. +type CharData []byte + +func makeCopy(b []byte) []byte { + b1 := make([]byte, len(b)) + copy(b1, b) + return b1 +} + +// Copy creates a new copy of CharData. +func (c CharData) Copy() CharData { return CharData(makeCopy(c)) } + +// A Comment represents an XML comment of the form . +// The bytes do not include the comment markers. +type Comment []byte + +// Copy creates a new copy of Comment. +func (c Comment) Copy() Comment { return Comment(makeCopy(c)) } + +// A ProcInst represents an XML processing instruction of the form +type ProcInst struct { + Target string + Inst []byte +} + +// Copy creates a new copy of ProcInst. +func (p ProcInst) Copy() ProcInst { + p.Inst = makeCopy(p.Inst) + return p +} + +// A Directive represents an XML directive of the form . +// The bytes do not include the markers. +type Directive []byte + +// Copy creates a new copy of Directive. +func (d Directive) Copy() Directive { return Directive(makeCopy(d)) } + +// CopyToken returns a copy of a Token. +func CopyToken(t Token) Token { + switch v := t.(type) { + case CharData: + return v.Copy() + case Comment: + return v.Copy() + case Directive: + return v.Copy() + case ProcInst: + return v.Copy() + case StartElement: + return v.Copy() + } + return t +} + +// A TokenReader is anything that can decode a stream of XML tokens, including a +// Decoder. +// +// When Token encounters an error or end-of-file condition after successfully +// reading a token, it returns the token. It may return the (non-nil) error from +// the same call or return the error (and a nil token) from a subsequent call. +// An instance of this general case is that a TokenReader returning a non-nil +// token at the end of the token stream may return either io.EOF or a nil error. +// The next Read should return nil, io.EOF. +// +// Implementations of Token are discouraged from returning a nil token with a +// nil error. Callers should treat a return of nil, nil as indicating that +// nothing happened; in particular it does not indicate EOF. +type TokenReader interface { + Token() (Token, error) +} + +// A Decoder represents an XML parser reading a particular input stream. +// The parser assumes that its input is encoded in UTF-8. +type Decoder struct { + // Strict defaults to true, enforcing the requirements + // of the XML specification. + // If set to false, the parser allows input containing common + // mistakes: + // * If an element is missing an end tag, the parser invents + // end tags as necessary to keep the return values from Token + // properly balanced. + // * In attribute values and character data, unknown or malformed + // character entities (sequences beginning with &) are left alone. + // + // Setting: + // + // d.Strict = false; + // d.AutoClose = HTMLAutoClose; + // d.Entity = HTMLEntity + // + // creates a parser that can handle typical HTML. + // + // Strict mode does not enforce the requirements of the XML name spaces TR. + // In particular it does not reject name space tags using undefined prefixes. + // Such tags are recorded with the unknown prefix as the name space URL. + Strict bool + + // When Strict == false, AutoClose indicates a set of elements to + // consider closed immediately after they are opened, regardless + // of whether an end element is present. + AutoClose []string + + // When Strict == false, AutoCloseAfterCharData indicates a set of elements + // to consider closed before any Token other than CharData, regardless of + // whether an end element is present. + AutoCloseAfterCharData []string + + // Entity can be used to map non-standard entity names to string replacements. + // The parser behaves as if these standard mappings are present in the map, + // regardless of the actual map content: + // + // "lt": "<", + // "gt": ">", + // "amp": "&", + // "apos": "'", + // "quot": `"`, + Entity map[string]string + + // CharsetReader, if non-nil, defines a function to generate + // charset-conversion readers, converting from the provided + // non-UTF-8 charset into UTF-8. If CharsetReader is nil or + // returns an error, parsing stops with an error. One of the + // the CharsetReader's result values must be non-nil. + CharsetReader func(charset string, input io.Reader) (io.Reader, error) + + // DefaultSpace sets the default name space used for unadorned tags, + // as if the entire XML stream were wrapped in an element containing + // the attribute xmlns="DefaultSpace". + DefaultSpace string + + r io.ByteReader + t TokenReader + buf bytes.Buffer + saved *bytes.Buffer + stk *stack + free *stack + needClose bool + toClose Name + nextToken Token + nextByte int + ns map[string]string + err error + line int + offset int64 + unmarshalDepth int +} + +// NewDecoder creates a new XML parser reading from r. +// If r does not implement io.ByteReader, NewDecoder will +// do its own buffering. +func NewDecoder(r io.Reader) *Decoder { + d := &Decoder{ + ns: make(map[string]string), + nextByte: -1, + line: 1, + Strict: true, + } + d.switchToReader(r) + return d +} + +// NewTokenDecoder creates a new XML parser using an underlying token stream. +func NewTokenDecoder(t TokenReader) *Decoder { + // Is it already a Decoder? + if d, ok := t.(*Decoder); ok { + return d + } + d := &Decoder{ + ns: make(map[string]string), + t: t, + nextByte: -1, + line: 1, + Strict: true, + } + return d +} + +// Token returns the next XML token in the input stream. +// At the end of the input stream, Token returns nil, io.EOF. +// +// Slices of bytes in the returned token data refer to the +// parser's internal buffer and remain valid only until the next +// call to Token. To acquire a copy of the bytes, call CopyToken +// or the token's Copy method. +// +// Token expands self-closing elements such as
+// into separate start and end elements returned by successive calls. +// +// Token guarantees that the StartElement and EndElement +// tokens it returns are properly nested and matched: +// if Token encounters an unexpected end element +// or EOF before all expected end elements, +// it will return an error. +// +// Token implements XML name spaces as described by +// http://www.w3.org/TR/REC-xml-names/. Each of the +// Name structures contained in the Token has the Space +// set to the URL identifying its name space when known. +// If Token encounters an unrecognized name space prefix, +// it uses the prefix as the Space rather than report an error. +func (d *Decoder) Token() (Token, error) { + if d.t != nil { + return d.t.Token() + } + var t Token + var err error + if d.stk != nil && d.stk.kind == stkEOF { + return nil, io.EOF + } + if d.nextToken != nil { + t = d.nextToken + d.nextToken = nil + } else if t, err = d.rawToken(); err != nil { + if err == io.EOF && d.stk != nil && d.stk.kind != stkEOF { + err = d.syntaxError("unexpected EOF") + } + return t, err + } + + if !d.Strict { + if t1, ok := d.autoClose(t); ok { + d.nextToken = t + t = t1 + } + } + switch t1 := t.(type) { + case StartElement: + // In XML name spaces, the translations listed in the + // attributes apply to the element name and + // to the other attribute names, so process + // the translations first. + for _, a := range t1.Attr { + if a.Name.Space == xmlnsPrefix { + v, ok := d.ns[a.Name.Local] + d.pushNs(a.Name.Local, v, ok) + d.ns[a.Name.Local] = a.Value + } + if a.Name.Space == "" && a.Name.Local == xmlnsPrefix { + // Default space for untagged names + v, ok := d.ns[""] + d.pushNs("", v, ok) + d.ns[""] = a.Value + } + } + + d.translate(&t1.Name, true) + for i := range t1.Attr { + d.translate(&t1.Attr[i].Name, false) + } + d.pushElement(t1.Name) + t = t1 + + case EndElement: + d.translate(&t1.Name, true) + if !d.popElement(&t1) { + return nil, d.err + } + t = t1 + } + return t, err +} + +const ( + xmlURL = "http://www.w3.org/XML/1998/namespace" + xmlnsPrefix = "xmlns" +) + +// Apply name space translation to name n. +// The default name space (for Space=="") +// applies only to element names, not to attribute names. +func (d *Decoder) translate(n *Name, isElementName bool) { + switch { + case n.Space == xmlnsPrefix: + return + case n.Space == "" && !isElementName: + return + case n.Space == xmlNamespacePrefix: + n.Space = xmlURL + case n.Space == "" && n.Local == xmlnsPrefix: + return + } + if v, ok := d.ns[n.Space]; ok { + n.Space = v + } else if n.Space == "" { + n.Space = d.DefaultSpace + } +} + +func (d *Decoder) switchToReader(r io.Reader) { + // Get efficient byte at a time reader. + // Assume that if reader has its own + // ReadByte, it's efficient enough. + // Otherwise, use bufio. + if rb, ok := r.(io.ByteReader); ok { + d.r = rb + } else { + d.r = bufio.NewReader(r) + } +} + +// Parsing state - stack holds old name space translations +// and the current set of open elements. The translations to pop when +// ending a given tag are *below* it on the stack, which is +// more work but forced on us by XML. +type stack struct { + next *stack + kind int + name Name + ok bool +} + +const ( + stkStart = iota + stkNs + stkEOF +) + +func (d *Decoder) push(kind int) *stack { + s := d.free + if s != nil { + d.free = s.next + } else { + s = new(stack) + } + s.next = d.stk + s.kind = kind + d.stk = s + return s +} + +func (d *Decoder) pop() *stack { + s := d.stk + if s != nil { + d.stk = s.next + s.next = d.free + d.free = s + } + return s +} + +// Record that after the current element is finished +// (that element is already pushed on the stack) +// Token should return EOF until popEOF is called. +func (d *Decoder) pushEOF() { + // Walk down stack to find Start. + // It might not be the top, because there might be stkNs + // entries above it. + start := d.stk + for start.kind != stkStart { + start = start.next + } + // The stkNs entries below a start are associated with that + // element too; skip over them. + for start.next != nil && start.next.kind == stkNs { + start = start.next + } + s := d.free + if s != nil { + d.free = s.next + } else { + s = new(stack) + } + s.kind = stkEOF + s.next = start.next + start.next = s +} + +// Undo a pushEOF. +// The element must have been finished, so the EOF should be at the top of the stack. +func (d *Decoder) popEOF() bool { + if d.stk == nil || d.stk.kind != stkEOF { + return false + } + d.pop() + return true +} + +// Record that we are starting an element with the given name. +func (d *Decoder) pushElement(name Name) { + s := d.push(stkStart) + s.name = name +} + +// Record that we are changing the value of ns[local]. +// The old value is url, ok. +func (d *Decoder) pushNs(local string, url string, ok bool) { + s := d.push(stkNs) + s.name.Local = local + s.name.Space = url + s.ok = ok +} + +// Creates a SyntaxError with the current line number. +func (d *Decoder) syntaxError(msg string) error { + return &SyntaxError{Msg: msg, Line: d.line} +} + +// Record that we are ending an element with the given name. +// The name must match the record at the top of the stack, +// which must be a pushElement record. +// After popping the element, apply any undo records from +// the stack to restore the name translations that existed +// before we saw this element. +func (d *Decoder) popElement(t *EndElement) bool { + s := d.pop() + name := t.Name + switch { + case s == nil || s.kind != stkStart: + d.err = d.syntaxError("unexpected end element ") + return false + case s.name.Local != name.Local: + if !d.Strict { + d.needClose = true + d.toClose = t.Name + t.Name = s.name + return true + } + d.err = d.syntaxError("element <" + s.name.Local + "> closed by ") + return false + case s.name.Space != name.Space: + d.err = d.syntaxError("element <" + s.name.Local + "> in space " + s.name.Space + + "closed by in space " + name.Space) + return false + } + + // Pop stack until a Start or EOF is on the top, undoing the + // translations that were associated with the element we just closed. + for d.stk != nil && d.stk.kind != stkStart && d.stk.kind != stkEOF { + s := d.pop() + if s.ok { + d.ns[s.name.Local] = s.name.Space + } else { + delete(d.ns, s.name.Local) + } + } + + return true +} + +// If the top element on the stack is autoclosing and +// t is not the end tag, invent the end tag. +func (d *Decoder) autoClose(t Token) (Token, bool) { + if d.stk == nil || d.stk.kind != stkStart { + return nil, false + } + name := strings.ToLower(d.stk.name.Local) + for _, s := range d.AutoClose { + if strings.ToLower(s) == name { + // This one should be auto closed if t doesn't close it. + et, ok := t.(EndElement) + if !ok || strings.ToLower(et.Name.Local) != name { + return EndElement{d.stk.name}, true + } + break + } + } + if _, ok := t.(CharData); !ok { + for _, s := range d.AutoCloseAfterCharData { + if strings.ToLower(s) == name { + // This one should be auto closed if t doesn't close it. + et, ok := t.(EndElement) + if !ok || strings.ToLower(et.Name.Local) != name { + return EndElement{d.stk.name}, true + } + break + } + } + } + return nil, false +} + +var errRawToken = errors.New("xml: cannot use RawToken from UnmarshalXML method") + +// RawToken is like Token but does not verify that +// start and end elements match and does not translate +// name space prefixes to their corresponding URLs. +func (d *Decoder) RawToken() (Token, error) { + if d.unmarshalDepth > 0 { + return nil, errRawToken + } + return d.rawToken() +} + +func (d *Decoder) rawToken() (Token, error) { + if d.err != nil { + return nil, d.err + } + if d.needClose { + // The last element we read was self-closing and + // we returned just the StartElement half. + // Return the EndElement half now. + d.needClose = false + return EndElement{d.toClose}, nil + } + + b, ok := d.getc() + if !ok { + return nil, d.err + } + + if b != '<' { + // Text section. + d.ungetc(b) + data := d.text(-1, false) + if data == nil { + return nil, d.err + } + return CharData(data), nil + } + + if b, ok = d.mustgetc(); !ok { + return nil, d.err + } + switch b { + case '/': + // ' { + d.err = d.syntaxError("invalid characters between ") + return nil, d.err + } + return EndElement{name}, nil + + case '?': + // ' { + break + } + b0 = b + } + data := d.buf.Bytes() + data = data[0 : len(data)-2] // chop ?> + + if target == "xml" { + content := string(data) + ver := procInst("version", content) + if ver != "" && ver != "1.0" { + d.err = fmt.Errorf("xml: unsupported version %q; only version 1.0 is supported", ver) + return nil, d.err + } + enc := procInst("encoding", content) + if enc != "" && enc != "utf-8" && enc != "UTF-8" && !strings.EqualFold(enc, "utf-8") { + if d.CharsetReader == nil { + d.err = fmt.Errorf("xml: encoding %q declared but Decoder.CharsetReader is nil", enc) + return nil, d.err + } + newr, err := d.CharsetReader(enc, d.r.(io.Reader)) + if err != nil { + d.err = fmt.Errorf("xml: opening charset %q: %v", enc, err) + return nil, d.err + } + if newr == nil { + panic("CharsetReader returned a nil Reader for charset " + enc) + } + d.switchToReader(newr) + } + } + return ProcInst{target, data}, nil + + case '!': + // ' { + d.err = d.syntaxError( + `invalid sequence "--" not allowed in comments`) + return nil, d.err + } + break + } + b0, b1 = b1, b + } + data := d.buf.Bytes() + data = data[0 : len(data)-3] // chop --> + return Comment(data), nil + + case '[': // . + data := d.text(-1, true) + if data == nil { + return nil, d.err + } + return CharData(data), nil + } + + // Probably a directive: , , etc. + // We don't care, but accumulate for caller. Quoted angle + // brackets do not count for nesting. + d.buf.Reset() + d.buf.WriteByte(b) + inquote := uint8(0) + depth := 0 + for { + if b, ok = d.mustgetc(); !ok { + return nil, d.err + } + if inquote == 0 && b == '>' && depth == 0 { + break + } + HandleB: + d.buf.WriteByte(b) + switch { + case b == inquote: + inquote = 0 + + case inquote != 0: + // in quotes, no special action + + case b == '\'' || b == '"': + inquote = b + + case b == '>' && inquote == 0: + depth-- + + case b == '<' && inquote == 0: + // Look for