Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions element.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"encoding/xml"
"fmt"
"io"
"maps"
"slices"
"strings"
)
Expand Down Expand Up @@ -171,7 +172,7 @@ func (e *element) writeGoType(w io.Writer, options *generateOptions, indentPrefi
if e.root && options.namedRoot {
fmt.Fprintf(w, "%s\tXMLName xml.Name `xml:\"%s\"`\n", indentPrefix, e.name.Local)
}
for _, exportedAttrName := range sortedKeys(attrValuesByExportedName) {
for _, exportedAttrName := range slices.Sorted(maps.Keys(attrValuesByExportedName)) {
attrValue := attrValuesByExportedName[exportedAttrName]
fmt.Fprintf(w, "%s\t%s %s `xml:\"%s,attr\"`\n", indentPrefix, exportedAttrName, attrValue.goType(options), attrValue.name.Local)
}
Expand All @@ -185,7 +186,7 @@ func (e *element) writeGoType(w io.Writer, options *generateOptions, indentPrefi
fmt.Fprintf(w, "%s\t%s string `xml:\",chardata\"`\n", indentPrefix, fieldName)
}

childElements := mapValues(e.childElements)
childElements := slices.Collect(maps.Values(e.childElements))
if options.preserveOrder {
slices.SortFunc(childElements, func(a, b *element) int {
return e.childOrder[a.name] - e.childOrder[b.name]
Expand Down
10 changes: 4 additions & 6 deletions generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ import (
"go/format"
"io"
"io/fs"
"maps"
"os"
"slices"
"sort"
"strings"

"golang.org/x/net/html/charset"
Expand Down Expand Up @@ -306,9 +306,9 @@ func (g *Generator) Generate() ([]byte, error) {
options.simpleTypes[name] = struct{}{}
delete(options.namedTypes, name)
}
typeElements = mapValues(options.namedTypes)
typeElements = slices.Collect(maps.Values(options.namedTypes))
} else {
typeElements = mapValues(g.typeElements)
typeElements = slices.Collect(maps.Values(g.typeElements))
}

if options.preserveOrder {
Expand Down Expand Up @@ -365,9 +365,7 @@ func (g *Generator) Generate() ([]byte, error) {
}
default:
fmt.Fprintf(sourceBuilder, "import (\n")
importPackageNames := mapKeys(options.importPackageNames)
sort.Strings(importPackageNames)
for _, importPackageName := range importPackageNames {
for _, importPackageName := range slices.Sorted(maps.Keys(options.importPackageNames)) {
fmt.Fprintf(sourceBuilder, "\t%q\n", importPackageName)
}
fmt.Fprintf(sourceBuilder, ")\n")
Expand Down
127 changes: 50 additions & 77 deletions xmlstruct.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@
package xmlstruct

import (
"cmp"
"encoding/xml"
"regexp"
"slices"
"strings"
"unicode"
)
Expand All @@ -31,69 +29,9 @@ const (
)

var (
// TitleFirstRuneExportNameFunc returns name.Local with the initial rune
// capitalized.
TitleFirstRuneExportNameFunc = func(name xml.Name) string {
runes := []rune(name.Local)
runes[0] = unicode.ToUpper(runes[0])
return string(runes)
}

kebabOrSnakeCaseWordBoundaryRx = regexp.MustCompile(`[-_]+\pL`)
nonIdentifierRuneRx = regexp.MustCompile(`[^\pL\pN]`)

// DefaultExportNameFunc returns name.Local with kebab- and snakecase words
// converted to UpperCamelCase and any Id suffix converted to ID.
DefaultExportNameFunc = func(name xml.Name) string {
localName := kebabOrSnakeCaseWordBoundaryRx.ReplaceAllStringFunc(name.Local, func(s string) string {
return strings.ToUpper(s[len(s)-1:])
})
localName = nonIdentifierRuneRx.ReplaceAllLiteralString(localName, "_")
runes := []rune(localName)
runes[0] = unicode.ToUpper(runes[0])
if len(runes) > 1 && runes[len(runes)-2] == 'I' && runes[len(runes)-1] == 'd' {
runes[len(runes)-1] = 'D'
}
return string(runes)
}

// DefaultUnexportNameFunc returns name.Local with kebab- and snakecase words
// converted to lowerCamelCase
// Any ID prefix is converted to id, and any Id suffix converted to ID.
DefaultUnexportNameFunc = func(name xml.Name) string {
localName := kebabOrSnakeCaseWordBoundaryRx.ReplaceAllStringFunc(name.Local, func(s string) string {
return strings.ToUpper(s[len(s)-1:])
})
localName = nonIdentifierRuneRx.ReplaceAllLiteralString(localName, "_")
runes := []rune(localName)
runes[0] = unicode.ToLower(runes[0])
if len(runes) > 1 {
if runes[len(runes)-2] == 'I' && runes[len(runes)-1] == 'd' {
runes[len(runes)-1] = 'D'
}
if runes[0] == 'i' && runes[1] == 'D' {
runes[1] = 'd'
}
}
return string(runes)
}
)

var (
// IgnoreNamespaceNameFunc returns name with name.Space cleared. The same
// local name in different namespaces will be treated as identical names.
IgnoreNamespaceNameFunc = func(name xml.Name) xml.Name {
return xml.Name{
Local: name.Local,
}
}

// The IdentityNameFunc returns name unchanged. The same local name in
// different namespaces will be treated as distinct names.
IdentityNameFunc = func(name xml.Name) xml.Name {
return name
}

DefaultNameFunc = IgnoreNamespaceNameFunc
)

Expand Down Expand Up @@ -134,25 +72,60 @@ type generateOptions struct {
emptyElements bool
}

func mapKeys[M ~map[K]V, K comparable, V any](m M) []K {
keys := make([]K, 0, len(m))
for k := range m {
keys = append(keys, k)
// DefaultExportNameFunc returns name.Local with kebab- and snake_case words
// converted to UpperCamelCase and any Id suffix converted to ID.
func DefaultExportNameFunc(name xml.Name) string {
localName := kebabOrSnakeCaseWordBoundaryRx.ReplaceAllStringFunc(name.Local, func(s string) string {
return strings.ToUpper(s[len(s)-1:])
})
localName = nonIdentifierRuneRx.ReplaceAllLiteralString(localName, "_")
runes := []rune(localName)
runes[0] = unicode.ToUpper(runes[0])
if len(runes) > 1 && runes[len(runes)-2] == 'I' && runes[len(runes)-1] == 'd' {
runes[len(runes)-1] = 'D'
}
return string(runes)
}

// DefaultUnexportNameFunc returns name.Local with kebab- and snake_case words
// converted to lowerCamelCase. Any ID prefix is converted to id, and any Id
// suffix converted to ID.
func DefaultUnexportNameFunc(name xml.Name) string {
localName := kebabOrSnakeCaseWordBoundaryRx.ReplaceAllStringFunc(name.Local, func(s string) string {
return strings.ToUpper(s[len(s)-1:])
})
localName = nonIdentifierRuneRx.ReplaceAllLiteralString(localName, "_")
runes := []rune(localName)
runes[0] = unicode.ToLower(runes[0])
if len(runes) > 1 {
if runes[len(runes)-2] == 'I' && runes[len(runes)-1] == 'd' {
runes[len(runes)-1] = 'D'
}
if runes[0] == 'i' && runes[1] == 'D' {
runes[1] = 'd'
}
}
return keys
return string(runes)
}

func mapValues[M ~map[K]V, K comparable, V any](m M) []V {
values := make([]V, 0, len(m))
for _, v := range m {
values = append(values, v)
// IgnoreNamespaceNameFunc returns name with name.Space cleared. The same local
// name in different namespaces will be treated as identical names.
func IgnoreNamespaceNameFunc(name xml.Name) xml.Name {
return xml.Name{
Local: name.Local,
}
return values
}

// sortedKeys returns the keys of m in order.
func sortedKeys[M ~map[K]V, K cmp.Ordered, V any](m M) []K {
keys := mapKeys(m)
slices.Sort(keys)
return keys
// The IdentityNameFunc returns name unchanged. The same local name in different
// namespaces will be treated as distinct names.
func IdentityNameFunc(name xml.Name) xml.Name {
return name
}

// TitleFirstRuneExportNameFunc returns name.Local with the initial rune
// capitalized.
func TitleFirstRuneExportNameFunc(name xml.Name) string {
runes := []rune(name.Local)
runes[0] = unicode.ToUpper(runes[0])
return string(runes)
}