Imported version 0.5
[decoratedstr.git] / decoratedstr.py
1 # -*- encoding: utf-8 -*-
2
3 from __future__ import print_function, unicode_literals
4 import six
5
6 __all__ = ['remove_decoration', 'decorated_match']
7
8 # for range \u00c0 \u0179
9 char_to_alternatives_lower = {
10     'a': 'àáâãäåāăą',
11     'c': 'çćĉċč',
12     'd': 'ďđ',
13     'e': 'èéêëēĕėęě',
14     'g': 'ĝğġģ',
15     'h': 'ĥħ',
16     'i': 'ìíîïĩīĭįı',
17     'j': 'ĵ',
18     'k': 'ķ',
19     'l': 'ĺļľŀł',
20     'n': 'ñńņňʼnŋ',
21     'o': 'òóôöøōŏő',
22     'r': 'ŕŗř',
23     's': 'śŝşš',
24     't': 'ţťŧ',
25     'u': 'ùúûüũūŭůűų',
26     'w': 'ŵ',
27     'y': 'ýÿŷ',
28     'z': 'źżž',
29 }
30
31 # This chars lower() function doesn't work
32 char_to_alternatives_upper = {
33     'I': 'İ',
34 }
35 char_to_alternatives = {} # idem, but with upper case too
36 for char, alternatives in six.iteritems(char_to_alternatives_lower):
37     char_to_alternatives[char] = alternatives
38     char_to_alternatives[char.upper()] = alternatives.upper()
39 for char, alternatives in six.iteritems(char_to_alternatives_upper):
40     char_to_alternatives[char] = alternatives
41
42 alternative_to_char = {} # reverse
43 for char, alternatives in six.iteritems(char_to_alternatives_lower):
44     for alternative in alternatives:
45         alternative_to_char[alternative] = char
46         alternative_to_char[alternative.upper()] = char.upper()
47 for char, alternatives in six.iteritems(char_to_alternatives_upper):
48     for alternative in alternatives:
49         alternative_to_char[alternative] = char
50
51 # ligatures (only two chars supported)
52 ligatures_expansions_lower = {
53     'æ': 'ae',
54     # 'ij': 'ij', buggy: see http://en.wikipedia.org/wiki/Typographic_ligature
55     'œ': 'oe',
56 }
57
58 ligatures_expansions = {} # idem, but with upper case too
59 for ligature, expansion in six.iteritems(ligatures_expansions_lower):
60     ligatures_expansions[ligature] = expansion
61     ligatures_expansions[ligature.upper()] = expansion[0].upper()+expansion[1:]
62
63 ligatures_contractions = {} # reverse
64 for ligature, expansion in six.iteritems(ligatures_expansions_lower):
65     ligatures_contractions[expansion] = ligature
66     ligatures_contractions[expansion[0].upper()+expansion[1:]] = ligature.upper()
67
68 def remove_decoration(txt):
69     result = ''
70     for l in txt:
71         l = alternative_to_char.get(l, l)
72         l = ligatures_expansions.get(l, l)
73         result += l
74     return result
75
76 def decorated_match_single_char(c, casesensitive=False):
77     assert type(c) == type('')
78     if not casesensitive:
79         c = c.lower()
80     result = c + char_to_alternatives.get(c, '')
81     if not casesensitive:
82         u = result.upper()
83         if result != u:
84             result += u
85     if len(result) > 1:
86         return '['+result+']'
87     else:
88         return result
89
90 def decorated_match(txt, casesensitive=False):
91     assert type(txt) == type('')
92     result = ''
93     txt = remove_decoration(txt)
94     if not casesensitive:
95         txt = txt.lower()
96     i = 0
97     while i < len(txt):
98         c1 = txt[i] # current character
99         c12 = txt[i:i+2] # both current and next characters. Contains a single char on last iteration so that it never matches, that is OK
100         ligature = ligatures_contractions.get(c12, None)
101         if ligature:
102             result += '('+ligature
103             if not casesensitive:
104                 result += '|'+ligature.upper()
105             result += '|'+decorated_match_single_char(c12[0], casesensitive) \
106                           +decorated_match_single_char(c12[1], casesensitive) \
107                    +')'
108             i += 1 # skip next character, we allready did both
109         else:
110             result += decorated_match_single_char(c1, casesensitive)
111         i += 1
112     return result
113
114
115 if __name__ == '__main__':
116     import sys
117     from optparse import OptionParser
118     parser = OptionParser(usage='%prog [options] string')
119     parser.add_option('--charset', help="set charset. default=%default", action='store', dest='charset', default='utf-8')
120     parser.add_option('-r', '--regexp', help="generate regular expression.", action='store_true', dest='regexp')
121     parser.add_option('-i', help="used with -r, make regexp case insensitive.", action='store_false', dest='casesensitive', default=True)
122     (options, args) = parser.parse_args()
123
124     if not args:
125         print('Missing required parameter. Try "Œuf"', file=sys.stderr)
126         sys.exit(1)
127     if six.PY3:
128         input = ' '.join(args)
129     else:
130         input = unicode(b' '.join(args), options.charset)
131     #print("input:", input)                            # Œuf
132     #print("undecorated:", remove_decoration(input))   # Oeuf
133     #print("regex:", decorated_match(input))           # (œ|Œ|[oòóôöøōŏőOÒÓÔÖØŌŎŐ][eèéêëēĕėęěEÈÉÊËĒĔĖĘĚ])[uùúûüũūŭůűųUÙÚÛÜŨŪŬŮŰŲ][fF]
134     if options.regexp:
135         if six.PY3:
136             print(decorated_match(input, options.casesensitive))
137         else:
138             print(decorated_match(input, options.casesensitive).encode(options.charset))
139     else:
140         if six.PY3:
141             print(remove_decoration(input))
142         else:
143             print(remove_decoration(input).encode(options.charset))