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