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