Posted Sep 06 by Gareth Hutchins.
Updated Sep 06.

At this year's Enterprise World, we announced OT2 - our flexible, hybrid platform for EIM services and application deployment. In this post, I'll be covering some of the Micro Services that are available on OT2 for Classifying and Extracting metadata from document images which enables you to provide EIM functionality to existing applications & processes.

Last activity Sep 06 by Gareth Hutchins.
404 views. 0 comments.

Introduction to Snap

One of the applications built on top of OT2 is snap - https://snap.opentext.com/

Snap allows users to drive a scanner, or import files, from a web browser and automatically classify & extract content from documents, validate that data and then export it to their EIM repositories. Here's what it looks like:

enter image description here

Although Snap is an application that's designed to provide Enterprise Capture capabilites to general users. It's also built on a set of different Micro Services that enable organisations to enhance exsisting applications or processes with Enterprise Capture capabilities.

This could be in the form of Low Code Platforms such as OpenText's Appworks https://www.opentext.com/products-and-solutions/products/digital-process-automation/appworks-platform or by enabling mobile applications with document recognition capabilities for things such as cheque deposits or ID capture for customer on boarding. You can really use Snap to automate any business process that requires the capturing of meta data from incoming document images.

enter image description here
You can see the sourcode for my sample mobile application that uses the Snap services here:
https://github.com/garethhutchins/SnapMobileWIP

Document Extraction

In this post, I'll be illustrating how to consume the OT2 Snap Micro services in order for us to classify images of documents and then extract the relevant meta data from those documents. I won't be covering how to create the extraction rules in this post, I'll do that another time.

I've decided to use an Invoice use case for this sample. Invoices are interesting because they contain both single instance attributes, like Invoice Date and also repeating attributes, the Invoice Line Items. This is where Snap's specialist document extraction techniques are required to identify and handle these repeating attributes.

Here's a copy of an example Invoice Image that I'll be using:

enter image description here
Form this, I want to extract the following information:

  • Vendor Name
  • Invoice Number
  • Invoice Date
  • Order Number
  • Net Amount
  • TAX Amount
  • Total Amount
  • Line Quanitity
  • Line Description
  • Line Unit Price
  • Line Amount

Getting a Snap Subscription

To get started, you can register for a Snap Subscription at https://snap.opentext.com/
Once, you're in. You can go to the developer console and see the list of subscriptions.
enter image description here
Select your Subscription and then we're going to create some API service credentials. These Service Credentials allow other applications to connect to the Snap Micro services.
As you create them, it's important to make a note of the secret as you won't be able to see the value of this again after it appears in this dialog:

enter image description here

Scripting Introduction

In the next few sections, I'm going to be showing an example in Python on how to consume the following services:

  • Creating a Session
  • Uploading an Image
  • Enhancing an Image
  • Classifying & Extracting Content

There are a number of other services available such as PDF creation, Image Conversion, Data Validation & Export that I'll cover in later posts. As this is an introduction, I'll be sticking to single page images to keep things simpler.
Also, this isn't designed to be a program, more of an overview on how to call the different services so I won't be creating functions.

Import Modules

To start with, I need to use some python modules in this post so we first need to define some imports in our Python script:

import requests #For REST Posts
import json #For JSON conversion
import base64 #Convert Files for posting
import mimetypes #Get the content Type
import ntpath #Get the File Path

Creating a Session

Before we can call any other services, we my first create a session. For this, we'll need to provide the username and password we use to login to Snap as well as the subscription details and service credentials. We also need to specify the data center, which we do in the URL. For example, if it's in the US, use .COM and for Europe use .EU.
This first post is going to be different to the others in that it's a Form and not a JSON request. However, it does return JSON. We also need to convert the Client + Secret to a Base64 string in the header.
The response we get back is something like this:

{
    "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6I...",
    "token_type": "bearer",
    "refresh_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6...",
    "expires_in": 86399,
    "scope": "readwrite",
    "jti": "dd7c731a-a3f6-4092-ba4a-5d1ee65aaa77"
}

In the response, we're going to get the access token which we'll then use to pass on to the other services.
So, to login and get the access token, the request will be something like this:

#First we're going to login to the OT2 authentication service
print("Logging in to OT2")

#This is the Autehntication URL
#eu "https://authservice.leap.opentext.eu/authserver/oauth/token"
authUrl = "https://authservice.leap.opentext.com/authserver/oauth/token"

#Now create the Login request
loginRequest = {}
loginRequest['grant_type'] = 'password'
loginRequest['username'] = 'gareth.hutchins@opentext.com'
loginRequest['password'] = '[Your Password Here]'
loginRequest['subscriptionName'] = '[Your Subscription Name Here]'

#Take the client secret from the developer console and convert it to base 64
client = '[Your Client Here]'
secret = '[Your Secret Here]'
clientSecret = client + ':' + secret
csEncoded = base64.b64encode(clientSecret.encode())

# You now need to decode the Base64 to a string version
csString = csEncoded.decode('utf-8')

#Add the Client Secret and content Type to the request header
loginHeaders={}
loginHeaders['Content-Type'] = 'application/x-www-form-urlencoded'
loginHeaders['Authorization'] = "Basic " + csString

#Now post the request
r = requests.post(authUrl,data=loginRequest,headers=loginHeaders)
loginResponse = json.loads(r.text)

#Get the Access Token from the request
accessToken = loginResponse['access_token']

Creating Future Service Headers

Now that we have the Access Token, we can create the service headers for all future calls that we're going to make, we'll also specify the custom content type that these services use.. We can do this in a new Object like so:

#use this token in all future headers
#Create the service headers
serviceHeaders = {}
serviceHeaders['Authorization'] = 'Bearer ' + accessToken
serviceHeaders['Content-Type'] = 'application/vnd.emc.captiva+json; charset=utf-8'

Uploading the Image

Next, we're going to upload the image for future processing. There are a few ways we can upload images. We can upload them as a JSON string, which is what I do, binary data & we can chunk larger files up to make the transfer easier.
As we upload the image, we're also going to specify the mime type and the file name. When we make the post, we're going to get back a file ID & content type that we'll use for the next service. The response we get back will be something like this:

{
    'returnStatus': {
        'status': 201,
        'code': 'OK0000',
        'message': '',
        'server': 'WS-S3999d5e497e4451195b999f2f8223996HS'
    },
    'id': 'F_908442b3824245b88aac2e54ff0e907bTIF',
    'contentType': 'image/tiff',
    'src': 'https: //snap.leap.opentext.com/cp-rest/session/files/F_908442b3824245b88aac2e54ff0e907bTIF',
    'updated': '2018-08-29T11: 39: 41.9048861Z'
}

So, to make this post and then get the id & content type the Python code will look something like this:

#Now we're going to upload the Image
print('Uploading Image')
#Create Image Upload object

imageUpload = {}

#Open the file and convert it to a Base 64 String
fileName = '/Snap Python/Images/MyFirstInvoice.jpg'
#we'll use this for later
originalFileName = ntpath.basename(fileName)

imageFile = open(fileName,'rb').read()
imageB64 = base64.encodebytes(imageFile)

#Assign this string to a data element called data
imageUpload['data'] = imageB64.decode('utf-8')

#Now get the Mimetype of the file
#add 0 to get ('image/tiff', None)
mime = mimetypes.guess_type(fileName)[0]
imageUpload['contentType'] = mime
#Now convert the object to json
uploadJson = json.dumps(imageUpload)

#Now upload the image
#base url for all commands
#eu 'https://snap.leap.opentext.eu/cp-rest'
#us 'https://snap.leap.opentext.com/cp-rest'
baseURL = 'https://snap.leap.opentext.com/cp-rest/session'
#Now for the file resource
uploadURL = baseURL + '/files'

uploadRequest = requests.post(uploadURL,data=uploadJson,headers=serviceHeaders)
print(uploadRequest.text)
uploadResponse = json.loads(uploadRequest.text)

#get the image id
uploadFileID = uploadResponse['id']
uploadContentType = uploadResponse['contentType']

Enhancing the Image

Now that we have the image uploaded to OT2, we're going to apply some pre-processing to make sure we get the best possible image quality before we try to look for the information that we're after. I've created a profile that will perform actions like de-skewing, resolution alignment and conversion to black and white to help with the OCR process.
enter image description here
As we make this call, we're going to specify the name of the profile I created (BW), we're also going to specify the file information we got back from the last call. This call will give us back a new file ID, the old one will still be available to us if we need it - say for PDF creation. We'll then pass this new file ID on to the next service. So full information we get back from this service looks something like this:

{

"returnStatus": {
    "status": 200,
    "code": "OK0000",
    "message": "",
    "server": "WS-S3999d5e497e4451195b999f2f8223996HS"
},
"licenseUsedPercent": 0,
"id": "ReqCPIMGPRO_5DH_0619351329",
"serviceName": "processimage",
"executionMilliSeconds": 1274,
"licensePagesUsed": 1,
"licensePagesUsed2": 0,
"resultItems": [{
    "nodeId": 0,
    "errorCode": "",
    "errorMessage": "",
    "values": [{
        "name": "DeskewAngle",
        "value": 0
    }],
    "files": [{
        "name": "SCN_0001.tif",
        "value": "F_c17e9127d2e74a54971703b5c651db68TIF",
        "contentType": "image/tiff",
        "src": "https://snap.leap.opentext.com/cp-rest/session/files/F_c17e9127d2e74a54971703b5c651db68TIF",
        "fileType": "tif"
    }]
}]

In the request, we can also specify the environment we're using. With Snap you get one production, one Test and one Development environment, I'm using my Dev environment for this which is defined using the value 'D' in a service props object, along with specifying the environment we're using. As I said at earlier, I'm just using single pages here, you would build multiple image requests by adding more files to the request items array below. So, the python code to make this request and get the resulting file information will be the following:

    #Now Enhance the Image
    print('Enhancing Image')
    enhanceURL = baseURL + '/services/processimage'

    processImage = {}
    #First create the Service Props
    enhanceServiceProps = []
    #We can also use this for the Classify & Extract request
    enviroment = {}
    enviroment['name'] = 'Env'
    enviroment['value'] = "D"
    enhanceServiceProps.append(enviroment)

    processImageProfile = {}
    processImageProfile['name'] = 'Profile'
    processImageProfile['value'] = 'BW'
    enhanceServiceProps.append(processImageProfile)

    processImage['serviceProps'] = enhanceServiceProps

    #Now assign the files
    processImageRequestItems= []
    processImageRequestItem={}

    processImageRequestItem['nodeID'] = '1'
    processImageRequestFiles = []

    processImageFile = {}
    processImageFile['value'] = uploadFileID
    processImageFile['contentType'] = uploadContentType
    processImageFile['name'] = originalFileName
    processImageRequestFiles.append(processImageFile)
    processImageRequestItem['files'] = processImageRequestFiles

    processImageRequestItems.append(processImageRequestItem)
    processImage['requestItems'] = processImageRequestItems
    #Now do the posting
    #Now convert to Json
    processImageJson = json.dumps(processImage)

    processImageRequest = requests.post(enhanceURL,data=processImageJson,headers=serviceHeaders)
    print(processImageRequest.text)

    #Get the values we're after for the next bit
    processImageResponse = json.loads(processImageRequest.text)
    enhanceFileID = processImageResponse['resultItems'][0]['files'][0]['value']
    enahnceConentType = processImageResponse['resultItems'][0]['files'][0]['contentType']
    enhanceFileType = processImageResponse['resultItems'][0]['files'][0]['fileType']

Classification & Extraction Service

Now that we've got the image we're after, we can now pass it to the service to perform the classification and extraction of the data that we want. There are a few different services we can use for this, I'm using the page service that will do each page independently of others. Other services can run on a document level or separate out the classification and extraction pieces if that's required for exception handling. The reason why we classify and extract the image is that we could have multiple document types, say invoices and remittances. We need to first identify the type of document before we know what data to look for, and Invoice will have different data than a remittance advice.
This service returns a lot of information. It returns something called a UIM object (Unified Index Model), this will be familiar to those of you who have Captiva expertise as it's been used in Captiva since version 7.0. The UIM object contains all types of information about the meta data that's been extracted from a document, such as:

  • Field Name
  • Field Value
  • Field Label
  • Field Status
  • Coordinated of found data
  • Extraction Confidence
  • ..And much more

With this data, you can return it into a user interface for people to review or pass it on to another system automatically.
If you take a look at my mobile app example, you can see how I manage it in a user interface before exporting the data. You can see the source code for my mobile application here: https://github.com/garethhutchins/SnapMobileWIP

The Python code for performing the classification and extraction of the data would be as follows:

#Now do the Classify Extract Page
print('Classifying & Extracting Page')
#The service URL
classifyExtractURL = baseURL + '/services/classifyextractpage'
#Create a new request object
classifyExtraPage = {}
#We can use the service props from before

classifyExtractServiceProps = []
#Add the Enviroment settings from before
classifyExtractServiceProps.append(enviroment)
classifyExtraPage['serviceProps'] = classifyExtractServiceProps

    #Now build the request items
    classifyExtraPageRequestItems = []
    classifyExtraPageRequestItem = {}
    classifyExtraPageRequestItem['nodeId'] = '1'
    classifyExtraPageImageFiles = []
    classifyExtraPageImageFile = {}
    classifyExtraPageImageFile['name'] = originalFileName
    classifyExtraPageImageFile['value'] = enhanceFileID
    classifyExtraPageImageFile['contentType'] = enahnceConentType
    classifyExtraPageImageFile['fileType'] = enhanceFileType
    classifyExtraPageImageFiles.append(classifyExtraPageImageFile)
    classifyExtraPageRequestItem['files'] = classifyExtraPageImageFiles
    classifyExtraPageRequestItems.append(classifyExtraPageRequestItem)
    classifyExtraPage['requestItems'] = classifyExtraPageRequestItems

    #Now convert it to json
    classifyExtractPageJson = json.dumps(classifyExtraPage)
    classifyExtractRequest = requests.post(classifyExtractURL,data=classifyExtractPageJson,headers=serviceHeaders)
    print(classifyExtractRequest.text)

The full response that you get back from this request will be something like what you see below, you might not scroll to the end as there's a lot there.

Summary

So, to summarize - OT2 has a number of different applications that are built on different micro services. These micro services allow you to enable existing processes and applications with discrete EIM capabilities. This post was an introduction on some of the call you can use to turn paper in to meaningful data.
Please feel free to contact me with any comments or questions.

The UIM Data

{
    "returnStatus": {
        "status": 200,
        "code": "OK0000",
        "message": "",
        "server": "WS-S02b5e00543ac4ba19198b2ef9f5e93c1HS"
    },
    "licenseUsedPercent": 0,
    "id": "ReqCPEXTRAC_5J4_015911313",
    "serviceName": "classifyextractpage",
    "executionMilliSeconds": 339,
    "licensePagesUsed": 2,
    "licensePagesUsed2": 0,
    "resultItems": [{
        "nodeId": 1,
        "errorCode": "",
        "errorMessage": "",
        "values": [{
            "name": "TemplateId",
            "value": "159"
        },
        {
            "name": "DocumentTypeName",
            "value": "Invoice"
        },
        {
            "name": "DocBoundary",
            "value": 1
        },
        {
            "name": "PaperOrientation",
            "value": 0
        },
        {
            "name": "UimData",
            "value": {
                "docType": "Invoice",
                "locale": "en-GB",
                "flaggedReason": null,
                "packageName": "",
                "nodeList": [{
                    "name": "VendorName",
                    "isArray": false,
                    "indexFieldType": "String",
                    "labelText": "Vendor Name",
                    "isRequired": false,
                    "controlType": "TextBox",
                    "data": [{
                        "arrayIndex": 0,
                        "value": "C.E.B. UK Ltd.",
                        "fieldError": null,
                        "mustConfirm": false,
                        "choices": null,
                        "locationRect": {
                            "left": 386,
                            "top": 42,
                            "width": 235,
                            "height": 47
                        },
                        "pageId": 1,
                        "confidence": 100,
                        "flaggedReason": ""
                    }]
                },
                {
                    "name": "InvoiceNumber",
                    "isArray": false,
                    "indexFieldType": "String",
                    "labelText": "Invoice Number",
                    "isRequired": false,
                    "controlType": "TextBox",
                    "data": [{
                        "arrayIndex": 0,
                        "value": "GH12345",
                        "fieldError": null,
                        "mustConfirm": false,
                        "choices": null,
                        "locationRect": {
                            "left": 542,
                            "top": 247,
                            "width": 97,
                            "height": 11
                        },
                        "pageId": 1,
                        "confidence": 97,
                        "flaggedReason": ""
                    }]
                },
                {
                    "name": "InvoiceDate",
                    "isArray": false,
                    "indexFieldType": "DateTime",
                    "labelText": "Invoice Date",
                    "isRequired": false,
                    "controlType": "TextBox",
                    "data": [{
                        "arrayIndex": 0,
                        "value": "2018-09-29T00:00:00.0000000",
                        "fieldError": null,
                        "mustConfirm": false,
                        "choices": null,
                        "locationRect": {
                            "left": 543,
                            "top": 274,
                            "width": 100,
                            "height": 14
                        },
                        "pageId": 1,
                        "confidence": 99,
                        "flaggedReason": ""
                    }]
                },
                {
                    "name": "OrderNumber",
                    "isArray": false,
                    "indexFieldType": "String",
                    "labelText": "Order Number",
                    "isRequired": false,
                    "controlType": "TextBox",
                    "data": [{
                        "arrayIndex": 0,
                        "value": "4500000873",
                        "fieldError": null,
                        "mustConfirm": false,
                        "choices": null,
                        "locationRect": {
                            "left": 542,
                            "top": 303,
                            "width": 100,
                            "height": 14
                        },
                        "pageId": 1,
                        "confidence": 100,
                        "flaggedReason": ""
                    }]
                },
                {
                    "name": "NetAmount",
                    "isArray": false,
                    "indexFieldType": "Number",
                    "labelText": "Net Amount",
                    "isRequired": false,
                    "controlType": "TextBox",
                    "data": [{
                        "arrayIndex": 0,
                        "value": 1177.00,
                        "fieldError": null,
                        "mustConfirm": false,
                        "choices": null,
                        "locationRect": {
                            "left": 661,
                            "top": 725,
                            "width": 88,
                            "height": 11
                        },
                        "pageId": 1,
                        "confidence": 97,
                        "flaggedReason": ""
                    }]
                },
                {
                    "name": "TAXAmount",
                    "isArray": false,
                    "indexFieldType": "Number",
                    "labelText": "TAX Amount",
                    "isRequired": false,
                    "controlType": "TextBox",
                    "data": [{
                        "arrayIndex": 0,
                        "value": 235.40,
                        "fieldError": null,
                        "mustConfirm": false,
                        "choices": null,
                        "locationRect": {
                            "left": 669,
                            "top": 739,
                            "width": 86,
                            "height": 14
                        },
                        "pageId": 1,
                        "confidence": 100,
                        "flaggedReason": ""
                    }]
                },
                {
                    "name": "TotalAmount",
                    "isArray": false,
                    "indexFieldType": "Number",
                    "labelText": "Total Amount",
                    "isRequired": false,
                    "controlType": "TextBox",
                    "data": [{
                        "arrayIndex": 0,
                        "value": 1412.40,
                        "fieldError": null,
                        "mustConfirm": false,
                        "choices": null,
                        "locationRect": {
                            "left": 661,
                            "top": 758,
                            "width": 88,
                            "height": 11
                        },
                        "pageId": 1,
                        "confidence": 97,
                        "flaggedReason": ""
                    }]
                },
                {
                    "name": "Quantity",
                    "isArray": true,
                    "indexFieldType": "Number",
                    "labelText": "Quantity",
                    "isRequired": false,
                    "controlType": "TextBox",
                    "data": [{
                        "arrayIndex": 0,
                        "value": 2,
                        "fieldError": null,
                        "mustConfirm": false,
                        "choices": null,
                        "locationRect": {
                            "left": 67,
                            "top": 489,
                            "width": 62,
                            "height": 8
                        },
                        "pageId": 1,
                        "confidence": 100,
                        "flaggedReason": ""
                    },
                    {
                        "arrayIndex": 1,
                        "value": 5,
                        "fieldError": null,
                        "mustConfirm": false,
                        "choices": null,
                        "locationRect": {
                            "left": 67,
                            "top": 506,
                            "width": 62,
                            "height": 8
                        },
                        "pageId": 1,
                        "confidence": 100,
                        "flaggedReason": ""
                    },
                    {
                        "arrayIndex": 2,
                        "value": 8,
                        "fieldError": null,
                        "mustConfirm": false,
                        "choices": null,
                        "locationRect": {
                            "left": 67,
                            "top": 523,
                            "width": 62,
                            "height": 8
                        },
                        "pageId": 1,
                        "confidence": 100,
                        "flaggedReason": ""
                    }]
                },
                {
                    "name": "Description",
                    "isArray": true,
                    "indexFieldType": "String",
                    "labelText": "Description",
                    "isRequired": false,
                    "controlType": "TextBox",
                    "data": [{
                        "arrayIndex": 0,
                        "value": "Fingerprint Scanner Pro Sense X-I",
                        "fieldError": null,
                        "mustConfirm": false,
                        "choices": null,
                        "locationRect": {
                            "left": 163,
                            "top": 489,
                            "width": 204,
                            "height": 11
                        },
                        "pageId": 1,
                        "confidence": 100,
                        "flaggedReason": ""
                    },
                    {
                        "arrayIndex": 1,
                        "value": "Notebook WebCam",
                        "fieldError": null,
                        "mustConfirm": false,
                        "choices": null,
                        "locationRect": {
                            "left": 163,
                            "top": 506,
                            "width": 204,
                            "height": 8
                        },
                        "pageId": 1,
                        "confidence": 100,
                        "flaggedReason": ""
                    },
                    {
                        "arrayIndex": 2,
                        "value": "Touchscreen Display (Multi Touch)",
                        "fieldError": null,
                        "mustConfirm": false,
                        "choices": null,
                        "locationRect": {
                            "left": 163,
                            "top": 523,
                            "width": 204,
                            "height": 11
                        },
                        "pageId": 1,
                        "confidence": 100,
                        "flaggedReason": ""
                    }]
                },
                {
                    "name": "UnitPrice",
                    "isArray": true,
                    "indexFieldType": "Number",
                    "labelText": "Unit Price",
                    "isRequired": false,
                    "controlType": "TextBox",
                    "data": [{
                        "arrayIndex": 0,
                        "value": 34.50,
                        "fieldError": null,
                        "mustConfirm": false,
                        "choices": null,
                        "locationRect": {
                            "left": 488,
                            "top": 489,
                            "width": 73,
                            "height": 8
                        },
                        "pageId": 1,
                        "confidence": 100,
                        "flaggedReason": ""
                    },
                    {
                        "arrayIndex": 1,
                        "value": 120.00,
                        "fieldError": null,
                        "mustConfirm": false,
                        "choices": null,
                        "locationRect": {
                            "left": 488,
                            "top": 506,
                            "width": 73,
                            "height": 8
                        },
                        "pageId": 1,
                        "confidence": 99,
                        "flaggedReason": ""
                    },
                    {
                        "arrayIndex": 2,
                        "value": 51.00,
                        "fieldError": null,
                        "mustConfirm": false,
                        "choices": null,
                        "locationRect": {
                            "left": 488,
                            "top": 523,
                            "width": 73,
                            "height": 8
                        },
                        "pageId": 1,
                        "confidence": 99,
                        "flaggedReason": ""
                    }]
                },
                {
                    "name": "LineAmount",
                    "isArray": true,
                    "indexFieldType": "Number",
                    "labelText": "Line Amount",
                    "isRequired": false,
                    "controlType": "TextBox",
                    "data": [{
                        "arrayIndex": 0,
                        "value": 69.00,
                        "fieldError": null,
                        "mustConfirm": false,
                        "choices": null,
                        "locationRect": {
                            "left": 631,
                            "top": 489,
                            "width": 102,
                            "height": 8
                        },
                        "pageId": 1,
                        "confidence": 100,
                        "flaggedReason": ""
                    },
                    {
                        "arrayIndex": 1,
                        "value": 600.00,
                        "fieldError": null,
                        "mustConfirm": false,
                        "choices": null,
                        "locationRect": {
                            "left": 631,
                            "top": 506,
                            "width": 102,
                            "height": 8
                        },
                        "pageId": 1,
                        "confidence": 100,
                        "flaggedReason": ""
                    },
                    {
                        "arrayIndex": 2,
                        "value": 408.00,
                        "fieldError": null,
                        "mustConfirm": false,
                        "choices": null,
                        "locationRect": {
                            "left": 631,
                            "top": 523,
                            "width": 102,
                            "height": 8
                        },
                        "pageId": 1,
                        "confidence": 100,
                        "flaggedReason": ""
                    }]
                }]
            }
        }],
        "files": []
    }]
}

Table of Contents

Your comment

To leave a comment, please sign in.