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
32Speech::Speech() {
33}
34
35Speech::~Speech() {
36}
37
38QString 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
168void 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
224void Speech::receivedStdout (K3Process *, char *buffer, int buflen) {
225 kDebug() << QString::fromLatin1(buffer, buflen) + QLatin1Char( '\n' );
226}
227void Speech::receivedStderr (K3Process *, char *buffer, int buflen) {
228 kDebug() << QString::fromLatin1(buffer, buflen) + QLatin1Char( '\n' );
229}
230
231void Speech::wroteStdin(K3Process *) {
232 process.closeStdin();
233}
234
235void Speech::processExited(K3Process *) {
236 delete this;
237}
238
239#include "speech.moc"
240