kmacroexpander.cpp
00001 /* 00002 This file is part of the KDE libraries 00003 00004 Copyright (c) 2002-2003 Oswald Buddenhagen <ossi@kde.org> 00005 Copyright (c) 2003 Waldo Bastian <bastian@kde.org> 00006 00007 This library is free software; you can redistribute it and/or 00008 modify it under the terms of the GNU Library General Public 00009 License as published by the Free Software Foundation; either 00010 version 2 of the License, or (at your option) any later version. 00011 00012 This library is distributed in the hope that it will be useful, 00013 but WITHOUT ANY WARRANTY; without even the implied warranty of 00014 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00015 Library General Public License for more details. 00016 00017 You should have received a copy of the GNU Library General Public License 00018 along with this library; see the file COPYING.LIB. If not, write to 00019 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 00020 Boston, MA 02110-1301, USA. 00021 */ 00022 00023 #include <kmacroexpander.h> 00024 00025 #include <tqvaluestack.h> 00026 #include <tqregexp.h> 00027 00028 KMacroExpanderBase::KMacroExpanderBase( TQChar c ) 00029 { 00030 escapechar = c; 00031 } 00032 00033 KMacroExpanderBase::~KMacroExpanderBase() 00034 { 00035 } 00036 00037 void 00038 KMacroExpanderBase::setEscapeChar( TQChar c ) 00039 { 00040 escapechar = c; 00041 } 00042 00043 TQChar 00044 KMacroExpanderBase::escapeChar() const 00045 { 00046 return escapechar; 00047 } 00048 00049 void KMacroExpanderBase::expandMacros( TQString &str ) 00050 { 00051 uint pos; 00052 int len; 00053 TQChar ec( escapechar ); 00054 TQStringList rst; 00055 TQString rsts; 00056 00057 for (pos = 0; pos < str.length(); ) { 00058 if (ec != (QChar)0) { 00059 if (str.unicode()[pos] != ec) 00060 goto nohit; 00061 if (!(len = expandEscapedMacro( str, pos, rst ))) 00062 goto nohit; 00063 } else { 00064 if (!(len = expandPlainMacro( str, pos, rst ))) 00065 goto nohit; 00066 } 00067 if (len < 0) { 00068 pos -= len; 00069 continue; 00070 } 00071 rsts = rst.join( " " ); 00072 rst.clear(); 00073 str.replace( pos, len, rsts ); 00074 pos += rsts.length(); 00075 continue; 00076 nohit: 00077 pos++; 00078 } 00079 } 00080 00081 00082 namespace KMacroExpander { 00083 00085 enum Quoting { noquote, singlequote, doublequote, dollarquote, 00086 paren, subst, group, math }; 00087 typedef struct { 00088 Quoting current; 00089 bool dquote; 00090 } State; 00091 typedef struct { 00092 TQString str; 00093 uint pos; 00094 } Save; 00095 00096 } 00097 00098 using namespace KMacroExpander; 00099 00100 bool KMacroExpanderBase::expandMacrosShellQuote( TQString &str, uint &pos ) 00101 { 00102 int len; 00103 uint pos2; 00104 TQChar ec( escapechar ); 00105 State state = { noquote, false }; 00106 TQValueStack<State> sstack; 00107 TQValueStack<Save> ostack; 00108 TQStringList rst; 00109 TQString rsts; 00110 00111 while (pos < str.length()) { 00112 TQChar cc( str.unicode()[pos] ); 00113 if (ec != (QChar)0) { 00114 if (cc != ec) 00115 goto nohit; 00116 if (!(len = expandEscapedMacro( str, pos, rst ))) 00117 goto nohit; 00118 } else { 00119 if (!(len = expandPlainMacro( str, pos, rst ))) 00120 goto nohit; 00121 } 00122 if (len < 0) { 00123 pos -= len; 00124 continue; 00125 } 00126 if (state.dquote) { 00127 rsts = rst.join( " " ); 00128 rsts.replace( TQRegExp("([$`\"\\\\])"), "\\\\1" ); 00129 } else if (state.current == dollarquote) { 00130 rsts = rst.join( " " ); 00131 rsts.replace( TQRegExp("(['\\\\])"), "\\\\1" ); 00132 } else if (state.current == singlequote) { 00133 rsts = rst.join( " " ); 00134 rsts.replace( '\'', "'\\''"); 00135 } else { 00136 if (rst.isEmpty()) { 00137 str.remove( pos, len ); 00138 continue; 00139 } else { 00140 rsts = "'"; 00141 #if 0 // this could pay off if join() would be cleverer and the strings were long 00142 for (TQStringList::Iterator it = rst.begin(); it != rst.end(); ++it) 00143 (*it).replace( '\'', "'\\''" ); 00144 rsts += rst.join( "' '" ); 00145 #else 00146 for (TQStringList::ConstIterator it = rst.begin(); it != rst.end(); ++it) { 00147 if (it != rst.begin()) 00148 rsts += "' '"; 00149 TQString trsts( *it ); 00150 trsts.replace( '\'', "'\\''" ); 00151 rsts += trsts; 00152 } 00153 #endif 00154 rsts += "'"; 00155 } 00156 } 00157 rst.clear(); 00158 str.replace( pos, len, rsts ); 00159 pos += rsts.length(); 00160 continue; 00161 nohit: 00162 if (state.current == singlequote) { 00163 if (cc == (QChar)'\'') 00164 state = sstack.pop(); 00165 } else if (cc == (QChar)'\\') { 00166 // always swallow the char -> prevent anomalies due to expansion 00167 pos += 2; 00168 continue; 00169 } else if (state.current == dollarquote) { 00170 if (cc == (QChar)'\'') 00171 state = sstack.pop(); 00172 } else if (cc == (QChar)'$') { 00173 cc = str[++pos]; 00174 if (cc == (QChar)'(') { 00175 sstack.push( state ); 00176 if (str[pos + 1] == (QChar)'(') { 00177 Save sav = { str, pos + 2 }; 00178 ostack.push( sav ); 00179 state.current = math; 00180 pos += 2; 00181 continue; 00182 } else { 00183 state.current = paren; 00184 state.dquote = false; 00185 } 00186 } else if (cc == (QChar)'{') { 00187 sstack.push( state ); 00188 state.current = subst; 00189 } else if (!state.dquote) { 00190 if (cc == (QChar)'\'') { 00191 sstack.push( state ); 00192 state.current = dollarquote; 00193 } else if (cc == (QChar)'"') { 00194 sstack.push( state ); 00195 state.current = doublequote; 00196 state.dquote = true; 00197 } 00198 } 00199 // always swallow the char -> prevent anomalies due to expansion 00200 } else if (cc == (QChar)'`') { 00201 str.replace( pos, 1, "$( " ); // add space -> avoid creating $(( 00202 pos2 = pos += 3; 00203 for (;;) { 00204 if (pos2 >= str.length()) { 00205 pos = pos2; 00206 return false; 00207 } 00208 cc = str.unicode()[pos2]; 00209 if (cc == (QChar)'`') 00210 break; 00211 if (cc == (QChar)'\\') { 00212 cc = str[++pos2]; 00213 if (cc == (QChar)'$' || cc == (QChar)'`' || cc == (QChar)'\\' || 00214 (cc == (QChar)'"' && state.dquote)) 00215 { 00216 str.remove( pos2 - 1, 1 ); 00217 continue; 00218 } 00219 } 00220 pos2++; 00221 } 00222 str[pos2] = ')'; 00223 sstack.push( state ); 00224 state.current = paren; 00225 state.dquote = false; 00226 continue; 00227 } else if (state.current == doublequote) { 00228 if (cc == (QChar)'"') 00229 state = sstack.pop(); 00230 } else if (cc == (QChar)'\'') { 00231 if (!state.dquote) { 00232 sstack.push( state ); 00233 state.current = singlequote; 00234 } 00235 } else if (cc == (QChar)'"') { 00236 if (!state.dquote) { 00237 sstack.push( state ); 00238 state.current = doublequote; 00239 state.dquote = true; 00240 } 00241 } else if (state.current == subst) { 00242 if (cc == (QChar)'}') 00243 state = sstack.pop(); 00244 } else if (cc == (QChar)')') { 00245 if (state.current == math) { 00246 if (str[pos + 1] == (QChar)')') { 00247 state = sstack.pop(); 00248 pos += 2; 00249 } else { 00250 // false hit: the $(( was a $( ( in fact 00251 // ash does not care, but bash does 00252 pos = ostack.top().pos; 00253 str = ostack.top().str; 00254 ostack.pop(); 00255 state.current = paren; 00256 state.dquote = false; 00257 sstack.push( state ); 00258 } 00259 continue; 00260 } else if (state.current == paren) 00261 state = sstack.pop(); 00262 else 00263 break; 00264 } else if (cc == (QChar)'}') { 00265 if (state.current == KMacroExpander::group) 00266 state = sstack.pop(); 00267 else 00268 break; 00269 } else if (cc == (QChar)'(') { 00270 sstack.push( state ); 00271 state.current = paren; 00272 } else if (cc == (QChar)'{') { 00273 sstack.push( state ); 00274 state.current = KMacroExpander::group; 00275 } 00276 pos++; 00277 } 00278 return sstack.empty(); 00279 } 00280 00281 bool KMacroExpanderBase::expandMacrosShellQuote( TQString &str ) 00282 { 00283 uint pos = 0; 00284 return expandMacrosShellQuote( str, pos ) && pos == str.length(); 00285 } 00286 00287 int KMacroExpanderBase::expandPlainMacro( const TQString &, uint, TQStringList & ) 00288 { qFatal( "KMacroExpanderBase::expandPlainMacro called!" ); return 0; } 00289 00290 int KMacroExpanderBase::expandEscapedMacro( const TQString &, uint, TQStringList & ) 00291 { qFatal( "KMacroExpanderBase::expandEscapedMacro called!" ); return 0; } 00292 00293 00295 00296 template<class KT,class VT> 00297 class KMacroMapExpander : public KMacroExpanderBase { 00298 00299 public: 00300 KMacroMapExpander( const TQMap<KT,VT> &map, TQChar c = '%' ) : 00301 KMacroExpanderBase( c ), macromap( map ) {} 00302 00303 protected: 00304 virtual int expandPlainMacro( const TQString &str, uint pos, TQStringList &ret ); 00305 virtual int expandEscapedMacro( const TQString &str, uint pos, TQStringList &ret ); 00306 00307 private: 00308 TQMap<KT,VT> macromap; 00309 }; 00310 00311 static TQStringList &operator+=( TQStringList &s, const TQString &n) { s << n; return s; } 00312 00314 00315 static bool 00316 isIdentifier( uint c ) 00317 { 00318 return c == '_' || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9'); 00319 } 00320 00322 00323 template<class VT> 00324 class KMacroMapExpander<TQChar,VT> : public KMacroExpanderBase { 00325 00326 public: 00327 KMacroMapExpander( const TQMap<TQChar,VT> &map, TQChar c = '%' ) : 00328 KMacroExpanderBase( c ), macromap( map ) {} 00329 00330 protected: 00331 virtual int expandPlainMacro( const TQString &str, uint pos, TQStringList &ret ); 00332 virtual int expandEscapedMacro( const TQString &str, uint pos, TQStringList &ret ); 00333 00334 private: 00335 TQMap<TQChar,VT> macromap; 00336 }; 00337 00338 template<class VT> 00339 int 00340 KMacroMapExpander<TQChar,VT>::expandPlainMacro( const TQString &str, uint pos, TQStringList &ret ) 00341 { 00342 TQMapConstIterator<TQChar,VT> it = macromap.find(str[pos]); 00343 if (it != macromap.end()) { 00344 ret += it.data(); 00345 return 1; 00346 } 00347 return 0; 00348 } 00349 00350 template<class VT> 00351 int 00352 KMacroMapExpander<TQChar,VT>::expandEscapedMacro( const TQString &str, uint pos, TQStringList &ret ) 00353 { 00354 if (str[pos + 1] == escapeChar()) { 00355 ret += TQString( escapeChar() ); 00356 return 2; 00357 } 00358 TQMapConstIterator<TQChar,VT> it = macromap.find(str[pos+1]); 00359 if (it != macromap.end()) { 00360 ret += it.data(); 00361 return 2; 00362 } 00363 00364 return 0; 00365 } 00366 00367 template<class VT> 00368 class KMacroMapExpander<TQString,VT> : public KMacroExpanderBase { 00369 00370 public: 00371 KMacroMapExpander( const TQMap<TQString,VT> &map, TQChar c = '%' ) : 00372 KMacroExpanderBase( c ), macromap( map ) {} 00373 00374 protected: 00375 virtual int expandPlainMacro( const TQString &str, uint pos, TQStringList &ret ); 00376 virtual int expandEscapedMacro( const TQString &str, uint pos, TQStringList &ret ); 00377 00378 private: 00379 TQMap<TQString,VT> macromap; 00380 }; 00381 00382 template<class VT> 00383 int 00384 KMacroMapExpander<TQString,VT>::expandPlainMacro( const TQString &str, uint pos, TQStringList &ret ) 00385 { 00386 if (isIdentifier( str[pos - 1].unicode() )) 00387 return 0; 00388 uint sl; 00389 for (sl = 0; isIdentifier( str[pos + sl].unicode() ); sl++); 00390 if (!sl) 00391 return 0; 00392 TQMapConstIterator<TQString,VT> it = 00393 macromap.find( TQConstString( str.unicode() + pos, sl ).string() ); 00394 if (it != macromap.end()) { 00395 ret += it.data(); 00396 return sl; 00397 } 00398 return 0; 00399 } 00400 00401 template<class VT> 00402 int 00403 KMacroMapExpander<TQString,VT>::expandEscapedMacro( const TQString &str, uint pos, TQStringList &ret ) 00404 { 00405 if (str[pos + 1] == escapeChar()) { 00406 ret += TQString( escapeChar() ); 00407 return 2; 00408 } 00409 uint sl, rsl, rpos; 00410 if (str[pos + 1] == (QChar)'{') { 00411 rpos = pos + 2; 00412 for (sl = 0; str[rpos + sl] != (QChar)'}'; sl++) 00413 if (rpos + sl >= str.length()) 00414 return 0; 00415 rsl = sl + 3; 00416 } else { 00417 rpos = pos + 1; 00418 for (sl = 0; isIdentifier( str[rpos + sl].unicode() ); sl++); 00419 rsl = sl + 1; 00420 } 00421 if (!sl) 00422 return 0; 00423 TQMapConstIterator<TQString,VT> it = 00424 macromap.find( TQConstString( str.unicode() + rpos, sl ).string() ); 00425 if (it != macromap.end()) { 00426 ret += it.data(); 00427 return rsl; 00428 } 00429 return 0; 00430 } 00431 00433 00434 int 00435 KCharMacroExpander::expandPlainMacro( const TQString &str, uint pos, TQStringList &ret ) 00436 { 00437 if (expandMacro( str[pos], ret )) 00438 return 1; 00439 return 0; 00440 } 00441 00442 int 00443 KCharMacroExpander::expandEscapedMacro( const TQString &str, uint pos, TQStringList &ret ) 00444 { 00445 if (str[pos + 1] == escapeChar()) { 00446 ret += TQString( escapeChar() ); 00447 return 2; 00448 } 00449 if (expandMacro( str[pos+1], ret )) 00450 return 2; 00451 return 0; 00452 } 00453 00454 int 00455 KWordMacroExpander::expandPlainMacro( const TQString &str, uint pos, TQStringList &ret ) 00456 { 00457 if (isIdentifier( str[pos - 1].unicode() )) 00458 return 0; 00459 uint sl; 00460 for (sl = 0; isIdentifier( str[pos + sl].unicode() ); sl++); 00461 if (!sl) 00462 return 0; 00463 if (expandMacro( TQConstString( str.unicode() + pos, sl ).string(), ret )) 00464 return sl; 00465 return 0; 00466 } 00467 00468 int 00469 KWordMacroExpander::expandEscapedMacro( const TQString &str, uint pos, TQStringList &ret ) 00470 { 00471 if (str[pos + 1] == escapeChar()) { 00472 ret += TQString( escapeChar() ); 00473 return 2; 00474 } 00475 uint sl, rsl, rpos; 00476 if (str[pos + 1] == (QChar)'{') { 00477 rpos = pos + 2; 00478 for (sl = 0; str[rpos + sl] != (QChar)'}'; sl++) 00479 if (rpos + sl >= str.length()) 00480 return 0; 00481 rsl = sl + 3; 00482 } else { 00483 rpos = pos + 1; 00484 for (sl = 0; isIdentifier( str[rpos + sl].unicode() ); sl++); 00485 rsl = sl + 1; 00486 } 00487 if (!sl) 00488 return 0; 00489 if (expandMacro( TQConstString( str.unicode() + rpos, sl ).string(), ret )) 00490 return rsl; 00491 return 0; 00492 } 00493 00495 00496 template<class KT,class VT> 00497 inline QString 00498 TexpandMacros( const TQString &ostr, const TQMap<KT,VT> &map, TQChar c ) 00499 { 00500 TQString str( ostr ); 00501 KMacroMapExpander<KT,VT> kmx( map, c ); 00502 kmx.expandMacros( str ); 00503 return str; 00504 } 00505 00506 template<class KT,class VT> 00507 inline QString 00508 TexpandMacrosShellQuote( const TQString &ostr, const TQMap<KT,VT> &map, TQChar c ) 00509 { 00510 TQString str( ostr ); 00511 KMacroMapExpander<KT,VT> kmx( map, c ); 00512 if (!kmx.expandMacrosShellQuote( str )) 00513 return TQString(); 00514 return str; 00515 } 00516 00517 // public API 00518 namespace KMacroExpander { 00519 00520 TQString expandMacros( const TQString &ostr, const TQMap<TQChar,TQString> &map, TQChar c ) { return TexpandMacros( ostr, map, c ); } 00521 TQString expandMacrosShellQuote( const TQString &ostr, const TQMap<TQChar,TQString> &map, TQChar c ) { return TexpandMacrosShellQuote( ostr, map, c ); } 00522 TQString expandMacros( const TQString &ostr, const TQMap<TQString,TQString> &map, TQChar c ) { return TexpandMacros( ostr, map, c ); } 00523 TQString expandMacrosShellQuote( const TQString &ostr, const TQMap<TQString,TQString> &map, TQChar c ) { return TexpandMacrosShellQuote( ostr, map, c ); } 00524 TQString expandMacros( const TQString &ostr, const TQMap<TQChar,TQStringList> &map, TQChar c ) { return TexpandMacros( ostr, map, c ); } 00525 TQString expandMacrosShellQuote( const TQString &ostr, const TQMap<TQChar,TQStringList> &map, TQChar c ) { return TexpandMacrosShellQuote( ostr, map, c ); } 00526 TQString expandMacros( const TQString &ostr, const TQMap<TQString,TQStringList> &map, TQChar c ) { return TexpandMacros( ostr, map, c ); } 00527 TQString expandMacrosShellQuote( const TQString &ostr, const TQMap<TQString,TQStringList> &map, TQChar c ) { return TexpandMacrosShellQuote( ostr, map, c ); } 00528 00529 } // namespace