aboutsummaryrefslogblamecommitdiffhomepage
path: root/core/src/Text/Edifact/Types.hs
blob: d0bbe0db32820f2161409b0ff56b1fe187a890c0 (plain) (tree)



























































































































                                                                                                    
{-# LANGUAGE DerivingStrategies         #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}

{-|
  Data types to represent Edifact values.

  See "Text.Edifact.Parsing" to learn how to build parsers and use such parsers.
 -}
module Text.Edifact.Types
  ( -- * Values
    Value(..)
  , Primitive(..)
  , Position(..)
    -- ** Element codes
  , MessageCode(..)
  , GroupCode(..)
  , SegmentCode(..)
  , CompositeCode(..)
  , SimpleCode(..)
    -- * Syntax
  , Syntax(..)
  , defaultSyntax
  ) where

import           Data.Scientific (Scientific)
import           Data.String     (IsString (..))
import           Data.Text       (Text)

-- | Code for a message.
--
-- Content is expected to match this regexp: @[A-Z]{6}@.
newtype MessageCode = MessageCode { getMessageCode :: String } deriving newtype (Eq, Show, IsString)

-- | Code for a segment group.
--
-- It's a code local to the message definition.
newtype GroupCode = GroupCode { getGroupCode :: String } deriving newtype (Eq, Show, IsString)

-- | Code for a segment.
--
-- Content is expected to match this regexp: @[A-Z]{3}@.
--
-- Standard segment codes are expected to match this regexp: @UN[A-Z]@.
newtype SegmentCode = SegmentCode { getSegmentCode :: String } deriving newtype (Eq, Show, IsString)

-- | Code for a composite element.
--
-- Content is expected to match this regexp: @C[0-9]{3}@.
--
-- It can also be used for standalone composites, frequently in standard segment
-- definitions. In this case the codes are expected to match this regexp: @S[0-9]{3}@.
newtype CompositeCode = CompositeCode String deriving newtype (Eq, Show, IsString)

-- | Code for a simple element.
--
-- Content is expected to match this regexp: @[0-9]{4}@.
newtype SimpleCode = SimpleCode String deriving newtype (Eq, Show, IsString)

-- | Annotation of the position of the value relative to the parent value.
--
-- Content is expected to match this regexp: @[0-9]{3,4}@.
--
-- Example values:
--
-- > "010" :: Position
-- > "0210" :: Position
--
-- See 'Text.Edifact.Parsing.position' for how to parse one.
newtype Position = Position { getPosition :: String } deriving newtype (Eq, Show, IsString)

-- | Representation of a simple component.
--
-- When defined by the 'Text.Edifact.Parsing.numeric' combinator, the simple
-- component will produce a 'Number'.
--
-- When parsed by the 'Text.Edifact.Parsing.alphaNumeric' or
-- 'Text.Edifact.Parsing.alpha' combinators, the simple component will produce a
-- 'Text.Edifact.Types.String' from the raw textual representation.
data Primitive = String Text       -- ^ Default representation of a simple component.
               | Number Scientific -- ^ Representation of a numerical simple component.
                 deriving stock (Eq, Show)

-- | String like primitive values can be constructed via overloaded strings.
-- This is convenient, but might be removed.
instance IsString Primitive where
  fromString = String . fromString

-- | Recursive data structure to represent parsed Edifact values.
data Value = Message   MessageCode   [(Position, [Value])]
           | Group     GroupCode     [(Position, [Value])]
           | Segment   SegmentCode   [(Position, Maybe Value)]
           | Composite CompositeCode [(Position, Maybe Value)]
           | Simple    SimpleCode    Primitive
             deriving stock (Show, Eq)

-- | Defines the special charactors the parser should respect.
--
-- This is defined in every payload via the @UNA@ segment (first segment expected).
data Syntax = Syntax { compositeSeparator :: Char
                     , elementSeparator   :: Char
                     , decimalSign        :: Char
                     , escape             :: Char
                     , segmentSeparator   :: Char
                     }

-- | Default value to initialize the parser.
--
-- > Syntax { compositeSeparator = ':'
-- >        , elementSeparator   = '+'
-- >        , decimalSign        = '.'
-- >        , escape             = '?'
-- >        , segmentSeparator   = '\''
-- >        }
--
-- Those default charactors should be considered as recommended values rather
-- than official default values.
defaultSyntax :: Syntax
defaultSyntax =
  Syntax { compositeSeparator = ':'
         , elementSeparator   = '+'
         , decimalSign        = '.'
         , escape             = '?'
         , segmentSeparator   = '\''
         }