KDECore
ktranscript.cpp
Go to the documentation of this file.
00001 /* This file is part of the KDE libraries Copyright (C) 2007 Chusslove Illich <caslav.ilic@gmx.net> 00002 00003 This library is free software; you can redistribute it and/or 00004 modify it under the terms of the GNU Library General Public 00005 License as published by the Free Software Foundation; either 00006 version 2 of the License, or (at your option) any later version. 00007 00008 This library is distributed in the hope that it will be useful, 00009 but WITHOUT ANY WARRANTY; without even the implied warranty of 00010 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00011 Library General Public License for more details. 00012 00013 You should have received a copy of the GNU Library General Public License 00014 along with this library; see the file COPYING.LIB. If not, write to 00015 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 00016 Boston, MA 02110-1301, USA. 00017 */ 00018 00019 #include <ktranscript_p.h> 00020 #include <common_helpers_p.h> 00021 00022 #include <config.h> 00023 00024 #include <kdecore_export.h> 00025 #include <kglobal.h> 00026 00027 //#include <unistd.h> 00028 00029 #include <kjs/value.h> 00030 #include <kjs/object.h> 00031 #include <kjs/lookup.h> 00032 #include <kjs/function.h> 00033 #include <kjs/interpreter.h> 00034 #include <kjs/string_object.h> 00035 #include <kjs/error_object.h> 00036 00037 #include <QVariant> 00038 #include <QStringList> 00039 #include <QList> 00040 #include <QDir> 00041 #include <QHash> 00042 #include <QPair> 00043 #include <QSet> 00044 #include <QFile> 00045 #include <QIODevice> 00046 #include <QTextStream> 00047 #include <QRegExp> 00048 #include <qendian.h> 00049 00050 using namespace KJS; 00051 00052 class KTranscriptImp; 00053 class Scriptface; 00054 00055 typedef QHash<QString, QString> TsConfigGroup; 00056 typedef QHash<QString, TsConfigGroup> TsConfig; 00057 00058 // Transcript implementation (used as singleton). 00059 class KTranscriptImp : public KTranscript 00060 { 00061 public: 00062 00063 KTranscriptImp (); 00064 ~KTranscriptImp (); 00065 00066 QString eval (const QList<QVariant> &argv, 00067 const QString &lang, 00068 const QString &ctry, 00069 const QString &msgctxt, 00070 const QHash<QString, QString> &dynctxt, 00071 const QString &msgid, 00072 const QStringList &subs, 00073 const QList<QVariant> &vals, 00074 const QString &final, 00075 QList<QStringList> &mods, 00076 QString &error, 00077 bool &fallback); 00078 00079 QStringList postCalls (const QString &lang); 00080 00081 // Lexical path of the module for the executing code. 00082 QString currentModulePath; 00083 00084 private: 00085 00086 void loadModules (const QList<QStringList> &mods, QString &error); 00087 void setupInterpreter (const QString &lang); 00088 00089 TsConfig config; 00090 00091 QHash<QString, Scriptface*> m_sface; 00092 }; 00093 00094 // Script-side transcript interface. 00095 class Scriptface : public JSObject 00096 { 00097 public: 00098 Scriptface (ExecState *exec, const TsConfigGroup &config); 00099 ~Scriptface (); 00100 00101 // Interface functions. 00102 JSValue *loadf (ExecState *exec, const List &fnames); 00103 JSValue *setcallf (ExecState *exec, JSValue *name, 00104 JSValue *func, JSValue *fval); 00105 JSValue *hascallf (ExecState *exec, JSValue *name); 00106 JSValue *acallf (ExecState *exec, const List &argv); 00107 JSValue *setcallForallf (ExecState *exec, JSValue *name, 00108 JSValue *func, JSValue *fval); 00109 JSValue *fallbackf (ExecState *exec); 00110 JSValue *nsubsf (ExecState *exec); 00111 JSValue *subsf (ExecState *exec, JSValue *index); 00112 JSValue *valsf (ExecState *exec, JSValue *index); 00113 JSValue *msgctxtf (ExecState *exec); 00114 JSValue *dynctxtf (ExecState *exec, JSValue *key); 00115 JSValue *msgidf (ExecState *exec); 00116 JSValue *msgkeyf (ExecState *exec); 00117 JSValue *msgstrff (ExecState *exec); 00118 JSValue *dbgputsf (ExecState *exec, JSValue *str); 00119 JSValue *warnputsf (ExecState *exec, JSValue *str); 00120 JSValue *localeCountryf (ExecState *exec); 00121 JSValue *normKeyf (ExecState *exec, JSValue *phrase); 00122 JSValue *loadPropsf (ExecState *exec, const List &fnames); 00123 JSValue *getPropf (ExecState *exec, JSValue *phrase, JSValue *prop); 00124 JSValue *setPropf (ExecState *exec, JSValue *phrase, JSValue *prop, JSValue *value); 00125 JSValue *toUpperFirstf (ExecState *exec, JSValue *str, JSValue *nalt); 00126 JSValue *toLowerFirstf (ExecState *exec, JSValue *str, JSValue *nalt); 00127 JSValue *getConfStringf (ExecState *exec, JSValue *key, JSValue *dval); 00128 JSValue *getConfBoolf (ExecState *exec, JSValue *key, JSValue *dval); 00129 JSValue *getConfNumberf (ExecState *exec, JSValue *key, JSValue *dval); 00130 00131 enum { 00132 Load, 00133 Setcall, 00134 Hascall, 00135 Acall, 00136 SetcallForall, 00137 Fallback, 00138 Nsubs, 00139 Subs, 00140 Vals, 00141 Msgctxt, 00142 Dynctxt, 00143 Msgid, 00144 Msgkey, 00145 Msgstrf, 00146 Dbgputs, 00147 Warnputs, 00148 LocaleCountry, 00149 NormKey, 00150 LoadProps, 00151 GetProp, 00152 SetProp, 00153 ToUpperFirst, 00154 ToLowerFirst, 00155 GetConfString, 00156 GetConfBool, 00157 GetConfNumber 00158 }; 00159 00160 // Helper methods to interface functions. 00161 QString loadProps_text (const QString &fpath); 00162 QString loadProps_bin (const QString &fpath); 00163 QString loadProps_bin_00 (const QString &fpath); 00164 QString loadProps_bin_01 (const QString &fpath); 00165 00166 // Virtual implementations. 00167 bool getOwnPropertySlot (ExecState *exec, const Identifier& propertyName, PropertySlot& slot); 00168 JSValue *getValueProperty (ExecState *exec, int token) const; 00169 void put (ExecState *exec, const Identifier &propertyName, JSValue *value, int attr); 00170 void putValueProperty (ExecState *exec, int token, JSValue *value, int attr); 00171 const ClassInfo* classInfo() const { return &info; } 00172 00173 static const ClassInfo info; 00174 00175 // Link to its interpreter. 00176 // FIXME: Probably accessible without the explicit link. 00177 Interpreter *jsi; 00178 00179 // Current message data. 00180 const QString *msgctxt; 00181 const QHash<QString, QString> *dynctxt; 00182 const QString *msgid; 00183 const QStringList *subs; 00184 const QList<QVariant> *vals; 00185 const QString *final; 00186 const QString *ctry; 00187 00188 // Fallback request handle. 00189 bool *fallback; 00190 00191 // Function register. 00192 QHash<QString, JSObject*> funcs; 00193 QHash<QString, JSValue*> fvals; 00194 QHash<QString, QString> fpaths; 00195 00196 // Ordering of those functions which execute for all messages. 00197 QList<QString> nameForalls; 00198 00199 // Property values per phrase (used by *Prop interface calls). 00200 // Not QStrings, in order to avoid conversion from UTF-8 when 00201 // loading compiled maps (less latency on startup). 00202 QHash<QByteArray, QHash<QByteArray, QByteArray> > phraseProps; 00203 // Unresolved property values per phrase, 00204 // containing the pointer to compiled pmap file handle and offset in it. 00205 QHash<QByteArray, QPair<QFile*, quint64> > phraseUnparsedProps; 00206 QHash<QByteArray, QByteArray> resolveUnparsedProps (const QByteArray &phrase); 00207 // Set of loaded pmap files by paths and file handle pointers. 00208 QSet<QString> loadedPmapPaths; 00209 QSet<QFile*> loadedPmapHandles; 00210 00211 // User config. 00212 TsConfigGroup config; 00213 }; 00214 00215 // ---------------------------------------------------------------------- 00216 // Custom debug and warning output (kdebug not available) 00217 #define DBGP "KTranscript: " 00218 void dbgout (const char*str) { 00219 #ifndef NDEBUG 00220 fprintf(stderr, DBGP"%s\n", str); 00221 #else 00222 Q_UNUSED(str); 00223 #endif 00224 } 00225 template <typename T1> 00226 void dbgout (const char* str, const T1 &a1) { 00227 #ifndef NDEBUG 00228 fprintf(stderr, DBGP"%s\n", QString::fromUtf8(str).arg(a1).toLocal8Bit().data()); 00229 #else 00230 Q_UNUSED(str); Q_UNUSED(a1); 00231 #endif 00232 } 00233 template <typename T1, typename T2> 00234 void dbgout (const char* str, const T1 &a1, const T2 &a2) { 00235 #ifndef NDEBUG 00236 fprintf(stderr, DBGP"%s\n", QString::fromUtf8(str).arg(a1).arg(a2).toLocal8Bit().data()); 00237 #else 00238 Q_UNUSED(str); Q_UNUSED(a1); Q_UNUSED(a2); 00239 #endif 00240 } 00241 template <typename T1, typename T2, typename T3> 00242 void dbgout (const char* str, const T1 &a1, const T2 &a2, const T3 &a3) { 00243 #ifndef NDEBUG 00244 fprintf(stderr, DBGP"%s\n", QString::fromUtf8(str).arg(a1).arg(a2).arg(a3).toLocal8Bit().data()); 00245 #else 00246 Q_UNUSED(str); Q_UNUSED(a1); Q_UNUSED(a2); Q_UNUSED(a3); 00247 #endif 00248 } 00249 00250 #define WARNP "KTranscript: " 00251 void warnout (const char*str) { 00252 fprintf(stderr, WARNP"%s\n", str); 00253 } 00254 template <typename T1> 00255 void warnout (const char* str, const T1 &a1) { 00256 fprintf(stderr, WARNP"%s\n", QString::fromUtf8(str).arg(a1).toLocal8Bit().data()); 00257 } 00258 00259 // ---------------------------------------------------------------------- 00260 // Conversions between QString and KJS UString. 00261 // Taken from kate. 00262 UString::UString(const QString &d) 00263 { 00264 unsigned int len = d.length(); 00265 UChar *dat = static_cast<UChar*>(fastMalloc(sizeof(UChar) * len)); 00266 memcpy(dat, d.unicode(), len * sizeof(UChar)); 00267 m_rep = UString::Rep::create(dat, len); 00268 } 00269 QString UString::qstring() const 00270 { 00271 return QString((QChar*) data(), size()); 00272 } 00273 00274 // ---------------------------------------------------------------------- 00275 // Produces a string out of a KJS exception. 00276 QString expt2str (ExecState *exec) 00277 { 00278 JSValue *expt = exec->exception(); 00279 if ( expt->isObject() 00280 && expt->getObject()->hasProperty(exec, "message")) 00281 { 00282 JSValue *msg = expt->getObject()->get(exec, "message"); 00283 return QString::fromLatin1("Error: %1").arg(msg->getString().qstring()); 00284 } 00285 else 00286 { 00287 QString strexpt = exec->exception()->toString(exec).qstring(); 00288 return QString::fromLatin1("Caught exception: %1").arg(strexpt); 00289 } 00290 } 00291 00292 // ---------------------------------------------------------------------- 00293 // Count number of lines in the string, 00294 // up to and excluding the requested position. 00295 int countLines (const QString &s, int p) 00296 { 00297 int n = 1; 00298 int len = s.length(); 00299 for (int i = 0; i < p && i < len; ++i) { 00300 if (s[i] == QLatin1Char('\n')) { 00301 ++n; 00302 } 00303 } 00304 return n; 00305 } 00306 00307 // ---------------------------------------------------------------------- 00308 // Normalize string key for hash lookups, 00309 QByteArray normKeystr (const QString &raw, bool mayHaveAcc = true) 00310 { 00311 // NOTE: Regexes should not be used here for performance reasons. 00312 // This function may potentially be called thousands of times 00313 // on application startup. 00314 00315 QString key = raw; 00316 00317 // Strip all whitespace. 00318 int len = key.length(); 00319 QString nkey; 00320 for (int i = 0; i < len; ++i) { 00321 QChar c = key[i]; 00322 if (!c.isSpace()) { 00323 nkey.append(c); 00324 } 00325 } 00326 key = nkey; 00327 00328 // Strip accelerator marker. 00329 if (mayHaveAcc) { 00330 key = removeAcceleratorMarker(key); 00331 } 00332 00333 // Convert to lower case. 00334 key = key.toLower(); 00335 00336 return key.toUtf8(); 00337 } 00338 00339 // ---------------------------------------------------------------------- 00340 // Trim multiline string in a "smart" way: 00341 // Remove leading and trailing whitespace up to and including first 00342 // newline from that side, if there is one; otherwise, don't touch. 00343 QString trimSmart (const QString &raw) 00344 { 00345 // NOTE: This could be done by a single regex, but is not due to 00346 // performance reasons. 00347 // This function may potentially be called thousands of times 00348 // on application startup. 00349 00350 int len = raw.length(); 00351 00352 int is = 0; 00353 while (is < len && raw[is].isSpace() && raw[is] != QLatin1Char('\n')) { 00354 ++is; 00355 } 00356 if (is >= len || raw[is] != QLatin1Char('\n')) { 00357 is = -1; 00358 } 00359 00360 int ie = len - 1; 00361 while (ie >= 0 && raw[ie].isSpace() && raw[ie] != QLatin1Char('\n')) { 00362 --ie; 00363 } 00364 if (ie < 0 || raw[ie] != QLatin1Char('\n')) { 00365 ie = len; 00366 } 00367 00368 return raw.mid(is + 1, ie - is - 1); 00369 } 00370 00371 // ---------------------------------------------------------------------- 00372 // Produce a JavaScript object out of Qt variant. 00373 JSValue *variantToJsValue (const QVariant &val) 00374 { 00375 QVariant::Type vtype = val.type(); 00376 if (vtype == QVariant::String) 00377 return jsString(val.toString()); 00378 else if ( vtype == QVariant::Double \ 00379 || vtype == QVariant::Int || vtype == QVariant::UInt \ 00380 || vtype == QVariant::LongLong || vtype == QVariant::ULongLong) 00381 return jsNumber(val.toDouble()); 00382 else 00383 return jsUndefined(); 00384 } 00385 00386 // ---------------------------------------------------------------------- 00387 // Parse ini-style config file, 00388 // returning content as hash of hashes by group and key. 00389 // Parsing is not fussy, it will read what it can. 00390 TsConfig readConfig (const QString &fname) 00391 { 00392 TsConfig config; 00393 // Add empty group. 00394 TsConfig::iterator configGroup; 00395 configGroup = config.insert(QString(), TsConfigGroup()); 00396 00397 QFile file(fname); 00398 if (!file.open(QIODevice::ReadOnly)) { 00399 return config; 00400 } 00401 QTextStream stream(&file); 00402 stream.setCodec("UTF-8"); 00403 while (!stream.atEnd()) { 00404 QString line = stream.readLine(); 00405 int p1, p2; 00406 00407 // Remove comment from the line. 00408 p1 = line.indexOf(QLatin1Char('#')); 00409 if (p1 >= 0) { 00410 line = line.left(p1); 00411 } 00412 line = line.trimmed(); 00413 if (line.isEmpty()) { 00414 continue; 00415 } 00416 00417 if (line[0] == QLatin1Char('[')) { 00418 // Group switch. 00419 p1 = 0; 00420 p2 = line.indexOf(QLatin1Char(']'), p1 + 1); 00421 if (p2 < 0) { 00422 continue; 00423 } 00424 QString group = line.mid(p1 + 1, p2 - p1 - 1).trimmed(); 00425 configGroup = config.find(group); 00426 if (configGroup == config.end()) { 00427 // Add new group. 00428 configGroup = config.insert(group, TsConfigGroup()); 00429 } 00430 } else { 00431 // Field. 00432 p1 = line.indexOf(QLatin1Char('=')); 00433 if (p1 < 0) { 00434 continue; 00435 } 00436 QString field = line.left(p1).trimmed(); 00437 QString value = line.mid(p1 + 1).trimmed(); 00438 if (!field.isEmpty()) { 00439 (*configGroup)[field] = value; 00440 } 00441 } 00442 } 00443 file.close(); 00444 00445 return config; 00446 } 00447 00448 // ---------------------------------------------------------------------- 00449 // Dynamic loading. 00450 K_GLOBAL_STATIC(KTranscriptImp, globalKTI) 00451 extern "C" 00452 { 00453 KDE_EXPORT KTranscript *load_transcript () 00454 { 00455 return globalKTI; 00456 } 00457 } 00458 00459 // ---------------------------------------------------------------------- 00460 // KTranscript definitions. 00461 00462 KTranscriptImp::KTranscriptImp () 00463 { 00464 // Load user configuration. 00465 const QString tsConfigPath = QDir::homePath() + QLatin1Char('/') + QLatin1String(".transcriptrc"); 00466 config = readConfig(tsConfigPath); 00467 } 00468 00469 KTranscriptImp::~KTranscriptImp () 00470 { 00471 // FIXME: vallgrind shows an afwul lot of "invalid read" in WTF:: stuff 00472 // when deref is called... Are we leaking somewhere? 00473 //foreach (Scriptface *sface, m_sface.values()) 00474 // sface->jsi->deref(); 00475 } 00476 00477 QString KTranscriptImp::eval (const QList<QVariant> &argv, 00478 const QString &lang, 00479 const QString &ctry, 00480 const QString &msgctxt, 00481 const QHash<QString, QString> &dynctxt, 00482 const QString &msgid, 00483 const QStringList &subs, 00484 const QList<QVariant> &vals, 00485 const QString &final, 00486 QList<QStringList> &mods, 00487 QString &error, 00488 bool &fallback) 00489 { 00490 //error = "debug"; return QString(); 00491 00492 error.clear(); // empty error message means successful evaluation 00493 fallback = false; // fallback not requested 00494 00495 #if 0 00496 // FIXME: Maybe not needed, as KJS has no native outside access? 00497 // Unportable (needs unistd.h)? 00498 00499 // If effective user id is root and real user id is not root. 00500 if (geteuid() == 0 && getuid() != 0) 00501 { 00502 // Since scripts are user input, and the program is running with 00503 // root permissions while real user is not root, do not invoke 00504 // scripting at all, to prevent exploits. 00505 error = "Security block: trying to execute a script in suid environment."; 00506 return QString(); 00507 } 00508 #endif 00509 00510 // Load any new modules and clear the list. 00511 if (!mods.isEmpty()) 00512 { 00513 loadModules(mods, error); 00514 mods.clear(); 00515 if (!error.isEmpty()) 00516 return QString(); 00517 } 00518 00519 // Add interpreters for new languages. 00520 // (though it should never happen here, but earlier when loading modules; 00521 // this also means there are no calls set, so the unregistered call error 00522 // below will be reported). 00523 if (!m_sface.contains(lang)) 00524 setupInterpreter(lang); 00525 00526 // Shortcuts. 00527 Scriptface *sface = m_sface[lang]; 00528 ExecState *exec = sface->jsi->globalExec(); 00529 JSObject *gobj = sface->jsi->globalObject(); 00530 00531 // Link current message data for script-side interface. 00532 sface->msgctxt = &msgctxt; 00533 sface->dynctxt = &dynctxt; 00534 sface->msgid = &msgid; 00535 sface->subs = &subs; 00536 sface->vals = &vals; 00537 sface->final = &final; 00538 sface->fallback = &fallback; 00539 sface->ctry = &ctry; 00540 00541 // Find corresponding JS function. 00542 int argc = argv.size(); 00543 if (argc < 1) 00544 { 00545 //error = "At least the call name must be supplied."; 00546 // Empty interpolation is OK, possibly used just to initialize 00547 // at a given point (e.g. for Ts.setForall() to start having effect). 00548 return QString(); 00549 } 00550 QString funcName = argv[0].toString(); 00551 if (!sface->funcs.contains(funcName)) 00552 { 00553 error = QString::fromLatin1("Unregistered call to '%1'.").arg(funcName); 00554 return QString(); 00555 } 00556 JSObject *func = sface->funcs[funcName]; 00557 JSValue *fval = sface->fvals[funcName]; 00558 00559 // Recover module path from the time of definition of this call, 00560 // for possible load calls. 00561 currentModulePath = sface->fpaths[funcName]; 00562 00563 // Execute function. 00564 List arglist; 00565 for (int i = 1; i < argc; ++i) 00566 arglist.append(variantToJsValue(argv[i])); 00567 JSValue *val; 00568 if (fval->isObject()) 00569 val = func->callAsFunction(exec, fval->getObject(), arglist); 00570 else // no object associated to this function, use global 00571 val = func->callAsFunction(exec, gobj, arglist); 00572 00573 if (fallback) 00574 // Fallback to ordinary translation requested. 00575 { 00576 // Possibly clear exception state. 00577 if (exec->hadException()) 00578 exec->clearException(); 00579 00580 return QString(); 00581 } 00582 else if (!exec->hadException()) 00583 // Evaluation successful. 00584 { 00585 if (val->isString()) 00586 // Good to go. 00587 { 00588 return val->getString().qstring(); 00589 } 00590 else 00591 // Accept only strings. 00592 { 00593 QString strval = val->toString(exec).qstring(); 00594 error = QString::fromLatin1("Non-string return value: %1").arg(strval); 00595 return QString(); 00596 } 00597 } 00598 else 00599 // Exception raised. 00600 { 00601 error = expt2str(exec); 00602 00603 exec->clearException(); 00604 00605 return QString(); 00606 } 00607 } 00608 00609 QStringList KTranscriptImp::postCalls (const QString &lang) 00610 { 00611 // Return no calls if scripting was not already set up for this language. 00612 // NOTE: This shouldn't happen, as postCalls cannot be called in such case. 00613 if (!m_sface.contains(lang)) 00614 return QStringList(); 00615 00616 // Shortcuts. 00617 Scriptface *sface = m_sface[lang]; 00618 00619 return sface->nameForalls; 00620 } 00621 00622 void KTranscriptImp::loadModules (const QList<QStringList> &mods, 00623 QString &error) 00624 { 00625 QList<QString> modErrors; 00626 00627 foreach (const QStringList &mod, mods) 00628 { 00629 QString mpath = mod[0]; 00630 QString mlang = mod[1]; 00631 00632 // Add interpreters for new languages. 00633 if (!m_sface.contains(mlang)) 00634 setupInterpreter(mlang); 00635 00636 // Setup current module path for loading submodules. 00637 // (sort of closure over invocations of loadf) 00638 int posls = mpath.lastIndexOf(QLatin1Char('/')); 00639 if (posls < 1) 00640 { 00641 modErrors.append(QString::fromLatin1("Funny module path '%1', skipping.") 00642 .arg(mpath)); 00643 continue; 00644 } 00645 currentModulePath = mpath.left(posls); 00646 QString fname = mpath.mid(posls + 1); 00647 // Scriptface::loadf() wants no extension on the filename 00648 fname = fname.left(fname.lastIndexOf(QLatin1Char('.'))); 00649 00650 // Load the module. 00651 ExecState *exec = m_sface[mlang]->jsi->globalExec(); 00652 List alist; 00653 alist.append(jsString(fname)); 00654 00655 m_sface[mlang]->loadf(exec, alist); 00656 00657 // Handle any exception. 00658 if (exec->hadException()) 00659 { 00660 modErrors.append(expt2str(exec)); 00661 exec->clearException(); 00662 } 00663 } 00664 00665 // Unset module path. 00666 currentModulePath.clear(); 00667 00668 foreach (const QString &merr, modErrors) 00669 error.append(merr + QLatin1Char('\n')); 00670 } 00671 00672 KJS_QT_UNICODE_IMPL 00673 00674 #define SFNAME "Ts" 00675 void KTranscriptImp::setupInterpreter (const QString &lang) 00676 { 00677 // Create new interpreter. 00678 Interpreter *jsi = new Interpreter; 00679 KJS_QT_UNICODE_SET; 00680 jsi->initGlobalObject(); 00681 jsi->ref(); 00682 00683 // Add scripting interface into the interpreter. 00684 // NOTE: Config may not contain an entry for the language, in which case 00685 // it is automatically constructed as an empty hash. This is intended. 00686 Scriptface *sface = new Scriptface(jsi->globalExec(), config[lang]); 00687 jsi->globalObject()->put(jsi->globalExec(), SFNAME, sface, 00688 DontDelete|ReadOnly); 00689 00690 // Store scriptface and link to its interpreter. 00691 sface->jsi = jsi; 00692 m_sface[lang] = sface; 00693 00694 //dbgout("=====> Created interpreter for '%1'", lang); 00695 } 00696 00697 // ---------------------------------------------------------------------- 00698 // Scriptface internal mechanics. 00699 #include "ktranscript.lut.h" 00700 00701 /* Source for ScriptfaceProtoTable. 00702 @begin ScriptfaceProtoTable 2 00703 load Scriptface::Load DontDelete|ReadOnly|Function 0 00704 setcall Scriptface::Setcall DontDelete|ReadOnly|Function 3 00705 hascall Scriptface::Hascall DontDelete|ReadOnly|Function 1 00706 acall Scriptface::Acall DontDelete|ReadOnly|Function 0 00707 setcallForall Scriptface::SetcallForall DontDelete|ReadOnly|Function 3 00708 fallback Scriptface::Fallback DontDelete|ReadOnly|Function 0 00709 nsubs Scriptface::Nsubs DontDelete|ReadOnly|Function 0 00710 subs Scriptface::Subs DontDelete|ReadOnly|Function 1 00711 vals Scriptface::Vals DontDelete|ReadOnly|Function 1 00712 msgctxt Scriptface::Msgctxt DontDelete|ReadOnly|Function 0 00713 dynctxt Scriptface::Dynctxt DontDelete|ReadOnly|Function 1 00714 msgid Scriptface::Msgid DontDelete|ReadOnly|Function 0 00715 msgkey Scriptface::Msgkey DontDelete|ReadOnly|Function 0 00716 msgstrf Scriptface::Msgstrf DontDelete|ReadOnly|Function 0 00717 dbgputs Scriptface::Dbgputs DontDelete|ReadOnly|Function 1 00718 warnputs Scriptface::Warnputs DontDelete|ReadOnly|Function 1 00719 localeCountry Scriptface::LocaleCountry DontDelete|ReadOnly|Function 0 00720 normKey Scriptface::NormKey DontDelete|ReadOnly|Function 1 00721 loadProps Scriptface::LoadProps DontDelete|ReadOnly|Function 0 00722 getProp Scriptface::GetProp DontDelete|ReadOnly|Function 2 00723 setProp Scriptface::SetProp DontDelete|ReadOnly|Function 3 00724 toUpperFirst Scriptface::ToUpperFirst DontDelete|ReadOnly|Function 2 00725 toLowerFirst Scriptface::ToLowerFirst DontDelete|ReadOnly|Function 2 00726 getConfString Scriptface::GetConfString DontDelete|ReadOnly|Function 2 00727 getConfBool Scriptface::GetConfBool DontDelete|ReadOnly|Function 2 00728 getConfNumber Scriptface::GetConfNumber DontDelete|ReadOnly|Function 2 00729 @end 00730 */ 00731 /* Source for ScriptfaceTable. 00732 @begin ScriptfaceTable 0 00733 @end 00734 */ 00735 00736 KJS_DEFINE_PROTOTYPE(ScriptfaceProto) 00737 KJS_IMPLEMENT_PROTOFUNC(ScriptfaceProtoFunc) 00738 KJS_IMPLEMENT_PROTOTYPE("Scriptface", ScriptfaceProto, ScriptfaceProtoFunc, ObjectPrototype) 00739 00740 const ClassInfo Scriptface::info = {"Scriptface", 0, &ScriptfaceTable, 0}; 00741 00742 Scriptface::Scriptface (ExecState *exec, const TsConfigGroup &config_) 00743 : JSObject(ScriptfaceProto::self(exec)), fallback(NULL), config(config_) 00744 {} 00745 00746 Scriptface::~Scriptface () 00747 { 00748 qDeleteAll(loadedPmapHandles); 00749 } 00750 00751 bool Scriptface::getOwnPropertySlot (ExecState *exec, const Identifier& propertyName, PropertySlot& slot) 00752 { 00753 return getStaticValueSlot<Scriptface, JSObject>(exec, &ScriptfaceTable, this, propertyName, slot); 00754 } 00755 00756 JSValue *Scriptface::getValueProperty (ExecState * /*exec*/, int token) const 00757 { 00758 switch (token) { 00759 default: 00760 dbgout("Scriptface::getValueProperty: Unknown property id %1", token); 00761 } 00762 return jsUndefined(); 00763 } 00764 00765 void Scriptface::put (ExecState *exec, const Identifier &propertyName, JSValue *value, int attr) 00766 { 00767 lookupPut<Scriptface, JSObject>(exec, propertyName, value, attr, &ScriptfaceTable, this); 00768 } 00769 00770 void Scriptface::putValueProperty (ExecState * /*exec*/, int token, JSValue * /*value*/, int /*attr*/) 00771 { 00772 switch(token) { 00773 default: 00774 dbgout("Scriptface::putValueProperty: Unknown property id %1", token); 00775 } 00776 } 00777 00778 #define CALLARG(i) (args.size() > i ? args[i] : jsNull()) 00779 JSValue *ScriptfaceProtoFunc::callAsFunction (ExecState *exec, JSObject *thisObj, const List &args) 00780 { 00781 if (!thisObj->inherits(&Scriptface::info)) { 00782 return throwError(exec, TypeError); 00783 } 00784 Scriptface *obj = static_cast<Scriptface*>(thisObj); 00785 switch (id) { 00786 case Scriptface::Load: 00787 return obj->loadf(exec, args); 00788 case Scriptface::Setcall: 00789 return obj->setcallf(exec, CALLARG(0), CALLARG(1), CALLARG(2)); 00790 case Scriptface::Hascall: 00791 return obj->hascallf(exec, CALLARG(0)); 00792 case Scriptface::Acall: 00793 return obj->acallf(exec, args); 00794 case Scriptface::SetcallForall: 00795 return obj->setcallForallf(exec, CALLARG(0), CALLARG(1), CALLARG(2)); 00796 case Scriptface::Fallback: 00797 return obj->fallbackf(exec); 00798 case Scriptface::Nsubs: 00799 return obj->nsubsf(exec); 00800 case Scriptface::Subs: 00801 return obj->subsf(exec, CALLARG(0)); 00802 case Scriptface::Vals: 00803 return obj->valsf(exec, CALLARG(0)); 00804 case Scriptface::Msgctxt: 00805 return obj->msgctxtf(exec); 00806 case Scriptface::Dynctxt: 00807 return obj->dynctxtf(exec, CALLARG(0)); 00808 case Scriptface::Msgid: 00809 return obj->msgidf(exec); 00810 case Scriptface::Msgkey: 00811 return obj->msgkeyf(exec); 00812 case Scriptface::Msgstrf: 00813 return obj->msgstrff(exec); 00814 case Scriptface::Dbgputs: 00815 return obj->dbgputsf(exec, CALLARG(0)); 00816 case Scriptface::Warnputs: 00817 return obj->warnputsf(exec, CALLARG(0)); 00818 case Scriptface::LocaleCountry: 00819 return obj->localeCountryf(exec); 00820 case Scriptface::NormKey: 00821 return obj->normKeyf(exec, CALLARG(0)); 00822 case Scriptface::LoadProps: 00823 return obj->loadPropsf(exec, args); 00824 case Scriptface::GetProp: 00825 return obj->getPropf(exec, CALLARG(0), CALLARG(1)); 00826 case Scriptface::SetProp: 00827 return obj->setPropf(exec, CALLARG(0), CALLARG(1), CALLARG(2)); 00828 case Scriptface::ToUpperFirst: 00829 return obj->toUpperFirstf(exec, CALLARG(0), CALLARG(1)); 00830 case Scriptface::ToLowerFirst: 00831 return obj->toLowerFirstf(exec, CALLARG(0), CALLARG(1)); 00832 case Scriptface::GetConfString: 00833 return obj->getConfStringf(exec, CALLARG(0), CALLARG(1)); 00834 case Scriptface::GetConfBool: 00835 return obj->getConfBoolf(exec, CALLARG(0), CALLARG(1)); 00836 case Scriptface::GetConfNumber: 00837 return obj->getConfNumberf(exec, CALLARG(0), CALLARG(1)); 00838 default: 00839 return jsUndefined(); 00840 } 00841 } 00842 00843 // ---------------------------------------------------------------------- 00844 // Scriptface interface functions. 00845 #define SPREF SFNAME"." 00846 00847 JSValue *Scriptface::loadf (ExecState *exec, const List &fnames) 00848 { 00849 if (globalKTI->currentModulePath.isEmpty()) 00850 return throwError(exec, GeneralError, 00851 SPREF"load: no current module path, aiiie..."); 00852 00853 for (int i = 0; i < fnames.size(); ++i) 00854 if (!fnames[i]->isString()) 00855 return throwError(exec, TypeError, 00856 SPREF"load: expected string as file name"); 00857 00858 for (int i = 0; i < fnames.size(); ++i) 00859 { 00860 QString qfname = fnames[i]->getString().qstring(); 00861 QString qfpath = globalKTI->currentModulePath + QLatin1Char('/') + qfname + QLatin1String(".js"); 00862 00863 QFile file(qfpath); 00864 if (!file.open(QIODevice::ReadOnly)) 00865 return throwError(exec, GeneralError, 00866 QString::fromLatin1(SPREF"load: cannot read file '%1'") \ 00867 .arg(qfpath)); 00868 00869 QTextStream stream(&file); 00870 stream.setCodec("UTF-8"); 00871 QString source = stream.readAll(); 00872 file.close(); 00873 00874 Completion comp = jsi->evaluate(qfpath, 0, source); 00875 00876 if (comp.complType() == Throw) 00877 { 00878 JSValue *exval = comp.value(); 00879 ExecState *exec = jsi->globalExec(); 00880 QString msg = exval->toString(exec).qstring(); 00881 00882 QString line; 00883 if (exval->type() == ObjectType) 00884 { 00885 JSValue *lval = exval->getObject()->get(exec, "line"); 00886 if (lval->type() == NumberType) 00887 line = QString::number(lval->toInt32(exec)); 00888 } 00889 00890 return throwError(exec, TypeError, 00891 QString::fromLatin1("at %1:%2: %3") 00892 .arg(qfpath, line, msg)); 00893 } 00894 dbgout("Loaded module: %1", qfpath); 00895 } 00896 00897 return jsUndefined(); 00898 } 00899 00900 JSValue *Scriptface::setcallf (ExecState *exec, JSValue *name, 00901 JSValue *func, JSValue *fval) 00902 { 00903 if (!name->isString()) 00904 return throwError(exec, TypeError, 00905 SPREF"setcall: expected string as first argument"); 00906 if ( !func->isObject() 00907 || !func->getObject()->implementsCall()) 00908 return throwError(exec, TypeError, 00909 SPREF"setcall: expected function as second argument"); 00910 if (!(fval->isObject() || fval->isNull())) 00911 return throwError(exec, TypeError, 00912 SPREF"setcall: expected object or null as third argument"); 00913 00914 QString qname = name->toString(exec).qstring(); 00915 funcs[qname] = func->getObject(); 00916 fvals[qname] = fval; 00917 00918 // Register values to keep GC from collecting them. Is this needed? 00919 put(exec, Identifier(QString::fromLatin1("#:f<%1>").arg(qname)), func, Internal); 00920 put(exec, Identifier(QString::fromLatin1("#:o<%1>").arg(qname)), fval, Internal); 00921 00922 // Set current module path as module path for this call, 00923 // in case it contains load subcalls. 00924 fpaths[qname] = globalKTI->currentModulePath; 00925 00926 return jsUndefined(); 00927 } 00928 00929 JSValue *Scriptface::hascallf (ExecState *exec, JSValue *name) 00930 { 00931 if (!name->isString()) 00932 return throwError(exec, TypeError, 00933 SPREF"hascall: expected string as first argument"); 00934 00935 QString qname = name->toString(exec).qstring(); 00936 return jsBoolean(funcs.contains(qname)); 00937 } 00938 00939 JSValue *Scriptface::acallf (ExecState *exec, const List &argv) 00940 { 00941 if (argv.size() < 1) { 00942 return throwError(exec, SyntaxError, 00943 SPREF"acall: expected at least one argument (call name)"); 00944 } 00945 if (!argv[0]->isString()) { 00946 return throwError(exec, SyntaxError, 00947 SPREF"acall: expected string as first argument (call name)"); 00948 } 00949 00950 // Get the function and its context object. 00951 QString callname = argv[0]->getString().qstring(); 00952 if (!funcs.contains(callname)) { 00953 return throwError(exec, EvalError, 00954 QString::fromLatin1(SPREF"acall: unregistered call to '%1'").arg(callname)); 00955 } 00956 JSObject *func = funcs[callname]; 00957 JSValue *fval = fvals[callname]; 00958 00959 // Recover module path from the time of definition of this call, 00960 // for possible load calls. 00961 globalKTI->currentModulePath = fpaths[callname]; 00962 00963 // Execute function. 00964 List arglist; 00965 for (int i = 1; i < argv.size(); ++i) 00966 arglist.append(argv[i]); 00967 JSValue *val; 00968 if (fval->isObject()) { 00969 // Call function with the context object. 00970 val = func->callAsFunction(exec, fval->getObject(), arglist); 00971 } 00972 else { 00973 // No context object associated to this function, use global. 00974 val = func->callAsFunction(exec, jsi->globalObject(), arglist); 00975 } 00976 return val; 00977 } 00978 00979 JSValue *Scriptface::setcallForallf (ExecState *exec, JSValue *name, 00980 JSValue *func, JSValue *fval) 00981 { 00982 if (!name->isString()) 00983 return throwError(exec, TypeError, 00984 SPREF"setcallForall: expected string as first argument"); 00985 if ( !func->isObject() 00986 || !func->getObject()->implementsCall()) 00987 return throwError(exec, TypeError, 00988 SPREF"setcallForall: expected function as second argument"); 00989 if (!(fval->isObject() || fval->isNull())) 00990 return throwError(exec, TypeError, 00991 SPREF"setcallForall: expected object or null as third argument"); 00992 00993 QString qname = name->toString(exec).qstring(); 00994 funcs[qname] = func->getObject(); 00995 fvals[qname] = fval; 00996 00997 // Register values to keep GC from collecting them. Is this needed? 00998 put(exec, Identifier(QString::fromLatin1("#:fall<%1>").arg(qname)), func, Internal); 00999 put(exec, Identifier(QString::fromLatin1("#:oall<%1>").arg(qname)), fval, Internal); 01000 01001 // Set current module path as module path for this call, 01002 // in case it contains load subcalls. 01003 fpaths[qname] = globalKTI->currentModulePath; 01004 01005 // Put in the queue order for execution on all messages. 01006 nameForalls.append(qname); 01007 01008 return jsUndefined(); 01009 } 01010 01011 JSValue *Scriptface::fallbackf (ExecState *exec) 01012 { 01013 Q_UNUSED(exec); 01014 if (fallback != NULL) 01015 *fallback = true; 01016 return jsUndefined(); 01017 } 01018 01019 JSValue *Scriptface::nsubsf (ExecState *exec) 01020 { 01021 Q_UNUSED(exec); 01022 return jsNumber(subs->size()); 01023 } 01024 01025 JSValue *Scriptface::subsf (ExecState *exec, JSValue *index) 01026 { 01027 if (!index->isNumber()) 01028 return throwError(exec, TypeError, 01029 SPREF"subs: expected number as first argument"); 01030 01031 int i = qRound(index->getNumber()); 01032 if (i < 0 || i >= subs->size()) 01033 return throwError(exec, RangeError, 01034 SPREF"subs: index out of range"); 01035 01036 return jsString(subs->at(i)); 01037 } 01038 01039 JSValue *Scriptface::valsf (ExecState *exec, JSValue *index) 01040 { 01041 if (!index->isNumber()) 01042 return throwError(exec, TypeError, 01043 SPREF"vals: expected number as first argument"); 01044 01045 int i = qRound(index->getNumber()); 01046 if (i < 0 || i >= vals->size()) 01047 return throwError(exec, RangeError, 01048 SPREF"vals: index out of range"); 01049 01050 return variantToJsValue(vals->at(i)); 01051 } 01052 01053 JSValue *Scriptface::msgctxtf (ExecState *exec) 01054 { 01055 Q_UNUSED(exec); 01056 return jsString(*msgctxt); 01057 } 01058 01059 JSValue *Scriptface::dynctxtf (ExecState *exec, JSValue *key) 01060 { 01061 if (!key->isString()) 01062 return throwError(exec, TypeError, 01063 SPREF"dynctxt: expected string as first argument"); 01064 01065 QString qkey = key->getString().qstring(); 01066 if (dynctxt->contains(qkey)) { 01067 return jsString(dynctxt->value(qkey)); 01068 } 01069 return jsUndefined(); 01070 } 01071 01072 JSValue *Scriptface::msgidf (ExecState *exec) 01073 { 01074 Q_UNUSED(exec); 01075 return jsString(*msgid); 01076 } 01077 01078 JSValue *Scriptface::msgkeyf (ExecState *exec) 01079 { 01080 Q_UNUSED(exec); 01081 return jsString(QString(*msgctxt + QLatin1Char('|') + *msgid)); 01082 } 01083 01084 JSValue *Scriptface::msgstrff (ExecState *exec) 01085 { 01086 Q_UNUSED(exec); 01087 return jsString(*final); 01088 } 01089 01090 JSValue *Scriptface::dbgputsf (ExecState *exec, JSValue *str) 01091 { 01092 if (!str->isString()) 01093 return throwError(exec, TypeError, 01094 SPREF"dbgputs: expected string as first argument"); 01095 01096 QString qstr = str->getString().qstring(); 01097 01098 dbgout("[JS-debug] %1", qstr); 01099 01100 return jsUndefined(); 01101 } 01102 01103 JSValue *Scriptface::warnputsf (ExecState *exec, JSValue *str) 01104 { 01105 if (!str->isString()) 01106 return throwError(exec, TypeError, 01107 SPREF"warnputs: expected string as first argument"); 01108 01109 QString qstr = str->getString().qstring(); 01110 01111 warnout("[JS-warning] %1", qstr); 01112 01113 return jsUndefined(); 01114 } 01115 01116 JSValue *Scriptface::localeCountryf (ExecState *exec) 01117 { 01118 Q_UNUSED(exec); 01119 return jsString(*ctry); 01120 } 01121 01122 JSValue *Scriptface::normKeyf (ExecState *exec, JSValue *phrase) 01123 { 01124 if (!phrase->isString()) { 01125 return throwError(exec, TypeError, 01126 SPREF"normKey: expected string as argument"); 01127 } 01128 01129 QByteArray nqphrase = normKeystr(phrase->toString(exec).qstring()); 01130 return jsString(QString::fromUtf8(nqphrase)); 01131 } 01132 01133 JSValue *Scriptface::loadPropsf (ExecState *exec, const List &fnames) 01134 { 01135 if (globalKTI->currentModulePath.isEmpty()) { 01136 return throwError(exec, GeneralError, 01137 SPREF"loadProps: no current module path, aiiie..."); 01138 } 01139 01140 for (int i = 0; i < fnames.size(); ++i) { 01141 if (!fnames[i]->isString()) { 01142 return throwError(exec, TypeError, 01143 SPREF"loadProps: expected string as file name"); 01144 } 01145 } 01146 01147 for (int i = 0; i < fnames.size(); ++i) 01148 { 01149 QString qfname = fnames[i]->getString().qstring(); 01150 QString qfpath_base = globalKTI->currentModulePath + QLatin1Char('/') + qfname; 01151 01152 // Determine which kind of map is available. 01153 // Give preference to compiled map. 01154 QString qfpath = qfpath_base + QLatin1String(".pmapc"); 01155 bool haveCompiled = true; 01156 QFile file_check(qfpath); 01157 if (!file_check.open(QIODevice::ReadOnly)) { 01158 haveCompiled = false; 01159 qfpath = qfpath_base + QLatin1String(".pmap"); 01160 QFile file_check(qfpath); 01161 if (!file_check.open(QIODevice::ReadOnly)) { 01162 return throwError(exec, GeneralError, 01163 QString::fromLatin1(SPREF"loadProps: cannot read map '%1'") 01164 .arg(qfpath_base)); 01165 } 01166 } 01167 file_check.close(); 01168 01169 // Load from appropriate type of map. 01170 if (!loadedPmapPaths.contains(qfpath)) { 01171 QString errorString; 01172 if (haveCompiled) { 01173 errorString = loadProps_bin(qfpath); 01174 } 01175 else { 01176 errorString = loadProps_text(qfpath); 01177 } 01178 if (!errorString.isEmpty()) { 01179 return throwError(exec, SyntaxError, errorString); 01180 } 01181 dbgout("Loaded property map: %1", qfpath); 01182 loadedPmapPaths.insert(qfpath); 01183 } 01184 } 01185 01186 return jsUndefined(); 01187 } 01188 01189 JSValue *Scriptface::getPropf (ExecState *exec, JSValue *phrase, JSValue *prop) 01190 { 01191 if (!phrase->isString()) { 01192 return throwError(exec, TypeError, 01193 SPREF"getProp: expected string as first argument"); 01194 } 01195 if (!prop->isString()) { 01196 return throwError(exec, TypeError, 01197 SPREF"getProp: expected string as second argument"); 01198 } 01199 01200 QByteArray qphrase = normKeystr(phrase->toString(exec).qstring()); 01201 QHash<QByteArray, QByteArray> props = phraseProps.value(qphrase); 01202 if (props.isEmpty()) { 01203 props = resolveUnparsedProps(qphrase); 01204 } 01205 if (!props.isEmpty()) { 01206 QByteArray qprop = normKeystr(prop->toString(exec).qstring()); 01207 QByteArray qval = props.value(qprop); 01208 if (!qval.isEmpty()) { 01209 return jsString(QString::fromUtf8(qval)); 01210 } 01211 } 01212 return jsUndefined(); 01213 } 01214 01215 JSValue *Scriptface::setPropf (ExecState *exec, JSValue *phrase, JSValue *prop, JSValue *value) 01216 { 01217 if (!phrase->isString()) { 01218 return throwError(exec, TypeError, 01219 SPREF"setProp: expected string as first argument"); 01220 } 01221 if (!prop->isString()) { 01222 return throwError(exec, TypeError, 01223 SPREF"setProp: expected string as second argument"); 01224 } 01225 if (!value->isString()) { 01226 return throwError(exec, TypeError, 01227 SPREF"setProp: expected string as third argument"); 01228 } 01229 01230 QByteArray qphrase = normKeystr(phrase->toString(exec).qstring()); 01231 QByteArray qprop = normKeystr(prop->toString(exec).qstring()); 01232 QByteArray qvalue = value->toString(exec).qstring().toUtf8(); 01233 // Any non-existent key in first or second-level hash will be created. 01234 phraseProps[qphrase][qprop] = qvalue; 01235 return jsUndefined(); 01236 } 01237 01238 static QString toCaseFirst (const QString &qstr, int qnalt, bool toupper) 01239 { 01240 static const QLatin1String head("~@"); 01241 static const int hlen = 2; //head.length() 01242 01243 // If the first letter is found within an alternatives directive, 01244 // change case of the first letter in each of the alternatives. 01245 QString qstrcc = qstr; 01246 int len = qstr.length(); 01247 QChar altSep; 01248 int remainingAlts = 0; 01249 bool checkCase = true; 01250 int numChcased = 0; 01251 int i = 0; 01252 while (i < len) { 01253 QChar c = qstr[i]; 01254 01255 if (qnalt && !remainingAlts && qstr.mid(i, hlen) == head) { 01256 // An alternatives directive is just starting. 01257 i += 2; 01258 if (i >= len) break; // malformed directive, bail out 01259 // Record alternatives separator, set number of remaining 01260 // alternatives, reactivate case checking. 01261 altSep = qstrcc[i]; 01262 remainingAlts = qnalt; 01263 checkCase = true; 01264 } 01265 else if (remainingAlts && c == altSep) { 01266 // Alternative separator found, reduce number of remaining 01267 // alternatives and reactivate case checking. 01268 --remainingAlts; 01269 checkCase = true; 01270 } 01271 else if (checkCase && c.isLetter()) { 01272 // Case check is active and the character is a letter; change case. 01273 if (toupper) { 01274 qstrcc[i] = c.toUpper(); 01275 } else { 01276 qstrcc[i] = c.toLower(); 01277 } 01278 ++numChcased; 01279 // No more case checks until next alternatives separator. 01280 checkCase = false; 01281 } 01282 01283 // If any letter has been changed, and there are no more alternatives 01284 // to be processed, we're done. 01285 if (numChcased > 0 && remainingAlts == 0) { 01286 break; 01287 } 01288 01289 // Go to next character. 01290 ++i; 01291 } 01292 01293 return qstrcc; 01294 } 01295 01296 JSValue *Scriptface::toUpperFirstf (ExecState *exec, 01297 JSValue *str, JSValue *nalt) 01298 { 01299 if (!str->isString()) { 01300 return throwError(exec, TypeError, 01301 SPREF"toUpperFirst: expected string as first argument"); 01302 } 01303 if (!(nalt->isNumber() || nalt->isNull())) { 01304 return throwError(exec, TypeError, 01305 SPREF"toUpperFirst: expected number as second argument"); 01306 } 01307 01308 QString qstr = str->toString(exec).qstring(); 01309 int qnalt = nalt->isNull() ? 0 : nalt->toInteger(exec); 01310 01311 QString qstruc = toCaseFirst(qstr, qnalt, true); 01312 01313 return jsString(qstruc); 01314 } 01315 01316 JSValue *Scriptface::toLowerFirstf (ExecState *exec, 01317 JSValue *str, JSValue *nalt) 01318 { 01319 if (!str->isString()) { 01320 return throwError(exec, TypeError, 01321 SPREF"toLowerFirst: expected string as first argument"); 01322 } 01323 if (!(nalt->isNumber() || nalt->isNull())) { 01324 return throwError(exec, TypeError, 01325 SPREF"toLowerFirst: expected number as second argument"); 01326 } 01327 01328 QString qstr = str->toString(exec).qstring(); 01329 int qnalt = nalt->isNull() ? 0 : nalt->toInteger(exec); 01330 01331 QString qstrlc = toCaseFirst(qstr, qnalt, false); 01332 01333 return jsString(qstrlc); 01334 } 01335 01336 JSValue *Scriptface::getConfStringf (ExecState *exec, 01337 JSValue *key, JSValue *dval) 01338 { 01339 if (!key->isString()) { 01340 return throwError(exec, TypeError, 01341 SPREF"getConfString: expected string " 01342 "as first argument"); 01343 } 01344 if (!(dval->isString() || dval->isNull())) { 01345 return throwError(exec, TypeError, 01346 SPREF"getConfString: expected string " 01347 "as second argument (when given)"); 01348 } 01349 01350 if (dval->isNull()) { 01351 dval = jsUndefined(); 01352 } 01353 01354 QString qkey = key->getString().qstring(); 01355 if (config.contains(qkey)) { 01356 return jsString(config.value(qkey)); 01357 } 01358 01359 return dval; 01360 } 01361 01362 JSValue *Scriptface::getConfBoolf (ExecState *exec, 01363 JSValue *key, JSValue *dval) 01364 { 01365 if (!key->isString()) { 01366 return throwError(exec, TypeError, 01367 SPREF"getConfBool: expected string as " 01368 "first argument"); 01369 } 01370 if (!(dval->isBoolean() || dval->isNull())) { 01371 return throwError(exec, TypeError, 01372 SPREF"getConfBool: expected boolean " 01373 "as second argument (when given)"); 01374 } 01375 01376 static QStringList falsities; 01377 if (falsities.isEmpty()) { 01378 falsities.append(QString(QLatin1Char('0'))); 01379 falsities.append(QString::fromLatin1("no")); 01380 falsities.append(QString::fromLatin1("false")); 01381 } 01382 01383 if (dval->isNull()) { 01384 dval = jsUndefined(); 01385 } 01386 01387 QString qkey = key->getString().qstring(); 01388 if (config.contains(qkey)) { 01389 QString qval = config.value(qkey).toLower(); 01390 return jsBoolean(!falsities.contains(qval)); 01391 } 01392 01393 return dval; 01394 } 01395 01396 JSValue *Scriptface::getConfNumberf (ExecState *exec, 01397 JSValue *key, JSValue *dval) 01398 { 01399 if (!key->isString()) { 01400 return throwError(exec, TypeError, 01401 SPREF"getConfNumber: expected string " 01402 "as first argument"); 01403 } 01404 if (!(dval->isNumber() || dval->isNull())) { 01405 return throwError(exec, TypeError, 01406 SPREF"getConfNumber: expected number " 01407 "as second argument (when given)"); 01408 } 01409 01410 if (dval->isNull()) { 01411 dval = jsUndefined(); 01412 } 01413 01414 QString qkey = key->getString().qstring(); 01415 if (config.contains(qkey)) { 01416 QString qval = config.value(qkey); 01417 bool convOk; 01418 double qnum = qval.toDouble(&convOk); 01419 if (convOk) { 01420 return jsNumber(qnum); 01421 } 01422 } 01423 01424 return dval; 01425 } 01426 01427 // ---------------------------------------------------------------------- 01428 // Scriptface helpers to interface functions. 01429 01430 QString Scriptface::loadProps_text (const QString &fpath) 01431 { 01432 QFile file(fpath); 01433 if (!file.open(QIODevice::ReadOnly)) { 01434 return QString::fromLatin1(SPREF"loadProps_text: cannot read file '%1'") 01435 .arg(fpath); 01436 } 01437 QTextStream stream(&file); 01438 stream.setCodec("UTF-8"); 01439 QString s = stream.readAll(); 01440 file.close(); 01441 01442 // Parse the map. 01443 // Should care about performance: possibly executed on each KDE 01444 // app startup and reading houndreds of thousands of characters. 01445 enum {s_nextEntry, s_nextKey, s_nextValue}; 01446 QList<QByteArray> ekeys; // holds keys for current entry 01447 QHash<QByteArray, QByteArray> props; // holds properties for current entry 01448 int slen = s.length(); 01449 int state = s_nextEntry; 01450 QByteArray pkey; 01451 QChar prop_sep, key_sep; 01452 int i = 0; 01453 while (1) { 01454 int i_checkpoint = i; 01455 01456 if (state == s_nextEntry) { 01457 while (s[i].isSpace()) { 01458 ++i; 01459 if (i >= slen) goto END_PROP_PARSE; 01460 } 01461 if (i + 1 >= slen) { 01462 return QString::fromLatin1(SPREF"loadProps_text: unexpected end " 01463 "of file in %1").arg(fpath); 01464 } 01465 if (s[i] != QLatin1Char('#')) { 01466 // Separator characters for this entry. 01467 key_sep = s[i]; 01468 prop_sep = s[i + 1]; 01469 if (key_sep.isLetter() || prop_sep.isLetter()) { 01470 return QString::fromLatin1(SPREF"loadProps_text: separator " 01471 "characters must not be letters at %1:%2") 01472 .arg(fpath).arg(countLines(s, i)); 01473 } 01474 01475 // Reset all data for current entry. 01476 ekeys.clear(); 01477 props.clear(); 01478 pkey.clear(); 01479 01480 i += 2; 01481 state = s_nextKey; 01482 } 01483 else { 01484 // This is a comment, skip to EOL, don't change state. 01485 while (s[i] != QLatin1Char('\n')) { 01486 ++i; 01487 if (i >= slen) goto END_PROP_PARSE; 01488 } 01489 } 01490 } 01491 else if (state == s_nextKey) { 01492 int ip = i; 01493 // Proceed up to next key or property separator. 01494 while (s[i] != key_sep && s[i] != prop_sep) { 01495 ++i; 01496 if (i >= slen) goto END_PROP_PARSE; 01497 } 01498 if (s[i] == key_sep) { 01499 // This is a property key, 01500 // record for when the value gets parsed. 01501 pkey = normKeystr(s.mid(ip, i - ip), false); 01502 01503 i += 1; 01504 state = s_nextValue; 01505 } 01506 else { // if (s[i] == prop_sep) { 01507 // This is an entry key, or end of entry. 01508 QByteArray ekey = normKeystr(s.mid(ip, i - ip), false); 01509 if (!ekey.isEmpty()) { 01510 // An entry key. 01511 ekeys.append(ekey); 01512 01513 i += 1; 01514 state = s_nextKey; 01515 } 01516 else { 01517 // End of entry. 01518 if (ekeys.size() < 1) { 01519 return QString::fromLatin1(SPREF"loadProps_text: no entry key " 01520 "for entry ending at %1:%2") 01521 .arg(fpath).arg(countLines(s, i)); 01522 } 01523 01524 // Add collected entry into global store, 01525 // once for each entry key (QHash implicitly shared). 01526 foreach (const QByteArray &ekey, ekeys) { 01527 phraseProps[ekey] = props; 01528 } 01529 01530 i += 1; 01531 state = s_nextEntry; 01532 } 01533 } 01534 } 01535 else if (state == s_nextValue) { 01536 int ip = i; 01537 // Proceed up to next property separator. 01538 while (s[i] != prop_sep) { 01539 ++i; 01540 if (i >= slen) goto END_PROP_PARSE; 01541 if (s[i] == key_sep) { 01542 return QString::fromLatin1(SPREF"loadProps_text: property separator " 01543 "inside property value at %1:%2") 01544 .arg(fpath).arg(countLines(s, i)); 01545 } 01546 } 01547 // Extract the property value and store the property. 01548 QByteArray pval = trimSmart(s.mid(ip, i - ip)).toUtf8(); 01549 props[pkey] = pval; 01550 01551 i += 1; 01552 state = s_nextKey; 01553 } 01554 else { 01555 return QString::fromLatin1(SPREF"loadProps: internal error 10 at %1:%2") 01556 .arg(fpath).arg(countLines(s, i)); 01557 } 01558 01559 // To avoid infinite looping and stepping out. 01560 if (i == i_checkpoint || i >= slen) { 01561 return QString::fromLatin1(SPREF"loadProps: internal error 20 at %1:%2") 01562 .arg(fpath).arg(countLines(s, i)); 01563 } 01564 } 01565 01566 END_PROP_PARSE: 01567 01568 if (state != s_nextEntry) { 01569 return QString::fromLatin1(SPREF"loadProps: unexpected end of file in %1") 01570 .arg(fpath); 01571 } 01572 01573 return QString(); 01574 } 01575 01576 // Read big-endian integer of nbytes length at position pos 01577 // in character array fc of length len. 01578 // Update position to point after the number. 01579 // In case of error, pos is set to -1. 01580 template <typename T> 01581 static int bin_read_int_nbytes (const char *fc, qlonglong len, qlonglong &pos, int nbytes) 01582 { 01583 if (pos + nbytes > len) { 01584 pos = -1; 01585 return 0; 01586 } 01587 T num = qFromBigEndian<T>((uchar*) fc + pos); 01588 pos += nbytes; 01589 return num; 01590 } 01591 01592 // Read 64-bit big-endian integer. 01593 static quint64 bin_read_int64 (const char *fc, qlonglong len, qlonglong &pos) 01594 { 01595 return bin_read_int_nbytes<quint64>(fc, len, pos, 8); 01596 } 01597 01598 // Read 32-bit big-endian integer. 01599 static quint32 bin_read_int (const char *fc, qlonglong len, qlonglong &pos) 01600 { 01601 return bin_read_int_nbytes<quint32>(fc, len, pos, 4); 01602 } 01603 01604 // Read string at position pos of character array fc of length n. 01605 // String is represented as 32-bit big-endian byte length followed by bytes. 01606 // Update position to point after the string. 01607 // In case of error, pos is set to -1. 01608 static QByteArray bin_read_string (const char *fc, qlonglong len, qlonglong &pos) 01609 { 01610 // Binary format stores strings as length followed by byte sequence. 01611 // No null-termination. 01612 int nbytes = bin_read_int(fc, len, pos); 01613 if (pos < 0) { 01614 return QByteArray(); 01615 } 01616 if (nbytes < 0 || pos + nbytes > len) { 01617 pos = -1; 01618 return QByteArray(); 01619 } 01620 QByteArray s(fc + pos, nbytes); 01621 pos += nbytes; 01622 return s; 01623 } 01624 01625 QString Scriptface::loadProps_bin (const QString &fpath) 01626 { 01627 QFile file(fpath); 01628 if (!file.open(QIODevice::ReadOnly)) { 01629 return QString::fromLatin1(SPREF"loadProps: cannot read file '%1'") 01630 .arg(fpath); 01631 } 01632 // Collect header. 01633 QByteArray head(8, '0'); 01634 file.read(head.data(), head.size()); 01635 file.close(); 01636 01637 // Choose pmap loader based on header. 01638 if (head == "TSPMAP00") { 01639 return loadProps_bin_00(fpath); 01640 } else if (head == "TSPMAP01") { 01641 return loadProps_bin_01(fpath); 01642 } 01643 else { 01644 return QString::fromLatin1(SPREF"loadProps: unknown version of compiled map '%1'") 01645 .arg(fpath); 01646 } 01647 } 01648 01649 QString Scriptface::loadProps_bin_00 (const QString &fpath) 01650 { 01651 QFile file(fpath); 01652 if (!file.open(QIODevice::ReadOnly)) { 01653 return QString::fromLatin1(SPREF"loadProps: cannot read file '%1'") 01654 .arg(fpath); 01655 } 01656 QByteArray fctmp = file.readAll(); 01657 file.close(); 01658 const char *fc = fctmp.data(); 01659 const int fclen = fctmp.size(); 01660 01661 // Indicates stream state. 01662 qlonglong pos = 0; 01663 01664 // Match header. 01665 QByteArray head(fc, 8); 01666 pos += 8; 01667 if (head != "TSPMAP00") goto END_PROP_PARSE; 01668 01669 // Read total number of entries. 01670 int nentries; 01671 nentries = bin_read_int(fc, fclen, pos); 01672 if (pos < 0) goto END_PROP_PARSE; 01673 01674 // Read all entries. 01675 for (int i = 0; i < nentries; ++i) { 01676 01677 // Read number of entry keys and all entry keys. 01678 QList<QByteArray> ekeys; 01679 int nekeys = bin_read_int(fc, fclen, pos); 01680 if (pos < 0) goto END_PROP_PARSE; 01681 for (int j = 0; j < nekeys; ++j) { 01682 QByteArray ekey = bin_read_string(fc, fclen, pos); 01683 if (pos < 0) goto END_PROP_PARSE; 01684 ekeys.append(ekey); 01685 } 01686 //dbgout("--------> ekey[0]={%1}", QString::fromUtf8(ekeys[0])); 01687 01688 // Read number of properties and all properties. 01689 QHash<QByteArray, QByteArray> props; 01690 int nprops = bin_read_int(fc, fclen, pos); 01691 if (pos < 0) goto END_PROP_PARSE; 01692 for (int j = 0; j < nprops; ++j) { 01693 QByteArray pkey = bin_read_string(fc, fclen, pos); 01694 if (pos < 0) goto END_PROP_PARSE; 01695 QByteArray pval = bin_read_string(fc, fclen, pos); 01696 if (pos < 0) goto END_PROP_PARSE; 01697 props[pkey] = pval; 01698 } 01699 01700 // Add collected entry into global store, 01701 // once for each entry key (QHash implicitly shared). 01702 foreach (const QByteArray &ekey, ekeys) { 01703 phraseProps[ekey] = props; 01704 } 01705 } 01706 01707 END_PROP_PARSE: 01708 01709 if (pos < 0) { 01710 return QString::fromLatin1(SPREF"loadProps: corrupt compiled map '%1'") 01711 .arg(fpath); 01712 } 01713 01714 return QString(); 01715 } 01716 01717 QString Scriptface::loadProps_bin_01 (const QString &fpath) 01718 { 01719 QFile *file = new QFile(fpath); 01720 if (!file->open(QIODevice::ReadOnly)) { 01721 return QString::fromLatin1(SPREF"loadProps: cannot read file '%1'") 01722 .arg(fpath); 01723 } 01724 01725 QByteArray fstr; 01726 qlonglong pos; 01727 01728 // Read the header and number and length of entry keys. 01729 fstr = file->read(8 + 4 + 8); 01730 pos = 0; 01731 QByteArray head = fstr.left(8); 01732 pos += 8; 01733 if (head != "TSPMAP01") { 01734 return QString::fromLatin1(SPREF"loadProps: corrupt compiled map '%1'") 01735 .arg(fpath); 01736 } 01737 quint32 numekeys = bin_read_int(fstr, fstr.size(), pos); 01738 quint64 lenekeys = bin_read_int64(fstr, fstr.size(), pos); 01739 01740 // Read entry keys. 01741 fstr = file->read(lenekeys); 01742 pos = 0; 01743 for (quint32 i = 0; i < numekeys; ++i) { 01744 QByteArray ekey = bin_read_string(fstr, lenekeys, pos); 01745 quint64 offset = bin_read_int64(fstr, lenekeys, pos); 01746 phraseUnparsedProps[ekey] = QPair<QFile*, quint64>(file, offset); 01747 } 01748 01749 // // Read property keys. 01750 // ...when it becomes necessary 01751 01752 loadedPmapHandles.insert(file); 01753 return QString(); 01754 } 01755 01756 QHash<QByteArray, QByteArray> Scriptface::resolveUnparsedProps (const QByteArray &phrase) 01757 { 01758 QPair<QFile*, quint64> ref = phraseUnparsedProps.value(phrase); 01759 QFile *file = ref.first; 01760 quint64 offset = ref.second; 01761 QHash<QByteArray, QByteArray> props; 01762 if (file != NULL && file->seek(offset)) { 01763 QByteArray fstr = file->read(4 + 4); 01764 qlonglong pos = 0; 01765 quint32 numpkeys = bin_read_int(fstr, fstr.size(), pos); 01766 quint32 lenpkeys = bin_read_int(fstr, fstr.size(), pos); 01767 fstr = file->read(lenpkeys); 01768 pos = 0; 01769 for (quint32 i = 0; i < numpkeys; ++i) { 01770 QByteArray pkey = bin_read_string(fstr, lenpkeys, pos); 01771 QByteArray pval = bin_read_string(fstr, lenpkeys, pos); 01772 props[pkey] = pval; 01773 } 01774 phraseProps[phrase] = props; 01775 phraseUnparsedProps.remove(phrase); 01776 } 01777 return props; 01778 }
KDE 4.7 API Reference