diff --git a/.vscode/settings.json b/.vscode/settings.json index 9edaa27c..03776f63 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -47,6 +47,7 @@ "sarama", "Satosa", "SATOSAV", + "schac", "SDJWT", "sdktrace", "semconv", @@ -55,6 +56,7 @@ "skatteverket", "stretchr", "SUNET", + "SVGID", "swaggo", "timestamppb", "VCTM" diff --git a/Makefile b/Makefile index 30f17b0e..1cc57bcf 100755 --- a/Makefile +++ b/Makefile @@ -74,7 +74,7 @@ build-ui: $(info Building ui) CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -v -o ./bin/$(NAME)_ui ${LDFLAGS} ./cmd/ui/main.go -docker-build: docker-build-verifier docker-build-registry docker-build-persistent docker-build-mockas docker-build-apigw docker-build-issuer docker-build-ui +docker-build: docker-build-verifier docker-build-registry docker-build-persistent docker-build-mockas docker-build-apigw docker-build-issuer docker-build-ui docker-build-portal docker-build-goland-debug: docker-build-verifier docker-build-registry docker-build-persistent docker-build-mockas docker-build-apigw docker-build-issuer docker-build-ui-goland-debug @@ -154,7 +154,7 @@ docker-push-portal: $(info Pushing docker images) docker push $(DOCKER_TAG_PORTAL) -docker-push: docker-push-verifier docker-push-registry docker-push-persistent docker-push-apigw docker-push-issuer docker-push-ui docker-push-mockas +docker-push: docker-push-verifier docker-push-registry docker-push-persistent docker-push-apigw docker-push-issuer docker-push-ui docker-push-mockas docker-push-portal $(info Pushing docker images) docker-tag-apigw: diff --git a/cmd/portal/main.go b/cmd/portal/main.go index b1fb8b76..49d1ff83 100644 --- a/cmd/portal/main.go +++ b/cmd/portal/main.go @@ -38,7 +38,7 @@ func main() { // main function log mainLog := log.New("main") - tracer, err := trace.NewForTesting(ctx, serviceName, log) + tracer, err := trace.New(ctx, cfg, serviceName, log) if err != nil { panic(err) } diff --git a/config.yaml b/config.yaml index 636bb52f..1aa53ffb 100644 --- a/config.yaml +++ b/config.yaml @@ -8,9 +8,9 @@ common: type: jaeger #timeout: 10 qr: - base_url: "http://vc_dev_apigw:8080" recovery_level: 2 size: 256 + issuing_base_url: https://satosa-test-1.sunet.se kafka: enabled: false brokers: @@ -63,6 +63,7 @@ issuer: enable_not_before: true valid_duration: 3600 verifiable_credential_type: "https://credential.sunet.se/identity_credential" + static_host: "http://vc_dev_portal:8080/statics" verifier: api_server: diff --git a/docker-compose.yaml b/docker-compose.yaml index 9937e102..a3dd1cb8 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -114,20 +114,20 @@ services: environment: - "VC_CONFIG_YAML=config.yaml" -# mockas2: -# container_name: "vc_dev_mockas2" -# image: docker.sunet.se/dc4eu/mockas:latest -# restart: always -# volumes: -# - ./config.yaml:/config.yaml:ro -# depends_on: -# - kafka0 -# - kafka1 -# networks: -# vc-dev-net: -# ipv4_address: 172.16.50.23 -# environment: -# - "VC_CONFIG_YAML=config.yaml" + # mockas2: + # container_name: "vc_dev_mockas2" + # image: docker.sunet.se/dc4eu/mockas:latest + # restart: always + # volumes: + # - ./config.yaml:/config.yaml:ro + # depends_on: + # - kafka0 + # - kafka1 + # networks: + # vc-dev-net: + # ipv4_address: 172.16.50.23 + # environment: + # - "VC_CONFIG_YAML=config.yaml" mongo: image: mongo:4.0.10 @@ -141,6 +141,19 @@ services: vc-dev-net: ipv4_address: 172.16.50.20 + portal: + image: docker.sunet.se/dc4eu/portal:latest + container_name: "vc_dev_portal" + restart: always + volumes: + - ./config.yaml:/config.yaml:ro + - ./statics:/statics:ro + networks: + vc-dev-net: + ipv4_address: 172.16.50.24 + environment: + - "VC_CONFIG_YAML=config.yaml" + jaeger: image: jaegertracing/all-in-one:latest container_name: vc_dev_jaeger @@ -254,7 +267,7 @@ networks: - subnet: 172.16.50.0/24 volumes: mongo_data: -# kraft0-data: -# driver: local -# kraft1-data: -# driver: local + # kraft0-data: + # driver: local + # kraft1-data: + # driver: local diff --git a/go.mod b/go.mod index faeac0b9..d621f923 100644 --- a/go.mod +++ b/go.mod @@ -21,7 +21,6 @@ require ( github.com/lestrrat-go/jwx v1.2.30 github.com/lithammer/shortuuid/v4 v4.0.0 github.com/moogar0880/problems v0.1.1 - github.com/pbnjay/grate v0.0.0-20231006022435-3f8e65d74a14 github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e github.com/stretchr/testify v1.9.0 github.com/swaggo/files v1.0.1 diff --git a/go.sum b/go.sum index 3283eeb8..9283d974 100644 --- a/go.sum +++ b/go.sum @@ -175,8 +175,6 @@ github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8 github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= github.com/moogar0880/problems v0.1.1 h1:bktLhq8NDG/czU2ZziYNigBFksx13RaYe5AVdNmHDT4= github.com/moogar0880/problems v0.1.1/go.mod h1:5Dxrk2sD7BfBAgnOzQ1yaTiuCYdGPUh49L8Vhfky62c= -github.com/pbnjay/grate v0.0.0-20231006022435-3f8e65d74a14 h1:ZfXdW7GIVZT3Z9oejLJ+GHrrQv/ezU2Bwqn0BF37s4g= -github.com/pbnjay/grate v0.0.0-20231006022435-3f8e65d74a14/go.mod h1:VaZEKQrYbYr2untVA/EFNdC6hM7GyARRNM+k4+5CmA0= github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= diff --git a/internal/apigw/apiv1/handlers_datastore.go b/internal/apigw/apiv1/handlers_datastore.go index e75b5bb6..15df625e 100644 --- a/internal/apigw/apiv1/handlers_datastore.go +++ b/internal/apigw/apiv1/handlers_datastore.go @@ -32,7 +32,7 @@ type UploadRequest struct { // @Param req body UploadRequest true " " // @Router /upload [post] func (c *Client) Upload(ctx context.Context, req *UploadRequest) error { - qr, err := req.Meta.QRGenerator(ctx, c.cfg.Common.QR.BaseURL, c.cfg.Common.QR.RecoveryLevel, c.cfg.Common.QR.Size) + qr, err := req.Meta.QRGenerator(ctx, c.cfg.Common.QR.IssuingBaseURL, c.cfg.Common.QR.RecoveryLevel, c.cfg.Common.QR.Size) if err != nil { c.log.Debug("QR code generation failed", "error", err) return err diff --git a/internal/issuer/apiv1/echic_metadata.go b/internal/issuer/apiv1/echic_metadata.go index a14f99de..e89900de 100644 --- a/internal/issuer/apiv1/echic_metadata.go +++ b/internal/issuer/apiv1/echic_metadata.go @@ -24,7 +24,7 @@ func (c *ehicClient) MetadataClaim(vct string) ([]string, error) { Rendering: Rendering{ Simple: SimpleRendering{ Logo: Logo{ - URI: fmt.Sprintf("%s/ehicCard.png", c.client.cfg.Issuer.JWTAttribute.Issuer), + URI: fmt.Sprintf("%s/ehicCard.png", c.client.cfg.Issuer.JWTAttribute.StaticHost), URIIntegrity: "sha256-94445b2ca72e9155260c8b4879112df7677e8b3df3dcee9b970b40534e26d4ab", AltText: "EHIC Card", }, @@ -33,7 +33,7 @@ func (c *ehicClient) MetadataClaim(vct string) ([]string, error) { }, SVGTemplates: []SVGTemplates{ { - URI: fmt.Sprintf("%s/ehicTemplate.png", c.client.cfg.Issuer.JWTAttribute.Issuer), + URI: fmt.Sprintf("%s/ehicTemplate.png", c.client.cfg.Issuer.JWTAttribute.StaticHost), URLIntegrity: "", Properties: SVGTemplateProperties{}, }, diff --git a/internal/issuer/apiv1/jwk_test.go b/internal/issuer/apiv1/jwk_test.go index 40b6cb70..f1134e03 100644 --- a/internal/issuer/apiv1/jwk_test.go +++ b/internal/issuer/apiv1/jwk_test.go @@ -54,7 +54,6 @@ func mockClient(t *testing.T) *Client { Type: "jaeger", Timeout: 0, }, - Queues: model.Queues{}, KeyValue: model.KeyValue{}, QR: model.QRCfg{}, Kafka: model.Kafka{}, diff --git a/internal/issuer/apiv1/pda1_metadata.go b/internal/issuer/apiv1/pda1_metadata.go index ebe73254..99665657 100644 --- a/internal/issuer/apiv1/pda1_metadata.go +++ b/internal/issuer/apiv1/pda1_metadata.go @@ -75,7 +75,7 @@ func (c *pda1Client) MetadataClaim(vct string) ([]string, error) { Rendering: Rendering{ Simple: SimpleRendering{ Logo: Logo{ - URI: fmt.Sprintf("%s/pda1.png", c.client.cfg.Issuer.JWTAttribute.Issuer), + URI: fmt.Sprintf("%s/pda1.png", c.client.cfg.Issuer.JWTAttribute.StaticHost), URIIntegrity: "sha256-94445b2ca72e9155260c8b4879112df7677e8b3df3dcee9b970b40534e26d4ab", AltText: "PDA1 Card", }, @@ -84,7 +84,7 @@ func (c *pda1Client) MetadataClaim(vct string) ([]string, error) { }, SVGTemplates: []SVGTemplates{ { - URI: fmt.Sprintf("%s/pda1Template.svg", c.client.cfg.Issuer.JWTAttribute.Issuer), + URI: fmt.Sprintf("%s/pda1Template.svg", c.client.cfg.Issuer.JWTAttribute.StaticHost), URLIntegrity: "", Properties: SVGTemplateProperties{}, }, diff --git a/internal/mockas/paris_users/pda1_users.csv b/internal/mockas/paris_users/pda1_users.csv new file mode 100644 index 00000000..e69de29b diff --git a/internal/mockas/paris_users/xls.go b/internal/mockas/paris_users/xls.go index e7ee3223..c577f7d5 100644 --- a/internal/mockas/paris_users/xls.go +++ b/internal/mockas/paris_users/xls.go @@ -1,13 +1,14 @@ package parisusers import ( + "encoding/csv" "fmt" + "os" "strings" "vc/pkg/ehic" "vc/pkg/model" "vc/pkg/pda1" - "github.com/lithammer/shortuuid/v4" "github.com/xuri/excelize/v2" ) @@ -29,9 +30,9 @@ func makePID(fs *excelize.File) map[string]*model.CompleteDocument { DocumentDataVersion: "1.0.0", Identities: []model.Identity{ { - AuthenticSourcePersonID: "", + AuthenticSourcePersonID: fmt.Sprintf("authentic_source_person_id_%s", row[0]), Schema: &model.IdentitySchema{ - Name: "SE", + Name: "FR", Version: "", }, FamilyName: row[6], @@ -76,6 +77,7 @@ func EHIC(sourceFilePath string) []model.CompleteDocument { SocialSecurityPin := row[4] startDate := row[5] endDate := row[6] + CardNumber := row[7] InstitutionID := row[8] InstitutionName := row[9] @@ -115,10 +117,10 @@ func EHIC(sourceFilePath string) []model.CompleteDocument { AuthenticSource: row[2], DocumentVersion: "1.0.0", DocumentType: "EHIC", - DocumentID: fmt.Sprintf("document_id_%s", shortuuid.New()), + DocumentID: fmt.Sprintf("document_id_ehic_%s", row[0]), RealData: false, Collect: &model.Collect{ - ID: fmt.Sprintf("collect_id_%s", shortuuid.New()), + ID: fmt.Sprintf("collect_id_ehic_%s", row[0]), ValidUntil: 0, }, Revocation: &model.Revocation{}, @@ -127,6 +129,19 @@ func EHIC(sourceFilePath string) []model.CompleteDocument { DocumentDataValidationRef: "", } + user.DocumentDisplay = &model.DocumentDisplay{ + Version: "1.0.0", + Type: "secure", + DescriptionStructured: map[string]any{ + "en": map[string]any{ + "documentType": "EHIC", + }, + "sv": map[string]any{ + "documentType": "EHIC", + }, + }, + } + } list := []model.CompleteDocument{} @@ -235,13 +250,13 @@ func PDA1(sourceFilePath string) []model.CompleteDocument { } user.Meta = &model.MetaData{ - AuthenticSource: row[2], + AuthenticSource: row[3], DocumentVersion: "1.0.0", DocumentType: "PDA1", - DocumentID: fmt.Sprintf("document_id_%s", shortuuid.New()), + DocumentID: fmt.Sprintf("document_id_pda1_%s", row[0]), RealData: false, Collect: &model.Collect{ - ID: fmt.Sprintf("collect_id_%s", shortuuid.New()), + ID: fmt.Sprintf("collect_id_pda1_%s", row[0]), ValidUntil: 0, }, Revocation: &model.Revocation{}, @@ -250,6 +265,19 @@ func PDA1(sourceFilePath string) []model.CompleteDocument { DocumentDataValidationRef: "", } + user.DocumentDisplay = &model.DocumentDisplay{ + Version: "1.0.0", + Type: "secure", + DescriptionStructured: map[string]any{ + "en": map[string]any{ + "documentType": "PDA1", + }, + "sv": map[string]any{ + "documentType": "PDA1", + }, + }, + } + } list := []model.CompleteDocument{} @@ -264,3 +292,31 @@ func PDA1(sourceFilePath string) []model.CompleteDocument { func Make(filePath string) []model.CompleteDocument { return append(EHIC(filePath), PDA1(filePath)...) } + +// CSV converts a list of CompleteDocument to CSV and writes it to a file +func CSV(docs model.CompleteDocuments) ([]string, [][]string, error) { + fs, err := os.Create("pda1_users.csv") + if err != nil { + return nil, nil, err + } + defer fs.Close() + + return docs.CSV() + +} + +func saveCSVToDisk(records [][]string, filePath string) error { + f, err := os.Create(filePath) + defer f.Close() + if err != nil { + return err + } + + w := csv.NewWriter(f) + defer w.Flush() + + if err := w.WriteAll(records); err != nil { + return err + } + return nil +} diff --git a/internal/mockas/paris_users/xls_test.go b/internal/mockas/paris_users/xls_test.go index dde819bb..61742717 100644 --- a/internal/mockas/paris_users/xls_test.go +++ b/internal/mockas/paris_users/xls_test.go @@ -2,6 +2,7 @@ package parisusers import ( "encoding/json" + "fmt" "testing" "github.com/stretchr/testify/assert" @@ -29,3 +30,22 @@ func TestMake(t *testing.T) { assert.Len(t, storage, 160) } + +func TestCSV(t *testing.T) { + storage := Make("testdata/users_paris.xlsx") + csv, _, err := CSV(storage) + assert.NoError(t, err) + + fmt.Printf("\nauthentic_source_person_id,given_name,family_name,birth_date,identity schema name,authentic_source,collect_id,document_type\n") + fmt.Printf("%v", csv) + +} + +func TestSaveCSVToDisk(t *testing.T) { + storage := Make("testdata/users_paris.xlsx") + _, records, err := CSV(storage) + assert.NoError(t, err) + + err = saveCSVToDisk(records, "../../../users_paris.csv") + assert.NoError(t, err) +} diff --git a/internal/portal/apiv1/handlers.go b/internal/portal/apiv1/handlers.go new file mode 100644 index 00000000..b0dbe665 --- /dev/null +++ b/internal/portal/apiv1/handlers.go @@ -0,0 +1,16 @@ +package apiv1 + +import ( + "context" + "vc/internal/gen/status/apiv1_status" + "vc/pkg/model" +) + +// Status return status for each ladok instance +func (c *Client) Status(ctx context.Context, req *apiv1_status.StatusRequest) (*apiv1_status.StatusReply, error) { + probes := model.Probes{} + + status := probes.Check("registry") + + return status, nil +} diff --git a/internal/portal/httpserver/api.go b/internal/portal/httpserver/api.go index 6d713cfd..c40c7819 100644 --- a/internal/portal/httpserver/api.go +++ b/internal/portal/httpserver/api.go @@ -1,6 +1,11 @@ package httpserver +import ( + "context" + "vc/internal/gen/status/apiv1_status" +) + // Apiv1 interface type Apiv1 interface { - // leaved empty for portal implementation later + Status(ctx context.Context, req *apiv1_status.StatusRequest) (*apiv1_status.StatusReply, error) } diff --git a/internal/portal/httpserver/endpoints.go b/internal/portal/httpserver/endpoints.go new file mode 100644 index 00000000..fe289202 --- /dev/null +++ b/internal/portal/httpserver/endpoints.go @@ -0,0 +1,17 @@ +package httpserver + +import ( + "context" + "vc/internal/gen/status/apiv1_status" + + "github.com/gin-gonic/gin" +) + +func (s *Service) endpointHealth(ctx context.Context, c *gin.Context) (any, error) { + request := &apiv1_status.StatusRequest{} + reply, err := s.apiv1.Status(ctx, request) + if err != nil { + return nil, err + } + return reply, nil +} diff --git a/internal/portal/httpserver/service.go b/internal/portal/httpserver/service.go index cceade11..14fc6809 100644 --- a/internal/portal/httpserver/service.go +++ b/internal/portal/httpserver/service.go @@ -46,7 +46,9 @@ func New(ctx context.Context, cfg *model.Cfg, apiv1 *apiv1.Client, tracer *trace } // statics if mainly for images/logos in vctm attribute - rgRoot.Static("statics", "./statics") + rgRoot.Static("/statics", "/statics") + + s.httpHelpers.Server.RegEndpoint(ctx, rgRoot, http.MethodGet, "health", s.endpointHealth) // Run http server go func() { diff --git a/internal/ui/static/ui.js b/internal/ui/static/ui.js index 739fcca2..b3988bb4 100644 --- a/internal/ui/static/ui.js +++ b/internal/ui/static/ui.js @@ -668,7 +668,7 @@ const addCredentialFormArticleToContainer = () => { const familyNameElement = createInputElement('family name', '', 'text'); const givenNameElement = createInputElement('given name', '', 'text'); const birthdateElement = createInputElement('birth date', '', 'text'); - const schemaNameElement = createInputElement('identity schema name', 'SE'); + const schemaNameElement = createInputElement('identity schema name', 'FR'); const documentTypeElement = createInputElement('document type (EHIC/PDA1)', 'EHIC'); const credentialTypeElement = createInputElement('credential type', 'vc+sd-jwt'); const authenticSourceElement = createInputElement('authentic source', 'SUNET'); diff --git a/pkg/model/config.go b/pkg/model/config.go index 887aec42..b13588c6 100644 --- a/pkg/model/config.go +++ b/pkg/model/config.go @@ -51,7 +51,6 @@ type Common struct { Log Log `yaml:"log"` Mongo Mongo `yaml:"mongo" validate:"omitempty"` Tracing OTEL `yaml:"tracing" validate:"required"` - Queues Queues `yaml:"queues" validate:"omitempty"` KeyValue KeyValue `yaml:"key_value" validate:"omitempty"` QR QRCfg `yaml:"qr" validate:"omitempty"` Kafka Kafka `yaml:"kafka" validate:"omitempty"` @@ -77,27 +76,10 @@ type PDF struct { // QRCfg holds the qr configuration type QRCfg struct { - BaseURL string `yaml:"base_url" validate:"required"` - RecoveryLevel int `yaml:"recovery_level" validate:"required,min=0,max=3"` - Size int `yaml:"size" validate:"required"` -} - -// Queues have the queue configuration -type Queues struct { - SimpleQueue struct { - VCPersistentSave struct { - Name string `yaml:"name" validate:"required"` - } `yaml:"vc_persistent_save" validate:"required"` - VCPersistentGet struct { - Name string `yaml:"name" validate:"required"` - } `yaml:"vc_persistent_get" validate:"required"` - VCPersistentDelete struct { - Name string `yaml:"name" validate:"required"` - } `yaml:"vc_persistent_delete" validate:"required"` - VCPersistentReplace struct { - Name string `yaml:"name" validate:"required"` - } `yaml:"vc_persistent_replace" validate:"required"` - } `yaml:"simple_queue" validate:"required"` + BaseURL string `yaml:"base_url" validate:"required"` + RecoveryLevel int `yaml:"recovery_level" validate:"required,min=0,max=3"` + Size int `yaml:"size" validate:"required"` + IssuingBaseURL string `yaml:"issuing_base_url" validate:"required"` } // JWTAttribute holds the jwt attribute configuration. @@ -106,6 +88,9 @@ type JWTAttribute struct { // Issuer of the token example: https://issuer.sunet.se Issuer string `yaml:"issuer" validate:"required"` + // StaticHost is the static host of the issuer, expose static files, like pictures + StaticHost string `yaml:"static_host" validate:"required"` + // EnableNotBefore states the time not before which the token is valid EnableNotBefore bool `yaml:"enable_not_before"` diff --git a/pkg/model/generic.go b/pkg/model/generic.go index 848f7f27..67d46dc5 100644 --- a/pkg/model/generic.go +++ b/pkg/model/generic.go @@ -4,8 +4,10 @@ import ( "context" "encoding/base64" "encoding/json" + "errors" "fmt" "net/url" + "strings" "github.com/skip2/go-qrcode" ) @@ -23,6 +25,9 @@ type CompleteDocument struct { QR *QR `json:"qr,omitempty" bson:"qr"` } +// CompleteDocuments is a array of CompleteDocument +type CompleteDocuments []CompleteDocument + // DocumentList is a generic type for document list type DocumentList struct { Meta *MetaData `json:"meta,omitempty" bson:"meta" validate:"required"` @@ -81,7 +86,6 @@ func (m *MetaData) QRGenerator(ctx context.Context, issuerBaseURL string, recove if err != nil { return nil, err } - fmt.Println("URL", credentialOfferURL) qrBase64 := base64.StdEncoding.EncodeToString(qrPNG) @@ -93,6 +97,57 @@ func (m *MetaData) QRGenerator(ctx context.Context, issuerBaseURL string, recove return qr, nil } +// CSV returns the document as a CSV +func (c *CompleteDocument) CSV() (string, []string, error) { + if len(c.Identities) == 0 { + return "", nil, errors.New("no identities found") + } + + if c.Meta == nil { + return "", nil, errors.New("no metadata found") + } + + qr, err := c.Meta.QRGenerator(context.Background(), "https://satosa-test-1.sunet.se", 2, 256) + if err != nil { + return "", nil, err + } + attributes := []string{ + c.Identities[0].AuthenticSourcePersonID, + c.Identities[0].GivenName, + c.Identities[0].FamilyName, + c.Identities[0].BirthDate, + c.Identities[0].Schema.Name, + c.Meta.AuthenticSource, + c.Meta.Collect.ID, + c.Meta.DocumentType, + c.Meta.DocumentID, + qr.CredentialOfferURL, + } + csv := strings.Join(attributes, ",") + + return csv, attributes, nil +} + +// CSV return CompleteDocuments as a CSV, string array +func (c *CompleteDocuments) CSV() ([]string, [][]string, error) { + if len(*c) == 0 { + return nil, nil, errors.New("no documents found") + } + + var csvResult []string + var csvRaw [][]string + for _, doc := range *c { + csvRow, raw, err := doc.CSV() + if err != nil { + return nil, nil, err + } + csvRaw = append(csvRaw, raw) + csvResult = append(csvResult, fmt.Sprintf("%v\n", csvRow)) + } + + return csvResult, csvRaw, nil +} + // CredentialOffer https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html 4.1.1 Credential Offer Parameters type CredentialOffer struct { CredentialIssuer string `json:"credential_issuer"` diff --git a/pkg/model/generic_test.go b/pkg/model/generic_test.go index 10cc40ef..62c3c736 100644 --- a/pkg/model/generic_test.go +++ b/pkg/model/generic_test.go @@ -2,7 +2,9 @@ package model import ( "context" + "encoding/json" "fmt" + "net/url" "testing" "github.com/stretchr/testify/assert" @@ -55,3 +57,124 @@ func TestQRGenerator(t *testing.T) { }) } } + +func TestDecodeCredentialOffer(t *testing.T) { + tts := []struct { + name string + have string + want map[string]any + }{ + { + name: "working from greece wallet", + have: "https://wallet.dc4eu.eu/cb?credential_offer=%7B%0A%20%20%22credential_issuer%22%3A%20%22https%3A%2F%2Fsatosa-test-1.sunet.se%22%2C%0A%20%20%22credential_configuration_ids%22%3A%20%5B%0A%20%20%20%20%22EHICCredential%22%0A%20%20%5D%2C%0A%20%20%22grants%22%3A%20%7B%0A%20%20%20%20%22authorization_code%22%3A%20%7B%0A%20%20%20%20%20%20%22issuer_state%22%3A%20%22authentic_source%3Dauthentic_source_se%26document_type%3DEHIC%26collect_id%3Dcollect_id_10%22%0A%20%20%20%20%7D%0A%20%20%7D%0A%7D", + want: map[string]any{ + "credential_issuer": "https://satosa-test-1.sunet.se", + "credential_configuration_ids": []string{ + "EHICCredential", + }, + "grants": map[string]any{ + "authorization_code": map[string]any{ + "issuer_state": "authentic_source=authentic_source_se&document_type=EHIC&collect_id=collect_id_10", + }, + }, + }, + }, + { + name: "not working from credential constructorn", + have: "https://wallet.dc4eu.eu/cb?credential_offer=%7B%22credential_issuer%22%3A%22https%3A%2F%2Fsatosa-test-1.sunet.se%22%2C%22credential_configuration_ids%22%3A%5B%22EHICCredential%22%5D%2C%22grants%22%3A%7B%22authorization_code%22%3A%7B%22issuer_state%22%3A%22collect_id%3Dcollect_id_ehic_86%5Cu0026document_type%3DEHIC%5Cu0026authentic_source%3DEHIC%3A00001%22%7D%7D%7D", + want: map[string]any{ + "credential_issuer": "https://satosa-test-1.sunet.se", + "credential_configuration_ids": []string{ + "EHICCredential", + }, + "grants": map[string]any{ + "authorization_code": map[string]any{ + "issuer_state": "collect_id=collect_id_ehic_86\u0026document_type=EHIC\u0026authentic_source=EHIC:00001", + }, + }, + }, + }, + } + + for _, tt := range tts { + t.Run(tt.name, func(t *testing.T) { + urlObject, err := url.Parse(tt.have) + assert.NoError(t, err) + + values, err := url.ParseQuery(urlObject.RawQuery) + assert.NoError(t, err) + + jsonWant, err := json.MarshalIndent(tt.want, "", " ") + assert.NoError(t, err) + + assert.JSONEq(t, string(jsonWant), values.Get("credential_offer")) + + fmt.Println("decoded", values.Get("credential_offer")) + }) + } +} + +func TestCSV(t *testing.T) { + tts := []struct { + name string + have CompleteDocument + wantCSV string + wantArray []string + }{ + { + name: "OK", + have: CompleteDocument{ + Identities: []Identity{ + { + GivenName: "John", + FamilyName: "Doe", + BirthDate: "1970-01-01", + AuthenticSourcePersonID: "test_authentic_source_person_id", + Schema: &IdentitySchema{ + Name: "schema_identity_name", + }, + }, + }, + Meta: &MetaData{ + AuthenticSource: "test_authentic_source", + DocumentVersion: "", + DocumentType: "PDA1", + DocumentID: "document_id_1", + RealData: false, + Collect: &Collect{ + ID: "collect_id_1", + ValidUntil: 0, + }, + Revocation: &Revocation{}, + CredentialValidFrom: 0, + CredentialValidTo: 0, + DocumentDataValidationRef: "", + }, + }, + wantCSV: "test_authentic_source_person_id,John,Doe,1970-01-01,schema_identity_name,test_authentic_source,collect_id_1,PDA1,document_id_1,https://wallet.dc4eu.eu/cb?credential_offer=%7B%22credential_issuer%22%3A%22https%3A%2F%2Fwallet.dc4eu.eu%22%2C%22credential_configuration_ids%22%3A%5B%22PDA1Credential%22%5D%2C%22grants%22%3A%7B%22authorization_code%22%3A%7B%22issuer_state%22%3A%22collect_id%3Dcollect_id_1%5Cu0026document_type%3DPDA1%5Cu0026authentic_source%3Dtest_authentic_source%22%7D%7D%7D", + wantArray: []string{ + "test_authentic_source_person_id", + "John", + "Doe", + "1970-01-01", + "schema_identity_name", + "test_authentic_source", + "collect_id_1", + "PDA1", + "document_id_1", + "https://wallet.dc4eu.eu/cb?credential_offer=%7B%22credential_issuer%22%3A%22https%3A%2F%2Fwallet.dc4eu.eu%22%2C%22credential_configuration_ids%22%3A%5B%22PDA1Credential%22%5D%2C%22grants%22%3A%7B%22authorization_code%22%3A%7B%22issuer_state%22%3A%22collect_id%3Dcollect_id_1%5Cu0026document_type%3DPDA1%5Cu0026authentic_source%3Dtest_authentic_source%22%7D%7D%7D", + }, + }, + } + + for _, tt := range tts { + t.Run(tt.name, func(t *testing.T) { + csvString, csvArray, err := tt.have.CSV() + assert.NoError(t, err) + + assert.Equal(t, tt.wantCSV, csvString) + + assert.Equal(t, tt.wantArray, csvArray) + }) + } +} diff --git a/vendor/github.com/pbnjay/grate/.gitignore b/vendor/github.com/pbnjay/grate/.gitignore deleted file mode 100644 index 0f4ba397..00000000 --- a/vendor/github.com/pbnjay/grate/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -cmd/grate2tsv/results -testdata - -*.pprof -*.pdf diff --git a/vendor/github.com/pbnjay/grate/LICENSE b/vendor/github.com/pbnjay/grate/LICENSE deleted file mode 100644 index 90c93bee..00000000 --- a/vendor/github.com/pbnjay/grate/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2021 Jeremy Jay - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file diff --git a/vendor/github.com/pbnjay/grate/README.md b/vendor/github.com/pbnjay/grate/README.md deleted file mode 100644 index fdc14635..00000000 --- a/vendor/github.com/pbnjay/grate/README.md +++ /dev/null @@ -1,45 +0,0 @@ -# grate - -A Go native tabular data extraction package. Currently supports `.xls`, `.xlsx`, `.csv`, `.tsv` formats. - -# Why? - -Grate focuses on speed and stability first, and makes no attempt to parse charts, figures, or other content types that may be present embedded within the input files. It tries to perform as few allocations as possible and errs on the side of caution. - -There are certainly still some bugs and edge cases, but we have run it successfully on a set of 400k `.xls` and `.xlsx` files to catch many bugs and error conditions. Please file an issue with any feedback and additional problem files. - -# Usage - -Grate provides a simple standard interface for all supported filetypes, allowing access to both named worksheets in spreadsheets and single tables in plaintext formats. - -```go -package main - -import ( - "fmt" - "os" - "strings" - - "github.com/pbnjay/grate" - _ "github.com/pbnjay/grate/simple" // tsv and csv support - _ "github.com/pbnjay/grate/xls" - _ "github.com/pbnjay/grate/xlsx" -) - -func main() { - wb, _ := grate.Open(os.Args[1]) // open the file - sheets, _ := wb.List() // list available sheets - for _, s := range sheets { // enumerate each sheet name - sheet, _ := wb.Get(s) // open the sheet - for sheet.Next() { // enumerate each row of data - row := sheet.Strings() // get the row's content as []string - fmt.Println(strings.Join(row, "\t")) - } - } - wb.Close() -} -``` - -# License - -All source code is licensed under the [MIT License](https://raw.github.com/pbnjay/grate/master/LICENSE). diff --git a/vendor/github.com/pbnjay/grate/commonxl/cell.go b/vendor/github.com/pbnjay/grate/commonxl/cell.go deleted file mode 100644 index 9b1c5b28..00000000 --- a/vendor/github.com/pbnjay/grate/commonxl/cell.go +++ /dev/null @@ -1,463 +0,0 @@ -package commonxl - -import ( - "fmt" - "math" - "net/url" - "strconv" - "time" - "unicode/utf16" -) - -// CellType annotates the type of data extracted in the cell. -type CellType uint16 - -// CellType annotations for various cell value types. -const ( - BlankCell CellType = iota - IntegerCell - FloatCell - StringCell - BooleanCell - DateCell - - HyperlinkStringCell // internal type to separate URLs - StaticCell // placeholder, internal use only -) - -// String returns a string description of the cell data type. -func (c CellType) String() string { - switch c { - case BlankCell: - return "blank" - case IntegerCell: - return "integer" - case FloatCell: - return "float" - case BooleanCell: - return "boolean" - case DateCell: - return "date" - case HyperlinkStringCell: - return "hyperlink" - case StaticCell: - return "static" - default: // StringCell, StaticCell - return "string" - } -} - -// Cell represents a single cell value. -type Cell []interface{} - -// internally, it is a slice sized 2 or 3 -// [Value, CellType] or [Value, CellType, FormatNumber] -// where FormatNumber is a uint16 if not 0 - -// Value returns the contents as a generic interface{}. -func (c Cell) Value() interface{} { - if len(c) == 0 { - return "" - } - return c[0] -} - -// SetURL adds a URL hyperlink to the cell. -func (c *Cell) SetURL(link string) { - (*c)[1] = HyperlinkStringCell - if len(*c) == 2 { - *c = append(*c, uint16(0), link) - } else { // len = 3 already - *c = append(*c, link) - } -} - -// URL returns the parsed URL when a cell contains a hyperlink. -func (c Cell) URL() (*url.URL, bool) { - if c.Type() == HyperlinkStringCell && len(c) >= 4 { - u, err := url.Parse(c[3].(string)) - return u, err == nil - } - return nil, false -} - -// Type returns the CellType of the value. -func (c Cell) Type() CellType { - if len(c) < 2 { - return BlankCell - } - return c[1].(CellType) -} - -// FormatNo returns the NumberFormat used for display. -func (c Cell) FormatNo() uint16 { - if len(c) == 3 { - return c[2].(uint16) - } - return 0 -} - -// Clone returns the new copy of this Cell. -func (c Cell) Clone() Cell { - c2 := make([]interface{}, len(c)) - for i, x := range c { - c2[i] = x - } - return c2 -} - -/////// - -var boolStrings = map[string]bool{ - "yes": true, "true": true, "t": true, "y": true, "1": true, "on": true, - "no": false, "false": false, "f": false, "n": false, "0": false, "off": false, - "YES": true, "TRUE": true, "T": true, "Y": true, "1.0": true, "ON": true, - "NO": false, "FALSE": false, "F": false, "N": false, "0.0": false, "OFF": false, -} - -// NewCellWithType creates a new cell value with the given type, coercing as necessary. -func NewCellWithType(value interface{}, t CellType, f *Formatter) Cell { - c := NewCell(value) - if c[1] == t { - // fast path if it was already typed correctly - return c - } - - if c[1] == BooleanCell { - if t == IntegerCell { - if c[0].(bool) { - c[0] = int64(1) - } else { - c[0] = int64(0) - } - c[1] = IntegerCell - } else if t == FloatCell { - if c[0].(bool) { - c[0] = float64(1.0) - } else { - c[0] = float64(0.0) - } - c[1] = FloatCell - } else if t == StringCell { - if c[0].(bool) { - c[0] = "TRUE" - } else { - c[0] = "FALSE" - } - c[1] = FloatCell - } - } - - if c[1] == FloatCell { - if t == IntegerCell { - c[0] = int64(c[0].(float64)) - c[1] = IntegerCell - } else if t == BooleanCell { - c[0] = c[0].(float64) != 0.0 - c[1] = BooleanCell - } - } - if c[1] == IntegerCell { - if t == FloatCell { - c[0] = float64(c[0].(int64)) - c[1] = FloatCell - } else if t == BooleanCell { - c[0] = c[0].(int64) != 0 - c[1] = BooleanCell - } - } - if c[1] == StringCell { - if t == IntegerCell { - x, _ := strconv.ParseInt(c[0].(string), 10, 64) - c[0] = x - c[1] = IntegerCell - } else if t == FloatCell { - x, _ := strconv.ParseFloat(c[0].(string), 64) - c[0] = x - c[1] = FloatCell - } else if t == BooleanCell { - c[0] = boolStrings[c[0].(string)] - c[1] = BooleanCell - } - } - if t == StringCell { - c[0] = fmt.Sprint(c[0]) - c[1] = StringCell - } - if t == DateCell { - if c[1] == FloatCell { - c[0] = f.ConvertToDate(c[0].(float64)) - } else if c[1] == IntegerCell { - c[0] = f.ConvertToDate(float64(c[0].(int64))) - } - c[1] = DateCell - } - return c -} - -// NewCell creates a new cell value from any builtin type. -func NewCell(value interface{}) Cell { - c := make([]interface{}, 2) - switch v := value.(type) { - case bool: - c[0] = v - c[1] = BooleanCell - case int: - c[0] = int64(v) - c[1] = IntegerCell - case int8: - c[0] = int64(v) - c[1] = IntegerCell - case int16: - c[0] = int64(v) - c[1] = IntegerCell - case int32: - c[0] = int64(v) - c[1] = IntegerCell - case int64: - c[0] = int64(v) - c[1] = IntegerCell - case uint8: - c[0] = int64(v) - c[1] = IntegerCell - case uint16: - c[0] = int64(v) - c[1] = IntegerCell - case uint32: - c[0] = int64(v) - c[1] = IntegerCell - - case uint: - if int64(v) > int64(math.MaxInt64) { - c[0] = float64(v) - c[1] = FloatCell - } else { - c[0] = int64(v) - c[1] = IntegerCell - } - case uint64: - if v > math.MaxInt64 { - c[0] = float64(v) - c[1] = FloatCell - } else { - c[0] = int64(v) - c[1] = IntegerCell - } - - case float32: - c[0] = float64(v) - c[1] = FloatCell - case float64: - c[0] = float64(v) - c[1] = FloatCell - - case string: - if len(v) == 0 { - c[0] = nil - c[1] = BlankCell - } else { - c[0] = v - c[1] = StringCell - } - case []byte: - if len(v) == 0 { - c[0] = nil - c[1] = BlankCell - } else { - c[0] = string(v) - c[1] = StringCell - } - case []uint16: - if len(v) == 0 { - c[0] = nil - c[1] = BlankCell - } else { - c[0] = string(utf16.Decode(v)) - c[1] = StringCell - } - case []rune: - if len(v) == 0 { - c[0] = nil - c[1] = BlankCell - } else { - c[0] = string(v) - c[1] = StringCell - } - case time.Time: - c[0] = v - c[1] = DateCell - - case fmt.Stringer: - s := v.String() - if len(s) == 0 { - c[0] = nil - c[1] = BlankCell - } else { - c[0] = s - c[1] = StringCell - } - default: - panic("grate: data type not handled") - } - return Cell(c) -} - -// SetFormatNumber changes the number format stored with the cell. -func (c *Cell) SetFormatNumber(f uint16) { - if f == 0 { - *c = (*c)[:2] - return - } - - if len(*c) == 2 { - *c = append(*c, f) - } else { - (*c)[2] = f - } -} - -func (c Cell) Equal(other Cell) bool { - if c.Type() == FloatCell || other.Type() == FloatCell || - c.Type() == IntegerCell || other.Type() == IntegerCell { - v1, ok := c[0].(float64) - v1x, okx := c[0].(int64) - if okx { - v1 = float64(v1x) - ok = true - } - if !ok { - fmt.Sscanf(fmt.Sprint(c[0]), "%g", &v1) - } - v2, ok := other[0].(float64) - v2x, okx := other[0].(int64) - if okx { - v2 = float64(v2x) - ok = true - } - if !ok { - fmt.Sscanf(fmt.Sprint(c[0]), "%g", &v2) - } - return v1 == v2 - } - - return c.Less(other) == other.Less(c) -} - -func (c Cell) Less(other Cell) bool { - if len(c) == 0 { - return false - } - switch v1 := c[0].(type) { - case nil: - return false - case bool: - // F < T = T - // F < F = F - // T < T = F - // T < F = F - if v1 { - return false - } - - // if v2 is truthy, return true - switch v2 := other[0].(type) { - case nil: - return false - case bool: - return v2 - case int64: - return v2 != 0 - case float64: - return v2 != 0.0 - case string: - return boolStrings[v2] - } - - case int64: - // v1 < v2 - - switch v2 := other[0].(type) { - case nil: - return false - case bool: - x := int64(0) - if v2 { - x = 1 - } - return v1 < x - case int64: - return v1 < v2 - case float64: - if v2 < math.MinInt64 { - return false - } - if v2 > math.MaxInt64 { - return true - } - return float64(v1) < v2 - case string: - var x int64 - _, err := fmt.Sscanf(v2, "%d", &x) - if err == nil { - return v1 < x - } - return fmt.Sprint(v1) < v2 - } - case float64: - switch v2 := other[0].(type) { - case nil: - return false - case bool: - x := float64(0.0) - if v2 { - x = 1.0 - } - return v1 < x - case int64: - if v1 < math.MinInt64 { - return true - } - if v1 > math.MaxInt64 { - return false - } - return v1 < float64(v2) - case float64: - return v1 < v2 - case string: - var x float64 - _, err := fmt.Sscanf(v2, "%g", &x) - if err == nil { - return v1 < x - } - return fmt.Sprint(v1) < v2 - } - case string: - //return v1 < fmt.Sprint(other[0]) - - switch v2 := other[0].(type) { - case nil: - return false - case bool: - return v2 && !boolStrings[v1] - case int64: - var x int64 - _, err := fmt.Sscanf(v1, "%d", &x) - if err == nil { - return x < v2 - } - return v1 < fmt.Sprint(v2) - case float64: - var x float64 - _, err := fmt.Sscanf(v1, "%g", &x) - if err == nil { - return x < v2 - } - return v1 < fmt.Sprint(v2) - case string: - return v1 < v2 - } - - } - - panic("unable to compare cells (invalid internal type)") -} diff --git a/vendor/github.com/pbnjay/grate/commonxl/dates.go b/vendor/github.com/pbnjay/grate/commonxl/dates.go deleted file mode 100644 index dad6ae97..00000000 --- a/vendor/github.com/pbnjay/grate/commonxl/dates.go +++ /dev/null @@ -1,78 +0,0 @@ -package commonxl - -import ( - "strings" - "time" -) - -// ConvertToDate converts a floating-point value using the -// Excel date serialization conventions. -func (x *Formatter) ConvertToDate(val float64) time.Time { - // http://web.archive.org/web/20190808062235/http://aa.usno.navy.mil/faq/docs/JD_Formula.php - v := int(val) - if v < 61 { - jdate := val + 0.5 - if (x.flags & fMode1904) != 0 { - jdate += 2416480.5 - } else { - jdate += 2415018.5 - } - JD := int(jdate) - frac := jdate - float64(JD) - - L := JD + 68569 - N := 4 * L / 146097 - L = L - (146097*N+3)/4 - I := 4000 * (L + 1) / 1461001 - L = L - 1461*I/4 + 31 - J := 80 * L / 2447 - day := L - 2447*J/80 - L = J / 11 - month := time.Month(J + 2 - 12*L) - year := 100*(N-49) + I + L - - t := time.Duration(float64(time.Hour*24) * frac) - return time.Date(year, month, day, 0, 0, 0, 0, time.UTC).Add(t) - } - frac := val - float64(v) - date := time.Date(1904, 1, 1, 0, 0, 0, 0, time.UTC) - if (x.flags & fMode1904) == 0 { - date = time.Date(1899, 12, 30, 0, 0, 0, 0, time.UTC) - } - - t := time.Duration(float64(time.Hour*24) * frac) - return date.AddDate(0, 0, v).Add(t) -} - -func timeFmtFunc(f string) FmtFunc { - return func(x *Formatter, v interface{}) string { - t, ok := v.(time.Time) - if !ok { - fval, ok := convertToFloat64(v) - if !ok { - return "MUST BE time.Time OR numeric TO FORMAT CORRECTLY" - } - t = x.ConvertToDate(fval) - } - //log.Println("formatting date", t, "with", f, "=", t.Format(f)) - return t.Format(f) - } -} - -// same as above but replaces "AM" and "PM" with chinese translations. -// TODO: implement others -func cnTimeFmtFunc(f string) FmtFunc { - return func(x *Formatter, v interface{}) string { - t, ok := v.(time.Time) - if !ok { - fval, ok := convertToFloat64(v) - if !ok { - return "MUST BE time.Time OR numeric TO FORMAT CORRECTLY" - } - t = x.ConvertToDate(fval) - } - s := t.Format(f) - s = strings.Replace(s, `AM`, `上午`, 1) - return strings.Replace(s, `PM`, `下午`, 1) - } -} diff --git a/vendor/github.com/pbnjay/grate/commonxl/fmt.go b/vendor/github.com/pbnjay/grate/commonxl/fmt.go deleted file mode 100644 index f99e01b3..00000000 --- a/vendor/github.com/pbnjay/grate/commonxl/fmt.go +++ /dev/null @@ -1,303 +0,0 @@ -package commonxl - -import ( - "fmt" - "strconv" - "strings" -) - -// FmtFunc will format a value according to the designated style. -type FmtFunc func(*Formatter, interface{}) string - -func staticFmtFunc(s string) FmtFunc { - return func(x *Formatter, v interface{}) string { - return s - } -} - -func surround(pre string, ff FmtFunc, post string) FmtFunc { - return func(x *Formatter, v interface{}) string { - return pre + ff(x, v) + post - } -} - -func addNegParens(ff FmtFunc) FmtFunc { - return func(x *Formatter, v interface{}) string { - s1 := ff(x, v) - if s1[0] == '-' { - return "(" + s1[1:] + ")" - } - return s1 - } -} - -func addCommas(ff FmtFunc) FmtFunc { - return func(x *Formatter, v interface{}) string { - s1 := ff(x, v) - isNeg := false - if s1[0] == '-' { - isNeg = true - s1 = s1[1:] - } - endIndex := strings.IndexAny(s1, ".eE") - if endIndex < 0 { - endIndex = len(s1) - } - for endIndex > 3 { - endIndex -= 3 - s1 = s1[:endIndex] + "," + s1[endIndex:] - } - if isNeg { - return "-" + s1 - } - return s1 - } -} - -func identFunc(x *Formatter, v interface{}) string { - switch x := v.(type) { - case bool: - if x { - return "TRUE" - } - return "FALSE" - case int64: - s := strconv.FormatInt(x, 10) - if len(s) <= 11 { - return s - } - case float64: - s := strconv.FormatFloat(x, 'f', -1, 64) - if len(s) <= 11 || (len(s) == 12 && x < 0) { - return s - } - s = strconv.FormatFloat(x, 'g', 6, 64) - if len(s) <= 11 { - return s - } - case string: - return x - case fmt.Stringer: - return x.String() - } - return fmt.Sprint(v) -} - -func sprintfFunc(fs string, mul int) FmtFunc { - wantInt64 := strings.Contains(fs, "%d") - return func(x *Formatter, v interface{}) string { - switch val := v.(type) { - case int, uint, int64, uint64, int32, uint32, uint16, int16: - return fmt.Sprintf(fs, v) - - case float64: - val *= float64(mul) - if wantInt64 { - v2 := int64(val) - return fmt.Sprintf(fs, v2) - } - return fmt.Sprintf(fs, val) - } - return fmt.Sprint(v) - } -} - -func convertToInt64(v interface{}) (int64, bool) { - x, ok := convertToFloat64(v) - return int64(x), ok -} - -func convertToFloat64(v interface{}) (float64, bool) { - switch val := v.(type) { - case float64: - return val, true - case bool: - if val { - return 1.0, true - } - return 0.0, true - case int: - return float64(val), true - case int8: - return float64(val), true - case int16: - return float64(val), true - case int32: - return float64(val), true - case int64: - return float64(val), true - case uint: - return float64(val), true - case uint8: - return float64(val), true - case uint16: - return float64(val), true - case uint32: - return float64(val), true - case uint64: - return float64(val), true - case float32: - return float64(val), true - case string: - nf, err := strconv.ParseFloat(val, 64) - return nf, err == nil - default: - return 0.0, false - } -} - -// replaces a zero with a dash -func zeroDashFunc(ff FmtFunc) FmtFunc { - return func(x *Formatter, v interface{}) string { - fval, ok := convertToFloat64(v) - if !ok { - // strings etc returned as-is - return fmt.Sprint(v) - } - if fval == 0.0 { - return "-" - } - return ff(x, v) - } -} - -func fracFmtFunc(n int) FmtFunc { - return func(x *Formatter, v interface{}) string { - f, ok := convertToFloat64(v) - if !ok { - return "MUST BE numeric TO FORMAT CORRECTLY" - } - w, n, d := DecimalToWholeFraction(f, n, n) - if n == 0 { - return fmt.Sprintf("%d", w) - } - if w == 0 { - if f < 0 && n > 0 { - n = -n - } - return fmt.Sprintf("%d/%d", n, d) - } - return fmt.Sprintf("%d %d/%d", w, n, d) - } -} - -// handle (up to) all four format cases: -// positive;negative;zero;other -func switchFmtFunc(pos FmtFunc, others ...FmtFunc) FmtFunc { - stringFF := identFunc - zeroFF := pos - negFF := pos - if len(others) > 0 { - negFF = others[0] - if len(others) > 1 { - zeroFF = others[1] - if len(others) > 2 { - stringFF = others[2] - } - } - } - return func(x *Formatter, v interface{}) string { - val, ok := convertToFloat64(v) - if !ok { - return stringFF(x, v) - } - if val == 0.0 { - return zeroFF(x, v) - } - if val < 0.0 { - return negFF(x, v) - } - return pos(x, v) - } -} - -// mapping of standard built-ins to Go date format funcs. -var goFormatters = map[uint16]FmtFunc{ - 0: identFunc, // FIXME: better "general" formatter - 49: identFunc, - - 14: timeFmtFunc(`01-02-06`), - 15: timeFmtFunc(`2-Jan-06`), - 16: timeFmtFunc(`2-Jan`), - 17: timeFmtFunc(`Jan-06`), - 20: timeFmtFunc(`15:04`), - 21: timeFmtFunc(`15:04:05`), - 22: timeFmtFunc(`1/2/06 15:04`), - 45: timeFmtFunc(`04:05`), - 46: timeFmtFunc(`3:04:05`), - 47: timeFmtFunc(`0405.9`), - 27: timeFmtFunc(`2006"年"1"月"`), - 28: timeFmtFunc(`1"月"2"日"`), - 29: timeFmtFunc(`1"月"2"日"`), - 30: timeFmtFunc(`1-2-06`), - 31: timeFmtFunc(`2006"年"1"月"2"日"`), - 32: timeFmtFunc(`15"时"04"分"`), - 33: timeFmtFunc(`15"时"04"分"05"秒"`), - 36: timeFmtFunc(`2006"年"2"月"`), - 50: timeFmtFunc(`2006"年"2"月"`), - 51: timeFmtFunc(`1"月"2"日"`), - 52: timeFmtFunc(`2006"年"1"月"`), - 53: timeFmtFunc(`1"月"2"日"`), - 54: timeFmtFunc(`1"月"2"日"`), - 57: timeFmtFunc(`2006"年"1"月"`), - 58: timeFmtFunc(`1"月"2"日"`), - 71: timeFmtFunc(`2/1/2006`), - 72: timeFmtFunc(`2-Jan-06`), - 73: timeFmtFunc(`2-Jan`), - 74: timeFmtFunc(`Jan-06`), - 75: timeFmtFunc(`15:04`), - 76: timeFmtFunc(`15:04:05`), - 77: timeFmtFunc(`2/1/2006 15:04`), - 78: timeFmtFunc(`04:05`), - 79: timeFmtFunc(`15:04:05`), - 80: timeFmtFunc(`04:05.9`), - 81: timeFmtFunc(`2/1/06`), - 18: timeFmtFunc(`3:04 PM`), - 19: timeFmtFunc(`3:04:05 PM`), - - 34: cnTimeFmtFunc(`PM 3"时"04"分"`), - 35: cnTimeFmtFunc(`PM 3"时"04"分"05"秒"`), - 55: cnTimeFmtFunc(`PM 3"时"04"分"`), - 56: cnTimeFmtFunc(`PM 3"时"04"分"05"秒`), - - 12: fracFmtFunc(1), - 13: fracFmtFunc(2), - - 69: fracFmtFunc(1), - 70: fracFmtFunc(2), - - 1: sprintfFunc(`%d`, 1), - 2: sprintfFunc(`%4.2f`, 1), - 59: sprintfFunc(`%d`, 1), - 60: sprintfFunc(`%4.2f`, 1), - - 9: sprintfFunc(`%d%%`, 100), - 10: sprintfFunc(`%4.2f%%`, 100), - 67: sprintfFunc(`%d%%`, 100), - 68: sprintfFunc(`%4.2f%%`, 100), - - 3: addCommas(sprintfFunc("%d", 1)), - 61: addCommas(sprintfFunc("%d", 1)), - 37: addNegParens(addCommas(sprintfFunc("%d", 1))), - 38: addNegParens(addCommas(sprintfFunc("%d", 1))), - - 4: addCommas(sprintfFunc("%4.2f", 1)), - 62: addCommas(sprintfFunc("%4.2f", 1)), - 39: addNegParens(addCommas(sprintfFunc("%4.2f", 1))), - 40: addNegParens(addCommas(sprintfFunc("%4.2f", 1))), - - 11: sprintfFunc(`%4.2E`, 1), - 48: sprintfFunc(`%3.1E`, 1), - - 41: zeroDashFunc(addCommas(sprintfFunc("%d", 1))), - 43: zeroDashFunc(addCommas(sprintfFunc("%4.2f", 1))), - - 42: switchFmtFunc( - surround("$", addCommas(sprintfFunc("%d", 1)), ""), - surround("$(", addCommas(sprintfFunc("%d", 1)), ")"), - staticFmtFunc("$-")), - 44: switchFmtFunc( - surround("$", addCommas(sprintfFunc("%4.2f", 1)), ""), - surround("$(", addCommas(sprintfFunc("%4.2f", 1)), ")"), - staticFmtFunc("$-")), -} diff --git a/vendor/github.com/pbnjay/grate/commonxl/formats.go b/vendor/github.com/pbnjay/grate/commonxl/formats.go deleted file mode 100644 index 49b58a94..00000000 --- a/vendor/github.com/pbnjay/grate/commonxl/formats.go +++ /dev/null @@ -1,382 +0,0 @@ -package commonxl - -import ( - "errors" - "fmt" - "regexp" - "strings" -) - -// Formatter contains formatting methods common to Excel spreadsheets. -type Formatter struct { - flags uint64 - customCodes map[uint16]FmtFunc - customCodeTypes map[uint16]CellType -} - -const ( - fMode1904 uint64 = 1 -) - -// Mode1904 indicates that dates start on Jan 1, 1904 -// this setting was used in early MacOS Excel applications. -func (x *Formatter) Mode1904(enabled bool) { - if enabled { - x.flags |= fMode1904 - } else { - x.flags = x.flags &^ fMode1904 - } -} - -// Add a custom number format to the formatter. -func (x *Formatter) Add(fmtID uint16, formatCode string) error { - if x.customCodes == nil { - x.customCodes = make(map[uint16]FmtFunc) - x.customCodeTypes = make(map[uint16]CellType) - } - if strings.ToLower(formatCode) == "general" { - x.customCodes[fmtID] = goFormatters[0] - return nil - } - _, ok := goFormatters[fmtID] - if ok { - return errors.New("grate/commonxl: cannot replace default number formats") - } - - _, ok2 := x.customCodes[fmtID] - if ok2 { - return errors.New("grate/commonxl: cannot replace existing number formats") - } - - x.customCodes[fmtID], x.customCodeTypes[fmtID] = makeFormatter(formatCode) - return nil -} - -func (x *Formatter) getCellType(fmtID uint16) (CellType, bool) { - if ct, ok := builtInFormatTypes[fmtID]; ok { - return ct, true - } - if x.customCodeTypes != nil { - ct, ok := x.customCodeTypes[fmtID] - return ct, ok - } - return 0, false -} - -var ( - minsMatch = regexp.MustCompile("h.*m.*s") - nonEsc = regexp.MustCompile(`([^"]|^)"`) - squash = regexp.MustCompile(`[*_].`) - fixEsc = regexp.MustCompile(`\\(.)`) - - formatMatchBrackets = regexp.MustCompile(`\[[^\]]*\]`) - formatMatchTextLiteral = regexp.MustCompile(`"[^"]*"`) -) - -func makeFormatter(s string) (FmtFunc, CellType) { - //log.Printf("makeFormatter('%s')", s) - // remove any coloring marks - s = formatMatchBrackets.ReplaceAllString(s, "") - if strings.Contains(s, ";") { - parts := strings.Split(s, ";") - posFF, ctypePos := makeFormatter(parts[0]) - rem := make([]FmtFunc, len(parts)-1) - for i, ps := range parts[1:] { - rem[i], _ = makeFormatter(ps) - } - return switchFmtFunc(posFF, rem...), ctypePos - } - - // escaped characters, and quoted text - s2 := fixEsc.ReplaceAllString(s, "") - s2 = formatMatchTextLiteral.ReplaceAllString(s, "") - - if strings.ContainsAny(s2, "ymdhs") { - // it's a date/time format - - if loc := minsMatch.FindStringIndex(s); loc != nil { - // m or mm in loc[0]:loc[1] is a minute format - inner := s[loc[0]:loc[1]] - inner = strings.Replace(inner, "mm", "04", 1) - inner = strings.Replace(inner, "m", "4", 1) - s = s[:loc[0]] + inner + s[loc[1]:] - } - dfreps := [][]string{ - {"hh", "15"}, {"h", "15"}, - {"ss", "05"}, {"s", "5"}, - {"mmmmm", "Jan"}, // super ambiguous, replace with 3-letter month - {"mmmm", "January"}, {"mmm", "Jan"}, - {"mm", "01"}, {"m", "1"}, - {"dddd", "Monday"}, {"ddd", "Mon"}, - {"dd", "02"}, {"d", "2"}, - {"yyyy", "2006"}, {"yy", "06"}, - } - if strings.Contains(s, "AM") || strings.Contains(s, "PM") { - dfreps[0][1] = "03" - dfreps[1][1] = "3" - } - for _, dfr := range dfreps { - s = strings.Replace(s, dfr[0], dfr[1], 1) - } - - s = nonEsc.ReplaceAllString(s, `$1`) - s = squash.ReplaceAllString(s, ``) - s = fixEsc.ReplaceAllString(s, `$1`) - - //log.Printf(" made time formatter '%s'", s) - return timeFmtFunc(s), DateCell - } - - var ff FmtFunc - var ctype CellType - if strings.ContainsAny(s, ".Ee") { - verb := "f" - if strings.ContainsAny(s, "Ee") { - verb = "E" - } - s = regexp.MustCompile("[eE]+[+-]0+").ReplaceAllString(s, "") - s2 := strings.ReplaceAll(s, ",", "") - i1 := strings.IndexAny(s2, "0") - i2 := strings.IndexByte(s2, '.') - i3 := strings.LastIndexAny(s2, "0.") - mul := 1 - if strings.Contains(s2, "%") { - mul = 100 - } - sf := fmt.Sprintf("%%%d.%d%s", i3-i1, i3-i2, verb) - //log.Printf(" made float formatter '%s'", sf) - ff = sprintfFunc(sf, mul) - ctype = FloatCell - } else { - s2 := strings.ReplaceAll(s, ",", "") - i1 := strings.IndexAny(s2, "0") - i2 := strings.LastIndexAny(s2, "0.") - mul := 1 - if strings.Contains(s2, "%") { - mul = 100 - } - sf := fmt.Sprintf("%%%dd", i2-i1) - if (i2 - i1) == 0 { - sf = "%d" - } - //log.Printf(" made int formatter '%s'", sf) - ff = sprintfFunc(sf, mul) - ctype = IntegerCell - } - - if strings.Contains(s, ",") { - ff = addCommas(ff) - //log.Printf(" added commas") - } - - surReg := regexp.MustCompile(`[0#?,.]+`) - prepost := surReg.Split(s, 2) - if len(prepost) > 0 && len(prepost[0]) > 0 { - prepost[0] = nonEsc.ReplaceAllString(prepost[0], `$1`) - prepost[0] = squash.ReplaceAllString(prepost[0], ``) - prepost[0] = fixEsc.ReplaceAllString(prepost[0], `$1`) - } - if len(prepost) == 1 { - if prepost[0] == "@" { - return identFunc, StringCell - } - //log.Printf(" added static ('%s')", prepost[0]) - return staticFmtFunc(prepost[0]), StringCell - } - if len(prepost[0]) > 0 || len(prepost[1]) > 0 { - prepost[1] = nonEsc.ReplaceAllString(prepost[1], `$1`) - prepost[1] = squash.ReplaceAllString(prepost[1], ``) - prepost[1] = fixEsc.ReplaceAllString(prepost[1], `$1`) - - ff = surround(prepost[0], ff, prepost[1]) - //log.Printf(" added surround ('%s' ... '%s')", prepost[0], prepost[1]) - } - - return ff, ctype -} - -// Get the number format func to use for formatting values, -// it returns false when fmtID is unknown. -func (x *Formatter) Get(fmtID uint16) (FmtFunc, bool) { - ff, ok := goFormatters[fmtID] - if !ok { - fs, ok2 := x.customCodes[fmtID] - if ok2 { - return fs, true - } - ff = identFunc - } - - return ff, ok -} - -// Apply the specified number format to the value. -// Returns false when fmtID is unknown. -func (x *Formatter) Apply(fmtID uint16, val interface{}) (string, bool) { - ff, ok := goFormatters[fmtID] - if !ok { - fs, ok2 := x.customCodes[fmtID] - if ok2 { - return fs(x, val), true - } - } - return ff(x, val), ok -} - -// builtInFormats are all the built-in number formats for XLS/XLSX. -var builtInFormats = map[uint16]string{ - 0: `General`, - 1: `0`, - 2: `0.00`, - 3: `#,##0`, - 4: `#,##0.00`, - 9: `0%`, - 10: `0.00%`, - - 11: `0.00E+00`, - 12: `# ?/?`, - 13: `# ??/??`, - 14: `mm-dd-yy`, - 15: `d-mmm-yy`, - 16: `d-mmm`, - 17: `mmm-yy`, - 18: `h:mm AM/PM`, - 19: `h:mm:ss AM/PM`, - 20: `h:mm`, - 21: `h:mm:ss`, - 22: `m/d/yy h:mm`, - 37: `#,##0 ;(#,##0)`, - 38: `#,##0 ;[Red](#,##0)`, - 39: `#,##0.00;(#,##0.00)`, - 40: `#,##0.00;[Red](#,##0.00)`, - - 41: `_(* #,##0_);_(* \(#,##0\);_(* "-"_);_(@_)`, - 42: `_("$"* #,##0_);_("$"* \(#,##0\);_("$"* "-"_);_(@_)`, - 43: `_(* #,##0.00_);_(* \(#,##0.00\);_(* "-"??_);_(@_)`, - 44: `_("$"* #,##0.00_);_("$"* \(#,##0.00\);_("$"* "-"??_);_(@_)`, - - 45: `mm:ss`, - 46: `[h]:mm:ss`, - 47: `mmss.0`, - 48: `##0.0E+0`, - 49: `@`, - - // zh-cn format codes - 27: `yyyy"年"m"月"`, - 28: `m"月"d"日"`, - 29: `m"月"d"日"`, - 30: `m-d-yy`, - 31: `yyyy"年"m"月"d"日"`, - 32: `h"时"mm"分"`, - 33: `h"时"mm"分"ss"秒"`, - 34: `上午/下午 h"时"mm"分"`, - 35: `上午/下午 h"时"mm"分"ss"秒"`, - 36: `yyyy"年"m"月"`, - 50: `yyyy"年"m"月"`, - 51: `m"月"d"日"`, - 52: `yyyy"年"m"月"`, - 53: `m"月"d"日"`, - 54: `m"月"d"日"`, - 55: `上午/下午 h"时"mm"分"`, - 56: `上午/下午 h"时"mm"分"ss"秒`, - 57: `yyyy"年"m"月"`, - 58: `m"月"d"日"`, - - // th-th format codes (in the spec these have a "t" prefix?) - 59: `0`, - 60: `0.00`, - 61: `#,##0`, - 62: `#,##0.00`, - 67: `0%`, - 68: `0.00%`, - 69: `# ?/?`, - 70: `# ??/??`, - - // th format code, but translated to aid the parser - 71: `d/m/yyyy`, // `ว/ด/ปปปป`, - 72: `d-mmm-yy`, // `ว-ดดด-ปป`, - 73: `d-mmm`, // `ว-ดดด`, - 74: `mmm-yy`, // `ดดด-ปป`, - 75: `h:mm`, // `ช:นน`, - 76: `h:mm:ss`, // `ช:นน:ทท`, - 77: `d/m/yyyy h:mm`, // `ว/ด/ปปปป ช:นน`, - 78: `mm:ss`, // `นน:ทท`, - 79: `[h]:mm:ss`, // `[ช]:นน:ทท`, - 80: `mm:ss.0`, // `นน:ทท.0`, - 81: `d/m/bb`, // `d/m/bb`, -} - -// builtInFormatTypes are the underlying datatypes for built-in number formats in XLS/XLSX. -var builtInFormatTypes = map[uint16]CellType{ - // 0 has no defined type - 1: IntegerCell, - 2: FloatCell, - 3: IntegerCell, - 4: FloatCell, - 9: FloatCell, - 10: FloatCell, - - 11: FloatCell, - 12: FloatCell, - 13: FloatCell, - 14: DateCell, - 15: DateCell, - 16: DateCell, - 17: DateCell, - 18: DateCell, - 19: DateCell, - 20: DateCell, - 21: DateCell, - 22: DateCell, - 37: IntegerCell, - 38: IntegerCell, - 39: FloatCell, - 40: FloatCell, - 41: IntegerCell, - 42: IntegerCell, - 43: FloatCell, - 44: FloatCell, - 45: DateCell, // Durations? - 46: DateCell, - 47: DateCell, - 48: FloatCell, - 49: StringCell, - 27: DateCell, - 28: DateCell, - 29: DateCell, - 30: DateCell, - 31: DateCell, - 32: DateCell, - 33: DateCell, - 34: DateCell, - 35: DateCell, - 36: DateCell, - 50: DateCell, - 51: DateCell, - 52: DateCell, - 53: DateCell, - 54: DateCell, - 55: DateCell, - 56: DateCell, - 57: DateCell, - 58: DateCell, - 59: IntegerCell, - 60: FloatCell, - 61: IntegerCell, - 62: FloatCell, - 67: FloatCell, - 68: FloatCell, - 69: FloatCell, - 70: FloatCell, - 71: DateCell, - 72: DateCell, - 73: DateCell, - 74: DateCell, - 75: DateCell, - 76: DateCell, - 77: DateCell, - 78: DateCell, - 79: DateCell, - 80: DateCell, - 81: DateCell, -} diff --git a/vendor/github.com/pbnjay/grate/commonxl/numbers.go b/vendor/github.com/pbnjay/grate/commonxl/numbers.go deleted file mode 100644 index c3df4c2d..00000000 --- a/vendor/github.com/pbnjay/grate/commonxl/numbers.go +++ /dev/null @@ -1,74 +0,0 @@ -package commonxl - -import ( - "math" -) - -// DecimalToWholeFraction converts a floating point value into a whole -// number and fraction approximation with at most nn digits in the numerator -// and nd digits in the denominator. -func DecimalToWholeFraction(val float64, nn, nd int) (whole, num, den int) { - wholeF, part := math.Modf(val) - if part == 0.0 { - return int(wholeF), 0, 1 - } - if part < 0.0 { - part = -part - } - whole = int(wholeF) - num, den = DecimalToFraction(part, nn, nd) - return -} - -// DecimalToFraction converts a floating point value into a fraction -// approximation with at most nn digits in the numerator and nd -// digits in the denominator. -func DecimalToFraction(val float64, nn, nd int) (num, den int) { - // http://web.archive.org/web/20111027100847/http://homepage.smc.edu/kennedy_john/DEC2FRAC.PDF - sign := 1 - z := val - if val < 0 { - sign = -1 - z = -val - } - if nn == 0 { - nn = 2 - } - if nd == 0 { - nd = 2 - } - maxn := math.Pow(10.0, float64(nn)) // numerator with nn digits - maxd := math.Pow(10.0, float64(nd)) // denominator with nd digits - - _, fracPart := math.Modf(val) - if fracPart == 0.0 { - return int(z) * sign, 1 - } - if fracPart < 1e-9 { - return sign, int(1e9) - } - if fracPart > 1e9 { - return int(1e9) * sign, 1 - } - - diff := 1.0 - denom := 1.0 - numer := 0.0 - var lastDenom, lastNumer float64 - for diff > 1e-10 && z != math.Floor(z) { - z = 1 / (z - math.Floor(z)) - tmp := denom - denom = (denom * math.Floor(z)) + lastDenom - lastDenom = tmp - lastNumer = numer - numer = math.Round(val * denom) - if numer >= maxn || denom >= maxd { - return sign * int(lastNumer), int(lastDenom) - } - diff = val - (numer / denom) - if diff < 0.0 { - diff = -diff - } - } - return sign * int(numer), int(denom) -} diff --git a/vendor/github.com/pbnjay/grate/commonxl/sheet.go b/vendor/github.com/pbnjay/grate/commonxl/sheet.go deleted file mode 100644 index a1653344..00000000 --- a/vendor/github.com/pbnjay/grate/commonxl/sheet.go +++ /dev/null @@ -1,232 +0,0 @@ -package commonxl - -import ( - "fmt" - "log" - "time" - - "github.com/pbnjay/grate" -) - -// Sheet holds raw and rendered values for a spreadsheet. -type Sheet struct { - Formatter *Formatter - NumRows int - NumCols int - Rows [][]Cell - - CurRow int -} - -// Resize the sheet for the number of rows and cols given. -// Newly added cells default to blank. -func (s *Sheet) Resize(rows, cols int) { - for i := range s.Rows { - if i > rows { - break - } - n := cols - len(s.Rows[i]) - if n <= 0 { - continue - } - s.Rows[i] = append(s.Rows[i], make([]Cell, n)...) - } - - if rows <= 0 { - rows = 1 - } - if cols <= 0 { - cols = 1 - } - s.CurRow = 0 - s.NumRows = rows - s.NumCols = cols - - for rows >= len(s.Rows) { - s.Rows = append(s.Rows, make([]Cell, cols)) - } -} - -// Put the value at the cell location given. -func (s *Sheet) Put(row, col int, value interface{}, fmtNum uint16) { - //log.Println(row, col, value, fmtNum) - if row >= s.NumRows || col >= s.NumCols { - if grate.Debug { - log.Printf("grate: cell out of bounds row %d>=%d, col %d>=%d", - row, s.NumRows, col, s.NumCols) - } - - // per the spec, this is an invalid Excel file - // but we'll resize in place instead of crashing out - if row >= s.NumRows { - s.NumRows = row + 1 - } - if col >= s.NumCols { - s.NumCols = col + 1 - } - s.Resize(s.NumRows, s.NumCols) - } - - if spec, ok := value.(string); ok { - if spec == grate.EndRowMerged || spec == grate.EndColumnMerged || spec == grate.ContinueRowMerged || spec == grate.ContinueColumnMerged { - s.Rows[row][col] = NewCell(value) - s.Rows[row][col][1] = StaticCell - return - } - } - - ct, ok := s.Formatter.getCellType(fmtNum) - if !ok || fmtNum == 0 { - s.Rows[row][col] = NewCell(value) - } else { - s.Rows[row][col] = NewCellWithType(value, ct, s.Formatter) - } - s.Rows[row][col].SetFormatNumber(fmtNum) -} - -// Set changes the value in an existing cell location. -// NB Currently only used for populating string results for formulas. -func (s *Sheet) Set(row, col int, value interface{}) { - if row > s.NumRows || col > s.NumCols { - log.Println("grate: cell out of bounds") - return - } - - s.Rows[row][col][0] = value - s.Rows[row][col][1] = StringCell -} - -// SetURL adds a hyperlink to an existing cell location. -func (s *Sheet) SetURL(row, col int, link string) { - if row > s.NumRows || col > s.NumCols { - log.Println("grate: cell out of bounds") - return - } - - s.Rows[row][col].SetURL(link) -} - -// Next advances to the next record of content. -// It MUST be called prior to any Scan(). -func (s *Sheet) Next() bool { - if (s.CurRow + 1) > len(s.Rows) { - return false - } - s.CurRow++ - return true -} - -// Raw extracts the raw Cell interfaces underlying the current row. -func (s *Sheet) Raw() []Cell { - rr := make([]Cell, s.NumCols) - for i, cell := range s.Rows[s.CurRow-1] { - rr[i] = cell.Clone() - } - return rr -} - -// Strings extracts values from the current record into a list of strings. -func (s *Sheet) Strings() []string { - res := make([]string, s.NumCols) - for i, cell := range s.Rows[s.CurRow-1] { - if cell.Type() == BlankCell { - res[i] = "" - continue - } - if cell.Type() == StaticCell { - res[i] = cell.Value().(string) - continue - } - val := cell.Value() - fs, ok := s.Formatter.Apply(cell.FormatNo(), val) - if !ok { - fs = fmt.Sprint(val) - } - res[i] = fs - } - return res -} - -// Types extracts the data types from the current record into a list. -// options: "boolean", "integer", "float", "string", "date", -// and special cases: "blank", "hyperlink" which are string types -func (s *Sheet) Types() []string { - res := make([]string, s.NumCols) - for i, cell := range s.Rows[s.CurRow-1] { - res[i] = cell.Type().String() - } - return res -} - -// Formats extracts the format code for the current record into a list. -func (s *Sheet) Formats() []string { - ok := true - res := make([]string, s.NumCols) - for i, cell := range s.Rows[s.CurRow-1] { - res[i], ok = builtInFormats[cell.FormatNo()] - if !ok { - res[i] = fmt.Sprint(cell.FormatNo()) - } - } - return res -} - -// Scan extracts values from the current record into the provided arguments -// Arguments must be pointers to one of 5 supported types: -// bool, int64, float64, string, or time.Time -// If invalid, returns ErrInvalidScanType -func (s *Sheet) Scan(args ...interface{}) error { - row := s.Rows[s.CurRow-1] - - for i, a := range args { - val := row[i].Value() - - switch v := a.(type) { - case bool, int64, float64, string, time.Time: - return fmt.Errorf("scan destinations must be pointer (arg %d is not)", i) - case *bool: - if x, ok := val.(bool); ok { - *v = x - } else { - return fmt.Errorf("scan destination %d expected *%T, not *bool", i, val) - } - case *int64: - if x, ok := val.(int64); ok { - *v = x - } else { - return fmt.Errorf("scan destination %d expected *%T, not *int64", i, val) - } - case *float64: - if x, ok := val.(float64); ok { - *v = x - } else { - return fmt.Errorf("scan destination %d expected *%T, not *float64", i, val) - } - case *string: - if x, ok := val.(string); ok { - *v = x - } else { - return fmt.Errorf("scan destination %d expected *%T, not *string", i, val) - } - case *time.Time: - if x, ok := val.(time.Time); ok { - *v = x - } else { - return fmt.Errorf("scan destination %d expected *%T, not *time.Time", i, val) - } - default: - return fmt.Errorf("scan destination for arg %d is not supported (%T)", i, a) - } - } - return nil -} - -// IsEmpty returns true if there are no data values. -func (s *Sheet) IsEmpty() bool { - return (s.NumCols <= 1 && s.NumRows <= 1) -} - -// Err returns the last error that occured. -func (s *Sheet) Err() error { - return nil -} diff --git a/vendor/github.com/pbnjay/grate/errs.go b/vendor/github.com/pbnjay/grate/errs.go deleted file mode 100644 index 784a5514..00000000 --- a/vendor/github.com/pbnjay/grate/errs.go +++ /dev/null @@ -1,44 +0,0 @@ -package grate - -import "errors" - -var ( - // configure at build time by adding go build arguments: - // -ldflags="-X github.com/pbnjay/grate.loglevel=debug" - loglevel string = "warn" - - // Debug should be set to true to expose detailed logging. - Debug bool = (loglevel == "debug") -) - -// ErrInvalidScanType is returned by Scan for invalid arguments. -var ErrInvalidScanType = errors.New("grate: Scan only supports *bool, *int, *float64, *string, *time.Time arguments") - -// ErrNotInFormat is used to auto-detect file types using the defined OpenFunc -// It is returned by OpenFunc when the code does not detect correct file formats. -var ErrNotInFormat = errors.New("grate: file is not in this format") - -// ErrUnknownFormat is used when grate does not know how to open a file format. -var ErrUnknownFormat = errors.New("grate: file format is not known/supported") - -type errx struct { - errs []error -} - -func (e errx) Error() string { - return e.errs[0].Error() -} -func (e errx) Unwrap() error { - if len(e.errs) > 1 { - return e.errs[1] - } - return nil -} - -// WrapErr wraps a set of errors. -func WrapErr(e ...error) error { - if len(e) == 1 { - return e[0] - } - return errx{errs: e} -} diff --git a/vendor/github.com/pbnjay/grate/grate.go b/vendor/github.com/pbnjay/grate/grate.go deleted file mode 100644 index b06f111d..00000000 --- a/vendor/github.com/pbnjay/grate/grate.go +++ /dev/null @@ -1,104 +0,0 @@ -// Package grate opens tabular data files (such as spreadsheets and delimited plaintext files) -// and allows programmatic access to the data contents in a consistent interface. -package grate - -import ( - "errors" - "log" - "sort" -) - -// Source represents a set of data collections. -type Source interface { - // List the individual data tables within this source. - List() ([]string, error) - - // Get a Collection from the source by name. - Get(name string) (Collection, error) - - // Close the source and discard memory. - Close() error -} - -// Collection represents an iterable collection of records. -type Collection interface { - // Next advances to the next record of content. - // It MUST be called prior to any Scan(). - Next() bool - - // Strings extracts values from the current record into a list of strings. - Strings() []string - - // Types extracts the data types from the current record into a list. - // options: "boolean", "integer", "float", "string", "date", - // and special cases: "blank", "hyperlink" which are string types - Types() []string - - // Formats extracts the format codes for the current record into a list. - Formats() []string - - // Scan extracts values from the current record into the provided arguments - // Arguments must be pointers to one of 5 supported types: - // bool, int64, float64, string, or time.Time - // If invalid, returns ErrInvalidScanType - Scan(args ...interface{}) error - - // IsEmpty returns true if there are no data values. - IsEmpty() bool - - // Err returns the last error that occured. - Err() error -} - -// OpenFunc defines a Source's instantiation function. -// It should return ErrNotInFormat immediately if filename is not of the correct file type. -type OpenFunc func(filename string) (Source, error) - -// Open a tabular data file and return a Source for accessing it's contents. -func Open(filename string) (Source, error) { - for _, o := range srcTable { - src, err := o.op(filename) - if err == nil { - return src, nil - } - if !errors.Is(err, ErrNotInFormat) { - return nil, err - } - if Debug { - log.Println(" ", filename, "is not in", o.name, "format") - } - } - return nil, ErrUnknownFormat -} - -type srcOpenTab struct { - name string - pri int - op OpenFunc -} - -var srcTable = make([]*srcOpenTab, 0, 20) - -// Register the named source as a grate datasource implementation. -func Register(name string, priority int, opener OpenFunc) error { - if Debug { - log.Println("Registering the", name, "format at priority", priority) - } - srcTable = append(srcTable, &srcOpenTab{name: name, pri: priority, op: opener}) - sort.Slice(srcTable, func(i, j int) bool { - return srcTable[i].pri < srcTable[j].pri - }) - return nil -} - -const ( - // ContinueColumnMerged marks a continuation column within a merged cell. - ContinueColumnMerged = "→" - // EndColumnMerged marks the last column of a merged cell. - EndColumnMerged = "⇥" - - // ContinueRowMerged marks a continuation row within a merged cell. - ContinueRowMerged = "↓" - // EndRowMerged marks the last row of a merged cell. - EndRowMerged = "⤓" -) diff --git a/vendor/github.com/pbnjay/grate/xlsx/sheets.go b/vendor/github.com/pbnjay/grate/xlsx/sheets.go deleted file mode 100644 index fc9c76b1..00000000 --- a/vendor/github.com/pbnjay/grate/xlsx/sheets.go +++ /dev/null @@ -1,214 +0,0 @@ -package xlsx - -import ( - "encoding/xml" - "errors" - "io" - "log" - "path/filepath" - "strconv" - "strings" - - "github.com/pbnjay/grate" - "github.com/pbnjay/grate/commonxl" -) - -type Sheet struct { - d *Document - relID string - name string - docname string - - err error - - wrapped *commonxl.Sheet -} - -var errNotLoaded = errors.New("xlsx: sheet not loaded") - -func (s *Sheet) parseSheet() error { - s.wrapped = &commonxl.Sheet{ - Formatter: &s.d.fmt, - } - linkmap := make(map[string]string) - base := filepath.Base(s.docname) - sub := strings.TrimSuffix(s.docname, base) - relsname := filepath.Join(sub, "_rels", base+".rels") - dec, clo, err := s.d.openXML(relsname) - if err == nil { - // rels might not exist for every sheet - tok, err := dec.RawToken() - for ; err == nil; tok, err = dec.RawToken() { - if v, ok := tok.(xml.StartElement); ok && v.Name.Local == "Relationship" { - ax := getAttrs(v.Attr, "Id", "Type", "Target", "TargetMode") - if ax[3] == "External" && ax[1] == "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink" { - linkmap[ax[0]] = ax[2] - } - } - } - clo.Close() - } - - dec, clo, err = s.d.openXML(s.docname) - if err != nil { - return err - } - defer clo.Close() - - currentCellType := BlankCellType - currentCell := "" - var fno uint16 - var maxCol, maxRow int - - tok, err := dec.RawToken() - for ; err == nil; tok, err = dec.RawToken() { - switch v := tok.(type) { - case xml.CharData: - if currentCell == "" { - continue - } - c, r := refToIndexes(currentCell) - if c >= 0 && r >= 0 { - var val interface{} = string(v) - - switch currentCellType { - case BooleanCellType: - if v[0] == '1' { - val = true - } else { - val = false - } - case DateCellType: - log.Println("CELL DATE", val, fno) - case NumberCellType: - fval, err := strconv.ParseFloat(string(v), 64) - if err == nil { - val = fval - } - //log.Println("CELL NUMBER", val, numFormat) - case SharedStringCellType: - //log.Println("CELL SHSTR", val, currentCellType, numFormat) - si, _ := strconv.ParseInt(string(v), 10, 64) - val = s.d.strings[si] - case BlankCellType: - //log.Println("CELL BLANK") - // don't place any values - continue - case ErrorCellType, FormulaStringCellType, InlineStringCellType: - //log.Println("CELL ERR/FORM/INLINE", val, currentCellType) - default: - log.Println("CELL UNKNOWN", val, currentCellType, fno) - } - s.wrapped.Put(r, c, val, fno) - } else { - //log.Println("FAIL row/col: ", currentCell) - } - case xml.StartElement: - switch v.Name.Local { - case "dimension": - ax := getAttrs(v.Attr, "ref") - if ax[0] == "A1" { - maxCol, maxRow = 1, 1 - // short-circuit empty sheet - s.wrapped.Resize(1, 1) - continue - } - dims := strings.Split(ax[0], ":") - if len(dims) == 1 { - maxCol, maxRow = refToIndexes(dims[0]) - } else { - //minCol, minRow := refToIndexes(dims[0]) - maxCol, maxRow = refToIndexes(dims[1]) - } - s.wrapped.Resize(maxRow, maxCol) - //log.Println("DIMENSION:", s.minRow, s.minCol, ">", s.maxRow, s.maxCol) - case "row": - //currentRow = ax["r"] // unsigned int row index - //log.Println("ROW", currentRow) - case "c": - ax := getAttrs(v.Attr, "t", "r", "s") - currentCellType = CellType(ax[0]) - if currentCellType == BlankCellType { - currentCellType = NumberCellType - } - currentCell = ax[1] // always an A1 style reference - style := ax[2] - sid, _ := strconv.ParseInt(style, 10, 64) - if len(s.d.xfs) > int(sid) { - fno = s.d.xfs[sid] - } else { - fno = 0 - } - //log.Println("CELL", currentCell, sid, numFormat, currentCellType) - case "v": - //log.Println("CELL VALUE", ax) - - case "mergeCell": - ax := getAttrs(v.Attr, "ref") - dims := strings.Split(ax[0], ":") - startCol, startRow := refToIndexes(dims[0]) - endCol, endRow := startCol, startRow - if len(dims) > 1 { - endCol, endRow = refToIndexes(dims[1]) - } - if endRow > maxRow { - endRow = maxRow - } - if endCol > maxCol { - endCol = maxCol - } - for r := startRow; r <= endRow; r++ { - for c := startCol; c <= endCol; c++ { - if r == startRow && c == startCol { - // has data already! - } else if c == startCol { - // first and last column MAY be the same - if r == endRow { - s.wrapped.Put(r, c, grate.EndRowMerged, 0) - } else { - s.wrapped.Put(r, c, grate.ContinueRowMerged, 0) - } - } else if c == endCol { - // first and last column are NOT the same - s.wrapped.Put(r, c, grate.EndColumnMerged, 0) - } else { - s.wrapped.Put(r, c, grate.ContinueColumnMerged, 0) - } - } - } - - case "hyperlink": - ax := getAttrs(v.Attr, "ref", "id") - col, row := refToIndexes(ax[0]) - link := linkmap[ax[1]] - s.wrapped.Put(row, col, link, 0) - s.wrapped.SetURL(row, col, link) - - case "worksheet", "mergeCells", "hyperlinks": - // containers - case "f": - //log.Println("start: ", v.Name.Local, v.Attr) - default: - if grate.Debug { - log.Println(" Unhandled sheet xml tag", v.Name.Local, v.Attr) - } - } - case xml.EndElement: - - switch v.Name.Local { - case "c": - currentCell = "" - case "row": - //currentRow = "" - } - default: - if grate.Debug { - log.Printf(" Unhandled sheet xml tokens %T %+v", tok, tok) - } - } - } - if err == io.EOF { - err = nil - } - return err -} diff --git a/vendor/github.com/pbnjay/grate/xlsx/types.go b/vendor/github.com/pbnjay/grate/xlsx/types.go deleted file mode 100644 index 427c0c0e..00000000 --- a/vendor/github.com/pbnjay/grate/xlsx/types.go +++ /dev/null @@ -1,92 +0,0 @@ -package xlsx - -import ( - "encoding/xml" - "strconv" - "strings" -) - -type CellType string - -// CellTypes define data type in section 18.18.11 -const ( - BlankCellType CellType = "" - BooleanCellType CellType = "b" - DateCellType CellType = "d" - ErrorCellType CellType = "e" - NumberCellType CellType = "n" - SharedStringCellType CellType = "s" - FormulaStringCellType CellType = "str" - InlineStringCellType CellType = "inlineStr" -) - -type staticCellType rune - -const ( - staticBlank staticCellType = 0 - - // marks a continuation column within a merged cell. - continueColumnMerged staticCellType = '→' - // marks the last column of a merged cell. - endColumnMerged staticCellType = '⇥' - - // marks a continuation row within a merged cell. - continueRowMerged staticCellType = '↓' - // marks the last row of a merged cell. - endRowMerged staticCellType = '⤓' -) - -func (s staticCellType) String() string { - if s == 0 { - return "" - } - return string([]rune{rune(s)}) -} - -// returns the 0-based index of the column string: -// "A"=0, "B"=1, "AA"=26, "BB"=53 -func col2int(col string) int { - idx := 0 - for _, c := range col { - idx *= 26 - idx += int(c - '@') - } - return idx - 1 -} - -func refToIndexes(r string) (column, row int) { - if len(r) < 2 { - return -1, -1 - } - i1 := strings.IndexAny(r, "0123456789") - if i1 <= 0 { - return -1, -1 - } - - // A1 Reference mode - col1 := r[:i1] - i2 := strings.IndexByte(r[i1:], 'C') - if i2 == -1 { - rn, _ := strconv.ParseInt(r[i1:], 10, 64) - return col2int(col1), int(rn) - 1 - } - - // R1C1 Reference Mode - col1 = r[i1:i2] - row1 := r[i2+1:] - cn, _ := strconv.ParseInt(col1, 10, 64) - rn, _ := strconv.ParseInt(row1, 10, 64) - return int(cn), int(rn) - 1 -} - -func getAttrs(attrs []xml.Attr, keys ...string) []string { - res := make([]string, len(keys)) - for _, a := range attrs { - for i, k := range keys { - if a.Name.Local == k { - res[i] = a.Value - } - } - } - return res -} diff --git a/vendor/github.com/pbnjay/grate/xlsx/workbook.go b/vendor/github.com/pbnjay/grate/xlsx/workbook.go deleted file mode 100644 index d789562f..00000000 --- a/vendor/github.com/pbnjay/grate/xlsx/workbook.go +++ /dev/null @@ -1,219 +0,0 @@ -package xlsx - -import ( - "encoding/xml" - "errors" - "io" - "log" - "path/filepath" - "strconv" - "strings" - - "github.com/pbnjay/grate" -) - -func (d *Document) parseRels(dec *xml.Decoder, basedir string) error { - tok, err := dec.RawToken() - for ; err == nil; tok, err = dec.RawToken() { - switch v := tok.(type) { - case xml.StartElement: - switch v.Name.Local { - case "Relationships": - // container - case "Relationship": - vals := make(map[string]string, 5) - for _, a := range v.Attr { - vals[a.Name.Local] = a.Value - } - if _, ok := d.rels[vals["Type"]]; !ok { - d.rels[vals["Type"]] = make(map[string]string) - } - if strings.HasPrefix(vals["Target"], "/") { - // handle malformed "absolute" paths cleanly - d.rels[vals["Type"]][vals["Id"]] = vals["Target"][1:] - } else { - d.rels[vals["Type"]][vals["Id"]] = filepath.Join(basedir, vals["Target"]) - } - if vals["Type"] == "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" { - d.primaryDoc = vals["Target"] - } - default: - if grate.Debug { - log.Println(" Unhandled relationship xml tag", v.Name.Local, v.Attr) - } - } - case xml.EndElement: - // not needed - default: - if grate.Debug { - log.Printf(" Unhandled relationship xml tokens %T %+v", tok, tok) - } - } - } - if err == io.EOF { - err = nil - } - return err -} - -func (d *Document) parseWorkbook(dec *xml.Decoder) error { - tok, err := dec.RawToken() - for ; err == nil; tok, err = dec.RawToken() { - switch v := tok.(type) { - case xml.StartElement: - switch v.Name.Local { - case "sheet": - vals := make(map[string]string, 5) - for _, a := range v.Attr { - vals[a.Name.Local] = a.Value - } - sheetID, ok1 := vals["id"] - sheetName, ok2 := vals["name"] - if !ok1 || !ok2 { - return errors.New("xlsx: invalid sheet definition") - } - s := &Sheet{ - d: d, - relID: sheetID, - name: sheetName, - docname: d.rels["http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet"][sheetID], - err: errNotLoaded, - } - d.sheets = append(d.sheets, s) - case "workbook", "sheets": - // containers - default: - if grate.Debug { - log.Println(" Unhandled workbook xml tag", v.Name.Local, v.Attr) - } - } - case xml.EndElement: - // not needed - default: - if grate.Debug { - log.Printf(" Unhandled workbook xml tokens %T %+v", tok, tok) - } - } - } - if err == io.EOF { - err = nil - } - return err -} - -func (d *Document) parseStyles(dec *xml.Decoder) error { - baseNumFormats := []string{} - d.xfs = d.xfs[:0] - - section := 0 - tok, err := dec.RawToken() - for ; err == nil; tok, err = dec.RawToken() { - switch v := tok.(type) { - case xml.StartElement: - switch v.Name.Local { - case "styleSheet": - // container - case "numFmt": - ax := getAttrs(v.Attr, "numFmtId", "formatCode") - fmtNo, _ := strconv.ParseInt(ax[0], 10, 16) - d.fmt.Add(uint16(fmtNo), ax[1]) - - case "cellStyleXfs": - section = 1 - case "cellXfs": - section = 2 - ax := getAttrs(v.Attr, "count") - n, _ := strconv.ParseInt(ax[0], 10, 64) - d.xfs = make([]uint16, 0, n) - - case "xf": - ax := getAttrs(v.Attr, "numFmtId", "applyNumberFormat", "xfId") - if section == 1 { - // load base styles, but only save number format - if ax[1] == "0" { - baseNumFormats = append(baseNumFormats, "0") - } else { - baseNumFormats = append(baseNumFormats, ax[0]) - } - } else if section == 2 { - // actual referencable cell styles - // 1) get base style so we can inherit format properly - baseID, _ := strconv.ParseInt(ax[2], 10, 64) - numFmtID := "0" - if len(baseNumFormats) > int(baseID) { - numFmtID = baseNumFormats[baseID] - } - - // 2) check if this XF overrides the base format - if ax[1] == "0" { - // remove the format (if it was inherited) - numFmtID = "0" - } else { - numFmtID = ax[0] - } - - nfid, _ := strconv.ParseInt(numFmtID, 10, 16) - d.xfs = append(d.xfs, uint16(nfid)) - } else { - panic("wheres is this xf??") - } - default: - if grate.Debug { - log.Println(" Unhandled style xml tag", v.Name.Local, v.Attr) - } - } - case xml.EndElement: - switch v.Name.Local { - case "cellStyleXfs": - section = 0 - case "cellXfs": - section = 0 - } - default: - if grate.Debug { - log.Printf(" Unhandled style xml tokens %T %+v", tok, tok) - } - } - } - if err == io.EOF { - err = nil - } - return err -} - -func (d *Document) parseSharedStrings(dec *xml.Decoder) error { - val := "" - tok, err := dec.RawToken() - for ; err == nil; tok, err = dec.RawToken() { - switch v := tok.(type) { - case xml.CharData: - val += string(v) - case xml.StartElement: - switch v.Name.Local { - case "si": - val = "" - case "t": - // no attributes to parse, we only want the CharData ... - case "sst": - // main container - default: - if grate.Debug { - log.Println(" Unhandled SST xml tag", v.Name.Local, v.Attr) - } - } - case xml.EndElement: - if v.Name.Local == "si" { - d.strings = append(d.strings, val) - continue - } - default: - if grate.Debug { - log.Printf(" Unhandled SST xml token %T %+v", tok, tok) - } - } - } - if err == io.EOF { - err = nil - } - return err -} diff --git a/vendor/github.com/pbnjay/grate/xlsx/xlsx.go b/vendor/github.com/pbnjay/grate/xlsx/xlsx.go deleted file mode 100644 index 3d5eaad2..00000000 --- a/vendor/github.com/pbnjay/grate/xlsx/xlsx.go +++ /dev/null @@ -1,170 +0,0 @@ -package xlsx - -import ( - "archive/zip" - "encoding/xml" - "errors" - "io" - "log" - "os" - "path/filepath" - "strings" - - "github.com/pbnjay/grate" - "github.com/pbnjay/grate/commonxl" -) - -var _ = grate.Register("xlsx", 5, Open) - -// Document contains an Office Open XML document. -type Document struct { - filename string - f *os.File - r *zip.Reader - primaryDoc string - - // type => id => filename - rels map[string]map[string]string - sheets []*Sheet - strings []string - xfs []uint16 - fmt commonxl.Formatter -} - -func (d *Document) Close() error { - d.xfs = d.xfs[:0] - d.xfs = nil - d.strings = d.strings[:0] - d.strings = nil - d.sheets = d.sheets[:0] - d.sheets = nil - return d.f.Close() -} - -func Open(filename string) (grate.Source, error) { - f, err := os.Open(filename) - if err != nil { - return nil, err - } - info, err := f.Stat() - if err != nil { - return nil, err - } - z, err := zip.NewReader(f, info.Size()) - if err != nil { - return nil, grate.WrapErr(err, grate.ErrNotInFormat) - } - d := &Document{ - filename: filename, - f: f, - r: z, - } - - d.rels = make(map[string]map[string]string, 4) - - // parse the primary relationships - dec, c, err := d.openXML("_rels/.rels") - if err != nil { - return nil, grate.WrapErr(err, grate.ErrNotInFormat) - } - err = d.parseRels(dec, "") - c.Close() - if err != nil { - return nil, grate.WrapErr(err, grate.ErrNotInFormat) - } - if d.primaryDoc == "" { - return nil, errors.New("xlsx: invalid document") - } - - // parse the secondary relationships to primary doc - base := filepath.Base(d.primaryDoc) - sub := strings.TrimSuffix(d.primaryDoc, base) - relfn := filepath.Join(sub, "_rels", base+".rels") - dec, c, err = d.openXML(relfn) - if err != nil { - return nil, err - } - err = d.parseRels(dec, sub) - c.Close() - if err != nil { - return nil, err - } - - // parse the workbook structure - dec, c, err = d.openXML(d.primaryDoc) - if err != nil { - return nil, err - } - err = d.parseWorkbook(dec) - c.Close() - if err != nil { - return nil, err - } - - styn := d.rels["http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles"] - for _, sst := range styn { - // parse the shared string table - dec, c, err = d.openXML(sst) - if err != nil { - return nil, err - } - err = d.parseStyles(dec) - c.Close() - if err != nil { - return nil, err - } - } - - ssn := d.rels["http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings"] - for _, sst := range ssn { - // parse the shared string table - dec, c, err = d.openXML(sst) - if err != nil { - return nil, err - } - err = d.parseSharedStrings(dec) - c.Close() - if err != nil { - return nil, err - } - } - - return d, nil -} - -func (d *Document) openXML(name string) (*xml.Decoder, io.Closer, error) { - if grate.Debug { - log.Println(" openXML", name) - } - for _, zf := range d.r.File { - if zf.Name == name { - zfr, err := zf.Open() - if err != nil { - return nil, nil, err - } - dec := xml.NewDecoder(zfr) - return dec, zfr, nil - } - } - return nil, nil, io.EOF -} - -func (d *Document) List() ([]string, error) { - res := make([]string, 0, len(d.sheets)) - for _, s := range d.sheets { - res = append(res, s.name) - } - return res, nil -} - -func (d *Document) Get(sheetName string) (grate.Collection, error) { - for _, s := range d.sheets { - if s.name == sheetName { - if s.err == errNotLoaded { - s.err = s.parseSheet() - } - return s.wrapped, s.err - } - } - return nil, errors.New("xlsx: sheet not found") -} diff --git a/vendor/modules.txt b/vendor/modules.txt index bde162b6..7fa6d3be 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -343,11 +343,6 @@ github.com/montanaflynn/stats # github.com/moogar0880/problems v0.1.1 ## explicit; go 1.12 github.com/moogar0880/problems -# github.com/pbnjay/grate v0.0.0-20231006022435-3f8e65d74a14 -## explicit; go 1.16 -github.com/pbnjay/grate -github.com/pbnjay/grate/commonxl -github.com/pbnjay/grate/xlsx # github.com/pelletier/go-toml/v2 v2.2.3 ## explicit; go 1.21.0 github.com/pelletier/go-toml/v2