Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/sacremoses/normalize.py: 76%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
4import re
5import regex
7from itertools import chain
10class MosesPunctNormalizer:
11 """
12 This is a Python port of the Moses punctuation normalizer from
13 https://github.com/moses-smt/mosesdecoder/blob/master/scripts/tokenizer/normalize-punctuation.perl
14 """
16 EXTRA_WHITESPACE = [ # lines 21 - 30
17 (r"\r", r""),
18 (r"\(", r" ("),
19 (r"\)", r") "),
20 (r" +", r" "),
21 (r"\) ([.!:?;,])", r")\g<1>"),
22 (r"\( ", r"("),
23 (r" \)", r")"),
24 (r"(\d) %", r"\g<1>%"),
25 (r" :", r":"),
26 (r" ;", r";"),
27 ]
29 NORMALIZE_UNICODE_IF_NOT_PENN = [(r"`", r"'"), (r"''", r' " ')] # lines 33 - 34
31 NORMALIZE_UNICODE = [ # lines 37 - 50
32 ("„", r'"'),
33 ("“", r'"'),
34 ("”", r'"'),
35 ("–", r"-"),
36 ("—", r" - "),
37 (r" +", r" "),
38 ("´", r"'"),
39 ("([a-zA-Z])‘([a-zA-Z])", r"\g<1>'\g<2>"),
40 ("([a-zA-Z])’([a-zA-Z])", r"\g<1>'\g<2>"),
41 ("‘", r"'"),
42 ("‚", r"'"),
43 ("’", r"'"),
44 (r"''", r'"'),
45 ("´´", r'"'),
46 ("…", r"..."),
47 ]
49 FRENCH_QUOTES = [ # lines 52 - 57
50 ("\u00A0«\u00A0", r'"'),
51 ("«\u00A0", r'"'),
52 ("«", r'"'),
53 ("\u00A0»\u00A0", r'"'),
54 ("\u00A0»", r'"'),
55 ("»", r'"'),
56 ]
58 HANDLE_PSEUDO_SPACES = [ # lines 59 - 67
59 ("\u00A0%", r"%"),
60 ("nº\u00A0", "nº "),
61 ("\u00A0:", r":"),
62 ("\u00A0ºC", " ºC"),
63 ("\u00A0cm", r" cm"),
64 ("\u00A0\\?", "?"),
65 ("\u00A0\\!", "!"),
66 ("\u00A0;", r";"),
67 (",\u00A0", r", "),
68 (r" +", r" "),
69 ]
71 EN_QUOTATION_FOLLOWED_BY_COMMA = [(r'"([,.]+)', r'\g<1>"')]
73 DE_ES_FR_QUOTATION_FOLLOWED_BY_COMMA = [
74 (r',"', r'",'),
75 (r'(\.+)"(\s*[^<])', r'"\g<1>\g<2>'), # don't fix period at end of sentence
76 ]
78 DE_ES_CZ_CS_FR = [
79 ("(\\d)\u00A0(\\d)", r"\g<1>,\g<2>"),
80 ]
82 OTHER = [
83 ("(\\d)\u00A0(\\d)", r"\g<1>.\g<2>"),
84 ]
86 # Regex substitutions from replace-unicode-punctuation.perl
87 # https://github.com/moses-smt/mosesdecoder/blob/master/scripts/tokenizer/replace-unicode-punctuation.perl
88 REPLACE_UNICODE_PUNCTUATION = [
89 (",", ","),
90 (r"。\s*", ". "),
91 ("、", ","),
92 ("”", '"'),
93 ("“", '"'),
94 ("∶", ":"),
95 (":", ":"),
96 ("?", "?"),
97 ("《", '"'),
98 ("》", '"'),
99 (")", ")"),
100 ("!", "!"),
101 ("(", "("),
102 (";", ";"),
103 ("」", '"'),
104 ("「", '"'),
105 ("0", "0"),
106 ("1", "1"),
107 ("2", "2"),
108 ("3", "3"),
109 ("4", "4"),
110 ("5", "5"),
111 ("6", "6"),
112 ("7", "7"),
113 ("8", "8"),
114 ("9", "9"),
115 (r".\s*", ". "),
116 ("~", "~"),
117 ("’", "'"),
118 ("…", "..."),
119 ("━", "-"),
120 ("〈", "<"),
121 ("〉", ">"),
122 ("【", "["),
123 ("】", "]"),
124 ("%", "%"),
125 ]
127 def __init__(
128 self,
129 lang="en",
130 penn=True,
131 norm_quote_commas=True,
132 norm_numbers=True,
133 pre_replace_unicode_punct=False,
134 post_remove_control_chars=False,
135 perl_parity=False
136 ):
137 """
138 :param language: The two-letter language code.
139 :type lang: str
140 :param penn: Normalize Penn Treebank style quotations.
141 :type penn: bool
142 :param norm_quote_commas: Normalize quotations and commas
143 :type norm_quote_commas: bool
144 :param norm_numbers: Normalize numbers
145 :type norm_numbers: bool
146 :param perl_parity: exact parity with perl script
147 :type perl_parity: bool
148 """
150 if perl_parity:
151 # Copy first: these are class attributes, so assigning into them
152 # in place would rewrite the defaults for every other normalizer in
153 # the process, including ones built with perl_parity=False.
154 self.NORMALIZE_UNICODE = list(self.NORMALIZE_UNICODE)
155 self.FRENCH_QUOTES = list(self.FRENCH_QUOTES)
156 self.NORMALIZE_UNICODE[11] = ("’", r'"')
157 self.FRENCH_QUOTES[0] = ("\u00A0«\u00A0", r' "')
158 self.FRENCH_QUOTES[3] = ("\u00A0»\u00A0", r'" ')
160 self.substitutions = [
161 self.EXTRA_WHITESPACE,
162 self.NORMALIZE_UNICODE,
163 self.FRENCH_QUOTES,
164 self.HANDLE_PSEUDO_SPACES,
165 ]
167 if penn: # Adds the penn substitutions after extra_whitespace regexes.
168 self.substitutions.insert(1, self.NORMALIZE_UNICODE_IF_NOT_PENN)
170 if norm_quote_commas:
171 if lang == "en":
172 self.substitutions.append(self.EN_QUOTATION_FOLLOWED_BY_COMMA)
173 elif lang in ["de", "es", "fr"]:
174 self.substitutions.append(self.DE_ES_FR_QUOTATION_FOLLOWED_BY_COMMA)
176 if norm_numbers:
177 if lang in ["de", "es", "cz", "cs", "fr"]:
178 self.substitutions.append(self.DE_ES_CZ_CS_FR)
179 else:
180 self.substitutions.append(self.OTHER)
182 self.substitutions = list(chain(*self.substitutions))
184 self.pre_replace_unicode_punct = pre_replace_unicode_punct
185 self.post_remove_control_chars = post_remove_control_chars
187 def normalize(self, text):
188 """
189 Returns a string with normalized punctuation.
190 """
191 # Optionally, replace unicode puncts BEFORE normalization.
192 if self.pre_replace_unicode_punct:
193 text = self.replace_unicode_punct(text)
195 # Actual normalization.
196 for regexp, substitution in self.substitutions:
197 # print(regexp, substitution)
198 text = re.sub(regexp, substitution, str(text))
199 # print(text)
201 # Optionally, replace unicode puncts BEFORE normalization.
202 if self.post_remove_control_chars:
203 text = self.remove_control_chars(text)
205 return text.strip()
207 def replace_unicode_punct(self, text):
208 for regexp, substitution in self.REPLACE_UNICODE_PUNCTUATION:
209 text = re.sub(regexp, substitution, str(text))
210 return text
212 def remove_control_chars(self, text):
213 return regex.sub(r"\p{C}", "", text)