1 {-# LANGUAGE FlexibleContexts #-}
2 {-# LANGUAGE NamedFieldPuns #-}
3 module Hcompta.Format.JCC.Amount.Read where
5 import Control.Applicative ((<$>), (<*))
6 import Control.Applicative ((<|>))
7 import Control.Monad (Monad(..), guard)
10 import qualified Data.Decimal
11 import Data.Eq (Eq(..))
12 import qualified Data.List as List
13 import Data.Maybe (Maybe(..))
14 import Data.Monoid (Monoid(..))
15 import Data.Ord (Ord(..))
16 import Data.String (String, fromString)
17 import Data.Typeable ()
18 import Prelude (($), (.), Int, fromIntegral, Num(..), id)
19 import qualified Text.Parsec as R hiding
25 import Text.Parsec (Stream, ParsecT, (<?>))
27 import qualified Hcompta.Unit as Unit
28 import Hcompta.Format.JCC (Amount(..), Unit(..))
29 import qualified Hcompta.Format.JCC.Amount.Style as Style
30 import Hcompta.Format.JCC.Common.Read
31 import qualified Hcompta.Lib.Parsec as R
37 => Char -- ^ Integral grouping separator.
38 -> Char -- ^ Fractioning separator.
39 -> Char -- ^ Fractional grouping separator.
41 ( [String] -- integral
42 , [String] -- fractional
43 , Maybe Style.Fractioning -- fractioning
44 , Maybe Style.Grouping -- grouping_integral
45 , Maybe Style.Grouping -- grouping_fractional
47 quantity int_group_sep frac_sep frac_group_sep = do
48 (integral, grouping_integral) <- do
51 [] -> return ([], Nothing)
53 t <- R.many $ R.char int_group_sep >> R.many1 R.digit
55 return (digits, grouping_of_digits int_group_sep digits)
56 (fractional, fractioning, grouping_fractional) <-
59 _ -> R.option ([], Nothing, Nothing)) $ do
60 fractioning <- R.char frac_sep
62 t <- R.many $ R.char frac_group_sep >> R.many1 R.digit
64 return (digits, Just fractioning
65 , grouping_of_digits frac_group_sep $ List.reverse digits)
74 grouping_of_digits :: Char -> [String] -> Maybe Style.Grouping
75 grouping_of_digits group_sep digits =
80 Style.Grouping group_sep $
81 canonicalize_grouping $
82 List.map List.length $ digits
83 canonicalize_grouping :: [Int] -> [Int]
84 canonicalize_grouping groups =
85 List.foldl' -- NOTE: remove duplicates at beginning and reverse.
86 (\acc l0 -> case acc of
87 l1:_ -> if l0 == l1 then acc else l0:acc
89 case groups of -- NOTE: keep only longer at beginning.
90 l0:l1:t -> if l0 > l1 then groups else l1:t
94 unit :: Stream s m Char => ParsecT s u m Unit
96 (symbol <|> letters) <?> "unit"
98 symbol :: Stream s m Char => ParsecT s u m Unit
100 fromString . (\c -> [c]) <$> do
102 case Data.Char.generalCategory c of
103 Data.Char.CurrencySymbol -> True
105 letters :: Stream s m Char => ParsecT s u m Unit
110 case Data.Char.generalCategory c of
111 Data.Char.LowercaseLetter -> True
112 Data.Char.ModifierLetter -> True
113 Data.Char.OtherLetter -> True
114 Data.Char.TitlecaseLetter -> True
115 Data.Char.UppercaseLetter -> True
119 amount :: Stream s m Char => ParsecT s u m (Style.Styled Amount)
123 R.option Nothing $ do
126 return $ Just $ (u, not $ List.null s)
133 , grouping_fractional
136 [ quantity '_' ',' '_' <* (R.notFollowedBy $ R.oneOf ",._")
137 , quantity '_' '.' '_' <* (R.notFollowedBy $ R.oneOf ",._")
138 , quantity ',' '.' '_' <* (R.notFollowedBy $ R.oneOf ",._")
139 , quantity '.' ',' '_' <* (R.notFollowedBy $ R.oneOf ",._")
141 let int = List.concat integral
142 let frac = List.concat fractional
143 let precision = List.length frac
144 guard (precision <= 255)
145 let mantissa = R.integer_of_digits 10 $ int `mappend` frac
147 ( Data.Decimal.Decimal
148 (fromIntegral precision)
152 , Style.grouping_integral
153 , Style.grouping_fractional
156 (amount_unit, unit_side, unit_spaced) <-
159 return (u, Just Style.Side_Left, Just s)
161 R.option (Unit.unit_empty, Nothing, Nothing) $ R.try $ do
164 return $ (u, Just Style.Side_Right, Just $ not $ List.null s)
172 { amount_quantity = left_signing qty
177 -- | Parse either '-' into 'negate', or '+' or '' into 'id'.
178 sign :: (Stream s m Char, Num i) => ParsecT s u m (i -> i)
180 (R.char '-' >> return negate)
181 <|> (R.char '+' >> return id)