Skip to content

Commit

Permalink
Add support for column separators in arrays (jgm#127).
Browse files Browse the repository at this point in the history
- Add [ColumnSeparator] to EArray.
- Readers.MathML can generate any of {CSNone, CSDashed, CSSolid}, which should round-trip through Writers.MathML
- {Readers,Writers}.TeX only support {CSNone, CSSolid}
- Readers.OMML currently generates CSNone, and Writers.OMML doesn't emit ColumnSeparator information
  • Loading branch information
aweinstock314 committed Jan 10, 2021
1 parent afe95c4 commit e808517
Show file tree
Hide file tree
Showing 9 changed files with 75 additions and 51 deletions.
9 changes: 8 additions & 1 deletion src/Text/TeXMath/Readers/MathML.hs
Original file line number Diff line number Diff line change
Expand Up @@ -478,7 +478,8 @@ table e = do
let (onlyAligns, exprs) = (map .map) fst &&& (map . map) snd $ rs
let rs' = map (pad (maximum (map length rs))) exprs
let aligns = map findAlign (transpose onlyAligns)
return $ EArray aligns rs'
colseps <- maybe (replicate (length aligns-1) CSNone) (map toColSep . T.splitOn " ") <$> findAttrQ "columnlines" e
return $ EArray aligns rs' colseps
where
findAlign xs = if null xs then AlignCenter
else foldl1 combine xs
Expand Down Expand Up @@ -602,6 +603,12 @@ toAlignment "center" = AlignCenter
toAlignment "right" = AlignRight
toAlignment _ = AlignCenter

toColSep :: T.Text -> ColumnSeparator
toColSep "none" = CSNone
toColSep "dashed" = CSDashed
toColSep "solid" = CSSolid
toColSep _ = CSNone

getPosition :: FormType -> TeXSymbolType
getPosition (FPrefix) = Open
getPosition (FPostfix) = Close
Expand Down
4 changes: 2 additions & 2 deletions src/Text/TeXMath/Readers/OMML.hs
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,7 @@ elemToExps' element | isElem "m" "eqArr" element =
let expLst = mapMaybe elemToBases (elChildren element)
expLst' = map (\es -> [map filterAmpersand es]) expLst
in
return [EArray [] expLst']
return [EArray [] expLst' []]
elemToExps' element | isElem "m" "f" element = do
num <- filterChildName (hasElemName "m" "num") element
den <- filterChildName (hasElemName "m" "den") element
Expand Down Expand Up @@ -365,7 +365,7 @@ elemToExps' element | isElem "m" "m" element =
(elChildren mr))
rows
in
return [EArray [AlignCenter] rowExps]
return [EArray [AlignCenter] rowExps []]
elemToExps' element | isElem "m" "nary" element = do
let naryPr = filterChildName (hasElemName "m" "naryPr") element
naryChr = naryPr >>=
Expand Down
39 changes: 22 additions & 17 deletions src/Text/TeXMath/Readers/TeX.hs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
module Text.TeXMath.Readers.TeX (readTeX)
where

import Data.List (intercalate, intersperse, find)
import Data.List (intercalate, intersperse, find, elemIndices)
import Data.Ratio ((%))
import Control.Monad
import Data.Char (isDigit, isAscii, isLetter)
Expand Down Expand Up @@ -250,7 +250,7 @@ genfrac _ = mzero
substack :: Text -> TP Exp
substack "\\substack" = do
formulas <- braces $ ignorable >> (manyExp expr) `sepEndBy` endLine
return $ EArray [AlignCenter] $ map (\x -> [[x]]) formulas
return $ EArray [AlignCenter] (map (\x -> [[x]]) formulas) []
substack _ = mzero

asGroup :: [Exp] -> Exp
Expand Down Expand Up @@ -372,14 +372,16 @@ arrayLine = notFollowedBy (ctrlseq "end" >> return '\n') >>
optional (try (ctrlseq "hline" >> ignorable'))
-- we don't represent the line, but it shouldn't crash parsing

arrayAlignments :: TP [Alignment]
arrayAlignments :: TP ([Alignment], [ColumnSeparator])
arrayAlignments = try $ do
as <- braces (many (letter <|> char '|'))
let letterToAlignment 'l' = AlignLeft
letterToAlignment 'c' = AlignCenter
letterToAlignment 'r' = AlignRight
letterToAlignment _ = AlignCenter
return $ map letterToAlignment $ filter (/= '|') as
let generate n f = map f [0..n-1]
let findColSeps = let seperatorPositions = elemIndices '|' as in generate (length as) (\i -> if i `elem` seperatorPositions then CSSolid else CSNone)
return $ (map letterToAlignment $ filter (/= '|') as, findColSeps)

environment :: Text -> TP Exp
environment "\\begin" = do
Expand Down Expand Up @@ -421,29 +423,31 @@ environments = M.fromList
, ("equation", equation)
]

alignsFromRows :: Alignment -> [ArrayLine] -> [Alignment]
alignsFromRows _ [] = []
alignsFromRows defaultAlignment (r:_) = replicate (length r) defaultAlignment
alignsFromRows :: Alignment -> ColumnSeparator -> [ArrayLine] -> ([Alignment], [ColumnSeparator])
alignsFromRows _ _ [] = ([], [])
alignsFromRows defaultAlignment defaultColSep (r:_) = let n = length r in (replicate n defaultAlignment, replicate (n-1) defaultColSep)

matrixWith :: Text -> Text -> TP Exp
matrixWith opendelim closedelim = do
lines' <- sepEndBy1 arrayLine endLineAMS
let aligns = alignsFromRows AlignCenter lines'
let (aligns, colseps) = alignsFromRows AlignCenter CSNone lines'
return $ if T.null opendelim && T.null closedelim
then EArray aligns lines'
then EArray aligns lines' colseps
else EDelimited opendelim closedelim
[Right $ EArray aligns lines']
[Right $ EArray aligns lines' colseps]

stdarray :: TP Exp
stdarray = do
aligns <- arrayAlignments
(aligns, colseps) <- arrayAlignments
--aligns <- arrayAlignments
lines' <- sepEndBy1 arrayLine endLine
return $ EArray aligns lines'
return $ EArray aligns lines' colseps

gather :: TP Exp
gather = do
rows <- sepEndBy arrayLine endLineAMS
return $ EArray (alignsFromRows AlignCenter rows) rows
let (aligns, colseps) = (alignsFromRows AlignCenter CSNone rows)
return $ EArray aligns rows colseps

equation :: TP Exp
equation = do
Expand All @@ -454,24 +458,25 @@ eqnarray :: TP Exp
eqnarray = do
rows <- sepEndBy1 arrayLine endLine
let n = maximum $ map length rows
return $ EArray (take n $ cycle [AlignRight, AlignCenter, AlignLeft]) rows
return $ EArray (take n $ cycle [AlignRight, AlignCenter, AlignLeft]) rows (replicate (n-1) CSNone)

align :: TP Exp
align = do
rows <- sepEndBy1 arrayLine endLineAMS
let n = maximum $ map length rows
return $ EArray (take n $ cycle [AlignRight, AlignLeft]) rows
return $ EArray (take n $ cycle [AlignRight, AlignLeft]) rows (replicate (n-1) CSNone)

flalign :: TP Exp
flalign = do
rows <- sepEndBy1 arrayLine endLineAMS
let n = maximum $ map length rows
return $ EArray (take n $ cycle [AlignLeft, AlignRight]) rows
return $ EArray (take n $ cycle [AlignLeft, AlignRight]) rows (replicate (n-1) CSNone)

cases :: TP Exp
cases = do
rs <- sepEndBy1 arrayLine endLineAMS
return $ EDelimited "{" "" [Right $ EArray (alignsFromRows AlignLeft rs) rs]
let (aligns, colseps) = (alignsFromRows AlignLeft CSNone rs)
return $ EDelimited "{" "" [Right $ EArray aligns rs colseps]

variable :: TP Exp
variable = do
Expand Down
12 changes: 8 additions & 4 deletions src/Text/TeXMath/Types.hs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

module Text.TeXMath.Types (Exp(..), TeXSymbolType(..), ArrayLine,
FractionType(..), TextType(..),
Alignment(..), DisplayType(..),
Alignment(..), ColumnSeparator(..), DisplayType(..),
Operator(..), FormType(..), Record(..),
Property, Position(..), Env, defaultEnv,
InEDelimited)
Expand All @@ -38,6 +38,9 @@ data TeXSymbolType = Ord | Op | Bin | Rel | Open | Close | Pun | Accent
data Alignment = AlignLeft | AlignCenter | AlignRight
deriving (Show, Read, Eq, Ord, Data, Typeable)

data ColumnSeparator = CSNone | CSDashed | CSSolid
deriving (Show, Read, Eq, Ord, Data, Typeable)

data FractionType = NormalFrac -- ^ Displayed or textual, acc to 'DisplayType'
| DisplayFrac -- ^ Force display mode
| InlineFrac -- ^ Force inline mode (textual)
Expand Down Expand Up @@ -85,10 +88,11 @@ data Exp =
| ESqrt Exp -- ^ A square root.
| EScaled Rational Exp -- ^ An expression that is scaled to some factor
-- of its normal size.
| EArray [Alignment] [ArrayLine] -- ^ An array or matrix. The first argument
| EArray [Alignment] [ArrayLine] [ColumnSeparator] -- ^ An array or matrix. The first argument
-- specifies the alignments of the columns; the second gives
-- the contents of the lines. All of these lists should be
-- the same length.
-- the contents of the lines; the third gives the type of separator between
-- columns. The first two of these lists should be the same length, the last
-- be one element shorter.
| EText TextType T.Text -- ^ Some normal text, possibly styled.
| EStyled TextType [Exp] -- ^ A group of styled expressions.
deriving (Show, Read, Eq, Ord, Data, Typeable)
Expand Down
2 changes: 1 addition & 1 deletion src/Text/TeXMath/Writers/Eqn.hs
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ writeExp (EStyled ttype es) =
TextBold -> "bold " <> contents
TextBoldItalic -> "bold italic " <> contents
_ -> contents
writeExp (EArray aligns rows) =
writeExp (EArray aligns rows _) = -- TODO: does \matrix support column separators?
"matrix{\n" <> T.concat cols <> "}"
where cols = zipWith tocol aligns (transpose rows)
tocol al cs =
Expand Down
11 changes: 8 additions & 3 deletions src/Text/TeXMath/Writers/MathML.hs
Original file line number Diff line number Diff line change
Expand Up @@ -113,14 +113,19 @@ makeText a s = case (leadingSp, trailingSp) of
_ -> False
attr = getMMLType a

makeArray :: TextType -> [Alignment] -> [ArrayLine] -> Element
makeArray tt as ls = unode "mtable" $
makeArray :: TextType -> [Alignment] -> [ArrayLine] -> [ColumnSeparator] -> Element
makeArray tt as ls cs = setColumnLines $ unode "mtable" $
map (unode "mtr" .
zipWith (\a -> setAlignment a . unode "mtd". map (showExp tt)) as') ls
where setAlignment AlignLeft = withAttribute "columnalign" "left"
setAlignment AlignRight = withAttribute "columnalign" "right"
setAlignment AlignCenter = withAttribute "columnalign" "center"
setColumnLines = withAttribute "columnlines" . T.intercalate " " $ map colSepToString cs'
colSepToString CSNone = "none"
colSepToString CSDashed = "dashed"
colSepToString CSSolid = "solid"
as' = as ++ cycle [AlignCenter]
cs' = cs ++ cycle [CSNone]

-- Kept as String for Text.XML.Light
withAttribute :: String -> T.Text -> Element -> Element
Expand Down Expand Up @@ -176,7 +181,7 @@ showExp tt e =
ESqrt x -> unode "msqrt" $ showExp tt x
ERoot i x -> unode "mroot" [showExp tt x, showExp tt i]
EScaled s x -> makeScaled s $ showExp tt x
EArray as ls -> makeArray tt as ls
EArray as ls cs -> makeArray tt as ls cs
EText a s -> makeText a s
EStyled a es -> makeStyled a $ map (showExp a) es

Expand Down
6 changes: 3 additions & 3 deletions src/Text/TeXMath/Writers/OMML.hs
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,8 @@ maximum' :: [Int] -> Int
maximum' [] = 0
maximum' xs = maximum xs

makeArray :: [Element] -> [Alignment] -> [ArrayLine] -> Element
makeArray props as rs = mnode "m" $ mProps : map toMr rs
makeArray :: [Element] -> [Alignment] -> [ArrayLine] -> [ColumnSeparator] -> Element
makeArray props as rs _ = mnode "m" $ mProps : map toMr rs
where mProps = mnode "mPr"
[ mnodeA "baseJc" "center" ()
, mnodeA "plcHide" "1" ()
Expand Down Expand Up @@ -269,7 +269,7 @@ showExp props e =
, mnode "e" $ showExp props x]]
EBoxed x -> [mnode "borderBox" [ mnode "e" $ showExp props x]]
EScaled _ x -> showExp props x -- no support for scaler?
EArray as ls -> [makeArray props as ls]
EArray as ls cs -> [makeArray props as ls cs]
EText a s -> [makeText a s]
EStyled a es -> concatMap (showExp (setProps a)) es

Expand Down
2 changes: 1 addition & 1 deletion src/Text/TeXMath/Writers/Pandoc.hs
Original file line number Diff line number Diff line change
Expand Up @@ -155,4 +155,4 @@ expToInlines tt (EOver convertible b e)
expToInlines _ (EUnderover _ _ _ _) = Nothing
expToInlines _ (EPhantom _) = Nothing
expToInlines _ (EBoxed _) = Nothing
expToInlines _ (EArray _ _) = Nothing
expToInlines _ (EArray _ _ _) = Nothing
41 changes: 22 additions & 19 deletions src/Text/TeXMath/Writers/TeX.hs
Original file line number Diff line number Diff line change
Expand Up @@ -115,25 +115,25 @@ writeExp (EDelimited "\x27E8" "\x27E9" [Right (EFraction NoLineFrac x y)]) = do
writeBinom "\\bangle" x y
writeExp (EDelimited open close [Right (EFraction NoLineFrac x y)]) = do
writeExp (EDelimited open close [Right (EArray [AlignCenter]
[[[x]],[[y]]])])
writeExp (EDelimited open close [Right (EArray aligns rows)]) = do
[[[x]],[[y]]] [])])
writeExp (EDelimited open close [Right (EArray aligns rows colseps)]) = do
env <- asks mathEnv
case ("amsmath" `elem` env, open, close) of
(True, "{", "") | aligns == [AlignLeft, AlignLeft] ->
table "cases" [] rows
table "cases" [] rows colseps
(True, "(", ")") | all (== AlignCenter) aligns ->
table "pmatrix" [] rows
table "pmatrix" [] rows colseps
(True, "[", "]") | all (== AlignCenter) aligns ->
table "bmatrix" [] rows
table "bmatrix" [] rows colseps
(True, "{", "}") | all (== AlignCenter) aligns ->
table "Bmatrix" [] rows
table "Bmatrix" [] rows colseps
(True, "\x2223", "\x2223") | all (== AlignCenter) aligns ->
table "vmatrix" [] rows
table "vmatrix" [] rows colseps
(True, "\x2225", "\x2225") | all (== AlignCenter) aligns ->
table "Vmatrix" [] rows
table "Vmatrix" [] rows colseps
_ -> do
writeDelim DLeft open
writeExp (EArray aligns rows)
writeExp (EArray aligns rows colseps)
writeDelim DRight close
writeExp (EDelimited open close es) = do
writeDelim DLeft open
Expand Down Expand Up @@ -247,30 +247,33 @@ writeExp (EStyled ttype es) = do
txtcmd <- (flip S.getLaTeXTextCommand ttype) <$> asks mathEnv
tell [ControlSeq txtcmd]
tellGroup (mapM_ writeExp $ everywhere (mkT (fromUnicode ttype)) es)
writeExp (EArray [AlignRight, AlignLeft] rows) = do
writeExp (EArray [AlignRight, AlignLeft] rows colseps) = do
env <- asks mathEnv
if "amsmath" `elem` env
then table "aligned" [] rows
else table "array" [AlignRight, AlignLeft] rows
writeExp (EArray aligns rows) = do
then table "aligned" [] rows colseps
else table "array" [AlignRight, AlignLeft] rows colseps
writeExp (EArray aligns rows colseps) = do
env <- asks mathEnv
if "amsmath" `elem` env && all (== AlignCenter) aligns
then table "matrix" [] rows
else table "array" aligns rows
then table "matrix" [] rows colseps
else table "array" aligns rows colseps

table :: T.Text -> [Alignment] -> [ArrayLine] -> Math ()
table name aligns rows = do
table :: T.Text -> [Alignment] -> [ArrayLine] -> [ColumnSeparator] -> Math ()
table name aligns rows colseps = do
tell [ControlSeq "\\begin", Grouped [Literal name]]
unless (null aligns) $
tell [Grouped [Literal columnAligns]]
tell [Token '\n']
mapM_ row rows
tell [ControlSeq "\\end", Grouped [Literal name]]
where
columnAligns = T.pack $ map alignmentToLetter aligns
columnAligns = T.pack . concat $ zipWith (\a c -> alignmentToLetter a:colsepToString c) aligns (colseps ++ [CSNone])
alignmentToLetter AlignLeft = 'l'
alignmentToLetter AlignCenter = 'c'
alignmentToLetter AlignRight = 'r'
colsepToString CSNone = ""
colsepToString CSDashed = "|" -- Lossy, CSDashed can only come from MathML input
colsepToString CSSolid = "|"

row :: ArrayLine -> Math ()
row [] = tell [Space, Literal "\\\\", Token '\n']
Expand Down Expand Up @@ -330,7 +333,7 @@ writeScript pos convertible b e1 = do

-- Replace an array with a substack if appropriate.
checkSubstack :: Exp -> Math ()
checkSubstack e@(EArray [AlignCenter] rows) = do
checkSubstack e@(EArray [AlignCenter] rows _) = do
env <- asks mathEnv
if "amsmath" `elem` env
then do
Expand Down

0 comments on commit e808517

Please sign in to comment.