packages feed

Eq-1.0: EqManips/InputParser/MathML.hs

{-# OPTIONS_GHC -fno-warn-orphans #-}
module EqManips.InputParser.MathML ( mathMlToEqLang
                                   , mathMlToEqLang'
                                   ) where

import Control.Applicative
import EqManips.Algorithm.Utils
import qualified EqManips.UnicodeSymbols as Uni

import Text.XML.HaXml.Parse
import Text.XML.HaXml.Types

-- | Type used to reduce the complexity of XML
-- tree and favor an easier pattern matching
data ReducedXmlTree =
      Xop String
    | Xsymb String
    | Xnum String
    | Xsqrt ReducedXmlTree
    | Xfrac ReducedXmlTree ReducedXmlTree
    | Xsup ReducedXmlTree ReducedXmlTree
    | XunderOver ReducedXmlTree ReducedXmlTree ReducedXmlTree
    | Xfenced String String ReducedXmlTree
    | Xrow [ReducedXmlTree]
    | Xtable [[ReducedXmlTree]]
    deriving (Show)

mathMlToEqLang' :: String -> String
mathMlToEqLang' = either id id . mathMlToEqLang

-- | Input XML code encoded in a string
-- output a string in Eq Language, ready to
-- be parsed by the usual meanings.
mathMlToEqLang :: String -> Either String String
mathMlToEqLang text =
    xmlParse' "mathml" text >>= simplifyXml >>= toProgramString

toProgramString :: ReducedXmlTree -> Either String String
toProgramString tree = (\s -> s "") <$> translate tree

simplifyXml :: Document a -> Either String ReducedXmlTree
simplifyXml (Document a b (Elem "m:math" c lst) l) =
    simplifyXml (Document a b (Elem "math" c lst) l)
simplifyXml (Document _ _ (Elem "math" _ lst) _) =
    Xrow <$> eitherMap (map simplifyContent lst)
simplifyXml _ = error "The xml document has the wrong format"

strOfContent :: Content a -> String
strOfContent (CString _ txt _) = txt
strOfContent _ = error "Xml string waited at this point"

elemOfContent :: Content a -> Element a
elemOfContent (CElem e _) = e
elemOfContent _ = error "Xml element waited at this point"

-- | Helper to simplify content
simplifyContent :: Content a -> Either String ReducedXmlTree
simplifyContent = simplify . elemOfContent

instance Applicative (Either a) where
    pure = Right
    (<*>) (Left a) _ = Left a
    (<*>) (Right _) (Left b) = Left b
    (<*>) (Right f) (Right v) = Right (f v)

instance Monad (Either a) where
    return = Right
    (>>=) (Left a) _ = Left a
    (>>=) (Right v) f = f v

eitherMap :: [Either a b] -> Either a [b]
eitherMap [] = Right []
eitherMap lst = foldr mapper (Right []) lst
    where mapper (Left a) _ = Left a
          mapper _ (Left a) = Left a
          mapper (Right v) (Right list) = Right (v:list)

-- | Really transform an XML file to a simplified tree
-- to make a better pattern matching
simplify :: Element a -> Either String ReducedXmlTree
-- This rule is for mathML generated by microsoft math input
-- panel whom got the bad habit of prefixing it by 'm:'
simplify (Elem ('m':':':xs) att cont) = simplify (Elem xs att cont)
simplify (Elem "mi" _ [c]) = Right . Xsymb $ strOfContent c
simplify (Elem "mn" _ [c]) = Right . Xnum $ strOfContent c
simplify (Elem "mo" _ [c]) = Right . Xop $ strOfContent c
simplify (Elem "mrow" _ lst) = Xrow <$> eitherMap (map simplifyContent lst)
simplify (Elem "msqrt" _ lst) = Xsqrt . Xrow <$> eitherMap (map simplifyContent lst)
simplify (Elem "mfrac" _ [a,b]) = Xfrac <$> simplifyContent a <*> simplifyContent b
simplify (Elem "msup" _ [a,b]) = Xsup <$> simplifyContent a <*> simplifyContent b
simplify (Elem "munderover" _ [a,b,c]) = 
    XunderOver <$> simplifyContent a <*> simplifyContent b <*> simplifyContent c

simplify (Elem "mtable" _ lst) = Xtable <$> lineList
    where lineList = eitherMap $ map (unrow . elemOfContent) lst

          unrow (Elem "m:mtr" a b) = unrow (Elem "mtr" a b)
          unrow (Elem "mtr" _ cells) = eitherMap $ map (uncell . elemOfContent) cells
          unrow _ = Left "Ill formed MathML Matrix"

          uncell (Elem "m:mtd" a b) = uncell (Elem "mtd" a b)
          uncell (Elem "mtd" _ cellList) = Xrow <$> eitherMap (map simplifyContent cellList)
          uncell _ = Left "Ill format MathML Matrix cell"

simplify (Elem "mfenced" [ ("open", AttValue [Left openChar])
                         , ("close", AttValue [Left closeChar]) ] lst) =

    Xfenced openChar closeChar . Xrow <$> eitherMap (map simplifyContent lst)

simplify (Elem "mfenced" attrs _lst) = Left $ show attrs
    
simplify (Elem elemName _ _) = Left $ "Unknown MathMl element : " ++ elemName

str :: String -> String -> String
str = (++)

char :: Char -> String -> String
char = (:)

uniSymbolTranslation :: [(Int, String)]
uniSymbolTranslation =
    [ (Uni.pi, "pi")
    , (Uni.infinity, "infinite") 
    ]

unicodeTranslation :: [(Int, String)]
unicodeTranslation =
    [ (Uni.logicalAnd, "&&")
    , (Uni.logicalOr, "||")
    , (Uni.logicalNot, "not")
    , (Uni.identicalTo, "==")
    , (Uni.lessThanOrEqualTo, "<=")
    , (Uni.greaterThanOrEqualTo, ">=")
    , (Uni.multiplicationSign , "*")
    ]

vardeclFinder :: [ReducedXmlTree]
              -> Maybe ([ReducedXmlTree],[ReducedXmlTree], String)
vardeclFinder = declFind []
    where declFind   _ [] = Nothing
          declFind acc (Xop [op]:next) 
            | fromEnum op == Uni.doubleStruckItalicSmalld = obtainVar acc next
          declFind acc (Xsymb ['d']:next) = obtainVar acc next
          declFind acc (Xsymb ['d', var]:next) = Just (reverse acc, next, [var])
          declFind acc (Xrow lst:next) = declFind acc (lst ++ next)
          declFind acc (x:xs) = declFind (x:acc) xs

          obtainVar _ [] = Nothing
          obtainVar acc (Xsymb var:next) = Just (reverse acc, next, var)
          obtainVar acc (Xrow lst:next) = obtainVar acc (lst ++ next)
          obtainVar _ _ = Nothing

-- | Real transformation =)
translate :: ReducedXmlTree -> Either String ShowS
translate (Xop [s]) = case lookup (fromEnum s) unicodeTranslation of
       Nothing -> Right $ char s
       Just v -> Right $ str v

translate (Xsymb [s]) = case lookup (fromEnum s) uniSymbolTranslation of
       Nothing -> Right $ char s
       Just v -> Right $ str v

-- Special case to handle matrix
translate (Xfenced op en body@(Xtable _)) 
    | (op == "(" && en == ")") || (op == "[" && en == "]") = translate body
translate (Xfenced op en (Xrow [body@(Xtable _)]))
    | (op == "(" && en == ")") || (op == "[" && en == "]") = translate body

translate (Xfenced "(" ")" body) =
    (\sub -> char '(' . sub . char ')') <$> translate body
translate (Xfenced "|" "|" body) =
    (\sub -> str "abs(" . sub . char ')') <$> translate body
translate (Xfenced str1 str2 body) =
    (\sub -> shows body . str str1 . sub . str str2) <$> translate body

translate (Xrow ((XunderOver (Xop [bigop]) lowerBound upperBound):rs))
    | fromEnum bigop == Uni.sum =
        (\ini end what -> str "sum(" . ini . char ',' . end . char ','
                                     . what . char ')')
                <$> translate lowerBound
                <*> translate upperBound
                <*> translate (Xrow rs)
    | fromEnum bigop == Uni.product =
        (\ini end what -> str "product(" . ini . char ',' . end . char ','
                                         . what . char ')')
                <$> translate lowerBound
                <*> translate upperBound
                <*> translate (Xrow rs)
    | fromEnum bigop == Uni.integral = case vardeclFinder rs of
            Nothing -> Left "Invalid integral definition, cannot be handled"
            Just (acc,rest,var) ->
                (\lower upper what rest' ->
                    str "integrate(" . lower . char ',' . upper
                                     . char ',' . what . char ',' 
                                     . str var . char ')' . rest')
                    <$> translate lowerBound
                    <*> translate upperBound
                    <*> translate (Xrow acc)
                    <*> translate (Xrow rest)
    | otherwise = Left "Unrecognized big operator"

translate (XunderOver _ _ _) = Left "Unrecognized operator"
translate (Xop s) = Right $ str s
translate (Xsymb s) = Right $ str s
translate (Xnum s) = Right $ str s
translate (Xsqrt subTree) = (\sub -> str "sqrt(" . sub . char ')')
                         <$> translate subTree 
translate (Xfrac a b) = (\a' b' -> char '(' . a' . str ") / (" . b' . char ')')
                     <$> translate a 
                     <*> translate b

translate (Xsup a b) = (\a' b' -> char '(' . a' . str ") ^ (" . b' . char ')')
                    <$> translate a 
                    <*> translate b

translate (Xrow []) = Right id
translate (Xrow lst) = concatS <$> eitherMap (map translate lst)

translate (Xtable []) = Left "Wrong table format"
translate (Xtable lst) =
    (\elems -> str "matrix( " . shows lineCount . char ',' . shows columncount . char ','
                              . interspereseS (char ',') elems . char ')')
        <$> (eitherMap . map translate $ concat lst) 
    where lineCount = length lst
          columncount = length $ head lst