hydrogen 1.2.3
Xml.cpp
Go to the documentation of this file.
1/*
2 * Hydrogen
3 * Copyright(c) 2002-2008 by Alex >Comix< Cominu [comix@users.sourceforge.net]
4 * Copyright(c) 2008-2024 The hydrogen development team [hydrogen-devel@lists.sourceforge.net]
5 *
6 * http://www.hydrogen-music.org
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY, without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program. If not, see https://www.gnu.org/licenses
20 *
21 */
22
23#include <core/Helpers/Xml.h>
24#include <core/Helpers/Legacy.h>
25
26#include <QtCore/QFile>
27#include <QtCore/QLocale>
28#include <QtCore/QString>
29#include <QtCore/QTextStream>
30#include <QtXmlPatterns/QXmlSchema>
31#include <QtXmlPatterns/QXmlSchemaValidator>
32#include <QAbstractMessageHandler>
33
34#define XMLNS_BASE "http://www.hydrogen-music.org/"
35#define XMLNS_XSI "http://www.w3.org/2001/XMLSchema-instance"
36
37namespace H2Core
38{
39
40class SilentMessageHandler : public QAbstractMessageHandler
41{
42public:
44 : QAbstractMessageHandler(nullptr)
45 {
46 }
47
48protected:
49 virtual void handleMessage(QtMsgType type, const QString &description,
50 const QUrl &identifier, const QSourceLocation &sourceLocation)
51 {
52 Q_UNUSED(type);
53 Q_UNUSED(identifier);
54 }
55
56};
57
58
59
61XMLNode::XMLNode( QDomNode node ) : QDomNode( node ) { }
62
63XMLNode XMLNode::createNode( const QString& name )
64{
65 XMLNode node = this->ownerDocument().createElement( name );
66 appendChild( node );
67 return node;
68}
69
70QString XMLNode::read_child_node( const QString& node, bool inexistent_ok, bool empty_ok, bool bSilent )
71{
72 if( isNull() ) {
73 ERRORLOG( QString( "try to read %1 XML node from an empty parent %2." )
74 .arg( node ).arg( nodeName() ) );
75 return nullptr;
76 }
77 QDomElement el = firstChildElement( node );
78 if( el.isNull() ) {
79 if ( !inexistent_ok && ! bSilent ) {
80 WARNINGLOG( QString( "XML node %1->%2 should exists." )
81 .arg( nodeName() ).arg( node ) );
82 }
83 return nullptr;
84 }
85 if( el.text().isEmpty() ) {
86 if( !empty_ok && ! bSilent ) {
87 WARNINGLOG( QString( "XML node %1->%2 should not be empty." )
88 .arg( nodeName() ).arg( node ) );
89 }
90 return nullptr;
91 }
92 return el.text();
93}
94
95QString XMLNode::read_string( const QString& node, const QString& sDefaultValue, bool inexistent_ok, bool empty_ok, bool bSilent )
96{
97 QString sText = read_child_node( node, inexistent_ok, empty_ok, bSilent );
98 if ( sText.isNull() && ! sDefaultValue.isEmpty() ) {
99 if ( ! bSilent ) {
100 WARNINGLOG( QString( "Using default value %1 for %2" )
101 .arg( sDefaultValue ).arg( node ) );
102 }
103 return sDefaultValue;
104 }
105 return sText;
106}
107QColor XMLNode::read_color( const QString& node, const QColor& default_value, bool inexistent_ok, bool empty_ok, bool bSilent )
108{
109 QString text = read_child_node( node, inexistent_ok, empty_ok, bSilent );
110
111 if ( ! text.isEmpty() ) {
112 QStringList textList = text.split( QLatin1Char( ',' ) );
113 if ( textList.size() != 3 ) {
114 if ( ! bSilent ) {
115 WARNINGLOG( QString( "Invalid color format [%1] for node [%2]" )
116 .arg( default_value.name() ).arg( node ) );
117 }
118 return default_value;
119 }
120
121 QColor color( textList[ 0 ].toInt(), textList[ 1 ].toInt(), textList[ 2 ].toInt() );
122 if ( ! color.isValid() ) {
123 if ( ! bSilent ) {
124 WARNINGLOG( QString( "Invalid color values [%1] for node [%2]" )
125 .arg( default_value.name() ).arg( node ) );
126 }
127 return default_value;
128 }
129 return color;
130 }
131
132 if ( ! bSilent ) {
133 WARNINGLOG( QString( "Using default value [%1] for node [%2]" )
134 .arg( default_value.name() ).arg( node ) );
135 }
136 return default_value;
137}
138
139float XMLNode::read_float( const QString& node, float default_value, bool inexistent_ok, bool empty_ok, bool bSilent )
140{
141 QString ret = read_child_node( node, inexistent_ok, empty_ok, bSilent );
142 if( ret.isNull() ) {
143 if ( ! bSilent ) {
144 WARNINGLOG( QString( "Using default value %1 for %2" )
145 .arg( default_value ).arg( node ) );
146 }
147 return default_value;
148 }
149 QLocale c_locale = QLocale::c();
150 return c_locale.toFloat( ret );
151}
152
153float XMLNode::read_float( const QString& node, float default_value, bool *pFound, bool inexistent_ok, bool empty_ok, bool bSilent )
154{
155 QString ret = read_child_node( node, inexistent_ok, empty_ok, bSilent );
156 if( ret.isNull() ) {
157 if ( ! bSilent ) {
158 WARNINGLOG( QString( "Using default value %1 for %2" )
159 .arg( default_value ).arg( node ) );
160 }
161 *pFound = false;
162 return default_value;
163 } else {
164 *pFound = true;
165 QLocale c_locale = QLocale::c();
166 return c_locale.toFloat( ret );
167 }
168}
169
170int XMLNode::read_int( const QString& node, int default_value, bool inexistent_ok, bool empty_ok, bool bSilent )
171{
172 QString ret = read_child_node( node, inexistent_ok, empty_ok, bSilent );
173 if( ret.isNull() ) {
174 if ( ! bSilent ) {
175 WARNINGLOG( QString( "Using default value %1 for %2" )
176 .arg( default_value ).arg( node ) );
177 }
178 return default_value;
179 }
180 QLocale c_locale = QLocale::c();
181 return c_locale.toInt( ret );
182}
183
184bool XMLNode::read_bool( const QString& node, bool default_value, bool inexistent_ok, bool empty_ok, bool bSilent )
185{
186 QString ret = read_child_node( node, inexistent_ok, empty_ok, bSilent );
187 if( ret.isNull() ) {
188 if ( ! bSilent ) {
189 WARNINGLOG( QString( "Using default value %1 for %2" )
190 .arg( default_value ).arg( node ) );
191 }
192 return default_value;
193 }
194 if( ret=="true" ) {
195 return true;
196 } else {
197 return false;
198 }
199}
200
201bool XMLNode::read_bool( const QString& node, bool default_value, bool* pFound, bool inexistent_ok, bool empty_ok, bool bSilent )
202{
203 QString ret = read_child_node( node, inexistent_ok, empty_ok, bSilent );
204 if( ret.isNull() ) {
205 *pFound = false;
206 if ( ! bSilent ) {
207 WARNINGLOG( QString( "Using default value %1 for %2" )
208 .arg( default_value ).arg( node ) );
209 }
210 return default_value;
211 }
212
213 *pFound = true;
214 if( ret=="true" ) {
215 return true;
216 } else {
217 return false;
218 }
219}
220
221QString XMLNode::read_text( bool empty_ok, bool bSilent )
222{
223 QString text = toElement().text();
224 if ( !empty_ok && text.isEmpty() && ! bSilent ) {
225 WARNINGLOG( QString( "XML node %1 should not be empty." ).arg( nodeName() ) );
226 }
227 return text;
228}
229
230QString XMLNode::read_attribute( const QString& attribute, const QString& default_value, bool inexistent_ok, bool empty_ok, bool bSilent )
231{
232 QDomElement el = toElement();
233 if ( !inexistent_ok && !el.hasAttribute( attribute ) ) {
234 if ( ! bSilent ) {
235 WARNINGLOG( QString( "XML node %1 attribute %2 should exists." )
236 .arg( nodeName() ).arg( attribute ) );
237 }
238 return default_value;
239 }
240 QString attr = el.attribute( attribute );
241 if ( attr.isEmpty() ) {
242 if( !empty_ok && ! bSilent ) {
243 WARNINGLOG( QString( "XML node %1 attribute %2 should not be empty." )
244 .arg( nodeName() ).arg( attribute ) );
245 }
246
247 if ( ! bSilent ) {
248 WARNINGLOG( QString( "Using default value %1 for attribute %2" )
249 .arg( default_value ).arg( attribute ) );
250 }
251 return default_value;
252 }
253 return attr;
254}
255
256void XMLNode::write_attribute( const QString& attribute, const QString& value )
257{
258 toElement().setAttribute( attribute, value );
259}
260
261void XMLNode::write_child_node( const QString& node, const QString& text )
262{
263 QDomDocument doc = this->ownerDocument();
264 QDomElement el = doc.createElement( node );
265 QDomText txt = doc.createTextNode( text );
266 el.appendChild( txt );
267 this->appendChild( el );
268}
269void XMLNode::write_string( const QString& node, const QString& value )
270{
271 write_child_node( node, value );
272}
273void XMLNode::write_color( const QString& node, const QColor& color )
274{
275 write_child_node( node, QString( "%1,%2,%3" )
276 .arg( color.red() )
277 .arg( color.green() )
278 .arg( color.blue() ) );
279}
280void XMLNode::write_float( const QString& node, const float value )
281{
282 write_child_node( node, QString::number( value ) );
283}
284void XMLNode::write_int( const QString& node, const int value )
285{
286 write_child_node( node, QString::number( value ) );
287}
288void XMLNode::write_bool( const QString& name, const bool value )
289{
290 write_child_node( name, QString( ( value ? "true" : "false" ) ) );
291}
292
293
295
296bool XMLDoc::read( const QString& sFilePath, const QString& sSchemaPath, bool bSilent )
297{
298
299 QFile file( sFilePath );
300 if ( !file.open( QIODevice::ReadOnly ) ) {
301 ERRORLOG( QString( "Unable to open [%1] for reading" )
302 .arg( sFilePath ) );
303 return false;
304 }
305
306 SilentMessageHandler handler;
307 QXmlSchema schema;
308 schema.setMessageHandler( &handler );
309
310 bool bSchemaUsable = false;
311
312 if ( ! sSchemaPath.isEmpty() ) {
313 QFile file( sSchemaPath );
314 if ( !file.open( QIODevice::ReadOnly ) ) {
315 ERRORLOG( QString( "Unable to open XML schema [%1] for reading." )
316 .arg( sSchemaPath ) );
317 } else {
318 schema.load( &file, QUrl::fromLocalFile( file.fileName() ) );
319 file.close();
320 if ( schema.isValid() ) {
321 bSchemaUsable = true;
322 } else {
323 ERRORLOG( QString( "XML schema [%1] is not valid. File [%2] will not be validated" )
324 .arg( sSchemaPath ).arg( sFilePath ) );
325 }
326 }
327 }
328
329 if ( bSchemaUsable ) {
330 QXmlSchemaValidator validator( schema );
331 if ( !validator.validate( &file, QUrl::fromLocalFile( file.fileName() ) ) ) {
332 if ( ! bSilent ) {
333 WARNINGLOG( QString( "XML document [%1] is not valid with respect to schema [%2], loading may fail" )
334 .arg( sFilePath ).arg( sSchemaPath ) );
335 }
336 file.close();
337 return false;
338 }
339 else if ( ! bSilent ) {
340 INFOLOG( QString( "XML document [%1] is valid with respect to schema [%2]" )
341 .arg( sFilePath ).arg( sSchemaPath ) );
342 }
343 file.seek( 0 );
344 }
345
346 if ( Legacy::checkTinyXMLCompatMode( &file ) ) {
347 // Document was created using TinyXML and not using QtXML. We
348 // need to convert it first.
349 if ( ! setContent( Legacy::convertFromTinyXML( &file ) ) ) {
350 ERRORLOG( QString( "Unable to read conversion result document [%1]" )
351 .arg( sFilePath ) );
352 file.close();
353 return false;
354 }
355 }
356 else {
357 // File was written using current format.
358 if ( ! setContent( &file ) ) {
359 ERRORLOG( QString( "Unable to read XML document [%1]" )
360 .arg( sFilePath ) );
361 file.close();
362 return false;
363 }
364 }
365 file.close();
366
367 return true;
368}
369
370bool XMLDoc::write( const QString& filepath )
371{
372 QFile file( filepath );
373 if ( !file.open( QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate ) ) {
374 ERRORLOG( QString( "Unable to open %1 for writing" ).arg( filepath ) );
375 return false;
376 }
377 QTextStream out( &file );
378 out.setCodec( "UTF-8" );
379 out << toString().toUtf8();
380 out.flush();
381
382 bool rv = true;
383 if ( !toString().isEmpty() && file.size() == 0 ) {
384 rv = false;
385 }
386
387 file.close();
388 return rv;
389}
390
391XMLNode XMLDoc::set_root( const QString& node_name, const QString& xmlns )
392{
393 QDomProcessingInstruction header = createProcessingInstruction( "xml", "version=\"1.0\" encoding=\"UTF-8\"" );
394 appendChild( header );
395 XMLNode root = createElement( node_name );
396 if ( !xmlns.isEmpty() ) {
397 QDomElement el = root.toElement();
398 el.setAttribute( "xmlns",XMLNS_BASE+xmlns );
399 el.setAttribute( "xmlns:xsi",XMLNS_XSI );
400 }
401 appendChild( root );
402 return root;
403}
404
405};
#define INFOLOG(x)
Definition Object.h:237
#define WARNINGLOG(x)
Definition Object.h:238
#define ERRORLOG(x)
Definition Object.h:239
#define XMLNS_XSI
Definition Xml.cpp:35
#define XMLNS_BASE
Definition Xml.cpp:34
static bool checkTinyXMLCompatMode(QFile *pFile, bool bSilent=false)
Definition Legacy.cpp:336
static QByteArray convertFromTinyXML(QFile *pFile, bool bSilent=false)
Definition Legacy.cpp:358
virtual void handleMessage(QtMsgType type, const QString &description, const QUrl &identifier, const QSourceLocation &sourceLocation)
Definition Xml.cpp:49
XMLNode set_root(const QString &node_name, const QString &xmlns=nullptr)
create the xml header and root node
Definition Xml.cpp:391
bool read(const QString &filepath, const QString &schemapath=nullptr, bool bSilent=false)
read the content of an xml file
Definition Xml.cpp:296
bool write(const QString &filepath)
write itself into a file
Definition Xml.cpp:370
XMLDoc()
basic constructor
Definition Xml.cpp:294
XMLNode is a subclass of QDomNode with read and write values methods.
Definition Xml.h:39
int read_int(const QString &node, int default_value, bool inexistent_ok=true, bool empty_ok=true, bool bSilent=false)
reads an integer stored into a child node
Definition Xml.cpp:170
void write_attribute(const QString &attribute, const QString &value)
write a string as an attribute of the node
Definition Xml.cpp:256
QString read_child_node(const QString &node, bool inexistent_ok, bool empty_ok, bool bSilent=false)
reads a string stored into a child node
Definition Xml.cpp:70
bool read_bool(const QString &node, bool default_value, bool inexistent_ok=true, bool empty_ok=true, bool bSilent=false)
reads a boolean stored into a child node
Definition Xml.cpp:184
QString read_attribute(const QString &attribute, const QString &default_value, bool inexistent_ok, bool empty_ok, bool bSilent=false)
reads an attribute from the node
Definition Xml.cpp:230
QColor read_color(const QString &node, const QColor &defaultValue=QColor(97, 167, 251), bool inexistent_ok=true, bool empty_ok=true, bool bSilent=false)
Definition Xml.cpp:107
void write_child_node(const QString &node, const QString &text)
write a string into a child node
Definition Xml.cpp:261
QString read_string(const QString &node, const QString &default_value, bool inexistent_ok=true, bool empty_ok=true, bool bSilent=false)
reads a string stored into a child node
Definition Xml.cpp:95
float read_float(const QString &node, float default_value, bool inexistent_ok=true, bool empty_ok=true, bool bSilent=false)
reads a float stored into a child node
Definition Xml.cpp:139
void write_color(const QString &node, const QColor &color)
Definition Xml.cpp:273
void write_float(const QString &node, const float value)
write a float into a child node
Definition Xml.cpp:280
XMLNode createNode(const QString &name)
create a new XMLNode that has to be appended into de XMLDoc
Definition Xml.cpp:63
XMLNode()
basic constructor
Definition Xml.cpp:60
void write_string(const QString &node, const QString &value)
write a string into a child node
Definition Xml.cpp:269
void write_bool(const QString &node, const bool value)
write a boolean into a child node
Definition Xml.cpp:288
QString read_text(bool empty_ok, bool bSilent=false)
reads the text (content) from the node
Definition Xml.cpp:221
void write_int(const QString &node, const int value)
write an integer into a child node
Definition Xml.cpp:284