We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Strange behavior in dictionaries if replacement contains space
<!-- Please read the text in this edit field before filling it in.
Please thoroughly read NVDA's wiki article on how to fill in this template, including how to provide the required files.
Issues may be closed if the required information is not present.
https://github.com/nvaccess/nvda/blob/master/devDocs/githubIssueTemplateExplanationAndExamples.md
Please also note that the NVDA project has a Citizen and Contributor Code of Conduct which can be found at https://github.com/nvaccess/nvda/blob/master/CODE_OF_CONDUCT.md. NV Access expects that all contributors and other community members read and abide by the rules set out in this document while participating or contributing to this project. This includes creating or commenting on issues and pull requests. 

Each of the questions and sections below start with multiple hash symbols (#). Place your answers and information on the blank line below each question.
-->

### Steps to reproduce:

1. First, set symbol level of "\\" to have it reported (i.e., None);
2. open a dictionary and create something like "foo" replaced by "foo bar" (note the space in replacement);
3. read text: "test of foo".

### Actual behavior:

Voice says:
"test of foo\\bar"

### Expected behavior:
<!--
Use "Speak command keys" (NVDA+4) and speech viewer to copy and paste here. 
Use braille viewer to copy and paste here.
You may additionally include an explanation.
-->

Voice should say:
"test of foo bar"
without any "\\".

### System configuration

#### NVDA installed/portable/running from source:

Installed and running from source.

#### NVDA version:

NVDA alpha-27727,86e93666 (installed).

#### Windows version:

Windows 10 22H2 Build 19045.2604 64-bit.

#### Other information about your system:

Tested with Eloquence and Microsoft OneCore (Elsa) synthesizers.

### Other questions

#### Does the issue still occur after restarting your computer?

Not tested.

#### Have you tried any other versions of NVDA? If so, please report their behaviors.

2023.1beta1 seems to not have this issue.

#### If NVDA add-ons are disabled, is your problem still occurring?

Yes.

#### Does the issue still occur after you run the COM Registration Fixing Tool in NVDA's tools menu?

Not tested.


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `source/speechDictHandler/__init__.py`
Content:
```
1 # A part of NonVisual Desktop Access (NVDA)
2 # Copyright (C) 2006-2023 NVDA Contributors <http://www.nvda-project.org/>
3 # This file is covered by the GNU General Public License.
4 # See the file COPYING for more details.
5 
6 import re
7 import globalVars
8 from logHandler import log
9 import os
10 import codecs
11 import api
12 import config
13 from . import dictFormatUpgrade
14 from .speechDictVars import speechDictsPath
15 
16 dictionaries = {}
17 dictTypes = ("temp", "voice", "default", "builtin") # ordered by their priority E.G. voice specific speech dictionary is processed before the default
18 
19 # Types of speech dictionary entries:
20 ENTRY_TYPE_ANYWHERE = 0 # String can match anywhere
21 ENTRY_TYPE_WORD = 2 # String must have word boundaries on both sides to match
22 ENTRY_TYPE_REGEXP = 1 # Regular expression
23 
24 class SpeechDictEntry:
25 
26 	def __init__(self, pattern, replacement,comment,caseSensitive=True,type=ENTRY_TYPE_ANYWHERE):
27 		self.pattern = pattern
28 		flags = re.U
29 		if not caseSensitive: flags|=re.IGNORECASE
30 		if type == ENTRY_TYPE_REGEXP:
31 			tempPattern = pattern
32 		elif type == ENTRY_TYPE_WORD:
33 			tempPattern = r"\b" + re.escape(pattern) + r"\b"
34 		else:
35 			tempPattern= re.escape(pattern)
36 			type = ENTRY_TYPE_ANYWHERE # Insure sane values.
37 		self.compiled = re.compile(tempPattern,flags)
38 		self.replacement = replacement
39 		self.comment=comment
40 		self.caseSensitive=caseSensitive
41 		self.type=type
42 
43 	def sub(self, text: str) -> str:
44 		if self.type == ENTRY_TYPE_REGEXP:
45 			replacement = self.replacement
46 		else:
47 			replacement = re.escape(self.replacement)
48 		return self.compiled.sub(replacement, text)
49 
50 class SpeechDict(list):
51 
52 	fileName = None
53 
54 	def load(self, fileName):
55 		self.fileName=fileName
56 		comment=""
57 		del self[:]
58 		log.debug("Loading speech dictionary '%s'..." % fileName)
59 		if not os.path.isfile(fileName): 
60 			log.debug("file '%s' not found." % fileName)
61 			return
62 		file = codecs.open(fileName,"r","utf_8_sig",errors="replace")
63 		for line in file:
64 			if line.isspace():
65 				comment=""
66 				continue
67 			line=line.rstrip('\r\n')
68 			if line.startswith('#'):
69 				if comment:
70 					comment+=" "
71 				comment+=line[1:]
72 			else:
73 				temp=line.split("\t")
74 				if len(temp) ==4:
75 					pattern = temp[0].replace(r'\#','#')
76 					replace = temp[1].replace(r'\#','#')
77 					try:
78 						dictionaryEntry=SpeechDictEntry(pattern, replace, comment, caseSensitive=bool(int(temp[2])), type=int(temp[3]))
79 						self.append(dictionaryEntry)
80 					except Exception as e:
81 						log.exception("Dictionary (\"%s\") entry invalid for \"%s\" error raised: \"%s\"" % (fileName, line, e))
82 					comment=""
83 				else:
84 					log.warning("can't parse line '%s'" % line)
85 		log.debug("%d loaded records." % len(self))
86 		file.close()
87 		return
88 
89 	def save(self,fileName=None):
90 		if not fileName:
91 			fileName=getattr(self,'fileName',None)
92 		if not fileName:
93 			return
94 		dirName=os.path.dirname(fileName)
95 		if not os.path.isdir(dirName):
96 			os.makedirs(dirName)
97 		file = codecs.open(fileName,"w","utf_8_sig",errors="replace")
98 		for entry in self:
99 			if entry.comment:
100 				file.write("#%s\r\n"%entry.comment)
101 			file.write("%s\t%s\t%s\t%s\r\n"%(entry.pattern.replace('#',r'\#'),entry.replacement.replace('#',r'\#'),int(entry.caseSensitive),entry.type))
102 		file.close()
103 
104 	def sub(self, text):
105 		invalidEntries = []
106 		for index, entry in enumerate(self):
107 			try:
108 				text = entry.sub(text)
109 			except re.error as exc:
110 				dictName = self.fileName or "temporary dictionary"
111 				log.error(f"Invalid dictionary entry {index+1} in {dictName}: \"{entry.pattern}\", {exc}")
112 				invalidEntries.append(index)
113 			for index in reversed(invalidEntries):
114 				del self[index]
115 		return text
116 
117 def processText(text):
118 	if not globalVars.speechDictionaryProcessing:
119 		return text
120 	for type in dictTypes:
121 		text=dictionaries[type].sub(text)
122 	return text
123 
124 def initialize():
125 	for type in dictTypes:
126 		dictionaries[type]=SpeechDict()
127 	dictionaries["default"].load(os.path.join(speechDictsPath, "default.dic"))
128 	dictionaries["builtin"].load(os.path.join(globalVars.appDir, "builtin.dic"))
129 
130 def loadVoiceDict(synth):
131 	"""Loads appropriate dictionary for the given synthesizer.
132 It handles case when the synthesizer doesn't support voice setting.
133 """
134 	try:
135 		dictFormatUpgrade.doAnyUpgrades(synth)
136 	except:
137 		log.error("error trying to upgrade dictionaries", exc_info=True)
138 		pass
139 	if synth.isSupported("voice"):
140 		voice = synth.availableVoices[synth.voice].displayName
141 		baseName = dictFormatUpgrade.createVoiceDictFileName(synth.name, voice)
142 	else:
143 		baseName=r"{synth}.dic".format(synth=synth.name)
144 	voiceDictsPath = dictFormatUpgrade.voiceDictsPath
145 	fileName= os.path.join(voiceDictsPath, synth.name, baseName)
146 	dictionaries["voice"].load(fileName)
147
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/source/speechDictHandler/__init__.py b/source/speechDictHandler/__init__.py
--- a/source/speechDictHandler/__init__.py
+++ b/source/speechDictHandler/__init__.py
@@ -44,7 +44,8 @@
 		if self.type == ENTRY_TYPE_REGEXP:
 			replacement = self.replacement
 		else:
-			replacement = re.escape(self.replacement)
+			# Escape the backslashes for non-regexp replacements
+			replacement = self.replacement.replace('\\', '\\\\')
 		return self.compiled.sub(replacement, text)
 
 class SpeechDict(list):

{"golden_diff": "diff --git a/source/speechDictHandler/__init__.py b/source/speechDictHandler/__init__.py\n--- a/source/speechDictHandler/__init__.py\n+++ b/source/speechDictHandler/__init__.py\n@@ -44,7 +44,8 @@\n \t\tif self.type == ENTRY_TYPE_REGEXP:\r\n \t\t\treplacement = self.replacement\r\n \t\telse:\r\n-\t\t\treplacement = re.escape(self.replacement)\r\n+\t\t\t# Escape the backslashes for non-regexp replacements\r\n+\t\t\treplacement = self.replacement.replace('\\\\', '\\\\\\\\')\r\n \t\treturn self.compiled.sub(replacement, text)\r\n \r\n class SpeechDict(list):\n", "issue": "Strange behavior in dictionaries if replacement contains space\n<!-- Please read the text in this edit field before filling it in.\r\nPlease thoroughly read NVDA's wiki article on how to fill in this template, including how to provide the required files.\r\nIssues may be closed if the required information is not present.\r\nhttps://github.com/nvaccess/nvda/blob/master/devDocs/githubIssueTemplateExplanationAndExamples.md\r\nPlease also note that the NVDA project has a Citizen and Contributor Code of Conduct which can be found at https://github.com/nvaccess/nvda/blob/master/CODE_OF_CONDUCT.md. NV Access expects that all contributors and other community members read and abide by the rules set out in this document while participating or contributing to this project. This includes creating or commenting on issues and pull requests. \r\n\r\nEach of the questions and sections below start with multiple hash symbols (#). Place your answers and information on the blank line below each question.\r\n-->\r\n\r\n### Steps to reproduce:\r\n\r\n1. First, set symbol level of \"\\\\\" to have it reported (i.e., None);\r\n2. open a dictionary and create something like \"foo\" replaced by \"foo bar\" (note the space in replacement);\r\n3. read text: \"test of foo\".\r\n\r\n### Actual behavior:\r\n\r\nVoice says:\r\n\"test of foo\\\\bar\"\r\n\r\n### Expected behavior:\r\n<!--\r\nUse \"Speak command keys\" (NVDA+4) and speech viewer to copy and paste here. \r\nUse braille viewer to copy and paste here.\r\nYou may additionally include an explanation.\r\n-->\r\n\r\nVoice should say:\r\n\"test of foo bar\"\r\nwithout any \"\\\\\".\r\n\r\n### System configuration\r\n\r\n#### NVDA installed/portable/running from source:\r\n\r\nInstalled and running from source.\r\n\r\n#### NVDA version:\r\n\r\nNVDA alpha-27727,86e93666 (installed).\r\n\r\n#### Windows version:\r\n\r\nWindows 10 22H2 Build 19045.2604 64-bit.\r\n\r\n#### Other information about your system:\r\n\r\nTested with Eloquence and Microsoft OneCore (Elsa) synthesizers.\r\n\r\n### Other questions\r\n\r\n#### Does the issue still occur after restarting your computer?\r\n\r\nNot tested.\r\n\r\n#### Have you tried any other versions of NVDA? If so, please report their behaviors.\r\n\r\n2023.1beta1 seems to not have this issue.\r\n\r\n#### If NVDA add-ons are disabled, is your problem still occurring?\r\n\r\nYes.\r\n\r\n#### Does the issue still occur after you run the COM Registration Fixing Tool in NVDA's tools menu?\r\n\r\nNot tested.\r\n\n", "before_files": [{"content": "# A part of NonVisual Desktop Access (NVDA)\r\n# Copyright (C) 2006-2023 NVDA Contributors <http://www.nvda-project.org/>\r\n# This file is covered by the GNU General Public License.\r\n# See the file COPYING for more details.\r\n\r\nimport re\r\nimport globalVars\r\nfrom logHandler import log\r\nimport os\r\nimport codecs\r\nimport api\r\nimport config\r\nfrom . import dictFormatUpgrade\r\nfrom .speechDictVars import speechDictsPath\r\n\r\ndictionaries = {}\r\ndictTypes = (\"temp\", \"voice\", \"default\", \"builtin\") # ordered by their priority E.G. voice specific speech dictionary is processed before the default\r\n\r\n# Types of speech dictionary entries:\r\nENTRY_TYPE_ANYWHERE = 0 # String can match anywhere\r\nENTRY_TYPE_WORD = 2 # String must have word boundaries on both sides to match\r\nENTRY_TYPE_REGEXP = 1 # Regular expression\r\n\r\nclass SpeechDictEntry:\r\n\r\n\tdef __init__(self, pattern, replacement,comment,caseSensitive=True,type=ENTRY_TYPE_ANYWHERE):\r\n\t\tself.pattern = pattern\r\n\t\tflags = re.U\r\n\t\tif not caseSensitive: flags|=re.IGNORECASE\r\n\t\tif type == ENTRY_TYPE_REGEXP:\r\n\t\t\ttempPattern = pattern\r\n\t\telif type == ENTRY_TYPE_WORD:\r\n\t\t\ttempPattern = r\"\\b\" + re.escape(pattern) + r\"\\b\"\r\n\t\telse:\r\n\t\t\ttempPattern= re.escape(pattern)\r\n\t\t\ttype = ENTRY_TYPE_ANYWHERE # Insure sane values.\r\n\t\tself.compiled = re.compile(tempPattern,flags)\r\n\t\tself.replacement = replacement\r\n\t\tself.comment=comment\r\n\t\tself.caseSensitive=caseSensitive\r\n\t\tself.type=type\r\n\r\n\tdef sub(self, text: str) -> str:\r\n\t\tif self.type == ENTRY_TYPE_REGEXP:\r\n\t\t\treplacement = self.replacement\r\n\t\telse:\r\n\t\t\treplacement = re.escape(self.replacement)\r\n\t\treturn self.compiled.sub(replacement, text)\r\n\r\nclass SpeechDict(list):\r\n\r\n\tfileName = None\r\n\r\n\tdef load(self, fileName):\r\n\t\tself.fileName=fileName\r\n\t\tcomment=\"\"\r\n\t\tdel self[:]\r\n\t\tlog.debug(\"Loading speech dictionary '%s'...\" % fileName)\r\n\t\tif not os.path.isfile(fileName): \r\n\t\t\tlog.debug(\"file '%s' not found.\" % fileName)\r\n\t\t\treturn\r\n\t\tfile = codecs.open(fileName,\"r\",\"utf_8_sig\",errors=\"replace\")\r\n\t\tfor line in file:\r\n\t\t\tif line.isspace():\r\n\t\t\t\tcomment=\"\"\r\n\t\t\t\tcontinue\r\n\t\t\tline=line.rstrip('\\r\\n')\r\n\t\t\tif line.startswith('#'):\r\n\t\t\t\tif comment:\r\n\t\t\t\t\tcomment+=\" \"\r\n\t\t\t\tcomment+=line[1:]\r\n\t\t\telse:\r\n\t\t\t\ttemp=line.split(\"\\t\")\r\n\t\t\t\tif len(temp) ==4:\r\n\t\t\t\t\tpattern = temp[0].replace(r'\\#','#')\r\n\t\t\t\t\treplace = temp[1].replace(r'\\#','#')\r\n\t\t\t\t\ttry:\r\n\t\t\t\t\t\tdictionaryEntry=SpeechDictEntry(pattern, replace, comment, caseSensitive=bool(int(temp[2])), type=int(temp[3]))\r\n\t\t\t\t\t\tself.append(dictionaryEntry)\r\n\t\t\t\t\texcept Exception as e:\r\n\t\t\t\t\t\tlog.exception(\"Dictionary (\\\"%s\\\") entry invalid for \\\"%s\\\" error raised: \\\"%s\\\"\" % (fileName, line, e))\r\n\t\t\t\t\tcomment=\"\"\r\n\t\t\t\telse:\r\n\t\t\t\t\tlog.warning(\"can't parse line '%s'\" % line)\r\n\t\tlog.debug(\"%d loaded records.\" % len(self))\r\n\t\tfile.close()\r\n\t\treturn\r\n\r\n\tdef save(self,fileName=None):\r\n\t\tif not fileName:\r\n\t\t\tfileName=getattr(self,'fileName',None)\r\n\t\tif not fileName:\r\n\t\t\treturn\r\n\t\tdirName=os.path.dirname(fileName)\r\n\t\tif not os.path.isdir(dirName):\r\n\t\t\tos.makedirs(dirName)\r\n\t\tfile = codecs.open(fileName,\"w\",\"utf_8_sig\",errors=\"replace\")\r\n\t\tfor entry in self:\r\n\t\t\tif entry.comment:\r\n\t\t\t\tfile.write(\"#%s\\r\\n\"%entry.comment)\r\n\t\t\tfile.write(\"%s\\t%s\\t%s\\t%s\\r\\n\"%(entry.pattern.replace('#',r'\\#'),entry.replacement.replace('#',r'\\#'),int(entry.caseSensitive),entry.type))\r\n\t\tfile.close()\r\n\r\n\tdef sub(self, text):\r\n\t\tinvalidEntries = []\r\n\t\tfor index, entry in enumerate(self):\r\n\t\t\ttry:\r\n\t\t\t\ttext = entry.sub(text)\r\n\t\t\texcept re.error as exc:\r\n\t\t\t\tdictName = self.fileName or \"temporary dictionary\"\r\n\t\t\t\tlog.error(f\"Invalid dictionary entry {index+1} in {dictName}: \\\"{entry.pattern}\\\", {exc}\")\r\n\t\t\t\tinvalidEntries.append(index)\r\n\t\t\tfor index in reversed(invalidEntries):\r\n\t\t\t\tdel self[index]\r\n\t\treturn text\r\n\r\ndef processText(text):\r\n\tif not globalVars.speechDictionaryProcessing:\r\n\t\treturn text\r\n\tfor type in dictTypes:\r\n\t\ttext=dictionaries[type].sub(text)\r\n\treturn text\r\n\r\ndef initialize():\r\n\tfor type in dictTypes:\r\n\t\tdictionaries[type]=SpeechDict()\r\n\tdictionaries[\"default\"].load(os.path.join(speechDictsPath, \"default.dic\"))\r\n\tdictionaries[\"builtin\"].load(os.path.join(globalVars.appDir, \"builtin.dic\"))\r\n\r\ndef loadVoiceDict(synth):\r\n\t\"\"\"Loads appropriate dictionary for the given synthesizer.\r\nIt handles case when the synthesizer doesn't support voice setting.\r\n\"\"\"\r\n\ttry:\r\n\t\tdictFormatUpgrade.doAnyUpgrades(synth)\r\n\texcept:\r\n\t\tlog.error(\"error trying to upgrade dictionaries\", exc_info=True)\r\n\t\tpass\r\n\tif synth.isSupported(\"voice\"):\r\n\t\tvoice = synth.availableVoices[synth.voice].displayName\r\n\t\tbaseName = dictFormatUpgrade.createVoiceDictFileName(synth.name, voice)\r\n\telse:\r\n\t\tbaseName=r\"{synth}.dic\".format(synth=synth.name)\r\n\tvoiceDictsPath = dictFormatUpgrade.voiceDictsPath\r\n\tfileName= os.path.join(voiceDictsPath, synth.name, baseName)\r\n\tdictionaries[\"voice\"].load(fileName)\r\n", "path": "source/speechDictHandler/__init__.py"}], "after_files": [{"content": "# A part of NonVisual Desktop Access (NVDA)\r\n# Copyright (C) 2006-2023 NVDA Contributors <http://www.nvda-project.org/>\r\n# This file is covered by the GNU General Public License.\r\n# See the file COPYING for more details.\r\n\r\nimport re\r\nimport globalVars\r\nfrom logHandler import log\r\nimport os\r\nimport codecs\r\nimport api\r\nimport config\r\nfrom . import dictFormatUpgrade\r\nfrom .speechDictVars import speechDictsPath\r\n\r\ndictionaries = {}\r\ndictTypes = (\"temp\", \"voice\", \"default\", \"builtin\") # ordered by their priority E.G. voice specific speech dictionary is processed before the default\r\n\r\n# Types of speech dictionary entries:\r\nENTRY_TYPE_ANYWHERE = 0 # String can match anywhere\r\nENTRY_TYPE_WORD = 2 # String must have word boundaries on both sides to match\r\nENTRY_TYPE_REGEXP = 1 # Regular expression\r\n\r\nclass SpeechDictEntry:\r\n\r\n\tdef __init__(self, pattern, replacement,comment,caseSensitive=True,type=ENTRY_TYPE_ANYWHERE):\r\n\t\tself.pattern = pattern\r\n\t\tflags = re.U\r\n\t\tif not caseSensitive: flags|=re.IGNORECASE\r\n\t\tif type == ENTRY_TYPE_REGEXP:\r\n\t\t\ttempPattern = pattern\r\n\t\telif type == ENTRY_TYPE_WORD:\r\n\t\t\ttempPattern = r\"\\b\" + re.escape(pattern) + r\"\\b\"\r\n\t\telse:\r\n\t\t\ttempPattern= re.escape(pattern)\r\n\t\t\ttype = ENTRY_TYPE_ANYWHERE # Insure sane values.\r\n\t\tself.compiled = re.compile(tempPattern,flags)\r\n\t\tself.replacement = replacement\r\n\t\tself.comment=comment\r\n\t\tself.caseSensitive=caseSensitive\r\n\t\tself.type=type\r\n\r\n\tdef sub(self, text: str) -> str:\r\n\t\tif self.type == ENTRY_TYPE_REGEXP:\r\n\t\t\treplacement = self.replacement\r\n\t\telse:\r\n\t\t\t# Escape the backslashes for non-regexp replacements\r\n\t\t\treplacement = self.replacement.replace('\\\\', '\\\\\\\\')\r\n\t\treturn self.compiled.sub(replacement, text)\r\n\r\nclass SpeechDict(list):\r\n\r\n\tfileName = None\r\n\r\n\tdef load(self, fileName):\r\n\t\tself.fileName=fileName\r\n\t\tcomment=\"\"\r\n\t\tdel self[:]\r\n\t\tlog.debug(\"Loading speech dictionary '%s'...\" % fileName)\r\n\t\tif not os.path.isfile(fileName): \r\n\t\t\tlog.debug(\"file '%s' not found.\" % fileName)\r\n\t\t\treturn\r\n\t\tfile = codecs.open(fileName,\"r\",\"utf_8_sig\",errors=\"replace\")\r\n\t\tfor line in file:\r\n\t\t\tif line.isspace():\r\n\t\t\t\tcomment=\"\"\r\n\t\t\t\tcontinue\r\n\t\t\tline=line.rstrip('\\r\\n')\r\n\t\t\tif line.startswith('#'):\r\n\t\t\t\tif comment:\r\n\t\t\t\t\tcomment+=\" \"\r\n\t\t\t\tcomment+=line[1:]\r\n\t\t\telse:\r\n\t\t\t\ttemp=line.split(\"\\t\")\r\n\t\t\t\tif len(temp) ==4:\r\n\t\t\t\t\tpattern = temp[0].replace(r'\\#','#')\r\n\t\t\t\t\treplace = temp[1].replace(r'\\#','#')\r\n\t\t\t\t\ttry:\r\n\t\t\t\t\t\tdictionaryEntry=SpeechDictEntry(pattern, replace, comment, caseSensitive=bool(int(temp[2])), type=int(temp[3]))\r\n\t\t\t\t\t\tself.append(dictionaryEntry)\r\n\t\t\t\t\texcept Exception as e:\r\n\t\t\t\t\t\tlog.exception(\"Dictionary (\\\"%s\\\") entry invalid for \\\"%s\\\" error raised: \\\"%s\\\"\" % (fileName, line, e))\r\n\t\t\t\t\tcomment=\"\"\r\n\t\t\t\telse:\r\n\t\t\t\t\tlog.warning(\"can't parse line '%s'\" % line)\r\n\t\tlog.debug(\"%d loaded records.\" % len(self))\r\n\t\tfile.close()\r\n\t\treturn\r\n\r\n\tdef save(self,fileName=None):\r\n\t\tif not fileName:\r\n\t\t\tfileName=getattr(self,'fileName',None)\r\n\t\tif not fileName:\r\n\t\t\treturn\r\n\t\tdirName=os.path.dirname(fileName)\r\n\t\tif not os.path.isdir(dirName):\r\n\t\t\tos.makedirs(dirName)\r\n\t\tfile = codecs.open(fileName,\"w\",\"utf_8_sig\",errors=\"replace\")\r\n\t\tfor entry in self:\r\n\t\t\tif entry.comment:\r\n\t\t\t\tfile.write(\"#%s\\r\\n\"%entry.comment)\r\n\t\t\tfile.write(\"%s\\t%s\\t%s\\t%s\\r\\n\"%(entry.pattern.replace('#',r'\\#'),entry.replacement.replace('#',r'\\#'),int(entry.caseSensitive),entry.type))\r\n\t\tfile.close()\r\n\r\n\tdef sub(self, text):\r\n\t\tinvalidEntries = []\r\n\t\tfor index, entry in enumerate(self):\r\n\t\t\ttry:\r\n\t\t\t\ttext = entry.sub(text)\r\n\t\t\texcept re.error as exc:\r\n\t\t\t\tdictName = self.fileName or \"temporary dictionary\"\r\n\t\t\t\tlog.error(f\"Invalid dictionary entry {index+1} in {dictName}: \\\"{entry.pattern}\\\", {exc}\")\r\n\t\t\t\tinvalidEntries.append(index)\r\n\t\t\tfor index in reversed(invalidEntries):\r\n\t\t\t\tdel self[index]\r\n\t\treturn text\r\n\r\ndef processText(text):\r\n\tif not globalVars.speechDictionaryProcessing:\r\n\t\treturn text\r\n\tfor type in dictTypes:\r\n\t\ttext=dictionaries[type].sub(text)\r\n\treturn text\r\n\r\ndef initialize():\r\n\tfor type in dictTypes:\r\n\t\tdictionaries[type]=SpeechDict()\r\n\tdictionaries[\"default\"].load(os.path.join(speechDictsPath, \"default.dic\"))\r\n\tdictionaries[\"builtin\"].load(os.path.join(globalVars.appDir, \"builtin.dic\"))\r\n\r\ndef loadVoiceDict(synth):\r\n\t\"\"\"Loads appropriate dictionary for the given synthesizer.\r\nIt handles case when the synthesizer doesn't support voice setting.\r\n\"\"\"\r\n\ttry:\r\n\t\tdictFormatUpgrade.doAnyUpgrades(synth)\r\n\texcept:\r\n\t\tlog.error(\"error trying to upgrade dictionaries\", exc_info=True)\r\n\t\tpass\r\n\tif synth.isSupported(\"voice\"):\r\n\t\tvoice = synth.availableVoices[synth.voice].displayName\r\n\t\tbaseName = dictFormatUpgrade.createVoiceDictFileName(synth.name, voice)\r\n\telse:\r\n\t\tbaseName=r\"{synth}.dic\".format(synth=synth.name)\r\n\tvoiceDictsPath = dictFormatUpgrade.voiceDictsPath\r\n\tfileName= os.path.join(voiceDictsPath, synth.name, baseName)\r\n\tdictionaries[\"voice\"].load(fileName)\r\n", "path": "source/speechDictHandler/__init__.py"}]}