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 QtGui module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
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 Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or (at your option) the GNU General
28 ** Public license version 3 or any later version approved by the KDE Free
29 ** Qt Foundation. The licenses are as published by the Free Software
30 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31 ** included in the packaging of this file. Please review the following
32 ** information to ensure the GNU General Public License requirements will
33 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34 ** https://www.gnu.org/licenses/gpl-3.0.html.
35 **
36 ** $QT_END_LICENSE$
37 **
38 ****************************************************************************/
39 #include "qtextdocumentwriter.h"
40 
41 #include <QtCore/qfile.h>
42 #include <QtCore/qbytearray.h>
43 #include <QtCore/qfileinfo.h>
44 #if QT_CONFIG(textcodec)
45 #include <QtCore/qtextcodec.h>
46 #endif
47 #include <QtCore/qtextstream.h>
48 #include <QtCore/qdebug.h>
49 #include "qtextdocument.h"
50 #include "qtextdocumentfragment.h"
51 
52 #include "qtextdocumentfragment_p.h"
53 #include "qtextodfwriter_p.h"
54 #if QT_CONFIG(textmarkdownwriter)
55 #include "qtextmarkdownwriter_p.h"
56 #endif
57 
58 #include <algorithm>
59 
60 QT_BEGIN_NAMESPACE
61 
62 class QTextDocumentWriterPrivate
63 {
64 public:
65     QTextDocumentWriterPrivate(QTextDocumentWriter* qq);
66 
67     // device
68     QByteArray format;
69     QIODevice *device;
70     bool deleteDevice;
71 #if QT_CONFIG(textcodec)
72     QTextCodec *codec;
73 #endif
74 
75     QTextDocumentWriter *q;
76 };
77 
78 /*!
79     \since 4.5
80     \class QTextDocumentWriter
81 
82     \brief The QTextDocumentWriter class provides a format-independent interface for writing a QTextDocument to files or other devices.
83     \inmodule QtGui
84 
85     \ingroup richtext-processing
86     \ingroup io
87 
88     To write a document, construct a QTextDocumentWriter object with either a
89     file name or a device object, and specify the document format to be
90     written. You can construct a writer and set the format using setFormat()
91     later.
92 
93     Call write() to write the document to the device. If the document is
94     successfully written, this function returns \c true. However, if an error
95     occurs when writing the document, it will return false.
96 
97     Call supportedDocumentFormats() for a list of formats that
98     QTextDocumentWriter can write.
99 
100     Since the capabilities of the supported output formats vary considerably,
101     the writer simply outputs the appropriate subset of objects for each format.
102     This typically includes the formatted text and images contained in a
103     document.
104 */
105 
106 /*!
107     \internal
108 */
QTextDocumentWriterPrivate(QTextDocumentWriter * qq)109 QTextDocumentWriterPrivate::QTextDocumentWriterPrivate(QTextDocumentWriter *qq)
110     : device(nullptr),
111     deleteDevice(false),
112 #if QT_CONFIG(textcodec)
113     codec(QTextCodec::codecForName("utf-8")),
114 #endif
115     q(qq)
116 {
117 }
118 
119 /*!
120     Constructs an empty QTextDocumentWriter object. Before writing, you must
121     call setFormat() to set a document format, then setDevice() or
122     setFileName().
123 */
QTextDocumentWriter()124 QTextDocumentWriter::QTextDocumentWriter()
125     : d(new QTextDocumentWriterPrivate(this))
126 {
127 }
128 
129 /*!
130     Constructs a QTextDocumentWriter object to write to the given \a device
131     in the document format specified by \a format.
132 */
QTextDocumentWriter(QIODevice * device,const QByteArray & format)133 QTextDocumentWriter::QTextDocumentWriter(QIODevice *device, const QByteArray &format)
134     : d(new QTextDocumentWriterPrivate(this))
135 {
136     d->device = device;
137     d->format = format;
138 }
139 
140 /*!
141     Constructs an QTextDocumentWriter object that will write to a file with
142     the name \a fileName, using the document format specified by \a format.
143     If \a format is not provided, QTextDocumentWriter will detect the document
144     format by inspecting the extension of \a fileName.
145 */
QTextDocumentWriter(const QString & fileName,const QByteArray & format)146 QTextDocumentWriter::QTextDocumentWriter(const QString &fileName, const QByteArray &format)
147     : QTextDocumentWriter(new QFile(fileName), format)
148 {
149     d->deleteDevice = true;
150 }
151 
152 /*!
153     Destroys the QTextDocumentWriter object.
154 */
~QTextDocumentWriter()155 QTextDocumentWriter::~QTextDocumentWriter()
156 {
157     if (d->deleteDevice)
158         delete d->device;
159     delete d;
160 }
161 
162 /*!
163     Sets the format used to write documents to the \a format specified.
164     \a format is a case insensitive text string. For example:
165 
166     \snippet code/src_gui_text_qtextdocumentwriter.cpp 0
167 
168     You can call supportedDocumentFormats() for the full list of formats
169     QTextDocumentWriter supports.
170 
171     \sa format()
172 */
setFormat(const QByteArray & format)173 void QTextDocumentWriter::setFormat (const QByteArray &format)
174 {
175     d->format = format;
176 }
177 
178 /*!
179     Returns the format used for writing documents.
180 
181     \sa setFormat()
182 */
format() const183 QByteArray QTextDocumentWriter::format () const
184 {
185     return d->format;
186 }
187 
188 /*!
189     Sets the writer's device to the \a device specified. If a device has
190     already been set, the old device is removed but otherwise left
191     unchanged.
192 
193     If the device is not already open, QTextDocumentWriter will attempt to
194     open the device in \l QIODevice::WriteOnly mode by calling open().
195 
196     \note This will not work for certain devices, such as QProcess,
197     QTcpSocket and QUdpSocket, where some configuration is required before
198     the device can be opened.
199 
200     \sa device(), setFileName()
201 */
setDevice(QIODevice * device)202 void QTextDocumentWriter::setDevice (QIODevice *device)
203 {
204     if (d->device && d->deleteDevice)
205         delete d->device;
206 
207     d->device = device;
208     d->deleteDevice = false;
209 }
210 
211 /*!
212     Returns the device currently assigned, or \nullptr if no device
213     has been assigned.
214 */
device() const215 QIODevice *QTextDocumentWriter::device () const
216 {
217     return d->device;
218 }
219 
220 /*!
221     Sets the name of the file to be written to \a fileName. Internally,
222     QTextDocumentWriter will create a QFile and open it in \l
223     QIODevice::WriteOnly mode, and use this file when writing the document.
224 
225     \sa fileName(), setDevice()
226 */
setFileName(const QString & fileName)227 void QTextDocumentWriter::setFileName (const QString &fileName)
228 {
229     setDevice(new QFile(fileName));
230     d->deleteDevice = true;
231 }
232 
233 /*!
234     If the currently assigned device is a QFile, or if setFileName()
235     has been called, this function returns the name of the file
236     to be written to. In all other cases, it returns an empty string.
237 
238     \sa setFileName(), setDevice()
239 */
fileName() const240 QString QTextDocumentWriter::fileName () const
241 {
242     QFile *file = qobject_cast<QFile *>(d->device);
243     return file ? file->fileName() : QString();
244 }
245 
246 /*!
247     Writes the given \a document to the assigned device or file and
248     returns \c true if successful; otherwise returns \c false.
249 */
write(const QTextDocument * document)250 bool QTextDocumentWriter::write(const QTextDocument *document)
251 {
252     QByteArray suffix;
253 
254     if (d->device && d->format.isEmpty()) {
255         // if there's no format, see if device is a file, and if so, find
256         // the file suffix
257         if (QFile *file = qobject_cast<QFile *>(d->device))
258             suffix = QFileInfo(file->fileName()).suffix().toLower().toLatin1();
259     }
260 
261     QByteArray format = !d->format.isEmpty() ? d->format.toLower() : suffix;
262 
263 #ifndef QT_NO_TEXTODFWRITER
264     if (format == "odf" || format == "opendocumentformat" || format == "odt") {
265         QTextOdfWriter writer(*document, d->device);
266 #if QT_CONFIG(textcodec)
267         writer.setCodec(d->codec);
268 #endif
269         return writer.writeAll();
270     }
271 #endif // QT_NO_TEXTODFWRITER
272 
273 #if QT_CONFIG(textmarkdownwriter)
274     if (format == "md" || format == "mkd" || format == "markdown") {
275         if (!d->device->isWritable() && !d->device->open(QIODevice::WriteOnly)) {
276             qWarning("QTextDocumentWriter::write: the device can not be opened for writing");
277             return false;
278         }
279         QTextStream s(d->device);
280         QTextMarkdownWriter writer(s, QTextDocument::MarkdownDialectGitHub);
281         return writer.writeAll(document);
282     }
283 #endif // textmarkdownwriter
284 
285 #ifndef QT_NO_TEXTHTMLPARSER
286     if (format == "html" || format == "htm") {
287         if (!d->device->isWritable() && ! d->device->open(QIODevice::WriteOnly)) {
288             qWarning("QTextDocumentWriter::write: the device cannot be opened for writing");
289             return false;
290         }
291         QTextStream ts(d->device);
292 #if QT_CONFIG(textcodec)
293         ts.setCodec(d->codec);
294         ts << document->toHtml(d->codec->name());
295 #endif
296         d->device->close();
297         return true;
298     }
299 #endif
300     if (format == "txt" || format == "plaintext") {
301         if (!d->device->isWritable() && ! d->device->open(QIODevice::WriteOnly)) {
302             qWarning("QTextDocumentWriter::write: the device cannot be opened for writing");
303             return false;
304         }
305         QTextStream ts(d->device);
306 #if QT_CONFIG(textcodec)
307         ts.setCodec(d->codec);
308 #endif
309         ts << document->toPlainText();
310         d->device->close();
311         return true;
312     }
313 
314     return false;
315 }
316 
317 /*!
318     Writes the document fragment specified by \a fragment to the assigned device
319     or file and returns \c true if successful; otherwise returns \c false.
320 */
write(const QTextDocumentFragment & fragment)321 bool QTextDocumentWriter::write(const QTextDocumentFragment &fragment)
322 {
323     if (fragment.d == nullptr)
324         return false; // invalid fragment.
325     QTextDocument *doc = fragment.d->doc;
326     if (doc)
327         return write(doc);
328     return false;
329 }
330 
331 /*!
332     Sets the codec for this stream to \a codec. The codec is used for
333     encoding any data that is written. By default, QTextDocumentWriter
334     uses UTF-8.
335 */
336 
337 #if QT_CONFIG(textcodec)
setCodec(QTextCodec * codec)338 void QTextDocumentWriter::setCodec(QTextCodec *codec)
339 {
340     if (codec == nullptr)
341         codec = QTextCodec::codecForName("UTF-8");
342     Q_ASSERT(codec);
343     d->codec = codec;
344 }
345 #endif
346 
347 /*!
348     Returns the codec that is currently assigned to the writer.
349 */
350 #if QT_CONFIG(textcodec)
codec() const351 QTextCodec *QTextDocumentWriter::codec() const
352 {
353     return d->codec;
354 }
355 #endif
356 
357 /*!
358     Returns the list of document formats supported by QTextDocumentWriter.
359 
360     By default, Qt can write the following formats:
361 
362     \table
363     \header \li Format    \li Description
364     \row    \li plaintext \li Plain text
365     \row    \li HTML      \li HyperText Markup Language
366     \row    \li markdown  \li Markdown (CommonMark or GitHub dialects)
367     \row    \li ODF       \li OpenDocument Format
368     \endtable
369 
370     \sa setFormat()
371 */
supportedDocumentFormats()372 QList<QByteArray> QTextDocumentWriter::supportedDocumentFormats()
373 {
374     QList<QByteArray> answer;
375     answer << "plaintext";
376 
377 #ifndef QT_NO_TEXTHTMLPARSER
378     answer << "HTML";
379 #endif
380 #ifndef QT_NO_TEXTODFWRITER
381     answer << "ODF";
382 #endif // QT_NO_TEXTODFWRITER
383 #if QT_CONFIG(textmarkdownwriter)
384     answer << "markdown";
385 #endif
386 
387     std::sort(answer.begin(), answer.end());
388     return answer;
389 }
390 
391 QT_END_NAMESPACE
392