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

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
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • kio
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • KPty
  • Kross
  • KUtils
  • Nepomuk
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver
Generated for kdelibs by doxygen 1.6.1
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