mirror of
https://github.com/Theodor-Springmann-Stiftung/kgpz_web.git
synced 2025-10-29 00:55:32 +00:00
Finalized resolver
This commit is contained in:
29
providers/xmlprovider/models.go
Normal file
29
providers/xmlprovider/models.go
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
package xmlprovider
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
type XMLItem interface {
|
||||||
|
fmt.Stringer
|
||||||
|
Keys() []string
|
||||||
|
Name() string
|
||||||
|
}
|
||||||
|
|
||||||
|
type ILibrary interface {
|
||||||
|
Parse(meta ParseMeta) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type ResolvingMap[T XMLItem] map[string][]Resolved[T]
|
||||||
|
|
||||||
|
type ReferenceResolver[T XMLItem] interface {
|
||||||
|
References() ResolvingMap[T]
|
||||||
|
}
|
||||||
|
|
||||||
|
type Resolved[T XMLItem] struct {
|
||||||
|
Item *T
|
||||||
|
Reference string
|
||||||
|
Category string
|
||||||
|
Cert bool
|
||||||
|
Conjecture bool
|
||||||
|
Comment string
|
||||||
|
MetaData map[string]string
|
||||||
|
}
|
||||||
@@ -7,30 +7,26 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ReferenceResolver interface {
|
|
||||||
GetReferences() map[string][]string
|
|
||||||
}
|
|
||||||
|
|
||||||
type Resolver[T XMLItem] struct {
|
type Resolver[T XMLItem] struct {
|
||||||
index map[string]map[string][]*T // Map[typeName][refID] -> []*T
|
index map[string]map[string][]Resolved[T] // Map[typeName][refID] -> []*T
|
||||||
mu sync.Mutex // Synchronization for thread safety
|
mu sync.Mutex // Synchronization for thread safety
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewResolver[T XMLItem]() *Resolver[T] {
|
func NewResolver[T XMLItem]() *Resolver[T] {
|
||||||
return &Resolver[T]{index: make(map[string]map[string][]*T)}
|
return &Resolver[T]{index: make(map[string]map[string][]Resolved[T])}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resolver[T]) Add(typeName, refID string, item *T) {
|
func (r *Resolver[T]) Add(typeName, refID string, item Resolved[T]) {
|
||||||
r.mu.Lock()
|
r.mu.Lock()
|
||||||
defer r.mu.Unlock()
|
defer r.mu.Unlock()
|
||||||
|
|
||||||
if _, exists := r.index[typeName]; !exists {
|
if _, exists := r.index[typeName]; !exists {
|
||||||
r.index[typeName] = make(map[string][]*T)
|
r.index[typeName] = make(map[string][]Resolved[T])
|
||||||
}
|
}
|
||||||
r.index[typeName][refID] = append(r.index[typeName][refID], item)
|
r.index[typeName][refID] = append(r.index[typeName][refID], item)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resolver[T]) Get(typeName, refID string) ([]*T, error) {
|
func (r *Resolver[T]) Get(typeName, refID string) ([]Resolved[T], error) {
|
||||||
r.mu.Lock()
|
r.mu.Lock()
|
||||||
defer r.mu.Unlock()
|
defer r.mu.Unlock()
|
||||||
|
|
||||||
|
|||||||
@@ -34,17 +34,6 @@ func (p ParseMeta) Failed(path string) bool {
|
|||||||
return slices.Contains(p.FailedPaths, path)
|
return slices.Contains(p.FailedPaths, path)
|
||||||
}
|
}
|
||||||
|
|
||||||
type XMLItem interface {
|
|
||||||
fmt.Stringer
|
|
||||||
Keys() []string
|
|
||||||
Name() string
|
|
||||||
}
|
|
||||||
|
|
||||||
type ILibrary interface {
|
|
||||||
Parse(meta ParseMeta) error
|
|
||||||
Latest() *ParseMeta
|
|
||||||
}
|
|
||||||
|
|
||||||
// An XMLProvider is a struct that holds holds serialized XML data of a specific type. It combines multiple parses IF a succeeded parse can not serialize the data from a path.
|
// An XMLProvider is a struct that holds holds serialized XML data of a specific type. It combines multiple parses IF a succeeded parse can not serialize the data from a path.
|
||||||
type XMLProvider[T XMLItem] struct {
|
type XMLProvider[T XMLItem] struct {
|
||||||
// INFO: map is type map[string]*T
|
// INFO: map is type map[string]*T
|
||||||
@@ -72,7 +61,8 @@ func NewXMLProvider[T XMLItem]() *XMLProvider[T] {
|
|||||||
func (p *XMLProvider[T]) Prepare() {
|
func (p *XMLProvider[T]) Prepare() {
|
||||||
p.mu.Lock()
|
p.mu.Lock()
|
||||||
defer p.mu.Unlock()
|
defer p.mu.Unlock()
|
||||||
p.Array = make([]T, 0)
|
// INFO: We take 1000 here as to not reallocate the memory as mutch.
|
||||||
|
p.Array = make([]T, 0, 1000)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *XMLProvider[T]) Serialize(dataholder XMLRootElement[T], path string, latest ParseMeta) error {
|
func (p *XMLProvider[T]) Serialize(dataholder XMLRootElement[T], path string, latest ParseMeta) error {
|
||||||
@@ -94,13 +84,14 @@ func (p *XMLProvider[T]) Serialize(dataholder XMLRootElement[T], path string, la
|
|||||||
}
|
}
|
||||||
|
|
||||||
// INFO: If the item has a GetReferences method, we add the references to the resolver.
|
// INFO: If the item has a GetReferences method, we add the references to the resolver.
|
||||||
// if refResolver, ok := any(item).(ReferenceResolver); ok {
|
if rr, ok := any(item).(ReferenceResolver[T]); ok {
|
||||||
// for name, ids := range refResolver.GetReferences() {
|
for name, ids := range rr.References() {
|
||||||
// for _, id := range ids {
|
for _, res := range ids {
|
||||||
// p.Resolver.Add(name, id, &item)
|
res.Item = &item
|
||||||
// }
|
p.Resolver.Add(name, res.Reference, res)
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
p.Array = append(p.Array, newItems...)
|
p.Array = append(p.Array, newItems...)
|
||||||
@@ -144,7 +135,7 @@ func (p *XMLProvider[T]) Cleanup(latest ParseMeta) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *XMLProvider[T]) ReverseLookup(item XMLItem) ([]*T, error) {
|
func (p *XMLProvider[T]) ReverseLookup(item XMLItem) ([]Resolved[T], error) {
|
||||||
keys := item.Keys()
|
keys := item.Keys()
|
||||||
|
|
||||||
if len(keys) == 0 {
|
if len(keys) == 0 {
|
||||||
@@ -158,7 +149,7 @@ func (p *XMLProvider[T]) ReverseLookup(item XMLItem) ([]*T, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return []*T{}, nil
|
return []Resolved[T]{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *XMLProvider[T]) String() string {
|
func (a *XMLProvider[T]) String() string {
|
||||||
|
|||||||
@@ -64,14 +64,17 @@ func AgentsView(letterorid string, lib *xmlmodels.Library) *AgentsListView {
|
|||||||
|
|
||||||
// TODO: We won't need to lock the library if we take down the server during parsing
|
// TODO: We won't need to lock the library if we take down the server during parsing
|
||||||
lib.Works.Lock()
|
lib.Works.Lock()
|
||||||
for _, w := range lib.Works.Array {
|
// for _, a := range res.Agents {
|
||||||
if ref, ok := w.ReferencesAgent(letterorid); ok {
|
//
|
||||||
if entry, ok := res.Agents[ref.Ref]; ok {
|
// }
|
||||||
entry.Works = append(entry.Works, WorkByAgent{Work: w, Reference: *ref})
|
// for _, w := range lib.Works.Array {
|
||||||
res.Agents[ref.Ref] = entry
|
// if ref, ok := w.ReferencesAgent(letterorid); ok {
|
||||||
}
|
// if entry, ok := res.Agents[ref.Ref]; ok {
|
||||||
}
|
// entry.Works = append(entry.Works, WorkByAgent{Work: w, Reference: *ref})
|
||||||
}
|
// res.Agents[ref.Ref] = entry
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
lib.Works.Unlock()
|
lib.Works.Unlock()
|
||||||
|
|
||||||
lib.Pieces.Lock()
|
lib.Pieces.Lock()
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package viewmodels
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"maps"
|
"maps"
|
||||||
"slices"
|
"slices"
|
||||||
"sort"
|
"sort"
|
||||||
@@ -33,7 +32,6 @@ func YearView(year int, lib *xmlmodels.Library) (*YearVM, error) {
|
|||||||
|
|
||||||
lib.Issues.Lock()
|
lib.Issues.Lock()
|
||||||
for _, issue := range lib.Issues.Array {
|
for _, issue := range lib.Issues.Array {
|
||||||
log.Printf("Issue: %v", issue)
|
|
||||||
y := issue.Datum.When.Year
|
y := issue.Datum.When.Year
|
||||||
years[y] = true
|
years[y] = true
|
||||||
if y == year {
|
if y == year {
|
||||||
|
|||||||
@@ -61,6 +61,13 @@ func (i Identifier) Keys() []string {
|
|||||||
return i.keys
|
return i.keys
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Reference struct {
|
||||||
|
Ref string `xml:"ref,attr"`
|
||||||
|
Category string `xml:"kat,attr"`
|
||||||
|
Unsicher bool `xml:"unsicher,attr"`
|
||||||
|
Inner Inner
|
||||||
|
}
|
||||||
|
|
||||||
type Value struct {
|
type Value struct {
|
||||||
Chardata string `xml:",chardata"`
|
Chardata string `xml:",chardata"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,12 +40,12 @@ func (l *Library) String() string {
|
|||||||
// INFO: this is the only place where the providers are created. There is no need for locking on access.
|
// INFO: this is the only place where the providers are created. There is no need for locking on access.
|
||||||
func NewLibrary() *Library {
|
func NewLibrary() *Library {
|
||||||
return &Library{
|
return &Library{
|
||||||
Agents: &xmlprovider.XMLProvider[Agent]{},
|
Agents: xmlprovider.NewXMLProvider[Agent](),
|
||||||
Places: &xmlprovider.XMLProvider[Place]{},
|
Places: xmlprovider.NewXMLProvider[Place](),
|
||||||
Works: &xmlprovider.XMLProvider[Work]{},
|
Works: xmlprovider.NewXMLProvider[Work](),
|
||||||
Categories: &xmlprovider.XMLProvider[Category]{},
|
Categories: xmlprovider.NewXMLProvider[Category](),
|
||||||
Issues: &xmlprovider.XMLProvider[Issue]{},
|
Issues: xmlprovider.NewXMLProvider[Issue](),
|
||||||
Pieces: &xmlprovider.XMLProvider[Piece]{},
|
Pieces: xmlprovider.NewXMLProvider[Piece](),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/Theodor-Springmann-Stiftung/kgpz_web/providers/xmlprovider"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -66,6 +67,111 @@ func (p Piece) ReferencesIssue(y, no int) (*IssueRef, bool) {
|
|||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p Piece) References() xmlprovider.ResolvingMap[Piece] {
|
||||||
|
refs := make(xmlprovider.ResolvingMap[Piece])
|
||||||
|
x := CategoryRef{}
|
||||||
|
|
||||||
|
for _, ref := range p.IssueRefs {
|
||||||
|
if ref.When.Year == 0 || ref.Nr == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if ref.Category != "" {
|
||||||
|
refs[x.Name()] = append(refs[x.Name()], xmlprovider.Resolved[Piece]{
|
||||||
|
Reference: ref.Category,
|
||||||
|
Cert: !ref.Unsicher,
|
||||||
|
Conjecture: false,
|
||||||
|
Comment: ref.Inner.InnerXML,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
refs[ref.Name()] = append(refs[ref.Name()], xmlprovider.Resolved[Piece]{
|
||||||
|
Reference: strconv.Itoa(ref.When.Year) + "-" + strconv.Itoa(ref.Nr),
|
||||||
|
Category: ref.Category,
|
||||||
|
Cert: !ref.Unsicher,
|
||||||
|
Conjecture: false,
|
||||||
|
Comment: ref.Inner.InnerXML,
|
||||||
|
MetaData: map[string]string{"Von": strconv.Itoa(ref.Von), "Bis": strconv.Itoa(ref.Bis)},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ref := range p.PlaceRefs {
|
||||||
|
if ref.Category != "" {
|
||||||
|
refs[x.Name()] = append(refs[x.Name()], xmlprovider.Resolved[Piece]{
|
||||||
|
Reference: ref.Category,
|
||||||
|
Cert: !ref.Unsicher,
|
||||||
|
Conjecture: false,
|
||||||
|
Comment: ref.Inner.InnerXML,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
refs[ref.Name()] = append(refs[ref.Name()], xmlprovider.Resolved[Piece]{
|
||||||
|
Reference: ref.Ref,
|
||||||
|
Category: ref.Category,
|
||||||
|
Cert: !ref.Unsicher,
|
||||||
|
Conjecture: false,
|
||||||
|
Comment: ref.Inner.InnerXML,
|
||||||
|
MetaData: map[string]string{},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ref := range p.AgentRefs {
|
||||||
|
if ref.Category != "" {
|
||||||
|
refs[x.Name()] = append(refs[x.Name()], xmlprovider.Resolved[Piece]{
|
||||||
|
Reference: ref.Category,
|
||||||
|
Cert: !ref.Unsicher,
|
||||||
|
Conjecture: false,
|
||||||
|
Comment: ref.Inner.InnerXML,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
refs[ref.Name()] = append(refs[ref.Name()], xmlprovider.Resolved[Piece]{
|
||||||
|
Reference: ref.Ref,
|
||||||
|
Category: ref.Category,
|
||||||
|
Cert: !ref.Unsicher,
|
||||||
|
Conjecture: false,
|
||||||
|
Comment: ref.Inner.InnerXML,
|
||||||
|
MetaData: map[string]string{},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ref := range p.WorkRefs {
|
||||||
|
if ref.Category != "" {
|
||||||
|
refs[x.Name()] = append(refs[x.Name()], xmlprovider.Resolved[Piece]{
|
||||||
|
Reference: ref.Category,
|
||||||
|
Cert: !ref.Unsicher,
|
||||||
|
Conjecture: false,
|
||||||
|
Comment: ref.Inner.InnerXML,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
refs[ref.Name()] = append(refs[ref.Name()], xmlprovider.Resolved[Piece]{
|
||||||
|
Reference: ref.Ref,
|
||||||
|
Category: ref.Category,
|
||||||
|
Cert: !ref.Unsicher,
|
||||||
|
Conjecture: false,
|
||||||
|
MetaData: map[string]string{},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ref := range p.PieceRefs {
|
||||||
|
if ref.Category != "" {
|
||||||
|
refs[x.Name()] = append(refs[x.Name()], xmlprovider.Resolved[Piece]{
|
||||||
|
Reference: ref.Category,
|
||||||
|
Cert: !ref.Unsicher,
|
||||||
|
Conjecture: false,
|
||||||
|
Comment: ref.Inner.InnerXML,
|
||||||
|
MetaData: map[string]string{},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
refs[ref.Name()] = append(refs[ref.Name()], xmlprovider.Resolved[Piece]{
|
||||||
|
Reference: ref.Ref,
|
||||||
|
Category: ref.Category,
|
||||||
|
Cert: !ref.Unsicher,
|
||||||
|
Conjecture: false,
|
||||||
|
Comment: ref.Inner.InnerXML,
|
||||||
|
MetaData: map[string]string{},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return refs
|
||||||
|
}
|
||||||
|
|
||||||
func (p Piece) ReferencesAgent(a string) (*AgentRef, bool) {
|
func (p Piece) ReferencesAgent(a string) (*AgentRef, bool) {
|
||||||
for _, i := range p.AgentRefs {
|
for _, i := range p.AgentRefs {
|
||||||
if strings.HasPrefix(i.Ref, a) {
|
if strings.HasPrefix(i.Ref, a) {
|
||||||
|
|||||||
@@ -2,18 +2,16 @@ package xmlmodels
|
|||||||
|
|
||||||
import "encoding/xml"
|
import "encoding/xml"
|
||||||
|
|
||||||
type Reference struct {
|
|
||||||
Ref string `xml:"ref,attr"`
|
|
||||||
Category string `xml:"kat,attr"`
|
|
||||||
Unsicher bool `xml:"unsicher,attr"`
|
|
||||||
Value
|
|
||||||
}
|
|
||||||
|
|
||||||
type AgentRef struct {
|
type AgentRef struct {
|
||||||
XMLName xml.Name `xml:"akteur"`
|
XMLName xml.Name `xml:"akteur"`
|
||||||
Reference
|
Reference
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ar AgentRef) Name() string {
|
||||||
|
var x Agent
|
||||||
|
return x.Name()
|
||||||
|
}
|
||||||
|
|
||||||
type IssueRef struct {
|
type IssueRef struct {
|
||||||
XMLName xml.Name `xml:"stueck"`
|
XMLName xml.Name `xml:"stueck"`
|
||||||
Nr int `xml:"nr,attr"`
|
Nr int `xml:"nr,attr"`
|
||||||
@@ -24,24 +22,49 @@ type IssueRef struct {
|
|||||||
Reference // Nicht im Schema
|
Reference // Nicht im Schema
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ir IssueRef) Name() string {
|
||||||
|
var x Issue
|
||||||
|
return x.Name()
|
||||||
|
}
|
||||||
|
|
||||||
type PlaceRef struct {
|
type PlaceRef struct {
|
||||||
XMLName xml.Name `xml:"ort"`
|
XMLName xml.Name `xml:"ort"`
|
||||||
Reference
|
Reference
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (pr PlaceRef) Name() string {
|
||||||
|
var x Place
|
||||||
|
return x.Name()
|
||||||
|
}
|
||||||
|
|
||||||
type CategoryRef struct {
|
type CategoryRef struct {
|
||||||
XMLName xml.Name `xml:"kategorie"`
|
XMLName xml.Name `xml:"kategorie"`
|
||||||
Reference
|
Reference
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (cr CategoryRef) Name() string {
|
||||||
|
var x Category
|
||||||
|
return x.Name()
|
||||||
|
}
|
||||||
|
|
||||||
type WorkRef struct {
|
type WorkRef struct {
|
||||||
XMLName xml.Name `xml:"werk"`
|
XMLName xml.Name `xml:"werk"`
|
||||||
Page string `xml:"s,attr"`
|
Page string `xml:"s,attr"`
|
||||||
Reference
|
Reference
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (wr WorkRef) Name() string {
|
||||||
|
var x Work
|
||||||
|
return x.Name()
|
||||||
|
}
|
||||||
|
|
||||||
type PieceRef struct {
|
type PieceRef struct {
|
||||||
XMLName xml.Name `xml:"beitrag"`
|
XMLName xml.Name `xml:"beitrag"`
|
||||||
Page string `xml:"s,attr"`
|
Page string `xml:"s,attr"`
|
||||||
Reference
|
Reference
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (pr PieceRef) Name() string {
|
||||||
|
var x Piece
|
||||||
|
return x.Name()
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ package xmlmodels
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"strings"
|
|
||||||
|
"github.com/Theodor-Springmann-Stiftung/kgpz_web/providers/xmlprovider"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Work struct {
|
type Work struct {
|
||||||
@@ -20,15 +21,6 @@ func (w Work) Name() string {
|
|||||||
return "work"
|
return "work"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p Work) ReferencesAgent(a string) (*AgentRef, bool) {
|
|
||||||
for _, i := range p.AgentRefs {
|
|
||||||
if strings.HasPrefix(i.Ref, a) {
|
|
||||||
return &i, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
|
|
||||||
type Citation struct {
|
type Citation struct {
|
||||||
XMLName xml.Name `xml:"zitation"`
|
XMLName xml.Name `xml:"zitation"`
|
||||||
Title string `xml:"title"`
|
Title string `xml:"title"`
|
||||||
@@ -37,6 +29,22 @@ type Citation struct {
|
|||||||
Inner
|
Inner
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w Work) References() xmlprovider.ResolvingMap[Work] {
|
||||||
|
refs := make(xmlprovider.ResolvingMap[Work])
|
||||||
|
|
||||||
|
for _, ref := range w.AgentRefs {
|
||||||
|
refs[ref.Name()] = append(refs[ref.Name()], xmlprovider.Resolved[Work]{
|
||||||
|
Item: &w, // Reference to the current Work item
|
||||||
|
Reference: ref.Ref, // Reference ID
|
||||||
|
Category: ref.Category, // Category of the reference
|
||||||
|
Cert: !ref.Unsicher, // Certainty flag (true if not unsure)
|
||||||
|
Conjecture: false,
|
||||||
|
Comment: ref.Inner.InnerXML,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return refs
|
||||||
|
}
|
||||||
func (w Work) String() string {
|
func (w Work) String() string {
|
||||||
data, _ := json.MarshalIndent(w, "", " ")
|
data, _ := json.MarshalIndent(w, "", " ")
|
||||||
return string(data)
|
return string(data)
|
||||||
|
|||||||
Reference in New Issue
Block a user