git.iko.soy/typeable/pandoc
on master help sign in
Writers.Shared: improve type of Lang and bcp47 parser.
643cbdfJohn MacFarlane
src/Text/Pandoc/Writers/ODT.hs+3−3
@@ -171,11 +171,11 @@ updateStyleWithLang (Just lang) arch = do
171 | e <- zEntries arch] }
171 | e <- zEntries arch] }
172
172
173addLang :: Lang -> Element -> Element
173addLang :: Lang -> Element -> Element
174−addLang (Lang lang country) = everywhere' (mkT updateLangAttr)
174+addLang lang = everywhere' (mkT updateLangAttr)
175 where updateLangAttr (Attr n@(QName "language" _ (Just "fo")) _)
175 where updateLangAttr (Attr n@(QName "language" _ (Just "fo")) _)
176− = Attr n lang
176+ = Attr n (langLanguage lang)
177 updateLangAttr (Attr n@(QName "country" _ (Just "fo")) _)
177 updateLangAttr (Attr n@(QName "country" _ (Just "fo")) _)
178− = Attr n country
178+ = Attr n (langRegion lang)
179 updateLangAttr x = x
179 updateLangAttr x = x
180
180
181-- | transform both Image and Math elements
181-- | transform both Image and Math elements
src/Text/Pandoc/Writers/OpenDocument.hs+9−9
@@ -614,7 +614,7 @@ data TextStyle = Italic
614 | Sup
614 | Sup
615 | SmallC
615 | SmallC
616 | Pre
616 | Pre
617− | Language String String
617+ | Language Lang
618 deriving ( Eq,Ord )
618 deriving ( Eq,Ord )
619
619
620textStyleAttr :: TextStyle -> [(String,String)]
620textStyleAttr :: TextStyle -> [(String,String)]
@@ -632,9 +632,9 @@ textStyleAttr s
632 | Pre <- s = [("style:font-name" ,"Courier New")
632 | Pre <- s = [("style:font-name" ,"Courier New")
633 ,("style:font-name-asian" ,"Courier New")
633 ,("style:font-name-asian" ,"Courier New")
634 ,("style:font-name-complex" ,"Courier New")]
634 ,("style:font-name-complex" ,"Courier New")]
635− | Language lang country <- s
635+ | Language lang <- s
636− = [("fo:language" ,lang)
636+ = [("fo:language" ,langLanguage lang)
637− ,("fo:country" ,country)]
637+ ,("fo:country" ,langRegion lang)]
638 | otherwise = []
638 | otherwise = []
639
639
640withLangFromAttr :: PandocMonad m => Attr -> OD m a -> OD m a
640withLangFromAttr :: PandocMonad m => Attr -> OD m a -> OD m a
@@ -642,8 +642,8 @@ withLangFromAttr (_,_,kvs) action =
642 case lookup "lang" kvs of
642 case lookup "lang" kvs of
643 Nothing -> action
643 Nothing -> action
644 Just l -> do
644 Just l -> do
645− mblang <- parseBCP47 l
645+ case parseBCP47 l of
646− case mblang of
647− Just (Lang lang country) -> withTextStyle
648− (Language lang country) action
646+ Right lang -> withTextStyle (Language lang) action
647+ Left _ -> do
648+ report $ InvalidLang l
649− _ -> action
649+ action
src/Text/Pandoc/Writers/Shared.hs+67−29
@@ -46,11 +46,12 @@ module Text.Pandoc.Writers.Shared (
46 , gridTable
46 , gridTable
47 )
47 )
48where
48where
49−import Control.Monad (liftM, zipWithM, mplus)
49+import Control.Monad (liftM, zipWithM, guard)
50import Data.Aeson (FromJSON (..), Result (..), ToJSON (..), Value (Object),
50import Data.Aeson (FromJSON (..), Result (..), ToJSON (..), Value (Object),
51 encode, fromJSON)
51 encode, fromJSON)
52+import Data.Char (isAscii, isLetter, isUpper, isLower)
52import qualified Data.HashMap.Strict as H
53import qualified Data.HashMap.Strict as H
53−import Data.List (groupBy, intersperse, transpose)
54+import Data.List (groupBy, intersperse, transpose, intercalate)
54import qualified Data.Map as M
55import qualified Data.Map as M
55import Data.Maybe (isJust)
56import Data.Maybe (isJust)
56import qualified Data.Text as T
57import qualified Data.Text as T
@@ -60,45 +61,82 @@ import Text.Pandoc.Class (PandocMonad, report)
60import Text.Pandoc.Logging
61import Text.Pandoc.Logging
61import Text.Pandoc.Options
62import Text.Pandoc.Options
62import Text.Pandoc.Pretty
63import Text.Pandoc.Pretty
63−import Text.Pandoc.Shared (splitBy)
64import Text.Pandoc.UTF8 (toStringLazy)
64import Text.Pandoc.UTF8 (toStringLazy)
65import Text.Pandoc.XML (escapeStringForXML)
65import Text.Pandoc.XML (escapeStringForXML)
66+import qualified Text.Parsec as P
66
67
67-- | Represents BCP 47 language/country code.
68-- | Represents BCP 47 language/country code.
68−data Lang = Lang String String
69+data Lang = Lang{ langLanguage :: String
70+ , langScript :: String
71+ , langRegion :: String
72+ , langVariants :: [String] }
73+ deriving (Eq, Ord, Show)
69
74
70-- | Render a Lang as BCP 47.
75-- | Render a Lang as BCP 47.
71renderLang :: Lang -> String
76renderLang :: Lang -> String
72−renderLang (Lang la co) = la ++ if null co
77+renderLang lang = intercalate "-" (langLanguage lang : filter (not . null)
73− then ""
78+ ([langScript lang, langRegion lang] ++ langVariants lang))
74− else '-':co
75
79
76-- | Get the contents of the `lang` metadata field or variable.
80-- | Get the contents of the `lang` metadata field or variable.
77getLang :: PandocMonad m => WriterOptions -> Meta -> m (Maybe Lang)
81getLang :: PandocMonad m => WriterOptions -> Meta -> m (Maybe Lang)
78−getLang opts meta = maybe (return Nothing) parseBCP47 $
82+getLang opts meta = case
79− case lookup "lang" (writerVariables opts) of
83+ (case lookup "lang" (writerVariables opts) of
80− Just s -> Just s
84+ Just s -> Just s
81− _ -> Nothing
85+ _ ->
82− `mplus`
83− case lookupMeta "lang" meta of
86+ case lookupMeta "lang" meta of
84− Just (MetaInlines [Str s]) -> Just s
87+ Just (MetaInlines [Str s]) -> Just s
85− Just (MetaString s) -> Just s
88+ Just (MetaString s) -> Just s
86− _ -> Nothing
89+ _ -> Nothing) of
90+ Nothing -> return Nothing
91+ Just s -> case parseBCP47 s of
92+ Left _ -> do
93+ report $ InvalidLang s
94+ return Nothing
95+ Right l -> return (Just l)
87
96
88−-- | Parse a BCP 47 string as a Lang, issuing a warning if there
97+-- | Parse a BCP 47 string as a Lang.
89−-- are issues.
90−parseBCP47 :: PandocMonad m => String -> m (Maybe Lang)
98+parseBCP47 :: String -> Either String Lang
91parseBCP47 lang =
99parseBCP47 lang =
92− case splitBy (== '-') lang of
100+ case P.parse bcp47 "lang" lang of
93− [la,co]
101+ Right r -> Right r
94− | length la == 2 && length co == 2
102+ Left e -> Left $ show e
103+ where bcp47 = do
104+ language <- pLanguage
105+ script <- P.option "" pScript
106+ region <- P.option "" pRegion
107+ variants <- P.many pVariant
108+ () <$ P.char '-' P.<|> P.eof
109+ return $ Lang{ langLanguage = language
110+ , langScript = script
111+ , langRegion = region
112+ , langVariants = variants }
113+ asciiLetter = P.satisfy (\c -> isAscii c && isLetter c)
114+ pLanguage = do
115+ cs <- P.many1 asciiLetter
116+ let lcs = length cs
117+ guard $ lcs == 2 || lcs == 3
118+ return cs
119+ pScript = P.try $ do
120+ P.char '-'
121+ x <- P.satisfy (\c -> isAscii c && isLetter c && isUpper c)
122+ xs <- P.count 3
123+ (P.satisfy (\c -> isAscii c && isLetter c && isLower c))
95− -> return $ Just $ Lang la co
124+ return (x:xs)
96− [la]
125+ pRegion = P.try $ do
126+ P.char '-'
127+ cs <- P.many1 asciiLetter
97− | length la == 2
128+ let lcs = length cs
129+ guard $ lcs == 2 || lcs == 3
98− -> return $ Just $ Lang la ""
130+ return cs
99− _ -> do
131+ pVariant = P.try $ do
100− report $ InvalidLang lang
132+ P.char '-'
133+ ds <- P.option "" (P.count 1 P.digit)
134+ cs <- P.many1 asciiLetter
135+ let var = ds ++ cs
136+ guard $ if null ds
137+ then length var >= 5 && length var <= 8
138+ else length var == 4
101− return Nothing
139+ return var
102
140
103-- | Create JSON value for template from a 'Meta' and an association list
141-- | Create JSON value for template from a 'Meta' and an association list
104-- of variables, specified at the command line or in the writer.
142-- of variables, specified at the command line or in the writer.