git.iko.soy/typeable/pandoc
on master help sign in
LaTeX reader: More siunitx improvements. Closes #6658.

There's still one slight divergence from the siunitx behavior: we get 'kg m/A/s' instead of 'kg m/(A s)'. At the moment I'm not going to worry about that.

commitbb11f5fb86993559f9999d4795355b02ae78cc3d authorJohn MacFarlane <jgm@berkeley.edu> dateThu 20 May 2021, 12:06 (5 years ago) parent4e990a8
3 files changed+161−52
src/Text/Pandoc/Readers/LaTeX/Parsing.hs+2−1 src/Text/Pandoc/Readers/LaTeX/SIunitx.hs+93−45 test/command/6658.md+66−6
src/Text/Pandoc/Readers/LaTeX/Parsing.hs+2−1
@@ -806,7 +806,8 @@ withRaw parser = do
806806
807807keyval :: PandocMonad m => LP m (Text, Text)
808808keyval = try $ do
809− Tok _ Word key <- satisfyTok isWordTok
809+ key <- untokenize <$> many1 (notFollowedBy (symbol '=') >>
810+ (symbol '-' <|> symbol '_' <|> satisfyTok isWordTok))
810811 sp
811812 val <- option mempty $ do
812813 symbol '='
src/Text/Pandoc/Readers/LaTeX/SIunitx.hs+93−45
@@ -10,27 +10,32 @@ import Text.Pandoc.Class
1010import Text.Pandoc.Parsing hiding (blankline, mathDisplay, mathInline,
1111 optional, space, spaces, withRaw, (<|>))
1212import Control.Applicative ((<|>))
13+import Control.Monad (void)
1314import qualified Data.Map as M
1415import Data.Char (isDigit)
1516import Data.Text (Text)
1617import qualified Data.Text as T
1718import Data.List (intersperse)
18−
19+import qualified Data.Sequence as Seq
20+import Text.Pandoc.Walk (walk)
1921
2022siunitxCommands :: PandocMonad m
2123 => LP m Inlines -> M.Map Text (LP m Inlines)
2224siunitxCommands tok = M.fromList
23− [ ("si", skipopts *> dosi tok)
25+ [ ("si", dosi tok)
2426 , ("SI", doSI tok)
2527 , ("SIrange", doSIrange True tok)
2628 , ("numrange", doSIrange False tok)
2729 , ("numlist", doSInumlist)
30+ , ("SIlist", doSIlist tok)
2831 , ("num", doSInum)
2932 , ("ang", doSIang)
3033 ]
3134
3235dosi :: PandocMonad m => LP m Inlines -> LP m Inlines
33−dosi tok = grouped (siUnit tok) <|> siUnit tok
36+dosi tok = do
37+ options <- option [] keyvals
38+ grouped (siUnit options tok) <|> siUnit options tok
3439
3540-- converts e.g. \SI{1}[\$]{} to "$ 1" or \SI{1}{\euro} to "1 €"
3641doSI :: PandocMonad m => LP m Inlines -> LP m Inlines
@@ -65,9 +70,29 @@ doSInumlist = do
6570 mconcat (intersperse (str "," <> space) (init xs)) <>
6671 text ", & " <> last xs
6772
73+doSIlist :: PandocMonad m => LP m Inlines -> LP m Inlines
74+doSIlist tok = do
75+ options <- option [] keyvals
76+ nums <- map tonum . T.splitOn ";" . untokenize <$> braced
77+ unit <- grouped (siUnit options tok) <|> siUnit options tok
78+ let xs = map (<> (str "\xa0" <> unit)) nums
79+ case xs of
80+ [] -> return mempty
81+ [x] -> return x
82+ _ -> return $
83+ mconcat (intersperse (str "," <> space) (init xs)) <>
84+ text ", & " <> last xs
85+
6886parseNum :: Parser Text () Inlines
6987parseNum = (mconcat <$> many parseNumPart) <* eof
7088
89+minus :: Text
90+minus = "\x2212"
91+
92+hyphenToMinus :: Inline -> Inline
93+hyphenToMinus (Str t) = Str (T.replace "-" minus t)
94+hyphenToMinus x = x
95+
7196parseNumPart :: Parser Text () Inlines
7297parseNumPart =
7398 parseDecimalNum <|>
@@ -83,7 +108,7 @@ parseNumPart =
83108 parseComma, parseI, parseX,
84109 parseExp, parseSpace :: Parser Text () Inlines
85110 parseDecimalNum = try $ do
86− pref <- option mempty $ (mempty <$ char '+') <|> ("\x2212" <$ char '-')
111+ pref <- option mempty $ (mempty <$ char '+') <|> (minus <$ char '-')
87112 basenum' <- many1 (satisfy (\c -> isDigit c || c == '.'))
88113 let basenum = pref <> T.pack
89114 (case basenum' of
@@ -155,20 +180,30 @@ doSIrange includeUnits tok = do
155180emptyOr160 :: Inlines -> Inlines
156181emptyOr160 x = if x == mempty then x else str "\160"
157182
158−siUnit :: forall m. PandocMonad m => LP m Inlines -> LP m Inlines
159−siUnit tok = mconcat <$> many1 siUnitPart
183+siUnit :: forall m. PandocMonad m => [(Text,Text)] -> LP m Inlines -> LP m Inlines
184+siUnit options tok = mconcat . intersperse (str "\xa0") <$> many1 siUnitPart
160185 where
161186 siUnitPart :: LP m Inlines
162− siUnitPart =
163− (siPrefix <*> siUnitPart)
164− <|> (do u <- siBase <|> tok
165− option u $ siSuffix <*> pure u)
187+ siUnitPart = try $ do
188+ skipMany (void (symbol '.') <|> void (symbol '~') <|> spaces1)
189+ x <- ((siPrefix <*> siBase)
190+ <|> (do u <- siBase <|> tok
191+ option u $ siSuffix <*> pure u))
192+ option x (siInfix x)
193+ siInfix :: Inlines -> LP m Inlines
194+ siInfix u1 = try $
195+ (do _ <- controlSeq "per"
196+ u2 <- siUnitPart
197+ let useSlash = lookup "per-mode" options == Just "symbol"
198+ if useSlash
199+ then return (u1 <> str "/" <> u2)
200+ else return (u1 <> str "\xa0" <> negateExponent u2))
201+ <|> (do _ <- symbol '/'
202+ u2 <- siUnitPart
203+ return (u1 <> str "/" <> u2))
166204 siPrefix :: LP m (Inlines -> Inlines)
167205 siPrefix =
168− (do _ <- controlSeq "per"
169− skipopts -- TODO handle option
170− return (str "/" <>))
171− <|> (do _ <- controlSeq "square"
206+ (do _ <- controlSeq "square"
172207 skipopts
173208 return (<> superscript "2"))
174209 <|> (do _ <- controlSeq "cubic"
@@ -176,7 +211,7 @@ siUnit tok = mconcat <$> many1 siUnitPart
176211 return (<> superscript "3"))
177212 <|> (do _ <- controlSeq "raisetothe"
178213 skipopts
179− n <- tok
214+ n <- walk hyphenToMinus <$> tok
180215 return (<> superscript n))
181216 siSuffix :: LP m (Inlines -> Inlines)
182217 siSuffix =
@@ -188,23 +223,57 @@ siUnit tok = mconcat <$> many1 siUnitPart
188223 return (<> superscript "3"))
189224 <|> (do _ <- controlSeq "tothe"
190225 skipopts
191− n <- tok
226+ n <- walk hyphenToMinus <$> tok
192227 return (<> superscript n))
228+ <|> (symbol '^' *> (do n <- walk hyphenToMinus <$> tok
229+ return (<> superscript n)))
230+ <|> (symbol '_' *> (do n <- walk hyphenToMinus <$> tok
231+ return (<> subscript n)))
232+ negateExponent :: Inlines -> Inlines
233+ negateExponent ils =
234+ case Seq.viewr (unMany ils) of
235+ xs Seq.:> Superscript ss -> (Many xs) <>
236+ superscript (str minus <> fromList ss)
237+ _ -> ils <> superscript (str (minus <> "1"))
193238 siBase :: LP m Inlines
194− siBase = mconcat <$> many1
239+ siBase =
195240 ((try
196241 (do Tok _ (CtrlSeq name) _ <- anyControlSeq
197− case M.lookup name siUnitMap of
198− Just il -> pure il
199− Nothing -> fail "not a unit command"))
242+ case M.lookup name siUnitModifierMap of
243+ Just il -> (il <>) <$> siBase
244+ Nothing ->
245+ case M.lookup name siUnitMap of
246+ Just il -> pure il
247+ Nothing -> fail "not a unit command"))
200248 <|> (do Tok _ Word t <- satisfyTok isWordTok
201249 return $ str t)
202− <|> (symbol '^' *> (superscript <$> tok))
203− <|> (symbol '_' *> (subscript <$> tok))
204− <|> (str "\xa0" <$ symbol '.')
205− <|> (str "\xa0" <$ symbol '~')
206250 )
207251
252+siUnitModifierMap :: M.Map Text Inlines
253+siUnitModifierMap = M.fromList
254+ [ ("atto", str "a")
255+ , ("centi", str "c")
256+ , ("deca", str "d")
257+ , ("deci", str "d")
258+ , ("deka", str "d")
259+ , ("exa", str "E")
260+ , ("femto", str "f")
261+ , ("giga", str "G")
262+ , ("hecto", str "h")
263+ , ("kilo", str "k")
264+ , ("mega", str "M")
265+ , ("micro", str "μ")
266+ , ("milli", str "m")
267+ , ("nano", str "n")
268+ , ("peta", str "P")
269+ , ("pico", str "p")
270+ , ("tera", str "T")
271+ , ("yocto", str "y")
272+ , ("yotta", str "Y")
273+ , ("zepto", str "z")
274+ , ("zetta", str "Z")
275+ ]
276+
208277siUnitMap :: M.Map Text Inlines
209278siUnitMap = M.fromList
210279 [ ("fg", str "fg")
@@ -303,7 +372,6 @@ siUnitMap = M.fromList
303372 , ("arcsecond", str "″")
304373 , ("astronomicalunit", str "ua")
305374 , ("atomicmassunit", str "u")
306− , ("atto", str "a")
307375 , ("bar", str "bar")
308376 , ("barn", str "b")
309377 , ("becquerel", str "Bq")
@@ -311,51 +379,38 @@ siUnitMap = M.fromList
311379 , ("bohr", emph (str "a") <> subscript (str "0"))
312380 , ("candela", str "cd")
313381 , ("celsius", str "°C")
314− , ("centi", str "c")
315382 , ("clight", emph (str "c") <> subscript (str "0"))
316383 , ("coulomb", str "C")
317384 , ("dalton", str "Da")
318385 , ("day", str "d")
319− , ("deca", str "d")
320− , ("deci", str "d")
321386 , ("decibel", str "db")
322387 , ("degreeCelsius",str "°C")
323388 , ("degree", str "°")
324− , ("deka", str "d")
325389 , ("electronmass", emph (str "m") <> subscript (str "e"))
326390 , ("electronvolt", str "eV")
327391 , ("elementarycharge", emph (str "e"))
328− , ("exa", str "E")
329392 , ("farad", str "F")
330− , ("femto", str "f")
331− , ("giga", str "G")
332393 , ("gram", str "g")
333394 , ("gray", str "Gy")
334395 , ("hartree", emph (str "E") <> subscript (str "h"))
335396 , ("hectare", str "ha")
336− , ("hecto", str "h")
337397 , ("henry", str "H")
338398 , ("hertz", str "Hz")
339399 , ("hour", str "h")
340400 , ("joule", str "J")
341401 , ("katal", str "kat")
342402 , ("kelvin", str "K")
343− , ("kilo", str "k")
344403 , ("kilogram", str "kg")
345404 , ("knot", str "kn")
346405 , ("liter", str "L")
347406 , ("litre", str "l")
348407 , ("lumen", str "lm")
349408 , ("lux", str "lx")
350− , ("mega", str "M")
351409 , ("meter", str "m")
352410 , ("metre", str "m")
353− , ("micro", str "μ")
354− , ("milli", str "m")
355411 , ("minute", str "min")
356412 , ("mmHg", str "mmHg")
357413 , ("mole", str "mol")
358− , ("nano", str "n")
359414 , ("nauticalmile", str "M")
360415 , ("neper", str "Np")
361416 , ("newton", str "N")
@@ -363,24 +418,17 @@ siUnitMap = M.fromList
363418 , ("Pa", str "Pa")
364419 , ("pascal", str "Pa")
365420 , ("percent", str "%")
366− , ("peta", str "P")
367− , ("pico", str "p")
368421 , ("planckbar", emph (str "\x210f"))
369422 , ("radian", str "rad")
370423 , ("second", str "s")
371424 , ("siemens", str "S")
372425 , ("sievert", str "Sv")
373426 , ("steradian", str "sr")
374− , ("tera", str "T")
375427 , ("tesla", str "T")
376428 , ("tonne", str "t")
377429 , ("volt", str "V")
378430 , ("watt", str "W")
379431 , ("weber", str "Wb")
380− , ("yocto", str "y")
381− , ("yotta", str "Y")
382− , ("zepto", str "z")
383− , ("zetta", str "Z")
384432 ]
385433
386434
test/command/6658.md+66−6
@@ -1,15 +1,75 @@
11```
2−pandoc -f latex
3−\SI{10.0 +- 3.3}{\ms}
2+pandoc -f latex -t html
3+\num{12345,67890}
4+
5+\num{1+-2i}
46
57\num{.3e45}
68
7−\ang{+10;+3;}
9+\num{1.654 x 2.34 x 3.430}
10+
11+\si{kg.m.s^{-1}}
12+
13+\si{\kilogram\metre\per\second}
14+
15+\si[per-mode=symbol]{\kilogram\metre\per\second}
16+
17+\si[per-mode=symbol]{\kilogram\metre\per\ampere\per\second}
18+
19+\numlist{10;20;30}
20+
21+\SIlist{0.13;0.67;0.80}{\milli\metre}
22+
23+\numrange{10}{20}
24+
25+\SIrange{0.13}{0.67}{\milli\metre}
26+
27+\ang{10}
28+
29+\ang{1;2;3}
30+
31+\ang{;;1}
32+
33+\ang{+10;;}
34+
35+\ang{-0;1;}
36+
37+\si{kg.m/s^2}
38+
39+\si{g_{polymer}~mol_{cat}.s^{-1}}
40+
41+\si{\kilo\gram\metre\per\square\second}
842
943\si{\gram\per\cubic\centi\metre}
44+
45+\si{\square\volt\cubic\lumen\per\farad}
46+
47+\si{\metre\squared\per\gray\cubic\lux}
48+
49+\si{\henry\second}
1050^D
11−<p>10.0 ± 3.3 ms</p>
51+<p>12345.67890</p>
52+<p>1 ± 2i</p>
1253<p>0.3 × 10<sup>45</sup></p>
13−<p>10°3′</p>
14−<p>g/cm<sup>3</sup></p>
54+<p>1.654 × 2.34 × 3.430</p>
55+<p>kg m s<sup>−1</sup></p>
56+<p>kg m s<sup>−1</sup></p>
57+<p>kg m/s</p>
58+<p>kg m/A/s</p>
59+<p>10, 20, &amp; 30</p>
60+<p>0.13 mm, 0.67 mm, &amp; 0.80 mm</p>
61+<p>10–20</p>
62+<p>0.13 mm–0.67 mm</p>
63+<p>10°</p>
64+<p>1°2′3″</p>
65+<p>1″</p>
66+<p>10°</p>
67+<p>-0°1′</p>
68+<p>kg m/s<sup>2</sup></p>
69+<p>g<sub>polymer</sub> mol<sub>cat</sub> s<sup>−1</sup></p>
70+<p>kg m s<sup>−2</sup></p>
71+<p>g cm<sup>−3</sup></p>
72+<p>V<sup>2</sup> lm<sup>3</sup> F<sup>−1</sup></p>
73+<p>m<sup>2</sup> Gy<sup>−1</sup> lx<sup>3</sup></p>
74+<p>H s</p>
1575```