1{-# LANGUAGE ViewPatterns #-} 2{-# LANGUAGE OverloadedStrings #-} 3{- 4Copyright (C) 2014 Matthew Pickering <matthewtpickering@gmail.com> 5 6This program is free software; you can redistribute it and/or modify 7it under the terms of the GNU General Public License as published by 8the Free Software Foundation; either version 2 of the License, or 9(at your option) any later version. 10 11This program is distributed in the hope that it will be useful, 12but WITHOUT ANY WARRANTY; without even the implied warranty of 13MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14GNU General Public License for more details. 15 16You should have received a copy of the GNU General Public License 17along with this program; if not, write to the Free Software 18Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 19-} 20{- | 21 22Parses MathML in conformance with the MathML3 specification. 23 24Unimplemented features: 25 26 - menclose 27 - mpadded 28 - mmultiscripts (etc) 29 - malignmark 30 - maligngroup 31 - Elementary Math 32 33To Improve: 34 35 - Handling of menclose 36 - Handling of mstyle 37-} 38 39module Text.TeXMath.Readers.MathML (readMathML) where 40 41import Text.XML.Light hiding (onlyText) 42import Text.TeXMath.Types 43import Text.TeXMath.Readers.MathML.MMLDict (getMathMLOperator) 44import Text.TeXMath.Readers.MathML.EntityMap (getUnicode) 45import Text.TeXMath.Shared (getTextType, readLength, getOperator, fixTree, 46 getSpaceWidth, isEmpty, empty) 47import Text.TeXMath.Unicode.ToTeX (getSymbolType) 48import Text.TeXMath.Unicode.ToUnicode (fromUnicode) 49import Text.TeXMath.Compat (throwError, Except, runExcept, MonadError) 50import Control.Applicative ((<$>), (<|>), (<*>)) 51import Control.Arrow ((&&&)) 52import Data.Maybe (fromMaybe, listToMaybe, isJust) 53import Data.Monoid (mconcat, First(..), getFirst) 54import Data.Semigroup ((<>)) 55import Data.List (transpose) 56import qualified Data.Text as T 57import Control.Monad (filterM, guard) 58import Control.Monad.Reader (ReaderT, runReaderT, asks, local) 59import Data.Either (rights) 60 61-- | Parse a MathML expression to a list of 'Exp'. 62readMathML :: T.Text -> Either T.Text [Exp] 63readMathML inp = map fixTree <$> 64 (runExcept (flip runReaderT defaultState (i >>= parseMathML))) 65 where 66 i = maybeToEither "Invalid XML" (parseXMLDoc inp) 67 68data MMLState = MMLState { attrs :: [Attr] 69 , position :: Maybe FormType 70 , inAccent :: Bool 71 , curStyle :: TextType } 72 73type MML = ReaderT MMLState (Except T.Text) 74 75data SupOrSub = Sub | Sup deriving (Show, Eq) 76 77data IR a = Stretchy TeXSymbolType (T.Text -> Exp) T.Text 78 | Trailing (Exp -> Exp -> Exp) Exp 79 | E a 80 81instance Show a => Show (IR a) where 82 show (Stretchy t _ s) = "Stretchy " ++ show t ++ " " ++ show s 83 show (Trailing _ s) = "Trailing " ++ show s 84 show (E s) = "E " ++ show s 85 86parseMathML :: Element -> MML [Exp] 87parseMathML e@(name -> "math") = do 88 e' <- row e 89 return $ 90 case e' of 91 EGrouped es -> es 92 _ -> [e'] 93parseMathML _ = throwError "Root must be math element" 94 95expr :: Element -> MML [IR Exp] 96expr e = local (addAttrs (elAttribs e)) (expr' e) 97 98expr' :: Element -> MML [IR Exp] 99expr' e = 100 case name e of 101 "mi" -> mkE <$> ident e 102 "mn" -> mkE <$> number e 103 "mo" -> (:[]) <$> op e 104 "mtext" -> mkE <$> text e 105 "ms" -> mkE <$> literal e 106 "mspace" -> mkE <$> space e 107 "mrow" -> mkE <$> row e 108 "mstyle" -> mkE <$> style e 109 "mfrac" -> mkE <$> frac e 110 "msqrt" -> mkE <$> msqrt e 111 "mroot" -> mkE <$> kroot e 112 "merror" -> return (mkE empty) 113 "mpadded" -> mkE <$> row e 114 "mphantom" -> mkE <$> phantom e 115 "mfenced" -> mkE <$> fenced e 116 "menclose" -> mkE <$> enclosed e 117 "msub" -> sub e 118 "msup" -> sup e 119 "msubsup" -> mkE <$> subsup e 120 "munder" -> mkE <$> under e 121 "mover" -> mkE <$> over e 122 "munderover" -> mkE <$> underover e 123 "mtable" -> mkE <$> table e 124 "maction" -> mkE <$> action e 125 "semantics" -> mkE <$> semantics e 126 "maligngroup" -> return $ mkE empty 127 "malignmark" -> return $ mkE empty 128 _ -> throwError $ "Unexpected element " <> err e 129 where 130 mkE :: Exp -> [IR Exp] 131 mkE = (:[]) . E 132 133 134-- Tokens 135 136ident :: Element -> MML Exp 137ident e = do 138 s <- getString e 139 let base = case getOperator (EMathOperator s) of 140 Just _ -> EMathOperator s 141 Nothing -> EIdentifier s 142 mbVariant <- findAttrQ "mathvariant" e 143 curstyle <- asks curStyle 144 case mbVariant of 145 Nothing -> return base 146 Just v 147 | curstyle == getTextType v -> return base 148 | otherwise -> return $ EStyled (getTextType v) [base] 149 150number :: Element -> MML Exp 151number e = ENumber <$> getString e 152 153op :: Element -> MML (IR Exp) 154op e = do 155 mInferredPosition <- (<|>) <$> (getFormType <$> findAttrQ "form" e) 156 <*> asks position 157 inferredPosition <- case mInferredPosition of 158 Just inferredPosition -> pure inferredPosition 159 Nothing -> throwError "Did not find an inferred position" 160 opString <- getString e 161 let dummy = Operator opString "" inferredPosition 0 0 0 [] 162 let opLookup = getMathMLOperator opString inferredPosition 163 let opDict = fromMaybe dummy opLookup 164 props <- filterM (checkAttr (properties opDict)) 165 ["fence", "accent", "stretchy"] 166 let objectPosition = getPosition $ form opDict 167 inScript <- asks inAccent 168 let ts = [("accent", ESymbol Accent), ("fence", ESymbol objectPosition)] 169 let fallback = case T.unpack opString of 170 [t] -> ESymbol (getSymbolType t) 171 _ -> if isJust opLookup 172 then ESymbol Ord 173 else EMathOperator 174 let constructor = 175 fromMaybe fallback 176 (getFirst . mconcat $ map (First . flip lookup ts) props) 177 if ("stretchy" `elem` props) && not inScript 178 then return $ Stretchy objectPosition constructor opString 179 else do 180 return $ (E . constructor) opString 181 where 182 checkAttr ps v = maybe (v `elem` ps) (=="true") <$> findAttrQ (T.unpack v) e 183 184text :: Element -> MML Exp 185text e = do 186 textStyle <- maybe TextNormal getTextType 187 <$> (findAttrQ "mathvariant" e) 188 s <- getString e 189 -- mathml seems to use mtext for spacing often; we get 190 -- more idiomatic math if we replace these with ESpace: 191 return $ case (textStyle, T.unpack s) of 192 (TextNormal, [c]) -> 193 case getSpaceWidth c of 194 Just w -> ESpace w 195 Nothing -> EText textStyle s 196 _ -> EText textStyle s 197 198literal :: Element -> MML Exp 199literal e = do 200 lquote <- fromMaybe "\x201C" <$> findAttrQ "lquote" e 201 rquote <- fromMaybe "\x201D" <$> findAttrQ "rquote" e 202 textStyle <- maybe TextNormal getTextType 203 <$> (findAttrQ "mathvariant" e) 204 s <- getString e 205 return $ EText textStyle $ lquote <> s <> rquote 206 207space :: Element -> MML Exp 208space e = do 209 width <- fromMaybe "0.0em" <$> (findAttrQ "width" e) 210 return $ ESpace (widthToNum width) 211 212-- Layout 213 214style :: Element -> MML Exp 215style e = do 216 tt <- maybe TextNormal getTextType <$> findAttrQ "mathvariant" e 217 curstyle <- asks curStyle 218 -- We do not want to propagate the mathvariant else 219 -- we end up with nested EStyled applying the same 220 -- style 221 result <- local (filterMathVariant . enterStyled tt) (row e) 222 return $ if curstyle == tt 223 then result 224 else EStyled tt [result] 225 226row :: Element -> MML Exp 227row e = mkExp <$> group e 228 229-- 1. matchNesting strips all additional IR 230-- 2. toEDelim 231-- 3. toExp makes sure that no additional nesting happens 232mkExp :: [IR Exp] -> Exp 233mkExp = toExp . toEDelim . matchNesting 234 235toExp :: [InEDelimited] -> Exp 236toExp [] = empty 237toExp xs = 238 if any isStretchy xs 239 then case xs of 240 [x] -> either (ESymbol Ord) id x 241 _ -> EDelimited "" "" xs 242 else 243 case xs of 244 [Right x] -> x 245 _ -> EGrouped (rights xs) 246 247 248toEDelim :: [IR InEDelimited] -> [InEDelimited] 249toEDelim [] = [] 250toEDelim [Stretchy _ con s] = [Right $ con s] 251toEDelim (xs) = map removeIR xs 252 253-- Strips internal representation from processed list 254removeIR :: IR a -> a 255removeIR (E e) = e 256removeIR _ = error "removeIR, should only be ever called on processed lists" 257 258-- Convers stretch to InEDelimited element 259removeStretch :: [IR Exp] -> [IR InEDelimited] 260removeStretch [Stretchy _ constructor s] = [E $ Right (constructor s)] 261removeStretch xs = map f xs 262 where 263 f (Stretchy _ _ s) = E $ Left s 264 f (E e) = E $ Right e 265 f (Trailing a b) = Trailing a b 266 267isStretchy :: InEDelimited -> Bool 268isStretchy (Left _) = True 269isStretchy (Right _) = False 270 271-- If at the end of a delimiter we need to apply the script to the whole 272-- expression. We only insert Trailing when reordering Stretchy 273trailingSup :: Maybe (T.Text, T.Text -> Exp) -> Maybe (T.Text, T.Text -> Exp) -> [IR InEDelimited] -> Exp 274trailingSup open close es = go es 275 where 276 go [] = case (open, close) of 277 (Nothing, Nothing) -> empty 278 (Just (openFence, conOpen), Nothing) -> conOpen openFence 279 (Nothing, Just (closeFence, conClose)) -> conClose closeFence 280 (Just (openFence, conOpen), Just (closeFence, conClose)) -> 281 EGrouped [conOpen openFence, conClose closeFence] 282 go es'@(last -> Trailing constructor e) = (constructor (go (init es')) e) 283 go es' = EDelimited (getFence open) (getFence close) (toEDelim es') 284 getFence = fromMaybe "" . fmap fst 285 286-- TODO: Break this into two functions 287-- Matches open and closing brackets 288-- The result of this function is a list with only E elements. 289matchNesting :: [IR Exp] -> [IR InEDelimited] 290matchNesting ((break isFence) -> (inis, rest)) = 291 let inis' = removeStretch inis in 292 case rest of 293 [] -> inis' 294 ((Stretchy Open conOpen opens): rs) -> 295 let jOpen = Just (opens, conOpen) 296 (body, rems) = go rs 0 [] 297 body' = matchNesting body in 298 case rems of 299 [] -> inis' ++ [E $ Right $ trailingSup jOpen Nothing body'] 300 (Stretchy Close conClose closes : rs') -> 301 let jClose = Just (closes, conClose) in 302 inis' ++ (E $ Right $ trailingSup jOpen jClose body') : matchNesting rs' 303 _ -> (error "matchNesting: Logical error 1") 304 ((Stretchy Close conClose closes): rs) -> 305 let jClose = Just (closes, conClose) in 306 (E $ Right $ trailingSup Nothing jClose (matchNesting inis)) : matchNesting rs 307 _ -> error "matchNesting: Logical error 2" 308 where 309 isOpen (Stretchy Open _ _) = True 310 isOpen _ = False 311 isClose (Stretchy Close _ _) = True 312 isClose _ = False 313 go :: [IR a] -> Int -> [IR a] -> ([IR a], [IR a]) 314 go (x:xs) 0 a | isClose x = (reverse a, x:xs) 315 go (x:xs) n a | isOpen x = go xs (n + 1) (x:a) 316 go (x:xs) n a | isClose x = go xs (n - 1) (x:a) 317 go (x:xs) n a = go xs n (x:a) 318 go [] _ a = (reverse a, []) 319 320isFence :: IR a -> Bool 321isFence (Stretchy Open _ _) = True 322isFence (Stretchy Close _ _) = True 323isFence _ = False 324 325group :: Element -> MML [IR Exp] 326group e = do 327 front <- concat <$> mapM expr frontSpaces 328 middle <- local resetPosition (row' body) 329 end <- concat <$> local resetPosition (mapM expr endSpaces) 330 return $ (front ++ middle ++ end) 331 where 332 cs = elChildren e 333 (frontSpaces, noFront) = span spacelike cs 334 (endSpaces, body) = let (as, bs) = span spacelike (reverse noFront) in 335 (reverse as, reverse bs) 336 337row' :: [Element] -> MML [IR Exp] 338row' [] = return [] 339row' [x] = do 340 pos <- maybe FInfix (const FPostfix) <$> asks position 341 local (setPosition pos) (expr x) 342row' (x:xs) = 343 do 344 pos <- maybe FPrefix (const FInfix) <$> asks position 345 e <- local (setPosition pos) (expr x) 346 es <- local (setPosition pos) (row' xs) 347 return (e ++ es) 348 349-- Indicates the closure of scope 350safeExpr :: Element -> MML Exp 351safeExpr e = mkExp <$> expr e 352 353frac :: Element -> MML Exp 354frac e = do 355 (num, denom) <- mapPairM safeExpr =<< (checkArgs2 e) 356 rawThick <- findAttrQ "linethickness" e 357 return $ 358 if thicknessZero rawThick 359 then EFraction NoLineFrac num denom 360 else EFraction NormalFrac num denom 361 362msqrt :: Element -> MML Exp 363msqrt e = ESqrt <$> (row e) 364 365kroot :: Element -> MML Exp 366kroot e = do 367 (base, index) <- mapPairM safeExpr =<< (checkArgs2 e) 368 return $ ERoot index base 369 370phantom :: Element -> MML Exp 371phantom e = EPhantom <$> row e 372 373fenced :: Element -> MML Exp 374fenced e = do 375 open <- fromMaybe "(" <$> (findAttrQ "open" e) 376 close <- fromMaybe ")" <$> (findAttrQ "close" e) 377 sep <- fromMaybe "," <$> (findAttrQ "separators" e) 378 let expanded = 379 case sep of 380 "" -> elChildren e 381 _ -> 382 let seps = map (\x -> unode "mo" [x]) $ T.unpack sep 383 sepsList = seps ++ repeat (last seps) in 384 fInterleave (elChildren e) (sepsList) 385 safeExpr $ unode "mrow" 386 ([tunode "mo" open | not $ T.null open] ++ 387 [unode "mrow" expanded] ++ 388 [tunode "mo" close | not $ T.null close]) 389 390-- This could approximate the variants 391enclosed :: Element -> MML Exp 392enclosed e = do 393 mbNotation <- findAttrQ "notation" e 394 case mbNotation of 395 Just "box" -> EBoxed <$> row e 396 _ -> row e 397 398action :: Element -> MML Exp 399action e = do 400 selection <- maybe 1 (read . T.unpack) <$> (findAttrQ "selection" e) -- 1-indexing 401 safeExpr =<< maybeToEither ("Selection out of range") 402 (listToMaybe $ drop (selection - 1) (elChildren e)) 403 404-- Scripts and Limits 405 406sub :: Element -> MML [IR Exp] 407sub e = do 408 (base, subs) <- checkArgs2 e 409 reorderScripts base subs ESub 410 411-- Handles case with strethy elements in the base of sub/sup 412reorderScripts :: Element -> Element -> (Exp -> Exp -> Exp) -> MML [IR Exp] 413reorderScripts e subs c = do 414 baseExpr <- expr e 415 subExpr <- postfixExpr subs 416 return $ 417 case baseExpr of 418 [s@(Stretchy Open _ _)] -> [s, E $ c empty subExpr] -- Open 419 [s@(Stretchy Close _ _)] -> [Trailing c subExpr, s] -- Close 420 [s@(Stretchy _ _ _)] -> [s, E $ ESub empty subExpr] -- Middle 421 _ -> [E $ c (mkExp baseExpr) subExpr] -- No stretch 422 423sup :: Element -> MML [IR Exp] 424sup e = do 425 (base, sups) <- checkArgs2 e 426 reorderScripts base sups ESuper 427 428subsup :: Element -> MML Exp 429subsup e = do 430 (base, subs, sups) <- checkArgs3 e 431 ESubsup <$> safeExpr base <*> (postfixExpr subs) 432 <*> (postfixExpr sups) 433 434under :: Element -> MML Exp 435under e = do 436 (base, below) <- checkArgs2 e 437 EUnder False <$> safeExpr base <*> postfixExpr below 438 439over :: Element -> MML Exp 440over e = do 441 (base, above) <- checkArgs2 e 442 EOver False <$> safeExpr base <*> postfixExpr above 443 444underover :: Element -> MML Exp 445underover e = do 446 (base, below, above) <- checkArgs3 e 447 EUnderover False <$> safeExpr base <*> (postfixExpr below) 448 <*> (postfixExpr above) 449 450-- Other 451 452semantics :: Element -> MML Exp 453semantics e = do 454 guard (not $ null cs) 455 first <- safeExpr (head cs) 456 if isEmpty first 457 then fromMaybe empty . getFirst . mconcat <$> mapM annotation (tail cs) 458 else return first 459 where 460 cs = elChildren e 461 462annotation :: Element -> MML (First Exp) 463annotation e = do 464 encoding <- findAttrQ "encoding" e 465 case encoding of 466 Just "application/mathml-presentation+xml" -> 467 First . Just <$> row e 468 Just "MathML-Presentation" -> 469 First . Just <$> row e 470 _ -> return (First Nothing) 471 472-- Table 473 474table :: Element -> MML Exp 475table e = do 476 defAlign <- maybe AlignCenter toAlignment <$> (findAttrQ "columnalign" e) 477 rs <- mapM (tableRow defAlign) (elChildren e) 478 let (onlyAligns, exprs) = (map .map) fst &&& (map . map) snd $ rs 479 let rs' = map (pad (maximum (map length rs))) exprs 480 let aligns = map findAlign (transpose onlyAligns) 481 return $ EArray aligns rs' 482 where 483 findAlign xs = if null xs then AlignCenter 484 else foldl1 combine xs 485 combine x y = if x == y then x else AlignCenter 486 487tableRow :: Alignment -> Element -> MML [(Alignment, [Exp])] 488tableRow a e = do 489 align <- maybe a toAlignment <$> (findAttrQ "columnalign" e) 490 case name e of 491 "mtr" -> mapM (tableCell align) (elChildren e) 492 "mlabeledtr" -> mapM (tableCell align) (tail $ elChildren e) 493 _ -> throwError $ "Invalid Element: Only expecting mtr elements " <> err e 494 495tableCell :: Alignment -> Element -> MML (Alignment, [Exp]) 496tableCell a e = do 497 align <- maybe a toAlignment <$> (findAttrQ "columnalign" e) 498 case name e of 499 "mtd" -> (,) align . (:[]) <$> row e 500 _ -> throwError $ "Invalid Element: Only expecting mtd elements " <> err e 501 502-- Fixup 503 504-- Library Functions 505 506maybeToEither :: (MonadError e m) => e -> Maybe a -> m a 507maybeToEither = flip maybe return . throwError 508 509--interleave up to end of shorter list 510fInterleave :: [a] -> [a] -> [a] 511fInterleave [] _ = [] 512fInterleave _ [] = [] 513fInterleave (x:xs) ys = x : fInterleave ys xs 514 515-- MMLState helper functions 516 517defaultState :: MMLState 518defaultState = MMLState [] Nothing False TextNormal 519 520addAttrs :: [Attr] -> MMLState -> MMLState 521addAttrs as s = s {attrs = (map renameAttr as) ++ attrs s } 522 523renameAttr :: Attr -> Attr 524renameAttr v@(qName . attrKey -> "accentunder") = 525 Attr (unqual "accent") (attrVal v) 526renameAttr a = a 527 528filterMathVariant :: MMLState -> MMLState 529filterMathVariant s@(attrs -> as) = 530 s{attrs = filter ((/= unqual "mathvariant") . attrKey) as} 531 532setPosition :: FormType -> MMLState -> MMLState 533setPosition p s = s {position = Just p} 534 535resetPosition :: MMLState -> MMLState 536resetPosition s = s {position = Nothing} 537 538enterAccent :: MMLState -> MMLState 539enterAccent s = s{ inAccent = True } 540 541enterStyled :: TextType -> MMLState -> MMLState 542enterStyled tt s = s{ curStyle = tt } 543 544-- Utility 545 546getString :: Element -> MML T.Text 547getString e = do 548 tt <- asks curStyle 549 return $ fromUnicode tt $ stripSpaces $ T.pack $ concatMap cdData 550 $ onlyText $ elContent $ e 551 552-- Finds only text data and replaces entity references with corresponding 553-- characters 554onlyText :: [Content] -> [CData] 555onlyText [] = [] 556onlyText ((Text c):xs) = c : onlyText xs 557onlyText (CRef s : xs) = (CData CDataText (fromMaybe s $ getUnicode' s) Nothing) : onlyText xs 558 where getUnicode' = fmap T.unpack . getUnicode . T.pack 559onlyText (_:xs) = onlyText xs 560 561checkArgs2 :: Element -> MML (Element, Element) 562checkArgs2 e = case elChildren e of 563 [a, b] -> return (a, b) 564 _ -> throwError ("Incorrect number of arguments for " <> err e) 565 566checkArgs3 :: Element -> MML (Element, Element, Element) 567checkArgs3 e = case elChildren e of 568 [a, b, c] -> return (a, b, c) 569 _ -> throwError ("Incorrect number of arguments for " <> err e) 570 571mapPairM :: Monad m => (a -> m b) -> (a, a) -> m (b, b) 572mapPairM f (a, b) = (,) <$> (f a) <*> (f b) 573 574err :: Element -> T.Text 575err e = name e <> maybe "" (\x -> " line " <> T.pack (show x)) (elLine e) 576 577-- Kept as String for Text.XML.Light 578findAttrQ :: String -> Element -> MML (Maybe T.Text) 579findAttrQ s e = do 580 inherit <- asks (lookupAttrQ s . attrs) 581 return $ fmap T.pack $ 582 findAttr (QName s Nothing Nothing) e 583 <|> inherit 584 585-- Kept as String for Text.XML.Light 586lookupAttrQ :: String -> [Attr] -> Maybe String 587lookupAttrQ s = lookupAttr (QName s Nothing Nothing) 588 589name :: Element -> T.Text 590name (elName -> (QName n _ _)) = T.pack n 591 592-- Kept as String for Text.XML.Light 593tunode :: String -> T.Text -> Element 594tunode s = unode s . T.unpack 595 596stripSpaces :: T.Text -> T.Text 597stripSpaces = T.dropAround isSpace 598 599toAlignment :: T.Text -> Alignment 600toAlignment "left" = AlignLeft 601toAlignment "center" = AlignCenter 602toAlignment "right" = AlignRight 603toAlignment _ = AlignCenter 604 605getPosition :: FormType -> TeXSymbolType 606getPosition (FPrefix) = Open 607getPosition (FPostfix) = Close 608getPosition (FInfix) = Op 609 610getFormType :: Maybe T.Text -> Maybe FormType 611getFormType (Just "infix") = (Just FInfix) 612getFormType (Just "prefix") = (Just FPrefix) 613getFormType (Just "postfix") = (Just FPostfix) 614getFormType _ = Nothing 615 616pad :: Int -> [[a]] -> [[a]] 617pad n xs = xs ++ (replicate (n - len) []) 618 where 619 len = length xs 620 621isSpace :: Char -> Bool 622isSpace ' ' = True 623isSpace '\t' = True 624isSpace '\n' = True 625isSpace _ = False 626 627spacelikeElems, cSpacelikeElems :: [T.Text] 628spacelikeElems = ["mtext", "mspace", "maligngroup", "malignmark"] 629cSpacelikeElems = ["mrow", "mstyle", "mphantom", "mpadded"] 630 631spacelike :: Element -> Bool 632spacelike e@(name -> uid) = 633 uid `elem` spacelikeElems || uid `elem` cSpacelikeElems && 634 and (map spacelike (elChildren e)) 635 636thicknessZero :: Maybe T.Text -> Bool 637thicknessZero (Just s) = thicknessToNum s == 0.0 638thicknessZero Nothing = False 639 640widthToNum :: T.Text -> Rational 641widthToNum s = 642 case s of 643 "veryverythinmathspace" -> 1/18 644 "verythinmathspace" -> 2/18 645 "thinmathspace" -> 3/18 646 "mediummathspace" -> 4/18 647 "thickmathspace" -> 5/18 648 "verythickmathspace" -> 6/18 649 "veryverythickmathspace" -> 7/18 650 "negativeveryverythinmathspace" -> -1/18 651 "negativeverythinmathspace" -> -2/18 652 "negativethinmathspace" -> -3/18 653 "negativemediummathspace" -> -4/18 654 "negativethickmathspace" -> -5/18 655 "negativeverythickmathspace" -> -6/18 656 "negativeveryverythickmathspace" -> -7/18 657 _ -> fromMaybe 0 (readLength s) 658 659thicknessToNum :: T.Text -> Rational 660thicknessToNum s = 661 case s of 662 "thin" -> (3/18) 663 "medium" -> (1/2) 664 "thick" -> 1 665 v -> fromMaybe 0.5 (readLength v) 666 667postfixExpr :: Element -> MML Exp 668postfixExpr e = local (setPosition FPostfix . enterAccent) (safeExpr e) 669