Skip to content

Commit

Permalink
fix
Browse files Browse the repository at this point in the history
  • Loading branch information
dtelyukh committed Oct 24, 2018
2 parents 1dfd788 + c55258d commit baf6ce0
Show file tree
Hide file tree
Showing 9 changed files with 173 additions and 65 deletions.
9 changes: 3 additions & 6 deletions converter.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,17 @@ import (
)

// Convert converts the given XML document to JSON
func Convert(r io.Reader, ps ...encoderPlugin) (*bytes.Buffer, error) {
func Convert(r io.Reader, ps ...plugin) (*bytes.Buffer, error) {
// Decode XML document
root := &Node{}
err := NewDecoder(r).Decode(root)
err := NewDecoder(r, ps...).Decode(root)
if err != nil {
return nil, err
}

// Then encode it in JSON
buf := new(bytes.Buffer)
e := NewEncoder(buf)
for _, p := range ps {
e = p.AddTo(e)
}
e := NewEncoder(buf, ps...)
err = e.Encode(root)
if err != nil {
return nil, err
Expand Down
42 changes: 20 additions & 22 deletions decoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@ const (

// A Decoder reads and decodes XML objects from an input stream.
type Decoder struct {
r io.Reader
err error
attributePrefix string
contentPrefix string
turnOffDefaultPrefixes bool
excludeAttrs map[string]bool
r io.Reader
err error
attributePrefix string
contentPrefix string
excludeAttrs map[string]bool
formatters []nodeFormatter
}

type element struct {
Expand All @@ -37,40 +37,34 @@ func (dec *Decoder) SetContentPrefix(prefix string) {
dec.contentPrefix = prefix
}

func (dec *Decoder) AddFormatters(formatters []nodeFormatter) {
dec.formatters = formatters
}

func (dec *Decoder) ExcludeAttributes(attrs []string) {
for _, attr := range attrs {
dec.excludeAttrs[attr] = true
}
}

func (dec *Decoder) TurnOffDefaultPrefixes() {
dec.turnOffDefaultPrefixes = true
}

func (dec *Decoder) DecodeWithCustomPrefixes(root *Node, contentPrefix string, attributePrefix string) error {
dec.contentPrefix = contentPrefix
dec.attributePrefix = attributePrefix
return dec.Decode(root)
}

// NewDecoder returns a new decoder that reads from r.
func NewDecoder(r io.Reader) *Decoder {
return &Decoder{r: r, excludeAttrs: map[string]bool{}}
func NewDecoder(r io.Reader, plugins ...plugin) *Decoder {
d := &Decoder{r: r, contentPrefix: contentPrefix, attributePrefix: attrPrefix, excludeAttrs: map[string]bool{}}
for _, p := range plugins {
d = p.AddToDecoder(d)
}
return d
}

// Decode reads the next JSON-encoded value from its
// input and stores it in the value pointed to by v.
func (dec *Decoder) Decode(root *Node) error {

if !dec.turnOffDefaultPrefixes {
if dec.contentPrefix == "" {
dec.contentPrefix = contentPrefix
}
if dec.attributePrefix == "" {
dec.attributePrefix = attrPrefix
}
}

xmlDec := xml.NewDecoder(dec.r)

// That will convert the charset if the provided XML is non-UTF-8
Expand Down Expand Up @@ -118,6 +112,10 @@ func (dec *Decoder) Decode(root *Node) error {
}
}

for _, formatter := range dec.formatters {
formatter.Format(root)
}

return nil
}

Expand Down
6 changes: 2 additions & 4 deletions decoder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,16 +38,14 @@ func TestDecode(t *testing.T) {

}

func TestDecodeWithoutDefaultsAndSomeAttributes(t *testing.T) {
func TestDecodeWithoutDefaultsAndExcludeAttributes(t *testing.T) {
assert := assert.New(t)

// Decode XML document
root := &Node{}
var err error
var dec *Decoder
dec = NewDecoder(strings.NewReader(s))
dec.TurnOffDefaultPrefixes()
dec.ExcludeAttributes([]string{"version", "generator"})
dec = NewDecoder(strings.NewReader(s), WithAttrPrefix(""), ExcludeAttributes([]string{"version", "generator"}))
err = dec.Decode(root)
assert.NoError(err)

Expand Down
12 changes: 3 additions & 9 deletions encoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ type Encoder struct {
}

// NewEncoder returns a new encoder that writes to w.
func NewEncoder(w io.Writer, plugins ...encoderPlugin) *Encoder {
e := &Encoder{w: w}
func NewEncoder(w io.Writer, plugins ...plugin) *Encoder {
e := &Encoder{w: w, contentPrefix: contentPrefix, attributePrefix: attrPrefix}
for _, p := range plugins {
e = p.AddTo(e)
e = p.AddToEncoder(e)
}
return e
}
Expand All @@ -32,12 +32,6 @@ func (enc *Encoder) Encode(root *Node) error {
if root == nil {
return nil
}
if enc.contentPrefix == "" {
enc.contentPrefix = contentPrefix
}
if enc.attributePrefix == "" {
enc.attributePrefix = attrPrefix
}

enc.err = enc.format(root, 0)

Expand Down
4 changes: 2 additions & 2 deletions encoder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,9 @@ func TestEncode(t *testing.T) {
assert.NoError(err)

attr := WithAttrPrefix("test")
attr.AddTo(enc)
attr.AddToEncoder(enc)
content := WithContentPrefix("test2")
content.AddTo(enc)
content.AddToEncoder(enc)

err = enc.Encode(root)
assert.NoError(err)
Expand Down
39 changes: 23 additions & 16 deletions parse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,24 @@ import (
)

type Product struct {
ID int `json:"id"`
Price float64 `json:"price"`
Deleted bool `json:"deleted"`
ID int `json:"id"`
Price float64 `json:"price"`
Deleted bool `json:"deleted"`
Nullable interface{} `json:"nullable"`
}

type StringProduct struct {
ID string `json:"id"`
Price string `json:"price"`
Deleted string `json:"deleted"`
ID string `json:"id"`
Price string `json:"price"`
Deleted string `json:"deleted"`
Nullable string `json:"nullable"`
}

type MixedProduct struct {
ID string `json:"id"`
Price float64 `json:"price"`
Deleted string `json:"deleted"`
ID string `json:"id"`
Price float64 `json:"price"`
Deleted string `json:"deleted"`
Nullable string `json:"nullable"`
}

const (
Expand All @@ -32,19 +35,21 @@ const (
<id>42</id>
<price>13.32</price>
<deleted>true</deleted>
<nullable>null</nullable>
`
)

func TestAllJSTypeParsing(t *testing.T) {
xml := strings.NewReader(productString)
jsBuf, err := Convert(xml, WithTypeConverter(Bool, Int, Float))
jsBuf, err := Convert(xml, WithTypeConverter(Bool, Int, Float, Null))
assert.NoError(t, err, "could not parse test xml")
product := Product{}
err = json.Unmarshal(jsBuf.Bytes(), &product)
assert.NoError(t, err, "could not unmarshal test json")
assert.Equal(t, 42, product.ID, "price should match")
assert.Equal(t, 42, product.ID, "ID should match")
assert.Equal(t, 13.32, product.Price, "price should match")
assert.Equal(t, true, product.Deleted, "price should match")
assert.Equal(t, true, product.Deleted, "deleted should match")
assert.Equal(t, nil, product.Nullable, "nullable should match")
}

func TestStringParsing(t *testing.T) {
Expand All @@ -54,9 +59,10 @@ func TestStringParsing(t *testing.T) {
product := StringProduct{}
err = json.Unmarshal(jsBuf.Bytes(), &product)
assert.NoError(t, err, "could not unmarshal test json")
assert.Equal(t, "42", product.ID, "price should match")
assert.Equal(t, "42", product.ID, "ID should match")
assert.Equal(t, "13.32", product.Price, "price should match")
assert.Equal(t, "true", product.Deleted, "price should match")
assert.Equal(t, "true", product.Deleted, "deleted should match")
assert.Equal(t, "null", product.Nullable, "nullable should match")
}

func TestMixedParsing(t *testing.T) {
Expand All @@ -66,7 +72,8 @@ func TestMixedParsing(t *testing.T) {
product := MixedProduct{}
err = json.Unmarshal(jsBuf.Bytes(), &product)
assert.NoError(t, err, "could not unmarshal test json")
assert.Equal(t, "42", product.ID, "price should match")
assert.Equal(t, "42", product.ID, "ID should match")
assert.Equal(t, 13.32, product.Price, "price should match")
assert.Equal(t, "true", product.Deleted, "price should match")
assert.Equal(t, "true", product.Deleted, "deleted should match")
assert.Equal(t, "null", product.Nullable, "nullable should match")
}
93 changes: 87 additions & 6 deletions plugins.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ import (
)

type (
// an encodePlugin is added to an encoder to allow custom functionality at runtime
encoderPlugin interface {
AddTo(*Encoder) *Encoder
// an plugin is added to an encoder or/and to an decoder to allow custom functionality at runtime
plugin interface {
AddToEncoder(*Encoder) *Encoder
AddToDecoder(*Decoder) *Decoder
}
// a type converter overides the default string sanitization for encoding json
encoderTypeConverter interface {
Expand All @@ -21,6 +22,22 @@ type (

attrPrefixer string
contentPrefixer string

excluder []string

nodesFormatter struct {
list []nodeFormatter
}
nodeFormatter struct {
path string
plugin nodePlugin
}

nodePlugin interface {
AddTo(*Node)
}

arrayFormatter struct{}
)

// WithTypeConverter allows customized js type conversion behavior by passing in the desired JSTypes
Expand All @@ -41,11 +58,15 @@ func (tc *customTypeConverter) parseAsString(t JSType) bool {
}

// Adds the type converter to the encoder
func (tc *customTypeConverter) AddTo(e *Encoder) *Encoder {
func (tc *customTypeConverter) AddToEncoder(e *Encoder) *Encoder {
e.tc = tc
return e
}

func (tc *customTypeConverter) AddToDecoder(d *Decoder) *Decoder {
return d
}

func (tc *customTypeConverter) Convert(s string) string {
// remove quotes if they exists
if strings.HasPrefix(s, `"`) && strings.HasSuffix(s, `"`) {
Expand All @@ -65,8 +86,14 @@ func WithAttrPrefix(prefix string) *attrPrefixer {
return &ap
}

func (a *attrPrefixer) AddTo(e *Encoder) {
func (a *attrPrefixer) AddToEncoder(e *Encoder) *Encoder {
e.attributePrefix = string((*a))
return e
}

func (a *attrPrefixer) AddToDecoder(d *Decoder) *Decoder {
d.attributePrefix = string((*a))
return d
}

// WithContentPrefix appends the given prefix to the json output of xml content fields to preserve namespaces
Expand All @@ -75,6 +102,60 @@ func WithContentPrefix(prefix string) *contentPrefixer {
return &c
}

func (c *contentPrefixer) AddTo(e *Encoder) {
func (c *contentPrefixer) AddToEncoder(e *Encoder) *Encoder {
e.contentPrefix = string((*c))
return e
}

func (c *contentPrefixer) AddToDecoder(d *Decoder) *Decoder {
d.contentPrefix = string((*c))
return d
}

// ExcludeAttributes excludes some xml attributes, for example, xmlns:xsi, xsi:noNamespaceSchemaLocation
func ExcludeAttributes(attrs []string) *excluder {
ex := excluder(attrs)
return &ex
}

func (ex *excluder) AddToEncoder(e *Encoder) *Encoder {
return e
}

func (ex *excluder) AddToDecoder(d *Decoder) *Decoder {
d.ExcludeAttributes([]string((*ex)))
return d
}

// WithNodes formats specific nodes
func WithNodes(n ...nodeFormatter) *nodesFormatter {
return &nodesFormatter{list: n}
}

func (nf *nodesFormatter) AddToEncoder(e *Encoder) *Encoder {
return e
}

func (nf *nodesFormatter) AddToDecoder(d *Decoder) *Decoder {
d.AddFormatters(nf.list)
return d
}

func NodePlugin(path string, plugin nodePlugin) nodeFormatter {
return nodeFormatter{path: path, plugin: plugin}
}

func (nf *nodeFormatter) Format(node *Node) {
child := node.GetChild(nf.path)
if child != nil {
nf.plugin.AddTo(child)
}
}

func ToArray() *arrayFormatter {
return &arrayFormatter{}
}

func (af *arrayFormatter) AddTo(n *Node) {
n.ChildrenAlwaysAsArray = true
}
Loading

0 comments on commit baf6ce0

Please sign in to comment.