1 | /*************************************************************************** |
2 | speech.cpp - description |
3 | ------------------- |
4 | begin : Son Sep 8 2002 |
5 | copyright : (C) 2002 by Gunnar Schmi Dt |
6 | email : kmouth@schmi-dt.de |
7 | ***************************************************************************/ |
8 | |
9 | /*************************************************************************** |
10 | * * |
11 | * This program is free software; you can redistribute it and/or modify * |
12 | * it under the terms of the GNU General Public License as published by * |
13 | * the Free Software Foundation; either version 2 of the License, or * |
14 | * (at your option) any later version. * |
15 | * * |
16 | ***************************************************************************/ |
17 | |
18 | #include "speech.h" |
19 | |
20 | #include <QtCore/QStack> |
21 | #include <QtCore/QRegExp> |
22 | #include <QtCore/QTextCodec> |
23 | #include <QtCore/QFile> |
24 | #include <QtCore/QHash> |
25 | #include <QtCore/QTextStream> |
26 | |
27 | #include <kdebug.h> |
28 | #include <kdeversion.h> |
29 | #define macroExpander |
30 | #include <kmacroexpander.h> |
31 | |
32 | Speech::Speech() { |
33 | } |
34 | |
35 | Speech::~Speech() { |
36 | } |
37 | |
38 | QString Speech::prepareCommand (QString command, const QString &text, |
39 | const QString &filename, const QString &language) { |
40 | #ifdef macroExpander |
41 | QHash<QChar,QString> map; |
42 | map[QLatin1Char( 't' )] = text; |
43 | map[QLatin1Char( 'f' )] = filename; |
44 | map[QLatin1Char( 'l' )] = language; |
45 | return KMacroExpander::expandMacrosShellQuote (command, map); |
46 | #else |
47 | QStack<bool> stack; // saved isdoublequote values during parsing of braces |
48 | bool issinglequote=false; // inside '...' ? |
49 | bool isdoublequote=false; // inside "..." ? |
50 | int noreplace=0; // nested braces when within ${...} |
51 | QString escText = K3ShellProcess::quote(text); |
52 | |
53 | // character sequences that change the state or need to be otherwise processed |
54 | QRegExp re_singlequote("('|%%|%t|%f|%l)" ); |
55 | QRegExp re_doublequote("(\"|\\\\|`|\\$\\(|\\$\\{|%%|%t|%f|%l)" ); |
56 | QRegExp re_noquote ("('|\"|\\\\|`|\\$\\(|\\$\\{|\\(|\\{|\\)|\\}|%%|%t|%f|%l)" ); |
57 | |
58 | // parse the command: |
59 | for (int i = re_noquote.search(command); |
60 | i != -1; |
61 | i = (issinglequote?re_singlequote.search(command,i) |
62 | :isdoublequote?re_doublequote.search(command,i) |
63 | :re_noquote.search(command,i)) |
64 | ) |
65 | // while there are character sequences that need to be processed |
66 | { |
67 | if ((command[i]=='(') || (command[i]=='{')) { // (...) or {...} |
68 | // assert(isdoublequote == false) |
69 | stack.push(isdoublequote); |
70 | if (noreplace > 0) |
71 | // count nested braces when within ${...} |
72 | noreplace++; |
73 | i++; |
74 | } |
75 | else if (command[i]=='$') { // $(...) or ${...} |
76 | stack.push(isdoublequote); |
77 | isdoublequote = false; |
78 | if ((noreplace > 0) || (command[i+1]=='{')) |
79 | // count nested braces when within ${...} |
80 | noreplace++; |
81 | i+=2; |
82 | } |
83 | else if ((command[i]==')') || (command[i]=='}')) { |
84 | // $(...) or (...) or ${...} or {...} |
85 | if (!stack.isEmpty()) |
86 | isdoublequote = stack.pop(); |
87 | else |
88 | qWarning("Parse error." ); |
89 | if (noreplace > 0) |
90 | // count nested braces when within ${...} |
91 | noreplace--; |
92 | i++; |
93 | } |
94 | else if (command[i]=='\'') { |
95 | issinglequote=!issinglequote; |
96 | i++; |
97 | } |
98 | else if (command[i]=='"') { |
99 | isdoublequote=!isdoublequote; |
100 | i++; |
101 | } |
102 | else if (command[i]=='\\') |
103 | i+=2; |
104 | else if (command[i]=='`') { |
105 | // Replace all `...` with safer $(...) |
106 | command.replace (i, 1, "$(" ); |
107 | QRegExp re_backticks("(`|\\\\`|\\\\\\\\|\\\\\\$)" ); |
108 | for (int i2=re_backticks.search(command,i+2); |
109 | i2!=-1; |
110 | i2=re_backticks.search(command,i2) |
111 | ) |
112 | { |
113 | if (command[i2] == '`') { |
114 | command.replace (i2, 1, ")" ); |
115 | i2=command.length(); // leave loop |
116 | } |
117 | else { |
118 | // remove backslash and ignore following character |
119 | command.remove (i2, 1); |
120 | i2++; |
121 | } |
122 | } |
123 | // Leave i unchanged! We need to process "$(" |
124 | } |
125 | else if (noreplace > 0) { // do not replace macros within ${...} |
126 | if (issinglequote) |
127 | i+=re_singlequote.matchedLength(); |
128 | else if (isdoublequote) |
129 | i+=re_doublequote.matchedLength(); |
130 | else |
131 | i+=re_noquote.matchedLength(); |
132 | } |
133 | else { // replace macro |
134 | QString match, v; |
135 | |
136 | // get match |
137 | if (issinglequote) |
138 | match=re_singlequote.cap(); |
139 | else if (isdoublequote) |
140 | match=re_doublequote.cap(); |
141 | else |
142 | match=re_noquote.cap(); |
143 | |
144 | // substitute %variables |
145 | if (match=="%t" ) |
146 | v = escText; |
147 | else if (match=="%f" ) |
148 | v = filename; |
149 | else if (match=="%%" ) |
150 | v = "%" ; |
151 | else if (match=="%l" ) |
152 | v = language; |
153 | |
154 | // %variable inside of a quote? |
155 | if (isdoublequote) |
156 | v='"'+v+'"'; |
157 | else if (issinglequote) |
158 | v='\''+v+'\''; |
159 | |
160 | command.replace (i, match.length(), v); |
161 | i+=v.length(); |
162 | } |
163 | } |
164 | return command; |
165 | #endif |
166 | } |
167 | |
168 | void Speech::speak(QString command, bool stdIn, const QString &text, const QString &language, int encoding, QTextCodec *codec) { |
169 | if (text.length () > 0) { |
170 | // 1. prepare the text: |
171 | // 1.a) encode the text |
172 | QTextStream ts (&encText, QIODevice::WriteOnly); |
173 | if (encoding == Local) |
174 | ts.setCodec (QTextCodec::codecForLocale()); |
175 | else if (encoding == Latin1) |
176 | ts.setCodec ("ISO-8859-1" ); |
177 | else if (encoding == Unicode) |
178 | ts.setCodec ("UTF-16" ); |
179 | else |
180 | ts.setCodec (codec); |
181 | ts << text; |
182 | ts.flush(); |
183 | |
184 | // 1.b) create a temporary file for the text |
185 | tempFile.open(); |
186 | QTextStream fs ( &tempFile ); |
187 | if (encoding == Local) |
188 | fs.setCodec (QTextCodec::codecForLocale()); |
189 | else if (encoding == Latin1) |
190 | fs.setCodec ("ISO-8859-1" ); |
191 | else if (encoding == Unicode) |
192 | fs.setCodec ("UTF-16" ); |
193 | else |
194 | fs.setCodec (codec); |
195 | fs << text; |
196 | fs << endl; |
197 | QString filename = tempFile.fileName(); |
198 | tempFile.flush(); |
199 | |
200 | // 2. prepare the command: |
201 | command = prepareCommand (command, QLatin1String( encText ), filename, language); |
202 | |
203 | |
204 | // 3. create a new process |
205 | process << command; |
206 | connect(&process, SIGNAL(processExited(K3Process*)), this, SLOT(processExited(K3Process*))); |
207 | connect(&process, SIGNAL(wroteStdin(K3Process*)), this, SLOT(wroteStdin(K3Process*))); |
208 | connect(&process, SIGNAL(receivedStdout(K3Process*,char*,int)), this, SLOT(receivedStdout(K3Process*,char*,int))); |
209 | connect(&process, SIGNAL(receivedStderr(K3Process*,char*,int)), this, SLOT(receivedStderr(K3Process*,char*,int))); |
210 | |
211 | // 4. start the process |
212 | if (stdIn) { |
213 | process.start(K3Process::NotifyOnExit, K3Process::All); |
214 | if (encText.size() > 0) |
215 | process.writeStdin(encText, encText.size()); |
216 | else |
217 | process.closeStdin(); |
218 | } |
219 | else |
220 | process.start(K3Process::NotifyOnExit, K3Process::AllOutput); |
221 | } |
222 | } |
223 | |
224 | void Speech::receivedStdout (K3Process *, char *buffer, int buflen) { |
225 | kDebug() << QString::fromLatin1(buffer, buflen) + QLatin1Char( '\n' ); |
226 | } |
227 | void Speech::receivedStderr (K3Process *, char *buffer, int buflen) { |
228 | kDebug() << QString::fromLatin1(buffer, buflen) + QLatin1Char( '\n' ); |
229 | } |
230 | |
231 | void Speech::wroteStdin(K3Process *) { |
232 | process.closeStdin(); |
233 | } |
234 | |
235 | void Speech::processExited(K3Process *) { |
236 | delete this; |
237 | } |
238 | |
239 | #include "speech.moc" |
240 | |