Implement serialization of reports

This commit is contained in:
Nyall Dawson 2017-12-29 20:51:03 +10:00
parent 6f2c63f3e5
commit 2654454c0b
13 changed files with 375 additions and 9 deletions

View File

@ -45,6 +45,14 @@ exposed to the Python bindings for unit testing purposes only.
%TypeHeaderCode
#include "qgsabstractreportsection.h"
%End
%ConvertToSubClassCode
if ( dynamic_cast< QgsReportSectionFieldGroup * >( sipCpp ) )
sipType = sipType_QgsReportSectionFieldGroup;
else if ( dynamic_cast< QgsReportSectionLayout * >( sipCpp ) )
sipType = sipType_QgsReportSectionLayout;
else
sipType = NULL;
%End
public:
@ -58,6 +66,11 @@ Note that ownership is not transferred to ``parent``.
virtual QString type() const = 0;
%Docstring
Returns the section subclass type.
%End
virtual QgsAbstractReportSection *clone() const = 0 /Factory/;
%Docstring
Clones the report section. Ownership of the returned section is
@ -272,6 +285,20 @@ Sets the current ``context`` for this section.
Returns the current context for this section.
.. seealso:: :py:func:`setContext()`
%End
bool writeXml( QDomElement &parentElement, QDomDocument &document, const QgsReadWriteContext &context ) const;
%Docstring
Stores the section state in a DOM element.
.. seealso:: :py:func:`readXml()`
%End
bool readXml( const QDomElement &sectionElement, const QDomDocument &document, const QgsReadWriteContext &context );
%Docstring
Sets the item state from a DOM element.
.. seealso:: :py:func:`writeXml()`
%End
protected:
@ -291,9 +318,27 @@ Copies the common properties of a report section to a ``destination`` section.
This method should be called from clone() implementations.
%End
void setParent( QgsAbstractReportSection *parent );
virtual void setParentSection( QgsAbstractReportSection *parent );
%Docstring
Sets the ``parent`` report section.
%End
virtual bool writePropertiesToElement( QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context ) const;
%Docstring
Stores section state within an XML DOM element.
.. seealso:: :py:func:`writeXml()`
.. seealso:: :py:func:`readPropertiesFromElement()`
%End
virtual bool readPropertiesFromElement( const QDomElement &element, const QDomDocument &document, const QgsReadWriteContext &context );
%Docstring
Sets section state from a DOM element.
.. seealso:: :py:func:`writePropertiesToElement()`
.. seealso:: :py:func:`readXml()`
%End
private:

View File

@ -38,6 +38,7 @@ Constructor for QgsReport, associated with the specified
Note that ownership is not transferred to ``project``.
%End
virtual QString type() const;
virtual QgsProject *layoutProject() const;
virtual QgsReport *clone() const /Factory/;

View File

@ -34,6 +34,8 @@ Constructor for QgsReportSectionFieldGroup, attached to the specified ``parent``
Note that ownership is not transferred to ``parent``.
%End
virtual QString type() const;
QgsLayout *body();
%Docstring
Returns the body layout for the section.
@ -85,6 +87,15 @@ Sets the ``field`` associated with this section.
virtual void reset();
virtual void setParentSection( QgsAbstractReportSection *parent );
protected:
virtual bool writePropertiesToElement( QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context ) const;
virtual bool readPropertiesFromElement( const QDomElement &element, const QDomDocument &document, const QgsReadWriteContext &context );
};

View File

@ -33,6 +33,8 @@ Constructor for QgsReportSectionLayout, attached to the specified ``parent`` sec
Note that ownership is not transferred to ``parent``.
%End
virtual QString type() const;
QgsLayout *body();
%Docstring
Returns the body layout for the section.
@ -55,6 +57,13 @@ is transferred to the report section.
virtual QgsLayout *nextBody( bool &ok );
protected:
virtual bool writePropertiesToElement( QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context ) const;
virtual bool readPropertiesFromElement( const QDomElement &element, const QDomDocument &document, const QgsReadWriteContext &context );
};

View File

@ -17,6 +17,8 @@
#include "qgsabstractreportsection.h"
#include "qgslayout.h"
#include "qgsreport.h"
#include "qgsreportsectionfieldgroup.h"
#include "qgsreportsectionlayout.h"
///@cond NOT_STABLE
@ -54,6 +56,95 @@ void QgsAbstractReportSection::setContext( const QgsReportSectionContext &contex
}
}
bool QgsAbstractReportSection::writeXml( QDomElement &parentElement, QDomDocument &doc, const QgsReadWriteContext &context ) const
{
QDomElement element = doc.createElement( QStringLiteral( "Section" ) );
element.setAttribute( QStringLiteral( "type" ), type() );
element.setAttribute( QStringLiteral( "headerEnabled" ), mHeaderEnabled ? "1" : "0" );
if ( mHeader )
{
QDomElement headerElement = doc.createElement( QStringLiteral( "header" ) );
headerElement.appendChild( mHeader->writeXml( doc, context ) );
element.appendChild( headerElement );
}
element.setAttribute( QStringLiteral( "footerEnabled" ), mFooterEnabled ? "1" : "0" );
if ( mFooter )
{
QDomElement footerElement = doc.createElement( QStringLiteral( "footer" ) );
footerElement.appendChild( mFooter->writeXml( doc, context ) );
element.appendChild( footerElement );
}
for ( QgsAbstractReportSection *section : mChildren )
{
section->writeXml( element, doc, context );
}
writePropertiesToElement( element, doc, context );
parentElement.appendChild( element );
return true;
}
bool QgsAbstractReportSection::readXml( const QDomElement &element, const QDomDocument &doc, const QgsReadWriteContext &context )
{
if ( element.nodeName() != QStringLiteral( "Section" ) )
{
return false;
}
mHeaderEnabled = element.attribute( QStringLiteral( "headerEnabled" ), "0" ).toInt();
mFooterEnabled = element.attribute( QStringLiteral( "footerEnabled" ), "0" ).toInt();
const QDomElement headerElement = element.firstChildElement( QStringLiteral( "header" ) );
if ( !headerElement.isNull() )
{
const QDomElement headerLayoutElem = headerElement.firstChild().toElement();
std::unique_ptr< QgsLayout > header = qgis::make_unique< QgsLayout >( project() );
header->readXml( headerLayoutElem, doc, context );
mHeader = std::move( header );
}
const QDomElement footerElement = element.firstChildElement( QStringLiteral( "footer" ) );
if ( !footerElement.isNull() )
{
const QDomElement footerLayoutElem = footerElement.firstChild().toElement();
std::unique_ptr< QgsLayout > footer = qgis::make_unique< QgsLayout >( project() );
footer->readXml( footerLayoutElem, doc, context );
mFooter = std::move( footer );
}
const QDomNodeList sectionItemList = element.childNodes();
for ( int i = 0; i < sectionItemList.size(); ++i )
{
const QDomElement currentSectionElem = sectionItemList.at( i ).toElement();
if ( currentSectionElem.nodeName() != QStringLiteral( "Section" ) )
continue;
const QString sectionType = currentSectionElem.attribute( QStringLiteral( "type" ) );
//TODO - eventually move this to a registry when there's enough subclasses to warrant it
std::unique_ptr< QgsAbstractReportSection > section;
if ( sectionType == QLatin1String( "SectionFieldGroup" ) )
{
section = qgis::make_unique< QgsReportSectionFieldGroup >();
}
else if ( sectionType == QLatin1String( "SectionLayout" ) )
{
section = qgis::make_unique< QgsReportSectionLayout >();
}
if ( section )
{
appendChild( section.get() );
section->readXml( currentSectionElem, doc, context );
( void )section.release(); //ownership was transferred already
}
}
bool result = readPropertiesFromElement( element, doc, context );
return result;
}
QString QgsAbstractReportSection::filePath( const QString &baseFilePath, const QString &extension )
{
QString base = QStringLiteral( "%1_%2" ).arg( baseFilePath ).arg( mSectionNumber, 4, 10, QChar( '0' ) );
@ -235,13 +326,13 @@ QgsAbstractReportSection *QgsAbstractReportSection::childSection( int index )
void QgsAbstractReportSection::appendChild( QgsAbstractReportSection *section )
{
section->setParent( this );
section->setParentSection( this );
mChildren.append( section );
}
void QgsAbstractReportSection::insertChild( int index, QgsAbstractReportSection *section )
{
section->setParent( this );
section->setParentSection( this );
index = std::max( 0, index );
index = std::min( index, mChildren.count() );
mChildren.insert( index, section );
@ -285,5 +376,15 @@ void QgsAbstractReportSection::copyCommonProperties( QgsAbstractReportSection *d
}
}
bool QgsAbstractReportSection::writePropertiesToElement( QDomElement &, QDomDocument &, const QgsReadWriteContext & ) const
{
return true;
}
bool QgsAbstractReportSection::readPropertiesFromElement( const QDomElement &, const QDomDocument &, const QgsReadWriteContext & )
{
return true;
}
///@endcond

View File

@ -53,6 +53,17 @@ class CORE_EXPORT QgsReportSectionContext
class CORE_EXPORT QgsAbstractReportSection : public QgsAbstractLayoutIterator
{
#ifdef SIP_RUN
SIP_CONVERT_TO_SUBCLASS_CODE
if ( dynamic_cast< QgsReportSectionFieldGroup * >( sipCpp ) )
sipType = sipType_QgsReportSectionFieldGroup;
else if ( dynamic_cast< QgsReportSectionLayout * >( sipCpp ) )
sipType = sipType_QgsReportSectionLayout;
else
sipType = NULL;
SIP_END
#endif
public:
/**
@ -69,6 +80,11 @@ class CORE_EXPORT QgsAbstractReportSection : public QgsAbstractLayoutIterator
//! QgsAbstractReportSection cannot be copied
QgsAbstractReportSection &operator=( const QgsAbstractReportSection &other ) = delete;
/**
* Returns the section subclass type.
*/
virtual QString type() const = 0;
/**
* Clones the report section. Ownership of the returned section is
* transferred to the caller.
@ -242,6 +258,18 @@ class CORE_EXPORT QgsAbstractReportSection : public QgsAbstractLayoutIterator
*/
const QgsReportSectionContext &context() const { return mContext; }
/**
* Stores the section state in a DOM element.
* \see readXml()
*/
bool writeXml( QDomElement &parentElement, QDomDocument &document, const QgsReadWriteContext &context ) const;
/**
* Sets the item state from a DOM element.
* \see writeXml()
*/
bool readXml( const QDomElement &sectionElement, const QDomDocument &document, const QgsReadWriteContext &context );
protected:
//! Report sub-sections
@ -263,7 +291,21 @@ class CORE_EXPORT QgsAbstractReportSection : public QgsAbstractLayoutIterator
/**
* Sets the \a parent report section.
*/
void setParent( QgsAbstractReportSection *parent ) { mParent = parent; }
virtual void setParentSection( QgsAbstractReportSection *parent ) { mParent = parent; }
/**
* Stores section state within an XML DOM element.
* \see writeXml()
* \see readPropertiesFromElement()
*/
virtual bool writePropertiesToElement( QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context ) const;
/**
* Sets section state from a DOM element.
* \see writePropertiesToElement()
* \see readXml()
*/
virtual bool readPropertiesFromElement( const QDomElement &element, const QDomDocument &document, const QgsReadWriteContext &context );
private:

View File

@ -40,15 +40,20 @@ void QgsReport::setName( const QString &name )
QDomElement QgsReport::writeLayoutXml( QDomDocument &document, const QgsReadWriteContext &context ) const
{
QDomElement element = document.createElement( QStringLiteral( "Report" ) );
writeXml( element, document, context );
element.setAttribute( QStringLiteral( "name" ), mName );
return element;
}
bool QgsReport::readLayoutXml( const QDomElement &layoutElement, const QDomDocument &document, const QgsReadWriteContext &context )
{
const QDomNodeList sectionList = layoutElement.elementsByTagName( QStringLiteral( "Section" ) );
if ( sectionList.count() > 0 )
{
readXml( sectionList.at( 0 ).toElement(), document, context );
}
setName( layoutElement.attribute( QStringLiteral( "name" ) ) );
return true;
}
///@endcond

View File

@ -52,6 +52,7 @@ class CORE_EXPORT QgsReport : public QObject, public QgsAbstractReportSection, p
*/
QgsReport( QgsProject *project );
QString type() const override { return QStringLiteral( "SectionReport" ); }
QgsProject *layoutProject() const override { return mProject; }
QgsReport *clone() const override SIP_FACTORY;
QString name() const override { return mName; }

View File

@ -98,6 +98,56 @@ void QgsReportSectionFieldGroup::reset()
mEncounteredValues.clear();
}
void QgsReportSectionFieldGroup::setParentSection( QgsAbstractReportSection *parent )
{
QgsAbstractReportSection::setParentSection( parent );
if ( !mCoverageLayer )
mCoverageLayer.resolveWeakly( project() );
}
bool QgsReportSectionFieldGroup::writePropertiesToElement( QDomElement &element, QDomDocument &doc, const QgsReadWriteContext &context ) const
{
element.setAttribute( QStringLiteral( "field" ), mField );
if ( mCoverageLayer )
{
element.setAttribute( QStringLiteral( "coverageLayer" ), mCoverageLayer.layerId );
element.setAttribute( QStringLiteral( "coverageLayerName" ), mCoverageLayer.name );
element.setAttribute( QStringLiteral( "coverageLayerSource" ), mCoverageLayer.source );
element.setAttribute( QStringLiteral( "coverageLayerProvider" ), mCoverageLayer.provider );
}
if ( mBody )
{
QDomElement bodyElement = doc.createElement( QStringLiteral( "body" ) );
bodyElement.appendChild( mBody->writeXml( doc, context ) );
element.appendChild( bodyElement );
}
return true;
}
bool QgsReportSectionFieldGroup::readPropertiesFromElement( const QDomElement &element, const QDomDocument &doc, const QgsReadWriteContext &context )
{
mField = element.attribute( QStringLiteral( "field" ) );
QString layerId = element.attribute( QStringLiteral( "coverageLayer" ) );
QString layerName = element.attribute( QStringLiteral( "coverageLayerName" ) );
QString layerSource = element.attribute( QStringLiteral( "coverageLayerSource" ) );
QString layerProvider = element.attribute( QStringLiteral( "coverageLayerProvider" ) );
mCoverageLayer = QgsVectorLayerRef( layerId, layerName, layerSource, layerProvider );
mCoverageLayer.resolveWeakly( project() );
const QDomElement bodyElement = element.firstChildElement( QStringLiteral( "body" ) );
if ( !bodyElement.isNull() )
{
const QDomElement bodyLayoutElem = bodyElement.firstChild().toElement();
std::unique_ptr< QgsLayout > body = qgis::make_unique< QgsLayout >( project() );
body->readXml( bodyLayoutElem, doc, context );
mBody = std::move( body );
}
return true;
}
QgsFeatureRequest QgsReportSectionFieldGroup::buildFeatureRequest() const
{
QgsFeatureRequest request;

View File

@ -44,6 +44,8 @@ class CORE_EXPORT QgsReportSectionFieldGroup : public QgsAbstractReportSection
*/
QgsReportSectionFieldGroup( QgsAbstractReportSection *parent = nullptr );
QString type() const override { return QStringLiteral( "SectionFieldGroup" ); }
/**
* Returns the body layout for the section.
* \see setBody()
@ -85,6 +87,12 @@ class CORE_EXPORT QgsReportSectionFieldGroup : public QgsAbstractReportSection
bool beginRender() override;
QgsLayout *nextBody( bool &ok ) override;
void reset() override;
void setParentSection( QgsAbstractReportSection *parent ) override;
protected:
bool writePropertiesToElement( QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context ) const override;
bool readPropertiesFromElement( const QDomElement &element, const QDomDocument &document, const QgsReadWriteContext &context ) override;
private:

View File

@ -59,5 +59,29 @@ QgsLayout *QgsReportSectionLayout::nextBody( bool &ok )
}
}
bool QgsReportSectionLayout::writePropertiesToElement( QDomElement &element, QDomDocument &doc, const QgsReadWriteContext &context ) const
{
if ( mBody )
{
QDomElement bodyElement = doc.createElement( QStringLiteral( "body" ) );
bodyElement.appendChild( mBody->writeXml( doc, context ) );
element.appendChild( bodyElement );
}
return true;
}
bool QgsReportSectionLayout::readPropertiesFromElement( const QDomElement &element, const QDomDocument &doc, const QgsReadWriteContext &context )
{
const QDomElement bodyElement = element.firstChildElement( QStringLiteral( "body" ) );
if ( !bodyElement.isNull() )
{
const QDomElement bodyLayoutElem = bodyElement.firstChild().toElement();
std::unique_ptr< QgsLayout > body = qgis::make_unique< QgsLayout >( project() );
body->readXml( bodyLayoutElem, doc, context );
mBody = std::move( body );
}
return true;
}
///@endcond

View File

@ -41,6 +41,8 @@ class CORE_EXPORT QgsReportSectionLayout : public QgsAbstractReportSection
*/
QgsReportSectionLayout( QgsAbstractReportSection *parent = nullptr );
QString type() const override { return QStringLiteral( "SectionLayout" ); }
/**
* Returns the body layout for the section.
* \see setBody()
@ -58,6 +60,11 @@ class CORE_EXPORT QgsReportSectionLayout : public QgsAbstractReportSection
bool beginRender() override;
QgsLayout *nextBody( bool &ok ) override;
protected:
bool writePropertiesToElement( QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context ) const override;
bool readPropertiesFromElement( const QDomElement &element, const QDomDocument &document, const QgsReadWriteContext &context ) override;
private:
bool mExportedBody = false;

View File

@ -21,8 +21,11 @@ from qgis.core import (QgsProject,
QgsReportSectionFieldGroup,
QgsVectorLayer,
QgsField,
QgsFeature)
QgsFeature,
QgsReadWriteContext,
QgsUnitTypes)
from qgis.testing import start_app, unittest
from qgis.PyQt.QtXml import QDomDocument
start_app()
@ -429,6 +432,65 @@ class TestQgsReport(unittest.TestCase):
self.assertEqual(r.layout().reportContext().feature().attributes(), ['PNG', 'state1', 'town1'])
self.assertFalse(r.next())
def testReadWriteXml(self):
p = QgsProject()
ptLayer = QgsVectorLayer("Point?crs=epsg:4326&field=country:string(20)&field=state:string(20)&field=town:string(20)", "points", "memory")
p.addMapLayer(ptLayer)
r = QgsReport(p)
r.setName('my report')
# add a header
r.setHeaderEnabled(True)
report_header = QgsLayout(p)
report_header.setUnits(QgsUnitTypes.LayoutInches)
r.setHeader(report_header)
# add a footer
r.setFooterEnabled(True)
report_footer = QgsLayout(p)
report_footer.setUnits(QgsUnitTypes.LayoutMeters)
r.setFooter(report_footer)
# add some subsections
child1 = QgsReportSectionLayout()
child1_body = QgsLayout(p)
child1_body.setUnits(QgsUnitTypes.LayoutPoints)
child1.setBody(child1_body)
child2 = QgsReportSectionLayout()
child2_body = QgsLayout(p)
child2_body.setUnits(QgsUnitTypes.LayoutPixels)
child2.setBody(child2_body)
child1.appendChild(child2)
child2a = QgsReportSectionFieldGroup()
child2a_body = QgsLayout(p)
child2a_body.setUnits(QgsUnitTypes.LayoutInches)
child2a.setBody(child2a_body)
child2a.setField('my field')
child2a.setLayer(ptLayer)
child1.appendChild(child2a)
r.appendChild(child1)
doc = QDomDocument("testdoc")
elem = r.writeLayoutXml(doc, QgsReadWriteContext())
r2 = QgsReport(p)
self.assertTrue(r2.readLayoutXml(elem, doc, QgsReadWriteContext()))
self.assertEqual(r2.name(), 'my report')
self.assertTrue(r2.headerEnabled())
self.assertEqual(r2.header().units(), QgsUnitTypes.LayoutInches)
self.assertTrue(r2.footerEnabled())
self.assertEqual(r2.footer().units(), QgsUnitTypes.LayoutMeters)
self.assertEqual(r2.childCount(), 1)
self.assertEqual(r2.childSection(0).body().units(), QgsUnitTypes.LayoutPoints)
self.assertEqual(r2.childSection(0).childCount(), 2)
self.assertEqual(r2.childSection(0).childSection(0).body().units(), QgsUnitTypes.LayoutPixels)
self.assertEqual(r2.childSection(0).childSection(1).body().units(), QgsUnitTypes.LayoutInches)
self.assertEqual(r2.childSection(0).childSection(1).field(), 'my field')
self.assertEqual(r2.childSection(0).childSection(1).layer(), ptLayer)
if __name__ == '__main__':
unittest.main()