crawlserv++  [under development]
Application for crawling and analyzing textual content of websites.
German.hpp
Go to the documentation of this file.
1 /*
2  *
3  * ---
4  *
5  * Copyright (C) 2020 Anselm Schmidt (ans[ät]ohai.su)
6  *
7  * This program is free software: you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation, either version 3 of the License, or
10  * (at your option) any later version in addition to the terms of any
11  * licences already herein identified.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program. If not, see <https://www.gnu.org/licenses/>.
20  *
21  * ---
22  *
23  * German.hpp
24  *
25  * Simple German stemmer based on CISTEM by Leonie Weißweiler and Alexander Fraser.
26  *
27  * Original: https://github.com/LeonieWeissweiler/CISTEM
28  *
29  * See:
30  *
31  * Weißweiler, Leonie / Fraser, Alexander: Developing a Stemmer for
32  * German Based on a Comparative Analysis of Publicly Available Stemmers,
33  * in: Proceedings of the German Society for Computational Linguistics and
34  * Language Technology (GSCL), 27th International Conference. Berlin,
35  * September 13–14, 2017.
36  *
37  *
38  * Created on: Aug 2, 2020
39  * Author: ans
40  */
41 
42 #ifndef DATA_STEMMER_GERMAN_HPP_
43 #define DATA_STEMMER_GERMAN_HPP_
44 
45 #include <cstddef> // std::size_t
46 #include <string> // std::string
47 
49 
50  /*
51  * CONSTANTS
52  */
53 
55 
57  inline constexpr auto minLengthStrip2{6};
58 
60  inline constexpr auto minLengthStrip1{4};
61 
63  inline constexpr auto binInv{0xff};
64 
66  inline constexpr auto toLowerCase{32};
67 
69  inline constexpr auto utf8mb2{0xC3};
70 
72  inline constexpr auto utf8mb3{0xE1};
73 
75  inline constexpr auto umlautA2sm{0xA4};
76 
78  inline constexpr auto umlautA2l{0x84};
79 
81  inline constexpr auto umlautO2sm{0xB6};
82 
84  inline constexpr auto umlautO2l{0x96};
85 
87  inline constexpr auto umlautU2sm{0xBC};
88 
90  inline constexpr auto umlautU2l{0x9C};
91 
93  inline constexpr auto sharpS2sm{0x9F};
94 
96  inline constexpr auto sharpS2l{0xBA};
97 
99  inline constexpr auto sharpS3l{0x9E};
100 
102 
103  /*
104  * DECLARATION
105  */
106 
107  void stemGerman(std::string& token);
108 
109  /*
110  * IMPLEMENTATION
111  */
112 
114 
118  inline void stemGerman(std::string& token) {
119  if(token.empty()) {
120  return;
121  }
122 
123  if(token.size() == 1) {
124  switch(token[0]) {
125  // remove punctuation
126  case '"':
127  case '!':
128  case '#':
129  case '$':
130  case '%':
131  case '&':
132  case '\'':
133  case '(':
134  case ')':
135  case '*':
136  case '+':
137  case ',':
138  case '-':
139  case '.':
140  case '/':
141  case ':':
142  case ';':
143  case '<':
144  case '=':
145  case '>':
146  case '?':
147  case '@':
148  case '[':
149  case '\\':
150  case ']':
151  case '^':
152  case '_':
153  case '`':
154  case '{':
155  case '|':
156  case '}':
157  case '~':
158  token.clear();
159 
160  break;
161 
162  // transform to lower-case
163  case 'A':
164  case 'B':
165  case 'C':
166  case 'D':
167  case 'E':
168  case 'F':
169  case 'G':
170  case 'H':
171  case 'I':
172  case 'J':
173  case 'K':
174  case 'L':
175  case 'M':
176  case 'N':
177  case 'O':
178  case 'P':
179  case 'Q':
180  case 'R':
181  case 'S':
182  case 'T':
183  case 'U':
184  case 'V':
185  case 'W':
186  case 'X':
187  case 'Y':
188  case 'Z':
189  token[0] += toLowerCase;
190 
191  break;
192 
193  default:
194  break;
195  }
196 
197  return;
198  }
199 
200  // replace umlauts and sharp s
201  for(std::size_t n{1}; n < token.size(); ++n) {
202  if((binInv & token[n - 1]) == utf8mb2) { //NOLINT(hicpp-signed-bitwise)
203  switch(binInv & token[n]) { //NOLINT(hicpp-signed-bitwise)
204  case umlautA2sm:
205  case umlautA2l:
206  //ä
207  token.erase(n, 1);
208 
209  token[n - 1] = 'a';
210 
211  break;
212 
213  case umlautO2sm:
214  case umlautO2l:
215  //ö
216  token.erase(n, 1);
217 
218  token[n - 1] = 'o';
219 
220  break;
221 
222  case umlautU2sm:
223  case umlautU2l:
224  //ü
225  token.erase(n, 1);
226 
227  token[n - 1] = 'u';
228 
229  break;
230 
231  case sharpS2sm:
232  //ß
233  token[n - 1] = 's';
234  token[n] = 's';
235 
236  ++n;
237 
238  break;
239 
240  default:
241  continue;
242  }
243  }
244  else if(
245  (binInv & token[n - 1]) == utf8mb3 //NOLINT(hicpp-signed-bitwise)
246  && (binInv & token[n]) == sharpS2l //NOLINT(hicpp-signed-bitwise)
247  && (n + 1) < token.size()
248  && (binInv & token[n + 1]) == sharpS3l //NOLINT(hicpp-signed-bitwise)
249  ) {
250  //ẞ
251  token.erase(n, 1);
252 
253  token[n - 1] = 's';
254  token[n] = 's';
255 
256  ++n;
257  }
258  else {
259  switch(token[n - 1]) {
260  // remove punctuation
261  case '"':
262  case '!':
263  case '#':
264  case '$':
265  case '%':
266  case '&':
267  case '\'':
268  case '(':
269  case ')':
270  case '*':
271  case '+':
272  case ',':
273  case '-':
274  case '.':
275  case '/':
276  case ':':
277  case ';':
278  case '<':
279  case '=':
280  case '>':
281  case '?':
282  case '@':
283  case '[':
284  case '\\':
285  case ']':
286  case '^':
287  case '_':
288  case '`':
289  case '{':
290  case '|':
291  case '}':
292  case '~':
293  token.erase(n - 1, 1);
294 
295  --n;
296 
297  break;
298 
299  // transform to lower-case
300  case 'A':
301  case 'B':
302  case 'C':
303  case 'D':
304  case 'E':
305  case 'F':
306  case 'G':
307  case 'H':
308  case 'I':
309  case 'J':
310  case 'K':
311  case 'L':
312  case 'M':
313  case 'N':
314  case 'O':
315  case 'P':
316  case 'Q':
317  case 'R':
318  case 'S':
319  case 'T':
320  case 'U':
321  case 'V':
322  case 'W':
323  case 'X':
324  case 'Y':
325  case 'Z':
326  token[n - 1] += toLowerCase;
327 
328  break;
329 
330  default:
331  continue;
332  }
333  }
334  }
335 
336  // check last character
337  switch(token.back()) {
338  // remove punctuation
339  case '"':
340  case '!':
341  case '#':
342  case '$':
343  case '%':
344  case '&':
345  case '\'':
346  case '(':
347  case ')':
348  case '*':
349  case '+':
350  case ',':
351  case '-':
352  case '.':
353  case '/':
354  case ':':
355  case ';':
356  case '<':
357  case '=':
358  case '>':
359  case '?':
360  case '@':
361  case '[':
362  case '\\':
363  case ']':
364  case '^':
365  case '_':
366  case '`':
367  case '{':
368  case '|':
369  case '}':
370  case '~':
371  token.pop_back();
372 
373  break;
374 
375  // transform to lower-case
376  case 'A':
377  case 'B':
378  case 'C':
379  case 'D':
380  case 'E':
381  case 'F':
382  case 'G':
383  case 'H':
384  case 'I':
385  case 'J':
386  case 'K':
387  case 'L':
388  case 'M':
389  case 'N':
390  case 'O':
391  case 'P':
392  case 'Q':
393  case 'R':
394  case 'S':
395  case 'T':
396  case 'U':
397  case 'V':
398  case 'W':
399  case 'X':
400  case 'Y':
401  case 'Z':
402  token.back() += toLowerCase;
403 
404  break;
405 
406  default:
407  break;
408  }
409 
410  // do not process short tokens any further
411  if(token.length() < minLengthStrip1) {
412  return;
413  }
414 
415  // strip 'ge-' if token is long enough
416  if(
417  token.length() >= minLengthStrip2
418  && token[0] == 'g'
419  && token[1] == 'e'
420  ) {
421  token.erase(0, 2);
422  }
423 
424  // keep important characters sequences
425  std::size_t ignore{};
426 
427  for(std::size_t n{1}; n < token.size(); ++n) {
428  if(token[n - 1] == 'e' && token[n] == 'i') {
429  token[n - 1] = '%';
430  token[n] = '%';
431 
432  ++n;
433  ++ignore;
434  }
435  else if(token[n - 1] == 'i' && token[n] == 'e') {
436  token[n - 1] = '&';
437  token[n] = '&';
438 
439  ++n;
440  ++ignore;
441  }
442  else if(
443  n + 1 < token.size()
444  && token[n - 1] == 's'
445  && token[n] == 'c'
446  && token[n + 1] == 'h'
447  ) {
448  token[n - 1] = '$';
449  token[n] = '$';
450  token[n + 1] = '$';
451 
452  n += 2;
453  ignore += 2;
454  }
455  }
456 
457  // mark double characters
458  char last{};
459 
460  for(auto& c : token) {
461  if(
462  c == last
463  && c != '%'
464  && c != '&'
465  && c != '$'
466  ) {
467  c = '*';
468  last = 0;
469  }
470  else {
471  last = c;
472  }
473  }
474 
475  while(token.size() - ignore >= minLengthStrip1) {
476  const auto indexLast{token.size() - 1};
477 
478  if(token.size() - ignore >= minLengthStrip2) {
479  switch(token[indexLast - 1]) {
480  case 'e':
481  switch(token[indexLast]) {
482  case 'm':
483  case 'r':
484  token.pop_back();
485  token.pop_back();
486 
487  continue;
488 
489  default:
490  break;
491  }
492 
493  break;
494 
495  case 'n':
496  if(token[indexLast] == 'd') {
497  // strip '-nd'
498  token.pop_back();
499  token.pop_back();
500 
501  continue;
502  }
503 
504  break;
505 
506  default:
507  break;
508  }
509  }
510 
511  // strip '-t', '-e', '-s', or '-n'
512  switch(token[token.size() - 1]) {
513  case 't':
514  case 'e':
515  case 's':
516  case 'n':
517  token.pop_back();
518 
519  continue;
520 
521  default:
522  break;
523  }
524 
525  break;
526  }
527 
528  // undo substitutions
529  last = 0;
530 
531  for(auto& c : token) {
532  if(c == '*') {
533  c = last;
534  }
535  else {
536  last = c;
537  }
538  }
539 
540  for(std::size_t n{1}; n < token.size(); ++n) {
541  switch(token[n - 1]) {
542  case '%':
543  token[n - 1] = 'e';
544  token[n] = 'i';
545 
546  ++n;
547 
548  continue;
549 
550  case '&':
551  token[n - 1] = 'i';
552  token[n] = 'e';
553 
554  ++n;
555 
556  continue;
557 
558  case '$':
559  token[n - 1] = 's';
560  token[n] = 'c';
561  token[n + 1] = 'h';
562 
563  n += 2;
564 
565  continue;
566 
567  default:
568  continue;
569  }
570  }
571  }
572 
573 } /* namespace crawlservpp::Data::Stemmer */
574 
575 #endif /* DATA_STEMMER_GERMAN_HPP_ */
constexpr auto binInv
Literal for binary inversion.
Definition: German.hpp:63
constexpr auto utf8mb3
First byte of 3-byte UTF-8 character for capital sharp s.
Definition: German.hpp:72
constexpr auto umlautA2sm
Second byte of UTF-8 umlaut ä.
Definition: German.hpp:75
void stemGerman(std::string &token)
Stems a token in German.
Definition: German.hpp:118
constexpr auto minLengthStrip1
Minimum length of a token to strip one letter from the end.
Definition: German.hpp:60
constexpr auto toLowerCase
Number to add to make uppercase ASCII letters lowercase.
Definition: German.hpp:66
constexpr auto sharpS3l
Third byte of UTF-8 capital sharp s.
Definition: German.hpp:99
constexpr auto sharpS2l
Second byte of UTF-8 capital sharp s.
Definition: German.hpp:96
constexpr auto umlautU2sm
Second byte of UTF-8 umlaut ü.
Definition: German.hpp:87
constexpr auto umlautU2l
Second byte of UTF-8 umlaut Ü.
Definition: German.hpp:90
constexpr auto umlautO2l
Second byte of UTF-8 umlaut Ö.
Definition: German.hpp:84
constexpr auto utf8mb2
First byte of 2-byte UTF-8 characters for umlauts and sharp s.
Definition: German.hpp:69
constexpr auto umlautA2l
Second byte of UTF-8 umlaut Ä.
Definition: German.hpp:78
constexpr auto umlautO2sm
Second byte of UTF-8 umlaut ö.
Definition: German.hpp:81
constexpr auto sharpS2sm
Second byte of UTF-8 sharp s.
Definition: German.hpp:93
constexpr auto minLengthStrip2
Minimum length of a token to strip two letters from the end or the beginning.
Definition: German.hpp:57
Namespace for linguistic stemmers.
Definition: English.hpp:44