Skip to content

Commit

Permalink
feat(source): support xml source (#3)
Browse files Browse the repository at this point in the history
convert xml to json, and then use json parser
  • Loading branch information
tenfyzhong committed Apr 20, 2024
1 parent 215dc8d commit 77cc7f3
Show file tree
Hide file tree
Showing 11 changed files with 224 additions and 27 deletions.
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,11 @@ GLOBAL OPTIONS:
input
--input file, -i file Input file, if not set, it will read from stdio
--rc Read input from clipboard (default: false)
--src type, -s type The source data type, it will use the suffix of the input file if not set, available value: `[json,yaml,proto,thrift,go,csv]`
--input file, -i file Input file, if not set, it will read from stdio
--rc Read input from clipboard (default: false)
--src type, -s type The source data type, it will use the suffix of the input file if not set, available value: `[json,yaml,proto,thrift,go,csv,xml]`
--xml-attribute-tag-prefix prefix Add prefix to xml attribute tag in go field, only works for xml source and go destination (default: ,)
--xml-content-tag-prefix prefix Add prefix to xml content tag in go field, only works for xml source and go destination
output
Expand Down
6 changes: 4 additions & 2 deletions cmd/st2/completions/st2.fish
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,7 @@ complete st2 -r -f -s s -l src -a "json yaml proto thrift go csv" -d 'The source
complete st2 -r -f -s d -l dst -a "go proto thrift" -d 'The destination data type, it will use the suffix of the output file if not set'
complete st2 -r -F -s o -l output -d 'Output file, if not set, it will write to stdout'
complete st2 -l wc -d 'Write output to clipboard'
complete st2 -l prefix -d 'Add prefix to struct name'
complete st2 -l suffix -d 'Add suffix to struct name'
complete st2 -r -f -l prefix -d 'Add prefix to struct name'
complete st2 -r -f -l suffix -d 'Add suffix to struct name'
complete st2 -r -f -l xml-content-tag-prefix -d 'Add `prefix` to xml content tag in go field, only works for xml source and go destination'
complete st2 -r -f -l xml-attribute-tag-prefix -d 'Add `prefix` to xml attribute tag in go field, only works for xml source and go destination'
50 changes: 40 additions & 10 deletions cmd/st2/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,17 @@ import (
)

const (
flagSrc = "src"
flagDst = "dst"
flagInput = "input"
flagOutput = "output"
flagRoot = "root"
flagReadClipboard = "rc"
flagWriteClipboard = "wc"
flagPrefix = "prefix"
flagSuffix = "suffix"
flagSrc = "src"
flagDst = "dst"
flagInput = "input"
flagOutput = "output"
flagRoot = "root"
flagReadClipboard = "rc"
flagWriteClipboard = "wc"
flagPrefix = "prefix"
flagSuffix = "suffix"
flagXMLContentTagPrefix = "xml-content-tag-prefix"
flagXMLAttributeTagPrefix = "xml-attribute-tag-prefix"

categoryCommon = "common"
categoryInput = "input"
Expand Down Expand Up @@ -75,7 +77,18 @@ func action(ctx context.Context, cmd *cli.Command) error {
return fmt.Errorf("src equals to dst\n\n")
}

st2Ctx := st2.NewContext(src, dst, cmd.String(flagRoot), cmd.String(flagPrefix), cmd.String(flagSuffix))
st2Ctx := st2.NewContext(
src,
dst,
cmd.String(flagRoot),
cmd.String(flagPrefix),
cmd.String(flagSuffix),
st2.XMLContext{
ContentTagPrefix: cmd.String(flagXMLContentTagPrefix),
AttributeTagPrefix: cmd.String(flagXMLAttributeTagPrefix),
},
)
fmt.Printf("st2Ctx:%+v\n", st2Ctx)

reader, err := getReader(cmd)
if err != nil {
Expand Down Expand Up @@ -141,6 +154,22 @@ func main() {
TakesFile: true,
Usage: "Input `file`, if not set, it will read from stdio",
},
&cli.StringFlag{
Name: flagXMLContentTagPrefix,
Category: categoryInput,
Required: false,
TakesFile: false,
Usage: "Add `prefix` to xml content tag in go field, only works for xml source and go destination",
},
&cli.StringFlag{
Name: flagXMLAttributeTagPrefix,
Category: categoryInput,
Required: false,
TakesFile: false,
DefaultText: st2.FlagXMLAttributeTagPrefixDefault,
Value: st2.FlagXMLAttributeTagPrefixDefault,
Usage: "Add `prefix` to xml attribute tag in go field, only works for xml source and go destination",
},
&cli.StringFlag{
Name: flagOutput,
Aliases: []string{"o"},
Expand All @@ -154,6 +183,7 @@ func main() {
Aliases: []string{"r"},
Category: categoryCommon,
DefaultText: st2.RootDefault,
Value: st2.RootDefault,
Usage: "The root struct `name`",
},
&cli.BoolFlag{
Expand Down
5 changes: 5 additions & 0 deletions cmd/st2/testdata/a.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<osm version="0.6" generator="CGImap 0.0.2">
<bounds minlat="54.0889580" minlon="12.2487570" maxlat="54.0913900" maxlon="12.2524800"/>
<foo>bar</foo>
</osm>
6 changes: 6 additions & 0 deletions const.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,11 @@ const (
LangCsv = "csv"
LangYaml = "yaml"
LangYml = "yml"
LangXML = "xml"

RootDefault = "Root"

FlagXMLAttributeTagPrefixDefault = ","
)

const (
Expand Down Expand Up @@ -77,6 +80,9 @@ var (
{
Lang: LangCsv,
},
{
Lang: LangXML,
},
}

DestinationLangs = []Lang{
Expand Down
29 changes: 18 additions & 11 deletions context.go
Original file line number Diff line number Diff line change
@@ -1,20 +1,27 @@
package st2

type XMLContext struct {
ContentTagPrefix string
AttributeTagPrefix string
}

// Context struct contains the context running
type Context struct {
Src string
Dst string
Root string
Prefix string
Suffix string
Src string
Dst string
Root string
Prefix string
Suffix string
XMLContext XMLContext
}

func NewContext(src, dst, root, prefix, suffix string) Context {
func NewContext(src, dst, root, prefix, suffix string, xmlContext XMLContext) Context {
return Context{
Src: src,
Dst: dst,
Root: normalizeToken(root, ""),
Prefix: normalizeToken(prefix, ""),
Suffix: normalizeToken(suffix, ""),
Src: src,
Dst: dst,
Root: normalizeToken(root, ""),
Prefix: normalizeToken(prefix, ""),
Suffix: normalizeToken(suffix, ""),
XMLContext: xmlContext,
}
}
2 changes: 2 additions & 0 deletions factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ func CreateParser(ctx Context) Parse {
return NewThriftParser(ctx)
case LangCsv:
return NewCsvParser(ctx)
case LangXML:
return NewXMLParser(ctx)
}
return nil
}
Expand Down
5 changes: 4 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ require (
)

require (
github.com/basgys/goxml2json v1.1.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
Expand All @@ -22,5 +23,7 @@ require (
golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56 // indirect
golang.org/x/image v0.15.0 // indirect
golang.org/x/mobile v0.0.0-20210716004757-34ab1303b554 // indirect
golang.org/x/sys v0.17.0 // indirect
golang.org/x/net v0.24.0 // indirect
golang.org/x/sys v0.19.0 // indirect
golang.org/x/text v0.14.0 // indirect
)
7 changes: 7 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/basgys/goxml2json v1.1.0 h1:4ln5i4rseYfXNd86lGEB+Vi652IsIXIvggKM/BhUKVw=
github.com/basgys/goxml2json v1.1.0/go.mod h1:wH7a5Np/Q4QoECFIU8zTQlZwZkrilY0itPfecMw41Dw=
github.com/cloudwego/thriftgo v0.2.4 h1:o3JTSygQXaNHmggZYqAkfCBdPGWuKH1Q8XCflCvsSIY=
github.com/cloudwego/thriftgo v0.2.4/go.mod h1:8i9AF5uDdWHGqzUhXDlubCjx4MEfKvWXGQlMWyR0tM4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
Expand Down Expand Up @@ -48,6 +50,8 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
Expand All @@ -57,10 +61,13 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
Expand Down
44 changes: 44 additions & 0 deletions xml_parser.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package st2

import (
"bytes"

xj "github.com/basgys/goxml2json"
)

type XMLUnmarshalTagFormat struct {
ContentTagPrefix string
AttributeTagPrefix string
}

func (x XMLUnmarshalTagFormat) Unmarshal(data []byte, v any) error {
reader := bytes.NewReader(data)
root := &xj.Node{}
decoder := xj.NewDecoder(reader)
err := decoder.DecodeWithCustomPrefixes(root, x.ContentTagPrefix, x.AttributeTagPrefix)
if err != nil {
return err
}

buf := new(bytes.Buffer)
encoder := xj.NewEncoder(buf)
err = encoder.Encode(root)
if err != nil {
return err
}

json := buf.Bytes()

return jsonapi.Unmarshal(json, v)
}

func (x XMLUnmarshalTagFormat) TagFormat() string {
return `xml:"%s"`
}

func NewXMLParser(ctx Context) *StructuredParser {
return NewStructuredParser(ctx, &XMLUnmarshalTagFormat{
ContentTagPrefix: ctx.XMLContext.ContentTagPrefix,
AttributeTagPrefix: ctx.XMLContext.AttributeTagPrefix,
})
}
89 changes: 89 additions & 0 deletions xml_parser_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package st2

import (
"reflect"
"testing"
)

func TestXMLUnmarshalTagFormat_Unmarshal(t *testing.T) {
type fields struct {
ContentTagPrefix string
AttributeTagPrefix string
}
type args struct {
data []byte
}
tests := []struct {
name string
fields fields
args args
wantErr bool
expectJson string
}{
{
name: "succ",
fields: fields{
ContentTagPrefix: "",
AttributeTagPrefix: ",",
},
args: args{
data: []byte(`<?xml version="1.0" encoding="UTF-8"?>
<osm version="0.6" generator="CGImap 0.0.2">
<bounds minlat="54.0889580" minlon="12.2487570" maxlat="54.0913900" maxlon="12.2524800"/>
<foo>bar</foo>
</osm>`),
},
wantErr: false,
expectJson: `{"osm":{",version":"0.6",",generator":"CGImap 0.0.2","bounds":{",minlon":"12.2487570",",maxlat":"54.0913900",",maxlon":"12.2524800",",minlat":"54.0889580"},"foo":"bar"}}`,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
x := XMLUnmarshalTagFormat{
ContentTagPrefix: tt.fields.ContentTagPrefix,
AttributeTagPrefix: tt.fields.AttributeTagPrefix,
}
var v any
if err := x.Unmarshal(tt.args.data, &v); (err != nil) != tt.wantErr {
t.Errorf("XMLUnmarshalTagFormat.Unmarshal() error = %v, wantErr %v", err, tt.wantErr)
}

var expectV any
if err := jsonapi.UnmarshalFromString(tt.expectJson, &expectV); err != nil {
t.Errorf("UnmarshalFromString error = %v", err)
}

if !reflect.DeepEqual(v, expectV) {
t.Errorf("%+v should equal to %+v", v, expectV)
}
})
}
}

func TestXMLUnmarshalTagFormat_TagFormat(t *testing.T) {
type fields struct {
ContentTagPrefix string
AttributeTagPrefix string
}
tests := []struct {
name string
fields fields
want string
}{
{
name: "test",
want: `xml:"%s"`,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
x := XMLUnmarshalTagFormat{
ContentTagPrefix: tt.fields.ContentTagPrefix,
AttributeTagPrefix: tt.fields.AttributeTagPrefix,
}
if got := x.TagFormat(); got != tt.want {
t.Errorf("XMLUnmarshalTagFormat.TagFormat() = %v, want %v", got, tt.want)
}
})
}
}

0 comments on commit 77cc7f3

Please sign in to comment.