• Skip to content
  • Skip to link menu
KDE 4.6 API Reference
  • KDE API Reference
  • kdelibs
  • KDE Home
  • Contact Us
 

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 }

KDECore

Skip menu "KDECore"
  • Main Page
  • Modules
  • Namespace List
  • Class Hierarchy
  • Alphabetical List
  • Class List
  • File List
  • Namespace Members
  • Class Members
  • Related Pages

kdelibs

Skip menu "kdelibs"
  • DNSSD
  • Interfaces
  •   KHexEdit
  •   KMediaPlayer
  •   KSpeech
  •   KTextEditor
  • Kate
  • kconf_update
  • KDE3Support
  •   KUnitTest
  • KDECore
  • KDED
  • KDEsu
  • KDEUI
  • KDEWebKit
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • kio
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • KPty
  • Kross
  • KUnitConversion
  • KUtils
  • Nepomuk
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver
Generated for kdelibs by doxygen 1.7.3
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal