Skip to content

Commit

Permalink
Merge pull request #1 from rafaeljusto/full-cnab
Browse files Browse the repository at this point in the history
Full cnab
  • Loading branch information
rafaeljusto committed Aug 21, 2017
2 parents 83fb7fd + 5a84441 commit 7b488c0
Show file tree
Hide file tree
Showing 3 changed files with 870 additions and 253 deletions.
90 changes: 88 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@ gocnab implements encoding and decoding of CNAB (Centro Nacional de Automação
Bancária) data as defined by [FEBRABAN](https://www.febraban.org.br/).

When marshaling it is possible to inform a struct, that will generate 1 CNAB
line, or a slice of structs to generate multiple CNAB lines. On unmarshal a
pointer to a struct or a slice of structs should be used.
line, or a slice of struct to generate multiple CNAB lines. On unmarshal a
pointer to a struct, a pointer to a slice of struct or a mapper
(`map[string]interface{}` for a full CNAB file) should be used.

The library use struct tags to define the position of the field in the CNAB
content `[begin,end)`. It supports the basic attribute types `string` (uppercase
Expand All @@ -32,6 +33,8 @@ go get -u github.com/rafaeljusto/gocnab

## Usage

For working with only a single line of the CNAB file:

```go
package main

Expand Down Expand Up @@ -69,3 +72,86 @@ func main() {
println(e1 == e2)
}
```

And for the whole CNAB file:

```go
package main

import "github.com/rafaeljusto/gocnab"

type header struct {
Identifier string `cnab:"0,1"`
HeaderA int `cnab:"1,5"`
}

type content struct {
Identifier string `cnab:"0,1"`
FieldA int `cnab:"1,20"`
FieldB string `cnab:"20,50"`
FieldC float64 `cnab:"50,60"`
FieldD uint `cnab:"60,70"`
FieldE bool `cnab:"70,71"`
}

type footer struct {
Identifier string `cnab:"0,1"`
FooterA string `cnab:"5,30"`
}

func main() {
h1 := header{
Identifier: "0",
HeaderA: 2,
}

c1 := []content{
{
Identifier: "1",
FieldA: 123,
FieldB: "THIS IS A TEXT",
FieldC: 50.30,
FieldD: 445,
FieldE: true,
},
{
Identifier: "1",
FieldA: 321,
FieldB: "THIS IS ANOTHER TEXT",
FieldC: 30.50,
FieldD: 544,
FieldE: false,
},
}

f1 := footer{
Identifier: "2",
FooterA: "FINAL TEXT",
}

data, err := gocnab.Marshal400(h1, c1, f1)
if err != nil {
println(err)
return
}

var h2 header
var c2 []content
var f2 footer

if err = gocnab.Unmarshal(data, map[string]interface{}{
"0": &h2,
"1": &c2,
"2": &f2,
}); err != nil {
println(err)
return
}

println(h1 == h2)
for i := range c1 {
println(c1[i] == c2[i])
}
println(f1 == f2)
}
```
213 changes: 154 additions & 59 deletions gocnab.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,13 @@ import (
)

// LineBreak defines the control characters at the end of each registry entry.
// It should be the hex encoded 0D0A except for the last one that should be the
// hex encoded 1A, but as we don't known if it is really the last line we will
// let the library user to add it manually.
// It should be the hex encoded 0D0A except for the last one.
const LineBreak = "\r\n"

// FinalControlCharacter defines the control character of the last registry
// entry. It should be the hex encoded 1A.
const FinalControlCharacter = "\x1A"

var (
// ErrUnsupportedType raised when trying to marshal something different from a
// struct or a slice.
Expand All @@ -38,75 +40,104 @@ var (
ErrInvalidFieldTagRange = errors.New("invalid range in cnab tag")
)

// Marshal240 returns the CNAB 240 encoding of v.
func Marshal240(v interface{}) ([]byte, error) {
rv := reflect.ValueOf(v)
// Marshal240 returns the CNAB 240 encoding of vs. The accepted types are struct
// and slice of struct, where only the exported struct fields with the tag
// "cnab" are going to be used. Invalid cnab tag ranges will generate errors.
//
// The following struct field types are supported: string, bool, int, int8,
// int16, int32, int64, uint, uint8, uint16, uint23, uint64, float32, float64,
// gocnab.Marshaler and encoding.TextMarshaler. Where string are transformed to
// uppercase and are left aligned in the CNAB space, booleans are represented as
// 1 or 0, numbers are right aligned with zeros and float decimal separators are
// removed.
//
// When only one parameter is given the generated CNAB line will only have break
// line symbols if the input is a slice of struct. When using multiple
// parameters the library determinate that you are trying to build the full CNAB
// file, so it add the breaking lines and the final control symbol.
func Marshal240(vs ...interface{}) ([]byte, error) {
return marshal(240, vs...)
}

switch rv.Kind() {
case reflect.Struct:
cnab240 := []byte(strings.Repeat(" ", 240))
err := marshal(cnab240, rv)
return cnab240, err
// Marshal400 returns the CNAB 400 encoding of vs. The accepted types are struct
// and slice of struct, where only the exported struct fields with the tag
// "cnab" are going to be used. Invalid cnab tag ranges will generate errors.
//
// The following struct field types are supported: string, bool, int, int8,
// int16, int32, int64, uint, uint8, uint16, uint23, uint64, float32, float64,
// gocnab.Marshaler and encoding.TextMarshaler. Where string are transformed to
// uppercase and are left aligned in the CNAB space, booleans are represented as
// 1 or 0, numbers are right aligned with zeros and float decimal separators are
// removed.
//
// When only one parameter is given the generated CNAB line will only have break
// line symbols if the input is a slice of struct. When using multiple
// parameters the library determinate that you are trying to build the full CNAB
// file, so it add the breaking lines and the final control symbol.
func Marshal400(vs ...interface{}) ([]byte, error) {
return marshal(400, vs...)
}

case reflect.Slice:
var cnab240 []byte
func marshal(lineSize int, vs ...interface{}) ([]byte, error) {
var cnab []byte

for i := 0; i < rv.Len(); i++ {
cnab240Line := []byte(strings.Repeat(" ", 240))
err := marshal(cnab240Line, rv.Index(i))
if err != nil {
return nil, err
}
for i, v := range vs {
cnabLine, err := marshalLine(lineSize, v)
if err != nil {
return nil, err
}

cnab240 = append(cnab240, cnab240Line...)
cnab = append(cnab, cnabLine...)

// don't add line break symbol to the last line
if i < rv.Len()-1 {
cnab240 = append(cnab240, []byte(LineBreak)...)
}
// don't add line break symbol to the last line
if len(vs) > 1 && i < len(vs)-1 {
cnab = append(cnab, []byte(LineBreak)...)
}
}

return cnab240, nil
if len(vs) > 1 && cnab != nil {
cnab = append(cnab, []byte(FinalControlCharacter)...)
}

return nil, ErrUnsupportedType
return cnab, nil
}

// Marshal400 returns the CNAB 400 encoding of v.
func Marshal400(v interface{}) ([]byte, error) {
func marshalLine(lineSize int, v interface{}) ([]byte, error) {
rv := reflect.ValueOf(v)

switch rv.Kind() {
case reflect.Struct:
cnab400 := []byte(strings.Repeat(" ", 400))
err := marshal(cnab400, rv)
return cnab400, err
cnab := []byte(strings.Repeat(" ", lineSize))
if err := marshalStruct(cnab, rv); err != nil {
return nil, err
}

return cnab, nil

case reflect.Slice:
var cnab400 []byte
var cnab []byte

for i := 0; i < rv.Len(); i++ {
cnab400Line := []byte(strings.Repeat(" ", 400))
err := marshal(cnab400Line, rv.Index(i))
if err != nil {
line := []byte(strings.Repeat(" ", lineSize))
if err := marshalStruct(line, rv.Index(i)); err != nil {
return nil, err
}

cnab400 = append(cnab400, cnab400Line...)
cnab = append(cnab, line...)

// don't add line break symbol to the last line
if i < rv.Len()-1 {
cnab400 = append(cnab400, []byte(LineBreak)...)
cnab = append(cnab, []byte(LineBreak)...)
}
}

return cnab400, nil
return cnab, nil
}

return nil, ErrUnsupportedType
}

func marshal(data []byte, v reflect.Value) error {
func marshalStruct(data []byte, v reflect.Value) error {
structType := v.Type()
for i := 0; i < structType.NumField(); i++ {
structField := structType.Field(i)
Expand Down Expand Up @@ -212,46 +243,110 @@ func setFieldContent(data []byte, fieldContent string, begin, end int) {
}

// Unmarshal parses the CNAB-encoded data and stores the result in the value
// pointed to by v.
// pointed to by v. Accepted types of v are: *struct, *[]struct or
// map[string]interface{}.
//
// The following struct field types are supported: string, bool, int, int8,
// int16, int32, int64, uint, uint8, uint16, uint23, uint64, float32, float64,
// gocnab.Unmarshaler and encoding.TextUnmarshaler.
//
// When parsing a full CNAB file we recommend using the map type (mapper) to
// fill different lines into the correct types. Usually the CNAB prefix
// determinate the type used, so the mapper key will be the prefix, and the
// mapper value is the pointer to the type that you're filling. For example, if
// we have a CNAB file where the starter character determinate the type, and for
// "0" is header, "1" is the content line (can repeat many times) and "2" is the
// footer, we could have the following code to unmarshal:
//
// header := struct{ A int `cnab:1,10` }{}
// content := []struct{ B string `cnab:1,10` }{}
// footer := struct{ C bool `cnab:1,2` }{}
//
// cnab.Unmarshal(data, map[string]interface{}{
// "0": &header,
// "1": &content,
// "2": &footer,
// })
func Unmarshal(data []byte, v interface{}) error {
rv := reflect.ValueOf(v)
if rv.Kind() != reflect.Ptr || rv.IsNil() {
if (rv.Kind() != reflect.Ptr && rv.Kind() != reflect.Map) || rv.IsNil() {
return ErrUnsupportedType
}

rvElem := rv.Elem()
if rv.Kind() == reflect.Ptr {
rvElem := rv.Elem()

switch rvElem.Kind() {
case reflect.Struct:
return unmarshal(data, rvElem)
switch rvElem.Kind() {
case reflect.Struct:
return unmarshalStruct(data, rvElem)

case reflect.Slice:
sliceType := rvElem.Type().Elem()
if sliceType.Kind() != reflect.Struct {
return ErrUnsupportedType
case reflect.Slice:
return unmarshalSlice(data, rvElem)
}
}

if mapper, ok := v.(map[string]interface{}); ok {
return unmarshalMapper(data, mapper)
}

cnabLines := bytes.Split(data, []byte(LineBreak))
for _, cnabLine := range cnabLines {
if len(cnabLine) == 0 {
return ErrUnsupportedType
}

func unmarshalMapper(data []byte, mapper map[string]interface{}) error {
cnabLinesGroupBy := make(map[string][]byte)
cnabLines := bytes.Split(data, []byte(LineBreak))

for _, cnabLine := range cnabLines {
if len(cnabLine) == 0 {
continue
}

for id := range mapper {
if !bytes.HasPrefix(cnabLine, []byte(id)) {
continue
}

itemValue := reflect.New(sliceType)
if err := unmarshal(cnabLine, itemValue.Elem()); err != nil {
return err
if _, ok := cnabLinesGroupBy[id]; ok {
cnabLinesGroupBy[id] = append(cnabLinesGroupBy[id], []byte(LineBreak)...)
}

rvElem.Set(reflect.Append(rvElem, itemValue.Elem()))
cnabLinesGroupBy[id] = append(cnabLinesGroupBy[id], cnabLine...)
}
}

return nil
for id, lines := range cnabLinesGroupBy {
if err := Unmarshal(lines, mapper[id]); err != nil {
return err
}
}

return ErrUnsupportedType
return nil
}

func unmarshalSlice(data []byte, v reflect.Value) error {
sliceType := v.Type().Elem()
if sliceType.Kind() != reflect.Struct {
return ErrUnsupportedType
}

cnabLines := bytes.Split(data, []byte(LineBreak))
for _, cnabLine := range cnabLines {
if len(cnabLine) == 0 {
continue
}

itemValue := reflect.New(sliceType)
if err := unmarshalStruct(cnabLine, itemValue.Elem()); err != nil {
return err
}

v.Set(reflect.Append(v, itemValue.Elem()))
}

return nil
}

func unmarshal(data []byte, v reflect.Value) error {
func unmarshalStruct(data []byte, v reflect.Value) error {
structType := v.Type()
for i := 0; i < structType.NumField(); i++ {
structField := structType.Field(i)
Expand Down

0 comments on commit 7b488c0

Please sign in to comment.