@@ -8,24 +8,29 @@ import Data.Aeson.Types (Parser)
8import qualified Data.Map.Strict as M
8import qualified Data.Map.Strict as M
9import Data.Maybe (catMaybes)
9import Data.Maybe (catMaybes)
10import Data.Text (Text)
10import Data.Text (Text)
11+import qualified Data.Text as T
13+import Database.V5.Bloodhound.Internal.Newtypes
12import Database.V5.Bloodhound.Internal.StringlyTyped
14import Database.V5.Bloodhound.Internal.StringlyTyped
14data Analysis = Analysis
16data Analysis = Analysis
15 { analysisAnalyzer :: M.Map Text AnalyzerDefinition
17 { analysisAnalyzer :: M.Map Text AnalyzerDefinition
16 , analysisTokenizer :: M.Map Text TokenizerDefinition
18 , analysisTokenizer :: M.Map Text TokenizerDefinition
19+ , analysisTokenFilter :: M.Map Text TokenFilterDefinition
17 } deriving (Eq, Show)
20 } deriving (Eq, Show)
19instance ToJSON Analysis where
22instance ToJSON Analysis where
20− toJSON (Analysis analyzer tokenizer) = object
23+ toJSON (Analysis analyzer tokenizer tokenFilter) = object
21 [ "analyzer" .= analyzer
24 [ "analyzer" .= analyzer
22 , "tokenizer" .= tokenizer
25 , "tokenizer" .= tokenizer
26+ , "filter" .= tokenFilter
25instance FromJSON Analysis where
29instance FromJSON Analysis where
26 parseJSON = withObject "Analysis" $ \m -> Analysis
30 parseJSON = withObject "Analysis" $ \m -> Analysis
27 <$> m .: "analyzer"
31 <$> m .: "analyzer"
28− <*> m .: "tokenizer"
32+ <*> m .:? "tokenizer" .!= M.empty
33+ <*> m .:? "filter" .!= M.empty
30newtype Tokenizer =
35newtype Tokenizer =
31 Tokenizer Text
36 Tokenizer Text
@@ -33,17 +38,19 @@ newtype Tokenizer =
34data AnalyzerDefinition = AnalyzerDefinition
39data AnalyzerDefinition = AnalyzerDefinition
35 { analyzerDefinitionTokenizer :: Maybe Tokenizer
40 { analyzerDefinitionTokenizer :: Maybe Tokenizer
41+ , analyzerDefinitionFilter :: [TokenFilter]
36 } deriving (Eq,Show)
42 } deriving (Eq,Show)
38instance ToJSON AnalyzerDefinition where
44instance ToJSON AnalyzerDefinition where
39− toJSON (AnalyzerDefinition tokenizer) = object $ catMaybes
45+ toJSON (AnalyzerDefinition tokenizer tokenFilter) = object $ catMaybes
40 [ fmap ("tokenizer" .=) tokenizer
46 [ fmap ("tokenizer" .=) tokenizer
47+ , Just $ "filter" .= tokenFilter
43instance FromJSON AnalyzerDefinition where
50instance FromJSON AnalyzerDefinition where
44 parseJSON = withObject "AnalyzerDefinition" $ \m -> AnalyzerDefinition
51 parseJSON = withObject "AnalyzerDefinition" $ \m -> AnalyzerDefinition
45 <$> m .:? "tokenizer"
52 <$> m .:? "tokenizer"
46−
53+ <*> m .:? "filter" .!= []
48data TokenizerDefinition
55data TokenizerDefinition
49 = TokenizerDefinitionNgram Ngram
56 = TokenizerDefinitionNgram Ngram
@@ -98,3 +105,210 @@ instance FromJSON TokenChar where
98 "punctuation" -> return TokenPunctuation
105 "punctuation" -> return TokenPunctuation
99 "symbol" -> return TokenSymbol
106 "symbol" -> return TokenSymbol
100 _ -> fail "invalid TokenChar"
107 _ -> fail "invalid TokenChar"
109+-- | Token filters are used to create custom analyzers.
110+data TokenFilterDefinition
111+ = TokenFilterDefinitionLowercase (Maybe Language)
112+ | TokenFilterDefinitionUppercase (Maybe Language)
113+ | TokenFilterDefinitionApostrophe
114+ | TokenFilterDefinitionReverse
115+ | TokenFilterDefinitionSnowball Language
116+ | TokenFilterDefinitionShingle Shingle
119+instance ToJSON TokenFilterDefinition where
120+ toJSON x = case x of
121+ TokenFilterDefinitionLowercase mlang -> object $ catMaybes
122+ [ Just $ "type" .= ("lowercase" :: Text)
123+ , fmap (\lang -> "language" .= languageToText lang) mlang
125+ TokenFilterDefinitionUppercase mlang -> object $ catMaybes
126+ [ Just $ "type" .= ("uppercase" :: Text)
127+ , fmap (\lang -> "language" .= languageToText lang) mlang
129+ TokenFilterDefinitionApostrophe -> object
130+ [ "type" .= ("apostrophe" :: Text)
132+ TokenFilterDefinitionReverse -> object
133+ [ "type" .= ("reverse" :: Text)
135+ TokenFilterDefinitionSnowball lang -> object
136+ [ "type" .= ("snowball" :: Text)
137+ , "language" .= languageToText lang
139+ TokenFilterDefinitionShingle s -> object
140+ [ "type" .= ("shingle" :: Text)
141+ , "max_shingle_size" .= shingleMaxSize s
142+ , "min_shingle_size" .= shingleMinSize s
143+ , "output_unigrams" .= shingleOutputUnigrams s
144+ , "output_unigrams_if_no_shingles" .= shingleOutputUnigramsIfNoShingles s
145+ , "token_separator" .= shingleTokenSeparator s
146+ , "filler_token" .= shingleFillerToken s
149+instance FromJSON TokenFilterDefinition where
150+ parseJSON = withObject "TokenFilterDefinition" $ \m -> do
153+ "reverse" -> return TokenFilterDefinitionReverse
154+ "apostrophe" -> return TokenFilterDefinitionApostrophe
155+ "lowercase" -> TokenFilterDefinitionLowercase
156+ <$> m .:? "language"
157+ "uppercase" -> TokenFilterDefinitionUppercase
158+ <$> m .:? "language"
159+ "snowball" -> TokenFilterDefinitionSnowball
161+ "shingle" -> fmap TokenFilterDefinitionShingle $ Shingle
162+ <$> (fmap.fmap) unStringlyTypedInt (m .:? "max_shingle_size") .!= 2
163+ <*> (fmap.fmap) unStringlyTypedInt (m .:? "min_shingle_size") .!= 2
164+ <*> (fmap.fmap) unStringlyTypedBool (m .:? "output_unigrams") .!= True
165+ <*> (fmap.fmap) unStringlyTypedBool (m .:? "output_unigrams_if_no_shingles") .!= False
166+ <*> m .:? "token_separator" .!= " "
167+ <*> m .:? "filler_token" .!= "_"
168+ _ -> fail ("unrecognized token filter type: " ++ T.unpack t)
170+-- | The set of languages that can be passed to various analyzers,
171+-- filters, etc. in ElasticSearch. Most data types in this module
172+-- that have a 'Language' field are actually only actually to
173+-- handle a subset of these languages. Consult the official
174+-- ElasticSearch documentation to see what is actually supported.
216+instance ToJSON Language where
217+ toJSON = Data.Aeson.String . languageToText
219+instance FromJSON Language where
220+ parseJSON = withText "Language" $ \t -> case languageFromText t of
221+ Nothing -> fail "not a supported ElasticSearch language"
222+ Just lang -> return lang
224+languageToText :: Language -> Text
225+languageToText x = case x of
227+ Armenian -> "armenian"
229+ Bengali -> "bengali"
230+ Brazilian -> "brazilian"
231+ Bulgarian -> "bulgarian"
232+ Catalan -> "catalan"
237+ English -> "english"
238+ Finnish -> "finnish"
240+ Galician -> "galician"
242+ German2 -> "german2"
245+ Hungarian -> "hungarian"
246+ Indonesian -> "indonesian"
248+ Italian -> "italian"
250+ Latvian -> "latvian"
251+ Lithuanian -> "lithuanian"
253+ Norwegian -> "norwegian"
254+ Persian -> "persian"
256+ Portuguese -> "portuguese"
257+ Romanian -> "romanian"
258+ Russian -> "russian"
260+ Spanish -> "spanish"
261+ Swedish -> "swedish"
263+ Turkish -> "turkish"
265+languageFromText :: Text -> Maybe Language
266+languageFromText x = case x of
267+ "arabic" -> Just Arabic
268+ "armenian" -> Just Armenian
269+ "basque" -> Just Basque
270+ "bengali" -> Just Bengali
271+ "brazilian" -> Just Brazilian
272+ "bulgarian" -> Just Bulgarian
273+ "catalan" -> Just Catalan
275+ "czech" -> Just Czech
276+ "danish" -> Just Danish
277+ "dutch" -> Just Dutch
278+ "english" -> Just English
279+ "finnish" -> Just Finnish
280+ "french" -> Just French
281+ "galician" -> Just Galician
282+ "german" -> Just German
283+ "german2" -> Just German2
284+ "greek" -> Just Greek
285+ "hindi" -> Just Hindi
286+ "hungarian" -> Just Hungarian
287+ "indonesian" -> Just Indonesian
288+ "irish" -> Just Irish
289+ "italian" -> Just Italian
291+ "latvian" -> Just Latvian
292+ "lithuanian" -> Just Lithuanian
293+ "lovins" -> Just Lovins
294+ "norwegian" -> Just Norwegian
295+ "persian" -> Just Persian
296+ "porter" -> Just Porter
297+ "portuguese" -> Just Portuguese
298+ "romanian" -> Just Romanian
299+ "russian" -> Just Russian
300+ "sorani" -> Just Sorani
301+ "spanish" -> Just Spanish
302+ "swedish" -> Just Swedish
304+ "turkish" -> Just Turkish
307+data Shingle = Shingle
308+ { shingleMaxSize :: Int
309+ , shingleMinSize :: Int
310+ , shingleOutputUnigrams :: Bool
311+ , shingleOutputUnigramsIfNoShingles :: Bool
312+ , shingleTokenSeparator :: Text
313+ , shingleFillerToken :: Text
314+ } deriving (Eq, Show)