OSDN Git Service

It's 2011 now.
[qt-creator-jp/qt-creator-jp.git] / src / plugins / projectexplorer / customwizard / customwizardparameters.cpp
1 /**************************************************************************
2 **
3 ** This file is part of Qt Creator
4 **
5 ** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
6 **
7 ** Contact: Nokia Corporation (qt-info@nokia.com)
8 **
9 ** No Commercial Usage
10 **
11 ** This file contains pre-release code and may not be distributed.
12 ** You may use this file in accordance with the terms and conditions
13 ** contained in the Technology Preview License Agreement accompanying
14 ** this package.
15 **
16 ** GNU Lesser General Public License Usage
17 **
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 2.1 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL included in the
21 ** packaging of this file.  Please review the following information to
22 ** ensure the GNU Lesser General Public License version 2.1 requirements
23 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
24 **
25 ** In addition, as a special exception, Nokia gives you certain additional
26 ** rights.  These rights are described in the Nokia Qt LGPL Exception
27 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
28 **
29 ** If you have questions regarding the use of this file, please contact
30 ** Nokia at qt-info@nokia.com.
31 **
32 **************************************************************************/
33
34 #include "customwizardparameters.h"
35 #include "customwizardpreprocessor.h"
36 #include "customwizardscriptgenerator.h"
37
38 #include <coreplugin/mimedatabase.h>
39 #include <coreplugin/icore.h>
40 #include <cpptools/cpptoolsconstants.h>
41
42 #include <utils/qtcassert.h>
43
44 #include <QtCore/QDebug>
45 #include <QtCore/QCoreApplication>
46 #include <QtCore/QLocale>
47 #include <QtCore/QFile>
48 #include <QtCore/QDir>
49 #include <QtCore/QFileInfo>
50 #include <QtCore/QXmlStreamReader>
51 #include <QtCore/QXmlStreamAttribute>
52 #include <QtCore/QTemporaryFile>
53 #include <QtScript/QScriptEngine>
54
55 #include <QtGui/QIcon>
56
57 enum { debug = 0 };
58
59 static const char customWizardElementC[] = "wizard";
60 static const char iconElementC[] = "icon";
61 static const char descriptionElementC[] = "description";
62 static const char displayNameElementC[] = "displayname";
63 static const char wizardEnabledAttributeC[] = "enabled";
64 static const char idAttributeC[] = "id";
65 static const char kindAttributeC[] = "kind";
66 static const char klassAttributeC[] = "class";
67 static const char firstPageAttributeC[] = "firstpage";
68 static const char langAttributeC[] = "xml:lang";
69 static const char categoryAttributeC[] = "category";
70 static const char displayCategoryElementC[] = "displaycategory";
71 static const char fieldPageTitleElementC[] = "fieldpagetitle";
72 static const char fieldsElementC[] = "fields";
73 static const char fieldElementC[] = "field";
74 static const char comboEntriesElementC[] = "comboentries";
75 static const char comboEntryElementC[] = "comboentry";
76 static const char comboEntryTextElementC[] = "comboentrytext";
77
78 static const char generatorScriptElementC[] = "generatorscript";
79 static const char generatorScriptBinaryAttributeC[] = "binary";
80 static const char generatorScriptWorkingDirectoryAttributeC[] = "workingdirectory";
81 static const char generatorScriptArgumentElementC[] = "argument";
82 static const char generatorScriptArgumentValueAttributeC[] = "value";
83 static const char generatorScriptArgumentOmitEmptyAttributeC[] = "omit-empty";
84 static const char generatorScriptArgumentWriteFileAttributeC[] = "write-file";
85
86 static const char fieldDescriptionElementC[] = "fielddescription";
87 static const char fieldNameAttributeC[] = "name";
88 static const char fieldMandatoryAttributeC[] = "mandatory";
89 static const char fieldControlElementC[] = "fieldcontrol";
90
91 static const char filesElementC[] = "files";
92 static const char filesGeneratorScriptAttributeC[] = "generatorscript";
93 static const char fileElementC[] = "file";
94 static const char fileSourceAttributeC[] = "source";
95 static const char fileTargetAttributeC[] = "target";
96 static const char fileBinaryAttributeC[] = "binary";
97
98 static const char rulesElementC[] = "validationrules";
99 static const char ruleElementC[] = "validationrule";
100 static const char ruleConditionAttributeC[] = "condition";
101 static const char ruleMessageElementC[] = "message";
102
103 enum ParseState {
104     ParseBeginning,
105     ParseWithinWizard,
106     ParseWithinFields,
107     ParseWithinField,
108     ParseWithinFieldDescription,
109     ParseWithinFieldControl,
110     ParseWithinComboEntries,
111     ParseWithinComboEntry,
112     ParseWithinComboEntryText,
113     ParseWithinFiles,
114     ParseWithinFile,
115     ParseWithinScript,
116     ParseWithinScriptArguments,
117     ParseWithinValidationRules,
118     ParseWithinValidationRule,
119     ParseWithinValidationRuleMessage,
120     ParseError
121 };
122
123 namespace ProjectExplorer {
124 namespace Internal {
125
126 const char customWizardFileOpenEditorAttributeC[] = "openeditor";
127 const char customWizardFileOpenProjectAttributeC[] = "openproject";
128
129 CustomWizardField::CustomWizardField() :
130     mandatory(false)
131 {
132 }
133
134 void CustomWizardField::clear()
135 {
136     mandatory = false;
137     name.clear();
138     description.clear();
139     controlAttributes.clear();
140 }
141
142 // Attribute map keys for combo entries
143 QString CustomWizardField::comboEntryValueKey(int i)
144 {
145     return QLatin1String("comboValue") + QString::number(i);
146 }
147
148 QString CustomWizardField::comboEntryTextKey(int i)
149 {
150     return QLatin1String("comboText") + QString::number(i);
151 }
152
153 CustomWizardFile::CustomWizardFile() :
154         openEditor(false), openProject(false), binary(false)
155 {
156 }
157
158 bool CustomWizardValidationRule::validateRules(const QList<CustomWizardValidationRule> &rules,
159                                                const QMap<QString, QString> &replacementMap,
160                                                QString *errorMessage)
161 {
162     errorMessage->clear();
163     if (rules.isEmpty())
164         return true;
165     QScriptEngine engine;
166     foreach(const CustomWizardValidationRule &rule, rules)
167     if (!rule.validate(engine, replacementMap)) {
168         *errorMessage = rule.message;
169         CustomWizardContext::replaceFields(replacementMap, errorMessage);
170         return false;
171     }
172     return true;
173 }
174
175 bool CustomWizardValidationRule::validate(QScriptEngine &engine, const QMap<QString, QString> &replacementMap) const
176 {
177     // Apply parameters and evaluate using JavaScript
178     QString cond = condition;
179     CustomWizardContext::replaceFields(replacementMap, &cond);
180     bool valid = false;
181     QString errorMessage;
182     if (!evaluateBooleanJavaScriptExpression(engine, cond, &valid, &errorMessage)) {
183         qWarning("Error in custom wizard validation expression '%s': %s",
184                  qPrintable(cond), qPrintable(errorMessage));
185         return false;
186     }
187     return valid;
188 }
189
190 CustomWizardParameters::CustomWizardParameters() :
191         firstPageId(-1)
192 {
193 }
194
195 void CustomWizardParameters::clear()
196 {
197     directory.clear();
198     files.clear();
199     fields.clear();
200     filesGeneratorScript.clear();
201     filesGeneratorScriptArguments.clear();
202     firstPageId = -1;
203     rules.clear();
204 }
205
206 // Resolve icon file path relative to config file directory.
207 static inline QIcon wizardIcon(const QString &configFileFullPath,
208                                const QString &xmlIconFileName)
209 {
210     const QFileInfo fi(xmlIconFileName);
211     if (fi.isFile())
212         return QIcon(fi.absoluteFilePath());
213     if (!fi.isRelative())
214         return QIcon();
215     // Expand by config path
216     const QFileInfo absFi(QFileInfo(configFileFullPath).absolutePath() +
217                           QLatin1Char('/') + xmlIconFileName);
218     if (absFi.isFile())
219         return QIcon(absFi.absoluteFilePath());
220     return QIcon();
221 }
222
223 // Forward a reader over element text
224 static inline bool skipOverElementText(QXmlStreamReader &reader)
225 {
226     QXmlStreamReader::TokenType next = QXmlStreamReader::EndElement;
227     do {
228         next = reader.readNext();
229     } while (next == QXmlStreamReader::Characters || next == QXmlStreamReader::EntityReference
230              || next == QXmlStreamReader::ProcessingInstruction || next == QXmlStreamReader::Comment);
231     return next == QXmlStreamReader::EndElement;
232 }
233
234 // Helper for reading text element depending on a language.
235 // Assign the element text to the string passed on if the language matches,
236 // that is, the element has no language attribute or there is an exact match.
237 // If there is no match, skip over the element text.
238 static inline bool assignLanguageElementText(QXmlStreamReader &reader,
239                                              const QString &desiredLanguage,
240                                              QString *target)
241 {
242     const QStringRef elementLanguage = reader.attributes().value(langAttributeC);
243     if (elementLanguage.isEmpty()) {
244         // Try to find a translation for our built-in Wizards
245         *target = QCoreApplication::translate("ProjectExplorer::CustomWizard", reader.readElementText().toLatin1().constData());
246         return true;
247     }
248     if (elementLanguage == desiredLanguage) {
249         *target = reader.readElementText();
250         return true;
251     }
252     // Language mismatch: forward to end element.
253     skipOverElementText(reader);
254     return false;
255 }
256
257 // Copy&paste from above to call a setter of BaseFileParameters.
258 // Implementation of a sophisticated mem_fun pattern is left
259 // as an exercise to the reader.
260 static inline bool assignLanguageElementText(QXmlStreamReader &reader,
261                                              const QString &desiredLanguage,
262                                              Core::BaseFileWizardParameters *bp,
263                                              void (Core::BaseFileWizardParameters::*setter)(const QString &))
264 {
265     const QStringRef elementLanguage = reader.attributes().value(langAttributeC);
266     if (elementLanguage.isEmpty()) {
267         // Try to find a translation for our built-in Wizards
268         const QString translated = QCoreApplication::translate("ProjectExplorer::CustomWizard", reader.readElementText().toLatin1().constData());
269         (bp->*setter)(translated);
270         return true;
271     }
272     if (elementLanguage == desiredLanguage) {
273         (bp->*setter)(reader.readElementText());
274         return true;
275     }
276     // Language mismatch: forward to end element.
277     skipOverElementText(reader);
278     return false;
279 }
280
281 // Read level sub-elements of "wizard"
282 static bool parseCustomProjectElement(QXmlStreamReader &reader,
283                                       const QString &configFileFullPath,
284                                       const QString &language,
285                                       CustomWizardParameters *p,
286                                       Core::BaseFileWizardParameters *bp)
287 {
288     const QStringRef elementName = reader.name();
289     if (elementName == QLatin1String(iconElementC)) {
290         const QString path = reader.readElementText();
291         const QIcon icon = wizardIcon(configFileFullPath, path);
292         if (icon.availableSizes().isEmpty()) {
293             qWarning("Invalid icon path '%s' encountered in custom project template %s.",
294                      qPrintable(path), qPrintable(configFileFullPath));
295         } else {
296                 bp->setIcon(icon);
297         }
298         return true;
299     }
300     if (elementName == QLatin1String(descriptionElementC)) {
301         assignLanguageElementText(reader, language, bp,
302                                   &Core::BaseFileWizardParameters::setDescription);
303         return true;
304     }
305     if (elementName == QLatin1String(displayNameElementC)) {
306         assignLanguageElementText(reader, language, bp,
307                                   &Core::BaseFileWizardParameters::setDisplayName);
308         return true;
309     }
310     if (elementName == QLatin1String(displayCategoryElementC)) {
311         assignLanguageElementText(reader, language, bp,
312                                   &Core::BaseFileWizardParameters::setDisplayCategory);
313         return true;
314     }
315     if (elementName == QLatin1String(fieldPageTitleElementC)) {
316         assignLanguageElementText(reader, language, &p->fieldPageTitle);
317         return true;
318     }
319     return false;
320 }
321
322 static inline QMap<QString, QString> attributesToStringMap(const QXmlStreamAttributes &attributes)
323 {
324     QMap<QString, QString> rc;
325     foreach(const QXmlStreamAttribute &attribute, attributes)
326         rc.insert(attribute.name().toString(), attribute.value().toString());
327     return rc;
328 }
329
330 // Switch parser state depending on opening element name.
331 static ParseState nextOpeningState(ParseState in, const QStringRef &name)
332 {
333     switch (in) {
334     case ParseBeginning:
335         if (name == QLatin1String(customWizardElementC))
336             return ParseWithinWizard;
337         break;
338     case ParseWithinWizard:
339         if (name == QLatin1String(fieldsElementC))
340             return ParseWithinFields;
341         if (name == QLatin1String(filesElementC))
342             return ParseWithinFiles;
343         if (name == QLatin1String(generatorScriptElementC))
344             return ParseWithinScript;
345         if (name == QLatin1String(rulesElementC))
346             return ParseWithinValidationRules;
347         break;
348     case ParseWithinFields:
349         if (name == QLatin1String(fieldElementC))
350             return ParseWithinField;
351         break;
352     case ParseWithinField:
353         if (name == QLatin1String(fieldDescriptionElementC))
354             return ParseWithinFieldDescription;
355         if (name == QLatin1String(fieldControlElementC))
356             return ParseWithinFieldControl;
357         break;
358     case ParseWithinFieldControl:
359         if (name == QLatin1String(comboEntriesElementC))
360             return ParseWithinComboEntries;
361         break;
362     case ParseWithinComboEntries:
363         if (name == QLatin1String(comboEntryElementC))
364             return ParseWithinComboEntry;
365         break;
366     case ParseWithinComboEntry:
367         if (name == QLatin1String(comboEntryTextElementC))
368             return ParseWithinComboEntryText;
369         break;
370     case ParseWithinFiles:
371         if (name == QLatin1String(fileElementC))
372             return ParseWithinFile;
373         break;
374     case ParseWithinScript:
375         if (name == QLatin1String(generatorScriptArgumentElementC))
376             return ParseWithinScriptArguments;
377         break;
378     case ParseWithinValidationRules:
379         if (name == QLatin1String(ruleElementC))
380             return ParseWithinValidationRule;
381         break;
382     case ParseWithinValidationRule:
383         if (name == QLatin1String(ruleMessageElementC))
384             return ParseWithinValidationRuleMessage;
385         break;
386     case ParseWithinFieldDescription: // No subelements
387     case ParseWithinComboEntryText:
388     case ParseWithinFile:
389     case ParseError:
390     case ParseWithinScriptArguments:
391     case ParseWithinValidationRuleMessage:
392         break;
393     }
394     return ParseError;
395 }
396
397 // Switch parser state depending on closing element name.
398 static ParseState nextClosingState(ParseState in, const QStringRef &name)
399 {
400     switch (in) {
401     case ParseBeginning:
402         break;
403     case ParseWithinWizard:
404         if (name == QLatin1String(customWizardElementC))
405             return ParseBeginning;
406         break;
407     case ParseWithinFields:
408         if (name == QLatin1String(fieldsElementC))
409             return ParseWithinWizard;
410         break;
411     case ParseWithinField:
412         if (name == QLatin1String(fieldElementC))
413             return ParseWithinFields;
414         break;
415     case ParseWithinFiles:
416         if (name == QLatin1String(filesElementC))
417             return ParseWithinWizard;
418         break;
419     case ParseWithinFile:
420         if (name == QLatin1String(fileElementC))
421             return ParseWithinFiles;
422         break;
423     case ParseWithinFieldDescription:
424         if (name == QLatin1String(fieldDescriptionElementC))
425             return ParseWithinField;
426         break;
427     case ParseWithinFieldControl:
428         if (name == QLatin1String(fieldControlElementC))
429             return ParseWithinField;
430         break;
431     case ParseWithinComboEntries:
432         if (name == QLatin1String(comboEntriesElementC))
433             return ParseWithinFieldControl;
434         break;
435     case ParseWithinComboEntry:
436         if (name == QLatin1String(comboEntryElementC))
437             return ParseWithinComboEntries;
438         break;
439     case ParseWithinComboEntryText:
440         if (name == QLatin1String(comboEntryTextElementC))
441             return ParseWithinComboEntry;
442         break;
443     case ParseWithinScript:
444         if (name == QLatin1String(generatorScriptElementC))
445             return ParseWithinWizard;
446         break;
447     case ParseWithinScriptArguments:
448         if (name == QLatin1String(generatorScriptArgumentElementC))
449             return ParseWithinScript;
450         break;
451     case ParseWithinValidationRuleMessage:
452         return ParseWithinValidationRule;
453     case ParseWithinValidationRule:
454         return ParseWithinValidationRules;
455     case ParseWithinValidationRules:
456         return ParseWithinWizard;
457     case ParseError:
458         break;
459     }
460     return ParseError;
461 }
462
463 // Parse kind attribute
464 static inline Core::IWizard::WizardKind kindAttribute(const QXmlStreamReader &r)
465 {
466     const QStringRef value = r.attributes().value(QLatin1String(kindAttributeC));
467     if (!value.isEmpty()) {
468         if (value == QLatin1String("file"))
469             return Core::IWizard::FileWizard;
470         if (value == QLatin1String("class"))
471             return Core::IWizard::ClassWizard;
472     }
473     return Core::IWizard::ProjectWizard;
474 }
475
476 static inline QString msgError(const QXmlStreamReader &reader,
477                                const QString &fileName,
478                                const QString &what)
479 {
480     return QString::fromLatin1("Error in %1 at line %2, column %3: %4").
481             arg(fileName).arg(reader.lineNumber()).arg(reader.columnNumber()).arg(what);
482 }
483
484 static inline bool booleanAttributeValue(const QXmlStreamReader &r, const char *nameC,
485                                          bool defaultValue)
486 {
487     const QStringRef attributeValue = r.attributes().value(QLatin1String(nameC));
488     if (attributeValue.isEmpty())
489         return defaultValue;
490     return attributeValue == QLatin1String("true");
491 }
492
493 static inline int integerAttributeValue(const QXmlStreamReader &r, const char *name, int defaultValue)
494 {
495     const QString sValue = r.attributes().value(QLatin1String(name)).toString();
496     if (sValue.isEmpty())
497         return defaultValue;
498     bool ok;
499     const int value = sValue.toInt(&ok);
500     if (!ok) {
501         qWarning("Invalid integer value specification '%s' for attribute '%s'.",
502                  qPrintable(sValue), name);
503         return defaultValue;
504     }
505     return value;
506 }
507
508 static inline QString attributeValue(const QXmlStreamReader &r, const char *name)
509 {
510     return r.attributes().value(QLatin1String(name)).toString();
511 }
512
513 // Return locale language attribute "de_UTF8" -> "de", empty string for "C"
514 static inline QString languageSetting()
515 {
516     QString name = Core::ICore::instance()->userInterfaceLanguage();
517     const int underScorePos = name.indexOf(QLatin1Char('_'));
518     if (underScorePos != -1)
519         name.truncate(underScorePos);
520     if (name.compare(QLatin1String("C"), Qt::CaseInsensitive) == 0)
521         name.clear();
522     return name;
523 }
524
525 GeneratorScriptArgument::GeneratorScriptArgument(const QString &v) :
526     value(v), flags(0)
527 {
528 }
529
530 // Main parsing routine
531 CustomWizardParameters::ParseResult
532      CustomWizardParameters::parse(QIODevice &device,
533                                    const QString &configFileFullPath,
534                                    Core::BaseFileWizardParameters *bp,
535                                    QString *errorMessage)
536 {
537     int comboEntryCount = 0;
538     QXmlStreamReader reader(&device);
539     QXmlStreamReader::TokenType token = QXmlStreamReader::EndDocument;
540     ParseState state = ParseBeginning;
541     clear();
542     bp->clear();
543     bp->setKind(Core::IWizard::ProjectWizard);
544     const QString language = languageSetting();
545     CustomWizardField field;
546     do {
547         token = reader.readNext();
548         switch (token) {
549         case QXmlStreamReader::Invalid:
550             *errorMessage = msgError(reader, configFileFullPath, reader.errorString());
551             return ParseFailed;
552         case QXmlStreamReader::StartElement:
553             do {
554                 // Read out subelements applicable to current state
555                 if (state == ParseWithinWizard && parseCustomProjectElement(reader, configFileFullPath, language, this, bp))
556                     break;
557                 // switch to next state
558                 state = nextOpeningState(state, reader.name());
559                 // Read attributes post state-switching
560                 switch (state) {
561                 case ParseError:
562                     *errorMessage = msgError(reader, configFileFullPath,
563                                              QString::fromLatin1("Unexpected start element %1").arg(reader.name().toString()));
564                     return ParseFailed;
565                 case ParseWithinWizard:
566                     if (!booleanAttributeValue(reader, wizardEnabledAttributeC, true))
567                         return ParseDisabled;
568                     bp->setId(attributeValue(reader, idAttributeC));
569                     bp->setCategory(attributeValue(reader, categoryAttributeC));
570                     bp->setKind(kindAttribute(reader));
571                     klass = attributeValue(reader, klassAttributeC);
572                     firstPageId = integerAttributeValue(reader, firstPageAttributeC, -1);
573                     break;
574                 case ParseWithinField: // field attribute
575                     field.name = attributeValue(reader, fieldNameAttributeC);
576                     field.mandatory = booleanAttributeValue(reader, fieldMandatoryAttributeC, false);
577                     break;
578                 case ParseWithinFieldDescription:
579                     assignLanguageElementText(reader, language, &field.description);
580                     state = ParseWithinField; // The above reads away the end tag, set state.
581                     break;
582                 case ParseWithinFieldControl: // Copy widget control attributes
583                     field.controlAttributes = attributesToStringMap(reader.attributes());
584                     break;
585                 case ParseWithinComboEntries:
586                     break;
587                 case ParseWithinComboEntry: // Combo entry with 'value' attribute
588                     field.controlAttributes.insert(CustomWizardField::comboEntryValueKey(comboEntryCount),
589                                                    attributeValue(reader, "value"));
590                     break;
591                 case ParseWithinComboEntryText: {
592
593                         // This reads away the end tag, set state here.
594                         QString text;
595                         if (assignLanguageElementText(reader, language, &text))
596                             field.controlAttributes.insert(CustomWizardField::comboEntryTextKey(comboEntryCount),
597                                                            text);
598                         state = ParseWithinComboEntry;
599                 }
600                      break;
601                 case ParseWithinFiles:
602                     break;
603                 case ParseWithinFile: { // file attribute
604                         CustomWizardFile file;
605                         file.source = attributeValue(reader, fileSourceAttributeC);
606                         file.target = attributeValue(reader, fileTargetAttributeC);
607                         file.openEditor = booleanAttributeValue(reader, customWizardFileOpenEditorAttributeC, false);
608                         file.openProject = booleanAttributeValue(reader, customWizardFileOpenProjectAttributeC, false);
609                         file.binary = booleanAttributeValue(reader, fileBinaryAttributeC, false);
610                         if (file.target.isEmpty())
611                             file.target = file.source;
612                         if (file.source.isEmpty()) {
613                             qWarning("Skipping empty file name in custom project.");
614                         } else {
615                             files.push_back(file);
616                         }
617                     }
618                     break;
619                 case ParseWithinScript:
620                     filesGeneratorScript = fixGeneratorScript(configFileFullPath, attributeValue(reader, generatorScriptBinaryAttributeC));
621                     if (filesGeneratorScript.isEmpty()) {
622                         *errorMessage = QString::fromLatin1("No binary specified for generator script.");
623                         return ParseFailed;
624                     }
625                     filesGeneratorScriptWorkingDirectory = attributeValue(reader, generatorScriptWorkingDirectoryAttributeC);
626                     break;
627                 case ParseWithinScriptArguments: {
628                     GeneratorScriptArgument argument(attributeValue(reader, generatorScriptArgumentValueAttributeC));
629                     if (argument.value.isEmpty()) {
630                         *errorMessage = QString::fromLatin1("No value specified for generator script argument.");
631                         return ParseFailed;
632                     }
633                     if (booleanAttributeValue(reader, generatorScriptArgumentOmitEmptyAttributeC, false))
634                         argument.flags |= GeneratorScriptArgument::OmitEmpty;
635                     if (booleanAttributeValue(reader, generatorScriptArgumentWriteFileAttributeC, false))
636                         argument.flags |= GeneratorScriptArgument::WriteFile;
637                     filesGeneratorScriptArguments.push_back(argument);
638                 }
639                     break;
640                 case ParseWithinValidationRule: {
641                     CustomWizardValidationRule rule;
642                     rule.condition = reader.attributes().value(QLatin1String(ruleConditionAttributeC)).toString();
643                     rules.push_back(rule);
644                 }
645                     break;
646                 case ParseWithinValidationRuleMessage:
647                     QTC_ASSERT(!rules.isEmpty(), return ParseFailed; )
648                     // This reads away the end tag, set state here.
649                     assignLanguageElementText(reader, language, &(rules.back().message));
650                     state = ParseWithinValidationRule;
651                     break;
652                 default:
653                     break;
654                 }
655             } while (false);
656             break;
657         case QXmlStreamReader::EndElement:
658             state = nextClosingState(state, reader.name());
659             switch (state) {
660             case ParseError:
661                 *errorMessage = msgError(reader, configFileFullPath,
662                                          QString::fromLatin1("Unexpected end element %1").arg(reader.name().toString()));
663                 return ParseFailed;
664             case ParseWithinFields:  // Leaving a field element
665                 fields.push_back(field);
666                 field.clear();
667                 break;
668             case ParseWithinComboEntries:
669                 comboEntryCount++;
670                 break;
671             default:
672                 break;
673             }
674             break;
675         default:
676             break;
677         }
678     } while (token != QXmlStreamReader::EndDocument);
679     return ParseOk;
680 }
681
682 CustomWizardParameters::ParseResult
683      CustomWizardParameters::parse(const QString &configFileFullPath,
684                                    Core::BaseFileWizardParameters *bp,
685                                    QString *errorMessage)
686 {
687     QFile configFile(configFileFullPath);
688     if (!configFile.open(QIODevice::ReadOnly|QIODevice::Text)) {
689         *errorMessage = QString::fromLatin1("Cannot open %1: %2").arg(configFileFullPath, configFile.errorString());
690         return ParseFailed;
691     }
692     return parse(configFile, configFileFullPath, bp, errorMessage);
693 }
694
695 QString CustomWizardParameters::toString() const
696 {
697     QString rc;
698     QTextStream str(&rc);
699     str << "Directory: " << directory << " Klass: '" << klass << "'\n";
700     if (!filesGeneratorScriptArguments.isEmpty()) {
701         str << "Script:";
702         foreach(const QString &a, filesGeneratorScript)
703             str << " '" << a << '\'';
704         if (!filesGeneratorScriptWorkingDirectory.isEmpty())
705             str << "\nrun in '" <<  filesGeneratorScriptWorkingDirectory << '\'';
706         str << "\nArguments: ";
707         foreach(const GeneratorScriptArgument &a, filesGeneratorScriptArguments) {
708             str << " '" << a.value  << '\'';
709             if (a.flags & GeneratorScriptArgument::OmitEmpty)
710                 str << " [omit empty]";
711             if (a.flags & GeneratorScriptArgument::WriteFile)
712                 str << " [write file]";
713             str << ',';
714         }
715         str << '\n';
716     }
717     foreach(const CustomWizardFile &f, files) {
718         str << "  File source: " << f.source << " Target: " << f.target;
719         if (f.openEditor)
720             str << " [editor]";
721         if (f.openProject)
722             str << " [project]";
723         if (f.binary)
724             str << " [binary]";
725         str << '\n';
726     }
727     foreach(const CustomWizardField &f, fields) {
728         str << "  Field name: " << f.name;
729         if (f.mandatory)
730             str << '*';
731         str << " Description: '" << f.description << '\'';
732         if (!f.controlAttributes.isEmpty()) {
733             typedef CustomWizardField::ControlAttributeMap::const_iterator AttrMapConstIt;
734             str << " Control: ";
735             const AttrMapConstIt cend = f.controlAttributes.constEnd();
736             for (AttrMapConstIt it = f.controlAttributes.constBegin(); it != cend; ++it)
737                 str << '\'' << it.key() << "' -> '" << it.value() << "' ";
738         }
739         str << '\n';
740     }
741     foreach(const CustomWizardValidationRule &r, rules)
742             str << "  Rule: '" << r.condition << "'->'" << r.message << '\n';
743     return rc;
744 }
745
746 // ------------ CustomWizardContext
747
748 static inline QString passThrough(const QString &in) { return in; }
749
750 // Do field replacements applying modifiers and string transformation
751 // for the value
752 template <class ValueStringTransformation>
753 bool replaceFieldHelper(ValueStringTransformation transform,
754                         const CustomWizardContext::FieldReplacementMap &fm,
755                         QString *s)
756 {
757     bool nonEmptyReplacements = false;
758     if (debug) {
759         qDebug().nospace() << "CustomWizardContext::replaceFields with " <<
760                 fm << *s;
761     }
762     const QChar delimiter = QLatin1Char('%');
763     const QChar modifierDelimiter = QLatin1Char(':');
764     int pos = 0;
765     while (pos < s->size()) {
766         pos = s->indexOf(delimiter, pos);
767         if (pos < 0)
768             break;
769         int nextPos = s->indexOf(delimiter, pos + 1);
770         if (nextPos == -1)
771             break;
772         nextPos++; // Point past 2nd delimiter
773         if (nextPos == pos + 2) {
774             pos = nextPos; // Skip '%%'
775             continue;
776         }
777         // Evaluate field specification for modifiers
778         // "%field:l%"
779         QString fieldSpec = s->mid(pos + 1, nextPos - pos - 2);
780         const int fieldSpecSize = fieldSpec.size();
781         char modifier = '\0';
782         if (fieldSpecSize >= 3 && fieldSpec.at(fieldSpecSize - 2) == modifierDelimiter) {
783             modifier = fieldSpec.at(fieldSpecSize - 1).toLatin1();
784             fieldSpec.truncate(fieldSpecSize - 2);
785         }
786         const CustomWizardContext::FieldReplacementMap::const_iterator it = fm.constFind(fieldSpec);
787         if (it == fm.constEnd()) {
788             pos = nextPos; // Not found, skip
789             continue;
790         }
791         // Assign
792         QString replacement = it.value();
793         switch (modifier) {
794         case 'l':
795             replacement = it.value().toLower();
796             break;
797         case 'u':
798             replacement = it.value().toUpper();
799             break;
800         case 'c': // Capitalize first letter
801             replacement = it.value();
802             if (!replacement.isEmpty())
803                 replacement[0] = replacement.at(0).toUpper();
804             break;
805         default:
806             break;
807         }
808         if (!replacement.isEmpty())
809             nonEmptyReplacements = true;
810         // Apply transformation to empty values as well.
811         s->replace(pos, nextPos - pos, transform(replacement));
812         nonEmptyReplacements = true;
813         pos += replacement.size();
814     }
815     return nonEmptyReplacements;
816 }
817
818 bool CustomWizardContext::replaceFields(const FieldReplacementMap &fm, QString *s)
819 {
820     return replaceFieldHelper(passThrough, fm, s);
821 }
822
823 // Transformation to be passed to replaceFieldHelper(). Writes the
824 // value to a text file and returns the file name to be inserted
825 // instead of the expanded field in the parsed template,
826 // used for the arguments of a generator script.
827 class TemporaryFileTransform {
828 public:
829     typedef CustomWizardContext::TemporaryFilePtr TemporaryFilePtr;
830     typedef CustomWizardContext::TemporaryFilePtrList TemporaryFilePtrList;
831
832     explicit TemporaryFileTransform(TemporaryFilePtrList *f);
833
834     QString operator()(const QString &) const;
835
836 private:
837     TemporaryFilePtrList *m_files;
838     QString m_pattern;
839 };
840
841 TemporaryFileTransform::TemporaryFileTransform(TemporaryFilePtrList *f) :
842     m_files(f), m_pattern(QDir::tempPath())
843 {
844     if (!m_pattern.endsWith(QLatin1Char('/')))
845         m_pattern += QLatin1Char('/');
846     m_pattern += QLatin1String("qtcreatorXXXXXX.txt");
847 }
848
849 QString TemporaryFileTransform::operator()(const QString &value) const
850 {
851     TemporaryFilePtr temporaryFile(new QTemporaryFile(m_pattern));
852     QTC_ASSERT(temporaryFile->open(), return QString(); )
853
854     temporaryFile->write(value.toLocal8Bit());
855     const QString name = temporaryFile->fileName();
856     temporaryFile->flush();
857     temporaryFile->close();
858     m_files->push_back(temporaryFile);
859     return name;
860 }
861
862 bool CustomWizardContext::replaceFields(const FieldReplacementMap &fm, QString *s,
863                                         TemporaryFilePtrList *files)
864 {
865     return replaceFieldHelper(TemporaryFileTransform(files), fm, s);
866 }
867
868 void CustomWizardContext::reset()
869 {
870     // Basic replacement fields: Suffixes.
871     baseReplacements.clear();
872     const Core::MimeDatabase *mdb = Core::ICore::instance()->mimeDatabase();
873     baseReplacements.insert(QLatin1String("CppSourceSuffix"),
874                             mdb->preferredSuffixByType(QLatin1String(CppTools::Constants::CPP_SOURCE_MIMETYPE)));
875     baseReplacements.insert(QLatin1String("CppHeaderSuffix"),
876                             mdb->preferredSuffixByType(QLatin1String(CppTools::Constants::CPP_HEADER_MIMETYPE)));
877     replacements.clear();
878     path.clear();
879     targetPath.clear();
880 }
881
882 QString CustomWizardContext::processFile(const FieldReplacementMap &fm, QString in)
883 {
884
885     if (in.isEmpty())
886         return in;
887
888     if (!fm.isEmpty())
889         replaceFields(fm, &in);
890
891     QString out;
892     QString errorMessage;
893     if (!customWizardPreprocess(in, &out, &errorMessage)) {
894         qWarning("Error preprocessing custom widget file: %s\nFile:\n%s",
895                  qPrintable(errorMessage), qPrintable(in));
896         return QString();
897     }
898     return out;
899 }
900
901 } // namespace Internal
902 } // namespace ProjectExplorer