1######################## BEGIN LICENSE BLOCK ########################
2# The Original Code is Mozilla Communicator client code.
3#
4# The Initial Developer of the Original Code is
5# Netscape Communications Corporation.
6# Portions created by the Initial Developer are Copyright (C) 1998
7# the Initial Developer. All Rights Reserved.
8#
9# Contributor(s):
10# Mark Pilgrim - port to Python
11#
12# This library is free software; you can redistribute it and/or
13# modify it under the terms of the GNU Lesser General Public
14# License as published by the Free Software Foundation; either
15# version 2.1 of the License, or (at your option) any later version.
16#
17# This library is distributed in the hope that it will be useful,
18# but WITHOUT ANY WARRANTY; without even the implied warranty of
19# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20# Lesser General Public License for more details.
21#
22# You should have received a copy of the GNU Lesser General Public
23# License along with this library; if not, see
24# <https://www.gnu.org/licenses/>.
25######################### END LICENSE BLOCK #########################
26
27from typing import Union
28
29from .big5freq import (
30 BIG5_CHAR_TO_FREQ_ORDER,
31 BIG5_TABLE_SIZE,
32 BIG5_TYPICAL_DISTRIBUTION_RATIO,
33)
34from .euckrfreq import (
35 EUCKR_CHAR_TO_FREQ_ORDER,
36 EUCKR_TABLE_SIZE,
37 EUCKR_TYPICAL_DISTRIBUTION_RATIO,
38)
39from .gb2312freq import (
40 GB2312_CHAR_TO_FREQ_ORDER,
41 GB2312_TABLE_SIZE,
42 GB2312_TYPICAL_DISTRIBUTION_RATIO,
43)
44from .jisfreq import (
45 JIS_CHAR_TO_FREQ_ORDER,
46 JIS_TABLE_SIZE,
47 JIS_TYPICAL_DISTRIBUTION_RATIO,
48)
49from .johabfreq import JOHAB_TO_EUCKR_ORDER_TABLE
50
51
52class CharDistributionAnalysis:
53 ENOUGH_DATA_THRESHOLD = 1024
54 SURE_YES = 0.99
55 SURE_NO = 0.01
56 MINIMUM_DATA_THRESHOLD = 3
57
58 def __init__(self) -> None:
59 # Mapping table to get frequency order from char order (get from
60 # GetOrder())
61 self._char_to_freq_order: tuple[int, ...] = tuple()
62 self._table_size = 0 # Size of above table
63 # This is a constant value which varies from language to language,
64 # used in calculating confidence. See
65 # http://www.mozilla.org/projects/intl/UniversalCharsetDetection.html
66 # for further detail.
67 self.typical_distribution_ratio = 0.0
68 self._done = False
69 self._total_chars = 0
70 self._freq_chars = 0
71 self.reset()
72
73 def reset(self) -> None:
74 """reset analyser, clear any state"""
75 # If this flag is set to True, detection is done and conclusion has
76 # been made
77 self._done = False
78 self._total_chars = 0 # Total characters encountered
79 # The number of characters whose frequency order is less than 512
80 self._freq_chars = 0
81
82 def feed(self, char: Union[bytes, bytearray], char_len: int) -> None:
83 """feed a character with known length"""
84 if char_len == 2:
85 # we only care about 2-bytes character in our distribution analysis
86 order = self.get_order(char)
87 else:
88 order = -1
89 if order >= 0:
90 self._total_chars += 1
91 # order is valid
92 if order < self._table_size:
93 if 512 > self._char_to_freq_order[order]:
94 self._freq_chars += 1
95
96 def get_confidence(self) -> float:
97 """return confidence based on existing data"""
98 # if we didn't receive any character in our consideration range,
99 # return negative answer
100 if self._total_chars <= 0 or self._freq_chars <= self.MINIMUM_DATA_THRESHOLD:
101 return self.SURE_NO
102
103 if self._total_chars != self._freq_chars:
104 r = self._freq_chars / (
105 (self._total_chars - self._freq_chars) * self.typical_distribution_ratio
106 )
107 if r < self.SURE_YES:
108 return r
109
110 # normalize confidence (we don't want to be 100% sure)
111 return self.SURE_YES
112
113 def got_enough_data(self) -> bool:
114 # It is not necessary to receive all data to draw conclusion.
115 # For charset detection, certain amount of data is enough
116 return self._total_chars > self.ENOUGH_DATA_THRESHOLD
117
118 def get_order(self, _: Union[bytes, bytearray]) -> int:
119 # We do not handle characters based on the original encoding string,
120 # but convert this encoding string to a number, here called order.
121 # This allows multiple encodings of a language to share one frequency
122 # table.
123 return -1
124
125
126class EUCKRDistributionAnalysis(CharDistributionAnalysis):
127 def __init__(self) -> None:
128 super().__init__()
129 self._char_to_freq_order = EUCKR_CHAR_TO_FREQ_ORDER
130 self._table_size = EUCKR_TABLE_SIZE
131 self.typical_distribution_ratio = EUCKR_TYPICAL_DISTRIBUTION_RATIO
132
133 def get_order(self, byte_str: Union[bytes, bytearray]) -> int: # type: ignore[reportIncompatibleMethodOverride]
134 # for euc-KR encoding, we are interested
135 # first byte range: 0xb0 -- 0xfe
136 # second byte range: 0xa1 -- 0xfe
137 # no validation needed here. State machine has done that
138 first_char = byte_str[0]
139 if first_char >= 0xB0:
140 return 94 * (first_char - 0xB0) + byte_str[1] - 0xA1
141 return -1
142
143
144class JOHABDistributionAnalysis(CharDistributionAnalysis):
145 def __init__(self) -> None:
146 super().__init__()
147 self._char_to_freq_order = EUCKR_CHAR_TO_FREQ_ORDER
148 self._table_size = EUCKR_TABLE_SIZE
149 self.typical_distribution_ratio = EUCKR_TYPICAL_DISTRIBUTION_RATIO
150
151 def get_order(self, byte_str: Union[bytes, bytearray]) -> int: # type: ignore[reportIncompatibleMethodOverride]
152 first_char = byte_str[0]
153 if 0x88 <= first_char < 0xD4:
154 code = first_char * 256 + byte_str[1]
155 return JOHAB_TO_EUCKR_ORDER_TABLE.get(code, -1)
156 return -1
157
158
159class GB2312DistributionAnalysis(CharDistributionAnalysis):
160 def __init__(self) -> None:
161 super().__init__()
162 self._char_to_freq_order = GB2312_CHAR_TO_FREQ_ORDER
163 self._table_size = GB2312_TABLE_SIZE
164 self.typical_distribution_ratio = GB2312_TYPICAL_DISTRIBUTION_RATIO
165
166 def get_order(self, byte_str: Union[bytes, bytearray]) -> int: # type: ignore[reportIncompatibleMethodOverride]
167 # for GB2312 encoding, we are interested
168 # first byte range: 0xb0 -- 0xfe
169 # second byte range: 0xa1 -- 0xfe
170 # no validation needed here. State machine has done that
171 first_char, second_char = byte_str[0], byte_str[1]
172 if (first_char >= 0xB0) and (second_char >= 0xA1):
173 return 94 * (first_char - 0xB0) + second_char - 0xA1
174 return -1
175
176
177class Big5DistributionAnalysis(CharDistributionAnalysis):
178 def __init__(self) -> None:
179 super().__init__()
180 self._char_to_freq_order = BIG5_CHAR_TO_FREQ_ORDER
181 self._table_size = BIG5_TABLE_SIZE
182 self.typical_distribution_ratio = BIG5_TYPICAL_DISTRIBUTION_RATIO
183
184 def get_order(self, byte_str: Union[bytes, bytearray]) -> int: # type: ignore[reportIncompatibleMethodOverride]
185 # for big5 encoding, we are interested
186 # first byte range: 0xa4 -- 0xfe
187 # second byte range: 0x40 -- 0x7e , 0xa1 -- 0xfe
188 # no validation needed here. State machine has done that
189 first_char, second_char = byte_str[0], byte_str[1]
190 if first_char >= 0xA4:
191 if second_char >= 0xA1:
192 return 157 * (first_char - 0xA4) + second_char - 0xA1 + 63
193 return 157 * (first_char - 0xA4) + second_char - 0x40
194 return -1
195
196
197class SJISDistributionAnalysis(CharDistributionAnalysis):
198 def __init__(self) -> None:
199 super().__init__()
200 self._char_to_freq_order = JIS_CHAR_TO_FREQ_ORDER
201 self._table_size = JIS_TABLE_SIZE
202 self.typical_distribution_ratio = JIS_TYPICAL_DISTRIBUTION_RATIO
203
204 def get_order(self, byte_str: Union[bytes, bytearray]) -> int: # type: ignore[reportIncompatibleMethodOverride]
205 # for sjis encoding, we are interested
206 # first byte range: 0x81 -- 0x9f , 0xe0 -- 0xfe
207 # second byte range: 0x40 -- 0x7e, 0x81 -- oxfe
208 # no validation needed here. State machine has done that
209 first_char, second_char = byte_str[0], byte_str[1]
210 if 0x81 <= first_char <= 0x9F:
211 order = 188 * (first_char - 0x81)
212 elif 0xE0 <= first_char <= 0xEF:
213 order = 188 * (first_char - 0xE0 + 31)
214 else:
215 return -1
216 order = order + second_char - 0x40
217 if second_char > 0x7F:
218 order = -1
219 return order
220
221
222class EUCJPDistributionAnalysis(CharDistributionAnalysis):
223 def __init__(self) -> None:
224 super().__init__()
225 self._char_to_freq_order = JIS_CHAR_TO_FREQ_ORDER
226 self._table_size = JIS_TABLE_SIZE
227 self.typical_distribution_ratio = JIS_TYPICAL_DISTRIBUTION_RATIO
228
229 def get_order(self, byte_str: Union[bytes, bytearray]) -> int: # type: ignore[reportIncompatibleMethodOverride]
230 # for euc-JP encoding, we are interested
231 # first byte range: 0xa0 -- 0xfe
232 # second byte range: 0xa1 -- 0xfe
233 # no validation needed here. State machine has done that
234 char = byte_str[0]
235 if char >= 0xA0:
236 return 94 * (char - 0xA1) + byte_str[1] - 0xA1
237 return -1