KI18n

ktranscript.cpp
1/* This file is part of the KDE libraries
2 SPDX-FileCopyrightText: 2007 Chusslove Illich <caslav.ilic@gmx.net>
3 SPDX-FileCopyrightText: 2014 Kevin Krammer <krammer@kde.org>
4
5 SPDX-License-Identifier: LGPL-2.0-or-later
6*/
7
8#include <common_helpers_p.h>
9#include <ktranscript_p.h>
10
11#include <ktranscript_export.h>
12
13//#include <unistd.h>
14
15#include <QJSEngine>
16
17#include <QDebug>
18#include <QDir>
19#include <QFile>
20#include <QHash>
21#include <QIODevice>
22#include <QJSValueIterator>
23#include <QList>
24#include <QSet>
25#include <QStandardPaths>
26#include <QStringList>
27#include <QTextStream>
28#include <QVariant>
29#include <qendian.h>
30
31class KTranscriptImp;
32class Scriptface;
33
36
37// Transcript implementation (used as singleton).
38class KTranscriptImp : public KTranscript
39{
40public:
41 KTranscriptImp();
42 ~KTranscriptImp() override;
43
44 QString eval(const QList<QVariant> &argv,
45 const QString &lang,
46 const QString &ctry,
47 const QString &msgctxt,
48 const QHash<QString, QString> &dynctxt,
49 const QString &msgid,
50 const QStringList &subs,
51 const QList<QVariant> &vals,
52 const QString &ftrans,
54 QString &error,
55 bool &fallback) override;
56
57 QStringList postCalls(const QString &lang) override;
58
59 // Lexical path of the module for the executing code.
60 QString currentModulePath;
61
62private:
63 void loadModules(const QList<QStringList> &mods, QString &error);
64 void setupInterpreter(const QString &lang);
65
66 TsConfig config;
67
69};
70
71// Script-side transcript interface.
72class Scriptface : public QObject
73{
75public:
76 explicit Scriptface(const TsConfigGroup &config, QObject *parent = nullptr);
77 ~Scriptface();
78
79 // Interface functions.
80 Q_INVOKABLE QJSValue load(const QString &name);
81 Q_INVOKABLE QJSValue setcall(const QJSValue &name, const QJSValue &func, const QJSValue &fval = QJSValue::NullValue);
82 Q_INVOKABLE QJSValue hascall(const QString &name);
83 Q_INVOKABLE QJSValue acallInternal(const QJSValue &args);
84 Q_INVOKABLE QJSValue setcallForall(const QJSValue &name, const QJSValue &func, const QJSValue &fval = QJSValue::NullValue);
85 Q_INVOKABLE QJSValue fallback();
86 Q_INVOKABLE QJSValue nsubs();
87 Q_INVOKABLE QJSValue subs(const QJSValue &index);
88 Q_INVOKABLE QJSValue vals(const QJSValue &index);
89 Q_INVOKABLE QJSValue msgctxt();
90 Q_INVOKABLE QJSValue dynctxt(const QString &key);
91 Q_INVOKABLE QJSValue msgid();
92 Q_INVOKABLE QJSValue msgkey();
93 Q_INVOKABLE QJSValue msgstrf();
94 Q_INVOKABLE void dbgputs(const QString &str);
95 Q_INVOKABLE void warnputs(const QString &str);
96 Q_INVOKABLE QJSValue localeCountry();
97 Q_INVOKABLE QJSValue normKey(const QJSValue &phrase);
98 Q_INVOKABLE QJSValue loadProps(const QString &name);
99 Q_INVOKABLE QJSValue getProp(const QJSValue &phrase, const QJSValue &prop);
100 Q_INVOKABLE QJSValue setProp(const QJSValue &phrase, const QJSValue &prop, const QJSValue &value);
101 Q_INVOKABLE QJSValue toUpperFirst(const QJSValue &str, const QJSValue &nalt = QJSValue::NullValue);
102 Q_INVOKABLE QJSValue toLowerFirst(const QJSValue &str, const QJSValue &nalt = QJSValue::NullValue);
103 Q_INVOKABLE QJSValue getConfString(const QJSValue &key, const QJSValue &dval = QJSValue::NullValue);
104 Q_INVOKABLE QJSValue getConfBool(const QJSValue &key, const QJSValue &dval = QJSValue::NullValue);
105 Q_INVOKABLE QJSValue getConfNumber(const QJSValue &key, const QJSValue &dval = QJSValue::NullValue);
106
107 // Helper methods to interface functions.
108 QJSValue load(const QJSValueList &names);
109 QJSValue loadProps(const QJSValueList &names);
110 QString loadProps_text(const QString &fpath);
111 QString loadProps_bin(const QString &fpath);
112 QString loadProps_bin_00(const QString &fpath);
113 QString loadProps_bin_01(const QString &fpath);
114
115 void put(const QString &propertyName, const QJSValue &value);
116
117 // Link to its script engine
118 QJSEngine *const scriptEngine;
119
120 // Current message data.
121 const QString *msgcontext;
122 const QHash<QString, QString> *dyncontext;
123 const QString *msgId;
124 const QStringList *subList;
125 const QList<QVariant> *valList;
126 const QString *ftrans;
127 const QString *ctry;
128
129 // Fallback request handle.
130 bool *fallbackRequest;
131
132 // Function register.
136
137 // Ordering of those functions which execute for all messages.
138 QList<QString> nameForalls;
139
140 // Property values per phrase (used by *Prop interface calls).
141 // Not QStrings, in order to avoid conversion from UTF-8 when
142 // loading compiled maps (less latency on startup).
144 // Unresolved property values per phrase,
145 // containing the pointer to compiled pmap file handle and offset in it.
146 struct UnparsedPropInfo {
147 QFile *pmapFile = nullptr;
148 quint64 offset = -1;
149 };
150 QHash<QByteArray, UnparsedPropInfo> phraseUnparsedProps;
151 QHash<QByteArray, QByteArray> resolveUnparsedProps(const QByteArray &phrase);
152 // Set of loaded pmap files by paths and file handle pointers.
153 QSet<QString> loadedPmapPaths;
154 QSet<QFile *> loadedPmapHandles;
155
156 // User config.
157 TsConfigGroup config;
158};
159
160// ----------------------------------------------------------------------
161// Custom debug and warning output (kdebug not available)
162#define DBGP "KTranscript: "
163void dbgout(const char *str)
164{
165#ifndef NDEBUG
166 fprintf(stderr, DBGP "%s\n", str);
167#else
168 Q_UNUSED(str);
169#endif
170}
171template<typename T1>
172void dbgout(const char *str, const T1 &a1)
173{
174#ifndef NDEBUG
175 fprintf(stderr, DBGP "%s\n", QString::fromUtf8(str).arg(a1).toLocal8Bit().data());
176#else
177 Q_UNUSED(str);
178 Q_UNUSED(a1);
179#endif
180}
181template<typename T1, typename T2>
182void dbgout(const char *str, const T1 &a1, const T2 &a2)
183{
184#ifndef NDEBUG
185 fprintf(stderr, DBGP "%s\n", QString::fromUtf8(str).arg(a1).arg(a2).toLocal8Bit().data());
186#else
187 Q_UNUSED(str);
188 Q_UNUSED(a1);
189 Q_UNUSED(a2);
190#endif
191}
192template<typename T1, typename T2, typename T3>
193void dbgout(const char *str, const T1 &a1, const T2 &a2, const T3 &a3)
194{
195#ifndef NDEBUG
196 fprintf(stderr, DBGP "%s\n", QString::fromUtf8(str).arg(a1).arg(a2).arg(a3).toLocal8Bit().data());
197#else
198 Q_UNUSED(str);
199 Q_UNUSED(a1);
200 Q_UNUSED(a2);
201 Q_UNUSED(a3);
202#endif
203}
204
205#define WARNP "KTranscript: "
206void warnout(const char *str)
207{
208 fprintf(stderr, WARNP "%s\n", str);
209}
210template<typename T1>
211void warnout(const char *str, const T1 &a1)
212{
213 fprintf(stderr, WARNP "%s\n", QString::fromUtf8(str).arg(a1).toLocal8Bit().data());
214}
215
216// ----------------------------------------------------------------------
217// Produces a string out of a script exception.
218
219QString expt2str(const QJSValue &expt)
220{
221 if (expt.isError()) {
222 const QJSValue message = expt.property(QStringLiteral("message"));
223 if (!message.isUndefined()) {
224 return QStringLiteral("Error: %1").arg(message.toString());
225 }
226 }
227
228 QString strexpt = expt.toString();
229 return QStringLiteral("Caught exception: %1").arg(strexpt);
230}
231
232// ----------------------------------------------------------------------
233// Count number of lines in the string,
234// up to and excluding the requested position.
235int countLines(const QString &s, int p)
236{
237 int n = 1;
238 int len = s.length();
239 for (int i = 0; i < p && i < len; ++i) {
240 if (s[i] == QLatin1Char('\n')) {
241 ++n;
242 }
243 }
244 return n;
245}
246
247// ----------------------------------------------------------------------
248// Normalize string key for hash lookups,
249QByteArray normKeystr(const QString &raw, bool mayHaveAcc = true)
250{
251 // NOTE: Regexes should not be used here for performance reasons.
252 // This function may potentially be called thousands of times
253 // on application startup.
254
255 QString key = raw;
256
257 // Strip all whitespace.
258 int len = key.length();
259 QString nkey;
260 for (int i = 0; i < len; ++i) {
261 QChar c = key[i];
262 if (!c.isSpace()) {
263 nkey.append(c);
264 }
265 }
266 key = nkey;
267
268 // Strip accelerator marker.
269 if (mayHaveAcc) {
270 key = removeAcceleratorMarker(key);
271 }
272
273 // Convert to lower case.
274 key = key.toLower();
275
276 return key.toUtf8();
277}
278
279// ----------------------------------------------------------------------
280// Trim multiline string in a "smart" way:
281// Remove leading and trailing whitespace up to and including first
282// newline from that side, if there is one; otherwise, don't touch.
283QString trimSmart(const QString &raw)
284{
285 // NOTE: This could be done by a single regex, but is not due to
286 // performance reasons.
287 // This function may potentially be called thousands of times
288 // on application startup.
289
290 int len = raw.length();
291
292 int is = 0;
293 while (is < len && raw[is].isSpace() && raw[is] != QLatin1Char('\n')) {
294 ++is;
295 }
296 if (is >= len || raw[is] != QLatin1Char('\n')) {
297 is = -1;
298 }
299
300 int ie = len - 1;
301 while (ie >= 0 && raw[ie].isSpace() && raw[ie] != QLatin1Char('\n')) {
302 --ie;
303 }
304 if (ie < 0 || raw[ie] != QLatin1Char('\n')) {
305 ie = len;
306 }
307
308 return raw.mid(is + 1, ie - is - 1);
309}
310
311// ----------------------------------------------------------------------
312// Produce a JavaScript object out of Qt variant.
313
314QJSValue variantToJsValue(const QVariant &val)
315{
316 const auto vtype = val.userType();
317 if (vtype == QMetaType::QString) {
318 return QJSValue(val.toString());
319 } else if (vtype == QMetaType::Bool) {
320 return QJSValue(val.toBool());
321 } else if (vtype == QMetaType::Double //
322 || vtype == QMetaType::Int //
323 || vtype == QMetaType::UInt //
324 || vtype == QMetaType::LongLong //
325 || vtype == QMetaType::ULongLong) {
326 return QJSValue(val.toDouble());
327 } else {
329 }
330}
331
332// ----------------------------------------------------------------------
333// Parse ini-style config file,
334// returning content as hash of hashes by group and key.
335// Parsing is not fussy, it will read what it can.
336TsConfig readConfig(const QString &fname)
337{
338 TsConfig config;
339 // Add empty group.
340 TsConfig::iterator configGroup;
341 configGroup = config.insert(QString(), TsConfigGroup());
342
343 QFile file(fname);
344 if (!file.open(QIODevice::ReadOnly)) {
345 return config;
346 }
347 QTextStream stream(&file);
348 while (!stream.atEnd()) {
349 QString line = stream.readLine();
350 int p1;
351 int p2;
352
353 // Remove comment from the line.
354 p1 = line.indexOf(QLatin1Char('#'));
355 if (p1 >= 0) {
356 line.truncate(p1);
357 }
358 line = line.trimmed();
359 if (line.isEmpty()) {
360 continue;
361 }
362
363 if (line[0] == QLatin1Char('[')) {
364 // Group switch.
365 p1 = 0;
366 p2 = line.indexOf(QLatin1Char(']'), p1 + 1);
367 if (p2 < 0) {
368 continue;
369 }
370 QString group = line.mid(p1 + 1, p2 - p1 - 1).trimmed();
371 configGroup = config.find(group);
372 if (configGroup == config.end()) {
373 // Add new group.
374 configGroup = config.insert(group, TsConfigGroup());
375 }
376 } else {
377 // Field.
378 p1 = line.indexOf(QLatin1Char('='));
379 if (p1 < 0) {
380 continue;
381 }
382
383 const QStringView lineView(line);
384 const QStringView field = lineView.left(p1).trimmed();
385 if (!field.isEmpty()) {
386 const QStringView value = lineView.mid(p1 + 1).trimmed();
387 (*configGroup)[field.toString()] = value.toString();
388 }
389 }
390 }
391 file.close();
392
393 return config;
394}
395
396// ----------------------------------------------------------------------
397// throw or log error, depending on context availability
398static QJSValue throwError(QJSEngine *engine, const QString &message)
399{
400 if (engine) {
401 return engine->evaluate(QStringLiteral("new Error(%1)").arg(message));
402 }
403
404 qCritical() << "Script error" << message;
406}
407
408#ifdef KTRANSCRIPT_TESTBUILD
409
410// ----------------------------------------------------------------------
411// Test build creation/destruction hooks
412static KTranscriptImp *s_transcriptInstance = nullptr;
413
414KTranscriptImp *globalKTI()
415{
416 return s_transcriptInstance;
417}
418
419KTranscript *autotestCreateKTranscriptImp()
420{
421 Q_ASSERT(s_transcriptInstance == nullptr);
422 s_transcriptInstance = new KTranscriptImp;
423 return s_transcriptInstance;
424}
425
426void autotestDestroyKTranscriptImp()
427{
428 Q_ASSERT(s_transcriptInstance != nullptr);
429 delete s_transcriptInstance;
430 s_transcriptInstance = nullptr;
431}
432
433#else
434
435// ----------------------------------------------------------------------
436// Dynamic loading.
437Q_GLOBAL_STATIC(KTranscriptImp, globalKTI)
438extern "C" {
439KTRANSCRIPT_EXPORT KTranscript *load_transcript()
440{
441 return globalKTI();
442}
443}
444#endif
445
446// ----------------------------------------------------------------------
447// KTranscript definitions.
448
449KTranscriptImp::KTranscriptImp()
450{
451 // Load user configuration.
452
453 QString tsConfigPath = QStandardPaths::locate(QStandardPaths::ConfigLocation, QStringLiteral("ktranscript.ini"));
454 if (tsConfigPath.isEmpty()) {
455 tsConfigPath = QDir::homePath() + QLatin1Char('/') + QLatin1String(".transcriptrc");
456 }
457 config = readConfig(tsConfigPath);
458}
459
460KTranscriptImp::~KTranscriptImp()
461{
462 qDeleteAll(m_sface);
463}
464
465QString KTranscriptImp::eval(const QList<QVariant> &argv,
466 const QString &lang,
467 const QString &ctry,
468 const QString &msgctxt,
469 const QHash<QString, QString> &dynctxt,
470 const QString &msgid,
471 const QStringList &subs,
472 const QList<QVariant> &vals,
473 const QString &ftrans,
474 QList<QStringList> &mods,
475 QString &error,
476 bool &fallback)
477{
478 // error = "debug"; return QString();
479
480 error.clear(); // empty error message means successful evaluation
481 fallback = false; // fallback not requested
482
483#if 0
484 // FIXME: Maybe not needed, as QJSEngine has no native outside access?
485 // Unportable (needs unistd.h)?
486
487 // If effective user id is root and real user id is not root.
488 if (geteuid() == 0 && getuid() != 0) {
489 // Since scripts are user input, and the program is running with
490 // root permissions while real user is not root, do not invoke
491 // scripting at all, to prevent exploits.
492 error = "Security block: trying to execute a script in suid environment.";
493 return QString();
494 }
495#endif
496
497 // Load any new modules and clear the list.
498 if (!mods.isEmpty()) {
499 loadModules(mods, error);
500 mods.clear();
501 if (!error.isEmpty()) {
502 return QString();
503 }
504 }
505
506 // Add interpreters for new languages.
507 // (though it should never happen here, but earlier when loading modules;
508 // this also means there are no calls set, so the unregistered call error
509 // below will be reported).
510 if (!m_sface.contains(lang)) {
511 setupInterpreter(lang);
512 }
513
514 // Shortcuts.
515 Scriptface *sface = m_sface[lang];
516
517 QJSEngine *engine = sface->scriptEngine;
518 QJSValue gobj = engine->globalObject();
519
520 // Link current message data for script-side interface.
521 sface->msgcontext = &msgctxt;
522 sface->dyncontext = &dynctxt;
523 sface->msgId = &msgid;
524 sface->subList = &subs;
525 sface->valList = &vals;
526 sface->ftrans = &ftrans;
527 sface->fallbackRequest = &fallback;
528 sface->ctry = &ctry;
529
530 // Find corresponding JS function.
531 int argc = argv.size();
532 if (argc < 1) {
533 // error = "At least the call name must be supplied.";
534 // Empty interpolation is OK, possibly used just to initialize
535 // at a given point (e.g. for Ts.setForall() to start having effect).
536 return QString();
537 }
538 QString funcName = argv[0].toString();
539 if (!sface->funcs.contains(funcName)) {
540 error = QStringLiteral("Unregistered call to '%1'.").arg(funcName);
541 return QString();
542 }
543
544 QJSValue func = sface->funcs[funcName];
545 QJSValue fval = sface->fvals[funcName];
546
547 // Recover module path from the time of definition of this call,
548 // for possible load calls.
549 currentModulePath = sface->fpaths[funcName];
550
551 // Execute function.
552 QJSValueList arglist;
553 arglist.reserve(argc - 1);
554 for (int i = 1; i < argc; ++i) {
555 arglist.append(engine->toScriptValue(argv[i]));
556 }
557
558 QJSValue val;
559 if (fval.isObject()) {
560 val = func.callWithInstance(fval, arglist);
561 } else { // no object associated to this function, use global
562 val = func.callWithInstance(gobj, arglist);
563 }
564
565 if (fallback) {
566 // Fallback to ordinary translation requested.
567 return QString();
568 } else if (!val.isError()) {
569 // Evaluation successful.
570
571 if (val.isString()) {
572 // Good to go.
573
574 return val.toString();
575 } else {
576 // Accept only strings.
577
578 QString strval = val.toString();
579 error = QStringLiteral("Non-string return value: %1").arg(strval);
580 return QString();
581 }
582 } else {
583 // Exception raised.
584
585 error = expt2str(val);
586
587 return QString();
588 }
589}
590
591QStringList KTranscriptImp::postCalls(const QString &lang)
592{
593 // Return no calls if scripting was not already set up for this language.
594 // NOTE: This shouldn't happen, as postCalls cannot be called in such case.
595 if (!m_sface.contains(lang)) {
596 return QStringList();
597 }
598
599 // Shortcuts.
600 Scriptface *sface = m_sface[lang];
601
602 return sface->nameForalls;
603}
604
605void KTranscriptImp::loadModules(const QList<QStringList> &mods, QString &error)
606{
607 QList<QString> modErrors;
608
609 for (const QStringList &mod : mods) {
610 QString mpath = mod[0];
611 QString mlang = mod[1];
612
613 // Add interpreters for new languages.
614 if (!m_sface.contains(mlang)) {
615 setupInterpreter(mlang);
616 }
617
618 // Setup current module path for loading submodules.
619 // (sort of closure over invocations of loadf)
620 int posls = mpath.lastIndexOf(QLatin1Char('/'));
621 if (posls < 1) {
622 modErrors.append(QStringLiteral("Funny module path '%1', skipping.").arg(mpath));
623 continue;
624 }
625 currentModulePath = mpath.left(posls);
626 QString fname = mpath.mid(posls + 1);
627 // Scriptface::loadf() wants no extension on the filename
628 fname = fname.left(fname.lastIndexOf(QLatin1Char('.')));
629
630 // Load the module.
631 QJSValueList alist;
632 alist.append(QJSValue(fname));
633
634 m_sface[mlang]->load(alist);
635 }
636
637 // Unset module path.
638 currentModulePath.clear();
639
640 for (const QString &merr : std::as_const(modErrors)) {
641 error.append(merr + QLatin1Char('\n'));
642 }
643}
644
645#define SFNAME "Ts"
646void KTranscriptImp::setupInterpreter(const QString &lang)
647{
648 // Add scripting interface
649 // Creates its own script engine and registers with it
650 // NOTE: Config may not contain an entry for the language, in which case
651 // it is automatically constructed as an empty hash. This is intended.
652 Scriptface *sface = new Scriptface(config[lang]);
653
654 // Store scriptface
655 m_sface[lang] = sface;
656
657 // dbgout("=====> Created interpreter for '%1'", lang);
658}
659
660Scriptface::Scriptface(const TsConfigGroup &config_, QObject *parent)
661 : QObject(parent)
662 , scriptEngine(new QJSEngine)
663 , fallbackRequest(nullptr)
664 , config(config_)
665{
666 QJSValue object = scriptEngine->newQObject(this);
667 scriptEngine->globalObject().setProperty(QStringLiteral(SFNAME), object);
668 scriptEngine->evaluate(QStringLiteral("Ts.acall = function() { return Ts.acallInternal(Array.prototype.slice.call(arguments)); };"));
669}
670
671Scriptface::~Scriptface()
672{
673 qDeleteAll(loadedPmapHandles);
674 scriptEngine->deleteLater();
675}
676
677void Scriptface::put(const QString &propertyName, const QJSValue &value)
678{
679 QJSValue internalObject = scriptEngine->globalObject().property(QStringLiteral("ScriptfaceInternal"));
680 if (internalObject.isUndefined()) {
681 internalObject = scriptEngine->newObject();
682 scriptEngine->globalObject().setProperty(QStringLiteral("ScriptfaceInternal"), internalObject);
683 }
684
685 internalObject.setProperty(propertyName, value);
686}
687
688// ----------------------------------------------------------------------
689// Scriptface interface functions.
690
691#ifdef _MSC_VER
692// Work around bizarre MSVC (2013) bug preventing use of QStringLiteral for concatenated string literals
693#define SPREF(X) QString::fromLatin1(SFNAME "." X)
694#else
695#define SPREF(X) QStringLiteral(SFNAME "." X)
696#endif
697
698QJSValue Scriptface::load(const QString &name)
699{
700 QJSValueList fnames;
701 fnames << name;
702 return load(fnames);
703}
704
705QJSValue Scriptface::setcall(const QJSValue &name, const QJSValue &func, const QJSValue &fval)
706{
707 if (!name.isString()) {
708 return throwError(scriptEngine, SPREF("setcall: expected string as first argument"));
709 }
710 if (!func.isCallable()) {
711 return throwError(scriptEngine, SPREF("setcall: expected function as second argument"));
712 }
713 if (!(fval.isObject() || fval.isNull())) {
714 return throwError(scriptEngine, SPREF("setcall: expected object or null as third argument"));
715 }
716
717 QString qname = name.toString();
718 funcs[qname] = func;
719 fvals[qname] = fval;
720
721 // Register values to keep GC from collecting them. Is this needed?
722 put(QStringLiteral("#:f<%1>").arg(qname), func);
723 put(QStringLiteral("#:o<%1>").arg(qname), fval);
724
725 // Set current module path as module path for this call,
726 // in case it contains load subcalls.
727 fpaths[qname] = globalKTI()->currentModulePath;
728
730}
731
732QJSValue Scriptface::hascall(const QString &qname)
733{
734 return QJSValue(funcs.contains(qname));
735}
736
737QJSValue Scriptface::acallInternal(const QJSValue &args)
738{
739 QJSValueIterator it(args);
740
741 if (!it.next()) {
742 return throwError(scriptEngine, SPREF("acall: expected at least one argument (call name)"));
743 }
744 if (!it.value().isString()) {
745 return throwError(scriptEngine, SPREF("acall: expected string as first argument (call name)"));
746 }
747 // Get the function and its context object.
748 QString callname = it.value().toString();
749 if (!funcs.contains(callname)) {
750 return throwError(scriptEngine, SPREF("acall: unregistered call to '%1'").arg(callname));
751 }
752 QJSValue func = funcs[callname];
753 QJSValue fval = fvals[callname];
754
755 // Recover module path from the time of definition of this call,
756 // for possible load calls.
757 globalKTI()->currentModulePath = fpaths[callname];
758
759 // Execute function.
760 QJSValueList arglist;
761 while (it.next()) {
762 arglist.append(it.value());
763 }
764
765 QJSValue val;
766 if (fval.isObject()) {
767 // Call function with the context object.
768 val = func.callWithInstance(fval, arglist);
769 } else {
770 // No context object associated to this function, use global.
771 val = func.callWithInstance(scriptEngine->globalObject(), arglist);
772 }
773 return val;
774}
775
776QJSValue Scriptface::setcallForall(const QJSValue &name, const QJSValue &func, const QJSValue &fval)
777{
778 if (!name.isString()) {
779 return throwError(scriptEngine, SPREF("setcallForall: expected string as first argument"));
780 }
781 if (!func.isCallable()) {
782 return throwError(scriptEngine, SPREF("setcallForall: expected function as second argument"));
783 }
784 if (!(fval.isObject() || fval.isNull())) {
785 return throwError(scriptEngine, SPREF("setcallForall: expected object or null as third argument"));
786 }
787
788 QString qname = name.toString();
789 funcs[qname] = func;
790 fvals[qname] = fval;
791
792 // Register values to keep GC from collecting them. Is this needed?
793 put(QStringLiteral("#:fall<%1>").arg(qname), func);
794 put(QStringLiteral("#:oall<%1>").arg(qname), fval);
795
796 // Set current module path as module path for this call,
797 // in case it contains load subcalls.
798 fpaths[qname] = globalKTI()->currentModulePath;
799
800 // Put in the queue order for execution on all messages.
801 nameForalls.append(qname);
802
804}
805
806QJSValue Scriptface::fallback()
807{
808 if (fallbackRequest) {
809 *fallbackRequest = true;
810 }
812}
813
814QJSValue Scriptface::nsubs()
815{
816 return QJSValue(static_cast<int>(subList->size()));
817}
818
819QJSValue Scriptface::subs(const QJSValue &index)
820{
821 if (!index.isNumber()) {
822 return throwError(scriptEngine, SPREF("subs: expected number as first argument"));
823 }
824
825 int i = qRound(index.toNumber());
826 if (i < 0 || i >= subList->size()) {
827 return throwError(scriptEngine, SPREF("subs: index out of range"));
828 }
829
830 return QJSValue(subList->at(i));
831}
832
833QJSValue Scriptface::vals(const QJSValue &index)
834{
835 if (!index.isNumber()) {
836 return throwError(scriptEngine, SPREF("vals: expected number as first argument"));
837 }
838
839 int i = qRound(index.toNumber());
840 if (i < 0 || i >= valList->size()) {
841 return throwError(scriptEngine, SPREF("vals: index out of range"));
842 }
843
844 return scriptEngine->toScriptValue(valList->at(i));
845 // return variantToJsValue(valList->at(i));
846}
847
848QJSValue Scriptface::msgctxt()
849{
850 return QJSValue(*msgcontext);
851}
852
853QJSValue Scriptface::dynctxt(const QString &qkey)
854{
855 auto valIt = dyncontext->constFind(qkey);
856 if (valIt != dyncontext->constEnd()) {
857 return QJSValue(*valIt);
858 }
860}
861
862QJSValue Scriptface::msgid()
863{
864 return QJSValue(*msgId);
865}
866
867QJSValue Scriptface::msgkey()
868{
869 return QJSValue(QString(*msgcontext + QLatin1Char('|') + *msgId));
870}
871
872QJSValue Scriptface::msgstrf()
873{
874 return QJSValue(*ftrans);
875}
876
877void Scriptface::dbgputs(const QString &qstr)
878{
879 dbgout("[JS-debug] %1", qstr);
880}
881
882void Scriptface::warnputs(const QString &qstr)
883{
884 warnout("[JS-warning] %1", qstr);
885}
886
887QJSValue Scriptface::localeCountry()
888{
889 return QJSValue(*ctry);
890}
891
892QJSValue Scriptface::normKey(const QJSValue &phrase)
893{
894 if (!phrase.isString()) {
895 return throwError(scriptEngine, SPREF("normKey: expected string as argument"));
896 }
897
898 QByteArray nqphrase = normKeystr(phrase.toString());
899 return QJSValue(QString::fromUtf8(nqphrase));
900}
901
902QJSValue Scriptface::loadProps(const QString &name)
903{
904 QJSValueList fnames;
905 fnames << name;
906 return loadProps(fnames);
907}
908
909QJSValue Scriptface::loadProps(const QJSValueList &fnames)
910{
911 if (globalKTI()->currentModulePath.isEmpty()) {
912 return throwError(scriptEngine, SPREF("loadProps: no current module path, aiiie..."));
913 }
914
915 for (int i = 0; i < fnames.size(); ++i) {
916 if (!fnames[i].isString()) {
917 return throwError(scriptEngine, SPREF("loadProps: expected string as file name"));
918 }
919 }
920
921 for (int i = 0; i < fnames.size(); ++i) {
922 QString qfname = fnames[i].toString();
923 QString qfpath_base = globalKTI()->currentModulePath + QLatin1Char('/') + qfname;
924
925 // Determine which kind of map is available.
926 // Give preference to compiled map.
927 QString qfpath = qfpath_base + QLatin1String(".pmapc");
928 bool haveCompiled = true;
929 QFile file_check(qfpath);
930 if (!file_check.open(QIODevice::ReadOnly)) {
931 haveCompiled = false;
932 qfpath = qfpath_base + QLatin1String(".pmap");
933 QFile file_check(qfpath);
934 if (!file_check.open(QIODevice::ReadOnly)) {
935 return throwError(scriptEngine, SPREF("loadProps: cannot read map '%1'").arg(qfpath));
936 }
937 }
938 file_check.close();
939
940 // Load from appropriate type of map.
941 if (!loadedPmapPaths.contains(qfpath)) {
942 QString errorString;
943 if (haveCompiled) {
944 errorString = loadProps_bin(qfpath);
945 } else {
946 errorString = loadProps_text(qfpath);
947 }
948 if (!errorString.isEmpty()) {
949 return throwError(scriptEngine, errorString);
950 }
951 dbgout("Loaded property map: %1", qfpath);
952 loadedPmapPaths.insert(qfpath);
953 }
954 }
955
957}
958
959QJSValue Scriptface::getProp(const QJSValue &phrase, const QJSValue &prop)
960{
961 if (!phrase.isString()) {
962 return throwError(scriptEngine, SPREF("getProp: expected string as first argument"));
963 }
964 if (!prop.isString()) {
965 return throwError(scriptEngine, SPREF("getProp: expected string as second argument"));
966 }
967
968 QByteArray qphrase = normKeystr(phrase.toString());
969 QHash<QByteArray, QByteArray> props = phraseProps.value(qphrase);
970 if (props.isEmpty()) {
971 props = resolveUnparsedProps(qphrase);
972 }
973 if (!props.isEmpty()) {
974 QByteArray qprop = normKeystr(prop.toString());
975 QByteArray qval = props.value(qprop);
976 if (!qval.isEmpty()) {
977 return QJSValue(QString::fromUtf8(qval));
978 }
979 }
981}
982
983QJSValue Scriptface::setProp(const QJSValue &phrase, const QJSValue &prop, const QJSValue &value)
984{
985 if (!phrase.isString()) {
986 return throwError(scriptEngine, SPREF("setProp: expected string as first argument"));
987 }
988 if (!prop.isString()) {
989 return throwError(scriptEngine, SPREF("setProp: expected string as second argument"));
990 }
991 if (!value.isString()) {
992 return throwError(scriptEngine, SPREF("setProp: expected string as third argument"));
993 }
994
995 QByteArray qphrase = normKeystr(phrase.toString());
996 QByteArray qprop = normKeystr(prop.toString());
997 QByteArray qvalue = value.toString().toUtf8();
998 // Any non-existent key in first or second-level hash will be created.
999 phraseProps[qphrase][qprop] = qvalue;
1001}
1002
1003static QString toCaseFirst(const QString &qstr, int qnalt, bool toupper)
1004{
1005 static const QLatin1String head("~@");
1006 static const int hlen = 2; // head.length()
1007
1008 // If the first letter is found within an alternatives directive,
1009 // change case of the first letter in each of the alternatives.
1010 QString qstrcc = qstr;
1011 const int len = qstr.length();
1012 QChar altSep;
1013 int remainingAlts = 0;
1014 bool checkCase = true;
1015 int numChcased = 0;
1016 int i = 0;
1017 while (i < len) {
1018 QChar c = qstr[i];
1019
1020 if (qnalt && !remainingAlts && QStringView(qstr).mid(i, hlen) == head) {
1021 // An alternatives directive is just starting.
1022 i += 2;
1023 if (i >= len) {
1024 break; // malformed directive, bail out
1025 }
1026 // Record alternatives separator, set number of remaining
1027 // alternatives, reactivate case checking.
1028 altSep = qstrcc[i];
1029 remainingAlts = qnalt;
1030 checkCase = true;
1031 } else if (remainingAlts && c == altSep) {
1032 // Alternative separator found, reduce number of remaining
1033 // alternatives and reactivate case checking.
1034 --remainingAlts;
1035 checkCase = true;
1036 } else if (checkCase && c.isLetter()) {
1037 // Case check is active and the character is a letter; change case.
1038 if (toupper) {
1039 qstrcc[i] = c.toUpper();
1040 } else {
1041 qstrcc[i] = c.toLower();
1042 }
1043 ++numChcased;
1044 // No more case checks until next alternatives separator.
1045 checkCase = false;
1046 }
1047
1048 // If any letter has been changed, and there are no more alternatives
1049 // to be processed, we're done.
1050 if (numChcased > 0 && remainingAlts == 0) {
1051 break;
1052 }
1053
1054 // Go to next character.
1055 ++i;
1056 }
1057
1058 return qstrcc;
1059}
1060
1061QJSValue Scriptface::toUpperFirst(const QJSValue &str, const QJSValue &nalt)
1062{
1063 if (!str.isString()) {
1064 return throwError(scriptEngine, SPREF("toUpperFirst: expected string as first argument"));
1065 }
1066 if (!(nalt.isNumber() || nalt.isNull())) {
1067 return throwError(scriptEngine, SPREF("toUpperFirst: expected number as second argument"));
1068 }
1069
1070 QString qstr = str.toString();
1071 int qnalt = nalt.isNull() ? 0 : nalt.toInt();
1072
1073 QString qstruc = toCaseFirst(qstr, qnalt, true);
1074
1075 return QJSValue(qstruc);
1076}
1077
1078QJSValue Scriptface::toLowerFirst(const QJSValue &str, const QJSValue &nalt)
1079{
1080 if (!str.isString()) {
1081 return throwError(scriptEngine, SPREF("toLowerFirst: expected string as first argument"));
1082 }
1083 if (!(nalt.isNumber() || nalt.isNull())) {
1084 return throwError(scriptEngine, SPREF("toLowerFirst: expected number as second argument"));
1085 }
1086
1087 QString qstr = str.toString();
1088 int qnalt = nalt.isNull() ? 0 : nalt.toInt();
1089
1090 QString qstrlc = toCaseFirst(qstr, qnalt, false);
1091
1092 return QJSValue(qstrlc);
1093}
1094
1095QJSValue Scriptface::getConfString(const QJSValue &key, const QJSValue &dval)
1096{
1097 if (!key.isString()) {
1098 return throwError(scriptEngine, QStringLiteral("getConfString: expected string as first argument"));
1099 }
1100 if (!(dval.isString() || dval.isNull())) {
1101 return throwError(scriptEngine, SPREF("getConfString: expected string as second argument (when given)"));
1102 }
1103
1104 QString qkey = key.toString();
1105 auto valIt = config.constFind(qkey);
1106 if (valIt != config.constEnd()) {
1107 return QJSValue(*valIt);
1108 }
1109
1110 return dval.isNull() ? QJSValue::UndefinedValue : dval;
1111}
1112
1113QJSValue Scriptface::getConfBool(const QJSValue &key, const QJSValue &dval)
1114{
1115 if (!key.isString()) {
1116 return throwError(scriptEngine, SPREF("getConfBool: expected string as first argument"));
1117 }
1118 if (!(dval.isBool() || dval.isNull())) {
1119 return throwError(scriptEngine, SPREF("getConfBool: expected boolean as second argument (when given)"));
1120 }
1121
1122 static QStringList falsities;
1123 if (falsities.isEmpty()) {
1124 falsities.append(QString(QLatin1Char('0')));
1125 falsities.append(QStringLiteral("no"));
1126 falsities.append(QStringLiteral("false"));
1127 }
1128
1129 QString qkey = key.toString();
1130 auto valIt = config.constFind(qkey);
1131 if (valIt != config.constEnd()) {
1132 QString qval = valIt->toLower();
1133 return QJSValue(!falsities.contains(qval));
1134 }
1135
1136 return dval.isNull() ? QJSValue::UndefinedValue : dval;
1137}
1138
1139QJSValue Scriptface::getConfNumber(const QJSValue &key, const QJSValue &dval)
1140{
1141 if (!key.isString()) {
1142 return throwError(scriptEngine,
1143 SPREF("getConfNumber: expected string "
1144 "as first argument"));
1145 }
1146 if (!(dval.isNumber() || dval.isNull())) {
1147 return throwError(scriptEngine,
1148 SPREF("getConfNumber: expected number "
1149 "as second argument (when given)"));
1150 }
1151
1152 QString qkey = key.toString();
1153 auto valIt = config.constFind(qkey);
1154 if (valIt != config.constEnd()) {
1155 const QString &qval = *valIt;
1156 bool convOk;
1157 double qnum = qval.toDouble(&convOk);
1158 if (convOk) {
1159 return QJSValue(qnum);
1160 }
1161 }
1162
1163 return dval.isNull() ? QJSValue::UndefinedValue : dval;
1164}
1165
1166// ----------------------------------------------------------------------
1167// Scriptface helpers to interface functions.
1168
1169QJSValue Scriptface::load(const QJSValueList &fnames)
1170{
1171 if (globalKTI()->currentModulePath.isEmpty()) {
1172 return throwError(scriptEngine, SPREF("load: no current module path, aiiie..."));
1173 }
1174
1175 for (int i = 0; i < fnames.size(); ++i) {
1176 if (!fnames[i].isString()) {
1177 return throwError(scriptEngine, SPREF("load: expected string as file name"));
1178 }
1179 }
1180
1181 for (int i = 0; i < fnames.size(); ++i) {
1182 QString qfname = fnames[i].toString();
1183 QString qfpath = globalKTI()->currentModulePath + QLatin1Char('/') + qfname + QLatin1String(".js");
1184
1185 QFile file(qfpath);
1186 if (!file.open(QIODevice::ReadOnly)) {
1187 return throwError(scriptEngine, SPREF("load: cannot read file '%1'").arg(qfpath));
1188 }
1189
1190 QTextStream stream(&file);
1191 QString source = stream.readAll();
1192 file.close();
1193
1194 QJSValue comp = scriptEngine->evaluate(source, qfpath, 0);
1195
1196 if (comp.isError()) {
1197 QString msg = comp.toString();
1198
1199 QString line;
1200 if (comp.isObject()) {
1201 QJSValue lval = comp.property(QStringLiteral("line"));
1202 if (lval.isNumber()) {
1203 line = QString::number(lval.toInt());
1204 }
1205 }
1206
1207 return throwError(scriptEngine, QStringLiteral("at %1:%2: %3").arg(qfpath, line, msg));
1208 }
1209 dbgout("Loaded module: %1", qfpath);
1210 }
1212}
1213
1214QString Scriptface::loadProps_text(const QString &fpath)
1215{
1216 QFile file(fpath);
1217 if (!file.open(QIODevice::ReadOnly)) {
1218 return SPREF("loadProps_text: cannot read file '%1'").arg(fpath);
1219 }
1220 QTextStream stream(&file);
1221 QString s = stream.readAll();
1222 file.close();
1223
1224 // Parse the map.
1225 // Should care about performance: possibly executed on each KDE
1226 // app startup and reading houndreds of thousands of characters.
1227 enum { s_nextEntry, s_nextKey, s_nextValue };
1228 QList<QByteArray> ekeys; // holds keys for current entry
1229 QHash<QByteArray, QByteArray> props; // holds properties for current entry
1230 int slen = s.length();
1231 int state = s_nextEntry;
1232 QByteArray pkey;
1233 QChar prop_sep;
1234 QChar key_sep;
1235 int i = 0;
1236 while (1) {
1237 int i_checkpoint = i;
1238
1239 if (state == s_nextEntry) {
1240 while (s[i].isSpace()) {
1241 ++i;
1242 if (i >= slen) {
1243 goto END_PROP_PARSE;
1244 }
1245 }
1246 if (i + 1 >= slen) {
1247 return SPREF("loadProps_text: unexpected end of file in %1").arg(fpath);
1248 }
1249 if (s[i] != QLatin1Char('#')) {
1250 // Separator characters for this entry.
1251 key_sep = s[i];
1252 prop_sep = s[i + 1];
1253 if (key_sep.isLetter() || prop_sep.isLetter()) {
1254 return SPREF("loadProps_text: separator characters must not be letters at %1:%2").arg(fpath).arg(countLines(s, i));
1255 }
1256
1257 // Reset all data for current entry.
1258 ekeys.clear();
1259 props.clear();
1260 pkey.clear();
1261
1262 i += 2;
1263 state = s_nextKey;
1264 } else {
1265 // This is a comment, skip to EOL, don't change state.
1266 while (s[i] != QLatin1Char('\n')) {
1267 ++i;
1268 if (i >= slen) {
1269 goto END_PROP_PARSE;
1270 }
1271 }
1272 }
1273 } else if (state == s_nextKey) {
1274 int ip = i;
1275 // Proceed up to next key or property separator.
1276 while (s[i] != key_sep && s[i] != prop_sep) {
1277 ++i;
1278 if (i >= slen) {
1279 goto END_PROP_PARSE;
1280 }
1281 }
1282 if (s[i] == key_sep) {
1283 // This is a property key,
1284 // record for when the value gets parsed.
1285 pkey = normKeystr(s.mid(ip, i - ip), false);
1286
1287 i += 1;
1288 state = s_nextValue;
1289 } else { // if (s[i] == prop_sep) {
1290 // This is an entry key, or end of entry.
1291 QByteArray ekey = normKeystr(s.mid(ip, i - ip), false);
1292 if (!ekey.isEmpty()) {
1293 // An entry key.
1294 ekeys.append(ekey);
1295
1296 i += 1;
1297 state = s_nextKey;
1298 } else {
1299 // End of entry.
1300 if (ekeys.size() < 1) {
1301 return SPREF("loadProps_text: no entry key for entry ending at %1:%2").arg(fpath).arg(countLines(s, i));
1302 }
1303
1304 // Add collected entry into global store,
1305 // once for each entry key (QHash implicitly shared).
1306 for (const QByteArray &ekey : std::as_const(ekeys)) {
1307 phraseProps[ekey] = props;
1308 }
1309
1310 i += 1;
1311 state = s_nextEntry;
1312 }
1313 }
1314 } else if (state == s_nextValue) {
1315 int ip = i;
1316 // Proceed up to next property separator.
1317 while (s[i] != prop_sep) {
1318 ++i;
1319 if (i >= slen) {
1320 goto END_PROP_PARSE;
1321 }
1322 if (s[i] == key_sep) {
1323 return SPREF("loadProps_text: property separator inside property value at %1:%2").arg(fpath).arg(countLines(s, i));
1324 }
1325 }
1326 // Extract the property value and store the property.
1327 QByteArray pval = trimSmart(s.mid(ip, i - ip)).toUtf8();
1328 props[pkey] = pval;
1329
1330 i += 1;
1331 state = s_nextKey;
1332 } else {
1333 return SPREF("loadProps: internal error 10 at %1:%2").arg(fpath).arg(countLines(s, i));
1334 }
1335
1336 // To avoid infinite looping and stepping out.
1337 if (i == i_checkpoint || i >= slen) {
1338 return SPREF("loadProps: internal error 20 at %1:%2").arg(fpath).arg(countLines(s, i));
1339 }
1340 }
1341
1342END_PROP_PARSE:
1343
1344 if (state != s_nextEntry) {
1345 return SPREF("loadProps: unexpected end of file in %1").arg(fpath);
1346 }
1347
1348 return QString();
1349}
1350
1351// Read big-endian integer of nbytes length at position pos
1352// in character array fc of length len.
1353// Update position to point after the number.
1354// In case of error, pos is set to -1.
1355template<typename T>
1356static int bin_read_int_nbytes(const char *fc, qlonglong len, qlonglong &pos, int nbytes)
1357{
1358 if (pos + nbytes > len) {
1359 pos = -1;
1360 return 0;
1361 }
1362 T num = qFromBigEndian<T>((uchar *)fc + pos);
1363 pos += nbytes;
1364 return num;
1365}
1366
1367// Read 64-bit big-endian integer.
1368static quint64 bin_read_int64(const char *fc, qlonglong len, qlonglong &pos)
1369{
1370 return bin_read_int_nbytes<quint64>(fc, len, pos, 8);
1371}
1372
1373// Read 32-bit big-endian integer.
1374static quint32 bin_read_int(const char *fc, qlonglong len, qlonglong &pos)
1375{
1376 return bin_read_int_nbytes<quint32>(fc, len, pos, 4);
1377}
1378
1379// Read string at position pos of character array fc of length n.
1380// String is represented as 32-bit big-endian byte length followed by bytes.
1381// Update position to point after the string.
1382// In case of error, pos is set to -1.
1383static QByteArray bin_read_string(const char *fc, qlonglong len, qlonglong &pos)
1384{
1385 // Binary format stores strings as length followed by byte sequence.
1386 // No null-termination.
1387 int nbytes = bin_read_int(fc, len, pos);
1388 if (pos < 0) {
1389 return QByteArray();
1390 }
1391 if (nbytes < 0 || pos + nbytes > len) {
1392 pos = -1;
1393 return QByteArray();
1394 }
1395 QByteArray s(fc + pos, nbytes);
1396 pos += nbytes;
1397 return s;
1398}
1399
1400QString Scriptface::loadProps_bin(const QString &fpath)
1401{
1402 QFile file(fpath);
1403 if (!file.open(QIODevice::ReadOnly)) {
1404 return SPREF("loadProps: cannot read file '%1'").arg(fpath);
1405 }
1406 // Collect header.
1407 QByteArray head(8, '0');
1408 file.read(head.data(), head.size());
1409 file.close();
1410
1411 // Choose pmap loader based on header.
1412 if (head == "TSPMAP00") {
1413 return loadProps_bin_00(fpath);
1414 } else if (head == "TSPMAP01") {
1415 return loadProps_bin_01(fpath);
1416 } else {
1417 return SPREF("loadProps: unknown version of compiled map '%1'").arg(fpath);
1418 }
1419}
1420
1421QString Scriptface::loadProps_bin_00(const QString &fpath)
1422{
1423 QFile file(fpath);
1424 if (!file.open(QIODevice::ReadOnly)) {
1425 return SPREF("loadProps: cannot read file '%1'").arg(fpath);
1426 }
1427 QByteArray fctmp = file.readAll();
1428 file.close();
1429 const char *fc = fctmp.data();
1430 const int fclen = fctmp.size();
1431
1432 // Indicates stream state.
1433 qlonglong pos = 0;
1434
1435 // Match header.
1436 QByteArray head(fc, 8);
1437 pos += 8;
1438 if (head != "TSPMAP00") {
1439 goto END_PROP_PARSE;
1440 }
1441
1442 // Read total number of entries.
1443 int nentries;
1444 nentries = bin_read_int(fc, fclen, pos);
1445 if (pos < 0) {
1446 goto END_PROP_PARSE;
1447 }
1448
1449 // Read all entries.
1450 for (int i = 0; i < nentries; ++i) {
1451 // Read number of entry keys and all entry keys.
1452 QList<QByteArray> ekeys;
1453 int nekeys = bin_read_int(fc, fclen, pos);
1454 if (pos < 0) {
1455 goto END_PROP_PARSE;
1456 }
1457 ekeys.reserve(nekeys); // nekeys are appended if data is not corrupted
1458 for (int j = 0; j < nekeys; ++j) {
1459 QByteArray ekey = bin_read_string(fc, fclen, pos);
1460 if (pos < 0) {
1461 goto END_PROP_PARSE;
1462 }
1463 ekeys.append(ekey);
1464 }
1465 // dbgout("--------> ekey[0]={%1}", QString::fromUtf8(ekeys[0]));
1466
1467 // Read number of properties and all properties.
1469 int nprops = bin_read_int(fc, fclen, pos);
1470 if (pos < 0) {
1471 goto END_PROP_PARSE;
1472 }
1473 for (int j = 0; j < nprops; ++j) {
1474 QByteArray pkey = bin_read_string(fc, fclen, pos);
1475 if (pos < 0) {
1476 goto END_PROP_PARSE;
1477 }
1478 QByteArray pval = bin_read_string(fc, fclen, pos);
1479 if (pos < 0) {
1480 goto END_PROP_PARSE;
1481 }
1482 props[pkey] = pval;
1483 }
1484
1485 // Add collected entry into global store,
1486 // once for each entry key (QHash implicitly shared).
1487 for (const QByteArray &ekey : std::as_const(ekeys)) {
1488 phraseProps[ekey] = props;
1489 }
1490 }
1491
1492END_PROP_PARSE:
1493
1494 if (pos < 0) {
1495 return SPREF("loadProps: corrupt compiled map '%1'").arg(fpath);
1496 }
1497
1498 return QString();
1499}
1500
1501QString Scriptface::loadProps_bin_01(const QString &fpath)
1502{
1503 QFile *file = new QFile(fpath);
1504 if (!file->open(QIODevice::ReadOnly)) {
1505 return SPREF("loadProps: cannot read file '%1'").arg(fpath);
1506 }
1507
1508 QByteArray fstr;
1509 qlonglong pos;
1510
1511 // Read the header and number and length of entry keys.
1512 fstr = file->read(8 + 4 + 8);
1513 pos = 0;
1514 QByteArray head = fstr.left(8);
1515 pos += 8;
1516 if (head != "TSPMAP01") {
1517 return SPREF("loadProps: corrupt compiled map '%1'").arg(fpath);
1518 }
1519 quint32 numekeys = bin_read_int(fstr, fstr.size(), pos);
1520 quint64 lenekeys = bin_read_int64(fstr, fstr.size(), pos);
1521
1522 // Read entry keys.
1523 fstr = file->read(lenekeys);
1524 pos = 0;
1525 for (quint32 i = 0; i < numekeys; ++i) {
1526 QByteArray ekey = bin_read_string(fstr, lenekeys, pos);
1527 quint64 offset = bin_read_int64(fstr, lenekeys, pos);
1528 phraseUnparsedProps[ekey] = {file, offset};
1529 }
1530
1531 // // Read property keys.
1532 // ...when it becomes necessary
1533
1534 loadedPmapHandles.insert(file);
1535 return QString();
1536}
1537
1538QHash<QByteArray, QByteArray> Scriptface::resolveUnparsedProps(const QByteArray &phrase)
1539{
1540 auto [file, offset] = phraseUnparsedProps.value(phrase);
1542 if (file && file->seek(offset)) {
1543 QByteArray fstr = file->read(4 + 4);
1544 qlonglong pos = 0;
1545 quint32 numpkeys = bin_read_int(fstr, fstr.size(), pos);
1546 quint32 lenpkeys = bin_read_int(fstr, fstr.size(), pos);
1547 fstr = file->read(lenpkeys);
1548 pos = 0;
1549 for (quint32 i = 0; i < numpkeys; ++i) {
1550 QByteArray pkey = bin_read_string(fstr, lenpkeys, pos);
1551 QByteArray pval = bin_read_string(fstr, lenpkeys, pos);
1552 props[pkey] = pval;
1553 }
1554 phraseProps[phrase] = props;
1555 phraseUnparsedProps.remove(phrase);
1556 }
1557 return props;
1558}
1559
1560#include "ktranscript.moc"
void error(QWidget *parent, const QString &text, const QString &title, const KGuiItem &buttonOk, Options options=Notify)
QString name(StandardAction id)
QCA_EXPORT void setProperty(const QString &name, const QVariant &value)
void clear()
char * data()
bool isEmpty() const const
QByteArray left(qsizetype len) const const
qsizetype size() const const
QByteArray toLower() const const
bool isLetter(char32_t ucs4)
bool isSpace(char32_t ucs4)
char32_t toLower(char32_t ucs4)
char32_t toUpper(char32_t ucs4)
QString homePath()
bool open(FILE *fh, OpenMode mode, FileHandleFlags handleFlags)
virtual bool seek(qint64 pos) override
void clear()
const_iterator constEnd() const const
const_iterator constFind(const Key &key) const const
bool contains(const Key &key) const const
iterator end()
iterator find(const Key &key)
iterator insert(const Key &key, const T &value)
bool isEmpty() const const
bool remove(const Key &key)
void reserve(qsizetype size)
T value(const Key &key) const const
QByteArray read(qint64 maxSize)
QJSValue evaluate(const QString &program, const QString &fileName, int lineNumber, QStringList *exceptionStackTrace)
QJSValue globalObject() const const
QJSValue newObject()
QJSValue toScriptValue(const T &value)
QJSValue callWithInstance(const QJSValue &instance, const QJSValueList &args) const const
bool isBool() const const
bool isCallable() const const
bool isError() const const
bool isNull() const const
bool isNumber() const const
bool isObject() const const
bool isString() const const
bool isUndefined() const const
QJSValue property(const QString &name) const const
void setProperty(const QString &name, const QJSValue &value)
qint32 toInt() const const
double toNumber() const const
QString toString() const const
void append(QList< T > &&value)
const_reference at(qsizetype i) const const
void clear()
bool isEmpty() const const
void reserve(qsizetype size)
qsizetype size() const const
Q_INVOKABLEQ_INVOKABLE
Q_OBJECTQ_OBJECT
void deleteLater()
QObject * parent() const const
bool contains(const QSet< T > &other) const const
iterator insert(const T &value)
QString locate(StandardLocation type, const QString &fileName, LocateOptions options)
QString & append(QChar ch)
QString arg(Args &&... args) const const
void clear()
QString fromUtf8(QByteArrayView str)
qsizetype indexOf(QChar ch, qsizetype from, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
qsizetype lastIndexOf(QChar ch, Qt::CaseSensitivity cs) const const
QString left(qsizetype n) const const
qsizetype length() const const
QString mid(qsizetype position, qsizetype n) const const
QString number(double n, char format, int precision)
double toDouble(bool *ok) const const
QString toLower() const const
QByteArray toUtf8() const const
QString trimmed() const const
void truncate(qsizetype position)
bool contains(QLatin1StringView str, Qt::CaseSensitivity cs) const const
QStringView left(qsizetype length) const const
QStringView mid(qsizetype start, qsizetype length) const const
bool isEmpty() const const
QString toString() const const
QStringView trimmed() const const
bool toBool() const const
double toDouble(bool *ok) const const
QString toString() const const
int userType() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Sat Dec 21 2024 17:05:34 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.