root / xap / tags / 0.1.5 / tokenizer / filters.py

Revision 226:7bbf27312bd7, 8.9 kB (checked in by Lafaye Philippe (RAGE2000) <lafaye@…>, 10 months ago)

Add a new version

Line 
1# -*- coding: utf8 -*-
2# Copyright (c) 2007 Tarek Ziadé <tziade@nuxeo.com>
3# This program is free software; you can redistribute it and/or modify
4# it under the terms of the GNU General Public License version 2 as published
5# by the Free Software Foundation.
6#
7# This program is distributed in the hope that it will be useful,
8# but WITHOUT ANY WARRANTY; without even the implied warranty of
9# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10# GNU General Public License for more details.
11#
12# You should have received a copy of the GNU General Public License
13# along with this program; if not, write to the Free Software
14# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
15# 02111-1307, USA.
16#
17import os
18
19__all__ =  ['applyFilter', 'applyFilters', 'AllFilters']
20
21filters = {}
22
23def registerFilter(filter_object):
24    global filters
25    filters[filter_object.getName()] = filter_object
26
27def applyFilter(name, text, options):
28    return filters[name].transform(text, options)
29
30def applyFilters(names, text, options):
31    if options is None:
32        options = {}
33
34    for name in names:
35        text = applyFilter(name, text, options)
36    return text
37
38class AllFilters(object):
39
40    name = 'allfilters'
41
42    def getName(self):
43        return self.name
44
45    def transform(self, text, options):
46        tokenizers = ('normalizer', 'splitter', 'stopwords', 'stemmer')
47        return applyFilters(tokenizers, text, options)
48
49
50class BaseFilter(object):
51
52    def setInitialState(self, text, options):
53        if isinstance(text, list):
54            if len(text) > 0:
55                self.was_str = isinstance(text[0], str)
56            else:
57                self.was_str = False
58        else:
59            self.was_str = isinstance(text, str)
60
61        if 'charset' in options:
62            self.charset = options['charset']
63        else:
64            self.charset = 'utf8'
65
66    def getFinalState(self, result):
67        if isinstance(result, list):
68            return [self.getFinalState(element) for element in result]
69        if isinstance(result, unicode) and self.was_str:
70            return result.encode(self.charset, "replace")
71        elif isinstance(result, str) and not self.was_str:
72            return result.decode(self.charset)
73        return result
74
75class TextSplitter(BaseFilter):
76
77    name = 'splitter'
78
79    char_list = ',;:/\'"#?!.-=+_`|()[]{}<>~&§%'
80    uchar_list = u',;:/\'"#?!.-=+_`|()[]{}<>~&§%'
81
82    def getName(self):
83        return self.name
84
85    def _cleanChar(self, char):
86        """ XXX at this time, we'll just use a small
87            black list we'll see later on adding a real normalizer
88            for each language that does all this in one pass
89        """
90        if char not in self.char_list:
91            return char
92        return ' '
93
94    def transform(self, text, options):
95        # removing unwanted character
96        self.setInitialState(text, options)
97        try:
98            from zopyx.txng3.splitter import Splitter
99            zopyx = True
100        except ImportError:
101            zopyx = False
102
103        if zopyx:
104            if 'treshold' in options:
105                return self.getFinalState([word for word in
106                                           Splitter(singlechar=0).split(text)])
107            else:
108                return self.getFinalState([word for word in
109                                           Splitter().split(text)])
110
111        #text = ''.join([self._cleanChar(char) for char in text])
112        text = ' '.join(text)
113
114        if isinstance(text, unicode):
115            char_list = self.uchar_list
116        else:
117            char_list = self.char_list
118
119        for char in char_list:
120            text = text.replace(char, ' ')
121
122        result = text.split()
123
124        if 'treshold' in options:
125            treshold = options['treshold']
126            return self.getFinalState([word.lower() for word in result
127                                       if len(word) >= treshold])
128        else:
129            return self.getFinalState([word.lower() for word in result])
130
131registerFilter(TextSplitter())
132
133class StopWords(object):
134
135    name = 'stopwords'
136    treshold = 2
137
138    def getName(self):
139        return self.name
140
141    def _getStopWords(self, lang=None):
142        """ simple text file, but will
143        probably move to a DB storage"""
144        currentpath = os.path.dirname(__file__)
145        basefilename = os.path.join(currentpath, 'stopwords.txt')
146        if lang is not None:
147            filename = os.path.join(currentpath, 'stopwords.%s.txt' % lang)
148            if not os.path.exists(filename):
149                filename = basefilename
150        else:
151            filename = basefilename
152
153        return [word.strip() for word in open(filename).readlines()
154                if not (word.startswith('#') or word.strip() == '')]
155
156    def transform(self, text, options):
157        if 'lang' not in options:
158            return text
159
160        if isinstance(text, unicode) or isinstance(text, str):
161            text = text.split()
162
163        treshold = options.get('treshold', self.treshold)
164        lang = options['lang']
165        stopwords = self._getStopWords(lang)
166        return [word for word in text if (word not in stopwords
167                and len(word) > treshold)]
168
169registerFilter(StopWords())
170
171class Normalizer(BaseFilter):
172
173    name = 'normalizer'
174
175    def getName(self):
176        return self.name
177
178    def _getNormalizedChars(self, lang=None):
179        """ simple text file, but will
180        probably move to a DB storage"""
181        currentpath = os.path.dirname(__file__)
182        basefilename = os.path.join(currentpath, 'normalized.txt')
183        if lang is not None:
184            filename = os.path.join(currentpath, 'normalized.%s.txt' % lang)
185            if not os.path.exists(filename):
186                filename = basefilename
187        else:
188            filename = basefilename
189
190        words = [word.strip() for word in open(filename).readlines()
191                 if not (word.startswith('#') or word.strip() == '')]
192
193        result = {}
194        for word in words:
195            splited = word.split()
196            result[splited[0].decode('utf8')] = splited[1]
197        return result
198
199    def _normalize(self, word, normalizer):
200        def normalized(car):
201            if car in normalizer:
202                return normalizer[car]
203            else:
204                return car
205
206        #normalized = [normalized(car) for car in word]
207        for car in normalizer:
208            word = word.replace(car, normalizer[car])
209
210        return word
211        #''.join(normalized)
212
213    def transform(self, text, options):
214        self.setInitialState(text, options)
215
216        if 'lang' not in options:
217            return text
218
219        if isinstance(text, unicode) or isinstance(text, str):
220            text = text.split()
221
222        lang = options['lang']
223        table = self._getNormalizedChars(lang)
224        try:
225            from zopyx.txng3 import normalizer
226            zopyx = True
227        except ImportError:
228            zopyx = False
229
230        if not zopyx:
231            result = [self._normalize(word, table) for word in text]
232        else:
233            result = normalizer.Normalizer(table.items()).normalize(text)
234
235        return self.getFinalState(result)
236
237registerFilter(Normalizer())
238
239class Stemmer(BaseFilter):
240
241    name = 'stemmer'
242    charset = 'utf8'
243
244    def getName(self):
245        return self.name
246
247    def getStemmerLanguage(self, lang):
248        # pystemmer uses its own lang codes
249        # XXX get the real ones
250        langs = {'dn': 'danish', 'dt':'dutch', 'en': 'english',
251                 'fr': 'french', 'de': 'german', 'it': 'italian',
252                 'nw': 'norwegian', 'pr': 'porter',
253                 'pg': 'portuguese', 'ru': 'russian', 'sp': 'spanish',
254                 'sw': 'swedish'}
255        if lang in langs:
256            return langs[lang]
257        return None
258
259    def transform(self, text, options):
260        self.setInitialState(text, options)
261
262        if 'lang' not in options:
263            return text
264
265        if 'charset' not in options:
266            charset = self.charset
267        else:
268            charset = options['charset']
269
270        if isinstance(text, str) or isinstance(text, unicode):
271            text = text.split()
272
273        def right_type(result):
274            if isinstance(result, unicode) and was_str:
275                return result.encode(charset, "replace")
276            elif isinstance(result, str) and not was_str:
277                return result.decode(charset)
278            return result
279
280        def checktype(element):
281            if isinstance(element, str):
282                return element.decode(charset, 'replace')
283            return element
284
285        text = [checktype(element) for element in text]
286
287        try:
288            from zopyx.txng3 import stemmer
289        except ImportError:
290            # module not available
291            return self.getFinalState(text)
292
293        lang = self.getStemmerLanguage(options['lang'])
294        if lang not in stemmer.availableStemmers():
295            return self.getFinalState(text)
296
297        stemmer = stemmer.Stemmer(lang)
298        return self.getFinalState(stemmer.stem(text))
299
300registerFilter(Stemmer())
Note: See TracBrowser for help on using the browser.