How to create an XML document with a custom structure when printing a report with Docentric

Introduction

When printing reports in D365FO there sometimes exists a need to have some key parts of these reports be read by a third party service.

Lots of third party services (especially SOAP) read XML documents that are of a specific structure.

In this post, we will show an example of how to create a custom XML document from the ground up in X++ code, by taking only specific parts of the data from a report when printing to the Docentric file print destination.

This custom XML document can then be saved to Azure Blob storage, Azure files, Sharepoint etc. where a third party service can access it.

Code example

The first step is to create a new class that inherits the base DSP class of the report that we want to create the custom XML document for, in this case DocSalesInvoiceReportDSP:

/// <summary>
/// This class creates a custom XML file based from the contents of the salesInvoice.report.
/// </summary>
class DocSalesInvoiceWithCustomXMLFileDSP extends DocSalesInvoiceReportDSP
{
    /// <summary>
    /// Used to change the description of the DSP class.
    /// </summary>
    /// <returns>String representing the DSP class description.</returns>
    public ClassDescription description()
    {
        return 'Customer invoice DSP with custom XML file';
    }

    /// <summary>
    /// Creates a custom XML document from the DDSP structure that is created in the super call.
    /// </summary>
    /// <param name = "_recordBuilder">Object that contains the records from which to read report data.</param>
        protected void generateXmlDataSource(DocXmlRecordBuilder _recordBuilder)
    {
        super(_recordBuilder);

        try
        {
            using (System.IO.MemoryStream fileContentMemoryStream = new System.IO.MemoryStream())
            {
                System.Xml.XmlWriterSettings writerSettings = new System.Xml.XmlWriterSettings();
        
                using (System.Xml.XmlWriter writer = System.Xml.XmlWriter::Create(fileContentMemoryStream, writerSettings))
                {
                    List xmlChildRecordsList = _recordBuilder.topRecord().childRecords();

                    ListEnumerator xmlChildRecordsListEnumerator = xmlChildRecordsList.getEnumerator();

                    // Create root element.
                    writer.WriteStartElement('SalesInvoice');

                    while (xmlChildRecordsListEnumerator.moveNext())
                    {
                        DocXmlRecord currentXMLRecord = xmlChildRecordsListEnumerator.current();

                        switch(currentXMLRecord.getRecordName())
                        {
                            case 'SalesInvoiceHeader':
                                writer.WriteStartElement('SalesInvoiceHeader');

                                writer.WriteElementString('SalesId', currentXMLRecord.getField('SalesId').getFieldValue());
                                writer.WriteElementString('InvoiceAccount', currentXMLRecord.getField('InvoiceAccount').getFieldValue());
                                writer.WriteElementString('InvoiceDate', currentXMLRecord.getField('InvoiceDate').getFieldValue());
                                writer.WriteElementString('InvoicingAddress', currentXMLRecord.getField('InvoicingAddress').getFieldValue());

                                writer.WriteEndElement();

                                break;
                            case 'SalesInvoiceLines':
                                writer.WriteStartElement('SalesInvoiceLine');

                                writer.WriteElementString('ItemId', currentXMLRecord.getField('ItemId').getFieldValue());
                                writer.WriteElementString('LineAmount', currentXMLRecord.getField('LineAmount').getFieldValue());
                                writer.WriteElementString('Qty', currentXMLRecord.getField('Qty').getFieldValue());
                                writer.WriteElementString('SalesPrice', currentXMLRecord.getField('SalesPrice').getFieldValue());

                                writer.WriteEndElement();
                                break;
                        }
                    }

                    // Close root element.
                    writer.WriteEndElement();
                }
        
                fileContentMemoryStream.Position = 0;

                container fileContent = DocGlobalHelper::convertMemoryStreamToContainer(fileContentMemoryStream);
                container fileSettings = [classIdGet(this), fileContent];
                this.parmParamsReporting().parmReportRunContext().parmCustomSettings(fileSettings);
            }
        }
        catch (Exception::CLRError)
        {
            DocGlobalHelper::handleClrException(funcName(), 'Error occurred while generating custom XML document for customer invoice (salesInvoice.Report)');
        }
        catch (Exception::Internal)
        {
            DocGlobalHelper::handleClrException(funcName(), 'Error occurred while generating custom XML document for customer invoice (salesInvoice.Report)');
        }
        catch
        {
            DocGlobalHelper::handleException(funcName(), 'Error occurred while generating custom XML document for customer invoice (salesInvoice.Report)');
        }
    }

    /// <summary>
    /// Delegate to intercept printing to the Docentric file print destination.
    /// </summary>
    /// <param name = "_printReportSettings">Holds information about the print report settings, used to ascertain if the appropriate setup is used.</param>
    /// <param name = "_filePrintDestSettings">Holds information about the print destination settings, used to check if the right file format is used.</param>
    /// <param name = "_fileExecutionInfo">Holds information about the file execution, not used in this case.</param>
    /// <param name = "_result">Result of the event handler, not used in this case.</param>
    [SubscribesTo(classStr(DocReportRunDelegates), delegateStr(DocReportRunDelegates, printToFileBegin))]
    public static void DocReportRunDelegates_printToFileBegin(DocPrintReportSettings _printReportSettings,
        DocPrintDestSettingsFile _filePrintDestSettings, DocPrintReportToFileExecutionInfo _fileExecutionInfo, DocEventHandlerResult _result)
    {
        if (_printReportSettings.parmReportId() == ssrsReportStr(SalesInvoice, Report)
                && !_printReportSettings.printSrsOriginalDesign() // Print only when printing Docentric templates.
                && _printReportSettings.parmCustomSettings()  // Check if the CustomSettings property is set, otherwise there is no XML file.
                && conPeek(_printReportSettings.parmCustomSettings(), 1) == classNum(DocSalesInvoiceWithCustomXMLFileDSP)  // Check if this class is the one who wrote the custom settings, so as to not conflict with other extensions.
                && _filePrintDestSettings.parmOutputFileFormat() == DocOutputFileFormat::XML)
        {
            container xmlMetadataDocument = conPeek(_printReportSettings.parmCustomSettings(), 2);
            _printReportSettings.addPrintedReportDocFromContainer(xmlMetadataDocument, DocOutputFileFormat::XML);
        }
    }

}

Running the example

Here we will show the result of the code example provided above.

First thing we need to do in order to run the code is to choose the appropriate DSP class in the Docentric AX Reports form:

Next, we should create a new Print management setup node where we can set up the Print destination settings (make sure to put the output format as XML):


Here we can also make it so the output is saved to Sharepoint:

Now, when we print the Sales invoice report, we will get the standard report executed (based on the Print destination settings of the Original Print management setup node) and we will create the custom XML document, which will be saved to Sharepoint:


The produced report looks like this:


And the XML document we created looks like this:

This was just a basic demonstration, but we hope it will help serve as a solid foundation for your customizations.

Happy coding!