Documentation
¶
Overview ¶
Package edifact reads and writes UN/EDIFACT interchanges, as defined by ISO 9735.
An Interchange holds messages, and each Message holds segments. Positions in a Segment count from 1, the way the published segment tables number them. For readers new to the format, EDIFACT in brief walks through a short file and defines the terms used here.
ix, err := edifact.Read(r)
if err != nil {
log.Fatal(err)
}
for _, eqd := range ix.Message(1).FindAll("EQD", "CN") {
fmt.Println(eqd.Element(2))
}
Reading ¶
Read reads the envelope and segments without consulting any specification. Envelope values have named accessors, such as Interchange.Sender and Message.Key. To read a position that has no named accessor, Interchange.Header, FunctionalGroup.Header and Message.Header return the UNB, UNG and UNH segments themselves.
Message.Find returns the first segment with a tag and qualifier, and Message.FindAll every one. An empty qualifier matches any. Segment.Element, Segment.Component and Segment.Components read positions. An absent position, or any position of a missing segment, reads as "", so a chain such as m.Find("RFF", "BM").Element(2) never panics.
Numbers and dates ¶
Segment.Float accepts a dot or a comma as the decimal mark, a leading decimal mark (.5) and an exponent (1E3). Segment.Int accepts an optional minus sign and digits. Both return ErrNumeric for anything else, such as +5, 2., 1,000.5 or NaN, and ErrEmpty for an empty or absent position.
Segment.Time reads a date in the format named by the component after it, the layout DTM uses. Segment.TimeIn takes the format as an argument, and ParseTime works on a plain string. These codes from code list 2379 are supported:
2 DDMMYY 301 YYMMDDHHMMZZZ 3 MMDDYY 401 HHMM 101 YYMMDD 402 HHMMSS 102 CCYYMMDD 404 HHMMSSZZZ 201 YYMMDDHHMM 602 CCYY 202 YYMMDDHHMMSS 610 CCYYMM 203 CCYYMMDDHHMM 718 CCYYMMDD-CCYYMMDD 204 CCYYMMDDHHMMSS 719 CCYYMMDDHHMM-CCYYMMDDHHMM 205 CCYYMMDDHHMMZHHMM
ZZZ is a time zone written as letters. Time reads GMT and UTC as UTC, and returns ErrDateZone for any other zone. A time from a format without a zone keeps its digits as written and comes back in UnknownTimezone, so a caller can attach the right zone. Segment.TimeRange and ParseTimeRange read the range formats 718 and 719.
Filler ¶
Read skips whitespace, control characters such as STX and ETX, and a UTF-8 byte order mark before the first segment or after the last. It also skips line breaks between segments, so files with one segment per line read the same as files with none.
Errors and diagnostics ¶
Read returns an error only when it cannot carry on:
- no UNB at the start, or a UNA with missing or repeated separators
- a service segment out of place, such as UNH inside a message
- a message or functional group still open at the end
- a segment with no terminator, or one longer than MaxSegmentSize
- anything after UNZ
By default, Read collects everything else on Interchange.Diagnostics and succeeds:
- a UNT, UNE or UNZ count or reference that does not match
- a header missing mandatory elements
- no UNZ
- some messages in functional groups and some not
- an interchange or functional group with no messages, or a message with no segments
- below syntax version 4, a message whose type, version, release or agency differs from its functional group's
- a value holding a character outside the interchange's charset, once per segment, and a charset this package does not support
- every problem a specification finds
Each Diagnostic gives its Severity, the message it concerns and the segment.
Read options ¶
Read, ReadWithSpec and Registry.Read take options after their other arguments:
- RejectReadOn returns the first diagnostic at or above a severity as an error. Default: none.
- MaxSegmentSize limits the length of one segment. Default: 16 MiB.
- DefaultDelimiters gives the separators for a file with no UNA. Default: the level A separators.
Specifications and segment groups ¶
Without a specification a message is a flat list of segments. A Spec describes one message type's segment table, so reading can sort the segments into SegmentGroup views and report what does not fit. The library ships no specs. Callers write them for the messages they handle.
A segment group is a view over the message's own segments, so a segment found through one is the same segment Message.Segments holds. Message.FindGroup returns the first repetition of a segment group and Message.FindAllGroups every one. Segment groups nest, and the same methods work at every level. On a missing message or segment group they return nil, so a chain ending in Element gives "" rather than panicking.
A Node is either a segment, named by Tag, or a segment group, named by Group and holding Nodes. Qualifier is the value element 1 must have, and Qualifiers lists several accepted in any order. Required means at least once, and for a node with Qualifiers any one of them is enough. Max is how often it may appear: 0 means once and Unbounded means without limit.
A spec may declare the same tag in several nodes with different qualifiers, and the same segment at two positions with other nodes between them. Registry.Register refuses a segment declared twice in a row, since Max covers that. Validation reports a warning for a segment that matches nothing where it appears, and for one repeated more often than its Max allows. Either stays in the view of the segment group it falls inside. Validation reports an error for an absent required segment or segment group.
Spec.Clone copies a spec deeply, to derive a variant without touching the original.
Keys and the registry ¶
A Key selects a spec from a message's UNH. Its fields are the data elements 0065 message type, 0052 version, 0054 release, 0051 controlling agency and 0057 association assigned code. Is matches a value exactly, a nil field matches anything, and Absent matches only an empty component. When several specs match a message, the one constraining the most fields wins, and a later registration beats an earlier one with the same fields.
A Registry holds specs. Registry.Register adds one and Registry.Load a slice. Registry.Validate checks an interchange built in code and fills in its segment groups, as Registry.Read does.
Choosing which specs apply ¶
The library keeps no registry between calls. Every read or write names the specs it uses:
Read Write Specs used edifact.Read(r) edifact.Write(w, ix) none reg.Read(r) reg.Write(w, ix) those in reg edifact.ReadWithSpec(r, s) edifact.WriteWithSpec(w, ix, s) s alone
Nothing is shared between registries, so separate parts of a program, or separate tests, can each use their own. Build a registry fully before reading with it. After that, any number of goroutines can read and write through it.
In a program that reads many files, build the registry once in a package of its own and call Read through a small function there:
// Package messages reads the EDIFACT messages this program handles.
package messages
var registry = sync.OnceValues(func() (*edifact.Registry, error) {
reg := edifact.NewRegistry()
return reg, reg.Load(specs)
})
func Read(r io.Reader, opts ...edifact.ReadOption) (*edifact.Interchange, error) {
reg, err := registry()
if err != nil {
return nil, err
}
return reg.Read(r, opts...)
}
Writing ¶
NewInterchange returns an interchange in UNOA, syntax version 2. Set the UNB values with its setters, then add messages with Interchange.AddMessage. Interchange.SetDateTime writes the date and time of preparation from a time.Time, in the format the syntax version requires: CCYYMMDD in version 4 and YYMMDD below it. Interchange.SetDate and Interchange.SetTime write them as text. Message.Add builds a segment from simple elements and Message.AddComposite from a slice of components per element. Segment.Set, Segment.SetInt, Segment.SetFloat and Segment.SetTime write one component of an existing segment, extending it as needed.
For formats 301 and 404, whose zone is written as letters, SetTime converts the time to UTC and writes UTC. For other formats it writes the time's own clock, and for 205 its own offset as well.
Interchange.AddMessageTo puts a message in a functional group from Interchange.AddFunctionalGroup. Write writes functional groups in the order they were added, each with its messages, and UNZ then counts functional groups rather than messages.
Message.Add appends to the end of the message. SegmentGroup.Add and SegmentGroup.AddGroup place each segment where its segment group belongs, even after later segment groups exist.
- computes the UNT, UNE and UNZ counts and references
- always writes a UNA declaring Interchange.Delimiters, with a comma as the decimal mark if Delimiters.Decimal is one and a dot otherwise. SetFloat values use that mark. Values set as text keep theirs.
- escapes separators inside values with the release character
- checks every value against the interchange's charset, and encodes UNOC values as ISO 8859-1. A character outside the charset gives ErrCharset, naming its position.
- drops trailing empty components and elements and trailing spaces, as ISO 9735 requires
- returns ErrIncompleteHeader for a UNB, UNG or UNH missing an element ISO 9735 makes mandatory, ErrNoContent for an interchange or functional group with no messages or a message with no segments, and ErrGroupMismatch below syntax version 4 for a message that differs from its functional group
- returns ErrHeaderDate for a UNB or UNG date whose length does not match the syntax version, and ErrSyntaxVersion for UNOW below version 4
- writes nothing if any of this fails
Registry.Write and WriteWithSpec validate every message first. They drop a segment with no data that the spec marks conditional, and write a mandatory one as its bare tag. Without a spec, Write writes every segment and validates nothing.
Write options ¶
- SegmentSuffixLF and SegmentSuffixCRLF write a line break after every segment. Default: none.
- AllowIncompleteHeaders writes headers missing mandatory elements.
- RejectWriteOn returns the first diagnostic at or above a severity as an error. Default: Error.
- FitCharset converts characters outside the charset where it can, rather than refusing them. Default: off.
Reading and writing back ¶
Reading a file and writing it back gives the same data, though not always the same bytes. Write drops line breaks, trailing spaces, trailing empty elements, and segments with no data that the spec marks conditional. It adds a UNA if there was none, and recomputes the trailer counts.
A file without a UNA may use a comma as the decimal mark, and Write declares a dot in the UNA it adds. The library cannot tell which values are numbers, so it leaves them as they are. Read the positions known to be numeric with Float and write them back with SetFloat.
Separators and syntax levels ¶
The UNA declares six characters, in order: component separator, element separator, decimal mark, release character, repetition separator and segment terminator. Interchange keeps what was read in its Delimiters field. A space in the release position means there is none, and writing a value that holds a separator then returns ErrUnreleasable.
The first component of UNB names the syntax level, which is mostly a character set, a Charset. Read reads any level when the file starts with a UNA. Without one, it understands the level A separators ' + : ?, plus * as the repetition separator in syntax version 4, or the separators given with DefaultDelimiters. A file without a UNA that uses the level B separators otherwise gives ErrUnsupportedSeparators.
Charsets ¶
A Charset is one of the syntax levels this package reads and writes:
UNOA level A: upper case A to Z, digits, space and . , - ( ) / = ' + : ?
! " % & * ; < >
UNOB level B: level A and lower case a to z
UNOC level C: ISO 8859-1
UNOW level W: UTF-8, syntax version 4 only
Interchange.SetCharset and Interchange.Charset write and read it. Values in a Segment are always Go strings in UTF-8. Read decodes UNOC from ISO 8859-1 and Write encodes it back. Write refuses a value holding a character outside the charset, and Read reports one as a warning. The level A service characters ' + : ? may appear in values, and Write releases them where they clash with the separators.
With FitCharset, Write first converts what it can: an accented Latin-1 letter becomes its base letter, such as é to e, Æ to AE and ß to ss, and in UNOA lower case becomes upper case. A file naming another syntax level, such as UNOD, is read with a warning and written with its values as they are.
Repetitions ¶
From syntax version 4, one element can hold several values, separated by the repetition separator. Segment.Repetitions counts them and Segment.Repetition reads one. Element, Component, Components and Set work on the first repetition, so code that ignores repetitions keeps working. Segment.SetRepetition writes into one.
To write repetitions, call Interchange.SetSyntaxVersion with 4. Syntax versions 1 to 3 have no repetitions. The fifth UNA position is a space, * is an ordinary character, and writing an element with several repetitions returns ErrNoRepetitionSeparator.
Explicit segment indices ¶
A tag can carry its nesting level and repetition number as components, such as DDD:1:2. Read takes the tag as DDD, so specs match it, and Segment.Index returns the numbers. Segment.SetIndex sets them.
Example ¶
Reading a file and pulling values out by position.
package main
import (
"fmt"
"strings"
"github.com/MaldivesPorts/edifact"
)
// bayplan is a short interchange cut down from a BAPLIE bayplan: two
// containers in their stowage cells. Newlines between segments are optional
// and ignored on read.
const bayplan = `UNA:+.? '
UNB+UNOA:2+SENDER+RECIPIENT+260101:1200+REF1'
UNH+M1+BAPLIE:D:95B:UN'
BGM++BAY1+9'
DTM+137:202601011200:203'
LOC+147+0010204::5'
MEA+WT++KGM:12000'
LOC+9+XXAAA:139:6'
LOC+11+XXBBB:139:6'
EQD+CN+XXXU1234567+22G1'
LOC+147+0010206::5'
MEA+WT++KGM:8500.5'
LOC+9+XXAAA:139:6'
LOC+11+XXCCC:139:6'
EQD+CN+XXXU7654321+45G1'
UNT+14+M1'
UNZ+1+REF1'`
func main() {
ix, err := edifact.Read(strings.NewReader(bayplan))
if err != nil {
fmt.Println(err)
return
}
fmt.Println("from", ix.Sender(), "to", ix.Recipient())
m := ix.Message(1)
fmt.Println("message", m.Key())
for _, eqd := range m.FindAll("EQD", "CN") {
fmt.Println("container", eqd.Element(2), "size and type", eqd.Element(3))
}
}
Output: from SENDER to RECIPIENT message BAPLIE:D:95B:UN:- container XXXU1234567 size and type 22G1 container XXXU7654321 size and type 45G1
Index ¶
- Constants
- Variables
- func Absent() *string
- func Is(v string) *string
- func ParseTime(value, format string) (time.Time, error)
- func ParseTimeRange(value, format string) (start, end time.Time, err error)
- func Write(w io.Writer, ix *Interchange, opts ...WriteOption) error
- func WriteWithSpec(w io.Writer, ix *Interchange, s Spec, opts ...WriteOption) error
- type Charset
- type Delimiters
- type Diagnostic
- type FunctionalGroup
- func (g *FunctionalGroup) Agency() string
- func (g *FunctionalGroup) ControlRef() string
- func (g *FunctionalGroup) Date() string
- func (g *FunctionalGroup) DateTime() (time.Time, error)
- func (g *FunctionalGroup) Header() *Segment
- func (g *FunctionalGroup) MessageType() string
- func (g *FunctionalGroup) Recipient() string
- func (g *FunctionalGroup) Release() string
- func (g *FunctionalGroup) Sender() string
- func (g *FunctionalGroup) SetAgency(a string)
- func (g *FunctionalGroup) SetControlRef(ref string)
- func (g *FunctionalGroup) SetDate(date string)
- func (g *FunctionalGroup) SetDateTime(t time.Time)
- func (g *FunctionalGroup) SetMessageType(t string)
- func (g *FunctionalGroup) SetRecipient(id, qualifier string)
- func (g *FunctionalGroup) SetRelease(release string)
- func (g *FunctionalGroup) SetSender(id, qualifier string)
- func (g *FunctionalGroup) SetTime(time string)
- func (g *FunctionalGroup) SetVersion(version string)
- func (g *FunctionalGroup) Time() string
- func (g *FunctionalGroup) Version() string
- type Interchange
- func (ix *Interchange) AddFunctionalGroup(messageType string) *FunctionalGroup
- func (ix *Interchange) AddMessage(k Key) *Message
- func (ix *Interchange) AddMessageTo(g *FunctionalGroup, k Key) *Message
- func (ix *Interchange) AgreementID() string
- func (ix *Interchange) ApplicationRef() string
- func (ix *Interchange) Charset() (Charset, bool)
- func (ix *Interchange) ControlRef() string
- func (ix *Interchange) Date() string
- func (ix *Interchange) DateTime() (time.Time, error)
- func (ix *Interchange) Grouped() bool
- func (ix *Interchange) Groups() []*FunctionalGroup
- func (ix *Interchange) Header() *Segment
- func (ix *Interchange) IsTest() bool
- func (ix *Interchange) Message(n int) *Message
- func (ix *Interchange) Messages() []*Message
- func (ix *Interchange) Recipient() string
- func (ix *Interchange) Sender() string
- func (ix *Interchange) SetAgreementID(id string)
- func (ix *Interchange) SetApplicationRef(ref string)
- func (ix *Interchange) SetCharset(c Charset)
- func (ix *Interchange) SetControlRef(ref string)
- func (ix *Interchange) SetDate(date string)
- func (ix *Interchange) SetDateTime(t time.Time)
- func (ix *Interchange) SetRecipient(id, qualifier string)
- func (ix *Interchange) SetSender(id, qualifier string)
- func (ix *Interchange) SetSyntaxVersion(v int) error
- func (ix *Interchange) SetTest(test bool)
- func (ix *Interchange) SetTime(time string)
- func (ix *Interchange) SyntaxVersion() int
- func (ix *Interchange) Time() string
- type Key
- type Message
- func (m *Message) Add(tag string, elems ...string) *Segment
- func (m *Message) AddComposite(tag string, elems ...[]string) *Segment
- func (m *Message) AddGroup(name string) *SegmentGroup
- func (m *Message) Find(tag, qualifier string) *Segment
- func (m *Message) FindAll(tag, qualifier string) []*Segment
- func (m *Message) FindAllGroups(name string) []*SegmentGroup
- func (m *Message) FindGroup(name string) *SegmentGroup
- func (m *Message) FunctionalGroup() *FunctionalGroup
- func (m *Message) Groups() []*SegmentGroup
- func (m *Message) Header() *Segment
- func (m *Message) Key() Key
- func (m *Message) Ref() string
- func (m *Message) Segments() []*Segment
- func (m *Message) SetKey(k Key)
- func (m *Message) SetRef(ref string)
- type Node
- type ReadOption
- type Registry
- func (r *Registry) Len() int
- func (r *Registry) Load(specs []Spec) error
- func (r *Registry) Lookup(k Key) (*Spec, bool)
- func (r *Registry) Read(src io.Reader, opts ...ReadOption) (*Interchange, error)
- func (r *Registry) Register(s Spec) error
- func (r *Registry) Validate(ix *Interchange) []Diagnostic
- func (r *Registry) Write(w io.Writer, ix *Interchange, opts ...WriteOption) error
- type Segment
- func (s *Segment) Component(i, j int) string
- func (s *Segment) Components(i int) []string
- func (s *Segment) Element(i int) string
- func (s *Segment) Float(elem, comp int) (float64, error)
- func (s *Segment) Index() []string
- func (s *Segment) Int(elem, comp int) (int, error)
- func (s *Segment) Len() int
- func (s *Segment) Qualifier() string
- func (s *Segment) Repetition(i, r int) []string
- func (s *Segment) Repetitions(i int) int
- func (s *Segment) Set(i, j int, v string)
- func (s *Segment) SetFloat(elem, comp int, v float64, prec int) error
- func (s *Segment) SetIndex(index ...string)
- func (s *Segment) SetInt(elem, comp int, v int)
- func (s *Segment) SetRepetition(i, r, j int, v string)
- func (s *Segment) SetTime(elem, comp int, t time.Time, format string) error
- func (s *Segment) Time(elem, comp int) (time.Time, error)
- func (s *Segment) TimeIn(elem, comp int, format string) (time.Time, error)
- func (s *Segment) TimeRange(elem, comp int) (start, end time.Time, err error)
- type SegmentGroup
- func (g *SegmentGroup) Add(tag string, elems ...string) *Segment
- func (g *SegmentGroup) AddComposite(tag string, elems ...[]string) *Segment
- func (g *SegmentGroup) AddGroup(name string) *SegmentGroup
- func (g *SegmentGroup) Find(tag, qualifier string) *Segment
- func (g *SegmentGroup) FindAll(tag, qualifier string) []*Segment
- func (g *SegmentGroup) FindAllGroups(name string) []*SegmentGroup
- func (g *SegmentGroup) FindGroup(name string) *SegmentGroup
- func (g *SegmentGroup) Groups() []*SegmentGroup
- func (g *SegmentGroup) Segments() []*Segment
- type Severity
- type Spec
- type WriteOption
Examples ¶
- Package
- DefaultDelimiters
- Diagnostic
- FitCharset
- Interchange.AddFunctionalGroup
- Interchange.SetCharset
- Interchange.SetDateTime
- Key
- MaxSegmentSize
- Registry.Load
- Registry.Read
- Registry.Validate
- RejectReadOn
- RejectWriteOn
- Segment.Float
- Segment.Index
- Segment.Repetition
- Segment.SetFloat
- Segment.Time
- SegmentGroup.Add
- Spec.Clone
- Write
Constants ¶
const Unbounded = -1
Unbounded lets a Node repeat without limit.
Variables ¶
var ( // UNOA is level A: upper case letters, digits, space and some punctuation. UNOA = Charset{"UNOA"} // UNOB is level B: level A and lower case letters. UNOB = Charset{"UNOB"} // UNOC is level C, the ISO 8859-1 character set. UNOC = Charset{"UNOC"} // UNOW is level W, UTF-8. It needs syntax version 4. UNOW = Charset{"UNOW"} )
var ( ErrNoUNB = errors.New("edifact: interchange does not open with UNB") ErrUnexpected = errors.New("edifact: service segment out of place") ErrUnclosed = errors.New("edifact: interchange ends with a message or functional group still open") )
Envelope structure the reader cannot carry on from.
var ErrCharset = errors.New("edifact: character outside the charset")
ErrCharset reports a value holding a character outside the interchange's charset.
var ErrDateFormat = errors.New("edifact: unknown date format qualifier")
ErrDateFormat reports a date format qualifier the package does not decode.
var ErrDateRange = errors.New("edifact: date format qualifier names a range")
ErrDateRange reports a date format qualifier naming a period rather than a single point in time.
var ErrDateZone = errors.New("edifact: unsupported time zone")
ErrDateZone reports a time zone, written as letters, that is neither GMT nor UTC. The code list does not define the zone's format, so nothing else is guessed at.
var ErrEmpty = errors.New("edifact: value is empty")
ErrEmpty reports a position that holds no value, which is distinct from one holding a value that will not convert.
var ErrGroupMismatch = errors.New("edifact: message does not match its functional group")
ErrGroupMismatch reports a message whose type, version, release or controlling agency differs from its functional group's, which syntax versions 1 to 3 forbid.
var ErrHeaderDate = errors.New("edifact: date of preparation does not match the syntax version")
ErrHeaderDate reports a UNB or UNG date of preparation whose length does not match the syntax version: eight digits in version 4 and six below it.
var ErrIncompleteHeader = errors.New("edifact: header is missing mandatory elements")
ErrIncompleteHeader reports a UNB, UNG or UNH missing an element ISO 9735 makes mandatory.
var ErrInvalidDelimiters = errors.New("edifact: invalid delimiters")
ErrInvalidDelimiters reports a component separator, element separator or segment terminator that is zero, or two delimiters that are the same character.
var ErrNoContent = errors.New("edifact: nothing to enclose")
ErrNoContent reports an interchange with no messages, a functional group with no messages, or a message with no segments between UNH and UNT.
var ErrNoRepetitionSeparator = errors.New("edifact: element repeats and there is no repetition separator")
ErrNoRepetitionSeparator reports an element with several repetitions, written to an interchange that declares no repetition separator.
var ErrNumeric = errors.New("edifact: not a valid EDIFACT numeric value")
ErrNumeric reports a value that is not an ISO 9735 numeric: an optional minus sign, digits with at most one decimal mark followed by a digit, and an optional exponent such as E3 or e-3.
var ErrSegmentTooLong = errors.New("edifact: segment is too long")
ErrSegmentTooLong reports a segment longer than MaxSegmentSize allows.
var ErrShortUNA = errors.New("edifact: UNA segment is too short")
ErrShortUNA reports a UNA segment with fewer than six characters after the tag.
var ErrSyntaxVersion = errors.New("edifact: syntax version must be 1 to 4")
ErrSyntaxVersion reports a syntax version other than 1 to 4.
var ErrUnreleasable = errors.New("edifact: value contains a separator and there is no release character")
ErrUnreleasable reports a value containing a separator, written to an interchange that has no release character to escape it with.
var ErrUnsupportedSeparators = errors.New("edifact: default separators other than level A need a UNA or DefaultDelimiters")
ErrUnsupportedSeparators reports an interchange with no UNA whose UNB uses the information separators IS3 and IS1, the defaults of syntax level B. Without a UNA, Read uses the level A separators unless DefaultDelimiters names others.
var ErrUnterminated = errors.New("edifact: segment is not terminated")
ErrUnterminated reports trailing content with no segment terminator.
var UnknownTimezone = time.FixedZone("UNKNOWN", 0)
UnknownTimezone is the location of a time read from a date format that carries no time zone. Its digits are as written and its offset is zero, which says nothing about the zone the sender meant.
Functions ¶
func Absent ¶
func Absent() *string
Absent returns a Key field matching only an empty component, as distinct from a nil field, which matches anything.
func ParseTime ¶
ParseTime decodes a value against a date format qualifier from code list 2379. A format with no time zone gives a time in UnknownTimezone. A zone written as GMT or UTC gives UTC, and an offset gives that offset.
Two-digit years follow the Go convention, so 00 to 68 read as 2000 to 2068 and 69 to 99 as 1969 to 1999.
func ParseTimeRange ¶
ParseTimeRange decodes a value against a format qualifier naming a period, such as 718 or 719. The two halves may be joined by a hyphen or run together.
func Write ¶
func Write(w io.Writer, ix *Interchange, opts ...WriteOption) error
Write serialises ix without validating it against any spec. ix is left as it was, including its messages' segment groups. To validate first, use a Registry's Write or WriteWithSpec.
Write always emits a UNA declaring ix.Delimiters.
Example ¶
Building an interchange and writing it.
package main
import (
"fmt"
"os"
"github.com/MaldivesPorts/edifact"
)
func main() {
ix := edifact.NewInterchange()
ix.SetSender("SENDER", "")
ix.SetRecipient("RECIPIENT", "")
ix.SetDate("260101")
ix.SetTime("1200")
ix.SetControlRef("REF1")
m := ix.AddMessage(edifact.Key{
Type: edifact.Is("BAPLIE"),
Version: edifact.Is("D"),
Release: edifact.Is("95B"),
Agency: edifact.Is("UN"),
})
m.SetRef("M1")
m.Add("BGM", "", "BAY1", "9")
m.AddComposite("DTM", []string{"137", "202601011200", "203"})
m.AddComposite("LOC", []string{"147"}, []string{"0010204", "", "5"})
m.Add("EQD", "CN", "XXXU1234567", "22G1")
if err := edifact.Write(os.Stdout, ix, edifact.SegmentSuffixLF()); err != nil {
fmt.Println(err)
}
}
Output: UNA:+.? ' UNB+UNOA:2+SENDER+RECIPIENT+260101:1200+REF1' UNH+M1+BAPLIE:D:95B:UN' BGM++BAY1+9' DTM+137:202601011200:203' LOC+147+0010204::5' EQD+CN+XXXU1234567+22G1' UNT+6+M1' UNZ+1+REF1'
func WriteWithSpec ¶
func WriteWithSpec(w io.Writer, ix *Interchange, s Spec, opts ...WriteOption) error
WriteWithSpec is Write with every message validated against s alone.
Types ¶
type Charset ¶ added in v0.2.0
type Charset struct {
// contains filtered or unexported fields
}
Charset is a syntax level this package reads and writes, named by the syntax identifier in UNB. Only the package's own values exist: UNOA, UNOB, UNOC and UNOW.
type Delimiters ¶
type Delimiters struct {
// Component separates the components of a composite element, ':' by
// default.
Component byte
// Element separates a segment's elements, '+' by default.
Element byte
// Decimal records what a UNA declared. Reading accepts either a dot or a
// comma whatever it says. Writing declares a comma when Decimal is one, and
// a dot otherwise.
Decimal byte
// Release is zero when the interchange declares no release character,
// which a UNA does by putting a space in that position.
Release byte
// Repetition separates the repetitions of an element in syntax version 4,
// '*' by default. It is zero below version 4, which has no repetitions.
Repetition byte
// Segment ends each segment, '\” by default.
Segment byte
}
Delimiters are the six characters declared by a UNA segment, in UNA order.
func ParseUNA ¶
func ParseUNA(b []byte) (Delimiters, bool, error)
ParseUNA reads delimiters from b when it opens with a UNA segment. The second result is false when it does not.
func StandardDelimiters ¶
func StandardDelimiters() Delimiters
StandardDelimiters returns the defaults used by this package.
- Repetition is zero. Write uses an asterisk for syntax version 4.
- Decimal is '.', the library's own default. ISO 9735 allows a dot or a comma.
type Diagnostic ¶
type Diagnostic struct {
// Severity is Warning or Error.
Severity Severity
// MessageIdx is the message's position in the interchange, counting from
// 1, or 0 when the diagnostic concerns the envelope.
MessageIdx int
// MessageRef is the message's UNH reference, which need not be unique.
MessageRef string
// Path names the segment groups enclosing the problem, such as /SG2/SG3,
// and is empty at the top level of a message or for the envelope.
Path string
// Tag is the segment tag, or the segment group's name when a whole
// segment group is at fault.
Tag string
// Qualifier is element 1 of the segment, or the qualifier a spec expected.
Qualifier string
// SegmentIdx counts from 1, starting at UNH when MessageIdx is set and at
// UNB otherwise. It is 0 when no single segment is at fault. Within a
// message, segment n is Segments()[n-2], with UNH at 1 and UNT one past the
// last of Segments().
SegmentIdx int
// Message describes the problem.
Message string
}
Diagnostic locates a structural violation within an interchange.
Example ¶
Read reports what does not fit the specification as diagnostics rather than failing, so a partly wrong file can still be read.
package main
import (
"fmt"
"strings"
"github.com/MaldivesPorts/edifact"
)
// bayplan is a short interchange cut down from a BAPLIE bayplan: two
// containers in their stowage cells. Newlines between segments are optional
// and ignored on read.
const bayplan = `UNA:+.? '
UNB+UNOA:2+SENDER+RECIPIENT+260101:1200+REF1'
UNH+M1+BAPLIE:D:95B:UN'
BGM++BAY1+9'
DTM+137:202601011200:203'
LOC+147+0010204::5'
MEA+WT++KGM:12000'
LOC+9+XXAAA:139:6'
LOC+11+XXBBB:139:6'
EQD+CN+XXXU1234567+22G1'
LOC+147+0010206::5'
MEA+WT++KGM:8500.5'
LOC+9+XXAAA:139:6'
LOC+11+XXCCC:139:6'
EQD+CN+XXXU7654321+45G1'
UNT+14+M1'
UNZ+1+REF1'`
// bayplanSpec is a simplified specification after the BAPLIE D.95B segment
// table, which has more segments than this: a header, then one SG2 per stowage
// cell, each holding an SG3 for the container in it.
func bayplanSpec() edifact.Spec {
return edifact.Spec{
Key: edifact.Key{
Type: edifact.Is("BAPLIE"),
Version: edifact.Is("D"),
Release: edifact.Is("95B"),
Agency: edifact.Is("UN"),
},
Nodes: []edifact.Node{
{Tag: "BGM", Required: true},
{Tag: "DTM", Qualifier: "137", Required: true},
{Group: "SG2", Max: edifact.Unbounded, Nodes: []edifact.Node{
{Tag: "LOC", Qualifier: "147", Required: true},
{Tag: "MEA", Qualifier: "WT"},
{Tag: "LOC", Qualifiers: []string{"9", "11"}, Max: 9},
{Group: "SG3", Max: 3, Nodes: []edifact.Node{
{Tag: "EQD", Qualifier: "CN", Required: true},
}},
}},
},
}
}
func main() {
reg := edifact.NewRegistry()
if err := reg.Register(bayplanSpec()); err != nil {
fmt.Println(err)
return
}
broken := strings.Replace(bayplan, "DTM+137:202601011200:203'", "FTX+AAI+++NOTE'", 1)
ix, err := reg.Read(strings.NewReader(broken))
if err != nil {
fmt.Println(err)
return
}
for _, d := range ix.Diagnostics {
fmt.Println(d)
}
}
Output: warning: message 1 (M1), segment 3, FTX+AAI: segment is not declared here error: message 1 (M1), segment 4, DTM+137: required segment is missing
func (Diagnostic) String ¶
func (d Diagnostic) String() string
String renders the diagnostic on one line, such as "error: message 1 (M1), segment 4, DTM+137: required segment is missing".
type FunctionalGroup ¶
type FunctionalGroup struct {
// contains filtered or unexported fields
}
FunctionalGroup is the UNG and UNE pair bracketing a set of messages. It holds no messages. Each message points at its functional group.
func (*FunctionalGroup) Agency ¶
func (g *FunctionalGroup) Agency() string
Agency returns the controlling agency.
func (*FunctionalGroup) ControlRef ¶
func (g *FunctionalGroup) ControlRef() string
ControlRef returns the functional group's reference, which UNE repeats.
func (*FunctionalGroup) Date ¶
func (g *FunctionalGroup) Date() string
Date returns the date of preparation, as YYMMDD or CCYYMMDD.
func (*FunctionalGroup) DateTime ¶ added in v0.2.0
func (g *FunctionalGroup) DateTime() (time.Time, error)
DateTime returns the date and time of preparation, read as Interchange.DateTime reads them.
func (*FunctionalGroup) Header ¶
func (g *FunctionalGroup) Header() *Segment
Header returns the UNG segment, creating an empty one if there is none. A nil g gives nil.
func (*FunctionalGroup) MessageType ¶
func (g *FunctionalGroup) MessageType() string
MessageType returns the message type the functional group names. Below syntax version 4, every message in the functional group must be of this type.
func (*FunctionalGroup) Recipient ¶
func (g *FunctionalGroup) Recipient() string
Recipient returns the functional group's recipient identification.
func (*FunctionalGroup) Release ¶
func (g *FunctionalGroup) Release() string
Release returns the message release number.
func (*FunctionalGroup) Sender ¶
func (g *FunctionalGroup) Sender() string
Sender returns the functional group's sender identification.
func (*FunctionalGroup) SetAgency ¶
func (g *FunctionalGroup) SetAgency(a string)
SetAgency writes the controlling agency.
func (*FunctionalGroup) SetControlRef ¶
func (g *FunctionalGroup) SetControlRef(ref string)
SetControlRef writes the functional group's reference.
func (*FunctionalGroup) SetDate ¶ added in v0.2.0
func (g *FunctionalGroup) SetDate(date string)
SetDate writes the date of preparation, as YYMMDD or CCYYMMDD.
func (*FunctionalGroup) SetDateTime ¶ added in v0.2.0
func (g *FunctionalGroup) SetDateTime(t time.Time)
SetDateTime writes t's date and time of preparation, in the formats the interchange's syntax version requires, as Interchange.SetDateTime does.
func (*FunctionalGroup) SetMessageType ¶
func (g *FunctionalGroup) SetMessageType(t string)
SetMessageType writes the message type the functional group names.
func (*FunctionalGroup) SetRecipient ¶
func (g *FunctionalGroup) SetRecipient(id, qualifier string)
SetRecipient writes the functional group's recipient identification and its qualifier.
func (*FunctionalGroup) SetRelease ¶ added in v0.2.0
func (g *FunctionalGroup) SetRelease(release string)
SetRelease writes the message release number.
func (*FunctionalGroup) SetSender ¶
func (g *FunctionalGroup) SetSender(id, qualifier string)
SetSender writes the functional group's sender identification and its qualifier.
func (*FunctionalGroup) SetTime ¶ added in v0.2.0
func (g *FunctionalGroup) SetTime(time string)
SetTime writes the time of preparation, as HHMM.
func (*FunctionalGroup) SetVersion ¶ added in v0.2.0
func (g *FunctionalGroup) SetVersion(version string)
SetVersion writes the message version number.
func (*FunctionalGroup) Time ¶
func (g *FunctionalGroup) Time() string
Time returns the time of preparation, as HHMM.
func (*FunctionalGroup) Version ¶
func (g *FunctionalGroup) Version() string
Version returns the message version number.
type Interchange ¶
type Interchange struct {
// Delimiters are the separators read from the UNA, or the defaults, and
// the ones Write uses.
Delimiters Delimiters
// Diagnostics are the problems Read found that did not stop it.
Diagnostics []Diagnostic
// contains filtered or unexported fields
}
Interchange is everything between UNB and UNZ.
func NewInterchange ¶
func NewInterchange() *Interchange
NewInterchange returns an empty interchange with the standard delimiters and a UNB naming syntax UNOA version 2.
func Read ¶
func Read(r io.Reader, opts ...ReadOption) (*Interchange, error)
Read parses an interchange without consulting any spec, so the result carries the envelope's own diagnostics and nothing about message structure. The error covers syntax alone. Anything structural is collected onto the returned interchange's Diagnostics, unless RejectReadOn turns it into an error. To check messages against specs, use Registry.Read or ReadWithSpec.
func ReadWithSpec ¶
func ReadWithSpec(r io.Reader, s Spec, opts ...ReadOption) (*Interchange, error)
ReadWithSpec parses an interchange, checking each message against s alone. A message whose UNH key s does not match is reported and left ungrouped.
func (*Interchange) AddFunctionalGroup ¶
func (ix *Interchange) AddFunctionalGroup(messageType string) *FunctionalGroup
AddFunctionalGroup appends a functional group whose UNG names messageType, and returns it.
Example ¶
A message joins a functional group when it is created. With functional groups, UNZ counts functional groups rather than messages.
package main
import (
"fmt"
"os"
"github.com/MaldivesPorts/edifact"
)
func main() {
ix := edifact.NewInterchange()
ix.SetSender("SENDER", "")
ix.SetRecipient("RECIPIENT", "")
ix.SetDate("260101")
ix.SetTime("1200")
ix.SetControlRef("REF1")
g := ix.AddFunctionalGroup("BAPLIE")
g.SetSender("SENDER", "")
g.SetRecipient("RECIPIENT", "")
g.SetDate("260101")
g.SetTime("1200")
g.SetControlRef("G1")
g.SetAgency("UN")
g.SetVersion("D")
g.SetRelease("95B")
m := ix.AddMessageTo(g, edifact.Key{
Type: edifact.Is("BAPLIE"),
Version: edifact.Is("D"),
Release: edifact.Is("95B"),
Agency: edifact.Is("UN"),
})
m.SetRef("M1")
m.Add("BGM", "", "BAY1", "9")
if err := edifact.Write(os.Stdout, ix, edifact.SegmentSuffixLF()); err != nil {
fmt.Println(err)
}
}
Output: UNA:+.? ' UNB+UNOA:2+SENDER+RECIPIENT+260101:1200+REF1' UNG+BAPLIE+SENDER+RECIPIENT+260101:1200+G1+UN+D:95B' UNH+M1+BAPLIE:D:95B:UN' BGM++BAY1+9' UNT+3+M1' UNE+1+G1' UNZ+1+REF1'
func (*Interchange) AddMessage ¶
func (ix *Interchange) AddMessage(k Key) *Message
AddMessage appends a message outside any functional group whose UNH names k, and returns it.
func (*Interchange) AddMessageTo ¶
func (ix *Interchange) AddMessageTo(g *FunctionalGroup, k Key) *Message
AddMessageTo appends a message in functional group g whose UNH names k, and returns it. A nil g adds the message outside any functional group.
func (*Interchange) AgreementID ¶
func (ix *Interchange) AgreementID() string
AgreementID returns the interchange agreement identifier.
func (*Interchange) ApplicationRef ¶
func (ix *Interchange) ApplicationRef() string
ApplicationRef returns the application reference.
func (*Interchange) Charset ¶ added in v0.2.0
func (ix *Interchange) Charset() (Charset, bool)
Charset returns the syntax level UNB names. It reports false when UNB names none, or one this package does not support, such as UNOD. The identifier as written stays readable through Header().Component(1, 1).
func (*Interchange) ControlRef ¶
func (ix *Interchange) ControlRef() string
ControlRef returns the interchange control reference, which UNZ repeats.
func (*Interchange) Date ¶
func (ix *Interchange) Date() string
Date returns the date of preparation, as YYMMDD or CCYYMMDD.
func (*Interchange) DateTime ¶ added in v0.2.0
func (ix *Interchange) DateTime() (time.Time, error)
DateTime returns the date and time of preparation. UNB carries no time zone, so the result is in UnknownTimezone. A six-digit date is read as YYMMDD and an eight-digit one as CCYYMMDD.
func (*Interchange) Grouped ¶
func (ix *Interchange) Grouped() bool
Grouped reports whether every message belongs to a functional group.
func (*Interchange) Groups ¶
func (ix *Interchange) Groups() []*FunctionalGroup
Groups returns the functional groups in write order, or nil when there are none. The slice is a copy.
func (*Interchange) Header ¶
func (ix *Interchange) Header() *Segment
Header returns the UNB segment, creating an empty one if there is none.
func (*Interchange) IsTest ¶
func (ix *Interchange) IsTest() bool
IsTest reports whether the test indicator is set.
func (*Interchange) Message ¶
func (ix *Interchange) Message(n int) *Message
Message returns message n, counting from 1, or nil when there is none.
func (*Interchange) Messages ¶
func (ix *Interchange) Messages() []*Message
Messages returns the messages in write order. The slice is a copy.
func (*Interchange) Recipient ¶
func (ix *Interchange) Recipient() string
Recipient returns the interchange recipient identification.
func (*Interchange) Sender ¶
func (ix *Interchange) Sender() string
Sender returns the interchange sender identification.
func (*Interchange) SetAgreementID ¶
func (ix *Interchange) SetAgreementID(id string)
SetAgreementID writes the interchange agreement identifier.
func (*Interchange) SetApplicationRef ¶
func (ix *Interchange) SetApplicationRef(ref string)
SetApplicationRef writes the application reference.
func (*Interchange) SetCharset ¶ added in v0.2.0
func (ix *Interchange) SetCharset(c Charset)
SetCharset writes the syntax identifier. Write refuses the zero Charset as a missing mandatory element.
Example ¶
SetCharset declares the syntax level. Write refuses a value outside it, here lower case in UNOA, and accepts it once the interchange declares UNOB.
package main
import (
"errors"
"fmt"
"io"
"github.com/MaldivesPorts/edifact"
)
func main() {
ix := edifact.NewInterchange()
ix.SetSender("SENDER", "")
ix.SetRecipient("RECIPIENT", "")
ix.SetDate("260101")
ix.SetTime("1200")
ix.SetControlRef("REF1")
m := ix.AddMessage(edifact.Key{Type: edifact.Is("TSTMSG"), Version: edifact.Is("D"), Release: edifact.Is("95B"), Agency: edifact.Is("UN")})
m.SetRef("M1")
m.Add("FTX", "AAA", "", "", "Handle with care")
err := edifact.Write(io.Discard, ix)
fmt.Println(errors.Is(err, edifact.ErrCharset), err)
ix.SetCharset(edifact.UNOB)
fmt.Println(edifact.Write(io.Discard, ix))
c, _ := ix.Charset()
fmt.Println(c)
}
Output: true FTX element 4 component 1: edifact: character outside the charset: 'a' (U+0061) is not in UNOA <nil> UNOB
func (*Interchange) SetControlRef ¶
func (ix *Interchange) SetControlRef(ref string)
SetControlRef writes the interchange control reference.
func (*Interchange) SetDate ¶ added in v0.2.0
func (ix *Interchange) SetDate(date string)
SetDate writes the date of preparation, as YYMMDD or CCYYMMDD.
func (*Interchange) SetDateTime ¶ added in v0.2.0
func (ix *Interchange) SetDateTime(t time.Time)
SetDateTime writes t's date and time of preparation: the date as CCYYMMDD in syntax version 4 and YYMMDD below it, and the time as HHMM. t's clock is written as it is. Set the syntax version first, since Write refuses a date whose length does not match it.
Example ¶
SetDateTime writes the date of preparation in the format the syntax version requires, and DateTime reads it back without a time zone.
package main
import (
"fmt"
"time"
"github.com/MaldivesPorts/edifact"
)
func main() {
when := time.Date(2026, 1, 2, 15, 4, 0, 0, time.UTC)
ix := edifact.NewInterchange()
ix.SetDateTime(when)
fmt.Println(ix.Date(), ix.Time())
if err := ix.SetSyntaxVersion(4); err != nil {
fmt.Println(err)
return
}
ix.SetDateTime(when)
fmt.Println(ix.Date(), ix.Time())
t, err := ix.DateTime()
fmt.Println(t.Format("2006-01-02 15:04"), t.Location(), err)
}
Output: 260102 1504 20260102 1504 2026-01-02 15:04 UNKNOWN <nil>
func (*Interchange) SetRecipient ¶
func (ix *Interchange) SetRecipient(id, qualifier string)
SetRecipient writes the recipient identification and its qualifier.
func (*Interchange) SetSender ¶
func (ix *Interchange) SetSender(id, qualifier string)
SetSender writes the sender identification and its qualifier.
func (*Interchange) SetSyntaxVersion ¶ added in v0.2.0
func (ix *Interchange) SetSyntaxVersion(v int) error
SetSyntaxVersion writes the ISO 9735 edition. It returns ErrSyntaxVersion, and writes nothing, for a version other than 1 to 4.
func (*Interchange) SetTest ¶
func (ix *Interchange) SetTest(test bool)
SetTest marks the interchange as test data.
func (*Interchange) SetTime ¶ added in v0.2.0
func (ix *Interchange) SetTime(time string)
SetTime writes the time of preparation, as HHMM.
func (*Interchange) SyntaxVersion ¶
func (ix *Interchange) SyntaxVersion() int
SyntaxVersion returns the ISO 9735 edition UNB names, 1 to 4, or 0 when it names none or another value.
func (*Interchange) Time ¶
func (ix *Interchange) Time() string
Time returns the time of preparation, as HHMM.
type Key ¶
type Key struct {
Type *string // 0065 message type
Version *string // 0052 message version number
Release *string // 0054 message release number
Agency *string // 0051 controlling agency
Association *string // 0057 association assigned code
}
Key identifies a message specification. It is the composite carried by UNH, element 2.
A nil field matches any value. A non-nil field must equal it exactly, so Absent matches only an empty component. A Key read off the wire never holds nil.
Example ¶
A nil Key field matches anything. Absent matches only an empty component, which tells a plain UN message from an industry variant of it.
package main
import (
"fmt"
"github.com/MaldivesPorts/edifact"
)
func main() {
anyBaplie := edifact.Key{Type: edifact.Is("BAPLIE")}
plain := edifact.Key{
Type: edifact.Is("BAPLIE"),
Version: edifact.Is("D"),
Release: edifact.Is("95B"),
Agency: edifact.Is("UN"),
Association: edifact.Absent(),
}
fmt.Println(anyBaplie)
fmt.Println(plain)
}
Output: BAPLIE:*:*:*:* BAPLIE:D:95B:UN:-
type Message ¶
type Message struct {
// contains filtered or unexported fields
}
Message is everything between UNH and UNT.
func (*Message) Add ¶
Add appends a segment built from elements, one component each, and returns it.
func (*Message) AddComposite ¶
AddComposite appends a segment whose elements each carry several components. The segment holds copies of elems.
func (*Message) AddGroup ¶
func (m *Message) AddGroup(name string) *SegmentGroup
AddGroup appends a segment group. name is the label used by the published tables, such as "SG2".
func (*Message) Find ¶
Find returns the first segment with the given tag whose element 1 matches qualifier, or nil. An empty qualifier matches any. A nil m gives nil.
func (*Message) FindAllGroups ¶
func (m *Message) FindAllGroups(name string) []*SegmentGroup
FindAllGroups returns every repetition of the named top-level segment group.
func (*Message) FindGroup ¶
func (m *Message) FindGroup(name string) *SegmentGroup
FindGroup returns the first repetition of the named top-level segment group, or nil.
func (*Message) FunctionalGroup ¶
func (m *Message) FunctionalGroup() *FunctionalGroup
FunctionalGroup returns the message's functional group, or nil when it is not in one or m is nil.
func (*Message) Groups ¶
func (m *Message) Groups() []*SegmentGroup
Groups returns the top-level segment groups, built by validation or AddGroup. The slice is a copy.
func (*Message) Header ¶
Header returns the UNH segment, creating an empty one if there is none. A nil m gives nil.
func (*Message) Key ¶
Key returns the message identifier, read from UNH rather than held apart from it. A nil m gives a zero Key.
type Node ¶
type Node struct {
// Tag is the segment tag, for a segment node.
Tag string
// Group is the segment group's label, such as SG2, for a segment group
// node.
Group string
// Qualifier is the value element 1 of a segment must have. Empty accepts
// any.
Qualifier string
// Qualifiers lists several values element 1 may have, any of which is
// accepted, for a segment that may carry any of them in any order. Set
// either Qualifier or Qualifiers, not both.
Qualifiers []string
// Required means the segment or segment group must appear at least once.
// For a node with Qualifiers, any one of them is enough.
Required bool
// Max is how many times it may repeat. Zero means once, and Unbounded
// means without limit.
Max int
// Nodes is a segment group's contents, in order. A new repetition of the
// segment group starts with any of its nodes up to and including the
// first required one.
Nodes []Node
}
Node is one entry in a message specification, either a segment or a segment group, never both.
type ReadOption ¶
type ReadOption func(*readConfig)
ReadOption adjusts how an interchange is read. See RejectReadOn, MaxSegmentSize and DefaultDelimiters.
func DefaultDelimiters ¶
func DefaultDelimiters(d Delimiters) ReadOption
DefaultDelimiters makes Read use d for an interchange with no UNA. A UNA still declares its own. By default, an interchange with no UNA is read with the level A separators.
Example ¶
A file without a UNA that uses the syntax level B separators reads once DefaultDelimiters names them: IS1 between components, IS3 between elements and IS4 after each segment.
package main
import (
"errors"
"fmt"
"strings"
"github.com/MaldivesPorts/edifact"
)
func main() {
levelB := strings.NewReplacer(":", "\x1f", "+", "\x1d", "'", "\x1c").Replace(
"UNB+UNOB:2+SENDER+RECIPIENT+260101:1200+REF1'" +
"UNH+M1+BAPLIE:D:95B:UN'BGM++BAY1+9'UNT+3+M1'UNZ+1+REF1'")
_, err := edifact.Read(strings.NewReader(levelB))
fmt.Println(errors.Is(err, edifact.ErrUnsupportedSeparators))
ix, err := edifact.Read(strings.NewReader(levelB), edifact.DefaultDelimiters(edifact.Delimiters{
Component: 0x1f,
Element: 0x1d,
Segment: 0x1c,
}))
if err != nil {
fmt.Println(err)
return
}
fmt.Println(ix.Message(1).Find("BGM", "").Element(2))
}
Output: true BAY1
func MaxSegmentSize ¶
func MaxSegmentSize(n int) ReadOption
MaxSegmentSize makes Read return ErrSegmentTooLong for a segment longer than n bytes, not counting its terminator or whitespace before it. By default, the limit is 16 MiB.
Example ¶
MaxSegmentSize bounds how long one segment may be, not counting its terminator.
package main
import (
"errors"
"fmt"
"strings"
"github.com/MaldivesPorts/edifact"
)
// bayplan is a short interchange cut down from a BAPLIE bayplan: two
// containers in their stowage cells. Newlines between segments are optional
// and ignored on read.
const bayplan = `UNA:+.? '
UNB+UNOA:2+SENDER+RECIPIENT+260101:1200+REF1'
UNH+M1+BAPLIE:D:95B:UN'
BGM++BAY1+9'
DTM+137:202601011200:203'
LOC+147+0010204::5'
MEA+WT++KGM:12000'
LOC+9+XXAAA:139:6'
LOC+11+XXBBB:139:6'
EQD+CN+XXXU1234567+22G1'
LOC+147+0010206::5'
MEA+WT++KGM:8500.5'
LOC+9+XXAAA:139:6'
LOC+11+XXCCC:139:6'
EQD+CN+XXXU7654321+45G1'
UNT+14+M1'
UNZ+1+REF1'`
func main() {
_, err := edifact.Read(strings.NewReader(bayplan), edifact.MaxSegmentSize(20))
fmt.Println(errors.Is(err, edifact.ErrSegmentTooLong), err)
}
Output: true edifact: segment is too long: over 20 bytes
func RejectReadOn ¶
func RejectReadOn(s Severity) ReadOption
RejectReadOn makes Read return the first diagnostic of severity s or above as an error. By default, Read rejects none.
Example ¶
Without RejectReadOn, Read collects diagnostics on the interchange. With it, Read returns the first one at or above the severity as an error.
package main
import (
"fmt"
"strings"
"github.com/MaldivesPorts/edifact"
)
// bayplan is a short interchange cut down from a BAPLIE bayplan: two
// containers in their stowage cells. Newlines between segments are optional
// and ignored on read.
const bayplan = `UNA:+.? '
UNB+UNOA:2+SENDER+RECIPIENT+260101:1200+REF1'
UNH+M1+BAPLIE:D:95B:UN'
BGM++BAY1+9'
DTM+137:202601011200:203'
LOC+147+0010204::5'
MEA+WT++KGM:12000'
LOC+9+XXAAA:139:6'
LOC+11+XXBBB:139:6'
EQD+CN+XXXU1234567+22G1'
LOC+147+0010206::5'
MEA+WT++KGM:8500.5'
LOC+9+XXAAA:139:6'
LOC+11+XXCCC:139:6'
EQD+CN+XXXU7654321+45G1'
UNT+14+M1'
UNZ+1+REF1'`
func main() {
miscounted := strings.Replace(bayplan, "UNT+14+M1'", "UNT+15+M1'", 1)
ix, err := edifact.Read(strings.NewReader(miscounted))
fmt.Println(len(ix.Diagnostics), "diagnostics,", err)
_, err = edifact.Read(strings.NewReader(miscounted), edifact.RejectReadOn(edifact.Error))
fmt.Println(err)
}
Output: 1 diagnostics, <nil> edifact: error: message 1 (M1), segment 14, UNT: count is 15, counted 14
type Registry ¶
type Registry struct {
// contains filtered or unexported fields
}
Registry resolves a Key to a Spec. Each Registry is independent of every other, and the package holds none of its own.
Register and Load are not safe for concurrent use. Once a Registry is built, any number of goroutines may read, validate and write through it.
func (*Registry) Load ¶
Load registers each spec in turn, stopping at the first that fails.
Example ¶
Load registers several specs at once. When more than one matches a message, the one constraining the most Key fields is used.
package main
import (
"fmt"
"strings"
"github.com/MaldivesPorts/edifact"
)
func main() {
plain := edifact.Spec{
Key: edifact.Key{
Type: edifact.Is("BAPLIE"),
Version: edifact.Is("D"),
Release: edifact.Is("95B"),
Agency: edifact.Is("UN"),
},
Nodes: []edifact.Node{
{Tag: "BGM", Required: true},
{Tag: "DTM", Qualifier: "137", Required: true},
},
}
// The same message with an association code, as an industry guide might
// define it.
variant := plain.Clone()
variant.Key.Association = edifact.Is("XX01")
variant.Nodes = append(variant.Nodes, edifact.Node{Tag: "FTX", Qualifier: "AAA"})
reg := edifact.NewRegistry()
if err := reg.Load([]edifact.Spec{plain, variant}); err != nil {
fmt.Println(err)
return
}
src := "UNB+UNOA:2+SENDER+RECIPIENT+260101:1200+REF1'" +
"UNH+M1+BAPLIE:D:95B:UN'BGM++BAY1+9'DTM+137:202601011200:203'UNT+4+M1'" +
"UNH+M2+BAPLIE:D:95B:UN:XX01'BGM++BAY2+9'DTM+137:202601011200:203'FTX+AAA+++NOTE'UNT+5+M2'" +
"UNZ+2+REF1'"
ix, err := reg.Read(strings.NewReader(src))
if err != nil {
fmt.Println(err)
return
}
fmt.Println(len(ix.Diagnostics), "diagnostics")
for _, m := range ix.Messages() {
spec, _ := reg.Lookup(m.Key())
fmt.Println(m.Ref(), m.Key(), "uses", spec.Key)
}
}
Output: 0 diagnostics M1 BAPLIE:D:95B:UN:- uses BAPLIE:D:95B:UN:* M2 BAPLIE:D:95B:UN:XX01 uses BAPLIE:D:95B:UN:XX01
func (*Registry) Lookup ¶
Lookup returns a copy of the spec matching k. Where several match, the one constraining the most fields wins, and the latest registration breaks a tie.
func (*Registry) Read ¶
func (r *Registry) Read(src io.Reader, opts ...ReadOption) (*Interchange, error)
Read parses an interchange, checking each message against the specs in r. A nil r consults no spec.
Example ¶
Reading against a specification sorts the segments into segment groups, so each container is read together with its own cell, weight and ports.
package main
import (
"fmt"
"strings"
"github.com/MaldivesPorts/edifact"
)
// bayplan is a short interchange cut down from a BAPLIE bayplan: two
// containers in their stowage cells. Newlines between segments are optional
// and ignored on read.
const bayplan = `UNA:+.? '
UNB+UNOA:2+SENDER+RECIPIENT+260101:1200+REF1'
UNH+M1+BAPLIE:D:95B:UN'
BGM++BAY1+9'
DTM+137:202601011200:203'
LOC+147+0010204::5'
MEA+WT++KGM:12000'
LOC+9+XXAAA:139:6'
LOC+11+XXBBB:139:6'
EQD+CN+XXXU1234567+22G1'
LOC+147+0010206::5'
MEA+WT++KGM:8500.5'
LOC+9+XXAAA:139:6'
LOC+11+XXCCC:139:6'
EQD+CN+XXXU7654321+45G1'
UNT+14+M1'
UNZ+1+REF1'`
// bayplanSpec is a simplified specification after the BAPLIE D.95B segment
// table, which has more segments than this: a header, then one SG2 per stowage
// cell, each holding an SG3 for the container in it.
func bayplanSpec() edifact.Spec {
return edifact.Spec{
Key: edifact.Key{
Type: edifact.Is("BAPLIE"),
Version: edifact.Is("D"),
Release: edifact.Is("95B"),
Agency: edifact.Is("UN"),
},
Nodes: []edifact.Node{
{Tag: "BGM", Required: true},
{Tag: "DTM", Qualifier: "137", Required: true},
{Group: "SG2", Max: edifact.Unbounded, Nodes: []edifact.Node{
{Tag: "LOC", Qualifier: "147", Required: true},
{Tag: "MEA", Qualifier: "WT"},
{Tag: "LOC", Qualifiers: []string{"9", "11"}, Max: 9},
{Group: "SG3", Max: 3, Nodes: []edifact.Node{
{Tag: "EQD", Qualifier: "CN", Required: true},
}},
}},
},
}
}
func main() {
reg := edifact.NewRegistry()
if err := reg.Register(bayplanSpec()); err != nil {
fmt.Println(err)
return
}
ix, err := reg.Read(strings.NewReader(bayplan))
if err != nil {
fmt.Println(err)
return
}
fmt.Println(len(ix.Diagnostics), "diagnostics")
for _, cell := range ix.Message(1).FindAllGroups("SG2") {
weight, _ := cell.Find("MEA", "WT").Float(3, 2)
container := cell.FindGroup("SG3").Find("EQD", "CN").Element(2)
fmt.Printf("%s in %s, %.1f kg, %s to %s\n",
container,
cell.Find("LOC", "147").Component(2, 1),
weight,
cell.Find("LOC", "9").Component(2, 1),
cell.Find("LOC", "11").Component(2, 1))
}
}
Output: 0 diagnostics XXXU1234567 in 0010204, 12000.0 kg, XXAAA to XXBBB XXXU7654321 in 0010206, 8500.5 kg, XXAAA to XXCCC
func (*Registry) Register ¶
Register validates s and stores a deep copy, so later edits to s do not reach the Registry. It refuses a malformed spec, such as one with no message type, a node naming both or neither of a tag and a segment group, or a segment declared twice in a row.
Registering a Key already present overrides the earlier spec for it, since Registry.Lookup prefers the later registration.
func (*Registry) Validate ¶
func (r *Registry) Validate(ix *Interchange) []Diagnostic
Validate checks every message in ix against its registered spec, and fills in each message's segment groups. A message whose Key resolves to no spec is reported and left ungrouped.
Example ¶
Validate checks an interchange built in code against the registry's specs, before Write would refuse it.
package main
import (
"fmt"
"github.com/MaldivesPorts/edifact"
)
// bayplanSpec is a simplified specification after the BAPLIE D.95B segment
// table, which has more segments than this: a header, then one SG2 per stowage
// cell, each holding an SG3 for the container in it.
func bayplanSpec() edifact.Spec {
return edifact.Spec{
Key: edifact.Key{
Type: edifact.Is("BAPLIE"),
Version: edifact.Is("D"),
Release: edifact.Is("95B"),
Agency: edifact.Is("UN"),
},
Nodes: []edifact.Node{
{Tag: "BGM", Required: true},
{Tag: "DTM", Qualifier: "137", Required: true},
{Group: "SG2", Max: edifact.Unbounded, Nodes: []edifact.Node{
{Tag: "LOC", Qualifier: "147", Required: true},
{Tag: "MEA", Qualifier: "WT"},
{Tag: "LOC", Qualifiers: []string{"9", "11"}, Max: 9},
{Group: "SG3", Max: 3, Nodes: []edifact.Node{
{Tag: "EQD", Qualifier: "CN", Required: true},
}},
}},
},
}
}
func main() {
reg := edifact.NewRegistry()
if err := reg.Register(bayplanSpec()); err != nil {
fmt.Println(err)
return
}
ix := edifact.NewInterchange()
m := ix.AddMessage(edifact.Key{
Type: edifact.Is("BAPLIE"),
Version: edifact.Is("D"),
Release: edifact.Is("95B"),
Agency: edifact.Is("UN"),
})
m.SetRef("M1")
m.Add("BGM", "", "BAY1", "9")
m.AddComposite("LOC", []string{"147"}, []string{"0010204", "", "5"})
for _, d := range reg.Validate(ix) {
fmt.Println(d)
}
}
Output: error: message 1 (M1), segment 3, DTM+137: required segment is missing
func (*Registry) Write ¶
func (r *Registry) Write(w io.Writer, ix *Interchange, opts ...WriteOption) error
Write validates ix against the specs in r and then serialises it, stopping at the first structural error, or the first warning under RejectWriteOn(Warning). A nil r skips validation.
type Segment ¶
type Segment struct {
// Tag is the segment code, without any explicit nesting and repetition
// indices.
Tag string
// contains filtered or unexported fields
}
Segment is one tagged line.
func (*Segment) Component ¶
Component returns component j of the first repetition of element i, both counting from 1, or "" when absent.
func (*Segment) Components ¶
Components returns a copy of the components of the first repetition of element i, counting from 1, or nil when absent.
func (*Segment) Element ¶
Element returns component 1 of the first repetition of element i, counting from 1 as the published tables do, or "" when absent.
func (*Segment) Float ¶
Float reads component comp of element elem, both counting from 1, as a number. Either a dot or a comma is accepted as the decimal mark, whatever the interchange declares.
Example ¶
Float accepts a dot or a comma as the decimal mark, and rejects anything ISO 9735 does not allow as a number.
package main
import (
"errors"
"fmt"
"github.com/MaldivesPorts/edifact"
)
func main() {
ix := edifact.NewInterchange()
m := ix.AddMessage(edifact.Key{})
mea := m.AddComposite("MEA", []string{"WT"}, nil, []string{"KGM", "8500,5"})
v, err := mea.Float(3, 2)
fmt.Println(v, err)
mea.Set(3, 2, "1,000.5")
_, err = mea.Float(3, 2)
fmt.Println(errors.Is(err, edifact.ErrNumeric))
}
Output: 8500.5 <nil> true
func (*Segment) Index ¶
Index returns a copy of the explicit nesting and repetition indices after the segment code, or nil when the tag carries none.
Example ¶
A tag can carry its nesting level and repetition number as components. Specs match the plain tag, and Index returns the numbers.
package main
import (
"fmt"
"strings"
"github.com/MaldivesPorts/edifact"
)
func main() {
src := "UNB+UNOA:2+SENDER+RECIPIENT+260101:1200+REF1'" +
"UNH+M1+TSTMSG:D:95B:UN'DDD:1:2+VALUE'UNT+3+M1'UNZ+1+REF1'"
ix, err := edifact.Read(strings.NewReader(src))
if err != nil {
fmt.Println(err)
return
}
s := ix.Message(1).Find("DDD", "")
fmt.Println(s.Tag, s.Index(), s.Element(1))
}
Output: DDD [1 2] VALUE
func (*Segment) Int ¶
Int reads component comp of element elem, both counting from 1, as a whole number: an optional minus sign and digits. Leading zeros are accepted.
func (*Segment) Qualifier ¶
Qualifier returns element 1, the value that distinguishes segments sharing a tag.
func (*Segment) Repetition ¶
Repetition returns a copy of the components of repetition r of element i, both counting from 1, or nil when absent.
Example ¶
In syntax version 4 an element can repeat within one segment, separated by the character in the fifth position of the UNA.
package main
import (
"fmt"
"strings"
"github.com/MaldivesPorts/edifact"
)
func main() {
src := "UNA:+.?*'" +
"UNB+UNOA:4+SENDER+RECIPIENT+20260101:1200+REF1'" +
"UNH+M1+TSTMSG:D:95B:UN'" +
"COM+111:TE*222:FX'" +
"UNT+3+M1'UNZ+1+REF1'"
ix, err := edifact.Read(strings.NewReader(src))
if err != nil {
fmt.Println(err)
return
}
com := ix.Message(1).Find("COM", "")
for r := 1; r <= com.Repetitions(1); r++ {
fmt.Println(com.Repetition(1, r))
}
fmt.Println(com.Element(1))
}
Output: [111 TE] [222 FX] 111
func (*Segment) Repetitions ¶
Repetitions returns the number of repetitions of element i, counting from 1, or 0 when the element is empty or absent.
func (*Segment) Set ¶
Set writes component j of the first repetition of element i, both counting from 1, extending the segment as needed.
func (*Segment) SetFloat ¶
SetFloat writes v to component comp of element elem, both counting from 1. It is held with a dot as the decimal mark, and Write renders it with the mark the interchange declares. prec is the number of decimal places, or -1 for the fewest digits that parse back to v. An infinite or NaN v gives ErrNumeric and writes nothing.
Example ¶
A file without a UNA may use a comma as the decimal mark. Write declares a dot and writes text values as they are, so a number read as text keeps its comma until it is set again with SetFloat.
package main
import (
"fmt"
"os"
"strings"
"github.com/MaldivesPorts/edifact"
)
func main() {
src := "UNB+UNOA:2+SENDER+RECIPIENT+260101:1200+REF1'" +
"UNH+M1+BAPLIE:D:95B:UN'MEA+WT++KGM:8500,5'UNT+3+M1'UNZ+1+REF1'"
ix, err := edifact.Read(strings.NewReader(src))
if err != nil {
fmt.Println(err)
return
}
mea := ix.Message(1).Find("MEA", "WT")
v, err := mea.Float(3, 2)
if err != nil {
fmt.Println(err)
return
}
if err := mea.SetFloat(3, 2, v, -1); err != nil {
fmt.Println(err)
return
}
if err := edifact.Write(os.Stdout, ix, edifact.SegmentSuffixLF()); err != nil {
fmt.Println(err)
}
}
Output: UNA:+.? ' UNB+UNOA:2+SENDER+RECIPIENT+260101:1200+REF1' UNH+M1+BAPLIE:D:95B:UN' MEA+WT++KGM:8500.5' UNT+3+M1' UNZ+1+REF1'
func (*Segment) SetIndex ¶
SetIndex replaces the explicit indices written after the segment code. No arguments removes them.
func (*Segment) SetRepetition ¶
SetRepetition writes component j of repetition r of element i, all counting from 1, extending the segment as needed.
func (*Segment) SetTime ¶
SetTime writes t in the given format to component comp of element elem, both counting from 1, and the format qualifier to the component after it. For formats 301 and 404, whose zone is written as letters, it converts t to UTC and writes UTC. For other formats it writes t's own clock, and for 205 its offset as well.
func (*Segment) Time ¶
Time reads component comp of element elem, both counting from 1, as a date and time, taking its format qualifier from the component after it. That is the layout of composite C507, where a DTM carries the qualifier, the value and the format in one element.
Example ¶
Element and Component count from 1, the way the published segment tables number positions. Typed accessors convert one component.
package main
import (
"fmt"
"strings"
"time"
"github.com/MaldivesPorts/edifact"
)
// bayplan is a short interchange cut down from a BAPLIE bayplan: two
// containers in their stowage cells. Newlines between segments are optional
// and ignored on read.
const bayplan = `UNA:+.? '
UNB+UNOA:2+SENDER+RECIPIENT+260101:1200+REF1'
UNH+M1+BAPLIE:D:95B:UN'
BGM++BAY1+9'
DTM+137:202601011200:203'
LOC+147+0010204::5'
MEA+WT++KGM:12000'
LOC+9+XXAAA:139:6'
LOC+11+XXBBB:139:6'
EQD+CN+XXXU1234567+22G1'
LOC+147+0010206::5'
MEA+WT++KGM:8500.5'
LOC+9+XXAAA:139:6'
LOC+11+XXCCC:139:6'
EQD+CN+XXXU7654321+45G1'
UNT+14+M1'
UNZ+1+REF1'`
func main() {
ix, err := edifact.Read(strings.NewReader(bayplan))
if err != nil {
fmt.Println(err)
return
}
dtm := ix.Message(1).Find("DTM", "137")
fmt.Println(dtm.Component(1, 2), "in format", dtm.Component(1, 3))
t, err := dtm.Time(1, 2)
fmt.Println(t.Format(time.RFC3339), err)
}
Output: 202601011200 in format 203 2026-01-01T12:00:00Z <nil>
type SegmentGroup ¶
type SegmentGroup struct {
// Name is the segment group's label in the spec, such as SG2.
Name string
// contains filtered or unexported fields
}
SegmentGroup is one repetition of a segment group declared by a spec, such as SG2. Its segments are pointers to the message's own, not copies, so the message stays the single storage and carries write order.
func (*SegmentGroup) Add ¶
func (g *SegmentGroup) Add(tag string, elems ...string) *Segment
Add builds a segment from elements, one component each, places it after the segment group's last segment, both in the segment group and in the message, and returns it. A segment group with no segments yet places it after the previous repetition of the same segment group, or else after the nearest earlier segment group beside it, or else before the nearest later one, or else after its container's own segments.
Example ¶
Add on a segment group places the new segment inside that segment group, even when it is not the last one, so the written order matches the segment group.
package main
import (
"fmt"
"strings"
"github.com/MaldivesPorts/edifact"
)
// bayplan is a short interchange cut down from a BAPLIE bayplan: two
// containers in their stowage cells. Newlines between segments are optional
// and ignored on read.
const bayplan = `UNA:+.? '
UNB+UNOA:2+SENDER+RECIPIENT+260101:1200+REF1'
UNH+M1+BAPLIE:D:95B:UN'
BGM++BAY1+9'
DTM+137:202601011200:203'
LOC+147+0010204::5'
MEA+WT++KGM:12000'
LOC+9+XXAAA:139:6'
LOC+11+XXBBB:139:6'
EQD+CN+XXXU1234567+22G1'
LOC+147+0010206::5'
MEA+WT++KGM:8500.5'
LOC+9+XXAAA:139:6'
LOC+11+XXCCC:139:6'
EQD+CN+XXXU7654321+45G1'
UNT+14+M1'
UNZ+1+REF1'`
// bayplanSpec is a simplified specification after the BAPLIE D.95B segment
// table, which has more segments than this: a header, then one SG2 per stowage
// cell, each holding an SG3 for the container in it.
func bayplanSpec() edifact.Spec {
return edifact.Spec{
Key: edifact.Key{
Type: edifact.Is("BAPLIE"),
Version: edifact.Is("D"),
Release: edifact.Is("95B"),
Agency: edifact.Is("UN"),
},
Nodes: []edifact.Node{
{Tag: "BGM", Required: true},
{Tag: "DTM", Qualifier: "137", Required: true},
{Group: "SG2", Max: edifact.Unbounded, Nodes: []edifact.Node{
{Tag: "LOC", Qualifier: "147", Required: true},
{Tag: "MEA", Qualifier: "WT"},
{Tag: "LOC", Qualifiers: []string{"9", "11"}, Max: 9},
{Group: "SG3", Max: 3, Nodes: []edifact.Node{
{Tag: "EQD", Qualifier: "CN", Required: true},
}},
}},
},
}
}
func main() {
reg := edifact.NewRegistry()
spec := bayplanSpec()
spec.Nodes[2].Nodes = append(spec.Nodes[2].Nodes, edifact.Node{Tag: "FTX", Qualifier: "AAA"})
if err := reg.Register(spec); err != nil {
fmt.Println(err)
return
}
ix, err := reg.Read(strings.NewReader(bayplan))
if err != nil {
fmt.Println(err)
return
}
first := ix.Message(1).FindGroup("SG2")
first.Add("FTX", "AAA", "", "", "HANDLE WITH CARE")
for _, s := range ix.Message(1).Segments() {
fmt.Println(strings.TrimSpace(s.Tag + " " + s.Qualifier()))
}
}
Output: BGM DTM 137 LOC 147 MEA WT LOC 9 LOC 11 EQD CN FTX AAA LOC 147 MEA WT LOC 9 LOC 11 EQD CN
func (*SegmentGroup) AddComposite ¶
func (g *SegmentGroup) AddComposite(tag string, elems ...[]string) *Segment
AddComposite is Add for a segment whose elements each carry several components. The segment holds copies of elems.
func (*SegmentGroup) AddGroup ¶
func (g *SegmentGroup) AddGroup(name string) *SegmentGroup
AddGroup appends a nested segment group.
func (*SegmentGroup) Find ¶
func (g *SegmentGroup) Find(tag, qualifier string) *Segment
Find returns the first segment in the segment group matching tag and qualifier.
func (*SegmentGroup) FindAll ¶
func (g *SegmentGroup) FindAll(tag, qualifier string) []*Segment
FindAll returns every segment in the segment group matching tag and qualifier.
func (*SegmentGroup) FindAllGroups ¶
func (g *SegmentGroup) FindAllGroups(name string) []*SegmentGroup
FindAllGroups returns every repetition of the named nested segment group.
func (*SegmentGroup) FindGroup ¶
func (g *SegmentGroup) FindGroup(name string) *SegmentGroup
FindGroup returns the first repetition of the named nested segment group, or nil.
func (*SegmentGroup) Groups ¶
func (g *SegmentGroup) Groups() []*SegmentGroup
Groups returns the nested segment groups. The slice is a copy.
func (*SegmentGroup) Segments ¶
func (g *SegmentGroup) Segments() []*Segment
Segments returns the segment group's segments in message order. The slice is a copy, and the segments are the message's own.
type Severity ¶
type Severity int
Severity ranks a diagnostic as a warning or an error.
const ( // Warning marks a problem that leaves the interchange usable, such as a // segment a spec does not declare. Warning Severity = iota // Error marks a problem that breaks the standard or a spec, such as a // missing required segment. Write with a registry refuses a message whose // validation gives one. Error )
type Spec ¶
type Spec struct {
// Key selects the messages this spec applies to.
Key Key
// Nodes are the message's segments and segment groups, in order.
Nodes []Node
}
Spec describes the segments between UNH and UNT for one message dialect.
func (Spec) Clone ¶
Clone returns a deep copy. Key holds pointers and Nodes is a slice, so a shallow copy would let an edit reach the original.
Example ¶
Clone copies a spec deeply, so a variant can change its key and nodes without touching the original.
package main
import (
"fmt"
"github.com/MaldivesPorts/edifact"
)
// bayplanSpec is a simplified specification after the BAPLIE D.95B segment
// table, which has more segments than this: a header, then one SG2 per stowage
// cell, each holding an SG3 for the container in it.
func bayplanSpec() edifact.Spec {
return edifact.Spec{
Key: edifact.Key{
Type: edifact.Is("BAPLIE"),
Version: edifact.Is("D"),
Release: edifact.Is("95B"),
Agency: edifact.Is("UN"),
},
Nodes: []edifact.Node{
{Tag: "BGM", Required: true},
{Tag: "DTM", Qualifier: "137", Required: true},
{Group: "SG2", Max: edifact.Unbounded, Nodes: []edifact.Node{
{Tag: "LOC", Qualifier: "147", Required: true},
{Tag: "MEA", Qualifier: "WT"},
{Tag: "LOC", Qualifiers: []string{"9", "11"}, Max: 9},
{Group: "SG3", Max: 3, Nodes: []edifact.Node{
{Tag: "EQD", Qualifier: "CN", Required: true},
}},
}},
},
}
}
func main() {
base := bayplanSpec()
variant := base.Clone()
variant.Key.Association = edifact.Is("XX01")
unit := &variant.Nodes[2]
unit.Nodes = append(unit.Nodes, edifact.Node{Tag: "FTX", Qualifier: "AAA"})
fmt.Println(base.Key, len(base.Nodes[2].Nodes), "nodes in SG2")
fmt.Println(variant.Key, len(variant.Nodes[2].Nodes), "nodes in SG2")
}
Output: BAPLIE:D:95B:UN:* 4 nodes in SG2 BAPLIE:D:95B:UN:XX01 5 nodes in SG2
type WriteOption ¶
type WriteOption func(*writeConfig)
WriteOption adjusts how an interchange is serialised. See SegmentSuffixLF, SegmentSuffixCRLF, AllowIncompleteHeaders and RejectWriteOn.
func AllowIncompleteHeaders ¶
func AllowIncompleteHeaders() WriteOption
AllowIncompleteHeaders writes a UNB, UNG or UNH that lacks elements ISO 9735 makes mandatory, rather than returning ErrIncompleteHeader.
func FitCharset ¶ added in v0.2.0
func FitCharset() WriteOption
FitCharset makes Write convert characters outside the interchange's charset where it can, rather than refusing them. An accented Latin-1 letter becomes its base letter, such as é to e, Æ to AE and ß to ss, and in UNOA lower case becomes upper case. Characters it cannot convert, such as € in UNOA, are still refused with ErrCharset. By default, Write converts nothing.
Example ¶
FitCharset converts what it can into the charset. In UNOA that means accented letters lose their accents and lower case becomes upper case.
package main
import (
"fmt"
"strings"
"github.com/MaldivesPorts/edifact"
)
func main() {
ix := edifact.NewInterchange()
ix.SetSender("SENDER", "")
ix.SetRecipient("RECIPIENT", "")
ix.SetDate("260101")
ix.SetTime("1200")
ix.SetControlRef("REF1")
m := ix.AddMessage(edifact.Key{Type: edifact.Is("TSTMSG"), Version: edifact.Is("D"), Release: edifact.Is("95B"), Agency: edifact.Is("UN")})
m.SetRef("M1")
m.Add("FTX", "AAA", "", "", "Café, Straße")
var sb strings.Builder
if err := edifact.Write(&sb, ix, edifact.FitCharset(), edifact.SegmentSuffixLF()); err != nil {
fmt.Println(err)
return
}
for _, line := range strings.Split(sb.String(), "\n") {
if strings.HasPrefix(line, "FTX") {
fmt.Println(line)
}
}
}
Output: FTX+AAA+++CAFE, STRASSE'
func RejectWriteOn ¶
func RejectWriteOn(s Severity) WriteOption
RejectWriteOn makes Write return the first diagnostic of severity s or above as an error. By default, Write rejects errors. A severity above Error counts as Error.
Example ¶
A registry's Write refuses errors by default. RejectWriteOn(Warning) makes it refuse warnings too, such as a segment the spec does not declare. A segment added at the end of the message falls inside its last segment group.
package main
import (
"fmt"
"io"
"strings"
"github.com/MaldivesPorts/edifact"
)
// bayplan is a short interchange cut down from a BAPLIE bayplan: two
// containers in their stowage cells. Newlines between segments are optional
// and ignored on read.
const bayplan = `UNA:+.? '
UNB+UNOA:2+SENDER+RECIPIENT+260101:1200+REF1'
UNH+M1+BAPLIE:D:95B:UN'
BGM++BAY1+9'
DTM+137:202601011200:203'
LOC+147+0010204::5'
MEA+WT++KGM:12000'
LOC+9+XXAAA:139:6'
LOC+11+XXBBB:139:6'
EQD+CN+XXXU1234567+22G1'
LOC+147+0010206::5'
MEA+WT++KGM:8500.5'
LOC+9+XXAAA:139:6'
LOC+11+XXCCC:139:6'
EQD+CN+XXXU7654321+45G1'
UNT+14+M1'
UNZ+1+REF1'`
// bayplanSpec is a simplified specification after the BAPLIE D.95B segment
// table, which has more segments than this: a header, then one SG2 per stowage
// cell, each holding an SG3 for the container in it.
func bayplanSpec() edifact.Spec {
return edifact.Spec{
Key: edifact.Key{
Type: edifact.Is("BAPLIE"),
Version: edifact.Is("D"),
Release: edifact.Is("95B"),
Agency: edifact.Is("UN"),
},
Nodes: []edifact.Node{
{Tag: "BGM", Required: true},
{Tag: "DTM", Qualifier: "137", Required: true},
{Group: "SG2", Max: edifact.Unbounded, Nodes: []edifact.Node{
{Tag: "LOC", Qualifier: "147", Required: true},
{Tag: "MEA", Qualifier: "WT"},
{Tag: "LOC", Qualifiers: []string{"9", "11"}, Max: 9},
{Group: "SG3", Max: 3, Nodes: []edifact.Node{
{Tag: "EQD", Qualifier: "CN", Required: true},
}},
}},
},
}
}
func main() {
reg := edifact.NewRegistry()
if err := reg.Register(bayplanSpec()); err != nil {
fmt.Println(err)
return
}
ix, err := edifact.Read(strings.NewReader(bayplan))
if err != nil {
fmt.Println(err)
return
}
ix.Message(1).Add("FTX", "AAI", "", "", "NOTE")
fmt.Println(reg.Write(io.Discard, ix))
fmt.Println(reg.Write(io.Discard, ix, edifact.RejectWriteOn(edifact.Warning)))
}
Output: <nil> edifact: warning: message 1 (M1), segment 14, /SG2/FTX+AAI: segment is not declared here
func SegmentSuffixCRLF ¶
func SegmentSuffixCRLF() WriteOption
SegmentSuffixCRLF writes a carriage return and a line feed after every segment terminator. Without a suffix option, segments run together.
func SegmentSuffixLF ¶
func SegmentSuffixLF() WriteOption
SegmentSuffixLF writes a line feed after every segment terminator. Without a suffix option, segments run together.