Compare commits

...

6 Commits

Author SHA1 Message Date
6758df2b1d updated dependencies
All checks were successful
continuous-integration/drone/push Build is passing
2024-08-19 03:44:45 +02:00
810583964c updated dependencies
All checks were successful
continuous-integration/drone/push Build is passing
2024-02-01 19:16:57 +01:00
ed361c8e9e updated dependencies
All checks were successful
continuous-integration/drone/push Build is passing
2023-10-01 12:01:06 +02:00
d62d777f05 updated dependencies
All checks were successful
continuous-integration/drone/push Build is passing
2023-05-17 13:26:26 +02:00
e1533ab274 updated dependencies
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2023-03-17 13:16:58 +01:00
d13318e927 replaced ioutil.ReadAll by io.ReadAll
All checks were successful
continuous-integration/drone/push Build is passing
2022-11-02 17:16:09 +01:00
34 changed files with 4267 additions and 3214 deletions

10
go.mod
View File

@ -1,13 +1,13 @@
module git.paulbsd.com/paulbsd/fuelprices module git.paulbsd.com/paulbsd/fuelprices
go 1.19 go 1.23
require ( require (
github.com/antchfx/xmlquery v1.3.12 github.com/antchfx/xmlquery v1.4.1
github.com/antchfx/xpath v1.2.1 // indirect github.com/antchfx/xpath v1.3.1 // indirect
github.com/influxdata/influxdb1-client v0.0.0-20220302092344-a9ab5670611c github.com/influxdata/influxdb1-client v0.0.0-20220302092344-a9ab5670611c
golang.org/x/net v0.1.0 // indirect golang.org/x/net v0.28.0 // indirect
golang.org/x/text v0.4.0 // indirect golang.org/x/text v0.17.0 // indirect
gopkg.in/ini.v1 v1.67.0 gopkg.in/ini.v1 v1.67.0
) )

60
go.sum
View File

@ -1,7 +1,20 @@
github.com/antchfx/xmlquery v1.3.12 h1:6TMGpdjpO/P8VhjnaYPXuqT3qyJ/VsqoyNTmJzNBTQ4= github.com/antchfx/xmlquery v1.3.12 h1:6TMGpdjpO/P8VhjnaYPXuqT3qyJ/VsqoyNTmJzNBTQ4=
github.com/antchfx/xmlquery v1.3.12/go.mod h1:3w2RvQvTz+DaT5fSgsELkSJcdNgkmg6vuXDEuhdwsPQ= github.com/antchfx/xmlquery v1.3.12/go.mod h1:3w2RvQvTz+DaT5fSgsELkSJcdNgkmg6vuXDEuhdwsPQ=
github.com/antchfx/xmlquery v1.3.15 h1:aJConNMi1sMha5G8YJoAIF5P+H+qG1L73bSItWHo8Tw=
github.com/antchfx/xmlquery v1.3.15/go.mod h1:zMDv5tIGjOxY/JCNNinnle7V/EwthZ5IT8eeCGJKRWA=
github.com/antchfx/xmlquery v1.3.18 h1:FSQ3wMuphnPPGJOFhvc+cRQ2CT/rUj4cyQXkJcjOwz0=
github.com/antchfx/xmlquery v1.3.18/go.mod h1:Afkq4JIeXut75taLSuI31ISJ/zeq+3jG7TunF7noreA=
github.com/antchfx/xmlquery v1.4.1 h1:YgpSwbeWvLp557YFTi8E3z6t6/hYjmFEtiEKbDfEbl0=
github.com/antchfx/xmlquery v1.4.1/go.mod h1:lKezcT8ELGt8kW5L+ckFMTbgdR61/odpPgDv8Gvi1fI=
github.com/antchfx/xpath v1.2.1 h1:qhp4EW6aCOVr5XIkT+l6LJ9ck/JsUH/yyauNgTQkBF8= github.com/antchfx/xpath v1.2.1 h1:qhp4EW6aCOVr5XIkT+l6LJ9ck/JsUH/yyauNgTQkBF8=
github.com/antchfx/xpath v1.2.1/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs= github.com/antchfx/xpath v1.2.1/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs=
github.com/antchfx/xpath v1.2.3/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs=
github.com/antchfx/xpath v1.2.4 h1:dW1HB/JxKvGtJ9WyVGJ0sIoEcqftV3SqIstujI+B9XY=
github.com/antchfx/xpath v1.2.4/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs=
github.com/antchfx/xpath v1.2.5 h1:hqZ+wtQ+KIOV/S3bGZcIhpgYC26um2bZYP2KVGcR7VY=
github.com/antchfx/xpath v1.2.5/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs=
github.com/antchfx/xpath v1.3.1 h1:PNbFuUqHwWl0xRjvUPjJ95Agbmdj2uzzIwmQKgu4oCk=
github.com/antchfx/xpath v1.3.1/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
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=
@ -15,16 +28,63 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0= golang.org/x/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0=
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=

View File

@ -3,7 +3,7 @@ package zipfile
import ( import (
"archive/zip" "archive/zip"
"bytes" "bytes"
"io/ioutil" "io"
"log" "log"
"net/http" "net/http"
"time" "time"
@ -27,7 +27,7 @@ func (zipfile *ZipFile) DownloadFile(c *config.Config) (err error) {
} }
defer resp.Body.Close() defer resp.Body.Close()
zipfile.Content, err = ioutil.ReadAll(resp.Body) zipfile.Content, err = io.ReadAll(resp.Body)
if err != nil { if err != nil {
return return
} }
@ -50,7 +50,7 @@ func (zipfile *ZipFile) ExtractZip(c *config.Config, xmlfile *xmlfile.XMLFile) (
if err != nil { if err != nil {
return err return err
} }
xmlfile.Content, err = ioutil.ReadAll(rc) xmlfile.Content, err = io.ReadAll(rc)
rc.Close() rc.Close()
} else { } else {
log.Fatal("File not found") log.Fatal("File not found")

View File

@ -1,17 +0,0 @@
language: go
go:
- 1.9.x
- 1.12.x
- 1.13.x
- 1.14.x
- 1.15.x
install:
- go get golang.org/x/net/html/charset
- go get github.com/antchfx/xpath
- go get github.com/mattn/goveralls
- go get github.com/golang/groupcache
script:
- $HOME/gopath/bin/goveralls -service=travis-ci

View File

@ -1,12 +1,10 @@
xmlquery # xmlquery
====
[![Build Status](https://travis-ci.org/antchfx/xmlquery.svg?branch=master)](https://travis-ci.org/antchfx/xmlquery) [![Build Status](https://github.com/antchfx/xmlquery/actions/workflows/testing.yml/badge.svg)](https://github.com/antchfx/xmlquery/actions/workflows/testing.yml)
[![Coverage Status](https://coveralls.io/repos/github/antchfx/xmlquery/badge.svg?branch=master)](https://coveralls.io/github/antchfx/xmlquery?branch=master)
[![GoDoc](https://godoc.org/github.com/antchfx/xmlquery?status.svg)](https://godoc.org/github.com/antchfx/xmlquery) [![GoDoc](https://godoc.org/github.com/antchfx/xmlquery?status.svg)](https://godoc.org/github.com/antchfx/xmlquery)
[![Go Report Card](https://goreportcard.com/badge/github.com/antchfx/xmlquery)](https://goreportcard.com/report/github.com/antchfx/xmlquery) [![Go Report Card](https://goreportcard.com/badge/github.com/antchfx/xmlquery)](https://goreportcard.com/report/github.com/antchfx/xmlquery)
Overview # Overview
===
`xmlquery` is an XPath query package for XML documents, allowing you to extract `xmlquery` is an XPath query package for XML documents, allowing you to extract
data or evaluate from XML documents with an XPath expression. data or evaluate from XML documents with an XPath expression.
@ -23,15 +21,13 @@ You can visit this page to learn about the supported XPath(1.0/2.0) syntax. http
[jsonquery](https://github.com/antchfx/jsonquery) - Package for the JSON document query. [jsonquery](https://github.com/antchfx/jsonquery) - Package for the JSON document query.
Installation # Installation
====
``` ```
$ go get github.com/antchfx/xmlquery $ go get github.com/antchfx/xmlquery
``` ```
# Quick Starts
Quick Starts
===
```go ```go
import ( import (
@ -75,8 +71,7 @@ func main(){
} }
``` ```
Getting Started # Getting Started
===
### Find specified XPath query. ### Find specified XPath query.
@ -110,7 +105,7 @@ doc, err := xmlquery.Parse(f)
#### Parse an XML in a stream fashion (simple case without elements filtering). #### Parse an XML in a stream fashion (simple case without elements filtering).
```go ```go
f, err := os.Open("../books.xml") f, _ := os.Open("../books.xml")
p, err := xmlquery.CreateStreamParser(f, "/bookstore/book") p, err := xmlquery.CreateStreamParser(f, "/bookstore/book")
for { for {
n, err := p.Read() n, err := p.Read()
@ -118,15 +113,18 @@ for {
break break
} }
if err != nil { if err != nil {
... panic(err)
} }
fmt.Println(n)
} }
``` ```
Notes: `CreateStreamParser()` used for saving memory if your had a large XML file to parse.
#### Parse an XML in a stream fashion (simple case advanced element filtering). #### Parse an XML in a stream fashion (simple case advanced element filtering).
```go ```go
f, err := os.Open("../books.xml") f, _ := os.Open("../books.xml")
p, err := xmlquery.CreateStreamParser(f, "/bookstore/book", "/bookstore/book[price>=10]") p, err := xmlquery.CreateStreamParser(f, "/bookstore/book", "/bookstore/book[price>=10]")
for { for {
n, err := p.Read() n, err := p.Read()
@ -134,8 +132,9 @@ for {
break break
} }
if err != nil { if err != nil {
... panic(err)
} }
fmt.Println(n)
} }
``` ```
@ -153,10 +152,17 @@ list := xmlquery.Find(doc, "//author")
book := xmlquery.FindOne(doc, "//book[2]") book := xmlquery.FindOne(doc, "//book[2]")
``` ```
#### Find all book elements and only get `id` attribute. (New Feature) #### Find the last book.
```go
book := xmlquery.FindOne(doc, "//book[last()]")
```
#### Find all book elements and only get `id` attribute.
```go ```go
list := xmlquery.Find(doc,"//book/@id") list := xmlquery.Find(doc,"//book/@id")
fmt.Println(list[0].InnerText) // outout @id value
``` ```
#### Find all books with id `bk104`. #### Find all books with id `bk104`.
@ -179,31 +185,62 @@ price := expr.Evaluate(xmlquery.CreateXPathNavigator(doc)).(float64)
fmt.Printf("total price: %f\n", price) fmt.Printf("total price: %f\n", price)
``` ```
#### Evaluate number of all book elements. #### Count the number of books.
```go ```go
expr, err := xpath.Compile("count(//book)") expr, err := xpath.Compile("count(//book)")
count := expr.Evaluate(xmlquery.CreateXPathNavigator(doc)).(float64)
```
#### Calculate the total price of all book prices.
```go
expr, err := xpath.Compile("sum(//book/price)")
price := expr.Evaluate(xmlquery.CreateXPathNavigator(doc)).(float64) price := expr.Evaluate(xmlquery.CreateXPathNavigator(doc)).(float64)
``` ```
FAQ # Advanced Features
====
#### `Find()` vs `QueryAll()`, which is better? ### Parse `UTF-16` XML file with `ParseWithOptions()`.
`Find` and `QueryAll` both do the same thing: searches all of matched XML nodes. ```go
`Find` panics if provided with an invalid XPath query, while `QueryAll` returns f, _ := os.Open(`UTF-16.XML`)
an error. // Convert UTF-16 XML to UTF-8
utf16ToUtf8Transformer := unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM).NewDecoder()
utf8Reader := transform.NewReader(f, utf16ToUtf8Transformer)
// Sets `CharsetReader`
options := xmlquery.ParserOptions{
Decoder: &xmlquery.DecoderOptions{
CharsetReader: func(charset string, input io.Reader) (io.Reader, error) {
return input, nil
},
},
}
doc, err := xmlquery.ParseWithOptions(utf8Reader, options)
```
#### Can I save my query expression object for the next query? ### Query with custom namespace prefix.
Yes, you can. We provide `QuerySelector` and `QuerySelectorAll` methods; they ```go
accept your query expression object. s := `<?xml version="1.0" encoding="UTF-8"?>
<pd:ProcessDefinition xmlns:pd="http://xmlns.xyz.com/process/2003" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<pd:activity name="Invoke Request-Response Service">
<pd:type>RequestReplyActivity</pd:type>
<pd:resourceType>OpClientReqActivity</pd:resourceType>
<pd:x>300</pd:x>
<pd:y>80</pd:y>
</pd:activity>
</pd:ProcessDefinition>`
nsMap := map[string]string{
"q": "http://xmlns.xyz.com/process/2003",
"r": "http://www.w3.org/1999/XSL/Transform",
"s": "http://www.w3.org/2001/XMLSchema",
}
expr, _ := xpath.CompileWithNS("//q:activity", nsMap)
node := xmlquery.QuerySelector(doc, expr)
```
Caching a query expression object avoids recompiling the XPath query #### Create XML document without call `xml.Marshal`.
expression, improving query performance.
#### Create XML document.
```go ```go
doc := &xmlquery.Node{ doc := &xmlquery.Node{
@ -233,10 +270,33 @@ title_text := &xmlquery.Node{
} }
title.FirstChild = title_text title.FirstChild = title_text
channel.FirstChild = title channel.FirstChild = title
fmt.Println(doc.OutputXML(true)) fmt.Println(doc.OutputXML(true))
// <?xml version="1.0"?><rss><channel><title>W3Schools Home Page</title></channel></rss> fmt.Println(doc.OutputXMLWithOptions(WithOutputSelf()))
``` ```
Questions Output:
===
```xml
<?xml version="1.0"?><rss><channel><title>W3Schools Home Page</title></channel></rss>
```
# FAQ
#### `Find()` vs `QueryAll()`, which is better?
`Find` and `QueryAll` both do the same thing: searches all of matched XML nodes.
`Find` panics if provided with an invalid XPath query, while `QueryAll` returns
an error.
#### Can I save my query expression object for the next query?
Yes, you can. We provide `QuerySelector` and `QuerySelectorAll` methods; they
accept your query expression object.
Caching a query expression object avoids recompiling the XPath query
expression, improving query performance.
# Questions
Please let me know if you have any questions Please let me know if you have any questions

View File

@ -1,121 +0,0 @@
<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" ?>
<bookstore specialty="novel">
<book id="bk101">
<author>Gambardella, Matthew</author>
<title>XML Developer's Guide</title>
<genre>Computer</genre>
<price>44.95</price>
<publish_date>2000-10-01</publish_date>
<description>An in-depth look at creating applications
with XML.</description>
</book>
<book id="bk102">
<author>Ralls, Kim</author>
<title>Midnight Rain</title>
<genre>Fantasy</genre>
<price>5.95</price>
<publish_date>2000-12-16</publish_date>
<description>A former architect battles corporate zombies,
an evil sorceress, and her own childhood to become queen
of the world.</description>
</book>
<book id="bk103">
<author>Corets, Eva</author>
<title>Maeve Ascendant</title>
<genre>Fantasy</genre>
<price>5.95</price>
<publish_date>2000-11-17</publish_date>
<description>After the collapse of a nanotechnology
society in England, the young survivors lay the
foundation for a new society.</description>
</book>
<book id="bk104">
<author>Corets, Eva</author>
<title>Oberon's Legacy</title>
<genre>Fantasy</genre>
<price>5.95</price>
<publish_date>2001-03-10</publish_date>
<description>In post-apocalypse England, the mysterious
agent known only as Oberon helps to create a new life
for the inhabitants of London. Sequel to Maeve
Ascendant.</description>
</book>
<book id="bk105">
<author>Corets, Eva</author>
<title>The Sundered Grail</title>
<genre>Fantasy</genre>
<price>5.95</price>
<publish_date>2001-09-10</publish_date>
<description>The two daughters of Maeve, half-sisters,
battle one another for control of England. Sequel to
Oberon's Legacy.</description>
</book>
<book id="bk106">
<author>Randall, Cynthia</author>
<title>Lover Birds</title>
<genre>Romance</genre>
<price>4.95</price>
<publish_date>2000-09-02</publish_date>
<description>When Carla meets Paul at an ornithology
conference, tempers fly as feathers get ruffled.</description>
</book>
<book id="bk107">
<author>Thurman, Paula</author>
<title>Splish Splash</title>
<genre>Romance</genre>
<price>4.95</price>
<publish_date>2000-11-02</publish_date>
<description>A deep sea diver finds true love twenty
thousand leagues beneath the sea.</description>
</book>
<book id="bk108">
<author>Knorr, Stefan</author>
<title>Creepy Crawlies</title>
<genre>Horror</genre>
<price>4.95</price>
<publish_date>2000-12-06</publish_date>
<description>An anthology of horror stories about roaches,
centipedes, scorpions and other insects.</description>
</book>
<book id="bk109">
<author>Kress, Peter</author>
<title>Paradox Lost</title>
<genre>Science Fiction</genre>
<price>6.95</price>
<publish_date>2000-11-02</publish_date>
<description>After an inadvertant trip through a Heisenberg
Uncertainty Device, James Salway discovers the problems
of being quantum.</description>
</book>
<book id="bk110">
<author>O'Brien, Tim</author>
<title>Microsoft .NET: The Programming Bible</title>
<genre>Computer</genre>
<price>36.95</price>
<publish_date>2000-12-09</publish_date>
<description>Microsoft's .NET initiative is explored in
detail in this deep programmer's reference.</description>
</book>
<book id="bk111">
<author>O'Brien, Tim</author>
<title>MSXML3: A Comprehensive Guide</title>
<genre>Computer</genre>
<price>36.95</price>
<publish_date>2000-12-01</publish_date>
<description>The Microsoft MSXML3 parser is covered in
detail, with attention to XML DOM interfaces, XSLT processing,
SAX and more.</description>
</book>
<book id="bk112">
<author>Galos, Mike</author>
<title>Visual Studio 7: A Comprehensive Guide</title>
<genre>Computer</genre>
<price>49.95</price>
<publish_date>2001-04-16</publish_date>
<description>Microsoft Visual Studio 7 is explored in depth,
looking at how Visual Basic, Visual C++, C#, and ASP+ are
integrated into a comprehensive development
environment.</description>
</book>
</bookstore>

View File

@ -1,7 +1,6 @@
package xmlquery package xmlquery
import ( import (
"bytes"
"encoding/xml" "encoding/xml"
"fmt" "fmt"
"html" "html"
@ -28,6 +27,8 @@ const (
CommentNode CommentNode
// AttributeNode is an attribute of element. // AttributeNode is an attribute of element.
AttributeNode AttributeNode
// NotationNode is a directive represents in document (for example, <!text...>).
NotationNode
) )
type Attr struct { type Attr struct {
@ -50,24 +51,78 @@ type Node struct {
level int // node level in the tree level int // node level in the tree
} }
type outputConfiguration struct {
printSelf bool
preserveSpaces bool
emptyElementTagSupport bool
skipComments bool
}
type OutputOption func(*outputConfiguration)
// WithOutputSelf configures the Node to print the root node itself
func WithOutputSelf() OutputOption {
return func(oc *outputConfiguration) {
oc.printSelf = true
}
}
// WithEmptyTagSupport empty tags should be written as <empty/> and
// not as <empty></empty>
func WithEmptyTagSupport() OutputOption {
return func(oc *outputConfiguration) {
oc.emptyElementTagSupport = true
}
}
// WithoutComments will skip comments in output
func WithoutComments() OutputOption {
return func(oc *outputConfiguration) {
oc.skipComments = true
}
}
// WithPreserveSpace will preserve spaces in output
func WithPreserveSpace() OutputOption {
return func(oc *outputConfiguration) {
oc.preserveSpaces = true
}
}
func newXMLName(name string) xml.Name {
if i := strings.IndexByte(name, ':'); i > 0 {
return xml.Name{
Space: name[:i],
Local: name[i+1:],
}
}
return xml.Name{
Local: name,
}
}
func (n *Node) Level() int {
return n.level
}
// InnerText returns the text between the start and end tags of the object. // InnerText returns the text between the start and end tags of the object.
func (n *Node) InnerText() string { func (n *Node) InnerText() string {
var output func(*bytes.Buffer, *Node) var output func(*strings.Builder, *Node)
output = func(buf *bytes.Buffer, n *Node) { output = func(b *strings.Builder, n *Node) {
switch n.Type { switch n.Type {
case TextNode, CharDataNode: case TextNode, CharDataNode:
buf.WriteString(n.Data) b.WriteString(n.Data)
case CommentNode: case CommentNode:
default: default:
for child := n.FirstChild; child != nil; child = child.NextSibling { for child := n.FirstChild; child != nil; child = child.NextSibling {
output(buf, child) output(b, child)
} }
} }
} }
var buf bytes.Buffer var b strings.Builder
output(&buf, n) output(&b, n)
return buf.String() return b.String()
} }
func (n *Node) sanitizedData(preserveSpaces bool) string { func (n *Node) sanitizedData(preserveSpaces bool) string {
@ -86,90 +141,142 @@ func calculatePreserveSpaces(n *Node, pastValue bool) bool {
return pastValue return pastValue
} }
func outputXML(buf *bytes.Buffer, n *Node, preserveSpaces bool) { func outputXML(b *strings.Builder, n *Node, preserveSpaces bool, config *outputConfiguration) {
preserveSpaces = calculatePreserveSpaces(n, preserveSpaces) preserveSpaces = calculatePreserveSpaces(n, preserveSpaces)
switch n.Type { switch n.Type {
case TextNode: case TextNode:
buf.WriteString(html.EscapeString(n.sanitizedData(preserveSpaces))) b.WriteString(html.EscapeString(n.sanitizedData(preserveSpaces)))
return return
case CharDataNode: case CharDataNode:
buf.WriteString("<![CDATA[") b.WriteString("<![CDATA[")
buf.WriteString(n.Data) b.WriteString(n.Data)
buf.WriteString("]]>") b.WriteString("]]>")
return return
case CommentNode: case CommentNode:
buf.WriteString("<!--") if !config.skipComments {
buf.WriteString(n.Data) b.WriteString("<!--")
buf.WriteString("-->") b.WriteString(n.Data)
b.WriteString("-->")
}
return
case NotationNode:
fmt.Fprintf(b, "<!%s>", n.Data)
return return
case DeclarationNode: case DeclarationNode:
buf.WriteString("<?" + n.Data) b.WriteString("<?" + n.Data)
default: default:
if n.Prefix == "" { if n.Prefix == "" {
buf.WriteString("<" + n.Data) b.WriteString("<" + n.Data)
} else { } else {
buf.WriteString("<" + n.Prefix + ":" + n.Data) fmt.Fprintf(b, "<%s:%s", n.Prefix, n.Data)
} }
} }
for _, attr := range n.Attr { for _, attr := range n.Attr {
if attr.Name.Space != "" { if attr.Name.Space != "" {
buf.WriteString(fmt.Sprintf(` %s:%s=`, attr.Name.Space, attr.Name.Local)) fmt.Fprintf(b, ` %s:%s=`, attr.Name.Space, attr.Name.Local)
} else { } else {
buf.WriteString(fmt.Sprintf(` %s=`, attr.Name.Local)) fmt.Fprintf(b, ` %s=`, attr.Name.Local)
} }
buf.WriteByte('"') b.WriteByte('"')
buf.WriteString(html.EscapeString(attr.Value)) b.WriteString(html.EscapeString(attr.Value))
buf.WriteByte('"') b.WriteByte('"')
} }
if n.Type == DeclarationNode { if n.Type == DeclarationNode {
buf.WriteString("?>") b.WriteString("?>")
} else { } else {
buf.WriteString(">") if n.FirstChild != nil || !config.emptyElementTagSupport {
b.WriteString(">")
} else {
b.WriteString("/>")
return
}
} }
for child := n.FirstChild; child != nil; child = child.NextSibling { for child := n.FirstChild; child != nil; child = child.NextSibling {
outputXML(buf, child, preserveSpaces) outputXML(b, child, preserveSpaces, config)
} }
if n.Type != DeclarationNode { if n.Type != DeclarationNode {
if n.Prefix == "" { if n.Prefix == "" {
buf.WriteString(fmt.Sprintf("</%s>", n.Data)) fmt.Fprintf(b, "</%s>", n.Data)
} else { } else {
buf.WriteString(fmt.Sprintf("</%s:%s>", n.Prefix, n.Data)) fmt.Fprintf(b, "</%s:%s>", n.Prefix, n.Data)
} }
} }
} }
// OutputXML returns the text that including tags name. // OutputXML returns the text that including tags name.
func (n *Node) OutputXML(self bool) string { func (n *Node) OutputXML(self bool) string {
config := &outputConfiguration{
printSelf: true,
emptyElementTagSupport: false,
}
preserveSpaces := calculatePreserveSpaces(n, false) preserveSpaces := calculatePreserveSpaces(n, false)
var buf bytes.Buffer var b strings.Builder
if self && n.Type != DocumentNode { if self && n.Type != DocumentNode {
outputXML(&buf, n, preserveSpaces) outputXML(&b, n, preserveSpaces, config)
} else { } else {
for n := n.FirstChild; n != nil; n = n.NextSibling { for n := n.FirstChild; n != nil; n = n.NextSibling {
outputXML(&buf, n, preserveSpaces) outputXML(&b, n, preserveSpaces, config)
} }
} }
return buf.String() return b.String()
}
// OutputXMLWithOptions returns the text that including tags name.
func (n *Node) OutputXMLWithOptions(opts ...OutputOption) string {
config := &outputConfiguration{}
// Set the options
for _, opt := range opts {
opt(config)
}
pastPreserveSpaces := config.preserveSpaces
preserveSpaces := calculatePreserveSpaces(n, pastPreserveSpaces)
var b strings.Builder
if config.printSelf && n.Type != DocumentNode {
outputXML(&b, n, preserveSpaces, config)
} else {
for n := n.FirstChild; n != nil; n = n.NextSibling {
outputXML(&b, n, preserveSpaces, config)
}
}
return b.String()
} }
// AddAttr adds a new attribute specified by 'key' and 'val' to a node 'n'. // AddAttr adds a new attribute specified by 'key' and 'val' to a node 'n'.
func AddAttr(n *Node, key, val string) { func AddAttr(n *Node, key, val string) {
var attr Attr attr := Attr{
if i := strings.Index(key, ":"); i > 0 { Name: newXMLName(key),
attr = Attr{
Name: xml.Name{Space: key[:i], Local: key[i+1:]},
Value: val,
}
} else {
attr = Attr{
Name: xml.Name{Local: key},
Value: val, Value: val,
} }
n.Attr = append(n.Attr, attr)
} }
n.Attr = append(n.Attr, attr) // SetAttr allows an attribute value with the specified name to be changed.
// If the attribute did not previously exist, it will be created.
func (n *Node) SetAttr(key, value string) {
name := newXMLName(key)
for i, attr := range n.Attr {
if attr.Name == name {
n.Attr[i].Value = value
return
}
}
AddAttr(n, key, value)
}
// RemoveAttr removes the attribute with the specified name.
func (n *Node) RemoveAttr(key string) {
name := newXMLName(key)
for i, attr := range n.Attr {
if attr.Name == name {
n.Attr = append(n.Attr[:i], n.Attr[i+1:]...)
return
}
}
} }
// AddChild adds a new node 'n' to a node 'parent' as its last child. // AddChild adds a new node 'n' to a node 'parent' as its last child.

View File

@ -2,6 +2,7 @@ package xmlquery
import ( import (
"encoding/xml" "encoding/xml"
"io"
) )
type ParserOptions struct { type ParserOptions struct {
@ -21,10 +22,12 @@ type DecoderOptions struct{
Strict bool Strict bool
AutoClose []string AutoClose []string
Entity map[string]string Entity map[string]string
CharsetReader func(charset string, input io.Reader) (io.Reader, error)
} }
func (options DecoderOptions) apply(decoder *xml.Decoder) { func (options DecoderOptions) apply(decoder *xml.Decoder) {
decoder.Strict = options.Strict decoder.Strict = options.Strict
decoder.AutoClose = options.AutoClose decoder.AutoClose = options.AutoClose
decoder.Entity = options.Entity decoder.Entity = options.Entity
decoder.CharsetReader = options.CharsetReader
} }

View File

@ -3,12 +3,12 @@ package xmlquery
import ( import (
"bufio" "bufio"
"encoding/xml" "encoding/xml"
"errors"
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
"regexp" "regexp"
"strings" "strings"
"sync"
"github.com/antchfx/xpath" "github.com/antchfx/xpath"
"golang.org/x/net/html/charset" "golang.org/x/net/html/charset"
@ -53,7 +53,6 @@ func ParseWithOptions(r io.Reader, options ParserOptions) (*Node, error) {
type parser struct { type parser struct {
decoder *xml.Decoder decoder *xml.Decoder
doc *Node doc *Node
space2prefix map[string]string
level int level int
prev *Node prev *Node
streamElementXPath *xpath.Expr // Under streaming mode, this specifies the xpath to the target element node(s). streamElementXPath *xpath.Expr // Under streaming mode, this specifies the xpath to the target element node(s).
@ -61,6 +60,13 @@ type parser struct {
streamNode *Node // Need to remember the last target node So we can clean it up upon next Read() call. streamNode *Node // Need to remember the last target node So we can clean it up upon next Read() call.
streamNodePrev *Node // Need to remember target node's prev so upon target node removal, we can restore correct prev. streamNodePrev *Node // Need to remember target node's prev so upon target node removal, we can restore correct prev.
reader *cachedReader // Need to maintain a reference to the reader, so we can determine whether a node contains CDATA. reader *cachedReader // Need to maintain a reference to the reader, so we can determine whether a node contains CDATA.
once sync.Once
space2prefix map[string]*xmlnsPrefix
}
type xmlnsPrefix struct {
name string
level int
} }
func createParser(r io.Reader) *parser { func createParser(r io.Reader) *parser {
@ -68,22 +74,26 @@ func createParser(r io.Reader) *parser {
p := &parser{ p := &parser{
decoder: xml.NewDecoder(reader), decoder: xml.NewDecoder(reader),
doc: &Node{Type: DocumentNode}, doc: &Node{Type: DocumentNode},
space2prefix: make(map[string]string),
level: 0, level: 0,
reader: reader, reader: reader,
} }
// http://www.w3.org/XML/1998/namespace is bound by definition to the prefix xml. if p.decoder.CharsetReader == nil {
p.space2prefix["http://www.w3.org/XML/1998/namespace"] = "xml"
p.decoder.CharsetReader = charset.NewReaderLabel p.decoder.CharsetReader = charset.NewReaderLabel
}
p.prev = p.doc p.prev = p.doc
return p return p
} }
func (p *parser) parse() (*Node, error) { func (p *parser) parse() (*Node, error) {
var streamElementNodeCounter int p.once.Do(func() {
p.space2prefix = map[string]*xmlnsPrefix{"http://www.w3.org/XML/1998/namespace": {name: "xml", level: 0}}
})
var streamElementNodeCounter int
for { for {
p.reader.StartCaching()
tok, err := p.decoder.Token() tok, err := p.decoder.Token()
p.reader.StopCaching()
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -92,23 +102,35 @@ func (p *parser) parse() (*Node, error) {
case xml.StartElement: case xml.StartElement:
if p.level == 0 { if p.level == 0 {
// mising XML declaration // mising XML declaration
node := &Node{Type: DeclarationNode, Data: "xml", level: 1} attributes := make([]Attr, 1)
attributes[0].Name = xml.Name{Local: "version"}
attributes[0].Value = "1.0"
node := &Node{
Type: DeclarationNode,
Data: "xml",
Attr: attributes,
level: 1,
}
AddChild(p.prev, node) AddChild(p.prev, node)
p.level = 1 p.level = 1
p.prev = node p.prev = node
} }
// https://www.w3.org/TR/xml-names/#scoping-defaulting
for _, att := range tok.Attr { for _, att := range tok.Attr {
if att.Name.Local == "xmlns" { if att.Name.Local == "xmlns" {
p.space2prefix[att.Value] = "" // https://github.com/antchfx/xmlquery/issues/67
if prefix, ok := p.space2prefix[att.Value]; !ok || (ok && prefix.level >= p.level) {
p.space2prefix[att.Value] = &xmlnsPrefix{name: "", level: p.level} // reset empty if exist the default namespace
}
} else if att.Name.Space == "xmlns" { } else if att.Name.Space == "xmlns" {
p.space2prefix[att.Value] = att.Name.Local // maybe there are have duplicate NamespaceURL?
p.space2prefix[att.Value] = &xmlnsPrefix{name: att.Name.Local, level: p.level}
} }
} }
if tok.Name.Space != "" { if space := tok.Name.Space; space != "" {
if _, found := p.space2prefix[tok.Name.Space]; !found { if _, found := p.space2prefix[space]; !found && p.decoder.Strict {
return nil, errors.New("xmlquery: invalid XML document, namespace is missing") return nil, fmt.Errorf("xmlquery: invalid XML document, namespace %s is missing", space)
} }
} }
@ -116,7 +138,7 @@ func (p *parser) parse() (*Node, error) {
for i, att := range tok.Attr { for i, att := range tok.Attr {
name := att.Name name := att.Name
if prefix, ok := p.space2prefix[name.Space]; ok { if prefix, ok := p.space2prefix[name.Space]; ok {
name.Space = prefix name.Space = prefix.name
} }
attributes[i] = Attr{ attributes[i] = Attr{
Name: name, Name: name,
@ -128,7 +150,6 @@ func (p *parser) parse() (*Node, error) {
node := &Node{ node := &Node{
Type: ElementNode, Type: ElementNode,
Data: tok.Name.Local, Data: tok.Name.Local,
Prefix: p.space2prefix[tok.Name.Space],
NamespaceURI: tok.Name.Space, NamespaceURI: tok.Name.Space,
Attr: attributes, Attr: attributes,
level: p.level, level: p.level,
@ -144,6 +165,15 @@ func (p *parser) parse() (*Node, error) {
} }
AddSibling(p.prev.Parent, node) AddSibling(p.prev.Parent, node)
} }
if node.NamespaceURI != "" {
if v, ok := p.space2prefix[node.NamespaceURI]; ok {
cached := string(p.reader.Cache())
if strings.HasPrefix(cached, fmt.Sprintf("%s:%s", v.name, node.Data)) || strings.HasPrefix(cached, fmt.Sprintf("<%s:%s", v.name, node.Data)) {
node.Prefix = v.name
}
}
}
// If we're in the streaming mode, we need to remember the node if it is the target node // If we're in the streaming mode, we need to remember the node if it is the target node
// so that when we finish processing the node's EndElement, we know how/what to return to // so that when we finish processing the node's EndElement, we know how/what to return to
// caller. Also we need to remove the target node from the tree upon next Read() call so // caller. Also we need to remove the target node from the tree upon next Read() call so
@ -161,7 +191,6 @@ func (p *parser) parse() (*Node, error) {
} }
p.prev = node p.prev = node
p.level++ p.level++
p.reader.StartCaching()
case xml.EndElement: case xml.EndElement:
p.level-- p.level--
// If we're in streaming mode, and we already have a potential streaming // If we're in streaming mode, and we already have a potential streaming
@ -198,7 +227,6 @@ func (p *parser) parse() (*Node, error) {
} }
} }
case xml.CharData: case xml.CharData:
p.reader.StopCaching()
// First, normalize the cache... // First, normalize the cache...
cached := strings.ToUpper(string(p.reader.Cache())) cached := strings.ToUpper(string(p.reader.Cache()))
nodeType := TextNode nodeType := TextNode
@ -217,7 +245,6 @@ func (p *parser) parse() (*Node, error) {
} }
AddSibling(p.prev.Parent, node) AddSibling(p.prev.Parent, node)
} }
p.reader.StartCaching()
case xml.Comment: case xml.Comment:
node := &Node{Type: CommentNode, Data: string(tok), level: p.level} node := &Node{Type: CommentNode, Data: string(tok), level: p.level}
if p.level == p.prev.level { if p.level == p.prev.level {
@ -254,6 +281,17 @@ func (p *parser) parse() (*Node, error) {
} }
p.prev = node p.prev = node
case xml.Directive: case xml.Directive:
node := &Node{Type: NotationNode, Data: string(tok), level: p.level}
if p.level == p.prev.level {
AddSibling(p.prev, node)
} else if p.level > p.prev.level {
AddChild(p.prev, node)
} else if p.level < p.prev.level {
for i := p.prev.level - p.level; i > 1; i-- {
p.prev = p.prev.Parent
}
AddSibling(p.prev.Parent, node)
}
} }
} }
} }
@ -270,6 +308,7 @@ type StreamParser struct {
// scenarios. // scenarios.
// //
// Scenario 1: simple case: // Scenario 1: simple case:
//
// xml := `<AAA><BBB>b1</BBB><BBB>b2</BBB></AAA>` // xml := `<AAA><BBB>b1</BBB><BBB>b2</BBB></AAA>`
// sp, err := CreateStreamParser(strings.NewReader(xml), "/AAA/BBB") // sp, err := CreateStreamParser(strings.NewReader(xml), "/AAA/BBB")
// if err != nil { // if err != nil {
@ -282,11 +321,14 @@ type StreamParser struct {
// } // }
// fmt.Println(n.OutputXML(true)) // fmt.Println(n.OutputXML(true))
// } // }
//
// Output will be: // Output will be:
//
// <BBB>b1</BBB> // <BBB>b1</BBB>
// <BBB>b2</BBB> // <BBB>b2</BBB>
// //
// Scenario 2: advanced case: // Scenario 2: advanced case:
//
// xml := `<AAA><BBB>b1</BBB><BBB>b2</BBB></AAA>` // xml := `<AAA><BBB>b1</BBB><BBB>b2</BBB></AAA>`
// sp, err := CreateStreamParser(strings.NewReader(xml), "/AAA/BBB", "/AAA/BBB[. != 'b1']") // sp, err := CreateStreamParser(strings.NewReader(xml), "/AAA/BBB", "/AAA/BBB[. != 'b1']")
// if err != nil { // if err != nil {
@ -299,7 +341,9 @@ type StreamParser struct {
// } // }
// fmt.Println(n.OutputXML(true)) // fmt.Println(n.OutputXML(true))
// } // }
//
// Output will be: // Output will be:
//
// <BBB>b2</BBB> // <BBB>b2</BBB>
// //
// As the argument names indicate, streamElementXPath should be used for // As the argument names indicate, streamElementXPath should be used for

View File

@ -28,14 +28,9 @@ func (n *Node) SelectAttr(name string) string {
} }
return "" return ""
} }
var local, space string xmlName := newXMLName(name)
local = name
if i := strings.Index(name, ":"); i > 0 {
space = name[:i]
local = name[i+1:]
}
for _, attr := range n.Attr { for _, attr := range n.Attr {
if attr.Name.Local == local && attr.Name.Space == space { if attr.Name == xmlName {
return attr.Value return attr.Value
} }
} }
@ -161,7 +156,7 @@ func (x *NodeNavigator) NodeType() xpath.NodeType {
switch x.curr.Type { switch x.curr.Type {
case CommentNode: case CommentNode:
return xpath.CommentNode return xpath.CommentNode
case TextNode, CharDataNode: case TextNode, CharDataNode, NotationNode:
return xpath.TextNode return xpath.TextNode
case DeclarationNode, DocumentNode: case DeclarationNode, DocumentNode:
return xpath.RootNode return xpath.RootNode

View File

@ -1,12 +0,0 @@
language: go
go:
- 1.6
- 1.9
- '1.10'
install:
- go get github.com/mattn/goveralls
script:
- $HOME/gopath/bin/goveralls -service=travis-ci

View File

@ -1,14 +1,13 @@
XPath # XPath
====
[![GoDoc](https://godoc.org/github.com/antchfx/xpath?status.svg)](https://godoc.org/github.com/antchfx/xpath) [![GoDoc](https://godoc.org/github.com/antchfx/xpath?status.svg)](https://godoc.org/github.com/antchfx/xpath)
[![Coverage Status](https://coveralls.io/repos/github/antchfx/xpath/badge.svg?branch=master)](https://coveralls.io/github/antchfx/xpath?branch=master) [![Coverage Status](https://coveralls.io/repos/github/antchfx/xpath/badge.svg?branch=master)](https://coveralls.io/github/antchfx/xpath?branch=master)
[![Build Status](https://travis-ci.org/antchfx/xpath.svg?branch=master)](https://travis-ci.org/antchfx/xpath) [![Build Status](https://github.com/antchfx/xpath/actions/workflows/testing.yml/badge.svg)](https://github.com/antchfx/xpath/actions/workflows/testing.yml)
[![Go Report Card](https://goreportcard.com/badge/github.com/antchfx/xpath)](https://goreportcard.com/report/github.com/antchfx/xpath) [![Go Report Card](https://goreportcard.com/badge/github.com/antchfx/xpath)](https://goreportcard.com/report/github.com/antchfx/xpath)
XPath is Go package provides selecting nodes from XML, HTML or other documents using XPath expression. XPath is Go package provides selecting nodes from XML, HTML or other documents using XPath expression.
Implementation # Implementation
===
- [htmlquery](https://github.com/antchfx/htmlquery) - an XPath query package for HTML document - [htmlquery](https://github.com/antchfx/htmlquery) - an XPath query package for HTML document
@ -16,8 +15,7 @@ Implementation
- [jsonquery](https://github.com/antchfx/jsonquery) - an XPath query package for JSON document - [jsonquery](https://github.com/antchfx/jsonquery) - an XPath query package for JSON document
Supported Features # Supported Features
===
#### The basic XPath patterns. #### The basic XPath patterns.
@ -63,11 +61,14 @@ Supported Features
- `child::*` : The child axis selects children of the current node. - `child::*` : The child axis selects children of the current node.
- `child::node()`: Selects all the children of the context node.
- `child::text()`: Selects all text node children of the context node.
- `descendant::*` : The descendant axis selects descendants of the current node. It is equivalent to '//'. - `descendant::*` : The descendant axis selects descendants of the current node. It is equivalent to '//'.
- `descendant-or-self::*` : Selects descendants including the current node. - `descendant-or-self::*` : Selects descendants including the current node.
- `attribute::*` : Selects attributes of the current element. It is equivalent to @* - `attribute::*` : Selects attributes of the current element. It is equivalent to @\*
- `following-sibling::*` : Selects nodes after the current node. - `following-sibling::*` : Selects nodes after the current node.
@ -93,21 +94,21 @@ Supported Features
- `a = b` : Standard comparisons. - `a = b` : Standard comparisons.
* a = b True if a equals b. - `a = b` : True if a equals b.
* a != b True if a is not equal to b. - `a != b` : True if a is not equal to b.
* a < b True if a is less than b. - `a < b` : True if a is less than b.
* a <= b True if a is less than or equal to b. - `a <= b` : True if a is less than or equal to b.
* a > b True if a is greater than b. - `a > b` : True if a is greater than b.
* a >= b True if a is greater than or equal to b. - `a >= b` : True if a is greater than or equal to b.
- `a + b` : Arithmetic expressions. - `a + b` : Arithmetic expressions.
* `- a` Unary minus - `- a` Unary minus
* a + b Add - `a + b` : Addition
* a - b Substract - `a - b` : Subtraction
* a * b Multiply - `a * b` : Multiplication
* a div b Divide - `a div b` : Division
* a mod b Floating point mod, like Java. - `a mod b` : Modulus (division remainder)
- `a or b` : Boolean `or` operation. - `a or b` : Boolean `or` operation.
@ -118,45 +119,49 @@ Supported Features
- `fun(arg1, ..., argn)` : Function calls: - `fun(arg1, ..., argn)` : Function calls:
| Function | Supported | | Function | Supported |
| --- | --- | | ----------------------- | --------- |
`boolean()`| ✓ | | `boolean()` | ✓ |
`ceiling()`| ✓ | | `ceiling()` | ✓ |
`choose()`| ✗ | | `choose()` | ✗ |
`concat()`| ✓ | | `concat()` | ✓ |
`contains()`| ✓ | | `contains()` | ✓ |
`count()`| ✓ | | `count()` | ✓ |
`current()`| ✗ | | `current()` | ✗ |
`document()`| ✗ | | `document()` | ✗ |
`element-available()`| ✗ | | `element-available()` | ✗ |
`ends-with()`| ✓ | | `ends-with()` | ✓ |
`false()`| ✓ | | `false()` | ✓ |
`floor()`| ✓ | | `floor()` | ✓ |
`format-number()`| ✗ | | `format-number()` | ✗ |
`function-available()`| ✗ | | `function-available()` | ✗ |
`generate-id()`| ✗ | | `generate-id()` | ✗ |
`id()`| ✗ | | `id()` | ✗ |
`key()`| ✗ | | `key()` | ✗ |
`lang()`| ✗ | | `lang()` | ✗ |
`last()`| ✓ | | `last()` | ✓ |
`local-name()`| ✓ | | `local-name()` | ✓ |
`matches()`| ✓ | | `lower-case()`[^1] | ✓ |
`name()`| ✓ | | `matches()` | ✓ |
`namespace-uri()`| ✓ | | `name()` | ✓ |
`normalize-space()`| ✓ | | `namespace-uri()` | ✓ |
`not()`| ✓ | | `normalize-space()` | ✓ |
`number()`| ✓ | | `not()` | ✓ |
`position()`| ✓ | | `number()` | ✓ |
`replace()`| ✓ | | `position()` | ✓ |
`reverse()`| ✓ | | `replace()` | ✓ |
`round()`| ✓ | | `reverse()` | ✓ |
`starts-with()`| ✓ | | `round()` | ✓ |
`string()`| ✓ | | `starts-with()` | ✓ |
`string-length()`| ✓ | | `string()` | ✓ |
`substring()`| ✓ | | `string-join()`[^1] | ✓ |
`substring-after()`| ✓ | | `string-length()` | ✓ |
`substring-before()`| ✓ | | `substring()` | ✓ |
`sum()`| ✓ | | `substring-after()` | ✓ |
`system-property()`| ✗ | | `substring-before()` | ✓ |
`translate()`| ✓ | | `sum()` | ✓ |
`true()`| ✓ | | `system-property()` | ✗ |
`unparsed-entity-url()` | ✗ | | `translate()` | ✓ |
| `true()` | ✓ |
| `unparsed-entity-url()` | ✗ |
[^1]: XPath-2.0 expression

View File

@ -7,15 +7,39 @@ import (
type flag int type flag int
const ( var flagsEnum = struct {
noneFlag flag = iota None flag
filterFlag SmartDesc flag
) PosFilter flag
Filter flag
Condition flag
}{
None: 0,
SmartDesc: 1,
PosFilter: 2,
Filter: 4,
Condition: 8,
}
type builderProp int
var builderProps = struct {
None builderProp
PosFilter builderProp
HasPosition builderProp
HasLast builderProp
NonFlat builderProp
}{
None: 0,
PosFilter: 1,
HasPosition: 2,
HasLast: 4,
NonFlat: 8,
}
// builder provides building an XPath expressions. // builder provides building an XPath expressions.
type builder struct { type builder struct {
depth int parseDepth int
flag flag
firstInput query firstInput query
} }
@ -44,6 +68,12 @@ func axisPredicate(root *axisNode) func(NodeNavigator) bool {
predicate := func(n NodeNavigator) bool { predicate := func(n NodeNavigator) bool {
if typ == n.NodeType() || typ == allNode { if typ == n.NodeType() || typ == allNode {
if nametest { if nametest {
type namespaceURL interface {
NamespaceURL() string
}
if ns, ok := n.(namespaceURL); ok && root.hasNamespaceURI {
return root.LocalName == n.LocalName() && root.namespaceURI == ns.NamespaceURL()
}
if root.LocalName == n.LocalName() && root.Prefix == n.Prefix() { if root.LocalName == n.LocalName() && root.Prefix == n.Prefix() {
return true return true
} }
@ -57,23 +87,26 @@ func axisPredicate(root *axisNode) func(NodeNavigator) bool {
return predicate return predicate
} }
// processAxisNode processes a query for the XPath axis node. // processAxis processes a query for the XPath axis node.
func (b *builder) processAxisNode(root *axisNode) (query, error) { func (b *builder) processAxis(root *axisNode, flags flag, props *builderProp) (query, error) {
var ( var (
err error err error
qyInput query qyInput query
qyOutput query qyOutput query
predicate = axisPredicate(root)
) )
b.firstInput = nil
predicate := axisPredicate(root)
if root.Input == nil { if root.Input == nil {
qyInput = &contextQuery{} qyInput = &contextQuery{}
*props = builderProps.None
} else { } else {
inputFlags := flagsEnum.None
if root.AxeType == "child" && (root.Input.Type() == nodeAxis) { if root.AxeType == "child" && (root.Input.Type() == nodeAxis) {
if input := root.Input.(*axisNode); input.AxeType == "descendant-or-self" { if input := root.Input.(*axisNode); input.AxeType == "descendant-or-self" {
var qyGrandInput query var qyGrandInput query
if input.Input != nil { if input.Input != nil {
qyGrandInput, _ = b.processNode(input.Input) qyGrandInput, _ = b.processNode(input.Input, flagsEnum.SmartDesc, props)
} else { } else {
qyGrandInput = &contextQuery{} qyGrandInput = &contextQuery{}
} }
@ -88,11 +121,14 @@ func (b *builder) processAxisNode(root *axisNode) (query, error) {
} }
return v return v
} }
qyOutput = &descendantQuery{Input: qyGrandInput, Predicate: filter, Self: true} qyOutput = &descendantQuery{name: root.LocalName, Input: qyGrandInput, Predicate: filter, Self: false}
*props |= builderProps.NonFlat
return qyOutput, nil return qyOutput, nil
} }
} else if ((flags & flagsEnum.Filter) == 0) && (root.AxeType == "descendant" || root.AxeType == "descendant-or-self") {
inputFlags |= flagsEnum.SmartDesc
} }
qyInput, err = b.processNode(root.Input) qyInput, err = b.processNode(root.Input, inputFlags, props)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -100,11 +136,13 @@ func (b *builder) processAxisNode(root *axisNode) (query, error) {
switch root.AxeType { switch root.AxeType {
case "ancestor": case "ancestor":
qyOutput = &ancestorQuery{Input: qyInput, Predicate: predicate} qyOutput = &ancestorQuery{name: root.LocalName, Input: qyInput, Predicate: predicate}
*props |= builderProps.NonFlat
case "ancestor-or-self": case "ancestor-or-self":
qyOutput = &ancestorQuery{Input: qyInput, Predicate: predicate, Self: true} qyOutput = &ancestorQuery{name: root.LocalName, Input: qyInput, Predicate: predicate, Self: true}
*props |= builderProps.NonFlat
case "attribute": case "attribute":
qyOutput = &attributeQuery{Input: qyInput, Predicate: predicate} qyOutput = &attributeQuery{name: root.LocalName, Input: qyInput, Predicate: predicate}
case "child": case "child":
filter := func(n NodeNavigator) bool { filter := func(n NodeNavigator) bool {
v := predicate(n) v := predicate(n)
@ -118,19 +156,35 @@ func (b *builder) processAxisNode(root *axisNode) (query, error) {
} }
return v return v
} }
qyOutput = &childQuery{Input: qyInput, Predicate: filter} if (*props & builderProps.NonFlat) == 0 {
qyOutput = &childQuery{name: root.LocalName, Input: qyInput, Predicate: filter}
} else {
qyOutput = &cachedChildQuery{name: root.LocalName, Input: qyInput, Predicate: filter}
}
case "descendant": case "descendant":
qyOutput = &descendantQuery{Input: qyInput, Predicate: predicate} if (flags & flagsEnum.SmartDesc) != flagsEnum.None {
qyOutput = &descendantOverDescendantQuery{name: root.LocalName, Input: qyInput, MatchSelf: false, Predicate: predicate}
} else {
qyOutput = &descendantQuery{name: root.LocalName, Input: qyInput, Predicate: predicate}
}
*props |= builderProps.NonFlat
case "descendant-or-self": case "descendant-or-self":
qyOutput = &descendantQuery{Input: qyInput, Predicate: predicate, Self: true} if (flags & flagsEnum.SmartDesc) != flagsEnum.None {
qyOutput = &descendantOverDescendantQuery{name: root.LocalName, Input: qyInput, MatchSelf: true, Predicate: predicate}
} else {
qyOutput = &descendantQuery{name: root.LocalName, Input: qyInput, Predicate: predicate, Self: true}
}
*props |= builderProps.NonFlat
case "following": case "following":
qyOutput = &followingQuery{Input: qyInput, Predicate: predicate} qyOutput = &followingQuery{Input: qyInput, Predicate: predicate}
*props |= builderProps.NonFlat
case "following-sibling": case "following-sibling":
qyOutput = &followingQuery{Input: qyInput, Predicate: predicate, Sibling: true} qyOutput = &followingQuery{Input: qyInput, Predicate: predicate, Sibling: true}
case "parent": case "parent":
qyOutput = &parentQuery{Input: qyInput, Predicate: predicate} qyOutput = &parentQuery{Input: qyInput, Predicate: predicate}
case "preceding": case "preceding":
qyOutput = &precedingQuery{Input: qyInput, Predicate: predicate} qyOutput = &precedingQuery{Input: qyInput, Predicate: predicate}
*props |= builderProps.NonFlat
case "preceding-sibling": case "preceding-sibling":
qyOutput = &precedingQuery{Input: qyInput, Predicate: predicate, Sibling: true} qyOutput = &precedingQuery{Input: qyInput, Predicate: predicate, Sibling: true}
case "self": case "self":
@ -144,56 +198,182 @@ func (b *builder) processAxisNode(root *axisNode) (query, error) {
return qyOutput, nil return qyOutput, nil
} }
// processFilterNode builds query for the XPath filter predicate. func canBeNumber(q query) bool {
func (b *builder) processFilterNode(root *filterNode) (query, error) { if q.ValueType() != xpathResultType.Any {
b.flag |= filterFlag return q.ValueType() == xpathResultType.Number
}
return true
}
qyInput, err := b.processNode(root.Input) // processFilterNode builds query for the XPath filter predicate.
func (b *builder) processFilter(root *filterNode, flags flag, props *builderProp) (query, error) {
first := (flags & flagsEnum.Filter) == 0
qyInput, err := b.processNode(root.Input, (flags | flagsEnum.Filter), props)
if err != nil { if err != nil {
return nil, err return nil, err
} }
qyCond, err := b.processNode(root.Condition) firstInput := b.firstInput
var propsCond builderProp
cond, err := b.processNode(root.Condition, flags, &propsCond)
if err != nil { if err != nil {
return nil, err return nil, err
} }
qyOutput := &filterQuery{Input: qyInput, Predicate: qyCond}
return qyOutput, nil // Checking whether is number
if canBeNumber(cond) || ((propsCond & (builderProps.HasPosition | builderProps.HasLast)) != 0) {
propsCond |= builderProps.HasPosition
flags |= flagsEnum.PosFilter
}
if root.Input.Type() != nodeFilter {
*props &= ^builderProps.PosFilter
}
if (propsCond & builderProps.HasPosition) != 0 {
*props |= builderProps.PosFilter
}
merge := (qyInput.Properties() & queryProps.Merge) != 0
if (propsCond & builderProps.HasPosition) != builderProps.None {
if (propsCond & builderProps.HasLast) != 0 {
// https://github.com/antchfx/xpath/issues/76
// https://github.com/antchfx/xpath/issues/78
if qyFunc, ok := cond.(*functionQuery); ok {
switch qyFunc.Input.(type) {
case *filterQuery:
cond = &lastQuery{Input: qyFunc.Input}
}
}
}
}
if first && firstInput != nil {
if merge && ((*props & builderProps.PosFilter) != 0) {
qyInput = &filterQuery{Input: qyInput, Predicate: cond, NoPosition: false}
var (
rootQuery = &contextQuery{}
parent query
)
switch axisQuery := firstInput.(type) {
case *ancestorQuery:
if _, ok := axisQuery.Input.(*contextQuery); !ok {
parent = axisQuery.Input
axisQuery.Input = rootQuery
}
case *attributeQuery:
if _, ok := axisQuery.Input.(*contextQuery); !ok {
parent = axisQuery.Input
axisQuery.Input = rootQuery
}
case *childQuery:
if _, ok := axisQuery.Input.(*contextQuery); !ok {
parent = axisQuery.Input
axisQuery.Input = rootQuery
}
case *cachedChildQuery:
if _, ok := axisQuery.Input.(*contextQuery); !ok {
parent = axisQuery.Input
axisQuery.Input = rootQuery
}
case *descendantQuery:
if _, ok := axisQuery.Input.(*contextQuery); !ok {
parent = axisQuery.Input
axisQuery.Input = rootQuery
}
case *followingQuery:
if _, ok := axisQuery.Input.(*contextQuery); !ok {
parent = axisQuery.Input
axisQuery.Input = rootQuery
}
case *precedingQuery:
if _, ok := axisQuery.Input.(*contextQuery); !ok {
parent = axisQuery.Input
axisQuery.Input = rootQuery
}
case *parentQuery:
if _, ok := axisQuery.Input.(*contextQuery); !ok {
parent = axisQuery.Input
axisQuery.Input = rootQuery
}
case *selfQuery:
if _, ok := axisQuery.Input.(*contextQuery); !ok {
parent = axisQuery.Input
axisQuery.Input = rootQuery
}
case *groupQuery:
if _, ok := axisQuery.Input.(*contextQuery); !ok {
parent = axisQuery.Input
axisQuery.Input = rootQuery
}
case *descendantOverDescendantQuery:
if _, ok := axisQuery.Input.(*contextQuery); !ok {
parent = axisQuery.Input
axisQuery.Input = rootQuery
}
}
b.firstInput = nil
if parent != nil {
return &mergeQuery{Input: parent, Child: qyInput}, nil
}
return qyInput, nil
}
b.firstInput = nil
}
resultQuery := &filterQuery{
Input: qyInput,
Predicate: cond,
NoPosition: (propsCond & builderProps.HasPosition) == 0,
}
return resultQuery, nil
} }
// processFunctionNode processes query for the XPath function node. // processFunctionNode processes query for the XPath function node.
func (b *builder) processFunctionNode(root *functionNode) (query, error) { func (b *builder) processFunction(root *functionNode, props *builderProp) (query, error) {
// Reset builder props
*props = builderProps.None
var qyOutput query var qyOutput query
switch root.FuncName { switch root.FuncName {
case "lower-case":
arg, err := b.processNode(root.Args[0], flagsEnum.None, props)
if err != nil {
return nil, err
}
qyOutput = &functionQuery{Input: arg, Func: lowerCaseFunc}
case "starts-with": case "starts-with":
arg1, err := b.processNode(root.Args[0]) arg1, err := b.processNode(root.Args[0], flagsEnum.None, props)
if err != nil { if err != nil {
return nil, err return nil, err
} }
arg2, err := b.processNode(root.Args[1]) arg2, err := b.processNode(root.Args[1], flagsEnum.None, props)
if err != nil { if err != nil {
return nil, err return nil, err
} }
qyOutput = &functionQuery{Input: b.firstInput, Func: startwithFunc(arg1, arg2)} qyOutput = &functionQuery{Func: startwithFunc(arg1, arg2)}
case "ends-with": case "ends-with":
arg1, err := b.processNode(root.Args[0]) arg1, err := b.processNode(root.Args[0], flagsEnum.None, props)
if err != nil { if err != nil {
return nil, err return nil, err
} }
arg2, err := b.processNode(root.Args[1]) arg2, err := b.processNode(root.Args[1], flagsEnum.None, props)
if err != nil { if err != nil {
return nil, err return nil, err
} }
qyOutput = &functionQuery{Input: b.firstInput, Func: endwithFunc(arg1, arg2)} qyOutput = &functionQuery{Func: endwithFunc(arg1, arg2)}
case "contains": case "contains":
arg1, err := b.processNode(root.Args[0]) arg1, err := b.processNode(root.Args[0], flagsEnum.None, props)
if err != nil { if err != nil {
return nil, err return nil, err
} }
arg2, err := b.processNode(root.Args[1]) arg2, err := b.processNode(root.Args[1], flagsEnum.None, props)
if err != nil { if err != nil {
return nil, err return nil, err
} }
qyOutput = &functionQuery{Input: b.firstInput, Func: containsFunc(arg1, arg2)} qyOutput = &functionQuery{Func: containsFunc(arg1, arg2)}
case "matches": case "matches":
//matches(string , pattern) //matches(string , pattern)
if len(root.Args) != 2 { if len(root.Args) != 2 {
@ -203,13 +383,19 @@ func (b *builder) processFunctionNode(root *functionNode) (query, error) {
arg1, arg2 query arg1, arg2 query
err error err error
) )
if arg1, err = b.processNode(root.Args[0]); err != nil { if arg1, err = b.processNode(root.Args[0], flagsEnum.None, props); err != nil {
return nil, err return nil, err
} }
if arg2, err = b.processNode(root.Args[1]); err != nil { if arg2, err = b.processNode(root.Args[1], flagsEnum.None, props); err != nil {
return nil, err return nil, err
} }
qyOutput = &functionQuery{Input: b.firstInput, Func: matchesFunc(arg1, arg2)} // Issue #92, testing the regular expression before.
if q, ok := arg2.(*constantQuery); ok {
if _, err = getRegexp(q.Val.(string)); err != nil {
return nil, fmt.Errorf("matches() got error. %v", err)
}
}
qyOutput = &functionQuery{Func: matchesFunc(arg1, arg2)}
case "substring": case "substring":
//substring( string , start [, length] ) //substring( string , start [, length] )
if len(root.Args) < 2 { if len(root.Args) < 2 {
@ -219,18 +405,18 @@ func (b *builder) processFunctionNode(root *functionNode) (query, error) {
arg1, arg2, arg3 query arg1, arg2, arg3 query
err error err error
) )
if arg1, err = b.processNode(root.Args[0]); err != nil { if arg1, err = b.processNode(root.Args[0], flagsEnum.None, props); err != nil {
return nil, err return nil, err
} }
if arg2, err = b.processNode(root.Args[1]); err != nil { if arg2, err = b.processNode(root.Args[1], flagsEnum.None, props); err != nil {
return nil, err return nil, err
} }
if len(root.Args) == 3 { if len(root.Args) == 3 {
if arg3, err = b.processNode(root.Args[2]); err != nil { if arg3, err = b.processNode(root.Args[2], flagsEnum.None, props); err != nil {
return nil, err return nil, err
} }
} }
qyOutput = &functionQuery{Input: b.firstInput, Func: substringFunc(arg1, arg2, arg3)} qyOutput = &functionQuery{Func: substringFunc(arg1, arg2, arg3)}
case "substring-before", "substring-after": case "substring-before", "substring-after":
//substring-xxxx( haystack, needle ) //substring-xxxx( haystack, needle )
if len(root.Args) != 2 { if len(root.Args) != 2 {
@ -240,14 +426,13 @@ func (b *builder) processFunctionNode(root *functionNode) (query, error) {
arg1, arg2 query arg1, arg2 query
err error err error
) )
if arg1, err = b.processNode(root.Args[0]); err != nil { if arg1, err = b.processNode(root.Args[0], flagsEnum.None, props); err != nil {
return nil, err return nil, err
} }
if arg2, err = b.processNode(root.Args[1]); err != nil { if arg2, err = b.processNode(root.Args[1], flagsEnum.None, props); err != nil {
return nil, err return nil, err
} }
qyOutput = &functionQuery{ qyOutput = &functionQuery{
Input: b.firstInput,
Func: substringIndFunc(arg1, arg2, root.FuncName == "substring-after"), Func: substringIndFunc(arg1, arg2, root.FuncName == "substring-after"),
} }
case "string-length": case "string-length":
@ -255,16 +440,16 @@ func (b *builder) processFunctionNode(root *functionNode) (query, error) {
if len(root.Args) < 1 { if len(root.Args) < 1 {
return nil, errors.New("xpath: string-length function must have at least one parameter") return nil, errors.New("xpath: string-length function must have at least one parameter")
} }
arg1, err := b.processNode(root.Args[0]) arg1, err := b.processNode(root.Args[0], flagsEnum.None, props)
if err != nil { if err != nil {
return nil, err return nil, err
} }
qyOutput = &functionQuery{Input: b.firstInput, Func: stringLengthFunc(arg1)} qyOutput = &functionQuery{Func: stringLengthFunc(arg1)}
case "normalize-space": case "normalize-space":
if len(root.Args) == 0 { if len(root.Args) == 0 {
return nil, errors.New("xpath: normalize-space function must have at least one parameter") return nil, errors.New("xpath: normalize-space function must have at least one parameter")
} }
argQuery, err := b.processNode(root.Args[0]) argQuery, err := b.processNode(root.Args[0], flagsEnum.None, props)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -278,16 +463,16 @@ func (b *builder) processFunctionNode(root *functionNode) (query, error) {
arg1, arg2, arg3 query arg1, arg2, arg3 query
err error err error
) )
if arg1, err = b.processNode(root.Args[0]); err != nil { if arg1, err = b.processNode(root.Args[0], flagsEnum.None, props); err != nil {
return nil, err return nil, err
} }
if arg2, err = b.processNode(root.Args[1]); err != nil { if arg2, err = b.processNode(root.Args[1], flagsEnum.None, props); err != nil {
return nil, err return nil, err
} }
if arg3, err = b.processNode(root.Args[2]); err != nil { if arg3, err = b.processNode(root.Args[2], flagsEnum.None, props); err != nil {
return nil, err return nil, err
} }
qyOutput = &functionQuery{Input: b.firstInput, Func: replaceFunc(arg1, arg2, arg3)} qyOutput = &functionQuery{Func: replaceFunc(arg1, arg2, arg3)}
case "translate": case "translate":
//translate( string , string, string ) //translate( string , string, string )
if len(root.Args) != 3 { if len(root.Args) != 3 {
@ -297,21 +482,21 @@ func (b *builder) processFunctionNode(root *functionNode) (query, error) {
arg1, arg2, arg3 query arg1, arg2, arg3 query
err error err error
) )
if arg1, err = b.processNode(root.Args[0]); err != nil { if arg1, err = b.processNode(root.Args[0], flagsEnum.None, props); err != nil {
return nil, err return nil, err
} }
if arg2, err = b.processNode(root.Args[1]); err != nil { if arg2, err = b.processNode(root.Args[1], flagsEnum.None, props); err != nil {
return nil, err return nil, err
} }
if arg3, err = b.processNode(root.Args[2]); err != nil { if arg3, err = b.processNode(root.Args[2], flagsEnum.None, props); err != nil {
return nil, err return nil, err
} }
qyOutput = &functionQuery{Input: b.firstInput, Func: translateFunc(arg1, arg2, arg3)} qyOutput = &functionQuery{Func: translateFunc(arg1, arg2, arg3)}
case "not": case "not":
if len(root.Args) == 0 { if len(root.Args) == 0 {
return nil, errors.New("xpath: not function must have at least one parameter") return nil, errors.New("xpath: not function must have at least one parameter")
} }
argQuery, err := b.processNode(root.Args[0]) argQuery, err := b.processNode(root.Args[0], flagsEnum.None, props)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -325,38 +510,46 @@ func (b *builder) processFunctionNode(root *functionNode) (query, error) {
err error err error
) )
if len(root.Args) == 1 { if len(root.Args) == 1 {
arg, err = b.processNode(root.Args[0]) arg, err = b.processNode(root.Args[0], flagsEnum.None, props)
if err != nil { if err != nil {
return nil, err return nil, err
} }
} }
switch root.FuncName { switch root.FuncName {
case "name": case "name":
qyOutput = &functionQuery{Input: b.firstInput, Func: nameFunc(arg)} qyOutput = &functionQuery{Func: nameFunc(arg)}
case "local-name": case "local-name":
qyOutput = &functionQuery{Input: b.firstInput, Func: localNameFunc(arg)} qyOutput = &functionQuery{Func: localNameFunc(arg)}
case "namespace-uri": case "namespace-uri":
qyOutput = &functionQuery{Input: b.firstInput, Func: namespaceFunc(arg)} qyOutput = &functionQuery{Func: namespaceFunc(arg)}
} }
case "true", "false": case "true", "false":
val := root.FuncName == "true" val := root.FuncName == "true"
qyOutput = &functionQuery{ qyOutput = &functionQuery{
Input: b.firstInput,
Func: func(_ query, _ iterator) interface{} { Func: func(_ query, _ iterator) interface{} {
return val return val
}, },
} }
case "last": case "last":
qyOutput = &functionQuery{Input: b.firstInput, Func: lastFunc} //switch typ := b.firstInput.(type) {
//case *groupQuery, *filterQuery:
// https://github.com/antchfx/xpath/issues/76
// https://github.com/antchfx/xpath/issues/78
//qyOutput = &lastQuery{Input: typ}
//default:
qyOutput = &functionQuery{Func: lastFunc}
//}
*props |= builderProps.HasLast
case "position": case "position":
qyOutput = &functionQuery{Input: b.firstInput, Func: positionFunc} qyOutput = &functionQuery{Func: positionFunc}
*props |= builderProps.HasPosition
case "boolean", "number", "string": case "boolean", "number", "string":
inp := b.firstInput var inp query
if len(root.Args) > 1 { if len(root.Args) > 1 {
return nil, fmt.Errorf("xpath: %s function must have at most one parameter", root.FuncName) return nil, fmt.Errorf("xpath: %s function must have at most one parameter", root.FuncName)
} }
if len(root.Args) == 1 { if len(root.Args) == 1 {
argQuery, err := b.processNode(root.Args[0]) argQuery, err := b.processNode(root.Args[0], flagsEnum.None, props)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -373,13 +566,10 @@ func (b *builder) processFunctionNode(root *functionNode) (query, error) {
} }
qyOutput = f qyOutput = f
case "count": case "count":
//if b.firstInput == nil {
// return nil, errors.New("xpath: expression must evaluate to node-set")
//}
if len(root.Args) == 0 { if len(root.Args) == 0 {
return nil, fmt.Errorf("xpath: count(node-sets) function must with have parameters node-sets") return nil, fmt.Errorf("xpath: count(node-sets) function must with have parameters node-sets")
} }
argQuery, err := b.processNode(root.Args[0]) argQuery, err := b.processNode(root.Args[0], flagsEnum.None, props)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -388,7 +578,7 @@ func (b *builder) processFunctionNode(root *functionNode) (query, error) {
if len(root.Args) == 0 { if len(root.Args) == 0 {
return nil, fmt.Errorf("xpath: sum(node-sets) function must with have parameters node-sets") return nil, fmt.Errorf("xpath: sum(node-sets) function must with have parameters node-sets")
} }
argQuery, err := b.processNode(root.Args[0]) argQuery, err := b.processNode(root.Args[0], flagsEnum.None, props)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -397,7 +587,7 @@ func (b *builder) processFunctionNode(root *functionNode) (query, error) {
if len(root.Args) == 0 { if len(root.Args) == 0 {
return nil, fmt.Errorf("xpath: ceiling(node-sets) function must with have parameters node-sets") return nil, fmt.Errorf("xpath: ceiling(node-sets) function must with have parameters node-sets")
} }
argQuery, err := b.processNode(root.Args[0]) argQuery, err := b.processNode(root.Args[0], flagsEnum.None, props)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -417,41 +607,65 @@ func (b *builder) processFunctionNode(root *functionNode) (query, error) {
} }
var args []query var args []query
for _, v := range root.Args { for _, v := range root.Args {
q, err := b.processNode(v) q, err := b.processNode(v, flagsEnum.None, props)
if err != nil { if err != nil {
return nil, err return nil, err
} }
args = append(args, q) args = append(args, q)
} }
qyOutput = &functionQuery{Input: b.firstInput, Func: concatFunc(args...)} qyOutput = &functionQuery{Func: concatFunc(args...)}
case "reverse": case "reverse":
if len(root.Args) == 0 { if len(root.Args) == 0 {
return nil, fmt.Errorf("xpath: reverse(node-sets) function must with have parameters node-sets") return nil, fmt.Errorf("xpath: reverse(node-sets) function must with have parameters node-sets")
} }
argQuery, err := b.processNode(root.Args[0]) argQuery, err := b.processNode(root.Args[0], flagsEnum.None, props)
if err != nil { if err != nil {
return nil, err return nil, err
} }
qyOutput = &transformFunctionQuery{Input: argQuery, Func: reverseFunc} qyOutput = &transformFunctionQuery{Input: argQuery, Func: reverseFunc}
case "string-join":
if len(root.Args) != 2 {
return nil, fmt.Errorf("xpath: string-join(node-sets, separator) function requires node-set and argument")
}
argQuery, err := b.processNode(root.Args[0], flagsEnum.None, props)
if err != nil {
return nil, err
}
arg1, err := b.processNode(root.Args[1], flagsEnum.None, props)
if err != nil {
return nil, err
}
qyOutput = &functionQuery{Input: argQuery, Func: stringJoinFunc(arg1)}
default: default:
return nil, fmt.Errorf("not yet support this function %s()", root.FuncName) return nil, fmt.Errorf("not yet support this function %s()", root.FuncName)
} }
if funcQuery, ok := qyOutput.(*functionQuery); ok && funcQuery.Input == nil {
funcQuery.Input = b.firstInput
}
return qyOutput, nil return qyOutput, nil
} }
func (b *builder) processOperatorNode(root *operatorNode) (query, error) { func (b *builder) processOperator(root *operatorNode, props *builderProp) (query, error) {
left, err := b.processNode(root.Left) var (
leftProp builderProp
rightProp builderProp
)
left, err := b.processNode(root.Left, flagsEnum.None, &leftProp)
if err != nil { if err != nil {
return nil, err return nil, err
} }
right, err := b.processNode(root.Right) right, err := b.processNode(root.Right, flagsEnum.None, &rightProp)
if err != nil { if err != nil {
return nil, err return nil, err
} }
*props = leftProp | rightProp
var qyOutput query var qyOutput query
switch root.Op { switch root.Op {
case "+", "-", "*", "div", "mod": // Numeric operator case "+", "-", "*", "div", "mod": // Numeric operator
var exprFunc func(interface{}, interface{}) interface{} var exprFunc func(iterator, interface{}, interface{}) interface{}
switch root.Op { switch root.Op {
case "+": case "+":
exprFunc = plusFunc exprFunc = plusFunc
@ -489,44 +703,50 @@ func (b *builder) processOperatorNode(root *operatorNode) (query, error) {
} }
qyOutput = &booleanQuery{Left: left, Right: right, IsOr: isOr} qyOutput = &booleanQuery{Left: left, Right: right, IsOr: isOr}
case "|": case "|":
*props |= builderProps.NonFlat
qyOutput = &unionQuery{Left: left, Right: right} qyOutput = &unionQuery{Left: left, Right: right}
} }
return qyOutput, nil return qyOutput, nil
} }
func (b *builder) processNode(root node) (q query, err error) { func (b *builder) processNode(root node, flags flag, props *builderProp) (q query, err error) {
if b.depth = b.depth + 1; b.depth > 1024 { if b.parseDepth = b.parseDepth + 1; b.parseDepth > 1024 {
err = errors.New("the xpath expressions is too complex") err = errors.New("the xpath expressions is too complex")
return return
} }
*props = builderProps.None
switch root.Type() { switch root.Type() {
case nodeConstantOperand: case nodeConstantOperand:
n := root.(*operandNode) n := root.(*operandNode)
q = &constantQuery{Val: n.Val} q = &constantQuery{Val: n.Val}
case nodeRoot: case nodeRoot:
q = &contextQuery{Root: true} q = &absoluteQuery{}
case nodeAxis: case nodeAxis:
q, err = b.processAxisNode(root.(*axisNode)) q, err = b.processAxis(root.(*axisNode), flags, props)
b.firstInput = q b.firstInput = q
case nodeFilter: case nodeFilter:
q, err = b.processFilterNode(root.(*filterNode)) q, err = b.processFilter(root.(*filterNode), flags, props)
b.firstInput = q
case nodeFunction: case nodeFunction:
q, err = b.processFunctionNode(root.(*functionNode)) q, err = b.processFunction(root.(*functionNode), props)
case nodeOperator: case nodeOperator:
q, err = b.processOperatorNode(root.(*operatorNode)) q, err = b.processOperator(root.(*operatorNode), props)
case nodeGroup: case nodeGroup:
q, err = b.processNode(root.(*groupNode).Input) q, err = b.processNode(root.(*groupNode).Input, flagsEnum.None, props)
if err != nil { if err != nil {
return return
} }
q = &groupQuery{Input: q} q = &groupQuery{Input: q}
if b.firstInput == nil {
b.firstInput = q
} }
}
b.parseDepth--
return return
} }
// build builds a specified XPath expressions expr. // build builds a specified XPath expressions expr.
func build(expr string) (q query, err error) { func build(expr string, namespaces map[string]string) (q query, err error) {
defer func() { defer func() {
if e := recover(); e != nil { if e := recover(); e != nil {
switch x := e.(type) { switch x := e.(type) {
@ -539,7 +759,8 @@ func build(expr string) (q query, err error) {
} }
} }
}() }()
root := parse(expr) root := parse(expr, namespaces)
b := &builder{} b := &builder{}
return b.processNode(root) props := builderProps.None
return b.processNode(root, flagsEnum.None, &props)
} }

View File

@ -113,7 +113,7 @@ func asNumber(t iterator, o interface{}) float64 {
case query: case query:
node := typ.Select(t) node := typ.Select(t)
if node == nil { if node == nil {
return float64(0) return math.NaN()
} }
if v, err := strconv.ParseFloat(node.Value(), 64); err == nil { if v, err := strconv.ParseFloat(node.Value(), 64); err == nil {
return v return v
@ -614,3 +614,40 @@ func reverseFunc(q query, t iterator) func() NodeNavigator {
return node return node
} }
} }
// string-join is a XPath Node Set functions string-join(node-set, separator).
func stringJoinFunc(arg1 query) func(query, iterator) interface{} {
return func(q query, t iterator) interface{} {
var separator string
switch v := functionArgs(arg1).Evaluate(t).(type) {
case string:
separator = v
case query:
node := v.Select(t)
if node != nil {
separator = node.Value()
}
}
q = functionArgs(q)
test := predicate(q)
var parts []string
switch v := q.Evaluate(t).(type) {
case string:
return v
case query:
for node := v.Select(t); node != nil; node = v.Select(t) {
if test(node) {
parts = append(parts, node.Value())
}
}
}
return strings.Join(parts, separator)
}
}
// lower-case is XPATH function that converts a string to lower case.
func lowerCaseFunc(q query, t iterator) interface{} {
v := functionArgs(q).Evaluate(t)
return strings.ToLower(asString(t, v))
}

View File

@ -1,40 +1,11 @@
package xpath package xpath
import ( import (
"fmt"
"reflect"
"strconv" "strconv"
) )
// The XPath number operator function list. // The XPath number operator function list.
// valueType is a return value type.
type valueType int
const (
booleanType valueType = iota
numberType
stringType
nodeSetType
)
func getValueType(i interface{}) valueType {
v := reflect.ValueOf(i)
switch v.Kind() {
case reflect.Float64:
return numberType
case reflect.String:
return stringType
case reflect.Bool:
return booleanType
default:
if _, ok := i.(query); ok {
return nodeSetType
}
}
panic(fmt.Errorf("xpath unknown value type: %v", v.Kind()))
}
type logical func(iterator, string, interface{}, interface{}) bool type logical func(iterator, string, interface{}, interface{}) bool
var logicalFuncs = [][]logical{ var logicalFuncs = [][]logical{
@ -228,91 +199,90 @@ func cmpBooleanBoolean(t iterator, op string, m, n interface{}) bool {
// eqFunc is an `=` operator. // eqFunc is an `=` operator.
func eqFunc(t iterator, m, n interface{}) interface{} { func eqFunc(t iterator, m, n interface{}) interface{} {
t1 := getValueType(m) t1 := getXPathType(m)
t2 := getValueType(n) t2 := getXPathType(n)
return logicalFuncs[t1][t2](t, "=", m, n) return logicalFuncs[t1][t2](t, "=", m, n)
} }
// gtFunc is an `>` operator. // gtFunc is an `>` operator.
func gtFunc(t iterator, m, n interface{}) interface{} { func gtFunc(t iterator, m, n interface{}) interface{} {
t1 := getValueType(m) t1 := getXPathType(m)
t2 := getValueType(n) t2 := getXPathType(n)
return logicalFuncs[t1][t2](t, ">", m, n) return logicalFuncs[t1][t2](t, ">", m, n)
} }
// geFunc is an `>=` operator. // geFunc is an `>=` operator.
func geFunc(t iterator, m, n interface{}) interface{} { func geFunc(t iterator, m, n interface{}) interface{} {
t1 := getValueType(m) t1 := getXPathType(m)
t2 := getValueType(n) t2 := getXPathType(n)
return logicalFuncs[t1][t2](t, ">=", m, n) return logicalFuncs[t1][t2](t, ">=", m, n)
} }
// ltFunc is an `<` operator. // ltFunc is an `<` operator.
func ltFunc(t iterator, m, n interface{}) interface{} { func ltFunc(t iterator, m, n interface{}) interface{} {
t1 := getValueType(m) t1 := getXPathType(m)
t2 := getValueType(n) t2 := getXPathType(n)
return logicalFuncs[t1][t2](t, "<", m, n) return logicalFuncs[t1][t2](t, "<", m, n)
} }
// leFunc is an `<=` operator. // leFunc is an `<=` operator.
func leFunc(t iterator, m, n interface{}) interface{} { func leFunc(t iterator, m, n interface{}) interface{} {
t1 := getValueType(m) t1 := getXPathType(m)
t2 := getValueType(n) t2 := getXPathType(n)
return logicalFuncs[t1][t2](t, "<=", m, n) return logicalFuncs[t1][t2](t, "<=", m, n)
} }
// neFunc is an `!=` operator. // neFunc is an `!=` operator.
func neFunc(t iterator, m, n interface{}) interface{} { func neFunc(t iterator, m, n interface{}) interface{} {
t1 := getValueType(m) t1 := getXPathType(m)
t2 := getValueType(n) t2 := getXPathType(n)
return logicalFuncs[t1][t2](t, "!=", m, n) return logicalFuncs[t1][t2](t, "!=", m, n)
} }
// orFunc is an `or` operator. // orFunc is an `or` operator.
var orFunc = func(t iterator, m, n interface{}) interface{} { var orFunc = func(t iterator, m, n interface{}) interface{} {
t1 := getValueType(m) t1 := getXPathType(m)
t2 := getValueType(n) t2 := getXPathType(n)
return logicalFuncs[t1][t2](t, "or", m, n) return logicalFuncs[t1][t2](t, "or", m, n)
} }
func numericExpr(m, n interface{}, cb func(float64, float64) float64) float64 { func numericExpr(t iterator, m, n interface{}, cb func(float64, float64) float64) float64 {
typ := reflect.TypeOf(float64(0)) a := asNumber(t, m)
a := reflect.ValueOf(m).Convert(typ) b := asNumber(t, n)
b := reflect.ValueOf(n).Convert(typ) return cb(a, b)
return cb(a.Float(), b.Float())
} }
// plusFunc is an `+` operator. // plusFunc is an `+` operator.
var plusFunc = func(m, n interface{}) interface{} { var plusFunc = func(t iterator, m, n interface{}) interface{} {
return numericExpr(m, n, func(a, b float64) float64 { return numericExpr(t, m, n, func(a, b float64) float64 {
return a + b return a + b
}) })
} }
// minusFunc is an `-` operator. // minusFunc is an `-` operator.
var minusFunc = func(m, n interface{}) interface{} { var minusFunc = func(t iterator, m, n interface{}) interface{} {
return numericExpr(m, n, func(a, b float64) float64 { return numericExpr(t, m, n, func(a, b float64) float64 {
return a - b return a - b
}) })
} }
// mulFunc is an `*` operator. // mulFunc is an `*` operator.
var mulFunc = func(m, n interface{}) interface{} { var mulFunc = func(t iterator, m, n interface{}) interface{} {
return numericExpr(m, n, func(a, b float64) float64 { return numericExpr(t, m, n, func(a, b float64) float64 {
return a * b return a * b
}) })
} }
// divFunc is an `DIV` operator. // divFunc is an `DIV` operator.
var divFunc = func(m, n interface{}) interface{} { var divFunc = func(t iterator, m, n interface{}) interface{} {
return numericExpr(m, n, func(a, b float64) float64 { return numericExpr(t, m, n, func(a, b float64) float64 {
return a / b return a / b
}) })
} }
// modFunc is an 'MOD' operator. // modFunc is an 'MOD' operator.
var modFunc = func(m, n interface{}) interface{} { var modFunc = func(t iterator, m, n interface{}) interface{} {
return numericExpr(m, n, func(a, b float64) float64 { return numericExpr(t, m, n, func(a, b float64) float64 {
return float64(int(a) % int(b)) return float64(int(a) % int(b))
}) })
} }

View File

@ -71,6 +71,7 @@ const (
type parser struct { type parser struct {
r *scanner r *scanner
d int d int
namespaces map[string]string
} }
// newOperatorNode returns new operator node OperatorNode. // newOperatorNode returns new operator node OperatorNode.
@ -84,8 +85,8 @@ func newOperandNode(v interface{}) node {
} }
// newAxisNode returns new axis node AxisNode. // newAxisNode returns new axis node AxisNode.
func newAxisNode(axeTyp, localName, prefix, prop string, n node) node { func newAxisNode(axeTyp, localName, prefix, prop string, n node, opts ...func(p *axisNode)) node {
return &axisNode{ a := axisNode{
nodeType: nodeAxis, nodeType: nodeAxis,
LocalName: localName, LocalName: localName,
Prefix: prefix, Prefix: prefix,
@ -93,6 +94,10 @@ func newAxisNode(axeTyp, localName, prefix, prop string, n node) node {
Prop: prop, Prop: prop,
Input: n, Input: n,
} }
for _, o := range opts {
o(&a)
}
return &a
} }
// newVariableNode returns new variable node VariableNode. // newVariableNode returns new variable node VariableNode.
@ -469,7 +474,16 @@ func (p *parser) parseNodeTest(n node, axeTyp string) (opnd node) {
if p.r.name == "*" { if p.r.name == "*" {
name = "" name = ""
} }
opnd = newAxisNode(axeTyp, name, prefix, "", n) opnd = newAxisNode(axeTyp, name, prefix, "", n, func(a *axisNode) {
if prefix != "" && p.namespaces != nil {
if ns, ok := p.namespaces[prefix]; ok {
a.hasNamespaceURI = true
a.namespaceURI = ns
} else {
panic(fmt.Sprintf("prefix %s not defined.", prefix))
}
}
})
} }
case itemStar: case itemStar:
opnd = newAxisNode(axeTyp, "", "", "", n) opnd = newAxisNode(axeTyp, "", "", "", n)
@ -531,11 +545,11 @@ func (p *parser) parseMethod(n node) node {
} }
// Parse parsing the XPath express string expr and returns a tree node. // Parse parsing the XPath express string expr and returns a tree node.
func parse(expr string) node { func parse(expr string, namespaces map[string]string) node {
r := &scanner{text: expr} r := &scanner{text: expr}
r.nextChar() r.nextChar()
r.nextItem() r.nextItem()
p := &parser{r: r} p := &parser{r: r, namespaces: namespaces}
return p.parseExpression(nil) return p.parseExpression(nil)
} }
@ -568,6 +582,8 @@ type axisNode struct {
AxeType string // name of the axes.[attribute|ancestor|child|....] AxeType string // name of the axes.[attribute|ancestor|child|....]
LocalName string // local part name of node. LocalName string // local part name of node.
Prefix string // prefix name of node. Prefix string // prefix name of node.
namespaceURI string // namespace URI of node
hasNamespaceURI bool // if namespace URI is set (can be "")
} }
func (a *axisNode) String() string { func (a *axisNode) String() string {

View File

@ -7,6 +7,44 @@ import (
"reflect" "reflect"
) )
// The return type of the XPath expression.
type resultType int
var xpathResultType = struct {
Boolean resultType
// A numeric value
Number resultType
String resultType
// A node collection.
NodeSet resultType
// Any of the XPath node types.
Any resultType
}{
Boolean: 0,
Number: 1,
String: 2,
NodeSet: 3,
Any: 4,
}
type queryProp int
var queryProps = struct {
None queryProp
Position queryProp
Count queryProp
Cached queryProp
Reverse queryProp
Merge queryProp
}{
None: 0,
Position: 1,
Count: 2,
Cached: 4,
Reverse: 8,
Merge: 16,
}
type iterator interface { type iterator interface {
Current() NodeNavigator Current() NodeNavigator
} }
@ -20,12 +58,15 @@ type query interface {
Evaluate(iterator) interface{} Evaluate(iterator) interface{}
Clone() query Clone() query
// ValueType returns the value type of the current query.
ValueType() resultType
Properties() queryProp
} }
// nopQuery is an empty query that always return nil for any query. // nopQuery is an empty query that always return nil for any query.
type nopQuery struct { type nopQuery struct{}
query
}
func (nopQuery) Select(iterator) NodeNavigator { return nil } func (nopQuery) Select(iterator) NodeNavigator { return nil }
@ -33,21 +74,23 @@ func (nopQuery) Evaluate(iterator) interface{} { return nil }
func (nopQuery) Clone() query { return nopQuery{} } func (nopQuery) Clone() query { return nopQuery{} }
func (nopQuery) ValueType() resultType { return xpathResultType.NodeSet }
func (nopQuery) Properties() queryProp {
return queryProps.Merge | queryProps.Position | queryProps.Count | queryProps.Cached
}
// contextQuery is returns current node on the iterator object query. // contextQuery is returns current node on the iterator object query.
type contextQuery struct { type contextQuery struct {
count int count int
Root bool // Moving to root-level node in the current context iterator.
} }
func (c *contextQuery) Select(t iterator) (n NodeNavigator) { func (c *contextQuery) Select(t iterator) NodeNavigator {
if c.count == 0 { if c.count > 0 {
return nil
}
c.count++ c.count++
n = t.Current().Copy() return t.Current().Copy()
if c.Root {
n.MoveToRoot()
}
}
return n
} }
func (c *contextQuery) Evaluate(iterator) interface{} { func (c *contextQuery) Evaluate(iterator) interface{} {
@ -56,12 +99,53 @@ func (c *contextQuery) Evaluate(iterator) interface{} {
} }
func (c *contextQuery) Clone() query { func (c *contextQuery) Clone() query {
return &contextQuery{count: 0, Root: c.Root} return &contextQuery{}
}
func (c *contextQuery) ValueType() resultType {
return xpathResultType.NodeSet
}
func (c *contextQuery) Properties() queryProp {
return queryProps.Merge | queryProps.Position | queryProps.Count | queryProps.Cached
}
type absoluteQuery struct {
count int
}
func (a *absoluteQuery) Select(t iterator) (n NodeNavigator) {
if a.count > 0 {
return
}
a.count++
n = t.Current().Copy()
n.MoveToRoot()
return
}
func (a *absoluteQuery) Evaluate(t iterator) interface{} {
a.count = 0
return a
}
func (a *absoluteQuery) Clone() query {
return &absoluteQuery{}
}
func (a *absoluteQuery) ValueType() resultType {
return xpathResultType.NodeSet
}
func (a *absoluteQuery) Properties() queryProp {
return queryProps.Merge | queryProps.Position | queryProps.Count | queryProps.Cached
} }
// ancestorQuery is an XPath ancestor node query.(ancestor::*|ancestor-self::*) // ancestorQuery is an XPath ancestor node query.(ancestor::*|ancestor-self::*)
type ancestorQuery struct { type ancestorQuery struct {
name string
iterator func() NodeNavigator iterator func() NodeNavigator
table map[uint64]bool
Self bool Self bool
Input query Input query
@ -69,6 +153,10 @@ type ancestorQuery struct {
} }
func (a *ancestorQuery) Select(t iterator) NodeNavigator { func (a *ancestorQuery) Select(t iterator) NodeNavigator {
if a.table == nil {
a.table = make(map[uint64]bool)
}
for { for {
if a.iterator == nil { if a.iterator == nil {
node := a.Input.Select(t) node := a.Input.Select(t)
@ -78,25 +166,28 @@ func (a *ancestorQuery) Select(t iterator) NodeNavigator {
first := true first := true
node = node.Copy() node = node.Copy()
a.iterator = func() NodeNavigator { a.iterator = func() NodeNavigator {
if first && a.Self { if first {
first = false first = false
if a.Predicate(node) { if a.Self && a.Predicate(node) {
return node return node
} }
} }
for node.MoveToParent() { for node.MoveToParent() {
if !a.Predicate(node) { if a.Predicate(node) {
continue
}
return node return node
} }
}
return nil return nil
} }
} }
if node := a.iterator(); node != nil { for node := a.iterator(); node != nil; node = a.iterator() {
node_id := getHashCode(node.Copy())
if _, ok := a.table[node_id]; !ok {
a.table[node_id] = true
return node return node
} }
}
a.iterator = nil a.iterator = nil
} }
} }
@ -112,11 +203,20 @@ func (a *ancestorQuery) Test(n NodeNavigator) bool {
} }
func (a *ancestorQuery) Clone() query { func (a *ancestorQuery) Clone() query {
return &ancestorQuery{Self: a.Self, Input: a.Input.Clone(), Predicate: a.Predicate} return &ancestorQuery{name: a.name, Self: a.Self, Input: a.Input.Clone(), Predicate: a.Predicate}
}
func (a *ancestorQuery) ValueType() resultType {
return xpathResultType.NodeSet
}
func (a *ancestorQuery) Properties() queryProp {
return queryProps.Position | queryProps.Count | queryProps.Cached | queryProps.Merge | queryProps.Reverse
} }
// attributeQuery is an XPath attribute node query.(@*) // attributeQuery is an XPath attribute node query.(@*)
type attributeQuery struct { type attributeQuery struct {
name string
iterator func() NodeNavigator iterator func() NodeNavigator
Input query Input query
@ -162,11 +262,20 @@ func (a *attributeQuery) Test(n NodeNavigator) bool {
} }
func (a *attributeQuery) Clone() query { func (a *attributeQuery) Clone() query {
return &attributeQuery{Input: a.Input.Clone(), Predicate: a.Predicate} return &attributeQuery{name: a.name, Input: a.Input.Clone(), Predicate: a.Predicate}
}
func (a *attributeQuery) ValueType() resultType {
return xpathResultType.NodeSet
}
func (a *attributeQuery) Properties() queryProp {
return queryProps.Merge
} }
// childQuery is an XPath child node query.(child::*) // childQuery is an XPath child node query.(child::*)
type childQuery struct { type childQuery struct {
name string
posit int posit int
iterator func() NodeNavigator iterator func() NodeNavigator
@ -216,7 +325,15 @@ func (c *childQuery) Test(n NodeNavigator) bool {
} }
func (c *childQuery) Clone() query { func (c *childQuery) Clone() query {
return &childQuery{Input: c.Input.Clone(), Predicate: c.Predicate} return &childQuery{name: c.name, Input: c.Input.Clone(), Predicate: c.Predicate}
}
func (c *childQuery) ValueType() resultType {
return xpathResultType.NodeSet
}
func (c *childQuery) Properties() queryProp {
return queryProps.Merge
} }
// position returns a position of current NodeNavigator. // position returns a position of current NodeNavigator.
@ -224,8 +341,75 @@ func (c *childQuery) position() int {
return c.posit return c.posit
} }
type cachedChildQuery struct {
name string
posit int
iterator func() NodeNavigator
Input query
Predicate func(NodeNavigator) bool
}
func (c *cachedChildQuery) Select(t iterator) NodeNavigator {
for {
if c.iterator == nil {
c.posit = 0
node := c.Input.Select(t)
if node == nil {
return nil
}
node = node.Copy()
first := true
c.iterator = func() NodeNavigator {
for {
if (first && !node.MoveToChild()) || (!first && !node.MoveToNext()) {
return nil
}
first = false
if c.Predicate(node) {
return node
}
}
}
}
if node := c.iterator(); node != nil {
c.posit++
return node
}
c.iterator = nil
}
}
func (c *cachedChildQuery) Evaluate(t iterator) interface{} {
c.Input.Evaluate(t)
c.iterator = nil
return c
}
func (c *cachedChildQuery) position() int {
return c.posit
}
func (c *cachedChildQuery) Test(n NodeNavigator) bool {
return c.Predicate(n)
}
func (c *cachedChildQuery) Clone() query {
return &childQuery{name: c.name, Input: c.Input.Clone(), Predicate: c.Predicate}
}
func (c *cachedChildQuery) ValueType() resultType {
return xpathResultType.NodeSet
}
func (c *cachedChildQuery) Properties() queryProp {
return queryProps.Merge
}
// descendantQuery is an XPath descendant node query.(descendant::* | descendant-or-self::*) // descendantQuery is an XPath descendant node query.(descendant::* | descendant-or-self::*)
type descendantQuery struct { type descendantQuery struct {
name string
iterator func() NodeNavigator iterator func() NodeNavigator
posit int posit int
level int level int
@ -245,14 +429,11 @@ func (d *descendantQuery) Select(t iterator) NodeNavigator {
} }
node = node.Copy() node = node.Copy()
d.level = 0 d.level = 0
positmap := make(map[int]int)
first := true first := true
d.iterator = func() NodeNavigator { d.iterator = func() NodeNavigator {
if first && d.Self { if first {
first = false first = false
if d.Predicate(node) { if d.Self && d.Predicate(node) {
d.posit = 1
positmap[d.level] = 1
return node return node
} }
} }
@ -260,7 +441,6 @@ func (d *descendantQuery) Select(t iterator) NodeNavigator {
for { for {
if node.MoveToChild() { if node.MoveToChild() {
d.level = d.level + 1 d.level = d.level + 1
positmap[d.level] = 0
} else { } else {
for { for {
if d.level == 0 { if d.level == 0 {
@ -274,8 +454,6 @@ func (d *descendantQuery) Select(t iterator) NodeNavigator {
} }
} }
if d.Predicate(node) { if d.Predicate(node) {
positmap[d.level]++
d.posit = positmap[d.level]
return node return node
} }
} }
@ -283,6 +461,7 @@ func (d *descendantQuery) Select(t iterator) NodeNavigator {
} }
if node := d.iterator(); node != nil { if node := d.iterator(); node != nil {
d.posit++
return node return node
} }
d.iterator = nil d.iterator = nil
@ -309,7 +488,15 @@ func (d *descendantQuery) depth() int {
} }
func (d *descendantQuery) Clone() query { func (d *descendantQuery) Clone() query {
return &descendantQuery{Self: d.Self, Input: d.Input.Clone(), Predicate: d.Predicate} return &descendantQuery{name: d.name, Self: d.Self, Input: d.Input.Clone(), Predicate: d.Predicate}
}
func (d *descendantQuery) ValueType() resultType {
return xpathResultType.NodeSet
}
func (d *descendantQuery) Properties() queryProp {
return queryProps.Merge
} }
// followingQuery is an XPath following node query.(following::*|following-sibling::*) // followingQuery is an XPath following node query.(following::*|following-sibling::*)
@ -390,6 +577,14 @@ func (f *followingQuery) Clone() query {
return &followingQuery{Input: f.Input.Clone(), Sibling: f.Sibling, Predicate: f.Predicate} return &followingQuery{Input: f.Input.Clone(), Sibling: f.Sibling, Predicate: f.Predicate}
} }
func (f *followingQuery) ValueType() resultType {
return xpathResultType.NodeSet
}
func (f *followingQuery) Properties() queryProp {
return queryProps.Merge
}
func (f *followingQuery) position() int { func (f *followingQuery) position() int {
return f.posit return f.posit
} }
@ -471,6 +666,14 @@ func (p *precedingQuery) Clone() query {
return &precedingQuery{Input: p.Input.Clone(), Sibling: p.Sibling, Predicate: p.Predicate} return &precedingQuery{Input: p.Input.Clone(), Sibling: p.Sibling, Predicate: p.Predicate}
} }
func (p *precedingQuery) ValueType() resultType {
return xpathResultType.NodeSet
}
func (p *precedingQuery) Properties() queryProp {
return queryProps.Merge | queryProps.Reverse
}
func (p *precedingQuery) position() int { func (p *precedingQuery) position() int {
return p.posit return p.posit
} }
@ -503,6 +706,14 @@ func (p *parentQuery) Clone() query {
return &parentQuery{Input: p.Input.Clone(), Predicate: p.Predicate} return &parentQuery{Input: p.Input.Clone(), Predicate: p.Predicate}
} }
func (p *parentQuery) ValueType() resultType {
return xpathResultType.NodeSet
}
func (p *parentQuery) Properties() queryProp {
return queryProps.Position | queryProps.Count | queryProps.Cached | queryProps.Merge
}
func (p *parentQuery) Test(n NodeNavigator) bool { func (p *parentQuery) Test(n NodeNavigator) bool {
return p.Predicate(n) return p.Predicate(n)
} }
@ -539,10 +750,20 @@ func (s *selfQuery) Clone() query {
return &selfQuery{Input: s.Input.Clone(), Predicate: s.Predicate} return &selfQuery{Input: s.Input.Clone(), Predicate: s.Predicate}
} }
func (s *selfQuery) ValueType() resultType {
return xpathResultType.NodeSet
}
func (s *selfQuery) Properties() queryProp {
return queryProps.Merge
}
// filterQuery is an XPath query for predicate filter. // filterQuery is an XPath query for predicate filter.
type filterQuery struct { type filterQuery struct {
Input query Input query
Predicate query Predicate query
NoPosition bool
posit int posit int
positmap map[int]int positmap map[int]int
} }
@ -558,8 +779,8 @@ func (f *filterQuery) do(t iterator) bool {
pt := getNodePosition(f.Input) pt := getNodePosition(f.Input)
return int(val.Float()) == pt return int(val.Float()) == pt
default: default:
if q, ok := f.Predicate.(query); ok { if f.Predicate != nil {
return q.Select(t) != nil return f.Predicate.Select(t) != nil
} }
} }
return false return false
@ -577,7 +798,7 @@ func (f *filterQuery) Select(t iterator) NodeNavigator {
node := f.Input.Select(t) node := f.Input.Select(t)
if node == nil { if node == nil {
return node return nil
} }
node = node.Copy() node = node.Copy()
@ -602,6 +823,14 @@ func (f *filterQuery) Clone() query {
return &filterQuery{Input: f.Input.Clone(), Predicate: f.Predicate.Clone()} return &filterQuery{Input: f.Input.Clone(), Predicate: f.Predicate.Clone()}
} }
func (f *filterQuery) ValueType() resultType {
return xpathResultType.NodeSet
}
func (f *filterQuery) Properties() queryProp {
return (queryProps.Position | f.Input.Properties()) & (queryProps.Reverse | queryProps.Merge)
}
// functionQuery is an XPath function that returns a computed value for // functionQuery is an XPath function that returns a computed value for
// the Evaluate call of the current NodeNavigator node. Select call isn't // the Evaluate call of the current NodeNavigator node. Select call isn't
// applicable for functionQuery. // applicable for functionQuery.
@ -624,6 +853,14 @@ func (f *functionQuery) Clone() query {
return &functionQuery{Input: f.Input.Clone(), Func: f.Func} return &functionQuery{Input: f.Input.Clone(), Func: f.Func}
} }
func (f *functionQuery) ValueType() resultType {
return xpathResultType.Any
}
func (f *functionQuery) Properties() queryProp {
return queryProps.Merge
}
// transformFunctionQuery diffs from functionQuery where the latter computes a scalar // transformFunctionQuery diffs from functionQuery where the latter computes a scalar
// value (number,string,boolean) for the current NodeNavigator node while the former // value (number,string,boolean) for the current NodeNavigator node while the former
// (transformFunctionQuery) performs a mapping or transform of the current NodeNavigator // (transformFunctionQuery) performs a mapping or transform of the current NodeNavigator
@ -652,6 +889,14 @@ func (f *transformFunctionQuery) Clone() query {
return &transformFunctionQuery{Input: f.Input.Clone(), Func: f.Func} return &transformFunctionQuery{Input: f.Input.Clone(), Func: f.Func}
} }
func (f *transformFunctionQuery) ValueType() resultType {
return xpathResultType.Any
}
func (f *transformFunctionQuery) Properties() queryProp {
return queryProps.Merge
}
// constantQuery is an XPath constant operand. // constantQuery is an XPath constant operand.
type constantQuery struct { type constantQuery struct {
Val interface{} Val interface{}
@ -669,6 +914,14 @@ func (c *constantQuery) Clone() query {
return c return c
} }
func (c *constantQuery) ValueType() resultType {
return getXPathType(c.Val)
}
func (c *constantQuery) Properties() queryProp {
return queryProps.Position | queryProps.Count | queryProps.Cached | queryProps.Merge
}
type groupQuery struct { type groupQuery struct {
posit int posit int
@ -676,14 +929,12 @@ type groupQuery struct {
} }
func (g *groupQuery) Select(t iterator) NodeNavigator { func (g *groupQuery) Select(t iterator) NodeNavigator {
for {
node := g.Input.Select(t) node := g.Input.Select(t)
if node == nil { if node == nil {
return nil return nil
} }
g.posit++ g.posit++
return node.Copy() return node
}
} }
func (g *groupQuery) Evaluate(t iterator) interface{} { func (g *groupQuery) Evaluate(t iterator) interface{} {
@ -691,7 +942,15 @@ func (g *groupQuery) Evaluate(t iterator) interface{} {
} }
func (g *groupQuery) Clone() query { func (g *groupQuery) Clone() query {
return &groupQuery{Input: g.Input} return &groupQuery{Input: g.Input.Clone()}
}
func (g *groupQuery) ValueType() resultType {
return g.Input.ValueType()
}
func (g *groupQuery) Properties() queryProp {
return queryProps.Position
} }
func (g *groupQuery) position() int { func (g *groupQuery) position() int {
@ -728,11 +987,19 @@ func (l *logicalQuery) Clone() query {
return &logicalQuery{Left: l.Left.Clone(), Right: l.Right.Clone(), Do: l.Do} return &logicalQuery{Left: l.Left.Clone(), Right: l.Right.Clone(), Do: l.Do}
} }
func (l *logicalQuery) ValueType() resultType {
return xpathResultType.Boolean
}
func (l *logicalQuery) Properties() queryProp {
return queryProps.Merge
}
// numericQuery is an XPath numeric operator expression. // numericQuery is an XPath numeric operator expression.
type numericQuery struct { type numericQuery struct {
Left, Right query Left, Right query
Do func(interface{}, interface{}) interface{} Do func(iterator, interface{}, interface{}) interface{}
} }
func (n *numericQuery) Select(t iterator) NodeNavigator { func (n *numericQuery) Select(t iterator) NodeNavigator {
@ -742,13 +1009,21 @@ func (n *numericQuery) Select(t iterator) NodeNavigator {
func (n *numericQuery) Evaluate(t iterator) interface{} { func (n *numericQuery) Evaluate(t iterator) interface{} {
m := n.Left.Evaluate(t) m := n.Left.Evaluate(t)
k := n.Right.Evaluate(t) k := n.Right.Evaluate(t)
return n.Do(m, k) return n.Do(t, m, k)
} }
func (n *numericQuery) Clone() query { func (n *numericQuery) Clone() query {
return &numericQuery{Left: n.Left.Clone(), Right: n.Right.Clone(), Do: n.Do} return &numericQuery{Left: n.Left.Clone(), Right: n.Right.Clone(), Do: n.Do}
} }
func (n *numericQuery) ValueType() resultType {
return xpathResultType.Number
}
func (n *numericQuery) Properties() queryProp {
return queryProps.Merge
}
type booleanQuery struct { type booleanQuery struct {
IsOr bool IsOr bool
Left, Right query Left, Right query
@ -839,6 +1114,14 @@ func (b *booleanQuery) Clone() query {
return &booleanQuery{IsOr: b.IsOr, Left: b.Left.Clone(), Right: b.Right.Clone()} return &booleanQuery{IsOr: b.IsOr, Left: b.Left.Clone(), Right: b.Right.Clone()}
} }
func (b *booleanQuery) ValueType() resultType {
return xpathResultType.Boolean
}
func (b *booleanQuery) Properties() queryProp {
return queryProps.Merge
}
type unionQuery struct { type unionQuery struct {
Left, Right query Left, Right query
iterator func() NodeNavigator iterator func() NodeNavigator
@ -896,6 +1179,184 @@ func (u *unionQuery) Clone() query {
return &unionQuery{Left: u.Left.Clone(), Right: u.Right.Clone()} return &unionQuery{Left: u.Left.Clone(), Right: u.Right.Clone()}
} }
func (u *unionQuery) ValueType() resultType {
return xpathResultType.NodeSet
}
func (u *unionQuery) Properties() queryProp {
return queryProps.Merge
}
type lastQuery struct {
buffer []NodeNavigator
counted bool
Input query
}
func (q *lastQuery) Select(t iterator) NodeNavigator {
return nil
}
func (q *lastQuery) Evaluate(t iterator) interface{} {
if !q.counted {
for {
node := q.Input.Select(t)
if node == nil {
break
}
q.buffer = append(q.buffer, node.Copy())
}
q.counted = true
}
return float64(len(q.buffer))
}
func (q *lastQuery) Clone() query {
return &lastQuery{Input: q.Input.Clone()}
}
func (q *lastQuery) ValueType() resultType {
return xpathResultType.Number
}
func (q *lastQuery) Properties() queryProp {
return queryProps.Merge
}
type descendantOverDescendantQuery struct {
name string
level int
posit int
currentNode NodeNavigator
Input query
MatchSelf bool
Predicate func(NodeNavigator) bool
}
func (d *descendantOverDescendantQuery) moveToFirstChild() bool {
if d.currentNode.MoveToChild() {
d.level++
return true
}
return false
}
func (d *descendantOverDescendantQuery) moveUpUntilNext() bool {
for !d.currentNode.MoveToNext() {
d.level--
if d.level == 0 {
return false
}
d.currentNode.MoveToParent()
}
return true
}
func (d *descendantOverDescendantQuery) Select(t iterator) NodeNavigator {
for {
if d.level == 0 {
node := d.Input.Select(t)
if node == nil {
return nil
}
d.currentNode = node.Copy()
d.posit = 0
if d.MatchSelf && d.Predicate(d.currentNode) {
d.posit = 1
return d.currentNode
}
d.moveToFirstChild()
} else if !d.moveUpUntilNext() {
continue
}
for ok := true; ok; ok = d.moveToFirstChild() {
if d.Predicate(d.currentNode) {
d.posit++
return d.currentNode
}
}
}
}
func (d *descendantOverDescendantQuery) Evaluate(t iterator) interface{} {
d.Input.Evaluate(t)
return d
}
func (d *descendantOverDescendantQuery) Clone() query {
return &descendantOverDescendantQuery{Input: d.Input.Clone(), Predicate: d.Predicate, MatchSelf: d.MatchSelf}
}
func (d *descendantOverDescendantQuery) ValueType() resultType {
return xpathResultType.NodeSet
}
func (d *descendantOverDescendantQuery) Properties() queryProp {
return queryProps.Merge
}
func (d *descendantOverDescendantQuery) position() int {
return d.posit
}
type mergeQuery struct {
Input query
Child query
iterator func() NodeNavigator
}
func (m *mergeQuery) Select(t iterator) NodeNavigator {
for {
if m.iterator == nil {
root := m.Input.Select(t)
if root == nil {
return nil
}
m.Child.Evaluate(t)
root = root.Copy()
t.Current().MoveTo(root)
var list []NodeNavigator
for node := m.Child.Select(t); node != nil; node = m.Child.Select(t) {
list = append(list, node.Copy())
}
i := 0
m.iterator = func() NodeNavigator {
if i >= len(list) {
return nil
}
result := list[i]
i++
return result
}
}
if node := m.iterator(); node != nil {
return node
}
m.iterator = nil
}
}
func (m *mergeQuery) Evaluate(t iterator) interface{} {
m.Input.Evaluate(t)
return m
}
func (m *mergeQuery) Clone() query {
return &mergeQuery{Input: m.Input.Clone(), Child: m.Child.Clone()}
}
func (m *mergeQuery) ValueType() resultType {
return xpathResultType.NodeSet
}
func (m *mergeQuery) Properties() queryProp {
return queryProps.Position | queryProps.Count | queryProps.Cached | queryProps.Merge
}
func getHashCode(n NodeNavigator) uint64 { func getHashCode(n NodeNavigator) uint64 {
var sb bytes.Buffer var sb bytes.Buffer
switch n.NodeType() { switch n.NodeType() {
@ -931,7 +1392,7 @@ func getHashCode(n NodeNavigator) uint64 {
} }
} }
h := fnv.New64a() h := fnv.New64a()
h.Write([]byte(sb.String())) h.Write(sb.Bytes())
return h.Sum64() return h.Sum64()
} }
@ -954,3 +1415,20 @@ func getNodeDepth(q query) int {
} }
return 0 return 0
} }
func getXPathType(i interface{}) resultType {
v := reflect.ValueOf(i)
switch v.Kind() {
case reflect.Float64:
return xpathResultType.Number
case reflect.String:
return xpathResultType.String
case reflect.Bool:
return xpathResultType.Boolean
default:
if _, ok := i.(query); ok {
return xpathResultType.NodeSet
}
}
panic(fmt.Errorf("xpath unknown value type: %v", v.Kind()))
}

View File

@ -84,14 +84,14 @@ func (t *NodeIterator) Current() NodeNavigator {
// MoveNext moves Navigator to the next match node. // MoveNext moves Navigator to the next match node.
func (t *NodeIterator) MoveNext() bool { func (t *NodeIterator) MoveNext() bool {
n := t.query.Select(t) n := t.query.Select(t)
if n != nil { if n == nil {
return false
}
if !t.node.MoveTo(n) { if !t.node.MoveTo(n) {
t.node = n.Copy() t.node = n.Copy()
} }
return true return true
} }
return false
}
// Select selects a node set using the specified XPath expression. // Select selects a node set using the specified XPath expression.
// This method is deprecated, recommend using Expr.Select() method instead. // This method is deprecated, recommend using Expr.Select() method instead.
@ -141,7 +141,7 @@ func Compile(expr string) (*Expr, error) {
if expr == "" { if expr == "" {
return nil, errors.New("expr expression is nil") return nil, errors.New("expr expression is nil")
} }
qy, err := build(expr) qy, err := build(expr, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -159,3 +159,18 @@ func MustCompile(expr string) *Expr {
} }
return exp return exp
} }
// CompileWithNS compiles an XPath expression string, using given namespaces map.
func CompileWithNS(expr string, namespaces map[string]string) (*Expr, error) {
if expr == "" {
return nil, errors.New("expr expression is nil")
}
qy, err := build(expr, namespaces)
if err != nil {
return nil, err
}
if qy == nil {
return nil, fmt.Errorf(fmt.Sprintf("undeclared variable in XPath expression: %s", expr))
}
return &Expr{s: expr, q: qy}, nil
}

4
vendor/golang.org/x/net/LICENSE generated vendored
View File

@ -1,4 +1,4 @@
Copyright (c) 2009 The Go Authors. All rights reserved. Copyright 2009 The Go Authors.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are modification, are permitted provided that the following conditions are
@ -10,7 +10,7 @@ notice, this list of conditions and the following disclaimer.
copyright notice, this list of conditions and the following disclaimer copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the in the documentation and/or other materials provided with the
distribution. distribution.
* Neither the name of Google Inc. nor the names of its * Neither the name of Google LLC nor the names of its
contributors may be used to endorse or promote products derived from contributors may be used to endorse or promote products derived from
this software without specific prior written permission. this software without specific prior written permission.

21
vendor/golang.org/x/net/html/doc.go generated vendored
View File

@ -92,6 +92,27 @@ example, to process each anchor node in depth-first order:
The relevant specifications include: The relevant specifications include:
https://html.spec.whatwg.org/multipage/syntax.html and https://html.spec.whatwg.org/multipage/syntax.html and
https://html.spec.whatwg.org/multipage/syntax.html#tokenization https://html.spec.whatwg.org/multipage/syntax.html#tokenization
# Security Considerations
Care should be taken when parsing and interpreting HTML, whether full documents
or fragments, within the framework of the HTML specification, especially with
regard to untrusted inputs.
This package provides both a tokenizer and a parser, which implement the
tokenization, and tokenization and tree construction stages of the WHATWG HTML
parsing specification respectively. While the tokenizer parses and normalizes
individual HTML tokens, only the parser constructs the DOM tree from the
tokenized HTML, as described in the tree construction stage of the
specification, dynamically modifying or extending the document's DOM tree.
If your use case requires semantically well-formed HTML documents, as defined by
the WHATWG specification, the parser should be used rather than the tokenizer.
In security contexts, if trust decisions are being made using the tokenized or
parsed content, the input must be re-serialized (for instance by using Render or
Token.String) in order for those trust decisions to hold, as the process of
tokenization or parsing may alter the content.
*/ */
package html // import "golang.org/x/net/html" package html // import "golang.org/x/net/html"

View File

@ -193,6 +193,87 @@ func lower(b []byte) []byte {
return b return b
} }
// escapeComment is like func escape but escapes its input bytes less often.
// Per https://github.com/golang/go/issues/58246 some HTML comments are (1)
// meaningful and (2) contain angle brackets that we'd like to avoid escaping
// unless we have to.
//
// "We have to" includes the '&' byte, since that introduces other escapes.
//
// It also includes those bytes (not including EOF) that would otherwise end
// the comment. Per the summary table at the bottom of comment_test.go, this is
// the '>' byte that, per above, we'd like to avoid escaping unless we have to.
//
// Studying the summary table (and T actions in its '>' column) closely, we
// only need to escape in states 43, 44, 49, 51 and 52. State 43 is at the
// start of the comment data. State 52 is after a '!'. The other three states
// are after a '-'.
//
// Our algorithm is thus to escape every '&' and to escape '>' if and only if:
// - The '>' is after a '!' or '-' (in the unescaped data) or
// - The '>' is at the start of the comment data (after the opening "<!--").
func escapeComment(w writer, s string) error {
// When modifying this function, consider manually increasing the
// maxSuffixLen constant in func TestComments, from 6 to e.g. 9 or more.
// That increase should only be temporary, not committed, as it
// exponentially affects the test running time.
if len(s) == 0 {
return nil
}
// Loop:
// - Grow j such that s[i:j] does not need escaping.
// - If s[j] does need escaping, output s[i:j] and an escaped s[j],
// resetting i and j to point past that s[j] byte.
i := 0
for j := 0; j < len(s); j++ {
escaped := ""
switch s[j] {
case '&':
escaped = "&amp;"
case '>':
if j > 0 {
if prev := s[j-1]; (prev != '!') && (prev != '-') {
continue
}
}
escaped = "&gt;"
default:
continue
}
if i < j {
if _, err := w.WriteString(s[i:j]); err != nil {
return err
}
}
if _, err := w.WriteString(escaped); err != nil {
return err
}
i = j + 1
}
if i < len(s) {
if _, err := w.WriteString(s[i:]); err != nil {
return err
}
}
return nil
}
// escapeCommentString is to EscapeString as escapeComment is to escape.
func escapeCommentString(s string) string {
if strings.IndexAny(s, "&>") == -1 {
return s
}
var buf bytes.Buffer
escapeComment(&buf, s)
return buf.String()
}
const escapedChars = "&'<>\"\r" const escapedChars = "&'<>\"\r"
func escape(w writer, s string) error { func escape(w writer, s string) error {

View File

@ -184,7 +184,7 @@ func (p *parser) clearStackToContext(s scope) {
} }
} }
// parseGenericRawTextElements implements the generic raw text element parsing // parseGenericRawTextElement implements the generic raw text element parsing
// algorithm defined in 12.2.6.2. // algorithm defined in 12.2.6.2.
// https://html.spec.whatwg.org/multipage/parsing.html#parsing-elements-that-contain-only-text // https://html.spec.whatwg.org/multipage/parsing.html#parsing-elements-that-contain-only-text
// TODO: Since both RAWTEXT and RCDATA states are treated as tokenizer's part // TODO: Since both RAWTEXT and RCDATA states are treated as tokenizer's part

View File

@ -85,7 +85,7 @@ func render1(w writer, n *Node) error {
if _, err := w.WriteString("<!--"); err != nil { if _, err := w.WriteString("<!--"); err != nil {
return err return err
} }
if err := escape(w, n.Data); err != nil { if err := escapeComment(w, n.Data); err != nil {
return err return err
} }
if _, err := w.WriteString("-->"); err != nil { if _, err := w.WriteString("-->"); err != nil {
@ -194,9 +194,8 @@ func render1(w writer, n *Node) error {
} }
} }
// Render any child nodes. // Render any child nodes
switch n.Data { if childTextNodesAreLiteral(n) {
case "iframe", "noembed", "noframes", "noscript", "plaintext", "script", "style", "xmp":
for c := n.FirstChild; c != nil; c = c.NextSibling { for c := n.FirstChild; c != nil; c = c.NextSibling {
if c.Type == TextNode { if c.Type == TextNode {
if _, err := w.WriteString(c.Data); err != nil { if _, err := w.WriteString(c.Data); err != nil {
@ -213,7 +212,7 @@ func render1(w writer, n *Node) error {
// last element in the file, with no closing tag. // last element in the file, with no closing tag.
return plaintextAbort return plaintextAbort
} }
default: } else {
for c := n.FirstChild; c != nil; c = c.NextSibling { for c := n.FirstChild; c != nil; c = c.NextSibling {
if err := render1(w, c); err != nil { if err := render1(w, c); err != nil {
return err return err
@ -231,6 +230,27 @@ func render1(w writer, n *Node) error {
return w.WriteByte('>') return w.WriteByte('>')
} }
func childTextNodesAreLiteral(n *Node) bool {
// Per WHATWG HTML 13.3, if the parent of the current node is a style,
// script, xmp, iframe, noembed, noframes, or plaintext element, and the
// current node is a text node, append the value of the node's data
// literally. The specification is not explicit about it, but we only
// enforce this if we are in the HTML namespace (i.e. when the namespace is
// "").
// NOTE: we also always include noscript elements, although the
// specification states that they should only be rendered as such if
// scripting is enabled for the node (which is not something we track).
if n.Namespace != "" {
return false
}
switch n.Data {
case "iframe", "noembed", "noframes", "noscript", "plaintext", "script", "style", "xmp":
return true
default:
return false
}
}
// writeQuoted writes s to w surrounded by quotes. Normally it will use double // writeQuoted writes s to w surrounded by quotes. Normally it will use double
// quotes, but if s contains a double quote, it will use single quotes. // quotes, but if s contains a double quote, it will use single quotes.
// It is used for writing the identifiers in a doctype declaration. // It is used for writing the identifiers in a doctype declaration.

View File

@ -110,7 +110,7 @@ func (t Token) String() string {
case SelfClosingTagToken: case SelfClosingTagToken:
return "<" + t.tagString() + "/>" return "<" + t.tagString() + "/>"
case CommentToken: case CommentToken:
return "<!--" + EscapeString(t.Data) + "-->" return "<!--" + escapeCommentString(t.Data) + "-->"
case DoctypeToken: case DoctypeToken:
return "<!DOCTYPE " + EscapeString(t.Data) + ">" return "<!DOCTYPE " + EscapeString(t.Data) + ">"
} }
@ -598,6 +598,11 @@ scriptDataDoubleEscapeEnd:
// readComment reads the next comment token starting with "<!--". The opening // readComment reads the next comment token starting with "<!--". The opening
// "<!--" has already been consumed. // "<!--" has already been consumed.
func (z *Tokenizer) readComment() { func (z *Tokenizer) readComment() {
// When modifying this function, consider manually increasing the
// maxSuffixLen constant in func TestComments, from 6 to e.g. 9 or more.
// That increase should only be temporary, not committed, as it
// exponentially affects the test running time.
z.data.start = z.raw.end z.data.start = z.raw.end
defer func() { defer func() {
if z.data.end < z.data.start { if z.data.end < z.data.start {
@ -605,14 +610,13 @@ func (z *Tokenizer) readComment() {
z.data.end = z.data.start z.data.end = z.data.start
} }
}() }()
for dashCount := 2; ; {
var dashCount int
beginning := true
for {
c := z.readByte() c := z.readByte()
if z.err != nil { if z.err != nil {
// Ignore up to two dashes at EOF. z.data.end = z.calculateAbruptCommentDataEnd()
if dashCount > 2 {
dashCount = 2
}
z.data.end = z.raw.end - dashCount
return return
} }
switch c { switch c {
@ -620,7 +624,7 @@ func (z *Tokenizer) readComment() {
dashCount++ dashCount++
continue continue
case '>': case '>':
if dashCount >= 2 { if dashCount >= 2 || beginning {
z.data.end = z.raw.end - len("-->") z.data.end = z.raw.end - len("-->")
return return
} }
@ -628,19 +632,52 @@ func (z *Tokenizer) readComment() {
if dashCount >= 2 { if dashCount >= 2 {
c = z.readByte() c = z.readByte()
if z.err != nil { if z.err != nil {
z.data.end = z.raw.end z.data.end = z.calculateAbruptCommentDataEnd()
return return
} } else if c == '>' {
if c == '>' {
z.data.end = z.raw.end - len("--!>") z.data.end = z.raw.end - len("--!>")
return return
} else if c == '-' {
dashCount = 1
beginning = false
continue
} }
} }
} }
dashCount = 0 dashCount = 0
beginning = false
} }
} }
func (z *Tokenizer) calculateAbruptCommentDataEnd() int {
raw := z.Raw()
const prefixLen = len("<!--")
if len(raw) >= prefixLen {
raw = raw[prefixLen:]
if hasSuffix(raw, "--!") {
return z.raw.end - 3
} else if hasSuffix(raw, "--") {
return z.raw.end - 2
} else if hasSuffix(raw, "-") {
return z.raw.end - 1
}
}
return z.raw.end
}
func hasSuffix(b []byte, suffix string) bool {
if len(b) < len(suffix) {
return false
}
b = b[len(b)-len(suffix):]
for i := range b {
if b[i] != suffix[i] {
return false
}
}
return true
}
// readUntilCloseAngle reads until the next ">". // readUntilCloseAngle reads until the next ">".
func (z *Tokenizer) readUntilCloseAngle() { func (z *Tokenizer) readUntilCloseAngle() {
z.data.start = z.raw.end z.data.start = z.raw.end
@ -873,10 +910,16 @@ func (z *Tokenizer) readTagAttrKey() {
return return
} }
switch c { switch c {
case ' ', '\n', '\r', '\t', '\f', '/': case '=':
z.pendingAttr[0].end = z.raw.end - 1 if z.pendingAttr[0].start+1 == z.raw.end {
return // WHATWG 13.2.5.32, if we see an equals sign before the attribute name
case '=', '>': // begins, we treat it as a character in the attribute name and continue.
continue
}
fallthrough
case ' ', '\n', '\r', '\t', '\f', '/', '>':
// WHATWG 13.2.5.33 Attribute name state
// We need to reconsume the char in the after attribute name state to support the / character
z.raw.end-- z.raw.end--
z.pendingAttr[0].end = z.raw.end z.pendingAttr[0].end = z.raw.end
return return
@ -895,6 +938,11 @@ func (z *Tokenizer) readTagAttrVal() {
if z.err != nil { if z.err != nil {
return return
} }
if c == '/' {
// WHATWG 13.2.5.34 After attribute name state
// U+002F SOLIDUS (/) - Switch to the self-closing start tag state.
return
}
if c != '=' { if c != '=' {
z.raw.end-- z.raw.end--
return return

4
vendor/golang.org/x/text/LICENSE generated vendored
View File

@ -1,4 +1,4 @@
Copyright (c) 2009 The Go Authors. All rights reserved. Copyright 2009 The Go Authors.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are modification, are permitted provided that the following conditions are
@ -10,7 +10,7 @@ notice, this list of conditions and the following disclaimer.
copyright notice, this list of conditions and the following disclaimer copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the in the documentation and/or other materials provided with the
distribution. distribution.
* Neither the name of Google Inc. nor the names of its * Neither the name of Google LLC nor the names of its
contributors may be used to endorse or promote products derived from contributors may be used to endorse or promote products derived from
this software without specific prior written permission. this software without specific prior written permission.

View File

@ -64,7 +64,7 @@ func (e FuncEncoding) NewEncoder() *encoding.Encoder {
// byte. // byte.
type RepertoireError byte type RepertoireError byte
// Error implements the error interrface. // Error implements the error interface.
func (r RepertoireError) Error() string { func (r RepertoireError) Error() string {
return "encoding: rune not supported by encoding." return "encoding: rune not supported by encoding."
} }

View File

@ -118,7 +118,7 @@ func (t Tag) Parent() Tag {
return Tag{language: lang, locale: lang} return Tag{language: lang, locale: lang}
} }
// returns token t and the rest of the string. // nextToken returns token t and the rest of the string.
func nextToken(s string) (t, tail string) { func nextToken(s string) (t, tail string) {
p := strings.Index(s[1:], "-") p := strings.Index(s[1:], "-")
if p == -1 { if p == -1 {

View File

@ -790,226 +790,226 @@ const (
var coreTags = []language.CompactCoreInfo{ // 773 elements var coreTags = []language.CompactCoreInfo{ // 773 elements
// Entry 0 - 1F // Entry 0 - 1F
0x00000000, 0x01600000, 0x016000d2, 0x01600161, 0x00000000, 0x01600000, 0x016000d3, 0x01600162,
0x01c00000, 0x01c00052, 0x02100000, 0x02100080, 0x01c00000, 0x01c00052, 0x02100000, 0x02100081,
0x02700000, 0x0270006f, 0x03a00000, 0x03a00001, 0x02700000, 0x02700070, 0x03a00000, 0x03a00001,
0x03a00023, 0x03a00039, 0x03a00062, 0x03a00067, 0x03a00023, 0x03a00039, 0x03a00063, 0x03a00068,
0x03a0006b, 0x03a0006c, 0x03a0006d, 0x03a00097, 0x03a0006c, 0x03a0006d, 0x03a0006e, 0x03a00098,
0x03a0009b, 0x03a000a1, 0x03a000a8, 0x03a000ac, 0x03a0009c, 0x03a000a2, 0x03a000a9, 0x03a000ad,
0x03a000b0, 0x03a000b9, 0x03a000ba, 0x03a000c9, 0x03a000b1, 0x03a000ba, 0x03a000bb, 0x03a000ca,
0x03a000e1, 0x03a000ed, 0x03a000f3, 0x03a00108, 0x03a000e2, 0x03a000ee, 0x03a000f4, 0x03a00109,
// Entry 20 - 3F // Entry 20 - 3F
0x03a0010b, 0x03a00115, 0x03a00117, 0x03a0011c, 0x03a0010c, 0x03a00116, 0x03a00118, 0x03a0011d,
0x03a00120, 0x03a00128, 0x03a0015e, 0x04000000, 0x03a00121, 0x03a00129, 0x03a0015f, 0x04000000,
0x04300000, 0x04300099, 0x04400000, 0x0440012f, 0x04300000, 0x0430009a, 0x04400000, 0x04400130,
0x04800000, 0x0480006e, 0x05800000, 0x05820000, 0x04800000, 0x0480006f, 0x05800000, 0x05820000,
0x05820032, 0x0585a000, 0x0585a032, 0x05e00000, 0x05820032, 0x0585b000, 0x0585b032, 0x05e00000,
0x05e00052, 0x07100000, 0x07100047, 0x07500000, 0x05e00052, 0x07100000, 0x07100047, 0x07500000,
0x07500162, 0x07900000, 0x0790012f, 0x07e00000, 0x07500163, 0x07900000, 0x07900130, 0x07e00000,
0x07e00038, 0x08200000, 0x0a000000, 0x0a0000c3, 0x07e00038, 0x08200000, 0x0a000000, 0x0a0000c4,
// Entry 40 - 5F // Entry 40 - 5F
0x0a500000, 0x0a500035, 0x0a500099, 0x0a900000, 0x0a500000, 0x0a500035, 0x0a50009a, 0x0a900000,
0x0a900053, 0x0a900099, 0x0b200000, 0x0b200078, 0x0a900053, 0x0a90009a, 0x0b200000, 0x0b200079,
0x0b500000, 0x0b500099, 0x0b700000, 0x0b720000, 0x0b500000, 0x0b50009a, 0x0b700000, 0x0b720000,
0x0b720033, 0x0b75a000, 0x0b75a033, 0x0d700000, 0x0b720033, 0x0b75b000, 0x0b75b033, 0x0d700000,
0x0d700022, 0x0d70006e, 0x0d700078, 0x0d70009e, 0x0d700022, 0x0d70006f, 0x0d700079, 0x0d70009f,
0x0db00000, 0x0db00035, 0x0db00099, 0x0dc00000, 0x0db00000, 0x0db00035, 0x0db0009a, 0x0dc00000,
0x0dc00106, 0x0df00000, 0x0df00131, 0x0e500000, 0x0dc00107, 0x0df00000, 0x0df00132, 0x0e500000,
0x0e500135, 0x0e900000, 0x0e90009b, 0x0e90009c, 0x0e500136, 0x0e900000, 0x0e90009c, 0x0e90009d,
// Entry 60 - 7F // Entry 60 - 7F
0x0fa00000, 0x0fa0005e, 0x0fe00000, 0x0fe00106, 0x0fa00000, 0x0fa0005f, 0x0fe00000, 0x0fe00107,
0x10000000, 0x1000007b, 0x10100000, 0x10100063, 0x10000000, 0x1000007c, 0x10100000, 0x10100064,
0x10100082, 0x10800000, 0x108000a4, 0x10d00000, 0x10100083, 0x10800000, 0x108000a5, 0x10d00000,
0x10d0002e, 0x10d00036, 0x10d0004e, 0x10d00060, 0x10d0002e, 0x10d00036, 0x10d0004e, 0x10d00061,
0x10d0009e, 0x10d000b2, 0x10d000b7, 0x11700000, 0x10d0009f, 0x10d000b3, 0x10d000b8, 0x11700000,
0x117000d4, 0x11f00000, 0x11f00060, 0x12400000, 0x117000d5, 0x11f00000, 0x11f00061, 0x12400000,
0x12400052, 0x12800000, 0x12b00000, 0x12b00114, 0x12400052, 0x12800000, 0x12b00000, 0x12b00115,
0x12d00000, 0x12d00043, 0x12f00000, 0x12f000a4, 0x12d00000, 0x12d00043, 0x12f00000, 0x12f000a5,
// Entry 80 - 9F // Entry 80 - 9F
0x13000000, 0x13000080, 0x13000122, 0x13600000, 0x13000000, 0x13000081, 0x13000123, 0x13600000,
0x1360005d, 0x13600087, 0x13900000, 0x13900001, 0x1360005e, 0x13600088, 0x13900000, 0x13900001,
0x1390001a, 0x13900025, 0x13900026, 0x1390002d, 0x1390001a, 0x13900025, 0x13900026, 0x1390002d,
0x1390002e, 0x1390002f, 0x13900034, 0x13900036, 0x1390002e, 0x1390002f, 0x13900034, 0x13900036,
0x1390003a, 0x1390003d, 0x13900042, 0x13900046, 0x1390003a, 0x1390003d, 0x13900042, 0x13900046,
0x13900048, 0x13900049, 0x1390004a, 0x1390004e, 0x13900048, 0x13900049, 0x1390004a, 0x1390004e,
0x13900050, 0x13900052, 0x1390005c, 0x1390005d, 0x13900050, 0x13900052, 0x1390005d, 0x1390005e,
0x13900060, 0x13900061, 0x13900063, 0x13900064, 0x13900061, 0x13900062, 0x13900064, 0x13900065,
// Entry A0 - BF // Entry A0 - BF
0x1390006d, 0x13900072, 0x13900073, 0x13900074, 0x1390006e, 0x13900073, 0x13900074, 0x13900075,
0x13900075, 0x1390007b, 0x1390007c, 0x1390007f, 0x13900076, 0x1390007c, 0x1390007d, 0x13900080,
0x13900080, 0x13900081, 0x13900083, 0x1390008a, 0x13900081, 0x13900082, 0x13900084, 0x1390008b,
0x1390008c, 0x1390008d, 0x13900096, 0x13900097, 0x1390008d, 0x1390008e, 0x13900097, 0x13900098,
0x13900098, 0x13900099, 0x1390009a, 0x1390009f, 0x13900099, 0x1390009a, 0x1390009b, 0x139000a0,
0x139000a0, 0x139000a4, 0x139000a7, 0x139000a9, 0x139000a1, 0x139000a5, 0x139000a8, 0x139000aa,
0x139000ad, 0x139000b1, 0x139000b4, 0x139000b5, 0x139000ae, 0x139000b2, 0x139000b5, 0x139000b6,
0x139000bf, 0x139000c0, 0x139000c6, 0x139000c7, 0x139000c0, 0x139000c1, 0x139000c7, 0x139000c8,
// Entry C0 - DF // Entry C0 - DF
0x139000ca, 0x139000cb, 0x139000cc, 0x139000ce, 0x139000cb, 0x139000cc, 0x139000cd, 0x139000cf,
0x139000d0, 0x139000d2, 0x139000d5, 0x139000d6, 0x139000d1, 0x139000d3, 0x139000d6, 0x139000d7,
0x139000d9, 0x139000dd, 0x139000df, 0x139000e0, 0x139000da, 0x139000de, 0x139000e0, 0x139000e1,
0x139000e6, 0x139000e7, 0x139000e8, 0x139000eb, 0x139000e7, 0x139000e8, 0x139000e9, 0x139000ec,
0x139000ec, 0x139000f0, 0x13900107, 0x13900109, 0x139000ed, 0x139000f1, 0x13900108, 0x1390010a,
0x1390010a, 0x1390010b, 0x1390010c, 0x1390010d, 0x1390010b, 0x1390010c, 0x1390010d, 0x1390010e,
0x1390010e, 0x1390010f, 0x13900112, 0x13900117, 0x1390010f, 0x13900110, 0x13900113, 0x13900118,
0x1390011b, 0x1390011d, 0x1390011f, 0x13900125, 0x1390011c, 0x1390011e, 0x13900120, 0x13900126,
// Entry E0 - FF // Entry E0 - FF
0x13900129, 0x1390012c, 0x1390012d, 0x1390012f, 0x1390012a, 0x1390012d, 0x1390012e, 0x13900130,
0x13900131, 0x13900133, 0x13900135, 0x13900139, 0x13900132, 0x13900134, 0x13900136, 0x1390013a,
0x1390013c, 0x1390013d, 0x1390013f, 0x13900142, 0x1390013d, 0x1390013e, 0x13900140, 0x13900143,
0x13900161, 0x13900162, 0x13900164, 0x13c00000, 0x13900162, 0x13900163, 0x13900165, 0x13c00000,
0x13c00001, 0x13e00000, 0x13e0001f, 0x13e0002c, 0x13c00001, 0x13e00000, 0x13e0001f, 0x13e0002c,
0x13e0003f, 0x13e00041, 0x13e00048, 0x13e00051, 0x13e0003f, 0x13e00041, 0x13e00048, 0x13e00051,
0x13e00054, 0x13e00056, 0x13e00059, 0x13e00065, 0x13e00054, 0x13e00057, 0x13e0005a, 0x13e00066,
0x13e00068, 0x13e00069, 0x13e0006e, 0x13e00086, 0x13e00069, 0x13e0006a, 0x13e0006f, 0x13e00087,
// Entry 100 - 11F // Entry 100 - 11F
0x13e00089, 0x13e0008f, 0x13e00094, 0x13e000cf, 0x13e0008a, 0x13e00090, 0x13e00095, 0x13e000d0,
0x13e000d8, 0x13e000e2, 0x13e000e4, 0x13e000e7, 0x13e000d9, 0x13e000e3, 0x13e000e5, 0x13e000e8,
0x13e000ec, 0x13e000f1, 0x13e0011a, 0x13e00135, 0x13e000ed, 0x13e000f2, 0x13e0011b, 0x13e00136,
0x13e00136, 0x13e0013b, 0x14000000, 0x1400006a, 0x13e00137, 0x13e0013c, 0x14000000, 0x1400006b,
0x14500000, 0x1450006e, 0x14600000, 0x14600052, 0x14500000, 0x1450006f, 0x14600000, 0x14600052,
0x14800000, 0x14800024, 0x1480009c, 0x14e00000, 0x14800000, 0x14800024, 0x1480009d, 0x14e00000,
0x14e00052, 0x14e00084, 0x14e000c9, 0x14e00114, 0x14e00052, 0x14e00085, 0x14e000ca, 0x14e00115,
0x15100000, 0x15100072, 0x15300000, 0x153000e7, 0x15100000, 0x15100073, 0x15300000, 0x153000e8,
// Entry 120 - 13F // Entry 120 - 13F
0x15800000, 0x15800063, 0x15800076, 0x15e00000, 0x15800000, 0x15800064, 0x15800077, 0x15e00000,
0x15e00036, 0x15e00037, 0x15e0003a, 0x15e0003b, 0x15e00036, 0x15e00037, 0x15e0003a, 0x15e0003b,
0x15e0003c, 0x15e00049, 0x15e0004b, 0x15e0004c, 0x15e0003c, 0x15e00049, 0x15e0004b, 0x15e0004c,
0x15e0004d, 0x15e0004e, 0x15e0004f, 0x15e00052, 0x15e0004d, 0x15e0004e, 0x15e0004f, 0x15e00052,
0x15e00062, 0x15e00067, 0x15e00078, 0x15e0007a, 0x15e00063, 0x15e00068, 0x15e00079, 0x15e0007b,
0x15e0007e, 0x15e00084, 0x15e00085, 0x15e00086, 0x15e0007f, 0x15e00085, 0x15e00086, 0x15e00087,
0x15e00091, 0x15e000a8, 0x15e000b7, 0x15e000ba, 0x15e00092, 0x15e000a9, 0x15e000b8, 0x15e000bb,
0x15e000bb, 0x15e000be, 0x15e000bf, 0x15e000c3, 0x15e000bc, 0x15e000bf, 0x15e000c0, 0x15e000c4,
// Entry 140 - 15F // Entry 140 - 15F
0x15e000c8, 0x15e000c9, 0x15e000cc, 0x15e000d3, 0x15e000c9, 0x15e000ca, 0x15e000cd, 0x15e000d4,
0x15e000d4, 0x15e000e5, 0x15e000ea, 0x15e00102, 0x15e000d5, 0x15e000e6, 0x15e000eb, 0x15e00103,
0x15e00107, 0x15e0010a, 0x15e00114, 0x15e0011c, 0x15e00108, 0x15e0010b, 0x15e00115, 0x15e0011d,
0x15e00120, 0x15e00122, 0x15e00128, 0x15e0013f, 0x15e00121, 0x15e00123, 0x15e00129, 0x15e00140,
0x15e00140, 0x15e0015f, 0x16900000, 0x1690009e, 0x15e00141, 0x15e00160, 0x16900000, 0x1690009f,
0x16d00000, 0x16d000d9, 0x16e00000, 0x16e00096, 0x16d00000, 0x16d000da, 0x16e00000, 0x16e00097,
0x17e00000, 0x17e0007b, 0x19000000, 0x1900006e, 0x17e00000, 0x17e0007c, 0x19000000, 0x1900006f,
0x1a300000, 0x1a30004e, 0x1a300078, 0x1a3000b2, 0x1a300000, 0x1a30004e, 0x1a300079, 0x1a3000b3,
// Entry 160 - 17F // Entry 160 - 17F
0x1a400000, 0x1a400099, 0x1a900000, 0x1ab00000, 0x1a400000, 0x1a40009a, 0x1a900000, 0x1ab00000,
0x1ab000a4, 0x1ac00000, 0x1ac00098, 0x1b400000, 0x1ab000a5, 0x1ac00000, 0x1ac00099, 0x1b400000,
0x1b400080, 0x1b4000d4, 0x1b4000d6, 0x1b800000, 0x1b400081, 0x1b4000d5, 0x1b4000d7, 0x1b800000,
0x1b800135, 0x1bc00000, 0x1bc00097, 0x1be00000, 0x1b800136, 0x1bc00000, 0x1bc00098, 0x1be00000,
0x1be00099, 0x1d100000, 0x1d100033, 0x1d100090, 0x1be0009a, 0x1d100000, 0x1d100033, 0x1d100091,
0x1d200000, 0x1d200060, 0x1d500000, 0x1d500092, 0x1d200000, 0x1d200061, 0x1d500000, 0x1d500093,
0x1d700000, 0x1d700028, 0x1e100000, 0x1e100095, 0x1d700000, 0x1d700028, 0x1e100000, 0x1e100096,
0x1e700000, 0x1e7000d6, 0x1ea00000, 0x1ea00053, 0x1e700000, 0x1e7000d7, 0x1ea00000, 0x1ea00053,
// Entry 180 - 19F // Entry 180 - 19F
0x1f300000, 0x1f500000, 0x1f800000, 0x1f80009d, 0x1f300000, 0x1f500000, 0x1f800000, 0x1f80009e,
0x1f900000, 0x1f90004e, 0x1f90009e, 0x1f900113, 0x1f900000, 0x1f90004e, 0x1f90009f, 0x1f900114,
0x1f900138, 0x1fa00000, 0x1fb00000, 0x20000000, 0x1f900139, 0x1fa00000, 0x1fb00000, 0x20000000,
0x200000a2, 0x20300000, 0x20700000, 0x20700052, 0x200000a3, 0x20300000, 0x20700000, 0x20700052,
0x20800000, 0x20a00000, 0x20a0012f, 0x20e00000, 0x20800000, 0x20a00000, 0x20a00130, 0x20e00000,
0x20f00000, 0x21000000, 0x2100007d, 0x21200000, 0x20f00000, 0x21000000, 0x2100007e, 0x21200000,
0x21200067, 0x21600000, 0x21700000, 0x217000a4, 0x21200068, 0x21600000, 0x21700000, 0x217000a5,
0x21f00000, 0x22300000, 0x2230012f, 0x22700000, 0x21f00000, 0x22300000, 0x22300130, 0x22700000,
// Entry 1A0 - 1BF // Entry 1A0 - 1BF
0x2270005a, 0x23400000, 0x234000c3, 0x23900000, 0x2270005b, 0x23400000, 0x234000c4, 0x23900000,
0x239000a4, 0x24200000, 0x242000ae, 0x24400000, 0x239000a5, 0x24200000, 0x242000af, 0x24400000,
0x24400052, 0x24500000, 0x24500082, 0x24600000, 0x24400052, 0x24500000, 0x24500083, 0x24600000,
0x246000a4, 0x24a00000, 0x24a000a6, 0x25100000, 0x246000a5, 0x24a00000, 0x24a000a7, 0x25100000,
0x25100099, 0x25400000, 0x254000aa, 0x254000ab, 0x2510009a, 0x25400000, 0x254000ab, 0x254000ac,
0x25600000, 0x25600099, 0x26a00000, 0x26a00099, 0x25600000, 0x2560009a, 0x26a00000, 0x26a0009a,
0x26b00000, 0x26b0012f, 0x26d00000, 0x26d00052, 0x26b00000, 0x26b00130, 0x26d00000, 0x26d00052,
0x26e00000, 0x26e00060, 0x27400000, 0x28100000, 0x26e00000, 0x26e00061, 0x27400000, 0x28100000,
// Entry 1C0 - 1DF // Entry 1C0 - 1DF
0x2810007b, 0x28a00000, 0x28a000a5, 0x29100000, 0x2810007c, 0x28a00000, 0x28a000a6, 0x29100000,
0x2910012f, 0x29500000, 0x295000b7, 0x2a300000, 0x29100130, 0x29500000, 0x295000b8, 0x2a300000,
0x2a300131, 0x2af00000, 0x2af00135, 0x2b500000, 0x2a300132, 0x2af00000, 0x2af00136, 0x2b500000,
0x2b50002a, 0x2b50004b, 0x2b50004c, 0x2b50004d, 0x2b50002a, 0x2b50004b, 0x2b50004c, 0x2b50004d,
0x2b800000, 0x2b8000af, 0x2bf00000, 0x2bf0009b, 0x2b800000, 0x2b8000b0, 0x2bf00000, 0x2bf0009c,
0x2bf0009c, 0x2c000000, 0x2c0000b6, 0x2c200000, 0x2bf0009d, 0x2c000000, 0x2c0000b7, 0x2c200000,
0x2c20004b, 0x2c400000, 0x2c4000a4, 0x2c500000, 0x2c20004b, 0x2c400000, 0x2c4000a5, 0x2c500000,
0x2c5000a4, 0x2c700000, 0x2c7000b8, 0x2d100000, 0x2c5000a5, 0x2c700000, 0x2c7000b9, 0x2d100000,
// Entry 1E0 - 1FF // Entry 1E0 - 1FF
0x2d1000a4, 0x2d10012f, 0x2e900000, 0x2e9000a4, 0x2d1000a5, 0x2d100130, 0x2e900000, 0x2e9000a5,
0x2ed00000, 0x2ed000cc, 0x2f100000, 0x2f1000bf, 0x2ed00000, 0x2ed000cd, 0x2f100000, 0x2f1000c0,
0x2f200000, 0x2f2000d1, 0x2f400000, 0x2f400052, 0x2f200000, 0x2f2000d2, 0x2f400000, 0x2f400052,
0x2ff00000, 0x2ff000c2, 0x30400000, 0x30400099, 0x2ff00000, 0x2ff000c3, 0x30400000, 0x3040009a,
0x30b00000, 0x30b000c5, 0x31000000, 0x31b00000, 0x30b00000, 0x30b000c6, 0x31000000, 0x31b00000,
0x31b00099, 0x31f00000, 0x31f0003e, 0x31f000d0, 0x31b0009a, 0x31f00000, 0x31f0003e, 0x31f000d1,
0x31f0010d, 0x32000000, 0x320000cb, 0x32500000, 0x31f0010e, 0x32000000, 0x320000cc, 0x32500000,
0x32500052, 0x33100000, 0x331000c4, 0x33a00000, 0x32500052, 0x33100000, 0x331000c5, 0x33a00000,
// Entry 200 - 21F // Entry 200 - 21F
0x33a0009c, 0x34100000, 0x34500000, 0x345000d2, 0x33a0009d, 0x34100000, 0x34500000, 0x345000d3,
0x34700000, 0x347000da, 0x34700110, 0x34e00000, 0x34700000, 0x347000db, 0x34700111, 0x34e00000,
0x34e00164, 0x35000000, 0x35000060, 0x350000d9, 0x34e00165, 0x35000000, 0x35000061, 0x350000da,
0x35100000, 0x35100099, 0x351000db, 0x36700000, 0x35100000, 0x3510009a, 0x351000dc, 0x36700000,
0x36700030, 0x36700036, 0x36700040, 0x3670005b, 0x36700030, 0x36700036, 0x36700040, 0x3670005c,
0x367000d9, 0x36700116, 0x3670011b, 0x36800000, 0x367000da, 0x36700117, 0x3670011c, 0x36800000,
0x36800052, 0x36a00000, 0x36a000da, 0x36c00000, 0x36800052, 0x36a00000, 0x36a000db, 0x36c00000,
0x36c00052, 0x36f00000, 0x37500000, 0x37600000, 0x36c00052, 0x36f00000, 0x37500000, 0x37600000,
// Entry 220 - 23F // Entry 220 - 23F
0x37a00000, 0x38000000, 0x38000117, 0x38700000, 0x37a00000, 0x38000000, 0x38000118, 0x38700000,
0x38900000, 0x38900131, 0x39000000, 0x3900006f, 0x38900000, 0x38900132, 0x39000000, 0x39000070,
0x390000a4, 0x39500000, 0x39500099, 0x39800000, 0x390000a5, 0x39500000, 0x3950009a, 0x39800000,
0x3980007d, 0x39800106, 0x39d00000, 0x39d05000, 0x3980007e, 0x39800107, 0x39d00000, 0x39d05000,
0x39d050e8, 0x39d36000, 0x39d36099, 0x3a100000, 0x39d050e9, 0x39d36000, 0x39d3609a, 0x3a100000,
0x3b300000, 0x3b3000e9, 0x3bd00000, 0x3bd00001, 0x3b300000, 0x3b3000ea, 0x3bd00000, 0x3bd00001,
0x3be00000, 0x3be00024, 0x3c000000, 0x3c00002a, 0x3be00000, 0x3be00024, 0x3c000000, 0x3c00002a,
0x3c000041, 0x3c00004e, 0x3c00005a, 0x3c000086, 0x3c000041, 0x3c00004e, 0x3c00005b, 0x3c000087,
// Entry 240 - 25F // Entry 240 - 25F
0x3c00008b, 0x3c0000b7, 0x3c0000c6, 0x3c0000d1, 0x3c00008c, 0x3c0000b8, 0x3c0000c7, 0x3c0000d2,
0x3c0000ee, 0x3c000118, 0x3c000126, 0x3c400000, 0x3c0000ef, 0x3c000119, 0x3c000127, 0x3c400000,
0x3c40003f, 0x3c400069, 0x3c4000e4, 0x3d400000, 0x3c40003f, 0x3c40006a, 0x3c4000e5, 0x3d400000,
0x3d40004e, 0x3d900000, 0x3d90003a, 0x3dc00000, 0x3d40004e, 0x3d900000, 0x3d90003a, 0x3dc00000,
0x3dc000bc, 0x3dc00104, 0x3de00000, 0x3de0012f, 0x3dc000bd, 0x3dc00105, 0x3de00000, 0x3de00130,
0x3e200000, 0x3e200047, 0x3e2000a5, 0x3e2000ae, 0x3e200000, 0x3e200047, 0x3e2000a6, 0x3e2000af,
0x3e2000bc, 0x3e200106, 0x3e200130, 0x3e500000, 0x3e2000bd, 0x3e200107, 0x3e200131, 0x3e500000,
0x3e500107, 0x3e600000, 0x3e60012f, 0x3eb00000, 0x3e500108, 0x3e600000, 0x3e600130, 0x3eb00000,
// Entry 260 - 27F // Entry 260 - 27F
0x3eb00106, 0x3ec00000, 0x3ec000a4, 0x3f300000, 0x3eb00107, 0x3ec00000, 0x3ec000a5, 0x3f300000,
0x3f30012f, 0x3fa00000, 0x3fa000e8, 0x3fc00000, 0x3f300130, 0x3fa00000, 0x3fa000e9, 0x3fc00000,
0x3fd00000, 0x3fd00072, 0x3fd000da, 0x3fd0010c, 0x3fd00000, 0x3fd00073, 0x3fd000db, 0x3fd0010d,
0x3ff00000, 0x3ff000d1, 0x40100000, 0x401000c3, 0x3ff00000, 0x3ff000d2, 0x40100000, 0x401000c4,
0x40200000, 0x4020004c, 0x40700000, 0x40800000, 0x40200000, 0x4020004c, 0x40700000, 0x40800000,
0x4085a000, 0x4085a0ba, 0x408e8000, 0x408e80ba, 0x4085b000, 0x4085b0bb, 0x408eb000, 0x408eb0bb,
0x40c00000, 0x40c000b3, 0x41200000, 0x41200111, 0x40c00000, 0x40c000b4, 0x41200000, 0x41200112,
0x41600000, 0x4160010f, 0x41c00000, 0x41d00000, 0x41600000, 0x41600110, 0x41c00000, 0x41d00000,
// Entry 280 - 29F // Entry 280 - 29F
0x41e00000, 0x41f00000, 0x41f00072, 0x42200000, 0x41e00000, 0x41f00000, 0x41f00073, 0x42200000,
0x42300000, 0x42300164, 0x42900000, 0x42900062, 0x42300000, 0x42300165, 0x42900000, 0x42900063,
0x4290006f, 0x429000a4, 0x42900115, 0x43100000, 0x42900070, 0x429000a5, 0x42900116, 0x43100000,
0x43100027, 0x431000c2, 0x4310014d, 0x43200000, 0x43100027, 0x431000c3, 0x4310014e, 0x43200000,
0x43220000, 0x43220033, 0x432200bd, 0x43220105, 0x43220000, 0x43220033, 0x432200be, 0x43220106,
0x4322014d, 0x4325a000, 0x4325a033, 0x4325a0bd, 0x4322014e, 0x4325b000, 0x4325b033, 0x4325b0be,
0x4325a105, 0x4325a14d, 0x43700000, 0x43a00000, 0x4325b106, 0x4325b14e, 0x43700000, 0x43a00000,
0x43b00000, 0x44400000, 0x44400031, 0x44400072, 0x43b00000, 0x44400000, 0x44400031, 0x44400073,
// Entry 2A0 - 2BF // Entry 2A0 - 2BF
0x4440010c, 0x44500000, 0x4450004b, 0x445000a4, 0x4440010d, 0x44500000, 0x4450004b, 0x445000a5,
0x4450012f, 0x44500131, 0x44e00000, 0x45000000, 0x44500130, 0x44500132, 0x44e00000, 0x45000000,
0x45000099, 0x450000b3, 0x450000d0, 0x4500010d, 0x4500009a, 0x450000b4, 0x450000d1, 0x4500010e,
0x46100000, 0x46100099, 0x46400000, 0x464000a4, 0x46100000, 0x4610009a, 0x46400000, 0x464000a5,
0x46400131, 0x46700000, 0x46700124, 0x46b00000, 0x46400132, 0x46700000, 0x46700125, 0x46b00000,
0x46b00123, 0x46f00000, 0x46f0006d, 0x46f0006f, 0x46b00124, 0x46f00000, 0x46f0006e, 0x46f00070,
0x47100000, 0x47600000, 0x47600127, 0x47a00000, 0x47100000, 0x47600000, 0x47600128, 0x47a00000,
0x48000000, 0x48200000, 0x48200129, 0x48a00000, 0x48000000, 0x48200000, 0x4820012a, 0x48a00000,
// Entry 2C0 - 2DF // Entry 2C0 - 2DF
0x48a0005d, 0x48a0012b, 0x48e00000, 0x49400000, 0x48a0005e, 0x48a0012c, 0x48e00000, 0x49400000,
0x49400106, 0x4a400000, 0x4a4000d4, 0x4a900000, 0x49400107, 0x4a400000, 0x4a4000d5, 0x4a900000,
0x4a9000ba, 0x4ac00000, 0x4ac00053, 0x4ae00000, 0x4a9000bb, 0x4ac00000, 0x4ac00053, 0x4ae00000,
0x4ae00130, 0x4b400000, 0x4b400099, 0x4b4000e8, 0x4ae00131, 0x4b400000, 0x4b40009a, 0x4b4000e9,
0x4bc00000, 0x4bc05000, 0x4bc05024, 0x4bc20000, 0x4bc00000, 0x4bc05000, 0x4bc05024, 0x4bc20000,
0x4bc20137, 0x4bc5a000, 0x4bc5a137, 0x4be00000, 0x4bc20138, 0x4bc5b000, 0x4bc5b138, 0x4be00000,
0x4be5a000, 0x4be5a0b4, 0x4bef1000, 0x4bef10b4, 0x4be5b000, 0x4be5b0b5, 0x4bef4000, 0x4bef40b5,
0x4c000000, 0x4c300000, 0x4c30013e, 0x4c900000, 0x4c000000, 0x4c300000, 0x4c30013f, 0x4c900000,
// Entry 2E0 - 2FF // Entry 2E0 - 2FF
0x4c900001, 0x4cc00000, 0x4cc0012f, 0x4ce00000, 0x4c900001, 0x4cc00000, 0x4cc00130, 0x4ce00000,
0x4cf00000, 0x4cf0004e, 0x4e500000, 0x4e500114, 0x4cf00000, 0x4cf0004e, 0x4e500000, 0x4e500115,
0x4f200000, 0x4fb00000, 0x4fb00131, 0x50900000, 0x4f200000, 0x4fb00000, 0x4fb00132, 0x50900000,
0x50900052, 0x51200000, 0x51200001, 0x51800000, 0x50900052, 0x51200000, 0x51200001, 0x51800000,
0x5180003b, 0x518000d6, 0x51f00000, 0x51f3b000, 0x5180003b, 0x518000d7, 0x51f00000, 0x51f3b000,
0x51f3b053, 0x51f3c000, 0x51f3c08d, 0x52800000, 0x51f3b053, 0x51f3c000, 0x51f3c08e, 0x52800000,
0x528000ba, 0x52900000, 0x5293b000, 0x5293b053, 0x528000bb, 0x52900000, 0x5293b000, 0x5293b053,
0x5293b08d, 0x5293b0c6, 0x5293b10d, 0x5293c000, 0x5293b08e, 0x5293b0c7, 0x5293b10e, 0x5293c000,
// Entry 300 - 31F // Entry 300 - 31F
0x5293c08d, 0x5293c0c6, 0x5293c12e, 0x52f00000, 0x5293c08e, 0x5293c0c7, 0x5293c12f, 0x52f00000,
0x52f00161, 0x52f00162,
} // Size: 3116 bytes } // Size: 3116 bytes
const specialTagsStr string = "ca-ES-valencia en-US-u-va-posix" const specialTagsStr string = "ca-ES-valencia en-US-u-va-posix"
// Total table size 3147 bytes (3KiB); checksum: 6772C83C // Total table size 3147 bytes (3KiB); checksum: 5A8FFFA5

View File

@ -409,7 +409,7 @@ func (t Tag) SetTypeForKey(key, value string) (Tag, error) {
return t, nil return t, nil
} }
// findKeyAndType returns the start and end position for the type corresponding // 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 // 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. // 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 // Note: the extensions are typically very small and are likely to contain

File diff suppressed because it is too large Load Diff

View File

@ -344,7 +344,7 @@ func (t Tag) Parent() Tag {
return Tag(compact.Tag(t).Parent()) return Tag(compact.Tag(t).Parent())
} }
// returns token t and the rest of the string. // nextToken returns token t and the rest of the string.
func nextToken(s string) (t, tail string) { func nextToken(s string) (t, tail string) {
p := strings.Index(s[1:], "-") p := strings.Index(s[1:], "-")
if p == -1 { if p == -1 {

View File

@ -434,7 +434,7 @@ func newMatcher(supported []Tag, options []MatchOption) *matcher {
// (their canonicalization simply substitutes a different language code, but // (their canonicalization simply substitutes a different language code, but
// nothing else), the match confidence is Exact, otherwise it is High. // nothing else), the match confidence is Exact, otherwise it is High.
for i, lm := range language.AliasMap { for i, lm := range language.AliasMap {
// If deprecated codes match and there is no fiddling with the script or // If deprecated codes match and there is no fiddling with the script
// or region, we consider it an exact match. // or region, we consider it an exact match.
conf := Exact conf := Exact
if language.AliasTypes[i] != language.Macro { if language.AliasTypes[i] != language.Macro {

View File

@ -23,31 +23,31 @@ const (
_419 = 31 _419 = 31
_BR = 65 _BR = 65
_CA = 73 _CA = 73
_ES = 110 _ES = 111
_GB = 123 _GB = 124
_MD = 188 _MD = 189
_PT = 238 _PT = 239
_UK = 306 _UK = 307
_US = 309 _US = 310
_ZZ = 357 _ZZ = 358
_XA = 323 _XA = 324
_XC = 325 _XC = 326
_XK = 333 _XK = 334
) )
const ( const (
_Latn = 90 _Latn = 91
_Hani = 57 _Hani = 57
_Hans = 59 _Hans = 59
_Hant = 60 _Hant = 60
_Qaaa = 147 _Qaaa = 149
_Qaai = 155 _Qaai = 157
_Qabx = 196 _Qabx = 198
_Zinh = 252 _Zinh = 255
_Zyyy = 257 _Zyyy = 260
_Zzzz = 258 _Zzzz = 261
) )
var regionToGroups = []uint8{ // 358 elements var regionToGroups = []uint8{ // 359 elements
// Entry 0 - 3F // Entry 0 - 3F
0x00, 0x00, 0x00, 0x04, 0x04, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x04, 0x00, 0x00, 0x04,
0x00, 0x00, 0x00, 0x00, 0x04, 0x04, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x04, 0x04, 0x00,
@ -60,51 +60,51 @@ var regionToGroups = []uint8{ // 358 elements
// Entry 40 - 7F // Entry 40 - 7F
0x04, 0x04, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x04, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00,
0x04, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x04, 0x00, 0x00, 0x04, 0x00, 0x04, 0x00, 0x00, 0x04, 0x00, 0x00, 0x04, 0x00, 0x00, 0x04,
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, 0x00,
// Entry 80 - BF
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, 0x00,
// Entry C0 - FF
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, 0x04, 0x00, 0x00, 0x00,
0x00, 0x00, 0x04, 0x00, 0x05, 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, 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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Entry 100 - 13F // 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, 0x00, 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, 0x04, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x04, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x08, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00,
0x00, 0x00, 0x00, 0x01, 0x00, 0x05, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x05, 0x04,
0x00, 0x04, 0x00, 0x04, 0x04, 0x05, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x04, 0x04, 0x05, 0x00,
// Entry 140 - 17F // 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, 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: 382 bytes } // Size: 383 bytes
var paradigmLocales = [][3]uint16{ // 3 elements var paradigmLocales = [][3]uint16{ // 3 elements
0: [3]uint16{0x139, 0x0, 0x7b}, 0: [3]uint16{0x139, 0x0, 0x7c},
1: [3]uint16{0x13e, 0x0, 0x1f}, 1: [3]uint16{0x13e, 0x0, 0x1f},
2: [3]uint16{0x3c0, 0x41, 0xee}, 2: [3]uint16{0x3c0, 0x41, 0xef},
} // Size: 42 bytes } // Size: 42 bytes
type mutualIntelligibility struct { type mutualIntelligibility struct {
@ -249,30 +249,30 @@ var matchLang = []mutualIntelligibility{ // 113 elements
// matchScript holds pairs of scriptIDs where readers of one script // matchScript holds pairs of scriptIDs where readers of one script
// can typically also read the other. Each is associated with a confidence. // can typically also read the other. Each is associated with a confidence.
var matchScript = []scriptIntelligibility{ // 26 elements var matchScript = []scriptIntelligibility{ // 26 elements
0: {wantLang: 0x432, haveLang: 0x432, wantScript: 0x5a, haveScript: 0x20, distance: 0x5}, 0: {wantLang: 0x432, haveLang: 0x432, wantScript: 0x5b, haveScript: 0x20, distance: 0x5},
1: {wantLang: 0x432, haveLang: 0x432, wantScript: 0x20, haveScript: 0x5a, distance: 0x5}, 1: {wantLang: 0x432, haveLang: 0x432, wantScript: 0x20, haveScript: 0x5b, distance: 0x5},
2: {wantLang: 0x58, haveLang: 0x3e2, wantScript: 0x5a, haveScript: 0x20, distance: 0xa}, 2: {wantLang: 0x58, haveLang: 0x3e2, wantScript: 0x5b, haveScript: 0x20, distance: 0xa},
3: {wantLang: 0xa5, haveLang: 0x139, wantScript: 0xe, haveScript: 0x5a, distance: 0xa}, 3: {wantLang: 0xa5, haveLang: 0x139, wantScript: 0xe, haveScript: 0x5b, distance: 0xa},
4: {wantLang: 0x1d7, haveLang: 0x3e2, wantScript: 0x8, haveScript: 0x20, distance: 0xa}, 4: {wantLang: 0x1d7, haveLang: 0x3e2, wantScript: 0x8, haveScript: 0x20, distance: 0xa},
5: {wantLang: 0x210, haveLang: 0x139, wantScript: 0x2e, haveScript: 0x5a, distance: 0xa}, 5: {wantLang: 0x210, haveLang: 0x139, wantScript: 0x2e, haveScript: 0x5b, distance: 0xa},
6: {wantLang: 0x24a, haveLang: 0x139, wantScript: 0x4e, haveScript: 0x5a, distance: 0xa}, 6: {wantLang: 0x24a, haveLang: 0x139, wantScript: 0x4f, haveScript: 0x5b, distance: 0xa},
7: {wantLang: 0x251, haveLang: 0x139, wantScript: 0x52, haveScript: 0x5a, distance: 0xa}, 7: {wantLang: 0x251, haveLang: 0x139, wantScript: 0x53, haveScript: 0x5b, distance: 0xa},
8: {wantLang: 0x2b8, haveLang: 0x139, wantScript: 0x57, haveScript: 0x5a, distance: 0xa}, 8: {wantLang: 0x2b8, haveLang: 0x139, wantScript: 0x58, haveScript: 0x5b, distance: 0xa},
9: {wantLang: 0x304, haveLang: 0x139, wantScript: 0x6e, haveScript: 0x5a, distance: 0xa}, 9: {wantLang: 0x304, haveLang: 0x139, wantScript: 0x6f, haveScript: 0x5b, distance: 0xa},
10: {wantLang: 0x331, haveLang: 0x139, wantScript: 0x75, haveScript: 0x5a, distance: 0xa}, 10: {wantLang: 0x331, haveLang: 0x139, wantScript: 0x76, haveScript: 0x5b, distance: 0xa},
11: {wantLang: 0x351, haveLang: 0x139, wantScript: 0x22, haveScript: 0x5a, distance: 0xa}, 11: {wantLang: 0x351, haveLang: 0x139, wantScript: 0x22, haveScript: 0x5b, distance: 0xa},
12: {wantLang: 0x395, haveLang: 0x139, wantScript: 0x81, haveScript: 0x5a, distance: 0xa}, 12: {wantLang: 0x395, haveLang: 0x139, wantScript: 0x83, haveScript: 0x5b, distance: 0xa},
13: {wantLang: 0x39d, haveLang: 0x139, wantScript: 0x36, haveScript: 0x5a, distance: 0xa}, 13: {wantLang: 0x39d, haveLang: 0x139, wantScript: 0x36, haveScript: 0x5b, distance: 0xa},
14: {wantLang: 0x3be, haveLang: 0x139, wantScript: 0x5, haveScript: 0x5a, distance: 0xa}, 14: {wantLang: 0x3be, haveLang: 0x139, wantScript: 0x5, haveScript: 0x5b, distance: 0xa},
15: {wantLang: 0x3fa, haveLang: 0x139, wantScript: 0x5, haveScript: 0x5a, distance: 0xa}, 15: {wantLang: 0x3fa, haveLang: 0x139, wantScript: 0x5, haveScript: 0x5b, distance: 0xa},
16: {wantLang: 0x40c, haveLang: 0x139, wantScript: 0xd4, haveScript: 0x5a, distance: 0xa}, 16: {wantLang: 0x40c, haveLang: 0x139, wantScript: 0xd6, haveScript: 0x5b, distance: 0xa},
17: {wantLang: 0x450, haveLang: 0x139, wantScript: 0xe3, haveScript: 0x5a, distance: 0xa}, 17: {wantLang: 0x450, haveLang: 0x139, wantScript: 0xe6, haveScript: 0x5b, distance: 0xa},
18: {wantLang: 0x461, haveLang: 0x139, wantScript: 0xe6, haveScript: 0x5a, distance: 0xa}, 18: {wantLang: 0x461, haveLang: 0x139, wantScript: 0xe9, haveScript: 0x5b, distance: 0xa},
19: {wantLang: 0x46f, haveLang: 0x139, wantScript: 0x2c, haveScript: 0x5a, distance: 0xa}, 19: {wantLang: 0x46f, haveLang: 0x139, wantScript: 0x2c, haveScript: 0x5b, distance: 0xa},
20: {wantLang: 0x476, haveLang: 0x3e2, wantScript: 0x5a, haveScript: 0x20, distance: 0xa}, 20: {wantLang: 0x476, haveLang: 0x3e2, wantScript: 0x5b, haveScript: 0x20, distance: 0xa},
21: {wantLang: 0x4b4, haveLang: 0x139, wantScript: 0x5, haveScript: 0x5a, distance: 0xa}, 21: {wantLang: 0x4b4, haveLang: 0x139, wantScript: 0x5, haveScript: 0x5b, distance: 0xa},
22: {wantLang: 0x4bc, haveLang: 0x3e2, wantScript: 0x5a, haveScript: 0x20, distance: 0xa}, 22: {wantLang: 0x4bc, haveLang: 0x3e2, wantScript: 0x5b, haveScript: 0x20, distance: 0xa},
23: {wantLang: 0x512, haveLang: 0x139, wantScript: 0x3e, haveScript: 0x5a, distance: 0xa}, 23: {wantLang: 0x512, haveLang: 0x139, wantScript: 0x3e, haveScript: 0x5b, distance: 0xa},
24: {wantLang: 0x529, haveLang: 0x529, wantScript: 0x3b, haveScript: 0x3c, distance: 0xf}, 24: {wantLang: 0x529, haveLang: 0x529, wantScript: 0x3b, haveScript: 0x3c, distance: 0xf},
25: {wantLang: 0x529, haveLang: 0x529, wantScript: 0x3c, haveScript: 0x3b, distance: 0x13}, 25: {wantLang: 0x529, haveLang: 0x529, wantScript: 0x3c, haveScript: 0x3b, distance: 0x13},
} // Size: 232 bytes } // Size: 232 bytes
@ -295,4 +295,4 @@ var matchRegion = []regionIntelligibility{ // 15 elements
14: {lang: 0x529, script: 0x3c, group: 0x80, distance: 0x5}, 14: {lang: 0x529, script: 0x3c, group: 0x80, distance: 0x5},
} // Size: 114 bytes } // Size: 114 bytes
// Total table size 1472 bytes (1KiB); checksum: F86C669 // Total table size 1473 bytes (1KiB); checksum: 7BB90B5C

12
vendor/modules.txt vendored
View File

@ -1,7 +1,7 @@
# github.com/antchfx/xmlquery v1.3.12 # github.com/antchfx/xmlquery v1.4.1
## explicit; go 1.14 ## explicit; go 1.14
github.com/antchfx/xmlquery github.com/antchfx/xmlquery
# github.com/antchfx/xpath v1.2.1 # github.com/antchfx/xpath v1.3.1
## explicit; go 1.14 ## explicit; go 1.14
github.com/antchfx/xpath github.com/antchfx/xpath
# github.com/davecgh/go-spew v1.1.1 # github.com/davecgh/go-spew v1.1.1
@ -16,13 +16,13 @@ github.com/influxdata/influxdb1-client/pkg/escape
github.com/influxdata/influxdb1-client/v2 github.com/influxdata/influxdb1-client/v2
# github.com/stretchr/testify v1.7.0 # github.com/stretchr/testify v1.7.0
## explicit; go 1.13 ## explicit; go 1.13
# golang.org/x/net v0.1.0 # golang.org/x/net v0.28.0
## explicit; go 1.17 ## explicit; go 1.18
golang.org/x/net/html golang.org/x/net/html
golang.org/x/net/html/atom golang.org/x/net/html/atom
golang.org/x/net/html/charset golang.org/x/net/html/charset
# golang.org/x/text v0.4.0 # golang.org/x/text v0.17.0
## explicit; go 1.17 ## explicit; go 1.18
golang.org/x/text/encoding golang.org/x/text/encoding
golang.org/x/text/encoding/charmap golang.org/x/text/encoding/charmap
golang.org/x/text/encoding/htmlindex golang.org/x/text/encoding/htmlindex