Repository: jitsi/jiwer

Issue:
RemoveSpecificWords is not functioning as expected
Hi! As the title says, the `RemoveSpecificWords` function does not work as I would expect it to. As an example, the following code

```
text = "he asked a helpful question"
    stop_words = ['a', 'he']
    print(jiwer.Compose([
        jiwer.ToLowerCase(),
        jiwer.RemoveMultipleSpaces(),
        jiwer.Strip(),
        jiwer.SentencesToListOfWords(),
        jiwer.RemoveEmptyStrings(),
        jiwer.RemovePunctuation(),
        jiwer.RemoveSpecificWords(stop_words),
    ])(text))
```

returns 

`['', 'sked', '', 'lpful', 'question']`

Is there a way to get this function recognize word boundaries? Thank you!

Discussion:

Patch:
diff --git a/README.md b/README.md
index 0b1633b..c2ccc60 100644
--- a/README.md
+++ b/README.md
@@ -176,6 +176,8 @@ print(jiwer.ReduceToSingleSentence()(sentences))
 #### RemoveSpecificWords
 
 `jiwer.RemoveSpecificWords(words_to_remove: List[str])` can be used to filter out certain words.
+As words are replaced with a ` ` character, make sure to that `jiwer.RemoveMultipleSpaces`, 
+`jiwer.Strip()` and `jiwer.RemoveEmptyStrings` are present in the composition _after_  `jiwer.RemoveSpecificWords`.
 
 Example:
 ```python
@@ -184,7 +186,8 @@ import jiwer
 sentences = ["yhe awesome", "the apple is not a pear", "yhe"]
 
 print(jiwer.RemoveSpecificWords(["yhe", "the", "a"])(sentences))
-# prints: ["awesome", "apple is pear", ""]
+# prints: ['  awesome', '  apple is not   pear', ' ']
+# note the extra spaces
 ```
 
 #### RemoveWhiteSpace
@@ -194,6 +197,9 @@ The whitespace characters are ` `, `\t`, `\n`, `\r`, `\x0b` and `\x0c`.
 Note that by default space (` `) is also removed, which will make it impossible to split a sentence into a list of words by using `ReduceToListOfListOfWords` 
 or `ReduceToSingleSentence`.
 This can be prevented by replacing all whitespace with the space character. 
+If so, make sure that `jiwer.RemoveMultipleSpaces`, 
+`jiwer.Strip()` and `jiwer.RemoveEmptyStrings` are present in the composition _after_  `jiwer.RemoveWhiteSpace`.
+
 
 Example:
 ```python
@@ -211,9 +217,8 @@ print(jiwer.RemoveWhiteSpace(replace_by_space=True)(sentences))
 
 #### RemovePunctuation
 
-`jiwer.RemovePunctuation()` can be used to filter out punctuation. The punctuation characters are:
-
-``'!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~'``
+`jiwer.RemovePunctuation()` can be used to filter out punctuation. The punctuation characters are defined as
+all unicode characters whose catogary name starts with `P`. See https://www.unicode.org/reports/tr44/#General_Category_Values. 
 
 Example:
 ```python
diff --git a/jiwer/transforms.py b/jiwer/transforms.py
index 1f717bb..e00abfe 100644
--- a/jiwer/transforms.py
+++ b/jiwer/transforms.py
@@ -21,8 +21,10 @@ This file implements the building blocks for transforming a collection
 of input strings to the desired format in order to calculate the WER.
 """
 
+import sys
 import re
 import string
+import unicodedata
 
 from typing import Union, List, Mapping
 
@@ -170,9 +172,33 @@ class ReduceToSingleSentence(AbstractTransform):
             return ["{}".format(self.word_delimiter).join(filtered_inp)]
 
 
-class RemoveSpecificWords(BaseRemoveTransform):
+class SubstituteRegexes(AbstractTransform):
+    def __init__(self, substitutions: Mapping[str, str]):
+        self.substitutions = substitutions
+
+    def process_string(self, s: str):
+        for key, value in self.substitutions.items():
+            s = re.sub(key, value, s)
+
+        return s
+
+
+class SubstituteWords(AbstractTransform):
+    def __init__(self, substitutions: Mapping[str, str]):
+        self.substitutions = substitutions
+
+    def process_string(self, s: str):
+        for key, value in self.substitutions.items():
+            s = re.sub(r"\b{}\b".format(re.escape(key)), value, s)
+
+        return s
+
+
+class RemoveSpecificWords(SubstituteWords):
     def __init__(self, words_to_remove: List[str]):
-        super().__init__(words_to_remove)
+        mapping = {word: " " for word in words_to_remove}
+
+        super().__init__(mapping)
 
 
 class RemoveWhiteSpace(BaseRemoveTransform):
@@ -189,9 +215,12 @@ class RemoveWhiteSpace(BaseRemoveTransform):
 
 class RemovePunctuation(BaseRemoveTransform):
     def __init__(self):
-        characters = [c for c in string.punctuation]
+        codepoints = range(sys.maxunicode + 1)
+        punctuation = set(
+            chr(i) for i in codepoints if unicodedata.category(chr(i)).startswith("P")
+        )
 
-        super().__init__(characters)
+        super().__init__(list(punctuation))
 
 
 class RemoveMultipleSpaces(AbstractTransform):
@@ -237,28 +266,6 @@ class ExpandCommonEnglishContractions(AbstractTransform):
         return s
 
 
-class SubstituteWords(AbstractTransform):
-    def __init__(self, substitutions: Mapping[str, str]):
-        self.substitutions = substitutions
-
-    def process_string(self, s: str):
-        for key, value in self.substitutions.items():
-            s = re.sub(r"\b{}\b".format(re.escape(key)), value, s)
-
-        return s
-
-
-class SubstituteRegexes(AbstractTransform):
-    def __init__(self, substitutions: Mapping[str, str]):
-        self.substitutions = substitutions
-
-    def process_string(self, s: str):
-        for key, value in self.substitutions.items():
-            s = re.sub(key, value, s)
-
-        return s
-
-
 class ToLowerCase(AbstractTransform):
     def process_string(self, s: str):
         return s.lower()


Test patch:
diff --git a/tests/test_transforms.py b/tests/test_transforms.py
index 834c455..62be954 100644
--- a/tests/test_transforms.py
+++ b/tests/test_transforms.py
@@ -168,18 +168,26 @@ class TestReduceToListOfListOfChars(unittest.TestCase):
 class TestRemoveSpecificWords(unittest.TestCase):
     def test_normal(self):
         cases = [
-            ("yhe about that bug", " about that bug"),
-            ("yeah about that bug", " about that bug"),
-            ("one bug", "one bug"),
-            (["yhe", "about", "bug"], ["", "about", "bug"]),
-            (["yeah", "about", "bug"], ["", "about", "bug"]),
+            (["yhe about that bug"], ["  about that bug"]),
+            (["yeah about that bug"], ["  about that bug"]),
+            (["one bug"], ["one bug"]),
+            (["yhe", "about", "bug"], [" ", "about", "bug"]),
+            (["yeah", "about", "bug"], [" ", "about", "bug"]),
             (["one", "bug"], ["one", "bug"]),
-            (["yhe about bug"], [" about bug"]),
-            (["yeah about bug"], [" about bug"]),
+            (["yhe about bug"], ["  about bug"]),
+            (["yeah about bug"], ["  about bug"]),
+            (["about bug yhe"], ["about bug  "]),
             (["one bug"], ["one bug"]),
+            (["he asked a helpful question"], ["  asked   helpful question"]),
+            (["normal sentence"], ["normal sentence"]),
+            (["yhe awesome", "  awesome"]),
+            (["the apple is not a pear", "  apple is not   pear"]),
+            (["yhe", " "]),
         ]
 
-        _apply_test_on(self, RemoveSpecificWords(["yhe", "yeah"]), cases)
+        _apply_test_on(
+            self, RemoveSpecificWords(["yhe", "yeah", "a", "he", "the"]), cases
+        )
 
 
 class TestRemoveWhiteSpace(unittest.TestCase):
@@ -205,6 +213,16 @@ class TestRemovePunctuation(unittest.TestCase):
         cases = [
             (["this is an example!", "this is an example"]),
             (["hello. goodbye", "hello goodbye"]),
+            (["this sentence has no punctuation", "this sentence has no punctuation"]),
+        ]
+
+        _apply_test_on(self, RemovePunctuation(), cases)
+
+    def test_non_ascii_punctuation(self):
+        cases = [
+            (["word༆’'", "word"]),
+            (["‘no’", "no"]),
+            (["“yes”", "yes"]),
         ]
 
         _apply_test_on(self, RemovePunctuation(), cases)