no more ofx
This commit is contained in:
9
go.mod
9
go.mod
@@ -5,19 +5,12 @@ go 1.23.0
|
|||||||
toolchain go1.24.2
|
toolchain go1.24.2
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/aclindsa/ofxgo v0.1.3
|
|
||||||
github.com/go-echarts/go-echarts/v2 v2.3.1
|
github.com/go-echarts/go-echarts/v2 v2.3.1
|
||||||
github.com/google/uuid v1.6.0
|
github.com/guptarohit/asciigraph v0.7.3
|
||||||
golang.org/x/crypto v0.38.0
|
golang.org/x/crypto v0.38.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
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
|
golang.org/x/sys v0.33.0 // indirect
|
||||||
golang.org/x/term v0.32.0 // indirect
|
golang.org/x/term v0.32.0 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
12
go.sum
12
go.sum
@@ -1,13 +1,7 @@
|
|||||||
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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
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 h1:Yw0HVjVTxpYm48l974dMjRzx8ni2ql0kKi/kawSgxFE=
|
||||||
github.com/go-echarts/go-echarts/v2 v2.3.1/go.mod h1:56YlvzhW/a+du15f3S2qUGNDfKnFOeJSThBIrVFHDtI=
|
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 h1:p05XDDn7cBTWiBqWb30mrwxd6oU0claAjqeytllnsPY=
|
||||||
github.com/guptarohit/asciigraph v0.7.3/go.mod h1:dYl5wwK4gNsnFf9Zp+l06rFiDZ5YtXM6x7SRWZ3KGag=
|
github.com/guptarohit/asciigraph v0.7.3/go.mod h1:dYl5wwK4gNsnFf9Zp+l06rFiDZ5YtXM6x7SRWZ3KGag=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
@@ -16,15 +10,9 @@ github.com/stretchr/testify v1.6.0 h1:jlIyCplCJFULU/01vCkhKuTyc3OorI3bJFuw6obfgh
|
|||||||
github.com/stretchr/testify v1.6.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
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 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
|
||||||
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
|
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 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
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 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=
|
||||||
golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
|
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 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA=
|
||||||
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
package ofx
|
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
////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)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
280
vendor/github.com/aclindsa/ofxgo/LICENSE
generated
vendored
280
vendor/github.com/aclindsa/ofxgo/LICENSE
generated
vendored
@@ -1,280 +0,0 @@
|
|||||||
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
|
|
||||||
132
vendor/github.com/aclindsa/ofxgo/README.md
generated
vendored
132
vendor/github.com/aclindsa/ofxgo/README.md
generated
vendored
@@ -1,132 +0,0 @@
|
|||||||
# OFXGo
|
|
||||||
|
|
||||||
[](https://goreportcard.com/report/github.com/aclindsa/ofxgo)
|
|
||||||
[](https://github.com/aclindsa/ofxgo/actions?query=workflow%3A%22ofxgo+CI+Test%22+branch%3Amaster)
|
|
||||||
[](https://coveralls.io/github/aclindsa/ofxgo?branch=master)
|
|
||||||
[](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).
|
|
||||||
366
vendor/github.com/aclindsa/ofxgo/bank.go
generated
vendored
366
vendor/github.com/aclindsa/ofxgo/bank.go
generated
vendored
@@ -1,366 +0,0 @@
|
|||||||
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 <DTSTART> 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
|
|
||||||
}
|
|
||||||
104
vendor/github.com/aclindsa/ofxgo/basic_client.go
generated
vendored
104
vendor/github.com/aclindsa/ofxgo/basic_client.go
generated
vendored
@@ -1,104 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
||||||
105
vendor/github.com/aclindsa/ofxgo/client.go
generated
vendored
105
vendor/github.com/aclindsa/ofxgo/client.go
generated
vendored
@@ -1,105 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
372
vendor/github.com/aclindsa/ofxgo/common.go
generated
vendored
372
vendor/github.com/aclindsa/ofxgo/common.go
generated
vendored
@@ -1,372 +0,0 @@
|
|||||||
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(`<?xml version="1.0" encoding="UTF-8" standalone="no"?>`)
|
|
||||||
if carriageReturn {
|
|
||||||
b.WriteByte('\r')
|
|
||||||
}
|
|
||||||
b.WriteByte('\n')
|
|
||||||
b.WriteString(`<?OFX OFXHEADER="200" VERSION="` + v.String() + `" SECURITY="NONE" OLDFILEUID="NONE" NEWFILEUID="NONE"?>`)
|
|
||||||
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 <TRNUID> 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 <BRANCHID> value must be provided in the <BANKACCTFROM> aggregate for this country system, but this field is missing."},
|
|
||||||
2026: {"Bank name doesn’t match bank ID", "ERROR", "The value of <BANKNAME> in the <EXTBANKACCTTO> aggregate is inconsistent with the value of <BANKID> in the <BANKACCTTO> 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 <PMTRQ> 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 <MFACHALLENGERQ> in next request."},
|
|
||||||
3001: {"MFA Challenge information is invalid", "ERROR", "User or client information sent in MFACHALLENGEA contains invalid information"},
|
|
||||||
6500: {"<REJECTIFMISSING>Y invalid without <TOKEN>", "ERROR", "This error code may appear in the <SYNCERROR> element of an <xxxSYNCRS> wrapper (in <PRESDLVMSGSRSV1> and V2 message set responses) or the <CODE> contained in any embedded transaction wrappers within a sync response. The corresponding sync request wrapper included <REJECTIFMISSING>Y with <REFRESH>Y or <TOKENONLY>Y, which is illegal."},
|
|
||||||
6501: {"Embedded transactions in request failed to process: Out of date", "WARN", "<REJECTIFMISSING>Y and embedded transactions appeared in the request sync wrapper and the provided <TOKEN> was out of date. This code should be used in the <SYNCERROR> of the response sync wrapper."},
|
|
||||||
6502: {"Unable to process embedded transaction due to out-of-date <TOKEN>", "ERROR", "Used in response transaction wrapper for embedded transactions when <SYNCERROR>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 <STPCHKRQ> 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 <PAYACCT> 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 <FREQ> 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 <SVC> specified in the service-activation request."},
|
|
||||||
13503: {"Cannot change user information", "ERROR", "The server does not support the <CHGUSERINFORQ> request."},
|
|
||||||
13504: {"<FI> Missing or Invalid in <SONRQ>", "ERROR", "The FI requires the client to provide the <FI> aggregate in the <SONRQ> 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 <USERPASS> 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 <PINCHRQ> request."},
|
|
||||||
15504: {"Could not provide random data", "ERROR", "The server could not generate random data as requested by the <CHALLENGERQ>."},
|
|
||||||
15505: {"Country system not supported", "ERROR", "The server does not support the country specified in the <COUNTRY> field of the <SONRQ> 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 <USERID>."},
|
|
||||||
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
|
|
||||||
}
|
|
||||||
2703
vendor/github.com/aclindsa/ofxgo/constants.go
generated
vendored
2703
vendor/github.com/aclindsa/ofxgo/constants.go
generated
vendored
File diff suppressed because it is too large
Load Diff
91
vendor/github.com/aclindsa/ofxgo/creditcard.go
generated
vendored
91
vendor/github.com/aclindsa/ofxgo/creditcard.go
generated
vendored
@@ -1,91 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
109
vendor/github.com/aclindsa/ofxgo/discovercard_client.go
generated
vendored
109
vendor/github.com/aclindsa/ofxgo/discovercard_client.go
generated
vendored
@@ -1,109 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
||||||
127
vendor/github.com/aclindsa/ofxgo/doc.go
generated
vendored
127
vendor/github.com/aclindsa/ofxgo/doc.go
generated
vendored
@@ -1,127 +0,0 @@
|
|||||||
/*
|
|
||||||
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
|
|
||||||
239
vendor/github.com/aclindsa/ofxgo/generate_constants.py
generated
vendored
239
vendor/github.com/aclindsa/ofxgo/generate_constants.py
generated
vendored
@@ -1,239 +0,0 @@
|
|||||||
#!/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("<GARBAGE><!LALDK>"), &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))
|
|
||||||
1414
vendor/github.com/aclindsa/ofxgo/invstmt.go
generated
vendored
1414
vendor/github.com/aclindsa/ofxgo/invstmt.go
generated
vendored
File diff suppressed because it is too large
Load Diff
344
vendor/github.com/aclindsa/ofxgo/leaf_elements.go
generated
vendored
344
vendor/github.com/aclindsa/ofxgo/leaf_elements.go
generated
vendored
@@ -1,344 +0,0 @@
|
|||||||
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/^<!ELEMENT\s+([A-Z0-9]+)\s+-\s+[oO]\s+%.*TYPE\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",
|
|
||||||
}
|
|
||||||
206
vendor/github.com/aclindsa/ofxgo/profile.go
generated
vendored
206
vendor/github.com/aclindsa/ofxgo/profile.go
generated
vendored
@@ -1,206 +0,0 @@
|
|||||||
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 <PINCHRQ> 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 // <xxxMSGSETVn>
|
|
||||||
Name string // <xxxMSGSETVn> (copy of XMLName.Local)
|
|
||||||
Ver Int `xml:"MSGSETCORE>VER"` // Message set version - should always match 'n' in <xxxMSGSETVn> of Name
|
|
||||||
URL String `xml:"MSGSETCORE>URL"` // URL where messages in this set are to be set
|
|
||||||
OfxSec ofxSec `xml:"MSGSETCORE>OFXSEC"` // NONE or 'TYPE 1'
|
|
||||||
TranspSec Boolean `xml:"MSGSETCORE>TRANSPSEC"` // Transport-level security must be used
|
|
||||||
SignonRealm String `xml:"MSGSETCORE>SIGNONREALM"` // Used to identify which SignonInfo to use for to this MessageSet
|
|
||||||
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 <REFRESH>Y within synchronizations. This option is irrelevant for full synchronization servers. Clients must ignore <REFRESHSUPT> (or its absence) if the profile also specifies <SYNCMODE>FULL. For lite synchronization, the default is N. Without <REFRESHSUPT>Y, lite synchronization servers are not required to support <REFRESH>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 <xxxMSGSET>. 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 <xxxMSGSET>
|
|
||||||
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
|
|
||||||
}
|
|
||||||
151
vendor/github.com/aclindsa/ofxgo/request.go
generated
vendored
151
vendor/github.com/aclindsa/ofxgo/request.go
generated
vendored
@@ -1,151 +0,0 @@
|
|||||||
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 //<SIGNONMSGSETV1>
|
|
||||||
Signup []Message //<SIGNUPMSGSETV1>
|
|
||||||
Bank []Message //<BANKMSGSETV1>
|
|
||||||
CreditCard []Message //<CREDITCARDMSGSETV1>
|
|
||||||
Loan []Message //<LOANMSGSETV1>
|
|
||||||
InvStmt []Message //<INVSTMTMSGSETV1>
|
|
||||||
InterXfer []Message //<INTERXFERMSGSETV1>
|
|
||||||
WireXfer []Message //<WIREXFERMSGSETV1>
|
|
||||||
Billpay []Message //<BILLPAYMSGSETV1>
|
|
||||||
Email []Message //<EMAILMSGSETV1>
|
|
||||||
SecList []Message //<SECLISTMSGSETV1>
|
|
||||||
PresDir []Message //<PRESDIRMSGSETV1>
|
|
||||||
PresDlv []Message //<PRESDLVMSGSETV1>
|
|
||||||
Prof []Message //<PROFMSGSETV1>
|
|
||||||
Image []Message //<IMAGEMSGSETV1>
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
505
vendor/github.com/aclindsa/ofxgo/response.go
generated
vendored
505
vendor/github.com/aclindsa/ofxgo/response.go
generated
vendored
@@ -1,505 +0,0 @@
|
|||||||
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 //<SIGNONMSGSETV1>
|
|
||||||
Signup []Message //<SIGNUPMSGSETV1>
|
|
||||||
Bank []Message //<BANKMSGSETV1>
|
|
||||||
CreditCard []Message //<CREDITCARDMSGSETV1>
|
|
||||||
Loan []Message //<LOANMSGSETV1>
|
|
||||||
InvStmt []Message //<INVSTMTMSGSETV1>
|
|
||||||
InterXfer []Message //<INTERXFERMSGSETV1>
|
|
||||||
WireXfer []Message //<WIREXFERMSGSETV1>
|
|
||||||
Billpay []Message //<BILLPAYMSGSETV1>
|
|
||||||
Email []Message //<EMAILMSGSETV1>
|
|
||||||
SecList []Message //<SECLISTMSGSETV1>
|
|
||||||
PresDir []Message //<PRESDIRMSGSETV1>
|
|
||||||
PresDlv []Message //<PRESDLVMSGSETV1>
|
|
||||||
Prof []Message //<PROFMSGSETV1>
|
|
||||||
Image []Message //<IMAGEMSGSETV1>
|
|
||||||
}
|
|
||||||
|
|
||||||
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<OFXHEADER>\d+)\s*` +
|
|
||||||
`DATA:\s*(?P<DATA>[A-Z]+)\s*` +
|
|
||||||
`VERSION:\s*(?P<VERSION>\d+)\s*` +
|
|
||||||
`SECURITY:\s*(?P<SECURITY>[\w]+)\s*` +
|
|
||||||
`ENCODING:\s*(?P<ENCODING>[A-Z0-9-]+)\s*` +
|
|
||||||
`CHARSET:\s*(?P<CHARSET>[\w-]+)\s*` +
|
|
||||||
`COMPRESSION:\s*(?P<COMPRESSION>[A-Z]+)\s*` +
|
|
||||||
`OLDFILEUID:\s*(?P<OLDFILEUID>[\w-]+)\s*` +
|
|
||||||
`NEWFILEUID:\s*(?P<NEWFILEUID>[\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
|
|
||||||
}
|
|
||||||
332
vendor/github.com/aclindsa/ofxgo/seclist.go
generated
vendored
332
vendor/github.com/aclindsa/ofxgo/seclist.go
generated
vendored
@@ -1,332 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
88
vendor/github.com/aclindsa/ofxgo/signon.go
generated
vendored
88
vendor/github.com/aclindsa/ofxgo/signon.go
generated
vendored
@@ -1,88 +0,0 @@
|
|||||||
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()
|
|
||||||
}
|
|
||||||
166
vendor/github.com/aclindsa/ofxgo/signup.go
generated
vendored
166
vendor/github.com/aclindsa/ofxgo/signup.go
generated
vendored
@@ -1,166 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
386
vendor/github.com/aclindsa/ofxgo/types.go
generated
vendored
386
vendor/github.com/aclindsa/ofxgo/types.go
generated
vendored
@@ -1,386 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
25
vendor/github.com/aclindsa/ofxgo/util.go
generated
vendored
25
vendor/github.com/aclindsa/ofxgo/util.go
generated
vendored
@@ -1,25 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
83
vendor/github.com/aclindsa/ofxgo/vanguard_client.go
generated
vendored
83
vendor/github.com/aclindsa/ofxgo/vanguard_client.go
generated
vendored
@@ -1,83 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
||||||
1107
vendor/github.com/aclindsa/xml/AUTHORS
generated
vendored
1107
vendor/github.com/aclindsa/xml/AUTHORS
generated
vendored
File diff suppressed because it is too large
Load Diff
1445
vendor/github.com/aclindsa/xml/CONTRIBUTORS
generated
vendored
1445
vendor/github.com/aclindsa/xml/CONTRIBUTORS
generated
vendored
File diff suppressed because it is too large
Load Diff
27
vendor/github.com/aclindsa/xml/LICENSE
generated
vendored
27
vendor/github.com/aclindsa/xml/LICENSE
generated
vendored
@@ -1,27 +0,0 @@
|
|||||||
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.
|
|
||||||
22
vendor/github.com/aclindsa/xml/PATENTS
generated
vendored
22
vendor/github.com/aclindsa/xml/PATENTS
generated
vendored
@@ -1,22 +0,0 @@
|
|||||||
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.
|
|
||||||
1075
vendor/github.com/aclindsa/xml/marshal.go
generated
vendored
1075
vendor/github.com/aclindsa/xml/marshal.go
generated
vendored
File diff suppressed because it is too large
Load Diff
749
vendor/github.com/aclindsa/xml/read.go
generated
vendored
749
vendor/github.com/aclindsa/xml/read.go
generated
vendored
@@ -1,749 +0,0 @@
|
|||||||
// 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
364
vendor/github.com/aclindsa/xml/typeinfo.go
generated
vendored
364
vendor/github.com/aclindsa/xml/typeinfo.go
generated
vendored
@@ -1,364 +0,0 @@
|
|||||||
// 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
|
|
||||||
}
|
|
||||||
2075
vendor/github.com/aclindsa/xml/xml.go
generated
vendored
2075
vendor/github.com/aclindsa/xml/xml.go
generated
vendored
File diff suppressed because it is too large
Load Diff
41
vendor/github.com/google/uuid/CHANGELOG.md
generated
vendored
41
vendor/github.com/google/uuid/CHANGELOG.md
generated
vendored
@@ -1,41 +0,0 @@
|
|||||||
# Changelog
|
|
||||||
|
|
||||||
## [1.6.0](https://github.com/google/uuid/compare/v1.5.0...v1.6.0) (2024-01-16)
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* add Max UUID constant ([#149](https://github.com/google/uuid/issues/149)) ([c58770e](https://github.com/google/uuid/commit/c58770eb495f55fe2ced6284f93c5158a62e53e3))
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* fix typo in version 7 uuid documentation ([#153](https://github.com/google/uuid/issues/153)) ([016b199](https://github.com/google/uuid/commit/016b199544692f745ffc8867b914129ecb47ef06))
|
|
||||||
* Monotonicity in UUIDv7 ([#150](https://github.com/google/uuid/issues/150)) ([a2b2b32](https://github.com/google/uuid/commit/a2b2b32373ff0b1a312b7fdf6d38a977099698a6))
|
|
||||||
|
|
||||||
## [1.5.0](https://github.com/google/uuid/compare/v1.4.0...v1.5.0) (2023-12-12)
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* Validate UUID without creating new UUID ([#141](https://github.com/google/uuid/issues/141)) ([9ee7366](https://github.com/google/uuid/commit/9ee7366e66c9ad96bab89139418a713dc584ae29))
|
|
||||||
|
|
||||||
## [1.4.0](https://github.com/google/uuid/compare/v1.3.1...v1.4.0) (2023-10-26)
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* UUIDs slice type with Strings() convenience method ([#133](https://github.com/google/uuid/issues/133)) ([cd5fbbd](https://github.com/google/uuid/commit/cd5fbbdd02f3e3467ac18940e07e062be1f864b4))
|
|
||||||
|
|
||||||
### Fixes
|
|
||||||
|
|
||||||
* Clarify that Parse's job is to parse but not necessarily validate strings. (Documents current behavior)
|
|
||||||
|
|
||||||
## [1.3.1](https://github.com/google/uuid/compare/v1.3.0...v1.3.1) (2023-08-18)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* Use .EqualFold() to parse urn prefixed UUIDs ([#118](https://github.com/google/uuid/issues/118)) ([574e687](https://github.com/google/uuid/commit/574e6874943741fb99d41764c705173ada5293f0))
|
|
||||||
|
|
||||||
## Changelog
|
|
||||||
26
vendor/github.com/google/uuid/CONTRIBUTING.md
generated
vendored
26
vendor/github.com/google/uuid/CONTRIBUTING.md
generated
vendored
@@ -1,26 +0,0 @@
|
|||||||
# How to contribute
|
|
||||||
|
|
||||||
We definitely welcome patches and contribution to this project!
|
|
||||||
|
|
||||||
### Tips
|
|
||||||
|
|
||||||
Commits must be formatted according to the [Conventional Commits Specification](https://www.conventionalcommits.org).
|
|
||||||
|
|
||||||
Always try to include a test case! If it is not possible or not necessary,
|
|
||||||
please explain why in the pull request description.
|
|
||||||
|
|
||||||
### Releasing
|
|
||||||
|
|
||||||
Commits that would precipitate a SemVer change, as described in the Conventional
|
|
||||||
Commits Specification, will trigger [`release-please`](https://github.com/google-github-actions/release-please-action)
|
|
||||||
to create a release candidate pull request. Once submitted, `release-please`
|
|
||||||
will create a release.
|
|
||||||
|
|
||||||
For tips on how to work with `release-please`, see its documentation.
|
|
||||||
|
|
||||||
### Legal requirements
|
|
||||||
|
|
||||||
In order to protect both you and ourselves, you will need to sign the
|
|
||||||
[Contributor License Agreement](https://cla.developers.google.com/clas).
|
|
||||||
|
|
||||||
You may have already signed it for other Google projects.
|
|
||||||
9
vendor/github.com/google/uuid/CONTRIBUTORS
generated
vendored
9
vendor/github.com/google/uuid/CONTRIBUTORS
generated
vendored
@@ -1,9 +0,0 @@
|
|||||||
Paul Borman <borman@google.com>
|
|
||||||
bmatsuo
|
|
||||||
shawnps
|
|
||||||
theory
|
|
||||||
jboverfelt
|
|
||||||
dsymonds
|
|
||||||
cd1
|
|
||||||
wallclockbuilder
|
|
||||||
dansouza
|
|
||||||
27
vendor/github.com/google/uuid/LICENSE
generated
vendored
27
vendor/github.com/google/uuid/LICENSE
generated
vendored
@@ -1,27 +0,0 @@
|
|||||||
Copyright (c) 2009,2014 Google Inc. 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.
|
|
||||||
21
vendor/github.com/google/uuid/README.md
generated
vendored
21
vendor/github.com/google/uuid/README.md
generated
vendored
@@ -1,21 +0,0 @@
|
|||||||
# uuid
|
|
||||||
The uuid package generates and inspects UUIDs based on
|
|
||||||
[RFC 4122](https://datatracker.ietf.org/doc/html/rfc4122)
|
|
||||||
and DCE 1.1: Authentication and Security Services.
|
|
||||||
|
|
||||||
This package is based on the github.com/pborman/uuid package (previously named
|
|
||||||
code.google.com/p/go-uuid). It differs from these earlier packages in that
|
|
||||||
a UUID is a 16 byte array rather than a byte slice. One loss due to this
|
|
||||||
change is the ability to represent an invalid UUID (vs a NIL UUID).
|
|
||||||
|
|
||||||
###### Install
|
|
||||||
```sh
|
|
||||||
go get github.com/google/uuid
|
|
||||||
```
|
|
||||||
|
|
||||||
###### Documentation
|
|
||||||
[](https://pkg.go.dev/github.com/google/uuid)
|
|
||||||
|
|
||||||
Full `go doc` style documentation for the package can be viewed online without
|
|
||||||
installing this package by using the GoDoc site here:
|
|
||||||
http://pkg.go.dev/github.com/google/uuid
|
|
||||||
80
vendor/github.com/google/uuid/dce.go
generated
vendored
80
vendor/github.com/google/uuid/dce.go
generated
vendored
@@ -1,80 +0,0 @@
|
|||||||
// Copyright 2016 Google Inc. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package uuid
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
// A Domain represents a Version 2 domain
|
|
||||||
type Domain byte
|
|
||||||
|
|
||||||
// Domain constants for DCE Security (Version 2) UUIDs.
|
|
||||||
const (
|
|
||||||
Person = Domain(0)
|
|
||||||
Group = Domain(1)
|
|
||||||
Org = Domain(2)
|
|
||||||
)
|
|
||||||
|
|
||||||
// NewDCESecurity returns a DCE Security (Version 2) UUID.
|
|
||||||
//
|
|
||||||
// The domain should be one of Person, Group or Org.
|
|
||||||
// On a POSIX system the id should be the users UID for the Person
|
|
||||||
// domain and the users GID for the Group. The meaning of id for
|
|
||||||
// the domain Org or on non-POSIX systems is site defined.
|
|
||||||
//
|
|
||||||
// For a given domain/id pair the same token may be returned for up to
|
|
||||||
// 7 minutes and 10 seconds.
|
|
||||||
func NewDCESecurity(domain Domain, id uint32) (UUID, error) {
|
|
||||||
uuid, err := NewUUID()
|
|
||||||
if err == nil {
|
|
||||||
uuid[6] = (uuid[6] & 0x0f) | 0x20 // Version 2
|
|
||||||
uuid[9] = byte(domain)
|
|
||||||
binary.BigEndian.PutUint32(uuid[0:], id)
|
|
||||||
}
|
|
||||||
return uuid, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewDCEPerson returns a DCE Security (Version 2) UUID in the person
|
|
||||||
// domain with the id returned by os.Getuid.
|
|
||||||
//
|
|
||||||
// NewDCESecurity(Person, uint32(os.Getuid()))
|
|
||||||
func NewDCEPerson() (UUID, error) {
|
|
||||||
return NewDCESecurity(Person, uint32(os.Getuid()))
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewDCEGroup returns a DCE Security (Version 2) UUID in the group
|
|
||||||
// domain with the id returned by os.Getgid.
|
|
||||||
//
|
|
||||||
// NewDCESecurity(Group, uint32(os.Getgid()))
|
|
||||||
func NewDCEGroup() (UUID, error) {
|
|
||||||
return NewDCESecurity(Group, uint32(os.Getgid()))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Domain returns the domain for a Version 2 UUID. Domains are only defined
|
|
||||||
// for Version 2 UUIDs.
|
|
||||||
func (uuid UUID) Domain() Domain {
|
|
||||||
return Domain(uuid[9])
|
|
||||||
}
|
|
||||||
|
|
||||||
// ID returns the id for a Version 2 UUID. IDs are only defined for Version 2
|
|
||||||
// UUIDs.
|
|
||||||
func (uuid UUID) ID() uint32 {
|
|
||||||
return binary.BigEndian.Uint32(uuid[0:4])
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d Domain) String() string {
|
|
||||||
switch d {
|
|
||||||
case Person:
|
|
||||||
return "Person"
|
|
||||||
case Group:
|
|
||||||
return "Group"
|
|
||||||
case Org:
|
|
||||||
return "Org"
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("Domain%d", int(d))
|
|
||||||
}
|
|
||||||
12
vendor/github.com/google/uuid/doc.go
generated
vendored
12
vendor/github.com/google/uuid/doc.go
generated
vendored
@@ -1,12 +0,0 @@
|
|||||||
// Copyright 2016 Google Inc. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// Package uuid generates and inspects UUIDs.
|
|
||||||
//
|
|
||||||
// UUIDs are based on RFC 4122 and DCE 1.1: Authentication and Security
|
|
||||||
// Services.
|
|
||||||
//
|
|
||||||
// A UUID is a 16 byte (128 bit) array. UUIDs may be used as keys to
|
|
||||||
// maps or compared directly.
|
|
||||||
package uuid
|
|
||||||
59
vendor/github.com/google/uuid/hash.go
generated
vendored
59
vendor/github.com/google/uuid/hash.go
generated
vendored
@@ -1,59 +0,0 @@
|
|||||||
// Copyright 2016 Google Inc. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package uuid
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/md5"
|
|
||||||
"crypto/sha1"
|
|
||||||
"hash"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Well known namespace IDs and UUIDs
|
|
||||||
var (
|
|
||||||
NameSpaceDNS = Must(Parse("6ba7b810-9dad-11d1-80b4-00c04fd430c8"))
|
|
||||||
NameSpaceURL = Must(Parse("6ba7b811-9dad-11d1-80b4-00c04fd430c8"))
|
|
||||||
NameSpaceOID = Must(Parse("6ba7b812-9dad-11d1-80b4-00c04fd430c8"))
|
|
||||||
NameSpaceX500 = Must(Parse("6ba7b814-9dad-11d1-80b4-00c04fd430c8"))
|
|
||||||
Nil UUID // empty UUID, all zeros
|
|
||||||
|
|
||||||
// The Max UUID is special form of UUID that is specified to have all 128 bits set to 1.
|
|
||||||
Max = UUID{
|
|
||||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
|
||||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// NewHash returns a new UUID derived from the hash of space concatenated with
|
|
||||||
// data generated by h. The hash should be at least 16 byte in length. The
|
|
||||||
// first 16 bytes of the hash are used to form the UUID. The version of the
|
|
||||||
// UUID will be the lower 4 bits of version. NewHash is used to implement
|
|
||||||
// NewMD5 and NewSHA1.
|
|
||||||
func NewHash(h hash.Hash, space UUID, data []byte, version int) UUID {
|
|
||||||
h.Reset()
|
|
||||||
h.Write(space[:]) //nolint:errcheck
|
|
||||||
h.Write(data) //nolint:errcheck
|
|
||||||
s := h.Sum(nil)
|
|
||||||
var uuid UUID
|
|
||||||
copy(uuid[:], s)
|
|
||||||
uuid[6] = (uuid[6] & 0x0f) | uint8((version&0xf)<<4)
|
|
||||||
uuid[8] = (uuid[8] & 0x3f) | 0x80 // RFC 4122 variant
|
|
||||||
return uuid
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMD5 returns a new MD5 (Version 3) UUID based on the
|
|
||||||
// supplied name space and data. It is the same as calling:
|
|
||||||
//
|
|
||||||
// NewHash(md5.New(), space, data, 3)
|
|
||||||
func NewMD5(space UUID, data []byte) UUID {
|
|
||||||
return NewHash(md5.New(), space, data, 3)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewSHA1 returns a new SHA1 (Version 5) UUID based on the
|
|
||||||
// supplied name space and data. It is the same as calling:
|
|
||||||
//
|
|
||||||
// NewHash(sha1.New(), space, data, 5)
|
|
||||||
func NewSHA1(space UUID, data []byte) UUID {
|
|
||||||
return NewHash(sha1.New(), space, data, 5)
|
|
||||||
}
|
|
||||||
38
vendor/github.com/google/uuid/marshal.go
generated
vendored
38
vendor/github.com/google/uuid/marshal.go
generated
vendored
@@ -1,38 +0,0 @@
|
|||||||
// Copyright 2016 Google Inc. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package uuid
|
|
||||||
|
|
||||||
import "fmt"
|
|
||||||
|
|
||||||
// MarshalText implements encoding.TextMarshaler.
|
|
||||||
func (uuid UUID) MarshalText() ([]byte, error) {
|
|
||||||
var js [36]byte
|
|
||||||
encodeHex(js[:], uuid)
|
|
||||||
return js[:], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalText implements encoding.TextUnmarshaler.
|
|
||||||
func (uuid *UUID) UnmarshalText(data []byte) error {
|
|
||||||
id, err := ParseBytes(data)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
*uuid = id
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalBinary implements encoding.BinaryMarshaler.
|
|
||||||
func (uuid UUID) MarshalBinary() ([]byte, error) {
|
|
||||||
return uuid[:], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalBinary implements encoding.BinaryUnmarshaler.
|
|
||||||
func (uuid *UUID) UnmarshalBinary(data []byte) error {
|
|
||||||
if len(data) != 16 {
|
|
||||||
return fmt.Errorf("invalid UUID (got %d bytes)", len(data))
|
|
||||||
}
|
|
||||||
copy(uuid[:], data)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
90
vendor/github.com/google/uuid/node.go
generated
vendored
90
vendor/github.com/google/uuid/node.go
generated
vendored
@@ -1,90 +0,0 @@
|
|||||||
// Copyright 2016 Google Inc. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package uuid
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
nodeMu sync.Mutex
|
|
||||||
ifname string // name of interface being used
|
|
||||||
nodeID [6]byte // hardware for version 1 UUIDs
|
|
||||||
zeroID [6]byte // nodeID with only 0's
|
|
||||||
)
|
|
||||||
|
|
||||||
// NodeInterface returns the name of the interface from which the NodeID was
|
|
||||||
// derived. The interface "user" is returned if the NodeID was set by
|
|
||||||
// SetNodeID.
|
|
||||||
func NodeInterface() string {
|
|
||||||
defer nodeMu.Unlock()
|
|
||||||
nodeMu.Lock()
|
|
||||||
return ifname
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetNodeInterface selects the hardware address to be used for Version 1 UUIDs.
|
|
||||||
// If name is "" then the first usable interface found will be used or a random
|
|
||||||
// Node ID will be generated. If a named interface cannot be found then false
|
|
||||||
// is returned.
|
|
||||||
//
|
|
||||||
// SetNodeInterface never fails when name is "".
|
|
||||||
func SetNodeInterface(name string) bool {
|
|
||||||
defer nodeMu.Unlock()
|
|
||||||
nodeMu.Lock()
|
|
||||||
return setNodeInterface(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
func setNodeInterface(name string) bool {
|
|
||||||
iname, addr := getHardwareInterface(name) // null implementation for js
|
|
||||||
if iname != "" && addr != nil {
|
|
||||||
ifname = iname
|
|
||||||
copy(nodeID[:], addr)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// We found no interfaces with a valid hardware address. If name
|
|
||||||
// does not specify a specific interface generate a random Node ID
|
|
||||||
// (section 4.1.6)
|
|
||||||
if name == "" {
|
|
||||||
ifname = "random"
|
|
||||||
randomBits(nodeID[:])
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// NodeID returns a slice of a copy of the current Node ID, setting the Node ID
|
|
||||||
// if not already set.
|
|
||||||
func NodeID() []byte {
|
|
||||||
defer nodeMu.Unlock()
|
|
||||||
nodeMu.Lock()
|
|
||||||
if nodeID == zeroID {
|
|
||||||
setNodeInterface("")
|
|
||||||
}
|
|
||||||
nid := nodeID
|
|
||||||
return nid[:]
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetNodeID sets the Node ID to be used for Version 1 UUIDs. The first 6 bytes
|
|
||||||
// of id are used. If id is less than 6 bytes then false is returned and the
|
|
||||||
// Node ID is not set.
|
|
||||||
func SetNodeID(id []byte) bool {
|
|
||||||
if len(id) < 6 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
defer nodeMu.Unlock()
|
|
||||||
nodeMu.Lock()
|
|
||||||
copy(nodeID[:], id)
|
|
||||||
ifname = "user"
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// NodeID returns the 6 byte node id encoded in uuid. It returns nil if uuid is
|
|
||||||
// not valid. The NodeID is only well defined for version 1 and 2 UUIDs.
|
|
||||||
func (uuid UUID) NodeID() []byte {
|
|
||||||
var node [6]byte
|
|
||||||
copy(node[:], uuid[10:])
|
|
||||||
return node[:]
|
|
||||||
}
|
|
||||||
12
vendor/github.com/google/uuid/node_js.go
generated
vendored
12
vendor/github.com/google/uuid/node_js.go
generated
vendored
@@ -1,12 +0,0 @@
|
|||||||
// Copyright 2017 Google Inc. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build js
|
|
||||||
|
|
||||||
package uuid
|
|
||||||
|
|
||||||
// getHardwareInterface returns nil values for the JS version of the code.
|
|
||||||
// This removes the "net" dependency, because it is not used in the browser.
|
|
||||||
// Using the "net" library inflates the size of the transpiled JS code by 673k bytes.
|
|
||||||
func getHardwareInterface(name string) (string, []byte) { return "", nil }
|
|
||||||
33
vendor/github.com/google/uuid/node_net.go
generated
vendored
33
vendor/github.com/google/uuid/node_net.go
generated
vendored
@@ -1,33 +0,0 @@
|
|||||||
// Copyright 2017 Google Inc. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build !js
|
|
||||||
|
|
||||||
package uuid
|
|
||||||
|
|
||||||
import "net"
|
|
||||||
|
|
||||||
var interfaces []net.Interface // cached list of interfaces
|
|
||||||
|
|
||||||
// getHardwareInterface returns the name and hardware address of interface name.
|
|
||||||
// If name is "" then the name and hardware address of one of the system's
|
|
||||||
// interfaces is returned. If no interfaces are found (name does not exist or
|
|
||||||
// there are no interfaces) then "", nil is returned.
|
|
||||||
//
|
|
||||||
// Only addresses of at least 6 bytes are returned.
|
|
||||||
func getHardwareInterface(name string) (string, []byte) {
|
|
||||||
if interfaces == nil {
|
|
||||||
var err error
|
|
||||||
interfaces, err = net.Interfaces()
|
|
||||||
if err != nil {
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, ifs := range interfaces {
|
|
||||||
if len(ifs.HardwareAddr) >= 6 && (name == "" || name == ifs.Name) {
|
|
||||||
return ifs.Name, ifs.HardwareAddr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
118
vendor/github.com/google/uuid/null.go
generated
vendored
118
vendor/github.com/google/uuid/null.go
generated
vendored
@@ -1,118 +0,0 @@
|
|||||||
// Copyright 2021 Google Inc. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package uuid
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"database/sql/driver"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
var jsonNull = []byte("null")
|
|
||||||
|
|
||||||
// NullUUID represents a UUID that may be null.
|
|
||||||
// NullUUID implements the SQL driver.Scanner interface so
|
|
||||||
// it can be used as a scan destination:
|
|
||||||
//
|
|
||||||
// var u uuid.NullUUID
|
|
||||||
// err := db.QueryRow("SELECT name FROM foo WHERE id=?", id).Scan(&u)
|
|
||||||
// ...
|
|
||||||
// if u.Valid {
|
|
||||||
// // use u.UUID
|
|
||||||
// } else {
|
|
||||||
// // NULL value
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
type NullUUID struct {
|
|
||||||
UUID UUID
|
|
||||||
Valid bool // Valid is true if UUID is not NULL
|
|
||||||
}
|
|
||||||
|
|
||||||
// Scan implements the SQL driver.Scanner interface.
|
|
||||||
func (nu *NullUUID) Scan(value interface{}) error {
|
|
||||||
if value == nil {
|
|
||||||
nu.UUID, nu.Valid = Nil, false
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
err := nu.UUID.Scan(value)
|
|
||||||
if err != nil {
|
|
||||||
nu.Valid = false
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
nu.Valid = true
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Value implements the driver Valuer interface.
|
|
||||||
func (nu NullUUID) Value() (driver.Value, error) {
|
|
||||||
if !nu.Valid {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
// Delegate to UUID Value function
|
|
||||||
return nu.UUID.Value()
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalBinary implements encoding.BinaryMarshaler.
|
|
||||||
func (nu NullUUID) MarshalBinary() ([]byte, error) {
|
|
||||||
if nu.Valid {
|
|
||||||
return nu.UUID[:], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return []byte(nil), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalBinary implements encoding.BinaryUnmarshaler.
|
|
||||||
func (nu *NullUUID) UnmarshalBinary(data []byte) error {
|
|
||||||
if len(data) != 16 {
|
|
||||||
return fmt.Errorf("invalid UUID (got %d bytes)", len(data))
|
|
||||||
}
|
|
||||||
copy(nu.UUID[:], data)
|
|
||||||
nu.Valid = true
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalText implements encoding.TextMarshaler.
|
|
||||||
func (nu NullUUID) MarshalText() ([]byte, error) {
|
|
||||||
if nu.Valid {
|
|
||||||
return nu.UUID.MarshalText()
|
|
||||||
}
|
|
||||||
|
|
||||||
return jsonNull, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalText implements encoding.TextUnmarshaler.
|
|
||||||
func (nu *NullUUID) UnmarshalText(data []byte) error {
|
|
||||||
id, err := ParseBytes(data)
|
|
||||||
if err != nil {
|
|
||||||
nu.Valid = false
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
nu.UUID = id
|
|
||||||
nu.Valid = true
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalJSON implements json.Marshaler.
|
|
||||||
func (nu NullUUID) MarshalJSON() ([]byte, error) {
|
|
||||||
if nu.Valid {
|
|
||||||
return json.Marshal(nu.UUID)
|
|
||||||
}
|
|
||||||
|
|
||||||
return jsonNull, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalJSON implements json.Unmarshaler.
|
|
||||||
func (nu *NullUUID) UnmarshalJSON(data []byte) error {
|
|
||||||
if bytes.Equal(data, jsonNull) {
|
|
||||||
*nu = NullUUID{}
|
|
||||||
return nil // valid null UUID
|
|
||||||
}
|
|
||||||
err := json.Unmarshal(data, &nu.UUID)
|
|
||||||
nu.Valid = err == nil
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
59
vendor/github.com/google/uuid/sql.go
generated
vendored
59
vendor/github.com/google/uuid/sql.go
generated
vendored
@@ -1,59 +0,0 @@
|
|||||||
// Copyright 2016 Google Inc. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package uuid
|
|
||||||
|
|
||||||
import (
|
|
||||||
"database/sql/driver"
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Scan implements sql.Scanner so UUIDs can be read from databases transparently.
|
|
||||||
// Currently, database types that map to string and []byte are supported. Please
|
|
||||||
// consult database-specific driver documentation for matching types.
|
|
||||||
func (uuid *UUID) Scan(src interface{}) error {
|
|
||||||
switch src := src.(type) {
|
|
||||||
case nil:
|
|
||||||
return nil
|
|
||||||
|
|
||||||
case string:
|
|
||||||
// if an empty UUID comes from a table, we return a null UUID
|
|
||||||
if src == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// see Parse for required string format
|
|
||||||
u, err := Parse(src)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Scan: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
*uuid = u
|
|
||||||
|
|
||||||
case []byte:
|
|
||||||
// if an empty UUID comes from a table, we return a null UUID
|
|
||||||
if len(src) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// assumes a simple slice of bytes if 16 bytes
|
|
||||||
// otherwise attempts to parse
|
|
||||||
if len(src) != 16 {
|
|
||||||
return uuid.Scan(string(src))
|
|
||||||
}
|
|
||||||
copy((*uuid)[:], src)
|
|
||||||
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("Scan: unable to scan type %T into UUID", src)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Value implements sql.Valuer so that UUIDs can be written to databases
|
|
||||||
// transparently. Currently, UUIDs map to strings. Please consult
|
|
||||||
// database-specific driver documentation for matching types.
|
|
||||||
func (uuid UUID) Value() (driver.Value, error) {
|
|
||||||
return uuid.String(), nil
|
|
||||||
}
|
|
||||||
134
vendor/github.com/google/uuid/time.go
generated
vendored
134
vendor/github.com/google/uuid/time.go
generated
vendored
@@ -1,134 +0,0 @@
|
|||||||
// Copyright 2016 Google Inc. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package uuid
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// A Time represents a time as the number of 100's of nanoseconds since 15 Oct
|
|
||||||
// 1582.
|
|
||||||
type Time int64
|
|
||||||
|
|
||||||
const (
|
|
||||||
lillian = 2299160 // Julian day of 15 Oct 1582
|
|
||||||
unix = 2440587 // Julian day of 1 Jan 1970
|
|
||||||
epoch = unix - lillian // Days between epochs
|
|
||||||
g1582 = epoch * 86400 // seconds between epochs
|
|
||||||
g1582ns100 = g1582 * 10000000 // 100s of a nanoseconds between epochs
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
timeMu sync.Mutex
|
|
||||||
lasttime uint64 // last time we returned
|
|
||||||
clockSeq uint16 // clock sequence for this run
|
|
||||||
|
|
||||||
timeNow = time.Now // for testing
|
|
||||||
)
|
|
||||||
|
|
||||||
// UnixTime converts t the number of seconds and nanoseconds using the Unix
|
|
||||||
// epoch of 1 Jan 1970.
|
|
||||||
func (t Time) UnixTime() (sec, nsec int64) {
|
|
||||||
sec = int64(t - g1582ns100)
|
|
||||||
nsec = (sec % 10000000) * 100
|
|
||||||
sec /= 10000000
|
|
||||||
return sec, nsec
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetTime returns the current Time (100s of nanoseconds since 15 Oct 1582) and
|
|
||||||
// clock sequence as well as adjusting the clock sequence as needed. An error
|
|
||||||
// is returned if the current time cannot be determined.
|
|
||||||
func GetTime() (Time, uint16, error) {
|
|
||||||
defer timeMu.Unlock()
|
|
||||||
timeMu.Lock()
|
|
||||||
return getTime()
|
|
||||||
}
|
|
||||||
|
|
||||||
func getTime() (Time, uint16, error) {
|
|
||||||
t := timeNow()
|
|
||||||
|
|
||||||
// If we don't have a clock sequence already, set one.
|
|
||||||
if clockSeq == 0 {
|
|
||||||
setClockSequence(-1)
|
|
||||||
}
|
|
||||||
now := uint64(t.UnixNano()/100) + g1582ns100
|
|
||||||
|
|
||||||
// If time has gone backwards with this clock sequence then we
|
|
||||||
// increment the clock sequence
|
|
||||||
if now <= lasttime {
|
|
||||||
clockSeq = ((clockSeq + 1) & 0x3fff) | 0x8000
|
|
||||||
}
|
|
||||||
lasttime = now
|
|
||||||
return Time(now), clockSeq, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ClockSequence returns the current clock sequence, generating one if not
|
|
||||||
// already set. The clock sequence is only used for Version 1 UUIDs.
|
|
||||||
//
|
|
||||||
// The uuid package does not use global static storage for the clock sequence or
|
|
||||||
// the last time a UUID was generated. Unless SetClockSequence is used, a new
|
|
||||||
// random clock sequence is generated the first time a clock sequence is
|
|
||||||
// requested by ClockSequence, GetTime, or NewUUID. (section 4.2.1.1)
|
|
||||||
func ClockSequence() int {
|
|
||||||
defer timeMu.Unlock()
|
|
||||||
timeMu.Lock()
|
|
||||||
return clockSequence()
|
|
||||||
}
|
|
||||||
|
|
||||||
func clockSequence() int {
|
|
||||||
if clockSeq == 0 {
|
|
||||||
setClockSequence(-1)
|
|
||||||
}
|
|
||||||
return int(clockSeq & 0x3fff)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetClockSequence sets the clock sequence to the lower 14 bits of seq. Setting to
|
|
||||||
// -1 causes a new sequence to be generated.
|
|
||||||
func SetClockSequence(seq int) {
|
|
||||||
defer timeMu.Unlock()
|
|
||||||
timeMu.Lock()
|
|
||||||
setClockSequence(seq)
|
|
||||||
}
|
|
||||||
|
|
||||||
func setClockSequence(seq int) {
|
|
||||||
if seq == -1 {
|
|
||||||
var b [2]byte
|
|
||||||
randomBits(b[:]) // clock sequence
|
|
||||||
seq = int(b[0])<<8 | int(b[1])
|
|
||||||
}
|
|
||||||
oldSeq := clockSeq
|
|
||||||
clockSeq = uint16(seq&0x3fff) | 0x8000 // Set our variant
|
|
||||||
if oldSeq != clockSeq {
|
|
||||||
lasttime = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Time returns the time in 100s of nanoseconds since 15 Oct 1582 encoded in
|
|
||||||
// uuid. The time is only defined for version 1, 2, 6 and 7 UUIDs.
|
|
||||||
func (uuid UUID) Time() Time {
|
|
||||||
var t Time
|
|
||||||
switch uuid.Version() {
|
|
||||||
case 6:
|
|
||||||
time := binary.BigEndian.Uint64(uuid[:8]) // Ignore uuid[6] version b0110
|
|
||||||
t = Time(time)
|
|
||||||
case 7:
|
|
||||||
time := binary.BigEndian.Uint64(uuid[:8])
|
|
||||||
t = Time((time>>16)*10000 + g1582ns100)
|
|
||||||
default: // forward compatible
|
|
||||||
time := int64(binary.BigEndian.Uint32(uuid[0:4]))
|
|
||||||
time |= int64(binary.BigEndian.Uint16(uuid[4:6])) << 32
|
|
||||||
time |= int64(binary.BigEndian.Uint16(uuid[6:8])&0xfff) << 48
|
|
||||||
t = Time(time)
|
|
||||||
}
|
|
||||||
return t
|
|
||||||
}
|
|
||||||
|
|
||||||
// ClockSequence returns the clock sequence encoded in uuid.
|
|
||||||
// The clock sequence is only well defined for version 1 and 2 UUIDs.
|
|
||||||
func (uuid UUID) ClockSequence() int {
|
|
||||||
return int(binary.BigEndian.Uint16(uuid[8:10])) & 0x3fff
|
|
||||||
}
|
|
||||||
43
vendor/github.com/google/uuid/util.go
generated
vendored
43
vendor/github.com/google/uuid/util.go
generated
vendored
@@ -1,43 +0,0 @@
|
|||||||
// Copyright 2016 Google Inc. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package uuid
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
)
|
|
||||||
|
|
||||||
// randomBits completely fills slice b with random data.
|
|
||||||
func randomBits(b []byte) {
|
|
||||||
if _, err := io.ReadFull(rander, b); err != nil {
|
|
||||||
panic(err.Error()) // rand should never fail
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// xvalues returns the value of a byte as a hexadecimal digit or 255.
|
|
||||||
var xvalues = [256]byte{
|
|
||||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
|
||||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
|
||||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
|
||||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 255, 255, 255, 255, 255, 255,
|
|
||||||
255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
|
||||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
|
||||||
255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
|
||||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
|
||||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
|
||||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
|
||||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
|
||||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
|
||||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
|
||||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
|
||||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
|
||||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
|
||||||
}
|
|
||||||
|
|
||||||
// xtob converts hex characters x1 and x2 into a byte.
|
|
||||||
func xtob(x1, x2 byte) (byte, bool) {
|
|
||||||
b1 := xvalues[x1]
|
|
||||||
b2 := xvalues[x2]
|
|
||||||
return (b1 << 4) | b2, b1 != 255 && b2 != 255
|
|
||||||
}
|
|
||||||
365
vendor/github.com/google/uuid/uuid.go
generated
vendored
365
vendor/github.com/google/uuid/uuid.go
generated
vendored
@@ -1,365 +0,0 @@
|
|||||||
// Copyright 2018 Google Inc. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package uuid
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"crypto/rand"
|
|
||||||
"encoding/hex"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
// A UUID is a 128 bit (16 byte) Universal Unique IDentifier as defined in RFC
|
|
||||||
// 4122.
|
|
||||||
type UUID [16]byte
|
|
||||||
|
|
||||||
// A Version represents a UUID's version.
|
|
||||||
type Version byte
|
|
||||||
|
|
||||||
// A Variant represents a UUID's variant.
|
|
||||||
type Variant byte
|
|
||||||
|
|
||||||
// Constants returned by Variant.
|
|
||||||
const (
|
|
||||||
Invalid = Variant(iota) // Invalid UUID
|
|
||||||
RFC4122 // The variant specified in RFC4122
|
|
||||||
Reserved // Reserved, NCS backward compatibility.
|
|
||||||
Microsoft // Reserved, Microsoft Corporation backward compatibility.
|
|
||||||
Future // Reserved for future definition.
|
|
||||||
)
|
|
||||||
|
|
||||||
const randPoolSize = 16 * 16
|
|
||||||
|
|
||||||
var (
|
|
||||||
rander = rand.Reader // random function
|
|
||||||
poolEnabled = false
|
|
||||||
poolMu sync.Mutex
|
|
||||||
poolPos = randPoolSize // protected with poolMu
|
|
||||||
pool [randPoolSize]byte // protected with poolMu
|
|
||||||
)
|
|
||||||
|
|
||||||
type invalidLengthError struct{ len int }
|
|
||||||
|
|
||||||
func (err invalidLengthError) Error() string {
|
|
||||||
return fmt.Sprintf("invalid UUID length: %d", err.len)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsInvalidLengthError is matcher function for custom error invalidLengthError
|
|
||||||
func IsInvalidLengthError(err error) bool {
|
|
||||||
_, ok := err.(invalidLengthError)
|
|
||||||
return ok
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse decodes s into a UUID or returns an error if it cannot be parsed. Both
|
|
||||||
// the standard UUID forms defined in RFC 4122
|
|
||||||
// (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx and
|
|
||||||
// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) are decoded. In addition,
|
|
||||||
// Parse accepts non-standard strings such as the raw hex encoding
|
|
||||||
// xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx and 38 byte "Microsoft style" encodings,
|
|
||||||
// e.g. {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}. Only the middle 36 bytes are
|
|
||||||
// examined in the latter case. Parse should not be used to validate strings as
|
|
||||||
// it parses non-standard encodings as indicated above.
|
|
||||||
func Parse(s string) (UUID, error) {
|
|
||||||
var uuid UUID
|
|
||||||
switch len(s) {
|
|
||||||
// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
|
||||||
case 36:
|
|
||||||
|
|
||||||
// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
|
||||||
case 36 + 9:
|
|
||||||
if !strings.EqualFold(s[:9], "urn:uuid:") {
|
|
||||||
return uuid, fmt.Errorf("invalid urn prefix: %q", s[:9])
|
|
||||||
}
|
|
||||||
s = s[9:]
|
|
||||||
|
|
||||||
// {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}
|
|
||||||
case 36 + 2:
|
|
||||||
s = s[1:]
|
|
||||||
|
|
||||||
// xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
|
||||||
case 32:
|
|
||||||
var ok bool
|
|
||||||
for i := range uuid {
|
|
||||||
uuid[i], ok = xtob(s[i*2], s[i*2+1])
|
|
||||||
if !ok {
|
|
||||||
return uuid, errors.New("invalid UUID format")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return uuid, nil
|
|
||||||
default:
|
|
||||||
return uuid, invalidLengthError{len(s)}
|
|
||||||
}
|
|
||||||
// s is now at least 36 bytes long
|
|
||||||
// it must be of the form xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
|
||||||
if s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-' {
|
|
||||||
return uuid, errors.New("invalid UUID format")
|
|
||||||
}
|
|
||||||
for i, x := range [16]int{
|
|
||||||
0, 2, 4, 6,
|
|
||||||
9, 11,
|
|
||||||
14, 16,
|
|
||||||
19, 21,
|
|
||||||
24, 26, 28, 30, 32, 34,
|
|
||||||
} {
|
|
||||||
v, ok := xtob(s[x], s[x+1])
|
|
||||||
if !ok {
|
|
||||||
return uuid, errors.New("invalid UUID format")
|
|
||||||
}
|
|
||||||
uuid[i] = v
|
|
||||||
}
|
|
||||||
return uuid, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseBytes is like Parse, except it parses a byte slice instead of a string.
|
|
||||||
func ParseBytes(b []byte) (UUID, error) {
|
|
||||||
var uuid UUID
|
|
||||||
switch len(b) {
|
|
||||||
case 36: // xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
|
||||||
case 36 + 9: // urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
|
||||||
if !bytes.EqualFold(b[:9], []byte("urn:uuid:")) {
|
|
||||||
return uuid, fmt.Errorf("invalid urn prefix: %q", b[:9])
|
|
||||||
}
|
|
||||||
b = b[9:]
|
|
||||||
case 36 + 2: // {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}
|
|
||||||
b = b[1:]
|
|
||||||
case 32: // xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
|
||||||
var ok bool
|
|
||||||
for i := 0; i < 32; i += 2 {
|
|
||||||
uuid[i/2], ok = xtob(b[i], b[i+1])
|
|
||||||
if !ok {
|
|
||||||
return uuid, errors.New("invalid UUID format")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return uuid, nil
|
|
||||||
default:
|
|
||||||
return uuid, invalidLengthError{len(b)}
|
|
||||||
}
|
|
||||||
// s is now at least 36 bytes long
|
|
||||||
// it must be of the form xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
|
||||||
if b[8] != '-' || b[13] != '-' || b[18] != '-' || b[23] != '-' {
|
|
||||||
return uuid, errors.New("invalid UUID format")
|
|
||||||
}
|
|
||||||
for i, x := range [16]int{
|
|
||||||
0, 2, 4, 6,
|
|
||||||
9, 11,
|
|
||||||
14, 16,
|
|
||||||
19, 21,
|
|
||||||
24, 26, 28, 30, 32, 34,
|
|
||||||
} {
|
|
||||||
v, ok := xtob(b[x], b[x+1])
|
|
||||||
if !ok {
|
|
||||||
return uuid, errors.New("invalid UUID format")
|
|
||||||
}
|
|
||||||
uuid[i] = v
|
|
||||||
}
|
|
||||||
return uuid, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MustParse is like Parse but panics if the string cannot be parsed.
|
|
||||||
// It simplifies safe initialization of global variables holding compiled UUIDs.
|
|
||||||
func MustParse(s string) UUID {
|
|
||||||
uuid, err := Parse(s)
|
|
||||||
if err != nil {
|
|
||||||
panic(`uuid: Parse(` + s + `): ` + err.Error())
|
|
||||||
}
|
|
||||||
return uuid
|
|
||||||
}
|
|
||||||
|
|
||||||
// FromBytes creates a new UUID from a byte slice. Returns an error if the slice
|
|
||||||
// does not have a length of 16. The bytes are copied from the slice.
|
|
||||||
func FromBytes(b []byte) (uuid UUID, err error) {
|
|
||||||
err = uuid.UnmarshalBinary(b)
|
|
||||||
return uuid, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Must returns uuid if err is nil and panics otherwise.
|
|
||||||
func Must(uuid UUID, err error) UUID {
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return uuid
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate returns an error if s is not a properly formatted UUID in one of the following formats:
|
|
||||||
// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
|
||||||
// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
|
||||||
// xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
|
||||||
// {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}
|
|
||||||
// It returns an error if the format is invalid, otherwise nil.
|
|
||||||
func Validate(s string) error {
|
|
||||||
switch len(s) {
|
|
||||||
// Standard UUID format
|
|
||||||
case 36:
|
|
||||||
|
|
||||||
// UUID with "urn:uuid:" prefix
|
|
||||||
case 36 + 9:
|
|
||||||
if !strings.EqualFold(s[:9], "urn:uuid:") {
|
|
||||||
return fmt.Errorf("invalid urn prefix: %q", s[:9])
|
|
||||||
}
|
|
||||||
s = s[9:]
|
|
||||||
|
|
||||||
// UUID enclosed in braces
|
|
||||||
case 36 + 2:
|
|
||||||
if s[0] != '{' || s[len(s)-1] != '}' {
|
|
||||||
return fmt.Errorf("invalid bracketed UUID format")
|
|
||||||
}
|
|
||||||
s = s[1 : len(s)-1]
|
|
||||||
|
|
||||||
// UUID without hyphens
|
|
||||||
case 32:
|
|
||||||
for i := 0; i < len(s); i += 2 {
|
|
||||||
_, ok := xtob(s[i], s[i+1])
|
|
||||||
if !ok {
|
|
||||||
return errors.New("invalid UUID format")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
return invalidLengthError{len(s)}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for standard UUID format
|
|
||||||
if len(s) == 36 {
|
|
||||||
if s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-' {
|
|
||||||
return errors.New("invalid UUID format")
|
|
||||||
}
|
|
||||||
for _, x := range []int{0, 2, 4, 6, 9, 11, 14, 16, 19, 21, 24, 26, 28, 30, 32, 34} {
|
|
||||||
if _, ok := xtob(s[x], s[x+1]); !ok {
|
|
||||||
return errors.New("invalid UUID format")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns the string form of uuid, xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
|
||||||
// , or "" if uuid is invalid.
|
|
||||||
func (uuid UUID) String() string {
|
|
||||||
var buf [36]byte
|
|
||||||
encodeHex(buf[:], uuid)
|
|
||||||
return string(buf[:])
|
|
||||||
}
|
|
||||||
|
|
||||||
// URN returns the RFC 2141 URN form of uuid,
|
|
||||||
// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx, or "" if uuid is invalid.
|
|
||||||
func (uuid UUID) URN() string {
|
|
||||||
var buf [36 + 9]byte
|
|
||||||
copy(buf[:], "urn:uuid:")
|
|
||||||
encodeHex(buf[9:], uuid)
|
|
||||||
return string(buf[:])
|
|
||||||
}
|
|
||||||
|
|
||||||
func encodeHex(dst []byte, uuid UUID) {
|
|
||||||
hex.Encode(dst, uuid[:4])
|
|
||||||
dst[8] = '-'
|
|
||||||
hex.Encode(dst[9:13], uuid[4:6])
|
|
||||||
dst[13] = '-'
|
|
||||||
hex.Encode(dst[14:18], uuid[6:8])
|
|
||||||
dst[18] = '-'
|
|
||||||
hex.Encode(dst[19:23], uuid[8:10])
|
|
||||||
dst[23] = '-'
|
|
||||||
hex.Encode(dst[24:], uuid[10:])
|
|
||||||
}
|
|
||||||
|
|
||||||
// Variant returns the variant encoded in uuid.
|
|
||||||
func (uuid UUID) Variant() Variant {
|
|
||||||
switch {
|
|
||||||
case (uuid[8] & 0xc0) == 0x80:
|
|
||||||
return RFC4122
|
|
||||||
case (uuid[8] & 0xe0) == 0xc0:
|
|
||||||
return Microsoft
|
|
||||||
case (uuid[8] & 0xe0) == 0xe0:
|
|
||||||
return Future
|
|
||||||
default:
|
|
||||||
return Reserved
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Version returns the version of uuid.
|
|
||||||
func (uuid UUID) Version() Version {
|
|
||||||
return Version(uuid[6] >> 4)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v Version) String() string {
|
|
||||||
if v > 15 {
|
|
||||||
return fmt.Sprintf("BAD_VERSION_%d", v)
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("VERSION_%d", v)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v Variant) String() string {
|
|
||||||
switch v {
|
|
||||||
case RFC4122:
|
|
||||||
return "RFC4122"
|
|
||||||
case Reserved:
|
|
||||||
return "Reserved"
|
|
||||||
case Microsoft:
|
|
||||||
return "Microsoft"
|
|
||||||
case Future:
|
|
||||||
return "Future"
|
|
||||||
case Invalid:
|
|
||||||
return "Invalid"
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("BadVariant%d", int(v))
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetRand sets the random number generator to r, which implements io.Reader.
|
|
||||||
// If r.Read returns an error when the package requests random data then
|
|
||||||
// a panic will be issued.
|
|
||||||
//
|
|
||||||
// Calling SetRand with nil sets the random number generator to the default
|
|
||||||
// generator.
|
|
||||||
func SetRand(r io.Reader) {
|
|
||||||
if r == nil {
|
|
||||||
rander = rand.Reader
|
|
||||||
return
|
|
||||||
}
|
|
||||||
rander = r
|
|
||||||
}
|
|
||||||
|
|
||||||
// EnableRandPool enables internal randomness pool used for Random
|
|
||||||
// (Version 4) UUID generation. The pool contains random bytes read from
|
|
||||||
// the random number generator on demand in batches. Enabling the pool
|
|
||||||
// may improve the UUID generation throughput significantly.
|
|
||||||
//
|
|
||||||
// Since the pool is stored on the Go heap, this feature may be a bad fit
|
|
||||||
// for security sensitive applications.
|
|
||||||
//
|
|
||||||
// Both EnableRandPool and DisableRandPool are not thread-safe and should
|
|
||||||
// only be called when there is no possibility that New or any other
|
|
||||||
// UUID Version 4 generation function will be called concurrently.
|
|
||||||
func EnableRandPool() {
|
|
||||||
poolEnabled = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// DisableRandPool disables the randomness pool if it was previously
|
|
||||||
// enabled with EnableRandPool.
|
|
||||||
//
|
|
||||||
// Both EnableRandPool and DisableRandPool are not thread-safe and should
|
|
||||||
// only be called when there is no possibility that New or any other
|
|
||||||
// UUID Version 4 generation function will be called concurrently.
|
|
||||||
func DisableRandPool() {
|
|
||||||
poolEnabled = false
|
|
||||||
defer poolMu.Unlock()
|
|
||||||
poolMu.Lock()
|
|
||||||
poolPos = randPoolSize
|
|
||||||
}
|
|
||||||
|
|
||||||
// UUIDs is a slice of UUID types.
|
|
||||||
type UUIDs []UUID
|
|
||||||
|
|
||||||
// Strings returns a string slice containing the string form of each UUID in uuids.
|
|
||||||
func (uuids UUIDs) Strings() []string {
|
|
||||||
var uuidStrs = make([]string, len(uuids))
|
|
||||||
for i, uuid := range uuids {
|
|
||||||
uuidStrs[i] = uuid.String()
|
|
||||||
}
|
|
||||||
return uuidStrs
|
|
||||||
}
|
|
||||||
44
vendor/github.com/google/uuid/version1.go
generated
vendored
44
vendor/github.com/google/uuid/version1.go
generated
vendored
@@ -1,44 +0,0 @@
|
|||||||
// Copyright 2016 Google Inc. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package uuid
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
)
|
|
||||||
|
|
||||||
// NewUUID returns a Version 1 UUID based on the current NodeID and clock
|
|
||||||
// sequence, and the current time. If the NodeID has not been set by SetNodeID
|
|
||||||
// or SetNodeInterface then it will be set automatically. If the NodeID cannot
|
|
||||||
// be set NewUUID returns nil. If clock sequence has not been set by
|
|
||||||
// SetClockSequence then it will be set automatically. If GetTime fails to
|
|
||||||
// return the current NewUUID returns nil and an error.
|
|
||||||
//
|
|
||||||
// In most cases, New should be used.
|
|
||||||
func NewUUID() (UUID, error) {
|
|
||||||
var uuid UUID
|
|
||||||
now, seq, err := GetTime()
|
|
||||||
if err != nil {
|
|
||||||
return uuid, err
|
|
||||||
}
|
|
||||||
|
|
||||||
timeLow := uint32(now & 0xffffffff)
|
|
||||||
timeMid := uint16((now >> 32) & 0xffff)
|
|
||||||
timeHi := uint16((now >> 48) & 0x0fff)
|
|
||||||
timeHi |= 0x1000 // Version 1
|
|
||||||
|
|
||||||
binary.BigEndian.PutUint32(uuid[0:], timeLow)
|
|
||||||
binary.BigEndian.PutUint16(uuid[4:], timeMid)
|
|
||||||
binary.BigEndian.PutUint16(uuid[6:], timeHi)
|
|
||||||
binary.BigEndian.PutUint16(uuid[8:], seq)
|
|
||||||
|
|
||||||
nodeMu.Lock()
|
|
||||||
if nodeID == zeroID {
|
|
||||||
setNodeInterface("")
|
|
||||||
}
|
|
||||||
copy(uuid[10:], nodeID[:])
|
|
||||||
nodeMu.Unlock()
|
|
||||||
|
|
||||||
return uuid, nil
|
|
||||||
}
|
|
||||||
76
vendor/github.com/google/uuid/version4.go
generated
vendored
76
vendor/github.com/google/uuid/version4.go
generated
vendored
@@ -1,76 +0,0 @@
|
|||||||
// Copyright 2016 Google Inc. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package uuid
|
|
||||||
|
|
||||||
import "io"
|
|
||||||
|
|
||||||
// New creates a new random UUID or panics. New is equivalent to
|
|
||||||
// the expression
|
|
||||||
//
|
|
||||||
// uuid.Must(uuid.NewRandom())
|
|
||||||
func New() UUID {
|
|
||||||
return Must(NewRandom())
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewString creates a new random UUID and returns it as a string or panics.
|
|
||||||
// NewString is equivalent to the expression
|
|
||||||
//
|
|
||||||
// uuid.New().String()
|
|
||||||
func NewString() string {
|
|
||||||
return Must(NewRandom()).String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewRandom returns a Random (Version 4) UUID.
|
|
||||||
//
|
|
||||||
// The strength of the UUIDs is based on the strength of the crypto/rand
|
|
||||||
// package.
|
|
||||||
//
|
|
||||||
// Uses the randomness pool if it was enabled with EnableRandPool.
|
|
||||||
//
|
|
||||||
// A note about uniqueness derived from the UUID Wikipedia entry:
|
|
||||||
//
|
|
||||||
// Randomly generated UUIDs have 122 random bits. One's annual risk of being
|
|
||||||
// hit by a meteorite is estimated to be one chance in 17 billion, that
|
|
||||||
// means the probability is about 0.00000000006 (6 × 10−11),
|
|
||||||
// equivalent to the odds of creating a few tens of trillions of UUIDs in a
|
|
||||||
// year and having one duplicate.
|
|
||||||
func NewRandom() (UUID, error) {
|
|
||||||
if !poolEnabled {
|
|
||||||
return NewRandomFromReader(rander)
|
|
||||||
}
|
|
||||||
return newRandomFromPool()
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewRandomFromReader returns a UUID based on bytes read from a given io.Reader.
|
|
||||||
func NewRandomFromReader(r io.Reader) (UUID, error) {
|
|
||||||
var uuid UUID
|
|
||||||
_, err := io.ReadFull(r, uuid[:])
|
|
||||||
if err != nil {
|
|
||||||
return Nil, err
|
|
||||||
}
|
|
||||||
uuid[6] = (uuid[6] & 0x0f) | 0x40 // Version 4
|
|
||||||
uuid[8] = (uuid[8] & 0x3f) | 0x80 // Variant is 10
|
|
||||||
return uuid, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func newRandomFromPool() (UUID, error) {
|
|
||||||
var uuid UUID
|
|
||||||
poolMu.Lock()
|
|
||||||
if poolPos == randPoolSize {
|
|
||||||
_, err := io.ReadFull(rander, pool[:])
|
|
||||||
if err != nil {
|
|
||||||
poolMu.Unlock()
|
|
||||||
return Nil, err
|
|
||||||
}
|
|
||||||
poolPos = 0
|
|
||||||
}
|
|
||||||
copy(uuid[:], pool[poolPos:(poolPos+16)])
|
|
||||||
poolPos += 16
|
|
||||||
poolMu.Unlock()
|
|
||||||
|
|
||||||
uuid[6] = (uuid[6] & 0x0f) | 0x40 // Version 4
|
|
||||||
uuid[8] = (uuid[8] & 0x3f) | 0x80 // Variant is 10
|
|
||||||
return uuid, nil
|
|
||||||
}
|
|
||||||
56
vendor/github.com/google/uuid/version6.go
generated
vendored
56
vendor/github.com/google/uuid/version6.go
generated
vendored
@@ -1,56 +0,0 @@
|
|||||||
// Copyright 2023 Google Inc. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package uuid
|
|
||||||
|
|
||||||
import "encoding/binary"
|
|
||||||
|
|
||||||
// UUID version 6 is a field-compatible version of UUIDv1, reordered for improved DB locality.
|
|
||||||
// It is expected that UUIDv6 will primarily be used in contexts where there are existing v1 UUIDs.
|
|
||||||
// Systems that do not involve legacy UUIDv1 SHOULD consider using UUIDv7 instead.
|
|
||||||
//
|
|
||||||
// see https://datatracker.ietf.org/doc/html/draft-peabody-dispatch-new-uuid-format-03#uuidv6
|
|
||||||
//
|
|
||||||
// NewV6 returns a Version 6 UUID based on the current NodeID and clock
|
|
||||||
// sequence, and the current time. If the NodeID has not been set by SetNodeID
|
|
||||||
// or SetNodeInterface then it will be set automatically. If the NodeID cannot
|
|
||||||
// be set NewV6 set NodeID is random bits automatically . If clock sequence has not been set by
|
|
||||||
// SetClockSequence then it will be set automatically. If GetTime fails to
|
|
||||||
// return the current NewV6 returns Nil and an error.
|
|
||||||
func NewV6() (UUID, error) {
|
|
||||||
var uuid UUID
|
|
||||||
now, seq, err := GetTime()
|
|
||||||
if err != nil {
|
|
||||||
return uuid, err
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
0 1 2 3
|
|
||||||
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
|
||||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
||||||
| time_high |
|
|
||||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
||||||
| time_mid | time_low_and_version |
|
|
||||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
||||||
|clk_seq_hi_res | clk_seq_low | node (0-1) |
|
|
||||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
||||||
| node (2-5) |
|
|
||||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
||||||
*/
|
|
||||||
|
|
||||||
binary.BigEndian.PutUint64(uuid[0:], uint64(now))
|
|
||||||
binary.BigEndian.PutUint16(uuid[8:], seq)
|
|
||||||
|
|
||||||
uuid[6] = 0x60 | (uuid[6] & 0x0F)
|
|
||||||
uuid[8] = 0x80 | (uuid[8] & 0x3F)
|
|
||||||
|
|
||||||
nodeMu.Lock()
|
|
||||||
if nodeID == zeroID {
|
|
||||||
setNodeInterface("")
|
|
||||||
}
|
|
||||||
copy(uuid[10:], nodeID[:])
|
|
||||||
nodeMu.Unlock()
|
|
||||||
|
|
||||||
return uuid, nil
|
|
||||||
}
|
|
||||||
104
vendor/github.com/google/uuid/version7.go
generated
vendored
104
vendor/github.com/google/uuid/version7.go
generated
vendored
@@ -1,104 +0,0 @@
|
|||||||
// Copyright 2023 Google Inc. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package uuid
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
)
|
|
||||||
|
|
||||||
// UUID version 7 features a time-ordered value field derived from the widely
|
|
||||||
// implemented and well known Unix Epoch timestamp source,
|
|
||||||
// the number of milliseconds seconds since midnight 1 Jan 1970 UTC, leap seconds excluded.
|
|
||||||
// As well as improved entropy characteristics over versions 1 or 6.
|
|
||||||
//
|
|
||||||
// see https://datatracker.ietf.org/doc/html/draft-peabody-dispatch-new-uuid-format-03#name-uuid-version-7
|
|
||||||
//
|
|
||||||
// Implementations SHOULD utilize UUID version 7 over UUID version 1 and 6 if possible.
|
|
||||||
//
|
|
||||||
// NewV7 returns a Version 7 UUID based on the current time(Unix Epoch).
|
|
||||||
// Uses the randomness pool if it was enabled with EnableRandPool.
|
|
||||||
// On error, NewV7 returns Nil and an error
|
|
||||||
func NewV7() (UUID, error) {
|
|
||||||
uuid, err := NewRandom()
|
|
||||||
if err != nil {
|
|
||||||
return uuid, err
|
|
||||||
}
|
|
||||||
makeV7(uuid[:])
|
|
||||||
return uuid, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewV7FromReader returns a Version 7 UUID based on the current time(Unix Epoch).
|
|
||||||
// it use NewRandomFromReader fill random bits.
|
|
||||||
// On error, NewV7FromReader returns Nil and an error.
|
|
||||||
func NewV7FromReader(r io.Reader) (UUID, error) {
|
|
||||||
uuid, err := NewRandomFromReader(r)
|
|
||||||
if err != nil {
|
|
||||||
return uuid, err
|
|
||||||
}
|
|
||||||
|
|
||||||
makeV7(uuid[:])
|
|
||||||
return uuid, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// makeV7 fill 48 bits time (uuid[0] - uuid[5]), set version b0111 (uuid[6])
|
|
||||||
// uuid[8] already has the right version number (Variant is 10)
|
|
||||||
// see function NewV7 and NewV7FromReader
|
|
||||||
func makeV7(uuid []byte) {
|
|
||||||
/*
|
|
||||||
0 1 2 3
|
|
||||||
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
|
||||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
||||||
| unix_ts_ms |
|
|
||||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
||||||
| unix_ts_ms | ver | rand_a (12 bit seq) |
|
|
||||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
||||||
|var| rand_b |
|
|
||||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
||||||
| rand_b |
|
|
||||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
||||||
*/
|
|
||||||
_ = uuid[15] // bounds check
|
|
||||||
|
|
||||||
t, s := getV7Time()
|
|
||||||
|
|
||||||
uuid[0] = byte(t >> 40)
|
|
||||||
uuid[1] = byte(t >> 32)
|
|
||||||
uuid[2] = byte(t >> 24)
|
|
||||||
uuid[3] = byte(t >> 16)
|
|
||||||
uuid[4] = byte(t >> 8)
|
|
||||||
uuid[5] = byte(t)
|
|
||||||
|
|
||||||
uuid[6] = 0x70 | (0x0F & byte(s>>8))
|
|
||||||
uuid[7] = byte(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
// lastV7time is the last time we returned stored as:
|
|
||||||
//
|
|
||||||
// 52 bits of time in milliseconds since epoch
|
|
||||||
// 12 bits of (fractional nanoseconds) >> 8
|
|
||||||
var lastV7time int64
|
|
||||||
|
|
||||||
const nanoPerMilli = 1000000
|
|
||||||
|
|
||||||
// getV7Time returns the time in milliseconds and nanoseconds / 256.
|
|
||||||
// The returned (milli << 12 + seq) is guarenteed to be greater than
|
|
||||||
// (milli << 12 + seq) returned by any previous call to getV7Time.
|
|
||||||
func getV7Time() (milli, seq int64) {
|
|
||||||
timeMu.Lock()
|
|
||||||
defer timeMu.Unlock()
|
|
||||||
|
|
||||||
nano := timeNow().UnixNano()
|
|
||||||
milli = nano / nanoPerMilli
|
|
||||||
// Sequence number is between 0 and 3906 (nanoPerMilli>>8)
|
|
||||||
seq = (nano - milli*nanoPerMilli) >> 8
|
|
||||||
now := milli<<12 + seq
|
|
||||||
if now <= lastV7time {
|
|
||||||
now = lastV7time + 1
|
|
||||||
milli = now >> 12
|
|
||||||
seq = now & 0xfff
|
|
||||||
}
|
|
||||||
lastV7time = now
|
|
||||||
return milli, seq
|
|
||||||
}
|
|
||||||
27
vendor/golang.org/x/text/LICENSE
generated
vendored
27
vendor/golang.org/x/text/LICENSE
generated
vendored
@@ -1,27 +0,0 @@
|
|||||||
Copyright 2009 The Go Authors.
|
|
||||||
|
|
||||||
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 LLC 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.
|
|
||||||
22
vendor/golang.org/x/text/PATENTS
generated
vendored
22
vendor/golang.org/x/text/PATENTS
generated
vendored
@@ -1,22 +0,0 @@
|
|||||||
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.
|
|
||||||
67
vendor/golang.org/x/text/currency/common.go
generated
vendored
67
vendor/golang.org/x/text/currency/common.go
generated
vendored
@@ -1,67 +0,0 @@
|
|||||||
// Code generated by running "go generate" in golang.org/x/text. DO NOT EDIT.
|
|
||||||
|
|
||||||
package currency
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"golang.org/x/text/language"
|
|
||||||
)
|
|
||||||
|
|
||||||
// This file contains code common to gen.go and the package code.
|
|
||||||
|
|
||||||
const (
|
|
||||||
cashShift = 3
|
|
||||||
roundMask = 0x7
|
|
||||||
|
|
||||||
nonTenderBit = 0x8000
|
|
||||||
)
|
|
||||||
|
|
||||||
// currencyInfo contains information about a currency.
|
|
||||||
// bits 0..2: index into roundings for standard rounding
|
|
||||||
// bits 3..5: index into roundings for cash rounding
|
|
||||||
type currencyInfo byte
|
|
||||||
|
|
||||||
// roundingType defines the scale (number of fractional decimals) and increments
|
|
||||||
// in terms of units of size 10^-scale. For example, for scale == 2 and
|
|
||||||
// increment == 1, the currency is rounded to units of 0.01.
|
|
||||||
type roundingType struct {
|
|
||||||
scale, increment uint8
|
|
||||||
}
|
|
||||||
|
|
||||||
// roundings contains rounding data for currencies. This struct is
|
|
||||||
// created by hand as it is very unlikely to change much.
|
|
||||||
var roundings = [...]roundingType{
|
|
||||||
{2, 1}, // default
|
|
||||||
{0, 1},
|
|
||||||
{1, 1},
|
|
||||||
{3, 1},
|
|
||||||
{4, 1},
|
|
||||||
{2, 5}, // cash rounding alternative
|
|
||||||
{2, 50},
|
|
||||||
}
|
|
||||||
|
|
||||||
// regionToCode returns a 16-bit region code. Only two-letter codes are
|
|
||||||
// supported. (Three-letter codes are not needed.)
|
|
||||||
func regionToCode(r language.Region) uint16 {
|
|
||||||
if s := r.String(); len(s) == 2 {
|
|
||||||
return uint16(s[0])<<8 | uint16(s[1])
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func toDate(t time.Time) uint32 {
|
|
||||||
y := t.Year()
|
|
||||||
if y == 1 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
date := uint32(y) << 4
|
|
||||||
date |= uint32(t.Month())
|
|
||||||
date <<= 5
|
|
||||||
date |= uint32(t.Day())
|
|
||||||
return date
|
|
||||||
}
|
|
||||||
|
|
||||||
func fromDate(date uint32) time.Time {
|
|
||||||
return time.Date(int(date>>9), time.Month((date>>5)&0xf), int(date&0x1f), 0, 0, 0, 0, time.UTC)
|
|
||||||
}
|
|
||||||
185
vendor/golang.org/x/text/currency/currency.go
generated
vendored
185
vendor/golang.org/x/text/currency/currency.go
generated
vendored
@@ -1,185 +0,0 @@
|
|||||||
// Copyright 2015 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.
|
|
||||||
|
|
||||||
//go:generate go run gen.go gen_common.go -output tables.go
|
|
||||||
|
|
||||||
// Package currency contains currency-related functionality.
|
|
||||||
//
|
|
||||||
// NOTE: the formatting functionality is currently under development and may
|
|
||||||
// change without notice.
|
|
||||||
package currency // import "golang.org/x/text/currency"
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"sort"
|
|
||||||
|
|
||||||
"golang.org/x/text/internal/tag"
|
|
||||||
"golang.org/x/text/language"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TODO:
|
|
||||||
// - language-specific currency names.
|
|
||||||
// - currency formatting.
|
|
||||||
// - currency information per region
|
|
||||||
// - register currency code (there are no private use area)
|
|
||||||
|
|
||||||
// TODO: remove Currency type from package language.
|
|
||||||
|
|
||||||
// Kind determines the rounding and rendering properties of a currency value.
|
|
||||||
type Kind struct {
|
|
||||||
rounding rounding
|
|
||||||
// TODO: formatting type: standard, accounting. See CLDR.
|
|
||||||
}
|
|
||||||
|
|
||||||
type rounding byte
|
|
||||||
|
|
||||||
const (
|
|
||||||
standard rounding = iota
|
|
||||||
cash
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// Standard defines standard rounding and formatting for currencies.
|
|
||||||
Standard Kind = Kind{rounding: standard}
|
|
||||||
|
|
||||||
// Cash defines rounding and formatting standards for cash transactions.
|
|
||||||
Cash Kind = Kind{rounding: cash}
|
|
||||||
|
|
||||||
// Accounting defines rounding and formatting standards for accounting.
|
|
||||||
Accounting Kind = Kind{rounding: standard}
|
|
||||||
)
|
|
||||||
|
|
||||||
// Rounding reports the rounding characteristics for the given currency, where
|
|
||||||
// scale is the number of fractional decimals and increment is the number of
|
|
||||||
// units in terms of 10^(-scale) to which to round to.
|
|
||||||
func (k Kind) Rounding(cur Unit) (scale, increment int) {
|
|
||||||
info := currency.Elem(int(cur.index))[3]
|
|
||||||
switch k.rounding {
|
|
||||||
case standard:
|
|
||||||
info &= roundMask
|
|
||||||
case cash:
|
|
||||||
info >>= cashShift
|
|
||||||
}
|
|
||||||
return int(roundings[info].scale), int(roundings[info].increment)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unit is an ISO 4217 currency designator.
|
|
||||||
type Unit struct {
|
|
||||||
index uint16
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns the ISO code of u.
|
|
||||||
func (u Unit) String() string {
|
|
||||||
if u.index == 0 {
|
|
||||||
return "XXX"
|
|
||||||
}
|
|
||||||
return currency.Elem(int(u.index))[:3]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Amount creates an Amount for the given currency unit and amount.
|
|
||||||
func (u Unit) Amount(amount interface{}) Amount {
|
|
||||||
// TODO: verify amount is a supported number type
|
|
||||||
return Amount{amount: amount, currency: u}
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
errSyntax = errors.New("currency: tag is not well-formed")
|
|
||||||
errValue = errors.New("currency: tag is not a recognized currency")
|
|
||||||
)
|
|
||||||
|
|
||||||
// ParseISO parses a 3-letter ISO 4217 currency code. It returns an error if s
|
|
||||||
// is not well-formed or not a recognized currency code.
|
|
||||||
func ParseISO(s string) (Unit, error) {
|
|
||||||
var buf [4]byte // Take one byte more to detect oversize keys.
|
|
||||||
key := buf[:copy(buf[:], s)]
|
|
||||||
if !tag.FixCase("XXX", key) {
|
|
||||||
return Unit{}, errSyntax
|
|
||||||
}
|
|
||||||
if i := currency.Index(key); i >= 0 {
|
|
||||||
if i == xxx {
|
|
||||||
return Unit{}, nil
|
|
||||||
}
|
|
||||||
return Unit{uint16(i)}, nil
|
|
||||||
}
|
|
||||||
return Unit{}, errValue
|
|
||||||
}
|
|
||||||
|
|
||||||
// MustParseISO is like ParseISO, but panics if the given currency unit
|
|
||||||
// cannot be parsed. It simplifies safe initialization of Unit values.
|
|
||||||
func MustParseISO(s string) Unit {
|
|
||||||
c, err := ParseISO(s)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
// FromRegion reports the currency unit that is currently legal tender in the
|
|
||||||
// given region according to CLDR. It will return false if region currently does
|
|
||||||
// not have a legal tender.
|
|
||||||
func FromRegion(r language.Region) (currency Unit, ok bool) {
|
|
||||||
x := regionToCode(r)
|
|
||||||
i := sort.Search(len(regionToCurrency), func(i int) bool {
|
|
||||||
return regionToCurrency[i].region >= x
|
|
||||||
})
|
|
||||||
if i < len(regionToCurrency) && regionToCurrency[i].region == x {
|
|
||||||
return Unit{regionToCurrency[i].code}, true
|
|
||||||
}
|
|
||||||
return Unit{}, false
|
|
||||||
}
|
|
||||||
|
|
||||||
// FromTag reports the most likely currency for the given tag. It considers the
|
|
||||||
// currency defined in the -u extension and infers the region if necessary.
|
|
||||||
func FromTag(t language.Tag) (Unit, language.Confidence) {
|
|
||||||
if cur := t.TypeForKey("cu"); len(cur) == 3 {
|
|
||||||
c, _ := ParseISO(cur)
|
|
||||||
return c, language.Exact
|
|
||||||
}
|
|
||||||
r, conf := t.Region()
|
|
||||||
if cur, ok := FromRegion(r); ok {
|
|
||||||
return cur, conf
|
|
||||||
}
|
|
||||||
return Unit{}, language.No
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
// Undefined and testing.
|
|
||||||
XXX Unit = Unit{}
|
|
||||||
XTS Unit = Unit{xts}
|
|
||||||
|
|
||||||
// G10 currencies https://en.wikipedia.org/wiki/G10_currencies.
|
|
||||||
USD Unit = Unit{usd}
|
|
||||||
EUR Unit = Unit{eur}
|
|
||||||
JPY Unit = Unit{jpy}
|
|
||||||
GBP Unit = Unit{gbp}
|
|
||||||
CHF Unit = Unit{chf}
|
|
||||||
AUD Unit = Unit{aud}
|
|
||||||
NZD Unit = Unit{nzd}
|
|
||||||
CAD Unit = Unit{cad}
|
|
||||||
SEK Unit = Unit{sek}
|
|
||||||
NOK Unit = Unit{nok}
|
|
||||||
|
|
||||||
// Additional common currencies as defined by CLDR.
|
|
||||||
BRL Unit = Unit{brl}
|
|
||||||
CNY Unit = Unit{cny}
|
|
||||||
DKK Unit = Unit{dkk}
|
|
||||||
INR Unit = Unit{inr}
|
|
||||||
RUB Unit = Unit{rub}
|
|
||||||
HKD Unit = Unit{hkd}
|
|
||||||
IDR Unit = Unit{idr}
|
|
||||||
KRW Unit = Unit{krw}
|
|
||||||
MXN Unit = Unit{mxn}
|
|
||||||
PLN Unit = Unit{pln}
|
|
||||||
SAR Unit = Unit{sar}
|
|
||||||
THB Unit = Unit{thb}
|
|
||||||
TRY Unit = Unit{try}
|
|
||||||
TWD Unit = Unit{twd}
|
|
||||||
ZAR Unit = Unit{zar}
|
|
||||||
|
|
||||||
// Precious metals.
|
|
||||||
XAG Unit = Unit{xag}
|
|
||||||
XAU Unit = Unit{xau}
|
|
||||||
XPT Unit = Unit{xpt}
|
|
||||||
XPD Unit = Unit{xpd}
|
|
||||||
)
|
|
||||||
220
vendor/golang.org/x/text/currency/format.go
generated
vendored
220
vendor/golang.org/x/text/currency/format.go
generated
vendored
@@ -1,220 +0,0 @@
|
|||||||
// Copyright 2015 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 currency
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"sort"
|
|
||||||
|
|
||||||
"golang.org/x/text/internal/format"
|
|
||||||
"golang.org/x/text/internal/language/compact"
|
|
||||||
"golang.org/x/text/internal/number"
|
|
||||||
|
|
||||||
"golang.org/x/text/language"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Amount is an amount-currency unit pair.
|
|
||||||
type Amount struct {
|
|
||||||
amount interface{} // Change to decimal(64|128).
|
|
||||||
currency Unit
|
|
||||||
}
|
|
||||||
|
|
||||||
// Currency reports the currency unit of this amount.
|
|
||||||
func (a Amount) Currency() Unit { return a.currency }
|
|
||||||
|
|
||||||
// TODO: based on decimal type, but may make sense to customize a bit.
|
|
||||||
// func (a Amount) Decimal()
|
|
||||||
// func (a Amount) Int() (int64, error)
|
|
||||||
// func (a Amount) Fraction() (int64, error)
|
|
||||||
// func (a Amount) Rat() *big.Rat
|
|
||||||
// func (a Amount) Float() (float64, error)
|
|
||||||
// func (a Amount) Scale() uint
|
|
||||||
// func (a Amount) Precision() uint
|
|
||||||
// func (a Amount) Sign() int
|
|
||||||
//
|
|
||||||
// Add/Sub/Div/Mul/Round.
|
|
||||||
|
|
||||||
// Format implements fmt.Formatter. It accepts format.State for
|
|
||||||
// language-specific rendering.
|
|
||||||
func (a Amount) Format(s fmt.State, verb rune) {
|
|
||||||
v := formattedValue{
|
|
||||||
currency: a.currency,
|
|
||||||
amount: a.amount,
|
|
||||||
format: defaultFormat,
|
|
||||||
}
|
|
||||||
v.Format(s, verb)
|
|
||||||
}
|
|
||||||
|
|
||||||
// formattedValue is currency amount or unit that implements language-sensitive
|
|
||||||
// formatting.
|
|
||||||
type formattedValue struct {
|
|
||||||
currency Unit
|
|
||||||
amount interface{} // Amount, Unit, or number.
|
|
||||||
format *options
|
|
||||||
}
|
|
||||||
|
|
||||||
// Format implements fmt.Formatter. It accepts format.State for
|
|
||||||
// language-specific rendering.
|
|
||||||
func (v formattedValue) Format(s fmt.State, verb rune) {
|
|
||||||
var tag language.Tag
|
|
||||||
var lang compact.ID
|
|
||||||
if state, ok := s.(format.State); ok {
|
|
||||||
tag = state.Language()
|
|
||||||
lang, _ = compact.RegionalID(compact.Tag(tag))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the options. Use DefaultFormat if not present.
|
|
||||||
opt := v.format
|
|
||||||
if opt == nil {
|
|
||||||
opt = defaultFormat
|
|
||||||
}
|
|
||||||
cur := v.currency
|
|
||||||
if cur.index == 0 {
|
|
||||||
cur = opt.currency
|
|
||||||
}
|
|
||||||
|
|
||||||
sym := opt.symbol(lang, cur)
|
|
||||||
if v.amount != nil {
|
|
||||||
var f number.Formatter
|
|
||||||
f.InitDecimal(tag)
|
|
||||||
|
|
||||||
scale, increment := opt.kind.Rounding(cur)
|
|
||||||
f.RoundingContext.SetScale(scale)
|
|
||||||
f.RoundingContext.Increment = uint32(increment)
|
|
||||||
f.RoundingContext.IncrementScale = uint8(scale)
|
|
||||||
f.RoundingContext.Mode = number.ToNearestAway
|
|
||||||
|
|
||||||
d := f.Append(nil, v.amount)
|
|
||||||
|
|
||||||
fmt.Fprint(s, sym, " ", string(d))
|
|
||||||
} else {
|
|
||||||
fmt.Fprint(s, sym)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Formatter decorates a given number, Unit or Amount with formatting options.
|
|
||||||
type Formatter func(amount interface{}) formattedValue
|
|
||||||
|
|
||||||
// func (f Formatter) Options(opts ...Option) Formatter
|
|
||||||
|
|
||||||
// TODO: call this a Formatter or FormatFunc?
|
|
||||||
|
|
||||||
var dummy = USD.Amount(0)
|
|
||||||
|
|
||||||
// adjust creates a new Formatter based on the adjustments of fn on f.
|
|
||||||
func (f Formatter) adjust(fn func(*options)) Formatter {
|
|
||||||
var o options = *(f(dummy).format)
|
|
||||||
fn(&o)
|
|
||||||
return o.format
|
|
||||||
}
|
|
||||||
|
|
||||||
// Default creates a new Formatter that defaults to currency unit c if a numeric
|
|
||||||
// value is passed that is not associated with a currency.
|
|
||||||
func (f Formatter) Default(currency Unit) Formatter {
|
|
||||||
return f.adjust(func(o *options) { o.currency = currency })
|
|
||||||
}
|
|
||||||
|
|
||||||
// Kind sets the kind of the underlying currency unit.
|
|
||||||
func (f Formatter) Kind(k Kind) Formatter {
|
|
||||||
return f.adjust(func(o *options) { o.kind = k })
|
|
||||||
}
|
|
||||||
|
|
||||||
var defaultFormat *options = ISO(dummy).format
|
|
||||||
|
|
||||||
var (
|
|
||||||
// Uses Narrow symbols. Overrides Symbol, if present.
|
|
||||||
NarrowSymbol Formatter = Formatter(formNarrow)
|
|
||||||
|
|
||||||
// Use Symbols instead of ISO codes, when available.
|
|
||||||
Symbol Formatter = Formatter(formSymbol)
|
|
||||||
|
|
||||||
// Use ISO code as symbol.
|
|
||||||
ISO Formatter = Formatter(formISO)
|
|
||||||
|
|
||||||
// TODO:
|
|
||||||
// // Use full name as symbol.
|
|
||||||
// Name Formatter
|
|
||||||
)
|
|
||||||
|
|
||||||
// options configures rendering and rounding options for an Amount.
|
|
||||||
type options struct {
|
|
||||||
currency Unit
|
|
||||||
kind Kind
|
|
||||||
|
|
||||||
symbol func(compactIndex compact.ID, c Unit) string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *options) format(amount interface{}) formattedValue {
|
|
||||||
v := formattedValue{format: o}
|
|
||||||
switch x := amount.(type) {
|
|
||||||
case Amount:
|
|
||||||
v.amount = x.amount
|
|
||||||
v.currency = x.currency
|
|
||||||
case *Amount:
|
|
||||||
v.amount = x.amount
|
|
||||||
v.currency = x.currency
|
|
||||||
case Unit:
|
|
||||||
v.currency = x
|
|
||||||
case *Unit:
|
|
||||||
v.currency = *x
|
|
||||||
default:
|
|
||||||
if o.currency.index == 0 {
|
|
||||||
panic("cannot format number without a currency being set")
|
|
||||||
}
|
|
||||||
// TODO: Must be a number.
|
|
||||||
v.amount = x
|
|
||||||
v.currency = o.currency
|
|
||||||
}
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
optISO = options{symbol: lookupISO}
|
|
||||||
optSymbol = options{symbol: lookupSymbol}
|
|
||||||
optNarrow = options{symbol: lookupNarrow}
|
|
||||||
)
|
|
||||||
|
|
||||||
// These need to be functions, rather than curried methods, as curried methods
|
|
||||||
// are evaluated at init time, causing tables to be included unconditionally.
|
|
||||||
func formISO(x interface{}) formattedValue { return optISO.format(x) }
|
|
||||||
func formSymbol(x interface{}) formattedValue { return optSymbol.format(x) }
|
|
||||||
func formNarrow(x interface{}) formattedValue { return optNarrow.format(x) }
|
|
||||||
|
|
||||||
func lookupISO(x compact.ID, c Unit) string { return c.String() }
|
|
||||||
func lookupSymbol(x compact.ID, c Unit) string { return normalSymbol.lookup(x, c) }
|
|
||||||
func lookupNarrow(x compact.ID, c Unit) string { return narrowSymbol.lookup(x, c) }
|
|
||||||
|
|
||||||
type symbolIndex struct {
|
|
||||||
index []uint16 // position corresponds with compact index of language.
|
|
||||||
data []curToIndex
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
normalSymbol = symbolIndex{normalLangIndex, normalSymIndex}
|
|
||||||
narrowSymbol = symbolIndex{narrowLangIndex, narrowSymIndex}
|
|
||||||
)
|
|
||||||
|
|
||||||
func (x *symbolIndex) lookup(lang compact.ID, c Unit) string {
|
|
||||||
for {
|
|
||||||
index := x.data[x.index[lang]:x.index[lang+1]]
|
|
||||||
i := sort.Search(len(index), func(i int) bool {
|
|
||||||
return index[i].cur >= c.index
|
|
||||||
})
|
|
||||||
if i < len(index) && index[i].cur == c.index {
|
|
||||||
x := index[i].idx
|
|
||||||
start := x + 1
|
|
||||||
end := start + uint16(symbols[x])
|
|
||||||
if start == end {
|
|
||||||
return c.String()
|
|
||||||
}
|
|
||||||
return symbols[start:end]
|
|
||||||
}
|
|
||||||
if lang == 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
lang = lang.Parent()
|
|
||||||
}
|
|
||||||
return c.String()
|
|
||||||
}
|
|
||||||
152
vendor/golang.org/x/text/currency/query.go
generated
vendored
152
vendor/golang.org/x/text/currency/query.go
generated
vendored
@@ -1,152 +0,0 @@
|
|||||||
// Copyright 2016 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 currency
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sort"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"golang.org/x/text/language"
|
|
||||||
)
|
|
||||||
|
|
||||||
// QueryIter represents a set of Units. The default set includes all Units that
|
|
||||||
// are currently in use as legal tender in any Region.
|
|
||||||
type QueryIter interface {
|
|
||||||
// Next returns true if there is a next element available.
|
|
||||||
// It must be called before any of the other methods are called.
|
|
||||||
Next() bool
|
|
||||||
|
|
||||||
// Unit returns the unit of the current iteration.
|
|
||||||
Unit() Unit
|
|
||||||
|
|
||||||
// Region returns the Region for the current iteration.
|
|
||||||
Region() language.Region
|
|
||||||
|
|
||||||
// From returns the date from which the unit was used in the region.
|
|
||||||
// It returns false if this date is unknown.
|
|
||||||
From() (time.Time, bool)
|
|
||||||
|
|
||||||
// To returns the date up till which the unit was used in the region.
|
|
||||||
// It returns false if this date is unknown or if the unit is still in use.
|
|
||||||
To() (time.Time, bool)
|
|
||||||
|
|
||||||
// IsTender reports whether the unit is a legal tender in the region during
|
|
||||||
// the specified date range.
|
|
||||||
IsTender() bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// Query represents a set of Units. The default set includes all Units that are
|
|
||||||
// currently in use as legal tender in any Region.
|
|
||||||
func Query(options ...QueryOption) QueryIter {
|
|
||||||
it := &iter{
|
|
||||||
end: len(regionData),
|
|
||||||
date: 0xFFFFFFFF,
|
|
||||||
}
|
|
||||||
for _, fn := range options {
|
|
||||||
fn(it)
|
|
||||||
}
|
|
||||||
return it
|
|
||||||
}
|
|
||||||
|
|
||||||
// NonTender returns a new query that also includes matching Units that are not
|
|
||||||
// legal tender.
|
|
||||||
var NonTender QueryOption = nonTender
|
|
||||||
|
|
||||||
func nonTender(i *iter) {
|
|
||||||
i.nonTender = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Historical selects the units for all dates.
|
|
||||||
var Historical QueryOption = historical
|
|
||||||
|
|
||||||
func historical(i *iter) {
|
|
||||||
i.date = hist
|
|
||||||
}
|
|
||||||
|
|
||||||
// A QueryOption can be used to change the set of unit information returned by
|
|
||||||
// a query.
|
|
||||||
type QueryOption func(*iter)
|
|
||||||
|
|
||||||
// Date queries the units that were in use at the given point in history.
|
|
||||||
func Date(t time.Time) QueryOption {
|
|
||||||
d := toDate(t)
|
|
||||||
return func(i *iter) {
|
|
||||||
i.date = d
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Region limits the query to only return entries for the given region.
|
|
||||||
func Region(r language.Region) QueryOption {
|
|
||||||
p, end := len(regionData), len(regionData)
|
|
||||||
x := regionToCode(r)
|
|
||||||
i := sort.Search(len(regionData), func(i int) bool {
|
|
||||||
return regionData[i].region >= x
|
|
||||||
})
|
|
||||||
if i < len(regionData) && regionData[i].region == x {
|
|
||||||
p = i
|
|
||||||
for i++; i < len(regionData) && regionData[i].region == x; i++ {
|
|
||||||
}
|
|
||||||
end = i
|
|
||||||
}
|
|
||||||
return func(i *iter) {
|
|
||||||
i.p, i.end = p, end
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
hist = 0x00
|
|
||||||
now = 0xFFFFFFFF
|
|
||||||
)
|
|
||||||
|
|
||||||
type iter struct {
|
|
||||||
*regionInfo
|
|
||||||
p, end int
|
|
||||||
date uint32
|
|
||||||
nonTender bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *iter) Next() bool {
|
|
||||||
for ; i.p < i.end; i.p++ {
|
|
||||||
i.regionInfo = ®ionData[i.p]
|
|
||||||
if !i.nonTender && !i.IsTender() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if i.date == hist || (i.from <= i.date && (i.to == 0 || i.date <= i.to)) {
|
|
||||||
i.p++
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *regionInfo) Region() language.Region {
|
|
||||||
// TODO: this could be much faster.
|
|
||||||
var buf [2]byte
|
|
||||||
buf[0] = uint8(r.region >> 8)
|
|
||||||
buf[1] = uint8(r.region)
|
|
||||||
return language.MustParseRegion(string(buf[:]))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *regionInfo) Unit() Unit {
|
|
||||||
return Unit{r.code &^ nonTenderBit}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *regionInfo) IsTender() bool {
|
|
||||||
return r.code&nonTenderBit == 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *regionInfo) From() (time.Time, bool) {
|
|
||||||
if r.from == 0 {
|
|
||||||
return time.Time{}, false
|
|
||||||
}
|
|
||||||
return fromDate(r.from), true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *regionInfo) To() (time.Time, bool) {
|
|
||||||
if r.to == 0 {
|
|
||||||
return time.Time{}, false
|
|
||||||
}
|
|
||||||
return fromDate(r.to), true
|
|
||||||
}
|
|
||||||
2629
vendor/golang.org/x/text/currency/tables.go
generated
vendored
2629
vendor/golang.org/x/text/currency/tables.go
generated
vendored
File diff suppressed because it is too large
Load Diff
41
vendor/golang.org/x/text/internal/format/format.go
generated
vendored
41
vendor/golang.org/x/text/internal/format/format.go
generated
vendored
@@ -1,41 +0,0 @@
|
|||||||
// Copyright 2015 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 format contains types for defining language-specific formatting of
|
|
||||||
// values.
|
|
||||||
//
|
|
||||||
// This package is internal now, but will eventually be exposed after the API
|
|
||||||
// settles.
|
|
||||||
package format // import "golang.org/x/text/internal/format"
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"golang.org/x/text/language"
|
|
||||||
)
|
|
||||||
|
|
||||||
// State represents the printer state passed to custom formatters. It provides
|
|
||||||
// access to the fmt.State interface and the sentence and language-related
|
|
||||||
// context.
|
|
||||||
type State interface {
|
|
||||||
fmt.State
|
|
||||||
|
|
||||||
// Language reports the requested language in which to render a message.
|
|
||||||
Language() language.Tag
|
|
||||||
|
|
||||||
// TODO: consider this and removing rune from the Format method in the
|
|
||||||
// Formatter interface.
|
|
||||||
//
|
|
||||||
// Verb returns the format variant to render, analogous to the types used
|
|
||||||
// in fmt. Use 'v' for the default or only variant.
|
|
||||||
// Verb() rune
|
|
||||||
|
|
||||||
// TODO: more info:
|
|
||||||
// - sentence context such as linguistic features passed by the translator.
|
|
||||||
}
|
|
||||||
|
|
||||||
// Formatter is analogous to fmt.Formatter.
|
|
||||||
type Formatter interface {
|
|
||||||
Format(state State, verb rune)
|
|
||||||
}
|
|
||||||
358
vendor/golang.org/x/text/internal/format/parser.go
generated
vendored
358
vendor/golang.org/x/text/internal/format/parser.go
generated
vendored
@@ -1,358 +0,0 @@
|
|||||||
// Copyright 2017 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 format
|
|
||||||
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
"unicode/utf8"
|
|
||||||
)
|
|
||||||
|
|
||||||
// A Parser parses a format string. The result from the parse are set in the
|
|
||||||
// struct fields.
|
|
||||||
type Parser struct {
|
|
||||||
Verb rune
|
|
||||||
|
|
||||||
WidthPresent bool
|
|
||||||
PrecPresent bool
|
|
||||||
Minus bool
|
|
||||||
Plus bool
|
|
||||||
Sharp bool
|
|
||||||
Space bool
|
|
||||||
Zero bool
|
|
||||||
|
|
||||||
// For the formats %+v %#v, we set the plusV/sharpV flags
|
|
||||||
// and clear the plus/sharp flags since %+v and %#v are in effect
|
|
||||||
// different, flagless formats set at the top level.
|
|
||||||
PlusV bool
|
|
||||||
SharpV bool
|
|
||||||
|
|
||||||
HasIndex bool
|
|
||||||
|
|
||||||
Width int
|
|
||||||
Prec int // precision
|
|
||||||
|
|
||||||
// retain arguments across calls.
|
|
||||||
Args []interface{}
|
|
||||||
// retain current argument number across calls
|
|
||||||
ArgNum int
|
|
||||||
|
|
||||||
// reordered records whether the format string used argument reordering.
|
|
||||||
Reordered bool
|
|
||||||
// goodArgNum records whether the most recent reordering directive was valid.
|
|
||||||
goodArgNum bool
|
|
||||||
|
|
||||||
// position info
|
|
||||||
format string
|
|
||||||
startPos int
|
|
||||||
endPos int
|
|
||||||
Status Status
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset initializes a parser to scan format strings for the given args.
|
|
||||||
func (p *Parser) Reset(args []interface{}) {
|
|
||||||
p.Args = args
|
|
||||||
p.ArgNum = 0
|
|
||||||
p.startPos = 0
|
|
||||||
p.Reordered = false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Text returns the part of the format string that was parsed by the last call
|
|
||||||
// to Scan. It returns the original substitution clause if the current scan
|
|
||||||
// parsed a substitution.
|
|
||||||
func (p *Parser) Text() string { return p.format[p.startPos:p.endPos] }
|
|
||||||
|
|
||||||
// SetFormat sets a new format string to parse. It does not reset the argument
|
|
||||||
// count.
|
|
||||||
func (p *Parser) SetFormat(format string) {
|
|
||||||
p.format = format
|
|
||||||
p.startPos = 0
|
|
||||||
p.endPos = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Status indicates the result type of a call to Scan.
|
|
||||||
type Status int
|
|
||||||
|
|
||||||
const (
|
|
||||||
StatusText Status = iota
|
|
||||||
StatusSubstitution
|
|
||||||
StatusBadWidthSubstitution
|
|
||||||
StatusBadPrecSubstitution
|
|
||||||
StatusNoVerb
|
|
||||||
StatusBadArgNum
|
|
||||||
StatusMissingArg
|
|
||||||
)
|
|
||||||
|
|
||||||
// ClearFlags reset the parser to default behavior.
|
|
||||||
func (p *Parser) ClearFlags() {
|
|
||||||
p.WidthPresent = false
|
|
||||||
p.PrecPresent = false
|
|
||||||
p.Minus = false
|
|
||||||
p.Plus = false
|
|
||||||
p.Sharp = false
|
|
||||||
p.Space = false
|
|
||||||
p.Zero = false
|
|
||||||
|
|
||||||
p.PlusV = false
|
|
||||||
p.SharpV = false
|
|
||||||
|
|
||||||
p.HasIndex = false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Scan scans the next part of the format string and sets the status to
|
|
||||||
// indicate whether it scanned a string literal, substitution or error.
|
|
||||||
func (p *Parser) Scan() bool {
|
|
||||||
p.Status = StatusText
|
|
||||||
format := p.format
|
|
||||||
end := len(format)
|
|
||||||
if p.endPos >= end {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
afterIndex := false // previous item in format was an index like [3].
|
|
||||||
|
|
||||||
p.startPos = p.endPos
|
|
||||||
p.goodArgNum = true
|
|
||||||
i := p.startPos
|
|
||||||
for i < end && format[i] != '%' {
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
if i > p.startPos {
|
|
||||||
p.endPos = i
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
// Process one verb
|
|
||||||
i++
|
|
||||||
|
|
||||||
p.Status = StatusSubstitution
|
|
||||||
|
|
||||||
// Do we have flags?
|
|
||||||
p.ClearFlags()
|
|
||||||
|
|
||||||
simpleFormat:
|
|
||||||
for ; i < end; i++ {
|
|
||||||
c := p.format[i]
|
|
||||||
switch c {
|
|
||||||
case '#':
|
|
||||||
p.Sharp = true
|
|
||||||
case '0':
|
|
||||||
p.Zero = !p.Minus // Only allow zero padding to the left.
|
|
||||||
case '+':
|
|
||||||
p.Plus = true
|
|
||||||
case '-':
|
|
||||||
p.Minus = true
|
|
||||||
p.Zero = false // Do not pad with zeros to the right.
|
|
||||||
case ' ':
|
|
||||||
p.Space = true
|
|
||||||
default:
|
|
||||||
// Fast path for common case of ascii lower case simple verbs
|
|
||||||
// without precision or width or argument indices.
|
|
||||||
if 'a' <= c && c <= 'z' && p.ArgNum < len(p.Args) {
|
|
||||||
if c == 'v' {
|
|
||||||
// Go syntax
|
|
||||||
p.SharpV = p.Sharp
|
|
||||||
p.Sharp = false
|
|
||||||
// Struct-field syntax
|
|
||||||
p.PlusV = p.Plus
|
|
||||||
p.Plus = false
|
|
||||||
}
|
|
||||||
p.Verb = rune(c)
|
|
||||||
p.ArgNum++
|
|
||||||
p.endPos = i + 1
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
// Format is more complex than simple flags and a verb or is malformed.
|
|
||||||
break simpleFormat
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do we have an explicit argument index?
|
|
||||||
i, afterIndex = p.updateArgNumber(format, i)
|
|
||||||
|
|
||||||
// Do we have width?
|
|
||||||
if i < end && format[i] == '*' {
|
|
||||||
i++
|
|
||||||
p.Width, p.WidthPresent = p.intFromArg()
|
|
||||||
|
|
||||||
if !p.WidthPresent {
|
|
||||||
p.Status = StatusBadWidthSubstitution
|
|
||||||
}
|
|
||||||
|
|
||||||
// We have a negative width, so take its value and ensure
|
|
||||||
// that the minus flag is set
|
|
||||||
if p.Width < 0 {
|
|
||||||
p.Width = -p.Width
|
|
||||||
p.Minus = true
|
|
||||||
p.Zero = false // Do not pad with zeros to the right.
|
|
||||||
}
|
|
||||||
afterIndex = false
|
|
||||||
} else {
|
|
||||||
p.Width, p.WidthPresent, i = parsenum(format, i, end)
|
|
||||||
if afterIndex && p.WidthPresent { // "%[3]2d"
|
|
||||||
p.goodArgNum = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do we have precision?
|
|
||||||
if i+1 < end && format[i] == '.' {
|
|
||||||
i++
|
|
||||||
if afterIndex { // "%[3].2d"
|
|
||||||
p.goodArgNum = false
|
|
||||||
}
|
|
||||||
i, afterIndex = p.updateArgNumber(format, i)
|
|
||||||
if i < end && format[i] == '*' {
|
|
||||||
i++
|
|
||||||
p.Prec, p.PrecPresent = p.intFromArg()
|
|
||||||
// Negative precision arguments don't make sense
|
|
||||||
if p.Prec < 0 {
|
|
||||||
p.Prec = 0
|
|
||||||
p.PrecPresent = false
|
|
||||||
}
|
|
||||||
if !p.PrecPresent {
|
|
||||||
p.Status = StatusBadPrecSubstitution
|
|
||||||
}
|
|
||||||
afterIndex = false
|
|
||||||
} else {
|
|
||||||
p.Prec, p.PrecPresent, i = parsenum(format, i, end)
|
|
||||||
if !p.PrecPresent {
|
|
||||||
p.Prec = 0
|
|
||||||
p.PrecPresent = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !afterIndex {
|
|
||||||
i, afterIndex = p.updateArgNumber(format, i)
|
|
||||||
}
|
|
||||||
p.HasIndex = afterIndex
|
|
||||||
|
|
||||||
if i >= end {
|
|
||||||
p.endPos = i
|
|
||||||
p.Status = StatusNoVerb
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
verb, w := utf8.DecodeRuneInString(format[i:])
|
|
||||||
p.endPos = i + w
|
|
||||||
p.Verb = verb
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case verb == '%': // Percent does not absorb operands and ignores f.wid and f.prec.
|
|
||||||
p.startPos = p.endPos - 1
|
|
||||||
p.Status = StatusText
|
|
||||||
case !p.goodArgNum:
|
|
||||||
p.Status = StatusBadArgNum
|
|
||||||
case p.ArgNum >= len(p.Args): // No argument left over to print for the current verb.
|
|
||||||
p.Status = StatusMissingArg
|
|
||||||
p.ArgNum++
|
|
||||||
case verb == 'v':
|
|
||||||
// Go syntax
|
|
||||||
p.SharpV = p.Sharp
|
|
||||||
p.Sharp = false
|
|
||||||
// Struct-field syntax
|
|
||||||
p.PlusV = p.Plus
|
|
||||||
p.Plus = false
|
|
||||||
fallthrough
|
|
||||||
default:
|
|
||||||
p.ArgNum++
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// intFromArg gets the ArgNumth element of Args. On return, isInt reports
|
|
||||||
// whether the argument has integer type.
|
|
||||||
func (p *Parser) intFromArg() (num int, isInt bool) {
|
|
||||||
if p.ArgNum < len(p.Args) {
|
|
||||||
arg := p.Args[p.ArgNum]
|
|
||||||
num, isInt = arg.(int) // Almost always OK.
|
|
||||||
if !isInt {
|
|
||||||
// Work harder.
|
|
||||||
switch v := reflect.ValueOf(arg); v.Kind() {
|
|
||||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
||||||
n := v.Int()
|
|
||||||
if int64(int(n)) == n {
|
|
||||||
num = int(n)
|
|
||||||
isInt = true
|
|
||||||
}
|
|
||||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
|
||||||
n := v.Uint()
|
|
||||||
if int64(n) >= 0 && uint64(int(n)) == n {
|
|
||||||
num = int(n)
|
|
||||||
isInt = true
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
// Already 0, false.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
p.ArgNum++
|
|
||||||
if tooLarge(num) {
|
|
||||||
num = 0
|
|
||||||
isInt = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseArgNumber returns the value of the bracketed number, minus 1
|
|
||||||
// (explicit argument numbers are one-indexed but we want zero-indexed).
|
|
||||||
// The opening bracket is known to be present at format[0].
|
|
||||||
// The returned values are the index, the number of bytes to consume
|
|
||||||
// up to the closing paren, if present, and whether the number parsed
|
|
||||||
// ok. The bytes to consume will be 1 if no closing paren is present.
|
|
||||||
func parseArgNumber(format string) (index int, wid int, ok bool) {
|
|
||||||
// There must be at least 3 bytes: [n].
|
|
||||||
if len(format) < 3 {
|
|
||||||
return 0, 1, false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find closing bracket.
|
|
||||||
for i := 1; i < len(format); i++ {
|
|
||||||
if format[i] == ']' {
|
|
||||||
width, ok, newi := parsenum(format, 1, i)
|
|
||||||
if !ok || newi != i {
|
|
||||||
return 0, i + 1, false
|
|
||||||
}
|
|
||||||
return width - 1, i + 1, true // arg numbers are one-indexed and skip paren.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0, 1, false
|
|
||||||
}
|
|
||||||
|
|
||||||
// updateArgNumber returns the next argument to evaluate, which is either the value of the passed-in
|
|
||||||
// argNum or the value of the bracketed integer that begins format[i:]. It also returns
|
|
||||||
// the new value of i, that is, the index of the next byte of the format to process.
|
|
||||||
func (p *Parser) updateArgNumber(format string, i int) (newi int, found bool) {
|
|
||||||
if len(format) <= i || format[i] != '[' {
|
|
||||||
return i, false
|
|
||||||
}
|
|
||||||
p.Reordered = true
|
|
||||||
index, wid, ok := parseArgNumber(format[i:])
|
|
||||||
if ok && 0 <= index && index < len(p.Args) {
|
|
||||||
p.ArgNum = index
|
|
||||||
return i + wid, true
|
|
||||||
}
|
|
||||||
p.goodArgNum = false
|
|
||||||
return i + wid, ok
|
|
||||||
}
|
|
||||||
|
|
||||||
// tooLarge reports whether the magnitude of the integer is
|
|
||||||
// too large to be used as a formatting width or precision.
|
|
||||||
func tooLarge(x int) bool {
|
|
||||||
const max int = 1e6
|
|
||||||
return x > max || x < -max
|
|
||||||
}
|
|
||||||
|
|
||||||
// parsenum converts ASCII to integer. num is 0 (and isnum is false) if no number present.
|
|
||||||
func parsenum(s string, start, end int) (num int, isnum bool, newi int) {
|
|
||||||
if start >= end {
|
|
||||||
return 0, false, end
|
|
||||||
}
|
|
||||||
for newi = start; newi < end && '0' <= s[newi] && s[newi] <= '9'; newi++ {
|
|
||||||
if tooLarge(num) {
|
|
||||||
return 0, false, end // Overflow; crazy long number most likely.
|
|
||||||
}
|
|
||||||
num = num*10 + int(s[newi]-'0')
|
|
||||||
isnum = true
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
16
vendor/golang.org/x/text/internal/language/common.go
generated
vendored
16
vendor/golang.org/x/text/internal/language/common.go
generated
vendored
@@ -1,16 +0,0 @@
|
|||||||
// Code generated by running "go generate" in golang.org/x/text. DO NOT EDIT.
|
|
||||||
|
|
||||||
package language
|
|
||||||
|
|
||||||
// This file contains code common to the maketables.go and the package code.
|
|
||||||
|
|
||||||
// AliasType is the type of an alias in AliasMap.
|
|
||||||
type AliasType int8
|
|
||||||
|
|
||||||
const (
|
|
||||||
Deprecated AliasType = iota
|
|
||||||
Macro
|
|
||||||
Legacy
|
|
||||||
|
|
||||||
AliasTypeUnknown AliasType = -1
|
|
||||||
)
|
|
||||||
29
vendor/golang.org/x/text/internal/language/compact.go
generated
vendored
29
vendor/golang.org/x/text/internal/language/compact.go
generated
vendored
@@ -1,29 +0,0 @@
|
|||||||
// Copyright 2018 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 language
|
|
||||||
|
|
||||||
// CompactCoreInfo is a compact integer with the three core tags encoded.
|
|
||||||
type CompactCoreInfo uint32
|
|
||||||
|
|
||||||
// GetCompactCore generates a uint32 value that is guaranteed to be unique for
|
|
||||||
// different language, region, and script values.
|
|
||||||
func GetCompactCore(t Tag) (cci CompactCoreInfo, ok bool) {
|
|
||||||
if t.LangID > langNoIndexOffset {
|
|
||||||
return 0, false
|
|
||||||
}
|
|
||||||
cci |= CompactCoreInfo(t.LangID) << (8 + 12)
|
|
||||||
cci |= CompactCoreInfo(t.ScriptID) << 12
|
|
||||||
cci |= CompactCoreInfo(t.RegionID)
|
|
||||||
return cci, true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tag generates a tag from c.
|
|
||||||
func (c CompactCoreInfo) Tag() Tag {
|
|
||||||
return Tag{
|
|
||||||
LangID: Language(c >> 20),
|
|
||||||
RegionID: Region(c & 0x3ff),
|
|
||||||
ScriptID: Script(c>>12) & 0xff,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
61
vendor/golang.org/x/text/internal/language/compact/compact.go
generated
vendored
61
vendor/golang.org/x/text/internal/language/compact/compact.go
generated
vendored
@@ -1,61 +0,0 @@
|
|||||||
// Copyright 2018 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 compact defines a compact representation of language tags.
|
|
||||||
//
|
|
||||||
// Common language tags (at least all for which locale information is defined
|
|
||||||
// in CLDR) are assigned a unique index. Each Tag is associated with such an
|
|
||||||
// ID for selecting language-related resources (such as translations) as well
|
|
||||||
// as one for selecting regional defaults (currency, number formatting, etc.)
|
|
||||||
//
|
|
||||||
// It may want to export this functionality at some point, but at this point
|
|
||||||
// this is only available for use within x/text.
|
|
||||||
package compact // import "golang.org/x/text/internal/language/compact"
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"golang.org/x/text/internal/language"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ID is an integer identifying a single tag.
|
|
||||||
type ID uint16
|
|
||||||
|
|
||||||
func getCoreIndex(t language.Tag) (id ID, ok bool) {
|
|
||||||
cci, ok := language.GetCompactCore(t)
|
|
||||||
if !ok {
|
|
||||||
return 0, false
|
|
||||||
}
|
|
||||||
i := sort.Search(len(coreTags), func(i int) bool {
|
|
||||||
return cci <= coreTags[i]
|
|
||||||
})
|
|
||||||
if i == len(coreTags) || coreTags[i] != cci {
|
|
||||||
return 0, false
|
|
||||||
}
|
|
||||||
return ID(i), true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parent returns the ID of the parent or the root ID if id is already the root.
|
|
||||||
func (id ID) Parent() ID {
|
|
||||||
return parents[id]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tag converts id to an internal language Tag.
|
|
||||||
func (id ID) Tag() language.Tag {
|
|
||||||
if int(id) >= len(coreTags) {
|
|
||||||
return specialTags[int(id)-len(coreTags)]
|
|
||||||
}
|
|
||||||
return coreTags[id].Tag()
|
|
||||||
}
|
|
||||||
|
|
||||||
var specialTags []language.Tag
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
tags := strings.Split(specialTagsStr, " ")
|
|
||||||
specialTags = make([]language.Tag, len(tags))
|
|
||||||
for i, t := range tags {
|
|
||||||
specialTags[i] = language.MustParse(t)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
260
vendor/golang.org/x/text/internal/language/compact/language.go
generated
vendored
260
vendor/golang.org/x/text/internal/language/compact/language.go
generated
vendored
@@ -1,260 +0,0 @@
|
|||||||
// Copyright 2013 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.
|
|
||||||
|
|
||||||
//go:generate go run gen.go gen_index.go -output tables.go
|
|
||||||
//go:generate go run gen_parents.go
|
|
||||||
|
|
||||||
package compact
|
|
||||||
|
|
||||||
// TODO: Remove above NOTE after:
|
|
||||||
// - verifying that tables are dropped correctly (most notably matcher tables).
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"golang.org/x/text/internal/language"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Tag represents a BCP 47 language tag. It is used to specify an instance of a
|
|
||||||
// specific language or locale. All language tag values are guaranteed to be
|
|
||||||
// well-formed.
|
|
||||||
type Tag struct {
|
|
||||||
// NOTE: exported tags will become part of the public API.
|
|
||||||
language ID
|
|
||||||
locale ID
|
|
||||||
full fullTag // always a language.Tag for now.
|
|
||||||
}
|
|
||||||
|
|
||||||
const _und = 0
|
|
||||||
|
|
||||||
type fullTag interface {
|
|
||||||
IsRoot() bool
|
|
||||||
Parent() language.Tag
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make a compact Tag from a fully specified internal language Tag.
|
|
||||||
func Make(t language.Tag) (tag Tag) {
|
|
||||||
if region := t.TypeForKey("rg"); len(region) == 6 && region[2:] == "zzzz" {
|
|
||||||
if r, err := language.ParseRegion(region[:2]); err == nil {
|
|
||||||
tFull := t
|
|
||||||
t, _ = t.SetTypeForKey("rg", "")
|
|
||||||
// TODO: should we not consider "va" for the language tag?
|
|
||||||
var exact1, exact2 bool
|
|
||||||
tag.language, exact1 = FromTag(t)
|
|
||||||
t.RegionID = r
|
|
||||||
tag.locale, exact2 = FromTag(t)
|
|
||||||
if !exact1 || !exact2 {
|
|
||||||
tag.full = tFull
|
|
||||||
}
|
|
||||||
return tag
|
|
||||||
}
|
|
||||||
}
|
|
||||||
lang, ok := FromTag(t)
|
|
||||||
tag.language = lang
|
|
||||||
tag.locale = lang
|
|
||||||
if !ok {
|
|
||||||
tag.full = t
|
|
||||||
}
|
|
||||||
return tag
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tag returns an internal language Tag version of this tag.
|
|
||||||
func (t Tag) Tag() language.Tag {
|
|
||||||
if t.full != nil {
|
|
||||||
return t.full.(language.Tag)
|
|
||||||
}
|
|
||||||
tag := t.language.Tag()
|
|
||||||
if t.language != t.locale {
|
|
||||||
loc := t.locale.Tag()
|
|
||||||
tag, _ = tag.SetTypeForKey("rg", strings.ToLower(loc.RegionID.String())+"zzzz")
|
|
||||||
}
|
|
||||||
return tag
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsCompact reports whether this tag is fully defined in terms of ID.
|
|
||||||
func (t *Tag) IsCompact() bool {
|
|
||||||
return t.full == nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MayHaveVariants reports whether a tag may have variants. If it returns false
|
|
||||||
// it is guaranteed the tag does not have variants.
|
|
||||||
func (t Tag) MayHaveVariants() bool {
|
|
||||||
return t.full != nil || int(t.language) >= len(coreTags)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MayHaveExtensions reports whether a tag may have extensions. If it returns
|
|
||||||
// false it is guaranteed the tag does not have them.
|
|
||||||
func (t Tag) MayHaveExtensions() bool {
|
|
||||||
return t.full != nil ||
|
|
||||||
int(t.language) >= len(coreTags) ||
|
|
||||||
t.language != t.locale
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsRoot returns true if t is equal to language "und".
|
|
||||||
func (t Tag) IsRoot() bool {
|
|
||||||
if t.full != nil {
|
|
||||||
return t.full.IsRoot()
|
|
||||||
}
|
|
||||||
return t.language == _und
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parent returns the CLDR parent of t. In CLDR, missing fields in data for a
|
|
||||||
// specific language are substituted with fields from the parent language.
|
|
||||||
// The parent for a language may change for newer versions of CLDR.
|
|
||||||
func (t Tag) Parent() Tag {
|
|
||||||
if t.full != nil {
|
|
||||||
return Make(t.full.Parent())
|
|
||||||
}
|
|
||||||
if t.language != t.locale {
|
|
||||||
// Simulate stripping -u-rg-xxxxxx
|
|
||||||
return Tag{language: t.language, locale: t.language}
|
|
||||||
}
|
|
||||||
// TODO: use parent lookup table once cycle from internal package is
|
|
||||||
// removed. Probably by internalizing the table and declaring this fast
|
|
||||||
// enough.
|
|
||||||
// lang := compactID(internal.Parent(uint16(t.language)))
|
|
||||||
lang, _ := FromTag(t.language.Tag().Parent())
|
|
||||||
return Tag{language: lang, locale: lang}
|
|
||||||
}
|
|
||||||
|
|
||||||
// nextToken returns token t and the rest of the string.
|
|
||||||
func nextToken(s string) (t, tail string) {
|
|
||||||
p := strings.Index(s[1:], "-")
|
|
||||||
if p == -1 {
|
|
||||||
return s[1:], ""
|
|
||||||
}
|
|
||||||
p++
|
|
||||||
return s[1:p], s[p:]
|
|
||||||
}
|
|
||||||
|
|
||||||
// LanguageID returns an index, where 0 <= index < NumCompactTags, for tags
|
|
||||||
// for which data exists in the text repository.The index will change over time
|
|
||||||
// and should not be stored in persistent storage. If t does not match a compact
|
|
||||||
// index, exact will be false and the compact index will be returned for the
|
|
||||||
// first match after repeatedly taking the Parent of t.
|
|
||||||
func LanguageID(t Tag) (id ID, exact bool) {
|
|
||||||
return t.language, t.full == nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RegionalID returns the ID for the regional variant of this tag. This index is
|
|
||||||
// used to indicate region-specific overrides, such as default currency, default
|
|
||||||
// calendar and week data, default time cycle, and default measurement system
|
|
||||||
// and unit preferences.
|
|
||||||
//
|
|
||||||
// For instance, the tag en-GB-u-rg-uszzzz specifies British English with US
|
|
||||||
// settings for currency, number formatting, etc. The CompactIndex for this tag
|
|
||||||
// will be that for en-GB, while the RegionalID will be the one corresponding to
|
|
||||||
// en-US.
|
|
||||||
func RegionalID(t Tag) (id ID, exact bool) {
|
|
||||||
return t.locale, t.full == nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// LanguageTag returns t stripped of regional variant indicators.
|
|
||||||
//
|
|
||||||
// At the moment this means it is stripped of a regional and variant subtag "rg"
|
|
||||||
// and "va" in the "u" extension.
|
|
||||||
func (t Tag) LanguageTag() Tag {
|
|
||||||
if t.full == nil {
|
|
||||||
return Tag{language: t.language, locale: t.language}
|
|
||||||
}
|
|
||||||
tt := t.Tag()
|
|
||||||
tt.SetTypeForKey("rg", "")
|
|
||||||
tt.SetTypeForKey("va", "")
|
|
||||||
return Make(tt)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RegionalTag returns the regional variant of the tag.
|
|
||||||
//
|
|
||||||
// At the moment this means that the region is set from the regional subtag
|
|
||||||
// "rg" in the "u" extension.
|
|
||||||
func (t Tag) RegionalTag() Tag {
|
|
||||||
rt := Tag{language: t.locale, locale: t.locale}
|
|
||||||
if t.full == nil {
|
|
||||||
return rt
|
|
||||||
}
|
|
||||||
b := language.Builder{}
|
|
||||||
tag := t.Tag()
|
|
||||||
// tag, _ = tag.SetTypeForKey("rg", "")
|
|
||||||
b.SetTag(t.locale.Tag())
|
|
||||||
if v := tag.Variants(); v != "" {
|
|
||||||
for _, v := range strings.Split(v, "-") {
|
|
||||||
b.AddVariant(v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, e := range tag.Extensions() {
|
|
||||||
b.AddExt(e)
|
|
||||||
}
|
|
||||||
return t
|
|
||||||
}
|
|
||||||
|
|
||||||
// FromTag reports closest matching ID for an internal language Tag.
|
|
||||||
func FromTag(t language.Tag) (id ID, exact bool) {
|
|
||||||
// TODO: perhaps give more frequent tags a lower index.
|
|
||||||
// TODO: we could make the indexes stable. This will excluded some
|
|
||||||
// possibilities for optimization, so don't do this quite yet.
|
|
||||||
exact = true
|
|
||||||
|
|
||||||
b, s, r := t.Raw()
|
|
||||||
if t.HasString() {
|
|
||||||
if t.IsPrivateUse() {
|
|
||||||
// We have no entries for user-defined tags.
|
|
||||||
return 0, false
|
|
||||||
}
|
|
||||||
hasExtra := false
|
|
||||||
if t.HasVariants() {
|
|
||||||
if t.HasExtensions() {
|
|
||||||
build := language.Builder{}
|
|
||||||
build.SetTag(language.Tag{LangID: b, ScriptID: s, RegionID: r})
|
|
||||||
build.AddVariant(t.Variants())
|
|
||||||
exact = false
|
|
||||||
t = build.Make()
|
|
||||||
}
|
|
||||||
hasExtra = true
|
|
||||||
} else if _, ok := t.Extension('u'); ok {
|
|
||||||
// TODO: va may mean something else. Consider not considering it.
|
|
||||||
// Strip all but the 'va' entry.
|
|
||||||
old := t
|
|
||||||
variant := t.TypeForKey("va")
|
|
||||||
t = language.Tag{LangID: b, ScriptID: s, RegionID: r}
|
|
||||||
if variant != "" {
|
|
||||||
t, _ = t.SetTypeForKey("va", variant)
|
|
||||||
hasExtra = true
|
|
||||||
}
|
|
||||||
exact = old == t
|
|
||||||
} else {
|
|
||||||
exact = false
|
|
||||||
}
|
|
||||||
if hasExtra {
|
|
||||||
// We have some variants.
|
|
||||||
for i, s := range specialTags {
|
|
||||||
if s == t {
|
|
||||||
return ID(i + len(coreTags)), exact
|
|
||||||
}
|
|
||||||
}
|
|
||||||
exact = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if x, ok := getCoreIndex(t); ok {
|
|
||||||
return x, exact
|
|
||||||
}
|
|
||||||
exact = false
|
|
||||||
if r != 0 && s == 0 {
|
|
||||||
// Deal with cases where an extra script is inserted for the region.
|
|
||||||
t, _ := t.Maximize()
|
|
||||||
if x, ok := getCoreIndex(t); ok {
|
|
||||||
return x, exact
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for t = t.Parent(); t != root; t = t.Parent() {
|
|
||||||
// No variants specified: just compare core components.
|
|
||||||
// The key has the form lllssrrr, where l, s, and r are nibbles for
|
|
||||||
// respectively the langID, scriptID, and regionID.
|
|
||||||
if x, ok := getCoreIndex(t); ok {
|
|
||||||
return x, exact
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0, exact
|
|
||||||
}
|
|
||||||
|
|
||||||
var root = language.Tag{}
|
|
||||||
120
vendor/golang.org/x/text/internal/language/compact/parents.go
generated
vendored
120
vendor/golang.org/x/text/internal/language/compact/parents.go
generated
vendored
@@ -1,120 +0,0 @@
|
|||||||
// Code generated by running "go generate" in golang.org/x/text. DO NOT EDIT.
|
|
||||||
|
|
||||||
package compact
|
|
||||||
|
|
||||||
// parents maps a compact index of a tag to the compact index of the parent of
|
|
||||||
// this tag.
|
|
||||||
var parents = []ID{ // 775 elements
|
|
||||||
// Entry 0 - 3F
|
|
||||||
0x0000, 0x0000, 0x0001, 0x0001, 0x0000, 0x0004, 0x0000, 0x0006,
|
|
||||||
0x0000, 0x0008, 0x0000, 0x000a, 0x000a, 0x000a, 0x000a, 0x000a,
|
|
||||||
0x000a, 0x000a, 0x000a, 0x000a, 0x000a, 0x000a, 0x000a, 0x000a,
|
|
||||||
0x000a, 0x000a, 0x000a, 0x000a, 0x000a, 0x000a, 0x000a, 0x000a,
|
|
||||||
0x000a, 0x000a, 0x000a, 0x000a, 0x000a, 0x000a, 0x000a, 0x0000,
|
|
||||||
0x0000, 0x0028, 0x0000, 0x002a, 0x0000, 0x002c, 0x0000, 0x0000,
|
|
||||||
0x002f, 0x002e, 0x002e, 0x0000, 0x0033, 0x0000, 0x0035, 0x0000,
|
|
||||||
0x0037, 0x0000, 0x0039, 0x0000, 0x003b, 0x0000, 0x0000, 0x003e,
|
|
||||||
// Entry 40 - 7F
|
|
||||||
0x0000, 0x0040, 0x0040, 0x0000, 0x0043, 0x0043, 0x0000, 0x0046,
|
|
||||||
0x0000, 0x0048, 0x0000, 0x0000, 0x004b, 0x004a, 0x004a, 0x0000,
|
|
||||||
0x004f, 0x004f, 0x004f, 0x004f, 0x0000, 0x0054, 0x0054, 0x0000,
|
|
||||||
0x0057, 0x0000, 0x0059, 0x0000, 0x005b, 0x0000, 0x005d, 0x005d,
|
|
||||||
0x0000, 0x0060, 0x0000, 0x0062, 0x0000, 0x0064, 0x0000, 0x0066,
|
|
||||||
0x0066, 0x0000, 0x0069, 0x0000, 0x006b, 0x006b, 0x006b, 0x006b,
|
|
||||||
0x006b, 0x006b, 0x006b, 0x0000, 0x0073, 0x0000, 0x0075, 0x0000,
|
|
||||||
0x0077, 0x0000, 0x0000, 0x007a, 0x0000, 0x007c, 0x0000, 0x007e,
|
|
||||||
// Entry 80 - BF
|
|
||||||
0x0000, 0x0080, 0x0080, 0x0000, 0x0083, 0x0083, 0x0000, 0x0086,
|
|
||||||
0x0087, 0x0087, 0x0087, 0x0086, 0x0088, 0x0087, 0x0087, 0x0087,
|
|
||||||
0x0086, 0x0087, 0x0087, 0x0087, 0x0087, 0x0087, 0x0087, 0x0088,
|
|
||||||
0x0087, 0x0087, 0x0087, 0x0087, 0x0088, 0x0087, 0x0088, 0x0087,
|
|
||||||
0x0087, 0x0088, 0x0087, 0x0087, 0x0087, 0x0087, 0x0087, 0x0087,
|
|
||||||
0x0087, 0x0087, 0x0087, 0x0086, 0x0087, 0x0087, 0x0087, 0x0087,
|
|
||||||
0x0087, 0x0087, 0x0087, 0x0087, 0x0087, 0x0087, 0x0087, 0x0087,
|
|
||||||
0x0087, 0x0087, 0x0087, 0x0087, 0x0087, 0x0086, 0x0087, 0x0086,
|
|
||||||
// Entry C0 - FF
|
|
||||||
0x0087, 0x0087, 0x0087, 0x0087, 0x0087, 0x0087, 0x0087, 0x0087,
|
|
||||||
0x0088, 0x0087, 0x0087, 0x0087, 0x0087, 0x0087, 0x0087, 0x0087,
|
|
||||||
0x0086, 0x0087, 0x0087, 0x0087, 0x0087, 0x0087, 0x0088, 0x0087,
|
|
||||||
0x0087, 0x0088, 0x0087, 0x0087, 0x0087, 0x0087, 0x0087, 0x0087,
|
|
||||||
0x0087, 0x0087, 0x0087, 0x0087, 0x0087, 0x0086, 0x0086, 0x0087,
|
|
||||||
0x0087, 0x0086, 0x0087, 0x0087, 0x0087, 0x0087, 0x0087, 0x0000,
|
|
||||||
0x00ef, 0x0000, 0x00f1, 0x00f2, 0x00f2, 0x00f2, 0x00f2, 0x00f2,
|
|
||||||
0x00f2, 0x00f2, 0x00f2, 0x00f2, 0x00f1, 0x00f2, 0x00f1, 0x00f1,
|
|
||||||
// Entry 100 - 13F
|
|
||||||
0x00f2, 0x00f2, 0x00f1, 0x00f2, 0x00f2, 0x00f2, 0x00f2, 0x00f1,
|
|
||||||
0x00f2, 0x00f2, 0x00f2, 0x00f2, 0x00f2, 0x00f2, 0x0000, 0x010e,
|
|
||||||
0x0000, 0x0110, 0x0000, 0x0112, 0x0000, 0x0114, 0x0114, 0x0000,
|
|
||||||
0x0117, 0x0117, 0x0117, 0x0117, 0x0000, 0x011c, 0x0000, 0x011e,
|
|
||||||
0x0000, 0x0120, 0x0120, 0x0000, 0x0123, 0x0123, 0x0123, 0x0123,
|
|
||||||
0x0123, 0x0123, 0x0123, 0x0123, 0x0123, 0x0123, 0x0123, 0x0123,
|
|
||||||
0x0123, 0x0123, 0x0123, 0x0123, 0x0123, 0x0123, 0x0123, 0x0123,
|
|
||||||
0x0123, 0x0123, 0x0123, 0x0123, 0x0123, 0x0123, 0x0123, 0x0123,
|
|
||||||
// Entry 140 - 17F
|
|
||||||
0x0123, 0x0123, 0x0123, 0x0123, 0x0123, 0x0123, 0x0123, 0x0123,
|
|
||||||
0x0123, 0x0123, 0x0123, 0x0123, 0x0123, 0x0123, 0x0123, 0x0123,
|
|
||||||
0x0123, 0x0123, 0x0000, 0x0152, 0x0000, 0x0154, 0x0000, 0x0156,
|
|
||||||
0x0000, 0x0158, 0x0000, 0x015a, 0x0000, 0x015c, 0x015c, 0x015c,
|
|
||||||
0x0000, 0x0160, 0x0000, 0x0000, 0x0163, 0x0000, 0x0165, 0x0000,
|
|
||||||
0x0167, 0x0167, 0x0167, 0x0000, 0x016b, 0x0000, 0x016d, 0x0000,
|
|
||||||
0x016f, 0x0000, 0x0171, 0x0171, 0x0000, 0x0174, 0x0000, 0x0176,
|
|
||||||
0x0000, 0x0178, 0x0000, 0x017a, 0x0000, 0x017c, 0x0000, 0x017e,
|
|
||||||
// Entry 180 - 1BF
|
|
||||||
0x0000, 0x0000, 0x0000, 0x0182, 0x0000, 0x0184, 0x0184, 0x0184,
|
|
||||||
0x0184, 0x0000, 0x0000, 0x0000, 0x018b, 0x0000, 0x0000, 0x018e,
|
|
||||||
0x0000, 0x0000, 0x0191, 0x0000, 0x0000, 0x0000, 0x0195, 0x0000,
|
|
||||||
0x0197, 0x0000, 0x0000, 0x019a, 0x0000, 0x0000, 0x019d, 0x0000,
|
|
||||||
0x019f, 0x0000, 0x01a1, 0x0000, 0x01a3, 0x0000, 0x01a5, 0x0000,
|
|
||||||
0x01a7, 0x0000, 0x01a9, 0x0000, 0x01ab, 0x0000, 0x01ad, 0x0000,
|
|
||||||
0x01af, 0x0000, 0x01b1, 0x01b1, 0x0000, 0x01b4, 0x0000, 0x01b6,
|
|
||||||
0x0000, 0x01b8, 0x0000, 0x01ba, 0x0000, 0x01bc, 0x0000, 0x0000,
|
|
||||||
// Entry 1C0 - 1FF
|
|
||||||
0x01bf, 0x0000, 0x01c1, 0x0000, 0x01c3, 0x0000, 0x01c5, 0x0000,
|
|
||||||
0x01c7, 0x0000, 0x01c9, 0x0000, 0x01cb, 0x01cb, 0x01cb, 0x01cb,
|
|
||||||
0x0000, 0x01d0, 0x0000, 0x01d2, 0x01d2, 0x0000, 0x01d5, 0x0000,
|
|
||||||
0x01d7, 0x0000, 0x01d9, 0x0000, 0x01db, 0x0000, 0x01dd, 0x0000,
|
|
||||||
0x01df, 0x01df, 0x0000, 0x01e2, 0x0000, 0x01e4, 0x0000, 0x01e6,
|
|
||||||
0x0000, 0x01e8, 0x0000, 0x01ea, 0x0000, 0x01ec, 0x0000, 0x01ee,
|
|
||||||
0x0000, 0x01f0, 0x0000, 0x0000, 0x01f3, 0x0000, 0x01f5, 0x01f5,
|
|
||||||
0x01f5, 0x0000, 0x01f9, 0x0000, 0x01fb, 0x0000, 0x01fd, 0x0000,
|
|
||||||
// Entry 200 - 23F
|
|
||||||
0x01ff, 0x0000, 0x0000, 0x0202, 0x0000, 0x0204, 0x0204, 0x0000,
|
|
||||||
0x0207, 0x0000, 0x0209, 0x0209, 0x0000, 0x020c, 0x020c, 0x0000,
|
|
||||||
0x020f, 0x020f, 0x020f, 0x020f, 0x020f, 0x020f, 0x020f, 0x0000,
|
|
||||||
0x0217, 0x0000, 0x0219, 0x0000, 0x021b, 0x0000, 0x0000, 0x0000,
|
|
||||||
0x0000, 0x0000, 0x0221, 0x0000, 0x0000, 0x0224, 0x0000, 0x0226,
|
|
||||||
0x0226, 0x0000, 0x0229, 0x0000, 0x022b, 0x022b, 0x0000, 0x0000,
|
|
||||||
0x022f, 0x022e, 0x022e, 0x0000, 0x0000, 0x0234, 0x0000, 0x0236,
|
|
||||||
0x0000, 0x0238, 0x0000, 0x0244, 0x023a, 0x0244, 0x0244, 0x0244,
|
|
||||||
// Entry 240 - 27F
|
|
||||||
0x0244, 0x0244, 0x0244, 0x0244, 0x023a, 0x0244, 0x0244, 0x0000,
|
|
||||||
0x0247, 0x0247, 0x0247, 0x0000, 0x024b, 0x0000, 0x024d, 0x0000,
|
|
||||||
0x024f, 0x024f, 0x0000, 0x0252, 0x0000, 0x0254, 0x0254, 0x0254,
|
|
||||||
0x0254, 0x0254, 0x0254, 0x0000, 0x025b, 0x0000, 0x025d, 0x0000,
|
|
||||||
0x025f, 0x0000, 0x0261, 0x0000, 0x0263, 0x0000, 0x0265, 0x0000,
|
|
||||||
0x0000, 0x0268, 0x0268, 0x0268, 0x0000, 0x026c, 0x0000, 0x026e,
|
|
||||||
0x0000, 0x0270, 0x0000, 0x0000, 0x0000, 0x0274, 0x0273, 0x0273,
|
|
||||||
0x0000, 0x0278, 0x0000, 0x027a, 0x0000, 0x027c, 0x0000, 0x0000,
|
|
||||||
// Entry 280 - 2BF
|
|
||||||
0x0000, 0x0000, 0x0281, 0x0000, 0x0000, 0x0284, 0x0000, 0x0286,
|
|
||||||
0x0286, 0x0286, 0x0286, 0x0000, 0x028b, 0x028b, 0x028b, 0x0000,
|
|
||||||
0x028f, 0x028f, 0x028f, 0x028f, 0x028f, 0x0000, 0x0295, 0x0295,
|
|
||||||
0x0295, 0x0295, 0x0000, 0x0000, 0x0000, 0x0000, 0x029d, 0x029d,
|
|
||||||
0x029d, 0x0000, 0x02a1, 0x02a1, 0x02a1, 0x02a1, 0x0000, 0x0000,
|
|
||||||
0x02a7, 0x02a7, 0x02a7, 0x02a7, 0x0000, 0x02ac, 0x0000, 0x02ae,
|
|
||||||
0x02ae, 0x0000, 0x02b1, 0x0000, 0x02b3, 0x0000, 0x02b5, 0x02b5,
|
|
||||||
0x0000, 0x0000, 0x02b9, 0x0000, 0x0000, 0x0000, 0x02bd, 0x0000,
|
|
||||||
// Entry 2C0 - 2FF
|
|
||||||
0x02bf, 0x02bf, 0x0000, 0x0000, 0x02c3, 0x0000, 0x02c5, 0x0000,
|
|
||||||
0x02c7, 0x0000, 0x02c9, 0x0000, 0x02cb, 0x0000, 0x02cd, 0x02cd,
|
|
||||||
0x0000, 0x0000, 0x02d1, 0x0000, 0x02d3, 0x02d0, 0x02d0, 0x0000,
|
|
||||||
0x0000, 0x02d8, 0x02d7, 0x02d7, 0x0000, 0x0000, 0x02dd, 0x0000,
|
|
||||||
0x02df, 0x0000, 0x02e1, 0x0000, 0x0000, 0x02e4, 0x0000, 0x02e6,
|
|
||||||
0x0000, 0x0000, 0x02e9, 0x0000, 0x02eb, 0x0000, 0x02ed, 0x0000,
|
|
||||||
0x02ef, 0x02ef, 0x0000, 0x0000, 0x02f3, 0x02f2, 0x02f2, 0x0000,
|
|
||||||
0x02f7, 0x0000, 0x02f9, 0x02f9, 0x02f9, 0x02f9, 0x02f9, 0x0000,
|
|
||||||
// Entry 300 - 33F
|
|
||||||
0x02ff, 0x0300, 0x02ff, 0x0000, 0x0303, 0x0051, 0x00e6,
|
|
||||||
} // Size: 1574 bytes
|
|
||||||
|
|
||||||
// Total table size 1574 bytes (1KiB); checksum: 895AAF0B
|
|
||||||
1015
vendor/golang.org/x/text/internal/language/compact/tables.go
generated
vendored
1015
vendor/golang.org/x/text/internal/language/compact/tables.go
generated
vendored
File diff suppressed because it is too large
Load Diff
91
vendor/golang.org/x/text/internal/language/compact/tags.go
generated
vendored
91
vendor/golang.org/x/text/internal/language/compact/tags.go
generated
vendored
@@ -1,91 +0,0 @@
|
|||||||
// Copyright 2013 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 compact
|
|
||||||
|
|
||||||
var (
|
|
||||||
und = Tag{}
|
|
||||||
|
|
||||||
Und Tag = Tag{}
|
|
||||||
|
|
||||||
Afrikaans Tag = Tag{language: afIndex, locale: afIndex}
|
|
||||||
Amharic Tag = Tag{language: amIndex, locale: amIndex}
|
|
||||||
Arabic Tag = Tag{language: arIndex, locale: arIndex}
|
|
||||||
ModernStandardArabic Tag = Tag{language: ar001Index, locale: ar001Index}
|
|
||||||
Azerbaijani Tag = Tag{language: azIndex, locale: azIndex}
|
|
||||||
Bulgarian Tag = Tag{language: bgIndex, locale: bgIndex}
|
|
||||||
Bengali Tag = Tag{language: bnIndex, locale: bnIndex}
|
|
||||||
Catalan Tag = Tag{language: caIndex, locale: caIndex}
|
|
||||||
Czech Tag = Tag{language: csIndex, locale: csIndex}
|
|
||||||
Danish Tag = Tag{language: daIndex, locale: daIndex}
|
|
||||||
German Tag = Tag{language: deIndex, locale: deIndex}
|
|
||||||
Greek Tag = Tag{language: elIndex, locale: elIndex}
|
|
||||||
English Tag = Tag{language: enIndex, locale: enIndex}
|
|
||||||
AmericanEnglish Tag = Tag{language: enUSIndex, locale: enUSIndex}
|
|
||||||
BritishEnglish Tag = Tag{language: enGBIndex, locale: enGBIndex}
|
|
||||||
Spanish Tag = Tag{language: esIndex, locale: esIndex}
|
|
||||||
EuropeanSpanish Tag = Tag{language: esESIndex, locale: esESIndex}
|
|
||||||
LatinAmericanSpanish Tag = Tag{language: es419Index, locale: es419Index}
|
|
||||||
Estonian Tag = Tag{language: etIndex, locale: etIndex}
|
|
||||||
Persian Tag = Tag{language: faIndex, locale: faIndex}
|
|
||||||
Finnish Tag = Tag{language: fiIndex, locale: fiIndex}
|
|
||||||
Filipino Tag = Tag{language: filIndex, locale: filIndex}
|
|
||||||
French Tag = Tag{language: frIndex, locale: frIndex}
|
|
||||||
CanadianFrench Tag = Tag{language: frCAIndex, locale: frCAIndex}
|
|
||||||
Gujarati Tag = Tag{language: guIndex, locale: guIndex}
|
|
||||||
Hebrew Tag = Tag{language: heIndex, locale: heIndex}
|
|
||||||
Hindi Tag = Tag{language: hiIndex, locale: hiIndex}
|
|
||||||
Croatian Tag = Tag{language: hrIndex, locale: hrIndex}
|
|
||||||
Hungarian Tag = Tag{language: huIndex, locale: huIndex}
|
|
||||||
Armenian Tag = Tag{language: hyIndex, locale: hyIndex}
|
|
||||||
Indonesian Tag = Tag{language: idIndex, locale: idIndex}
|
|
||||||
Icelandic Tag = Tag{language: isIndex, locale: isIndex}
|
|
||||||
Italian Tag = Tag{language: itIndex, locale: itIndex}
|
|
||||||
Japanese Tag = Tag{language: jaIndex, locale: jaIndex}
|
|
||||||
Georgian Tag = Tag{language: kaIndex, locale: kaIndex}
|
|
||||||
Kazakh Tag = Tag{language: kkIndex, locale: kkIndex}
|
|
||||||
Khmer Tag = Tag{language: kmIndex, locale: kmIndex}
|
|
||||||
Kannada Tag = Tag{language: knIndex, locale: knIndex}
|
|
||||||
Korean Tag = Tag{language: koIndex, locale: koIndex}
|
|
||||||
Kirghiz Tag = Tag{language: kyIndex, locale: kyIndex}
|
|
||||||
Lao Tag = Tag{language: loIndex, locale: loIndex}
|
|
||||||
Lithuanian Tag = Tag{language: ltIndex, locale: ltIndex}
|
|
||||||
Latvian Tag = Tag{language: lvIndex, locale: lvIndex}
|
|
||||||
Macedonian Tag = Tag{language: mkIndex, locale: mkIndex}
|
|
||||||
Malayalam Tag = Tag{language: mlIndex, locale: mlIndex}
|
|
||||||
Mongolian Tag = Tag{language: mnIndex, locale: mnIndex}
|
|
||||||
Marathi Tag = Tag{language: mrIndex, locale: mrIndex}
|
|
||||||
Malay Tag = Tag{language: msIndex, locale: msIndex}
|
|
||||||
Burmese Tag = Tag{language: myIndex, locale: myIndex}
|
|
||||||
Nepali Tag = Tag{language: neIndex, locale: neIndex}
|
|
||||||
Dutch Tag = Tag{language: nlIndex, locale: nlIndex}
|
|
||||||
Norwegian Tag = Tag{language: noIndex, locale: noIndex}
|
|
||||||
Punjabi Tag = Tag{language: paIndex, locale: paIndex}
|
|
||||||
Polish Tag = Tag{language: plIndex, locale: plIndex}
|
|
||||||
Portuguese Tag = Tag{language: ptIndex, locale: ptIndex}
|
|
||||||
BrazilianPortuguese Tag = Tag{language: ptBRIndex, locale: ptBRIndex}
|
|
||||||
EuropeanPortuguese Tag = Tag{language: ptPTIndex, locale: ptPTIndex}
|
|
||||||
Romanian Tag = Tag{language: roIndex, locale: roIndex}
|
|
||||||
Russian Tag = Tag{language: ruIndex, locale: ruIndex}
|
|
||||||
Sinhala Tag = Tag{language: siIndex, locale: siIndex}
|
|
||||||
Slovak Tag = Tag{language: skIndex, locale: skIndex}
|
|
||||||
Slovenian Tag = Tag{language: slIndex, locale: slIndex}
|
|
||||||
Albanian Tag = Tag{language: sqIndex, locale: sqIndex}
|
|
||||||
Serbian Tag = Tag{language: srIndex, locale: srIndex}
|
|
||||||
SerbianLatin Tag = Tag{language: srLatnIndex, locale: srLatnIndex}
|
|
||||||
Swedish Tag = Tag{language: svIndex, locale: svIndex}
|
|
||||||
Swahili Tag = Tag{language: swIndex, locale: swIndex}
|
|
||||||
Tamil Tag = Tag{language: taIndex, locale: taIndex}
|
|
||||||
Telugu Tag = Tag{language: teIndex, locale: teIndex}
|
|
||||||
Thai Tag = Tag{language: thIndex, locale: thIndex}
|
|
||||||
Turkish Tag = Tag{language: trIndex, locale: trIndex}
|
|
||||||
Ukrainian Tag = Tag{language: ukIndex, locale: ukIndex}
|
|
||||||
Urdu Tag = Tag{language: urIndex, locale: urIndex}
|
|
||||||
Uzbek Tag = Tag{language: uzIndex, locale: uzIndex}
|
|
||||||
Vietnamese Tag = Tag{language: viIndex, locale: viIndex}
|
|
||||||
Chinese Tag = Tag{language: zhIndex, locale: zhIndex}
|
|
||||||
SimplifiedChinese Tag = Tag{language: zhHansIndex, locale: zhHansIndex}
|
|
||||||
TraditionalChinese Tag = Tag{language: zhHantIndex, locale: zhHantIndex}
|
|
||||||
Zulu Tag = Tag{language: zuIndex, locale: zuIndex}
|
|
||||||
)
|
|
||||||
167
vendor/golang.org/x/text/internal/language/compose.go
generated
vendored
167
vendor/golang.org/x/text/internal/language/compose.go
generated
vendored
@@ -1,167 +0,0 @@
|
|||||||
// Copyright 2018 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 language
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// A Builder allows constructing a Tag from individual components.
|
|
||||||
// Its main user is Compose in the top-level language package.
|
|
||||||
type Builder struct {
|
|
||||||
Tag Tag
|
|
||||||
|
|
||||||
private string // the x extension
|
|
||||||
variants []string
|
|
||||||
extensions []string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make returns a new Tag from the current settings.
|
|
||||||
func (b *Builder) Make() Tag {
|
|
||||||
t := b.Tag
|
|
||||||
|
|
||||||
if len(b.extensions) > 0 || len(b.variants) > 0 {
|
|
||||||
sort.Sort(sortVariants(b.variants))
|
|
||||||
sort.Strings(b.extensions)
|
|
||||||
|
|
||||||
if b.private != "" {
|
|
||||||
b.extensions = append(b.extensions, b.private)
|
|
||||||
}
|
|
||||||
n := maxCoreSize + tokenLen(b.variants...) + tokenLen(b.extensions...)
|
|
||||||
buf := make([]byte, n)
|
|
||||||
p := t.genCoreBytes(buf)
|
|
||||||
t.pVariant = byte(p)
|
|
||||||
p += appendTokens(buf[p:], b.variants...)
|
|
||||||
t.pExt = uint16(p)
|
|
||||||
p += appendTokens(buf[p:], b.extensions...)
|
|
||||||
t.str = string(buf[:p])
|
|
||||||
// We may not always need to remake the string, but when or when not
|
|
||||||
// to do so is rather tricky.
|
|
||||||
scan := makeScanner(buf[:p])
|
|
||||||
t, _ = parse(&scan, "")
|
|
||||||
return t
|
|
||||||
|
|
||||||
} else if b.private != "" {
|
|
||||||
t.str = b.private
|
|
||||||
t.RemakeString()
|
|
||||||
}
|
|
||||||
return t
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetTag copies all the settings from a given Tag. Any previously set values
|
|
||||||
// are discarded.
|
|
||||||
func (b *Builder) SetTag(t Tag) {
|
|
||||||
b.Tag.LangID = t.LangID
|
|
||||||
b.Tag.RegionID = t.RegionID
|
|
||||||
b.Tag.ScriptID = t.ScriptID
|
|
||||||
// TODO: optimize
|
|
||||||
b.variants = b.variants[:0]
|
|
||||||
if variants := t.Variants(); variants != "" {
|
|
||||||
for _, vr := range strings.Split(variants[1:], "-") {
|
|
||||||
b.variants = append(b.variants, vr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
b.extensions, b.private = b.extensions[:0], ""
|
|
||||||
for _, e := range t.Extensions() {
|
|
||||||
b.AddExt(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddExt adds extension e to the tag. e must be a valid extension as returned
|
|
||||||
// by Tag.Extension. If the extension already exists, it will be discarded,
|
|
||||||
// except for a -u extension, where non-existing key-type pairs will added.
|
|
||||||
func (b *Builder) AddExt(e string) {
|
|
||||||
if e[0] == 'x' {
|
|
||||||
if b.private == "" {
|
|
||||||
b.private = e
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for i, s := range b.extensions {
|
|
||||||
if s[0] == e[0] {
|
|
||||||
if e[0] == 'u' {
|
|
||||||
b.extensions[i] += e[1:]
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
b.extensions = append(b.extensions, e)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetExt sets the extension e to the tag. e must be a valid extension as
|
|
||||||
// returned by Tag.Extension. If the extension already exists, it will be
|
|
||||||
// overwritten, except for a -u extension, where the individual key-type pairs
|
|
||||||
// will be set.
|
|
||||||
func (b *Builder) SetExt(e string) {
|
|
||||||
if e[0] == 'x' {
|
|
||||||
b.private = e
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for i, s := range b.extensions {
|
|
||||||
if s[0] == e[0] {
|
|
||||||
if e[0] == 'u' {
|
|
||||||
b.extensions[i] = e + s[1:]
|
|
||||||
} else {
|
|
||||||
b.extensions[i] = e
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
b.extensions = append(b.extensions, e)
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddVariant adds any number of variants.
|
|
||||||
func (b *Builder) AddVariant(v ...string) {
|
|
||||||
for _, v := range v {
|
|
||||||
if v != "" {
|
|
||||||
b.variants = append(b.variants, v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ClearVariants removes any variants previously added, including those
|
|
||||||
// copied from a Tag in SetTag.
|
|
||||||
func (b *Builder) ClearVariants() {
|
|
||||||
b.variants = b.variants[:0]
|
|
||||||
}
|
|
||||||
|
|
||||||
// ClearExtensions removes any extensions previously added, including those
|
|
||||||
// copied from a Tag in SetTag.
|
|
||||||
func (b *Builder) ClearExtensions() {
|
|
||||||
b.private = ""
|
|
||||||
b.extensions = b.extensions[:0]
|
|
||||||
}
|
|
||||||
|
|
||||||
func tokenLen(token ...string) (n int) {
|
|
||||||
for _, t := range token {
|
|
||||||
n += len(t) + 1
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func appendTokens(b []byte, token ...string) int {
|
|
||||||
p := 0
|
|
||||||
for _, t := range token {
|
|
||||||
b[p] = '-'
|
|
||||||
copy(b[p+1:], t)
|
|
||||||
p += 1 + len(t)
|
|
||||||
}
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
type sortVariants []string
|
|
||||||
|
|
||||||
func (s sortVariants) Len() int {
|
|
||||||
return len(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s sortVariants) Swap(i, j int) {
|
|
||||||
s[j], s[i] = s[i], s[j]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s sortVariants) Less(i, j int) bool {
|
|
||||||
return variantIndex[s[i]] < variantIndex[s[j]]
|
|
||||||
}
|
|
||||||
28
vendor/golang.org/x/text/internal/language/coverage.go
generated
vendored
28
vendor/golang.org/x/text/internal/language/coverage.go
generated
vendored
@@ -1,28 +0,0 @@
|
|||||||
// Copyright 2014 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 language
|
|
||||||
|
|
||||||
// BaseLanguages returns the list of all supported base languages. It generates
|
|
||||||
// the list by traversing the internal structures.
|
|
||||||
func BaseLanguages() []Language {
|
|
||||||
base := make([]Language, 0, NumLanguages)
|
|
||||||
for i := 0; i < langNoIndexOffset; i++ {
|
|
||||||
// We included "und" already for the value 0.
|
|
||||||
if i != nonCanonicalUnd {
|
|
||||||
base = append(base, Language(i))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
i := langNoIndexOffset
|
|
||||||
for _, v := range langNoIndex {
|
|
||||||
for k := 0; k < 8; k++ {
|
|
||||||
if v&1 == 1 {
|
|
||||||
base = append(base, Language(i))
|
|
||||||
}
|
|
||||||
v >>= 1
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return base
|
|
||||||
}
|
|
||||||
627
vendor/golang.org/x/text/internal/language/language.go
generated
vendored
627
vendor/golang.org/x/text/internal/language/language.go
generated
vendored
@@ -1,627 +0,0 @@
|
|||||||
// Copyright 2013 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.
|
|
||||||
|
|
||||||
//go:generate go run gen.go gen_common.go -output tables.go
|
|
||||||
|
|
||||||
package language // import "golang.org/x/text/internal/language"
|
|
||||||
|
|
||||||
// TODO: Remove above NOTE after:
|
|
||||||
// - verifying that tables are dropped correctly (most notably matcher tables).
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// maxCoreSize is the maximum size of a BCP 47 tag without variants and
|
|
||||||
// extensions. Equals max lang (3) + script (4) + max reg (3) + 2 dashes.
|
|
||||||
maxCoreSize = 12
|
|
||||||
|
|
||||||
// max99thPercentileSize is a somewhat arbitrary buffer size that presumably
|
|
||||||
// is large enough to hold at least 99% of the BCP 47 tags.
|
|
||||||
max99thPercentileSize = 32
|
|
||||||
|
|
||||||
// maxSimpleUExtensionSize is the maximum size of a -u extension with one
|
|
||||||
// key-type pair. Equals len("-u-") + key (2) + dash + max value (8).
|
|
||||||
maxSimpleUExtensionSize = 14
|
|
||||||
)
|
|
||||||
|
|
||||||
// Tag represents a BCP 47 language tag. It is used to specify an instance of a
|
|
||||||
// specific language or locale. All language tag values are guaranteed to be
|
|
||||||
// well-formed. The zero value of Tag is Und.
|
|
||||||
type Tag struct {
|
|
||||||
// TODO: the following fields have the form TagTypeID. This name is chosen
|
|
||||||
// to allow refactoring the public package without conflicting with its
|
|
||||||
// Base, Script, and Region methods. Once the transition is fully completed
|
|
||||||
// the ID can be stripped from the name.
|
|
||||||
|
|
||||||
LangID Language
|
|
||||||
RegionID Region
|
|
||||||
// TODO: we will soon run out of positions for ScriptID. Idea: instead of
|
|
||||||
// storing lang, region, and ScriptID codes, store only the compact index and
|
|
||||||
// have a lookup table from this code to its expansion. This greatly speeds
|
|
||||||
// up table lookup, speed up common variant cases.
|
|
||||||
// This will also immediately free up 3 extra bytes. Also, the pVariant
|
|
||||||
// field can now be moved to the lookup table, as the compact index uniquely
|
|
||||||
// determines the offset of a possible variant.
|
|
||||||
ScriptID Script
|
|
||||||
pVariant byte // offset in str, includes preceding '-'
|
|
||||||
pExt uint16 // offset of first extension, includes preceding '-'
|
|
||||||
|
|
||||||
// str is the string representation of the Tag. It will only be used if the
|
|
||||||
// tag has variants or extensions.
|
|
||||||
str string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make is a convenience wrapper for Parse that omits the error.
|
|
||||||
// In case of an error, a sensible default is returned.
|
|
||||||
func Make(s string) Tag {
|
|
||||||
t, _ := Parse(s)
|
|
||||||
return t
|
|
||||||
}
|
|
||||||
|
|
||||||
// Raw returns the raw base language, script and region, without making an
|
|
||||||
// attempt to infer their values.
|
|
||||||
// TODO: consider removing
|
|
||||||
func (t Tag) Raw() (b Language, s Script, r Region) {
|
|
||||||
return t.LangID, t.ScriptID, t.RegionID
|
|
||||||
}
|
|
||||||
|
|
||||||
// equalTags compares language, script and region subtags only.
|
|
||||||
func (t Tag) equalTags(a Tag) bool {
|
|
||||||
return t.LangID == a.LangID && t.ScriptID == a.ScriptID && t.RegionID == a.RegionID
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsRoot returns true if t is equal to language "und".
|
|
||||||
func (t Tag) IsRoot() bool {
|
|
||||||
if int(t.pVariant) < len(t.str) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return t.equalTags(Und)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsPrivateUse reports whether the Tag consists solely of an IsPrivateUse use
|
|
||||||
// tag.
|
|
||||||
func (t Tag) IsPrivateUse() bool {
|
|
||||||
return t.str != "" && t.pVariant == 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemakeString is used to update t.str in case lang, script or region changed.
|
|
||||||
// It is assumed that pExt and pVariant still point to the start of the
|
|
||||||
// respective parts.
|
|
||||||
func (t *Tag) RemakeString() {
|
|
||||||
if t.str == "" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
extra := t.str[t.pVariant:]
|
|
||||||
if t.pVariant > 0 {
|
|
||||||
extra = extra[1:]
|
|
||||||
}
|
|
||||||
if t.equalTags(Und) && strings.HasPrefix(extra, "x-") {
|
|
||||||
t.str = extra
|
|
||||||
t.pVariant = 0
|
|
||||||
t.pExt = 0
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var buf [max99thPercentileSize]byte // avoid extra memory allocation in most cases.
|
|
||||||
b := buf[:t.genCoreBytes(buf[:])]
|
|
||||||
if extra != "" {
|
|
||||||
diff := len(b) - int(t.pVariant)
|
|
||||||
b = append(b, '-')
|
|
||||||
b = append(b, extra...)
|
|
||||||
t.pVariant = uint8(int(t.pVariant) + diff)
|
|
||||||
t.pExt = uint16(int(t.pExt) + diff)
|
|
||||||
} else {
|
|
||||||
t.pVariant = uint8(len(b))
|
|
||||||
t.pExt = uint16(len(b))
|
|
||||||
}
|
|
||||||
t.str = string(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
// genCoreBytes writes a string for the base languages, script and region tags
|
|
||||||
// to the given buffer and returns the number of bytes written. It will never
|
|
||||||
// write more than maxCoreSize bytes.
|
|
||||||
func (t *Tag) genCoreBytes(buf []byte) int {
|
|
||||||
n := t.LangID.StringToBuf(buf[:])
|
|
||||||
if t.ScriptID != 0 {
|
|
||||||
n += copy(buf[n:], "-")
|
|
||||||
n += copy(buf[n:], t.ScriptID.String())
|
|
||||||
}
|
|
||||||
if t.RegionID != 0 {
|
|
||||||
n += copy(buf[n:], "-")
|
|
||||||
n += copy(buf[n:], t.RegionID.String())
|
|
||||||
}
|
|
||||||
return n
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns the canonical string representation of the language tag.
|
|
||||||
func (t Tag) String() string {
|
|
||||||
if t.str != "" {
|
|
||||||
return t.str
|
|
||||||
}
|
|
||||||
if t.ScriptID == 0 && t.RegionID == 0 {
|
|
||||||
return t.LangID.String()
|
|
||||||
}
|
|
||||||
buf := [maxCoreSize]byte{}
|
|
||||||
return string(buf[:t.genCoreBytes(buf[:])])
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalText implements encoding.TextMarshaler.
|
|
||||||
func (t Tag) MarshalText() (text []byte, err error) {
|
|
||||||
if t.str != "" {
|
|
||||||
text = append(text, t.str...)
|
|
||||||
} else if t.ScriptID == 0 && t.RegionID == 0 {
|
|
||||||
text = append(text, t.LangID.String()...)
|
|
||||||
} else {
|
|
||||||
buf := [maxCoreSize]byte{}
|
|
||||||
text = buf[:t.genCoreBytes(buf[:])]
|
|
||||||
}
|
|
||||||
return text, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalText implements encoding.TextUnmarshaler.
|
|
||||||
func (t *Tag) UnmarshalText(text []byte) error {
|
|
||||||
tag, err := Parse(string(text))
|
|
||||||
*t = tag
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Variants returns the part of the tag holding all variants or the empty string
|
|
||||||
// if there are no variants defined.
|
|
||||||
func (t Tag) Variants() string {
|
|
||||||
if t.pVariant == 0 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return t.str[t.pVariant:t.pExt]
|
|
||||||
}
|
|
||||||
|
|
||||||
// VariantOrPrivateUseTags returns variants or private use tags.
|
|
||||||
func (t Tag) VariantOrPrivateUseTags() string {
|
|
||||||
if t.pExt > 0 {
|
|
||||||
return t.str[t.pVariant:t.pExt]
|
|
||||||
}
|
|
||||||
return t.str[t.pVariant:]
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasString reports whether this tag defines more than just the raw
|
|
||||||
// components.
|
|
||||||
func (t Tag) HasString() bool {
|
|
||||||
return t.str != ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parent returns the CLDR parent of t. In CLDR, missing fields in data for a
|
|
||||||
// specific language are substituted with fields from the parent language.
|
|
||||||
// The parent for a language may change for newer versions of CLDR.
|
|
||||||
func (t Tag) Parent() Tag {
|
|
||||||
if t.str != "" {
|
|
||||||
// Strip the variants and extensions.
|
|
||||||
b, s, r := t.Raw()
|
|
||||||
t = Tag{LangID: b, ScriptID: s, RegionID: r}
|
|
||||||
if t.RegionID == 0 && t.ScriptID != 0 && t.LangID != 0 {
|
|
||||||
base, _ := addTags(Tag{LangID: t.LangID})
|
|
||||||
if base.ScriptID == t.ScriptID {
|
|
||||||
return Tag{LangID: t.LangID}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return t
|
|
||||||
}
|
|
||||||
if t.LangID != 0 {
|
|
||||||
if t.RegionID != 0 {
|
|
||||||
maxScript := t.ScriptID
|
|
||||||
if maxScript == 0 {
|
|
||||||
max, _ := addTags(t)
|
|
||||||
maxScript = max.ScriptID
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := range parents {
|
|
||||||
if Language(parents[i].lang) == t.LangID && Script(parents[i].maxScript) == maxScript {
|
|
||||||
for _, r := range parents[i].fromRegion {
|
|
||||||
if Region(r) == t.RegionID {
|
|
||||||
return Tag{
|
|
||||||
LangID: t.LangID,
|
|
||||||
ScriptID: Script(parents[i].script),
|
|
||||||
RegionID: Region(parents[i].toRegion),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Strip the script if it is the default one.
|
|
||||||
base, _ := addTags(Tag{LangID: t.LangID})
|
|
||||||
if base.ScriptID != maxScript {
|
|
||||||
return Tag{LangID: t.LangID, ScriptID: maxScript}
|
|
||||||
}
|
|
||||||
return Tag{LangID: t.LangID}
|
|
||||||
} else if t.ScriptID != 0 {
|
|
||||||
// The parent for an base-script pair with a non-default script is
|
|
||||||
// "und" instead of the base language.
|
|
||||||
base, _ := addTags(Tag{LangID: t.LangID})
|
|
||||||
if base.ScriptID != t.ScriptID {
|
|
||||||
return Und
|
|
||||||
}
|
|
||||||
return Tag{LangID: t.LangID}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Und
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseExtension parses s as an extension and returns it on success.
|
|
||||||
func ParseExtension(s string) (ext string, err error) {
|
|
||||||
defer func() {
|
|
||||||
if recover() != nil {
|
|
||||||
ext = ""
|
|
||||||
err = ErrSyntax
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
scan := makeScannerString(s)
|
|
||||||
var end int
|
|
||||||
if n := len(scan.token); n != 1 {
|
|
||||||
return "", ErrSyntax
|
|
||||||
}
|
|
||||||
scan.toLower(0, len(scan.b))
|
|
||||||
end = parseExtension(&scan)
|
|
||||||
if end != len(s) {
|
|
||||||
return "", ErrSyntax
|
|
||||||
}
|
|
||||||
return string(scan.b), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasVariants reports whether t has variants.
|
|
||||||
func (t Tag) HasVariants() bool {
|
|
||||||
return uint16(t.pVariant) < t.pExt
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasExtensions reports whether t has extensions.
|
|
||||||
func (t Tag) HasExtensions() bool {
|
|
||||||
return int(t.pExt) < len(t.str)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extension returns the extension of type x for tag t. It will return
|
|
||||||
// false for ok if t does not have the requested extension. The returned
|
|
||||||
// extension will be invalid in this case.
|
|
||||||
func (t Tag) Extension(x byte) (ext string, ok bool) {
|
|
||||||
for i := int(t.pExt); i < len(t.str)-1; {
|
|
||||||
var ext string
|
|
||||||
i, ext = getExtension(t.str, i)
|
|
||||||
if ext[0] == x {
|
|
||||||
return ext, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "", false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extensions returns all extensions of t.
|
|
||||||
func (t Tag) Extensions() []string {
|
|
||||||
e := []string{}
|
|
||||||
for i := int(t.pExt); i < len(t.str)-1; {
|
|
||||||
var ext string
|
|
||||||
i, ext = getExtension(t.str, i)
|
|
||||||
e = append(e, ext)
|
|
||||||
}
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
|
|
||||||
// TypeForKey returns the type associated with the given key, where key and type
|
|
||||||
// are of the allowed values defined for the Unicode locale extension ('u') in
|
|
||||||
// https://www.unicode.org/reports/tr35/#Unicode_Language_and_Locale_Identifiers.
|
|
||||||
// TypeForKey will traverse the inheritance chain to get the correct value.
|
|
||||||
//
|
|
||||||
// If there are multiple types associated with a key, only the first will be
|
|
||||||
// returned. If there is no type associated with a key, it returns the empty
|
|
||||||
// string.
|
|
||||||
func (t Tag) TypeForKey(key string) string {
|
|
||||||
if _, start, end, _ := t.findTypeForKey(key); end != start {
|
|
||||||
s := t.str[start:end]
|
|
||||||
if p := strings.IndexByte(s, '-'); p >= 0 {
|
|
||||||
s = s[:p]
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
errPrivateUse = errors.New("cannot set a key on a private use tag")
|
|
||||||
errInvalidArguments = errors.New("invalid key or type")
|
|
||||||
)
|
|
||||||
|
|
||||||
// SetTypeForKey returns a new Tag with the key set to type, where key and type
|
|
||||||
// are of the allowed values defined for the Unicode locale extension ('u') in
|
|
||||||
// https://www.unicode.org/reports/tr35/#Unicode_Language_and_Locale_Identifiers.
|
|
||||||
// An empty value removes an existing pair with the same key.
|
|
||||||
func (t Tag) SetTypeForKey(key, value string) (Tag, error) {
|
|
||||||
if t.IsPrivateUse() {
|
|
||||||
return t, errPrivateUse
|
|
||||||
}
|
|
||||||
if len(key) != 2 {
|
|
||||||
return t, errInvalidArguments
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove the setting if value is "".
|
|
||||||
if value == "" {
|
|
||||||
start, sep, end, _ := t.findTypeForKey(key)
|
|
||||||
if start != sep {
|
|
||||||
// Remove a possible empty extension.
|
|
||||||
switch {
|
|
||||||
case t.str[start-2] != '-': // has previous elements.
|
|
||||||
case end == len(t.str), // end of string
|
|
||||||
end+2 < len(t.str) && t.str[end+2] == '-': // end of extension
|
|
||||||
start -= 2
|
|
||||||
}
|
|
||||||
if start == int(t.pVariant) && end == len(t.str) {
|
|
||||||
t.str = ""
|
|
||||||
t.pVariant, t.pExt = 0, 0
|
|
||||||
} else {
|
|
||||||
t.str = fmt.Sprintf("%s%s", t.str[:start], t.str[end:])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return t, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(value) < 3 || len(value) > 8 {
|
|
||||||
return t, errInvalidArguments
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
buf [maxCoreSize + maxSimpleUExtensionSize]byte
|
|
||||||
uStart int // start of the -u extension.
|
|
||||||
)
|
|
||||||
|
|
||||||
// Generate the tag string if needed.
|
|
||||||
if t.str == "" {
|
|
||||||
uStart = t.genCoreBytes(buf[:])
|
|
||||||
buf[uStart] = '-'
|
|
||||||
uStart++
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create new key-type pair and parse it to verify.
|
|
||||||
b := buf[uStart:]
|
|
||||||
copy(b, "u-")
|
|
||||||
copy(b[2:], key)
|
|
||||||
b[4] = '-'
|
|
||||||
b = b[:5+copy(b[5:], value)]
|
|
||||||
scan := makeScanner(b)
|
|
||||||
if parseExtensions(&scan); scan.err != nil {
|
|
||||||
return t, scan.err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assemble the replacement string.
|
|
||||||
if t.str == "" {
|
|
||||||
t.pVariant, t.pExt = byte(uStart-1), uint16(uStart-1)
|
|
||||||
t.str = string(buf[:uStart+len(b)])
|
|
||||||
} else {
|
|
||||||
s := t.str
|
|
||||||
start, sep, end, hasExt := t.findTypeForKey(key)
|
|
||||||
if start == sep {
|
|
||||||
if hasExt {
|
|
||||||
b = b[2:]
|
|
||||||
}
|
|
||||||
t.str = fmt.Sprintf("%s-%s%s", s[:sep], b, s[end:])
|
|
||||||
} else {
|
|
||||||
t.str = fmt.Sprintf("%s-%s%s", s[:start+3], value, s[end:])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return t, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// findTypeForKey returns the start and end position for the type corresponding
|
|
||||||
// to key or the point at which to insert the key-value pair if the type
|
|
||||||
// wasn't found. The hasExt return value reports whether an -u extension was present.
|
|
||||||
// Note: the extensions are typically very small and are likely to contain
|
|
||||||
// only one key-type pair.
|
|
||||||
func (t Tag) findTypeForKey(key string) (start, sep, end int, hasExt bool) {
|
|
||||||
p := int(t.pExt)
|
|
||||||
if len(key) != 2 || p == len(t.str) || p == 0 {
|
|
||||||
return p, p, p, false
|
|
||||||
}
|
|
||||||
s := t.str
|
|
||||||
|
|
||||||
// Find the correct extension.
|
|
||||||
for p++; s[p] != 'u'; p++ {
|
|
||||||
if s[p] > 'u' {
|
|
||||||
p--
|
|
||||||
return p, p, p, false
|
|
||||||
}
|
|
||||||
if p = nextExtension(s, p); p == len(s) {
|
|
||||||
return len(s), len(s), len(s), false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Proceed to the hyphen following the extension name.
|
|
||||||
p++
|
|
||||||
|
|
||||||
// curKey is the key currently being processed.
|
|
||||||
curKey := ""
|
|
||||||
|
|
||||||
// Iterate over keys until we get the end of a section.
|
|
||||||
for {
|
|
||||||
end = p
|
|
||||||
for p++; p < len(s) && s[p] != '-'; p++ {
|
|
||||||
}
|
|
||||||
n := p - end - 1
|
|
||||||
if n <= 2 && curKey == key {
|
|
||||||
if sep < end {
|
|
||||||
sep++
|
|
||||||
}
|
|
||||||
return start, sep, end, true
|
|
||||||
}
|
|
||||||
switch n {
|
|
||||||
case 0, // invalid string
|
|
||||||
1: // next extension
|
|
||||||
return end, end, end, true
|
|
||||||
case 2:
|
|
||||||
// next key
|
|
||||||
curKey = s[end+1 : p]
|
|
||||||
if curKey > key {
|
|
||||||
return end, end, end, true
|
|
||||||
}
|
|
||||||
start = end
|
|
||||||
sep = p
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseBase parses a 2- or 3-letter ISO 639 code.
|
|
||||||
// It returns a ValueError if s is a well-formed but unknown language identifier
|
|
||||||
// or another error if another error occurred.
|
|
||||||
func ParseBase(s string) (l Language, err error) {
|
|
||||||
defer func() {
|
|
||||||
if recover() != nil {
|
|
||||||
l = 0
|
|
||||||
err = ErrSyntax
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
if n := len(s); n < 2 || 3 < n {
|
|
||||||
return 0, ErrSyntax
|
|
||||||
}
|
|
||||||
var buf [3]byte
|
|
||||||
return getLangID(buf[:copy(buf[:], s)])
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseScript parses a 4-letter ISO 15924 code.
|
|
||||||
// It returns a ValueError if s is a well-formed but unknown script identifier
|
|
||||||
// or another error if another error occurred.
|
|
||||||
func ParseScript(s string) (scr Script, err error) {
|
|
||||||
defer func() {
|
|
||||||
if recover() != nil {
|
|
||||||
scr = 0
|
|
||||||
err = ErrSyntax
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
if len(s) != 4 {
|
|
||||||
return 0, ErrSyntax
|
|
||||||
}
|
|
||||||
var buf [4]byte
|
|
||||||
return getScriptID(script, buf[:copy(buf[:], s)])
|
|
||||||
}
|
|
||||||
|
|
||||||
// EncodeM49 returns the Region for the given UN M.49 code.
|
|
||||||
// It returns an error if r is not a valid code.
|
|
||||||
func EncodeM49(r int) (Region, error) {
|
|
||||||
return getRegionM49(r)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseRegion parses a 2- or 3-letter ISO 3166-1 or a UN M.49 code.
|
|
||||||
// It returns a ValueError if s is a well-formed but unknown region identifier
|
|
||||||
// or another error if another error occurred.
|
|
||||||
func ParseRegion(s string) (r Region, err error) {
|
|
||||||
defer func() {
|
|
||||||
if recover() != nil {
|
|
||||||
r = 0
|
|
||||||
err = ErrSyntax
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
if n := len(s); n < 2 || 3 < n {
|
|
||||||
return 0, ErrSyntax
|
|
||||||
}
|
|
||||||
var buf [3]byte
|
|
||||||
return getRegionID(buf[:copy(buf[:], s)])
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsCountry returns whether this region is a country or autonomous area. This
|
|
||||||
// includes non-standard definitions from CLDR.
|
|
||||||
func (r Region) IsCountry() bool {
|
|
||||||
if r == 0 || r.IsGroup() || r.IsPrivateUse() && r != _XK {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsGroup returns whether this region defines a collection of regions. This
|
|
||||||
// includes non-standard definitions from CLDR.
|
|
||||||
func (r Region) IsGroup() bool {
|
|
||||||
if r == 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return int(regionInclusion[r]) < len(regionContainment)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Contains returns whether Region c is contained by Region r. It returns true
|
|
||||||
// if c == r.
|
|
||||||
func (r Region) Contains(c Region) bool {
|
|
||||||
if r == c {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
g := regionInclusion[r]
|
|
||||||
if g >= nRegionGroups {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
m := regionContainment[g]
|
|
||||||
|
|
||||||
d := regionInclusion[c]
|
|
||||||
b := regionInclusionBits[d]
|
|
||||||
|
|
||||||
// A contained country may belong to multiple disjoint groups. Matching any
|
|
||||||
// of these indicates containment. If the contained region is a group, it
|
|
||||||
// must strictly be a subset.
|
|
||||||
if d >= nRegionGroups {
|
|
||||||
return b&m != 0
|
|
||||||
}
|
|
||||||
return b&^m == 0
|
|
||||||
}
|
|
||||||
|
|
||||||
var errNoTLD = errors.New("language: region is not a valid ccTLD")
|
|
||||||
|
|
||||||
// TLD returns the country code top-level domain (ccTLD). UK is returned for GB.
|
|
||||||
// In all other cases it returns either the region itself or an error.
|
|
||||||
//
|
|
||||||
// This method may return an error for a region for which there exists a
|
|
||||||
// canonical form with a ccTLD. To get that ccTLD canonicalize r first. The
|
|
||||||
// region will already be canonicalized it was obtained from a Tag that was
|
|
||||||
// obtained using any of the default methods.
|
|
||||||
func (r Region) TLD() (Region, error) {
|
|
||||||
// See http://en.wikipedia.org/wiki/Country_code_top-level_domain for the
|
|
||||||
// difference between ISO 3166-1 and IANA ccTLD.
|
|
||||||
if r == _GB {
|
|
||||||
r = _UK
|
|
||||||
}
|
|
||||||
if (r.typ() & ccTLD) == 0 {
|
|
||||||
return 0, errNoTLD
|
|
||||||
}
|
|
||||||
return r, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Canonicalize returns the region or a possible replacement if the region is
|
|
||||||
// deprecated. It will not return a replacement for deprecated regions that
|
|
||||||
// are split into multiple regions.
|
|
||||||
func (r Region) Canonicalize() Region {
|
|
||||||
if cr := normRegion(r); cr != 0 {
|
|
||||||
return cr
|
|
||||||
}
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
// Variant represents a registered variant of a language as defined by BCP 47.
|
|
||||||
type Variant struct {
|
|
||||||
ID uint8
|
|
||||||
str string
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseVariant parses and returns a Variant. An error is returned if s is not
|
|
||||||
// a valid variant.
|
|
||||||
func ParseVariant(s string) (v Variant, err error) {
|
|
||||||
defer func() {
|
|
||||||
if recover() != nil {
|
|
||||||
v = Variant{}
|
|
||||||
err = ErrSyntax
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
s = strings.ToLower(s)
|
|
||||||
if id, ok := variantIndex[s]; ok {
|
|
||||||
return Variant{id, s}, nil
|
|
||||||
}
|
|
||||||
return Variant{}, NewValueError([]byte(s))
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns the string representation of the variant.
|
|
||||||
func (v Variant) String() string {
|
|
||||||
return v.str
|
|
||||||
}
|
|
||||||
412
vendor/golang.org/x/text/internal/language/lookup.go
generated
vendored
412
vendor/golang.org/x/text/internal/language/lookup.go
generated
vendored
@@ -1,412 +0,0 @@
|
|||||||
// Copyright 2013 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 language
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"sort"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"golang.org/x/text/internal/tag"
|
|
||||||
)
|
|
||||||
|
|
||||||
// findIndex tries to find the given tag in idx and returns a standardized error
|
|
||||||
// if it could not be found.
|
|
||||||
func findIndex(idx tag.Index, key []byte, form string) (index int, err error) {
|
|
||||||
if !tag.FixCase(form, key) {
|
|
||||||
return 0, ErrSyntax
|
|
||||||
}
|
|
||||||
i := idx.Index(key)
|
|
||||||
if i == -1 {
|
|
||||||
return 0, NewValueError(key)
|
|
||||||
}
|
|
||||||
return i, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func searchUint(imap []uint16, key uint16) int {
|
|
||||||
return sort.Search(len(imap), func(i int) bool {
|
|
||||||
return imap[i] >= key
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
type Language uint16
|
|
||||||
|
|
||||||
// getLangID returns the langID of s if s is a canonical subtag
|
|
||||||
// or langUnknown if s is not a canonical subtag.
|
|
||||||
func getLangID(s []byte) (Language, error) {
|
|
||||||
if len(s) == 2 {
|
|
||||||
return getLangISO2(s)
|
|
||||||
}
|
|
||||||
return getLangISO3(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO language normalization as well as the AliasMaps could be moved to the
|
|
||||||
// higher level package, but it is a bit tricky to separate the generation.
|
|
||||||
|
|
||||||
func (id Language) Canonicalize() (Language, AliasType) {
|
|
||||||
return normLang(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
// normLang returns the mapped langID of id according to mapping m.
|
|
||||||
func normLang(id Language) (Language, AliasType) {
|
|
||||||
k := sort.Search(len(AliasMap), func(i int) bool {
|
|
||||||
return AliasMap[i].From >= uint16(id)
|
|
||||||
})
|
|
||||||
if k < len(AliasMap) && AliasMap[k].From == uint16(id) {
|
|
||||||
return Language(AliasMap[k].To), AliasTypes[k]
|
|
||||||
}
|
|
||||||
return id, AliasTypeUnknown
|
|
||||||
}
|
|
||||||
|
|
||||||
// getLangISO2 returns the langID for the given 2-letter ISO language code
|
|
||||||
// or unknownLang if this does not exist.
|
|
||||||
func getLangISO2(s []byte) (Language, error) {
|
|
||||||
if !tag.FixCase("zz", s) {
|
|
||||||
return 0, ErrSyntax
|
|
||||||
}
|
|
||||||
if i := lang.Index(s); i != -1 && lang.Elem(i)[3] != 0 {
|
|
||||||
return Language(i), nil
|
|
||||||
}
|
|
||||||
return 0, NewValueError(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
const base = 'z' - 'a' + 1
|
|
||||||
|
|
||||||
func strToInt(s []byte) uint {
|
|
||||||
v := uint(0)
|
|
||||||
for i := 0; i < len(s); i++ {
|
|
||||||
v *= base
|
|
||||||
v += uint(s[i] - 'a')
|
|
||||||
}
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
|
|
||||||
// converts the given integer to the original ASCII string passed to strToInt.
|
|
||||||
// len(s) must match the number of characters obtained.
|
|
||||||
func intToStr(v uint, s []byte) {
|
|
||||||
for i := len(s) - 1; i >= 0; i-- {
|
|
||||||
s[i] = byte(v%base) + 'a'
|
|
||||||
v /= base
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// getLangISO3 returns the langID for the given 3-letter ISO language code
|
|
||||||
// or unknownLang if this does not exist.
|
|
||||||
func getLangISO3(s []byte) (Language, error) {
|
|
||||||
if tag.FixCase("und", s) {
|
|
||||||
// first try to match canonical 3-letter entries
|
|
||||||
for i := lang.Index(s[:2]); i != -1; i = lang.Next(s[:2], i) {
|
|
||||||
if e := lang.Elem(i); e[3] == 0 && e[2] == s[2] {
|
|
||||||
// We treat "und" as special and always translate it to "unspecified".
|
|
||||||
// Note that ZZ and Zzzz are private use and are not treated as
|
|
||||||
// unspecified by default.
|
|
||||||
id := Language(i)
|
|
||||||
if id == nonCanonicalUnd {
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
return id, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if i := altLangISO3.Index(s); i != -1 {
|
|
||||||
return Language(altLangIndex[altLangISO3.Elem(i)[3]]), nil
|
|
||||||
}
|
|
||||||
n := strToInt(s)
|
|
||||||
if langNoIndex[n/8]&(1<<(n%8)) != 0 {
|
|
||||||
return Language(n) + langNoIndexOffset, nil
|
|
||||||
}
|
|
||||||
// Check for non-canonical uses of ISO3.
|
|
||||||
for i := lang.Index(s[:1]); i != -1; i = lang.Next(s[:1], i) {
|
|
||||||
if e := lang.Elem(i); e[2] == s[1] && e[3] == s[2] {
|
|
||||||
return Language(i), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0, NewValueError(s)
|
|
||||||
}
|
|
||||||
return 0, ErrSyntax
|
|
||||||
}
|
|
||||||
|
|
||||||
// StringToBuf writes the string to b and returns the number of bytes
|
|
||||||
// written. cap(b) must be >= 3.
|
|
||||||
func (id Language) StringToBuf(b []byte) int {
|
|
||||||
if id >= langNoIndexOffset {
|
|
||||||
intToStr(uint(id)-langNoIndexOffset, b[:3])
|
|
||||||
return 3
|
|
||||||
} else if id == 0 {
|
|
||||||
return copy(b, "und")
|
|
||||||
}
|
|
||||||
l := lang[id<<2:]
|
|
||||||
if l[3] == 0 {
|
|
||||||
return copy(b, l[:3])
|
|
||||||
}
|
|
||||||
return copy(b, l[:2])
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns the BCP 47 representation of the langID.
|
|
||||||
// Use b as variable name, instead of id, to ensure the variable
|
|
||||||
// used is consistent with that of Base in which this type is embedded.
|
|
||||||
func (b Language) String() string {
|
|
||||||
if b == 0 {
|
|
||||||
return "und"
|
|
||||||
} else if b >= langNoIndexOffset {
|
|
||||||
b -= langNoIndexOffset
|
|
||||||
buf := [3]byte{}
|
|
||||||
intToStr(uint(b), buf[:])
|
|
||||||
return string(buf[:])
|
|
||||||
}
|
|
||||||
l := lang.Elem(int(b))
|
|
||||||
if l[3] == 0 {
|
|
||||||
return l[:3]
|
|
||||||
}
|
|
||||||
return l[:2]
|
|
||||||
}
|
|
||||||
|
|
||||||
// ISO3 returns the ISO 639-3 language code.
|
|
||||||
func (b Language) ISO3() string {
|
|
||||||
if b == 0 || b >= langNoIndexOffset {
|
|
||||||
return b.String()
|
|
||||||
}
|
|
||||||
l := lang.Elem(int(b))
|
|
||||||
if l[3] == 0 {
|
|
||||||
return l[:3]
|
|
||||||
} else if l[2] == 0 {
|
|
||||||
return altLangISO3.Elem(int(l[3]))[:3]
|
|
||||||
}
|
|
||||||
// This allocation will only happen for 3-letter ISO codes
|
|
||||||
// that are non-canonical BCP 47 language identifiers.
|
|
||||||
return l[0:1] + l[2:4]
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsPrivateUse reports whether this language code is reserved for private use.
|
|
||||||
func (b Language) IsPrivateUse() bool {
|
|
||||||
return langPrivateStart <= b && b <= langPrivateEnd
|
|
||||||
}
|
|
||||||
|
|
||||||
// SuppressScript returns the script marked as SuppressScript in the IANA
|
|
||||||
// language tag repository, or 0 if there is no such script.
|
|
||||||
func (b Language) SuppressScript() Script {
|
|
||||||
if b < langNoIndexOffset {
|
|
||||||
return Script(suppressScript[b])
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
type Region uint16
|
|
||||||
|
|
||||||
// getRegionID returns the region id for s if s is a valid 2-letter region code
|
|
||||||
// or unknownRegion.
|
|
||||||
func getRegionID(s []byte) (Region, error) {
|
|
||||||
if len(s) == 3 {
|
|
||||||
if isAlpha(s[0]) {
|
|
||||||
return getRegionISO3(s)
|
|
||||||
}
|
|
||||||
if i, err := strconv.ParseUint(string(s), 10, 10); err == nil {
|
|
||||||
return getRegionM49(int(i))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return getRegionISO2(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
// getRegionISO2 returns the regionID for the given 2-letter ISO country code
|
|
||||||
// or unknownRegion if this does not exist.
|
|
||||||
func getRegionISO2(s []byte) (Region, error) {
|
|
||||||
i, err := findIndex(regionISO, s, "ZZ")
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
return Region(i) + isoRegionOffset, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// getRegionISO3 returns the regionID for the given 3-letter ISO country code
|
|
||||||
// or unknownRegion if this does not exist.
|
|
||||||
func getRegionISO3(s []byte) (Region, error) {
|
|
||||||
if tag.FixCase("ZZZ", s) {
|
|
||||||
for i := regionISO.Index(s[:1]); i != -1; i = regionISO.Next(s[:1], i) {
|
|
||||||
if e := regionISO.Elem(i); e[2] == s[1] && e[3] == s[2] {
|
|
||||||
return Region(i) + isoRegionOffset, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for i := 0; i < len(altRegionISO3); i += 3 {
|
|
||||||
if tag.Compare(altRegionISO3[i:i+3], s) == 0 {
|
|
||||||
return Region(altRegionIDs[i/3]), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0, NewValueError(s)
|
|
||||||
}
|
|
||||||
return 0, ErrSyntax
|
|
||||||
}
|
|
||||||
|
|
||||||
func getRegionM49(n int) (Region, error) {
|
|
||||||
if 0 < n && n <= 999 {
|
|
||||||
const (
|
|
||||||
searchBits = 7
|
|
||||||
regionBits = 9
|
|
||||||
regionMask = 1<<regionBits - 1
|
|
||||||
)
|
|
||||||
idx := n >> searchBits
|
|
||||||
buf := fromM49[m49Index[idx]:m49Index[idx+1]]
|
|
||||||
val := uint16(n) << regionBits // we rely on bits shifting out
|
|
||||||
i := sort.Search(len(buf), func(i int) bool {
|
|
||||||
return buf[i] >= val
|
|
||||||
})
|
|
||||||
if r := fromM49[int(m49Index[idx])+i]; r&^regionMask == val {
|
|
||||||
return Region(r & regionMask), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var e ValueError
|
|
||||||
fmt.Fprint(bytes.NewBuffer([]byte(e.v[:])), n)
|
|
||||||
return 0, e
|
|
||||||
}
|
|
||||||
|
|
||||||
// normRegion returns a region if r is deprecated or 0 otherwise.
|
|
||||||
// TODO: consider supporting BYS (-> BLR), CSK (-> 200 or CZ), PHI (-> PHL) and AFI (-> DJ).
|
|
||||||
// TODO: consider mapping split up regions to new most populous one (like CLDR).
|
|
||||||
func normRegion(r Region) Region {
|
|
||||||
m := regionOldMap
|
|
||||||
k := sort.Search(len(m), func(i int) bool {
|
|
||||||
return m[i].From >= uint16(r)
|
|
||||||
})
|
|
||||||
if k < len(m) && m[k].From == uint16(r) {
|
|
||||||
return Region(m[k].To)
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
iso3166UserAssigned = 1 << iota
|
|
||||||
ccTLD
|
|
||||||
bcp47Region
|
|
||||||
)
|
|
||||||
|
|
||||||
func (r Region) typ() byte {
|
|
||||||
return regionTypes[r]
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns the BCP 47 representation for the region.
|
|
||||||
// It returns "ZZ" for an unspecified region.
|
|
||||||
func (r Region) String() string {
|
|
||||||
if r < isoRegionOffset {
|
|
||||||
if r == 0 {
|
|
||||||
return "ZZ"
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("%03d", r.M49())
|
|
||||||
}
|
|
||||||
r -= isoRegionOffset
|
|
||||||
return regionISO.Elem(int(r))[:2]
|
|
||||||
}
|
|
||||||
|
|
||||||
// ISO3 returns the 3-letter ISO code of r.
|
|
||||||
// Note that not all regions have a 3-letter ISO code.
|
|
||||||
// In such cases this method returns "ZZZ".
|
|
||||||
func (r Region) ISO3() string {
|
|
||||||
if r < isoRegionOffset {
|
|
||||||
return "ZZZ"
|
|
||||||
}
|
|
||||||
r -= isoRegionOffset
|
|
||||||
reg := regionISO.Elem(int(r))
|
|
||||||
switch reg[2] {
|
|
||||||
case 0:
|
|
||||||
return altRegionISO3[reg[3]:][:3]
|
|
||||||
case ' ':
|
|
||||||
return "ZZZ"
|
|
||||||
}
|
|
||||||
return reg[0:1] + reg[2:4]
|
|
||||||
}
|
|
||||||
|
|
||||||
// M49 returns the UN M.49 encoding of r, or 0 if this encoding
|
|
||||||
// is not defined for r.
|
|
||||||
func (r Region) M49() int {
|
|
||||||
return int(m49[r])
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsPrivateUse reports whether r has the ISO 3166 User-assigned status. This
|
|
||||||
// may include private-use tags that are assigned by CLDR and used in this
|
|
||||||
// implementation. So IsPrivateUse and IsCountry can be simultaneously true.
|
|
||||||
func (r Region) IsPrivateUse() bool {
|
|
||||||
return r.typ()&iso3166UserAssigned != 0
|
|
||||||
}
|
|
||||||
|
|
||||||
type Script uint16
|
|
||||||
|
|
||||||
// getScriptID returns the script id for string s. It assumes that s
|
|
||||||
// is of the format [A-Z][a-z]{3}.
|
|
||||||
func getScriptID(idx tag.Index, s []byte) (Script, error) {
|
|
||||||
i, err := findIndex(idx, s, "Zzzz")
|
|
||||||
return Script(i), err
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns the script code in title case.
|
|
||||||
// It returns "Zzzz" for an unspecified script.
|
|
||||||
func (s Script) String() string {
|
|
||||||
if s == 0 {
|
|
||||||
return "Zzzz"
|
|
||||||
}
|
|
||||||
return script.Elem(int(s))
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsPrivateUse reports whether this script code is reserved for private use.
|
|
||||||
func (s Script) IsPrivateUse() bool {
|
|
||||||
return _Qaaa <= s && s <= _Qabx
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
maxAltTaglen = len("en-US-POSIX")
|
|
||||||
maxLen = maxAltTaglen
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// grandfatheredMap holds a mapping from legacy and grandfathered tags to
|
|
||||||
// their base language or index to more elaborate tag.
|
|
||||||
grandfatheredMap = map[[maxLen]byte]int16{
|
|
||||||
[maxLen]byte{'a', 'r', 't', '-', 'l', 'o', 'j', 'b', 'a', 'n'}: _jbo, // art-lojban
|
|
||||||
[maxLen]byte{'i', '-', 'a', 'm', 'i'}: _ami, // i-ami
|
|
||||||
[maxLen]byte{'i', '-', 'b', 'n', 'n'}: _bnn, // i-bnn
|
|
||||||
[maxLen]byte{'i', '-', 'h', 'a', 'k'}: _hak, // i-hak
|
|
||||||
[maxLen]byte{'i', '-', 'k', 'l', 'i', 'n', 'g', 'o', 'n'}: _tlh, // i-klingon
|
|
||||||
[maxLen]byte{'i', '-', 'l', 'u', 'x'}: _lb, // i-lux
|
|
||||||
[maxLen]byte{'i', '-', 'n', 'a', 'v', 'a', 'j', 'o'}: _nv, // i-navajo
|
|
||||||
[maxLen]byte{'i', '-', 'p', 'w', 'n'}: _pwn, // i-pwn
|
|
||||||
[maxLen]byte{'i', '-', 't', 'a', 'o'}: _tao, // i-tao
|
|
||||||
[maxLen]byte{'i', '-', 't', 'a', 'y'}: _tay, // i-tay
|
|
||||||
[maxLen]byte{'i', '-', 't', 's', 'u'}: _tsu, // i-tsu
|
|
||||||
[maxLen]byte{'n', 'o', '-', 'b', 'o', 'k'}: _nb, // no-bok
|
|
||||||
[maxLen]byte{'n', 'o', '-', 'n', 'y', 'n'}: _nn, // no-nyn
|
|
||||||
[maxLen]byte{'s', 'g', 'n', '-', 'b', 'e', '-', 'f', 'r'}: _sfb, // sgn-BE-FR
|
|
||||||
[maxLen]byte{'s', 'g', 'n', '-', 'b', 'e', '-', 'n', 'l'}: _vgt, // sgn-BE-NL
|
|
||||||
[maxLen]byte{'s', 'g', 'n', '-', 'c', 'h', '-', 'd', 'e'}: _sgg, // sgn-CH-DE
|
|
||||||
[maxLen]byte{'z', 'h', '-', 'g', 'u', 'o', 'y', 'u'}: _cmn, // zh-guoyu
|
|
||||||
[maxLen]byte{'z', 'h', '-', 'h', 'a', 'k', 'k', 'a'}: _hak, // zh-hakka
|
|
||||||
[maxLen]byte{'z', 'h', '-', 'm', 'i', 'n', '-', 'n', 'a', 'n'}: _nan, // zh-min-nan
|
|
||||||
[maxLen]byte{'z', 'h', '-', 'x', 'i', 'a', 'n', 'g'}: _hsn, // zh-xiang
|
|
||||||
|
|
||||||
// Grandfathered tags with no modern replacement will be converted as
|
|
||||||
// follows:
|
|
||||||
[maxLen]byte{'c', 'e', 'l', '-', 'g', 'a', 'u', 'l', 'i', 's', 'h'}: -1, // cel-gaulish
|
|
||||||
[maxLen]byte{'e', 'n', '-', 'g', 'b', '-', 'o', 'e', 'd'}: -2, // en-GB-oed
|
|
||||||
[maxLen]byte{'i', '-', 'd', 'e', 'f', 'a', 'u', 'l', 't'}: -3, // i-default
|
|
||||||
[maxLen]byte{'i', '-', 'e', 'n', 'o', 'c', 'h', 'i', 'a', 'n'}: -4, // i-enochian
|
|
||||||
[maxLen]byte{'i', '-', 'm', 'i', 'n', 'g', 'o'}: -5, // i-mingo
|
|
||||||
[maxLen]byte{'z', 'h', '-', 'm', 'i', 'n'}: -6, // zh-min
|
|
||||||
|
|
||||||
// CLDR-specific tag.
|
|
||||||
[maxLen]byte{'r', 'o', 'o', 't'}: 0, // root
|
|
||||||
[maxLen]byte{'e', 'n', '-', 'u', 's', '-', 'p', 'o', 's', 'i', 'x'}: -7, // en_US_POSIX"
|
|
||||||
}
|
|
||||||
|
|
||||||
altTagIndex = [...]uint8{0, 17, 31, 45, 61, 74, 86, 102}
|
|
||||||
|
|
||||||
altTags = "xtg-x-cel-gaulishen-GB-oxendicten-x-i-defaultund-x-i-enochiansee-x-i-mingonan-x-zh-minen-US-u-va-posix"
|
|
||||||
)
|
|
||||||
|
|
||||||
func grandfathered(s [maxAltTaglen]byte) (t Tag, ok bool) {
|
|
||||||
if v, ok := grandfatheredMap[s]; ok {
|
|
||||||
if v < 0 {
|
|
||||||
return Make(altTags[altTagIndex[-v-1]:altTagIndex[-v]]), true
|
|
||||||
}
|
|
||||||
t.LangID = Language(v)
|
|
||||||
return t, true
|
|
||||||
}
|
|
||||||
return t, false
|
|
||||||
}
|
|
||||||
226
vendor/golang.org/x/text/internal/language/match.go
generated
vendored
226
vendor/golang.org/x/text/internal/language/match.go
generated
vendored
@@ -1,226 +0,0 @@
|
|||||||
// Copyright 2013 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 language
|
|
||||||
|
|
||||||
import "errors"
|
|
||||||
|
|
||||||
type scriptRegionFlags uint8
|
|
||||||
|
|
||||||
const (
|
|
||||||
isList = 1 << iota
|
|
||||||
scriptInFrom
|
|
||||||
regionInFrom
|
|
||||||
)
|
|
||||||
|
|
||||||
func (t *Tag) setUndefinedLang(id Language) {
|
|
||||||
if t.LangID == 0 {
|
|
||||||
t.LangID = id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Tag) setUndefinedScript(id Script) {
|
|
||||||
if t.ScriptID == 0 {
|
|
||||||
t.ScriptID = id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Tag) setUndefinedRegion(id Region) {
|
|
||||||
if t.RegionID == 0 || t.RegionID.Contains(id) {
|
|
||||||
t.RegionID = id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ErrMissingLikelyTagsData indicates no information was available
|
|
||||||
// to compute likely values of missing tags.
|
|
||||||
var ErrMissingLikelyTagsData = errors.New("missing likely tags data")
|
|
||||||
|
|
||||||
// addLikelySubtags sets subtags to their most likely value, given the locale.
|
|
||||||
// In most cases this means setting fields for unknown values, but in some
|
|
||||||
// cases it may alter a value. It returns an ErrMissingLikelyTagsData error
|
|
||||||
// if the given locale cannot be expanded.
|
|
||||||
func (t Tag) addLikelySubtags() (Tag, error) {
|
|
||||||
id, err := addTags(t)
|
|
||||||
if err != nil {
|
|
||||||
return t, err
|
|
||||||
} else if id.equalTags(t) {
|
|
||||||
return t, nil
|
|
||||||
}
|
|
||||||
id.RemakeString()
|
|
||||||
return id, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// specializeRegion attempts to specialize a group region.
|
|
||||||
func specializeRegion(t *Tag) bool {
|
|
||||||
if i := regionInclusion[t.RegionID]; i < nRegionGroups {
|
|
||||||
x := likelyRegionGroup[i]
|
|
||||||
if Language(x.lang) == t.LangID && Script(x.script) == t.ScriptID {
|
|
||||||
t.RegionID = Region(x.region)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Maximize returns a new tag with missing tags filled in.
|
|
||||||
func (t Tag) Maximize() (Tag, error) {
|
|
||||||
return addTags(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func addTags(t Tag) (Tag, error) {
|
|
||||||
// We leave private use identifiers alone.
|
|
||||||
if t.IsPrivateUse() {
|
|
||||||
return t, nil
|
|
||||||
}
|
|
||||||
if t.ScriptID != 0 && t.RegionID != 0 {
|
|
||||||
if t.LangID != 0 {
|
|
||||||
// already fully specified
|
|
||||||
specializeRegion(&t)
|
|
||||||
return t, nil
|
|
||||||
}
|
|
||||||
// Search matches for und-script-region. Note that for these cases
|
|
||||||
// region will never be a group so there is no need to check for this.
|
|
||||||
list := likelyRegion[t.RegionID : t.RegionID+1]
|
|
||||||
if x := list[0]; x.flags&isList != 0 {
|
|
||||||
list = likelyRegionList[x.lang : x.lang+uint16(x.script)]
|
|
||||||
}
|
|
||||||
for _, x := range list {
|
|
||||||
// Deviating from the spec. See match_test.go for details.
|
|
||||||
if Script(x.script) == t.ScriptID {
|
|
||||||
t.setUndefinedLang(Language(x.lang))
|
|
||||||
return t, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if t.LangID != 0 {
|
|
||||||
// Search matches for lang-script and lang-region, where lang != und.
|
|
||||||
if t.LangID < langNoIndexOffset {
|
|
||||||
x := likelyLang[t.LangID]
|
|
||||||
if x.flags&isList != 0 {
|
|
||||||
list := likelyLangList[x.region : x.region+uint16(x.script)]
|
|
||||||
if t.ScriptID != 0 {
|
|
||||||
for _, x := range list {
|
|
||||||
if Script(x.script) == t.ScriptID && x.flags&scriptInFrom != 0 {
|
|
||||||
t.setUndefinedRegion(Region(x.region))
|
|
||||||
return t, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if t.RegionID != 0 {
|
|
||||||
count := 0
|
|
||||||
goodScript := true
|
|
||||||
tt := t
|
|
||||||
for _, x := range list {
|
|
||||||
// We visit all entries for which the script was not
|
|
||||||
// defined, including the ones where the region was not
|
|
||||||
// defined. This allows for proper disambiguation within
|
|
||||||
// regions.
|
|
||||||
if x.flags&scriptInFrom == 0 && t.RegionID.Contains(Region(x.region)) {
|
|
||||||
tt.RegionID = Region(x.region)
|
|
||||||
tt.setUndefinedScript(Script(x.script))
|
|
||||||
goodScript = goodScript && tt.ScriptID == Script(x.script)
|
|
||||||
count++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if count == 1 {
|
|
||||||
return tt, nil
|
|
||||||
}
|
|
||||||
// Even if we fail to find a unique Region, we might have
|
|
||||||
// an unambiguous script.
|
|
||||||
if goodScript {
|
|
||||||
t.ScriptID = tt.ScriptID
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Search matches for und-script.
|
|
||||||
if t.ScriptID != 0 {
|
|
||||||
x := likelyScript[t.ScriptID]
|
|
||||||
if x.region != 0 {
|
|
||||||
t.setUndefinedRegion(Region(x.region))
|
|
||||||
t.setUndefinedLang(Language(x.lang))
|
|
||||||
return t, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Search matches for und-region. If und-script-region exists, it would
|
|
||||||
// have been found earlier.
|
|
||||||
if t.RegionID != 0 {
|
|
||||||
if i := regionInclusion[t.RegionID]; i < nRegionGroups {
|
|
||||||
x := likelyRegionGroup[i]
|
|
||||||
if x.region != 0 {
|
|
||||||
t.setUndefinedLang(Language(x.lang))
|
|
||||||
t.setUndefinedScript(Script(x.script))
|
|
||||||
t.RegionID = Region(x.region)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
x := likelyRegion[t.RegionID]
|
|
||||||
if x.flags&isList != 0 {
|
|
||||||
x = likelyRegionList[x.lang]
|
|
||||||
}
|
|
||||||
if x.script != 0 && x.flags != scriptInFrom {
|
|
||||||
t.setUndefinedLang(Language(x.lang))
|
|
||||||
t.setUndefinedScript(Script(x.script))
|
|
||||||
return t, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Search matches for lang.
|
|
||||||
if t.LangID < langNoIndexOffset {
|
|
||||||
x := likelyLang[t.LangID]
|
|
||||||
if x.flags&isList != 0 {
|
|
||||||
x = likelyLangList[x.region]
|
|
||||||
}
|
|
||||||
if x.region != 0 {
|
|
||||||
t.setUndefinedScript(Script(x.script))
|
|
||||||
t.setUndefinedRegion(Region(x.region))
|
|
||||||
}
|
|
||||||
specializeRegion(&t)
|
|
||||||
if t.LangID == 0 {
|
|
||||||
t.LangID = _en // default language
|
|
||||||
}
|
|
||||||
return t, nil
|
|
||||||
}
|
|
||||||
return t, ErrMissingLikelyTagsData
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Tag) setTagsFrom(id Tag) {
|
|
||||||
t.LangID = id.LangID
|
|
||||||
t.ScriptID = id.ScriptID
|
|
||||||
t.RegionID = id.RegionID
|
|
||||||
}
|
|
||||||
|
|
||||||
// minimize removes the region or script subtags from t such that
|
|
||||||
// t.addLikelySubtags() == t.minimize().addLikelySubtags().
|
|
||||||
func (t Tag) minimize() (Tag, error) {
|
|
||||||
t, err := minimizeTags(t)
|
|
||||||
if err != nil {
|
|
||||||
return t, err
|
|
||||||
}
|
|
||||||
t.RemakeString()
|
|
||||||
return t, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// minimizeTags mimics the behavior of the ICU 51 C implementation.
|
|
||||||
func minimizeTags(t Tag) (Tag, error) {
|
|
||||||
if t.equalTags(Und) {
|
|
||||||
return t, nil
|
|
||||||
}
|
|
||||||
max, err := addTags(t)
|
|
||||||
if err != nil {
|
|
||||||
return t, err
|
|
||||||
}
|
|
||||||
for _, id := range [...]Tag{
|
|
||||||
{LangID: t.LangID},
|
|
||||||
{LangID: t.LangID, RegionID: t.RegionID},
|
|
||||||
{LangID: t.LangID, ScriptID: t.ScriptID},
|
|
||||||
} {
|
|
||||||
if x, err := addTags(id); err == nil && max.equalTags(x) {
|
|
||||||
t.setTagsFrom(id)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return t, nil
|
|
||||||
}
|
|
||||||
608
vendor/golang.org/x/text/internal/language/parse.go
generated
vendored
608
vendor/golang.org/x/text/internal/language/parse.go
generated
vendored
@@ -1,608 +0,0 @@
|
|||||||
// Copyright 2013 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 language
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"sort"
|
|
||||||
|
|
||||||
"golang.org/x/text/internal/tag"
|
|
||||||
)
|
|
||||||
|
|
||||||
// isAlpha returns true if the byte is not a digit.
|
|
||||||
// b must be an ASCII letter or digit.
|
|
||||||
func isAlpha(b byte) bool {
|
|
||||||
return b > '9'
|
|
||||||
}
|
|
||||||
|
|
||||||
// isAlphaNum returns true if the string contains only ASCII letters or digits.
|
|
||||||
func isAlphaNum(s []byte) bool {
|
|
||||||
for _, c := range s {
|
|
||||||
if !('a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || '0' <= c && c <= '9') {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// ErrSyntax is returned by any of the parsing functions when the
|
|
||||||
// input is not well-formed, according to BCP 47.
|
|
||||||
// TODO: return the position at which the syntax error occurred?
|
|
||||||
var ErrSyntax = errors.New("language: tag is not well-formed")
|
|
||||||
|
|
||||||
// ErrDuplicateKey is returned when a tag contains the same key twice with
|
|
||||||
// different values in the -u section.
|
|
||||||
var ErrDuplicateKey = errors.New("language: different values for same key in -u extension")
|
|
||||||
|
|
||||||
// ValueError is returned by any of the parsing functions when the
|
|
||||||
// input is well-formed but the respective subtag is not recognized
|
|
||||||
// as a valid value.
|
|
||||||
type ValueError struct {
|
|
||||||
v [8]byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewValueError creates a new ValueError.
|
|
||||||
func NewValueError(tag []byte) ValueError {
|
|
||||||
var e ValueError
|
|
||||||
copy(e.v[:], tag)
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e ValueError) tag() []byte {
|
|
||||||
n := bytes.IndexByte(e.v[:], 0)
|
|
||||||
if n == -1 {
|
|
||||||
n = 8
|
|
||||||
}
|
|
||||||
return e.v[:n]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error implements the error interface.
|
|
||||||
func (e ValueError) Error() string {
|
|
||||||
return fmt.Sprintf("language: subtag %q is well-formed but unknown", e.tag())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Subtag returns the subtag for which the error occurred.
|
|
||||||
func (e ValueError) Subtag() string {
|
|
||||||
return string(e.tag())
|
|
||||||
}
|
|
||||||
|
|
||||||
// scanner is used to scan BCP 47 tokens, which are separated by _ or -.
|
|
||||||
type scanner struct {
|
|
||||||
b []byte
|
|
||||||
bytes [max99thPercentileSize]byte
|
|
||||||
token []byte
|
|
||||||
start int // start position of the current token
|
|
||||||
end int // end position of the current token
|
|
||||||
next int // next point for scan
|
|
||||||
err error
|
|
||||||
done bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeScannerString(s string) scanner {
|
|
||||||
scan := scanner{}
|
|
||||||
if len(s) <= len(scan.bytes) {
|
|
||||||
scan.b = scan.bytes[:copy(scan.bytes[:], s)]
|
|
||||||
} else {
|
|
||||||
scan.b = []byte(s)
|
|
||||||
}
|
|
||||||
scan.init()
|
|
||||||
return scan
|
|
||||||
}
|
|
||||||
|
|
||||||
// makeScanner returns a scanner using b as the input buffer.
|
|
||||||
// b is not copied and may be modified by the scanner routines.
|
|
||||||
func makeScanner(b []byte) scanner {
|
|
||||||
scan := scanner{b: b}
|
|
||||||
scan.init()
|
|
||||||
return scan
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *scanner) init() {
|
|
||||||
for i, c := range s.b {
|
|
||||||
if c == '_' {
|
|
||||||
s.b[i] = '-'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
s.scan()
|
|
||||||
}
|
|
||||||
|
|
||||||
// restToLower converts the string between start and end to lower case.
|
|
||||||
func (s *scanner) toLower(start, end int) {
|
|
||||||
for i := start; i < end; i++ {
|
|
||||||
c := s.b[i]
|
|
||||||
if 'A' <= c && c <= 'Z' {
|
|
||||||
s.b[i] += 'a' - 'A'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *scanner) setError(e error) {
|
|
||||||
if s.err == nil || (e == ErrSyntax && s.err != ErrSyntax) {
|
|
||||||
s.err = e
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// resizeRange shrinks or grows the array at position oldStart such that
|
|
||||||
// a new string of size newSize can fit between oldStart and oldEnd.
|
|
||||||
// Sets the scan point to after the resized range.
|
|
||||||
func (s *scanner) resizeRange(oldStart, oldEnd, newSize int) {
|
|
||||||
s.start = oldStart
|
|
||||||
if end := oldStart + newSize; end != oldEnd {
|
|
||||||
diff := end - oldEnd
|
|
||||||
var b []byte
|
|
||||||
if n := len(s.b) + diff; n > cap(s.b) {
|
|
||||||
b = make([]byte, n)
|
|
||||||
copy(b, s.b[:oldStart])
|
|
||||||
} else {
|
|
||||||
b = s.b[:n]
|
|
||||||
}
|
|
||||||
copy(b[end:], s.b[oldEnd:])
|
|
||||||
s.b = b
|
|
||||||
s.next = end + (s.next - s.end)
|
|
||||||
s.end = end
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// replace replaces the current token with repl.
|
|
||||||
func (s *scanner) replace(repl string) {
|
|
||||||
s.resizeRange(s.start, s.end, len(repl))
|
|
||||||
copy(s.b[s.start:], repl)
|
|
||||||
}
|
|
||||||
|
|
||||||
// gobble removes the current token from the input.
|
|
||||||
// Caller must call scan after calling gobble.
|
|
||||||
func (s *scanner) gobble(e error) {
|
|
||||||
s.setError(e)
|
|
||||||
if s.start == 0 {
|
|
||||||
s.b = s.b[:+copy(s.b, s.b[s.next:])]
|
|
||||||
s.end = 0
|
|
||||||
} else {
|
|
||||||
s.b = s.b[:s.start-1+copy(s.b[s.start-1:], s.b[s.end:])]
|
|
||||||
s.end = s.start - 1
|
|
||||||
}
|
|
||||||
s.next = s.start
|
|
||||||
}
|
|
||||||
|
|
||||||
// deleteRange removes the given range from s.b before the current token.
|
|
||||||
func (s *scanner) deleteRange(start, end int) {
|
|
||||||
s.b = s.b[:start+copy(s.b[start:], s.b[end:])]
|
|
||||||
diff := end - start
|
|
||||||
s.next -= diff
|
|
||||||
s.start -= diff
|
|
||||||
s.end -= diff
|
|
||||||
}
|
|
||||||
|
|
||||||
// scan parses the next token of a BCP 47 string. Tokens that are larger
|
|
||||||
// than 8 characters or include non-alphanumeric characters result in an error
|
|
||||||
// and are gobbled and removed from the output.
|
|
||||||
// It returns the end position of the last token consumed.
|
|
||||||
func (s *scanner) scan() (end int) {
|
|
||||||
end = s.end
|
|
||||||
s.token = nil
|
|
||||||
for s.start = s.next; s.next < len(s.b); {
|
|
||||||
i := bytes.IndexByte(s.b[s.next:], '-')
|
|
||||||
if i == -1 {
|
|
||||||
s.end = len(s.b)
|
|
||||||
s.next = len(s.b)
|
|
||||||
i = s.end - s.start
|
|
||||||
} else {
|
|
||||||
s.end = s.next + i
|
|
||||||
s.next = s.end + 1
|
|
||||||
}
|
|
||||||
token := s.b[s.start:s.end]
|
|
||||||
if i < 1 || i > 8 || !isAlphaNum(token) {
|
|
||||||
s.gobble(ErrSyntax)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
s.token = token
|
|
||||||
return end
|
|
||||||
}
|
|
||||||
if n := len(s.b); n > 0 && s.b[n-1] == '-' {
|
|
||||||
s.setError(ErrSyntax)
|
|
||||||
s.b = s.b[:len(s.b)-1]
|
|
||||||
}
|
|
||||||
s.done = true
|
|
||||||
return end
|
|
||||||
}
|
|
||||||
|
|
||||||
// acceptMinSize parses multiple tokens of the given size or greater.
|
|
||||||
// It returns the end position of the last token consumed.
|
|
||||||
func (s *scanner) acceptMinSize(min int) (end int) {
|
|
||||||
end = s.end
|
|
||||||
s.scan()
|
|
||||||
for ; len(s.token) >= min; s.scan() {
|
|
||||||
end = s.end
|
|
||||||
}
|
|
||||||
return end
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse parses the given BCP 47 string and returns a valid Tag. If parsing
|
|
||||||
// failed it returns an error and any part of the tag that could be parsed.
|
|
||||||
// If parsing succeeded but an unknown value was found, it returns
|
|
||||||
// ValueError. The Tag returned in this case is just stripped of the unknown
|
|
||||||
// value. All other values are preserved. It accepts tags in the BCP 47 format
|
|
||||||
// and extensions to this standard defined in
|
|
||||||
// https://www.unicode.org/reports/tr35/#Unicode_Language_and_Locale_Identifiers.
|
|
||||||
func Parse(s string) (t Tag, err error) {
|
|
||||||
// TODO: consider supporting old-style locale key-value pairs.
|
|
||||||
if s == "" {
|
|
||||||
return Und, ErrSyntax
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
if recover() != nil {
|
|
||||||
t = Und
|
|
||||||
err = ErrSyntax
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
if len(s) <= maxAltTaglen {
|
|
||||||
b := [maxAltTaglen]byte{}
|
|
||||||
for i, c := range s {
|
|
||||||
// Generating invalid UTF-8 is okay as it won't match.
|
|
||||||
if 'A' <= c && c <= 'Z' {
|
|
||||||
c += 'a' - 'A'
|
|
||||||
} else if c == '_' {
|
|
||||||
c = '-'
|
|
||||||
}
|
|
||||||
b[i] = byte(c)
|
|
||||||
}
|
|
||||||
if t, ok := grandfathered(b); ok {
|
|
||||||
return t, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
scan := makeScannerString(s)
|
|
||||||
return parse(&scan, s)
|
|
||||||
}
|
|
||||||
|
|
||||||
func parse(scan *scanner, s string) (t Tag, err error) {
|
|
||||||
t = Und
|
|
||||||
var end int
|
|
||||||
if n := len(scan.token); n <= 1 {
|
|
||||||
scan.toLower(0, len(scan.b))
|
|
||||||
if n == 0 || scan.token[0] != 'x' {
|
|
||||||
return t, ErrSyntax
|
|
||||||
}
|
|
||||||
end = parseExtensions(scan)
|
|
||||||
} else if n >= 4 {
|
|
||||||
return Und, ErrSyntax
|
|
||||||
} else { // the usual case
|
|
||||||
t, end = parseTag(scan, true)
|
|
||||||
if n := len(scan.token); n == 1 {
|
|
||||||
t.pExt = uint16(end)
|
|
||||||
end = parseExtensions(scan)
|
|
||||||
} else if end < len(scan.b) {
|
|
||||||
scan.setError(ErrSyntax)
|
|
||||||
scan.b = scan.b[:end]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if int(t.pVariant) < len(scan.b) {
|
|
||||||
if end < len(s) {
|
|
||||||
s = s[:end]
|
|
||||||
}
|
|
||||||
if len(s) > 0 && tag.Compare(s, scan.b) == 0 {
|
|
||||||
t.str = s
|
|
||||||
} else {
|
|
||||||
t.str = string(scan.b)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
t.pVariant, t.pExt = 0, 0
|
|
||||||
}
|
|
||||||
return t, scan.err
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseTag parses language, script, region and variants.
|
|
||||||
// It returns a Tag and the end position in the input that was parsed.
|
|
||||||
// If doNorm is true, then <lang>-<extlang> will be normalized to <extlang>.
|
|
||||||
func parseTag(scan *scanner, doNorm bool) (t Tag, end int) {
|
|
||||||
var e error
|
|
||||||
// TODO: set an error if an unknown lang, script or region is encountered.
|
|
||||||
t.LangID, e = getLangID(scan.token)
|
|
||||||
scan.setError(e)
|
|
||||||
scan.replace(t.LangID.String())
|
|
||||||
langStart := scan.start
|
|
||||||
end = scan.scan()
|
|
||||||
for len(scan.token) == 3 && isAlpha(scan.token[0]) {
|
|
||||||
// From http://tools.ietf.org/html/bcp47, <lang>-<extlang> tags are equivalent
|
|
||||||
// to a tag of the form <extlang>.
|
|
||||||
if doNorm {
|
|
||||||
lang, e := getLangID(scan.token)
|
|
||||||
if lang != 0 {
|
|
||||||
t.LangID = lang
|
|
||||||
langStr := lang.String()
|
|
||||||
copy(scan.b[langStart:], langStr)
|
|
||||||
scan.b[langStart+len(langStr)] = '-'
|
|
||||||
scan.start = langStart + len(langStr) + 1
|
|
||||||
}
|
|
||||||
scan.gobble(e)
|
|
||||||
}
|
|
||||||
end = scan.scan()
|
|
||||||
}
|
|
||||||
if len(scan.token) == 4 && isAlpha(scan.token[0]) {
|
|
||||||
t.ScriptID, e = getScriptID(script, scan.token)
|
|
||||||
if t.ScriptID == 0 {
|
|
||||||
scan.gobble(e)
|
|
||||||
}
|
|
||||||
end = scan.scan()
|
|
||||||
}
|
|
||||||
if n := len(scan.token); n >= 2 && n <= 3 {
|
|
||||||
t.RegionID, e = getRegionID(scan.token)
|
|
||||||
if t.RegionID == 0 {
|
|
||||||
scan.gobble(e)
|
|
||||||
} else {
|
|
||||||
scan.replace(t.RegionID.String())
|
|
||||||
}
|
|
||||||
end = scan.scan()
|
|
||||||
}
|
|
||||||
scan.toLower(scan.start, len(scan.b))
|
|
||||||
t.pVariant = byte(end)
|
|
||||||
end = parseVariants(scan, end, t)
|
|
||||||
t.pExt = uint16(end)
|
|
||||||
return t, end
|
|
||||||
}
|
|
||||||
|
|
||||||
var separator = []byte{'-'}
|
|
||||||
|
|
||||||
// parseVariants scans tokens as long as each token is a valid variant string.
|
|
||||||
// Duplicate variants are removed.
|
|
||||||
func parseVariants(scan *scanner, end int, t Tag) int {
|
|
||||||
start := scan.start
|
|
||||||
varIDBuf := [4]uint8{}
|
|
||||||
variantBuf := [4][]byte{}
|
|
||||||
varID := varIDBuf[:0]
|
|
||||||
variant := variantBuf[:0]
|
|
||||||
last := -1
|
|
||||||
needSort := false
|
|
||||||
for ; len(scan.token) >= 4; scan.scan() {
|
|
||||||
// TODO: measure the impact of needing this conversion and redesign
|
|
||||||
// the data structure if there is an issue.
|
|
||||||
v, ok := variantIndex[string(scan.token)]
|
|
||||||
if !ok {
|
|
||||||
// unknown variant
|
|
||||||
// TODO: allow user-defined variants?
|
|
||||||
scan.gobble(NewValueError(scan.token))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
varID = append(varID, v)
|
|
||||||
variant = append(variant, scan.token)
|
|
||||||
if !needSort {
|
|
||||||
if last < int(v) {
|
|
||||||
last = int(v)
|
|
||||||
} else {
|
|
||||||
needSort = true
|
|
||||||
// There is no legal combinations of more than 7 variants
|
|
||||||
// (and this is by no means a useful sequence).
|
|
||||||
const maxVariants = 8
|
|
||||||
if len(varID) > maxVariants {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
end = scan.end
|
|
||||||
}
|
|
||||||
if needSort {
|
|
||||||
sort.Sort(variantsSort{varID, variant})
|
|
||||||
k, l := 0, -1
|
|
||||||
for i, v := range varID {
|
|
||||||
w := int(v)
|
|
||||||
if l == w {
|
|
||||||
// Remove duplicates.
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
varID[k] = varID[i]
|
|
||||||
variant[k] = variant[i]
|
|
||||||
k++
|
|
||||||
l = w
|
|
||||||
}
|
|
||||||
if str := bytes.Join(variant[:k], separator); len(str) == 0 {
|
|
||||||
end = start - 1
|
|
||||||
} else {
|
|
||||||
scan.resizeRange(start, end, len(str))
|
|
||||||
copy(scan.b[scan.start:], str)
|
|
||||||
end = scan.end
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return end
|
|
||||||
}
|
|
||||||
|
|
||||||
type variantsSort struct {
|
|
||||||
i []uint8
|
|
||||||
v [][]byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s variantsSort) Len() int {
|
|
||||||
return len(s.i)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s variantsSort) Swap(i, j int) {
|
|
||||||
s.i[i], s.i[j] = s.i[j], s.i[i]
|
|
||||||
s.v[i], s.v[j] = s.v[j], s.v[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s variantsSort) Less(i, j int) bool {
|
|
||||||
return s.i[i] < s.i[j]
|
|
||||||
}
|
|
||||||
|
|
||||||
type bytesSort struct {
|
|
||||||
b [][]byte
|
|
||||||
n int // first n bytes to compare
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b bytesSort) Len() int {
|
|
||||||
return len(b.b)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b bytesSort) Swap(i, j int) {
|
|
||||||
b.b[i], b.b[j] = b.b[j], b.b[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b bytesSort) Less(i, j int) bool {
|
|
||||||
for k := 0; k < b.n; k++ {
|
|
||||||
if b.b[i][k] == b.b[j][k] {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return b.b[i][k] < b.b[j][k]
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseExtensions parses and normalizes the extensions in the buffer.
|
|
||||||
// It returns the last position of scan.b that is part of any extension.
|
|
||||||
// It also trims scan.b to remove excess parts accordingly.
|
|
||||||
func parseExtensions(scan *scanner) int {
|
|
||||||
start := scan.start
|
|
||||||
exts := [][]byte{}
|
|
||||||
private := []byte{}
|
|
||||||
end := scan.end
|
|
||||||
for len(scan.token) == 1 {
|
|
||||||
extStart := scan.start
|
|
||||||
ext := scan.token[0]
|
|
||||||
end = parseExtension(scan)
|
|
||||||
extension := scan.b[extStart:end]
|
|
||||||
if len(extension) < 3 || (ext != 'x' && len(extension) < 4) {
|
|
||||||
scan.setError(ErrSyntax)
|
|
||||||
end = extStart
|
|
||||||
continue
|
|
||||||
} else if start == extStart && (ext == 'x' || scan.start == len(scan.b)) {
|
|
||||||
scan.b = scan.b[:end]
|
|
||||||
return end
|
|
||||||
} else if ext == 'x' {
|
|
||||||
private = extension
|
|
||||||
break
|
|
||||||
}
|
|
||||||
exts = append(exts, extension)
|
|
||||||
}
|
|
||||||
sort.Sort(bytesSort{exts, 1})
|
|
||||||
if len(private) > 0 {
|
|
||||||
exts = append(exts, private)
|
|
||||||
}
|
|
||||||
scan.b = scan.b[:start]
|
|
||||||
if len(exts) > 0 {
|
|
||||||
scan.b = append(scan.b, bytes.Join(exts, separator)...)
|
|
||||||
} else if start > 0 {
|
|
||||||
// Strip trailing '-'.
|
|
||||||
scan.b = scan.b[:start-1]
|
|
||||||
}
|
|
||||||
return end
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseExtension parses a single extension and returns the position of
|
|
||||||
// the extension end.
|
|
||||||
func parseExtension(scan *scanner) int {
|
|
||||||
start, end := scan.start, scan.end
|
|
||||||
switch scan.token[0] {
|
|
||||||
case 'u': // https://www.ietf.org/rfc/rfc6067.txt
|
|
||||||
attrStart := end
|
|
||||||
scan.scan()
|
|
||||||
for last := []byte{}; len(scan.token) > 2; scan.scan() {
|
|
||||||
if bytes.Compare(scan.token, last) != -1 {
|
|
||||||
// Attributes are unsorted. Start over from scratch.
|
|
||||||
p := attrStart + 1
|
|
||||||
scan.next = p
|
|
||||||
attrs := [][]byte{}
|
|
||||||
for scan.scan(); len(scan.token) > 2; scan.scan() {
|
|
||||||
attrs = append(attrs, scan.token)
|
|
||||||
end = scan.end
|
|
||||||
}
|
|
||||||
sort.Sort(bytesSort{attrs, 3})
|
|
||||||
copy(scan.b[p:], bytes.Join(attrs, separator))
|
|
||||||
break
|
|
||||||
}
|
|
||||||
last = scan.token
|
|
||||||
end = scan.end
|
|
||||||
}
|
|
||||||
// Scan key-type sequences. A key is of length 2 and may be followed
|
|
||||||
// by 0 or more "type" subtags from 3 to the maximum of 8 letters.
|
|
||||||
var last, key []byte
|
|
||||||
for attrEnd := end; len(scan.token) == 2; last = key {
|
|
||||||
key = scan.token
|
|
||||||
end = scan.end
|
|
||||||
for scan.scan(); end < scan.end && len(scan.token) > 2; scan.scan() {
|
|
||||||
end = scan.end
|
|
||||||
}
|
|
||||||
// TODO: check key value validity
|
|
||||||
if bytes.Compare(key, last) != 1 || scan.err != nil {
|
|
||||||
// We have an invalid key or the keys are not sorted.
|
|
||||||
// Start scanning keys from scratch and reorder.
|
|
||||||
p := attrEnd + 1
|
|
||||||
scan.next = p
|
|
||||||
keys := [][]byte{}
|
|
||||||
for scan.scan(); len(scan.token) == 2; {
|
|
||||||
keyStart := scan.start
|
|
||||||
end = scan.end
|
|
||||||
for scan.scan(); end < scan.end && len(scan.token) > 2; scan.scan() {
|
|
||||||
end = scan.end
|
|
||||||
}
|
|
||||||
keys = append(keys, scan.b[keyStart:end])
|
|
||||||
}
|
|
||||||
sort.Stable(bytesSort{keys, 2})
|
|
||||||
if n := len(keys); n > 0 {
|
|
||||||
k := 0
|
|
||||||
for i := 1; i < n; i++ {
|
|
||||||
if !bytes.Equal(keys[k][:2], keys[i][:2]) {
|
|
||||||
k++
|
|
||||||
keys[k] = keys[i]
|
|
||||||
} else if !bytes.Equal(keys[k], keys[i]) {
|
|
||||||
scan.setError(ErrDuplicateKey)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
keys = keys[:k+1]
|
|
||||||
}
|
|
||||||
reordered := bytes.Join(keys, separator)
|
|
||||||
if e := p + len(reordered); e < end {
|
|
||||||
scan.deleteRange(e, end)
|
|
||||||
end = e
|
|
||||||
}
|
|
||||||
copy(scan.b[p:], reordered)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case 't': // https://www.ietf.org/rfc/rfc6497.txt
|
|
||||||
scan.scan()
|
|
||||||
if n := len(scan.token); n >= 2 && n <= 3 && isAlpha(scan.token[1]) {
|
|
||||||
_, end = parseTag(scan, false)
|
|
||||||
scan.toLower(start, end)
|
|
||||||
}
|
|
||||||
for len(scan.token) == 2 && !isAlpha(scan.token[1]) {
|
|
||||||
end = scan.acceptMinSize(3)
|
|
||||||
}
|
|
||||||
case 'x':
|
|
||||||
end = scan.acceptMinSize(1)
|
|
||||||
default:
|
|
||||||
end = scan.acceptMinSize(2)
|
|
||||||
}
|
|
||||||
return end
|
|
||||||
}
|
|
||||||
|
|
||||||
// getExtension returns the name, body and end position of the extension.
|
|
||||||
func getExtension(s string, p int) (end int, ext string) {
|
|
||||||
if s[p] == '-' {
|
|
||||||
p++
|
|
||||||
}
|
|
||||||
if s[p] == 'x' {
|
|
||||||
return len(s), s[p:]
|
|
||||||
}
|
|
||||||
end = nextExtension(s, p)
|
|
||||||
return end, s[p:end]
|
|
||||||
}
|
|
||||||
|
|
||||||
// nextExtension finds the next extension within the string, searching
|
|
||||||
// for the -<char>- pattern from position p.
|
|
||||||
// In the fast majority of cases, language tags will have at most
|
|
||||||
// one extension and extensions tend to be small.
|
|
||||||
func nextExtension(s string, p int) int {
|
|
||||||
for n := len(s) - 3; p < n; {
|
|
||||||
if s[p] == '-' {
|
|
||||||
if s[p+2] == '-' {
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
p += 3
|
|
||||||
} else {
|
|
||||||
p++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return len(s)
|
|
||||||
}
|
|
||||||
3494
vendor/golang.org/x/text/internal/language/tables.go
generated
vendored
3494
vendor/golang.org/x/text/internal/language/tables.go
generated
vendored
File diff suppressed because it is too large
Load Diff
48
vendor/golang.org/x/text/internal/language/tags.go
generated
vendored
48
vendor/golang.org/x/text/internal/language/tags.go
generated
vendored
@@ -1,48 +0,0 @@
|
|||||||
// Copyright 2013 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 language
|
|
||||||
|
|
||||||
// MustParse is like Parse, but panics if the given BCP 47 tag cannot be parsed.
|
|
||||||
// It simplifies safe initialization of Tag values.
|
|
||||||
func MustParse(s string) Tag {
|
|
||||||
t, err := Parse(s)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return t
|
|
||||||
}
|
|
||||||
|
|
||||||
// MustParseBase is like ParseBase, but panics if the given base cannot be parsed.
|
|
||||||
// It simplifies safe initialization of Base values.
|
|
||||||
func MustParseBase(s string) Language {
|
|
||||||
b, err := ParseBase(s)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// MustParseScript is like ParseScript, but panics if the given script cannot be
|
|
||||||
// parsed. It simplifies safe initialization of Script values.
|
|
||||||
func MustParseScript(s string) Script {
|
|
||||||
scr, err := ParseScript(s)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return scr
|
|
||||||
}
|
|
||||||
|
|
||||||
// MustParseRegion is like ParseRegion, but panics if the given region cannot be
|
|
||||||
// parsed. It simplifies safe initialization of Region values.
|
|
||||||
func MustParseRegion(s string) Region {
|
|
||||||
r, err := ParseRegion(s)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
// Und is the root language.
|
|
||||||
var Und Tag
|
|
||||||
55
vendor/golang.org/x/text/internal/number/common.go
generated
vendored
55
vendor/golang.org/x/text/internal/number/common.go
generated
vendored
@@ -1,55 +0,0 @@
|
|||||||
// Code generated by running "go generate" in golang.org/x/text. DO NOT EDIT.
|
|
||||||
|
|
||||||
package number
|
|
||||||
|
|
||||||
import (
|
|
||||||
"unicode/utf8"
|
|
||||||
|
|
||||||
"golang.org/x/text/internal/language/compact"
|
|
||||||
)
|
|
||||||
|
|
||||||
// A system identifies a CLDR numbering system.
|
|
||||||
type system byte
|
|
||||||
|
|
||||||
type systemData struct {
|
|
||||||
id system
|
|
||||||
digitSize byte // number of UTF-8 bytes per digit
|
|
||||||
zero [utf8.UTFMax]byte // UTF-8 sequence of zero digit.
|
|
||||||
}
|
|
||||||
|
|
||||||
// A SymbolType identifies a symbol of a specific kind.
|
|
||||||
type SymbolType int
|
|
||||||
|
|
||||||
const (
|
|
||||||
SymDecimal SymbolType = iota
|
|
||||||
SymGroup
|
|
||||||
SymList
|
|
||||||
SymPercentSign
|
|
||||||
SymPlusSign
|
|
||||||
SymMinusSign
|
|
||||||
SymExponential
|
|
||||||
SymSuperscriptingExponent
|
|
||||||
SymPerMille
|
|
||||||
SymInfinity
|
|
||||||
SymNan
|
|
||||||
SymTimeSeparator
|
|
||||||
|
|
||||||
NumSymbolTypes
|
|
||||||
)
|
|
||||||
|
|
||||||
const hasNonLatnMask = 0x8000
|
|
||||||
|
|
||||||
// symOffset is an offset into altSymData if the bit indicated by hasNonLatnMask
|
|
||||||
// is not 0 (with this bit masked out), and an offset into symIndex otherwise.
|
|
||||||
//
|
|
||||||
// TODO: this type can be a byte again if we use an indirection into altsymData
|
|
||||||
// and introduce an alt -> offset slice (the length of this will be number of
|
|
||||||
// alternatives plus 1). This also allows getting rid of the compactTag field
|
|
||||||
// in altSymData. In total this will save about 1K.
|
|
||||||
type symOffset uint16
|
|
||||||
|
|
||||||
type altSymData struct {
|
|
||||||
compactTag compact.ID
|
|
||||||
symIndex symOffset
|
|
||||||
system system
|
|
||||||
}
|
|
||||||
500
vendor/golang.org/x/text/internal/number/decimal.go
generated
vendored
500
vendor/golang.org/x/text/internal/number/decimal.go
generated
vendored
@@ -1,500 +0,0 @@
|
|||||||
// Copyright 2017 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.
|
|
||||||
|
|
||||||
//go:generate stringer -type RoundingMode
|
|
||||||
|
|
||||||
package number
|
|
||||||
|
|
||||||
import (
|
|
||||||
"math"
|
|
||||||
"strconv"
|
|
||||||
)
|
|
||||||
|
|
||||||
// RoundingMode determines how a number is rounded to the desired precision.
|
|
||||||
type RoundingMode byte
|
|
||||||
|
|
||||||
const (
|
|
||||||
ToNearestEven RoundingMode = iota // towards the nearest integer, or towards an even number if equidistant.
|
|
||||||
ToNearestZero // towards the nearest integer, or towards zero if equidistant.
|
|
||||||
ToNearestAway // towards the nearest integer, or away from zero if equidistant.
|
|
||||||
ToPositiveInf // towards infinity
|
|
||||||
ToNegativeInf // towards negative infinity
|
|
||||||
ToZero // towards zero
|
|
||||||
AwayFromZero // away from zero
|
|
||||||
numModes
|
|
||||||
)
|
|
||||||
|
|
||||||
const maxIntDigits = 20
|
|
||||||
|
|
||||||
// A Decimal represents a floating point number in decimal format.
|
|
||||||
// Digits represents a number [0, 1.0), and the absolute value represented by
|
|
||||||
// Decimal is Digits * 10^Exp. Leading and trailing zeros may be omitted and Exp
|
|
||||||
// may point outside a valid position in Digits.
|
|
||||||
//
|
|
||||||
// Examples:
|
|
||||||
//
|
|
||||||
// Number Decimal
|
|
||||||
// 12345 Digits: [1, 2, 3, 4, 5], Exp: 5
|
|
||||||
// 12.345 Digits: [1, 2, 3, 4, 5], Exp: 2
|
|
||||||
// 12000 Digits: [1, 2], Exp: 5
|
|
||||||
// 12000.00 Digits: [1, 2], Exp: 5
|
|
||||||
// 0.00123 Digits: [1, 2, 3], Exp: -2
|
|
||||||
// 0 Digits: [], Exp: 0
|
|
||||||
type Decimal struct {
|
|
||||||
digits
|
|
||||||
|
|
||||||
buf [maxIntDigits]byte
|
|
||||||
}
|
|
||||||
|
|
||||||
type digits struct {
|
|
||||||
Digits []byte // mantissa digits, big-endian
|
|
||||||
Exp int32 // exponent
|
|
||||||
Neg bool
|
|
||||||
Inf bool // Takes precedence over Digits and Exp.
|
|
||||||
NaN bool // Takes precedence over Inf.
|
|
||||||
}
|
|
||||||
|
|
||||||
// Digits represents a floating point number represented in digits of the
|
|
||||||
// base in which a number is to be displayed. It is similar to Decimal, but
|
|
||||||
// keeps track of trailing fraction zeros and the comma placement for
|
|
||||||
// engineering notation. Digits must have at least one digit.
|
|
||||||
//
|
|
||||||
// Examples:
|
|
||||||
//
|
|
||||||
// Number Decimal
|
|
||||||
// decimal
|
|
||||||
// 12345 Digits: [1, 2, 3, 4, 5], Exp: 5 End: 5
|
|
||||||
// 12.345 Digits: [1, 2, 3, 4, 5], Exp: 2 End: 5
|
|
||||||
// 12000 Digits: [1, 2], Exp: 5 End: 5
|
|
||||||
// 12000.00 Digits: [1, 2], Exp: 5 End: 7
|
|
||||||
// 0.00123 Digits: [1, 2, 3], Exp: -2 End: 3
|
|
||||||
// 0 Digits: [], Exp: 0 End: 1
|
|
||||||
// scientific (actual exp is Exp - Comma)
|
|
||||||
// 0e0 Digits: [0], Exp: 1, End: 1, Comma: 1
|
|
||||||
// .0e0 Digits: [0], Exp: 0, End: 1, Comma: 0
|
|
||||||
// 0.0e0 Digits: [0], Exp: 1, End: 2, Comma: 1
|
|
||||||
// 1.23e4 Digits: [1, 2, 3], Exp: 5, End: 3, Comma: 1
|
|
||||||
// .123e5 Digits: [1, 2, 3], Exp: 5, End: 3, Comma: 0
|
|
||||||
// engineering
|
|
||||||
// 12.3e3 Digits: [1, 2, 3], Exp: 5, End: 3, Comma: 2
|
|
||||||
type Digits struct {
|
|
||||||
digits
|
|
||||||
// End indicates the end position of the number.
|
|
||||||
End int32 // For decimals Exp <= End. For scientific len(Digits) <= End.
|
|
||||||
// Comma is used for the comma position for scientific (always 0 or 1) and
|
|
||||||
// engineering notation (always 0, 1, 2, or 3).
|
|
||||||
Comma uint8
|
|
||||||
// IsScientific indicates whether this number is to be rendered as a
|
|
||||||
// scientific number.
|
|
||||||
IsScientific bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Digits) NumFracDigits() int {
|
|
||||||
if d.Exp >= d.End {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return int(d.End - d.Exp)
|
|
||||||
}
|
|
||||||
|
|
||||||
// normalize returns a new Decimal with leading and trailing zeros removed.
|
|
||||||
func (d *Decimal) normalize() (n Decimal) {
|
|
||||||
n = *d
|
|
||||||
b := n.Digits
|
|
||||||
// Strip leading zeros. Resulting number of digits is significant digits.
|
|
||||||
for len(b) > 0 && b[0] == 0 {
|
|
||||||
b = b[1:]
|
|
||||||
n.Exp--
|
|
||||||
}
|
|
||||||
// Strip trailing zeros
|
|
||||||
for len(b) > 0 && b[len(b)-1] == 0 {
|
|
||||||
b = b[:len(b)-1]
|
|
||||||
}
|
|
||||||
if len(b) == 0 {
|
|
||||||
n.Exp = 0
|
|
||||||
}
|
|
||||||
n.Digits = b
|
|
||||||
return n
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Decimal) clear() {
|
|
||||||
b := d.Digits
|
|
||||||
if b == nil {
|
|
||||||
b = d.buf[:0]
|
|
||||||
}
|
|
||||||
*d = Decimal{}
|
|
||||||
d.Digits = b[:0]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *Decimal) String() string {
|
|
||||||
if x.NaN {
|
|
||||||
return "NaN"
|
|
||||||
}
|
|
||||||
var buf []byte
|
|
||||||
if x.Neg {
|
|
||||||
buf = append(buf, '-')
|
|
||||||
}
|
|
||||||
if x.Inf {
|
|
||||||
buf = append(buf, "Inf"...)
|
|
||||||
return string(buf)
|
|
||||||
}
|
|
||||||
switch {
|
|
||||||
case len(x.Digits) == 0:
|
|
||||||
buf = append(buf, '0')
|
|
||||||
case x.Exp <= 0:
|
|
||||||
// 0.00ddd
|
|
||||||
buf = append(buf, "0."...)
|
|
||||||
buf = appendZeros(buf, -int(x.Exp))
|
|
||||||
buf = appendDigits(buf, x.Digits)
|
|
||||||
|
|
||||||
case /* 0 < */ int(x.Exp) < len(x.Digits):
|
|
||||||
// dd.ddd
|
|
||||||
buf = appendDigits(buf, x.Digits[:x.Exp])
|
|
||||||
buf = append(buf, '.')
|
|
||||||
buf = appendDigits(buf, x.Digits[x.Exp:])
|
|
||||||
|
|
||||||
default: // len(x.Digits) <= x.Exp
|
|
||||||
// ddd00
|
|
||||||
buf = appendDigits(buf, x.Digits)
|
|
||||||
buf = appendZeros(buf, int(x.Exp)-len(x.Digits))
|
|
||||||
}
|
|
||||||
return string(buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
func appendDigits(buf []byte, digits []byte) []byte {
|
|
||||||
for _, c := range digits {
|
|
||||||
buf = append(buf, c+'0')
|
|
||||||
}
|
|
||||||
return buf
|
|
||||||
}
|
|
||||||
|
|
||||||
// appendZeros appends n 0 digits to buf and returns buf.
|
|
||||||
func appendZeros(buf []byte, n int) []byte {
|
|
||||||
for ; n > 0; n-- {
|
|
||||||
buf = append(buf, '0')
|
|
||||||
}
|
|
||||||
return buf
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *digits) round(mode RoundingMode, n int) {
|
|
||||||
if n >= len(d.Digits) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Make rounding decision: The result mantissa is truncated ("rounded down")
|
|
||||||
// by default. Decide if we need to increment, or "round up", the (unsigned)
|
|
||||||
// mantissa.
|
|
||||||
inc := false
|
|
||||||
switch mode {
|
|
||||||
case ToNegativeInf:
|
|
||||||
inc = d.Neg
|
|
||||||
case ToPositiveInf:
|
|
||||||
inc = !d.Neg
|
|
||||||
case ToZero:
|
|
||||||
// nothing to do
|
|
||||||
case AwayFromZero:
|
|
||||||
inc = true
|
|
||||||
case ToNearestEven:
|
|
||||||
inc = d.Digits[n] > 5 || d.Digits[n] == 5 &&
|
|
||||||
(len(d.Digits) > n+1 || n == 0 || d.Digits[n-1]&1 != 0)
|
|
||||||
case ToNearestAway:
|
|
||||||
inc = d.Digits[n] >= 5
|
|
||||||
case ToNearestZero:
|
|
||||||
inc = d.Digits[n] > 5 || d.Digits[n] == 5 && len(d.Digits) > n+1
|
|
||||||
default:
|
|
||||||
panic("unreachable")
|
|
||||||
}
|
|
||||||
if inc {
|
|
||||||
d.roundUp(n)
|
|
||||||
} else {
|
|
||||||
d.roundDown(n)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// roundFloat rounds a floating point number.
|
|
||||||
func (r RoundingMode) roundFloat(x float64) float64 {
|
|
||||||
// Make rounding decision: The result mantissa is truncated ("rounded down")
|
|
||||||
// by default. Decide if we need to increment, or "round up", the (unsigned)
|
|
||||||
// mantissa.
|
|
||||||
abs := x
|
|
||||||
if x < 0 {
|
|
||||||
abs = -x
|
|
||||||
}
|
|
||||||
i, f := math.Modf(abs)
|
|
||||||
if f == 0.0 {
|
|
||||||
return x
|
|
||||||
}
|
|
||||||
inc := false
|
|
||||||
switch r {
|
|
||||||
case ToNegativeInf:
|
|
||||||
inc = x < 0
|
|
||||||
case ToPositiveInf:
|
|
||||||
inc = x >= 0
|
|
||||||
case ToZero:
|
|
||||||
// nothing to do
|
|
||||||
case AwayFromZero:
|
|
||||||
inc = true
|
|
||||||
case ToNearestEven:
|
|
||||||
// TODO: check overflow
|
|
||||||
inc = f > 0.5 || f == 0.5 && int64(i)&1 != 0
|
|
||||||
case ToNearestAway:
|
|
||||||
inc = f >= 0.5
|
|
||||||
case ToNearestZero:
|
|
||||||
inc = f > 0.5
|
|
||||||
default:
|
|
||||||
panic("unreachable")
|
|
||||||
}
|
|
||||||
if inc {
|
|
||||||
i += 1
|
|
||||||
}
|
|
||||||
if abs != x {
|
|
||||||
i = -i
|
|
||||||
}
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *digits) roundUp(n int) {
|
|
||||||
if n < 0 || n >= len(x.Digits) {
|
|
||||||
return // nothing to do
|
|
||||||
}
|
|
||||||
// find first digit < 9
|
|
||||||
for n > 0 && x.Digits[n-1] >= 9 {
|
|
||||||
n--
|
|
||||||
}
|
|
||||||
|
|
||||||
if n == 0 {
|
|
||||||
// all digits are 9s => round up to 1 and update exponent
|
|
||||||
x.Digits[0] = 1 // ok since len(x.Digits) > n
|
|
||||||
x.Digits = x.Digits[:1]
|
|
||||||
x.Exp++
|
|
||||||
return
|
|
||||||
}
|
|
||||||
x.Digits[n-1]++
|
|
||||||
x.Digits = x.Digits[:n]
|
|
||||||
// x already trimmed
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *digits) roundDown(n int) {
|
|
||||||
if n < 0 || n >= len(x.Digits) {
|
|
||||||
return // nothing to do
|
|
||||||
}
|
|
||||||
x.Digits = x.Digits[:n]
|
|
||||||
trim(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
// trim cuts off any trailing zeros from x's mantissa;
|
|
||||||
// they are meaningless for the value of x.
|
|
||||||
func trim(x *digits) {
|
|
||||||
i := len(x.Digits)
|
|
||||||
for i > 0 && x.Digits[i-1] == 0 {
|
|
||||||
i--
|
|
||||||
}
|
|
||||||
x.Digits = x.Digits[:i]
|
|
||||||
if i == 0 {
|
|
||||||
x.Exp = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// A Converter converts a number into decimals according to the given rounding
|
|
||||||
// criteria.
|
|
||||||
type Converter interface {
|
|
||||||
Convert(d *Decimal, r RoundingContext)
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
signed = true
|
|
||||||
unsigned = false
|
|
||||||
)
|
|
||||||
|
|
||||||
// Convert converts the given number to the decimal representation using the
|
|
||||||
// supplied RoundingContext.
|
|
||||||
func (d *Decimal) Convert(r RoundingContext, number interface{}) {
|
|
||||||
switch f := number.(type) {
|
|
||||||
case Converter:
|
|
||||||
d.clear()
|
|
||||||
f.Convert(d, r)
|
|
||||||
case float32:
|
|
||||||
d.ConvertFloat(r, float64(f), 32)
|
|
||||||
case float64:
|
|
||||||
d.ConvertFloat(r, f, 64)
|
|
||||||
case int:
|
|
||||||
d.ConvertInt(r, signed, uint64(f))
|
|
||||||
case int8:
|
|
||||||
d.ConvertInt(r, signed, uint64(f))
|
|
||||||
case int16:
|
|
||||||
d.ConvertInt(r, signed, uint64(f))
|
|
||||||
case int32:
|
|
||||||
d.ConvertInt(r, signed, uint64(f))
|
|
||||||
case int64:
|
|
||||||
d.ConvertInt(r, signed, uint64(f))
|
|
||||||
case uint:
|
|
||||||
d.ConvertInt(r, unsigned, uint64(f))
|
|
||||||
case uint8:
|
|
||||||
d.ConvertInt(r, unsigned, uint64(f))
|
|
||||||
case uint16:
|
|
||||||
d.ConvertInt(r, unsigned, uint64(f))
|
|
||||||
case uint32:
|
|
||||||
d.ConvertInt(r, unsigned, uint64(f))
|
|
||||||
case uint64:
|
|
||||||
d.ConvertInt(r, unsigned, f)
|
|
||||||
|
|
||||||
default:
|
|
||||||
d.NaN = true
|
|
||||||
// TODO:
|
|
||||||
// case string: if produced by strconv, allows for easy arbitrary pos.
|
|
||||||
// case reflect.Value:
|
|
||||||
// case big.Float
|
|
||||||
// case big.Int
|
|
||||||
// case big.Rat?
|
|
||||||
// catch underlyings using reflect or will this already be done by the
|
|
||||||
// message package?
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ConvertInt converts an integer to decimals.
|
|
||||||
func (d *Decimal) ConvertInt(r RoundingContext, signed bool, x uint64) {
|
|
||||||
if r.Increment > 0 {
|
|
||||||
// TODO: if uint64 is too large, fall back to float64
|
|
||||||
if signed {
|
|
||||||
d.ConvertFloat(r, float64(int64(x)), 64)
|
|
||||||
} else {
|
|
||||||
d.ConvertFloat(r, float64(x), 64)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
d.clear()
|
|
||||||
if signed && int64(x) < 0 {
|
|
||||||
x = uint64(-int64(x))
|
|
||||||
d.Neg = true
|
|
||||||
}
|
|
||||||
d.fillIntDigits(x)
|
|
||||||
d.Exp = int32(len(d.Digits))
|
|
||||||
}
|
|
||||||
|
|
||||||
// ConvertFloat converts a floating point number to decimals.
|
|
||||||
func (d *Decimal) ConvertFloat(r RoundingContext, x float64, size int) {
|
|
||||||
d.clear()
|
|
||||||
if math.IsNaN(x) {
|
|
||||||
d.NaN = true
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Simple case: decimal notation
|
|
||||||
if r.Increment > 0 {
|
|
||||||
scale := int(r.IncrementScale)
|
|
||||||
mult := 1.0
|
|
||||||
if scale >= len(scales) {
|
|
||||||
mult = math.Pow(10, float64(scale))
|
|
||||||
} else {
|
|
||||||
mult = scales[scale]
|
|
||||||
}
|
|
||||||
// We multiply x instead of dividing inc as it gives less rounding
|
|
||||||
// issues.
|
|
||||||
x *= mult
|
|
||||||
x /= float64(r.Increment)
|
|
||||||
x = r.Mode.roundFloat(x)
|
|
||||||
x *= float64(r.Increment)
|
|
||||||
x /= mult
|
|
||||||
}
|
|
||||||
|
|
||||||
abs := x
|
|
||||||
if x < 0 {
|
|
||||||
d.Neg = true
|
|
||||||
abs = -x
|
|
||||||
}
|
|
||||||
if math.IsInf(abs, 1) {
|
|
||||||
d.Inf = true
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// By default we get the exact decimal representation.
|
|
||||||
verb := byte('g')
|
|
||||||
prec := -1
|
|
||||||
// As the strconv API does not return the rounding accuracy, we can only
|
|
||||||
// round using ToNearestEven.
|
|
||||||
if r.Mode == ToNearestEven {
|
|
||||||
if n := r.RoundSignificantDigits(); n >= 0 {
|
|
||||||
prec = n
|
|
||||||
} else if n = r.RoundFractionDigits(); n >= 0 {
|
|
||||||
prec = n
|
|
||||||
verb = 'f'
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// TODO: At this point strconv's rounding is imprecise to the point that
|
|
||||||
// it is not usable for this purpose.
|
|
||||||
// See https://github.com/golang/go/issues/21714
|
|
||||||
// If rounding is requested, we ask for a large number of digits and
|
|
||||||
// round from there to simulate rounding only once.
|
|
||||||
// Ideally we would have strconv export an AppendDigits that would take
|
|
||||||
// a rounding mode and/or return an accuracy. Something like this would
|
|
||||||
// work:
|
|
||||||
// AppendDigits(dst []byte, x float64, base, size, prec int) (digits []byte, exp, accuracy int)
|
|
||||||
hasPrec := r.RoundSignificantDigits() >= 0
|
|
||||||
hasScale := r.RoundFractionDigits() >= 0
|
|
||||||
if hasPrec || hasScale {
|
|
||||||
// prec is the number of mantissa bits plus some extra for safety.
|
|
||||||
// We need at least the number of mantissa bits as decimals to
|
|
||||||
// accurately represent the floating point without rounding, as each
|
|
||||||
// bit requires one more decimal to represent: 0.5, 0.25, 0.125, ...
|
|
||||||
prec = 60
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
b := strconv.AppendFloat(d.Digits[:0], abs, verb, prec, size)
|
|
||||||
i := 0
|
|
||||||
k := 0
|
|
||||||
beforeDot := 1
|
|
||||||
for i < len(b) {
|
|
||||||
if c := b[i]; '0' <= c && c <= '9' {
|
|
||||||
b[k] = c - '0'
|
|
||||||
k++
|
|
||||||
d.Exp += int32(beforeDot)
|
|
||||||
} else if c == '.' {
|
|
||||||
beforeDot = 0
|
|
||||||
d.Exp = int32(k)
|
|
||||||
} else {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
d.Digits = b[:k]
|
|
||||||
if i != len(b) {
|
|
||||||
i += len("e")
|
|
||||||
pSign := i
|
|
||||||
exp := 0
|
|
||||||
for i++; i < len(b); i++ {
|
|
||||||
exp *= 10
|
|
||||||
exp += int(b[i] - '0')
|
|
||||||
}
|
|
||||||
if b[pSign] == '-' {
|
|
||||||
exp = -exp
|
|
||||||
}
|
|
||||||
d.Exp = int32(exp) + 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Decimal) fillIntDigits(x uint64) {
|
|
||||||
if cap(d.Digits) < maxIntDigits {
|
|
||||||
d.Digits = d.buf[:]
|
|
||||||
} else {
|
|
||||||
d.Digits = d.buf[:maxIntDigits]
|
|
||||||
}
|
|
||||||
i := 0
|
|
||||||
for ; x > 0; x /= 10 {
|
|
||||||
d.Digits[i] = byte(x % 10)
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
d.Digits = d.Digits[:i]
|
|
||||||
for p := 0; p < i; p++ {
|
|
||||||
i--
|
|
||||||
d.Digits[p], d.Digits[i] = d.Digits[i], d.Digits[p]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var scales [70]float64
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
x := 1.0
|
|
||||||
for i := range scales {
|
|
||||||
scales[i] = x
|
|
||||||
x *= 10
|
|
||||||
}
|
|
||||||
}
|
|
||||||
533
vendor/golang.org/x/text/internal/number/format.go
generated
vendored
533
vendor/golang.org/x/text/internal/number/format.go
generated
vendored
@@ -1,533 +0,0 @@
|
|||||||
// Copyright 2017 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 number
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strconv"
|
|
||||||
"unicode/utf8"
|
|
||||||
|
|
||||||
"golang.org/x/text/language"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TODO:
|
|
||||||
// - grouping of fractions
|
|
||||||
// - allow user-defined superscript notation (such as <sup>4</sup>)
|
|
||||||
// - same for non-breaking spaces, like
|
|
||||||
|
|
||||||
// A VisibleDigits computes digits, comma placement and trailing zeros as they
|
|
||||||
// will be shown to the user.
|
|
||||||
type VisibleDigits interface {
|
|
||||||
Digits(buf []byte, t language.Tag, scale int) Digits
|
|
||||||
// TODO: Do we also need to add the verb or pass a format.State?
|
|
||||||
}
|
|
||||||
|
|
||||||
// Formatting proceeds along the following lines:
|
|
||||||
// 0) Compose rounding information from format and context.
|
|
||||||
// 1) Convert a number into a Decimal.
|
|
||||||
// 2) Sanitize Decimal by adding trailing zeros, removing leading digits, and
|
|
||||||
// (non-increment) rounding. The Decimal that results from this is suitable
|
|
||||||
// for determining the plural form.
|
|
||||||
// 3) Render the Decimal in the localized form.
|
|
||||||
|
|
||||||
// Formatter contains all the information needed to render a number.
|
|
||||||
type Formatter struct {
|
|
||||||
Pattern
|
|
||||||
Info
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *Formatter) init(t language.Tag, index []uint8) {
|
|
||||||
f.Info = InfoFromTag(t)
|
|
||||||
f.Pattern = formats[index[tagToID(t)]]
|
|
||||||
}
|
|
||||||
|
|
||||||
// InitPattern initializes a Formatter for the given Pattern.
|
|
||||||
func (f *Formatter) InitPattern(t language.Tag, pat *Pattern) {
|
|
||||||
f.Info = InfoFromTag(t)
|
|
||||||
f.Pattern = *pat
|
|
||||||
}
|
|
||||||
|
|
||||||
// InitDecimal initializes a Formatter using the default Pattern for the given
|
|
||||||
// language.
|
|
||||||
func (f *Formatter) InitDecimal(t language.Tag) {
|
|
||||||
f.init(t, tagToDecimal)
|
|
||||||
}
|
|
||||||
|
|
||||||
// InitScientific initializes a Formatter using the default Pattern for the
|
|
||||||
// given language.
|
|
||||||
func (f *Formatter) InitScientific(t language.Tag) {
|
|
||||||
f.init(t, tagToScientific)
|
|
||||||
f.Pattern.MinFractionDigits = 0
|
|
||||||
f.Pattern.MaxFractionDigits = -1
|
|
||||||
}
|
|
||||||
|
|
||||||
// InitEngineering initializes a Formatter using the default Pattern for the
|
|
||||||
// given language.
|
|
||||||
func (f *Formatter) InitEngineering(t language.Tag) {
|
|
||||||
f.init(t, tagToScientific)
|
|
||||||
f.Pattern.MinFractionDigits = 0
|
|
||||||
f.Pattern.MaxFractionDigits = -1
|
|
||||||
f.Pattern.MaxIntegerDigits = 3
|
|
||||||
f.Pattern.MinIntegerDigits = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// InitPercent initializes a Formatter using the default Pattern for the given
|
|
||||||
// language.
|
|
||||||
func (f *Formatter) InitPercent(t language.Tag) {
|
|
||||||
f.init(t, tagToPercent)
|
|
||||||
}
|
|
||||||
|
|
||||||
// InitPerMille initializes a Formatter using the default Pattern for the given
|
|
||||||
// language.
|
|
||||||
func (f *Formatter) InitPerMille(t language.Tag) {
|
|
||||||
f.init(t, tagToPercent)
|
|
||||||
f.Pattern.DigitShift = 3
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *Formatter) Append(dst []byte, x interface{}) []byte {
|
|
||||||
var d Decimal
|
|
||||||
r := f.RoundingContext
|
|
||||||
d.Convert(r, x)
|
|
||||||
return f.Render(dst, FormatDigits(&d, r))
|
|
||||||
}
|
|
||||||
|
|
||||||
func FormatDigits(d *Decimal, r RoundingContext) Digits {
|
|
||||||
if r.isScientific() {
|
|
||||||
return scientificVisibleDigits(r, d)
|
|
||||||
}
|
|
||||||
return decimalVisibleDigits(r, d)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *Formatter) Format(dst []byte, d *Decimal) []byte {
|
|
||||||
return f.Render(dst, FormatDigits(d, f.RoundingContext))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *Formatter) Render(dst []byte, d Digits) []byte {
|
|
||||||
var result []byte
|
|
||||||
var postPrefix, preSuffix int
|
|
||||||
if d.IsScientific {
|
|
||||||
result, postPrefix, preSuffix = appendScientific(dst, f, &d)
|
|
||||||
} else {
|
|
||||||
result, postPrefix, preSuffix = appendDecimal(dst, f, &d)
|
|
||||||
}
|
|
||||||
if f.PadRune == 0 {
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
width := int(f.FormatWidth)
|
|
||||||
if count := utf8.RuneCount(result); count < width {
|
|
||||||
insertPos := 0
|
|
||||||
switch f.Flags & PadMask {
|
|
||||||
case PadAfterPrefix:
|
|
||||||
insertPos = postPrefix
|
|
||||||
case PadBeforeSuffix:
|
|
||||||
insertPos = preSuffix
|
|
||||||
case PadAfterSuffix:
|
|
||||||
insertPos = len(result)
|
|
||||||
}
|
|
||||||
num := width - count
|
|
||||||
pad := [utf8.UTFMax]byte{' '}
|
|
||||||
sz := 1
|
|
||||||
if r := f.PadRune; r != 0 {
|
|
||||||
sz = utf8.EncodeRune(pad[:], r)
|
|
||||||
}
|
|
||||||
extra := sz * num
|
|
||||||
if n := len(result) + extra; n < cap(result) {
|
|
||||||
result = result[:n]
|
|
||||||
copy(result[insertPos+extra:], result[insertPos:])
|
|
||||||
} else {
|
|
||||||
buf := make([]byte, n)
|
|
||||||
copy(buf, result[:insertPos])
|
|
||||||
copy(buf[insertPos+extra:], result[insertPos:])
|
|
||||||
result = buf
|
|
||||||
}
|
|
||||||
for ; num > 0; num-- {
|
|
||||||
insertPos += copy(result[insertPos:], pad[:sz])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// decimalVisibleDigits converts d according to the RoundingContext. Note that
|
|
||||||
// the exponent may change as a result of this operation.
|
|
||||||
func decimalVisibleDigits(r RoundingContext, d *Decimal) Digits {
|
|
||||||
if d.NaN || d.Inf {
|
|
||||||
return Digits{digits: digits{Neg: d.Neg, NaN: d.NaN, Inf: d.Inf}}
|
|
||||||
}
|
|
||||||
n := Digits{digits: d.normalize().digits}
|
|
||||||
|
|
||||||
exp := n.Exp
|
|
||||||
exp += int32(r.DigitShift)
|
|
||||||
|
|
||||||
// Cap integer digits. Remove *most-significant* digits.
|
|
||||||
if r.MaxIntegerDigits > 0 {
|
|
||||||
if p := int(exp) - int(r.MaxIntegerDigits); p > 0 {
|
|
||||||
if p > len(n.Digits) {
|
|
||||||
p = len(n.Digits)
|
|
||||||
}
|
|
||||||
if n.Digits = n.Digits[p:]; len(n.Digits) == 0 {
|
|
||||||
exp = 0
|
|
||||||
} else {
|
|
||||||
exp -= int32(p)
|
|
||||||
}
|
|
||||||
// Strip leading zeros.
|
|
||||||
for len(n.Digits) > 0 && n.Digits[0] == 0 {
|
|
||||||
n.Digits = n.Digits[1:]
|
|
||||||
exp--
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rounding if not already done by Convert.
|
|
||||||
p := len(n.Digits)
|
|
||||||
if maxSig := int(r.MaxSignificantDigits); maxSig > 0 {
|
|
||||||
p = maxSig
|
|
||||||
}
|
|
||||||
if maxFrac := int(r.MaxFractionDigits); maxFrac >= 0 {
|
|
||||||
if cap := int(exp) + maxFrac; cap < p {
|
|
||||||
p = int(exp) + maxFrac
|
|
||||||
}
|
|
||||||
if p < 0 {
|
|
||||||
p = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
n.round(r.Mode, p)
|
|
||||||
|
|
||||||
// set End (trailing zeros)
|
|
||||||
n.End = int32(len(n.Digits))
|
|
||||||
if n.End == 0 {
|
|
||||||
exp = 0
|
|
||||||
if r.MinFractionDigits > 0 {
|
|
||||||
n.End = int32(r.MinFractionDigits)
|
|
||||||
}
|
|
||||||
if p := int32(r.MinSignificantDigits) - 1; p > n.End {
|
|
||||||
n.End = p
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if end := exp + int32(r.MinFractionDigits); end > n.End {
|
|
||||||
n.End = end
|
|
||||||
}
|
|
||||||
if n.End < int32(r.MinSignificantDigits) {
|
|
||||||
n.End = int32(r.MinSignificantDigits)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
n.Exp = exp
|
|
||||||
return n
|
|
||||||
}
|
|
||||||
|
|
||||||
// appendDecimal appends a formatted number to dst. It returns two possible
|
|
||||||
// insertion points for padding.
|
|
||||||
func appendDecimal(dst []byte, f *Formatter, n *Digits) (b []byte, postPre, preSuf int) {
|
|
||||||
if dst, ok := f.renderSpecial(dst, n); ok {
|
|
||||||
return dst, 0, len(dst)
|
|
||||||
}
|
|
||||||
digits := n.Digits
|
|
||||||
exp := n.Exp
|
|
||||||
|
|
||||||
// Split in integer and fraction part.
|
|
||||||
var intDigits, fracDigits []byte
|
|
||||||
numInt := 0
|
|
||||||
numFrac := int(n.End - n.Exp)
|
|
||||||
if exp > 0 {
|
|
||||||
numInt = int(exp)
|
|
||||||
if int(exp) >= len(digits) { // ddddd | ddddd00
|
|
||||||
intDigits = digits
|
|
||||||
} else { // ddd.dd
|
|
||||||
intDigits = digits[:exp]
|
|
||||||
fracDigits = digits[exp:]
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
fracDigits = digits
|
|
||||||
}
|
|
||||||
|
|
||||||
neg := n.Neg
|
|
||||||
affix, suffix := f.getAffixes(neg)
|
|
||||||
dst = appendAffix(dst, f, affix, neg)
|
|
||||||
savedLen := len(dst)
|
|
||||||
|
|
||||||
minInt := int(f.MinIntegerDigits)
|
|
||||||
if minInt == 0 && f.MinSignificantDigits > 0 {
|
|
||||||
minInt = 1
|
|
||||||
}
|
|
||||||
// add leading zeros
|
|
||||||
for i := minInt; i > numInt; i-- {
|
|
||||||
dst = f.AppendDigit(dst, 0)
|
|
||||||
if f.needsSep(i) {
|
|
||||||
dst = append(dst, f.Symbol(SymGroup)...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
i := 0
|
|
||||||
for ; i < len(intDigits); i++ {
|
|
||||||
dst = f.AppendDigit(dst, intDigits[i])
|
|
||||||
if f.needsSep(numInt - i) {
|
|
||||||
dst = append(dst, f.Symbol(SymGroup)...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for ; i < numInt; i++ {
|
|
||||||
dst = f.AppendDigit(dst, 0)
|
|
||||||
if f.needsSep(numInt - i) {
|
|
||||||
dst = append(dst, f.Symbol(SymGroup)...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if numFrac > 0 || f.Flags&AlwaysDecimalSeparator != 0 {
|
|
||||||
dst = append(dst, f.Symbol(SymDecimal)...)
|
|
||||||
}
|
|
||||||
// Add trailing zeros
|
|
||||||
i = 0
|
|
||||||
for n := -int(n.Exp); i < n; i++ {
|
|
||||||
dst = f.AppendDigit(dst, 0)
|
|
||||||
}
|
|
||||||
for _, d := range fracDigits {
|
|
||||||
i++
|
|
||||||
dst = f.AppendDigit(dst, d)
|
|
||||||
}
|
|
||||||
for ; i < numFrac; i++ {
|
|
||||||
dst = f.AppendDigit(dst, 0)
|
|
||||||
}
|
|
||||||
return appendAffix(dst, f, suffix, neg), savedLen, len(dst)
|
|
||||||
}
|
|
||||||
|
|
||||||
func scientificVisibleDigits(r RoundingContext, d *Decimal) Digits {
|
|
||||||
if d.NaN || d.Inf {
|
|
||||||
return Digits{digits: digits{Neg: d.Neg, NaN: d.NaN, Inf: d.Inf}}
|
|
||||||
}
|
|
||||||
n := Digits{digits: d.normalize().digits, IsScientific: true}
|
|
||||||
|
|
||||||
// Normalize to have at least one digit. This simplifies engineering
|
|
||||||
// notation.
|
|
||||||
if len(n.Digits) == 0 {
|
|
||||||
n.Digits = append(n.Digits, 0)
|
|
||||||
n.Exp = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// Significant digits are transformed by the parser for scientific notation
|
|
||||||
// and do not need to be handled here.
|
|
||||||
maxInt, numInt := int(r.MaxIntegerDigits), int(r.MinIntegerDigits)
|
|
||||||
if numInt == 0 {
|
|
||||||
numInt = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// If a maximum number of integers is specified, the minimum must be 1
|
|
||||||
// and the exponent is grouped by this number (e.g. for engineering)
|
|
||||||
if maxInt > numInt {
|
|
||||||
// Correct the exponent to reflect a single integer digit.
|
|
||||||
numInt = 1
|
|
||||||
// engineering
|
|
||||||
// 0.01234 ([12345]e-1) -> 1.2345e-2 12.345e-3
|
|
||||||
// 12345 ([12345]e+5) -> 1.2345e4 12.345e3
|
|
||||||
d := int(n.Exp-1) % maxInt
|
|
||||||
if d < 0 {
|
|
||||||
d += maxInt
|
|
||||||
}
|
|
||||||
numInt += d
|
|
||||||
}
|
|
||||||
|
|
||||||
p := len(n.Digits)
|
|
||||||
if maxSig := int(r.MaxSignificantDigits); maxSig > 0 {
|
|
||||||
p = maxSig
|
|
||||||
}
|
|
||||||
if maxFrac := int(r.MaxFractionDigits); maxFrac >= 0 && numInt+maxFrac < p {
|
|
||||||
p = numInt + maxFrac
|
|
||||||
}
|
|
||||||
n.round(r.Mode, p)
|
|
||||||
|
|
||||||
n.Comma = uint8(numInt)
|
|
||||||
n.End = int32(len(n.Digits))
|
|
||||||
if minSig := int32(r.MinFractionDigits) + int32(numInt); n.End < minSig {
|
|
||||||
n.End = minSig
|
|
||||||
}
|
|
||||||
return n
|
|
||||||
}
|
|
||||||
|
|
||||||
// appendScientific appends a formatted number to dst. It returns two possible
|
|
||||||
// insertion points for padding.
|
|
||||||
func appendScientific(dst []byte, f *Formatter, n *Digits) (b []byte, postPre, preSuf int) {
|
|
||||||
if dst, ok := f.renderSpecial(dst, n); ok {
|
|
||||||
return dst, 0, 0
|
|
||||||
}
|
|
||||||
digits := n.Digits
|
|
||||||
numInt := int(n.Comma)
|
|
||||||
numFrac := int(n.End) - int(n.Comma)
|
|
||||||
|
|
||||||
var intDigits, fracDigits []byte
|
|
||||||
if numInt <= len(digits) {
|
|
||||||
intDigits = digits[:numInt]
|
|
||||||
fracDigits = digits[numInt:]
|
|
||||||
} else {
|
|
||||||
intDigits = digits
|
|
||||||
}
|
|
||||||
neg := n.Neg
|
|
||||||
affix, suffix := f.getAffixes(neg)
|
|
||||||
dst = appendAffix(dst, f, affix, neg)
|
|
||||||
savedLen := len(dst)
|
|
||||||
|
|
||||||
i := 0
|
|
||||||
for ; i < len(intDigits); i++ {
|
|
||||||
dst = f.AppendDigit(dst, intDigits[i])
|
|
||||||
if f.needsSep(numInt - i) {
|
|
||||||
dst = append(dst, f.Symbol(SymGroup)...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for ; i < numInt; i++ {
|
|
||||||
dst = f.AppendDigit(dst, 0)
|
|
||||||
if f.needsSep(numInt - i) {
|
|
||||||
dst = append(dst, f.Symbol(SymGroup)...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if numFrac > 0 || f.Flags&AlwaysDecimalSeparator != 0 {
|
|
||||||
dst = append(dst, f.Symbol(SymDecimal)...)
|
|
||||||
}
|
|
||||||
i = 0
|
|
||||||
for ; i < len(fracDigits); i++ {
|
|
||||||
dst = f.AppendDigit(dst, fracDigits[i])
|
|
||||||
}
|
|
||||||
for ; i < numFrac; i++ {
|
|
||||||
dst = f.AppendDigit(dst, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// exp
|
|
||||||
buf := [12]byte{}
|
|
||||||
// TODO: use exponential if superscripting is not available (no Latin
|
|
||||||
// numbers or no tags) and use exponential in all other cases.
|
|
||||||
exp := n.Exp - int32(n.Comma)
|
|
||||||
exponential := f.Symbol(SymExponential)
|
|
||||||
if exponential == "E" {
|
|
||||||
dst = append(dst, f.Symbol(SymSuperscriptingExponent)...)
|
|
||||||
dst = f.AppendDigit(dst, 1)
|
|
||||||
dst = f.AppendDigit(dst, 0)
|
|
||||||
switch {
|
|
||||||
case exp < 0:
|
|
||||||
dst = append(dst, superMinus...)
|
|
||||||
exp = -exp
|
|
||||||
case f.Flags&AlwaysExpSign != 0:
|
|
||||||
dst = append(dst, superPlus...)
|
|
||||||
}
|
|
||||||
b = strconv.AppendUint(buf[:0], uint64(exp), 10)
|
|
||||||
for i := len(b); i < int(f.MinExponentDigits); i++ {
|
|
||||||
dst = append(dst, superDigits[0]...)
|
|
||||||
}
|
|
||||||
for _, c := range b {
|
|
||||||
dst = append(dst, superDigits[c-'0']...)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
dst = append(dst, exponential...)
|
|
||||||
switch {
|
|
||||||
case exp < 0:
|
|
||||||
dst = append(dst, f.Symbol(SymMinusSign)...)
|
|
||||||
exp = -exp
|
|
||||||
case f.Flags&AlwaysExpSign != 0:
|
|
||||||
dst = append(dst, f.Symbol(SymPlusSign)...)
|
|
||||||
}
|
|
||||||
b = strconv.AppendUint(buf[:0], uint64(exp), 10)
|
|
||||||
for i := len(b); i < int(f.MinExponentDigits); i++ {
|
|
||||||
dst = f.AppendDigit(dst, 0)
|
|
||||||
}
|
|
||||||
for _, c := range b {
|
|
||||||
dst = f.AppendDigit(dst, c-'0')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return appendAffix(dst, f, suffix, neg), savedLen, len(dst)
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
superMinus = "\u207B" // SUPERSCRIPT HYPHEN-MINUS
|
|
||||||
superPlus = "\u207A" // SUPERSCRIPT PLUS SIGN
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// Note: the digits are not sequential!!!
|
|
||||||
superDigits = []string{
|
|
||||||
"\u2070", // SUPERSCRIPT DIGIT ZERO
|
|
||||||
"\u00B9", // SUPERSCRIPT DIGIT ONE
|
|
||||||
"\u00B2", // SUPERSCRIPT DIGIT TWO
|
|
||||||
"\u00B3", // SUPERSCRIPT DIGIT THREE
|
|
||||||
"\u2074", // SUPERSCRIPT DIGIT FOUR
|
|
||||||
"\u2075", // SUPERSCRIPT DIGIT FIVE
|
|
||||||
"\u2076", // SUPERSCRIPT DIGIT SIX
|
|
||||||
"\u2077", // SUPERSCRIPT DIGIT SEVEN
|
|
||||||
"\u2078", // SUPERSCRIPT DIGIT EIGHT
|
|
||||||
"\u2079", // SUPERSCRIPT DIGIT NINE
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
func (f *Formatter) getAffixes(neg bool) (affix, suffix string) {
|
|
||||||
str := f.Affix
|
|
||||||
if str != "" {
|
|
||||||
if f.NegOffset > 0 {
|
|
||||||
if neg {
|
|
||||||
str = str[f.NegOffset:]
|
|
||||||
} else {
|
|
||||||
str = str[:f.NegOffset]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sufStart := 1 + str[0]
|
|
||||||
affix = str[1:sufStart]
|
|
||||||
suffix = str[sufStart+1:]
|
|
||||||
}
|
|
||||||
// TODO: introduce a NeedNeg sign to indicate if the left pattern already
|
|
||||||
// has a sign marked?
|
|
||||||
if f.NegOffset == 0 && (neg || f.Flags&AlwaysSign != 0) {
|
|
||||||
affix = "-" + affix
|
|
||||||
}
|
|
||||||
return affix, suffix
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *Formatter) renderSpecial(dst []byte, d *Digits) (b []byte, ok bool) {
|
|
||||||
if d.NaN {
|
|
||||||
return fmtNaN(dst, f), true
|
|
||||||
}
|
|
||||||
if d.Inf {
|
|
||||||
return fmtInfinite(dst, f, d), true
|
|
||||||
}
|
|
||||||
return dst, false
|
|
||||||
}
|
|
||||||
|
|
||||||
func fmtNaN(dst []byte, f *Formatter) []byte {
|
|
||||||
return append(dst, f.Symbol(SymNan)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func fmtInfinite(dst []byte, f *Formatter, d *Digits) []byte {
|
|
||||||
affix, suffix := f.getAffixes(d.Neg)
|
|
||||||
dst = appendAffix(dst, f, affix, d.Neg)
|
|
||||||
dst = append(dst, f.Symbol(SymInfinity)...)
|
|
||||||
dst = appendAffix(dst, f, suffix, d.Neg)
|
|
||||||
return dst
|
|
||||||
}
|
|
||||||
|
|
||||||
func appendAffix(dst []byte, f *Formatter, affix string, neg bool) []byte {
|
|
||||||
quoting := false
|
|
||||||
escaping := false
|
|
||||||
for _, r := range affix {
|
|
||||||
switch {
|
|
||||||
case escaping:
|
|
||||||
// escaping occurs both inside and outside of quotes
|
|
||||||
dst = append(dst, string(r)...)
|
|
||||||
escaping = false
|
|
||||||
case r == '\\':
|
|
||||||
escaping = true
|
|
||||||
case r == '\'':
|
|
||||||
quoting = !quoting
|
|
||||||
case quoting:
|
|
||||||
dst = append(dst, string(r)...)
|
|
||||||
case r == '%':
|
|
||||||
if f.DigitShift == 3 {
|
|
||||||
dst = append(dst, f.Symbol(SymPerMille)...)
|
|
||||||
} else {
|
|
||||||
dst = append(dst, f.Symbol(SymPercentSign)...)
|
|
||||||
}
|
|
||||||
case r == '-' || r == '+':
|
|
||||||
if neg {
|
|
||||||
dst = append(dst, f.Symbol(SymMinusSign)...)
|
|
||||||
} else if f.Flags&ElideSign == 0 {
|
|
||||||
dst = append(dst, f.Symbol(SymPlusSign)...)
|
|
||||||
} else {
|
|
||||||
dst = append(dst, ' ')
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
dst = append(dst, string(r)...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return dst
|
|
||||||
}
|
|
||||||
152
vendor/golang.org/x/text/internal/number/number.go
generated
vendored
152
vendor/golang.org/x/text/internal/number/number.go
generated
vendored
@@ -1,152 +0,0 @@
|
|||||||
// Copyright 2016 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.
|
|
||||||
|
|
||||||
//go:generate go run gen.go gen_common.go
|
|
||||||
|
|
||||||
// Package number contains tools and data for formatting numbers.
|
|
||||||
package number
|
|
||||||
|
|
||||||
import (
|
|
||||||
"unicode/utf8"
|
|
||||||
|
|
||||||
"golang.org/x/text/internal/language/compact"
|
|
||||||
"golang.org/x/text/language"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Info holds number formatting configuration data.
|
|
||||||
type Info struct {
|
|
||||||
system systemData // numbering system information
|
|
||||||
symIndex symOffset // index to symbols
|
|
||||||
}
|
|
||||||
|
|
||||||
// InfoFromLangID returns a Info for the given compact language identifier and
|
|
||||||
// numbering system identifier. If system is the empty string, the default
|
|
||||||
// numbering system will be taken for that language.
|
|
||||||
func InfoFromLangID(compactIndex compact.ID, numberSystem string) Info {
|
|
||||||
p := langToDefaults[compactIndex]
|
|
||||||
// Lookup the entry for the language.
|
|
||||||
pSymIndex := symOffset(0) // Default: Latin, default symbols
|
|
||||||
system, ok := systemMap[numberSystem]
|
|
||||||
if !ok {
|
|
||||||
// Take the value for the default numbering system. This is by far the
|
|
||||||
// most common case as an alternative numbering system is hardly used.
|
|
||||||
if p&hasNonLatnMask == 0 { // Latn digits.
|
|
||||||
pSymIndex = p
|
|
||||||
} else { // Non-Latn or multiple numbering systems.
|
|
||||||
// Take the first entry from the alternatives list.
|
|
||||||
data := langToAlt[p&^hasNonLatnMask]
|
|
||||||
pSymIndex = data.symIndex
|
|
||||||
system = data.system
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
langIndex := compactIndex
|
|
||||||
ns := system
|
|
||||||
outerLoop:
|
|
||||||
for ; ; p = langToDefaults[langIndex] {
|
|
||||||
if p&hasNonLatnMask == 0 {
|
|
||||||
if ns == 0 {
|
|
||||||
// The index directly points to the symbol data.
|
|
||||||
pSymIndex = p
|
|
||||||
break
|
|
||||||
}
|
|
||||||
// Move to the parent and retry.
|
|
||||||
langIndex = langIndex.Parent()
|
|
||||||
} else {
|
|
||||||
// The index points to a list of symbol data indexes.
|
|
||||||
for _, e := range langToAlt[p&^hasNonLatnMask:] {
|
|
||||||
if e.compactTag != langIndex {
|
|
||||||
if langIndex == 0 {
|
|
||||||
// The CLDR root defines full symbol information for
|
|
||||||
// all numbering systems (even though mostly by
|
|
||||||
// means of aliases). Fall back to the default entry
|
|
||||||
// for Latn if there is no data for the numbering
|
|
||||||
// system of this language.
|
|
||||||
if ns == 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
// Fall back to Latin and start from the original
|
|
||||||
// language. See
|
|
||||||
// https://unicode.org/reports/tr35/#Locale_Inheritance.
|
|
||||||
ns = numLatn
|
|
||||||
langIndex = compactIndex
|
|
||||||
continue outerLoop
|
|
||||||
}
|
|
||||||
// Fall back to parent.
|
|
||||||
langIndex = langIndex.Parent()
|
|
||||||
} else if e.system == ns {
|
|
||||||
pSymIndex = e.symIndex
|
|
||||||
break outerLoop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if int(system) >= len(numSysData) { // algorithmic
|
|
||||||
// Will generate ASCII digits in case the user inadvertently calls
|
|
||||||
// WriteDigit or Digit on it.
|
|
||||||
d := numSysData[0]
|
|
||||||
d.id = system
|
|
||||||
return Info{
|
|
||||||
system: d,
|
|
||||||
symIndex: pSymIndex,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Info{
|
|
||||||
system: numSysData[system],
|
|
||||||
symIndex: pSymIndex,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// InfoFromTag returns a Info for the given language tag.
|
|
||||||
func InfoFromTag(t language.Tag) Info {
|
|
||||||
return InfoFromLangID(tagToID(t), t.TypeForKey("nu"))
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsDecimal reports if the numbering system can convert decimal to native
|
|
||||||
// symbols one-to-one.
|
|
||||||
func (n Info) IsDecimal() bool {
|
|
||||||
return int(n.system.id) < len(numSysData)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteDigit writes the UTF-8 sequence for n corresponding to the given ASCII
|
|
||||||
// digit to dst and reports the number of bytes written. dst must be large
|
|
||||||
// enough to hold the rune (can be up to utf8.UTFMax bytes).
|
|
||||||
func (n Info) WriteDigit(dst []byte, asciiDigit rune) int {
|
|
||||||
copy(dst, n.system.zero[:n.system.digitSize])
|
|
||||||
dst[n.system.digitSize-1] += byte(asciiDigit - '0')
|
|
||||||
return int(n.system.digitSize)
|
|
||||||
}
|
|
||||||
|
|
||||||
// AppendDigit appends the UTF-8 sequence for n corresponding to the given digit
|
|
||||||
// to dst and reports the number of bytes written. dst must be large enough to
|
|
||||||
// hold the rune (can be up to utf8.UTFMax bytes).
|
|
||||||
func (n Info) AppendDigit(dst []byte, digit byte) []byte {
|
|
||||||
dst = append(dst, n.system.zero[:n.system.digitSize]...)
|
|
||||||
dst[len(dst)-1] += digit
|
|
||||||
return dst
|
|
||||||
}
|
|
||||||
|
|
||||||
// Digit returns the digit for the numbering system for the corresponding ASCII
|
|
||||||
// value. For example, ni.Digit('3') could return '三'. Note that the argument
|
|
||||||
// is the rune constant '3', which equals 51, not the integer constant 3.
|
|
||||||
func (n Info) Digit(asciiDigit rune) rune {
|
|
||||||
var x [utf8.UTFMax]byte
|
|
||||||
n.WriteDigit(x[:], asciiDigit)
|
|
||||||
r, _ := utf8.DecodeRune(x[:])
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
// Symbol returns the string for the given symbol type.
|
|
||||||
func (n Info) Symbol(t SymbolType) string {
|
|
||||||
return symData.Elem(int(symIndex[n.symIndex][t]))
|
|
||||||
}
|
|
||||||
|
|
||||||
func formatForLang(t language.Tag, index []byte) *Pattern {
|
|
||||||
return &formats[index[tagToID(t)]]
|
|
||||||
}
|
|
||||||
|
|
||||||
func tagToID(t language.Tag) compact.ID {
|
|
||||||
id, _ := compact.RegionalID(compact.Tag(t))
|
|
||||||
return id
|
|
||||||
}
|
|
||||||
485
vendor/golang.org/x/text/internal/number/pattern.go
generated
vendored
485
vendor/golang.org/x/text/internal/number/pattern.go
generated
vendored
@@ -1,485 +0,0 @@
|
|||||||
// Copyright 2015 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 number
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"unicode/utf8"
|
|
||||||
)
|
|
||||||
|
|
||||||
// This file contains a parser for the CLDR number patterns as described in
|
|
||||||
// https://unicode.org/reports/tr35/tr35-numbers.html#Number_Format_Patterns.
|
|
||||||
//
|
|
||||||
// The following BNF is derived from this standard.
|
|
||||||
//
|
|
||||||
// pattern := subpattern (';' subpattern)?
|
|
||||||
// subpattern := affix? number exponent? affix?
|
|
||||||
// number := decimal | sigDigits
|
|
||||||
// decimal := '#'* '0'* ('.' fraction)? | '#' | '0'
|
|
||||||
// fraction := '0'* '#'*
|
|
||||||
// sigDigits := '#'* '@' '@'* '#'*
|
|
||||||
// exponent := 'E' '+'? '0'* '0'
|
|
||||||
// padSpec := '*' \L
|
|
||||||
//
|
|
||||||
// Notes:
|
|
||||||
// - An affix pattern may contain any runes, but runes with special meaning
|
|
||||||
// should be escaped.
|
|
||||||
// - Sequences of digits, '#', and '@' in decimal and sigDigits may have
|
|
||||||
// interstitial commas.
|
|
||||||
|
|
||||||
// TODO: replace special characters in affixes (-, +, ¤) with control codes.
|
|
||||||
|
|
||||||
// Pattern holds information for formatting numbers. It is designed to hold
|
|
||||||
// information from CLDR number patterns.
|
|
||||||
//
|
|
||||||
// This pattern is precompiled for all patterns for all languages. Even though
|
|
||||||
// the number of patterns is not very large, we want to keep this small.
|
|
||||||
//
|
|
||||||
// This type is only intended for internal use.
|
|
||||||
type Pattern struct {
|
|
||||||
RoundingContext
|
|
||||||
|
|
||||||
Affix string // includes prefix and suffix. First byte is prefix length.
|
|
||||||
Offset uint16 // Offset into Affix for prefix and suffix
|
|
||||||
NegOffset uint16 // Offset into Affix for negative prefix and suffix or 0.
|
|
||||||
PadRune rune
|
|
||||||
FormatWidth uint16
|
|
||||||
|
|
||||||
GroupingSize [2]uint8
|
|
||||||
Flags PatternFlag
|
|
||||||
}
|
|
||||||
|
|
||||||
// A RoundingContext indicates how a number should be converted to digits.
|
|
||||||
// It contains all information needed to determine the "visible digits" as
|
|
||||||
// required by the pluralization rules.
|
|
||||||
type RoundingContext struct {
|
|
||||||
// TODO: unify these two fields so that there is a more unambiguous meaning
|
|
||||||
// of how precision is handled.
|
|
||||||
MaxSignificantDigits int16 // -1 is unlimited
|
|
||||||
MaxFractionDigits int16 // -1 is unlimited
|
|
||||||
|
|
||||||
Increment uint32
|
|
||||||
IncrementScale uint8 // May differ from printed scale.
|
|
||||||
|
|
||||||
Mode RoundingMode
|
|
||||||
|
|
||||||
DigitShift uint8 // Number of decimals to shift. Used for % and ‰.
|
|
||||||
|
|
||||||
// Number of digits.
|
|
||||||
MinIntegerDigits uint8
|
|
||||||
|
|
||||||
MaxIntegerDigits uint8
|
|
||||||
MinFractionDigits uint8
|
|
||||||
MinSignificantDigits uint8
|
|
||||||
|
|
||||||
MinExponentDigits uint8
|
|
||||||
}
|
|
||||||
|
|
||||||
// RoundSignificantDigits returns the number of significant digits an
|
|
||||||
// implementation of Convert may round to or n < 0 if there is no maximum or
|
|
||||||
// a maximum is not recommended.
|
|
||||||
func (r *RoundingContext) RoundSignificantDigits() (n int) {
|
|
||||||
if r.MaxFractionDigits == 0 && r.MaxSignificantDigits > 0 {
|
|
||||||
return int(r.MaxSignificantDigits)
|
|
||||||
} else if r.isScientific() && r.MaxIntegerDigits == 1 {
|
|
||||||
if r.MaxSignificantDigits == 0 ||
|
|
||||||
int(r.MaxFractionDigits+1) == int(r.MaxSignificantDigits) {
|
|
||||||
// Note: don't add DigitShift: it is only used for decimals.
|
|
||||||
return int(r.MaxFractionDigits) + 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
|
|
||||||
// RoundFractionDigits returns the number of fraction digits an implementation
|
|
||||||
// of Convert may round to or n < 0 if there is no maximum or a maximum is not
|
|
||||||
// recommended.
|
|
||||||
func (r *RoundingContext) RoundFractionDigits() (n int) {
|
|
||||||
if r.MinExponentDigits == 0 &&
|
|
||||||
r.MaxSignificantDigits == 0 &&
|
|
||||||
r.MaxFractionDigits >= 0 {
|
|
||||||
return int(r.MaxFractionDigits) + int(r.DigitShift)
|
|
||||||
}
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetScale fixes the RoundingContext to a fixed number of fraction digits.
|
|
||||||
func (r *RoundingContext) SetScale(scale int) {
|
|
||||||
r.MinFractionDigits = uint8(scale)
|
|
||||||
r.MaxFractionDigits = int16(scale)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *RoundingContext) SetPrecision(prec int) {
|
|
||||||
r.MaxSignificantDigits = int16(prec)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *RoundingContext) isScientific() bool {
|
|
||||||
return r.MinExponentDigits > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *Pattern) needsSep(pos int) bool {
|
|
||||||
p := pos - 1
|
|
||||||
size := int(f.GroupingSize[0])
|
|
||||||
if size == 0 || p == 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if p == size {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if p -= size; p < 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
// TODO: make second groupingsize the same as first if 0 so that we can
|
|
||||||
// avoid this check.
|
|
||||||
if x := int(f.GroupingSize[1]); x != 0 {
|
|
||||||
size = x
|
|
||||||
}
|
|
||||||
return p%size == 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// A PatternFlag is a bit mask for the flag field of a Pattern.
|
|
||||||
type PatternFlag uint8
|
|
||||||
|
|
||||||
const (
|
|
||||||
AlwaysSign PatternFlag = 1 << iota
|
|
||||||
ElideSign // Use space instead of plus sign. AlwaysSign must be true.
|
|
||||||
AlwaysExpSign
|
|
||||||
AlwaysDecimalSeparator
|
|
||||||
ParenthesisForNegative // Common pattern. Saves space.
|
|
||||||
|
|
||||||
PadAfterNumber
|
|
||||||
PadAfterAffix
|
|
||||||
|
|
||||||
PadBeforePrefix = 0 // Default
|
|
||||||
PadAfterPrefix = PadAfterAffix
|
|
||||||
PadBeforeSuffix = PadAfterNumber
|
|
||||||
PadAfterSuffix = PadAfterNumber | PadAfterAffix
|
|
||||||
PadMask = PadAfterNumber | PadAfterAffix
|
|
||||||
)
|
|
||||||
|
|
||||||
type parser struct {
|
|
||||||
*Pattern
|
|
||||||
|
|
||||||
leadingSharps int
|
|
||||||
|
|
||||||
pos int
|
|
||||||
err error
|
|
||||||
doNotTerminate bool
|
|
||||||
groupingCount uint
|
|
||||||
hasGroup bool
|
|
||||||
buf []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *parser) setError(err error) {
|
|
||||||
if p.err == nil {
|
|
||||||
p.err = err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *parser) updateGrouping() {
|
|
||||||
if p.hasGroup &&
|
|
||||||
0 < p.groupingCount && p.groupingCount < 255 {
|
|
||||||
p.GroupingSize[1] = p.GroupingSize[0]
|
|
||||||
p.GroupingSize[0] = uint8(p.groupingCount)
|
|
||||||
}
|
|
||||||
p.groupingCount = 0
|
|
||||||
p.hasGroup = true
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
// TODO: more sensible and localizeable error messages.
|
|
||||||
errMultiplePadSpecifiers = errors.New("format: pattern has multiple pad specifiers")
|
|
||||||
errInvalidPadSpecifier = errors.New("format: invalid pad specifier")
|
|
||||||
errInvalidQuote = errors.New("format: invalid quote")
|
|
||||||
errAffixTooLarge = errors.New("format: prefix or suffix exceeds maximum UTF-8 length of 256 bytes")
|
|
||||||
errDuplicatePercentSign = errors.New("format: duplicate percent sign")
|
|
||||||
errDuplicatePermilleSign = errors.New("format: duplicate permille sign")
|
|
||||||
errUnexpectedEnd = errors.New("format: unexpected end of pattern")
|
|
||||||
)
|
|
||||||
|
|
||||||
// ParsePattern extracts formatting information from a CLDR number pattern.
|
|
||||||
//
|
|
||||||
// See https://unicode.org/reports/tr35/tr35-numbers.html#Number_Format_Patterns.
|
|
||||||
func ParsePattern(s string) (f *Pattern, err error) {
|
|
||||||
p := parser{Pattern: &Pattern{}}
|
|
||||||
|
|
||||||
s = p.parseSubPattern(s)
|
|
||||||
|
|
||||||
if s != "" {
|
|
||||||
// Parse negative sub pattern.
|
|
||||||
if s[0] != ';' {
|
|
||||||
p.setError(errors.New("format: error parsing first sub pattern"))
|
|
||||||
return nil, p.err
|
|
||||||
}
|
|
||||||
neg := parser{Pattern: &Pattern{}} // just for extracting the affixes.
|
|
||||||
s = neg.parseSubPattern(s[len(";"):])
|
|
||||||
p.NegOffset = uint16(len(p.buf))
|
|
||||||
p.buf = append(p.buf, neg.buf...)
|
|
||||||
}
|
|
||||||
if s != "" {
|
|
||||||
p.setError(errors.New("format: spurious characters at end of pattern"))
|
|
||||||
}
|
|
||||||
if p.err != nil {
|
|
||||||
return nil, p.err
|
|
||||||
}
|
|
||||||
if affix := string(p.buf); affix == "\x00\x00" || affix == "\x00\x00\x00\x00" {
|
|
||||||
// No prefix or suffixes.
|
|
||||||
p.NegOffset = 0
|
|
||||||
} else {
|
|
||||||
p.Affix = affix
|
|
||||||
}
|
|
||||||
if p.Increment == 0 {
|
|
||||||
p.IncrementScale = 0
|
|
||||||
}
|
|
||||||
return p.Pattern, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *parser) parseSubPattern(s string) string {
|
|
||||||
s = p.parsePad(s, PadBeforePrefix)
|
|
||||||
s = p.parseAffix(s)
|
|
||||||
s = p.parsePad(s, PadAfterPrefix)
|
|
||||||
|
|
||||||
s = p.parse(p.number, s)
|
|
||||||
p.updateGrouping()
|
|
||||||
|
|
||||||
s = p.parsePad(s, PadBeforeSuffix)
|
|
||||||
s = p.parseAffix(s)
|
|
||||||
s = p.parsePad(s, PadAfterSuffix)
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *parser) parsePad(s string, f PatternFlag) (tail string) {
|
|
||||||
if len(s) >= 2 && s[0] == '*' {
|
|
||||||
r, sz := utf8.DecodeRuneInString(s[1:])
|
|
||||||
if p.PadRune != 0 {
|
|
||||||
p.err = errMultiplePadSpecifiers
|
|
||||||
} else {
|
|
||||||
p.Flags |= f
|
|
||||||
p.PadRune = r
|
|
||||||
}
|
|
||||||
return s[1+sz:]
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *parser) parseAffix(s string) string {
|
|
||||||
x := len(p.buf)
|
|
||||||
p.buf = append(p.buf, 0) // placeholder for affix length
|
|
||||||
|
|
||||||
s = p.parse(p.affix, s)
|
|
||||||
|
|
||||||
n := len(p.buf) - x - 1
|
|
||||||
if n > 0xFF {
|
|
||||||
p.setError(errAffixTooLarge)
|
|
||||||
}
|
|
||||||
p.buf[x] = uint8(n)
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// state implements a state transition. It returns the new state. A state
|
|
||||||
// function may set an error on the parser or may simply return on an incorrect
|
|
||||||
// token and let the next phase fail.
|
|
||||||
type state func(r rune) state
|
|
||||||
|
|
||||||
// parse repeatedly applies a state function on the given string until a
|
|
||||||
// termination condition is reached.
|
|
||||||
func (p *parser) parse(fn state, s string) (tail string) {
|
|
||||||
for i, r := range s {
|
|
||||||
p.doNotTerminate = false
|
|
||||||
if fn = fn(r); fn == nil || p.err != nil {
|
|
||||||
return s[i:]
|
|
||||||
}
|
|
||||||
p.FormatWidth++
|
|
||||||
}
|
|
||||||
if p.doNotTerminate {
|
|
||||||
p.setError(errUnexpectedEnd)
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *parser) affix(r rune) state {
|
|
||||||
switch r {
|
|
||||||
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
|
|
||||||
'#', '@', '.', '*', ',', ';':
|
|
||||||
return nil
|
|
||||||
case '\'':
|
|
||||||
p.FormatWidth--
|
|
||||||
return p.escapeFirst
|
|
||||||
case '%':
|
|
||||||
if p.DigitShift != 0 {
|
|
||||||
p.setError(errDuplicatePercentSign)
|
|
||||||
}
|
|
||||||
p.DigitShift = 2
|
|
||||||
case '\u2030': // ‰ Per mille
|
|
||||||
if p.DigitShift != 0 {
|
|
||||||
p.setError(errDuplicatePermilleSign)
|
|
||||||
}
|
|
||||||
p.DigitShift = 3
|
|
||||||
// TODO: handle currency somehow: ¤, ¤¤, ¤¤¤, ¤¤¤¤
|
|
||||||
}
|
|
||||||
p.buf = append(p.buf, string(r)...)
|
|
||||||
return p.affix
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *parser) escapeFirst(r rune) state {
|
|
||||||
switch r {
|
|
||||||
case '\'':
|
|
||||||
p.buf = append(p.buf, "\\'"...)
|
|
||||||
return p.affix
|
|
||||||
default:
|
|
||||||
p.buf = append(p.buf, '\'')
|
|
||||||
p.buf = append(p.buf, string(r)...)
|
|
||||||
}
|
|
||||||
return p.escape
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *parser) escape(r rune) state {
|
|
||||||
switch r {
|
|
||||||
case '\'':
|
|
||||||
p.FormatWidth--
|
|
||||||
p.buf = append(p.buf, '\'')
|
|
||||||
return p.affix
|
|
||||||
default:
|
|
||||||
p.buf = append(p.buf, string(r)...)
|
|
||||||
}
|
|
||||||
return p.escape
|
|
||||||
}
|
|
||||||
|
|
||||||
// number parses a number. The BNF says the integer part should always have
|
|
||||||
// a '0', but that does not appear to be the case according to the rest of the
|
|
||||||
// documentation. We will allow having only '#' numbers.
|
|
||||||
func (p *parser) number(r rune) state {
|
|
||||||
switch r {
|
|
||||||
case '#':
|
|
||||||
p.groupingCount++
|
|
||||||
p.leadingSharps++
|
|
||||||
case '@':
|
|
||||||
p.groupingCount++
|
|
||||||
p.leadingSharps = 0
|
|
||||||
p.MaxFractionDigits = -1
|
|
||||||
return p.sigDigits(r)
|
|
||||||
case ',':
|
|
||||||
if p.leadingSharps == 0 { // no leading commas
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
p.updateGrouping()
|
|
||||||
case 'E':
|
|
||||||
p.MaxIntegerDigits = uint8(p.leadingSharps)
|
|
||||||
return p.exponent
|
|
||||||
case '.': // allow ".##" etc.
|
|
||||||
p.updateGrouping()
|
|
||||||
return p.fraction
|
|
||||||
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
|
|
||||||
return p.integer(r)
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return p.number
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *parser) integer(r rune) state {
|
|
||||||
if !('0' <= r && r <= '9') {
|
|
||||||
var next state
|
|
||||||
switch r {
|
|
||||||
case 'E':
|
|
||||||
if p.leadingSharps > 0 {
|
|
||||||
p.MaxIntegerDigits = uint8(p.leadingSharps) + p.MinIntegerDigits
|
|
||||||
}
|
|
||||||
next = p.exponent
|
|
||||||
case '.':
|
|
||||||
next = p.fraction
|
|
||||||
case ',':
|
|
||||||
next = p.integer
|
|
||||||
}
|
|
||||||
p.updateGrouping()
|
|
||||||
return next
|
|
||||||
}
|
|
||||||
p.Increment = p.Increment*10 + uint32(r-'0')
|
|
||||||
p.groupingCount++
|
|
||||||
p.MinIntegerDigits++
|
|
||||||
return p.integer
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *parser) sigDigits(r rune) state {
|
|
||||||
switch r {
|
|
||||||
case '@':
|
|
||||||
p.groupingCount++
|
|
||||||
p.MaxSignificantDigits++
|
|
||||||
p.MinSignificantDigits++
|
|
||||||
case '#':
|
|
||||||
return p.sigDigitsFinal(r)
|
|
||||||
case 'E':
|
|
||||||
p.updateGrouping()
|
|
||||||
return p.normalizeSigDigitsWithExponent()
|
|
||||||
default:
|
|
||||||
p.updateGrouping()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return p.sigDigits
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *parser) sigDigitsFinal(r rune) state {
|
|
||||||
switch r {
|
|
||||||
case '#':
|
|
||||||
p.groupingCount++
|
|
||||||
p.MaxSignificantDigits++
|
|
||||||
case 'E':
|
|
||||||
p.updateGrouping()
|
|
||||||
return p.normalizeSigDigitsWithExponent()
|
|
||||||
default:
|
|
||||||
p.updateGrouping()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return p.sigDigitsFinal
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *parser) normalizeSigDigitsWithExponent() state {
|
|
||||||
p.MinIntegerDigits, p.MaxIntegerDigits = 1, 1
|
|
||||||
p.MinFractionDigits = p.MinSignificantDigits - 1
|
|
||||||
p.MaxFractionDigits = p.MaxSignificantDigits - 1
|
|
||||||
p.MinSignificantDigits, p.MaxSignificantDigits = 0, 0
|
|
||||||
return p.exponent
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *parser) fraction(r rune) state {
|
|
||||||
switch r {
|
|
||||||
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
|
|
||||||
p.Increment = p.Increment*10 + uint32(r-'0')
|
|
||||||
p.IncrementScale++
|
|
||||||
p.MinFractionDigits++
|
|
||||||
p.MaxFractionDigits++
|
|
||||||
case '#':
|
|
||||||
p.MaxFractionDigits++
|
|
||||||
case 'E':
|
|
||||||
if p.leadingSharps > 0 {
|
|
||||||
p.MaxIntegerDigits = uint8(p.leadingSharps) + p.MinIntegerDigits
|
|
||||||
}
|
|
||||||
return p.exponent
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return p.fraction
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *parser) exponent(r rune) state {
|
|
||||||
switch r {
|
|
||||||
case '+':
|
|
||||||
// Set mode and check it wasn't already set.
|
|
||||||
if p.Flags&AlwaysExpSign != 0 || p.MinExponentDigits > 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
p.Flags |= AlwaysExpSign
|
|
||||||
p.doNotTerminate = true
|
|
||||||
return p.exponent
|
|
||||||
case '0':
|
|
||||||
p.MinExponentDigits++
|
|
||||||
return p.exponent
|
|
||||||
}
|
|
||||||
// termination condition
|
|
||||||
if p.MinExponentDigits == 0 {
|
|
||||||
p.setError(errors.New("format: need at least one digit"))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
30
vendor/golang.org/x/text/internal/number/roundingmode_string.go
generated
vendored
30
vendor/golang.org/x/text/internal/number/roundingmode_string.go
generated
vendored
@@ -1,30 +0,0 @@
|
|||||||
// Code generated by "stringer -type RoundingMode"; DO NOT EDIT.
|
|
||||||
|
|
||||||
package number
|
|
||||||
|
|
||||||
import "strconv"
|
|
||||||
|
|
||||||
func _() {
|
|
||||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
|
||||||
// Re-run the stringer command to generate them again.
|
|
||||||
var x [1]struct{}
|
|
||||||
_ = x[ToNearestEven-0]
|
|
||||||
_ = x[ToNearestZero-1]
|
|
||||||
_ = x[ToNearestAway-2]
|
|
||||||
_ = x[ToPositiveInf-3]
|
|
||||||
_ = x[ToNegativeInf-4]
|
|
||||||
_ = x[ToZero-5]
|
|
||||||
_ = x[AwayFromZero-6]
|
|
||||||
_ = x[numModes-7]
|
|
||||||
}
|
|
||||||
|
|
||||||
const _RoundingMode_name = "ToNearestEvenToNearestZeroToNearestAwayToPositiveInfToNegativeInfToZeroAwayFromZeronumModes"
|
|
||||||
|
|
||||||
var _RoundingMode_index = [...]uint8{0, 13, 26, 39, 52, 65, 71, 83, 91}
|
|
||||||
|
|
||||||
func (i RoundingMode) String() string {
|
|
||||||
if i >= RoundingMode(len(_RoundingMode_index)-1) {
|
|
||||||
return "RoundingMode(" + strconv.FormatInt(int64(i), 10) + ")"
|
|
||||||
}
|
|
||||||
return _RoundingMode_name[_RoundingMode_index[i]:_RoundingMode_index[i+1]]
|
|
||||||
}
|
|
||||||
1219
vendor/golang.org/x/text/internal/number/tables.go
generated
vendored
1219
vendor/golang.org/x/text/internal/number/tables.go
generated
vendored
File diff suppressed because it is too large
Load Diff
86
vendor/golang.org/x/text/internal/stringset/set.go
generated
vendored
86
vendor/golang.org/x/text/internal/stringset/set.go
generated
vendored
@@ -1,86 +0,0 @@
|
|||||||
// Copyright 2016 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 stringset provides a way to represent a collection of strings
|
|
||||||
// compactly.
|
|
||||||
package stringset
|
|
||||||
|
|
||||||
import "sort"
|
|
||||||
|
|
||||||
// A Set holds a collection of strings that can be looked up by an index number.
|
|
||||||
type Set struct {
|
|
||||||
// These fields are exported to allow for code generation.
|
|
||||||
|
|
||||||
Data string
|
|
||||||
Index []uint16
|
|
||||||
}
|
|
||||||
|
|
||||||
// Elem returns the string with index i. It panics if i is out of range.
|
|
||||||
func (s *Set) Elem(i int) string {
|
|
||||||
return s.Data[s.Index[i]:s.Index[i+1]]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Len returns the number of strings in the set.
|
|
||||||
func (s *Set) Len() int {
|
|
||||||
return len(s.Index) - 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// Search returns the index of the given string or -1 if it is not in the set.
|
|
||||||
// The Set must have been created with strings in sorted order.
|
|
||||||
func Search(s *Set, str string) int {
|
|
||||||
// TODO: optimize this if it gets used a lot.
|
|
||||||
n := len(s.Index) - 1
|
|
||||||
p := sort.Search(n, func(i int) bool {
|
|
||||||
return s.Elem(i) >= str
|
|
||||||
})
|
|
||||||
if p == n || str != s.Elem(p) {
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
// A Builder constructs Sets.
|
|
||||||
type Builder struct {
|
|
||||||
set Set
|
|
||||||
index map[string]int
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewBuilder returns a new and initialized Builder.
|
|
||||||
func NewBuilder() *Builder {
|
|
||||||
return &Builder{
|
|
||||||
set: Set{
|
|
||||||
Index: []uint16{0},
|
|
||||||
},
|
|
||||||
index: map[string]int{},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set creates the set created so far.
|
|
||||||
func (b *Builder) Set() Set {
|
|
||||||
return b.set
|
|
||||||
}
|
|
||||||
|
|
||||||
// Index returns the index for the given string, which must have been added
|
|
||||||
// before.
|
|
||||||
func (b *Builder) Index(s string) int {
|
|
||||||
return b.index[s]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add adds a string to the index. Strings that are added by a single Add will
|
|
||||||
// be stored together, unless they match an existing string.
|
|
||||||
func (b *Builder) Add(ss ...string) {
|
|
||||||
// First check if the string already exists.
|
|
||||||
for _, s := range ss {
|
|
||||||
if _, ok := b.index[s]; ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
b.index[s] = len(b.set.Index) - 1
|
|
||||||
b.set.Data += s
|
|
||||||
x := len(b.set.Data)
|
|
||||||
if x > 0xFFFF {
|
|
||||||
panic("Index too > 0xFFFF")
|
|
||||||
}
|
|
||||||
b.set.Index = append(b.set.Index, uint16(x))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
100
vendor/golang.org/x/text/internal/tag/tag.go
generated
vendored
100
vendor/golang.org/x/text/internal/tag/tag.go
generated
vendored
@@ -1,100 +0,0 @@
|
|||||||
// Copyright 2015 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 tag contains functionality handling tags and related data.
|
|
||||||
package tag // import "golang.org/x/text/internal/tag"
|
|
||||||
|
|
||||||
import "sort"
|
|
||||||
|
|
||||||
// An Index converts tags to a compact numeric value.
|
|
||||||
//
|
|
||||||
// All elements are of size 4. Tags may be up to 4 bytes long. Excess bytes can
|
|
||||||
// be used to store additional information about the tag.
|
|
||||||
type Index string
|
|
||||||
|
|
||||||
// Elem returns the element data at the given index.
|
|
||||||
func (s Index) Elem(x int) string {
|
|
||||||
return string(s[x*4 : x*4+4])
|
|
||||||
}
|
|
||||||
|
|
||||||
// Index reports the index of the given key or -1 if it could not be found.
|
|
||||||
// Only the first len(key) bytes from the start of the 4-byte entries will be
|
|
||||||
// considered for the search and the first match in Index will be returned.
|
|
||||||
func (s Index) Index(key []byte) int {
|
|
||||||
n := len(key)
|
|
||||||
// search the index of the first entry with an equal or higher value than
|
|
||||||
// key in s.
|
|
||||||
index := sort.Search(len(s)/4, func(i int) bool {
|
|
||||||
return cmp(s[i*4:i*4+n], key) != -1
|
|
||||||
})
|
|
||||||
i := index * 4
|
|
||||||
if cmp(s[i:i+len(key)], key) != 0 {
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
return index
|
|
||||||
}
|
|
||||||
|
|
||||||
// Next finds the next occurrence of key after index x, which must have been
|
|
||||||
// obtained from a call to Index using the same key. It returns x+1 or -1.
|
|
||||||
func (s Index) Next(key []byte, x int) int {
|
|
||||||
if x++; x*4 < len(s) && cmp(s[x*4:x*4+len(key)], key) == 0 {
|
|
||||||
return x
|
|
||||||
}
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
|
|
||||||
// cmp returns an integer comparing a and b lexicographically.
|
|
||||||
func cmp(a Index, b []byte) int {
|
|
||||||
n := len(a)
|
|
||||||
if len(b) < n {
|
|
||||||
n = len(b)
|
|
||||||
}
|
|
||||||
for i, c := range b[:n] {
|
|
||||||
switch {
|
|
||||||
case a[i] > c:
|
|
||||||
return 1
|
|
||||||
case a[i] < c:
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
switch {
|
|
||||||
case len(a) < len(b):
|
|
||||||
return -1
|
|
||||||
case len(a) > len(b):
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compare returns an integer comparing a and b lexicographically.
|
|
||||||
func Compare(a string, b []byte) int {
|
|
||||||
return cmp(Index(a), b)
|
|
||||||
}
|
|
||||||
|
|
||||||
// FixCase reformats b to the same pattern of cases as form.
|
|
||||||
// If returns false if string b is malformed.
|
|
||||||
func FixCase(form string, b []byte) bool {
|
|
||||||
if len(form) != len(b) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
for i, c := range b {
|
|
||||||
if form[i] <= 'Z' {
|
|
||||||
if c >= 'a' {
|
|
||||||
c -= 'z' - 'Z'
|
|
||||||
}
|
|
||||||
if c < 'A' || 'Z' < c {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if c <= 'Z' {
|
|
||||||
c += 'z' - 'Z'
|
|
||||||
}
|
|
||||||
if c < 'a' || 'z' < c {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
b[i] = c
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
187
vendor/golang.org/x/text/language/coverage.go
generated
vendored
187
vendor/golang.org/x/text/language/coverage.go
generated
vendored
@@ -1,187 +0,0 @@
|
|||||||
// Copyright 2014 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 language
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"sort"
|
|
||||||
|
|
||||||
"golang.org/x/text/internal/language"
|
|
||||||
)
|
|
||||||
|
|
||||||
// The Coverage interface is used to define the level of coverage of an
|
|
||||||
// internationalization service. Note that not all types are supported by all
|
|
||||||
// services. As lists may be generated on the fly, it is recommended that users
|
|
||||||
// of a Coverage cache the results.
|
|
||||||
type Coverage interface {
|
|
||||||
// Tags returns the list of supported tags.
|
|
||||||
Tags() []Tag
|
|
||||||
|
|
||||||
// BaseLanguages returns the list of supported base languages.
|
|
||||||
BaseLanguages() []Base
|
|
||||||
|
|
||||||
// Scripts returns the list of supported scripts.
|
|
||||||
Scripts() []Script
|
|
||||||
|
|
||||||
// Regions returns the list of supported regions.
|
|
||||||
Regions() []Region
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
// Supported defines a Coverage that lists all supported subtags. Tags
|
|
||||||
// always returns nil.
|
|
||||||
Supported Coverage = allSubtags{}
|
|
||||||
)
|
|
||||||
|
|
||||||
// TODO:
|
|
||||||
// - Support Variants, numbering systems.
|
|
||||||
// - CLDR coverage levels.
|
|
||||||
// - Set of common tags defined in this package.
|
|
||||||
|
|
||||||
type allSubtags struct{}
|
|
||||||
|
|
||||||
// Regions returns the list of supported regions. As all regions are in a
|
|
||||||
// consecutive range, it simply returns a slice of numbers in increasing order.
|
|
||||||
// The "undefined" region is not returned.
|
|
||||||
func (s allSubtags) Regions() []Region {
|
|
||||||
reg := make([]Region, language.NumRegions)
|
|
||||||
for i := range reg {
|
|
||||||
reg[i] = Region{language.Region(i + 1)}
|
|
||||||
}
|
|
||||||
return reg
|
|
||||||
}
|
|
||||||
|
|
||||||
// Scripts returns the list of supported scripts. As all scripts are in a
|
|
||||||
// consecutive range, it simply returns a slice of numbers in increasing order.
|
|
||||||
// The "undefined" script is not returned.
|
|
||||||
func (s allSubtags) Scripts() []Script {
|
|
||||||
scr := make([]Script, language.NumScripts)
|
|
||||||
for i := range scr {
|
|
||||||
scr[i] = Script{language.Script(i + 1)}
|
|
||||||
}
|
|
||||||
return scr
|
|
||||||
}
|
|
||||||
|
|
||||||
// BaseLanguages returns the list of all supported base languages. It generates
|
|
||||||
// the list by traversing the internal structures.
|
|
||||||
func (s allSubtags) BaseLanguages() []Base {
|
|
||||||
bs := language.BaseLanguages()
|
|
||||||
base := make([]Base, len(bs))
|
|
||||||
for i, b := range bs {
|
|
||||||
base[i] = Base{b}
|
|
||||||
}
|
|
||||||
return base
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tags always returns nil.
|
|
||||||
func (s allSubtags) Tags() []Tag {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// coverage is used by NewCoverage which is used as a convenient way for
|
|
||||||
// creating Coverage implementations for partially defined data. Very often a
|
|
||||||
// package will only need to define a subset of slices. coverage provides a
|
|
||||||
// convenient way to do this. Moreover, packages using NewCoverage, instead of
|
|
||||||
// their own implementation, will not break if later new slice types are added.
|
|
||||||
type coverage struct {
|
|
||||||
tags func() []Tag
|
|
||||||
bases func() []Base
|
|
||||||
scripts func() []Script
|
|
||||||
regions func() []Region
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *coverage) Tags() []Tag {
|
|
||||||
if s.tags == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return s.tags()
|
|
||||||
}
|
|
||||||
|
|
||||||
// bases implements sort.Interface and is used to sort base languages.
|
|
||||||
type bases []Base
|
|
||||||
|
|
||||||
func (b bases) Len() int {
|
|
||||||
return len(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b bases) Swap(i, j int) {
|
|
||||||
b[i], b[j] = b[j], b[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b bases) Less(i, j int) bool {
|
|
||||||
return b[i].langID < b[j].langID
|
|
||||||
}
|
|
||||||
|
|
||||||
// BaseLanguages returns the result from calling s.bases if it is specified or
|
|
||||||
// otherwise derives the set of supported base languages from tags.
|
|
||||||
func (s *coverage) BaseLanguages() []Base {
|
|
||||||
if s.bases == nil {
|
|
||||||
tags := s.Tags()
|
|
||||||
if len(tags) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
a := make([]Base, len(tags))
|
|
||||||
for i, t := range tags {
|
|
||||||
a[i] = Base{language.Language(t.lang())}
|
|
||||||
}
|
|
||||||
sort.Sort(bases(a))
|
|
||||||
k := 0
|
|
||||||
for i := 1; i < len(a); i++ {
|
|
||||||
if a[k] != a[i] {
|
|
||||||
k++
|
|
||||||
a[k] = a[i]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return a[:k+1]
|
|
||||||
}
|
|
||||||
return s.bases()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *coverage) Scripts() []Script {
|
|
||||||
if s.scripts == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return s.scripts()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *coverage) Regions() []Region {
|
|
||||||
if s.regions == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return s.regions()
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewCoverage returns a Coverage for the given lists. It is typically used by
|
|
||||||
// packages providing internationalization services to define their level of
|
|
||||||
// coverage. A list may be of type []T or func() []T, where T is either Tag,
|
|
||||||
// Base, Script or Region. The returned Coverage derives the value for Bases
|
|
||||||
// from Tags if no func or slice for []Base is specified. For other unspecified
|
|
||||||
// types the returned Coverage will return nil for the respective methods.
|
|
||||||
func NewCoverage(list ...interface{}) Coverage {
|
|
||||||
s := &coverage{}
|
|
||||||
for _, x := range list {
|
|
||||||
switch v := x.(type) {
|
|
||||||
case func() []Base:
|
|
||||||
s.bases = v
|
|
||||||
case func() []Script:
|
|
||||||
s.scripts = v
|
|
||||||
case func() []Region:
|
|
||||||
s.regions = v
|
|
||||||
case func() []Tag:
|
|
||||||
s.tags = v
|
|
||||||
case []Base:
|
|
||||||
s.bases = func() []Base { return v }
|
|
||||||
case []Script:
|
|
||||||
s.scripts = func() []Script { return v }
|
|
||||||
case []Region:
|
|
||||||
s.regions = func() []Region { return v }
|
|
||||||
case []Tag:
|
|
||||||
s.tags = func() []Tag { return v }
|
|
||||||
default:
|
|
||||||
panic(fmt.Sprintf("language: unsupported set type %T", v))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
98
vendor/golang.org/x/text/language/doc.go
generated
vendored
98
vendor/golang.org/x/text/language/doc.go
generated
vendored
@@ -1,98 +0,0 @@
|
|||||||
// Copyright 2017 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 language implements BCP 47 language tags and related functionality.
|
|
||||||
//
|
|
||||||
// The most important function of package language is to match a list of
|
|
||||||
// user-preferred languages to a list of supported languages.
|
|
||||||
// It alleviates the developer of dealing with the complexity of this process
|
|
||||||
// and provides the user with the best experience
|
|
||||||
// (see https://blog.golang.org/matchlang).
|
|
||||||
//
|
|
||||||
// # Matching preferred against supported languages
|
|
||||||
//
|
|
||||||
// A Matcher for an application that supports English, Australian English,
|
|
||||||
// Danish, and standard Mandarin can be created as follows:
|
|
||||||
//
|
|
||||||
// var matcher = language.NewMatcher([]language.Tag{
|
|
||||||
// language.English, // The first language is used as fallback.
|
|
||||||
// language.MustParse("en-AU"),
|
|
||||||
// language.Danish,
|
|
||||||
// language.Chinese,
|
|
||||||
// })
|
|
||||||
//
|
|
||||||
// This list of supported languages is typically implied by the languages for
|
|
||||||
// which there exists translations of the user interface.
|
|
||||||
//
|
|
||||||
// User-preferred languages usually come as a comma-separated list of BCP 47
|
|
||||||
// language tags.
|
|
||||||
// The MatchString finds best matches for such strings:
|
|
||||||
//
|
|
||||||
// handler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
// lang, _ := r.Cookie("lang")
|
|
||||||
// accept := r.Header.Get("Accept-Language")
|
|
||||||
// tag, _ := language.MatchStrings(matcher, lang.String(), accept)
|
|
||||||
//
|
|
||||||
// // tag should now be used for the initialization of any
|
|
||||||
// // locale-specific service.
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// The Matcher's Match method can be used to match Tags directly.
|
|
||||||
//
|
|
||||||
// Matchers are aware of the intricacies of equivalence between languages, such
|
|
||||||
// as deprecated subtags, legacy tags, macro languages, mutual
|
|
||||||
// intelligibility between scripts and languages, and transparently passing
|
|
||||||
// BCP 47 user configuration.
|
|
||||||
// For instance, it will know that a reader of Bokmål Danish can read Norwegian
|
|
||||||
// and will know that Cantonese ("yue") is a good match for "zh-HK".
|
|
||||||
//
|
|
||||||
// # Using match results
|
|
||||||
//
|
|
||||||
// To guarantee a consistent user experience to the user it is important to
|
|
||||||
// use the same language tag for the selection of any locale-specific services.
|
|
||||||
// For example, it is utterly confusing to substitute spelled-out numbers
|
|
||||||
// or dates in one language in text of another language.
|
|
||||||
// More subtly confusing is using the wrong sorting order or casing
|
|
||||||
// algorithm for a certain language.
|
|
||||||
//
|
|
||||||
// All the packages in x/text that provide locale-specific services
|
|
||||||
// (e.g. collate, cases) should be initialized with the tag that was
|
|
||||||
// obtained at the start of an interaction with the user.
|
|
||||||
//
|
|
||||||
// Note that Tag that is returned by Match and MatchString may differ from any
|
|
||||||
// of the supported languages, as it may contain carried over settings from
|
|
||||||
// the user tags.
|
|
||||||
// This may be inconvenient when your application has some additional
|
|
||||||
// locale-specific data for your supported languages.
|
|
||||||
// Match and MatchString both return the index of the matched supported tag
|
|
||||||
// to simplify associating such data with the matched tag.
|
|
||||||
//
|
|
||||||
// # Canonicalization
|
|
||||||
//
|
|
||||||
// If one uses the Matcher to compare languages one does not need to
|
|
||||||
// worry about canonicalization.
|
|
||||||
//
|
|
||||||
// The meaning of a Tag varies per application. The language package
|
|
||||||
// therefore delays canonicalization and preserves information as much
|
|
||||||
// as possible. The Matcher, however, will always take into account that
|
|
||||||
// two different tags may represent the same language.
|
|
||||||
//
|
|
||||||
// By default, only legacy and deprecated tags are converted into their
|
|
||||||
// canonical equivalent. All other information is preserved. This approach makes
|
|
||||||
// the confidence scores more accurate and allows matchers to distinguish
|
|
||||||
// between variants that are otherwise lost.
|
|
||||||
//
|
|
||||||
// As a consequence, two tags that should be treated as identical according to
|
|
||||||
// BCP 47 or CLDR, like "en-Latn" and "en", will be represented differently. The
|
|
||||||
// Matcher handles such distinctions, though, and is aware of the
|
|
||||||
// equivalence relations. The CanonType type can be used to alter the
|
|
||||||
// canonicalization form.
|
|
||||||
//
|
|
||||||
// # References
|
|
||||||
//
|
|
||||||
// BCP 47 - Tags for Identifying Languages http://tools.ietf.org/html/bcp47
|
|
||||||
package language // import "golang.org/x/text/language"
|
|
||||||
|
|
||||||
// TODO: explanation on how to match languages for your own locale-specific
|
|
||||||
// service.
|
|
||||||
605
vendor/golang.org/x/text/language/language.go
generated
vendored
605
vendor/golang.org/x/text/language/language.go
generated
vendored
@@ -1,605 +0,0 @@
|
|||||||
// Copyright 2013 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.
|
|
||||||
|
|
||||||
//go:generate go run gen.go -output tables.go
|
|
||||||
|
|
||||||
package language
|
|
||||||
|
|
||||||
// TODO: Remove above NOTE after:
|
|
||||||
// - verifying that tables are dropped correctly (most notably matcher tables).
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"golang.org/x/text/internal/language"
|
|
||||||
"golang.org/x/text/internal/language/compact"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Tag represents a BCP 47 language tag. It is used to specify an instance of a
|
|
||||||
// specific language or locale. All language tag values are guaranteed to be
|
|
||||||
// well-formed.
|
|
||||||
type Tag compact.Tag
|
|
||||||
|
|
||||||
func makeTag(t language.Tag) (tag Tag) {
|
|
||||||
return Tag(compact.Make(t))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Tag) tag() language.Tag {
|
|
||||||
return (*compact.Tag)(t).Tag()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Tag) isCompact() bool {
|
|
||||||
return (*compact.Tag)(t).IsCompact()
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: improve performance.
|
|
||||||
func (t *Tag) lang() language.Language { return t.tag().LangID }
|
|
||||||
func (t *Tag) region() language.Region { return t.tag().RegionID }
|
|
||||||
func (t *Tag) script() language.Script { return t.tag().ScriptID }
|
|
||||||
|
|
||||||
// Make is a convenience wrapper for Parse that omits the error.
|
|
||||||
// In case of an error, a sensible default is returned.
|
|
||||||
func Make(s string) Tag {
|
|
||||||
return Default.Make(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make is a convenience wrapper for c.Parse that omits the error.
|
|
||||||
// In case of an error, a sensible default is returned.
|
|
||||||
func (c CanonType) Make(s string) Tag {
|
|
||||||
t, _ := c.Parse(s)
|
|
||||||
return t
|
|
||||||
}
|
|
||||||
|
|
||||||
// Raw returns the raw base language, script and region, without making an
|
|
||||||
// attempt to infer their values.
|
|
||||||
func (t Tag) Raw() (b Base, s Script, r Region) {
|
|
||||||
tt := t.tag()
|
|
||||||
return Base{tt.LangID}, Script{tt.ScriptID}, Region{tt.RegionID}
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsRoot returns true if t is equal to language "und".
|
|
||||||
func (t Tag) IsRoot() bool {
|
|
||||||
return compact.Tag(t).IsRoot()
|
|
||||||
}
|
|
||||||
|
|
||||||
// CanonType can be used to enable or disable various types of canonicalization.
|
|
||||||
type CanonType int
|
|
||||||
|
|
||||||
const (
|
|
||||||
// Replace deprecated base languages with their preferred replacements.
|
|
||||||
DeprecatedBase CanonType = 1 << iota
|
|
||||||
// Replace deprecated scripts with their preferred replacements.
|
|
||||||
DeprecatedScript
|
|
||||||
// Replace deprecated regions with their preferred replacements.
|
|
||||||
DeprecatedRegion
|
|
||||||
// Remove redundant scripts.
|
|
||||||
SuppressScript
|
|
||||||
// Normalize legacy encodings. This includes legacy languages defined in
|
|
||||||
// CLDR as well as bibliographic codes defined in ISO-639.
|
|
||||||
Legacy
|
|
||||||
// Map the dominant language of a macro language group to the macro language
|
|
||||||
// subtag. For example cmn -> zh.
|
|
||||||
Macro
|
|
||||||
// The CLDR flag should be used if full compatibility with CLDR is required.
|
|
||||||
// There are a few cases where language.Tag may differ from CLDR. To follow all
|
|
||||||
// of CLDR's suggestions, use All|CLDR.
|
|
||||||
CLDR
|
|
||||||
|
|
||||||
// Raw can be used to Compose or Parse without Canonicalization.
|
|
||||||
Raw CanonType = 0
|
|
||||||
|
|
||||||
// Replace all deprecated tags with their preferred replacements.
|
|
||||||
Deprecated = DeprecatedBase | DeprecatedScript | DeprecatedRegion
|
|
||||||
|
|
||||||
// All canonicalizations recommended by BCP 47.
|
|
||||||
BCP47 = Deprecated | SuppressScript
|
|
||||||
|
|
||||||
// All canonicalizations.
|
|
||||||
All = BCP47 | Legacy | Macro
|
|
||||||
|
|
||||||
// Default is the canonicalization used by Parse, Make and Compose. To
|
|
||||||
// preserve as much information as possible, canonicalizations that remove
|
|
||||||
// potentially valuable information are not included. The Matcher is
|
|
||||||
// designed to recognize similar tags that would be the same if
|
|
||||||
// they were canonicalized using All.
|
|
||||||
Default = Deprecated | Legacy
|
|
||||||
|
|
||||||
canonLang = DeprecatedBase | Legacy | Macro
|
|
||||||
|
|
||||||
// TODO: LikelyScript, LikelyRegion: suppress similar to ICU.
|
|
||||||
)
|
|
||||||
|
|
||||||
// canonicalize returns the canonicalized equivalent of the tag and
|
|
||||||
// whether there was any change.
|
|
||||||
func canonicalize(c CanonType, t language.Tag) (language.Tag, bool) {
|
|
||||||
if c == Raw {
|
|
||||||
return t, false
|
|
||||||
}
|
|
||||||
changed := false
|
|
||||||
if c&SuppressScript != 0 {
|
|
||||||
if t.LangID.SuppressScript() == t.ScriptID {
|
|
||||||
t.ScriptID = 0
|
|
||||||
changed = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if c&canonLang != 0 {
|
|
||||||
for {
|
|
||||||
if l, aliasType := t.LangID.Canonicalize(); l != t.LangID {
|
|
||||||
switch aliasType {
|
|
||||||
case language.Legacy:
|
|
||||||
if c&Legacy != 0 {
|
|
||||||
if t.LangID == _sh && t.ScriptID == 0 {
|
|
||||||
t.ScriptID = _Latn
|
|
||||||
}
|
|
||||||
t.LangID = l
|
|
||||||
changed = true
|
|
||||||
}
|
|
||||||
case language.Macro:
|
|
||||||
if c&Macro != 0 {
|
|
||||||
// We deviate here from CLDR. The mapping "nb" -> "no"
|
|
||||||
// qualifies as a typical Macro language mapping. However,
|
|
||||||
// for legacy reasons, CLDR maps "no", the macro language
|
|
||||||
// code for Norwegian, to the dominant variant "nb". This
|
|
||||||
// change is currently under consideration for CLDR as well.
|
|
||||||
// See https://unicode.org/cldr/trac/ticket/2698 and also
|
|
||||||
// https://unicode.org/cldr/trac/ticket/1790 for some of the
|
|
||||||
// practical implications. TODO: this check could be removed
|
|
||||||
// if CLDR adopts this change.
|
|
||||||
if c&CLDR == 0 || t.LangID != _nb {
|
|
||||||
changed = true
|
|
||||||
t.LangID = l
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case language.Deprecated:
|
|
||||||
if c&DeprecatedBase != 0 {
|
|
||||||
if t.LangID == _mo && t.RegionID == 0 {
|
|
||||||
t.RegionID = _MD
|
|
||||||
}
|
|
||||||
t.LangID = l
|
|
||||||
changed = true
|
|
||||||
// Other canonicalization types may still apply.
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if c&Legacy != 0 && t.LangID == _no && c&CLDR != 0 {
|
|
||||||
t.LangID = _nb
|
|
||||||
changed = true
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if c&DeprecatedScript != 0 {
|
|
||||||
if t.ScriptID == _Qaai {
|
|
||||||
changed = true
|
|
||||||
t.ScriptID = _Zinh
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if c&DeprecatedRegion != 0 {
|
|
||||||
if r := t.RegionID.Canonicalize(); r != t.RegionID {
|
|
||||||
changed = true
|
|
||||||
t.RegionID = r
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return t, changed
|
|
||||||
}
|
|
||||||
|
|
||||||
// Canonicalize returns the canonicalized equivalent of the tag.
|
|
||||||
func (c CanonType) Canonicalize(t Tag) (Tag, error) {
|
|
||||||
// First try fast path.
|
|
||||||
if t.isCompact() {
|
|
||||||
if _, changed := canonicalize(c, compact.Tag(t).Tag()); !changed {
|
|
||||||
return t, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// It is unlikely that one will canonicalize a tag after matching. So do
|
|
||||||
// a slow but simple approach here.
|
|
||||||
if tag, changed := canonicalize(c, t.tag()); changed {
|
|
||||||
tag.RemakeString()
|
|
||||||
return makeTag(tag), nil
|
|
||||||
}
|
|
||||||
return t, nil
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Confidence indicates the level of certainty for a given return value.
|
|
||||||
// For example, Serbian may be written in Cyrillic or Latin script.
|
|
||||||
// The confidence level indicates whether a value was explicitly specified,
|
|
||||||
// whether it is typically the only possible value, or whether there is
|
|
||||||
// an ambiguity.
|
|
||||||
type Confidence int
|
|
||||||
|
|
||||||
const (
|
|
||||||
No Confidence = iota // full confidence that there was no match
|
|
||||||
Low // most likely value picked out of a set of alternatives
|
|
||||||
High // value is generally assumed to be the correct match
|
|
||||||
Exact // exact match or explicitly specified value
|
|
||||||
)
|
|
||||||
|
|
||||||
var confName = []string{"No", "Low", "High", "Exact"}
|
|
||||||
|
|
||||||
func (c Confidence) String() string {
|
|
||||||
return confName[c]
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns the canonical string representation of the language tag.
|
|
||||||
func (t Tag) String() string {
|
|
||||||
return t.tag().String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalText implements encoding.TextMarshaler.
|
|
||||||
func (t Tag) MarshalText() (text []byte, err error) {
|
|
||||||
return t.tag().MarshalText()
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalText implements encoding.TextUnmarshaler.
|
|
||||||
func (t *Tag) UnmarshalText(text []byte) error {
|
|
||||||
var tag language.Tag
|
|
||||||
err := tag.UnmarshalText(text)
|
|
||||||
*t = makeTag(tag)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Base returns the base language of the language tag. If the base language is
|
|
||||||
// unspecified, an attempt will be made to infer it from the context.
|
|
||||||
// It uses a variant of CLDR's Add Likely Subtags algorithm. This is subject to change.
|
|
||||||
func (t Tag) Base() (Base, Confidence) {
|
|
||||||
if b := t.lang(); b != 0 {
|
|
||||||
return Base{b}, Exact
|
|
||||||
}
|
|
||||||
tt := t.tag()
|
|
||||||
c := High
|
|
||||||
if tt.ScriptID == 0 && !tt.RegionID.IsCountry() {
|
|
||||||
c = Low
|
|
||||||
}
|
|
||||||
if tag, err := tt.Maximize(); err == nil && tag.LangID != 0 {
|
|
||||||
return Base{tag.LangID}, c
|
|
||||||
}
|
|
||||||
return Base{0}, No
|
|
||||||
}
|
|
||||||
|
|
||||||
// Script infers the script for the language tag. If it was not explicitly given, it will infer
|
|
||||||
// a most likely candidate.
|
|
||||||
// If more than one script is commonly used for a language, the most likely one
|
|
||||||
// is returned with a low confidence indication. For example, it returns (Cyrl, Low)
|
|
||||||
// for Serbian.
|
|
||||||
// If a script cannot be inferred (Zzzz, No) is returned. We do not use Zyyy (undetermined)
|
|
||||||
// as one would suspect from the IANA registry for BCP 47. In a Unicode context Zyyy marks
|
|
||||||
// common characters (like 1, 2, 3, '.', etc.) and is therefore more like multiple scripts.
|
|
||||||
// See https://www.unicode.org/reports/tr24/#Values for more details. Zzzz is also used for
|
|
||||||
// unknown value in CLDR. (Zzzz, Exact) is returned if Zzzz was explicitly specified.
|
|
||||||
// Note that an inferred script is never guaranteed to be the correct one. Latin is
|
|
||||||
// almost exclusively used for Afrikaans, but Arabic has been used for some texts
|
|
||||||
// in the past. Also, the script that is commonly used may change over time.
|
|
||||||
// It uses a variant of CLDR's Add Likely Subtags algorithm. This is subject to change.
|
|
||||||
func (t Tag) Script() (Script, Confidence) {
|
|
||||||
if scr := t.script(); scr != 0 {
|
|
||||||
return Script{scr}, Exact
|
|
||||||
}
|
|
||||||
tt := t.tag()
|
|
||||||
sc, c := language.Script(_Zzzz), No
|
|
||||||
if scr := tt.LangID.SuppressScript(); scr != 0 {
|
|
||||||
// Note: it is not always the case that a language with a suppress
|
|
||||||
// script value is only written in one script (e.g. kk, ms, pa).
|
|
||||||
if tt.RegionID == 0 {
|
|
||||||
return Script{scr}, High
|
|
||||||
}
|
|
||||||
sc, c = scr, High
|
|
||||||
}
|
|
||||||
if tag, err := tt.Maximize(); err == nil {
|
|
||||||
if tag.ScriptID != sc {
|
|
||||||
sc, c = tag.ScriptID, Low
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
tt, _ = canonicalize(Deprecated|Macro, tt)
|
|
||||||
if tag, err := tt.Maximize(); err == nil && tag.ScriptID != sc {
|
|
||||||
sc, c = tag.ScriptID, Low
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Script{sc}, c
|
|
||||||
}
|
|
||||||
|
|
||||||
// Region returns the region for the language tag. If it was not explicitly given, it will
|
|
||||||
// infer a most likely candidate from the context.
|
|
||||||
// It uses a variant of CLDR's Add Likely Subtags algorithm. This is subject to change.
|
|
||||||
func (t Tag) Region() (Region, Confidence) {
|
|
||||||
if r := t.region(); r != 0 {
|
|
||||||
return Region{r}, Exact
|
|
||||||
}
|
|
||||||
tt := t.tag()
|
|
||||||
if tt, err := tt.Maximize(); err == nil {
|
|
||||||
return Region{tt.RegionID}, Low // TODO: differentiate between high and low.
|
|
||||||
}
|
|
||||||
tt, _ = canonicalize(Deprecated|Macro, tt)
|
|
||||||
if tag, err := tt.Maximize(); err == nil {
|
|
||||||
return Region{tag.RegionID}, Low
|
|
||||||
}
|
|
||||||
return Region{_ZZ}, No // TODO: return world instead of undetermined?
|
|
||||||
}
|
|
||||||
|
|
||||||
// Variants returns the variants specified explicitly for this language tag.
|
|
||||||
// or nil if no variant was specified.
|
|
||||||
func (t Tag) Variants() []Variant {
|
|
||||||
if !compact.Tag(t).MayHaveVariants() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
v := []Variant{}
|
|
||||||
x, str := "", t.tag().Variants()
|
|
||||||
for str != "" {
|
|
||||||
x, str = nextToken(str)
|
|
||||||
v = append(v, Variant{x})
|
|
||||||
}
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parent returns the CLDR parent of t. In CLDR, missing fields in data for a
|
|
||||||
// specific language are substituted with fields from the parent language.
|
|
||||||
// The parent for a language may change for newer versions of CLDR.
|
|
||||||
//
|
|
||||||
// Parent returns a tag for a less specific language that is mutually
|
|
||||||
// intelligible or Und if there is no such language. This may not be the same as
|
|
||||||
// simply stripping the last BCP 47 subtag. For instance, the parent of "zh-TW"
|
|
||||||
// is "zh-Hant", and the parent of "zh-Hant" is "und".
|
|
||||||
func (t Tag) Parent() Tag {
|
|
||||||
return Tag(compact.Tag(t).Parent())
|
|
||||||
}
|
|
||||||
|
|
||||||
// nextToken returns token t and the rest of the string.
|
|
||||||
func nextToken(s string) (t, tail string) {
|
|
||||||
p := strings.Index(s[1:], "-")
|
|
||||||
if p == -1 {
|
|
||||||
return s[1:], ""
|
|
||||||
}
|
|
||||||
p++
|
|
||||||
return s[1:p], s[p:]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extension is a single BCP 47 extension.
|
|
||||||
type Extension struct {
|
|
||||||
s string
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns the string representation of the extension, including the
|
|
||||||
// type tag.
|
|
||||||
func (e Extension) String() string {
|
|
||||||
return e.s
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseExtension parses s as an extension and returns it on success.
|
|
||||||
func ParseExtension(s string) (e Extension, err error) {
|
|
||||||
ext, err := language.ParseExtension(s)
|
|
||||||
return Extension{ext}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Type returns the one-byte extension type of e. It returns 0 for the zero
|
|
||||||
// exception.
|
|
||||||
func (e Extension) Type() byte {
|
|
||||||
if e.s == "" {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return e.s[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tokens returns the list of tokens of e.
|
|
||||||
func (e Extension) Tokens() []string {
|
|
||||||
return strings.Split(e.s, "-")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extension returns the extension of type x for tag t. It will return
|
|
||||||
// false for ok if t does not have the requested extension. The returned
|
|
||||||
// extension will be invalid in this case.
|
|
||||||
func (t Tag) Extension(x byte) (ext Extension, ok bool) {
|
|
||||||
if !compact.Tag(t).MayHaveExtensions() {
|
|
||||||
return Extension{}, false
|
|
||||||
}
|
|
||||||
e, ok := t.tag().Extension(x)
|
|
||||||
return Extension{e}, ok
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extensions returns all extensions of t.
|
|
||||||
func (t Tag) Extensions() []Extension {
|
|
||||||
if !compact.Tag(t).MayHaveExtensions() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
e := []Extension{}
|
|
||||||
for _, ext := range t.tag().Extensions() {
|
|
||||||
e = append(e, Extension{ext})
|
|
||||||
}
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
|
|
||||||
// TypeForKey returns the type associated with the given key, where key and type
|
|
||||||
// are of the allowed values defined for the Unicode locale extension ('u') in
|
|
||||||
// https://www.unicode.org/reports/tr35/#Unicode_Language_and_Locale_Identifiers.
|
|
||||||
// TypeForKey will traverse the inheritance chain to get the correct value.
|
|
||||||
//
|
|
||||||
// If there are multiple types associated with a key, only the first will be
|
|
||||||
// returned. If there is no type associated with a key, it returns the empty
|
|
||||||
// string.
|
|
||||||
func (t Tag) TypeForKey(key string) string {
|
|
||||||
if !compact.Tag(t).MayHaveExtensions() {
|
|
||||||
if key != "rg" && key != "va" {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return t.tag().TypeForKey(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetTypeForKey returns a new Tag with the key set to type, where key and type
|
|
||||||
// are of the allowed values defined for the Unicode locale extension ('u') in
|
|
||||||
// https://www.unicode.org/reports/tr35/#Unicode_Language_and_Locale_Identifiers.
|
|
||||||
// An empty value removes an existing pair with the same key.
|
|
||||||
func (t Tag) SetTypeForKey(key, value string) (Tag, error) {
|
|
||||||
tt, err := t.tag().SetTypeForKey(key, value)
|
|
||||||
return makeTag(tt), err
|
|
||||||
}
|
|
||||||
|
|
||||||
// NumCompactTags is the number of compact tags. The maximum tag is
|
|
||||||
// NumCompactTags-1.
|
|
||||||
const NumCompactTags = compact.NumCompactTags
|
|
||||||
|
|
||||||
// CompactIndex returns an index, where 0 <= index < NumCompactTags, for tags
|
|
||||||
// for which data exists in the text repository.The index will change over time
|
|
||||||
// and should not be stored in persistent storage. If t does not match a compact
|
|
||||||
// index, exact will be false and the compact index will be returned for the
|
|
||||||
// first match after repeatedly taking the Parent of t.
|
|
||||||
func CompactIndex(t Tag) (index int, exact bool) {
|
|
||||||
id, exact := compact.LanguageID(compact.Tag(t))
|
|
||||||
return int(id), exact
|
|
||||||
}
|
|
||||||
|
|
||||||
var root = language.Tag{}
|
|
||||||
|
|
||||||
// Base is an ISO 639 language code, used for encoding the base language
|
|
||||||
// of a language tag.
|
|
||||||
type Base struct {
|
|
||||||
langID language.Language
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseBase parses a 2- or 3-letter ISO 639 code.
|
|
||||||
// It returns a ValueError if s is a well-formed but unknown language identifier
|
|
||||||
// or another error if another error occurred.
|
|
||||||
func ParseBase(s string) (Base, error) {
|
|
||||||
l, err := language.ParseBase(s)
|
|
||||||
return Base{l}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns the BCP 47 representation of the base language.
|
|
||||||
func (b Base) String() string {
|
|
||||||
return b.langID.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// ISO3 returns the ISO 639-3 language code.
|
|
||||||
func (b Base) ISO3() string {
|
|
||||||
return b.langID.ISO3()
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsPrivateUse reports whether this language code is reserved for private use.
|
|
||||||
func (b Base) IsPrivateUse() bool {
|
|
||||||
return b.langID.IsPrivateUse()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Script is a 4-letter ISO 15924 code for representing scripts.
|
|
||||||
// It is idiomatically represented in title case.
|
|
||||||
type Script struct {
|
|
||||||
scriptID language.Script
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseScript parses a 4-letter ISO 15924 code.
|
|
||||||
// It returns a ValueError if s is a well-formed but unknown script identifier
|
|
||||||
// or another error if another error occurred.
|
|
||||||
func ParseScript(s string) (Script, error) {
|
|
||||||
sc, err := language.ParseScript(s)
|
|
||||||
return Script{sc}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns the script code in title case.
|
|
||||||
// It returns "Zzzz" for an unspecified script.
|
|
||||||
func (s Script) String() string {
|
|
||||||
return s.scriptID.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsPrivateUse reports whether this script code is reserved for private use.
|
|
||||||
func (s Script) IsPrivateUse() bool {
|
|
||||||
return s.scriptID.IsPrivateUse()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Region is an ISO 3166-1 or UN M.49 code for representing countries and regions.
|
|
||||||
type Region struct {
|
|
||||||
regionID language.Region
|
|
||||||
}
|
|
||||||
|
|
||||||
// EncodeM49 returns the Region for the given UN M.49 code.
|
|
||||||
// It returns an error if r is not a valid code.
|
|
||||||
func EncodeM49(r int) (Region, error) {
|
|
||||||
rid, err := language.EncodeM49(r)
|
|
||||||
return Region{rid}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseRegion parses a 2- or 3-letter ISO 3166-1 or a UN M.49 code.
|
|
||||||
// It returns a ValueError if s is a well-formed but unknown region identifier
|
|
||||||
// or another error if another error occurred.
|
|
||||||
func ParseRegion(s string) (Region, error) {
|
|
||||||
r, err := language.ParseRegion(s)
|
|
||||||
return Region{r}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns the BCP 47 representation for the region.
|
|
||||||
// It returns "ZZ" for an unspecified region.
|
|
||||||
func (r Region) String() string {
|
|
||||||
return r.regionID.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// ISO3 returns the 3-letter ISO code of r.
|
|
||||||
// Note that not all regions have a 3-letter ISO code.
|
|
||||||
// In such cases this method returns "ZZZ".
|
|
||||||
func (r Region) ISO3() string {
|
|
||||||
return r.regionID.ISO3()
|
|
||||||
}
|
|
||||||
|
|
||||||
// M49 returns the UN M.49 encoding of r, or 0 if this encoding
|
|
||||||
// is not defined for r.
|
|
||||||
func (r Region) M49() int {
|
|
||||||
return r.regionID.M49()
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsPrivateUse reports whether r has the ISO 3166 User-assigned status. This
|
|
||||||
// may include private-use tags that are assigned by CLDR and used in this
|
|
||||||
// implementation. So IsPrivateUse and IsCountry can be simultaneously true.
|
|
||||||
func (r Region) IsPrivateUse() bool {
|
|
||||||
return r.regionID.IsPrivateUse()
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsCountry returns whether this region is a country or autonomous area. This
|
|
||||||
// includes non-standard definitions from CLDR.
|
|
||||||
func (r Region) IsCountry() bool {
|
|
||||||
return r.regionID.IsCountry()
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsGroup returns whether this region defines a collection of regions. This
|
|
||||||
// includes non-standard definitions from CLDR.
|
|
||||||
func (r Region) IsGroup() bool {
|
|
||||||
return r.regionID.IsGroup()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Contains returns whether Region c is contained by Region r. It returns true
|
|
||||||
// if c == r.
|
|
||||||
func (r Region) Contains(c Region) bool {
|
|
||||||
return r.regionID.Contains(c.regionID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TLD returns the country code top-level domain (ccTLD). UK is returned for GB.
|
|
||||||
// In all other cases it returns either the region itself or an error.
|
|
||||||
//
|
|
||||||
// This method may return an error for a region for which there exists a
|
|
||||||
// canonical form with a ccTLD. To get that ccTLD canonicalize r first. The
|
|
||||||
// region will already be canonicalized it was obtained from a Tag that was
|
|
||||||
// obtained using any of the default methods.
|
|
||||||
func (r Region) TLD() (Region, error) {
|
|
||||||
tld, err := r.regionID.TLD()
|
|
||||||
return Region{tld}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Canonicalize returns the region or a possible replacement if the region is
|
|
||||||
// deprecated. It will not return a replacement for deprecated regions that
|
|
||||||
// are split into multiple regions.
|
|
||||||
func (r Region) Canonicalize() Region {
|
|
||||||
return Region{r.regionID.Canonicalize()}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Variant represents a registered variant of a language as defined by BCP 47.
|
|
||||||
type Variant struct {
|
|
||||||
variant string
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseVariant parses and returns a Variant. An error is returned if s is not
|
|
||||||
// a valid variant.
|
|
||||||
func ParseVariant(s string) (Variant, error) {
|
|
||||||
v, err := language.ParseVariant(s)
|
|
||||||
return Variant{v.String()}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns the string representation of the variant.
|
|
||||||
func (v Variant) String() string {
|
|
||||||
return v.variant
|
|
||||||
}
|
|
||||||
735
vendor/golang.org/x/text/language/match.go
generated
vendored
735
vendor/golang.org/x/text/language/match.go
generated
vendored
@@ -1,735 +0,0 @@
|
|||||||
// Copyright 2013 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 language
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"golang.org/x/text/internal/language"
|
|
||||||
)
|
|
||||||
|
|
||||||
// A MatchOption configures a Matcher.
|
|
||||||
type MatchOption func(*matcher)
|
|
||||||
|
|
||||||
// PreferSameScript will, in the absence of a match, result in the first
|
|
||||||
// preferred tag with the same script as a supported tag to match this supported
|
|
||||||
// tag. The default is currently true, but this may change in the future.
|
|
||||||
func PreferSameScript(preferSame bool) MatchOption {
|
|
||||||
return func(m *matcher) { m.preferSameScript = preferSame }
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(v1.0.0): consider making Matcher a concrete type, instead of interface.
|
|
||||||
// There doesn't seem to be too much need for multiple types.
|
|
||||||
// Making it a concrete type allows MatchStrings to be a method, which will
|
|
||||||
// improve its discoverability.
|
|
||||||
|
|
||||||
// MatchStrings parses and matches the given strings until one of them matches
|
|
||||||
// the language in the Matcher. A string may be an Accept-Language header as
|
|
||||||
// handled by ParseAcceptLanguage. The default language is returned if no
|
|
||||||
// other language matched.
|
|
||||||
func MatchStrings(m Matcher, lang ...string) (tag Tag, index int) {
|
|
||||||
for _, accept := range lang {
|
|
||||||
desired, _, err := ParseAcceptLanguage(accept)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if tag, index, conf := m.Match(desired...); conf != No {
|
|
||||||
return tag, index
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tag, index, _ = m.Match()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Matcher is the interface that wraps the Match method.
|
|
||||||
//
|
|
||||||
// Match returns the best match for any of the given tags, along with
|
|
||||||
// a unique index associated with the returned tag and a confidence
|
|
||||||
// score.
|
|
||||||
type Matcher interface {
|
|
||||||
Match(t ...Tag) (tag Tag, index int, c Confidence)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Comprehends reports the confidence score for a speaker of a given language
|
|
||||||
// to being able to comprehend the written form of an alternative language.
|
|
||||||
func Comprehends(speaker, alternative Tag) Confidence {
|
|
||||||
_, _, c := NewMatcher([]Tag{alternative}).Match(speaker)
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMatcher returns a Matcher that matches an ordered list of preferred tags
|
|
||||||
// against a list of supported tags based on written intelligibility, closeness
|
|
||||||
// of dialect, equivalence of subtags and various other rules. It is initialized
|
|
||||||
// with the list of supported tags. The first element is used as the default
|
|
||||||
// value in case no match is found.
|
|
||||||
//
|
|
||||||
// Its Match method matches the first of the given Tags to reach a certain
|
|
||||||
// confidence threshold. The tags passed to Match should therefore be specified
|
|
||||||
// in order of preference. Extensions are ignored for matching.
|
|
||||||
//
|
|
||||||
// The index returned by the Match method corresponds to the index of the
|
|
||||||
// matched tag in t, but is augmented with the Unicode extension ('u')of the
|
|
||||||
// corresponding preferred tag. This allows user locale options to be passed
|
|
||||||
// transparently.
|
|
||||||
func NewMatcher(t []Tag, options ...MatchOption) Matcher {
|
|
||||||
return newMatcher(t, options)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *matcher) Match(want ...Tag) (t Tag, index int, c Confidence) {
|
|
||||||
var tt language.Tag
|
|
||||||
match, w, c := m.getBest(want...)
|
|
||||||
if match != nil {
|
|
||||||
tt, index = match.tag, match.index
|
|
||||||
} else {
|
|
||||||
// TODO: this should be an option
|
|
||||||
tt = m.default_.tag
|
|
||||||
if m.preferSameScript {
|
|
||||||
outer:
|
|
||||||
for _, w := range want {
|
|
||||||
script, _ := w.Script()
|
|
||||||
if script.scriptID == 0 {
|
|
||||||
// Don't do anything if there is no script, such as with
|
|
||||||
// private subtags.
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for i, h := range m.supported {
|
|
||||||
if script.scriptID == h.maxScript {
|
|
||||||
tt, index = h.tag, i
|
|
||||||
break outer
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// TODO: select first language tag based on script.
|
|
||||||
}
|
|
||||||
if w.RegionID != tt.RegionID && w.RegionID != 0 {
|
|
||||||
if w.RegionID != 0 && tt.RegionID != 0 && tt.RegionID.Contains(w.RegionID) {
|
|
||||||
tt.RegionID = w.RegionID
|
|
||||||
tt.RemakeString()
|
|
||||||
} else if r := w.RegionID.String(); len(r) == 2 {
|
|
||||||
// TODO: also filter macro and deprecated.
|
|
||||||
tt, _ = tt.SetTypeForKey("rg", strings.ToLower(r)+"zzzz")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Copy options from the user-provided tag into the result tag. This is hard
|
|
||||||
// to do after the fact, so we do it here.
|
|
||||||
// TODO: add in alternative variants to -u-va-.
|
|
||||||
// TODO: add preferred region to -u-rg-.
|
|
||||||
if e := w.Extensions(); len(e) > 0 {
|
|
||||||
b := language.Builder{}
|
|
||||||
b.SetTag(tt)
|
|
||||||
for _, e := range e {
|
|
||||||
b.AddExt(e)
|
|
||||||
}
|
|
||||||
tt = b.Make()
|
|
||||||
}
|
|
||||||
return makeTag(tt), index, c
|
|
||||||
}
|
|
||||||
|
|
||||||
// ErrMissingLikelyTagsData indicates no information was available
|
|
||||||
// to compute likely values of missing tags.
|
|
||||||
var ErrMissingLikelyTagsData = errors.New("missing likely tags data")
|
|
||||||
|
|
||||||
// func (t *Tag) setTagsFrom(id Tag) {
|
|
||||||
// t.LangID = id.LangID
|
|
||||||
// t.ScriptID = id.ScriptID
|
|
||||||
// t.RegionID = id.RegionID
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Tag Matching
|
|
||||||
// CLDR defines an algorithm for finding the best match between two sets of language
|
|
||||||
// tags. The basic algorithm defines how to score a possible match and then find
|
|
||||||
// the match with the best score
|
|
||||||
// (see https://www.unicode.org/reports/tr35/#LanguageMatching).
|
|
||||||
// Using scoring has several disadvantages. The scoring obfuscates the importance of
|
|
||||||
// the various factors considered, making the algorithm harder to understand. Using
|
|
||||||
// scoring also requires the full score to be computed for each pair of tags.
|
|
||||||
//
|
|
||||||
// We will use a different algorithm which aims to have the following properties:
|
|
||||||
// - clarity on the precedence of the various selection factors, and
|
|
||||||
// - improved performance by allowing early termination of a comparison.
|
|
||||||
//
|
|
||||||
// Matching algorithm (overview)
|
|
||||||
// Input:
|
|
||||||
// - supported: a set of supported tags
|
|
||||||
// - default: the default tag to return in case there is no match
|
|
||||||
// - desired: list of desired tags, ordered by preference, starting with
|
|
||||||
// the most-preferred.
|
|
||||||
//
|
|
||||||
// Algorithm:
|
|
||||||
// 1) Set the best match to the lowest confidence level
|
|
||||||
// 2) For each tag in "desired":
|
|
||||||
// a) For each tag in "supported":
|
|
||||||
// 1) compute the match between the two tags.
|
|
||||||
// 2) if the match is better than the previous best match, replace it
|
|
||||||
// with the new match. (see next section)
|
|
||||||
// b) if the current best match is Exact and pin is true the result will be
|
|
||||||
// frozen to the language found thusfar, although better matches may
|
|
||||||
// still be found for the same language.
|
|
||||||
// 3) If the best match so far is below a certain threshold, return "default".
|
|
||||||
//
|
|
||||||
// Ranking:
|
|
||||||
// We use two phases to determine whether one pair of tags are a better match
|
|
||||||
// than another pair of tags. First, we determine a rough confidence level. If the
|
|
||||||
// levels are different, the one with the highest confidence wins.
|
|
||||||
// Second, if the rough confidence levels are identical, we use a set of tie-breaker
|
|
||||||
// rules.
|
|
||||||
//
|
|
||||||
// The confidence level of matching a pair of tags is determined by finding the
|
|
||||||
// lowest confidence level of any matches of the corresponding subtags (the
|
|
||||||
// result is deemed as good as its weakest link).
|
|
||||||
// We define the following levels:
|
|
||||||
// Exact - An exact match of a subtag, before adding likely subtags.
|
|
||||||
// MaxExact - An exact match of a subtag, after adding likely subtags.
|
|
||||||
// [See Note 2].
|
|
||||||
// High - High level of mutual intelligibility between different subtag
|
|
||||||
// variants.
|
|
||||||
// Low - Low level of mutual intelligibility between different subtag
|
|
||||||
// variants.
|
|
||||||
// No - No mutual intelligibility.
|
|
||||||
//
|
|
||||||
// The following levels can occur for each type of subtag:
|
|
||||||
// Base: Exact, MaxExact, High, Low, No
|
|
||||||
// Script: Exact, MaxExact [see Note 3], Low, No
|
|
||||||
// Region: Exact, MaxExact, High
|
|
||||||
// Variant: Exact, High
|
|
||||||
// Private: Exact, No
|
|
||||||
//
|
|
||||||
// Any result with a confidence level of Low or higher is deemed a possible match.
|
|
||||||
// Once a desired tag matches any of the supported tags with a level of MaxExact
|
|
||||||
// or higher, the next desired tag is not considered (see Step 2.b).
|
|
||||||
// Note that CLDR provides languageMatching data that defines close equivalence
|
|
||||||
// classes for base languages, scripts and regions.
|
|
||||||
//
|
|
||||||
// Tie-breaking
|
|
||||||
// If we get the same confidence level for two matches, we apply a sequence of
|
|
||||||
// tie-breaking rules. The first that succeeds defines the result. The rules are
|
|
||||||
// applied in the following order.
|
|
||||||
// 1) Original language was defined and was identical.
|
|
||||||
// 2) Original region was defined and was identical.
|
|
||||||
// 3) Distance between two maximized regions was the smallest.
|
|
||||||
// 4) Original script was defined and was identical.
|
|
||||||
// 5) Distance from want tag to have tag using the parent relation [see Note 5.]
|
|
||||||
// If there is still no winner after these rules are applied, the first match
|
|
||||||
// found wins.
|
|
||||||
//
|
|
||||||
// Notes:
|
|
||||||
// [2] In practice, as matching of Exact is done in a separate phase from
|
|
||||||
// matching the other levels, we reuse the Exact level to mean MaxExact in
|
|
||||||
// the second phase. As a consequence, we only need the levels defined by
|
|
||||||
// the Confidence type. The MaxExact confidence level is mapped to High in
|
|
||||||
// the public API.
|
|
||||||
// [3] We do not differentiate between maximized script values that were derived
|
|
||||||
// from suppressScript versus most likely tag data. We determined that in
|
|
||||||
// ranking the two, one ranks just after the other. Moreover, the two cannot
|
|
||||||
// occur concurrently. As a consequence, they are identical for practical
|
|
||||||
// purposes.
|
|
||||||
// [4] In case of deprecated, macro-equivalents and legacy mappings, we assign
|
|
||||||
// the MaxExact level to allow iw vs he to still be a closer match than
|
|
||||||
// en-AU vs en-US, for example.
|
|
||||||
// [5] In CLDR a locale inherits fields that are unspecified for this locale
|
|
||||||
// from its parent. Therefore, if a locale is a parent of another locale,
|
|
||||||
// it is a strong measure for closeness, especially when no other tie
|
|
||||||
// breaker rule applies. One could also argue it is inconsistent, for
|
|
||||||
// example, when pt-AO matches pt (which CLDR equates with pt-BR), even
|
|
||||||
// though its parent is pt-PT according to the inheritance rules.
|
|
||||||
//
|
|
||||||
// Implementation Details:
|
|
||||||
// There are several performance considerations worth pointing out. Most notably,
|
|
||||||
// we preprocess as much as possible (within reason) at the time of creation of a
|
|
||||||
// matcher. This includes:
|
|
||||||
// - creating a per-language map, which includes data for the raw base language
|
|
||||||
// and its canonicalized variant (if applicable),
|
|
||||||
// - expanding entries for the equivalence classes defined in CLDR's
|
|
||||||
// languageMatch data.
|
|
||||||
// The per-language map ensures that typically only a very small number of tags
|
|
||||||
// need to be considered. The pre-expansion of canonicalized subtags and
|
|
||||||
// equivalence classes reduces the amount of map lookups that need to be done at
|
|
||||||
// runtime.
|
|
||||||
|
|
||||||
// matcher keeps a set of supported language tags, indexed by language.
|
|
||||||
type matcher struct {
|
|
||||||
default_ *haveTag
|
|
||||||
supported []*haveTag
|
|
||||||
index map[language.Language]*matchHeader
|
|
||||||
passSettings bool
|
|
||||||
preferSameScript bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// matchHeader has the lists of tags for exact matches and matches based on
|
|
||||||
// maximized and canonicalized tags for a given language.
|
|
||||||
type matchHeader struct {
|
|
||||||
haveTags []*haveTag
|
|
||||||
original bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// haveTag holds a supported Tag and its maximized script and region. The maximized
|
|
||||||
// or canonicalized language is not stored as it is not needed during matching.
|
|
||||||
type haveTag struct {
|
|
||||||
tag language.Tag
|
|
||||||
|
|
||||||
// index of this tag in the original list of supported tags.
|
|
||||||
index int
|
|
||||||
|
|
||||||
// conf is the maximum confidence that can result from matching this haveTag.
|
|
||||||
// When conf < Exact this means it was inserted after applying a CLDR equivalence rule.
|
|
||||||
conf Confidence
|
|
||||||
|
|
||||||
// Maximized region and script.
|
|
||||||
maxRegion language.Region
|
|
||||||
maxScript language.Script
|
|
||||||
|
|
||||||
// altScript may be checked as an alternative match to maxScript. If altScript
|
|
||||||
// matches, the confidence level for this match is Low. Theoretically there
|
|
||||||
// could be multiple alternative scripts. This does not occur in practice.
|
|
||||||
altScript language.Script
|
|
||||||
|
|
||||||
// nextMax is the index of the next haveTag with the same maximized tags.
|
|
||||||
nextMax uint16
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeHaveTag(tag language.Tag, index int) (haveTag, language.Language) {
|
|
||||||
max := tag
|
|
||||||
if tag.LangID != 0 || tag.RegionID != 0 || tag.ScriptID != 0 {
|
|
||||||
max, _ = canonicalize(All, max)
|
|
||||||
max, _ = max.Maximize()
|
|
||||||
max.RemakeString()
|
|
||||||
}
|
|
||||||
return haveTag{tag, index, Exact, max.RegionID, max.ScriptID, altScript(max.LangID, max.ScriptID), 0}, max.LangID
|
|
||||||
}
|
|
||||||
|
|
||||||
// altScript returns an alternative script that may match the given script with
|
|
||||||
// a low confidence. At the moment, the langMatch data allows for at most one
|
|
||||||
// script to map to another and we rely on this to keep the code simple.
|
|
||||||
func altScript(l language.Language, s language.Script) language.Script {
|
|
||||||
for _, alt := range matchScript {
|
|
||||||
// TODO: also match cases where language is not the same.
|
|
||||||
if (language.Language(alt.wantLang) == l || language.Language(alt.haveLang) == l) &&
|
|
||||||
language.Script(alt.haveScript) == s {
|
|
||||||
return language.Script(alt.wantScript)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// addIfNew adds a haveTag to the list of tags only if it is a unique tag.
|
|
||||||
// Tags that have the same maximized values are linked by index.
|
|
||||||
func (h *matchHeader) addIfNew(n haveTag, exact bool) {
|
|
||||||
h.original = h.original || exact
|
|
||||||
// Don't add new exact matches.
|
|
||||||
for _, v := range h.haveTags {
|
|
||||||
if equalsRest(v.tag, n.tag) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Allow duplicate maximized tags, but create a linked list to allow quickly
|
|
||||||
// comparing the equivalents and bail out.
|
|
||||||
for i, v := range h.haveTags {
|
|
||||||
if v.maxScript == n.maxScript &&
|
|
||||||
v.maxRegion == n.maxRegion &&
|
|
||||||
v.tag.VariantOrPrivateUseTags() == n.tag.VariantOrPrivateUseTags() {
|
|
||||||
for h.haveTags[i].nextMax != 0 {
|
|
||||||
i = int(h.haveTags[i].nextMax)
|
|
||||||
}
|
|
||||||
h.haveTags[i].nextMax = uint16(len(h.haveTags))
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
h.haveTags = append(h.haveTags, &n)
|
|
||||||
}
|
|
||||||
|
|
||||||
// header returns the matchHeader for the given language. It creates one if
|
|
||||||
// it doesn't already exist.
|
|
||||||
func (m *matcher) header(l language.Language) *matchHeader {
|
|
||||||
if h := m.index[l]; h != nil {
|
|
||||||
return h
|
|
||||||
}
|
|
||||||
h := &matchHeader{}
|
|
||||||
m.index[l] = h
|
|
||||||
return h
|
|
||||||
}
|
|
||||||
|
|
||||||
func toConf(d uint8) Confidence {
|
|
||||||
if d <= 10 {
|
|
||||||
return High
|
|
||||||
}
|
|
||||||
if d < 30 {
|
|
||||||
return Low
|
|
||||||
}
|
|
||||||
return No
|
|
||||||
}
|
|
||||||
|
|
||||||
// newMatcher builds an index for the given supported tags and returns it as
|
|
||||||
// a matcher. It also expands the index by considering various equivalence classes
|
|
||||||
// for a given tag.
|
|
||||||
func newMatcher(supported []Tag, options []MatchOption) *matcher {
|
|
||||||
m := &matcher{
|
|
||||||
index: make(map[language.Language]*matchHeader),
|
|
||||||
preferSameScript: true,
|
|
||||||
}
|
|
||||||
for _, o := range options {
|
|
||||||
o(m)
|
|
||||||
}
|
|
||||||
if len(supported) == 0 {
|
|
||||||
m.default_ = &haveTag{}
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
// Add supported languages to the index. Add exact matches first to give
|
|
||||||
// them precedence.
|
|
||||||
for i, tag := range supported {
|
|
||||||
tt := tag.tag()
|
|
||||||
pair, _ := makeHaveTag(tt, i)
|
|
||||||
m.header(tt.LangID).addIfNew(pair, true)
|
|
||||||
m.supported = append(m.supported, &pair)
|
|
||||||
}
|
|
||||||
m.default_ = m.header(supported[0].lang()).haveTags[0]
|
|
||||||
// Keep these in two different loops to support the case that two equivalent
|
|
||||||
// languages are distinguished, such as iw and he.
|
|
||||||
for i, tag := range supported {
|
|
||||||
tt := tag.tag()
|
|
||||||
pair, max := makeHaveTag(tt, i)
|
|
||||||
if max != tt.LangID {
|
|
||||||
m.header(max).addIfNew(pair, true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// update is used to add indexes in the map for equivalent languages.
|
|
||||||
// update will only add entries to original indexes, thus not computing any
|
|
||||||
// transitive relations.
|
|
||||||
update := func(want, have uint16, conf Confidence) {
|
|
||||||
if hh := m.index[language.Language(have)]; hh != nil {
|
|
||||||
if !hh.original {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
hw := m.header(language.Language(want))
|
|
||||||
for _, ht := range hh.haveTags {
|
|
||||||
v := *ht
|
|
||||||
if conf < v.conf {
|
|
||||||
v.conf = conf
|
|
||||||
}
|
|
||||||
v.nextMax = 0 // this value needs to be recomputed
|
|
||||||
if v.altScript != 0 {
|
|
||||||
v.altScript = altScript(language.Language(want), v.maxScript)
|
|
||||||
}
|
|
||||||
hw.addIfNew(v, conf == Exact && hh.original)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add entries for languages with mutual intelligibility as defined by CLDR's
|
|
||||||
// languageMatch data.
|
|
||||||
for _, ml := range matchLang {
|
|
||||||
update(ml.want, ml.have, toConf(ml.distance))
|
|
||||||
if !ml.oneway {
|
|
||||||
update(ml.have, ml.want, toConf(ml.distance))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add entries for possible canonicalizations. This is an optimization to
|
|
||||||
// ensure that only one map lookup needs to be done at runtime per desired tag.
|
|
||||||
// First we match deprecated equivalents. If they are perfect equivalents
|
|
||||||
// (their canonicalization simply substitutes a different language code, but
|
|
||||||
// nothing else), the match confidence is Exact, otherwise it is High.
|
|
||||||
for i, lm := range language.AliasMap {
|
|
||||||
// If deprecated codes match and there is no fiddling with the script
|
|
||||||
// or region, we consider it an exact match.
|
|
||||||
conf := Exact
|
|
||||||
if language.AliasTypes[i] != language.Macro {
|
|
||||||
if !isExactEquivalent(language.Language(lm.From)) {
|
|
||||||
conf = High
|
|
||||||
}
|
|
||||||
update(lm.To, lm.From, conf)
|
|
||||||
}
|
|
||||||
update(lm.From, lm.To, conf)
|
|
||||||
}
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
// getBest gets the best matching tag in m for any of the given tags, taking into
|
|
||||||
// account the order of preference of the given tags.
|
|
||||||
func (m *matcher) getBest(want ...Tag) (got *haveTag, orig language.Tag, c Confidence) {
|
|
||||||
best := bestMatch{}
|
|
||||||
for i, ww := range want {
|
|
||||||
w := ww.tag()
|
|
||||||
var max language.Tag
|
|
||||||
// Check for exact match first.
|
|
||||||
h := m.index[w.LangID]
|
|
||||||
if w.LangID != 0 {
|
|
||||||
if h == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// Base language is defined.
|
|
||||||
max, _ = canonicalize(Legacy|Deprecated|Macro, w)
|
|
||||||
// A region that is added through canonicalization is stronger than
|
|
||||||
// a maximized region: set it in the original (e.g. mo -> ro-MD).
|
|
||||||
if w.RegionID != max.RegionID {
|
|
||||||
w.RegionID = max.RegionID
|
|
||||||
}
|
|
||||||
// TODO: should we do the same for scripts?
|
|
||||||
// See test case: en, sr, nl ; sh ; sr
|
|
||||||
max, _ = max.Maximize()
|
|
||||||
} else {
|
|
||||||
// Base language is not defined.
|
|
||||||
if h != nil {
|
|
||||||
for i := range h.haveTags {
|
|
||||||
have := h.haveTags[i]
|
|
||||||
if equalsRest(have.tag, w) {
|
|
||||||
return have, w, Exact
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if w.ScriptID == 0 && w.RegionID == 0 {
|
|
||||||
// We skip all tags matching und for approximate matching, including
|
|
||||||
// private tags.
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
max, _ = w.Maximize()
|
|
||||||
if h = m.index[max.LangID]; h == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pin := true
|
|
||||||
for _, t := range want[i+1:] {
|
|
||||||
if w.LangID == t.lang() {
|
|
||||||
pin = false
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Check for match based on maximized tag.
|
|
||||||
for i := range h.haveTags {
|
|
||||||
have := h.haveTags[i]
|
|
||||||
best.update(have, w, max.ScriptID, max.RegionID, pin)
|
|
||||||
if best.conf == Exact {
|
|
||||||
for have.nextMax != 0 {
|
|
||||||
have = h.haveTags[have.nextMax]
|
|
||||||
best.update(have, w, max.ScriptID, max.RegionID, pin)
|
|
||||||
}
|
|
||||||
return best.have, best.want, best.conf
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if best.conf <= No {
|
|
||||||
if len(want) != 0 {
|
|
||||||
return nil, want[0].tag(), No
|
|
||||||
}
|
|
||||||
return nil, language.Tag{}, No
|
|
||||||
}
|
|
||||||
return best.have, best.want, best.conf
|
|
||||||
}
|
|
||||||
|
|
||||||
// bestMatch accumulates the best match so far.
|
|
||||||
type bestMatch struct {
|
|
||||||
have *haveTag
|
|
||||||
want language.Tag
|
|
||||||
conf Confidence
|
|
||||||
pinnedRegion language.Region
|
|
||||||
pinLanguage bool
|
|
||||||
sameRegionGroup bool
|
|
||||||
// Cached results from applying tie-breaking rules.
|
|
||||||
origLang bool
|
|
||||||
origReg bool
|
|
||||||
paradigmReg bool
|
|
||||||
regGroupDist uint8
|
|
||||||
origScript bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// update updates the existing best match if the new pair is considered to be a
|
|
||||||
// better match. To determine if the given pair is a better match, it first
|
|
||||||
// computes the rough confidence level. If this surpasses the current match, it
|
|
||||||
// will replace it and update the tie-breaker rule cache. If there is a tie, it
|
|
||||||
// proceeds with applying a series of tie-breaker rules. If there is no
|
|
||||||
// conclusive winner after applying the tie-breaker rules, it leaves the current
|
|
||||||
// match as the preferred match.
|
|
||||||
//
|
|
||||||
// If pin is true and have and tag are a strong match, it will henceforth only
|
|
||||||
// consider matches for this language. This corresponds to the idea that most
|
|
||||||
// users have a strong preference for the first defined language. A user can
|
|
||||||
// still prefer a second language over a dialect of the preferred language by
|
|
||||||
// explicitly specifying dialects, e.g. "en, nl, en-GB". In this case pin should
|
|
||||||
// be false.
|
|
||||||
func (m *bestMatch) update(have *haveTag, tag language.Tag, maxScript language.Script, maxRegion language.Region, pin bool) {
|
|
||||||
// Bail if the maximum attainable confidence is below that of the current best match.
|
|
||||||
c := have.conf
|
|
||||||
if c < m.conf {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Don't change the language once we already have found an exact match.
|
|
||||||
if m.pinLanguage && tag.LangID != m.want.LangID {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Pin the region group if we are comparing tags for the same language.
|
|
||||||
if tag.LangID == m.want.LangID && m.sameRegionGroup {
|
|
||||||
_, sameGroup := regionGroupDist(m.pinnedRegion, have.maxRegion, have.maxScript, m.want.LangID)
|
|
||||||
if !sameGroup {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if c == Exact && have.maxScript == maxScript {
|
|
||||||
// If there is another language and then another entry of this language,
|
|
||||||
// don't pin anything, otherwise pin the language.
|
|
||||||
m.pinLanguage = pin
|
|
||||||
}
|
|
||||||
if equalsRest(have.tag, tag) {
|
|
||||||
} else if have.maxScript != maxScript {
|
|
||||||
// There is usually very little comprehension between different scripts.
|
|
||||||
// In a few cases there may still be Low comprehension. This possibility
|
|
||||||
// is pre-computed and stored in have.altScript.
|
|
||||||
if Low < m.conf || have.altScript != maxScript {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c = Low
|
|
||||||
} else if have.maxRegion != maxRegion {
|
|
||||||
if High < c {
|
|
||||||
// There is usually a small difference between languages across regions.
|
|
||||||
c = High
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We store the results of the computations of the tie-breaker rules along
|
|
||||||
// with the best match. There is no need to do the checks once we determine
|
|
||||||
// we have a winner, but we do still need to do the tie-breaker computations.
|
|
||||||
// We use "beaten" to keep track if we still need to do the checks.
|
|
||||||
beaten := false // true if the new pair defeats the current one.
|
|
||||||
if c != m.conf {
|
|
||||||
if c < m.conf {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
beaten = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tie-breaker rules:
|
|
||||||
// We prefer if the pre-maximized language was specified and identical.
|
|
||||||
origLang := have.tag.LangID == tag.LangID && tag.LangID != 0
|
|
||||||
if !beaten && m.origLang != origLang {
|
|
||||||
if m.origLang {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
beaten = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// We prefer if the pre-maximized region was specified and identical.
|
|
||||||
origReg := have.tag.RegionID == tag.RegionID && tag.RegionID != 0
|
|
||||||
if !beaten && m.origReg != origReg {
|
|
||||||
if m.origReg {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
beaten = true
|
|
||||||
}
|
|
||||||
|
|
||||||
regGroupDist, sameGroup := regionGroupDist(have.maxRegion, maxRegion, maxScript, tag.LangID)
|
|
||||||
if !beaten && m.regGroupDist != regGroupDist {
|
|
||||||
if regGroupDist > m.regGroupDist {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
beaten = true
|
|
||||||
}
|
|
||||||
|
|
||||||
paradigmReg := isParadigmLocale(tag.LangID, have.maxRegion)
|
|
||||||
if !beaten && m.paradigmReg != paradigmReg {
|
|
||||||
if !paradigmReg {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
beaten = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Next we prefer if the pre-maximized script was specified and identical.
|
|
||||||
origScript := have.tag.ScriptID == tag.ScriptID && tag.ScriptID != 0
|
|
||||||
if !beaten && m.origScript != origScript {
|
|
||||||
if m.origScript {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
beaten = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update m to the newly found best match.
|
|
||||||
if beaten {
|
|
||||||
m.have = have
|
|
||||||
m.want = tag
|
|
||||||
m.conf = c
|
|
||||||
m.pinnedRegion = maxRegion
|
|
||||||
m.sameRegionGroup = sameGroup
|
|
||||||
m.origLang = origLang
|
|
||||||
m.origReg = origReg
|
|
||||||
m.paradigmReg = paradigmReg
|
|
||||||
m.origScript = origScript
|
|
||||||
m.regGroupDist = regGroupDist
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func isParadigmLocale(lang language.Language, r language.Region) bool {
|
|
||||||
for _, e := range paradigmLocales {
|
|
||||||
if language.Language(e[0]) == lang && (r == language.Region(e[1]) || r == language.Region(e[2])) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// regionGroupDist computes the distance between two regions based on their
|
|
||||||
// CLDR grouping.
|
|
||||||
func regionGroupDist(a, b language.Region, script language.Script, lang language.Language) (dist uint8, same bool) {
|
|
||||||
const defaultDistance = 4
|
|
||||||
|
|
||||||
aGroup := uint(regionToGroups[a]) << 1
|
|
||||||
bGroup := uint(regionToGroups[b]) << 1
|
|
||||||
for _, ri := range matchRegion {
|
|
||||||
if language.Language(ri.lang) == lang && (ri.script == 0 || language.Script(ri.script) == script) {
|
|
||||||
group := uint(1 << (ri.group &^ 0x80))
|
|
||||||
if 0x80&ri.group == 0 {
|
|
||||||
if aGroup&bGroup&group != 0 { // Both regions are in the group.
|
|
||||||
return ri.distance, ri.distance == defaultDistance
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (aGroup|bGroup)&group == 0 { // Both regions are not in the group.
|
|
||||||
return ri.distance, ri.distance == defaultDistance
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return defaultDistance, true
|
|
||||||
}
|
|
||||||
|
|
||||||
// equalsRest compares everything except the language.
|
|
||||||
func equalsRest(a, b language.Tag) bool {
|
|
||||||
// TODO: don't include extensions in this comparison. To do this efficiently,
|
|
||||||
// though, we should handle private tags separately.
|
|
||||||
return a.ScriptID == b.ScriptID && a.RegionID == b.RegionID && a.VariantOrPrivateUseTags() == b.VariantOrPrivateUseTags()
|
|
||||||
}
|
|
||||||
|
|
||||||
// isExactEquivalent returns true if canonicalizing the language will not alter
|
|
||||||
// the script or region of a tag.
|
|
||||||
func isExactEquivalent(l language.Language) bool {
|
|
||||||
for _, o := range notEquivalent {
|
|
||||||
if o == l {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
var notEquivalent []language.Language
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
// Create a list of all languages for which canonicalization may alter the
|
|
||||||
// script or region.
|
|
||||||
for _, lm := range language.AliasMap {
|
|
||||||
tag := language.Tag{LangID: language.Language(lm.From)}
|
|
||||||
if tag, _ = canonicalize(All, tag); tag.ScriptID != 0 || tag.RegionID != 0 {
|
|
||||||
notEquivalent = append(notEquivalent, language.Language(lm.From))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Maximize undefined regions of paradigm locales.
|
|
||||||
for i, v := range paradigmLocales {
|
|
||||||
t := language.Tag{LangID: language.Language(v[0])}
|
|
||||||
max, _ := t.Maximize()
|
|
||||||
if v[1] == 0 {
|
|
||||||
paradigmLocales[i][1] = uint16(max.RegionID)
|
|
||||||
}
|
|
||||||
if v[2] == 0 {
|
|
||||||
paradigmLocales[i][2] = uint16(max.RegionID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
256
vendor/golang.org/x/text/language/parse.go
generated
vendored
256
vendor/golang.org/x/text/language/parse.go
generated
vendored
@@ -1,256 +0,0 @@
|
|||||||
// Copyright 2013 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 language
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"sort"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"golang.org/x/text/internal/language"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ValueError is returned by any of the parsing functions when the
|
|
||||||
// input is well-formed but the respective subtag is not recognized
|
|
||||||
// as a valid value.
|
|
||||||
type ValueError interface {
|
|
||||||
error
|
|
||||||
|
|
||||||
// Subtag returns the subtag for which the error occurred.
|
|
||||||
Subtag() string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse parses the given BCP 47 string and returns a valid Tag. If parsing
|
|
||||||
// failed it returns an error and any part of the tag that could be parsed.
|
|
||||||
// If parsing succeeded but an unknown value was found, it returns
|
|
||||||
// ValueError. The Tag returned in this case is just stripped of the unknown
|
|
||||||
// value. All other values are preserved. It accepts tags in the BCP 47 format
|
|
||||||
// and extensions to this standard defined in
|
|
||||||
// https://www.unicode.org/reports/tr35/#Unicode_Language_and_Locale_Identifiers.
|
|
||||||
// The resulting tag is canonicalized using the default canonicalization type.
|
|
||||||
func Parse(s string) (t Tag, err error) {
|
|
||||||
return Default.Parse(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse parses the given BCP 47 string and returns a valid Tag. If parsing
|
|
||||||
// failed it returns an error and any part of the tag that could be parsed.
|
|
||||||
// If parsing succeeded but an unknown value was found, it returns
|
|
||||||
// ValueError. The Tag returned in this case is just stripped of the unknown
|
|
||||||
// value. All other values are preserved. It accepts tags in the BCP 47 format
|
|
||||||
// and extensions to this standard defined in
|
|
||||||
// https://www.unicode.org/reports/tr35/#Unicode_Language_and_Locale_Identifiers.
|
|
||||||
// The resulting tag is canonicalized using the canonicalization type c.
|
|
||||||
func (c CanonType) Parse(s string) (t Tag, err error) {
|
|
||||||
defer func() {
|
|
||||||
if recover() != nil {
|
|
||||||
t = Tag{}
|
|
||||||
err = language.ErrSyntax
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
tt, err := language.Parse(s)
|
|
||||||
if err != nil {
|
|
||||||
return makeTag(tt), err
|
|
||||||
}
|
|
||||||
tt, changed := canonicalize(c, tt)
|
|
||||||
if changed {
|
|
||||||
tt.RemakeString()
|
|
||||||
}
|
|
||||||
return makeTag(tt), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compose creates a Tag from individual parts, which may be of type Tag, Base,
|
|
||||||
// Script, Region, Variant, []Variant, Extension, []Extension or error. If a
|
|
||||||
// Base, Script or Region or slice of type Variant or Extension is passed more
|
|
||||||
// than once, the latter will overwrite the former. Variants and Extensions are
|
|
||||||
// accumulated, but if two extensions of the same type are passed, the latter
|
|
||||||
// will replace the former. For -u extensions, though, the key-type pairs are
|
|
||||||
// added, where later values overwrite older ones. A Tag overwrites all former
|
|
||||||
// values and typically only makes sense as the first argument. The resulting
|
|
||||||
// tag is returned after canonicalizing using the Default CanonType. If one or
|
|
||||||
// more errors are encountered, one of the errors is returned.
|
|
||||||
func Compose(part ...interface{}) (t Tag, err error) {
|
|
||||||
return Default.Compose(part...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compose creates a Tag from individual parts, which may be of type Tag, Base,
|
|
||||||
// Script, Region, Variant, []Variant, Extension, []Extension or error. If a
|
|
||||||
// Base, Script or Region or slice of type Variant or Extension is passed more
|
|
||||||
// than once, the latter will overwrite the former. Variants and Extensions are
|
|
||||||
// accumulated, but if two extensions of the same type are passed, the latter
|
|
||||||
// will replace the former. For -u extensions, though, the key-type pairs are
|
|
||||||
// added, where later values overwrite older ones. A Tag overwrites all former
|
|
||||||
// values and typically only makes sense as the first argument. The resulting
|
|
||||||
// tag is returned after canonicalizing using CanonType c. If one or more errors
|
|
||||||
// are encountered, one of the errors is returned.
|
|
||||||
func (c CanonType) Compose(part ...interface{}) (t Tag, err error) {
|
|
||||||
defer func() {
|
|
||||||
if recover() != nil {
|
|
||||||
t = Tag{}
|
|
||||||
err = language.ErrSyntax
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
var b language.Builder
|
|
||||||
if err = update(&b, part...); err != nil {
|
|
||||||
return und, err
|
|
||||||
}
|
|
||||||
b.Tag, _ = canonicalize(c, b.Tag)
|
|
||||||
return makeTag(b.Make()), err
|
|
||||||
}
|
|
||||||
|
|
||||||
var errInvalidArgument = errors.New("invalid Extension or Variant")
|
|
||||||
|
|
||||||
func update(b *language.Builder, part ...interface{}) (err error) {
|
|
||||||
for _, x := range part {
|
|
||||||
switch v := x.(type) {
|
|
||||||
case Tag:
|
|
||||||
b.SetTag(v.tag())
|
|
||||||
case Base:
|
|
||||||
b.Tag.LangID = v.langID
|
|
||||||
case Script:
|
|
||||||
b.Tag.ScriptID = v.scriptID
|
|
||||||
case Region:
|
|
||||||
b.Tag.RegionID = v.regionID
|
|
||||||
case Variant:
|
|
||||||
if v.variant == "" {
|
|
||||||
err = errInvalidArgument
|
|
||||||
break
|
|
||||||
}
|
|
||||||
b.AddVariant(v.variant)
|
|
||||||
case Extension:
|
|
||||||
if v.s == "" {
|
|
||||||
err = errInvalidArgument
|
|
||||||
break
|
|
||||||
}
|
|
||||||
b.SetExt(v.s)
|
|
||||||
case []Variant:
|
|
||||||
b.ClearVariants()
|
|
||||||
for _, v := range v {
|
|
||||||
b.AddVariant(v.variant)
|
|
||||||
}
|
|
||||||
case []Extension:
|
|
||||||
b.ClearExtensions()
|
|
||||||
for _, e := range v {
|
|
||||||
b.SetExt(e.s)
|
|
||||||
}
|
|
||||||
// TODO: support parsing of raw strings based on morphology or just extensions?
|
|
||||||
case error:
|
|
||||||
if v != nil {
|
|
||||||
err = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var errInvalidWeight = errors.New("ParseAcceptLanguage: invalid weight")
|
|
||||||
var errTagListTooLarge = errors.New("tag list exceeds max length")
|
|
||||||
|
|
||||||
// ParseAcceptLanguage parses the contents of an Accept-Language header as
|
|
||||||
// defined in http://www.ietf.org/rfc/rfc2616.txt and returns a list of Tags and
|
|
||||||
// a list of corresponding quality weights. It is more permissive than RFC 2616
|
|
||||||
// and may return non-nil slices even if the input is not valid.
|
|
||||||
// The Tags will be sorted by highest weight first and then by first occurrence.
|
|
||||||
// Tags with a weight of zero will be dropped. An error will be returned if the
|
|
||||||
// input could not be parsed.
|
|
||||||
func ParseAcceptLanguage(s string) (tag []Tag, q []float32, err error) {
|
|
||||||
defer func() {
|
|
||||||
if recover() != nil {
|
|
||||||
tag = nil
|
|
||||||
q = nil
|
|
||||||
err = language.ErrSyntax
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
if strings.Count(s, "-") > 1000 {
|
|
||||||
return nil, nil, errTagListTooLarge
|
|
||||||
}
|
|
||||||
|
|
||||||
var entry string
|
|
||||||
for s != "" {
|
|
||||||
if entry, s = split(s, ','); entry == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
entry, weight := split(entry, ';')
|
|
||||||
|
|
||||||
// Scan the language.
|
|
||||||
t, err := Parse(entry)
|
|
||||||
if err != nil {
|
|
||||||
id, ok := acceptFallback[entry]
|
|
||||||
if !ok {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
t = makeTag(language.Tag{LangID: id})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Scan the optional weight.
|
|
||||||
w := 1.0
|
|
||||||
if weight != "" {
|
|
||||||
weight = consume(weight, 'q')
|
|
||||||
weight = consume(weight, '=')
|
|
||||||
// consume returns the empty string when a token could not be
|
|
||||||
// consumed, resulting in an error for ParseFloat.
|
|
||||||
if w, err = strconv.ParseFloat(weight, 32); err != nil {
|
|
||||||
return nil, nil, errInvalidWeight
|
|
||||||
}
|
|
||||||
// Drop tags with a quality weight of 0.
|
|
||||||
if w <= 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tag = append(tag, t)
|
|
||||||
q = append(q, float32(w))
|
|
||||||
}
|
|
||||||
sort.Stable(&tagSort{tag, q})
|
|
||||||
return tag, q, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// consume removes a leading token c from s and returns the result or the empty
|
|
||||||
// string if there is no such token.
|
|
||||||
func consume(s string, c byte) string {
|
|
||||||
if s == "" || s[0] != c {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return strings.TrimSpace(s[1:])
|
|
||||||
}
|
|
||||||
|
|
||||||
func split(s string, c byte) (head, tail string) {
|
|
||||||
if i := strings.IndexByte(s, c); i >= 0 {
|
|
||||||
return strings.TrimSpace(s[:i]), strings.TrimSpace(s[i+1:])
|
|
||||||
}
|
|
||||||
return strings.TrimSpace(s), ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add hack mapping to deal with a small number of cases that occur
|
|
||||||
// in Accept-Language (with reasonable frequency).
|
|
||||||
var acceptFallback = map[string]language.Language{
|
|
||||||
"english": _en,
|
|
||||||
"deutsch": _de,
|
|
||||||
"italian": _it,
|
|
||||||
"french": _fr,
|
|
||||||
"*": _mul, // defined in the spec to match all languages.
|
|
||||||
}
|
|
||||||
|
|
||||||
type tagSort struct {
|
|
||||||
tag []Tag
|
|
||||||
q []float32
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *tagSort) Len() int {
|
|
||||||
return len(s.q)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *tagSort) Less(i, j int) bool {
|
|
||||||
return s.q[i] > s.q[j]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *tagSort) Swap(i, j int) {
|
|
||||||
s.tag[i], s.tag[j] = s.tag[j], s.tag[i]
|
|
||||||
s.q[i], s.q[j] = s.q[j], s.q[i]
|
|
||||||
}
|
|
||||||
298
vendor/golang.org/x/text/language/tables.go
generated
vendored
298
vendor/golang.org/x/text/language/tables.go
generated
vendored
@@ -1,298 +0,0 @@
|
|||||||
// Code generated by running "go generate" in golang.org/x/text. DO NOT EDIT.
|
|
||||||
|
|
||||||
package language
|
|
||||||
|
|
||||||
// CLDRVersion is the CLDR version from which the tables in this package are derived.
|
|
||||||
const CLDRVersion = "32"
|
|
||||||
|
|
||||||
const (
|
|
||||||
_de = 269
|
|
||||||
_en = 313
|
|
||||||
_fr = 350
|
|
||||||
_it = 505
|
|
||||||
_mo = 784
|
|
||||||
_no = 879
|
|
||||||
_nb = 839
|
|
||||||
_pt = 960
|
|
||||||
_sh = 1031
|
|
||||||
_mul = 806
|
|
||||||
_und = 0
|
|
||||||
)
|
|
||||||
const (
|
|
||||||
_001 = 1
|
|
||||||
_419 = 31
|
|
||||||
_BR = 65
|
|
||||||
_CA = 73
|
|
||||||
_ES = 111
|
|
||||||
_GB = 124
|
|
||||||
_MD = 189
|
|
||||||
_PT = 239
|
|
||||||
_UK = 307
|
|
||||||
_US = 310
|
|
||||||
_ZZ = 358
|
|
||||||
_XA = 324
|
|
||||||
_XC = 326
|
|
||||||
_XK = 334
|
|
||||||
)
|
|
||||||
const (
|
|
||||||
_Latn = 91
|
|
||||||
_Hani = 57
|
|
||||||
_Hans = 59
|
|
||||||
_Hant = 60
|
|
||||||
_Qaaa = 149
|
|
||||||
_Qaai = 157
|
|
||||||
_Qabx = 198
|
|
||||||
_Zinh = 255
|
|
||||||
_Zyyy = 260
|
|
||||||
_Zzzz = 261
|
|
||||||
)
|
|
||||||
|
|
||||||
var regionToGroups = []uint8{ // 359 elements
|
|
||||||
// Entry 0 - 3F
|
|
||||||
0x00, 0x00, 0x00, 0x04, 0x04, 0x00, 0x00, 0x04,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x04, 0x04, 0x04, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x04, 0x00,
|
|
||||||
0x00, 0x04, 0x00, 0x00, 0x04, 0x01, 0x00, 0x00,
|
|
||||||
0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x04, 0x04, 0x00, 0x04,
|
|
||||||
// Entry 40 - 7F
|
|
||||||
0x04, 0x04, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x04, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x04, 0x00, 0x00, 0x04, 0x00, 0x00, 0x04,
|
|
||||||
0x00, 0x00, 0x04, 0x00, 0x04, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x04, 0x00,
|
|
||||||
0x08, 0x00, 0x04, 0x00, 0x00, 0x08, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x04,
|
|
||||||
// Entry 80 - BF
|
|
||||||
0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x04, 0x00,
|
|
||||||
0x00, 0x00, 0x04, 0x01, 0x00, 0x04, 0x02, 0x00,
|
|
||||||
0x04, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00,
|
|
||||||
0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x08, 0x08, 0x00, 0x00, 0x00, 0x04,
|
|
||||||
// Entry C0 - FF
|
|
||||||
0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
|
|
||||||
0x01, 0x04, 0x08, 0x04, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x04, 0x00, 0x04, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x04, 0x00, 0x05, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
// Entry 100 - 13F
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04,
|
|
||||||
0x00, 0x00, 0x00, 0x04, 0x04, 0x00, 0x00, 0x00,
|
|
||||||
0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x08, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x05, 0x04,
|
|
||||||
0x00, 0x00, 0x04, 0x00, 0x04, 0x04, 0x05, 0x00,
|
|
||||||
// Entry 140 - 17F
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
} // Size: 383 bytes
|
|
||||||
|
|
||||||
var paradigmLocales = [][3]uint16{ // 3 elements
|
|
||||||
0: [3]uint16{0x139, 0x0, 0x7c},
|
|
||||||
1: [3]uint16{0x13e, 0x0, 0x1f},
|
|
||||||
2: [3]uint16{0x3c0, 0x41, 0xef},
|
|
||||||
} // Size: 42 bytes
|
|
||||||
|
|
||||||
type mutualIntelligibility struct {
|
|
||||||
want uint16
|
|
||||||
have uint16
|
|
||||||
distance uint8
|
|
||||||
oneway bool
|
|
||||||
}
|
|
||||||
type scriptIntelligibility struct {
|
|
||||||
wantLang uint16
|
|
||||||
haveLang uint16
|
|
||||||
wantScript uint8
|
|
||||||
haveScript uint8
|
|
||||||
distance uint8
|
|
||||||
}
|
|
||||||
type regionIntelligibility struct {
|
|
||||||
lang uint16
|
|
||||||
script uint8
|
|
||||||
group uint8
|
|
||||||
distance uint8
|
|
||||||
}
|
|
||||||
|
|
||||||
// matchLang holds pairs of langIDs of base languages that are typically
|
|
||||||
// mutually intelligible. Each pair is associated with a confidence and
|
|
||||||
// whether the intelligibility goes one or both ways.
|
|
||||||
var matchLang = []mutualIntelligibility{ // 113 elements
|
|
||||||
0: {want: 0x1d1, have: 0xb7, distance: 0x4, oneway: false},
|
|
||||||
1: {want: 0x407, have: 0xb7, distance: 0x4, oneway: false},
|
|
||||||
2: {want: 0x407, have: 0x1d1, distance: 0x4, oneway: false},
|
|
||||||
3: {want: 0x407, have: 0x432, distance: 0x4, oneway: false},
|
|
||||||
4: {want: 0x43a, have: 0x1, distance: 0x4, oneway: false},
|
|
||||||
5: {want: 0x1a3, have: 0x10d, distance: 0x4, oneway: true},
|
|
||||||
6: {want: 0x295, have: 0x10d, distance: 0x4, oneway: true},
|
|
||||||
7: {want: 0x101, have: 0x36f, distance: 0x8, oneway: false},
|
|
||||||
8: {want: 0x101, have: 0x347, distance: 0x8, oneway: false},
|
|
||||||
9: {want: 0x5, have: 0x3e2, distance: 0xa, oneway: true},
|
|
||||||
10: {want: 0xd, have: 0x139, distance: 0xa, oneway: true},
|
|
||||||
11: {want: 0x16, have: 0x367, distance: 0xa, oneway: true},
|
|
||||||
12: {want: 0x21, have: 0x139, distance: 0xa, oneway: true},
|
|
||||||
13: {want: 0x56, have: 0x13e, distance: 0xa, oneway: true},
|
|
||||||
14: {want: 0x58, have: 0x3e2, distance: 0xa, oneway: true},
|
|
||||||
15: {want: 0x71, have: 0x3e2, distance: 0xa, oneway: true},
|
|
||||||
16: {want: 0x75, have: 0x139, distance: 0xa, oneway: true},
|
|
||||||
17: {want: 0x82, have: 0x1be, distance: 0xa, oneway: true},
|
|
||||||
18: {want: 0xa5, have: 0x139, distance: 0xa, oneway: true},
|
|
||||||
19: {want: 0xb2, have: 0x15e, distance: 0xa, oneway: true},
|
|
||||||
20: {want: 0xdd, have: 0x153, distance: 0xa, oneway: true},
|
|
||||||
21: {want: 0xe5, have: 0x139, distance: 0xa, oneway: true},
|
|
||||||
22: {want: 0xe9, have: 0x3a, distance: 0xa, oneway: true},
|
|
||||||
23: {want: 0xf0, have: 0x15e, distance: 0xa, oneway: true},
|
|
||||||
24: {want: 0xf9, have: 0x15e, distance: 0xa, oneway: true},
|
|
||||||
25: {want: 0x100, have: 0x139, distance: 0xa, oneway: true},
|
|
||||||
26: {want: 0x130, have: 0x139, distance: 0xa, oneway: true},
|
|
||||||
27: {want: 0x13c, have: 0x139, distance: 0xa, oneway: true},
|
|
||||||
28: {want: 0x140, have: 0x151, distance: 0xa, oneway: true},
|
|
||||||
29: {want: 0x145, have: 0x13e, distance: 0xa, oneway: true},
|
|
||||||
30: {want: 0x158, have: 0x101, distance: 0xa, oneway: true},
|
|
||||||
31: {want: 0x16d, have: 0x367, distance: 0xa, oneway: true},
|
|
||||||
32: {want: 0x16e, have: 0x139, distance: 0xa, oneway: true},
|
|
||||||
33: {want: 0x16f, have: 0x139, distance: 0xa, oneway: true},
|
|
||||||
34: {want: 0x17e, have: 0x139, distance: 0xa, oneway: true},
|
|
||||||
35: {want: 0x190, have: 0x13e, distance: 0xa, oneway: true},
|
|
||||||
36: {want: 0x194, have: 0x13e, distance: 0xa, oneway: true},
|
|
||||||
37: {want: 0x1a4, have: 0x1be, distance: 0xa, oneway: true},
|
|
||||||
38: {want: 0x1b4, have: 0x139, distance: 0xa, oneway: true},
|
|
||||||
39: {want: 0x1b8, have: 0x139, distance: 0xa, oneway: true},
|
|
||||||
40: {want: 0x1d4, have: 0x15e, distance: 0xa, oneway: true},
|
|
||||||
41: {want: 0x1d7, have: 0x3e2, distance: 0xa, oneway: true},
|
|
||||||
42: {want: 0x1d9, have: 0x139, distance: 0xa, oneway: true},
|
|
||||||
43: {want: 0x1e7, have: 0x139, distance: 0xa, oneway: true},
|
|
||||||
44: {want: 0x1f8, have: 0x139, distance: 0xa, oneway: true},
|
|
||||||
45: {want: 0x20e, have: 0x1e1, distance: 0xa, oneway: true},
|
|
||||||
46: {want: 0x210, have: 0x139, distance: 0xa, oneway: true},
|
|
||||||
47: {want: 0x22d, have: 0x15e, distance: 0xa, oneway: true},
|
|
||||||
48: {want: 0x242, have: 0x3e2, distance: 0xa, oneway: true},
|
|
||||||
49: {want: 0x24a, have: 0x139, distance: 0xa, oneway: true},
|
|
||||||
50: {want: 0x251, have: 0x139, distance: 0xa, oneway: true},
|
|
||||||
51: {want: 0x265, have: 0x139, distance: 0xa, oneway: true},
|
|
||||||
52: {want: 0x274, have: 0x48a, distance: 0xa, oneway: true},
|
|
||||||
53: {want: 0x28a, have: 0x3e2, distance: 0xa, oneway: true},
|
|
||||||
54: {want: 0x28e, have: 0x1f9, distance: 0xa, oneway: true},
|
|
||||||
55: {want: 0x2a3, have: 0x139, distance: 0xa, oneway: true},
|
|
||||||
56: {want: 0x2b5, have: 0x15e, distance: 0xa, oneway: true},
|
|
||||||
57: {want: 0x2b8, have: 0x139, distance: 0xa, oneway: true},
|
|
||||||
58: {want: 0x2be, have: 0x139, distance: 0xa, oneway: true},
|
|
||||||
59: {want: 0x2c3, have: 0x15e, distance: 0xa, oneway: true},
|
|
||||||
60: {want: 0x2ed, have: 0x139, distance: 0xa, oneway: true},
|
|
||||||
61: {want: 0x2f1, have: 0x15e, distance: 0xa, oneway: true},
|
|
||||||
62: {want: 0x2fa, have: 0x139, distance: 0xa, oneway: true},
|
|
||||||
63: {want: 0x2ff, have: 0x7e, distance: 0xa, oneway: true},
|
|
||||||
64: {want: 0x304, have: 0x139, distance: 0xa, oneway: true},
|
|
||||||
65: {want: 0x30b, have: 0x3e2, distance: 0xa, oneway: true},
|
|
||||||
66: {want: 0x31b, have: 0x1be, distance: 0xa, oneway: true},
|
|
||||||
67: {want: 0x31f, have: 0x1e1, distance: 0xa, oneway: true},
|
|
||||||
68: {want: 0x320, have: 0x139, distance: 0xa, oneway: true},
|
|
||||||
69: {want: 0x331, have: 0x139, distance: 0xa, oneway: true},
|
|
||||||
70: {want: 0x351, have: 0x139, distance: 0xa, oneway: true},
|
|
||||||
71: {want: 0x36a, have: 0x347, distance: 0xa, oneway: false},
|
|
||||||
72: {want: 0x36a, have: 0x36f, distance: 0xa, oneway: true},
|
|
||||||
73: {want: 0x37a, have: 0x139, distance: 0xa, oneway: true},
|
|
||||||
74: {want: 0x387, have: 0x139, distance: 0xa, oneway: true},
|
|
||||||
75: {want: 0x389, have: 0x139, distance: 0xa, oneway: true},
|
|
||||||
76: {want: 0x38b, have: 0x15e, distance: 0xa, oneway: true},
|
|
||||||
77: {want: 0x390, have: 0x139, distance: 0xa, oneway: true},
|
|
||||||
78: {want: 0x395, have: 0x139, distance: 0xa, oneway: true},
|
|
||||||
79: {want: 0x39d, have: 0x139, distance: 0xa, oneway: true},
|
|
||||||
80: {want: 0x3a5, have: 0x139, distance: 0xa, oneway: true},
|
|
||||||
81: {want: 0x3be, have: 0x139, distance: 0xa, oneway: true},
|
|
||||||
82: {want: 0x3c4, have: 0x13e, distance: 0xa, oneway: true},
|
|
||||||
83: {want: 0x3d4, have: 0x10d, distance: 0xa, oneway: true},
|
|
||||||
84: {want: 0x3d9, have: 0x139, distance: 0xa, oneway: true},
|
|
||||||
85: {want: 0x3e5, have: 0x15e, distance: 0xa, oneway: true},
|
|
||||||
86: {want: 0x3e9, have: 0x1be, distance: 0xa, oneway: true},
|
|
||||||
87: {want: 0x3fa, have: 0x139, distance: 0xa, oneway: true},
|
|
||||||
88: {want: 0x40c, have: 0x139, distance: 0xa, oneway: true},
|
|
||||||
89: {want: 0x423, have: 0x139, distance: 0xa, oneway: true},
|
|
||||||
90: {want: 0x429, have: 0x139, distance: 0xa, oneway: true},
|
|
||||||
91: {want: 0x431, have: 0x139, distance: 0xa, oneway: true},
|
|
||||||
92: {want: 0x43b, have: 0x139, distance: 0xa, oneway: true},
|
|
||||||
93: {want: 0x43e, have: 0x1e1, distance: 0xa, oneway: true},
|
|
||||||
94: {want: 0x445, have: 0x139, distance: 0xa, oneway: true},
|
|
||||||
95: {want: 0x450, have: 0x139, distance: 0xa, oneway: true},
|
|
||||||
96: {want: 0x461, have: 0x139, distance: 0xa, oneway: true},
|
|
||||||
97: {want: 0x467, have: 0x3e2, distance: 0xa, oneway: true},
|
|
||||||
98: {want: 0x46f, have: 0x139, distance: 0xa, oneway: true},
|
|
||||||
99: {want: 0x476, have: 0x3e2, distance: 0xa, oneway: true},
|
|
||||||
100: {want: 0x3883, have: 0x139, distance: 0xa, oneway: true},
|
|
||||||
101: {want: 0x480, have: 0x139, distance: 0xa, oneway: true},
|
|
||||||
102: {want: 0x482, have: 0x139, distance: 0xa, oneway: true},
|
|
||||||
103: {want: 0x494, have: 0x3e2, distance: 0xa, oneway: true},
|
|
||||||
104: {want: 0x49d, have: 0x139, distance: 0xa, oneway: true},
|
|
||||||
105: {want: 0x4ac, have: 0x529, distance: 0xa, oneway: true},
|
|
||||||
106: {want: 0x4b4, have: 0x139, distance: 0xa, oneway: true},
|
|
||||||
107: {want: 0x4bc, have: 0x3e2, distance: 0xa, oneway: true},
|
|
||||||
108: {want: 0x4e5, have: 0x15e, distance: 0xa, oneway: true},
|
|
||||||
109: {want: 0x4f2, have: 0x139, distance: 0xa, oneway: true},
|
|
||||||
110: {want: 0x512, have: 0x139, distance: 0xa, oneway: true},
|
|
||||||
111: {want: 0x518, have: 0x139, distance: 0xa, oneway: true},
|
|
||||||
112: {want: 0x52f, have: 0x139, distance: 0xa, oneway: true},
|
|
||||||
} // Size: 702 bytes
|
|
||||||
|
|
||||||
// matchScript holds pairs of scriptIDs where readers of one script
|
|
||||||
// can typically also read the other. Each is associated with a confidence.
|
|
||||||
var matchScript = []scriptIntelligibility{ // 26 elements
|
|
||||||
0: {wantLang: 0x432, haveLang: 0x432, wantScript: 0x5b, haveScript: 0x20, distance: 0x5},
|
|
||||||
1: {wantLang: 0x432, haveLang: 0x432, wantScript: 0x20, haveScript: 0x5b, distance: 0x5},
|
|
||||||
2: {wantLang: 0x58, haveLang: 0x3e2, wantScript: 0x5b, haveScript: 0x20, distance: 0xa},
|
|
||||||
3: {wantLang: 0xa5, haveLang: 0x139, wantScript: 0xe, haveScript: 0x5b, distance: 0xa},
|
|
||||||
4: {wantLang: 0x1d7, haveLang: 0x3e2, wantScript: 0x8, haveScript: 0x20, distance: 0xa},
|
|
||||||
5: {wantLang: 0x210, haveLang: 0x139, wantScript: 0x2e, haveScript: 0x5b, distance: 0xa},
|
|
||||||
6: {wantLang: 0x24a, haveLang: 0x139, wantScript: 0x4f, haveScript: 0x5b, distance: 0xa},
|
|
||||||
7: {wantLang: 0x251, haveLang: 0x139, wantScript: 0x53, haveScript: 0x5b, distance: 0xa},
|
|
||||||
8: {wantLang: 0x2b8, haveLang: 0x139, wantScript: 0x58, haveScript: 0x5b, distance: 0xa},
|
|
||||||
9: {wantLang: 0x304, haveLang: 0x139, wantScript: 0x6f, haveScript: 0x5b, distance: 0xa},
|
|
||||||
10: {wantLang: 0x331, haveLang: 0x139, wantScript: 0x76, haveScript: 0x5b, distance: 0xa},
|
|
||||||
11: {wantLang: 0x351, haveLang: 0x139, wantScript: 0x22, haveScript: 0x5b, distance: 0xa},
|
|
||||||
12: {wantLang: 0x395, haveLang: 0x139, wantScript: 0x83, haveScript: 0x5b, distance: 0xa},
|
|
||||||
13: {wantLang: 0x39d, haveLang: 0x139, wantScript: 0x36, haveScript: 0x5b, distance: 0xa},
|
|
||||||
14: {wantLang: 0x3be, haveLang: 0x139, wantScript: 0x5, haveScript: 0x5b, distance: 0xa},
|
|
||||||
15: {wantLang: 0x3fa, haveLang: 0x139, wantScript: 0x5, haveScript: 0x5b, distance: 0xa},
|
|
||||||
16: {wantLang: 0x40c, haveLang: 0x139, wantScript: 0xd6, haveScript: 0x5b, distance: 0xa},
|
|
||||||
17: {wantLang: 0x450, haveLang: 0x139, wantScript: 0xe6, haveScript: 0x5b, distance: 0xa},
|
|
||||||
18: {wantLang: 0x461, haveLang: 0x139, wantScript: 0xe9, haveScript: 0x5b, distance: 0xa},
|
|
||||||
19: {wantLang: 0x46f, haveLang: 0x139, wantScript: 0x2c, haveScript: 0x5b, distance: 0xa},
|
|
||||||
20: {wantLang: 0x476, haveLang: 0x3e2, wantScript: 0x5b, haveScript: 0x20, distance: 0xa},
|
|
||||||
21: {wantLang: 0x4b4, haveLang: 0x139, wantScript: 0x5, haveScript: 0x5b, distance: 0xa},
|
|
||||||
22: {wantLang: 0x4bc, haveLang: 0x3e2, wantScript: 0x5b, haveScript: 0x20, distance: 0xa},
|
|
||||||
23: {wantLang: 0x512, haveLang: 0x139, wantScript: 0x3e, haveScript: 0x5b, distance: 0xa},
|
|
||||||
24: {wantLang: 0x529, haveLang: 0x529, wantScript: 0x3b, haveScript: 0x3c, distance: 0xf},
|
|
||||||
25: {wantLang: 0x529, haveLang: 0x529, wantScript: 0x3c, haveScript: 0x3b, distance: 0x13},
|
|
||||||
} // Size: 232 bytes
|
|
||||||
|
|
||||||
var matchRegion = []regionIntelligibility{ // 15 elements
|
|
||||||
0: {lang: 0x3a, script: 0x0, group: 0x4, distance: 0x4},
|
|
||||||
1: {lang: 0x3a, script: 0x0, group: 0x84, distance: 0x4},
|
|
||||||
2: {lang: 0x139, script: 0x0, group: 0x1, distance: 0x4},
|
|
||||||
3: {lang: 0x139, script: 0x0, group: 0x81, distance: 0x4},
|
|
||||||
4: {lang: 0x13e, script: 0x0, group: 0x3, distance: 0x4},
|
|
||||||
5: {lang: 0x13e, script: 0x0, group: 0x83, distance: 0x4},
|
|
||||||
6: {lang: 0x3c0, script: 0x0, group: 0x3, distance: 0x4},
|
|
||||||
7: {lang: 0x3c0, script: 0x0, group: 0x83, distance: 0x4},
|
|
||||||
8: {lang: 0x529, script: 0x3c, group: 0x2, distance: 0x4},
|
|
||||||
9: {lang: 0x529, script: 0x3c, group: 0x82, distance: 0x4},
|
|
||||||
10: {lang: 0x3a, script: 0x0, group: 0x80, distance: 0x5},
|
|
||||||
11: {lang: 0x139, script: 0x0, group: 0x80, distance: 0x5},
|
|
||||||
12: {lang: 0x13e, script: 0x0, group: 0x80, distance: 0x5},
|
|
||||||
13: {lang: 0x3c0, script: 0x0, group: 0x80, distance: 0x5},
|
|
||||||
14: {lang: 0x529, script: 0x3c, group: 0x80, distance: 0x5},
|
|
||||||
} // Size: 114 bytes
|
|
||||||
|
|
||||||
// Total table size 1473 bytes (1KiB); checksum: 7BB90B5C
|
|
||||||
145
vendor/golang.org/x/text/language/tags.go
generated
vendored
145
vendor/golang.org/x/text/language/tags.go
generated
vendored
@@ -1,145 +0,0 @@
|
|||||||
// Copyright 2013 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 language
|
|
||||||
|
|
||||||
import "golang.org/x/text/internal/language/compact"
|
|
||||||
|
|
||||||
// TODO: Various sets of commonly use tags and regions.
|
|
||||||
|
|
||||||
// MustParse is like Parse, but panics if the given BCP 47 tag cannot be parsed.
|
|
||||||
// It simplifies safe initialization of Tag values.
|
|
||||||
func MustParse(s string) Tag {
|
|
||||||
t, err := Parse(s)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return t
|
|
||||||
}
|
|
||||||
|
|
||||||
// MustParse is like Parse, but panics if the given BCP 47 tag cannot be parsed.
|
|
||||||
// It simplifies safe initialization of Tag values.
|
|
||||||
func (c CanonType) MustParse(s string) Tag {
|
|
||||||
t, err := c.Parse(s)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return t
|
|
||||||
}
|
|
||||||
|
|
||||||
// MustParseBase is like ParseBase, but panics if the given base cannot be parsed.
|
|
||||||
// It simplifies safe initialization of Base values.
|
|
||||||
func MustParseBase(s string) Base {
|
|
||||||
b, err := ParseBase(s)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// MustParseScript is like ParseScript, but panics if the given script cannot be
|
|
||||||
// parsed. It simplifies safe initialization of Script values.
|
|
||||||
func MustParseScript(s string) Script {
|
|
||||||
scr, err := ParseScript(s)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return scr
|
|
||||||
}
|
|
||||||
|
|
||||||
// MustParseRegion is like ParseRegion, but panics if the given region cannot be
|
|
||||||
// parsed. It simplifies safe initialization of Region values.
|
|
||||||
func MustParseRegion(s string) Region {
|
|
||||||
r, err := ParseRegion(s)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
und = Tag{}
|
|
||||||
|
|
||||||
Und Tag = Tag{}
|
|
||||||
|
|
||||||
Afrikaans Tag = Tag(compact.Afrikaans)
|
|
||||||
Amharic Tag = Tag(compact.Amharic)
|
|
||||||
Arabic Tag = Tag(compact.Arabic)
|
|
||||||
ModernStandardArabic Tag = Tag(compact.ModernStandardArabic)
|
|
||||||
Azerbaijani Tag = Tag(compact.Azerbaijani)
|
|
||||||
Bulgarian Tag = Tag(compact.Bulgarian)
|
|
||||||
Bengali Tag = Tag(compact.Bengali)
|
|
||||||
Catalan Tag = Tag(compact.Catalan)
|
|
||||||
Czech Tag = Tag(compact.Czech)
|
|
||||||
Danish Tag = Tag(compact.Danish)
|
|
||||||
German Tag = Tag(compact.German)
|
|
||||||
Greek Tag = Tag(compact.Greek)
|
|
||||||
English Tag = Tag(compact.English)
|
|
||||||
AmericanEnglish Tag = Tag(compact.AmericanEnglish)
|
|
||||||
BritishEnglish Tag = Tag(compact.BritishEnglish)
|
|
||||||
Spanish Tag = Tag(compact.Spanish)
|
|
||||||
EuropeanSpanish Tag = Tag(compact.EuropeanSpanish)
|
|
||||||
LatinAmericanSpanish Tag = Tag(compact.LatinAmericanSpanish)
|
|
||||||
Estonian Tag = Tag(compact.Estonian)
|
|
||||||
Persian Tag = Tag(compact.Persian)
|
|
||||||
Finnish Tag = Tag(compact.Finnish)
|
|
||||||
Filipino Tag = Tag(compact.Filipino)
|
|
||||||
French Tag = Tag(compact.French)
|
|
||||||
CanadianFrench Tag = Tag(compact.CanadianFrench)
|
|
||||||
Gujarati Tag = Tag(compact.Gujarati)
|
|
||||||
Hebrew Tag = Tag(compact.Hebrew)
|
|
||||||
Hindi Tag = Tag(compact.Hindi)
|
|
||||||
Croatian Tag = Tag(compact.Croatian)
|
|
||||||
Hungarian Tag = Tag(compact.Hungarian)
|
|
||||||
Armenian Tag = Tag(compact.Armenian)
|
|
||||||
Indonesian Tag = Tag(compact.Indonesian)
|
|
||||||
Icelandic Tag = Tag(compact.Icelandic)
|
|
||||||
Italian Tag = Tag(compact.Italian)
|
|
||||||
Japanese Tag = Tag(compact.Japanese)
|
|
||||||
Georgian Tag = Tag(compact.Georgian)
|
|
||||||
Kazakh Tag = Tag(compact.Kazakh)
|
|
||||||
Khmer Tag = Tag(compact.Khmer)
|
|
||||||
Kannada Tag = Tag(compact.Kannada)
|
|
||||||
Korean Tag = Tag(compact.Korean)
|
|
||||||
Kirghiz Tag = Tag(compact.Kirghiz)
|
|
||||||
Lao Tag = Tag(compact.Lao)
|
|
||||||
Lithuanian Tag = Tag(compact.Lithuanian)
|
|
||||||
Latvian Tag = Tag(compact.Latvian)
|
|
||||||
Macedonian Tag = Tag(compact.Macedonian)
|
|
||||||
Malayalam Tag = Tag(compact.Malayalam)
|
|
||||||
Mongolian Tag = Tag(compact.Mongolian)
|
|
||||||
Marathi Tag = Tag(compact.Marathi)
|
|
||||||
Malay Tag = Tag(compact.Malay)
|
|
||||||
Burmese Tag = Tag(compact.Burmese)
|
|
||||||
Nepali Tag = Tag(compact.Nepali)
|
|
||||||
Dutch Tag = Tag(compact.Dutch)
|
|
||||||
Norwegian Tag = Tag(compact.Norwegian)
|
|
||||||
Punjabi Tag = Tag(compact.Punjabi)
|
|
||||||
Polish Tag = Tag(compact.Polish)
|
|
||||||
Portuguese Tag = Tag(compact.Portuguese)
|
|
||||||
BrazilianPortuguese Tag = Tag(compact.BrazilianPortuguese)
|
|
||||||
EuropeanPortuguese Tag = Tag(compact.EuropeanPortuguese)
|
|
||||||
Romanian Tag = Tag(compact.Romanian)
|
|
||||||
Russian Tag = Tag(compact.Russian)
|
|
||||||
Sinhala Tag = Tag(compact.Sinhala)
|
|
||||||
Slovak Tag = Tag(compact.Slovak)
|
|
||||||
Slovenian Tag = Tag(compact.Slovenian)
|
|
||||||
Albanian Tag = Tag(compact.Albanian)
|
|
||||||
Serbian Tag = Tag(compact.Serbian)
|
|
||||||
SerbianLatin Tag = Tag(compact.SerbianLatin)
|
|
||||||
Swedish Tag = Tag(compact.Swedish)
|
|
||||||
Swahili Tag = Tag(compact.Swahili)
|
|
||||||
Tamil Tag = Tag(compact.Tamil)
|
|
||||||
Telugu Tag = Tag(compact.Telugu)
|
|
||||||
Thai Tag = Tag(compact.Thai)
|
|
||||||
Turkish Tag = Tag(compact.Turkish)
|
|
||||||
Ukrainian Tag = Tag(compact.Ukrainian)
|
|
||||||
Urdu Tag = Tag(compact.Urdu)
|
|
||||||
Uzbek Tag = Tag(compact.Uzbek)
|
|
||||||
Vietnamese Tag = Tag(compact.Vietnamese)
|
|
||||||
Chinese Tag = Tag(compact.Chinese)
|
|
||||||
SimplifiedChinese Tag = Tag(compact.SimplifiedChinese)
|
|
||||||
TraditionalChinese Tag = Tag(compact.TraditionalChinese)
|
|
||||||
Zulu Tag = Tag(compact.Zulu)
|
|
||||||
)
|
|
||||||
19
vendor/modules.txt
vendored
19
vendor/modules.txt
vendored
@@ -1,9 +1,3 @@
|
|||||||
# github.com/aclindsa/ofxgo v0.1.3
|
|
||||||
## explicit; go 1.9
|
|
||||||
github.com/aclindsa/ofxgo
|
|
||||||
# github.com/aclindsa/xml v0.0.0-20201125035057-bbd5c9ec99ac
|
|
||||||
## explicit; go 1.12
|
|
||||||
github.com/aclindsa/xml
|
|
||||||
# github.com/go-echarts/go-echarts/v2 v2.3.1
|
# github.com/go-echarts/go-echarts/v2 v2.3.1
|
||||||
## explicit; go 1.18
|
## explicit; go 1.18
|
||||||
github.com/go-echarts/go-echarts/v2/actions
|
github.com/go-echarts/go-echarts/v2/actions
|
||||||
@@ -13,9 +7,6 @@ github.com/go-echarts/go-echarts/v2/opts
|
|||||||
github.com/go-echarts/go-echarts/v2/render
|
github.com/go-echarts/go-echarts/v2/render
|
||||||
github.com/go-echarts/go-echarts/v2/templates
|
github.com/go-echarts/go-echarts/v2/templates
|
||||||
github.com/go-echarts/go-echarts/v2/types
|
github.com/go-echarts/go-echarts/v2/types
|
||||||
# github.com/google/uuid v1.6.0
|
|
||||||
## explicit
|
|
||||||
github.com/google/uuid
|
|
||||||
# github.com/guptarohit/asciigraph v0.7.3
|
# github.com/guptarohit/asciigraph v0.7.3
|
||||||
## explicit; go 1.11
|
## explicit; go 1.11
|
||||||
github.com/guptarohit/asciigraph
|
github.com/guptarohit/asciigraph
|
||||||
@@ -30,13 +21,3 @@ golang.org/x/sys/windows
|
|||||||
# golang.org/x/term v0.32.0
|
# golang.org/x/term v0.32.0
|
||||||
## explicit; go 1.23.0
|
## explicit; go 1.23.0
|
||||||
golang.org/x/term
|
golang.org/x/term
|
||||||
# golang.org/x/text v0.25.0
|
|
||||||
## explicit; go 1.23.0
|
|
||||||
golang.org/x/text/currency
|
|
||||||
golang.org/x/text/internal/format
|
|
||||||
golang.org/x/text/internal/language
|
|
||||||
golang.org/x/text/internal/language/compact
|
|
||||||
golang.org/x/text/internal/number
|
|
||||||
golang.org/x/text/internal/stringset
|
|
||||||
golang.org/x/text/internal/tag
|
|
||||||
golang.org/x/text/language
|
|
||||||
|
|||||||
Reference in New Issue
Block a user