Add metadata validator

Adds a new abstract base class QgsMetadataValidator for validating
metadata against standard schemas.

Initially only QgsNativeMetadataValidator for validating against
the native QGIS metadata schema is implemented.

In future this could be extended with Dublin Core, ISO 19115
validators, etc...
This commit is contained in:
Nyall Dawson 2017-04-18 16:10:59 +10:00
parent cc3d67a207
commit 5600395e29
8 changed files with 463 additions and 18 deletions

View File

@ -277,6 +277,7 @@
%Include layertree/qgslayertreeutils.sip
%Include metadata/qgslayermetadata.sip
%Include metadata/qgslayermetadatavalidator.sip
%Include processing/qgsprocessingalgorithm.sip
%Include processing/qgsprocessingcontext.sip

View File

@ -253,7 +253,7 @@ class QgsLayerMetadata
void setLanguage( const QString &language );
%Docstring
Sets the human language associated with the resource. While a formal vocabulary is not imposed,
Sets the human ``language`` associated with the resource. While a formal vocabulary is not imposed,
ideally values should be taken from the ISO 639.2 or ISO 3166 specifications,
e.g. 'ENG' or 'SPA' (ISO 639.2) or 'EN-AU' (ISO 3166).
\see language()
@ -269,7 +269,7 @@ class QgsLayerMetadata
void setType( const QString &type );
%Docstring
Sets the type (nature) of the resource. While a formal vocabulary is not imposed, it is advised
Sets the ``type`` (nature) of the resource. While a formal vocabulary is not imposed, it is advised
to use the ISO 19115 MD_ScopeCode values. E.g. 'dataset' or 'series'.
\see type()
%End
@ -283,7 +283,7 @@ class QgsLayerMetadata
void setTitle( const QString &title );
%Docstring
Sets the human readable title (name) of the resource, typically displayed in search results.
Sets the human readable ``title`` (name) of the resource, typically displayed in search results.
\see title()
%End
@ -296,7 +296,7 @@ class QgsLayerMetadata
void setAbstract( const QString &abstract );
%Docstring
Sets a free-form abstract (description) of the resource.
Sets a free-form ``abstract`` (description) of the resource.
\see abstract()
%End
@ -310,7 +310,7 @@ class QgsLayerMetadata
void setFees( const QString &fees );
%Docstring
Sets the fees associated with using the resource.
Sets the ``fees`` associated with using the resource.
Use an empty string if no fees are set.
\see fees()
%End
@ -324,7 +324,7 @@ class QgsLayerMetadata
void setConstraints( const QgsLayerMetadata::ConstraintList &constraints );
%Docstring
Sets the list of constraints associated with using the resource.
Sets the list of ``constraints`` associated with using the resource.
\see constraints()
%End
@ -337,7 +337,7 @@ class QgsLayerMetadata
void setRights( const QStringList &rights );
%Docstring
Sets a list of rights (attribution or copyright strings) associated with the resource.
Sets a list of ``rights`` (attribution or copyright strings) associated with the resource.
\see rights()
%End
@ -350,7 +350,7 @@ class QgsLayerMetadata
void setEncoding( const QString &encoding );
%Docstring
Sets the character encoding of the data in the resource. Use an empty string if no encoding is set.
Sets the character ``encoding`` of the data in the resource. Use an empty string if no encoding is set.
\see encoding()
%End
@ -401,7 +401,7 @@ class QgsLayerMetadata
void setKeywords( const KeywordMap &keywords );
%Docstring
Sets the keywords map, which is a set of descriptive keywords associated with the resource.
Sets the ``keywords`` map, which is a set of descriptive keywords associated with the resource.
The map key is the vocabulary string and map value is a list of keywords for that vocabulary.
Calling this replaces any existing keyword vocabularies.
@ -415,7 +415,7 @@ class QgsLayerMetadata
void addKeywords( const QString &vocabulary, const QStringList &keywords );
%Docstring
Adds a list of descriptive keywords for a specified vocabulary. Any existing
Adds a list of descriptive ``keywords`` for a specified ``vocabulary``. Any existing
keywords for the same vocabulary will be replaced. Other vocabularies
will not be affected.
@ -438,7 +438,7 @@ class QgsLayerMetadata
QStringList keywords( const QString &vocabulary ) const;
%Docstring
Returns a list of keywords for the specified vocabulary.
Returns a list of keywords for the specified ``vocabulary``.
If the vocabulary is not contained in the metadata, an empty
list will be returned.
@ -458,7 +458,7 @@ class QgsLayerMetadata
void setContacts( const QgsLayerMetadata::ContactList &contacts );
%Docstring
Sets the list of contacts or entities associated with the resource. Any existing contacts
Sets the list of ``contacts`` or entities associated with the resource. Any existing contacts
will be replaced.
\see contacts()
\see addContact()
@ -466,7 +466,7 @@ class QgsLayerMetadata
void addContact( const QgsLayerMetadata::Contact &contact );
%Docstring
Adds an individual contact to the existing contacts.
Adds an individual ``contact`` to the existing contacts.
\see contacts()
\see setContacts()
%End
@ -488,20 +488,20 @@ class QgsLayerMetadata
void addLink( const QgsLayerMetadata::Link &link );
%Docstring
Adds an individual link to the existing links.
Adds an individual ``link`` to the existing links.
\see links()
\see setLinks()
%End
void saveToLayer( QgsMapLayer *layer ) const;
%Docstring
Saves the metadata to a layer's custom properties (see QgsMapLayer.setCustomProperty() ).
Saves the metadata to a ``layer``'s custom properties (see QgsMapLayer.setCustomProperty() ).
\see readFromLayer()
%End
void readFromLayer( const QgsMapLayer *layer );
%Docstring
Reads the metadata state from a layer's custom properties (see QgsMapLayer.customProperty() ).
Reads the metadata state from a ``layer``'s custom properties (see QgsMapLayer.customProperty() ).
\see saveToLayer()
%End

View File

@ -0,0 +1,96 @@
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/metadata/qgslayermetadatavalidator.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/
class QgsMetadataValidator
{
%Docstring
Abstract base class for metadata validators.
.. versionadded:: 3.0
%End
%TypeHeaderCode
#include "qgslayermetadatavalidator.h"
%End
public:
struct ValidationResult
{
ValidationResult( const QString &section, const QString &note, const QVariant &identifier = QVariant() );
%Docstring
Constructor for ValidationResult.
%End
QString section;
%Docstring
Metadata section which failed the validation
%End
QVariant identifier;
%Docstring
Optional identifier for the failed metadata item.
For instance, in list type metadata elements this
will be set to the list index of the failed metadata
item.
%End
QString note;
%Docstring
The reason behind the validation failure.
%End
};
virtual ~QgsMetadataValidator();
virtual bool validate( const QgsLayerMetadata &metadata, QList< QgsMetadataValidator::ValidationResult > &results /Out/ ) const = 0;
%Docstring
Validates a ``metadata`` object, and returns true if the
metadata is considered valid.
If validation fails, the ``results`` list will be filled with a list of
items describing why the validation failed and what needs to be rectified
to fix the metadata.
:rtype: bool
%End
};
class QgsNativeMetadataValidator : QgsMetadataValidator
{
%Docstring
A validator for the native QGIS metadata schema definition.
.. versionadded:: 3.0
%End
%TypeHeaderCode
#include "qgslayermetadatavalidator.h"
%End
public:
QgsNativeMetadataValidator();
%Docstring
Constructor for QgsNativeMetadataValidator.
%End
virtual bool validate( const QgsLayerMetadata &metadata, QList< QgsMetadataValidator::ValidationResult > &results /Out/ ) const;
};
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/metadata/qgslayermetadatavalidator.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/

View File

@ -829,7 +829,7 @@ Return pointer to layer's undo stack
virtual void setMetadata( const QgsLayerMetadata &metadata );
%Docstring
Sets the layer's metadata store.
Sets the layer's ``metadata`` store.
.. versionadded:: 3.0
\see metadata()
\see metadataChanged()

View File

@ -72,6 +72,7 @@ SET(QGIS_CORE_SRCS
layertree/qgslayertree.cpp
metadata/qgslayermetadata.cpp
metadata/qgslayermetadatavalidator.cpp
auth/qgsauthcertutils.cpp
auth/qgsauthconfig.cpp
@ -858,6 +859,7 @@ SET(QGIS_CORE_HDRS
composer/qgspaperitem.h
metadata/qgslayermetadata.h
metadata/qgslayermetadatavalidator.h
processing/qgsprocessingalgorithm.h
processing/qgsprocessingcontext.h

View File

@ -0,0 +1,130 @@
/***************************************************************************
qgslayermetadatavalidator.cpp
-----------------------------
begin : April 2017
copyright : (C) 2017 by Nyall Dawson
email : nyall dot dawson at gmail dot com
***************************************************************************/
/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include "qgslayermetadatavalidator.h"
#include "qgslayermetadata.h"
bool QgsNativeMetadataValidator::validate( const QgsLayerMetadata &metadata, QList<ValidationResult> &results ) const
{
results.clear();
bool result = true;
if ( metadata.identifier().isEmpty() )
{
result = false;
results << ValidationResult( QObject::tr( "identifier" ), QObject::tr( "Identifier element is required." ) );
}
if ( metadata.language().isEmpty() )
{
result = false;
results << ValidationResult( QObject::tr( "language" ), QObject::tr( "Language element is required." ) );
}
if ( metadata.type().isEmpty() )
{
result = false;
results << ValidationResult( QObject::tr( "type" ), QObject::tr( "Type element is required." ) );
}
if ( metadata.title().isEmpty() )
{
result = false;
results << ValidationResult( QObject::tr( "title" ), QObject::tr( "Title element is required." ) );
}
if ( metadata.abstract().isEmpty() )
{
result = false;
results << ValidationResult( QObject::tr( "abstract" ), QObject::tr( "Abstract element is required." ) );
}
//result = result && !metadata.license().isEmpty();
if ( !metadata.crs().isValid() )
{
result = false;
results << ValidationResult( QObject::tr( "crs" ), QObject::tr( "A valid CRS element is required." ) );
}
if ( metadata.contacts().isEmpty() )
{
result = false;
results << ValidationResult( QObject::tr( "contacts" ), QObject::tr( "At least one contact is required." ) );
}
if ( metadata.links().isEmpty() )
{
result = false;
results << ValidationResult( QObject::tr( "links" ), QObject::tr( "At least one link is required." ) );
}
// validate keywords
QgsLayerMetadata::KeywordMap keywords = metadata.keywords();
QgsLayerMetadata::KeywordMap::const_iterator keywordIt = keywords.constBegin();
int index = 0;
for ( ; keywordIt != keywords.constEnd(); ++keywordIt )
{
if ( keywordIt.key().isEmpty() )
{
result = false;
results << ValidationResult( QObject::tr( "keywords" ), QObject::tr( "Keyword vocabulary cannot be empty." ), index );
}
if ( keywordIt.value().isEmpty() )
{
result = false;
results << ValidationResult( QObject::tr( "keywords" ), QObject::tr( "Keyword list cannot be empty." ), index );
}
index++;
}
// validate contacts
index = 0;
Q_FOREACH ( const QgsLayerMetadata::Contact &contact, metadata.contacts() )
{
if ( contact.name.isEmpty() )
{
result = false;
results << ValidationResult( QObject::tr( "contacts" ), QObject::tr( "Contact name cannot be empty." ), index );
}
index++;
}
// validate links
index = 0;
Q_FOREACH ( const QgsLayerMetadata::Link &link, metadata.links() )
{
if ( link.name.isEmpty() )
{
result = false;
results << ValidationResult( QObject::tr( "links" ), QObject::tr( "Link name cannot be empty." ), index );
}
if ( link.type.isEmpty() )
{
result = false;
results << ValidationResult( QObject::tr( "links" ), QObject::tr( "Link type cannot be empty." ), index );
}
if ( link.url.isEmpty() )
{
result = false;
results << ValidationResult( QObject::tr( "links" ), QObject::tr( "Link url cannot be empty." ), index );
}
index++;
}
return result;
}

View File

@ -0,0 +1,104 @@
/***************************************************************************
qgslayermetadatavalidator.h
---------------------------
begin : April 2017
copyright : (C) 2017 by Nyall Dawson
email : nyall dot dawson at gmail dot com
***************************************************************************/
/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#ifndef QGSLAYERMETADATAVALIDATOR_H
#define QGSLAYERMETADATAVALIDATOR_H
#include "qgis.h"
#include "qgis_core.h"
class QgsLayerMetadata;
/**
* \ingroup core
* \class QgsMetadataValidator
* \brief Abstract base class for metadata validators.
* \since QGIS 3.0
*/
class CORE_EXPORT QgsMetadataValidator
{
public:
/**
* Contains the parameters describing a metadata validation
* failure.
*/
struct ValidationResult
{
/**
* Constructor for ValidationResult.
*/
ValidationResult( const QString &section, const QString &note, const QVariant &identifier = QVariant() )
: section( section )
, identifier( identifier )
, note( note )
{}
//! Metadata section which failed the validation
QString section;
/**
* Optional identifier for the failed metadata item.
* For instance, in list type metadata elements this
* will be set to the list index of the failed metadata
* item.
*/
QVariant identifier;
//! The reason behind the validation failure.
QString note;
};
virtual ~QgsMetadataValidator() = default;
/**
* Validates a \a metadata object, and returns true if the
* metadata is considered valid.
* If validation fails, the \a results list will be filled with a list of
* items describing why the validation failed and what needs to be rectified
* to fix the metadata.
*/
virtual bool validate( const QgsLayerMetadata &metadata, QList< QgsMetadataValidator::ValidationResult > &results SIP_OUT ) const = 0;
};
/**
* \ingroup core
* \class QgsNativeMetadataValidator
* \brief A validator for the native QGIS metadata schema definition.
* \since QGIS 3.0
*/
class CORE_EXPORT QgsNativeMetadataValidator : public QgsMetadataValidator
{
public:
/**
* Constructor for QgsNativeMetadataValidator.
*/
QgsNativeMetadataValidator() = default;
bool validate( const QgsLayerMetadata &metadata, QList< QgsMetadataValidator::ValidationResult > &results SIP_OUT ) const override;
};
#endif // QGSLAYERMETADATAVALIDATOR_H

View File

@ -16,7 +16,8 @@ import qgis # NOQA
from qgis.core import (QgsLayerMetadata,
QgsCoordinateReferenceSystem,
QgsVectorLayer)
QgsVectorLayer,
QgsNativeMetadataValidator)
from qgis.testing import start_app, unittest
start_app()
@ -300,6 +301,117 @@ class TestQgsLayerMetadata(unittest.TestCase):
m2.readFromLayer(vl)
self.checkExpectedMetadata(m2)
def testValidateNative(self): # spellok
"""
Test validating metadata against QGIS native schema
"""
m = self.createTestMetadata()
v = QgsNativeMetadataValidator()
res, list = v.validate(m)
self.assertTrue(res)
self.assertFalse(list)
# corrupt metadata piece by piece...
m = self.createTestMetadata()
m.setIdentifier('')
res, list = v.validate(m)
self.assertFalse(res)
self.assertEqual(list[0].section, 'identifier')
m = self.createTestMetadata()
m.setLanguage('')
res, list = v.validate(m)
self.assertFalse(res)
self.assertEqual(list[0].section, 'language')
m = self.createTestMetadata()
m.setType('')
res, list = v.validate(m)
self.assertFalse(res)
self.assertEqual(list[0].section, 'type')
m = self.createTestMetadata()
m.setTitle('')
res, list = v.validate(m)
self.assertFalse(res)
self.assertEqual(list[0].section, 'title')
m = self.createTestMetadata()
m.setAbstract('')
res, list = v.validate(m)
self.assertFalse(res)
self.assertEqual(list[0].section, 'abstract')
m = self.createTestMetadata()
m.setCrs(QgsCoordinateReferenceSystem())
res, list = v.validate(m)
self.assertFalse(res)
self.assertEqual(list[0].section, 'crs')
m = self.createTestMetadata()
m.setContacts([])
res, list = v.validate(m)
self.assertFalse(res)
self.assertEqual(list[0].section, 'contacts')
m = self.createTestMetadata()
m.setLinks([])
res, list = v.validate(m)
self.assertFalse(res)
self.assertEqual(list[0].section, 'links')
m = self.createTestMetadata()
m.setKeywords({'': ['kw1', 'kw2']})
res, list = v.validate(m)
self.assertFalse(res)
self.assertEqual(list[0].section, 'keywords')
self.assertEqual(list[0].identifier, 0)
m = self.createTestMetadata()
m.setKeywords({'AA': []})
res, list = v.validate(m)
self.assertFalse(res)
self.assertEqual(list[0].section, 'keywords')
self.assertEqual(list[0].identifier, 0)
m = self.createTestMetadata()
c = m.contacts()[0]
c.name = ''
m.setContacts([c])
res, list = v.validate(m)
self.assertFalse(res)
self.assertEqual(list[0].section, 'contacts')
self.assertEqual(list[0].identifier, 0)
m = self.createTestMetadata()
l = m.links()[0]
l.name = ''
m.setLinks([l])
res, list = v.validate(m)
self.assertFalse(res)
self.assertEqual(list[0].section, 'links')
self.assertEqual(list[0].identifier, 0)
m = self.createTestMetadata()
l = m.links()[0]
l.type = ''
m.setLinks([l])
res, list = v.validate(m)
self.assertFalse(res)
self.assertEqual(list[0].section, 'links')
self.assertEqual(list[0].identifier, 0)
m = self.createTestMetadata()
l = m.links()[0]
l.url = ''
m.setLinks([l])
res, list = v.validate(m)
self.assertFalse(res)
self.assertEqual(list[0].section, 'links')
self.assertEqual(list[0].identifier, 0)
if __name__ == '__main__':
unittest.main()