Documentation
¶
Overview ¶
Package ocrline provides Marshal and Unmarshal functions for fixed-width line-based file formats such as Nets AvtaleGiro, OCR Giro, and Bankgirot AutoGiro.
Struct fields are annotated with `ocr` tags that specify their position within a fixed-width line, along with optional alignment and padding directives.
Tag Syntax ¶
ocr:"start:end[,option...]"
Where start and end are zero-based byte positions (like Go slice indices), and options can be:
- align-left, align-right — field alignment (default depends on type)
- pad-zero, pad-space — padding character (default depends on type)
- omitempty — if the field is zero-valued, fill with padding instead
Fields without an `ocr` tag are skipped. Embedded structs are traversed recursively.
Gaps Between Fields ¶
Byte positions not covered by any struct field are filled with '0' by default. To fill specific gaps with a different character (e.g. spaces), implement the Filler interface on the record struct:
func (r PaymentClaim) OCRFill() []ocrline.Fill {
return []ocrline.Fill{
{Start: 21, End: 32, Char: ' '},
}
}
Type Defaults ¶
The library uses the Go type of each field to determine default alignment and padding:
- int, int8..int64, uint..uint64: right-aligned, zero-padded
- string: left-aligned, space-padded
- Types implementing Marshaler / Unmarshaler: delegated to the type
Custom Types ¶
Types can implement Marshaler and Unmarshaler to control their own serialization, similar to encoding/json:
type ServiceCode string
func (s ServiceCode) MarshalOCR() (string, error) { return string(s), nil }
func (s *ServiceCode) UnmarshalOCR(data string) error { *s = ServiceCode(data); return nil }
Validation ¶
Struct tag metadata is parsed, validated, and cached on first use of a type (like encoding/json). Subsequent calls for the same struct type incur no reflection overhead. The following are validated once per type:
- Tag syntax: start and end must be valid integers, start >= 0, end > start
- Overlapping fields: two fields covering the same byte positions are rejected
On each Unmarshal call, field ranges are checked against the input line length.
Line Width ¶
By default, Marshal pads the output to 80 characters. Use MarshalWidth to specify a different line width, or pass 0 to disable padding.
Usage ¶
type Header struct {
FormatCode string `ocr:"0:2"`
ServiceCode string `ocr:"2:4"`
RecordType int `ocr:"6:8"`
}
// Unmarshal
var h Header
err := ocrline.Unmarshal("NY000010...", &h)
// Marshal
line, err := ocrline.Marshal(h)
Example (AvtaleGiroPaymentClaim) ¶
package main
import (
"fmt"
"log"
"strings"
"github.com/karolusz/ocrline"
)
// ServiceCode is a custom type that implements Marshaler/Unmarshaler.
type ServiceCode string
func (s ServiceCode) MarshalOCR() (string, error) { return string(s), nil }
func (s *ServiceCode) UnmarshalOCR(data string) error {
*s = ServiceCode(strings.TrimSpace(data))
return nil
}
// RecordBase demonstrates embedded struct composition.
type RecordBase struct {
FormatCode string `ocr:"0:2"`
ServiceCode ServiceCode `ocr:"2:4"`
TxType string `ocr:"4:6"`
RecordType int `ocr:"6:8"`
}
// PaymentClaim demonstrates AvtaleGiro payment claim with gap fills.
// No filler fields needed - gaps are handled by implementing Filler.
type PaymentClaim struct {
RecordBase
TransactionNumber int `ocr:"8:15"`
NetsDate string `ocr:"15:21"`
Amount int `ocr:"32:49"`
KID string `ocr:"49:74,align-right,pad-space"`
}
// OCRFill specifies that the gap at positions 21:32 should be filled with spaces.
func (r PaymentClaim) OCRFill() []ocrline.Fill {
return []ocrline.Fill{
{Start: 21, End: 32, Char: ' '},
}
}
func main() {
line := "NY2121300000001170604 00000000000000100 008000011688373000000"
var claim PaymentClaim
if err := ocrline.Unmarshal(line, &claim); err != nil {
log.Fatal(err)
}
fmt.Printf("Service: %s\n", claim.ServiceCode)
fmt.Printf("Transaction: %d\n", claim.TransactionNumber)
fmt.Printf("Amount: %d øre\n", claim.Amount)
fmt.Printf("KID: %s\n", claim.KID)
}
Output: Service: 21 Transaction: 1 Amount: 100 øre KID: 008000011688373
Example (CustomWidth) ¶
package main
import (
"fmt"
"log"
"github.com/karolusz/ocrline"
)
func main() {
type ShortRecord struct {
Code string `ocr:"0:2"`
Value int `ocr:"2:10"`
}
r := ShortRecord{Code: "AB", Value: 42}
// Marshal with no padding (width = 0)
line, err := ocrline.MarshalWidth(r, 0)
if err != nil {
log.Fatal(err)
}
fmt.Printf("No padding: %q (len=%d)\n", line, len(line))
// Marshal with custom width
line, err = ocrline.MarshalWidth(r, 40)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Width 40: %q (len=%d)\n", line, len(line))
}
Output: No padding: "AB00000042" (len=10) Width 40: "AB00000042000000000000000000000000000000" (len=40)
Example (Marshal) ¶
package main
import (
"fmt"
"log"
"strings"
"github.com/karolusz/ocrline"
)
// ServiceCode is a custom type that implements Marshaler/Unmarshaler.
type ServiceCode string
func (s ServiceCode) MarshalOCR() (string, error) { return string(s), nil }
func (s *ServiceCode) UnmarshalOCR(data string) error {
*s = ServiceCode(strings.TrimSpace(data))
return nil
}
// RecordBase demonstrates embedded struct composition.
type RecordBase struct {
FormatCode string `ocr:"0:2"`
ServiceCode ServiceCode `ocr:"2:4"`
TxType string `ocr:"4:6"`
RecordType int `ocr:"6:8"`
}
// TransmissionStart demonstrates a complete AvtaleGiro record.
type TransmissionStart struct {
RecordBase
DataTransmitter string `ocr:"8:16"`
TransmissionNumber string `ocr:"16:23"`
DataRecipient string `ocr:"23:31"`
}
func main() {
record := TransmissionStart{
RecordBase: RecordBase{
FormatCode: "NY",
ServiceCode: "00",
TxType: "00",
RecordType: 10,
},
DataTransmitter: "55555555",
TransmissionNumber: "1000081",
DataRecipient: "00008080",
}
line, err := ocrline.Marshal(&record)
if err != nil {
log.Fatal(err)
}
fmt.Println(line)
}
Output: NY000010555555551000081000080800000000000000000000000000000000000000000000000000
Example (RoundTrip) ¶
package main
import (
"fmt"
"log"
"strings"
"github.com/karolusz/ocrline"
)
// ServiceCode is a custom type that implements Marshaler/Unmarshaler.
type ServiceCode string
func (s ServiceCode) MarshalOCR() (string, error) { return string(s), nil }
func (s *ServiceCode) UnmarshalOCR(data string) error {
*s = ServiceCode(strings.TrimSpace(data))
return nil
}
// RecordBase demonstrates embedded struct composition.
type RecordBase struct {
FormatCode string `ocr:"0:2"`
ServiceCode ServiceCode `ocr:"2:4"`
TxType string `ocr:"4:6"`
RecordType int `ocr:"6:8"`
}
// TransmissionStart demonstrates a complete AvtaleGiro record.
type TransmissionStart struct {
RecordBase
DataTransmitter string `ocr:"8:16"`
TransmissionNumber string `ocr:"16:23"`
DataRecipient string `ocr:"23:31"`
}
func main() {
original := "NY000010555555551000081000080800000000000000000000000000000000000000000000000000"
// Unmarshal
var record TransmissionStart
if err := ocrline.Unmarshal(original, &record); err != nil {
log.Fatal(err)
}
// Marshal back
result, err := ocrline.Marshal(&record)
if err != nil {
log.Fatal(err)
}
fmt.Println(result == original)
}
Output: true
Example (Unmarshal) ¶
package main
import (
"fmt"
"log"
"strings"
"github.com/karolusz/ocrline"
)
// ServiceCode is a custom type that implements Marshaler/Unmarshaler.
type ServiceCode string
func (s ServiceCode) MarshalOCR() (string, error) { return string(s), nil }
func (s *ServiceCode) UnmarshalOCR(data string) error {
*s = ServiceCode(strings.TrimSpace(data))
return nil
}
// RecordBase demonstrates embedded struct composition.
type RecordBase struct {
FormatCode string `ocr:"0:2"`
ServiceCode ServiceCode `ocr:"2:4"`
TxType string `ocr:"4:6"`
RecordType int `ocr:"6:8"`
}
// TransmissionStart demonstrates a complete AvtaleGiro record.
type TransmissionStart struct {
RecordBase
DataTransmitter string `ocr:"8:16"`
TransmissionNumber string `ocr:"16:23"`
DataRecipient string `ocr:"23:31"`
}
func main() {
line := "NY000010555555551000081000080800000000000000000000000000000000000000000000000000"
var record TransmissionStart
if err := ocrline.Unmarshal(line, &record); err != nil {
log.Fatal(err)
}
fmt.Printf("Format: %s\n", record.FormatCode)
fmt.Printf("Service: %s\n", record.ServiceCode)
fmt.Printf("Record Type: %d\n", record.RecordType)
fmt.Printf("Transmitter: %s\n", record.DataTransmitter)
fmt.Printf("Number: %s\n", record.TransmissionNumber)
fmt.Printf("Recipient: %s\n", record.DataRecipient)
}
Output: Format: NY Service: 00 Record Type: 10 Transmitter: 55555555 Number: 1000081 Recipient: 00008080
Index ¶
- Constants
- func Marshal(v any) (string, error)
- func MarshalWidth(v any, width int) (string, error)
- func Unmarshal(line string, v any) error
- type Fill
- type Filler
- type InvalidMarshalError
- type InvalidUnmarshalError
- type MarshalFieldError
- type Marshaler
- type OverlapError
- type TagError
- type UnmarshalFieldError
- type UnmarshalRangeError
- type Unmarshaler
Examples ¶
Constants ¶
const DefaultLineWidth = 80
DefaultLineWidth is the default output line width used by Marshal.
Variables ¶
This section is empty.
Functions ¶
func Marshal ¶
Marshal returns the OCR line encoding of v, padded to DefaultLineWidth (80) characters.
v must be a struct or a pointer to a struct. Fields are encoded according to their `ocr` tags. Any positions not covered by struct fields are filled with '0'.
Marshal traverses embedded structs recursively.
func MarshalWidth ¶
MarshalWidth returns the OCR line encoding of v, padded to the specified width. If width is 0, no padding is applied and the line is exactly as wide as the rightmost field end position.
v must be a struct or a pointer to a struct.
Types ¶
type Fill ¶
Fill describes a range of bytes that should be filled with a specific character during marshalling. This is used to fill gaps between fields with characters other than the default '0'.
type Filler ¶
type Filler interface {
OCRFill() []Fill
}
Filler is an optional interface that record structs can implement to specify how gaps (byte positions not covered by any field) should be filled during marshalling.
Gaps not covered by any Fill entry default to '0'.
Example:
func (r PaymentClaim) OCRFill() []ocrline.Fill {
return []ocrline.Fill{
{Start: 21, End: 32, Char: ' '},
}
}
type InvalidMarshalError ¶
InvalidMarshalError describes an invalid argument passed to Marshal.
func (*InvalidMarshalError) Error ¶
func (e *InvalidMarshalError) Error() string
type InvalidUnmarshalError ¶
InvalidUnmarshalError describes an invalid argument passed to Unmarshal.
func (*InvalidUnmarshalError) Error ¶
func (e *InvalidUnmarshalError) Error() string
type MarshalFieldError ¶
MarshalFieldError describes an error marshalling a specific field.
func (*MarshalFieldError) Error ¶
func (e *MarshalFieldError) Error() string
func (*MarshalFieldError) Unwrap ¶
func (e *MarshalFieldError) Unwrap() error
type Marshaler ¶
Marshaler is the interface implemented by types that can marshal themselves into a fixed-width OCR field string.
type OverlapError ¶
OverlapError describes two fields whose ocr tag ranges overlap.
func (*OverlapError) Error ¶
func (e *OverlapError) Error() string
type UnmarshalFieldError ¶
UnmarshalFieldError describes an error unmarshalling a specific field.
func (*UnmarshalFieldError) Error ¶
func (e *UnmarshalFieldError) Error() string
func (*UnmarshalFieldError) Unwrap ¶
func (e *UnmarshalFieldError) Unwrap() error
type UnmarshalRangeError ¶
UnmarshalRangeError describes a field whose ocr tag range exceeds the input line.
func (*UnmarshalRangeError) Error ¶
func (e *UnmarshalRangeError) Error() string
type Unmarshaler ¶
Unmarshaler is the interface implemented by types that can unmarshal a fixed-width OCR field string into themselves.