1 /***************************************************************************
2                           qgsproject.cpp -  description
3                              -------------------
4   begin                : February 24, 2005
5   copyright            : (C) 2005 by Mark Coletti
6   email                : mcoletti at gmail.com
7 ***************************************************************************/
8 
9 /***************************************************************************
10  *                                                                         *
11  *   This program is free software; you can redistribute it and/or modify  *
12  *   it under the terms of the GNU General Public License as published by  *
13  *   the Free Software Foundation; either version 2 of the License, or     *
14  *   (at your option) any later version.                                   *
15  *                                                                         *
16  ***************************************************************************/
17 
18 #include "qgsprojectproperty.h"
19 #include "qgslogger.h"
20 #include "qgis.h"
21 #include "qgsmessagelog.h"
22 
23 #include <QDomDocument>
24 #include <QStringList>
25 
QgsProjectProperty()26 QgsProjectProperty::QgsProjectProperty() //NOLINT
27 {
28 }
29 
dump(int tabs) const30 void QgsProjectPropertyValue::dump( int tabs ) const
31 {
32   Q_UNUSED( tabs )
33 #ifdef QGISDEBUG
34 
35   QString tabString;
36   tabString.fill( '\t', tabs );
37 
38   if ( QVariant::StringList == mValue.type() )
39   {
40     const QStringList sl = mValue.toStringList();
41 
42     for ( const auto &string : sl )
43     {
44       QgsDebugMsgLevel( QStringLiteral( "%1[%2] " ).arg( tabString, string ), 4 );
45     }
46   }
47   else
48   {
49     QgsDebugMsgLevel( QStringLiteral( "%1%2" ).arg( tabString, mValue.toString() ), 4 );
50   }
51 #endif
52 }
53 
readXml(const QDomNode & keyNode)54 bool QgsProjectPropertyValue::readXml( const QDomNode &keyNode )
55 {
56   // this *should* be a Dom element node
57   QDomElement subkeyElement = keyNode.toElement();
58 
59   // get the type so that we can properly parse the key value
60   QString typeString = subkeyElement.attribute( QStringLiteral( "type" ) );
61 
62   if ( typeString.isNull() )
63   {
64     QgsDebugMsg( QStringLiteral( "null ``type'' attribute for %1" ).arg( keyNode.nodeName() ) );
65 
66     return false;
67   }
68 
69   // the values come in as strings; we need to restore them to their
70   // original values *and* types
71   mValue.clear();
72 
73   // get the type associated with the value first
74   QVariant::Type type = QVariant::nameToType( typeString.toLocal8Bit().constData() );
75 
76   // This huge switch is left-over from an earlier incarnation of
77   // QgsProject where there was a fine level of granularity for value
78   // types.  The current interface, borrowed from QgsSettings, supports a
79   // very small sub-set of these types.  However, I've left all the
80   // other types just in case the interface is expanded to include these
81   // other types.
82 
83   switch ( type )
84   {
85     case QVariant::Invalid:
86       QgsDebugMsg( QStringLiteral( "invalid value type %1 .. " ).arg( typeString ) );
87       return false;
88 
89     case QVariant::Map:
90       QgsDebugMsg( QStringLiteral( "no support for QVariant::Map" ) );
91       return false;
92 
93     case QVariant::List:
94       QgsDebugMsg( QStringLiteral( "no support for QVariant::List" ) );
95       return false;
96 
97     case QVariant::String:
98       mValue = subkeyElement.text();  // no translating necessary
99       break;
100 
101     case QVariant::StringList:
102     {
103       int i = 0;
104       QDomNodeList values = keyNode.childNodes();
105 
106       // all the QStringList values will be inside <value> elements
107       QStringList valueStringList;
108 
109       while ( i < values.count() )
110       {
111         if ( "value" == values.item( i ).nodeName() )
112         {
113           // <value>s have only one element, which contains actual string value
114           valueStringList.append( values.item( i ).firstChild().nodeValue() );
115         }
116         else
117         {
118           QgsDebugMsg( QStringLiteral( "non <value> element ``%1'' in string list" ).arg( values.item( i ).nodeName() ) );
119         }
120 
121         ++i;
122       }
123 
124       mValue = valueStringList;
125       break;
126     }
127 
128     case QVariant::Font:
129       QgsDebugMsg( QStringLiteral( "no support for QVariant::Font" ) );
130       return false;
131 
132     case QVariant::Pixmap:
133       QgsDebugMsg( QStringLiteral( "no support for QVariant::Pixmap" ) );
134       return false;
135 
136     case QVariant::Brush:
137       QgsDebugMsg( QStringLiteral( "no support for QVariant::Brush" ) );
138       return false;
139 
140     case QVariant::Rect:
141       QgsDebugMsg( QStringLiteral( "no support for QVariant::Rect" ) );
142       return false;
143 
144     case QVariant::Size:
145       QgsDebugMsg( QStringLiteral( "no support for QVariant::Size" ) );
146       return false;
147 
148     case QVariant::Color:
149       QgsDebugMsg( QStringLiteral( "no support for QVariant::Color" ) );
150       return false;
151 
152     case QVariant::Palette:
153       QgsDebugMsg( QStringLiteral( "no support for QVariant::Palette" ) );
154       return false;
155 
156     case QVariant::Point:
157       QgsDebugMsg( QStringLiteral( "no support for QVariant::Point" ) );
158       return false;
159 
160     case QVariant::Image:
161       QgsDebugMsg( QStringLiteral( "no support for QVariant::Image" ) );
162       return false;
163 
164     case QVariant::Int:
165       mValue = QVariant( subkeyElement.text() ).toInt();
166       break;
167 
168     case QVariant::UInt:
169       mValue = QVariant( subkeyElement.text() ).toUInt();
170       break;
171 
172     case QVariant::Bool:
173       mValue = QVariant( subkeyElement.text() ).toBool();
174       break;
175 
176     case QVariant::Double:
177       mValue = QVariant( subkeyElement.text() ).toDouble();
178       break;
179 
180     case QVariant::ByteArray:
181       mValue = QVariant( subkeyElement.text() ).toByteArray();
182       break;
183 
184     case QVariant::Polygon:
185       QgsDebugMsg( QStringLiteral( "no support for QVariant::Polygon" ) );
186       return false;
187 
188     case QVariant::Region:
189       QgsDebugMsg( QStringLiteral( "no support for QVariant::Region" ) );
190       return false;
191 
192     case QVariant::Bitmap:
193       QgsDebugMsg( QStringLiteral( "no support for QVariant::Bitmap" ) );
194       return false;
195 
196     case QVariant::Cursor:
197       QgsDebugMsg( QStringLiteral( "no support for QVariant::Cursor" ) );
198       return false;
199 
200     case QVariant::BitArray :
201       QgsDebugMsg( QStringLiteral( "no support for QVariant::BitArray" ) );
202       return false;
203 
204     case QVariant::KeySequence :
205       QgsDebugMsg( QStringLiteral( "no support for QVariant::KeySequence" ) );
206       return false;
207 
208     case QVariant::Pen :
209       QgsDebugMsg( QStringLiteral( "no support for QVariant::Pen" ) );
210       return false;
211 
212 #if 0 // Currently unsupported variant types
213     case QVariant::LongLong :
214       value_ = QVariant( subkeyElement.text() ).toLongLong();
215       break;
216 
217     case QVariant::ULongLong :
218       value_ = QVariant( subkeyElement.text() ).toULongLong();
219       break;
220 #endif
221     default :
222       QgsDebugMsg( QStringLiteral( "unsupported value type %1 .. not properly translated to QVariant" ).arg( typeString ) );
223   }
224 
225   return true;
226 
227 }
228 
229 
230 // keyElement is created by parent QgsProjectPropertyKey
writeXml(QString const & nodeName,QDomElement & keyElement,QDomDocument & document)231 bool QgsProjectPropertyValue::writeXml( QString const &nodeName,
232                                         QDomElement    &keyElement,
233                                         QDomDocument   &document )
234 {
235   QDomElement valueElement = document.createElement( nodeName );
236 
237   // remember the type so that we can rebuild it when the project is read in
238   valueElement.setAttribute( QStringLiteral( "type" ), mValue.typeName() );
239 
240 
241   // we handle string lists differently from other types in that we
242   // create a sequence of repeated elements to cover all the string list
243   // members; each value will be in a <value></value> tag.
244   // XXX Not the most elegant way to handle string lists?
245   if ( QVariant::StringList == mValue.type() )
246   {
247     QStringList sl = mValue.toStringList();
248 
249     for ( QStringList::iterator i = sl.begin();
250           i != sl.end();
251           ++i )
252     {
253       QDomElement stringListElement = document.createElement( QStringLiteral( "value" ) );
254       QDomText valueText = document.createTextNode( *i );
255       stringListElement.appendChild( valueText );
256 
257       valueElement.appendChild( stringListElement );
258     }
259   }
260   else                    // we just plop the value in as plain ole text
261   {
262     QDomText valueText = document.createTextNode( mValue.toString() );
263     valueElement.appendChild( valueText );
264   }
265 
266   keyElement.appendChild( valueElement );
267 
268   return true;
269 }
270 
271 
QgsProjectPropertyKey(const QString & name)272 QgsProjectPropertyKey::QgsProjectPropertyKey( const QString &name )
273   : mName( name )
274 {}
275 
~QgsProjectPropertyKey()276 QgsProjectPropertyKey::~QgsProjectPropertyKey()
277 {
278   clearKeys();
279 }
280 
value() const281 QVariant QgsProjectPropertyKey::value() const
282 {
283   QgsProjectProperty *foundQgsProperty = mProperties.value( name() );
284 
285   if ( !foundQgsProperty )
286   {
287     QgsDebugMsg( QStringLiteral( "key has null child" ) );
288     return QVariant();     // just return an QVariant::Invalid
289   }
290 
291   return foundQgsProperty->value();
292 }
293 
294 
dump(int tabs) const295 void QgsProjectPropertyKey::dump( int tabs ) const
296 {
297   QString tabString;
298 
299   tabString.fill( '\t', tabs );
300 
301   QgsDebugMsgLevel( QStringLiteral( "%1name: %2" ).arg( tabString, name() ), 4 );
302 
303   tabs++;
304   tabString.fill( '\t', tabs );
305 
306   if ( ! mProperties.isEmpty() )
307   {
308     QHashIterator < QString, QgsProjectProperty * > i( mProperties );
309     while ( i.hasNext() )
310     {
311       if ( i.next().value()->isValue() )
312       {
313         QgsProjectPropertyValue *propertyValue = static_cast<QgsProjectPropertyValue *>( i.value() );
314 
315         if ( QVariant::StringList == propertyValue->value().type() )
316         {
317           QgsDebugMsgLevel( QStringLiteral( "%1key: <%2>  value:" ).arg( tabString, i.key() ), 4 );
318           propertyValue->dump( tabs + 1 );
319         }
320         else
321         {
322           QgsDebugMsgLevel( QStringLiteral( "%1key: <%2>  value: %3" ).arg( tabString, i.key(), propertyValue->value().toString() ), 4 );
323         }
324       }
325       else
326       {
327         QgsDebugMsgLevel( QStringLiteral( "%1key: <%2>  subkey: <%3>" )
328                           .arg( tabString,
329                                 i.key(),
330                                 static_cast<QgsProjectPropertyKey *>( i.value() )->name() ), 4 );
331         i.value()->dump( tabs + 1 );
332       }
333 
334 #if 0
335       qDebug( "<%s>", name().toUtf8().constData() );
336       if ( i.value()->isValue() )
337       {
338         qDebug( "   <%s>", i.key().toUtf8().constData() );
339       }
340       i.value()->dump();
341       if ( i.value()->isValue() )
342       {
343         qDebug( "   </%s>", i.key().toUtf8().constData() );
344       }
345       qDebug( "</%s>", name().toUtf8().constData() );
346 #endif
347     }
348   }
349 
350 }
351 
352 
353 
readXml(const QDomNode & keyNode)354 bool QgsProjectPropertyKey::readXml( const QDomNode &keyNode )
355 {
356   int i = 0;
357   QDomNodeList subkeys = keyNode.childNodes();
358 
359   while ( i < subkeys.count() )
360   {
361     // if the current node is an element that has a "type" attribute,
362     // then we know it's a leaf node; i.e., a subkey _value_, and not
363     // a subkey
364     if ( subkeys.item( i ).hasAttributes() && // if we have attributes
365          subkeys.item( i ).isElement() && // and we're an element
366          subkeys.item( i ).toElement().hasAttribute( QStringLiteral( "type" ) ) ) // and we have a "type" attribute
367     {
368       // then we're a key value
369       delete mProperties.take( subkeys.item( i ).nodeName() );
370       mProperties.insert( subkeys.item( i ).nodeName(), new QgsProjectPropertyValue );
371 
372       QDomNode subkey = subkeys.item( i );
373 
374       if ( !mProperties[subkeys.item( i ).nodeName()]->readXml( subkey ) )
375       {
376         QgsDebugMsg( QStringLiteral( "unable to parse key value %1" ).arg( subkeys.item( i ).nodeName() ) );
377       }
378     }
379     else // otherwise it's a subkey, so just recurse on down the remaining keys
380     {
381       addKey( subkeys.item( i ).nodeName() );
382 
383       QDomNode subkey = subkeys.item( i );
384 
385       if ( !mProperties[subkeys.item( i ).nodeName()]->readXml( subkey ) )
386       {
387         QgsDebugMsg( QStringLiteral( "unable to parse subkey %1" ).arg( subkeys.item( i ).nodeName() ) );
388       }
389     }
390 
391     ++i;
392   }
393 
394   return true;
395 }
396 
397 
398 /*
399   Property keys will always create a Dom element for itself and then
400   recursively call writeXml for any constituent properties.
401 */
writeXml(QString const & nodeName,QDomElement & element,QDomDocument & document)402 bool QgsProjectPropertyKey::writeXml( QString const &nodeName, QDomElement &element, QDomDocument &document )
403 {
404   // If it's an _empty_ node (i.e., one with no properties) we need to emit
405   // an empty place holder; else create new Dom elements as necessary.
406 
407   QDomElement keyElement = document.createElement( nodeName ); // Dom element for this property key
408 
409   if ( ! mProperties.isEmpty() )
410   {
411     auto keys = mProperties.keys();
412     std::sort( keys.begin(), keys.end() );
413 
414     for ( const auto &key : std::as_const( keys ) )
415     {
416       if ( !mProperties.value( key )->writeXml( key, keyElement, document ) )
417         QgsMessageLog::logMessage( tr( "Failed to save project property %1" ).arg( key ) );
418     }
419   }
420 
421   element.appendChild( keyElement );
422 
423   return true;
424 }
425 
entryList(QStringList & entries) const426 void QgsProjectPropertyKey::entryList( QStringList &entries ) const
427 {
428   // now add any leaf nodes to the entries list
429   QHashIterator < QString, QgsProjectProperty * > i( mProperties );
430   while ( i.hasNext() )
431   {
432     // add any of the nodes that have just a single value
433     if ( i.next().value()->isLeaf() )
434     {
435       entries.append( i.key() );
436     }
437   }
438 }
439 
subkeyList(QStringList & entries) const440 void QgsProjectPropertyKey::subkeyList( QStringList &entries ) const
441 {
442   // now add any leaf nodes to the entries list
443   QHashIterator < QString, QgsProjectProperty * > i( mProperties );
444   while ( i.hasNext() )
445   {
446     // add any of the nodes that have just a single value
447     if ( !i.next().value()->isLeaf() )
448     {
449       entries.append( i.key() );
450     }
451   }
452 }
453 
454 
isLeaf() const455 bool QgsProjectPropertyKey::isLeaf() const
456 {
457   if ( 0 == count() )
458   {
459     return true;
460   }
461   else if ( 1 == count() )
462   {
463     QHashIterator < QString, QgsProjectProperty * > i( mProperties );
464 
465     if ( i.hasNext() && i.next().value()->isValue() )
466     {
467       return true;
468     }
469   }
470 
471   return false;
472 }
473 
setName(const QString & name)474 void QgsProjectPropertyKey::setName( const QString &name )
475 {
476   mName = name;
477 }
478