diff --git a/.gitignore b/.gitignore
index 79afd26b178cefd6dd480d44c018a515d5367a6a..c1e0548149a3e96e5d2e85ffd8e200da54ad40ce 100644
--- a/.gitignore
+++ b/.gitignore
@@ -196,6 +196,7 @@ tmp/
 /tests/auto/treeviewfind/tst_treeviewfind
 /tests/auto/utils/ansiescapecodehandler/tst_ansiescapecodehandler
 /tests/auto/utils/fileutils/tst_fileutils
+/tests/auto/utils/templateengine/tst_templateengine
 /tests/auto/utils_stringutils/tst_utils_stringutils
 /tests/auto/valgrind/callgrind/tst_callgrindparsertests
 /tests/auto/valgrind/memcheck/modeldemo
diff --git a/src/plugins/projectexplorer/customwizard/customwizardpreprocessor.cpp b/src/libs/utils/templateengine.cpp
similarity index 54%
rename from src/plugins/projectexplorer/customwizard/customwizardpreprocessor.cpp
rename to src/libs/utils/templateengine.cpp
index e2493707d537d01a70616417c4a829e4b94599c6..c372276e7a55ba6f0743feb7f784f40e3c8838f3 100644
--- a/src/plugins/projectexplorer/customwizard/customwizardpreprocessor.cpp
+++ b/src/libs/utils/templateengine.cpp
@@ -28,24 +28,16 @@
 **
 ****************************************************************************/
 
-#include "customwizardpreprocessor.h"
-#ifdef WITH_TESTS
-#  include "../projectexplorer.h"
-#  include <QTest>
-#endif
+#include "templateengine.h"
 
-#include <utils/qtcassert.h>
+#include "qtcassert.h"
 
-#include <QStringList>
-#include <QStack>
-#include <QRegExp>
-#include <QDebug>
 #include <QJSEngine>
+#include <QStack>
 
-namespace ProjectExplorer {
-namespace Internal {
+namespace Utils {
 
-enum  { debug = 0 };
+namespace Internal {
 
 // Preprocessor: Conditional section type.
 enum PreprocessorSection {
@@ -58,7 +50,8 @@ enum PreprocessorSection {
 
 // Preprocessor: Section stack entry containing nested '@if' section
 // state.
-struct PreprocessStackEntry {
+class PreprocessStackEntry {
+public:
     PreprocessStackEntry(PreprocessorSection section = OtherSection,
                          bool parentEnabled = true,
                          bool condition = false,
@@ -70,11 +63,9 @@ struct PreprocessStackEntry {
     bool anyIfClauseMatched; // Determines if 'else' triggers
 };
 
-PreprocessStackEntry::PreprocessStackEntry(PreprocessorSection s,
-                                           bool p, bool c, bool a) :
+PreprocessStackEntry::PreprocessStackEntry(PreprocessorSection s, bool p, bool c, bool a) :
     section(s), parentEnabled(p), condition(c), anyIfClauseMatched(a)
-{
-}
+{ }
 
 // Context for preprocessing.
 class PreprocessContext {
@@ -102,13 +93,14 @@ PreprocessContext::PreprocessContext() :
     m_elsePattern(QLatin1String("^[\\s]*@[\\s]*else.*$")),
     m_endifPattern(QLatin1String("^[\\s]*@[\\s]*endif.*$"))
 {
-    QTC_ASSERT(m_ifPattern.isValid() && m_elsifPattern.isValid()
-               && m_elsePattern.isValid() &&m_endifPattern.isValid(), return);
+    QTC_CHECK(m_ifPattern.isValid() && m_elsifPattern.isValid()
+              && m_elsePattern.isValid() && m_endifPattern.isValid());
 }
 
 void PreprocessContext::reset()
 {
-    m_sectionStack.clear(); // Add a default, enabled section.
+    m_sectionStack.clear();
+    // Add a default, enabled section.
     m_sectionStack.push(PreprocessStackEntry(OtherSection, true, true));
 }
 
@@ -135,35 +127,6 @@ PreprocessorSection PreprocessContext::preprocessorLine(const QString &in,
     return OtherSection;
 }
 
-// Evaluate an expression within an 'if'/'elsif' to a bool via QJSEngine
-bool evaluateBooleanJavaScriptExpression(QJSEngine &engine, const QString &expression, bool *result, QString *errorMessage)
-{
-    errorMessage->clear();
-    *result = false;
-    const QJSValue value = engine.evaluate(expression);
-    if (value.isError()) {
-        *errorMessage = QString::fromLatin1("Error in \"%1\": %2").
-                        arg(expression, value.toString());
-        return false;
-    }
-    // Try to convert to bool, be that an int or whatever.
-    if (value.isBool()) {
-        *result = value.toBool();
-        return true;
-    }
-    if (value.isNumber()) {
-        *result = !qFuzzyCompare(value.toNumber(), 0);
-        return true;
-    }
-    if (value.isString()) {
-        *result = !value.toString().isEmpty();
-        return true;
-    }
-    *errorMessage = QString::fromLatin1("Cannot convert result of \"%1\" (\"%2\"to bool.").
-                        arg(expression, value.toString());
-    return false;
-}
-
 static inline QString msgEmptyStack(int line)
 {
     return QString::fromLatin1("Unmatched '@endif' at line %1.").arg(line);
@@ -184,7 +147,8 @@ bool PreprocessContext::process(const QString &in, QString *out, QString *errorM
     for (int l = 0; l < lineCount; l++) {
         // Check for element of the stack (be it dummy, else something is wrong).
         if (m_sectionStack.isEmpty()) {
-            *errorMessage = msgEmptyStack(l);
+            if (errorMessage)
+                *errorMessage = msgEmptyStack(l);
             return false;
         }
     QString expression;
@@ -195,32 +159,33 @@ bool PreprocessContext::process(const QString &in, QString *out, QString *errorM
         case IfSection:
             // '@If': Push new section
             if (top.condition) {
-                if (!evaluateBooleanJavaScriptExpression(m_scriptEngine, expression, &expressionValue, errorMessage)) {
-                    *errorMessage = QString::fromLatin1("Error in @if at %1: %2").
-                            arg(l + 1).arg(*errorMessage);
+                if (!TemplateEngine::evaluateBooleanJavaScriptExpression(m_scriptEngine, expression,
+                                                                         &expressionValue, errorMessage)) {
+                    if (errorMessage)
+                        *errorMessage = QString::fromLatin1("Error in @if at %1: %2")
+                            .arg(l + 1).arg(*errorMessage);
                     return false;
                 }
             }
-            if (debug)
-                qDebug("'%s' : expr='%s' -> %d", qPrintable(lines.at(l)), qPrintable(expression), expressionValue);
             m_sectionStack.push(PreprocessStackEntry(IfSection,
                                                      top.condition, expressionValue, expressionValue));
             break;
         case ElsifSection: // '@elsif': Check condition.
             if (top.section != IfSection && top.section != ElsifSection) {
-                *errorMessage = QString::fromLatin1("No preceding @if found for @elsif at %1").
-                                arg(l + 1);
+                if (errorMessage)
+                    *errorMessage = QString::fromLatin1("No preceding @if found for @elsif at %1")
+                        .arg(l + 1);
                 return false;
             }
             if (top.parentEnabled) {
-                if (!evaluateBooleanJavaScriptExpression(m_scriptEngine, expression, &expressionValue, errorMessage)) {
-                    *errorMessage = QString::fromLatin1("Error in @elsif at %1: %2").
-                            arg(l + 1).arg(*errorMessage);
+                if (!TemplateEngine::evaluateBooleanJavaScriptExpression(m_scriptEngine, expression,
+                                                                         &expressionValue, errorMessage)) {
+                    if (errorMessage)
+                        *errorMessage = QString::fromLatin1("Error in @elsif at %1: %2")
+                            .arg(l + 1).arg(*errorMessage);
                     return false;
                 }
             }
-            if (debug)
-                qDebug("'%s' : expr='%s' -> %d", qPrintable(lines.at(l)), qPrintable(expression), expressionValue);
             top.section = ElsifSection;
             // ignore consecutive '@elsifs' once something matched
             if (top.anyIfClauseMatched) {
@@ -232,13 +197,12 @@ bool PreprocessContext::process(const QString &in, QString *out, QString *errorM
             break;
         case ElseSection: // '@else': Check condition.
             if (top.section != IfSection && top.section != ElsifSection) {
-                *errorMessage = QString::fromLatin1("No preceding @if/@elsif found for @else at %1").
-                                                    arg(l + 1);
+                if (errorMessage)
+                    *errorMessage = QString::fromLatin1("No preceding @if/@elsif found for @else at %1")
+                        .arg(l + 1);
                 return false;
             }
             expressionValue = top.parentEnabled && !top.anyIfClauseMatched;
-            if (debug)
-                qDebug("%s -> %d", qPrintable(lines.at(l)), expressionValue);
             top.section = ElseSection;
             top.condition = expressionValue;
             break;
@@ -257,97 +221,96 @@ bool PreprocessContext::process(const QString &in, QString *out, QString *errorM
     return true;
 }
 
-/*!
-    Implements a custom wizard preprocessor based on JavaScript expressions.
-
-    Preprocesses a string using a simple syntax:
-    \code
-Text
-@if <JavaScript-expression>
-Bla...
-@elsif <JavaScript-expression2>
-Blup
-@endif
-\endcode
-
-    The JavaScript-expressions must evaluate to integers or boolean, like
-    \c '2 == 1 + 1', \c '"a" == "a"'. The variables of the custom wizard will be
-    expanded before, so \c "%VAR%" should be used for strings and \c %VAR% for integers.
-
-    \sa ProjectExplorer::CustomWizard
-*/
+} // namespace Internal
 
-bool customWizardPreprocess(const QString &in, QString *out, QString *errorMessage)
+bool TemplateEngine::preprocessText(const QString &in, QString *out, QString *errorMessage)
 {
-    PreprocessContext context;
+    Internal::PreprocessContext context;
     return context.process(in, out, errorMessage);
 }
 
-} // namespace Internal
-
-#ifdef WITH_TESTS // Run qtcreator -test ProjectExplorer
-
-void ProjectExplorerPlugin::testCustomWizardPreprocessor_data()
+QString TemplateEngine::processText(MacroExpander *expander, const QString &input,
+                                    QString *errorMessage)
 {
-    QTest::addColumn<QString>("input");
-    QTest::addColumn<QString>("expectedOutput");
-    QTest::addColumn<bool>("expectedSuccess");
-    QTest::addColumn<QString>("expectedErrorMessage");
-    QTest::newRow("if")
-        << QString::fromLatin1("@if 1\nline 1\n@elsif 0\nline 2\n@else\nline 3\n@endif\n")
-        << QString::fromLatin1("line 1")
-        << true << QString();
-    QTest::newRow("elsif")
-        << QString::fromLatin1("@if 0\nline 1\n@elsif 1\nline 2\n@else\nline 3\n@endif\n")
-        << QString::fromLatin1("line 2")
-        << true << QString();
-    QTest::newRow("else")
-        << QString::fromLatin1("@if 0\nline 1\n@elsif 0\nline 2\n@else\nline 3\n@endif\n")
-        << QString::fromLatin1("line 3")
-        << true << QString();
-    QTest::newRow("nested-if")
-        << QString::fromLatin1("@if 1\n"
-                               "  @if 1\nline 1\n@elsif 0\nline 2\n@else\nline 3\n@endif\n"
-                               "@else\n"
-                               "  @if 1\nline 4\n@elsif 0\nline 5\n@else\nline 6\n@endif\n"
-                               "@endif\n")
-        << QString::fromLatin1("line 1")
-        << true << QString();
-    QTest::newRow("nested-else")
-        << QString::fromLatin1("@if 0\n"
-                               "  @if 1\nline 1\n@elsif 0\nline 2\n@else\nline 3\n@endif\n"
-                               "@else\n"
-                               "  @if 1\nline 4\n@elsif 0\nline 5\n@else\nline 6\n@endif\n"
-                               "@endif\n")
-        << QString::fromLatin1("line 4")
-        << true << QString();
-    QTest::newRow("twice-nested-if")
-        << QString::fromLatin1("@if 0\n"
-                               "  @if 1\n"
-                               "    @if 1\nline 1\n@else\nline 2\n@endif\n"
-                               "  @endif\n"
-                               "@else\n"
-                               "  @if 1\n"
-                               "    @if 1\nline 3\n@else\nline 4\n@endif\n"
-                               "  @endif\n"
-                               "@endif\n")
-        << QString::fromLatin1("line 3")
-        << true << QString();
+    if (errorMessage)
+        errorMessage->clear();
+
+    if (input.isEmpty())
+        return input;
+
+    // Recursively expand macros:
+    QString in = input;
+    QString oldIn;
+    for (int i = 0; i < 5 && in != oldIn; ++i) {
+        oldIn = in;
+        in = expander->expand(oldIn);
+    }
+
+    QString out;
+    if (!preprocessText(in, &out, errorMessage))
+        return QString();
+
+    // Expand \n, \t and handle line continuation:
+    QString result;
+    result.reserve(out.count());
+    bool isEscaped = false;
+    for (int i = 0; i < out.count(); ++i) {
+        const QChar c = out.at(i);
+
+        if (isEscaped) {
+            if (c == QLatin1Char('n'))
+                result.append(QLatin1Char('\n'));
+            else if (c == QLatin1Char('t'))
+                result.append(QLatin1Char('\t'));
+            else if (c != QLatin1Char('\n'))
+                result.append(c);
+            isEscaped = false;
+        } else {
+            if (c == QLatin1Char('\\'))
+                isEscaped = true;
+            else
+                result.append(c);
+        }
+    }
+    return result;
 }
 
-void ProjectExplorerPlugin::testCustomWizardPreprocessor()
+bool TemplateEngine::evaluateBooleanJavaScriptExpression(QJSEngine &engine,
+                                                         const QString &expression, bool *result,
+                                                         QString *errorMessage)
 {
-    QFETCH(QString, input);
-    QFETCH(QString, expectedOutput);
-    QFETCH(bool, expectedSuccess);
-    QFETCH(QString, expectedErrorMessage);
-
-    QString errorMessage;
-    QString output;
-    const bool success = Internal::customWizardPreprocess(input, &output, &errorMessage);
-    QCOMPARE(success, expectedSuccess);
-    QCOMPARE(output.trimmed(), expectedOutput.trimmed());
-    QCOMPARE(errorMessage, expectedErrorMessage);
+    if (errorMessage)
+        errorMessage->clear();
+    if (result)
+        *result = false;
+    const QJSValue value = engine.evaluate(expression);
+    if (value.isError()) {
+        if (errorMessage)
+            *errorMessage = QString::fromLatin1("Error in \"%1\": %2")
+                .arg(expression, value.toString());
+        return false;
+    }
+    // Try to convert to bool, be that an int or whatever.
+    if (value.isBool()) {
+        if (result)
+            *result = value.toBool();
+        return true;
+    }
+    if (value.isNumber()) {
+        if (result)
+            *result = !qFuzzyCompare(value.toNumber(), 0);
+        return true;
+    }
+    if (value.isString()) {
+        if (result)
+            *result = !value.toString().isEmpty();
+        return true;
+    }
+    if (errorMessage)
+        *errorMessage = QString::fromLatin1("Cannot convert result of \"%1\" (\"%2\"to bool.")
+            .arg(expression, value.toString());
+
+    return false;
 }
-#endif // WITH_TESTS
-} // namespace ProjectExplorer
+
+} // namespace Utils
diff --git a/src/plugins/projectexplorer/customwizard/customwizardpreprocessor.h b/src/libs/utils/templateengine.h
similarity index 69%
rename from src/plugins/projectexplorer/customwizard/customwizardpreprocessor.h
rename to src/libs/utils/templateengine.h
index 66af67609fcb7ab184044c750cbbff0b73086eef..a277841ca09eb43c53d12108172dbe95aa41b859 100644
--- a/src/plugins/projectexplorer/customwizard/customwizardpreprocessor.h
+++ b/src/libs/utils/templateengine.h
@@ -28,20 +28,30 @@
 **
 ****************************************************************************/
 
-#ifndef CUSTOMWIZARDPREPROCESSOR_H
-#define CUSTOMWIZARDPREPROCESSOR_H
+#ifndef TEMPLATEENGINE_H
+#define TEMPLATEENGINE_H
+
+#include "utils_global.h"
+
+#include "macroexpander.h"
 
 #include <QString>
 
-QT_FORWARD_DECLARE_CLASS(QJSEngine)
+QT_FORWARD_DECLARE_CLASS(QJSEngine);
+
+namespace Utils {
+
+class QTCREATOR_UTILS_EXPORT TemplateEngine {
+public:
+    static bool preprocessText(const QString &input, QString *output, QString *errorMessage);
+
+    static QString processText(MacroExpander *expander, const QString &input,
+                               QString *errorMessage);
 
-namespace ProjectExplorer {
-namespace Internal {
+    static bool evaluateBooleanJavaScriptExpression(QJSEngine &engine, const QString &expression,
+                                                    bool *result, QString *errorMessage);
+};
 
-bool customWizardPreprocess(const QString &in, QString *out, QString *errorMessage);
-/* Helper to evaluate an expression. */
-bool evaluateBooleanJavaScriptExpression(QJSEngine &engine, const QString &expression, bool *result, QString *errorMessage);
-} // namespace Internal
-} // namespace ProjectExplorer
+} // namespace Utils
 
-#endif // CUSTOMWIZARDPREPROCESSOR_H
+#endif // TEMPLATEENGINE_H
diff --git a/src/libs/utils/utils-lib.pri b/src/libs/utils/utils-lib.pri
index a4b7ed138182f78bd1a464c49b9b77d90cdd6a19..bb66f7b5a5a348add6bc40f4d2bb223f1deb2609 100644
--- a/src/libs/utils/utils-lib.pri
+++ b/src/libs/utils/utils-lib.pri
@@ -16,6 +16,7 @@ SOURCES += $$PWD/environment.cpp \
     $$PWD/shellcommandpage.cpp \
     $$PWD/settingsselector.cpp \
     $$PWD/stringutils.cpp \
+    $$PWD/templateengine.cpp \
     $$PWD/textfieldcheckbox.cpp \
     $$PWD/textfieldcombobox.cpp \
     $$PWD/filesearch.cpp \
@@ -107,6 +108,7 @@ HEADERS += \
     $$PWD/shellcommand.h \
     $$PWD/shellcommandpage.h \
     $$PWD/stringutils.h \
+    $$PWD/templateengine.h \
     $$PWD/textfieldcheckbox.h \
     $$PWD/textfieldcombobox.h \
     $$PWD/filesearch.h \
diff --git a/src/libs/utils/utils.pro b/src/libs/utils/utils.pro
index cb28e4ad43eae0b803403abeb6d0b3eab0f551a3..d69ff0ff44b814809d9d611cc8f0021b925579ab 100644
--- a/src/libs/utils/utils.pro
+++ b/src/libs/utils/utils.pro
@@ -1,4 +1,4 @@
-QT += gui network
+QT += gui network qml
 
 include(../../qtcreatorlibrary.pri)
 include(utils-lib.pri)
diff --git a/src/libs/utils/utils.qbs b/src/libs/utils/utils.qbs
index 5b705a26bcfc0c31c5d0f945402bd1f84c59c807..c0e1879ee466f2a5f888bb182771fad454bd9e82 100644
--- a/src/libs/utils/utils.qbs
+++ b/src/libs/utils/utils.qbs
@@ -28,7 +28,7 @@ QtcLibrary {
         cpp.frameworks: ["Foundation"]
     }
 
-    Depends { name: "Qt"; submodules: ["widgets", "network", "script", "concurrent"] }
+    Depends { name: "Qt"; submodules: ["gui", "network", "qml"] }
     Depends { name: "app_version_header" }
 
     files: [
diff --git a/src/plugins/projectexplorer/customwizard/customwizard.pri b/src/plugins/projectexplorer/customwizard/customwizard.pri
index fa3cf931e250a09ef1d6d10281f8680b835833b0..ce2c0a723e86a902e82d26d1acbfe37209baa85c 100644
--- a/src/plugins/projectexplorer/customwizard/customwizard.pri
+++ b/src/plugins/projectexplorer/customwizard/customwizard.pri
@@ -1,10 +1,8 @@
 HEADERS += $$PWD/customwizard.h \
     $$PWD/customwizardparameters.h \
     $$PWD/customwizardpage.h \
-    customwizard/customwizardpreprocessor.h \
-    customwizard/customwizardscriptgenerator.h
+    $$PWD/customwizardscriptgenerator.h
 SOURCES += $$PWD/customwizard.cpp \
     $$PWD/customwizardparameters.cpp \
     $$PWD/customwizardpage.cpp \
-    customwizard/customwizardpreprocessor.cpp \
-    customwizard/customwizardscriptgenerator.cpp
+    $$PWD/customwizardscriptgenerator.cpp
diff --git a/src/plugins/projectexplorer/customwizard/customwizardparameters.cpp b/src/plugins/projectexplorer/customwizard/customwizardparameters.cpp
index 86e9c3658e9d7faecf6d6b29974ef216ef0c1cfc..19a161c0a6abb8e86ea811059b96b7dfd320df58 100644
--- a/src/plugins/projectexplorer/customwizard/customwizardparameters.cpp
+++ b/src/plugins/projectexplorer/customwizard/customwizardparameters.cpp
@@ -29,13 +29,14 @@
 ****************************************************************************/
 
 #include "customwizardparameters.h"
-#include "customwizardpreprocessor.h"
 #include "customwizardscriptgenerator.h"
 
 #include <coreplugin/icore.h>
 #include <cpptools/cpptoolsconstants.h>
 
 #include <utils/mimetypes/mimedatabase.h>
+#include <utils/macroexpander.h>
+#include <utils/templateengine.h>
 #include <utils/qtcassert.h>
 
 #include <QCoreApplication>
@@ -194,7 +195,7 @@ bool CustomWizardValidationRule::validate(QJSEngine &engine, const QMap<QString,
     CustomWizardContext::replaceFields(replacementMap, &cond);
     bool valid = false;
     QString errorMessage;
-    if (!evaluateBooleanJavaScriptExpression(engine, cond, &valid, &errorMessage)) {
+    if (!Utils::TemplateEngine::evaluateBooleanJavaScriptExpression(engine, cond, &valid, &errorMessage)) {
         qWarning("Error in custom wizard validation expression '%s': %s",
                  qPrintable(cond), qPrintable(errorMessage));
         return false;
@@ -954,7 +955,7 @@ QString CustomWizardContext::processFile(const FieldReplacementMap &fm, QString
 
     QString out;
     QString errorMessage;
-    if (!customWizardPreprocess(in, &out, &errorMessage)) {
+    if (!Utils::TemplateEngine::preprocessText(in, &out, &errorMessage)) {
         qWarning("Error preprocessing custom widget file: %s\nFile:\n%s",
                  qPrintable(errorMessage), qPrintable(in));
         return QString();
diff --git a/src/plugins/projectexplorer/jsonwizard/jsonwizard.cpp b/src/plugins/projectexplorer/jsonwizard/jsonwizard.cpp
index 57cad839463ff4bd352342d5669c6f54fc8c90e7..2693271b5c8beb7b8607d634dc4a5be94a1e1336 100644
--- a/src/plugins/projectexplorer/jsonwizard/jsonwizard.cpp
+++ b/src/plugins/projectexplorer/jsonwizard/jsonwizard.cpp
@@ -34,7 +34,6 @@
 
 #include "../project.h"
 #include "../projectexplorer.h"
-#include "../customwizard/customwizardpreprocessor.h"
 
 #include <coreplugin/editormanager/editormanager.h>
 #include <coreplugin/messagemanager.h>
@@ -202,51 +201,6 @@ QHash<QString, QVariant> JsonWizard::variables() const
     return result;
 }
 
-QString JsonWizard::processText(Utils::MacroExpander *expander, const QString &input,
-                                QString *errorMessage)
-{
-    errorMessage->clear();
-
-    if (input.isEmpty())
-        return input;
-
-    // Recursively expand macros:
-    QString in = input;
-    QString oldIn;
-    for (int i = 0; i < 5 && in != oldIn; ++i) {
-        oldIn = in;
-        in = expander->expand(oldIn);
-    }
-
-    QString out;
-    if (!Internal::customWizardPreprocess(in, &out, errorMessage))
-        return QString();
-
-    // Expand \n, \t and handle line continuation:
-    QString result;
-    result.reserve(out.count());
-    bool isEscaped = false;
-    for (int i = 0; i < out.count(); ++i) {
-        const QChar c = out.at(i);
-
-        if (isEscaped) {
-            if (c == QLatin1Char('n'))
-                result.append(QLatin1Char('\n'));
-            else if (c == QLatin1Char('t'))
-                result.append(QLatin1Char('\t'));
-            else if (c != QLatin1Char('\n'))
-                result.append(c);
-            isEscaped = false;
-        } else {
-            if (c == QLatin1Char('\\'))
-                isEscaped = true;
-            else
-                result.append(c);
-        }
-    }
-    return result;
-}
-
 void JsonWizard::accept()
 {
     auto page = qobject_cast<Utils::WizardPage *>(currentPage());
diff --git a/src/plugins/projectexplorer/jsonwizard/jsonwizard.h b/src/plugins/projectexplorer/jsonwizard/jsonwizard.h
index cbbddaefa5e981ecc7782dc9dfa003d070b501b5..7809879b9f430da3598586248f037205886b4f72 100644
--- a/src/plugins/projectexplorer/jsonwizard/jsonwizard.h
+++ b/src/plugins/projectexplorer/jsonwizard/jsonwizard.h
@@ -86,9 +86,6 @@ public:
 
     QHash<QString, QVariant> variables() const override;
 
-    static QString processText(Utils::MacroExpander *expander, const QString &input,
-                               QString *errorMessage);
-
 signals:
     void preGenerateFiles(); // emitted before files are generated (can happen several times!)
     void postGenerateFiles(const JsonWizard::GeneratorFiles &files); // emitted after commitToFileList was called.
diff --git a/src/plugins/projectexplorer/jsonwizard/jsonwizardfilegenerator.cpp b/src/plugins/projectexplorer/jsonwizard/jsonwizardfilegenerator.cpp
index b6d2526ed84cfa762dcbe24581871352965706df..f80076527c545900fd6f157158f0f8fefcc3e696 100644
--- a/src/plugins/projectexplorer/jsonwizard/jsonwizardfilegenerator.cpp
+++ b/src/plugins/projectexplorer/jsonwizard/jsonwizardfilegenerator.cpp
@@ -39,6 +39,7 @@
 #include <utils/fileutils.h>
 #include <utils/qtcassert.h>
 #include <utils/macroexpander.h>
+#include <utils/templateengine.h>
 
 #include <QCoreApplication>
 #include <QDir>
@@ -164,8 +165,8 @@ Core::GeneratedFiles JsonWizardFileGenerator::fileList(Utils::MacroExpander *exp
                 });
                 nested.registerExtraResolver([expander](QString n, QString *ret) { return expander->resolveMacro(n, ret); });
 
-                gf.setContents(JsonWizard::processText(&nested, QString::fromUtf8(reader.data()),
-                                                       errorMessage));
+                gf.setContents(Utils::TemplateEngine::processText(&nested, QString::fromUtf8(reader.data()),
+                                                                  errorMessage));
                 if (!errorMessage->isEmpty()) {
                     *errorMessage = QCoreApplication::translate("ProjectExplorer::JsonWizard", "When processing \"%1\":<br>%2")
                             .arg(sourcePath, *errorMessage);
diff --git a/src/plugins/projectexplorer/jsonwizard/jsonwizardscannergenerator.cpp b/src/plugins/projectexplorer/jsonwizard/jsonwizardscannergenerator.cpp
index dc4c6da937ab57a676a111a34c5e83af52be67eb..d3597a51d53444052bd448f890f91e1a3903c1c2 100644
--- a/src/plugins/projectexplorer/jsonwizard/jsonwizardscannergenerator.cpp
+++ b/src/plugins/projectexplorer/jsonwizard/jsonwizardscannergenerator.cpp
@@ -30,7 +30,6 @@
 
 #include "jsonwizardscannergenerator.h"
 
-#include "../customwizard/customwizardpreprocessor.h"
 #include "../projectexplorer.h"
 #include "../iprojectmanager.h"
 #include "jsonwizard.h"
diff --git a/src/plugins/projectexplorer/projectexplorer.h b/src/plugins/projectexplorer/projectexplorer.h
index 5ef9b9713e504c87feff49da42566c3dd8f8e100..35415a1a0303d3fa2b6cc6e440d62804fb299045 100644
--- a/src/plugins/projectexplorer/projectexplorer.h
+++ b/src/plugins/projectexplorer/projectexplorer.h
@@ -219,9 +219,6 @@ private slots:
     void testAbiFromTargetTriplet();
 
     void testDeviceManager();
-
-    void testCustomWizardPreprocessor_data();
-    void testCustomWizardPreprocessor();
 #endif
 };
 
diff --git a/src/plugins/projectexplorer/projectexplorer.qbs b/src/plugins/projectexplorer/projectexplorer.qbs
index 4cba524ee7be18cd8c60ec97a916596e2d0bbb88..4b6f1faecd3f8768e09aeda342a982bd4d2c30e1 100644
--- a/src/plugins/projectexplorer/projectexplorer.qbs
+++ b/src/plugins/projectexplorer/projectexplorer.qbs
@@ -192,7 +192,6 @@ QtcPlugin {
             "customwizard.cpp", "customwizard.h",
             "customwizardpage.cpp", "customwizardpage.h",
             "customwizardparameters.cpp", "customwizardparameters.h",
-            "customwizardpreprocessor.cpp", "customwizardpreprocessor.h",
             "customwizardscriptgenerator.cpp", "customwizardscriptgenerator.h"
         ]
     }
diff --git a/tests/auto/utils/templateengine/templateengine.pro b/tests/auto/utils/templateengine/templateengine.pro
new file mode 100644
index 0000000000000000000000000000000000000000..53c4e77793cd76d587ce17d3f7289d80dbb06aab
--- /dev/null
+++ b/tests/auto/utils/templateengine/templateengine.pro
@@ -0,0 +1,6 @@
+QTC_LIB_DEPENDS += utils
+include(../../qttest.pri)
+
+DEFINES -= QT_USE_FAST_OPERATOR_PLUS QT_USE_FAST_CONCATENATION
+
+SOURCES += tst_templateengine.cpp
diff --git a/tests/auto/utils/templateengine/templateengine.qbs b/tests/auto/utils/templateengine/templateengine.qbs
new file mode 100644
index 0000000000000000000000000000000000000000..3dfc1c3238737f6f67f3f873771f8c4c2ed2961d
--- /dev/null
+++ b/tests/auto/utils/templateengine/templateengine.qbs
@@ -0,0 +1,7 @@
+import qbs
+
+QtcAutotest {
+    name: "StringUtils autotest"
+    Depends { name: "Utils" }
+    files: "tst_templateengine.cpp"
+}
diff --git a/tests/auto/utils/templateengine/tst_templateengine.cpp b/tests/auto/utils/templateengine/tst_templateengine.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..88da527ff557783515d8984ad39b69a6a5f32b13
--- /dev/null
+++ b/tests/auto/utils/templateengine/tst_templateengine.cpp
@@ -0,0 +1,109 @@
+/****************************************************************************
+**
+** Copyright (C) 2015 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company.  For licensing terms and
+** conditions see http://www.qt.io/terms-conditions.  For further information
+** use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 or version 3 as published by the Free
+** Software Foundation and appearing in the file LICENSE.LGPLv21 and
+** LICENSE.LGPLv3 included in the packaging of this file.  Please review the
+** following information to ensure the GNU Lesser General Public License
+** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, The Qt Company gives you certain additional
+** rights.  These rights are described in The Qt Company LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+****************************************************************************/
+
+#include <utils/templateengine.h>
+
+#include <QtTest>
+
+//TESTED_COMPONENT=src/libs/utils
+
+class tst_TemplateEngine : public QObject
+{
+    Q_OBJECT
+
+private slots:
+    void testTemplateEngine_data();
+    void testTemplateEngine();
+};
+
+void tst_TemplateEngine::testTemplateEngine_data()
+{
+    QTest::addColumn<QString>("input");
+    QTest::addColumn<QString>("expectedOutput");
+    QTest::addColumn<QString>("expectedErrorMessage");
+    QTest::newRow("if")
+        << QString::fromLatin1("@if 1\nline 1\n@elsif 0\nline 2\n@else\nline 3\n@endif\n")
+        << QString::fromLatin1("line 1\n\n")
+        << QString();
+    QTest::newRow("elsif")
+        << QString::fromLatin1("@if 0\nline 1\n@elsif 1\nline 2\n@else\nline 3\n@endif\n")
+        << QString::fromLatin1("line 2\n\n")
+        << QString();
+    QTest::newRow("else")
+        << QString::fromLatin1("@if 0\nline 1\n@elsif 0\nline 2\n@else\nline 3\n@endif\n")
+        << QString::fromLatin1("line 3\n\n")
+        << QString();
+    QTest::newRow("nested-if")
+        << QString::fromLatin1("@if 1\n"
+                               "  @if 1\nline 1\n@elsif 0\nline 2\n@else\nline 3\n@endif\n"
+                               "@else\n"
+                               "  @if 1\nline 4\n@elsif 0\nline 5\n@else\nline 6\n@endif\n"
+                               "@endif\n")
+        << QString::fromLatin1("line 1\n\n")
+        << QString();
+    QTest::newRow("nested-else")
+        << QString::fromLatin1("@if 0\n"
+                               "  @if 1\nline 1\n@elsif 0\nline 2\n@else\nline 3\n@endif\n"
+                               "@else\n"
+                               "  @if 1\nline 4\n@elsif 0\nline 5\n@else\nline 6\n@endif\n"
+                               "@endif\n")
+        << QString::fromLatin1("line 4\n\n")
+        << QString();
+    QTest::newRow("twice-nested-if")
+        << QString::fromLatin1("@if 0\n"
+                               "  @if 1\n"
+                               "    @if 1\nline 1\n@else\nline 2\n@endif\n"
+                               "  @endif\n"
+                               "@else\n"
+                               "  @if 1\n"
+                               "    @if 1\nline 3\n@else\nline 4\n@endif\n"
+                               "  @endif\n"
+                               "@endif\n")
+        << QString::fromLatin1("line 3\n\n")
+        << QString();
+}
+
+void tst_TemplateEngine::testTemplateEngine()
+{
+    QFETCH(QString, input);
+    QFETCH(QString, expectedOutput);
+    QFETCH(QString, expectedErrorMessage);
+
+    QString errorMessage;
+    QString output = Utils::TemplateEngine::processText(Utils::globalMacroExpander(), input, &errorMessage);
+
+    QCOMPARE(output, expectedOutput);
+    QCOMPARE(errorMessage, expectedErrorMessage);
+}
+
+
+QTEST_MAIN(tst_TemplateEngine)
+
+#include "tst_templateengine.moc"
diff --git a/tests/auto/utils/utils.pro b/tests/auto/utils/utils.pro
index 5bb6b1f2e3c5f9814ca0decd03519ceeec9b97fa..884634641e1656a7dc7dde158a8c816bfdfa61b6 100644
--- a/tests/auto/utils/utils.pro
+++ b/tests/auto/utils/utils.pro
@@ -4,4 +4,5 @@ SUBDIRS = \
     fileutils \
     ansiescapecodehandler \
     stringutils \
+    templateengine \
     treemodel
diff --git a/tests/auto/utils/utils.qbs b/tests/auto/utils/utils.qbs
index a5bb12ea50535a38d5587303f1ebf351460217fc..1917c51df2df2e4fb68385047f493d083de3ff47 100644
--- a/tests/auto/utils/utils.qbs
+++ b/tests/auto/utils/utils.qbs
@@ -6,6 +6,7 @@ Project {
         "fileutils/fileutils.qbs",
         "ansiescapecodehandler/ansiescapecodehandler.qbs",
         "stringutils/stringutils.qbs",
+        "templateengine/templateengine.qbs",
         "treemodel/treemodel.qbs",
     ]
 }