@@ -122,13 +175,23 @@ templ editorSkeleton() {
}
-templ headExtras() {
+templ headExtras(initialExampleID string) {
+ @bootstrapData(initialExampleID)
+}
+
+templ bootstrapData(initialExampleID string) {
+ @templ.JSONScript("editor-bootstrap", bootstrap(initialExampleID))
}
templ darkModeToggle() {
diff --git a/editor/editor_templ.go b/editor/editor_templ.go
index 8c2e668..d4bae81 100644
--- a/editor/editor_templ.go
+++ b/editor/editor_templ.go
@@ -1,6 +1,6 @@
// Code generated by templ - DO NOT EDIT.
-// templ: version: v0.3.977
+// templ: version: v0.3.1001
package editor
//lint:file-ignore SA4006 This context is only used if a nested component is present.
@@ -9,13 +9,14 @@ import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
import (
+ "github.com/invopop/gobl.dev/editor/examples"
"github.com/invopop/icons"
popui "github.com/invopop/popui.go"
"github.com/invopop/popui.go/props"
"path"
)
-func Page() templ.Component {
+func Page(initialExampleID string) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
@@ -60,7 +61,19 @@ func Page() templ.Component {
}()
}
ctx = templ.InitializeContext(ctx)
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "
0\">")
+ templ_7745c5c3_Err = examplePicker().Render(ctx, templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, " ")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = formatPicker().Render(ctx, templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, " 0\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -74,7 +87,7 @@ func Page() templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -102,7 +115,7 @@ func Page() templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, " Envelop")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, " Envelop")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -116,7 +129,7 @@ func Page() templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, " ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -132,7 +145,7 @@ func Page() templ.Component {
}()
}
ctx = templ.InitializeContext(ctx)
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "Build")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "Build")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -149,7 +162,7 @@ func Page() templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, " ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -165,7 +178,7 @@ func Page() templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, " ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -181,7 +194,7 @@ func Page() templ.Component {
}()
}
ctx = templ.InitializeContext(ctx)
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -189,7 +202,7 @@ func Page() templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -206,6 +219,8 @@ func Page() templ.Component {
Importmap: []props.Import{
{Name: "codemirror", URL: "https://esm.sh/codemirror@6.0.1"},
{Name: "@codemirror/state", URL: "https://esm.sh/@codemirror/state@6"},
+ {Name: "@codemirror/view", URL: "https://esm.sh/@codemirror/view@6"},
+ {Name: "@codemirror/lang-xml", URL: "https://esm.sh/@codemirror/lang-xml@6"},
{Name: "codemirror-json-schema", URL: "https://esm.sh/@invopop/codemirror-json-schema@0.9.3"},
{Name: "@fsegurai/codemirror-theme-vscode-light", URL: "https://esm.sh/@fsegurai/codemirror-theme-vscode-light@6"},
{Name: "@fsegurai/codemirror-theme-vscode-dark", URL: "https://esm.sh/@fsegurai/codemirror-theme-vscode-dark@6"},
@@ -219,7 +234,7 @@ func Page() templ.Component {
Scripts: []props.Script{
{Src: path.Join(AssetPath, popui.Versioned(editorAssets, "editor-data.js"))},
},
- Head: headExtras(),
+ Head: headExtras(initialExampleID),
Data: "editor",
}).Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
@@ -229,7 +244,7 @@ func Page() templ.Component {
})
}
-func editorSkeleton() templ.Component {
+func examplePicker() templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
@@ -250,19 +265,205 @@ func editorSkeleton() templ.Component {
templ_7745c5c3_Var7 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "
")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return nil
+ })
+}
+
+func formatPicker() templ.Component {
+ return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
+ return templ_7745c5c3_CtxErr
+ }
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var11 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var11 == nil {
+ templ_7745c5c3_Var11 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "
")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return nil
+ })
+}
+
+func editorSkeleton() templ.Component {
+ return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
+ return templ_7745c5c3_CtxErr
+ }
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var14 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var14 == nil {
+ templ_7745c5c3_Var14 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for i := range 20 {
if i < 15 {
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "
")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return nil
+ })
+}
+
+func headExtras(initialExampleID string) templ.Component {
+ return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
+ return templ_7745c5c3_CtxErr
+ }
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var15 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var15 == nil {
+ templ_7745c5c3_Var15 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "
")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = bootstrapData(initialExampleID).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -270,7 +471,7 @@ func editorSkeleton() templ.Component {
})
}
-func headExtras() templ.Component {
+func bootstrapData(initialExampleID string) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
@@ -286,12 +487,12 @@ func headExtras() templ.Component {
}()
}
ctx = templ.InitializeContext(ctx)
- templ_7745c5c3_Var8 := templ.GetChildren(ctx)
- if templ_7745c5c3_Var8 == nil {
- templ_7745c5c3_Var8 = templ.NopComponent
+ templ_7745c5c3_Var16 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var16 == nil {
+ templ_7745c5c3_Var16 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "
")
+ templ_7745c5c3_Err = templ.JSONScript("editor-bootstrap", bootstrap(initialExampleID)).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -315,12 +516,12 @@ func darkModeToggle() templ.Component {
}()
}
ctx = templ.InitializeContext(ctx)
- templ_7745c5c3_Var9 := templ.GetChildren(ctx)
- if templ_7745c5c3_Var9 == nil {
- templ_7745c5c3_Var9 = templ.NopComponent
+ templ_7745c5c3_Var17 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var17 == nil {
+ templ_7745c5c3_Var17 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -352,12 +553,12 @@ func goblTitle() templ.Component {
}()
}
ctx = templ.InitializeContext(ctx)
- templ_7745c5c3_Var10 := templ.GetChildren(ctx)
- if templ_7745c5c3_Var10 == nil {
- templ_7745c5c3_Var10 = templ.NopComponent
+ templ_7745c5c3_Var18 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var18 == nil {
+ templ_7745c5c3_Var18 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -365,7 +566,7 @@ func goblTitle() templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "GOBL Editor
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "GOBL Editor
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
diff --git a/editor/examples/catalog.go b/editor/examples/catalog.go
new file mode 100644
index 0000000..bce7b17
--- /dev/null
+++ b/editor/examples/catalog.go
@@ -0,0 +1,116 @@
+// Package examples holds curated starter invoices exposed through the editor.
+package examples
+
+import (
+ "embed"
+ "fmt"
+)
+
+// Example describes a curated starter document.
+type Example struct {
+ ID string `json:"id"`
+ Label string `json:"label"`
+ Type string `json:"type"`
+ Country string `json:"country"`
+ Addon string `json:"addon,omitempty"`
+ Description string `json:"description,omitempty"`
+}
+
+// Group is a set of examples that share the same document Type. Returned by
+// Grouped in the order the types first appear in the catalog.
+type Group struct {
+ Type string `json:"type"`
+ Items []Example `json:"items"`
+}
+
+//go:embed *.json
+var files embed.FS
+
+// Document types. More may appear alongside Invoice as the catalog grows
+// (e.g. "Credit Note", "Delivery", "Payment").
+const (
+ TypeInvoice = "Invoice"
+)
+
+// catalog is the ordered list of available examples. Ordering: plain country
+// variant first, then addon variants, grouped by country alpha-code.
+var catalog = []Example{
+ {ID: "de", Label: "DE — Germany", Type: TypeInvoice, Country: "DE", Description: "Standard German VAT invoice."},
+ {ID: "de-xrechnung", Label: "DE — XRechnung", Type: TypeInvoice, Country: "DE", Addon: "de-xrechnung-v3", Description: "German public-sector e-invoice."},
+ {ID: "es", Label: "ES — Spain", Type: TypeInvoice, Country: "ES", Description: "Standard Spanish VAT invoice."},
+ {ID: "es-verifactu", Label: "ES — VERI*FACTU", Type: TypeInvoice, Country: "ES", Addon: "es-verifactu-v1", Description: "Spanish real-time VAT reporting (AEAT)."},
+ {ID: "es-tbai", Label: "ES — TicketBAI", Type: TypeInvoice, Country: "ES", Addon: "es-tbai-v1", Description: "Basque Country e-invoicing."},
+ {ID: "fr", Label: "FR — France", Type: TypeInvoice, Country: "FR", Description: "Standard French VAT invoice."},
+ {ID: "fr-facturx", Label: "FR — Factur-X", Type: TypeInvoice, Country: "FR", Addon: "fr-facturx-v1", Description: "French hybrid PDF/XML e-invoice."},
+ {ID: "gb", Label: "GB — United Kingdom", Type: TypeInvoice, Country: "GB", Description: "UK VAT invoice."},
+ {ID: "it-fatturapa", Label: "IT — FatturaPA", Type: TypeInvoice, Country: "IT", Addon: "it-sdi-v1", Description: "Italian SdI electronic invoice."},
+ {ID: "nl", Label: "NL — Netherlands", Type: TypeInvoice, Country: "NL", Description: "Dutch VAT invoice."},
+ {ID: "pt", Label: "PT — Portugal", Type: TypeInvoice, Country: "PT", Description: "Portuguese VAT invoice."},
+ {ID: "us", Label: "US — United States", Type: TypeInvoice, Country: "US", Description: "Basic sales-tax invoice."},
+}
+
+// All returns the curated examples in display order.
+func All() []Example {
+ return catalog
+}
+
+// Grouped returns the examples bucketed by Type, in the order each type
+// first appears in the catalog. Items within each group preserve their
+// catalog ordering.
+func Grouped() []Group {
+ groups := make([]Group, 0)
+ index := map[string]int{}
+ for _, e := range catalog {
+ i, ok := index[e.Type]
+ if !ok {
+ index[e.Type] = len(groups)
+ groups = append(groups, Group{Type: e.Type, Items: []Example{e}})
+ continue
+ }
+ groups[i].Items = append(groups[i].Items, e)
+ }
+ return groups
+}
+
+// Get returns the raw JSON for a given example ID.
+func Get(id string) ([]byte, bool) {
+ for _, e := range catalog {
+ if e.ID == id {
+ data, err := files.ReadFile(e.ID + ".json")
+ if err != nil {
+ return nil, false
+ }
+ return data, true
+ }
+ }
+ return nil, false
+}
+
+// Find returns the example metadata for a given ID.
+func Find(id string) (Example, bool) {
+ for _, e := range catalog {
+ if e.ID == id {
+ return e, true
+ }
+ }
+ return Example{}, false
+}
+
+// DefaultFor returns the preferred example for the given ISO-3166-1 alpha-2
+// country code (upper-case). It picks the first entry whose Country matches,
+// falling back to the US example if no match is found. The plain-country
+// variant (no addon) wins when both a plain and addon variant exist, because
+// of the ordering inside catalog.
+func DefaultFor(country string) Example {
+ for _, e := range catalog {
+ if e.Country == country {
+ return e
+ }
+ }
+ for _, e := range catalog {
+ if e.ID == "us" {
+ return e
+ }
+ }
+ panic(fmt.Errorf("examples: us fallback not found"))
+}
diff --git a/editor/examples/de-xrechnung.json b/editor/examples/de-xrechnung.json
new file mode 100644
index 0000000..d435b8c
--- /dev/null
+++ b/editor/examples/de-xrechnung.json
@@ -0,0 +1,78 @@
+{
+ "$schema": "https://gobl.org/draft-0/bill/invoice",
+ "$addons": ["de-xrechnung-v3"],
+ "currency": "EUR",
+ "issue_date": "2026-01-15",
+ "series": "SAMPLE",
+ "code": "001",
+ "supplier": {
+ "name": "Provide One GmbH",
+ "tax_id": {
+ "country": "DE",
+ "code": "111111125"
+ },
+ "inboxes": [
+ {"email": "billing@example.com"}
+ ],
+ "people": [
+ {
+ "name": {"given": "John", "surname": "Doe"},
+ "emails": [{"addr": "billing@example.com"}]
+ }
+ ],
+ "addresses": [
+ {
+ "num": "16",
+ "street": "Dietmar-Hopp-Allee",
+ "locality": "Walldorf",
+ "code": "69190",
+ "country": "DE"
+ }
+ ],
+ "telephones": [{"num": "+49100200300"}]
+ },
+ "customer": {
+ "name": "Sample Consumer",
+ "tax_id": {
+ "country": "DE",
+ "code": "282741168"
+ },
+ "inboxes": [
+ {"email": "billing@example.com"}
+ ],
+ "emails": [{"addr": "email@sample.com"}],
+ "addresses": [
+ {
+ "num": "25",
+ "street": "Werner-Heisenberg-Allee",
+ "locality": "München",
+ "code": "80939",
+ "country": "DE"
+ }
+ ],
+ "telephones": [{"num": "+49100200300"}]
+ },
+ "lines": [
+ {
+ "quantity": "20",
+ "item": {
+ "name": "Development services",
+ "price": "90.00",
+ "unit": "h"
+ },
+ "taxes": [
+ {"cat": "VAT", "rate": "standard"}
+ ]
+ }
+ ],
+ "ordering": {"code": "12345"},
+ "payment": {
+ "instructions": {
+ "key": "credit-transfer+sepa",
+ "credit_transfer": [
+ {"iban": "DE89370400440532013000", "name": "Random Bank Co."}
+ ]
+ },
+ "terms": {"detail": "Please pay within 10 days"}
+ }
+}
diff --git a/editor/examples/de.json b/editor/examples/de.json
new file mode 100644
index 0000000..5d01af7
--- /dev/null
+++ b/editor/examples/de.json
@@ -0,0 +1,55 @@
+{
+ "$schema": "https://gobl.org/draft-0/bill/invoice",
+ "currency": "EUR",
+ "issue_date": "2026-01-15",
+ "series": "SAMPLE",
+ "code": "001",
+ "supplier": {
+ "name": "Provide One GmbH",
+ "tax_id": {
+ "country": "DE",
+ "code": "111111125"
+ },
+ "addresses": [
+ {
+ "num": "16",
+ "street": "Dietmar-Hopp-Allee",
+ "locality": "Walldorf",
+ "code": "69190",
+ "country": "DE"
+ }
+ ]
+ },
+ "customer": {
+ "name": "Sample Consumer",
+ "tax_id": {
+ "country": "DE",
+ "code": "282741168"
+ },
+ "addresses": [
+ {
+ "num": "25",
+ "street": "Werner-Heisenberg-Allee",
+ "locality": "München",
+ "code": "80939",
+ "country": "DE"
+ }
+ ]
+ },
+ "lines": [
+ {
+ "quantity": "20",
+ "item": {
+ "name": "Development services",
+ "price": "90.00",
+ "unit": "h"
+ },
+ "taxes": [
+ {
+ "cat": "VAT",
+ "rate": "standard"
+ }
+ ]
+ }
+ ]
+}
diff --git a/editor/examples/es-tbai.json b/editor/examples/es-tbai.json
new file mode 100644
index 0000000..ceb8619
--- /dev/null
+++ b/editor/examples/es-tbai.json
@@ -0,0 +1,50 @@
+{
+ "$schema": "https://gobl.org/draft-0/bill/invoice",
+ "$addons": ["es-tbai-v1"],
+ "currency": "EUR",
+ "issue_date": "2026-01-15",
+ "series": "SAMPLE",
+ "code": "001",
+ "supplier": {
+ "name": "Provide One S.L.",
+ "tax_id": {
+ "country": "ES",
+ "code": "B98602642"
+ },
+ "emails": [{"addr": "billing@example.com"}],
+ "addresses": [
+ {
+ "num": "42",
+ "street": "San Frantzisko",
+ "locality": "Bilbo",
+ "region": "Bizkaia",
+ "code": "48003",
+ "country": "ES"
+ }
+ ]
+ },
+ "customer": {
+ "name": "Sample Consumer",
+ "tax_id": {
+ "country": "ES",
+ "code": "54387763P"
+ }
+ },
+ "lines": [
+ {
+ "quantity": "20",
+ "item": {
+ "key": "services",
+ "name": "Development services",
+ "price": "90.00",
+ "unit": "h"
+ },
+ "taxes": [
+ {"cat": "VAT", "rate": "standard"}
+ ]
+ }
+ ],
+ "notes": [
+ {"key": "general", "text": "Sample TicketBAI invoice"}
+ ]
+}
diff --git a/editor/examples/es-verifactu.json b/editor/examples/es-verifactu.json
new file mode 100644
index 0000000..f6bb21b
--- /dev/null
+++ b/editor/examples/es-verifactu.json
@@ -0,0 +1,49 @@
+{
+ "$schema": "https://gobl.org/draft-0/bill/invoice",
+ "$addons": ["es-verifactu-v1"],
+ "currency": "EUR",
+ "issue_date": "2026-01-15",
+ "series": "SAMPLE",
+ "code": "001",
+ "supplier": {
+ "name": "Provide One S.L.",
+ "tax_id": {
+ "country": "ES",
+ "code": "B98602642"
+ },
+ "emails": [{"addr": "billing@example.com"}],
+ "addresses": [
+ {
+ "num": "42",
+ "street": "Calle Pradillo",
+ "locality": "Madrid",
+ "region": "Madrid",
+ "code": "28002",
+ "country": "ES"
+ }
+ ]
+ },
+ "customer": {
+ "name": "Sample Consumer",
+ "tax_id": {
+ "country": "ES",
+ "code": "54387763P"
+ }
+ },
+ "lines": [
+ {
+ "quantity": "20",
+ "item": {
+ "name": "Development services",
+ "price": "90.00",
+ "unit": "h"
+ },
+ "taxes": [
+ {"cat": "VAT", "rate": "standard"}
+ ]
+ }
+ ],
+ "notes": [
+ {"key": "general", "text": "Sample VERI*FACTU invoice"}
+ ]
+}
diff --git a/editor/examples/es.json b/editor/examples/es.json
new file mode 100644
index 0000000..338a369
--- /dev/null
+++ b/editor/examples/es.json
@@ -0,0 +1,44 @@
+{
+ "$schema": "https://gobl.org/draft-0/bill/invoice",
+ "currency": "EUR",
+ "issue_date": "2026-01-15",
+ "series": "SAMPLE",
+ "code": "001",
+ "supplier": {
+ "name": "Provide One S.L.",
+ "tax_id": {
+ "country": "ES",
+ "code": "B98602642"
+ },
+ "addresses": [
+ {
+ "num": "42",
+ "street": "Calle Pradillo",
+ "locality": "Madrid",
+ "region": "Madrid",
+ "code": "28002",
+ "country": "ES"
+ }
+ ]
+ },
+ "customer": {
+ "name": "Sample Consumer",
+ "tax_id": {
+ "country": "ES",
+ "code": "54387763P"
+ }
+ },
+ "lines": [
+ {
+ "quantity": "20",
+ "item": {
+ "name": "Development services",
+ "price": "90.00",
+ "unit": "h"
+ },
+ "taxes": [
+ {"cat": "VAT", "rate": "standard"}
+ ]
+ }
+ ]
+}
diff --git a/editor/examples/fr-facturx.json b/editor/examples/fr-facturx.json
new file mode 100644
index 0000000..bc76a3c
--- /dev/null
+++ b/editor/examples/fr-facturx.json
@@ -0,0 +1,61 @@
+{
+ "$schema": "https://gobl.org/draft-0/bill/invoice",
+ "$addons": ["fr-facturx-v1"],
+ "currency": "EUR",
+ "issue_date": "2026-01-15",
+ "series": "SAMPLE",
+ "code": "001",
+ "supplier": {
+ "name": "Provide One SAS",
+ "tax_id": {
+ "country": "FR",
+ "code": "44732829320"
+ },
+ "emails": [{"addr": "billing@example.com"}],
+ "addresses": [
+ {
+ "num": "1",
+ "street": "Rue de Rivoli",
+ "locality": "Paris",
+ "code": "75001",
+ "country": "FR"
+ }
+ ]
+ },
+ "customer": {
+ "name": "Sample Consumer",
+ "tax_id": {
+ "country": "FR",
+ "code": "356000000"
+ },
+ "emails": [{"addr": "email@sample.com"}],
+ "addresses": [
+ {
+ "num": "1",
+ "street": "Rue Sundacsakn",
+ "locality": "Saint-Germain-En-Laye",
+ "code": "75050",
+ "country": "FR"
+ }
+ ]
+ },
+ "lines": [
+ {
+ "quantity": "20",
+ "item": {
+ "name": "Development services",
+ "price": "90.00"
+ },
+ "discounts": [
+ {"percent": "10%", "reason": "Special discount"}
+ ],
+ "taxes": [
+ {"cat": "VAT", "percent": "20%"}
+ ]
+ }
+ ],
+ "payment": {
+ "instructions": {"key": "card"},
+ "terms": {"detail": "Please pay within 10 days"}
+ }
+}
diff --git a/editor/examples/fr.json b/editor/examples/fr.json
new file mode 100644
index 0000000..db07664
--- /dev/null
+++ b/editor/examples/fr.json
@@ -0,0 +1,51 @@
+{
+ "$schema": "https://gobl.org/draft-0/bill/invoice",
+ "currency": "EUR",
+ "issue_date": "2026-01-15",
+ "series": "SAMPLE",
+ "code": "001",
+ "supplier": {
+ "name": "Provide One SAS",
+ "tax_id": {
+ "country": "FR",
+ "code": "44732829320"
+ },
+ "addresses": [
+ {
+ "num": "1",
+ "street": "Rue de Rivoli",
+ "locality": "Paris",
+ "code": "75001",
+ "country": "FR"
+ }
+ ]
+ },
+ "customer": {
+ "name": "Sample Consumer",
+ "tax_id": {
+ "country": "FR",
+ "code": "356000000"
+ },
+ "addresses": [
+ {
+ "num": "1",
+ "street": "Rue Sundacsakn",
+ "locality": "Saint-Germain-En-Laye",
+ "code": "75050",
+ "country": "FR"
+ }
+ ]
+ },
+ "lines": [
+ {
+ "quantity": "20",
+ "item": {
+ "name": "Development services",
+ "price": "90.00"
+ },
+ "taxes": [
+ {"cat": "VAT", "percent": "20%"}
+ ]
+ }
+ ]
+}
diff --git a/editor/examples/gb.json b/editor/examples/gb.json
new file mode 100644
index 0000000..9c1e074
--- /dev/null
+++ b/editor/examples/gb.json
@@ -0,0 +1,45 @@
+{
+ "$schema": "https://gobl.org/draft-0/bill/invoice",
+ "currency": "GBP",
+ "issue_date": "2026-01-15",
+ "series": "SAMPLE",
+ "code": "001",
+ "supplier": {
+ "name": "Provide One Ltd.",
+ "tax_id": {
+ "country": "GB",
+ "code": "844281425"
+ },
+ "addresses": [
+ {
+ "num": "10",
+ "street": "King's Road",
+ "locality": "London",
+ "code": "SW3 4UD",
+ "country": "GB"
+ }
+ ]
+ },
+ "customer": {
+ "name": "Sample Customer",
+ "tax_id": {
+ "country": "GB"
+ }
+ },
+ "lines": [
+ {
+ "quantity": "20",
+ "item": {
+ "name": "Development services",
+ "price": "90.00",
+ "unit": "h"
+ },
+ "taxes": [
+ {
+ "cat": "VAT",
+ "percent": "20%"
+ }
+ ]
+ }
+ ]
+}
diff --git a/editor/examples/it-fatturapa.json b/editor/examples/it-fatturapa.json
new file mode 100644
index 0000000..76d3032
--- /dev/null
+++ b/editor/examples/it-fatturapa.json
@@ -0,0 +1,64 @@
+{
+ "$schema": "https://gobl.org/draft-0/bill/invoice",
+ "$addons": ["it-sdi-v1"],
+ "series": "FT",
+ "code": "001",
+ "currency": "EUR",
+ "issue_date": "2026-01-15",
+ "tax": {
+ "prices_include": "VAT"
+ },
+ "type": "standard",
+ "supplier": {
+ "name": "Company Name S.r.l.",
+ "tax_id": {
+ "country": "IT",
+ "code": "12345678903"
+ },
+ "registration": {
+ "capital": "50000.00",
+ "currency": "EUR",
+ "entry": "123456",
+ "office": "RM"
+ },
+ "addresses": [
+ {
+ "num": "102",
+ "street": "Via California",
+ "locality": "Palermo",
+ "region": "PA",
+ "code": "33213",
+ "country": "IT"
+ }
+ ]
+ },
+ "customer": {
+ "name": "Monica Bellucci",
+ "tax_id": {"country": "IT"},
+ "identities": [
+ {"key": "it-fiscal-code", "code": "RSSGNN60R30H501U"}
+ ],
+ "addresses": [
+ {
+ "num": "23",
+ "street": "Via dei Mille",
+ "locality": "Firenze",
+ "region": "FI",
+ "code": "00100",
+ "country": "IT"
+ }
+ ]
+ },
+ "lines": [
+ {
+ "quantity": "1",
+ "item": {
+ "name": "Cleaning services",
+ "price": "125.00"
+ },
+ "taxes": [
+ {"cat": "VAT", "rate": "standard"}
+ ]
+ }
+ ]
+}
diff --git a/editor/examples/nl.json b/editor/examples/nl.json
new file mode 100644
index 0000000..a3d438b
--- /dev/null
+++ b/editor/examples/nl.json
@@ -0,0 +1,40 @@
+{
+ "$schema": "https://gobl.org/draft-0/bill/invoice",
+ "currency": "EUR",
+ "issue_date": "2026-01-15",
+ "series": "SAMPLE",
+ "code": "001",
+ "supplier": {
+ "name": "Provide One B.V.",
+ "tax_id": {
+ "country": "NL",
+ "code": "000099995B57"
+ },
+ "addresses": [
+ {
+ "num": "55",
+ "street": "Herengracht",
+ "locality": "Amsterdam",
+ "code": "1015 BN",
+ "country": "NL"
+ }
+ ]
+ },
+ "customer": {
+ "name": "Sample Consumer",
+ "tax_id": {"country": "NL"}
+ },
+ "lines": [
+ {
+ "quantity": "20",
+ "item": {
+ "name": "Development services",
+ "price": "90.00",
+ "unit": "h"
+ },
+ "taxes": [
+ {"cat": "VAT", "rate": "standard"}
+ ]
+ }
+ ]
+}
diff --git a/editor/examples/pt.json b/editor/examples/pt.json
new file mode 100644
index 0000000..bbd3fe3
--- /dev/null
+++ b/editor/examples/pt.json
@@ -0,0 +1,40 @@
+{
+ "$schema": "https://gobl.org/draft-0/bill/invoice",
+ "currency": "EUR",
+ "issue_date": "2026-01-15",
+ "series": "SAMPLE",
+ "code": "001",
+ "supplier": {
+ "name": "Provide One Lda.",
+ "tax_id": {
+ "country": "PT",
+ "code": "503504924"
+ },
+ "addresses": [
+ {
+ "num": "12",
+ "street": "Rua Augusta",
+ "locality": "Lisboa",
+ "code": "1100-053",
+ "country": "PT"
+ }
+ ]
+ },
+ "customer": {
+ "name": "Sample Consumer",
+ "tax_id": {"country": "PT"}
+ },
+ "lines": [
+ {
+ "quantity": "20",
+ "item": {
+ "name": "Development services",
+ "price": "90.00",
+ "unit": "h"
+ },
+ "taxes": [
+ {"cat": "VAT", "rate": "standard"}
+ ]
+ }
+ ]
+}
diff --git a/editor/examples/us.json b/editor/examples/us.json
new file mode 100644
index 0000000..9474bf3
--- /dev/null
+++ b/editor/examples/us.json
@@ -0,0 +1,40 @@
+{
+ "$schema": "https://gobl.org/draft-0/bill/invoice",
+ "currency": "USD",
+ "issue_date": "2026-01-15",
+ "series": "SAMPLE",
+ "code": "001",
+ "supplier": {
+ "name": "Acme Inc.",
+ "tax_id": {
+ "country": "US"
+ },
+ "addresses": [
+ {
+ "street": "123 Market Street",
+ "locality": "San Francisco",
+ "region": "CA",
+ "code": "94103",
+ "country": "US"
+ }
+ ]
+ },
+ "customer": {
+ "name": "Sample Customer"
+ },
+ "lines": [
+ {
+ "quantity": "10",
+ "item": {
+ "name": "Development Services",
+ "price": "100.00"
+ },
+ "taxes": [
+ {
+ "cat": "ST",
+ "percent": "8.25%"
+ }
+ ]
+ }
+ ]
+}
diff --git a/editor/geo.go b/editor/geo.go
new file mode 100644
index 0000000..f27d53d
--- /dev/null
+++ b/editor/geo.go
@@ -0,0 +1,52 @@
+package editor
+
+import (
+ "strings"
+
+ "github.com/invopop/gobl.dev/editor/examples"
+)
+
+// pickExampleFromAcceptLanguage walks an Accept-Language header in order of
+// appearance (ignoring q-weights, close enough for a default picker) and
+// returns the first curated example whose country matches a tag's region.
+// Falls back to the US example if no tag yields a match.
+func pickExampleFromAcceptLanguage(header string) examples.Example {
+ for _, raw := range strings.Split(header, ",") {
+ tag := strings.TrimSpace(raw)
+ if i := strings.Index(tag, ";"); i >= 0 {
+ tag = tag[:i]
+ }
+ region := regionFromTag(tag)
+ if region == "" {
+ continue
+ }
+ for _, e := range examples.All() {
+ if e.Country == region {
+ return e
+ }
+ }
+ }
+ return examples.DefaultFor("US")
+}
+
+// regionFromTag extracts the ISO-3166-1 alpha-2 region code from a BCP-47 tag
+// like "en-GB" or "es-419". Returns the region in upper-case, or an empty
+// string if no two-letter region is present.
+func regionFromTag(tag string) string {
+ parts := strings.Split(tag, "-")
+ for _, p := range parts[1:] {
+ if len(p) == 2 && isAlpha(p) {
+ return strings.ToUpper(p)
+ }
+ }
+ return ""
+}
+
+func isAlpha(s string) bool {
+ for _, r := range s {
+ if !((r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z')) {
+ return false
+ }
+ }
+ return true
+}
diff --git a/editor/geo_test.go b/editor/geo_test.go
new file mode 100644
index 0000000..f13c220
--- /dev/null
+++ b/editor/geo_test.go
@@ -0,0 +1,31 @@
+package editor
+
+import "testing"
+
+func TestPickExampleFromAcceptLanguage(t *testing.T) {
+ cases := []struct {
+ name string
+ header string
+ wantID string
+ }{
+ {"empty falls back to US", "", "us"},
+ {"nonsense falls back to US", "xx,yy", "us"},
+ {"tag without region", "en", "us"},
+ {"en-US", "en-US", "us"},
+ {"it-IT maps to IT example", "it-IT", "it-fatturapa"},
+ {"es-ES maps to first ES variant", "es-ES", "es"},
+ {"de-DE maps to DE plain", "de-DE", "de"},
+ {"fr-FR maps to FR plain", "fr-FR", "fr"},
+ {"fallback through list", "xx,de-DE,fr-FR", "de"},
+ {"q-suffix stripped", "en-GB;q=0.9,fr-FR;q=0.8", "gb"},
+ {"region in third position", "zh-Hans-NL", "nl"},
+ }
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ got := pickExampleFromAcceptLanguage(c.header)
+ if got.ID != c.wantID {
+ t.Fatalf("got %q, want %q", got.ID, c.wantID)
+ }
+ })
+ }
+}
diff --git a/go.mod b/go.mod
index 8d44145..80d5650 100644
--- a/go.mod
+++ b/go.mod
@@ -9,10 +9,13 @@ require (
github.com/invopop/gobl v0.502.2
github.com/invopop/gobl.br.nfe v0.0.1
github.com/invopop/gobl.br.nfse v0.0.1
+ github.com/invopop/gobl.cii v0.39.0
github.com/invopop/gobl.fr.ctc v0.0.4
- github.com/invopop/gobl.mx.cfdi v0.61.0
- github.com/invopop/gobl.pt.saft v0.0.1
+ github.com/invopop/gobl.html v0.101.0
+ github.com/invopop/gobl.mx.cfdi v0.62.0
+ github.com/invopop/gobl.pt.saft v0.0.5
github.com/invopop/gobl.sa.zatca v0.0.2
+ github.com/invopop/gobl.ubl v0.56.0
github.com/invopop/icons v0.14.0
github.com/invopop/popui.go v0.30.0
github.com/invopop/yaml v0.3.1
@@ -20,16 +23,17 @@ require (
github.com/mark3labs/mcp-go v0.46.0
github.com/spf13/cobra v1.9.1
github.com/stretchr/testify v1.11.1
- gitlab.com/flimzy/testy v0.14.0
+ gitlab.com/flimzy/testy v0.15.0
gopkg.in/yaml.v3 v3.0.1
)
require (
- cloud.google.com/go v0.118.0 // indirect
+ cloud.google.com/go v0.119.0 // indirect
github.com/Masterminds/semver/v3 v3.3.1 // indirect
github.com/Oudwins/tailwind-merge-go v0.2.1 // indirect
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
github.com/bahlo/generic-list-go v0.2.0 // indirect
+ github.com/beevik/etree v1.6.0 // indirect
github.com/buger/jsonparser v1.1.2 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/expr-lang/expr v1.17.8 // indirect
@@ -37,22 +41,31 @@ require (
github.com/google/jsonschema-go v0.4.2 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
+ github.com/invopop/ctxi18n v0.9.0 // indirect
github.com/invopop/jsonschema v0.14.0 // indirect
+ github.com/invopop/validation v0.8.0 // indirect
+ github.com/invopop/xmlctx v0.13.0 // indirect
+ github.com/invopop/xmldsig v0.14.0 // indirect
+ github.com/jonboulle/clockwork v0.5.0 // indirect
github.com/labstack/echo/v4 v4.15.0 // indirect
github.com/labstack/gommon v0.4.2 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/pb33f/ordered-map/v2 v2.3.1 // indirect
+ github.com/piglig/go-qr v0.2.4 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
+ github.com/russellhaering/goxmldsig v1.6.0 // indirect
github.com/spf13/cast v1.7.1 // indirect
github.com/spf13/pflag v1.0.6 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
+ github.com/yuin/goldmark v1.4.13 // indirect
go.yaml.in/yaml/v4 v4.0.0-rc.2 // indirect
golang.org/x/crypto v0.53.0 // indirect
golang.org/x/net v0.56.0 // indirect
golang.org/x/sys v0.46.0 // indirect
golang.org/x/text v0.38.0 // indirect
+ software.sslmate.com/src/go-pkcs12 v0.7.0 // indirect
)
diff --git a/go.sum b/go.sum
index c023849..6127302 100644
--- a/go.sum
+++ b/go.sum
@@ -1,5 +1,5 @@
-cloud.google.com/go v0.118.0 h1:tvZe1mgqRxpiVa3XlIGMiPcEUbP1gNXELgD4y/IXmeQ=
-cloud.google.com/go v0.118.0/go.mod h1:zIt2pkedt/mo+DQjcT4/L3NDxzHPR29j5HcclNH+9PM=
+cloud.google.com/go v0.119.0 h1:tw7OjErMzJKbbjaEHkrt60KQrK5Wus/boCZ7tm5/RNE=
+cloud.google.com/go v0.119.0/go.mod h1:fwB8QLzTcNevxqi8dcpR+hoMIs3jBherGS9VUBDAW08=
github.com/LastPossum/kamino v0.0.2 h1:Zry5lS7x7TTU1hzzk3Utnp+rX8kk/wWhuW52Ha9As+U=
github.com/LastPossum/kamino v0.0.2/go.mod h1:H8Qm+6DGeNOoXk9hHIOEAQWS9nbo0YwK32pC/7REsOE=
github.com/Masterminds/semver/v3 v3.3.1 h1:QtNSWtVZ3nBfk8mAOu/B6v7FMJ+NHTIgUPi7rj+4nv4=
@@ -12,6 +12,8 @@ github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3d
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
+github.com/beevik/etree v1.6.0 h1:u8Kwy8pp9D9XeITj2Z0XtA5qqZEmtJtuXZRQi+j03eE=
+github.com/beevik/etree v1.6.0/go.mod h1:bh4zJxiIr62SOf9pRzN7UUYaEDa9HEKafK25+sLc0Gc=
github.com/buger/jsonparser v1.1.2 h1:frqHqw7otoVbk5M8LlE/L7HTnIq2v9RX6EJ48i9AxJk=
github.com/buger/jsonparser v1.1.2/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
@@ -33,28 +35,46 @@ github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4=
github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
+github.com/invopop/ctxi18n v0.9.0 h1:BIia4u4OngaHVn/7gvK0w6lccOXVtad8xU0KgJ+mnVA=
+github.com/invopop/ctxi18n v0.9.0/go.mod h1:1Osw+JGYA+anHt0Z4reF36r5FtGHYjGQ+m1X7keIhPc=
github.com/invopop/gobl v0.502.2 h1:guH++uYsy5RjCjn7qGNwFq1xWLceNx0Pmjz/Fm2KCRw=
github.com/invopop/gobl v0.502.2/go.mod h1:HmiEdQreTSQYyNbhs81VKTmI7BAJKYC/6enh9RDwnE0=
github.com/invopop/gobl.br.nfe v0.0.1 h1:ywJycz2wiyeNygbRhuRJg52gTnIECsbZ5+pGFqh3eyE=
github.com/invopop/gobl.br.nfe v0.0.1/go.mod h1:ZIJSVz5257xciD+RkBhyvqjtS46Pgw23idTca6A8ZIk=
github.com/invopop/gobl.br.nfse v0.0.1 h1:BdwNiG7vk7bPMgE4m102P+UysOxmYWhOKIDfFI6RKEA=
github.com/invopop/gobl.br.nfse v0.0.1/go.mod h1:m22voo72ZScRiwIS49wXhzGRahzBO+IEAN0M0Kij3QI=
+github.com/invopop/gobl.cii v0.39.0 h1:1fT1xvUisSPXwi5wdBVSEwpJo4wgix6ADJ0jXhQpc2Q=
+github.com/invopop/gobl.cii v0.39.0/go.mod h1:/uL78fZ5KjE3DcSFsmvNb+mZ6i0m8U1b2Rx14d+AXbc=
github.com/invopop/gobl.fr.ctc v0.0.4 h1:x4eJ3hp9Y9lTCzWFhNqZYm9kCXyuC34EytOuaU03faE=
github.com/invopop/gobl.fr.ctc v0.0.4/go.mod h1:YXJ0G7lCWxUehI4C2xBPL1y6rTNC29t/kOul7wY+3Xs=
-github.com/invopop/gobl.mx.cfdi v0.61.0 h1:f/rtRl5mIgePWQxjLB1UfsU45dQp/4/ZxJjsHGGtjiA=
-github.com/invopop/gobl.mx.cfdi v0.61.0/go.mod h1:2ag6z2QhCMltl5YcxL9yL8IzY+mXuOJe39yhk9YWTsM=
-github.com/invopop/gobl.pt.saft v0.0.1 h1:sXG+HsiN8rZVAxSvv33i/5+8QM8UJbadklmZZUkA5dM=
-github.com/invopop/gobl.pt.saft v0.0.1/go.mod h1:0itJS6OpV+2BcaD4xRwEf4gEOcVeGdrfoghwxB+UWrg=
+github.com/invopop/gobl.html v0.101.0 h1:gD+7RsWhwA9avdgsJ21jr99PJXGxKAKAiXriYOaAfY8=
+github.com/invopop/gobl.html v0.101.0/go.mod h1:1OjBWuOZcMlui6ufbdP7yz43LHaithVMgxKAMllgg+M=
+github.com/invopop/gobl.mx.cfdi v0.62.0 h1:Uwh5wbwiTNHJ9fWOK72ma/xVoJMdPvdMf3VI57PjKRw=
+github.com/invopop/gobl.mx.cfdi v0.62.0/go.mod h1:sXy7JIXauSKRqT0HgHsFICIV9YXuSHK3Quq9uZWZ9L0=
+github.com/invopop/gobl.pt.saft v0.0.5 h1:Hc2Sbd280fDgPi9FznI/Xi95lAMzG8A4YEp6SW4H6uY=
+github.com/invopop/gobl.pt.saft v0.0.5/go.mod h1:z5L+dLFBx6PmEdacnFWehYWgiYZSM2HaPzx6ld+DnQs=
github.com/invopop/gobl.sa.zatca v0.0.2 h1:qU2Y0LP+4mjvZkuG1TOUp/Zs0XxRAzkzQFHy3WOVFEU=
github.com/invopop/gobl.sa.zatca v0.0.2/go.mod h1:tmLCoaKb4X7sZ2zRU8LalA1sgxM2kQv/uqMzZslhE6A=
+github.com/invopop/gobl.ubl v0.56.0 h1:v3nF0HAysOLCDnDMcTS3qnZCFdibeWq4cpZEZZ5+WAs=
+github.com/invopop/gobl.ubl v0.56.0/go.mod h1:R6+ENuOExqbwayTHblx+xwZg2gnjHpLiIytX5BIvW2o=
github.com/invopop/icons v0.14.0 h1:Bk0hF+EI/x3XwWX1IqMeFDMdu766zY3zFBqTkImCdhU=
github.com/invopop/icons v0.14.0/go.mod h1:rXrrY2Rz7Z7KINyQIPMaauqXTZNhVMDI2MYIiux436Y=
github.com/invopop/jsonschema v0.14.0 h1:MHQqLhvpNUZfw+hM3AZDYK7jxO8FZoQeQM77g8iyZjg=
github.com/invopop/jsonschema v0.14.0/go.mod h1:ygm6C2EaVNMBDPpaPlnOA2pFAxBnxGjFlMZABxm9n2I=
+github.com/invopop/phive v0.6.0 h1:wtk5+ieD/muViF6SJXGGVA/vYPzed9NoaCl63TjmWU4=
+github.com/invopop/phive v0.6.0/go.mod h1:2Njf8Ci6tjfZkvq7VfdX5Esjx4Q3lzETciSFZ2afKFA=
github.com/invopop/popui.go v0.30.0 h1:Yx91iSDm7HW2xvGUQCdrRm+HmK1RuA7jxn26+EDbCTg=
github.com/invopop/popui.go v0.30.0/go.mod h1:2YoksZOz2hkSuSF+F2FUJVlnviPlAwmvu/UpD2hJww8=
+github.com/invopop/validation v0.8.0 h1:e5hXHGnONHImgJdonIpNbctg1hlWy1ncaHoVIQ0JWuw=
+github.com/invopop/validation v0.8.0/go.mod h1:nLLeXYPGwUNfdCdJo7/q3yaHO62LSx/3ri7JvgKR9vg=
+github.com/invopop/xmlctx v0.13.0 h1:ZNRMC0O/A5h8InoLVSA7tIjjrhJn/NDBYfByBUpSb+g=
+github.com/invopop/xmlctx v0.13.0/go.mod h1:xZ3Bdf0jq2GcjN6QNroUa+l37kyXDXmDEEKiBj9NAl0=
+github.com/invopop/xmldsig v0.14.0 h1:ROwf32DZtX2EekrrOSjLLiY0s2kPEcqHZculITlZfAw=
+github.com/invopop/xmldsig v0.14.0/go.mod h1:oWDotOqdKbbwmfA1B057TVsNZW/V1XgoXsRgrW5by18=
github.com/invopop/yaml v0.3.1 h1:f0+ZpmhfBSS4MhG+4HYseMdJhoeeopbSKbq5Rpeelso=
github.com/invopop/yaml v0.3.1/go.mod h1:PMOp3nn4/12yEZUFfmOuNHJsZToEEOwoWsT+D81KkeA=
+github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I=
+github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
@@ -73,12 +93,16 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/pb33f/ordered-map/v2 v2.3.1 h1:5319HDO0aw4DA4gzi+zv4FXU9UlSs3xGZ40wcP1nBjY=
github.com/pb33f/ordered-map/v2 v2.3.1/go.mod h1:qxFQgd0PkVUtOMCkTapqotNgzRhMPL7VvaHKbd1HnmQ=
+github.com/piglig/go-qr v0.2.4 h1:G/fY3/Oq0NI1oc0lEhBv75QXUtIW/FmcL9l8D1jIo1M=
+github.com/piglig/go-qr v0.2.4/go.mod h1:funyXL4IdgMPcbICoVm1XweMtZy7Px3kyITTENkmA5w=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
+github.com/russellhaering/goxmldsig v1.6.0 h1:8fdWXEPh2k/NZNQBPFNoVfS3JmzS4ZprY/sAOpKQLks=
+github.com/russellhaering/goxmldsig v1.6.0/go.mod h1:TrnaquDcYxWXfJrOjeMBTX4mLBeYAqaHEyUeWPxZlBM=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
@@ -94,8 +118,12 @@ github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQ
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=
github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4=
-gitlab.com/flimzy/testy v0.14.0 h1:2nZV4Wa1OSJb3rOKHh0GJqvvhtE03zT+sKnPCI0owfQ=
-gitlab.com/flimzy/testy v0.14.0/go.mod h1:m3aGuwdXc+N3QgnH+2Ar2zf1yg0UxNdIaXKvC5SlfMk=
+github.com/yosssi/gohtml v0.0.0-20201013000340-ee4748c638f4 h1:0sw0nJM544SpsihWx1bkXdYLQDlzRflMgFJQ4Yih9ts=
+github.com/yosssi/gohtml v0.0.0-20201013000340-ee4748c638f4/go.mod h1:+ccdNT0xMY1dtc5XBxumbYfOUhmduiGudqaDgD2rVRE=
+github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE=
+github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
+gitlab.com/flimzy/testy v0.15.0 h1:69TL12IpxqGUyL8NuRV3Z5OhIDszXLNqLtfBDhOV3ys=
+gitlab.com/flimzy/testy v0.15.0/go.mod h1:KbAJWCwB++0hEFzeeQRbC7vdZYP/yEha94s4X1wVFrw=
go.yaml.in/yaml/v4 v4.0.0-rc.2 h1:/FrI8D64VSr4HtGIlUtlFMGsm7H7pWTbj6vOLVZcA6s=
go.yaml.in/yaml/v4 v4.0.0-rc.2/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0=
golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto=
@@ -107,8 +135,17 @@ golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw=
golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE=
golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4=
+google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb h1:ITgPrl429bc6+2ZraNSzMDk3I95nmQln2fuPstKwFDE=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
+google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
+google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
+google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
+google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+software.sslmate.com/src/go-pkcs12 v0.7.0 h1:Db8W44cB54TWD7stUFFSWxdfpdn6fZVcDl0w3R4RVM0=
+software.sslmate.com/src/go-pkcs12 v0.7.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI=