@@ -8,24 +8,29 @@ import Data.Aeson.Types (Parser)
88import qualified Data.Map.Strict as M
99import Data.Maybe (catMaybes)
1010import Data.Text (Text)
11+import qualified Data.Text as T
1112
13+import Database.V5.Bloodhound.Internal.Newtypes
1214import Database.V5.Bloodhound.Internal.StringlyTyped
1315
1416data Analysis = Analysis
1517 { analysisAnalyzer :: M.Map Text AnalyzerDefinition
1618 , analysisTokenizer :: M.Map Text TokenizerDefinition
19+ , analysisTokenFilter :: M.Map Text TokenFilterDefinition
1720 } deriving (Eq, Show)
1821
1922instance ToJSON Analysis where
20− toJSON (Analysis analyzer tokenizer) = object
23+ toJSON (Analysis analyzer tokenizer tokenFilter) = object
2124 [ "analyzer" .= analyzer
2225 , "tokenizer" .= tokenizer
26+ , "filter" .= tokenFilter
2327 ]
2428
2529instance FromJSON Analysis where
2630 parseJSON = withObject "Analysis" $ \m -> Analysis
2731 <$> m .: "analyzer"
28− <*> m .: "tokenizer"
32+ <*> m .:? "tokenizer" .!= M.empty
33+ <*> m .:? "filter" .!= M.empty
2934
3035newtype Tokenizer =
3136 Tokenizer Text
@@ -33,17 +38,19 @@ newtype Tokenizer =
3338
3439data AnalyzerDefinition = AnalyzerDefinition
3540 { analyzerDefinitionTokenizer :: Maybe Tokenizer
41+ , analyzerDefinitionFilter :: [TokenFilter]
3642 } deriving (Eq,Show)
3743
3844instance ToJSON AnalyzerDefinition where
39− toJSON (AnalyzerDefinition tokenizer) = object $ catMaybes
45+ toJSON (AnalyzerDefinition tokenizer tokenFilter) = object $ catMaybes
4046 [ fmap ("tokenizer" .=) tokenizer
47+ , Just $ "filter" .= tokenFilter
4148 ]
4249
4350instance FromJSON AnalyzerDefinition where
4451 parseJSON = withObject "AnalyzerDefinition" $ \m -> AnalyzerDefinition
4552 <$> m .:? "tokenizer"
46−
53+ <*> m .:? "filter" .!= []
4754
4855data TokenizerDefinition
4956 = TokenizerDefinitionNgram Ngram
@@ -98,3 +105,210 @@ instance FromJSON TokenChar where
98105 "punctuation" -> return TokenPunctuation
99106 "symbol" -> return TokenSymbol
100107 _ -> fail "invalid TokenChar"
108+
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
117+ deriving (Eq, Show)
118+
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
124+ ]
125+ TokenFilterDefinitionUppercase mlang -> object $ catMaybes
126+ [ Just $ "type" .= ("uppercase" :: Text)
127+ , fmap (\lang -> "language" .= languageToText lang) mlang
128+ ]
129+ TokenFilterDefinitionApostrophe -> object
130+ [ "type" .= ("apostrophe" :: Text)
131+ ]
132+ TokenFilterDefinitionReverse -> object
133+ [ "type" .= ("reverse" :: Text)
134+ ]
135+ TokenFilterDefinitionSnowball lang -> object
136+ [ "type" .= ("snowball" :: Text)
137+ , "language" .= languageToText lang
138+ ]
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
147+ ]
148+
149+instance FromJSON TokenFilterDefinition where
150+ parseJSON = withObject "TokenFilterDefinition" $ \m -> do
151+ t <- m .: "type"
152+ case (t :: Text) of
153+ "reverse" -> return TokenFilterDefinitionReverse
154+ "apostrophe" -> return TokenFilterDefinitionApostrophe
155+ "lowercase" -> TokenFilterDefinitionLowercase
156+ <$> m .:? "language"
157+ "uppercase" -> TokenFilterDefinitionUppercase
158+ <$> m .:? "language"
159+ "snowball" -> TokenFilterDefinitionSnowball
160+ <$> m .: "language"
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)
169+
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.
175+data Language
176+ = Arabic
177+ | Armenian
178+ | Basque
179+ | Bengali
180+ | Brazilian
181+ | Bulgarian
182+ | Catalan
183+ | Cjk
184+ | Czech
185+ | Danish
186+ | Dutch
187+ | English
188+ | Finnish
189+ | French
190+ | Galician
191+ | German
192+ | German2
193+ | Greek
194+ | Hindi
195+ | Hungarian
196+ | Indonesian
197+ | Irish
198+ | Italian
199+ | Kp
200+ | Latvian
201+ | Lithuanian
202+ | Lovins
203+ | Norwegian
204+ | Persian
205+ | Porter
206+ | Portuguese
207+ | Romanian
208+ | Russian
209+ | Sorani
210+ | Spanish
211+ | Swedish
212+ | Thai
213+ | Turkish
214+ deriving (Eq, Show)
215+
216+instance ToJSON Language where
217+ toJSON = Data.Aeson.String . languageToText
218+
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
223+
224+languageToText :: Language -> Text
225+languageToText x = case x of
226+ Arabic -> "arabic"
227+ Armenian -> "armenian"
228+ Basque -> "basque"
229+ Bengali -> "bengali"
230+ Brazilian -> "brazilian"
231+ Bulgarian -> "bulgarian"
232+ Catalan -> "catalan"
233+ Cjk -> "cjk"
234+ Czech -> "czech"
235+ Danish -> "danish"
236+ Dutch -> "dutch"
237+ English -> "english"
238+ Finnish -> "finnish"
239+ French -> "french"
240+ Galician -> "galician"
241+ German -> "german"
242+ German2 -> "german2"
243+ Greek -> "greek"
244+ Hindi -> "hindi"
245+ Hungarian -> "hungarian"
246+ Indonesian -> "indonesian"
247+ Irish -> "irish"
248+ Italian -> "italian"
249+ Kp -> "kp"
250+ Latvian -> "latvian"
251+ Lithuanian -> "lithuanian"
252+ Lovins -> "lovins"
253+ Norwegian -> "norwegian"
254+ Persian -> "persian"
255+ Porter -> "porter"
256+ Portuguese -> "portuguese"
257+ Romanian -> "romanian"
258+ Russian -> "russian"
259+ Sorani -> "sorani"
260+ Spanish -> "spanish"
261+ Swedish -> "swedish"
262+ Thai -> "thai"
263+ Turkish -> "turkish"
264+
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
274+ "cjk" -> Just Cjk
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
290+ "kp" -> Just Kp
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
303+ "thai" -> Just Thai
304+ "turkish" -> Just Turkish
305+ _ -> Nothing
306+
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)