Skip to content

Commit 0200ff0

Browse files
committed
Code and readme
1 parent a462cbd commit 0200ff0

File tree

1 file changed

+302
-0
lines changed

1 file changed

+302
-0
lines changed

arabic_reshaper.py

Lines changed: 302 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,302 @@
1+
# -*- coding: utf-8 -*-
2+
3+
# This work is licensed under the GNU Public License (GPL).
4+
# To view a copy of this license, visit http://www.gnu.org/copyleft/gpl.html
5+
6+
# Written by Abd Allah Diab (mpcabd)
7+
# Email: mpcabd ^at^ gmail ^dot^ com
8+
# Website: http://mpcabd.igeex.biz
9+
10+
# Ported and tweaked from Java to Python, from Better Arabic Reshaper [https://github.com/agawish/Better-Arabic-Reshaper/]
11+
12+
import re
13+
14+
DEFINED_CHARACTERS_ORGINAL_ALF_UPPER_MDD = u'\u0622'
15+
DEFINED_CHARACTERS_ORGINAL_ALF_UPPER_HAMAZA = u'\u0623'
16+
DEFINED_CHARACTERS_ORGINAL_ALF_LOWER_HAMAZA = u'\u0625'
17+
DEFINED_CHARACTERS_ORGINAL_ALF = u'\u0627'
18+
DEFINED_CHARACTERS_ORGINAL_LAM = u'\u0644'
19+
20+
LAM_ALEF_GLYPHS = [
21+
[u'\u3BA6', u'\uFEF6', u'\uFEF5'],
22+
[u'\u3BA7', u'\uFEF8', u'\uFEF7'],
23+
[u'\u0627', u'\uFEFC', u'\uFEFB'],
24+
[u'\u0625', u'\uFEFA', u'\uFEF9']
25+
]
26+
27+
HARAKAT = [
28+
u'\u0600', u'\u0601', u'\u0602', u'\u0603', u'\u0606', u'\u0607', u'\u0608', u'\u0609',
29+
u'\u060A', u'\u060B', u'\u060D', u'\u060E', u'\u0610', u'\u0611', u'\u0612', u'\u0613',
30+
u'\u0614', u'\u0615', u'\u0616', u'\u0617', u'\u0618', u'\u0619', u'\u061A', u'\u061B',
31+
u'\u061E', u'\u061F', u'\u0621', u'\u063B', u'\u063C', u'\u063D', u'\u063E', u'\u063F',
32+
u'\u0640', u'\u064B', u'\u064C', u'\u064D', u'\u064E', u'\u064F', u'\u0650', u'\u0651',
33+
u'\u0652', u'\u0653', u'\u0654', u'\u0655', u'\u0656', u'\u0657', u'\u0658', u'\u0659',
34+
u'\u065A', u'\u065B', u'\u065C', u'\u065D', u'\u065E', u'\u0660', u'\u066A', u'\u066B',
35+
u'\u066C', u'\u066F', u'\u0670', u'\u0672', u'\u06D4', u'\u06D5', u'\u06D6', u'\u06D7',
36+
u'\u06D8', u'\u06D9', u'\u06DA', u'\u06DB', u'\u06DC', u'\u06DF', u'\u06E0', u'\u06E1',
37+
u'\u06E2', u'\u06E3', u'\u06E4', u'\u06E5', u'\u06E6', u'\u06E7', u'\u06E8', u'\u06E9',
38+
u'\u06EA', u'\u06EB', u'\u06EC', u'\u06ED', u'\u06EE', u'\u06EF', u'\u06D6', u'\u06D7',
39+
u'\u06D8', u'\u06D9', u'\u06DA', u'\u06DB', u'\u06DC', u'\u06DD', u'\u06DE', u'\u06DF',
40+
u'\u06F0', u'\u06FD', u'\uFE70', u'\uFE71', u'\uFE72', u'\uFE73', u'\uFE74', u'\uFE75',
41+
u'\uFE76', u'\uFE77', u'\uFE78', u'\uFE79', u'\uFE7A', u'\uFE7B', u'\uFE7C', u'\uFE7D',
42+
u'\uFE7E', u'\uFE7F', u'\uFC5E', u'\uFC5F', u'\uFC60', u'\uFC61', u'\uFC62', u'\uFC63'
43+
]
44+
45+
ARABIC_GLYPHS = {
46+
u'\u0622' : [u'\u0622', u'\uFE81', u'\uFE81', u'\uFE82', u'\uFE82', 2],
47+
u'\u0623' : [u'\u0623', u'\uFE83', u'\uFE83', u'\uFE84', u'\uFE84', 2],
48+
u'\u0624' : [u'\u0624', u'\uFE85', u'\uFE85', u'\uFE86', u'\uFE86', 2],
49+
u'\u0625' : [u'\u0625', u'\uFE87', u'\uFE87', u'\uFE88', u'\uFE88', 2],
50+
u'\u0626' : [u'\u0626', u'\uFE89', u'\uFE8B', u'\uFE8C', u'\uFE8A', 4],
51+
u'\u0627' : [u'\u0627', u'\u0627', u'\u0627', u'\uFE8E', u'\uFE8E', 2],
52+
u'\u0628' : [u'\u0628', u'\uFE8F', u'\uFE91', u'\uFE92', u'\uFE90', 4],
53+
u'\u0629' : [u'\u0629', u'\uFE93', u'\uFE93', u'\uFE94', u'\uFE94', 2],
54+
u'\u062A' : [u'\u062A', u'\uFE95', u'\uFE97', u'\uFE98', u'\uFE96', 4],
55+
u'\u062B' : [u'\u062B', u'\uFE99', u'\uFE9B', u'\uFE9C', u'\uFE9A', 4],
56+
u'\u062C' : [u'\u062C', u'\uFE9D', u'\uFE9F', u'\uFEA0', u'\uFE9E', 4],
57+
u'\u062D' : [u'\u062D', u'\uFEA1', u'\uFEA3', u'\uFEA4', u'\uFEA2', 4],
58+
u'\u062E' : [u'\u062E', u'\uFEA5', u'\uFEA7', u'\uFEA8', u'\uFEA6', 4],
59+
u'\u062F' : [u'\u062F', u'\uFEA9', u'\uFEA9', u'\uFEAA', u'\uFEAA', 2],
60+
u'\u0630' : [u'\u0630', u'\uFEAB', u'\uFEAB', u'\uFEAC', u'\uFEAC', 2],
61+
u'\u0631' : [u'\u0631', u'\uFEAD', u'\uFEAD', u'\uFEAE', u'\uFEAE', 2],
62+
u'\u0632' : [u'\u0632', u'\uFEAF', u'\uFEAF', u'\uFEB0', u'\uFEB0', 2],
63+
u'\u0633' : [u'\u0633', u'\uFEB1', u'\uFEB3', u'\uFEB4', u'\uFEB2', 4],
64+
u'\u0634' : [u'\u0634', u'\uFEB5', u'\uFEB7', u'\uFEB8', u'\uFEB6', 4],
65+
u'\u0635' : [u'\u0635', u'\uFEB9', u'\uFEBB', u'\uFEBC', u'\uFEBA', 4],
66+
u'\u0636' : [u'\u0636', u'\uFEBD', u'\uFEBF', u'\uFEC0', u'\uFEBE', 4],
67+
u'\u0637' : [u'\u0637', u'\uFEC1', u'\uFEC3', u'\uFEC4', u'\uFEC2', 4],
68+
u'\u0638' : [u'\u0638', u'\uFEC5', u'\uFEC7', u'\uFEC8', u'\uFEC6', 4],
69+
u'\u0639' : [u'\u0639', u'\uFEC9', u'\uFECB', u'\uFECC', u'\uFECA', 4],
70+
u'\u063A' : [u'\u063A', u'\uFECD', u'\uFECF', u'\uFED0', u'\uFECE', 4],
71+
u'\u0641' : [u'\u0641', u'\uFED1', u'\uFED3', u'\uFED4', u'\uFED2', 4],
72+
u'\u0642' : [u'\u0642', u'\uFED5', u'\uFED7', u'\uFED8', u'\uFED6', 4],
73+
u'\u0643' : [u'\u0643', u'\uFED9', u'\uFEDB', u'\uFEDC', u'\uFEDA', 4],
74+
u'\u0644' : [u'\u0644', u'\uFEDD', u'\uFEDF', u'\uFEE0', u'\uFEDE', 4],
75+
u'\u0645' : [u'\u0645', u'\uFEE1', u'\uFEE3', u'\uFEE4', u'\uFEE2', 4],
76+
u'\u0646' : [u'\u0646', u'\uFEE5', u'\uFEE7', u'\uFEE8', u'\uFEE6', 4],
77+
u'\u0647' : [u'\u0647', u'\uFEE9', u'\uFEEB', u'\uFEEC', u'\uFEEA', 4],
78+
u'\u0648' : [u'\u0648', u'\uFEED', u'\uFEED', u'\uFEEE', u'\uFEEE', 2],
79+
u'\u0649' : [u'\u0649', u'\uFEEF', u'\uFEEF', u'\uFEF0', u'\uFEF0', 2],
80+
u'\u0671' : [u'\u0671', u'\u0671', u'\u0671', u'\uFB51', u'\uFB51', 2],
81+
u'\u064A' : [u'\u064A', u'\uFEF1', u'\uFEF3', u'\uFEF4', u'\uFEF2', 4],
82+
u'\u066E' : [u'\u066E', u'\uFBE4', u'\uFBE8', u'\uFBE9', u'\uFBE5', 4],
83+
u'\u06AA' : [u'\u06AA', u'\uFB8E', u'\uFB90', u'\uFB91', u'\uFB8F', 4],
84+
u'\u06C1' : [u'\u06C1', u'\uFBA6', u'\uFBA8', u'\uFBA9', u'\uFBA7', 4],
85+
u'\u06E4' : [u'\u06E4', u'\u06E4', u'\u06E4', u'\u06E4', u'\uFEEE', 2]
86+
}
87+
88+
ARABIC_GLYPHS_LIST = [
89+
[u'\u0622', u'\uFE81', u'\uFE81', u'\uFE82', u'\uFE82', 2],
90+
[u'\u0623', u'\uFE83', u'\uFE83', u'\uFE84', u'\uFE84', 2],
91+
[u'\u0624', u'\uFE85', u'\uFE85', u'\uFE86', u'\uFE86', 2],
92+
[u'\u0625', u'\uFE87', u'\uFE87', u'\uFE88', u'\uFE88', 2],
93+
[u'\u0626', u'\uFE89', u'\uFE8B', u'\uFE8C', u'\uFE8A', 4],
94+
[u'\u0627', u'\u0627', u'\u0627', u'\uFE8E', u'\uFE8E', 2],
95+
[u'\u0628', u'\uFE8F', u'\uFE91', u'\uFE92', u'\uFE90', 4],
96+
[u'\u0629', u'\uFE93', u'\uFE93', u'\uFE94', u'\uFE94', 2],
97+
[u'\u062A', u'\uFE95', u'\uFE97', u'\uFE98', u'\uFE96', 4],
98+
[u'\u062B', u'\uFE99', u'\uFE9B', u'\uFE9C', u'\uFE9A', 4],
99+
[u'\u062C', u'\uFE9D', u'\uFE9F', u'\uFEA0', u'\uFE9E', 4],
100+
[u'\u062D', u'\uFEA1', u'\uFEA3', u'\uFEA4', u'\uFEA2', 4],
101+
[u'\u062E', u'\uFEA5', u'\uFEA7', u'\uFEA8', u'\uFEA6', 4],
102+
[u'\u062F', u'\uFEA9', u'\uFEA9', u'\uFEAA', u'\uFEAA', 2],
103+
[u'\u0630', u'\uFEAB', u'\uFEAB', u'\uFEAC', u'\uFEAC', 2],
104+
[u'\u0631', u'\uFEAD', u'\uFEAD', u'\uFEAE', u'\uFEAE', 2],
105+
[u'\u0632', u'\uFEAF', u'\uFEAF', u'\uFEB0', u'\uFEB0', 2],
106+
[u'\u0633', u'\uFEB1', u'\uFEB3', u'\uFEB4', u'\uFEB2', 4],
107+
[u'\u0634', u'\uFEB5', u'\uFEB7', u'\uFEB8', u'\uFEB6', 4],
108+
[u'\u0635', u'\uFEB9', u'\uFEBB', u'\uFEBC', u'\uFEBA', 4],
109+
[u'\u0636', u'\uFEBD', u'\uFEBF', u'\uFEC0', u'\uFEBE', 4],
110+
[u'\u0637', u'\uFEC1', u'\uFEC3', u'\uFEC4', u'\uFEC2', 4],
111+
[u'\u0638', u'\uFEC5', u'\uFEC7', u'\uFEC8', u'\uFEC6', 4],
112+
[u'\u0639', u'\uFEC9', u'\uFECB', u'\uFECC', u'\uFECA', 4],
113+
[u'\u063A', u'\uFECD', u'\uFECF', u'\uFED0', u'\uFECE', 4],
114+
[u'\u0641', u'\uFED1', u'\uFED3', u'\uFED4', u'\uFED2', 4],
115+
[u'\u0642', u'\uFED5', u'\uFED7', u'\uFED8', u'\uFED6', 4],
116+
[u'\u0643', u'\uFED9', u'\uFEDB', u'\uFEDC', u'\uFEDA', 4],
117+
[u'\u0644', u'\uFEDD', u'\uFEDF', u'\uFEE0', u'\uFEDE', 4],
118+
[u'\u0645', u'\uFEE1', u'\uFEE3', u'\uFEE4', u'\uFEE2', 4],
119+
[u'\u0646', u'\uFEE5', u'\uFEE7', u'\uFEE8', u'\uFEE6', 4],
120+
[u'\u0647', u'\uFEE9', u'\uFEEB', u'\uFEEC', u'\uFEEA', 4],
121+
[u'\u0648', u'\uFEED', u'\uFEED', u'\uFEEE', u'\uFEEE', 2],
122+
[u'\u0649', u'\uFEEF', u'\uFEEF', u'\uFEF0', u'\uFEF0', 2],
123+
[u'\u0671', u'\u0671', u'\u0671', u'\uFB51', u'\uFB51', 2],
124+
[u'\u064A', u'\uFEF1', u'\uFEF3', u'\uFEF4', u'\uFEF2', 4],
125+
[u'\u066E', u'\uFBE4', u'\uFBE8', u'\uFBE9', u'\uFBE5', 4],
126+
[u'\u06AA', u'\uFB8E', u'\uFB90', u'\uFB91', u'\uFB8F', 4],
127+
[u'\u06C1', u'\uFBA6', u'\uFBA8', u'\uFBA9', u'\uFBA7', 4],
128+
[u'\u06E4', u'\u06E4', u'\u06E4', u'\u06E4', u'\uFEEE', 2]
129+
]
130+
131+
def get_reshaped_glyph(target, location):
132+
if ARABIC_GLYPHS.has_key(target):
133+
return ARABIC_GLYPHS[target][location]
134+
else:
135+
return target
136+
137+
def get_glyph_type(target):
138+
if ARABIC_GLYPHS.has_key(target):
139+
return ARABIC_GLYPHS[target][5]
140+
else:
141+
return 2
142+
143+
def is_haraka(target):
144+
return target in HARAKAT
145+
146+
def replace_lam_alef(unshaped_word):
147+
list_word = list(unshaped_word)
148+
letter_before = u''
149+
for i in range(len(unshaped_word)):
150+
if not is_haraka(unshaped_word[i]) and unshaped_word[i] != DEFINED_CHARACTERS_ORGINAL_LAM:
151+
letter_before = unshaped_word[i]
152+
153+
if unshaped_word[i] == DEFINED_CHARACTERS_ORGINAL_LAM:
154+
candidate_lam = unshaped_word[i]
155+
lam_position = i
156+
haraka_position = i + 1
157+
158+
while haraka_position < len(unshaped_word) and is_haraka(unshaped_word[haraka_position]):
159+
haraka_position += 1
160+
161+
if haraka_position < len(unshaped_word):
162+
if lam_position > 0 and get_glyph_type(letter_before) > 2:
163+
lam_alef = get_lam_alef(list_word[haraka_position], candidate_lam, False)
164+
else:
165+
lam_alef = get_lam_alef(list_word[haraka_position], candidate_lam, True)
166+
if lam_alef != '':
167+
list_word[lam_position] = lam_alef
168+
list_word[haraka_position] = u' '
169+
170+
return u''.join(list_word).replace(u' ', u'')
171+
172+
def get_lam_alef(candidate_alef, candidate_lam, is_end_of_word):
173+
shift_rate = 1
174+
reshaped_lam_alef = u''
175+
if is_end_of_word:
176+
shift_rate += 1
177+
178+
if DEFINED_CHARACTERS_ORGINAL_LAM == candidate_lam:
179+
if DEFINED_CHARACTERS_ORGINAL_ALF_UPPER_MDD == candidate_alef:
180+
reshaped_lam_alef = LAM_ALEF_GLYPHS[0][shift_rate]
181+
182+
if DEFINED_CHARACTERS_ORGINAL_ALF_UPPER_HAMAZA == candidate_alef:
183+
reshaped_lam_alef = LAM_ALEF_GLYPHS[1][shift_rate]
184+
185+
if DEFINED_CHARACTERS_ORGINAL_ALF == candidate_alef:
186+
reshaped_lam_alef = LAM_ALEF_GLYPHS[2][shift_rate]
187+
188+
if DEFINED_CHARACTERS_ORGINAL_ALF_LOWER_HAMAZA == candidate_alef:
189+
reshaped_lam_alef = LAM_ALEF_GLYPHS[3][shift_rate]
190+
191+
return reshaped_lam_alef
192+
193+
class DecomposedWord(object):
194+
def __init__(self, word):
195+
self.stripped_harakat = []
196+
self.harakat_positions = []
197+
self.stripped_regular_letters = []
198+
self.letters_position = []
199+
200+
for i in range(len(word)):
201+
c = word[i]
202+
if is_haraka(c):
203+
self.harakat_positions.append(i)
204+
self.stripped_harakat.append(c)
205+
else:
206+
self.letters_position.append(i)
207+
self.stripped_regular_letters.append(c)
208+
209+
def reconstruct_word(self, reshaped_word):
210+
l = list(u'\0' * (len(self.stripped_harakat) + len(reshaped_word)))
211+
for i in range(len(self.letters_position)):
212+
l[self.letters_position[i]] = reshaped_word[i]
213+
for i in range(len(self.harakat_positions)):
214+
l[self.harakat_positions[i]] = self.stripped_harakat[i]
215+
return u''.join(l)
216+
217+
def get_reshaped_word(unshaped_word):
218+
unshaped_word = replace_lam_alef(unshaped_word)
219+
decomposed_word = DecomposedWord(unshaped_word)
220+
result = u''
221+
if decomposed_word.stripped_regular_letters:
222+
result = reshape_it(u''.join(decomposed_word.stripped_regular_letters))
223+
return decomposed_word.reconstruct_word(result)
224+
225+
def reshape_it(unshaped_word):
226+
reshaped_word = [get_reshaped_glyph(unshaped_word[0], 2)]
227+
for i in range(1, len(unshaped_word) - 1):
228+
if get_glyph_type(unshaped_word[i - 1]) == 2:
229+
reshaped_word.append(get_reshaped_glyph(unshaped_word[i], 2))
230+
else:
231+
reshaped_word.append(get_reshaped_glyph(unshaped_word[i], 3))
232+
233+
if len(unshaped_word) >= 2:
234+
if get_glyph_type(unshaped_word[-2]) == 2:
235+
reshaped_word.append(get_reshaped_glyph(unshaped_word[-1], 1))
236+
else:
237+
reshaped_word.append(get_reshaped_glyph(unshaped_word[-1], 4))
238+
239+
return u''.join(reshaped_word)
240+
241+
242+
def is_arabic_character(target):
243+
return ARABIC_GLYPHS.has_key(target) or target in HARAKAT
244+
245+
def get_words(sentence):
246+
if sentence:
247+
return re.split('\\s', sentence)
248+
return []
249+
250+
def has_arabic_letters(word):
251+
for c in word:
252+
if is_arabic_character(c):
253+
return True
254+
return False
255+
256+
def is_arabic_word(word):
257+
for c in word:
258+
if not is_arabic_character(c):
259+
return False
260+
return True
261+
262+
def get_words_from_mixed_word(word):
263+
temp_word = u''
264+
words = []
265+
for c in word:
266+
if is_arabic_character(c):
267+
if temp_word and not is_arabic_word(temp_word):
268+
words.append(temp_word)
269+
temp_word = c
270+
else:
271+
temp_word += c
272+
else:
273+
if temp_word and is_arabic_word(temp_word):
274+
words.append(temp_word)
275+
temp_word = c
276+
else:
277+
temp_word += c
278+
if temp_word:
279+
words.append(temp_word)
280+
return words
281+
282+
def reshape(text):
283+
if text:
284+
lines = re.split('\\r?\\n', text)
285+
for i in range(len(lines)):
286+
lines[i] = reshape_sentence(lines[i])
287+
return u'\n'.join(lines)
288+
return u''
289+
290+
def reshape_sentence(sentence):
291+
words = get_words(sentence)
292+
for i in range(len(words)):
293+
word = words[i]
294+
if has_arabic_letters(word):
295+
if is_arabic_word(word):
296+
words[i] = get_reshaped_word(word)
297+
else:
298+
mixed_words = get_words_from_mixed_word(word)
299+
for j in range(len(mixed_words)):
300+
mixed_words[j] = get_reshaped_word(mixed_words[j])
301+
words[i] = u''.join(mixed_words)
302+
return u' '.join(words)

0 commit comments

Comments
 (0)