Initial import from the monolithic Qt.
[profile/ivi/qtxmlpatterns.git] / tools / xmlpatterns / main.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (qt-info@nokia.com)
6 **
7 ** This file is part of the QtXmlPatterns module of the Qt Toolkit.
8 **
9 ** $QT_BEGIN_LICENSE:LGPL$
10 ** No Commercial Usage
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 ** Alternatively, this file may be used under the terms of the GNU Lesser
18 ** General Public License version 2.1 as published by the Free Software
19 ** Foundation and appearing in the file LICENSE.LGPL included in the
20 ** packaging of this file.  Please review the following information to
21 ** ensure the GNU Lesser General Public License version 2.1 requirements
22 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
23 **
24 ** In addition, as a special exception, Nokia gives you certain additional
25 ** rights.  These rights are described in the Nokia Qt LGPL Exception
26 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
27 **
28 ** If you have questions regarding the use of this file, please contact
29 ** Nokia at qt-info@nokia.com.
30 **
31 **
32 **
33 **
34 **
35 **
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
42 #include <QtCore/QDir>
43 #include <QtCore/QtDebug>
44 #include <QtCore/QFile>
45 #include <QtCore/QFileInfo>
46 #include <QtCore/QStringList>
47 #include <QtCore/QTextCodec>
48 #include <QtCore/QTextStream>
49 #include <QtCore/QUrl>
50 #include <QtCore/QVariant>
51 #include <QtCore/QVector>
52 #include <QtCore/QCoreApplication>
53
54 #include <QtXmlPatterns/QXmlFormatter>
55 #include <QtXmlPatterns/QXmlItem>
56 #include <QtXmlPatterns/QXmlQuery>
57 #include <QtXmlPatterns/QXmlSerializer>
58
59 #include "private/qautoptr_p.h"
60 #include "qapplicationargument_p.h"
61 #include "qapplicationargumentparser_p.h"
62
63 #if defined(Q_OS_WIN) && !defined(Q_OS_WINCE)
64 /* Needed for opening stdout with _fdopen & friends. io.h seems to not be
65  * needed on MinGW though. */
66 #include <io.h>
67 #include <fcntl.h>
68 #endif
69
70 #include "main.h"
71
72 QT_USE_NAMESPACE
73
74 /* The two Q_DECLARE_METATYPE macros must appear before the code
75  * on a couple of HPUX platforms. */
76
77 /*!
78  \internal
79  \since 4.4
80  Represents the name and value found in "-param name=value".
81  */
82 typedef QPair<QString, QString> Parameter;
83 Q_DECLARE_METATYPE(Parameter)
84
85 /*!
86  \internal
87  \since 4.4
88  For the -output switch.
89  */
90 Q_DECLARE_METATYPE(QIODevice *)
91
92 /*!
93  \class PatternistApplicationParser
94  \brief Subclass to handle -param name=value
95  \internal
96  \since 4.4
97  \reentrant
98  */
99 class PatternistApplicationParser : public QApplicationArgumentParser
100 {
101 public:
102     inline PatternistApplicationParser(int argc, char **argv,
103                                        const QXmlNamePool &np) : QApplicationArgumentParser(argc, argv)
104                                                                , m_namePool(np)
105 #ifdef Q_OS_WIN
106                                                                , m_stdout(0)
107 #endif
108     {
109     }
110
111 #ifdef Q_OS_WIN
112     virtual ~PatternistApplicationParser()
113     {
114         /* QFile::~QFile() nor QFile::close() frees the handle when
115          * we use QFile::open() so we have to do it manually.
116          *
117          * "If stream is NULL, the invalid parameter handler is invoked," so
118          * lets try to avoid that. */
119         if(m_stdout)
120             fclose(m_stdout);
121     }
122 #endif
123
124 protected:
125     virtual QVariant convertToValue(const QApplicationArgument &arg,
126                                     const QString &input) const
127     {
128         if(arg.name() == QLatin1String("param"))
129         {
130             const int assign = input.indexOf(QLatin1Char('='));
131
132             if(assign == -1)
133             {
134                 message(QXmlPatternistCLI::tr("Each binding must contain an equal sign."));
135                 return QVariant();
136             }
137
138             const QString name(input.left(assign));
139             const QString value(input.mid(assign + 1));
140
141             if(!QXmlName::isNCName(name))
142             {
143                 message(QXmlPatternistCLI::tr("The variable name must be a valid NCName, which %1 isn't.").arg(name));
144                 return QVariant();
145             }
146
147             /* The value.isNull() check ensures we can bind variables whose value is an empty string. */
148             return QVariant::fromValue(Parameter(name, value.isNull() ? QString(QLatin1String("")) : value ));
149         }
150         else if(arg.name() == QLatin1String("output"))
151         {
152             QFile *const f = new QFile(input);
153
154             if(f->open(QIODevice::WriteOnly))
155                 return QVariant::fromValue(static_cast<QIODevice *>(f));
156             else
157             {
158                 message(QXmlPatternistCLI::tr("Failed to open file %1 for writing: %2").arg(f->fileName(), f->errorString()));
159                 return QVariant();
160             }
161         }
162         else if(arg.name() == QLatin1String("initial-template"))
163         {
164             const QXmlName name(QXmlName::fromClarkName(input, m_namePool));
165             if(name.isNull())
166             {
167                 message(QXmlPatternistCLI::tr("%1 is an invalid Clark Name").arg(input));
168                 return QVariant();
169             }
170             else
171                 return QVariant::fromValue(name);
172         }
173         else
174             return QApplicationArgumentParser::convertToValue(arg, input);
175     }
176
177     virtual QString typeToName(const QApplicationArgument &argument) const
178     {
179         if(argument.name() == QLatin1String("param"))
180             return QLatin1String("name=value");
181         else if(argument.name() == QLatin1String("output"))
182             return QLatin1String("local file");
183         else
184             return QApplicationArgumentParser::typeToName(argument);
185     }
186
187     virtual QVariant defaultValue(const QApplicationArgument &argument) const
188     {
189         if(argument.name() == QLatin1String("output"))
190         {
191             QFile *const out = new QFile();
192
193 #ifdef Q_OS_WIN
194             /* If we don't open stdout in "binary" mode on Windows, it will translate
195              * 0xA into 0xD 0xA. */
196             _setmode(_fileno(stdout), _O_BINARY);
197             m_stdout = _wfdopen(_fileno(stdout), L"wb");
198             out->open(m_stdout, QIODevice::WriteOnly);
199 #else
200             out->open(stdout, QIODevice::WriteOnly);
201 #endif
202
203             return QVariant::fromValue(static_cast<QIODevice *>(out));
204         }
205         else
206             return QApplicationArgumentParser::defaultValue(argument);
207     }
208
209 private:
210     QXmlNamePool    m_namePool;
211 #ifdef Q_OS_WIN
212     mutable FILE *  m_stdout;
213 #endif
214 };
215
216 static inline QUrl finalizeURI(const QApplicationArgumentParser &parser,
217                                const QApplicationArgument &isURI,
218                                const QApplicationArgument &arg)
219 {
220     QUrl userURI;
221     {
222         const QString stringURI(parser.value(arg).toString());
223
224         if(parser.has(isURI))
225             userURI = QUrl::fromEncoded(stringURI.toLatin1());
226         else
227             userURI = QUrl::fromLocalFile(stringURI);
228     }
229
230     return QUrl::fromLocalFile(QDir::current().absolutePath() + QLatin1Char('/')).resolved(userURI);
231 }
232
233 int main(int argc, char **argv)
234 {
235     enum ExitCode
236     {
237         /**
238          * We start from 2, because QApplicationArgumentParser
239          * uses 1.
240          */
241         QueryFailure = 2,
242         StdOutFailure
243     };
244
245     const QCoreApplication app(argc, argv);
246     QCoreApplication::setApplicationName(QLatin1String("xmlpatterns"));
247
248     QXmlNamePool namePool;
249     PatternistApplicationParser parser(argc, argv, namePool);
250     parser.setApplicationDescription(QLatin1String("A tool for running XQuery queries."));
251     parser.setApplicationVersion(QLatin1String("0.1"));
252
253     QApplicationArgument param(QLatin1String("param"),
254                                QXmlPatternistCLI::tr("Binds an external variable. The value is directly available using the variable reference: $name."),
255                                qMetaTypeId<Parameter>());
256     param.setMaximumOccurrence(-1);
257     parser.addArgument(param);
258
259     const QApplicationArgument noformat(QLatin1String("no-format"),
260                                         QXmlPatternistCLI::tr("By default output is formatted for readability. When specified, strict serialization is performed."));
261     parser.addArgument(noformat);
262
263     const QApplicationArgument isURI(QLatin1String("is-uri"),
264                                      QXmlPatternistCLI::tr("If specified, all filenames on the command line are interpreted as URIs instead of a local filenames."));
265     parser.addArgument(isURI);
266
267     const QApplicationArgument initialTemplateName(QLatin1String("initial-template"),
268                                                    QXmlPatternistCLI::tr("The name of the initial template to call as a Clark Name."),
269                                                    QVariant::String);
270     parser.addArgument(initialTemplateName);
271
272     /* The temporary object is required to compile with g++ 3.3. */
273     QApplicationArgument queryURI = QApplicationArgument(QLatin1String("query/stylesheet"),
274                                                          QXmlPatternistCLI::tr("A local filename pointing to the query to run. If the name ends with .xsl it's assumed "
275                                                                                "to be an XSL-T stylesheet. If it ends with .xq, it's assumed to be an XQuery query. (In "
276                                                                                "other cases it's also assumed to be an XQuery query, but that interpretation may "
277                                                                                "change in a future release of Qt.)"),
278                                                          QVariant::String);
279     queryURI.setMinimumOccurrence(1);
280     queryURI.setNameless(true);
281     parser.addArgument(queryURI);
282
283     QApplicationArgument focus = QApplicationArgument(QLatin1String("focus"),
284                                                       QXmlPatternistCLI::tr("The document to use as focus. Mandatory "
285                                                                             "in case a stylesheet is used. This option is "
286                                                                             "also affected by the is-uris option."),
287                                                       QVariant::String);
288     focus.setMinimumOccurrence(0);
289     focus.setNameless(true);
290     parser.addArgument(focus);
291
292     QApplicationArgument output(QLatin1String("output"),
293                                 QXmlPatternistCLI::tr("A local file to which the output should be written. "
294                                                       "The file is overwritten, or if not exist, created. "
295                                                       "If absent, stdout is used."),
296                                 qMetaTypeId<QIODevice *>());
297     parser.addArgument(output);
298
299     if(!parser.parse())
300         return parser.exitCode();
301
302     /* Get the query URI. */
303     const QUrl effectiveURI(finalizeURI(parser, isURI, queryURI));
304
305     QXmlQuery::QueryLanguage lang;
306
307     if(effectiveURI.toString().endsWith(QLatin1String(".xsl")))
308          lang = QXmlQuery::XSLT20;
309     else
310          lang = QXmlQuery::XQuery10;
311
312     if(lang == QXmlQuery::XQuery10 && parser.has(initialTemplateName))
313     {
314         parser.message(QXmlPatternistCLI::tr("An initial template name cannot be specified when running an XQuery."));
315         return QApplicationArgumentParser::ParseError;
316     }
317
318     QXmlQuery query(lang, namePool);
319
320     query.setInitialTemplateName(qvariant_cast<QXmlName>(parser.value(initialTemplateName)));
321
322     /* Bind external variables. */
323     {
324         const QVariantList parameters(parser.values(param));
325         const int len = parameters.count();
326
327         /* For tracking duplicates. */
328         QSet<QString> usedParameters;
329
330         for(int i = 0; i < len; ++i)
331         {
332             const Parameter p(qvariant_cast<Parameter>(parameters.at(i)));
333
334             if(usedParameters.contains(p.first))
335             {
336                 parser.message(QXmlPatternistCLI::tr("Each parameter must be unique, %1 is specified at least twice.").arg(p.first));
337                 return QApplicationArgumentParser::ParseError;
338             }
339             else
340             {
341                 usedParameters.insert(p.first);
342                 query.bindVariable(p.first, QXmlItem(p.second));
343             }
344         }
345     }
346
347     if(parser.has(focus))
348     {
349         if(!query.setFocus(finalizeURI(parser, isURI, focus)))
350             return QueryFailure;
351     }
352     else if(lang == QXmlQuery::XSLT20 && !parser.has(initialTemplateName))
353     {
354         parser.message(QXmlPatternistCLI::tr("When a stylesheet is used, a "
355                                              "document must be specified as a focus, or an "
356                                              "initial template name must be specified, or both."));
357         return QApplicationArgumentParser::ParseError;
358     }
359
360     query.setQuery(effectiveURI);
361
362     const QPatternist::AutoPtr<QIODevice> outDevice(qvariant_cast<QIODevice *>(parser.value(output)));
363     Q_ASSERT(outDevice);
364     Q_ASSERT(outDevice->isWritable());
365
366     if(query.isValid())
367     {
368         typedef QPatternist::AutoPtr<QAbstractXmlReceiver> RecPtr;
369         RecPtr receiver;
370
371         if(parser.has(noformat))
372             receiver = RecPtr(new QXmlSerializer(query, outDevice.data()));
373         else
374             receiver = RecPtr(new QXmlFormatter(query, outDevice.data()));
375
376         const bool success = query.evaluateTo(receiver.data());
377
378         if(success)
379             return parser.exitCode();
380         else
381             return QueryFailure;
382     }
383     else
384         return QueryFailure;
385 }
386