Skip to content

Commit

Permalink
Merge pull request #7 from ChrisTrenkamp/v0.9.10
Browse files Browse the repository at this point in the history
Added support for unmarshalling results into structs and slices.
  • Loading branch information
ChrisTrenkamp authored Aug 26, 2023
2 parents 333462d + fbe9b5b commit 77f3855
Show file tree
Hide file tree
Showing 4 changed files with 446 additions and 0 deletions.
78 changes: 78 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,84 @@ func main() {
}
```

## Unmarshal result into a struct

```go
package main

import (
"bytes"
"fmt"

"github.com/ChrisTrenkamp/xsel/exec"
"github.com/ChrisTrenkamp/xsel/grammar"
"github.com/ChrisTrenkamp/xsel/node"
"github.com/ChrisTrenkamp/xsel/parser"
"github.com/ChrisTrenkamp/xsel/store"
)

func main() {
xml := `
<Root xmlns="http://www.adventure-works.com">
<Customers>
<Customer CustomerID="GREAL">
<CompanyName>Great Lakes Food Market</CompanyName>
<ContactName>Howard Snyder</ContactName>
<ContactTitle>Marketing Manager</ContactTitle>
<FullAddress>
<Address>2732 Baker Blvd.</Address>
<City>Eugene</City>
<Region>OR</Region>
</FullAddress>
</Customer>
<Customer CustomerID="HUNGC">
<CompanyName>Hungry Coyote Import Store</CompanyName>
<ContactName>Yoshi Latimer</ContactName>
<FullAddress>
<Address>City Center Plaza 516 Main St.</Address>
<City>Walla Walla</City>
<Region>WA</Region>
</FullAddress>
</Customer>
</Customers>
</Root>
`

type Address struct {
Address string `xsel:"NS:Address"`
City string `xsel:"NS:City"`
Region string `xsel:"NS:Region"`
}

type Customer struct {
Id string `xsel:"@CustomerID"`
Name string `xsel:"NS:CompanyName"`
ContactName string `xsel:"NS:ContactName"`
Address Address `xsel:"NS:FullAddress"`
}

type Customers struct {
Customers []Customer `xsel:"NS:Customers/NS:Customer"`
}

contextSettings := func(c *exec.ContextSettings) {
c.NamespaceDecls["NS"] = "http://www.adventure-works.com"
}

xpath := grammar.MustBuild(`/NS:Root`)
parser := parser.ReadXml(bytes.NewBufferString(xml))
cursor, _ := store.CreateInMemory(parser)
result, _ := exec.Exec(cursor, &xpath, contextSettings)

customers := Customers{}
exec.Unmarshal(result, &customers, contextSettings) // Remember to check for errors

fmt.Printf("%+v\n", customers)
//{Customers:[{Id:GREAL Name:Great Lakes Food Market ContactName:Howard Snyder Address:{Address:2732 Baker Blvd. City:Eugene Region:OR}}
// {Id:HUNGC Name:Hungry Coyote Import Store ContactName:Yoshi Latimer Address:{Address:City Center Plaza 516 Main St. City:Walla Walla Region:WA}}]}
}
```

## Extensible

`xsel` supplies an XML parser (using the `encoding/xml` package) out of the box, but the XPath logic does not depend directly on XML. It instead depends on the interfaces defined in the [node](https://pkg.go.dev/github.com/ChrisTrenkamp/xsel/node) and [store](https://pkg.go.dev/github.com/ChrisTrenkamp/xsel/store) packages. This means it's possible to use `xsel` for querying against non-XML documents. The [parser](https://pkg.go.dev/github.com/ChrisTrenkamp/xsel/parser) package supplies methods for parsing XML, HTML, and JSON documents.
Expand Down
61 changes: 61 additions & 0 deletions exec/doc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,3 +97,64 @@ func ExampleExec_custom_variables() {

fmt.Println(result)
}

func ExampleExec_unmarshal() {
xml := `
<Root xmlns="http://www.adventure-works.com">
<Customers>
<Customer CustomerID="GREAL">
<CompanyName>Great Lakes Food Market</CompanyName>
<ContactName>Howard Snyder</ContactName>
<ContactTitle>Marketing Manager</ContactTitle>
<FullAddress>
<Address>2732 Baker Blvd.</Address>
<City>Eugene</City>
<Region>OR</Region>
</FullAddress>
</Customer>
<Customer CustomerID="HUNGC">
<CompanyName>Hungry Coyote Import Store</CompanyName>
<ContactName>Yoshi Latimer</ContactName>
<FullAddress>
<Address>City Center Plaza 516 Main St.</Address>
<City>Walla Walla</City>
<Region>WA</Region>
</FullAddress>
</Customer>
</Customers>
</Root>
`

type Address struct {
Address string `xsel:"NS:Address"`
City string `xsel:"NS:City"`
Region string `xsel:"NS:Region"`
}

type Customer struct {
Id string `xsel:"@CustomerID"`
Name string `xsel:"NS:CompanyName"`
ContactName string `xsel:"NS:ContactName"`
Address Address `xsel:"NS:FullAddress"`
}

type Customers struct {
Customers []Customer `xsel:"NS:Customers/NS:Customer"`
}

contextSettings := func(c *exec.ContextSettings) {
c.NamespaceDecls["NS"] = "http://www.adventure-works.com"
}

xpath := grammar.MustBuild(`/NS:Root`)
parser := parser.ReadXml(bytes.NewBufferString(xml))
cursor, _ := store.CreateInMemory(parser)
result, _ := exec.Exec(cursor, &xpath, contextSettings)

customers := Customers{}
exec.Unmarshal(result, &customers, contextSettings) // Remember to check for errors

fmt.Printf("%+v\n", customers)
//{Customers:[{Id:GREAL Name:Great Lakes Food Market ContactName:Howard Snyder Address:{Address:2732 Baker Blvd. City:Eugene Region:OR}}
// {Id:HUNGC Name:Hungry Coyote Import Store ContactName:Yoshi Latimer Address:{Address:City Center Plaza 516 Main St. City:Walla Walla Region:WA}}]}
}
78 changes: 78 additions & 0 deletions exec/exec_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package exec
import (
"bytes"
"math"
"reflect"
"testing"

"github.com/ChrisTrenkamp/xsel/grammar"
Expand Down Expand Up @@ -1550,3 +1551,80 @@ func TestHtmlDocument(t *testing.T) {
t.Error("bad href value")
}
}

func TestUnmarshal(t *testing.T) {
type SubUnmarshalTarget struct {
A *string `xsel:"a"`
Battr bool `xsel:"b/@attr"`
Ignore string
}

type SliceUnmarshalTarget struct {
Elem string `xsel:"."`
}

type UnmarshalTarget struct {
Text string `xsel:"normalize-space(text())"`
Attr float32 `xsel:"node/@attr"`
Attr64 float64 `xsel:"node/@attr"`
Subfield **SubUnmarshalTarget `xsel:"node"`
Slice *[]*SliceUnmarshalTarget `xsel:"slice/elem"`
StringSlice []string `xsel:"slice/elem"`
Uint8 uint8 `xsel:"slice/elem[1]"`
Int8 int8 `xsel:"slice/elem[1]"`
Uint16 uint16 `xsel:"slice/elem[1]"`
Int16 int16 `xsel:"slice/elem[1]"`
Uint32 uint32 `xsel:"slice/elem[1]"`
Int32 int32 `xsel:"slice/elem[1]"`
Uint64 uint64 `xsel:"slice/elem[1]"`
Int64 int64 `xsel:"slice/elem[1]"`
}

xml := `
<root>
foo
<node attr="3.14">
<a>a</a>
<b attr="true"/>
</node>
<slice>
<elem>1</elem>
<elem>2</elem>
<elem>3</elem>
</slice>
</root>
`
nodes := execXmlNodes(t, "/root", xml)
target := UnmarshalTarget{}

if err := Unmarshal(nodes, &target); err != nil {
t.Error(err)
}

a := "a"
subExpected := &SubUnmarshalTarget{
A: &a,
Battr: true,
}
sliceResults := []*SliceUnmarshalTarget{{"1"}, {"2"}, {"3"}}
expected := UnmarshalTarget{
Text: "foo",
Attr: 3.14,
Attr64: 3.14,
Subfield: &subExpected,
Slice: &sliceResults,
StringSlice: []string{"1", "2", "3"},
Uint8: 1,
Int8: 1,
Uint16: 1,
Int16: 1,
Uint32: 1,
Int32: 1,
Uint64: 1,
Int64: 1,
}

if !reflect.DeepEqual(expected, target) {
t.Error("incorrect result")
}
}
Loading

0 comments on commit 77f3855

Please sign in to comment.