1 /** -*- mode: c++ ; c-basic-offset: 2 -*-
2  *
3  *  @file Misc.cpp
4  *
5  *  Copyright 2017 Sebastien Fourey
6  *
7  *  This file is part of G'MIC-Qt, a generic plug-in for raster graphics
8  *  editors, offering hundreds of filters thanks to the underlying G'MIC
9  *  image processing framework.
10  *
11  *  gmic_qt is free software: you can redistribute it and/or modify
12  *  it under the terms of the GNU General Public License as published by
13  *  the Free Software Foundation, either version 3 of the License, or
14  *  (at your option) any later version.
15  *
16  *  gmic_qt is distributed in the hope that it will be useful,
17  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
18  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19  *  GNU General Public License for more details.
20  *
21  *  You should have received a copy of the GNU General Public License
22  *  along with gmic_qt.  If not, see <http://www.gnu.org/licenses/>.
23  *
24  */
25 
26 #include "Misc.h"
27 #include <QByteArray>
28 #include <QChar>
29 #include <QDebug>
30 #include <QMap>
31 #include <QRegularExpression>
32 #include <QString>
33 #include <QStringList>
34 #include <QTextStream>
35 #include <algorithm>
36 #include <cctype>
37 #include "Common.h"
38 #include "Globals.h"
39 #include "HtmlTranslator.h"
40 #include "Logger.h"
41 #include "gmic.h"
42 
43 namespace
44 {
skipSpaces(const char * & pc)45 inline void skipSpaces(const char *& pc)
46 {
47   while (isspace(*pc)) {
48     ++pc;
49   }
50 }
isEmptyOrSpaceSequence(const char * pc)51 inline bool isEmptyOrSpaceSequence(const char * pc)
52 {
53   while (*pc) {
54     if (!isspace(*pc)) {
55       return false;
56     }
57     ++pc;
58   }
59   return true;
60 }
61 } // namespace
62 
63 namespace GmicQt
64 {
65 
commandFromOutputMessageMode(OutputMessageMode mode)66 QString commandFromOutputMessageMode(OutputMessageMode mode)
67 {
68   switch (mode) {
69   case OutputMessageMode::Quiet:
70   case OutputMessageMode::VerboseConsole:
71   case OutputMessageMode::VerboseLogFile:
72   case OutputMessageMode::Unspecified:
73     return "";
74   case OutputMessageMode::VeryVerboseConsole:
75   case OutputMessageMode::VeryVerboseLogFile:
76     return "v 3";
77   case OutputMessageMode::DebugConsole:
78   case OutputMessageMode::DebugLogFile:
79     return "debug";
80   }
81   return "";
82 }
83 
appendWithSpace(QString & str,const QString & other)84 void appendWithSpace(QString & str, const QString & other)
85 {
86   if (str.isEmpty() || other.isEmpty()) {
87     str += other;
88     return;
89   }
90   str += QChar(' ');
91   str += other;
92 }
93 
downcaseCommandTitle(QString & title)94 void downcaseCommandTitle(QString & title)
95 {
96   QMap<int, QString> acronyms;
97   // Acronyms
98   QRegExp re("([A-Z0-9]{2,255})");
99   int index = 0;
100   while ((index = re.indexIn(title, index)) != -1) {
101     QString pattern = re.cap(0);
102     acronyms[index] = pattern;
103     index += pattern.length();
104   }
105 
106   // 3D
107   re.setPattern("([1-9])[dD] ");
108   if ((index = re.indexIn(title, 0)) != -1) {
109     acronyms[index] = re.cap(1) + "d ";
110   }
111 
112   // B&amp;W
113   re.setPattern("(B&amp;W|[ \\[]Lab|[ \\[]YCbCr)");
114   index = 0;
115   while ((index = re.indexIn(title, index)) != -1) {
116     acronyms[index] = re.cap(1);
117     index += re.cap(1).length();
118   }
119 
120   // Uppercase letter in last position, after a space
121   re.setPattern(" ([A-Z])$");
122   if ((index = re.indexIn(title, 0)) != -1) {
123     acronyms[index] = re.cap(0);
124   }
125   title = title.toLower();
126   QMap<int, QString>::const_iterator it = acronyms.cbegin();
127   while (it != acronyms.cend()) {
128     title.replace(it.key(), it.value().length(), it.value());
129     ++it;
130   }
131   title[0] = title[0].toUpper();
132 }
133 
parseGmicFilterParameters(const QString & text,QStringList & args)134 bool parseGmicFilterParameters(const QString & text, QStringList & args)
135 {
136   return parseGmicFilterParameters(text.toUtf8().constData(), args);
137 }
138 
parseGmicFilterParameters(const char * text,QStringList & args)139 bool parseGmicFilterParameters(const char * text, QStringList & args)
140 {
141   args.clear();
142   if (!text) {
143     return false;
144   }
145   skipSpaces(text);
146   const char * pc = text;
147   bool quoted = false;
148   bool escaped = false;
149   bool meaningfulSpaceFound = false;
150   char * buffer = new char[strlen(pc)]();
151   char * output = buffer;
152   while (*pc && !((meaningfulSpaceFound = (!quoted && !escaped && isspace(*pc))))) {
153     if (escaped) {
154       *output++ = *pc++;
155       escaped = false;
156     } else if (*pc == '\\') {
157       escaped = true;
158       ++pc;
159     } else if (*pc == '"') {
160       quoted = !quoted;
161       ++pc;
162     } else if (!quoted && !escaped && (*pc == ',')) {
163       *output = '\0';
164       args.push_back(QString::fromUtf8(buffer));
165       output = buffer;
166       ++pc;
167     } else {
168       *output++ = *pc++;
169     }
170   }
171   const bool endsWidthComma = (output == buffer) && (pc > text) && (!quoted && !escaped && (pc[-1] == ','));
172   if ((output != buffer) || endsWidthComma) {
173     *output = '\0';
174     args.push_back(QString::fromUtf8(buffer));
175   }
176   delete[] buffer;
177   if (quoted || (meaningfulSpaceFound && !isEmptyOrSpaceSequence(pc))) {
178     args.clear();
179     return false;
180   }
181   return true;
182 }
183 
parseGmicUniqueFilterCommand(const char * text,QString & command,QString & arguments)184 bool parseGmicUniqueFilterCommand(const char * text, QString & command, QString & arguments)
185 {
186   arguments.clear();
187   command.clear();
188   if (!text) {
189     return false;
190   }
191   const char * commandBegin = text;
192   skipSpaces(commandBegin);
193   if (*commandBegin == '\0') {
194     return false;
195   }
196   const char * pc = commandBegin;
197   while (isalnum(*pc) || (*pc == '_')) {
198     ++pc;
199   }
200   if ((*pc != '\0') && !isspace(*pc)) {
201     return false;
202   }
203   const char * const commandEnd = pc;
204   skipSpaces(pc);
205 
206   bool quoted = false;
207   bool escaped = false;
208   bool meaningfulSpaceFound = false;
209   const char * argumentStart = pc;
210   while (*pc && !((meaningfulSpaceFound = (!quoted && !escaped && isspace(*pc))))) {
211     if (escaped) {
212       escaped = false;
213     } else if (*pc == '\\') {
214       escaped = true;
215     } else if (*pc == '"') {
216       quoted = !quoted;
217     }
218     ++pc;
219   }
220   if (quoted || (meaningfulSpaceFound && !isEmptyOrSpaceSequence(pc))) {
221     return false;
222   }
223   command = QString::fromUtf8(commandBegin, static_cast<int>(commandEnd - commandBegin));
224   arguments = QString::fromUtf8(argumentStart, static_cast<int>(pc - argumentStart));
225   return true;
226 }
227 
escapeUnescapedQuotes(const QString & text)228 QString escapeUnescapedQuotes(const QString & text)
229 {
230   std::string source_str = text.toStdString();
231   const char * pc = source_str.c_str();
232   std::vector<char> output_str(2 * source_str.size() + 1, static_cast<char>(0));
233   char * out = output_str.data();
234   bool escaped = false;
235   while (*pc) {
236     if (escaped) {
237       escaped = false;
238     } else if (*pc == '\\') {
239       escaped = true;
240     } else if (*pc == '"') {
241       *out++ = '\\';
242     }
243     *out++ = *pc++;
244   }
245   QString result = QString::fromUtf8(output_str.data());
246   return result;
247 }
248 
filterFullPathWithoutTags(const QList<QString> & path,const QString & name)249 QString filterFullPathWithoutTags(const QList<QString> & path, const QString & name)
250 {
251   QStringList noTags = {QString()};
252   for (QString str : path) {
253     if (str.startsWith(WarningPrefix)) {
254       str.remove(0, 1);
255     }
256     noTags.push_back(HtmlTranslator::removeTags(str));
257   }
258   noTags.push_back(HtmlTranslator::removeTags(name));
259   return noTags.join('/');
260 }
261 
filterFullPathBasename(const QString & path)262 QString filterFullPathBasename(const QString & path)
263 {
264   QString result = path;
265   result.remove(QRegularExpression("^.*/"));
266   return result;
267 }
268 
flattenGmicParameterList(const QList<QString> & list,const QVector<bool> & quotedParameters)269 QString flattenGmicParameterList(const QList<QString> & list, const QVector<bool> & quotedParameters)
270 {
271   QString result;
272   if (list.isEmpty()) {
273     return result;
274   }
275   QList<QString>::const_iterator itList = list.constBegin();
276   QVector<bool>::const_iterator itQuoting = quotedParameters.constBegin();
277   result += (*itQuoting++) ? quotedString(*itList++) : (*itList++);
278   while (itList != list.end()) {
279     result += QString(",%1").arg((*itQuoting++) ? quotedString(*itList++) : (*itList++));
280   }
281   return result;
282 }
283 
mergedWithSpace(const QString & prefix,const QString & suffix)284 QString mergedWithSpace(const QString & prefix, const QString & suffix)
285 {
286   if (prefix.isEmpty() || suffix.isEmpty()) {
287     return prefix + suffix;
288   }
289   return prefix + QChar(' ') + suffix;
290 }
291 
elided(const QString & text,int width)292 QString elided(const QString & text, int width)
293 {
294   if (text.length() <= width) {
295     return text;
296   }
297   return text.left(std::max(0, width - 3)) + "...";
298 }
299 
quotedParameters(const QList<QString> & parameters)300 QVector<bool> quotedParameters(const QList<QString> & parameters)
301 {
302   QVector<bool> result;
303   for (const auto & str : parameters) {
304     result.push_back(str.startsWith("\""));
305   }
306   return result;
307 }
308 
mergeSubsequences(const QStringList & sequence,const QVector<int> & subSequenceLengths)309 QStringList mergeSubsequences(const QStringList & sequence, const QVector<int> & subSequenceLengths)
310 {
311   QStringList result;
312   QVector<int> lengths = subSequenceLengths;
313   QStringList::const_iterator itInput = sequence.constBegin();
314   QVector<int>::iterator itLength = lengths.begin();
315   while ((itInput != sequence.constEnd()) && (itLength != lengths.end())) {
316     if (*itLength <= 0) {
317       ++itLength;
318       continue;
319     }
320     QString text = *itInput++;
321     --(*itLength);
322     while (*itLength > 0) {
323       text += QString(",%1").arg(*itInput++);
324       --(*itLength);
325     }
326     result.push_back(text);
327     ++itLength;
328   }
329   if ((itInput != sequence.constEnd()) || (itLength != lengths.end())) {
330     Logger::warning(QObject::tr("List %1 cannot be merged considering theses runs: %2").arg(stringify(sequence)).arg(stringify(subSequenceLengths)));
331     return QStringList();
332   }
333   return result;
334 }
335 
completePrefixFromFullList(const QStringList & prefix,const QStringList & fullList)336 QStringList completePrefixFromFullList(const QStringList & prefix, const QStringList & fullList)
337 {
338   if (fullList.size() <= prefix.size()) {
339     return prefix;
340   }
341   QStringList result = prefix;
342   QStringList::const_iterator it = fullList.constBegin();
343   it += prefix.size();
344   while (it != fullList.constEnd()) {
345     result.push_back(*it++);
346   }
347   return result;
348 }
349 
quotedString(QString text)350 QString quotedString(QString text)
351 {
352   return QString("\"%1\"").arg(escapeUnescapedQuotes(text));
353 }
354 
quotedStringList(const QStringList & stringList)355 QStringList quotedStringList(const QStringList & stringList)
356 {
357   QStringList result;
358   for (const auto & text : stringList) {
359     result.push_back(quotedString(text));
360   }
361   return result;
362 }
363 
unescaped(const QString & text)364 QString unescaped(const QString & text)
365 {
366   QByteArray ba = text.toUtf8();
367   cimg_library::cimg::strunescape(ba.data());
368   return QString::fromUtf8(ba.data());
369 }
370 
unquoted(const QString & text)371 QString unquoted(const QString & text)
372 {
373   QRegularExpression re("^\\s*\"(.*)\"\\s*$");
374   auto match = re.match(text);
375   if (match.hasMatch()) {
376     return match.captured(1);
377   } else {
378     return text.trimmed();
379   }
380 }
381 
expandParameterList(const QStringList & parameters,QVector<int> sizes)382 QStringList expandParameterList(const QStringList & parameters, QVector<int> sizes)
383 {
384   // FIXME : Handle errors here
385   QStringList result;
386   Q_ASSERT_X(parameters.size() == sizes.size(), __PRETTY_FUNCTION__, QString("Sizes are different (parameters: %1, sizes: %2)").arg(parameters.size()).arg(sizes.size()).toStdString().c_str());
387   QStringList::const_iterator itParam = parameters.constBegin();
388   auto itSize = sizes.constBegin();
389   while (itParam != parameters.constEnd() && itSize != sizes.constEnd()) {
390     if (*itSize > 1) {
391       result.append(itParam->split(","));
392     } else if (*itSize == 1) {
393       result.push_back(*itParam);
394     } else {
395       Q_ASSERT_X((*itSize >= 1), __PRETTY_FUNCTION__, QString("Parameter size should be at least 1 (it is %1)").arg(*itSize).toStdString().c_str());
396     }
397     ++itParam;
398     ++itSize;
399   }
400   return result;
401 }
402 
403 } // namespace GmicQt
404