citeproc 0.11 → 0.12
raw patch · 12 files changed
+656/−446 lines, 12 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
- Citeproc.Locale: lookupQuotes :: Locale -> ((Text, Text), (Text, Text))
+ Citeproc.Types: StandardVariable :: VariableType
+ Citeproc.Types: lookupQuotes :: Locale -> ((Text, Text), (Text, Text))
+ Citeproc.Types: parseCslJson :: CiteprocOutput a => Locale -> Text -> a
+ Citeproc.Types: superscriptChars :: Map Char Text
- Citeproc.CslJson: parseCslJson :: Locale -> Text -> CslJson Text
+ Citeproc.CslJson: parseCslJson :: CiteprocOutput a => Locale -> Text -> a
- Citeproc.Types: addFormatting :: CiteprocOutput a => Formatting -> a -> a
+ Citeproc.Types: addFormatting :: CiteprocOutput a => Locale -> Formatting -> a -> a
- Citeproc.Types: renderOutput :: CiteprocOutput a => CiteprocOptions -> Output a -> a
+ Citeproc.Types: renderOutput :: CiteprocOutput a => CiteprocOptions -> Locale -> Output a -> a
Files
- CHANGELOG.md +53/−0
- citeproc.cabal +1/−1
- src/Citeproc.hs +2/−2
- src/Citeproc/CslJson.hs +11/−323
- src/Citeproc/Eval.hs +39/−30
- src/Citeproc/Locale.hs +1/−18
- src/Citeproc/Pandoc.hs +0/−1
- src/Citeproc/Types.hs +295/−64
- test/Spec.hs +11/−7
- test/extra/issue_173.txt +66/−0
- test/extra/issue_174.txt +98/−0
- test/extra/issue_175.txt +79/−0
CHANGELOG.md view
@@ -1,5 +1,58 @@ # citeproc changelog +## 0.12++ * Fix suppression of substituted variables for number,+ short- variants (#174).++ * Fix is-numeric detection for non-locators with en-dash (#175).++ * Fix handling of CSL JSON formatting (i.e. handling of+ tags like `<i>` and also quotes, which are "smartified") (#170,+ #173).++ We were previously handling this in the wrong way, by parsing+ it in the `fromText` method for `CslJson Text`. This meant that+ there was no way to prevent quotes from turning into smart+ quotes, even in things like URLs (see #173).++ What is more, we were not handling this sort of formatting at+ all in other contexts, e.g. in terms, `text` elements with a+ `value`, locators, prefixes, and suffixes. The CSL tests were+ passing only because the raw HTML was being passed through+ verbatim, but in other formats this led to undesirable results+ (see #170).++ Formatting is now parsed in prefix, suffix, locator, text+ value, and terms. And `fromText` is now just `CslText`, so that+ purely textual values like URLs can be inserted.++ We also handle the conversion of Unicode superscript characters+ to explicit `<sup>.</sup>` in CSL JSON somewhat differently.+ Instead of making the replacement in *parsing* CSL JSON,+ which would now affect other formats too, because terms in+ locales are parsed as CSL JSON, we make the replacement in+ *rendering* CSL JSON. This gives us behavior consistent with+ the previous behavior, with the substitution occuring in+ CSL JSON output but not in Pandoc.++ [API changes]++ + Citeproc.Types now exports `parseCslJson`, `lookupQuotes`,+ `superscriptChars`.+ + Citeproc.Locale no longer exports `lookupQuotes`.+ + `parseCslJson` now takes an additional parameter for a Locale.++ * Add StandardVariable constructor for VariableType [API change]+ (#173). Also change the interpretation of StringVariable,+ so that StringVariables are not parsed for quotes and HTML-like+ formatting. Make URL, ISBN, etc. StringVariables.++ * Test suite improvements:++ + Indicate FAILED:EXPECTED or :UNEXPECTED in output.+ + Fix formatting of columns in test summary.+ ## 0.11 * Expand macros in evaluation rather than style parsing (#172).
citeproc.cabal view
@@ -1,6 +1,6 @@ cabal-version: 2.2 name: citeproc-version: 0.11+version: 0.12 synopsis: Generates citations and bibliography from CSL styles. description: citeproc parses CSL style files and uses them to generate a list of formatted citations and bibliography
src/Citeproc.hs view
@@ -37,13 +37,13 @@ rCitations = map ( trimR . localizeQuotes locale . movePunct- . renderOutput opts+ . renderOutput opts locale ) citationOs rBibliography = map (second ( trimR . localizeQuotes locale . movePunct- . renderOutput opts{ linkCitations = False } ))+ . renderOutput opts{ linkCitations = False } locale )) bibliographyOs locale = mergeLocales mblang style trimR = dropTextWhileEnd (== ' ')
src/Citeproc/CslJson.hs view
@@ -30,19 +30,17 @@ import Citeproc.Types-import Citeproc.Locale (lookupQuotes) import Citeproc.CaseTransform import Data.Ord ()-import Data.Char (isAlphaNum, isSpace, isAscii, isPunctuation)+import qualified Data.Map as M+import Data.Char (isAlphaNum, isSpace, isPunctuation) import Data.Text (Text) import qualified Data.Text as T import Data.Foldable (fold) import Data.Functor.Identity-import Data.Attoparsec.Text as P import Data.Aeson (FromJSON(..), ToJSON(..), Value(..), object) import Control.Monad.Trans.State-import Control.Monad (guard, when)-import Control.Applicative ((<|>))+import Control.Monad (when) import Data.Generics.Uniplate.Direct data CslJson a =@@ -103,7 +101,9 @@ instance CiteprocOutput (CslJson Text) where toText = fold- fromText = parseCslJson mempty+ fromText = \t -> if T.null t+ then CslEmpty+ else CslText t dropTextWhile = dropTextWhile' dropTextWhileEnd = dropTextWhileEnd' addFontVariant x =@@ -183,106 +183,6 @@ CslDiv t x -> CslDiv t (dropTextWhileEnd' f x) CslLink t x -> CslLink t (dropTextWhileEnd' f x) -parseCslJson :: Locale -> Text -> CslJson Text-parseCslJson locale t =- case P.parseOnly- (P.many' (pCslJson locale) <* P.endOfInput) t of- Left _ -> CslText t- Right xs -> mconcat xs--pCslJson :: Locale -> P.Parser (CslJson Text)-pCslJson locale = P.choice- [ pCslText- , pCslQuoted- , pCslItalic- , pCslBold- , pCslUnderline- , pCslNoDecoration- , pCslSmallCaps- , pCslSup- , pCslSub- , pCslBaseline- , pCslNoCase- , pCslSymbol- ]- where- ((outerOpenQuote, outerCloseQuote), (innerOpenQuote, innerCloseQuote)) =- lookupQuotes locale- isSpecialChar c = c == '<' || c == '>' || c == '\'' || c == '"' ||- c == '’' || (not (isAscii c) && (isSuperscriptChar c || isQuoteChar c))- isQuoteChar = P.inClass- (T.unpack (outerOpenQuote <> outerCloseQuote <>- innerOpenQuote <> innerCloseQuote))- isSuperscriptChar = P.inClass superscriptChars- isApostrophe '\'' = True- isApostrophe '’' = True- isApostrophe _ = False- pCsl = pCslJson locale- notFollowedBySpace =- P.peekChar' >>= guard . not . isSpaceChar- isSpaceChar = P.inClass [' ','\t','\n','\r']- pOpenQuote = (("\"" <$ P.char '"')- <|> ("'" <$ P.char '\'')- <|> (outerCloseQuote <$ P.string outerOpenQuote)- <|> (innerCloseQuote <$ P.string innerOpenQuote))- <* notFollowedBySpace- pSpace = P.skipWhile isSpaceChar- pCslText = CslText . addNarrowSpace <$>- ( do t <- P.takeWhile1 (\c -> isAlphaNum c && not (isSpecialChar c))- -- apostrophe- P.option t $ do _ <- P.satisfy isApostrophe- t' <- P.takeWhile1 isAlphaNum- return (t <> "’" <> t')- <|>- (P.takeWhile1 (\c -> not (isAlphaNum c || isSpecialChar c))) )- pCslQuoted = CslQuoted <$>- do cl <- pOpenQuote- mbc <- peekChar- case mbc of- Just c | T.singleton c == cl -> fail "unexpected close quote"- _ -> return ()- mconcat <$> P.manyTill' pCsl (P.string cl)- pCslSymbol = do- c <- P.satisfy isSpecialChar- return $- if isApostrophe c- then CslText "’"- else charToSup c- pCslItalic = CslItalic . mconcat <$>- (P.string "<i>" *> P.manyTill' pCsl (P.string "</i>"))- pCslBold = CslBold . mconcat <$>- (P.string "<b>" *> P.manyTill' pCsl (P.string "</b>"))- pCslUnderline = CslUnderline . mconcat <$>- (P.string "<u>" *> P.manyTill' pCsl (P.string "</u>"))- pCslNoDecoration = CslNoDecoration . mconcat <$>- (P.string "<span" *> pSpace *>- P.string "class=\"nodecor\"" *> pSpace *> P.char '>' *>- P.manyTill' pCsl (P.string "</span>"))- pCslSup = CslSup . mconcat <$>- (P.string "<sup>" *> P.manyTill' pCsl (P.string "</sup>"))- pCslSub = CslSub . mconcat <$>- (P.string "<sub>" *> P.manyTill' pCsl (P.string "</sub>"))- pCslBaseline = CslBaseline . mconcat <$>- (P.string "<span" *> pSpace *> P.string "style=\"baseline\">" *>- P.manyTill' pCsl (P.string "</span>"))- pCslSmallCaps = CslSmallCaps . mconcat <$>- ((P.string "<span" *> pSpace *>- P.string "style=\"font-variant:" *> pSpace *>- P.string "small-caps;" *> pSpace *> P.char '"' *>- pSpace *> P.char '>' *> P.manyTill' pCsl (P.string "</span>"))- <|>- (P.string "<sc>" *> P.manyTill' pCsl (P.string "</sc>")))- pCslNoCase = CslNoCase . mconcat <$>- (P.string "<span" *> pSpace *>- P.string "class=\"nocase\"" *> pSpace *> P.char '>' *>- P.manyTill' pCsl (P.string "</span>"))- addNarrowSpace =- T.replace " ;" "\x202F;" .- T.replace " ?" "\x202F?" .- T.replace " !" "\x202F!" .- T.replace " »" "\x202F»" .- T.replace "« " "«\x202F"- data RenderContext = RenderContext { useOuterQuotes :: Bool@@ -305,7 +205,11 @@ go :: RenderContext -> CslJson Text -> Text go ctx el = case el of- CslText t -> escape t+ CslText t+ | Just (c, "") <- T.uncons t+ , Just t' <- M.lookup c superscriptChars+ -> "<sup>" <> t' <> "</sup>"+ | otherwise -> escape t CslEmpty -> mempty CslConcat x y -> go ctx x <> go ctx y CslQuoted x@@ -611,219 +515,3 @@ CslText t -> CslText t CslEmpty -> CslEmpty -superscriptChars :: [Char]-superscriptChars =- [ '\x00AA'- , '\x00B2'- , '\x00B3'- , '\x00B9'- , '\x00BA'- , '\x02B0'- , '\x02B1'- , '\x02B2'- , '\x02B3'- , '\x02B4'- , '\x02B5'- , '\x02B6'- , '\x02B7'- , '\x02B8'- , '\x02E0'- , '\x02E1'- , '\x02E2'- , '\x02E3'- , '\x02E4'- , '\x1D2C'- , '\x1D2D'- , '\x1D2E'- , '\x1D30'- , '\x1D31'- , '\x1D32'- , '\x1D33'- , '\x1D34'- , '\x1D35'- , '\x1D36'- , '\x1D37'- , '\x1D38'- , '\x1D39'- , '\x1D3A'- , '\x1D3C'- , '\x1D3D'- , '\x1D3E'- , '\x1D3F'- , '\x1D40'- , '\x1D41'- , '\x1D42'- , '\x1D43'- , '\x1D44'- , '\x1D45'- , '\x1D46'- , '\x1D47'- , '\x1D48'- , '\x1D49'- , '\x1D4A'- , '\x1D4B'- , '\x1D4C'- , '\x1D4D'- , '\x1D4F'- , '\x1D50'- , '\x1D51'- , '\x1D52'- , '\x1D53'- , '\x1D54'- , '\x1D55'- , '\x1D56'- , '\x1D57'- , '\x1D58'- , '\x1D59'- , '\x1D5A'- , '\x1D5B'- , '\x1D5C'- , '\x1D5D'- , '\x1D5E'- , '\x1D5F'- , '\x1D60'- , '\x1D61'- , '\x2070'- , '\x2071'- , '\x2074'- , '\x2075'- , '\x2076'- , '\x2077'- , '\x2078'- , '\x2079'- , '\x207A'- , '\x207B'- , '\x207C'- , '\x207D'- , '\x207E'- , '\x207F'- , '\x2120'- , '\x2122'- , '\x3192'- , '\x3193'- , '\x3194'- , '\x3195'- , '\x3196'- , '\x3197'- , '\x3198'- , '\x3199'- , '\x319A'- , '\x319B'- , '\x319C'- , '\x319D'- , '\x319E'- , '\x319F'- , '\x02C0'- , '\x02C1'- , '\x06E5'- , '\x06E6'- ]--charToSup :: Char -> CslJson Text-charToSup c =- case c of- '\x00AA' -> CslSup (CslText "\x0061")- '\x00B2' -> CslSup (CslText "\x0032")- '\x00B3' -> CslSup (CslText "\x0033")- '\x00B9' -> CslSup (CslText "\x0031")- '\x00BA' -> CslSup (CslText "\x006F")- '\x02B0' -> CslSup (CslText "\x0068")- '\x02B1' -> CslSup (CslText "\x0266")- '\x02B2' -> CslSup (CslText "\x006A")- '\x02B3' -> CslSup (CslText "\x0072")- '\x02B4' -> CslSup (CslText "\x0279")- '\x02B5' -> CslSup (CslText "\x027B")- '\x02B6' -> CslSup (CslText "\x0281")- '\x02B7' -> CslSup (CslText "\x0077")- '\x02B8' -> CslSup (CslText "\x0079")- '\x02E0' -> CslSup (CslText "\x0263")- '\x02E1' -> CslSup (CslText "\x006C")- '\x02E2' -> CslSup (CslText "\x0073")- '\x02E3' -> CslSup (CslText "\x0078")- '\x02E4' -> CslSup (CslText "\x0295")- '\x1D2C' -> CslSup (CslText "\x0041")- '\x1D2D' -> CslSup (CslText "\x00C6")- '\x1D2E' -> CslSup (CslText "\x0042")- '\x1D30' -> CslSup (CslText "\x0044")- '\x1D31' -> CslSup (CslText "\x0045")- '\x1D32' -> CslSup (CslText "\x018E")- '\x1D33' -> CslSup (CslText "\x0047")- '\x1D34' -> CslSup (CslText "\x0048")- '\x1D35' -> CslSup (CslText "\x0049")- '\x1D36' -> CslSup (CslText "\x004A")- '\x1D37' -> CslSup (CslText "\x004B")- '\x1D38' -> CslSup (CslText "\x004C")- '\x1D39' -> CslSup (CslText "\x004D")- '\x1D3A' -> CslSup (CslText "\x004E")- '\x1D3C' -> CslSup (CslText "\x004F")- '\x1D3D' -> CslSup (CslText "\x0222")- '\x1D3E' -> CslSup (CslText "\x0050")- '\x1D3F' -> CslSup (CslText "\x0052")- '\x1D40' -> CslSup (CslText "\x0054")- '\x1D41' -> CslSup (CslText "\x0055")- '\x1D42' -> CslSup (CslText "\x0057")- '\x1D43' -> CslSup (CslText "\x0061")- '\x1D44' -> CslSup (CslText "\x0250")- '\x1D45' -> CslSup (CslText "\x0251")- '\x1D46' -> CslSup (CslText "\x1D02")- '\x1D47' -> CslSup (CslText "\x0062")- '\x1D48' -> CslSup (CslText "\x0064")- '\x1D49' -> CslSup (CslText "\x0065")- '\x1D4A' -> CslSup (CslText "\x0259")- '\x1D4B' -> CslSup (CslText "\x025B")- '\x1D4C' -> CslSup (CslText "\x025C")- '\x1D4D' -> CslSup (CslText "\x0067")- '\x1D4F' -> CslSup (CslText "\x006B")- '\x1D50' -> CslSup (CslText "\x006D")- '\x1D51' -> CslSup (CslText "\x014B")- '\x1D52' -> CslSup (CslText "\x006F")- '\x1D53' -> CslSup (CslText "\x0254")- '\x1D54' -> CslSup (CslText "\x1D16")- '\x1D55' -> CslSup (CslText "\x1D17")- '\x1D56' -> CslSup (CslText "\x0070")- '\x1D57' -> CslSup (CslText "\x0074")- '\x1D58' -> CslSup (CslText "\x0075")- '\x1D59' -> CslSup (CslText "\x1D1D")- '\x1D5A' -> CslSup (CslText "\x026F")- '\x1D5B' -> CslSup (CslText "\x0076")- '\x1D5C' -> CslSup (CslText "\x1D25")- '\x1D5D' -> CslSup (CslText "\x03B2")- '\x1D5E' -> CslSup (CslText "\x03B3")- '\x1D5F' -> CslSup (CslText "\x03B4")- '\x1D60' -> CslSup (CslText "\x03C6")- '\x1D61' -> CslSup (CslText "\x03C7")- '\x2070' -> CslSup (CslText "\x0030")- '\x2071' -> CslSup (CslText "\x0069")- '\x2074' -> CslSup (CslText "\x0034")- '\x2075' -> CslSup (CslText "\x0035")- '\x2076' -> CslSup (CslText "\x0036")- '\x2077' -> CslSup (CslText "\x0037")- '\x2078' -> CslSup (CslText "\x0038")- '\x2079' -> CslSup (CslText "\x0039")- '\x207A' -> CslSup (CslText "\x002B")- '\x207B' -> CslSup (CslText "\x2212")- '\x207C' -> CslSup (CslText "\x003D")- '\x207D' -> CslSup (CslText "\x0028")- '\x207E' -> CslSup (CslText "\x0029")- '\x207F' -> CslSup (CslText "\x006E")- '\x2120' -> CslSup (CslText "\x0053\x004D")- '\x2122' -> CslSup (CslText "\x0054\x004D")- '\x3192' -> CslSup (CslText "\x4E00")- '\x3193' -> CslSup (CslText "\x4E8C")- '\x3194' -> CslSup (CslText "\x4E09")- '\x3195' -> CslSup (CslText "\x56DB")- '\x3196' -> CslSup (CslText "\x4E0A")- '\x3197' -> CslSup (CslText "\x4E2D")- '\x3198' -> CslSup (CslText "\x4E0B")- '\x3199' -> CslSup (CslText "\x7532")- '\x319A' -> CslSup (CslText "\x4E59")- '\x319B' -> CslSup (CslText "\x4E19")- '\x319C' -> CslSup (CslText "\x4E01")- '\x319D' -> CslSup (CslText "\x5929")- '\x319E' -> CslSup (CslText "\x5730")- '\x319F' -> CslSup (CslText "\x4EBA")- '\x02C0' -> CslSup (CslText "\x0294")- '\x02C1' -> CslSup (CslText "\x0295")- '\x06E5' -> CslSup (CslText "\x0648")- '\x06E6' -> CslSup (CslText "\x064A")- _ -> CslText $ T.singleton c
src/Citeproc/Eval.hs view
@@ -1024,8 +1024,9 @@ case lookupReference citeId refmap of Nothing -> return $ SortKeyValue sortdir Nothing Just ref -> do+ locale <- asks contextLocale k <- normalizeSortKey . toText .- renderOutput defaultCiteprocOptions . grouped+ renderOutput defaultCiteprocOptions locale . grouped <$> withRWS newContext (eElements elts) return $ SortKeyValue sortdir (Just k) where@@ -1489,34 +1490,34 @@ Verb -> lookupTerm' term{ termForm = Long } Short -> lookupTerm' term{ termForm = Long } _ -> return NullOutput- f ((_,t):_) = return $ if T.null t- then NullOutput- else Literal $ fromText t+ f ((_,t):_) = if T.null t+ then return NullOutput+ else do+ locale <- asks contextLocale+ return $ Literal $ parseCslJson locale t pageRange :: CiteprocOutput a => Text -> Eval a (Output a) pageRange x = do- pageDelim <- lookupTerm'- emptyTerm{ termName = "page-range-delimiter" }+ delimTerms <- lookupTerm emptyTerm{ termName = "page-range-delimiter" }+ let pageDelim = case delimTerms of+ (_,d):_ -> d+ _ -> "" mbPageRangeFormat <- asks (stylePageRangeFormat . contextStyleOptions) let ranges = map T.strip $ T.groupBy (\c d -> not (c == ',' || c == '&' || d == ',' || d == '&')) x return $ formatted mempty{ formatDelimiter = Just " " }- $ map (formatPageRange mbPageRangeFormat- (case pageDelim of- NullOutput -> literal $ T.singleton enDash- delim -> delim)) ranges+ $ map (literal . formatPageRange mbPageRangeFormat pageDelim) ranges enDash :: Char enDash = '\x2013' -formatPageRange :: CiteprocOutput a- => Maybe PageRangeFormat- -> Output a+formatPageRange :: Maybe PageRangeFormat -> Text- -> Output a-formatPageRange _ _ "&" = literal "&"-formatPageRange _ _ "," = literal ","+ -> Text+ -> Text+formatPageRange _ _ "&" = "&"+formatPageRange _ _ "," = "," formatPageRange mbPageRangeFormat delim t = let isDash '-' = True isDash '\x2013' = True@@ -1525,9 +1526,9 @@ then [T.replace "\\-" "-" t] else map T.strip $ T.split isDash t inRange pref xs- | T.null pref = grouped (intersperse delim (map literal xs))- | otherwise = grouped- (literal pref : intersperse delim (map literal xs))+ | T.null pref = mconcat (intersperse delim xs)+ | otherwise = mconcat+ (pref : intersperse delim xs) changedDigits xs ys = length $ filter not $ zipWith (==) (xs ++ repeat ' ') ys minimal threshold pref x y =@@ -1538,8 +1539,8 @@ else inRange pref [x, resty] _ -> inRange pref [x, y] in case rangeParts of- [] -> NullOutput- [w] -> literal w+ [] -> ""+ [w] -> w [w,v] | Nothing <- mbPageRangeFormat -> inRange mempty [w,v] | Just fmt <- mbPageRangeFormat -> do@@ -1578,7 +1579,7 @@ PageRangeMinimal -> minimal 1 pref x y' PageRangeMinimalTwo -> minimal 2 pref x y' else inRange mempty [w,v]- _ -> literal t+ _ -> t eText :: CiteprocOutput a => TextType -> Eval a (Output a) eText (TextVariable varForm v) = do@@ -1611,9 +1612,10 @@ Tagged TagLocator <$> pageRange x | otherwise -> do updateVarCount 1 1+ locale <- asks contextLocale return $ Tagged TagLocator $- formatPageRange Nothing- (literal $ T.singleton enDash) x+ Literal . parseCslJson locale $+ formatPageRange Nothing (T.singleton enDash) x Nothing -> NullOutput <$ updateVarCount 1 0 "year-suffix" -> do@@ -1683,7 +1685,10 @@ Just (NumVal x) -> return $ Literal $ fromText (T.pack (show x)) _ -> return NullOutput- unless (isNothing mbv) $ deleteSubstitutedVariables [v]+ unless (isNothing mbv) $ deleteSubstitutedVariables+ (v : if varForm == ShortForm -- see #174+ then [v <> "-short"]+ else []) if v == "title" && res /= NullOutput then do modify (\st -> st { stateUsedTitle = True })@@ -1705,7 +1710,9 @@ eText (TextMacro name) = do warn ("unexpanded macro " <> name) return NullOutput-eText (TextValue t) = return $ Literal $ fromText t+eText (TextValue t) = do+ locale <- asks contextLocale+ return . Literal $ parseCslJson locale t eText (TextTerm term) = do t' <- lookupTerm' term t'' <- if termName term == "no date"@@ -2635,7 +2642,7 @@ | T.all isDigit t' -> True | otherwise -> False let isNumeric t = all isNumericChunk- (T.split (\c -> c == ',' || c == '-' || c == '&') t)+ (T.split (\c -> c == ',' || c == '-' || c == '&' || c == '\x2013') t) let testCondition cond = case cond of HasVariable "locator" -> hasLocator@@ -2684,6 +2691,7 @@ Just (FancyVal x) -> splitNums (toText x) Just (TextVal t) -> splitNums t _ -> []+ unless (isNothing mbv) $ deleteSubstitutedVariables [var] grouped <$> mapM (evalNumber nform mbGender) nparts evalNumber :: CiteprocOutput a@@ -2701,9 +2709,10 @@ res <- (if i > 99 then filter (\(t,_) -> termMatch t /= Just WholeNumber) else id) <$> lookupTerm twomatch+ locale <- asks contextLocale case res of ((_,suff):_) ->- return $ Literal $ fromText (dectext <> suff)+ return $ Literal $ fromText dectext <> parseCslJson locale suff [] -> do -- not an exact match res' <- (if i > 10 then filter (\(t,_) ->@@ -2712,12 +2721,12 @@ else id) <$> lookupTerm onematch case res' of ((_,suff):_) ->- return $ Literal $ fromText (dectext <> suff)+ return $ Literal $ fromText dectext <> parseCslJson locale suff [] -> do res'' <- lookupTerm fallback case res'' of ((_,suff):_) ->- return $ Literal $ fromText (dectext <> suff)+ return $ Literal $ fromText dectext <> parseCslJson locale suff [] -> do warn $ "no ordinal suffix found for " <> dectext return $ Literal $ fromText (T.pack (show i))
src/Citeproc/Locale.hs view
@@ -3,15 +3,13 @@ module Citeproc.Locale ( parseLocale, getLocale,- getPrimaryDialect,- lookupQuotes+ getPrimaryDialect ) where import Citeproc.Types import Citeproc.Element (runElementParser, pLocale) import Citeproc.Data (localeFiles) import qualified Data.Map as M-import Data.Maybe (fromMaybe) import Data.Text (Text) import qualified Text.XML as X import System.FilePath (takeExtension, dropExtension)@@ -107,19 +105,4 @@ (\l -> M.lookup (toCode l) locales)) of Just loc -> loc Nothing -> Left $ CiteprocLocaleNotFound $ renderLang lang--lookupTerm :: Locale -> Text -> Maybe Text-lookupTerm locale termname = do- let terms = localeTerms locale- case M.lookup termname terms of- Just ((_,t):_) -> Just t- _ -> Nothing--lookupQuotes :: Locale -> ((Text, Text), (Text, Text))-lookupQuotes locale = ((outerOpen, outerClose), (innerOpen, innerClose))- where- outerOpen = fromMaybe "\x201C" $ lookupTerm locale "open-quote"- outerClose = fromMaybe "\x201D" $ lookupTerm locale "close-quote"- innerOpen = fromMaybe "\x2018" $ lookupTerm locale "open-inner-quote"- innerClose = fromMaybe "\x2019" $ lookupTerm locale "close-inner-quote"
src/Citeproc/Pandoc.hs view
@@ -17,7 +17,6 @@ import Citeproc.CaseTransform import Control.Monad.Trans.State.Strict as S import Control.Monad (unless, when)-import Citeproc.Locale (lookupQuotes) import Data.Functor.Reverse import Data.Char (isSpace, isPunctuation, isAlphaNum)
src/Citeproc/Types.hs view
@@ -107,6 +107,9 @@ , lookupAbbreviation , Result(..) , Inputs(..)+ , parseCslJson+ , lookupQuotes+ , superscriptChars ) where import qualified Data.Set as Set@@ -116,7 +119,7 @@ import qualified Data.CaseInsensitive as CI import Control.Monad (foldM, guard, mzero) import Control.Applicative ((<|>), optional)-import Data.Char (isLower, isDigit, isLetter, isSpace)+import Data.Char (isLower, isDigit, isLetter, isSpace, isAlphaNum, isAscii) import Data.Text (Text) import GHC.Generics (Generic) import qualified Data.Text as T@@ -212,8 +215,8 @@ addHyperlink :: Text -> a -> a localizeQuotes :: Locale -> a -> a -addFormatting :: CiteprocOutput a => Formatting -> a -> a-addFormatting f x =+addFormatting :: CiteprocOutput a => Locale -> Formatting -> a -> a+addFormatting locale f x = if T.null (toText x) -- TODO inefficient then mempty else@@ -231,10 +234,10 @@ $ x where addPrefix z = case formatPrefix f of- Just s -> mconcat $ fixPunct [fromText s, z]+ Just s -> mconcat $ fixPunct [parseCslJson locale s, z] Nothing -> z addSuffix z = case formatSuffix f of- Just s -> mconcat $ fixPunct [z, fromText s]+ Just s -> mconcat $ fixPunct [z, parseCslJson locale s] Nothing -> z affixesInside = formatAffixesInside f @@ -931,6 +934,9 @@ | otherwise = Reference i t d <$> case variableType k of StringVariable -> do+ v' <- TextVal <$> readString v+ return $ M.insert k v' m+ StandardVariable -> do v' <- FancyVal <$> parseJSON v <|> TextVal <$> readString v return $ M.insert k v' m NumberVariable -> do@@ -946,7 +952,7 @@ NameVariable -> do v' <- parseJSON v return $ M.insert k (NamesVal v') m- UnknownVariable -> -- treat as string variable if possible+ UnknownVariable -> -- treat as standard variable if possible case v of String{} -> (\x -> M.insert k x m) <$> (FancyVal <$> parseJSON v <|> TextVal <$> readString v)@@ -996,6 +1002,7 @@ | NameVariable | NumberVariable | StringVariable+ | StandardVariable | UnknownVariable deriving (Show, Eq) @@ -1052,53 +1059,53 @@ variableType "supplement-number" = NumberVariable variableType "version" = NumberVariable variableType "volume" = NumberVariable-variableType "abstract" = StringVariable-variableType "annote" = StringVariable-variableType "archive" = StringVariable-variableType "archive_collection" = StringVariable-variableType "archive_location" = StringVariable-variableType "archive-place" = StringVariable-variableType "authority" = StringVariable-variableType "call-number" = StringVariable-variableType "citation-key" = StringVariable-variableType "citation-label" = StringVariable-variableType "collection-title" = StringVariable-variableType "container-title" = StringVariable-variableType "container-title-short" = StringVariable-variableType "dimensions" = StringVariable-variableType "division" = StringVariable+variableType "abstract" = StandardVariable+variableType "annote" = StandardVariable+variableType "archive" = StandardVariable+variableType "archive_collection" = StandardVariable+variableType "archive_location" = StandardVariable+variableType "archive-place" = StandardVariable+variableType "authority" = StandardVariable+variableType "call-number" = StandardVariable+variableType "citation-key" = StandardVariable+variableType "citation-label" = StandardVariable+variableType "collection-title" = StandardVariable+variableType "container-title" = StandardVariable+variableType "container-title-short" = StandardVariable+variableType "dimensions" = StandardVariable+variableType "division" = StandardVariable variableType "DOI" = StringVariable-variableType "event" = StringVariable-variableType "event-place" = StringVariable-variableType "event-title" = StringVariable --(new name for "event" to avoid confusion with new "event" type) -variableType "genre" = StringVariable+variableType "event" = StandardVariable+variableType "event-place" = StandardVariable+variableType "event-title" = StandardVariable --(new name for "event" to avoid confusion with new "event" type) +variableType "genre" = StandardVariable variableType "ISBN" = StringVariable variableType "ISSN" = StringVariable-variableType "jurisdiction" = StringVariable-variableType "keyword" = StringVariable-variableType "language" = StringVariable-variableType "license" = StringVariable-variableType "medium" = StringVariable-variableType "note" = StringVariable-variableType "original-publisher" = StringVariable-variableType "original-publisher-place" = StringVariable-variableType "original-title" = StringVariable-variableType "part-title" = StringVariable+variableType "jurisdiction" = StandardVariable+variableType "keyword" = StandardVariable+variableType "language" = StandardVariable+variableType "license" = StandardVariable+variableType "medium" = StandardVariable+variableType "note" = StandardVariable+variableType "original-publisher" = StandardVariable+variableType "original-publisher-place" = StandardVariable+variableType "original-title" = StandardVariable+variableType "part-title" = StandardVariable variableType "PMID" = StringVariable variableType "PMCID" = StringVariable-variableType "publisher" = StringVariable-variableType "publisher-place" = StringVariable-variableType "references" = StringVariable-variableType "reviewed-genre" = StringVariable-variableType "reviewed-title" = StringVariable-variableType "scale" = StringVariable-variableType "source" = StringVariable-variableType "status" = StringVariable-variableType "title" = StringVariable-variableType "title-short" = StringVariable+variableType "publisher" = StandardVariable+variableType "publisher-place" = StandardVariable+variableType "references" = StandardVariable+variableType "reviewed-genre" = StandardVariable+variableType "reviewed-title" = StandardVariable+variableType "scale" = StandardVariable+variableType "source" = StandardVariable+variableType "status" = StandardVariable+variableType "title" = StandardVariable+variableType "title-short" = StandardVariable variableType "URL" = StringVariable-variableType "volume-title" = StringVariable-variableType "year-suffix" = StringVariable+variableType "volume-title" = StandardVariable+variableType "year-suffix" = StandardVariable variableType _ = UnknownVariable newtype (ReferenceMap a) =@@ -1571,42 +1578,42 @@ outputToText (Linked _ xs) = T.unwords $ map outputToText xs outputToText (InNote x) = outputToText x -renderOutput :: CiteprocOutput a => CiteprocOptions -> Output a -> a-renderOutput _ NullOutput = mempty-renderOutput _ (Literal x) = x-renderOutput opts (Tagged (TagItem itemtype ident) x)+renderOutput :: CiteprocOutput a => CiteprocOptions -> Locale -> Output a -> a+renderOutput _ _ NullOutput = mempty+renderOutput _ _ (Literal x) = x+renderOutput opts locale (Tagged (TagItem itemtype ident) x) | linkCitations opts , itemtype /= AuthorOnly- = addHyperlink ("#ref-" <> unItemId ident) $ renderOutput opts x-renderOutput opts (Tagged (TagNames _ _ ns) x)+ = addHyperlink ("#ref-" <> unItemId ident) $ renderOutput opts locale x+renderOutput opts locale (Tagged (TagNames _ _ ns) x) -- a hack to ensure that names starting with lowercase don't get -- capitalized in pandoc footnotes: see jgm/pandoc#10983 | any hasNonstandardCase ns- = addTextCase Nothing PreserveCase $ renderOutput opts x-renderOutput opts (Tagged _ x) = renderOutput opts x-renderOutput opts (Formatted f [Linked url xs])+ = addTextCase Nothing PreserveCase $ renderOutput opts locale x+renderOutput opts locale (Tagged _ x) = renderOutput opts locale x+renderOutput opts locale (Formatted f [Linked url xs]) | linkBibliography opts , url == prefix <> anchor -- ensure correct handling of link prefixes like (https://doi.org/) -- when a link's prefix+anchor=target, ensure the link includes the prefix -- (see pandoc#6723 and citeproc#88)- = renderOutput opts $ Linked url [Formatted f xs]+ = renderOutput opts locale $ Linked url [Formatted f xs] where anchor = mconcat (map outputToText xs) prefix = fromMaybe "" (formatPrefix f)-renderOutput opts (Formatted formatting xs) =- addFormatting formatting . mconcat . fixPunct .+renderOutput opts locale (Formatted formatting xs) =+ addFormatting locale formatting . mconcat . fixPunct . (case formatDelimiter formatting of Just d -> addDelimiters (fromText d)- Nothing -> id) . filter (/= mempty) $ map (renderOutput opts) xs-renderOutput opts (Linked url xs)+ Nothing -> id) . filter (/= mempty) $ map (renderOutput opts locale) xs+renderOutput opts locale (Linked url xs) = (if linkBibliography opts then addHyperlink url- else id) . mconcat . fixPunct $ map (renderOutput opts) xs-renderOutput opts (InNote x) = inNote $+ else id) . mconcat . fixPunct $ map (renderOutput opts locale) xs+renderOutput opts locale (InNote x) = inNote $ dropTextWhile isSpace $ dropTextWhile (\c -> c == ',' || c == ';' || c == '.' || c == ':') $- renderOutput opts x+ renderOutput opts locale x hasNonstandardCase :: Name -> Bool hasNonstandardCase name =@@ -1810,3 +1817,227 @@ Left _ -> return Nothing Right lang -> return $ Just lang) +parseCslJson :: CiteprocOutput a => Locale -> Text -> a+parseCslJson locale t =+ case P.parseOnly+ (P.many' (pCslJson locale) <* P.endOfInput) t of+ Left _ -> fromText t+ Right xs -> mconcat xs++pCslJson :: CiteprocOutput a => Locale -> P.Parser a+pCslJson locale = P.choice+ [ pCslText+ , pCslQuoted+ , pCslItalic+ , pCslBold+ , pCslUnderline+ , pCslNoDecoration+ , pCslSmallCaps+ , pCslSup+ , pCslSub+ , pCslBaseline+ , pCslNoCase+ , pCslSymbol+ ]+ where+ ((outerOpenQuote, outerCloseQuote), (innerOpenQuote, innerCloseQuote)) =+ lookupQuotes locale+ isSpecialChar c = c == '<' || c == '>' || c == '\'' || c == '"' ||+ c == '’' || (not (isAscii c) && (isQuoteChar c || isSuperscriptChar c))+ isQuoteChar = P.inClass+ (T.unpack (outerOpenQuote <> outerCloseQuote <>+ innerOpenQuote <> innerCloseQuote))+ isApostrophe '\'' = True+ isApostrophe '’' = True+ isApostrophe _ = False+ pCsl = pCslJson locale+ notFollowedBySpace =+ P.peekChar' >>= guard . not . isSpaceChar+ isSpaceChar = P.inClass [' ','\t','\n','\r']+ pOpenQuote = (("\"" <$ P.char '"')+ <|> ("'" <$ P.char '\'')+ <|> (outerCloseQuote <$ P.string outerOpenQuote)+ <|> (innerCloseQuote <$ P.string innerOpenQuote))+ <* notFollowedBySpace+ pSpace = P.skipWhile isSpaceChar+ pCslText = fromText . addNarrowSpace <$>+ ( do t <- P.takeWhile1 (\c -> isAlphaNum c && not (isSpecialChar c))+ -- apostrophe+ P.option t $ do _ <- P.satisfy isApostrophe+ t' <- P.takeWhile1 isAlphaNum+ return (t <> "’" <> t')+ <|>+ P.takeWhile1 (\c -> not (isAlphaNum c || isSpecialChar c)) )+ pCslQuoted = addQuotes <$>+ do cl <- pOpenQuote+ mbc <- P.peekChar+ case mbc of+ Just c | T.singleton c == cl -> fail "unexpected close quote"+ _ -> return ()+ mconcat <$> P.manyTill' pCsl (P.string cl)+ pCslSymbol = do+ c <- P.satisfy isSpecialChar+ return $+ if isApostrophe c+ then fromText "’"+ else fromText $ T.singleton c+ pCslItalic = addFontStyle ItalicFont . mconcat <$>+ (P.string "<i>" *> P.manyTill' pCsl (P.string "</i>"))+ pCslBold = addFontWeight BoldWeight . mconcat <$>+ (P.string "<b>" *> P.manyTill' pCsl (P.string "</b>"))+ pCslUnderline = addTextDecoration UnderlineDecoration . mconcat <$>+ (P.string "<u>" *> P.manyTill' pCsl (P.string "</u>"))+ pCslNoDecoration = addTextDecoration NoDecoration . mconcat <$>+ (P.string "<span" *> pSpace *>+ P.string "class=\"nodecor\"" *> pSpace *> P.char '>' *>+ P.manyTill' pCsl (P.string "</span>"))+ pCslSup = addVerticalAlign SupAlign . mconcat <$>+ (P.string "<sup>" *> P.manyTill' pCsl (P.string "</sup>"))+ pCslSub = addVerticalAlign SubAlign . mconcat <$>+ (P.string "<sub>" *> P.manyTill' pCsl (P.string "</sub>"))+ pCslBaseline = addVerticalAlign BaselineAlign . mconcat <$>+ (P.string "<span" *> pSpace *> P.string "style=\"baseline\">" *>+ P.manyTill' pCsl (P.string "</span>"))+ pCslSmallCaps = addFontVariant SmallCapsVariant . mconcat <$>+ ((P.string "<span" *> pSpace *>+ P.string "style=\"font-variant:" *> pSpace *>+ P.string "small-caps;" *> pSpace *> P.char '"' *>+ pSpace *> P.char '>' *> P.manyTill' pCsl (P.string "</span>"))+ <|>+ (P.string "<sc>" *> P.manyTill' pCsl (P.string "</sc>")))+ pCslNoCase = addTextCase (localeLanguage locale) PreserveCase . mconcat <$>+ (P.string "<span" *> pSpace *>+ P.string "class=\"nocase\"" *> pSpace *> P.char '>' *>+ P.manyTill' pCsl (P.string "</span>"))+ addNarrowSpace =+ T.replace " ;" "\x202F;" .+ T.replace " ?" "\x202F?" .+ T.replace " !" "\x202F!" .+ T.replace " »" "\x202F»" .+ T.replace "« " "«\x202F"++lookupLocaleTerm :: Locale -> Text -> Maybe Text+lookupLocaleTerm locale termname = do+ let terms = localeTerms locale+ case M.lookup termname terms of+ Just ((_,t):_) -> Just t+ _ -> Nothing++lookupQuotes :: Locale -> ((Text, Text), (Text, Text))+lookupQuotes locale = ((outerOpen, outerClose), (innerOpen, innerClose))+ where+ outerOpen = fromMaybe "\x201C" $ lookupLocaleTerm locale "open-quote"+ outerClose = fromMaybe "\x201D" $ lookupLocaleTerm locale "close-quote"+ innerOpen = fromMaybe "\x2018" $ lookupLocaleTerm locale "open-inner-quote"+ innerClose = fromMaybe "\x2019" $ lookupLocaleTerm locale "close-inner-quote"++isSuperscriptChar :: Char -> Bool+isSuperscriptChar c = M.member c superscriptChars++superscriptChars :: M.Map Char Text+superscriptChars = M.fromList+ [ ('\x00AA' , "\x0061")+ , ('\x00B2' , "\x0032")+ , ('\x00B3' , "\x0033")+ , ('\x00B9' , "\x0031")+ , ('\x00BA' , "\x006F")+ , ('\x02B0' , "\x0068")+ , ('\x02B1' , "\x0266")+ , ('\x02B2' , "\x006A")+ , ('\x02B3' , "\x0072")+ , ('\x02B4' , "\x0279")+ , ('\x02B5' , "\x027B")+ , ('\x02B6' , "\x0281")+ , ('\x02B7' , "\x0077")+ , ('\x02B8' , "\x0079")+ , ('\x02E0' , "\x0263")+ , ('\x02E1' , "\x006C")+ , ('\x02E2' , "\x0073")+ , ('\x02E3' , "\x0078")+ , ('\x02E4' , "\x0295")+ , ('\x1D2C' , "\x0041")+ , ('\x1D2D' , "\x00C6")+ , ('\x1D2E' , "\x0042")+ , ('\x1D30' , "\x0044")+ , ('\x1D31' , "\x0045")+ , ('\x1D32' , "\x018E")+ , ('\x1D33' , "\x0047")+ , ('\x1D34' , "\x0048")+ , ('\x1D35' , "\x0049")+ , ('\x1D36' , "\x004A")+ , ('\x1D37' , "\x004B")+ , ('\x1D38' , "\x004C")+ , ('\x1D39' , "\x004D")+ , ('\x1D3A' , "\x004E")+ , ('\x1D3C' , "\x004F")+ , ('\x1D3D' , "\x0222")+ , ('\x1D3E' , "\x0050")+ , ('\x1D3F' , "\x0052")+ , ('\x1D40' , "\x0054")+ , ('\x1D41' , "\x0055")+ , ('\x1D42' , "\x0057")+ , ('\x1D43' , "\x0061")+ , ('\x1D44' , "\x0250")+ , ('\x1D45' , "\x0251")+ , ('\x1D46' , "\x1D02")+ , ('\x1D47' , "\x0062")+ , ('\x1D48' , "\x0064")+ , ('\x1D49' , "\x0065")+ , ('\x1D4A' , "\x0259")+ , ('\x1D4B' , "\x025B")+ , ('\x1D4C' , "\x025C")+ , ('\x1D4D' , "\x0067")+ , ('\x1D4F' , "\x006B")+ , ('\x1D50' , "\x006D")+ , ('\x1D51' , "\x014B")+ , ('\x1D52' , "\x006F")+ , ('\x1D53' , "\x0254")+ , ('\x1D54' , "\x1D16")+ , ('\x1D55' , "\x1D17")+ , ('\x1D56' , "\x0070")+ , ('\x1D57' , "\x0074")+ , ('\x1D58' , "\x0075")+ , ('\x1D59' , "\x1D1D")+ , ('\x1D5A' , "\x026F")+ , ('\x1D5B' , "\x0076")+ , ('\x1D5C' , "\x1D25")+ , ('\x1D5D' , "\x03B2")+ , ('\x1D5E' , "\x03B3")+ , ('\x1D5F' , "\x03B4")+ , ('\x1D60' , "\x03C6")+ , ('\x1D61' , "\x03C7")+ , ('\x2070' , "\x0030")+ , ('\x2071' , "\x0069")+ , ('\x2074' , "\x0034")+ , ('\x2075' , "\x0035")+ , ('\x2076' , "\x0036")+ , ('\x2077' , "\x0037")+ , ('\x2078' , "\x0038")+ , ('\x2079' , "\x0039")+ , ('\x207A' , "\x002B")+ , ('\x207B' , "\x2212")+ , ('\x207C' , "\x003D")+ , ('\x207D' , "\x0028")+ , ('\x207E' , "\x0029")+ , ('\x207F' , "\x006E")+ , ('\x2120' , "\x0053\x004D")+ , ('\x2122' , "\x0054\x004D")+ , ('\x3192' , "\x4E00")+ , ('\x3193' , "\x4E8C")+ , ('\x3194' , "\x4E09")+ , ('\x3195' , "\x56DB")+ , ('\x3196' , "\x4E0A")+ , ('\x3197' , "\x4E2D")+ , ('\x3198' , "\x4E0B")+ , ('\x3199' , "\x7532")+ , ('\x319A' , "\x4E59")+ , ('\x319B' , "\x4E19")+ , ('\x319C' , "\x4E01")+ , ('\x319D' , "\x5929")+ , ('\x319E' , "\x5730")+ , ('\x319F' , "\x4EBA")+ , ('\x02C0' , "\x0294")+ , ('\x02C1' , "\x0295")+ , ('\x06E5' , "\x0648")+ , ('\x06E6' , "\x064A")+ ]
test/Spec.hs view
@@ -186,12 +186,14 @@ then do modify $ \st -> st{ passed = (category test, path test) : passed st } -- suppress PASSED messages- -- liftIO $ TIO.putStrLn $ "[PASSED] " <> path test+ -- liftIO $ TIO.putStrLn $ "[PASSED] " <> path test return Passed else do modify $ \st -> st{ failed = (category test, path test) : failed st } liftIO $ do- TIO.putStrLn $ "[FAILED] " <> T.pack (path test)+ TIO.putStrLn $ (if path test `elem` expectedFailures+ then "[FAILED:EXPECTED] "+ else "[FAILED:UNEXPECTED] ") <> T.pack (path test) showDiff expected actual return $ Failed actual expected @@ -312,7 +314,7 @@ putStrLn "" let categories = sort $ Set.toList $ foldr (Set.insert . category) mempty testCases- putStrLn $ printf "%-15s %6s %6s %6s %6s"+ putStrLn $ printf "%-30s %6s %6s %6s %6s" ("CATEGORY" :: String) ("PASS" :: String) ("FAIL" :: String)@@ -324,17 +326,17 @@ let e = length . filter ((== cat) . fst) . errored $ counts let s = length . filter ((== cat) . fst) . skipped $ counts let percent = (fromIntegral p / fromIntegral (p + f + e) :: Double)- putStrLn $ printf "%-15s %6d %6d %6d %6d |%-20s|"+ putStrLn $ printf "%-30s %6d %6d %6d %6d |%-20s|" (T.unpack cat) p f e s (replicate (floor (percent * 20.0)) '+') mapM_ resultsFor categories- putStrLn $ printf "%-15s %6s %6s %6s %6s"+ putStrLn $ printf "%-30s %6s %6s %6s %6s" ("-------------" :: String) ("-----" :: String) ("-----" :: String) ("-----" :: String) ("-----" :: String)- putStrLn $ printf "%-15s %6d %6d %6d %6d"+ putStrLn $ printf "%-30s %6d %6d %6d %6d" ("(all)" :: String) (length (passed counts)) (length (failed counts))@@ -453,5 +455,7 @@ "test/csl/bugreports_AutomaticallyDeleteItemsFails.txt", "test/csl/affix_WithCommas.txt", "test/csl/affix_CommaAfterQuote.txt",- "test/overrides/flipflop_NumericField.txt"+ "test/overrides/flipflop_NumericField.txt",+ "test/csl/testers_SecondAutoGeneratedZoteroPluginTest.txt",+ "test/csl/testers_FirstAutoGeneratedZoteroPluginTest.txt" ]
+ test/extra/issue_173.txt view
@@ -0,0 +1,66 @@+>>===== MODE =====>>+bibliography+<<===== MODE =====<<+++++>>===== RESULT =====>>+<div class="csl-bib-body">+ <div class="csl-entry">https://www.researchgate.net/publication/308399994_Teo's_Brew_-_The_Re-composition_of_'Bitches_Brew'_in_the_Studio</div>+</div>+<<===== RESULT =====<<+++>>===== CSL =====>>+<?xml version="1.0" encoding="utf-8"?>+<style+ xmlns="http://purl.org/net/xbiblio/csl"+ class="note"+ version="1.0">+ <info>+ <id />+ <title />+ </info>+ <citation>+ <layout>+ <text variable="URL"/>+ </layout>+ </citation>+ <bibliography>+ <layout>+ <text variable="URL"/>+ </layout>+ </bibliography>+</style>+<<===== CSL =====<<+++>>===== CITATION-ITEMS =====>>+[ [ {"id":"Doe" } ] ]+<<===== CITATION-ITEMS =====<<++++>>===== INPUT =====>>+[+ {+ "author": [+ {+ "family": "Doe",+ "given": "John"+ }+ ],+ "id": "Doe",+ "type": "book",+ "title": "Title",+ "URL":"https://www.researchgate.net/publication/308399994_Teo's_Brew_-_The_Re-composition_of_'Bitches_Brew'_in_the_Studio"+ }+]+<<===== INPUT =====<<+++>>===== VERSION =====>>+1.0+<<===== VERSION =====<<+
+ test/extra/issue_174.txt view
@@ -0,0 +1,98 @@+>>===== MODE =====>>+citation+<<===== MODE =====<<+++>>===== RESULT =====>>+Title, ALCT, 3+<<===== RESULT =====<<+++>>===== CSL =====>>+<?xml version="1.0" encoding="utf-8"?>+<style class="note" version="1.0" xmlns="http://purl.org/net/xbiblio/csl">+ <info>+ <title>Variable substitution test</title>+ <id>http://www.zotero.org/styles/subsitute-test</id>+ </info>+ <citation>+ <layout>+ <group delimiter=", ">+ <!-- short title -->+ <names variable="composer">+ <substitute>+ <text form="short" variable="title"/>+ </substitute>+ </names>+ <text form="short" variable="title"/>+ <!-- short container title -->+ <names variable="author">+ <substitute>+ <text form="short" variable="container-title"/>+ </substitute>+ </names>+ <text form="short" variable="container-title"/>+ <!-- number -->+ <names variable="illustrator">+ <substitute>+ <number variable="volume"/>+ </substitute>+ </names>+ <number variable="volume"/>+ </group>+ </layout>+ </citation>+ <bibliography>+ <layout>+ <group delimiter=", ">+ <!-- title -->+ <names variable="composer">+ <substitute>+ <text variable="title"/>+ </substitute>+ </names>+ <text variable="title"/>+ <!-- container title -->+ <names variable="author">+ <substitute>+ <text variable="container-title"/>+ </substitute>+ </names>+ <text variable="container-title"/>+ <!-- number as text -->+ <names variable="illustrator">+ <substitute>+ <text variable="volume"/>+ </substitute>+ </names>+ <text variable="volume"/>+ </group>+ </layout>+ </bibliography>+</style>+<<===== CSL =====<<+++>>===== CITATION-ITEMS =====>>+[ [ {"id":"test" } ] ]+<<===== CITATION-ITEMS =====<<++++>>===== INPUT =====>>+[+ { "id": "test",+ "title": "A title with a long form",+ "title-short": "Title",+ "container-title": "A long container title",+ "container-title-short": "ALCT",+ "volume": "3"+ }+]+<<===== INPUT =====<<+++>>===== VERSION =====>>+1.0+<<===== VERSION =====<<+
+ test/extra/issue_175.txt view
@@ -0,0 +1,79 @@+>>===== MODE =====>>+citation+<<===== MODE =====<<+++>>===== RESULT =====>>+numeric volume: volumes 1–2+<<===== RESULT =====<<+++>>===== CSL =====>>+<?xml version="1.0" encoding="utf-8"?>+<style class="note" version="1.0" xmlns="http://purl.org/net/xbiblio/csl">+ <info>+ <title>Numeric test</title>+ <id>http://www.zotero.org/styles/numeric-test</id>+ <link href="http://www.zotero.org/styles/locator-test" rel="self"/>+ <updated>2025-05-20T00:00:00+00:00</updated>+ </info>+ <citation>+ <layout>+ <group delimiter=" ">+ <choose>+ <if variable="volume">+ <choose>+ <if is-numeric="volume">+ <text value="numeric volume:"/>+ </if>+ <else>+ <text value="non-numeric volume:"/>+ </else>+ </choose>+ <label variable="volume"/>+ <text variable="volume"/>+ </if>+ </choose>+ <choose>+ <if variable="locator">+ <choose>+ <if is-numeric="locator">+ <text value="numeric locator:"/>+ </if>+ <else>+ <text value="non-numeric locator:"/>+ </else>+ </choose>+ <label variable="locator"/>+ <text variable="locator"/>+ </if>+ </choose>+ </group>+ </layout>+ </citation>+</style>+<<===== CSL =====<<+++>>===== CITATION-ITEMS =====>>+[ [ {"id":"VolumeTest1" } ] ]+<<===== CITATION-ITEMS =====<<++++>>===== INPUT =====>>+[+ {+ "id": "VolumeTest1",+ "type": "book",+ "title": "Title",+ "volume": "1–2"+ }+]+<<===== INPUT =====<<+++>>===== VERSION =====>>+1.0+<<===== VERSION =====<<+