@@ -6,6 +6,7 @@ module Database.V5.Bloodhound.Internal.Analysis where
6import Bloodhound.Import
6import Bloodhound.Import
8import qualified Data.Map.Strict as M
8import qualified Data.Map.Strict as M
9import qualified Data.Text as T
10import qualified Data.Text as T
11import Database.V5.Bloodhound.Internal.Newtypes
12import Database.V5.Bloodhound.Internal.Newtypes
@@ -15,13 +16,15 @@ data Analysis = Analysis
15 { analysisAnalyzer :: M.Map Text AnalyzerDefinition
16 { analysisAnalyzer :: M.Map Text AnalyzerDefinition
16 , analysisTokenizer :: M.Map Text TokenizerDefinition
17 , analysisTokenizer :: M.Map Text TokenizerDefinition
17 , analysisTokenFilter :: M.Map Text TokenFilterDefinition
18 , analysisTokenFilter :: M.Map Text TokenFilterDefinition
19+ , analysisCharFilter :: M.Map Text CharFilterDefinition
18 } deriving (Eq, Show)
20 } deriving (Eq, Show)
20instance ToJSON Analysis where
22instance ToJSON Analysis where
21− toJSON (Analysis analyzer tokenizer tokenFilter) = object
23+ toJSON (Analysis analyzer tokenizer tokenFilter charFilter) = object
22 [ "analyzer" .= analyzer
24 [ "analyzer" .= analyzer
23 , "tokenizer" .= tokenizer
25 , "tokenizer" .= tokenizer
24 , "filter" .= tokenFilter
26 , "filter" .= tokenFilter
27+ , "char_filter" .= charFilter
27instance FromJSON Analysis where
30instance FromJSON Analysis where
@@ -29,6 +32,7 @@ instance FromJSON Analysis where
29 <$> m .: "analyzer"
32 <$> m .: "analyzer"
30 <*> m .:? "tokenizer" .!= M.empty
33 <*> m .:? "tokenizer" .!= M.empty
31 <*> m .:? "filter" .!= M.empty
34 <*> m .:? "filter" .!= M.empty
35+ <*> m .:? "char_filter" .!= M.empty
33newtype Tokenizer =
37newtype Tokenizer =
34 Tokenizer Text
38 Tokenizer Text
@@ -37,18 +41,57 @@ newtype Tokenizer =
37data AnalyzerDefinition = AnalyzerDefinition
41data AnalyzerDefinition = AnalyzerDefinition
38 { analyzerDefinitionTokenizer :: Maybe Tokenizer
42 { analyzerDefinitionTokenizer :: Maybe Tokenizer
39 , analyzerDefinitionFilter :: [TokenFilter]
43 , analyzerDefinitionFilter :: [TokenFilter]
44+ , analyzerDefinitionCharFilter :: [CharFilter]
40 } deriving (Eq,Show)
45 } deriving (Eq,Show)
42instance ToJSON AnalyzerDefinition where
47instance ToJSON AnalyzerDefinition where
43− toJSON (AnalyzerDefinition tokenizer tokenFilter) = object $ catMaybes
48+ toJSON (AnalyzerDefinition tokenizer tokenFilter charFilter) =
44 [ fmap ("tokenizer" .=) tokenizer
50 [ fmap ("tokenizer" .=) tokenizer
45 , Just $ "filter" .= tokenFilter
51 , Just $ "filter" .= tokenFilter
52+ , Just $ "char_filter" .= charFilter
48instance FromJSON AnalyzerDefinition where
55instance FromJSON AnalyzerDefinition where
49 parseJSON = withObject "AnalyzerDefinition" $ \m -> AnalyzerDefinition
56 parseJSON = withObject "AnalyzerDefinition" $ \m -> AnalyzerDefinition
50 <$> m .:? "tokenizer"
57 <$> m .:? "tokenizer"
51 <*> m .:? "filter" .!= []
58 <*> m .:? "filter" .!= []
59+ <*> m .:? "char_filter" .!= []
61+-- | Character filters are used to preprocess the stream of characters
62+-- before it is passed to the tokenizer.
63+data CharFilterDefinition
64+ = CharFilterDefinitionMapping (M.Map Text Text)
65+ | CharFilterDefinitionPatternReplace
66+ { charFilterDefinitionPatternReplacePattern :: Text
67+ , charFilterDefinitionPatternReplaceReplacement :: Text
68+ , charFilterDefinitionPatternReplaceFlags :: Maybe Text
72+instance ToJSON CharFilterDefinition where
73+ toJSON (CharFilterDefinitionMapping ms) = object
74+ [ "type" .= ("mapping" :: Text)
75+ , "mappings" .= [a <> " => " <> b | (a, b) <- M.toList ms] ]
76+ toJSON (CharFilterDefinitionPatternReplace pat repl flags) = object $
77+ [ "type" .= ("pattern_replace" :: Text)
79+ , "replacement" .= repl
80+ ] ++ maybe [] (\f -> ["flags" .= f]) flags
82+instance FromJSON CharFilterDefinition where
83+ parseJSON = withObject "CharFilterDefinition" $ \m -> do
86+ "mapping" -> CharFilterDefinitionMapping . M.fromList <$> ms
88+ ms = m .: "mappings" >>= mapM parseMapping
89+ parseMapping kv = case T.splitOn "=>" kv of
90+ (k:vs) -> pure (T.strip k, T.strip $ T.concat vs)
91+ _ -> fail "mapping is not of the format key => value"
92+ "pattern_replace" -> CharFilterDefinitionPatternReplace
93+ <$> m .: "pattern" <*> m .: "replacement" <*> m .:? "flags"
94+ _ -> fail ("unrecognized character filter type: " ++ T.unpack t)
53newtype TokenizerDefinition =
96newtype TokenizerDefinition =
54 TokenizerDefinitionNgram Ngram
97 TokenizerDefinitionNgram Ngram
@@ -112,6 +155,8 @@ data TokenFilterDefinition
112 | TokenFilterDefinitionReverse
155 | TokenFilterDefinitionReverse
113 | TokenFilterDefinitionSnowball Language
156 | TokenFilterDefinitionSnowball Language
114 | TokenFilterDefinitionShingle Shingle
157 | TokenFilterDefinitionShingle Shingle
158+ | TokenFilterDefinitionStemmer Language
159+ | TokenFilterDefinitionStop (Either Language [StopWord])
115 deriving (Eq, Show)
160 deriving (Eq, Show)
117instance ToJSON TokenFilterDefinition where
162instance ToJSON TokenFilterDefinition where
@@ -143,6 +188,16 @@ instance ToJSON TokenFilterDefinition where
143 , "token_separator" .= shingleTokenSeparator s
188 , "token_separator" .= shingleTokenSeparator s
144 , "filler_token" .= shingleFillerToken s
189 , "filler_token" .= shingleFillerToken s
191+ TokenFilterDefinitionStemmer lang -> object
192+ [ "type" .= ("stemmer" :: Text)
193+ , "language" .= languageToText lang
195+ TokenFilterDefinitionStop stop -> object
196+ [ "type" .= ("stop" :: Text)
197+ , "stopwords" .= case stop of
198+ Left lang -> String $ "_" <> languageToText lang <> "_"
199+ Right stops -> toJSON stops
147instance FromJSON TokenFilterDefinition where
202instance FromJSON TokenFilterDefinition where
148 parseJSON = withObject "TokenFilterDefinition" $ \m -> do
203 parseJSON = withObject "TokenFilterDefinition" $ \m -> do
@@ -163,6 +218,19 @@ instance FromJSON TokenFilterDefinition where
163 <*> (fmap.fmap) unStringlyTypedBool (m .:? "output_unigrams_if_no_shingles") .!= False
218 <*> (fmap.fmap) unStringlyTypedBool (m .:? "output_unigrams_if_no_shingles") .!= False
164 <*> m .:? "token_separator" .!= " "
219 <*> m .:? "token_separator" .!= " "
165 <*> m .:? "filler_token" .!= "_"
220 <*> m .:? "filler_token" .!= "_"
221+ "stemmer" -> TokenFilterDefinitionStemmer
224+ stop <- m .: "stopwords"
225+ stop' <- case stop of
231+ . T.dropEnd 1 $ lang
232+ _ -> Right <$> parseJSON stop
233+ return (TokenFilterDefinitionStop stop')
166 _ -> fail ("unrecognized token filter type: " ++ T.unpack t)
234 _ -> fail ("unrecognized token filter type: " ++ T.unpack t)
168-- | The set of languages that can be passed to various analyzers,
236-- | The set of languages that can be passed to various analyzers,